Compare commits

...

228 Commits

Author SHA1 Message Date
6e5759b1d1 Made incompatible client disconnect messages more informative 2017-09-25 14:30:11 +01:00
03d3e595d6 Implement JWT signature verification and Xbox Live checks, close #315
This can be enabled or disabled using the "online-mode" directive in
server.properties.

NOTE: For safety reasons it is enabled by default, since many naive server owners currently believe that authentication is not needed because "the client is forced to sign-in".
Newsflash for readers: the forced authentication is easily bypassed using a LAN proxy.

Un-authenticated LAN connections will still work fine if the online mode is disabled.

Added the following API methods:
- Server->getOnlineMode() : bool
- Server->requiresAuthentication() : bool
- Player->isAuthenticated() : bool

JWT verification is rather expensive, so it is done in an AsyncTask. Make sure you don't hog your worker threads.
2017-09-25 12:30:58 +01:00
8ca59d12e9 Updated PocketMine-Language submodule 2017-09-25 11:36:39 +01:00
89e4defa29 use null coalesce in Server->getLevel() 2017-09-25 09:51:21 +01:00
f5534a9ab0 Server: add some typehints 2017-09-25 09:49:19 +01:00
28bce8d48c Fixed plugins causing crashes by using Level->unload() and add a warning
yes, we don't want you to use this, but it still shouldn't crash if it can be prevented...
2017-09-25 09:46:19 +01:00
3c02a6a8ed Fixed eating sounds 2017-09-24 21:18:08 +01:00
6b0ac8adb8 Don't overwrite the input map with reindexed stuff
we might need this again?
2017-09-24 19:17:00 +01:00
38ec5da260 Refactored API compatibility checking code into its own method (#1394)
Refactored API compatibility checking code into its own method so plugins can use it
this change was inspired by https://github.com/poggit/devirion/blob/master/src/poggit/virion/devirion/DEVirion.php#L140-L172
2017-09-24 15:00:08 +01:00
240cc3043a Rewritten crafting, fixed #45 2017-09-24 14:14:24 +01:00
043ae487de Fixed some inconsistent uses of new ShortTag vs setValue() in Furnace 2017-09-24 10:34:25 +01:00
f12701e582 Fixed possible undefined NBT in Furnace, close #1398 2017-09-24 10:29:36 +01:00
6e961ae897 Addition of Coarse Dirt (#1396) 2017-09-23 22:25:03 +01:00
e1d10f595a AdventureSettingsPacket: removed leftover TODO comment 2017-09-23 16:51:58 +01:00
178dd1b981 Fixed block picking, added PlayerBlockPickEvent 2017-09-23 14:42:28 +01:00
826ec90856 Revert "Workaround for some plugins crashing clients during PlayerLoginEvent"
This reverts commit 087a994393.
2017-09-23 13:23:19 +01:00
0523f26613 Send forced movement when getting bad movements after teleport, fixes AlwaysSpawn-type plugins
it's mostly harmless to send it here anyway
2017-09-23 12:49:46 +01:00
5190d9c1e2 Fixed possible issue with JWT decoding
this is url-encoded, these characters should be replaced before base64_decode()ing. Not sure how this didn't get noticed before now.
2017-09-22 19:52:08 +01:00
c8fd0eaf8b Removed autogenerated stub TODO from BoneBlock 2017-09-21 19:50:03 +01:00
53ef9b653a Added some getters to FloatingTextParticle 2017-09-21 19:10:30 +01:00
030cc4afb0 FloatingTextParticle: import cleanup & typehints 2017-09-21 19:09:33 +01:00
9bd7f771d3 "Fixed" FloatingTextParticle yet again
- nametag visibility flags don't work properly, only players show them all the time
- invisibility overrides nametag visibility
- scale 0 triggers asserts on debug builds

..... how hard is it to NOT break these simple things each update Mojang?

closes #1205
2017-09-21 19:04:45 +01:00
10f6a0eef0 FloatingTextParticle: fixed incorrect parameter type doc 2017-09-21 18:31:12 +01:00
d0a96f35da ./ hack is back (/help is client-side >_<) 2017-09-21 17:51:39 +01:00
65e908a403 Move Travis Test server files into a separate directory
this is for convenience locally running Travis Test so my existing data doesn't interfere
2017-09-21 17:16:27 +01:00
d7091f4460 Fixed not being able to disable stats reporting using command line options 2017-09-21 17:14:44 +01:00
c6670b2e74 Fixed Nether Wart's name 2017-09-21 16:56:41 +01:00
194278d986 Updated TesterPlugin submodule 2017-09-21 16:56:27 +01:00
0e2e9aab2e Fixed crash when block classes override the constructor but don't specify a fallback name 2017-09-21 16:43:33 +01:00
1b5fed983b Revert "Fixed slab placement, close #145", reopen #145, close #1314
This reverts commit f2ff0198cc.
2017-09-21 14:41:18 +01:00
5aba87b250 Added brown and red mushroom blocks 2017-09-21 14:07:51 +01:00
f01ce8e994 null and void typehints 2017-09-21 12:54:04 +01:00
d89b8cf12e Clean up SlotChangeAction inventory handling 2017-09-21 12:44:03 +01:00
6aa9b081e9 Cleanup unused imports 2017-09-21 12:26:41 +01:00
dbed80386a Removed redundant interface 2017-09-21 12:22:47 +01:00
cefad0444c Merge branch 'master' into mcpe-1.2 2017-09-21 10:32:35 +01:00
ee052f91d4 Fixed some air items with count 1 instead of 0 2017-09-21 10:30:14 +01:00
ef6250967f Use Item->isNull() more 2017-09-21 10:29:29 +01:00
61cfdac6a1 Fixed a mistake in entity attack handler 2017-09-21 10:18:52 +01:00
fd7fb10223 Return null on unmatched inventory action and log details 2017-09-20 18:38:14 +01:00
6897cb4774 Moved inventory action magic slot constants where they belong 2017-09-20 18:27:29 +01:00
8e7ad532f1 Updated RakLib submodule 2017-09-20 18:16:41 +01:00
9e8366725a Bump for 1.2.0.81 2017-09-20 17:30:27 +01:00
b14ecc18c4 Remove unused imports 2017-09-20 12:24:44 +01:00
55720d9f0a Added InventoryAction->onPreExecute(), fixed PlayerDropItemEvent deleting items 2017-09-20 12:19:42 +01:00
0262465a26 Fixed dupe cake glitch
this is what happens when you try to be clever when not properly awake
2017-09-20 11:19:15 +01:00
7996a7b08c Testing handling multiple result items for ShapedRecipes
this doesn't work yet, I wanted to see how glitchy it is with cakes. The answer is: very glitchy.
2017-09-20 11:14:09 +01:00
4a1fc1bdf7 don't try to send contents during inventory construction 2017-09-20 10:18:24 +01:00
85b2b2ae2e Don't send tile inventory slots during the constructor
This is completely pointless and a waste of time.
2017-09-20 10:15:28 +01:00
38e11aae5e Some cleanup to how EntityInventoryChangeEvents are handled 2017-09-20 10:13:05 +01:00
f0755d1659 Fixed handling of recipes that require a crafting table 2017-09-20 09:43:49 +01:00
fd33a65e3b Small cleanup of recipe UUID handling (furnace recipes don't need UUIDs) 2017-09-20 09:34:00 +01:00
7baadf9dad Throw updated pthreads at Travis 2017-09-19 20:49:41 +01:00
ca23864e4c CraftingManager: use null coalesce for matching furnace recipes 2017-09-19 20:03:21 +01:00
8728547a11 Remove unused imports 2017-09-19 19:58:53 +01:00
90fb3c5e12 Moved getNetworkType() to ContainerInventory since it's not used anywhere else 2017-09-19 19:57:22 +01:00
1fb6d12a6b Add getInventory() to Container interface where it's actually useful 2017-09-19 19:26:41 +01:00
1323d89139 Remove redundant duplicated code for sendContents() and sendSlot() 2017-09-19 19:07:12 +01:00
136ab1dba1 Inventory->getItem(): Removed useless clones
this already returns a copy of the item anyway... wtf?
2017-09-19 18:49:08 +01:00
8cae20e818 Removed hotbar slot linking (works like PC now) 2017-09-19 18:36:57 +01:00
ff2b3bfa2a SimpleCommandMap: remove some dupe and arrange commands alphabetically 2017-09-18 18:43:06 +01:00
361b262d3a Merge branch 'master' into mcpe-1.2 2017-09-18 10:29:38 +01:00
1fd7f441b4 Travis: use older version of pthreads
master is broken - https://github.com/krakjoe/pthreads/issues/757
2017-09-18 10:20:15 +01:00
3f56d6ddc8 RakLibInterface: removed useless needACK condition 2017-09-18 09:42:25 +01:00
1e4cbb0dd9 RakLibInterface: move array initialization to default value
doesn't make sense to do this in the ctor when all the others are normal
2017-09-18 09:34:00 +01:00
a99eee9def Removed redundant assignment 2017-09-17 20:01:11 +01:00
bdee746e46 Automatically enable ANSI colours on Windows versions that support it
Note that stream_isatty() and sapi_windows_vt100_support() are ONLY defined on PHP 7.2, and the latter is only available on Windows.
2017-09-17 19:57:20 +01:00
642c7733cd Cleaned up ShapedRecipe handling, ShapedRecipe API changes
use shapes from json instead of just generating maps
fix a ton of bugs
2017-09-17 11:45:16 +01:00
c8199e14ad Removed redundant duplicate method call 2017-09-16 23:09:14 +01:00
0f37bc35ba Always evacuate the crafting grid on close, no matter whether it's big or not
otherwise items will get deleted and people will cry
2017-09-16 21:58:10 +01:00
8dc3d019f6 Return handled on fake window close 2017-09-16 21:55:25 +01:00
bd64172750 Added API method Item->equalsExact() and removed some boilerplate code 2017-09-15 16:48:46 +01:00
0e51820dfb Merge remote-tracking branch 'origin/master' into mcpe-1.2 2017-09-15 15:54:30 +01:00
30d2318bb7 Merge pull request #1383 from pmmp/quoted-command-args
Quoted command args & allow playernames with spaces
2017-09-15 14:04:48 +01:00
63634d7e7d Added compaction and sorting for repeated slot changes in a single transaction
Now items should be able to move around the crafting grid correctly.
2017-09-15 13:32:17 +01:00
d941bf8e74 Add vanilla-style crafting grid item evacuation server-side when closing the window in case something goes wrong 2017-09-15 13:22:53 +01:00
8c9d9626ab Merge branch 'new-pack-codes' 2017-09-14 19:53:07 +01:00
6b34c47c96 Merge branch 'master' into mcpe-1.2 2017-09-14 18:16:45 +01:00
77241e14ce Bumped to ALPHA8 to account for AsyncTask API changes 2017-09-14 17:49:12 +01:00
15b08c1417 Added capability to dump AsyncWorkers' memory (#1379)
This now actually works with PHP 7.2 + latest pthreads, before it was too unstable.
2017-09-14 16:45:48 +01:00
4d1daecd91 oops! 2017-09-14 11:01:47 +01:00
53e5db5142 Updated PreProcessor submodule 2017-09-14 10:58:46 +01:00
ad72fe6232 Make use of awesome new pack() codes for floats 2017-09-14 10:41:53 +01:00
8b33f711d0 Allow spaces in player names 2017-09-14 10:15:35 +01:00
319735db3a Add support for quoting command arguments
Un-escape quotes in inputted strings
2017-09-14 10:15:30 +01:00
c283d87494 Some minor cleanup of PocketMine.php 2017-09-13 19:14:31 +01:00
be27e03126 Some minor AutoUpdater cleanup, stop hardcoding everything 2017-09-13 18:51:06 +01:00
c1c290cd39 Beware matching items that aren't actually correct
This would only ever happen if we received the actions in the wrong order, but that wouldn't surprise me.
2017-09-13 11:37:10 +01:00
5267c571e9 add handling for -100 fake source type (evacuate crafting table contents) 2017-09-13 11:15:31 +01:00
0fac3b9a9d Added encode for InventoryTransactionPacket and refactor some stuff 2017-09-13 11:14:04 +01:00
23a38400e2 Added CraftingGrid and BigCraftingGrid, WIP stuff for crafting
moving whole stacks in & out of the crafting grid works now, splitting stacks is fucked up because the transaction system can't handle the same slot changing multiple times in one transaction
2017-09-12 19:34:06 +01:00
297172d111 Send creative inventory for all gamemodes, fixed recipe book 2017-09-12 14:40:16 +01:00
825d4f9702 Location cleanup (#1380)
There's no sense rewriting code that the parent constructor already implements.
2017-09-12 12:18:35 +01:00
1d31958ce6 Updated preprocessor submodule 2017-09-12 09:12:38 +01:00
130a60f2b2 Fixed ItemFactory::isRegistered() returns false for blocks 2017-09-11 18:23:26 +01:00
07268e4b37 Added API methods to determine if a block or item is already registered 2017-09-11 16:22:55 +01:00
441efc4ae2 Merge branch 'master' into mcpe-1.2 2017-09-11 14:40:25 +01:00
88bd7713c5 Fix preprocessor 2017-09-11 09:42:31 +01:00
aaa3b6e59a Added explicit AsyncTask->storeLocal(), removed AsyncTask->__construct() object storage (#1322)
Far too often I see people using IDEs which generate the constructors for them and then accidentally unintentionally store things in the object store. This parent constructor behaviour is unexpected. If a developer wants to store something, they should now do so explicitly by calling storeLocal().
2017-09-10 20:31:28 +01:00
25adac8859 Added support for Composer (#323) 2017-09-10 19:23:34 +01:00
8d0b881762 fixed command arg types 2017-09-10 13:56:34 +01:00
16cb75ef38 Merge branch 'master' into mcpe-1.2 2017-09-09 21:58:30 +01:00
3b9689674d Merge remote-tracking branch 'origin/php/7.0' 2017-09-09 21:57:44 +01:00
7f5d8cc900 Always log stack traces regardless of whether log-debug is enabled 2017-09-09 19:27:26 +01:00
8761256246 Be more clear about WHY not to use source installs in production 2017-09-09 19:21:32 +01:00
8c363cb571 Added capability to specify arguments to PocketMine.php when running start.ps1
Example: .\start.ps1 --disable-ansi --debug.level=2
2017-09-09 18:44:18 +01:00
10b765e17a Merge branch 'php/7.0' into mcpe-1.2 2017-09-09 18:08:47 +01:00
0eb866bf25 Updated AvailableCommandsPacket 2017-09-09 14:23:19 +01:00
c46caa38e1 merge 2017-09-09 11:33:00 +01:00
17d949f476 Fixed SPL being reported as incompatible when it's actually not found 2017-09-09 11:25:59 +01:00
c569f55933 Fixed can't find sources when PocketMine.php is run from anywhere other than the repository root 2017-09-09 11:22:56 +01:00
01d8d216ca Yet another merge commit 2017-09-09 00:53:03 +01:00
f1ccee505b Submodule update (this is getting annoying) 2017-09-09 00:51:20 +01:00
a61adb5991 Merge branch 'php/7.0' 2017-09-08 21:40:25 +01:00
cae1a3bb4b Updated DevTools submodule 2017-09-08 21:38:14 +01:00
6681bd250a Merge branch 'php/7.0' 2017-09-08 20:37:09 +01:00
38293913ee Updated DevTools submodule 2017-09-08 20:36:32 +01:00
8493ce8a35 Merge branch 'php/7.0' 2017-09-07 20:07:16 +01:00
9b7868238c Improved Travis Test, capture error output from console, test phar 2017-09-07 20:03:58 +01:00
953c1ef4ec Fixed formatting issues in Travis test script 2017-09-07 20:03:54 +01:00
021a9a4820 Merge branch 'php/7.0' 2017-09-07 19:33:01 +01:00
5b7565664c Removed WeakRef from Travis CI 2017-09-07 19:32:04 +01:00
ebdfbe6bb9 Removed flight controls hack for spectator mode
THEY FINALLY FIXED IT
2017-09-07 19:06:36 +01:00
85ff236461 Fixed formatting issues in Travis test script 2017-09-07 17:27:07 +01:00
d7422d9283 Updated for 1.2.0.31 beta 2017-09-07 10:50:53 +01:00
fcb3c4820e Merge branch 'php/7.0' into mcpe-1.2 2017-09-07 10:42:20 +01:00
c72ef605b9 Fixed server crash when a garbage timezone value is set in php.ini, fallback to auto-detection 2017-09-07 10:40:32 +01:00
e274f1b7f8 Merge branch 'php/7.0' 2017-09-06 17:54:50 +01:00
69514c5763 Submodule update: Fixes little-endian longs being written in the wrong order, closes #1358 2017-09-06 17:54:38 +01:00
12c154badf Merge branch 'php/7.0' 2017-09-05 20:06:02 +01:00
c9ee206fe6 Merge branch 'php/7.0' 2017-09-04 19:42:03 +01:00
6877ac35eb Merge branch 'php/7.0' 2017-09-04 10:01:47 +01:00
78d49f8e66 Merge branch 'php/7.0' into mcpe-1.2 2017-09-03 15:02:41 +01:00
de6ebc5791 Merge branch 'php/7.0' 2017-09-03 15:00:29 +01:00
2cff5a500c Merge branch 'php/7.0' 2017-09-02 19:05:27 +01:00
f077ba4748 Merge branch 'php/7.0' into mcpe-1.2 2017-09-02 19:05:18 +01:00
dcf34b7188 Merge branch 'php/7.0' 2017-09-02 18:57:49 +01:00
ca84532640 Merge branch 'php/7.0' into mcpe-1.2 2017-09-02 18:57:39 +01:00
75e32b11b7 Merge branch 'php/7.0' into mcpe-1.2 2017-09-02 18:29:53 +01:00
9f44b2ed75 fixing ClientboundMapItemDataPacket 2017-09-02 18:22:53 +01:00
1c02c747ca Merge branch 'php/7.0' 2017-09-02 13:13:54 +01:00
a6c0f1512c Send the hotbar instead of contents when resetting hotbar
This used to be fine before 1.2, but now hotbar is handled separately.
2017-09-02 11:07:14 +01:00
604d8ecf9a Protocol changes for 1.2.0.25 2017-09-02 11:05:49 +01:00
5d75d3d5b6 Merge branch 'php/7.0' into mcpe-1.2 2017-09-01 23:10:58 +01:00
8b13b520e0 Merge branch 'php/7.0' 2017-09-01 20:22:25 +01:00
00e4fff259 Fixed Item fromString() crash on PHP 7.2 2017-09-01 20:05:04 +01:00
a06c934f4d Merge branch 'php/7.0' 2017-09-01 19:37:27 +01:00
5335ed9394 Merge branch 'php/7.0' 2017-09-01 16:57:51 +01:00
16aeb0ac85 Update .travis.yml 2017-08-31 21:12:45 +01:00
8caabd3267 Check for existence of ChunkUtils extension 2017-08-31 21:04:36 +01:00
ddfe828445 Require PHP 7.2, bump PocketMine-MP version to 1.7dev 2017-08-31 20:27:05 +01:00
67ad2d25b9 Added FireImmune data flag 2017-08-30 18:36:36 +01:00
190f4dd6ab New entity metadata flags 2017-08-30 14:13:24 +01:00
6d6283b7f3 Fixed Player->sendPosition() not working correctly 2017-08-28 20:07:04 +01:00
a3d21de559 Cleaned up network inventory action reading and core action creation 2017-08-28 20:04:35 +01:00
ece0692229 Fixed UUID corruption in recipe data
this is important for MultiRecipes to work correctly (yes I know we don't use these yet!)
2017-08-28 18:04:11 +01:00
b5d2402c9b Merge branch 'master' into mcpe-1.2 2017-08-28 18:02:09 +01:00
c7fd3eb725 Merge branch 'master' into mcpe-1.2 2017-08-27 16:09:23 +01:00
5433a3f964 Merge branch 'master' into mcpe-1.2 2017-08-24 19:26:52 +01:00
2c3d7c49f9 Updated creative inventory data with new item json serialization (more compact) 2017-08-24 12:17:17 +01:00
76acb1da7b New crafting recipe data format, more readable & more compact 2017-08-24 12:05:35 +01:00
17518195d1 Be more smart about json-serializing items
Don't include nbt_hex if we don't have a NBT tag
Don't include damage unless it's non-zero
Don't include count unless it's non-1
2017-08-24 12:02:03 +01:00
2443a57234 Merge branch 'master' into mcpe-1.2 2017-08-24 11:57:41 +01:00
95752ef542 Merge branch 'master' into mcpe-1.2 2017-08-23 13:20:35 +01:00
da4c9cf404 Fixed inventory cyclic references causing players to not get garbage-collected 2017-08-23 13:13:15 +01:00
770616d4ab Merge branch 'master' into mcpe-1.2 2017-08-22 20:48:32 +01:00
445a67954d Merge changes from master 2017-08-22 14:13:31 +01:00
4250e99e3a Updated for 1.2.0.22 2017-08-22 11:35:56 +01:00
121777375e Rewired eating 2017-08-21 12:52:20 +01:00
93e149e91c Rewiring release-item action to fix bows 2017-08-20 22:14:31 +01:00
1f70a7830e Branch merge 2017-08-20 21:07:19 +01:00
159b2e3d5e Merge branch 'master' into mcpe-1.2 2017-08-19 21:42:33 +01:00
e0307411da Cleaned up PlayerList handling 2017-08-19 19:36:15 +01:00
e5e76d4c93 Merge branch 'master' into mcpe-1.2 2017-08-18 18:39:39 +01:00
2688228a6f Don't dump subchunk raw data 2017-08-18 16:57:07 +01:00
e15eefc58f ... 2017-08-18 13:58:33 +01:00
8853452feb Updated for 1.2.0.18 2017-08-18 12:36:04 +01:00
09c53552c1 Merge branch 'master' into mcpe-1.2 2017-08-18 08:29:40 +01:00
1f6d325328 Added API for assigning permanent windows, fixed teleportation breaking inventory 2017-08-17 19:43:59 +01:00
f35ca147bb Merge branch 'master' into mcpe-1.2 2017-08-17 18:38:30 +01:00
b6fb2bca13 forgot to add this to the merge 2017-08-17 17:27:49 +01:00
4f1302adf2 Merge branch 'master' into mcpe-1.2 2017-08-17 17:14:16 +01:00
643e10037c Merge branch 'master' into mcpe-1.2 2017-08-16 13:19:37 +01:00
eda2473e78 New LevelEventPacket constants 2017-08-16 12:53:53 +01:00
4b65fef957 Fixed LevelEvent broadcasting 2017-08-16 12:53:32 +01:00
fbe2567e58 Merge branch 'master' into mcpe-1.2 2017-08-16 12:31:12 +01:00
5fc50aeda5 Found an unknown field in StartGamePacket 2017-08-16 10:10:42 +01:00
9be1b929a5 Added PhpDoc for packet field types and changed float x,y,z to Vector3 2017-08-13 20:02:07 +01:00
6480f7a989 Found an unknown field in TextPacket and added some docs 2017-08-13 18:20:06 +01:00
1576a79644 more packets 2017-08-13 17:43:33 +01:00
02cbf800d0 Added encode/decode for ModalFormResponsePacket 2017-08-12 19:33:16 +01:00
5a4fbc6f5a Handle exception for crafting and resend inventories 2017-08-12 19:11:57 +01:00
83fcec3e94 Don't add actions to the transaction if a crash occurred when getting the source inventory 2017-08-12 14:29:12 +01:00
5d436a06ec Added a method to get player cursor inventory 2017-08-12 14:10:47 +01:00
8958b3c51c Many many changes related to inventory transactions, fixed item dropping, fixed creative menu 2017-08-11 19:57:30 +01:00
c1ff7bbef4 Added creative-inventory magic slot numbers and renamed some constants 2017-08-11 12:31:11 +01:00
74ee94b385 Duct tape for inventory transactions, removed ContainerSetSlotPacket 2017-08-10 20:05:15 +01:00
5208ad885c Added crafting use-ingredient fake transaction source 2017-08-10 18:42:14 +01:00
51be88c698 Fixed AdventureSettings not working 2017-08-10 13:01:20 +01:00
0dc8362536 Added custom player permission level 2017-08-10 11:34:34 +01:00
9bae4d8ef6 updates for 1.2.0.11 2017-08-10 11:15:23 +01:00
bb4808c23e attacking entities working
just moved some code :P
2017-08-10 10:49:14 +01:00
3025f76cd0 Fix a couple of Sign bugs 2017-08-10 08:57:57 +01:00
1e539c4e3b fix some 1.2 translation issues, close pmmp/PocketMine-Language#19 2017-08-09 21:40:20 +01:00
590003d7c1 Fixed PlayerListPacket 2017-08-09 19:51:39 +01:00
72d40860f3 Remove useless else branch and and return unhandled for unmatched window IDs 2017-08-09 19:05:00 +01:00
d3d1e32309 Removed teleport zero-offsets (not needed in 1.2) 2017-08-09 17:01:56 +01:00
36d47a33f3 Fixed crash on player death 2017-08-09 13:34:25 +01:00
260179197b Use SplFixedArrays in inventory, added more typehints and cleaned up some duplicated code 2017-08-09 13:12:07 +01:00
75644b5df2 s/windowid/windowId 2017-08-08 12:37:26 +01:00
3ad1b1ba7f Added some ContainerSetDataPacket constants 2017-08-08 12:19:11 +01:00
b4c2305c7f Minor cleanup of Human->initEntity() 2017-08-08 10:44:11 +01:00
1d0f0a2999 Merge branch 'master' into mcpe-1.2 2017-08-08 10:23:19 +01:00
8ca37b3813 Fixed a bug in multiline chat handling 2017-08-07 19:41:08 +01:00
2ba601b6e9 Fixed signs 2017-08-07 19:40:45 +01:00
7958fffa07 Move some code around to fix block placing, breaking, and throwing snowballs 2017-08-07 12:28:07 +01:00
98e0a2ecba Removed InventoryType, added new inventory API methods 2017-08-07 11:31:36 +01:00
899e318a88 Merge branch 'master' into mcpe-1.2 2017-08-06 18:46:56 +01:00
989505c42c Updated crafting & creative data from 1.2.0.7 2017-08-06 17:21:52 +01:00
d9da9accbc Fix packet buffers when encoding twice 2017-08-06 17:21:52 +01:00
711d62b5eb Updated block & item IDs from 1.2.0.7
Note to self: these may need updating again later in the beta.
2017-08-06 17:21:52 +01:00
49506659e0 More constants 2017-08-06 17:21:51 +01:00
7886918140 Cleaned up some bad code in DataPacket, added encode/decodeHeader and made encode/decodePayload protected 2017-08-06 17:21:51 +01:00
8a151dc373 Fixed PlayerSkinPacket for 1.2.0.7 2017-08-06 17:21:51 +01:00
58a12fdfa3 Updated for 1.2.0.7 2017-08-06 17:21:51 +01:00
50dffeb6a1 Day 3, part 1 2017-08-06 17:21:51 +01:00
63d2b341b9 Day 2 2017-08-06 17:21:51 +01:00
77cd8e7799 More broken mess to spawn 1.2 2017-08-06 17:21:51 +01:00
241 changed files with 30749 additions and 23588 deletions

2
.gitignore vendored
View File

@ -28,3 +28,5 @@ Desktop.ini
# Sphinx-doc
/docs/build/
!/docs/requirements.txt
vendor/*

View File

@ -1,12 +1,21 @@
language: php
php:
- 7.0
- 7.2
before_script:
- pecl install channel://pecl.php.net/pthreads-3.1.6
- pecl install channel://pecl.php.net/weakref-0.3.3
- echo | pecl install channel://pecl.php.net/yaml-2.0.0
# - pecl install channel://pecl.php.net/pthreads-3.1.6
- echo | pecl install channel://pecl.php.net/yaml-2.0.2
- git clone https://github.com/krakjoe/pthreads.git
- cd pthreads
- git checkout 6c6b15138c923b69cfa46ee05fc2dd45da587287
- phpize
- ./configure
- make
- make install
- cd ..
- echo "extension=pthreads.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- composer install
script:
- ./tests/travis.sh

33
composer.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "pmmp/pocketmine-mp",
"description": "A server software for Minecraft: Pocket Edition written in PHP",
"type": "project",
"homepage": "https://pmmp.io",
"license": "LGPL-3.0",
"require": {
"php": ">=7.2",
"ext-bcmath": "*",
"ext-curl": "*",
"ext-hash": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pthreads": ">=3.1.7dev",
"ext-reflection": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-yaml": ">=2.0.0",
"ext-zip": "*",
"ext-zlib": ">=1.2.11"
},
"autoload": {
"exclude-from-classmap": [
"src/spl/stubs"
],
"psr-0": {
"": ["src", "src/spl"]
}
}
}

36
composer.lock generated Normal file
View File

@ -0,0 +1,36 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "d4fecad9dce5314493052c870c8cf059",
"packages": [],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"ext-pthreads": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.2",
"ext-bcmath": "*",
"ext-curl": "*",
"ext-hash": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pthreads": ">=3.1.7dev",
"ext-reflection": "*",
"ext-sockets": "*",
"ext-spl": "*",
"ext-yaml": ">=2.0.0",
"ext-zip": "*",
"ext-zlib": ">=1.2.11"
},
"platform-dev": []
}

View File

@ -25,7 +25,9 @@ namespace pocketmine;
use pocketmine\event\server\LowMemoryEvent;
use pocketmine\event\Timings;
use pocketmine\scheduler\DumpWorkerMemoryTask;
use pocketmine\scheduler\GarbageCollectionTask;
use pocketmine\utils\MainLogger;
use pocketmine\utils\Utils;
class MemoryManager{
@ -74,6 +76,9 @@ class MemoryManager{
/** @var bool */
private $cacheTrigger;
/** @var bool */
private $dumpWorkers = true;
public function __construct(Server $server){
$this->server = $server;
@ -131,6 +136,7 @@ class MemoryManager{
$this->chunkCache = (bool) $this->server->getProperty("memory.world-caches.disable-chunk-cache", true);
$this->cacheTrigger = (bool) $this->server->getProperty("memory.world-caches.low-memory-trigger", true);
$this->dumpWorkers = (bool) $this->server->getProperty("memory.memory-dump.dump-async-worker", true);
gc_enable();
}
@ -261,6 +267,27 @@ class MemoryManager{
* @param int $maxStringSize
*/
public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize){
MainLogger::getLogger()->notice("[Dump] After the memory dump is done, the server might crash");
self::dumpMemory($this->server, $this->server->getLoader(), $outputFolder, $maxNesting, $maxStringSize);
if($this->dumpWorkers){
$scheduler = $this->server->getScheduler();
for($i = 0, $size = $scheduler->getAsyncTaskPoolSize(); $i < $size; ++$i){
$scheduler->scheduleAsyncTaskToWorker(new DumpWorkerMemoryTask($outputFolder, $maxNesting, $maxStringSize), $i);
}
}
}
/**
* Static memory dumper accessible from any thread.
*
* @param mixed $startingObject
* @param \ClassLoader $loader
* @param string $outputFolder
* @param int $maxNesting
* @param int $maxStringSize
*/
public static function dumpMemory($startingObject, \ClassLoader $loader, string $outputFolder, int $maxNesting, int $maxStringSize){
$hardLimit = ini_get('memory_limit');
ini_set('memory_limit', '-1');
gc_disable();
@ -269,12 +296,8 @@ class MemoryManager{
mkdir($outputFolder, 0777, true);
}
$this->server->getLogger()->notice("[Dump] After the memory dump is done, the server might crash");
$obData = fopen($outputFolder . "/objects.js", "wb+");
$staticProperties = [];
$data = [];
$objects = [];
@ -283,8 +306,10 @@ class MemoryManager{
$instanceCounts = [];
$staticProperties = [];
$staticCount = 0;
foreach($this->server->getLoader()->getClasses() as $className){
foreach($loader->getClasses() as $className){
$reflection = new \ReflectionClass($className);
$staticProperties[$className] = [];
foreach($reflection->getProperties() as $property){
@ -297,7 +322,7 @@ class MemoryManager{
}
$staticCount++;
$this->continueDump($property->getValue(), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
self::continueDump($property->getValue(), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($staticProperties[$className]) === 0){
@ -305,9 +330,39 @@ class MemoryManager{
}
}
echo "[Dump] Wrote $staticCount static properties\n";
file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
MainLogger::getLogger()->info("[Dump] Wrote $staticCount static properties");
$this->continueDump($this->server, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
if($GLOBALS !== null){ //This might be null if we're on a different thread
$globalVariables = [];
$globalCount = 0;
$ignoredGlobals = [
'GLOBALS' => true,
'_SERVER' => true,
'_REQUEST' => true,
'_POST' => true,
'_GET' => true,
'_FILES' => true,
'_ENV' => true,
'_COOKIE' => true,
'_SESSION' => true
];
foreach($GLOBALS as $varName => $value){
if(isset($ignoredGlobals[$varName])){
continue;
}
$globalCount++;
self::continueDump($value, $globalVariables[$varName], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
file_put_contents($outputFolder . "/globalVariables.js", json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
MainLogger::getLogger()->info("[Dump] Wrote $globalCount global variables");
}
self::continueDump($startingObject, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
do{
$continue = false;
@ -349,25 +404,26 @@ class MemoryManager{
if(!$property->isPublic()){
$property->setAccessible(true);
}
$this->continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
self::continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
fwrite($obData, "$hash@$className: " . json_encode($info, JSON_UNESCAPED_SLASHES) . "\n");
}
echo "[Dump] Wrote " . count($objects) . " objects\n";
}while($continue);
MainLogger::getLogger()->info("[Dump] Wrote " . count($objects) . " objects");
fclose($obData);
file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
file_put_contents($outputFolder . "/serverEntry.js", json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
file_put_contents($outputFolder . "/referenceCounts.js", json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
arsort($instanceCounts, SORT_NUMERIC);
file_put_contents($outputFolder . "/instanceCounts.js", json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
echo "[Dump] Finished!\n";
MainLogger::getLogger()->info("[Dump] Finished!");
ini_set('memory_limit', $hardLimit);
gc_enable();
@ -382,7 +438,7 @@ class MemoryManager{
* @param int $maxNesting
* @param int $maxStringSize
*/
private function continueDump($from, &$data, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
private static function continueDump($from, &$data, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
if($maxNesting <= 0){
$data = "(error) NESTING LIMIT REACHED";
return;
@ -406,7 +462,7 @@ class MemoryManager{
}
$data = [];
foreach($from as $key => $value){
$this->continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
self::continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
}
}elseif(is_string($from)){
$data = "(string) len(". strlen($from) .") " . substr(Utils::printable($from), 0, $maxStringSize);

File diff suppressed because it is too large Load Diff

View File

@ -70,6 +70,7 @@ namespace {
}
namespace pocketmine {
use pocketmine\utils\Binary;
use pocketmine\utils\MainLogger;
use pocketmine\utils\ServerKiller;
@ -78,9 +79,9 @@ namespace pocketmine {
use pocketmine\wizard\SetupWizard;
use raklib\RakLib;
const VERSION = "1.6.2dev";
const API_VERSION = "3.0.0-ALPHA7";
const CODENAME = "Unleashed";
const VERSION = "1.7dev";
const API_VERSION = "3.0.0-ALPHA8";
const CODENAME = "[REDACTED]";
/*
* Startup code. Do not look at it, it may harm you.
@ -89,8 +90,8 @@ namespace pocketmine {
* Enjoy it as much as I did writing it. I don't want to do it again.
*/
if(version_compare("7.0", PHP_VERSION) > 0 or version_compare("7.1", PHP_VERSION) <= 0){
echo "[CRITICAL] You must use PHP 7.0" . PHP_EOL;
if(version_compare("7.2", PHP_VERSION) > 0){
echo "[CRITICAL] You must use PHP >= 7.2" . PHP_EOL;
echo "[CRITICAL] Please use the installer provided on the homepage." . PHP_EOL;
exit(1);
}
@ -120,30 +121,40 @@ namespace pocketmine {
if(\Phar::running(true) !== ""){
define('pocketmine\PATH', \Phar::running(true) . "/");
}else{
define('pocketmine\PATH', realpath(getcwd()) . DIRECTORY_SEPARATOR);
define('pocketmine\PATH', dirname(__FILE__, 3) . DIRECTORY_SEPARATOR);
}
$requiredSplVer = "0.0.1";
if(!is_file(\pocketmine\PATH . "src/spl/version.php") or version_compare($requiredSplVer, require(\pocketmine\PATH . "src/spl/version.php")) > 0){
if(!is_file(\pocketmine\PATH . "src/spl/version.php")){
echo "[CRITICAL] Cannot find PocketMine-SPL or incompatible version." . PHP_EOL;
echo "[CRITICAL] Please update your submodules or use provided builds." . PHP_EOL;
exit(1);
}elseif(version_compare($requiredSplVer, require(\pocketmine\PATH . "src/spl/version.php")) > 0){
echo "[CRITICAL] Incompatible PocketMine-SPL submodule version ($requiredSplVer is required)." . PHP_EOL;
echo "[CRITICAL] Please update your submodules or use provided builds." . PHP_EOL;
exit(1);
}
if(is_file(\pocketmine\PATH . "vendor/autoload.php")){
require_once(\pocketmine\PATH . "vendor/autoload.php");
}else{
echo "[CRITICAL] Composer autoloader not found" . PHP_EOL;
echo "[CRITICAL] Please initialize composer dependencies before running." . PHP_EOL;
exit(1);
}
if(!class_exists("ClassLoader", false)){
if(!is_file(\pocketmine\PATH . "src/spl/ClassLoader.php")){
echo "[CRITICAL] Unable to find the PocketMine-SPL library." . PHP_EOL;
echo "[CRITICAL] Please use provided builds or clone the repository recursively." . PHP_EOL;
exit(1);
}
require_once(\pocketmine\PATH . "src/spl/ClassLoader.php");
require_once(\pocketmine\PATH . "src/spl/BaseClassLoader.php");
}
/*
* We now use the Composer autoloader, but this autoloader is still used by RakLib and for loading plugins.
*/
$autoloader = new \BaseClassLoader();
$autoloader->addPath(\pocketmine\PATH . "src");
$autoloader->addPath(\pocketmine\PATH . "src" . DIRECTORY_SEPARATOR . "spl");
$autoloader->register(true);
$autoloader->register(false);
if(!class_exists(RakLib::class)){
echo "[CRITICAL] Unable to find the RakLib library." . PHP_EOL;
@ -186,40 +197,51 @@ namespace pocketmine {
$logger = new MainLogger(\pocketmine\DATA . "server.log");
$logger->registerStatic();
if(!ini_get("date.timezone")){
do{
$timezone = ini_get("date.timezone");
if($timezone !== ""){
/*
* This is here so that people don't come to us complaining and fill up the issue tracker when they put
* an incorrect timezone abbreviation in php.ini apparently.
*/
if(strpos($timezone, "/") === false){
$default_timezone = timezone_name_from_abbr($timezone);
if($default_timezone !== false){
ini_set("date.timezone", $default_timezone);
date_default_timezone_set($default_timezone);
break;
}else{
//Bad php.ini value, try another method to detect timezone
$logger->warning("Timezone \"$timezone\" could not be parsed as a valid timezone from php.ini, falling back to auto-detection");
}
}else{
date_default_timezone_set($timezone);
break;
}
}
if(($timezone = detect_system_timezone()) and date_default_timezone_set($timezone)){
//Success! Timezone has already been set and validated in the if statement.
//This here is just for redundancy just in case some program wants to read timezone data from the ini.
ini_set("date.timezone", $timezone);
}else{
//If system timezone detection fails or timezone is an invalid value.
if($response = Utils::getURL("http://ip-api.com/json")
and $ip_geolocation_data = json_decode($response, true)
and $ip_geolocation_data['status'] !== 'fail'
and date_default_timezone_set($ip_geolocation_data['timezone'])
){
//Again, for redundancy.
ini_set("date.timezone", $ip_geolocation_data['timezone']);
}else{
ini_set("date.timezone", "UTC");
date_default_timezone_set("UTC");
$logger->warning("Timezone could not be automatically determined. An incorrect timezone will result in incorrect timestamps on console logs. It has been set to \"UTC\" by default. You can change it on the php.ini file.");
}
break;
}
}else{
/*
* This is here so that people don't come to us complaining and fill up the issue tracker when they put
* an incorrect timezone abbreviation in php.ini apparently.
*/
$timezone = ini_get("date.timezone");
if(strpos($timezone, "/") === false){
$default_timezone = timezone_name_from_abbr($timezone);
ini_set("date.timezone", $default_timezone);
date_default_timezone_set($default_timezone);
}else{
date_default_timezone_set($timezone);
if($response = Utils::getURL("http://ip-api.com/json") //If system timezone detection fails or timezone is an invalid value.
and $ip_geolocation_data = json_decode($response, true)
and $ip_geolocation_data['status'] !== 'fail'
and date_default_timezone_set($ip_geolocation_data['timezone'])
){
//Again, for redundancy.
ini_set("date.timezone", $ip_geolocation_data['timezone']);
break;
}
}
ini_set("date.timezone", "UTC");
date_default_timezone_set("UTC");
$logger->warning("Timezone could not be automatically determined or was set to an invalid value. An incorrect timezone will result in incorrect timestamps on console logs. It has been set to \"UTC\" by default. You can change it on the php.ini file.");
}while(false);
function detect_system_timezone(){
switch(Utils::getOS()){
@ -438,8 +460,8 @@ namespace pocketmine {
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
}
if(version_compare($pthreads_version, "3.1.5") < 0){
$logger->critical("pthreads >= 3.1.5 is required, while you have $pthreads_version.");
if(version_compare($pthreads_version, "3.1.7-dev") < 0){
$logger->critical("pthreads >= 3.1.7-dev is required, while you have $pthreads_version.");
++$errors;
}
@ -524,7 +546,7 @@ namespace pocketmine {
if(\Phar::running(true) === ""){
$logger->warning("Non-packaged PocketMine-MP installation detected, do not use on production.");
$logger->warning("Non-packaged PocketMine-MP installation detected. Consider using a phar in production for better performance.");
}
ThreadManager::init();
@ -536,19 +558,7 @@ namespace pocketmine {
$killer->start();
usleep(10000); //Fixes ServerKiller not being able to start on single-core machines
$erroredThreads = 0;
foreach(ThreadManager::getInstance()->getAll() as $id => $thread){
$logger->debug("Stopping " . $thread->getThreadName() . " thread");
try{
$thread->quit();
$logger->debug($thread->getThreadName() . " thread stopped successfully.");
}catch(\ThreadException $e){
++$erroredThreads;
$logger->debug("Could not stop " . $thread->getThreadName() . " thread: " . $e->getMessage());
}
}
if($erroredThreads > 0){
if(ThreadManager::getInstance()->stopAll() > 0){
if(\pocketmine\DEBUG > 1){
echo "Some threads could not be stopped, performing a force-kill" . PHP_EOL . PHP_EOL;
}

View File

@ -47,7 +47,6 @@ use pocketmine\event\Timings;
use pocketmine\event\TimingsHandler;
use pocketmine\event\TranslationContainer;
use pocketmine\inventory\CraftingManager;
use pocketmine\inventory\InventoryType;
use pocketmine\inventory\Recipe;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\ItemFactory;
@ -83,6 +82,7 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\network\mcpe\RakLibInterface;
use pocketmine\network\Network;
use pocketmine\network\query\QueryHandler;
@ -194,6 +194,9 @@ class Server{
/** @var int */
private $maxPlayers;
/** @var bool */
private $onlineMode = true;
/** @var bool */
private $autoSave;
@ -339,6 +342,24 @@ class Server{
return $this->maxPlayers;
}
/**
* Returns whether the server requires that players be authenticated to Xbox Live. If true, connecting players who
* are not logged into Xbox Live will be disconnected.
*
* @return bool
*/
public function getOnlineMode() : bool{
return $this->onlineMode;
}
/**
* Alias of {@link #getOnlineMode()}.
* @return bool
*/
public function requiresAuthentication() : bool{
return $this->getOnlineMode();
}
/**
* @return int
*/
@ -915,7 +936,7 @@ class Server{
/**
* @return Level|null
*/
public function getDefaultLevel(){
public function getDefaultLevel() : ?Level{
return $this->levelDefault;
}
@ -926,7 +947,7 @@ class Server{
*
* @param Level|null $level
*/
public function setDefaultLevel($level){
public function setDefaultLevel(?Level $level) : void{
if($level === null or ($this->isLevelLoaded($level->getFolderName()) and $level !== $this->levelDefault)){
$this->levelDefault = $level;
}
@ -946,12 +967,8 @@ class Server{
*
* @return Level|null
*/
public function getLevel(int $levelId){
if(isset($this->levels[$levelId])){
return $this->levels[$levelId];
}
return null;
public function getLevel(int $levelId) : ?Level{
return $this->levels[$levelId] ?? null;
}
/**
@ -961,7 +978,7 @@ class Server{
*
* @return Level|null
*/
public function getLevelByName(string $name){
public function getLevelByName(string $name) : ?Level{
foreach($this->getLevels() as $level){
if($level->getFolderName() === $name){
return $level;
@ -983,13 +1000,16 @@ class Server{
if($level === $this->getDefaultLevel() and !$forceUnload){
throw new \InvalidStateException("The default level cannot be unloaded while running, please switch levels.");
}
if($level->unload($forceUnload) === true){
unset($this->levels[$level->getId()]);
return true;
}
return $level->unload($forceUnload);
}
return false;
/**
* @internal
* @param Level $level
*/
public function removeLevel(Level $level) : void{
unset($this->levels[$level->getId()]);
}
/**
@ -1485,7 +1505,8 @@ class Server{
"enable-rcon" => false,
"rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10),
"auto-save" => true,
"view-distance" => 8
"view-distance" => 8,
"online-mode" => true
]);
$this->forceLanguage = $this->getProperty("settings.force-language", false);
@ -1558,6 +1579,16 @@ class Server{
$this->maxPlayers = $this->getConfigInt("max-players", 20);
$this->setAutoSave($this->getConfigBoolean("auto-save", true));
$this->onlineMode = $this->getConfigBoolean("online-mode", true);
if($this->onlineMode){
$this->logger->notice($this->getLanguage()->translateString("pocketmine.server.auth", ["enabled", "will"]));
$this->logger->notice($this->getLanguage()->translateString("pocketmine.server.authProperty", ["disable", "false"]));
}else{
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.auth", ["disabled", "will not"]));
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.authWarning"));
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.authProperty", ["enable", "true"]));
}
if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){
$this->setConfigInt("difficulty", 3);
}
@ -1585,6 +1616,7 @@ class Server{
]));
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.license", [$this->getName()]));
Timings::init();
$this->consoleSender = new ConsoleCommandSender();
@ -1592,7 +1624,6 @@ class Server{
Entity::init();
Tile::init();
InventoryType::init();
BlockFactory::init();
Enchantment::init();
ItemFactory::init();
@ -2280,32 +2311,45 @@ class Server{
if(isset($this->playerList[$player->getRawUniqueId()])){
unset($this->playerList[$player->getRawUniqueId()]);
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_REMOVE;
$pk->entries[] = [$player->getUniqueId()];
$this->broadcastPacket($this->playerList, $pk);
$this->removePlayerListData($player->getUniqueId());
}
}
public function updatePlayerListData(UUID $uuid, $entityId, $name, $skinId, $skinData, array $players = null){
/**
* @param UUID $uuid
* @param int $entityId
* @param string $name
* @param string $skinId
* @param string $skinData
* @param Player[]|null $players
*/
public function updatePlayerListData(UUID $uuid, int $entityId, string $name, string $skinId, string $skinData, array $players = null){
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_ADD;
$pk->entries[] = [$uuid, $entityId, $name, $skinId, $skinData];
$pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, $skinId, $skinData);
$this->broadcastPacket($players ?? $this->playerList, $pk);
}
/**
* @param UUID $uuid
* @param Player[]|null $players
*/
public function removePlayerListData(UUID $uuid, array $players = null){
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_REMOVE;
$pk->entries[] = [$uuid];
$pk->entries[] = PlayerListEntry::createRemovalEntry($uuid);
$this->broadcastPacket($players ?? $this->playerList, $pk);
}
/**
* @param Player $p
*/
public function sendFullPlayerListData(Player $p){
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_ADD;
foreach($this->playerList as $player){
$pk->entries[] = [$player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkinId(), $player->getSkinData()];
$pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkinId(), $player->getSkinData());
}
$p->dataPacket($pk);
@ -2375,7 +2419,7 @@ class Server{
}
public function sendUsage($type = SendUsageTask::TYPE_STATUS){
if($this->getProperty("anonymous-statistics.enabled", true)){
if((bool) $this->getProperty("anonymous-statistics.enabled", true)){
$this->scheduler->scheduleAsyncTask(new SendUsageTask($this, $type, $this->uniquePlayers));
}
$this->uniquePlayers = [];

View File

@ -51,16 +51,17 @@ abstract class Thread extends \Thread{
* (unless you are using a custom autoloader).
*/
public function registerClassLoader(){
require(\pocketmine\PATH . "vendor/autoload.php");
if(!interface_exists("ClassLoader", false)){
require(\pocketmine\PATH . "src/spl/ClassLoader.php");
require(\pocketmine\PATH . "src/spl/BaseClassLoader.php");
}
if($this->classLoader !== null){
$this->classLoader->register(true);
$this->classLoader->register(false);
}
}
public function start(int $options = PTHREADS_INHERIT_ALL){
public function start(?int $options = \PTHREADS_INHERIT_ALL){
ThreadManager::getInstance()->add($this);
if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine;
use pocketmine\utils\MainLogger;
class ThreadManager extends \Volatile{
/** @var ThreadManager */
@ -68,4 +70,23 @@ class ThreadManager extends \Volatile{
return $array;
}
public function stopAll() : int{
$logger = MainLogger::getLogger();
$erroredThreads = 0;
foreach($this->getAll() as $thread){
$logger->debug("Stopping " . $thread->getThreadName() . " thread");
try{
$thread->quit();
$logger->debug($thread->getThreadName() . " thread stopped successfully.");
}catch(\ThreadException $e){
++$erroredThreads;
$logger->debug("Could not stop " . $thread->getThreadName() . " thread: " . $e->getMessage());
}
}
return $erroredThreads;
}
}

View File

@ -52,16 +52,17 @@ abstract class Worker extends \Worker{
* (unless you are using a custom autoloader).
*/
public function registerClassLoader(){
require(\pocketmine\PATH . "vendor/autoload.php");
if(!interface_exists("ClassLoader", false)){
require(\pocketmine\PATH . "src/spl/ClassLoader.php");
require(\pocketmine\PATH . "src/spl/BaseClassLoader.php");
}
if($this->classLoader !== null){
$this->classLoader->register(true);
$this->classLoader->register(false);
}
}
public function start(int $options = PTHREADS_INHERIT_ALL){
public function start(?int $options = \PTHREADS_INHERIT_ALL){
ThreadManager::getInstance()->add($this);
if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){

View File

@ -54,7 +54,7 @@ class Air extends Transparent{
return true;
}
public function canBeReplaced(Block $with = null) : bool{
public function canBeReplaced() : bool{
return true;
}

View File

@ -61,7 +61,7 @@ class Block extends Position implements BlockIds, Metadatable{
protected $id;
/** @var int */
protected $meta = 0;
/** @var string */
/** @var string|null */
protected $fallbackName;
/** @var int|null */
protected $itemId;
@ -70,12 +70,12 @@ class Block extends Position implements BlockIds, Metadatable{
public $boundingBox = null;
/**
* @param int $id The block type's ID, 0-255
* @param int $meta Meta value of the block type
* @param string $name English name of the block type (TODO: implement translations)
* @param int $itemId The item ID of the block type, used for block picking and dropping items.
* @param int $id The block type's ID, 0-255
* @param int $meta Meta value of the block type
* @param string|null $name English name of the block type (TODO: implement translations)
* @param int $itemId The item ID of the block type, used for block picking and dropping items.
*/
public function __construct(int $id, int $meta = 0, string $name = "Unknown", int $itemId = null){
public function __construct(int $id, int $meta = 0, string $name = null, int $itemId = null){
$this->id = $id;
$this->meta = $meta;
$this->fallbackName = $name;
@ -86,7 +86,7 @@ class Block extends Position implements BlockIds, Metadatable{
* @return string
*/
public function getName() : string{
return $this->fallbackName;
return $this->fallbackName ?? "Unknown";
}
/**
@ -284,11 +284,9 @@ class Block extends Position implements BlockIds, Metadatable{
}
/**
* @param Block|null $with
*
* @return bool
*/
public function canBeReplaced(Block $with = null) : bool{
public function canBeReplaced() : bool{
return false;
}

View File

@ -168,8 +168,8 @@ class BlockFactory{
self::registerBlock(new Trapdoor());
//TODO: MONSTER_EGG
self::registerBlock(new StoneBricks());
//TODO: BROWN_MUSHROOM_BLOCK
//TODO: RED_MUSHROOM_BLOCK
self::registerBlock(new BrownMushroomBlock());
self::registerBlock(new RedMushroomBlock());
self::registerBlock(new IronBars());
self::registerBlock(new GlassPane());
self::registerBlock(new Melon());
@ -341,7 +341,7 @@ class BlockFactory{
public static function registerBlock(Block $block, bool $override = false){
$id = $block->getId();
if(self::$list[$id] !== null and !(self::$list[$id] instanceof UnknownBlock) and !$override){
if(!$override and self::isRegistered($id)){
throw new \RuntimeException("Trying to overwrite an already registered block");
}
@ -403,4 +403,15 @@ class BlockFactory{
public static function getBlockStatesArray() : \SplFixedArray{
return self::$fullList;
}
/**
* Returns whether a specified block ID is already registered in the block factory.
*
* @param int $id
* @return bool
*/
public static function isRegistered(int $id) : bool{
$b = self::$list[$id];
return $b !== null and !($b instanceof UnknownBlock);
}
}

View File

@ -109,7 +109,7 @@ interface BlockIds{
const CACTUS = 81;
const CLAY_BLOCK = 82;
const REEDS_BLOCK = 83, SUGARCANE_BLOCK = 83;
const JUKEBOX = 84;
const FENCE = 85;
const PUMPKIN = 86;
const NETHERRACK = 87;
@ -201,7 +201,8 @@ interface BlockIds{
const COAL_BLOCK = 173;
const PACKED_ICE = 174;
const DOUBLE_PLANT = 175;
const STANDING_BANNER = 176;
const WALL_BANNER = 177;
const DAYLIGHT_DETECTOR_INVERTED = 178, DAYLIGHT_SENSOR_INVERTED = 178;
const RED_SANDSTONE = 179;
const RED_SANDSTONE_STAIRS = 180;
@ -227,6 +228,7 @@ interface BlockIds{
const PURPUR_STAIRS = 203;
const UNDYED_SHULKER_BOX = 205;
const END_BRICKS = 206;
const FROSTED_ICE = 207;
const END_ROD = 208;
@ -270,6 +272,7 @@ interface BlockIds{
const INFO_UPDATE2 = 249;
const MOVINGBLOCK = 250, MOVING_BLOCK = 250;
const OBSERVER = 251;
const STRUCTURE_BLOCK = 252;
const RESERVED6 = 255;

View File

@ -60,7 +60,7 @@ class BoneBlock extends Solid{
public function getDrops(Item $item) : array{
if($item->isPickaxe() >= Tool::TIER_WOODEN){
return parent::getDrops($item); // TODO: Change the autogenerated stub
return parent::getDrops($item);
}
return [];

View File

@ -21,21 +21,21 @@
declare(strict_types=1);
namespace pocketmine\inventory;
namespace pocketmine\block;
/**
* Saves all the information regarding default inventory sizes and types
*/
interface SlotType{
const RESULT = 0;
use pocketmine\item\Item;
const CRAFTING = 1; //Not used in Minecraft: PE yet
class BrownMushroomBlock extends RedMushroomBlock{
const ARMOR = 2;
protected $id = Block::BROWN_MUSHROOM_BLOCK;
const CONTAINER = 3;
public function getName() : string{
return "Brown Mushroom Block";
}
const HOTBAR = 4;
const FUEL = 5;
public function getDrops(Item $item) : array{
return [
Item::get(Item::BROWN_MUSHROOM, 0, mt_rand(0, 2))
];
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\inventory\BigCraftingGrid;
use pocketmine\item\Item;
use pocketmine\item\Tool;
use pocketmine\Player;
@ -49,6 +50,7 @@ class CraftingTable extends Solid{
public function onActivate(Item $item, Player $player = null) : bool{
if($player instanceof Player){
$player->setCraftingGrid(new BigCraftingGrid($player));
$player->craftingType = 1;
}

View File

@ -44,13 +44,20 @@ class Dirt extends Solid{
}
public function getName() : string{
if($this->meta === 1){
return "Coarse Dirt";
}
return "Dirt";
}
public function onActivate(Item $item, Player $player = null) : bool{
if($item->isHoe()){
$item->useOn($this);
$this->getLevel()->setBlock($this, BlockFactory::get(Block::FARMLAND, 0), true);
if($this->meta === 1){
$this->getLevel()->setBlock($this, BlockFactory::get(Block::DIRT), true);
}else{
$this->getLevel()->setBlock($this, BlockFactory::get(Block::FARMLAND), true);
}
return true;
}

View File

@ -38,7 +38,7 @@ class DoublePlant extends Flowable{
$this->meta = $meta;
}
public function canBeReplaced(Block $with = null) : bool{
public function canBeReplaced() : bool{
return $this->meta === 2 or $this->meta === 3; //grass or fern
}

View File

@ -57,7 +57,7 @@ class Fire extends Flowable{
return false;
}
public function canBeReplaced(Block $with = null) : bool{
public function canBeReplaced() : bool{
return true;
}

View File

@ -83,6 +83,7 @@ class Grass extends Solid{
$vector->z = mt_rand($this->z - 1, $this->z + 1);
if(
$this->level->getBlockIdAt($vector->x, $vector->y, $vector->z) !== Block::DIRT or
$this->level->getBlockDataAt($vector->x, $vector->y, $vector->z) === 1 or
$this->level->getFullLightAt($vector->x, $vector->y + 1, $vector->z) < 4 or
BlockFactory::$lightFilter[$this->level->getBlockIdAt($vector->x, $vector->y + 1, $vector->z)] >= 3
){

View File

@ -41,7 +41,7 @@ abstract class Liquid extends Transparent{
return false;
}
public function canBeReplaced(Block $with = null) : bool{
public function canBeReplaced() : bool{
return true;
}

View File

@ -40,6 +40,10 @@ class NetherWartPlant extends Flowable{
$this->meta = $meta;
}
public function getName() : string{
return "Nether Wart";
}
public function ticksRandomly() : bool{
return true;
}

View File

@ -21,34 +21,35 @@
declare(strict_types=1);
namespace pocketmine\inventory;
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\item\Tool;
interface Transaction{
class RedMushroomBlock extends Solid{
/**
* @return Inventory
*/
public function getInventory() : Inventory;
protected $id = Block::RED_MUSHROOM_BLOCK;
/**
* @return int
*/
public function getSlot() : int;
public function __construct(int $meta = 0){
$this->meta = $meta;
}
/**
* @return Item
*/
public function getSourceItem() : Item;
public function getName() : string{
return "Red Mushroom Block";
}
/**
* @return Item
*/
public function getTargetItem() : Item;
public function getHardness() : float{
return 0.2;
}
public function getToolType() : int{
return Tool::TYPE_AXE;
}
public function getDrops(Item $item) : array{
return [
Item::get(Item::RED_MUSHROOM, 0, mt_rand(0, 2))
];
}
/**
* @return float
*/
public function getCreationTime() : float;
}

View File

@ -42,7 +42,7 @@ class SnowLayer extends Flowable{
return "Snow Layer";
}
public function canBeReplaced(Block $with = null) : bool{
public function canBeReplaced() : bool{
return true;
}

View File

@ -37,7 +37,7 @@ class TallGrass extends Flowable{
$this->meta = $meta;
}
public function canBeReplaced(Block $with = null) : bool{
public function canBeReplaced() : bool{
return true;
}

View File

@ -78,10 +78,6 @@ class WoodenSlab extends Transparent{
}
}
public function canBeReplaced(Block $with = null) : bool{
return $with !== null and $with->getId() === $this->getId() and ($with->getDamage() & 0x07) === ($this->getDamage() & 0x07);
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $facePos, Player $player = null) : bool{
$this->meta &= 0x07;
if($face === Vector3::SIDE_DOWN){

View File

@ -85,48 +85,52 @@ class SimpleCommandMap implements CommandMap{
}
private function setDefaultCommands(){
$this->register("pocketmine", new VersionCommand("version"));
$this->register("pocketmine", new PluginsCommand("plugins"));
$this->register("pocketmine", new SeedCommand("seed"));
$this->register("pocketmine", new HelpCommand("help"));
$this->register("pocketmine", new StopCommand("stop"));
$this->register("pocketmine", new TellCommand("tell"));
$this->register("pocketmine", new DefaultGamemodeCommand("defaultgamemode"));
$this->register("pocketmine", new BanCommand("ban"));
$this->register("pocketmine", new BanIpCommand("ban-ip"));
$this->register("pocketmine", new BanListCommand("banlist"));
$this->register("pocketmine", new PardonCommand("pardon"));
$this->register("pocketmine", new PardonIpCommand("pardon-ip"));
$this->register("pocketmine", new SayCommand("say"));
$this->register("pocketmine", new MeCommand("me"));
$this->register("pocketmine", new ListCommand("list"));
$this->register("pocketmine", new DifficultyCommand("difficulty"));
$this->register("pocketmine", new KickCommand("kick"));
$this->register("pocketmine", new OpCommand("op"));
$this->register("pocketmine", new DeopCommand("deop"));
$this->register("pocketmine", new WhitelistCommand("whitelist"));
$this->register("pocketmine", new SaveOnCommand("save-on"));
$this->register("pocketmine", new SaveOffCommand("save-off"));
$this->register("pocketmine", new SaveCommand("save-all"));
$this->register("pocketmine", new GiveCommand("give"));
$this->register("pocketmine", new EffectCommand("effect"));
$this->register("pocketmine", new EnchantCommand("enchant"));
$this->register("pocketmine", new ParticleCommand("particle"));
$this->register("pocketmine", new GamemodeCommand("gamemode"));
$this->register("pocketmine", new KillCommand("kill"));
$this->register("pocketmine", new SpawnpointCommand("spawnpoint"));
$this->register("pocketmine", new SetWorldSpawnCommand("setworldspawn"));
$this->register("pocketmine", new TeleportCommand("tp"));
$this->register("pocketmine", new TimeCommand("time"));
$this->register("pocketmine", new TimingsCommand("timings"));
$this->register("pocketmine", new TitleCommand("title"));
$this->register("pocketmine", new ReloadCommand("reload"));
$this->register("pocketmine", new TransferServerCommand("transferserver"));
$this->registerAll("pocketmine", [
new BanCommand("ban"),
new BanIpCommand("ban-ip"),
new BanListCommand("banlist"),
new DefaultGamemodeCommand("defaultgamemode"),
new DeopCommand("deop"),
new DifficultyCommand("difficulty"),
new EffectCommand("effect"),
new EnchantCommand("enchant"),
new GamemodeCommand("gamemode"),
new GiveCommand("give"),
new HelpCommand("help"),
new KickCommand("kick"),
new KillCommand("kill"),
new ListCommand("list"),
new MeCommand("me"),
new OpCommand("op"),
new PardonCommand("pardon"),
new PardonIpCommand("pardon-ip"),
new ParticleCommand("particle"),
new PluginsCommand("plugins"),
new ReloadCommand("reload"),
new SaveCommand("save-all"),
new SaveOffCommand("save-off"),
new SaveOnCommand("save-on"),
new SayCommand("say"),
new SeedCommand("seed"),
new SetWorldSpawnCommand("setworldspawn"),
new SpawnpointCommand("spawnpoint"),
new StopCommand("stop"),
new TeleportCommand("tp"),
new TellCommand("tell"),
new TimeCommand("time"),
new TimingsCommand("timings"),
new TitleCommand("title"),
new TransferServerCommand("transferserver"),
new VersionCommand("version"),
new WhitelistCommand("whitelist")
]);
if($this->server->getProperty("debug.commands", false)){
$this->register("pocketmine", new StatusCommand("status"));
$this->register("pocketmine", new GarbageCollectorCommand("gc"));
$this->register("pocketmine", new DumpMemoryCommand("dumpmemory"));
$this->registerAll("pocketmine", [
new StatusCommand("status"),
new GarbageCollectorCommand("gc"),
new DumpMemoryCommand("dumpmemory")
]);
}
}
@ -223,7 +227,7 @@ class SimpleCommandMap implements CommandMap{
}
public function dispatch(CommandSender $sender, string $commandLine) : bool{
$args = explode(" ", $commandLine);
$args = array_map("stripslashes", str_getcsv($commandLine, " "));
$sentCommandLabel = "";
$target = $this->matchCommand($sentCommandLabel, $args);

View File

@ -118,7 +118,7 @@ class EffectCommand extends VanillaCommand{
$effect->setDuration($duration)->setAmplifier($amplification);
$player->addEffect($effect);
self::broadcastCommandMessage($sender, new TranslationContainer("%commands.effect.success", [$effect->getName(), $effect->getId(), $effect->getAmplifier(), $player->getDisplayName(), $effect->getDuration() / 20]));
self::broadcastCommandMessage($sender, new TranslationContainer("%commands.effect.success", [$effect->getName(), $effect->getAmplifier(), $player->getDisplayName(), $effect->getDuration() / 20, $effect->getId()]));
}

View File

@ -76,10 +76,10 @@ class GamemodeCommand extends VanillaCommand{
$sender->sendMessage("Game mode change for " . $target->getName() . " failed!");
}else{
if($target === $sender){
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.self", ['blame', 'mojang', Server::getGamemodeString($gameMode)]));
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.self", [Server::getGamemodeString($gameMode)]));
}else{
$target->sendMessage(new TranslationContainer("gameMode.changed", [Server::getGamemodeString($gameMode)]));
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.other", ['blame mojang', $target->getName(), Server::getGamemodeString($gameMode)]));
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.other", [$target->getName(), Server::getGamemodeString($gameMode)]));
}
}

View File

@ -84,12 +84,9 @@ class Arrow extends Projectile{
$pk = new AddEntityPacket();
$pk->type = Arrow::NETWORK_ID;
$pk->entityRuntimeId = $this->getId();
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->speedX = $this->motionX;
$pk->speedY = $this->motionY;
$pk->speedZ = $this->motionZ;
$pk->position = $this->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->yaw;
$pk->pitch = $this->pitch;
$pk->metadata = $this->dataProperties;

View File

@ -55,8 +55,10 @@ use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\protocol\MoveEntityPacket;
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
use pocketmine\network\mcpe\protocol\SetEntityDataPacket;
use pocketmine\network\mcpe\protocol\SetEntityMotionPacket;
use pocketmine\Player;
use pocketmine\plugin\Plugin;
use pocketmine\Server;
@ -199,8 +201,13 @@ abstract class Entity extends Location implements Metadatable{
const DATA_FLAG_IDLING = 39;
const DATA_FLAG_EVOKER_SPELL = 40;
const DATA_FLAG_CHARGE_ATTACK = 41;
const DATA_FLAG_LINGER = 45;
const DATA_FLAG_WASD_CONTROLLED = 42;
const DATA_FLAG_CAN_POWER_JUMP = 43;
const DATA_FLAG_LINGER = 44;
const DATA_FLAG_HAS_COLLISION = 45;
const DATA_FLAG_AFFECTED_BY_GRAVITY = 46;
const DATA_FLAG_FIRE_IMMUNE = 47;
const DATA_FLAG_DANCING = 48;
public static $entityCount = 1;
/** @var Entity[] */
@ -436,6 +443,9 @@ abstract class Entity extends Location implements Metadatable{
$this->attributeMap = new AttributeMap();
$this->addAttributes();
$this->setGenericFlag(self::DATA_FLAG_AFFECTED_BY_GRAVITY, true);
$this->setGenericFlag(self::DATA_FLAG_HAS_COLLISION, true);
$this->chunk->addEntity($this);
$this->level->addEntity($this);
$this->initEntity();
@ -1161,7 +1171,7 @@ abstract class Entity extends Location implements Metadatable{
$this->lastYaw = $this->yaw;
$this->lastPitch = $this->pitch;
$this->level->addEntityMovement($this->chunk->getX(), $this->chunk->getZ(), $this->id, $this->x, $this->y + $this->baseOffset, $this->z, $this->yaw, $this->pitch, $this->yaw);
$this->broadcastMovement();
}
if($diffMotion > 0.0025 or ($diffMotion > 0.0001 and $this->getMotion()->lengthSquared() <= 0.0001)){ //0.05 ** 2
@ -1169,10 +1179,33 @@ abstract class Entity extends Location implements Metadatable{
$this->lastMotionY = $this->motionY;
$this->lastMotionZ = $this->motionZ;
$this->level->addEntityMotion($this->chunk->getX(), $this->chunk->getZ(), $this->id, $this->motionX, $this->motionY, $this->motionZ);
$this->broadcastMotion();
}
}
public function getOffsetPosition(Vector3 $vector3) : Vector3{
return new Vector3($vector3->x, $vector3->y + $this->baseOffset, $vector3->z);
}
protected function broadcastMovement(){
$pk = new MoveEntityPacket();
$pk->entityRuntimeId = $this->id;
$pk->position = $this->getOffsetPosition($this);
$pk->yaw = $this->yaw;
$pk->pitch = $this->pitch;
$pk->headYaw = $this->yaw; //TODO
$this->level->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $pk);
}
protected function broadcastMotion(){
$pk = new SetEntityMotionPacket();
$pk->entityRuntimeId = $this->id;
$pk->motion = $this->getMotion();
$this->level->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $pk);
}
protected function applyDragBeforeGravity() : bool{
return false;
}

View File

@ -26,7 +26,6 @@ namespace pocketmine\entity;
use pocketmine\block\BlockFactory;
use pocketmine\event\entity\EntityBlockChangeEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item as ItemItem;
use pocketmine\item\ItemFactory;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
@ -126,12 +125,8 @@ class FallingSand extends Entity{
$pk = new AddEntityPacket();
$pk->type = FallingSand::NETWORK_ID;
$pk->entityRuntimeId = $this->getId();
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->speedX = $this->motionX;
$pk->speedY = $this->motionY;
$pk->speedZ = $this->motionZ;
$pk->position = $this->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->yaw;
$pk->pitch = $this->pitch;
$pk->metadata = $this->dataProperties;

View File

@ -31,12 +31,10 @@ 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;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\Player;
@ -280,30 +278,34 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
return $this->inventory;
}
/**
* For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
*/
protected function initHumanData(){
if(isset($this->namedtag->NameTag)){
$this->setNameTag($this->namedtag["NameTag"]);
}
if(isset($this->namedtag->Skin) and $this->namedtag->Skin instanceof CompoundTag){
$this->setSkin($this->namedtag->Skin["Data"], $this->namedtag->Skin["Name"]);
}
$this->uuid = UUID::fromData((string) $this->getId(), $this->getSkinData(), $this->getNameTag());
}
protected function initEntity(){
$this->setPlayerFlag(self::DATA_PLAYER_FLAG_SLEEP, false);
$this->setDataProperty(self::DATA_PLAYER_BED_POSITION, self::DATA_TYPE_POS, [0, 0, 0], false);
$this->inventory = new PlayerInventory($this);
if($this instanceof Player){
$this->addWindow($this->inventory, 0);
}else{
if(isset($this->namedtag->NameTag)){
$this->setNameTag($this->namedtag["NameTag"]);
}
if(isset($this->namedtag->Skin) and $this->namedtag->Skin instanceof CompoundTag){
$this->setSkin($this->namedtag->Skin["Data"], $this->namedtag->Skin["Name"]);
}
$this->uuid = UUID::fromData((string) $this->getId(), $this->getSkinData(), $this->getNameTag());
}
$this->initHumanData();
if(isset($this->namedtag->Inventory) and $this->namedtag->Inventory instanceof ListTag){
foreach($this->namedtag->Inventory as $item){
foreach($this->namedtag->Inventory as $i => $item){
if($item["Slot"] >= 0 and $item["Slot"] < 9){ //Hotbar
$this->inventory->setHotbarSlotIndex($item["Slot"], isset($item["TrueSlot"]) ? $item["TrueSlot"] : -1);
//Old hotbar saving stuff, remove it (useless now)
unset($this->namedtag->Inventory->{$i});
}elseif($item["Slot"] >= 100 and $item["Slot"] < 104){ //Armor
$this->inventory->setItem($this->inventory->getSize() + $item["Slot"] - 100, ItemItem::nbtDeserialize($item));
}else{
@ -445,28 +447,6 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$this->namedtag->Inventory = new ListTag("Inventory", []);
$this->namedtag->Inventory->setTagType(NBT::TAG_Compound);
if($this->inventory !== null){
for($slot = 0; $slot < 9; ++$slot){
$hotbarSlot = $this->inventory->getHotbarSlotIndex($slot);
if($hotbarSlot !== -1){
$item = $this->inventory->getItem($hotbarSlot);
if($item->getId() !== 0 and $item->getCount() > 0){
$tag = $item->nbtSerialize($slot);
$tag->TrueSlot = new ByteTag("TrueSlot", $hotbarSlot);
$this->namedtag->Inventory[$slot] = $tag;
continue;
}
}
$this->namedtag->Inventory[$slot] = new CompoundTag("", [
new ByteTag("Count", 0),
new ShortTag("Damage", 0),
new ByteTag("Slot", $slot),
new ByteTag("TrueSlot", -1),
new ShortTag("id", 0)
]);
}
//Normal inventory
$slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize();
for($slot = $this->inventory->getHotbarSize(); $slot < $slotCount; ++$slot){
@ -511,12 +491,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$pk->uuid = $this->getUniqueId();
$pk->username = $this->getName();
$pk->entityRuntimeId = $this->getId();
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->speedX = $this->motionX;
$pk->speedY = $this->motionY;
$pk->speedZ = $this->motionZ;
$pk->position = $this->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->yaw;
$pk->pitch = $this->pitch;
$pk->item = $this->getInventory()->getItemInHand();
@ -537,6 +513,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
foreach($this->inventory->getViewers() as $viewer){
$viewer->removeWindow($this->inventory);
}
$this->inventory = null;
}
parent::close();
}

View File

@ -206,12 +206,8 @@ class Item extends Entity{
public function spawnTo(Player $player){
$pk = new AddItemEntityPacket();
$pk->entityRuntimeId = $this->getId();
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->speedX = $this->motionX;
$pk->speedY = $this->motionY;
$pk->speedZ = $this->motionZ;
$pk->position = $this->asVector3();
$pk->motion = $this->getMotion();
$pk->item = $this->getItem();
$pk->metadata = $this->dataProperties;
$player->dataPacket($pk);

View File

@ -117,12 +117,8 @@ class PrimedTNT extends Entity implements Explosive{
$pk = new AddEntityPacket();
$pk->type = PrimedTNT::NETWORK_ID;
$pk->entityRuntimeId = $this->getId();
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->speedX = $this->motionX;
$pk->speedY = $this->motionY;
$pk->speedZ = $this->motionZ;
$pk->position = $this->asVector3();
$pk->motion = $this->getMotion();
$pk->metadata = $this->dataProperties;
$player->dataPacket($pk);

View File

@ -60,12 +60,8 @@ class Snowball extends Projectile{
$pk = new AddEntityPacket();
$pk->type = Snowball::NETWORK_ID;
$pk->entityRuntimeId = $this->getId();
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->speedX = $this->motionX;
$pk->speedY = $this->motionY;
$pk->speedZ = $this->motionZ;
$pk->position = $this->asVector3();
$pk->motion = $this->getMotion();
$pk->metadata = $this->dataProperties;
$player->dataPacket($pk);

View File

@ -131,12 +131,8 @@ class Squid extends WaterAnimal{
$pk = new AddEntityPacket();
$pk->entityRuntimeId = $this->getId();
$pk->type = Squid::NETWORK_ID;
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->speedX = $this->motionX;
$pk->speedY = $this->motionY;
$pk->speedZ = $this->motionZ;
$pk->position = $this->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->yaw;
$pk->pitch = $this->pitch;
$pk->metadata = $this->dataProperties;

View File

@ -55,12 +55,8 @@ class Villager extends Creature implements NPC, Ageable{
$pk = new AddEntityPacket();
$pk->entityRuntimeId = $this->getId();
$pk->type = Villager::NETWORK_ID;
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->speedX = $this->motionX;
$pk->speedY = $this->motionY;
$pk->speedZ = $this->motionZ;
$pk->position = $this->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->yaw;
$pk->pitch = $this->pitch;
$pk->metadata = $this->dataProperties;

View File

@ -43,12 +43,8 @@ class Zombie extends Monster{
$pk = new AddEntityPacket();
$pk->entityRuntimeId = $this->getId();
$pk->type = Zombie::NETWORK_ID;
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->speedX = $this->motionX;
$pk->speedY = $this->motionY;
$pk->speedZ = $this->motionZ;
$pk->position = $this->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->yaw;
$pk->pitch = $this->pitch;
$pk->metadata = $this->dataProperties;

View File

@ -46,7 +46,7 @@ class SignChangeEvent extends BlockEvent implements Cancellable{
public function __construct(Block $theBlock, Player $thePlayer, array $theLines){
parent::__construct($theBlock);
$this->player = $thePlayer;
$this->lines = $theLines;
$this->setLines($theLines);
}
/**

View File

@ -26,51 +26,55 @@ namespace pocketmine\event\inventory;
use pocketmine\event\Cancellable;
use pocketmine\event\Event;
use pocketmine\inventory\Recipe;
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\item\Item;
use pocketmine\Player;
class CraftItemEvent extends Event implements Cancellable{
public static $handlerList = null;
/** @var Item[] */
private $input;
/** @var Recipe */
private $recipe;
/** @var Player */
private $player;
/** @var CraftingTransaction */
private $transaction;
/**
* @param Player $player
* @param Item[] $input
* @param Recipe $recipe
* @param CraftingTransaction $transaction
*/
public function __construct(Player $player, array $input, Recipe $recipe){
$this->player = $player;
$this->input = $input;
$this->recipe = $recipe;
public function __construct(CraftingTransaction $transaction){
$this->transaction = $transaction;
}
public function getTransaction() : CraftingTransaction{
return $this->transaction;
}
/**
* @deprecated This returns a one-dimensional array of ingredients and does not account for the positioning of
* items in the crafting grid. Prefer getting the input map from the transaction instead.
*
* @return Item[]
*/
public function getInput() : array{
return array_map(function(Item $item) : Item{
return clone $item;
}, $this->input);
}, array_merge(...$this->transaction->getInputMap()));
}
/**
* @return Recipe
*/
public function getRecipe() : Recipe{
return $this->recipe;
$recipe = $this->transaction->getRecipe();
if($recipe === null){
throw new \RuntimeException("This shouldn't be called if the transaction can't be executed");
}
return $recipe;
}
/**
* @return Player
*/
public function getPlayer() : Player{
return $this->player;
return $this->transaction->getSource();
}
}

View File

@ -25,7 +25,7 @@ namespace pocketmine\event\inventory;
use pocketmine\event\Cancellable;
use pocketmine\event\Event;
use pocketmine\inventory\TransactionGroup;
use pocketmine\inventory\transaction\InventoryTransaction;
/**
* Called when there is a transaction between two Inventory objects.
@ -34,21 +34,21 @@ use pocketmine\inventory\TransactionGroup;
class InventoryTransactionEvent extends Event implements Cancellable{
public static $handlerList = null;
/** @var TransactionGroup */
private $ts;
/** @var InventoryTransaction */
private $transaction;
/**
* @param TransactionGroup $ts
* @param InventoryTransaction $transaction
*/
public function __construct(TransactionGroup $ts){
$this->ts = $ts;
public function __construct(InventoryTransaction $transaction){
$this->transaction = $transaction;
}
/**
* @return TransactionGroup
* @return InventoryTransaction
*/
public function getTransaction() : TransactionGroup{
return $this->ts;
public function getTransaction() : InventoryTransaction{
return $this->transaction;
}
}

View File

@ -0,0 +1,59 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\block\Block;
use pocketmine\event\Cancellable;
use pocketmine\item\Item;
use pocketmine\Player;
/**
* Called when a player middle-clicks on a block to get an item in creative mode.
*/
class PlayerBlockPickEvent extends PlayerEvent implements Cancellable{
public static $handlerList = null;
/** @var Block */
private $blockClicked;
/** @var Item */
private $resultItem;
public function __construct(Player $player, Block $blockClicked, Item $resultItem){
$this->player = $player;
$this->blockClicked = $blockClicked;
$this->resultItem = $resultItem;
}
public function getBlock() : Block{
return $this->blockClicked;
}
public function getResultItem() : Item{
return $this->resultItem;
}
public function setResultItem(Item $item) : void{
$this->resultItem = clone $item;
}
}

View File

@ -34,18 +34,21 @@ class PlayerItemHeldEvent extends PlayerEvent implements Cancellable{
private $item;
/** @var int */
private $hotbarSlot;
/** @var int */
private $inventorySlot;
public function __construct(Player $player, Item $item, int $inventorySlot, int $hotbarSlot){
public function __construct(Player $player, Item $item, int $hotbarSlot){
$this->player = $player;
$this->item = $item;
$this->inventorySlot = $inventorySlot;
$this->hotbarSlot = $hotbarSlot;
}
/**
* Returns the hotbar slot the player is attempting to hold.
*
* NOTE: This event is called BEFORE the slot is equipped server-side. Setting the player's held item during this
* event will result in the **old** slot being changed, not this one.
*
* To change the item in the slot that the player is attempting to hold, set the slot that this function reports.
*
* @return int
*/
public function getSlot() : int{
@ -53,14 +56,25 @@ class PlayerItemHeldEvent extends PlayerEvent implements Cancellable{
}
/**
* @deprecated This is currently an alias of {@link getSlot}
*
* Some background for confused future readers: Before MCPE 1.2, hotbar slots and inventory slots were not the same
* thing - a hotbar slot was a link to a certain slot in the inventory.
* As of 1.2, hotbar slots are now always linked to their respective slots in the inventory, meaning that the two
* are now synonymous, rendering the separate methods obsolete.
*
* @return int
*/
public function getInventorySlot() : int{
return $this->inventorySlot;
return $this->getSlot();
}
/**
* Returns the item in the slot that the player is trying to equip.
*
* @return Item
*/
public function getItem() : Item{
return $this->item;
}
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\event\server;
use pocketmine\event\Cancellable;
use pocketmine\network\SourceInterface;
/**
* Called when a network interface is registered into the network, for example the RakLib interface.

View File

@ -24,21 +24,39 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\level\Position;
use pocketmine\network\mcpe\protocol\types\WindowTypes;
use pocketmine\Player;
class AnvilInventory extends ContainerInventory{
/** @var FakeBlockMenu */
protected $holder;
public function __construct(Position $pos){
parent::__construct(new FakeBlockMenu($this, $pos), InventoryType::get(InventoryType::ANVIL));
parent::__construct(new FakeBlockMenu($this, $pos));
}
public function getNetworkType() : int{
return WindowTypes::ANVIL;
}
public function getName() : string{
return "Anvil";
}
public function getDefaultSize() : int{
return 3; //1 input, 1 material, 1 result
}
/**
* This override is here for documentation and code completion purposes only.
* @return FakeBlockMenu
*/
public function getHolder(){
return $this->holder;
}
public function onClose(Player $who){
public function onClose(Player $who) : void{
parent::onClose($who);
for($i = 0; $i < 2; ++$i){

View File

@ -23,29 +23,23 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\entity\Entity;
use pocketmine\event\entity\EntityInventoryChangeEvent;
use pocketmine\event\inventory\InventoryOpenEvent;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\Player;
use pocketmine\Server;
abstract class BaseInventory implements Inventory{
/** @var InventoryType */
protected $type;
/** @var int */
protected $maxStackSize = Inventory::MAX_STACK;
/** @var int */
protected $size;
/** @var string */
protected $name;
/** @var string */
protected $title;
/** @var Item[] */
/** @var \SplFixedArray<Item> */
protected $slots = [];
/** @var Player[] */
protected $viewers = [];
@ -54,108 +48,105 @@ abstract class BaseInventory implements Inventory{
/**
* @param InventoryHolder $holder
* @param InventoryType $type
* @param Item[] $items
* @param int $overrideSize
* @param string $overrideTitle
* @param int $size
* @param string $title
*/
public function __construct(InventoryHolder $holder, InventoryType $type, array $items = [], $overrideSize = null, $overrideTitle = null){
public function __construct(InventoryHolder $holder, array $items = [], int $size = null, string $title = null){
$this->holder = $holder;
$this->type = $type;
if($overrideSize !== null){
$this->size = (int) $overrideSize;
}else{
$this->size = $this->type->getDefaultSize();
}
$this->slots = new \SplFixedArray($size ?? $this->getDefaultSize());
$this->title = $title ?? $this->getName();
if($overrideTitle !== null){
$this->title = $overrideTitle;
}else{
$this->title = $this->type->getDefaultTitle();
}
$this->name = $this->type->getDefaultTitle();
$this->setContents($items);
$this->setContents($items, false);
}
public function __destruct(){
$this->holder = null;
$this->slots = [];
}
public function getSize() : int{
return $this->size;
}
public function setSize(int $size){
$this->size = $size;
}
public function getMaxStackSize() : int{
return $this->maxStackSize;
}
public function getName() : string{
return $this->name;
}
abstract public function getName() : string;
public function getTitle() : string{
return $this->title;
}
public function getItem(int $index) : Item{
assert($index >= 0, "Inventory slot should not be negative");
return isset($this->slots[$index]) ? clone $this->slots[$index] : ItemFactory::get(Item::AIR, 0, 0);
/**
* Returns the size of the inventory.
* @return int
*/
public function getSize() : int{
return $this->slots->getSize();
}
/**
* Sets the new size of the inventory.
* WARNING: If the size is smaller, any items past the new size will be lost.
*
* @param int $size
*/
public function setSize(int $size){
$this->slots->setSize($size);
}
abstract public function getDefaultSize() : int;
public function getMaxStackSize() : int{
return $this->maxStackSize;
}
public function getItem(int $index) : Item{
return $this->slots[$index] !== null ? clone $this->slots[$index] : ItemFactory::get(Item::AIR, 0, 0);
}
/**
* @return Item[]
*/
public function getContents() : array{
return $this->slots;
return array_filter($this->slots->toArray(), function(Item $item = null){ return $item !== null; });
}
/**
* @param Item[] $items
* @param bool $send
*/
public function setContents(array $items){
if(count($items) > $this->size){
$items = array_slice($items, 0, $this->size, true);
public function setContents(array $items, bool $send = true) : void{
if(count($items) > $this->getSize()){
$items = array_slice($items, 0, $this->getSize(), true);
}
for($i = 0; $i < $this->size; ++$i){
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
if(!isset($items[$i])){
if(isset($this->slots[$i])){
$this->clear($i);
if($this->slots[$i] !== null){
$this->clear($i, false);
}
}else{
if(!$this->setItem($i, $items[$i])){
$this->clear($i);
if(!$this->setItem($i, $items[$i], false)){
$this->clear($i, false);
}
}
}
if($send){
$this->sendContents($this->getViewers());
}
}
public function setItem(int $index, Item $item) : bool{
$item = clone $item;
if($index < 0 or $index >= $this->size){
return false;
}elseif($item->getId() === 0 or $item->getCount() <= 0){
return $this->clear($index);
protected function doSetItemEvents(int $index, Item $newItem) : ?Item{
return $newItem;
}
public function setItem(int $index, Item $item, bool $send = true) : bool{
if($item->isNull()){
$item = ItemFactory::get(Item::AIR, 0, 0);
}else{
$item = clone $item;
}
$holder = $this->getHolder();
if($holder instanceof Entity){
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($holder, $this->getItem($index), $item, $index));
if($ev->isCancelled()){
$this->sendSlot($index, $this->getViewers());
return false;
}
$item = $ev->getNewItem();
$newItem = $this->doSetItemEvents($index, $item);
if($newItem === null){
return false;
}
$old = $this->getItem($index);
$this->slots[$index] = clone $item;
$this->onSlotChange($index, $old);
$this->slots[$index] = $newItem->isNull() ? null : $newItem;
$this->onSlotChange($index, $old, $send);
return true;
}
@ -189,7 +180,7 @@ abstract class BaseInventory implements Inventory{
return $slots;
}
public function remove(Item $item){
public function remove(Item $item) : void{
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasCompoundTag();
@ -200,13 +191,13 @@ abstract class BaseInventory implements Inventory{
}
}
public function first(Item $item) : int{
$count = max(1, $item->getCount());
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasCompoundTag();
public function first(Item $item, bool $exact = false) : int{
$count = $exact ? $item->getCount() : max(1, $item->getCount());
$checkDamage = $exact || !$item->hasAnyDamageValue();
$checkTags = $exact || $item->hasCompoundTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags) and $i->getCount() >= $count){
if($item->equals($i, $checkDamage, $checkTags) and ($i->getCount() === $count or (!$exact and $i->getCount() > $count))){
return $index;
}
}
@ -215,8 +206,8 @@ abstract class BaseInventory implements Inventory{
}
public function firstEmpty() : int{
for($i = 0; $i < $this->size; ++$i){
if($this->getItem($i)->getId() === Item::AIR){
foreach($this->slots as $i => $slot){
if($slot === null or $slot->isNull()){
return $i;
}
}
@ -234,7 +225,7 @@ abstract class BaseInventory implements Inventory{
if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){
$item->setCount($item->getCount() - $diff);
}
}elseif($slot->getId() === Item::AIR){
}elseif($slot->isNull()){
$item->setCount($item->getCount() - $this->getMaxStackSize());
}
@ -260,7 +251,7 @@ abstract class BaseInventory implements Inventory{
for($i = 0; $i < $this->getSize(); ++$i){
$item = $this->getItem($i);
if($item->getId() === Item::AIR or $item->getCount() <= 0){
if($item->isNull()){
$emptySlots[] = $i;
}
@ -315,7 +306,7 @@ abstract class BaseInventory implements Inventory{
for($i = 0; $i < $this->getSize(); ++$i){
$item = $this->getItem($i);
if($item->getId() === Item::AIR or $item->getCount() <= 0){
if($item->isNull()){
continue;
}
@ -339,35 +330,16 @@ abstract class BaseInventory implements Inventory{
return $itemSlots;
}
public function clear(int $index) : bool{
if(isset($this->slots[$index])){
$item = ItemFactory::get(Item::AIR, 0, 0);
$old = $this->slots[$index];
$holder = $this->getHolder();
if($holder instanceof Entity){
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($holder, $old, $item, $index));
if($ev->isCancelled()){
$this->sendSlot($index, $this->getViewers());
return false;
}
$item = $ev->getNewItem();
}
if($item->getId() !== Item::AIR){
$this->slots[$index] = clone $item;
}else{
unset($this->slots[$index]);
}
$this->onSlotChange($index, $old);
}
return true;
public function clear(int $index, bool $send = true) : bool{
return $this->setItem($index, ItemFactory::get(Item::AIR, 0, 0), $send);
}
public function clearAll(){
foreach($this->getContents() as $index => $i){
$this->clear($index);
public function clearAll() : void{
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$this->clear($i, false);
}
$this->sendContents($this->getViewers());
}
/**
@ -381,7 +353,7 @@ abstract class BaseInventory implements Inventory{
return $this->holder;
}
public function setMaxStackSize(int $size){
public function setMaxStackSize(int $size) : void{
$this->maxStackSize = $size;
}
@ -395,44 +367,46 @@ abstract class BaseInventory implements Inventory{
return true;
}
public function close(Player $who){
public function close(Player $who) : void{
$this->onClose($who);
}
public function onOpen(Player $who){
public function onOpen(Player $who) : void{
$this->viewers[spl_object_hash($who)] = $who;
}
public function onClose(Player $who){
public function onClose(Player $who) : void{
unset($this->viewers[spl_object_hash($who)]);
}
public function onSlotChange($index, $before){
$this->sendSlot($index, $this->getViewers());
public function onSlotChange(int $index, Item $before, bool $send) : void{
if($send){
$this->sendSlot($index, $this->getViewers());
}
}
/**
* @param Player|Player[] $target
*/
public function sendContents($target){
public function sendContents($target) : void{
if($target instanceof Player){
$target = [$target];
}
$pk = new ContainerSetContentPacket();
$pk->slots = [];
for($i = 0; $i < $this->getSize(); ++$i){
$pk->slots[$i] = $this->getItem($i);
$pk = new InventoryContentPacket();
//Using getSize() here allows PlayerInventory to report that it's 4 slots smaller than it actually is (armor hack)
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$pk->items[$i] = $this->getItem($i);
}
foreach($target as $player){
if(($id = $player->getWindowId($this)) === -1 or $player->spawned !== true){
if(($id = $player->getWindowId($this)) === ContainerIds::NONE or $player->spawned !== true){
$this->close($player);
continue;
}
$pk->windowid = $id;
$pk->targetEid = $player->getId();
$pk->windowId = $id;
$player->dataPacket($pk);
}
}
@ -441,27 +415,22 @@ abstract class BaseInventory implements Inventory{
* @param int $index
* @param Player|Player[] $target
*/
public function sendSlot($index, $target){
public function sendSlot(int $index, $target) : void{
if($target instanceof Player){
$target = [$target];
}
$pk = new ContainerSetSlotPacket();
$pk->slot = $index;
$pk->item = clone $this->getItem($index);
$pk = new InventorySlotPacket();
$pk->inventorySlot = $index;
$pk->item = $this->getItem($index);
foreach($target as $player){
if(($id = $player->getWindowId($this)) === -1){
if(($id = $player->getWindowId($this)) === ContainerIds::NONE){
$this->close($player);
continue;
}
$pk->windowid = $id;
$pk->windowId = $id;
$player->dataPacket($pk);
}
}
public function getType() : InventoryType{
return $this->type;
}
}

View File

@ -1,73 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
class BaseTransaction implements Transaction{
/** @var Inventory */
protected $inventory;
/** @var int */
protected $slot;
/** @var Item */
protected $sourceItem;
/** @var Item */
protected $targetItem;
/** @var float */
protected $creationTime;
/**
* @param Inventory $inventory
* @param int $slot
* @param Item $sourceItem
* @param Item $targetItem
*/
public function __construct(Inventory $inventory, int $slot, Item $sourceItem, Item $targetItem){
$this->inventory = $inventory;
$this->slot = $slot;
$this->sourceItem = clone $sourceItem;
$this->targetItem = clone $targetItem;
$this->creationTime = microtime(true);
}
public function getCreationTime() : float{
return $this->creationTime;
}
public function getInventory() : Inventory{
return $this->inventory;
}
public function getSlot() : int{
return $this->slot;
}
public function getSourceItem() : Item{
return clone $this->sourceItem;
}
public function getTargetItem() : Item{
return clone $this->targetItem;
}
}

View File

@ -23,6 +23,9 @@ declare(strict_types=1);
namespace pocketmine\inventory;
class BigShapedRecipe extends ShapedRecipe{
class BigCraftingGrid extends CraftingGrid{
public function getDefaultSize() : int{
return 9;
}
}

View File

@ -26,22 +26,43 @@ namespace pocketmine\inventory;
use pocketmine\level\Level;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\types\WindowTypes;
use pocketmine\Player;
use pocketmine\tile\Chest;
class ChestInventory extends ContainerInventory{
/** @var Chest */
protected $holder;
/**
* @param Chest $tile
*/
public function __construct(Chest $tile){
parent::__construct($tile, InventoryType::get(InventoryType::CHEST));
parent::__construct($tile);
}
public function getNetworkType() : int{
return WindowTypes::CONTAINER;
}
public function getName() : string{
return "Chest";
}
public function getDefaultSize() : int{
return 27;
}
/**
* This override is here for documentation and code completion purposes only.
* @return Chest
*/
public function getHolder(){
return $this->holder;
}
public function onOpen(Player $who){
public function onOpen(Player $who) : void{
parent::onOpen($who);
if(count($this->getViewers()) === 1 and ($level = $this->getHolder()->getLevel()) instanceof Level){
@ -50,7 +71,7 @@ class ChestInventory extends ContainerInventory{
}
}
public function onClose(Player $who){
public function onClose(Player $who) : void{
if(count($this->getViewers()) === 1 and ($level = $this->getHolder()->getLevel()) instanceof Level){
$this->broadcastBlockEventPacket(1, 0); //chest close
$level->broadcastLevelSoundEvent($this->getHolder()->add(0.5, 0.5, 0.5), LevelSoundEventPacket::SOUND_CHEST_CLOSED);

View File

@ -29,11 +29,11 @@ use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\Player;
abstract class ContainerInventory extends BaseInventory{
public function onOpen(Player $who){
public function onOpen(Player $who) : void{
parent::onOpen($who);
$pk = new ContainerOpenPacket();
$pk->windowid = $who->getWindowId($this);
$pk->type = $this->getType()->getNetworkType();
$pk->windowId = $who->getWindowId($this);
$pk->type = $this->getNetworkType();
$holder = $this->getHolder();
if($holder instanceof Vector3){
$pk->x = $holder->getX();
@ -48,10 +48,16 @@ abstract class ContainerInventory extends BaseInventory{
$this->sendContents($who);
}
public function onClose(Player $who){
public function onClose(Player $who) : void{
$pk = new ContainerClosePacket();
$pk->windowid = $who->getWindowId($this);
$pk->windowId = $who->getWindowId($this);
$who->dataPacket($pk);
parent::onClose($who);
}
/**
* Returns the Minecraft PE inventory type used to show the inventory window to clients.
* @return int
*/
abstract public function getNetworkType() : int;
}

View File

@ -0,0 +1,53 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\Player;
class CraftingGrid extends BaseInventory{
public function __construct(Player $holder){
parent::__construct($holder);
}
public function getDefaultSize() : int{
return 4;
}
public function setSize(int $size){
throw new \BadMethodCallException("Cannot change the size of a crafting grid");
}
public function getName() : string{
return "Crafting";
}
public function sendSlot(int $index, $target) : void{
//we can't send a slot of a client-sided inventory window
}
public function sendContents($target) : void{
//we can't send the contents of a client-sided inventory window
}
}

View File

@ -1,62 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
/**
* Manages crafting operations
* This class includes future methods for shaped crafting
*
* TODO: add small matrix inventory
*/
class CraftingInventory extends BaseInventory{
/** @var Inventory */
private $resultInventory;
/**
* @param InventoryHolder $holder
* @param Inventory $resultInventory
* @param InventoryType $inventoryType
*
* @throws \Exception
*/
public function __construct(InventoryHolder $holder, Inventory $resultInventory, InventoryType $inventoryType){
if($inventoryType->getDefaultTitle() !== "Crafting"){
throw new \InvalidStateException("Invalid Inventory type, expected CRAFTING or WORKBENCH");
}
$this->resultInventory = $resultInventory;
parent::__construct($holder, $inventoryType);
}
/**
* @return Inventory
*/
public function getResultInventory(){
return $this->resultInventory;
}
public function getSize() : int{
return $this->getResultInventory()->getSize() + parent::getSize();
}
}

View File

@ -34,11 +34,13 @@ use pocketmine\utils\UUID;
class CraftingManager{
/** @var Recipe[] */
/** @var CraftingRecipe[] */
public $recipes = [];
/** @var Recipe[][] */
protected $recipeLookup = [];
/** @var ShapedRecipe[][] */
protected $shapedRecipes = [];
/** @var ShapelessRecipe[][] */
protected $shapelessRecipes = [];
/** @var FurnaceRecipe[] */
public $furnaceRecipes = [];
@ -66,17 +68,14 @@ class CraftingManager{
$this->registerRecipe($result);
break;
case 1:
// TODO: handle multiple result items
$first = $recipe["output"][0];
$result = new ShapedRecipe(Item::jsonDeserialize($first), $recipe["height"], $recipe["width"]);
$first = array_shift($recipe["output"]);
$shape = array_chunk($recipe["input"], $recipe["width"]);
foreach($shape as $y => $row){
foreach($row as $x => $ingredient){
$result->addIngredient($x, $y, Item::jsonDeserialize($ingredient));
}
}
$this->registerRecipe($result);
$this->registerRecipe(new ShapedRecipe(
Item::jsonDeserialize($first),
$recipe["shape"],
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]),
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"])
));
break;
case 2:
case 3:
@ -95,7 +94,7 @@ class CraftingManager{
/**
* Rebuilds the cached CraftingDataPacket.
*/
public function buildCraftingDataCache(){
public function buildCraftingDataCache() : void{
Timings::$craftingDataCacheRebuildTimer->startTiming();
$pk = new CraftingDataPacket();
$pk->cleanRecipes = true;
@ -131,6 +130,14 @@ class CraftingManager{
return $this->craftingDataCache;
}
/**
* Function used to arrange Shapeless Recipe ingredient lists into a consistent order.
*
* @param Item $i1
* @param Item $i2
*
* @return int
*/
public function sort(Item $i1, Item $i2){
if($i1->getId() > $i2->getId()){
return 1;
@ -151,9 +158,9 @@ class CraftingManager{
/**
* @param UUID $id
* @return Recipe|null
* @return CraftingRecipe|null
*/
public function getRecipe(UUID $id){
public function getRecipe(UUID $id) : ?CraftingRecipe{
$index = $id->toBinary();
return $this->recipes[$index] ?? null;
}
@ -165,6 +172,20 @@ class CraftingManager{
return $this->recipes;
}
/**
* @return ShapelessRecipe[][]
*/
public function getShapelessRecipes() : array{
return $this->shapelessRecipes;
}
/**
* @return ShapedRecipe[][]
*/
public function getShapedRecipes() : array{
return $this->shapedRecipes;
}
/**
* @return FurnaceRecipe[]
*/
@ -172,140 +193,117 @@ class CraftingManager{
return $this->furnaceRecipes;
}
/**
* @param Item $input
*
* @return FurnaceRecipe|null
*/
public function matchFurnaceRecipe(Item $input){
if(isset($this->furnaceRecipes[$input->getId() . ":" . $input->getDamage()])){
return $this->furnaceRecipes[$input->getId() . ":" . $input->getDamage()];
}elseif(isset($this->furnaceRecipes[$input->getId() . ":?"])){
return $this->furnaceRecipes[$input->getId() . ":?"];
}
return null;
}
/**
* @param ShapedRecipe $recipe
*/
public function registerShapedRecipe(ShapedRecipe $recipe){
$result = $recipe->getResult();
$this->recipes[$recipe->getId()->toBinary()] = $recipe;
$ingredients = $recipe->getIngredientMap();
$hash = "";
foreach($ingredients as $v){
foreach($v as $item){
if($item !== null){
/** @var Item $item */
$hash .= $item->getId() . ":" . ($item->hasAnyDamageValue() ? "?" : $item->getDamage()) . "x" . $item->getCount() . ",";
}
}
$hash .= ";";
}
$this->recipeLookup[$result->getId() . ":" . $result->getDamage()][$hash] = $recipe;
public function registerShapedRecipe(ShapedRecipe $recipe) : void{
$this->shapedRecipes[json_encode($recipe->getResult())][json_encode($recipe->getIngredientMap())] = $recipe;
$this->craftingDataCache = null;
}
/**
* @param ShapelessRecipe $recipe
*/
public function registerShapelessRecipe(ShapelessRecipe $recipe){
$result = $recipe->getResult();
$this->recipes[$recipe->getId()->toBinary()] = $recipe;
$hash = "";
public function registerShapelessRecipe(ShapelessRecipe $recipe) : void{
$ingredients = $recipe->getIngredientList();
usort($ingredients, [$this, "sort"]);
foreach($ingredients as $item){
$hash .= $item->getId() . ":" . ($item->hasAnyDamageValue() ? "?" : $item->getDamage()) . "x" . $item->getCount() . ",";
}
$this->recipeLookup[$result->getId() . ":" . $result->getDamage()][$hash] = $recipe;
$this->shapelessRecipes[json_encode($recipe->getResult())][json_encode($ingredients)] = $recipe;
$this->craftingDataCache = null;
}
/**
* @param FurnaceRecipe $recipe
*/
public function registerFurnaceRecipe(FurnaceRecipe $recipe){
public function registerFurnaceRecipe(FurnaceRecipe $recipe) : void{
$input = $recipe->getInput();
$this->furnaceRecipes[$input->getId() . ":" . ($input->hasAnyDamageValue() ? "?" : $input->getDamage())] = $recipe;
$this->craftingDataCache = null;
}
/**
* @param ShapelessRecipe $recipe
* @return bool
* Clones a map of Item objects to avoid accidental modification.
*
* @param Item[][] $map
* @return Item[][]
*/
public function matchRecipe(ShapelessRecipe $recipe) : bool{
if(!isset($this->recipeLookup[$idx = $recipe->getResult()->getId() . ":" . $recipe->getResult()->getDamage()])){
return false;
}
$hash = "";
$ingredients = $recipe->getIngredientList();
usort($ingredients, [$this, "sort"]);
foreach($ingredients as $item){
$hash .= $item->getId() . ":" . ($item->hasAnyDamageValue() ? "?" : $item->getDamage()) . "x" . $item->getCount() . ",";
}
if(isset($this->recipeLookup[$idx][$hash])){
return true;
}
$hasRecipe = null;
foreach($this->recipeLookup[$idx] as $possibleRecipe){
if($possibleRecipe instanceof ShapelessRecipe){
if($possibleRecipe->getIngredientCount() !== count($ingredients)){
continue;
}
$checkInput = $possibleRecipe->getIngredientList();
foreach($ingredients as $item){
$amount = $item->getCount();
foreach($checkInput as $k => $checkItem){
if($checkItem->equals($item, !$checkItem->hasAnyDamageValue(), $checkItem->hasCompoundTag())){
$remove = min($checkItem->getCount(), $amount);
$checkItem->setCount($checkItem->getCount() - $remove);
if($checkItem->getCount() === 0){
unset($checkInput[$k]);
}
$amount -= $remove;
if($amount === 0){
break;
}
}
}
}
if(count($checkInput) === 0){
$hasRecipe = $possibleRecipe;
break;
}
}
if($hasRecipe instanceof Recipe){
break;
private function cloneItemMap(array $map) : array{
/** @var Item[] $row */
foreach($map as $y => $row){
foreach($row as $x => $item){
$item = clone $item;
}
}
return $hasRecipe !== null;
return $map;
}
/**
* @param Item[][] $inputMap
* @param Item $primaryOutput
* @param Item[][] $extraOutputMap
*
* @return CraftingRecipe|null
*/
public function matchRecipe(array $inputMap, Item $primaryOutput, array $extraOutputMap) : ?CraftingRecipe{
//TODO: try to match special recipes before anything else (first they need to be implemented!)
$outputHash = json_encode($primaryOutput);
if(isset($this->shapedRecipes[$outputHash])){
$inputHash = json_encode($inputMap);
$recipe = $this->shapedRecipes[$outputHash][$inputHash] ?? null;
if($recipe !== null and $recipe->matchItems($this->cloneItemMap($inputMap), $this->cloneItemMap($extraOutputMap))){ //matched a recipe by hash
return $recipe;
}
foreach($this->shapedRecipes[$outputHash] as $recipe){
if($recipe->matchItems($this->cloneItemMap($inputMap), $this->cloneItemMap($extraOutputMap))){
return $recipe;
}
}
}
if(isset($this->shapelessRecipes[$outputHash])){
$list = array_merge(...$inputMap);
usort($list, [$this, "sort"]);
$inputHash = json_encode($list);
$recipe = $this->shapelessRecipes[$outputHash][$inputHash] ?? null;
if($recipe !== null and $recipe->matchItems($this->cloneItemMap($inputMap), $this->cloneItemMap($extraOutputMap))){
return $recipe;
}
foreach($this->shapelessRecipes[$outputHash] as $recipe){
if($recipe->matchItems($this->cloneItemMap($inputMap), $this->cloneItemMap($extraOutputMap))){
return $recipe;
}
}
}
return null;
}
/**
* @param Item $input
*
* @return FurnaceRecipe|null
*/
public function matchFurnaceRecipe(Item $input) : ?FurnaceRecipe{
return $this->furnaceRecipes[$input->getId() . ":" . $input->getDamage()] ?? $this->furnaceRecipes[$input->getId() . ":?"] ?? null;
}
/**
* @param Recipe $recipe
*/
public function registerRecipe(Recipe $recipe){
$recipe->setId(UUID::fromData((string) ++self::$RECIPE_COUNT, (string) $recipe->getResult()->getId(), (string) $recipe->getResult()->getDamage(), (string) $recipe->getResult()->getCount(), $recipe->getResult()->getCompoundTag()));
if($recipe instanceof ShapedRecipe){
$this->registerShapedRecipe($recipe);
}elseif($recipe instanceof ShapelessRecipe){
$this->registerShapelessRecipe($recipe);
}elseif($recipe instanceof FurnaceRecipe){
$this->registerFurnaceRecipe($recipe);
public function registerRecipe(Recipe $recipe) : void{
if($recipe instanceof CraftingRecipe){
$result = $recipe->getResult();
$recipe->setId($uuid = UUID::fromData((string) ++self::$RECIPE_COUNT, (string) $result->getId(), (string) $result->getDamage(), (string) $result->getCount(), $result->getCompoundTag()));
$this->recipes[$uuid->toBinary()] = $recipe;
}
$recipe->registerToCraftingManager($this);
}
}

View File

@ -0,0 +1,63 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\utils\UUID;
interface CraftingRecipe extends Recipe{
/**
* @return UUID|null
*/
public function getId() : ?UUID;
/**
* @param UUID $id
*/
public function setId(UUID $id);
public function requiresCraftingTable() : bool;
/**
* @return Item[]
*/
public function getExtraResults() : array;
/**
* @return Item[]
*/
public function getAllResults() : array;
/**
* Returns whether the specified list of crafting grid inputs and outputs matches this recipe. Outputs DO NOT
* include the primary result item.
*
* @param Item[][] $input 2D array of items taken from the crafting grid
* @param Item[][] $output 2D array of items put back into the crafting grid (secondary results)
*
* @return bool
*/
public function matchItems(array $input, array $output) : bool;
}

View File

@ -39,13 +39,24 @@ class DoubleChestInventory extends ChestInventory implements InventoryHolder{
$this->left = $left->getRealInventory();
$this->right = $right->getRealInventory();
$items = array_merge($this->left->getContents(), $this->right->getContents());
BaseInventory::__construct($this, InventoryType::get(InventoryType::DOUBLE_CHEST), $items);
BaseInventory::__construct($this, $items);
}
public function getName() : string{
return "Double Chest";
}
public function getDefaultSize() : int{
return $this->left->getDefaultSize() + $this->right->getDefaultSize();
}
public function getInventory(){
return $this;
}
/**
* @return Chest
*/
public function getHolder(){
return $this->left->getHolder();
}
@ -54,17 +65,17 @@ class DoubleChestInventory extends ChestInventory implements InventoryHolder{
return $index < $this->left->getSize() ? $this->left->getItem($index) : $this->right->getItem($index - $this->right->getSize());
}
public function setItem(int $index, Item $item) : bool{
return $index < $this->left->getSize() ? $this->left->setItem($index, $item) : $this->right->setItem($index - $this->right->getSize(), $item);
public function setItem(int $index, Item $item, bool $send = true) : bool{
return $index < $this->left->getSize() ? $this->left->setItem($index, $item, $send) : $this->right->setItem($index - $this->right->getSize(), $item, $send);
}
public function clear(int $index) : bool{
return $index < $this->left->getSize() ? $this->left->clear($index) : $this->right->clear($index - $this->right->getSize());
public function clear(int $index, bool $send = true) : bool{
return $index < $this->left->getSize() ? $this->left->clear($index, $send) : $this->right->clear($index - $this->right->getSize(), $send);
}
public function getContents() : array{
$contents = [];
for($i = 0; $i < $this->getSize(); ++$i){
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$contents[$i] = $this->getItem($i);
}
@ -73,29 +84,32 @@ class DoubleChestInventory extends ChestInventory implements InventoryHolder{
/**
* @param Item[] $items
* @param bool $send
*/
public function setContents(array $items){
if(count($items) > $this->size){
$items = array_slice($items, 0, $this->size, true);
public function setContents(array $items, bool $send = true) : void{
$size = $this->getSize();
if(count($items) > $size){
$items = array_slice($items, 0, $size, true);
}
$leftSize = $this->left->getSize();
for($i = 0; $i < $this->size; ++$i){
for($i = 0; $i < $size; ++$i){
if(!isset($items[$i])){
if($i < $this->left->size){
if(isset($this->left->slots[$i])){
$this->clear($i);
}
}elseif(isset($this->right->slots[$i - $this->left->size])){
$this->clear($i);
if(($i < $leftSize and isset($this->left->slots[$i])) or isset($this->right->slots[$i - $leftSize])){
$this->clear($i, false);
}
}elseif(!$this->setItem($i, $items[$i])){
$this->clear($i);
}elseif(!$this->setItem($i, $items[$i], false)){
$this->clear($i, false);
}
}
if($send){
$this->sendContents($this->getViewers());
}
}
public function onOpen(Player $who){
public function onOpen(Player $who) : void{
parent::onOpen($who);
if(count($this->getViewers()) === 1){
@ -111,7 +125,7 @@ class DoubleChestInventory extends ChestInventory implements InventoryHolder{
}
}
public function onClose(Player $who){
public function onClose(Player $who) : void{
if(count($this->getViewers()) === 1){
$pk = new BlockEventPacket();
$pk->x = $this->right->getHolder()->getX();

View File

@ -24,21 +24,39 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\level\Position;
use pocketmine\network\mcpe\protocol\types\WindowTypes;
use pocketmine\Player;
class EnchantInventory extends ContainerInventory{
/** @var FakeBlockMenu */
protected $holder;
public function __construct(Position $pos){
parent::__construct(new FakeBlockMenu($this, $pos), InventoryType::get(InventoryType::ENCHANT_TABLE));
parent::__construct(new FakeBlockMenu($this, $pos));
}
public function getNetworkType() : int{
return WindowTypes::ENCHANTMENT;
}
public function getName() : string{
return "Enchantment Table";
}
public function getDefaultSize() : int{
return 2; //1 input, 1 lapis
}
/**
* This override is here for documentation and code completion purposes only.
* @return FakeBlockMenu
*/
public function getHolder(){
return $this->holder;
}
public function onClose(Player $who){
public function onClose(Player $who) : void{
parent::onClose($who);
for($i = 0; $i < 2; ++$i){

View File

@ -0,0 +1,50 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\entity\Entity;
use pocketmine\event\entity\EntityInventoryChangeEvent;
use pocketmine\item\Item;
use pocketmine\Server;
abstract class EntityInventory extends BaseInventory{
/** @var Entity */
protected $holder;
protected function doSetItemEvents(int $index, Item $newItem) : ?Item{
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $this->getItem($index), $newItem, $index));
if($ev->isCancelled()){
return null;
}
return $ev->getNewItem();
}
/**
* @return Entity|InventoryHolder
*/
public function getHolder(){
return parent::getHolder();
}
}

View File

@ -24,14 +24,31 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\types\WindowTypes;
use pocketmine\tile\Furnace;
class FurnaceInventory extends ContainerInventory{
/** @var Furnace */
protected $holder;
public function __construct(Furnace $tile){
parent::__construct($tile, InventoryType::get(InventoryType::FURNACE));
parent::__construct($tile);
}
public function getNetworkType() : int{
return WindowTypes::FURNACE;
}
public function getName() : string{
return "Furnace";
}
public function getDefaultSize() : int{
return 3; //1 input, 1 fuel, 1 output
}
/**
* This override is here for documentation and code completion purposes only.
* @return Furnace
*/
public function getHolder(){
@ -86,8 +103,8 @@ class FurnaceInventory extends ContainerInventory{
return $this->setItem(0, $item);
}
public function onSlotChange($index, $before){
parent::onSlotChange($index, $before);
public function onSlotChange(int $index, Item $before, bool $send) : void{
parent::onSlotChange($index, $before, $send);
$this->getHolder()->scheduleUpdate();
}

View File

@ -24,14 +24,9 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\Server;
use pocketmine\utils\UUID;
class FurnaceRecipe implements Recipe{
/** @var UUID|null */
private $id = null;
/** @var Item */
private $output;
@ -47,24 +42,6 @@ class FurnaceRecipe implements Recipe{
$this->ingredient = clone $ingredient;
}
/**
* @return UUID|null
*/
public function getId(){
return $this->id;
}
/**
* @param UUID $id
*/
public function setId(UUID $id){
if($this->id !== null){
throw new \InvalidStateException("Id is already set");
}
$this->id = $id;
}
/**
* @param Item $item
*/
@ -86,7 +63,7 @@ class FurnaceRecipe implements Recipe{
return clone $this->output;
}
public function registerToCraftingManager(){
Server::getInstance()->getCraftingManager()->registerFurnaceRecipe($this);
public function registerToCraftingManager(CraftingManager $manager) : void{
$manager->registerFurnaceRecipe($this);
}
}

View File

@ -45,7 +45,7 @@ interface Inventory{
/**
* @param int $size
*/
public function setMaxStackSize(int $size);
public function setMaxStackSize(int $size) : void;
/**
* @return string
@ -70,10 +70,11 @@ interface Inventory{
*
* @param int $index
* @param Item $item
* @param bool $send
*
* @return bool
*/
public function setItem(int $index, Item $item) : bool;
public function setItem(int $index, Item $item, bool $send = true) : bool;
/**
* Stores the given Items in the inventory. This will try to fill
@ -113,19 +114,20 @@ interface Inventory{
/**
* @param Item[] $items
* @param bool $send
*/
public function setContents(array $items);
public function setContents(array $items, bool $send = true) : void;
/**
* @param Player|Player[] $target
*/
public function sendContents($target);
public function sendContents($target) : void;
/**
* @param int $index
* @param Player|Player[] $target
*/
public function sendSlot($index, $target);
public function sendSlot(int $index, $target) : void;
/**
* Checks if the inventory contains any Item with the same material data.
@ -148,14 +150,17 @@ interface Inventory{
public function all(Item $item) : array;
/**
* Will return the first slot has the same id and metadata (if not null) as the Item.
* -1 if not found, will check amount
* Returns the first slot number containing an item with the same ID, damage (if not any-damage), NBT (if not empty)
* and count >= to the count of the specified item stack.
*
* If $exact is true, only items with equal ID, damage, NBT and count will match.
*
* @param Item $item
* @param bool $exact
*
* @return int
*/
public function first(Item $item) : int;
public function first(Item $item, bool $exact = false) : int;
/**
* Returns the first empty slot, or -1 if not found
@ -169,21 +174,22 @@ interface Inventory{
*
* @param Item $item
*/
public function remove(Item $item);
public function remove(Item $item) : void;
/**
* Will clear a specific slot
*
* @param int $index
* @param int $index
* @param bool $send
*
* @return bool
*/
public function clear(int $index) : bool;
public function clear(int $index, bool $send = true) : bool;
/**
* Clears all the slots
*/
public function clearAll();
public function clearAll() : void;
/**
* Gets all the Players viewing the inventory
@ -193,11 +199,6 @@ interface Inventory{
*/
public function getViewers() : array;
/**
* @return InventoryType
*/
public function getType() : InventoryType;
/**
* @return InventoryHolder
*/
@ -206,7 +207,7 @@ interface Inventory{
/**
* @param Player $who
*/
public function onOpen(Player $who);
public function onOpen(Player $who) : void;
/**
* Tries to open the inventory to a player
@ -217,16 +218,17 @@ interface Inventory{
*/
public function open(Player $who) : bool;
public function close(Player $who);
public function close(Player $who) : void;
/**
* @param Player $who
*/
public function onClose(Player $who);
public function onClose(Player $who) : void;
/**
* @param int $index
* @param Item $before
* @param bool $send
*/
public function onSlotChange($index, $before);
public function onSlotChange(int $index, Item $before, bool $send) : void;
}

View File

@ -1,111 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
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;
const FURNACE = 3;
const CRAFTING = 4;
const WORKBENCH = 5;
const STONECUTTER = 6;
const BREWING_STAND = 7;
const ANVIL = 8;
const ENCHANT_TABLE = 9;
private static $default = [];
private $size;
private $title;
private $typeId;
/**
* @param $index
*
* @return InventoryType|null
*/
public static function get($index){
return static::$default[$index] ?? null;
}
public static function init(){
if(count(static::$default) > 0){
return;
}
//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
];
}
/**
* @param int $defaultSize
* @param string $defaultTitle
* @param int $typeId
*/
private function __construct($defaultSize, $defaultTitle, $typeId = 0){
$this->size = $defaultSize;
$this->title = $defaultTitle;
$this->typeId = $typeId;
}
/**
* @return int
*/
public function getDefaultSize() : int{
return $this->size;
}
/**
* @return string
*/
public function getDefaultTitle() : string{
return $this->title;
}
/**
* @return int
*/
public function getNetworkType() : int{
return $this->typeId;
}
}

View File

@ -23,41 +23,33 @@ declare(strict_types=1);
namespace pocketmine\inventory;
interface TransactionGroup{
use pocketmine\Player;
class PlayerCursorInventory extends BaseInventory{
/** @var Player */
protected $holder;
public function __construct(Player $holder){
parent::__construct($holder);
}
public function getName() : string{
return "Cursor";
}
public function getDefaultSize() : int{
return 1;
}
public function setSize(int $size){
throw new \BadMethodCallException("Cursor can only carry one item at a time");
}
/**
* @return float
* This override is here for documentation and code completion purposes only.
* @return Player
*/
public function getCreationTime() : float;
/**
* @return Transaction[]
*/
public function getTransactions() : array;
/**
* @return Inventory[]
*/
public function getInventories() : array;
/**
* @param Transaction $transaction
*/
public function addTransaction(Transaction $transaction);
/**
* @return bool
*/
public function canExecute() : bool;
/**
* @return bool
*/
public function execute() : bool;
/**
* @return bool
*/
public function hasExecuted() : bool;
public function getHolder(){
return $this->holder;
}
}

View File

@ -25,27 +25,38 @@ namespace pocketmine\inventory;
use pocketmine\entity\Human;
use pocketmine\event\entity\EntityArmorChangeEvent;
use pocketmine\event\entity\EntityInventoryChangeEvent;
use pocketmine\event\player\PlayerItemHeldEvent;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\Player;
use pocketmine\Server;
class PlayerInventory extends BaseInventory{
class PlayerInventory extends EntityInventory{
/** @var Human */
protected $holder;
/** @var int */
protected $itemInHandIndex = 0;
/** @var int[] */
protected $hotbar;
/**
* @param Human $player
*/
public function __construct(Human $player){
$this->resetHotbar(false);
parent::__construct($player, InventoryType::get(InventoryType::PLAYER));
parent::__construct($player);
}
public function getName() : string{
return "Player";
}
public function getDefaultSize() : int{
return 40; //36 inventory, 4 armor
}
public function getSize() : int{
@ -61,131 +72,90 @@ class PlayerInventory extends BaseInventory{
* 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.
* @param int $hotbarSlot Number of the hotbar slot to equip.
*
* @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($hotbarSlot);
}
if($hotbarSlot < 0 or $hotbarSlot >= $this->getHotbarSize() or $inventorySlot < -1 or $inventorySlot >= $this->getSize()){
public function equipItem(int $hotbarSlot) : bool{
if(!$this->isHotbarSlot($hotbarSlot)){
$this->sendContents($this->getHolder());
return false;
}
if($inventorySlot === -1){
$item = ItemFactory::get(Item::AIR, 0, 0);
}else{
$item = $this->getItem($inventorySlot);
}
$this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $item, $inventorySlot, $hotbarSlot));
$this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($hotbarSlot), $hotbarSlot));
if($ev->isCancelled()){
$this->sendContents($this->getHolder());
$this->sendHeldItem($this->getHolder());
return false;
}
$this->setHotbarSlotIndex($hotbarSlot, $inventorySlot);
$this->setHeldItemIndex($hotbarSlot, false);
return true;
}
/**
* 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;
private function isHotbarSlot(int $slot) : bool{
return $slot >= 0 and $slot <= $this->getHotbarSize();
}
/**
* 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 $slot
* @throws \InvalidArgumentException
*/
private function throwIfNotHotbarSlot(int $slot){
if(!$this->isHotbarSlot($slot)){
throw new \InvalidArgumentException("$slot is not a valid hotbar slot index (expected 0 - " . ($this->getHotbarSize() - 1) . ")");
}
}
/**
* Returns the item in the specified hotbar slot.
*
* @param int $hotbarSlot
* @param int $inventorySlot
*/
public function setHotbarSlotIndex($hotbarSlot, $inventorySlot){
if($hotbarSlot < 0 or $hotbarSlot >= $this->getHotbarSize()){
throw new \InvalidArgumentException("Hotbar slot index \"$hotbarSlot\" is out of range");
}elseif($inventorySlot < -1 or $inventorySlot >= $this->getSize()){
throw new \InvalidArgumentException("Inventory slot index \"$inventorySlot\" is out of range");
}
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;
}
/**
* Returns the item in the slot linked to the specified hotbar slot, or Air if the slot is not linked to any hotbar slot.
* @param int $hotbarSlotIndex
*
* @return Item
*
* @throws \InvalidArgumentException if the hotbar slot index is out of range
*/
public function getHotbarSlotItem(int $hotbarSlotIndex) : Item{
$inventorySlot = $this->getHotbarSlotIndex($hotbarSlotIndex);
if($inventorySlot !== -1){
return $this->getItem($inventorySlot);
}else{
return ItemFactory::get(Item::AIR, 0, 0);
}
public function getHotbarSlotItem(int $hotbarSlot) : Item{
$this->throwIfNotHotbarSlot($hotbarSlot);
return $this->getItem($hotbarSlot);
}
/**
* Resets hotbar links to their original defaults.
* @param bool $send Whether to send changes to the holder.
* @deprecated
* @return int
*/
public function resetHotbar(bool $send = true){
$this->hotbar = range(0, $this->getHotbarSize() - 1, 1);
if($send){
$this->sendContents($this->getHolder());
}
public function getHeldItemSlot() : int{
return $this->getHeldItemIndex();
}
/**
* Returns the hotbar slot number the holder is currently holding.
* @return int
*/
public function getHeldItemIndex(){
public function getHeldItemIndex() : int{
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.
* @param int $hotbarSlot 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.
*
* @throws \InvalidArgumentException if the hotbar slot is out of range
*/
public function setHeldItemIndex($index, $send = true){
if($index >= 0 and $index < $this->getHotbarSize()){
$this->itemInHandIndex = $index;
public function setHeldItemIndex(int $hotbarSlot, bool $send = true){
$this->throwIfNotHotbarSlot($hotbarSlot);
if($this->getHolder() instanceof Player and $send){
$this->sendHeldItem($this->getHolder());
}
$this->itemInHandIndex = $hotbarSlot;
$this->sendHeldItem($this->getHolder()->getViewers());
}else{
throw new \InvalidArgumentException("Hotbar slot index \"$index\" is out of range");
if($this->getHolder() instanceof Player and $send){
$this->sendHeldItem($this->getHolder());
}
$this->sendHeldItem($this->getHolder()->getViewers());
}
/**
@ -193,39 +163,19 @@ class PlayerInventory extends BaseInventory{
*
* @return Item
*/
public function getItemInHand(){
public function getItemInHand() : Item{
return $this->getHotbarSlotItem($this->itemInHandIndex);
}
/**
* Sets the item in the currently-held slot to the specified item.
*
* @param Item $item
*
* @return bool
*/
public function setItemInHand(Item $item){
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()){
$this->setHotbarSlotIndex($this->getHeldItemIndex(), $slot);
}
public function setItemInHand(Item $item) : bool{
return $this->setItem($this->getHeldItemIndex(), $item);
}
/**
@ -238,24 +188,23 @@ class PlayerInventory extends BaseInventory{
$pk = new MobEquipmentPacket();
$pk->entityRuntimeId = $this->getHolder()->getId();
$pk->item = $item;
$pk->inventorySlot = $this->getHeldItemSlot();
$pk->hotbarSlot = $this->getHeldItemIndex();
$pk->inventorySlot = $pk->hotbarSlot = $this->getHeldItemIndex();
$pk->windowId = ContainerIds::INVENTORY;
if(!is_array($target)){
$target->dataPacket($pk);
if($this->getHeldItemSlot() !== -1 and $target === $this->getHolder()){
$this->sendSlot($this->getHeldItemSlot(), $target);
if($target === $this->getHolder()){
$this->sendSlot($this->getHeldItemIndex(), $target);
}
}else{
$this->getHolder()->getLevel()->getServer()->broadcastPacket($target, $pk);
if($this->getHeldItemSlot() !== -1 and in_array($this->getHolder(), $target, true)){
$this->sendSlot($this->getHeldItemSlot(), $this->getHolder());
if(in_array($this->getHolder(), $target, true)){
$this->sendSlot($this->getHeldItemIndex(), $this->getHolder());
}
}
}
public function onSlotChange($index, $before){
public function onSlotChange(int $index, Item $before, bool $send) : void{
$holder = $this->getHolder();
if($holder instanceof Player and !$holder->spawned){
return;
@ -266,7 +215,7 @@ class PlayerInventory extends BaseInventory{
$this->sendArmorSlot($index, $this->getHolder()->getViewers());
}else{
//Do not send armor by accident here.
parent::onSlotChange($index, $before);
parent::onSlotChange($index, $before, $send);
}
}
@ -274,124 +223,77 @@ class PlayerInventory extends BaseInventory{
* Returns the number of slots in the hotbar.
* @return int
*/
public function getHotbarSize(){
public function getHotbarSize() : int{
return 9;
}
public function getArmorItem($index){
public function getArmorItem(int $index) : Item{
return $this->getItem($this->getSize() + $index);
}
public function setArmorItem($index, Item $item){
public function setArmorItem(int $index, Item $item) : bool{
return $this->setItem($this->getSize() + $index, $item);
}
public function getHelmet(){
public function getHelmet() : Item{
return $this->getItem($this->getSize());
}
public function getChestplate(){
public function getChestplate() : Item{
return $this->getItem($this->getSize() + 1);
}
public function getLeggings(){
public function getLeggings() : Item{
return $this->getItem($this->getSize() + 2);
}
public function getBoots(){
public function getBoots() : Item{
return $this->getItem($this->getSize() + 3);
}
public function setHelmet(Item $helmet){
public function setHelmet(Item $helmet) : bool{
return $this->setItem($this->getSize(), $helmet);
}
public function setChestplate(Item $chestplate){
public function setChestplate(Item $chestplate) : bool{
return $this->setItem($this->getSize() + 1, $chestplate);
}
public function setLeggings(Item $leggings){
public function setLeggings(Item $leggings) : bool{
return $this->setItem($this->getSize() + 2, $leggings);
}
public function setBoots(Item $boots){
public function setBoots(Item $boots) : bool{
return $this->setItem($this->getSize() + 3, $boots);
}
public function setItem(int $index, Item $item) : bool{
if($index < 0 or $index >= $this->size){
return false;
}elseif($item->getId() === 0 or $item->getCount() <= 0){
return $this->clear($index);
}
if($index >= $this->getSize()){ //Armor change
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $this->getItem($index), $item, $index));
if($ev->isCancelled() and $this->getHolder() instanceof Human){
$this->sendArmorSlot($index, $this->getViewers());
return false;
}
$item = $ev->getNewItem();
}else{
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $this->getItem($index), $item, $index));
protected function doSetItemEvents(int $index, Item $newItem) : ?Item{
if($index >= $this->getSize()){
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $this->getItem($index), $newItem, $index));
if($ev->isCancelled()){
$this->sendSlot($index, $this->getViewers());
return false;
return null;
}
$item = $ev->getNewItem();
return $ev->getNewItem();
}
$old = $this->getItem($index);
$this->slots[$index] = clone $item;
$this->onSlotChange($index, $old);
return true;
return parent::doSetItemEvents($index, $newItem);
}
public function clear(int $index) : bool{
if(isset($this->slots[$index])){
$item = ItemFactory::get(Item::AIR, 0, 0);
$old = $this->slots[$index];
if($index >= $this->getSize() and $index < $this->size){ //Armor change
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $old, $item, $index));
if($ev->isCancelled()){
if($index >= $this->size){
$this->sendArmorSlot($index, $this->getViewers());
}else{
$this->sendSlot($index, $this->getViewers());
}
return false;
}
$item = $ev->getNewItem();
}else{
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $old, $item, $index));
if($ev->isCancelled()){
if($index >= $this->size){
$this->sendArmorSlot($index, $this->getViewers());
}else{
$this->sendSlot($index, $this->getViewers());
}
return false;
}
$item = $ev->getNewItem();
}
if($item->getId() !== Item::AIR){
$this->slots[$index] = clone $item;
}else{
unset($this->slots[$index]);
}
public function clearAll() : void{
parent::clearAll();
$this->onSlotChange($index, $old);
for($i = $this->getSize(), $m = $i + 4; $i < $m; ++$i){
$this->clear($i, false);
}
return true;
$this->sendArmorContents($this->getViewers());
}
/**
* @return Item[]
*/
public function getArmorContents(){
public function getArmorContents() : array{
$armor = [];
for($i = 0; $i < 4; ++$i){
@ -401,13 +303,6 @@ class PlayerInventory extends BaseInventory{
return $armor;
}
public function clearAll(){
$limit = $this->getSize() + 4;
for($index = 0; $index < $limit; ++$index){
$this->clear($index);
}
}
/**
* @param Player|Player[] $target
*/
@ -425,10 +320,9 @@ class PlayerInventory extends BaseInventory{
foreach($target as $player){
if($player === $this->getHolder()){
$pk2 = new ContainerSetContentPacket();
$pk2->windowid = ContainerIds::ARMOR;
$pk2->slots = $armor;
$pk2->targetEid = $player->getId();
$pk2 = new InventoryContentPacket();
$pk2->windowId = ContainerIds::ARMOR;
$pk2->items = $armor;
$player->dataPacket($pk2);
}else{
$player->dataPacket($pk);
@ -445,12 +339,10 @@ class PlayerInventory extends BaseInventory{
$items[$i] = ItemFactory::get(Item::AIR, 0, 0);
}
if($items[$i]->getId() === Item::AIR){
$this->clear($this->getSize() + $i);
}else{
$this->setItem($this->getSize() + $i, $items[$i]);
}
$this->setItem($this->getSize() + $i, $items[$i], false);
}
$this->sendArmorContents($this->getViewers());
}
@ -458,7 +350,7 @@ class PlayerInventory extends BaseInventory{
* @param int $index
* @param Player|Player[] $target
*/
public function sendArmorSlot($index, $target){
public function sendArmorSlot(int $index, $target){
if($target instanceof Player){
$target = [$target];
}
@ -473,9 +365,10 @@ class PlayerInventory extends BaseInventory{
foreach($target as $player){
if($player === $this->getHolder()){
/** @var Player $player */
$pk2 = new ContainerSetSlotPacket();
$pk2->windowid = ContainerIds::ARMOR;
$pk2->slot = $index - $this->getSize();
$pk2 = new InventorySlotPacket();
$pk2->windowId = ContainerIds::ARMOR;
$pk2->inventorySlot = $index - $this->getSize();
$pk2->item = $this->getItem($index);
$player->dataPacket($pk2);
}else{
@ -484,90 +377,25 @@ class PlayerInventory extends BaseInventory{
}
}
/**
* @param Player|Player[] $target
*/
public function sendContents($target){
if($target instanceof Player){
$target = [$target];
}
$pk = new ContainerSetContentPacket();
$pk->slots = [];
for($i = 0; $i < $this->getSize(); ++$i){ //Do not send armor by error here
$pk->slots[$i] = $this->getItem($i);
}
//Because PE is stupid and shows 9 less slots than you send it, give it 9 dummy slots so it shows all the REAL slots.
for($i = $this->getSize(); $i < $this->getSize() + $this->getHotbarSize(); ++$i){
$pk->slots[$i] = ItemFactory::get(Item::AIR, 0, 0);
}
foreach($target as $player){
$pk->hotbar = [];
if($player === $this->getHolder()){
for($i = 0; $i < $this->getHotbarSize(); ++$i){
$index = $this->getHotbarSlotIndex($i);
$pk->hotbar[] = $index <= -1 ? -1 : $index + $this->getHotbarSize();
}
}
if(($id = $player->getWindowId($this)) === -1 or $player->spawned !== true){
$this->close($player);
continue;
}
$pk->windowid = $id;
$pk->targetEid = $player->getId(); //TODO: check if this is correct
$player->dataPacket(clone $pk);
}
}
public function sendCreativeContents(){
$pk = new ContainerSetContentPacket();
$pk->windowid = ContainerIds::CREATIVE;
if($this->getHolder()->getGamemode() === Player::CREATIVE){
$pk = new InventoryContentPacket();
$pk->windowId = ContainerIds::CREATIVE;
if(!$this->getHolder()->isSpectator()){ //fill it for all gamemodes except spectator
foreach(Item::getCreativeItems() as $i => $item){
$pk->slots[$i] = clone $item;
$pk->items[$i] = clone $item;
}
}
$pk->targetEid = $this->getHolder()->getId();
$this->getHolder()->dataPacket($pk);
}
/**
* @param int $index
* @param Player|Player[] $target
*/
public function sendSlot($index, $target){
if($target instanceof Player){
$target = [$target];
}
$pk = new ContainerSetSlotPacket();
$pk->slot = $index;
$pk->item = clone $this->getItem($index);
foreach($target as $player){
if($player === $this->getHolder()){
/** @var Player $player */
$pk->windowid = 0;
$player->dataPacket(clone $pk);
}else{
if(($id = $player->getWindowId($this)) === -1){
$this->close($player);
continue;
}
$pk->windowid = $id;
$player->dataPacket(clone $pk);
}
}
}
/**
* This override is here for documentation and code completion purposes only.
* @return Human|Player
*/
public function getHolder(){
return parent::getHolder();
return $this->holder;
}
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\utils\UUID;
interface Recipe{
@ -33,15 +32,5 @@ interface Recipe{
*/
public function getResult() : Item;
public function registerToCraftingManager();
/**
* @return UUID|null
*/
public function getId();
/**
* @param UUID $id
*/
public function setId(UUID $id);
public function registerToCraftingManager(CraftingManager $manager) : void;
}

View File

@ -25,62 +25,111 @@ namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\math\Vector2;
use pocketmine\Server;
use pocketmine\utils\UUID;
class ShapedRecipe implements Recipe{
class ShapedRecipe implements CraftingRecipe{
/** @var Item */
private $output;
private $primaryResult;
/** @var Item[] */
private $extraResults = [];
/** @var UUID|null */
private $id = null;
/** @var string[] */
private $shape = [];
/** @var Item[][] */
private $ingredients = [];
/** @var Vector2[][] */
private $shapeItems = [];
/** @var Item[] char => Item map */
private $ingredientList = [];
/**
* @param Item $result
* @param int $height
* @param int $width
* Constructs a ShapedRecipe instance.
*
* @throws \Exception
* @param Item $primaryResult
* @param string[] $shape<br>
* Array of 1, 2, or 3 strings representing the rows of the recipe.
* This accepts an array of 1, 2 or 3 strings. Each string should be of the same length and must be at most 3
* characters long. Each character represents a unique type of ingredient. Spaces are interpreted as air.
* @param Item[] $ingredients<br>
* Char => Item map of items to be set into the shape.
* This accepts an array of Items, indexed by character. Every unique character (except space) in the shape
* array MUST have a corresponding item in this list. Space character is automatically treated as air.
* @param Item[] $extraResults<br>
* List of additional result items to leave in the crafting grid afterwards. Used for things like cake recipe
* empty buckets.
*
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
*/
public function __construct(Item $result, int $height, int $width){
for($h = 0; $h < $height; $h++){
if($width === 0 or $width > 3){
throw new \InvalidStateException("Crafting rows should be 1, 2, 3 wide, not $width");
}
$this->ingredients[] = array_fill(0, $width, null);
public function __construct(Item $primaryResult, array $shape, array $ingredients, array $extraResults = []){
$rowCount = count($shape);
if($rowCount > 3 or $rowCount <= 0){
throw new \InvalidArgumentException("Shaped recipes may only have 1, 2 or 3 rows, not $rowCount");
}
$this->output = clone $result;
$shape = array_values($shape);
$columnCount = strlen($shape[0]);
if($columnCount > 3 or $rowCount <= 0){
throw new \InvalidArgumentException("Shaped recipes may only have 1, 2 or 3 columns, not $columnCount");
}
foreach($shape as $y => $row){
if(strlen($row) !== $columnCount){
throw new \InvalidArgumentException("Shaped recipe rows must all have the same length (expected $columnCount, got " . strlen($row) . ")");
}
for($x = 0; $x < $columnCount; ++$x){
if($row{$x} !== ' ' and !isset($ingredients[$row{$x}])){
throw new \InvalidArgumentException("No item specified for symbol '" . $row{$x} . "'");
}
}
}
$this->primaryResult = clone $primaryResult;
foreach($extraResults as $item){
$this->extraResults[] = clone $item;
}
$this->shape = $shape;
foreach($ingredients as $char => $i){
$this->setIngredient($char, $i);
}
}
public function getWidth() : int{
return count($this->ingredients[0]);
return strlen($this->shape[0]);
}
public function getHeight() : int{
return count($this->ingredients);
return count($this->shape);
}
/**
* @return Item
*/
public function getResult() : Item{
return $this->output;
return $this->primaryResult;
}
/**
* @return Item[]
*/
public function getExtraResults() : array{
return $this->extraResults;
}
/**
* @return Item[]
*/
public function getAllResults() : array{
$results = $this->extraResults;
array_unshift($results, $this->primaryResult);
return $results;
}
/**
* @return UUID|null
*/
public function getId(){
public function getId() : ?UUID{
return $this->id;
}
@ -92,58 +141,32 @@ class ShapedRecipe implements Recipe{
$this->id = $id;
}
/**
* @param int $x
* @param int $y
* @param Item $item
*
* @return $this
*/
public function addIngredient(int $x, int $y, Item $item){
$this->ingredients[$y][$x] = clone $item;
return $this;
}
/**
* @param string $key
* @param Item $item
*
* @return $this
* @throws \Exception
* @throws \InvalidArgumentException
*/
public function setIngredient(string $key, Item $item){
if(!array_key_exists($key, $this->shape)){
throw new \Exception("Symbol does not appear in the shape: " . $key);
if(strpos(implode($this->shape), $key) === false){
throw new \InvalidArgumentException("Symbol '$key' does not appear in the recipe shape");
}
$this->fixRecipe($key, $item);
$this->ingredientList[$key] = clone $item;
return $this;
}
/**
* @param string $key
* @param Item $item
*/
protected function fixRecipe(string $key, Item $item){
foreach($this->shapeItems[$key] as $entry){
$this->ingredients[$entry->y][$entry->x] = clone $item;
}
}
/**
* @return Item[][]
*/
public function getIngredientMap() : array{
$ingredients = [];
foreach($this->ingredients as $y => $row){
$ingredients[$y] = [];
foreach($row as $x => $ingredient){
if($ingredient !== null){
$ingredients[$y][$x] = clone $ingredient;
}else{
$ingredients[$y][$x] = ItemFactory::get(Item::AIR);
}
for($y = 0, $y2 = $this->getHeight(); $y < $y2; ++$y){
for($x = 0, $x2 = $this->getWidth(); $x < $x2; ++$x){
$ingredients[$y][$x] = $this->getIngredient($x, $y);
}
}
@ -157,17 +180,82 @@ class ShapedRecipe implements Recipe{
* @return Item
*/
public function getIngredient(int $x, int $y) : Item{
return $this->ingredients[$y][$x] ?? ItemFactory::get(Item::AIR);
$exists = $this->ingredientList[$this->shape[$y]{$x}] ?? null;
return $exists !== null ? clone $exists : ItemFactory::get(Item::AIR, 0, 0);
}
/**
* Returns an array of strings containing characters representing the recipe's shape.
* @return string[]
*/
public function getShape() : array{
return $this->shape;
}
public function registerToCraftingManager(){
Server::getInstance()->getCraftingManager()->registerShapedRecipe($this);
public function registerToCraftingManager(CraftingManager $manager) : void{
$manager->registerShapedRecipe($this);
}
public function requiresCraftingTable() : bool{
return $this->getHeight() > 2 or $this->getWidth() > 2;
}
/**
* @param Item[][] $input
* @param Item[][] $output
*
* @return bool
*/
public function matchItems(array $input, array $output) : bool{
$map = $this->getIngredientMap();
//match the given items to the requested items
for($y = 0, $y2 = $this->getHeight(); $y < $y2; ++$y){
for($x = 0, $x2 = $this->getWidth(); $x < $x2; ++$x){
$given = $input[$y][$x] ?? null;
$required = $map[$y][$x];
if($given === null or !$required->equals($given, !$required->hasAnyDamageValue(), $required->hasCompoundTag()) or $required->getCount() !== $given->getCount()){
return false;
}
unset($input[$y][$x]);
}
}
//we shouldn't need to check if there's anything left in the map, the last block should take care of that
//however, we DO need to check if there are too many items in the grid outside of the recipe
/**
* @var Item[] $row
*/
foreach($input as $y => $row){
foreach($row as $x => $needItem){
if(!$needItem->isNull()){
return false; //too many input ingredients
}
}
}
//and then, finally, check that the output items are good:
/** @var Item[] $haveItems */
$haveItems = array_merge(...$output);
$needItems = $this->getExtraResults();
foreach($haveItems as $j => $haveItem){
if($haveItem->isNull()){
unset($haveItems[$j]);
continue;
}
foreach($needItems as $i => $needItem){
if($needItem->equals($haveItem, !$needItem->hasAnyDamageValue(), $needItem->hasCompoundTag()) and $needItem->getCount() === $haveItem->getCount()){
unset($haveItems[$j], $needItems[$i]);
break;
}
}
}
return count($haveItems) === 0 and count($needItems) === 0;
}
}

View File

@ -24,10 +24,9 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\Server;
use pocketmine\utils\UUID;
class ShapelessRecipe implements Recipe{
class ShapelessRecipe implements CraftingRecipe{
/** @var Item */
private $output;
@ -44,7 +43,7 @@ class ShapelessRecipe implements Recipe{
/**
* @return UUID|null
*/
public function getId(){
public function getId() : ?UUID{
return $this->id;
}
@ -63,6 +62,14 @@ class ShapelessRecipe implements Recipe{
return clone $this->output;
}
public function getExtraResults() : array{
return []; //TODO
}
public function getAllResults() : array{
return [$this->getResult()]; //TODO
}
/**
* @param Item $item
*
@ -129,7 +136,62 @@ class ShapelessRecipe implements Recipe{
return $count;
}
public function registerToCraftingManager(){
Server::getInstance()->getCraftingManager()->registerShapelessRecipe($this);
public function registerToCraftingManager(CraftingManager $manager) : void{
$manager->registerShapelessRecipe($this);
}
public function requiresCraftingTable() : bool{
return count($this->ingredients) > 4;
}
/**
* @param Item[][] $input
* @param Item[][] $output
*
* @return bool
*/
public function matchItems(array $input, array $output) : bool{
/** @var Item[] $haveInputs */
$haveInputs = array_merge(...$input); //we don't care how the items were arranged
$needInputs = $this->getIngredientList();
if(!$this->matchItemList($haveInputs, $needInputs)){
return false;
}
/** @var Item[] $haveOutputs */
$haveOutputs = array_merge(...$output);
$needOutputs = $this->getExtraResults();
if(!$this->matchItemList($haveOutputs, $needOutputs)){
return false;
}
return true;
}
/**
* @param Item[] $haveItems
* @param Item[] $needItems
*
* @return bool
*/
private function matchItemList(array $haveItems, array $needItems) : bool{
foreach($haveItems as $j => $haveItem){
if($haveItem->isNull()){
unset($haveItems[$j]);
continue;
}
foreach($needItems as $i => $needItem){
if($needItem->equals($haveItem, !$needItem->hasAnyDamageValue(), $needItem->hasCompoundTag()) and $needItem->getCount() === $haveItem->getCount()){
unset($haveItems[$j], $needItems[$i]);
break;
}
}
}
return count($haveItems) === 0 and count($needItems) === 0;
}
}

View File

@ -1,177 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\event\inventory\InventoryTransactionEvent;
use pocketmine\item\Item;
use pocketmine\Player;
use pocketmine\Server;
/**
* This TransactionGroup only allows doing Transaction between one / two inventories
*/
class SimpleTransactionGroup implements TransactionGroup{
/** @var float */
private $creationTime;
protected $hasExecuted = false;
/** @var Player */
protected $source = null;
/** @var Inventory[] */
protected $inventories = [];
/** @var Transaction[] */
protected $transactions = [];
/**
* @param Player $source
*/
public function __construct(Player $source = null){
$this->creationTime = microtime(true);
$this->source = $source;
}
/**
* @return Player
*/
public function getSource() : Player{
return $this->source;
}
public function getCreationTime() : float{
return $this->creationTime;
}
/**
* @return Inventory[]
*/
public function getInventories() : array{
return $this->inventories;
}
/**
* @return Transaction[]
*/
public function getTransactions() : array{
return $this->transactions;
}
public function addTransaction(Transaction $transaction){
if(isset($this->transactions[spl_object_hash($transaction)])){
return;
}
foreach($this->transactions as $hash => $tx){
if($tx->getInventory() === $transaction->getInventory() and $tx->getSlot() === $transaction->getSlot()){
if($transaction->getCreationTime() >= $tx->getCreationTime()){
unset($this->transactions[$hash]);
}else{
return;
}
}
}
$this->transactions[spl_object_hash($transaction)] = $transaction;
$this->inventories[spl_object_hash($transaction->getInventory())] = $transaction->getInventory();
}
/**
* @param Item[] $needItems
* @param Item[] $haveItems
*
* @return bool
*/
protected function matchItems(array &$needItems, array &$haveItems) : bool{
foreach($this->transactions as $key => $ts){
if($ts->getTargetItem()->getId() !== Item::AIR){
$needItems[] = $ts->getTargetItem();
}
$checkSourceItem = $ts->getInventory()->getItem($ts->getSlot());
$sourceItem = $ts->getSourceItem();
if(!$checkSourceItem->equals($sourceItem) or $sourceItem->getCount() !== $checkSourceItem->getCount()){
return false;
}
if($sourceItem->getId() !== Item::AIR){
$haveItems[] = $sourceItem;
}
}
foreach($needItems as $i => $needItem){
foreach($haveItems as $j => $haveItem){
if($needItem->equals($haveItem)){
$amount = min($needItem->getCount(), $haveItem->getCount());
$needItem->setCount($needItem->getCount() - $amount);
$haveItem->setCount($haveItem->getCount() - $amount);
if($haveItem->getCount() === 0){
unset($haveItems[$j]);
}
if($needItem->getCount() === 0){
unset($needItems[$i]);
break;
}
}
}
}
return true;
}
public function canExecute() : bool{
$haveItems = [];
$needItems = [];
return $this->matchItems($needItems, $haveItems) and count($this->transactions) > 0 and ((count($haveItems) === 0 and count($needItems) === 0) or $this->source->isCreative(true));
}
/**
* @return bool
*/
public function execute() : bool{
if($this->hasExecuted() or !$this->canExecute()){
return false;
}
Server::getInstance()->getPluginManager()->callEvent($ev = new InventoryTransactionEvent($this));
if($ev->isCancelled()){
foreach($this->inventories as $inventory){
if($inventory instanceof PlayerInventory){
$inventory->sendArmorContents($this->getSource());
}
$inventory->sendContents($this->getSource());
}
return false;
}
foreach($this->transactions as $transaction){
$transaction->getInventory()->setItem($transaction->getSlot(), $transaction->getTargetItem());
}
$this->hasExecuted = true;
return true;
}
public function hasExecuted() : bool{
return $this->hasExecuted;
}
}

View File

@ -0,0 +1,185 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\transaction;
use pocketmine\event\inventory\CraftItemEvent;
use pocketmine\inventory\BigCraftingGrid;
use pocketmine\inventory\CraftingRecipe;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\Player;
class CraftingTransaction extends InventoryTransaction{
protected $gridSize;
/** @var Item[][] */
protected $inputs;
/** @var Item[][] */
protected $secondaryOutputs;
/** @var Item|null */
protected $primaryOutput;
/** @var CraftingRecipe|null */
protected $recipe = null;
public function __construct(Player $source, $actions = []){
$this->gridSize = ($source->getCraftingGrid() instanceof BigCraftingGrid) ? 3 : 2;
$air = ItemFactory::get(Item::AIR, 0, 0);
$this->inputs = array_fill(0, $this->gridSize, array_fill(0, $this->gridSize, $air));
$this->secondaryOutputs = array_fill(0, $this->gridSize, array_fill(0, $this->gridSize, $air));
parent::__construct($source, $actions);
}
public function setInput(int $index, Item $item) : void{
$y = (int) ($index / $this->gridSize);
$x = $index % $this->gridSize;
if($this->inputs[$y][$x]->isNull()){
$this->inputs[$y][$x] = clone $item;
}else{
throw new \RuntimeException("Input $index has already been set");
}
}
public function getInputMap() : array{
return $this->inputs;
}
public function setExtraOutput(int $index, Item $item) : void{
$y = (int) ($index / $this->gridSize);
$x = $index % $this->gridSize;
if($this->secondaryOutputs[$y][$x]->isNull()){
$this->secondaryOutputs[$y][$x] = clone $item;
}else{
throw new \RuntimeException("Output $index has already been set");
}
}
public function getPrimaryOutput() : ?Item{
return $this->primaryOutput;
}
public function setPrimaryOutput(Item $item) : void{
if($this->primaryOutput === null){
$this->primaryOutput = clone $item;
}else{
throw new \RuntimeException("Primary result item has already been set");
}
}
public function getRecipe() : ?CraftingRecipe{
return $this->recipe;
}
private function reindexInputs() : array{
$xOffset = $this->gridSize;
$yOffset = $this->gridSize;
$height = 0;
$width = 0;
foreach($this->inputs as $y => $row){
foreach($row as $x => $item){
if(!$item->isNull()){
$xOffset = min($x, $xOffset);
$yOffset = min($y, $yOffset);
$height = max($y + 1 - $yOffset, $height);
$width = max($x + 1 - $xOffset, $width);
}
}
}
if($height === 0 or $width === 0){
return [];
}
$air = ItemFactory::get(Item::AIR, 0, 0);
$reindexed = array_fill(0, $height, array_fill(0, $width, $air));
foreach($reindexed as $y => $row){
foreach($row as $x => $item){
$reindexed[$y][$x] = $this->inputs[$y + $yOffset][$x + $xOffset];
}
}
return $reindexed;
}
public function canExecute() : bool{
$inputs = $this->reindexInputs();
$this->recipe = $this->source->getServer()->getCraftingManager()->matchRecipe($inputs, $this->primaryOutput, $this->secondaryOutputs);
return $this->recipe !== null and parent::canExecute();
}
protected function callExecuteEvent() : bool{
$this->source->getServer()->getPluginManager()->callEvent($ev = new CraftItemEvent($this));
return !$ev->isCancelled();
}
public function execute() : bool{
if(parent::execute()){
switch($this->primaryOutput->getId()){
case Item::CRAFTING_TABLE:
$this->source->awardAchievement("buildWorkBench");
break;
case Item::WOODEN_PICKAXE:
$this->source->awardAchievement("buildPickaxe");
break;
case Item::FURNACE:
$this->source->awardAchievement("buildFurnace");
break;
case Item::WOODEN_HOE:
$this->source->awardAchievement("buildHoe");
break;
case Item::BREAD:
$this->source->awardAchievement("makeBread");
break;
case Item::CAKE:
$this->source->awardAchievement("bakeCake");
break;
case Item::STONE_PICKAXE:
case Item::GOLDEN_PICKAXE:
case Item::IRON_PICKAXE:
case Item::DIAMOND_PICKAXE:
$this->source->awardAchievement("buildBetterPickaxe");
break;
case Item::WOODEN_SWORD:
$this->source->awardAchievement("buildSword");
break;
case Item::DIAMOND:
$this->source->awardAchievement("diamond");
break;
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,288 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\transaction;
use pocketmine\event\inventory\InventoryTransactionEvent;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\Player;
use pocketmine\Server;
use pocketmine\utils\MainLogger;
/**
* This InventoryTransaction only allows doing Transaction between one / two inventories
*/
class InventoryTransaction{
/** @var float */
private $creationTime;
protected $hasExecuted = false;
/** @var Player */
protected $source;
/** @var Inventory[] */
protected $inventories = [];
/** @var InventoryAction[] */
protected $actions = [];
/**
* @param Player $source
* @param InventoryAction[] $actions
*/
public function __construct(Player $source, array $actions = []){
$this->creationTime = microtime(true);
$this->source = $source;
foreach($actions as $action){
$this->addAction($action);
}
}
/**
* @return Player
*/
public function getSource() : Player{
return $this->source;
}
public function getCreationTime() : float{
return $this->creationTime;
}
/**
* @return Inventory[]
*/
public function getInventories() : array{
return $this->inventories;
}
/**
* @return InventoryAction[]
*/
public function getActions() : array{
return $this->actions;
}
/**
* @param InventoryAction $action
*/
public function addAction(InventoryAction $action) : void{
if(!isset($this->actions[$hash = spl_object_hash($action)])){
$this->actions[spl_object_hash($action)] = $action;
$action->onAddToTransaction($this);
}else{
throw new \InvalidArgumentException("Tried to add the same action to a transaction twice");
}
}
/**
* @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
* involving inventories.
*
* @param Inventory $inventory
*/
public function addInventory(Inventory $inventory) : void{
if(!isset($this->inventories[$hash = spl_object_hash($inventory)])){
$this->inventories[$hash] = $inventory;
}
}
/**
* @param Item[] $needItems
* @param Item[] $haveItems
*
* @return bool
*/
protected function matchItems(array &$needItems, array &$haveItems) : bool{
foreach($this->actions as $key => $action){
if(!$action->getTargetItem()->isNull()){
$needItems[] = $action->getTargetItem();
}
if(!$action->isValid($this->source)){
return false;
}
if(!$action->getSourceItem()->isNull()){
$haveItems[] = $action->getSourceItem();
}
}
foreach($needItems as $i => $needItem){
foreach($haveItems as $j => $haveItem){
if($needItem->equals($haveItem)){
$amount = min($needItem->getCount(), $haveItem->getCount());
$needItem->setCount($needItem->getCount() - $amount);
$haveItem->setCount($haveItem->getCount() - $amount);
if($haveItem->getCount() === 0){
unset($haveItems[$j]);
}
if($needItem->getCount() === 0){
unset($needItems[$i]);
break;
}
}
}
}
return true;
}
/**
* Iterates over SlotChangeActions in this transaction and compacts any which refer to the same slot in the same
* inventory so they can be correctly handled.
*
* Under normal circumstances, the same slot would never be changed more than once in a single transaction. However,
* due to the way things like the crafting grid are "implemented" in MCPE 1.2 (a.k.a. hacked-in), we may get
* multiple slot changes referring to the same slot in a single transaction. These multiples are not even guaranteed
* to be in the correct order (slot splitting in the crafting grid for example, causes the actions to be sent in the
* wrong order), so this method also tries to chain them into order.
*
* @return bool
*/
protected function squashDuplicateSlotChanges() : bool{
/** @var SlotChangeAction[][] $slotChanges */
$slotChanges = [];
foreach($this->actions as $key => $action){
if($action instanceof SlotChangeAction){
$slotChanges[spl_object_hash($action->getInventory()) . "@" . $action->getSlot()][] = $action;
}
}
foreach($slotChanges as $hash => $list){
if(count($list) === 1){ //No need to compact slot changes if there is only one on this slot
unset($slotChanges[$hash]);
continue;
}
$originalList = $list;
/** @var SlotChangeAction|null $originalAction */
$originalAction = null;
/** @var Item|null $lastTargetItem */
$lastTargetItem = null;
foreach($list as $i => $action){
if($action->isValid($this->source)){
$originalAction = $action;
$lastTargetItem = $action->getTargetItem();
unset($list[$i]);
break;
}
}
if($originalAction === null){
return false; //Couldn't find any actions that had a source-item matching the current inventory slot
}
do{
$sortedThisLoop = 0;
foreach($list as $i => $action){
$actionSource = $action->getSourceItem();
if($actionSource->equalsExact($lastTargetItem)){
$lastTargetItem = $action->getTargetItem();
unset($list[$i]);
$sortedThisLoop++;
}
}
}while($sortedThisLoop > 0);
if(count($list) > 0){ //couldn't chain all the actions together
MainLogger::getLogger()->debug("Failed to compact " . count($originalList) . " actions for " . $this->source->getName());
return false;
}
foreach($originalList as $action){
unset($this->actions[spl_object_hash($action)]);
}
$this->addAction(new SlotChangeAction($originalAction->getInventory(), $originalAction->getSlot(), $originalAction->getSourceItem(), $lastTargetItem));
}
return true;
}
/**
* @return bool
*/
public function canExecute() : bool{
$this->squashDuplicateSlotChanges();
$haveItems = [];
$needItems = [];
return $this->matchItems($needItems, $haveItems) and count($this->actions) > 0 and count($haveItems) === 0 and count($needItems) === 0;
}
protected function handleFailed() : void{
foreach($this->actions as $action){
$action->onExecuteFail($this->source);
}
}
protected function callExecuteEvent() : bool{
Server::getInstance()->getPluginManager()->callEvent($ev = new InventoryTransactionEvent($this));
return !$ev->isCancelled();
}
/**
* @return bool
*/
public function execute() : bool{
if($this->hasExecuted() or !$this->canExecute()){
return false;
}
if(!$this->callExecuteEvent()){
$this->handleFailed();
return true;
}
foreach($this->actions as $action){
if(!$action->onPreExecute($this->source)){
$this->handleFailed();
return true;
}
}
foreach($this->actions as $action){
if($action->execute($this->source)){
$action->onExecuteSuccess($this->source);
}else{
$action->onExecuteFail($this->source);
}
}
$this->hasExecuted = true;
return true;
}
/**
* @return bool
*/
public function hasExecuted() : bool{
return $this->hasExecuted;
}
}

View File

@ -0,0 +1,59 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\transaction\action;
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\Player;
/**
* Action used to take the primary result item during crafting.
*/
class CraftingTakeResultAction extends InventoryAction{
public function onAddToTransaction(InventoryTransaction $transaction) : void{
if($transaction instanceof CraftingTransaction){
$transaction->setPrimaryOutput($this->getSourceItem());
}else{
throw new \InvalidStateException(get_class($this) . " can only be added to CraftingTransactions");
}
}
public function isValid(Player $source) : bool{
return true;
}
public function execute(Player $source) : bool{
return true;
}
public function onExecuteSuccess(Player $source) : void{
}
public function onExecuteFail(Player $source) : void{
}
}

View File

@ -0,0 +1,73 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\transaction\action;
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\Item;
use pocketmine\Player;
/**
* Action used to take ingredients out of the crafting grid, or put secondary results into the crafting grid, when
* crafting.
*/
class CraftingTransferMaterialAction extends InventoryAction{
/** @var int */
private $slot;
public function __construct(Item $sourceItem, Item $targetItem, int $slot){
parent::__construct($sourceItem, $targetItem);
$this->slot = $slot;
}
public function onAddToTransaction(InventoryTransaction $transaction) : void{
if($transaction instanceof CraftingTransaction){
if($this->sourceItem->isNull()){
$transaction->setInput($this->slot, $this->targetItem);
}elseif($this->targetItem->isNull()){
$transaction->setExtraOutput($this->slot, $this->sourceItem);
}else{
throw new \InvalidStateException("Invalid " . get_class($this) . ", either source or target item must be air, got source: " . $this->sourceItem . ", target: " . $this->targetItem);
}
}else{
throw new \InvalidStateException(get_class($this) . " can only be added to CraftingTransactions");
}
}
public function isValid(Player $source) : bool{
return true;
}
public function execute(Player $source) : bool{
return true;
}
public function onExecuteSuccess(Player $source) : void{
}
public function onExecuteFail(Player $source) : void{
}
}

View File

@ -0,0 +1,85 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\transaction\action;
use pocketmine\item\Item;
use pocketmine\Player;
class CreativeInventoryAction extends InventoryAction{
/**
* Player put an item into the creative window to destroy it.
*/
const TYPE_DELETE_ITEM = 0;
/**
* Player took an item from the creative window.
*/
const TYPE_CREATE_ITEM = 1;
protected $actionType;
public function __construct(Item $sourceItem, Item $targetItem, int $actionType){
parent::__construct($sourceItem, $targetItem);
$this->actionType = $actionType;
}
/**
* Checks that the player is in creative, and (if creating an item) that the item exists in the creative inventory.
*
* @param Player $source
*
* @return bool
*/
public function isValid(Player $source) : bool{
return $source->isCreative(true) and
($this->actionType === self::TYPE_DELETE_ITEM or Item::getCreativeItemIndex($this->sourceItem) !== -1);
}
/**
* Returns the type of the action.
*/
public function getActionType() : int{
return $this->actionType;
}
/**
* No need to do anything extra here: this type just provides a place for items to disappear or appear from.
*
* @param Player $source
*
* @return bool
*/
public function execute(Player $source) : bool{
return true;
}
public function onExecuteSuccess(Player $source) : void{
}
public function onExecuteFail(Player $source) : void{
}
}

View File

@ -0,0 +1,71 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\transaction\action;
use pocketmine\event\player\PlayerDropItemEvent;
use pocketmine\Player;
/**
* Represents an action involving dropping an item into the world.
*/
class DropItemAction extends InventoryAction{
/**
* Verifies that the source item of a drop-item action must be air. This is not strictly necessary, just a sanity
* check.
*
* @param Player $source
* @return bool
*/
public function isValid(Player $source) : bool{
return $this->sourceItem->isNull();
}
public function onPreExecute(Player $source) : bool{
$source->getServer()->getPluginManager()->callEvent($ev = new PlayerDropItemEvent($source, $this->targetItem));
if($ev->isCancelled()){
return false;
}
return true;
}
/**
* Drops the target item in front of the player.
*
* @param Player $source
* @return bool
*/
public function execute(Player $source) : bool{
return $source->dropItem($this->targetItem);
}
public function onExecuteSuccess(Player $source) : void{
}
public function onExecuteFail(Player $source) : void{
}
}

View File

@ -0,0 +1,124 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\transaction\action;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\Item;
use pocketmine\Player;
/**
* Represents an action involving a change that applies in some way to an inventory or other item-source.
*/
abstract class InventoryAction{
/** @var float */
private $creationTime;
/** @var Item */
protected $sourceItem;
/** @var Item */
protected $targetItem;
public function __construct(Item $sourceItem, Item $targetItem){
$this->sourceItem = $sourceItem;
$this->targetItem = $targetItem;
$this->creationTime = microtime(true);
}
public function getCreationTime() : float{
return $this->creationTime;
}
/**
* Returns the item that was present before the action took place.
* @return Item
*/
public function getSourceItem() : Item{
return clone $this->sourceItem;
}
/**
* Returns the item that the action attempted to replace the source item with.
* @return Item
*/
public function getTargetItem() : Item{
return clone $this->targetItem;
}
/**
* Returns whether this action is currently valid. This should perform any necessary sanity checks.
*
* @param Player $source
*
* @return bool
*/
abstract public function isValid(Player $source) : bool;
/**
* Called when the action is added to the specified InventoryTransaction.
*
* @param InventoryTransaction $transaction
*/
public function onAddToTransaction(InventoryTransaction $transaction) : void{
}
/**
* Called by inventory transactions before any actions are processed. If this returns false, the transaction will
* be cancelled.
*
* @param Player $source
*
* @return bool
*/
public function onPreExecute(Player $source) : bool{
return true;
}
/**
* Performs actions needed to complete the inventory-action server-side. Returns if it was successful. Will return
* false if plugins cancelled events. This will only be called if the transaction which it is part of is considered
* valid.
*
* @param Player $source
*
* @return bool
*/
abstract public function execute(Player $source) : bool;
/**
* Performs additional actions when this inventory-action completed successfully.
*
* @param Player $source
*/
abstract public function onExecuteSuccess(Player $source) : void;
/**
* Performs additional actions when this inventory-action did not complete successfully.
*
* @param Player $source
*/
abstract public function onExecuteFail(Player $source) : void;
}

View File

@ -0,0 +1,122 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory\transaction\action;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\Item;
use pocketmine\Player;
/**
* Represents an action causing a change in an inventory slot.
*/
class SlotChangeAction extends InventoryAction{
/** @var Inventory */
protected $inventory;
/** @var int */
private $inventorySlot;
/**
* @param Inventory $inventory
* @param int $inventorySlot
* @param Item $sourceItem
* @param Item $targetItem
*/
public function __construct(Inventory $inventory, int $inventorySlot, Item $sourceItem, Item $targetItem){
parent::__construct($sourceItem, $targetItem);
$this->inventory = $inventory;
$this->inventorySlot = $inventorySlot;
}
/**
* Returns the inventory involved in this action.
*
* @return Inventory
*/
public function getInventory() : Inventory{
return $this->inventory;
}
/**
* Returns the slot in the inventory which this action modified.
* @return int
*/
public function getSlot() : int{
return $this->inventorySlot;
}
/**
* Checks if the item in the inventory at the specified slot is the same as this action's source item.
*
* @param Player $source
*
* @return bool
*/
public function isValid(Player $source) : bool{
$check = $this->inventory->getItem($this->inventorySlot);
return $check->equalsExact($this->sourceItem);
}
/**
* Adds this action's target inventory to the transaction's inventory list.
*
* @param InventoryTransaction $transaction
*
*/
public function onAddToTransaction(InventoryTransaction $transaction) : void{
$transaction->addInventory($this->inventory);
}
/**
* Sets the item into the target inventory.
*
* @param Player $source
*
* @return bool
*/
public function execute(Player $source) : bool{
return $this->inventory->setItem($this->inventorySlot, $this->targetItem, false);
}
/**
* Sends slot changes to other viewers of the inventory. This will not send any change back to the source Player.
*
* @param Player $source
*/
public function onExecuteSuccess(Player $source) : void{
$viewers = $this->inventory->getViewers();
unset($viewers[spl_object_hash($source)]);
$this->inventory->sendSlot($this->inventorySlot, $viewers);
}
/**
* Sends the original slot contents to the source player to revert the action.
*
* @param Player $source
*/
public function onExecuteFail(Player $source) : void{
$this->inventory->sendSlot($this->inventorySlot, $source);
}
}

View File

@ -643,6 +643,10 @@ class Item implements ItemIds, \JsonSerializable{
return $this;
}
public function isNull() : bool{
return $this->count <= 0 or $this->id === Item::AIR;
}
/**
* Returns the name of the item, or the custom name if it is set.
* @return string
@ -879,6 +883,16 @@ class Item implements ItemIds, \JsonSerializable{
return false;
}
/**
* Returns whether the specified item stack has the same ID, damage, NBT and count as this item stack.
* @param Item $other
*
* @return bool
*/
final public function equalsExact(Item $other) : bool{
return $this->equals($other, true, true) and $this->count === $other->count;
}
/**
* @deprecated Use {@link Item#equals} instead, this method will be removed in the future.
*
@ -905,12 +919,23 @@ class Item implements ItemIds, \JsonSerializable{
* @return array
*/
final public function jsonSerialize(){
return [
"id" => $this->getId(),
"damage" => $this->getDamage(),
"count" => $this->getCount(),
"nbt_hex" => bin2hex($this->getCompoundTag())
$data = [
"id" => $this->getId()
];
if($this->getDamage() !== 0){
$data["damage"] = $this->getDamage();
}
if($this->getCount() !== 1){
$data["count"] = $this->getCount();
}
if($this->hasCompoundTag()){
$data["nbt_hex"] = bin2hex($this->getCompoundTag());
}
return $data;
}
/**
@ -922,9 +947,9 @@ class Item implements ItemIds, \JsonSerializable{
final public static function jsonDeserialize(array $data) : Item{
return ItemFactory::get(
(int) $data["id"],
(int) $data["damage"],
(int) $data["count"],
(string) ($data["nbt"] ?? hex2bin($data["nbt_hex"])) //`nbt` key might contain old raw data
(int) ($data["damage"] ?? 0),
(int) ($data["count"] ?? 1),
(string) ($data["nbt"] ?? (isset($data["nbt_hex"]) ? hex2bin($data["nbt_hex"]) : "")) //`nbt` key might contain old raw data
);
}

View File

@ -41,7 +41,7 @@ class ItemBlock extends Item{
public function setDamage(int $meta){
$this->meta = $meta;
$this->block->setDamage($this->meta !== -1 ? $this->meta : 0);
$this->block->setDamage($this->meta !== -1 ? $this->meta & 0xf : 0);
}
public function getBlock() : Block{

View File

@ -256,7 +256,7 @@ class ItemFactory{
*/
public static function registerItem(Item $item, bool $override = false){
$id = $item->getId();
if(!$override and self::$list[$id] !== null){
if(!$override and self::isRegistered($id)){
throw new \RuntimeException("Trying to overwrite an already registered item");
}
@ -284,7 +284,7 @@ class ItemFactory{
if($id < 256){
/* Blocks must have a damage value 0-15, but items can have damage value -1 to indicate that they are
* crafting ingredients with any-damage. */
$item = new ItemBlock(BlockFactory::get($id, $meta !== -1 ? $meta : 0), $meta);
$item = new ItemBlock(BlockFactory::get($id, $meta !== -1 ? $meta & 0xf : 0), $meta);
}else{
/** @var Item|null $listed */
$listed = self::$list[$id];
@ -338,14 +338,29 @@ class ItemFactory{
if(defined(Item::class . "::" . strtoupper($b[0]))){
$item = self::get(constant(Item::class . "::" . strtoupper($b[0])), $meta);
if($item->getId() === Item::AIR and strtoupper($b[0]) !== "AIR"){
$item = self::get($b[0] & 0xFFFF, $meta);
if($item->getId() === Item::AIR and strtoupper($b[0]) !== "AIR" and is_numeric($b[0])){
$item = self::get(((int) $b[0]) & 0xFFFF, $meta);
}
}elseif(is_numeric($b[0])){
$item = self::get(((int) $b[0]) & 0xFFFF, $meta);
}else{
$item = self::get($b[0] & 0xFFFF, $meta);
$item = self::get(Item::AIR, 0, 0);
}
return $item;
}
}
/**
* Returns whether the specified item ID is already registered in the item factory.
*
* @param int $id
* @return bool
*/
public static function isRegistered(int $id) : bool{
if($id < 256){
return BlockFactory::isRegistered($id);
}
return self::$list[$id] !== null;
}
}

View File

@ -156,7 +156,8 @@ interface ItemIds extends BlockIds{
const SPAWN_EGG = 383;
const BOTTLE_O_ENCHANTING = 384, EXPERIENCE_BOTTLE = 384;
const FIREBALL = 385, FIRE_CHARGE = 385;
const WRITABLE_BOOK = 386;
const WRITTEN_BOOK = 387;
const EMERALD = 388;
const FRAME = 389, ITEM_FRAME = 389;
const FLOWER_POT = 390;
@ -170,7 +171,8 @@ interface ItemIds extends BlockIds{
const CARROTONASTICK = 398, CARROT_ON_A_STICK = 398;
const NETHERSTAR = 399, NETHER_STAR = 399;
const PUMPKIN_PIE = 400;
const FIREWORKS = 401;
const FIREWORKSCHARGE = 402, FIREWORKS_CHARGE = 402;
const ENCHANTED_BOOK = 403;
const COMPARATOR = 404;
const NETHERBRICK = 405, NETHER_BRICK = 405;
@ -193,7 +195,7 @@ interface ItemIds extends BlockIds{
const PRISMARINE_CRYSTALS = 422;
const MUTTONRAW = 423, MUTTON_RAW = 423, RAW_MUTTON = 423;
const COOKED_MUTTON = 424, MUTTONCOOKED = 424, MUTTON_COOKED = 424;
const ARMOR_STAND = 425;
const END_CRYSTAL = 426;
const SPRUCE_DOOR = 427;
const BIRCH_DOOR = 428;
@ -211,6 +213,7 @@ interface ItemIds extends BlockIds{
const COMMAND_BLOCK_MINECART = 443, MINECART_WITH_COMMAND_BLOCK = 443;
const ELYTRA = 444;
const SHULKER_SHELL = 445;
const BANNER = 446;
const TOTEM = 450;
@ -226,4 +229,17 @@ interface ItemIds extends BlockIds{
const APPLEENCHANTED = 466, APPLE_ENCHANTED = 466, ENCHANTED_GOLDEN_APPLE = 466;
const RECORD_13 = 500;
const RECORD_CAT = 501;
const RECORD_BLOCKS = 502;
const RECORD_CHIRP = 503;
const RECORD_FAR = 504;
const RECORD_MALL = 505;
const RECORD_MELLOHI = 506;
const RECORD_STAL = 507;
const RECORD_STRAD = 508;
const RECORD_WARD = 509;
const RECORD_11 = 510;
const RECORD_WAIT = 511;
}

View File

@ -225,9 +225,7 @@ class Explosion{
}
$pk = new ExplodePacket();
$pk->x = $this->source->x;
$pk->y = $this->source->y;
$pk->z = $this->source->z;
$pk->position = $this->source->asVector3();
$pk->radius = $this->size;
$pk->records = $send;
$this->level->addChunkPacket($source->x >> 4, $source->z >> 4, $pk);

View File

@ -44,7 +44,6 @@ use pocketmine\event\level\SpawnChangeEvent;
use pocketmine\event\LevelTimings;
use pocketmine\event\player\PlayerInteractEvent;
use pocketmine\event\Timings;
use pocketmine\inventory\InventoryHolder;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\level\format\Chunk;
@ -78,14 +77,13 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
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;
use pocketmine\tile\Chest;
use pocketmine\tile\Container;
use pocketmine\tile\Tile;
use pocketmine\utils\Random;
use pocketmine\utils\ReversePriorityQueue;
@ -441,7 +439,7 @@ class Level implements ChunkManager, Metadatable{
$pk = new LevelEventPacket();
$pk->evid = $evid;
$pk->data = $data;
list($pk->x, $pk->y, $pk->z) = [$pos->x, $pos->y, $pos->z];
$pk->position = $pos->asVector3();
$this->addChunkPacket($pos->x >> 4, $pos->z >> 4, $pk);
}
@ -462,7 +460,7 @@ class Level implements ChunkManager, Metadatable{
$pk->extraData = $extraData;
$pk->unknownBool = $unknown;
$pk->disableRelativeVolume = $disableRelativeVolume;
list($pk->x, $pk->y, $pk->z) = [$pos->x, $pos->y, $pos->z];
$pk->position = $pos->asVector3();
$this->addChunkPacket($pos->x >> 4, $pos->z >> 4, $pk);
}
@ -475,6 +473,8 @@ class Level implements ChunkManager, Metadatable{
}
/**
* @internal DO NOT use this from plugins, it's for internal use only. Use Server->unloadLevel() instead.
*
* Unloads the current level from memory safely
*
* @param bool $force default false, force unload of default level
@ -509,6 +509,8 @@ class Level implements ChunkManager, Metadatable{
$this->server->setDefaultLevel(null);
}
$this->server->removeLevel($this);
$this->close();
return true;
@ -760,9 +762,7 @@ class Level implements ChunkManager, Metadatable{
public function sendBlockExtraData(int $x, int $y, int $z, int $id, int $data, array $targets = null){
$pk = new LevelEventPacket;
$pk->evid = LevelEventPacket::EVENT_SET_DATA;
$pk->x = $x + 0.5;
$pk->y = $y + 0.5;
$pk->z = $z + 0.5;
$pk->position = new Vector3($x, $y, $z);
$pk->data = ($data << 8) | $id;
$this->server->broadcastPacket($targets ?? $this->getChunkPlayers($x >> 4, $z >> 4), $pk);
@ -1622,7 +1622,7 @@ class Level implements ChunkManager, Metadatable{
$tile = $this->getTile($target);
if($tile !== null){
if($tile instanceof InventoryHolder){
if($tile instanceof Container){
if($tile instanceof Chest){
$tile->unpair();
}
@ -1727,20 +1727,23 @@ class Level implements ChunkManager, Metadatable{
return true;
}
if(!$item->canBePlaced()){
if($item->canBePlaced()){
$hand = $item->getBlock();
$hand->position($blockReplace);
}else{
return false;
}
$hand = $item->getBlock();
if(!($blockReplace->canBeReplaced() === true or ($hand->getId() === Item::WOODEN_SLAB and $blockReplace->getId() === Item::WOODEN_SLAB) or ($hand->getId() === Item::STONE_SLAB and $blockReplace->getId() === Item::STONE_SLAB))){
return false;
}
if($blockClicked->canBeReplaced($hand)){
if($blockClicked->canBeReplaced() === true){
$blockReplace = $blockClicked;
}elseif(!$blockReplace->canBeReplaced($hand)){
return false;
$hand->position($blockReplace);
//$face = -1;
}
$hand->position($blockReplace);
if($hand->isSolid() === true and $hand->getBoundingBox() !== null){
$entities = $this->getCollidingEntities($hand->getBoundingBox());
foreach($entities as $e){
@ -2863,25 +2866,4 @@ class Level implements ChunkManager, Metadatable{
public function removeMetadata(string $metadataKey, Plugin $owningPlugin){
$this->server->getLevelMetadata()->removeMetadata($this, $metadataKey, $owningPlugin);
}
public function addEntityMotion(int $chunkX, int $chunkZ, int $entityId, float $x, float $y, float $z){
$pk = new SetEntityMotionPacket();
$pk->entityRuntimeId = $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){
$pk = new MoveEntityPacket();
$pk->entityRuntimeId = $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

@ -41,12 +41,9 @@ class Location extends Position{
* @param Level $level
*/
public function __construct($x = 0, $y = 0, $z = 0, $yaw = 0.0, $pitch = 0.0, Level $level = null){
$this->x = $x;
$this->y = $y;
$this->z = $z;
$this->yaw = $yaw;
$this->pitch = $pitch;
$this->level = $level;
parent::__construct($x, $y, $z, $level);
}
/**

View File

@ -114,7 +114,7 @@ class EmptySubChunk implements SubChunkInterface{
}
public function networkSerialize() : string{
return "\x00" . str_repeat("\x00", 10240);
return "\x00" . str_repeat("\x00", 6144);
}
public function fastSerialize() : string{

View File

@ -219,8 +219,7 @@ class SubChunk implements SubChunkInterface{
}
public function networkSerialize() : string{
// storage version, ids, data, skylight, blocklight
return "\x00" . $this->ids . $this->data . $this->skyLight . $this->blockLight;
return "\x00" . $this->ids . $this->data;
}
public function fastSerialize() : string{
@ -239,4 +238,8 @@ class SubChunk implements SubChunkInterface{
substr($data, 8192, 2048) //block light
);
}
public function __debugInfo(){
return [];
}
}

View File

@ -27,6 +27,6 @@ declare(strict_types=1);
namespace pocketmine\level\generator\object;
abstract class Object{
abstract class PopulatorObject{
}

View File

@ -39,9 +39,7 @@ class DestroyBlockParticle extends Particle{
public function encode(){
$pk = new LevelEventPacket;
$pk->evid = LevelEventPacket::EVENT_PARTICLE_DESTROY;
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->position = $this->asVector3();
$pk->data = $this->data;
return $pk;

View File

@ -24,10 +24,12 @@ declare(strict_types=1);
namespace pocketmine\level\particle;
use pocketmine\entity\Entity;
use pocketmine\entity\Item as ItemEntity;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
use pocketmine\utils\UUID;
class FloatingTextParticle extends Particle{
//TODO: HACK!
@ -39,29 +41,37 @@ class FloatingTextParticle extends Particle{
/**
* @param Vector3 $pos
* @param int $text
* @param string $text
* @param string $title
*/
public function __construct(Vector3 $pos, $text, $title = ""){
public function __construct(Vector3 $pos, string $text, string $title = ""){
parent::__construct($pos->x, $pos->y, $pos->z);
$this->text = $text;
$this->title = $title;
}
public function setText($text){
public function getText() : string{
return $this->text;
}
public function setText(string $text) : void{
$this->text = $text;
}
public function setTitle($title){
public function getTitle() : string{
return $this->title;
}
public function setTitle(string $title) : void{
$this->title = $title;
}
public function isInvisible(){
public function isInvisible() : bool{
return $this->invisible;
}
public function setInvisible($value = true){
$this->invisible = (bool) $value;
public function setInvisible(bool $value = true){
$this->invisible = $value;
}
public function encode(){
@ -77,26 +87,22 @@ class FloatingTextParticle extends Particle{
}
if(!$this->invisible){
$pk = new AddEntityPacket();
$pk = new AddPlayerPacket();
$pk->uuid = UUID::fromRandom();
$pk->username = "";
$pk->entityRuntimeId = $this->entityId;
$pk->type = ItemEntity::NETWORK_ID;
$pk->x = $this->x;
$pk->y = $this->y - 0.75;
$pk->z = $this->z;
$pk->speedX = 0;
$pk->speedY = 0;
$pk->speedZ = 0;
$pk->yaw = 0;
$pk->pitch = 0;
$pk->position = $this->asVector3(); //TODO: check offset
$pk->item = ItemFactory::get(Item::AIR, 0, 0);
$flags = (
(1 << Entity::DATA_FLAG_CAN_SHOW_NAMETAG) |
(1 << Entity::DATA_FLAG_ALWAYS_SHOW_NAMETAG) |
(1 << Entity::DATA_FLAG_IMMOBILE)
);
$pk->metadata = [
Entity::DATA_FLAGS => [Entity::DATA_TYPE_LONG, $flags],
Entity::DATA_NAMETAG => [Entity::DATA_TYPE_STRING, $this->title . ($this->text !== "" ? "\n" . $this->text : "")]
Entity::DATA_FLAGS => [Entity::DATA_TYPE_LONG, $flags],
Entity::DATA_NAMETAG => [Entity::DATA_TYPE_STRING, $this->title . ($this->text !== "" ? "\n" . $this->text : "")],
Entity::DATA_SCALE => [Entity::DATA_TYPE_FLOAT, 0.01] //zero causes problems on debug builds
];
$p[] = $pk;

View File

@ -40,9 +40,7 @@ class GenericParticle extends Particle{
public function encode(){
$pk = new LevelEventPacket;
$pk->evid = LevelEventPacket::EVENT_ADD_PARTICLE_MASK | $this->id;
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->position = $this->asVector3();
$pk->data = $this->data;
return $pk;

View File

@ -40,9 +40,7 @@ class MobSpawnParticle extends Particle{
public function encode(){
$pk = new LevelEventPacket;
$pk->evid = LevelEventPacket::EVENT_PARTICLE_SPAWN;
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->position = $this->asVector3();
$pk->data = ($this->width & 0xff) + (($this->height & 0xff) << 8);
return $pk;

View File

@ -49,9 +49,7 @@ class GenericSound extends Sound{
public function encode(){
$pk = new LevelEventPacket;
$pk->evid = $this->id;
$pk->x = $this->x;
$pk->y = $this->y;
$pk->z = $this->z;
$pk->position = $this->asVector3();
$pk->data = (int) $this->pitch;
return $pk;

View File

@ -27,7 +27,6 @@ use pocketmine\network\mcpe\protocol\AddBehaviorTreePacket;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\AddHangingEntityPacket;
use pocketmine\network\mcpe\protocol\AddItemEntityPacket;
use pocketmine\network\mcpe\protocol\AddItemPacket;
use pocketmine\network\mcpe\protocol\AddPaintingPacket;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
@ -36,6 +35,7 @@ use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
use pocketmine\network\mcpe\protocol\BookEditPacket;
use pocketmine\network\mcpe\protocol\BossEventPacket;
use pocketmine\network\mcpe\protocol\CameraPacket;
use pocketmine\network\mcpe\protocol\ChangeDimensionPacket;
@ -43,26 +43,28 @@ use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
use pocketmine\network\mcpe\protocol\ClientToServerHandshakePacket;
use pocketmine\network\mcpe\protocol\ClientboundMapItemDataPacket;
use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket;
use pocketmine\network\mcpe\protocol\CommandStepPacket;
use pocketmine\network\mcpe\protocol\CommandOutputPacket;
use pocketmine\network\mcpe\protocol\CommandRequestPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\DropItemPacket;
use pocketmine\network\mcpe\protocol\EntityEventPacket;
use pocketmine\network\mcpe\protocol\EntityFallPacket;
use pocketmine\network\mcpe\protocol\EntityPickRequestPacket;
use pocketmine\network\mcpe\protocol\EventPacket;
use pocketmine\network\mcpe\protocol\ExplodePacket;
use pocketmine\network\mcpe\protocol\FullChunkDataPacket;
use pocketmine\network\mcpe\protocol\GameRulesChangedPacket;
use pocketmine\network\mcpe\protocol\GuiDataPickItemPacket;
use pocketmine\network\mcpe\protocol\HurtArmorPacket;
use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryActionPacket;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
@ -71,17 +73,21 @@ use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MoveEntityPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NpcRequestPacket;
use pocketmine\network\mcpe\protocol\PhotoTransferPacket;
use pocketmine\network\mcpe\protocol\PlaySoundPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\PurchaseReceiptPacket;
use pocketmine\network\mcpe\protocol\RemoveBlockPacket;
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
use pocketmine\network\mcpe\protocol\ReplaceItemInSlotPacket;
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkDataPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
@ -91,24 +97,30 @@ use pocketmine\network\mcpe\protocol\ResourcePackStackPacket;
use pocketmine\network\mcpe\protocol\ResourcePacksInfoPacket;
use pocketmine\network\mcpe\protocol\RespawnPacket;
use pocketmine\network\mcpe\protocol\RiderJumpPacket;
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
use pocketmine\network\mcpe\protocol\ServerSettingsResponsePacket;
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
use pocketmine\network\mcpe\protocol\SetCommandsEnabledPacket;
use pocketmine\network\mcpe\protocol\SetDefaultGameTypePacket;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
use pocketmine\network\mcpe\protocol\SetEntityDataPacket;
use pocketmine\network\mcpe\protocol\SetEntityLinkPacket;
use pocketmine\network\mcpe\protocol\SetEntityMotionPacket;
use pocketmine\network\mcpe\protocol\SetHealthPacket;
use pocketmine\network\mcpe\protocol\SetLastHurtByPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket;
use pocketmine\network\mcpe\protocol\SetTimePacket;
use pocketmine\network\mcpe\protocol\SetTitlePacket;
use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
use pocketmine\network\mcpe\protocol\ShowProfilePacket;
use pocketmine\network\mcpe\protocol\ShowStoreOfferPacket;
use pocketmine\network\mcpe\protocol\SimpleEventPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\StopSoundPacket;
use pocketmine\network\mcpe\protocol\StructureBlockUpdatePacket;
use pocketmine\network\mcpe\protocol\SubClientLoginPacket;
use pocketmine\network\mcpe\protocol\TakeItemEntityPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\TransferPacket;
@ -116,7 +128,7 @@ use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\network\mcpe\protocol\UpdateEquipPacket;
use pocketmine\network\mcpe\protocol\UpdateTradePacket;
use pocketmine\network\mcpe\protocol\UseItemPacket;
use pocketmine\network\mcpe\protocol\WSConnectPacket;
abstract class NetworkSession{
@ -202,10 +214,6 @@ abstract class NetworkSession{
return false;
}
public function handleRemoveBlock(RemoveBlockPacket $packet) : bool{
return false;
}
public function handleUpdateBlock(UpdateBlockPacket $packet) : bool{
return false;
}
@ -242,6 +250,10 @@ abstract class NetworkSession{
return false;
}
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
return false;
}
public function handleMobEquipment(MobEquipmentPacket $packet) : bool{
return false;
}
@ -258,7 +270,7 @@ abstract class NetworkSession{
return false;
}
public function handleUseItem(UseItemPacket $packet) : bool{
public function handleEntityPickRequest(EntityPickRequestPacket $packet) : bool{
return false;
}
@ -302,14 +314,6 @@ abstract class NetworkSession{
return false;
}
public function handleDropItem(DropItemPacket $packet) : bool{
return false;
}
public function handleInventoryAction(InventoryActionPacket $packet) : bool{
return false;
}
public function handleContainerOpen(ContainerOpenPacket $packet) : bool{
return false;
}
@ -318,7 +322,15 @@ abstract class NetworkSession{
return false;
}
public function handleContainerSetSlot(ContainerSetSlotPacket $packet) : bool{
public function handlePlayerHotbar(PlayerHotbarPacket $packet) : bool{
return false;
}
public function handleInventoryContent(InventoryContentPacket $packet) : bool{
return false;
}
public function handleInventorySlot(InventorySlotPacket $packet) : bool{
return false;
}
@ -326,10 +338,6 @@ abstract class NetworkSession{
return false;
}
public function handleContainerSetContent(ContainerSetContentPacket $packet) : bool{
return false;
}
public function handleCraftingData(CraftingDataPacket $packet) : bool{
return false;
}
@ -338,6 +346,10 @@ abstract class NetworkSession{
return false;
}
public function handleGuiDataPickItem(GuiDataPickItemPacket $packet) : bool{
return false;
}
public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{
return false;
}
@ -406,10 +418,6 @@ abstract class NetworkSession{
return false;
}
public function handleReplaceItemInSlot(ReplaceItemInSlotPacket $packet) : bool{
return false;
}
public function handleGameRulesChanged(GameRulesChangedPacket $packet) : bool{
return false;
}
@ -418,10 +426,6 @@ abstract class NetworkSession{
return false;
}
public function handleAddItem(AddItemPacket $packet) : bool{
return false;
}
public function handleBossEvent(BossEventPacket $packet) : bool{
return false;
}
@ -434,7 +438,7 @@ abstract class NetworkSession{
return false;
}
public function handleCommandStep(CommandStepPacket $packet) : bool{
public function handleCommandRequest(CommandRequestPacket $packet) : bool{
return false;
}
@ -442,6 +446,10 @@ abstract class NetworkSession{
return false;
}
public function handleCommandOutput(CommandOutputPacket $packet) : bool{
return false;
}
public function handleUpdateTrade(UpdateTradePacket $packet) : bool{
return false;
}
@ -494,4 +502,56 @@ abstract class NetworkSession{
return false;
}
public function handlePlayerSkin(PlayerSkinPacket $packet) : bool{
return false;
}
public function handleSubClientLogin(SubClientLoginPacket $packet) : bool{
return false;
}
public function handleWSConnect(WSConnectPacket $packet) : bool{
return false;
}
public function handleSetLastHurtBy(SetLastHurtByPacket $packet) : bool{
return false;
}
public function handleBookEdit(BookEditPacket $packet) : bool{
return false;
}
public function handleNpcRequest(NpcRequestPacket $packet) : bool{
return false;
}
public function handlePhotoTransfer(PhotoTransferPacket $packet) : bool{
return false;
}
public function handleModalFormRequest(ModalFormRequestPacket $packet) : bool{
return false;
}
public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{
return false;
}
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
return false;
}
public function handleServerSettingsResponse(ServerSettingsResponsePacket $packet) : bool{
return false;
}
public function handleShowProfile(ShowProfilePacket $packet) : bool{
return false;
}
public function handleSetDefaultGameType(SetDefaultGameTypePacket $packet) : bool{
return false;
}
}

View File

@ -33,33 +33,35 @@ use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
use pocketmine\network\mcpe\protocol\BossEventPacket;
use pocketmine\network\mcpe\protocol\ClientToServerHandshakePacket;
use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket;
use pocketmine\network\mcpe\protocol\CommandStepPacket;
use pocketmine\network\mcpe\protocol\CommandRequestPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\DropItemPacket;
use pocketmine\network\mcpe\protocol\EntityEventPacket;
use pocketmine\network\mcpe\protocol\EntityFallPacket;
use pocketmine\network\mcpe\protocol\EntityPickRequestPacket;
use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
use pocketmine\network\mcpe\protocol\RemoveBlockPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket;
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\UseItemPacket;
use pocketmine\Player;
use pocketmine\Server;
@ -76,11 +78,6 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
}
public function handleDataPacket(DataPacket $packet){
//TODO: Remove this hack once InteractPacket spam issue is fixed
if($packet->buffer === "\x21\x04\x00"){
return;
}
$timings = Timings::getReceiveDataPacketTimings($packet);
$timings->startTiming();
@ -111,17 +108,17 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
}
public function handleText(TextPacket $packet) : bool{
return $this->player->handleText($packet);
if($packet->type === TextPacket::TYPE_CHAT){
return $this->player->chat($packet->message);
}
return false;
}
public function handleMovePlayer(MovePlayerPacket $packet) : bool{
return $this->player->handleMovePlayer($packet);
}
public function handleRemoveBlock(RemoveBlockPacket $packet) : bool{
return $this->player->handleRemoveBlock($packet);
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return $this->player->handleLevelSoundEvent($packet);
}
@ -130,6 +127,10 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
return $this->player->handleEntityEvent($packet);
}
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
return $this->player->handleInventoryTransaction($packet); //TODO
}
public function handleMobEquipment(MobEquipmentPacket $packet) : bool{
return $this->player->handleMobEquipment($packet);
}
@ -146,8 +147,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
return $this->player->handleBlockPickRequest($packet);
}
public function handleUseItem(UseItemPacket $packet) : bool{
return $this->player->handleUseItem($packet);
public function handleEntityPickRequest(EntityPickRequestPacket $packet) : bool{
return false; //TODO
}
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
@ -162,20 +163,16 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
return $this->player->handleAnimate($packet);
}
public function handleDropItem(DropItemPacket $packet) : bool{
return $this->player->handleDropItem($packet);
}
public function handleContainerClose(ContainerClosePacket $packet) : bool{
return $this->player->handleContainerClose($packet);
}
public function handleContainerSetSlot(ContainerSetSlotPacket $packet) : bool{
return $this->player->handleContainerSetSlot($packet);
public function handlePlayerHotbar(PlayerHotbarPacket $packet) : bool{
return $this->player->handlePlayerHotbar($packet);
}
public function handleCraftingEvent(CraftingEventPacket $packet) : bool{
return $this->player->handleCraftingEvent($packet);
return true; //this is a broken useless packet, so we don't use it
}
public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{
@ -218,8 +215,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
return $this->player->handleShowCredits($packet);
}
public function handleCommandStep(CommandStepPacket $packet) : bool{
return $this->player->handleCommandStep($packet);
public function handleCommandRequest(CommandRequestPacket $packet) : bool{
return $this->player->chat($packet->command);
}
public function handleCommandBlockUpdate(CommandBlockUpdatePacket $packet) : bool{
@ -229,4 +226,16 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
return $this->player->handleResourcePackChunkRequest($packet);
}
public function handlePlayerSkin(PlayerSkinPacket $packet) : bool{
return false; //TODO
}
public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{
return false; //TODO: GUI stuff
}
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
return false; //TODO: GUI stuff
}
}

View File

@ -54,7 +54,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
private $players = [];
/** @var string[] */
private $identifiers;
private $identifiers = [];
/** @var int[] */
private $identifiersACK = [];
@ -63,9 +63,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
private $interface;
public function __construct(Server $server){
$this->server = $server;
$this->identifiers = [];
$this->rakLib = new RakLibServer($this->server->getLogger(), $this->server->getLoader(), $this->server->getPort(), $this->server->getIp() === "" ? "0.0.0.0" : $this->server->getIp(), false);
$this->interface = new ServerHandler($this->rakLib, $this);
@ -213,13 +211,10 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
if($packet instanceof BatchPacket){
if($needACK){
$pk = new EncapsulatedPacket();
$pk->identifierACK = $this->identifiersACK[$identifier]++;
$pk->buffer = $packet->buffer;
$pk->reliability = $immediate ? PacketReliability::RELIABLE : PacketReliability::RELIABLE_ORDERED;
$pk->orderChannel = 0;
if($needACK === true){
$pk->identifierACK = $this->identifiersACK[$identifier]++;
}
}else{
if(!isset($packet->__encapsulatedPacket)){
$packet->__encapsulatedPacket = new CachedEncapsulatedPacket;

View File

@ -0,0 +1,144 @@
<?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\network\mcpe;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\Player;
use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;
use pocketmine\utils\MainLogger;
class VerifyLoginTask extends AsyncTask{
const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
/** @var LoginPacket */
private $packet;
/**
* @var bool
* Whether the keychain signatures were validated correctly. This will be set to false if any link in the keychain
* has an invalid signature. If false, the keychain might have been tampered with.
* The player will always be disconnected if this is false.
*/
private $valid = true;
/**
* @var bool
* Whether the player is logged into Xbox Live. This is true if any link in the keychain is signed with the Mojang
* root public key.
*/
private $authenticated = false;
public function __construct(Player $player, LoginPacket $packet){
$this->storeLocal($player);
$this->packet = $packet;
}
public function onRun(){
$packet = $this->packet; //Get it in a local variable to make sure it stays unserialized
$currentKey = null;
foreach($packet->chainData["chain"] as $jwt){
if(!$this->validateToken($jwt, $currentKey)){
$this->valid = false;
return;
}
}
if(!$this->validateToken($packet->clientDataJwt, $currentKey)){
$this->valid = false;
}
}
private function validateToken(string $jwt, ?string &$currentPublicKey) : bool{
[$headB64, $payloadB64, $sigB64] = explode('.', $jwt);
$headers = json_decode(base64_decode(strtr($headB64, '-_', '+/'), true), true);
if($currentPublicKey === null){ //First link, check that it is self-signed
$currentPublicKey = $headers["x5u"];
}
$plainSignature = base64_decode(strtr($sigB64, '-_', '+/'), true);
//OpenSSL wants a DER-encoded signature, so we extract R and S from the plain signature and crudely serialize it.
assert(strlen($plainSignature) === 96);
[$rString, $sString] = str_split($plainSignature, 48);
$rString = ltrim($rString, "\x00");
if(ord($rString{0}) >= 128){ //Would be considered signed, pad it with an extra zero
$rString = "\x00" . $rString;
}
$sString = ltrim($sString, "\x00");
if(ord($sString{0}) >= 128){ //Would be considered signed, pad it with an extra zero
$sString = "\x00" . $sString;
}
//0x02 = Integer ASN.1 tag
$sequence = "\x02" . chr(strlen($rString)) . $rString . "\x02" . chr(strlen($sString)) . $sString;
//0x30 = Sequence ASN.1 tag
$derSignature = "\x30" . chr(strlen($sequence)) . $sequence;
$v = openssl_verify("$headB64.$payloadB64", $derSignature, "-----BEGIN PUBLIC KEY-----\n" . wordwrap($currentPublicKey, 64, "\n", true) . "\n-----END PUBLIC KEY-----\n", OPENSSL_ALGO_SHA384);
if($v !== 1){
return false; //bad signature, it might have been tampered with
}
if($currentPublicKey === self::MOJANG_ROOT_PUBLIC_KEY){
$this->authenticated = true; //we're signed into xbox live
}
$claims = json_decode(base64_decode(strtr($payloadB64, '-_', '+/'), true), true);
$time = time();
if(isset($claims["nbf"]) and $claims["nbf"] > $time){
return false; //token can't be used yet
}
if(isset($claims["exp"]) and $claims["exp"] < $time){
return false; //token has expired
}
$currentPublicKey = $claims["identityPublicKey"]; //the next link should be signed with this
return true;
}
public function onCompletion(Server $server){
/** @var Player $player */
$player = $this->fetchLocal($server);
if($player->isClosed()){
$server->getLogger()->error("Player " . $player->getName() . " was disconnected before their login could be verified");
}else{
$player->onVerifyCompleted($this->packet, $this->valid, $this->authenticated);
}
}
}

View File

@ -33,11 +33,11 @@ class AddBehaviorTreePacket extends DataPacket{
/** @var string */
public $unknownString1;
public function decodePayload(){
protected function decodePayload(){
$this->unknownString1 = $this->getString();
}
public function encodePayload(){
protected function encodePayload(){
$this->putString($this->unknownString1);
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\entity\Attribute;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
class AddEntityPacket extends DataPacket{
@ -35,26 +36,30 @@ class AddEntityPacket extends DataPacket{
public $entityUniqueId = null; //TODO
/** @var int */
public $entityRuntimeId;
/** @var int */
public $type;
public $x;
public $y;
public $z;
public $speedX = 0.0;
public $speedY = 0.0;
public $speedZ = 0.0;
/** @var Vector3 */
public $position;
/** @var Vector3|null */
public $motion;
/** @var float */
public $yaw = 0.0;
/** @var float */
public $pitch = 0.0;
/** @var Attribute[] */
public $attributes = [];
/** @var array */
public $metadata = [];
/** @var array */
public $links = [];
public function decodePayload(){
protected function decodePayload(){
$this->entityUniqueId = $this->getEntityUniqueId();
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->type = $this->getUnsignedVarInt();
$this->getVector3f($this->x, $this->y, $this->z);
$this->getVector3f($this->speedX, $this->speedY, $this->speedZ);
$this->position = $this->getVector3Obj();
$this->motion = $this->getVector3Obj();
$this->pitch = $this->getLFloat();
$this->yaw = $this->getLFloat();
@ -79,18 +84,16 @@ class AddEntityPacket extends DataPacket{
$this->metadata = $this->getEntityMetadata();
$linkCount = $this->getUnsignedVarInt();
for($i = 0; $i < $linkCount; ++$i){
$this->links[$i][0] = $this->getEntityUniqueId();
$this->links[$i][1] = $this->getEntityUniqueId();
$this->links[$i][2] = $this->getByte();
$this->links[] = $this->getEntityLink();
}
}
public function encodePayload(){
protected function encodePayload(){
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putUnsignedVarInt($this->type);
$this->putVector3f($this->x, $this->y, $this->z);
$this->putVector3f($this->speedX, $this->speedY, $this->speedZ);
$this->putVector3Obj($this->position);
$this->putVector3ObjNullable($this->motion);
$this->putLFloat($this->pitch);
$this->putLFloat($this->yaw);
@ -105,9 +108,7 @@ class AddEntityPacket extends DataPacket{
$this->putEntityMetadata($this->metadata);
$this->putUnsignedVarInt(count($this->links));
foreach($this->links as $link){
$this->putEntityUniqueId($link[0]);
$this->putEntityUniqueId($link[1]);
$this->putByte($link[2]);
$this->putEntityLink($link);
}
}

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