Compare commits

..

200 Commits

Author SHA1 Message Date
002feacf8e Release 4.0.0-BETA11 2021-11-06 01:16:58 +00:00
b8523f7a18 Player: fix the fix which just degraded performance
if a chunk was requested for generation, count++ and count(activeRequests)++, which means that we would only get to submit half as many generation requests as we're allowed to.
Calculate the limit at the start and remember it instead.
2021-11-06 01:00:35 +00:00
640e88009b Player: fixed a mistake in generation rate limit
we don't want to allow sending further chunks when we haven't generated near ones, because we won't be able to see them anyway, and we might end up not needing them.
This now fully matches the results of PM3.
2021-11-06 00:57:37 +00:00
6cd272c9e1 Update transient composer dependencies 2021-11-06 00:55:13 +00:00
566c57bcd3 we no longer need submodules for these jobs 2021-11-06 00:35:39 +00:00
3c754b079c Move resources/locale to Composer dependency
all remaining submodules are now non-essential to running a server.
They are also versioned and updates can be done automatically using 'composer update'.

Finally, we can also put an end to the issue of translations being rendered incorrectly or being missing due to outdated submodules.
2021-11-06 00:32:58 +00:00
dbf9a33160 ChunkSelector: Improve algorithm to send chunks in proper circles, instead of squares
this ensures that the edge of loaded terain is always the same distance
away in any direction. This also means that when flying parallel to X or
Z axes, you now have about 12% more chunks directly in front of you,
instead of to your left and right, which gives the impression that
chunks are loading faster (they aren't, they are just being ordered in a
more sensible way).
2021-11-06 00:15:04 +00:00
07b1cff306 Bonemeal is no longer consumed when cancelling plant growth events (#4551) 2021-11-05 16:15:55 +00:00
0989c77037 Player: check that horizontal distance travelled is > 0 before adding exhaustion, or triggering a new chunk order
closes #4548
2021-11-05 15:46:55 +00:00
5107d0df4e Player: cap maximum number of active generation requests
this fixes the horrible spotty chunk loading seen in https://twitter.com/dktapps/status/1456397007946461190?s=20.
In practice, this made chunks invisible on teleport for several tens of seconds after teleporting. Having a larger chunks-per-tick with large render distance compounded to worsen the problem.
It wasn't really noticeable on small render distances, but very obvious on large ones with fast chunk sending and slow generation.

This also fixes #4187 (at least to the extent that it works on PM3, anyway).
2021-11-05 15:09:42 +00:00
579ef63663 EntityDataHelper: accept FloatTag for vector3 as well as Double
MCPE uses Float for entity positions.
2021-11-04 20:46:34 +00:00
8abc952c74 simulate-chunk-selector: do not reallocate colours on every frame 2021-11-04 19:38:40 +00:00
4c3a5fdd73 Clean PHPStan baselines from 1.0.2 2021-11-04 19:28:52 +00:00
54f287feb6 Merge branch 'stable' 2021-11-04 19:27:41 +00:00
84f8b3eb2d Move CrashDump to pocketmine\crash namespace 2021-11-04 19:23:45 +00:00
15fca84f3b remove some PHPStan error patterns 2021-11-04 19:22:49 +00:00
c60144210f Regenerate PHPStan bugs baseline 2021-11-04 19:18:29 +00:00
8ac999cbd4 Use object models for crashdump generation 2021-11-04 16:55:04 +00:00
4f8501ff34 Added a tool to visualise behaviour of ChunkSelector
I actually intended to write a tool for debugging generation, but it turns out this, as an intermediary step, is also useful and a whole bunch of fun to play with.
2021-11-04 14:14:55 +00:00
2405e45b35 Player: mark as not using item when held item slot is changed
closes #4538
2021-11-03 21:26:20 +00:00
e0b07ff308 Human: do not add more XP if totalXp limit was already reached
this matches the vanilla behaviour. For some reason it doesn't consider levels (so you can have a level higher or lower than this without actually having that amount of XP), but this matches Java behaviour as of 1.10.

fixes #4543
2021-11-03 20:45:55 +00:00
729f831b8f PHPStan 1.0.2 2021-11-03 20:26:32 +00:00
0356716e8e PopulationTask: do not expose internal fields as public
this code dates back to pthreads v2, when visibility on Threaded object fields meant different things (wtf, krakjoe??)
2021-11-03 15:23:43 +00:00
5c81b04813 PopulationTask: use typed properties 2021-11-03 15:22:49 +00:00
1ebb206762 World: fixed yet another edge case in drainPopulationRequestQueue() leading to assertion failure
really had to go fucking nuclear on it :(
2021-11-03 14:58:58 +00:00
29e2d92098 Bump phpstan/phpstan from 1.0.0 to 1.0.1 (#4541)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Commits](https://github.com/phpstan/phpstan/compare/1.0.0...1.0.1)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-03 11:27:42 +00:00
ef82a2cd79 Fixed PlayerEmoteEvent::setEmoteId() being useless 2021-11-02 23:10:26 +00:00
87031627bf Do not call PlayerEmoteEvent if rate limit was reached 2021-11-02 23:09:43 +00:00
f066199971 Implement emote support (#4523) 2021-11-02 23:04:55 +00:00
a0e9eec652 4.0.0-BETA11 is next 2021-11-02 19:18:20 +00:00
fa6a432d58 Release 4.0.0-BETA10 2021-11-02 19:18:19 +00:00
102277c636 draft-release: fixed BedrockData JSON minification 2021-11-02 19:16:36 +00:00
f50f26d52e 4.0.0-BETA10 is next 2021-11-02 19:14:15 +00:00
4ca7c29cde Release 4.0.0-BETA9 2021-11-02 19:14:12 +00:00
9f64bc8180 Merge branch 'stable' 2021-11-02 17:30:36 +00:00
f75a05d7fa 3.25.3 is next 2021-11-02 17:21:49 +00:00
3dae873731 Release 3.25.2 2021-11-02 17:21:44 +00:00
aa0dc60c32 Merge branch 'master' of github.com:pmmp/PocketMine-MP 2021-11-02 17:10:19 +00:00
d184838ba0 Move Promise classes to their own namespace 2021-11-02 17:10:07 +00:00
ead8ccf08d CocoaBlock: call BlockGrowEvent when growing for any reason (#4536) 2021-11-02 17:05:07 +00:00
38f97bed52 World: fixed PopulationTask failed assumption that generator is always registered
if the worker selected previously had a generator registered, but has since been shutdown, the workerStartHook that cleans up generatorRegisteredWorkers won't yet have been called.
This results in the worker being started by the submission of PopulationTask, and the generator doesn't get preemptively registered.
2021-11-02 16:58:14 +00:00
275f145418 BedrockData is now a Composer dependency
this should put a stop to people nagging me about incorrect blocks (we have a check to make sure composer dependencies are up to date).
2021-11-02 16:45:45 +00:00
6b07f7a5ec pmmp/BedrockProtocol@5.0.0+bedrock-1.17.40 2021-11-02 16:22:56 +00:00
e131c2cefa Drop pocketmine/spl 2021-11-02 16:08:29 +00:00
e34364412b Replace InvalidStateException usages with InvalidArgument or LogicException 2021-11-02 16:05:54 +00:00
4eef458d29 Config: throw AssumptionFailedError if config type is invalid or DETECT during save()
this should never happen ... it was already checked in load()
2021-11-02 16:03:43 +00:00
8b3565b75d PopulationTask no longer depends on a World object
this means it's now possible to test generation offline without too much hassle.
World::generateChunkCallback() has been removed from public API.
2021-11-02 15:40:51 +00:00
facfd7c04a sanity check 2021-11-02 15:26:54 +00:00
65ef9f786a Use standard chunkHash() to index population chunks 2021-11-02 15:25:03 +00:00
4dc13ab3da ConsoleReaderThread: strip control characters
this fixes a bug I encountered when accidentally pressing ctrl+a+d (which inserts a chr(1) character), because it made the server unable to find the command - but still reported an error containing what looked like a valid command (character isn't printable).
2021-11-02 15:11:23 +00:00
ede4157814 Check to see if the player can start using the Releasable item. (#4532) 2021-11-02 14:36:16 +00:00
34ea199fb0 World: fixed additional edge case - population promise rejected before task completion
if this happened, the index would stay set in activeChunkPopulationTasks, eventually causing the generation queue to jam up completely and non-forced generation to come to a standstill.
2021-11-02 14:30:23 +00:00
1775699f05 World: make sure that chunks locked by PopulationTask always get unlocked, no matter what
fixes #4527
2021-11-02 14:20:42 +00:00
32a857b8b4 fix CS 2021-11-02 14:09:16 +00:00
7e4be29fc4 Gracefully force-shutdown on failure to start RakLib
this now won't generate a crashdump.
2021-11-02 13:51:01 +00:00
c17587d436 World: use new Vector3() instead of Block->getPosition()
When profiling this, I noticed that we spend a stupidly large amount of time creating useless Position objects in the case of update=true, because Vector3->sides() calls Position->getSide(), which calls Position::fromObject(parent::getSide()). This is stupid because the update logic doesn't require Positions anywhere (as evidenced by this change needing no other alterations.

A rough profile shows that this improves setBlock() performance by about 25% in the update=true case, which is a pretty big margin.
As an added bonus, it gets rid of some unrealized cyclic dependencies in World->changedBlocks.
2021-11-02 03:00:00 +00:00
0f6b7e48cb Updated BedrockProtocol:
it's weirdly satisfying that LevelChunkPacket::create() with the extra parameter turns out to be exactly the same length as the old way.
2021-11-02 01:37:56 +00:00
f2912fcdd8 Updated pocketmine/log and pocketmine/log-pthreads (BC breaks included)
AttachableLogger deals with Closures now instead of LoggerAttachment objects
ThreadedLoggerAttachment no longer implements LoggerAttachment
2021-11-01 22:22:22 +00:00
f6cb4f9597 Updated BedrockProtocol 2021-11-01 21:32:02 +00:00
54442f7e4b Merge branch 'stable' 2021-11-01 21:18:35 +00:00
5257755dc5 shut 2021-11-01 21:15:58 +00:00
8c16ecaa5b Merge branch 'stable' 2021-11-01 21:08:32 +00:00
3214da8642 pthreads 4.0.0 2021-11-01 21:01:59 +00:00
f827a555d5 Merge branch 'stable' 2021-11-01 18:13:24 +00:00
61145baded Merge commit 27ae959e8 2021-11-01 17:42:31 +00:00
4d54d6c552 Merge commit f8f39687e2 2021-11-01 17:41:47 +00:00
485bc2c565 Merge commit 94737934de 2021-11-01 17:41:10 +00:00
07b4f844a9 Merge commit debb469de1 2021-11-01 17:40:40 +00:00
19e5775f6b Merge commit 73dc0598e4 2021-11-01 17:40:06 +00:00
804fb3f603 Merge commit 141fbde660 2021-11-01 17:39:40 +00:00
6175b03433 Merge commit 69952ae2af 2021-11-01 17:36:34 +00:00
a78248a19c Merge commit '71f2a34616961d6328f06fd911b6d4450a61643e' 2021-11-01 17:33:02 +00:00
414ccb9f10 Merge commit 'd17cd658030e66009c0bb32941387c7444d3ffe0' 2021-11-01 17:31:05 +00:00
794142fe49 Merge l7/l8/l9 baselines into actual-problems
it doesn't serve any practical purpose to keep these separated, particularly since it's getting so difficult to figure out which errors are coming from which levels (since we always use 9, it doesn't really make any difference).
2021-11-01 17:27:31 +00:00
ff27c5f7db PHPStan 1.0.0 2021-11-01 17:24:20 +00:00
4d4362801f AvailableCommandsPacket: remove dead code 2021-11-01 17:01:26 +00:00
0babe0a1ab LevelDB: remove unused private method 2021-11-01 16:55:05 +00:00
d696ebcda3 Level: do not use static:: to access levelIdCounter
the field is private.
2021-11-01 16:52:27 +00:00
c3768b997a Updated to pmmp/BedrockProtocol@146498c279 2021-11-01 16:13:33 +00:00
f6480017ce Update PHPUnit dependency junk 2021-11-01 16:12:37 +00:00
9f5c16bc46 Projectile: use closure instead of do/while for reading id/data of block
not ideal, but whatever I guess... this at least provides scope isolation
2021-11-01 15:56:28 +00:00
8865bb73ba BanEntry: remove useless do/while 2021-11-01 15:52:55 +00:00
2dee1dbc28 Remove ridiculous code in ResourcePackManager 2021-11-01 15:41:21 +00:00
0f0b6f0efa Utils: eliminate usages of backtick operator 2021-11-01 15:25:56 +00:00
d5f13d8be2 Timezone: make PHPStan 1.0 happy 2021-11-01 15:24:16 +00:00
27ae959e89 Terminal: backport shell_exec() code from PM4 to make PHPStan 1.0 happy 2021-11-01 15:23:36 +00:00
f8f39687e2 Achievement: declare proper type for $list static property 2021-11-01 15:22:33 +00:00
94737934de PlayerDeathEvent: fixed LSP violation reported by PHPStan 1.0 2021-11-01 14:17:54 +00:00
debb469de1 Updated PHPUnit dependency junk 2021-11-01 13:54:04 +00:00
616eb0050d World: remove premature optimisation of setBlockAt() introduced by ece28e5d7b
closes #4531
it turns out that letting the light updates themselves handle this is faster than trying to get in the way.
2021-11-01 02:34:44 +00:00
9d30bc8b95 World: fixed assertion failure when requesting, cancelling, and then re-requesting chunks via requestChunkPopulation()
if the request/cancel/re-request happens all in the time before the queue gets drained, chunk hashes may appear multiple times in the queue. We don't want to process them twice if this happens (although it's mostly harmless anyway).
2021-11-01 02:17:11 +00:00
46b7d35cd3 Player: return from callback if used chunk status is not REQUESTED_GENERATION()
this can happen especially on large render distances when flying fast and changing direction - we decide we don't want the chunk, then, after changing direction and re-ordering chunks, we decide we do want it again, and end up registering a second callback. In this case, we need to ensure that only one of the callbacks gets executed (it doesn't matter which one).
2021-11-01 01:45:49 +00:00
c781efcf90 World: avoid calling the same logic twice in requestChunkPopulation()
orderChunkPopulation() checks the preconditions too. Have them both call an internal function that doesn't.
2021-11-01 00:36:57 +00:00
e4a54f5b6a World: deduplicate code in request/orderChunkPopulation 2021-11-01 00:33:18 +00:00
afb54f1ae4 World: flip orderChunkPopulation() condition around
this makes it more obvious that the code is similar to requestChunkPopulation() barring one distinct difference.
2021-11-01 00:25:30 +00:00
8f803df511 World: check population locks _after_ checking if the chunk is already populated, not before
this led to another case where a population request would be queued up for an already-populated chunk for no reason.
2021-10-31 23:54:27 +00:00
f4a3c40b5c World: use better variable names in orderChunkPopulation() 2021-10-31 23:48:00 +00:00
9dec82cdbc World: fixed requestChunkPopulation() queuing requests for chunks which are already populated
this led to chunk sending getting bogged down if there were more than population-queue-size chunks waiting to be generated.
2021-10-31 23:40:34 +00:00
74031d2fbe World: remove the fulfilled promise from the population request map
fixes crash when unregistering chunk loaders
2021-10-31 23:05:12 +00:00
bd60e41268 Revert "Revert "Player: do not re-request the same ungenerated chunks multiple times""
This reverts commit 3265d3f6b4.
2021-10-31 22:57:56 +00:00
96cfdc79b8 World: fixed original promise not getting fulfilled if the chunk became populated=true after a promise was already made (but not fulfilled) to populate it
this could happen if a plugin calls setPopulated(true) on a chunk after a request for its population landed in the queue, but before it actually got processed. In that case, the promise would never get fulfilled.
2021-10-31 22:54:37 +00:00
2fa0a914ff World::orderChunkPopulation() may return a pre-resolved promise
this does not indicate a failure; it indicates that the chunk has already been successfully populated.
In this case, we shouldn't be putting the task back on the queue.

This is skirting around the real bug, which is that requestChunkPopulation() doesn't check if the target chunk is already populated before it creates a new promise that it will be.
2021-10-31 22:51:37 +00:00
08636d079d Promise: expose isResolved() 2021-10-31 22:48:52 +00:00
3265d3f6b4 Revert "Player: do not re-request the same ungenerated chunks multiple times"
This reverts commit 866020dfdb.

For some fucking reason this broke resending chunks in some cases (and
sending chunks at all in others). I don't have time to debug this right
now, so it's going to have to remain broken, infuriatingly enough.
2021-10-31 21:25:21 +00:00
0f78a2b5ef Advisory chunk locking for chunk population (#4513)
this allows chunks locked for population to be modified. If the PopulationTask detects that the chunk was modified during the onCompletion(), the result of the population will be discarded and rescheduled, so that it includes user modifications.
2021-10-31 21:11:20 +00:00
f1a791ef75 Improved Promise API - separate resolver and consumer APIs
this makes creating a promise slightly more cumbersome, but I'm more concerned about people who might try to call 'new Promise' directly.
2021-10-31 19:49:57 +00:00
866020dfdb Player: do not re-request the same ungenerated chunks multiple times
this doesn't affect chunk resends, since they'll be kicked back to NEEDED, which is detected by orderChunks().
2021-10-31 18:52:37 +00:00
c580bb2434 InGamePacketHandler: mark player as not using item in more cases
fixes #4355
2021-10-31 14:41:31 +00:00
4fe3f69702 World: eliminate final remaining 'no loaders attached' debug message on player creation 2021-10-31 14:33:27 +00:00
018006541e changelog: mention block-picking changes
[ci skip]
2021-10-31 14:12:12 +00:00
fbb91d123d World::unregisterChunkListenerFromAll(): go through unregisterChunkListener()
this ensures that everything gets cleaned up properly (e.g. player chunk listeners).
2021-10-31 14:03:40 +00:00
3dc75644d9 World: avoid duplicated logger code in initChunk() 2021-10-31 14:02:25 +00:00
1cabe4baf3 World: do not crash on duplicate tiles loaded from disk
closes #4049
2021-10-31 13:58:32 +00:00
73dc0598e4 CrashDump: remove derp space 2021-10-30 23:22:37 +01:00
faad2365e2 World: Register a temporary chunk loader on chunks used by PopulationTask
fixes #3839
2021-10-30 22:17:06 +01:00
4f816d03a7 SurvivalBlockBreakHandler: remove useless code 2021-10-30 21:35:58 +01:00
1d22761d27 Remove useless newline 2021-10-30 21:25:47 +01:00
5b8ce7e3e2 Cake: fixed desync on cancellation of eating
closes #3591
we don't support eating in creative right now, but the cake shouldn't appear to be eaten when it's not.
2021-10-30 21:02:24 +01:00
08f3c18de9 Arrow: do not add pickups to creative players' inventories
closes #2932
2021-10-30 17:16:46 +01:00
141fbde660 Player: fixed getting re-banned on rejoin after unban from hardcore death
closes #2175
2021-10-30 16:58:03 +01:00
465a509858 World: remove spammy debug message 2021-10-30 16:13:01 +01:00
69952ae2af Human: limit lifetime total XP range to INT32_MAX
closes #4484
2021-10-30 16:05:10 +01:00
71f2a34616 Entity: spawnTo() must silently swallow errors
Player->showPlayer() assumes that spawnTo() will take care of all the checks necessary to ensure we don't actually spawn a player to someone it shouldn't be able to see. In PM3, there's nothing we can do about that.
This could be a problem if anything decides to override spawnTo() to do additional stuff and assumes that the function will always succeed; however, there's not much reason to do that (plugins sending packets should override sendSpawnPacket() instead).
2021-10-30 15:38:27 +01:00
63dfcc60c3 4.0.0-BETA9 is next 2021-10-29 22:35:23 +01:00
428bd5ae91 Release 4.0.0-BETA8 2021-10-29 22:35:15 +01:00
d17cd65803 3.25.2 is next 2021-10-29 22:23:28 +01:00
a8d5e8c5f6 Release 3.25.1 2021-10-29 22:23:22 +01:00
19f448d074 pocketmine/math 0.4.0 2021-10-29 21:56:56 +01:00
1c18c731ef bootstrap: check for zlib raw support in leveldb 2021-10-29 19:15:12 +01:00
8a2ecfe1d4 Merge branch 'stable' 2021-10-29 19:04:01 +01:00
089e62b44e Entity::spawnTo(): verify that the target player belongs to the same world as the entity
this should never be hit in the PM case, but it's an InvalidArgument rather than AssumptionFailedError because plugins can and do call this with bad things.
2021-10-29 18:54:00 +01:00
32a34d2494 Location: change order of constructor parameters
to be consistent with Position::__construct() and Location::fromObject() (although Location::fromObject() has no choice, thanks to the anti-feature known as late static binding ...)
2021-10-29 15:43:09 +01:00
ee9f5e0044 Location: make __construct() parameters mandatory
I did consider allowing yaw/pitch to remain optional, but considering the implicit immutability of Location, it really doesn't make any sense to create a Location with default yaw/pitch - just create a Position in that case instead.
2021-10-29 15:40:58 +01:00
88b7389080 InventoryManager: reduce code duplication 2021-10-29 15:37:52 +01:00
f1cc168d26 phpstan: exclude a couple of files from analysis temporarily
close #4472
2021-10-29 00:23:13 +01:00
fb5543a2ad Updated BedrockProtocol dependency 2021-10-29 00:16:11 +01:00
a4eda9a8f5 World: call nearby entities' onNearbyBlockChange() in setChunk()
fixes #2779 in all known cases.
2021-10-28 23:59:32 +01:00
eb75df6f8e World: Intelligently perform automatic transfer or deletion of tiles in setChunk(), depending on the context
tiles may be deleted in the following circumstances:
1) the target block in the new chunk doesn't expect a tile
2) the target block in the new chunk expects a different type of tile (responsibility of the plugin developer to create the new tile)
3) there's already a tile in the target chunk which conflicts with the old one

In all other cases, the tile will be transferred.

This resolves a large number of unintentional bugs caused by world editors replacing chunks without setting the deleteTilesAndEntities parameter to false (even the core itself does it).

closes #4520
2021-10-28 23:48:17 +01:00
c66790b6a6 World: never delete entities in setChunk()
entities exist completely independently from chunks now, so there is no need to interact with them whatsoever.
As I wrote in #4520, there's no sense in deleting entities here, since a chunk replacement is essentially just a mass block update.

On that theme, it might be a good idea to call Entity->onNearbyBlockChange() for all entities in the target and adjacent chunks when replacing a chunk, to ensure that they get the proper movement updates.
2021-10-28 23:42:28 +01:00
d78801b9d5 World: fixed tiles and entities getting deleted when adjacent chunks are modified during population 2021-10-28 22:24:47 +01:00
d410db4302 Chunk: rename DIRTY_FLAG_TERRAIN to DIRTY_FLAG_BLOCKS
we use the word 'terrain' elsewhere to refer to the combination of blocks and biomes, so using TERRAIN here is misleading.
2021-10-28 22:11:07 +01:00
a62ce64fdd Revert "Chunk: added modification counter"
This reverts commit a5418a019d.

The more I assessed this, the more I realized that this implementation
doesn't actually offer any value. Since modcounters don't persist after
chunk unload + reload, they can't be reliably used to detect changes in
chunks without additional event subscriptions.
For the purpose I actually intended to use them for (population task
cancellation) there's a) another solution, and b) modcounts are
unreliable for that too, because of the aforementioned potential for
chunks to get unloaded and reloaded.
For the case of detecting dirty chunks within PopulationTask itself,
they are also unnecessary, since the dirty flags are sufficient within
there, since FastChunkSerializer doesn't copy dirty flags.

In conclusion, this was a misbegotten addition with little real value,
but does impact performance in hot paths.
2021-10-28 21:02:04 +01:00
5db3915aad Make MemoryManager aware of ChunkCache 2021-10-28 20:28:00 +01:00
eb40b741ae StandardPacketBroadcaster now splits broadcasts by session-specific PacketSerializerContext
in the normal case, all sessions will share the same PacketSerializerContext and Compressor, so this code will be the same as before
However, for the multi-protocol hackers out there, this should reduce the maintenance burden (@Driesboy) since now only the PacketSerializerContext needs to be maintained. I recommend a separate PacketSerializerContext for each protocol (perhaps put the protocol version in the serializer context too, if you need it for some reason).
2021-10-28 20:15:37 +01:00
b3720b3f17 4.0.0-BETA8 is next 2021-10-28 17:28:53 +01:00
7effa03ba4 Release 4.0.0-BETA7 2021-10-28 17:28:48 +01:00
d0474ccd92 make-release: note which channel the build will be released into 2021-10-28 16:29:20 +01:00
2b0768f720 make-release: fixed retention of +dev on release versions 2021-10-28 16:26:56 +01:00
dba148cfaa build/make-release: make arg parsing use getopt 2021-10-28 16:25:18 +01:00
48f77abe7e Leave channel ID in VersionInfo
so that I don't have to type it out every time I make a new release. Most of the time it's going to be posted to the same channel as before anyway.
2021-10-28 16:03:43 +01:00
bb05af103d PluginManager: fixed crash when using a plugin-loader plugin (read: devtools)
closes #4518
2021-10-28 15:55:05 +01:00
0ef5c67b9b Use static constructor for MovePlayerPacket
this marks the last of the packets created using the old way.
2021-10-27 21:10:16 +01:00
6d89265510 Player: reduce code duplication
back when this was just hardcoded >> 4 everywhere, nobody thought anything of it, but now it uses constants, it's easy to cross-reference and see where the duplicates are.
2021-10-26 23:02:50 +01:00
a7d8a598e1 World: reduce code duplication for chunk coordinate calculation 2021-10-26 22:58:17 +01:00
51fbff204b World: make PhpStorm understand return type of getAdjacentChunks() 2021-10-26 20:32:09 +01:00
1873457840 PopulationTask: stop using dynamic properties 2021-10-26 20:21:58 +01:00
fca70efbb1 World: move chunk population related methods to be in the same overall place 2021-10-26 16:44:08 +01:00
8f88393184 World: Specialize generateChunkCallback() for PopulationTask
this allows us to also set the adjacent chunks before calling ChunkPopulateEvent, to give a more accurate picture of what changed.
2021-10-26 15:28:00 +01:00
b9d9b69bbe ConsoleReaderThread: trim the string before returning it
it will have a newline at the end that was added by the subprocess when posting it to the main process.
2021-10-26 01:07:14 +01:00
1d99cd329a CS again 2021-10-26 00:50:43 +01:00
bd8cba1a7f Added unit tests for Utils::testValidInstance() 2021-10-26 00:49:41 +01:00
24d4daec90 Utils::testValidInstance() now accepts interfaces for the baseName 2021-10-26 00:32:32 +01:00
4178c81209 Utils: fixed testValidInstance() not accepting the same valid class for both className and baseName
this caused problems in PlayerCreationEvent because plugins set the base class and then set the player class to the same thing.
2021-10-26 00:31:30 +01:00
94f4ef5862 PopulationTask: Throw AssumptionFailedError if center chunk is null for some reason 2021-10-25 21:07:03 +01:00
2e2515354c PopulationTask: fixed undefined method call
fuck you PhpStorm! fuck you PhpStorm! fuck you PhpStorm!
2021-10-25 20:57:43 +01:00
359d0835f3 CS 2021-10-25 20:54:39 +01:00
d4cbde6f10 PopulationTask: use modification counters to detect changed chunks
instead of using terrain dirty flags, which aren't suitable for this purpose
2021-10-25 20:53:50 +01:00
a5418a019d Chunk: added modification counter
this is independent from the terrain dirty flags (which are specifically used to track state of chunks needing to be saved).
2021-10-25 20:53:11 +01:00
baba25953f Chunk: make all parameters of __construct() mandatory and non-nullable
having the constructor fill in defaults for these invariably causes bugs.
2021-10-25 20:22:50 +01:00
d53347454b Chunk: use HeightArray::fill() 2021-10-25 20:17:30 +01:00
401e8d117b Flat: use a less dumb way to build biome array 2021-10-25 20:15:33 +01:00
9835d75f65 Chunk: removed heighArray parameter from constructor
we don't pass this anywhere, and really it should be dynamically initialized anyway, just like light.
2021-10-25 20:13:50 +01:00
b8519d1af4 World: fixed every chunk having terrain saved at least once, even if unmodified
setPopulated() sets dirty flags on the chunk, causing the autosave sweep
to think they've been changed when they haven't. We now pass
terrainPopulated to the constructor to avoid this ambiguity recurring in
the future.
2021-10-25 19:53:47 +01:00
f6e53f826b Fixed Anvil/McRegion chunks getting autosaved on first time, even when unchanged
setGenerated/setPopulated and friends set hasChanged = true, which causes the world to autosave them the first time around, even though they weren't modified.
2021-10-25 19:52:44 +01:00
42ede30e77 ... 2021-10-23 23:57:28 +01:00
04aedc6494 Updated BedrockProtocol 2021-10-23 23:54:49 +01:00
701a71a4ee Sound::encode() position is no longer nullable
making this nullable was based on the invalid assumption that global sounds have no position, but it turns out they _do_ still use the position to make the sound come from the correct direction.
2021-10-23 02:01:26 +01:00
e50072dc27 Clean PHPStan baselines 2021-10-23 01:55:10 +01:00
c77829f4ad Migrate packet creation to use ::create() methods in all but one case
MovePlayerPacket doesn't yet have a ::create() due to a complication with fields that aren't always present.
2021-10-23 01:46:01 +01:00
c773e43eda Updated BedrockProtocol to pmmp/BedrockProtocol@97fa88e9ef 2021-10-23 01:16:45 +01:00
c262c2e726 Updated composer dependencies 2021-10-23 01:14:54 +01:00
a4b65d6a3f PlayerCreationEvent: verify that the class actually exists and is instantiable
this ensures that crashdumps blame the plugin instead of the core on bad classes, such as in this case: https://crash.pmmp.io/view/5331225
2021-10-21 20:37:45 +01:00
986b4e0651 Enforce single-line PhpDoc for properties where possible 2021-10-21 20:32:37 +01:00
2971bf30a5 actions: combine code verify into one step
this way the diff takes one less click to get to.
2021-10-21 00:30:19 +01:00
4f2bcb61d6 Fixed crashdump generation when crashing before PluginManager was created 2021-10-20 23:35:04 +01:00
e2275cc8ec PluginManager: Prevent infinite recursion in loadPlugins()
if a plugin calls loadPlugins(server->getPluginPath()) during its onLoad(), and it itself is in that plugin path, an infinite recursion will occur.
2021-10-20 23:10:18 +01:00
620874d902 PluginManager: Extract checkPluginLoadability() to a PluginLoadabilityChecker unit
this can be more easily unit-tested.
2021-10-20 22:31:56 +01:00
44508a138f Moved plugin extension requirement checks to PluginManager::checkPluginLoadability()
these don't really belong in PluginDescription.
2021-10-20 22:13:30 +01:00
aa408c9a97 Fixed 9646128d01 2021-10-20 21:54:57 +01:00
6d78a0b435 CS 2021-10-20 21:52:42 +01:00
76b4b23d98 PluginManager: remove loadPlugin()
loadPlugins() is now the preferred option, since it does all the proper checks.
In addition, the server now acknowledges that loading a single plugin may cause multiple plugins to be loaded, so returning only a single Plugin is not representative of what's actually happening.
2021-10-20 21:52:19 +01:00
03fcd844eb PluginManager::loadPlugins() now accepts files as well as directories
loadPlugins() is now a superior option to loadPlugin(), since it enforces dependency checks and also supports automatic loading of plugins when new loaders are installed.
2021-10-20 21:36:14 +01:00
fecc13f362 Merge branch 'master' of github.com:pmmp/PocketMine-MP 2021-10-20 21:23:14 +01:00
9646128d01 Updated resources/locale submodule to pmmp/Language@09c709f242 2021-10-20 21:22:56 +01:00
a788954551 Fixed dependency handling across plugin loaders (#3971) 2021-10-20 20:22:00 +01:00
dc07ac33d3 protocol: fixed missing field of CraftRecipeAuto 2021-10-20 19:47:32 +01:00
ec3986827c Update BedrockProtocol to 3.0.1, widen constraint to allow newer patch versions 2021-10-20 16:20:10 +01:00
09c840b66a Update transient composer dependencies 2021-10-20 16:19:20 +01:00
80b402e529 ItemTranslator: throw the proper exceptions when failing to map network IDs 2021-10-20 14:01:39 +01:00
a3f8546ac4 4.0.0-BETA7 is next 2021-10-19 19:13:43 +01:00
200 changed files with 4382 additions and 2893 deletions

View File

@ -42,7 +42,7 @@ jobs:
sed -i "s/const BUILD_NUMBER = 0/const BUILD_NUMBER = ${BUILD_NUMBER}/" src/VersionInfo.php
- name: Minify BedrockData JSON files
run: php resources/vanilla/.minify_json.php
run: php vendor/pocketmine/bedrock-data/.minify_json.php
- name: Build PocketMine-MP.phar
run: php -dphar.readonly=0 build/server-phar.php --git ${{ github.sha }}

View File

@ -91,8 +91,6 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Restore PHP build cache
id: php-build-cache
@ -195,8 +193,6 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Restore PHP build cache
id: php-build-cache
@ -238,11 +234,10 @@ jobs:
- name: Regenerate KnownTranslation APIs
run: php build/generate-known-translation-apis.php
- name: Run git diff
run: git diff
- name: Fail job if changes were made
run: git diff --quiet
- name: Verify code is unchanged
run: |
git diff
git diff --quiet
codestyle:
name: Code Style checks
@ -254,10 +249,10 @@ jobs:
- uses: actions/checkout@v2
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.9.0
uses: shivammathur/setup-php@2.15.0
with:
php-version: 8.0
tools: php-cs-fixer
tools: php-cs-fixer:3.2
- name: Run PHP-CS-Fixer
run: php-cs-fixer fix --dry-run --diff

6
.gitmodules vendored
View File

@ -1,12 +1,6 @@
[submodule "resources/locale"]
path = resources/locale
url = https://github.com/pmmp/Language.git
[submodule "tests/plugins/DevTools"]
path = tests/plugins/DevTools
url = https://github.com/pmmp/DevTools.git
[submodule "build/php"]
path = build/php
url = https://github.com/pmmp/php-build-scripts.git
[submodule "resources/vanilla"]
path = resources/vanilla
url = https://github.com/pmmp/BedrockData.git

View File

@ -61,6 +61,11 @@ return (new PhpCsFixer\Config)
],
'sort_algorithm' => 'alpha'
],
'phpdoc_line_span' => [
'property' => 'single',
'method' => null,
'const' => null
],
'phpdoc_trim' => true,
'phpdoc_trim_consecutive_blank_line_separation' => true,
'single_import_per_statement' => true,

View File

@ -172,7 +172,7 @@ HEADER;
echo "Done generating KnownTranslationFactory.\n";
}
$lang = parse_ini_file(Path::join(\pocketmine\RESOURCE_PATH, "locale", "eng.ini"), false, INI_SCANNER_RAW);
$lang = parse_ini_file(Path::join(\pocketmine\LOCALE_DATA_PATH, "eng.ini"), false, INI_SCANNER_RAW);
if($lang === false){
fwrite(STDERR, "Missing language files!\n");
exit(1);

View File

@ -25,18 +25,26 @@ namespace pocketmine\build\make_release;
use pocketmine\utils\VersionString;
use pocketmine\VersionInfo;
use function count;
use function array_keys;
use function array_map;
use function dirname;
use function fgets;
use function file_get_contents;
use function file_put_contents;
use function fwrite;
use function getopt;
use function is_string;
use function max;
use function preg_replace;
use function sleep;
use function sprintf;
use function str_pad;
use function strlen;
use function system;
use const STDERR;
use const STDIN;
use const STDOUT;
use const STR_PAD_LEFT;
require_once dirname(__DIR__) . '/vendor/autoload.php';
@ -60,22 +68,38 @@ function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev
file_put_contents($versionInfoPath, $versionInfo);
}
/**
* @param string[] $argv
* @phpstan-param list<string> $argv
*/
function main(array $argv) : void{
if(count($argv) < 2){
fwrite(STDERR, "Arguments: <channel> [release version] [next version]\n");
exit(1);
const ACCEPTED_OPTS = [
"current" => "Version to insert and tag",
"next" => "Version to put in the file after tagging",
"channel" => "Release channel to post this build into"
];
function main() : void{
$filteredOpts = [];
foreach(getopt("", ["current:", "next:", "channel:", "help"]) as $optName => $optValue){
if($optName === "help"){
fwrite(STDOUT, "Options:\n");
$maxLength = max(array_map(fn(string $str) => strlen($str), array_keys(ACCEPTED_OPTS)));
foreach(ACCEPTED_OPTS as $acceptedName => $description){
fwrite(STDOUT, str_pad("--$acceptedName", $maxLength + 4, " ", STR_PAD_LEFT) . ": $description\n");
}
exit(0);
}
if(!is_string($optValue)){
fwrite(STDERR, "--$optName expects exactly 1 value\n");
exit(1);
}
$filteredOpts[$optName] = $optValue;
}
if(isset($argv[2])){
$currentVer = new VersionString($argv[2]);
if(isset($filteredOpts["current"])){
$currentVer = new VersionString($filteredOpts["current"]);
}else{
$currentVer = VersionInfo::VERSION();
$currentVer = new VersionString(VersionInfo::BASE_VERSION);
}
if(isset($argv[3])){
$nextVer = new VersionString($argv[3]);
if(isset($filteredOpts["next"])){
$nextVer = new VersionString($filteredOpts["next"]);
}else{
$nextVer = new VersionString(sprintf(
"%u.%u.%u",
@ -84,8 +108,10 @@ function main(array $argv) : void{
$currentVer->getPatch() + 1
));
}
$channel = $filteredOpts["channel"] ?? VersionInfo::BUILD_CHANNEL;
echo "About to tag version $currentVer. Next version will be $nextVer.\n";
echo "$currentVer will be published on release channel \"$channel\".\n";
echo "please add appropriate notes to the changelog and press enter...";
fgets(STDIN);
system('git add "' . dirname(__DIR__) . '/changelogs"');
@ -95,10 +121,10 @@ function main(array $argv) : void{
exit(1);
}
$versionInfoPath = dirname(__DIR__) . '/src/VersionInfo.php';
replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $argv[1]);
replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $channel);
system('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"');
system('git tag ' . $currentVer->getBaseVersion());
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, "");
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel);
system('git add "' . $versionInfoPath . '"');
system('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"');
echo "pushing changes in 5 seconds\n";
@ -106,4 +132,4 @@ function main(array $argv) : void{
system('git push origin HEAD ' . $currentVer->getBaseVersion());
}
main($argv);
main();

View File

@ -9,3 +9,15 @@ Plugin developers should **only** update their required API to this version if y
# 3.25.0
- Added support for Minecraft: Bedrock Edition 1.17.40.
- Removed compatibility with earlier versions.
# 3.25.1
- Fixed autosave bug that caused unmodified chunks to be saved at least once (during the first autosave after they were loaded).
- `Entity->spawnTo()` now has an additional sanity check for matching worlds (might expose a few new errors in plugins).
- Fixed a missing field in `CraftRecipeAuto` item stack request type.
# 3.25.2
- Now analysed using level 9 on PHPStan 1.0.0.
- `ext-pthreads` v4.0.0 or newer is now required.
- Fixed crash in `Player->showPlayer()` when the target is not in the same world.
- `Human->setLifetimeTotalXp()` now limits the maximum value to 2^31.
- Fixed players, who died in hardcore mode and were unbanned, getting re-banned on next server join.

View File

@ -1274,6 +1274,8 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
### Inventory
- Implemented offhand inventory.
- Block-picking is now supported in survival mode.
- Block picking behaviour now matches vanilla (no longer overwrites held item, jumps to existing item where possible).
# 4.0.0-BETA2
Released 10th September 2021.
@ -1460,3 +1462,175 @@ Released 19th October 2021.
## Gameplay
- Picking up some items from a dropped stack of items is now supported. This fixes various bugs with being unable to pick up items with an almost-full inventory.
# 4.0.0-BETA7
Released 28th October 2021.
## General
- Phar plugins are now able to depend on folder plugins loaded by DevTools.
- Now uses [`pocketmine/bedrock-protocol@58c53a259e819a076bf8fe875d2a012da7d19d65`](https://github.com/pmmp/BedrockProtocol/tree/58c53a259e819a076bf8fe875d2a012da7d19d65). This version features significant changes, including:
- Standardisation of various field names (e.g. `eid` -> `actorRuntimeId`, `evid` -> `eventId`)
- Rename of `entity` related fields to `actor` where appropriate (e.g. `entityRuntimeId` -> `actorRuntimeId`)
- Block position `x`/`y`/`z` fields replaced by `BlockPosition`
- Static `::create()` functions for all packets, which ensure that fields can't be forgotten
## Fixes
- Fixed server crash when clients send itemstacks with unmappable dynamic item IDs.
- Fixed server crash on invalid ItemStackRequest action types.
- Fixed autosave bug that caused unmodified chunks to be saved at least once (during the first autosave after they were loaded).
- Fixed `ConsoleReaderThread` returning strings with newlines still on the end.
- Fixed changes made to adjacent chunks in `ChunkPopulateEvent` (e.g. setting blocks) sometimes getting overwritten.
## API
### Event
- `PlayerCreationEvent` now verifies that the player class set is instantiable - this ensures that plugins get properly blamed for breaking things.
### World
- `World->generateChunkCallback()` has been specialized for use by `PopulationTask`. This allows fixing various inconsistencies involving `ChunkPopulateEvent` (e.g. modifications to adjacent chunks in `ChunkPopulationEvent` might be wiped out, if the population of the target chunk modified the adjacent chunk).
- It now accepts `Chunk $centerChunk, array<int, Chunk> $adjacentChunks` (instead of `?Chunk $chunk`).
- It's no longer expected to be used by plugins - plugins should be using `World->setChunk()` anyway.
- `Chunk->getModificationCounter()` has been added. This is a number starting from `0` when the `Chunk` object is first created (unless overridden by the constructor). It's incremented every time blocks or biomes are changed in the chunk. It resets after the chunk is unloaded and reloaded.
- The following API methods have changed signatures:
- `Sound->encode()` no longer accepts `null` for the position.
- `Chunk->__construct()`: removed `HeightArray $heightMap` parameter, added `bool $terrainPopulated` and `int $modificationCounter` parameters.
### Plugin
- `PluginManager->loadPlugins()` now accepts paths to files as well as directories, in which case it will load only the plugin found in the target file.
- The following API methods have been removed:
- `PluginManager->loadPlugin()`: use `PluginManager->loadPlugins()` instead
# 4.0.0-BETA8
Released 29th October 2021.
## General
- Chunk packet caches are now cleared by the memory manager on low memory.
- `Entity->spawnTo()` now has an additional sanity check for matching worlds (might expose a few new errors in plugins).
- [`pocketmine/math` 0.4.0](https://github.com/pmmp/Math/releases/tag/0.4.0) is now used. Please see its release notes for changes.
## Fixes
- Zlib raw check for LevelDB is now done directly on startup, avoiding crashes when later trying to load worlds.
- Fixed tiles and entities getting deleted from adjacent chunks during chunk population.
- Fixed players being unable to open their inventories more than once.
- Fixed entities not getting updated when a nearby chunk is replaced (e.g. dropped items would float in midair if the ground was lower than before)
## API
### World
- `World::setChunk()` has the following changes:
- `$deleteEntitiesAndTiles` parameter has been removed.
- Entities are no longer deleted on chunk replacement.
- Tiles are no longer deleted on chunk replacement, unless one of the following conditions is met:
- the target block in the new chunk doesn't expect a tile
- the target block in the new chunk expects a different type of tile (responsibility of the plugin developer to create the new tile)
- there's already a tile in the target chunk which conflicts with the old one
- `Location::__construct()` has the following changes:
- `world` parameter is now 4th instead of last.
- All parameters are now mandatory.
- Reverted addition of chunk modification counters in previous beta.
- `Chunk::DIRTY_FLAG_TERRAIN` has been renamed to `Chunk::DIRTY_FLAG_BLOCKS`.
# 4.0.0-BETA9
Released 2nd November 2021.
## General
- Now analysed using level 9 on PHPStan 1.0.0.
- `ext-pthreads` v4.0.0 or newer is now required.
- `resources/vanilla` submodule has been removed. BedrockData is now included via Composer dependency [`pocketmine/bedrock-data`](https://packagist.org/packages/pocketmine/bedrock-data).
- `pocketmine/spl` Composer dependency has been dropped.
- The following Composer dependency versions are now required:
- [`pocketmine/bedrock-protocol` v5.0.0](https://github.com/pmmp/BedrockProtocol/tree/5.0.0+bedrock-1.17.40) features substantial changes to its API compared to 3.0.1, which was used in 4.0.0-BETA8. Please see its [release notes](https://github.com/pmmp/BedrockData/releases/tag/5.0.0+bedrock-1.17.40).
- [`pocketmine/log` v0.4.0](https://github.com/pmmp/Log/tree/0.4.0) removes the `LoggerAttachment` interface and replaces logger attachment objects with closures.
- [`pocketmine/log-pthreads` v0.4.0](https://github.com/pmmp/LogPthreads/tree/0.4.0)
- [`pocketmine/classloader` v0.2.0](https://github.com/pmmp/ClassLoader/tree/0.2.0)
- A noisy debug message in `World->updateAllLight()` has been removed.
## API
### Entity
- `Human->setLifetimeTotalXp()` now limits the maximum value to 2^31.
### Event
- `BlockGrowEvent` is now called when cocoa pods grow.
### Item
- Added `Releasable->canStartUsingItem()`.
### Network
- Added `NetworkInterfaceStartException`, which may be thrown by `Network->registerInterface()` and `NetworkInterface->start()`.
### Player
- `SurvivalBlockBreakHandler::createIfNecessary()` has been removed.
- `SurvivalBlockBreakHandler->__construct()` is now public.
- `UsedChunkStatus::REQUESTED()` has been renamed to `REQUESTED_SENDING`.
- `UsedChunkStatus::REQUESTED_GENERATION()` has been added.
### Utils
- `Promise` API has changed:
- Promise-related classes have been moved to `pocketmine\promise` namespace.
- It's now split into `Promise` and `PromiseResolver`.
- `PromiseResolver` provides only `resolve()` and `reject()`. It should be used by callbacks to resolve a promise.
- `Promise` now provides only `onCompletion()` and `isResolved()` APIs. This should be given to consumers to allow them to handle the result of the async operation.
- `PromiseResolver` must not be created directly. Use `new PromiseResolver` and `PromiseResolver->getPromise()`.
### World
- Improved performance of `setBlock()` by around 35% when the `$update` parameter is set to `true`.
- Improved performance of `setBlock()` by around 30% when the `$update` parameter is set to `false`.
- `World->generateChunkCallback()` is no longer exposed to public API.
- `World->getAdjacentChunks()` now returns an array indexed using `World::chunkHash()`, where the `x` and `z` components are the relative offsets from the target chunk (range -1 to +1).
- `World->lockChunk()` now requires `ChunkLockId $lockId` parameter.
- `World->unlockChunk()` now requires a `?ChunkLockId $lockId` parameter. If a non-null lockID is given, the lock on the chunk will only be removed if it matches the given lockID.
- `World->unlockChunk()` now returns `bool` instead of `void` (to signal whether unlocking succeded or not).
- Added `ChunkLockId` class.
## Fixes
### World
- Fixed server crash when tiles with colliding positions are loaded from saved data. Now, an error is logged, but the server won't crash.
- Fixed server crash when liquids and other items flow into terrain locked for population. Now, an advisory locking mechanism is used, and population results will be discarded and recalculated if modifications are detected.
- Fixed various crashes that could occur if a chunk was flagged with `setPopulated(true)` after a promise had already been created for its population.
- Fixed `AssumptionFailedError` in `PopulationTask` when workers previously used for generation are shutdown, and then restarted on the fly by a generation request.
- Fixed assertion failure in `World->drainPopulationRequestQueue()` when requesting, cancelling and then re-requesting generation of a chunk while the generator was busy.
- Fixed generation potentially getting stuck if a population request was cancelled while the population task was running (failure to remove locks from used chunks).
- Fixed `World->requestChunkPopulation()` not taking into account that the target chunk may already be populated. This caused a variety of strange bugs and performance issues.
- Fixed potential memory leak caused by `World->unregisterChunkListenerFromAll()` not taking players into account.
- Fixed debug spam of `chunk has no loaders registered` messages during chunk generation.
### Other fixes
- Fixed server crash when unable to bind to the desired port. Now, the server will show an error and gracefully stop without a crashdump instead.
- Fixed server crash in `Player->showPlayer()` when the target is not in the same world.
- Fixed players, who died in hardcore mode and were unbanned, getting re-banned on next server join.
- Fixed cake block desync when attempting to eat in creative (eating in creative is not yet supported, but the block rollback was missing).
- Fixed players being able to eat items more quickly by dropping them while eating.
- Fixed arrows getting added to creative players' inventories when picked up.
- Fixed players re-requesting the same ungenerated chunks multiple times before they were sent.
- Fixed commands not working in some cases after using some control sequences on the console.
# 4.0.0-BETA10
Released 2nd November 2021.
## Fixes
- Fixed an issue with BedrockData JSON minification which broke the release build of 4.0.0-BETA9.
# 4.0.0-BETA11
Released 6th November 2021.
## General
- `resources/locale` submodule has been removed. Language files are now included via Composer dependency [`pocketmine/locale-data`](https://packagist.org/packages/pocketmine/locale-data).
- This means it's now possible to run a server from git sources without cloning submodules :)
- All remaining submodules (DevTools, build/php) are non-essential for building and running a server.
- Added a tool `tools/simulate-chunk-sending.php` to visualise the behaviour of `ChunkSelector`.
## Fixes
- Fixed server crash on saving when player XP has reached int32 max (XP is now capped, similar to Java Edition).
- Fixed another edge case in chunk generation that led to assertion failures.
- Fixed server crash when finding a list of `TAG_Float` for entity positions instead of `TAG_Double`.
- Fixed fast eating when picking up items.
- Fixed terrain being invisible for a long time when teleporting into ungenerated terrain.
- Fixed weird chunk loading when teleporting into ungenerated terrain (sometimes farther chunks would render before closer ones, leaving holes in the map temporarily).
- Fixed players re-requesting chunks when turning their heads or jumping.
- Fixed bonemeal sometimes being consumed even when cancelling `BlockGrowEvent` and `StructureGrowEvent`.
## API
### Event
- Added `PlayerEmoteEvent`.
### Gameplay
- Chunks are now sent in proper circles. This improves the experience when flying quickly parallel to X or Z axis, since now more chunks in front of the player will load sooner.
- Added support for emotes.

View File

@ -22,7 +22,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pthreads": "~3.2.0",
"ext-pthreads": "^4.0",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
@ -34,27 +34,28 @@
"adhocore/json-comment": "^1.1",
"fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0",
"pocketmine/bedrock-protocol": "3.0.0+bedrock1.17.40",
"pocketmine/bedrock-data": "^1.4.0+bedrock-1.17.40",
"pocketmine/bedrock-protocol": "^5.0.0+bedrock-1.17.40",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "dev-master",
"pocketmine/classloader": "^0.2.0",
"pocketmine/color": "^0.2.0",
"pocketmine/errorhandler": "^0.3.0",
"pocketmine/log": "^0.3.0",
"pocketmine/log-pthreads": "^0.2.0",
"pocketmine/math": "^0.3.0",
"pocketmine/locale-data": "^1.0.3",
"pocketmine/log": "^0.4.0",
"pocketmine/log-pthreads": "^0.4.0",
"pocketmine/math": "^0.4.0",
"pocketmine/nbt": "^0.3.0",
"pocketmine/raklib": "^0.14.2",
"pocketmine/raklib-ipc": "^0.1.0",
"pocketmine/snooze": "^0.3.0",
"pocketmine/spl": "dev-master",
"ramsey/uuid": "^4.1",
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-phpunit": "^0.12.6",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpstan/phpstan": "1.0.2",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.2"
},
"autoload": {

312
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "ddffa959e1045fc084bb9142884f27a3",
"content-hash": "e36db7fb94bd79034dcc599c3029a621",
"packages": [
{
"name": "adhocore/json-comment",
@ -248,17 +248,43 @@
"time": "2020-12-01T19:48:11+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
"version": "3.0.0+bedrock1.17.40",
"name": "pocketmine/bedrock-data",
"version": "1.4.0+bedrock-1.17.40",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "ea9e22563b3d56f3b94b4a132c725912d029c101"
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "f29b7be8fa3046d2ee4c6421485b97b3f5b07774"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/ea9e22563b3d56f3b94b4a132c725912d029c101",
"reference": "ea9e22563b3d56f3b94b4a132c725912d029c101",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/f29b7be8fa3046d2ee4c6421485b97b3f5b07774",
"reference": "f29b7be8fa3046d2ee4c6421485b97b3f5b07774",
"shasum": ""
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/BedrockData/issues",
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.17.40"
},
"time": "2021-10-19T16:55:41+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
"version": "5.0.0+bedrock-1.17.40",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "67c0c15b4044cab2190501933912c3d02c5f63ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/67c0c15b4044cab2190501933912c3d02c5f63ab",
"reference": "67c0c15b4044cab2190501933912c3d02c5f63ab",
"shasum": ""
},
"require": {
@ -267,14 +293,14 @@
"php": "^8.0",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/color": "^0.2.0",
"pocketmine/math": "^0.3.0",
"pocketmine/math": "^0.3.0 || ^0.4.0",
"pocketmine/nbt": "^0.3.0",
"ramsey/uuid": "^4.1"
},
"require-dev": {
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-phpunit": "^0.12.21",
"phpstan/phpstan-strict-rules": "^0.12.10",
"phpstan/phpstan": "1.0.0",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5"
},
"type": "library",
@ -290,22 +316,22 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/bedrock-1.17.40"
"source": "https://github.com/pmmp/BedrockProtocol/tree/5.0.0+bedrock-1.17.40"
},
"time": "2021-10-19T17:28:41+00:00"
"time": "2021-11-02T01:27:05+00:00"
},
{
"name": "pocketmine/binaryutils",
"version": "0.2.1",
"version": "0.2.2",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BinaryUtils.git",
"reference": "8cd078e2426f8100331f2d73bef10f481dad6cde"
"reference": "f883e1cf9099ed6a757a10a2f75b3333eeb2cdf9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/8cd078e2426f8100331f2d73bef10f481dad6cde",
"reference": "8cd078e2426f8100331f2d73bef10f481dad6cde",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/f883e1cf9099ed6a757a10a2f75b3333eeb2cdf9",
"reference": "f883e1cf9099ed6a757a10a2f75b3333eeb2cdf9",
"shasum": ""
},
"require": {
@ -314,7 +340,7 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.85",
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-strict-rules": "^0.12.4"
},
"type": "library",
@ -330,9 +356,9 @@
"description": "Classes and methods for conveniently handling binary data",
"support": {
"issues": "https://github.com/pmmp/BinaryUtils/issues",
"source": "https://github.com/pmmp/BinaryUtils/tree/0.2.1"
"source": "https://github.com/pmmp/BinaryUtils/tree/0.2.2"
},
"time": "2021-05-30T19:42:57+00:00"
"time": "2021-10-22T19:54:16+00:00"
},
{
"name": "pocketmine/callback-validator",
@ -386,31 +412,31 @@
},
{
"name": "pocketmine/classloader",
"version": "dev-master",
"version": "0.2.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/ClassLoader.git",
"reference": "80226e0917be79ac3230606113e25134a31e6a85"
"reference": "49ea303993efdfb39cd302e2156d50aa78209e78"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/ClassLoader/zipball/80226e0917be79ac3230606113e25134a31e6a85",
"reference": "80226e0917be79ac3230606113e25134a31e6a85",
"url": "https://api.github.com/repos/pmmp/ClassLoader/zipball/49ea303993efdfb39cd302e2156d50aa78209e78",
"reference": "49ea303993efdfb39cd302e2156d50aa78209e78",
"shasum": ""
},
"require": {
"ext-pthreads": "~3.2.0",
"ext-pthreads": "~3.2.0 || ^4.0",
"ext-reflection": "*",
"php": "^7.2 || ^8.0"
"php": "^8.0"
},
"conflict": {
"pocketmine/spl": "<0.4"
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.80",
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-strict-rules": "^0.12.4",
"phpunit/phpunit": "^8.5 || ^9.5"
"phpunit/phpunit": "^9.5"
},
"type": "library",
"autoload": {
@ -425,9 +451,9 @@
"description": "Ad-hoc autoloading components used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/ClassLoader/issues",
"source": "https://github.com/pmmp/ClassLoader/tree/master"
"source": "https://github.com/pmmp/ClassLoader/tree/0.2.0"
},
"time": "2021-05-29T23:09:32+00:00"
"time": "2021-11-01T20:17:27+00:00"
},
{
"name": "pocketmine/color",
@ -506,17 +532,40 @@
"time": "2021-02-12T18:56:22+00:00"
},
{
"name": "pocketmine/log",
"version": "0.3.0",
"name": "pocketmine/locale-data",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Log.git",
"reference": "03ab1316da0b1978a7a1c8dd73e1c2a973cb62ec"
"url": "https://github.com/pmmp/Language.git",
"reference": "7342b4eb593036c739e7f0c0ed95299ada69ff19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Log/zipball/03ab1316da0b1978a7a1c8dd73e1c2a973cb62ec",
"reference": "03ab1316da0b1978a7a1c8dd73e1c2a973cb62ec",
"url": "https://api.github.com/repos/pmmp/Language/zipball/7342b4eb593036c739e7f0c0ed95299ada69ff19",
"reference": "7342b4eb593036c739e7f0c0ed95299ada69ff19",
"shasum": ""
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"description": "Language resources used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Language/issues",
"source": "https://github.com/pmmp/Language/tree/1.0.3"
},
"time": "2021-11-06T00:27:03+00:00"
},
{
"name": "pocketmine/log",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Log.git",
"reference": "e6c912c0f9055c81d23108ec2d179b96f404c043"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Log/zipball/e6c912c0f9055c81d23108ec2d179b96f404c043",
"reference": "e6c912c0f9055c81d23108ec2d179b96f404c043",
"shasum": ""
},
"require": {
@ -526,7 +575,7 @@
"pocketmine/spl": "<0.4"
},
"require-dev": {
"phpstan/phpstan": "0.12.80",
"phpstan/phpstan": "0.12.88",
"phpstan/phpstan-strict-rules": "^0.12.2"
},
"type": "library",
@ -542,28 +591,28 @@
"description": "Logging components used by PocketMine-MP and related projects",
"support": {
"issues": "https://github.com/pmmp/Log/issues",
"source": "https://github.com/pmmp/Log/tree/0.3.0"
"source": "https://github.com/pmmp/Log/tree/0.4.0"
},
"time": "2021-05-18T21:00:49+00:00"
"time": "2021-06-18T19:08:09+00:00"
},
{
"name": "pocketmine/log-pthreads",
"version": "0.2.0",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/LogPthreads.git",
"reference": "6be3445c48c62eba3922f987f000bb20c81d161f"
"reference": "61f709e8cf36bcc24e4efe02acded680a1ce23cd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/LogPthreads/zipball/6be3445c48c62eba3922f987f000bb20c81d161f",
"reference": "6be3445c48c62eba3922f987f000bb20c81d161f",
"url": "https://api.github.com/repos/pmmp/LogPthreads/zipball/61f709e8cf36bcc24e4efe02acded680a1ce23cd",
"reference": "61f709e8cf36bcc24e4efe02acded680a1ce23cd",
"shasum": ""
},
"require": {
"ext-pthreads": "~3.2.0",
"ext-pthreads": "~3.2.0 || ^4.0",
"php": "^7.4 || ^8.0",
"pocketmine/log": "^0.2.0 || ^0.3.0"
"pocketmine/log": "^0.4.0"
},
"conflict": {
"pocketmine/spl": "<0.4"
@ -586,32 +635,32 @@
"description": "Logging components specialized for pthreads used by PocketMine-MP and related projects",
"support": {
"issues": "https://github.com/pmmp/LogPthreads/issues",
"source": "https://github.com/pmmp/LogPthreads/tree/0.2.0"
"source": "https://github.com/pmmp/LogPthreads/tree/0.4.0"
},
"time": "2021-05-18T22:15:28+00:00"
"time": "2021-11-01T21:42:09+00:00"
},
{
"name": "pocketmine/math",
"version": "0.3.0",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Math.git",
"reference": "83ec067b12c066fc61d9fb129daf7e61ef3b1d63"
"reference": "6d64e2555bd2e95ed024574f75d1cefc135c89fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Math/zipball/83ec067b12c066fc61d9fb129daf7e61ef3b1d63",
"reference": "83ec067b12c066fc61d9fb129daf7e61ef3b1d63",
"url": "https://api.github.com/repos/pmmp/Math/zipball/6d64e2555bd2e95ed024574f75d1cefc135c89fc",
"reference": "6d64e2555bd2e95ed024574f75d1cefc135c89fc",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"php": "^8.0",
"php-64bit": "*"
},
"require-dev": {
"irstea/phpunit-shim": "^8.5 || ^9.5",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.90",
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-strict-rules": "^0.12.4"
},
"type": "library",
@ -627,9 +676,9 @@
"description": "PHP library containing math related code used in PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Math/issues",
"source": "https://github.com/pmmp/Math/tree/0.3.0"
"source": "https://github.com/pmmp/Math/tree/0.4.0"
},
"time": "2021-07-14T18:39:31+00:00"
"time": "2021-10-29T20:33:10+00:00"
},
{
"name": "pocketmine/nbt",
@ -759,25 +808,25 @@
},
{
"name": "pocketmine/snooze",
"version": "0.3.0",
"version": "0.3.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Snooze.git",
"reference": "fe5b1dbf0d6267da882d1f67924772bd93db833d"
"reference": "0ac8fc2a781c419a1f64ebca4d5835028f59e29b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Snooze/zipball/fe5b1dbf0d6267da882d1f67924772bd93db833d",
"reference": "fe5b1dbf0d6267da882d1f67924772bd93db833d",
"url": "https://api.github.com/repos/pmmp/Snooze/zipball/0ac8fc2a781c419a1f64ebca4d5835028f59e29b",
"reference": "0ac8fc2a781c419a1f64ebca4d5835028f59e29b",
"shasum": ""
},
"require": {
"ext-pthreads": ">=3.1.7dev",
"ext-pthreads": "~3.2.0 || ^4.0",
"php-64bit": "^7.3 || ^8.0"
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.88",
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-strict-rules": "^0.12.4"
},
"type": "library",
@ -793,46 +842,9 @@
"description": "Thread notification management library for code using the pthreads extension",
"support": {
"issues": "https://github.com/pmmp/Snooze/issues",
"source": "https://github.com/pmmp/Snooze/tree/0.3.0"
"source": "https://github.com/pmmp/Snooze/tree/0.3.1"
},
"time": "2021-06-13T13:57:47+00:00"
},
{
"name": "pocketmine/spl",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/pmmp/SPL.git",
"reference": "b7a8904f912c1f6d38ad867ff1120614ccb80171"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/SPL/zipball/b7a8904f912c1f6d38ad867ff1120614ccb80171",
"reference": "b7a8904f912c1f6d38ad867ff1120614ccb80171",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.8"
},
"type": "library",
"autoload": {
"classmap": [
"./src"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"description": "Standard library files required by PocketMine-MP and related projects",
"support": {
"issues": "https://github.com/pmmp/SPL/issues",
"source": "https://github.com/pmmp/SPL/tree/master"
},
"time": "2021-01-15T15:19:34+00:00"
"time": "2021-11-01T20:50:08+00:00"
},
{
"name": "ramsey/collection",
@ -1491,16 +1503,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.13.0",
"version": "v4.13.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "50953a2691a922aa1769461637869a0a2faa3f53"
"reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53",
"reference": "50953a2691a922aa1769461637869a0a2faa3f53",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd",
"reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd",
"shasum": ""
},
"require": {
@ -1541,9 +1553,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1"
},
"time": "2021-09-20T12:20:58+00:00"
"time": "2021-11-03T20:52:16+00:00"
},
{
"name": "phar-io/manifest",
@ -1711,16 +1723,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.2.2",
"version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
"shasum": ""
},
"require": {
@ -1731,7 +1743,8 @@
"webmozart/assert": "^1.9.1"
},
"require-dev": {
"mockery/mockery": "~1.3.2"
"mockery/mockery": "~1.3.2",
"psalm/phar": "^4.8"
},
"type": "library",
"extra": {
@ -1761,9 +1774,9 @@
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
},
"time": "2020-09-03T19:13:55+00:00"
"time": "2021-10-19T17:43:47+00:00"
},
{
"name": "phpdocumentor/type-resolver",
@ -1884,16 +1897,16 @@
},
{
"name": "phpstan/phpstan",
"version": "0.12.99",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7"
"reference": "e9e2a501102ba0b126b2f63a7f0a3b151056fe91"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4d40f1d759942f523be267a1bab6884f46ca3f7",
"reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e9e2a501102ba0b126b2f63a7f0a3b151056fe91",
"reference": "e9e2a501102ba0b126b2f63a7f0a3b151056fe91",
"shasum": ""
},
"require": {
@ -1909,7 +1922,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.12-dev"
"dev-master": "1.0-dev"
}
},
"autoload": {
@ -1924,7 +1937,7 @@
"description": "PHPStan - PHP Static Analysis Tool",
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/0.12.99"
"source": "https://github.com/phpstan/phpstan/tree/1.0.2"
},
"funding": [
{
@ -1944,38 +1957,39 @@
"type": "tidelift"
}
],
"time": "2021-09-12T20:09:55+00:00"
"time": "2021-11-03T16:09:51+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
"version": "0.12.22",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "7c01ef93bf128b4ac8bdad38c54b2a4fd6b0b3cc"
"reference": "9eb88c9f689003a8a2a5ae9e010338ee94dc39b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/7c01ef93bf128b4ac8bdad38c54b2a4fd6b0b3cc",
"reference": "7c01ef93bf128b4ac8bdad38c54b2a4fd6b0b3cc",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/9eb88c9f689003a8a2a5ae9e010338ee94dc39b3",
"reference": "9eb88c9f689003a8a2a5ae9e010338ee94dc39b3",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"phpstan/phpstan": "^0.12.92"
"phpstan/phpstan": "^1.0"
},
"conflict": {
"phpunit/phpunit": "<7.0"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-strict-rules": "^0.12.6",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5"
},
"type": "phpstan-extension",
"extra": {
"branch-alias": {
"dev-master": "0.12-dev"
"dev-master": "1.0-dev"
},
"phpstan": {
"includes": [
@ -1996,37 +2010,38 @@
"description": "PHPUnit extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
"source": "https://github.com/phpstan/phpstan-phpunit/tree/0.12.22"
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.0.0"
},
"time": "2021-08-12T10:53:43+00:00"
"time": "2021-10-14T08:03:54+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "0.12.11",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "2b72e8e17d2034145f239126e876e5fb659675e2"
"reference": "7f50eb112f37fda2ef956813d3f1e9b1e69d7940"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/2b72e8e17d2034145f239126e876e5fb659675e2",
"reference": "2b72e8e17d2034145f239126e876e5fb659675e2",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7f50eb112f37fda2ef956813d3f1e9b1e69d7940",
"reference": "7f50eb112f37fda2ef956813d3f1e9b1e69d7940",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"phpstan/phpstan": "^0.12.96"
"phpstan/phpstan": "^1.0"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-phpunit": "^0.12.16",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^9.5"
},
"type": "phpstan-extension",
"extra": {
"branch-alias": {
"dev-master": "0.12-dev"
"dev-master": "1.0-dev"
},
"phpstan": {
"includes": [
@ -2046,29 +2061,29 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/0.12.11"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.0.0"
},
"time": "2021-08-21T11:36:27+00:00"
"time": "2021-10-11T06:57:58+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.7",
"version": "9.2.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218"
"reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218",
"reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cf04e88a2e3c56fc1a65488afd493325b4c1bc3e",
"reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.12.0",
"nikic/php-parser": "^4.13.0",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@ -2117,7 +2132,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.8"
},
"funding": [
{
@ -2125,7 +2140,7 @@
"type": "github"
}
],
"time": "2021-09-17T05:39:03+00:00"
"time": "2021-10-30T08:01:38+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -3488,10 +3503,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"pocketmine/classloader": 20,
"pocketmine/spl": 20
},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@ -3512,7 +3524,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pthreads": "~3.2.0",
"ext-pthreads": "^4.0",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",

View File

@ -1,10 +1,7 @@
includes:
- tests/phpstan/configs/actual-problems.neon
- tests/phpstan/configs/check-explicit-mixed-baseline.neon
- tests/phpstan/configs/gc-hacks.neon
- tests/phpstan/configs/impossible-generics.neon
- tests/phpstan/configs/l7-baseline.neon
- tests/phpstan/configs/l8-baseline.neon
- tests/phpstan/configs/php-bugs.neon
- tests/phpstan/configs/phpstan-bugs.neon
- tests/phpstan/configs/pthreads-bugs.neon
@ -19,8 +16,7 @@ rules:
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule
parameters:
level: 8
checkExplicitMixed: true
level: 9
checkMissingCallableSignature: true
treatPhpDocTypesAsCertain: false
bootstrapFiles:

Submodule resources/locale deleted from 17fc6a1050

View File

@ -35,4 +35,6 @@ define('pocketmine\_CORE_CONSTANTS_INCLUDED', true);
define('pocketmine\PATH', dirname(__DIR__) . '/');
define('pocketmine\RESOURCE_PATH', dirname(__DIR__) . '/resources/');
define('pocketmine\BEDROCK_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/bedrock-data/');
define('pocketmine\LOCALE_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/locale-data/');
define('pocketmine\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__) . '/vendor/autoload.php');

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine;
use pocketmine\event\server\LowMemoryEvent;
use pocketmine\network\mcpe\cache\ChunkCache;
use pocketmine\scheduler\DumpWorkerMemoryTask;
use pocketmine\scheduler\GarbageCollectionTask;
use pocketmine\timings\Timings;
@ -187,6 +188,7 @@ class MemoryManager{
foreach($this->server->getWorldManager()->getWorlds() as $world){
$world->clearCache(true);
}
ChunkCache::pruneCaches();
}
if($this->lowMemChunkGC){

View File

@ -116,8 +116,8 @@ namespace pocketmine {
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
}
if(version_compare($pthreads_version, "3.2.0") < 0){
$messages[] = "pthreads >= 3.2.0 is required, while you have $pthreads_version.";
if(version_compare($pthreads_version, "4.0.0") < 0 || version_compare($pthreads_version, "5.0.0") > 0){
$messages[] = "pthreads ^4.0.0 is required, while you have $pthreads_version.";
}
}
@ -126,6 +126,9 @@ namespace pocketmine {
if(version_compare($leveldb_version, "0.2.1") < 0){
$messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version.";
}
if(!defined('LEVELDB_ZLIB_RAW_COMPRESSION')){
$messages[] = "Given version of php-leveldb doesn't support ZLIB_RAW compression (use https://github.com/pmmp/php-leveldb)";
}
}
$chunkutils2_version = phpversion("chunkutils2");

View File

@ -34,6 +34,7 @@ use pocketmine\console\ConsoleCommandSender;
use pocketmine\console\ConsoleReaderThread;
use pocketmine\crafting\CraftingManager;
use pocketmine\crafting\CraftingManagerFromDataHelper;
use pocketmine\crash\CrashDump;
use pocketmine\data\java\GameModeIdMap;
use pocketmine\entity\EntityDataHelper;
use pocketmine\entity\Location;
@ -64,6 +65,7 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\raklib\RakLibInterface;
use pocketmine\network\Network;
use pocketmine\network\NetworkInterfaceStartException;
use pocketmine\network\query\DedicatedQueryNetworkInterface;
use pocketmine\network\query\QueryHandler;
use pocketmine\network\query\QueryInfo;
@ -81,6 +83,8 @@ use pocketmine\plugin\PluginGraylist;
use pocketmine\plugin\PluginManager;
use pocketmine\plugin\PluginOwned;
use pocketmine\plugin\ScriptPluginLoader;
use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\AsyncPool;
use pocketmine\snooze\SleeperHandler;
@ -97,7 +101,6 @@ use pocketmine\utils\MainLogger;
use pocketmine\utils\NotCloneable;
use pocketmine\utils\NotSerializable;
use pocketmine\utils\Process;
use pocketmine\utils\Promise;
use pocketmine\utils\SignalHandler;
use pocketmine\utils\Terminal;
use pocketmine\utils\TextFormat;
@ -548,11 +551,11 @@ class Server{
$playerPos = null;
$spawn = $world->getSpawnLocation();
}
$playerPromise = new Promise();
$playerPromiseResolver = new PromiseResolver();
$world->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
function() use ($playerPromise, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{
function() use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{
if(!$session->isConnected()){
$playerPromise->reject();
$playerPromiseResolver->reject();
return;
}
@ -572,16 +575,16 @@ class Server{
if(!$player->hasPlayedBefore()){
$player->onGround = true; //TODO: this hack is needed for new players in-air ticks - they don't get detected as on-ground until they move
}
$playerPromise->resolve($player);
$playerPromiseResolver->resolve($player);
},
static function() use ($playerPromise, $session) : void{
static function() use ($playerPromiseResolver, $session) : void{
if($session->isConnected()){
$session->disconnect("Spawn terrain generation failed");
}
$playerPromise->reject();
$playerPromiseResolver->reject();
}
);
return $playerPromise;
return $playerPromiseResolver->getPromise();
}
/**
@ -737,7 +740,7 @@ class Server{
public function __construct(\DynamicClassLoader $autoloader, \AttachableThreadedLogger $logger, string $dataPath, string $pluginPath){
if(self::$instance !== null){
throw new \InvalidStateException("Only one server instance can exist at once");
throw new \LogicException("Only one server instance can exist at once");
}
self::$instance = $this;
$this->startTime = microtime(true);
@ -930,7 +933,7 @@ class Server{
$this->commandMap = new SimpleCommandMap($this);
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\RESOURCE_PATH, "vanilla", "recipes.json"));
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes.json"));
$this->resourceManager = new ResourcePackManager(Path::join($this->getDataPath(), "resource_packs"), $this->logger);
@ -979,6 +982,7 @@ class Server{
$this->enablePlugins(PluginEnableOrder::POSTWORLD());
if(!$this->startupPrepareNetworkInterfaces()){
$this->forceShutdown();
return;
}
@ -1112,7 +1116,18 @@ class Server{
private function startupPrepareNetworkInterfaces() : bool{
$useQuery = $this->configGroup->getConfigBool("enable-query", true);
if(!$this->network->registerInterface(new RakLibInterface($this)) && $useQuery){
try{
$rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this));
}catch(NetworkInterfaceStartException $e){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed(
$this->getIp(),
(string) $this->getPort(),
$e->getMessage()
)));
return false;
}
if(!$rakLibRegistered && $useQuery){
//RakLib would normally handle the transport for Query packets
//if it's not registered we need to make sure Query still works
$this->network->registerInterface(new DedicatedQueryNetworkInterface($this->getIp(), $this->getPort(), new \PrefixedLogger($this->logger, "Dedicated Query Interface")));
@ -1476,7 +1491,7 @@ class Server{
ini_set("memory_limit", '-1'); //Fix error dump not dumped on memory problems
try{
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_create()));
$dump = new CrashDump($this);
$dump = new CrashDump($this, $this->pluginManager ?? null);
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_submit($dump->getPath())));
@ -1490,8 +1505,8 @@ class Server{
}
@touch($stamp); //update file timestamp
$plugin = $dump->getData()["plugin"];
if(is_string($plugin)){
$plugin = $dump->getData()->plugin;
if($plugin !== ""){
$p = $this->pluginManager->getPlugin($plugin);
if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){
$this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
@ -1499,7 +1514,7 @@ class Server{
}
}
if($dump->getData()["error"]["type"] === \ParseError::class){
if($dump->getData()->error["type"] === \ParseError::class){
$report = false;
}

View File

@ -29,7 +29,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.0.0-BETA6";
public const BASE_VERSION = "4.0.0-BETA11";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_NUMBER = 0;
public const BUILD_CHANNEL = "beta";

View File

@ -493,7 +493,7 @@ class Block{
return $this->position->getWorld()->getBlock($this->position->getSide($side, $step));
}
throw new \InvalidStateException("Block does not have a valid world");
throw new \LogicException("Block does not have a valid world");
}
/**

View File

@ -94,8 +94,7 @@ class Cake extends Transparent implements FoodSource{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$player->consumeObject($this);
return true;
return $player->consumeObject($this);
}
return false;

View File

@ -26,6 +26,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\TreeType;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
@ -94,10 +95,7 @@ class CocoaBlock extends Transparent{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->age < 2 and $item instanceof Fertilizer){
$this->age++;
$this->position->getWorld()->setBlock($this->position, $this);
if($item instanceof Fertilizer && $this->grow()){
$item->pop();
return true;
@ -117,12 +115,25 @@ class CocoaBlock extends Transparent{
}
public function onRandomTick() : void{
if($this->age < 2 and mt_rand(1, 5) === 1){
$this->age++;
$this->position->getWorld()->setBlock($this->position, $this);
if(mt_rand(1, 5) === 1){
$this->grow();
}
}
private function grow() : bool{
if($this->age < 2){
$block = clone $this;
$block->age++;
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
return true;
}
}
return false;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::COCOA_BEANS()->setCount($this->age === 2 ? mt_rand(2, 3) : 1)

View File

@ -80,10 +80,9 @@ abstract class Crops extends Flowable{
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
$item->pop();
}
$item->pop();
return true;
}

View File

@ -76,9 +76,7 @@ class Sapling extends Flowable{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){
$this->grow($player);
if($item instanceof Fertilizer && $this->grow($player)){
$item->pop();
return true;
@ -108,19 +106,20 @@ class Sapling extends Flowable{
}
}
private function grow(?Player $player) : void{
private function grow(?Player $player) : bool{
$random = new Random(mt_rand());
$tree = TreeFactory::get($random, $this->treeType);
$transaction = $tree?->getBlockTransaction($this->position->getWorld(), $this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ(), $random);
if($transaction === null){
return;
return false;
}
$ev = new StructureGrowEvent($this, $transaction, $player);
$ev->call();
if(!$ev->isCancelled()){
$transaction->apply();
return $transaction->apply();
}
return false;
}
public function getFuelTime() : int{

View File

@ -48,7 +48,8 @@ class Sugarcane extends Flowable{
return 0b1111;
}
private function grow() : void{
private function grow() : bool{
$grew = false;
for($y = 1; $y < 3; ++$y){
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
break;
@ -61,12 +62,14 @@ class Sugarcane extends Flowable{
break;
}
$this->position->getWorld()->setBlock($b->position, $ev->getNewState());
$grew = true;
}else{
break;
}
}
$this->age = 0;
$this->position->getWorld()->setBlock($this->position, $this);
return $grew;
}
public function getAge() : int{ return $this->age; }
@ -82,12 +85,10 @@ class Sugarcane extends Flowable{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
$this->grow();
if(!$this->getSide(Facing::DOWN)->isSameType($this) && $this->grow()){
$item->pop();
}
$item->pop();
return true;
}

View File

@ -99,9 +99,9 @@ class SweetBerryBush extends Flowable{
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
$item->pop();
}
$item->pop();
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
$this->position->getWorld()->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
$this->position->getWorld()->dropItem($this->position, $this->asItem()->setCount($dropAmount));

View File

@ -25,6 +25,7 @@ namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\world\Position;
use pocketmine\world\sound\ChestCloseSound;
use pocketmine\world\sound\ChestOpenSound;
@ -50,6 +51,6 @@ class ChestInventory extends SimpleInventory implements BlockInventory{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(1, $isOpen ? 1 : 0, $holder->asVector3()));
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0));
}
}

View File

@ -28,6 +28,7 @@ use pocketmine\inventory\DelegateInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerEnderInventory;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\player\Player;
use pocketmine\world\Position;
use pocketmine\world\sound\EnderChestCloseSound;
@ -74,7 +75,7 @@ class EnderChestInventory extends DelegateInventory implements BlockInventory{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(1, $isOpen ? 1 : 0, $holder->asVector3()));
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0));
}
public function onClose(Player $who) : void{

View File

@ -27,6 +27,7 @@ use pocketmine\block\BlockLegacyIds;
use pocketmine\inventory\SimpleInventory;
use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\world\Position;
use pocketmine\world\sound\ShulkerBoxCloseSound;
use pocketmine\world\sound\ShulkerBoxOpenSound;
@ -59,6 +60,6 @@ class ShulkerBoxInventory extends SimpleInventory implements BlockInventory{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(1, $isOpen ? 1 : 0, $holder->asVector3()));
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0));
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\math\Facing;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\BlockActorDataPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
final class Bell extends Spawnable{
@ -81,6 +82,6 @@ final class Bell extends Spawnable{
$nbt->setByte(self::TAG_RINGING, 1);
$nbt->setInt(self::TAG_DIRECTION, BlockDataSerializer::writeLegacyHorizontalFacing($bellHitFace));
$nbt->setInt(self::TAG_TICKS, 0);
return BlockActorDataPacket::create($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ(), new CacheableNbt($nbt));
return BlockActorDataPacket::create(BlockPosition::fromVector3($this->position), new CacheableNbt($nbt));
}
}

View File

@ -25,7 +25,7 @@ namespace pocketmine\block\utils;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Translatable;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
use pocketmine\utils\EnumTrait;
/**
@ -54,18 +54,18 @@ final class RecordType{
protected static function setup() : void{
self::registerAll(
new RecordType("disk_13", "C418 - 13", LevelSoundEventPacket::SOUND_RECORD_13, KnownTranslationFactory::item_record_13_desc()),
new RecordType("disk_cat", "C418 - cat", LevelSoundEventPacket::SOUND_RECORD_CAT, KnownTranslationFactory::item_record_cat_desc()),
new RecordType("disk_blocks", "C418 - blocks", LevelSoundEventPacket::SOUND_RECORD_BLOCKS, KnownTranslationFactory::item_record_blocks_desc()),
new RecordType("disk_chirp", "C418 - chirp", LevelSoundEventPacket::SOUND_RECORD_CHIRP, KnownTranslationFactory::item_record_chirp_desc()),
new RecordType("disk_far", "C418 - far", LevelSoundEventPacket::SOUND_RECORD_FAR, KnownTranslationFactory::item_record_far_desc()),
new RecordType("disk_mall", "C418 - mall", LevelSoundEventPacket::SOUND_RECORD_MALL, KnownTranslationFactory::item_record_mall_desc()),
new RecordType("disk_mellohi", "C418 - mellohi", LevelSoundEventPacket::SOUND_RECORD_MELLOHI, KnownTranslationFactory::item_record_mellohi_desc()),
new RecordType("disk_stal", "C418 - stal", LevelSoundEventPacket::SOUND_RECORD_STAL, KnownTranslationFactory::item_record_stal_desc()),
new RecordType("disk_strad", "C418 - strad", LevelSoundEventPacket::SOUND_RECORD_STRAD, KnownTranslationFactory::item_record_strad_desc()),
new RecordType("disk_ward", "C418 - ward", LevelSoundEventPacket::SOUND_RECORD_WARD, KnownTranslationFactory::item_record_ward_desc()),
new RecordType("disk_11", "C418 - 11", LevelSoundEventPacket::SOUND_RECORD_11, KnownTranslationFactory::item_record_11_desc()),
new RecordType("disk_wait", "C418 - wait", LevelSoundEventPacket::SOUND_RECORD_WAIT, KnownTranslationFactory::item_record_wait_desc())
new RecordType("disk_13", "C418 - 13", LevelSoundEvent::RECORD_13, KnownTranslationFactory::item_record_13_desc()),
new RecordType("disk_cat", "C418 - cat", LevelSoundEvent::RECORD_CAT, KnownTranslationFactory::item_record_cat_desc()),
new RecordType("disk_blocks", "C418 - blocks", LevelSoundEvent::RECORD_BLOCKS, KnownTranslationFactory::item_record_blocks_desc()),
new RecordType("disk_chirp", "C418 - chirp", LevelSoundEvent::RECORD_CHIRP, KnownTranslationFactory::item_record_chirp_desc()),
new RecordType("disk_far", "C418 - far", LevelSoundEvent::RECORD_FAR, KnownTranslationFactory::item_record_far_desc()),
new RecordType("disk_mall", "C418 - mall", LevelSoundEvent::RECORD_MALL, KnownTranslationFactory::item_record_mall_desc()),
new RecordType("disk_mellohi", "C418 - mellohi", LevelSoundEvent::RECORD_MELLOHI, KnownTranslationFactory::item_record_mellohi_desc()),
new RecordType("disk_stal", "C418 - stal", LevelSoundEvent::RECORD_STAL, KnownTranslationFactory::item_record_stal_desc()),
new RecordType("disk_strad", "C418 - strad", LevelSoundEvent::RECORD_STRAD, KnownTranslationFactory::item_record_strad_desc()),
new RecordType("disk_ward", "C418 - ward", LevelSoundEvent::RECORD_WARD, KnownTranslationFactory::item_record_ward_desc()),
new RecordType("disk_11", "C418 - 11", LevelSoundEvent::RECORD_11, KnownTranslationFactory::item_record_11_desc()),
new RecordType("disk_wait", "C418 - wait", LevelSoundEvent::RECORD_WAIT, KnownTranslationFactory::item_record_wait_desc())
//TODO: Lena Raine - Pigstep
);
}

View File

@ -113,7 +113,7 @@ class TeleportCommand extends VanillaCommand{
$x = $this->getRelativeDouble($base->x, $sender, $targetArgs[0]);
$y = $this->getRelativeDouble($base->y, $sender, $targetArgs[1], 0, 256);
$z = $this->getRelativeDouble($base->z, $sender, $targetArgs[2]);
$targetLocation = new Location($x, $y, $z, $yaw, $pitch, $base->getWorld());
$targetLocation = new Location($x, $y, $z, $base->getWorld(), $yaw, $pitch);
$subject->teleport($targetLocation);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_tp_success_coordinates(

View File

@ -39,6 +39,7 @@ use function stream_socket_accept;
use function stream_socket_get_name;
use function stream_socket_server;
use function stream_socket_shutdown;
use function trim;
use const PHP_BINARY;
use const STREAM_SHUT_RDWR;
@ -113,7 +114,9 @@ final class ConsoleReaderThread extends Thread{
break;
}
$buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $command);
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
$buffer[] = $command;
if($notifier !== null){
$notifier->wakeupSleeper();
}

View File

@ -111,7 +111,7 @@ class CraftingGrid extends SimpleInventory{
return $this->getItem(($y + $this->startY) * $this->gridWidth + ($x + $this->startX));
}
throw new \InvalidStateException("No ingredients found in grid");
throw new \LogicException("No ingredients found in grid");
}
/**

View File

@ -21,16 +21,18 @@
declare(strict_types=1);
namespace pocketmine;
namespace pocketmine\crash;
use Composer\InstalledVersions;
use pocketmine\errorhandler\ErrorTypeToStringMap;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\plugin\PluginBase;
use pocketmine\plugin\PluginManager;
use pocketmine\Server;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use pocketmine\VersionInfo;
use Webmozart\PathUtil\Path;
use function base64_encode;
use function date;
@ -69,6 +71,7 @@ use const FILE_IGNORE_NEW_LINES;
use const JSON_UNESCAPED_SLASHES;
use const PHP_EOL;
use const PHP_OS;
use const PHP_VERSION;
use const SORT_STRING;
class CrashDump{
@ -91,19 +94,18 @@ class CrashDump{
private $fp;
/** @var float */
private $time;
/**
* @var mixed[]
* @phpstan-var array<string, mixed>
*/
private $data = [];
private CrashDumpData $data;
/** @var string */
private $encodedData = "";
private $encodedData;
/** @var string */
private $path;
public function __construct(Server $server){
private ?PluginManager $pluginManager;
public function __construct(Server $server, ?PluginManager $pluginManager){
$this->time = microtime(true);
$this->server = $server;
$this->pluginManager = $pluginManager;
$crashPath = Path::join($this->server->getDataPath(), "crashdumps");
if(!is_dir($crashPath)){
@ -115,9 +117,10 @@ class CrashDump{
throw new \RuntimeException("Could not create Crash Dump");
}
$this->fp = $fp;
$this->data["format_version"] = self::FORMAT_VERSION;
$this->data["time"] = $this->time;
$this->data["uptime"] = $this->time - $this->server->getStartTime();
$this->data = new CrashDumpData();
$this->data->format_version = self::FORMAT_VERSION;
$this->data->time = $this->time;
$this->data->uptime = $this->time - $this->server->getStartTime();
$this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", (int) $this->time));
$this->addLine();
$this->baseCrash();
@ -139,11 +142,7 @@ class CrashDump{
return $this->encodedData;
}
/**
* @return mixed[]
* @phpstan-return array<string, mixed>
*/
public function getData() : array{
public function getData() : CrashDumpData{
return $this->data;
}
@ -166,26 +165,25 @@ class CrashDump{
}
private function pluginsData() : void{
if($this->server->getPluginManager() instanceof PluginManager){
if($this->pluginManager !== null){
$plugins = $this->pluginManager->getPlugins();
$this->addLine();
$this->addLine("Loaded plugins:");
$this->data["plugins"] = [];
$plugins = $this->server->getPluginManager()->getPlugins();
ksort($plugins, SORT_STRING);
foreach($plugins as $p){
$d = $p->getDescription();
$this->data["plugins"][$d->getName()] = [
"name" => $d->getName(),
"version" => $d->getVersion(),
"authors" => $d->getAuthors(),
"api" => $d->getCompatibleApis(),
"enabled" => $p->isEnabled(),
"depends" => $d->getDepend(),
"softDepends" => $d->getSoftDepend(),
"main" => $d->getMain(),
"load" => mb_strtoupper($d->getOrder()->name()),
"website" => $d->getWebsite()
];
$this->data->plugins[$d->getName()] = new CrashDumpDataPluginEntry(
name: $d->getName(),
version: $d->getVersion(),
authors: $d->getAuthors(),
api: $d->getCompatibleApis(),
enabled: $p->isEnabled(),
depends: $d->getDepend(),
softDepends: $d->getSoftDepend(),
main: $d->getMain(),
load: mb_strtoupper($d->getOrder()->name()),
website: $d->getWebsite()
);
$this->addLine($d->getName() . " " . $d->getVersion() . " by " . implode(", ", $d->getAuthors()) . " for API(s) " . implode(", ", $d->getCompatibleApis()));
}
}
@ -195,32 +193,26 @@ class CrashDump{
global $argv;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-settings", true)){
$this->data["parameters"] = (array) $argv;
$this->data->parameters = (array) $argv;
if(($serverDotProperties = @file_get_contents(Path::join($this->server->getDataPath(), "server.properties"))) !== false){
$this->data["server.properties"] = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties);
}else{
$this->data["server.properties"] = $serverDotProperties;
$this->data->serverDotProperties = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties) ?? throw new AssumptionFailedError("Pattern is valid");
}
if(($pocketmineDotYml = @file_get_contents(Path::join($this->server->getDataPath(), "pocketmine.yml"))) !== false){
$this->data["pocketmine.yml"] = $pocketmineDotYml;
}else{
$this->data["pocketmine.yml"] = "";
$this->data->pocketmineDotYml = $pocketmineDotYml;
}
}else{
$this->data["pocketmine.yml"] = "";
$this->data["server.properties"] = "";
$this->data["parameters"] = [];
}
$extensions = [];
foreach(get_loaded_extensions() as $ext){
$extensions[$ext] = phpversion($ext);
$version = phpversion($ext);
if($version === false) throw new AssumptionFailedError();
$extensions[$ext] = $version;
}
$this->data["extensions"] = $extensions;
$this->data->extensions = $extensions;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){
ob_start();
phpinfo();
$this->data["phpinfo"] = ob_get_contents();
$this->data->phpinfo = ob_get_contents(); // @phpstan-ignore-line
ob_end_clean();
}
}
@ -252,18 +244,18 @@ class CrashDump{
if(isset($lastError["trace"])){
$lastError["trace"] = Utils::printableTrace($lastError["trace"]);
}
$this->data["lastError"] = $lastError;
$this->data->lastError = $lastError;
}
$this->data["error"] = $error;
unset($this->data["error"]["fullFile"]);
unset($this->data["error"]["trace"]);
$this->data->error = $error;
unset($this->data->error["fullFile"]);
unset($this->data->error["trace"]);
$this->addLine("Error: " . $error["message"]);
$this->addLine("File: " . $error["file"]);
$this->addLine("Line: " . $error["line"]);
$this->addLine("Type: " . $error["type"]);
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_NONE;
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_NONE;
if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace
foreach($error["trace"] as $frame){
if(!isset($frame["file"])){
@ -277,21 +269,20 @@ class CrashDump{
$this->addLine();
$this->addLine("Code:");
$this->data["code"] = [];
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-code", true) and file_exists($error["fullFile"])){
$file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES);
if($file !== false){
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){
$this->addLine("[" . ($l + 1) . "] " . $file[$l]);
$this->data["code"][$l + 1] = $file[$l];
$this->data->code[$l + 1] = $file[$l];
}
}
}
$this->addLine();
$this->addLine("Backtrace:");
foreach(($this->data["trace"] = Utils::printableTrace($error["trace"])) as $line){
foreach(($this->data->trace = Utils::printableTrace($error["trace"])) as $line){
$this->addLine($line);
}
$this->addLine();
@ -303,10 +294,10 @@ class CrashDump{
$this->addLine();
if($crashFrame){
$this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN");
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_DIRECT;
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_DIRECT;
}else{
$this->addLine("A PLUGIN WAS INVOLVED IN THIS CRASH");
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_INDIRECT;
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_INDIRECT;
}
if(file_exists($filePath)){
@ -316,7 +307,7 @@ class CrashDump{
foreach($this->server->getPluginManager()->getPlugins() as $plugin){
$filePath = Filesystem::cleanPath($file->getValue($plugin));
if(strpos($frameCleanPath, $filePath) === 0){
$this->data["plugin"] = $plugin->getName();
$this->data->plugin = $plugin->getName();
$this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName());
break;
}
@ -338,25 +329,26 @@ class CrashDump{
);
}
$this->data["general"] = [];
$this->data["general"]["name"] = $this->server->getName();
$this->data["general"]["base_version"] = VersionInfo::BASE_VERSION;
$this->data["general"]["build"] = VersionInfo::BUILD_NUMBER;
$this->data["general"]["is_dev"] = VersionInfo::IS_DEVELOPMENT_BUILD;
$this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL;
$this->data["general"]["git"] = VersionInfo::GIT_HASH();
$this->data["general"]["uname"] = php_uname("a");
$this->data["general"]["php"] = phpversion();
$this->data["general"]["zend"] = zend_version();
$this->data["general"]["php_os"] = PHP_OS;
$this->data["general"]["os"] = Utils::getOS();
$this->data["general"]["composer_libraries"] = $composerLibraries;
$this->data->general = new CrashDumpDataGeneral(
name: $this->server->getName(),
base_version: VersionInfo::BASE_VERSION,
build: VersionInfo::BUILD_NUMBER,
is_dev: VersionInfo::IS_DEVELOPMENT_BUILD,
protocol: ProtocolInfo::CURRENT_PROTOCOL,
git: VersionInfo::GIT_HASH(),
uname: php_uname("a"),
php: PHP_VERSION,
zend: zend_version(),
php_os: PHP_OS,
os: Utils::getOS(),
composer_libraries: $composerLibraries,
);
$this->addLine($this->server->getName() . " version: " . $version->getFullVersion(true) . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "]");
$this->addLine("Git commit: " . VersionInfo::GIT_HASH());
$this->addLine("uname -a: " . php_uname("a"));
$this->addLine("PHP Version: " . phpversion());
$this->addLine("Zend version: " . zend_version());
$this->addLine("OS : " . PHP_OS . ", " . Utils::getOS());
$this->addLine("OS: " . PHP_OS . ", " . Utils::getOS());
$this->addLine("Composer libraries: ");
foreach($composerLibraries as $library => $libraryVersion){
$this->addLine("- $library $libraryVersion");

View File

@ -0,0 +1,84 @@
<?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\crash;
final class CrashDumpData implements \JsonSerializable{
public int $format_version;
public float $time;
public float $uptime;
/** @var mixed[] */
public array $lastError = [];
/** @var mixed[] */
public array $error;
public string $plugin_involvement;
public string $plugin = "";
/** @var string[] */
public array $code = [];
/** @var string[] */
public array $trace;
/**
* @var CrashDumpDataPluginEntry[]
* @phpstan-var array<string, CrashDumpDataPluginEntry>
*/
public array $plugins = [];
/** @var string[] */
public array $parameters = [];
public string $serverDotProperties = "";
public string $pocketmineDotYml = "";
/**
* @var string[]
* @phpstan-var array<string, string>
*/
public array $extensions = [];
public string $phpinfo = "";
public CrashDumpDataGeneral $general;
/**
* @return mixed[]
*/
public function jsonSerialize() : array{
$result = (array) $this;
unset($result["serverDotProperties"]);
unset($result["pocketmineDotYml"]);
$result["pocketmine.yml"] = $this->pocketmineDotYml;
$result["server.properties"] = $this->serverDotProperties;
return $result;
}
}

View File

@ -0,0 +1,46 @@
<?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\crash;
final class CrashDumpDataGeneral{
/**
* @param string[] $composer_libraries
* @phpstan-param array<string, string> $composer_libraries
*/
public function __construct(
public string $name,
public string $base_version,
public int $build,
public bool $is_dev,
public int $protocol,
public string $git,
public string $uname,
public string $php,
public string $zend,
public string $php_os,
public string $os,
public array $composer_libraries,
){}
}

View File

@ -0,0 +1,45 @@
<?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\crash;
final class CrashDumpDataPluginEntry{
/**
* @param string[] $authors
* @param string[] $api
* @param string[] $depends
* @param string[] $softDepends
*/
public function __construct(
public string $name,
public string $version,
public array $authors,
public array $api,
public bool $enabled,
public array $depends,
public array $softDepends,
public string $main,
public string $load,
public string $website,
){}
}

View File

@ -30,6 +30,6 @@ final class LegacyBlockIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;
public function __construct(){
parent::__construct(Path::join(\pocketmine\RESOURCE_PATH, 'vanilla', 'block_id_map.json'));
parent::__construct(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'block_id_map.json'));
}
}

View File

@ -30,6 +30,6 @@ final class LegacyEntityIdToStringIdMap extends LegacyToStringBidirectionalIdMap
use SingletonTrait;
public function __construct(){
parent::__construct(Path::join(\pocketmine\RESOURCE_PATH, 'vanilla', 'entity_id_map.json'));
parent::__construct(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'entity_id_map.json'));
}
}

View File

@ -30,6 +30,6 @@ final class LegacyItemIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;
public function __construct(){
parent::__construct(Path::join(\pocketmine\RESOURCE_PATH, 'vanilla', 'item_id_map.json'));
parent::__construct(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'item_id_map.json'));
}
}

View File

@ -980,7 +980,7 @@ abstract class Entity{
final public function scheduleUpdate() : void{
if($this->closed){
throw new \InvalidStateException("Cannot schedule update on garbage entity " . get_class($this));
throw new \LogicException("Cannot schedule update on garbage entity " . get_class($this));
}
$this->getWorld()->updateEntities[$this->id] = $this;
}
@ -1185,9 +1185,9 @@ abstract class Entity{
($this->boundingBox->minX + $this->boundingBox->maxX) / 2,
$this->boundingBox->minY - $this->ySize,
($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2,
$this->location->world,
$this->location->yaw,
$this->location->pitch,
$this->location->world
$this->location->pitch
);
$this->getWorld()->onEntityMoved($this);
@ -1418,20 +1418,21 @@ abstract class Entity{
* Called by spawnTo() to send whatever packets needed to spawn the entity to the client.
*/
protected function sendSpawnPacket(Player $player) : void{
$pk = new AddActorPacket();
$pk->entityRuntimeId = $this->getId();
$pk->type = static::getNetworkTypeId();
$pk->position = $this->location->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->location->yaw;
$pk->headYaw = $this->location->yaw; //TODO
$pk->pitch = $this->location->pitch;
$pk->attributes = array_map(function(Attribute $attr) : NetworkAttribute{
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue());
}, $this->attributeMap->getAll());
$pk->metadata = $this->getAllNetworkData();
$player->getNetworkSession()->sendDataPacket($pk);
$player->getNetworkSession()->sendDataPacket(AddActorPacket::create(
$this->getId(), //TODO: actor unique ID
$this->getId(),
static::getNetworkTypeId(),
$this->location->asVector3(),
$this->getMotion(),
$this->location->pitch,
$this->location->yaw,
$this->location->yaw, //TODO: head yaw
array_map(function(Attribute $attr) : NetworkAttribute{
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue());
}, $this->attributeMap->getAll()),
$this->getAllNetworkData(),
[] //TODO: entity links
));
}
public function spawnTo(Player $player) : void{
@ -1439,7 +1440,7 @@ abstract class Entity{
//TODO: this will cause some visible lag during chunk resends; if the player uses a spawn egg in a chunk, the
//created entity won't be visible until after the resend arrives. However, this is better than possibly crashing
//the player by sending them entities too early.
if(!isset($this->hasSpawned[$id]) and $player->hasReceivedChunk($this->location->getFloorX() >> Chunk::COORD_BIT_SIZE, $this->location->getFloorZ() >> Chunk::COORD_BIT_SIZE)){
if(!isset($this->hasSpawned[$id]) and $player->getWorld() === $this->getWorld() and $player->hasReceivedChunk($this->location->getFloorX() >> Chunk::COORD_BIT_SIZE, $this->location->getFloorZ() >> Chunk::COORD_BIT_SIZE)){
$this->hasSpawned[$id] = $player;
$this->sendSpawnPacket($player);

View File

@ -59,10 +59,10 @@ final class EntityDataHelper{
if($pos === null and $optional){
return new Vector3(0, 0, 0);
}
if(!($pos instanceof ListTag) or $pos->getTagType() !== NBT::TAG_Double){
throw new \UnexpectedValueException("'$tagName' should be a List<Double>");
if(!($pos instanceof ListTag) or ($pos->getTagType() !== NBT::TAG_Double && $pos->getTagType() !== NBT::TAG_Float)){
throw new \UnexpectedValueException("'$tagName' should be a List<Double> or List<Float>");
}
/** @var DoubleTag[] $values */
/** @var DoubleTag[]|FloatTag[] $values */
$values = $pos->getValue();
if(count($values) !== 3){
throw new \UnexpectedValueException("Expected exactly 3 entries in '$tagName' tag");

View File

@ -27,6 +27,7 @@ use pocketmine\entity\utils\ExperienceUtils;
use pocketmine\event\player\PlayerExperienceChangeEvent;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\utils\Limits;
use pocketmine\world\sound\XpCollectSound;
use pocketmine\world\sound\XpLevelUpSound;
use function array_rand;
@ -151,6 +152,7 @@ class ExperienceManager{
* @param bool $playSound Whether to play level-up and XP gained sounds.
*/
public function addXp(int $amount, bool $playSound = true) : bool{
$amount = min($amount, Limits::INT32_MAX - $this->totalXp);
$oldLevel = $this->getXpLevel();
$oldTotal = $this->getCurrentTotalXp();
@ -223,8 +225,8 @@ class ExperienceManager{
* score when they die. (TODO: add this when MCPE supports it)
*/
public function setLifetimeTotalXp(int $amount) : void{
if($amount < 0){
throw new \InvalidArgumentException("XP must be greater than 0");
if($amount < 0 || $amount > Limits::INT32_MAX){
throw new \InvalidArgumentException("XP must be greater than 0 and less than " . Limits::INT32_MAX);
}
$this->totalXp = $amount;

View File

@ -47,8 +47,10 @@ use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\types\DeviceOS;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty;
@ -145,7 +147,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
*/
public function sendSkin(?array $targets = null) : void{
$this->server->broadcastPackets($targets ?? $this->hasSpawned, [
PlayerSkinPacket::create($this->getUniqueId(), SkinAdapterSingleton::get()->toSkinData($this->skin))
PlayerSkinPacket::create($this->getUniqueId(), "", "", SkinAdapterSingleton::get()->toSkinData($this->skin))
]);
}
@ -444,17 +446,24 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$player->getNetworkSession()->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), SkinAdapterSingleton::get()->toSkinData($this->skin))]));
}
$pk = new AddPlayerPacket();
$pk->uuid = $this->getUniqueId();
$pk->username = $this->getName();
$pk->entityRuntimeId = $this->getId();
$pk->position = $this->location->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->location->yaw;
$pk->pitch = $this->location->pitch;
$pk->item = ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand()));
$pk->metadata = $this->getAllNetworkData();
$player->getNetworkSession()->sendDataPacket($pk);
$player->getNetworkSession()->sendDataPacket(AddPlayerPacket::create(
$this->getUniqueId(),
$this->getName(),
$this->getId(), //TODO: actor unique ID
$this->getId(),
"",
$this->location->asVector3(),
$this->getMotion(),
$this->location->pitch,
$this->location->yaw,
$this->location->yaw, //TODO: head yaw
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand())),
$this->getAllNetworkData(),
AdventureSettingsPacket::create(0, 0, 0, 0, 0, $this->getId()), //TODO
[], //TODO: entity links
"", //device ID (we intentionally don't send this - secvuln)
DeviceOS::UNKNOWN //we intentionally don't send this (secvuln)
));
//TODO: Hack for MCPE 1.2.13: DATA_NAMETAG is useless in AddPlayerPacket, so it has to be sent separately
$this->sendData([$player], [EntityMetadataProperties::NAMETAG => new StringMetadataProperty($this->getNameTag())]);

View File

@ -34,7 +34,7 @@ class Location extends Position{
/** @var float */
public $pitch;
public function __construct(float $x, float $y, float $z, float $yaw = 0.0, float $pitch = 0.0, ?World $world = null){
public function __construct(float $x, float $y, float $z, ?World $world, float $yaw, float $pitch){
$this->yaw = $yaw;
$this->pitch = $pitch;
parent::__construct($x, $y, $z, $world);
@ -44,14 +44,14 @@ class Location extends Position{
* @return Location
*/
public static function fromObject(Vector3 $pos, ?World $world, float $yaw = 0.0, float $pitch = 0.0){
return new Location($pos->x, $pos->y, $pos->z, $yaw, $pitch, $world ?? (($pos instanceof Position) ? $pos->world : null));
return new Location($pos->x, $pos->y, $pos->z, $world ?? (($pos instanceof Position) ? $pos->world : null), $yaw, $pitch);
}
/**
* Return a Location instance
*/
public function asLocation() : Location{
return new Location($this->x, $this->y, $this->z, $this->yaw, $this->pitch, $this->world);
return new Location($this->x, $this->y, $this->z, $this->world, $this->yaw, $this->pitch);
}
public function getYaw() : float{

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class ArmSwingAnimation implements Animation{
@ -37,7 +38,7 @@ final class ArmSwingAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->entity->getId(), ActorEventPacket::ARM_SWING, 0)
ActorEventPacket::create($this->entity->getId(), ActorEvent::ARM_SWING, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\projectile\Arrow;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
class ArrowShakeAnimation implements Animation{
@ -40,7 +41,7 @@ class ArrowShakeAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->arrow->getId(), ActorEventPacket::ARROW_SHAKE, $this->durationInTicks)
ActorEventPacket::create($this->arrow->getId(), ActorEvent::ARROW_SHAKE, $this->durationInTicks)
];
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\entity\Human;
use pocketmine\item\Item;
use pocketmine\network\mcpe\convert\ItemTranslator;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class ConsumingItemAnimation implements Animation{
@ -45,7 +46,7 @@ final class ConsumingItemAnimation implements Animation{
[$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($this->item->getId(), $this->item->getMeta());
return [
//TODO: need to check the data values
ActorEventPacket::create($this->human->getId(), ActorEventPacket::EATING_ITEM, ($netId << 16) | $netData)
ActorEventPacket::create($this->human->getId(), ActorEvent::EATING_ITEM, ($netId << 16) | $netData)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class DeathAnimation implements Animation{
@ -37,7 +38,7 @@ final class DeathAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->entity->getId(), ActorEventPacket::DEATH_ANIMATION, 0)
ActorEventPacket::create($this->entity->getId(), ActorEvent::DEATH_ANIMATION, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class HurtAnimation implements Animation{
@ -37,7 +38,7 @@ final class HurtAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->entity->getId(), ActorEventPacket::HURT_ANIMATION, 0)
ActorEventPacket::create($this->entity->getId(), ActorEvent::HURT_ANIMATION, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class RespawnAnimation implements Animation{
@ -37,7 +38,7 @@ final class RespawnAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->entity->getId(), ActorEventPacket::RESPAWN, 0)
ActorEventPacket::create($this->entity->getId(), ActorEvent::RESPAWN, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Squid;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class SquidInkCloudAnimation implements Animation{
@ -37,7 +38,7 @@ final class SquidInkCloudAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->squid->getId(), ActorEventPacket::SQUID_INK_CLOUD, 0)
ActorEventPacket::create($this->squid->getId(), ActorEvent::SQUID_INK_CLOUD, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Human;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class TotemUseAnimation implements Animation{
@ -38,7 +39,7 @@ final class TotemUseAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->human->getId(), ActorEventPacket::CONSUME_TOTEM, 0)
ActorEventPacket::create($this->human->getId(), ActorEvent::CONSUME_TOTEM, 0)
];
}
}

View File

@ -206,14 +206,15 @@ class ItemEntity extends Entity{
}
protected function sendSpawnPacket(Player $player) : void{
$pk = new AddItemActorPacket();
$pk->entityRuntimeId = $this->getId();
$pk->position = $this->location->asVector3();
$pk->motion = $this->getMotion();
$pk->item = ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getItem()));
$pk->metadata = $this->getAllNetworkData();
$player->getNetworkSession()->sendDataPacket($pk);
$player->getNetworkSession()->sendDataPacket(AddItemActorPacket::create(
$this->getId(), //TODO: entity unique ID
$this->getId(),
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getItem())),
$this->location->asVector3(),
$this->getMotion(),
$this->getAllNetworkData(),
false //TODO: I have no idea what this is needed for, but right now we don't support fishing anyway
));
}
public function getOffsetPosition(Vector3 $vector3) : Vector3{

View File

@ -149,17 +149,17 @@ class Painting extends Entity{
}
protected function sendSpawnPacket(Player $player) : void{
$pk = new AddPaintingPacket();
$pk->entityRuntimeId = $this->getId();
$pk->position = new Vector3(
($this->boundingBox->minX + $this->boundingBox->maxX) / 2,
($this->boundingBox->minY + $this->boundingBox->maxY) / 2,
($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2
);
$pk->direction = self::FACING_TO_DATA[$this->facing];
$pk->title = $this->motive->getName();
$player->getNetworkSession()->sendDataPacket($pk);
$player->getNetworkSession()->sendDataPacket(AddPaintingPacket::create(
$this->getId(), //TODO: entity unique ID
$this->getId(),
new Vector3(
($this->boundingBox->minX + $this->boundingBox->maxX) / 2,
($this->boundingBox->minY + $this->boundingBox->maxY) / 2,
($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2
),
self::FACING_TO_DATA[$this->facing],
$this->motive->getName()
));
}
/**

View File

@ -175,6 +175,7 @@ class Arrow extends Projectile{
$item = VanillaItems::ARROW();
$playerInventory = match(true){
!$player->hasFiniteResources() => null, //arrows are not picked up in creative
$player->getOffHandInventory()->getItem(0)->canStackWith($item) and $player->getOffHandInventory()->canAddItem($item) => $player->getOffHandInventory(),
$player->getInventory()->canAddItem($item) => $player->getInventory(),
default => null

View File

@ -77,32 +77,28 @@ abstract class Projectile extends Entity{
$this->setHealth(1);
$this->damage = $nbt->getDouble("damage", $this->damage);
do{
$blockPos = null;
$blockId = null;
$blockData = null;
(function() use ($nbt) : void{
if(($tileXTag = $nbt->getTag("tileX")) instanceof IntTag and ($tileYTag = $nbt->getTag("tileY")) instanceof IntTag and ($tileZTag = $nbt->getTag("tileZ")) instanceof IntTag){
$blockPos = new Vector3($tileXTag->getValue(), $tileYTag->getValue(), $tileZTag->getValue());
}else{
break;
return;
}
if(($blockIdTag = $nbt->getTag("blockId")) instanceof IntTag){
$blockId = $blockIdTag->getValue();
}else{
break;
return;
}
if(($blockDataTag = $nbt->getTag("blockData")) instanceof ByteTag){
$blockData = $blockDataTag->getValue();
}else{
break;
return;
}
$this->blockHit = BlockFactory::getInstance()->get($blockId, $blockData);
$this->blockHit->position($this->getWorld(), $blockPos->getFloorX(), $blockPos->getFloorY(), $blockPos->getFloorZ());
}while(false);
})();
}
public function canCollideWith(Entity $entity) : bool{

View File

@ -47,7 +47,7 @@ class HandlerList{
*/
public function register(RegisteredListener $listener) : void{
if(isset($this->handlerSlots[$listener->getPriority()][spl_object_id($listener)])){
throw new \InvalidStateException("This listener is already registered to priority {$listener->getPriority()} of event {$this->class}");
throw new \InvalidArgumentException("This listener is already registered to priority {$listener->getPriority()} of event {$this->class}");
}
$this->handlerSlots[$listener->getPriority()][spl_object_id($listener)] = $listener;
}

View File

@ -29,7 +29,7 @@ namespace pocketmine\event\entity;
class EntityEffectRemoveEvent extends EntityEffectEvent{
public function cancel() : void{
if($this->getEffect()->getDuration() <= 0){
throw new \InvalidStateException("Removal of expired effects cannot be cancelled");
throw new \LogicException("Removal of expired effects cannot be cancelled");
}
parent::cancel();
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\event\player;
use pocketmine\event\Event;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use function is_a;
/**
@ -96,10 +97,7 @@ class PlayerCreationEvent extends Event{
* @phpstan-param class-string<Player> $class
*/
public function setPlayerClass($class) : void{
if(!is_a($class, $this->baseClass, true)){
throw new \RuntimeException("Class $class must extend " . $this->baseClass);
}
Utils::testValidInstance($class, $this->baseClass);
$this->playerClass = $class;
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\player\Player;
/**
* Called when a player uses an emote.
*/
class PlayerEmoteEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
public function __construct(
Player $player,
private string $emoteId
){
$this->player = $player;
}
public function getEmoteId() : string{
return $this->emoteId;
}
public function setEmoteId(string $emoteId) : void{
$this->emoteId = $emoteId;
}
}

View File

@ -37,7 +37,7 @@ final class CreativeInventory{
private $creative = [];
private function __construct(){
$creativeItems = json_decode(file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "vanilla", "creativeitems.json")), true);
$creativeItems = json_decode(file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, "creativeitems.json")), true);
foreach($creativeItems as $data){
$item = Item::jsonDeserialize($data);

View File

@ -123,4 +123,8 @@ class Bow extends Tool implements Releasable{
return ItemUseResult::SUCCESS();
}
public function canStartUsingItem(Player $player) : bool{
return !$player->hasFiniteResources() || $player->getOffHandInventory()->contains($arrow = VanillaItems::ARROW()) || $player->getInventory()->contains($arrow);
}
}

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Living;
use pocketmine\player\Player;
abstract class Food extends Item implements FoodSourceItem{
public function requiresHunger() : bool{
@ -41,4 +42,8 @@ abstract class Food extends Item implements FoodSourceItem{
public function onConsume(Living $consumer) : void{
}
public function canStartUsingItem(Player $player) : bool{
return !$this->requiresHunger() || $player->getHungerManager()->isHungry();
}
}

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Living;
use pocketmine\player\Player;
class MilkBucket extends Item implements ConsumableItem{
@ -42,4 +43,8 @@ class MilkBucket extends Item implements ConsumableItem{
public function onConsume(Living $consumer) : void{
$consumer->getEffects()->clear();
}
public function canStartUsingItem(Player $player) : bool{
return true;
}
}

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Living;
use pocketmine\player\Player;
class Potion extends Item implements ConsumableItem{
@ -52,4 +53,8 @@ class Potion extends Item implements ConsumableItem{
public function getResidue() : Item{
return VanillaItems::GLASS_BOTTLE();
}
public function canStartUsingItem(Player $player) : bool{
return true;
}
}

View File

@ -23,9 +23,13 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\player\Player;
/**
* Interface implemented by objects that can be used.
*/
interface Releasable{
public function canStartUsingItem(Player $player) : bool;
}

View File

@ -1950,6 +1950,14 @@ final class KnownTranslationFactory{
]);
}
public static function pocketmine_server_networkStartFailed(Translatable|string $ipAddress, Translatable|string $port, Translatable|string $errorMessage) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_NETWORKSTARTFAILED, [
"ipAddress" => $ipAddress,
"port" => $port,
"errorMessage" => $errorMessage,
]);
}
public static function pocketmine_server_query_running(Translatable|string $param0, Translatable|string $param1) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_QUERY_RUNNING, [
0 => $param0,
@ -2075,6 +2083,10 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POTION_SATURATION, []);
}
public static function potion_slowFalling() : Translatable{
return new Translatable(KnownTranslationKeys::POTION_SLOWFALLING, []);
}
public static function potion_waterBreathing() : Translatable{
return new Translatable(KnownTranslationKeys::POTION_WATERBREATHING, []);
}

View File

@ -401,6 +401,7 @@ final class KnownTranslationKeys{
public const POCKETMINE_SERVER_INFO_EXTENDED = "pocketmine.server.info.extended";
public const POCKETMINE_SERVER_LICENSE = "pocketmine.server.license";
public const POCKETMINE_SERVER_NETWORKSTART = "pocketmine.server.networkStart";
public const POCKETMINE_SERVER_NETWORKSTARTFAILED = "pocketmine.server.networkStartFailed";
public const POCKETMINE_SERVER_QUERY_RUNNING = "pocketmine.server.query.running";
public const POCKETMINE_SERVER_START = "pocketmine.server.start";
public const POCKETMINE_SERVER_STARTFINISHED = "pocketmine.server.startFinished";
@ -430,6 +431,7 @@ final class KnownTranslationKeys{
public const POTION_REGENERATION = "potion.regeneration";
public const POTION_RESISTANCE = "potion.resistance";
public const POTION_SATURATION = "potion.saturation";
public const POTION_SLOWFALLING = "potion.slowFalling";
public const POTION_WATERBREATHING = "potion.waterBreathing";
public const POTION_WEAKNESS = "potion.weakness";
public const POTION_WITHER = "potion.wither";

View File

@ -52,7 +52,7 @@ class Language{
*/
public static function getLanguageList(string $path = "") : array{
if($path === ""){
$path = Path::join(\pocketmine\RESOURCE_PATH, "locale");
$path = \pocketmine\LOCALE_DATA_PATH;
}
if(is_dir($path)){
@ -101,7 +101,7 @@ class Language{
$this->langName = strtolower($lang);
if($path === null){
$path = Path::join(\pocketmine\RESOURCE_PATH, "locale");
$path = \pocketmine\LOCALE_DATA_PATH;
}
$this->lang = self::loadLang($path, $this->langName);

View File

@ -91,6 +91,9 @@ class Network{
$this->sessionManager->tick();
}
/**
* @throws NetworkInterfaceStartException
*/
public function registerInterface(NetworkInterface $interface) : bool{
$ev = new NetworkInterfaceRegisterEvent($interface);
$ev->call();

View File

@ -33,6 +33,7 @@ interface NetworkInterface{
/**
* Performs actions needed to start the interface after it is registered.
* @throws NetworkInterfaceStartException
*/
public function start() : void;

View File

@ -0,0 +1,32 @@
<?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;
/**
* Thrown when a network interface fails to start for some reason.
* This should be used when, for example, your network interface can't bind to the port it wants.
*/
final class NetworkInterfaceStartException extends \RuntimeException{
}

View File

@ -72,7 +72,7 @@ class ChunkRequestTask extends AsyncTask{
$subCount = ChunkSerializer::getSubChunkCount($chunk);
$encoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary());
$payload = ChunkSerializer::serializeFullChunk($chunk, RuntimeBlockMapping::getInstance(), $encoderContext, $this->tiles);
$this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $subCount, $payload))->getBuffer()));
$this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create($this->chunkX, $this->chunkZ, $subCount, null, $payload))->getBuffer()));
}
public function onError() : void{

View File

@ -45,6 +45,7 @@ use pocketmine\network\mcpe\protocol\CreativeContentPacket;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
@ -150,7 +151,7 @@ class InventoryManager{
return;
}
}
throw new \UnsupportedOperationException("Unsupported inventory type");
throw new \LogicException("Unsupported inventory type");
}
/** @phpstan-return ObjectSet<ContainerOpenClosure> */
@ -164,27 +165,22 @@ class InventoryManager{
//TODO: we should be using some kind of tagging system to identify the types. Instanceof is flaky especially
//if the class isn't final, not to mention being inflexible.
if($inv instanceof BlockInventory){
switch(true){
case $inv instanceof LoomInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::LOOM, $inv->getHolder())];
case $inv instanceof FurnaceInventory:
return match($inv->getFurnaceType()->id()){
FurnaceType::FURNACE()->id() => [ContainerOpenPacket::blockInvVec3($id, WindowTypes::FURNACE, $inv->getHolder())],
FurnaceType::BLAST_FURNACE()->id() => [ContainerOpenPacket::blockInvVec3($id, WindowTypes::BLAST_FURNACE, $inv->getHolder())],
FurnaceType::SMOKER()->id() => [ContainerOpenPacket::blockInvVec3($id, WindowTypes::SMOKER, $inv->getHolder())],
$blockPosition = BlockPosition::fromVector3($inv->getHolder());
$windowType = match(true){
$inv instanceof LoomInventory => WindowTypes::LOOM,
$inv instanceof FurnaceInventory => match($inv->getFurnaceType()->id()){
FurnaceType::FURNACE()->id() => WindowTypes::FURNACE,
FurnaceType::BLAST_FURNACE()->id() => WindowTypes::BLAST_FURNACE,
FurnaceType::SMOKER()->id() => WindowTypes::SMOKER,
default => throw new AssumptionFailedError("Unreachable")
};
case $inv instanceof EnchantInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::ENCHANTMENT, $inv->getHolder())];
case $inv instanceof BrewingStandInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::BREWING_STAND, $inv->getHolder())];
case $inv instanceof AnvilInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::ANVIL, $inv->getHolder())];
case $inv instanceof HopperInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::HOPPER, $inv->getHolder())];
default:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::CONTAINER, $inv->getHolder())];
}
},
$inv instanceof EnchantInventory => WindowTypes::ENCHANTMENT,
$inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND,
$inv instanceof AnvilInventory => WindowTypes::ANVIL,
$inv instanceof HopperInventory => WindowTypes::HOPPER,
default => WindowTypes::CONTAINER
};
return [ContainerOpenPacket::blockInv($id, $windowType, $blockPosition)];
}
return null;
}
@ -279,6 +275,7 @@ class InventoryManager{
$this->player->getId(),
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->player->getInventory()->getItemInHand())),
$selected,
$selected,
ContainerIds::INVENTORY
));
$this->clientSelectedHotbarSlot = $selected;

