mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-11 20:10:17 +00:00
Compare commits
45 Commits
5.4.4
...
5.5.0-BETA
Author | SHA1 | Date | |
---|---|---|---|
c8100480ac | |||
8814d06dfd | |||
923f7561fb | |||
f4e1c31dcf | |||
998fcf20db | |||
1504fdca24 | |||
bf668c0f6c | |||
d942748203 | |||
29fdc8b08d | |||
20a41b00ba | |||
df96e023dc | |||
f4d5605de1 | |||
d03e4d17ec | |||
cd6abbe0bb | |||
22778583cf | |||
d44e0e87d0 | |||
47b448965d | |||
b2414b4c29 | |||
b3c740081e | |||
beaca8bb6d | |||
e323c5dd76 | |||
f516c3c502 | |||
5afbb9d807 | |||
b330cbe8e2 | |||
39867b97c5 | |||
e48b5b2ec0 | |||
37f2dafae1 | |||
7826e0a11e | |||
97700636c6 | |||
447f061566 | |||
a5d8ef7a6c | |||
59c88fe7f7 | |||
77dfbc4e23 | |||
baefbce863 | |||
9f14901820 | |||
515f8eae4c | |||
1cf508abdb | |||
6ac45526f9 | |||
c91c8c2f9e | |||
82f87cc2da | |||
6000bcccdd | |||
0b86fafafb | |||
2608637210 | |||
442d65143d | |||
0629d11e13 |
2
.github/workflows/build-docker-image.yml
vendored
2
.github/workflows/build-docker-image.yml
vendored
@ -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
|
||||
|
2
.github/workflows/discord-release-notify.yml
vendored
2
.github/workflows/discord-release-notify.yml
vendored
@ -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
|
||||
|
4
.github/workflows/draft-release.yml
vendored
4
.github/workflows/draft-release.yml
vendored
@ -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 }}
|
||||
|
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@ -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
|
||||
|
2
.github/workflows/update-updater-api.yml
vendored
2
.github/workflows/update-updater-api.yml
vendored
@ -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 }}
|
||||
|
Submodule build/php updated: 3331f8c0d5...a053f65e18
@ -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.
|
||||
|
@ -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
156
changelogs/5.5-beta.md
Normal 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
|
@ -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
163
composer.lock
generated
@ -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"
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
119
src/block/PinkPetals.php
Normal 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)];
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
class WeightedPressurePlateHeavy extends WeightedPressurePlate{
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
class WeightedPressurePlateLight extends WeightedPressurePlate{
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
115
src/block/utils/BlockEventHelper.php
Normal file
115
src/block/utils/BlockEventHelper.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
|
@ -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()
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
59
src/event/block/FarmlandHydrationChangeEvent.php
Normal file
59
src/event/block/FarmlandHydrationChangeEvent.php
Normal 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;
|
||||
}
|
||||
}
|
52
src/event/block/PressurePlateUpdateEvent.php
Normal file
52
src/event/block/PressurePlateUpdateEvent.php
Normal 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; }
|
||||
}
|
75
src/event/player/PlayerEnchantingOptionsRequestEvent.php
Normal file
75
src/event/player/PlayerEnchantingOptionsRequestEvent.php
Normal 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;
|
||||
}
|
||||
}
|
85
src/event/player/PlayerItemEnchantEvent.php
Normal file
85
src/event/player/PlayerItemEnchantEvent.php
Normal 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;
|
||||
}
|
||||
}
|
44
src/event/world/WorldDifficultyChangeEvent.php
Normal file
44
src/event/world/WorldDifficultyChangeEvent.php
Normal 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; }
|
||||
}
|
134
src/inventory/transaction/EnchantingTransaction.php
Normal file
134
src/inventory/transaction/EnchantingTransaction.php
Normal 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();
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
||||
|
42
src/item/ArmorMaterial.php
Normal file
42
src/item/ArmorMaterial.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
30
src/item/EnchantedBook.php
Normal file
30
src/item/EnchantedBook.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
73
src/item/VanillaArmorMaterials.php
Normal file
73
src/item/VanillaArmorMaterials.php
Normal 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));
|
||||
}
|
||||
}
|
@ -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]));
|
||||
}
|
||||
|
||||
}
|
||||
|
211
src/item/enchantment/AvailableEnchantmentRegistry.php
Normal file
211
src/item/enchantment/AvailableEnchantmentRegistry.php
Normal 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;
|
||||
}
|
||||
}
|
233
src/item/enchantment/EnchantingHelper.php
Normal file
233
src/item/enchantment/EnchantingHelper.php
Normal 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;
|
||||
}
|
||||
}
|
66
src/item/enchantment/EnchantingOption.php
Normal file
66
src/item/enchantment/EnchantingOption.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
34
src/item/enchantment/IncompatibleEnchantmentGroups.php
Normal file
34
src/item/enchantment/IncompatibleEnchantmentGroups.php
Normal 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";
|
||||
}
|
94
src/item/enchantment/IncompatibleEnchantmentRegistry.php
Normal file
94
src/item/enchantment/IncompatibleEnchantmentRegistry.php
Normal 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;
|
||||
}
|
||||
}
|
190
src/item/enchantment/ItemEnchantmentTagRegistry.php
Normal file
190
src/item/enchantment/ItemEnchantmentTagRegistry.php
Normal 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'"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
57
src/item/enchantment/ItemEnchantmentTags.php
Normal file
57
src/item/enchantment/ItemEnchantmentTags.php
Normal 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";
|
||||
}
|
@ -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;
|
||||
|
@ -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){
|
||||
|
@ -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{
|
||||
|
@ -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++;
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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 = [];
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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[]
|
||||
|
@ -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
Reference in New Issue
Block a user