Compare commits

..

45 Commits

Author SHA1 Message Date
c8100480ac Release 5.5.0-BETA1 2023-08-23 17:09:34 +01:00
8814d06dfd Fix CS 2023-08-23 17:00:18 +01:00
923f7561fb Enchantment: added @see tags to @deprecated methods 2023-08-23 16:53:09 +01:00
f4e1c31dcf Change some weird constant names 2023-08-23 16:52:47 +01:00
998fcf20db Remove useless Cancellable from PressurePlateUpdateEvent 2023-08-23 16:52:22 +01:00
1504fdca24 Use 'enchanting' terminology
'enchant' just didn't feel right, being a verb.
All these things pertain to the act of enchanting.

This is now also consistent with CraftingTransaction etc. The ship already sailed on EnchantInventory, which will have to be renamed at a later datte. However, that was already inconsistent with 'enchanting table', so that's the odd one out here.
2023-08-23 16:14:17 +01:00
bf668c0f6c Rename EnchantHelper related stuff
Perhaps this and EnchantOption should be called EnchantingHelper and EnchantingOption respectively. The terminology used is rather inconsistent, but 'enchantment' definitely isn't the right word here.
2023-08-23 16:07:02 +01:00
d942748203 Move enchanting seed generation to EnchantmentHelper 2023-08-23 15:52:49 +01:00
29fdc8b08d Private constructor for EnchantmentHelper 2023-08-23 15:49:31 +01:00
20a41b00ba StringToTParser: added registerAlias() 2023-08-23 15:24:29 +01:00
df96e023dc Require pocketmine/nbt 1.0.0 2023-08-23 14:42:50 +01:00
f4d5605de1 Use hasHandlers() on more warm-hot events 2023-08-23 14:35:53 +01:00
d03e4d17ec Use hasHandlers() for events in player movement processing pathway
this should offer a minor performance improvement.
2023-08-23 14:26:17 +01:00
cd6abbe0bb BaseSign: remove redundant condition 2023-08-21 16:30:16 +01:00
22778583cf Sign: implement waxing using honeycomb 2023-08-21 16:28:17 +01:00
d44e0e87d0 BaseSign: implement sign editing
this was originally submitted by #6000, but considering the overcomplicated PR and the triviality of the feature, I figured it would be quicker to do it myself instead of having a bunch of back-and-forth bikeshedding over it.
2023-08-21 16:14:43 +01:00
47b448965d Merge branch 'stable' into minor-next 2023-08-21 16:08:27 +01:00
b2414b4c29 EnchantTransaction: cleanup XP cost checking logic 2023-08-18 12:33:07 +01:00
b3c740081e Merge branch 'stable' into minor-next 2023-08-18 12:28:45 +01:00
beaca8bb6d EnchantTransaction: fixed XP level costs when minimum level is less than the XP cost
this can happen and happens in vanilla too. In these cases, as much of the XP cost as possible is deducted.
2023-08-16 14:51:47 +01:00
e323c5dd76 Implement pressure plate activation logic and events (#5991)
closes #5936

This implements all of the basic activation logic for pressure plates.
It also introduces a PressurePlateUpdateEvent, which is called in pulses when entities are standing on top of the plate and when it deactivates. Deactivation can be detected by checking if the list of activating entities is empty.

---------

Co-authored-by: Javier León <58715544+JavierLeon9966@users.noreply.github.com>
2023-08-16 13:00:23 +01:00
f516c3c502 EnchantCommand: ensure that books are turned into enchanted book items 2023-08-15 19:10:48 +01:00
5afbb9d807 Allow enchanted books to be enchanted
if an enchanted book is obtained via /give without enchantments, it should be able to receive enchantments in an enchanting table, exactly the same as regular books.
2023-08-15 19:10:03 +01:00
b330cbe8e2 Merge remote-tracking branch 'origin/stable' into minor-next 2023-08-15 17:41:41 +01:00
39867b97c5 Implement enchanting using enchanting tables (#5953)
Co-authored-by: Dylan K. Taylor <dktapps@pmmp.io>
2023-08-15 17:28:26 +01:00
e48b5b2ec0 GeneratorManager: allow aliasing existing generators 2023-08-10 13:07:03 +01:00
37f2dafae1 PluginBase: make saveResource() use copy() instead of overengineered streams garbage 2023-08-09 16:16:11 +01:00
7826e0a11e Merge branch 'stable' into minor-next 2023-08-09 16:14:05 +01:00
97700636c6 PluginBase: added getResourceFolder() and getResourcePath(), deprecate getResource() (#5961)
This is a step towards #5958.

While it's not actually necessary to add these functions (since people could just use getFile() . "/resources/whatever.yml" instead), this helps preserve the convention of using the `resources` folder, which might be helpful for external tools.

As an example:
stream_get_contents($this->getResource("lang/eng.ini"));
(which is actually incorrect, since it leaks a resource)
can now be replaced by
file_get_contents($this->getResourcePath("lang/eng.ini"));
quite trivially.

getResourceFolder() can be used with scandir() to enumerate resources instead of using getResources(), although getResources() still provides utility in the relativized resource paths.
2023-08-09 16:09:16 +01:00
447f061566 Use Event::hasHandlers() for a few more hot events 2023-08-09 15:46:20 +01:00
a5d8ef7a6c Add FarmlandHydrationChangeEvent (#5916) 2023-08-09 12:33:25 +01:00
59c88fe7f7 Added WorldDifficultyChangeEvent 2023-08-09 12:22:03 +01:00
77dfbc4e23 Implemented pink petals (#5940) 2023-08-09 11:33:33 +01:00
baefbce863 Merge branch 'stable' into minor-next 2023-08-08 18:27:50 +01:00
9f14901820 Merge branch 'stable' into minor-next 2023-08-08 17:48:12 +01:00
515f8eae4c ÂResourcePackManager: allow setting force_resources from a plugin 2023-08-07 17:05:45 +01:00
1cf508abdb World: use Facing::OFFSET in getHighestAdjacentLight() 2023-08-03 16:51:09 +01:00
6ac45526f9 Use new features in pocketmine/math 1.0.0 2023-08-03 16:46:16 +01:00
c91c8c2f9e Improving performance of small moving entities (e.g. dropped items) (#5954)
* World: cache block AABBs directly in the world
this removes some indirection when fetching the AABBs, and also allows the AABB cache to live longer than the block cache.

In local testing this showed a 10-20% performance improvement, but it was difficult to properly measure.

* World: eliminate padding block checks in getCollisionBoxes()
this substantially improves the function's performance for small entities.

The padding of 1 block in each direction was previously necessary to account for blocks like fences, which might have an AABB larger than the cell containing them. However, by tracking this information in the collisionBoxCache directly, we can avoid the need to check this at the expense of slightly more complex code. This reduces the number of blocks checked for a moving item entity from 27-64 all the way down to 1-8, which is a major improvement.

Locally, this change allowed me to simulate 2100 item entities without lag, compared with 1500 on the previous commit.
2023-08-03 14:51:51 +01:00
82f87cc2da Reduce repeated block-change-event related code
the new helper code reveals even more repetition, but this is at least consistent now.
2023-08-02 13:40:12 +01:00
6000bcccdd Merge pull request #5707 from pmmp/hot-events-optimisation
Avoid unnecessary event-related work in hot paths when the events have no registered handlers
2023-08-01 18:19:10 +01:00
0b86fafafb Hot path optimisation for DataPacketSendEvent 2023-08-01 17:41:53 +01:00
2608637210 HandlerListManager: track RegisteredListenerCache directly
This change improves the performance of calling an event with 0 handlers by about 10% with no other changes.

Since we have to access the list eventually anyway, we can cut out some unnecessary work by returning the handlers from the cache directly, instead of fetching the HandlerList for no reason.

This also improves the performance of Event::hasHandlers() by about 40%, which is pretty significant (120 ns -> 80 ns).
2023-08-01 17:37:49 +01:00
442d65143d Merge branch 'minor-next' into hot-events-optimisation 2023-08-01 17:01:52 +01:00
0629d11e13 Avoid unnecessary events work in handleDataPacket if the events have no registered handlers
this particular optimisation became possible thanks to changes in 4.19.

I observed that the allocation of Event objects and calling ->call() was costing us a significant percentage of the time taken in PlayerAuthInputPacket handlers. This change produces a measurable 2 microsecond reduction in overhead for PlayerAuthInputPacket handling when players are not moving (10.7 -> 8.7 microseconds). On a server with 200 players, this translates into a 1% reduction in CPU load for PlayerAuthInputPacket alone. It will also benefit other packets, but not to the extent that PlayerAuthInputPacket benefits.
2023-04-16 20:51:55 +01:00
107 changed files with 3314 additions and 589 deletions

View File

@ -28,7 +28,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Clone pmmp/PocketMine-Docker repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
repository: pmmp/PocketMine-Docker
fetch-depth: 1

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.25.5

View File

@ -15,7 +15,7 @@ jobs:
php-version: [8.1]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
submodules: true
@ -86,7 +86,7 @@ jobs:
${{ github.workspace }}/build_info.json
- name: Create draft release
uses: ncipollo/release-action@v1.13.0
uses: ncipollo/release-action@v1.12.0
with:
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json
commit: ${{ github.sha }}

View File

@ -17,7 +17,7 @@ jobs:
php: ["8.1", "8.2"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@2.0.0
@ -52,7 +52,7 @@ jobs:
php: ["8.1", "8.2"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@2.0.0
@ -87,7 +87,7 @@ jobs:
php: ["8.1", "8.2"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
submodules: true
@ -124,7 +124,7 @@ jobs:
php: ["8.1", "8.2"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@2.0.0
@ -170,7 +170,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.25.5

View File

@ -14,7 +14,7 @@ jobs:
- name: Install jq
run: sudo apt update && sudo apt install jq -y
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
repository: ${{ github.repository_owner }}/update.pmmp.io
ssh-key: ${{ secrets.UPDATE_PMMP_IO_DEPLOY_KEY }}

View File

@ -60,9 +60,3 @@ Released 9th August 2023.
- Fixed `PluginBase->saveResource()` leaking file resources when the data file already exists in the plugin's data folder. This bug existed since 2014 and was only discovered recently.
- Fixed coral blocks becoming dead after calling `getDropsForCompatibleTool()` on them.
- Fixed `BlockDeathEvent->getOldState()` returning a block which is already dead.
# 4.23.6
Released 21st August 2023.
## Fixes
- Added a workaround for armor and other inventories not working correctly after inventory sync. This is caused by a client bug.

View File

@ -107,27 +107,3 @@ Released 9th August 2023.
## Fixes
- Fixed cake accepting candle placement when slices have already been eaten.
- Fixed fire charges not lighting candles.
# 5.4.3
Released 21st August 2023.
## Included releases
- [4.23.6](https://github.com/pmmp/PocketMine-MP/blob/4.23.6/changelogs/4.23.md#4236) - Armor inventory client bug workaround
## Fixes
- Fixed crashdumps not generating correctly on fatal errors.
- Fixed `PotionCauldron::setPotionItem()` not validating the item type.
- Fixed chorus fruit not considering teleport destinations below y=0.
- Fixed cake dropping itself when mined.
# 5.4.4
Released 6th September 2023.
## General
- Crashdumps caused by non-phar plugins are now submitted to the Crash Archive, the same as other plugins. Previously, non-phar plugin crashes would not be submitted, causing maintainers to potentially miss important issues.
## Fixes
- Fixed player Y coordinates sometimes being slightly below the top of the block they were standing on (floating point error due to subtracting eye height).
- Fixed template slot of smithing tables not accepting any items.
- `tools/generate-bedrock-data-from-packets.php` is now significantly less spammy when warning about duplicated recipes.
- Fixed empty stack traces in `lastError` data of crashdumps.

156
changelogs/5.5-beta.md Normal file
View File

@ -0,0 +1,156 @@
# 5.5.0-BETA1
Released 23rd August 2023.
**For Minecraft: Bedrock Edition 1.20.0**
This is a minor feature release, including performance improvements, new API methods, and new gameplay features.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## Dependencies
- Updated `pocketmine/math` dependency to [`1.0.0`](https://github.com/pmmp/Math/releases/tag/1.0.0).
- Updated `pocketmine/nbt` dependency to [`1.0.0`](https://github.com/pmmp/NBT/releases/tag/1.0.0).
## Performance
- Some events are now no longer fired if no handlers are registered.
- This improves performance by avoiding unnecessary object allocations and function calls.
- Events such as `DataPacketReceiveEvent`, `DataPacketSendEvent` and `PlayerMoveEvent` are optimized away almost completely by this change, offering some much-needed performance gains.
- Significantly improved performance of small moving entities, such as dropped items.
- This was achieved by a combination of changes, which together improved observed performance with 2000 item entities moving in water by 30-40%.
- The benefit of this will be most noticeable in SkyBlock servers, where large cactus farms can generate thousands of dropped items.
- `World->getCollisionBoxes()` now uses an improved search method, which reduces the work done by the function by almost 90% for small entities.
- This improves performance of collision detection for small entities, such as dropped items.
## Gameplay
### General
- Implemented enchanting using an enchanting table (yes, finally!)
- Thanks to [@S3v3Nice](https://github.com/S3v3Nice) for investing lots of time and effort into developing this.
- Since this feature is quite complex, it's possible there may be bugs. Please be vigilant and report any issues you find.
### Blocks
- The following new blocks have been implemented:
- Pink Petals
- Pressure plates are now functional, in the sense that they react when entities stand on them and perform the correct logic.
- Note that since redstone is not yet implemented, pressure plates do not activate any redstone devices, similar to buttons and levers.
- Signs can now be edited by right-clicking them.
- Signs can now be waxed using a honeycomb, which prevents them from being edited.
### Items
- The following new items have been implemented:
- Enchanted Book
## API
### `pocketmine\block`
- The following new API methods have been added:
- `public Block->getEnchantmentTags() : list<string>` returns a list of strings indicating which types of enchantment can be applied to the block when in item form
- `public BlockTypeInfo->getEnchantmentTags() : list<string>`
- `protected PressurePlate->getActivationBox() : AxisAlignedBB` - returns the AABB entities must intersect with in order to activate the pressure plate (not the same as the visual shape)
- `protected PressurePlate->hasOutputSignal() : bool` - returns whether the pressure plate has an output signal - this should be implemented by subclasses
- `protected PressurePlate->calculatePlateState() : array{Block, ?bool}` - returns the state the pressure plate will change to if the given list of entities are standing on it, and a bool indicating whether the plate activated or deactivated this tick
- `protected PressurePlate->filterIrrelevantEntities(list<Entity> $entities) : list<Entity>` - returns the given list filtered of entities that don't affect the plate's state (e.g. dropped items don't affect stone pressure plates)
- `public BaseSign->isWaxed() : bool`
- `public BaseSign->setWaxed(bool $waxed) : $this`
- `public inventory\EnchantInventory->getInput() : Item`
- `public inventory\EnchantInventory->getLapis() : Item`
- `public inventory\EnchantInventory->getOutput(int $optionId) : ?Item` - returns the item that would be produced if the input item was enchanted with the selected option, or `null` if the option is invalid
- `public inventory\EnchantInventory->getOption(int $optionId) : EnchantOption` - returns the enchanting option at the given index
- The following API methods have signature changes:
- `BlockTypeInfo->__construct()` now accepts an optional `list<string> $enchantmentTags` parameter
- `PressurePlate->__construct()` now accepts an optional `int $deactivationDelayTicks` parameter
- `WeightedPressurePlate->__construct()` now accepts optional `int $deactivationDelayTicks` and `float $signalStrengthFactor` parameters
- `SimplePressurePlate->__construct()` now accepts an optional `int $deactivationDelayTicks` parameter
- The following new classes have been added:
- `PinkPetals`
- `utils\BlockEventHelper` - provides helper methods for calling block-related events
- The following classes have been deprecated:
- `WeightedPressurePlateLight`
- `WeightedPressurePlateHeavy`
### `pocketmine\entity`
- The following new API methods have been added:
- `public Human->getEnchantmentSeed() : int` - returns the current seed used to randomize options shown on the enchanting table for this human
- `public Human->setEnchantmentSeed(int $seed) : void`
- `public Human->regenerateEnchantmentSeed() : void` - returns a new randomly generated seed which can be set with `setEnchantmentSeed()`
### `pocketmine\event`
- The following new classes have been added:
- `block\FarmlandHydrationChangeEvent` - called when farmland is hydrated or dehydrated
- `block\PressurePlateUpdateEvent` - called when a pressure plate is activated or changes its power output
- `player\PlayerEnchantingOptionsRequestEvent` - called when a player puts an item to be enchanted into an enchanting table, to allow plugins to modify the enchanting options shown
- `player\PlayerItemEnchantEvent` - called when a player enchants an item in an enchanting table
- `world\WorldDifficultyChangeEvent` - called when a world's difficulty is changed
- The following new API methods have been added:
- `public static Event::hasHandlers() : bool` - returns whether the event class has any registered handlers - used like `SomeEvent::hasHandlers()`
- `public HandlerListManager->getHandlersFor(class-string<? extends Event> $event) : list<RegisteredListener>` - returns a list of all registered listeners for the given event class, using cache if available
### `pocketmine\inventory\transaction`
- The following new classes have been added:
- `EnchantingTransaction` - used when a player enchants an item in an enchanting table
### `pocketmine\item`
- The following new API methods have been added:
- `public Armor->getMaterial() : ArmorMaterial` - returns an object containing properties shared by all items of the same armor material
- `public ArmorTypeInfo->getMaterial() : ArmorMaterial`
- `public Item->getEnchantability() : int` - returns the enchantability value of the item - higher values increase the chance of more powerful enchantments being offered by an enchanting table
- `public Item->getEnchantmentTags() : list<string>` - returns a list of strings indicating which types of enchantment can be applied to the item
- `public ToolTier->getEnchantability() : int`
- The following API methods have signature changes:
- `Item->__construct()` now accepts an optional `list<string> $enchantmentTags` parameter
- `ArmorTypeInfo->__construct()` now accepts an optional `?ArmorMaterial $material` parameter
- The following new classes have been added:
- `ArmorMaterial` - container for shared armor properties
- `VanillaArmorMaterials` - all vanilla armor materials
- `EnchantedBook` - represents an enchanted book item
### `pocketmine\item\enchantment`
- The following new classes have been added:
- `AvailableEnchantmentRegistry` - enchantments to be displayed on the enchanting table are selected from here - custom enchantments may be added
- `EnchantingHelper` - static class containing various helper methods for enchanting tables
- `EnchantingOption` - represents an option on the enchanting table menu
- `IncompatibleEnchantmentGroups` - list of constants naming groups of enchantments that are incompatible with each other - custom enchantments may be added using these group names to make them incompatible with existing enchantments in the same group
- `IncompatibleEnchantmentRegistry` - manages which enchantments are considered incompatible with each other - custom enchantments may be added using existing group names to make them incompatible with existing enchantments in the same group, or to entirely new groups
- `ItemEnchantmentTagRegistry` - manages item enchantment compatibility tags and which tags include which other tags
- `ItemEnchantmentTags` - list of constants naming item types for enchantment compatibility checks
- The following classes have been deprecated
- `ItemFlags`
- The following API methods have been added:
- `public Enchantment->isCompatibleWith(Enchantment $other) : bool`
- `public Enchantment->getMinEnchantingPower()` - returns the minimum enchanting power (derived from enchantability and number of bookshelves) needed to allow this enchantment to show on the enchanting table with a given level
- `public Enchantment->getMaxEnchantingPower()` - upper limit of enchanting power for this enchantment to be offered on the enchanting table with a given level
- The following API methods have signature changes:
- `Enchantment->__construct()` now accepts optional `(\Closure(int $level) : int)|null $minEnchantingPower` and `int $enchantingPowerRange` parameters
- `Enchantment->__construct()` parameters `$primaryItemFlags` and `$secondaryItemFlags` are now deprecated and no longer used
- `ProtectionEnchantment->__construct()` has extra parameters to reflect `Enchantment->__construct()` changes
- The following API methods have been deprecated:
- `Enchantment->getPrimaryItemFlags()` - use API methods provided by `AvailableEnchantmentRegistry` instead
- `Enchantment->getSecondaryItemFlags()` - use API methods provided by `AvailableEnchantmentRegistry` instead
- `Enchantment->hasPrimaryItemType()`
- `Enchantment->hasSecondaryItemType()`
### `pocketmine\plugin`
- The following new API methods have been added:
- `public PluginBase->getResourcePath(string $filename) : string` - returns a URI to an embedded resource file that can be used with `file_get_contents()` and similar functions
- `public PluginBase->getResourceFolder() : string` - returns a URI to the plugin's folder of embedded resources
- The following API methods have been deprecated:
- `PluginBase->getResource()` - prefer using `getResourcePath()` with `file_get_contents()` or other PHP built-in functions instead
### `pocketmine\resourcepacks`
- The following new API methods have been added:
- `public ResourcePackManager->setResourcePacksRequired(bool $value) : void` - sets whether players must accept resource packs in order to join
### `pocketmine\world\generator`
- The following new API methods have been added:
- `public GeneratorManager->addAlias(string $name, string $alias) : void` - allows registering a generator alias without copying the generator registration parameters
### `pocketmine\world\sound`
- The following new classes have been added:
- `PressurePlateActivateSound`
- `PressurePlateDeactivateSound`
### `pocketmine\utils`
- The following new API methods have been added:
- `public StringToTParser->registerAlias(string $existing, string $alias) : void` - allows registering a string alias without copying registration parameters

View File

@ -43,8 +43,8 @@
"pocketmine/errorhandler": "^0.6.0",
"pocketmine/locale-data": "~2.19.0",
"pocketmine/log": "^0.4.0",
"pocketmine/math": "^0.4.0",
"pocketmine/nbt": "^0.3.2",
"pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.0.0",
"pocketmine/raklib": "^0.15.0",
"pocketmine/raklib-ipc": "^0.2.0",
"pocketmine/snooze": "^0.5.0",

163
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "27b30e0ed071ba0e6733545a695c3586",
"content-hash": "9237955fd97ba7c1697d80314fa9ad6f",
"packages": [
{
"name": "adhocore/json-comment",
@ -200,16 +200,16 @@
},
{
"name": "pocketmine/bedrock-protocol",
"version": "23.0.4+bedrock-1.20.10",
"version": "23.0.3+bedrock-1.20.10",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "ae0d8f4d49506674b7ff622f7c81ce241dc49adb"
"reference": "e4157c7af3f91e1b08fe21be171eb73dad7029e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/ae0d8f4d49506674b7ff622f7c81ce241dc49adb",
"reference": "ae0d8f4d49506674b7ff622f7c81ce241dc49adb",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/e4157c7af3f91e1b08fe21be171eb73dad7029e9",
"reference": "e4157c7af3f91e1b08fe21be171eb73dad7029e9",
"shasum": ""
},
"require": {
@ -223,7 +223,7 @@
"ramsey/uuid": "^4.1"
},
"require-dev": {
"phpstan/phpstan": "1.10.33",
"phpstan/phpstan": "1.10.7",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5"
@ -241,9 +241,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/23.0.4+bedrock-1.20.10"
"source": "https://github.com/pmmp/BedrockProtocol/tree/23.0.3+bedrock-1.20.10"
},
"time": "2023-09-06T07:36:44+00:00"
"time": "2023-08-03T15:30:52+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -480,16 +480,16 @@
},
{
"name": "pocketmine/math",
"version": "0.4.3",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Math.git",
"reference": "47a243d320b01c8099d65309967934c188111549"
"reference": "dc132d93595b32e9f210d78b3c8d43c662a5edbf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Math/zipball/47a243d320b01c8099d65309967934c188111549",
"reference": "47a243d320b01c8099d65309967934c188111549",
"url": "https://api.github.com/repos/pmmp/Math/zipball/dc132d93595b32e9f210d78b3c8d43c662a5edbf",
"reference": "dc132d93595b32e9f210d78b3c8d43c662a5edbf",
"shasum": ""
},
"require": {
@ -498,7 +498,7 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "1.8.2",
"phpstan/phpstan": "~1.10.3",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.5"
},
@ -515,22 +515,22 @@
"description": "PHP library containing math related code used in PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Math/issues",
"source": "https://github.com/pmmp/Math/tree/0.4.3"
"source": "https://github.com/pmmp/Math/tree/1.0.0"
},
"time": "2022-08-25T18:43:37+00:00"
"time": "2023-08-03T12:56:33+00:00"
},
{
"name": "pocketmine/nbt",
"version": "0.3.4",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "62c02464c6708b2467c1e1a2af01af09d5114eda"
"reference": "20540271cb59e04672cb163dca73366f207974f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/62c02464c6708b2467c1e1a2af01af09d5114eda",
"reference": "62c02464c6708b2467c1e1a2af01af09d5114eda",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/20540271cb59e04672cb163dca73366f207974f1",
"reference": "20540271cb59e04672cb163dca73366f207974f1",
"shasum": ""
},
"require": {
@ -540,7 +540,7 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "1.10.3",
"phpstan/phpstan": "1.10.25",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5"
},
@ -557,9 +557,9 @@
"description": "PHP library for working with Named Binary Tags",
"support": {
"issues": "https://github.com/pmmp/NBT/issues",
"source": "https://github.com/pmmp/NBT/tree/0.3.4"
"source": "https://github.com/pmmp/NBT/tree/1.0.0"
},
"time": "2023-04-10T11:31:20+00:00"
"time": "2023-07-14T13:01:49+00:00"
},
{
"name": "pocketmine/netresearch-jsonmapper",
@ -985,16 +985,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.28.0",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
"shasum": ""
},
"require": {
@ -1009,7 +1009,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -1047,7 +1047,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
},
"funding": [
{
@ -1063,20 +1063,20 @@
"type": "tidelift"
}
],
"time": "2023-01-26T09:26:14+00:00"
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.28.0",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "42292d99c55abe617799667f454222c54c60e229"
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "42292d99c55abe617799667f454222c54c60e229",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"shasum": ""
},
"require": {
@ -1091,7 +1091,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -1130,7 +1130,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
},
"funding": [
{
@ -1146,7 +1146,7 @@
"type": "tidelift"
}
],
"time": "2023-07-28T09:04:16+00:00"
"time": "2022-11-03T14:55:06+00:00"
}
],
"packages-dev": [
@ -1440,16 +1440,16 @@
},
{
"name": "phpstan/phpstan-phpunit",
"version": "1.3.14",
"version": "1.3.13",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "614acc10c522e319639bf38b0698a4a566665f04"
"reference": "d8bdab0218c5eb0964338d24a8511b65e9c94fa5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/614acc10c522e319639bf38b0698a4a566665f04",
"reference": "614acc10c522e319639bf38b0698a4a566665f04",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d8bdab0218c5eb0964338d24a8511b65e9c94fa5",
"reference": "d8bdab0218c5eb0964338d24a8511b65e9c94fa5",
"shasum": ""
},
"require": {
@ -1462,7 +1462,7 @@
"require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-strict-rules": "^1.5.1",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5"
},
"type": "phpstan-extension",
@ -1486,9 +1486,9 @@
"description": "PHPUnit extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.14"
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.13"
},
"time": "2023-08-25T09:46:39+00:00"
"time": "2023-05-26T11:05:59+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
@ -1541,16 +1541,16 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "10.1.4",
"version": "10.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "cd59bb34756a16ca8253ce9b2909039c227fff71"
"reference": "be1fe461fdc917de2a29a452ccf2657d325b443d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cd59bb34756a16ca8253ce9b2909039c227fff71",
"reference": "cd59bb34756a16ca8253ce9b2909039c227fff71",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/be1fe461fdc917de2a29a452ccf2657d325b443d",
"reference": "be1fe461fdc917de2a29a452ccf2657d325b443d",
"shasum": ""
},
"require": {
@ -1607,7 +1607,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.4"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.3"
},
"funding": [
{
@ -1615,20 +1615,20 @@
"type": "github"
}
],
"time": "2023-08-31T14:04:38+00:00"
"time": "2023-07-26T13:45:28+00:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "4.1.0",
"version": "4.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c"
"reference": "5647d65443818959172645e7ed999217360654b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c",
"reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/5647d65443818959172645e7ed999217360654b6",
"reference": "5647d65443818959172645e7ed999217360654b6",
"shasum": ""
},
"require": {
@ -1668,7 +1668,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0"
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.0.2"
},
"funding": [
{
@ -1676,7 +1676,7 @@
"type": "github"
}
],
"time": "2023-08-31T06:24:48+00:00"
"time": "2023-05-07T09:13:23+00:00"
},
{
"name": "phpunit/php-invoker",
@ -1743,16 +1743,16 @@
},
{
"name": "phpunit/php-text-template",
"version": "3.0.1",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748"
"reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748",
"reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/9f3d3709577a527025f55bcf0f7ab8052c8bb37d",
"reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d",
"shasum": ""
},
"require": {
@ -1790,8 +1790,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
"security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
"source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1"
"source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.0"
},
"funding": [
{
@ -1799,7 +1798,7 @@
"type": "github"
}
],
"time": "2023-08-31T14:07:24+00:00"
"time": "2023-02-03T06:56:46+00:00"
},
{
"name": "phpunit/php-timer",
@ -1862,16 +1861,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.3.3",
"version": "10.3.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "241ed4dd0db1c096984e62d414c4e1ac8d5dbff4"
"reference": "0dafb1175c366dd274eaa9a625e914451506bcd1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/241ed4dd0db1c096984e62d414c4e1ac8d5dbff4",
"reference": "241ed4dd0db1c096984e62d414c4e1ac8d5dbff4",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0dafb1175c366dd274eaa9a625e914451506bcd1",
"reference": "0dafb1175c366dd274eaa9a625e914451506bcd1",
"shasum": ""
},
"require": {
@ -1943,7 +1942,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.3"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.2"
},
"funding": [
{
@ -1959,7 +1958,7 @@
"type": "tidelift"
}
],
"time": "2023-09-05T04:34:51+00:00"
"time": "2023-08-15T05:34:23+00:00"
},
{
"name": "sebastian/cli-parser",
@ -2207,16 +2206,16 @@
},
{
"name": "sebastian/complexity",
"version": "3.0.1",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "c70b73893e10757af9c6a48929fa6a333b56a97a"
"reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c70b73893e10757af9c6a48929fa6a333b56a97a",
"reference": "c70b73893e10757af9c6a48929fa6a333b56a97a",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/e67d240970c9dc7ea7b2123a6d520e334dd61dc6",
"reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6",
"shasum": ""
},
"require": {
@ -2252,8 +2251,7 @@
"homepage": "https://github.com/sebastianbergmann/complexity",
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
"security": "https://github.com/sebastianbergmann/complexity/security/policy",
"source": "https://github.com/sebastianbergmann/complexity/tree/3.0.1"
"source": "https://github.com/sebastianbergmann/complexity/tree/3.0.0"
},
"funding": [
{
@ -2261,7 +2259,7 @@
"type": "github"
}
],
"time": "2023-08-31T09:55:53+00:00"
"time": "2023-02-03T06:59:47+00:00"
},
{
"name": "sebastian/diff",
@ -2535,16 +2533,16 @@
},
{
"name": "sebastian/lines-of-code",
"version": "2.0.1",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d"
"reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/649e40d279e243d985aa8fb6e74dd5bb28dc185d",
"reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/17c4d940ecafb3d15d2cf916f4108f664e28b130",
"reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130",
"shasum": ""
},
"require": {
@ -2580,8 +2578,7 @@
"homepage": "https://github.com/sebastianbergmann/lines-of-code",
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.1"
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.0"
},
"funding": [
{
@ -2589,7 +2586,7 @@
"type": "github"
}
],
"time": "2023-08-31T09:25:50+00:00"
"time": "2023-02-03T07:08:02+00:00"
},
{
"name": "sebastian/object-enumerator",
@ -2963,5 +2960,5 @@
"platform-overrides": {
"php": "8.1.0"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}

View File

@ -80,6 +80,7 @@ use pocketmine\player\PlayerDataProvider;
use pocketmine\player\PlayerDataSaveException;
use pocketmine\player\PlayerInfo;
use pocketmine\plugin\PharPluginLoader;
use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginEnableOrder;
use pocketmine\plugin\PluginGraylist;
use pocketmine\plugin\PluginManager;
@ -1605,6 +1606,15 @@ class Server{
}
@touch($stamp); //update file timestamp
$plugin = $dump->getData()->plugin;
if($plugin !== ""){
$p = $this->pluginManager->getPlugin($plugin);
if($p instanceof Plugin && !($p->getPluginLoader() instanceof PharPluginLoader)){
$this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
$report = false;
}
}
if($dump->getData()->error["type"] === \ParseError::class){
$report = false;
}

View File

@ -31,9 +31,9 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.4.4";
public const BASE_VERSION = "5.5.0-BETA1";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "stable";
public const BUILD_CHANNEL = "beta";
/**
* PocketMine-MP-specific version ID for world data. Used to determine what fixes need to be applied to old world

View File

@ -23,10 +23,10 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\event\block\BlockDeathEvent;
use pocketmine\item\Item;
use function mt_rand;
@ -46,11 +46,7 @@ abstract class BaseCoral extends Transparent{
public function onScheduledUpdate() : void{
if(!$this->dead && !$this->isCoveredWithWater()){
$ev = new BlockDeathEvent($this, (clone $this)->setDead(true));
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::die($this, (clone $this)->setDead(true));
}
}

View File

@ -49,6 +49,8 @@ abstract class BaseSign extends Transparent{
use WoodTypeTrait;
protected SignText $text;
private bool $waxed = false;
protected ?int $editorEntityRuntimeId = null;
/** @var \Closure() : Item */
@ -69,6 +71,7 @@ abstract class BaseSign extends Transparent{
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileSign){
$this->text = $tile->getText();
$this->waxed = $tile->isWaxed();
$this->editorEntityRuntimeId = $tile->getEditorEntityRuntimeId();
}
@ -80,6 +83,7 @@ abstract class BaseSign extends Transparent{
$tile = $this->position->getWorld()->getTile($this->position);
assert($tile instanceof TileSign);
$tile->setText($this->text);
$tile->setWaxed($this->waxed);
$tile->setEditorEntityRuntimeId($this->editorEntityRuntimeId);
}
@ -147,10 +151,26 @@ abstract class BaseSign extends Transparent{
return false;
}
private function wax(Player $player, Item $item) : bool{
if($this->waxed){
return false;
}
$this->waxed = true;
$this->position->getWorld()->setBlock($this->position, $this);
$item->pop();
return true;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($player === null){
return false;
}
if($this->waxed){
return true;
}
$dyeColor = $item instanceof Dye ? $item->getColor() : match($item->getTypeId()){
ItemTypeIds::BONE_MEAL => DyeColor::WHITE(),
ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE(),
@ -159,20 +179,25 @@ abstract class BaseSign extends Transparent{
};
if($dyeColor !== null){
$color = $dyeColor->equals(DyeColor::BLACK()) ? new Color(0, 0, 0) : $dyeColor->getRgbValue();
if($color->toARGB() === $this->text->getBaseColor()->toARGB()){
return false;
}
if($this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item)){
if(
$color->toARGB() !== $this->text->getBaseColor()->toARGB() &&
$this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item)
){
$this->position->getWorld()->addSound($this->position, new DyeUseSound());
return true;
}
}elseif($item->getTypeId() === ItemTypeIds::INK_SAC){
return $this->changeSignGlowingState(false, $player, $item);
}elseif($item->getTypeId() === ItemTypeIds::GLOW_INK_SAC){
return $this->changeSignGlowingState(true, $player, $item);
}elseif(match($item->getTypeId()){
ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item),
ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item),
ItemTypeIds::HONEYCOMB => $this->wax($player, $item),
default => false
}){
return true;
}
return false;
$player->openSignEditor($this->position);
return true;
}
/**
@ -188,6 +213,17 @@ abstract class BaseSign extends Transparent{
return $this;
}
/**
* Returns whether the sign has been waxed using a honeycomb. If true, the sign cannot be edited by a player.
*/
public function isWaxed() : bool{ return $this->waxed; }
/** @return $this */
public function setWaxed(bool $waxed) : self{
$this->waxed = $waxed;
return $this;
}
/**
* Sets the runtime entity ID of the player editing this sign. Only this player will be able to edit the sign.
* This is used to prevent multiple players from editing the same sign at the same time, and to prevent players
@ -217,8 +253,8 @@ abstract class BaseSign extends Transparent{
}
$ev = new SignChangeEvent($this, $author, new SignText(array_map(function(string $line) : string{
return TextFormat::clean($line, false);
}, $text->getLines())));
if($this->editorEntityRuntimeId === null || $this->editorEntityRuntimeId !== $author->getId()){
}, $text->getLines()), $this->text->getBaseColor(), $this->text->isGlowing()));
if($this->waxed || $this->editorEntityRuntimeId !== $author->getId()){
$ev->cancel();
}
$ev->call();

View File

@ -36,6 +36,9 @@ use pocketmine\data\runtime\RuntimeDataSizeCalculator;
use pocketmine\data\runtime\RuntimeDataWriter;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Projectile;
use pocketmine\item\enchantment\AvailableEnchantmentRegistry;
use pocketmine\item\enchantment\ItemEnchantmentTagRegistry;
use pocketmine\item\enchantment\ItemEnchantmentTags;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\ItemBlock;
@ -422,6 +425,19 @@ class Block{
return $this->typeInfo->getBreakInfo();
}
/**
* Returns tags that represent the type of item being enchanted and are used to determine
* what enchantments can be applied to the item of this block during in-game enchanting (enchanting table, anvil, fishing, etc.).
* @see ItemEnchantmentTags
* @see ItemEnchantmentTagRegistry
* @see AvailableEnchantmentRegistry
*
* @return string[]
*/
public function getEnchantmentTags() : array{
return $this->typeInfo->getEnchantmentTags();
}
/**
* Do the actions needed so the block is broken with the Item
*

View File

@ -736,8 +736,9 @@ final class BlockTypeIds{
public const SMALL_DRIPLEAF = 10706;
public const BIG_DRIPLEAF_HEAD = 10707;
public const BIG_DRIPLEAF_STEM = 10708;
public const PINK_PETALS = 10709;
public const FIRST_UNUSED_BLOCK_ID = 10709;
public const FIRST_UNUSED_BLOCK_ID = 10710;
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;

View File

@ -35,10 +35,12 @@ final class BlockTypeInfo{
/**
* @param string[] $typeTags
* @param string[] $enchantmentTags
*/
public function __construct(
private BlockBreakInfo $breakInfo,
array $typeTags = []
array $typeTags = [],
private array $enchantmentTags = []
){
$this->typeTags = array_fill_keys($typeTags, true);
}
@ -49,4 +51,17 @@ final class BlockTypeInfo{
public function getTypeTags() : array{ return array_keys($this->typeTags); }
public function hasTypeTag(string $tag) : bool{ return isset($this->typeTags[$tag]); }
/**
* Returns tags that represent the type of item being enchanted and are used to determine
* what enchantments can be applied to the item of this block during in-game enchanting (enchanting table, anvil, fishing, etc.).
* @see ItemEnchantmentTags
* @see ItemEnchantmentTagRegistry
* @see AvailableEnchantmentRegistry
*
* @return string[]
*/
public function getEnchantmentTags() : array{
return $this->enchantmentTags;
}
}

