mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-16 06:15:09 +00:00
Compare commits
215 Commits
api/3.0.0-
...
api/3.0.0-
Author | SHA1 | Date | |
---|---|---|---|
84ec944b6b | |||
6ef132e468 | |||
2a59977440 | |||
716efe2549 | |||
5a9b5db103 | |||
beed94dfb9 | |||
c6229b1e52 | |||
d682fdfdf0 | |||
0e7f364a41 | |||
559504225a | |||
8ddf781a51 | |||
7cafaf2844 | |||
6ece57e23e | |||
2204942338 | |||
00a226921c | |||
e7406ba096 | |||
a356e36340 | |||
547a09c8d4 | |||
de95046c11 | |||
9e2b26de23 | |||
b867cf4c91 | |||
d3f4b185f3 | |||
86de0bddd9 | |||
207056fb9d | |||
930945db18 | |||
d2efcee115 | |||
eefa8abaf2 | |||
8a775e0c45 | |||
f2159c5948 | |||
4ab8233fe0 | |||
40775f5d86 | |||
319763dd93 | |||
a455e25665 | |||
1c7773c5f1 | |||
7c66af5994 | |||
cb7264e0e5 | |||
f7e1939ae8 | |||
172d7339f9 | |||
c01e0354bd | |||
e312c697fd | |||
80292c6c7a | |||
dda47ee566 | |||
5863d001bd | |||
6b72bbc234 | |||
1c2895eb12 | |||
fd982afce6 | |||
f1510428d0 | |||
630f0fab7f | |||
202bac28fc | |||
874afc2fd2 | |||
f75cc93160 | |||
b24d516eda | |||
845b124f89 | |||
16972bf9a5 | |||
87a52a4f35 | |||
7f838a8c36 | |||
b5f473a3df | |||
40a6f4dee9 | |||
69ac80518c | |||
45e5b6b04c | |||
cb059ea713 | |||
afb2e0c51f | |||
cd477163cd | |||
6b747f9272 | |||
868602a559 | |||
bc1c75a15a | |||
1c3d89cfef | |||
c84ec90398 | |||
3e76c3a6dd | |||
52f2596dc5 | |||
2079e2fd88 | |||
217f66e180 | |||
07f32765ba | |||
788bd6fc20 | |||
01440fb659 | |||
dda8c6cc8f | |||
1da870b298 | |||
7a36d80384 | |||
bb79684480 | |||
4245274aec | |||
b9dfc7551a | |||
839a2ce07e | |||
34f833fa79 | |||
c9cf3d5aa4 | |||
5332887a0a | |||
5926bab323 | |||
0750b3ab59 | |||
96801be3d3 | |||
2fb92c1c62 | |||
c040579e09 | |||
9a35b4fbc8 | |||
940b20c191 | |||
c21768df26 | |||
2d927db264 | |||
a5a51fb9c5 | |||
47f7af6739 | |||
b7a3230f73 | |||
6a03f8d434 | |||
06f2a9c674 | |||
0e64d4bbc2 | |||
284c18d401 | |||
6ba4a8fe5c | |||
4638ccbb68 | |||
36cda5de61 | |||
9c350dbe47 | |||
2673e4de7f | |||
be449b6106 | |||
4c61ad9f2d | |||
66fbfdd47b | |||
8a28021b44 | |||
ab1150382a | |||
8114ceaf68 | |||
0d37d0d896 | |||
cc0b4d888e | |||
116cba9fae | |||
51a20470f6 | |||
6e1abe7b15 | |||
cbb003bf29 | |||
fa5e66478c | |||
bcbb5de5bb | |||
548df21645 | |||
b7b73aab23 | |||
a8650a241c | |||
d26713ab59 | |||
3138e02acb | |||
d264a04db4 | |||
3c709b1d3e | |||
c344caaf78 | |||
9e341f74d8 | |||
e7dbda922a | |||
92193fd27b | |||
bb85308b01 | |||
565335f29e | |||
78278a0b93 | |||
955dc38be4 | |||
f58ee2028e | |||
083d1e9ef8 | |||
90abc28c29 | |||
6c5dbd7359 | |||
4f27bce5b3 | |||
6f1b12b021 | |||
a71747347f | |||
004880548c | |||
91a92b4e57 | |||
ed765a2c9b | |||
e1fb4a44e9 | |||
2cb98c48c2 | |||
d41bdfc31c | |||
c925845173 | |||
7fb3c7343f | |||
132e04fdbb | |||
1f2b584400 | |||
d31e92bbe7 | |||
08cd944e5d | |||
dbb579aa73 | |||
bc0598c0f1 | |||
f87b745771 | |||
8d43faf16e | |||
f00e7ccb54 | |||
7b5e5832cb | |||
dd6abff712 | |||
7e1bdd474a | |||
e31333edd4 | |||
c052ee5847 | |||
f8c2eb8c3a | |||
94d78ca554 | |||
c7fdbea0f0 | |||
a19996a7cf | |||
9311b4f248 | |||
295d9bc80b | |||
3a044f0154 | |||
adb7df212c | |||
9e92a350e3 | |||
005c2419e9 | |||
d823ff18d8 | |||
9b47aed0ab | |||
55598ba703 | |||
425686755b | |||
6676029319 | |||
e008a3cd5e | |||
5aed0fb0d5 | |||
d0faf3df91 | |||
564b50ea33 | |||
ea0f291cb5 | |||
56990eb28b | |||
477cb77002 | |||
93896977d0 | |||
554816b8b6 | |||
fc5fa01442 | |||
f204422432 | |||
d6d3184e37 | |||
c569fd86b1 | |||
e33eb0ddb6 | |||
0a8bd72e11 | |||
4ee8d14584 | |||
663cb514e2 | |||
15f098074a | |||
8bf3b6bbea | |||
eb13cec5d0 | |||
0cd1e82c52 | |||
1ee689e759 | |||
d25c8d93ca | |||
4fbc5738e3 | |||
d588222e84 | |||
c3fb2e9f23 | |||
cfb6856634 | |||
11e0387e19 | |||
dc7b5b14d5 | |||
5eab956da6 | |||
ad88ca09bd | |||
f98a964cdc | |||
99995579d7 | |||
4ae18526d1 | |||
282095513a | |||
d6e343c2cf |
7
.github/ISSUE_TEMPLATE.md
vendored
7
.github/ISSUE_TEMPLATE.md
vendored
@ -1,7 +1,14 @@
|
||||
### Issue description
|
||||
<!--- use our forum https://forums.pmmp.io for questions -->
|
||||
<!--- Any issues requesting updates to new versions of MCPE will be treated as spam. We do not need issues to tell us that there is a new version available. -->
|
||||
<!---
|
||||
Write a short description about the issue
|
||||
|
||||
If you are reporting a regression or unexpected behaviour, please include the below information:
|
||||
Expected result: What were you expecting to happen?
|
||||
Actual result: What actually happened?
|
||||
-->
|
||||
|
||||
### Steps to reproduce the issue
|
||||
<!--- help us find the problem by adding steps to reproduce the issue -->
|
||||
1. ...
|
||||
|
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
## Introduction
|
||||
<!-- Explain existing problems or why this pull request is necessary -->
|
||||
|
||||
### Relevant issues
|
||||
<!-- List relevant issues here -->
|
||||
<!--
|
||||
|
||||
* Fixes #1
|
||||
* Fixes #2
|
||||
|
||||
-->
|
||||
|
||||
## Changes
|
||||
### API changes
|
||||
<!-- Any additions to the API that should be documented in release notes? -->
|
||||
|
||||
### Behavioural changes
|
||||
<!-- Any change in how the server behaves, or its performance? -->
|
||||
|
||||
## Backwards compatibility
|
||||
<!-- Any possible backwards incompatible changes? How are they solved, or how can they be solved? -->
|
||||
|
||||
## Follow-up
|
||||
<!-- Suggest any actions to be done before/after merging this pull request -->
|
||||
<!--
|
||||
|
||||
Requires translations:
|
||||
|
||||
| Name | Value in eng.ini |
|
||||
| :--: | :---: |
|
||||
| `foo.bar` | `Foo bar` |
|
||||
|
||||
-->
|
||||
|
||||
## Tests
|
||||
<!-- Attach scripts or actions to test this pull request, as well as the result -->
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ timings/*
|
||||
server.properties
|
||||
/pocketmine.yml
|
||||
memoryDump_*/*
|
||||
resource_packs/
|
||||
|
||||
# Common IDEs
|
||||
.idea/
|
||||
|
@ -13,19 +13,17 @@ If you don't find what you're looking for there, [talk to a human](#discussion).
|
||||
- [#pmmp + #pocketmine channel IRC](http://webchat.freenode.net/?channels=pmmp,pocketmine)
|
||||
|
||||
### Plugins
|
||||
There are a very wide range of already-written plugins available which you can use to customise your server. Check out the [old plugin repository](http://plugins.pocketmine.net/), [Poggit](https://poggit.pmmp.io) or just search GitHub.
|
||||
There are a very wide range of already-written plugins available which you can use to customise your server. Check out [the old plugin repository](http://plugins.pocketmine.net/) and [Poggit](https://poggit.pmmp.io), or just search GitHub.
|
||||
|
||||
### For developers
|
||||
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP%20Docs/doxygen/) - Doxygen documentation generated from development
|
||||
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP-doc/doxygen/) - Doxygen documentation generated from development
|
||||
* [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - A development tools plugin for creating plugins.
|
||||
|
||||
### Can I contribute?
|
||||
Yes you can! Contributions are welcomed provided that they comply with our [Contributing Guidelines](CONTRIBUTING.md). Please ensure you read the relevant sections of the guidelines carefully before making a Pull Request or opening an Issue.
|
||||
|
||||
<!-- TODO uncomment this when Jenkins is fixed
|
||||
### Where can I get the latest .phar?
|
||||
Head over to our [official Jenkins server](https://jenkins.pmmp.io/)
|
||||
-->
|
||||
|
||||
## Third-party Libraries/Protocols Used
|
||||
* __[PHP Sockets](http://php.net/manual/en/book.sockets.php)__
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use pocketmine\network\protocol\Info;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\plugin\PluginBase;
|
||||
use pocketmine\plugin\PluginLoadOrder;
|
||||
use pocketmine\utils\Utils;
|
||||
@ -143,7 +143,7 @@ class CrashDump{
|
||||
$error = $lastExceptionError;
|
||||
}else{
|
||||
$error = (array) error_get_last();
|
||||
$error["trace"] = @getTrace(3);
|
||||
$error["trace"] = getTrace(4); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump
|
||||
$errorConversion = [
|
||||
E_ERROR => "E_ERROR",
|
||||
E_WARNING => "E_WARNING",
|
||||
@ -226,7 +226,7 @@ class CrashDump{
|
||||
$this->data["general"] = [];
|
||||
$this->data["general"]["version"] = $version->get(false);
|
||||
$this->data["general"]["build"] = $version->getBuild();
|
||||
$this->data["general"]["protocol"] = Info::CURRENT_PROTOCOL;
|
||||
$this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL;
|
||||
$this->data["general"]["api"] = \pocketmine\API_VERSION;
|
||||
$this->data["general"]["git"] = \pocketmine\GIT_COMMIT;
|
||||
$this->data["general"]["raklib"] = RakLib::VERSION;
|
||||
@ -235,7 +235,7 @@ class CrashDump{
|
||||
$this->data["general"]["zend"] = zend_version();
|
||||
$this->data["general"]["php_os"] = PHP_OS;
|
||||
$this->data["general"]["os"] = Utils::getOS();
|
||||
$this->addLine("PocketMine-MP version: " . $version->get(false) . " #" . $version->getBuild() . " [Protocol " . Info::CURRENT_PROTOCOL . "; API " . API_VERSION . "]");
|
||||
$this->addLine("PocketMine-MP version: " . $version->get(false) . " #" . $version->getBuild() . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "; API " . API_VERSION . "]");
|
||||
$this->addLine("Git commit: " . GIT_COMMIT);
|
||||
$this->addLine("uname -a: " . php_uname("a"));
|
||||
$this->addLine("PHP Version: " . phpversion());
|
||||
|
@ -47,7 +47,7 @@ class MemoryManager{
|
||||
private $garbageCollectionTrigger;
|
||||
private $garbageCollectionAsync;
|
||||
|
||||
private $chunkLimit;
|
||||
private $chunkRadiusOverride;
|
||||
private $chunkCollect;
|
||||
private $chunkTrigger;
|
||||
|
||||
@ -104,7 +104,7 @@ class MemoryManager{
|
||||
$this->garbageCollectionTrigger = (bool) $this->server->getProperty("memory.garbage-collection.low-memory-trigger", true);
|
||||
$this->garbageCollectionAsync = (bool) $this->server->getProperty("memory.garbage-collection.collect-async-worker", true);
|
||||
|
||||
$this->chunkLimit = (int) $this->server->getProperty("memory.max-chunks.trigger-limit", 96);
|
||||
$this->chunkRadiusOverride = (int) $this->server->getProperty("memory.max-chunks.chunk-radius", 4);
|
||||
$this->chunkCollect = (bool) $this->server->getProperty("memory.max-chunks.trigger-chunk-collect", true);
|
||||
$this->chunkTrigger = (bool) $this->server->getProperty("memory.max-chunks.low-memory-trigger", true);
|
||||
|
||||
@ -122,8 +122,15 @@ class MemoryManager{
|
||||
return !($this->lowMemory and $this->chunkTrigger);
|
||||
}
|
||||
|
||||
public function getViewDistance($distance){
|
||||
return $this->lowMemory ? min($this->chunkLimit, $distance) : $distance;
|
||||
/**
|
||||
* Returns the allowed chunk radius based on the current memory usage.
|
||||
*
|
||||
* @param int $distance
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getViewDistance(int $distance) : int{
|
||||
return $this->lowMemory ? min($this->chunkRadiusOverride, $distance) : $distance;
|
||||
}
|
||||
|
||||
public function trigger($memory, $limit, $global = false, $triggerCount = 0){
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -74,7 +74,7 @@ namespace pocketmine {
|
||||
use raklib\RakLib;
|
||||
|
||||
const VERSION = "1.6.2dev";
|
||||
const API_VERSION = "3.0.0-ALPHA4";
|
||||
const API_VERSION = "3.0.0-ALPHA5";
|
||||
const CODENAME = "Unleashed";
|
||||
|
||||
/*
|
||||
@ -128,7 +128,6 @@ namespace pocketmine {
|
||||
|
||||
set_time_limit(0); //Who set it to 30 seconds?!?!
|
||||
|
||||
gc_enable();
|
||||
error_reporting(-1);
|
||||
ini_set("allow_url_fopen", 1);
|
||||
ini_set("display_errors", 1);
|
||||
@ -357,7 +356,7 @@ namespace pocketmine {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function getTrace($start = 1, $trace = null){
|
||||
function getTrace($start = 0, $trace = null){
|
||||
if($trace === null){
|
||||
if(function_exists("xdebug_get_function_stack")){
|
||||
$trace = array_reverse(xdebug_get_function_stack());
|
||||
@ -398,11 +397,6 @@ namespace pocketmine {
|
||||
++$errors;
|
||||
}
|
||||
|
||||
if(!extension_loaded("sockets")){
|
||||
$logger->critical("Unable to find the Socket extension.");
|
||||
++$errors;
|
||||
}
|
||||
|
||||
$pthreads_version = phpversion("pthreads");
|
||||
if(substr_count($pthreads_version, ".") < 2){
|
||||
$pthreads_version = "0.$pthreads_version";
|
||||
@ -431,19 +425,21 @@ namespace pocketmine {
|
||||
");
|
||||
}
|
||||
|
||||
if(!extension_loaded("curl")){
|
||||
$logger->critical("Unable to find the cURL extension.");
|
||||
$extensions = [
|
||||
"curl" => "cURL",
|
||||
"json" => "JSON",
|
||||
"mbstring" => "Multibyte String",
|
||||
"yaml" => "YAML",
|
||||
"sockets" => "Sockets",
|
||||
"zip" => "Zip",
|
||||
"zlib" => "Zlib"
|
||||
];
|
||||
|
||||
foreach($extensions as $ext => $name){
|
||||
if(!extension_loaded($ext)){
|
||||
$logger->critical("Unable to find the $name ($ext) extension.");
|
||||
++$errors;
|
||||
}
|
||||
|
||||
if(!extension_loaded("yaml")){
|
||||
$logger->critical("Unable to find the YAML extension.");
|
||||
++$errors;
|
||||
}
|
||||
|
||||
if(!extension_loaded("zlib")){
|
||||
$logger->critical("Unable to find the Zlib extension.");
|
||||
++$errors;
|
||||
}
|
||||
|
||||
if($errors > 0){
|
||||
@ -453,20 +449,25 @@ namespace pocketmine {
|
||||
exit(1); //Exit with error
|
||||
}
|
||||
|
||||
if(PHP_INT_SIZE < 8){
|
||||
$logger->warning("Running PocketMine-MP with 32-bit systems/PHP is deprecated. Support for 32-bit may be dropped in the future.");
|
||||
}
|
||||
|
||||
$gitHash = str_repeat("00", 20);
|
||||
if(file_exists(\pocketmine\PATH . ".git/HEAD")){ //Found Git information!
|
||||
$ref = trim(file_get_contents(\pocketmine\PATH . ".git/HEAD"));
|
||||
if(preg_match('/^[0-9a-f]{40}$/i', $ref)){
|
||||
define('pocketmine\GIT_COMMIT', strtolower($ref));
|
||||
$gitHash = strtolower($ref);
|
||||
}elseif(substr($ref, 0, 5) === "ref: "){
|
||||
$refFile = \pocketmine\PATH . ".git/" . substr($ref, 5);
|
||||
if(is_file($refFile)){
|
||||
define('pocketmine\GIT_COMMIT', strtolower(trim(file_get_contents($refFile))));
|
||||
$gitHash = strtolower(trim(file_get_contents($refFile)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!defined('pocketmine\GIT_COMMIT')){ //Unknown :(
|
||||
define('pocketmine\GIT_COMMIT', str_repeat("00", 20));
|
||||
}
|
||||
|
||||
define('pocketmine\GIT_COMMIT', $gitHash);
|
||||
|
||||
|
||||
@define("ENDIANNESS", (pack("d", 1) === "\77\360\0\0\0\0\0\0" ? Binary::BIG_ENDIAN : Binary::LITTLE_ENDIAN));
|
||||
@define("INT32_MASK", is_int(0xffffffff) ? 0xffffffff : -1);
|
||||
|
@ -48,8 +48,8 @@ use pocketmine\inventory\Recipe;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\lang\BaseLang;
|
||||
use pocketmine\level\format\io\LevelProviderManager;
|
||||
use pocketmine\level\format\io\leveldb\LevelDB;
|
||||
use pocketmine\level\format\io\LevelProviderManager;
|
||||
use pocketmine\level\format\io\region\Anvil;
|
||||
use pocketmine\level\format\io\region\McRegion;
|
||||
use pocketmine\level\format\io\region\PMAnvil;
|
||||
@ -74,13 +74,13 @@ use pocketmine\nbt\tag\LongTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\network\CompressBatchedTask;
|
||||
use pocketmine\network\mcpe\protocol\BatchPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\network\mcpe\protocol\PlayerListPacket;
|
||||
use pocketmine\network\mcpe\RakLibInterface;
|
||||
use pocketmine\network\Network;
|
||||
use pocketmine\network\protocol\BatchPacket;
|
||||
use pocketmine\network\protocol\DataPacket;
|
||||
use pocketmine\network\protocol\Info as ProtocolInfo;
|
||||
use pocketmine\network\protocol\PlayerListPacket;
|
||||
use pocketmine\network\query\QueryHandler;
|
||||
use pocketmine\network\RakLibInterface;
|
||||
use pocketmine\network\rcon\RCON;
|
||||
use pocketmine\network\upnp\UPnP;
|
||||
use pocketmine\permission\BanList;
|
||||
@ -90,6 +90,7 @@ use pocketmine\plugin\Plugin;
|
||||
use pocketmine\plugin\PluginLoadOrder;
|
||||
use pocketmine\plugin\PluginManager;
|
||||
use pocketmine\plugin\ScriptPluginLoader;
|
||||
use pocketmine\resourcepacks\ResourcePackManager;
|
||||
use pocketmine\scheduler\FileWriteTask;
|
||||
use pocketmine\scheduler\SendUsageTask;
|
||||
use pocketmine\scheduler\ServerScheduler;
|
||||
@ -157,6 +158,9 @@ class Server{
|
||||
private $currentTPS = 20;
|
||||
private $currentUse = 0;
|
||||
|
||||
/** @var bool */
|
||||
private $doTitleTick = true;
|
||||
|
||||
private $sendUsageTicker = 0;
|
||||
|
||||
private $dispatchSignals = false;
|
||||
@ -176,6 +180,9 @@ class Server{
|
||||
/** @var CraftingManager */
|
||||
private $craftingManager;
|
||||
|
||||
/** @var ResourcePackManager */
|
||||
private $resourceManager;
|
||||
|
||||
/** @var ConsoleCommandSender */
|
||||
private $consoleSender;
|
||||
|
||||
@ -333,8 +340,19 @@ class Server{
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getViewDistance(){
|
||||
return max(56, $this->getProperty("chunk-sending.max-chunks", 256));
|
||||
public function getViewDistance() : int{
|
||||
return max(2, $this->getConfigInt("view-distance", 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view distance up to the currently-allowed limit.
|
||||
*
|
||||
* @param int $distance
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAllowedViewDistance(int $distance) : int{
|
||||
return max(2, min($distance, $this->memoryManager->getViewDistance($this->getViewDistance())));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -583,6 +601,13 @@ class Server{
|
||||
return $this->craftingManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResourcePackManager
|
||||
*/
|
||||
public function getResourceManager() : ResourcePackManager{
|
||||
return $this->resourceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServerScheduler
|
||||
*/
|
||||
@ -676,7 +701,7 @@ class Server{
|
||||
*
|
||||
* @return CompoundTag
|
||||
*/
|
||||
public function getOfflinePlayerData($name){
|
||||
public function getOfflinePlayerData($name) : CompoundTag{
|
||||
$name = strtolower($name);
|
||||
$path = $this->getDataPath() . "players/";
|
||||
if($this->shouldSavePlayerData()){
|
||||
@ -795,7 +820,7 @@ class Server{
|
||||
public function getPlayerExact($name){
|
||||
$name = strtolower($name);
|
||||
foreach($this->getOnlinePlayers() as $player){
|
||||
if(strtolower($player->getName()) === $name){
|
||||
if($player->getLowerCaseName() === $name){
|
||||
return $player;
|
||||
}
|
||||
}
|
||||
@ -812,7 +837,7 @@ class Server{
|
||||
$partialName = strtolower($partialName);
|
||||
$matchedPlayers = [];
|
||||
foreach($this->getOnlinePlayers() as $player){
|
||||
if(strtolower($player->getName()) === $partialName){
|
||||
if($player->getLowerCaseName() === $partialName){
|
||||
$matchedPlayers = [$player];
|
||||
break;
|
||||
}elseif(stripos($player->getName(), $partialName) !== false){
|
||||
@ -1076,6 +1101,31 @@ class Server{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches all levels for the entity with the specified ID.
|
||||
* Useful for tracking entities across multiple worlds without needing strong references.
|
||||
*
|
||||
* @param int $entityId
|
||||
* @param Level|null $expectedLevel Level to look in first for the target
|
||||
*
|
||||
* @return Entity|null
|
||||
*/
|
||||
public function findEntity(int $entityId, Level $expectedLevel = null){
|
||||
$levels = $this->levels;
|
||||
if($expectedLevel !== null){
|
||||
array_unshift($levels, $expectedLevel);
|
||||
}
|
||||
|
||||
foreach($levels as $level){
|
||||
assert(!$level->isClosed());
|
||||
if(($entity = $level->getEntity($entityId)) instanceof Entity){
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $variable
|
||||
* @param string $defaultValue
|
||||
@ -1384,6 +1434,7 @@ class Server{
|
||||
"enable-rcon" => false,
|
||||
"rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10),
|
||||
"auto-save" => true,
|
||||
"view-distance" => 8
|
||||
]);
|
||||
|
||||
$this->forceLanguage = $this->getProperty("settings.force-language", false);
|
||||
@ -1418,6 +1469,8 @@ class Server{
|
||||
$this->alwaysTickPlayers = (int) $this->getProperty("level-settings.always-tick-players", false);
|
||||
$this->baseTickRate = (int) $this->getProperty("level-settings.base-tick-rate", 1);
|
||||
|
||||
$this->doTitleTick = (bool) $this->getProperty("console.title-tick", true);
|
||||
|
||||
$this->scheduler = new ServerScheduler();
|
||||
|
||||
if($this->getConfigBoolean("enable-rcon", false) === true){
|
||||
@ -1498,6 +1551,8 @@ class Server{
|
||||
Attribute::init();
|
||||
$this->craftingManager = new CraftingManager();
|
||||
|
||||
$this->resourceManager = new ResourcePackManager($this, $this->getDataPath() . "resource_packs" . DIRECTORY_SEPARATOR);
|
||||
|
||||
$this->pluginManager = new PluginManager($this, $this->commandMap);
|
||||
$this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
|
||||
$this->pluginManager->setUseTimings($this->getProperty("settings.enable-profiling", false));
|
||||
@ -1990,9 +2045,8 @@ class Server{
|
||||
$errline = $e->getLine();
|
||||
|
||||
$type = ($errno === E_ERROR or $errno === E_USER_ERROR) ? \LogLevel::ERROR : (($errno === E_USER_WARNING or $errno === E_WARNING) ? \LogLevel::WARNING : \LogLevel::NOTICE);
|
||||
if(($pos = strpos($errstr, "\n")) !== false){
|
||||
$errstr = substr($errstr, 0, $pos);
|
||||
}
|
||||
|
||||
$errstr = preg_replace('/\s+/', ' ', trim($errstr));
|
||||
|
||||
$errfile = cleanPath($errfile);
|
||||
|
||||
@ -2004,7 +2058,7 @@ class Server{
|
||||
"fullFile" => $e->getFile(),
|
||||
"file" => $errfile,
|
||||
"line" => $errline,
|
||||
"trace" => @getTrace(1, $trace)
|
||||
"trace" => getTrace(0, $trace)
|
||||
];
|
||||
|
||||
global $lastExceptionError, $lastError;
|
||||
@ -2155,7 +2209,7 @@ class Server{
|
||||
foreach($this->players as $p){
|
||||
if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){
|
||||
$p->close("", "Login timeout");
|
||||
}elseif($this->alwaysTickPlayers){
|
||||
}elseif($this->alwaysTickPlayers and $p->joined){
|
||||
$p->onUpdate($currentTick);
|
||||
}
|
||||
}
|
||||
@ -2200,7 +2254,7 @@ class Server{
|
||||
if($this->getAutoSave()){
|
||||
Timings::$worldSaveTimer->startTiming();
|
||||
foreach($this->players as $index => $player){
|
||||
if($player->isOnline()){
|
||||
if($player->joined){
|
||||
$player->save(true);
|
||||
}elseif(!$player->isConnected()){
|
||||
$this->removePlayer($player);
|
||||
@ -2215,7 +2269,9 @@ class Server{
|
||||
}
|
||||
|
||||
public function sendUsage($type = SendUsageTask::TYPE_STATUS){
|
||||
if($this->getProperty("anonymous-statistics.enabled", true)){
|
||||
$this->scheduler->scheduleAsyncTask(new SendUsageTask($this, $type, $this->uniquePlayers));
|
||||
}
|
||||
$this->uniquePlayers = [];
|
||||
}
|
||||
|
||||
@ -2249,10 +2305,6 @@ class Server{
|
||||
}
|
||||
|
||||
private function titleTick(){
|
||||
if(!Terminal::hasFormattingCodes()){
|
||||
return;
|
||||
}
|
||||
|
||||
$d = Utils::getRealMemoryUsage();
|
||||
|
||||
$u = Utils::getMemoryUsage(true);
|
||||
@ -2328,7 +2380,9 @@ class Server{
|
||||
}
|
||||
|
||||
if(($this->tickCounter & 0b1111) === 0){
|
||||
if($this->doTitleTick and Terminal::hasFormattingCodes()){
|
||||
$this->titleTick();
|
||||
}
|
||||
$this->currentTPS = 20;
|
||||
$this->currentUse = 0;
|
||||
|
||||
@ -2393,4 +2447,13 @@ class Server{
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when something attempts to serialize the server instance.
|
||||
*
|
||||
* @throws \BadMethodCallException because Server instances cannot be serialized
|
||||
*/
|
||||
public function __sleep(){
|
||||
throw new \BadMethodCallException("Cannot serialize Server instance");
|
||||
}
|
||||
}
|
||||
|
@ -81,13 +81,7 @@ class FenceGate extends Transparent{
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){
|
||||
$faces = [
|
||||
0 => 3,
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
3 => 2,
|
||||
];
|
||||
$this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0] & 0x03;
|
||||
$this->meta = ($player instanceof Player ? ($player->getDirection() - 1) & 0x03 : 0);
|
||||
$this->getLevel()->setBlock($block, $this, true, true);
|
||||
|
||||
return true;
|
||||
@ -100,8 +94,11 @@ class FenceGate extends Transparent{
|
||||
}
|
||||
|
||||
public function onActivate(Item $item, Player $player = null){
|
||||
$this->meta ^= 0x04; //Flip open/close state
|
||||
//TODO: Face player when opened
|
||||
$this->meta = (($this->meta ^ 0x04) & ~0x02);
|
||||
|
||||
if($player !== null){
|
||||
$this->meta |= (($player->getDirection() - 1) & 0x02);
|
||||
}
|
||||
|
||||
$this->getLevel()->setBlock($this, $this, true);
|
||||
$this->level->addSound(new DoorSound($this));
|
||||
|
@ -29,4 +29,8 @@ class Furnace extends BurningFurnace{
|
||||
public function getName(){
|
||||
return "Furnace";
|
||||
}
|
||||
|
||||
public function getLightLevel(){
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\block;
|
||||
|
||||
|
||||
class GlowingObsidian extends Transparent{
|
||||
class GlowingObsidian extends Solid{
|
||||
|
||||
protected $id = self::GLOWING_OBSIDIAN;
|
||||
|
||||
|
@ -64,7 +64,7 @@ class Lava extends Liquid{
|
||||
|
||||
public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){
|
||||
$ret = $this->getLevel()->setBlock($this, $this, true, false);
|
||||
$this->getLevel()->scheduleUpdate($this, $this->tickRate());
|
||||
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ abstract class Liquid extends Transparent{
|
||||
public function onUpdate($type){
|
||||
if($type === Level::BLOCK_UPDATE_NORMAL){
|
||||
$this->checkForHarden();
|
||||
$this->getLevel()->scheduleUpdate($this, $this->tickRate());
|
||||
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
|
||||
}elseif($type === Level::BLOCK_UPDATE_SCHEDULED){
|
||||
if($this->temporalVector === null){
|
||||
$this->temporalVector = new Vector3(0, 0, 0);
|
||||
@ -242,7 +242,7 @@ abstract class Liquid extends Transparent{
|
||||
$this->getLevel()->setBlock($this, new Air(), true);
|
||||
}else{
|
||||
$this->getLevel()->setBlock($this, Block::get($this->id, $decay), true);
|
||||
$this->getLevel()->scheduleUpdate($this, $this->tickRate());
|
||||
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
|
||||
}
|
||||
}elseif($flag){
|
||||
//$this->getLevel()->scheduleUpdate($this, $this->tickRate());
|
||||
@ -262,10 +262,10 @@ abstract class Liquid extends Transparent{
|
||||
|
||||
if($decay >= 8){
|
||||
$this->getLevel()->setBlock($bottomBlock, Block::get($this->id, $decay), true);
|
||||
$this->getLevel()->scheduleUpdate($bottomBlock, $this->tickRate());
|
||||
$this->getLevel()->scheduleDelayedBlockUpdate($bottomBlock, $this->tickRate());
|
||||
}else{
|
||||
$this->getLevel()->setBlock($bottomBlock, Block::get($this->id, $decay + 8), true);
|
||||
$this->getLevel()->scheduleUpdate($bottomBlock, $this->tickRate());
|
||||
$this->getLevel()->scheduleDelayedBlockUpdate($bottomBlock, $this->tickRate());
|
||||
}
|
||||
}elseif($decay >= 0 and ($decay === 0 or !$bottomBlock->canBeFlowedInto())){
|
||||
$flags = $this->getOptimalFlowDirections();
|
||||
@ -310,7 +310,7 @@ abstract class Liquid extends Transparent{
|
||||
}
|
||||
|
||||
$this->getLevel()->setBlock($block, Block::get($this->getId(), $newFlowDecay), true);
|
||||
$this->getLevel()->scheduleUpdate($block, $this->tickRate());
|
||||
$this->getLevel()->scheduleDelayedBlockUpdate($block, $this->tickRate());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,6 @@
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\Tool;
|
||||
use pocketmine\Player;
|
||||
|
||||
class LitPumpkin extends Pumpkin{
|
||||
|
||||
protected $id = self::LIT_PUMPKIN;
|
||||
|
@ -32,4 +32,8 @@ class RedstoneTorch extends Torch{
|
||||
public function getName(){
|
||||
return "Redstone Torch";
|
||||
}
|
||||
|
||||
public function getLightLevel(){
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ class Torch extends Flowable{
|
||||
}
|
||||
|
||||
public function getLightLevel(){
|
||||
return 15;
|
||||
return 14;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
|
@ -48,7 +48,7 @@ class Water extends Liquid{
|
||||
|
||||
public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){
|
||||
$ret = $this->getLevel()->setBlock($this, $this, true, false);
|
||||
$this->getLevel()->scheduleUpdate($this, $this->tickRate());
|
||||
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
@ -32,12 +32,12 @@ use pocketmine\Server;
|
||||
use pocketmine\utils\TextFormat;
|
||||
|
||||
abstract class Command{
|
||||
/** @var \stdClass */
|
||||
/** @var array */
|
||||
private static $defaultDataTemplate = null;
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
/** @var \stdClass */
|
||||
/** @var array */
|
||||
protected $commandData = null;
|
||||
|
||||
/** @var string */
|
||||
@ -87,11 +87,11 @@ abstract class Command{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an \stdClass containing command data
|
||||
* Returns an array containing command data
|
||||
*
|
||||
* @return \stdClass
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultCommandData() : \stdClass{
|
||||
public function getDefaultCommandData() : array{
|
||||
return $this->commandData;
|
||||
}
|
||||
|
||||
@ -101,25 +101,28 @@ abstract class Command{
|
||||
*
|
||||
* @param Player $player
|
||||
*
|
||||
* @return \stdClass|null
|
||||
* @return array
|
||||
*/
|
||||
public function generateCustomCommandData(Player $player){
|
||||
//TODO: fix command permission filtering on join
|
||||
/*if(!$this->testPermissionSilent($player)){
|
||||
return null;
|
||||
}*/
|
||||
$customData = clone $this->commandData;
|
||||
$customData->aliases = $this->getAliases();
|
||||
/*foreach($customData->overloads as &$overload){
|
||||
if(isset($overload->pocketminePermission) and !$player->hasPermission($overload->pocketminePermission)){
|
||||
unset($overload);
|
||||
$customData = $this->commandData;
|
||||
$customData["aliases"] = $this->getAliases();
|
||||
/*foreach($customData["overloads"] as $overloadName => $overload){
|
||||
if(isset($overload["pocketminePermission"]) and !$player->hasPermission($overload["pocketminePermission"])){
|
||||
unset($customData["overloads"][$overloadName]);
|
||||
}
|
||||
}*/
|
||||
return $customData;
|
||||
}
|
||||
|
||||
public function getOverloads(): \stdClass{
|
||||
return $this->commandData->overloads;
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getOverloads(): array{
|
||||
return $this->commandData["overloads"];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,7 +132,7 @@ abstract class Command{
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public abstract function execute(CommandSender $sender, $commandLabel, array $args);
|
||||
abstract public function execute(CommandSender $sender, $commandLabel, array $args);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
@ -142,7 +145,7 @@ abstract class Command{
|
||||
* @return string
|
||||
*/
|
||||
public function getPermission(){
|
||||
return $this->commandData->pocketminePermission ?? null;
|
||||
return $this->commandData["pocketminePermission"] ?? null;
|
||||
}
|
||||
|
||||
|
||||
@ -151,9 +154,9 @@ abstract class Command{
|
||||
*/
|
||||
public function setPermission($permission){
|
||||
if($permission !== null){
|
||||
$this->commandData->pocketminePermission = $permission;
|
||||
$this->commandData["pocketminePermission"] = $permission;
|
||||
}else{
|
||||
unset($this->commandData->pocketminePermission);
|
||||
unset($this->commandData["pocketminePermission"]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,7 +242,7 @@ abstract class Command{
|
||||
public function unregister(CommandMap $commandMap){
|
||||
if($this->allowChangesFrom($commandMap)){
|
||||
$this->commandMap = null;
|
||||
$this->activeAliases = $this->commandData->aliases;
|
||||
$this->activeAliases = $this->commandData["aliases"];
|
||||
$this->label = $this->nextLabel;
|
||||
|
||||
return true;
|
||||
@ -282,7 +285,7 @@ abstract class Command{
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(){
|
||||
return $this->commandData->description;
|
||||
return $this->commandData["description"];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -296,7 +299,7 @@ abstract class Command{
|
||||
* @param string[] $aliases
|
||||
*/
|
||||
public function setAliases(array $aliases){
|
||||
$this->commandData->aliases = $aliases;
|
||||
$this->commandData["aliases"] = $aliases;
|
||||
if(!$this->isRegistered()){
|
||||
$this->activeAliases = (array) $aliases;
|
||||
}
|
||||
@ -306,7 +309,7 @@ abstract class Command{
|
||||
* @param string $description
|
||||
*/
|
||||
public function setDescription($description){
|
||||
$this->commandData->description = $description;
|
||||
$this->commandData["description"] = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -323,11 +326,14 @@ abstract class Command{
|
||||
$this->usageMessage = $usage;
|
||||
}
|
||||
|
||||
public static final function generateDefaultData() : \stdClass{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static final function generateDefaultData() : array{
|
||||
if(self::$defaultDataTemplate === null){
|
||||
self::$defaultDataTemplate = json_decode(file_get_contents(Server::getInstance()->getFilePath() . "src/pocketmine/resources/command_default.json"));
|
||||
self::$defaultDataTemplate = json_decode(file_get_contents(Server::getInstance()->getFilePath() . "src/pocketmine/resources/command_default.json"), true);
|
||||
}
|
||||
return clone self::$defaultDataTemplate;
|
||||
return self::$defaultDataTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +70,7 @@ class PluginCommand extends Command implements PluginIdentifiableCommand{
|
||||
* @param CommandExecutor $executor
|
||||
*/
|
||||
public function setExecutor(CommandExecutor $executor){
|
||||
$this->executor = ($executor != null) ? $executor : $this->owningPlugin;
|
||||
$this->executor = $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,6 +57,7 @@ use pocketmine\command\defaults\TeleportCommand;
|
||||
use pocketmine\command\defaults\TellCommand;
|
||||
use pocketmine\command\defaults\TimeCommand;
|
||||
use pocketmine\command\defaults\TimingsCommand;
|
||||
use pocketmine\command\defaults\TransferServerCommand;
|
||||
use pocketmine\command\defaults\VanillaCommand;
|
||||
use pocketmine\command\defaults\VersionCommand;
|
||||
use pocketmine\command\defaults\WhitelistCommand;
|
||||
@ -115,6 +116,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
$this->register("pocketmine", new TimeCommand("time"));
|
||||
$this->register("pocketmine", new TimingsCommand("timings"));
|
||||
$this->register("pocketmine", new ReloadCommand("reload"));
|
||||
$this->register("pocketmine", new TransferServerCommand("transferserver"));
|
||||
|
||||
if($this->server->getProperty("debug.commands", false)){
|
||||
$this->register("pocketmine", new StatusCommand("status"));
|
||||
@ -134,7 +136,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
if($label === null){
|
||||
$label = $command->getName();
|
||||
}
|
||||
$label = strtolower(trim($label));
|
||||
$label = trim($label);
|
||||
$fallbackPrefix = strtolower(trim($fallbackPrefix));
|
||||
|
||||
$registered = $this->registerAlias($command, false, $fallbackPrefix, $label);
|
||||
@ -175,15 +177,35 @@ class SimpleCommandMap implements CommandMap{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function dispatch(CommandSender $sender, $commandLine){
|
||||
$args = explode(" ", $commandLine);
|
||||
/**
|
||||
* Returns a command to match the specified command line, or null if no matching command was found.
|
||||
* This method is intended to provide capability for handling commands with spaces in their name.
|
||||
* The referenced parameters will be modified accordingly depending on the resulting matched command.
|
||||
*
|
||||
* @param string &$commandName
|
||||
* @param string[] &$args
|
||||
*
|
||||
* @return Command|null
|
||||
*/
|
||||
public function matchCommand(string &$commandName, array &$args){
|
||||
$count = min(count($args), 255);
|
||||
|
||||
if(count($args) === 0){
|
||||
return false;
|
||||
for($i = 0; $i < $count; ++$i){
|
||||
$commandName .= array_shift($args);
|
||||
if(($command = $this->getCommand($commandName)) instanceof Command){
|
||||
return $command;
|
||||
}
|
||||
|
||||
$sentCommandLabel = strtolower(array_shift($args));
|
||||
$target = $this->getCommand($sentCommandLabel);
|
||||
$commandName .= " ";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function dispatch(CommandSender $sender, $commandLine){
|
||||
$args = explode(" ", $commandLine);
|
||||
$sentCommandLabel = "";
|
||||
$target = $this->matchCommand($sentCommandLabel, $args);
|
||||
|
||||
if($target === null){
|
||||
return false;
|
||||
@ -211,11 +233,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
}
|
||||
|
||||
public function getCommand($name){
|
||||
if(isset($this->knownCommands[$name])){
|
||||
return $this->knownCommands[$name];
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->knownCommands[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,7 +251,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
$values = $this->server->getCommandAliases();
|
||||
|
||||
foreach($values as $alias => $commandStrings){
|
||||
if(strpos($alias, ":") !== false or strpos($alias, " ") !== false){
|
||||
if(strpos($alias, ":") !== false){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.illegal", [$alias]));
|
||||
continue;
|
||||
}
|
||||
@ -241,20 +259,33 @@ class SimpleCommandMap implements CommandMap{
|
||||
$targets = [];
|
||||
|
||||
$bad = "";
|
||||
$recursive = "";
|
||||
foreach($commandStrings as $commandString){
|
||||
$args = explode(" ", $commandString);
|
||||
$command = $this->getCommand($args[0]);
|
||||
$commandName = "";
|
||||
$command = $this->matchCommand($commandName, $args);
|
||||
|
||||
|
||||
if($command === null){
|
||||
if(strlen($bad) > 0){
|
||||
$bad .= ", ";
|
||||
}
|
||||
$bad .= $commandString;
|
||||
}elseif($commandName === $alias){
|
||||
if($recursive !== ""){
|
||||
$recursive .= ", ";
|
||||
}
|
||||
$recursive .= $commandString;
|
||||
}else{
|
||||
$targets[] = $commandString;
|
||||
}
|
||||
}
|
||||
|
||||
if($recursive !== ""){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.recursive", [$alias, $recursive]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(strlen($bad) > 0){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.notFound", [$alias, $bad]));
|
||||
continue;
|
||||
|
@ -24,7 +24,7 @@ namespace pocketmine\command\defaults;
|
||||
use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\event\TranslationContainer;
|
||||
use pocketmine\network\protocol\SetDifficultyPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
|
||||
use pocketmine\Server;
|
||||
|
||||
class DifficultyCommand extends VanillaCommand{
|
||||
|
@ -23,7 +23,6 @@ namespace pocketmine\command\defaults;
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\entity\Effect;
|
||||
use pocketmine\entity\InstantEffect;
|
||||
use pocketmine\event\TranslationContainer;
|
||||
use pocketmine\utils\TextFormat;
|
||||
|
||||
@ -80,12 +79,9 @@ class EffectCommand extends VanillaCommand{
|
||||
$amplification = 0;
|
||||
|
||||
if(count($args) >= 3){
|
||||
$duration = (int) $args[2];
|
||||
if(!($effect instanceof InstantEffect)){
|
||||
$duration *= 20;
|
||||
}
|
||||
}elseif($effect instanceof InstantEffect){
|
||||
$duration = 1;
|
||||
$duration = ((int) $args[2]) * 20; //ticks
|
||||
}else{
|
||||
$duration = $effect->getDefaultDuration();
|
||||
}
|
||||
|
||||
if(count($args) >= 4){
|
||||
|
@ -24,6 +24,9 @@ namespace pocketmine\command\defaults;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\event\TimingsHandler;
|
||||
use pocketmine\event\TranslationContainer;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\scheduler\BulkCurlTask;
|
||||
use pocketmine\Server;
|
||||
|
||||
class TimingsCommand extends VanillaCommand{
|
||||
|
||||
@ -101,39 +104,42 @@ class TimingsCommand extends VanillaCommand{
|
||||
"poster" => $sender->getServer()->getName(),
|
||||
"content" => stream_get_contents($fileTimings)
|
||||
];
|
||||
|
||||
$ch = curl_init("http://paste.ubuntu.com/");
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
|
||||
curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($ch, CURLOPT_AUTOREFERER, false);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["User-Agent: " . $this->getName() . " " . $sender->getServer()->getPocketMineVersion()]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
try{
|
||||
$data = curl_exec($ch);
|
||||
if($data === false){
|
||||
throw new \Exception(curl_error($ch));
|
||||
}
|
||||
}catch(\Exception $e){
|
||||
$sender->getServer()->getLogger()->logException($e);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
if(preg_match('#^Location: http://paste\\.ubuntu\\.com/([0-9]{1,})/#m', $data, $matches) == 0){
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.pasteError"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsUpload", ["http://paste.ubuntu.com/" . $matches[1] . "/"]));
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsRead", ["http://timings.aikar.co/?url=" . $matches[1]]));
|
||||
fclose($fileTimings);
|
||||
|
||||
$sender->getServer()->getScheduler()->scheduleAsyncTask(new class([
|
||||
["page" => "http://paste.ubuntu.com", "extraOpts" => [
|
||||
CURLOPT_HTTPHEADER => ["User-Agent: " . $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion()],
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
]]
|
||||
], $sender) extends BulkCurlTask{
|
||||
public function onCompletion(Server $server){
|
||||
$sender = $this->fetchLocal($server);
|
||||
if($sender instanceof Player and !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
|
||||
return;
|
||||
}
|
||||
$result = $this->getResult()[0];
|
||||
if($result instanceof \RuntimeException){
|
||||
$server->getLogger()->logException($result);
|
||||
return;
|
||||
}
|
||||
list(, $headers) = $result;
|
||||
foreach($headers as $headerGroup){
|
||||
if(isset($headerGroup["location"]) and preg_match('#^http://paste\\.ubuntu\\.com/([0-9]{1,})/#', trim($headerGroup["location"]), $match)){
|
||||
$pasteId = $match[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isset($pasteId)){
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsUpload", ["http://paste.ubuntu.com/" . $pasteId . "/"]));
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsRead",
|
||||
["http://" . $sender->getServer()->getProperty("timings.host", "timings.pmmp.io") . "/?url=$pasteId"]));
|
||||
}else{
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.pasteError"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}else{
|
||||
fclose($fileTimings);
|
||||
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsWrite", [$timings]));
|
||||
|
56
src/pocketmine/command/defaults/TransferServerCommand.php
Normal file
56
src/pocketmine/command/defaults/TransferServerCommand.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
namespace pocketmine\command\defaults;
|
||||
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\event\TranslationContainer;
|
||||
use pocketmine\Player;
|
||||
|
||||
class TransferServerCommand extends VanillaCommand{
|
||||
|
||||
public function __construct($name){
|
||||
parent::__construct(
|
||||
$name,
|
||||
"%pocketmine.command.transferserver.description",
|
||||
"%pocketmine.command.transferserver.usage"
|
||||
);
|
||||
$this->setPermission("pocketmine.command.transferserver");
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, $commandLabel, array $args){
|
||||
if(count($args) < 1){
|
||||
$sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage]));
|
||||
|
||||
return false;
|
||||
}elseif(!($sender instanceof Player)){
|
||||
$sender->sendMessage("This command must be executed as a player");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$sender->transfer($args[0], (int) ($args[1] ?? 19132));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ namespace pocketmine\command\defaults;
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\event\TranslationContainer;
|
||||
use pocketmine\network\protocol\Info;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\utils\TextFormat;
|
||||
|
||||
@ -51,7 +51,7 @@ class VersionCommand extends VanillaCommand{
|
||||
$sender->getServer()->getCodename(),
|
||||
$sender->getServer()->getApiVersion(),
|
||||
$sender->getServer()->getVersion(),
|
||||
Info::CURRENT_PROTOCOL
|
||||
ProtocolInfo::CURRENT_PROTOCOL
|
||||
]));
|
||||
}else{
|
||||
$pluginName = implode(" ", $args);
|
||||
|
@ -24,7 +24,7 @@ namespace pocketmine\entity;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\level\particle\CriticalParticle;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\network\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class Arrow extends Projectile{
|
||||
|
@ -165,11 +165,15 @@ class Attribute{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function resetToDefault(){
|
||||
$this->setValue($this->getDefaultValue());
|
||||
}
|
||||
|
||||
public function getValue(){
|
||||
return $this->currentValue;
|
||||
}
|
||||
|
||||
public function setValue($value, $fit = false){
|
||||
public function setValue($value, $fit = false, bool $forceSend = false){
|
||||
if($value > $this->getMaxValue() or $value < $this->getMinValue()){
|
||||
if(!$fit){
|
||||
throw new \InvalidArgumentException("Value $value exceeds the range!");
|
||||
@ -180,7 +184,10 @@ class Attribute{
|
||||
if($this->currentValue != $value){
|
||||
$this->desynchronized = true;
|
||||
$this->currentValue = $value;
|
||||
}elseif($forceSend){
|
||||
$this->desynchronized = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,9 @@ class AttributeMap implements \ArrayAccess{
|
||||
return $this->attributes[$id] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Attribute[]
|
||||
*/
|
||||
public function getAll(): array{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
@ -26,22 +26,20 @@ use pocketmine\event\entity\EntityEffectAddEvent;
|
||||
use pocketmine\event\entity\EntityEffectRemoveEvent;
|
||||
use pocketmine\event\entity\EntityRegainHealthEvent;
|
||||
use pocketmine\event\player\PlayerExhaustEvent;
|
||||
use pocketmine\network\protocol\MobEffectPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEffectPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\utils\Config;
|
||||
|
||||
class Effect{
|
||||
const SPEED = 1;
|
||||
const SLOWNESS = 2;
|
||||
const HASTE = 3;
|
||||
const SWIFTNESS = 3;
|
||||
const FATIGUE = 4;
|
||||
const MINING_FATIGUE = 4;
|
||||
const FATIGUE = 4, MINING_FATIGUE = 4;
|
||||
const STRENGTH = 5;
|
||||
// TODO: const HEALING = 6;
|
||||
// TODO: const HARMING = 7;
|
||||
const INSTANT_HEALTH = 6, HEALING = 6;
|
||||
const INSTANT_DAMAGE = 7, HARMING = 7;
|
||||
const JUMP = 8;
|
||||
const NAUSEA = 9;
|
||||
const CONFUSION = 9;
|
||||
const NAUSEA = 9, CONFUSION = 9;
|
||||
const REGENERATION = 10;
|
||||
const DAMAGE_RESISTANCE = 11;
|
||||
const FIRE_RESISTANCE = 12;
|
||||
@ -54,44 +52,43 @@ class Effect{
|
||||
const POISON = 19;
|
||||
const WITHER = 20;
|
||||
const HEALTH_BOOST = 21;
|
||||
const ABSORPTION = 22; // TODO implement
|
||||
const ABSORPTION = 22;
|
||||
const SATURATION = 23;
|
||||
const LEVITATION = 24; //TODO
|
||||
|
||||
/** @var Effect[] */
|
||||
protected static $effects;
|
||||
protected static $effects = [];
|
||||
|
||||
public static function init(){
|
||||
self::$effects = new \SplFixedArray(256);
|
||||
$config = new Config(\pocketmine\PATH . "src/pocketmine/resources/effects.json", Config::JSON, []);
|
||||
|
||||
self::$effects[Effect::SPEED] = new Effect(Effect::SPEED, "%potion.moveSpeed", 124, 175, 198);
|
||||
self::$effects[Effect::SLOWNESS] = new Effect(Effect::SLOWNESS, "%potion.moveSlowdown", 90, 108, 129, true);
|
||||
self::$effects[Effect::SWIFTNESS] = new Effect(Effect::SWIFTNESS, "%potion.digSpeed", 217, 192, 67);
|
||||
self::$effects[Effect::FATIGUE] = new Effect(Effect::FATIGUE, "%potion.digSlowDown", 74, 66, 23, true);
|
||||
self::$effects[Effect::STRENGTH] = new Effect(Effect::STRENGTH, "%potion.damageBoost", 147, 36, 35);
|
||||
//self::$effects[Effect::HEALING] = new InstantEffect(Effect::HEALING, "%potion.heal", 248, 36, 35);
|
||||
//self::$effects[Effect::HARMING] = new InstantEffect(Effect::HARMING, "%potion.harm", 67, 10, 9, true);
|
||||
self::$effects[Effect::JUMP] = new Effect(Effect::JUMP, "%potion.jump", 34, 255, 76);
|
||||
self::$effects[Effect::NAUSEA] = new Effect(Effect::NAUSEA, "%potion.confusion", 85, 29, 74, true);
|
||||
self::$effects[Effect::REGENERATION] = new Effect(Effect::REGENERATION, "%potion.regeneration", 205, 92, 171);
|
||||
self::$effects[Effect::DAMAGE_RESISTANCE] = new Effect(Effect::DAMAGE_RESISTANCE, "%potion.resistance", 153, 69, 58);
|
||||
self::$effects[Effect::FIRE_RESISTANCE] = new Effect(Effect::FIRE_RESISTANCE, "%potion.fireResistance", 228, 154, 58);
|
||||
self::$effects[Effect::WATER_BREATHING] = new Effect(Effect::WATER_BREATHING, "%potion.waterBreathing", 46, 82, 153);
|
||||
self::$effects[Effect::INVISIBILITY] = new Effect(Effect::INVISIBILITY, "%potion.invisibility", 127, 131, 146);
|
||||
self::$effects[Effect::BLINDNESS] = new Effect(Effect::BLINDNESS, "%potion.blindness", 191, 192, 192);
|
||||
self::$effects[Effect::NIGHT_VISION] = new Effect(Effect::NIGHT_VISION, "%potion.nightVision", 0, 0, 139);
|
||||
self::$effects[Effect::HUNGER] = new Effect(Effect::HUNGER, "%potion.hunger", 46, 139, 87);
|
||||
self::$effects[Effect::WEAKNESS] = new Effect(Effect::WEAKNESS, "%potion.weakness", 72, 77, 72, true);
|
||||
self::$effects[Effect::POISON] = new Effect(Effect::POISON, "%potion.poison", 78, 147, 49, true);
|
||||
self::$effects[Effect::WITHER] = new Effect(Effect::WITHER, "%potion.wither", 53, 42, 39, true);
|
||||
self::$effects[Effect::HEALTH_BOOST] = new Effect(Effect::HEALTH_BOOST, "%potion.healthBoost", 248, 125, 35);
|
||||
self::$effects[Effect::ABSORPTION] = new Effect(Effect::ABSORPTION, "%potion.absorption", 36, 107, 251);
|
||||
self::$effects[Effect::SATURATION] = new Effect(Effect::SATURATION, "%potion.saturation", 255, 0, 255);
|
||||
foreach($config->getAll() as $name => $data){
|
||||
$color = hexdec($data["color"]);
|
||||
$r = ($color >> 16) & 0xff;
|
||||
$g = ($color >> 8) & 0xff;
|
||||
$b = $color & 0xff;
|
||||
self::registerEffect($name, new Effect(
|
||||
$data["id"],
|
||||
"%" . $data["name"],
|
||||
$r,
|
||||
$g,
|
||||
$b,
|
||||
$data["isBad"] ?? false,
|
||||
$data["default_duration"] ?? 300 * 20,
|
||||
$data["has_bubbles"] ?? true
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public static function registerEffect(string $internalName, Effect $effect){
|
||||
self::$effects[$effect->getId()] = $effect;
|
||||
self::$effects[$internalName] = $effect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return $this
|
||||
* @return Effect|null
|
||||
*/
|
||||
public static function getEffect($id){
|
||||
if(isset(self::$effects[$id])){
|
||||
@ -100,9 +97,14 @@ class Effect{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Effect|null
|
||||
*/
|
||||
public static function getEffectByName($name){
|
||||
if(defined(Effect::class . "::" . strtoupper($name))){
|
||||
return self::getEffect(constant(Effect::class . "::" . strtoupper($name)));
|
||||
if(isset(self::$effects[$name])){
|
||||
return clone self::$effects[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -124,40 +126,106 @@ class Effect{
|
||||
|
||||
protected $bad;
|
||||
|
||||
public function __construct($id, $name, $r, $g, $b, $isBad = false){
|
||||
protected $defaultDuration = 300 * 20;
|
||||
|
||||
protected $hasBubbles = true;
|
||||
|
||||
/**
|
||||
* @param int $id Effect ID as per Minecraft PE
|
||||
* @param string $name Translation key used for effect name
|
||||
* @param int $r 0-255, red balance of potion particle colour
|
||||
* @param int $g 0-255, green balance of potion particle colour
|
||||
* @param int $b 0-255, blue balance of potion particle colour
|
||||
* @param bool $isBad Whether the effect is harmful
|
||||
* @param int $defaultDuration Duration in ticks the effect will last for by default if applied without a duration.
|
||||
* @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles)
|
||||
*/
|
||||
public function __construct($id, $name, $r, $g, $b, $isBad = false, int $defaultDuration = 300 * 20, bool $hasBubbles = true){
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->bad = (bool) $isBad;
|
||||
$this->setColor($r, $g, $b);
|
||||
$this->defaultDuration = $defaultDuration;
|
||||
$this->duration = $defaultDuration;
|
||||
$this->hasBubbles = $hasBubbles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translation key used to translate this effect's name.
|
||||
* @return string
|
||||
*/
|
||||
public function getName(){
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effect ID as per Minecraft PE
|
||||
* @return int
|
||||
*/
|
||||
public function getId(){
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration in ticks of the effect.
|
||||
* @param $ticks
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDuration($ticks){
|
||||
$this->duration = $ticks;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration remaining of the effect in ticks.
|
||||
* @return int
|
||||
*/
|
||||
public function getDuration(){
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default duration this effect will apply for if a duration is not specified.
|
||||
* @return int
|
||||
*/
|
||||
public function getDefaultDuration() : int{
|
||||
return $this->defaultDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this effect will give the subject potion bubbles.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasBubbles() : bool{
|
||||
return $this->hasBubbles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this effect will produce some visible effect, such as bubbles or particles.
|
||||
* NOTE: Do not confuse this with {@link Effect#hasBubbles}. For example, Instant Damage does not have bubbles, but still produces visible effects (particles).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isVisible(){
|
||||
return $this->show;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the visibility of the effect.
|
||||
* @param bool $bool
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setVisible($bool){
|
||||
$this->show = (bool) $bool;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amplifier of this effect.
|
||||
* TODO: fix mess of amplifier used instead of level for effect calculation.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAmplifier(){
|
||||
@ -174,19 +242,40 @@ class Effect{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the effect is ambient.
|
||||
* @return bool
|
||||
*/
|
||||
public function isAmbient(){
|
||||
return $this->ambient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ambiency of this effect.
|
||||
* @param bool $ambient
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAmbient($ambient = true){
|
||||
$this->ambient = (bool) $ambient;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this effect is harmful.
|
||||
* TODO: implement inverse effect results for undead mobs
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isBad(){
|
||||
return $this->bad;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the effect will do something on the current tick.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canTick(){
|
||||
switch($this->id){
|
||||
case Effect::POISON:
|
||||
@ -212,10 +301,19 @@ class Effect{
|
||||
return ($this->duration % $interval) === 0;
|
||||
}
|
||||
return true;
|
||||
case Effect::INSTANT_DAMAGE:
|
||||
case Effect::INSTANT_HEALTH:
|
||||
//If forced to last longer than 1 tick, these apply every tick.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies effect results to an entity.
|
||||
*
|
||||
* @param Entity $entity
|
||||
*/
|
||||
public function applyEffect(Entity $entity){
|
||||
switch($this->id){
|
||||
case Effect::POISON:
|
||||
@ -241,17 +339,48 @@ class Effect{
|
||||
if($entity instanceof Human){
|
||||
$entity->exhaust(0.5 * $this->amplifier, PlayerExhaustEvent::CAUSE_POTION);
|
||||
}
|
||||
break;
|
||||
case Effect::INSTANT_HEALTH:
|
||||
//TODO: add particles (witch spell)
|
||||
if($entity->getHealth() < $entity->getMaxHealth()){
|
||||
$amount = 2 * (2 ** (($this->amplifier + 1) % 32));
|
||||
$entity->heal($amount, new EntityRegainHealthEvent($entity, $amount, EntityRegainHealthEvent::CAUSE_MAGIC));
|
||||
}
|
||||
break;
|
||||
case Effect::INSTANT_DAMAGE:
|
||||
//TODO: add particles (witch spell)
|
||||
$amount = 2 * (2 ** (($this->amplifier + 1) % 32));
|
||||
$entity->attack($amount, new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, $amount));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an RGB color array of this effect's color.
|
||||
* @return array
|
||||
*/
|
||||
public function getColor(){
|
||||
return [$this->color >> 16, ($this->color >> 8) & 0xff, $this->color & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of this effect.
|
||||
*
|
||||
* @param int $r
|
||||
* @param int $g
|
||||
* @param int $b
|
||||
*/
|
||||
public function setColor($r, $g, $b){
|
||||
$this->color = (($r & 0xff) << 16) + (($g & 0xff) << 8) + ($b & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this effect to the Entity, performing effect overriding as specified.
|
||||
*
|
||||
* @param Entity $entity
|
||||
* @param bool $modify
|
||||
* @param Effect|null $oldEffect
|
||||
*/
|
||||
public function add(Entity $entity, $modify = false, Effect $oldEffect = null){
|
||||
$entity->getLevel()->getServer()->getPluginManager()->callEvent($ev = new EntityEffectAddEvent($entity, $this, $modify, $oldEffect));
|
||||
if($ev->isCancelled()){
|
||||
@ -259,7 +388,7 @@ class Effect{
|
||||
}
|
||||
if($entity instanceof Player){
|
||||
$pk = new MobEffectPacket();
|
||||
$pk->eid = 0;
|
||||
$pk->eid = $entity->getId();
|
||||
$pk->effectId = $this->getId();
|
||||
$pk->amplifier = $this->getAmplifier();
|
||||
$pk->particles = $this->isVisible();
|
||||
@ -273,10 +402,12 @@ class Effect{
|
||||
$entity->dataPacket($pk);
|
||||
}
|
||||
|
||||
if($this->id === Effect::INVISIBILITY){
|
||||
switch($this->id){
|
||||
case Effect::INVISIBILITY:
|
||||
$entity->setDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_INVISIBLE, true);
|
||||
$entity->setNameTagVisible(false);
|
||||
}elseif($this->id === Effect::SPEED){
|
||||
break;
|
||||
case Effect::SPEED:
|
||||
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
|
||||
if($ev->willModify() and $oldEffect !== null){
|
||||
$speed = $attr->getValue() / (1 + 0.2 * $oldEffect->getAmplifier());
|
||||
@ -285,7 +416,8 @@ class Effect{
|
||||
}
|
||||
$speed *= (1 + 0.2 * $this->amplifier);
|
||||
$attr->setValue($speed);
|
||||
}elseif($this->id === Effect::SLOWNESS){
|
||||
break;
|
||||
case Effect::SLOWNESS:
|
||||
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
|
||||
if($ev->willModify() and $oldEffect !== null){
|
||||
$speed = $attr->getValue() / (1 - 0.15 * $oldEffect->getAmplifier());
|
||||
@ -293,10 +425,35 @@ class Effect{
|
||||
$speed = $attr->getValue();
|
||||
}
|
||||
$speed *= (1 - 0.15 * $this->amplifier);
|
||||
$attr->setValue($speed);
|
||||
$attr->setValue($speed, true);
|
||||
break;
|
||||
|
||||
case Effect::HEALTH_BOOST:
|
||||
$attr = $entity->getAttributeMap()->getAttribute(Attribute::HEALTH);
|
||||
if($ev->willModify() and $oldEffect !== null){
|
||||
$max = $attr->getMaxValue() - (4 * ($oldEffect->getAmplifier() + 1));
|
||||
}else{
|
||||
$max = $attr->getMaxValue();
|
||||
}
|
||||
|
||||
$max += (4 * ($this->amplifier + 1));
|
||||
$attr->setMaxValue($max);
|
||||
break;
|
||||
case Effect::ABSORPTION:
|
||||
$new = (4 * ($this->amplifier + 1));
|
||||
if($new > $entity->getAbsorption()){
|
||||
$entity->setAbsorption($new);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the effect from the entity, resetting any changed values back to their original defaults.
|
||||
*
|
||||
* @param Entity $entity
|
||||
*/
|
||||
public function remove(Entity $entity){
|
||||
$entity->getLevel()->getServer()->getPluginManager()->callEvent($ev = new EntityEffectRemoveEvent($entity, $this));
|
||||
if($ev->isCancelled()){
|
||||
@ -304,22 +461,33 @@ class Effect{
|
||||
}
|
||||
if($entity instanceof Player){
|
||||
$pk = new MobEffectPacket();
|
||||
$pk->eid = 0;
|
||||
$pk->eid = $entity->getId();
|
||||
$pk->eventId = MobEffectPacket::EVENT_REMOVE;
|
||||
$pk->effectId = $this->getId();
|
||||
|
||||
$entity->dataPacket($pk);
|
||||
}
|
||||
|
||||
if($this->id === Effect::INVISIBILITY){
|
||||
switch($this->id){
|
||||
case Effect::INVISIBILITY:
|
||||
$entity->setDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_INVISIBLE, false);
|
||||
$entity->setNameTagVisible(true);
|
||||
}elseif($this->id === Effect::SPEED){
|
||||
break;
|
||||
case Effect::SPEED:
|
||||
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
|
||||
$attr->setValue($attr->getValue() / (1 + 0.2 * $this->amplifier));
|
||||
}elseif($this->id === Effect::SLOWNESS){
|
||||
break;
|
||||
case Effect::SLOWNESS:
|
||||
$attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED);
|
||||
$attr->setValue($attr->getValue() / (1 - 0.15 * $this->amplifier));
|
||||
break;
|
||||
case Effect::HEALTH_BOOST:
|
||||
$attr = $entity->getAttributeMap()->getAttribute(Attribute::HEALTH);
|
||||
$attr->setMaxValue($attr->getMaxValue() - (4 * ($this->amplifier + 1)));
|
||||
break;
|
||||
case Effect::ABSORPTION:
|
||||
$entity->setAbsorption(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,9 +52,9 @@ use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\network\protocol\MobEffectPacket;
|
||||
use pocketmine\network\protocol\RemoveEntityPacket;
|
||||
use pocketmine\network\protocol\SetEntityDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEffectPacket;
|
||||
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetEntityDataPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\Server;
|
||||
@ -109,6 +109,14 @@ abstract class Entity extends Location implements Metadatable{
|
||||
* 58 (byte)
|
||||
* 59 (float)
|
||||
* 60 (float) */
|
||||
const DATA_AREA_EFFECT_CLOUD_RADIUS = 61; //float
|
||||
const DATA_AREA_EFFECT_CLOUD_WAITING = 62; //int
|
||||
const DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 63; //int
|
||||
/* 64 (int), shulker-related
|
||||
* 65 (byte), shulker-related
|
||||
* 66 (short) shulker-related
|
||||
* 67 (unknown), shulker-related */
|
||||
const DATA_TRADING_PLAYER_EID = 68; //long
|
||||
|
||||
|
||||
const DATA_FLAG_ONFIRE = 0;
|
||||
@ -331,6 +339,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
$this->invulnerable = $this->namedtag["Invulnerable"] > 0 ? true : false;
|
||||
|
||||
$this->attributeMap = new AttributeMap();
|
||||
$this->addAttributes();
|
||||
|
||||
$this->chunk->addEntity($this);
|
||||
$this->level->addEntity($this);
|
||||
@ -388,7 +397,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getScale(): float{
|
||||
public function getScale() : float{
|
||||
return $this->getDataProperty(self::DATA_SCALE);
|
||||
}
|
||||
|
||||
@ -396,7 +405,24 @@ abstract class Entity extends Location implements Metadatable{
|
||||
* @param float $value
|
||||
*/
|
||||
public function setScale(float $value){
|
||||
$multiplier = $value / $this->getScale();
|
||||
|
||||
$this->width *= $multiplier;
|
||||
$this->height *= $multiplier;
|
||||
$halfWidth = $this->width / 2;
|
||||
|
||||
$this->boundingBox->setBounds(
|
||||
$this->x - $halfWidth,
|
||||
$this->y,
|
||||
$this->z - $halfWidth,
|
||||
$this->x + $halfWidth,
|
||||
$this->y + $this->height,
|
||||
$this->z + $halfWidth
|
||||
);
|
||||
|
||||
$this->setDataProperty(self::DATA_SCALE, self::DATA_TYPE_FLOAT, $value);
|
||||
$this->setDataProperty(self::DATA_BOUNDING_BOX_WIDTH, self::DATA_TYPE_FLOAT, $this->width);
|
||||
$this->setDataProperty(self::DATA_BOUNDING_BOX_HEIGHT, self::DATA_TYPE_FLOAT, $this->height);
|
||||
}
|
||||
|
||||
public function isSneaking(){
|
||||
@ -415,7 +441,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
if($value !== $this->isSprinting()){
|
||||
$this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_SPRINTING, (bool) $value);
|
||||
$attr = $this->attributeMap->getAttribute(Attribute::MOVEMENT_SPEED);
|
||||
$attr->setValue($value ? ($attr->getValue() * 1.3) : ($attr->getValue() / 1.3));
|
||||
$attr->setValue($value ? ($attr->getValue() * 1.3) : ($attr->getValue() / 1.3), false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -462,7 +488,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
if(isset($this->effects[$effect->getId()])){
|
||||
$oldEffect = $this->effects[$effect->getId()];
|
||||
if(
|
||||
abs($effect->getAmplifier()) <= ($oldEffect->getAmplifier())
|
||||
abs($effect->getAmplifier()) < ($oldEffect->getAmplifier())
|
||||
or (abs($effect->getAmplifier()) === abs($oldEffect->getAmplifier())
|
||||
and $effect->getDuration() < $oldEffect->getDuration())
|
||||
){
|
||||
@ -476,10 +502,6 @@ abstract class Entity extends Location implements Metadatable{
|
||||
$this->effects[$effect->getId()] = $effect;
|
||||
|
||||
$this->recalculateEffectColor();
|
||||
|
||||
if($effect->getId() === Effect::HEALTH_BOOST){
|
||||
$this->setHealth($this->getHealth() + 4 * ($effect->getAmplifier() + 1));
|
||||
}
|
||||
}
|
||||
|
||||
protected function recalculateEffectColor(){
|
||||
@ -488,7 +510,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
$count = 0;
|
||||
$ambient = true;
|
||||
foreach($this->effects as $effect){
|
||||
if($effect->isVisible()){
|
||||
if($effect->isVisible() and $effect->hasBubbles()){
|
||||
$c = $effect->getColor();
|
||||
$color[0] += $c[0] * ($effect->getAmplifier() + 1);
|
||||
$color[1] += $c[1] * ($effect->getAmplifier() + 1);
|
||||
@ -505,7 +527,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
$g = ($color[1] / $count) & 0xff;
|
||||
$b = ($color[2] / $count) & 0xff;
|
||||
|
||||
$this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, ($r << 16) + ($g << 8) + $b);
|
||||
$this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, 0xff000000 | ($r << 16) | ($g << 8) | $b);
|
||||
$this->setDataProperty(Entity::DATA_POTION_AMBIENT, Entity::DATA_TYPE_BYTE, $ambient ? 1 : 0);
|
||||
}else{
|
||||
$this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, 0);
|
||||
@ -621,8 +643,6 @@ abstract class Entity extends Location implements Metadatable{
|
||||
|
||||
$this->scheduleUpdate();
|
||||
|
||||
$this->addAttributes();
|
||||
|
||||
if(isset($this->namedtag->ActiveEffects)){
|
||||
foreach($this->namedtag->ActiveEffects->getValue() as $e){
|
||||
$amplifier = $e["Amplifier"] & 0xff; //0-255 only
|
||||
@ -661,7 +681,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
public function sendPotionEffects(Player $player){
|
||||
foreach($this->effects as $effect){
|
||||
$pk = new MobEffectPacket();
|
||||
$pk->eid = 0;
|
||||
$pk->eid = $this->id;
|
||||
$pk->effectId = $effect->getId();
|
||||
$pk->amplifier = $effect->getAmplifier();
|
||||
$pk->particles = $effect->isVisible();
|
||||
@ -691,20 +711,23 @@ abstract class Entity extends Location implements Metadatable{
|
||||
}
|
||||
$p->dataPacket(clone $pk);
|
||||
}
|
||||
|
||||
if($this instanceof Player){
|
||||
$pk->eid = 0;
|
||||
$this->dataPacket($pk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Player $player
|
||||
* @param bool $send
|
||||
*/
|
||||
public function despawnFrom(Player $player){
|
||||
public function despawnFrom(Player $player, bool $send = true){
|
||||
if(isset($this->hasSpawned[$player->getLoaderId()])){
|
||||
if($send){
|
||||
$pk = new RemoveEntityPacket();
|
||||
$pk->eid = $this->getId();
|
||||
$pk->eid = $this->id;
|
||||
$player->dataPacket($pk);
|
||||
}
|
||||
unset($this->hasSpawned[$player->getLoaderId()]);
|
||||
}
|
||||
}
|
||||
@ -731,7 +754,21 @@ abstract class Entity extends Location implements Metadatable{
|
||||
|
||||
$this->setLastDamageCause($source);
|
||||
|
||||
$this->setHealth($this->getHealth() - $source->getFinalDamage());
|
||||
$damage = $source->getFinalDamage();
|
||||
|
||||
$absorption = $this->getAbsorption();
|
||||
if($absorption > 0){
|
||||
if($absorption > $damage){
|
||||
//Use absorption health before normal health.
|
||||
$this->setAbsorption($absorption - $damage);
|
||||
$damage = 0;
|
||||
}else{
|
||||
$this->setAbsorption(0);
|
||||
$damage -= $absorption;
|
||||
}
|
||||
}
|
||||
|
||||
$this->setHealth($this->getHealth() - $damage);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -781,6 +818,14 @@ abstract class Entity extends Location implements Metadatable{
|
||||
}
|
||||
}
|
||||
|
||||
public function getAbsorption() : int{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function setAbsorption(int $absorption){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EntityDamageEvent $type
|
||||
*/
|
||||
@ -803,7 +848,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxHealth(){
|
||||
return $this->maxHealth + ($this->hasEffect(Effect::HEALTH_BOOST) ? 4 * ($this->getEffect(Effect::HEALTH_BOOST)->getAmplifier() + 1) : 0);
|
||||
return $this->maxHealth;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1065,7 +1110,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
//return !($this instanceof Player);
|
||||
}
|
||||
|
||||
public final function scheduleUpdate(){
|
||||
final public function scheduleUpdate(){
|
||||
$this->level->updateEntities[$this->id] = $this;
|
||||
}
|
||||
|
||||
@ -1632,13 +1677,22 @@ abstract class Entity extends Location implements Metadatable{
|
||||
if(!$this->closed){
|
||||
$this->server->getPluginManager()->callEvent(new EntityDespawnEvent($this));
|
||||
$this->closed = true;
|
||||
|
||||
$this->despawnFromAll();
|
||||
$this->hasSpawned = [];
|
||||
|
||||
if($this->chunk !== null){
|
||||
$this->chunk->removeEntity($this);
|
||||
$this->chunk = null;
|
||||
}
|
||||
|
||||
if($this->getLevel() !== null){
|
||||
$this->getLevel()->removeEntity($this);
|
||||
$this->setLevel(null);
|
||||
}
|
||||
|
||||
$this->namedtag = null;
|
||||
$this->lastDamageCause = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ use pocketmine\item\Item as ItemItem;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\network\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class FallingSand extends Entity{
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\event\player\PlayerExhaustEvent;
|
||||
use pocketmine\inventory\InventoryHolder;
|
||||
use pocketmine\inventory\PlayerInventory;
|
||||
use pocketmine\item\Item as ItemItem;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
@ -35,8 +36,7 @@ use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\network\protocol\AddPlayerPacket;
|
||||
use pocketmine\network\protocol\RemoveEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
@ -62,13 +62,21 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
public $eyeHeight = 1.62;
|
||||
|
||||
protected $skinId;
|
||||
protected $skin;
|
||||
protected $skin = null;
|
||||
|
||||
protected $foodTickTimer = 0;
|
||||
|
||||
protected $totalXp = 0;
|
||||
protected $xpSeed;
|
||||
|
||||
public function __construct(Level $level, CompoundTag $nbt){
|
||||
if($this->skin === null and (!isset($nbt->Skin) or !isset($nbt->Skin->Data) or !Player::isValidSkin($nbt->Skin->Data->getValue()))){
|
||||
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
|
||||
}
|
||||
|
||||
parent::__construct($level, $nbt);
|
||||
}
|
||||
|
||||
public function getSkinData(){
|
||||
return $this->skin;
|
||||
}
|
||||
@ -96,10 +104,23 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
* @param string $skinId
|
||||
*/
|
||||
public function setSkin($str, $skinId){
|
||||
if(!Player::isValidSkin($str)){
|
||||
throw new \InvalidStateException("Specified skin is not valid, must be 8KiB or 16KiB");
|
||||
}
|
||||
|
||||
$this->skin = $str;
|
||||
$this->skinId = $skinId;
|
||||
}
|
||||
|
||||
public function jump(){
|
||||
parent::jump();
|
||||
if($this->isSprinting()){
|
||||
$this->exhaust(0.8, PlayerExhaustEvent::CAUSE_SPRINT_JUMPING);
|
||||
}else{
|
||||
$this->exhaust(0.2, PlayerExhaustEvent::CAUSE_JUMPING);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFood() : float{
|
||||
return $this->attributeMap->getAttribute(Attribute::HUNGER)->getValue();
|
||||
}
|
||||
@ -285,6 +306,12 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($this->namedtag->SelectedInventorySlot) and $this->namedtag->SelectedInventorySlot instanceof IntTag){
|
||||
$this->inventory->setHeldItemIndex($this->namedtag->SelectedInventorySlot->getValue(), false);
|
||||
}else{
|
||||
$this->inventory->setHeldItemIndex(0, false);
|
||||
}
|
||||
|
||||
parent::initEntity();
|
||||
|
||||
if(!isset($this->namedtag->foodLevel) or !($this->namedtag->foodLevel instanceof IntTag)){
|
||||
@ -347,42 +374,50 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
public function entityBaseTick($tickDiff = 1){
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
$this->doFoodTick($tickDiff);
|
||||
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
public function doFoodTick(int $tickDiff = 1){
|
||||
if($this->isAlive()){
|
||||
$food = $this->getFood();
|
||||
$health = $this->getHealth();
|
||||
$difficulty = $this->server->getDifficulty();
|
||||
|
||||
$this->foodTickTimer += $tickDiff;
|
||||
if($this->foodTickTimer >= 80){
|
||||
$this->foodTickTimer = 0;
|
||||
}
|
||||
|
||||
if($difficulty === 0 and $this->foodTickTimer % 10 === 0){ //Peaceful
|
||||
if($food < 20){
|
||||
$this->addFood(1.0);
|
||||
}
|
||||
if($this->foodTickTimer % 20 === 0 and $health < $this->getMaxHealth()){
|
||||
$this->heal(1, new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_SATURATION));
|
||||
}
|
||||
}
|
||||
|
||||
if($this->foodTickTimer === 0){
|
||||
if($food >= 18){
|
||||
$this->foodTickTimer++;
|
||||
if($this->foodTickTimer >= 80 and $health < $this->getMaxHealth()){
|
||||
if($health < $this->getMaxHealth()){
|
||||
$this->heal(1, new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_SATURATION));
|
||||
$this->exhaust(3.0, PlayerExhaustEvent::CAUSE_HEALTH_REGEN);
|
||||
$this->foodTickTimer = 0;
|
||||
|
||||
}
|
||||
}elseif($food === 0){
|
||||
$this->foodTickTimer++;
|
||||
if($this->foodTickTimer >= 80){
|
||||
$diff = $this->server->getDifficulty();
|
||||
$can = false;
|
||||
if($diff === 1){
|
||||
$can = $health > 10;
|
||||
}elseif($diff === 2){
|
||||
$can = $health > 1;
|
||||
}elseif($diff === 3){
|
||||
$can = true;
|
||||
}
|
||||
if($can){
|
||||
}elseif($food <= 0){
|
||||
if(($difficulty === 1 and $health > 10) or ($difficulty === 2 and $health > 1) or $difficulty === 3){
|
||||
$this->attack(1, new EntityDamageEvent($this, EntityDamageEvent::CAUSE_STARVATION, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($food <= 6){
|
||||
if($this->isSprinting()){
|
||||
$this->setSprinting(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
@ -402,6 +437,12 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
|
||||
public function saveNBT(){
|
||||
parent::saveNBT();
|
||||
|
||||
$this->namedtag->foodLevel = new IntTag("foodLevel", $this->getFood());
|
||||
$this->namedtag->foodExhaustionLevel = new FloatTag("foodExhaustionLevel", $this->getExhaustion());
|
||||
$this->namedtag->foodSaturationLevel = new FloatTag("foodSaturationLevel", $this->getSaturation());
|
||||
$this->namedtag->foodTickTimer = new IntTag("foodTickTimer", $this->foodTickTimer);
|
||||
|
||||
$this->namedtag->Inventory = new ListTag("Inventory", []);
|
||||
$this->namedtag->Inventory->setTagType(NBT::TAG_Compound);
|
||||
if($this->inventory !== null){
|
||||
@ -428,12 +469,13 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
}
|
||||
|
||||
//Normal inventory
|
||||
$slotCount = Player::SURVIVAL_SLOTS + 9;
|
||||
//$slotCount = (($this instanceof Player and ($this->gamemode & 0x01) === 1) ? Player::CREATIVE_SLOTS : Player::SURVIVAL_SLOTS) + 9;
|
||||
for($slot = 9; $slot < $slotCount; ++$slot){
|
||||
$slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize();
|
||||
for($slot = $this->inventory->getHotbarSize(); $slot < $slotCount; ++$slot){
|
||||
$item = $this->inventory->getItem($slot - 9);
|
||||
if($item->getId() !== ItemItem::AIR){
|
||||
$this->namedtag->Inventory[$slot] = $item->nbtSerialize($slot);
|
||||
}
|
||||
}
|
||||
|
||||
//Armor
|
||||
for($slot = 100; $slot < 104; ++$slot){
|
||||
@ -442,6 +484,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
$this->namedtag->Inventory[$slot] = $item->nbtSerialize($slot);
|
||||
}
|
||||
}
|
||||
|
||||
$this->namedtag->SelectedInventorySlot = new IntTag("SelectedInventorySlot", $this->inventory->getHeldItemIndex());
|
||||
}
|
||||
|
||||
if(strlen($this->getSkinData()) > 0){
|
||||
@ -456,7 +500,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
if($player !== $this and !isset($this->hasSpawned[$player->getLoaderId()])){
|
||||
$this->hasSpawned[$player->getLoaderId()] = $player;
|
||||
|
||||
if(strlen($this->skin) < 64 * 32 * 4){
|
||||
if(!Player::isValidSkin($this->skin)){
|
||||
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
|
||||
}
|
||||
|
||||
@ -488,20 +532,9 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
}
|
||||
}
|
||||
|
||||
public function despawnFrom(Player $player){
|
||||
if(isset($this->hasSpawned[$player->getLoaderId()])){
|
||||
|
||||
$pk = new RemoveEntityPacket();
|
||||
$pk->eid = $this->getId();
|
||||
|
||||
$player->dataPacket($pk);
|
||||
unset($this->hasSpawned[$player->getLoaderId()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function close(){
|
||||
if(!$this->closed){
|
||||
if(!($this instanceof Player) or $this->loggedIn){
|
||||
if($this->inventory !== null){
|
||||
foreach($this->inventory->getViewers() as $viewer){
|
||||
$viewer->removeWindow($this->inventory);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ use pocketmine\item\Item as ItemItem;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\network\protocol\AddItemEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddItemEntityPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class Item extends Entity{
|
||||
|
@ -31,7 +31,7 @@ use pocketmine\event\Timings;
|
||||
use pocketmine\item\Item as ItemItem;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\network\protocol\EntityEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\EntityEventPacket;
|
||||
use pocketmine\utils\BlockIterator;
|
||||
|
||||
abstract class Living extends Entity implements Damageable{
|
||||
@ -43,6 +43,8 @@ abstract class Living extends Entity implements Damageable{
|
||||
|
||||
protected $invisible = false;
|
||||
|
||||
protected $jumpVelocity = 0.42;
|
||||
|
||||
protected function initEntity(){
|
||||
parent::initEntity();
|
||||
|
||||
@ -68,7 +70,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
public function setHealth($amount){
|
||||
$wasAlive = $this->isAlive();
|
||||
parent::setHealth($amount);
|
||||
$this->attributeMap->getAttribute(Attribute::HEALTH)->setValue($this->getHealth());
|
||||
$this->attributeMap->getAttribute(Attribute::HEALTH)->setValue($this->getHealth(), true);
|
||||
if($this->isAlive() and !$wasAlive){
|
||||
$pk = new EntityEventPacket();
|
||||
$pk->eid = $this->getId();
|
||||
@ -77,16 +79,28 @@ abstract class Living extends Entity implements Damageable{
|
||||
}
|
||||
}
|
||||
|
||||
public function getMaxHealth(){
|
||||
return $this->attributeMap->getAttribute(Attribute::HEALTH)->getMaxValue();
|
||||
}
|
||||
|
||||
public function setMaxHealth($amount){
|
||||
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount);
|
||||
}
|
||||
|
||||
public function getAbsorption() : int{
|
||||
return (int) $this->attributeMap->getAttribute(Attribute::ABSORPTION)->getValue();
|
||||
}
|
||||
|
||||
public function setAbsorption(int $absorption){
|
||||
$this->attributeMap->getAttribute(Attribute::ABSORPTION)->setValue($absorption);
|
||||
}
|
||||
|
||||
public function saveNBT(){
|
||||
parent::saveNBT();
|
||||
$this->namedtag->Health = new ShortTag("Health", $this->getHealth());
|
||||
}
|
||||
|
||||
public abstract function getName();
|
||||
abstract public function getName();
|
||||
|
||||
public function hasLineOfSight(Entity $entity){
|
||||
//TODO: head height
|
||||
@ -103,6 +117,23 @@ abstract class Living extends Entity implements Damageable{
|
||||
$this->attackTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initial upwards velocity of a jumping entity in blocks/tick, including additional velocity due to effects.
|
||||
* @return float
|
||||
*/
|
||||
public function getJumpVelocity() : float{
|
||||
return $this->jumpVelocity + ($this->hasEffect(Effect::JUMP) ? (($this->getEffect(Effect::JUMP)->getAmplifier() + 1) / 10) : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the entity jumps from the ground. This method adds upwards velocity to the entity.
|
||||
*/
|
||||
public function jump(){
|
||||
if($this->onGround){
|
||||
$this->motionY = $this->getJumpVelocity(); //Y motion should already be 0 if we're jumping from the ground.
|
||||
}
|
||||
}
|
||||
|
||||
public function attack($damage, EntityDamageEvent $source){
|
||||
if($this->attackTime > 0 or $this->noDamageTicks > 0){
|
||||
$lastCause = $this->getLastDamageCause();
|
||||
@ -123,6 +154,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
$e = $source->getChild();
|
||||
}
|
||||
|
||||
if($e !== null){
|
||||
if($e->isOnFire() > 0){
|
||||
$this->setOnFire(2 * $this->server->getDifficulty());
|
||||
}
|
||||
@ -131,6 +163,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
$deltaZ = $this->z - $e->z;
|
||||
$this->knockBack($e, $damage, $deltaX, $deltaZ, $source->getKnockBack());
|
||||
}
|
||||
}
|
||||
|
||||
$pk = new EntityEventPacket();
|
||||
$pk->eid = $this->getId();
|
||||
@ -169,6 +202,10 @@ abstract class Living extends Entity implements Damageable{
|
||||
return;
|
||||
}
|
||||
parent::kill();
|
||||
$this->callDeathEvent();
|
||||
}
|
||||
|
||||
protected function callDeathEvent(){
|
||||
$this->server->getPluginManager()->callEvent($ev = new EntityDeathEvent($this, $this->getDrops()));
|
||||
foreach($ev->getDrops() as $item){
|
||||
$this->getLevel()->dropItem($this, $item);
|
||||
|
@ -25,7 +25,7 @@ use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\event\entity\ExplosionPrimeEvent;
|
||||
use pocketmine\level\Explosion;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\network\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class PrimedTNT extends Entity implements Explosive{
|
||||
|
@ -23,7 +23,7 @@ namespace pocketmine\entity;
|
||||
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\network\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class Snowball extends Projectile{
|
||||
|
@ -25,8 +25,8 @@ use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\item\Item as ItemItem;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\AddEntityPacket;
|
||||
use pocketmine\network\protocol\EntityEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\EntityEventPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class Squid extends WaterAnimal implements Ageable{
|
||||
@ -43,8 +43,8 @@ class Squid extends WaterAnimal implements Ageable{
|
||||
private $switchDirectionTicker = 0;
|
||||
|
||||
public function initEntity(){
|
||||
$this->setMaxHealth(10);
|
||||
parent::initEntity();
|
||||
$this->setMaxHealth(5);
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
@ -60,7 +60,9 @@ class Squid extends WaterAnimal implements Ageable{
|
||||
if($source instanceof EntityDamageByEntityEvent){
|
||||
$this->swimSpeed = mt_rand(150, 350) / 2000;
|
||||
$e = $source->getDamager();
|
||||
if($e !== null){
|
||||
$this->swimDirection = (new Vector3($this->x - $e->x, $this->y - $e->y, $this->z - $e->z))->normalize();
|
||||
}
|
||||
|
||||
$pk = new EntityEventPacket();
|
||||
$pk->eid = $this->getId();
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\entity;
|
||||
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\network\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class Villager extends Creature implements NPC, Ageable{
|
||||
|
@ -23,7 +23,7 @@ namespace pocketmine\entity;
|
||||
|
||||
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||
use pocketmine\item\Item as ItemItem;
|
||||
use pocketmine\network\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class Zombie extends Monster{
|
||||
|
@ -30,8 +30,6 @@ abstract class Event{
|
||||
* Any callable event must declare the static variable
|
||||
*
|
||||
* public static $handlerList = null;
|
||||
* public static $eventPool = [];
|
||||
* public static $nextEvent = 0;
|
||||
*
|
||||
* Not doing so will deny the proper event initialization
|
||||
*/
|
||||
|
@ -25,6 +25,13 @@ use pocketmine\level\Level;
|
||||
|
||||
class LevelTimings{
|
||||
|
||||
/** @var TimingsHandler */
|
||||
public $setBlock;
|
||||
/** @var TimingsHandler */
|
||||
public $doBlockLightUpdates;
|
||||
/** @var TimingsHandler */
|
||||
public $doBlockSkyLightUpdates;
|
||||
|
||||
/** @var TimingsHandler */
|
||||
public $mobSpawn;
|
||||
/** @var TimingsHandler */
|
||||
@ -79,6 +86,10 @@ class LevelTimings{
|
||||
public function __construct(Level $level){
|
||||
$name = $level->getFolderName() . " - ";
|
||||
|
||||
$this->setBlock = new TimingsHandler("** " . $name . "setBlock");
|
||||
$this->doBlockLightUpdates = new TimingsHandler("** " . $name . "doBlockLightUpdates");
|
||||
$this->doBlockSkyLightUpdates = new TimingsHandler("** " . $name . "doBlockSkyLightUpdates");
|
||||
|
||||
$this->mobSpawn = new TimingsHandler("** " . $name . "mobSpawn");
|
||||
$this->doChunkUnload = new TimingsHandler("** " . $name . "doChunkUnload");
|
||||
$this->doTickPending = new TimingsHandler("** " . $name . "doTickPending");
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\event;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\network\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\plugin\PluginManager;
|
||||
use pocketmine\scheduler\PluginTask;
|
||||
|
@ -24,6 +24,9 @@ namespace pocketmine\event\entity;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
/**
|
||||
* Called when an entity takes damage from a block.
|
||||
*/
|
||||
class EntityDamageByBlockEvent extends EntityDamageEvent{
|
||||
|
||||
/** @var Block */
|
||||
|
@ -23,10 +23,13 @@ namespace pocketmine\event\entity;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
/**
|
||||
* Called when an entity takes damage from an entity sourced from another entity, for example being hit by a snowball thrown by a Player.
|
||||
*/
|
||||
class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent{
|
||||
|
||||
/** @var Entity */
|
||||
private $childEntity;
|
||||
/** @var int */
|
||||
private $childEntityEid;
|
||||
|
||||
|
||||
/**
|
||||
@ -37,15 +40,17 @@ class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent{
|
||||
* @param int|int[] $damage
|
||||
*/
|
||||
public function __construct(Entity $damager, Entity $childEntity, Entity $entity, $cause, $damage){
|
||||
$this->childEntity = $childEntity;
|
||||
$this->childEntityEid = $childEntity->getId();
|
||||
parent::__construct($damager, $entity, $cause, $damage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Entity
|
||||
* Returns the entity which caused the damage, or null if the entity has been killed or closed.
|
||||
*
|
||||
* @return Entity|null
|
||||
*/
|
||||
public function getChild(){
|
||||
return $this->childEntity;
|
||||
return $this->getEntity()->getLevel()->getServer()->findEntity($this->childEntityEid, $this->getEntity()->getLevel());
|
||||
}
|
||||
|
||||
|
||||
|
@ -24,10 +24,13 @@ namespace pocketmine\event\entity;
|
||||
use pocketmine\entity\Effect;
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
/**
|
||||
* Called when an entity takes damage from another entity.
|
||||
*/
|
||||
class EntityDamageByEntityEvent extends EntityDamageEvent{
|
||||
|
||||
/** @var Entity */
|
||||
private $damager;
|
||||
/** @var int */
|
||||
private $damagerEid;
|
||||
/** @var float */
|
||||
private $knockBack;
|
||||
|
||||
@ -39,7 +42,7 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
|
||||
* @param float $knockBack
|
||||
*/
|
||||
public function __construct(Entity $damager, Entity $entity, $cause, $damage, $knockBack = 0.4){
|
||||
$this->damager = $damager;
|
||||
$this->damagerEid = $damager->getId();
|
||||
$this->knockBack = $knockBack;
|
||||
parent::__construct($entity, $cause, $damage);
|
||||
$this->addAttackerModifiers($damager);
|
||||
@ -56,10 +59,12 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Entity
|
||||
* Returns the attacking entity, or null if the attacker has been killed or closed.
|
||||
*
|
||||
* @return Entity|null
|
||||
*/
|
||||
public function getDamager(){
|
||||
return $this->damager;
|
||||
return $this->getEntity()->getLevel()->getServer()->findEntity($this->damagerEid, $this->getEntity()->getLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,6 +25,9 @@ use pocketmine\entity\Effect;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\Cancellable;
|
||||
|
||||
/**
|
||||
* Called when an entity takes damage.
|
||||
*/
|
||||
class EntityDamageEvent extends EntityEvent implements Cancellable{
|
||||
public static $handlerList = null;
|
||||
|
||||
|
@ -34,19 +34,21 @@ class PlayerExhaustEvent extends PlayerEvent implements Cancellable{
|
||||
const CAUSE_HEALTH_REGEN = 4;
|
||||
const CAUSE_POTION = 5;
|
||||
const CAUSE_WALKING = 6;
|
||||
const CAUSE_SNEAKING = 7;
|
||||
const CAUSE_SPRINTING = 7;
|
||||
const CAUSE_SWIMMING = 8;
|
||||
const CAUSE_JUMPING = 10;
|
||||
const CAUSE_JUMPING = 9;
|
||||
const CAUSE_SPRINT_JUMPING = 10;
|
||||
const CAUSE_CUSTOM = 11;
|
||||
|
||||
const CAUSE_FLAG_SPRINT = 0x10000;
|
||||
|
||||
/** @var float */
|
||||
private $amount;
|
||||
/** @var int */
|
||||
private $cause;
|
||||
|
||||
public function __construct(Human $human, float $amount, int $cause){
|
||||
$this->player = $human;
|
||||
$this->amount = $amount;
|
||||
$this->cause = $cause;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,4 +65,12 @@ class PlayerExhaustEvent extends PlayerEvent implements Cancellable{
|
||||
public function setAmount(float $amount){
|
||||
$this->amount = $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an int cause of the exhaustion - one of the constants at the top of this class.
|
||||
* @return int
|
||||
*/
|
||||
public function getCause() : int{
|
||||
return $this->cause;
|
||||
}
|
||||
}
|
||||
|
66
src/pocketmine/event/player/PlayerTransferEvent.php
Normal file
66
src/pocketmine/event/player/PlayerTransferEvent.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\player;
|
||||
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\Player;
|
||||
|
||||
class PlayerTransferEvent extends PlayerEvent implements Cancellable{
|
||||
public static $handlerList = null;
|
||||
|
||||
protected $address;
|
||||
protected $port = 19132;
|
||||
protected $message;
|
||||
|
||||
public function __construct(Player $player, string $address, int $port, string $message){
|
||||
$this->player = $player;
|
||||
$this->address = $address;
|
||||
$this->port = $port;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function getAddress() : string{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
public function setAddress(string $address){
|
||||
$this->address = $address;
|
||||
}
|
||||
|
||||
public function getPort() : int{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
public function setPort(int $port){
|
||||
$this->port = $port;
|
||||
}
|
||||
|
||||
public function getMessage() : string{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function setMessage(string $message){
|
||||
$this->message = $message;
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\event\server;
|
||||
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\network\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class DataPacketReceiveEvent extends ServerEvent implements Cancellable{
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\event\server;
|
||||
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\network\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
class DataPacketSendEvent extends ServerEvent implements Cancellable{
|
||||
|
@ -25,8 +25,8 @@ use pocketmine\entity\Entity;
|
||||
use pocketmine\event\entity\EntityInventoryChangeEvent;
|
||||
use pocketmine\event\inventory\InventoryOpenEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\network\protocol\ContainerSetContentPacket;
|
||||
use pocketmine\network\protocol\ContainerSetSlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\network\protocol\BlockEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockEventPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\tile\Chest;
|
||||
|
||||
|
@ -22,8 +22,8 @@
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\ContainerClosePacket;
|
||||
use pocketmine\network\protocol\ContainerOpenPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
abstract class ContainerInventory extends BaseInventory{
|
||||
|
@ -23,7 +23,7 @@ namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\event\Timings;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\network\protocol\CraftingDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\MainLogger;
|
||||
@ -54,7 +54,6 @@ class CraftingManager{
|
||||
switch($recipe["type"]){
|
||||
case 0:
|
||||
// TODO: handle multiple result items
|
||||
if(count($recipe["output"]) === 1){
|
||||
$first = $recipe["output"][0];
|
||||
$result = new ShapelessRecipe(Item::get($first["id"], $first["damage"], $first["count"], $first["nbt"]));
|
||||
|
||||
@ -62,11 +61,9 @@ class CraftingManager{
|
||||
$result->addIngredient(Item::get($ingredient["id"], $ingredient["damage"], $ingredient["count"], $first["nbt"]));
|
||||
}
|
||||
$this->registerRecipe($result);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// TODO: handle multiple result items
|
||||
if(count($recipe["output"]) === 1){
|
||||
$first = $recipe["output"][0];
|
||||
$result = new ShapedRecipe(Item::get($first["id"], $first["damage"], $first["count"], $first["nbt"]), $recipe["height"], $recipe["width"]);
|
||||
|
||||
@ -77,7 +74,6 @@ class CraftingManager{
|
||||
}
|
||||
}
|
||||
$this->registerRecipe($result);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
|
@ -23,7 +23,7 @@ namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\network\protocol\BlockEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockEventPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\tile\Chest;
|
||||
|
||||
|
@ -21,10 +21,14 @@
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\types\WindowTypes;
|
||||
|
||||
/**
|
||||
* Saves all the information regarding default inventory sizes and types
|
||||
*/
|
||||
class InventoryType{
|
||||
|
||||
//NOTE: Do not confuse these with the network IDs.
|
||||
const CHEST = 0;
|
||||
const DOUBLE_CHEST = 1;
|
||||
const PLAYER = 2;
|
||||
@ -56,19 +60,19 @@ class InventoryType{
|
||||
return;
|
||||
}
|
||||
|
||||
static::$default[static::CHEST] = new InventoryType(27, "Chest", 0);
|
||||
static::$default[static::DOUBLE_CHEST] = new InventoryType(27 + 27, "Double Chest", 0);
|
||||
static::$default[static::PLAYER] = new InventoryType(36 + 4, "Player", 0); //36 CONTAINER, 4 ARMOR
|
||||
static::$default[static::CRAFTING] = new InventoryType(5, "Crafting", 1); //4 CRAFTING slots, 1 RESULT
|
||||
static::$default[static::WORKBENCH] = new InventoryType(10, "Crafting", 1); //9 CRAFTING slots, 1 RESULT
|
||||
static::$default[static::FURNACE] = new InventoryType(3, "Furnace", 2); //2 INPUT, 1 OUTPUT
|
||||
static::$default[static::ENCHANT_TABLE] = new InventoryType(2, "Enchant", 3); //1 INPUT/OUTPUT, 1 LAPIS
|
||||
static::$default[static::BREWING_STAND] = new InventoryType(4, "Brewing", 4); //1 INPUT, 3 POTION
|
||||
static::$default[static::ANVIL] = new InventoryType(3, "Anvil", 5); //2 INPUT, 1 OUTPUT
|
||||
//TODO: add the below
|
||||
//6: dispenser
|
||||
//7: dropper
|
||||
//8: hopper
|
||||
//TODO: move network stuff out of here
|
||||
//TODO: move inventory data to json
|
||||
static::$default = [
|
||||
static::CHEST => new InventoryType(27, "Chest", WindowTypes::CONTAINER),
|
||||
static::DOUBLE_CHEST => new InventoryType(27 + 27, "Double Chest", WindowTypes::CONTAINER),
|
||||
static::PLAYER => new InventoryType(36 + 4, "Player", WindowTypes::INVENTORY), //36 CONTAINER, 4 ARMOR
|
||||
static::CRAFTING => new InventoryType(5, "Crafting", WindowTypes::INVENTORY), //yes, the use of INVENTORY is intended! 4 CRAFTING slots, 1 RESULT
|
||||
static::WORKBENCH => new InventoryType(10, "Crafting", WindowTypes::WORKBENCH), //9 CRAFTING slots, 1 RESULT
|
||||
static::FURNACE => new InventoryType(3, "Furnace", WindowTypes::FURNACE), //2 INPUT, 1 OUTPUT
|
||||
static::ENCHANT_TABLE => new InventoryType(2, "Enchant", WindowTypes::ENCHANTMENT), //1 INPUT/OUTPUT, 1 LAPIS
|
||||
static::BREWING_STAND => new InventoryType(4, "Brewing", WindowTypes::BREWING_STAND), //1 INPUT, 3 POTION
|
||||
static::ANVIL => new InventoryType(3, "Anvil", WindowTypes::ANVIL) //2 INPUT, 1 OUTP
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,10 +26,10 @@ use pocketmine\event\entity\EntityArmorChangeEvent;
|
||||
use pocketmine\event\entity\EntityInventoryChangeEvent;
|
||||
use pocketmine\event\player\PlayerItemHeldEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\network\protocol\ContainerSetContentPacket;
|
||||
use pocketmine\network\protocol\ContainerSetSlotPacket;
|
||||
use pocketmine\network\protocol\MobArmorEquipmentPacket;
|
||||
use pocketmine\network\protocol\MobEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
@ -40,7 +40,7 @@ class PlayerInventory extends BaseInventory{
|
||||
protected $hotbar;
|
||||
|
||||
public function __construct(Human $player){
|
||||
$this->hotbar = range(0, $this->getHotbarSize() - 1, 1);
|
||||
$this->resetHotbar(false);
|
||||
parent::__construct($player, InventoryType::get(InventoryType::PLAYER));
|
||||
}
|
||||
|
||||
@ -53,20 +53,95 @@ class PlayerInventory extends BaseInventory{
|
||||
$this->sendContents($this->getViewers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a client equips a hotbar slot. This method should not be used by plugins.
|
||||
* This method will call PlayerItemHeldEvent.
|
||||
*
|
||||
* @param int $hotbarSlot Number of the hotbar slot to equip.
|
||||
* @param int|null $inventorySlot Inventory slot to map to the specified hotbar slot. Supply null to make no change to the link.
|
||||
*
|
||||
* @return bool if the equipment change was successful, false if not.
|
||||
*/
|
||||
public function equipItem(int $hotbarSlot, $inventorySlot = null) : bool{
|
||||
if($inventorySlot === null){
|
||||
$inventorySlot = $this->getHotbarSlotIndex($this->getHeldItemIndex());
|
||||
}
|
||||
if($hotbarSlot < 0 or $hotbarSlot >= $this->getHotbarSize() or $inventorySlot < -1 or $inventorySlot >= $this->getSize()){
|
||||
$this->sendContents($this->getHolder());
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($inventorySlot), $inventorySlot, $hotbarSlot));
|
||||
if($ev->isCancelled()){
|
||||
$this->sendContents($this->getHolder());
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->setHotbarSlotIndex($hotbarSlot, $inventorySlot);
|
||||
$this->setHeldItemIndex($hotbarSlot, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the inventory slot mapped to the specified hotbar slot, or -1 if the hotbar slot does not exist.
|
||||
* @param int $index
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHotbarSlotIndex($index){
|
||||
return ($index >= 0 and $index < $this->getHotbarSize()) ? $this->hotbar[$index] : -1;
|
||||
return $this->hotbar[$index] ?? -1;
|
||||
}
|
||||
|
||||
public function setHotbarSlotIndex($index, $slot){
|
||||
if($index >= 0 and $index < $this->getHotbarSize() and $slot >= -1 and $slot < $this->getSize()){
|
||||
$this->hotbar[$index] = $slot;
|
||||
/**
|
||||
* Links a hotbar slot to the specified slot in the main inventory. -1 links to no slot and will clear the hotbar slot.
|
||||
* This method is intended for use in network interaction with clients only.
|
||||
*
|
||||
* NOTE: Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
|
||||
*
|
||||
* @param int $hotbarSlot
|
||||
* @param int $inventorySlot
|
||||
*/
|
||||
public function setHotbarSlotIndex($hotbarSlot, $inventorySlot){
|
||||
if($hotbarSlot >= 0 and $hotbarSlot < $this->getHotbarSize() and $inventorySlot >= -1 and $inventorySlot < $this->getSize()){
|
||||
if($inventorySlot !== -1 and ($alreadyEquippedIndex = array_search($inventorySlot, $this->hotbar)) !== false){
|
||||
/* Swap the slots
|
||||
* This assumes that the equipped slot can only be equipped in one other slot
|
||||
* it will not account for ancient bugs where the same slot ended up linked to several hotbar slots.
|
||||
* Such bugs will require a hotbar reset to default.
|
||||
*/
|
||||
$this->hotbar[$alreadyEquippedIndex] = $this->hotbar[$hotbarSlot];
|
||||
}
|
||||
$this->hotbar[$hotbarSlot] = $inventorySlot;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets hotbar links to their original defaults.
|
||||
* @param bool $send Whether to send changes to the holder.
|
||||
*/
|
||||
public function resetHotbar(bool $send = true){
|
||||
$this->hotbar = range(0, $this->getHotbarSize() - 1, 1);
|
||||
if($send){
|
||||
$this->sendContents($this->getHolder());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hotbar slot number the holder is currently holding.
|
||||
* @return int
|
||||
*/
|
||||
public function getHeldItemIndex(){
|
||||
return $this->itemInHandIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets which hotbar slot the player is currently loading.
|
||||
*
|
||||
* @param int $index 0-8 index of the hotbar slot to hold
|
||||
* @param bool $send Whether to send updates back to the inventory holder. This should usually be true for plugin calls.
|
||||
* It should only be false to prevent feedback loops of equipment packets between client and server.
|
||||
*/
|
||||
public function setHeldItemIndex($index, $send = true){
|
||||
if($index >= 0 and $index < $this->getHotbarSize()){
|
||||
$this->itemInHandIndex = $index;
|
||||
@ -79,6 +154,11 @@ class PlayerInventory extends BaseInventory{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently-held item.
|
||||
*
|
||||
* @return Item
|
||||
*/
|
||||
public function getItemInHand(){
|
||||
$item = $this->getItem($this->getHeldItemSlot());
|
||||
if($item instanceof Item){
|
||||
@ -89,6 +169,7 @@ class PlayerInventory extends BaseInventory{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the item in the currently-held slot to the specified item.
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
@ -97,39 +178,39 @@ class PlayerInventory extends BaseInventory{
|
||||
return $this->setItem($this->getHeldItemSlot(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hotbar slot number currently held.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHeldItemSlot(){
|
||||
return $this->getHotbarSlotIndex($this->itemInHandIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hotbar slot link of the currently-held hotbar slot.
|
||||
* @deprecated Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
|
||||
*
|
||||
* @param int $slot
|
||||
*/
|
||||
public function setHeldItemSlot($slot){
|
||||
if($slot >= -1 and $slot < $this->getSize()){
|
||||
$item = $this->getItem($slot);
|
||||
|
||||
$itemIndex = $this->getHeldItemIndex();
|
||||
|
||||
if($this->getHolder() instanceof Player){
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $item, $slot, $itemIndex));
|
||||
if($ev->isCancelled()){
|
||||
$this->sendContents($this->getHolder());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->setHotbarSlotIndex($itemIndex, $slot);
|
||||
$this->setHotbarSlotIndex($this->getHeldItemIndex(), $slot);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the currently-held item to specified targets.
|
||||
* @param Player|Player[] $target
|
||||
*/
|
||||
public function sendHeldItem($target){
|
||||
$item = $this->getItemInHand();
|
||||
|
||||
$pk = new MobEquipmentPacket();
|
||||
$pk->eid = ($target === $this->getHolder() ? 0 : $this->getHolder()->getId());
|
||||
$pk->eid = $this->getHolder()->getId();
|
||||
$pk->item = $item;
|
||||
$pk->slot = $this->getHeldItemSlot();
|
||||
$pk->selectedSlot = $this->getHeldItemIndex();
|
||||
$pk->inventorySlot = $this->getHeldItemSlot();
|
||||
$pk->hotbarSlot = $this->getHeldItemIndex();
|
||||
|
||||
if(!is_array($target)){
|
||||
$target->dataPacket($pk);
|
||||
@ -161,6 +242,10 @@ class PlayerInventory extends BaseInventory{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of slots in the hotbar.
|
||||
* @return int
|
||||
*/
|
||||
public function getHotbarSize(){
|
||||
return 9;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class SimpleTransactionGroup implements TransactionGroup{
|
||||
}
|
||||
$checkSourceItem = $ts->getInventory()->getItem($ts->getSlot());
|
||||
$sourceItem = $ts->getSourceItem();
|
||||
if(!$checkSourceItem->deepEquals($sourceItem) or $sourceItem->getCount() !== $checkSourceItem->getCount()){
|
||||
if(!$checkSourceItem->equals($sourceItem) or $sourceItem->getCount() !== $checkSourceItem->getCount()){
|
||||
return false;
|
||||
}
|
||||
if($sourceItem->getId() !== Item::AIR){
|
||||
@ -108,7 +108,7 @@ class SimpleTransactionGroup implements TransactionGroup{
|
||||
|
||||
foreach($needItems as $i => $needItem){
|
||||
foreach($haveItems as $j => $haveItem){
|
||||
if($needItem->deepEquals($haveItem)){
|
||||
if($needItem->equals($haveItem)){
|
||||
$amount = min($needItem->getCount(), $haveItem->getCount());
|
||||
$needItem->setCount($needItem->getCount() - $amount);
|
||||
$haveItem->setCount($haveItem->getCount() - $amount);
|
||||
@ -130,7 +130,21 @@ class SimpleTransactionGroup implements TransactionGroup{
|
||||
$haveItems = [];
|
||||
$needItems = [];
|
||||
|
||||
return $this->matchItems($haveItems, $needItems) and count($haveItems) === 0 and count($needItems) === 0 and count($this->transactions) > 0;
|
||||
if($this->matchItems($needItems, $haveItems) and count($this->transactions) > 0){
|
||||
if(count($haveItems) === 0 and count($needItems) === 0){
|
||||
return true;
|
||||
}elseif($this->source->isCreative(true) and count($needItems) > 0){ //Added items from creative inventory
|
||||
foreach($needItems as $item){
|
||||
if(Item::getCreativeItemIndex($item) === -1 and $item->getId() !== Item::AIR){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function execute(){
|
||||
|
@ -24,7 +24,7 @@ namespace pocketmine\item;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Human;
|
||||
use pocketmine\event\entity\EntityEatItemEvent;
|
||||
use pocketmine\network\protocol\EntityEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\EntityEventPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
abstract class Food extends Item implements FoodSource{
|
||||
|
@ -46,12 +46,22 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
private static $cachedParser = null;
|
||||
|
||||
private static function parseCompoundTag(string $tag) : CompoundTag{
|
||||
if(strlen($tag) === 0){
|
||||
throw new \InvalidArgumentException("No NBT data found in supplied string");
|
||||
}
|
||||
|
||||
if(self::$cachedParser === null){
|
||||
self::$cachedParser = new NBT(NBT::LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
self::$cachedParser->read($tag);
|
||||
return self::$cachedParser->getData();
|
||||
$data = self::$cachedParser->getData();
|
||||
|
||||
if(!($data instanceof CompoundTag)){
|
||||
throw new \InvalidArgumentException("Invalid item NBT string given, it could not be deserialized");
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private static function writeCompoundTag(CompoundTag $tag) : string{
|
||||
@ -65,13 +75,19 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
|
||||
/** @var \SplFixedArray */
|
||||
public static $list = null;
|
||||
/** @var Block|null */
|
||||
protected $block;
|
||||
/** @var int */
|
||||
protected $id;
|
||||
/** @var int */
|
||||
protected $meta;
|
||||
/** @var string */
|
||||
private $tags = "";
|
||||
/** @var CompoundTag|null */
|
||||
private $cachedNBT = null;
|
||||
/** @var int */
|
||||
public $count;
|
||||
protected $durability = 0;
|
||||
/** @var string */
|
||||
protected $name;
|
||||
|
||||
public function canBeActivated(){
|
||||
@ -291,7 +307,17 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static function get(int $id, int $meta = 0, int $count = 1, string $tags = "") : Item{
|
||||
/**
|
||||
* Returns an instance of the Item with the specified id, meta, count and NBT.
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $meta
|
||||
* @param int $count
|
||||
* @param CompoundTag|string $tags
|
||||
*
|
||||
* @return Item
|
||||
*/
|
||||
public static function get(int $id, int $meta = 0, int $count = 1, $tags = "") : Item{
|
||||
try{
|
||||
$class = self::$list[$id];
|
||||
if($class === null){
|
||||
@ -341,6 +367,12 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param int $meta
|
||||
* @param int $count
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(int $id, int $meta = 0, int $count = 1, string $name = "Unknown"){
|
||||
$this->id = $id & 0xffff;
|
||||
$this->meta = $meta !== -1 ? $meta & 0xffff : -1;
|
||||
@ -352,6 +384,13 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Item's NBT
|
||||
*
|
||||
* @param CompoundTag|string $tags
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCompoundTag($tags){
|
||||
if($tags instanceof CompoundTag){
|
||||
$this->setNamedTag($tags);
|
||||
@ -364,16 +403,24 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serialized NBT of the Item
|
||||
* @return string
|
||||
*/
|
||||
public function getCompoundTag() : string{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this Item has a non-empty NBT.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCompoundTag() : bool{
|
||||
return $this->tags !== "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCustomBlockData() : bool{
|
||||
if(!$this->hasCompoundTag()){
|
||||
return false;
|
||||
@ -401,6 +448,11 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CompoundTag $compound
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCustomBlockData(CompoundTag $compound){
|
||||
$tags = clone $compound;
|
||||
$tags->setName("BlockEntityTag");
|
||||
@ -417,6 +469,9 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CompoundTag|null
|
||||
*/
|
||||
public function getCustomBlockData(){
|
||||
if(!$this->hasCompoundTag()){
|
||||
return null;
|
||||
@ -430,6 +485,9 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasEnchantments() : bool{
|
||||
if(!$this->hasCompoundTag()){
|
||||
return false;
|
||||
@ -447,7 +505,7 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @param int $id
|
||||
*
|
||||
* @return Enchantment|null
|
||||
*/
|
||||
@ -524,6 +582,9 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return $enchantments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCustomName() : bool{
|
||||
if(!$this->hasCompoundTag()){
|
||||
return false;
|
||||
@ -540,6 +601,9 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCustomName() : string{
|
||||
if(!$this->hasCompoundTag()){
|
||||
return "";
|
||||
@ -556,6 +620,11 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCustomName(string $name){
|
||||
if($name === ""){
|
||||
$this->clearCustomName();
|
||||
@ -580,6 +649,9 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function clearCustomName(){
|
||||
if(!$this->hasCompoundTag()){
|
||||
return $this;
|
||||
@ -598,6 +670,35 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLore() : array{
|
||||
$tag = $this->getNamedTagEntry("display");
|
||||
if($tag instanceof CompoundTag and isset($tag->Lore) and $tag->Lore instanceof ListTag){
|
||||
$lines = [];
|
||||
foreach($tag->Lore->getValue() as $line){
|
||||
$lines[] = $line->getValue();
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function setLore(array $lines){
|
||||
$tag = $this->getNamedTag() ?? new CompoundTag("", []);
|
||||
if(!isset($tag->display)){
|
||||
$tag->display = new CompoundTag("display", []);
|
||||
}
|
||||
$tag->display->Lore = new ListTag("Lore");
|
||||
$tag->display->Lore->setTagType(NBT::TAG_String);
|
||||
$count = 0;
|
||||
foreach($lines as $line){
|
||||
$tag->display->Lore[$count++] = new StringTag("", $line);
|
||||
}
|
||||
|
||||
$this->setNamedTag($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return Tag|null
|
||||
@ -611,6 +712,10 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a tree of Tag objects representing the Item's NBT
|
||||
* @return null|CompoundTag
|
||||
*/
|
||||
public function getNamedTag(){
|
||||
if(!$this->hasCompoundTag()){
|
||||
return null;
|
||||
@ -620,6 +725,12 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return $this->cachedNBT = self::parseCompoundTag($this->tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Item's NBT from the supplied CompoundTag object.
|
||||
* @param CompoundTag $tag
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setNamedTag(CompoundTag $tag){
|
||||
if($tag->getCount() === 0){
|
||||
return $this->clearNamedTag();
|
||||
@ -631,37 +742,73 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the Item's NBT.
|
||||
* @return Item
|
||||
*/
|
||||
public function clearNamedTag(){
|
||||
return $this->setCompoundTag("");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCount() : int{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $count
|
||||
*/
|
||||
public function setCount(int $count){
|
||||
$this->count = $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the item, or the custom name if it is set.
|
||||
* @return string
|
||||
*/
|
||||
final public function getName() : string{
|
||||
return $this->hasCustomName() ? $this->getCustomName() : $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
final public function canBePlaced() : bool{
|
||||
return $this->block !== null and $this->block->canBePlaced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether an entity can eat or drink this item.
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeConsumed() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this item can be consumed by the supplied Entity.
|
||||
* @param Entity $entity
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeConsumedBy(Entity $entity) : bool{
|
||||
return $this->canBeConsumed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the item is consumed by an Entity.
|
||||
* @param Entity $entity
|
||||
*/
|
||||
public function onConsume(Entity $entity){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block corresponding to this Item.
|
||||
* @return Block
|
||||
*/
|
||||
public function getBlock() : Block{
|
||||
if($this->block instanceof Block){
|
||||
return clone $this->block;
|
||||
@ -670,22 +817,41 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
final public function getId() : int{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
final public function getDamage() : int{
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $meta
|
||||
*/
|
||||
public function setDamage(int $meta){
|
||||
$this->meta = $meta !== -1 ? $meta & 0xFFFF : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this item can match any item with an equivalent ID with any meta value.
|
||||
* Used in crafting recipes which accept multiple variants of the same item, for example crafting tables recipes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAnyDamageValue() : bool{
|
||||
return $this->meta === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest amount of this item which will fit into one inventory slot.
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxStackSize(){
|
||||
return 64;
|
||||
}
|
||||
@ -752,28 +918,75 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a player uses this item on a block.
|
||||
*
|
||||
* @param Level $level
|
||||
* @param Player $player
|
||||
* @param Block $block
|
||||
* @param Block $target
|
||||
* @param int $face
|
||||
* @param float $fx
|
||||
* @param float $fy
|
||||
* @param float $fz
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){
|
||||
return false;
|
||||
}
|
||||
|
||||
public final function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
|
||||
return $this->id === $item->getId() and ($checkDamage === false or $this->getDamage() === $item->getDamage()) and ($checkCompound === false or $this->getCompoundTag() === $item->getCompoundTag());
|
||||
}
|
||||
|
||||
public final function deepEquals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
|
||||
if($this->equals($item, $checkDamage, $checkCompound)){
|
||||
/**
|
||||
* Compares an Item to this Item and check if they match.
|
||||
*
|
||||
* @param Item $item
|
||||
* @param bool $checkDamage Whether to verify that the damage values match.
|
||||
* @param bool $checkCompound Whether to verify that the items' NBT match.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final public function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
|
||||
if($this->id === $item->getId() and ($checkDamage === false or $this->getDamage() === $item->getDamage())){
|
||||
if($checkCompound){
|
||||
if($item->getCompoundTag() === $this->getCompoundTag()){
|
||||
return true;
|
||||
}elseif($item->hasCompoundTag() and $this->hasCompoundTag()){
|
||||
}elseif($this->hasCompoundTag() and $item->hasCompoundTag()){
|
||||
//Serialized NBT didn't match, check the cached object tree.
|
||||
return NBT::matchTree($this->getNamedTag(), $item->getNamedTag());
|
||||
}
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
final public function __toString() : string{
|
||||
return "Item " . $this->name . " (" . $this->id . ":" . ($this->meta === null ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : "");
|
||||
/**
|
||||
* @deprecated Use {@link Item#equals} instead, this method will be removed in the future.
|
||||
*
|
||||
* @param Item $item
|
||||
* @param bool $checkDamage
|
||||
* @param bool $checkCompound
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final public function deepEquals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
|
||||
return $this->equals($item, $checkDamage, $checkCompound);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
final public function __toString() : string{
|
||||
return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of item stack properties that can be serialized to json.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
final public function jsonSerialize(){
|
||||
return [
|
||||
"id" => $this->id,
|
||||
@ -839,4 +1052,12 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function __clone(){
|
||||
if($this->block !== null){
|
||||
$this->block = clone $this->block;
|
||||
}
|
||||
|
||||
$this->cachedNBT = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,10 +37,6 @@ class ItemBlock extends Item{
|
||||
$this->block->setDamage($this->meta !== -1 ? $this->meta : 0);
|
||||
}
|
||||
|
||||
public function __clone(){
|
||||
$this->block = clone $this->block;
|
||||
}
|
||||
|
||||
public function getBlock() : Block{
|
||||
return $this->block;
|
||||
}
|
||||
|
@ -34,7 +34,11 @@ class BaseLang{
|
||||
$path = \pocketmine\PATH . "src/pocketmine/lang/locale/";
|
||||
}
|
||||
|
||||
$files = array_filter(scandir($path), function($filename){
|
||||
if(is_dir($path)){
|
||||
$allFiles = scandir($path);
|
||||
|
||||
if($allFiles !== false){
|
||||
$files = array_filter($allFiles, function($filename){
|
||||
return substr($filename, -4) === ".ini";
|
||||
});
|
||||
|
||||
@ -50,6 +54,10 @@ class BaseLang{
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected $langName;
|
||||
|
||||
|
Submodule src/pocketmine/lang/locale updated: 3499fe4a6a...2ab4d9172e
@ -38,7 +38,7 @@ use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\DoubleTag;
|
||||
use pocketmine\nbt\tag\FloatTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\network\protocol\ExplodePacket;
|
||||
use pocketmine\network\mcpe\protocol\ExplodePacket;
|
||||
use pocketmine\utils\Random;
|
||||
|
||||
class Explosion{
|
||||
@ -201,7 +201,7 @@ class Explosion{
|
||||
|
||||
$pos = new Vector3($block->x, $block->y, $block->z);
|
||||
|
||||
for($side = 0; $side < 5; $side++){
|
||||
for($side = 0; $side <= 5; $side++){
|
||||
$sideBlock = $pos->getSide($side);
|
||||
if(!isset($this->affectedBlocks[$index = Level::blockHash($sideBlock->x, $sideBlock->y, $sideBlock->z)]) and !isset($updateBlocks[$index])){
|
||||
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->level->getBlock($sideBlock)));
|
||||
|
@ -90,14 +90,14 @@ use pocketmine\nbt\tag\FloatTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\network\protocol\BatchPacket;
|
||||
use pocketmine\network\protocol\DataPacket;
|
||||
use pocketmine\network\protocol\FullChunkDataPacket;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\protocol\MoveEntityPacket;
|
||||
use pocketmine\network\protocol\SetEntityMotionPacket;
|
||||
use pocketmine\network\protocol\SetTimePacket;
|
||||
use pocketmine\network\protocol\UpdateBlockPacket;
|
||||
use pocketmine\network\mcpe\protocol\BatchPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\FullChunkDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\MoveEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetEntityMotionPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTimePacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\Server;
|
||||
@ -134,9 +134,6 @@ class Level implements ChunkManager, Metadatable{
|
||||
/** @var Tile[] */
|
||||
private $tiles = [];
|
||||
|
||||
private $motionToSend = [];
|
||||
private $moveToSend = [];
|
||||
|
||||
/** @var Player[] */
|
||||
private $players = [];
|
||||
|
||||
@ -155,6 +152,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
private $cacheChunks = false;
|
||||
|
||||
private $sendTimeTicker = 0;
|
||||
|
||||
/** @var Server */
|
||||
private $server;
|
||||
|
||||
@ -191,8 +190,11 @@ class Level implements ChunkManager, Metadatable{
|
||||
private $changedBlocks = [];
|
||||
|
||||
/** @var ReversePriorityQueue */
|
||||
private $updateQueue;
|
||||
private $updateQueueIndex = [];
|
||||
private $scheduledBlockUpdateQueue;
|
||||
private $scheduledBlockUpdateQueueIndex = [];
|
||||
|
||||
/** @var \SplQueue */
|
||||
private $neighbourBlockUpdateQueue = [];
|
||||
|
||||
/** @var Player[][] */
|
||||
private $chunkSendQueue = [];
|
||||
@ -334,8 +336,11 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->generator = Generator::getGenerator($this->provider->getGenerator());
|
||||
|
||||
$this->folderName = $name;
|
||||
$this->updateQueue = new ReversePriorityQueue();
|
||||
$this->updateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
|
||||
$this->scheduledBlockUpdateQueue = new ReversePriorityQueue();
|
||||
$this->scheduledBlockUpdateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
|
||||
|
||||
$this->neighbourBlockUpdateQueue = new \SplQueue();
|
||||
|
||||
$this->time = (int) $this->provider->getTime();
|
||||
|
||||
$this->chunkTickRadius = min($this->server->getViewDistance(), max(1, (int) $this->server->getProperty("chunk-ticking.tick-radius", 4)));
|
||||
@ -417,7 +422,9 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
public function close(){
|
||||
assert(!$this->closed, "Tried to close a level which is already closed");
|
||||
if($this->closed){
|
||||
throw new \InvalidStateException("Tried to close a level which is already closed");
|
||||
}
|
||||
|
||||
if($this->getAutoSave()){
|
||||
$this->save();
|
||||
@ -620,7 +627,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
if($this->stopTime === true){
|
||||
return;
|
||||
}else{
|
||||
$this->time += $this->tickRate;
|
||||
$this->time += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -644,20 +651,41 @@ class Level implements ChunkManager, Metadatable{
|
||||
*
|
||||
*/
|
||||
public function doTick(int $currentTick){
|
||||
if($this->closed){
|
||||
throw new \InvalidStateException("Attempted to tick a Level which has been closed");
|
||||
}
|
||||
|
||||
$this->timings->doTick->startTiming();
|
||||
|
||||
$this->checkTime();
|
||||
|
||||
if(++$this->sendTimeTicker === 200){
|
||||
$this->sendTime();
|
||||
$this->sendTimeTicker = 0;
|
||||
}
|
||||
|
||||
$this->unloadChunks();
|
||||
|
||||
//Do block updates
|
||||
$this->timings->doTickPending->startTiming();
|
||||
while($this->updateQueue->count() > 0 and $this->updateQueue->current()["priority"] <= $currentTick){
|
||||
$block = $this->getBlock($this->updateQueue->extract()["data"]);
|
||||
unset($this->updateQueueIndex[Level::blockHash($block->x, $block->y, $block->z)]);
|
||||
|
||||
//Delayed updates
|
||||
while($this->scheduledBlockUpdateQueue->count() > 0 and $this->scheduledBlockUpdateQueue->current()["priority"] <= $currentTick){
|
||||
$block = $this->getBlock($this->scheduledBlockUpdateQueue->extract()["data"]);
|
||||
unset($this->scheduledBlockUpdateQueueIndex[Level::blockHash($block->x, $block->y, $block->z)]);
|
||||
$block->onUpdate(self::BLOCK_UPDATE_SCHEDULED);
|
||||
}
|
||||
|
||||
//Normal updates
|
||||
while($this->neighbourBlockUpdateQueue->count() > 0){
|
||||
$index = $this->neighbourBlockUpdateQueue->dequeue();
|
||||
Level::getBlockXYZ($index, $x, $y, $z);
|
||||
$this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($x, $y, $z))));
|
||||
if(!$ev->isCancelled()){
|
||||
$ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
$this->timings->doTickPending->stopTiming();
|
||||
|
||||
$this->timings->entityTick->startTiming();
|
||||
@ -716,35 +744,6 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->checkSleep();
|
||||
}
|
||||
|
||||
foreach($this->moveToSend as $index => $entry){
|
||||
Level::getXZ($index, $chunkX, $chunkZ);
|
||||
foreach($entry as $e){
|
||||
$pk = new MoveEntityPacket();
|
||||
$pk->eid = $e[0];
|
||||
$pk->x = $e[1];
|
||||
$pk->y = $e[2];
|
||||
$pk->z = $e[3];
|
||||
$pk->yaw = $e[4];
|
||||
$pk->headYaw = $e[5];
|
||||
$pk->pitch = $e[6];
|
||||
$this->addChunkPacket($chunkX, $chunkZ, $pk);
|
||||
}
|
||||
}
|
||||
$this->moveToSend = [];
|
||||
|
||||
foreach($this->motionToSend as $index => $entry){
|
||||
Level::getXZ($index, $chunkX, $chunkZ);
|
||||
foreach($entry as $entity){
|
||||
$pk = new SetEntityMotionPacket();
|
||||
$pk->eid = $entity[0];
|
||||
$pk->motionX = $entity[1];
|
||||
$pk->motionY = $entity[2];
|
||||
$pk->motionZ = $entity[3];
|
||||
$this->addChunkPacket($chunkX, $chunkZ, $pk);
|
||||
}
|
||||
}
|
||||
$this->motionToSend = [];
|
||||
|
||||
foreach($this->chunkPackets as $index => $entries){
|
||||
Level::getXZ($index, $chunkX, $chunkZ);
|
||||
$chunkPlayers = $this->getChunkPlayers($chunkX, $chunkZ);
|
||||
@ -992,7 +991,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
public function saveChunks(){
|
||||
foreach($this->chunks as $chunk){
|
||||
if($chunk->hasChanged()){
|
||||
if($chunk->hasChanged() and $chunk->isGenerated()){
|
||||
$this->provider->setChunk($chunk->getX(), $chunk->getZ(), $chunk);
|
||||
$this->provider->saveChunk($chunk->getX(), $chunk->getZ());
|
||||
$chunk->setChanged(false);
|
||||
@ -1037,15 +1036,45 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in the future due to misleading/ambiguous name. Use {@link Level#scheduleDelayedBlockUpdate} instead.
|
||||
*
|
||||
* @param Vector3 $pos
|
||||
* @param int $delay
|
||||
*/
|
||||
public function scheduleUpdate(Vector3 $pos, int $delay){
|
||||
if(isset($this->updateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->updateQueueIndex[$index] <= $delay){
|
||||
$this->scheduleDelayedBlockUpdate($pos, $delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a block update to be executed after the specified number of ticks.
|
||||
* Blocks will be updated with the scheduled update type.
|
||||
*
|
||||
* @param Vector3 $pos
|
||||
* @param int $delay
|
||||
*/
|
||||
public function scheduleDelayedBlockUpdate(Vector3 $pos, int $delay){
|
||||
if(isset($this->scheduledBlockUpdateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->scheduledBlockUpdateQueueIndex[$index] <= $delay){
|
||||
return;
|
||||
}
|
||||
$this->updateQueueIndex[$index] = $delay;
|
||||
$this->updateQueue->insert(new Vector3((int) $pos->x, (int) $pos->y, (int) $pos->z), (int) $delay + $this->server->getTick());
|
||||
$this->scheduledBlockUpdateQueueIndex[$index] = $delay;
|
||||
$this->scheduledBlockUpdateQueue->insert(new Vector3((int) $pos->x, (int) $pos->y, (int) $pos->z), (int) $delay + $this->server->getTick());
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the blocks around the specified position to be updated at the end of this tick.
|
||||
* Blocks will be updated with the normal update type.
|
||||
*
|
||||
* @param Vector3 $pos
|
||||
*/
|
||||
public function scheduleNeighbourBlockUpdates(Vector3 $pos){
|
||||
$pos = $pos->floor();
|
||||
|
||||
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x + 1, $pos->y, $pos->z));
|
||||
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x - 1, $pos->y, $pos->z));
|
||||
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y + 1, $pos->z));
|
||||
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y - 1, $pos->z));
|
||||
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y, $pos->z + 1));
|
||||
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y, $pos->z - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1279,17 +1308,42 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
public function updateBlockSkyLight(int $x, int $y, int $z){
|
||||
$this->timings->doBlockSkyLightUpdates->startTiming();
|
||||
//TODO
|
||||
$this->timings->doBlockSkyLightUpdates->stopTiming();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest light level available in the positions adjacent to the specified block coordinates.
|
||||
*
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $z
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHighestAdjacentBlockLight(int $x, int $y, int $z) : int{
|
||||
return max([
|
||||
$this->getBlockLightAt($x + 1, $y, $z),
|
||||
$this->getBlockLightAt($x - 1, $y, $z),
|
||||
$this->getBlockLightAt($x, $y + 1, $z),
|
||||
$this->getBlockLightAt($x, $y - 1, $z),
|
||||
$this->getBlockLightAt($x, $y, $z + 1),
|
||||
$this->getBlockLightAt($x, $y, $z - 1)
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateBlockLight(int $x, int $y, int $z){
|
||||
$this->timings->doBlockLightUpdates->startTiming();
|
||||
|
||||
$lightPropagationQueue = new \SplQueue();
|
||||
$lightRemovalQueue = new \SplQueue();
|
||||
$visited = [];
|
||||
$removalVisited = [];
|
||||
|
||||
$id = $this->getBlockIdAt($x, $y, $z);
|
||||
$oldLevel = $this->getBlockLightAt($x, $y, $z);
|
||||
$newLevel = (int) Block::$light[$this->getBlockIdAt($x, $y, $z)];
|
||||
$newLevel = max(Block::$light[$id], $this->getHighestAdjacentBlockLight($x, $y, $z) - Block::$lightFilter[$id]);
|
||||
|
||||
if($oldLevel !== $newLevel){
|
||||
$this->setBlockLightAt($x, $y, $z, $newLevel);
|
||||
@ -1321,7 +1375,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
/** @var Vector3 $node */
|
||||
$node = $lightPropagationQueue->dequeue();
|
||||
|
||||
$lightLevel = $this->getBlockLightAt($node->x, $node->y, $node->z) - (int) Block::$lightFilter[$this->getBlockIdAt($node->x, $node->y, $node->z)];
|
||||
$lightLevel = $this->getBlockLightAt($node->x, $node->y, $node->z);
|
||||
|
||||
if($lightLevel >= 1){
|
||||
$this->computeSpreadBlockLight($node->x - 1, $node->y, $node->z, $lightLevel, $lightPropagationQueue, $visited);
|
||||
@ -1332,6 +1386,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->computeSpreadBlockLight($node->x, $node->y, $node->z + 1, $lightLevel, $lightPropagationQueue, $visited);
|
||||
}
|
||||
}
|
||||
|
||||
$this->timings->doBlockLightUpdates->stopTiming();
|
||||
}
|
||||
|
||||
private function computeRemoveBlockLight(int $x, int $y, int $z, int $currentLight, \SplQueue $queue, \SplQueue $spreadQueue, array &$visited, array &$spreadVisited){
|
||||
@ -1358,6 +1414,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
private function computeSpreadBlockLight(int $x, int $y, int $z, int $currentLight, \SplQueue $queue, array &$visited){
|
||||
if($y < 0) return;
|
||||
$current = $this->getBlockLightAt($x, $y, $z);
|
||||
$currentLight -= Block::$lightFilter[$this->getBlockIdAt($x, $y, $z)];
|
||||
|
||||
if($current < $currentLight){
|
||||
$this->setBlockLightAt($x, $y, $z, $currentLight);
|
||||
@ -1395,6 +1452,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->timings->setBlock->startTiming();
|
||||
|
||||
if($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f, $block->getId(), $block->getDamage())){
|
||||
if(!($pos instanceof Position)){
|
||||
$pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z);
|
||||
@ -1429,14 +1488,17 @@ class Level implements ChunkManager, Metadatable{
|
||||
$entity->scheduleUpdate();
|
||||
}
|
||||
$ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
|
||||
$this->scheduleNeighbourBlockUpdates($pos);
|
||||
}
|
||||
}
|
||||
|
||||
$this->updateAround($pos);
|
||||
}
|
||||
$this->timings->setBlock->stopTiming();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->timings->setBlock->stopTiming();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1499,41 +1561,60 @@ class Level implements ChunkManager, Metadatable{
|
||||
if($player !== null){
|
||||
$ev = new BlockBreakEvent($player, $target, $item, ($player->isCreative() or $player->allowInstaBreak()));
|
||||
|
||||
if($player->isSurvival() and $item instanceof Item and !$target->isBreakable($item)){
|
||||
if(($player->isSurvival() and $item instanceof Item and !$target->isBreakable($item)) or $player->isSpectator()){
|
||||
$ev->setCancelled();
|
||||
}elseif(!$player->isOp() and ($distance = $this->server->getSpawnRadius()) > -1){
|
||||
}elseif(!$player->hasPermission("pocketmine.spawnprotect.bypass") and ($distance = $this->server->getSpawnRadius()) > -1){
|
||||
$t = new Vector2($target->x, $target->z);
|
||||
$s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z);
|
||||
if(count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this
|
||||
$ev->setCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
if($player->isAdventure(true) and !$ev->isCancelled()){
|
||||
$tag = $item->getNamedTagEntry("CanDestroy");
|
||||
$canBreak = false;
|
||||
if($tag instanceof ListTag){
|
||||
foreach($tag as $v){
|
||||
if($v instanceof StringTag){
|
||||
$entry = Item::fromString($v->getValue());
|
||||
if($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()){
|
||||
$canBreak = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ev->setCancelled(!$canBreak);
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
|
||||
$breakTime = $target->getBreakTime($item);
|
||||
$breakTime = ceil($target->getBreakTime($item) * 20);
|
||||
|
||||
if($player->isCreative() and $breakTime > 0.15){
|
||||
$breakTime = 0.15;
|
||||
if($player->isCreative() and $breakTime > 3){
|
||||
$breakTime = 3;
|
||||
}
|
||||
|
||||
if($player->hasEffect(Effect::SWIFTNESS)){
|
||||
$breakTime *= 1 - (0.2 * ($player->getEffect(Effect::SWIFTNESS)->getAmplifier() + 1));
|
||||
if($player->hasEffect(Effect::HASTE)){
|
||||
$breakTime *= 1 - (0.2 * ($player->getEffect(Effect::HASTE)->getAmplifier() + 1));
|
||||
}
|
||||
|
||||
if($player->hasEffect(Effect::MINING_FATIGUE)){
|
||||
$breakTime *= 1 + (0.3 * ($player->getEffect(Effect::MINING_FATIGUE)->getAmplifier() + 1));
|
||||
}
|
||||
|
||||
$breakTime -= 0.05; //1 tick compensation
|
||||
$breakTime -= 1; //1 tick compensation
|
||||
|
||||
if(!$ev->getInstaBreak() and ($player->lastBreak + $breakTime) > microtime(true)){
|
||||
if(!$ev->getInstaBreak() and ((ceil($player->lastBreak * 20)) + $breakTime) > ceil(microtime(true) * 20)){
|
||||
return false;
|
||||
}
|
||||
|
||||
$player->lastBreak = microtime(true);
|
||||
$player->lastBreak = PHP_INT_MAX;
|
||||
|
||||
$drops = $ev->getDrops();
|
||||
|
||||
@ -1553,24 +1634,6 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
}
|
||||
|
||||
$tag = $item->getNamedTagEntry("CanDestroy");
|
||||
if($tag instanceof ListTag){
|
||||
$canBreak = false;
|
||||
foreach($tag as $v){
|
||||
if($v instanceof StringTag){
|
||||
$entry = Item::fromString($v->getValue());
|
||||
if($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()){
|
||||
$canBreak = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!$canBreak){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if($createParticles){
|
||||
$this->addParticle(new DestroyBlockParticle($target->add(0.5, 0.5, 0.5), $target));
|
||||
}
|
||||
@ -1638,13 +1701,32 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
if($player !== null){
|
||||
$ev = new PlayerInteractEvent($player, $item, $target, $face, $target->getId() === 0 ? PlayerInteractEvent::RIGHT_CLICK_AIR : PlayerInteractEvent::RIGHT_CLICK_BLOCK);
|
||||
if(!$player->isOp() and ($distance = $this->server->getSpawnRadius()) > -1){
|
||||
if(!$player->hasPermission("pocketmine.spawnprotect.bypass") and ($distance = $this->server->getSpawnRadius()) > -1){
|
||||
$t = new Vector2($target->x, $target->z);
|
||||
$s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z);
|
||||
if(count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this
|
||||
$ev->setCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
if($player->isAdventure(true) and !$ev->isCancelled()){
|
||||
$canPlace = false;
|
||||
$tag = $item->getNamedTagEntry("CanPlaceOn");
|
||||
if($tag instanceof ListTag){
|
||||
foreach($tag as $v){
|
||||
if($v instanceof StringTag){
|
||||
$entry = Item::fromString($v->getValue());
|
||||
if($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()){
|
||||
$canPlace = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ev->setCancelled(!$canPlace);
|
||||
}
|
||||
|
||||
$this->server->getPluginManager()->callEvent($ev);
|
||||
if(!$ev->isCancelled()){
|
||||
$target->onUpdate(self::BLOCK_UPDATE_TOUCH);
|
||||
@ -1707,28 +1789,10 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
}
|
||||
|
||||
$tag = $item->getNamedTagEntry("CanPlaceOn");
|
||||
if($tag instanceof ListTag){
|
||||
$canPlace = false;
|
||||
foreach($tag as $v){
|
||||
if($v instanceof StringTag){
|
||||
$entry = Item::fromString($v->getValue());
|
||||
if($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()){
|
||||
$canPlace = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!$canPlace){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($player !== null){
|
||||
$ev = new BlockPlaceEvent($player, $hand, $block, $target, $item);
|
||||
if(!$player->isOp() and ($distance = $this->server->getSpawnRadius()) > -1){
|
||||
if(!$player->hasPermission("pocketmine.spawnprotect.bypass") and ($distance = $this->server->getSpawnRadius()) > -1){
|
||||
$t = new Vector2($target->x, $target->z);
|
||||
$s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z);
|
||||
if(count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this
|
||||
@ -2166,18 +2230,20 @@ class Level implements ChunkManager, Metadatable{
|
||||
$oldEntities = $oldChunk !== null ? $oldChunk->getEntities() : [];
|
||||
$oldTiles = $oldChunk !== null ? $oldChunk->getTiles() : [];
|
||||
|
||||
$this->provider->setChunk($chunkX, $chunkZ, $chunk);
|
||||
$this->chunks[$index] = $chunk;
|
||||
|
||||
foreach($oldEntities as $entity){
|
||||
$chunk->addEntity($entity);
|
||||
$oldChunk->removeEntity($entity);
|
||||
$entity->chunk = $chunk;
|
||||
}
|
||||
|
||||
foreach($oldTiles as $tile){
|
||||
$chunk->addTile($tile);
|
||||
$oldChunk->removeTile($tile);
|
||||
$tile->chunk = $chunk;
|
||||
}
|
||||
|
||||
$this->provider->setChunk($chunkX, $chunkZ, $chunk);
|
||||
$this->chunks[$index] = $chunk;
|
||||
}
|
||||
|
||||
unset($this->chunkCache[$index]);
|
||||
@ -2497,7 +2563,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
try{
|
||||
if($chunk !== null){
|
||||
if($trySave and $this->getAutoSave()){
|
||||
if($trySave and $this->getAutoSave() and $chunk->isGenerated()){
|
||||
$entities = 0;
|
||||
foreach($chunk->getEntities() as $e){
|
||||
if($e instanceof Player){
|
||||
@ -2820,16 +2886,23 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
public function addEntityMotion(int $chunkX, int $chunkZ, int $entityId, float $x, float $y, float $z){
|
||||
if(!isset($this->motionToSend[$index = Level::chunkHash($chunkX, $chunkZ)])){
|
||||
$this->motionToSend[$index] = [];
|
||||
}
|
||||
$this->motionToSend[$index][$entityId] = [$entityId, $x, $y, $z];
|
||||
$pk = new SetEntityMotionPacket();
|
||||
$pk->eid = $entityId;
|
||||
$pk->motionX = $x;
|
||||
$pk->motionY = $y;
|
||||
$pk->motionZ = $z;
|
||||
$this->addChunkPacket($chunkX, $chunkZ, $pk);
|
||||
}
|
||||
|
||||
public function addEntityMovement(int $chunkX, int $chunkZ, int $entityId, float $x, float $y, float $z, float $yaw, float $pitch, $headYaw = null){
|
||||
if(!isset($this->moveToSend[$index = Level::chunkHash($chunkX, $chunkZ)])){
|
||||
$this->moveToSend[$index] = [];
|
||||
}
|
||||
$this->moveToSend[$index][$entityId] = [$entityId, $x, $y, $z, $yaw, $headYaw === null ? $yaw : $headYaw, $pitch];
|
||||
$pk = new MoveEntityPacket();
|
||||
$pk->eid = $entityId;
|
||||
$pk->x = $x;
|
||||
$pk->y = $y;
|
||||
$pk->z = $z;
|
||||
$pk->yaw = $yaw;
|
||||
$pk->pitch = $pitch;
|
||||
$pk->headYaw = $headYaw ?? $yaw;
|
||||
$this->addChunkPacket($chunkX, $chunkZ, $pk);
|
||||
}
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ class Chunk{
|
||||
* @param int $level 0-15
|
||||
*/
|
||||
public function setBlockSkyLight(int $x, int $y, int $z, int $level){
|
||||
if($this->getSubChunk($y >> 4)->setBlockSkyLight($x, $y & 0x0f, $z, $level)){
|
||||
if($this->getSubChunk($y >> 4, true)->setBlockSkyLight($x, $y & 0x0f, $z, $level)){
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
}
|
||||
@ -334,7 +334,7 @@ class Chunk{
|
||||
* @param int $level 0-15
|
||||
*/
|
||||
public function setBlockLight(int $x, int $y, int $z, int $level){
|
||||
if($this->getSubChunk($y >> 4)->setBlockLight($x, $y & 0x0f, $z, $level)){
|
||||
if($this->getSubChunk($y >> 4, true)->setBlockLight($x, $y & 0x0f, $z, $level)){
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
}
|
||||
@ -568,6 +568,9 @@ class Chunk{
|
||||
* @param Entity $entity
|
||||
*/
|
||||
public function addEntity(Entity $entity){
|
||||
if($entity->closed){
|
||||
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to a chunk");
|
||||
}
|
||||
$this->entities[$entity->getId()] = $entity;
|
||||
if(!($entity instanceof Player) and $this->isInit){
|
||||
$this->hasChanged = true;
|
||||
@ -588,6 +591,9 @@ class Chunk{
|
||||
* @param Tile $tile
|
||||
*/
|
||||
public function addTile(Tile $tile){
|
||||
if($tile->closed){
|
||||
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to a chunk");
|
||||
}
|
||||
$this->tiles[$tile->getId()] = $tile;
|
||||
if(isset($this->tileList[$index = (($tile->x & 0x0f) << 12) | (($tile->z & 0x0f) << 8) | ($tile->y & 0xff)]) and $this->tileList[$index] !== $tile){
|
||||
$this->tileList[$index]->close();
|
||||
@ -691,12 +697,19 @@ class Chunk{
|
||||
continue; //Fixes entities allocated in wrong chunks.
|
||||
}
|
||||
|
||||
if(($entity = Entity::createEntity($nbt["id"], $level, $nbt)) instanceof Entity){
|
||||
try{
|
||||
$entity = Entity::createEntity($nbt["id"], $level, $nbt);
|
||||
if($entity instanceof Entity){
|
||||
$entity->spawnToAll();
|
||||
}else{
|
||||
$changed = true;
|
||||
continue;
|
||||
}
|
||||
}catch(\Throwable $t){
|
||||
$level->getServer()->getLogger()->logException($t);
|
||||
$changed = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
$level->timings->syncChunkLoadEntitiesTimer->stopTiming();
|
||||
|
@ -47,8 +47,11 @@ class SubChunk{
|
||||
}
|
||||
|
||||
public function isEmpty() : bool{
|
||||
assert(strlen($this->ids) === 4096, "Wrong length of ID array, expecting 4096 bytes, got " . strlen($this->ids));
|
||||
return substr_count($this->ids, "\x00") === 4096;
|
||||
return (
|
||||
substr_count($this->ids, "\x00") === 4096 and
|
||||
substr_count($this->skyLight, "\xff") === 2048 and
|
||||
substr_count($this->blockLight, "\x00") === 2048
|
||||
);
|
||||
}
|
||||
|
||||
public function getBlockId(int $x, int $y, int $z) : int{
|
||||
|
@ -22,18 +22,18 @@
|
||||
namespace pocketmine\level\format\io\leveldb;
|
||||
|
||||
use pocketmine\level\format\Chunk;
|
||||
use pocketmine\level\format\SubChunk;
|
||||
use pocketmine\level\format\io\BaseLevelProvider;
|
||||
use pocketmine\level\format\io\ChunkUtils;
|
||||
use pocketmine\level\generator\Generator;
|
||||
use pocketmine\level\format\SubChunk;
|
||||
use pocketmine\level\generator\Flat;
|
||||
use pocketmine\level\generator\Generator;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\level\LevelException;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\tag\{
|
||||
ByteTag, CompoundTag, FloatTag, IntTag, LongTag, StringTag
|
||||
};
|
||||
use pocketmine\network\protocol\Info as ProtocolInfo;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\MainLogger;
|
||||
|
||||
@ -477,9 +477,13 @@ class LevelDB extends BaseLevelProvider{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function saveChunk(int $x, int $z) : bool{
|
||||
if($this->isChunkLoaded($x, $z)){
|
||||
$this->writeChunk($this->getChunk($x, $z));
|
||||
public function saveChunk(int $chunkX, int $chunkZ) : bool{
|
||||
if($this->isChunkLoaded($chunkX, $chunkZ)){
|
||||
$chunk = $this->getChunk($chunkX, $chunkZ);
|
||||
if(!$chunk->isGenerated()){
|
||||
throw new \InvalidStateException("Cannot save un-generated chunk");
|
||||
}
|
||||
$this->writeChunk($chunk);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ class Anvil extends McRegion{
|
||||
public function nbtDeserialize(string $data){
|
||||
$nbt = new NBT(NBT::BIG_ENDIAN);
|
||||
try{
|
||||
$nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE);
|
||||
$nbt->readCompressed($data);
|
||||
|
||||
$chunk = $nbt->getData();
|
||||
|
||||
|
@ -19,23 +19,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
namespace pocketmine\network\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
namespace pocketmine\level\format\io\region;
|
||||
|
||||
|
||||
class BatchPacket extends DataPacket{
|
||||
const NETWORK_ID = Info::BATCH_PACKET;
|
||||
|
||||
public $payload;
|
||||
|
||||
public function decode(){
|
||||
$this->payload = $this->getString();
|
||||
}
|
||||
|
||||
public function encode(){
|
||||
$this->reset();
|
||||
$this->putString($this->payload);
|
||||
}
|
||||
class CorruptedRegionException extends RegionException{
|
||||
|
||||
}
|
@ -30,6 +30,7 @@ use pocketmine\level\format\io\ChunkUtils;
|
||||
use pocketmine\level\format\SubChunk;
|
||||
use pocketmine\level\generator\Generator;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\level\LevelException;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\tag\{
|
||||
ByteArrayTag, ByteTag, CompoundTag, IntArrayTag, IntTag, ListTag, LongTag, StringTag
|
||||
@ -126,7 +127,7 @@ class McRegion extends BaseLevelProvider{
|
||||
public function nbtDeserialize(string $data){
|
||||
$nbt = new NBT(NBT::BIG_ENDIAN);
|
||||
try{
|
||||
$nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE);
|
||||
$nbt->readCompressed($data);
|
||||
|
||||
$chunk = $nbt->getData();
|
||||
|
||||
@ -219,12 +220,9 @@ class McRegion extends BaseLevelProvider{
|
||||
$isValid = (file_exists($path . "/level.dat") and is_dir($path . "/region/"));
|
||||
|
||||
if($isValid){
|
||||
$files = glob($path . "/region/*.mc*");
|
||||
if(empty($files)){ //possible glob() issue on some systems
|
||||
$files = array_filter(scandir($path . "/region/"), function($file){
|
||||
return substr($file, strrpos($file, ".") + 1, 2) === "mc"; //region file
|
||||
});
|
||||
}
|
||||
|
||||
foreach($files as $f){
|
||||
if(substr($f, strrpos($f, ".") + 1) !== static::REGION_FILE_EXTENSION){
|
||||
@ -309,7 +307,11 @@ class McRegion extends BaseLevelProvider{
|
||||
|
||||
public function saveChunk(int $chunkX, int $chunkZ) : bool{
|
||||
if($this->isChunkLoaded($chunkX, $chunkZ)){
|
||||
$this->getRegion($chunkX >> 5, $chunkZ >> 5)->writeChunk($this->getChunk($chunkX, $chunkZ));
|
||||
$chunk = $this->getChunk($chunkX, $chunkZ);
|
||||
if(!$chunk->isGenerated()){
|
||||
throw new \InvalidStateException("Cannot save un-generated chunk");
|
||||
}
|
||||
$this->getRegion($chunkX >> 5, $chunkZ >> 5)->writeChunk($chunk);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -438,6 +440,22 @@ class McRegion extends BaseLevelProvider{
|
||||
protected function loadRegion(int $x, int $z){
|
||||
if(!isset($this->regions[$index = Level::chunkHash($x, $z)])){
|
||||
$this->regions[$index] = new RegionLoader($this, $x, $z, static::REGION_FILE_EXTENSION);
|
||||
try{
|
||||
$this->regions[$index]->open();
|
||||
}catch(CorruptedRegionException $e){
|
||||
$logger = $this->level->getServer()->getLogger();
|
||||
$logger->error("Corrupted region file detected: " . $e->getMessage());
|
||||
|
||||
$this->regions[$index]->close(false); //Do not write anything to the file
|
||||
|
||||
$path = $this->regions[$index]->getFilePath();
|
||||
$backupPath = $path . ".bak." . time();
|
||||
rename($path, $backupPath);
|
||||
$logger->error("Corrupted region file has been backed up to " . $backupPath);
|
||||
|
||||
$this->regions[$index] = new RegionLoader($this, $x, $z, static::REGION_FILE_EXTENSION);
|
||||
$this->regions[$index]->open(); //this will create a new empty region to replace the corrupted one
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ class PMAnvil extends Anvil{
|
||||
public function nbtDeserialize(string $data){
|
||||
$nbt = new NBT(NBT::BIG_ENDIAN);
|
||||
try{
|
||||
$nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE);
|
||||
$nbt->readCompressed($data);
|
||||
|
||||
$chunk = $nbt->getData();
|
||||
|
||||
|
@ -19,8 +19,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
namespace pocketmine\entity;
|
||||
|
||||
class InstantEffect extends Effect{
|
||||
namespace pocketmine\level\format\io\region;
|
||||
|
||||
|
||||
class RegionException extends \RuntimeException{
|
||||
|
||||
}
|
@ -25,6 +25,7 @@ namespace pocketmine\level\format\io\region;
|
||||
|
||||
use pocketmine\level\format\Chunk;
|
||||
use pocketmine\level\format\io\ChunkException;
|
||||
use pocketmine\level\LevelException;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\MainLogger;
|
||||
|
||||
@ -32,7 +33,11 @@ class RegionLoader{
|
||||
const VERSION = 1;
|
||||
const COMPRESSION_GZIP = 1;
|
||||
const COMPRESSION_ZLIB = 2;
|
||||
|
||||
const MAX_SECTOR_LENGTH = 256 << 12; //256 sectors, (1 MiB)
|
||||
const REGION_HEADER_LENGTH = 8192; //4096 location table + 4096 timestamps
|
||||
const MAX_REGION_FILE_SIZE = 32 * 32 * self::MAX_SECTOR_LENGTH + self::REGION_HEADER_LENGTH; //32 * 32 1MiB chunks + header size
|
||||
|
||||
public static $COMPRESSION_LEVEL = 7;
|
||||
|
||||
protected $x;
|
||||
@ -51,10 +56,21 @@ class RegionLoader{
|
||||
$this->z = $regionZ;
|
||||
$this->levelProvider = $level;
|
||||
$this->filePath = $this->levelProvider->getPath() . "region/r.$regionX.$regionZ.$fileExtension";
|
||||
}
|
||||
|
||||
public function open(){
|
||||
$exists = file_exists($this->filePath);
|
||||
if(!$exists){
|
||||
touch($this->filePath);
|
||||
}else{
|
||||
$fileSize = filesize($this->filePath);
|
||||
if($fileSize > self::MAX_REGION_FILE_SIZE){
|
||||
throw new CorruptedRegionException("Corrupted oversized region file found, should be a maximum of " . self::MAX_REGION_FILE_SIZE . " bytes, got " . $fileSize . " bytes");
|
||||
}elseif($fileSize % 4096 !== 0){
|
||||
throw new CorruptedRegionException("Region file should be padded to a multiple of 4KiB");
|
||||
}
|
||||
}
|
||||
|
||||
$this->filePointer = fopen($this->filePath, "r+b");
|
||||
stream_set_read_buffer($this->filePointer, 1024 * 16); //16KB
|
||||
stream_set_write_buffer($this->filePointer, 1024 * 16); //16KB
|
||||
@ -170,9 +186,20 @@ class RegionLoader{
|
||||
return $x + ($z << 5);
|
||||
}
|
||||
|
||||
public function close(){
|
||||
/**
|
||||
* Writes the region header and closes the file
|
||||
*
|
||||
* @param bool $writeHeader
|
||||
*/
|
||||
public function close(bool $writeHeader = true){
|
||||
if(is_resource($this->filePointer)){
|
||||
if($writeHeader){
|
||||
$this->writeLocationTable();
|
||||
}
|
||||
|
||||
fclose($this->filePointer);
|
||||
}
|
||||
|
||||
$this->levelProvider = null;
|
||||
}
|
||||
|
||||
@ -255,14 +282,34 @@ class RegionLoader{
|
||||
fseek($this->filePointer, 0);
|
||||
$this->lastSector = 1;
|
||||
|
||||
$data = unpack("N*", fread($this->filePointer, 4 * 1024 * 2)); //1024 records * 4 bytes * 2 times
|
||||
$headerRaw = fread($this->filePointer, self::REGION_HEADER_LENGTH);
|
||||
if(($len = strlen($headerRaw)) !== self::REGION_HEADER_LENGTH){
|
||||
throw new CorruptedRegionException("Invalid region file header, expected " . self::REGION_HEADER_LENGTH . " bytes, got " . $len . " bytes");
|
||||
}
|
||||
|
||||
$data = unpack("N*", $headerRaw);
|
||||
$usedOffsets = [];
|
||||
for($i = 0; $i < 1024; ++$i){
|
||||
$index = $data[$i + 1];
|
||||
$offset = $index >> 8;
|
||||
if($offset !== 0){
|
||||
fseek($this->filePointer, ($offset << 12));
|
||||
if(fgetc($this->filePointer) === false){ //Try and read from the location
|
||||
throw new CorruptedRegionException("Region file location offset points to invalid location");
|
||||
}elseif(isset($usedOffsets[$offset])){
|
||||
throw new CorruptedRegionException("Found two chunk offsets pointing to the same location");
|
||||
}else{
|
||||
$usedOffsets[$offset] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->locationTable[$i] = [$index >> 8, $index & 0xff, $data[1024 + $i + 1]];
|
||||
if(($this->locationTable[$i][0] + $this->locationTable[$i][1] - 1) > $this->lastSector){
|
||||
$this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1] - 1;
|
||||
}
|
||||
}
|
||||
|
||||
fseek($this->filePointer, 0);
|
||||
}
|
||||
|
||||
private function writeLocationTable(){
|
||||
@ -300,4 +347,7 @@ class RegionLoader{
|
||||
return $this->z;
|
||||
}
|
||||
|
||||
public function getFilePath() : string{
|
||||
return $this->filePath;
|
||||
}
|
||||
}
|
||||
|
@ -233,17 +233,17 @@ abstract class Generator{
|
||||
return $noiseArray;
|
||||
}
|
||||
|
||||
public abstract function __construct(array $settings = []);
|
||||
abstract public function __construct(array $settings = []);
|
||||
|
||||
public abstract function init(ChunkManager $level, Random $random);
|
||||
abstract public function init(ChunkManager $level, Random $random);
|
||||
|
||||
public abstract function generateChunk($chunkX, $chunkZ);
|
||||
abstract public function generateChunk($chunkX, $chunkZ);
|
||||
|
||||
public abstract function populateChunk($chunkX, $chunkZ);
|
||||
abstract public function populateChunk($chunkX, $chunkZ);
|
||||
|
||||
public abstract function getSettings();
|
||||
abstract public function getSettings();
|
||||
|
||||
public abstract function getName();
|
||||
abstract public function getName();
|
||||
|
||||
public abstract function getSpawn();
|
||||
abstract public function getSpawn();
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ abstract class Biome{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public abstract function getName();
|
||||
abstract public function getName();
|
||||
|
||||
public function getMinElevation(){
|
||||
return $this->minElevation;
|
||||
|
@ -22,6 +22,7 @@
|
||||
namespace pocketmine\level\generator\object;
|
||||
|
||||
use pocketmine\level\ChunkManager;
|
||||
use pocketmine\utils\Random;
|
||||
|
||||
class BigTree extends Tree{
|
||||
private $trunkHeightMultiplier = 0.618;
|
||||
@ -38,13 +39,13 @@ class BigTree extends Tree{
|
||||
private $addLogVines = false;
|
||||
private $addCocoaPlants = false;
|
||||
|
||||
public function canPlaceObject(ChunkManager $level, $x, $y, $z){
|
||||
public function canPlaceObject(ChunkManager $level, $x, $y, $z, Random $random){
|
||||
return false;
|
||||
}
|
||||
|
||||
public function placeObject(ChunkManager $level, $x, $y, $z, $type){
|
||||
public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){
|
||||
|
||||
$this->trunkHeight = (int) ($this->totalHeight * $this->trunkHeightMultiplier);
|
||||
/*$this->trunkHeight = (int) ($this->totalHeight * $this->trunkHeightMultiplier);
|
||||
$leaves = $this->getLeafGroupPoints($level, $pos);
|
||||
foreach($leaves as $leafGroup){
|
||||
$groupX = $leafGroup->getBlockX();
|
||||
@ -54,7 +55,7 @@ class BigTree extends Tree{
|
||||
$this->generateGroupLayer($level, $groupX, $yy, $groupZ, $this->getLeafGroupLayerSize($yy - $groupY));
|
||||
}
|
||||
}
|
||||
/*final BlockIterator trunk = new BlockIterator(new Point(w, x, y - 1, z), new Point(w, x, y + trunkHeight, z));
|
||||
final BlockIterator trunk = new BlockIterator(new Point(w, x, y - 1, z), new Point(w, x, y + trunkHeight, z));
|
||||
while (trunk.hasNext()) {
|
||||
trunk.next().setMaterial(VanillaMaterials.LOG, logMetadata);
|
||||
}
|
||||
@ -69,13 +70,13 @@ class BigTree extends Tree{
|
||||
for($xx = -$xzRadius; $xx < ($xzRadius + 1); ++$xx){
|
||||
for($zz = -$xzRadius; $zz < ($xzRadius + 1); ++$zz){
|
||||
if((abs($xx) != $xzRadius or abs($zz) != $xzRadius) and $yRadius != 0){
|
||||
$level->setBlock($pos->x + $xx, $pos->y + $yy, $pos->z + $zz, 18, $type);
|
||||
$level->setBlock($pos->x + $xx, $pos->y + $yy, $pos->z + $zz, 18, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for($yy = 0; $yy < ($this->totalHeight - 1); ++$yy){
|
||||
$level->setBlock($x, $pos->y + $yy, $z, 17, $type);
|
||||
$level->setBlock($x, $pos->y + $yy, $z, 17, $this->type);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ class Pond{
|
||||
}
|
||||
|
||||
public function canPlaceObject(ChunkManager $level, Vector3 $pos){
|
||||
return false;
|
||||
}
|
||||
|
||||
public function placeObject(ChunkManager $level, Vector3 $pos){
|
||||
|
@ -23,6 +23,7 @@ namespace pocketmine\level\generator\populator;
|
||||
|
||||
use pocketmine\block\Water;
|
||||
use pocketmine\level\ChunkManager;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\Random;
|
||||
|
||||
class Pond extends Populator{
|
||||
@ -36,8 +37,8 @@ class Pond extends Populator{
|
||||
$y = $random->nextBoundedInt(128);
|
||||
$z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 16);
|
||||
$pond = new \pocketmine\level\generator\object\Pond($random, new Water());
|
||||
if($pond->canPlaceObject($level, $x, $y, $z)){
|
||||
$pond->placeObject($level, $x, $y, $z);
|
||||
if($pond->canPlaceObject($level, $v = new Vector3($x, $y, $z))){
|
||||
$pond->placeObject($level, $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,5 +28,5 @@ use pocketmine\level\ChunkManager;
|
||||
use pocketmine\utils\Random;
|
||||
|
||||
abstract class Populator{
|
||||
public abstract function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random);
|
||||
abstract public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random);
|
||||
}
|
@ -23,7 +23,7 @@ namespace pocketmine\level\particle;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class DestroyBlockParticle extends Particle{
|
||||
|
||||
|
@ -24,8 +24,8 @@ namespace pocketmine\level\particle;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Item as ItemEntity;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\AddEntityPacket;
|
||||
use pocketmine\network\protocol\RemoveEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
|
||||
|
||||
class FloatingTextParticle extends Particle{
|
||||
//TODO: HACK!
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\particle;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class GenericParticle extends Particle{
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\particle;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class MobSpawnParticle extends Particle{
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\particle;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
|
||||
abstract class Particle extends Vector3{
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class AnvilBreakSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class AnvilFallSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class AnvilUseSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class BlazeShootSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class ClickSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class DoorBumpSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class DoorCrashSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class DoorSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class EndermanTeleportSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class FizzSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class GenericSound extends Sound{
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class GhastShootSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class GhastSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
@ -22,7 +22,7 @@
|
||||
namespace pocketmine\level\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
|
||||
class LaunchSound extends GenericSound{
|
||||
public function __construct(Vector3 $pos, $pitch = 0){
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user