Compare commits

...

215 Commits

Author SHA1 Message Date
84ec944b6b Use short class names for unhandled packet logging, added some documentation 2017-04-27 12:16:24 +01:00
6ef132e468 Updated PocketMine-Language submodule 2017-04-27 11:50:17 +01:00
2a59977440 Added various checks for region file validity (#393)
Check size, check header size, check location table offsets point to valid locations, check for shared offsets, prevent issues with corrupted or junk data
2017-04-27 09:14:02 +01:00
716efe2549 Fixed a stupid mistake in AddEntityPacket 2017-04-25 18:04:41 +01:00
5a9b5db103 Better cURL API, use async in timings (#834)
* Improved cURL functions
* Created BulkCurlTask
* Use asynchronous cURL posting in /timings paste

Closes #509
2017-04-25 11:52:18 +01:00
beed94dfb9 Update DevTools submodule 2017-04-25 11:41:55 +01:00
c6229b1e52 Merge network current changes - checkpointing
This is stable enough for everyone to be using it in production, and we're about 200 commits up from the previous tag. This branch contains hundreds of bug fixes, regardless of other changes which are not yet finished.
2017-04-25 11:00:51 +01:00
d682fdfdf0 Food and exhaustion should not apply in creative, close #860 2017-04-24 13:31:05 +01:00
0e7f364a41 Fixed chunk object memory leak when chunks are changed, close #419
If a player quit the server in the 1-second between a chunk changing and a fresh chunk-order requesting a resend of that chunk, the player wouldn't know they were using that chunk and did not unregister themselves, causing the subject chunks to always remain loaded.
2017-04-24 11:55:33 +01:00
559504225a Throw an exception before calling base entity constructor if skin is not set or invalid, close #835 (#855) 2017-04-24 09:50:55 +01:00
8ddf781a51 Oops, a typo 2017-04-22 20:24:24 +08:00
7cafaf2844 This line in README is a bit ambiguous
It sounded like "Poggit" was an adverbial clause to specify the name of "the old plugin repository".
2017-04-22 20:22:55 +08:00
6ece57e23e Merge changes from master 2017-04-21 22:48:18 +01:00
2204942338 Fixed the half-done hunger implementation, fixed lots of bugs related to hunger
- Fixed starvation doesn't deal any damage at all (Human->getFood() returns a float, not an int, === 0 won't work so great)
- Added exhaustion for sprinting, walking, jumping and sprint-jumping as per MCPE (these use MCPE values, and yes MCPE does walking exhaustion!)
- Fixed attributes don't get reset after player death
- Added food and hunger regeneration in peaceful difficulty
- Added API methods Living->jump() (motion isn't updated yet, so this won't actually do much if plugins try to use it) and Living->getJumpVelocity()

TODO: implement exhaustion for swimming
2017-04-21 19:48:25 +01:00
00a226921c Fixed server crash when taking damage after being killed when having Health Boost effect 2017-04-21 18:52:04 +01:00
e7406ba096 Fixed squid health attribute errors 2017-04-21 14:42:19 +01:00
a356e36340 Autogenerated data for 1.0.7.0
Did they actually _change_ anything or just trying to keep it on par with the game version? >_<
2017-04-21 13:22:36 +01:00
547a09c8d4 Fix "Creating default object from empty value" (#858)
while setting lore to items.
2017-04-20 20:39:09 +01:00
de95046c11 Reduce header sizes on PR template
Stop getting in my face
2017-04-20 10:43:10 +01:00
9e2b26de23 Remove redundant null check in PluginCommand (#853) 2017-04-19 17:59:01 +01:00
b867cf4c91 Fix a potential newline issue in PULL_REQUEST_TEMPLATE.md 2017-04-19 11:50:49 +08:00
d3f4b185f3 Fixed typo in PULL_REQUEST_TEMPLATE.md 2017-04-19 11:49:46 +08:00
86de0bddd9 World should only be completely immutable if we're in spectator mode
Fixes being unable to break any blocks at all in adventure mode
2017-04-18 20:01:51 +01:00
207056fb9d Fixed adventure mode being useless 2017-04-18 19:51:42 +01:00
930945db18 Create PULL_REQUEST_TEMPLATE.md (#845)
Adapted from the PHP RFC template
2017-04-18 16:33:37 +01:00
d2efcee115 Fixed tiles and entities being closed when replacing chunks, should fix #490 2017-04-18 14:47:42 +01:00
eefa8abaf2 Throw exceptions if something attempts to add a closed Tile or Entity to a chunk 2017-04-18 14:45:33 +01:00
8a775e0c45 Fix PopSound giving failed click sound. (#829) 2017-04-15 10:02:00 +01:00
f2159c5948 Fixed unlit furnaces glowing in the dark, close #508
Whether Furnace extending BurningFurnace actually makes logical sense is a different question, but that cna be resolved any other time.
2017-04-14 15:30:37 +01:00
4ab8233fe0 Fixed shooting bow while sprinting, close #827 2017-04-14 15:27:32 +01:00
40775f5d86 Fixed issues with subtitles not working correctly, close #823 (#825) 2017-04-14 10:42:23 +01:00
319763dd93 Fix #823
Thanks to @SuperMaXAleX
2017-04-13 22:32:53 +03:00
a455e25665 Merge remote-tracking branch 'jacknoordhuis/patch-2' 2017-04-13 13:28:06 +00:00
1c7773c5f1 Visibility keyword before final or abstract keyword. (#814) 2017-04-13 20:58:53 +08:00
7c66af5994 "Fixed" resource packs
TODO: new fields
2017-04-12 11:54:47 +01:00
cb7264e0e5 Hexdump unhandled packets 2017-04-12 11:20:58 +01:00
f7e1939ae8 Changed some handling of unknown packet content 2017-04-12 11:16:50 +01:00
172d7339f9 Autogenerated data for 1.0.6.52 2017-04-12 11:11:37 +01:00
c01e0354bd Address #816
Removes redundant compression argument from NBT::readCompressed() and
NBT:: readNetworkCompressed()
2017-04-12 00:42:27 +10:00
e312c697fd Merge master into api3/network 2017-04-10 21:21:29 +01:00
80292c6c7a Actually use iusername instead of repeatedly lowercasing player names (#811)
#blameshoghi
2017-04-10 21:15:38 +01:00
dda47ee566 Fix typo in explosion (#700) 2017-04-10 09:17:34 +01:00
5863d001bd Merge pull request #524 from pmmp/issues/516
Allow custom JSON pretty print options
2017-04-09 16:15:35 +08:00
6b72bbc234 Allow custom JSON pretty print options
Resolves #516
Closes #517
2017-04-06 15:05:39 +08:00
1c2895eb12 Fixed absorption application logic, close #518 2017-04-05 21:16:40 +01:00
fd982afce6 Removed misleading outdated documentation (#519) 2017-04-05 20:31:56 +01:00
f1510428d0 Fixed double gc_enable call (#492) 2017-04-03 09:40:48 +01:00
630f0fab7f Fixed block update recursion issues (#464)
* Schedule all neighbour block updates to execute at the end of the tick, fixed recursion crash, close #251

* doTickPending timings now include neighbour block update times, refactored some var names
2017-04-01 20:18:56 +01:00
202bac28fc Merge master into api3/network 2017-04-01 19:37:15 +01:00
874afc2fd2 fixed players need to move to pick up dropped items, close #498 2017-04-01 19:33:16 +01:00
f75cc93160 HOW did nobody notice this?! 2017-04-01 14:08:02 +01:00
b24d516eda Send TransferPacket with immediate priority, should fix #497 2017-04-01 10:40:05 +01:00
845b124f89 Stop autosaving players who haven't joined yet, close #494 2017-03-31 20:33:15 +01:00
16972bf9a5 Fix issues with writing negative numbers as non-zigzag varints, close #493 2017-03-31 18:59:40 +01:00
87a52a4f35 Fixed yet another crash when level-settings.always-tick-players is set to true 2017-03-31 16:09:40 +01:00
7f838a8c36 Fixed crashes due to adding players 'online' far too early, fixed some Player save logic 2017-03-31 13:45:28 +01:00
b5f473a3df Throw an exception when attempting to tick closed Levels 2017-03-31 13:14:05 +01:00
40a6f4dee9 Elevated level close check to exception level
As an assertion, this will crash on save if the level is already closed due to the provider being null.
2017-03-30 19:41:42 +01:00
69ac80518c some improvements to the horrendous mess that is the handling of joining and quitting, fixed some crashes, probably caused some other crashes
I can't fix this completely because it's just too much of a fucking mess. NEED to separate network stuff from Player.
2017-03-30 19:33:47 +01:00
45e5b6b04c Do not subscribe to broadcast permissions until the player spawns
This is unnecessary since the player won't see any messages sent before they spawn anyway. This was also causing an occasional client-sided crash due to TextPackets being sent to players at bad times during the login sequence.
2017-03-30 16:29:18 +01:00
cb059ea713 fix some PhpStorm inspections 2017-03-30 12:10:59 +01:00
afb2e0c51f fixed setting entity scale doesn't resize bounding box, close #484 2017-03-30 09:34:52 +01:00
cd477163cd New Timings v1 host is up 2017-03-30 09:15:01 +01:00
6b747f9272 Added basic API for working with titles 2017-03-29 20:02:16 +01:00
868602a559 Add __clone to CompoundTag and ListTag, fixed issues with items sharing the same NBT tag objects 2017-03-29 13:58:36 +01:00
bc1c75a15a Throw exceptions when failing to deserialize item NBT data, fixed weird crashes when an invalid NBT tag is set on an item 2017-03-29 13:39:43 +01:00
1c3d89cfef Fixed lighting issues with subchunks containing no blocks
A subchunk with no blocks is not necessarily empty.
2017-03-29 11:34:43 +01:00
c84ec90398 Set forceMovement to null when player is closed (#472)
Cater for the very very very slim chance that a player could quit while teleporting, be leaked and then have their level unloaded and leak their level.
2017-03-29 09:20:31 +01:00
3e76c3a6dd Added handling for tile picking, added API for setting item lore
worked almost out of the box (some W10 equipment bugs though)
2017-03-28 18:47:51 +01:00
52f2596dc5 Merge branch 'master' into api3/network 2017-03-28 12:27:40 +01:00
2079e2fd88 Fixed entity visibility Player object memory leak, close #416 2017-03-28 12:26:02 +01:00
217f66e180 Removed redundant method override leftover from 0.15 2017-03-28 12:25:54 +01:00
07f32765ba Merge branch 'api3/network_mcpe-1.0.5' into api3/network 2017-03-26 18:52:30 +01:00
788bd6fc20 Fixed resource packs/login sequence fail, added basic safety restrictions for packet sending before clients are logged in
close #452
2017-03-26 14:42:23 +01:00
01440fb659 Fixed players receiving double SetEntityMotionPackets for themselves 2017-03-26 13:40:39 +01:00
dda8c6cc8f Removed a condition that's been useless almost since the beginning of PocketMine
This condition has been useless since before NBT was introduced to PocketMine.
If there was a use for it, it should have been placed BEFORE anything attempted to read from the NBT.

However, Server now handles bad data automatically now, so Server->getOfflinePlayerData() will never _not_ return a CompoundTag. Hence I've added a CompoundTag type-hint.
2017-03-26 13:20:46 +01:00
1da870b298 Measure block break times in ticks instead of floating-point real-time 2017-03-26 10:36:19 +01:00
7a36d80384 Fixed broken block-break timer logic causing creative players to be unable to remove fire after breaking blocks
This also causes some annoying issues with instabreak (false positives). Shoghi dude, this did _not_ fix those issues, only hid them and replaced them with different ones.
2017-03-26 10:36:19 +01:00
bb79684480 Merge branch 'api3/network' into api3/network_mcpe-1.0.5 2017-03-25 21:31:48 +00:00
4245274aec Merge branch 'master' into api3/network 2017-03-25 21:26:46 +00:00
b9dfc7551a Added Permission to bypass spawn protection, close #440 (#451) 2017-03-25 20:34:42 +00:00
839a2ce07e Merge branch 'patch-4' of https://github.com/SOF3/PocketMine-MP-Original 2017-03-25 16:58:46 +00:00
34f833fa79 Do not save empty inventory slots 2017-03-25 12:02:09 +00:00
c9cf3d5aa4 Throw an exception when something attempts to serialize Server (#459) 2017-03-25 10:33:05 +00:00
5332887a0a Fixed command name case sensitivity issue noted in #462 2017-03-25 10:26:06 +00:00
5926bab323 Block light bug fixes (#454)
* Fixed an age-old light calculation bug causing solid blocks to filter their own light, fixed #375, probably fixed #288
Light spread reduction should be done based on the _target's_ light filter level, not the source.

* Revert "Fix Glowing Obsidian lighting"
This hack is no longer necessary.
This reverts commit 35c33ba980.

* Fixed wrong light levels for torch and redstone torch

* Take adjacent light levels and opacity changes into account, block light will now spread when an obstruction is removed, close #455

* Added timings for Level->setBlock() and lighting updates
2017-03-24 17:56:26 +00:00
0750b3ab59 Added pocketmine.yml option to disable the title ticker (#447)
This gets really spammy on some consoles when you stick it in the background, but I don't want to lose colour for the sake of that.
2017-03-24 16:03:10 +00:00
96801be3d3 Fixed #453 multiple refs to the same cached NBT object tree 2017-03-23 11:40:49 +00:00
2fb92c1c62 Fixed wrong constant value for EntityEventPacket::RESPAWN 2017-03-22 16:10:42 +00:00
c040579e09 Fixed a mistake in spaced command handling
Nothing drastic, just a self-defeating line of code.
2017-03-21 15:11:48 +00:00
9a35b4fbc8 Removed redundant TODO comment 2017-03-21 14:03:53 +00:00
940b20c191 Implemented Absorption effect
This is a little buggy due to a client-sided bug. https://bugs.mojang.com/browse/MCPE-20520
TODO: add attribute save/restore
2017-03-21 13:23:57 +00:00
c21768df26 Updated Effect constants, removed incorrect/misleading SWIFTNESS constant
So what? I'd rather crash plugins than have them suddenly behave strangely because SWIFTNESS is now an alias for SPEED instead of HASTE.
2017-03-21 11:49:18 +00:00
2d927db264 Implemented Instant Health and Instant Damage effects 2017-03-21 11:38:08 +00:00
a5a51fb9c5 Merge branch 'master' into api3/network_mcpe-1.0.5 2017-03-21 10:47:41 +00:00
47f7af6739 Fixed usage reporting cannot be disabled 2017-03-20 21:26:20 +00:00
b7a3230f73 Fixed botched effect override condition for equivalent amplifiers 2017-03-20 18:56:54 +00:00
6a03f8d434 Fixed server creating resource packs directory inside itself when running from a phar
Once again, epic facepalm @dktapps
2017-03-20 13:28:45 +00:00
06f2a9c674 Fix client-side death bug (#438) 2017-03-20 12:21:58 +00:00
0e64d4bbc2 Given Player->iusername a use, added Player->getLowerCaseName()
Micro optimizations by not repeatedly lowercasing names when searching
2017-03-20 12:21:02 +00:00
284c18d401 Added debug for mismatched item equipment
tool damage packets sent in the wrong order?
This could be bad for performance since the entire inventory is resent every time this issue crops up.
2017-03-20 10:58:43 +00:00
6ba4a8fe5c Moved batch packet handling into BatchPacket->handle(), fixed data packet receive timings to include MCPE packet decode time 2017-03-20 10:26:53 +00:00
4638ccbb68 Remove this workaround (client bug fixed in 1.0.5 beta) 2017-03-19 21:58:12 +00:00
36cda5de61 Merge branch 'api3/network' into api3/network_mcpe-1.0.5 2017-03-19 21:54:14 +00:00
9c350dbe47 Fixed DataPacketReceiveEvent, fixed packet receive timings, gave Player->handleDataPacket() a new use 2017-03-19 21:50:09 +00:00
2673e4de7f More anti-leak measures for double chest inventory issues 2017-03-19 11:25:56 +00:00
be449b6106 Removed useless condition from RemoveBlockPacket handler 2017-03-19 10:32:54 +00:00
4c61ad9f2d Stop skipping stack frames (#425) 2017-03-19 10:24:33 +00:00
66fbfdd47b Fixed hunger not saving, resolves 1 of #435 (#439) 2017-03-18 21:58:02 +00:00
8a28021b44 Use hash_file instead of OpenSSL for resource pack hashing
Epic facepalm. I totally forgot this function existed. >_<
2017-03-18 21:47:04 +00:00
ab1150382a Merge branch 'api3/network' into api3/network_mcpe-1.0.5 2017-03-18 16:30:06 +00:00
8114ceaf68 Merge branch 'master' into api3/network 2017-03-18 16:29:38 +00:00
0d37d0d896 Added some documentation to resource packs namespace 2017-03-18 16:07:03 +00:00
cc0b4d888e Use a resource for reading resource packs from disk 2017-03-18 15:49:48 +00:00
116cba9fae Added expected and actual result questions to issue template 2017-03-18 15:32:58 +00:00
51a20470f6 Switch back to the old chunk-packet method since MoveEntityPacket and SetEntityMotionPacket no longer have lists 2017-03-18 15:03:41 +00:00
6e1abe7b15 Fixed some formatting issues in FlowerPot 2017-03-18 14:24:23 +00:00
cbb003bf29 Guard against leaked closed tiles leaking chunks and NBT trees
Once again, this does not fix the actual issue, only reduces the impact of it.
2017-03-18 11:50:05 +00:00
fa5e66478c Auto update checks are now asynchronous, improves startup time (#433) 2017-03-17 14:43:12 +00:00
bcbb5de5bb Added reference parameters for errors for Utils::getURL() and Utils::postURL(), close #332 (#357) 2017-03-16 19:15:31 +00:00
548df21645 Small docs fix (#432) 2017-03-16 18:19:30 +00:00
b7b73aab23 Fixed username regex failing, close #427 2017-03-15 19:22:43 +00:00
a8650a241c Removed @deprecated warning from PlayerInventory->setHotbarSlotIndex()
Core uses it, it's just that plugins shouldn't.
2017-03-14 17:37:29 +00:00
d26713ab59 Use assoc instead of object for command data, fix data modifications affecting all commands
how did I do manage to do somthing this stupid -_- smh what an idiot
Lucky permission is a root node, or the whole commands system would've been compromised. Epic fail.
2017-03-14 11:39:59 +00:00
3138e02acb Added support for commands with spaces in their names (#422) 2017-03-13 20:55:06 +00:00
d264a04db4 Added detection for recursive server aliases (#424)
* Added detection for recursive server aliases, close #423

* Oops
2017-03-13 20:43:34 +00:00
3c709b1d3e Return false on unhandled/unknown resource pack client response status 2017-03-13 15:52:00 +00:00
c344caaf78 Refactor InventoryNetworkIds as WindowTypes 2017-03-13 11:39:54 +00:00
9e341f74d8 Added new window types and found some UpdateTradePacket fields 2017-03-13 11:27:44 +00:00
e7dbda922a Rename some CommandBlockUpdatePacket fields 2017-03-13 10:44:40 +00:00
92193fd27b Use entity IDs in EntityDamageBy*EntityEvents, fixed memory leaks related to PvP/PvE/PvM (#418) 2017-03-13 10:30:31 +00:00
bb85308b01 Fix undefined variable 2017-03-13 09:46:39 +00:00
565335f29e Revert "TODO: REVERT - Added a workaround for client text duplication"
This reverts commit 52748fcf64.
2017-03-12 16:18:30 -04:00
78278a0b93 Fixed a mistake in old effect handling 2017-03-12 20:15:21 +00:00
955dc38be4 Fixed botch-job implementation of Health Boost, will now actually work and not crash the server 2017-03-12 20:06:39 +00:00
f58ee2028e Moved effects stuff to json 2017-03-12 19:52:57 +00:00
083d1e9ef8 Deprecated Item->deepEquals(), added automatic deep checking in equals(), added some documentation for Item API methods 2017-03-12 14:46:34 +00:00
90abc28c29 Merge branch 'api3/network' into api3/network_mcpe-1.0.5 2017-03-12 12:30:56 +00:00
6c5dbd7359 Merge branch 'master' into api3/network 2017-03-12 12:22:11 +00:00
4f27bce5b3 Destroy NBT references when closing entities, alleviates memory issues on leaked Player objects
This does NOT FIX THE ACTUAL ISSUES, only eliminates some of the symptoms.
2017-03-12 10:53:27 +00:00
6f1b12b021 Added new 1.0.5 packets 2017-03-11 19:58:32 +00:00
a71747347f Updated Doxygen documentation link 2017-03-11 16:51:20 +00:00
004880548c Autogenerated data for 1.0.5.0 2017-03-11 12:13:55 +00:00
91a92b4e57 Use a pre-created resource packs config with comments to explain how to use it 2017-03-11 11:40:58 +00:00
ed765a2c9b Added debug messages for resource requests with invalid pack IDs 2017-03-11 11:19:14 +00:00
e1fb4a44e9 Updated PocketMine-Language submodule 2017-03-11 11:03:14 +00:00
2cb98c48c2 Improved dependency checking 2017-03-10 21:51:05 +00:00
d41bdfc31c Added resource packs support 2017-03-10 21:10:46 +00:00
c925845173 Added forceSend for attribute value setting, fixed slowness >= 7 removed client-side when sprinting 2017-03-09 21:33:55 +00:00
7fb3c7343f Fit attribute value to range when applying slowness, close #410
According to http://minecraft.gamepedia.com/Status_effect#Slowness, anything higher than slowness 7 will cause the player to be unable to move. Therefore this value should be clamped to a minimum of 0, not crash.
2017-03-09 21:01:10 +00:00
132e04fdbb Hotbar/inventory bugfixes (#399)
- Fixed most issues with item equipment in creative
- Added save and restore of currently-held item
- Reset hotbar on death, added API method PlayerInventory->resetHotbar()
- Creative players now have more leeway to get items, alleviates issues with item equipment in desktop GUI
- Fixed creative players wearing armour
- Found unknown field in ContainerSetSlotPacket
- Removed outdated/redundant constants
- Use a case statement in ContainerSetSlotPacket handler, added handling for 0x7a hotbar slot link update
2017-03-09 20:31:55 +00:00
1f2b584400 Merge branch 'master' into api3/network 2017-03-09 18:31:10 +00:00
d31e92bbe7 Remove beta tag, bump client version to 1.0.4.11 2017-03-09 18:30:51 +00:00
08cd944e5d Merge branch 'master' into api3/network 2017-03-09 18:12:37 +00:00
dbb579aa73 Updated LevelSoundEvent constants 2017-03-09 17:55:26 +00:00
bc0598c0f1 Autogenerated data for 1.0.4.1 2017-03-09 17:55:26 +00:00
f87b745771 New entity metadata and found some UpdateTradePacket fields 2017-03-09 17:55:26 +00:00
8d43faf16e Added Inventory network IDs interface 2017-03-09 17:55:26 +00:00
f00e7ccb54 Forgot preprocessor header include 2017-03-09 17:55:26 +00:00
7b5e5832cb Added UpdateTradePacket 2017-03-09 17:55:25 +00:00
dd6abff712 Autogenerated data for 1.0.4.0 2017-03-09 17:55:25 +00:00
7e1bdd474a Revert multi-world hack (client issue fixed in 1.0.4.0), close #260
This reverts commit 162b993e65.
2017-03-09 17:55:25 +00:00
e31333edd4 Update ISSUE_TEMPLATE.md 2017-03-09 16:35:12 +00:00
c052ee5847 Set alpha value to 0xff, fixed potion bubbles, close #407
TODO: implement transparency
2017-03-09 12:26:24 +00:00
f8c2eb8c3a Fixed signed VarInt encoding on 64-bit systems
Numbers represented as hex or binary with the 32nd bit set, for example 0xffffffff, were not considered as signed on 64-bit.
2017-03-09 12:23:24 +00:00
94d78ca554 Added missing returns 2017-03-08 20:38:11 +00:00
c7fdbea0f0 Merge branch 'master' into api3/network 2017-03-08 20:35:41 +00:00
a19996a7cf Added deprecation warning for 32-bit 2017-03-08 20:29:25 +00:00
9311b4f248 Remove unneeded comments 2017-03-08 20:29:24 +00:00
295d9bc80b Cleaned up muddled varint/varlong mess, added separate methods for entity unique and runtime ids, moved some MCPE-protocol-specific methods out of BinaryStream 2017-03-08 20:29:24 +00:00
3a044f0154 Added methods for VarLong, limited Binary::readVarInt() to 5-byte numbers 2017-03-08 20:29:23 +00:00
adb7df212c Let the parent caller catch this so we get encapsulated packet hexdumps 2017-03-08 20:29:22 +00:00
9e92a350e3 ClientboundMapItemDataPacket 2017-03-08 20:29:15 +00:00
005c2419e9 Fixed batched packets being encoded twice 2017-03-08 20:29:14 +00:00
d823ff18d8 Bump API version to 3.0.0-ALPHA5 (not finalized) 2017-03-08 20:29:14 +00:00
9b47aed0ab Added MapInfoRequestPacket 2017-03-08 20:29:13 +00:00
55598ba703 Moaaaar resource packets 2017-03-08 20:29:13 +00:00
425686755b Added basic resource-pack response handling, fixed sounds, broadcast sounds received from client
There are still a lot of sounds which do not work, these are supposed to be sent by the server and will be fixed at a later date.
2017-03-08 20:29:04 +00:00
6676029319 Improved some handlers, added detection for no-clip 2017-03-08 20:29:03 +00:00
e008a3cd5e Added handling for unknown packets 2017-03-08 20:29:03 +00:00
5aed0fb0d5 Remove redundant TODO comment 2017-03-08 20:29:02 +00:00
d0faf3df91 Added S2C and C2S handshake packet classes and stub handlers
TODO: implement encryption

Add boilerplate reset() for C2S packet encode

This crap really needs fixing
2017-03-08 20:28:52 +00:00
564b50ea33 Added API methods for validating usernames and skins 2017-03-08 20:28:41 +00:00
ea0f291cb5 Added class method DataPacket->canBeBatched() 2017-03-08 20:28:40 +00:00
56990eb28b MCPE protocol gets its own namespace 2017-03-08 20:28:39 +00:00
477cb77002 Exploded Player->handleDataPacket() into 70+ methods 2017-03-08 20:28:20 +00:00
93896977d0 Add default 2017-03-07 10:18:58 +00:00
554816b8b6 Added configuration option to pocketmine.yml to allow changing timings host, added new host mcpetimings.com 2017-03-07 09:24:32 +00:00
fc5fa01442 Removed type-hint silently breaking use of CompoundTags in Item::get() 2017-03-06 19:45:49 +00:00
f204422432 Fixed precedence issue 2017-03-06 11:40:28 +00:00
d6d3184e37 Fixed players can't join if spawn-radius is higher than the player's view distance
TODO: use this properly instead of calculating a count
2017-03-06 11:37:39 +00:00
c569fd86b1 Simplified Vector3::getOppositeSide() (#377)
* Simplified Vector3::getOppositeSide()

* Throw exception on bad input values

* @throws doc
2017-03-05 19:30:12 +08:00
e33eb0ddb6 Fixed missing permission registration in in #355 (#396) 2017-03-05 10:03:59 +00:00
0a8bd72e11 New Jenkins server is up
This reverts commit 68998bac48.
2017-03-04 22:35:38 +00:00
4ee8d14584 Added API for transferring players to other servers (#355)
* Added API method `Player->transfer()` and PlayerTransferEvent
2017-03-04 18:22:31 +00:00
663cb514e2 Fixed missing Cake recipe
TODO: add support for multiple crafting recipe result items
2017-03-04 15:03:53 +00:00
15f098074a Fixed batched packets being encoded twice 2017-03-03 17:33:30 +00:00
8bf3b6bbea Added ShowCreditsPacket 2017-03-02 11:04:51 +00:00
eb13cec5d0 Added new packets 2017-03-02 11:04:51 +00:00
0cd1e82c52 Fixed encode/decode of ResourcePacksInfoPacket and ResourcePackClientResponsePacket 2017-03-02 11:04:50 +00:00
1ee689e759 Fixed mess of entity ID 0 for players, fixed emeralds 2017-03-02 11:04:00 +00:00
d25c8d93ca Revert "Better time ticking and sync (#2)"
didn't consider modded clients, how naive 🤦

This reverts commit e9f2bf0085.
2017-03-02 10:53:40 +00:00
4fbc5738e3 Re-implemented chunk sending (#304)
Re-implement chunk sending, send chunks inside a radius instead of below a count

This sends chunks in concentric squares around players. When the radius is hit, it will pad out the radius until a full circle of chunks is loaded around the player.
TODO: implement radius-per-tick, send chunks in concentric circles, use radius for player spawning.

To set your server chunk radius, change `view-distance` in server.properties. Values are intended to be the same as MCPE render distance values. With matching client and server render distances the chunks should reach the horizon.

NOTE: You may notice significantly increased memory usage per player when increasing these values to something respectable. This is normal and expected.
A player with render distance 14 for example will cause loading of 600+ chunks. A player cannot however exceed the render distance limit set in server.properties - the server will simply not send any more chunks.

Render distance of 8 chunks is approximately 200 chunks. This is roughly equivalent to the original default max-chunks of 192 in pocketmine.yml, but sent in a circle instead of a square.

Wait for client to request a chunk radius before ordering chunks

Use 8 for default maximum radius (roughly matches old setting of 192)

Calculate spawn chunk count from chunk-sending.spawn-radius
2017-03-02 10:30:30 +00:00
d588222e84 Added an exception throw for accessing permissions of closed players 2017-02-28 10:49:09 +00:00
c3fb2e9f23 Fixed broadcasting quit messages to quitting player, fixed accessing permission of closed players on quit 2017-02-28 10:43:11 +00:00
cfb6856634 Fixed Player object memory leak when players with admin channel permissions are closed 2017-02-25 15:39:36 +00:00
11e0387e19 Show an error if no language files found during setup, mitigates #380 2017-02-25 11:21:32 +00:00
dc7b5b14d5 Fixed wrong encoding of ResourcePacksInfoPacket 2017-02-24 12:38:02 +00:00
5eab956da6 Add block-break check for spectator mode, fixes players in spectator able to break blocks when adventure settings are not set correctly 2017-02-23 19:39:08 +00:00
ad88ca09bd Fixed cannot remove block metadata 2017-02-23 15:18:42 +00:00
f98a964cdc Fixed and silenced some inspections 2017-02-23 15:17:28 +00:00
99995579d7 Added WorldBuilder flag to AdventureSettingsPacket 2017-02-23 12:20:36 +00:00
4ae18526d1 Fix fence gates opening in opposite direction to expected
This still occasionally occurs due to a bug that seems to exist with
entity rotation calculations. May happen at 45° 135° 225° and 315°
2017-02-22 09:46:54 +00:00
282095513a Throw exception when attempting to save a non-generated chunk (#367) 2017-02-21 19:24:16 +00:00
d6e343c2cf Premature optimization again 2016-08-24 17:15:22 +08:00
236 changed files with 7708 additions and 2832 deletions

View File

@ -1,7 +1,14 @@
### Issue description
<!--- use our forum https://forums.pmmp.io for questions -->
<!--- Any issues requesting updates to new versions of MCPE will be treated as spam. We do not need issues to tell us that there is a new version available. -->
<!---
Write a short description about the issue
If you are reporting a regression or unexpected behaviour, please include the below information:
Expected result: What were you expecting to happen?
Actual result: What actually happened?
-->
### Steps to reproduce the issue
<!--- help us find the problem by adding steps to reproduce the issue -->
1. ...

36
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,36 @@
## Introduction
<!-- Explain existing problems or why this pull request is necessary -->
### Relevant issues
<!-- List relevant issues here -->
<!--
* Fixes #1
* Fixes #2
-->
## Changes
### API changes
<!-- Any additions to the API that should be documented in release notes? -->
### Behavioural changes
<!-- Any change in how the server behaves, or its performance? -->
## Backwards compatibility
<!-- Any possible backwards incompatible changes? How are they solved, or how can they be solved? -->
## Follow-up
<!-- Suggest any actions to be done before/after merging this pull request -->
<!--
Requires translations:
| Name | Value in eng.ini |
| :--: | :---: |
| `foo.bar` | `Foo bar` |
-->
## Tests
<!-- Attach scripts or actions to test this pull request, as well as the result -->

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ timings/*
server.properties
/pocketmine.yml
memoryDump_*/*
resource_packs/
# Common IDEs
.idea/

View File

@ -13,19 +13,17 @@ If you don't find what you're looking for there, [talk to a human](#discussion).
- [#pmmp + #pocketmine channel IRC](http://webchat.freenode.net/?channels=pmmp,pocketmine)
### Plugins
There are a very wide range of already-written plugins available which you can use to customise your server. Check out the [old plugin repository](http://plugins.pocketmine.net/), [Poggit](https://poggit.pmmp.io) or just search GitHub.
There are a very wide range of already-written plugins available which you can use to customise your server. Check out [the old plugin repository](http://plugins.pocketmine.net/) and [Poggit](https://poggit.pmmp.io), or just search GitHub.
### For developers
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP%20Docs/doxygen/) - Doxygen documentation generated from development
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP-doc/doxygen/) - Doxygen documentation generated from development
* [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - A development tools plugin for creating plugins.
### Can I contribute?
Yes you can! Contributions are welcomed provided that they comply with our [Contributing Guidelines](CONTRIBUTING.md). Please ensure you read the relevant sections of the guidelines carefully before making a Pull Request or opening an Issue.
<!-- TODO uncomment this when Jenkins is fixed
### Where can I get the latest .phar?
Head over to our [official Jenkins server](https://jenkins.pmmp.io/)
-->
## Third-party Libraries/Protocols Used
* __[PHP Sockets](http://php.net/manual/en/book.sockets.php)__

View File

@ -21,7 +21,7 @@
namespace pocketmine;
use pocketmine\network\protocol\Info;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\plugin\PluginBase;
use pocketmine\plugin\PluginLoadOrder;
use pocketmine\utils\Utils;
@ -143,7 +143,7 @@ class CrashDump{
$error = $lastExceptionError;
}else{
$error = (array) error_get_last();
$error["trace"] = @getTrace(3);
$error["trace"] = getTrace(4); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump
$errorConversion = [
E_ERROR => "E_ERROR",
E_WARNING => "E_WARNING",
@ -226,7 +226,7 @@ class CrashDump{
$this->data["general"] = [];
$this->data["general"]["version"] = $version->get(false);
$this->data["general"]["build"] = $version->getBuild();
$this->data["general"]["protocol"] = Info::CURRENT_PROTOCOL;
$this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL;
$this->data["general"]["api"] = \pocketmine\API_VERSION;
$this->data["general"]["git"] = \pocketmine\GIT_COMMIT;
$this->data["general"]["raklib"] = RakLib::VERSION;
@ -235,7 +235,7 @@ class CrashDump{
$this->data["general"]["zend"] = zend_version();
$this->data["general"]["php_os"] = PHP_OS;
$this->data["general"]["os"] = Utils::getOS();
$this->addLine("PocketMine-MP version: " . $version->get(false) . " #" . $version->getBuild() . " [Protocol " . Info::CURRENT_PROTOCOL . "; API " . API_VERSION . "]");
$this->addLine("PocketMine-MP version: " . $version->get(false) . " #" . $version->getBuild() . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "; API " . API_VERSION . "]");
$this->addLine("Git commit: " . GIT_COMMIT);
$this->addLine("uname -a: " . php_uname("a"));
$this->addLine("PHP Version: " . phpversion());

View File

@ -47,7 +47,7 @@ class MemoryManager{
private $garbageCollectionTrigger;
private $garbageCollectionAsync;
private $chunkLimit;
private $chunkRadiusOverride;
private $chunkCollect;
private $chunkTrigger;
@ -104,7 +104,7 @@ class MemoryManager{
$this->garbageCollectionTrigger = (bool) $this->server->getProperty("memory.garbage-collection.low-memory-trigger", true);
$this->garbageCollectionAsync = (bool) $this->server->getProperty("memory.garbage-collection.collect-async-worker", true);
$this->chunkLimit = (int) $this->server->getProperty("memory.max-chunks.trigger-limit", 96);
$this->chunkRadiusOverride = (int) $this->server->getProperty("memory.max-chunks.chunk-radius", 4);
$this->chunkCollect = (bool) $this->server->getProperty("memory.max-chunks.trigger-chunk-collect", true);
$this->chunkTrigger = (bool) $this->server->getProperty("memory.max-chunks.low-memory-trigger", true);
@ -122,8 +122,15 @@ class MemoryManager{
return !($this->lowMemory and $this->chunkTrigger);
}
public function getViewDistance($distance){
return $this->lowMemory ? min($this->chunkLimit, $distance) : $distance;
/**
* Returns the allowed chunk radius based on the current memory usage.
*
* @param int $distance
*
* @return int
*/
public function getViewDistance(int $distance) : int{
return $this->lowMemory ? min($this->chunkRadiusOverride, $distance) : $distance;
}
public function trigger($memory, $limit, $global = false, $triggerCount = 0){

File diff suppressed because it is too large Load Diff

View File

@ -74,7 +74,7 @@ namespace pocketmine {
use raklib\RakLib;
const VERSION = "1.6.2dev";
const API_VERSION = "3.0.0-ALPHA4";
const API_VERSION = "3.0.0-ALPHA5";
const CODENAME = "Unleashed";
/*
@ -128,7 +128,6 @@ namespace pocketmine {
set_time_limit(0); //Who set it to 30 seconds?!?!
gc_enable();
error_reporting(-1);
ini_set("allow_url_fopen", 1);
ini_set("display_errors", 1);
@ -357,7 +356,7 @@ namespace pocketmine {
return -1;
}
function getTrace($start = 1, $trace = null){
function getTrace($start = 0, $trace = null){
if($trace === null){
if(function_exists("xdebug_get_function_stack")){
$trace = array_reverse(xdebug_get_function_stack());
@ -398,11 +397,6 @@ namespace pocketmine {
++$errors;
}
if(!extension_loaded("sockets")){
$logger->critical("Unable to find the Socket extension.");
++$errors;
}
$pthreads_version = phpversion("pthreads");
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
@ -431,19 +425,21 @@ namespace pocketmine {
");
}
if(!extension_loaded("curl")){
$logger->critical("Unable to find the cURL extension.");
++$errors;
}
$extensions = [
"curl" => "cURL",
"json" => "JSON",
"mbstring" => "Multibyte String",
"yaml" => "YAML",
"sockets" => "Sockets",
"zip" => "Zip",
"zlib" => "Zlib"
];
if(!extension_loaded("yaml")){
$logger->critical("Unable to find the YAML extension.");
++$errors;
}
if(!extension_loaded("zlib")){
$logger->critical("Unable to find the Zlib extension.");
++$errors;
foreach($extensions as $ext => $name){
if(!extension_loaded($ext)){
$logger->critical("Unable to find the $name ($ext) extension.");
++$errors;
}
}
if($errors > 0){
@ -453,20 +449,25 @@ namespace pocketmine {
exit(1); //Exit with error
}
if(PHP_INT_SIZE < 8){
$logger->warning("Running PocketMine-MP with 32-bit systems/PHP is deprecated. Support for 32-bit may be dropped in the future.");
}
$gitHash = str_repeat("00", 20);
if(file_exists(\pocketmine\PATH . ".git/HEAD")){ //Found Git information!
$ref = trim(file_get_contents(\pocketmine\PATH . ".git/HEAD"));
if(preg_match('/^[0-9a-f]{40}$/i', $ref)){
define('pocketmine\GIT_COMMIT', strtolower($ref));
$gitHash = strtolower($ref);
}elseif(substr($ref, 0, 5) === "ref: "){
$refFile = \pocketmine\PATH . ".git/" . substr($ref, 5);
if(is_file($refFile)){
define('pocketmine\GIT_COMMIT', strtolower(trim(file_get_contents($refFile))));
$gitHash = strtolower(trim(file_get_contents($refFile)));
}
}
}
if(!defined('pocketmine\GIT_COMMIT')){ //Unknown :(
define('pocketmine\GIT_COMMIT', str_repeat("00", 20));
}
define('pocketmine\GIT_COMMIT', $gitHash);
@define("ENDIANNESS", (pack("d", 1) === "\77\360\0\0\0\0\0\0" ? Binary::BIG_ENDIAN : Binary::LITTLE_ENDIAN));
@define("INT32_MASK", is_int(0xffffffff) ? 0xffffffff : -1);

View File

@ -48,8 +48,8 @@ use pocketmine\inventory\Recipe;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\Item;
use pocketmine\lang\BaseLang;
use pocketmine\level\format\io\LevelProviderManager;
use pocketmine\level\format\io\leveldb\LevelDB;
use pocketmine\level\format\io\LevelProviderManager;
use pocketmine\level\format\io\region\Anvil;
use pocketmine\level\format\io\region\McRegion;
use pocketmine\level\format\io\region\PMAnvil;
@ -74,13 +74,13 @@ use pocketmine\nbt\tag\LongTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\CompressBatchedTask;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\RakLibInterface;
use pocketmine\network\Network;
use pocketmine\network\protocol\BatchPacket;
use pocketmine\network\protocol\DataPacket;
use pocketmine\network\protocol\Info as ProtocolInfo;
use pocketmine\network\protocol\PlayerListPacket;
use pocketmine\network\query\QueryHandler;
use pocketmine\network\RakLibInterface;
use pocketmine\network\rcon\RCON;
use pocketmine\network\upnp\UPnP;
use pocketmine\permission\BanList;
@ -90,6 +90,7 @@ use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginLoadOrder;
use pocketmine\plugin\PluginManager;
use pocketmine\plugin\ScriptPluginLoader;
use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\FileWriteTask;
use pocketmine\scheduler\SendUsageTask;
use pocketmine\scheduler\ServerScheduler;
@ -157,6 +158,9 @@ class Server{
private $currentTPS = 20;
private $currentUse = 0;
/** @var bool */
private $doTitleTick = true;
private $sendUsageTicker = 0;
private $dispatchSignals = false;
@ -176,6 +180,9 @@ class Server{
/** @var CraftingManager */
private $craftingManager;
/** @var ResourcePackManager */
private $resourceManager;
/** @var ConsoleCommandSender */
private $consoleSender;
@ -333,8 +340,19 @@ class Server{
/**
* @return int
*/
public function getViewDistance(){
return max(56, $this->getProperty("chunk-sending.max-chunks", 256));
public function getViewDistance() : int{
return max(2, $this->getConfigInt("view-distance", 8));
}
/**
* Returns a view distance up to the currently-allowed limit.
*
* @param int $distance
*
* @return int
*/
public function getAllowedViewDistance(int $distance) : int{
return max(2, min($distance, $this->memoryManager->getViewDistance($this->getViewDistance())));
}
/**
@ -583,6 +601,13 @@ class Server{
return $this->craftingManager;
}
/**
* @return ResourcePackManager
*/
public function getResourceManager() : ResourcePackManager{
return $this->resourceManager;
}
/**
* @return ServerScheduler
*/
@ -676,7 +701,7 @@ class Server{
*
* @return CompoundTag
*/
public function getOfflinePlayerData($name){
public function getOfflinePlayerData($name) : CompoundTag{
$name = strtolower($name);
$path = $this->getDataPath() . "players/";
if($this->shouldSavePlayerData()){
@ -795,7 +820,7 @@ class Server{
public function getPlayerExact($name){
$name = strtolower($name);
foreach($this->getOnlinePlayers() as $player){
if(strtolower($player->getName()) === $name){
if($player->getLowerCaseName() === $name){
return $player;
}
}
@ -812,7 +837,7 @@ class Server{
$partialName = strtolower($partialName);
$matchedPlayers = [];
foreach($this->getOnlinePlayers() as $player){
if(strtolower($player->getName()) === $partialName){
if($player->getLowerCaseName() === $partialName){
$matchedPlayers = [$player];
break;
}elseif(stripos($player->getName(), $partialName) !== false){
@ -1076,6 +1101,31 @@ class Server{
return true;
}
/**
* Searches all levels for the entity with the specified ID.
* Useful for tracking entities across multiple worlds without needing strong references.
*
* @param int $entityId
* @param Level|null $expectedLevel Level to look in first for the target
*
* @return Entity|null
*/
public function findEntity(int $entityId, Level $expectedLevel = null){
$levels = $this->levels;
if($expectedLevel !== null){
array_unshift($levels, $expectedLevel);
}
foreach($levels as $level){
assert(!$level->isClosed());
if(($entity = $level->getEntity($entityId)) instanceof Entity){
return $entity;
}
}
return null;
}
/**
* @param string $variable
* @param string $defaultValue
@ -1384,6 +1434,7 @@ class Server{
"enable-rcon" => false,
"rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10),
"auto-save" => true,
"view-distance" => 8
]);
$this->forceLanguage = $this->getProperty("settings.force-language", false);
@ -1418,6 +1469,8 @@ class Server{
$this->alwaysTickPlayers = (int) $this->getProperty("level-settings.always-tick-players", false);
$this->baseTickRate = (int) $this->getProperty("level-settings.base-tick-rate", 1);
$this->doTitleTick = (bool) $this->getProperty("console.title-tick", true);
$this->scheduler = new ServerScheduler();
if($this->getConfigBoolean("enable-rcon", false) === true){
@ -1498,6 +1551,8 @@ class Server{
Attribute::init();
$this->craftingManager = new CraftingManager();
$this->resourceManager = new ResourcePackManager($this, $this->getDataPath() . "resource_packs" . DIRECTORY_SEPARATOR);
$this->pluginManager = new PluginManager($this, $this->commandMap);
$this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
$this->pluginManager->setUseTimings($this->getProperty("settings.enable-profiling", false));
@ -1990,9 +2045,8 @@ class Server{
$errline = $e->getLine();
$type = ($errno === E_ERROR or $errno === E_USER_ERROR) ? \LogLevel::ERROR : (($errno === E_USER_WARNING or $errno === E_WARNING) ? \LogLevel::WARNING : \LogLevel::NOTICE);
if(($pos = strpos($errstr, "\n")) !== false){
$errstr = substr($errstr, 0, $pos);
}
$errstr = preg_replace('/\s+/', ' ', trim($errstr));
$errfile = cleanPath($errfile);
@ -2004,7 +2058,7 @@ class Server{
"fullFile" => $e->getFile(),
"file" => $errfile,
"line" => $errline,
"trace" => @getTrace(1, $trace)
"trace" => getTrace(0, $trace)
];
global $lastExceptionError, $lastError;
@ -2155,7 +2209,7 @@ class Server{
foreach($this->players as $p){
if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){
$p->close("", "Login timeout");
}elseif($this->alwaysTickPlayers){
}elseif($this->alwaysTickPlayers and $p->joined){
$p->onUpdate($currentTick);
}
}
@ -2200,7 +2254,7 @@ class Server{
if($this->getAutoSave()){
Timings::$worldSaveTimer->startTiming();
foreach($this->players as $index => $player){
if($player->isOnline()){
if($player->joined){
$player->save(true);
}elseif(!$player->isConnected()){
$this->removePlayer($player);
@ -2215,7 +2269,9 @@ class Server{
}
public function sendUsage($type = SendUsageTask::TYPE_STATUS){
$this->scheduler->scheduleAsyncTask(new SendUsageTask($this, $type, $this->uniquePlayers));
if($this->getProperty("anonymous-statistics.enabled", true)){
$this->scheduler->scheduleAsyncTask(new SendUsageTask($this, $type, $this->uniquePlayers));
}
$this->uniquePlayers = [];
}
@ -2249,10 +2305,6 @@ class Server{
}
private function titleTick(){
if(!Terminal::hasFormattingCodes()){
return;
}
$d = Utils::getRealMemoryUsage();
$u = Utils::getMemoryUsage(true);
@ -2328,7 +2380,9 @@ class Server{
}
if(($this->tickCounter & 0b1111) === 0){
$this->titleTick();
if($this->doTitleTick and Terminal::hasFormattingCodes()){
$this->titleTick();
}
$this->currentTPS = 20;
$this->currentUse = 0;
@ -2393,4 +2447,13 @@ class Server{
return true;
}
/**
* Called when something attempts to serialize the server instance.
*
* @throws \BadMethodCallException because Server instances cannot be serialized
*/
public function __sleep(){
throw new \BadMethodCallException("Cannot serialize Server instance");
}
}

View File

@ -81,13 +81,7 @@ class FenceGate extends Transparent{
}
public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){
$faces = [
0 => 3,
1 => 0,
2 => 1,
3 => 2,
];
$this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0] & 0x03;
$this->meta = ($player instanceof Player ? ($player->getDirection() - 1) & 0x03 : 0);
$this->getLevel()->setBlock($block, $this, true, true);
return true;
@ -100,8 +94,11 @@ class FenceGate extends Transparent{
}
public function onActivate(Item $item, Player $player = null){
$this->meta ^= 0x04; //Flip open/close state
//TODO: Face player when opened
$this->meta = (($this->meta ^ 0x04) & ~0x02);
if($player !== null){
$this->meta |= (($player->getDirection() - 1) & 0x02);
}
$this->getLevel()->setBlock($this, $this, true);
$this->level->addSound(new DoorSound($this));

View File

@ -29,4 +29,8 @@ class Furnace extends BurningFurnace{
public function getName(){
return "Furnace";
}
public function getLightLevel(){
return 0;
}
}

View File

@ -22,7 +22,7 @@
namespace pocketmine\block;
class GlowingObsidian extends Transparent{
class GlowingObsidian extends Solid{
protected $id = self::GLOWING_OBSIDIAN;

View File

@ -64,7 +64,7 @@ class Lava extends Liquid{
public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){
$ret = $this->getLevel()->setBlock($this, $this, true, false);
$this->getLevel()->scheduleUpdate($this, $this->tickRate());
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
return $ret;
}

View File

@ -189,7 +189,7 @@ abstract class Liquid extends Transparent{
public function onUpdate($type){
if($type === Level::BLOCK_UPDATE_NORMAL){
$this->checkForHarden();
$this->getLevel()->scheduleUpdate($this, $this->tickRate());
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
}elseif($type === Level::BLOCK_UPDATE_SCHEDULED){
if($this->temporalVector === null){
$this->temporalVector = new Vector3(0, 0, 0);
@ -242,7 +242,7 @@ abstract class Liquid extends Transparent{
$this->getLevel()->setBlock($this, new Air(), true);
}else{
$this->getLevel()->setBlock($this, Block::get($this->id, $decay), true);
$this->getLevel()->scheduleUpdate($this, $this->tickRate());
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
}
}elseif($flag){
//$this->getLevel()->scheduleUpdate($this, $this->tickRate());
@ -262,10 +262,10 @@ abstract class Liquid extends Transparent{
if($decay >= 8){
$this->getLevel()->setBlock($bottomBlock, Block::get($this->id, $decay), true);
$this->getLevel()->scheduleUpdate($bottomBlock, $this->tickRate());
$this->getLevel()->scheduleDelayedBlockUpdate($bottomBlock, $this->tickRate());
}else{
$this->getLevel()->setBlock($bottomBlock, Block::get($this->id, $decay + 8), true);
$this->getLevel()->scheduleUpdate($bottomBlock, $this->tickRate());
$this->getLevel()->scheduleDelayedBlockUpdate($bottomBlock, $this->tickRate());
}
}elseif($decay >= 0 and ($decay === 0 or !$bottomBlock->canBeFlowedInto())){
$flags = $this->getOptimalFlowDirections();
@ -310,7 +310,7 @@ abstract class Liquid extends Transparent{
}
$this->getLevel()->setBlock($block, Block::get($this->getId(), $newFlowDecay), true);
$this->getLevel()->scheduleUpdate($block, $this->tickRate());
$this->getLevel()->scheduleDelayedBlockUpdate($block, $this->tickRate());
}
}

View File

@ -21,10 +21,6 @@
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\item\Tool;
use pocketmine\Player;
class LitPumpkin extends Pumpkin{
protected $id = self::LIT_PUMPKIN;

View File

@ -32,4 +32,8 @@ class RedstoneTorch extends Torch{
public function getName(){
return "Redstone Torch";
}
public function getLightLevel(){
return 7;
}
}

View File

@ -34,7 +34,7 @@ class Torch extends Flowable{
}
public function getLightLevel(){
return 15;
return 14;
}
public function getName(){

View File

@ -48,7 +48,7 @@ class Water extends Liquid{
public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){
$ret = $this->getLevel()->setBlock($this, $this, true, false);
$this->getLevel()->scheduleUpdate($this, $this->tickRate());
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
return $ret;
}

View File

@ -32,12 +32,12 @@ use pocketmine\Server;
use pocketmine\utils\TextFormat;
abstract class Command{
/** @var \stdClass */
/** @var array */
private static $defaultDataTemplate = null;
/** @var string */
private $name;
/** @var \stdClass */
/** @var array */
protected $commandData = null;
/** @var string */
@ -87,11 +87,11 @@ abstract class Command{
}
/**
* Returns an \stdClass containing command data
* Returns an array containing command data
*
* @return \stdClass
* @return array
*/
public function getDefaultCommandData() : \stdClass{
public function getDefaultCommandData() : array{
return $this->commandData;
}
@ -101,25 +101,28 @@ abstract class Command{
*
* @param Player $player
*
* @return \stdClass|null
* @return array
*/
public function generateCustomCommandData(Player $player){
//TODO: fix command permission filtering on join
/*if(!$this->testPermissionSilent($player)){
return null;
}*/
$customData = clone $this->commandData;
$customData->aliases = $this->getAliases();
/*foreach($customData->overloads as &$overload){
if(isset($overload->pocketminePermission) and !$player->hasPermission($overload->pocketminePermission)){
unset($overload);
$customData = $this->commandData;
$customData["aliases"] = $this->getAliases();
/*foreach($customData["overloads"] as $overloadName => $overload){
if(isset($overload["pocketminePermission"]) and !$player->hasPermission($overload["pocketminePermission"])){
unset($customData["overloads"][$overloadName]);
}
}*/
return $customData;
}
public function getOverloads(): \stdClass{
return $this->commandData->overloads;
/**
* @return array
*/
public function getOverloads(): array{
return $this->commandData["overloads"];
}
/**
@ -129,7 +132,7 @@ abstract class Command{
*
* @return mixed
*/
public abstract function execute(CommandSender $sender, $commandLabel, array $args);
abstract public function execute(CommandSender $sender, $commandLabel, array $args);
/**
* @return string
@ -142,7 +145,7 @@ abstract class Command{
* @return string
*/
public function getPermission(){
return $this->commandData->pocketminePermission ?? null;
return $this->commandData["pocketminePermission"] ?? null;
}
@ -151,9 +154,9 @@ abstract class Command{
*/
public function setPermission($permission){
if($permission !== null){
$this->commandData->pocketminePermission = $permission;
$this->commandData["pocketminePermission"] = $permission;
}else{
unset($this->commandData->pocketminePermission);
unset($this->commandData["pocketminePermission"]);
}
}
@ -239,7 +242,7 @@ abstract class Command{
public function unregister(CommandMap $commandMap){
if($this->allowChangesFrom($commandMap)){
$this->commandMap = null;
$this->activeAliases = $this->commandData->aliases;
$this->activeAliases = $this->commandData["aliases"];
$this->label = $this->nextLabel;
return true;
@ -282,7 +285,7 @@ abstract class Command{
* @return string
*/
public function getDescription(){
return $this->commandData->description;
return $this->commandData["description"];
}
/**
@ -296,7 +299,7 @@ abstract class Command{
* @param string[] $aliases
*/
public function setAliases(array $aliases){
$this->commandData->aliases = $aliases;
$this->commandData["aliases"] = $aliases;
if(!$this->isRegistered()){
$this->activeAliases = (array) $aliases;
}
@ -306,7 +309,7 @@ abstract class Command{
* @param string $description
*/
public function setDescription($description){
$this->commandData->description = $description;
$this->commandData["description"] = $description;
}
/**
@ -323,11 +326,14 @@ abstract class Command{
$this->usageMessage = $usage;
}
public static final function generateDefaultData() : \stdClass{
/**
* @return array
*/
public static final function generateDefaultData() : array{
if(self::$defaultDataTemplate === null){
self::$defaultDataTemplate = json_decode(file_get_contents(Server::getInstance()->getFilePath() . "src/pocketmine/resources/command_default.json"));
self::$defaultDataTemplate = json_decode(file_get_contents(Server::getInstance()->getFilePath() . "src/pocketmine/resources/command_default.json"), true);
}
return clone self::$defaultDataTemplate;
return self::$defaultDataTemplate;
}
/**

View File

@ -70,7 +70,7 @@ class PluginCommand extends Command implements PluginIdentifiableCommand{
* @param CommandExecutor $executor
*/
public function setExecutor(CommandExecutor $executor){
$this->executor = ($executor != null) ? $executor : $this->owningPlugin;
$this->executor = $executor;
}
/**

View File

@ -57,6 +57,7 @@ use pocketmine\command\defaults\TeleportCommand;
use pocketmine\command\defaults\TellCommand;
use pocketmine\command\defaults\TimeCommand;
use pocketmine\command\defaults\TimingsCommand;
use pocketmine\command\defaults\TransferServerCommand;
use pocketmine\command\defaults\VanillaCommand;
use pocketmine\command\defaults\VersionCommand;
use pocketmine\command\defaults\WhitelistCommand;
@ -115,6 +116,7 @@ class SimpleCommandMap implements CommandMap{
$this->register("pocketmine", new TimeCommand("time"));
$this->register("pocketmine", new TimingsCommand("timings"));
$this->register("pocketmine", new ReloadCommand("reload"));
$this->register("pocketmine", new TransferServerCommand("transferserver"));
if($this->server->getProperty("debug.commands", false)){
$this->register("pocketmine", new StatusCommand("status"));
@ -134,7 +136,7 @@ class SimpleCommandMap implements CommandMap{
if($label === null){
$label = $command->getName();
}
$label = strtolower(trim($label));
$label = trim($label);
$fallbackPrefix = strtolower(trim($fallbackPrefix));
$registered = $this->registerAlias($command, false, $fallbackPrefix, $label);
@ -175,15 +177,35 @@ class SimpleCommandMap implements CommandMap{
return true;
}
public function dispatch(CommandSender $sender, $commandLine){
$args = explode(" ", $commandLine);
/**
* Returns a command to match the specified command line, or null if no matching command was found.
* This method is intended to provide capability for handling commands with spaces in their name.
* The referenced parameters will be modified accordingly depending on the resulting matched command.
*
* @param string &$commandName
* @param string[] &$args
*
* @return Command|null
*/
public function matchCommand(string &$commandName, array &$args){
$count = min(count($args), 255);
if(count($args) === 0){
return false;
for($i = 0; $i < $count; ++$i){
$commandName .= array_shift($args);
if(($command = $this->getCommand($commandName)) instanceof Command){
return $command;
}
$commandName .= " ";
}
$sentCommandLabel = strtolower(array_shift($args));
$target = $this->getCommand($sentCommandLabel);
return null;
}
public function dispatch(CommandSender $sender, $commandLine){
$args = explode(" ", $commandLine);
$sentCommandLabel = "";
$target = $this->matchCommand($sentCommandLabel, $args);
if($target === null){
return false;
@ -211,11 +233,7 @@ class SimpleCommandMap implements CommandMap{
}
public function getCommand($name){
if(isset($this->knownCommands[$name])){
return $this->knownCommands[$name];
}
return null;
return $this->knownCommands[$name] ?? null;
}
/**
@ -233,7 +251,7 @@ class SimpleCommandMap implements CommandMap{
$values = $this->server->getCommandAliases();
foreach($values as $alias => $commandStrings){
if(strpos($alias, ":") !== false or strpos($alias, " ") !== false){
if(strpos($alias, ":") !== false){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.illegal", [$alias]));
continue;
}
@ -241,20 +259,33 @@ class SimpleCommandMap implements CommandMap{
$targets = [];
$bad = "";
$recursive = "";
foreach($commandStrings as $commandString){
$args = explode(" ", $commandString);
$command = $this->getCommand($args[0]);
$commandName = "";
$command = $this->matchCommand($commandName, $args);
if($command === null){
if(strlen($bad) > 0){
$bad .= ", ";
}
$bad .= $commandString;
}elseif($commandName === $alias){
if($recursive !== ""){
$recursive .= ", ";
}
$recursive .= $commandString;
}else{
$targets[] = $commandString;
}
}
if($recursive !== ""){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.recursive", [$alias, $recursive]));
continue;
}
if(strlen($bad) > 0){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.notFound", [$alias, $bad]));
continue;

View File

@ -24,7 +24,7 @@ namespace pocketmine\command\defaults;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\event\TranslationContainer;
use pocketmine\network\protocol\SetDifficultyPacket;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
use pocketmine\Server;
class DifficultyCommand extends VanillaCommand{

View File

@ -23,7 +23,6 @@ namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\entity\Effect;
use pocketmine\entity\InstantEffect;
use pocketmine\event\TranslationContainer;
use pocketmine\utils\TextFormat;
@ -80,12 +79,9 @@ class EffectCommand extends VanillaCommand{
$amplification = 0;
if(count($args) >= 3){
$duration = (int) $args[2];
if(!($effect instanceof InstantEffect)){
$duration *= 20;
}
}elseif($effect instanceof InstantEffect){
$duration = 1;
$duration = ((int) $args[2]) * 20; //ticks
}else{
$duration = $effect->getDefaultDuration();
}
if(count($args) >= 4){

View File

@ -24,6 +24,9 @@ namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\event\TimingsHandler;
use pocketmine\event\TranslationContainer;
use pocketmine\Player;
use pocketmine\scheduler\BulkCurlTask;
use pocketmine\Server;
class TimingsCommand extends VanillaCommand{
@ -101,39 +104,42 @@ class TimingsCommand extends VanillaCommand{
"poster" => $sender->getServer()->getName(),
"content" => stream_get_contents($fileTimings)
];
$ch = curl_init("http://paste.ubuntu.com/");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_AUTOREFERER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["User-Agent: " . $this->getName() . " " . $sender->getServer()->getPocketMineVersion()]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
try{
$data = curl_exec($ch);
if($data === false){
throw new \Exception(curl_error($ch));
}
}catch(\Exception $e){
$sender->getServer()->getLogger()->logException($e);
}
curl_close($ch);
if(preg_match('#^Location: http://paste\\.ubuntu\\.com/([0-9]{1,})/#m', $data, $matches) == 0){
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.pasteError"));
return true;
}
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsUpload", ["http://paste.ubuntu.com/" . $matches[1] . "/"]));
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsRead", ["http://timings.aikar.co/?url=" . $matches[1]]));
fclose($fileTimings);
$sender->getServer()->getScheduler()->scheduleAsyncTask(new class([
["page" => "http://paste.ubuntu.com", "extraOpts" => [
CURLOPT_HTTPHEADER => ["User-Agent: " . $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion()],
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $data,
]]
], $sender) extends BulkCurlTask{
public function onCompletion(Server $server){
$sender = $this->fetchLocal($server);
if($sender instanceof Player and !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
return;
}
$result = $this->getResult()[0];
if($result instanceof \RuntimeException){
$server->getLogger()->logException($result);
return;
}
list(, $headers) = $result;
foreach($headers as $headerGroup){
if(isset($headerGroup["location"]) and preg_match('#^http://paste\\.ubuntu\\.com/([0-9]{1,})/#', trim($headerGroup["location"]), $match)){
$pasteId = $match[1];
break;
}
}
if(isset($pasteId)){
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsUpload", ["http://paste.ubuntu.com/" . $pasteId . "/"]));
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsRead",
["http://" . $sender->getServer()->getProperty("timings.host", "timings.pmmp.io") . "/?url=$pasteId"]));
}else{
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.pasteError"));
}
}
});
}else{
fclose($fileTimings);
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsWrite", [$timings]));

View File

@ -0,0 +1,56 @@
<?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/
*
*
*/
namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\event\TranslationContainer;
use pocketmine\Player;
class TransferServerCommand extends VanillaCommand{
public function __construct($name){
parent::__construct(
$name,
"%pocketmine.command.transferserver.description",
"%pocketmine.command.transferserver.usage"
);
$this->setPermission("pocketmine.command.transferserver");
}
public function execute(CommandSender $sender, $commandLabel, array $args){
if(count($args) < 1){
$sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage]));
return false;
}elseif(!($sender instanceof Player)){
$sender->sendMessage("This command must be executed as a player");
return false;
}
$sender->transfer($args[0], (int) ($args[1] ?? 19132));
return true;
}
}

View File

@ -23,7 +23,7 @@ namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\event\TranslationContainer;
use pocketmine\network\protocol\Info;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\plugin\Plugin;
use pocketmine\utils\TextFormat;
@ -51,7 +51,7 @@ class VersionCommand extends VanillaCommand{
$sender->getServer()->getCodename(),
$sender->getServer()->getApiVersion(),
$sender->getServer()->getVersion(),
Info::CURRENT_PROTOCOL
ProtocolInfo::CURRENT_PROTOCOL
]));
}else{
$pluginName = implode(" ", $args);

View File

@ -24,7 +24,7 @@ namespace pocketmine\entity;
use pocketmine\level\Level;
use pocketmine\level\particle\CriticalParticle;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\Player;
class Arrow extends Projectile{

View File

@ -165,11 +165,15 @@ class Attribute{
return $this;
}
public function resetToDefault(){
$this->setValue($this->getDefaultValue());
}
public function getValue(){
return $this->currentValue;
}
public function setValue($value, $fit = false){
public function setValue($value, $fit = false, bool $forceSend = false){
if($value > $this->getMaxValue() or $value < $this->getMinValue()){
if(!$fit){
throw new \InvalidArgumentException("Value $value exceeds the range!");
@ -180,7 +184,10 @@ class Attribute{
if($this->currentValue != $value){
$this->desynchronized = true;
$this->currentValue = $value;
}elseif($forceSend){
$this->desynchronized = true;
}
return $this;
}

View File

@ -38,6 +38,9 @@ class AttributeMap implements \ArrayAccess{
return $this->attributes[$id] ?? null;
}
/**
* @return Attribute[]
*/
public function getAll(): array{
return $this->attributes;
}

View File

@ -26,22 +26,20 @@ use pocketmine\event\entity\EntityEffectAddEvent;
use pocketmine\event\entity\EntityEffectRemoveEvent;
use pocketmine\event\entity\EntityRegainHealthEvent;
use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\network\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\Player;
use pocketmine\utils\Config;
class Effect{
const SPEED = 1;
const SLOWNESS = 2;
const HASTE = 3;
const SWIFTNESS = 3;
const FATIGUE = 4;
const MINING_FATIGUE = 4;
const FATIGUE = 4, MINING_FATIGUE = 4;
const STRENGTH = 5;
// TODO: const HEALING = 6;
// TODO: const HARMING = 7;
const INSTANT_HEALTH = 6, HEALING = 6;
const INSTANT_DAMAGE = 7, HARMING = 7;
const JUMP = 8;
const NAUSEA = 9;
const CONFUSION = 9;
const NAUSEA = 9, CONFUSION = 9;
const REGENERATION = 10;
const DAMAGE_RESISTANCE = 11;
const FIRE_RESISTANCE = 12;
@ -54,44 +52,43 @@ class Effect{
const POISON = 19;
const WITHER = 20;
const HEALTH_BOOST = 21;
const ABSORPTION = 22; // TODO implement
const ABSORPTION = 22;
const SATURATION = 23;
const LEVITATION = 24; //TODO
/** @var Effect[] */
protected static $effects;
protected static $effects = [];
public static function init(){
self::$effects = new \SplFixedArray(256);
$config = new Config(\pocketmine\PATH . "src/pocketmine/resources/effects.json", Config::JSON, []);
self::$effects[Effect::SPEED] = new Effect(Effect::SPEED, "%potion.moveSpeed", 124, 175, 198);
self::$effects[Effect::SLOWNESS] = new Effect(Effect::SLOWNESS, "%potion.moveSlowdown", 90, 108, 129, true);
self::$effects[Effect::SWIFTNESS] = new Effect(Effect::SWIFTNESS, "%potion.digSpeed", 217, 192, 67);
self::$effects[Effect::FATIGUE] = new Effect(Effect::FATIGUE, "%potion.digSlowDown", 74, 66, 23, true);
self::$effects[Effect::STRENGTH] = new Effect(Effect::STRENGTH, "%potion.damageBoost", 147, 36, 35);
//self::$effects[Effect::HEALING] = new InstantEffect(Effect::HEALING, "%potion.heal", 248, 36, 35);
//self::$effects[Effect::HARMING] = new InstantEffect(Effect::HARMING, "%potion.harm", 67, 10, 9, true);
self::$effects[Effect::JUMP] = new Effect(Effect::JUMP, "%potion.jump", 34, 255, 76);
self::$effects[Effect::NAUSEA] = new Effect(Effect::NAUSEA, "%potion.confusion", 85, 29, 74, true);
self::$effects[Effect::REGENERATION] = new Effect(Effect::REGENERATION, "%potion.regeneration", 205, 92, 171);
self::$effects[Effect::DAMAGE_RESISTANCE] = new Effect(Effect::DAMAGE_RESISTANCE, "%potion.resistance", 153, 69, 58);
self::$effects[Effect::FIRE_RESISTANCE] = new Effect(Effect::FIRE_RESISTANCE, "%potion.fireResistance", 228, 154, 58);
self::$effects[Effect::WATER_BREATHING] = new Effect(Effect::WATER_BREATHING, "%potion.waterBreathing", 46, 82, 153);
self::$effects[Effect::INVISIBILITY] = new Effect(Effect::INVISIBILITY, "%potion.invisibility", 127, 131, 146);
self::$effects[Effect::BLINDNESS] = new Effect(Effect::BLINDNESS, "%potion.blindness", 191, 192, 192);
self::$effects[Effect::NIGHT_VISION] = new Effect(Effect::NIGHT_VISION, "%potion.nightVision", 0, 0, 139);
self::$effects[Effect::HUNGER] = new Effect(Effect::HUNGER, "%potion.hunger", 46, 139, 87);
self::$effects[Effect::WEAKNESS] = new Effect(Effect::WEAKNESS, "%potion.weakness", 72, 77, 72, true);
self::$effects[Effect::POISON] = new Effect(Effect::POISON, "%potion.poison", 78, 147, 49, true);
self::$effects[Effect::WITHER] = new Effect(Effect::WITHER, "%potion.wither", 53, 42, 39, true);
self::$effects[Effect::HEALTH_BOOST] = new Effect(Effect::HEALTH_BOOST, "%potion.healthBoost", 248, 125, 35);
self::$effects[Effect::ABSORPTION] = new Effect(Effect::ABSORPTION, "%potion.absorption", 36, 107, 251);
self::$effects[Effect::SATURATION] = new Effect(Effect::SATURATION, "%potion.saturation", 255, 0, 255);
foreach($config->getAll() as $name => $data){
$color = hexdec($data["color"]);
$r = ($color >> 16) & 0xff;
$g = ($color >> 8) & 0xff;
$b = $color & 0xff;
self::registerEffect($name, new Effect(
$data["id"],
"%" . $data["name"],
$r,
$g,
$b,
$data["isBad"] ?? false,
$data["default_duration"] ?? 300 * 20,
$data["has_bubbles"] ?? true
));
}
}
public static function registerEffect(string $internalName, Effect $effect){
self::$effects[$effect->getId()] = $effect;
self::$effects[$internalName] = $effect;
}
/**
* @param int $id
*
* @return $this
* @return Effect|null
*/
public static function getEffect($id){
if(isset(self::$effects[$id])){
@ -100,9 +97,14 @@ class Effect{
return null;
}
/**
* @param string $name
*
* @return Effect|null
*/
public static function getEffectByName($name){
if(defined(Effect::class . "::" . strtoupper($name))){
return self::getEffect(constant(Effect::class . "::" . strtoupper($name)));
if(isset(self::$effects[$name])){
return clone self::$effects[$name];
}
return null;
}
@ -124,40 +126,106 @@ class Effect{
protected $bad;
public function __construct($id, $name, $r, $g, $b, $isBad = false){
protected $defaultDuration = 300 * 20;
protected $hasBubbles = true;
/**
* @param int $id Effect ID as per Minecraft PE
* @param string $name Translation key used for effect name
* @param int $r 0-255, red balance of potion particle colour
* @param int $g 0-255, green balance of potion particle colour
* @param int $b 0-255, blue balance of potion particle colour
* @param bool $isBad Whether the effect is harmful
* @param int $defaultDuration Duration in ticks the effect will last for by default if applied without a duration.
* @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles)
*/
public function __construct($id, $name, $r, $g, $b, $isBad = false, int $defaultDuration = 300 * 20, bool $hasBubbles = true){
$this->id = $id;
$this->name = $name;
$this->bad = (bool) $isBad;
$this->setColor($r, $g, $b);
$this->defaultDuration = $defaultDuration;
$this->duration = $defaultDuration;
$this->hasBubbles = $hasBubbles;
}
/**
* Returns the translation key used to translate this effect's name.
* @return string
*/
public function getName(){
return $this->name;
}
/**
* Returns the effect ID as per Minecraft PE
* @return int
*/
public function getId(){
return $this->id;
}
/**
* Sets the duration in ticks of the effect.
* @param $ticks
*
* @return $this
*/
public function setDuration($ticks){
$this->duration = $ticks;
return $this;
}
/**
* Returns the duration remaining of the effect in ticks.
* @return int
*/
public function getDuration(){
return $this->duration;
}
/**
* Returns the default duration this effect will apply for if a duration is not specified.
* @return int
*/
public function getDefaultDuration() : int{
return $this->defaultDuration;
}
/**
* Returns whether this effect will give the subject potion bubbles.
* @return bool
*/
public function hasBubbles() : bool{
return $this->hasBubbles;
}
/**
* Returns whether this effect will produce some visible effect, such as bubbles or particles.
* NOTE: Do not confuse this with {@link Effect#hasBubbles}. For example, Instant Damage does not have bubbles, but still produces visible effects (particles).
*
* @return bool
*/
public function isVisible(){
return $this->show;
}
/**
* Changes the visibility of the effect.
* @param bool $bool
*
* @return $this
*/
public function setVisible($bool){
$this->show = (bool) $bool;
return $this;
}
/**
* Returns the amplifier of this effect.
* TODO: fix mess of amplifier used instead of level for effect calculation.
*
* @return int
*/
public function getAmplifier(){
@ -174,19 +242,40 @@ class Effect{
return $this;
}
/**
* Returns whether the effect is ambient.
* @return bool
*/
public function isAmbient(){
return $this->ambient;
}
/**
* Sets the ambiency of this effect.
* @param bool $ambient
*
* @return $this
*/
public function setAmbient($ambient = true){
$this->ambient = (bool) $ambient;
return $this;
}
/**
* Returns whether this effect is harmful.
* TODO: implement inverse effect results for undead mobs
*
* @return bool
*/
public function isBad(){
return $this->bad;
}
/**
* Returns whether the effect will do something on the current tick.
*
* @return bool
*/
public function canTick(){
switch($this->id){
case Effect::POISON:
@ -212,10 +301,19 @@ class Effect{
return ($this->duration % $interval) === 0;
}
return true;
case Effect::INSTANT_DAMAGE:
case Effect::INSTANT_HEALTH:
//If forced to last longer than 1 tick, these apply every tick.
return true;
}
return false;
}
/**
* Applies effect results to an entity.
*
* @param Entity $entity
*/
public function applyEffect(Entity $entity){
switch($this->id){
case Effect::POISON:
@ -241,17 +339,48 @@ class Effect{
if($entity instanceof Human){
$entity->exhaust(0.5 * $this->amplifier, PlayerExhaustEvent::CAUSE_POTION);
}
break;
case Effect::INSTANT_HEALTH:
//TODO: add particles (witch spell)
if($entity->getHealth() < $entity->getMaxHealth()){
$amount = 2 * (2 ** (($this->amplifier + 1) % 32));
$entity->heal($amount, new EntityRegainHealthEvent($entity, $amount, EntityRegainHealthEvent::CAUSE_MAGIC));
}
break;
case Effect::INSTANT_DAMAGE:
//TODO: add particles (witch spell)
$amount = 2 * (2 ** (($this->amplifier + 1) % 32));
$entity->attack($amount, new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, $amount));
break;
}
}
/**
* Returns an RGB color array of this effect's color.
* @return array
*/
public function getColor(){
return [$this->color >> 16, ($this->color >> 8) & 0xff, $this->color & 0xff];
}
/**
* Sets the color of this effect.
*
* @param int $r
* @param int $g
* @param int $b
*/
public function setColor($r, $g, $b){
$this->color = (($r & 0xff) << 16) + (($g & 0xff) << 8) + ($b & 0xff);
}
/**
* Adds this effect to the Entity, performing effect overriding as specified.
*
* @param Entity $entity
* @param bool $modify
* @param Effect|null $oldEffect
*/
public function add(Entity $entity, $modify = false, Effect $oldEffect = null){
$entity->getLevel()->getServer()->getPluginManager()->callEvent($ev = new EntityEffectAddEvent($entity, $this, $modify, $oldEffect));
if($ev->isCancelled()){
@ -259,7 +388,7 @@ class Effect{
}
if($entity instanceof Player){
$pk = new MobEffectPacket();
$pk->eid = 0;
$pk->eid = $entity->getId();
$pk->effectId = $this->getId();
$pk->amplifier = $this->getAmplifier();
$pk->particles = $this->isVisible();
@ -273,30 +402,58 @@ class Effect{
$entity->dataPacket($pk);
}
if($this->id === Effect::INVISIBILITY){
$entity->setDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_INVISIBLE, true);
$entity->setNameTagVisible(false);
}elseif($this->id === Effect::SPEED){
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
if($ev->willModify() and $oldEffect !== null){
$speed = $attr->getValue() / (1 + 0.2 * $oldEffect->getAmplifier());
}else{
$speed = $attr->getValue();
}
$speed *= (1 + 0.2 * $this->amplifier);
$attr->setValue($speed);
}elseif($this->id === Effect::SLOWNESS){
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
if($ev->willModify() and $oldEffect !== null){
$speed = $attr->getValue() / (1 - 0.15 * $oldEffect->getAmplifier());
}else{
$speed = $attr->getValue();
}
$speed *= (1 - 0.15 * $this->amplifier);
$attr->setValue($speed);
switch($this->id){
case Effect::INVISIBILITY:
$entity->setDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_INVISIBLE, true);
$entity->setNameTagVisible(false);
break;
case Effect::SPEED:
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
if($ev->willModify() and $oldEffect !== null){
$speed = $attr->getValue() / (1 + 0.2 * $oldEffect->getAmplifier());
}else{
$speed = $attr->getValue();
}
$speed *= (1 + 0.2 * $this->amplifier);
$attr->setValue($speed);
break;
case Effect::SLOWNESS:
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
if($ev->willModify() and $oldEffect !== null){
$speed = $attr->getValue() / (1 - 0.15 * $oldEffect->getAmplifier());
}else{
$speed = $attr->getValue();
}
$speed *= (1 - 0.15 * $this->amplifier);
$attr->setValue($speed, true);
break;
case Effect::HEALTH_BOOST:
$attr = $entity->getAttributeMap()->getAttribute(Attribute::HEALTH);
if($ev->willModify() and $oldEffect !== null){
$max = $attr->getMaxValue() - (4 * ($oldEffect->getAmplifier() + 1));
}else{
$max = $attr->getMaxValue();
}
$max += (4 * ($this->amplifier + 1));
$attr->setMaxValue($max);
break;
case Effect::ABSORPTION:
$new = (4 * ($this->amplifier + 1));
if($new > $entity->getAbsorption()){
$entity->setAbsorption($new);
}
break;
}
}
/**
* Removes the effect from the entity, resetting any changed values back to their original defaults.
*
* @param Entity $entity
*/
public function remove(Entity $entity){
$entity->getLevel()->getServer()->getPluginManager()->callEvent($ev = new EntityEffectRemoveEvent($entity, $this));
if($ev->isCancelled()){
@ -304,22 +461,33 @@ class Effect{
}
if($entity instanceof Player){
$pk = new MobEffectPacket();
$pk->eid = 0;
$pk->eid = $entity->getId();
$pk->eventId = MobEffectPacket::EVENT_REMOVE;
$pk->effectId = $this->getId();
$entity->dataPacket($pk);
}
if($this->id === Effect::INVISIBILITY){
$entity->setDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_INVISIBLE, false);
$entity->setNameTagVisible(true);
}elseif($this->id === Effect::SPEED){
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
$attr->setValue($attr->getValue() / (1 + 0.2 * $this->amplifier));
}elseif($this->id === Effect::SLOWNESS){
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
$attr->setValue($attr->getValue() / (1 - 0.15 * $this->amplifier));
switch($this->id){
case Effect::INVISIBILITY:
$entity->setDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_INVISIBLE, false);
$entity->setNameTagVisible(true);
break;
case Effect::SPEED:
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
$attr->setValue($attr->getValue() / (1 + 0.2 * $this->amplifier));
break;
case Effect::SLOWNESS:
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
$attr->setValue($attr->getValue() / (1 - 0.15 * $this->amplifier));
break;
case Effect::HEALTH_BOOST:
$attr = $entity->getAttributeMap()->getAttribute(Attribute::HEALTH);
$attr->setMaxValue($attr->getMaxValue() - (4 * ($this->amplifier + 1)));
break;
case Effect::ABSORPTION:
$entity->setAbsorption(0);
break;
}
}
}

View File

@ -52,9 +52,9 @@ use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\protocol\MobEffectPacket;
use pocketmine\network\protocol\RemoveEntityPacket;
use pocketmine\network\protocol\SetEntityDataPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
use pocketmine\network\mcpe\protocol\SetEntityDataPacket;
use pocketmine\Player;
use pocketmine\plugin\Plugin;
use pocketmine\Server;
@ -109,6 +109,14 @@ abstract class Entity extends Location implements Metadatable{
* 58 (byte)
* 59 (float)
* 60 (float) */
const DATA_AREA_EFFECT_CLOUD_RADIUS = 61; //float
const DATA_AREA_EFFECT_CLOUD_WAITING = 62; //int
const DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 63; //int
/* 64 (int), shulker-related
* 65 (byte), shulker-related
* 66 (short) shulker-related
* 67 (unknown), shulker-related */
const DATA_TRADING_PLAYER_EID = 68; //long
const DATA_FLAG_ONFIRE = 0;
@ -331,6 +339,7 @@ abstract class Entity extends Location implements Metadatable{
$this->invulnerable = $this->namedtag["Invulnerable"] > 0 ? true : false;
$this->attributeMap = new AttributeMap();
$this->addAttributes();
$this->chunk->addEntity($this);
$this->level->addEntity($this);
@ -384,21 +393,38 @@ abstract class Entity extends Location implements Metadatable{
public function setNameTagAlwaysVisible($value = true){
$this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ALWAYS_SHOW_NAMETAG, $value);
}
/**
* @return float
*/
public function getScale(): float{
public function getScale() : float{
return $this->getDataProperty(self::DATA_SCALE);
}
/**
* @param float $value
*/
public function setScale(float $value){
$multiplier = $value / $this->getScale();
$this->width *= $multiplier;
$this->height *= $multiplier;
$halfWidth = $this->width / 2;
$this->boundingBox->setBounds(
$this->x - $halfWidth,
$this->y,
$this->z - $halfWidth,
$this->x + $halfWidth,
$this->y + $this->height,
$this->z + $halfWidth
);
$this->setDataProperty(self::DATA_SCALE, self::DATA_TYPE_FLOAT, $value);
$this->setDataProperty(self::DATA_BOUNDING_BOX_WIDTH, self::DATA_TYPE_FLOAT, $this->width);
$this->setDataProperty(self::DATA_BOUNDING_BOX_HEIGHT, self::DATA_TYPE_FLOAT, $this->height);
}
public function isSneaking(){
return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_SNEAKING);
}
@ -415,7 +441,7 @@ abstract class Entity extends Location implements Metadatable{
if($value !== $this->isSprinting()){
$this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_SPRINTING, (bool) $value);
$attr = $this->attributeMap->getAttribute(Attribute::MOVEMENT_SPEED);
$attr->setValue($value ? ($attr->getValue() * 1.3) : ($attr->getValue() / 1.3));
$attr->setValue($value ? ($attr->getValue() * 1.3) : ($attr->getValue() / 1.3), false, true);
}
}
@ -462,7 +488,7 @@ abstract class Entity extends Location implements Metadatable{
if(isset($this->effects[$effect->getId()])){
$oldEffect = $this->effects[$effect->getId()];
if(
abs($effect->getAmplifier()) <= ($oldEffect->getAmplifier())
abs($effect->getAmplifier()) < ($oldEffect->getAmplifier())
or (abs($effect->getAmplifier()) === abs($oldEffect->getAmplifier())
and $effect->getDuration() < $oldEffect->getDuration())
){
@ -476,10 +502,6 @@ abstract class Entity extends Location implements Metadatable{
$this->effects[$effect->getId()] = $effect;
$this->recalculateEffectColor();
if($effect->getId() === Effect::HEALTH_BOOST){
$this->setHealth($this->getHealth() + 4 * ($effect->getAmplifier() + 1));
}
}
protected function recalculateEffectColor(){
@ -488,7 +510,7 @@ abstract class Entity extends Location implements Metadatable{
$count = 0;
$ambient = true;
foreach($this->effects as $effect){
if($effect->isVisible()){
if($effect->isVisible() and $effect->hasBubbles()){
$c = $effect->getColor();
$color[0] += $c[0] * ($effect->getAmplifier() + 1);
$color[1] += $c[1] * ($effect->getAmplifier() + 1);
@ -505,7 +527,7 @@ abstract class Entity extends Location implements Metadatable{
$g = ($color[1] / $count) & 0xff;
$b = ($color[2] / $count) & 0xff;
$this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, ($r << 16) + ($g << 8) + $b);
$this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, 0xff000000 | ($r << 16) | ($g << 8) | $b);
$this->setDataProperty(Entity::DATA_POTION_AMBIENT, Entity::DATA_TYPE_BYTE, $ambient ? 1 : 0);
}else{
$this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, 0);
@ -621,8 +643,6 @@ abstract class Entity extends Location implements Metadatable{
$this->scheduleUpdate();
$this->addAttributes();
if(isset($this->namedtag->ActiveEffects)){
foreach($this->namedtag->ActiveEffects->getValue() as $e){
$amplifier = $e["Amplifier"] & 0xff; //0-255 only
@ -661,7 +681,7 @@ abstract class Entity extends Location implements Metadatable{
public function sendPotionEffects(Player $player){
foreach($this->effects as $effect){
$pk = new MobEffectPacket();
$pk->eid = 0;
$pk->eid = $this->id;
$pk->effectId = $effect->getId();
$pk->amplifier = $effect->getAmplifier();
$pk->particles = $effect->isVisible();
@ -691,20 +711,23 @@ abstract class Entity extends Location implements Metadatable{
}
$p->dataPacket(clone $pk);
}
if($this instanceof Player){
$pk->eid = 0;
$this->dataPacket($pk);
}
}
/**
* @param Player $player
* @param bool $send
*/
public function despawnFrom(Player $player){
public function despawnFrom(Player $player, bool $send = true){
if(isset($this->hasSpawned[$player->getLoaderId()])){
$pk = new RemoveEntityPacket();
$pk->eid = $this->getId();
$player->dataPacket($pk);
if($send){
$pk = new RemoveEntityPacket();
$pk->eid = $this->id;
$player->dataPacket($pk);
}
unset($this->hasSpawned[$player->getLoaderId()]);
}
}
@ -731,7 +754,21 @@ abstract class Entity extends Location implements Metadatable{
$this->setLastDamageCause($source);
$this->setHealth($this->getHealth() - $source->getFinalDamage());
$damage = $source->getFinalDamage();
$absorption = $this->getAbsorption();
if($absorption > 0){
if($absorption > $damage){
//Use absorption health before normal health.
$this->setAbsorption($absorption - $damage);
$damage = 0;
}else{
$this->setAbsorption(0);
$damage -= $absorption;
}
}
$this->setHealth($this->getHealth() - $damage);
}
/**
@ -781,6 +818,14 @@ abstract class Entity extends Location implements Metadatable{
}
}
public function getAbsorption() : int{
return 0;
}
public function setAbsorption(int $absorption){
}
/**
* @param EntityDamageEvent $type
*/
@ -803,7 +848,7 @@ abstract class Entity extends Location implements Metadatable{
* @return int
*/
public function getMaxHealth(){
return $this->maxHealth + ($this->hasEffect(Effect::HEALTH_BOOST) ? 4 * ($this->getEffect(Effect::HEALTH_BOOST)->getAmplifier() + 1) : 0);
return $this->maxHealth;
}
/**
@ -1065,7 +1110,7 @@ abstract class Entity extends Location implements Metadatable{
//return !($this instanceof Player);
}
public final function scheduleUpdate(){
final public function scheduleUpdate(){
$this->level->updateEntities[$this->id] = $this;
}
@ -1632,13 +1677,22 @@ abstract class Entity extends Location implements Metadatable{
if(!$this->closed){
$this->server->getPluginManager()->callEvent(new EntityDespawnEvent($this));
$this->closed = true;
$this->despawnFromAll();
$this->hasSpawned = [];
if($this->chunk !== null){
$this->chunk->removeEntity($this);
$this->chunk = null;
}
if($this->getLevel() !== null){
$this->getLevel()->removeEntity($this);
$this->setLevel(null);
}
$this->namedtag = null;
$this->lastDamageCause = null;
}
}

View File

@ -29,7 +29,7 @@ use pocketmine\item\Item as ItemItem;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\network\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\Player;
class FallingSand extends Entity{

View File

@ -27,6 +27,7 @@ use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\inventory\InventoryHolder;
use pocketmine\inventory\PlayerInventory;
use pocketmine\item\Item as ItemItem;
use pocketmine\level\Level;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
@ -35,8 +36,7 @@ use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\protocol\AddPlayerPacket;
use pocketmine\network\protocol\RemoveEntityPacket;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\Player;
use pocketmine\utils\UUID;
@ -62,13 +62,21 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
public $eyeHeight = 1.62;
protected $skinId;
protected $skin;
protected $skin = null;
protected $foodTickTimer = 0;
protected $totalXp = 0;
protected $xpSeed;
public function __construct(Level $level, CompoundTag $nbt){
if($this->skin === null and (!isset($nbt->Skin) or !isset($nbt->Skin->Data) or !Player::isValidSkin($nbt->Skin->Data->getValue()))){
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
}
parent::__construct($level, $nbt);
}
public function getSkinData(){
return $this->skin;
}
@ -96,10 +104,23 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
* @param string $skinId
*/
public function setSkin($str, $skinId){
if(!Player::isValidSkin($str)){
throw new \InvalidStateException("Specified skin is not valid, must be 8KiB or 16KiB");
}
$this->skin = $str;
$this->skinId = $skinId;
}
public function jump(){
parent::jump();
if($this->isSprinting()){
$this->exhaust(0.8, PlayerExhaustEvent::CAUSE_SPRINT_JUMPING);
}else{
$this->exhaust(0.2, PlayerExhaustEvent::CAUSE_JUMPING);
}
}
public function getFood() : float{
return $this->attributeMap->getAttribute(Attribute::HUNGER)->getValue();
}
@ -285,6 +306,12 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
}
}
if(isset($this->namedtag->SelectedInventorySlot) and $this->namedtag->SelectedInventorySlot instanceof IntTag){
$this->inventory->setHeldItemIndex($this->namedtag->SelectedInventorySlot->getValue(), false);
}else{
$this->inventory->setHeldItemIndex(0, false);
}
parent::initEntity();
if(!isset($this->namedtag->foodLevel) or !($this->namedtag->foodLevel instanceof IntTag)){
@ -347,42 +374,50 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
public function entityBaseTick($tickDiff = 1){
$hasUpdate = parent::entityBaseTick($tickDiff);
$this->doFoodTick($tickDiff);
return $hasUpdate;
}
public function doFoodTick(int $tickDiff = 1){
if($this->isAlive()){
$food = $this->getFood();
$health = $this->getHealth();
if($food >= 18){
$this->foodTickTimer++;
if($this->foodTickTimer >= 80 and $health < $this->getMaxHealth()){
$this->heal(1, new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_SATURATION));
$this->exhaust(3.0, PlayerExhaustEvent::CAUSE_HEALTH_REGEN);
$this->foodTickTimer = 0;
$difficulty = $this->server->getDifficulty();
$this->foodTickTimer += $tickDiff;
if($this->foodTickTimer >= 80){
$this->foodTickTimer = 0;
}
if($difficulty === 0 and $this->foodTickTimer % 10 === 0){ //Peaceful
if($food < 20){
$this->addFood(1.0);
}
}elseif($food === 0){
$this->foodTickTimer++;
if($this->foodTickTimer >= 80){
$diff = $this->server->getDifficulty();
$can = false;
if($diff === 1){
$can = $health > 10;
}elseif($diff === 2){
$can = $health > 1;
}elseif($diff === 3){
$can = true;
if($this->foodTickTimer % 20 === 0 and $health < $this->getMaxHealth()){
$this->heal(1, new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_SATURATION));
}
}
if($this->foodTickTimer === 0){
if($food >= 18){
if($health < $this->getMaxHealth()){
$this->heal(1, new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_SATURATION));
$this->exhaust(3.0, PlayerExhaustEvent::CAUSE_HEALTH_REGEN);
}
if($can){
}elseif($food <= 0){
if(($difficulty === 1 and $health > 10) or ($difficulty === 2 and $health > 1) or $difficulty === 3){
$this->attack(1, new EntityDamageEvent($this, EntityDamageEvent::CAUSE_STARVATION, 1));
}
}
}
if($food <= 6){
if($this->isSprinting()){
$this->setSprinting(false);
}
}
}
return $hasUpdate;
}
public function getName(){
@ -402,6 +437,12 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
public function saveNBT(){
parent::saveNBT();
$this->namedtag->foodLevel = new IntTag("foodLevel", $this->getFood());
$this->namedtag->foodExhaustionLevel = new FloatTag("foodExhaustionLevel", $this->getExhaustion());
$this->namedtag->foodSaturationLevel = new FloatTag("foodSaturationLevel", $this->getSaturation());
$this->namedtag->foodTickTimer = new IntTag("foodTickTimer", $this->foodTickTimer);
$this->namedtag->Inventory = new ListTag("Inventory", []);
$this->namedtag->Inventory->setTagType(NBT::TAG_Compound);
if($this->inventory !== null){
@ -428,11 +469,12 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
}
//Normal inventory
$slotCount = Player::SURVIVAL_SLOTS + 9;
//$slotCount = (($this instanceof Player and ($this->gamemode & 0x01) === 1) ? Player::CREATIVE_SLOTS : Player::SURVIVAL_SLOTS) + 9;
for($slot = 9; $slot < $slotCount; ++$slot){
$slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize();
for($slot = $this->inventory->getHotbarSize(); $slot < $slotCount; ++$slot){
$item = $this->inventory->getItem($slot - 9);
$this->namedtag->Inventory[$slot] = $item->nbtSerialize($slot);
if($item->getId() !== ItemItem::AIR){
$this->namedtag->Inventory[$slot] = $item->nbtSerialize($slot);
}
}
//Armor
@ -442,6 +484,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$this->namedtag->Inventory[$slot] = $item->nbtSerialize($slot);
}
}
$this->namedtag->SelectedInventorySlot = new IntTag("SelectedInventorySlot", $this->inventory->getHeldItemIndex());
}
if(strlen($this->getSkinData()) > 0){
@ -456,7 +500,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
if($player !== $this and !isset($this->hasSpawned[$player->getLoaderId()])){
$this->hasSpawned[$player->getLoaderId()] = $player;
if(strlen($this->skin) < 64 * 32 * 4){
if(!Player::isValidSkin($this->skin)){
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
}
@ -488,20 +532,9 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
}
}
public function despawnFrom(Player $player){
if(isset($this->hasSpawned[$player->getLoaderId()])){
$pk = new RemoveEntityPacket();
$pk->eid = $this->getId();
$player->dataPacket($pk);
unset($this->hasSpawned[$player->getLoaderId()]);
}
}
public function close(){
if(!$this->closed){
if(!($this instanceof Player) or $this->loggedIn){
if($this->inventory !== null){
foreach($this->inventory->getViewers() as $viewer){
$viewer->removeWindow($this->inventory);
}

View File

@ -28,7 +28,7 @@ use pocketmine\item\Item as ItemItem;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\protocol\AddItemEntityPacket;
use pocketmine\network\mcpe\protocol\AddItemEntityPacket;
use pocketmine\Player;
class Item extends Entity{

View File

@ -31,7 +31,7 @@ use pocketmine\event\Timings;
use pocketmine\item\Item as ItemItem;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\network\protocol\EntityEventPacket;
use pocketmine\network\mcpe\protocol\EntityEventPacket;
use pocketmine\utils\BlockIterator;
abstract class Living extends Entity implements Damageable{
@ -43,6 +43,8 @@ abstract class Living extends Entity implements Damageable{
protected $invisible = false;
protected $jumpVelocity = 0.42;
protected function initEntity(){
parent::initEntity();
@ -68,7 +70,7 @@ abstract class Living extends Entity implements Damageable{
public function setHealth($amount){
$wasAlive = $this->isAlive();
parent::setHealth($amount);
$this->attributeMap->getAttribute(Attribute::HEALTH)->setValue($this->getHealth());
$this->attributeMap->getAttribute(Attribute::HEALTH)->setValue($this->getHealth(), true);
if($this->isAlive() and !$wasAlive){
$pk = new EntityEventPacket();
$pk->eid = $this->getId();
@ -77,16 +79,28 @@ abstract class Living extends Entity implements Damageable{
}
}
public function getMaxHealth(){
return $this->attributeMap->getAttribute(Attribute::HEALTH)->getMaxValue();
}
public function setMaxHealth($amount){
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount);
}
public function getAbsorption() : int{
return (int) $this->attributeMap->getAttribute(Attribute::ABSORPTION)->getValue();
}
public function setAbsorption(int $absorption){
$this->attributeMap->getAttribute(Attribute::ABSORPTION)->setValue($absorption);
}
public function saveNBT(){
parent::saveNBT();
$this->namedtag->Health = new ShortTag("Health", $this->getHealth());
}
public abstract function getName();
abstract public function getName();
public function hasLineOfSight(Entity $entity){
//TODO: head height
@ -103,6 +117,23 @@ abstract class Living extends Entity implements Damageable{
$this->attackTime = 0;
}
/**
* Returns the initial upwards velocity of a jumping entity in blocks/tick, including additional velocity due to effects.
* @return float
*/
public function getJumpVelocity() : float{
return $this->jumpVelocity + ($this->hasEffect(Effect::JUMP) ? (($this->getEffect(Effect::JUMP)->getAmplifier() + 1) / 10) : 0);
}
/**
* Called when the entity jumps from the ground. This method adds upwards velocity to the entity.
*/
public function jump(){
if($this->onGround){
$this->motionY = $this->getJumpVelocity(); //Y motion should already be 0 if we're jumping from the ground.
}
}
public function attack($damage, EntityDamageEvent $source){
if($this->attackTime > 0 or $this->noDamageTicks > 0){
$lastCause = $this->getLastDamageCause();
@ -123,13 +154,15 @@ abstract class Living extends Entity implements Damageable{
$e = $source->getChild();
}
if($e->isOnFire() > 0){
$this->setOnFire(2 * $this->server->getDifficulty());
}
if($e !== null){
if($e->isOnFire() > 0){
$this->setOnFire(2 * $this->server->getDifficulty());
}
$deltaX = $this->x - $e->x;
$deltaZ = $this->z - $e->z;
$this->knockBack($e, $damage, $deltaX, $deltaZ, $source->getKnockBack());
$deltaX = $this->x - $e->x;
$deltaZ = $this->z - $e->z;
$this->knockBack($e, $damage, $deltaX, $deltaZ, $source->getKnockBack());
}
}
$pk = new EntityEventPacket();
@ -169,6 +202,10 @@ abstract class Living extends Entity implements Damageable{
return;
}
parent::kill();
$this->callDeathEvent();
}
protected function callDeathEvent(){
$this->server->getPluginManager()->callEvent($ev = new EntityDeathEvent($this, $this->getDrops()));
foreach($ev->getDrops() as $item){
$this->getLevel()->dropItem($this, $item);

View File

@ -25,7 +25,7 @@ use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\ExplosionPrimeEvent;
use pocketmine\level\Explosion;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\network\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\Player;
class PrimedTNT extends Entity implements Explosive{

View File

@ -23,7 +23,7 @@ namespace pocketmine\entity;
use pocketmine\level\Level;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\Player;
class Snowball extends Projectile{

View File

@ -25,8 +25,8 @@ use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item as ItemItem;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\AddEntityPacket;
use pocketmine\network\protocol\EntityEventPacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\EntityEventPacket;
use pocketmine\Player;
class Squid extends WaterAnimal implements Ageable{
@ -43,8 +43,8 @@ class Squid extends WaterAnimal implements Ageable{
private $switchDirectionTicker = 0;
public function initEntity(){
$this->setMaxHealth(10);
parent::initEntity();
$this->setMaxHealth(5);
}
public function getName(){
@ -60,7 +60,9 @@ class Squid extends WaterAnimal implements Ageable{
if($source instanceof EntityDamageByEntityEvent){
$this->swimSpeed = mt_rand(150, 350) / 2000;
$e = $source->getDamager();
$this->swimDirection = (new Vector3($this->x - $e->x, $this->y - $e->y, $this->z - $e->z))->normalize();
if($e !== null){
$this->swimDirection = (new Vector3($this->x - $e->x, $this->y - $e->y, $this->z - $e->z))->normalize();
}
$pk = new EntityEventPacket();
$pk->eid = $this->getId();

View File

@ -22,7 +22,7 @@
namespace pocketmine\entity;
use pocketmine\nbt\tag\IntTag;
use pocketmine\network\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\Player;
class Villager extends Creature implements NPC, Ageable{

View File

@ -23,7 +23,7 @@ namespace pocketmine\entity;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\item\Item as ItemItem;
use pocketmine\network\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\Player;
class Zombie extends Monster{

View File

@ -30,8 +30,6 @@ abstract class Event{
* Any callable event must declare the static variable
*
* public static $handlerList = null;
* public static $eventPool = [];
* public static $nextEvent = 0;
*
* Not doing so will deny the proper event initialization
*/
@ -85,4 +83,4 @@ abstract class Event{
return static::$handlerList;
}
}
}

View File

@ -25,6 +25,13 @@ use pocketmine\level\Level;
class LevelTimings{
/** @var TimingsHandler */
public $setBlock;
/** @var TimingsHandler */
public $doBlockLightUpdates;
/** @var TimingsHandler */
public $doBlockSkyLightUpdates;
/** @var TimingsHandler */
public $mobSpawn;
/** @var TimingsHandler */
@ -79,6 +86,10 @@ class LevelTimings{
public function __construct(Level $level){
$name = $level->getFolderName() . " - ";
$this->setBlock = new TimingsHandler("** " . $name . "setBlock");
$this->doBlockLightUpdates = new TimingsHandler("** " . $name . "doBlockLightUpdates");
$this->doBlockSkyLightUpdates = new TimingsHandler("** " . $name . "doBlockSkyLightUpdates");
$this->mobSpawn = new TimingsHandler("** " . $name . "mobSpawn");
$this->doChunkUnload = new TimingsHandler("** " . $name . "doChunkUnload");
$this->doTickPending = new TimingsHandler("** " . $name . "doTickPending");

View File

@ -22,7 +22,7 @@
namespace pocketmine\event;
use pocketmine\entity\Entity;
use pocketmine\network\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\Player;
use pocketmine\plugin\PluginManager;
use pocketmine\scheduler\PluginTask;

View File

@ -24,6 +24,9 @@ namespace pocketmine\event\entity;
use pocketmine\block\Block;
use pocketmine\entity\Entity;
/**
* Called when an entity takes damage from a block.
*/
class EntityDamageByBlockEvent extends EntityDamageEvent{
/** @var Block */

View File

@ -23,10 +23,13 @@ namespace pocketmine\event\entity;
use pocketmine\entity\Entity;
/**
* Called when an entity takes damage from an entity sourced from another entity, for example being hit by a snowball thrown by a Player.
*/
class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent{
/** @var Entity */
private $childEntity;
/** @var int */
private $childEntityEid;
/**
@ -37,15 +40,17 @@ class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent{
* @param int|int[] $damage
*/
public function __construct(Entity $damager, Entity $childEntity, Entity $entity, $cause, $damage){
$this->childEntity = $childEntity;
$this->childEntityEid = $childEntity->getId();
parent::__construct($damager, $entity, $cause, $damage);
}
/**
* @return Entity
* Returns the entity which caused the damage, or null if the entity has been killed or closed.
*
* @return Entity|null
*/
public function getChild(){
return $this->childEntity;
return $this->getEntity()->getLevel()->getServer()->findEntity($this->childEntityEid, $this->getEntity()->getLevel());
}

View File

@ -24,10 +24,13 @@ namespace pocketmine\event\entity;
use pocketmine\entity\Effect;
use pocketmine\entity\Entity;
/**
* Called when an entity takes damage from another entity.
*/
class EntityDamageByEntityEvent extends EntityDamageEvent{
/** @var Entity */
private $damager;
/** @var int */
private $damagerEid;
/** @var float */
private $knockBack;
@ -39,7 +42,7 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
* @param float $knockBack
*/
public function __construct(Entity $damager, Entity $entity, $cause, $damage, $knockBack = 0.4){
$this->damager = $damager;
$this->damagerEid = $damager->getId();
$this->knockBack = $knockBack;
parent::__construct($entity, $cause, $damage);
$this->addAttackerModifiers($damager);
@ -56,10 +59,12 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
}
/**
* @return Entity
* Returns the attacking entity, or null if the attacker has been killed or closed.
*
* @return Entity|null
*/
public function getDamager(){
return $this->damager;
return $this->getEntity()->getLevel()->getServer()->findEntity($this->damagerEid, $this->getEntity()->getLevel());
}
/**

View File

@ -25,6 +25,9 @@ use pocketmine\entity\Effect;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
/**
* Called when an entity takes damage.
*/
class EntityDamageEvent extends EntityEvent implements Cancellable{
public static $handlerList = null;

View File

@ -34,19 +34,21 @@ class PlayerExhaustEvent extends PlayerEvent implements Cancellable{
const CAUSE_HEALTH_REGEN = 4;
const CAUSE_POTION = 5;
const CAUSE_WALKING = 6;
const CAUSE_SNEAKING = 7;
const CAUSE_SPRINTING = 7;
const CAUSE_SWIMMING = 8;
const CAUSE_JUMPING = 10;
const CAUSE_JUMPING = 9;
const CAUSE_SPRINT_JUMPING = 10;
const CAUSE_CUSTOM = 11;
const CAUSE_FLAG_SPRINT = 0x10000;
/** @var float */
private $amount;
/** @var int */
private $cause;
public function __construct(Human $human, float $amount, int $cause){
$this->player = $human;
$this->amount = $amount;
$this->cause = $cause;
}
/**
@ -63,4 +65,12 @@ class PlayerExhaustEvent extends PlayerEvent implements Cancellable{
public function setAmount(float $amount){
$this->amount = $amount;
}
/**
* Returns an int cause of the exhaustion - one of the constants at the top of this class.
* @return int
*/
public function getCause() : int{
return $this->cause;
}
}

View File

@ -0,0 +1,66 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\event\Cancellable;
use pocketmine\Player;
class PlayerTransferEvent extends PlayerEvent implements Cancellable{
public static $handlerList = null;
protected $address;
protected $port = 19132;
protected $message;
public function __construct(Player $player, string $address, int $port, string $message){
$this->player = $player;
$this->address = $address;
$this->port = $port;
$this->message = $message;
}
public function getAddress() : string{
return $this->address;
}
public function setAddress(string $address){
$this->address = $address;
}
public function getPort() : int{
return $this->port;
}
public function setPort(int $port){
$this->port = $port;
}
public function getMessage() : string{
return $this->message;
}
public function setMessage(string $message){
$this->message = $message;
}
}

View File

@ -22,7 +22,7 @@
namespace pocketmine\event\server;
use pocketmine\event\Cancellable;
use pocketmine\network\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\Player;
class DataPacketReceiveEvent extends ServerEvent implements Cancellable{

View File

@ -22,7 +22,7 @@
namespace pocketmine\event\server;
use pocketmine\event\Cancellable;
use pocketmine\network\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\Player;
class DataPacketSendEvent extends ServerEvent implements Cancellable{

View File

@ -25,8 +25,8 @@ use pocketmine\entity\Entity;
use pocketmine\event\entity\EntityInventoryChangeEvent;
use pocketmine\event\inventory\InventoryOpenEvent;
use pocketmine\item\Item;
use pocketmine\network\protocol\ContainerSetContentPacket;
use pocketmine\network\protocol\ContainerSetSlotPacket;
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
use pocketmine\Player;
use pocketmine\Server;

View File

@ -22,7 +22,7 @@
namespace pocketmine\inventory;
use pocketmine\level\Level;
use pocketmine\network\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\Player;
use pocketmine\tile\Chest;

View File

@ -22,8 +22,8 @@
namespace pocketmine\inventory;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\ContainerClosePacket;
use pocketmine\network\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\Player;
abstract class ContainerInventory extends BaseInventory{

View File

@ -23,7 +23,7 @@ namespace pocketmine\inventory;
use pocketmine\event\Timings;
use pocketmine\item\Item;
use pocketmine\network\protocol\CraftingDataPacket;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\Server;
use pocketmine\utils\Config;
use pocketmine\utils\MainLogger;
@ -54,30 +54,26 @@ class CraftingManager{
switch($recipe["type"]){
case 0:
// TODO: handle multiple result items
if(count($recipe["output"]) === 1){
$first = $recipe["output"][0];
$result = new ShapelessRecipe(Item::get($first["id"], $first["damage"], $first["count"], $first["nbt"]));
$first = $recipe["output"][0];
$result = new ShapelessRecipe(Item::get($first["id"], $first["damage"], $first["count"], $first["nbt"]));
foreach($recipe["input"] as $ingredient){
$result->addIngredient(Item::get($ingredient["id"], $ingredient["damage"], $ingredient["count"], $first["nbt"]));
}
$this->registerRecipe($result);
foreach($recipe["input"] as $ingredient){
$result->addIngredient(Item::get($ingredient["id"], $ingredient["damage"], $ingredient["count"], $first["nbt"]));
}
$this->registerRecipe($result);
break;
case 1:
// TODO: handle multiple result items
if(count($recipe["output"]) === 1){
$first = $recipe["output"][0];
$result = new ShapedRecipe(Item::get($first["id"], $first["damage"], $first["count"], $first["nbt"]), $recipe["height"], $recipe["width"]);
$first = $recipe["output"][0];
$result = new ShapedRecipe(Item::get($first["id"], $first["damage"], $first["count"], $first["nbt"]), $recipe["height"], $recipe["width"]);
$shape = array_chunk($recipe["input"], $recipe["width"]);
foreach($shape as $y => $row){
foreach($row as $x => $ingredient){
$result->addIngredient($x, $y, Item::get($ingredient["id"], ($ingredient["damage"] < 0 ? -1 : $ingredient["damage"]), $ingredient["count"], $ingredient["nbt"]));
}
$shape = array_chunk($recipe["input"], $recipe["width"]);
foreach($shape as $y => $row){
foreach($row as $x => $ingredient){
$result->addIngredient($x, $y, Item::get($ingredient["id"], ($ingredient["damage"] < 0 ? -1 : $ingredient["damage"]), $ingredient["count"], $ingredient["nbt"]));
}
$this->registerRecipe($result);
}
$this->registerRecipe($result);
break;
case 2:
case 3:

View File

@ -23,7 +23,7 @@ namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\level\Level;
use pocketmine\network\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\Player;
use pocketmine\tile\Chest;

View File

@ -21,10 +21,14 @@
namespace pocketmine\inventory;
use pocketmine\network\mcpe\protocol\types\WindowTypes;
/**
* Saves all the information regarding default inventory sizes and types
*/
class InventoryType{
//NOTE: Do not confuse these with the network IDs.
const CHEST = 0;
const DOUBLE_CHEST = 1;
const PLAYER = 2;
@ -56,19 +60,19 @@ class InventoryType{
return;
}
static::$default[static::CHEST] = new InventoryType(27, "Chest", 0);
static::$default[static::DOUBLE_CHEST] = new InventoryType(27 + 27, "Double Chest", 0);
static::$default[static::PLAYER] = new InventoryType(36 + 4, "Player", 0); //36 CONTAINER, 4 ARMOR
static::$default[static::CRAFTING] = new InventoryType(5, "Crafting", 1); //4 CRAFTING slots, 1 RESULT
static::$default[static::WORKBENCH] = new InventoryType(10, "Crafting", 1); //9 CRAFTING slots, 1 RESULT
static::$default[static::FURNACE] = new InventoryType(3, "Furnace", 2); //2 INPUT, 1 OUTPUT
static::$default[static::ENCHANT_TABLE] = new InventoryType(2, "Enchant", 3); //1 INPUT/OUTPUT, 1 LAPIS
static::$default[static::BREWING_STAND] = new InventoryType(4, "Brewing", 4); //1 INPUT, 3 POTION
static::$default[static::ANVIL] = new InventoryType(3, "Anvil", 5); //2 INPUT, 1 OUTPUT
//TODO: add the below
//6: dispenser
//7: dropper
//8: hopper
//TODO: move network stuff out of here
//TODO: move inventory data to json
static::$default = [
static::CHEST => new InventoryType(27, "Chest", WindowTypes::CONTAINER),
static::DOUBLE_CHEST => new InventoryType(27 + 27, "Double Chest", WindowTypes::CONTAINER),
static::PLAYER => new InventoryType(36 + 4, "Player", WindowTypes::INVENTORY), //36 CONTAINER, 4 ARMOR
static::CRAFTING => new InventoryType(5, "Crafting", WindowTypes::INVENTORY), //yes, the use of INVENTORY is intended! 4 CRAFTING slots, 1 RESULT
static::WORKBENCH => new InventoryType(10, "Crafting", WindowTypes::WORKBENCH), //9 CRAFTING slots, 1 RESULT
static::FURNACE => new InventoryType(3, "Furnace", WindowTypes::FURNACE), //2 INPUT, 1 OUTPUT
static::ENCHANT_TABLE => new InventoryType(2, "Enchant", WindowTypes::ENCHANTMENT), //1 INPUT/OUTPUT, 1 LAPIS
static::BREWING_STAND => new InventoryType(4, "Brewing", WindowTypes::BREWING_STAND), //1 INPUT, 3 POTION
static::ANVIL => new InventoryType(3, "Anvil", WindowTypes::ANVIL) //2 INPUT, 1 OUTP
];
}
/**

View File

@ -26,10 +26,10 @@ use pocketmine\event\entity\EntityArmorChangeEvent;
use pocketmine\event\entity\EntityInventoryChangeEvent;
use pocketmine\event\player\PlayerItemHeldEvent;
use pocketmine\item\Item;
use pocketmine\network\protocol\ContainerSetContentPacket;
use pocketmine\network\protocol\ContainerSetSlotPacket;
use pocketmine\network\protocol\MobArmorEquipmentPacket;
use pocketmine\network\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\Player;
use pocketmine\Server;
@ -40,7 +40,7 @@ class PlayerInventory extends BaseInventory{
protected $hotbar;
public function __construct(Human $player){
$this->hotbar = range(0, $this->getHotbarSize() - 1, 1);
$this->resetHotbar(false);
parent::__construct($player, InventoryType::get(InventoryType::PLAYER));
}
@ -53,20 +53,95 @@ class PlayerInventory extends BaseInventory{
$this->sendContents($this->getViewers());
}
public function getHotbarSlotIndex($index){
return ($index >= 0 and $index < $this->getHotbarSize()) ? $this->hotbar[$index] : -1;
/**
* Called when a client equips a hotbar slot. This method should not be used by plugins.
* This method will call PlayerItemHeldEvent.
*
* @param int $hotbarSlot Number of the hotbar slot to equip.
* @param int|null $inventorySlot Inventory slot to map to the specified hotbar slot. Supply null to make no change to the link.
*
* @return bool if the equipment change was successful, false if not.
*/
public function equipItem(int $hotbarSlot, $inventorySlot = null) : bool{
if($inventorySlot === null){
$inventorySlot = $this->getHotbarSlotIndex($this->getHeldItemIndex());
}
if($hotbarSlot < 0 or $hotbarSlot >= $this->getHotbarSize() or $inventorySlot < -1 or $inventorySlot >= $this->getSize()){
$this->sendContents($this->getHolder());
return false;
}
$this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($inventorySlot), $inventorySlot, $hotbarSlot));
if($ev->isCancelled()){
$this->sendContents($this->getHolder());
return false;
}
$this->setHotbarSlotIndex($hotbarSlot, $inventorySlot);
$this->setHeldItemIndex($hotbarSlot, false);
return true;
}
public function setHotbarSlotIndex($index, $slot){
if($index >= 0 and $index < $this->getHotbarSize() and $slot >= -1 and $slot < $this->getSize()){
$this->hotbar[$index] = $slot;
/**
* Returns the index of the inventory slot mapped to the specified hotbar slot, or -1 if the hotbar slot does not exist.
* @param int $index
*
* @return int
*/
public function getHotbarSlotIndex($index){
return $this->hotbar[$index] ?? -1;
}
/**
* Links a hotbar slot to the specified slot in the main inventory. -1 links to no slot and will clear the hotbar slot.
* This method is intended for use in network interaction with clients only.
*
* NOTE: Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
*
* @param int $hotbarSlot
* @param int $inventorySlot
*/
public function setHotbarSlotIndex($hotbarSlot, $inventorySlot){
if($hotbarSlot >= 0 and $hotbarSlot < $this->getHotbarSize() and $inventorySlot >= -1 and $inventorySlot < $this->getSize()){
if($inventorySlot !== -1 and ($alreadyEquippedIndex = array_search($inventorySlot, $this->hotbar)) !== false){
/* Swap the slots
* This assumes that the equipped slot can only be equipped in one other slot
* it will not account for ancient bugs where the same slot ended up linked to several hotbar slots.
* Such bugs will require a hotbar reset to default.
*/
$this->hotbar[$alreadyEquippedIndex] = $this->hotbar[$hotbarSlot];
}
$this->hotbar[$hotbarSlot] = $inventorySlot;
}
}
/**
* Resets hotbar links to their original defaults.
* @param bool $send Whether to send changes to the holder.
*/
public function resetHotbar(bool $send = true){
$this->hotbar = range(0, $this->getHotbarSize() - 1, 1);
if($send){
$this->sendContents($this->getHolder());
}
}
/**
* Returns the hotbar slot number the holder is currently holding.
* @return int
*/
public function getHeldItemIndex(){
return $this->itemInHandIndex;
}
/**
* Sets which hotbar slot the player is currently loading.
*
* @param int $index 0-8 index of the hotbar slot to hold
* @param bool $send Whether to send updates back to the inventory holder. This should usually be true for plugin calls.
* It should only be false to prevent feedback loops of equipment packets between client and server.
*/
public function setHeldItemIndex($index, $send = true){
if($index >= 0 and $index < $this->getHotbarSize()){
$this->itemInHandIndex = $index;
@ -79,6 +154,11 @@ class PlayerInventory extends BaseInventory{
}
}
/**
* Returns the currently-held item.
*
* @return Item
*/
public function getItemInHand(){
$item = $this->getItem($this->getHeldItemSlot());
if($item instanceof Item){
@ -89,6 +169,7 @@ class PlayerInventory extends BaseInventory{
}
/**
* Sets the item in the currently-held slot to the specified item.
* @param Item $item
*
* @return bool
@ -97,39 +178,39 @@ class PlayerInventory extends BaseInventory{
return $this->setItem($this->getHeldItemSlot(), $item);
}
/**
* Returns the hotbar slot number currently held.
*
* @return int
*/
public function getHeldItemSlot(){
return $this->getHotbarSlotIndex($this->itemInHandIndex);
}
/**
* Sets the hotbar slot link of the currently-held hotbar slot.
* @deprecated Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
*
* @param int $slot
*/
public function setHeldItemSlot($slot){
if($slot >= -1 and $slot < $this->getSize()){
$item = $this->getItem($slot);
$itemIndex = $this->getHeldItemIndex();
if($this->getHolder() instanceof Player){
Server::getInstance()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $item, $slot, $itemIndex));
if($ev->isCancelled()){
$this->sendContents($this->getHolder());
return;
}
}
$this->setHotbarSlotIndex($itemIndex, $slot);
$this->setHotbarSlotIndex($this->getHeldItemIndex(), $slot);
}
}
/**
* Sends the currently-held item to specified targets.
* @param Player|Player[] $target
*/
public function sendHeldItem($target){
$item = $this->getItemInHand();
$pk = new MobEquipmentPacket();
$pk->eid = ($target === $this->getHolder() ? 0 : $this->getHolder()->getId());
$pk->eid = $this->getHolder()->getId();
$pk->item = $item;
$pk->slot = $this->getHeldItemSlot();
$pk->selectedSlot = $this->getHeldItemIndex();
$pk->inventorySlot = $this->getHeldItemSlot();
$pk->hotbarSlot = $this->getHeldItemIndex();
if(!is_array($target)){
$target->dataPacket($pk);
@ -161,6 +242,10 @@ class PlayerInventory extends BaseInventory{
}
}
/**
* Returns the number of slots in the hotbar.
* @return int
*/
public function getHotbarSize(){
return 9;
}

View File

@ -98,7 +98,7 @@ class SimpleTransactionGroup implements TransactionGroup{
}
$checkSourceItem = $ts->getInventory()->getItem($ts->getSlot());
$sourceItem = $ts->getSourceItem();
if(!$checkSourceItem->deepEquals($sourceItem) or $sourceItem->getCount() !== $checkSourceItem->getCount()){
if(!$checkSourceItem->equals($sourceItem) or $sourceItem->getCount() !== $checkSourceItem->getCount()){
return false;
}
if($sourceItem->getId() !== Item::AIR){
@ -108,7 +108,7 @@ class SimpleTransactionGroup implements TransactionGroup{
foreach($needItems as $i => $needItem){
foreach($haveItems as $j => $haveItem){
if($needItem->deepEquals($haveItem)){
if($needItem->equals($haveItem)){
$amount = min($needItem->getCount(), $haveItem->getCount());
$needItem->setCount($needItem->getCount() - $amount);
$haveItem->setCount($haveItem->getCount() - $amount);
@ -130,7 +130,21 @@ class SimpleTransactionGroup implements TransactionGroup{
$haveItems = [];
$needItems = [];
return $this->matchItems($haveItems, $needItems) and count($haveItems) === 0 and count($needItems) === 0 and count($this->transactions) > 0;
if($this->matchItems($needItems, $haveItems) and count($this->transactions) > 0){
if(count($haveItems) === 0 and count($needItems) === 0){
return true;
}elseif($this->source->isCreative(true) and count($needItems) > 0){ //Added items from creative inventory
foreach($needItems as $item){
if(Item::getCreativeItemIndex($item) === -1 and $item->getId() !== Item::AIR){
return false;
}
}
return true;
}
}
return false;
}
public function execute(){

View File

@ -24,7 +24,7 @@ namespace pocketmine\item;
use pocketmine\entity\Entity;
use pocketmine\entity\Human;
use pocketmine\event\entity\EntityEatItemEvent;
use pocketmine\network\protocol\EntityEventPacket;
use pocketmine\network\mcpe\protocol\EntityEventPacket;
use pocketmine\Player;
abstract class Food extends Item implements FoodSource{

View File

@ -46,12 +46,22 @@ class Item implements ItemIds, \JsonSerializable{
private static $cachedParser = null;
private static function parseCompoundTag(string $tag) : CompoundTag{
if(strlen($tag) === 0){
throw new \InvalidArgumentException("No NBT data found in supplied string");
}
if(self::$cachedParser === null){
self::$cachedParser = new NBT(NBT::LITTLE_ENDIAN);
}
self::$cachedParser->read($tag);
return self::$cachedParser->getData();
$data = self::$cachedParser->getData();
if(!($data instanceof CompoundTag)){
throw new \InvalidArgumentException("Invalid item NBT string given, it could not be deserialized");
}
return $data;
}
private static function writeCompoundTag(CompoundTag $tag) : string{
@ -65,13 +75,19 @@ class Item implements ItemIds, \JsonSerializable{
/** @var \SplFixedArray */
public static $list = null;
/** @var Block|null */
protected $block;
/** @var int */
protected $id;
/** @var int */
protected $meta;
/** @var string */
private $tags = "";
/** @var CompoundTag|null */
private $cachedNBT = null;
/** @var int */
public $count;
protected $durability = 0;
/** @var string */
protected $name;
public function canBeActivated(){
@ -291,7 +307,17 @@ class Item implements ItemIds, \JsonSerializable{
return -1;
}
public static function get(int $id, int $meta = 0, int $count = 1, string $tags = "") : Item{
/**
* Returns an instance of the Item with the specified id, meta, count and NBT.
*
* @param int $id
* @param int $meta
* @param int $count
* @param CompoundTag|string $tags
*
* @return Item
*/
public static function get(int $id, int $meta = 0, int $count = 1, $tags = "") : Item{
try{
$class = self::$list[$id];
if($class === null){
@ -341,6 +367,12 @@ class Item implements ItemIds, \JsonSerializable{
}
}
/**
* @param int $id
* @param int $meta
* @param int $count
* @param string $name
*/
public function __construct(int $id, int $meta = 0, int $count = 1, string $name = "Unknown"){
$this->id = $id & 0xffff;
$this->meta = $meta !== -1 ? $meta & 0xffff : -1;
@ -352,6 +384,13 @@ class Item implements ItemIds, \JsonSerializable{
}
}
/**
* Sets the Item's NBT
*
* @param CompoundTag|string $tags
*
* @return $this
*/
public function setCompoundTag($tags){
if($tags instanceof CompoundTag){
$this->setNamedTag($tags);
@ -364,16 +403,24 @@ class Item implements ItemIds, \JsonSerializable{
}
/**
* Returns the serialized NBT of the Item
* @return string
*/
public function getCompoundTag() : string{
return $this->tags;
}
/**
* Returns whether this Item has a non-empty NBT.
* @return bool
*/
public function hasCompoundTag() : bool{
return $this->tags !== "";
}
/**
* @return bool
*/
public function hasCustomBlockData() : bool{
if(!$this->hasCompoundTag()){
return false;
@ -401,6 +448,11 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
/**
* @param CompoundTag $compound
*
* @return $this
*/
public function setCustomBlockData(CompoundTag $compound){
$tags = clone $compound;
$tags->setName("BlockEntityTag");
@ -417,6 +469,9 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
/**
* @return CompoundTag|null
*/
public function getCustomBlockData(){
if(!$this->hasCompoundTag()){
return null;
@ -430,6 +485,9 @@ class Item implements ItemIds, \JsonSerializable{
return null;
}
/**
* @return bool
*/
public function hasEnchantments() : bool{
if(!$this->hasCompoundTag()){
return false;
@ -447,7 +505,7 @@ class Item implements ItemIds, \JsonSerializable{
}
/**
* @param $id
* @param int $id
*
* @return Enchantment|null
*/
@ -524,6 +582,9 @@ class Item implements ItemIds, \JsonSerializable{
return $enchantments;
}
/**
* @return bool
*/
public function hasCustomName() : bool{
if(!$this->hasCompoundTag()){
return false;
@ -540,6 +601,9 @@ class Item implements ItemIds, \JsonSerializable{
return false;
}
/**
* @return string
*/
public function getCustomName() : string{
if(!$this->hasCompoundTag()){
return "";
@ -556,6 +620,11 @@ class Item implements ItemIds, \JsonSerializable{
return "";
}
/**
* @param string $name
*
* @return $this
*/
public function setCustomName(string $name){
if($name === ""){
$this->clearCustomName();
@ -580,6 +649,9 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
/**
* @return $this
*/
public function clearCustomName(){
if(!$this->hasCompoundTag()){
return $this;
@ -598,6 +670,35 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
public function getLore() : array{
$tag = $this->getNamedTagEntry("display");
if($tag instanceof CompoundTag and isset($tag->Lore) and $tag->Lore instanceof ListTag){
$lines = [];
foreach($tag->Lore->getValue() as $line){
$lines[] = $line->getValue();
}
return $lines;
}
return [];
}
public function setLore(array $lines){
$tag = $this->getNamedTag() ?? new CompoundTag("", []);
if(!isset($tag->display)){
$tag->display = new CompoundTag("display", []);
}
$tag->display->Lore = new ListTag("Lore");
$tag->display->Lore->setTagType(NBT::TAG_String);
$count = 0;
foreach($lines as $line){
$tag->display->Lore[$count++] = new StringTag("", $line);
}
$this->setNamedTag($tag);
}
/**
* @param $name
* @return Tag|null
@ -611,6 +712,10 @@ class Item implements ItemIds, \JsonSerializable{
return null;
}
/**
* Returns a tree of Tag objects representing the Item's NBT
* @return null|CompoundTag
*/
public function getNamedTag(){
if(!$this->hasCompoundTag()){
return null;
@ -620,6 +725,12 @@ class Item implements ItemIds, \JsonSerializable{
return $this->cachedNBT = self::parseCompoundTag($this->tags);
}
/**
* Sets the Item's NBT from the supplied CompoundTag object.
* @param CompoundTag $tag
*
* @return $this
*/
public function setNamedTag(CompoundTag $tag){
if($tag->getCount() === 0){
return $this->clearNamedTag();
@ -631,37 +742,73 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
/**
* Removes the Item's NBT.
* @return Item
*/
public function clearNamedTag(){
return $this->setCompoundTag("");
}
/**
* @return int
*/
public function getCount() : int{
return $this->count;
}
/**
* @param int $count
*/
public function setCount(int $count){
$this->count = $count;
}
/**
* Returns the name of the item, or the custom name if it is set.
* @return string
*/
final public function getName() : string{
return $this->hasCustomName() ? $this->getCustomName() : $this->name;
}
/**
* @return bool
*/
final public function canBePlaced() : bool{
return $this->block !== null and $this->block->canBePlaced();
}
/**
* Returns whether an entity can eat or drink this item.
* @return bool
*/
public function canBeConsumed() : bool{
return false;
}
/**
* Returns whether this item can be consumed by the supplied Entity.
* @param Entity $entity
*
* @return bool
*/
public function canBeConsumedBy(Entity $entity) : bool{
return $this->canBeConsumed();
}
/**
* Called when the item is consumed by an Entity.
* @param Entity $entity
*/
public function onConsume(Entity $entity){
}
/**
* Returns the block corresponding to this Item.
* @return Block
*/
public function getBlock() : Block{
if($this->block instanceof Block){
return clone $this->block;
@ -670,22 +817,41 @@ class Item implements ItemIds, \JsonSerializable{
}
}
/**
* @return int
*/
final public function getId() : int{
return $this->id;
}
/**
* @return int
*/
final public function getDamage() : int{
return $this->meta;
}
/**
* @param int $meta
*/
public function setDamage(int $meta){
$this->meta = $meta !== -1 ? $meta & 0xFFFF : -1;
}
/**
* Returns whether this item can match any item with an equivalent ID with any meta value.
* Used in crafting recipes which accept multiple variants of the same item, for example crafting tables recipes.
*
* @return bool
*/
public function hasAnyDamageValue() : bool{
return $this->meta === -1;
}
/**
* Returns the highest amount of this item which will fit into one inventory slot.
* @return int
*/
public function getMaxStackSize(){
return 64;
}
@ -752,28 +918,75 @@ class Item implements ItemIds, \JsonSerializable{
return 1;
}
/**
* Called when a player uses this item on a block.
*
* @param Level $level
* @param Player $player
* @param Block $block
* @param Block $target
* @param int $face
* @param float $fx
* @param float $fy
* @param float $fz
*
* @return bool
*/
public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){
return false;
}
public final function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
return $this->id === $item->getId() and ($checkDamage === false or $this->getDamage() === $item->getDamage()) and ($checkCompound === false or $this->getCompoundTag() === $item->getCompoundTag());
}
public final function deepEquals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
if($this->equals($item, $checkDamage, $checkCompound)){
return true;
}elseif($item->hasCompoundTag() and $this->hasCompoundTag()){
return NBT::matchTree($this->getNamedTag(), $item->getNamedTag());
/**
* Compares an Item to this Item and check if they match.
*
* @param Item $item
* @param bool $checkDamage Whether to verify that the damage values match.
* @param bool $checkCompound Whether to verify that the items' NBT match.
*
* @return bool
*/
final public function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
if($this->id === $item->getId() and ($checkDamage === false or $this->getDamage() === $item->getDamage())){
if($checkCompound){
if($item->getCompoundTag() === $this->getCompoundTag()){
return true;
}elseif($this->hasCompoundTag() and $item->hasCompoundTag()){
//Serialized NBT didn't match, check the cached object tree.
return NBT::matchTree($this->getNamedTag(), $item->getNamedTag());
}
}else{
return true;
}
}
return false;
}
final public function __toString() : string{
return "Item " . $this->name . " (" . $this->id . ":" . ($this->meta === null ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : "");
/**
* @deprecated Use {@link Item#equals} instead, this method will be removed in the future.
*
* @param Item $item
* @param bool $checkDamage
* @param bool $checkCompound
*
* @return bool
*/
final public function deepEquals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
return $this->equals($item, $checkDamage, $checkCompound);
}
/**
* @return string
*/
final public function __toString() : string{
return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : "");
}
/**
* Returns an array of item stack properties that can be serialized to json.
*
* @return array
*/
final public function jsonSerialize(){
return [
"id" => $this->id,
@ -839,4 +1052,12 @@ class Item implements ItemIds, \JsonSerializable{
return $item;
}
public function __clone(){
if($this->block !== null){
$this->block = clone $this->block;
}
$this->cachedNBT = null;
}
}

View File

@ -37,10 +37,6 @@ class ItemBlock extends Item{
$this->block->setDamage($this->meta !== -1 ? $this->meta : 0);
}
public function __clone(){
$this->block = clone $this->block;
}
public function getBlock() : Block{
return $this->block;
}

View File

@ -34,21 +34,29 @@ class BaseLang{
$path = \pocketmine\PATH . "src/pocketmine/lang/locale/";
}
$files = array_filter(scandir($path), function($filename){
return substr($filename, -4) === ".ini";
});
if(is_dir($path)){
$allFiles = scandir($path);
$result = [];
if($allFiles !== false){
$files = array_filter($allFiles, function($filename){
return substr($filename, -4) === ".ini";
});
foreach($files as $file){
$strings = [];
self::loadLang($path . $file, $strings);
if(isset($strings["language.name"])){
$result[substr($file, 0, -4)] = $strings["language.name"];
$result = [];
foreach($files as $file){
$strings = [];
self::loadLang($path . $file, $strings);
if(isset($strings["language.name"])){
$result[substr($file, 0, -4)] = $strings["language.name"];
}
}
return $result;
}
}
return $result;
return [];
}
protected $langName;

View File

@ -38,7 +38,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\DoubleTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\network\protocol\ExplodePacket;
use pocketmine\network\mcpe\protocol\ExplodePacket;
use pocketmine\utils\Random;
class Explosion{
@ -201,7 +201,7 @@ class Explosion{
$pos = new Vector3($block->x, $block->y, $block->z);
for($side = 0; $side < 5; $side++){
for($side = 0; $side <= 5; $side++){
$sideBlock = $pos->getSide($side);
if(!isset($this->affectedBlocks[$index = Level::blockHash($sideBlock->x, $sideBlock->y, $sideBlock->z)]) and !isset($updateBlocks[$index])){
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->level->getBlock($sideBlock)));

View File

@ -90,14 +90,14 @@ use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\protocol\BatchPacket;
use pocketmine\network\protocol\DataPacket;
use pocketmine\network\protocol\FullChunkDataPacket;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\protocol\MoveEntityPacket;
use pocketmine\network\protocol\SetEntityMotionPacket;
use pocketmine\network\protocol\SetTimePacket;
use pocketmine\network\protocol\UpdateBlockPacket;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\FullChunkDataPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\MoveEntityPacket;
use pocketmine\network\mcpe\protocol\SetEntityMotionPacket;
use pocketmine\network\mcpe\protocol\SetTimePacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\Player;
use pocketmine\plugin\Plugin;
use pocketmine\Server;
@ -134,9 +134,6 @@ class Level implements ChunkManager, Metadatable{
/** @var Tile[] */
private $tiles = [];
private $motionToSend = [];
private $moveToSend = [];
/** @var Player[] */
private $players = [];
@ -155,6 +152,8 @@ class Level implements ChunkManager, Metadatable{
private $cacheChunks = false;
private $sendTimeTicker = 0;
/** @var Server */
private $server;
@ -191,8 +190,11 @@ class Level implements ChunkManager, Metadatable{
private $changedBlocks = [];
/** @var ReversePriorityQueue */
private $updateQueue;
private $updateQueueIndex = [];
private $scheduledBlockUpdateQueue;
private $scheduledBlockUpdateQueueIndex = [];
/** @var \SplQueue */
private $neighbourBlockUpdateQueue = [];
/** @var Player[][] */
private $chunkSendQueue = [];
@ -334,8 +336,11 @@ class Level implements ChunkManager, Metadatable{
$this->generator = Generator::getGenerator($this->provider->getGenerator());
$this->folderName = $name;
$this->updateQueue = new ReversePriorityQueue();
$this->updateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
$this->scheduledBlockUpdateQueue = new ReversePriorityQueue();
$this->scheduledBlockUpdateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
$this->neighbourBlockUpdateQueue = new \SplQueue();
$this->time = (int) $this->provider->getTime();
$this->chunkTickRadius = min($this->server->getViewDistance(), max(1, (int) $this->server->getProperty("chunk-ticking.tick-radius", 4)));
@ -417,7 +422,9 @@ class Level implements ChunkManager, Metadatable{
}
public function close(){
assert(!$this->closed, "Tried to close a level which is already closed");
if($this->closed){
throw new \InvalidStateException("Tried to close a level which is already closed");
}
if($this->getAutoSave()){
$this->save();
@ -620,7 +627,7 @@ class Level implements ChunkManager, Metadatable{
if($this->stopTime === true){
return;
}else{
$this->time += $this->tickRate;
$this->time += 1;
}
}
@ -644,20 +651,41 @@ class Level implements ChunkManager, Metadatable{
*
*/
public function doTick(int $currentTick){
if($this->closed){
throw new \InvalidStateException("Attempted to tick a Level which has been closed");
}
$this->timings->doTick->startTiming();
$this->checkTime();
if(++$this->sendTimeTicker === 200){
$this->sendTime();
$this->sendTimeTicker = 0;
}
$this->unloadChunks();
//Do block updates
$this->timings->doTickPending->startTiming();
while($this->updateQueue->count() > 0 and $this->updateQueue->current()["priority"] <= $currentTick){
$block = $this->getBlock($this->updateQueue->extract()["data"]);
unset($this->updateQueueIndex[Level::blockHash($block->x, $block->y, $block->z)]);
//Delayed updates
while($this->scheduledBlockUpdateQueue->count() > 0 and $this->scheduledBlockUpdateQueue->current()["priority"] <= $currentTick){
$block = $this->getBlock($this->scheduledBlockUpdateQueue->extract()["data"]);
unset($this->scheduledBlockUpdateQueueIndex[Level::blockHash($block->x, $block->y, $block->z)]);
$block->onUpdate(self::BLOCK_UPDATE_SCHEDULED);
}
//Normal updates
while($this->neighbourBlockUpdateQueue->count() > 0){
$index = $this->neighbourBlockUpdateQueue->dequeue();
Level::getBlockXYZ($index, $x, $y, $z);
$this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($x, $y, $z))));
if(!$ev->isCancelled()){
$ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
}
}
$this->timings->doTickPending->stopTiming();
$this->timings->entityTick->startTiming();
@ -716,35 +744,6 @@ class Level implements ChunkManager, Metadatable{
$this->checkSleep();
}
foreach($this->moveToSend as $index => $entry){
Level::getXZ($index, $chunkX, $chunkZ);
foreach($entry as $e){
$pk = new MoveEntityPacket();
$pk->eid = $e[0];
$pk->x = $e[1];
$pk->y = $e[2];
$pk->z = $e[3];
$pk->yaw = $e[4];
$pk->headYaw = $e[5];
$pk->pitch = $e[6];
$this->addChunkPacket($chunkX, $chunkZ, $pk);
}
}
$this->moveToSend = [];
foreach($this->motionToSend as $index => $entry){
Level::getXZ($index, $chunkX, $chunkZ);
foreach($entry as $entity){
$pk = new SetEntityMotionPacket();
$pk->eid = $entity[0];
$pk->motionX = $entity[1];
$pk->motionY = $entity[2];
$pk->motionZ = $entity[3];
$this->addChunkPacket($chunkX, $chunkZ, $pk);
}
}
$this->motionToSend = [];
foreach($this->chunkPackets as $index => $entries){
Level::getXZ($index, $chunkX, $chunkZ);
$chunkPlayers = $this->getChunkPlayers($chunkX, $chunkZ);
@ -992,7 +991,7 @@ class Level implements ChunkManager, Metadatable{
public function saveChunks(){
foreach($this->chunks as $chunk){
if($chunk->hasChanged()){
if($chunk->hasChanged() and $chunk->isGenerated()){
$this->provider->setChunk($chunk->getX(), $chunk->getZ(), $chunk);
$this->provider->saveChunk($chunk->getX(), $chunk->getZ());
$chunk->setChanged(false);
@ -1037,15 +1036,45 @@ class Level implements ChunkManager, Metadatable{
}
/**
* @deprecated This method will be removed in the future due to misleading/ambiguous name. Use {@link Level#scheduleDelayedBlockUpdate} instead.
*
* @param Vector3 $pos
* @param int $delay
*/
public function scheduleUpdate(Vector3 $pos, int $delay){
if(isset($this->updateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->updateQueueIndex[$index] <= $delay){
$this->scheduleDelayedBlockUpdate($pos, $delay);
}
/**
* Schedules a block update to be executed after the specified number of ticks.
* Blocks will be updated with the scheduled update type.
*
* @param Vector3 $pos
* @param int $delay
*/
public function scheduleDelayedBlockUpdate(Vector3 $pos, int $delay){
if(isset($this->scheduledBlockUpdateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->scheduledBlockUpdateQueueIndex[$index] <= $delay){
return;
}
$this->updateQueueIndex[$index] = $delay;
$this->updateQueue->insert(new Vector3((int) $pos->x, (int) $pos->y, (int) $pos->z), (int) $delay + $this->server->getTick());
$this->scheduledBlockUpdateQueueIndex[$index] = $delay;
$this->scheduledBlockUpdateQueue->insert(new Vector3((int) $pos->x, (int) $pos->y, (int) $pos->z), (int) $delay + $this->server->getTick());
}
/**
* Schedules the blocks around the specified position to be updated at the end of this tick.
* Blocks will be updated with the normal update type.
*
* @param Vector3 $pos
*/
public function scheduleNeighbourBlockUpdates(Vector3 $pos){
$pos = $pos->floor();
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x + 1, $pos->y, $pos->z));
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x - 1, $pos->y, $pos->z));
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y + 1, $pos->z));
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y - 1, $pos->z));
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y, $pos->z + 1));
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y, $pos->z - 1));
}
/**
@ -1279,17 +1308,42 @@ class Level implements ChunkManager, Metadatable{
}
public function updateBlockSkyLight(int $x, int $y, int $z){
$this->timings->doBlockSkyLightUpdates->startTiming();
//TODO
$this->timings->doBlockSkyLightUpdates->stopTiming();
}
/**
* Returns the highest light level available in the positions adjacent to the specified block coordinates.
*
* @param int $x
* @param int $y
* @param int $z
*
* @return int
*/
public function getHighestAdjacentBlockLight(int $x, int $y, int $z) : int{
return max([
$this->getBlockLightAt($x + 1, $y, $z),
$this->getBlockLightAt($x - 1, $y, $z),
$this->getBlockLightAt($x, $y + 1, $z),
$this->getBlockLightAt($x, $y - 1, $z),
$this->getBlockLightAt($x, $y, $z + 1),
$this->getBlockLightAt($x, $y, $z - 1)
]);
}
public function updateBlockLight(int $x, int $y, int $z){
$this->timings->doBlockLightUpdates->startTiming();
$lightPropagationQueue = new \SplQueue();
$lightRemovalQueue = new \SplQueue();
$visited = [];
$removalVisited = [];
$id = $this->getBlockIdAt($x, $y, $z);
$oldLevel = $this->getBlockLightAt($x, $y, $z);
$newLevel = (int) Block::$light[$this->getBlockIdAt($x, $y, $z)];
$newLevel = max(Block::$light[$id], $this->getHighestAdjacentBlockLight($x, $y, $z) - Block::$lightFilter[$id]);
if($oldLevel !== $newLevel){
$this->setBlockLightAt($x, $y, $z, $newLevel);
@ -1321,7 +1375,7 @@ class Level implements ChunkManager, Metadatable{
/** @var Vector3 $node */
$node = $lightPropagationQueue->dequeue();
$lightLevel = $this->getBlockLightAt($node->x, $node->y, $node->z) - (int) Block::$lightFilter[$this->getBlockIdAt($node->x, $node->y, $node->z)];
$lightLevel = $this->getBlockLightAt($node->x, $node->y, $node->z);
if($lightLevel >= 1){
$this->computeSpreadBlockLight($node->x - 1, $node->y, $node->z, $lightLevel, $lightPropagationQueue, $visited);
@ -1332,6 +1386,8 @@ class Level implements ChunkManager, Metadatable{
$this->computeSpreadBlockLight($node->x, $node->y, $node->z + 1, $lightLevel, $lightPropagationQueue, $visited);
}
}
$this->timings->doBlockLightUpdates->stopTiming();
}
private function computeRemoveBlockLight(int $x, int $y, int $z, int $currentLight, \SplQueue $queue, \SplQueue $spreadQueue, array &$visited, array &$spreadVisited){
@ -1358,6 +1414,7 @@ class Level implements ChunkManager, Metadatable{
private function computeSpreadBlockLight(int $x, int $y, int $z, int $currentLight, \SplQueue $queue, array &$visited){
if($y < 0) return;
$current = $this->getBlockLightAt($x, $y, $z);
$currentLight -= Block::$lightFilter[$this->getBlockIdAt($x, $y, $z)];
if($current < $currentLight){
$this->setBlockLightAt($x, $y, $z, $currentLight);
@ -1395,6 +1452,8 @@ class Level implements ChunkManager, Metadatable{
return false;
}
$this->timings->setBlock->startTiming();
if($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f, $block->getId(), $block->getDamage())){
if(!($pos instanceof Position)){
$pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z);
@ -1429,14 +1488,17 @@ class Level implements ChunkManager, Metadatable{
$entity->scheduleUpdate();
}
$ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
$this->scheduleNeighbourBlockUpdates($pos);
}
$this->updateAround($pos);
}
$this->timings->setBlock->stopTiming();
return true;
}
$this->timings->setBlock->stopTiming();
return false;
}
@ -1499,41 +1561,60 @@ class Level implements ChunkManager, Metadatable{
if($player !== null){
$ev = new BlockBreakEvent($player, $target, $item, ($player->isCreative() or $player->allowInstaBreak()));
if($player->isSurvival() and $item instanceof Item and !$target->isBreakable($item)){
if(($player->isSurvival() and $item instanceof Item and !$target->isBreakable($item)) or $player->isSpectator()){
$ev->setCancelled();
}elseif(!$player->isOp() and ($distance = $this->server->getSpawnRadius()) > -1){
}elseif(!$player->hasPermission("pocketmine.spawnprotect.bypass") and ($distance = $this->server->getSpawnRadius()) > -1){
$t = new Vector2($target->x, $target->z);
$s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z);
if(count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this
$ev->setCancelled();
}
}
if($player->isAdventure(true) and !$ev->isCancelled()){
$tag = $item->getNamedTagEntry("CanDestroy");
$canBreak = false;
if($tag instanceof ListTag){
foreach($tag as $v){
if($v instanceof StringTag){
$entry = Item::fromString($v->getValue());
if($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()){
$canBreak = true;
break;
}
}
}
}
$ev->setCancelled(!$canBreak);
}
$this->server->getPluginManager()->callEvent($ev);
if($ev->isCancelled()){
return false;
}
$breakTime = $target->getBreakTime($item);
$breakTime = ceil($target->getBreakTime($item) * 20);
if($player->isCreative() and $breakTime > 0.15){
$breakTime = 0.15;
if($player->isCreative() and $breakTime > 3){
$breakTime = 3;
}
if($player->hasEffect(Effect::SWIFTNESS)){
$breakTime *= 1 - (0.2 * ($player->getEffect(Effect::SWIFTNESS)->getAmplifier() + 1));
if($player->hasEffect(Effect::HASTE)){
$breakTime *= 1 - (0.2 * ($player->getEffect(Effect::HASTE)->getAmplifier() + 1));
}
if($player->hasEffect(Effect::MINING_FATIGUE)){
$breakTime *= 1 + (0.3 * ($player->getEffect(Effect::MINING_FATIGUE)->getAmplifier() + 1));
}
$breakTime -= 0.05; //1 tick compensation
$breakTime -= 1; //1 tick compensation
if(!$ev->getInstaBreak() and ($player->lastBreak + $breakTime) > microtime(true)){
if(!$ev->getInstaBreak() and ((ceil($player->lastBreak * 20)) + $breakTime) > ceil(microtime(true) * 20)){
return false;
}
$player->lastBreak = microtime(true);
$player->lastBreak = PHP_INT_MAX;
$drops = $ev->getDrops();
@ -1553,24 +1634,6 @@ class Level implements ChunkManager, Metadatable{
}
}
$tag = $item->getNamedTagEntry("CanDestroy");
if($tag instanceof ListTag){
$canBreak = false;
foreach($tag as $v){
if($v instanceof StringTag){
$entry = Item::fromString($v->getValue());
if($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()){
$canBreak = true;
break;
}
}
}
if(!$canBreak){
return false;
}
}
if($createParticles){
$this->addParticle(new DestroyBlockParticle($target->add(0.5, 0.5, 0.5), $target));
}
@ -1638,13 +1701,32 @@ class Level implements ChunkManager, Metadatable{
if($player !== null){
$ev = new PlayerInteractEvent($player, $item, $target, $face, $target->getId() === 0 ? PlayerInteractEvent::RIGHT_CLICK_AIR : PlayerInteractEvent::RIGHT_CLICK_BLOCK);
if(!$player->isOp() and ($distance = $this->server->getSpawnRadius()) > -1){
if(!$player->hasPermission("pocketmine.spawnprotect.bypass") and ($distance = $this->server->getSpawnRadius()) > -1){
$t = new Vector2($target->x, $target->z);
$s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z);
if(count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this
$ev->setCancelled();
}
}
if($player->isAdventure(true) and !$ev->isCancelled()){
$canPlace = false;
$tag = $item->getNamedTagEntry("CanPlaceOn");
if($tag instanceof ListTag){
foreach($tag as $v){
if($v instanceof StringTag){
$entry = Item::fromString($v->getValue());
if($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()){
$canPlace = true;
break;
}
}
}
}
$ev->setCancelled(!$canPlace);
}
$this->server->getPluginManager()->callEvent($ev);
if(!$ev->isCancelled()){
$target->onUpdate(self::BLOCK_UPDATE_TOUCH);
@ -1707,28 +1789,10 @@ class Level implements ChunkManager, Metadatable{
}
}
$tag = $item->getNamedTagEntry("CanPlaceOn");
if($tag instanceof ListTag){
$canPlace = false;
foreach($tag as $v){
if($v instanceof StringTag){
$entry = Item::fromString($v->getValue());
if($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()){
$canPlace = true;
break;
}
}
}
if(!$canPlace){
return false;
}
}
if($player !== null){
$ev = new BlockPlaceEvent($player, $hand, $block, $target, $item);
if(!$player->isOp() and ($distance = $this->server->getSpawnRadius()) > -1){
if(!$player->hasPermission("pocketmine.spawnprotect.bypass") and ($distance = $this->server->getSpawnRadius()) > -1){
$t = new Vector2($target->x, $target->z);
$s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z);
if(count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this
@ -2166,18 +2230,20 @@ class Level implements ChunkManager, Metadatable{
$oldEntities = $oldChunk !== null ? $oldChunk->getEntities() : [];
$oldTiles = $oldChunk !== null ? $oldChunk->getTiles() : [];
$this->provider->setChunk($chunkX, $chunkZ, $chunk);
$this->chunks[$index] = $chunk;
foreach($oldEntities as $entity){
$chunk->addEntity($entity);
$oldChunk->removeEntity($entity);
$entity->chunk = $chunk;
}
foreach($oldTiles as $tile){
$chunk->addTile($tile);
$oldChunk->removeTile($tile);
$tile->chunk = $chunk;
}
$this->provider->setChunk($chunkX, $chunkZ, $chunk);
$this->chunks[$index] = $chunk;
}
unset($this->chunkCache[$index]);
@ -2497,7 +2563,7 @@ class Level implements ChunkManager, Metadatable{
try{
if($chunk !== null){
if($trySave and $this->getAutoSave()){
if($trySave and $this->getAutoSave() and $chunk->isGenerated()){
$entities = 0;
foreach($chunk->getEntities() as $e){
if($e instanceof Player){
@ -2820,16 +2886,23 @@ class Level implements ChunkManager, Metadatable{
}
public function addEntityMotion(int $chunkX, int $chunkZ, int $entityId, float $x, float $y, float $z){
if(!isset($this->motionToSend[$index = Level::chunkHash($chunkX, $chunkZ)])){
$this->motionToSend[$index] = [];
}
$this->motionToSend[$index][$entityId] = [$entityId, $x, $y, $z];
$pk = new SetEntityMotionPacket();
$pk->eid = $entityId;
$pk->motionX = $x;
$pk->motionY = $y;
$pk->motionZ = $z;
$this->addChunkPacket($chunkX, $chunkZ, $pk);
}
public function addEntityMovement(int $chunkX, int $chunkZ, int $entityId, float $x, float $y, float $z, float $yaw, float $pitch, $headYaw = null){
if(!isset($this->moveToSend[$index = Level::chunkHash($chunkX, $chunkZ)])){
$this->moveToSend[$index] = [];
}
$this->moveToSend[$index][$entityId] = [$entityId, $x, $y, $z, $yaw, $headYaw === null ? $yaw : $headYaw, $pitch];
$pk = new MoveEntityPacket();
$pk->eid = $entityId;
$pk->x = $x;
$pk->y = $y;
$pk->z = $z;
$pk->yaw = $yaw;
$pk->pitch = $pitch;
$pk->headYaw = $headYaw ?? $yaw;
$this->addChunkPacket($chunkX, $chunkZ, $pk);
}
}

View File

@ -307,7 +307,7 @@ class Chunk{
* @param int $level 0-15
*/
public function setBlockSkyLight(int $x, int $y, int $z, int $level){
if($this->getSubChunk($y >> 4)->setBlockSkyLight($x, $y & 0x0f, $z, $level)){
if($this->getSubChunk($y >> 4, true)->setBlockSkyLight($x, $y & 0x0f, $z, $level)){
$this->hasChanged = true;
}
}
@ -334,7 +334,7 @@ class Chunk{
* @param int $level 0-15
*/
public function setBlockLight(int $x, int $y, int $z, int $level){
if($this->getSubChunk($y >> 4)->setBlockLight($x, $y & 0x0f, $z, $level)){
if($this->getSubChunk($y >> 4, true)->setBlockLight($x, $y & 0x0f, $z, $level)){
$this->hasChanged = true;
}
}
@ -568,6 +568,9 @@ class Chunk{
* @param Entity $entity
*/
public function addEntity(Entity $entity){
if($entity->closed){
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to a chunk");
}
$this->entities[$entity->getId()] = $entity;
if(!($entity instanceof Player) and $this->isInit){
$this->hasChanged = true;
@ -588,6 +591,9 @@ class Chunk{
* @param Tile $tile
*/
public function addTile(Tile $tile){
if($tile->closed){
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to a chunk");
}
$this->tiles[$tile->getId()] = $tile;
if(isset($this->tileList[$index = (($tile->x & 0x0f) << 12) | (($tile->z & 0x0f) << 8) | ($tile->y & 0xff)]) and $this->tileList[$index] !== $tile){
$this->tileList[$index]->close();
@ -691,9 +697,16 @@ class Chunk{
continue; //Fixes entities allocated in wrong chunks.
}
if(($entity = Entity::createEntity($nbt["id"], $level, $nbt)) instanceof Entity){
$entity->spawnToAll();
}else{
try{
$entity = Entity::createEntity($nbt["id"], $level, $nbt);
if($entity instanceof Entity){
$entity->spawnToAll();
}else{
$changed = true;
continue;
}
}catch(\Throwable $t){
$level->getServer()->getLogger()->logException($t);
$changed = true;
continue;
}

View File

@ -47,8 +47,11 @@ class SubChunk{
}
public function isEmpty() : bool{
assert(strlen($this->ids) === 4096, "Wrong length of ID array, expecting 4096 bytes, got " . strlen($this->ids));
return substr_count($this->ids, "\x00") === 4096;
return (
substr_count($this->ids, "\x00") === 4096 and
substr_count($this->skyLight, "\xff") === 2048 and
substr_count($this->blockLight, "\x00") === 2048
);
}
public function getBlockId(int $x, int $y, int $z) : int{

View File

@ -22,18 +22,18 @@
namespace pocketmine\level\format\io\leveldb;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\SubChunk;
use pocketmine\level\format\io\BaseLevelProvider;
use pocketmine\level\format\io\ChunkUtils;
use pocketmine\level\generator\Generator;
use pocketmine\level\format\SubChunk;
use pocketmine\level\generator\Flat;
use pocketmine\level\generator\Generator;
use pocketmine\level\Level;
use pocketmine\level\LevelException;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\{
ByteTag, CompoundTag, FloatTag, IntTag, LongTag, StringTag
};
use pocketmine\network\protocol\Info as ProtocolInfo;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\utils\Binary;
use pocketmine\utils\MainLogger;
@ -477,9 +477,13 @@ class LevelDB extends BaseLevelProvider{
return false;
}
public function saveChunk(int $x, int $z) : bool{
if($this->isChunkLoaded($x, $z)){
$this->writeChunk($this->getChunk($x, $z));
public function saveChunk(int $chunkX, int $chunkZ) : bool{
if($this->isChunkLoaded($chunkX, $chunkZ)){
$chunk = $this->getChunk($chunkX, $chunkZ);
if(!$chunk->isGenerated()){
throw new \InvalidStateException("Cannot save un-generated chunk");
}
$this->writeChunk($chunk);
return true;
}

View File

@ -101,7 +101,7 @@ class Anvil extends McRegion{
public function nbtDeserialize(string $data){
$nbt = new NBT(NBT::BIG_ENDIAN);
try{
$nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE);
$nbt->readCompressed($data);
$chunk = $nbt->getData();

View File

@ -19,23 +19,10 @@
*
*/
namespace pocketmine\network\protocol;
#include <rules/DataPacket.h>
namespace pocketmine\level\format\io\region;
class BatchPacket extends DataPacket{
const NETWORK_ID = Info::BATCH_PACKET;
public $payload;
public function decode(){
$this->payload = $this->getString();
}
public function encode(){
$this->reset();
$this->putString($this->payload);
}
class CorruptedRegionException extends RegionException{
}

View File

@ -30,6 +30,7 @@ use pocketmine\level\format\io\ChunkUtils;
use pocketmine\level\format\SubChunk;
use pocketmine\level\generator\Generator;
use pocketmine\level\Level;
use pocketmine\level\LevelException;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\{
ByteArrayTag, ByteTag, CompoundTag, IntArrayTag, IntTag, ListTag, LongTag, StringTag
@ -126,7 +127,7 @@ class McRegion extends BaseLevelProvider{
public function nbtDeserialize(string $data){
$nbt = new NBT(NBT::BIG_ENDIAN);
try{
$nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE);
$nbt->readCompressed($data);
$chunk = $nbt->getData();
@ -219,12 +220,9 @@ class McRegion extends BaseLevelProvider{
$isValid = (file_exists($path . "/level.dat") and is_dir($path . "/region/"));
if($isValid){
$files = glob($path . "/region/*.mc*");
if(empty($files)){ //possible glob() issue on some systems
$files = array_filter(scandir($path . "/region/"), function($file){
return substr($file, strrpos($file, ".") + 1, 2) === "mc"; //region file
});
}
$files = array_filter(scandir($path . "/region/"), function($file){
return substr($file, strrpos($file, ".") + 1, 2) === "mc"; //region file
});
foreach($files as $f){
if(substr($f, strrpos($f, ".") + 1) !== static::REGION_FILE_EXTENSION){
@ -309,7 +307,11 @@ class McRegion extends BaseLevelProvider{
public function saveChunk(int $chunkX, int $chunkZ) : bool{
if($this->isChunkLoaded($chunkX, $chunkZ)){
$this->getRegion($chunkX >> 5, $chunkZ >> 5)->writeChunk($this->getChunk($chunkX, $chunkZ));
$chunk = $this->getChunk($chunkX, $chunkZ);
if(!$chunk->isGenerated()){
throw new \InvalidStateException("Cannot save un-generated chunk");
}
$this->getRegion($chunkX >> 5, $chunkZ >> 5)->writeChunk($chunk);
return true;
}
@ -438,6 +440,22 @@ class McRegion extends BaseLevelProvider{
protected function loadRegion(int $x, int $z){
if(!isset($this->regions[$index = Level::chunkHash($x, $z)])){
$this->regions[$index] = new RegionLoader($this, $x, $z, static::REGION_FILE_EXTENSION);
try{
$this->regions[$index]->open();
}catch(CorruptedRegionException $e){
$logger = $this->level->getServer()->getLogger();
$logger->error("Corrupted region file detected: " . $e->getMessage());
$this->regions[$index]->close(false); //Do not write anything to the file
$path = $this->regions[$index]->getFilePath();
$backupPath = $path . ".bak." . time();
rename($path, $backupPath);
$logger->error("Corrupted region file has been backed up to " . $backupPath);
$this->regions[$index] = new RegionLoader($this, $x, $z, static::REGION_FILE_EXTENSION);
$this->regions[$index]->open(); //this will create a new empty region to replace the corrupted one
}
}
}

View File

@ -104,7 +104,7 @@ class PMAnvil extends Anvil{
public function nbtDeserialize(string $data){
$nbt = new NBT(NBT::BIG_ENDIAN);
try{
$nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE);
$nbt->readCompressed($data);
$chunk = $nbt->getData();

View File

@ -19,8 +19,10 @@
*
*/
namespace pocketmine\entity;
class InstantEffect extends Effect{
namespace pocketmine\level\format\io\region;
class RegionException extends \RuntimeException{
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\level\format\io\region;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\io\ChunkException;
use pocketmine\level\LevelException;
use pocketmine\utils\Binary;
use pocketmine\utils\MainLogger;
@ -32,7 +33,11 @@ class RegionLoader{
const VERSION = 1;
const COMPRESSION_GZIP = 1;
const COMPRESSION_ZLIB = 2;
const MAX_SECTOR_LENGTH = 256 << 12; //256 sectors, (1 MiB)
const REGION_HEADER_LENGTH = 8192; //4096 location table + 4096 timestamps
const MAX_REGION_FILE_SIZE = 32 * 32 * self::MAX_SECTOR_LENGTH + self::REGION_HEADER_LENGTH; //32 * 32 1MiB chunks + header size
public static $COMPRESSION_LEVEL = 7;
protected $x;
@ -51,10 +56,21 @@ class RegionLoader{
$this->z = $regionZ;
$this->levelProvider = $level;
$this->filePath = $this->levelProvider->getPath() . "region/r.$regionX.$regionZ.$fileExtension";
}
public function open(){
$exists = file_exists($this->filePath);
if(!$exists){
touch($this->filePath);
}else{
$fileSize = filesize($this->filePath);
if($fileSize > self::MAX_REGION_FILE_SIZE){
throw new CorruptedRegionException("Corrupted oversized region file found, should be a maximum of " . self::MAX_REGION_FILE_SIZE . " bytes, got " . $fileSize . " bytes");
}elseif($fileSize % 4096 !== 0){
throw new CorruptedRegionException("Region file should be padded to a multiple of 4KiB");
}
}
$this->filePointer = fopen($this->filePath, "r+b");
stream_set_read_buffer($this->filePointer, 1024 * 16); //16KB
stream_set_write_buffer($this->filePointer, 1024 * 16); //16KB
@ -170,9 +186,20 @@ class RegionLoader{
return $x + ($z << 5);
}
public function close(){
$this->writeLocationTable();
fclose($this->filePointer);
/**
* Writes the region header and closes the file
*
* @param bool $writeHeader
*/
public function close(bool $writeHeader = true){
if(is_resource($this->filePointer)){
if($writeHeader){
$this->writeLocationTable();
}
fclose($this->filePointer);
}
$this->levelProvider = null;
}
@ -255,14 +282,34 @@ class RegionLoader{
fseek($this->filePointer, 0);
$this->lastSector = 1;
$data = unpack("N*", fread($this->filePointer, 4 * 1024 * 2)); //1024 records * 4 bytes * 2 times
$headerRaw = fread($this->filePointer, self::REGION_HEADER_LENGTH);
if(($len = strlen($headerRaw)) !== self::REGION_HEADER_LENGTH){
throw new CorruptedRegionException("Invalid region file header, expected " . self::REGION_HEADER_LENGTH . " bytes, got " . $len . " bytes");
}
$data = unpack("N*", $headerRaw);
$usedOffsets = [];
for($i = 0; $i < 1024; ++$i){
$index = $data[$i + 1];
$offset = $index >> 8;
if($offset !== 0){
fseek($this->filePointer, ($offset << 12));
if(fgetc($this->filePointer) === false){ //Try and read from the location
throw new CorruptedRegionException("Region file location offset points to invalid location");
}elseif(isset($usedOffsets[$offset])){
throw new CorruptedRegionException("Found two chunk offsets pointing to the same location");
}else{
$usedOffsets[$offset] = true;
}
}
$this->locationTable[$i] = [$index >> 8, $index & 0xff, $data[1024 + $i + 1]];
if(($this->locationTable[$i][0] + $this->locationTable[$i][1] - 1) > $this->lastSector){
$this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1] - 1;
}
}
fseek($this->filePointer, 0);
}
private function writeLocationTable(){
@ -300,4 +347,7 @@ class RegionLoader{
return $this->z;
}
public function getFilePath() : string{
return $this->filePath;
}
}

View File

@ -233,17 +233,17 @@ abstract class Generator{
return $noiseArray;
}
public abstract function __construct(array $settings = []);
abstract public function __construct(array $settings = []);
public abstract function init(ChunkManager $level, Random $random);
abstract public function init(ChunkManager $level, Random $random);
public abstract function generateChunk($chunkX, $chunkZ);
abstract public function generateChunk($chunkX, $chunkZ);
public abstract function populateChunk($chunkX, $chunkZ);
abstract public function populateChunk($chunkX, $chunkZ);
public abstract function getSettings();
abstract public function getSettings();
public abstract function getName();
abstract public function getName();
public abstract function getSpawn();
abstract public function getSpawn();
}

View File

@ -137,7 +137,7 @@ abstract class Biome{
return $this->id;
}
public abstract function getName();
abstract public function getName();
public function getMinElevation(){
return $this->minElevation;

View File

@ -22,6 +22,7 @@
namespace pocketmine\level\generator\object;
use pocketmine\level\ChunkManager;
use pocketmine\utils\Random;
class BigTree extends Tree{
private $trunkHeightMultiplier = 0.618;
@ -38,13 +39,13 @@ class BigTree extends Tree{
private $addLogVines = false;
private $addCocoaPlants = false;
public function canPlaceObject(ChunkManager $level, $x, $y, $z){
public function canPlaceObject(ChunkManager $level, $x, $y, $z, Random $random){
return false;
}
public function placeObject(ChunkManager $level, $x, $y, $z, $type){
public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){
$this->trunkHeight = (int) ($this->totalHeight * $this->trunkHeightMultiplier);
/*$this->trunkHeight = (int) ($this->totalHeight * $this->trunkHeightMultiplier);
$leaves = $this->getLeafGroupPoints($level, $pos);
foreach($leaves as $leafGroup){
$groupX = $leafGroup->getBlockX();
@ -54,7 +55,7 @@ class BigTree extends Tree{
$this->generateGroupLayer($level, $groupX, $yy, $groupZ, $this->getLeafGroupLayerSize($yy - $groupY));
}
}
/*final BlockIterator trunk = new BlockIterator(new Point(w, x, y - 1, z), new Point(w, x, y + trunkHeight, z));
final BlockIterator trunk = new BlockIterator(new Point(w, x, y - 1, z), new Point(w, x, y + trunkHeight, z));
while (trunk.hasNext()) {
trunk.next().setMaterial(VanillaMaterials.LOG, logMetadata);
}
@ -69,13 +70,13 @@ class BigTree extends Tree{
for($xx = -$xzRadius; $xx < ($xzRadius + 1); ++$xx){
for($zz = -$xzRadius; $zz < ($xzRadius + 1); ++$zz){
if((abs($xx) != $xzRadius or abs($zz) != $xzRadius) and $yRadius != 0){
$level->setBlock($pos->x + $xx, $pos->y + $yy, $pos->z + $zz, 18, $type);
$level->setBlock($pos->x + $xx, $pos->y + $yy, $pos->z + $zz, 18, $this->type);
}
}
}
}
for($yy = 0; $yy < ($this->totalHeight - 1); ++$yy){
$level->setBlock($x, $pos->y + $yy, $z, 17, $type);
$level->setBlock($x, $pos->y + $yy, $z, 17, $this->type);
}
*/
}

View File

@ -36,6 +36,7 @@ class Pond{
}
public function canPlaceObject(ChunkManager $level, Vector3 $pos){
return false;
}
public function placeObject(ChunkManager $level, Vector3 $pos){

View File

@ -23,6 +23,7 @@ namespace pocketmine\level\generator\populator;
use pocketmine\block\Water;
use pocketmine\level\ChunkManager;
use pocketmine\math\Vector3;
use pocketmine\utils\Random;
class Pond extends Populator{
@ -36,8 +37,8 @@ class Pond extends Populator{
$y = $random->nextBoundedInt(128);
$z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 16);
$pond = new \pocketmine\level\generator\object\Pond($random, new Water());
if($pond->canPlaceObject($level, $x, $y, $z)){
$pond->placeObject($level, $x, $y, $z);
if($pond->canPlaceObject($level, $v = new Vector3($x, $y, $z))){
$pond->placeObject($level, $v);
}
}
}

View File

@ -28,5 +28,5 @@ use pocketmine\level\ChunkManager;
use pocketmine\utils\Random;
abstract class Populator{
public abstract function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random);
abstract public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random);
}

View File

@ -23,7 +23,7 @@ namespace pocketmine\level\particle;
use pocketmine\block\Block;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class DestroyBlockParticle extends Particle{

View File

@ -24,8 +24,8 @@ namespace pocketmine\level\particle;
use pocketmine\entity\Entity;
use pocketmine\entity\Item as ItemEntity;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\AddEntityPacket;
use pocketmine\network\protocol\RemoveEntityPacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
class FloatingTextParticle extends Particle{
//TODO: HACK!

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\particle;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class GenericParticle extends Particle{

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\particle;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class MobSpawnParticle extends Particle{

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\particle;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
abstract class Particle extends Vector3{

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class AnvilBreakSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class AnvilFallSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class AnvilUseSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class BlazeShootSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class ClickSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class DoorBumpSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class DoorCrashSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class DoorSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class EndermanTeleportSound extends GenericSound{
public function __construct(Vector3 $pos){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class FizzSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class GenericSound extends Sound{

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class GhastShootSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class GhastSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

View File

@ -22,7 +22,7 @@
namespace pocketmine\level\sound;
use pocketmine\math\Vector3;
use pocketmine\network\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class LaunchSound extends GenericSound{
public function __construct(Vector3 $pos, $pitch = 0){

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