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