mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-11 03:58:06 +00:00
Compare commits
228 Commits
api/3.0.0-
...
1.7dev-83
Author | SHA1 | Date | |
---|---|---|---|
6e5759b1d1 | |||
03d3e595d6 | |||
8ca59d12e9 | |||
89e4defa29 | |||
f5534a9ab0 | |||
28bce8d48c | |||
3c02a6a8ed | |||
6b0ac8adb8 | |||
38ec5da260 | |||
240cc3043a | |||
043ae487de | |||
f12701e582 | |||
6e961ae897 | |||
e1d10f595a | |||
178dd1b981 | |||
826ec90856 | |||
0523f26613 | |||
5190d9c1e2 | |||
c8fd0eaf8b | |||
53ef9b653a | |||
030cc4afb0 | |||
9bd7f771d3 | |||
10f6a0eef0 | |||
d0a96f35da | |||
65e908a403 | |||
d7091f4460 | |||
c6670b2e74 | |||
194278d986 | |||
0e2e9aab2e | |||
1b5fed983b | |||
5aba87b250 | |||
f01ce8e994 | |||
d89b8cf12e | |||
6aa9b081e9 | |||
dbed80386a | |||
cefad0444c | |||
ee052f91d4 | |||
ef6250967f | |||
61cfdac6a1 | |||
fd7fb10223 | |||
6897cb4774 | |||
8e7ad532f1 | |||
9e8366725a | |||
b14ecc18c4 | |||
55720d9f0a | |||
0262465a26 | |||
7996a7b08c | |||
4a1fc1bdf7 | |||
85b2b2ae2e | |||
38e11aae5e | |||
f0755d1659 | |||
fd33a65e3b | |||
7baadf9dad | |||
ca23864e4c | |||
8728547a11 | |||
90fb3c5e12 | |||
1fb6d12a6b | |||
1323d89139 | |||
136ab1dba1 | |||
8cae20e818 | |||
ff2b3bfa2a | |||
361b262d3a | |||
1fd7f441b4 | |||
3f56d6ddc8 | |||
1e4cbb0dd9 | |||
a99eee9def | |||
bdee746e46 | |||
642c7733cd | |||
c8199e14ad | |||
0f37bc35ba | |||
8dc3d019f6 | |||
bd64172750 | |||
0e51820dfb | |||
30d2318bb7 | |||
63634d7e7d | |||
d941bf8e74 | |||
8c9d9626ab | |||
6b34c47c96 | |||
77241e14ce | |||
15b08c1417 | |||
4d1daecd91 | |||
53e5db5142 | |||
ad72fe6232 | |||
8b33f711d0 | |||
319735db3a | |||
c283d87494 | |||
be27e03126 | |||
c1c290cd39 | |||
5267c571e9 | |||
0fac3b9a9d | |||
23a38400e2 | |||
297172d111 | |||
825d4f9702 | |||
1d31958ce6 | |||
130a60f2b2 | |||
07268e4b37 | |||
441efc4ae2 | |||
88bd7713c5 | |||
aaa3b6e59a | |||
25adac8859 | |||
8d0b881762 | |||
16cb75ef38 | |||
3b9689674d | |||
7f5d8cc900 | |||
8761256246 | |||
8c363cb571 | |||
10b765e17a | |||
0eb866bf25 | |||
c46caa38e1 | |||
17d949f476 | |||
c569f55933 | |||
01d8d216ca | |||
f1ccee505b | |||
a61adb5991 | |||
cae1a3bb4b | |||
6681bd250a | |||
38293913ee | |||
8493ce8a35 | |||
9b7868238c | |||
953c1ef4ec | |||
021a9a4820 | |||
5b7565664c | |||
ebdfbe6bb9 | |||
85ff236461 | |||
d7422d9283 | |||
fcb3c4820e | |||
c72ef605b9 | |||
e274f1b7f8 | |||
69514c5763 | |||
12c154badf | |||
c9ee206fe6 | |||
6877ac35eb | |||
78d49f8e66 | |||
de6ebc5791 | |||
2cff5a500c | |||
f077ba4748 | |||
dcf34b7188 | |||
ca84532640 | |||
75e32b11b7 | |||
9f44b2ed75 | |||
1c02c747ca | |||
a6c0f1512c | |||
604d8ecf9a | |||
5d75d3d5b6 | |||
8b13b520e0 | |||
00e4fff259 | |||
a06c934f4d | |||
5335ed9394 | |||
16aeb0ac85 | |||
8caabd3267 | |||
ddfe828445 | |||
67ad2d25b9 | |||
190f4dd6ab | |||
6d6283b7f3 | |||
a3d21de559 | |||
ece0692229 | |||
b5d2402c9b | |||
c7fd3eb725 | |||
5433a3f964 | |||
2c3d7c49f9 | |||
76acb1da7b | |||
17518195d1 | |||
2443a57234 | |||
95752ef542 | |||
da4c9cf404 | |||
770616d4ab | |||
445a67954d | |||
4250e99e3a | |||
121777375e | |||
93e149e91c | |||
1f70a7830e | |||
159b2e3d5e | |||
e0307411da | |||
e5e76d4c93 | |||
2688228a6f | |||
e15eefc58f | |||
8853452feb | |||
09c53552c1 | |||
1f6d325328 | |||
f35ca147bb | |||
b6fb2bca13 | |||
4f1302adf2 | |||
643e10037c | |||
eda2473e78 | |||
4b65fef957 | |||
fbe2567e58 | |||
5fc50aeda5 | |||
9be1b929a5 | |||
6480f7a989 | |||
1576a79644 | |||
02cbf800d0 | |||
5a4fbc6f5a | |||
83fcec3e94 | |||
5d436a06ec | |||
8958b3c51c | |||
c1ff7bbef4 | |||
74ee94b385 | |||
5208ad885c | |||
51be88c698 | |||
0dc8362536 | |||
9bae4d8ef6 | |||
bb4808c23e | |||
3025f76cd0 | |||
1e539c4e3b | |||
590003d7c1 | |||
72d40860f3 | |||
d3d1e32309 | |||
36d47a33f3 | |||
260179197b | |||
75644b5df2 | |||
3ad1b1ba7f | |||
b4c2305c7f | |||
1d0f0a2999 | |||
8ca37b3813 | |||
2ba601b6e9 | |||
7958fffa07 | |||
98e0a2ecba | |||
899e318a88 | |||
989505c42c | |||
d9da9accbc | |||
711d62b5eb | |||
49506659e0 | |||
7886918140 | |||
8a151dc373 | |||
58a12fdfa3 | |||
50dffeb6a1 | |||
63d2b341b9 | |||
77cd8e7799 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,3 +28,5 @@ Desktop.ini
|
||||
# Sphinx-doc
|
||||
/docs/build/
|
||||
!/docs/requirements.txt
|
||||
|
||||
vendor/*
|
||||
|
17
.travis.yml
17
.travis.yml
@ -1,12 +1,21 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.0
|
||||
- 7.2
|
||||
|
||||
before_script:
|
||||
- pecl install channel://pecl.php.net/pthreads-3.1.6
|
||||
- pecl install channel://pecl.php.net/weakref-0.3.3
|
||||
- echo | pecl install channel://pecl.php.net/yaml-2.0.0
|
||||
# - pecl install channel://pecl.php.net/pthreads-3.1.6
|
||||
- echo | pecl install channel://pecl.php.net/yaml-2.0.2
|
||||
- git clone https://github.com/krakjoe/pthreads.git
|
||||
- cd pthreads
|
||||
- git checkout 6c6b15138c923b69cfa46ee05fc2dd45da587287
|
||||
- phpize
|
||||
- ./configure
|
||||
- make
|
||||
- make install
|
||||
- cd ..
|
||||
- echo "extension=pthreads.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
- composer install
|
||||
|
||||
script:
|
||||
- ./tests/travis.sh
|
||||
|
33
composer.json
Normal file
33
composer.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "pmmp/pocketmine-mp",
|
||||
"description": "A server software for Minecraft: Pocket Edition written in PHP",
|
||||
"type": "project",
|
||||
"homepage": "https://pmmp.io",
|
||||
"license": "LGPL-3.0",
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"ext-bcmath": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-phar": "*",
|
||||
"ext-pthreads": ">=3.1.7dev",
|
||||
"ext-reflection": "*",
|
||||
"ext-sockets": "*",
|
||||
"ext-spl": "*",
|
||||
"ext-yaml": ">=2.0.0",
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": ">=1.2.11"
|
||||
},
|
||||
"autoload": {
|
||||
"exclude-from-classmap": [
|
||||
"src/spl/stubs"
|
||||
],
|
||||
"psr-0": {
|
||||
"": ["src", "src/spl"]
|
||||
}
|
||||
}
|
||||
}
|
36
composer.lock
generated
Normal file
36
composer.lock
generated
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "d4fecad9dce5314493052c870c8cf059",
|
||||
"packages": [],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"ext-pthreads": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.2",
|
||||
"ext-bcmath": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-phar": "*",
|
||||
"ext-pthreads": ">=3.1.7dev",
|
||||
"ext-reflection": "*",
|
||||
"ext-sockets": "*",
|
||||
"ext-spl": "*",
|
||||
"ext-yaml": ">=2.0.0",
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": ">=1.2.11"
|
||||
},
|
||||
"platform-dev": []
|
||||
}
|
@ -25,7 +25,9 @@ namespace pocketmine;
|
||||
|
||||
use pocketmine\event\server\LowMemoryEvent;
|
||||
use pocketmine\event\Timings;
|
||||
use pocketmine\scheduler\DumpWorkerMemoryTask;
|
||||
use pocketmine\scheduler\GarbageCollectionTask;
|
||||
use pocketmine\utils\MainLogger;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
class MemoryManager{
|
||||
@ -74,6 +76,9 @@ class MemoryManager{
|
||||
/** @var bool */
|
||||
private $cacheTrigger;
|
||||
|
||||
/** @var bool */
|
||||
private $dumpWorkers = true;
|
||||
|
||||
public function __construct(Server $server){
|
||||
$this->server = $server;
|
||||
|
||||
@ -131,6 +136,7 @@ class MemoryManager{
|
||||
$this->chunkCache = (bool) $this->server->getProperty("memory.world-caches.disable-chunk-cache", true);
|
||||
$this->cacheTrigger = (bool) $this->server->getProperty("memory.world-caches.low-memory-trigger", true);
|
||||
|
||||
$this->dumpWorkers = (bool) $this->server->getProperty("memory.memory-dump.dump-async-worker", true);
|
||||
gc_enable();
|
||||
}
|
||||
|
||||
@ -261,6 +267,27 @@ class MemoryManager{
|
||||
* @param int $maxStringSize
|
||||
*/
|
||||
public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize){
|
||||
MainLogger::getLogger()->notice("[Dump] After the memory dump is done, the server might crash");
|
||||
self::dumpMemory($this->server, $this->server->getLoader(), $outputFolder, $maxNesting, $maxStringSize);
|
||||
|
||||
if($this->dumpWorkers){
|
||||
$scheduler = $this->server->getScheduler();
|
||||
for($i = 0, $size = $scheduler->getAsyncTaskPoolSize(); $i < $size; ++$i){
|
||||
$scheduler->scheduleAsyncTaskToWorker(new DumpWorkerMemoryTask($outputFolder, $maxNesting, $maxStringSize), $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static memory dumper accessible from any thread.
|
||||
*
|
||||
* @param mixed $startingObject
|
||||
* @param \ClassLoader $loader
|
||||
* @param string $outputFolder
|
||||
* @param int $maxNesting
|
||||
* @param int $maxStringSize
|
||||
*/
|
||||
public static function dumpMemory($startingObject, \ClassLoader $loader, string $outputFolder, int $maxNesting, int $maxStringSize){
|
||||
$hardLimit = ini_get('memory_limit');
|
||||
ini_set('memory_limit', '-1');
|
||||
gc_disable();
|
||||
@ -269,12 +296,8 @@ class MemoryManager{
|
||||
mkdir($outputFolder, 0777, true);
|
||||
}
|
||||
|
||||
$this->server->getLogger()->notice("[Dump] After the memory dump is done, the server might crash");
|
||||
|
||||
$obData = fopen($outputFolder . "/objects.js", "wb+");
|
||||
|
||||
$staticProperties = [];
|
||||
|
||||
$data = [];
|
||||
|
||||
$objects = [];
|
||||
@ -283,8 +306,10 @@ class MemoryManager{
|
||||
|
||||
$instanceCounts = [];
|
||||
|
||||
$staticProperties = [];
|
||||
$staticCount = 0;
|
||||
foreach($this->server->getLoader()->getClasses() as $className){
|
||||
|
||||
foreach($loader->getClasses() as $className){
|
||||
$reflection = new \ReflectionClass($className);
|
||||
$staticProperties[$className] = [];
|
||||
foreach($reflection->getProperties() as $property){
|
||||
@ -297,7 +322,7 @@ class MemoryManager{
|
||||
}
|
||||
|
||||
$staticCount++;
|
||||
$this->continueDump($property->getValue(), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
self::continueDump($property->getValue(), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
|
||||
if(count($staticProperties[$className]) === 0){
|
||||
@ -305,9 +330,39 @@ class MemoryManager{
|
||||
}
|
||||
}
|
||||
|
||||
echo "[Dump] Wrote $staticCount static properties\n";
|
||||
file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
MainLogger::getLogger()->info("[Dump] Wrote $staticCount static properties");
|
||||
|
||||
$this->continueDump($this->server, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
if($GLOBALS !== null){ //This might be null if we're on a different thread
|
||||
$globalVariables = [];
|
||||
$globalCount = 0;
|
||||
|
||||
$ignoredGlobals = [
|
||||
'GLOBALS' => true,
|
||||
'_SERVER' => true,
|
||||
'_REQUEST' => true,
|
||||
'_POST' => true,
|
||||
'_GET' => true,
|
||||
'_FILES' => true,
|
||||
'_ENV' => true,
|
||||
'_COOKIE' => true,
|
||||
'_SESSION' => true
|
||||
];
|
||||
|
||||
foreach($GLOBALS as $varName => $value){
|
||||
if(isset($ignoredGlobals[$varName])){
|
||||
continue;
|
||||
}
|
||||
|
||||
$globalCount++;
|
||||
self::continueDump($value, $globalVariables[$varName], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
|
||||
file_put_contents($outputFolder . "/globalVariables.js", json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
MainLogger::getLogger()->info("[Dump] Wrote $globalCount global variables");
|
||||
}
|
||||
|
||||
self::continueDump($startingObject, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
|
||||
do{
|
||||
$continue = false;
|
||||
@ -349,25 +404,26 @@ class MemoryManager{
|
||||
if(!$property->isPublic()){
|
||||
$property->setAccessible(true);
|
||||
}
|
||||
$this->continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
self::continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
|
||||
fwrite($obData, "$hash@$className: " . json_encode($info, JSON_UNESCAPED_SLASHES) . "\n");
|
||||
}
|
||||
|
||||
echo "[Dump] Wrote " . count($objects) . " objects\n";
|
||||
|
||||
}while($continue);
|
||||
|
||||
MainLogger::getLogger()->info("[Dump] Wrote " . count($objects) . " objects");
|
||||
|
||||
fclose($obData);
|
||||
|
||||
file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
file_put_contents($outputFolder . "/serverEntry.js", json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
file_put_contents($outputFolder . "/referenceCounts.js", json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
|
||||
arsort($instanceCounts, SORT_NUMERIC);
|
||||
file_put_contents($outputFolder . "/instanceCounts.js", json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
|
||||
echo "[Dump] Finished!\n";
|
||||
MainLogger::getLogger()->info("[Dump] Finished!");
|
||||
|
||||
ini_set('memory_limit', $hardLimit);
|
||||
gc_enable();
|
||||
@ -382,7 +438,7 @@ class MemoryManager{
|
||||
* @param int $maxNesting
|
||||
* @param int $maxStringSize
|
||||
*/
|
||||
private function continueDump($from, &$data, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
|
||||
private static function continueDump($from, &$data, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
|
||||
if($maxNesting <= 0){
|
||||
$data = "(error) NESTING LIMIT REACHED";
|
||||
return;
|
||||
@ -406,7 +462,7 @@ class MemoryManager{
|
||||
}
|
||||
$data = [];
|
||||
foreach($from as $key => $value){
|
||||
$this->continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
|
||||
self::continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
|
||||
}
|
||||
}elseif(is_string($from)){
|
||||
$data = "(string) len(". strlen($from) .") " . substr(Utils::printable($from), 0, $maxStringSize);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -70,6 +70,7 @@ namespace {
|
||||
}
|
||||
|
||||
namespace pocketmine {
|
||||
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\MainLogger;
|
||||
use pocketmine\utils\ServerKiller;
|
||||
@ -78,9 +79,9 @@ namespace pocketmine {
|
||||
use pocketmine\wizard\SetupWizard;
|
||||
use raklib\RakLib;
|
||||
|
||||
const VERSION = "1.6.2dev";
|
||||
const API_VERSION = "3.0.0-ALPHA7";
|
||||
const CODENAME = "Unleashed";
|
||||
const VERSION = "1.7dev";
|
||||
const API_VERSION = "3.0.0-ALPHA8";
|
||||
const CODENAME = "[REDACTED]";
|
||||
|
||||
/*
|
||||
* Startup code. Do not look at it, it may harm you.
|
||||
@ -89,8 +90,8 @@ namespace pocketmine {
|
||||
* Enjoy it as much as I did writing it. I don't want to do it again.
|
||||
*/
|
||||
|
||||
if(version_compare("7.0", PHP_VERSION) > 0 or version_compare("7.1", PHP_VERSION) <= 0){
|
||||
echo "[CRITICAL] You must use PHP 7.0" . PHP_EOL;
|
||||
if(version_compare("7.2", PHP_VERSION) > 0){
|
||||
echo "[CRITICAL] You must use PHP >= 7.2" . PHP_EOL;
|
||||
echo "[CRITICAL] Please use the installer provided on the homepage." . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
@ -120,30 +121,40 @@ namespace pocketmine {
|
||||
if(\Phar::running(true) !== ""){
|
||||
define('pocketmine\PATH', \Phar::running(true) . "/");
|
||||
}else{
|
||||
define('pocketmine\PATH', realpath(getcwd()) . DIRECTORY_SEPARATOR);
|
||||
define('pocketmine\PATH', dirname(__FILE__, 3) . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
$requiredSplVer = "0.0.1";
|
||||
if(!is_file(\pocketmine\PATH . "src/spl/version.php") or version_compare($requiredSplVer, require(\pocketmine\PATH . "src/spl/version.php")) > 0){
|
||||
if(!is_file(\pocketmine\PATH . "src/spl/version.php")){
|
||||
echo "[CRITICAL] Cannot find PocketMine-SPL or incompatible version." . PHP_EOL;
|
||||
echo "[CRITICAL] Please update your submodules or use provided builds." . PHP_EOL;
|
||||
exit(1);
|
||||
}elseif(version_compare($requiredSplVer, require(\pocketmine\PATH . "src/spl/version.php")) > 0){
|
||||
echo "[CRITICAL] Incompatible PocketMine-SPL submodule version ($requiredSplVer is required)." . PHP_EOL;
|
||||
echo "[CRITICAL] Please update your submodules or use provided builds." . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if(is_file(\pocketmine\PATH . "vendor/autoload.php")){
|
||||
require_once(\pocketmine\PATH . "vendor/autoload.php");
|
||||
}else{
|
||||
echo "[CRITICAL] Composer autoloader not found" . PHP_EOL;
|
||||
echo "[CRITICAL] Please initialize composer dependencies before running." . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if(!class_exists("ClassLoader", false)){
|
||||
if(!is_file(\pocketmine\PATH . "src/spl/ClassLoader.php")){
|
||||
echo "[CRITICAL] Unable to find the PocketMine-SPL library." . PHP_EOL;
|
||||
echo "[CRITICAL] Please use provided builds or clone the repository recursively." . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
require_once(\pocketmine\PATH . "src/spl/ClassLoader.php");
|
||||
require_once(\pocketmine\PATH . "src/spl/BaseClassLoader.php");
|
||||
}
|
||||
|
||||
/*
|
||||
* We now use the Composer autoloader, but this autoloader is still used by RakLib and for loading plugins.
|
||||
*/
|
||||
$autoloader = new \BaseClassLoader();
|
||||
$autoloader->addPath(\pocketmine\PATH . "src");
|
||||
$autoloader->addPath(\pocketmine\PATH . "src" . DIRECTORY_SEPARATOR . "spl");
|
||||
$autoloader->register(true);
|
||||
$autoloader->register(false);
|
||||
|
||||
if(!class_exists(RakLib::class)){
|
||||
echo "[CRITICAL] Unable to find the RakLib library." . PHP_EOL;
|
||||
@ -186,40 +197,51 @@ namespace pocketmine {
|
||||
$logger = new MainLogger(\pocketmine\DATA . "server.log");
|
||||
$logger->registerStatic();
|
||||
|
||||
if(!ini_get("date.timezone")){
|
||||
do{
|
||||
$timezone = ini_get("date.timezone");
|
||||
if($timezone !== ""){
|
||||
/*
|
||||
* This is here so that people don't come to us complaining and fill up the issue tracker when they put
|
||||
* an incorrect timezone abbreviation in php.ini apparently.
|
||||
*/
|
||||
if(strpos($timezone, "/") === false){
|
||||
$default_timezone = timezone_name_from_abbr($timezone);
|
||||
if($default_timezone !== false){
|
||||
ini_set("date.timezone", $default_timezone);
|
||||
date_default_timezone_set($default_timezone);
|
||||
break;
|
||||
}else{
|
||||
//Bad php.ini value, try another method to detect timezone
|
||||
$logger->warning("Timezone \"$timezone\" could not be parsed as a valid timezone from php.ini, falling back to auto-detection");
|
||||
}
|
||||
}else{
|
||||
date_default_timezone_set($timezone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(($timezone = detect_system_timezone()) and date_default_timezone_set($timezone)){
|
||||
//Success! Timezone has already been set and validated in the if statement.
|
||||
//This here is just for redundancy just in case some program wants to read timezone data from the ini.
|
||||
ini_set("date.timezone", $timezone);
|
||||
}else{
|
||||
//If system timezone detection fails or timezone is an invalid value.
|
||||
if($response = Utils::getURL("http://ip-api.com/json")
|
||||
and $ip_geolocation_data = json_decode($response, true)
|
||||
and $ip_geolocation_data['status'] !== 'fail'
|
||||
and date_default_timezone_set($ip_geolocation_data['timezone'])
|
||||
){
|
||||
//Again, for redundancy.
|
||||
ini_set("date.timezone", $ip_geolocation_data['timezone']);
|
||||
}else{
|
||||
ini_set("date.timezone", "UTC");
|
||||
date_default_timezone_set("UTC");
|
||||
$logger->warning("Timezone could not be automatically determined. An incorrect timezone will result in incorrect timestamps on console logs. It has been set to \"UTC\" by default. You can change it on the php.ini file.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
/*
|
||||
* This is here so that people don't come to us complaining and fill up the issue tracker when they put
|
||||
* an incorrect timezone abbreviation in php.ini apparently.
|
||||
*/
|
||||
$timezone = ini_get("date.timezone");
|
||||
if(strpos($timezone, "/") === false){
|
||||
$default_timezone = timezone_name_from_abbr($timezone);
|
||||
ini_set("date.timezone", $default_timezone);
|
||||
date_default_timezone_set($default_timezone);
|
||||
}else{
|
||||
date_default_timezone_set($timezone);
|
||||
|
||||
if($response = Utils::getURL("http://ip-api.com/json") //If system timezone detection fails or timezone is an invalid value.
|
||||
and $ip_geolocation_data = json_decode($response, true)
|
||||
and $ip_geolocation_data['status'] !== 'fail'
|
||||
and date_default_timezone_set($ip_geolocation_data['timezone'])
|
||||
){
|
||||
//Again, for redundancy.
|
||||
ini_set("date.timezone", $ip_geolocation_data['timezone']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ini_set("date.timezone", "UTC");
|
||||
date_default_timezone_set("UTC");
|
||||
$logger->warning("Timezone could not be automatically determined or was set to an invalid value. An incorrect timezone will result in incorrect timestamps on console logs. It has been set to \"UTC\" by default. You can change it on the php.ini file.");
|
||||
}while(false);
|
||||
|
||||
|
||||
function detect_system_timezone(){
|
||||
switch(Utils::getOS()){
|
||||
@ -438,8 +460,8 @@ namespace pocketmine {
|
||||
if(substr_count($pthreads_version, ".") < 2){
|
||||
$pthreads_version = "0.$pthreads_version";
|
||||
}
|
||||
if(version_compare($pthreads_version, "3.1.5") < 0){
|
||||
$logger->critical("pthreads >= 3.1.5 is required, while you have $pthreads_version.");
|
||||
if(version_compare($pthreads_version, "3.1.7-dev") < 0){
|
||||
$logger->critical("pthreads >= 3.1.7-dev is required, while you have $pthreads_version.");
|
||||
++$errors;
|
||||
}
|
||||
|
||||
@ -524,7 +546,7 @@ namespace pocketmine {
|
||||
|
||||
|
||||
if(\Phar::running(true) === ""){
|
||||
$logger->warning("Non-packaged PocketMine-MP installation detected, do not use on production.");
|
||||
$logger->warning("Non-packaged PocketMine-MP installation detected. Consider using a phar in production for better performance.");
|
||||
}
|
||||
|
||||
ThreadManager::init();
|
||||
@ -536,19 +558,7 @@ namespace pocketmine {
|
||||
$killer->start();
|
||||
usleep(10000); //Fixes ServerKiller not being able to start on single-core machines
|
||||
|
||||
$erroredThreads = 0;
|
||||
foreach(ThreadManager::getInstance()->getAll() as $id => $thread){
|
||||
$logger->debug("Stopping " . $thread->getThreadName() . " thread");
|
||||
try{
|
||||
$thread->quit();
|
||||
$logger->debug($thread->getThreadName() . " thread stopped successfully.");
|
||||
}catch(\ThreadException $e){
|
||||
++$erroredThreads;
|
||||
$logger->debug("Could not stop " . $thread->getThreadName() . " thread: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if($erroredThreads > 0){
|
||||
if(ThreadManager::getInstance()->stopAll() > 0){
|
||||
if(\pocketmine\DEBUG > 1){
|
||||
echo "Some threads could not be stopped, performing a force-kill" . PHP_EOL . PHP_EOL;
|
||||
}
|
||||
|
@ -47,7 +47,6 @@ use pocketmine\event\Timings;
|
||||
use pocketmine\event\TimingsHandler;
|
||||
use pocketmine\event\TranslationContainer;
|
||||
use pocketmine\inventory\CraftingManager;
|
||||
use pocketmine\inventory\InventoryType;
|
||||
use pocketmine\inventory\Recipe;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
use pocketmine\item\ItemFactory;
|
||||
@ -83,6 +82,7 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerListPacket;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
|
||||
use pocketmine\network\mcpe\RakLibInterface;
|
||||
use pocketmine\network\Network;
|
||||
use pocketmine\network\query\QueryHandler;
|
||||
@ -194,6 +194,9 @@ class Server{
|
||||
/** @var int */
|
||||
private $maxPlayers;
|
||||
|
||||
/** @var bool */
|
||||
private $onlineMode = true;
|
||||
|
||||
/** @var bool */
|
||||
private $autoSave;
|
||||
|
||||
@ -339,6 +342,24 @@ class Server{
|
||||
return $this->maxPlayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the server requires that players be authenticated to Xbox Live. If true, connecting players who
|
||||
* are not logged into Xbox Live will be disconnected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getOnlineMode() : bool{
|
||||
return $this->onlineMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of {@link #getOnlineMode()}.
|
||||
* @return bool
|
||||
*/
|
||||
public function requiresAuthentication() : bool{
|
||||
return $this->getOnlineMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
@ -915,7 +936,7 @@ class Server{
|
||||
/**
|
||||
* @return Level|null
|
||||
*/
|
||||
public function getDefaultLevel(){
|
||||
public function getDefaultLevel() : ?Level{
|
||||
return $this->levelDefault;
|
||||
}
|
||||
|
||||
@ -926,7 +947,7 @@ class Server{
|
||||
*
|
||||
* @param Level|null $level
|
||||
*/
|
||||
public function setDefaultLevel($level){
|
||||
public function setDefaultLevel(?Level $level) : void{
|
||||
if($level === null or ($this->isLevelLoaded($level->getFolderName()) and $level !== $this->levelDefault)){
|
||||
$this->levelDefault = $level;
|
||||
}
|
||||
@ -946,12 +967,8 @@ class Server{
|
||||
*
|
||||
* @return Level|null
|
||||
*/
|
||||
public function getLevel(int $levelId){
|
||||
if(isset($this->levels[$levelId])){
|
||||
return $this->levels[$levelId];
|
||||
}
|
||||
|
||||
return null;
|
||||
public function getLevel(int $levelId) : ?Level{
|
||||
return $this->levels[$levelId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -961,7 +978,7 @@ class Server{
|
||||
*
|
||||
* @return Level|null
|
||||
*/
|
||||
public function getLevelByName(string $name){
|
||||
public function getLevelByName(string $name) : ?Level{
|
||||
foreach($this->getLevels() as $level){
|
||||
if($level->getFolderName() === $name){
|
||||
return $level;
|
||||
@ -983,13 +1000,16 @@ class Server{
|
||||
if($level === $this->getDefaultLevel() and !$forceUnload){
|
||||
throw new \InvalidStateException("The default level cannot be unloaded while running, please switch levels.");
|
||||
}
|
||||
if($level->unload($forceUnload) === true){
|
||||
unset($this->levels[$level->getId()]);
|
||||
|
||||
return true;
|
||||
}
|
||||
return $level->unload($forceUnload);
|
||||
}
|
||||
|
||||
return false;
|
||||
/**
|
||||
* @internal
|
||||
* @param Level $level
|
||||
*/
|
||||
public function removeLevel(Level $level) : void{
|
||||
unset($this->levels[$level->getId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1485,7 +1505,8 @@ class Server{
|
||||
"enable-rcon" => false,
|
||||
"rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10),
|
||||
"auto-save" => true,
|
||||
"view-distance" => 8
|
||||
"view-distance" => 8,
|
||||
"online-mode" => true
|
||||
]);
|
||||
|
||||
$this->forceLanguage = $this->getProperty("settings.force-language", false);
|
||||
@ -1558,6 +1579,16 @@ class Server{
|
||||
$this->maxPlayers = $this->getConfigInt("max-players", 20);
|
||||
$this->setAutoSave($this->getConfigBoolean("auto-save", true));
|
||||
|
||||
$this->onlineMode = $this->getConfigBoolean("online-mode", true);
|
||||
if($this->onlineMode){
|
||||
$this->logger->notice($this->getLanguage()->translateString("pocketmine.server.auth", ["enabled", "will"]));
|
||||
$this->logger->notice($this->getLanguage()->translateString("pocketmine.server.authProperty", ["disable", "false"]));
|
||||
}else{
|
||||
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.auth", ["disabled", "will not"]));
|
||||
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.authWarning"));
|
||||
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.authProperty", ["enable", "true"]));
|
||||
}
|
||||
|
||||
if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){
|
||||
$this->setConfigInt("difficulty", 3);
|
||||
}
|
||||
@ -1585,6 +1616,7 @@ class Server{
|
||||
]));
|
||||
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.license", [$this->getName()]));
|
||||
|
||||
|
||||
Timings::init();
|
||||
|
||||
$this->consoleSender = new ConsoleCommandSender();
|
||||
@ -1592,7 +1624,6 @@ class Server{
|
||||
|
||||
Entity::init();
|
||||
Tile::init();
|
||||
InventoryType::init();
|
||||
BlockFactory::init();
|
||||
Enchantment::init();
|
||||
ItemFactory::init();
|
||||
@ -2280,32 +2311,45 @@ class Server{
|
||||
if(isset($this->playerList[$player->getRawUniqueId()])){
|
||||
unset($this->playerList[$player->getRawUniqueId()]);
|
||||
|
||||
$pk = new PlayerListPacket();
|
||||
$pk->type = PlayerListPacket::TYPE_REMOVE;
|
||||
$pk->entries[] = [$player->getUniqueId()];
|
||||
$this->broadcastPacket($this->playerList, $pk);
|
||||
$this->removePlayerListData($player->getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
public function updatePlayerListData(UUID $uuid, $entityId, $name, $skinId, $skinData, array $players = null){
|
||||
/**
|
||||
* @param UUID $uuid
|
||||
* @param int $entityId
|
||||
* @param string $name
|
||||
* @param string $skinId
|
||||
* @param string $skinData
|
||||
* @param Player[]|null $players
|
||||
*/
|
||||
public function updatePlayerListData(UUID $uuid, int $entityId, string $name, string $skinId, string $skinData, array $players = null){
|
||||
$pk = new PlayerListPacket();
|
||||
$pk->type = PlayerListPacket::TYPE_ADD;
|
||||
$pk->entries[] = [$uuid, $entityId, $name, $skinId, $skinData];
|
||||
|
||||
$pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, $skinId, $skinData);
|
||||
$this->broadcastPacket($players ?? $this->playerList, $pk);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UUID $uuid
|
||||
* @param Player[]|null $players
|
||||
*/
|
||||
public function removePlayerListData(UUID $uuid, array $players = null){
|
||||
$pk = new PlayerListPacket();
|
||||
$pk->type = PlayerListPacket::TYPE_REMOVE;
|
||||
$pk->entries[] = [$uuid];
|
||||
$pk->entries[] = PlayerListEntry::createRemovalEntry($uuid);
|
||||
$this->broadcastPacket($players ?? $this->playerList, $pk);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Player $p
|
||||
*/
|
||||
public function sendFullPlayerListData(Player $p){
|
||||
$pk = new PlayerListPacket();
|
||||
$pk->type = PlayerListPacket::TYPE_ADD;
|
||||
foreach($this->playerList as $player){
|
||||
$pk->entries[] = [$player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkinId(), $player->getSkinData()];
|
||||
$pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkinId(), $player->getSkinData());
|
||||
}
|
||||
|
||||
$p->dataPacket($pk);
|
||||
@ -2375,7 +2419,7 @@ class Server{
|
||||
}
|
||||
|
||||
public function sendUsage($type = SendUsageTask::TYPE_STATUS){
|
||||
if($this->getProperty("anonymous-statistics.enabled", true)){
|
||||
if((bool) $this->getProperty("anonymous-statistics.enabled", true)){
|
||||
$this->scheduler->scheduleAsyncTask(new SendUsageTask($this, $type, $this->uniquePlayers));
|
||||
}
|
||||
$this->uniquePlayers = [];
|
||||
|
@ -51,16 +51,17 @@ abstract class Thread extends \Thread{
|
||||
* (unless you are using a custom autoloader).
|
||||
*/
|
||||
public function registerClassLoader(){
|
||||
require(\pocketmine\PATH . "vendor/autoload.php");
|
||||
if(!interface_exists("ClassLoader", false)){
|
||||
require(\pocketmine\PATH . "src/spl/ClassLoader.php");
|
||||
require(\pocketmine\PATH . "src/spl/BaseClassLoader.php");
|
||||
}
|
||||
if($this->classLoader !== null){
|
||||
$this->classLoader->register(true);
|
||||
$this->classLoader->register(false);
|
||||
}
|
||||
}
|
||||
|
||||
public function start(int $options = PTHREADS_INHERIT_ALL){
|
||||
public function start(?int $options = \PTHREADS_INHERIT_ALL){
|
||||
ThreadManager::getInstance()->add($this);
|
||||
|
||||
if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use pocketmine\utils\MainLogger;
|
||||
|
||||
class ThreadManager extends \Volatile{
|
||||
|
||||
/** @var ThreadManager */
|
||||
@ -68,4 +70,23 @@ class ThreadManager extends \Volatile{
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function stopAll() : int{
|
||||
$logger = MainLogger::getLogger();
|
||||
|
||||
$erroredThreads = 0;
|
||||
|
||||
foreach($this->getAll() as $thread){
|
||||
$logger->debug("Stopping " . $thread->getThreadName() . " thread");
|
||||
try{
|
||||
$thread->quit();
|
||||
$logger->debug($thread->getThreadName() . " thread stopped successfully.");
|
||||
}catch(\ThreadException $e){
|
||||
++$erroredThreads;
|
||||
$logger->debug("Could not stop " . $thread->getThreadName() . " thread: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $erroredThreads;
|
||||
}
|
||||
}
|
@ -52,16 +52,17 @@ abstract class Worker extends \Worker{
|
||||
* (unless you are using a custom autoloader).
|
||||
*/
|
||||
public function registerClassLoader(){
|
||||
require(\pocketmine\PATH . "vendor/autoload.php");
|
||||
if(!interface_exists("ClassLoader", false)){
|
||||
require(\pocketmine\PATH . "src/spl/ClassLoader.php");
|
||||
require(\pocketmine\PATH . "src/spl/BaseClassLoader.php");
|
||||
}
|
||||
if($this->classLoader !== null){
|
||||
$this->classLoader->register(true);
|
||||
$this->classLoader->register(false);
|
||||
}
|
||||
}
|
||||
|
||||
public function start(int $options = PTHREADS_INHERIT_ALL){
|
||||
public function start(?int $options = \PTHREADS_INHERIT_ALL){
|
||||
ThreadManager::getInstance()->add($this);
|
||||
|
||||
if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){
|
||||
|
@ -54,7 +54,7 @@ class Air extends Transparent{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canBeReplaced(Block $with = null) : bool{
|
||||
public function canBeReplaced() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
protected $id;
|
||||
/** @var int */
|
||||
protected $meta = 0;
|
||||
/** @var string */
|
||||
/** @var string|null */
|
||||
protected $fallbackName;
|
||||
/** @var int|null */
|
||||
protected $itemId;
|
||||
@ -70,12 +70,12 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
public $boundingBox = null;
|
||||
|
||||
/**
|
||||
* @param int $id The block type's ID, 0-255
|
||||
* @param int $meta Meta value of the block type
|
||||
* @param string $name English name of the block type (TODO: implement translations)
|
||||
* @param int $itemId The item ID of the block type, used for block picking and dropping items.
|
||||
* @param int $id The block type's ID, 0-255
|
||||
* @param int $meta Meta value of the block type
|
||||
* @param string|null $name English name of the block type (TODO: implement translations)
|
||||
* @param int $itemId The item ID of the block type, used for block picking and dropping items.
|
||||
*/
|
||||
public function __construct(int $id, int $meta = 0, string $name = "Unknown", int $itemId = null){
|
||||
public function __construct(int $id, int $meta = 0, string $name = null, int $itemId = null){
|
||||
$this->id = $id;
|
||||
$this->meta = $meta;
|
||||
$this->fallbackName = $name;
|
||||
@ -86,7 +86,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
* @return string
|
||||
*/
|
||||
public function getName() : string{
|
||||
return $this->fallbackName;
|
||||
return $this->fallbackName ?? "Unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,11 +284,9 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Block|null $with
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeReplaced(Block $with = null) : bool{
|
||||
public function canBeReplaced() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -168,8 +168,8 @@ class BlockFactory{
|
||||
self::registerBlock(new Trapdoor());
|
||||
//TODO: MONSTER_EGG
|
||||
self::registerBlock(new StoneBricks());
|
||||
//TODO: BROWN_MUSHROOM_BLOCK
|
||||
//TODO: RED_MUSHROOM_BLOCK
|
||||
self::registerBlock(new BrownMushroomBlock());
|
||||
self::registerBlock(new RedMushroomBlock());
|
||||
self::registerBlock(new IronBars());
|
||||
self::registerBlock(new GlassPane());
|
||||
self::registerBlock(new Melon());
|
||||
@ -341,7 +341,7 @@ class BlockFactory{
|
||||
public static function registerBlock(Block $block, bool $override = false){
|
||||
$id = $block->getId();
|
||||
|
||||
if(self::$list[$id] !== null and !(self::$list[$id] instanceof UnknownBlock) and !$override){
|
||||
if(!$override and self::isRegistered($id)){
|
||||
throw new \RuntimeException("Trying to overwrite an already registered block");
|
||||
}
|
||||
|
||||
@ -403,4 +403,15 @@ class BlockFactory{
|
||||
public static function getBlockStatesArray() : \SplFixedArray{
|
||||
return self::$fullList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a specified block ID is already registered in the block factory.
|
||||
*
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public static function isRegistered(int $id) : bool{
|
||||
$b = self::$list[$id];
|
||||
return $b !== null and !($b instanceof UnknownBlock);
|
||||
}
|
||||
}
|
@ -109,7 +109,7 @@ interface BlockIds{
|
||||
const CACTUS = 81;
|
||||
const CLAY_BLOCK = 82;
|
||||
const REEDS_BLOCK = 83, SUGARCANE_BLOCK = 83;
|
||||
|
||||
const JUKEBOX = 84;
|
||||
const FENCE = 85;
|
||||
const PUMPKIN = 86;
|
||||
const NETHERRACK = 87;
|
||||
@ -201,7 +201,8 @@ interface BlockIds{
|
||||
const COAL_BLOCK = 173;
|
||||
const PACKED_ICE = 174;
|
||||
const DOUBLE_PLANT = 175;
|
||||
|
||||
const STANDING_BANNER = 176;
|
||||
const WALL_BANNER = 177;
|
||||
const DAYLIGHT_DETECTOR_INVERTED = 178, DAYLIGHT_SENSOR_INVERTED = 178;
|
||||
const RED_SANDSTONE = 179;
|
||||
const RED_SANDSTONE_STAIRS = 180;
|
||||
@ -227,6 +228,7 @@ interface BlockIds{
|
||||
|
||||
const PURPUR_STAIRS = 203;
|
||||
|
||||
const UNDYED_SHULKER_BOX = 205;
|
||||
const END_BRICKS = 206;
|
||||
const FROSTED_ICE = 207;
|
||||
const END_ROD = 208;
|
||||
@ -270,6 +272,7 @@ interface BlockIds{
|
||||
const INFO_UPDATE2 = 249;
|
||||
const MOVINGBLOCK = 250, MOVING_BLOCK = 250;
|
||||
const OBSERVER = 251;
|
||||
const STRUCTURE_BLOCK = 252;
|
||||
|
||||
const RESERVED6 = 255;
|
||||
|
||||
|
@ -60,7 +60,7 @@ class BoneBlock extends Solid{
|
||||
|
||||
public function getDrops(Item $item) : array{
|
||||
if($item->isPickaxe() >= Tool::TIER_WOODEN){
|
||||
return parent::getDrops($item); // TODO: Change the autogenerated stub
|
||||
return parent::getDrops($item);
|
||||
}
|
||||
|
||||
return [];
|
||||
|
@ -21,21 +21,21 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
namespace pocketmine\block;
|
||||
|
||||
/**
|
||||
* Saves all the information regarding default inventory sizes and types
|
||||
*/
|
||||
interface SlotType{
|
||||
const RESULT = 0;
|
||||
use pocketmine\item\Item;
|
||||
|
||||
const CRAFTING = 1; //Not used in Minecraft: PE yet
|
||||
class BrownMushroomBlock extends RedMushroomBlock{
|
||||
|
||||
const ARMOR = 2;
|
||||
protected $id = Block::BROWN_MUSHROOM_BLOCK;
|
||||
|
||||
const CONTAINER = 3;
|
||||
public function getName() : string{
|
||||
return "Brown Mushroom Block";
|
||||
}
|
||||
|
||||
const HOTBAR = 4;
|
||||
|
||||
const FUEL = 5;
|
||||
public function getDrops(Item $item) : array{
|
||||
return [
|
||||
Item::get(Item::BROWN_MUSHROOM, 0, mt_rand(0, 2))
|
||||
];
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\inventory\BigCraftingGrid;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\Tool;
|
||||
use pocketmine\Player;
|
||||
@ -49,6 +50,7 @@ class CraftingTable extends Solid{
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($player instanceof Player){
|
||||
$player->setCraftingGrid(new BigCraftingGrid($player));
|
||||
$player->craftingType = 1;
|
||||
}
|
||||
|
||||
|
@ -44,13 +44,20 @@ class Dirt extends Solid{
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
if($this->meta === 1){
|
||||
return "Coarse Dirt";
|
||||
}
|
||||
return "Dirt";
|
||||
}
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($item->isHoe()){
|
||||
$item->useOn($this);
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::FARMLAND, 0), true);
|
||||
if($this->meta === 1){
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::DIRT), true);
|
||||
}else{
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::FARMLAND), true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class DoublePlant extends Flowable{
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
public function canBeReplaced(Block $with = null) : bool{
|
||||
public function canBeReplaced() : bool{
|
||||
return $this->meta === 2 or $this->meta === 3; //grass or fern
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ class Fire extends Flowable{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canBeReplaced(Block $with = null) : bool{
|
||||
public function canBeReplaced() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -83,6 +83,7 @@ class Grass extends Solid{
|
||||
$vector->z = mt_rand($this->z - 1, $this->z + 1);
|
||||
if(
|
||||
$this->level->getBlockIdAt($vector->x, $vector->y, $vector->z) !== Block::DIRT or
|
||||
$this->level->getBlockDataAt($vector->x, $vector->y, $vector->z) === 1 or
|
||||
$this->level->getFullLightAt($vector->x, $vector->y + 1, $vector->z) < 4 or
|
||||
BlockFactory::$lightFilter[$this->level->getBlockIdAt($vector->x, $vector->y + 1, $vector->z)] >= 3
|
||||
){
|
||||
|
@ -41,7 +41,7 @@ abstract class Liquid extends Transparent{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canBeReplaced(Block $with = null) : bool{
|
||||
public function canBeReplaced() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,10 @@ class NetherWartPlant extends Flowable{
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Nether Wart";
|
||||
}
|
||||
|
||||
public function ticksRandomly() : bool{
|
||||
return true;
|
||||
}
|
||||
|
@ -21,34 +21,35 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\Tool;
|
||||
|
||||
interface Transaction{
|
||||
class RedMushroomBlock extends Solid{
|
||||
|
||||
/**
|
||||
* @return Inventory
|
||||
*/
|
||||
public function getInventory() : Inventory;
|
||||
protected $id = Block::RED_MUSHROOM_BLOCK;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSlot() : int;
|
||||
public function __construct(int $meta = 0){
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item
|
||||
*/
|
||||
public function getSourceItem() : Item;
|
||||
public function getName() : string{
|
||||
return "Red Mushroom Block";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item
|
||||
*/
|
||||
public function getTargetItem() : Item;
|
||||
public function getHardness() : float{
|
||||
return 0.2;
|
||||
}
|
||||
|
||||
public function getToolType() : int{
|
||||
return Tool::TYPE_AXE;
|
||||
}
|
||||
|
||||
public function getDrops(Item $item) : array{
|
||||
return [
|
||||
Item::get(Item::RED_MUSHROOM, 0, mt_rand(0, 2))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getCreationTime() : float;
|
||||
}
|
@ -42,7 +42,7 @@ class SnowLayer extends Flowable{
|
||||
return "Snow Layer";
|
||||
}
|
||||
|
||||
public function canBeReplaced(Block $with = null) : bool{
|
||||
public function canBeReplaced() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ class TallGrass extends Flowable{
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
public function canBeReplaced(Block $with = null) : bool{
|
||||
public function canBeReplaced() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -78,10 +78,6 @@ class WoodenSlab extends Transparent{
|
||||
}
|
||||
}
|
||||
|
||||
public function canBeReplaced(Block $with = null) : bool{
|
||||
return $with !== null and $with->getId() === $this->getId() and ($with->getDamage() & 0x07) === ($this->getDamage() & 0x07);
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $facePos, Player $player = null) : bool{
|
||||
$this->meta &= 0x07;
|
||||
if($face === Vector3::SIDE_DOWN){
|
||||
|
@ -85,48 +85,52 @@ class SimpleCommandMap implements CommandMap{
|
||||
}
|
||||
|
||||
private function setDefaultCommands(){
|
||||
$this->register("pocketmine", new VersionCommand("version"));
|
||||
$this->register("pocketmine", new PluginsCommand("plugins"));
|
||||
$this->register("pocketmine", new SeedCommand("seed"));
|
||||
$this->register("pocketmine", new HelpCommand("help"));
|
||||
$this->register("pocketmine", new StopCommand("stop"));
|
||||
$this->register("pocketmine", new TellCommand("tell"));
|
||||
$this->register("pocketmine", new DefaultGamemodeCommand("defaultgamemode"));
|
||||
$this->register("pocketmine", new BanCommand("ban"));
|
||||
$this->register("pocketmine", new BanIpCommand("ban-ip"));
|
||||
$this->register("pocketmine", new BanListCommand("banlist"));
|
||||
$this->register("pocketmine", new PardonCommand("pardon"));
|
||||
$this->register("pocketmine", new PardonIpCommand("pardon-ip"));
|
||||
$this->register("pocketmine", new SayCommand("say"));
|
||||
$this->register("pocketmine", new MeCommand("me"));
|
||||
$this->register("pocketmine", new ListCommand("list"));
|
||||
$this->register("pocketmine", new DifficultyCommand("difficulty"));
|
||||
$this->register("pocketmine", new KickCommand("kick"));
|
||||
$this->register("pocketmine", new OpCommand("op"));
|
||||
$this->register("pocketmine", new DeopCommand("deop"));
|
||||
$this->register("pocketmine", new WhitelistCommand("whitelist"));
|
||||
$this->register("pocketmine", new SaveOnCommand("save-on"));
|
||||
$this->register("pocketmine", new SaveOffCommand("save-off"));
|
||||
$this->register("pocketmine", new SaveCommand("save-all"));
|
||||
$this->register("pocketmine", new GiveCommand("give"));
|
||||
$this->register("pocketmine", new EffectCommand("effect"));
|
||||
$this->register("pocketmine", new EnchantCommand("enchant"));
|
||||
$this->register("pocketmine", new ParticleCommand("particle"));
|
||||
$this->register("pocketmine", new GamemodeCommand("gamemode"));
|
||||
$this->register("pocketmine", new KillCommand("kill"));
|
||||
$this->register("pocketmine", new SpawnpointCommand("spawnpoint"));
|
||||
$this->register("pocketmine", new SetWorldSpawnCommand("setworldspawn"));
|
||||
$this->register("pocketmine", new TeleportCommand("tp"));
|
||||
$this->register("pocketmine", new TimeCommand("time"));
|
||||
$this->register("pocketmine", new TimingsCommand("timings"));
|
||||
$this->register("pocketmine", new TitleCommand("title"));
|
||||
$this->register("pocketmine", new ReloadCommand("reload"));
|
||||
$this->register("pocketmine", new TransferServerCommand("transferserver"));
|
||||
$this->registerAll("pocketmine", [
|
||||
new BanCommand("ban"),
|
||||
new BanIpCommand("ban-ip"),
|
||||
new BanListCommand("banlist"),
|
||||
new DefaultGamemodeCommand("defaultgamemode"),
|
||||
new DeopCommand("deop"),
|
||||
new DifficultyCommand("difficulty"),
|
||||
new EffectCommand("effect"),
|
||||
new EnchantCommand("enchant"),
|
||||
new GamemodeCommand("gamemode"),
|
||||
new GiveCommand("give"),
|
||||
new HelpCommand("help"),
|
||||
new KickCommand("kick"),
|
||||
new KillCommand("kill"),
|
||||
new ListCommand("list"),
|
||||
new MeCommand("me"),
|
||||
new OpCommand("op"),
|
||||
new PardonCommand("pardon"),
|
||||
new PardonIpCommand("pardon-ip"),
|
||||
new ParticleCommand("particle"),
|
||||
new PluginsCommand("plugins"),
|
||||
new ReloadCommand("reload"),
|
||||
new SaveCommand("save-all"),
|
||||
new SaveOffCommand("save-off"),
|
||||
new SaveOnCommand("save-on"),
|
||||
new SayCommand("say"),
|
||||
new SeedCommand("seed"),
|
||||
new SetWorldSpawnCommand("setworldspawn"),
|
||||
new SpawnpointCommand("spawnpoint"),
|
||||
new StopCommand("stop"),
|
||||
new TeleportCommand("tp"),
|
||||
new TellCommand("tell"),
|
||||
new TimeCommand("time"),
|
||||
new TimingsCommand("timings"),
|
||||
new TitleCommand("title"),
|
||||
new TransferServerCommand("transferserver"),
|
||||
new VersionCommand("version"),
|
||||
new WhitelistCommand("whitelist")
|
||||
]);
|
||||
|
||||
if($this->server->getProperty("debug.commands", false)){
|
||||
$this->register("pocketmine", new StatusCommand("status"));
|
||||
$this->register("pocketmine", new GarbageCollectorCommand("gc"));
|
||||
$this->register("pocketmine", new DumpMemoryCommand("dumpmemory"));
|
||||
$this->registerAll("pocketmine", [
|
||||
new StatusCommand("status"),
|
||||
new GarbageCollectorCommand("gc"),
|
||||
new DumpMemoryCommand("dumpmemory")
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +227,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
}
|
||||
|
||||
public function dispatch(CommandSender $sender, string $commandLine) : bool{
|
||||
$args = explode(" ", $commandLine);
|
||||
$args = array_map("stripslashes", str_getcsv($commandLine, " "));
|
||||
$sentCommandLabel = "";
|
||||
$target = $this->matchCommand($sentCommandLabel, $args);
|
||||
|
||||
|
@ -118,7 +118,7 @@ class EffectCommand extends VanillaCommand{
|
||||
$effect->setDuration($duration)->setAmplifier($amplification);
|
||||
|
||||
$player->addEffect($effect);
|
||||
self::broadcastCommandMessage($sender, new TranslationContainer("%commands.effect.success", [$effect->getName(), $effect->getId(), $effect->getAmplifier(), $player->getDisplayName(), $effect->getDuration() / 20]));
|
||||
self::broadcastCommandMessage($sender, new TranslationContainer("%commands.effect.success", [$effect->getName(), $effect->getAmplifier(), $player->getDisplayName(), $effect->getDuration() / 20, $effect->getId()]));
|
||||
}
|
||||
|
||||
|
||||
|
@ -76,10 +76,10 @@ class GamemodeCommand extends VanillaCommand{
|
||||
$sender->sendMessage("Game mode change for " . $target->getName() . " failed!");
|
||||
}else{
|
||||
if($target === $sender){
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.self", ['blame', 'mojang', Server::getGamemodeString($gameMode)]));
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.self", [Server::getGamemodeString($gameMode)]));
|
||||
}else{
|
||||
$target->sendMessage(new TranslationContainer("gameMode.changed", [Server::getGamemodeString($gameMode)]));
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.other", ['blame mojang', $target->getName(), Server::getGamemodeString($gameMode)]));
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.other", [$target->getName(), Server::getGamemodeString($gameMode)]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,12 +84,9 @@ class Arrow extends Projectile{
|
||||
$pk = new AddEntityPacket();
|
||||
$pk->type = Arrow::NETWORK_ID;
|
||||
$pk->entityRuntimeId = $this->getId();
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = $this->motionX;
|
||||
$pk->speedY = $this->motionY;
|
||||
$pk->speedZ = $this->motionZ;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->motion = $this->getMotion();
|
||||
|
||||
$pk->yaw = $this->yaw;
|
||||
$pk->pitch = $this->pitch;
|
||||
$pk->metadata = $this->dataProperties;
|
||||
|
@ -55,8 +55,10 @@ use pocketmine\nbt\tag\FloatTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\network\mcpe\protocol\MoveEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetEntityDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetEntityMotionPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\Server;
|
||||
@ -199,8 +201,13 @@ abstract class Entity extends Location implements Metadatable{
|
||||
const DATA_FLAG_IDLING = 39;
|
||||
const DATA_FLAG_EVOKER_SPELL = 40;
|
||||
const DATA_FLAG_CHARGE_ATTACK = 41;
|
||||
|
||||
const DATA_FLAG_LINGER = 45;
|
||||
const DATA_FLAG_WASD_CONTROLLED = 42;
|
||||
const DATA_FLAG_CAN_POWER_JUMP = 43;
|
||||
const DATA_FLAG_LINGER = 44;
|
||||
const DATA_FLAG_HAS_COLLISION = 45;
|
||||
const DATA_FLAG_AFFECTED_BY_GRAVITY = 46;
|
||||
const DATA_FLAG_FIRE_IMMUNE = 47;
|
||||
const DATA_FLAG_DANCING = 48;
|
||||
|
||||
public static $entityCount = 1;
|
||||
/** @var Entity[] */
|
||||
@ -436,6 +443,9 @@ abstract class Entity extends Location implements Metadatable{
|
||||
$this->attributeMap = new AttributeMap();
|
||||
$this->addAttributes();
|
||||
|
||||
$this->setGenericFlag(self::DATA_FLAG_AFFECTED_BY_GRAVITY, true);
|
||||
$this->setGenericFlag(self::DATA_FLAG_HAS_COLLISION, true);
|
||||
|
||||
$this->chunk->addEntity($this);
|
||||
$this->level->addEntity($this);
|
||||
$this->initEntity();
|
||||
@ -1161,7 +1171,7 @@ abstract class Entity extends Location implements Metadatable{
|
||||
$this->lastYaw = $this->yaw;
|
||||
$this->lastPitch = $this->pitch;
|
||||
|
||||
$this->level->addEntityMovement($this->chunk->getX(), $this->chunk->getZ(), $this->id, $this->x, $this->y + $this->baseOffset, $this->z, $this->yaw, $this->pitch, $this->yaw);
|
||||
$this->broadcastMovement();
|
||||
}
|
||||
|
||||
if($diffMotion > 0.0025 or ($diffMotion > 0.0001 and $this->getMotion()->lengthSquared() <= 0.0001)){ //0.05 ** 2
|
||||
@ -1169,10 +1179,33 @@ abstract class Entity extends Location implements Metadatable{
|
||||
$this->lastMotionY = $this->motionY;
|
||||
$this->lastMotionZ = $this->motionZ;
|
||||
|
||||
$this->level->addEntityMotion($this->chunk->getX(), $this->chunk->getZ(), $this->id, $this->motionX, $this->motionY, $this->motionZ);
|
||||
$this->broadcastMotion();
|
||||
}
|
||||
}
|
||||
|
||||
public function getOffsetPosition(Vector3 $vector3) : Vector3{
|
||||
return new Vector3($vector3->x, $vector3->y + $this->baseOffset, $vector3->z);
|
||||
}
|
||||
|
||||
protected function broadcastMovement(){
|
||||
$pk = new MoveEntityPacket();
|
||||
$pk->entityRuntimeId = $this->id;
|
||||
$pk->position = $this->getOffsetPosition($this);
|
||||
$pk->yaw = $this->yaw;
|
||||
$pk->pitch = $this->pitch;
|
||||
$pk->headYaw = $this->yaw; //TODO
|
||||
|
||||
$this->level->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $pk);
|
||||
}
|
||||
|
||||
protected function broadcastMotion(){
|
||||
$pk = new SetEntityMotionPacket();
|
||||
$pk->entityRuntimeId = $this->id;
|
||||
$pk->motion = $this->getMotion();
|
||||
|
||||
$this->level->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $pk);
|
||||
}
|
||||
|
||||
protected function applyDragBeforeGravity() : bool{
|
||||
return false;
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ namespace pocketmine\entity;
|
||||
use pocketmine\block\BlockFactory;
|
||||
use pocketmine\event\entity\EntityBlockChangeEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\item\Item as ItemItem;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
@ -126,12 +125,8 @@ class FallingSand extends Entity{
|
||||
$pk = new AddEntityPacket();
|
||||
$pk->type = FallingSand::NETWORK_ID;
|
||||
$pk->entityRuntimeId = $this->getId();
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = $this->motionX;
|
||||
$pk->speedY = $this->motionY;
|
||||
$pk->speedZ = $this->motionZ;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->motion = $this->getMotion();
|
||||
$pk->yaw = $this->yaw;
|
||||
$pk->pitch = $this->pitch;
|
||||
$pk->metadata = $this->dataProperties;
|
||||
|
@ -31,12 +31,10 @@ use pocketmine\inventory\PlayerInventory;
|
||||
use pocketmine\item\Item as ItemItem;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\FloatTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
|
||||
use pocketmine\Player;
|
||||
@ -280,30 +278,34 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
return $this->inventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
|
||||
*/
|
||||
protected function initHumanData(){
|
||||
if(isset($this->namedtag->NameTag)){
|
||||
$this->setNameTag($this->namedtag["NameTag"]);
|
||||
}
|
||||
|
||||
if(isset($this->namedtag->Skin) and $this->namedtag->Skin instanceof CompoundTag){
|
||||
$this->setSkin($this->namedtag->Skin["Data"], $this->namedtag->Skin["Name"]);
|
||||
}
|
||||
|
||||
$this->uuid = UUID::fromData((string) $this->getId(), $this->getSkinData(), $this->getNameTag());
|
||||
}
|
||||
|
||||
protected function initEntity(){
|
||||
|
||||
$this->setPlayerFlag(self::DATA_PLAYER_FLAG_SLEEP, false);
|
||||
$this->setDataProperty(self::DATA_PLAYER_BED_POSITION, self::DATA_TYPE_POS, [0, 0, 0], false);
|
||||
|
||||
$this->inventory = new PlayerInventory($this);
|
||||
if($this instanceof Player){
|
||||
$this->addWindow($this->inventory, 0);
|
||||
}else{
|
||||
if(isset($this->namedtag->NameTag)){
|
||||
$this->setNameTag($this->namedtag["NameTag"]);
|
||||
}
|
||||
|
||||
if(isset($this->namedtag->Skin) and $this->namedtag->Skin instanceof CompoundTag){
|
||||
$this->setSkin($this->namedtag->Skin["Data"], $this->namedtag->Skin["Name"]);
|
||||
}
|
||||
|
||||
$this->uuid = UUID::fromData((string) $this->getId(), $this->getSkinData(), $this->getNameTag());
|
||||
}
|
||||
$this->initHumanData();
|
||||
|
||||
if(isset($this->namedtag->Inventory) and $this->namedtag->Inventory instanceof ListTag){
|
||||
foreach($this->namedtag->Inventory as $item){
|
||||
foreach($this->namedtag->Inventory as $i => $item){
|
||||
if($item["Slot"] >= 0 and $item["Slot"] < 9){ //Hotbar
|
||||
$this->inventory->setHotbarSlotIndex($item["Slot"], isset($item["TrueSlot"]) ? $item["TrueSlot"] : -1);
|
||||
//Old hotbar saving stuff, remove it (useless now)
|
||||
unset($this->namedtag->Inventory->{$i});
|
||||
}elseif($item["Slot"] >= 100 and $item["Slot"] < 104){ //Armor
|
||||
$this->inventory->setItem($this->inventory->getSize() + $item["Slot"] - 100, ItemItem::nbtDeserialize($item));
|
||||
}else{
|
||||
@ -445,28 +447,6 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
$this->namedtag->Inventory = new ListTag("Inventory", []);
|
||||
$this->namedtag->Inventory->setTagType(NBT::TAG_Compound);
|
||||
if($this->inventory !== null){
|
||||
for($slot = 0; $slot < 9; ++$slot){
|
||||
$hotbarSlot = $this->inventory->getHotbarSlotIndex($slot);
|
||||
if($hotbarSlot !== -1){
|
||||
$item = $this->inventory->getItem($hotbarSlot);
|
||||
if($item->getId() !== 0 and $item->getCount() > 0){
|
||||
$tag = $item->nbtSerialize($slot);
|
||||
$tag->TrueSlot = new ByteTag("TrueSlot", $hotbarSlot);
|
||||
$this->namedtag->Inventory[$slot] = $tag;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->namedtag->Inventory[$slot] = new CompoundTag("", [
|
||||
new ByteTag("Count", 0),
|
||||
new ShortTag("Damage", 0),
|
||||
new ByteTag("Slot", $slot),
|
||||
new ByteTag("TrueSlot", -1),
|
||||
new ShortTag("id", 0)
|
||||
]);
|
||||
}
|
||||
|
||||
//Normal inventory
|
||||
$slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize();
|
||||
for($slot = $this->inventory->getHotbarSize(); $slot < $slotCount; ++$slot){
|
||||
@ -511,12 +491,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
$pk->uuid = $this->getUniqueId();
|
||||
$pk->username = $this->getName();
|
||||
$pk->entityRuntimeId = $this->getId();
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = $this->motionX;
|
||||
$pk->speedY = $this->motionY;
|
||||
$pk->speedZ = $this->motionZ;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->motion = $this->getMotion();
|
||||
$pk->yaw = $this->yaw;
|
||||
$pk->pitch = $this->pitch;
|
||||
$pk->item = $this->getInventory()->getItemInHand();
|
||||
@ -537,6 +513,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
foreach($this->inventory->getViewers() as $viewer){
|
||||
$viewer->removeWindow($this->inventory);
|
||||
}
|
||||
|
||||
$this->inventory = null;
|
||||
}
|
||||
parent::close();
|
||||
}
|
||||
|
@ -206,12 +206,8 @@ class Item extends Entity{
|
||||
public function spawnTo(Player $player){
|
||||
$pk = new AddItemEntityPacket();
|
||||
$pk->entityRuntimeId = $this->getId();
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = $this->motionX;
|
||||
$pk->speedY = $this->motionY;
|
||||
$pk->speedZ = $this->motionZ;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->motion = $this->getMotion();
|
||||
$pk->item = $this->getItem();
|
||||
$pk->metadata = $this->dataProperties;
|
||||
$player->dataPacket($pk);
|
||||
|
@ -117,12 +117,8 @@ class PrimedTNT extends Entity implements Explosive{
|
||||
$pk = new AddEntityPacket();
|
||||
$pk->type = PrimedTNT::NETWORK_ID;
|
||||
$pk->entityRuntimeId = $this->getId();
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = $this->motionX;
|
||||
$pk->speedY = $this->motionY;
|
||||
$pk->speedZ = $this->motionZ;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->motion = $this->getMotion();
|
||||
$pk->metadata = $this->dataProperties;
|
||||
$player->dataPacket($pk);
|
||||
|
||||
|
@ -60,12 +60,8 @@ class Snowball extends Projectile{
|
||||
$pk = new AddEntityPacket();
|
||||
$pk->type = Snowball::NETWORK_ID;
|
||||
$pk->entityRuntimeId = $this->getId();
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = $this->motionX;
|
||||
$pk->speedY = $this->motionY;
|
||||
$pk->speedZ = $this->motionZ;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->motion = $this->getMotion();
|
||||
$pk->metadata = $this->dataProperties;
|
||||
$player->dataPacket($pk);
|
||||
|
||||
|
@ -131,12 +131,8 @@ class Squid extends WaterAnimal{
|
||||
$pk = new AddEntityPacket();
|
||||
$pk->entityRuntimeId = $this->getId();
|
||||
$pk->type = Squid::NETWORK_ID;
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = $this->motionX;
|
||||
$pk->speedY = $this->motionY;
|
||||
$pk->speedZ = $this->motionZ;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->motion = $this->getMotion();
|
||||
$pk->yaw = $this->yaw;
|
||||
$pk->pitch = $this->pitch;
|
||||
$pk->metadata = $this->dataProperties;
|
||||
|
@ -55,12 +55,8 @@ class Villager extends Creature implements NPC, Ageable{
|
||||
$pk = new AddEntityPacket();
|
||||
$pk->entityRuntimeId = $this->getId();
|
||||
$pk->type = Villager::NETWORK_ID;
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = $this->motionX;
|
||||
$pk->speedY = $this->motionY;
|
||||
$pk->speedZ = $this->motionZ;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->motion = $this->getMotion();
|
||||
$pk->yaw = $this->yaw;
|
||||
$pk->pitch = $this->pitch;
|
||||
$pk->metadata = $this->dataProperties;
|
||||
|
@ -43,12 +43,8 @@ class Zombie extends Monster{
|
||||
$pk = new AddEntityPacket();
|
||||
$pk->entityRuntimeId = $this->getId();
|
||||
$pk->type = Zombie::NETWORK_ID;
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = $this->motionX;
|
||||
$pk->speedY = $this->motionY;
|
||||
$pk->speedZ = $this->motionZ;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->motion = $this->getMotion();
|
||||
$pk->yaw = $this->yaw;
|
||||
$pk->pitch = $this->pitch;
|
||||
$pk->metadata = $this->dataProperties;
|
||||
|
@ -46,7 +46,7 @@ class SignChangeEvent extends BlockEvent implements Cancellable{
|
||||
public function __construct(Block $theBlock, Player $thePlayer, array $theLines){
|
||||
parent::__construct($theBlock);
|
||||
$this->player = $thePlayer;
|
||||
$this->lines = $theLines;
|
||||
$this->setLines($theLines);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,51 +26,55 @@ namespace pocketmine\event\inventory;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\Event;
|
||||
use pocketmine\inventory\Recipe;
|
||||
use pocketmine\inventory\transaction\CraftingTransaction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
|
||||
class CraftItemEvent extends Event implements Cancellable{
|
||||
public static $handlerList = null;
|
||||
|
||||
/** @var Item[] */
|
||||
private $input;
|
||||
/** @var Recipe */
|
||||
private $recipe;
|
||||
/** @var Player */
|
||||
private $player;
|
||||
|
||||
/** @var CraftingTransaction */
|
||||
private $transaction;
|
||||
|
||||
/**
|
||||
* @param Player $player
|
||||
* @param Item[] $input
|
||||
* @param Recipe $recipe
|
||||
* @param CraftingTransaction $transaction
|
||||
*/
|
||||
public function __construct(Player $player, array $input, Recipe $recipe){
|
||||
$this->player = $player;
|
||||
$this->input = $input;
|
||||
$this->recipe = $recipe;
|
||||
public function __construct(CraftingTransaction $transaction){
|
||||
$this->transaction = $transaction;
|
||||
}
|
||||
|
||||
public function getTransaction() : CraftingTransaction{
|
||||
return $this->transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This returns a one-dimensional array of ingredients and does not account for the positioning of
|
||||
* items in the crafting grid. Prefer getting the input map from the transaction instead.
|
||||
*
|
||||
* @return Item[]
|
||||
*/
|
||||
public function getInput() : array{
|
||||
return array_map(function(Item $item) : Item{
|
||||
return clone $item;
|
||||
}, $this->input);
|
||||
}, array_merge(...$this->transaction->getInputMap()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Recipe
|
||||
*/
|
||||
public function getRecipe() : Recipe{
|
||||
return $this->recipe;
|
||||
$recipe = $this->transaction->getRecipe();
|
||||
if($recipe === null){
|
||||
throw new \RuntimeException("This shouldn't be called if the transaction can't be executed");
|
||||
}
|
||||
|
||||
return $recipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Player
|
||||
*/
|
||||
public function getPlayer() : Player{
|
||||
return $this->player;
|
||||
return $this->transaction->getSource();
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace pocketmine\event\inventory;
|
||||
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\Event;
|
||||
use pocketmine\inventory\TransactionGroup;
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
|
||||
/**
|
||||
* Called when there is a transaction between two Inventory objects.
|
||||
@ -34,21 +34,21 @@ use pocketmine\inventory\TransactionGroup;
|
||||
class InventoryTransactionEvent extends Event implements Cancellable{
|
||||
public static $handlerList = null;
|
||||
|
||||
/** @var TransactionGroup */
|
||||
private $ts;
|
||||
/** @var InventoryTransaction */
|
||||
private $transaction;
|
||||
|
||||
/**
|
||||
* @param TransactionGroup $ts
|
||||
* @param InventoryTransaction $transaction
|
||||
*/
|
||||
public function __construct(TransactionGroup $ts){
|
||||
$this->ts = $ts;
|
||||
public function __construct(InventoryTransaction $transaction){
|
||||
$this->transaction = $transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TransactionGroup
|
||||
* @return InventoryTransaction
|
||||
*/
|
||||
public function getTransaction() : TransactionGroup{
|
||||
return $this->ts;
|
||||
public function getTransaction() : InventoryTransaction{
|
||||
return $this->transaction;
|
||||
}
|
||||
|
||||
}
|
||||
|
59
src/pocketmine/event/player/PlayerBlockPickEvent.php
Normal file
59
src/pocketmine/event/player/PlayerBlockPickEvent.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\player;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
|
||||
/**
|
||||
* Called when a player middle-clicks on a block to get an item in creative mode.
|
||||
*/
|
||||
class PlayerBlockPickEvent extends PlayerEvent implements Cancellable{
|
||||
public static $handlerList = null;
|
||||
|
||||
/** @var Block */
|
||||
private $blockClicked;
|
||||
/** @var Item */
|
||||
private $resultItem;
|
||||
|
||||
public function __construct(Player $player, Block $blockClicked, Item $resultItem){
|
||||
$this->player = $player;
|
||||
$this->blockClicked = $blockClicked;
|
||||
$this->resultItem = $resultItem;
|
||||
}
|
||||
|
||||
public function getBlock() : Block{
|
||||
return $this->blockClicked;
|
||||
}
|
||||
|
||||
public function getResultItem() : Item{
|
||||
return $this->resultItem;
|
||||
}
|
||||
|
||||
public function setResultItem(Item $item) : void{
|
||||
$this->resultItem = clone $item;
|
||||
}
|
||||
}
|
@ -34,18 +34,21 @@ class PlayerItemHeldEvent extends PlayerEvent implements Cancellable{
|
||||
private $item;
|
||||
/** @var int */
|
||||
private $hotbarSlot;
|
||||
/** @var int */
|
||||
private $inventorySlot;
|
||||
|
||||
public function __construct(Player $player, Item $item, int $inventorySlot, int $hotbarSlot){
|
||||
public function __construct(Player $player, Item $item, int $hotbarSlot){
|
||||
$this->player = $player;
|
||||
$this->item = $item;
|
||||
$this->inventorySlot = $inventorySlot;
|
||||
$this->hotbarSlot = $hotbarSlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hotbar slot the player is attempting to hold.
|
||||
*
|
||||
* NOTE: This event is called BEFORE the slot is equipped server-side. Setting the player's held item during this
|
||||
* event will result in the **old** slot being changed, not this one.
|
||||
*
|
||||
* To change the item in the slot that the player is attempting to hold, set the slot that this function reports.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSlot() : int{
|
||||
@ -53,14 +56,25 @@ class PlayerItemHeldEvent extends PlayerEvent implements Cancellable{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This is currently an alias of {@link getSlot}
|
||||
*
|
||||
* Some background for confused future readers: Before MCPE 1.2, hotbar slots and inventory slots were not the same
|
||||
* thing - a hotbar slot was a link to a certain slot in the inventory.
|
||||
* As of 1.2, hotbar slots are now always linked to their respective slots in the inventory, meaning that the two
|
||||
* are now synonymous, rendering the separate methods obsolete.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getInventorySlot() : int{
|
||||
return $this->inventorySlot;
|
||||
return $this->getSlot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item in the slot that the player is trying to equip.
|
||||
*
|
||||
* @return Item
|
||||
*/
|
||||
public function getItem() : Item{
|
||||
return $this->item;
|
||||
}
|
||||
|
||||
}
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\event\server;
|
||||
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\network\SourceInterface;
|
||||
|
||||
/**
|
||||
* Called when a network interface is registered into the network, for example the RakLib interface.
|
||||
|
@ -24,21 +24,39 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\level\Position;
|
||||
use pocketmine\network\mcpe\protocol\types\WindowTypes;
|
||||
use pocketmine\Player;
|
||||
|
||||
class AnvilInventory extends ContainerInventory{
|
||||
|
||||
/** @var FakeBlockMenu */
|
||||
protected $holder;
|
||||
|
||||
public function __construct(Position $pos){
|
||||
parent::__construct(new FakeBlockMenu($this, $pos), InventoryType::get(InventoryType::ANVIL));
|
||||
parent::__construct(new FakeBlockMenu($this, $pos));
|
||||
}
|
||||
|
||||
public function getNetworkType() : int{
|
||||
return WindowTypes::ANVIL;
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Anvil";
|
||||
}
|
||||
|
||||
public function getDefaultSize() : int{
|
||||
return 3; //1 input, 1 material, 1 result
|
||||
}
|
||||
|
||||
/**
|
||||
* This override is here for documentation and code completion purposes only.
|
||||
* @return FakeBlockMenu
|
||||
*/
|
||||
public function getHolder(){
|
||||
return $this->holder;
|
||||
}
|
||||
|
||||
public function onClose(Player $who){
|
||||
public function onClose(Player $who) : void{
|
||||
parent::onClose($who);
|
||||
|
||||
for($i = 0; $i < 2; ++$i){
|
||||
|
@ -23,29 +23,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\entity\EntityInventoryChangeEvent;
|
||||
use pocketmine\event\inventory\InventoryOpenEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\ContainerIds;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
abstract class BaseInventory implements Inventory{
|
||||
|
||||
/** @var InventoryType */
|
||||
protected $type;
|
||||
/** @var int */
|
||||
protected $maxStackSize = Inventory::MAX_STACK;
|
||||
/** @var int */
|
||||
protected $size;
|
||||
/** @var string */
|
||||
protected $name;
|
||||
/** @var string */
|
||||
protected $title;
|
||||
/** @var Item[] */
|
||||
/** @var \SplFixedArray<Item> */
|
||||
protected $slots = [];
|
||||
/** @var Player[] */
|
||||
protected $viewers = [];
|
||||
@ -54,108 +48,105 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
/**
|
||||
* @param InventoryHolder $holder
|
||||
* @param InventoryType $type
|
||||
* @param Item[] $items
|
||||
* @param int $overrideSize
|
||||
* @param string $overrideTitle
|
||||
* @param int $size
|
||||
* @param string $title
|
||||
*/
|
||||
public function __construct(InventoryHolder $holder, InventoryType $type, array $items = [], $overrideSize = null, $overrideTitle = null){
|
||||
public function __construct(InventoryHolder $holder, array $items = [], int $size = null, string $title = null){
|
||||
$this->holder = $holder;
|
||||
|
||||
$this->type = $type;
|
||||
if($overrideSize !== null){
|
||||
$this->size = (int) $overrideSize;
|
||||
}else{
|
||||
$this->size = $this->type->getDefaultSize();
|
||||
}
|
||||
$this->slots = new \SplFixedArray($size ?? $this->getDefaultSize());
|
||||
$this->title = $title ?? $this->getName();
|
||||
|
||||
if($overrideTitle !== null){
|
||||
$this->title = $overrideTitle;
|
||||
}else{
|
||||
$this->title = $this->type->getDefaultTitle();
|
||||
}
|
||||
|
||||
$this->name = $this->type->getDefaultTitle();
|
||||
|
||||
$this->setContents($items);
|
||||
$this->setContents($items, false);
|
||||
}
|
||||
|
||||
public function __destruct(){
|
||||
$this->holder = null;
|
||||
$this->slots = [];
|
||||
}
|
||||
|
||||
public function getSize() : int{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function setSize(int $size){
|
||||
$this->size = $size;
|
||||
}
|
||||
|
||||
public function getMaxStackSize() : int{
|
||||
return $this->maxStackSize;
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return $this->name;
|
||||
}
|
||||
abstract public function getName() : string;
|
||||
|
||||
public function getTitle() : string{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getItem(int $index) : Item{
|
||||
assert($index >= 0, "Inventory slot should not be negative");
|
||||
return isset($this->slots[$index]) ? clone $this->slots[$index] : ItemFactory::get(Item::AIR, 0, 0);
|
||||
/**
|
||||
* Returns the size of the inventory.
|
||||
* @return int
|
||||
*/
|
||||
public function getSize() : int{
|
||||
return $this->slots->getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new size of the inventory.
|
||||
* WARNING: If the size is smaller, any items past the new size will be lost.
|
||||
*
|
||||
* @param int $size
|
||||
*/
|
||||
public function setSize(int $size){
|
||||
$this->slots->setSize($size);
|
||||
}
|
||||
|
||||
abstract public function getDefaultSize() : int;
|
||||
|
||||
public function getMaxStackSize() : int{
|
||||
return $this->maxStackSize;
|
||||
}
|
||||
|
||||
public function getItem(int $index) : Item{
|
||||
return $this->slots[$index] !== null ? clone $this->slots[$index] : ItemFactory::get(Item::AIR, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
*/
|
||||
public function getContents() : array{
|
||||
return $this->slots;
|
||||
return array_filter($this->slots->toArray(), function(Item $item = null){ return $item !== null; });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
* @param bool $send
|
||||
*/
|
||||
public function setContents(array $items){
|
||||
if(count($items) > $this->size){
|
||||
$items = array_slice($items, 0, $this->size, true);
|
||||
public function setContents(array $items, bool $send = true) : void{
|
||||
if(count($items) > $this->getSize()){
|
||||
$items = array_slice($items, 0, $this->getSize(), true);
|
||||
}
|
||||
|
||||
for($i = 0; $i < $this->size; ++$i){
|
||||
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
||||
if(!isset($items[$i])){
|
||||
if(isset($this->slots[$i])){
|
||||
$this->clear($i);
|
||||
if($this->slots[$i] !== null){
|
||||
$this->clear($i, false);
|
||||
}
|
||||
}else{
|
||||
if(!$this->setItem($i, $items[$i])){
|
||||
$this->clear($i);
|
||||
if(!$this->setItem($i, $items[$i], false)){
|
||||
$this->clear($i, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($send){
|
||||
$this->sendContents($this->getViewers());
|
||||
}
|
||||
}
|
||||
|
||||
public function setItem(int $index, Item $item) : bool{
|
||||
$item = clone $item;
|
||||
if($index < 0 or $index >= $this->size){
|
||||
return false;
|
||||
}elseif($item->getId() === 0 or $item->getCount() <= 0){
|
||||
return $this->clear($index);
|
||||
protected function doSetItemEvents(int $index, Item $newItem) : ?Item{
|
||||
return $newItem;
|
||||
}
|
||||
|
||||
public function setItem(int $index, Item $item, bool $send = true) : bool{
|
||||
if($item->isNull()){
|
||||
$item = ItemFactory::get(Item::AIR, 0, 0);
|
||||
}else{
|
||||
$item = clone $item;
|
||||
}
|
||||
|
||||
$holder = $this->getHolder();
|
||||
if($holder instanceof Entity){
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($holder, $this->getItem($index), $item, $index));
|
||||
if($ev->isCancelled()){
|
||||
$this->sendSlot($index, $this->getViewers());
|
||||
return false;
|
||||
}
|
||||
$item = $ev->getNewItem();
|
||||
$newItem = $this->doSetItemEvents($index, $item);
|
||||
if($newItem === null){
|
||||
return false;
|
||||
}
|
||||
|
||||
$old = $this->getItem($index);
|
||||
$this->slots[$index] = clone $item;
|
||||
$this->onSlotChange($index, $old);
|
||||
$this->slots[$index] = $newItem->isNull() ? null : $newItem;
|
||||
$this->onSlotChange($index, $old, $send);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -189,7 +180,7 @@ abstract class BaseInventory implements Inventory{
|
||||
return $slots;
|
||||
}
|
||||
|
||||
public function remove(Item $item){
|
||||
public function remove(Item $item) : void{
|
||||
$checkDamage = !$item->hasAnyDamageValue();
|
||||
$checkTags = $item->hasCompoundTag();
|
||||
|
||||
@ -200,13 +191,13 @@ abstract class BaseInventory implements Inventory{
|
||||
}
|
||||
}
|
||||
|
||||
public function first(Item $item) : int{
|
||||
$count = max(1, $item->getCount());
|
||||
$checkDamage = !$item->hasAnyDamageValue();
|
||||
$checkTags = $item->hasCompoundTag();
|
||||
public function first(Item $item, bool $exact = false) : int{
|
||||
$count = $exact ? $item->getCount() : max(1, $item->getCount());
|
||||
$checkDamage = $exact || !$item->hasAnyDamageValue();
|
||||
$checkTags = $exact || $item->hasCompoundTag();
|
||||
|
||||
foreach($this->getContents() as $index => $i){
|
||||
if($item->equals($i, $checkDamage, $checkTags) and $i->getCount() >= $count){
|
||||
if($item->equals($i, $checkDamage, $checkTags) and ($i->getCount() === $count or (!$exact and $i->getCount() > $count))){
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
@ -215,8 +206,8 @@ abstract class BaseInventory implements Inventory{
|
||||
}
|
||||
|
||||
public function firstEmpty() : int{
|
||||
for($i = 0; $i < $this->size; ++$i){
|
||||
if($this->getItem($i)->getId() === Item::AIR){
|
||||
foreach($this->slots as $i => $slot){
|
||||
if($slot === null or $slot->isNull()){
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
@ -234,7 +225,7 @@ abstract class BaseInventory implements Inventory{
|
||||
if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){
|
||||
$item->setCount($item->getCount() - $diff);
|
||||
}
|
||||
}elseif($slot->getId() === Item::AIR){
|
||||
}elseif($slot->isNull()){
|
||||
$item->setCount($item->getCount() - $this->getMaxStackSize());
|
||||
}
|
||||
|
||||
@ -260,7 +251,7 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
for($i = 0; $i < $this->getSize(); ++$i){
|
||||
$item = $this->getItem($i);
|
||||
if($item->getId() === Item::AIR or $item->getCount() <= 0){
|
||||
if($item->isNull()){
|
||||
$emptySlots[] = $i;
|
||||
}
|
||||
|
||||
@ -315,7 +306,7 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
for($i = 0; $i < $this->getSize(); ++$i){
|
||||
$item = $this->getItem($i);
|
||||
if($item->getId() === Item::AIR or $item->getCount() <= 0){
|
||||
if($item->isNull()){
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -339,35 +330,16 @@ abstract class BaseInventory implements Inventory{
|
||||
return $itemSlots;
|
||||
}
|
||||
|
||||
public function clear(int $index) : bool{
|
||||
if(isset($this->slots[$index])){
|
||||
$item = ItemFactory::get(Item::AIR, 0, 0);
|
||||
$old = $this->slots[$index];
|
||||
$holder = $this->getHolder();
|
||||
if($holder instanceof Entity){
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($holder, $old, $item, $index));
|
||||
if($ev->isCancelled()){
|
||||
$this->sendSlot($index, $this->getViewers());
|
||||
return false;
|
||||
}
|
||||
$item = $ev->getNewItem();
|
||||
}
|
||||
if($item->getId() !== Item::AIR){
|
||||
$this->slots[$index] = clone $item;
|
||||
}else{
|
||||
unset($this->slots[$index]);
|
||||
}
|
||||
|
||||
$this->onSlotChange($index, $old);
|
||||
}
|
||||
|
||||
return true;
|
||||
public function clear(int $index, bool $send = true) : bool{
|
||||
return $this->setItem($index, ItemFactory::get(Item::AIR, 0, 0), $send);
|
||||
}
|
||||
|
||||
public function clearAll(){
|
||||
foreach($this->getContents() as $index => $i){
|
||||
$this->clear($index);
|
||||
public function clearAll() : void{
|
||||
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
||||
$this->clear($i, false);
|
||||
}
|
||||
|
||||
$this->sendContents($this->getViewers());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -381,7 +353,7 @@ abstract class BaseInventory implements Inventory{
|
||||
return $this->holder;
|
||||
}
|
||||
|
||||
public function setMaxStackSize(int $size){
|
||||
public function setMaxStackSize(int $size) : void{
|
||||
$this->maxStackSize = $size;
|
||||
}
|
||||
|
||||
@ -395,44 +367,46 @@ abstract class BaseInventory implements Inventory{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function close(Player $who){
|
||||
public function close(Player $who) : void{
|
||||
$this->onClose($who);
|
||||
}
|
||||
|
||||
public function onOpen(Player $who){
|
||||
public function onOpen(Player $who) : void{
|
||||
$this->viewers[spl_object_hash($who)] = $who;
|
||||
}
|
||||
|
||||
public function onClose(Player $who){
|
||||
public function onClose(Player $who) : void{
|
||||
unset($this->viewers[spl_object_hash($who)]);
|
||||
}
|
||||
|
||||
public function onSlotChange($index, $before){
|
||||
$this->sendSlot($index, $this->getViewers());
|
||||
public function onSlotChange(int $index, Item $before, bool $send) : void{
|
||||
if($send){
|
||||
$this->sendSlot($index, $this->getViewers());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Player|Player[] $target
|
||||
*/
|
||||
public function sendContents($target){
|
||||
public function sendContents($target) : void{
|
||||
if($target instanceof Player){
|
||||
$target = [$target];
|
||||
}
|
||||
|
||||
$pk = new ContainerSetContentPacket();
|
||||
$pk->slots = [];
|
||||
for($i = 0; $i < $this->getSize(); ++$i){
|
||||
$pk->slots[$i] = $this->getItem($i);
|
||||
$pk = new InventoryContentPacket();
|
||||
|
||||
//Using getSize() here allows PlayerInventory to report that it's 4 slots smaller than it actually is (armor hack)
|
||||
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
||||
$pk->items[$i] = $this->getItem($i);
|
||||
}
|
||||
|
||||
foreach($target as $player){
|
||||
if(($id = $player->getWindowId($this)) === -1 or $player->spawned !== true){
|
||||
if(($id = $player->getWindowId($this)) === ContainerIds::NONE or $player->spawned !== true){
|
||||
$this->close($player);
|
||||
continue;
|
||||
}
|
||||
$pk->windowid = $id;
|
||||
$pk->targetEid = $player->getId();
|
||||
$pk->windowId = $id;
|
||||
$player->dataPacket($pk);
|
||||
}
|
||||
}
|
||||
@ -441,27 +415,22 @@ abstract class BaseInventory implements Inventory{
|
||||
* @param int $index
|
||||
* @param Player|Player[] $target
|
||||
*/
|
||||
public function sendSlot($index, $target){
|
||||
public function sendSlot(int $index, $target) : void{
|
||||
if($target instanceof Player){
|
||||
$target = [$target];
|
||||
}
|
||||
|
||||
$pk = new ContainerSetSlotPacket();
|
||||
$pk->slot = $index;
|
||||
$pk->item = clone $this->getItem($index);
|
||||
$pk = new InventorySlotPacket();
|
||||
$pk->inventorySlot = $index;
|
||||
$pk->item = $this->getItem($index);
|
||||
|
||||
foreach($target as $player){
|
||||
if(($id = $player->getWindowId($this)) === -1){
|
||||
if(($id = $player->getWindowId($this)) === ContainerIds::NONE){
|
||||
$this->close($player);
|
||||
continue;
|
||||
}
|
||||
$pk->windowid = $id;
|
||||
$pk->windowId = $id;
|
||||
$player->dataPacket($pk);
|
||||
}
|
||||
}
|
||||
|
||||
public function getType() : InventoryType{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
class BaseTransaction implements Transaction{
|
||||
/** @var Inventory */
|
||||
protected $inventory;
|
||||
/** @var int */
|
||||
protected $slot;
|
||||
/** @var Item */
|
||||
protected $sourceItem;
|
||||
/** @var Item */
|
||||
protected $targetItem;
|
||||
/** @var float */
|
||||
protected $creationTime;
|
||||
|
||||
/**
|
||||
* @param Inventory $inventory
|
||||
* @param int $slot
|
||||
* @param Item $sourceItem
|
||||
* @param Item $targetItem
|
||||
*/
|
||||
public function __construct(Inventory $inventory, int $slot, Item $sourceItem, Item $targetItem){
|
||||
$this->inventory = $inventory;
|
||||
$this->slot = $slot;
|
||||
$this->sourceItem = clone $sourceItem;
|
||||
$this->targetItem = clone $targetItem;
|
||||
$this->creationTime = microtime(true);
|
||||
}
|
||||
|
||||
public function getCreationTime() : float{
|
||||
return $this->creationTime;
|
||||
}
|
||||
|
||||
public function getInventory() : Inventory{
|
||||
return $this->inventory;
|
||||
}
|
||||
|
||||
public function getSlot() : int{
|
||||
return $this->slot;
|
||||
}
|
||||
|
||||
public function getSourceItem() : Item{
|
||||
return clone $this->sourceItem;
|
||||
}
|
||||
|
||||
public function getTargetItem() : Item{
|
||||
return clone $this->targetItem;
|
||||
}
|
||||
}
|
@ -23,6 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
class BigShapedRecipe extends ShapedRecipe{
|
||||
class BigCraftingGrid extends CraftingGrid{
|
||||
|
||||
public function getDefaultSize() : int{
|
||||
return 9;
|
||||
}
|
||||
}
|
@ -26,22 +26,43 @@ namespace pocketmine\inventory;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\network\mcpe\protocol\BlockEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\WindowTypes;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\tile\Chest;
|
||||
|
||||
class ChestInventory extends ContainerInventory{
|
||||
|
||||
/** @var Chest */
|
||||
protected $holder;
|
||||
|
||||
/**
|
||||
* @param Chest $tile
|
||||
*/
|
||||
public function __construct(Chest $tile){
|
||||
parent::__construct($tile, InventoryType::get(InventoryType::CHEST));
|
||||
parent::__construct($tile);
|
||||
}
|
||||
|
||||
public function getNetworkType() : int{
|
||||
return WindowTypes::CONTAINER;
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Chest";
|
||||
}
|
||||
|
||||
public function getDefaultSize() : int{
|
||||
return 27;
|
||||
}
|
||||
|
||||
/**
|
||||
* This override is here for documentation and code completion purposes only.
|
||||
* @return Chest
|
||||
*/
|
||||
public function getHolder(){
|
||||
return $this->holder;
|
||||
}
|
||||
|
||||
public function onOpen(Player $who){
|
||||
public function onOpen(Player $who) : void{
|
||||
parent::onOpen($who);
|
||||
|
||||
if(count($this->getViewers()) === 1 and ($level = $this->getHolder()->getLevel()) instanceof Level){
|
||||
@ -50,7 +71,7 @@ class ChestInventory extends ContainerInventory{
|
||||
}
|
||||
}
|
||||
|
||||
public function onClose(Player $who){
|
||||
public function onClose(Player $who) : void{
|
||||
if(count($this->getViewers()) === 1 and ($level = $this->getHolder()->getLevel()) instanceof Level){
|
||||
$this->broadcastBlockEventPacket(1, 0); //chest close
|
||||
$level->broadcastLevelSoundEvent($this->getHolder()->add(0.5, 0.5, 0.5), LevelSoundEventPacket::SOUND_CHEST_CLOSED);
|
||||
|
@ -29,11 +29,11 @@ use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
|
||||
use pocketmine\Player;
|
||||
|
||||
abstract class ContainerInventory extends BaseInventory{
|
||||
public function onOpen(Player $who){
|
||||
public function onOpen(Player $who) : void{
|
||||
parent::onOpen($who);
|
||||
$pk = new ContainerOpenPacket();
|
||||
$pk->windowid = $who->getWindowId($this);
|
||||
$pk->type = $this->getType()->getNetworkType();
|
||||
$pk->windowId = $who->getWindowId($this);
|
||||
$pk->type = $this->getNetworkType();
|
||||
$holder = $this->getHolder();
|
||||
if($holder instanceof Vector3){
|
||||
$pk->x = $holder->getX();
|
||||
@ -48,10 +48,16 @@ abstract class ContainerInventory extends BaseInventory{
|
||||
$this->sendContents($who);
|
||||
}
|
||||
|
||||
public function onClose(Player $who){
|
||||
public function onClose(Player $who) : void{
|
||||
$pk = new ContainerClosePacket();
|
||||
$pk->windowid = $who->getWindowId($this);
|
||||
$pk->windowId = $who->getWindowId($this);
|
||||
$who->dataPacket($pk);
|
||||
parent::onClose($who);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Minecraft PE inventory type used to show the inventory window to clients.
|
||||
* @return int
|
||||
*/
|
||||
abstract public function getNetworkType() : int;
|
||||
}
|
53
src/pocketmine/inventory/CraftingGrid.php
Normal file
53
src/pocketmine/inventory/CraftingGrid.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\Player;
|
||||
|
||||
class CraftingGrid extends BaseInventory{
|
||||
|
||||
public function __construct(Player $holder){
|
||||
parent::__construct($holder);
|
||||
}
|
||||
|
||||
public function getDefaultSize() : int{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public function setSize(int $size){
|
||||
throw new \BadMethodCallException("Cannot change the size of a crafting grid");
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Crafting";
|
||||
}
|
||||
|
||||
public function sendSlot(int $index, $target) : void{
|
||||
//we can't send a slot of a client-sided inventory window
|
||||
}
|
||||
|
||||
public function sendContents($target) : void{
|
||||
//we can't send the contents of a client-sided inventory window
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
/**
|
||||
* Manages crafting operations
|
||||
* This class includes future methods for shaped crafting
|
||||
*
|
||||
* TODO: add small matrix inventory
|
||||
*/
|
||||
class CraftingInventory extends BaseInventory{
|
||||
|
||||
/** @var Inventory */
|
||||
private $resultInventory;
|
||||
|
||||
/**
|
||||
* @param InventoryHolder $holder
|
||||
* @param Inventory $resultInventory
|
||||
* @param InventoryType $inventoryType
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(InventoryHolder $holder, Inventory $resultInventory, InventoryType $inventoryType){
|
||||
if($inventoryType->getDefaultTitle() !== "Crafting"){
|
||||
throw new \InvalidStateException("Invalid Inventory type, expected CRAFTING or WORKBENCH");
|
||||
}
|
||||
$this->resultInventory = $resultInventory;
|
||||
parent::__construct($holder, $inventoryType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Inventory
|
||||
*/
|
||||
public function getResultInventory(){
|
||||
return $this->resultInventory;
|
||||
}
|
||||
|
||||
public function getSize() : int{
|
||||
return $this->getResultInventory()->getSize() + parent::getSize();
|
||||
}
|
||||
}
|
@ -34,11 +34,13 @@ use pocketmine\utils\UUID;
|
||||
|
||||
class CraftingManager{
|
||||
|
||||
/** @var Recipe[] */
|
||||
/** @var CraftingRecipe[] */
|
||||
public $recipes = [];
|
||||
|
||||
/** @var Recipe[][] */
|
||||
protected $recipeLookup = [];
|
||||
/** @var ShapedRecipe[][] */
|
||||
protected $shapedRecipes = [];
|
||||
/** @var ShapelessRecipe[][] */
|
||||
protected $shapelessRecipes = [];
|
||||
|
||||
/** @var FurnaceRecipe[] */
|
||||
public $furnaceRecipes = [];
|
||||
@ -66,17 +68,14 @@ class CraftingManager{
|
||||
$this->registerRecipe($result);
|
||||
break;
|
||||
case 1:
|
||||
// TODO: handle multiple result items
|
||||
$first = $recipe["output"][0];
|
||||
$result = new ShapedRecipe(Item::jsonDeserialize($first), $recipe["height"], $recipe["width"]);
|
||||
$first = array_shift($recipe["output"]);
|
||||
|
||||
$shape = array_chunk($recipe["input"], $recipe["width"]);
|
||||
foreach($shape as $y => $row){
|
||||
foreach($row as $x => $ingredient){
|
||||
$result->addIngredient($x, $y, Item::jsonDeserialize($ingredient));
|
||||
}
|
||||
}
|
||||
$this->registerRecipe($result);
|
||||
$this->registerRecipe(new ShapedRecipe(
|
||||
Item::jsonDeserialize($first),
|
||||
$recipe["shape"],
|
||||
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]),
|
||||
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"])
|
||||
));
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
@ -95,7 +94,7 @@ class CraftingManager{
|
||||
/**
|
||||
* Rebuilds the cached CraftingDataPacket.
|
||||
*/
|
||||
public function buildCraftingDataCache(){
|
||||
public function buildCraftingDataCache() : void{
|
||||
Timings::$craftingDataCacheRebuildTimer->startTiming();
|
||||
$pk = new CraftingDataPacket();
|
||||
$pk->cleanRecipes = true;
|
||||
@ -131,6 +130,14 @@ class CraftingManager{
|
||||
return $this->craftingDataCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used to arrange Shapeless Recipe ingredient lists into a consistent order.
|
||||
*
|
||||
* @param Item $i1
|
||||
* @param Item $i2
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function sort(Item $i1, Item $i2){
|
||||
if($i1->getId() > $i2->getId()){
|
||||
return 1;
|
||||
@ -151,9 +158,9 @@ class CraftingManager{
|
||||
|
||||
/**
|
||||
* @param UUID $id
|
||||
* @return Recipe|null
|
||||
* @return CraftingRecipe|null
|
||||
*/
|
||||
public function getRecipe(UUID $id){
|
||||
public function getRecipe(UUID $id) : ?CraftingRecipe{
|
||||
$index = $id->toBinary();
|
||||
return $this->recipes[$index] ?? null;
|
||||
}
|
||||
@ -165,6 +172,20 @@ class CraftingManager{
|
||||
return $this->recipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapelessRecipe[][]
|
||||
*/
|
||||
public function getShapelessRecipes() : array{
|
||||
return $this->shapelessRecipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapedRecipe[][]
|
||||
*/
|
||||
public function getShapedRecipes() : array{
|
||||
return $this->shapedRecipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FurnaceRecipe[]
|
||||
*/
|
||||
@ -172,140 +193,117 @@ class CraftingManager{
|
||||
return $this->furnaceRecipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item $input
|
||||
*
|
||||
* @return FurnaceRecipe|null
|
||||
*/
|
||||
public function matchFurnaceRecipe(Item $input){
|
||||
if(isset($this->furnaceRecipes[$input->getId() . ":" . $input->getDamage()])){
|
||||
return $this->furnaceRecipes[$input->getId() . ":" . $input->getDamage()];
|
||||
}elseif(isset($this->furnaceRecipes[$input->getId() . ":?"])){
|
||||
return $this->furnaceRecipes[$input->getId() . ":?"];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShapedRecipe $recipe
|
||||
*/
|
||||
public function registerShapedRecipe(ShapedRecipe $recipe){
|
||||
$result = $recipe->getResult();
|
||||
$this->recipes[$recipe->getId()->toBinary()] = $recipe;
|
||||
$ingredients = $recipe->getIngredientMap();
|
||||
$hash = "";
|
||||
foreach($ingredients as $v){
|
||||
foreach($v as $item){
|
||||
if($item !== null){
|
||||
/** @var Item $item */
|
||||
$hash .= $item->getId() . ":" . ($item->hasAnyDamageValue() ? "?" : $item->getDamage()) . "x" . $item->getCount() . ",";
|
||||
}
|
||||
}
|
||||
|
||||
$hash .= ";";
|
||||
}
|
||||
|
||||
$this->recipeLookup[$result->getId() . ":" . $result->getDamage()][$hash] = $recipe;
|
||||
public function registerShapedRecipe(ShapedRecipe $recipe) : void{
|
||||
$this->shapedRecipes[json_encode($recipe->getResult())][json_encode($recipe->getIngredientMap())] = $recipe;
|
||||
$this->craftingDataCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShapelessRecipe $recipe
|
||||
*/
|
||||
public function registerShapelessRecipe(ShapelessRecipe $recipe){
|
||||
$result = $recipe->getResult();
|
||||
$this->recipes[$recipe->getId()->toBinary()] = $recipe;
|
||||
$hash = "";
|
||||
public function registerShapelessRecipe(ShapelessRecipe $recipe) : void{
|
||||
$ingredients = $recipe->getIngredientList();
|
||||
usort($ingredients, [$this, "sort"]);
|
||||
foreach($ingredients as $item){
|
||||
$hash .= $item->getId() . ":" . ($item->hasAnyDamageValue() ? "?" : $item->getDamage()) . "x" . $item->getCount() . ",";
|
||||
}
|
||||
$this->recipeLookup[$result->getId() . ":" . $result->getDamage()][$hash] = $recipe;
|
||||
$this->shapelessRecipes[json_encode($recipe->getResult())][json_encode($ingredients)] = $recipe;
|
||||
$this->craftingDataCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FurnaceRecipe $recipe
|
||||
*/
|
||||
public function registerFurnaceRecipe(FurnaceRecipe $recipe){
|
||||
public function registerFurnaceRecipe(FurnaceRecipe $recipe) : void{
|
||||
$input = $recipe->getInput();
|
||||
$this->furnaceRecipes[$input->getId() . ":" . ($input->hasAnyDamageValue() ? "?" : $input->getDamage())] = $recipe;
|
||||
$this->craftingDataCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShapelessRecipe $recipe
|
||||
* @return bool
|
||||
* Clones a map of Item objects to avoid accidental modification.
|
||||
*
|
||||
* @param Item[][] $map
|
||||
* @return Item[][]
|
||||
*/
|
||||
public function matchRecipe(ShapelessRecipe $recipe) : bool{
|
||||
if(!isset($this->recipeLookup[$idx = $recipe->getResult()->getId() . ":" . $recipe->getResult()->getDamage()])){
|
||||
return false;
|
||||
}
|
||||
|
||||
$hash = "";
|
||||
$ingredients = $recipe->getIngredientList();
|
||||
usort($ingredients, [$this, "sort"]);
|
||||
foreach($ingredients as $item){
|
||||
$hash .= $item->getId() . ":" . ($item->hasAnyDamageValue() ? "?" : $item->getDamage()) . "x" . $item->getCount() . ",";
|
||||
}
|
||||
|
||||
if(isset($this->recipeLookup[$idx][$hash])){
|
||||
return true;
|
||||
}
|
||||
|
||||
$hasRecipe = null;
|
||||
foreach($this->recipeLookup[$idx] as $possibleRecipe){
|
||||
if($possibleRecipe instanceof ShapelessRecipe){
|
||||
if($possibleRecipe->getIngredientCount() !== count($ingredients)){
|
||||
continue;
|
||||
}
|
||||
$checkInput = $possibleRecipe->getIngredientList();
|
||||
foreach($ingredients as $item){
|
||||
$amount = $item->getCount();
|
||||
foreach($checkInput as $k => $checkItem){
|
||||
if($checkItem->equals($item, !$checkItem->hasAnyDamageValue(), $checkItem->hasCompoundTag())){
|
||||
$remove = min($checkItem->getCount(), $amount);
|
||||
$checkItem->setCount($checkItem->getCount() - $remove);
|
||||
if($checkItem->getCount() === 0){
|
||||
unset($checkInput[$k]);
|
||||
}
|
||||
$amount -= $remove;
|
||||
if($amount === 0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(count($checkInput) === 0){
|
||||
$hasRecipe = $possibleRecipe;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($hasRecipe instanceof Recipe){
|
||||
break;
|
||||
private function cloneItemMap(array $map) : array{
|
||||
/** @var Item[] $row */
|
||||
foreach($map as $y => $row){
|
||||
foreach($row as $x => $item){
|
||||
$item = clone $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $hasRecipe !== null;
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[][] $inputMap
|
||||
* @param Item $primaryOutput
|
||||
* @param Item[][] $extraOutputMap
|
||||
*
|
||||
* @return CraftingRecipe|null
|
||||
*/
|
||||
public function matchRecipe(array $inputMap, Item $primaryOutput, array $extraOutputMap) : ?CraftingRecipe{
|
||||
//TODO: try to match special recipes before anything else (first they need to be implemented!)
|
||||
|
||||
$outputHash = json_encode($primaryOutput);
|
||||
if(isset($this->shapedRecipes[$outputHash])){
|
||||
$inputHash = json_encode($inputMap);
|
||||
$recipe = $this->shapedRecipes[$outputHash][$inputHash] ?? null;
|
||||
|
||||
if($recipe !== null and $recipe->matchItems($this->cloneItemMap($inputMap), $this->cloneItemMap($extraOutputMap))){ //matched a recipe by hash
|
||||
return $recipe;
|
||||
}
|
||||
|
||||
foreach($this->shapedRecipes[$outputHash] as $recipe){
|
||||
if($recipe->matchItems($this->cloneItemMap($inputMap), $this->cloneItemMap($extraOutputMap))){
|
||||
return $recipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($this->shapelessRecipes[$outputHash])){
|
||||
$list = array_merge(...$inputMap);
|
||||
usort($list, [$this, "sort"]);
|
||||
|
||||
$inputHash = json_encode($list);
|
||||
$recipe = $this->shapelessRecipes[$outputHash][$inputHash] ?? null;
|
||||
|
||||
if($recipe !== null and $recipe->matchItems($this->cloneItemMap($inputMap), $this->cloneItemMap($extraOutputMap))){
|
||||
return $recipe;
|
||||
}
|
||||
|
||||
foreach($this->shapelessRecipes[$outputHash] as $recipe){
|
||||
if($recipe->matchItems($this->cloneItemMap($inputMap), $this->cloneItemMap($extraOutputMap))){
|
||||
return $recipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item $input
|
||||
*
|
||||
* @return FurnaceRecipe|null
|
||||
*/
|
||||
public function matchFurnaceRecipe(Item $input) : ?FurnaceRecipe{
|
||||
return $this->furnaceRecipes[$input->getId() . ":" . $input->getDamage()] ?? $this->furnaceRecipes[$input->getId() . ":?"] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Recipe $recipe
|
||||
*/
|
||||
public function registerRecipe(Recipe $recipe){
|
||||
$recipe->setId(UUID::fromData((string) ++self::$RECIPE_COUNT, (string) $recipe->getResult()->getId(), (string) $recipe->getResult()->getDamage(), (string) $recipe->getResult()->getCount(), $recipe->getResult()->getCompoundTag()));
|
||||
|
||||
if($recipe instanceof ShapedRecipe){
|
||||
$this->registerShapedRecipe($recipe);
|
||||
}elseif($recipe instanceof ShapelessRecipe){
|
||||
$this->registerShapelessRecipe($recipe);
|
||||
}elseif($recipe instanceof FurnaceRecipe){
|
||||
$this->registerFurnaceRecipe($recipe);
|
||||
public function registerRecipe(Recipe $recipe) : void{
|
||||
if($recipe instanceof CraftingRecipe){
|
||||
$result = $recipe->getResult();
|
||||
$recipe->setId($uuid = UUID::fromData((string) ++self::$RECIPE_COUNT, (string) $result->getId(), (string) $result->getDamage(), (string) $result->getCount(), $result->getCompoundTag()));
|
||||
$this->recipes[$uuid->toBinary()] = $recipe;
|
||||
}
|
||||
|
||||
$recipe->registerToCraftingManager($this);
|
||||
}
|
||||
|
||||
}
|
||||
|
63
src/pocketmine/inventory/CraftingRecipe.php
Normal file
63
src/pocketmine/inventory/CraftingRecipe.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
interface CraftingRecipe extends Recipe{
|
||||
|
||||
/**
|
||||
* @return UUID|null
|
||||
*/
|
||||
public function getId() : ?UUID;
|
||||
|
||||
/**
|
||||
* @param UUID $id
|
||||
*/
|
||||
public function setId(UUID $id);
|
||||
|
||||
public function requiresCraftingTable() : bool;
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
*/
|
||||
public function getExtraResults() : array;
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
*/
|
||||
public function getAllResults() : array;
|
||||
|
||||
/**
|
||||
* Returns whether the specified list of crafting grid inputs and outputs matches this recipe. Outputs DO NOT
|
||||
* include the primary result item.
|
||||
*
|
||||
* @param Item[][] $input 2D array of items taken from the crafting grid
|
||||
* @param Item[][] $output 2D array of items put back into the crafting grid (secondary results)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matchItems(array $input, array $output) : bool;
|
||||
}
|
@ -39,13 +39,24 @@ class DoubleChestInventory extends ChestInventory implements InventoryHolder{
|
||||
$this->left = $left->getRealInventory();
|
||||
$this->right = $right->getRealInventory();
|
||||
$items = array_merge($this->left->getContents(), $this->right->getContents());
|
||||
BaseInventory::__construct($this, InventoryType::get(InventoryType::DOUBLE_CHEST), $items);
|
||||
BaseInventory::__construct($this, $items);
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Double Chest";
|
||||
}
|
||||
|
||||
public function getDefaultSize() : int{
|
||||
return $this->left->getDefaultSize() + $this->right->getDefaultSize();
|
||||
}
|
||||
|
||||
public function getInventory(){
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Chest
|
||||
*/
|
||||
public function getHolder(){
|
||||
return $this->left->getHolder();
|
||||
}
|
||||
@ -54,17 +65,17 @@ class DoubleChestInventory extends ChestInventory implements InventoryHolder{
|
||||
return $index < $this->left->getSize() ? $this->left->getItem($index) : $this->right->getItem($index - $this->right->getSize());
|
||||
}
|
||||
|
||||
public function setItem(int $index, Item $item) : bool{
|
||||
return $index < $this->left->getSize() ? $this->left->setItem($index, $item) : $this->right->setItem($index - $this->right->getSize(), $item);
|
||||
public function setItem(int $index, Item $item, bool $send = true) : bool{
|
||||
return $index < $this->left->getSize() ? $this->left->setItem($index, $item, $send) : $this->right->setItem($index - $this->right->getSize(), $item, $send);
|
||||
}
|
||||
|
||||
public function clear(int $index) : bool{
|
||||
return $index < $this->left->getSize() ? $this->left->clear($index) : $this->right->clear($index - $this->right->getSize());
|
||||
public function clear(int $index, bool $send = true) : bool{
|
||||
return $index < $this->left->getSize() ? $this->left->clear($index, $send) : $this->right->clear($index - $this->right->getSize(), $send);
|
||||
}
|
||||
|
||||
public function getContents() : array{
|
||||
$contents = [];
|
||||
for($i = 0; $i < $this->getSize(); ++$i){
|
||||
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
||||
$contents[$i] = $this->getItem($i);
|
||||
}
|
||||
|
||||
@ -73,29 +84,32 @@ class DoubleChestInventory extends ChestInventory implements InventoryHolder{
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
* @param bool $send
|
||||
*/
|
||||
public function setContents(array $items){
|
||||
if(count($items) > $this->size){
|
||||
$items = array_slice($items, 0, $this->size, true);
|
||||
public function setContents(array $items, bool $send = true) : void{
|
||||
$size = $this->getSize();
|
||||
if(count($items) > $size){
|
||||
$items = array_slice($items, 0, $size, true);
|
||||
}
|
||||
|
||||
$leftSize = $this->left->getSize();
|
||||
|
||||
for($i = 0; $i < $this->size; ++$i){
|
||||
for($i = 0; $i < $size; ++$i){
|
||||
if(!isset($items[$i])){
|
||||
if($i < $this->left->size){
|
||||
if(isset($this->left->slots[$i])){
|
||||
$this->clear($i);
|
||||
}
|
||||
}elseif(isset($this->right->slots[$i - $this->left->size])){
|
||||
$this->clear($i);
|
||||
if(($i < $leftSize and isset($this->left->slots[$i])) or isset($this->right->slots[$i - $leftSize])){
|
||||
$this->clear($i, false);
|
||||
}
|
||||
}elseif(!$this->setItem($i, $items[$i])){
|
||||
$this->clear($i);
|
||||
}elseif(!$this->setItem($i, $items[$i], false)){
|
||||
$this->clear($i, false);
|
||||
}
|
||||
}
|
||||
|
||||
if($send){
|
||||
$this->sendContents($this->getViewers());
|
||||
}
|
||||
}
|
||||
|
||||
public function onOpen(Player $who){
|
||||
public function onOpen(Player $who) : void{
|
||||
parent::onOpen($who);
|
||||
|
||||
if(count($this->getViewers()) === 1){
|
||||
@ -111,7 +125,7 @@ class DoubleChestInventory extends ChestInventory implements InventoryHolder{
|
||||
}
|
||||
}
|
||||
|
||||
public function onClose(Player $who){
|
||||
public function onClose(Player $who) : void{
|
||||
if(count($this->getViewers()) === 1){
|
||||
$pk = new BlockEventPacket();
|
||||
$pk->x = $this->right->getHolder()->getX();
|
||||
|
@ -24,21 +24,39 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\level\Position;
|
||||
use pocketmine\network\mcpe\protocol\types\WindowTypes;
|
||||
use pocketmine\Player;
|
||||
|
||||
class EnchantInventory extends ContainerInventory{
|
||||
|
||||
/** @var FakeBlockMenu */
|
||||
protected $holder;
|
||||
|
||||
public function __construct(Position $pos){
|
||||
parent::__construct(new FakeBlockMenu($this, $pos), InventoryType::get(InventoryType::ENCHANT_TABLE));
|
||||
parent::__construct(new FakeBlockMenu($this, $pos));
|
||||
}
|
||||
|
||||
public function getNetworkType() : int{
|
||||
return WindowTypes::ENCHANTMENT;
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Enchantment Table";
|
||||
}
|
||||
|
||||
public function getDefaultSize() : int{
|
||||
return 2; //1 input, 1 lapis
|
||||
}
|
||||
|
||||
/**
|
||||
* This override is here for documentation and code completion purposes only.
|
||||
* @return FakeBlockMenu
|
||||
*/
|
||||
public function getHolder(){
|
||||
return $this->holder;
|
||||
}
|
||||
|
||||
public function onClose(Player $who){
|
||||
public function onClose(Player $who) : void{
|
||||
parent::onClose($who);
|
||||
|
||||
for($i = 0; $i < 2; ++$i){
|
||||
|
50
src/pocketmine/inventory/EntityInventory.php
Normal file
50
src/pocketmine/inventory/EntityInventory.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\entity\EntityInventoryChangeEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Server;
|
||||
|
||||
abstract class EntityInventory extends BaseInventory{
|
||||
/** @var Entity */
|
||||
protected $holder;
|
||||
|
||||
protected function doSetItemEvents(int $index, Item $newItem) : ?Item{
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $this->getItem($index), $newItem, $index));
|
||||
if($ev->isCancelled()){
|
||||
return null;
|
||||
}
|
||||
|
||||
return $ev->getNewItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Entity|InventoryHolder
|
||||
*/
|
||||
public function getHolder(){
|
||||
return parent::getHolder();
|
||||
}
|
||||
}
|
@ -24,14 +24,31 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\network\mcpe\protocol\types\WindowTypes;
|
||||
use pocketmine\tile\Furnace;
|
||||
|
||||
class FurnaceInventory extends ContainerInventory{
|
||||
/** @var Furnace */
|
||||
protected $holder;
|
||||
|
||||
public function __construct(Furnace $tile){
|
||||
parent::__construct($tile, InventoryType::get(InventoryType::FURNACE));
|
||||
parent::__construct($tile);
|
||||
}
|
||||
|
||||
public function getNetworkType() : int{
|
||||
return WindowTypes::FURNACE;
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Furnace";
|
||||
}
|
||||
|
||||
public function getDefaultSize() : int{
|
||||
return 3; //1 input, 1 fuel, 1 output
|
||||
}
|
||||
|
||||
/**
|
||||
* This override is here for documentation and code completion purposes only.
|
||||
* @return Furnace
|
||||
*/
|
||||
public function getHolder(){
|
||||
@ -86,8 +103,8 @@ class FurnaceInventory extends ContainerInventory{
|
||||
return $this->setItem(0, $item);
|
||||
}
|
||||
|
||||
public function onSlotChange($index, $before){
|
||||
parent::onSlotChange($index, $before);
|
||||
public function onSlotChange(int $index, Item $before, bool $send) : void{
|
||||
parent::onSlotChange($index, $before, $send);
|
||||
|
||||
$this->getHolder()->scheduleUpdate();
|
||||
}
|
||||
|
@ -24,14 +24,9 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
class FurnaceRecipe implements Recipe{
|
||||
|
||||
/** @var UUID|null */
|
||||
private $id = null;
|
||||
|
||||
/** @var Item */
|
||||
private $output;
|
||||
|
||||
@ -47,24 +42,6 @@ class FurnaceRecipe implements Recipe{
|
||||
$this->ingredient = clone $ingredient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UUID|null
|
||||
*/
|
||||
public function getId(){
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UUID $id
|
||||
*/
|
||||
public function setId(UUID $id){
|
||||
if($this->id !== null){
|
||||
throw new \InvalidStateException("Id is already set");
|
||||
}
|
||||
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item $item
|
||||
*/
|
||||
@ -86,7 +63,7 @@ class FurnaceRecipe implements Recipe{
|
||||
return clone $this->output;
|
||||
}
|
||||
|
||||
public function registerToCraftingManager(){
|
||||
Server::getInstance()->getCraftingManager()->registerFurnaceRecipe($this);
|
||||
public function registerToCraftingManager(CraftingManager $manager) : void{
|
||||
$manager->registerFurnaceRecipe($this);
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ interface Inventory{
|
||||
/**
|
||||
* @param int $size
|
||||
*/
|
||||
public function setMaxStackSize(int $size);
|
||||
public function setMaxStackSize(int $size) : void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
@ -70,10 +70,11 @@ interface Inventory{
|
||||
*
|
||||
* @param int $index
|
||||
* @param Item $item
|
||||
* @param bool $send
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function setItem(int $index, Item $item) : bool;
|
||||
public function setItem(int $index, Item $item, bool $send = true) : bool;
|
||||
|
||||
/**
|
||||
* Stores the given Items in the inventory. This will try to fill
|
||||
@ -113,19 +114,20 @@ interface Inventory{
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
* @param bool $send
|
||||
*/
|
||||
public function setContents(array $items);
|
||||
public function setContents(array $items, bool $send = true) : void;
|
||||
|
||||
/**
|
||||
* @param Player|Player[] $target
|
||||
*/
|
||||
public function sendContents($target);
|
||||
public function sendContents($target) : void;
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
* @param Player|Player[] $target
|
||||
*/
|
||||
public function sendSlot($index, $target);
|
||||
public function sendSlot(int $index, $target) : void;
|
||||
|
||||
/**
|
||||
* Checks if the inventory contains any Item with the same material data.
|
||||
@ -148,14 +150,17 @@ interface Inventory{
|
||||
public function all(Item $item) : array;
|
||||
|
||||
/**
|
||||
* Will return the first slot has the same id and metadata (if not null) as the Item.
|
||||
* -1 if not found, will check amount
|
||||
* Returns the first slot number containing an item with the same ID, damage (if not any-damage), NBT (if not empty)
|
||||
* and count >= to the count of the specified item stack.
|
||||
*
|
||||
* If $exact is true, only items with equal ID, damage, NBT and count will match.
|
||||
*
|
||||
* @param Item $item
|
||||
* @param bool $exact
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function first(Item $item) : int;
|
||||
public function first(Item $item, bool $exact = false) : int;
|
||||
|
||||
/**
|
||||
* Returns the first empty slot, or -1 if not found
|
||||
@ -169,21 +174,22 @@ interface Inventory{
|
||||
*
|
||||
* @param Item $item
|
||||
*/
|
||||
public function remove(Item $item);
|
||||
public function remove(Item $item) : void;
|
||||
|
||||
/**
|
||||
* Will clear a specific slot
|
||||
*
|
||||
* @param int $index
|
||||
* @param int $index
|
||||
* @param bool $send
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear(int $index) : bool;
|
||||
public function clear(int $index, bool $send = true) : bool;
|
||||
|
||||
/**
|
||||
* Clears all the slots
|
||||
*/
|
||||
public function clearAll();
|
||||
public function clearAll() : void;
|
||||
|
||||
/**
|
||||
* Gets all the Players viewing the inventory
|
||||
@ -193,11 +199,6 @@ interface Inventory{
|
||||
*/
|
||||
public function getViewers() : array;
|
||||
|
||||
/**
|
||||
* @return InventoryType
|
||||
*/
|
||||
public function getType() : InventoryType;
|
||||
|
||||
/**
|
||||
* @return InventoryHolder
|
||||
*/
|
||||
@ -206,7 +207,7 @@ interface Inventory{
|
||||
/**
|
||||
* @param Player $who
|
||||
*/
|
||||
public function onOpen(Player $who);
|
||||
public function onOpen(Player $who) : void;
|
||||
|
||||
/**
|
||||
* Tries to open the inventory to a player
|
||||
@ -217,16 +218,17 @@ interface Inventory{
|
||||
*/
|
||||
public function open(Player $who) : bool;
|
||||
|
||||
public function close(Player $who);
|
||||
public function close(Player $who) : void;
|
||||
|
||||
/**
|
||||
* @param Player $who
|
||||
*/
|
||||
public function onClose(Player $who);
|
||||
public function onClose(Player $who) : void;
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
* @param Item $before
|
||||
* @param bool $send
|
||||
*/
|
||||
public function onSlotChange($index, $before);
|
||||
public function onSlotChange(int $index, Item $before, bool $send) : void;
|
||||
}
|
||||
|
@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\types\WindowTypes;
|
||||
|
||||
/**
|
||||
* Saves all the information regarding default inventory sizes and types
|
||||
*/
|
||||
class InventoryType{
|
||||
|
||||
//NOTE: Do not confuse these with the network IDs.
|
||||
const CHEST = 0;
|
||||
const DOUBLE_CHEST = 1;
|
||||
const PLAYER = 2;
|
||||
const FURNACE = 3;
|
||||
const CRAFTING = 4;
|
||||
const WORKBENCH = 5;
|
||||
const STONECUTTER = 6;
|
||||
const BREWING_STAND = 7;
|
||||
const ANVIL = 8;
|
||||
const ENCHANT_TABLE = 9;
|
||||
|
||||
private static $default = [];
|
||||
|
||||
private $size;
|
||||
private $title;
|
||||
private $typeId;
|
||||
|
||||
/**
|
||||
* @param $index
|
||||
*
|
||||
* @return InventoryType|null
|
||||
*/
|
||||
public static function get($index){
|
||||
return static::$default[$index] ?? null;
|
||||
}
|
||||
|
||||
public static function init(){
|
||||
if(count(static::$default) > 0){
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO: move network stuff out of here
|
||||
//TODO: move inventory data to json
|
||||
static::$default = [
|
||||
static::CHEST => new InventoryType(27, "Chest", WindowTypes::CONTAINER),
|
||||
static::DOUBLE_CHEST => new InventoryType(27 + 27, "Double Chest", WindowTypes::CONTAINER),
|
||||
static::PLAYER => new InventoryType(36 + 4, "Player", WindowTypes::INVENTORY), //36 CONTAINER, 4 ARMOR
|
||||
static::CRAFTING => new InventoryType(5, "Crafting", WindowTypes::INVENTORY), //yes, the use of INVENTORY is intended! 4 CRAFTING slots, 1 RESULT
|
||||
static::WORKBENCH => new InventoryType(10, "Crafting", WindowTypes::WORKBENCH), //9 CRAFTING slots, 1 RESULT
|
||||
static::FURNACE => new InventoryType(3, "Furnace", WindowTypes::FURNACE), //2 INPUT, 1 OUTPUT
|
||||
static::ENCHANT_TABLE => new InventoryType(2, "Enchant", WindowTypes::ENCHANTMENT), //1 INPUT/OUTPUT, 1 LAPIS
|
||||
static::BREWING_STAND => new InventoryType(4, "Brewing", WindowTypes::BREWING_STAND), //1 INPUT, 3 POTION
|
||||
static::ANVIL => new InventoryType(3, "Anvil", WindowTypes::ANVIL) //2 INPUT, 1 OUTP
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $defaultSize
|
||||
* @param string $defaultTitle
|
||||
* @param int $typeId
|
||||
*/
|
||||
private function __construct($defaultSize, $defaultTitle, $typeId = 0){
|
||||
$this->size = $defaultSize;
|
||||
$this->title = $defaultTitle;
|
||||
$this->typeId = $typeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDefaultSize() : int{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultTitle() : string{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getNetworkType() : int{
|
||||
return $this->typeId;
|
||||
}
|
||||
}
|
@ -23,41 +23,33 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
interface TransactionGroup{
|
||||
use pocketmine\Player;
|
||||
|
||||
class PlayerCursorInventory extends BaseInventory{
|
||||
/** @var Player */
|
||||
protected $holder;
|
||||
|
||||
public function __construct(Player $holder){
|
||||
parent::__construct($holder);
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Cursor";
|
||||
}
|
||||
|
||||
public function getDefaultSize() : int{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function setSize(int $size){
|
||||
throw new \BadMethodCallException("Cursor can only carry one item at a time");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
* This override is here for documentation and code completion purposes only.
|
||||
* @return Player
|
||||
*/
|
||||
public function getCreationTime() : float;
|
||||
|
||||
/**
|
||||
* @return Transaction[]
|
||||
*/
|
||||
public function getTransactions() : array;
|
||||
|
||||
/**
|
||||
* @return Inventory[]
|
||||
*/
|
||||
public function getInventories() : array;
|
||||
|
||||
/**
|
||||
* @param Transaction $transaction
|
||||
*/
|
||||
public function addTransaction(Transaction $transaction);
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function canExecute() : bool;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function execute() : bool;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExecuted() : bool;
|
||||
|
||||
public function getHolder(){
|
||||
return $this->holder;
|
||||
}
|
||||
}
|
@ -25,27 +25,38 @@ namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\entity\Human;
|
||||
use pocketmine\event\entity\EntityArmorChangeEvent;
|
||||
use pocketmine\event\entity\EntityInventoryChangeEvent;
|
||||
use pocketmine\event\player\PlayerItemHeldEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\ContainerIds;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
class PlayerInventory extends BaseInventory{
|
||||
class PlayerInventory extends EntityInventory{
|
||||
|
||||
/** @var Human */
|
||||
protected $holder;
|
||||
|
||||
/** @var int */
|
||||
protected $itemInHandIndex = 0;
|
||||
/** @var int[] */
|
||||
protected $hotbar;
|
||||
|
||||
/**
|
||||
* @param Human $player
|
||||
*/
|
||||
public function __construct(Human $player){
|
||||
$this->resetHotbar(false);
|
||||
parent::__construct($player, InventoryType::get(InventoryType::PLAYER));
|
||||
parent::__construct($player);
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Player";
|
||||
}
|
||||
|
||||
public function getDefaultSize() : int{
|
||||
return 40; //36 inventory, 4 armor
|
||||
}
|
||||
|
||||
public function getSize() : int{
|
||||
@ -61,131 +72,90 @@ class PlayerInventory extends BaseInventory{
|
||||
* Called when a client equips a hotbar slot. This method should not be used by plugins.
|
||||
* This method will call PlayerItemHeldEvent.
|
||||
*
|
||||
* @param int $hotbarSlot Number of the hotbar slot to equip.
|
||||
* @param int|null $inventorySlot Inventory slot to map to the specified hotbar slot. Supply null to make no change to the link.
|
||||
* @param int $hotbarSlot Number of the hotbar slot to equip.
|
||||
*
|
||||
* @return bool if the equipment change was successful, false if not.
|
||||
*/
|
||||
public function equipItem(int $hotbarSlot, $inventorySlot = null) : bool{
|
||||
if($inventorySlot === null){
|
||||
$inventorySlot = $this->getHotbarSlotIndex($hotbarSlot);
|
||||
}
|
||||
|
||||
if($hotbarSlot < 0 or $hotbarSlot >= $this->getHotbarSize() or $inventorySlot < -1 or $inventorySlot >= $this->getSize()){
|
||||
public function equipItem(int $hotbarSlot) : bool{
|
||||
if(!$this->isHotbarSlot($hotbarSlot)){
|
||||
$this->sendContents($this->getHolder());
|
||||
return false;
|
||||
}
|
||||
|
||||
if($inventorySlot === -1){
|
||||
$item = ItemFactory::get(Item::AIR, 0, 0);
|
||||
}else{
|
||||
$item = $this->getItem($inventorySlot);
|
||||
}
|
||||
|
||||
$this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $item, $inventorySlot, $hotbarSlot));
|
||||
$this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($hotbarSlot), $hotbarSlot));
|
||||
|
||||
if($ev->isCancelled()){
|
||||
$this->sendContents($this->getHolder());
|
||||
$this->sendHeldItem($this->getHolder());
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->setHotbarSlotIndex($hotbarSlot, $inventorySlot);
|
||||
$this->setHeldItemIndex($hotbarSlot, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the inventory slot mapped to the specified hotbar slot, or -1 if the hotbar slot does not exist.
|
||||
* @param int $index
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHotbarSlotIndex($index){
|
||||
return $this->hotbar[$index] ?? -1;
|
||||
private function isHotbarSlot(int $slot) : bool{
|
||||
return $slot >= 0 and $slot <= $this->getHotbarSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a hotbar slot to the specified slot in the main inventory. -1 links to no slot and will clear the hotbar slot.
|
||||
* This method is intended for use in network interaction with clients only.
|
||||
*
|
||||
* NOTE: Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
|
||||
* @param int $slot
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function throwIfNotHotbarSlot(int $slot){
|
||||
if(!$this->isHotbarSlot($slot)){
|
||||
throw new \InvalidArgumentException("$slot is not a valid hotbar slot index (expected 0 - " . ($this->getHotbarSize() - 1) . ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item in the specified hotbar slot.
|
||||
*
|
||||
* @param int $hotbarSlot
|
||||
* @param int $inventorySlot
|
||||
*/
|
||||
public function setHotbarSlotIndex($hotbarSlot, $inventorySlot){
|
||||
if($hotbarSlot < 0 or $hotbarSlot >= $this->getHotbarSize()){
|
||||
throw new \InvalidArgumentException("Hotbar slot index \"$hotbarSlot\" is out of range");
|
||||
}elseif($inventorySlot < -1 or $inventorySlot >= $this->getSize()){
|
||||
throw new \InvalidArgumentException("Inventory slot index \"$inventorySlot\" is out of range");
|
||||
}
|
||||
|
||||
if($inventorySlot !== -1 and ($alreadyEquippedIndex = array_search($inventorySlot, $this->hotbar)) !== false){
|
||||
/* Swap the slots
|
||||
* This assumes that the equipped slot can only be equipped in one other slot
|
||||
* it will not account for ancient bugs where the same slot ended up linked to several hotbar slots.
|
||||
* Such bugs will require a hotbar reset to default.
|
||||
*/
|
||||
$this->hotbar[$alreadyEquippedIndex] = $this->hotbar[$hotbarSlot];
|
||||
}
|
||||
|
||||
$this->hotbar[$hotbarSlot] = $inventorySlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item in the slot linked to the specified hotbar slot, or Air if the slot is not linked to any hotbar slot.
|
||||
* @param int $hotbarSlotIndex
|
||||
*
|
||||
* @return Item
|
||||
*
|
||||
* @throws \InvalidArgumentException if the hotbar slot index is out of range
|
||||
*/
|
||||
public function getHotbarSlotItem(int $hotbarSlotIndex) : Item{
|
||||
$inventorySlot = $this->getHotbarSlotIndex($hotbarSlotIndex);
|
||||
if($inventorySlot !== -1){
|
||||
return $this->getItem($inventorySlot);
|
||||
}else{
|
||||
return ItemFactory::get(Item::AIR, 0, 0);
|
||||
}
|
||||
public function getHotbarSlotItem(int $hotbarSlot) : Item{
|
||||
$this->throwIfNotHotbarSlot($hotbarSlot);
|
||||
return $this->getItem($hotbarSlot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets hotbar links to their original defaults.
|
||||
* @param bool $send Whether to send changes to the holder.
|
||||
* @deprecated
|
||||
* @return int
|
||||
*/
|
||||
public function resetHotbar(bool $send = true){
|
||||
$this->hotbar = range(0, $this->getHotbarSize() - 1, 1);
|
||||
if($send){
|
||||
$this->sendContents($this->getHolder());
|
||||
}
|
||||
public function getHeldItemSlot() : int{
|
||||
return $this->getHeldItemIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hotbar slot number the holder is currently holding.
|
||||
* @return int
|
||||
*/
|
||||
public function getHeldItemIndex(){
|
||||
public function getHeldItemIndex() : int{
|
||||
return $this->itemInHandIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets which hotbar slot the player is currently loading.
|
||||
*
|
||||
* @param int $index 0-8 index of the hotbar slot to hold
|
||||
* @param bool $send Whether to send updates back to the inventory holder. This should usually be true for plugin calls.
|
||||
* @param int $hotbarSlot 0-8 index of the hotbar slot to hold
|
||||
* @param bool $send Whether to send updates back to the inventory holder. This should usually be true for plugin calls.
|
||||
* It should only be false to prevent feedback loops of equipment packets between client and server.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the hotbar slot is out of range
|
||||
*/
|
||||
public function setHeldItemIndex($index, $send = true){
|
||||
if($index >= 0 and $index < $this->getHotbarSize()){
|
||||
$this->itemInHandIndex = $index;
|
||||
public function setHeldItemIndex(int $hotbarSlot, bool $send = true){
|
||||
$this->throwIfNotHotbarSlot($hotbarSlot);
|
||||
|
||||
if($this->getHolder() instanceof Player and $send){
|
||||
$this->sendHeldItem($this->getHolder());
|
||||
}
|
||||
$this->itemInHandIndex = $hotbarSlot;
|
||||
|
||||
$this->sendHeldItem($this->getHolder()->getViewers());
|
||||
}else{
|
||||
throw new \InvalidArgumentException("Hotbar slot index \"$index\" is out of range");
|
||||
if($this->getHolder() instanceof Player and $send){
|
||||
$this->sendHeldItem($this->getHolder());
|
||||
}
|
||||
|
||||
$this->sendHeldItem($this->getHolder()->getViewers());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -193,39 +163,19 @@ class PlayerInventory extends BaseInventory{
|
||||
*
|
||||
* @return Item
|
||||
*/
|
||||
public function getItemInHand(){
|
||||
public function getItemInHand() : Item{
|
||||
return $this->getHotbarSlotItem($this->itemInHandIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the item in the currently-held slot to the specified item.
|
||||
*
|
||||
* @param Item $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function setItemInHand(Item $item){
|
||||
return $this->setItem($this->getHeldItemSlot(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hotbar slot number currently held.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHeldItemSlot(){
|
||||
return $this->getHotbarSlotIndex($this->itemInHandIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hotbar slot link of the currently-held hotbar slot.
|
||||
* @deprecated Do not change hotbar slot mapping with plugins, this will cause myriad client-sided bugs, especially with desktop GUI clients.
|
||||
*
|
||||
* @param int $slot
|
||||
*/
|
||||
public function setHeldItemSlot($slot){
|
||||
if($slot >= -1 and $slot < $this->getSize()){
|
||||
$this->setHotbarSlotIndex($this->getHeldItemIndex(), $slot);
|
||||
}
|
||||
public function setItemInHand(Item $item) : bool{
|
||||
return $this->setItem($this->getHeldItemIndex(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,24 +188,23 @@ class PlayerInventory extends BaseInventory{
|
||||
$pk = new MobEquipmentPacket();
|
||||
$pk->entityRuntimeId = $this->getHolder()->getId();
|
||||
$pk->item = $item;
|
||||
$pk->inventorySlot = $this->getHeldItemSlot();
|
||||
$pk->hotbarSlot = $this->getHeldItemIndex();
|
||||
$pk->inventorySlot = $pk->hotbarSlot = $this->getHeldItemIndex();
|
||||
$pk->windowId = ContainerIds::INVENTORY;
|
||||
|
||||
if(!is_array($target)){
|
||||
$target->dataPacket($pk);
|
||||
if($this->getHeldItemSlot() !== -1 and $target === $this->getHolder()){
|
||||
$this->sendSlot($this->getHeldItemSlot(), $target);
|
||||
if($target === $this->getHolder()){
|
||||
$this->sendSlot($this->getHeldItemIndex(), $target);
|
||||
}
|
||||
}else{
|
||||
$this->getHolder()->getLevel()->getServer()->broadcastPacket($target, $pk);
|
||||
if($this->getHeldItemSlot() !== -1 and in_array($this->getHolder(), $target, true)){
|
||||
$this->sendSlot($this->getHeldItemSlot(), $this->getHolder());
|
||||
if(in_array($this->getHolder(), $target, true)){
|
||||
$this->sendSlot($this->getHeldItemIndex(), $this->getHolder());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onSlotChange($index, $before){
|
||||
public function onSlotChange(int $index, Item $before, bool $send) : void{
|
||||
$holder = $this->getHolder();
|
||||
if($holder instanceof Player and !$holder->spawned){
|
||||
return;
|
||||
@ -266,7 +215,7 @@ class PlayerInventory extends BaseInventory{
|
||||
$this->sendArmorSlot($index, $this->getHolder()->getViewers());
|
||||
}else{
|
||||
//Do not send armor by accident here.
|
||||
parent::onSlotChange($index, $before);
|
||||
parent::onSlotChange($index, $before, $send);
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,124 +223,77 @@ class PlayerInventory extends BaseInventory{
|
||||
* Returns the number of slots in the hotbar.
|
||||
* @return int
|
||||
*/
|
||||
public function getHotbarSize(){
|
||||
public function getHotbarSize() : int{
|
||||
return 9;
|
||||
}
|
||||
|
||||
public function getArmorItem($index){
|
||||
public function getArmorItem(int $index) : Item{
|
||||
return $this->getItem($this->getSize() + $index);
|
||||
}
|
||||
|
||||
public function setArmorItem($index, Item $item){
|
||||
public function setArmorItem(int $index, Item $item) : bool{
|
||||
return $this->setItem($this->getSize() + $index, $item);
|
||||
}
|
||||
|
||||
public function getHelmet(){
|
||||
public function getHelmet() : Item{
|
||||
return $this->getItem($this->getSize());
|
||||
}
|
||||
|
||||
public function getChestplate(){
|
||||
public function getChestplate() : Item{
|
||||
return $this->getItem($this->getSize() + 1);
|
||||
}
|
||||
|
||||
public function getLeggings(){
|
||||
public function getLeggings() : Item{
|
||||
return $this->getItem($this->getSize() + 2);
|
||||
}
|
||||
|
||||
public function getBoots(){
|
||||
public function getBoots() : Item{
|
||||
return $this->getItem($this->getSize() + 3);
|
||||
}
|
||||
|
||||
public function setHelmet(Item $helmet){
|
||||
public function setHelmet(Item $helmet) : bool{
|
||||
return $this->setItem($this->getSize(), $helmet);
|
||||
}
|
||||
|
||||
public function setChestplate(Item $chestplate){
|
||||
public function setChestplate(Item $chestplate) : bool{
|
||||
return $this->setItem($this->getSize() + 1, $chestplate);
|
||||
}
|
||||
|
||||
public function setLeggings(Item $leggings){
|
||||
public function setLeggings(Item $leggings) : bool{
|
||||
return $this->setItem($this->getSize() + 2, $leggings);
|
||||
}
|
||||
|
||||
public function setBoots(Item $boots){
|
||||
public function setBoots(Item $boots) : bool{
|
||||
return $this->setItem($this->getSize() + 3, $boots);
|
||||
}
|
||||
|
||||
public function setItem(int $index, Item $item) : bool{
|
||||
if($index < 0 or $index >= $this->size){
|
||||
return false;
|
||||
}elseif($item->getId() === 0 or $item->getCount() <= 0){
|
||||
return $this->clear($index);
|
||||
}
|
||||
|
||||
if($index >= $this->getSize()){ //Armor change
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $this->getItem($index), $item, $index));
|
||||
if($ev->isCancelled() and $this->getHolder() instanceof Human){
|
||||
$this->sendArmorSlot($index, $this->getViewers());
|
||||
return false;
|
||||
}
|
||||
$item = $ev->getNewItem();
|
||||
}else{
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $this->getItem($index), $item, $index));
|
||||
protected function doSetItemEvents(int $index, Item $newItem) : ?Item{
|
||||
if($index >= $this->getSize()){
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $this->getItem($index), $newItem, $index));
|
||||
if($ev->isCancelled()){
|
||||
$this->sendSlot($index, $this->getViewers());
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
$item = $ev->getNewItem();
|
||||
|
||||
return $ev->getNewItem();
|
||||
}
|
||||
|
||||
|
||||
$old = $this->getItem($index);
|
||||
$this->slots[$index] = clone $item;
|
||||
$this->onSlotChange($index, $old);
|
||||
|
||||
return true;
|
||||
return parent::doSetItemEvents($index, $newItem);
|
||||
}
|
||||
|
||||
public function clear(int $index) : bool{
|
||||
if(isset($this->slots[$index])){
|
||||
$item = ItemFactory::get(Item::AIR, 0, 0);
|
||||
$old = $this->slots[$index];
|
||||
if($index >= $this->getSize() and $index < $this->size){ //Armor change
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $old, $item, $index));
|
||||
if($ev->isCancelled()){
|
||||
if($index >= $this->size){
|
||||
$this->sendArmorSlot($index, $this->getViewers());
|
||||
}else{
|
||||
$this->sendSlot($index, $this->getViewers());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
$item = $ev->getNewItem();
|
||||
}else{
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $old, $item, $index));
|
||||
if($ev->isCancelled()){
|
||||
if($index >= $this->size){
|
||||
$this->sendArmorSlot($index, $this->getViewers());
|
||||
}else{
|
||||
$this->sendSlot($index, $this->getViewers());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
$item = $ev->getNewItem();
|
||||
}
|
||||
if($item->getId() !== Item::AIR){
|
||||
$this->slots[$index] = clone $item;
|
||||
}else{
|
||||
unset($this->slots[$index]);
|
||||
}
|
||||
public function clearAll() : void{
|
||||
parent::clearAll();
|
||||
|
||||
$this->onSlotChange($index, $old);
|
||||
for($i = $this->getSize(), $m = $i + 4; $i < $m; ++$i){
|
||||
$this->clear($i, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
$this->sendArmorContents($this->getViewers());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
*/
|
||||
public function getArmorContents(){
|
||||
public function getArmorContents() : array{
|
||||
$armor = [];
|
||||
|
||||
for($i = 0; $i < 4; ++$i){
|
||||
@ -401,13 +303,6 @@ class PlayerInventory extends BaseInventory{
|
||||
return $armor;
|
||||
}
|
||||
|
||||
public function clearAll(){
|
||||
$limit = $this->getSize() + 4;
|
||||
for($index = 0; $index < $limit; ++$index){
|
||||
$this->clear($index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Player|Player[] $target
|
||||
*/
|
||||
@ -425,10 +320,9 @@ class PlayerInventory extends BaseInventory{
|
||||
|
||||
foreach($target as $player){
|
||||
if($player === $this->getHolder()){
|
||||
$pk2 = new ContainerSetContentPacket();
|
||||
$pk2->windowid = ContainerIds::ARMOR;
|
||||
$pk2->slots = $armor;
|
||||
$pk2->targetEid = $player->getId();
|
||||
$pk2 = new InventoryContentPacket();
|
||||
$pk2->windowId = ContainerIds::ARMOR;
|
||||
$pk2->items = $armor;
|
||||
$player->dataPacket($pk2);
|
||||
}else{
|
||||
$player->dataPacket($pk);
|
||||
@ -445,12 +339,10 @@ class PlayerInventory extends BaseInventory{
|
||||
$items[$i] = ItemFactory::get(Item::AIR, 0, 0);
|
||||
}
|
||||
|
||||
if($items[$i]->getId() === Item::AIR){
|
||||
$this->clear($this->getSize() + $i);
|
||||
}else{
|
||||
$this->setItem($this->getSize() + $i, $items[$i]);
|
||||
}
|
||||
$this->setItem($this->getSize() + $i, $items[$i], false);
|
||||
}
|
||||
|
||||
$this->sendArmorContents($this->getViewers());
|
||||
}
|
||||
|
||||
|
||||
@ -458,7 +350,7 @@ class PlayerInventory extends BaseInventory{
|
||||
* @param int $index
|
||||
* @param Player|Player[] $target
|
||||
*/
|
||||
public function sendArmorSlot($index, $target){
|
||||
public function sendArmorSlot(int $index, $target){
|
||||
if($target instanceof Player){
|
||||
$target = [$target];
|
||||
}
|
||||
@ -473,9 +365,10 @@ class PlayerInventory extends BaseInventory{
|
||||
foreach($target as $player){
|
||||
if($player === $this->getHolder()){
|
||||
/** @var Player $player */
|
||||
$pk2 = new ContainerSetSlotPacket();
|
||||
$pk2->windowid = ContainerIds::ARMOR;
|
||||
$pk2->slot = $index - $this->getSize();
|
||||
|
||||
$pk2 = new InventorySlotPacket();
|
||||
$pk2->windowId = ContainerIds::ARMOR;
|
||||
$pk2->inventorySlot = $index - $this->getSize();
|
||||
$pk2->item = $this->getItem($index);
|
||||
$player->dataPacket($pk2);
|
||||
}else{
|
||||
@ -484,90 +377,25 @@ class PlayerInventory extends BaseInventory{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Player|Player[] $target
|
||||
*/
|
||||
public function sendContents($target){
|
||||
if($target instanceof Player){
|
||||
$target = [$target];
|
||||
}
|
||||
|
||||
$pk = new ContainerSetContentPacket();
|
||||
$pk->slots = [];
|
||||
|
||||
for($i = 0; $i < $this->getSize(); ++$i){ //Do not send armor by error here
|
||||
$pk->slots[$i] = $this->getItem($i);
|
||||
}
|
||||
|
||||
//Because PE is stupid and shows 9 less slots than you send it, give it 9 dummy slots so it shows all the REAL slots.
|
||||
for($i = $this->getSize(); $i < $this->getSize() + $this->getHotbarSize(); ++$i){
|
||||
$pk->slots[$i] = ItemFactory::get(Item::AIR, 0, 0);
|
||||
}
|
||||
|
||||
foreach($target as $player){
|
||||
$pk->hotbar = [];
|
||||
if($player === $this->getHolder()){
|
||||
for($i = 0; $i < $this->getHotbarSize(); ++$i){
|
||||
$index = $this->getHotbarSlotIndex($i);
|
||||
$pk->hotbar[] = $index <= -1 ? -1 : $index + $this->getHotbarSize();
|
||||
}
|
||||
}
|
||||
if(($id = $player->getWindowId($this)) === -1 or $player->spawned !== true){
|
||||
$this->close($player);
|
||||
continue;
|
||||
}
|
||||
$pk->windowid = $id;
|
||||
$pk->targetEid = $player->getId(); //TODO: check if this is correct
|
||||
$player->dataPacket(clone $pk);
|
||||
}
|
||||
}
|
||||
|
||||
public function sendCreativeContents(){
|
||||
$pk = new ContainerSetContentPacket();
|
||||
$pk->windowid = ContainerIds::CREATIVE;
|
||||
if($this->getHolder()->getGamemode() === Player::CREATIVE){
|
||||
$pk = new InventoryContentPacket();
|
||||
$pk->windowId = ContainerIds::CREATIVE;
|
||||
|
||||
if(!$this->getHolder()->isSpectator()){ //fill it for all gamemodes except spectator
|
||||
foreach(Item::getCreativeItems() as $i => $item){
|
||||
$pk->slots[$i] = clone $item;
|
||||
$pk->items[$i] = clone $item;
|
||||
}
|
||||
}
|
||||
$pk->targetEid = $this->getHolder()->getId();
|
||||
|
||||
$this->getHolder()->dataPacket($pk);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
* @param Player|Player[] $target
|
||||
*/
|
||||
public function sendSlot($index, $target){
|
||||
if($target instanceof Player){
|
||||
$target = [$target];
|
||||
}
|
||||
|
||||
$pk = new ContainerSetSlotPacket();
|
||||
$pk->slot = $index;
|
||||
$pk->item = clone $this->getItem($index);
|
||||
|
||||
foreach($target as $player){
|
||||
if($player === $this->getHolder()){
|
||||
/** @var Player $player */
|
||||
$pk->windowid = 0;
|
||||
$player->dataPacket(clone $pk);
|
||||
}else{
|
||||
if(($id = $player->getWindowId($this)) === -1){
|
||||
$this->close($player);
|
||||
continue;
|
||||
}
|
||||
$pk->windowid = $id;
|
||||
$player->dataPacket(clone $pk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This override is here for documentation and code completion purposes only.
|
||||
* @return Human|Player
|
||||
*/
|
||||
public function getHolder(){
|
||||
return parent::getHolder();
|
||||
return $this->holder;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
interface Recipe{
|
||||
|
||||
@ -33,15 +32,5 @@ interface Recipe{
|
||||
*/
|
||||
public function getResult() : Item;
|
||||
|
||||
public function registerToCraftingManager();
|
||||
|
||||
/**
|
||||
* @return UUID|null
|
||||
*/
|
||||
public function getId();
|
||||
|
||||
/**
|
||||
* @param UUID $id
|
||||
*/
|
||||
public function setId(UUID $id);
|
||||
public function registerToCraftingManager(CraftingManager $manager) : void;
|
||||
}
|
@ -25,62 +25,111 @@ namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\math\Vector2;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
class ShapedRecipe implements Recipe{
|
||||
class ShapedRecipe implements CraftingRecipe{
|
||||
/** @var Item */
|
||||
private $output;
|
||||
private $primaryResult;
|
||||
/** @var Item[] */
|
||||
private $extraResults = [];
|
||||
|
||||
/** @var UUID|null */
|
||||
private $id = null;
|
||||
|
||||
/** @var string[] */
|
||||
private $shape = [];
|
||||
|
||||
/** @var Item[][] */
|
||||
private $ingredients = [];
|
||||
/** @var Vector2[][] */
|
||||
private $shapeItems = [];
|
||||
/** @var Item[] char => Item map */
|
||||
private $ingredientList = [];
|
||||
|
||||
/**
|
||||
* @param Item $result
|
||||
* @param int $height
|
||||
* @param int $width
|
||||
* Constructs a ShapedRecipe instance.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @param Item $primaryResult
|
||||
* @param string[] $shape<br>
|
||||
* Array of 1, 2, or 3 strings representing the rows of the recipe.
|
||||
* This accepts an array of 1, 2 or 3 strings. Each string should be of the same length and must be at most 3
|
||||
* characters long. Each character represents a unique type of ingredient. Spaces are interpreted as air.
|
||||
* @param Item[] $ingredients<br>
|
||||
* Char => Item map of items to be set into the shape.
|
||||
* This accepts an array of Items, indexed by character. Every unique character (except space) in the shape
|
||||
* array MUST have a corresponding item in this list. Space character is automatically treated as air.
|
||||
* @param Item[] $extraResults<br>
|
||||
* List of additional result items to leave in the crafting grid afterwards. Used for things like cake recipe
|
||||
* empty buckets.
|
||||
*
|
||||
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
|
||||
*/
|
||||
public function __construct(Item $result, int $height, int $width){
|
||||
for($h = 0; $h < $height; $h++){
|
||||
if($width === 0 or $width > 3){
|
||||
throw new \InvalidStateException("Crafting rows should be 1, 2, 3 wide, not $width");
|
||||
}
|
||||
$this->ingredients[] = array_fill(0, $width, null);
|
||||
public function __construct(Item $primaryResult, array $shape, array $ingredients, array $extraResults = []){
|
||||
$rowCount = count($shape);
|
||||
if($rowCount > 3 or $rowCount <= 0){
|
||||
throw new \InvalidArgumentException("Shaped recipes may only have 1, 2 or 3 rows, not $rowCount");
|
||||
}
|
||||
|
||||
$this->output = clone $result;
|
||||
$shape = array_values($shape);
|
||||
|
||||
$columnCount = strlen($shape[0]);
|
||||
if($columnCount > 3 or $rowCount <= 0){
|
||||
throw new \InvalidArgumentException("Shaped recipes may only have 1, 2 or 3 columns, not $columnCount");
|
||||
}
|
||||
|
||||
foreach($shape as $y => $row){
|
||||
if(strlen($row) !== $columnCount){
|
||||
throw new \InvalidArgumentException("Shaped recipe rows must all have the same length (expected $columnCount, got " . strlen($row) . ")");
|
||||
}
|
||||
|
||||
for($x = 0; $x < $columnCount; ++$x){
|
||||
if($row{$x} !== ' ' and !isset($ingredients[$row{$x}])){
|
||||
throw new \InvalidArgumentException("No item specified for symbol '" . $row{$x} . "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->primaryResult = clone $primaryResult;
|
||||
foreach($extraResults as $item){
|
||||
$this->extraResults[] = clone $item;
|
||||
}
|
||||
|
||||
$this->shape = $shape;
|
||||
|
||||
foreach($ingredients as $char => $i){
|
||||
$this->setIngredient($char, $i);
|
||||
}
|
||||
}
|
||||
|
||||
public function getWidth() : int{
|
||||
return count($this->ingredients[0]);
|
||||
return strlen($this->shape[0]);
|
||||
}
|
||||
|
||||
public function getHeight() : int{
|
||||
return count($this->ingredients);
|
||||
return count($this->shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item
|
||||
*/
|
||||
public function getResult() : Item{
|
||||
return $this->output;
|
||||
return $this->primaryResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
*/
|
||||
public function getExtraResults() : array{
|
||||
return $this->extraResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
*/
|
||||
public function getAllResults() : array{
|
||||
$results = $this->extraResults;
|
||||
array_unshift($results, $this->primaryResult);
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UUID|null
|
||||
*/
|
||||
public function getId(){
|
||||
public function getId() : ?UUID{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
@ -92,58 +141,32 @@ class ShapedRecipe implements Recipe{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param Item $item
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addIngredient(int $x, int $y, Item $item){
|
||||
$this->ingredients[$y][$x] = clone $item;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param Item $item
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setIngredient(string $key, Item $item){
|
||||
if(!array_key_exists($key, $this->shape)){
|
||||
throw new \Exception("Symbol does not appear in the shape: " . $key);
|
||||
if(strpos(implode($this->shape), $key) === false){
|
||||
throw new \InvalidArgumentException("Symbol '$key' does not appear in the recipe shape");
|
||||
}
|
||||
|
||||
$this->fixRecipe($key, $item);
|
||||
$this->ingredientList[$key] = clone $item;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param Item $item
|
||||
*/
|
||||
protected function fixRecipe(string $key, Item $item){
|
||||
foreach($this->shapeItems[$key] as $entry){
|
||||
$this->ingredients[$entry->y][$entry->x] = clone $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item[][]
|
||||
*/
|
||||
public function getIngredientMap() : array{
|
||||
$ingredients = [];
|
||||
foreach($this->ingredients as $y => $row){
|
||||
$ingredients[$y] = [];
|
||||
foreach($row as $x => $ingredient){
|
||||
if($ingredient !== null){
|
||||
$ingredients[$y][$x] = clone $ingredient;
|
||||
}else{
|
||||
$ingredients[$y][$x] = ItemFactory::get(Item::AIR);
|
||||
}
|
||||
|
||||
for($y = 0, $y2 = $this->getHeight(); $y < $y2; ++$y){
|
||||
for($x = 0, $x2 = $this->getWidth(); $x < $x2; ++$x){
|
||||
$ingredients[$y][$x] = $this->getIngredient($x, $y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,17 +180,82 @@ class ShapedRecipe implements Recipe{
|
||||
* @return Item
|
||||
*/
|
||||
public function getIngredient(int $x, int $y) : Item{
|
||||
return $this->ingredients[$y][$x] ?? ItemFactory::get(Item::AIR);
|
||||
$exists = $this->ingredientList[$this->shape[$y]{$x}] ?? null;
|
||||
return $exists !== null ? clone $exists : ItemFactory::get(Item::AIR, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of strings containing characters representing the recipe's shape.
|
||||
* @return string[]
|
||||
*/
|
||||
public function getShape() : array{
|
||||
return $this->shape;
|
||||
}
|
||||
|
||||
public function registerToCraftingManager(){
|
||||
Server::getInstance()->getCraftingManager()->registerShapedRecipe($this);
|
||||
public function registerToCraftingManager(CraftingManager $manager) : void{
|
||||
$manager->registerShapedRecipe($this);
|
||||
}
|
||||
|
||||
public function requiresCraftingTable() : bool{
|
||||
return $this->getHeight() > 2 or $this->getWidth() > 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[][] $input
|
||||
* @param Item[][] $output
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matchItems(array $input, array $output) : bool{
|
||||
$map = $this->getIngredientMap();
|
||||
|
||||
//match the given items to the requested items
|
||||
for($y = 0, $y2 = $this->getHeight(); $y < $y2; ++$y){
|
||||
for($x = 0, $x2 = $this->getWidth(); $x < $x2; ++$x){
|
||||
$given = $input[$y][$x] ?? null;
|
||||
$required = $map[$y][$x];
|
||||
|
||||
if($given === null or !$required->equals($given, !$required->hasAnyDamageValue(), $required->hasCompoundTag()) or $required->getCount() !== $given->getCount()){
|
||||
return false;
|
||||
}
|
||||
|
||||
unset($input[$y][$x]);
|
||||
}
|
||||
}
|
||||
|
||||
//we shouldn't need to check if there's anything left in the map, the last block should take care of that
|
||||
//however, we DO need to check if there are too many items in the grid outside of the recipe
|
||||
|
||||
/**
|
||||
* @var Item[] $row
|
||||
*/
|
||||
foreach($input as $y => $row){
|
||||
foreach($row as $x => $needItem){
|
||||
if(!$needItem->isNull()){
|
||||
return false; //too many input ingredients
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//and then, finally, check that the output items are good:
|
||||
|
||||
/** @var Item[] $haveItems */
|
||||
$haveItems = array_merge(...$output);
|
||||
$needItems = $this->getExtraResults();
|
||||
foreach($haveItems as $j => $haveItem){
|
||||
if($haveItem->isNull()){
|
||||
unset($haveItems[$j]);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach($needItems as $i => $needItem){
|
||||
if($needItem->equals($haveItem, !$needItem->hasAnyDamageValue(), $needItem->hasCompoundTag()) and $needItem->getCount() === $haveItem->getCount()){
|
||||
unset($haveItems[$j], $needItems[$i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count($haveItems) === 0 and count($needItems) === 0;
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,9 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
class ShapelessRecipe implements Recipe{
|
||||
class ShapelessRecipe implements CraftingRecipe{
|
||||
/** @var Item */
|
||||
private $output;
|
||||
|
||||
@ -44,7 +43,7 @@ class ShapelessRecipe implements Recipe{
|
||||
/**
|
||||
* @return UUID|null
|
||||
*/
|
||||
public function getId(){
|
||||
public function getId() : ?UUID{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
@ -63,6 +62,14 @@ class ShapelessRecipe implements Recipe{
|
||||
return clone $this->output;
|
||||
}
|
||||
|
||||
public function getExtraResults() : array{
|
||||
return []; //TODO
|
||||
}
|
||||
|
||||
public function getAllResults() : array{
|
||||
return [$this->getResult()]; //TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item $item
|
||||
*
|
||||
@ -129,7 +136,62 @@ class ShapelessRecipe implements Recipe{
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function registerToCraftingManager(){
|
||||
Server::getInstance()->getCraftingManager()->registerShapelessRecipe($this);
|
||||
public function registerToCraftingManager(CraftingManager $manager) : void{
|
||||
$manager->registerShapelessRecipe($this);
|
||||
}
|
||||
|
||||
public function requiresCraftingTable() : bool{
|
||||
return count($this->ingredients) > 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[][] $input
|
||||
* @param Item[][] $output
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matchItems(array $input, array $output) : bool{
|
||||
/** @var Item[] $haveInputs */
|
||||
$haveInputs = array_merge(...$input); //we don't care how the items were arranged
|
||||
$needInputs = $this->getIngredientList();
|
||||
|
||||
if(!$this->matchItemList($haveInputs, $needInputs)){
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Item[] $haveOutputs */
|
||||
$haveOutputs = array_merge(...$output);
|
||||
$needOutputs = $this->getExtraResults();
|
||||
|
||||
if(!$this->matchItemList($haveOutputs, $needOutputs)){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $haveItems
|
||||
* @param Item[] $needItems
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function matchItemList(array $haveItems, array $needItems) : bool{
|
||||
foreach($haveItems as $j => $haveItem){
|
||||
if($haveItem->isNull()){
|
||||
unset($haveItems[$j]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
foreach($needItems as $i => $needItem){
|
||||
if($needItem->equals($haveItem, !$needItem->hasAnyDamageValue(), $needItem->hasCompoundTag()) and $needItem->getCount() === $haveItem->getCount()){
|
||||
unset($haveItems[$j], $needItems[$i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count($haveItems) === 0 and count($needItems) === 0;
|
||||
}
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\event\inventory\InventoryTransactionEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
/**
|
||||
* This TransactionGroup only allows doing Transaction between one / two inventories
|
||||
*/
|
||||
class SimpleTransactionGroup implements TransactionGroup{
|
||||
/** @var float */
|
||||
private $creationTime;
|
||||
protected $hasExecuted = false;
|
||||
/** @var Player */
|
||||
protected $source = null;
|
||||
|
||||
/** @var Inventory[] */
|
||||
protected $inventories = [];
|
||||
|
||||
/** @var Transaction[] */
|
||||
protected $transactions = [];
|
||||
|
||||
/**
|
||||
* @param Player $source
|
||||
*/
|
||||
public function __construct(Player $source = null){
|
||||
$this->creationTime = microtime(true);
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Player
|
||||
*/
|
||||
public function getSource() : Player{
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
public function getCreationTime() : float{
|
||||
return $this->creationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Inventory[]
|
||||
*/
|
||||
public function getInventories() : array{
|
||||
return $this->inventories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Transaction[]
|
||||
*/
|
||||
public function getTransactions() : array{
|
||||
return $this->transactions;
|
||||
}
|
||||
|
||||
public function addTransaction(Transaction $transaction){
|
||||
if(isset($this->transactions[spl_object_hash($transaction)])){
|
||||
return;
|
||||
}
|
||||
foreach($this->transactions as $hash => $tx){
|
||||
if($tx->getInventory() === $transaction->getInventory() and $tx->getSlot() === $transaction->getSlot()){
|
||||
if($transaction->getCreationTime() >= $tx->getCreationTime()){
|
||||
unset($this->transactions[$hash]);
|
||||
}else{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->transactions[spl_object_hash($transaction)] = $transaction;
|
||||
$this->inventories[spl_object_hash($transaction->getInventory())] = $transaction->getInventory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $needItems
|
||||
* @param Item[] $haveItems
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function matchItems(array &$needItems, array &$haveItems) : bool{
|
||||
foreach($this->transactions as $key => $ts){
|
||||
if($ts->getTargetItem()->getId() !== Item::AIR){
|
||||
$needItems[] = $ts->getTargetItem();
|
||||
}
|
||||
$checkSourceItem = $ts->getInventory()->getItem($ts->getSlot());
|
||||
$sourceItem = $ts->getSourceItem();
|
||||
if(!$checkSourceItem->equals($sourceItem) or $sourceItem->getCount() !== $checkSourceItem->getCount()){
|
||||
return false;
|
||||
}
|
||||
if($sourceItem->getId() !== Item::AIR){
|
||||
$haveItems[] = $sourceItem;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($needItems as $i => $needItem){
|
||||
foreach($haveItems as $j => $haveItem){
|
||||
if($needItem->equals($haveItem)){
|
||||
$amount = min($needItem->getCount(), $haveItem->getCount());
|
||||
$needItem->setCount($needItem->getCount() - $amount);
|
||||
$haveItem->setCount($haveItem->getCount() - $amount);
|
||||
if($haveItem->getCount() === 0){
|
||||
unset($haveItems[$j]);
|
||||
}
|
||||
if($needItem->getCount() === 0){
|
||||
unset($needItems[$i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canExecute() : bool{
|
||||
$haveItems = [];
|
||||
$needItems = [];
|
||||
|
||||
return $this->matchItems($needItems, $haveItems) and count($this->transactions) > 0 and ((count($haveItems) === 0 and count($needItems) === 0) or $this->source->isCreative(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function execute() : bool{
|
||||
if($this->hasExecuted() or !$this->canExecute()){
|
||||
return false;
|
||||
}
|
||||
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new InventoryTransactionEvent($this));
|
||||
if($ev->isCancelled()){
|
||||
foreach($this->inventories as $inventory){
|
||||
if($inventory instanceof PlayerInventory){
|
||||
$inventory->sendArmorContents($this->getSource());
|
||||
}
|
||||
$inventory->sendContents($this->getSource());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($this->transactions as $transaction){
|
||||
$transaction->getInventory()->setItem($transaction->getSlot(), $transaction->getTargetItem());
|
||||
}
|
||||
|
||||
$this->hasExecuted = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function hasExecuted() : bool{
|
||||
return $this->hasExecuted;
|
||||
}
|
||||
}
|
185
src/pocketmine/inventory/transaction/CraftingTransaction.php
Normal file
185
src/pocketmine/inventory/transaction/CraftingTransaction.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory\transaction;
|
||||
|
||||
use pocketmine\event\inventory\CraftItemEvent;
|
||||
use pocketmine\inventory\BigCraftingGrid;
|
||||
use pocketmine\inventory\CraftingRecipe;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\Player;
|
||||
|
||||
class CraftingTransaction extends InventoryTransaction{
|
||||
|
||||
protected $gridSize;
|
||||
/** @var Item[][] */
|
||||
protected $inputs;
|
||||
/** @var Item[][] */
|
||||
protected $secondaryOutputs;
|
||||
/** @var Item|null */
|
||||
protected $primaryOutput;
|
||||
|
||||
/** @var CraftingRecipe|null */
|
||||
protected $recipe = null;
|
||||
|
||||
public function __construct(Player $source, $actions = []){
|
||||
$this->gridSize = ($source->getCraftingGrid() instanceof BigCraftingGrid) ? 3 : 2;
|
||||
|
||||
$air = ItemFactory::get(Item::AIR, 0, 0);
|
||||
$this->inputs = array_fill(0, $this->gridSize, array_fill(0, $this->gridSize, $air));
|
||||
$this->secondaryOutputs = array_fill(0, $this->gridSize, array_fill(0, $this->gridSize, $air));
|
||||
|
||||
parent::__construct($source, $actions);
|
||||
}
|
||||
|
||||
public function setInput(int $index, Item $item) : void{
|
||||
$y = (int) ($index / $this->gridSize);
|
||||
$x = $index % $this->gridSize;
|
||||
|
||||
if($this->inputs[$y][$x]->isNull()){
|
||||
$this->inputs[$y][$x] = clone $item;
|
||||
}else{
|
||||
throw new \RuntimeException("Input $index has already been set");
|
||||
}
|
||||
}
|
||||
|
||||
public function getInputMap() : array{
|
||||
return $this->inputs;
|
||||
}
|
||||
|
||||
public function setExtraOutput(int $index, Item $item) : void{
|
||||
$y = (int) ($index / $this->gridSize);
|
||||
$x = $index % $this->gridSize;
|
||||
|
||||
if($this->secondaryOutputs[$y][$x]->isNull()){
|
||||
$this->secondaryOutputs[$y][$x] = clone $item;
|
||||
}else{
|
||||
throw new \RuntimeException("Output $index has already been set");
|
||||
}
|
||||
}
|
||||
|
||||
public function getPrimaryOutput() : ?Item{
|
||||
return $this->primaryOutput;
|
||||
}
|
||||
|
||||
public function setPrimaryOutput(Item $item) : void{
|
||||
if($this->primaryOutput === null){
|
||||
$this->primaryOutput = clone $item;
|
||||
}else{
|
||||
throw new \RuntimeException("Primary result item has already been set");
|
||||
}
|
||||
}
|
||||
|
||||
public function getRecipe() : ?CraftingRecipe{
|
||||
return $this->recipe;
|
||||
}
|
||||
|
||||
private function reindexInputs() : array{
|
||||
$xOffset = $this->gridSize;
|
||||
$yOffset = $this->gridSize;
|
||||
|
||||
$height = 0;
|
||||
$width = 0;
|
||||
|
||||
foreach($this->inputs as $y => $row){
|
||||
foreach($row as $x => $item){
|
||||
if(!$item->isNull()){
|
||||
$xOffset = min($x, $xOffset);
|
||||
$yOffset = min($y, $yOffset);
|
||||
|
||||
$height = max($y + 1 - $yOffset, $height);
|
||||
$width = max($x + 1 - $xOffset, $width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($height === 0 or $width === 0){
|
||||
return [];
|
||||
}
|
||||
|
||||
$air = ItemFactory::get(Item::AIR, 0, 0);
|
||||
$reindexed = array_fill(0, $height, array_fill(0, $width, $air));
|
||||
foreach($reindexed as $y => $row){
|
||||
foreach($row as $x => $item){
|
||||
$reindexed[$y][$x] = $this->inputs[$y + $yOffset][$x + $xOffset];
|
||||
}
|
||||
}
|
||||
|
||||
return $reindexed;
|
||||
}
|
||||
|
||||
public function canExecute() : bool{
|
||||
$inputs = $this->reindexInputs();
|
||||
|
||||
$this->recipe = $this->source->getServer()->getCraftingManager()->matchRecipe($inputs, $this->primaryOutput, $this->secondaryOutputs);
|
||||
|
||||
return $this->recipe !== null and parent::canExecute();
|
||||
}
|
||||
|
||||
protected function callExecuteEvent() : bool{
|
||||
$this->source->getServer()->getPluginManager()->callEvent($ev = new CraftItemEvent($this));
|
||||
return !$ev->isCancelled();
|
||||
}
|
||||
|
||||
public function execute() : bool{
|
||||
if(parent::execute()){
|
||||
switch($this->primaryOutput->getId()){
|
||||
case Item::CRAFTING_TABLE:
|
||||
$this->source->awardAchievement("buildWorkBench");
|
||||
break;
|
||||
case Item::WOODEN_PICKAXE:
|
||||
$this->source->awardAchievement("buildPickaxe");
|
||||
break;
|
||||
case Item::FURNACE:
|
||||
$this->source->awardAchievement("buildFurnace");
|
||||
break;
|
||||
case Item::WOODEN_HOE:
|
||||
$this->source->awardAchievement("buildHoe");
|
||||
break;
|
||||
case Item::BREAD:
|
||||
$this->source->awardAchievement("makeBread");
|
||||
break;
|
||||
case Item::CAKE:
|
||||
$this->source->awardAchievement("bakeCake");
|
||||
break;
|
||||
case Item::STONE_PICKAXE:
|
||||
case Item::GOLDEN_PICKAXE:
|
||||
case Item::IRON_PICKAXE:
|
||||
case Item::DIAMOND_PICKAXE:
|
||||
$this->source->awardAchievement("buildBetterPickaxe");
|
||||
break;
|
||||
case Item::WOODEN_SWORD:
|
||||
$this->source->awardAchievement("buildSword");
|
||||
break;
|
||||
case Item::DIAMOND:
|
||||
$this->source->awardAchievement("diamond");
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
288
src/pocketmine/inventory/transaction/InventoryTransaction.php
Normal file
288
src/pocketmine/inventory/transaction/InventoryTransaction.php
Normal file
@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory\transaction;
|
||||
|
||||
use pocketmine\event\inventory\InventoryTransactionEvent;
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\inventory\transaction\action\InventoryAction;
|
||||
use pocketmine\inventory\transaction\action\SlotChangeAction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\MainLogger;
|
||||
|
||||
/**
|
||||
* This InventoryTransaction only allows doing Transaction between one / two inventories
|
||||
*/
|
||||
class InventoryTransaction{
|
||||
/** @var float */
|
||||
private $creationTime;
|
||||
protected $hasExecuted = false;
|
||||
/** @var Player */
|
||||
protected $source;
|
||||
|
||||
/** @var Inventory[] */
|
||||
protected $inventories = [];
|
||||
|
||||
/** @var InventoryAction[] */
|
||||
protected $actions = [];
|
||||
|
||||
/**
|
||||
* @param Player $source
|
||||
* @param InventoryAction[] $actions
|
||||
*/
|
||||
public function __construct(Player $source, array $actions = []){
|
||||
$this->creationTime = microtime(true);
|
||||
$this->source = $source;
|
||||
foreach($actions as $action){
|
||||
$this->addAction($action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Player
|
||||
*/
|
||||
public function getSource() : Player{
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
public function getCreationTime() : float{
|
||||
return $this->creationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Inventory[]
|
||||
*/
|
||||
public function getInventories() : array{
|
||||
return $this->inventories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InventoryAction[]
|
||||
*/
|
||||
public function getActions() : array{
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InventoryAction $action
|
||||
*/
|
||||
public function addAction(InventoryAction $action) : void{
|
||||
if(!isset($this->actions[$hash = spl_object_hash($action)])){
|
||||
$this->actions[spl_object_hash($action)] = $action;
|
||||
$action->onAddToTransaction($this);
|
||||
}else{
|
||||
throw new \InvalidArgumentException("Tried to add the same action to a transaction twice");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
|
||||
* involving inventories.
|
||||
*
|
||||
* @param Inventory $inventory
|
||||
*/
|
||||
public function addInventory(Inventory $inventory) : void{
|
||||
if(!isset($this->inventories[$hash = spl_object_hash($inventory)])){
|
||||
$this->inventories[$hash] = $inventory;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $needItems
|
||||
* @param Item[] $haveItems
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function matchItems(array &$needItems, array &$haveItems) : bool{
|
||||
foreach($this->actions as $key => $action){
|
||||
if(!$action->getTargetItem()->isNull()){
|
||||
$needItems[] = $action->getTargetItem();
|
||||
}
|
||||
|
||||
if(!$action->isValid($this->source)){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$action->getSourceItem()->isNull()){
|
||||
$haveItems[] = $action->getSourceItem();
|
||||
}
|
||||
}
|
||||
|
||||
foreach($needItems as $i => $needItem){
|
||||
foreach($haveItems as $j => $haveItem){
|
||||
if($needItem->equals($haveItem)){
|
||||
$amount = min($needItem->getCount(), $haveItem->getCount());
|
||||
$needItem->setCount($needItem->getCount() - $amount);
|
||||
$haveItem->setCount($haveItem->getCount() - $amount);
|
||||
if($haveItem->getCount() === 0){
|
||||
unset($haveItems[$j]);
|
||||
}
|
||||
if($needItem->getCount() === 0){
|
||||
unset($needItems[$i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over SlotChangeActions in this transaction and compacts any which refer to the same slot in the same
|
||||
* inventory so they can be correctly handled.
|
||||
*
|
||||
* Under normal circumstances, the same slot would never be changed more than once in a single transaction. However,
|
||||
* due to the way things like the crafting grid are "implemented" in MCPE 1.2 (a.k.a. hacked-in), we may get
|
||||
* multiple slot changes referring to the same slot in a single transaction. These multiples are not even guaranteed
|
||||
* to be in the correct order (slot splitting in the crafting grid for example, causes the actions to be sent in the
|
||||
* wrong order), so this method also tries to chain them into order.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function squashDuplicateSlotChanges() : bool{
|
||||
/** @var SlotChangeAction[][] $slotChanges */
|
||||
$slotChanges = [];
|
||||
foreach($this->actions as $key => $action){
|
||||
if($action instanceof SlotChangeAction){
|
||||
$slotChanges[spl_object_hash($action->getInventory()) . "@" . $action->getSlot()][] = $action;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($slotChanges as $hash => $list){
|
||||
if(count($list) === 1){ //No need to compact slot changes if there is only one on this slot
|
||||
unset($slotChanges[$hash]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$originalList = $list;
|
||||
|
||||
/** @var SlotChangeAction|null $originalAction */
|
||||
$originalAction = null;
|
||||
/** @var Item|null $lastTargetItem */
|
||||
$lastTargetItem = null;
|
||||
|
||||
foreach($list as $i => $action){
|
||||
if($action->isValid($this->source)){
|
||||
$originalAction = $action;
|
||||
$lastTargetItem = $action->getTargetItem();
|
||||
unset($list[$i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($originalAction === null){
|
||||
return false; //Couldn't find any actions that had a source-item matching the current inventory slot
|
||||
}
|
||||
|
||||
do{
|
||||
$sortedThisLoop = 0;
|
||||
foreach($list as $i => $action){
|
||||
$actionSource = $action->getSourceItem();
|
||||
if($actionSource->equalsExact($lastTargetItem)){
|
||||
$lastTargetItem = $action->getTargetItem();
|
||||
unset($list[$i]);
|
||||
$sortedThisLoop++;
|
||||
}
|
||||
}
|
||||
}while($sortedThisLoop > 0);
|
||||
|
||||
if(count($list) > 0){ //couldn't chain all the actions together
|
||||
MainLogger::getLogger()->debug("Failed to compact " . count($originalList) . " actions for " . $this->source->getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($originalList as $action){
|
||||
unset($this->actions[spl_object_hash($action)]);
|
||||
}
|
||||
|
||||
$this->addAction(new SlotChangeAction($originalAction->getInventory(), $originalAction->getSlot(), $originalAction->getSourceItem(), $lastTargetItem));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function canExecute() : bool{
|
||||
$this->squashDuplicateSlotChanges();
|
||||
|
||||
$haveItems = [];
|
||||
$needItems = [];
|
||||
return $this->matchItems($needItems, $haveItems) and count($this->actions) > 0 and count($haveItems) === 0 and count($needItems) === 0;
|
||||
}
|
||||
|
||||
protected function handleFailed() : void{
|
||||
foreach($this->actions as $action){
|
||||
$action->onExecuteFail($this->source);
|
||||
}
|
||||
}
|
||||
|
||||
protected function callExecuteEvent() : bool{
|
||||
Server::getInstance()->getPluginManager()->callEvent($ev = new InventoryTransactionEvent($this));
|
||||
return !$ev->isCancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function execute() : bool{
|
||||
if($this->hasExecuted() or !$this->canExecute()){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$this->callExecuteEvent()){
|
||||
$this->handleFailed();
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach($this->actions as $action){
|
||||
if(!$action->onPreExecute($this->source)){
|
||||
$this->handleFailed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($this->actions as $action){
|
||||
if($action->execute($this->source)){
|
||||
$action->onExecuteSuccess($this->source);
|
||||
}else{
|
||||
$action->onExecuteFail($this->source);
|
||||
}
|
||||
}
|
||||
|
||||
$this->hasExecuted = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExecuted() : bool{
|
||||
return $this->hasExecuted;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory\transaction\action;
|
||||
|
||||
use pocketmine\inventory\transaction\CraftingTransaction;
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
use pocketmine\Player;
|
||||
|
||||
/**
|
||||
* Action used to take the primary result item during crafting.
|
||||
*/
|
||||
class CraftingTakeResultAction extends InventoryAction{
|
||||
|
||||
public function onAddToTransaction(InventoryTransaction $transaction) : void{
|
||||
if($transaction instanceof CraftingTransaction){
|
||||
$transaction->setPrimaryOutput($this->getSourceItem());
|
||||
}else{
|
||||
throw new \InvalidStateException(get_class($this) . " can only be added to CraftingTransactions");
|
||||
}
|
||||
}
|
||||
|
||||
public function isValid(Player $source) : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function execute(Player $source) : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onExecuteSuccess(Player $source) : void{
|
||||
|
||||
}
|
||||
|
||||
public function onExecuteFail(Player $source) : void{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory\transaction\action;
|
||||
|
||||
use pocketmine\inventory\transaction\CraftingTransaction;
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
|
||||
/**
|
||||
* Action used to take ingredients out of the crafting grid, or put secondary results into the crafting grid, when
|
||||
* crafting.
|
||||
*/
|
||||
class CraftingTransferMaterialAction extends InventoryAction{
|
||||
/** @var int */
|
||||
private $slot;
|
||||
|
||||
public function __construct(Item $sourceItem, Item $targetItem, int $slot){
|
||||
parent::__construct($sourceItem, $targetItem);
|
||||
$this->slot = $slot;
|
||||
}
|
||||
|
||||
public function onAddToTransaction(InventoryTransaction $transaction) : void{
|
||||
if($transaction instanceof CraftingTransaction){
|
||||
if($this->sourceItem->isNull()){
|
||||
$transaction->setInput($this->slot, $this->targetItem);
|
||||
}elseif($this->targetItem->isNull()){
|
||||
$transaction->setExtraOutput($this->slot, $this->sourceItem);
|
||||
}else{
|
||||
throw new \InvalidStateException("Invalid " . get_class($this) . ", either source or target item must be air, got source: " . $this->sourceItem . ", target: " . $this->targetItem);
|
||||
}
|
||||
}else{
|
||||
throw new \InvalidStateException(get_class($this) . " can only be added to CraftingTransactions");
|
||||
}
|
||||
}
|
||||
|
||||
public function isValid(Player $source) : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function execute(Player $source) : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onExecuteSuccess(Player $source) : void{
|
||||
|
||||
}
|
||||
|
||||
public function onExecuteFail(Player $source) : void{
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory\transaction\action;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
|
||||
class CreativeInventoryAction extends InventoryAction{
|
||||
|
||||
/**
|
||||
* Player put an item into the creative window to destroy it.
|
||||
*/
|
||||
const TYPE_DELETE_ITEM = 0;
|
||||
/**
|
||||
* Player took an item from the creative window.
|
||||
*/
|
||||
const TYPE_CREATE_ITEM = 1;
|
||||
|
||||
protected $actionType;
|
||||
|
||||
public function __construct(Item $sourceItem, Item $targetItem, int $actionType){
|
||||
parent::__construct($sourceItem, $targetItem);
|
||||
$this->actionType = $actionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the player is in creative, and (if creating an item) that the item exists in the creative inventory.
|
||||
*
|
||||
* @param Player $source
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid(Player $source) : bool{
|
||||
return $source->isCreative(true) and
|
||||
($this->actionType === self::TYPE_DELETE_ITEM or Item::getCreativeItemIndex($this->sourceItem) !== -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the action.
|
||||
*/
|
||||
public function getActionType() : int{
|
||||
return $this->actionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* No need to do anything extra here: this type just provides a place for items to disappear or appear from.
|
||||
*
|
||||
* @param Player $source
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function execute(Player $source) : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onExecuteSuccess(Player $source) : void{
|
||||
|
||||
}
|
||||
|
||||
public function onExecuteFail(Player $source) : void{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory\transaction\action;
|
||||
|
||||
use pocketmine\event\player\PlayerDropItemEvent;
|
||||
use pocketmine\Player;
|
||||
|
||||
/**
|
||||
* Represents an action involving dropping an item into the world.
|
||||
*/
|
||||
class DropItemAction extends InventoryAction{
|
||||
|
||||
/**
|
||||
* Verifies that the source item of a drop-item action must be air. This is not strictly necessary, just a sanity
|
||||
* check.
|
||||
*
|
||||
* @param Player $source
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid(Player $source) : bool{
|
||||
return $this->sourceItem->isNull();
|
||||
}
|
||||
|
||||
public function onPreExecute(Player $source) : bool{
|
||||
$source->getServer()->getPluginManager()->callEvent($ev = new PlayerDropItemEvent($source, $this->targetItem));
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the target item in front of the player.
|
||||
*
|
||||
* @param Player $source
|
||||
* @return bool
|
||||
*/
|
||||
public function execute(Player $source) : bool{
|
||||
return $source->dropItem($this->targetItem);
|
||||
}
|
||||
|
||||
public function onExecuteSuccess(Player $source) : void{
|
||||
|
||||
}
|
||||
|
||||
public function onExecuteFail(Player $source) : void{
|
||||
|
||||
}
|
||||
}
|
124
src/pocketmine/inventory/transaction/action/InventoryAction.php
Normal file
124
src/pocketmine/inventory/transaction/action/InventoryAction.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory\transaction\action;
|
||||
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
|
||||
/**
|
||||
* Represents an action involving a change that applies in some way to an inventory or other item-source.
|
||||
*/
|
||||
abstract class InventoryAction{
|
||||
|
||||
/** @var float */
|
||||
private $creationTime;
|
||||
/** @var Item */
|
||||
protected $sourceItem;
|
||||
/** @var Item */
|
||||
protected $targetItem;
|
||||
|
||||
public function __construct(Item $sourceItem, Item $targetItem){
|
||||
$this->sourceItem = $sourceItem;
|
||||
$this->targetItem = $targetItem;
|
||||
|
||||
$this->creationTime = microtime(true);
|
||||
}
|
||||
|
||||
public function getCreationTime() : float{
|
||||
return $this->creationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item that was present before the action took place.
|
||||
* @return Item
|
||||
*/
|
||||
public function getSourceItem() : Item{
|
||||
return clone $this->sourceItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item that the action attempted to replace the source item with.
|
||||
* @return Item
|
||||
*/
|
||||
public function getTargetItem() : Item{
|
||||
return clone $this->targetItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this action is currently valid. This should perform any necessary sanity checks.
|
||||
*
|
||||
* @param Player $source
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isValid(Player $source) : bool;
|
||||
|
||||
/**
|
||||
* Called when the action is added to the specified InventoryTransaction.
|
||||
*
|
||||
* @param InventoryTransaction $transaction
|
||||
*/
|
||||
public function onAddToTransaction(InventoryTransaction $transaction) : void{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by inventory transactions before any actions are processed. If this returns false, the transaction will
|
||||
* be cancelled.
|
||||
*
|
||||
* @param Player $source
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onPreExecute(Player $source) : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs actions needed to complete the inventory-action server-side. Returns if it was successful. Will return
|
||||
* false if plugins cancelled events. This will only be called if the transaction which it is part of is considered
|
||||
* valid.
|
||||
*
|
||||
* @param Player $source
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function execute(Player $source) : bool;
|
||||
|
||||
/**
|
||||
* Performs additional actions when this inventory-action completed successfully.
|
||||
*
|
||||
* @param Player $source
|
||||
*/
|
||||
abstract public function onExecuteSuccess(Player $source) : void;
|
||||
|
||||
/**
|
||||
* Performs additional actions when this inventory-action did not complete successfully.
|
||||
*
|
||||
* @param Player $source
|
||||
*/
|
||||
abstract public function onExecuteFail(Player $source) : void;
|
||||
|
||||
}
|
122
src/pocketmine/inventory/transaction/action/SlotChangeAction.php
Normal file
122
src/pocketmine/inventory/transaction/action/SlotChangeAction.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory\transaction\action;
|
||||
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
|
||||
/**
|
||||
* Represents an action causing a change in an inventory slot.
|
||||
*/
|
||||
class SlotChangeAction extends InventoryAction{
|
||||
|
||||
/** @var Inventory */
|
||||
protected $inventory;
|
||||
/** @var int */
|
||||
private $inventorySlot;
|
||||
|
||||
/**
|
||||
* @param Inventory $inventory
|
||||
* @param int $inventorySlot
|
||||
* @param Item $sourceItem
|
||||
* @param Item $targetItem
|
||||
*/
|
||||
public function __construct(Inventory $inventory, int $inventorySlot, Item $sourceItem, Item $targetItem){
|
||||
parent::__construct($sourceItem, $targetItem);
|
||||
$this->inventory = $inventory;
|
||||
$this->inventorySlot = $inventorySlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the inventory involved in this action.
|
||||
*
|
||||
* @return Inventory
|
||||
*/
|
||||
public function getInventory() : Inventory{
|
||||
return $this->inventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the slot in the inventory which this action modified.
|
||||
* @return int
|
||||
*/
|
||||
public function getSlot() : int{
|
||||
return $this->inventorySlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the item in the inventory at the specified slot is the same as this action's source item.
|
||||
*
|
||||
* @param Player $source
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid(Player $source) : bool{
|
||||
$check = $this->inventory->getItem($this->inventorySlot);
|
||||
return $check->equalsExact($this->sourceItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this action's target inventory to the transaction's inventory list.
|
||||
*
|
||||
* @param InventoryTransaction $transaction
|
||||
*
|
||||
*/
|
||||
public function onAddToTransaction(InventoryTransaction $transaction) : void{
|
||||
$transaction->addInventory($this->inventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the item into the target inventory.
|
||||
*
|
||||
* @param Player $source
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function execute(Player $source) : bool{
|
||||
return $this->inventory->setItem($this->inventorySlot, $this->targetItem, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends slot changes to other viewers of the inventory. This will not send any change back to the source Player.
|
||||
*
|
||||
* @param Player $source
|
||||
*/
|
||||
public function onExecuteSuccess(Player $source) : void{
|
||||
$viewers = $this->inventory->getViewers();
|
||||
unset($viewers[spl_object_hash($source)]);
|
||||
$this->inventory->sendSlot($this->inventorySlot, $viewers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the original slot contents to the source player to revert the action.
|
||||
*
|
||||
* @param Player $source
|
||||
*/
|
||||
public function onExecuteFail(Player $source) : void{
|
||||
$this->inventory->sendSlot($this->inventorySlot, $source);
|
||||
}
|
||||
}
|
@ -643,6 +643,10 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isNull() : bool{
|
||||
return $this->count <= 0 or $this->id === Item::AIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the item, or the custom name if it is set.
|
||||
* @return string
|
||||
@ -879,6 +883,16 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified item stack has the same ID, damage, NBT and count as this item stack.
|
||||
* @param Item $other
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final public function equalsExact(Item $other) : bool{
|
||||
return $this->equals($other, true, true) and $this->count === $other->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Item#equals} instead, this method will be removed in the future.
|
||||
*
|
||||
@ -905,12 +919,23 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
* @return array
|
||||
*/
|
||||
final public function jsonSerialize(){
|
||||
return [
|
||||
"id" => $this->getId(),
|
||||
"damage" => $this->getDamage(),
|
||||
"count" => $this->getCount(),
|
||||
"nbt_hex" => bin2hex($this->getCompoundTag())
|
||||
$data = [
|
||||
"id" => $this->getId()
|
||||
];
|
||||
|
||||
if($this->getDamage() !== 0){
|
||||
$data["damage"] = $this->getDamage();
|
||||
}
|
||||
|
||||
if($this->getCount() !== 1){
|
||||
$data["count"] = $this->getCount();
|
||||
}
|
||||
|
||||
if($this->hasCompoundTag()){
|
||||
$data["nbt_hex"] = bin2hex($this->getCompoundTag());
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -922,9 +947,9 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
final public static function jsonDeserialize(array $data) : Item{
|
||||
return ItemFactory::get(
|
||||
(int) $data["id"],
|
||||
(int) $data["damage"],
|
||||
(int) $data["count"],
|
||||
(string) ($data["nbt"] ?? hex2bin($data["nbt_hex"])) //`nbt` key might contain old raw data
|
||||
(int) ($data["damage"] ?? 0),
|
||||
(int) ($data["count"] ?? 1),
|
||||
(string) ($data["nbt"] ?? (isset($data["nbt_hex"]) ? hex2bin($data["nbt_hex"]) : "")) //`nbt` key might contain old raw data
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ class ItemBlock extends Item{
|
||||
|
||||
public function setDamage(int $meta){
|
||||
$this->meta = $meta;
|
||||
$this->block->setDamage($this->meta !== -1 ? $this->meta : 0);
|
||||
$this->block->setDamage($this->meta !== -1 ? $this->meta & 0xf : 0);
|
||||
}
|
||||
|
||||
public function getBlock() : Block{
|
||||
|
@ -256,7 +256,7 @@ class ItemFactory{
|
||||
*/
|
||||
public static function registerItem(Item $item, bool $override = false){
|
||||
$id = $item->getId();
|
||||
if(!$override and self::$list[$id] !== null){
|
||||
if(!$override and self::isRegistered($id)){
|
||||
throw new \RuntimeException("Trying to overwrite an already registered item");
|
||||
}
|
||||
|
||||
@ -284,7 +284,7 @@ class ItemFactory{
|
||||
if($id < 256){
|
||||
/* Blocks must have a damage value 0-15, but items can have damage value -1 to indicate that they are
|
||||
* crafting ingredients with any-damage. */
|
||||
$item = new ItemBlock(BlockFactory::get($id, $meta !== -1 ? $meta : 0), $meta);
|
||||
$item = new ItemBlock(BlockFactory::get($id, $meta !== -1 ? $meta & 0xf : 0), $meta);
|
||||
}else{
|
||||
/** @var Item|null $listed */
|
||||
$listed = self::$list[$id];
|
||||
@ -338,14 +338,29 @@ class ItemFactory{
|
||||
|
||||
if(defined(Item::class . "::" . strtoupper($b[0]))){
|
||||
$item = self::get(constant(Item::class . "::" . strtoupper($b[0])), $meta);
|
||||
if($item->getId() === Item::AIR and strtoupper($b[0]) !== "AIR"){
|
||||
$item = self::get($b[0] & 0xFFFF, $meta);
|
||||
if($item->getId() === Item::AIR and strtoupper($b[0]) !== "AIR" and is_numeric($b[0])){
|
||||
$item = self::get(((int) $b[0]) & 0xFFFF, $meta);
|
||||
}
|
||||
}elseif(is_numeric($b[0])){
|
||||
$item = self::get(((int) $b[0]) & 0xFFFF, $meta);
|
||||
}else{
|
||||
$item = self::get($b[0] & 0xFFFF, $meta);
|
||||
$item = self::get(Item::AIR, 0, 0);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified item ID is already registered in the item factory.
|
||||
*
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public static function isRegistered(int $id) : bool{
|
||||
if($id < 256){
|
||||
return BlockFactory::isRegistered($id);
|
||||
}
|
||||
return self::$list[$id] !== null;
|
||||
}
|
||||
}
|
@ -156,7 +156,8 @@ interface ItemIds extends BlockIds{
|
||||
const SPAWN_EGG = 383;
|
||||
const BOTTLE_O_ENCHANTING = 384, EXPERIENCE_BOTTLE = 384;
|
||||
const FIREBALL = 385, FIRE_CHARGE = 385;
|
||||
|
||||
const WRITABLE_BOOK = 386;
|
||||
const WRITTEN_BOOK = 387;
|
||||
const EMERALD = 388;
|
||||
const FRAME = 389, ITEM_FRAME = 389;
|
||||
const FLOWER_POT = 390;
|
||||
@ -170,7 +171,8 @@ interface ItemIds extends BlockIds{
|
||||
const CARROTONASTICK = 398, CARROT_ON_A_STICK = 398;
|
||||
const NETHERSTAR = 399, NETHER_STAR = 399;
|
||||
const PUMPKIN_PIE = 400;
|
||||
|
||||
const FIREWORKS = 401;
|
||||
const FIREWORKSCHARGE = 402, FIREWORKS_CHARGE = 402;
|
||||
const ENCHANTED_BOOK = 403;
|
||||
const COMPARATOR = 404;
|
||||
const NETHERBRICK = 405, NETHER_BRICK = 405;
|
||||
@ -193,7 +195,7 @@ interface ItemIds extends BlockIds{
|
||||
const PRISMARINE_CRYSTALS = 422;
|
||||
const MUTTONRAW = 423, MUTTON_RAW = 423, RAW_MUTTON = 423;
|
||||
const COOKED_MUTTON = 424, MUTTONCOOKED = 424, MUTTON_COOKED = 424;
|
||||
|
||||
const ARMOR_STAND = 425;
|
||||
const END_CRYSTAL = 426;
|
||||
const SPRUCE_DOOR = 427;
|
||||
const BIRCH_DOOR = 428;
|
||||
@ -211,6 +213,7 @@ interface ItemIds extends BlockIds{
|
||||
const COMMAND_BLOCK_MINECART = 443, MINECART_WITH_COMMAND_BLOCK = 443;
|
||||
const ELYTRA = 444;
|
||||
const SHULKER_SHELL = 445;
|
||||
const BANNER = 446;
|
||||
|
||||
const TOTEM = 450;
|
||||
|
||||
@ -226,4 +229,17 @@ interface ItemIds extends BlockIds{
|
||||
|
||||
const APPLEENCHANTED = 466, APPLE_ENCHANTED = 466, ENCHANTED_GOLDEN_APPLE = 466;
|
||||
|
||||
const RECORD_13 = 500;
|
||||
const RECORD_CAT = 501;
|
||||
const RECORD_BLOCKS = 502;
|
||||
const RECORD_CHIRP = 503;
|
||||
const RECORD_FAR = 504;
|
||||
const RECORD_MALL = 505;
|
||||
const RECORD_MELLOHI = 506;
|
||||
const RECORD_STAL = 507;
|
||||
const RECORD_STRAD = 508;
|
||||
const RECORD_WARD = 509;
|
||||
const RECORD_11 = 510;
|
||||
const RECORD_WAIT = 511;
|
||||
|
||||
}
|
||||
|
Submodule src/pocketmine/lang/locale updated: 9868a649ad...bfa58add7c
@ -225,9 +225,7 @@ class Explosion{
|
||||
}
|
||||
|
||||
$pk = new ExplodePacket();
|
||||
$pk->x = $this->source->x;
|
||||
$pk->y = $this->source->y;
|
||||
$pk->z = $this->source->z;
|
||||
$pk->position = $this->source->asVector3();
|
||||
$pk->radius = $this->size;
|
||||
$pk->records = $send;
|
||||
$this->level->addChunkPacket($source->x >> 4, $source->z >> 4, $pk);
|
||||
|
@ -44,7 +44,6 @@ use pocketmine\event\level\SpawnChangeEvent;
|
||||
use pocketmine\event\LevelTimings;
|
||||
use pocketmine\event\player\PlayerInteractEvent;
|
||||
use pocketmine\event\Timings;
|
||||
use pocketmine\inventory\InventoryHolder;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\level\format\Chunk;
|
||||
@ -78,14 +77,13 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\MoveEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetEntityMotionPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTimePacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\tile\Chest;
|
||||
use pocketmine\tile\Container;
|
||||
use pocketmine\tile\Tile;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\utils\ReversePriorityQueue;
|
||||
@ -441,7 +439,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$pk = new LevelEventPacket();
|
||||
$pk->evid = $evid;
|
||||
$pk->data = $data;
|
||||
list($pk->x, $pk->y, $pk->z) = [$pos->x, $pos->y, $pos->z];
|
||||
$pk->position = $pos->asVector3();
|
||||
$this->addChunkPacket($pos->x >> 4, $pos->z >> 4, $pk);
|
||||
}
|
||||
|
||||
@ -462,7 +460,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$pk->extraData = $extraData;
|
||||
$pk->unknownBool = $unknown;
|
||||
$pk->disableRelativeVolume = $disableRelativeVolume;
|
||||
list($pk->x, $pk->y, $pk->z) = [$pos->x, $pos->y, $pos->z];
|
||||
$pk->position = $pos->asVector3();
|
||||
$this->addChunkPacket($pos->x >> 4, $pos->z >> 4, $pk);
|
||||
}
|
||||
|
||||
@ -475,6 +473,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal DO NOT use this from plugins, it's for internal use only. Use Server->unloadLevel() instead.
|
||||
*
|
||||
* Unloads the current level from memory safely
|
||||
*
|
||||
* @param bool $force default false, force unload of default level
|
||||
@ -509,6 +509,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->server->setDefaultLevel(null);
|
||||
}
|
||||
|
||||
$this->server->removeLevel($this);
|
||||
|
||||
$this->close();
|
||||
|
||||
return true;
|
||||
@ -760,9 +762,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
public function sendBlockExtraData(int $x, int $y, int $z, int $id, int $data, array $targets = null){
|
||||
$pk = new LevelEventPacket;
|
||||
$pk->evid = LevelEventPacket::EVENT_SET_DATA;
|
||||
$pk->x = $x + 0.5;
|
||||
$pk->y = $y + 0.5;
|
||||
$pk->z = $z + 0.5;
|
||||
$pk->position = new Vector3($x, $y, $z);
|
||||
$pk->data = ($data << 8) | $id;
|
||||
|
||||
$this->server->broadcastPacket($targets ?? $this->getChunkPlayers($x >> 4, $z >> 4), $pk);
|
||||
@ -1622,7 +1622,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
$tile = $this->getTile($target);
|
||||
if($tile !== null){
|
||||
if($tile instanceof InventoryHolder){
|
||||
if($tile instanceof Container){
|
||||
if($tile instanceof Chest){
|
||||
$tile->unpair();
|
||||
}
|
||||
@ -1727,20 +1727,23 @@ class Level implements ChunkManager, Metadatable{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!$item->canBePlaced()){
|
||||
if($item->canBePlaced()){
|
||||
$hand = $item->getBlock();
|
||||
$hand->position($blockReplace);
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
|
||||
$hand = $item->getBlock();
|
||||
if(!($blockReplace->canBeReplaced() === true or ($hand->getId() === Item::WOODEN_SLAB and $blockReplace->getId() === Item::WOODEN_SLAB) or ($hand->getId() === Item::STONE_SLAB and $blockReplace->getId() === Item::STONE_SLAB))){
|
||||
return false;
|
||||
}
|
||||
|
||||
if($blockClicked->canBeReplaced($hand)){
|
||||
if($blockClicked->canBeReplaced() === true){
|
||||
$blockReplace = $blockClicked;
|
||||
}elseif(!$blockReplace->canBeReplaced($hand)){
|
||||
return false;
|
||||
$hand->position($blockReplace);
|
||||
//$face = -1;
|
||||
}
|
||||
|
||||
$hand->position($blockReplace);
|
||||
|
||||
if($hand->isSolid() === true and $hand->getBoundingBox() !== null){
|
||||
$entities = $this->getCollidingEntities($hand->getBoundingBox());
|
||||
foreach($entities as $e){
|
||||
@ -2863,25 +2866,4 @@ class Level implements ChunkManager, Metadatable{
|
||||
public function removeMetadata(string $metadataKey, Plugin $owningPlugin){
|
||||
$this->server->getLevelMetadata()->removeMetadata($this, $metadataKey, $owningPlugin);
|
||||
}
|
||||
|
||||
public function addEntityMotion(int $chunkX, int $chunkZ, int $entityId, float $x, float $y, float $z){
|
||||
$pk = new SetEntityMotionPacket();
|
||||
$pk->entityRuntimeId = $entityId;
|
||||
$pk->motionX = $x;
|
||||
$pk->motionY = $y;
|
||||
$pk->motionZ = $z;
|
||||
$this->addChunkPacket($chunkX, $chunkZ, $pk);
|
||||
}
|
||||
|
||||
public function addEntityMovement(int $chunkX, int $chunkZ, int $entityId, float $x, float $y, float $z, float $yaw, float $pitch, $headYaw = null){
|
||||
$pk = new MoveEntityPacket();
|
||||
$pk->entityRuntimeId = $entityId;
|
||||
$pk->x = $x;
|
||||
$pk->y = $y;
|
||||
$pk->z = $z;
|
||||
$pk->yaw = $yaw;
|
||||
$pk->pitch = $pitch;
|
||||
$pk->headYaw = $headYaw ?? $yaw;
|
||||
$this->addChunkPacket($chunkX, $chunkZ, $pk);
|
||||
}
|
||||
}
|
||||
|
@ -41,12 +41,9 @@ class Location extends Position{
|
||||
* @param Level $level
|
||||
*/
|
||||
public function __construct($x = 0, $y = 0, $z = 0, $yaw = 0.0, $pitch = 0.0, Level $level = null){
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
$this->z = $z;
|
||||
$this->yaw = $yaw;
|
||||
$this->pitch = $pitch;
|
||||
$this->level = $level;
|
||||
parent::__construct($x, $y, $z, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,7 +114,7 @@ class EmptySubChunk implements SubChunkInterface{
|
||||
}
|
||||
|
||||
public function networkSerialize() : string{
|
||||
return "\x00" . str_repeat("\x00", 10240);
|
||||
return "\x00" . str_repeat("\x00", 6144);
|
||||
}
|
||||
|
||||
public function fastSerialize() : string{
|
||||
|
@ -219,8 +219,7 @@ class SubChunk implements SubChunkInterface{
|
||||
}
|
||||
|
||||
public function networkSerialize() : string{
|
||||
// storage version, ids, data, skylight, blocklight
|
||||
return "\x00" . $this->ids . $this->data . $this->skyLight . $this->blockLight;
|
||||
return "\x00" . $this->ids . $this->data;
|
||||
}
|
||||
|
||||
public function fastSerialize() : string{
|
||||
@ -239,4 +238,8 @@ class SubChunk implements SubChunkInterface{
|
||||
substr($data, 8192, 2048) //block light
|
||||
);
|
||||
}
|
||||
|
||||
public function __debugInfo(){
|
||||
return [];
|
||||
}
|
||||
}
|
@ -27,6 +27,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\level\generator\object;
|
||||
|
||||
|
||||
abstract class Object{
|
||||
abstract class PopulatorObject{
|
||||
|
||||
}
|
@ -39,9 +39,7 @@ class DestroyBlockParticle extends Particle{
|
||||
public function encode(){
|
||||
$pk = new LevelEventPacket;
|
||||
$pk->evid = LevelEventPacket::EVENT_PARTICLE_DESTROY;
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->data = $this->data;
|
||||
|
||||
return $pk;
|
||||
|
@ -24,10 +24,12 @@ declare(strict_types=1);
|
||||
namespace pocketmine\level\particle;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Item as ItemEntity;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
|
||||
use pocketmine\utils\UUID;
|
||||
|
||||
class FloatingTextParticle extends Particle{
|
||||
//TODO: HACK!
|
||||
@ -39,29 +41,37 @@ class FloatingTextParticle extends Particle{
|
||||
|
||||
/**
|
||||
* @param Vector3 $pos
|
||||
* @param int $text
|
||||
* @param string $text
|
||||
* @param string $title
|
||||
*/
|
||||
public function __construct(Vector3 $pos, $text, $title = ""){
|
||||
public function __construct(Vector3 $pos, string $text, string $title = ""){
|
||||
parent::__construct($pos->x, $pos->y, $pos->z);
|
||||
$this->text = $text;
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
public function setText($text){
|
||||
public function getText() : string{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function setText(string $text) : void{
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
public function setTitle($title){
|
||||
public function getTitle() : string{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(string $title) : void{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
public function isInvisible(){
|
||||
public function isInvisible() : bool{
|
||||
return $this->invisible;
|
||||
}
|
||||
|
||||
public function setInvisible($value = true){
|
||||
$this->invisible = (bool) $value;
|
||||
public function setInvisible(bool $value = true){
|
||||
$this->invisible = $value;
|
||||
}
|
||||
|
||||
public function encode(){
|
||||
@ -77,26 +87,22 @@ class FloatingTextParticle extends Particle{
|
||||
}
|
||||
|
||||
if(!$this->invisible){
|
||||
|
||||
$pk = new AddEntityPacket();
|
||||
$pk = new AddPlayerPacket();
|
||||
$pk->uuid = UUID::fromRandom();
|
||||
$pk->username = "";
|
||||
$pk->entityRuntimeId = $this->entityId;
|
||||
$pk->type = ItemEntity::NETWORK_ID;
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y - 0.75;
|
||||
$pk->z = $this->z;
|
||||
$pk->speedX = 0;
|
||||
$pk->speedY = 0;
|
||||
$pk->speedZ = 0;
|
||||
$pk->yaw = 0;
|
||||
$pk->pitch = 0;
|
||||
$pk->position = $this->asVector3(); //TODO: check offset
|
||||
$pk->item = ItemFactory::get(Item::AIR, 0, 0);
|
||||
|
||||
$flags = (
|
||||
(1 << Entity::DATA_FLAG_CAN_SHOW_NAMETAG) |
|
||||
(1 << Entity::DATA_FLAG_ALWAYS_SHOW_NAMETAG) |
|
||||
(1 << Entity::DATA_FLAG_IMMOBILE)
|
||||
);
|
||||
$pk->metadata = [
|
||||
Entity::DATA_FLAGS => [Entity::DATA_TYPE_LONG, $flags],
|
||||
Entity::DATA_NAMETAG => [Entity::DATA_TYPE_STRING, $this->title . ($this->text !== "" ? "\n" . $this->text : "")]
|
||||
Entity::DATA_FLAGS => [Entity::DATA_TYPE_LONG, $flags],
|
||||
Entity::DATA_NAMETAG => [Entity::DATA_TYPE_STRING, $this->title . ($this->text !== "" ? "\n" . $this->text : "")],
|
||||
Entity::DATA_SCALE => [Entity::DATA_TYPE_FLOAT, 0.01] //zero causes problems on debug builds
|
||||
];
|
||||
|
||||
$p[] = $pk;
|
||||
|
@ -40,9 +40,7 @@ class GenericParticle extends Particle{
|
||||
public function encode(){
|
||||
$pk = new LevelEventPacket;
|
||||
$pk->evid = LevelEventPacket::EVENT_ADD_PARTICLE_MASK | $this->id;
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->data = $this->data;
|
||||
|
||||
return $pk;
|
||||
|
@ -40,9 +40,7 @@ class MobSpawnParticle extends Particle{
|
||||
public function encode(){
|
||||
$pk = new LevelEventPacket;
|
||||
$pk->evid = LevelEventPacket::EVENT_PARTICLE_SPAWN;
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->data = ($this->width & 0xff) + (($this->height & 0xff) << 8);
|
||||
|
||||
return $pk;
|
||||
|
@ -49,9 +49,7 @@ class GenericSound extends Sound{
|
||||
public function encode(){
|
||||
$pk = new LevelEventPacket;
|
||||
$pk->evid = $this->id;
|
||||
$pk->x = $this->x;
|
||||
$pk->y = $this->y;
|
||||
$pk->z = $this->z;
|
||||
$pk->position = $this->asVector3();
|
||||
$pk->data = (int) $this->pitch;
|
||||
|
||||
return $pk;
|
||||
|
@ -27,7 +27,6 @@ use pocketmine\network\mcpe\protocol\AddBehaviorTreePacket;
|
||||
use pocketmine\network\mcpe\protocol\AddEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddHangingEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddItemEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddPaintingPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
|
||||
@ -36,6 +35,7 @@ use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\BookEditPacket;
|
||||
use pocketmine\network\mcpe\protocol\BossEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\CameraPacket;
|
||||
use pocketmine\network\mcpe\protocol\ChangeDimensionPacket;
|
||||
@ -43,26 +43,28 @@ use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientToServerHandshakePacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundMapItemDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket;
|
||||
use pocketmine\network\mcpe\protocol\CommandStepPacket;
|
||||
use pocketmine\network\mcpe\protocol\CommandOutputPacket;
|
||||
use pocketmine\network\mcpe\protocol\CommandRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\DisconnectPacket;
|
||||
use pocketmine\network\mcpe\protocol\DropItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\EntityEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\EntityFallPacket;
|
||||
use pocketmine\network\mcpe\protocol\EntityPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\EventPacket;
|
||||
use pocketmine\network\mcpe\protocol\ExplodePacket;
|
||||
use pocketmine\network\mcpe\protocol\FullChunkDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\GameRulesChangedPacket;
|
||||
use pocketmine\network\mcpe\protocol\GuiDataPickItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\HurtArmorPacket;
|
||||
use pocketmine\network\mcpe\protocol\InteractPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryActionPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
|
||||
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
@ -71,17 +73,21 @@ use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEffectPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
|
||||
use pocketmine\network\mcpe\protocol\MoveEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\NpcRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\PhotoTransferPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlaySoundPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerListPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
|
||||
use pocketmine\network\mcpe\protocol\PurchaseReceiptPacket;
|
||||
use pocketmine\network\mcpe\protocol\RemoveBlockPacket;
|
||||
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\ReplaceItemInSlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
|
||||
use pocketmine\network\mcpe\protocol\ResourcePackChunkDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
|
||||
@ -91,24 +97,30 @@ use pocketmine\network\mcpe\protocol\ResourcePackStackPacket;
|
||||
use pocketmine\network\mcpe\protocol\ResourcePacksInfoPacket;
|
||||
use pocketmine\network\mcpe\protocol\RespawnPacket;
|
||||
use pocketmine\network\mcpe\protocol\RiderJumpPacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerSettingsResponsePacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetCommandsEnabledPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetDefaultGameTypePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetEntityDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetEntityLinkPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetEntityMotionPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetHealthPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetLastHurtByPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTimePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTitlePacket;
|
||||
use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
|
||||
use pocketmine\network\mcpe\protocol\ShowProfilePacket;
|
||||
use pocketmine\network\mcpe\protocol\ShowStoreOfferPacket;
|
||||
use pocketmine\network\mcpe\protocol\SimpleEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
|
||||
use pocketmine\network\mcpe\protocol\StartGamePacket;
|
||||
use pocketmine\network\mcpe\protocol\StopSoundPacket;
|
||||
use pocketmine\network\mcpe\protocol\StructureBlockUpdatePacket;
|
||||
use pocketmine\network\mcpe\protocol\SubClientLoginPacket;
|
||||
use pocketmine\network\mcpe\protocol\TakeItemEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\TextPacket;
|
||||
use pocketmine\network\mcpe\protocol\TransferPacket;
|
||||
@ -116,7 +128,7 @@ use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateEquipPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateTradePacket;
|
||||
use pocketmine\network\mcpe\protocol\UseItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\WSConnectPacket;
|
||||
|
||||
abstract class NetworkSession{
|
||||
|
||||
@ -202,10 +214,6 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleRemoveBlock(RemoveBlockPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleUpdateBlock(UpdateBlockPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -242,6 +250,10 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleMobEquipment(MobEquipmentPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -258,7 +270,7 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleUseItem(UseItemPacket $packet) : bool{
|
||||
public function handleEntityPickRequest(EntityPickRequestPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -302,14 +314,6 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleDropItem(DropItemPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleInventoryAction(InventoryActionPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleContainerOpen(ContainerOpenPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -318,7 +322,15 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleContainerSetSlot(ContainerSetSlotPacket $packet) : bool{
|
||||
public function handlePlayerHotbar(PlayerHotbarPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleInventoryContent(InventoryContentPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleInventorySlot(InventorySlotPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -326,10 +338,6 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleContainerSetContent(ContainerSetContentPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleCraftingData(CraftingDataPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -338,6 +346,10 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleGuiDataPickItem(GuiDataPickItemPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -406,10 +418,6 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleReplaceItemInSlot(ReplaceItemInSlotPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleGameRulesChanged(GameRulesChangedPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -418,10 +426,6 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleAddItem(AddItemPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleBossEvent(BossEventPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -434,7 +438,7 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleCommandStep(CommandStepPacket $packet) : bool{
|
||||
public function handleCommandRequest(CommandRequestPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -442,6 +446,10 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleCommandOutput(CommandOutputPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleUpdateTrade(UpdateTradePacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -494,4 +502,56 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handlePlayerSkin(PlayerSkinPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleSubClientLogin(SubClientLoginPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleWSConnect(WSConnectPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleSetLastHurtBy(SetLastHurtByPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleBookEdit(BookEditPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleNpcRequest(NpcRequestPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handlePhotoTransfer(PhotoTransferPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleModalFormRequest(ModalFormRequestPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleServerSettingsResponse(ServerSettingsResponsePacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleShowProfile(ShowProfilePacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleSetDefaultGameType(SetDefaultGameTypePacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -33,33 +33,35 @@ use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\BossEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientToServerHandshakePacket;
|
||||
use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket;
|
||||
use pocketmine\network\mcpe\protocol\CommandStepPacket;
|
||||
use pocketmine\network\mcpe\protocol\CommandRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetSlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\DropItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\EntityEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\EntityFallPacket;
|
||||
use pocketmine\network\mcpe\protocol\EntityPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\InteractPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
|
||||
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LoginPacket;
|
||||
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
|
||||
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\RemoveBlockPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
|
||||
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
|
||||
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
|
||||
use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
|
||||
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
|
||||
use pocketmine\network\mcpe\protocol\TextPacket;
|
||||
use pocketmine\network\mcpe\protocol\UseItemPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\Server;
|
||||
|
||||
@ -76,11 +78,6 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
}
|
||||
|
||||
public function handleDataPacket(DataPacket $packet){
|
||||
//TODO: Remove this hack once InteractPacket spam issue is fixed
|
||||
if($packet->buffer === "\x21\x04\x00"){
|
||||
return;
|
||||
}
|
||||
|
||||
$timings = Timings::getReceiveDataPacketTimings($packet);
|
||||
$timings->startTiming();
|
||||
|
||||
@ -111,17 +108,17 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
}
|
||||
|
||||
public function handleText(TextPacket $packet) : bool{
|
||||
return $this->player->handleText($packet);
|
||||
if($packet->type === TextPacket::TYPE_CHAT){
|
||||
return $this->player->chat($packet->message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleMovePlayer(MovePlayerPacket $packet) : bool{
|
||||
return $this->player->handleMovePlayer($packet);
|
||||
}
|
||||
|
||||
public function handleRemoveBlock(RemoveBlockPacket $packet) : bool{
|
||||
return $this->player->handleRemoveBlock($packet);
|
||||
}
|
||||
|
||||
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
|
||||
return $this->player->handleLevelSoundEvent($packet);
|
||||
}
|
||||
@ -130,6 +127,10 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
return $this->player->handleEntityEvent($packet);
|
||||
}
|
||||
|
||||
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
|
||||
return $this->player->handleInventoryTransaction($packet); //TODO
|
||||
}
|
||||
|
||||
public function handleMobEquipment(MobEquipmentPacket $packet) : bool{
|
||||
return $this->player->handleMobEquipment($packet);
|
||||
}
|
||||
@ -146,8 +147,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
return $this->player->handleBlockPickRequest($packet);
|
||||
}
|
||||
|
||||
public function handleUseItem(UseItemPacket $packet) : bool{
|
||||
return $this->player->handleUseItem($packet);
|
||||
public function handleEntityPickRequest(EntityPickRequestPacket $packet) : bool{
|
||||
return false; //TODO
|
||||
}
|
||||
|
||||
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
|
||||
@ -162,20 +163,16 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
return $this->player->handleAnimate($packet);
|
||||
}
|
||||
|
||||
public function handleDropItem(DropItemPacket $packet) : bool{
|
||||
return $this->player->handleDropItem($packet);
|
||||
}
|
||||
|
||||
public function handleContainerClose(ContainerClosePacket $packet) : bool{
|
||||
return $this->player->handleContainerClose($packet);
|
||||
}
|
||||
|
||||
public function handleContainerSetSlot(ContainerSetSlotPacket $packet) : bool{
|
||||
return $this->player->handleContainerSetSlot($packet);
|
||||
public function handlePlayerHotbar(PlayerHotbarPacket $packet) : bool{
|
||||
return $this->player->handlePlayerHotbar($packet);
|
||||
}
|
||||
|
||||
public function handleCraftingEvent(CraftingEventPacket $packet) : bool{
|
||||
return $this->player->handleCraftingEvent($packet);
|
||||
return true; //this is a broken useless packet, so we don't use it
|
||||
}
|
||||
|
||||
public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{
|
||||
@ -218,8 +215,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
return $this->player->handleShowCredits($packet);
|
||||
}
|
||||
|
||||
public function handleCommandStep(CommandStepPacket $packet) : bool{
|
||||
return $this->player->handleCommandStep($packet);
|
||||
public function handleCommandRequest(CommandRequestPacket $packet) : bool{
|
||||
return $this->player->chat($packet->command);
|
||||
}
|
||||
|
||||
public function handleCommandBlockUpdate(CommandBlockUpdatePacket $packet) : bool{
|
||||
@ -229,4 +226,16 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
|
||||
return $this->player->handleResourcePackChunkRequest($packet);
|
||||
}
|
||||
|
||||
public function handlePlayerSkin(PlayerSkinPacket $packet) : bool{
|
||||
return false; //TODO
|
||||
}
|
||||
|
||||
public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{
|
||||
return false; //TODO: GUI stuff
|
||||
}
|
||||
|
||||
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
|
||||
return false; //TODO: GUI stuff
|
||||
}
|
||||
}
|
@ -54,7 +54,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
private $players = [];
|
||||
|
||||
/** @var string[] */
|
||||
private $identifiers;
|
||||
private $identifiers = [];
|
||||
|
||||
/** @var int[] */
|
||||
private $identifiersACK = [];
|
||||
@ -63,9 +63,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
private $interface;
|
||||
|
||||
public function __construct(Server $server){
|
||||
|
||||
$this->server = $server;
|
||||
$this->identifiers = [];
|
||||
|
||||
$this->rakLib = new RakLibServer($this->server->getLogger(), $this->server->getLoader(), $this->server->getPort(), $this->server->getIp() === "" ? "0.0.0.0" : $this->server->getIp(), false);
|
||||
$this->interface = new ServerHandler($this->rakLib, $this);
|
||||
@ -213,13 +211,10 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
if($packet instanceof BatchPacket){
|
||||
if($needACK){
|
||||
$pk = new EncapsulatedPacket();
|
||||
$pk->identifierACK = $this->identifiersACK[$identifier]++;
|
||||
$pk->buffer = $packet->buffer;
|
||||
$pk->reliability = $immediate ? PacketReliability::RELIABLE : PacketReliability::RELIABLE_ORDERED;
|
||||
$pk->orderChannel = 0;
|
||||
|
||||
if($needACK === true){
|
||||
$pk->identifierACK = $this->identifiersACK[$identifier]++;
|
||||
}
|
||||
}else{
|
||||
if(!isset($packet->__encapsulatedPacket)){
|
||||
$packet->__encapsulatedPacket = new CachedEncapsulatedPacket;
|
||||
|
144
src/pocketmine/network/mcpe/VerifyLoginTask.php
Normal file
144
src/pocketmine/network/mcpe/VerifyLoginTask.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\LoginPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\MainLogger;
|
||||
|
||||
class VerifyLoginTask extends AsyncTask{
|
||||
|
||||
const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
|
||||
|
||||
/** @var LoginPacket */
|
||||
private $packet;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* Whether the keychain signatures were validated correctly. This will be set to false if any link in the keychain
|
||||
* has an invalid signature. If false, the keychain might have been tampered with.
|
||||
* The player will always be disconnected if this is false.
|
||||
*/
|
||||
private $valid = true;
|
||||
/**
|
||||
* @var bool
|
||||
* Whether the player is logged into Xbox Live. This is true if any link in the keychain is signed with the Mojang
|
||||
* root public key.
|
||||
*/
|
||||
private $authenticated = false;
|
||||
|
||||
|
||||
public function __construct(Player $player, LoginPacket $packet){
|
||||
$this->storeLocal($player);
|
||||
$this->packet = $packet;
|
||||
}
|
||||
|
||||
public function onRun(){
|
||||
$packet = $this->packet; //Get it in a local variable to make sure it stays unserialized
|
||||
|
||||
$currentKey = null;
|
||||
|
||||
foreach($packet->chainData["chain"] as $jwt){
|
||||
if(!$this->validateToken($jwt, $currentKey)){
|
||||
$this->valid = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$this->validateToken($packet->clientDataJwt, $currentKey)){
|
||||
$this->valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
private function validateToken(string $jwt, ?string &$currentPublicKey) : bool{
|
||||
[$headB64, $payloadB64, $sigB64] = explode('.', $jwt);
|
||||
|
||||
$headers = json_decode(base64_decode(strtr($headB64, '-_', '+/'), true), true);
|
||||
|
||||
if($currentPublicKey === null){ //First link, check that it is self-signed
|
||||
$currentPublicKey = $headers["x5u"];
|
||||
}
|
||||
|
||||
$plainSignature = base64_decode(strtr($sigB64, '-_', '+/'), true);
|
||||
|
||||
//OpenSSL wants a DER-encoded signature, so we extract R and S from the plain signature and crudely serialize it.
|
||||
|
||||
assert(strlen($plainSignature) === 96);
|
||||
|
||||
[$rString, $sString] = str_split($plainSignature, 48);
|
||||
|
||||
$rString = ltrim($rString, "\x00");
|
||||
if(ord($rString{0}) >= 128){ //Would be considered signed, pad it with an extra zero
|
||||
$rString = "\x00" . $rString;
|
||||
}
|
||||
|
||||
$sString = ltrim($sString, "\x00");
|
||||
if(ord($sString{0}) >= 128){ //Would be considered signed, pad it with an extra zero
|
||||
$sString = "\x00" . $sString;
|
||||
}
|
||||
|
||||
//0x02 = Integer ASN.1 tag
|
||||
$sequence = "\x02" . chr(strlen($rString)) . $rString . "\x02" . chr(strlen($sString)) . $sString;
|
||||
//0x30 = Sequence ASN.1 tag
|
||||
$derSignature = "\x30" . chr(strlen($sequence)) . $sequence;
|
||||
|
||||
$v = openssl_verify("$headB64.$payloadB64", $derSignature, "-----BEGIN PUBLIC KEY-----\n" . wordwrap($currentPublicKey, 64, "\n", true) . "\n-----END PUBLIC KEY-----\n", OPENSSL_ALGO_SHA384);
|
||||
if($v !== 1){
|
||||
return false; //bad signature, it might have been tampered with
|
||||
}
|
||||
|
||||
if($currentPublicKey === self::MOJANG_ROOT_PUBLIC_KEY){
|
||||
$this->authenticated = true; //we're signed into xbox live
|
||||
}
|
||||
|
||||
$claims = json_decode(base64_decode(strtr($payloadB64, '-_', '+/'), true), true);
|
||||
|
||||
$time = time();
|
||||
if(isset($claims["nbf"]) and $claims["nbf"] > $time){
|
||||
return false; //token can't be used yet
|
||||
}
|
||||
|
||||
if(isset($claims["exp"]) and $claims["exp"] < $time){
|
||||
return false; //token has expired
|
||||
}
|
||||
|
||||
$currentPublicKey = $claims["identityPublicKey"]; //the next link should be signed with this
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onCompletion(Server $server){
|
||||
/** @var Player $player */
|
||||
$player = $this->fetchLocal($server);
|
||||
if($player->isClosed()){
|
||||
$server->getLogger()->error("Player " . $player->getName() . " was disconnected before their login could be verified");
|
||||
}else{
|
||||
$player->onVerifyCompleted($this->packet, $this->valid, $this->authenticated);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -33,11 +33,11 @@ class AddBehaviorTreePacket extends DataPacket{
|
||||
/** @var string */
|
||||
public $unknownString1;
|
||||
|
||||
public function decodePayload(){
|
||||
protected function decodePayload(){
|
||||
$this->unknownString1 = $this->getString();
|
||||
}
|
||||
|
||||
public function encodePayload(){
|
||||
protected function encodePayload(){
|
||||
$this->putString($this->unknownString1);
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\entity\Attribute;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
|
||||
class AddEntityPacket extends DataPacket{
|
||||
@ -35,26 +36,30 @@ class AddEntityPacket extends DataPacket{
|
||||
public $entityUniqueId = null; //TODO
|
||||
/** @var int */
|
||||
public $entityRuntimeId;
|
||||
/** @var int */
|
||||
public $type;
|
||||
public $x;
|
||||
public $y;
|
||||
public $z;
|
||||
public $speedX = 0.0;
|
||||
public $speedY = 0.0;
|
||||
public $speedZ = 0.0;
|
||||
/** @var Vector3 */
|
||||
public $position;
|
||||
/** @var Vector3|null */
|
||||
public $motion;
|
||||
/** @var float */
|
||||
public $yaw = 0.0;
|
||||
/** @var float */
|
||||
public $pitch = 0.0;
|
||||
|
||||
/** @var Attribute[] */
|
||||
public $attributes = [];
|
||||
/** @var array */
|
||||
public $metadata = [];
|
||||
/** @var array */
|
||||
public $links = [];
|
||||
|
||||
public function decodePayload(){
|
||||
protected function decodePayload(){
|
||||
$this->entityUniqueId = $this->getEntityUniqueId();
|
||||
$this->entityRuntimeId = $this->getEntityRuntimeId();
|
||||
$this->type = $this->getUnsignedVarInt();
|
||||
$this->getVector3f($this->x, $this->y, $this->z);
|
||||
$this->getVector3f($this->speedX, $this->speedY, $this->speedZ);
|
||||
$this->position = $this->getVector3Obj();
|
||||
$this->motion = $this->getVector3Obj();
|
||||
$this->pitch = $this->getLFloat();
|
||||
$this->yaw = $this->getLFloat();
|
||||
|
||||
@ -79,18 +84,16 @@ class AddEntityPacket extends DataPacket{
|
||||
$this->metadata = $this->getEntityMetadata();
|
||||
$linkCount = $this->getUnsignedVarInt();
|
||||
for($i = 0; $i < $linkCount; ++$i){
|
||||
$this->links[$i][0] = $this->getEntityUniqueId();
|
||||
$this->links[$i][1] = $this->getEntityUniqueId();
|
||||
$this->links[$i][2] = $this->getByte();
|
||||
$this->links[] = $this->getEntityLink();
|
||||
}
|
||||
}
|
||||
|
||||
public function encodePayload(){
|
||||
protected function encodePayload(){
|
||||
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
|
||||
$this->putEntityRuntimeId($this->entityRuntimeId);
|
||||
$this->putUnsignedVarInt($this->type);
|
||||
$this->putVector3f($this->x, $this->y, $this->z);
|
||||
$this->putVector3f($this->speedX, $this->speedY, $this->speedZ);
|
||||
$this->putVector3Obj($this->position);
|
||||
$this->putVector3ObjNullable($this->motion);
|
||||
$this->putLFloat($this->pitch);
|
||||
$this->putLFloat($this->yaw);
|
||||
|
||||
@ -105,9 +108,7 @@ class AddEntityPacket extends DataPacket{
|
||||
$this->putEntityMetadata($this->metadata);
|
||||
$this->putUnsignedVarInt(count($this->links));
|
||||
foreach($this->links as $link){
|
||||
$this->putEntityUniqueId($link[0]);
|
||||
$this->putEntityUniqueId($link[1]);
|
||||
$this->putByte($link[2]);
|
||||
$this->putEntityLink($link);
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user