diff --git a/README.md b/README.md
index 60984d2..81f3bb6 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
@@ -16,21 +16,23 @@
---
-
Shopkeepers v0.9.1 for PocketMine-MP 5
+
Shopkeepers v1.0 for PocketMine-MP 5
**⚠️ We are not in any way related to the [Shopkeepers plugin](https://dev.bukkit.org/projects/shopkeepers) for Bukkit!**
## Introduction video
-
Watch the video on YouTube
+
Watch the video on YouTube
## Features
-- Players can create theyr own Shopkeepers and manage it
+- Players can create their own Shopkeepers and manage it
- Admin Shopkeepers
- Vanilla trade page
- Shopkeeper inventory for non-admin Shopkeepers
- Hit prevention for shopkeepers
- Easy configuration with in-game GUI
+- Double trade supported
+- Custom skin support
## Compatibility
**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!
@@ -59,6 +61,26 @@ Here a list of all commands that you can use:
| summon | SHOP NAME | Summon a Shopkeeper entity (as a Villager) for your Shop |
| rename | SHOP NAME and NEW NAME | [NOT AVAILABLE] Rename a current shop |
| list | none | Show all of your shops |
+| history | SHOP NAME and PAGE | Show the trade history for the shopkeeper |
+
+## Shopkeepers Skin System (SSS)
+Yes, the v1.0 brought an epic function: now you can set a skin of a Shopkeeper.
+Unfortunately players can't add a skin of a Shopkeeper for multiple reasons:
+- Memory
+- Memory
+- Memory
+- Hmm, Memory?
+
+Anyways, to avoid abuse of this system we have made this feature usable only by the server administrator.
+
+### How to add a skin of a Shopkeeper
+You should have seen something new in the `Shopkeepers` folder, the `skins` folder, and that is where all the skins should be put.
+> **Warning**
+> Skins MUST BE in a `.png` file!
+
+The file name should be composed as follows: `
_.png`, for example `FoxWorn3365_Fox.png` is valid and will be used by the plugin.
+### I don't want to select skins
+Well, if no skin is provided the classic villager is spawned. yeee
## F.A.Q.
### How to create an Admin shop
@@ -66,8 +88,8 @@ There is not really an Admin shop but you can activate this function by using th
### How to see a Shopkeeper's inventory
There are two ways:
-- Use the command `/sk info ` and then click on the chest at the center of the GUI
-- Click on the Shopkeeper (Villager) entity
+- Use the command `/sk info ` and then click on the chest!
+- Click on the Shopkeeper (Villager) entity and then click on the chest!
### I want to access to the Shopkeeper's trade page but if i click the entity i access the inventory!
Easy: shift and click on the Shopkeeper
@@ -75,6 +97,15 @@ Easy: shift and click on the Shopkeeper
### How to despawn a Shopkeeper
More easy: just hit it, it will die in only one hit!
+### I have [ClearLag](https://poggit.pmmp.io/p/ClearLag/2.1.0) and it removes the Shopkeepers entites!
+Edit the config of ClearLag changing to `false` [this option](https://github.com/tobiaskirchmaier/ClearLag/blob/03e2a03a5f8868216dfc89eb78f51523ff228d6b/resources/config.yml#L84C5-L84C24).
+
+### How can I change the skin of a Shopkeeper
+We actually support a Skin System, please read up.
+
+### OMG I CAN'T ACCESS TO THE INVENTORYM uyigqwieduwefibef
+If the Shopkeeper is an Admin Shop it does not have an inventory!
+
## Bug reporting
Reporting bugs ~~to developers~~ to the developer😢 is very important to ensure the stability of the plugin, so in order to better track and manage all reports it is **incredibly necessary** that they are reported via [GitHub Issues](https://github.com/FoxWorn3365/Shopkeepers/issues).
Here is what to include in the reporting to make it perfect:
@@ -87,7 +118,7 @@ Here is what to include in the reporting to make it perfect:
4. (OPTIONAL) The plugin download source
> Knowing where you downloaded the plugin from might help, always better to know some information
5. (OPTIONAL) The `Shopkeepers.phar`
-> "Last wade," in case we can analyze the source
+> "Last wade", in case we can analyze the source
## Contribution guide
Any contribution is greatly appreciated because you help me to lighten my workload, so here are some small guidelines to follow when you want to contribute:
@@ -200,13 +231,16 @@ Now, let's see the object:
```json
{
"author":"",
+ "enabled":true,
"admin":false,
"title":"",
"namevisible":false,
+ "history":base64EncodedHistoryOfTransactions,
"inventory":[],
"items":[
{
"buy":nbtSerializedItem,
+ "buy2":?nbtSerializedItem,
"sell":nbtSerializedItem
}
]
@@ -215,9 +249,11 @@ Now, let's see the object:
| Name | Type | Description |
| --- | --- | --- |
| author | string | The username of the shop author |
+| enabled | bool | Is the shop enabled? This option can be changed by the player in the menu config |
| admin | bool | If the shop is an Admin shop, so you don't have to put items in the inventory and you won't earn anything |
| title | string | The real name of the shop |
| namevisible | bool | Should the shop name be visible when summoned? |
+| history | string | A base64 encoded string with all the transactions |
| inventory | array | The inventory of the shop |
| items | array | A list of all recepies (max 9) |
@@ -234,6 +270,8 @@ Also [this plugin](https://github.com/FrozenArea/TradeAPI) helped me!
- [x] Player shop
- [x] Shopkeeper's inventory updating when players buy
- [x] Shopkeeper deny a trade if the inventory is without the item
+- [x] Double trade
+- [x] Skin system
- [ ] Online shopkeeper editor
- [ ] Online shopkeeper shop (yes, you will be able to make shopping from your phone on the subway!)
diff --git a/ShopInventory.md b/ShopInventory.md
deleted file mode 100644
index 3090f3b..0000000
--- a/ShopInventory.md
+++ /dev/null
@@ -1,94 +0,0 @@
-I loved this file because it was beautiful
-
-```php
-getSize(); $a++) {
- if (!$inventory->isSlotEmpty($a)) {
- $items[$a] = json_decode(self::itemSerializator($inventory->getItem($a)));
- }
- }
- return $items;
- }
-
- public static function unserialize(string $json) : array {
- $items = [];
- foreach (json_decode($json) as $slot => $itemInfo) {
- $items[$slot] = SerializedItem::decode($itemInfo);
- }
- return $items;
- }
-
- public static function itemSerializator(Item $item) : string {
- $object = self::compoundJsonizer($item->getNamedTag());
- $iteminfo = new \stdClass;
- $iteminfo->networkitem = ItemUtils::encode($item, true);
- $iteminfo->networkitem->type = 0;
- $iteminfo->count = $item->getCount();
- $iteminfo->customname = $item->getCustomName() ?? null;
- $iteminfo->serializednbt = $object;
- return json_encode($iteminfo);
- }
-
- public static function compoundJsonizer(CompoundTag $tag, bool $toObject = false) : object|string {
- $object = new \stdClass;
- $object->__mapping = new \stdClass;
- foreach ($tag->getValue() as $name => $value) {
- //print_r($value);
- if ($value instanceof CompoundTag) {
- $t = "CompoundTag";
- $value = self::compoundJsonizer($value, true);
- } elseif ($value instanceof Tag) {
- $t = $value::class;
- $value = $value->getValue();
- }
-
- if ($value instanceof Tag) {
- // OK WTF?
- $value = $value->getValue();
- }
-
- $object->__mapping->{$name} = str_replace('pocketmine\nbt\tag\\', '', $t);
-
- $object->{$name} = $value;
- }
-
- if ($toObject) {
- return $object;
- }
-
- return json_encode($object);
- }
-
- public static function jsonCompoundier(string $json) : CompoundTag {
- $tag = new CompoundTag();
- $mapping = null;
- foreach (json_decode($json) as $key => $value) {
- if ($key == "__mapping") {
- $mapping = $value;
- continue;
- }
-
- $type = str_replace('Tag', '', 'set' . $mapping->{$key});
-
- if ($type == 'setCompound') {
- $tag->setTag($key, self::jsonCompoundier(json_encode($value)));
- continue;
- }
-
- $tag->{$type}($key, $value);
- }
- return $tag;
- }
-}
-```
\ No newline at end of file
diff --git a/plugin-banner-white.png b/plugin-banner-white.png
deleted file mode 100644
index 53e4931..0000000
Binary files a/plugin-banner-white.png and /dev/null differ
diff --git a/plugin-banner.png b/plugin-banner.png
deleted file mode 100644
index 83250a3..0000000
Binary files a/plugin-banner.png and /dev/null differ
diff --git a/plugin.yml b/plugin.yml
index ec4282f..0c11d25 100644
--- a/plugin.yml
+++ b/plugin.yml
@@ -1,5 +1,5 @@
name: Shopkeepers
-version: 0.9.1
+version: 1.0.0
api: 5.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] "
+ usage: "/shopkeepers [list|create|summon|rename|edit|info|history] "
aliases:
- sk
- shopk
@@ -43,6 +43,12 @@ permissions:
shopkeepers.shop.namevisible:
description: "Allow users to decide if the shopkepeer's name should be visible or no"
default: true
+ shopkeepers.shop.history:
+ description: "Allow users to view the trade history of his Shopkeepers"
+ default: true
+ shopkeepers.shop.enableDisable:
+ description: "Allow users to enable and disable their own Shopkeepers from the menu"
+ default: true
shopkeepers.shop.admin:
description: "Allows users to decide if the shopkeepers should be admin or none"
default: op
diff --git a/src/FoxWorn3365/Shopkeepers/Core.php b/src/FoxWorn3365/Shopkeepers/Core.php
index 02f27ae..8606c75 100644
--- a/src/FoxWorn3365/Shopkeepers/Core.php
+++ b/src/FoxWorn3365/Shopkeepers/Core.php
@@ -36,6 +36,7 @@
use pocketmine\utils\TextFormat;
use pocketmine\Server;
use pocketmine\level\Position;
+use pocketmine\entity\Entity;
// Events
use pocketmine\event\entity\EntityDamageEvent as Damage;
@@ -49,6 +50,8 @@
use pocketmine\network\mcpe\protocol\ActorEventPacket as EntityEventPacket;
use pocketmine\network\mcpe\protocol\ItemStackRequestPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
+use pocketmine\network\mcpe\protocol\BookEditPacket;
+use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
// Custom
use FoxWorn3365\Shopkeepers\Menu\InfoMenu;
@@ -57,9 +60,11 @@
use FoxWorn3365\Shopkeepers\Menu\ListMenu;
use FoxWorn3365\Shopkeepers\Menu\ShopInfoMenu;
use FoxWorn3365\Shopkeepers\entity\Shopkeeper;
+use FoxWorn3365\Shopkeepers\entity\HumanShopkeeper;
use FoxWorn3365\Shopkeepers\shop\Manager;
use FoxWorn3365\Shopkeepers\utils\NbtManager;
use FoxWorn3365\Shopkeepers\utils\Utils;
+use FoxWorn3365\Shopkeepers\utils\SkinUtils;
// Newtork Parts
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequest;
@@ -68,6 +73,7 @@
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\DeprecatedCraftingResultsStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\convert\TypeConverter;
+use pocketmine\network\mcpe\protocol\types\inventory\UseItemOnEntityTransactionData;
// Exceptions
use pocketmine\network\mcpe\convert\TypeConversionException;
@@ -77,19 +83,19 @@ class Core extends PluginBase implements Listener {
protected EntityManager $entities;
protected object $trades;
protected object $tradeQueue;
+ protected object $handle;
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";
public const AUTHOR = "FoxWorn3365";
- public const VERSION = "0.9.1-pre";
+ public const VERSION = "1.0.0";
public function onLoad() : void {
$this->menu = new \stdClass;
$this->trades = new \stdClass;
$this->tradeQueue = new \stdClass;
+ $this->handle = new \stdClass;
}
public function onEnable() : void {
@@ -101,6 +107,7 @@ public function onEnable() : void {
// Create the config folder if it does not exists
@mkdir($this->getDataFolder());
+ @mkdir($this->getDataFolder() . 'skins');
// Check for file integrity
Utils::integrityChecker($this->getDataFolder());
@@ -131,42 +138,18 @@ public function onPlayerJoin(PlayerJoinEvent $event) {
// Create the player's object queue for managing event procession
$this->tradeQueue->{$event->getPlayer()->getName()} = false;
+ $this->handle->{$event->getPlayer()->getName()} = false;
}
public function onPlayerEntityInteract(Interaction $event) : void {
- if (!$event->getPlayer()->hasPermission("shopkeepers.shop.use")) {
- $event->getPlayer()->sendMessage(self::NOT_PERM_MSG);
- }
- $entity = $event->getEntity();
- if ($entity instanceof Shopkeeper) {
- $data = $entity->getConfig();
- $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
- $manager = new Manager($cm);
- $this->trades->{$event->getPlayer()->getName()} = new \stdClass;
- $this->trades->{$event->getPlayer()->getName()}->config = $data;
- $manager->send($event->getPlayer(), $entity);
- }
+ if (!$this->handle->{$event->getPlayer()->getName()}) {
+ $this->handle->{$event->getPlayer()->getName()} = true;
+ $this->entityInteractionLoad($event->getEntity(), $event->getPlayer());
}
}
public function onEntitySpawn(EntitySpawnEvent $event) : void {
- if ($event->getEntity() instanceof Shopkeeper) {
+ if ($event->getEntity() instanceof Shopkeeper || $event->getEntity() instanceof HumanShopkeeper) {
// Add the shopkeeper to entity interface
if (!$event->getEntity()->hasCustomShopkeeperEntityId()) {
// FIRST, check if the limit is not trepassed
@@ -196,6 +179,7 @@ public function onCommand(CommandSender $sender, Command $command, $label, array
$sender->sendMessage("This command can be only executed by in-game players!");
return false;
}
+
$shop = new ConfigManager($sender, $this->getDataFolder());
// Empty treath
@@ -229,8 +213,8 @@ public function onCommand(CommandSender $sender, Command $command, $label, array
$sender->sendMessage(self::NOT_PERM_MSG);
}
- if (empty($name = $args[1])) {
- $name = $this->generateRandomString(7);
+ if (empty($name = @$args[1])) {
+ $name = $this->generateRandomString(6);
}
foreach ($this->config->get('banned-shop-names', []) as $banned) {
@@ -302,7 +286,14 @@ public function onCommand(CommandSender $sender, Command $command, $label, array
$shopdata = new \stdClass;
$shopdata->author = $sender->getName();
$shopdata->shop = $name;
- $villager = new Shopkeeper($pos, $shopdata);
+ if (SkinUtils::find($name, $sender->getName(), $this->getDataFolder())) {
+ // Has a skin, let's summon an human entity after getting the skin
+ $skin = SkinUtils::get($name, $sender->getName(), $this->getDataFolder());
+ $villager = new HumanShopkeeper($pos, $skin, $shopdata);
+ } else {
+ // A simple Shopkeeper, so summon a villager-like entity
+ $villager = new Shopkeeper($pos, $shopdata);
+ }
$villager->setNameTag($name);
$villager->setNameTagAlwaysVisible($shop->get()->{$name}->namevisible);
$villager->spawnToAll();
@@ -311,6 +302,40 @@ public function onCommand(CommandSender $sender, Command $command, $label, array
} elseif ($args[0] === "remove" || $args[0] === "despawn") {
$sender->sendMessage("To remove a shopkeeper just hit it!");
return true;
+ } elseif ($args[0] === "history" && !empty($args[1])) {
+ if (!$sender->hasPermission("shopkeepers.shop.history")) {
+ $sender->sendMessage(self::NOT_PERM_MSG);
+ }
+
+ $name = $args[1];
+ if (@$shop->get()?->{$name} === null) {
+ $sender->sendMessage("You don't have a shop called {$name}!");
+ return false;
+ }
+
+ if (!empty($args[2])) {
+ $page = $args[2];
+ } else {
+ $page = 1;
+ }
+
+ $history = (array)json_decode(base64_decode(@$shop->get()->{$name}->history));
+
+ // Let's divide in pages
+ $pages = ceil(count($history)/20);
+
+ if ($page > $pages) {
+ $sender->sendMessage("§cSorry but there are only {$pages} pages!");
+ } else {
+ $message = "§lTrade history for Shopkeeper {$name}.§r\nPage §l{$page}§r/{$pages}\n";
+ for ($a = ($page-1)*20; $a < $page*20; $a++) {
+ if (!empty($history[$a])) {
+ $message .= "\n" . $history[$a];
+ }
+ }
+ $sender->sendMessage($message);
+ }
+ return true;
} elseif ($args[0] === "rename" && !empty($args[1]) && !empty($args[2])) {
if (!$sender->hasPermission("shopkeepers.shop.rename")) {
$sender->sendMessage(self::NOT_PERM_MSG);
@@ -348,13 +373,21 @@ public function onPacket(DataPacketReceiveEvent $event) : void {
$ic->slot = false;
$ic->cconsume = false;
$ic->specialstack = false;
+ $ic->finalconsume = false;
+ $ic->count = 1;
+ $ic->added = false;
+ $count = 1;
$cm = null;
$quota = 0;
$itemglobal = null;
$inventoryInsideConfig = null;
+ $log = "";
+ $stackcount = 1;
+ $maxcount = 1;
+
if ($event->getPacket() instanceof ItemStackRequestPacket) {
$inventory = $event->getOrigin()->getPlayer()->getInventory();
-
+ $maxcount = count($event->getPacket()->getRequests());
foreach ($event->getPacket()->getRequests() as $request) {
if ($request instanceof ItemStackRequest) {
foreach ($request->getActions() as $action) {
@@ -365,7 +398,10 @@ public function onPacket(DataPacketReceiveEvent $event) : void {
}
$this->tradeQueue->{$event->getOrigin()->getPlayer()->getName()} = true;
if (@$this->trades->{$event->getOrigin()->getPlayer()->getName()} !== null) {
- $this->trades->{$event->getOrigin()->getPlayer()->getName()}->items = [];
+ $this->trades->{$event->getOrigin()->getPlayer()->getName()}->itemsAdd = [];
+ $this->trades->{$event->getOrigin()->getPlayer()->getName()}->quota = [];
+ // Update the log with the player
+ $log = date("d/m/Y - H:i:s") . " >> Player §l{$event->getOrigin()->getPlayer()->getName()} §rpurchased ";
// Save the config manager
$cm = new ConfigManager($this->trades->{$event->getOrigin()->getPlayer()->getName()}->config->author, $this->getDataFolder());
$cm->setSingleKey($this->trades->{$event->getOrigin()->getPlayer()->getName()}->config->shop);
@@ -390,6 +426,7 @@ public function onPacket(DataPacketReceiveEvent $event) : void {
}
*/
$item->setCount($result->getCount());
+ $log .= "§l{$item->getCount()}§r {$item->getName()} for ";
// Before set this we need to check and update the villager's inventory
$total = $result->getCount();
if (!$cm->get()->{$cm->getSingleKey()}->admin) {
@@ -423,7 +460,7 @@ public function onPacket(DataPacketReceiveEvent $event) : void {
}
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!");
+ Utils::errorLogger($this->getDataFolder(), "ERROR", "InventoryInsideConfig at Core.php#476 was an object and not an array!\nPlase report this with an issue!");
$inventoryInsideConfig = [];
}
@@ -431,7 +468,7 @@ public function onPacket(DataPacketReceiveEvent $event) : void {
$object->inventory = $inventoryInsideConfig;
$cm->set($cm->getSingleKey(), $object);
}
- $this->trades->{$event->getOrigin()->getPlayer()->getName()}->items[] = $item;
+ $this->trades->{$event->getOrigin()->getPlayer()->getName()}->itemsAdd[] = $item;
}
}
}
@@ -440,90 +477,121 @@ public function onPacket(DataPacketReceiveEvent $event) : void {
} elseif ($action instanceof CraftingConsumeInputStackRequestAction) {
if (!$ic->cconsume && $ic->crafting && !$ic->slot) {
$ic->cconsume = true;
- $this->trades->{$event->getOrigin()->getPlayer()->getName()}->quota = $action->getCount();
+ $this->trades->{$event->getOrigin()->getPlayer()->getName()}->quota[] = $action->getCount();
+ } elseif (!$ic->slot && $ic->cconsume && $ic->crafting && !$ic->finalconsume) {
+ $ic->finalconsume = true;
+ $ic->count = 2;
+ $this->trades->{$event->getOrigin()->getPlayer()->getName()}->quota[] = $action->getCount();
}
} elseif ($action instanceof PlaceStackRequestAction) {
if (!$ic->slot && $ic->crafting && $ic->cconsume && $cm instanceof ConfigManager) {
$ic->slot = true;
- $dest = $action->getDestination()->getSlotId();
- // Put cctr to slot
- if (@$this->trades->{$event->getOrigin()->getPlayer()->getName()} !== null) {
- $data = $this->trades->{$event->getOrigin()->getPlayer()->getName()};
- $quota = $this->trades->{$event->getOrigin()->getPlayer()->getName()}->quota;
- $referredItem = clone $data->item;
- if ($quota <= 0) {
- // Error on qta
- return;
- }
- if ($data->count > 1) {
- $this->trades->{$event->getOrigin()->getPlayer()->getName()}->count = $this->trades->{$event->getOrigin()->getPlayer()->getName()}->count - $quota;
- $data->item->setCount($data->count - $quota);
- foreach ($this->trades->{$event->getOrigin()->getPlayer()->getName()}->items as $item) {
- $event->getOrigin()->getPlayer()->getInventory()->addItem($item);
+ for ($a = 0; $a < $ic->count; $a++) {
+ $localCount = $a;
+
+ $dest = $action->getDestination()->getSlotId();
+ // Put cctr to slot
+ if (@$this->trades->{$event->getOrigin()->getPlayer()->getName()} !== null) {
+ $data = $this->trades->{$event->getOrigin()->getPlayer()->getName()};
+ $quota = $data->quota[$localCount];
+ $referredItem = clone $data->items[$localCount]->item;
+ if ($quota <= 0) {
+ // Error on qta
+ return;
}
- $first = $event->getOrigin()->getPlayer()->getInventory()->first($data->item);
- if ($first > -1) {
- $item = $event->getOrigin()->getPlayer()->getInventory()->getItem($first);
- $item->setCount($item->getCount() - $quota);
- $event->getOrigin()->getPlayer()->getInventory()->setItem($first, $item);
- } elseif ($first == -1 && $data->item->getCount() !== 0) {
- $item = $data->item;
- $item->setCount($item->getCount() - $quota);
- $event->getOrigin()->getPlayer()->getInventory()->addItem($item);
+
+ if ($a === 0) {
+ $log .= "§l" . $quota . "§r " . $data->items[$localCount]->item->getName();
+ } else {
+ $log .= " and §l" . $quota . "§r " . $data->items[$localCount]->item->getName();
+ $config = $cm->get()->{$cm->getSingleKey()};
+ $inv = (array)json_decode(base64_decode($config->history));
+ $inv[] = $log;
+ $config->history = base64_encode(json_encode($inv));
+ $cm->set($cm->getSingleKey(), $config);
}
- // Now add the earned to the Shopkeeper's inventory!
- $count = $quota;
- $posed = false;
- $inventoryInsideConfig = $cm->get()->{$cm->getSingleKey()}->inventory;
- if (!$cm->get()->{$cm->getSingleKey()}->admin) {
- foreach ($inventoryInsideConfig as $slot => $indexedItem) {
- $indexedItem = NbtManager::decode($indexedItem);
- if ($indexedItem->equals($referredItem)) {
- // Add if is not 64
- if ($indexedItem->getCount() + $count <= 64) {
- // Perfect, add and exit!
- $indexedItem->setCount($indexedItem->getCount() + $count);
- $inventoryInsideConfig[$slot] = NbtManager::encode($indexedItem);
- $posed = true;
- $count = 0;
- break;
- } elseif ($indexedItem->getCount() + $count > 64) {
- // Add what we can
- $count = $count - (64 - $indexedItem->getCount());
- $indexedItem->setCount(64);
- $inventoryInsideConfig[$slot] = NbtManager::encode($indexedItem);
- }
+
+ if ($data->items[$localCount]->count > 0) {
+ $this->trades->{$event->getOrigin()->getPlayer()->getName()}->count = $this->trades->{$event->getOrigin()->getPlayer()->getName()}->items[$localCount]->count - $quota;
+ $data->items[$localCount]->item->setCount($data->count - $quota);
+ foreach ($this->trades->{$event->getOrigin()->getPlayer()->getName()}->itemsAdd as $item) {
+ if (!$ic->added) {
+ $ic->added = true;
+ $event->getOrigin()->getPlayer()->getInventory()->addItem($item);
}
}
-
- if (!$posed && $count <= 64 && $count > 0) {
- for ($a = 0; $a < 51; $a++) {
- if (empty($inventoryInsideConfig[$a])) {
- $referredItem->setCount($count);
- $inventoryInsideConfig[$a] = NbtManager::encode($referredItem);
- break;
+ $first = $event->getOrigin()->getPlayer()->getInventory()->first($data->items[$localCount]->item);
+ if ($first > -1) {
+ $item = $event->getOrigin()->getPlayer()->getInventory()->getItem($first);
+ $item->setCount($item->getCount() - $quota);
+ $event->getOrigin()->getPlayer()->getInventory()->setItem($first, $item);
+ } elseif ($first == -1 && $data->items[$localCount]->item->getCount() !== 0) {
+ $item = $data->items[$localCount]->item;
+ $item->setCount($item->getCount() - $quota);
+ $event->getOrigin()->getPlayer()->getInventory()->addItem($item);
+ }
+ // Now add the earned to the Shopkeeper's inventory!
+ $count = $quota;
+ $posed = false;
+ $inventoryInsideConfig = $cm->get()->{$cm->getSingleKey()}->inventory;
+ if (!$cm->get()->{$cm->getSingleKey()}->admin) {
+ foreach ($inventoryInsideConfig as $slot => $indexedItem) {
+ $indexedItem = NbtManager::decode($indexedItem);
+ if ($indexedItem->equals($referredItem)) {
+ // Add if is not 64
+ if ($indexedItem->getCount() + $count <= 64) {
+ // Perfect, add and exit!
+ $indexedItem->setCount($indexedItem->getCount() + $count);
+ $inventoryInsideConfig[$slot] = NbtManager::encode($indexedItem);
+ $posed = true;
+ $count = 0;
+ break;
+ } elseif ($indexedItem->getCount() + $count > 64) {
+ // Add what we can
+ $count = $count - (64 - $indexedItem->getCount());
+ $indexedItem->setCount(64);
+ $inventoryInsideConfig[$slot] = NbtManager::encode($indexedItem);
+ }
+ }
+ }
+
+ if (!$posed && $count <= 64 && $count > 0) {
+ for ($a = 0; $a < 51; $a++) {
+ if (empty($inventoryInsideConfig[$a])) {
+ $referredItem->setCount($count);
+ $inventoryInsideConfig[$a] = NbtManager::encode($referredItem);
+ break;
+ }
}
}
+
+ $config = $cm->get()->{$cm->getSingleKey()};
+ $config->inventory = $inventoryInsideConfig;
+ $cm->set($cm->getSingleKey(), $config);
}
-
- $config = $cm->get()->{$cm->getSingleKey()};
- $config->inventory = $inventoryInsideConfig;
- $cm->set($cm->getSingleKey(), $config);
- }
- }
- } else {
- $event->getOrigin()->getPlayer()->sendMessage("§cError!\nIt seems that you are not trading rn!");
+ $count++;
+ }
+ } else {
+ $event->getOrigin()->getPlayer()->sendMessage("§cError!\nIt seems that you are not trading rn!");
+ }
}
+ $this->trades->{$event->getOrigin()->getPlayer()->getName()} = null;
} else {
if (!$ic->specialstack) {
- $ic->specialstack = true;
+ if ($count > $maxcount) {
+ $ic->specialstack = true;
+ }
+
if (@$this->trades->{$event->getOrigin()->getPlayer()->getName()} !== null && $action->getSource()->getContainerId() != 47) {
// So we need to get the item from the slot
if ($this->trades->{$event->getOrigin()->getPlayer()->getName()} instanceof \stdClass) {
// If it's stdClass it's beautiful!
if ($event->getOrigin()->getPlayer()->getInventory()->getSize() > $action->getSource()->getSlotId()) {
- $this->trades->{$event->getOrigin()->getPlayer()->getName()}->item = @$event->getOrigin()->getPlayer()->getInventory()->getItem($action->getSource()->getSlotId()) ?? VanillaItems::AIR();
- $this->trades->{$event->getOrigin()->getPlayer()->getName()}->count = $action->getCount();
+ $item = new \stdClass;
+ $item->item = @$event->getOrigin()->getPlayer()->getInventory()->getItem($action->getSource()->getSlotId()) ?? VanillaItems::AIR();
+ $item->count = $action->getCount();
+ $this->trades->{$event->getOrigin()->getPlayer()->getName()}->items[] = $item;
+ $stackcount++;
}
}
}
@@ -539,14 +607,26 @@ public function onPacket(DataPacketReceiveEvent $event) : void {
}
}
} elseif ($event->getPacket() instanceof ContainerClosePacket) {
+ $this->handle->{$event->getOrigin()->getPlayer()->getName()} = false;
if (@$this->trades->{$event->getOrigin()->getPlayer()->getName()} !== null) {
$this->trades->{$event->getOrigin()->getPlayer()->getName()} = null;
}
+ } elseif ($event->getPacket() instanceof InventoryTransactionPacket && $event->getPacket()->trData instanceof UseItemOnEntityTransactionData && $event->getPacket()->trData->getActionType() === UseItemOnEntityTransactionData::ACTION_INTERACT) {
+ //$entity = $this->getServer()->getWorldManager()->findEntity($event->getPacket()->trData->getEntityRuntimeId());
+ $entity = $this->getServer()->getWorldManager()->findEntity($event->getPacket()->trData->getActorRuntimeId());
+ $player = $entity->getWorld()->getNearestEntity($event->getPacket()->trData->getPlayerPosition(), 2);
+
+ if (($entity instanceof Shopkeeper || $entity instanceof HumanShopkeeper) && $player instanceof Player) {
+ if (!$this->handle->{$player->getName()}) {
+ $this->handle->{$player->getName()} = true;
+ $this->entityInteractionLoad($entity, $player);
+ }
+ }
}
}
- public function onEntityDamage(Damage $event) {
- if ($event->getEntity() instanceof Shopkeeper) {
+ public function onEntityDamage(Damage $event) : void {
+ if ($event->getEntity() instanceof Shopkeeper || $event->getEntity() instanceof HumanShopkeeper) {
if ($event instanceof EntityDamageByEntityEvent) {
if (@$event->getDamager() === null) {
$event->cancel();
@@ -574,6 +654,43 @@ public function onEntityDamage(Damage $event) {
}
}
+ public function entityInteractionLoad(Entity $entity, Player $player) : void {
+ if (!$player->hasPermission("shopkeepers.shop.use")) {
+ $player->sendMessage(self::NOT_PERM_MSG);
+ }
+ if ($entity instanceof Shopkeeper || $entity instanceof HumanShopkeeper) {
+ $data = $entity->getConfig();
+ $cm = new ConfigManager($data->author, $this->getDataFolder());
+ $cm->setSingleKey($data->shop);
+ if (@$cm->get()->{$data->shop} === null) {
+ // Oh no, no config!
+ $player->sendMessage("§cSorry but this shop does not exists anymore!");
+ // Remove the shop
+ $this->entities->remove($this->entities->generateEntityHash($entity));
+ $entity->kill();
+ return;
+ } elseif ($data->author === $player->getName() && !$player->isSneaking()) {
+ // Open the shopkeeper's ~~inventory~~ info page RN!
+ $menu = new ShopInfoMenu($cm, true);
+ $menu->create()->send($player);
+ } else {
+ // It's a shopkeeper!
+ // BEAUTIFUL!
+ // Now let's open the shopkeeper interface
+ // First, check if the shop is enabled
+ if (@$cm->get()->{$cm->getSingleKey()}->enabled) {
+ $manager = new Manager($cm);
+ $this->trades->{$player->getName()} = new \stdClass;
+ $this->trades->{$player->getName()}->config = $data;
+ $this->trades->{$player->getName()}->items = [];
+ $manager->send($player, $entity);
+ } else {
+ $event->getPlayer()->sendMessage("Sorry but this shop is §cdisabled§r!");
+ }
+ }
+ }
+ }
+
// https://stackoverflow.com/questions/4356289/php-random-string-generator
// I'm only lazy
protected function generateRandomString($length = 10) : string {
diff --git a/src/FoxWorn3365/Shopkeepers/EntityManager.php b/src/FoxWorn3365/Shopkeepers/EntityManager.php
index b1571b1..696758b 100644
--- a/src/FoxWorn3365/Shopkeepers/EntityManager.php
+++ b/src/FoxWorn3365/Shopkeepers/EntityManager.php
@@ -23,7 +23,9 @@
use pocketmine\entity\Location;
use pocketmine\Server;
+use FoxWorn3365\Shopkeepers\utils\SkinUtils;
use FoxWorn3365\Shopkeepers\entity\Shopkeeper;
+use FoxWorn3365\Shopkeepers\entity\HumanShopkeeper;
class EntityManager {
protected string $base;
@@ -49,7 +51,7 @@ protected function retrive() : void {
}
}
- public function add(Shopkeeper $shop) : void {
+ public function add(Shopkeeper|HumanShopkeeper $shop) : void {
// x, y, yw, z, pitch, world, (base64)data
//$this->elements[] = "{$shop->getLocation()->getX()},{$shop->getLocation()->getY()},{$shop->getLocation()->getYaw()},{$shop->getLocation()->getZ()},{$shop->getLocation()->getPitch()},{$shop->getWorld()->getId()}," . base64_encode(json_encode($shop->getConfig()));
$this->elements[] = $this->generateEntityHash($shop);
@@ -61,17 +63,7 @@ public function get(int $slot) : ?string {
return $this->elements[$slot];
}
- public function loadAll(Server $server) : void {
- foreach ($this->elements as $data) {
- $data = explode(",", $data);
- $location = new Location($data[0], $data[1], $data[3], $server->getWorldManager()->getWorld($data[5]), $data[2], $data[4]);
- $shopkeeper = new Shopkeeper($location);
- $shopkeeper->setConfig(json_decode(base64_decode($data[6])));
- $shopkeeper->spawnToAll();
- }
- }
-
- public function generateEntityHash(Shopkeeper $shop) : string {
+ public function generateEntityHash(Shopkeeper|HumanShopkeeper $shop) : string {
// GitHub user: "Use object plz"
// Me: okok i'll do it AND NOW ITZ TIMW!
return base64_encode(json_encode([
@@ -128,10 +120,15 @@ public function loadPlayer(Player $player) : void {
}
}
- protected static function createEntity(string $rawdata, Server $server) : Shopkeeper {
+ protected static function createEntity(string $rawdata, Server $server) : Shopkeeper|HumanShopkeeper {
$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, json_decode(base64_decode($data->config)), $data->id);
+ if (SkinUtils::find(json_decode(base64_decode($data->config))->shop, json_decode(base64_decode($data->config))->author, $server->getPluginManager()->getPlugin("Shopkeepers")->getDataFolder())) {
+ $skin = SkinUtils::load(json_decode(base64_decode($data->config))->shop, json_decode(base64_decode($data->config))->author, $server->getPluginManager()->getPlugin("Shopkeepers")->getDataFolder());
+ $entity = new HumanShopkeeper($location, $skin, json_decode(base64_decode($data->config)), $data->id);
+ } else {
+ $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 892d3d2..0e70860 100644
--- a/src/FoxWorn3365/Shopkeepers/Menu/EditItemMenu.php
+++ b/src/FoxWorn3365/Shopkeepers/Menu/EditItemMenu.php
@@ -21,6 +21,7 @@
namespace FoxWorn3365\Shopkeepers\Menu;
use pocketmine\item\VanillaItems;
+use pocketmine\block\VanillaBlocks;
use pocketmine\item\Item;
// Pocketmine Network part
@@ -84,8 +85,9 @@ function edit() : InvMenu {
$slot->sellsign = 12;
$slot->buysign = 9;
- $sell = $object->sell;
- $buy = $object->buy;
+ $sell = @$object->sell;
+ $buy = @$object->buy;
+ $buy2 = @$object->buy2;
$signsell = Factory::sign(4, 'Put what do you want to sell to my right!');
$signbuy = Factory::sign(4, 'Put what do you want to buy to my right!');
@@ -111,13 +113,21 @@ function edit() : InvMenu {
$this->menu->getInventory()->setItem(1, Factory::item(35, 13, "+1"));
$this->menu->getInventory()->setItem(19, Factory::item(35, 14, "-1"));
+ // 2nd buy qta increasator and decreasator
+ $this->menu->getInventory()->setItem(2, Factory::item(35, 13, "+1"));
+ $this->menu->getInventory()->setItem(20, Factory::item(35, 14, "-1"));
+
// Sell qta increasator and decreasator
- $this->menu->getInventory()->setItem(4, Factory::item(35, 13, "+1"));
- $this->menu->getInventory()->setItem(22, Factory::item(35, 14, "-1"));
+ $this->menu->getInventory()->setItem(5, Factory::item(35, 13, "+1"));
+ $this->menu->getInventory()->setItem(23, Factory::item(35, 14, "-1"));
+ //$this->menu->getInventory()->setItem(11, Factory::stringItem("minecraft:chest", "porcodio"));
// Put data
$this->menu->getInventory()->setItem(10, $buyitem);
- $this->menu->getInventory()->setItem(13, $sellitem);
+ if ($buy2 !== null && SerializedItem::decode($buy2)->getName() !== "Air") {
+ $this->menu->getInventory()->setItem(11, SerializedItem::decode($buy2));
+ }
+ $this->menu->getInventory()->setItem(14, $sellitem);
$cm = $this->cm;
$config = $this->config;
@@ -163,23 +173,49 @@ function edit() : InvMenu {
$object->buy = SerializedItem::encode($item);
}
break;
- case 4:
- $item = $inventory->getItem(13);
+ case 2:
+ $item = $inventory->getItem(11);
+ if ($item->getName() === "Air") {
+ break;
+ }
+ if ($item->getCount() + 1 > 64) {
+ $transaction->getPlayer()->sendMessage("§cYou can't sell an item for more than 64 items!");
+ } else {
+ $item->setCount($item->getCount()+1);
+ $inventory->setItem(11, $item);
+ $object->buy2 = SerializedItem::encode($item);
+ }
+ break;
+ case 20:
+ $item = $inventory->getItem(11);
+ if ($item->getName() === "Air") {
+ break;
+ }
+ if ($item->getCount()-1 < 1) {
+ $transaction->getPlayer()->sendMessage("§cYou can't sell an item for less than 1 item!");
+ } else {
+ $item->setCount($item->getCount()-1);
+ $inventory->setItem(11, $item);
+ $object->buy2 = SerializedItem::encode($item);
+ }
+ break;
+ case 5:
+ $item = $inventory->getItem(14);
if ($item->getCount()+1 > 64) {
$transaction->getPlayer()->sendMessage("§cYou can't sell more than 64 items!");
} else {
$item->setCount($item->getCount()+1);
- $inventory->setItem(13, $item);
+ $inventory->setItem(14, $item);
$object->sell = SerializedItem::encode($item);
}
break;
- case 22:
- $item = $inventory->getItem(13);
+ case 23:
+ $item = $inventory->getItem(14);
if ($item->getCount()-1 < 1) {
$transaction->getPlayer()->sendMessage("§cYou can't sell less than 1 item!");
} else {
$item->setCount($item->getCount()-1);
- $inventory->setItem(13, $item);
+ $inventory->setItem(14, $item);
$object->sell = SerializedItem::encode($item);
}
break;
@@ -193,14 +229,39 @@ function edit() : InvMenu {
$inventory->setItem(10, $transaction->getItemClickedWith());
}
break;
- case 13:
+ case 14:
if ($transaction->getItemClickedWith() !== null && $transaction->getItemClickedWith() != VanillaItems::AIR()) {
// Let's change the object also in the inventory
- $inventory->clear(13);
+ $inventory->clear(14);
// Now let's decode the item
$object->sell = SerializedItem::encode($transaction->getItemClickedWith());
usleep(5000);
- $inventory->setItem(13, $transaction->getItemClickedWith());
+ $inventory->setItem(14, $transaction->getItemClickedWith());
+ }
+ break;
+ case 11:
+ $presence = $inventory->getItem(10);
+ if (@$presence->getName() == "Air" || $presence === null) {
+ $transaction->getPlayer()->sendMessage("§4Sorry but you cannot cannot set the first buy item!");
+ usleep(2500);
+ $inventory->clear(11);
+ $object->buy2 = null;
+ break;
+ }
+
+ if ($transaction->getItemClickedWith() !== null && @$transaction->getItemClickedWith()->getVanillaName() != "Air") {
+ // Let's change the object also in the inventory
+ $inventory->clear(11);
+ // Now let's decode the item
+ $object->buy2 = SerializedItem::encode($transaction->getItemClickedWith());
+ usleep(5000);
+ $inventory->setItem(11, $transaction->getItemClickedWith());
+ } elseif ($transaction->getItemClickedWith() === null || @$transaction->getItemClickedWith()->getVanillaName() == "Air") {
+ $object->buy2 = null;
+ usleep(2500);
+ $inventory->clear(11);
+ } else {
+ var_dump($transaction->getItemClickedWith()->getBlock()->getName());
}
break;
}
diff --git a/src/FoxWorn3365/Shopkeepers/Menu/EditMenu.php b/src/FoxWorn3365/Shopkeepers/Menu/EditMenu.php
index 6929d50..efcc26b 100644
--- a/src/FoxWorn3365/Shopkeepers/Menu/EditMenu.php
+++ b/src/FoxWorn3365/Shopkeepers/Menu/EditMenu.php
@@ -46,7 +46,7 @@ function __construct(ConfigManager $cm, string $name) {
}
public function create() : InvMenu {
- $this->menu->setName("Edit shop {$this->config->title}");
+ $this->menu->setName("§l§bTrades §r§l- §r{$this->config->title}");
// LAST SLOT: 26
$slotcount = 9;
$defaultconfig = (object)[
@@ -82,17 +82,24 @@ public function create() : InvMenu {
if ($itemconstructor->getVanillaName() == 'Stained Glass Pane') {
$displayname = "No item set!";
$setname = "Nothing";
+ $displaycount = "";
} else {
$displayname = $itemconstructor->getVanillaName();
+ $displaycount = $itemconstructor->getCount() . ' ';
// Load buy block and add to the description
if (@$bk->buy !== null) {
$buy = SerializedItem::decode($bk->buy);
- $setname = "{$buy->getCount()} {$buy->getName()}";
+ $setname = "§l{$buy->getCount()} §r{$buy->getName()}";
} else {
$setname = "Nothing!";
}
+
+ if (@$bk->buy2 !== null) {
+ $buy2 = SerializedItem::decode($bk->buy2);
+ $setname = "{$setname} and for §l{$buy2->getCount()} §r{$buy2->getName()}";
+ }
}
- $itemconstructor->setCustomName("§r{$displayname}\nSold for: {$setname}");
+ $itemconstructor->setCustomName("§r§l{$displaycount}{$displayname}\n\n§r§oSold for:§r {$setname}");
$itemmenu = Utils::getIntItem(35, 1);
$itemmenu->setCustomName("§rEdit this item");
$this->menu->getInventory()->setItem($slotcount, $itemconstructor);
diff --git a/src/FoxWorn3365/Shopkeepers/Menu/InfoMenu.php b/src/FoxWorn3365/Shopkeepers/Menu/InfoMenu.php
index 6fd8f26..7b1050c 100644
--- a/src/FoxWorn3365/Shopkeepers/Menu/InfoMenu.php
+++ b/src/FoxWorn3365/Shopkeepers/Menu/InfoMenu.php
@@ -39,7 +39,7 @@ function __construct() {
}
public function create(Player $player, string $basedir) : InvMenu {
- $this->menu->setName("Welcome to Shopkeepers");
+ $this->menu->setName("§b§lShopkeepers");
$inventory = $this->menu->getInventory();
// Draw the upper and downer line
diff --git a/src/FoxWorn3365/Shopkeepers/Menu/ShopConfigMenu.php b/src/FoxWorn3365/Shopkeepers/Menu/ShopConfigMenu.php
index 0302f85..2eab368 100644
--- a/src/FoxWorn3365/Shopkeepers/Menu/ShopConfigMenu.php
+++ b/src/FoxWorn3365/Shopkeepers/Menu/ShopConfigMenu.php
@@ -47,7 +47,7 @@ function __construct(ConfigManager $cm) {
}
public function create() : InvMenu {
- $this->menu->setName("Edit shop {$this->cm->getSingleKey()}");
+ $this->menu->setName("§6§lConfig §r§l- §r{$this->cm->getSingleKey()}");
$inventory = $this->menu->getInventory();
// Draw the upper and downer line
diff --git a/src/FoxWorn3365/Shopkeepers/Menu/ShopInfoMenu.php b/src/FoxWorn3365/Shopkeepers/Menu/ShopInfoMenu.php
index 8f9041a..7520d58 100644
--- a/src/FoxWorn3365/Shopkeepers/Menu/ShopInfoMenu.php
+++ b/src/FoxWorn3365/Shopkeepers/Menu/ShopInfoMenu.php
@@ -30,8 +30,10 @@
use FoxWorn3365\Shopkeepers\utils\Factory;
use FoxWorn3365\Shopkeepers\utils\NbtManager;
use FoxWorn3365\Shopkeepers\ConfigManager;
+use FoxWorn3365\Shopkeepers\utils\SkinUtils;
use FoxWorn3365\Shopkeepers\entity\Shopkeeper;
+use FoxWorn3365\Shopkeepers\entity\HumanShopkeeper;
class ShopInfoMenu {
protected InvMenu $menu;
@@ -49,7 +51,7 @@ function __construct(ConfigManager $cm, bool $local = false) {
}
public function create() : InvMenu {
- $this->menu->setName("View shop {$this->cm->getSingleKey()}");
+ $this->menu->setName("§b§lInfo §r§l- §r{$this->cm->getSingleKey()}");
$inventory = $this->menu->getInventory();
// Draw the useful line
@@ -60,38 +62,39 @@ public function create() : InvMenu {
$inventory->setItem(4, Factory::egg($this->cm->getSingleKey()));
// Now set the informations
- $inventory->setItem(10, Factory::item(377, 0, '§lConfig'));
+ $inventory->setItem(10, Factory::item(377, 0, "§l§6Config\n\n§r§oSee settings for this Shopkeeper"));
// Villager inventory
if (!$this->config->admin) {
- $inventory->setItem(12, Factory::item(54, 0, "§lInventory"));
+ $inventory->setItem(12, Factory::stringItem("minecraft:chest", "§l§9Inventory\n\n§r§oSee the inventory of the Shopkeeper"));
} else {
- $inventory->setItem(12, Factory::barrier("§l§cShop inventory\n§rDisabled!\n§oThis is an admin shop!")); // ID: -161 Meta: 0 BRID: 10390
+ $inventory->setItem(12, Factory::barrier("§l§cShop inventory\n\n§rDisabled!\n§oThis is an admin shop!"));
}
// 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"));
+ if (@!$this->config->enabled) {
+ $inventory->setItem(20, Factory::stringItem("minecraft:torch", "§2§lEnable\n\n§r§oEnable this Shopkeeper. Yeeeee"));
+ } else {
+ $inventory->setItem(20, Factory::stringItem("minecraft:torch", "§4§lDisable\n\n§r§oDisable this Shopkeeper until a new order (yes, from you)"));
+ }
// Summon option
- $head = Utils::getItem("minecraft:skull");
- $head->setCustomName("§r§lSummon");
- $inventory->setItem(22, $head);
+ $inventory->setItem(22, Factory::stringItem("minecraft:skull", "§l§8Summon\n\n§r§oSummon an entity for this shop.\n§e§oNOTE: §r§oIt will look in your current direction!"));
// Misteryous option
- $inventory->setItem(24, Factory::barrier("§oUnknown\n\nThis function will be implemented with the §bSales & Shops §r§oupdate AKA §lv1.0"));
+ $inventory->setItem(24, Factory::stringItem("minecraft:writable_book", "§b§lTrades History\n\n§r§oSee the trades history of this Shopkeeper"));
// Edit Shopkeepers trades
- $st = Utils::getItem("minecraft:smithing_table");
- $st->setCustomName("§r§lTrades");
- $inventory->setItem(14, $st);
+ $inventory->setItem(14, Factory::stringItem("minecraft:smithing_table", "§d§lTrades\n\n§r§oView and edit the Shopkeeper's trades"));
- $inventory->setItem(16, Factory::item(35, 14, "§c§lDelete"));
+ // Delete options
+ $inventory->setItem(16, Factory::item(35, 14, "§c§lDelete\n\n§r§oThis Shopkeeper will be deleted §cFOREVER§r§o!"));
$cm = $this->cm;
$config = $this->config;
$local = $this->local;
- $this->menu->setListener(function($transaction) use ($cm, $config, $local) {
+ $this->menu->setListener(function($transaction) use ($cm, &$config, $local) {
$slot = $transaction->getAction()->getSlot();
switch ($slot) {
case 10:
@@ -137,13 +140,59 @@ public function create() : InvMenu {
$shopdata = new \stdClass;
$shopdata->author = $transaction->getPlayer()->getName();
$shopdata->shop = $cm->getSingleKey();
- $villager = new Shopkeeper($transaction->getPlayer()->getLocation());
+ if (SkinUtils::find($cm->getSingleKey(), $transaction->getPlayer()->getName(), $transaction->getPlayer()->getServer()->getPluginManager()->getPlugin("Shopkeepers")->getDataFolder())) {
+ // Has a skin, let's summon an human entity after getting the skin
+ $skin = SkinUtils::get($cm->getSingleKey(), $transaction->getPlayer()->getName(), $transaction->getPlayer()->getServer()->getPluginManager()->getPlugin("Shopkeepers")->getDataFolder());
+ $villager = new HumanShopkeeper($transaction->getPlayer()->getLocation(), $skin, $shopdata);
+ } else {
+ // A simple Shopkeeper, so summon a villager-like entity
+ $villager = new Shopkeeper($transaction->getPlayer()->getLocation(), $shopdata);
+ }
$villager->setNameTag($cm->getSingleKey());
$villager->setNameTagAlwaysVisible($config->namevisible);
- $villager->setConfig($shopdata);
$villager->spawnToAll();
$transaction->getPlayer()->removeCurrentWindow();
break;
+ case 20:
+ if (!$transaction->getPlayer()->hasPermission("shopkeepers.shop.enableDisable")) {
+ $transaction->getPlayer()->removeCurrentWindow();
+ $transaction->getPlayer()->sendMessage(self::NOT_PERM_MSG);
+ break;
+ }
+
+ if (@!$config->enabled) {
+ $config->enabled = true;
+ } else {
+ $config->enabled = false;
+ }
+
+ $cm->set($cm->getSingleKey(), $config);
+ $transaction->getPlayer()->removeCurrentWindow();
+ break;
+ case 24:
+ if (!$transaction->getPlayer()->hasPermission("shopkeepers.shop.history")) {
+ $transaction->getPlayer()->removeCurrentWindow();
+ $transaction->getPlayer()->sendMessage(self::NOT_PERM_MSG);
+ break;
+ }
+
+ $transaction->getPlayer()->removeCurrentWindow();
+ $transaction->getPlayer()->sendMessage("For the complete history please use /sk history [PAGE]");
+ $array = (array)json_decode(base64_decode($config->history));
+ if (count($array) > 20) {
+ $message = "§lLast 20 trades for this Shopkeeper:§r\n";
+ $count = count($array) - 20;
+ } else {
+ $message = "§lLast " . count($array) . " trades for this Shopkeeper:§r\n";
+ $count = 0;
+ }
+
+ for ($a = $count; $a < count($array); $a++) {
+ $item = $array[$a];
+ $message .= "\n{$item}";
+ }
+ $transaction->getPlayer()->sendMessage($message);
+ break;
}
return $transaction->discard();
});
diff --git a/src/FoxWorn3365/Shopkeepers/Menu/ShopInventoryMenu.php b/src/FoxWorn3365/Shopkeepers/Menu/ShopInventoryMenu.php
index e93b620..5905b25 100644
--- a/src/FoxWorn3365/Shopkeepers/Menu/ShopInventoryMenu.php
+++ b/src/FoxWorn3365/Shopkeepers/Menu/ShopInventoryMenu.php
@@ -45,7 +45,7 @@ function __construct(ConfigManager $cm) {
}
public function create() : InvMenu {
- $this->menu->setName("Editing {$this->cm->getSingleKey()}'s inventory...");
+ $this->menu->setName("§c§lInventory §r§l- §r{$this->cm->getSingleKey()}");
$inventory = $this->menu->getInventory();
// First, let's import the inventory
foreach ($this->config->inventory as $slot => $item) {
diff --git a/src/FoxWorn3365/Shopkeepers/entity/HumanShopkeeper.php b/src/FoxWorn3365/Shopkeepers/entity/HumanShopkeeper.php
new file mode 100644
index 0000000..d12fa5a
--- /dev/null
+++ b/src/FoxWorn3365/Shopkeepers/entity/HumanShopkeeper.php
@@ -0,0 +1,76 @@
+setCanSaveWithChunk(false);
+
+ $this->shopconfig = $generalizedConfig;
+ $this->customShopkeeperEntityId = $customId;
+ }
+
+ public function getName(): string {
+ return "Shop Villager";
+ }
+
+ public static function getNetworkTypeId(): string {
+ return "minecraft:villager";
+ }
+
+ protected function getInitialSizeInfo() : EntitySizeInfo {
+ return new EntitySizeInfo(1.8, 0.6, 1.62);
+ }
+
+ public function setConfig(object $config) : void {
+ $this->shopconfig = $config;
+ }
+
+ 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/entity/Shopkeeper.php b/src/FoxWorn3365/Shopkeepers/entity/Shopkeeper.php
index 8c6c909..14b5255 100644
--- a/src/FoxWorn3365/Shopkeepers/entity/Shopkeeper.php
+++ b/src/FoxWorn3365/Shopkeepers/entity/Shopkeeper.php
@@ -21,6 +21,7 @@
namespace FoxWorn3365\Shopkeepers\entity;
use pocketmine\entity\Villager;
+use pocketmine\entity\Human;
use pocketmine\entity\Location;
use pocketmine\entity\EntitySizeInfo;
diff --git a/src/FoxWorn3365/Shopkeepers/shop/ElementContainer.php b/src/FoxWorn3365/Shopkeepers/shop/ElementContainer.php
index 3730776..c47d9af 100644
--- a/src/FoxWorn3365/Shopkeepers/shop/ElementContainer.php
+++ b/src/FoxWorn3365/Shopkeepers/shop/ElementContainer.php
@@ -31,7 +31,7 @@ class ElementContainer {
protected array $elements = [];
public ListTag $nbt;
- public function add(Item|string $sell, Item|string $buy, array $inventory, bool $admin = false) : void {
+ public function add(Item|string $sell, Item|string $buy, array $inventory, bool $admin = false, Item|string $buy2 = null) : void {
$tag = CompoundTag::create();
// Simple tag management to optimize the speed. Anyways the system will give always a string so it's kida useless
@@ -40,6 +40,14 @@ public function add(Item|string $sell, Item|string $buy, array $inventory, bool
} else {
$tag->setTag("buyA", NbtManager::partialDecode($buy));
}
+
+ if ($buy2 !== null) {
+ if ($buy2 instanceof Item) {
+ $tag->setTag("buyB", $buy2->nbtSerialize(-1));
+ } else {
+ $tag->setTag("buyB", NbtManager::partialDecode($buy2));
+ }
+ }
if ($sell instanceof Item) {
$tag->setTag("sell", $sell->nbtSerialize(-1));
@@ -80,6 +88,7 @@ public function add(Item|string $sell, Item|string $buy, array $inventory, bool
$tag->setInt("demand", 1);
$tag->setInt("traderExp", 0);
$tag->setInt("priceMultiplierA", 0.0);
+ $tag->setInt("priceMultiplierB", 0.0);
$this->elements[] = $tag;
}
diff --git a/src/FoxWorn3365/Shopkeepers/shop/Manager.php b/src/FoxWorn3365/Shopkeepers/shop/Manager.php
index 3e40470..8f04ba7 100644
--- a/src/FoxWorn3365/Shopkeepers/shop/Manager.php
+++ b/src/FoxWorn3365/Shopkeepers/shop/Manager.php
@@ -30,13 +30,14 @@
use FoxWorn3365\Shopkeepers\utils\Utils;
use FoxWorn3365\Shopkeepers\utils\ItemUtils;
use FoxWorn3365\Shopkeepers\entity\Shopkeeper;
+use FoxWorn3365\Shopkeepers\entity\HumanShopkeeper;
class Manager {
protected Shop $shop;
protected ConfigManager $cm;
protected object $config;
protected Player $player;
- protected Shopkeeper $entity;
+ protected Shopkeeper|HumanShopkeeper $entity;
protected ElementContainer $container;
function __construct(ConfigManager $cm) {
@@ -45,15 +46,18 @@ function __construct(ConfigManager $cm) {
$this->container = new ElementContainer();
}
- public function send(Player $player, Shopkeeper $entity) : void {
+ public function send(Player $player, Shopkeeper|HumanShopkeeper $entity) : void {
$this->player = $player;
$this->entity = $entity;
foreach ($this->config->items as $itemconfig) {
if ($itemconfig === null) { continue; }
- if (!(!empty($itemconfig->sell) && !empty($itemconfig->buy)) && gettype($this->config->inventory) !== 'array') {
+ if (gettype(@$this->config->inventory) !== 'array') {
continue;
+ } else {
+ if (@$itemconfig->sell !== null && @$itemconfig->buy !== null) {
+ $this->container->add($itemconfig->sell, @$itemconfig->buy, $this->config->inventory, $this->config->admin, @$itemconfig->buy2);
+ }
}
- $this->container->add($itemconfig->sell, $itemconfig->buy, $this->config->inventory, $this->config->admin);
}
$shop = new Shop($this->container->toNBT(), $player, $entity, $this->config->title);
diff --git a/src/FoxWorn3365/Shopkeepers/shop/Shop.php b/src/FoxWorn3365/Shopkeepers/shop/Shop.php
index f70bee8..b8983f0 100644
--- a/src/FoxWorn3365/Shopkeepers/shop/Shop.php
+++ b/src/FoxWorn3365/Shopkeepers/shop/Shop.php
@@ -34,14 +34,15 @@
use pocketmine\player\Player;
use FoxWorn3365\Shopkeepers\entity\Shopkeeper;
+use FoxWorn3365\Shopkeepers\entity\HumanShopkeeper;
class Shop {
protected ListTag $elements;
protected Player $player;
- protected Shopkeeper $shop;
+ protected Shopkeeper|HumanShopkeeper $shop;
protected string $title;
- function __construct(ListTag $elements, Player $player, Shopkeeper $shop, string $title = "Trade") {
+ function __construct(ListTag $elements, Player $player, Shopkeeper|HumanShopkeeper $shop, string $title = "Trade") {
$this->elements = $elements;
$this->player = $player;
$this->shop = $shop;
diff --git a/src/FoxWorn3365/Shopkeepers/utils/Factory.php b/src/FoxWorn3365/Shopkeepers/utils/Factory.php
index 8694d10..ee8352d 100644
--- a/src/FoxWorn3365/Shopkeepers/utils/Factory.php
+++ b/src/FoxWorn3365/Shopkeepers/utils/Factory.php
@@ -61,7 +61,8 @@ public static function egg(string $name, int $count = 1) : ?Item {
}
public static function barrier(string $name, int $count = 1) : ?Item {
- $barrier = ItemUtils::decode(-161, 0, 10390);
+ //$barrier = ItemUtils::decode(-161, 0, 10390);
+ $barrier = Utils::getItem("minecraft:barrier");
$barrier->setCustomName("§r{$name}");
$barrier->setCount($count);
return $barrier;
@@ -77,4 +78,11 @@ public static function nbt(string $nbt, string $name, int $count = 1) {
public static function itemStack(int $id, int $meta, int $netId, int $count = 1) : ItemStack {
return new ItemStack($id, $meta, $count, $netId, new CompoundTag(), [], []);
}
+
+ public static function stringItem(string $item, string $name, int $count = 1) : ?Item {
+ $item = Utils::getItem($item);
+ $item->setCustomName("§r{$name}");
+ $item->setCount($count);
+ return $item;
+ }
}
\ No newline at end of file
diff --git a/src/FoxWorn3365/Shopkeepers/utils/SkinUtils.php b/src/FoxWorn3365/Shopkeepers/utils/SkinUtils.php
new file mode 100644
index 0000000..66ad454
--- /dev/null
+++ b/src/FoxWorn3365/Shopkeepers/utils/SkinUtils.php
@@ -0,0 +1,50 @@
+inventory) === 'object') {
self::errorLogger($data_dir, "NOTICE", "Value of 'inventory' inside shop '{$name}', file '{$file}' is an object! Corrected");
- $shop->inventory = (array)$shop->inventory;
+ $it = [];
+ foreach ($shop->inventory as $item) {
+ $it[] = $item;
+ }
+ $shop->inventory = $it;
} else {
self::errorLogger($data_dir, "WARNING", "Value of 'inventory' inside shop '{$name}', file '{$file}' is not a correct value! Neutralized");
$shop->inventory = [];
}
}
+
+ // Validate and add if not present the history camp
+ if (@$shop->history === null) {
+ $shop->history = "";
+ } elseif (gettype($shop->history) !== 'string') {
+ $shop->history = "";
+ }
+
+ // Validate and add if not present the enabled camp
+ if (@$shop->enabled === null) {
+ $shop->enabled = true;
+ } elseif (gettype($shop->enabled) !== 'bool') {
+ $shop->enabled = true;
+ }
+
// Update the shop
$end->{$name} = $shop;
}
@@ -132,6 +151,14 @@ public static function comparator(Item $buy, int $sellcount, array $items) : str
}
}
+ public static function entityFixer(string $data_dir, object|array $data) : void {
+ if (gettype($data) !== 'array') {
+ if (gettype($data) === 'object') {
+ file_put_contents($data_dir, (array)$data);
+ }
+ }
+ }
+
public static function randomizer(int $lenght) : int {
$buffer = "";
for ($a = 0; $a < $lenght; $a++) {
diff --git a/src/Himbeer/LibSkin/LibSkin.php b/src/Himbeer/LibSkin/LibSkin.php
new file mode 100644
index 0000000..2ec4c36
--- /dev/null
+++ b/src/Himbeer/LibSkin/LibSkin.php
@@ -0,0 +1,34 @@
+ 64,
+ 64 * 64 * 4 => 64,
+ 128 * 128 * 4 => 128
+ ];
+
+ public const SKIN_HEIGHT_MAP = [
+ 64 * 32 * 4 => 32,
+ 64 * 64 * 4 => 64,
+ 128 * 128 * 4 => 128
+ ];
+
+ public static function validateSize(int $size) {
+ if (!in_array($size, self::ACCEPTED_SKIN_SIZES)) {
+ throw new Exception("Invalid skin size");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Himbeer/LibSkin/SkinConverter.php b/src/Himbeer/LibSkin/SkinConverter.php
new file mode 100644
index 0000000..48ce54d
--- /dev/null
+++ b/src/Himbeer/LibSkin/SkinConverter.php
@@ -0,0 +1,106 @@
+> 24) & 0xff;
+ $r = ($rgba >> 16) & 0xff;
+ $g = ($rgba >> 8) & 0xff;
+ $b = $rgba & 0xff;
+ $skinData .= chr($r) . chr($g) . chr($b) . chr(~(($a << 1) | ($a >> 6)) & 0xff);
+ }
+ }
+ if ($destroyImage) imagedestroy($image);
+ return $skinData;
+ }
+}
diff --git a/src/Himbeer/LibSkin/SkinGatherer.php b/src/Himbeer/LibSkin/SkinGatherer.php
new file mode 100644
index 0000000..2bb8ee3
--- /dev/null
+++ b/src/Himbeer/LibSkin/SkinGatherer.php
@@ -0,0 +1,128 @@
+getOfflinePlayerData($playerName);
+ if ($namedTag === null) {
+ return null;
+ }
+ $skinTag = $namedTag->getCompoundTag("Skin");
+ if ($skinTag === null) {
+ return null;
+ }
+ $skinData = $skinTag->getByteArray("Data");
+ return $skinData;
+ }
+
+ /**
+ * @param string $userName
+ * @param callable $callback A function which gets called when the request is finished, with the first argument being the skin data (or null) and the second the success/error state
+ *
+ * @throws Exception
+ */
+ public static function getJavaEditionSkinData(string $userName, callable $callback) {
+ self::getJavaEditionSkinUrl($userName, function($skinUrl, $state) use ($callback) {
+ $callback($skinUrl === null ? null : SkinConverter::imageToSkinDataFromPngPath($skinUrl), $state);
+ });
+ }
+
+ /**
+ * @param string $userName Java Edition player name
+ * @param callable $callback A function which gets called when the request is finished, with the first argument being the URL (or null) and the second the success/error state
+ */
+ public static function getJavaEditionSkinUrl(string $userName, callable $callback) {
+ self::asyncHttpGetRequest("https://api.mojang.com/users/profiles/minecraft/{$userName}", function(InternetRequestResult|null $response) use ($callback) {
+ if ($response === null) {
+ $callback(null, self::MCJE_STATE_ERR_UNKNOWN);
+ return;
+ }
+ $body = $response->getBody();
+ if ($body === "") {
+ if ($response->getCode() === 204) { // Status Code 204: No Content
+ $callback(null, self::MCJE_STATE_ERR_PLAYER_NOT_FOUND);
+ } else {
+ $callback(null, self::MCJE_STATE_ERR_UNKNOWN);
+ }
+ return;
+ }
+ $data = json_decode($body, true);
+ if ($data === null || !isset($data["id"])) {
+ $callback(null, self::MCJE_STATE_ERR_UNKNOWN);
+ return;
+ }
+ self::asyncHttpGetRequest("https://sessionserver.mojang.com/session/minecraft/profile/{$data["id"]}", function(InternetRequestResult|null $response) use ($callback) {
+ if ($response === null) {
+ $callback(null, self::MCJE_STATE_ERR_UNKNOWN);
+ return;
+ }
+ $body = $response->getBody();
+ if ($body === "") {
+ $callback(null, self::MCJE_STATE_ERR_UNKNOWN);
+ return;
+ }
+ $data = json_decode($body, true);
+ if ($data === null || !isset($data["properties"][0]["name"]) || $data["properties"][0]["name"] !== "textures") {
+ if (isset($data["error"]) && $data["error"] === "TooManyRequestsException") {
+ $callback(null, self::MCJE_STATE_ERR_TOO_MANY_REQUESTS);
+ } else {
+ $callback(null, self::MCJE_STATE_ERR_UNKNOWN);
+ }
+ return;
+ }
+ if (isset($data["properties"][0]["value"]) && ($b64dec = base64_decode($data["properties"][0]["value"]))) {
+ $textureInfo = json_decode($b64dec, true);
+ if ($textureInfo !== null && isset($textureInfo["textures"]["SKIN"]["url"])) {
+ $skinUrl = $textureInfo["textures"]["SKIN"]["url"];
+ $callback($skinUrl, self::MCJE_STATE_SUCCESS);
+ return;
+ }
+ }
+ $callback(null, self::MCJE_STATE_ERR_UNKNOWN);
+ });
+ });
+ }
+
+ /**
+ * @param string $url
+ * @param callable $callback
+ */
+ private static function asyncHttpGetRequest(string $url, callable $callback) {
+ /**
+ * @param InternetRequestResult[] $results
+ *
+ * @return void
+ */
+ $bulkCurlTaskCallback = function(array $results) use ($callback) {
+ if (isset($results[0]) && !$results[0] instanceof InternetException) {
+ $callback($results[0]);
+ } else {
+ $callback(null);
+ }
+ };
+ $task = new BulkCurlTask([
+ new BulkCurlTaskOperation($url)
+ ], $bulkCurlTaskCallback);
+ Server::getInstance()->getAsyncPool()->submitTask($task);
+ }
+}
\ No newline at end of file