Compare commits

..

145 Commits
3.1.5 ... 3.2.1

Author SHA1 Message Date
1193efd69e Release 3.2.1 2018-08-31 18:58:53 +01:00
f466fd5568 Updated runtime IDs table
apparently 1 (!) missing entry (for barrier) was causing client sided crashes unexplained. This is infuriating.
2018-08-31 16:19:42 +01:00
d5a5209334 Merge branch 'release/3.1' into release/3.2 2018-08-30 15:46:46 +01:00
3a85e6cab9 Backport ce58294305 for 3.x line 2018-08-30 15:46:26 +01:00
bca493a682 3.2.1 is next 2018-08-30 11:04:47 +01:00
ba12dfafd6 Release 3.2.0 2018-08-29 16:28:35 +01:00
e09087de26 Fix version numbers 2018-08-29 16:27:01 +01:00
888dba704b Merge branch 'mcpe-1.6' into release/3.2 2018-08-29 16:23:53 +01:00
511249c562 Sync 3.2 and 3.1 branches 2018-08-29 16:23:28 +01:00
17f1bf5512 3.1.6 is next 2018-08-29 16:08:45 +01:00
05d9bb45d0 Merge branch 'release/3.1' into release/3.2 2018-08-26 19:15:16 +01:00
e5cda34548 Merge branch 'release/3.1' into mcpe-1.6 2018-08-26 18:05:14 +01:00
032b20f659 Server: remove premature optimization of findEntity() 2018-08-25 18:37:18 +01:00
fe6d546190 Merge branch 'release/3.1' into release/3.2 2018-08-25 17:49:21 +01:00
3e6f70ddf6 Merge branch 'release/3.1' into mcpe-1.6 2018-08-23 18:00:06 +01:00
bea634a9b7 Change air tank regeneration to match UA (#2396) 2018-08-22 19:10:53 +01:00
8daf3dc8b4 Merge branch 'release/3.1' into release/3.2 2018-08-22 15:41:52 +01:00
d419d4308f Add a couple of item constants, register Scute item
leaving out turtle helmet for now because of complications relating to the effect application - I REALLY don't want to tick armour if I can avoid it, due to the performance concerns.
2018-08-21 19:54:24 +01:00
9ca38ba868 Protocol changes for 1.6.0.8 + resource packs "fix" 2018-08-21 17:36:55 +01:00
424c50e1e9 Protocol changes for 1.6.0.5, minus Entity->Actor rename 2018-08-21 17:36:29 +01:00
566f3c6262 AvailableCommandsPacket: stricter decode and handling 2018-08-21 17:14:54 +01:00
0d05dcec08 AvailableCommandsPacket: deal with dynamic enums
somehow I missed this, thanks @NiclasOlofsson for pointing it out
2018-08-21 17:14:53 +01:00
986077e03c Protocol changes for 1.6.0.1 2018-08-21 17:14:53 +01:00
ddcb2f002a Tile: Be explicit about not calling Tile::createNBT() (#2388)
A common pitfall developers fall into with this function is that it has to be called from the scope of the tile class you're creating NBT for, but people commonly do Tile::createNBT() directly, which then results in cryptic "Tile is not registered" errors. This now throws a BadMethodCallException instead to be fully clear about this.

In the future this will be removed completely once NBT is no longer required to create a tile, but for now this is a confusing issue that should be dealt with.
2018-08-19 19:54:22 +01:00
c496480d2b Merge branch 'release/3.1' into release/3.2 2018-08-19 19:52:40 +01:00
2eda8cfad3 Merge branch 'release/3.1' into release/3.2 2018-08-19 16:01:47 +01:00
21e7b5ea43 TesterPlugin: removed usage of deprecated ServerCommandEvent 2018-08-19 14:05:26 +01:00
8304675af7 Merge branch 'release/3.1' into release/3.2 2018-08-19 13:59:58 +01:00
ebffff0caa Added CommandEvent, deprecated (Remote)?ServerCommandEvent (#2376) 2018-08-19 12:59:16 +01:00
0dc4bd36e1 Form: change handleResponse() return type to void
this returning is counter intuitive and doesn't make any sense without the queuing mechanism. Instead it's simpler to just use Player->sendForm().
2018-08-19 11:44:59 +01:00
9d17c9a09d Merge branch 'release/3.1' into release/3.2 2018-08-19 11:22:58 +01:00
bfa415e108 Add unit tests for json decoding hack 2018-08-19 10:30:52 +01:00
b66095cb36 Added a hack for MC W10 JSON empty strings bug (#2383) 2018-08-19 10:10:40 +01:00
0336ae8229 Merge branch 'release/3.1' into release/3.2 2018-08-17 19:47:41 +01:00
4b3e17e681 Sync 3.2 and 3.1 branches 2018-08-16 18:33:54 +01:00
5424644ca1 Merge branch 'release/3.1' into release/3.2 2018-08-16 15:52:19 +01:00
aa7c4bc64d Merge branch 'release/3.1' into release/3.2 2018-08-15 13:48:46 +01:00
df8e10cad9 Forms API, part 1: add Player->sendForm() and Form interface
There's no implementation here yet, but that can come later. This lays the ground for allowing plugins to have an integrated method to send forms, as well as a solution to the ID conflict problem.

A built in implementation should not be a concretion and it should be able to be swapped for third party implementations. This enables the possiblity to do so.
2018-08-14 16:06:55 +01:00
d98a6e566c Merge branch 'release/3.1' into release/3.2 2018-08-14 15:03:30 +01:00
ade2be9eee Merge branch 'release/3.1' into release/3.2 2018-08-13 13:22:40 +01:00
39ed6a7cdf Merge branch 'release/3.1' into release/3.2 2018-08-11 19:59:53 +01:00
bec5aaa54b Merge branch 'release/3.1' into release/3.2 2018-08-11 19:37:18 +01:00
cf29ab1f17 Arrow: remove unused import 2018-08-07 19:06:40 +01:00
c5c5a53a13 Merge branch 'release/3.1' into release/3.2 2018-08-07 18:49:13 +01:00
63a65680ac typo 2018-08-07 14:43:29 +01:00
47cd6fe105 EntityDamageEvent: Add API to customize Living entity attack cooldown time
closes #2310
2018-08-07 14:39:26 +01:00
f582b5a3db Merge branch 'release/3.1' into release/3.2 2018-08-07 14:32:38 +01:00
7f0fa2ac3d PluginBase: Do not fill defaults from resources/config.yml (#2316)
This fixes #2219.
2018-08-07 12:33:24 +01:00
max
f3b2bcfd13 Added Conduit Power effect 2018-08-06 21:30:15 +01:00
c947909c2e Updated language submodule 2018-08-06 20:52:53 +01:00
09dadc72bc Merge branch 'release/3.1' into release/3.2 2018-08-06 18:45:08 +01:00
ca541032ae Move Player death message derivation to PlayerDeathEvent static method
This now no longer requires a Player to operate, only a player name.
2018-08-05 12:19:16 +01:00
bcf9915082 Merge branch 'release/3.1' into release/3.2 2018-08-05 11:33:11 +01:00
8d6dc4e188 Merge branch 'release/3.1' into release/3.2 2018-08-04 16:47:26 +01:00
12d8d925c8 TimingsCommand: check for instances of InternetException only 2018-08-04 14:59:31 +01:00
f3f229ef7c Internet: only catch InternetExceptions - anything else is an unexpected fault condition 2018-08-04 14:51:26 +01:00
6614183c7f Merge branch 'release/3.1' into release/3.2 2018-08-03 20:07:52 +01:00
7ebf3c7bf4 Merge branch 'release/3.1' into release/3.2 2018-08-03 18:50:22 +01:00
334caaaa34 Merge branch 'release/3.1' into release/3.2 2018-08-03 18:24:44 +01:00
6fcaef068f Merge branch 'release/3.1' into release/3.2 2018-08-02 14:43:51 +01:00
c09ad9263b Empty merge of 3.1 into 3.2 2018-07-30 15:21:42 +01:00
4cc2f037a9 Merge branch 'release/3.1' into release/3.2 2018-07-30 14:54:10 +01:00
99045fe21a Entity: Implement setting score tag 2018-07-30 09:36:32 +01:00
bda271ca63 Merge branch 'release/3.1' into release/3.2 2018-07-27 11:47:36 +01:00
b3f2396ea5 UPnP: Make error message less useless 2018-07-26 16:00:35 +01:00
ab0510cb37 Merge branch 'release/3.1' into release/3.2 2018-07-26 14:40:33 +01:00
9a423be1db Internet: Throw more specific exceptions
RuntimeException is very generic and might be thrown for other reasons apart from web request failures.

This is backwards compatible because InternetException is a descendent of RuntimeException. Additionally, getURL() and postURL() have intentionally been left untouched for backwards compatibility's sake.
2018-07-26 12:34:14 +01:00
08be51dc23 Clear permissions on server reload 2018-07-26 10:40:28 +01:00
94352782d5 https://media.giphy.com/media/UAUtB4Oi9U4EM/giphy.gif 2018-07-26 10:31:57 +01:00
8fae79f85b Merge branch 'release/3.1' into release/3.2 2018-07-26 10:25:19 +01:00
9a2845640b Permissions management cleanup (#2332)
* Added a new PermissionManager, remove ridiculous cyclic dependencies of Permissions on Server

Aside from all the other ridiculous design problems with the permission system, the biggest problems are its API. This is, once again, a result of poor API design copied from Bukkit.

This pull request removes all permission-related functionality from `PluginManager` and moves it to the `pocketmine\permission\PermissionManager` class.

As can be observed from the removed code in the diff, the permissions system was previously entirely dependent on the Server, because it needed to get the PluginManager for registering permissions. This is utterly ridiculous. This refactor isolates _most_ permission-related functionality within the `permission` namespace.

As mentioned above, this stupid API is a direct result of copying from Bukkit. If you look at the API documentation for Bukkit for `PluginManager` you will see that the methods I'm deprecating here are also in there.

## Changes
- Added a new `PermissionManager` class. This can be accessed via its singleton `getInstance()` static method.
- Deprecated the following `PluginManager` methods - these will be removed no later than 4.0.0:
  - `getPermission()`
  - `addPermission()`
  - `removePermission()`
  - `getDefaultPermissions()`
  - `recalculatePermissionDefaults()`
  - `subscribeToPermission()`
  - `unsubscribeFromPermission()`
  - `getPermissionSubscriptions()`
  - `subscribeToDefaultPerms()`
  - `unsubscribeFromDefaultPerms()`
  - `getDefaultPermSubscriptions()`
  - `getPermissions()`
2018-07-26 10:21:41 +01:00
580f71d496 Permission: cosmetic reorg 2018-07-25 20:56:23 +01:00
24f11779f2 Level: don't try to unregister generators from non-live workers
this was causing garbage-collected workers to get restarted on shutdown if they previously had the generator registered for that level.
2018-07-25 18:52:17 +01:00
706c620d04 Move Internet-related functions from Utils into their own class (#2324)
- Added `Internet::getIP()`, `Internet::getURL()`, `Internet::postURL()`, and `Internet::simpleCurl()`.
- Deprecated the corresponding functions in `Utils`. Updating to the new functions is as simple as replacing `Utils` with `Internet`, since this doesn't break backwards compatibility.

The deprecations should be catered for by plugin developers. These deprecated redirects will be removed no later than 4.0.0.
2018-07-25 15:51:18 +01:00
951870e6ec Merge branch 'release/3.1' into release/3.2 2018-07-25 15:30:40 +01:00
1405099768 Merge branch 'release/3.1' into release/3.2 2018-07-24 17:19:18 +01:00
1464487945 Utils: remove unnecessary do...while(false) from getIP() 2018-07-24 16:33:03 +01:00
40c28f4d26 PluginManager: Automatically create data directories for plugins (#2284) 2018-07-21 15:57:37 +01:00
90bf94f8f7 Merge branch 'release/3.1' into release/3.2 2018-07-21 09:53:41 +01:00
522ef042a7 yet another empty merge... this is tiresome 2018-07-20 12:21:49 +01:00
76ee6bc298 Merge branch 'release/3.1' into release/3.2 2018-07-20 11:57:13 +01:00
04f20c703c Merge branch 'release/3.1' into release/3.2 2018-07-20 11:30:39 +01:00
efe4b0cd3a Merge branch 'release/3.1' into release/3.2 2018-07-18 15:14:34 +01:00
527d8e9374 Merge branch 'release/3.1' into release/3.2 2018-07-17 18:35:16 +01:00
c1c70a8a98 move up a version
pushing this back to 3.2 so that we can make space for MCPE 1.5 line as 3.1.
2018-07-17 18:34:55 +01:00
b7f15b6574 Merge branch 'release/3.0' into release/3.1 2018-07-17 16:56:57 +01:00
6ab2fa84da added some tests for ItemFactory::fromString() 2018-07-17 14:52:47 +01:00
b480c63060 Fixed ItemFactory::fromString() meta handling bug introduced by 71c3c34976 2018-07-17 14:46:08 +01:00
f6b54f5116 Server: don't create levels inside catch-all
Under normal circumstances, none of the boxed code will throw exceptions. Under exceptional circumstances, the caller should know about it. Usually the caller is the server. We don't want to catch unexpected exceptions because those should crash the server and generate a crashdump.
2018-07-17 12:18:46 +01:00
89bfc380e3 Merge branch 'release/3.0' into release/3.1 2018-07-17 12:14:33 +01:00
40030e9800 added some LevelProviderManager tests 2018-07-17 12:02:08 +01:00
ad1cf38c21 LevelProviderManager: tighten up checks on registering 2018-07-17 12:02:08 +01:00
5d769147ca LevelProviderManager: make addProvider() throw InvalidArgumentException instead of LevelException
LevelException is not useful because it's too generic.
2018-07-17 12:02:08 +01:00
6f00a30ad7 Merge branch 'release/3.0' into release/3.1 2018-07-17 10:12:52 +01:00
b4bf6901e3 Server: remove useless try/catch around Query event firing
this doesn't raise any exceptions, and if it causes Errors to be thrown, those are defects that should be fixed. A catch-all is a bad thing.
2018-07-17 10:10:28 +01:00
71c3c34976 ItemFactory: prepare for handling items with negative IDs 2018-07-16 13:24:12 +01:00
16c253d7a9 Item: allow negative IDs
this will be needed in the future for extended blocks support.
2018-07-16 12:46:16 +01:00
7efe767f1f Merge branch 'release/3.0' into release/3.1 2018-07-16 12:08:22 +01:00
2e18fe710c MemoryManager: Shut down idle workers during GC to reclaim memory
workers can be a major memory hog, especially if you have lots of them.
2018-07-14 18:07:37 +01:00
878dd3b842 Merge branch 'release/3.0' into release/3.1 2018-07-14 16:06:02 +01:00
478a131aa5 Flat: separate logic of parsing preset and generating base chunk
now always generates the base chunk on init, because the generator won't be created unless there is generation to be done.
2018-07-14 11:51:49 +01:00
53068caf3c Level: Only register generators when attempting to actually generate chunks
This saves a ton of memory on servers which don't generate any chunks during their runtime (which is most servers).
2018-07-14 11:34:55 +01:00
fe7ad7a5b3 Merge branch 'release/3.0' into release/3.1 2018-07-14 10:39:28 +01:00
24f749a933 Merge branch 'release/3.0' into release/3.1 2018-07-13 12:36:10 +01:00
af80aefd45 Remove async config save (#2298)
As discussed in #2297:

Honestly I don't see a fit purpose for async saving at all. It should either always be synchronous or always asynchronous, and at the user's own option. However, this isn't currently possible because Config doesn't enable you to get the serialized content without writing it to disk.

Consider the following code:
```php
		for($i = 0, $size = $this->getServer()->getAsyncPool()->getSize(); $i < $size; ++$i){
			$this->getServer()->getAsyncPool()->submitTask(new class extends AsyncTask{
				public function onRun(){
					sleep(5);
				}
			});
		}
		$config = $this->getConfig();
		$config->set("steve", "hi");
		$config->save(true);
		$config->set("steve", "bye");
		$config->save(false);
```
Output:
```yml
---
steve: hi
...
```
Expected output:
```yml
---
steve: bye
...
```

Additionally, if your configs are causing you performance issues when you're saving, it's a clear sign that
a) you're saving too much
b) you're abusing configs and should consider using a database.

Configs should be used for _simple_ data which does not change much. Configuration is such that the _user_ is expected to be able to modify it. As such, it should never be an issue to save synchronously.

In the future, something like ReactPHP may be introduced to allow proper async saving. When this happens, async saving would always be sequential but non blocking. Using threads for this makes no sense.
2018-07-12 19:31:00 +01:00
1d5c741f28 PluginBase: Automatically save default config if it doesn't exist (#2285)
I wasn't sure whether this would be considered a bug fix or a feature. Nonetheless, it's a behavioural change, so it belongs in 3.1 if anywhere.

Prior to this, plugins would be required to call saveDefaultConfig() before calling getConfig() or anything else. Calling getConfig() without saveDefaultConfig() first would generate an empty configuration file. Instead, it now saves the default config before loading it.
2018-07-12 19:25:48 +01:00
3a373b880d Listener: Add documentation on functionality (#2292)
The Listener interface is one of the most magical parts of PocketMine-MP, and before this pull request it didn't have a single bit of documentation.
2018-07-12 19:24:46 +01:00
1b7cd156aa Merge branch 'release/3.0' into release/3.1 2018-07-12 18:04:19 +01:00
7a164a8254 PluginManager: Allow @ignoreCancelled annotation on event handlers to not have parameters (#2294) 2018-07-12 17:12:14 +01:00
066c990301 Merge branch 'release/3.0' into release/3.1 2018-07-11 10:21:16 +01:00
287ff8d7bf Merge branch 'release/3.0' into release/3.1 2018-07-11 09:15:19 +01:00
1087212d75 Merge branch 'release/3.0' into release/3.1 2018-07-10 12:48:02 +01:00
0c350f2f57 Add quitMessage parameter to Player::kick() 2018-07-09 18:40:30 +01:00
bfcef2ab6b Add setReason() method to PlayerKickEvent 2018-07-09 18:36:19 +01:00
2994d0f3ae Merge branch 'release/3.0' into release/3.1 2018-07-09 10:06:28 +01:00
57cc0ebe75 Merge branch 'release/3.0' into release/3.1 2018-07-08 12:17:06 +01:00
7554d9a370 Empty merge 2018-07-07 19:22:30 +01:00
32574118ea Implemented Mending enchantment (#2257) 2018-07-06 13:28:33 +01:00
5a3135659b Merge branch 'release/3.0' into release/3.1 2018-07-06 13:12:13 +01:00
b90d7d1839 Merge branch 'release/3.0' into release/3.1 2018-07-05 17:43:11 +01:00
670b940837 PocketMine.php: clean up on platform dependency checks 2018-07-05 17:32:13 +01:00
6cad7be3ef Merge branch 'release/3.0' into release/3.1 2018-07-05 12:11:12 +01:00
28a72a93b4 Chunk: Use an SplFixedArray for heightmap
this goes on 3.1 because it changes the behaviour of chunk cloning, which might possibly break some plugins, and this isn't a bug fix.

This should see no change in behaviour other than a minor performance improvement and slight reduction in memory usage.
2018-07-05 11:58:20 +01:00
0f0d12bebc Merge branch 'release/3.0' into release/3.1 2018-07-02 16:58:15 +01:00
dfc11abf2d Level: fixed sendBlocks() documentation 2018-07-02 16:53:48 +01:00
17eef9f902 Level: stricten type checks on sendBlocks()
because people are morons
2018-07-02 16:53:00 +01:00
b04319a4ab Merge branch 'release/3.0' into release/3.1 2018-06-29 12:30:52 +01:00
0afbf6c547 Merge branch 'release/3.0' into release/3.1 2018-06-29 12:19:25 +01:00
ec2cca04a7 Merge branch 'release/3.0' into release/3.1 2018-06-24 17:34:37 +01:00
272b76d24c fix Punch mess 2018-06-24 13:43:52 +01:00
8c672cb7c8 Implemented Sharpness, Fire Aspect and Knockback enchantments 2018-06-24 12:13:54 +01:00
4d9368f205 Merge branch 'release/3.0' into release/3.1 2018-06-24 12:07:45 +01:00
97c267c70c Implemented Punch enchantment 2018-06-23 17:40:01 +01:00
85a3c0e7dc Implemented Flame enchantment 2018-06-23 17:11:20 +01:00
2f70a1eefb Implemented Thorns enchantment (#2258)
This implementation is rough and can probably be improved to make it extendable, but this works for now and can be improved later.
2018-06-23 13:36:58 +01:00
7ba6e92b6c Merge branch 'release/3.0' into release/3.1 2018-06-23 13:04:05 +01:00
47c862bc38 Projectile: check for blockhit change on nearby blockupdate 2018-06-23 12:57:13 +01:00
860c20109b TNT can now be ignited by burning arrows 2018-06-23 12:54:21 +01:00
1c0b49343c Implemented Infinity enchantment (#2259) 2018-06-23 11:44:35 +01:00
814a949580 Implemented Power enchantment 2018-06-23 11:39:39 +01:00
b393f5f17e Projectile: ensure that damage multiplier gets saved and restored 2018-06-23 10:41:04 +01:00
f1970492c1 Projectile: added API to modify projectile base damage multiplier
This adds two new methods:
- Projectile->getBaseDamage()
- Projectile->setBaseDamage()
2018-06-23 10:38:58 +01:00
4c9ca53b32 Merge branch 'release/3.0' into release/3.1 2018-06-22 17:47:33 +01:00
390db976e5 Arrow: allow controlling pickup mode (like PC)
This allows controlling how arrows are picked up:
- by anything
- by only creative players
- by nothing

This adds new API methods to Arrow:
- getPickupMode()
- setPickupMode()

This adds new public constants to Arrow:
- PICKUP_NONE
- PICKUP_ANY
- PICKUP_CREATIVE
2018-06-22 13:40:32 +01:00
98ac534820 bump version 2018-06-22 13:22:11 +01:00
85 changed files with 2314 additions and 775 deletions

View File

@ -243,6 +243,9 @@ class MemoryManager{
if($this->garbageCollectionAsync){
$pool = $this->server->getAsyncPool();
if(($w = $pool->shutdownUnusedWorkers()) > 0){
$this->server->getLogger()->debug("Shut down $w idle async pool workers");
}
foreach($pool->getRunningWorkers() as $i){
$pool->submitTaskToWorker(new GarbageCollectionTask(), $i);
}

View File

@ -32,11 +32,9 @@ use pocketmine\entity\Effect;
use pocketmine\entity\EffectInstance;
use pocketmine\entity\Entity;
use pocketmine\entity\Human;
use pocketmine\entity\Living;
use pocketmine\entity\object\ItemEntity;
use pocketmine\entity\projectile\Arrow;
use pocketmine\entity\Skin;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\inventory\InventoryCloseEvent;
@ -68,6 +66,8 @@ use pocketmine\event\player\PlayerToggleSneakEvent;
use pocketmine\event\player\PlayerToggleSprintEvent;
use pocketmine\event\player\PlayerTransferEvent;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\form\Form;
use pocketmine\form\FormValidationException;
use pocketmine\inventory\CraftingGrid;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerCursorInventory;
@ -77,6 +77,8 @@ use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Consumable;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\item\enchantment\MeleeWeaponEnchantment;
use pocketmine\item\Item;
use pocketmine\item\WritableBook;
use pocketmine\item\WrittenBook;
@ -116,6 +118,7 @@ use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
@ -146,6 +149,7 @@ use pocketmine\network\SourceInterface;
use pocketmine\permission\PermissibleBase;
use pocketmine\permission\PermissionAttachment;
use pocketmine\permission\PermissionAttachmentInfo;
use pocketmine\permission\PermissionManager;
use pocketmine\plugin\Plugin;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\tile\ItemFrame;
@ -193,9 +197,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
*/
protected $sessionAdapter;
/** @var int */
protected $protocol = -1;
/** @var string */
protected $ip;
/** @var int */
@ -322,6 +323,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var int[] ID => ticks map */
protected $usedItemsCooldown = [];
/** @var int */
protected $formIdCounter = 0;
/** @var Form[] */
protected $forms = [];
/**
* @return TranslationContainer|string
*/
@ -637,8 +643,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function recalculatePermissions(){
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
$permManager = PermissionManager::getInstance();
$permManager->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
$permManager->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
if($this->perm === null){
return;
@ -647,10 +654,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->perm->recalculatePermissions();
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
}
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
}
if($this->spawned){
@ -1018,10 +1025,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->sendPlayStatus(PlayStatusPacket::PLAYER_SPAWN);
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
}
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
}
$this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this,
@ -1839,8 +1846,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return false;
}
$this->protocol = $packet->protocol;
if($packet->protocol !== ProtocolInfo::CURRENT_PROTOCOL){
if($packet->protocol < ProtocolInfo::CURRENT_PROTOCOL){
$this->sendPlayStatus(PlayStatusPacket::LOGIN_FAILED_CLIENT, true);
@ -1923,7 +1928,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
public function sendPlayStatus(int $status, bool $immediate = false){
$pk = new PlayStatusPacket();
$pk->status = $status;
$pk->protocol = $this->protocol;
$this->sendDataPacket($pk, false, $immediate);
}
@ -2031,7 +2035,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
case ResourcePackClientResponsePacket::STATUS_SEND_PACKS:
$manager = $this->server->getResourcePackManager();
foreach($packet->packIds as $uuid){
$pack = $manager->getPackById($uuid);
$pack = $manager->getPackById(substr($uuid, 0, strpos($uuid, "_"))); //dirty hack for mojang's dirty hack for versions
if(!($pack instanceof ResourcePack)){
//Client requested a resource pack but we don't have it available on the server
$this->close("", "disconnectionScreen.resourcePack", true);
@ -2499,6 +2503,19 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $heldItem->getAttackPoints());
$meleeEnchantmentDamage = 0;
/** @var EnchantmentInstance[] $meleeEnchantments */
$meleeEnchantments = [];
foreach($heldItem->getEnchantments() as $enchantment){
$type = $enchantment->getType();
if($type instanceof MeleeWeaponEnchantment and $type->isApplicableTo($target)){
$meleeEnchantmentDamage += $type->getDamageBonus($enchantment->getLevel());
$meleeEnchantments[] = $enchantment;
}
}
$ev->setModifier($meleeEnchantmentDamage, EntityDamageEvent::MODIFIER_WEAPON_ENCHANTMENTS);
if($cancelled){
$ev->setCancelled();
}
@ -2526,6 +2543,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
foreach($meleeEnchantments as $enchantment){
$type = $enchantment->getType();
assert($type instanceof MeleeWeaponEnchantment);
$type->onPostAttack($this, $target, $enchantment->getLevel());
}
if($this->isAlive()){
//reactive damage like thorns might cause us to be killed by attacking another mob, which
//would mean we'd already have dropped the inventory by the time we reached here
@ -3147,12 +3170,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
*
* @param string $reason
* @param bool $isAdmin
* @param TextContainer|string $quitMessage
*
* @return bool
*/
public function kick(string $reason = "", bool $isAdmin = true) : bool{
$this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, $this->getLeaveMessage()));
public function kick(string $reason = "", bool $isAdmin = true, $quitMessage = null) : bool{
$this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, $quitMessage ?? $this->getLeaveMessage()));
if(!$ev->isCancelled()){
$reason = $ev->getReason();
$message = $reason;
if($isAdmin){
if(!$this->isBanned()){
@ -3330,6 +3355,45 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->dataPacket($pk);
}
/**
* Sends a Form to the player, or queue to send it if a form is already open.
*
* @param Form $form
*/
public function sendForm(Form $form) : void{
$id = $this->formIdCounter++;
$pk = new ModalFormRequestPacket();
$pk->formId = $id;
$pk->formData = json_encode($form);
if($this->dataPacket($pk)){
$this->forms[$id] = $form;
}
}
/**
* @param int $formId
* @param mixed $responseData
*
* @return bool
*/
public function onFormSubmit(int $formId, $responseData) : bool{
if(!isset($this->forms[$formId])){
$this->server->getLogger()->debug("Got unexpected response for form $formId");
return false;
}
try{
$this->forms[$formId]->handleResponse($this, $responseData);
}catch(FormValidationException $e){
$this->server->getLogger()->critical("Failed to validate form " . get_class($this->forms[$formId]) . ": " . $e->getMessage());
$this->server->getLogger()->logException($e);
}finally{
unset($this->forms[$formId]);
}
return true;
}
/**
* Note for plugin developers: use kick() with the isAdmin
* flag set to kick without the "Kicked by admin" part instead of this method.
@ -3350,8 +3414,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->interface->close($this, $notify ? $reason : "");
$this->sessionAdapter = null;
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
$this->stopSleep();
@ -3500,123 +3564,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
protected function onDeath() : void{
$message = "death.attack.generic";
$params = [
$this->getDisplayName()
];
$cause = $this->getLastDamageCause();
switch($cause === null ? EntityDamageEvent::CAUSE_CUSTOM : $cause->getCause()){
case EntityDamageEvent::CAUSE_ENTITY_ATTACK:
if($cause instanceof EntityDamageByEntityEvent){
$e = $cause->getDamager();
if($e instanceof Player){
$message = "death.attack.player";
$params[] = $e->getDisplayName();
break;
}elseif($e instanceof Living){
$message = "death.attack.mob";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}else{
$params[] = "Unknown";
}
}
break;
case EntityDamageEvent::CAUSE_PROJECTILE:
if($cause instanceof EntityDamageByEntityEvent){
$e = $cause->getDamager();
if($e instanceof Player){
$message = "death.attack.arrow";
$params[] = $e->getDisplayName();
}elseif($e instanceof Living){
$message = "death.attack.arrow";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}else{
$params[] = "Unknown";
}
}
break;
case EntityDamageEvent::CAUSE_SUICIDE:
$message = "death.attack.generic";
break;
case EntityDamageEvent::CAUSE_VOID:
$message = "death.attack.outOfWorld";
break;
case EntityDamageEvent::CAUSE_FALL:
if($cause instanceof EntityDamageEvent){
if($cause->getFinalDamage() > 2){
$message = "death.fell.accident.generic";
break;
}
}
$message = "death.attack.fall";
break;
case EntityDamageEvent::CAUSE_SUFFOCATION:
$message = "death.attack.inWall";
break;
case EntityDamageEvent::CAUSE_LAVA:
$message = "death.attack.lava";
break;
case EntityDamageEvent::CAUSE_FIRE:
$message = "death.attack.onFire";
break;
case EntityDamageEvent::CAUSE_FIRE_TICK:
$message = "death.attack.inFire";
break;
case EntityDamageEvent::CAUSE_DROWNING:
$message = "death.attack.drown";
break;
case EntityDamageEvent::CAUSE_CONTACT:
if($cause instanceof EntityDamageByBlockEvent){
if($cause->getDamager()->getId() === Block::CACTUS){
$message = "death.attack.cactus";
}
}
break;
case EntityDamageEvent::CAUSE_BLOCK_EXPLOSION:
case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION:
if($cause instanceof EntityDamageByEntityEvent){
$e = $cause->getDamager();
if($e instanceof Player){
$message = "death.attack.explosion.player";
$params[] = $e->getDisplayName();
}elseif($e instanceof Living){
$message = "death.attack.explosion.player";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}
}else{
$message = "death.attack.explosion";
}
break;
case EntityDamageEvent::CAUSE_MAGIC:
$message = "death.attack.magic";
break;
case EntityDamageEvent::CAUSE_CUSTOM:
break;
default:
break;
}
//Crafting grid must always be evacuated even if keep-inventory is true. This dumps the contents into the
//main inventory and drops the rest on the ground.
$this->doCloseInventory();
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops(), new TranslationContainer($message, $params)));
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops()));
if(!$ev->getKeepInventory()){
foreach($ev->getDrops() as $item){

View File

@ -37,7 +37,7 @@ namespace pocketmine {
use pocketmine\wizard\SetupWizard;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.1.5";
const BASE_VERSION = "3.2.1";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;
@ -53,83 +53,89 @@ namespace pocketmine {
* Enjoy it as much as I did writing it. I don't want to do it again.
*/
if(version_compare(MIN_PHP_VERSION, PHP_VERSION) > 0){
critical_error(\pocketmine\NAME . " requires PHP >= " . MIN_PHP_VERSION . ", but you have PHP " . PHP_VERSION . ".");
critical_error("Please refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
exit(1);
}
if(PHP_INT_SIZE < 8){
critical_error("Running " . \pocketmine\NAME . " with 32-bit systems/PHP is no longer supported.");
critical_error("Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.");
critical_error("Please refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
exit(1);
}
/* Dependencies check */
$errors = 0;
if(php_sapi_name() !== "cli"){
critical_error("You must run " . \pocketmine\NAME . " using the CLI.");
++$errors;
}
$extensions = [
"bcmath" => "BC Math",
"curl" => "cURL",
"ctype" => "ctype",
"date" => "Date",
"hash" => "Hash",
"json" => "JSON",
"mbstring" => "Multibyte String",
"openssl" => "OpenSSL",
"pcre" => "PCRE",
"phar" => "Phar",
"pthreads" => "pthreads",
"reflection" => "Reflection",
"sockets" => "Sockets",
"spl" => "SPL",
"yaml" => "YAML",
"zip" => "Zip",
"zlib" => "Zlib"
];
foreach($extensions as $ext => $name){
if(!extension_loaded($ext)){
critical_error("Unable to find the $name ($ext) extension.");
++$errors;
/**
* @return string[]
*/
function check_platform_dependencies(){
if(version_compare(MIN_PHP_VERSION, PHP_VERSION) > 0){
//If PHP version isn't high enough, anything below might break, so don't bother checking it.
return [
\pocketmine\NAME . " requires PHP >= " . MIN_PHP_VERSION . ", but you have PHP " . PHP_VERSION . "."
];
}
}
if(extension_loaded("pthreads")){
$pthreads_version = phpversion("pthreads");
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
$messages = [];
if(PHP_INT_SIZE < 8){
$messages[] = "Running " . \pocketmine\NAME . " with 32-bit systems/PHP is no longer supported. Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.";
}
if(version_compare($pthreads_version, "3.1.7dev") < 0){
critical_error("pthreads >= 3.1.7dev is required, while you have $pthreads_version.");
++$errors;
if(php_sapi_name() !== "cli"){
$messages[] = "You must run " . \pocketmine\NAME . " using the CLI.";
}
}
if(extension_loaded("leveldb")){
$leveldb_version = phpversion("leveldb");
if(version_compare($leveldb_version, "0.2.1") < 0){
critical_error("php-leveldb >= 0.2.1 is required, while you have $leveldb_version");
++$errors;
$extensions = [
"bcmath" => "BC Math",
"curl" => "cURL",
"ctype" => "ctype",
"date" => "Date",
"hash" => "Hash",
"json" => "JSON",
"mbstring" => "Multibyte String",
"openssl" => "OpenSSL",
"pcre" => "PCRE",
"phar" => "Phar",
"pthreads" => "pthreads",
"reflection" => "Reflection",
"sockets" => "Sockets",
"spl" => "SPL",
"yaml" => "YAML",
"zip" => "Zip",
"zlib" => "Zlib"
];
foreach($extensions as $ext => $name){
if(!extension_loaded($ext)){
$messages[] = "Unable to find the $name ($ext) extension.";
}
}
if(extension_loaded("pthreads")){
$pthreads_version = phpversion("pthreads");
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
}
if(version_compare($pthreads_version, "3.1.7dev") < 0){
$messages[] = "pthreads >= 3.1.7dev is required, while you have $pthreads_version.";
}
}
if(extension_loaded("leveldb")){
$leveldb_version = phpversion("leveldb");
if(version_compare($leveldb_version, "0.2.1") < 0){
$messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version.";
}
}
if(extension_loaded("pocketmine")){
$messages[] = "The native PocketMine extension is no longer supported.";
}
return $messages;
}
if(extension_loaded("pocketmine")){
critical_error("The native PocketMine extension is no longer supported.");
++$errors;
}
if($errors > 0){
if(!empty($messages = check_platform_dependencies())){
echo PHP_EOL;
$binary = version_compare(PHP_VERSION, "5.4") >= 0 ? PHP_BINARY : "unknown";
critical_error("Selected PHP binary ($binary) does not satisfy some requirements.");
foreach($messages as $m){
echo " - $m" . PHP_EOL;
}
critical_error("Please recompile PHP with the needed configuration, or refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
echo PHP_EOL;
exit(1);
}
unset($messages);
error_reporting(-1);

View File

@ -39,6 +39,7 @@ use pocketmine\event\HandlerList;
use pocketmine\event\level\LevelInitEvent;
use pocketmine\event\level\LevelLoadEvent;
use pocketmine\event\player\PlayerDataSaveEvent;
use pocketmine\event\server\CommandEvent;
use pocketmine\event\server\QueryRegenerateEvent;
use pocketmine\event\server\ServerCommandEvent;
use pocketmine\inventory\CraftingManager;
@ -82,6 +83,7 @@ use pocketmine\network\rcon\RCON;
use pocketmine\network\upnp\UPnP;
use pocketmine\permission\BanList;
use pocketmine\permission\DefaultPermissions;
use pocketmine\permission\PermissionManager;
use pocketmine\plugin\PharPluginLoader;
use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginLoadOrder;
@ -99,6 +101,7 @@ use pocketmine\timings\TimingsHandler;
use pocketmine\updater\AutoUpdater;
use pocketmine\utils\Binary;
use pocketmine\utils\Config;
use pocketmine\utils\Internet;
use pocketmine\utils\MainLogger;
use pocketmine\utils\Terminal;
use pocketmine\utils\TextFormat;
@ -1025,15 +1028,8 @@ class Server{
return false;
}
try{
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
}catch(\Throwable $e){
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, $e->getMessage()]));
$this->logger->logException($e);
return false;
}
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
@ -1076,21 +1072,15 @@ class Server{
}
}
try{
$path = $this->getDataPath() . "worlds/" . $name . "/";
/** @var LevelProvider $providerClass */
$providerClass::generate($path, $name, $seed, $generator, $options);
$path = $this->getDataPath() . "worlds/" . $name . "/";
/** @var LevelProvider $providerClass */
$providerClass::generate($path, $name, $seed, $generator, $options);
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
$level->setTickRate($this->baseTickRate);
}catch(\Throwable $e){
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.generationError", [$name, $e->getMessage()]));
$this->logger->logException($e);
return false;
}
$level->setTickRate($this->baseTickRate);
$this->getPluginManager()->callEvent(new LevelInitEvent($level));
@ -1148,17 +1138,12 @@ class Server{
* Useful for tracking entities across multiple worlds without needing strong references.
*
* @param int $entityId
* @param Level|null $expectedLevel Level to look in first for the target
* @param Level|null $expectedLevel @deprecated Level to look in first for the target
*
* @return Entity|null
*/
public function findEntity(int $entityId, Level $expectedLevel = null){
$levels = $this->levels;
if($expectedLevel !== null){
array_unshift($levels, $expectedLevel);
}
foreach($levels as $level){
foreach($this->levels as $level){
assert(!$level->isClosed());
if(($entity = $level->getEntity($entityId)) instanceof Entity){
return $entity;
@ -1305,7 +1290,7 @@ class Server{
if(($player = $this->getPlayerExact($name)) !== null){
$player->recalculatePermissions();
}
$this->operators->save(true);
$this->operators->save();
}
/**
@ -1325,7 +1310,7 @@ class Server{
*/
public function addWhitelist(string $name){
$this->whitelist->set(strtolower($name), true);
$this->whitelist->save(true);
$this->whitelist->save();
}
/**
@ -1638,7 +1623,7 @@ class Server{
$this->resourceManager = new ResourcePackManager($this->getDataPath() . "resource_packs" . DIRECTORY_SEPARATOR, $this->logger);
$this->pluginManager = new PluginManager($this, $this->commandMap, ((bool) $this->getProperty("plugins.legacy-data-dir", true)) ? null : $this->getDataPath() . "plugin_data" . DIRECTORY_SEPARATOR);
$this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
$this->profilingTickRate = (float) $this->getProperty("settings.profile-report-trigger", 20);
$this->pluginManager->registerInterface(new PharPluginLoader($this->autoloader));
$this->pluginManager->registerInterface(new ScriptPluginLoader());
@ -1696,7 +1681,7 @@ class Server{
}
if($this->properties->hasChanged()){
$this->properties->save(true);
$this->properties->save();
}
if(!($this->getDefaultLevel() instanceof Level)){
@ -1747,7 +1732,7 @@ class Server{
if(!is_array($recipients)){
/** @var Player[] $recipients */
$recipients = [];
foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
}
@ -1773,7 +1758,7 @@ class Server{
/** @var Player[] $recipients */
$recipients = [];
foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
}
@ -1803,7 +1788,7 @@ class Server{
/** @var Player[] $recipients */
$recipients = [];
foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
}
@ -1828,7 +1813,7 @@ class Server{
/** @var CommandSender[] $recipients */
$recipients = [];
foreach(explode(";", $permissions) as $permission){
foreach($this->pluginManager->getPermissionSubscriptions($permission) as $permissible){
foreach(PermissionManager::getInstance()->getPermissionSubscriptions($permission) as $permissible){
if($permissible instanceof CommandSender and $permissible->hasPermission($permission)){
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
}
@ -1953,10 +1938,20 @@ class Server{
*
* @param CommandSender $sender
* @param string $commandLine
* @param bool $internal
*
* @return bool
*/
public function dispatchCommand(CommandSender $sender, string $commandLine) : bool{
public function dispatchCommand(CommandSender $sender, string $commandLine, bool $internal = false) : bool{
if(!$internal){
$this->pluginManager->callEvent($ev = new CommandEvent($sender, $commandLine));
if($ev->isCancelled()){
return false;
}
$commandLine = $ev->getCommand();
}
if($this->commandMap->dispatch($sender, $commandLine)){
return true;
}
@ -1976,6 +1971,7 @@ class Server{
$this->pluginManager->disablePlugins();
$this->pluginManager->clearPlugins();
PermissionManager::getInstance()->clearPermissions();
$this->commandMap->clearCommands();
$this->logger->info("Reloading properties...");
@ -2218,7 +2214,7 @@ class Server{
if($report){
$url = ($this->getProperty("auto-report.use-https", true) ? "https" : "http") . "://" . $this->getProperty("auto-report.host", "crash.pmmp.io") . "/submit/api";
$reply = Utils::postURL($url, [
$reply = Internet::postURL($url, [
"report" => "yes",
"name" => $this->getName() . " " . $this->getPocketMineVersion(),
"email" => "crash@pocketmine.net",
@ -2533,13 +2529,9 @@ class Server{
}
if(($this->tickCounter & 0b111111111) === 0){
try{
$this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5));
if($this->queryHandler !== null){
$this->queryHandler->regenerateInfo();
}
}catch(\Throwable $e){
$this->logger->logException($e);
$this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5));
if($this->queryHandler !== null){
$this->queryHandler->regenerateInfo();
}
}

View File

@ -421,8 +421,8 @@ class BlockFactory{
public static function registerStaticRuntimeIdMappings() : void{
/** @var mixed[] $runtimeIdMap */
$runtimeIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
foreach($runtimeIdMap as $obj){
self::registerMapping($obj["runtimeID"], $obj["id"], $obj["data"]);
foreach($runtimeIdMap as $k => $obj){
self::registerMapping($k, $obj["id"], $obj["data"]);
}
}

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Arrow;
use pocketmine\item\FlintSteel;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
@ -56,6 +57,16 @@ class TNT extends Solid{
return false;
}
public function hasEntityCollision() : bool{
return true;
}
public function onEntityCollide(Entity $entity) : void{
if($entity instanceof Arrow and $entity->isOnFire()){
$this->ignite();
}
}
public function ignite(int $fuse = 80){
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), true);

View File

@ -28,6 +28,7 @@ namespace pocketmine\command;
use pocketmine\lang\TextContainer;
use pocketmine\lang\TranslationContainer;
use pocketmine\permission\PermissionManager;
use pocketmine\Server;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\TextFormat;
@ -293,7 +294,7 @@ abstract class Command{
$m = clone $message;
$result = "[" . $source->getName() . ": " . ($source->getServer()->getLanguage()->get($m->getText()) !== $m->getText() ? "%" : "") . $m->getText() . "]";
$users = $source->getServer()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
$users = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
$colored = TextFormat::GRAY . TextFormat::ITALIC . $result;
$m->setText($result);
@ -301,7 +302,7 @@ abstract class Command{
$m->setText($colored);
$colored = clone $m;
}else{
$users = $source->getServer()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
$users = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
$result = new TranslationContainer("chat.type.admin", [$source->getName(), $message]);
$colored = new TranslationContainer(TextFormat::GRAY . TextFormat::ITALIC . "%chat.type.admin", [$source->getName(), $message]);
}

View File

@ -59,7 +59,7 @@ class FormattedCommandAlias extends Command{
}
foreach($commands as $command){
$result |= Server::getInstance()->dispatchCommand($sender, $command);
$result |= Server::getInstance()->dispatchCommand($sender, $command, true);
}
return (bool) $result;

View File

@ -30,6 +30,7 @@ use pocketmine\Player;
use pocketmine\scheduler\BulkCurlTask;
use pocketmine\Server;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\InternetException;
class TimingsCommand extends VanillaCommand{
@ -131,7 +132,7 @@ class TimingsCommand extends VanillaCommand{
return;
}
$result = $this->getResult()[0];
if($result instanceof \RuntimeException){
if($result instanceof InternetException){
$server->getLogger()->logException($result);
return;
}

View File

@ -56,6 +56,7 @@ class Effect{
public const SATURATION = 23;
public const LEVITATION = 24; //TODO
public const FATAL_POISON = 25;
public const CONDUIT_POWER = 26;
/** @var Effect[] */
protected static $effects = [];
@ -86,6 +87,7 @@ class Effect{
self::registerEffect(new Effect(Effect::SATURATION, "%potion.saturation", new Color(0xf8, 0x24, 0x23), false, 1));
self::registerEffect(new Effect(Effect::LEVITATION, "%potion.levitation", new Color(0xce, 0xff, 0xff)));
self::registerEffect(new Effect(Effect::FATAL_POISON, "%potion.poison", new Color(0x4e, 0x93, 0x31), true));
self::registerEffect(new Effect(Effect::CONDUIT_POWER, "%potion.conduitPower", new Color(0x1d, 0xc2, 0xd1)));
}
/**

View File

@ -610,6 +610,20 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->propertyManager->setByte(self::DATA_ALWAYS_SHOW_NAMETAG, $value ? 1 : 0);
}
/**
* @return string|null
*/
public function getScoreTag() : ?string{
return $this->propertyManager->getString(self::DATA_SCORE_TAG);
}
/**
* @param string $score
*/
public function setScoreTag(string $score) : void{
$this->propertyManager->setString(self::DATA_SCORE_TAG, $score);
}
/**
* @return float
*/
@ -735,7 +749,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public function getOwningEntity() : ?Entity{
$eid = $this->getOwningEntityId();
if($eid !== null){
return $this->server->findEntity($eid, $this->level);
return $this->server->findEntity($eid);
}
return null;
@ -775,7 +789,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public function getTargetEntity() : ?Entity{
$eid = $this->getTargetEntityId();
if($eid !== null){
return $this->server->findEntity($eid, $this->level);
return $this->server->findEntity($eid);
}
return null;

View File

@ -34,6 +34,7 @@ use pocketmine\inventory\EntityInventoryEventProcessor;
use pocketmine\inventory\InventoryHolder;
use pocketmine\inventory\PlayerInventory;
use pocketmine\item\Consumable;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\FoodSource;
use pocketmine\item\Item;
@ -518,6 +519,42 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
return $this->xpCooldown === 0;
}
public function onPickupXp(int $xpValue) : void{
static $mainHandIndex = -1;
//TODO: replace this with a more generic equipment getting/setting interface
/** @var Durable[] $equipment */
$equipment = [];
if(($item = $this->inventory->getItemInHand()) instanceof Durable and $item->hasEnchantment(Enchantment::MENDING)){
$equipment[$mainHandIndex] = $item;
}
//TODO: check offhand
foreach($this->armorInventory->getContents() as $k => $item){
if($item instanceof Durable and $item->hasEnchantment(Enchantment::MENDING)){
$equipment[$k] = $item;
}
}
if(!empty($equipment)){
$repairItem = $equipment[$k = array_rand($equipment)];
if($repairItem->getDamage() > 0){
$repairAmount = min($repairItem->getDamage(), $xpValue * 2);
$repairItem->setDamage($repairItem->getDamage() - $repairAmount);
$xpValue -= (int) ceil($repairAmount / 2);
if($k === $mainHandIndex){
$this->inventory->setItemInHand($repairItem);
}else{
$this->armorInventory->setItem($k, $repairItem);
}
}
}
$this->addXp($xpValue); //this will still get fired even if the value is 0 due to mending, to play sounds
$this->resetXpCooldown();
}
/**
* Sets the duration in ticks until the human can pick up another XP orb.
*

View File

@ -34,6 +34,7 @@ use pocketmine\inventory\ArmorInventory;
use pocketmine\inventory\ArmorInventoryEventProcessor;
use pocketmine\item\Armor;
use pocketmine\item\Consumable;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
@ -476,6 +477,26 @@ abstract class Living extends Entity implements Damageable{
protected function applyPostDamageEffects(EntityDamageEvent $source) : void{
$this->setAbsorption(max(0, $this->getAbsorption() + $source->getModifier(EntityDamageEvent::MODIFIER_ABSORPTION)));
$this->damageArmor($source->getBaseDamage());
if($source instanceof EntityDamageByEntityEvent){
$damage = 0;
foreach($this->armorInventory->getContents() as $k => $item){
if($item instanceof Armor and ($thornsLevel = $item->getEnchantmentLevel(Enchantment::THORNS)) > 0){
if(mt_rand(0, 99) < $thornsLevel * 15){
$this->damageItem($item, 3);
$damage += ($thornsLevel > 10 ? $thornsLevel - 10 : 1 + mt_rand(0, 3));
}else{
$this->damageItem($item, 1); //thorns causes an extra +1 durability loss even if it didn't activate
}
$this->armorInventory->setItem($k, $item);
}
}
if($damage > 0){
$source->getDamager()->attack(new EntityDamageByEntityEvent($this, $source->getDamager(), EntityDamageEvent::CAUSE_MAGIC, $damage));
}
}
}
/**
@ -490,16 +511,20 @@ abstract class Living extends Entity implements Damageable{
$armor = $this->armorInventory->getContents(true);
foreach($armor as $item){
if($item instanceof Armor){
$item->applyDamage($durabilityRemoved);
if($item->isBroken()){
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_BREAK);
}
$this->damageItem($item, $durabilityRemoved);
}
}
$this->armorInventory->setContents($armor);
}
private function damageItem(Durable $item, int $durabilityRemoved) : void{
$item->applyDamage($durabilityRemoved);
if($item->isBroken()){
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_BREAK);
}
}
public function attack(EntityDamageEvent $source) : void{
if($this->attackTime > 0 or $this->noDamageTicks > 0){
$lastCause = $this->getLastDamageCause();
@ -535,7 +560,7 @@ abstract class Living extends Entity implements Damageable{
return;
}
$this->attackTime = 10; //0.5 seconds cooldown
$this->attackTime = $source->getAttackCooldown();
if($source instanceof EntityDamageByEntityEvent){
$e = $source->getDamager();
@ -640,13 +665,8 @@ abstract class Living extends Entity implements Damageable{
$this->attack($ev);
}
if(!$this->canBreathe()){
$this->setBreathing(false);
$this->doAirSupplyTick($tickDiff);
if($this->doAirSupplyTick($tickDiff)){
$hasUpdate = true;
}elseif(!$this->isBreathing()){
$this->setBreathing(true);
$this->setAirSupplyTicks($this->getMaxAirSupplyTicks());
}
}
@ -675,22 +695,42 @@ abstract class Living extends Entity implements Damageable{
}
/**
* Ticks the entity's air supply when it cannot breathe.
* Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water.
*
* @param int $tickDiff
*
* @return bool
*/
protected function doAirSupplyTick(int $tickDiff) : void{
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(Enchantment::RESPIRATION)) <= 0 or
lcg_value() <= (1 / ($respirationLevel + 1))
){
$ticks = $this->getAirSupplyTicks() - $tickDiff;
protected function doAirSupplyTick(int $tickDiff) : bool{
$ticks = $this->getAirSupplyTicks();
$oldTicks = $ticks;
if(!$this->canBreathe()){
$this->setBreathing(false);
if($ticks <= -20){
$this->setAirSupplyTicks(0);
$this->onAirExpired();
}else{
$this->setAirSupplyTicks($ticks);
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(Enchantment::RESPIRATION)) <= 0 or
lcg_value() <= (1 / ($respirationLevel + 1))
){
$ticks -= $tickDiff;
if($ticks <= -20){
$ticks = 0;
$this->onAirExpired();
}
}
}elseif(!$this->isBreathing()){
if($ticks < ($max = $this->getMaxAirSupplyTicks())){
$ticks += $tickDiff * 5;
}
if($ticks >= $max){
$ticks = $max;
$this->setBreathing(true);
}
}
if($ticks !== $oldTicks){
$this->setAirSupplyTicks($ticks);
}
return $ticks !== $oldTicks;
}
/**
@ -698,7 +738,7 @@ abstract class Living extends Entity implements Damageable{
* @return bool
*/
public function canBreathe() : bool{
return $this->hasEffect(Effect::WATER_BREATHING) or !$this->isUnderwater();
return $this->hasEffect(Effect::WATER_BREATHING) or $this->hasEffect(Effect::CONDUIT_POWER) or !$this->isUnderwater();
}
/**

View File

@ -144,7 +144,7 @@ class ExperienceOrb extends Entity{
return null;
}
$entity = $this->server->findEntity($this->targetPlayerRuntimeId, $this->level);
$entity = $this->server->findEntity($this->targetPlayerRuntimeId);
if($entity instanceof Human){
return $entity;
}
@ -200,10 +200,7 @@ class ExperienceOrb extends Entity{
if($currentTarget->canPickupXp() and $this->boundingBox->intersectsWith($currentTarget->getBoundingBox())){
$this->flagForDespawn();
$currentTarget->addXp($this->getXpValue());
$currentTarget->resetXpCooldown();
//TODO: check Mending enchantment
$currentTarget->onPickupXp($this->getXpValue());
}
}

View File

@ -40,19 +40,44 @@ use pocketmine\Player;
class Arrow extends Projectile{
public const NETWORK_ID = self::ARROW;
public const PICKUP_NONE = 0;
public const PICKUP_ANY = 1;
public const PICKUP_CREATIVE = 2;
private const TAG_PICKUP = "pickup"; //TAG_Byte
public $width = 0.25;
public $height = 0.25;
protected $gravity = 0.05;
protected $drag = 0.01;
protected $damage = 2;
/** @var float */
protected $damage = 2.0;
/** @var int */
protected $pickupMode = self::PICKUP_ANY;
/** @var float */
protected $punchKnockback = 0.0;
public function __construct(Level $level, CompoundTag $nbt, ?Entity $shootingEntity = null, bool $critical = false){
parent::__construct($level, $nbt, $shootingEntity);
$this->setCritical($critical);
}
protected function initEntity() : void{
parent::initEntity();
$this->pickupMode = $this->namedtag->getByte(self::TAG_PICKUP, self::PICKUP_ANY, true);
}
public function saveNBT() : void{
parent::saveNBT();
$this->namedtag->setByte(self::TAG_PICKUP, $this->pickupMode, true);
}
public function isCritical() : bool{
return $this->getGenericFlag(self::DATA_FLAG_CRITICAL);
}
@ -70,6 +95,20 @@ class Arrow extends Projectile{
}
}
/**
* @return float
*/
public function getPunchKnockback() : float{
return $this->punchKnockback;
}
/**
* @param float $punchKnockback
*/
public function setPunchKnockback(float $punchKnockback) : void{
$this->punchKnockback = $punchKnockback;
}
public function entityBaseTick(int $tickDiff = 1) : bool{
if($this->closed){
return false;
@ -95,6 +134,31 @@ class Arrow extends Projectile{
$this->broadcastEntityEvent(EntityEventPacket::ARROW_SHAKE, 7); //7 ticks
}
protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{
parent::onHitEntity($entityHit, $hitResult);
if($this->punchKnockback > 0){
$horizontalSpeed = sqrt($this->motion->x ** 2 + $this->motion->z ** 2);
if($horizontalSpeed > 0){
$multiplier = $this->punchKnockback * 0.6 / $horizontalSpeed;
$entityHit->setMotion($entityHit->getMotion()->add($this->motion->x * $multiplier, 0.1, $this->motion->z * $multiplier));
}
}
}
/**
* @return int
*/
public function getPickupMode() : int{
return $this->pickupMode;
}
/**
* @param int $pickupMode
*/
public function setPickupMode(int $pickupMode) : void{
$this->pickupMode = $pickupMode;
}
public function onCollideWithPlayer(Player $player) : void{
if($this->blockHit === null){
return;
@ -107,7 +171,12 @@ class Arrow extends Projectile{
return;
}
$this->server->getPluginManager()->callEvent($ev = new InventoryPickupArrowEvent($playerInventory, $this));
$ev = new InventoryPickupArrowEvent($playerInventory, $this);
if($this->pickupMode === self::PICKUP_NONE or ($this->pickupMode === self::PICKUP_CREATIVE and !$player->isCreative())){
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
if($ev->isCancelled()){
return;
}

View File

@ -46,7 +46,8 @@ abstract class Projectile extends Entity{
public const DATA_SHOOTER_ID = 17;
protected $damage = 0;
/** @var float */
protected $damage = 0.0;
/** @var Vector3|null */
protected $blockHit;
@ -74,6 +75,7 @@ abstract class Projectile extends Entity{
$this->setMaxHealth(1);
$this->setHealth(1);
$this->age = $this->namedtag->getShort("Age", $this->age);
$this->damage = $this->namedtag->getDouble("damage", $this->damage);
do{
$blockHit = null;
@ -112,6 +114,25 @@ abstract class Projectile extends Entity{
return false;
}
/**
* Returns the base damage applied on collision. This is multiplied by the projectile's speed to give a result
* damage.
*
* @return float
*/
public function getBaseDamage() : float{
return $this->damage;
}
/**
* Sets the base amount of damage applied by the projectile.
*
* @param float $damage
*/
public function setBaseDamage(float $damage) : void{
$this->damage = $damage;
}
/**
* Returns the amount of damage this projectile will deal to the entity it hits.
* @return int
@ -124,6 +145,7 @@ abstract class Projectile extends Entity{
parent::saveNBT();
$this->namedtag->setShort("Age", $this->age);
$this->namedtag->setDouble("damage", $this->damage);
if($this->blockHit !== null){
$this->namedtag->setInt("tileX", $this->blockHit->x);
@ -140,17 +162,19 @@ abstract class Projectile extends Entity{
return true;
}
public function hasMovementUpdate() : bool{
$parent = parent::hasMovementUpdate();
if($parent and $this->blockHit !== null){
public function onNearbyBlockChange() : void{
if($this->blockHit !== null){
$blockIn = $this->level->getBlockAt($this->blockHit->x, $this->blockHit->y, $this->blockHit->z);
if($blockIn->getId() === $this->blockHitId and $blockIn->getDamage() === $this->blockHitData){
return false;
if($blockIn->getId() !== $this->blockHitId or $blockIn->getDamage() !== $this->blockHitData){
$this->blockHit = $this->blockHitId = $this->blockHitData = null;
}
}
return $parent;
parent::onNearbyBlockChange();
}
public function hasMovementUpdate() : bool{
return $this->blockHit === null and parent::hasMovementUpdate();
}
public function move(float $dx, float $dy, float $dz) : void{

View File

@ -23,6 +23,37 @@ declare(strict_types=1);
namespace pocketmine\event;
use pocketmine\plugin\PluginManager;
/**
* Classes implementing this interface can be registered to receive called Events.
* @see PluginManager::registerEvents()
*
* A function in a Listener class must meet the following criteria to be registered as an event handler:
*
* - MUST be public
* - MUST NOT be static
* - MUST accept EXACTLY ONE class parameter which:
* - MUST be a VALID class extending Event
* - MUST NOT be abstract, UNLESS it has an `@allowHandle` annotation
*
* Event handlers do not have to have any particular name - they are detected using reflection.
* They SHOULD NOT return any values (but this is not currently enforced).
*
* Functions which meet the criteria can have the following annotations in their doc comments:
*
* - `@notHandler`: Marks a function as NOT being an event handler. Only needed if the function meets the above criteria.
* - `@softDepend [PluginName]`: Handler WILL NOT be registered if its event doesn't exist. Useful for soft-depending
* on plugin events. Plugin name is optional.
* Example: `@softDepend SimpleAuth`
* - `@ignoreCancelled`: Cancelled events WILL NOT be passed to this handler.
* - `@priority <PRIORITY>`: Sets the priority at which this event handler will receive events.
* Example: `@priority HIGHEST`
* @see EventPriority for a list of possible options.
*
* Event handlers will receive any instanceof the Event class they have chosen to receive. For example, an
* EntityDamageEvent handler will also receive any subclass of EntityDamageEvent.
*/
interface Listener{
}

View File

@ -51,6 +51,6 @@ class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent{
* @return Entity|null
*/
public function getChild() : ?Entity{
return $this->getEntity()->getLevel()->getServer()->findEntity($this->childEntityEid, $this->getEntity()->getLevel());
return $this->getEntity()->getLevel()->getServer()->findEntity($this->childEntityEid);
}
}

View File

@ -69,7 +69,7 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
* @return Entity|null
*/
public function getDamager() : ?Entity{
return $this->getEntity()->getLevel()->getServer()->findEntity($this->damagerEntityId, $this->getEntity()->getLevel());
return $this->getEntity()->getLevel()->getServer()->findEntity($this->damagerEntityId);
}
/**

View File

@ -38,6 +38,7 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
public const MODIFIER_ARMOR_ENCHANTMENTS = 6;
public const MODIFIER_CRITICAL = 7;
public const MODIFIER_TOTEM = 8;
public const MODIFIER_WEAPON_ENCHANTMENTS = 9;
public const CAUSE_CONTACT = 0;
public const CAUSE_ENTITY_ATTACK = 1;
@ -68,6 +69,9 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
/** @var float[] */
private $originals;
/** @var int */
private $attackCooldown = 10;
/**
* @param Entity $entity
@ -196,4 +200,24 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
return true;
}
/**
* Returns the cooldown in ticks before the target entity can be attacked again.
*
* @return int
*/
public function getAttackCooldown() : int{
return $this->attackCooldown;
}
/**
* Sets the cooldown in ticks before the target entity can be attacked again.
*
* NOTE: This value is not used in non-Living entities
*
* @param int $attackCooldown
*/
public function setAttackCooldown(int $attackCooldown) : void{
$this->attackCooldown = $attackCooldown;
}
}

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\event\Cancellable;
use pocketmine\permission\PermissionManager;
use pocketmine\Player;
use pocketmine\Server;
@ -55,7 +56,7 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{
$this->format = $format;
if($recipients === null){
$this->recipients = Server::getInstance()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_USERS);
$this->recipients = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_USERS);
}else{
$this->recipients = $recipients;
}

View File

@ -23,9 +23,15 @@ declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\block\Block;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\EntityDeathEvent;
use pocketmine\item\Item;
use pocketmine\lang\TextContainer;
use pocketmine\lang\TranslationContainer;
use pocketmine\Player;
class PlayerDeathEvent extends EntityDeathEvent{
@ -37,13 +43,13 @@ class PlayerDeathEvent extends EntityDeathEvent{
private $keepInventory = false;
/**
* @param Player $entity
* @param Item[] $drops
* @param string|TextContainer $deathMessage
* @param Player $entity
* @param Item[] $drops
* @param string|TextContainer|null $deathMessage Null will cause the default vanilla message to be used
*/
public function __construct(Player $entity, array $drops, $deathMessage){
public function __construct(Player $entity, array $drops, $deathMessage = null){
parent::__construct($entity, $drops);
$this->deathMessage = $deathMessage;
$this->deathMessage = $deathMessage ?? self::deriveMessage($entity->getDisplayName(), $entity->getLastDamageCause());
}
/**
@ -81,4 +87,123 @@ class PlayerDeathEvent extends EntityDeathEvent{
public function setKeepInventory(bool $keepInventory) : void{
$this->keepInventory = $keepInventory;
}
/**
* Returns the vanilla death message for the given death cause.
*
* @param string $name
* @param null|EntityDamageEvent $deathCause
*
* @return TranslationContainer
*/
public static function deriveMessage(string $name, ?EntityDamageEvent $deathCause) : TranslationContainer{
$message = "death.attack.generic";
$params = [$name];
switch($deathCause === null ? EntityDamageEvent::CAUSE_CUSTOM : $deathCause->getCause()){
case EntityDamageEvent::CAUSE_ENTITY_ATTACK:
if($deathCause instanceof EntityDamageByEntityEvent){
$e = $deathCause->getDamager();
if($e instanceof Player){
$message = "death.attack.player";
$params[] = $e->getDisplayName();
break;
}elseif($e instanceof Living){
$message = "death.attack.mob";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}else{
$params[] = "Unknown";
}
}
break;
case EntityDamageEvent::CAUSE_PROJECTILE:
if($deathCause instanceof EntityDamageByEntityEvent){
$e = $deathCause->getDamager();
if($e instanceof Player){
$message = "death.attack.arrow";
$params[] = $e->getDisplayName();
}elseif($e instanceof Living){
$message = "death.attack.arrow";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}else{
$params[] = "Unknown";
}
}
break;
case EntityDamageEvent::CAUSE_SUICIDE:
$message = "death.attack.generic";
break;
case EntityDamageEvent::CAUSE_VOID:
$message = "death.attack.outOfWorld";
break;
case EntityDamageEvent::CAUSE_FALL:
if($deathCause instanceof EntityDamageEvent){
if($deathCause->getFinalDamage() > 2){
$message = "death.fell.accident.generic";
break;
}
}
$message = "death.attack.fall";
break;
case EntityDamageEvent::CAUSE_SUFFOCATION:
$message = "death.attack.inWall";
break;
case EntityDamageEvent::CAUSE_LAVA:
$message = "death.attack.lava";
break;
case EntityDamageEvent::CAUSE_FIRE:
$message = "death.attack.onFire";
break;
case EntityDamageEvent::CAUSE_FIRE_TICK:
$message = "death.attack.inFire";
break;
case EntityDamageEvent::CAUSE_DROWNING:
$message = "death.attack.drown";
break;
case EntityDamageEvent::CAUSE_CONTACT:
if($deathCause instanceof EntityDamageByBlockEvent){
if($deathCause->getDamager()->getId() === Block::CACTUS){
$message = "death.attack.cactus";
}
}
break;
case EntityDamageEvent::CAUSE_BLOCK_EXPLOSION:
case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION:
if($deathCause instanceof EntityDamageByEntityEvent){
$e = $deathCause->getDamager();
if($e instanceof Player){
$message = "death.attack.explosion.player";
$params[] = $e->getDisplayName();
}elseif($e instanceof Living){
$message = "death.attack.explosion.player";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}
}else{
$message = "death.attack.explosion";
}
break;
case EntityDamageEvent::CAUSE_MAGIC:
$message = "death.attack.magic";
break;
case EntityDamageEvent::CAUSE_CUSTOM:
break;
default:
break;
}
return new TranslationContainer($message, $params);
}
}

View File

@ -50,6 +50,13 @@ class PlayerKickEvent extends PlayerEvent implements Cancellable{
$this->reason = $reason;
}
/**
* @param string $reason
*/
public function setReason(string $reason) : void{
$this->reason = $reason;
}
public function getReason() : string{
return $this->reason;
}

View File

@ -0,0 +1,73 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\server;
use pocketmine\command\CommandSender;
use pocketmine\event\Cancellable;
/**
* Called when any CommandSender runs a command, early in the process
*
* You don't want to use this except for a few cases like logging commands,
* blocking commands on certain places, or applying modifiers.
*
* The message DOES NOT contain a slash at the start
*/
class CommandEvent extends ServerEvent implements Cancellable{
/** @var string */
protected $command;
/** @var CommandSender */
protected $sender;
/**
* @param CommandSender $sender
* @param string $command
*/
public function __construct(CommandSender $sender, string $command){
$this->sender = $sender;
$this->command = $command;
}
/**
* @return CommandSender
*/
public function getSender() : CommandSender{
return $this->sender;
}
/**
* @return string
*/
public function getCommand() : string{
return $this->command;
}
/**
* @param string $command
*/
public function setCommand(string $command) : void{
$this->command = $command;
}
}

View File

@ -27,6 +27,8 @@ use pocketmine\command\CommandSender;
/**
* This event is called when a command is received over RCON.
*
* @deprecated Use CommandEvent instead.
*/
class RemoteServerCommandEvent extends ServerCommandEvent{

View File

@ -33,6 +33,8 @@ use pocketmine\event\Cancellable;
* blocking commands on certain places, or applying modifiers.
*
* The message DOES NOT contain a slash at the start
*
* @deprecated Use CommandEvent instead.
*/
class ServerCommandEvent extends ServerEvent implements Cancellable{
/** @var string */

View File

@ -0,0 +1,43 @@
<?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\form;
use pocketmine\Player;
/**
* Form implementations must implement this interface to be able to utilize the Player form-sending mechanism.
* There is no restriction on custom implementations other than that they must implement this.
*/
interface Form extends \JsonSerializable{
/**
* Handles a form response from a player.
*
* @param Player $player
* @param mixed $data
*
* @throws FormValidationException if the data could not be processed
*/
public function handleResponse(Player $player, $data) : void;
}

View File

@ -0,0 +1,28 @@
<?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\form;
class FormValidationException extends \RuntimeException{
}

View File

@ -24,9 +24,11 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Arrow as ArrowEntity;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\entity\EntityShootBowEvent;
use pocketmine\event\entity\ProjectileLaunchEvent;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\Player;
@ -64,6 +66,21 @@ class Bow extends Tool{
$entity = Entity::createEntity("Arrow", $player->getLevel(), $nbt, $player, $force == 2);
if($entity instanceof Projectile){
$infinity = $this->hasEnchantment(Enchantment::INFINITY);
if($entity instanceof ArrowEntity){
if($infinity){
$entity->setPickupMode(ArrowEntity::PICKUP_CREATIVE);
}
if(($punchLevel = $this->getEnchantmentLevel(Enchantment::PUNCH)) > 0){
$entity->setPunchKnockback($punchLevel);
}
}
if(($powerLevel = $this->getEnchantmentLevel(Enchantment::POWER)) > 0){
$entity->setBaseDamage($entity->getBaseDamage() + (($powerLevel + 1) / 2));
}
if($this->hasEnchantment(Enchantment::FLAME)){
$entity->setOnFire($entity->getFireTicks() * 20 + 100);
}
$ev = new EntityShootBowEvent($player, $this, $entity, $force);
if($force < 0.1 or $diff < 5){
@ -80,7 +97,9 @@ class Bow extends Tool{
}else{
$entity->setMotion($entity->getMotion()->multiply($ev->getForce()));
if($player->isSurvival()){
$player->getInventory()->removeItem(ItemFactory::get(Item::ARROW, 0, 1));
if(!$infinity){ //TODO: tipped arrows are still consumed when Infinity is applied
$player->getInventory()->removeItem(ItemFactory::get(Item::ARROW, 0, 1));
}
$this->applyDamage(1);
}

View File

@ -196,7 +196,10 @@ class Item implements ItemIds, \JsonSerializable{
* @param string $name
*/
public function __construct(int $id, int $meta = 0, string $name = "Unknown"){
$this->id = $id & 0xffff;
if($id < -0x8000 or $id > 0x7fff){ //signed short range
throw new \InvalidArgumentException("ID must be in range " . -0x8000 . " - " . 0x7fff);
}
$this->id = $id;
$this->setDamage($meta);
$this->name = $name;
}
@ -916,7 +919,7 @@ class Item implements ItemIds, \JsonSerializable{
*/
public function nbtSerialize(int $slot = -1, string $tagName = "") : CompoundTag{
$result = new CompoundTag($tagName, [
new ShortTag("id", Binary::signShort($this->id)),
new ShortTag("id", $this->id),
new ByteTag("Count", Binary::signByte($this->count)),
new ShortTag("Damage", $this->meta)
]);
@ -951,7 +954,7 @@ class Item implements ItemIds, \JsonSerializable{
$idTag = $tag->getTag("id");
if($idTag instanceof ShortTag){
$item = ItemFactory::get(Binary::unsignShort($idTag->getValue()), $meta, $count);
$item = ItemFactory::get($idTag->getValue(), $meta, $count);
}elseif($idTag instanceof StringTag){ //PC item save format
$item = ItemFactory::fromString($idTag->getValue());
$item->setDamage($meta);

View File

@ -246,6 +246,8 @@ class ItemFactory{
self::registerItem(new Item(Item::NAUTILUS_SHELL, 0, "Nautilus Shell"));
self::registerItem(new GoldenAppleEnchanted());
self::registerItem(new Item(Item::HEART_OF_THE_SEA, 0, "Heart of the Sea"));
self::registerItem(new Item(Item::TURTLE_SHELL_PIECE, 0, "Scute"));
//TODO: TURTLE_HELMET
//TODO: COMPOUND
//TODO: RECORD_13
@ -281,7 +283,7 @@ class ItemFactory{
throw new \RuntimeException("Trying to overwrite an already registered item");
}
self::$list[$id] = clone $item;
self::$list[self::getListOffset($id)] = clone $item;
}
/**
@ -302,10 +304,10 @@ class ItemFactory{
try{
/** @var Item|null $listed */
$listed = self::$list[$id];
$listed = self::$list[self::getListOffset($id)];
if($listed !== null){
$item = clone $listed;
}elseif($id < 256){
}elseif($id < 256){ //intentionally includes negatives, for extended block IDs
/* Blocks must have a damage value 0-15, but items can have damage value -1 to indicate that they are
* crafting ingredients with any-damage. */
$item = new ItemBlock($id, $meta);
@ -353,13 +355,13 @@ class ItemFactory{
if(!isset($b[1])){
$meta = 0;
}elseif(is_numeric($b[1])){
$meta = $b[1] & 0xFFFF;
$meta = (int) $b[1];
}else{
throw new \InvalidArgumentException("Unable to parse \"" . $b[1] . "\" from \"" . $str . "\" as a valid meta value");
}
if(is_numeric($b[0])){
$item = self::get(((int) $b[0]) & 0xFFFF, $meta);
$item = self::get((int) $b[0], $meta);
}elseif(defined(ItemIds::class . "::" . strtoupper($b[0]))){
$item = self::get(constant(ItemIds::class . "::" . strtoupper($b[0])), $meta);
}else{
@ -380,6 +382,13 @@ class ItemFactory{
if($id < 256){
return BlockFactory::isRegistered($id);
}
return self::$list[$id] !== null;
return self::$list[self::getListOffset($id)] !== null;
}
private static function getListOffset(int $id) : int{
if($id < -0x8000 or $id > 0x7fff){
throw new \InvalidArgumentException("ID must be in range " . -0x8000 . " - " . 0x7fff);
}
return $id & 0xffff;
}
}

View File

@ -235,6 +235,8 @@ interface ItemIds extends BlockIds{
public const NAUTILUS_SHELL = 465;
public const APPLEENCHANTED = 466, APPLE_ENCHANTED = 466, ENCHANTED_GOLDEN_APPLE = 466;
public const HEART_OF_THE_SEA = 467;
public const TURTLE_SHELL_PIECE = 468;
public const TURTLE_HELMET = 469;
public const COMPOUND = 499;
public const RECORD_13 = 500;

View File

@ -114,13 +114,26 @@ class Enchantment{
self::registerEnchantment(new ProtectionEnchantment(self::PROJECTILE_PROTECTION, "%enchantment.protect.projectile", self::RARITY_UNCOMMON, self::SLOT_ARMOR, self::SLOT_NONE, 4, 1.5, [
EntityDamageEvent::CAUSE_PROJECTILE
]));
self::registerEnchantment(new Enchantment(self::THORNS, "%enchantment.thorns", self::RARITY_MYTHIC, self::SLOT_TORSO, self::SLOT_HEAD | self::SLOT_LEGS | self::SLOT_FEET, 3));
self::registerEnchantment(new Enchantment(self::RESPIRATION, "%enchantment.oxygen", self::RARITY_RARE, self::SLOT_HEAD, self::SLOT_NONE, 3));
self::registerEnchantment(new SharpnessEnchantment(self::SHARPNESS, "%enchantment.damage.all", self::RARITY_COMMON, self::SLOT_SWORD, self::SLOT_AXE, 5));
//TODO: smite, bane of arthropods (these don't make sense now because their applicable mobs don't exist yet)
self::registerEnchantment(new KnockbackEnchantment(self::KNOCKBACK, "%enchantment.knockback", self::RARITY_UNCOMMON, self::SLOT_SWORD, self::SLOT_NONE, 2));
self::registerEnchantment(new FireAspectEnchantment(self::FIRE_ASPECT, "%enchantment.fire", self::RARITY_RARE, self::SLOT_SWORD, self::SLOT_NONE, 2));
self::registerEnchantment(new Enchantment(self::EFFICIENCY, "%enchantment.digging", self::RARITY_COMMON, self::SLOT_DIG, self::SLOT_SHEARS, 5));
self::registerEnchantment(new Enchantment(self::SILK_TOUCH, "%enchantment.untouching", self::RARITY_MYTHIC, self::SLOT_DIG, self::SLOT_SHEARS, 1));
self::registerEnchantment(new Enchantment(self::UNBREAKING, "%enchantment.durability", self::RARITY_UNCOMMON, self::SLOT_DIG | self::SLOT_ARMOR | self::SLOT_FISHING_ROD | self::SLOT_BOW, self::SLOT_TOOL | self::SLOT_CARROT_STICK | self::SLOT_ELYTRA, 3));
self::registerEnchantment(new Enchantment(self::POWER, "%enchantment.arrowDamage", self::RARITY_COMMON, self::SLOT_BOW, self::SLOT_NONE, 5));
self::registerEnchantment(new Enchantment(self::PUNCH, "%enchantment.arrowKnockback", self::RARITY_RARE, self::SLOT_BOW, self::SLOT_NONE, 2));
self::registerEnchantment(new Enchantment(self::FLAME, "%enchantment.arrowFire", self::RARITY_RARE, self::SLOT_BOW, self::SLOT_NONE, 1));
self::registerEnchantment(new Enchantment(self::INFINITY, "%enchantment.arrowInfinite", self::RARITY_MYTHIC, self::SLOT_BOW, self::SLOT_NONE, 1));
self::registerEnchantment(new Enchantment(self::MENDING, "%enchantment.mending", self::RARITY_RARE, self::SLOT_NONE, self::SLOT_ALL, 1));
self::registerEnchantment(new Enchantment(self::VANISHING, "%enchantment.curse.vanishing", self::RARITY_MYTHIC, self::SLOT_NONE, self::SLOT_ALL, 1));
}

View File

@ -0,0 +1,41 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
use pocketmine\entity\Entity;
class FireAspectEnchantment extends MeleeWeaponEnchantment{
public function isApplicableTo(Entity $victim) : bool{
return true;
}
public function getDamageBonus(int $enchantmentLevel) : float{
return 0;
}
public function onPostAttack(Entity $attacker, Entity $victim, int $enchantmentLevel) : void{
$victim->setOnFire($enchantmentLevel * 4);
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
class KnockbackEnchantment extends MeleeWeaponEnchantment{
public function isApplicableTo(Entity $victim) : bool{
return $victim instanceof Living;
}
public function getDamageBonus(int $enchantmentLevel) : float{
return 0;
}
public function onPostAttack(Entity $attacker, Entity $victim, int $enchantmentLevel) : void{
if($victim instanceof Living){
$victim->knockBack($attacker, 0, $victim->x - $attacker->x, $victim->z - $attacker->z, $enchantmentLevel * 0.5);
}
}
}

View File

@ -0,0 +1,63 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
use pocketmine\entity\Entity;
/**
* Classes extending this class can be applied to weapons and activate when used by a mob to attack another mob in melee
* combat.
*/
abstract class MeleeWeaponEnchantment extends Enchantment{
/**
* Returns whether this melee enchantment has an effect on the target entity. For example, Smite only applies to
* undead mobs.
*
* @param Entity $victim
*
* @return bool
*/
abstract public function isApplicableTo(Entity $victim) : bool;
/**
* Returns the amount of additional damage caused by this enchantment to applicable targets.
*
* @param int $enchantmentLevel
*
* @return float
*/
abstract public function getDamageBonus(int $enchantmentLevel) : float;
/**
* Called after damaging the entity to apply any post damage effects to the target.
*
* @param Entity $attacker
* @param Entity $victim
* @param int $enchantmentLevel
*/
public function onPostAttack(Entity $attacker, Entity $victim, int $enchantmentLevel) : void{
}
}

View File

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

View File

@ -200,6 +200,8 @@ class Level implements ChunkManager, Metadatable{
private $chunkPopulationLock = [];
/** @var int */
private $chunkPopulationQueueSize = 2;
/** @var bool[] */
private $generatorRegisteredWorkers = [];
/** @var bool */
private $autoSave = true;
@ -245,9 +247,6 @@ class Level implements ChunkManager, Metadatable{
/** @var bool */
private $closed = false;
/** @var \Closure */
private $asyncPoolStartHook;
public static function chunkHash(int $x, int $z) : int{
return (($x & 0xFFFFFFFF) << 32) | ($z & 0xFFFFFFFF);
}
@ -363,10 +362,6 @@ class Level implements ChunkManager, Metadatable{
$this->temporalPosition = new Position(0, 0, 0, $this);
$this->temporalVector = new Vector3(0, 0, 0);
$this->tickRate = 1;
$this->server->getAsyncPool()->addWorkerStartHook($this->asyncPoolStartHook = function(int $worker) : void{
$this->registerGeneratorToWorker($worker);
});
}
public function getTickRate() : int{
@ -382,15 +377,18 @@ class Level implements ChunkManager, Metadatable{
}
public function registerGeneratorToWorker(int $worker) : void{
$this->generatorRegisteredWorkers[$worker] = true;
$this->server->getAsyncPool()->submitTaskToWorker(new GeneratorRegisterTask($this, $this->generator, $this->provider->getGeneratorOptions()), $worker);
}
public function unregisterGenerator(){
$pool = $this->server->getAsyncPool();
$pool->removeWorkerStartHook($this->asyncPoolStartHook);
foreach($pool->getRunningWorkers() as $i){
$pool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i);
if(isset($this->generatorRegisteredWorkers[$i])){
$pool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i);
}
}
$this->generatorRegisteredWorkers = [];
}
public function getBlockMetadata() : BlockMetadataStore{
@ -848,18 +846,18 @@ class Level implements ChunkManager, Metadatable{
}
/**
* @param Player[] $target
* @param Block[] $blocks
* @param int $flags
* @param bool $optimizeRebuilds
* @param Player[] $target
* @param Vector3[] $blocks
* @param int $flags
* @param bool $optimizeRebuilds
*/
public function sendBlocks(array $target, array $blocks, int $flags = UpdateBlockPacket::FLAG_NONE, bool $optimizeRebuilds = false){
$packets = [];
if($optimizeRebuilds){
$chunks = [];
foreach($blocks as $b){
if($b === null){
continue;
if(!($b instanceof Vector3)){
throw new \TypeError("Expected Vector3 in blocks array, got " . (is_object($b) ? get_class($b) : gettype($b)));
}
$pk = new UpdateBlockPacket();
@ -890,8 +888,8 @@ class Level implements ChunkManager, Metadatable{
}
}else{
foreach($blocks as $b){
if($b === null){
continue;
if(!($b instanceof Vector3)){
throw new \TypeError("Expected Vector3 in blocks array, got " . (is_object($b) ? get_class($b) : gettype($b)));
}
$pk = new UpdateBlockPacket();
@ -2953,8 +2951,13 @@ class Level implements ChunkManager, Metadatable{
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
}
}
$task = new PopulationTask($this, $chunk);
$this->server->getAsyncPool()->submitTask($task);
$workerId = $this->server->getAsyncPool()->selectWorker();
if(!isset($this->generatorRegisteredWorkers[$workerId])){
$this->registerGeneratorToWorker($workerId);
}
$this->server->getAsyncPool()->submitTaskToWorker($task, $workerId);
}
Timings::$populationTimer->stopTiming();

View File

@ -75,8 +75,8 @@ class Chunk{
/** @var Entity[] */
protected $entities = [];
/** @var int[] */
protected $heightMap = [];
/** @var \SplFixedArray|int[] */
protected $heightMap;
/** @var string */
protected $biomeIds;
@ -110,11 +110,11 @@ class Chunk{
}
if(count($heightMap) === 256){
$this->heightMap = $heightMap;
$this->heightMap = \SplFixedArray::fromArray($heightMap);
}else{
assert(count($heightMap) === 0, "Wrong HeightMap value count, expected 256, got " . count($heightMap));
$val = ($this->height * 16);
$this->heightMap = array_fill(0, 256, $val);
$this->heightMap = \SplFixedArray::fromArray(array_fill(0, 256, $val));
}
if(strlen($biomeIds) === 256){
@ -739,7 +739,7 @@ class Chunk{
* @return int[]
*/
public function getHeightMapArray() : array{
return $this->heightMap;
return $this->heightMap->toArray();
}
/**

View File

@ -27,7 +27,6 @@ use pocketmine\level\format\io\leveldb\LevelDB;
use pocketmine\level\format\io\region\Anvil;
use pocketmine\level\format\io\region\McRegion;
use pocketmine\level\format\io\region\PMAnvil;
use pocketmine\level\LevelException;
abstract class LevelProviderManager{
protected static $providers = [];
@ -42,12 +41,21 @@ abstract class LevelProviderManager{
/**
* @param string $class
*
* @throws LevelException
* @throws \InvalidArgumentException
*/
public static function addProvider(string $class){
if(!is_subclass_of($class, LevelProvider::class)){
throw new LevelException("Class is not a subclass of LevelProvider");
try{
$reflection = new \ReflectionClass($class);
}catch(\ReflectionException $e){
throw new \InvalidArgumentException("Class $class does not exist");
}
if(!$reflection->implementsInterface(LevelProvider::class)){
throw new \InvalidArgumentException("Class $class does not implement " . LevelProvider::class);
}
if(!$reflection->isInstantiable()){
throw new \InvalidArgumentException("Class $class cannot be constructed");
}
/** @var LevelProvider $class */
self::$providers[strtolower($class::getProviderName())] = $class;
}

View File

@ -26,11 +26,13 @@ namespace pocketmine\level\generator;
use pocketmine\block\Block;
use pocketmine\block\BlockFactory;
use pocketmine\item\ItemFactory;
use pocketmine\level\ChunkManager;
use pocketmine\level\format\Chunk;
use pocketmine\level\generator\object\OreType;
use pocketmine\level\generator\populator\Ore;
use pocketmine\level\generator\populator\Populator;
use pocketmine\math\Vector3;
use pocketmine\utils\Random;
class Flat extends Generator{
/** @var Chunk */
@ -41,6 +43,8 @@ class Flat extends Generator{
private $structure;
/** @var int */
private $floorLevel;
/** @var int */
private $biome;
/** @var mixed[] */
private $options;
/** @var string */
@ -55,9 +59,15 @@ class Flat extends Generator{
}
public function __construct(array $options = []){
$this->preset = "2;7,2x3,2;1;";
//$this->preset = "2;7,59x1,3x3,2;1;spawn(radius=10 block=89),decoration(treecount=80 grasscount=45)";
$this->options = $options;
if(isset($this->options["preset"]) and $this->options["preset"] != ""){
$this->preset = $this->options["preset"];
}else{
$this->preset = "2;7,2x3,2;1;";
//$this->preset = "2;7,59x1,3x3,2;1;spawn(radius=10 block=89),decoration(treecount=80 grasscount=45)";
}
$this->parsePreset();
if(isset($this->options["decoration"])){
$ores = new Ore();
@ -90,39 +100,14 @@ class Flat extends Generator{
return $result;
}
protected function generateBaseChunk(string $preset) : void{
$this->preset = $preset;
$preset = explode(";", $preset);
$version = (int) $preset[0];
protected function parsePreset() : void{
$preset = explode(";", $this->preset);
$blocks = (string) ($preset[1] ?? "");
$biome = (int) ($preset[2] ?? 1);
$this->biome = (int) ($preset[2] ?? 1);
$options = (string) ($preset[3] ?? "");
$this->structure = self::parseLayers($blocks);
$this->floorLevel = $y = count($this->structure);
$this->chunk = new Chunk(0, 0);
$this->chunk->setGenerated();
for($Z = 0; $Z < 16; ++$Z){
for($X = 0; $X < 16; ++$X){
$this->chunk->setBiomeId($X, $Z, $biome);
}
}
$count = count($this->structure);
for($sy = 0; $sy < $count; $sy += 16){
$subchunk = $this->chunk->getSubChunk($sy >> 4, true);
for($y = 0; $y < 16 and isset($this->structure[$y | $sy]); ++$y){
list($id, $meta) = $this->structure[$y | $sy];
for($Z = 0; $Z < 16; ++$Z){
for($X = 0; $X < 16; ++$X){
$subchunk->setBlock($X, $y, $Z, $id, $meta);
}
}
}
}
$this->floorLevel = count($this->structure);
preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches);
foreach($matches[2] as $i => $option){
@ -141,14 +126,37 @@ class Flat extends Generator{
}
}
public function generateChunk(int $chunkX, int $chunkZ) : void{
if($this->chunk === null){
if(isset($this->options["preset"]) and $this->options["preset"] != ""){
$this->generateBaseChunk($this->options["preset"]);
}else{
$this->generateBaseChunk($this->preset);
protected function generateBaseChunk() : void{
$this->chunk = new Chunk(0, 0);
$this->chunk->setGenerated();
for($Z = 0; $Z < 16; ++$Z){
for($X = 0; $X < 16; ++$X){
$this->chunk->setBiomeId($X, $Z, $this->biome);
}
}
$count = count($this->structure);
for($sy = 0; $sy < $count; $sy += 16){
$subchunk = $this->chunk->getSubChunk($sy >> 4, true);
for($y = 0; $y < 16 and isset($this->structure[$y | $sy]); ++$y){
list($id, $meta) = $this->structure[$y | $sy];
for($Z = 0; $Z < 16; ++$Z){
for($X = 0; $X < 16; ++$X){
$subchunk->setBlock($X, $y, $Z, $id, $meta);
}
}
}
}
}
public function init(ChunkManager $level, Random $random) : void{
parent::init($level, $random);
$this->generateBaseChunk();
}
public function generateChunk(int $chunkX, int $chunkZ) : void{
$chunk = clone $this->chunk;
$chunk->setX($chunkX);
$chunk->setZ($chunkZ);

View File

@ -79,6 +79,7 @@ use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MoveEntityAbsolutePacket;
use pocketmine\network\mcpe\protocol\MoveEntityDeltaPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket;
use pocketmine\network\mcpe\protocol\NpcRequestPacket;
use pocketmine\network\mcpe\protocol\PhotoTransferPacket;
use pocketmine\network\mcpe\protocol\PlaySoundPacket;
@ -115,6 +116,7 @@ use pocketmine\network\mcpe\protocol\SetLastHurtByPacket;
use pocketmine\network\mcpe\protocol\SetLocalPlayerAsInitializedPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
use pocketmine\network\mcpe\protocol\SetScorePacket;
use pocketmine\network\mcpe\protocol\SetScoreboardIdentityPacket;
use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket;
use pocketmine\network\mcpe\protocol\SetTimePacket;
use pocketmine\network\mcpe\protocol\SetTitlePacket;
@ -134,6 +136,7 @@ use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockSyncedPacket;
use pocketmine\network\mcpe\protocol\UpdateEquipPacket;
use pocketmine\network\mcpe\protocol\UpdateSoftEnumPacket;
use pocketmine\network\mcpe\protocol\UpdateTradePacket;
use pocketmine\network\mcpe\protocol\WSConnectPacket;
@ -585,7 +588,19 @@ abstract class NetworkSession{
return false;
}
public function handleSetScoreboardIdentity(SetScoreboardIdentityPacket $packet) : bool{
return false;
}
public function handleSetLocalPlayerAsInitialized(SetLocalPlayerAsInitializedPacket $packet) : bool{
return false;
}
public function handleUpdateSoftEnum(UpdateSoftEnumPacket $packet) : bool{
return false;
}
public function handleNetworkStackLatency(NetworkStackLatencyPacket $packet) : bool{
return false;
}
}

View File

@ -239,7 +239,37 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
}
public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{
return false; //TODO: GUI stuff
return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true));
}
/**
* Hack to work around a stupid bug in Minecraft W10 which causes empty strings to be sent unquoted in form responses.
*
* @param string $json
* @param bool $assoc
*
* @return mixed
*/
private static function stupid_json_decode(string $json, bool $assoc = false){
if(preg_match('/^\[(.+)\]$/s', $json, $matches) > 0){
$parts = preg_split('/(?:"(?:\\"|[^"])*"|)\K(,)/', $matches[1]); //Splits on commas not inside quotes, ignoring escaped quotes
foreach($parts as $k => $part){
$part = trim($part);
if($part === ""){
$part = "\"\"";
}
$parts[$k] = $part;
}
$fixed = "[" . implode(",", $parts) . "]";
if(($ret = json_decode($fixed, $assoc)) === null){
throw new \InvalidArgumentException("Failed to fix JSON: " . json_last_error_msg() . "(original: $json, modified: $fixed)");
}
return $ret;
}
return json_decode($json, $assoc);
}
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{

View File

@ -46,7 +46,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
* Sometimes this gets changed when the MCPE-layer protocol gets broken to the point where old and new can't
* communicate. It's important that we check this to avoid catastrophes.
*/
private const MCPE_RAKNET_PROTOCOL_VERSION = 8;
private const MCPE_RAKNET_PROTOCOL_VERSION = 9;
/** @var Server */
private $server;

View File

@ -145,7 +145,7 @@ class VerifyLoginTask extends AsyncTask{
public function onCompletion(Server $server){
/** @var Player $player */
$player = $this->fetchLocal();
if($player->isClosed()){
if(!$player->isConnected()){
$server->getLogger()->error("Player " . $player->getName() . " was disconnected before their login could be verified");
}else{
$player->onVerifyCompleted($this->packet, $this->error, $this->authenticated);

View File

@ -75,6 +75,9 @@ class AddPlayerPacket extends DataPacket{
/** @var EntityLink[] */
public $links = [];
/** @var string */
public $deviceId = ""; //TODO: fill player's device ID (???)
protected function decodePayload(){
$this->uuid = $this->getUUID();
$this->username = $this->getString();
@ -103,6 +106,8 @@ class AddPlayerPacket extends DataPacket{
for($i = 0; $i < $linkCount; ++$i){
$this->links[$i] = $this->getEntityLink();
}
$this->deviceId = $this->getString();
}
protected function encodePayload(){
@ -133,6 +138,8 @@ class AddPlayerPacket extends DataPacket{
foreach($this->links as $link){
$this->putEntityLink($link);
}
$this->putString($this->deviceId);
}
public function handle(NetworkSession $session) : bool{

View File

@ -103,6 +103,13 @@ class AvailableCommandsPacket extends DataPacket{
*/
public $commandData = [];
/**
* @var CommandEnum[]
* List of dynamic command enums, also referred to as "soft" enums. These can by dynamically updated mid-game
* without resending this packet.
*/
public $softEnums = [];
protected function decodePayload(){
for($i = 0, $this->enumValuesCount = $this->getUnsignedVarInt(); $i < $this->enumValuesCount; ++$i){
$this->enumValues[] = $this->getString();
@ -119,6 +126,10 @@ class AvailableCommandsPacket extends DataPacket{
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->commandData[] = $this->getCommandData();
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->softEnums[] = $this->getSoftEnum();
}
}
protected function getEnum() : CommandEnum{
@ -133,6 +144,18 @@ class AvailableCommandsPacket extends DataPacket{
return $retval;
}
protected function getSoftEnum() : CommandEnum{
$retval = new CommandEnum();
$retval->enumName = $this->getString();
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
//Get the enum value from the initial pile of mess
$retval->enumValues[] = $this->getString();
}
return $retval;
}
protected function putEnum(CommandEnum $enum){
$this->putString($enum->enumName);
@ -147,6 +170,15 @@ class AvailableCommandsPacket extends DataPacket{
}
}
protected function putSoftEnum(CommandEnum $enum) : void{
$this->putString($enum->enumName);
$this->putUnsignedVarInt(count($enum->enumValues));
foreach($enum->enumValues as $value){
$this->putString($value);
}
}
protected function getEnumValueIndex() : int{
if($this->enumValuesCount < 256){
return $this->getByte();
@ -185,13 +217,17 @@ class AvailableCommandsPacket extends DataPacket{
if($parameter->paramType & self::ARG_FLAG_ENUM){
$index = ($parameter->paramType & 0xffff);
$parameter->enum = $this->enums[$index] ?? null;
assert($parameter->enum !== null, "expected enum at $index, but got none");
}elseif(($parameter->paramType & self::ARG_FLAG_VALID) === 0){ //postfix (guessing)
if($parameter->enum === null){
throw new \UnexpectedValueException("expected enum at $index, but got none");
}
}elseif($parameter->paramType & self::ARG_FLAG_POSTFIX){
$index = ($parameter->paramType & 0xffff);
$parameter->postfix = $this->postfixes[$index] ?? null;
assert($parameter->postfix !== null, "expected postfix at $index, but got none");
if($parameter->postfix === null){
throw new \UnexpectedValueException("expected postfix at $index, but got none");
}
}elseif(($parameter->paramType & self::ARG_FLAG_VALID) === 0){
throw new \UnexpectedValueException("Invalid parameter type 0x" . dechex($parameter->paramType));
}
$retval->overloads[$overloadIndex][$paramIndex] = $parameter;
@ -227,7 +263,7 @@ class AvailableCommandsPacket extends DataPacket{
if($key === false){
throw new \InvalidStateException("Postfix '$parameter->postfix' not in postfixes array");
}
$type = $parameter->paramType << 24 | $key;
$type = self::ARG_FLAG_POSTFIX | $key;
}else{
$type = $parameter->paramType;
}
@ -266,13 +302,12 @@ class AvailableCommandsPacket extends DataPacket{
case self::ARG_TYPE_COMMAND:
return "command";
}
}elseif($argtype !== 0){
//guessed
$baseType = $argtype >> 24;
$typeName = $this->argTypeToString(self::ARG_FLAG_VALID | $baseType);
}elseif($argtype & self::ARG_FLAG_POSTFIX){
$postfix = $this->postfixes[$argtype & 0xffff];
return $typeName . " (postfix $postfix)";
return "int (postfix $postfix)";
}else{
throw new \UnexpectedValueException("Unknown arg type 0x" . dechex($argtype));
}
return "unknown ($argtype)";
@ -334,6 +369,11 @@ class AvailableCommandsPacket extends DataPacket{
foreach($this->commandData as $data){
$this->putCommandData($data);
}
$this->putUnsignedVarInt(count($this->softEnums));
foreach($this->softEnums as $enum){
$this->putSoftEnum($enum);
}
}
public function handle(NetworkSession $session) : bool{

View File

@ -74,10 +74,6 @@ abstract class DataPacket extends NetworkBinaryStream{
protected function decodeHeader(){
$pid = $this->getUnsignedVarInt();
assert($pid === static::NETWORK_ID);
$this->senderSubId = $this->getByte();
$this->recipientSubId = $this->getByte();
assert($this->senderSubId === 0 and $this->recipientSubId === 0, "Got unexpected non-zero split-screen bytes (byte1: $this->senderSubId, byte2: $this->recipientSubId");
}
/**
@ -96,9 +92,6 @@ abstract class DataPacket extends NetworkBinaryStream{
protected function encodeHeader(){
$this->putUnsignedVarInt(static::NETWORK_ID);
$this->putByte($this->senderSubId);
$this->putByte($this->recipientSubId);
}
/**

View File

@ -79,13 +79,6 @@ class LoginPacket extends DataPacket{
protected function decodePayload(){
$this->protocol = $this->getInt();
if($this->protocol !== ProtocolInfo::CURRENT_PROTOCOL){
if($this->protocol > 0xffff){ //guess MCPE <= 1.1
$this->offset -= 6;
$this->protocol = $this->getInt();
}
}
try{
$this->decodeConnectionRequest();
}catch(\Throwable $e){

View File

@ -0,0 +1,47 @@
<?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\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class NetworkStackLatencyPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::NETWORK_STACK_LATENCY_PACKET;
/** @var int */
public $timestamp;
protected function decodePayload(){
$this->timestamp = $this->getLLong();
}
protected function encodePayload(){
$this->putLLong($this->timestamp);
}
public function handle(NetworkSession $session) : bool{
return $session->handleNetworkStackLatency($this);
}
}

View File

@ -142,7 +142,10 @@ class PacketPool{
static::registerPacket(new LabTablePacket());
static::registerPacket(new UpdateBlockSyncedPacket());
static::registerPacket(new MoveEntityDeltaPacket());
static::registerPacket(new SetScoreboardIdentityPacket());
static::registerPacket(new SetLocalPlayerAsInitializedPacket());
static::registerPacket(new UpdateSoftEnumPacket());
static::registerPacket(new NetworkStackLatencyPacket());
static::registerPacket(new BatchPacket());
}

View File

@ -43,12 +43,6 @@ class PlayStatusPacket extends DataPacket{
/** @var int */
public $status;
/**
* @var int
* Used to determine how to write the packet when we disconnect incompatible clients.
*/
public $protocol = ProtocolInfo::CURRENT_PROTOCOL;
protected function decodePayload(){
$this->status = $this->getInt();
}
@ -57,14 +51,6 @@ class PlayStatusPacket extends DataPacket{
return true;
}
protected function encodeHeader(){
if($this->protocol < 130){ //MCPE <= 1.1
$this->putByte(static::NETWORK_ID);
}else{
parent::encodeHeader();
}
}
protected function encodePayload(){
$this->putInt($this->status);
}

View File

@ -39,15 +39,15 @@ interface ProtocolInfo{
/**
* Actual Minecraft: PE protocol version
*/
public const CURRENT_PROTOCOL = 274;
public const CURRENT_PROTOCOL = 282;
/**
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
*/
public const MINECRAFT_VERSION = 'v1.5.0';
public const MINECRAFT_VERSION = 'v1.6.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.5.0';
public const MINECRAFT_VERSION_NETWORK = '1.6.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -160,6 +160,9 @@ interface ProtocolInfo{
public const LAB_TABLE_PACKET = 0x6d;
public const UPDATE_BLOCK_SYNCED_PACKET = 0x6e;
public const MOVE_ENTITY_DELTA_PACKET = 0x6f;
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x70;
public const SET_SCOREBOARD_IDENTITY_PACKET = 0x70;
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x71;
public const UPDATE_SOFT_ENUM_PACKET = 0x72;
public const NETWORK_STACK_LATENCY_PACKET = 0x73;
}

View File

@ -70,6 +70,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->putLLong($entry->getPackSize());
$this->putString(""); //TODO: encryption key
$this->putString(""); //TODO: subpack name
$this->putString(""); //TODO: content identity
}
$this->putLShort(count($this->resourcePackEntries));
foreach($this->resourcePackEntries as $entry){
@ -78,6 +79,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->putLLong($entry->getPackSize());
$this->putString(""); //TODO: encryption key
$this->putString(""); //TODO: subpack name
$this->putString(""); //TODO: content identity
}
}

View File

@ -0,0 +1,69 @@
<?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\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\ScoreboardIdentityPacketEntry;
class SetScoreboardIdentityPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::SET_SCOREBOARD_IDENTITY_PACKET;
public const TYPE_REGISTER_IDENTITY = 0;
public const TYPE_CLEAR_IDENTITY = 1;
/** @var int */
public $type;
/** @var ScoreboardIdentityPacketEntry[] */
public $entries = [];
protected function decodePayload(){
$this->type = $this->getByte();
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$entry = new ScoreboardIdentityPacketEntry();
$entry->scoreboardId = $this->getVarLong();
if($this->type === self::TYPE_REGISTER_IDENTITY){
$entry->uuid = $this->getUUID();
}
$this->entries[] = $entry;
}
}
protected function encodePayload(){
$this->putByte($this->type);
$this->putUnsignedVarInt(count($this->entries));
foreach($this->entries as $entry){
$this->putVarLong($entry->scoreboardId);
if($this->type === self::TYPE_REGISTER_IDENTITY){
$this->putUUID($entry->uuid);
}
}
}
public function handle(NetworkSession $session) : bool{
return $session->handleSetScoreboardIdentity($this);
}
}

View File

@ -27,12 +27,16 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkBinaryStream;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
class StartGamePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::START_GAME_PACKET;
/** @var string|null */
private static $runtimeIdTable;
/** @var int */
public $entityUniqueId;
/** @var int */
@ -125,6 +129,8 @@ class StartGamePacket extends DataPacket{
public $currentTick = 0; //only used if isTrial is true
/** @var int */
public $enchantmentSeed = 0;
/** @var string */
public $multiplayerCorrelationId = ""; //TODO: this should be filled with a UUID of some sort
protected function decodePayload(){
$this->entityUniqueId = $this->getEntityUniqueId();
@ -175,6 +181,14 @@ class StartGamePacket extends DataPacket{
$this->currentTick = $this->getLLong();
$this->enchantmentSeed = $this->getVarInt();
$count = $this->getUnsignedVarInt();
for($i = 0; $i < $count; ++$i){
$this->getString();
$this->getLShort();
}
$this->multiplayerCorrelationId = $this->getString();
}
protected function encodePayload(){
@ -226,6 +240,21 @@ class StartGamePacket extends DataPacket{
$this->putLLong($this->currentTick);
$this->putVarInt($this->enchantmentSeed);
if(self::$runtimeIdTable === null){
//this is a really nasty hack, but it'll do for now
$stream = new NetworkBinaryStream();
$data = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
$stream->putUnsignedVarInt(count($data));
foreach($data as $v){
$stream->putString($v["name"]);
$stream->putLShort($v["data"]);
}
self::$runtimeIdTable = $stream->buffer;
}
$this->put(self::$runtimeIdTable);
$this->putString($this->multiplayerCorrelationId);
}
public function handle(NetworkSession $session) : bool{

View File

@ -0,0 +1,64 @@
<?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\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class UpdateSoftEnumPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::UPDATE_SOFT_ENUM_PACKET;
public const TYPE_ADD = 0;
public const TYPE_REMOVE = 1;
public const TYPE_SET = 2;
/** @var string */
public $enumName;
/** @var string[] */
public $values = [];
/** @var int */
public $type;
protected function decodePayload(){
$this->enumName = $this->getString();
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->values[] = $this->getString();
}
$this->type = $this->getByte();
}
protected function encodePayload(){
$this->putString($this->enumName);
$this->putUnsignedVarInt(count($this->values));
foreach($this->values as $v){
$this->putString($v);
}
$this->putByte($this->type);
}
public function handle(NetworkSession $session) : bool{
return $session->handleUpdateSoftEnum($this);
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types;
use pocketmine\utils\UUID;
class ScoreboardIdentityPacketEntry{
/** @var int */
public $scoreboardId;
/** @var UUID|null */
public $uuid;
}

View File

@ -26,12 +26,13 @@ declare(strict_types=1);
*/
namespace pocketmine\network\upnp;
use pocketmine\utils\Internet;
use pocketmine\utils\Utils;
abstract class UPnP{
public static function PortForward(int $port) : void{
if(!Utils::$online){
if(!Internet::$online){
throw new \RuntimeException("Server is offline");
}
if(Utils::getOS() !== "win"){
@ -46,8 +47,9 @@ abstract class UPnP{
/** @noinspection PhpUndefinedClassInspection */
$com = new \COM("HNetCfg.NATUPnP");
/** @noinspection PhpUndefinedFieldInspection */
if($com === false or !is_object($com->StaticPortMappingCollection)){
throw new \RuntimeException("Failed to portforward (unsupported?)");
throw new \RuntimeException("Failed to portforward using UPnP. Ensure that network discovery is enabled in Control Panel.");
}
/** @noinspection PhpUndefinedFieldInspection */
@ -55,7 +57,7 @@ abstract class UPnP{
}
public static function RemovePortForward(int $port) : bool{
if(!Utils::$online){
if(!Internet::$online){
return false;
}
if(Utils::getOS() != "win" or !class_exists("COM")){

View File

@ -23,8 +23,6 @@ declare(strict_types=1);
namespace pocketmine\permission;
use pocketmine\Server;
abstract class DefaultPermissions{
public const ROOT = "pocketmine";
@ -40,9 +38,9 @@ abstract class DefaultPermissions{
return self::registerPermission($perm);
}
Server::getInstance()->getPluginManager()->addPermission($perm);
PermissionManager::getInstance()->addPermission($perm);
return Server::getInstance()->getPluginManager()->getPermission($perm->getName());
return PermissionManager::getInstance()->getPermission($perm->getName());
}
public static function registerCorePermissions(){

View File

@ -25,7 +25,6 @@ namespace pocketmine\permission;
use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginException;
use pocketmine\Server;
use pocketmine\timings\Timings;
class PermissibleBase implements Permissible{
@ -92,7 +91,7 @@ class PermissibleBase implements Permissible{
return $this->permissions[$name]->getValue();
}
if(($perm = Server::getInstance()->getPluginManager()->getPermission($name)) !== null){
if(($perm = PermissionManager::getInstance()->getPermission($name)) !== null){
$perm = $perm->getDefault();
return $perm === Permission::DEFAULT_TRUE or ($this->isOp() and $perm === Permission::DEFAULT_OP) or (!$this->isOp() and $perm === Permission::DEFAULT_NOT_OP);
@ -147,13 +146,14 @@ class PermissibleBase implements Permissible{
Timings::$permissibleCalculationTimer->startTiming();
$this->clearPermissions();
$defaults = Server::getInstance()->getPluginManager()->getDefaultPermissions($this->isOp());
Server::getInstance()->getPluginManager()->subscribeToDefaultPerms($this->isOp(), $this->parent ?? $this);
$permManager = PermissionManager::getInstance();
$defaults = $permManager->getDefaultPermissions($this->isOp());
$permManager->subscribeToDefaultPerms($this->isOp(), $this->parent ?? $this);
foreach($defaults as $perm){
$name = $perm->getName();
$this->permissions[$name] = new PermissionAttachmentInfo($this->parent ?? $this, $name, null, true);
Server::getInstance()->getPluginManager()->subscribeToPermission($name, $this->parent ?? $this);
$permManager->subscribeToPermission($name, $this->parent ?? $this);
$this->calculateChildPermissions($perm->getChildren(), false, null);
}
@ -165,13 +165,13 @@ class PermissibleBase implements Permissible{
}
public function clearPermissions(){
$pluginManager = Server::getInstance()->getPluginManager();
$permManager = PermissionManager::getInstance();
foreach(array_keys($this->permissions) as $name){
$pluginManager->unsubscribeFromPermission($name, $this->parent ?? $this);
$permManager->unsubscribeFromPermission($name, $this->parent ?? $this);
}
$pluginManager->unsubscribeFromDefaultPerms(false, $this->parent ?? $this);
$pluginManager->unsubscribeFromDefaultPerms(true, $this->parent ?? $this);
$permManager->unsubscribeFromDefaultPerms(false, $this->parent ?? $this);
$permManager->unsubscribeFromDefaultPerms(true, $this->parent ?? $this);
$this->permissions = [];
}
@ -182,11 +182,12 @@ class PermissibleBase implements Permissible{
* @param PermissionAttachment|null $attachment
*/
private function calculateChildPermissions(array $children, bool $invert, ?PermissionAttachment $attachment){
$permManager = PermissionManager::getInstance();
foreach($children as $name => $v){
$perm = Server::getInstance()->getPluginManager()->getPermission($name);
$perm = $permManager->getPermission($name);
$value = ($v xor $invert);
$this->permissions[$name] = new PermissionAttachmentInfo($this->parent ?? $this, $name, $attachment, $value);
Server::getInstance()->getPluginManager()->subscribeToPermission($name, $this->parent ?? $this);
$permManager->subscribeToPermission($name, $this->parent ?? $this);
if($perm instanceof Permission){
$this->calculateChildPermissions($perm->getChildren(), !$value, $attachment);

View File

@ -27,8 +27,6 @@ declare(strict_types=1);
namespace pocketmine\permission;
use pocketmine\Server;
/**
* Represents a permission
*/
@ -78,6 +76,65 @@ class Permission{
}
}
/**
* @param array $data
* @param string $default
*
* @return Permission[]
*/
public static function loadPermissions(array $data, string $default = self::DEFAULT_OP) : array{
$result = [];
foreach($data as $key => $entry){
$result[] = self::loadPermission($key, $entry, $default, $result);
}
return $result;
}
/**
* @param string $name
* @param array $data
* @param string $default
* @param array $output
*
* @return Permission
*
* @throws \Exception
*/
public static function loadPermission(string $name, array $data, string $default = self::DEFAULT_OP, array &$output = []) : Permission{
$desc = null;
$children = [];
if(isset($data["default"])){
$value = Permission::getByName($data["default"]);
if($value !== null){
$default = $value;
}else{
throw new \InvalidStateException("'default' key contained unknown value");
}
}
if(isset($data["children"])){
if(is_array($data["children"])){
foreach($data["children"] as $k => $v){
if(is_array($v)){
if(($perm = self::loadPermission($k, $v, $default, $output)) !== null){
$output[] = $perm;
}
}
$children[$k] = true;
}
}else{
throw new \InvalidStateException("'children' key is of wrong type");
}
}
if(isset($data["description"])){
$desc = $data["description"];
}
return new Permission($name, $desc, $default, $children);
}
/** @var string */
private $name;
@ -158,13 +215,13 @@ class Permission{
* @return Permissible[]
*/
public function getPermissibles() : array{
return Server::getInstance()->getPluginManager()->getPermissionSubscriptions($this->name);
return PermissionManager::getInstance()->getPermissionSubscriptions($this->name);
}
public function recalculatePermissibles(){
$perms = $this->getPermissibles();
Server::getInstance()->getPluginManager()->recalculatePermissionDefaults($this);
PermissionManager::getInstance()->recalculatePermissionDefaults($this);
foreach($perms as $p){
$p->recalculatePermissions();
@ -184,10 +241,10 @@ class Permission{
$name->recalculatePermissibles();
return null;
}else{
$perm = Server::getInstance()->getPluginManager()->getPermission($name);
$perm = PermissionManager::getInstance()->getPermission($name);
if($perm === null){
$perm = new Permission($name);
Server::getInstance()->getPluginManager()->addPermission($perm);
PermissionManager::getInstance()->addPermission($perm);
}
$this->addParent($perm, $value);
@ -195,64 +252,4 @@ class Permission{
return $perm;
}
}
/**
* @param array $data
* @param string $default
*
* @return Permission[]
*/
public static function loadPermissions(array $data, string $default = self::DEFAULT_OP) : array{
$result = [];
foreach($data as $key => $entry){
$result[] = self::loadPermission($key, $entry, $default, $result);
}
return $result;
}
/**
* @param string $name
* @param array $data
* @param string $default
* @param array $output
*
* @return Permission
*
* @throws \Exception
*/
public static function loadPermission(string $name, array $data, string $default = self::DEFAULT_OP, array &$output = []) : Permission{
$desc = null;
$children = [];
if(isset($data["default"])){
$value = Permission::getByName($data["default"]);
if($value !== null){
$default = $value;
}else{
throw new \InvalidStateException("'default' key contained unknown value");
}
}
if(isset($data["children"])){
if(is_array($data["children"])){
foreach($data["children"] as $k => $v){
if(is_array($v)){
if(($perm = self::loadPermission($k, $v, $default, $output)) !== null){
$output[] = $perm;
}
}
$children[$k] = true;
}
}else{
throw new \InvalidStateException("'children' key is of wrong type");
}
}
if(isset($data["description"])){
$desc = $data["description"];
}
return new Permission($name, $desc, $default, $children);
}
}

View File

@ -0,0 +1,221 @@
<?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\permission;
use pocketmine\timings\Timings;
class PermissionManager{
/** @var PermissionManager|null */
private static $instance = null;
public static function getInstance() : PermissionManager{
if(self::$instance === null){
self::$instance = new self;
}
return self::$instance;
}
/** @var Permission[] */
protected $permissions = [];
/** @var Permission[] */
protected $defaultPerms = [];
/** @var Permission[] */
protected $defaultPermsOp = [];
/** @var Permissible[][] */
protected $permSubs = [];
/** @var Permissible[] */
protected $defSubs = [];
/** @var Permissible[] */
protected $defSubsOp = [];
/**
* @param string $name
*
* @return null|Permission
*/
public function getPermission(string $name){
return $this->permissions[$name] ?? null;
}
/**
* @param Permission $permission
*
* @return bool
*/
public function addPermission(Permission $permission) : bool{
if(!isset($this->permissions[$permission->getName()])){
$this->permissions[$permission->getName()] = $permission;
$this->calculatePermissionDefault($permission);
return true;
}
return false;
}
/**
* @param string|Permission $permission
*/
public function removePermission($permission){
if($permission instanceof Permission){
unset($this->permissions[$permission->getName()]);
}else{
unset($this->permissions[$permission]);
}
}
/**
* @param bool $op
*
* @return Permission[]
*/
public function getDefaultPermissions(bool $op) : array{
if($op){
return $this->defaultPermsOp;
}else{
return $this->defaultPerms;
}
}
/**
* @param Permission $permission
*/
public function recalculatePermissionDefaults(Permission $permission){
if(isset($this->permissions[$permission->getName()])){
unset($this->defaultPermsOp[$permission->getName()]);
unset($this->defaultPerms[$permission->getName()]);
$this->calculatePermissionDefault($permission);
}
}
/**
* @param Permission $permission
*/
private function calculatePermissionDefault(Permission $permission){
Timings::$permissionDefaultTimer->startTiming();
if($permission->getDefault() === Permission::DEFAULT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
$this->defaultPermsOp[$permission->getName()] = $permission;
$this->dirtyPermissibles(true);
}
if($permission->getDefault() === Permission::DEFAULT_NOT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
$this->defaultPerms[$permission->getName()] = $permission;
$this->dirtyPermissibles(false);
}
Timings::$permissionDefaultTimer->startTiming();
}
/**
* @param bool $op
*/
private function dirtyPermissibles(bool $op){
foreach($this->getDefaultPermSubscriptions($op) as $p){
$p->recalculatePermissions();
}
}
/**
* @param string $permission
* @param Permissible $permissible
*/
public function subscribeToPermission(string $permission, Permissible $permissible){
if(!isset($this->permSubs[$permission])){
$this->permSubs[$permission] = [];
}
$this->permSubs[$permission][spl_object_hash($permissible)] = $permissible;
}
/**
* @param string $permission
* @param Permissible $permissible
*/
public function unsubscribeFromPermission(string $permission, Permissible $permissible){
if(isset($this->permSubs[$permission])){
unset($this->permSubs[$permission][spl_object_hash($permissible)]);
if(count($this->permSubs[$permission]) === 0){
unset($this->permSubs[$permission]);
}
}
}
/**
* @param string $permission
*
* @return array|Permissible[]
*/
public function getPermissionSubscriptions(string $permission) : array{
return $this->permSubs[$permission] ?? [];
}
/**
* @param bool $op
* @param Permissible $permissible
*/
public function subscribeToDefaultPerms(bool $op, Permissible $permissible){
if($op){
$this->defSubsOp[spl_object_hash($permissible)] = $permissible;
}else{
$this->defSubs[spl_object_hash($permissible)] = $permissible;
}
}
/**
* @param bool $op
* @param Permissible $permissible
*/
public function unsubscribeFromDefaultPerms(bool $op, Permissible $permissible){
if($op){
unset($this->defSubsOp[spl_object_hash($permissible)]);
}else{
unset($this->defSubs[spl_object_hash($permissible)]);
}
}
/**
* @param bool $op
*
* @return Permissible[]
*/
public function getDefaultPermSubscriptions(bool $op) : array{
if($op){
return $this->defSubsOp;
}
return $this->defSubs;
}
/**
* @return Permission[]
*/
public function getPermissions() : array{
return $this->permissions;
}
public function clearPermissions() : void{
$this->permissions = [];
$this->defaultPerms = [];
$this->defaultPermsOp = [];
}
}

View File

@ -256,12 +256,8 @@ abstract class PluginBase implements Plugin{
}
public function reloadConfig(){
@mkdir($this->dataFolder);
$this->saveDefaultConfig();
$this->config = new Config($this->configFile);
if(($configStream = $this->getResource("config.yml")) !== null){
$this->config->setDefaults(yaml_parse(Config::fixYAMLIndexes(stream_get_contents($configStream))));
fclose($configStream);
}
}
/**

View File

@ -34,13 +34,13 @@ use pocketmine\event\plugin\PluginEnableEvent;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\permission\Permissible;
use pocketmine\permission\Permission;
use pocketmine\permission\PermissionManager;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\Utils;
/**
* Manages all the plugins, Permissions and Permissibles
* Manages all the plugins
*/
class PluginManager{
private const MAX_EVENT_CALL_DEPTH = 50;
@ -61,36 +61,6 @@ class PluginManager{
*/
protected $enabledPlugins = [];
/**
* @var Permission[]
*/
protected $permissions = [];
/**
* @var Permission[]
*/
protected $defaultPerms = [];
/**
* @var Permission[]
*/
protected $defaultPermsOp = [];
/**
* @var Permissible[][]
*/
protected $permSubs = [];
/**
* @var Permissible[]
*/
protected $defSubs = [];
/**
* @var Permissible[]
*/
protected $defSubsOp = [];
/**
* @var PluginLoader[]
*/
@ -178,6 +148,9 @@ class PluginManager{
$this->server->getLogger()->error("Projected dataFolder '" . $dataFolder . "' for " . $description->getName() . " exists and is not a directory");
return null;
}
if(!file_exists($dataFolder)){
mkdir($dataFolder, 0777, true);
}
$prefixed = $loader->getAccessProtocol() . $path;
$loader->loadPlugin($prefixed);
@ -425,166 +398,137 @@ class PluginManager{
}
/**
* @deprecated
* @see PermissionManager::getPermission()
*
* @param string $name
*
* @return null|Permission
*/
public function getPermission(string $name){
return $this->permissions[$name] ?? null;
return PermissionManager::getInstance()->getPermission($name);
}
/**
* @deprecated
* @see PermissionManager::addPermission()
*
* @param Permission $permission
*
* @return bool
*/
public function addPermission(Permission $permission) : bool{
if(!isset($this->permissions[$permission->getName()])){
$this->permissions[$permission->getName()] = $permission;
$this->calculatePermissionDefault($permission);
return true;
}
return false;
return PermissionManager::getInstance()->addPermission($permission);
}
/**
* @deprecated
* @see PermissionManager::removePermission()
*
* @param string|Permission $permission
*/
public function removePermission($permission){
if($permission instanceof Permission){
unset($this->permissions[$permission->getName()]);
}else{
unset($this->permissions[$permission]);
}
PermissionManager::getInstance()->removePermission($permission);
}
/**
* @deprecated
* @see PermissionManager::getDefaultPermissions()
*
* @param bool $op
*
* @return Permission[]
*/
public function getDefaultPermissions(bool $op) : array{
if($op){
return $this->defaultPermsOp;
}else{
return $this->defaultPerms;
}
return PermissionManager::getInstance()->getDefaultPermissions($op);
}
/**
* @deprecated
* @see PermissionManager::recalculatePermissionDefaults()
*
* @param Permission $permission
*/
public function recalculatePermissionDefaults(Permission $permission){
if(isset($this->permissions[$permission->getName()])){
unset($this->defaultPermsOp[$permission->getName()]);
unset($this->defaultPerms[$permission->getName()]);
$this->calculatePermissionDefault($permission);
}
}
/**
* @param Permission $permission
*/
private function calculatePermissionDefault(Permission $permission){
Timings::$permissionDefaultTimer->startTiming();
if($permission->getDefault() === Permission::DEFAULT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
$this->defaultPermsOp[$permission->getName()] = $permission;
$this->dirtyPermissibles(true);
}
if($permission->getDefault() === Permission::DEFAULT_NOT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){
$this->defaultPerms[$permission->getName()] = $permission;
$this->dirtyPermissibles(false);
}
Timings::$permissionDefaultTimer->startTiming();
}
/**
* @param bool $op
*/
private function dirtyPermissibles(bool $op){
foreach($this->getDefaultPermSubscriptions($op) as $p){
$p->recalculatePermissions();
}
PermissionManager::getInstance()->recalculatePermissionDefaults($permission);
}
/**
* @deprecated
* @see PermissionManager::subscribeToPermission()
*
* @param string $permission
* @param Permissible $permissible
*/
public function subscribeToPermission(string $permission, Permissible $permissible){
if(!isset($this->permSubs[$permission])){
$this->permSubs[$permission] = [];
}
$this->permSubs[$permission][spl_object_hash($permissible)] = $permissible;
PermissionManager::getInstance()->subscribeToPermission($permission, $permissible);
}
/**
* @deprecated
* @see PermissionManager::unsubscribeFromPermission()
*
* @param string $permission
* @param Permissible $permissible
*/
public function unsubscribeFromPermission(string $permission, Permissible $permissible){
if(isset($this->permSubs[$permission])){
unset($this->permSubs[$permission][spl_object_hash($permissible)]);
if(count($this->permSubs[$permission]) === 0){
unset($this->permSubs[$permission]);
}
}
PermissionManager::getInstance()->unsubscribeFromPermission($permission, $permissible);
}
/**
* @deprecated
* @see PermissionManager::getPermissionSubscriptions()
*
* @param string $permission
*
* @return array|Permissible[]
*/
public function getPermissionSubscriptions(string $permission) : array{
return $this->permSubs[$permission] ?? [];
return PermissionManager::getInstance()->getPermissionSubscriptions($permission);
}
/**
* @deprecated
* @see PermissionManager::subscribeToDefaultPerms()
*
* @param bool $op
* @param Permissible $permissible
*/
public function subscribeToDefaultPerms(bool $op, Permissible $permissible){
if($op){
$this->defSubsOp[spl_object_hash($permissible)] = $permissible;
}else{
$this->defSubs[spl_object_hash($permissible)] = $permissible;
}
PermissionManager::getInstance()->subscribeToDefaultPerms($op, $permissible);
}
/**
* @deprecated
* @see PermissionManager::unsubscribeFromDefaultPerms()
*
* @param bool $op
* @param Permissible $permissible
*/
public function unsubscribeFromDefaultPerms(bool $op, Permissible $permissible){
if($op){
unset($this->defSubsOp[spl_object_hash($permissible)]);
}else{
unset($this->defSubs[spl_object_hash($permissible)]);
}
PermissionManager::getInstance()->unsubscribeFromDefaultPerms($op, $permissible);
}
/**
* @deprecated
* @see PermissionManager::getDefaultPermSubscriptions()
*
* @param bool $op
*
* @return Permissible[]
*/
public function getDefaultPermSubscriptions(bool $op) : array{
if($op){
return $this->defSubsOp;
}
return $this->defSubs;
return PermissionManager::getInstance()->getDefaultPermSubscriptions($op);
}
/**
* @deprecated
* @see PermissionManager::getPermissions()
*
* @return Permission[]
*/
public function getPermissions() : array{
return $this->permissions;
return PermissionManager::getInstance()->getPermissions();
}
/**
@ -604,8 +548,9 @@ class PluginManager{
try{
$this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.enable", [$plugin->getDescription()->getFullName()]));
$permManager = PermissionManager::getInstance();
foreach($plugin->getDescription()->getPermissions() as $perm){
$this->addPermission($perm);
$permManager->addPermission($perm);
}
$plugin->getScheduler()->setEnabled(true);
$plugin->setEnabled(true);
@ -700,8 +645,9 @@ class PluginManager{
}
$plugin->getScheduler()->shutdown();
HandlerList::unregisterAll($plugin);
$permManager = PermissionManager::getInstance();
foreach($plugin->getDescription()->getPermissions() as $perm){
$this->removePermission($perm);
$permManager->removePermission($perm);
}
}
}
@ -717,9 +663,6 @@ class PluginManager{
$this->plugins = [];
$this->enabledPlugins = [];
$this->fileAssociations = [];
$this->permissions = [];
$this->defaultPerms = [];
$this->defaultPermsOp = [];
}
/**
@ -791,7 +734,21 @@ class PluginManager{
}catch(\InvalidArgumentException $e){
throw new PluginException("Event handler " . get_class($listener) . "->" . $method->getName() . "() declares invalid/unknown priority \"" . $tags["priority"] . "\"");
}
$ignoreCancelled = isset($tags["ignoreCancelled"]) && strtolower($tags["ignoreCancelled"]) === "true";
$ignoreCancelled = false;
if(isset($tags["ignoreCancelled"])){
switch(strtolower($tags["ignoreCancelled"])){
case "true":
case "":
$ignoreCancelled = true;
break;
case "false":
$ignoreCancelled = false;
break;
default:
throw new PluginException("Event handler " . get_class($listener) . "->" . $method->getName() . "() declares invalid @ignoreCancelled value \"" . $tags["ignoreCancelled"] . "\"");
}
}
$parameters = $method->getParameters();
try{

File diff suppressed because one or more lines are too long

View File

@ -168,18 +168,15 @@ class AsyncPool{
}
/**
* Submits an AsyncTask to the worker with the least load. If all workers are busy and the pool is not full, a new
* worker may be started.
* Selects a worker ID to run a task.
*
* @param AsyncTask $task
* - if an idle worker is found, it will be selected
* - else, if the worker pool is not full, a new worker will be selected
* - else, the worker with the smallest backlog is chosen.
*
* @return int
*/
public function submitTask(AsyncTask $task) : int{
if($task->getTaskId() !== null){
throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once");
}
public function selectWorker() : int{
$worker = null;
$minUsage = PHP_INT_MAX;
foreach($this->workerUsage as $i => $usage){
@ -202,7 +199,23 @@ class AsyncPool{
}
assert($worker !== null);
return $worker;
}
/**
* Submits an AsyncTask to the worker with the least load. If all workers are busy and the pool is not full, a new
* worker may be started.
*
* @param AsyncTask $task
*
* @return int
*/
public function submitTask(AsyncTask $task) : int{
if($task->getTaskId() !== null){
throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once");
}
$worker = $this->selectWorker();
$this->submitTaskToWorker($task, $worker);
return $worker;
}
@ -304,6 +317,19 @@ class AsyncPool{
$this->collectWorkers();
}
public function shutdownUnusedWorkers() : int{
$ret = 0;
foreach($this->workerUsage as $i => $usage){
if($usage === 0){
$this->workers[$i]->quit();
unset($this->workers[$i], $this->workerUsage[$i]);
$ret++;
}
}
return $ret;
}
/**
* Cancels all pending tasks and shuts down all the workers in the pool.
*/

View File

@ -23,12 +23,13 @@ declare(strict_types=1);
namespace pocketmine\scheduler;
use pocketmine\utils\Utils;
use pocketmine\utils\Internet;
use pocketmine\utils\InternetException;
/**
* Executes a consecutive list of cURL operations.
*
* The result of this AsyncTask is an array of arrays (returned from {@link Utils::simpleCurl}) or RuntimeException objects.
* The result of this AsyncTask is an array of arrays (returned from {@link Utils::simpleCurl}) or InternetException objects.
*
* @package pocketmine\scheduler
*/
@ -55,8 +56,8 @@ class BulkCurlTask extends AsyncTask{
$results = [];
foreach($operations as $op){
try{
$results[] = Utils::simpleCurl($op["page"], $op["timeout"] ?? 10, $op["extraHeaders"] ?? [], $op["extraOpts"] ?? []);
}catch(\RuntimeException $e){
$results[] = Internet::simpleCurl($op["page"], $op["timeout"] ?? 10, $op["extraHeaders"] ?? [], $op["extraOpts"] ?? []);
}catch(InternetException $e){
$results[] = $e;
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\scheduler;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\Server;
use pocketmine\utils\Internet;
use pocketmine\utils\Utils;
use pocketmine\utils\UUID;
use pocketmine\utils\VersionString;
@ -147,7 +148,7 @@ class SendUsageTask extends AsyncTask{
public function onRun(){
try{
Utils::postURL($this->endpoint, $this->data, 5, [
Internet::postURL($this->endpoint, $this->data, 5, [
"Content-Type: application/json",
"Content-Length: " . strlen($this->data)
]);

View File

@ -209,6 +209,9 @@ abstract class Tile extends Position{
* @return CompoundTag
*/
public static function createNBT(Vector3 $pos, ?int $face = null, ?Item $item = null, ?Player $player = null) : CompoundTag{
if(static::class === self::class){
throw new \BadMethodCallException(__METHOD__ . " must be called from the scope of a child class");
}
$nbt = new CompoundTag("", [
new StringTag(self::TAG_ID, static::getSaveId()),
new IntTag(self::TAG_X, (int) $pos->x),

View File

@ -27,7 +27,7 @@ namespace pocketmine\updater;
use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;
use pocketmine\utils\Utils;
use pocketmine\utils\Internet;
class UpdateCheckTask extends AsyncTask{
@ -45,7 +45,7 @@ class UpdateCheckTask extends AsyncTask{
public function onRun(){
$error = "";
$response = Utils::getURL($this->endpoint . "?channel=" . $this->channel, 4, [], $error);
$response = Internet::getURL($this->endpoint . "?channel=" . $this->channel, 4, [], $error);
$this->error = $error;
if($response !== false){

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\utils;
use pocketmine\scheduler\FileWriteTask;
use pocketmine\Server;
@ -186,11 +185,9 @@ class Config{
}
/**
* @param bool $async
*
* @return bool
*/
public function save(bool $async = false) : bool{
public function save() : bool{
if($this->correct){
try{
$content = null;
@ -214,11 +211,7 @@ class Config{
throw new \InvalidStateException("Config type is unknown, has not been set or not detected");
}
if($async){
Server::getInstance()->getAsyncPool()->submitTask(new FileWriteTask($this->file, $content));
}else{
file_put_contents($this->file, $content);
}
file_put_contents($this->file, $content);
}catch(\Throwable $e){
$logger = Server::getInstance()->getLogger();
$logger->critical("Could not save Config " . $this->file . ": " . $e->getMessage());

View File

@ -0,0 +1,185 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
class Internet{
public static $ip = false;
public static $online = true;
/**
* Gets the External IP using an external service, it is cached
*
* @param bool $force default false, force IP check even when cached
*
* @return string|bool
*/
public static function getIP(bool $force = false){
if(!self::$online){
return false;
}elseif(self::$ip !== false and !$force){
return self::$ip;
}
$ip = self::getURL("http://api.ipify.org/");
if($ip !== false){
return self::$ip = $ip;
}
$ip = self::getURL("http://checkip.dyndns.org/");
if($ip !== false and preg_match('#Current IP Address\: ([0-9a-fA-F\:\.]*)#', trim(strip_tags($ip)), $matches) > 0){
return self::$ip = $matches[1];
}
$ip = self::getURL("http://www.checkip.org/");
if($ip !== false and preg_match('#">([0-9a-fA-F\:\.]*)</span>#', $ip, $matches) > 0){
return self::$ip = $matches[1];
}
$ip = self::getURL("http://checkmyip.org/");
if($ip !== false and preg_match('#Your IP address is ([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){
return self::$ip = $matches[1];
}
$ip = self::getURL("http://ifconfig.me/ip");
if($ip !== false and trim($ip) != ""){
return self::$ip = trim($ip);
}
return false;
}
/**
* GETs an URL using cURL
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
*
* @param string $page
* @param int $timeout default 10
* @param array $extraHeaders
* @param string &$err Will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
* @param array[] &$headers
* @param int &$httpCode
*
* @return bool|mixed false if an error occurred, mixed data if successful.
*/
public static function getURL(string $page, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
try{
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders);
return $ret;
}catch(InternetException $ex){
$err = $ex->getMessage();
return false;
}
}
/**
* POSTs data to an URL
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
*
* @param string $page
* @param array|string $args
* @param int $timeout
* @param array $extraHeaders
* @param string &$err Will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
* @param array[] &$headers
* @param int &$httpCode
*
* @return bool|mixed false if an error occurred, mixed data if successful.
*/
public static function postURL(string $page, $args, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
try{
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders, [
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $args
]);
return $ret;
}catch(InternetException $ex){
$err = $ex->getMessage();
return false;
}
}
/**
* General cURL shorthand function.
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
*
* @param string $page
* @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms.
* @param string[] $extraHeaders extra headers to send as a plain string array
* @param array $extraOpts extra CURLOPT_* to set as an [opt => value] map
* @param callable|null $onSuccess function to be called if there is no error. Accepts a resource argument as the cURL handle.
*
* @return array a plain array of three [result body : string, headers : array[], HTTP response code : int]. Headers are grouped by requests with strtolower(header name) as keys and header value as values
*
* @throws InternetException if a cURL error occurs
*/
public static function simpleCurl(string $page, $timeout = 10, array $extraHeaders = [], array $extraOpts = [], callable $onSuccess = null){
if(!self::$online){
throw new InternetException("Cannot execute web request while offline");
}
$ch = curl_init($page);
curl_setopt_array($ch, $extraOpts + [
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_AUTOREFERER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT_MS => (int) ($timeout * 1000),
CURLOPT_TIMEOUT_MS => (int) ($timeout * 1000),
CURLOPT_HTTPHEADER => array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 " . \pocketmine\NAME], $extraHeaders),
CURLOPT_HEADER => true
]);
try{
$raw = curl_exec($ch);
$error = curl_error($ch);
if($error !== ""){
throw new InternetException($error);
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$rawHeaders = substr($raw, 0, $headerSize);
$body = substr($raw, $headerSize);
$headers = [];
foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){
$headerGroup = [];
foreach(explode("\r\n", $rawHeaderGroup) as $line){
$nameValue = explode(":", $line, 2);
if(isset($nameValue[1])){
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);
}
}
$headers[] = $headerGroup;
}
if($onSuccess !== null){
$onSuccess($ch);
}
return [$body, $headers, $httpCode];
}finally{
curl_close($ch);
}
}
}

View File

@ -0,0 +1,28 @@
<?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\utils;
class InternetException extends \RuntimeException{
}

View File

@ -61,7 +61,7 @@ abstract class Timezone{
break;
}
if($response = Utils::getURL("http://ip-api.com/json") //If system timezone detection fails or timezone is an invalid value.
if($response = Internet::getURL("http://ip-api.com/json") //If system timezone detection fails or timezone is an invalid value.
and $ip_geolocation_data = json_decode($response, true)
and $ip_geolocation_data['status'] !== 'fail'
and date_default_timezone_set($ip_geolocation_data['timezone'])

View File

@ -33,8 +33,6 @@ use pocketmine\ThreadManager;
* Big collection of functions
*/
class Utils{
public static $online = true;
public static $ip = false;
public static $os;
/** @var UUID|null */
private static $serverUniqueId = null;
@ -123,55 +121,15 @@ class Utils{
}
/**
* Gets the External IP using an external service, it is cached
* @deprecated
* @see Internet::getIP()
*
* @param bool $force default false, force IP check even when cached
*
* @return string|bool
*/
public static function getIP(bool $force = false){
if(!Utils::$online){
return false;
}elseif(Utils::$ip !== false and !$force){
return Utils::$ip;
}
do{
$ip = Utils::getURL("http://api.ipify.org/");
if($ip !== false){
Utils::$ip = $ip;
break;
}
$ip = Utils::getURL("http://checkip.dyndns.org/");
if($ip !== false and preg_match('#Current IP Address\: ([0-9a-fA-F\:\.]*)#', trim(strip_tags($ip)), $matches) > 0){
Utils::$ip = $matches[1];
break;
}
$ip = Utils::getURL("http://www.checkip.org/");
if($ip !== false and preg_match('#">([0-9a-fA-F\:\.]*)</span>#', $ip, $matches) > 0){
Utils::$ip = $matches[1];
break;
}
$ip = Utils::getURL("http://checkmyip.org/");
if($ip !== false and preg_match('#Your IP address is ([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){
Utils::$ip = $matches[1];
break;
}
$ip = Utils::getURL("http://ifconfig.me/ip");
if($ip !== false and trim($ip) != ""){
Utils::$ip = trim($ip);
break;
}
return false;
}while(false);
return Utils::$ip;
return Internet::getIP($force);
}
/**
@ -373,8 +331,8 @@ class Utils{
}*/
/**
* GETs an URL using cURL
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
* @deprecated
* @see Internet::getURL()
*
* @param string $page
* @param int $timeout default 10
@ -386,18 +344,12 @@ class Utils{
* @return bool|mixed false if an error occurred, mixed data if successful.
*/
public static function getURL(string $page, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
try{
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders);
return $ret;
}catch(\RuntimeException $ex){
$err = $ex->getMessage();
return false;
}
return Internet::getURL($page, $timeout, $extraHeaders, $err, $headers, $httpCode);
}
/**
* POSTs data to an URL
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
* @deprecated
* @see Internet::postURL()
*
* @param string $page
* @param array|string $args
@ -410,22 +362,12 @@ class Utils{
* @return bool|mixed false if an error occurred, mixed data if successful.
*/
public static function postURL(string $page, $args, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
try{
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders, [
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $args
]);
return $ret;
}catch(\RuntimeException $ex){
$err = $ex->getMessage();
return false;
}
return Internet::postURL($page, $args, $timeout, $extraHeaders, $err, $headers, $httpCode);
}
/**
* General cURL shorthand function.
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
* @deprecated
* @see Internet::simpleCurl()
*
* @param string $page
* @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms.
@ -438,53 +380,7 @@ class Utils{
* @throws \RuntimeException if a cURL error occurs
*/
public static function simpleCurl(string $page, $timeout = 10, array $extraHeaders = [], array $extraOpts = [], callable $onSuccess = null){
if(!Utils::$online){
throw new \RuntimeException("Server is offline");
}
$ch = curl_init($page);
curl_setopt_array($ch, $extraOpts + [
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_AUTOREFERER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT_MS => (int) ($timeout * 1000),
CURLOPT_TIMEOUT_MS => (int) ($timeout * 1000),
CURLOPT_HTTPHEADER => array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 " . \pocketmine\NAME], $extraHeaders),
CURLOPT_HEADER => true
]);
try{
$raw = curl_exec($ch);
$error = curl_error($ch);
if($error !== ""){
throw new \RuntimeException($error);
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$rawHeaders = substr($raw, 0, $headerSize);
$body = substr($raw, $headerSize);
$headers = [];
foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){
$headerGroup = [];
foreach(explode("\r\n", $rawHeaderGroup) as $line){
$nameValue = explode(":", $line, 2);
if(isset($nameValue[1])){
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);
}
}
$headers[] = $headerGroup;
}
if($onSuccess !== null){
$onSuccess($ch);
}
return [$body, $headers, $httpCode];
}finally{
curl_close($ch);
}
return Internet::simpleCurl($page, $timeout, $extraHeaders, $extraOpts, $onSuccess);
}
public static function javaStringHash(string $string) : int{

View File

@ -29,7 +29,7 @@ namespace pocketmine\wizard;
use pocketmine\lang\BaseLang;
use pocketmine\utils\Config;
use pocketmine\utils\Utils;
use pocketmine\utils\Internet;
class SetupWizard{
public const DEFAULT_NAME = \pocketmine\NAME . " Server";
@ -91,7 +91,7 @@ class SetupWizard{
}
private function showLicense() : bool{
$this->message($this->lang->get("welcome_to_pocketmine"));
$this->message($this->lang->translateString("welcome_to_pocketmine", [\pocketmine\NAME]));
echo <<<LICENSE
This program is free software: you can redistribute it and/or modify
@ -102,7 +102,7 @@ class SetupWizard{
LICENSE;
$this->writeLine();
if(strtolower($this->getInput($this->lang->get("accept_license"), "n", "y/N")) !== "y"){
$this->error($this->lang->get("you_have_to_accept_the_license"));
$this->error($this->lang->translateString("you_have_to_accept_the_license", [\pocketmine\NAME]));
sleep(5);
return false;
@ -205,7 +205,7 @@ LICENSE;
$this->message($this->lang->get("ip_get"));
$externalIP = Utils::getIP();
$externalIP = Internet::getIP();
if($externalIP === false){
$externalIP = "unknown (server offline)";
}
@ -219,7 +219,7 @@ LICENSE;
private function endWizard(){
$this->message($this->lang->get("you_have_finished"));
$this->message($this->lang->get("pocketmine_plugins"));
$this->message($this->lang->get("pocketmine_will_start"));
$this->message($this->lang->translateString("pocketmine_will_start", [\pocketmine\NAME]));
$this->writeLine();
$this->writeLine();

View File

@ -51,4 +51,29 @@ class ItemTest extends TestCase{
self::assertEquals(BlockFactory::isRegistered($id), ItemFactory::isRegistered($id));
}
}
public function itemFromStringProvider() : array{
return [
["dye:4", ItemIds::DYE, 4],
["351", ItemIds::DYE, 0],
["351:4", ItemIds::DYE, 4],
["stone:3", ItemIds::STONE, 3],
["minecraft:string", ItemIds::STRING, 0],
["diamond_pickaxe", ItemIds::DIAMOND_PICKAXE, 0],
["diamond_pickaxe:5", ItemIds::DIAMOND_PICKAXE, 5]
];
}
/**
* @dataProvider itemFromStringProvider
* @param string $string
* @param int $id
* @param int $meta
*/
public function testFromStringSingle(string $string, int $id, int $meta) : void{
$item = ItemFactory::fromString($string);
self::assertEquals($id, $item->getId());
self::assertEquals($meta, $item->getDamage());
}
}

View File

@ -0,0 +1,28 @@
<?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\level\format\io;
abstract class AbstractLevelProvider implements LevelProvider{
}

View File

@ -0,0 +1,28 @@
<?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\level\format\io;
interface InterfaceLevelProvider extends LevelProvider{
}

View File

@ -0,0 +1,47 @@
<?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\level\format\io;
use PHPUnit\Framework\TestCase;
class LevelProviderManagerTest extends TestCase{
public function testAddNonClassProvider() : void{
$this->expectException(\InvalidArgumentException::class);
LevelProviderManager::addProvider("lol");
}
public function testAddAbstractClassProvider() : void{
$this->expectException(\InvalidArgumentException::class);
LevelProviderManager::addProvider(AbstractLevelProvider::class);
}
public function testAddInterfaceProvider() : void{
$this->expectException(\InvalidArgumentException::class);
LevelProviderManager::addProvider(InterfaceLevelProvider::class);
}
}

View File

@ -0,0 +1,56 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe;
use PHPUnit\Framework\TestCase;
class StupidJsonDecodeTest extends TestCase{
public function stupidJsonDecodeProvider() : array{
return [
["[\n \"a\",\"b,c,d,e\\\" \",,0,1,2, false, 0.001]", ['a', 'b,c,d,e" ', '', 0, 1, 2, false, 0.001]],
["0", 0],
["false", false],
["NULL", null],
['["\",,\"word","a\",,\"word2",]', ['",,"word', 'a",,"word2', '']],
['["\",,\"word","a\",,\"word2",""]', ['",,"word', 'a",,"word2', '']]
];
}
/**
* @dataProvider stupidJsonDecodeProvider
*
* @param string $brokenJson
* @param mixed $expect
*
* @throws \ReflectionException
*/
public function testStupidJsonDecode(string $brokenJson, $expect){
$func = new \ReflectionMethod(PlayerNetworkSessionAdapter::class, 'stupid_json_decode');
$func->setAccessible(true);
$decoded = $func->invoke(null, $brokenJson, true);
self::assertEquals($expect, $decoded);
}
}

View File

@ -1,7 +1,7 @@
name: TesterPlugin
main: pmmp\TesterPlugin\Main
version: 0.1.0
api: [3.0.0]
api: [3.2.0]
load: POSTWORLD
author: pmmp
description: Plugin used to run tests on PocketMine-MP

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace pmmp\TesterPlugin;
use pocketmine\event\Listener;
use pocketmine\event\server\ServerCommandEvent;
use pocketmine\event\server\CommandEvent;
use pocketmine\plugin\PluginBase;
class Main extends PluginBase implements Listener{
@ -48,7 +48,7 @@ class Main extends PluginBase implements Listener{
];
}
public function onServerCommand(ServerCommandEvent $event){
public function onServerCommand(CommandEvent $event){
//The CI will send this command as a failsafe to prevent the build from hanging if the tester plugin failed to
//run. However, if the plugin loaded successfully we don't want to allow this to stop the server as there may
//be asynchronous tests running. Instead we cancel this and stop the server of our own accord once all tests