View File

@ -23,10 +23,10 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
@ -111,12 +111,7 @@ class Cactus extends Transparent{
}
$b = $world->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
if($b->getTypeId() === BlockTypeIds::AIR){
$ev = new BlockGrowEvent($b, VanillaBlocks::CACTUS());
$ev->call();
if($ev->isCancelled()){
break;
}
$world->setBlock($b->position, $ev->getNewState());
BlockEventHelper::grow($b, VanillaBlocks::CACTUS(), null);
}else{
break;
}

View File

@ -23,10 +23,10 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
@ -114,16 +114,12 @@ class CaveVines extends Flowable{
return true;
}
if($item instanceof Fertilizer){
$ev = new BlockGrowEvent($this, (clone $this)
$newState = (clone $this)
->setBerries(true)
->setHead(!$this->getSide(Facing::DOWN)->hasSameTypeId($this))
);
$ev->call();
if($ev->isCancelled()){
return false;
->setHead(!$this->getSide(Facing::DOWN)->hasSameTypeId($this));
if(BlockEventHelper::grow($this, $newState, $player)){
$item->pop();
}
$item->pop();
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
return true;
}
return false;
@ -141,16 +137,10 @@ class CaveVines extends Flowable{
if($world->isInWorld($growthPos->getFloorX(), $growthPos->getFloorY(), $growthPos->getFloorZ())){
$block = $world->getBlock($growthPos);
if($block->getTypeId() === BlockTypeIds::AIR){
$ev = new BlockGrowEvent($block, VanillaBlocks::CAVE_VINES()
$newState = VanillaBlocks::CAVE_VINES()
->setAge($this->age + 1)
->setBerries(mt_rand(1, 9) === 1)
);
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($growthPos, $ev->getNewState());
}
->setBerries(mt_rand(1, 9) === 1);
BlockEventHelper::grow($block, $newState, null);
}
}
}

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\block\utils\WoodType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
@ -123,12 +123,7 @@ class CocoaBlock extends Transparent{
if($this->age < self::MAX_AGE){
$block = clone $this;
$block->age++;
$ev = new BlockGrowEvent($this, $block, $player);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
return true;
}
return BlockEventHelper::grow($this, $block, $player);
}
return false;
}

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
use pocketmine\event\block\BlockFormEvent;
use pocketmine\math\Facing;
class ConcretePowder extends Opaque implements Fallable{
@ -43,11 +43,7 @@ class ConcretePowder extends Opaque implements Fallable{
public function onNearbyBlockChange() : void{
if(($water = $this->getAdjacentWater()) !== null){
$ev = new BlockFormEvent($this, VanillaBlocks::CONCRETE()->setColor($this->color), $water);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::form($this, VanillaBlocks::CONCRETE()->setColor($this->color), $water);
}else{
$this->startFalling();
}

View File

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\event\block\BlockDeathEvent;
use pocketmine\item\Item;
use function mt_rand;
@ -55,11 +55,7 @@ final class CoralBlock extends Opaque{
}
}
if(!$hasWater){
$ev = new BlockDeathEvent($this, (clone $this)->setDead(true));
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::die($this, (clone $this)->setDead(true));
}
}
}

