mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-15 22:05:06 +00:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
7bbb2617c8 | |||
2ec65ba799 | |||
3dd7c09351 | |||
afc6e8878d | |||
7eaf1246a0 | |||
645c44ae9c | |||
331b05c681 | |||
3abe80184a | |||
7618b13c6e | |||
351cd4bfd7 | |||
1154c7c1ab | |||
4a3e42f82e | |||
8ecf9717d6 | |||
3ca80b353b | |||
7d172e2211 | |||
e934e2bd26 | |||
3177d19730 | |||
82104a8251 | |||
e9a17374d1 | |||
c82dfef9b0 | |||
688be0a404 | |||
33e6b63fe5 | |||
5d92eddc82 | |||
0324392cd8 | |||
01b0742bd4 | |||
fcb2ccab99 | |||
a45a96b3ee | |||
26df37e6ef | |||
554f96bc24 | |||
0ea3861d43 | |||
a323a5e56d | |||
79caba22a7 | |||
30815bc8a2 | |||
161ab5af16 | |||
ab41594da2 | |||
80d4c11061 | |||
e767796986 | |||
dea7031b86 | |||
7e3d099d5b | |||
5abc7f866a |
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@ -11,3 +11,8 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
|
14
.github/workflows/build-docker-image.yml
vendored
14
.github/workflows/build-docker-image.yml
vendored
@ -12,16 +12,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Clone pmmp/PocketMine-Docker repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: pmmp/PocketMine-Docker
|
||||
fetch-depth: 1
|
||||
@ -46,7 +46,7 @@ jobs:
|
||||
run: echo ::set-output name=NAME::$(echo "${GITHUB_REPOSITORY,,}")
|
||||
|
||||
- name: Build image for tag
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
- name: Build image for major tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
- name: Build image for minor tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -85,7 +85,7 @@ jobs:
|
||||
|
||||
- name: Build image for latest tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
|
10
.github/workflows/draft-release.yml
vendored
10
.github/workflows/draft-release.yml
vendored
@ -13,17 +13,17 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.12.0
|
||||
uses: shivammathur/setup-php@2.18.1
|
||||
with:
|
||||
php-version: 8.0
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
@ -60,7 +60,7 @@ jobs:
|
||||
run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} > build_info.json
|
||||
|
||||
- name: Upload release artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: release_artifacts
|
||||
path: |
|
||||
@ -69,7 +69,7 @@ jobs:
|
||||
${{ github.workspace }}/build_info.json
|
||||
|
||||
- name: Create draft release
|
||||
uses: ncipollo/release-action@v1.8.6
|
||||
uses: ncipollo/release-action@v1.10.0
|
||||
with:
|
||||
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json
|
||||
commit: ${{ github.sha }}
|
||||
|
20
.github/workflows/main.yml
vendored
20
.github/workflows/main.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
||||
php: [8.0.18]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
||||
@ -46,7 +46,7 @@ jobs:
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
@ -72,7 +72,7 @@ jobs:
|
||||
php: [8.0.18]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
||||
@ -84,7 +84,7 @@ jobs:
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
@ -110,7 +110,7 @@ jobs:
|
||||
php: [8.0.18]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
@ -124,7 +124,7 @@ jobs:
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
@ -150,7 +150,7 @@ jobs:
|
||||
php: [8.0.18]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
||||
@ -162,7 +162,7 @@ jobs:
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
@ -192,10 +192,10 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@2.18.1
|
||||
with:
|
||||
php-version: 8.0
|
||||
tools: php-cs-fixer:3.2
|
||||
|
2
.github/workflows/update-updater-api.yml
vendored
2
.github/workflows/update-updater-api.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
- name: Install jq
|
||||
run: sudo apt update && sudo apt install jq -y
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: pmmp/update.pmmp.io
|
||||
ssh-key: ${{ secrets.UPDATE_PMMP_IO_DEPLOY_KEY }}
|
||||
|
@ -43,3 +43,35 @@ Released 16th May 2022.
|
||||
- Fixed server crash when chunks are unloaded during chunk generation callbacks
|
||||
- Fixed dead coral fan items placing coral fans in the wrong orientation.
|
||||
- Fixed max stack size of boat items.
|
||||
|
||||
# 4.3.4
|
||||
Released 22nd May 2022.
|
||||
|
||||
## Fixes
|
||||
- Fixed `difficulty` in `server.properties` having no effect - it's now applied to newly generated worlds.
|
||||
- Note: this setting still doesn't behave the same way as vanilla due to potential disruption to existing servers.
|
||||
- Fixed paintings not working in newly generated worlds and some other cases.
|
||||
- Fixed inventory window switching breaking the inventory UI in some cases (e.g. pressing E while clicking a chest).
|
||||
- Fixed minecart items incorrectly stacking.
|
||||
- Fixed incorrect light levels in translucent blocks at the top of the world.
|
||||
- Fixed teleporting sleeping players causing broken behaviour on the sleeping player's client.
|
||||
- Fixed `EntityExplodeEvent->setYield()` accepting values outside the range 0-100.
|
||||
- Fixed `ExplosionPrimeEvent->setForce()` accepting negative values (later resulting in crashes).
|
||||
|
||||
## Documentation
|
||||
- Updated documentation for the following events:
|
||||
- `CommandEvent`
|
||||
- `EntityDespawnEvent`
|
||||
- `EntityExplodeEvent`
|
||||
- `EntitySpawnEvent`
|
||||
- `ExplosionPrimeEvent`
|
||||
- `InventoryTransactionEvent`
|
||||
- `ItemDespawnEvent`
|
||||
- `ItemSpawnEvent`
|
||||
- `PlayerCommandPreprocessEvent`
|
||||
- `PlayerDropItemEvent`
|
||||
- `PlayerItemHeldEvent`
|
||||
- `PlayerKickEvent`
|
||||
- `PlayerQuitEvent`
|
||||
- `PlayerTransferEvent`
|
||||
- `UpdateNotifyEvent`
|
||||
|
@ -1093,6 +1093,7 @@ class Server{
|
||||
$creationOptions->setGeneratorClass($generatorClass);
|
||||
$creationOptions->setGeneratorOptions($generatorOptions);
|
||||
|
||||
$creationOptions->setDifficulty($this->getDifficulty());
|
||||
if(isset($options["difficulty"]) && is_string($options["difficulty"])){
|
||||
$creationOptions->setDifficulty(World::getDifficultyFromString($options["difficulty"]));
|
||||
}
|
||||
@ -1127,6 +1128,7 @@ class Server{
|
||||
if($convertedSeed !== null){
|
||||
$creationOptions->setSeed($convertedSeed);
|
||||
}
|
||||
$creationOptions->setDifficulty($this->getDifficulty());
|
||||
$this->worldManager->generateWorld($default, $creationOptions);
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.3.3";
|
||||
public const BASE_VERSION = "4.3.4";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
|
@ -23,9 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block\inventory;
|
||||
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\inventory\SimpleInventory;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemIds;
|
||||
use pocketmine\network\mcpe\protocol\BlockEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
use pocketmine\world\Position;
|
||||
@ -50,7 +50,7 @@ class ShulkerBoxInventory extends SimpleInventory implements BlockInventory{
|
||||
}
|
||||
|
||||
public function canAddItem(Item $item) : bool{
|
||||
if($item->getId() === BlockLegacyIds::UNDYED_SHULKER_BOX || $item->getId() === BlockLegacyIds::SHULKER_BOX){
|
||||
if($item->getId() === ItemIds::UNDYED_SHULKER_BOX || $item->getId() === ItemIds::SHULKER_BOX){
|
||||
return false;
|
||||
}
|
||||
return parent::canAddItem($item);
|
||||
|
@ -173,8 +173,6 @@ final class EntityFactory{
|
||||
$this->register(Human::class, function(World $world, CompoundTag $nbt) : Human{
|
||||
return new Human(Helper::parseLocation($nbt, $world), Human::parseSkinNBT($nbt), $nbt);
|
||||
}, ['Human']);
|
||||
|
||||
PaintingMotive::init();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,8 @@ declare(strict_types=1);
|
||||
namespace pocketmine\entity\object;
|
||||
|
||||
class PaintingMotive{
|
||||
private static bool $initialized = false;
|
||||
|
||||
/** @var PaintingMotive[] */
|
||||
protected static $motives = [];
|
||||
|
||||
@ -76,6 +78,9 @@ class PaintingMotive{
|
||||
* @return PaintingMotive[]
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
if(!self::$initialized){
|
||||
self::init();
|
||||
}
|
||||
return self::$motives;
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,9 @@ namespace pocketmine\event\entity;
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
/**
|
||||
* Called when a entity is despawned
|
||||
* Called when an entity is removed from the world. This could be for a variety of reasons, including chunks being
|
||||
* unloaded, entity death, etc.
|
||||
*
|
||||
* @phpstan-extends EntityEvent<Entity>
|
||||
*/
|
||||
class EntityDespawnEvent extends EntityEvent{
|
||||
|
@ -31,7 +31,11 @@ use pocketmine\utils\Utils;
|
||||
use pocketmine\world\Position;
|
||||
|
||||
/**
|
||||
* Called when a entity explodes
|
||||
* Called when an entity explodes, after the explosion's impact has been calculated.
|
||||
* No changes have been made to the world at this stage.
|
||||
*
|
||||
* @see ExplosionPrimeEvent
|
||||
*
|
||||
* @phpstan-extends EntityEvent<Entity>
|
||||
*/
|
||||
class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
||||
@ -47,12 +51,16 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
||||
protected $yield;
|
||||
|
||||
/**
|
||||
* @param Block[] $blocks
|
||||
* @param Block[] $blocks
|
||||
* @param float $yield 0-100
|
||||
*/
|
||||
public function __construct(Entity $entity, Position $position, array $blocks, float $yield){
|
||||
$this->entity = $entity;
|
||||
$this->position = $position;
|
||||
$this->blocks = $blocks;
|
||||
if($yield < 0.0 || $yield > 100.0){
|
||||
throw new \InvalidArgumentException("Yield must be in range 0.0 - 100.0");
|
||||
}
|
||||
$this->yield = $yield;
|
||||
}
|
||||
|
||||
@ -61,6 +69,8 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of blocks destroyed by the explosion.
|
||||
*
|
||||
* @return Block[]
|
||||
*/
|
||||
public function getBlockList() : array{
|
||||
@ -68,6 +78,8 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blocks destroyed by the explosion.
|
||||
*
|
||||
* @param Block[] $blocks
|
||||
*/
|
||||
public function setBlockList(array $blocks) : void{
|
||||
@ -75,11 +87,22 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
||||
$this->blocks = $blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage chance of drops from each block destroyed by the explosion.
|
||||
* @return float 0-100
|
||||
*/
|
||||
public function getYield() : float{
|
||||
return $this->yield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the percentage chance of drops from each block destroyed by the explosion.
|
||||
* @param float $yield 0-100
|
||||
*/
|
||||
public function setYield(float $yield) : void{
|
||||
if($yield < 0.0 || $yield > 100.0){
|
||||
throw new \InvalidArgumentException("Yield must be in range 0.0 - 100.0");
|
||||
}
|
||||
$this->yield = $yield;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ namespace pocketmine\event\entity;
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
/**
|
||||
* Called when a entity is spawned
|
||||
* Called when an entity is added to the world. This might be a new entity or an entity loaded from storage.
|
||||
*
|
||||
* @phpstan-extends EntityEvent<Entity>
|
||||
*/
|
||||
class EntitySpawnEvent extends EntityEvent{
|
||||
|
@ -28,7 +28,11 @@ use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
|
||||
/**
|
||||
* Called when a entity decides to explode
|
||||
* Called when an entity decides to explode, before the explosion's impact is calculated.
|
||||
* This allows changing the force of the explosion and whether it will destroy blocks.
|
||||
*
|
||||
* @see EntityExplodeEvent
|
||||
*
|
||||
* @phpstan-extends EntityEvent<Entity>
|
||||
*/
|
||||
class ExplosionPrimeEvent extends EntityEvent implements Cancellable{
|
||||
@ -40,6 +44,9 @@ class ExplosionPrimeEvent extends EntityEvent implements Cancellable{
|
||||
private $blockBreaking;
|
||||
|
||||
public function __construct(Entity $entity, float $force){
|
||||
if($force <= 0){
|
||||
throw new \InvalidArgumentException("Explosion radius must be positive");
|
||||
}
|
||||
$this->entity = $entity;
|
||||
$this->force = $force;
|
||||
$this->blockBreaking = true;
|
||||
@ -50,6 +57,9 @@ class ExplosionPrimeEvent extends EntityEvent implements Cancellable{
|
||||
}
|
||||
|
||||
public function setForce(float $force) : void{
|
||||
if($force <= 0){
|
||||
throw new \InvalidArgumentException("Explosion radius must be positive");
|
||||
}
|
||||
$this->force = $force;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,9 @@ use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
|
||||
/**
|
||||
* Called when a dropped item tries to despawn due to its despawn delay running out.
|
||||
* Cancelling the event will reset the despawn delay to default (5 minutes).
|
||||
*
|
||||
* @phpstan-extends EntityEvent<ItemEntity>
|
||||
*/
|
||||
class ItemDespawnEvent extends EntityEvent implements Cancellable{
|
||||
|
@ -26,6 +26,15 @@ namespace pocketmine\event\entity;
|
||||
use pocketmine\entity\object\ItemEntity;
|
||||
|
||||
/**
|
||||
* Called when an item is spawned or loaded.
|
||||
*
|
||||
* Some possible reasons include:
|
||||
* - item is loaded from disk
|
||||
* - player dropping an item
|
||||
* - block drops
|
||||
* - loot of a player or entity
|
||||
*
|
||||
* @see PlayerDropItemEvent
|
||||
* @phpstan-extends EntityEvent<ItemEntity>
|
||||
*/
|
||||
class ItemSpawnEvent extends EntityEvent{
|
||||
|
@ -29,8 +29,17 @@ use pocketmine\event\Event;
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
|
||||
/**
|
||||
* Called when there is a transaction between two Inventory objects.
|
||||
* The source of this can be a Player, entities, mobs, or even hoppers in the future!
|
||||
* Called when a player performs actions involving items in inventories.
|
||||
*
|
||||
* This may involve multiple inventories, and may include actions such as:
|
||||
* - moving items from one slot to another
|
||||
* - splitting itemstacks
|
||||
* - dragging itemstacks across inventory slots (slot painting)
|
||||
* - dropping an item on the ground
|
||||
* - taking an item from the creative inventory menu
|
||||
* - destroying (trashing) an item
|
||||
*
|
||||
* @see https://doc.pmmp.io/en/rtfd/developer-reference/inventory-transactions.html for more information on inventory transactions
|
||||
*/
|
||||
class InventoryTransactionEvent extends Event implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
@ -28,12 +28,10 @@ use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
/**
|
||||
* Called when a player runs a command or chats, early in the process
|
||||
* Called when a player runs a command or chats, before it is processed.
|
||||
*
|
||||
* You don't want to use this except for a few cases like logging commands,
|
||||
* blocking commands on certain places, or applying modifiers.
|
||||
*
|
||||
* The message contains a slash at the start
|
||||
* If the message is prefixed with a / (forward slash), it will be interpreted as a command.
|
||||
* Otherwise, it will be broadcasted as a chat message.
|
||||
*/
|
||||
class PlayerCommandPreprocessEvent extends PlayerEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
@ -29,7 +29,7 @@ use pocketmine\item\Item;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
/**
|
||||
* Called when a player tries to drop an item from its hotbar
|
||||
* Called when a player tries to drop an item
|
||||
*/
|
||||
class PlayerDropItemEvent extends PlayerEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
@ -28,6 +28,11 @@ use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
/**
|
||||
* Called when a player's held item changes.
|
||||
* This could be because they selected a different hotbar slot, or because the item in the selected hotbar slot was
|
||||
* changed.
|
||||
*/
|
||||
class PlayerItemHeldEvent extends PlayerEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
|
@ -29,7 +29,7 @@ use pocketmine\lang\Translatable;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
/**
|
||||
* Called when a player leaves the server
|
||||
* Called when a player is kicked (forcibly disconnected) from the server, e.g. if an operator used /kick.
|
||||
*/
|
||||
class PlayerKickEvent extends PlayerEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
@ -46,18 +46,33 @@ class PlayerKickEvent extends PlayerEvent implements Cancellable{
|
||||
$this->reason = $reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message shown on the kicked player's disconnection screen.
|
||||
* This message is also displayed in the console and server log.
|
||||
*/
|
||||
public function setReason(string $reason) : void{
|
||||
$this->reason = $reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message shown on the kicked player's disconnection screen.
|
||||
* This message is also displayed in the console and server log.
|
||||
* When kicked by the /kick command, the default is something like "Kicked by admin.".
|
||||
*/
|
||||
public function getReason() : string{
|
||||
return $this->reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the quit message broadcasted to other players.
|
||||
*/
|
||||
public function setQuitMessage(Translatable|string $quitMessage) : void{
|
||||
$this->quitMessage = $quitMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quit message broadcasted to other players, e.g. "Steve left the game".
|
||||
*/
|
||||
public function getQuitMessage() : Translatable|string{
|
||||
return $this->quitMessage;
|
||||
}
|
||||
|
@ -27,7 +27,14 @@ use pocketmine\lang\Translatable;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
/**
|
||||
* Called when a player leaves the server
|
||||
* Called when a player disconnects from the server for any reason.
|
||||
*
|
||||
* Some possible reasons include:
|
||||
* - being kicked by an operator
|
||||
* - disconnecting from the game
|
||||
* - timeout due to network connectivity issues
|
||||
*
|
||||
* @see PlayerKickEvent
|
||||
*/
|
||||
class PlayerQuitEvent extends PlayerEvent{
|
||||
|
||||
@ -42,14 +49,23 @@ class PlayerQuitEvent extends PlayerEvent{
|
||||
$this->quitReason = $quitReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the quit message broadcasted to other players.
|
||||
*/
|
||||
public function setQuitMessage(Translatable|string $quitMessage) : void{
|
||||
$this->quitMessage = $quitMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quit message broadcasted to other players, e.g. "Steve left the game".
|
||||
*/
|
||||
public function getQuitMessage() : Translatable|string{
|
||||
return $this->quitMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the disconnect reason shown in the server log and on the console.
|
||||
*/
|
||||
public function getQuitReason() : string{
|
||||
return $this->quitReason;
|
||||
}
|
||||
|
@ -27,6 +27,9 @@ use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
/**
|
||||
* Called when a player attempts to be transferred to another server, e.g. by using /transferserver.
|
||||
*/
|
||||
class PlayerTransferEvent extends PlayerEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
@ -44,26 +47,44 @@ class PlayerTransferEvent extends PlayerEvent implements Cancellable{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination server address. This could be an IP or a domain name.
|
||||
*/
|
||||
public function getAddress() : string{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination server address.
|
||||
*/
|
||||
public function setAddress(string $address) : void{
|
||||
$this->address = $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination server port.
|
||||
*/
|
||||
public function getPort() : int{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination server port.
|
||||
*/
|
||||
public function setPort(int $port) : void{
|
||||
$this->port = $port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the disconnect reason shown in the server log and on the console.
|
||||
*/
|
||||
public function getMessage() : string{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the disconnect reason shown in the server log and on the console.
|
||||
*/
|
||||
public function setMessage(string $message) : void{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
@ -28,12 +28,18 @@ use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
|
||||
/**
|
||||
* Called when any CommandSender runs a command, early in the process
|
||||
* Called when any CommandSender runs a command, before it is parsed.
|
||||
*
|
||||
* You don't want to use this except for a few cases like logging commands,
|
||||
* blocking commands on certain places, or applying modifiers.
|
||||
* This can be used for logging commands, or preprocessing the command string to add custom features (e.g. selectors).
|
||||
*
|
||||
* The message DOES NOT contain a slash at the start
|
||||
* WARNING: DO NOT use this to block commands. Many commands have aliases.
|
||||
* For example, /version can also be invoked using /ver or /about.
|
||||
* To prevent command senders from using certain commands, deny them permission to use the commands you don't want them
|
||||
* to have access to.
|
||||
*
|
||||
* @see Permissible::addAttachment()
|
||||
*
|
||||
* The message DOES NOT begin with a slash.
|
||||
*/
|
||||
class CommandEvent extends ServerEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
@ -26,7 +26,7 @@ namespace pocketmine\event\server;
|
||||
use pocketmine\updater\UpdateChecker;
|
||||
|
||||
/**
|
||||
* Called when the AutoUpdater receives notification of an available PocketMine-MP update.
|
||||
* Called when the update checker receives notification of an available PocketMine-MP update.
|
||||
* Plugins may use this event to perform actions when an update notification is received.
|
||||
*/
|
||||
class UpdateNotifyEvent extends ServerEvent{
|
||||
|
@ -27,7 +27,7 @@ use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\World;
|
||||
|
||||
/**
|
||||
* Called when a Chunk is loaded
|
||||
* Called when a Chunk is loaded or newly created by the world generator.
|
||||
*/
|
||||
class ChunkLoadEvent extends ChunkEvent{
|
||||
/** @var bool */
|
||||
@ -38,6 +38,10 @@ class ChunkLoadEvent extends ChunkEvent{
|
||||
$this->newChunk = $newChunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the chunk is newly generated.
|
||||
* If false, the chunk was loaded from storage.
|
||||
*/
|
||||
public function isNewChunk() : bool{
|
||||
return $this->newChunk;
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ declare(strict_types=1);
|
||||
namespace pocketmine\event\world;
|
||||
|
||||
/**
|
||||
* Called when a Chunk is populated (after receiving it on the main thread)
|
||||
* Called when a Chunk is fully populated by the world generator.
|
||||
* This means that the terrain has been generated, and all artifacts (e.g. trees, grass, ponds, etc.) have been placed.
|
||||
*/
|
||||
class ChunkPopulateEvent extends ChunkEvent{
|
||||
|
||||
|
@ -27,7 +27,7 @@ use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
|
||||
/**
|
||||
* Called when a Chunk is unloaded
|
||||
* Called when a Chunk is unloaded from memory.
|
||||
*/
|
||||
class ChunkUnloadEvent extends ChunkEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\event\world;
|
||||
|
||||
/**
|
||||
* Called when a World is initializing
|
||||
* Called when a new world is created/generated.
|
||||
*/
|
||||
class WorldInitEvent extends WorldEvent{
|
||||
|
||||
|
@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\event\world;
|
||||
|
||||
/**
|
||||
* Called when a World is loaded
|
||||
* Called when a world is loaded or newly created/generated.
|
||||
*/
|
||||
class WorldLoadEvent extends WorldEvent{
|
||||
|
||||
|
@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\event\world;
|
||||
|
||||
/**
|
||||
* Called when a World is saved
|
||||
* Called when a world is saved. Saving may be triggered manually (e.g. via commands) or automatically (autosave).
|
||||
*/
|
||||
class WorldSaveEvent extends WorldEvent{
|
||||
|
||||
|
@ -27,7 +27,7 @@ use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
|
||||
/**
|
||||
* Called when a World is unloaded
|
||||
* Called when a world is unloaded from memory.
|
||||
*/
|
||||
class WorldUnloadEvent extends WorldEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
@ -148,7 +148,7 @@ class InventoryTransaction{
|
||||
try{
|
||||
$action->validate($this->source);
|
||||
}catch(TransactionValidationException $e){
|
||||
throw new TransactionValidationException(get_class($action) . ": " . $e->getMessage(), 0, $e);
|
||||
throw new TransactionValidationException(get_class($action) . "#" . spl_object_id($action) . ": " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
if(!$action->getSourceItem()->isNull()){
|
||||
|
@ -25,5 +25,9 @@ namespace pocketmine\item;
|
||||
|
||||
class Minecart extends Item{
|
||||
|
||||
public function getMaxStackSize() : int{
|
||||
return 1;
|
||||
}
|
||||
|
||||
//TODO
|
||||
}
|
||||
|
@ -67,13 +67,6 @@ use function spl_object_id;
|
||||
* @phpstan-type ContainerOpenClosure \Closure(int $id, Inventory $inventory) : (list<ClientboundPacket>|null)
|
||||
*/
|
||||
class InventoryManager{
|
||||
|
||||
//TODO: HACK!
|
||||
//these IDs are used for 1.16 to restore 1.14ish crafting & inventory behaviour; since they don't seem to have any
|
||||
//effect on the behaviour of inventory transactions I don't currently plan to integrate these into the main system.
|
||||
private const RESERVED_WINDOW_ID_RANGE_START = ContainerIds::LAST - 10;
|
||||
private const HARDCODED_INVENTORY_WINDOW_ID = self::RESERVED_WINDOW_ID_RANGE_START + 2;
|
||||
|
||||
/** @var Player */
|
||||
private $player;
|
||||
/** @var NetworkSession */
|
||||
@ -84,15 +77,6 @@ class InventoryManager{
|
||||
/** @var int */
|
||||
private $lastInventoryNetworkId = ContainerIds::FIRST;
|
||||
|
||||
/**
|
||||
* TODO: HACK! This tracks GUIs for inventories that the server considers "always open" so that the client can't
|
||||
* open them twice. (1.16 hack)
|
||||
* @var true[]
|
||||
* @phpstan-var array<int, true>
|
||||
* @internal
|
||||
*/
|
||||
protected $openHardcodedWindows = [];
|
||||
|
||||
/**
|
||||
* @var Item[][]
|
||||
* @phpstan-var array<int, array<int, Item>>
|
||||
@ -104,6 +88,10 @@ class InventoryManager{
|
||||
/** @phpstan-var ObjectSet<ContainerOpenClosure> */
|
||||
private ObjectSet $containerOpenCallbacks;
|
||||
|
||||
private ?int $pendingCloseWindowId = null;
|
||||
/** @phpstan-var \Closure() : void */
|
||||
private ?\Closure $pendingOpenWindowCallback = null;
|
||||
|
||||
public function __construct(Player $player, NetworkSession $session){
|
||||
$this->player = $player;
|
||||
$this->session = $session;
|
||||
@ -125,6 +113,12 @@ class InventoryManager{
|
||||
$this->windowMap[$id] = $inventory;
|
||||
}
|
||||
|
||||
private function addDynamic(Inventory $inventory) : int{
|
||||
$this->lastInventoryNetworkId = max(ContainerIds::FIRST, ($this->lastInventoryNetworkId + 1) % ContainerIds::LAST);
|
||||
$this->add($this->lastInventoryNetworkId, $inventory);
|
||||
return $this->lastInventoryNetworkId;
|
||||
}
|
||||
|
||||
private function remove(int $id) : void{
|
||||
unset($this->windowMap[$id], $this->initiatedSlotChanges[$id]);
|
||||
}
|
||||
@ -168,21 +162,47 @@ class InventoryManager{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the server initiates a window close, it does so by sending a ContainerClose to the client, which causes the
|
||||
* client to behave as if it initiated the close itself. It responds by sending a ContainerClose back to the server,
|
||||
* which the server is then expected to respond to.
|
||||
*
|
||||
* Sending the client a new window before sending this final response creates buggy behaviour on the client, which
|
||||
* is problematic when switching windows. Therefore, we defer sending any new windows until after the client
|
||||
* responds to our window close instruction, so that we can complete the window handshake correctly.
|
||||
*
|
||||
* This is a pile of complicated garbage that only exists because Mojang overengineered the process of opening and
|
||||
* closing inventory windows.
|
||||
*
|
||||
* @phpstan-param \Closure() : void $func
|
||||
*/
|
||||
private function openWindowDeferred(\Closure $func) : void{
|
||||
if($this->pendingCloseWindowId !== null){
|
||||
$this->session->getLogger()->debug("Deferring opening of new window, waiting for close ack of window $this->pendingCloseWindowId");
|
||||
$this->pendingOpenWindowCallback = $func;
|
||||
}else{
|
||||
$func();
|
||||
}
|
||||
}
|
||||
|
||||
public function onCurrentWindowChange(Inventory $inventory) : void{
|
||||
$this->onCurrentWindowRemove();
|
||||
$this->add($this->lastInventoryNetworkId = max(ContainerIds::FIRST, ($this->lastInventoryNetworkId + 1) % self::RESERVED_WINDOW_ID_RANGE_START), $inventory);
|
||||
|
||||
foreach($this->containerOpenCallbacks as $callback){
|
||||
$pks = $callback($this->lastInventoryNetworkId, $inventory);
|
||||
if($pks !== null){
|
||||
foreach($pks as $pk){
|
||||
$this->session->sendDataPacket($pk);
|
||||
$this->openWindowDeferred(function() use ($inventory) : void{
|
||||
$windowId = $this->addDynamic($inventory);
|
||||
|
||||
foreach($this->containerOpenCallbacks as $callback){
|
||||
$pks = $callback($windowId, $inventory);
|
||||
if($pks !== null){
|
||||
foreach($pks as $pk){
|
||||
$this->session->sendDataPacket($pk);
|
||||
}
|
||||
$this->syncContents($inventory);
|
||||
return;
|
||||
}
|
||||
$this->syncContents($inventory);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new \LogicException("Unsupported inventory type");
|
||||
throw new \LogicException("Unsupported inventory type");
|
||||
});
|
||||
}
|
||||
|
||||
/** @phpstan-return ObjectSet<ContainerOpenClosure> */
|
||||
@ -218,31 +238,32 @@ class InventoryManager{
|
||||
}
|
||||
|
||||
public function onClientOpenMainInventory() : void{
|
||||
$id = self::HARDCODED_INVENTORY_WINDOW_ID;
|
||||
if(!isset($this->openHardcodedWindows[$id])){
|
||||
//TODO: HACK! this restores 1.14ish behaviour, but this should be able to be listened to and
|
||||
//controlled by plugins. However, the player is always a subscriber to their own inventory so it
|
||||
//doesn't integrate well with the regular container system right now.
|
||||
$this->openHardcodedWindows[$id] = true;
|
||||
$this->onCurrentWindowRemove();
|
||||
|
||||
$this->openWindowDeferred(function() : void{
|
||||
$windowId = $this->addDynamic($this->player->getInventory());
|
||||
|
||||
$this->session->sendDataPacket(ContainerOpenPacket::entityInv(
|
||||
InventoryManager::HARDCODED_INVENTORY_WINDOW_ID,
|
||||
$windowId,
|
||||
WindowTypes::INVENTORY,
|
||||
$this->player->getId()
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function onCurrentWindowRemove() : void{
|
||||
if(isset($this->windowMap[$this->lastInventoryNetworkId])){
|
||||
$this->remove($this->lastInventoryNetworkId);
|
||||
$this->session->sendDataPacket(ContainerClosePacket::create($this->lastInventoryNetworkId, true));
|
||||
if($this->pendingCloseWindowId !== null){
|
||||
throw new AssumptionFailedError("We should not have opened a new window while a window was waiting to be closed");
|
||||
}
|
||||
$this->pendingCloseWindowId = $this->lastInventoryNetworkId;
|
||||
}
|
||||
}
|
||||
|
||||
public function onClientRemoveWindow(int $id) : void{
|
||||
if(isset($this->openHardcodedWindows[$id])){
|
||||
unset($this->openHardcodedWindows[$id]);
|
||||
}elseif($id === $this->lastInventoryNetworkId){
|
||||
if($id === $this->lastInventoryNetworkId){
|
||||
$this->remove($id);
|
||||
$this->player->removeCurrentWindow();
|
||||
}else{
|
||||
@ -252,6 +273,13 @@ class InventoryManager{
|
||||
//Always send this, even if no window matches. If we told the client to close a window, it will behave as if it
|
||||
//initiated the close and expect an ack.
|
||||
$this->session->sendDataPacket(ContainerClosePacket::create($id, false));
|
||||
|
||||
if($this->pendingOpenWindowCallback !== null && $id === $this->pendingCloseWindowId){
|
||||
$this->session->getLogger()->debug("Opening deferred window after close ack of window $id");
|
||||
$this->pendingCloseWindowId = null;
|
||||
($this->pendingOpenWindowCallback)();
|
||||
$this->pendingOpenWindowCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function syncSlot(Inventory $inventory, int $slot) : void{
|
||||
|
@ -723,6 +723,7 @@ class NetworkSession{
|
||||
}
|
||||
|
||||
public function onServerRespawn() : void{
|
||||
$this->syncAttributes($this->player, $this->player->getAttributeMap()->getAll());
|
||||
$this->player->sendData(null);
|
||||
|
||||
$this->syncAdventureSettings($this->player);
|
||||
|
@ -2373,6 +2373,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
if(parent::teleport($pos, $yaw, $pitch)){
|
||||
|
||||
$this->removeCurrentWindow();
|
||||
$this->stopSleep();
|
||||
|
||||
$this->sendPosition($this->location, $this->location->yaw, $this->location->pitch, MovePlayerPacket::MODE_TELEPORT);
|
||||
$this->broadcastMovement(true);
|
||||
@ -2384,7 +2385,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
if($this->spawnChunkLoadCount !== -1){
|
||||
$this->spawnChunkLoadCount = 0;
|
||||
}
|
||||
$this->stopSleep();
|
||||
$this->blockBreakHandler = null;
|
||||
|
||||
//TODO: workaround for player last pos not getting updated
|
||||
@ -2481,7 +2481,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO: client side race condition here makes the opening work incorrectly
|
||||
$this->removeCurrentWindow();
|
||||
|
||||
if(($inventoryManager = $this->getNetworkSession()->getInvManager()) === null){
|
||||
|
@ -42,6 +42,7 @@ use pocketmine\world\utils\SubChunkExplorer;
|
||||
use pocketmine\world\utils\SubChunkExplorerStatus;
|
||||
use function ceil;
|
||||
use function floor;
|
||||
use function min;
|
||||
use function mt_rand;
|
||||
use function sqrt;
|
||||
|
||||
@ -95,9 +96,6 @@ class Explosion{
|
||||
|
||||
$blockFactory = BlockFactory::getInstance();
|
||||
|
||||
$currentChunk = null;
|
||||
$currentSubChunk = null;
|
||||
|
||||
$mRays = $this->rays - 1;
|
||||
for($i = 0; $i < $this->rays; ++$i){
|
||||
for($j = 0; $j < $this->rays; ++$j){
|
||||
@ -156,10 +154,8 @@ class Explosion{
|
||||
* and creating sounds and particles.
|
||||
*/
|
||||
public function explodeB() : bool{
|
||||
$updateBlocks = [];
|
||||
|
||||
$source = (new Vector3($this->source->x, $this->source->y, $this->source->z))->floor();
|
||||
$yield = (1 / $this->size) * 100;
|
||||
$yield = min(100, (1 / $this->size) * 100);
|
||||
|
||||
if($this->what instanceof Entity){
|
||||
$ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield);
|
||||
|
@ -114,6 +114,9 @@ abstract class LightUpdate{
|
||||
$context->removalQueue->enqueue([$x, $y, $z, $oldLevel]);
|
||||
}
|
||||
}
|
||||
}elseif($this->getEffectiveLight($x, $y, $z) > 0){ //outside the chunk (e.g. virtual sky light from y=256)
|
||||
$context->spreadVisited[$blockHash] = true;
|
||||
$context->spreadQueue->enqueue([$x, $y, $z]);
|
||||
}
|
||||
}
|
||||
return $context;
|
||||
|
@ -610,6 +610,11 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getAttributeMap\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getLanguage\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#"
|
||||
count: 1
|
||||
@ -665,6 +670,11 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAttributes\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$for of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAdventureSettings\\(\\) expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#"
|
||||
count: 2
|
||||
@ -945,11 +955,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/utils/Utils.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$array of function array_map expects array, mixed given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/utils/Utils.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getFullBlock\\(\\) on pocketmine\\\\world\\\\format\\\\SubChunk\\|null\\.$#"
|
||||
count: 1
|
||||
|
Reference in New Issue
Block a user