View File

@ -62,6 +62,7 @@ use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
@ -88,6 +89,7 @@ use pocketmine\network\mcpe\protocol\SetTitlePacket;
use pocketmine\network\mcpe\protocol\TakeItemActorPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\TransferPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\command\CommandData;
use pocketmine\network\mcpe\protocol\types\command\CommandEnum;
use pocketmine\network\mcpe\protocol\types\command\CommandParameter;
@ -347,6 +349,10 @@ class NetworkSession{
try{
foreach($stream->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){
if($packet === null){
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
throw new PacketHandlingException("Unknown packet received");
}
try{
$this->handleDataPacket($packet, $buffer);
}catch(PacketHandlingException $e){
@ -446,6 +452,8 @@ class NetworkSession{
}
}
public function getPacketSerializerContext() : PacketSerializerContext{ return $this->packetSerializerContext; }
public function getBroadcaster() : PacketBroadcaster{ return $this->broadcaster; }
public function getCompressor() : Compressor{
@ -529,8 +537,6 @@ class NetworkSession{
/**
* Instructs the remote client to connect to a different server.
*
* @throws \UnsupportedOperationException
*/
public function transfer(string $ip, int $port, string $reason = "transfer") : void{
$this->tryDisconnect(function() use ($ip, $port, $reason) : void{
@ -556,7 +562,7 @@ class NetworkSession{
*/
private function doServerDisconnect(string $reason, bool $notify = true) : void{
if($notify){
$this->sendDataPacket($reason === "" ? DisconnectPacket::silent() : DisconnectPacket::message($reason), true);
$this->sendDataPacket(DisconnectPacket::create($reason !== "" ? $reason : null), true);
}
$this->sender->close($notify ? $reason : "");
@ -726,16 +732,17 @@ class NetworkSession{
$yaw = $yaw ?? $location->getYaw();
$pitch = $pitch ?? $location->getPitch();
$pk = new MovePlayerPacket();
$pk->entityRuntimeId = $this->player->getId();
$pk->position = $this->player->getOffsetPosition($pos);
$pk->pitch = $pitch;
$pk->headYaw = $yaw;
$pk->yaw = $yaw;
$pk->mode = $mode;
$pk->onGround = $this->player->onGround;
$this->sendDataPacket($pk);
$this->sendDataPacket(MovePlayerPacket::simple(
$this->player->getId(),
$this->player->getOffsetPosition($pos),
$pitch,
$yaw,
$yaw, //TODO: head yaw
$mode,
$this->player->onGround,
0, //TODO: riding entity ID
0 //TODO: tick
));
if($this->handler instanceof InGamePacketHandler){
$this->handler->forceMoveSync = true;
@ -748,16 +755,17 @@ class NetworkSession{
}
public function syncViewAreaCenterPoint(Vector3 $newPos, int $viewDistance) : void{
$this->sendDataPacket(NetworkChunkPublisherUpdatePacket::create($newPos->getFloorX(), $newPos->getFloorY(), $newPos->getFloorZ(), $viewDistance * 16)); //blocks, not chunks >.>
$this->sendDataPacket(NetworkChunkPublisherUpdatePacket::create(BlockPosition::fromVector3($newPos), $viewDistance * 16)); //blocks, not chunks >.>
}
public function syncPlayerSpawnPoint(Position $newSpawn) : void{
[$x, $y, $z] = [$newSpawn->getFloorX(), $newSpawn->getFloorY(), $newSpawn->getFloorZ()];
$this->sendDataPacket(SetSpawnPositionPacket::playerSpawn($x, $y, $z, DimensionIds::OVERWORLD, $x, $y, $z));
$newSpawnBlockPosition = BlockPosition::fromVector3($newSpawn);
//TODO: respawn causing block position (bed, respawn anchor)
$this->sendDataPacket(SetSpawnPositionPacket::playerSpawn($newSpawnBlockPosition, DimensionIds::OVERWORLD, $newSpawnBlockPosition));
}
public function syncWorldSpawnPoint(Position $newSpawn) : void{
$this->sendDataPacket(SetSpawnPositionPacket::worldSpawn($newSpawn->getFloorX(), $newSpawn->getFloorY(), $newSpawn->getFloorZ(), DimensionIds::OVERWORLD));
$this->sendDataPacket(SetSpawnPositionPacket::worldSpawn(BlockPosition::fromVector3($newSpawn), DimensionIds::OVERWORLD));
}
public function syncGameMode(GameMode $mode, bool $isRollback = false) : void{
@ -774,7 +782,15 @@ class NetworkSession{
* TODO: make this less specialized
*/
public function syncAdventureSettings(Player $for) : void{
$pk = new AdventureSettingsPacket();
$isOp = $for->hasPermission(DefaultPermissions::ROOT_OPERATOR);
$pk = AdventureSettingsPacket::create(
0,
$isOp ? AdventureSettingsPacket::PERMISSION_OPERATOR : AdventureSettingsPacket::PERMISSION_NORMAL,
0,
$isOp ? PlayerPermissions::OPERATOR : PlayerPermissions::MEMBER,
0,
$for->getId()
);
$pk->setFlag(AdventureSettingsPacket::WORLD_IMMUTABLE, $for->isSpectator());
$pk->setFlag(AdventureSettingsPacket::NO_PVP, $for->isSpectator());
@ -785,11 +801,6 @@ class NetworkSession{
//TODO: permission flags
$isOp = $for->hasPermission(DefaultPermissions::ROOT_OPERATOR);
$pk->commandPermission = ($isOp ? AdventureSettingsPacket::PERMISSION_OPERATOR : AdventureSettingsPacket::PERMISSION_NORMAL);
$pk->playerPermission = ($isOp ? PlayerPermissions::OPERATOR : PlayerPermissions::MEMBER);
$pk->entityUniqueId = $for->getId();
$this->sendDataPacket($pk);
}
@ -826,9 +837,9 @@ class NetworkSession{
}
public function syncAvailableCommands() : void{
$pk = new AvailableCommandsPacket();
$commandData = [];
foreach($this->server->getCommandMap()->getCommands() as $name => $command){
if(isset($pk->commandData[$command->getName()]) or $command->getName() === "help" or !$command->testPermissionSilent($this->player)){
if(isset($commandData[$command->getName()]) or $command->getName() === "help" or !$command->testPermissionSilent($this->player)){
continue;
}
@ -855,10 +866,10 @@ class NetworkSession{
]
);
$pk->commandData[$command->getName()] = $data;
$commandData[$command->getName()] = $data;
}
$this->sendDataPacket($pk);
$this->sendDataPacket(AvailableCommandsPacket::create($commandData, [], [], []));
}
public function onRawChatMessage(string $message) : void{
@ -916,7 +927,7 @@ class NetworkSession{
$this->logger->debug("Tried to send no-longer-active chunk $chunkX $chunkZ in world " . $world->getFolderName());
return;
}
if(!$status->equals(UsedChunkStatus::REQUESTED())){
if(!$status->equals(UsedChunkStatus::REQUESTED_SENDING())){
//TODO: make this an error
//this could be triggered due to the shitty way that chunk resends are handled
//right now - not because of the spammy re-requesting, but because the chunk status reverts
@ -966,12 +977,12 @@ class NetworkSession{
public function onMobMainHandItemChange(Human $mob) : void{
//TODO: we could send zero for slot here because remote players don't need to know which slot was selected
$inv = $mob->getInventory();
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand())), $inv->getHeldItemIndex(), ContainerIds::INVENTORY));
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand())), $inv->getHeldItemIndex(), $inv->getHeldItemIndex(), ContainerIds::INVENTORY));
}
public function onMobOffHandItemChange(Human $mob) : void{
$inv = $mob->getOffHandInventory();
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItem(0))), 0, ContainerIds::OFFHAND));
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItem(0))), 0, 0, ContainerIds::OFFHAND));
}
public function onMobArmorChange(Living $mob) : void{
@ -1033,6 +1044,10 @@ class NetworkSession{
$this->sendDataPacket(SetTitlePacket::setAnimationTimes($fadeIn, $stay, $fadeOut));
}
public function onEmote(Player $from, string $emoteId) : void{
$this->sendDataPacket(EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
}
public function tick() : void{
if($this->info === null){
if(time() >= $this->connectTime + 10){

View File

@ -23,10 +23,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pocketmine\network\mcpe\compression\Compressor;
use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\Server;
use function spl_object_id;
@ -40,35 +37,40 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
}
public function broadcastPackets(array $recipients, array $packets) : void{
//TODO: we should be using session-specific serializer contexts for this
$stream = PacketBatch::fromPackets(new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()), ...$packets);
/** @var Compressor[] $compressors */
$buffers = [];
$compressors = [];
/** @var NetworkSession[][] $compressorTargets */
$compressorTargets = [];
$targetMap = [];
foreach($recipients as $recipient){
$compressor = $recipient->getCompressor();
$compressorId = spl_object_id($compressor);
$serializerContext = $recipient->getPacketSerializerContext();
$bufferId = spl_object_id($serializerContext);
if(!isset($buffers[$bufferId])){
$buffers[$bufferId] = PacketBatch::fromPackets($serializerContext, ...$packets);
}
//TODO: different compressors might be compatible, it might not be necessary to split them up by object
$compressors[$compressorId] = $compressor;
$compressorTargets[$compressorId][] = $recipient;
$compressor = $recipient->getCompressor();
$compressors[spl_object_id($compressor)] = $compressor;
$targetMap[$bufferId][spl_object_id($compressor)][] = $recipient;
}
foreach($compressors as $compressorId => $compressor){
if(!$compressor->willCompress($stream->getBuffer())){
foreach($compressorTargets[$compressorId] as $target){
foreach($packets as $pk){
$target->addToSendBuffer($pk);
foreach($targetMap as $bufferId => $compressorMap){
$buffer = $buffers[$bufferId];
foreach($compressorMap as $compressorId => $compressorTargets){
$compressor = $compressors[$compressorId];
if(!$compressor->willCompress($buffer->getBuffer())){
foreach($compressorTargets as $target){
foreach($packets as $pk){
$target->addToSendBuffer($pk);
}
}
}else{
$promise = $this->server->prepareBatch($buffer, $compressor);
foreach($compressorTargets as $target){
$target->queueCompressed($promise);
}
}
}else{
$promise = $this->server->prepareBatch($stream, $compressor);
foreach($compressorTargets[$compressorId] as $target){
$target->queueCompressed($promise);
}
}
}
}
}

View File

@ -36,9 +36,6 @@ use function strlen;
/**
* This class is used by the current MCPE protocol system to store cached chunk packets for fast resending.
*
* TODO: make MemoryManager aware of this so the cache can be destroyed when memory is low
* TODO: this needs a hook for world unloading
*/
class ChunkCache implements ChunkListener{
/** @var self[][] */
@ -69,6 +66,19 @@ class ChunkCache implements ChunkListener{
return self::$instances[$worldId][$compressorId];
}
public static function pruneCaches() : void{
foreach(self::$instances as $compressorMap){
foreach($compressorMap as $chunkCache){
foreach($chunkCache->caches as $chunkHash => $promise){
if($promise->hasResult()){
//Do not clear promises that are not yet fulfilled; they will have requesters waiting on them
unset($chunkCache->caches[$chunkHash]);
}
}
}
}
}
/** @var World */
private $world;
/** @var Compressor */

View File

@ -71,15 +71,14 @@ final class CraftingDataCache{
*/
private function buildCraftingDataCache(CraftingManager $manager) : CraftingDataPacket{
Timings::$craftingDataCacheRebuild->startTiming();
$pk = new CraftingDataPacket();
$pk->cleanRecipes = true;
$counter = 0;
$nullUUID = Uuid::fromString(Uuid::NIL);
$converter = TypeConverter::getInstance();
$recipesWithTypeIds = [];
foreach($manager->getShapelessRecipes() as $list){
foreach($list as $recipe){
$pk->entries[] = new ProtocolShapelessRecipe(
$recipesWithTypeIds[] = new ProtocolShapelessRecipe(
CraftingDataPacket::ENTRY_SHAPELESS,
Binary::writeInt(++$counter),
array_map(function(Item $item) use ($converter) : RecipeIngredient{
@ -104,7 +103,7 @@ final class CraftingDataCache{
$inputs[$row][$column] = $converter->coreItemStackToRecipeIngredient($recipe->getIngredient($column, $row));
}
}
$pk->entries[] = $r = new ProtocolShapedRecipe(
$recipesWithTypeIds[] = $r = new ProtocolShapedRecipe(
CraftingDataPacket::ENTRY_SHAPED,
Binary::writeInt(++$counter),
$inputs,
@ -128,7 +127,7 @@ final class CraftingDataCache{
};
foreach($manager->getFurnaceRecipeManager($furnaceType)->getAll() as $recipe){
$input = $converter->coreItemStackToNet($recipe->getInput());
$pk->entries[] = new ProtocolFurnaceRecipe(
$recipesWithTypeIds[] = new ProtocolFurnaceRecipe(
CraftingDataPacket::ENTRY_FURNACE_DATA,
$input->getId(),
$input->getMeta(),
@ -139,6 +138,6 @@ final class CraftingDataCache{
}
Timings::$craftingDataCacheRebuild->stopTiming();
return $pk;
return CraftingDataPacket::create($recipesWithTypeIds, [], [], [], true);
}
}

View File

@ -50,8 +50,8 @@ class StaticPacketCache{
private static function make() : self{
return new self(
BiomeDefinitionListPacket::create(self::loadCompoundFromFile(Path::join(\pocketmine\RESOURCE_PATH, 'vanilla', 'biome_definitions.nbt'))),
AvailableActorIdentifiersPacket::create(self::loadCompoundFromFile(Path::join(\pocketmine\RESOURCE_PATH, 'vanilla', 'entity_identifiers.nbt')))
BiomeDefinitionListPacket::create(self::loadCompoundFromFile(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'biome_definitions.nbt'))),
AvailableActorIdentifiersPacket::create(self::loadCompoundFromFile(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'entity_identifiers.nbt')))
);
}

View File

@ -59,7 +59,7 @@ class CompressBatchPromise{
public function resolve(string $result) : void{
if(!$this->cancelled){
if($this->result !== null){
throw new \InvalidStateException("Cannot resolve promise more than once");
throw new \LogicException("Cannot resolve promise more than once");
}
$this->result = $result;
foreach($this->callbacks as $callback){
@ -80,7 +80,7 @@ class CompressBatchPromise{
public function getResult() : string{
$this->checkCancelled();
if($this->result === null){
throw new \InvalidStateException("Promise has not yet been resolved");
throw new \LogicException("Promise has not yet been resolved");
}
return $this->result;
}
@ -95,7 +95,7 @@ class CompressBatchPromise{
public function cancel() : void{
if($this->hasResult()){
throw new \InvalidStateException("Cannot cancel a resolved promise");
throw new \LogicException("Cannot cancel a resolved promise");
}
$this->cancelled = true;
}

View File

@ -39,7 +39,7 @@ final class GlobalItemTypeDictionary{
use SingletonTrait;
private static function make() : self{
$data = file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, 'vanilla', 'required_item_list.json'));
$data = file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'required_item_list.json'));
if($data === false) throw new AssumptionFailedError("Missing required resource file");
$table = json_decode($data, true);
if(!is_array($table)){

View File

@ -66,14 +66,14 @@ final class ItemTranslator{
private $complexNetToCoreMapping = [];
private static function make() : self{
$data = file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, 'vanilla', 'r16_to_current_item_map.json'));
$data = file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'r16_to_current_item_map.json'));
if($data === false) throw new AssumptionFailedError("Missing required resource file");
$json = json_decode($data, true);
if(!is_array($json) or !isset($json["simple"], $json["complex"]) || !is_array($json["simple"]) || !is_array($json["complex"])){
throw new AssumptionFailedError("Invalid item table format");
}
$legacyStringToIntMapRaw = file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, 'vanilla', 'item_id_map.json'));
$legacyStringToIntMapRaw = file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'item_id_map.json'));
if($legacyStringToIntMapRaw === false){
throw new AssumptionFailedError("Missing required resource file");
}
@ -175,11 +175,12 @@ final class ItemTranslator{
/**
* @return int[]
* @phpstan-return array{int, int}
* @throws TypeConversionException
*/
public function fromNetworkId(int $networkId, int $networkMeta, ?bool &$isComplexMapping = null) : array{
if(isset($this->complexNetToCoreMapping[$networkId])){
if($networkMeta !== 0){
throw new \UnexpectedValueException("Unexpected non-zero network meta on complex item mapping");
throw new TypeConversionException("Unexpected non-zero network meta on complex item mapping");
}
$isComplexMapping = true;
return $this->complexNetToCoreMapping[$networkId];
@ -188,12 +189,13 @@ final class ItemTranslator{
if(isset($this->simpleNetToCoreMapping[$networkId])){
return [$this->simpleNetToCoreMapping[$networkId], $networkMeta];
}
throw new \UnexpectedValueException("Unmapped network ID/metadata combination $networkId:$networkMeta");
throw new TypeConversionException("Unmapped network ID/metadata combination $networkId:$networkMeta");
}
/**
* @return int[]
* @phpstan-return array{int, int}
* @throws TypeConversionException
*/
public function fromNetworkIdWithWildcardHandling(int $networkId, int $networkMeta) : array{
$isComplexMapping = false;

View File

@ -49,7 +49,7 @@ final class RuntimeBlockMapping{
private $bedrockKnownStates;
private function __construct(){
$canonicalBlockStatesFile = file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "vanilla", "canonical_block_states.nbt"));
$canonicalBlockStatesFile = file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, "canonical_block_states.nbt"));
if($canonicalBlockStatesFile === false){
throw new AssumptionFailedError("Missing required resource file");
}
@ -67,7 +67,7 @@ final class RuntimeBlockMapping{
$legacyIdMap = LegacyBlockIdToStringIdMap::getInstance();
/** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
$legacyStateMap = [];
$legacyStateMapReader = PacketSerializer::decoder(file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "vanilla", "r12_to_current_block_map.bin")), 0, new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()));
$legacyStateMapReader = PacketSerializer::decoder(file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, "r12_to_current_block_map.bin")), 0, new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()));
$nbtReader = new NetworkNbtSerializer();
while(!$legacyStateMapReader->feof()){
$id = $legacyStateMapReader->getString();

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\handler;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\RespawnPacket;
use pocketmine\network\mcpe\protocol\types\PlayerAction;
use pocketmine\player\Player;
class DeathPacketHandler extends PacketHandler{
@ -49,7 +50,7 @@ class DeathPacketHandler extends PacketHandler{
}
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
if($packet->action === PlayerActionPacket::ACTION_RESPAWN){
if($packet->action === PlayerAction::RESPAWN){
$this->player->respawn();
return true;
}

View File

@ -59,6 +59,7 @@ use pocketmine\network\mcpe\protocol\CommandRequestPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
@ -83,6 +84,7 @@ use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
use pocketmine\network\mcpe\protocol\SubClientLoginPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\MismatchTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
@ -92,6 +94,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\network\mcpe\protocol\types\inventory\UseItemOnEntityTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\UseItemTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes;
use pocketmine\network\mcpe\protocol\types\PlayerAction;
use pocketmine\network\PacketHandlingException;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
@ -199,14 +202,14 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleActorEvent(ActorEventPacket $packet) : bool{
if($packet->entityRuntimeId !== $this->player->getId()){
if($packet->actorRuntimeId !== $this->player->getId()){
//TODO HACK: EATING_ITEM is sent back to the server when the server sends it for other players (1.14 bug, maybe earlier)
return $packet->event === ActorEventPacket::EATING_ITEM;
return $packet->actorRuntimeId === ActorEvent::EATING_ITEM;
}
$this->player->doCloseInventory();
switch($packet->event){
case ActorEventPacket::EATING_ITEM: //TODO: ignore this and handle it server-side
switch($packet->eventId){
case ActorEvent::EATING_ITEM: //TODO: ignore this and handle it server-side
$item = $this->player->getInventory()->getItemInHand();
if($item->isNull()){
return false;
@ -295,6 +298,7 @@ class InGamePacketHandler extends PacketHandler{
//all of the parts before we can execute it
return true;
}
$this->player->setUsingItem(false);
try{
$this->inventoryManager->onTransactionStart($this->craftingTransaction);
$this->craftingTransaction->execute();
@ -330,6 +334,7 @@ class InGamePacketHandler extends PacketHandler{
return true;
}
$this->player->setUsingItem(false);
$transaction = new InventoryTransaction($this->player, $actions);
$this->inventoryManager->onTransactionStart($transaction);
try{
@ -356,12 +361,12 @@ class InGamePacketHandler extends PacketHandler{
switch($data->getActionType()){
case UseItemTransactionData::ACTION_CLICK_BLOCK:
//TODO: start hack for client spam bug
$clickPos = $data->getClickPos();
$clickPos = $data->getClickPosition();
$spamBug = ($this->lastRightClickData !== null and
microtime(true) - $this->lastRightClickTime < 0.1 and //100ms
$this->lastRightClickData->getPlayerPos()->distanceSquared($data->getPlayerPos()) < 0.00001 and
$this->lastRightClickData->getBlockPos()->equals($data->getBlockPos()) and
$this->lastRightClickData->getClickPos()->distanceSquared($clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
$this->lastRightClickData->getPlayerPosition()->distanceSquared($data->getPlayerPosition()) < 0.00001 and
$this->lastRightClickData->getBlockPosition()->equals($data->getBlockPosition()) and
$this->lastRightClickData->getClickPosition()->distanceSquared($clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
);
//get rid of continued spam if the player clicks and holds right-click
$this->lastRightClickData = $data;
@ -371,9 +376,10 @@ class InGamePacketHandler extends PacketHandler{
}
//TODO: end hack for client spam bug
$blockPos = $data->getBlockPos();
if(!$this->player->interactBlock($blockPos, $data->getFace(), $clickPos)){
$this->onFailedBlockAction($blockPos, $data->getFace());
$blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos)){
$this->onFailedBlockAction($vBlockPos, $data->getFace());
}elseif(
!array_key_exists($windowId = InventoryManager::HARDCODED_CRAFTING_GRID_WINDOW_ID, $this->openHardcodedWindows) &&
$this->player->getCraftingGrid()->getGridWidth() === CraftingGrid::SIZE_BIG
@ -381,7 +387,7 @@ class InGamePacketHandler extends PacketHandler{
//TODO: HACK! crafting grid doesn't fit very well into the current PM container system, so this hack
//allows it to carry on working approximately the same way as it did in 1.14
$this->openHardcodedWindows[$windowId] = true;
$this->session->sendDataPacket(ContainerOpenPacket::blockInvVec3(
$this->session->sendDataPacket(ContainerOpenPacket::blockInv(
InventoryManager::HARDCODED_CRAFTING_GRID_WINDOW_ID,
WindowTypes::WORKBENCH,
$blockPos
@ -389,9 +395,10 @@ class InGamePacketHandler extends PacketHandler{
}
return true;
case UseItemTransactionData::ACTION_BREAK_BLOCK:
$blockPos = $data->getBlockPos();
if(!$this->player->breakBlock($blockPos)){
$this->onFailedBlockAction($blockPos, null);
$blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->breakBlock($vBlockPos)){
$this->onFailedBlockAction($vBlockPos, null);
}
return true;
case UseItemTransactionData::ACTION_CLICK_AIR:
@ -432,7 +439,7 @@ class InGamePacketHandler extends PacketHandler{
}
private function handleUseItemOnEntityTransaction(UseItemOnEntityTransactionData $data) : bool{
$target = $this->player->getWorld()->getEntity($data->getEntityRuntimeId());
$target = $this->player->getWorld()->getEntity($data->getActorRuntimeId());
if($target === null){
return false;
}
@ -442,7 +449,7 @@ class InGamePacketHandler extends PacketHandler{
//TODO: use transactiondata for rollbacks here
switch($data->getActionType()){
case UseItemOnEntityTransactionData::ACTION_INTERACT:
if(!$this->player->interactEntity($target, $data->getClickPos())){
if(!$this->player->interactEntity($target, $data->getClickPosition())){
$this->inventoryManager->syncSlot($this->player->getInventory(), $this->player->getInventory()->getHeldItemIndex());
}
return true;
@ -498,7 +505,7 @@ class InGamePacketHandler extends PacketHandler{
//TODO: implement handling for this where it matters
return true;
}
$target = $this->player->getWorld()->getEntity($packet->target);
$target = $this->player->getWorld()->getEntity($packet->targetActorRuntimeId);
if($target === null){
return false;
}
@ -521,7 +528,7 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleBlockPickRequest(BlockPickRequestPacket $packet) : bool{
return $this->player->pickBlock(new Vector3($packet->blockX, $packet->blockY, $packet->blockZ), $packet->addUserData);
return $this->player->pickBlock(new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ()), $packet->addUserData);
}
public function handleActorPickRequest(ActorPickRequestPacket $packet) : bool{
@ -529,63 +536,63 @@ class InGamePacketHandler extends PacketHandler{
}
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
$pos = new Vector3($packet->x, $packet->y, $packet->z);
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
switch($packet->action){
case PlayerActionPacket::ACTION_START_BREAK:
case PlayerAction::START_BREAK:
if(!$this->player->attackBlock($pos, $packet->face)){
$this->onFailedBlockAction($pos, $packet->face);
}
break;
case PlayerActionPacket::ACTION_ABORT_BREAK:
case PlayerActionPacket::ACTION_STOP_BREAK:
case PlayerAction::ABORT_BREAK:
case PlayerAction::STOP_BREAK:
$this->player->stopBreakBlock($pos);
break;
case PlayerActionPacket::ACTION_START_SLEEPING:
case PlayerAction::START_SLEEPING:
//unused
break;
case PlayerActionPacket::ACTION_STOP_SLEEPING:
case PlayerAction::STOP_SLEEPING:
$this->player->stopSleep();
break;
case PlayerActionPacket::ACTION_JUMP:
case PlayerAction::JUMP:
$this->player->jump();
return true;
case PlayerActionPacket::ACTION_START_SPRINT:
case PlayerAction::START_SPRINT:
if(!$this->player->toggleSprint(true)){
$this->player->sendData([$this->player]);
}
return true;
case PlayerActionPacket::ACTION_STOP_SPRINT:
case PlayerAction::STOP_SPRINT:
if(!$this->player->toggleSprint(false)){
$this->player->sendData([$this->player]);
}
return true;
case PlayerActionPacket::ACTION_START_SNEAK:
case PlayerAction::START_SNEAK:
if(!$this->player->toggleSneak(true)){
$this->player->sendData([$this->player]);
}
return true;
case PlayerActionPacket::ACTION_STOP_SNEAK:
case PlayerAction::STOP_SNEAK:
if(!$this->player->toggleSneak(false)){
$this->player->sendData([$this->player]);
}
return true;
case PlayerActionPacket::ACTION_START_GLIDE:
case PlayerActionPacket::ACTION_STOP_GLIDE:
case PlayerAction::START_GLIDE:
case PlayerAction::STOP_GLIDE:
break; //TODO
case PlayerActionPacket::ACTION_CRACK_BREAK:
case PlayerAction::CRACK_BREAK:
$this->player->continueBreakBlock($pos, $packet->face);
break;
case PlayerActionPacket::ACTION_START_SWIMMING:
case PlayerAction::START_SWIMMING:
break; //TODO
case PlayerActionPacket::ACTION_STOP_SWIMMING:
case PlayerAction::STOP_SWIMMING:
//TODO: handle this when it doesn't spam every damn tick (yet another spam bug!!)
break;
case PlayerActionPacket::ACTION_INTERACT_BLOCK: //TODO: ignored (for now)
case PlayerAction::INTERACT_BLOCK: //TODO: ignored (for now)
break;
case PlayerActionPacket::ACTION_CREATIVE_PLAYER_DESTROY_BLOCK:
case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK:
//TODO: do we need to handle this?
break;
default:
@ -628,7 +635,7 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{
if($packet->entityUniqueId !== $this->player->getId()){
if($packet->targetActorUniqueId !== $this->player->getId()){
return false; //TODO: operators can change other people's permissions using this
}
@ -648,13 +655,13 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleBlockActorData(BlockActorDataPacket $packet) : bool{
$pos = new Vector3($packet->x, $packet->y, $packet->z);
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
if($pos->distanceSquared($this->player->getLocation()) > 10000){
return false;
}
$block = $this->player->getLocation()->getWorld()->getBlock($pos);
$nbt = $packet->namedtag->getRoot();
$nbt = $packet->nbt->getRoot();
if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
if($block instanceof BaseSign){
@ -678,7 +685,7 @@ class InGamePacketHandler extends PacketHandler{
return true;
}
$this->session->getLogger()->debug("Invalid sign update data: " . base64_encode($packet->namedtag->getEncodedNbt()));
$this->session->getLogger()->debug("Invalid sign update data: " . base64_encode($packet->nbt->getEncodedNbt()));
}
return false;
@ -712,9 +719,10 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleItemFrameDropItem(ItemFrameDropItemPacket $packet) : bool{
$block = $this->player->getWorld()->getBlockAt($packet->x, $packet->y, $packet->z);
$blockPosition = $packet->blockPosition;
$block = $this->player->getWorld()->getBlockAt($blockPosition->getX(), $blockPosition->getY(), $blockPosition->getZ());
if($block instanceof ItemFrame and $block->getFramedItem() !== null){
return $this->player->attackBlock(new Vector3($packet->x, $packet->y, $packet->z), $block->getFacing());
return $this->player->attackBlock(new Vector3($blockPosition->getX(), $blockPosition->getY(), $blockPosition->getZ()), $block->getFacing());
}
return false;
}
@ -883,4 +891,9 @@ class InGamePacketHandler extends PacketHandler{
*/
return true;
}
public function handleEmote(EmotePacket $packet) : bool{
$this->player->emote($packet->getEmoteId());
return true;
}
}

View File

@ -31,9 +31,11 @@ use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\BoolGameRule;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\network\mcpe\protocol\types\LevelSettings;
use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings;
use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
@ -64,39 +66,46 @@ class PreSpawnPacketHandler extends PacketHandler{
}
public function setUp() : void{
$spawnPosition = $this->player->getSpawn();
$location = $this->player->getLocation();
$pk = new StartGamePacket();
$pk->entityUniqueId = $this->player->getId();
$pk->entityRuntimeId = $this->player->getId();
$pk->playerGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->player->getGamemode());
$pk->playerPosition = $this->player->getOffsetPosition($location);
$pk->pitch = $location->pitch;
$pk->yaw = $location->yaw;
$pk->seed = -1;
$pk->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
$pk->worldGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->server->getGamemode());
$pk->difficulty = $location->getWorld()->getDifficulty();
$pk->spawnX = $spawnPosition->getFloorX();
$pk->spawnY = $spawnPosition->getFloorY();
$pk->spawnZ = $spawnPosition->getFloorZ();
$pk->hasAchievementsDisabled = true;
$pk->time = $location->getWorld()->getTime();
$pk->eduEditionOffer = 0;
$pk->rainLevel = 0; //TODO: implement these properly
$pk->lightningLevel = 0;
$pk->commandsEnabled = true;
$pk->gameRules = [
$levelSettings = new LevelSettings();
$levelSettings->seed = -1;
$levelSettings->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
$levelSettings->worldGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->server->getGamemode());
$levelSettings->difficulty = $location->getWorld()->getDifficulty();
$levelSettings->spawnPosition = BlockPosition::fromVector3($location->getWorld()->getSpawnLocation());
$levelSettings->hasAchievementsDisabled = true;
$levelSettings->time = $location->getWorld()->getTime();
$levelSettings->eduEditionOffer = 0;
$levelSettings->rainLevel = 0; //TODO: implement these properly
$levelSettings->lightningLevel = 0;
$levelSettings->commandsEnabled = true;
$levelSettings->gameRules = [
"naturalregeneration" => new BoolGameRule(false, false) //Hack for client side regeneration
];
$pk->experiments = new Experiments([], false);
$pk->levelId = "";
$pk->worldName = $this->server->getMotd();
$pk->itemTable = GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(); //TODO: check if this is actually needed
$pk->playerMovementSettings = new PlayerMovementSettings(PlayerMovementType::LEGACY, 0, false);
$pk->serverSoftwareVersion = sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true));
$this->session->sendDataPacket($pk);
$levelSettings->experiments = new Experiments([], false);
$this->session->sendDataPacket(StartGamePacket::create(
$this->player->getId(),
$this->player->getId(),
TypeConverter::getInstance()->coreGameModeToProtocol($this->player->getGamemode()),
$this->player->getOffsetPosition($location),
$location->pitch,
$location->yaw,
$levelSettings,
"",
$this->server->getMotd(),
"",
false,
new PlayerMovementSettings(PlayerMovementType::LEGACY, 0, false),
0,
0,
"",
false,
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
[],
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries()
));
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers());
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs());

View File

@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe\handler;
use pocketmine\lang\KnownTranslationKeys;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\ResourcePackChunkDataPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket;
@ -34,6 +35,7 @@ use pocketmine\network\mcpe\protocol\ResourcePackStackPacket;
use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\resourcepacks\ResourcePackManager;
use function array_map;
@ -113,7 +115,9 @@ class ResourcePacksPacketHandler extends PacketHandler{
self::PACK_CHUNK_SIZE,
(int) ceil($pack->getPackSize() / self::PACK_CHUNK_SIZE),
$pack->getPackSize(),
$pack->getSha256()
$pack->getSha256(),
false,
ResourcePackType::ADDON //TODO: this might be an addon (not behaviour pack), needed to properly support client-side custom items
));
}
$this->session->getLogger()->debug("Player requested download of " . count($packet->packIds) . " resource packs");
@ -130,7 +134,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
//we don't force here, because it doesn't have user-facing effects
//but it does have an annoying side-effect when true: it makes
//the client remove its own non-server-supplied resource packs.
$this->session->sendDataPacket(ResourcePackStackPacket::create($stack, [], false, new Experiments([], false)));
$this->session->sendDataPacket(ResourcePackStackPacket::create($stack, [], false, ProtocolInfo::MINECRAFT_VERSION_NETWORK, new Experiments([], false)));
$this->session->getLogger()->debug("Applying resource pack stack");
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:

View File

@ -32,10 +32,12 @@ use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\StandardPacketBroadcaster;
use pocketmine\network\Network;
use pocketmine\network\NetworkInterfaceStartException;
use pocketmine\network\PacketHandlingException;
use pocketmine\Server;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\utils\Utils;
use raklib\generic\SocketException;
use raklib\protocol\EncapsulatedPacket;
use raklib\protocol\PacketReliability;
use raklib\server\ipc\RakLibToUserThreadMessageReceiver;
@ -120,7 +122,11 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
while($this->eventReceiver->handle($this));
});
$this->server->getLogger()->debug("Waiting for RakLib to start...");
$this->rakLib->startAndWait();
try{
$this->rakLib->startAndWait();
}catch(SocketException $e){
throw new NetworkInterfaceStartException($e->getMessage(), 0, $e);
}
$this->server->getLogger()->debug("RakLib booted successfully");
}
@ -132,7 +138,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
if(!$this->rakLib->isRunning()){
$e = $this->rakLib->getCrashInfo();
if($e !== null){
throw new \RuntimeException("RakLib crashed: $e");
throw new \RuntimeException("RakLib crashed: " . $e->makePrettyMessage());
}
throw new \Exception("RakLib Thread crashed without crash information");
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\raklib;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\thread\Thread;
use raklib\generic\Socket;
use raklib\generic\SocketException;
use raklib\server\ipc\RakLibToUserThreadMessageSender;
use raklib\server\ipc\UserToRakLibThreadMessageReceiver;
use raklib\server\Server;
@ -68,7 +69,7 @@ class RakLibServer extends Thread{
/** @var SleeperNotifier */
protected $mainThreadNotifier;
/** @var string|null */
/** @var RakLibThreadCrashInfo|null */
public $crashInfo = null;
public function __construct(
@ -102,24 +103,24 @@ class RakLibServer extends Thread{
* @return void
*/
public function shutdownHandler(){
if($this->cleanShutdown !== true){
if($this->cleanShutdown !== true && $this->crashInfo === null){
$error = error_get_last();
if($error !== null){
$this->logger->emergency("Fatal error: " . $error["message"] . " in " . $error["file"] . " on line " . $error["line"]);
$this->setCrashInfo($error['message']);
$this->setCrashInfo(RakLibThreadCrashInfo::fromLastErrorInfo($error));
}else{
$this->logger->emergency("RakLib shutdown unexpectedly");
}
}
}
public function getCrashInfo() : ?string{
public function getCrashInfo() : ?RakLibThreadCrashInfo{
return $this->crashInfo;
}
private function setCrashInfo(string $info) : void{
$this->synchronized(function(string $info) : void{
private function setCrashInfo(RakLibThreadCrashInfo $info) : void{
$this->synchronized(function(RakLibThreadCrashInfo $info) : void{
$this->crashInfo = $info;
$this->notify();
}, $info);
@ -131,8 +132,12 @@ class RakLibServer extends Thread{
while(!$this->ready and $this->crashInfo === null){
$this->wait();
}
if($this->crashInfo !== null){
throw new \RuntimeException("RakLib failed to start: $this->crashInfo");
$crashInfo = $this->crashInfo;
if($crashInfo !== null){
if($crashInfo->getClass() === SocketException::class){
throw new SocketException($crashInfo->getMessage());
}
throw new \RuntimeException("RakLib failed to start: " . $crashInfo->makePrettyMessage());
}
});
}
@ -145,7 +150,12 @@ class RakLibServer extends Thread{
register_shutdown_function([$this, "shutdownHandler"]);
$socket = new Socket($this->address);
try{
$socket = new Socket($this->address);
}catch(SocketException $e){
$this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e));
return;
}
$manager = new Server(
$this->serverId,
$this->logger,
@ -166,7 +176,7 @@ class RakLibServer extends Thread{
$manager->waitShutdown();
$this->cleanShutdown = true;
}catch(\Throwable $e){
$this->setCrashInfo($e->getMessage());
$this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e));
$this->logger->logException($e);
}
}

View File

@ -0,0 +1,62 @@
<?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\raklib;
use pocketmine\utils\Filesystem;
use function get_class;
use function sprintf;
final class RakLibThreadCrashInfo{
public function __construct(
private ?string $class,
private string $message,
private string $file,
private int $line
){}
public static function fromThrowable(\Throwable $e) : self{
return new self(get_class($e), $e->getMessage(), $e->getFile(), $e->getLine());
}
/**
* @phpstan-param array{message: string, file: string, line: int} $info
*/
public static function fromLastErrorInfo(array $info) : self{
return new self(null, $info["message"], $info["file"], $info["line"]);
}
/** @return string|null */
public function getClass() : ?string{ return $this->class; }
public function getMessage() : string{ return $this->message; }
public function getFile() : string{ return $this->file; }
public function getLine() : int{ return $this->line; }
public function makePrettyMessage() : string{
return sprintf("%s: \"%s\" in %s on line %d", $this->class ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line);
}
}

View File

@ -146,7 +146,7 @@ class PermissibleInternal implements Permissible{
foreach($this->rootPermissions as $name => $isGranted){
$perm = $permManager->getPermission($name);
if($perm === null){
throw new \InvalidStateException("Unregistered root permission $name");
throw new \LogicException("Unregistered root permission $name");
}
$this->permissions[$name] = new PermissionAttachmentInfo($name, null, $isGranted, null);
$permManager->subscribeToPermission($name, $this);

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\player;
use pocketmine\world\World;
use const M_SQRT2;
//TODO: turn this into an interface?
final class ChunkSelector{
@ -33,34 +34,44 @@ final class ChunkSelector{
* @phpstan-return \Generator<int, int, void, void>
*/
public function selectChunks(int $radius, int $centerX, int $centerZ) : \Generator{
$radiusSquared = $radius ** 2;
for($subRadius = 0; $subRadius < $radius; $subRadius++){
$subRadiusSquared = $subRadius ** 2;
$nextSubRadiusSquared = ($subRadius + 1) ** 2;
$minX = (int) ($subRadius / M_SQRT2);
for($x = 0; $x < $radius; ++$x){
for($z = 0; $z <= $x; ++$z){
if(($x ** 2 + $z ** 2) > $radiusSquared){
break; //skip to next band
}
$lastZ = 0;
//If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be.
for($x = $subRadius; $x >= $minX; --$x){
for($z = $lastZ; $z <= $x; ++$z){
$distanceSquared = ($x ** 2 + $z ** 2);
if($distanceSquared < $subRadiusSquared){
continue;
}elseif($distanceSquared >= $nextSubRadiusSquared){
break; //skip to next X
}
/* Top right quadrant */
yield World::chunkHash($centerX + $x, $centerZ + $z);
/* Top left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ + $z);
/* Bottom right quadrant */
yield World::chunkHash($centerX + $x, $centerZ - $z - 1);
/* Bottom left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
$lastZ = $z;
//If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be.
if($x !== $z){
/* Top right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ + $x);
/* Top left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ + $x);
/* Bottom right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ - $x - 1);
/* Bottom left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
/* Top right quadrant */
yield World::chunkHash($centerX + $x, $centerZ + $z);
/* Top left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ + $z);
/* Bottom right quadrant */
yield World::chunkHash($centerX + $x, $centerZ - $z - 1);
/* Bottom left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
if($x !== $z){
/* Top right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ + $x);
/* Top left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ + $x);
/* Bottom right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ - $x - 1);
/* Bottom left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
}
}
}
}

View File

@ -53,6 +53,7 @@ use pocketmine\event\player\PlayerChatEvent;
use pocketmine\event\player\PlayerCommandPreprocessEvent;
use pocketmine\event\player\PlayerDeathEvent;
use pocketmine\event\player\PlayerDisplayNameChangeEvent;
use pocketmine\event\player\PlayerEmoteEvent;
use pocketmine\event\player\PlayerEntityInteractEvent;
use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\event\player\PlayerGameModeChangeEvent;
@ -72,6 +73,7 @@ use pocketmine\event\player\PlayerToggleSprintEvent;
use pocketmine\event\player\PlayerTransferEvent;
use pocketmine\form\Form;
use pocketmine\form\FormValidationException;
use pocketmine\inventory\CallbackInventoryListener;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerCursorInventory;
use pocketmine\inventory\transaction\action\DropItemAction;
@ -97,6 +99,7 @@ use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
@ -192,6 +195,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* @phpstan-var array<int, UsedChunkStatus>
*/
protected array $usedChunks = [];
/**
* @var true[]
* @phpstan-var array<int, true>
*/
private array $activeChunkGenerationRequests = [];
/**
* @var true[] chunkHash => dummy
* @phpstan-var array<int, true>
@ -233,6 +241,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/** @var int[] ID => ticks map */
protected array $usedItemsCooldown = [];
private int $lastEmoteTick = 0;
protected int $formIdCounter = 0;
/** @var Form[] */
protected array $forms = [];
@ -270,9 +280,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$world = $spawnLocation->getWorld();
//load the spawn chunk so we can see the terrain
$world->registerChunkLoader($this->chunkLoader, $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE, true);
$world->registerChunkListener($this, $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE);
$this->usedChunks[World::chunkHash($spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE)] = UsedChunkStatus::NEEDED();
$xSpawnChunk = $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE;
$zSpawnChunk = $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE;
$world->registerChunkLoader($this->chunkLoader, $xSpawnChunk, $zSpawnChunk, true);
$world->registerChunkListener($this, $xSpawnChunk, $zSpawnChunk);
$this->usedChunks[World::chunkHash($xSpawnChunk, $zSpawnChunk)] = UsedChunkStatus::NEEDED();
parent::__construct($spawnLocation, $this->playerInfo->getSkin(), $namedtag);
}
@ -285,6 +297,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
parent::initEntity($nbt);
$this->addDefaultWindows();
$this->inventory->getListeners()->add(new CallbackInventoryListener(
function(Inventory $unused, int $slot) : void{
if($slot === $this->inventory->getHeldItemIndex()){
$this->setUsingItem(false);
}
},
function() : void{
$this->setUsingItem(false);
}
));
$this->firstPlayed = $nbt->getLong("firstPlayed", $now = (int) (microtime(true) * 1000));
$this->lastPlayed = $nbt->getLong("lastPlayed", $now);
@ -391,7 +414,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
public function spawnTo(Player $player) : void{
if($this->isAlive() and $player->isAlive() and $player->getWorld() === $this->getWorld() and $player->canSee($this) and !$this->isSpectator()){
if($this->isAlive() and $player->isAlive() and $player->canSee($this) and !$this->isSpectator()){
parent::spawnTo($player);
}
}
@ -472,7 +495,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
public function getNetworkSession() : NetworkSession{
if($this->networkSession === null){
throw new \InvalidStateException("Player is not connected");
throw new \LogicException("Player is not connected");
}
return $this->networkSession;
}
@ -625,6 +648,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
$this->getNetworkSession()->stopUsingChunk($x, $z);
unset($this->usedChunks[$index]);
unset($this->activeChunkGenerationRequests[$index]);
}
$world->unregisterChunkLoader($this->chunkLoader, $x, $z);
$world->unregisterChunkListener($this, $x, $z);
@ -661,8 +685,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$count = 0;
$world = $this->getWorld();
$limit = $this->chunksPerTick - count($this->activeChunkGenerationRequests);
foreach($this->loadQueue as $index => $distance){
if($count >= $this->chunksPerTick){
if($count >= $limit){
break;
}
@ -673,7 +699,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
++$count;
$this->usedChunks[$index] = UsedChunkStatus::NEEDED();
$this->usedChunks[$index] = UsedChunkStatus::REQUESTED_GENERATION();
$this->activeChunkGenerationRequests[$index] = true;
unset($this->loadQueue[$index]);
$this->getWorld()->registerChunkLoader($this->chunkLoader, $X, $Z, true);
$this->getWorld()->registerChunkListener($this, $X, $Z);
@ -682,15 +710,14 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if(!$this->isConnected() || !isset($this->usedChunks[$index]) || $world !== $this->getWorld()){
return;
}
if(!$this->usedChunks[$index]->equals(UsedChunkStatus::NEEDED())){
//TODO: make this an error
//we may have added multiple completion handlers, since the Player keeps re-requesting chunks
//it doesn't have yet (a relic from the old system, but also currently relied on for chunk resends).
//in this event, make sure we don't try to send the chunk multiple times.
if(!$this->usedChunks[$index]->equals(UsedChunkStatus::REQUESTED_GENERATION())){
//We may have previously requested this, decided we didn't want it, and then decided we did want
//it again, all before the generation request got executed. In that case, the promise would have
//multiple callbacks for this player. In that case, only the first one matters.
return;
}
unset($this->loadQueue[$index]);
$this->usedChunks[$index] = UsedChunkStatus::REQUESTED();
unset($this->activeChunkGenerationRequests[$index]);
$this->usedChunks[$index] = UsedChunkStatus::REQUESTED_SENDING();
$this->getNetworkSession()->startUsingChunk($X, $Z, function() use ($X, $Z, $index) : void{
$this->usedChunks[$index] = UsedChunkStatus::SENT();
@ -757,7 +784,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if($this->getHealth() <= 0){
$this->logger->debug("Quit while dead, forcing respawn");
$this->respawn();
$this->actuallyRespawn();
}
}
@ -1161,16 +1188,18 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->lastLocation = $to;
$this->broadcastMovement();
$distance = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
if($this->isSprinting()){
$this->hungerManager->exhaust(0.1 * $distance, PlayerExhaustEvent::CAUSE_SPRINTING);
}else{
$this->hungerManager->exhaust(0.01 * $distance, PlayerExhaustEvent::CAUSE_WALKING);
}
$horizontalDistanceTravelled = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
if($horizontalDistanceTravelled > 0){
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
if($this->isSprinting()){
$this->hungerManager->exhaust(0.1 * $horizontalDistanceTravelled, PlayerExhaustEvent::CAUSE_SPRINTING);
}else{
$this->hungerManager->exhaust(0.01 * $horizontalDistanceTravelled, PlayerExhaustEvent::CAUSE_WALKING);
}
if($this->nextChunkOrderRun > 20){
$this->nextChunkOrderRun = 20;
if($this->nextChunkOrderRun > 20){
$this->nextChunkOrderRun = 20;
}
}
}
@ -1372,7 +1401,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->inventory->setItemInHand($item);
}
$this->setUsingItem($item instanceof Releasable);
$this->setUsingItem($item instanceof Releasable && $item->canStartUsingItem($this));
return true;
}
@ -1515,8 +1544,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return true;
}
if(!$this->isCreative()){
$this->blockBreakHandler = SurvivalBlockBreakHandler::createIfNecessary($this, $pos, $target, $face, 16);
if(!$this->isCreative() && !$block->getBreakInfo()->breaksInstantly()){
$this->blockBreakHandler = new SurvivalBlockBreakHandler($this, $pos, $target, $face, 16);
}
return true;
@ -1725,6 +1754,21 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return true;
}
public function emote(string $emoteId) : void{
$currentTick = $this->server->getTick();
if($currentTick - $this->lastEmoteTick > 5){
$this->lastEmoteTick = $currentTick;
$event = new PlayerEmoteEvent($this, $emoteId);
$event->call();
if(!$event->isCancelled()){
$emoteId = $event->getEmoteId();
foreach($this->getViewers() as $player){
$player->getNetworkSession()->onEmote($this, $emoteId);
}
}
}
}
/**
* Drops an item on the ground in front of the player.
*/
@ -1939,7 +1983,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
*/
public function onPostDisconnect(string $reason, Translatable|string|null $quitMessage) : void{
if($this->isConnected()){
throw new \InvalidStateException("Player is still connected");
throw new \LogicException("Player is still connected");
}
//prevent the player receiving their own disconnect message
@ -2098,10 +2142,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
public function respawn() : void{
if($this->respawnLocked){
return;
}
$this->respawnLocked = true;
if($this->server->isHardcore()){
if($this->kick("You have been banned because you died in hardcore mode")){ //this allows plugins to prevent the ban by cancelling PlayerKickEvent
$this->server->getNameBans()->addBan($this->getName(), "Died in hardcore mode");
@ -2109,6 +2149,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return;
}
$this->actuallyRespawn();
}
protected function actuallyRespawn() : void{
if($this->respawnLocked){
return;
}
$this->respawnLocked = true;
$this->logger->debug("Waiting for spawn terrain generation for respawn");
$spawn = $this->getSpawn();
$spawn->getWorld()->orderChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
@ -2183,7 +2232,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$properties->setGenericFlag(EntityMetadataFlags::ACTION, $this->startAction > -1);
$properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null);
$properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping ?? new Vector3(0, 0, 0));
$properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping !== null ? BlockPosition::fromVector3($this->sleeping) : new BlockPosition(0, 0, 0));
}
public function sendData(?array $targets, ?array $data = null) : void{

View File

@ -28,6 +28,7 @@ use pocketmine\entity\animation\ArmSwingAnimation;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelEvent;
use pocketmine\world\particle\BlockPunchParticle;
use pocketmine\world\sound\BlockPunchSound;
use function abs;
@ -58,7 +59,7 @@ final class SurvivalBlockBreakHandler{
/** @var float */
private $breakProgress = 0;
private function __construct(Player $player, Vector3 $blockPos, Block $block, int $targetedFace, int $maxPlayerDistance, int $fxTickInterval = self::DEFAULT_FX_INTERVAL_TICKS){
public function __construct(Player $player, Vector3 $blockPos, Block $block, int $targetedFace, int $maxPlayerDistance, int $fxTickInterval = self::DEFAULT_FX_INTERVAL_TICKS){
$this->player = $player;
$this->blockPos = $blockPos;
$this->block = $block;
@ -70,19 +71,11 @@ final class SurvivalBlockBreakHandler{
if($this->breakSpeed > 0){
$this->player->getWorld()->broadcastPacketToViewers(
$this->blockPos,
LevelEventPacket::create(LevelEventPacket::EVENT_BLOCK_START_BREAK, (int) (65535 * $this->breakSpeed), $this->blockPos)
LevelEventPacket::create(LevelEvent::BLOCK_START_BREAK, (int) (65535 * $this->breakSpeed), $this->blockPos)
);
}
}
public static function createIfNecessary(Player $player, Vector3 $blockPos, Block $block, int $targetedFace, int $maxPlayerDistance, int $fxTickInterval = self::DEFAULT_FX_INTERVAL_TICKS) : ?self{
$breakInfo = $block->getBreakInfo();
if(!$breakInfo->breaksInstantly()){
return new self($player, $blockPos, $block, $targetedFace, $maxPlayerDistance, $fxTickInterval);
}
return null;
}
/**
* Returns the calculated break speed as percentage progress per game tick.
*/
@ -100,8 +93,7 @@ final class SurvivalBlockBreakHandler{
}
public function update() : bool{
if(
$this->player->getPosition()->distanceSquared($this->blockPos->add(0.5, 0.5, 0.5)) > $this->maxPlayerDistance ** 2){
if($this->player->getPosition()->distanceSquared($this->blockPos->add(0.5, 0.5, 0.5)) > $this->maxPlayerDistance ** 2){
return false;
}
@ -147,7 +139,7 @@ final class SurvivalBlockBreakHandler{
if($this->player->getWorld()->isInLoadedTerrain($this->blockPos)){
$this->player->getWorld()->broadcastPacketToViewers(
$this->blockPos,
LevelEventPacket::create(LevelEventPacket::EVENT_BLOCK_STOP_BREAK, 0, $this->blockPos)
LevelEventPacket::create(LevelEvent::BLOCK_STOP_BREAK, 0, $this->blockPos)
);
}
}

View File

@ -32,7 +32,8 @@ use pocketmine\utils\EnumTrait;
* @generate-registry-docblock
*
* @method static UsedChunkStatus NEEDED()
* @method static UsedChunkStatus REQUESTED()
* @method static UsedChunkStatus REQUESTED_GENERATION()
* @method static UsedChunkStatus REQUESTED_SENDING()
* @method static UsedChunkStatus SENT()
*/
final class UsedChunkStatus{
@ -41,7 +42,8 @@ final class UsedChunkStatus{
protected static function setup() : void{
self::registerAll(
new self("NEEDED"),
new self("REQUESTED"),
new self("REQUESTED_GENERATION"),
new self("REQUESTED_SENDING"),
new self("SENT")
);
}

View File

@ -30,13 +30,9 @@ use function array_map;
use function array_values;
use function is_array;
use function is_string;
use function phpversion;
use function preg_match;
use function str_replace;
use function stripos;
use function strlen;
use function substr;
use function version_compare;
use function yaml_parse;
class PluginDescription{
@ -247,40 +243,6 @@ class PluginDescription{
return $this->extensions;
}
/**
* Checks if the current PHP runtime has the extensions required by the plugin.
*
* @throws PluginException if there are required extensions missing or have incompatible version, or if the version constraint cannot be parsed
*/
public function checkRequiredExtensions() : void{
foreach($this->extensions as $name => $versionConstrs){
$gotVersion = phpversion($name);
if($gotVersion === false){
throw new PluginException("Required extension $name not loaded");
}
foreach($versionConstrs as $constr){ // versionConstrs_loop
if($constr === "*"){
continue;
}
if($constr === ""){
throw new PluginException("One of the extension version constraints of $name is empty. Consider quoting the version string in plugin.yml");
}
foreach(["<=", "le", "<>", "!=", "ne", "<", "lt", "==", "=", "eq", ">=", "ge", ">", "gt"] as $comparator){
// warning: the > character should be quoted in YAML
if(substr($constr, 0, strlen($comparator)) === $comparator){
$version = substr($constr, strlen($comparator));
if(!version_compare($gotVersion, $version, $comparator)){
throw new PluginException("Required extension $name has an incompatible version ($gotVersion not $constr)");
}
continue 2; // versionConstrs_loop
}
}
throw new PluginException("Error parsing version constraint: $constr");
}
}
}
/**
* @return string[]
*/

View File

@ -0,0 +1,42 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\plugin;
final class PluginLoadTriage{
/**
* @var PluginLoadTriageEntry[]
* @phpstan-var array<string, PluginLoadTriageEntry>
*/
public $plugins = [];
/**
* @var string[][]
* @phpstan-var array<string, list<string>>
*/
public $dependencies = [];
/**
* @var string[][]
* @phpstan-var array<string, list<string>>
*/
public $softDependencies = [];
}

View File

@ -0,0 +1,108 @@
<?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\plugin;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Translatable;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\utils\Utils;
use pocketmine\utils\VersionString;
use function array_intersect;
use function count;
use function implode;
use function in_array;
use function phpversion;
use function stripos;
use function strlen;
use function substr;
use function version_compare;
final class PluginLoadabilityChecker{
public function __construct(
private string $apiVersion
){}
public function check(PluginDescription $description) : Translatable|null{
$name = $description->getName();
if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
return KnownTranslationFactory::pocketmine_plugin_restrictedName();
}
foreach($description->getCompatibleApis() as $api){
if(!VersionString::isValidBaseVersion($api)){
return KnownTranslationFactory::pocketmine_plugin_invalidAPI($api);
}
}
if(!ApiVersion::isCompatible($this->apiVersion, $description->getCompatibleApis())){
return KnownTranslationFactory::pocketmine_plugin_incompatibleAPI(implode(", ", $description->getCompatibleApis()));
}
$ambiguousVersions = ApiVersion::checkAmbiguousVersions($description->getCompatibleApis());
if(count($ambiguousVersions) > 0){
return KnownTranslationFactory::pocketmine_plugin_ambiguousMinAPI(implode(", ", $ambiguousVersions));
}
if(count($description->getCompatibleOperatingSystems()) > 0 and !in_array(Utils::getOS(), $description->getCompatibleOperatingSystems(), true)) {
return KnownTranslationFactory::pocketmine_plugin_incompatibleOS(implode(", ", $description->getCompatibleOperatingSystems()));
}
if(count($pluginMcpeProtocols = $description->getCompatibleMcpeProtocols()) > 0){
$serverMcpeProtocols = [ProtocolInfo::CURRENT_PROTOCOL];
if(count(array_intersect($pluginMcpeProtocols, $serverMcpeProtocols)) === 0){
return KnownTranslationFactory::pocketmine_plugin_incompatibleProtocol(implode(", ", $pluginMcpeProtocols));
}
}
foreach($description->getRequiredExtensions() as $extensionName => $versionConstrs){
$gotVersion = phpversion($extensionName);
if($gotVersion === false){
return KnownTranslationFactory::pocketmine_plugin_extensionNotLoaded($extensionName);
}
foreach($versionConstrs as $k => $constr){ // versionConstrs_loop
if($constr === "*"){
continue;
}
if($constr === ""){
return KnownTranslationFactory::pocketmine_plugin_emptyExtensionVersionConstraint(extensionName: $extensionName, constraintIndex: "$k");
}
foreach(["<=", "le", "<>", "!=", "ne", "<", "lt", "==", "=", "eq", ">=", "ge", ">", "gt"] as $comparator){
// warning: the > character should be quoted in YAML
if(substr($constr, 0, strlen($comparator)) === $comparator){
$version = substr($constr, strlen($comparator));
if(!version_compare($gotVersion, $version, $comparator)){
return KnownTranslationFactory::pocketmine_plugin_incompatibleExtensionVersion(extensionName: $extensionName, extensionVersion: $gotVersion, pluginRequirement: $constr);
}
continue 2; // versionConstrs_loop
}
}
return KnownTranslationFactory::pocketmine_plugin_invalidExtensionVersionConstraint(extensionName: $extensionName, versionConstraint: $constr);
}
}
return null;
}
}

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