View File

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Facing;
@ -68,11 +68,7 @@ abstract class Crops extends Flowable{
if($block->age > self::MAX_AGE){
$block->age = self::MAX_AGE;
}
$ev = new BlockGrowEvent($this, $block, $player);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
if(BlockEventHelper::grow($this, $block, $player)){
$item->pop();
}
@ -96,11 +92,7 @@ abstract class Crops extends Flowable{
if($this->age < self::MAX_AGE && mt_rand(0, 2) === 1){
$block = clone $this;
++$block->age;
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::grow($this, $block, null);
}
}
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\block;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\event\block\FarmlandHydrationChangeEvent;
use pocketmine\event\entity\EntityTrampleFarmlandEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
@ -73,14 +74,22 @@ class Farmland extends Transparent{
$world = $this->position->getWorld();
if(!$this->canHydrate()){
if($this->wetness > 0){
$this->wetness--;
$world->setBlock($this->position, $this, false);
$event = new FarmlandHydrationChangeEvent($this, $this->wetness, $this->wetness - 1);
$event->call();
if(!$event->isCancelled()){
$this->wetness = $event->getNewHydration();
$world->setBlock($this->position, $this, false);
}
}else{
$world->setBlock($this->position, VanillaBlocks::DIRT());
}
}elseif($this->wetness < self::MAX_WETNESS){
$this->wetness = self::MAX_WETNESS;
$world->setBlock($this->position, $this, false);
$event = new FarmlandHydrationChangeEvent($this, $this->wetness, self::MAX_WETNESS);
$event->call();
if(!$event->isCancelled()){
$this->wetness = $event->getNewHydration();
$world->setBlock($this->position, $this, false);
}
}
}

View File

@ -23,10 +23,10 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockBurnEvent;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\math\Facing;
use pocketmine\world\format\Chunk;
use pocketmine\world\World;
@ -145,9 +145,13 @@ class Fire extends BaseFire{
private function burnBlock(Block $block, int $chanceBound) : void{
if(mt_rand(0, $chanceBound) < $block->getFlammability()){
$ev = new BlockBurnEvent($block, $this);
$ev->call();
if(!$ev->isCancelled()){
$cancelled = false;
if(BlockBurnEvent::hasHandlers()){
$ev = new BlockBurnEvent($block, $this);
$ev->call();
$cancelled = $ev->isCancelled();
}
if(!$cancelled){
$block->onIncinerate();
$world = $this->position->getWorld();
@ -225,13 +229,6 @@ class Fire extends BaseFire{
}
private function spreadBlock(Block $block, Block $newState) : bool{
$ev = new BlockSpreadEvent($block, $this, $newState);
$ev->call();
if(!$ev->isCancelled()){
$block->position->getWorld()->setBlock($block->position, $ev->getNewState());
return true;
}
return false;
return BlockEventHelper::spread($block, $newState, $this);
}
}

View File

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockMeltEvent;
use function mt_rand;
class FrostedIce extends Ice{
@ -97,11 +97,7 @@ class FrostedIce extends Ice{
private function tryMelt() : bool{
$world = $this->position->getWorld();
if($this->age >= self::MAX_AGE){
$ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::melt($this, VanillaBlocks::WATER());
return true;
}

View File

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
@ -166,14 +166,7 @@ class GlowLichen extends Transparent{
return false;
}
$ev = new BlockSpreadEvent($replacedBlock, $this, $replacementBlock);
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($replacedBlock->getPosition(), $ev->getNewState());
return true;
}
return false;
return BlockEventHelper::spread($replacedBlock, $replacementBlock, $this);
}
/**

View File

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\DirtType;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Hoe;
use pocketmine\item\Item;
@ -58,11 +58,7 @@ class Grass extends Opaque{
$lightAbove = $world->getFullLightAt($this->position->x, $this->position->y + 1, $this->position->z);
if($lightAbove < 4 && $world->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)->getLightFilter() >= 2){
//grass dies
$ev = new BlockSpreadEvent($this, $this, VanillaBlocks::DIRT());
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($this->position, $ev->getNewState(), false);
}
BlockEventHelper::spread($this, VanillaBlocks::DIRT(), $this);
}elseif($lightAbove >= 9){
//try grass spread
for($i = 0; $i < 4; ++$i){
@ -80,11 +76,7 @@ class Grass extends Opaque{
continue;
}
$ev = new BlockSpreadEvent($b, $this, VanillaBlocks::GRASS());
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($b->position, $ev->getNewState(), false);
}
BlockEventHelper::spread($b, VanillaBlocks::GRASS(), $this);
}
}
}

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\event\block\BlockMeltEvent;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\player\Player;
@ -53,11 +53,7 @@ class Ice extends Transparent{
public function onRandomTick() : void{
$world = $this->position->getWorld();
if($world->getHighestAdjacentBlockLight($this->position->x, $this->position->y, $this->position->z) >= 12){
$ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::melt($this, VanillaBlocks::WATER());
}
}

View File

@ -116,10 +116,15 @@ class Leaves extends Transparent{
public function onRandomTick() : void{
if(!$this->noDecay && $this->checkDecay){
$ev = new LeavesDecayEvent($this);
$ev->call();
$cancelled = false;
if(LeavesDecayEvent::hasHandlers()){
$ev = new LeavesDecayEvent($this);
$ev->call();
$cancelled = $ev->isCancelled();
}
$world = $this->position->getWorld();
if($ev->isCancelled() || $this->findLog($this->position)){
if($cancelled || $this->findLog($this->position)){
$this->checkDecay = false;
$world->setBlock($this->position, $this, false);
}else{

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\MinimumCostFlowCalculator;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\event\block\BlockFormEvent;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
@ -363,12 +363,8 @@ abstract class Liquid extends Transparent{
}
protected function liquidCollide(Block $cause, Block $result) : bool{
$ev = new BlockFormEvent($this, $result, $cause);
$ev->call();
if(!$ev->isCancelled()){
$world = $this->position->getWorld();
$world->setBlock($this->position, $ev->getNewState());
$world->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
if(BlockEventHelper::form($this, $result, $cause)){
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
}
return true;
}

View File

@ -113,14 +113,15 @@ class MobHead extends Flowable{
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
$collisionBox = AxisAlignedBB::one()->contract(0.25, 0, 0.25)->trim(Facing::UP, 0.5);
return match($this->facing){
Facing::NORTH => [$collisionBox->offset(0, 0.25, 0.25)],
Facing::SOUTH => [$collisionBox->offset(0, 0.25, -0.25)],
Facing::WEST => [$collisionBox->offset(0.25, 0.25, 0)],
Facing::EAST => [$collisionBox->offset(-0.25, 0.25, 0)],
default => [$collisionBox]
};
$collisionBox = AxisAlignedBB::one()
->contract(0.25, 0, 0.25)
->trim(Facing::UP, 0.5);
if($this->facing !== Facing::UP){
$collisionBox = $collisionBox
->offsetTowards(Facing::opposite($this->facing), 0.25)
->offsetTowards(Facing::UP, 0.25);
}
return [$collisionBox];
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{

View File

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\DirtType;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use function mt_rand;
@ -54,11 +54,7 @@ class Mycelium extends Opaque{
$block = $world->getBlockAt($x, $y, $z);
if($block instanceof Dirt && $block->getDirtType()->equals(DirtType::NORMAL())){
if($block->getSide(Facing::UP) instanceof Transparent){
$ev = new BlockSpreadEvent($block, $this, VanillaBlocks::MYCELIUM());
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($block->position, $ev->getNewState());
}
BlockEventHelper::spread($block, VanillaBlocks::MYCELIUM(), $this);
}
}
}

View File

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
@ -76,11 +76,7 @@ class NetherWartPlant extends Flowable{
if($this->age < self::MAX_AGE && mt_rand(0, 10) === 0){ //Still growing
$block = clone $this;
$block->age++;
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::grow($this, $block, null);
}
}

119
src/block/PinkPetals.php Normal file
View File

@ -0,0 +1,119 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class PinkPetals extends Flowable{
use HorizontalFacingTrait;
public const MIN_COUNT = 1;
public const MAX_COUNT = 4;
protected int $count = self::MIN_COUNT;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
$w->boundedInt(2, self::MIN_COUNT, self::MAX_COUNT, $this->count);
}
public function getCount() : int{
return $this->count;
}
/** @return $this */
public function setCount(int $count) : self{
if($count < self::MIN_COUNT || $count > self::MAX_COUNT){
throw new \InvalidArgumentException("Count must be in range " . self::MIN_COUNT . " ... " . self::MAX_COUNT);
}
$this->count = $count;
return $this;
}
private function canBeSupportedAt(Block $block) : bool{
$supportBlock = $block->getSide(Facing::DOWN);
//TODO: Moss block
return $supportBlock->hasTypeTag(BlockTypeTags::DIRT) || $supportBlock->hasTypeTag(BlockTypeTags::MUD);
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
return ($blockReplace instanceof PinkPetals && $blockReplace->getCount() < self::MAX_COUNT) || parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($this)){
return false;
}
if($blockReplace instanceof PinkPetals && $blockReplace->getCount() < self::MAX_COUNT){
$this->count = $blockReplace->getCount() + 1;
$this->facing = $blockReplace->getFacing();
}elseif($player !== null){
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($item instanceof Fertilizer){
$grew = false;
if($this->count < self::MAX_COUNT){
$grew = BlockEventHelper::grow($this, (clone $this)->setCount($this->count + 1), $player);
}else{
$this->position->getWorld()->dropItem($this->position->add(0, 0.5, 0), $this->asItem());
$grew = true;
}
if($grew){
$item->pop();
return true;
}
}
return false;
}
public function getFlameEncouragement() : int{
return 60;
}
public function getFlammability() : int{
return 100;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [$this->asItem()->setCount($this->count)];
}
}

View File

@ -24,14 +24,33 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\SupportType;
use pocketmine\entity\Entity;
use pocketmine\event\block\PressurePlateUpdateEvent;
use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\PressurePlateActivateSound;
use pocketmine\world\sound\PressurePlateDeactivateSound;
use function count;
abstract class PressurePlate extends Transparent{
private readonly int $deactivationDelayTicks;
public function __construct(
BlockIdentifier $idInfo,
string $name,
BlockTypeInfo $typeInfo,
int $deactivationDelayTicks = 20 //TODO: make this mandatory in PM6
){
parent::__construct($idInfo, $name, $typeInfo);
$this->deactivationDelayTicks = $deactivationDelayTicks;
}
public function isSolid() : bool{
return false;
}
@ -61,5 +80,89 @@ abstract class PressurePlate extends Transparent{
}
}
//TODO
public function hasEntityCollision() : bool{
return true;
}
public function onEntityInside(Entity $entity) : bool{
if(!$this->hasOutputSignal()){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 0);
}
return true;
}
/**
* Returns the AABB that entities must intersect to activate the pressure plate.
* Note that this is not the same as the collision box (pressure plate doesn't have one), nor the visual bounding
* box. The activation area has a height of 0.25 blocks.
*/
protected function getActivationBox() : AxisAlignedBB{
return AxisAlignedBB::one()
->squash(Axis::X, 1 / 8)
->squash(Axis::Z, 1 / 8)
->trim(Facing::UP, 3 / 4)
->offset($this->position->x, $this->position->y, $this->position->z);
}
/**
* TODO: make this abstract in PM6
*/
protected function hasOutputSignal() : bool{
return false;
}
/**
* TODO: make this abstract in PM6
*
* @param Entity[] $entities
*
* @return mixed[]
* @phpstan-return array{Block, ?bool}
*/
protected function calculatePlateState(array $entities) : array{
return [$this, null];
}
/**
* Filters entities which don't affect the pressure plate state from the given list.
*
* @param Entity[] $entities
* @return Entity[]
*/
protected function filterIrrelevantEntities(array $entities) : array{
return $entities;
}
public function onScheduledUpdate() : void{
$world = $this->position->getWorld();
$intersectionAABB = $this->getActivationBox();
$activatingEntities = $this->filterIrrelevantEntities($world->getNearbyEntities($intersectionAABB));
//if an irrelevant entity is inside the full cube space of the pressure plate but not activating the plate,
//it will cause scheduled updates on the plate every tick. We don't want to fire events in this case if the
//plate is already deactivated.
if(count($activatingEntities) > 0 || $this->hasOutputSignal()){
[$newState, $pressedChange] = $this->calculatePlateState($activatingEntities);
//always call this, in case there are new entities on the plate
if(PressurePlateUpdateEvent::hasHandlers()){
$ev = new PressurePlateUpdateEvent($this, $newState, $activatingEntities);
$ev->call();
$newState = $ev->isCancelled() ? null : $ev->getNewState();
}
if($newState !== null){
$world->setBlock($this->position, $newState);
if($pressedChange !== null){
$world->addSound($this->position, $pressedChange ?
new PressurePlateActivateSound($this) :
new PressurePlateDeactivateSound($this)
);
}
}
if($pressedChange ?? $this->hasOutputSignal()){
$world->scheduleDelayedBlockUpdate($this->position, $this->deactivationDelayTicks);
}
}
}
}

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\data\runtime\RuntimeDataDescriber;
use function count;
abstract class SimplePressurePlate extends PressurePlate{
protected bool $pressed = false;
@ -39,4 +40,19 @@ abstract class SimplePressurePlate extends PressurePlate{
$this->pressed = $pressed;
return $this;
}
protected function hasOutputSignal() : bool{
return $this->pressed;
}
protected function calculatePlateState(array $entities) : array{
$newPressed = count($entities) > 0;
if($newPressed === $this->pressed){
return [$this, null];
}
return [
(clone $this)->setPressed($newPressed),
$newPressed
];
}
}

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockMeltEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB;
@ -105,11 +105,7 @@ class SnowLayer extends Flowable implements Fallable{
public function onRandomTick() : void{
$world = $this->position->getWorld();
if($world->getBlockLightAt($this->position->x, $this->position->y, $this->position->z) >= 12){
$ev = new BlockMeltEvent($this, VanillaBlocks::AIR());
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::melt($this, VanillaBlocks::AIR());
}
}

View File

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use function array_rand;
@ -64,11 +64,7 @@ abstract class Stem extends Crops{
if($this->age < self::MAX_AGE){
$block = clone $this;
++$block->age;
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::grow($this, $block, null);
}else{
$grow = $this->getPlant();
foreach(Facing::HORIZONTAL as $side){
@ -80,12 +76,7 @@ abstract class Stem extends Crops{
$facing = Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)];
$side = $this->getSide($facing);
if($side->getTypeId() === BlockTypeIds::AIR && $side->getSide(Facing::DOWN)->hasTypeTag(BlockTypeTags::DIRT)){
$ev = new BlockGrowEvent($side, $grow);
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($this->position, $this->setFacing($facing));
$world->setBlock($side->position, $ev->getNewState());
}
BlockEventHelper::grow($side, $grow, null);
}
}
}

View File

@ -23,6 +23,13 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use function array_filter;
class StonePressurePlate extends SimplePressurePlate{
protected function filterIrrelevantEntities(array $entities) : array{
return array_filter($entities, fn(Entity $e) => $e instanceof Living); //TODO: armor stands should activate stone plates too
}
}

View File

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Facing;
@ -60,13 +60,11 @@ class Sugarcane extends Flowable{
}
$b = $world->getBlockAt($pos->x, $pos->y + $y, $pos->z);
if($b->getTypeId() === BlockTypeIds::AIR){
$ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE(), $player);
$ev->call();
if($ev->isCancelled()){
if(BlockEventHelper::grow($b, VanillaBlocks::SUGARCANE(), $player)){
$grew = true;
}else{
break;
}
$world->setBlock($b->position, $ev->getNewState());
$grew = true;
}elseif(!$b->hasSameTypeId($this)){
break;
}

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
@ -87,15 +87,9 @@ class SweetBerryBush extends Flowable{
if($this->age < self::STAGE_MATURE && $item instanceof Fertilizer){
$block = clone $this;
$block->age++;
$ev = new BlockGrowEvent($this, $block, $player);
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($this->position, $ev->getNewState());
if(BlockEventHelper::grow($this, $block, $player)){
$item->pop();
}
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
$world->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
$world->dropItem($this->position, $this->asItem()->setCount($dropAmount));
@ -133,11 +127,7 @@ class SweetBerryBush extends Flowable{
if($this->age < self::STAGE_MATURE && mt_rand(0, 2) === 1){
$block = clone $this;
++$block->age;
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
BlockEventHelper::grow($this, $block, null);
}
}

View File

@ -59,6 +59,7 @@ use pocketmine\block\utils\SaplingType;
use pocketmine\block\utils\WoodType;
use pocketmine\crafting\FurnaceType;
use pocketmine\entity\projectile\Projectile;
use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags;
use pocketmine\item\Item;
use pocketmine\item\ToolTier;
use pocketmine\math\Facing;
@ -573,6 +574,7 @@ use function mb_strtolower;
* @method static PackedIce PACKED_ICE()
* @method static Opaque PACKED_MUD()
* @method static DoublePlant PEONY()
* @method static PinkPetals PINK_PETALS()
* @method static Flower PINK_TULIP()
* @method static Podzol PODZOL()
* @method static Opaque POLISHED_ANDESITE()
@ -840,6 +842,7 @@ final class VanillaBlocks{
self::register("lilac", new DoublePlant(new BID(Ids::LILAC), "Lilac", new Info(BreakInfo::instant())));
self::register("rose_bush", new DoublePlant(new BID(Ids::ROSE_BUSH), "Rose Bush", new Info(BreakInfo::instant())));
self::register("peony", new DoublePlant(new BID(Ids::PEONY), "Peony", new Info(BreakInfo::instant())));
self::register("pink_petals", new PinkPetals(new BID(Ids::PINK_PETALS), "Pink Petals", new Info(BreakInfo::instant())));
self::register("double_tallgrass", new DoubleTallGrass(new BID(Ids::DOUBLE_TALLGRASS), "Double Tallgrass", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
self::register("large_fern", new DoubleTallGrass(new BID(Ids::LARGE_FERN), "Large Fern", new Info(BreakInfo::instant(ToolType::SHEARS, 1))));
self::register("dragon_egg", new DragonEgg(new BID(Ids::DRAGON_EGG), "Dragon Egg", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD()))));
@ -964,7 +967,7 @@ final class VanillaBlocks{
$pumpkinBreakInfo = new Info(BreakInfo::axe(1.0));
self::register("pumpkin", new Pumpkin(new BID(Ids::PUMPKIN), "Pumpkin", $pumpkinBreakInfo));
self::register("carved_pumpkin", new CarvedPumpkin(new BID(Ids::CARVED_PUMPKIN), "Carved Pumpkin", $pumpkinBreakInfo));
self::register("carved_pumpkin", new CarvedPumpkin(new BID(Ids::CARVED_PUMPKIN), "Carved Pumpkin", new Info(BreakInfo::axe(1.0), enchantmentTags: [EnchantmentTags::MASK])));
self::register("lit_pumpkin", new LitPumpkin(new BID(Ids::LIT_PUMPKIN), "Jack o'Lantern", $pumpkinBreakInfo));
self::register("pumpkin_stem", new PumpkinStem(new BID(Ids::PUMPKIN_STEM), "Pumpkin Stem", new Info(BreakInfo::instant())));
@ -1000,7 +1003,7 @@ final class VanillaBlocks{
self::register("sea_lantern", new SeaLantern(new BID(Ids::SEA_LANTERN), "Sea Lantern", new Info(new BreakInfo(0.3))));
self::register("sea_pickle", new SeaPickle(new BID(Ids::SEA_PICKLE), "Sea Pickle", new Info(BreakInfo::instant())));
self::register("mob_head", new MobHead(new BID(Ids::MOB_HEAD, TileMobHead::class), "Mob Head", new Info(new BreakInfo(1.0))));
self::register("mob_head", new MobHead(new BID(Ids::MOB_HEAD, TileMobHead::class), "Mob Head", new Info(new BreakInfo(1.0), enchantmentTags: [EnchantmentTags::MASK])));
self::register("slime", new Slime(new BID(Ids::SLIME), "Slime Block", new Info(BreakInfo::instant())));
self::register("snow", new Snow(new BID(Ids::SNOW), "Snow Block", new Info(BreakInfo::shovel(0.2, ToolTier::WOOD()))));
self::register("snow_layer", new SnowLayer(new BID(Ids::SNOW_LAYER), "Snow Layer", new Info(BreakInfo::shovel(0.1, ToolTier::WOOD()))));
@ -1111,8 +1114,20 @@ final class VanillaBlocks{
self::register("lily_pad", new WaterLily(new BID(Ids::LILY_PAD), "Lily Pad", new Info(BreakInfo::instant())));
$weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD()));
self::register("weighted_pressure_plate_heavy", new WeightedPressurePlateHeavy(new BID(Ids::WEIGHTED_PRESSURE_PLATE_HEAVY), "Weighted Pressure Plate Heavy", $weightedPressurePlateBreakInfo));
self::register("weighted_pressure_plate_light", new WeightedPressurePlateLight(new BID(Ids::WEIGHTED_PRESSURE_PLATE_LIGHT), "Weighted Pressure Plate Light", $weightedPressurePlateBreakInfo));
self::register("weighted_pressure_plate_heavy", new WeightedPressurePlateHeavy(
new BID(Ids::WEIGHTED_PRESSURE_PLATE_HEAVY),
"Weighted Pressure Plate Heavy",
$weightedPressurePlateBreakInfo,
deactivationDelayTicks: 10,
signalStrengthFactor: 0.1
));
self::register("weighted_pressure_plate_light", new WeightedPressurePlateLight(
new BID(Ids::WEIGHTED_PRESSURE_PLATE_LIGHT),
"Weighted Pressure Plate Light",
$weightedPressurePlateBreakInfo,
deactivationDelayTicks: 10,
signalStrengthFactor: 1.0
));
self::register("wheat", new Wheat(new BID(Ids::WHEAT), "Wheat Block", new Info(BreakInfo::instant())));
$leavesBreakInfo = new Info(new class(0.2, ToolType::HOE) extends BreakInfo{
@ -1263,7 +1278,7 @@ final class VanillaBlocks{
self::register($idName("door"), new WoodenDoor(WoodLikeBlockIdHelper::getDoorIdentifier($woodType), $name . " Door", $woodenDoorBreakInfo, $woodType));
self::register($idName("button"), new WoodenButton(WoodLikeBlockIdHelper::getButtonIdentifier($woodType), $name . " Button", $woodenButtonBreakInfo, $woodType));
self::register($idName("pressure_plate"), new WoodenPressurePlate(WoodLikeBlockIdHelper::getPressurePlateIdentifier($woodType), $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType));
self::register($idName("pressure_plate"), new WoodenPressurePlate(WoodLikeBlockIdHelper::getPressurePlateIdentifier($woodType), $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType, 20));
self::register($idName("trapdoor"), new WoodenTrapdoor(WoodLikeBlockIdHelper::getTrapdoorIdentifier($woodType), $name . " Trapdoor", $woodenDoorBreakInfo, $woodType));
[$floorSignId, $wallSignId, $signAsItem] = WoodLikeBlockIdHelper::getSignInfo($woodType);
@ -1488,7 +1503,7 @@ final class VanillaBlocks{
$prefix = fn(string $thing) => "Polished Blackstone" . ($thing !== "" ? " $thing" : "");
self::register("polished_blackstone", new Opaque(new BID(Ids::POLISHED_BLACKSTONE), $prefix(""), $blackstoneBreakInfo));
self::register("polished_blackstone_button", new StoneButton(new BID(Ids::POLISHED_BLACKSTONE_BUTTON), $prefix("Button"), new Info(BreakInfo::pickaxe(0.5))));
self::register("polished_blackstone_pressure_plate", new StonePressurePlate(new BID(Ids::POLISHED_BLACKSTONE_PRESSURE_PLATE), $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD()))));
self::register("polished_blackstone_pressure_plate", new StonePressurePlate(new BID(Ids::POLISHED_BLACKSTONE_PRESSURE_PLATE), $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD())), 20));
self::register("polished_blackstone_slab", new Slab(new BID(Ids::POLISHED_BLACKSTONE_SLAB), $prefix(""), $slabBreakInfo));
self::register("polished_blackstone_stairs", new Stair(new BID(Ids::POLISHED_BLACKSTONE_STAIRS), $prefix("Stairs"), $blackstoneBreakInfo));
self::register("polished_blackstone_wall", new Wall(new BID(Ids::POLISHED_BLACKSTONE_WALL), $prefix("Wall"), $blackstoneBreakInfo));

View File

@ -24,7 +24,40 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait;
use function ceil;
use function count;
use function max;
use function min;
abstract class WeightedPressurePlate extends PressurePlate{
class WeightedPressurePlate extends PressurePlate{
use AnalogRedstoneSignalEmitterTrait;
private readonly float $signalStrengthFactor;
/**
* @param float $signalStrengthFactor Number of entities on the plate is divided by this value to get signal strength
*/
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo, int $deactivationDelayTicks, float $signalStrengthFactor = 1.0){
parent::__construct($idInfo, $name, $typeInfo, $deactivationDelayTicks);
$this->signalStrengthFactor = $signalStrengthFactor;
}
protected function hasOutputSignal() : bool{
return $this->signalStrength > 0;
}
protected function calculatePlateState(array $entities) : array{
$newSignalStrength = min(15, max(0,
(int) ceil(count($entities) * $this->signalStrengthFactor)
));
if($newSignalStrength === $this->signalStrength){
return [$this, null];
}
$wasActive = $this->signalStrength !== 0;
$isActive = $newSignalStrength !== 0;
return [
(clone $this)->setOutputSignalStrength($newSignalStrength),
$wasActive !== $isActive ? $isActive : null
];
}
}

View File

@ -23,6 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
/**
* @deprecated
*/
class WeightedPressurePlateHeavy extends WeightedPressurePlate{
}

View File

@ -23,6 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
/**
* @deprecated
*/
class WeightedPressurePlateLight extends WeightedPressurePlate{
}

View File

@ -23,11 +23,23 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\WoodType;
use pocketmine\block\utils\WoodTypeTrait;
class WoodenPressurePlate extends SimplePressurePlate{
use WoodTypeTrait;
public function __construct(
BlockIdentifier $idInfo,
string $name,
BlockTypeInfo $typeInfo,
WoodType $woodType,
int $deactivationDelayTicks = 20 //TODO: make this mandatory in PM6
){
$this->woodType = $woodType;
parent::__construct($idInfo, $name, $typeInfo, $deactivationDelayTicks);
}
public function getFuelTime() : int{
return 300;
}

View File

@ -23,9 +23,15 @@ declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\event\player\PlayerEnchantingOptionsRequestEvent;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\item\enchantment\EnchantingHelper as Helper;
use pocketmine\item\enchantment\EnchantingOption;
use pocketmine\item\Item;
use pocketmine\world\Position;
use function array_values;
use function count;
class EnchantInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
@ -33,8 +39,47 @@ class EnchantInventory extends SimpleInventory implements BlockInventory, Tempor
public const SLOT_INPUT = 0;
public const SLOT_LAPIS = 1;
/** @var EnchantingOption[] $options */
private array $options = [];
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(2);
}
protected function onSlotChange(int $index, Item $before) : void{
if($index === self::SLOT_INPUT){
foreach($this->viewers as $viewer){
$this->options = [];
$item = $this->getInput();
$options = Helper::generateOptions($this->holder, $item, $viewer->getEnchantmentSeed());
$event = new PlayerEnchantingOptionsRequestEvent($viewer, $this, $options);
$event->call();
if(!$event->isCancelled() && count($event->getOptions()) > 0){
$this->options = array_values($event->getOptions());
$viewer->getNetworkSession()->getInvManager()?->syncEnchantingTableOptions($this->options);
}
}
}
parent::onSlotChange($index, $before);
}
public function getInput() : Item{
return $this->getItem(self::SLOT_INPUT);
}
public function getLapis() : Item{
return $this->getItem(self::SLOT_LAPIS);
}
public function getOutput(int $optionId) : ?Item{
$option = $this->getOption($optionId);
return $option === null ? null : Helper::enchantItem($this->getInput(), $option->getEnchantments());
}
public function getOption(int $optionId) : ?EnchantingOption{
return $this->options[$optionId] ?? null;
}
}

View File

@ -32,6 +32,6 @@ final class SmithingTableInventory extends SimpleInventory implements BlockInven
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(3);
parent::__construct(2);
}
}

View File

@ -68,6 +68,8 @@ class Sign extends Spawnable{
}
protected SignText $text;
private bool $waxed = false;
protected ?int $editorEntityRuntimeId = null;
public function __construct(World $world, Vector3 $pos){
@ -101,6 +103,7 @@ class Sign extends Spawnable{
}
$this->text = new SignText($text);
}
$this->waxed = $nbt->getByte(self::TAG_WAXED, 0) !== 0;
}
protected function writeSaveData(CompoundTag $nbt) : void{
@ -113,6 +116,7 @@ class Sign extends Spawnable{
$nbt->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB()));
$nbt->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0);
$nbt->setByte(self::TAG_LEGACY_BUG_RESOLVE, 1);
$nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
}
public function getText() : SignText{
@ -123,6 +127,10 @@ class Sign extends Spawnable{
$this->text = $text;
}
public function isWaxed() : bool{ return $this->waxed; }
public function setWaxed(bool $waxed) : void{ $this->waxed = $waxed; }
/**
* Returns the entity runtime ID of the player who placed this sign. Only the player whose entity ID matches this
* one may edit the sign text.
@ -153,7 +161,7 @@ class Sign extends Spawnable{
->setByte(self::TAG_GLOWING_TEXT, 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1)
);
$nbt->setByte(self::TAG_WAXED, 0);
$nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
$nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1);
}
}

View File

@ -0,0 +1,115 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\utils;
use pocketmine\block\Block;
use pocketmine\event\block\BlockDeathEvent;
use pocketmine\event\block\BlockFormEvent;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\event\block\BlockMeltEvent;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\player\Player;
/**
* Helper class to call block changing events and apply the results to the world.
* TODO: try to further reduce the amount of code duplication here - while this is much better than before, it's still
* very repetitive.
*/
final class BlockEventHelper{
public static function grow(Block $oldState, Block $newState, ?Player $causingPlayer) : bool{
if(BlockGrowEvent::hasHandlers()){
$ev = new BlockGrowEvent($oldState, $newState, $causingPlayer);
$ev->call();
if($ev->isCancelled()){
return false;
}
$newState = $ev->getNewState();
}
$position = $oldState->getPosition();
$position->getWorld()->setBlock($position, $newState);
return true;
}
public static function spread(Block $oldState, Block $newState, Block $source) : bool{
if(BlockSpreadEvent::hasHandlers()){
$ev = new BlockSpreadEvent($oldState, $source, $newState);
$ev->call();
if($ev->isCancelled()){
return false;
}
$newState = $ev->getNewState();
}
$position = $oldState->getPosition();
$position->getWorld()->setBlock($position, $newState);
return true;
}
public static function form(Block $oldState, Block $newState, Block $cause) : bool{
if(BlockFormEvent::hasHandlers()){
$ev = new BlockFormEvent($oldState, $newState, $cause);
$ev->call();
if($ev->isCancelled()){
return false;
}
$newState = $ev->getNewState();
}
$position = $oldState->getPosition();
$position->getWorld()->setBlock($position, $newState);
return true;
}
public static function melt(Block $oldState, Block $newState) : bool{
if(BlockMeltEvent::hasHandlers()){
$ev = new BlockMeltEvent($oldState, $newState);
$ev->call();
if($ev->isCancelled()){
return false;
}
$newState = $ev->getNewState();
}
$position = $oldState->getPosition();
$position->getWorld()->setBlock($position, $newState);
return true;
}
public static function die(Block $oldState, Block $newState) : bool{
if(BlockDeathEvent::hasHandlers()){
$ev = new BlockDeathEvent($oldState, $newState);
$ev->call();
if($ev->isCancelled()){
return false;
}
$newState = $ev->getNewState();
}
$position = $oldState->getPosition();
$position->getWorld()->setBlock($position, $newState);
return true;
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\item\enchantment\EnchantingHelper;
use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\item\enchantment\StringToEnchantmentParser;
use pocketmine\lang\KnownTranslationFactory;
@ -76,8 +77,9 @@ class EnchantCommand extends VanillaCommand{
}
}
$item->addEnchantment(new EnchantmentInstance($enchantment, $level));
$player->getInventory()->setItemInHand($item);
//this is necessary to deal with enchanted books, which are a different item type than regular books
$enchantedItem = EnchantingHelper::enchantItem($item, [new EnchantmentInstance($enchantment, $level)]);
$player->getInventory()->setItemInHand($enchantedItem);
self::broadcastCommandMessage($sender, KnownTranslationFactory::commands_enchant_success($player->getName()));
return true;

View File

@ -206,7 +206,6 @@ class CrashDump{
if(isset($lastError)){
$this->data->lastError = $lastError;
$this->data->lastError["message"] = mb_scrub($this->data->lastError["message"], 'UTF-8');
$this->data->lastError["trace"] = array_map(array: $lastError["trace"], callback: fn(ThreadCrashInfoFrame $frame) => $frame->getPrintableFrame());
}
$this->data->error = $error;

View File

@ -100,6 +100,7 @@ use pocketmine\block\MobHead;
use pocketmine\block\NetherPortal;
use pocketmine\block\NetherVines;
use pocketmine\block\NetherWartPlant;
use pocketmine\block\PinkPetals;
use pocketmine\block\Potato;
use pocketmine\block\PoweredRail;
use pocketmine\block\PumpkinStem;
@ -1364,6 +1365,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::ORANGE_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_ORANGE));
$this->map(Blocks::OXEYE_DAISY(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_OXEYE));
$this->map(Blocks::PEONY(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_PAEONIA, Writer::create(Ids::DOUBLE_PLANT)));
$this->map(Blocks::PINK_PETALS(), function(PinkPetals $block) : Writer{
return Writer::create(Ids::PINK_PETALS)
->writeLegacyHorizontalFacing($block->getFacing())
->writeInt(StateNames::GROWTH, $block->getCount() - 1);
});
$this->map(Blocks::PINK_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_PINK));
$this->map(Blocks::POLISHED_ANDESITE(), fn() => Helper::encodeStone(StringValues::STONE_TYPE_ANDESITE_SMOOTH));
$this->map(Blocks::POLISHED_ANDESITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_POLISHED_ANDESITE));

