mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-20 16:00:20 +00:00
Merge branch 'next-minor' into next-major
This commit is contained in:
commit
9295afe8b9
@ -1 +1 @@
|
||||
Subproject commit cf79c0172283a0a26f2c3695af260d100e0fdabd
|
||||
Subproject commit 50062b5861235fbb9fe6e5d5b30684f4cb464470
|
@ -55,7 +55,7 @@
|
||||
"webmozart/path-util": "^2.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.8.5",
|
||||
"phpstan/phpstan": "1.8.6",
|
||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||
"phpunit/phpunit": "^9.2"
|
||||
|
28
composer.lock
generated
28
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "284e89a86c5b7fdb4d97ec5516666b36",
|
||||
"content-hash": "5a893a5cf9cd6e4c8f70b332cbde4a2d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -1560,16 +1560,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.8.5",
|
||||
"version": "1.8.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "f6598a5ff12ca4499a836815e08b4d77a2ddeb20"
|
||||
"reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/f6598a5ff12ca4499a836815e08b4d77a2ddeb20",
|
||||
"reference": "f6598a5ff12ca4499a836815e08b4d77a2ddeb20",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/c386ab2741e64cc9e21729f891b28b2b10fe6618",
|
||||
"reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1599,7 +1599,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||
"source": "https://github.com/phpstan/phpstan/tree/1.8.5"
|
||||
"source": "https://github.com/phpstan/phpstan/tree/1.8.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1615,7 +1615,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-09-07T16:05:32+00:00"
|
||||
"time": "2022-09-23T09:54:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
@ -1671,21 +1671,21 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "1.4.3",
|
||||
"version": "1.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "431b3d6e8040075de196680cd5bc95735987b4ae"
|
||||
"reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/431b3d6e8040075de196680cd5bc95735987b4ae",
|
||||
"reference": "431b3d6e8040075de196680cd5bc95735987b4ae",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6",
|
||||
"reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"phpstan/phpstan": "^1.8.3"
|
||||
"phpstan/phpstan": "^1.8.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"nikic/php-parser": "^4.13.0",
|
||||
@ -1713,9 +1713,9 @@
|
||||
"description": "Extra strict and opinionated rules for PHPStan",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.3"
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.4"
|
||||
},
|
||||
"time": "2022-08-26T15:05:46+00:00"
|
||||
"time": "2022-09-21T11:38:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
|
@ -76,21 +76,38 @@ class Block{
|
||||
$this->position = clone $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing information about how to identify and store this block type, such as its legacy
|
||||
* numeric ID(s), tile type (if any), and legacy variant metadata.
|
||||
*/
|
||||
public function getIdInfo() : BlockIdentifier{
|
||||
return $this->idInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the printable English name of the block.
|
||||
*/
|
||||
public function getName() : string{
|
||||
return $this->fallbackName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Returns the full blockstate ID of this block. This is a compact way of representing a blockstate used to store
|
||||
* blocks in chunks at runtime.
|
||||
*
|
||||
* This ID can be used to later obtain a copy of this block using {@link BlockFactory::fromStateId()}.
|
||||
*/
|
||||
public function getStateId() : int{
|
||||
return ($this->getTypeId() << self::INTERNAL_STATE_DATA_BITS) | $this->computeStateData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block as an item.
|
||||
* State information such as facing, powered/unpowered, open/closed, etc., is discarded.
|
||||
* Type information such as colour, wood type, etc. is preserved.
|
||||
*/
|
||||
public function asItem() : Item{
|
||||
return new ItemBlock($this);
|
||||
}
|
||||
@ -191,6 +208,12 @@ class Block{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes information about the block into the world. This writes the blockstate ID into the chunk, and creates
|
||||
* and/or removes tiles as necessary.
|
||||
*
|
||||
* Note: Do not call this directly. Pass the block to {@link World::setBlock()} instead.
|
||||
*/
|
||||
public function writeStateToWorld() : void{
|
||||
$world = $this->position->getWorld();
|
||||
$world->getOrLoadChunkAtPosition($this->position)->setFullBlock($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
|
||||
@ -216,7 +239,7 @@ class Block{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a type ID that identifies this type of block. This does not include information like facing, colour,
|
||||
* Returns a type ID that identifies this type of block. This does not include information like facing, open/closed,
|
||||
* powered/unpowered, etc.
|
||||
*/
|
||||
public function getTypeId() : int{
|
||||
@ -263,22 +286,36 @@ class Block{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this block can be replaced by another block placed in the same position.
|
||||
*/
|
||||
public function canBeReplaced() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this block can replace the given block in the given placement conditions.
|
||||
* This is used to allow slabs of the same type to combine into double slabs.
|
||||
*/
|
||||
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
|
||||
return $blockReplace->canBeReplaced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the Block, using block space and block target, and side. Returns if the block has been placed.
|
||||
* Generates a block transaction to set all blocks affected by placing this block. Usually this is just the block
|
||||
* itself, but may be multiple blocks in some cases (such as doors).
|
||||
*
|
||||
* @return bool whether the placement should go ahead
|
||||
*/
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$tx->addBlock($blockReplace->position, $this);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called immediately after the block has been placed in the world. Since placement uses a block transaction, some
|
||||
* things may not be possible until after the transaction has been executed.
|
||||
*/
|
||||
public function onPostPlace() : void{
|
||||
|
||||
}
|
||||
@ -320,7 +357,7 @@ class Block{
|
||||
|
||||
/**
|
||||
* Called when this block is randomly updated due to chunk ticking.
|
||||
* WARNING: This will not be called if ticksRandomly() does not return true!
|
||||
* WARNING: This will not be called if {@link Block::ticksRandomly()} does not return true!
|
||||
*/
|
||||
public function onRandomTick() : void{
|
||||
|
||||
@ -343,8 +380,7 @@ class Block{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this block is attacked (left-clicked). This is called when a player left-clicks the block to try and
|
||||
* start to break it in survival mode.
|
||||
* Called when this block is attacked (left-clicked) by a player attempting to start breaking it in survival.
|
||||
*
|
||||
* @return bool if an action took place, prevents starting to break the block if true.
|
||||
*/
|
||||
@ -352,11 +388,19 @@ class Block{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a multiplier applied to the velocity of entities moving on top of this block. A higher value will make
|
||||
* the block more slippery (like ice).
|
||||
*
|
||||
* @return float 0.0-1.0
|
||||
*/
|
||||
public function getFrictionFactor() : float{
|
||||
return 0.6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of light emitted by this block.
|
||||
*
|
||||
* @return int 0-15
|
||||
*/
|
||||
public function getLightLevel() : int{
|
||||
@ -399,10 +443,6 @@ class Block{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasEntityCollision() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether entities can climb up this block.
|
||||
*/
|
||||
@ -410,10 +450,6 @@ class Block{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function addVelocityToEntity(Entity $entity) : ?Vector3{
|
||||
return null;
|
||||
}
|
||||
|
||||
final public function getPosition() : Position{
|
||||
return $this->position;
|
||||
}
|
||||
@ -496,6 +532,7 @@ class Block{
|
||||
|
||||
/**
|
||||
* Returns the item that players will equip when middle-clicking on this block.
|
||||
* If addUserData is true, additional data may be added, such as banner patterns, chest contents, etc.
|
||||
*/
|
||||
public function getPickedItem(bool $addUserData = false) : Item{
|
||||
$item = $this->asItem();
|
||||
@ -623,7 +660,7 @@ class Block{
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for collision against an AxisAlignedBB
|
||||
* Returns whether any of the block's collision boxes intersect with the given AxisAlignedBB.
|
||||
*/
|
||||
public function collidesWithBB(AxisAlignedBB $bb) : bool{
|
||||
foreach($this->getCollisionBoxes() as $bb2){
|
||||
@ -635,10 +672,21 @@ class Block{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the block has actions to be executed when an entity enters its cell (full cube space).
|
||||
*
|
||||
* @see Block::onEntityInside()
|
||||
*/
|
||||
public function hasEntityCollision() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an entity's bounding box clips inside this block's cell. Note that the entity may not be intersecting
|
||||
* with the collision box or bounding box.
|
||||
*
|
||||
* WARNING: This will not be called if {@link Block::hasEntityCollision()} returns false.
|
||||
*
|
||||
* @return bool Whether the block is still the same after the intersection. If it changed (e.g. due to an explosive
|
||||
* being ignited), this should return false.
|
||||
*/
|
||||
@ -646,6 +694,19 @@ class Block{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a direction vector describing which way an entity intersecting this block should be pushed.
|
||||
* This is used by liquids to push entities in liquid currents.
|
||||
*
|
||||
* The returned vector is summed with vectors from every other block the entity is intersecting, and normalized to
|
||||
* produce a final direction vector.
|
||||
*
|
||||
* WARNING: This will not be called if {@link Block::hasEntityCollision()} does not return true!
|
||||
*/
|
||||
public function addVelocityToEntity(Entity $entity) : ?Vector3{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an entity lands on this block (usually due to falling).
|
||||
* @return float|null The new vertical velocity of the entity, or null if unchanged.
|
||||
@ -662,6 +723,13 @@ class Block{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of collision bounding boxes for this block.
|
||||
* These are used for:
|
||||
* - entity movement collision checks (to ensure entities can't clip through blocks)
|
||||
* - projectile flight paths
|
||||
* - block placement (to ensure the player can't place blocks inside itself or another entity)
|
||||
* - anti-cheat checks in plugins
|
||||
*
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
final public function getCollisionBoxes() : array{
|
||||
@ -692,6 +760,10 @@ class Block{
|
||||
return [AxisAlignedBB::one()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of support that the block can provide on the given face. This is used to determine whether
|
||||
* blocks placed on the given face can be supported by this block.
|
||||
*/
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::FULL();
|
||||
}
|
||||
@ -702,6 +774,10 @@ class Block{
|
||||
return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a ray trace along the line between the two positions using the block's collision boxes.
|
||||
* Returns the intersection point closest to pos1, or null if no intersection occurred.
|
||||
*/
|
||||
public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
|
||||
$bbs = $this->getCollisionBoxes();
|
||||
if(count($bbs) === 0){
|
||||
|
@ -26,6 +26,10 @@ namespace pocketmine\block;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
|
||||
/**
|
||||
* "Flowable" blocks are destroyed if water flows into the same space as the block. These blocks usually don't have any
|
||||
* collision boxes, and can't provide support for other blocks.
|
||||
*/
|
||||
abstract class Flowable extends Transparent{
|
||||
|
||||
public function canBeFlowedInto() : bool{
|
||||
|
@ -23,6 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
/**
|
||||
* Opaque blocks do not allow light to pass through. They are usually collidable full-cube blocks.
|
||||
* Most blocks in Minecraft fall into this category.
|
||||
*/
|
||||
class Opaque extends Block{
|
||||
|
||||
public function isSolid() : bool{
|
||||
|
@ -29,6 +29,9 @@ use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Thin blocks behave like glass panes. They connect to full-cube blocks horizontally adjacent to them if possible.
|
||||
*/
|
||||
class Thin extends Transparent{
|
||||
/** @var bool[] facing => dummy */
|
||||
protected array $connections = [];
|
||||
|
@ -23,6 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
/**
|
||||
* Transparent blocks do not block any light from propagating through them.
|
||||
*
|
||||
* Note: This does **not** imply that the block is **visually** transparent. For example, chests allow light to pass
|
||||
* through, but the player cannot see through them except at the edges.
|
||||
*/
|
||||
class Transparent extends Block{
|
||||
|
||||
public function isTransparent() : bool{
|
||||
|
@ -27,6 +27,9 @@ use pocketmine\data\runtime\RuntimeDataReader;
|
||||
use pocketmine\data\runtime\RuntimeDataWriter;
|
||||
use pocketmine\item\Item;
|
||||
|
||||
/**
|
||||
* Represents a block which is unrecognized or not implemented.
|
||||
*/
|
||||
class UnknownBlock extends Transparent{
|
||||
|
||||
private int $stateData;
|
||||
|
@ -175,20 +175,6 @@ final class EntityFactory{
|
||||
}, ['Human']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure(World, CompoundTag) : Entity $creationFunc
|
||||
*/
|
||||
private static function validateCreationFunc(\Closure $creationFunc) : void{
|
||||
$sig = new CallbackType(
|
||||
new ReturnType(Entity::class),
|
||||
new ParameterType("world", World::class),
|
||||
new ParameterType("nbt", CompoundTag::class)
|
||||
);
|
||||
if(!$sig->isSatisfiedBy($creationFunc)){
|
||||
throw new \TypeError("Declaration of callable `" . CallbackType::createFromCallable($creationFunc) . "` must be compatible with `" . $sig . "`");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an entity type into the index.
|
||||
*
|
||||
@ -207,7 +193,11 @@ final class EntityFactory{
|
||||
throw new \InvalidArgumentException("At least one save name must be provided");
|
||||
}
|
||||
Utils::testValidInstance($className, Entity::class);
|
||||
self::validateCreationFunc($creationFunc);
|
||||
Utils::validateCallableSignature(new CallbackType(
|
||||
new ReturnType(Entity::class),
|
||||
new ParameterType("world", World::class),
|
||||
new ParameterType("nbt", CompoundTag::class)
|
||||
), $creationFunc);
|
||||
|
||||
foreach($saveNames as $name){
|
||||
$this->creationFuncs[$name] = $creationFunc;
|
||||
|
@ -33,5 +33,8 @@ class HealthBoostEffect extends Effect{
|
||||
|
||||
public function remove(Living $entity, EffectInstance $instance) : void{
|
||||
$entity->setMaxHealth($entity->getMaxHealth() - 4 * $instance->getEffectLevel());
|
||||
if($entity->getHealth() > $entity->getMaxHealth()){
|
||||
$entity->setHealth($entity->getMaxHealth());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,7 @@ use pocketmine\network\mcpe\protocol\SetTimePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTitlePacket;
|
||||
use pocketmine\network\mcpe\protocol\TakeItemActorPacket;
|
||||
use pocketmine\network\mcpe\protocol\TextPacket;
|
||||
use pocketmine\network\mcpe\protocol\ToastRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\TransferPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandData;
|
||||
@ -1100,6 +1101,10 @@ class NetworkSession{
|
||||
$this->sendDataPacket(EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
|
||||
}
|
||||
|
||||
public function onToastNotification(string $title, string $body) : void{
|
||||
$this->sendDataPacket(ToastRequestPacket::create($title, $body));
|
||||
}
|
||||
|
||||
public function tick() : void{
|
||||
if($this->info === null){
|
||||
if(time() >= $this->connectTime + 10){
|
||||
|
@ -420,6 +420,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
return $this->lastPlayed - $this->firstPlayed > 1; // microtime(true) - microtime(true) may have less than one millisecond difference
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the player is allowed to toggle flight mode.
|
||||
*
|
||||
* If set to false, the player will be locked in its current flight mode (flying/not flying), and attempts by the
|
||||
* player to enter or exit flight mode will be prevented.
|
||||
*
|
||||
* Note: Setting this to false DOES NOT change whether the player is currently flying. Use
|
||||
* {@link Player::setFlying()} for that purpose.
|
||||
*/
|
||||
public function setAllowFlight(bool $value) : void{
|
||||
if($this->allowFlight !== $value){
|
||||
$this->allowFlight = $value;
|
||||
@ -427,10 +436,24 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the player is allowed to toggle its flight state.
|
||||
*
|
||||
* If false, the player is locked in its current flight mode (flying/not flying), and attempts by the player to
|
||||
* enter or exit flight mode will be prevented.
|
||||
*/
|
||||
public function getAllowFlight() : bool{
|
||||
return $this->allowFlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the player's movement may be obstructed by blocks with collision boxes.
|
||||
* If set to false, the player can move through any block unobstructed.
|
||||
*
|
||||
* Note: Enabling flight mode in conjunction with this is recommended. A non-flying player will simply fall through
|
||||
* the ground into the void.
|
||||
* @see Player::setFlying()
|
||||
*/
|
||||
public function setHasBlockCollision(bool $value) : void{
|
||||
if($this->blockCollision !== $value){
|
||||
$this->blockCollision = $value;
|
||||
@ -438,6 +461,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether blocks may obstruct the player's movement.
|
||||
* If false, the player can move through any block unobstructed.
|
||||
*/
|
||||
public function hasBlockCollision() : bool{
|
||||
return $this->blockCollision;
|
||||
}
|
||||
@ -1023,7 +1050,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
protected function internalSetGameMode(GameMode $gameMode) : void{
|
||||
$this->gamemode = $gameMode;
|
||||
|
||||
$this->allowFlight = $this->isCreative();
|
||||
$this->allowFlight = $this->gamemode->equals(GameMode::CREATIVE());
|
||||
$this->hungerManager->setEnabled($this->isSurvival());
|
||||
|
||||
if($this->isSpectator()){
|
||||
@ -1992,6 +2019,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$this->getNetworkSession()->onTip($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a toast message to the player, or queue to send it if a toast message is already shown.
|
||||
*/
|
||||
public function sendToastNotification(string $title, string $body) : void{
|
||||
$this->getNetworkSession()->onToastNotification($title, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Form to the player, or queue to send it if a form is already open.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user