diff --git a/README.md b/README.md index f15b706..1936c73 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@

--- -

Shopkeepers for PocketMine-MP 4

+

Shopkeepers v0.9.1 for PocketMine-MP 4


**⚠️ We are not in any way related to the [Shopkeepers plugin](https://dev.bukkit.org/projects/shopkeepers) for Bukkit!** @@ -36,7 +36,16 @@ **Shopkeepers** is made to be multi-version, in fact I announce with great joy that the plugin is available for both PocketMine-MP 5 and PocketMine-MP 4! > **Warning**
> This is the branch for **PocketMine-MP 4** only!
-> The branch for PocketMine-MP **5** can be found [here](https://github.com/FoxWorn3365/Shopkeepers) +> The branch for PocketMine-MP **5** can be found [here](https://github.com/FoxWorn3365/Shopkeepers) or on [poggit]() + +## Configuration +The configuration of **Shopkeepers** allows you to customize some values to make it suitable for all servers. +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| enabled | bool | true | Is the plugin enabled? | +| max-entities-for-player | int | 5 | Max shopkeeper's entities for one player (PER SHOP) | +| max-entities-bypass | array | [] | Player that can bypass this limitation | +| banned-shop-names | array | [] | List of banned names | ## Commands The base command is `/shopkeepers` but you can also use `/sk`, `/skeepers` and `/shopk` as aliases. diff --git a/default-config.yml b/default-config.yml new file mode 100644 index 0000000..f05e018 --- /dev/null +++ b/default-config.yml @@ -0,0 +1,20 @@ +# +# Shopkeepers v0.9.1 by FoxWorm3365 +# (C) 2023-now FoxWorn3365 +# +# Relased under the GPL-3.0 license +# https://github.com/FoxWorn3365/Shopkeepers/blob/main/LICENSE +# + +enabled: true + +# Max shopkeeper's entities for one player (PER SHOP) +max-entities-for-player: 5 +# Player that can bypass this limitation +max-entities-bypass: + - YourMinecraftUsername + +# Moderation settings - THIS IS A CONTAIN CONDITION so if you set 'pro' also names like 'apron', 'prototypus', 'proto', 'pro' and it's case INSENSITIVE +banned-shop-names: # Banned shop names, array + - hitler + - nazi \ No newline at end of file diff --git a/plugin.yml b/plugin.yml index 318b5c0..863fe7c 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,5 +1,5 @@ name: Shopkeepers -version: 0.8.2 +version: 0.9.1 api: 4.0.0 main: FoxWorn3365\Shopkeepers\Core @@ -10,7 +10,7 @@ description: Add shopkeepers to your PocketMine server! commands: shopkeepers: description: The main shopkeepers command - usage: "/shopkeepers [list|create|summon|rename|edit|info|inventory] " + usage: "/shopkeepers [list|create|summon|rename|edit|info] " aliases: - sk - shopk diff --git a/src/FoxWorn3365/Shopkeepers/ConfigManager.php b/src/FoxWorn3365/Shopkeepers/ConfigManager.php index b868b84..6df6aa6 100644 --- a/src/FoxWorn3365/Shopkeepers/ConfigManager.php +++ b/src/FoxWorn3365/Shopkeepers/ConfigManager.php @@ -62,6 +62,12 @@ public function set(string $key, mixed $value) : void { $this->update($config); } + public function remove(string $key) : void { + $config = $this->get(); + unset($config->{$key}); + $this->update($config); + } + public function setSingleKey(string $key) : void { $this->key = $key; } diff --git a/src/FoxWorn3365/Shopkeepers/Core.php b/src/FoxWorn3365/Shopkeepers/Core.php index b0e276f..04d2ea5 100644 --- a/src/FoxWorn3365/Shopkeepers/Core.php +++ b/src/FoxWorn3365/Shopkeepers/Core.php @@ -43,6 +43,7 @@ use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\player\PlayerJoinEvent; use pocketmine\event\server\DataPacketReceiveEvent; +use pocketmine\event\entity\EntitySpawnEvent; // Packets use pocketmine\network\mcpe\protocol\ActorEventPacket as EntityEventPacket; @@ -74,11 +75,13 @@ class Core extends PluginBase implements Listener { protected object $trades; protected object $tradeQueue; + protected string $defaultConfig = "IwojIFNob3BrZWVwZXJzIHYwLjkuMSBieSBGb3hXb3JtMzM2NQojIChDKSAyMDIzLW5vdyBGb3hXb3JuMzM2NQojIAojIFJlbGFzZWQgdW5kZXIgdGhlIEdQTC0zLjAgbGljZW5zZSAKIyBodHRwczovL2dpdGh1Yi5jb20vRm94V29ybjMzNjUvU2hvcGtlZXBlcnMvYmxvYi9tYWluL0xJQ0VOU0UKIwoKZW5hYmxlZDogdHJ1ZQoKIyBNYXggc2hvcGtlZXBlcidzIGVudGl0aWVzIGZvciBvbmUgcGxheWVyIChQRVIgU0hPUCkKbWF4LWVudGl0aWVzLWZvci1wbGF5ZXI6IDUKIyBQbGF5ZXIgdGhhdCBjYW4gYnlwYXNzIHRoaXMgbGltaXRhdGlvbgptYXgtZW50aXRpZXMtYnlwYXNzOgogIC0gWW91ck1pbmVjcmFmdFVzZXJuYW1lCgojIE1vZGVyYXRpb24gc2V0dGluZ3MgICAtIFRISVMgSVMgQSBDT05UQUlOIENPTkRJVElPTiBzbyBpZiB5b3Ugc2V0ICdwcm8nIGFsc28gbmFtZXMgbGlrZSAnYXByb24nLCAncHJvdG90eXB1cycsICdwcm90bycsICdwcm8nIGFuZCBpdCdzIGNhc2UgSU5TRU5TSVRJVkUKYmFubmVkLXNob3AtbmFtZXM6CiAgLSBoaXRsZXIKICAtIG5hemkKCiMgQmFubmVkIHNob3AgaXRlbSBuYW1lcyBzbyB0aGV5IGNhbid0IGJlIHNvbGQgb3IgYm91Z2h0CmJhbm5lZC1pdGVtLW5hbWVzOgogIC0gZGlhbW9uZF9heGUKCiMgQmFubmVkIGl0ZW0gSURzIApiYW5uZWQtaXRlbS1pZHM6CiAgLSAyNTU="; + protected float $server = 5.0; protected const NOT_PERM_MSG = "§cSorry but you don't have permissions to use this command!\nPlease contact your server administrator"; - protected const AUTHOR = "FoxWorn3365"; - protected const VERSION = "0.8.2-pre-relase"; + public const AUTHOR = "FoxWorn3365"; + public const VERSION = "0.9.1-pre"; public function onLoad() : void { $this->menu = new \stdClass; @@ -104,6 +107,19 @@ public function onEnable() : void { // Register event listener $this->getServer()->getPluginManager()->registerEvents($this, $this); + + // Load the config + if (!file_exists($this->getDataFolder() . "config.yml")) { + file_put_contents($this->getDataFolder() . "config.yml", base64_decode($this->defaultConfig)); + } + + // Open the config + $this->config = new Config($this->getDataFolder() . "config.yml", Config::YAML); + + // Shall we need to disable the plugin? + if (!$this->config->get('enabled', true)) { + $this->getServer()->getPluginManager()->disablePlugin($this); // F + } } public function onPlayerJoin(PlayerJoinEvent $event) { @@ -121,26 +137,23 @@ public function onPlayerEntityInteract(Interaction $event) : void { $entity = $event->getEntity(); if ($entity instanceof Shopkeeper) { $data = $entity->getConfig(); - if ($data->author === $event->getPlayer()->getName() && !$event->getPlayer()->isSneaking()) { - // Open the shopkeeper's inventory RN! - $cm = new ConfigManager($data->author, $this->getDataFolder()); - $cm->setSingleKey($data->shop); - if ($cm->get()->{$data->shop}->admin) { - // Admin shops don't have inventory so open the normal trade menu and runnnn - $manager = new Manager($cm); - $this->trades->{$event->getPlayer()->getName()} = new \stdClass; - $this->trades->{$event->getPlayer()->getName()}->config = $data; - $manager->send($event->getPlayer(), $entity); - } else { - $menu = new ShopInventoryMenu($cm); - $menu->create()->send($event->getPlayer()); - } + $cm = new ConfigManager($data->author, $this->getDataFolder()); + $cm->setSingleKey($data->shop); + if (@$cm->get()->{$data->shop} === null) { + // Oh no, no config! + $event->getPlayer()->sendMessage("§cSorry but this shop does not exists anymore!"); + // Remove the shop + $this->entities->remove($this->entities->generateEntityHash($event->getEntity())); + $event->getEntity()->kill(); + return; + } elseif ($data->author === $event->getPlayer()->getName() && !$event->getPlayer()->isSneaking()) { + // Open the shopkeeper's ~~inventory~~ info page RN! + $menu = new ShopInfoMenu($cm, true); + $menu->create()->send($event->getPlayer()); } else { // It's a shopkeeper! // BEAUTIFUL! // Now let's open the shopkeeper interface - $cm = new ConfigManager($data->author, $this->getDataFolder()); - $cm->setSingleKey($data->shop); $manager = new Manager($cm); $this->trades->{$event->getPlayer()->getName()} = new \stdClass; $this->trades->{$event->getPlayer()->getName()}->config = $data; @@ -149,7 +162,33 @@ public function onPlayerEntityInteract(Interaction $event) : void { } } - public function onCommand(CommandSender $sender, Command $command, $label, array $args) : bool{ + public function onEntitySpawn(EntitySpawnEvent $event) : void { + if ($event->getEntity() instanceof Shopkeeper) { + // Add the shopkeeper to entity interface + if (!$event->getEntity()->hasCustomShopkeeperEntityId()) { + // FIRST, check if the limit is not trepassed + $name = $event->getEntity()->getConfig()->shop; + $author = $event->getEntity()->getConfig()->author; + if (@$this->entities->list->{$author}->{$name} !== null) { + if ($this->entities->list->{$author}->{$name} + 1 > $this->config->get('max-entities-for-player', 3) && !in_array($author, $this->config->get('max-entities-bypass', []))) { + // Do not consent + $event->getEntity()->getWorld()->getServer()->getPlayerExact($author)->sendMessage("§cSorry but you have reached the max shopkeepers entity for the shop {$name}\n§rUsed: " . $this->entities->list->{$author}->{$name} ."/" . $this->config->get('max-entities-for-player', 3)); + $event->getEntity()->kill(); + return; + } else { + $this->entities->list->{$author}->{$name}++; + } + } else { + $this->entities->list->{$author}->{$name} = 1; + } + $entity = $event->getEntity(); + $entity->setCustomShopkeeperEntityId(Utils::randomizer(10)); + $this->entities->add($event->getEntity()); + } + } + } + + public function onCommand(CommandSender $sender, Command $command, $label, array $args) : bool { if (!($sender instanceof Player)) { $sender->sendMessage("This command can be only executed by in-game players!"); return false; @@ -190,6 +229,15 @@ public function onCommand(CommandSender $sender, Command $command, $label, array if (empty($name = $args[1])) { $name = $this->generateRandomString(7); } + + foreach ($this->config->get('banned-shop-names', []) as $banned) { + if (strpos($name, $banned) !== false) { + // Oh crap, this is banned! + $sender->sendMessage("§cSorry but this name is banned!\n§rPlase contact your server administrator"); + return false; + } + } + // Create the config // OOOO why are u running? before, check if there's also an existing name if (@$shop->get()?->{$name} !== null) { @@ -251,31 +299,42 @@ public function onCommand(CommandSender $sender, Command $command, $label, array $shopdata = new \stdClass; $shopdata->author = $sender->getName(); $shopdata->shop = $name; - $villager = new Shopkeeper($pos); + $villager = new Shopkeeper($pos, $shopdata); $villager->setNameTag($name); $villager->setNameTagAlwaysVisible($shop->get()->{$name}->namevisible); - $villager->setConfig($shopdata); $villager->spawnToAll(); - $this->entities->add($villager); + // Will be managed by EntitySpawnEvent $this->entities->add($villager); return true; } elseif ($args[0] === "remove" || $args[0] === "despawn") { $sender->sendMessage("To remove a shopkeeper just hit it!"); return true; - } elseif (empty($args[0])) { - if (!$sender->hasPermission("shopkeepers.shop.defaultGUI")) { + } elseif ($args[0] === "rename" && !empty($args[1]) && !empty($args[2])) { + if (!$sender->hasPermission("shopkeepers.shop.rename")) { $sender->sendMessage(self::NOT_PERM_MSG); } - $menu = new InfoMenu(); - $menu->create($sender, $this->getDataFolder())->send($sender); + $name = $args[1]; + if (@$shop->get()?->{$name} === null) { + $sender->sendMessage("You don't have a shop called {$name}!"); + return false; + } + + // Fix name + $shop->set($args[2], $shop->get()->{$name}); + $shop->remove($name); + + $sender->sendMessage("Shop {$name} successfully renamed!"); return true; - } else { + } elseif (empty($args[0])) { if (!$sender->hasPermission("shopkeepers.shop.defaultGUI")) { $sender->sendMessage(self::NOT_PERM_MSG); } $menu = new InfoMenu(); $menu->create($sender, $this->getDataFolder())->send($sender); + return true; + } else { + return false; } return false; } @@ -315,13 +374,19 @@ public function onPacket(DataPacketReceiveEvent $event) : void { $event->getOrigin()->getPlayer()->sendMessage("§cYour inventory is full!"); return; } else { + if ($result->getId() === 25266) { + // Is a custom item + $item = NbtManager::decode(Utils::comparator($this->trades->{$event->getOrigin()->getPlayer()->getName()}->item, $result->getCount(), $cm->get()->{$cm->getSingleKey()}->items)); + } else { + $translator = new TypeConverter(); + $item = $translator->netItemStackToCore($result); + } + /* if ($this->server < 5) { $translator = new TypeConverter(); $item = $translator->netItemStackToCore($result); - } else { - $translator = (new TypeConverter())->getItemTranslator(); - $item = $translator->fromNetworkId($result->getId(), $result->getMeta(), $result->getBlockRuntimeId()); } + */ $item->setCount($result->getCount()); // Before set this we need to check and update the villager's inventory $total = $result->getCount(); @@ -354,14 +419,17 @@ public function onPacket(DataPacketReceiveEvent $event) : void { if ($total > 0) { return; } + + if (gettype($inventoryInsideConfig) !== 'array') { + Utils::errorLogger($this->getDataFolder(), "ERROR", "InventoryInsideConfig at Core.php#364 was an object and not an array!\nPlase report this with an issue!"); + $inventoryInsideConfig = []; + } $object = $cm->get()->{$cm->getSingleKey()}; $object->inventory = $inventoryInsideConfig; $cm->set($cm->getSingleKey(), $object); } $this->trades->{$event->getOrigin()->getPlayer()->getName()}->items[] = $item; - // Remove this item from the entity's inventory - //$itemglobal = $item; } } } @@ -485,9 +553,11 @@ public function onEntityDamage(Damage $event) { if ($event->getDamager() instanceof Player) { if ($event->getEntity()->getConfig()->author === $event->getDamager()->getName() && $event->getDamager()->hasPermission("shopkeepers.shop.remove")) { $this->entities->remove($this->entities->generateEntityHash($event->getEntity())); + $this->entities->list->{$event->getEntity()->getConfig()->author}->{$event->getEntity()->getConfig()->shop}--; $event->getEntity()->kill(); } elseif ($event->getEntity()->getConfig()->author === $event->getDamager()->getName() && $event->getDamager()->hasPermission("shopkeepers.shop.kill")) { $this->entities->remove($this->entities->generateEntityHash($event->getEntity())); + $this->entities->list->{$event->getEntity()->getConfig()->author}->{$event->getEntity()->getConfig()->shop}--; $event->getEntity()->kill(); } else { $event->getDamager()->sendMessage("§cYou can't damage a shopkeeper!"); diff --git a/src/FoxWorn3365/Shopkeepers/EntityManager.php b/src/FoxWorn3365/Shopkeepers/EntityManager.php index d7d2ae5..b1571b1 100644 --- a/src/FoxWorn3365/Shopkeepers/EntityManager.php +++ b/src/FoxWorn3365/Shopkeepers/EntityManager.php @@ -29,10 +29,12 @@ class EntityManager { protected string $base; protected array $elements = []; public array $entities = []; + public object $list; function __construct(string $base) { $this->base = $base; $this->retrive(); + $this->list = new \stdClass; } protected function update() : void { @@ -80,6 +82,7 @@ public function generateEntityHash(Shopkeeper $shop) : string { 'pitch' => $shop->getLocation()->getPitch(), 'world' => $shop->getWorld()->getId(), 'config' => base64_encode(json_encode($shop->getConfig())), + 'id' => $shop->getCustomShopkeeperEntityId(), 'nametag' => base64_encode(json_encode([ 'visible' => $shop->isNameTagAlwaysVisible(), 'tag' => $shop->getNameTag() @@ -94,7 +97,6 @@ public function remove(string $hash) : void { foreach ($this->elements as $element) { if ($element == $hash) { $this->elements[$count] = null; - unset($this->elements[$count]); $this->update(); return; } @@ -103,17 +105,33 @@ public function remove(string $hash) : void { } public function loadPlayer(Player $player) : void { + if (@$this->list->{$player->getName()} === null) { + $this->list->{$player->getName()} = new \stdClass; + } + $server = $player->getServer(); foreach ($this->elements as $shop) { - self::createEntity($shop, $player->getServer())->spawnTo($player); + if ($shop !== null) { + $entity = self::createEntity($shop, $player->getServer()); + if (@$this->list->{$entity->getConfig()->author} === null) { + $this->list->{$entity->getConfig()->author} = new \stdClass; + $this->list->{$entity->getConfig()->author}->{$entity->getConfig()->shop} = 1; + } else { + if (@$this->list->{$entity->getConfig()->author}->{$entity->getConfig()->shop} !== null) { + $this->list->{$entity->getConfig()->author}->{$entity->getConfig()->shop}++; + } else { + $this->list->{$entity->getConfig()->author}->{$entity->getConfig()->shop} = 1; + } + } + $entity->spawnTo($player); + } } } protected static function createEntity(string $rawdata, Server $server) : Shopkeeper { $data = (object)json_decode(base64_decode($rawdata)); $location = new Location($data->x, $data->y, $data->z, $server->getWorldManager()->getWorld($data->world), $data->yaw, $data->pitch); - $entity = new Shopkeeper($location); - $entity->setConfig(json_decode(base64_decode($data->config))); + $entity = new Shopkeeper($location, json_decode(base64_decode($data->config)), $data->id); $tags = json_decode(base64_decode($data->nametag)); $entity->setNameTag($tags->tag); $entity->setNameTagAlwaysVisible($tags->visible); diff --git a/src/FoxWorn3365/Shopkeepers/Menu/EditItemMenu.php b/src/FoxWorn3365/Shopkeepers/Menu/EditItemMenu.php index f8e64fb..892d3d2 100644 --- a/src/FoxWorn3365/Shopkeepers/Menu/EditItemMenu.php +++ b/src/FoxWorn3365/Shopkeepers/Menu/EditItemMenu.php @@ -135,13 +135,13 @@ function edit() : InvMenu { // Now let's analyze the slot switch ($action->getSlot()) { - case 7: + case 17: // Oh crap, we need to delete this! $config->items[$index] = null; - unset($config->items[$index]); - $cm->set($cm->getSingleKey(), json_encode($config)); + $cm->set($cm->getSingleKey(), $config); $retmenu = new EditMenu($cm, $cm->getSingleKey()); $retmenu->create()->send($transaction->getPlayer()); + return $transaction->discard(); break; case 1: $item = $inventory->getItem(10); @@ -163,7 +163,7 @@ function edit() : InvMenu { $object->buy = SerializedItem::encode($item); } break; - case 4: + case 4: $item = $inventory->getItem(13); if ($item->getCount()+1 > 64) { $transaction->getPlayer()->sendMessage("§cYou can't sell more than 64 items!"); diff --git a/src/FoxWorn3365/Shopkeepers/Menu/ListMenu.php b/src/FoxWorn3365/Shopkeepers/Menu/ListMenu.php index 431d9fd..d1fc5ec 100644 --- a/src/FoxWorn3365/Shopkeepers/Menu/ListMenu.php +++ b/src/FoxWorn3365/Shopkeepers/Menu/ListMenu.php @@ -65,7 +65,12 @@ public function create() : InvMenu { break; } $nameassociations[$slotindex] = $name; - $inventory->setItem($slotindex, Factory::item(388, 0, "{$name}\nStatus: §2Active")); + if ($config->admin) { + $shop = "§2true"; + } else { + $shop = "§4false"; + } + $inventory->setItem($slotindex, Factory::egg("§l{$name}\n\n§lTrades: §r" . count($config->items) . "/9\n§lAdmin shop:§r {$shop}")); $slotindex++; } diff --git a/src/FoxWorn3365/Shopkeepers/Menu/ShopConfigMenu.php b/src/FoxWorn3365/Shopkeepers/Menu/ShopConfigMenu.php index baf0428..0302f85 100644 --- a/src/FoxWorn3365/Shopkeepers/Menu/ShopConfigMenu.php +++ b/src/FoxWorn3365/Shopkeepers/Menu/ShopConfigMenu.php @@ -54,6 +54,9 @@ public function create() : InvMenu { Draw::line(0, 8, $inventory, Factory::item(160, 8, "")); Draw::line(18, 26, $inventory, Factory::item(160, 8, "")); + $inventory->clear(4); + $inventory->setItem(4, Factory::egg($this->cm->getSingleKey() . "\n§oClick to return back!")); + // Pass first option if ($this->config->namevisible) { $inventory->setItem(10, Factory::item(35, 5, "Shop's name Visible\nStatus: §2§lActive\n§r§oClick to disable!")); @@ -117,6 +120,11 @@ public function create() : InvMenu { $config->admin = true; } break; + case 4: + // Return back to the ShopInfoMenu menu + $menu = new ShopInfoMenu($cm); + $menu->create()->send($transaction->getPlayer()); + break; } $cm->set($cm->getSingleKey(), $config); diff --git a/src/FoxWorn3365/Shopkeepers/Menu/ShopInfoMenu.php b/src/FoxWorn3365/Shopkeepers/Menu/ShopInfoMenu.php index d9ca220..8f9041a 100644 --- a/src/FoxWorn3365/Shopkeepers/Menu/ShopInfoMenu.php +++ b/src/FoxWorn3365/Shopkeepers/Menu/ShopInfoMenu.php @@ -28,23 +28,28 @@ use FoxWorn3365\Shopkeepers\utils\Utils; use FoxWorn3365\Shopkeepers\utils\Draw; use FoxWorn3365\Shopkeepers\utils\Factory; +use FoxWorn3365\Shopkeepers\utils\NbtManager; use FoxWorn3365\Shopkeepers\ConfigManager; +use FoxWorn3365\Shopkeepers\entity\Shopkeeper; + class ShopInfoMenu { protected InvMenu $menu; protected ConfigManager $cm; protected object $config; + protected bool $local; protected const NOT_PERM_MSG = "§cSorry but you don't have permissions to use this command!\nPlease contact your server administrator"; - function __construct(ConfigManager $cm) { + function __construct(ConfigManager $cm, bool $local = false) { $this->menu = InvMenu::create(InvMenu::TYPE_CHEST); $this->cm = $cm; $this->config = $cm->get()->{$cm->getSingleKey()}; + $this->local = $local; } public function create() : InvMenu { - $this->menu->setName("'{$this->cm->getSingleKey()}' shop - Info"); + $this->menu->setName("View shop {$this->cm->getSingleKey()}"); $inventory = $this->menu->getInventory(); // Draw the useful line @@ -52,24 +57,41 @@ public function create() : InvMenu { // Now set the villager egg with name in the middle (slot 4) $inventory->clear(4); - $inventory->setItem(4, Factory::item(388, 0, $this->cm->getSingleKey())); + $inventory->setItem(4, Factory::egg($this->cm->getSingleKey())); // Now set the informations - $inventory->setItem(10, Factory::item(377, 0, 'Shop config')); + $inventory->setItem(10, Factory::item(377, 0, '§lConfig')); // Villager inventory if (!$this->config->admin) { - $inventory->setItem(13, Factory::item(54, 0, "Shop inventory")); + $inventory->setItem(12, Factory::item(54, 0, "§lInventory")); + } else { + $inventory->setItem(12, Factory::barrier("§l§cShop inventory\n§rDisabled!\n§oThis is an admin shop!")); // ID: -161 Meta: 0 BRID: 10390 } + // Shop discounts announcer for v1.0 + $inventory->setItem(20, Factory::item(388, 0, "§o§lSales\n\n§r§oThis function will be implemented with the §bSales & Shops §r§oupdate AKA §lv1.0")); + + // Summon option + $head = Utils::getItem("minecraft:skull"); + $head->setCustomName("§r§lSummon"); + $inventory->setItem(22, $head); + + // Misteryous option + $inventory->setItem(24, Factory::barrier("§oUnknown\n\nThis function will be implemented with the §bSales & Shops §r§oupdate AKA §lv1.0")); + // Edit Shopkeepers trades $st = Utils::getItem("minecraft:smithing_table"); - $st->setCustomName("§rEdit shop trades"); - $inventory->setItem(16, $st); + $st->setCustomName("§r§lTrades"); + $inventory->setItem(14, $st); + + $inventory->setItem(16, Factory::item(35, 14, "§c§lDelete")); $cm = $this->cm; + $config = $this->config; + $local = $this->local; - $this->menu->setListener(function($transaction) use ($cm) { + $this->menu->setListener(function($transaction) use ($cm, $config, $local) { $slot = $transaction->getAction()->getSlot(); switch ($slot) { case 10: @@ -77,17 +99,20 @@ public function create() : InvMenu { $menu = new ShopConfigMenu($cm); $menu->create()->send($transaction->getPlayer()); break; - case 13: + case 12: // Shop inventory - if (!$transaction->getPlayer()->hasPermission("shopkeepers.shop.allowRemoteInventoryOpen")) { + if (!$transaction->getPlayer()->hasPermission("shopkeepers.shop.allowRemoteInventoryOpen") && !$local) { $transaction->getPlayer()->removeCurrentWindow(); $transaction->getPlayer()->sendMessage(self::NOT_PERM_MSG); break; } - $menu = new ShopInventoryMenu($cm); - $menu->create()->send($transaction->getPlayer()); + + if (!$config->admin) { + $menu = new ShopInventoryMenu($cm); + $menu->create()->send($transaction->getPlayer()); + } break; - case 16: + case 14: if (!$transaction->getPlayer()->hasPermission("shopkeepers.shop.edit")) { $transaction->getPlayer()->removeCurrentWindow(); $transaction->getPlayer()->sendMessage(self::NOT_PERM_MSG); @@ -96,6 +121,29 @@ public function create() : InvMenu { $edit = new EditMenu($cm, $cm->getSingleKey()); $edit->create()->send($transaction->getPlayer()); break; + case 16: + // F, we need to delete this + $cm->remove($cm->getSingleKey()); + $transaction->getPlayer()->removeCurrentWindow(); + $transaction->getPlayer()->sendMessage("Your shop named {$cm->getSingleKey()} has been §cdeleted§r with success!"); + break; + case 22: + if (!$transaction->getPlayer()->hasPermission("shopkeepers.shop.summon")) { + $transaction->getPlayer()->removeCurrentWindow(); + $transaction->getPlayer()->sendMessage(self::NOT_PERM_MSG); + break; + } + // Summon entity + $shopdata = new \stdClass; + $shopdata->author = $transaction->getPlayer()->getName(); + $shopdata->shop = $cm->getSingleKey(); + $villager = new Shopkeeper($transaction->getPlayer()->getLocation()); + $villager->setNameTag($cm->getSingleKey()); + $villager->setNameTagAlwaysVisible($config->namevisible); + $villager->setConfig($shopdata); + $villager->spawnToAll(); + $transaction->getPlayer()->removeCurrentWindow(); + break; } return $transaction->discard(); }); diff --git a/src/FoxWorn3365/Shopkeepers/entity/Shopkeeper.php b/src/FoxWorn3365/Shopkeepers/entity/Shopkeeper.php index 0e8b9b5..8c6c909 100644 --- a/src/FoxWorn3365/Shopkeepers/entity/Shopkeeper.php +++ b/src/FoxWorn3365/Shopkeepers/entity/Shopkeeper.php @@ -25,14 +25,16 @@ use pocketmine\entity\EntitySizeInfo; class Shopkeeper extends Villager { - public ?object $shopconfig; + public ?object $shopconfig = null; + public ?int $customShopkeeperEntityId = null; - public function __construct(Location $loc, ?object $generalizedConfig = null) { + public function __construct(Location $loc, ?object $generalizedConfig = null, ?int $customId = null) { parent::__construct($loc, null); $this->setCanSaveWithChunk(false); $this->shopconfig = $generalizedConfig; + $this->customShopkeeperEntityId = $customId; } public function getName(): string { @@ -54,4 +56,19 @@ public function setConfig(object $config) : void { public function getConfig() : object { return $this->shopconfig; } + + public function setCustomShopkeeperEntityId(int $id) : void { + $this->customShopkeeperEntityId = $id; + } + + public function getCustomShopkeeperEntityId() : ?int { + return $this->customShopkeeperEntityId; + } + + public function hasCustomShopkeeperEntityId() : bool { + if ($this->customShopkeeperEntityId === null) { + return false; + } + return true; + } } \ No newline at end of file diff --git a/src/FoxWorn3365/Shopkeepers/shop/Manager.php b/src/FoxWorn3365/Shopkeepers/shop/Manager.php index fdf04dd..3e40470 100644 --- a/src/FoxWorn3365/Shopkeepers/shop/Manager.php +++ b/src/FoxWorn3365/Shopkeepers/shop/Manager.php @@ -49,7 +49,8 @@ public function send(Player $player, Shopkeeper $entity) : void { $this->player = $player; $this->entity = $entity; foreach ($this->config->items as $itemconfig) { - if (!(!empty($itemconfig->sell) && !empty($itemconfig->buy))) { + if ($itemconfig === null) { continue; } + if (!(!empty($itemconfig->sell) && !empty($itemconfig->buy)) && gettype($this->config->inventory) !== 'array') { continue; } $this->container->add($itemconfig->sell, $itemconfig->buy, $this->config->inventory, $this->config->admin); diff --git a/src/FoxWorn3365/Shopkeepers/utils/Factory.php b/src/FoxWorn3365/Shopkeepers/utils/Factory.php index 83865ad..8694d10 100644 --- a/src/FoxWorn3365/Shopkeepers/utils/Factory.php +++ b/src/FoxWorn3365/Shopkeepers/utils/Factory.php @@ -28,6 +28,9 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; +// Network +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; + final class Factory { public static function sign(int $meta, string $text) : ?Item { $item = Utils::getIntItem(160, $meta); @@ -42,4 +45,36 @@ public static function item(int $id, int $meta, string $name, int $count = 1) : $item->setCount($count); return $item; } + + public static function rawItem(int $id, int $meta, string $name, int $count = 1) : ?Item { + $item = Utils::getIntItem($id, $meta); + $item->setCustomName($name); + $item->setCount($count); + return $item; + } + + public static function egg(string $name, int $count = 1) : ?Item { + $egg = ItemUtils::decode(451, 0, 0); + $egg->setCustomName("§r{$name}"); + $egg->setCount($count); + return $egg; + } + + public static function barrier(string $name, int $count = 1) : ?Item { + $barrier = ItemUtils::decode(-161, 0, 10390); + $barrier->setCustomName("§r{$name}"); + $barrier->setCount($count); + return $barrier; + } + + public static function nbt(string $nbt, string $name, int $count = 1) { + $item = NbtManager::decode($nbt); + $item->setCustomName("§r{$name}"); + $item->setCount($count); + return $item; + } + + public static function itemStack(int $id, int $meta, int $netId, int $count = 1) : ItemStack { + return new ItemStack($id, $meta, $count, $netId, new CompoundTag(), [], []); + } } \ No newline at end of file diff --git a/src/FoxWorn3365/Shopkeepers/utils/ItemUtils.php b/src/FoxWorn3365/Shopkeepers/utils/ItemUtils.php index 80712a3..202b85e 100644 --- a/src/FoxWorn3365/Shopkeepers/utils/ItemUtils.php +++ b/src/FoxWorn3365/Shopkeepers/utils/ItemUtils.php @@ -41,11 +41,11 @@ public static final function encode(Item $item, bool $toObject = true) : array|o } public static final function decode(int $id, int $meta, int $network) : ?Item { - return (new TypeConverter())->getItemTranslator()->fromNetworkId($id, $meta, $network); + return (new TypeConverter())->netItemStackToCore(Factory::itemStack($id, $meta, $network)); } public static final function objectDecode(object $object) : ?Item { - return (new TypeConverter())->getItemTranslator()->fromNetworkId($object->id, $object->meta, $object->network); + return self::decode($object->id, $object->meta, $object->network); } public static final function typeDecode(object $object) : ?Item { @@ -61,4 +61,14 @@ public static final function typeDecode(object $object) : ?Item { public static final function stringParser(string $string) : ?Item { return (new StringToItemParser())->parse($string); } + + public static function getId(Item $item) : int { + $translator = (new TypeConverter())->getItemTranslator(); + return $translator->toNetworkIdQuiet($item)[0]; + } + + public static function getMeta(Item $item) : int { + $translator = (new TypeConverter())->getItemTranslator(); + return $translator->toNetworkIdQuiet($item)[1]; + } } \ No newline at end of file diff --git a/src/FoxWorn3365/Shopkeepers/utils/Utils.php b/src/FoxWorn3365/Shopkeepers/utils/Utils.php index c265430..1a1710c 100644 --- a/src/FoxWorn3365/Shopkeepers/utils/Utils.php +++ b/src/FoxWorn3365/Shopkeepers/utils/Utils.php @@ -26,8 +26,8 @@ use pocketmine\item\Item; use pocketmine\item\VanillaItems; -class Utils { - static function getItem(string $itemid) { +final class Utils { + public static function getItem(string $itemid) : mixed { try { return LegacyStringToItemParser::getInstance()->parse(trim($itemid)); } catch (LegacyStringToItemParserException) { @@ -35,7 +35,7 @@ static function getItem(string $itemid) { } } - static function getIntItem(int $id, int $meta = 0) { + public static function getIntItem(int $id, int $meta = 0) : mixed { $itemid = "{$id}:{$meta}"; try { return LegacyStringToItemParser::getInstance()->parse(trim($itemid)); @@ -44,7 +44,7 @@ static function getIntItem(int $id, int $meta = 0) { } } - static function errorLogger(string $data_dir, string $severity, string $reason) : void { + public static function errorLogger(string $data_dir, string $severity, string $reason) : void { if (file_exists("{$data_dir}error.txt")) { $stream = file_get_contents("{$data_dir}error.txt"); } else { @@ -54,7 +54,7 @@ static function errorLogger(string $data_dir, string $severity, string $reason) file_put_contents("{$data_dir}error.txt", $stream); } - static function integrityChecker(string $data_dir) : void { + public static function integrityChecker(string $data_dir) : void { foreach (glob("{$data_dir}*.json") as $file) { $content = file_get_contents($file); if (empty($content) || $content == " ") { @@ -65,8 +65,88 @@ static function integrityChecker(string $data_dir) : void { self::errorLogger($data_dir, "ERROR", "Invalid JSON in file {$file}!"); // Remove the dangerous file @unlink($file); + } else { + // Correct the file + self::shopTypeChecker($data_dir, json_decode($content), $file); } } + + // Now remove empty values from the .entities.json + if (file_exists("{$data_dir}.entities.json")) { + file_put_contents("{$data_dir}.entities.json", json_encode(self::clearArray(json_decode(file_get_contents("{$data_dir}.entities.json"))))); + } // Perfect, ready to go! } + + public static function shopTypeChecker(string $data_dir, object $object, string $file) : void { + $end = clone $object; + foreach ($object as $name => $shop_a) { + $shop = clone $shop_a; + if (gettype($shop->admin) !== 'boolean') { + self::errorLogger($data_dir, "NOTICE", "Value of 'admin' inside shop '{$name}', file '{$file}' is not a boolean! Corrected"); + $shop->admin = false; + } + + if (gettype($shop->namevisible) !== 'boolean') { + self::errorLogger($data_dir, "NOTICE", "Value of 'namevisible' inside shop '{$name}', file '{$file}' is not a boolean! Corrected"); + $shop->namevisible = false; + } + + if (gettype($shop->items) !== 'array') { + // Oh shit is not array! + if (gettype($shop->items) === 'object') { + self::errorLogger($data_dir, "NOTICE", "Value of 'items' inside shop '{$name}', file '{$file}' is an object! Corrected"); + $it = []; + foreach ($shop->items as $item) { + $it[] = $item; + } + $shop->items = $it; + //var_dump($shop->items); + } else { + self::errorLogger($data_dir, "WARNING", "Value of 'items' inside shop '{$name}', file '{$file}' is not a correct value! Neutralized"); + $shop->items = []; + } + } + + if (gettype($shop->inventory) !== 'array') { + // Oh shit is not array! + if (gettype($shop->inventory) === 'object') { + self::errorLogger($data_dir, "NOTICE", "Value of 'inventory' inside shop '{$name}', file '{$file}' is an object! Corrected"); + $shop->inventory = (array)$shop->inventory; + } else { + self::errorLogger($data_dir, "WARNING", "Value of 'inventory' inside shop '{$name}', file '{$file}' is not a correct value! Neutralized"); + $shop->inventory = []; + } + } + // Update the shop + $end->{$name} = $shop; + } + file_put_contents($file, json_encode($end)); + } + + public static function comparator(Item $buy, int $sellcount, array $items) : string { + foreach ($items as $item) { + if (SerializedItem::decode($item->buy)->equals($buy) && SerializedItem::decode($item->sell)->getCount() === $sellcount) { + return $item->sell; + } + } + } + + public static function randomizer(int $lenght) : int { + $buffer = ""; + for ($a = 0; $a < $lenght; $a++) { + $buffer .= rand(0, 9); + } + return (int)$buffer; + } + + public static function clearArray(array $array) : array { + $return = []; + foreach ($array as $element) { + if ($element !== null) { + $return[] = $element; + } + } + return $return; + } } \ No newline at end of file