mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-13 17:29:44 +00:00
Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11862895039
This commit is contained in:
commit
b6f55b78a9
31
.github/workflows/team-pr-auto-approve.yml
vendored
31
.github/workflows/team-pr-auto-approve.yml
vendored
@ -13,30 +13,25 @@ on:
|
||||
- reopened
|
||||
- ready_for_review
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
approve:
|
||||
name: Auto approve
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check if PR author has write access
|
||||
id: check-permission
|
||||
uses: actions-cool/check-user-permission@v2
|
||||
- name: Generate access token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
require: write
|
||||
username: ${{ github.event.pull_request.user.login }}
|
||||
#technically this would be fine for dependabot but generally bots don't count as team members
|
||||
check-bot: true
|
||||
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
|
||||
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: RestrictedActions
|
||||
|
||||
#TODO: Some way to avoid unnecessary repeated reviews would be nice here
|
||||
|
||||
- name: Approve PR if authorized
|
||||
if: steps.check-permission.outputs.require-result == 'true' && steps.check-permission.outputs.check-result == 'false'
|
||||
uses: juliangruber/approve-pull-request-action@v2
|
||||
- name: Dispatch restricted action
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ github.event.pull_request.number }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
repository: ${{ github.repository_owner }}/RestrictedActions
|
||||
event-type: auto_approve_collaborator_pr
|
||||
client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}" }'
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Vector3;
|
||||
|
||||
/**
|
||||
* "Flowable" blocks are destroyed if water flows into the same space as the block. These blocks usually don't have any
|
||||
@ -40,6 +41,11 @@ abstract class Flowable extends Transparent{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
|
||||
return (!$this->canBeFlowedInto() || !$blockReplace instanceof Liquid) &&
|
||||
parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
|
@ -24,60 +24,20 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\BlockEventHelper;
|
||||
use pocketmine\block\utils\MultiAnySupportTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\item\Fertilizer;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\World;
|
||||
use function array_key_first;
|
||||
use function count;
|
||||
use function shuffle;
|
||||
|
||||
class GlowLichen extends Transparent{
|
||||
|
||||
/** @var int[] */
|
||||
protected array $faces = [];
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->facingFlags($this->faces);
|
||||
}
|
||||
|
||||
/** @return int[] */
|
||||
public function getFaces() : array{ return $this->faces; }
|
||||
|
||||
public function hasFace(int $face) : bool{
|
||||
return isset($this->faces[$face]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $faces
|
||||
* @return $this
|
||||
*/
|
||||
public function setFaces(array $faces) : self{
|
||||
$uniqueFaces = [];
|
||||
foreach($faces as $face){
|
||||
Facing::validate($face);
|
||||
$uniqueFaces[$face] = $face;
|
||||
}
|
||||
$this->faces = $uniqueFaces;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function setFace(int $face, bool $value) : self{
|
||||
Facing::validate($face);
|
||||
if($value){
|
||||
$this->faces[$face] = $face;
|
||||
}else{
|
||||
unset($this->faces[$face]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
use MultiAnySupportTrait;
|
||||
|
||||
public function getLightLevel() : int{
|
||||
return 7;
|
||||
@ -102,39 +62,11 @@ class GlowLichen extends Transparent{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$this->faces = $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
|
||||
$availableFaces = $this->getAvailableFaces();
|
||||
|
||||
if(count($availableFaces) === 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
$opposite = Facing::opposite($face);
|
||||
$placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
|
||||
$this->faces[$placedFace] = $placedFace;
|
||||
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$changed = false;
|
||||
|
||||
foreach($this->faces as $face){
|
||||
if($this->getAdjacentSupportType($face) !== SupportType::FULL){
|
||||
unset($this->faces[$face]);
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if($changed){
|
||||
$world = $this->position->getWorld();
|
||||
if(count($this->faces) === 0){
|
||||
$world->useBreakOn($this->position);
|
||||
}else{
|
||||
$world->setBlock($this->position, $this);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
protected function getInitialPlaceFaces(Block $blockReplace) : array{
|
||||
return $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
|
||||
}
|
||||
|
||||
private function getSpreadBlock(Block $replace, int $spreadFace) : ?Block{
|
||||
@ -261,17 +193,4 @@ class GlowLichen extends Transparent{
|
||||
public function getFlammability() : int{
|
||||
return 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, int> $faces
|
||||
*/
|
||||
private function getAvailableFaces() : array{
|
||||
$faces = [];
|
||||
foreach(Facing::ALL as $face){
|
||||
if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
|
||||
$faces[$face] = $face;
|
||||
}
|
||||
}
|
||||
return $faces;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,9 @@ use pocketmine\world\Position;
|
||||
|
||||
class Sugarcane extends Flowable{
|
||||
use AgeableTrait;
|
||||
use StaticSupportTrait;
|
||||
use StaticSupportTrait {
|
||||
onNearbyBlockChange as onSupportBlockChange;
|
||||
}
|
||||
|
||||
public const MAX_AGE = 15;
|
||||
|
||||
@ -97,7 +99,13 @@ class Sugarcane extends Flowable{
|
||||
}
|
||||
|
||||
public function onRandomTick() : void{
|
||||
if(!$this->getSide(Facing::DOWN)->hasSameTypeId($this)){
|
||||
$down = $this->getSide(Facing::DOWN);
|
||||
if(!$down->hasSameTypeId($this)){
|
||||
if(!$this->hasNearbyWater($down)){
|
||||
$this->position->getWorld()->useBreakOn($this->position, createParticles: true);
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->age === self::MAX_AGE){
|
||||
$this->grow($this->position);
|
||||
}else{
|
||||
@ -123,4 +131,23 @@ class Sugarcane extends Flowable{
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function hasNearbyWater(Block $down) : bool{
|
||||
foreach($down->getHorizontalSides() as $sideBlock){
|
||||
$blockId = $sideBlock->getTypeId();
|
||||
if($blockId === BlockTypeIds::WATER || $blockId === BlockTypeIds::FROSTED_ICE){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$down = $this->getSide(Facing::DOWN);
|
||||
if(!$down->hasSameTypeId($this) && !$this->hasNearbyWater($down)){
|
||||
$this->position->getWorld()->useBreakOn($this->position, createParticles: true);
|
||||
}else{
|
||||
$this->onSupportBlockChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
72
src/block/utils/MultiAnyFacingTrait.php
Normal file
72
src/block/utils/MultiAnyFacingTrait.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?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\block\utils;
|
||||
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\math\Facing;
|
||||
|
||||
/**
|
||||
* Used by blocks that can have multiple target faces in the area of one solid block, such as covering three sides of a corner.
|
||||
*/
|
||||
trait MultiAnyFacingTrait{
|
||||
|
||||
/** @var int[] */
|
||||
protected array $faces = [];
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->facingFlags($this->faces);
|
||||
}
|
||||
|
||||
/** @return int[] */
|
||||
public function getFaces() : array{ return $this->faces; }
|
||||
|
||||
public function hasFace(int $face) : bool{
|
||||
return isset($this->faces[$face]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $faces
|
||||
* @return $this
|
||||
*/
|
||||
public function setFaces(array $faces) : self{
|
||||
$uniqueFaces = [];
|
||||
foreach($faces as $face){
|
||||
Facing::validate($face);
|
||||
$uniqueFaces[$face] = $face;
|
||||
}
|
||||
$this->faces = $uniqueFaces;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function setFace(int $face, bool $value) : self{
|
||||
Facing::validate($face);
|
||||
if($value){
|
||||
$this->faces[$face] = $face;
|
||||
}else{
|
||||
unset($this->faces[$face]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
96
src/block/utils/MultiAnySupportTrait.php
Normal file
96
src/block/utils/MultiAnySupportTrait.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?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\block\utils;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use function array_key_first;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Used by blocks that have multiple support requirements in the area of one solid block, such as covering three sides of a corner.
|
||||
* Prevents placement if support isn't available and automatically destroys a block side if it's support is removed.
|
||||
*/
|
||||
trait MultiAnySupportTrait{
|
||||
use MultiAnyFacingTrait;
|
||||
|
||||
/**
|
||||
* Returns a list of faces that block should already have when placed.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
abstract protected function getInitialPlaceFaces(Block $blockReplace) : array;
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$this->faces = $this->getInitialPlaceFaces($blockReplace);
|
||||
$availableFaces = $this->getAvailableFaces();
|
||||
|
||||
if(count($availableFaces) === 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
$opposite = Facing::opposite($face);
|
||||
$placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
|
||||
$this->faces[$placedFace] = $placedFace;
|
||||
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$changed = false;
|
||||
|
||||
foreach($this->faces as $face){
|
||||
if($this->getAdjacentSupportType($face) !== SupportType::FULL){
|
||||
unset($this->faces[$face]);
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if($changed){
|
||||
$world = $this->position->getWorld();
|
||||
if(count($this->faces) === 0){
|
||||
$world->useBreakOn($this->position);
|
||||
}else{
|
||||
$world->setBlock($this->position, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, int> $faces
|
||||
*/
|
||||
private function getAvailableFaces() : array{
|
||||
$faces = [];
|
||||
foreach(Facing::ALL as $face){
|
||||
if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
|
||||
$faces[$face] = $face;
|
||||
}
|
||||
}
|
||||
return $faces;
|
||||
}
|
||||
}
|
@ -64,6 +64,7 @@ use pocketmine\command\defaults\TransferServerCommand;
|
||||
use pocketmine\command\defaults\VanillaCommand;
|
||||
use pocketmine\command\defaults\VersionCommand;
|
||||
use pocketmine\command\defaults\WhitelistCommand;
|
||||
use pocketmine\command\defaults\XpCommand;
|
||||
use pocketmine\command\utils\CommandStringHelper;
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
@ -128,7 +129,8 @@ class SimpleCommandMap implements CommandMap{
|
||||
new TitleCommand(),
|
||||
new TransferServerCommand(),
|
||||
new VersionCommand(),
|
||||
new WhitelistCommand()
|
||||
new WhitelistCommand(),
|
||||
new XpCommand(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
89
src/command/defaults/XpCommand.php
Normal file
89
src/command/defaults/XpCommand.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?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\command\defaults;
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\entity\Attribute;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Limits;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function abs;
|
||||
use function count;
|
||||
use function str_ends_with;
|
||||
use function substr;
|
||||
|
||||
class XpCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
parent::__construct(
|
||||
"xp",
|
||||
KnownTranslationFactory::pocketmine_command_xp_description(),
|
||||
KnownTranslationFactory::pocketmine_command_xp_usage()
|
||||
);
|
||||
$this->setPermissions([
|
||||
DefaultPermissionNames::COMMAND_XP_SELF,
|
||||
DefaultPermissionNames::COMMAND_XP_OTHER
|
||||
]);
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
if(count($args) < 1){
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER);
|
||||
if($player === null){
|
||||
return true;
|
||||
}
|
||||
|
||||
$xpManager = $player->getXpManager();
|
||||
if(str_ends_with($args[0], "L")){
|
||||
$xpLevelAttr = $player->getAttributeMap()->get(Attribute::EXPERIENCE_LEVEL) ?? throw new AssumptionFailedError();
|
||||
$maxXpLevel = (int) $xpLevelAttr->getMaxValue();
|
||||
$currentXpLevel = $xpManager->getXpLevel();
|
||||
$xpLevels = $this->getInteger($sender, substr($args[0], 0, -1), -$currentXpLevel, $maxXpLevel - $currentXpLevel);
|
||||
if($xpLevels >= 0){
|
||||
$xpManager->addXpLevels($xpLevels, false);
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_xp_success_levels((string) $xpLevels, $player->getName()));
|
||||
}else{
|
||||
$xpLevels = abs($xpLevels);
|
||||
$xpManager->subtractXpLevels($xpLevels);
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_xp_success_negative_levels((string) $xpLevels, $player->getName()));
|
||||
}
|
||||
}else{
|
||||
$xp = $this->getInteger($sender, $args[0], max: Limits::INT32_MAX);
|
||||
if($xp < 0){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_xp_failure_widthdrawXp()->prefix(TextFormat::RED));
|
||||
}else{
|
||||
$xpManager->addXp($xp, false);
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_xp_success((string) $xp, $player->getName()));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -84,6 +84,8 @@ final class DefaultPermissionNames{
|
||||
public const COMMAND_WHITELIST_LIST = "pocketmine.command.whitelist.list";
|
||||
public const COMMAND_WHITELIST_RELOAD = "pocketmine.command.whitelist.reload";
|
||||
public const COMMAND_WHITELIST_REMOVE = "pocketmine.command.whitelist.remove";
|
||||
public const COMMAND_XP_OTHER = "pocketmine.command.xp.other";
|
||||
public const COMMAND_XP_SELF = "pocketmine.command.xp.self";
|
||||
public const GROUP_CONSOLE = "pocketmine.group.console";
|
||||
public const GROUP_OPERATOR = "pocketmine.group.operator";
|
||||
public const GROUP_USER = "pocketmine.group.user";
|
||||
|
@ -112,5 +112,7 @@ abstract class DefaultPermissions{
|
||||
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_LIST, l10n::pocketmine_permission_command_whitelist_list()), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_RELOAD, l10n::pocketmine_permission_command_whitelist_reload()), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_REMOVE, l10n::pocketmine_permission_command_whitelist_remove()), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_XP_OTHER, l10n::pocketmine_permission_command_xp_other()), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_XP_SELF, l10n::pocketmine_permission_command_xp_self()), [$operatorRoot]);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user