View File

@ -28,6 +28,7 @@ use pocketmine\block\Block;
use pocketmine\block\CaveVines;
use pocketmine\block\ChorusFlower;
use pocketmine\block\Light;
use pocketmine\block\PinkPetals;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\SweetBerryBush;
@ -1175,6 +1176,13 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->mapSlab(Ids::OXIDIZED_CUT_COPPER_SLAB, Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED()));
$this->mapStairs(Ids::OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED()));
$this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT())->setAxis($in->readPillarAxis()));
$this->map(Ids::PINK_PETALS, function(Reader $in) : Block{
//Pink petals only uses 0-3, but GROWTH state can go up to 7
$growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7);
return Blocks::PINK_PETALS()
->setFacing($in->readLegacyHorizontalFacing())
->setCount(min($growth + 1, PinkPetals::MAX_COUNT));
});
$this->mapStairs(Ids::POLISHED_ANDESITE_STAIRS, fn() => Blocks::POLISHED_ANDESITE_STAIRS());
$this->map(Ids::POLISHED_BASALT, function(Reader $in) : Block{
return Blocks::POLISHED_BASALT()

View File

@ -223,6 +223,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::ECHO_SHARD, Items::ECHO_SHARD());
$this->map1to1Item(Ids::EGG, Items::EGG());
$this->map1to1Item(Ids::EMERALD, Items::EMERALD());
$this->map1to1Item(Ids::ENCHANTED_BOOK, Items::ENCHANTED_BOOK());
$this->map1to1Item(Ids::ENCHANTED_GOLDEN_APPLE, Items::ENCHANTED_GOLDEN_APPLE());
$this->map1to1Item(Ids::ENDER_PEARL, Items::ENDER_PEARL());
$this->map1to1Item(Ids::EXPERIENCE_BOTTLE, Items::EXPERIENCE_BOTTLE());

View File

@ -37,6 +37,7 @@ use pocketmine\inventory\InventoryHolder;
use pocketmine\inventory\PlayerEnderInventory;
use pocketmine\inventory\PlayerInventory;
use pocketmine\inventory\PlayerOffHandInventory;
use pocketmine\item\enchantment\EnchantingHelper;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\Totem;
@ -66,7 +67,6 @@ use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
use pocketmine\player\Player;
use pocketmine\utils\Limits;
use pocketmine\world\sound\TotemUseSound;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
@ -76,7 +76,6 @@ use function array_key_exists;
use function array_merge;
use function array_values;
use function min;
use function random_int;
class Human extends Living implements ProjectileSource, InventoryHolder{
@ -211,6 +210,18 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
return $this->xpManager;
}
public function getEnchantmentSeed() : int{
return $this->xpSeed;
}
public function setEnchantmentSeed(int $seed) : void{
$this->xpSeed = $seed;
}
public function regenerateEnchantmentSeed() : void{
$this->xpSeed = EnchantingHelper::generateSeed();
}
public function getXpDropAmount() : int{
//this causes some XP to be lost on death when above level 1 (by design), dropping at most enough points for
//about 7.5 levels of XP.
@ -334,7 +345,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
if(($xpSeedTag = $nbt->getTag(self::TAG_XP_SEED)) instanceof IntTag){
$this->xpSeed = $xpSeedTag->getValue();
}else{
$this->xpSeed = random_int(Limits::INT32_MIN, Limits::INT32_MAX);
$this->xpSeed = EnchantingHelper::generateSeed();
}
}

View File

@ -133,14 +133,18 @@ class HungerManager{
if(!$this->enabled){
return 0;
}
$ev = new PlayerExhaustEvent($this->entity, $amount, $cause);
$ev->call();
if($ev->isCancelled()){
return 0.0;
$evAmount = $amount;
if(PlayerExhaustEvent::hasHandlers()){
$ev = new PlayerExhaustEvent($this->entity, $amount, $cause);
$ev->call();
if($ev->isCancelled()){
return 0.0;
}
$evAmount = $ev->getAmount();
}
$exhaustion = $this->getExhaustion();
$exhaustion += $ev->getAmount();
$exhaustion += $evAmount;
while($exhaustion >= 4.0){
$exhaustion -= 4.0;
@ -159,7 +163,7 @@ class HungerManager{
}
$this->setExhaustion($exhaustion);
return $ev->getAmount();
return $evAmount;
}
public function getFoodTickTimer() : int{

View File

@ -27,6 +27,7 @@ declare(strict_types=1);
namespace pocketmine\event;
use pocketmine\timings\Timings;
use function count;
use function get_class;
abstract class Event{
@ -54,11 +55,11 @@ abstract class Event{
$timings = Timings::getEventTimings($this);
$timings->startTiming();
$handlerList = HandlerListManager::global()->getListFor(get_class($this));
$handlers = HandlerListManager::global()->getHandlersFor(static::class);
++self::$eventCallDepth;
try{
foreach($handlerList->getListenerList() as $registration){
foreach($handlers as $registration){
$registration->callEvent($this);
}
}finally{
@ -66,4 +67,14 @@ abstract class Event{
$timings->stopTiming();
}
}
/**
* Returns whether the current class context has any registered global handlers.
* This can be used in hot code paths to avoid unnecessary event object creation.
*
* Usage: SomeEventClass::hasHandlers()
*/
public static function hasHandlers() : bool{
return count(HandlerListManager::global()->getHandlersFor(static::class)) > 0;
}
}

View File

@ -33,8 +33,6 @@ class HandlerList{
/** @var RegisteredListener[][] */
private array $handlerSlots = [];
private RegisteredListenerCache $handlerCache;
/** @var RegisteredListenerCache[] */
private array $affectedHandlerCaches = [];
@ -44,9 +42,9 @@ class HandlerList{
*/
public function __construct(
private string $class,
private ?HandlerList $parentList
private ?HandlerList $parentList,
private RegisteredListenerCache $handlerCache = new RegisteredListenerCache()
){
$this->handlerCache = new RegisteredListenerCache();
for($list = $this; $list !== null; $list = $list->parentList){
$list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache;
}

View File

@ -36,6 +36,11 @@ class HandlerListManager{
/** @var HandlerList[] classname => HandlerList */
private array $allLists = [];
/**
* @var RegisteredListenerCache[] event class name => cache
* @phpstan-var array<class-string<Event>, RegisteredListenerCache>
*/
private array $handlerCaches = [];
/**
* Unregisters all the listeners
@ -98,7 +103,25 @@ class HandlerListManager{
}
$parent = self::resolveNearestHandleableParent($class);
return $this->allLists[$event] = new HandlerList($event, $parent !== null ? $this->getListFor($parent->getName()) : null);
$cache = new RegisteredListenerCache();
$this->handlerCaches[$event] = $cache;
return $this->allLists[$event] = new HandlerList(
$event,
parentList: $parent !== null ? $this->getListFor($parent->getName()) : null,
handlerCache: $cache
);
}
/**
* @phpstan-template TEvent of Event
* @phpstan-param class-string<TEvent> $event
*
* @return RegisteredListener[]
*/
public function getHandlersFor(string $event) : array{
$cache = $this->handlerCaches[$event] ?? null;
//getListFor() will populate the cache for the next call
return $cache?->list ?? $this->getListFor($event)->getListenerList();
}
/**

View File

@ -0,0 +1,59 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\block;
use pocketmine\block\Block;
use pocketmine\block\Farmland;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
/**
* Called when farmland hydration is updated.
*/
class FarmlandHydrationChangeEvent extends BlockEvent implements Cancellable{
use CancellableTrait;
public function __construct(
Block $block,
private int $oldHydration,
private int $newHydration,
){
parent::__construct($block);
}
public function getOldHydration() : int{
return $this->oldHydration;
}
public function getNewHydration() : int{
return $this->newHydration;
}
public function setNewHydration(int $hydration) : void{
if($hydration < 0 || $hydration > Farmland::MAX_WETNESS){
throw new \InvalidArgumentException("Hydration must be in range 0 ... " . Farmland::MAX_WETNESS);
}
$this->newHydration = $hydration;
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\block;
use pocketmine\block\Block;
use pocketmine\entity\Entity;
/**
* Called whenever the list of entities on a pressure plate changes.
* Depending on the type of pressure plate, this might turn on/off its signal, or change the signal strength.
*/
final class PressurePlateUpdateEvent extends BaseBlockChangeEvent{
/**
* @param Entity[] $activatingEntities
*/
public function __construct(
Block $block,
Block $newState,
private array $activatingEntities
){
parent::__construct($block, $newState);
}
/**
* Returns a list of entities intersecting the pressure plate's activation box.
* If the pressure plate is about to deactivate, this list will be empty.
*
* @return Entity[]
*/
public function getActivatingEntities() : array{ return $this->activatingEntities; }
}

View File

@ -0,0 +1,75 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\block\inventory\EnchantInventory;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\event\Event;
use pocketmine\item\enchantment\EnchantingOption;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use function count;
/**
* Called when a player inserts an item into an enchanting table's input slot.
* The options provided by the event will be shown on the enchanting table menu.
*/
class PlayerEnchantingOptionsRequestEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
/**
* @param EnchantingOption[] $options
*/
public function __construct(
Player $player,
private readonly EnchantInventory $inventory,
private array $options
){
$this->player = $player;
}
public function getInventory() : EnchantInventory{
return $this->inventory;
}
/**
* @return EnchantingOption[]
*/
public function getOptions() : array{
return $this->options;
}
/**
* @param EnchantingOption[] $options
*/
public function setOptions(array $options) : void{
Utils::validateArrayValueType($options, function(EnchantingOption $_) : void{ });
if(($optionCount = count($options)) > 3){
throw new \LogicException("The maximum number of options for an enchanting table is 3, but $optionCount have been passed");
}
$this->options = $options;
}
}

View File

@ -0,0 +1,85 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\inventory\transaction\EnchantingTransaction;
use pocketmine\item\enchantment\EnchantingOption;
use pocketmine\item\Item;
use pocketmine\player\Player;
/**
* Called when a player enchants an item using an enchanting table.
*/
class PlayerItemEnchantEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
public function __construct(
Player $player,
private readonly EnchantingTransaction $transaction,
private readonly EnchantingOption $option,
private readonly Item $inputItem,
private readonly Item $outputItem,
private readonly int $cost
){
$this->player = $player;
}
/**
* Returns the inventory transaction involved in this enchant event.
*/
public function getTransaction() : EnchantingTransaction{
return $this->transaction;
}
/**
* Returns the enchantment option used.
*/
public function getOption() : EnchantingOption{
return $this->option;
}
/**
* Returns the item to be enchanted.
*/
public function getInputItem() : Item{
return clone $this->inputItem;
}
/**
* Returns the enchanted item.
*/
public function getOutputItem() : Item{
return clone $this->outputItem;
}
/**
* Returns the number of XP levels and lapis that will be subtracted after enchanting
* if the player is not in creative mode.
*/
public function getCost() : int{
return $this->cost;
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\world;
use pocketmine\world\World;
/**
* Called when a world's difficulty is changed.
*/
final class WorldDifficultyChangeEvent extends WorldEvent{
public function __construct(
World $world,
private int $oldDifficulty,
private int $newDifficulty
){
parent::__construct($world);
}
public function getOldDifficulty() : int{ return $this->oldDifficulty; }
public function getNewDifficulty() : int{ return $this->newDifficulty; }
}

View File

@ -0,0 +1,134 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\transaction;
use pocketmine\event\player\PlayerItemEnchantEvent;
use pocketmine\item\enchantment\EnchantingHelper;
use pocketmine\item\enchantment\EnchantingOption;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use function count;
use function min;
class EnchantingTransaction extends InventoryTransaction{
private ?Item $inputItem = null;
private ?Item $outputItem = null;
public function __construct(
Player $source,
private readonly EnchantingOption $option,
private readonly int $cost
){
parent::__construct($source);
}
private function validateOutput() : void{
if($this->inputItem === null || $this->outputItem === null){
throw new AssumptionFailedError("Expected that inputItem and outputItem are not null before validating output");
}
$enchantedInput = EnchantingHelper::enchantItem($this->inputItem, $this->option->getEnchantments());
if(!$this->outputItem->equalsExact($enchantedInput)){
throw new TransactionValidationException("Invalid output item");
}
}
private function validateFiniteResources(int $lapisSpent) : void{
if($lapisSpent !== $this->cost){
throw new TransactionValidationException("Expected the amount of lapis lazuli spent to be $this->cost, but received $lapisSpent");
}
$xpLevel = $this->source->getXpManager()->getXpLevel();
$requiredXpLevel = $this->option->getRequiredXpLevel();
if($xpLevel < $requiredXpLevel){
throw new TransactionValidationException("Player's XP level $xpLevel is less than the required XP level $requiredXpLevel");
}
//XP level cost is intentionally not checked here, as the required level may be lower than the cost, allowing
//the option to be used with less XP than the cost - in this case, as much XP as possible will be deducted.
}
public function validate() : void{
if(count($this->actions) < 1){
throw new TransactionValidationException("Transaction must have at least one action to be executable");
}
/** @var Item[] $inputs */
$inputs = [];
/** @var Item[] $outputs */
$outputs = [];
$this->matchItems($outputs, $inputs);
$lapisSpent = 0;
foreach($inputs as $input){
if($input->getTypeId() === ItemTypeIds::LAPIS_LAZULI){
$lapisSpent = $input->getCount();
}else{
if($this->inputItem !== null){
throw new TransactionValidationException("Received more than 1 items to enchant");
}
$this->inputItem = $input;
}
}
if($this->inputItem === null){
throw new TransactionValidationException("No item to enchant received");
}
if(($outputCount = count($outputs)) !== 1){
throw new TransactionValidationException("Expected 1 output item, but received $outputCount");
}
$this->outputItem = $outputs[0];
$this->validateOutput();
if($this->source->hasFiniteResources()){
$this->validateFiniteResources($lapisSpent);
}
}
public function execute() : void{
parent::execute();
if($this->source->hasFiniteResources()){
//If the required XP level is less than the XP cost, the option can be selected with less XP than the cost.
//In this case, as much XP as possible will be taken.
$this->source->getXpManager()->subtractXpLevels(min($this->cost, $this->source->getXpManager()->getXpLevel()));
}
$this->source->regenerateEnchantmentSeed();
}
protected function callExecuteEvent() : bool{
if($this->inputItem === null || $this->outputItem === null){
throw new AssumptionFailedError("Expected that inputItem and outputItem are not null before executing the event");
}
$event = new PlayerItemEnchantEvent($this->source, $this, $this->option, $this->inputItem, $this->outputItem, $this->cost);
$event->call();
return !$event->isCancelled();
}
}

View File

@ -44,8 +44,11 @@ class Armor extends Durable{
protected ?Color $customColor = null;
public function __construct(ItemIdentifier $identifier, string $name, ArmorTypeInfo $info){
parent::__construct($identifier, $name);
/**
* @param string[] $enchantmentTags
*/
public function __construct(ItemIdentifier $identifier, string $name, ArmorTypeInfo $info, array $enchantmentTags = []){
parent::__construct($identifier, $name, $enchantmentTags);
$this->armorInfo = $info;
}
@ -72,6 +75,14 @@ class Armor extends Durable{
return $this->armorInfo->isFireProof();
}
public function getMaterial() : ArmorMaterial{
return $this->armorInfo->getMaterial();
}
public function getEnchantability() : int{
return $this->armorInfo->getMaterial()->getEnchantability();
}
/**
* Returns the dyed colour of this armour piece. This generally only applies to leather armour.
*/

View File

@ -0,0 +1,42 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item;
class ArmorMaterial{
public function __construct(
private readonly int $enchantability
){
}
/**
* Returns the value that defines how enchantable the item is.
*
* The higher an item's enchantability is, the more likely it will be to gain high-level enchantments
* or multiple enchantments upon being enchanted in an enchanting table.
*/
public function getEnchantability() : int{
return $this->enchantability;
}
}

View File

@ -24,13 +24,18 @@ declare(strict_types=1);
namespace pocketmine\item;
class ArmorTypeInfo{
private ArmorMaterial $material;
public function __construct(
private int $defensePoints,
private int $maxDurability,
private int $armorSlot,
private int $toughness = 0,
private bool $fireProof = false
){}
private bool $fireProof = false,
?ArmorMaterial $material = null
){
$this->material = $material ?? VanillaArmorMaterials::LEATHER();
}
public function getDefensePoints() : int{
return $this->defensePoints;
@ -51,4 +56,8 @@ class ArmorTypeInfo{
public function isFireProof() : bool{
return $this->fireProof;
}
public function getMaterial() : ArmorMaterial{
return $this->material;
}
}

View File

@ -0,0 +1,30 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item;
class EnchantedBook extends Item{
public function getMaxStackSize() : int{
return 1;
}
}

View File

@ -107,10 +107,13 @@ class Item implements \JsonSerializable{
* NOTE: This should NOT BE USED for creating items to set into an inventory. Use VanillaItems for that
* purpose.
* @see VanillaItems
*
* @param string[] $enchantmentTags
*/
public function __construct(
private ItemIdentifier $identifier,
protected string $name = "Unknown"
protected string $name = "Unknown",
private array $enchantmentTags = []
){
$this->nbt = new CompoundTag();
}
@ -455,6 +458,29 @@ class Item implements \JsonSerializable{
return $this->name;
}
/**
* Returns tags that represent the type of item being enchanted and are used to determine
* what enchantments can be applied to this item during in-game enchanting (enchanting table, anvil, fishing, etc.).
* @see ItemEnchantmentTags
* @see ItemEnchantmentTagRegistry
* @see AvailableEnchantmentRegistry
*
* @return string[]
*/
public function getEnchantmentTags() : array{
return $this->enchantmentTags;
}
/**
* Returns the value that defines how enchantable the item is.
*
* The higher an item's enchantability is, the more likely it will be to gain high-level enchantments
* or multiple enchantments upon being enchanted in an enchanting table.
*/
public function getEnchantability() : int{
return 1;
}
final public function canBePlaced() : bool{
return $this->getBlock()->canBePlaced();
}

View File

@ -36,7 +36,7 @@ final class ItemBlock extends Item{
public function __construct(
private Block $block
){
parent::__construct(ItemIdentifier::fromBlock($block), $block->getName());
parent::__construct(ItemIdentifier::fromBlock($block), $block->getName(), $block->getEnchantmentTags());
}
protected function describeState(RuntimeDataDescriber $w) : void{

View File

@ -303,8 +303,9 @@ final class ItemTypeIds{
public const MANGROVE_BOAT = 20264;
public const GLOW_BERRIES = 20265;
public const CHERRY_SIGN = 20266;
public const ENCHANTED_BOOK = 20267;
public const FIRST_UNUSED_ITEM_ID = 20267;
public const FIRST_UNUSED_ITEM_ID = 20268;
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;

View File

@ -856,6 +856,7 @@ final class StringToItemParser extends StringToTParser{
$result->registerBlock("packed_ice", fn() => Blocks::PACKED_ICE());
$result->registerBlock("packed_mud", fn() => Blocks::PACKED_MUD());
$result->registerBlock("peony", fn() => Blocks::PEONY());
$result->registerBlock("pink_petals", fn() => Blocks::PINK_PETALS());
$result->registerBlock("pink_tulip", fn() => Blocks::PINK_TULIP());
$result->registerBlock("piglin_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::PIGLIN()));
$result->registerBlock("plank", fn() => Blocks::OAK_PLANKS());
@ -1276,6 +1277,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("egg", fn() => Items::EGG());
$result->register("elixir", fn() => Items::MEDICINE()->setType(MedicineType::ELIXIR()));
$result->register("emerald", fn() => Items::EMERALD());
$result->register("enchanted_book", fn() => Items::ENCHANTED_BOOK());
$result->register("enchanted_golden_apple", fn() => Items::ENCHANTED_GOLDEN_APPLE());
$result->register("enchanting_bottle", fn() => Items::EXPERIENCE_BOTTLE());
$result->register("ender_pearl", fn() => Items::ENDER_PEARL());

View File

@ -26,8 +26,11 @@ namespace pocketmine\item;
abstract class TieredTool extends Tool{
protected ToolTier $tier;
public function __construct(ItemIdentifier $identifier, string $name, ToolTier $tier){
parent::__construct($identifier, $name);
/**
* @param string[] $enchantmentTags
*/
public function __construct(ItemIdentifier $identifier, string $name, ToolTier $tier, array $enchantmentTags = []){
parent::__construct($identifier, $name, $enchantmentTags);
$this->tier = $tier;
}
@ -43,6 +46,10 @@ abstract class TieredTool extends Tool{
return $this->tier->getBaseEfficiency();
}
public function getEnchantability() : int{
return $this->tier->getEnchantability();
}
public function getFuelTime() : int{
if($this->tier->equals(ToolTier::WOOD())){
return 200;

View File

@ -45,12 +45,12 @@ final class ToolTier{
protected static function setup() : void{
self::registerAll(
new self("wood", 1, 60, 5, 2),
new self("gold", 2, 33, 5, 12),
new self("stone", 3, 132, 6, 4),
new self("iron", 4, 251, 7, 6),
new self("diamond", 5, 1562, 8, 8),
new self("netherite", 6, 2032, 9, 9)
new self("wood", 1, 60, 5, 2, 15),
new self("gold", 2, 33, 5, 12, 22),
new self("stone", 3, 132, 6, 4, 5),
new self("iron", 4, 251, 7, 6, 14),
new self("diamond", 5, 1562, 8, 8, 10),
new self("netherite", 6, 2032, 9, 9, 15)
);
}
@ -59,7 +59,8 @@ final class ToolTier{
private int $harvestLevel,
private int $maxDurability,
private int $baseAttackPoints,
private int $baseEfficiency
private int $baseEfficiency,
private int $enchantability
){
$this->Enum___construct($name);
}
@ -79,4 +80,14 @@ final class ToolTier{
public function getBaseEfficiency() : int{
return $this->baseEfficiency;
}
/**
* Returns the value that defines how enchantable the item is.
*
* The higher an item's enchantability is, the more likely it will be to gain high-level enchantments
* or multiple enchantments upon being enchanted in an enchanting table.
*/
public function getEnchantability() : int{
return $this->enchantability;
}
}

View File

@ -0,0 +1,73 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\utils\RegistryTrait;
/**
* This doc-block is generated automatically, do not modify it manually.
* This must be regenerated whenever registry members are added, removed or changed.
* @see build/generate-registry-annotations.php
* @generate-registry-docblock
*
* @method static ArmorMaterial CHAINMAIL()
* @method static ArmorMaterial DIAMOND()
* @method static ArmorMaterial GOLD()
* @method static ArmorMaterial IRON()
* @method static ArmorMaterial LEATHER()
* @method static ArmorMaterial NETHERITE()
* @method static ArmorMaterial TURTLE()
*/
final class VanillaArmorMaterials{
use RegistryTrait;
private function __construct(){
// NOOP
}
protected static function register(string $name, ArmorMaterial $armorMaterial) : void{
self::_registryRegister($name, $armorMaterial);
}
/**
* @return ArmorMaterial[]
* @phpstan-return array<string, ArmorMaterial>
*/
public static function getAll() : array{
// phpstan doesn't support generic traits yet :(
/** @var ArmorMaterial[] $result */
$result = self::_registryGetAll();
return $result;
}
protected static function setup() : void{
self::register("leather", new ArmorMaterial(15));
self::register("chainmail", new ArmorMaterial(12));
self::register("iron", new ArmorMaterial(9));
self::register("turtle", new ArmorMaterial(9));
self::register("gold", new ArmorMaterial(25));
self::register("diamond", new ArmorMaterial(10));
self::register("netherite", new ArmorMaterial(15));
}
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\block\utils\RecordType;
use pocketmine\block\VanillaBlocks;
use pocketmine\block\VanillaBlocks as Blocks;
use pocketmine\entity\Entity;
use pocketmine\entity\Location;
@ -32,8 +31,10 @@ use pocketmine\entity\Squid;
use pocketmine\entity\Villager;
use pocketmine\entity\Zombie;
use pocketmine\inventory\ArmorInventory;
use pocketmine\item\enchantment\ItemEnchantmentTags as EnchantmentTags;
use pocketmine\item\ItemIdentifier as IID;
use pocketmine\item\ItemTypeIds as Ids;
use pocketmine\item\VanillaArmorMaterials as ArmorMaterials;
use pocketmine\math\Vector3;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\CloningRegistryTrait;
@ -151,6 +152,7 @@ use pocketmine\world\World;
* @method static Item ECHO_SHARD()
* @method static Egg EGG()
* @method static Item EMERALD()
* @method static EnchantedBook ENCHANTED_BOOK()
* @method static GoldenAppleEnchanted ENCHANTED_GOLDEN_APPLE()
* @method static EnderPearl ENDER_PEARL()
* @method static ExperienceBottle EXPERIENCE_BOTTLE()
@ -337,7 +339,7 @@ final class VanillaItems{
self::registerSpawnEggs();
self::registerTierToolItems();
self::register("air", VanillaBlocks::AIR()->asItem()->setCount(0));
self::register("air", Blocks::AIR()->asItem()->setCount(0));
self::register("acacia_sign", new ItemBlockWallOrFloor(new IID(Ids::ACACIA_SIGN), Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN()));
self::register("amethyst_shard", new Item(new IID(Ids::AMETHYST_SHARD), "Amethyst Shard"));
@ -355,8 +357,8 @@ final class VanillaItems{
self::register("bleach", new Item(new IID(Ids::BLEACH), "Bleach"));
self::register("bone", new Item(new IID(Ids::BONE), "Bone"));
self::register("bone_meal", new Fertilizer(new IID(Ids::BONE_MEAL), "Bone Meal"));
self::register("book", new Book(new IID(Ids::BOOK), "Book"));
self::register("bow", new Bow(new IID(Ids::BOW), "Bow"));
self::register("book", new Book(new IID(Ids::BOOK), "Book", [EnchantmentTags::ALL]));
self::register("bow", new Bow(new IID(Ids::BOW), "Bow", [EnchantmentTags::BOW]));
self::register("bowl", new Bowl(new IID(Ids::BOWL), "Bowl"));
self::register("bread", new Bread(new IID(Ids::BREAD), "Bread"));
self::register("brick", new Item(new IID(Ids::BRICK), "Brick"));
@ -408,7 +410,7 @@ final class VanillaItems{
self::register("clownfish", new Clownfish(new IID(Ids::CLOWNFISH), "Clownfish"));
self::register("coal", new Coal(new IID(Ids::COAL), "Coal"));
self::register("cocoa_beans", new CocoaBeans(new IID(Ids::COCOA_BEANS), "Cocoa Beans"));
self::register("compass", new Compass(new IID(Ids::COMPASS), "Compass"));
self::register("compass", new Compass(new IID(Ids::COMPASS), "Compass", [EnchantmentTags::COMPASS]));
self::register("cooked_chicken", new CookedChicken(new IID(Ids::COOKED_CHICKEN), "Cooked Chicken"));
self::register("cooked_fish", new CookedFish(new IID(Ids::COOKED_FISH), "Cooked Fish"));
self::register("cooked_mutton", new CookedMutton(new IID(Ids::COOKED_MUTTON), "Cooked Mutton"));
@ -429,15 +431,16 @@ final class VanillaItems{
self::register("echo_shard", new Item(new IID(Ids::ECHO_SHARD), "Echo Shard"));
self::register("egg", new Egg(new IID(Ids::EGG), "Egg"));
self::register("emerald", new Item(new IID(Ids::EMERALD), "Emerald"));
self::register("enchanted_book", new EnchantedBook(new IID(Ids::ENCHANTED_BOOK), "Enchanted Book", [EnchantmentTags::ALL]));
self::register("enchanted_golden_apple", new GoldenAppleEnchanted(new IID(Ids::ENCHANTED_GOLDEN_APPLE), "Enchanted Golden Apple"));
self::register("ender_pearl", new EnderPearl(new IID(Ids::ENDER_PEARL), "Ender Pearl"));
self::register("experience_bottle", new ExperienceBottle(new IID(Ids::EXPERIENCE_BOTTLE), "Bottle o' Enchanting"));
self::register("feather", new Item(new IID(Ids::FEATHER), "Feather"));
self::register("fermented_spider_eye", new Item(new IID(Ids::FERMENTED_SPIDER_EYE), "Fermented Spider Eye"));
self::register("fire_charge", new FireCharge(new IID(Ids::FIRE_CHARGE), "Fire Charge"));
self::register("fishing_rod", new FishingRod(new IID(Ids::FISHING_ROD), "Fishing Rod"));
self::register("fishing_rod", new FishingRod(new IID(Ids::FISHING_ROD), "Fishing Rod", [EnchantmentTags::FISHING_ROD]));
self::register("flint", new Item(new IID(Ids::FLINT), "Flint"));
self::register("flint_and_steel", new FlintSteel(new IID(Ids::FLINT_AND_STEEL), "Flint and Steel"));
self::register("flint_and_steel", new FlintSteel(new IID(Ids::FLINT_AND_STEEL), "Flint and Steel", [EnchantmentTags::FLINT_AND_STEEL]));
self::register("ghast_tear", new Item(new IID(Ids::GHAST_TEAR), "Ghast Tear"));
self::register("glass_bottle", new GlassBottle(new IID(Ids::GLASS_BOTTLE), "Glass Bottle"));
self::register("glistering_melon", new Item(new IID(Ids::GLISTERING_MELON), "Glistering Melon"));
@ -521,7 +524,7 @@ final class VanillaItems{
self::register("redstone_dust", new Redstone(new IID(Ids::REDSTONE_DUST), "Redstone"));
self::register("rotten_flesh", new RottenFlesh(new IID(Ids::ROTTEN_FLESH), "Rotten Flesh"));
self::register("scute", new Item(new IID(Ids::SCUTE), "Scute"));
self::register("shears", new Shears(new IID(Ids::SHEARS), "Shears"));
self::register("shears", new Shears(new IID(Ids::SHEARS), "Shears", [EnchantmentTags::SHEARS]));
self::register("shulker_shell", new Item(new IID(Ids::SHULKER_SHELL), "Shulker Shell"));
self::register("slimeball", new Item(new IID(Ids::SLIMEBALL), "Slimeball"));
self::register("snowball", new Snowball(new IID(Ids::SNOWBALL), "Snowball"));
@ -577,67 +580,67 @@ final class VanillaItems{
}
private static function registerTierToolItems() : void{
self::register("diamond_axe", new Axe(new IID(Ids::DIAMOND_AXE), "Diamond Axe", ToolTier::DIAMOND()));
self::register("golden_axe", new Axe(new IID(Ids::GOLDEN_AXE), "Golden Axe", ToolTier::GOLD()));
self::register("iron_axe", new Axe(new IID(Ids::IRON_AXE), "Iron Axe", ToolTier::IRON()));
self::register("netherite_axe", new Axe(new IID(Ids::NETHERITE_AXE), "Netherite Axe", ToolTier::NETHERITE()));
self::register("stone_axe", new Axe(new IID(Ids::STONE_AXE), "Stone Axe", ToolTier::STONE()));
self::register("wooden_axe", new Axe(new IID(Ids::WOODEN_AXE), "Wooden Axe", ToolTier::WOOD()));
self::register("diamond_hoe", new Hoe(new IID(Ids::DIAMOND_HOE), "Diamond Hoe", ToolTier::DIAMOND()));
self::register("golden_hoe", new Hoe(new IID(Ids::GOLDEN_HOE), "Golden Hoe", ToolTier::GOLD()));
self::register("iron_hoe", new Hoe(new IID(Ids::IRON_HOE), "Iron Hoe", ToolTier::IRON()));
self::register("netherite_hoe", new Hoe(new IID(Ids::NETHERITE_HOE), "Netherite Hoe", ToolTier::NETHERITE()));
self::register("stone_hoe", new Hoe(new IID(Ids::STONE_HOE), "Stone Hoe", ToolTier::STONE()));
self::register("wooden_hoe", new Hoe(new IID(Ids::WOODEN_HOE), "Wooden Hoe", ToolTier::WOOD()));
self::register("diamond_pickaxe", new Pickaxe(new IID(Ids::DIAMOND_PICKAXE), "Diamond Pickaxe", ToolTier::DIAMOND()));
self::register("golden_pickaxe", new Pickaxe(new IID(Ids::GOLDEN_PICKAXE), "Golden Pickaxe", ToolTier::GOLD()));
self::register("iron_pickaxe", new Pickaxe(new IID(Ids::IRON_PICKAXE), "Iron Pickaxe", ToolTier::IRON()));
self::register("netherite_pickaxe", new Pickaxe(new IID(Ids::NETHERITE_PICKAXE), "Netherite Pickaxe", ToolTier::NETHERITE()));
self::register("stone_pickaxe", new Pickaxe(new IID(Ids::STONE_PICKAXE), "Stone Pickaxe", ToolTier::STONE()));
self::register("wooden_pickaxe", new Pickaxe(new IID(Ids::WOODEN_PICKAXE), "Wooden Pickaxe", ToolTier::WOOD()));
self::register("diamond_shovel", new Shovel(new IID(Ids::DIAMOND_SHOVEL), "Diamond Shovel", ToolTier::DIAMOND()));
self::register("golden_shovel", new Shovel(new IID(Ids::GOLDEN_SHOVEL), "Golden Shovel", ToolTier::GOLD()));
self::register("iron_shovel", new Shovel(new IID(Ids::IRON_SHOVEL), "Iron Shovel", ToolTier::IRON()));
self::register("netherite_shovel", new Shovel(new IID(Ids::NETHERITE_SHOVEL), "Netherite Shovel", ToolTier::NETHERITE()));
self::register("stone_shovel", new Shovel(new IID(Ids::STONE_SHOVEL), "Stone Shovel", ToolTier::STONE()));
self::register("wooden_shovel", new Shovel(new IID(Ids::WOODEN_SHOVEL), "Wooden Shovel", ToolTier::WOOD()));
self::register("diamond_sword", new Sword(new IID(Ids::DIAMOND_SWORD), "Diamond Sword", ToolTier::DIAMOND()));
self::register("golden_sword", new Sword(new IID(Ids::GOLDEN_SWORD), "Golden Sword", ToolTier::GOLD()));
self::register("iron_sword", new Sword(new IID(Ids::IRON_SWORD), "Iron Sword", ToolTier::IRON()));
self::register("netherite_sword", new Sword(new IID(Ids::NETHERITE_SWORD), "Netherite Sword", ToolTier::NETHERITE()));
self::register("stone_sword", new Sword(new IID(Ids::STONE_SWORD), "Stone Sword", ToolTier::STONE()));
self::register("wooden_sword", new Sword(new IID(Ids::WOODEN_SWORD), "Wooden Sword", ToolTier::WOOD()));
self::register("diamond_axe", new Axe(new IID(Ids::DIAMOND_AXE), "Diamond Axe", ToolTier::DIAMOND(), [EnchantmentTags::AXE]));
self::register("golden_axe", new Axe(new IID(Ids::GOLDEN_AXE), "Golden Axe", ToolTier::GOLD(), [EnchantmentTags::AXE]));
self::register("iron_axe", new Axe(new IID(Ids::IRON_AXE), "Iron Axe", ToolTier::IRON(), [EnchantmentTags::AXE]));
self::register("netherite_axe", new Axe(new IID(Ids::NETHERITE_AXE), "Netherite Axe", ToolTier::NETHERITE(), [EnchantmentTags::AXE]));
self::register("stone_axe", new Axe(new IID(Ids::STONE_AXE), "Stone Axe", ToolTier::STONE(), [EnchantmentTags::AXE]));
self::register("wooden_axe", new Axe(new IID(Ids::WOODEN_AXE), "Wooden Axe", ToolTier::WOOD(), [EnchantmentTags::AXE]));
self::register("diamond_hoe", new Hoe(new IID(Ids::DIAMOND_HOE), "Diamond Hoe", ToolTier::DIAMOND(), [EnchantmentTags::HOE]));
self::register("golden_hoe", new Hoe(new IID(Ids::GOLDEN_HOE), "Golden Hoe", ToolTier::GOLD(), [EnchantmentTags::HOE]));
self::register("iron_hoe", new Hoe(new IID(Ids::IRON_HOE), "Iron Hoe", ToolTier::IRON(), [EnchantmentTags::HOE]));
self::register("netherite_hoe", new Hoe(new IID(Ids::NETHERITE_HOE), "Netherite Hoe", ToolTier::NETHERITE(), [EnchantmentTags::HOE]));
self::register("stone_hoe", new Hoe(new IID(Ids::STONE_HOE), "Stone Hoe", ToolTier::STONE(), [EnchantmentTags::HOE]));
self::register("wooden_hoe", new Hoe(new IID(Ids::WOODEN_HOE), "Wooden Hoe", ToolTier::WOOD(), [EnchantmentTags::HOE]));
self::register("diamond_pickaxe", new Pickaxe(new IID(Ids::DIAMOND_PICKAXE), "Diamond Pickaxe", ToolTier::DIAMOND(), [EnchantmentTags::PICKAXE]));
self::register("golden_pickaxe", new Pickaxe(new IID(Ids::GOLDEN_PICKAXE), "Golden Pickaxe", ToolTier::GOLD(), [EnchantmentTags::PICKAXE]));
self::register("iron_pickaxe", new Pickaxe(new IID(Ids::IRON_PICKAXE), "Iron Pickaxe", ToolTier::IRON(), [EnchantmentTags::PICKAXE]));
self::register("netherite_pickaxe", new Pickaxe(new IID(Ids::NETHERITE_PICKAXE), "Netherite Pickaxe", ToolTier::NETHERITE(), [EnchantmentTags::PICKAXE]));
self::register("stone_pickaxe", new Pickaxe(new IID(Ids::STONE_PICKAXE), "Stone Pickaxe", ToolTier::STONE(), [EnchantmentTags::PICKAXE]));
self::register("wooden_pickaxe", new Pickaxe(new IID(Ids::WOODEN_PICKAXE), "Wooden Pickaxe", ToolTier::WOOD(), [EnchantmentTags::PICKAXE]));
self::register("diamond_shovel", new Shovel(new IID(Ids::DIAMOND_SHOVEL), "Diamond Shovel", ToolTier::DIAMOND(), [EnchantmentTags::SHOVEL]));
self::register("golden_shovel", new Shovel(new IID(Ids::GOLDEN_SHOVEL), "Golden Shovel", ToolTier::GOLD(), [EnchantmentTags::SHOVEL]));
self::register("iron_shovel", new Shovel(new IID(Ids::IRON_SHOVEL), "Iron Shovel", ToolTier::IRON(), [EnchantmentTags::SHOVEL]));
self::register("netherite_shovel", new Shovel(new IID(Ids::NETHERITE_SHOVEL), "Netherite Shovel", ToolTier::NETHERITE(), [EnchantmentTags::SHOVEL]));
self::register("stone_shovel", new Shovel(new IID(Ids::STONE_SHOVEL), "Stone Shovel", ToolTier::STONE(), [EnchantmentTags::SHOVEL]));
self::register("wooden_shovel", new Shovel(new IID(Ids::WOODEN_SHOVEL), "Wooden Shovel", ToolTier::WOOD(), [EnchantmentTags::SHOVEL]));
self::register("diamond_sword", new Sword(new IID(Ids::DIAMOND_SWORD), "Diamond Sword", ToolTier::DIAMOND(), [EnchantmentTags::SWORD]));
self::register("golden_sword", new Sword(new IID(Ids::GOLDEN_SWORD), "Golden Sword", ToolTier::GOLD(), [EnchantmentTags::SWORD]));
self::register("iron_sword", new Sword(new IID(Ids::IRON_SWORD), "Iron Sword", ToolTier::IRON(), [EnchantmentTags::SWORD]));
self::register("netherite_sword", new Sword(new IID(Ids::NETHERITE_SWORD), "Netherite Sword", ToolTier::NETHERITE(), [EnchantmentTags::SWORD]));
self::register("stone_sword", new Sword(new IID(Ids::STONE_SWORD), "Stone Sword", ToolTier::STONE(), [EnchantmentTags::SWORD]));
self::register("wooden_sword", new Sword(new IID(Ids::WOODEN_SWORD), "Wooden Sword", ToolTier::WOOD(), [EnchantmentTags::SWORD]));
}
private static function registerArmorItems() : void{
self::register("chainmail_boots", new Armor(new IID(Ids::CHAINMAIL_BOOTS), "Chainmail Boots", new ArmorTypeInfo(1, 196, ArmorInventory::SLOT_FEET)));
self::register("diamond_boots", new Armor(new IID(Ids::DIAMOND_BOOTS), "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2)));
self::register("golden_boots", new Armor(new IID(Ids::GOLDEN_BOOTS), "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET)));
self::register("iron_boots", new Armor(new IID(Ids::IRON_BOOTS), "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET)));
self::register("leather_boots", new Armor(new IID(Ids::LEATHER_BOOTS), "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET)));
self::register("netherite_boots", new Armor(new IID(Ids::NETHERITE_BOOTS), "Netherite Boots", new ArmorTypeInfo(3, 482, ArmorInventory::SLOT_FEET, 3, true)));
self::register("chainmail_boots", new Armor(new IID(Ids::CHAINMAIL_BOOTS), "Chainmail Boots", new ArmorTypeInfo(1, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::BOOTS]));
self::register("diamond_boots", new Armor(new IID(Ids::DIAMOND_BOOTS), "Diamond Boots", new ArmorTypeInfo(3, 430, ArmorInventory::SLOT_FEET, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::BOOTS]));
self::register("golden_boots", new Armor(new IID(Ids::GOLDEN_BOOTS), "Golden Boots", new ArmorTypeInfo(1, 92, ArmorInventory::SLOT_FEET, material: ArmorMaterials::GOLD()), [EnchantmentTags::BOOTS]));
self::register("iron_boots", new Armor(new IID(Ids::IRON_BOOTS), "Iron Boots", new ArmorTypeInfo(2, 196, ArmorInventory::SLOT_FEET, material: ArmorMaterials::IRON()), [EnchantmentTags::BOOTS]));
self::register("leather_boots", new Armor(new IID(Ids::LEATHER_BOOTS), "Leather Boots", new ArmorTypeInfo(1, 66, ArmorInventory::SLOT_FEET, material: ArmorMaterials::LEATHER()), [EnchantmentTags::BOOTS]));
self::register("netherite_boots", new Armor(new IID(Ids::NETHERITE_BOOTS), "Netherite Boots", new ArmorTypeInfo(3, 482, ArmorInventory::SLOT_FEET, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::BOOTS]));
self::register("chainmail_chestplate", new Armor(new IID(Ids::CHAINMAIL_CHESTPLATE), "Chainmail Chestplate", new ArmorTypeInfo(5, 241, ArmorInventory::SLOT_CHEST)));
self::register("diamond_chestplate", new Armor(new IID(Ids::DIAMOND_CHESTPLATE), "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2)));
self::register("golden_chestplate", new Armor(new IID(Ids::GOLDEN_CHESTPLATE), "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST)));
self::register("iron_chestplate", new Armor(new IID(Ids::IRON_CHESTPLATE), "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST)));
self::register("leather_tunic", new Armor(new IID(Ids::LEATHER_TUNIC), "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST)));
self::register("netherite_chestplate", new Armor(new IID(Ids::NETHERITE_CHESTPLATE), "Netherite Chestplate", new ArmorTypeInfo(8, 593, ArmorInventory::SLOT_CHEST, 3, true)));
self::register("chainmail_chestplate", new Armor(new IID(Ids::CHAINMAIL_CHESTPLATE), "Chainmail Chestplate", new ArmorTypeInfo(5, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::CHESTPLATE]));
self::register("diamond_chestplate", new Armor(new IID(Ids::DIAMOND_CHESTPLATE), "Diamond Chestplate", new ArmorTypeInfo(8, 529, ArmorInventory::SLOT_CHEST, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::CHESTPLATE]));
self::register("golden_chestplate", new Armor(new IID(Ids::GOLDEN_CHESTPLATE), "Golden Chestplate", new ArmorTypeInfo(5, 113, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::GOLD()), [EnchantmentTags::CHESTPLATE]));
self::register("iron_chestplate", new Armor(new IID(Ids::IRON_CHESTPLATE), "Iron Chestplate", new ArmorTypeInfo(6, 241, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::IRON()), [EnchantmentTags::CHESTPLATE]));
self::register("leather_tunic", new Armor(new IID(Ids::LEATHER_TUNIC), "Leather Tunic", new ArmorTypeInfo(3, 81, ArmorInventory::SLOT_CHEST, material: ArmorMaterials::LEATHER()), [EnchantmentTags::CHESTPLATE]));
self::register("netherite_chestplate", new Armor(new IID(Ids::NETHERITE_CHESTPLATE), "Netherite Chestplate", new ArmorTypeInfo(8, 593, ArmorInventory::SLOT_CHEST, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::CHESTPLATE]));
self::register("chainmail_helmet", new Armor(new IID(Ids::CHAINMAIL_HELMET), "Chainmail Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD)));
self::register("diamond_helmet", new Armor(new IID(Ids::DIAMOND_HELMET), "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2)));
self::register("golden_helmet", new Armor(new IID(Ids::GOLDEN_HELMET), "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD)));
self::register("iron_helmet", new Armor(new IID(Ids::IRON_HELMET), "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD)));
self::register("leather_cap", new Armor(new IID(Ids::LEATHER_CAP), "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD)));
self::register("netherite_helmet", new Armor(new IID(Ids::NETHERITE_HELMET), "Netherite Helmet", new ArmorTypeInfo(3, 408, ArmorInventory::SLOT_HEAD, 3, true)));
self::register("turtle_helmet", new TurtleHelmet(new IID(Ids::TURTLE_HELMET), "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD)));
self::register("chainmail_helmet", new Armor(new IID(Ids::CHAINMAIL_HELMET), "Chainmail Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::HELMET]));
self::register("diamond_helmet", new Armor(new IID(Ids::DIAMOND_HELMET), "Diamond Helmet", new ArmorTypeInfo(3, 364, ArmorInventory::SLOT_HEAD, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::HELMET]));
self::register("golden_helmet", new Armor(new IID(Ids::GOLDEN_HELMET), "Golden Helmet", new ArmorTypeInfo(2, 78, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::GOLD()), [EnchantmentTags::HELMET]));
self::register("iron_helmet", new Armor(new IID(Ids::IRON_HELMET), "Iron Helmet", new ArmorTypeInfo(2, 166, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::IRON()), [EnchantmentTags::HELMET]));
self::register("leather_cap", new Armor(new IID(Ids::LEATHER_CAP), "Leather Cap", new ArmorTypeInfo(1, 56, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::LEATHER()), [EnchantmentTags::HELMET]));
self::register("netherite_helmet", new Armor(new IID(Ids::NETHERITE_HELMET), "Netherite Helmet", new ArmorTypeInfo(3, 408, ArmorInventory::SLOT_HEAD, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::HELMET]));
self::register("turtle_helmet", new TurtleHelmet(new IID(Ids::TURTLE_HELMET), "Turtle Shell", new ArmorTypeInfo(2, 276, ArmorInventory::SLOT_HEAD, material: ArmorMaterials::TURTLE()), [EnchantmentTags::HELMET]));
self::register("chainmail_leggings", new Armor(new IID(Ids::CHAINMAIL_LEGGINGS), "Chainmail Leggings", new ArmorTypeInfo(4, 226, ArmorInventory::SLOT_LEGS)));
self::register("diamond_leggings", new Armor(new IID(Ids::DIAMOND_LEGGINGS), "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2)));
self::register("golden_leggings", new Armor(new IID(Ids::GOLDEN_LEGGINGS), "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS)));
self::register("iron_leggings", new Armor(new IID(Ids::IRON_LEGGINGS), "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS)));
self::register("leather_pants", new Armor(new IID(Ids::LEATHER_PANTS), "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS)));
self::register("netherite_leggings", new Armor(new IID(Ids::NETHERITE_LEGGINGS), "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true)));
self::register("chainmail_leggings", new Armor(new IID(Ids::CHAINMAIL_LEGGINGS), "Chainmail Leggings", new ArmorTypeInfo(4, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::CHAINMAIL()), [EnchantmentTags::LEGGINGS]));
self::register("diamond_leggings", new Armor(new IID(Ids::DIAMOND_LEGGINGS), "Diamond Leggings", new ArmorTypeInfo(6, 496, ArmorInventory::SLOT_LEGS, 2, material: ArmorMaterials::DIAMOND()), [EnchantmentTags::LEGGINGS]));
self::register("golden_leggings", new Armor(new IID(Ids::GOLDEN_LEGGINGS), "Golden Leggings", new ArmorTypeInfo(3, 106, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::GOLD()), [EnchantmentTags::LEGGINGS]));
self::register("iron_leggings", new Armor(new IID(Ids::IRON_LEGGINGS), "Iron Leggings", new ArmorTypeInfo(5, 226, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::IRON()), [EnchantmentTags::LEGGINGS]));
self::register("leather_pants", new Armor(new IID(Ids::LEATHER_PANTS), "Leather Pants", new ArmorTypeInfo(2, 76, ArmorInventory::SLOT_LEGS, material: ArmorMaterials::LEATHER()), [EnchantmentTags::LEGGINGS]));
self::register("netherite_leggings", new Armor(new IID(Ids::NETHERITE_LEGGINGS), "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::LEGGINGS]));
}
}

View File

@ -0,0 +1,211 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
use pocketmine\item\enchantment\ItemEnchantmentTagRegistry as TagRegistry;
use pocketmine\item\enchantment\ItemEnchantmentTags as Tags;
use pocketmine\item\enchantment\VanillaEnchantments as Enchantments;
use pocketmine\item\Item;
use pocketmine\utils\SingletonTrait;
use function array_filter;
use function array_values;
use function count;
use function spl_object_id;
/**
* Registry of enchantments that can be applied to items during in-game enchanting (enchanting table, anvil, fishing, etc.).
*/
final class AvailableEnchantmentRegistry{
use SingletonTrait;
/** @var Enchantment[] */
private array $enchantments = [];
/** @var string[][] */
private array $primaryItemTags = [];
/** @var string[][] */
private array $secondaryItemTags = [];
private function __construct(){
$this->register(Enchantments::PROTECTION(), [Tags::ARMOR], []);
$this->register(Enchantments::FIRE_PROTECTION(), [Tags::ARMOR], []);
$this->register(Enchantments::FEATHER_FALLING(), [Tags::BOOTS], []);
$this->register(Enchantments::BLAST_PROTECTION(), [Tags::ARMOR], []);
$this->register(Enchantments::PROJECTILE_PROTECTION(), [Tags::ARMOR], []);
$this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]);
$this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []);
$this->register(Enchantments::SHARPNESS(), [Tags::SWORD, Tags::AXE], []);
$this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []);
$this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []);
$this->register(Enchantments::EFFICIENCY(), [Tags::BLOCK_TOOLS], [Tags::SHEARS]);
$this->register(Enchantments::FORTUNE(), [Tags::BLOCK_TOOLS], []);
$this->register(Enchantments::SILK_TOUCH(), [Tags::BLOCK_TOOLS], [Tags::SHEARS]);
$this->register(
Enchantments::UNBREAKING(),
[Tags::ARMOR, Tags::WEAPONS, Tags::FISHING_ROD],
[Tags::SHEARS, Tags::FLINT_AND_STEEL, Tags::SHIELD, Tags::CARROT_ON_STICK, Tags::ELYTRA, Tags::BRUSH]
);
$this->register(Enchantments::POWER(), [Tags::BOW], []);
$this->register(Enchantments::PUNCH(), [Tags::BOW], []);
$this->register(Enchantments::FLAME(), [Tags::BOW], []);
$this->register(Enchantments::INFINITY(), [Tags::BOW], []);
$this->register(
Enchantments::MENDING(),
[],
[Tags::ARMOR, Tags::WEAPONS, Tags::FISHING_ROD,
Tags::SHEARS, Tags::FLINT_AND_STEEL, Tags::SHIELD, Tags::CARROT_ON_STICK, Tags::ELYTRA, Tags::BRUSH]
);
$this->register(Enchantments::VANISHING(), [], [Tags::ALL]);
$this->register(Enchantments::SWIFT_SNEAK(), [], [Tags::LEGGINGS]);
}
/**
* @param string[] $primaryItemTags
* @param string[] $secondaryItemTags
*/
public function register(Enchantment $enchantment, array $primaryItemTags, array $secondaryItemTags) : void{
$this->enchantments[spl_object_id($enchantment)] = $enchantment;
$this->setPrimaryItemTags($enchantment, $primaryItemTags);
$this->setSecondaryItemTags($enchantment, $secondaryItemTags);
}
public function unregister(Enchantment $enchantment) : void{
unset($this->enchantments[spl_object_id($enchantment)]);
unset($this->primaryItemTags[spl_object_id($enchantment)]);
unset($this->secondaryItemTags[spl_object_id($enchantment)]);
}
public function unregisterAll() : void{
$this->enchantments = [];
$this->primaryItemTags = [];
$this->secondaryItemTags = [];
}
public function isRegistered(Enchantment $enchantment) : bool{
return isset($this->enchantments[spl_object_id($enchantment)]);
}
/**
* Returns primary compatibility tags for the specified enchantment.
*
* An item matching at least one of these tags (or its descendents) can be:
* - Offered this enchantment in an enchanting table
* - Enchanted by any means allowed by secondary tags
*
* @return string[]
*/
public function getPrimaryItemTags(Enchantment $enchantment) : array{
return $this->primaryItemTags[spl_object_id($enchantment)] ?? [];
}
/**
* @param string[] $tags
*/
public function setPrimaryItemTags(Enchantment $enchantment, array $tags) : void{
if(!$this->isRegistered($enchantment)){
throw new \LogicException("Cannot set primary item tags for non-registered enchantment");
}
$this->primaryItemTags[spl_object_id($enchantment)] = array_values($tags);
}
/**
* Returns secondary compatibility tags for the specified enchantment.
*
* An item matching at least one of these tags (or its descendents) can be:
* - Combined with an enchanted book with this enchantment in an anvil
* - Obtained as loot with this enchantment, e.g. fishing, treasure chests, mob equipment, etc.
*
* @return string[]
*/
public function getSecondaryItemTags(Enchantment $enchantment) : array{
return $this->secondaryItemTags[spl_object_id($enchantment)] ?? [];
}
/**
* @param string[] $tags
*/
public function setSecondaryItemTags(Enchantment $enchantment, array $tags) : void{
if(!$this->isRegistered($enchantment)){
throw new \LogicException("Cannot set secondary item tags for non-registered enchantment");
}
$this->secondaryItemTags[spl_object_id($enchantment)] = array_values($tags);
}
/**
* Returns enchantments that can be applied to the specified item in an enchanting table (primary only).
*
* @return Enchantment[]
*/
public function getPrimaryEnchantmentsForItem(Item $item) : array{
$itemTags = $item->getEnchantmentTags();
if(count($itemTags) === 0 || $item->hasEnchantments()){
return [];
}
return array_filter(
$this->enchantments,
fn(Enchantment $e) => TagRegistry::getInstance()->isTagArrayIntersection($this->getPrimaryItemTags($e), $itemTags)
);
}
/**
* Returns all available enchantments compatible with the item.
*
* Warning: not suitable for obtaining enchantments for an enchanting table
* (use {@link AvailableEnchantmentRegistry::getPrimaryEnchantmentsForItem()} for that).
*
* @return Enchantment[]
*/
public function getAllEnchantmentsForItem(Item $item) : array{
if(count($item->getEnchantmentTags()) === 0){
return [];
}
return array_filter(
$this->enchantments,
fn(Enchantment $enchantment) => $this->isAvailableForItem($enchantment, $item)
);
}
/**
* Returns whether the specified enchantment can be applied to the particular item.
*
* Warning: not suitable for checking the availability of enchantment for an enchanting table.
*/
public function isAvailableForItem(Enchantment $enchantment, Item $item) : bool{
$itemTags = $item->getEnchantmentTags();
$tagRegistry = TagRegistry::getInstance();
return $tagRegistry->isTagArrayIntersection($this->getPrimaryItemTags($enchantment), $itemTags) ||
$tagRegistry->isTagArrayIntersection($this->getSecondaryItemTags($enchantment), $itemTags);
}
/**
* @return Enchantment[]
*/
public function getAll() : array{
return $this->enchantments;
}
}

View File

@ -0,0 +1,233 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
use pocketmine\block\BlockTypeIds;
use pocketmine\item\enchantment\AvailableEnchantmentRegistry as EnchantmentRegistry;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\item\VanillaItems as Items;
use pocketmine\utils\Limits;
use pocketmine\utils\Random;
use pocketmine\world\Position;
use function abs;
use function array_filter;
use function chr;
use function count;
use function floor;
use function max;
use function min;
use function mt_rand;
use function ord;
use function round;
/**
* Helper methods used for enchanting using the enchanting table.
*/
final class EnchantingHelper{
private const MAX_BOOKSHELF_COUNT = 15;
private function __construct(){
//NOOP
}
/**
* Generates a new random seed for enchant option randomization.
*/
public static function generateSeed() : int{
return mt_rand(Limits::INT32_MIN, Limits::INT32_MAX);
}
/**
* @param EnchantmentInstance[] $enchantments
*/
public static function enchantItem(Item $item, array $enchantments) : Item{
$resultItem = $item->getTypeId() === ItemTypeIds::BOOK ? Items::ENCHANTED_BOOK() : clone $item;
foreach($enchantments as $enchantment){
$resultItem->addEnchantment($enchantment);
}
return $resultItem;
}
/**
* @return EnchantingOption[]
*/
public static function generateOptions(Position $tablePos, Item $input, int $seed) : array{
if($input->isNull() || $input->hasEnchantments()){
return [];
}
$random = new Random($seed);
$bookshelfCount = self::countBookshelves($tablePos);
$baseRequiredLevel = $random->nextRange(1, 8) + ($bookshelfCount >> 1) + $random->nextRange(0, $bookshelfCount);
$topRequiredLevel = (int) floor(max($baseRequiredLevel / 3, 1));
$middleRequiredLevel = (int) floor($baseRequiredLevel * 2 / 3 + 1);
$bottomRequiredLevel = max($baseRequiredLevel, $bookshelfCount * 2);
return [
self::createOption($random, $input, $topRequiredLevel),
self::createOption($random, $input, $middleRequiredLevel),
self::createOption($random, $input, $bottomRequiredLevel),
];
}
private static function countBookshelves(Position $tablePos) : int{
$bookshelfCount = 0;
$world = $tablePos->getWorld();
for($x = -2; $x <= 2; $x++){
for($z = -2; $z <= 2; $z++){
// We only check blocks at a distance of 2 blocks from the enchanting table
if(abs($x) !== 2 && abs($z) !== 2){
continue;
}
// Ensure the space between the bookshelf stack at this X/Z and the enchanting table is empty
for($y = 0; $y <= 1; $y++){
// Calculate the coordinates of the space between the bookshelf and the enchanting table
$spaceX = max(min($x, 1), -1);
$spaceZ = max(min($z, 1), -1);
$spaceBlock = $world->getBlock($tablePos->add($spaceX, $y, $spaceZ));
if($spaceBlock->getTypeId() !== BlockTypeIds::AIR){
continue 2;
}
}
// Finally, check the number of bookshelves at the current position
for($y = 0; $y <= 1; $y++){
$block = $world->getBlock($tablePos->add($x, $y, $z));
if($block->getTypeId() === BlockTypeIds::BOOKSHELF){
$bookshelfCount++;
if($bookshelfCount === self::MAX_BOOKSHELF_COUNT){
return $bookshelfCount;
}
}
}
}
}
return $bookshelfCount;
}
private static function createOption(Random $random, Item $inputItem, int $requiredXpLevel) : EnchantingOption{
$enchantingPower = $requiredXpLevel;
$enchantability = $inputItem->getEnchantability();
$enchantingPower = $enchantingPower + $random->nextRange(0, $enchantability >> 2) + $random->nextRange(0, $enchantability >> 2) + 1;
// Random bonus for enchanting power between 0.85 and 1.15
$bonus = 1 + ($random->nextFloat() + $random->nextFloat() - 1) * 0.15;
$enchantingPower = (int) round($enchantingPower * $bonus);
$resultEnchantments = [];
$availableEnchantments = self::getAvailableEnchantments($enchantingPower, $inputItem);
$lastEnchantment = self::getRandomWeightedEnchantment($random, $availableEnchantments);
if($lastEnchantment !== null){
$resultEnchantments[] = $lastEnchantment;
// With probability (power + 1) / 50, continue adding enchantments
while($random->nextFloat() <= ($enchantingPower + 1) / 50){
// Remove from the list of available enchantments anything that conflicts
// with previously-chosen enchantments
$availableEnchantments = array_filter(
$availableEnchantments,
function(EnchantmentInstance $e) use ($lastEnchantment){
return $e->getType() !== $lastEnchantment->getType() &&
$e->getType()->isCompatibleWith($lastEnchantment->getType());
}
);
$lastEnchantment = self::getRandomWeightedEnchantment($random, $availableEnchantments);
if($lastEnchantment === null){
break;
}
$resultEnchantments[] = $lastEnchantment;
$enchantingPower >>= 1;
}
}
return new EnchantingOption($requiredXpLevel, self::getRandomOptionName($random), $resultEnchantments);
}
/**
* @return EnchantmentInstance[]
*/
private static function getAvailableEnchantments(int $enchantingPower, Item $item) : array{
$list = [];
foreach(EnchantmentRegistry::getInstance()->getPrimaryEnchantmentsForItem($item) as $enchantment){
for($lvl = $enchantment->getMaxLevel(); $lvl > 0; $lvl--){
if($enchantingPower >= $enchantment->getMinEnchantingPower($lvl) &&
$enchantingPower <= $enchantment->getMaxEnchantingPower($lvl)
){
$list[] = new EnchantmentInstance($enchantment, $lvl);
break;
}
}
}
return $list;
}
/**
* @param EnchantmentInstance[] $enchantments
*/
private static function getRandomWeightedEnchantment(Random $random, array $enchantments) : ?EnchantmentInstance{
if(count($enchantments) === 0){
return null;
}
$totalWeight = 0;
foreach($enchantments as $enchantment){
$totalWeight += $enchantment->getType()->getRarity();
}
$result = null;
$randomWeight = $random->nextRange(1, $totalWeight);
foreach($enchantments as $enchantment){
$randomWeight -= $enchantment->getType()->getRarity();
if($randomWeight <= 0){
$result = $enchantment;
break;
}
}
return $result;
}
private static function getRandomOptionName(Random $random) : string{
$name = "";
for($i = $random->nextRange(5, 15); $i > 0; $i--){
$name .= chr($random->nextRange(ord("a"), ord("z")));
}
return $name;
}
}

View File

@ -0,0 +1,66 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
/**
* Represents an option on the enchanting table menu.
* If selected, all the enchantments in the option will be applied to the item.
*/
class EnchantingOption{
/**
* @param EnchantmentInstance[] $enchantments
*/
public function __construct(
private int $requiredXpLevel,
private string $displayName,
private array $enchantments
){}
/**
* Returns the minimum amount of XP levels required to select this enchantment option.
* It's NOT the number of XP levels that will be subtracted after enchanting.
*/
public function getRequiredXpLevel() : int{
return $this->requiredXpLevel;
}
/**
* Returns the name that will be translated to the 'Standard Galactic Alphabet' client-side.
* This can be any arbitrary text string, since the vanilla client cannot read the text anyway.
* Example: 'bless creature range free'.
*/
public function getDisplayName() : string{
return $this->displayName;
}
/**
* Returns the enchantments that will be applied to the item when this option is clicked.
*
* @return EnchantmentInstance[]
*/
public function getEnchantments() : array{
return $this->enchantments;
}
}

View File

@ -23,9 +23,13 @@ declare(strict_types=1);
namespace pocketmine\item\enchantment;
use DaveRandom\CallbackValidator\CallbackType;
use DaveRandom\CallbackValidator\ParameterType;
use DaveRandom\CallbackValidator\ReturnType;
use pocketmine\lang\Translatable;
use pocketmine\utils\NotCloneable;
use pocketmine\utils\NotSerializable;
use pocketmine\utils\Utils;
/**
* Manages enchantment type data.
@ -34,13 +38,32 @@ class Enchantment{
use NotCloneable;
use NotSerializable;
/** @var \Closure(int $level) : int $minEnchantingPower */
private \Closure $minEnchantingPower;
/**
* @phpstan-param null|(\Closure(int $level) : int) $minEnchantingPower
*
* @param int $primaryItemFlags @deprecated
* @param int $secondaryItemFlags @deprecated
* @param int $enchantingPowerRange Value used to calculate the maximum enchanting power (minEnchantingPower + enchantingPowerRange)
*/
public function __construct(
private Translatable|string $name,
private int $rarity,
private int $primaryItemFlags,
private int $secondaryItemFlags,
private int $maxLevel
){}
private int $maxLevel,
?\Closure $minEnchantingPower = null,
private int $enchantingPowerRange = 50
){
$this->minEnchantingPower = $minEnchantingPower ?? fn(int $level) : int => 1;
Utils::validateCallableSignature(new CallbackType(
new ReturnType("int"),
new ParameterType("level", "int")
), $this->minEnchantingPower);
}
/**
* Returns a translation key for this enchantment's name.
@ -58,6 +81,9 @@ class Enchantment{
/**
* Returns a bitset indicating what item types can have this item applied from an enchanting table.
*
* @deprecated
* @see AvailableEnchantmentRegistry::getPrimaryItemTags()
*/
public function getPrimaryItemFlags() : int{
return $this->primaryItemFlags;
@ -66,6 +92,9 @@ class Enchantment{
/**
* Returns a bitset indicating what item types cannot have this item applied from an enchanting table, but can from
* an anvil.
*
* @deprecated
* @see AvailableEnchantmentRegistry::getSecondaryItemTags()
*/
public function getSecondaryItemFlags() : int{
return $this->secondaryItemFlags;
@ -73,6 +102,9 @@ class Enchantment{
/**
* Returns whether this enchantment can apply to the item type from an enchanting table.
*
* @deprecated
* @see AvailableEnchantmentRegistry
*/
public function hasPrimaryItemType(int $flag) : bool{
return ($this->primaryItemFlags & $flag) !== 0;
@ -80,6 +112,9 @@ class Enchantment{
/**
* Returns whether this enchantment can apply to the item type from an anvil, if it is not a primary item.
*
* @deprecated
* @see AvailableEnchantmentRegistry
*/
public function hasSecondaryItemType(int $flag) : bool{
return ($this->secondaryItemFlags & $flag) !== 0;
@ -92,5 +127,34 @@ class Enchantment{
return $this->maxLevel;
}
//TODO: methods for min/max XP cost bounds based on enchantment level (not needed yet - enchanting is client-side)
/**
* Returns whether this enchantment can be applied to the item along with the given enchantment.
*/
public function isCompatibleWith(Enchantment $other) : bool{
return IncompatibleEnchantmentRegistry::getInstance()->areCompatible($this, $other);
}
/**
* Returns the minimum enchanting power value required for the particular level of the enchantment
* to be available in an enchanting table.
*
* Enchanting power is a random value based on the number of bookshelves around an enchanting table
* and the enchantability of the item being enchanted. It is only used when determining the available
* enchantments for the enchantment options.
*/
public function getMinEnchantingPower(int $level) : int{
return ($this->minEnchantingPower)($level);
}
/**
* Returns the maximum enchanting power value allowed for the particular level of the enchantment
* to be available in an enchanting table.
*
* Enchanting power is a random value based on the number of bookshelves around an enchanting table
* and the enchantability of the item being enchanted. It is only used when determining the available
* enchantments for the enchantment options.
*/
public function getMaxEnchantingPower(int $level) : int{
return $this->getMinEnchantingPower($level) + $this->enchantingPowerRange;
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
/**
* Constants for groupings of incompatible enchantments.
* Enchantments belonging to the same incompatibility group cannot be applied side-by-side on the same item.
*/
final class IncompatibleEnchantmentGroups{
public const PROTECTION = "protection";
public const BOW_INFINITE = "bow_infinite";
public const BLOCK_DROPS = "block_drops";
}

View File

@ -0,0 +1,94 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
use pocketmine\item\enchantment\IncompatibleEnchantmentGroups as Groups;
use pocketmine\item\enchantment\VanillaEnchantments as Enchantments;
use pocketmine\utils\SingletonTrait;
use function array_intersect_key;
use function count;
use function spl_object_id;
/**
* Manages which enchantments are incompatible with each other.
* Enchantments can be added to groups to make them incompatible with all other enchantments already in that group.
*/
final class IncompatibleEnchantmentRegistry{
use SingletonTrait;
/**
* @phpstan-var array<int, array<string, true>>
* @var true[][]
*/
private array $incompatibilityMap = [];
private function __construct(){
$this->register(Groups::PROTECTION, [Enchantments::PROTECTION(), Enchantments::FIRE_PROTECTION(), Enchantments::BLAST_PROTECTION(), Enchantments::PROJECTILE_PROTECTION()]);
$this->register(Groups::BOW_INFINITE, [Enchantments::INFINITY(), Enchantments::MENDING()]);
$this->register(Groups::BLOCK_DROPS, [Enchantments::FORTUNE(), Enchantments::SILK_TOUCH()]);
}
/**
* Register incompatibility for an enchantment group.
*
* All enchantments belonging to the same group are incompatible with each other,
* i.e. they cannot be added together on the same item.
*
* @param Enchantment[] $enchantments
*/
public function register(string $tag, array $enchantments) : void{
foreach($enchantments as $enchantment){
$this->incompatibilityMap[spl_object_id($enchantment)][$tag] = true;
}
}
/**
* Unregister incompatibility for some enchantments of a particular group.
*
* @param Enchantment[] $enchantments
*/
public function unregister(string $tag, array $enchantments) : void{
foreach($enchantments as $enchantment){
unset($this->incompatibilityMap[spl_object_id($enchantment)][$tag]);
}
}
/**
* Unregister incompatibility for all enchantments of a particular group.
*/
public function unregisterAll(string $tag) : void{
foreach($this->incompatibilityMap as $id => $tags){
unset($this->incompatibilityMap[$id][$tag]);
}
}
/**
* Returns whether two enchantments can be applied to the same item.
*/
public function areCompatible(Enchantment $first, Enchantment $second) : bool{
$firstIncompatibilities = $this->incompatibilityMap[spl_object_id($first)] ?? [];
$secondIncompatibilities = $this->incompatibilityMap[spl_object_id($second)] ?? [];
return count(array_intersect_key($firstIncompatibilities, $secondIncompatibilities)) === 0;
}
}

View File

@ -0,0 +1,190 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
use pocketmine\item\enchantment\ItemEnchantmentTags as Tags;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use function array_diff;
use function array_intersect;
use function array_merge;
use function array_search;
use function array_shift;
use function array_unique;
use function count;
/**
* Manages known item enchantment tags and the relations between them.
* Used to determine which tags belong to which other tags, and to check if lists of tags intersect.
*/
final class ItemEnchantmentTagRegistry{
use SingletonTrait;
/**
* @phpstan-var array<string, list<string>>
* @var string[][]
*/
private array $tagMap = [];
private function __construct(){
$this->register(Tags::ARMOR, [Tags::HELMET, Tags::CHESTPLATE, Tags::LEGGINGS, Tags::BOOTS]);
$this->register(Tags::SHIELD);
$this->register(Tags::SWORD);
$this->register(Tags::TRIDENT);
$this->register(Tags::BOW);
$this->register(Tags::CROSSBOW);
$this->register(Tags::SHEARS);
$this->register(Tags::FLINT_AND_STEEL);
$this->register(Tags::BLOCK_TOOLS, [Tags::AXE, Tags::PICKAXE, Tags::SHOVEL, Tags::HOE]);
$this->register(Tags::FISHING_ROD);
$this->register(Tags::CARROT_ON_STICK);
$this->register(Tags::COMPASS);
$this->register(Tags::MASK);
$this->register(Tags::ELYTRA);
$this->register(Tags::BRUSH);
$this->register(Tags::WEAPONS, [
Tags::SWORD,
Tags::TRIDENT,
Tags::BOW,
Tags::CROSSBOW,
Tags::BLOCK_TOOLS,
]);
}
/**
* Register tag and its nested tags.
*
* @param string[] $nestedTags
*/
public function register(string $tag, array $nestedTags = []) : void{
$this->assertNotInternalTag($tag);
foreach($nestedTags as $nestedTag){
if(!isset($this->tagMap[$nestedTag])){
$this->register($nestedTag);
}
$this->tagMap[$tag][] = $nestedTag;
}
if(!isset($this->tagMap[$tag])){
$this->tagMap[$tag] = [];
$this->tagMap[Tags::ALL][] = $tag;
}
}
public function unregister(string $tag) : void{
if(!isset($this->tagMap[$tag])){
return;
}
$this->assertNotInternalTag($tag);
unset($this->tagMap[$tag]);
foreach(Utils::stringifyKeys($this->tagMap) as $key => $nestedTags){
if(($nestedKey = array_search($tag, $nestedTags, true)) !== false){
unset($this->tagMap[$key][$nestedKey]);
}
}
}
/**
* Remove specified nested tags.
*
* @param string[] $nestedTags
*/
public function removeNested(string $tag, array $nestedTags) : void{
$this->assertNotInternalTag($tag);
$this->tagMap[$tag] = array_diff($this->tagMap[$tag], $nestedTags);
}
/**
* Returns nested tags of a particular tag.
*
* @return string[]
*/
public function getNested(string $tag) : array{
return $this->tagMap[$tag] ?? [];
}
/**
* @param string[] $firstTags
* @param string[] $secondTags
*/
public function isTagArrayIntersection(array $firstTags, array $secondTags) : bool{
if(count($firstTags) === 0 || count($secondTags) === 0){
return false;
}
$firstLeafTags = $this->getLeafTagsForArray($firstTags);
$secondLeafTags = $this->getLeafTagsForArray($secondTags);
return count(array_intersect($firstLeafTags, $secondLeafTags)) !== 0;
}
/**
* Returns all tags that are recursively nested within each tag in the array and do not have any nested tags.
*
* @param string[] $tags
*
* @return string[]
*/
private function getLeafTagsForArray(array $tags) : array{
$leafTagArrays = [];
foreach($tags as $tag){
$leafTagArrays[] = $this->getLeafTags($tag);
}
return array_unique(array_merge(...$leafTagArrays));
}
/**
* Returns all tags that are recursively nested within the given tag and do not have any nested tags.
*
* @return string[]
*/
private function getLeafTags(string $tag) : array{
$result = [];
$tagsToHandle = [$tag];
while(count($tagsToHandle) !== 0){
$currentTag = array_shift($tagsToHandle);
$nestedTags = $this->getNested($currentTag);
if(count($nestedTags) === 0){
$result[] = $currentTag;
}else{
$tagsToHandle = array_merge($tagsToHandle, $nestedTags);
}
}
return $result;
}
private function assertNotInternalTag(string $tag) : void{
if($tag === Tags::ALL){
throw new \InvalidArgumentException(
"Cannot perform any operations on the internal item enchantment tag '$tag'"
);
}
}
}

View File

@ -0,0 +1,57 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
/**
* Tags used by items and enchantments to determine which enchantments can be applied to which items.
* Some tags may contain other tags.
* @see ItemEnchantmentTagRegistry
*/
final class ItemEnchantmentTags{
public const ALL = "all";
public const ARMOR = "armor";
public const HELMET = "helmet";
public const CHESTPLATE = "chestplate";
public const LEGGINGS = "leggings";
public const BOOTS = "boots";
public const SHIELD = "shield";
public const SWORD = "sword";
public const TRIDENT = "trident";
public const BOW = "bow";
public const CROSSBOW = "crossbow";
public const SHEARS = "shears";
public const FLINT_AND_STEEL = "flint_and_steel";
public const BLOCK_TOOLS = "block_tools";
public const AXE = "axe";
public const PICKAXE = "pickaxe";
public const SHOVEL = "shovel";
public const HOE = "hoe";
public const FISHING_ROD = "fishing_rod";
public const CARROT_ON_STICK = "carrot_on_stick";
public const COMPASS = "compass";
public const MASK = "mask";
public const ELYTRA = "elytra";
public const BRUSH = "brush";
public const WEAPONS = "weapons";
}

View File

@ -23,14 +23,13 @@ declare(strict_types=1);
namespace pocketmine\item\enchantment;
/** @deprecated */
final class ItemFlags{
private function __construct(){
//NOOP
}
//TODO: this should probably move to protocol
public const NONE = 0x0;
public const ALL = 0xffff;
public const ARMOR = self::HEAD | self::TORSO | self::LEGS | self::FEET;

View File

@ -36,10 +36,15 @@ class ProtectionEnchantment extends Enchantment{
/**
* ProtectionEnchantment constructor.
*
* @phpstan-param null|(\Closure(int $level) : int) $minEnchantingPower
*
* @param int $primaryItemFlags @deprecated
* @param int $secondaryItemFlags @deprecated
* @param int[]|null $applicableDamageTypes EntityDamageEvent::CAUSE_* constants which this enchantment type applies to, or null if it applies to all types of damage.
* @param int $enchantingPowerRange Value used to calculate the maximum enchanting power (minEnchantingPower + enchantingPowerRange)
*/
public function __construct(Translatable|string $name, int $rarity, int $primaryItemFlags, int $secondaryItemFlags, int $maxLevel, float $typeModifier, ?array $applicableDamageTypes){
parent::__construct($name, $rarity, $primaryItemFlags, $secondaryItemFlags, $maxLevel);
public function __construct(Translatable|string $name, int $rarity, int $primaryItemFlags, int $secondaryItemFlags, int $maxLevel, float $typeModifier, ?array $applicableDamageTypes, ?\Closure $minEnchantingPower = null, int $enchantingPowerRange = 50){
parent::__construct($name, $rarity, $primaryItemFlags, $secondaryItemFlags, $maxLevel, $minEnchantingPower, $enchantingPowerRange);
$this->typeModifier = $typeModifier;
if($applicableDamageTypes !== null){

View File

@ -59,47 +59,224 @@ final class VanillaEnchantments{
use RegistryTrait;
protected static function setup() : void{
self::register("PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_all(), Rarity::COMMON, ItemFlags::ARMOR, ItemFlags::NONE, 4, 0.75, null));
self::register("FIRE_PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_fire(), Rarity::UNCOMMON, ItemFlags::ARMOR, ItemFlags::NONE, 4, 1.25, [
EntityDamageEvent::CAUSE_FIRE,
EntityDamageEvent::CAUSE_FIRE_TICK,
EntityDamageEvent::CAUSE_LAVA
//TODO: check fireballs
]));
self::register("FEATHER_FALLING", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_fall(), Rarity::UNCOMMON, ItemFlags::FEET, ItemFlags::NONE, 4, 2.5, [
EntityDamageEvent::CAUSE_FALL
]));
self::register("BLAST_PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_explosion(), Rarity::RARE, ItemFlags::ARMOR, ItemFlags::NONE, 4, 1.5, [
EntityDamageEvent::CAUSE_BLOCK_EXPLOSION,
EntityDamageEvent::CAUSE_ENTITY_EXPLOSION
]));
self::register("PROJECTILE_PROTECTION", new ProtectionEnchantment(KnownTranslationFactory::enchantment_protect_projectile(), Rarity::UNCOMMON, ItemFlags::ARMOR, ItemFlags::NONE, 4, 1.5, [
EntityDamageEvent::CAUSE_PROJECTILE
]));
self::register("THORNS", new Enchantment(KnownTranslationFactory::enchantment_thorns(), Rarity::MYTHIC, ItemFlags::TORSO, ItemFlags::HEAD | ItemFlags::LEGS | ItemFlags::FEET, 3));
self::register("RESPIRATION", new Enchantment(KnownTranslationFactory::enchantment_oxygen(), Rarity::RARE, ItemFlags::HEAD, ItemFlags::NONE, 3));
self::register("PROTECTION", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_all(),
Rarity::COMMON,
0,
0,
4,
0.75,
null,
fn(int $level) : int => 11 * ($level - 1) + 1,
20
));
self::register("FIRE_PROTECTION", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_fire(),
Rarity::UNCOMMON,
0,
0,
4,
1.25,
[
EntityDamageEvent::CAUSE_FIRE,
EntityDamageEvent::CAUSE_FIRE_TICK,
EntityDamageEvent::CAUSE_LAVA
//TODO: check fireballs
],
fn(int $level) : int => 8 * ($level - 1) + 10,
12
));
self::register("FEATHER_FALLING", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_fall(),
Rarity::UNCOMMON,
0,
0,
4,
2.5,
[
EntityDamageEvent::CAUSE_FALL
],
fn(int $level) : int => 6 * ($level - 1) + 5,
10
));
self::register("BLAST_PROTECTION", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_explosion(),
Rarity::RARE,
0,
0,
4,
1.5,
[
EntityDamageEvent::CAUSE_BLOCK_EXPLOSION,
EntityDamageEvent::CAUSE_ENTITY_EXPLOSION
],
fn(int $level) : int => 8 * ($level - 1) + 5,
12
));
self::register("PROJECTILE_PROTECTION", new ProtectionEnchantment(
KnownTranslationFactory::enchantment_protect_projectile(),
Rarity::UNCOMMON,
0,
0,
4,
1.5,
[
EntityDamageEvent::CAUSE_PROJECTILE
],
fn(int $level) : int => 6 * ($level - 1) + 3,
15
));
self::register("THORNS", new Enchantment(
KnownTranslationFactory::enchantment_thorns(),
Rarity::MYTHIC,
0,
0,
3,
fn(int $level) : int => 20 * ($level - 1) + 10,
50
));
self::register("RESPIRATION", new Enchantment(
KnownTranslationFactory::enchantment_oxygen(),
Rarity::RARE,
0,
0,
3,
fn(int $level) : int => 10 * $level,
30
));
self::register("SHARPNESS", new SharpnessEnchantment(KnownTranslationFactory::enchantment_damage_all(), Rarity::COMMON, ItemFlags::SWORD, ItemFlags::AXE, 5));
//TODO: smite, bane of arthropods (these don't make sense now because their applicable mobs don't exist yet)
self::register("SHARPNESS", new SharpnessEnchantment(
KnownTranslationFactory::enchantment_damage_all(),
Rarity::COMMON,
0,
0,
5,
fn(int $level) : int => 11 * ($level - 1) + 1,
20
));
self::register("KNOCKBACK", new KnockbackEnchantment(
KnownTranslationFactory::enchantment_knockback(),
Rarity::UNCOMMON,
0,
0,
2,
fn(int $level) : int => 20 * ($level - 1) + 5,
50
));
self::register("FIRE_ASPECT", new FireAspectEnchantment(
KnownTranslationFactory::enchantment_fire(),
Rarity::RARE,
0,
0,
2,
fn(int $level) : int => 20 * ($level - 1) + 10,
50
));
//TODO: smite, bane of arthropods, looting (these don't make sense now because their applicable mobs don't exist yet)
self::register("KNOCKBACK", new KnockbackEnchantment(KnownTranslationFactory::enchantment_knockback(), Rarity::UNCOMMON, ItemFlags::SWORD, ItemFlags::NONE, 2));
self::register("FIRE_ASPECT", new FireAspectEnchantment(KnownTranslationFactory::enchantment_fire(), Rarity::RARE, ItemFlags::SWORD, ItemFlags::NONE, 2));
self::register("EFFICIENCY", new Enchantment(
KnownTranslationFactory::enchantment_digging(),
Rarity::COMMON,
0,
0,
5,
fn(int $level) : int => 10 * ($level - 1) + 1,
50
));
self::register("FORTUNE", new Enchantment(
KnownTranslationFactory::enchantment_lootBonusDigger(),
Rarity::RARE,
0,
0,
3,
fn(int $level) : int => 9 * ($level - 1) + 15,
50
));
self::register("SILK_TOUCH", new Enchantment(
KnownTranslationFactory::enchantment_untouching(),
Rarity::MYTHIC,
0,
0,
1,
fn(int $level) : int => 15,
50
));
self::register("UNBREAKING", new Enchantment(
KnownTranslationFactory::enchantment_durability(),
Rarity::UNCOMMON,
0,
0,
3,
fn(int $level) : int => 8 * ($level - 1) + 5,
50
));
self::register("EFFICIENCY", new Enchantment(KnownTranslationFactory::enchantment_digging(), Rarity::COMMON, ItemFlags::DIG, ItemFlags::SHEARS, 5));
self::register("FORTUNE", new Enchantment(KnownTranslationFactory::enchantment_lootBonusDigger(), Rarity::RARE, ItemFlags::DIG, ItemFlags::NONE, 3));
self::register("SILK_TOUCH", new Enchantment(KnownTranslationFactory::enchantment_untouching(), Rarity::MYTHIC, ItemFlags::DIG, ItemFlags::SHEARS, 1));
self::register("UNBREAKING", new Enchantment(KnownTranslationFactory::enchantment_durability(), Rarity::UNCOMMON, ItemFlags::DIG | ItemFlags::ARMOR | ItemFlags::FISHING_ROD | ItemFlags::BOW, ItemFlags::TOOL | ItemFlags::CARROT_STICK | ItemFlags::ELYTRA, 3));
self::register("POWER", new Enchantment(
KnownTranslationFactory::enchantment_arrowDamage(),
Rarity::COMMON,
0,
0,
5,
fn(int $level) : int => 10 * ($level - 1) + 1,
15
));
self::register("PUNCH", new Enchantment(
KnownTranslationFactory::enchantment_arrowKnockback(),
Rarity::RARE,
0,
0,
2,
fn(int $level) : int => 20 * ($level - 1) + 12,
25
));
self::register("FLAME", new Enchantment(
KnownTranslationFactory::enchantment_arrowFire(),
Rarity::RARE,
0,
0,
1,
fn(int $level) : int => 20,
30
));
self::register("INFINITY", new Enchantment(
KnownTranslationFactory::enchantment_arrowInfinite(),
Rarity::MYTHIC,
0,
0,
1,
fn(int $level) : int => 20,
30
));
self::register("POWER", new Enchantment(KnownTranslationFactory::enchantment_arrowDamage(), Rarity::COMMON, ItemFlags::BOW, ItemFlags::NONE, 5));
self::register("PUNCH", new Enchantment(KnownTranslationFactory::enchantment_arrowKnockback(), Rarity::RARE, ItemFlags::BOW, ItemFlags::NONE, 2));
self::register("FLAME", new Enchantment(KnownTranslationFactory::enchantment_arrowFire(), Rarity::RARE, ItemFlags::BOW, ItemFlags::NONE, 1));
self::register("INFINITY", new Enchantment(KnownTranslationFactory::enchantment_arrowInfinite(), Rarity::MYTHIC, ItemFlags::BOW, ItemFlags::NONE, 1));
self::register("MENDING", new Enchantment(
KnownTranslationFactory::enchantment_mending(),
Rarity::RARE,
0,
0,
1,
fn(int $level) : int => 25,
50
));
self::register("MENDING", new Enchantment(KnownTranslationFactory::enchantment_mending(), Rarity::RARE, ItemFlags::NONE, ItemFlags::ALL, 1));
self::register("VANISHING", new Enchantment(
KnownTranslationFactory::enchantment_curse_vanishing(),
Rarity::MYTHIC,
0,
0,
1,
fn(int $level) : int => 25,
25
));
self::register("VANISHING", new Enchantment(KnownTranslationFactory::enchantment_curse_vanishing(), Rarity::MYTHIC, ItemFlags::NONE, ItemFlags::ALL, 1));
self::register("SWIFT_SNEAK", new Enchantment(KnownTranslationFactory::enchantment_swift_sneak(), Rarity::MYTHIC, ItemFlags::NONE, ItemFlags::LEGS, 3));
self::register("SWIFT_SNEAK", new Enchantment(
KnownTranslationFactory::enchantment_swift_sneak(),
Rarity::MYTHIC,
0,
0,
3,
fn(int $level) : int => 10 * $level,
5
));
}
protected static function register(string $name, Enchantment $member) : void{

View File

@ -35,9 +35,12 @@ use pocketmine\block\inventory\LoomInventory;
use pocketmine\block\inventory\SmithingTableInventory;
use pocketmine\block\inventory\StonecutterInventory;
use pocketmine\crafting\FurnaceType;
use pocketmine\data\bedrock\EnchantmentIdMap;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\enchantment\EnchantingOption;
use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\network\mcpe\cache\CreativeInventoryCache;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
@ -46,7 +49,10 @@ use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\PlayerEnchantOptionsPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\Enchant;
use pocketmine\network\mcpe\protocol\types\EnchantOption as ProtocolEnchantOption;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
@ -59,6 +65,7 @@ use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\ObjectSet;
use function array_fill_keys;
use function array_keys;
use function array_map;
use function array_search;
use function count;
use function get_class;
@ -104,6 +111,12 @@ class InventoryManager{
private bool $fullSyncRequested = false;
/** @var int[] network recipe ID => enchanting table option index */
private array $enchantingTableOptions = [];
//TODO: this should be based on the total number of crafting recipes - if there are ever 100k recipes, this will
//conflict with regular recipes
private int $nextEnchantingTableOptionId = 100000;
public function __construct(
private Player $player,
private NetworkSession $session
@ -383,6 +396,7 @@ class InventoryManager{
throw new AssumptionFailedError("We should not have opened a new window while a window was waiting to be closed");
}
$this->pendingCloseWindowId = $this->lastInventoryNetworkId;
$this->enchantingTableOptions = [];
}
}
@ -630,6 +644,39 @@ class InventoryManager{
$this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache($this->player->getCreativeInventory()));
}
/**
* @param EnchantingOption[] $options
*/
public function syncEnchantingTableOptions(array $options) : void{
$protocolOptions = [];
foreach($options as $index => $option){
$optionId = $this->nextEnchantingTableOptionId++;
$this->enchantingTableOptions[$optionId] = $index;
$protocolEnchantments = array_map(
fn(EnchantmentInstance $e) => new Enchant(EnchantmentIdMap::getInstance()->toId($e->getType()), $e->getLevel()),
$option->getEnchantments()
);
// We don't pay attention to the $slotFlags, $heldActivatedEnchantments and $selfActivatedEnchantments
// as everything works fine without them (perhaps these values are used somehow in the BDS).
$protocolOptions[] = new ProtocolEnchantOption(
$option->getRequiredXpLevel(),
0, $protocolEnchantments,
[],
[],
$option->getDisplayName(),
$optionId
);
}
$this->session->sendDataPacket(PlayerEnchantOptionsPacket::create($protocolOptions));
}
public function getEnchantingTableOptionIndex(int $recipeId) : ?int{
return $this->enchantingTableOptions[$recipeId] ?? null;
}
private function newItemStackId() : int{
return $this->nextItemStackId++;
}

View File

@ -406,10 +406,12 @@ class NetworkSession{
$timings->startTiming();
try{
$ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer);
$ev->call();
if($ev->isCancelled()){
return;
if(DataPacketDecodeEvent::hasHandlers()){
$ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer);
$ev->call();
if($ev->isCancelled()){
return;
}
}
$decodeTimings = Timings::getDecodeDataPacketTimings($packet);
@ -429,19 +431,22 @@ class NetworkSession{
$decodeTimings->stopTiming();
}
$ev = new DataPacketReceiveEvent($this, $packet);
$ev->call();
if(!$ev->isCancelled()){
$handlerTimings = Timings::getHandleDataPacketTimings($packet);
$handlerTimings->startTiming();
try{
if($this->handler === null || !$packet->handle($this->handler)){
$this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer()));
}
}finally{
$handlerTimings->stopTiming();
if(DataPacketReceiveEvent::hasHandlers()){
$ev = new DataPacketReceiveEvent($this, $packet);
$ev->call();
if($ev->isCancelled()){
return;
}
}
$handlerTimings = Timings::getHandleDataPacketTimings($packet);
$handlerTimings->startTiming();
try{
if($this->handler === null || !$packet->handle($this->handler)){
$this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer()));
}
}finally{
$handlerTimings->stopTiming();
}
}finally{
$timings->stopTiming();
}
@ -459,12 +464,16 @@ class NetworkSession{
$timings = Timings::getSendDataPacketTimings($packet);
$timings->startTiming();
try{
$ev = new DataPacketSendEvent([$this], [$packet]);
$ev->call();
if($ev->isCancelled()){
return false;
if(DataPacketSendEvent::hasHandlers()){
$ev = new DataPacketSendEvent([$this], [$packet]);
$ev->call();
if($ev->isCancelled()){
return false;
}
$packets = $ev->getPackets();
}else{
$packets = [$packet];
}
$packets = $ev->getPackets();
foreach($packets as $evPacket){
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $evPacket));

View File

@ -44,12 +44,14 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
public function broadcastPackets(array $recipients, array $packets) : void{
//TODO: this shouldn't really be called here, since the broadcaster might be replaced by an alternative
//implementation that doesn't fire events
$ev = new DataPacketSendEvent($recipients, $packets);
$ev->call();
if($ev->isCancelled()){
return;
if(DataPacketSendEvent::hasHandlers()){
$ev = new DataPacketSendEvent($recipients, $packets);
$ev->call();
if($ev->isCancelled()){
return;
}
$packets = $ev->getPackets();
}
$packets = $ev->getPackets();
$compressors = [];

View File

@ -201,7 +201,7 @@ class InGamePacketHandler extends PacketHandler{
}
$hasMoved = $this->lastPlayerAuthInputPosition === null || !$this->lastPlayerAuthInputPosition->equals($rawPos);
$newPos = $rawPos->subtract(0, 1.62, 0)->round(4);
$newPos = $rawPos->round(4)->subtract(0, 1.62, 0);
if($this->forceMoveSync && $hasMoved){
$curPos = $this->player->getLocation();

View File

@ -23,11 +23,13 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use pocketmine\block\inventory\EnchantInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\CreateItemAction;
use pocketmine\inventory\transaction\action\DestroyItemAction;
use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\inventory\transaction\EnchantingTransaction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionBuilder;
use pocketmine\inventory\transaction\TransactionBuilderInventory;
@ -287,7 +289,7 @@ class ItemStackRequestExecutor{
* @throws ItemStackRequestProcessException
*/
private function assertDoingCrafting() : void{
if(!$this->specialTransaction instanceof CraftingTransaction){
if(!$this->specialTransaction instanceof CraftingTransaction && !$this->specialTransaction instanceof EnchantingTransaction){
if($this->specialTransaction === null){
throw new ItemStackRequestProcessException("Expected CraftRecipe or CraftRecipeAuto action to precede this action");
}else{
@ -333,7 +335,16 @@ class ItemStackRequestExecutor{
$this->setNextCreatedItem($item, true);
}elseif($action instanceof CraftRecipeStackRequestAction){
$this->beginCrafting($action->getRecipeId(), 1);
$window = $this->player->getCurrentWindow();
if($window instanceof EnchantInventory){
$optionId = $this->inventoryManager->getEnchantingTableOptionIndex($action->getRecipeId());
if($optionId !== null && ($option = $window->getOption($optionId)) !== null){
$this->specialTransaction = new EnchantingTransaction($this->player, $option, $optionId + 1);
$this->setNextCreatedItem($window->getOutput($optionId));
}
}else{
$this->beginCrafting($action->getRecipeId(), 1);
}
}elseif($action instanceof CraftRecipeAutoStackRequestAction){
$this->beginCrafting($action->getRecipeId(), $action->getRepetitions());
}elseif($action instanceof CraftingConsumeInputStackRequestAction){

View File

@ -1330,18 +1330,20 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$deltaAngle = abs($this->lastLocation->yaw - $to->yaw) + abs($this->lastLocation->pitch - $to->pitch);
if($delta > 0.0001 || $deltaAngle > 1.0){
$ev = new PlayerMoveEvent($this, $from, $to);
if(PlayerMoveEvent::hasHandlers()){
$ev = new PlayerMoveEvent($this, $from, $to);
$ev->call();
$ev->call();
if($ev->isCancelled()){
$this->revertMovement($from);
return;
}
if($ev->isCancelled()){
$this->revertMovement($from);
return;
}
if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination
$this->teleport($ev->getTo());
return;
if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination
$this->teleport($ev->getTo());
return;
}
}
$this->lastLocation = $to;

View File

@ -30,19 +30,16 @@ use pocketmine\command\PluginCommand;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\scheduler\TaskScheduler;
use pocketmine\Server;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Config;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
use function copy;
use function count;
use function dirname;
use function fclose;
use function file_exists;
use function fopen;
use function mkdir;
use function rtrim;
use function str_contains;
use function stream_copy_to_stream;
use function strtolower;
use function trim;
use const DIRECTORY_SEPARATOR;
@ -50,6 +47,8 @@ use const DIRECTORY_SEPARATOR;
abstract class PluginBase implements Plugin, CommandExecutor{
private bool $isEnabled = false;
private string $resourceFolder;
private ?Config $config = null;
private string $configFile;
@ -67,6 +66,8 @@ abstract class PluginBase implements Plugin, CommandExecutor{
$this->dataFolder = rtrim($dataFolder, "/" . DIRECTORY_SEPARATOR) . "/";
//TODO: this is accessed externally via reflection, not unused
$this->file = rtrim($file, "/" . DIRECTORY_SEPARATOR) . "/";
$this->resourceFolder = Path::join($this->file, "resources") . "/";
$this->configFile = Path::join($this->dataFolder, "config.yml");
$prefix = $this->getDescription()->getPrefix();
@ -209,6 +210,27 @@ abstract class PluginBase implements Plugin, CommandExecutor{
}
/**
* Returns the path to the folder where the plugin's embedded resource files are usually located.
* Note: This is NOT the same as the data folder. The files in this folder should be considered read-only.
*/
public function getResourceFolder() : string{
return $this->resourceFolder;
}
/**
* Returns the full path to a data file in the plugin's resources folder.
* This path can be used with standard PHP functions like fopen() or file_get_contents().
*
* Note: Any path returned by this function should be considered READ-ONLY.
*/
public function getResourcePath(string $filename) : string{
return Path::join($this->getResourceFolder(), $filename);
}
/**
* @deprecated Prefer using standard PHP functions with {@link PluginBase::getResourcePath()}, like
* file_get_contents() or fopen().
*
* Gets an embedded resource on the plugin file.
* WARNING: You must close the resource given using fclose()
*
@ -226,26 +248,21 @@ abstract class PluginBase implements Plugin, CommandExecutor{
return false;
}
$out = Path::join($this->dataFolder, $filename);
if(file_exists($out) && !$replace){
$source = Path::join($this->resourceFolder, $filename);
if(!file_exists($source)){
return false;
}
if(($resource = $this->getResource($filename)) === null){
$destination = Path::join($this->dataFolder, $filename);
if(file_exists($destination) && !$replace){
return false;
}
if(!file_exists(dirname($out))){
mkdir(dirname($out), 0755, true);
if(!file_exists(dirname($destination))){
mkdir(dirname($destination), 0755, true);
}
$fp = fopen($out, "wb");
if($fp === false) throw new AssumptionFailedError("fopen() should not fail with wb flags");
$ret = stream_copy_to_stream($resource, $fp) > 0;
fclose($fp);
fclose($resource);
return $ret;
return copy($source, $destination);
}
/**

View File

@ -154,6 +154,13 @@ class ResourcePackManager{
return $this->serverForceResources;
}
/**
* Sets whether players must accept resource packs in order to join.
*/
public function setResourcePacksRequired(bool $value) : void{
$this->serverForceResources = $value;
}
/**
* Returns an array of resource packs in use, sorted in order of priority.
* @return ResourcePack[]

View File

@ -58,6 +58,21 @@ abstract class StringToTParser{
$this->callbackMap[$this->reprocess($alias)] = $callback;
}
/**
* Registers a new alias for an existing known alias.
*/
public function registerAlias(string $existing, string $alias) : void{
$existingKey = $this->reprocess($existing);
if(!isset($this->callbackMap[$existingKey])){
throw new \InvalidArgumentException("Cannot register new alias for unknown existing alias \"$existing\"");
}
$newKey = $this->reprocess($alias);
if(isset($this->callbackMap[$newKey])){
throw new \InvalidArgumentException("Alias \"$newKey\" is already registered");
}
$this->callbackMap[$newKey] = $this->callbackMap[$existingKey];
}
/**
* Tries to parse the specified string into a corresponding instance of T.
* @phpstan-return T|null

Some files were not shown because too many files have changed in this diff Show More