From c1c5c96c90f4aa6b5c7ff83b408651c9fe98c2a2 Mon Sep 17 00:00:00 2001 From: alejandroliu Date: Sun, 6 Nov 2016 12:48:55 +0100 Subject: [PATCH] 1.2.2 --- README.md | 316 ++++++++- media/GoldStd1-icon.png | Bin 0 -> 8565 bytes media/GoldStd2-icon.png | Bin 0 -> 16329 bytes plugin.yml | 67 ++ resources/default.skin | Bin 0 -> 2869 bytes resources/messages/messages.ini | 85 +++ resources/messages/spa.ini | 97 +++ resources/shops.yml | 35 + src/aliuly/goldstd/Main.php | 249 +++++++ src/aliuly/goldstd/ShopKeep.php | 612 ++++++++++++++++++ src/aliuly/goldstd/SignMgr.php | 331 ++++++++++ src/aliuly/goldstd/TradingMgr.php | 189 ++++++ src/aliuly/goldstd/common/ItemName.php | 129 ++++ src/aliuly/goldstd/common/MPMU.php | 246 +++++++ src/aliuly/goldstd/common/MoneyAPI.php | 146 +++++ src/aliuly/goldstd/common/Npc.php | 100 +++ .../goldstd/common/PluginCallbackTask.php | 52 ++ src/aliuly/goldstd/common/mc.php | 105 +++ 18 files changed, 2758 insertions(+), 1 deletion(-) create mode 100644 media/GoldStd1-icon.png create mode 100644 media/GoldStd2-icon.png create mode 100644 plugin.yml create mode 100644 resources/default.skin create mode 100644 resources/messages/messages.ini create mode 100644 resources/messages/spa.ini create mode 100644 resources/shops.yml create mode 100644 src/aliuly/goldstd/Main.php create mode 100644 src/aliuly/goldstd/ShopKeep.php create mode 100644 src/aliuly/goldstd/SignMgr.php create mode 100644 src/aliuly/goldstd/TradingMgr.php create mode 100644 src/aliuly/goldstd/common/ItemName.php create mode 100644 src/aliuly/goldstd/common/MPMU.php create mode 100644 src/aliuly/goldstd/common/MoneyAPI.php create mode 100644 src/aliuly/goldstd/common/Npc.php create mode 100644 src/aliuly/goldstd/common/PluginCallbackTask.php create mode 100644 src/aliuly/goldstd/common/mc.php diff --git a/README.md b/README.md index 37219b4..5b9ecf7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,316 @@ # GoldStd -A different economy plugin + +* Summary: Gold based economy plugin +* Dependency Plugins: N/A +* PocketMine-MP version: 1.5 (API:1.12.0) +* DependencyPlugins: - +* OptionalPlugins: N/A +* Categories: Economy +* Plugin Access: Commands, Entities, Items + +## Overview + + + + +**DO NOT POST QUESTION/BUG-REPORTS/REQUESTS IN THE REVIEWS** + +It is difficult to carry a conversation in the reviews. If you +have a question/bug-report/request please use the +[Thread](http://forums.pocketmine.net/threads/goldstd.8071/) for +that. You are more likely to get a response and help that way. + +_NOTE:_ + +This documentation was last updated for version **1.2.2**. + + + + +Implements an economy plugin based on Gold Ingots (by default) as the +currency. + +Basic Usage: + +* pay $$ +* balance +* shopkeep spawn [player|location] [shop] + +To pay people you tap on them while holding a gold ingot. + +## Documentation + +GoldStd implements an economy plugin based on Gold Ingots (by default) +as the currency. This allows to add game mechanics to the game +without artificial commands or other artificial constructs. + +You can then pay people without using the chat console. Also, you may +lose all your money if you get killed. Players can stash their gold +on Chests, but they would need to guard them (just like in real life), +etc. You can see how much money you have directly in the inventory +window, etc. + +### Commands + +The chat console commands are there for convenience but are not needed +for regular gameplay: + +* pay $$ + By default when you tap on another player, only 1 gold ingot get + transferred. This command can be used to facilitate larger + transactions. If you use this command the next tap will transfer + the desired amount in one go. +* balance + If you are rich enough, your money will be in multiple stacks. This + commands will add the stacks for you. +* shopkeep + This command is used to manage shop keepers. Sub-commands: + - /shopkeep spawn [player|location] [shop] + - player - Shop keepr will be spawn where this player is located. + - location: x,y,z or x,y,z,yaw,pitch or x,y,z,yaw,pitch,world + - shop - shop definition from configuration file. + +### Signs + +GoldStd supports three types of signs. + +1. Shop signs +2. Gambling signs +3. Trading signs + +#### Shop Signs + +Place a sign with the following text: + + [CODE] + [SHOP] + Item_name [x10] + price + + [/CODE] + +Players will be able to buy the specified items at the set price. +Defaults to 1, but this can be changed with the x?? modifier. +Examples from RuinPvP: + +* [SHOP] + * Wooden Sword + * 200p +* [SHOP] + * Golden Sword + * 250p +* [SHOP] + * Stone Sword + * 500p +* [SHOP] + * Iron Sword + * 700p +* [SHOP] + * Diamond Sword + * 1000p +* [SHOP] + * Apples x10 + * 200p + +#### Gambling Signs + +Tap a sign and you place a bet. Place a sign with the following text: + + [CODE] + [CASINO] + : + + [/CODE] + +The odds is the number of chances, so the higher the number the less +chance of payout. Payout is how much you get pay if you win. Price +is how much each bet costs. +Examples: + +* [CASINO] + * 5:300 + * 100p + +#### Trading Sings + +Tap a sign and you can trade items. +Place a sign with the following text: + + [CODE] + [TRADE] + + + [/CODE] + +Examples: + +* [TRADE] + * Diamond Sword + * Wood x5 +* [TRADE] + * Spawn Egg:32 + * Emerald x2 + * Zombie Spawn egg + +#### Potions Shop + +Tap a sign and you get a potions effect. +Place a sign with the following text: + + [CODE] + [POTIONS] + + + [/CODE] + +Duration is a value in seconds. + +Examples: + +* [POTIONS] + * 1:120:1 + * 10p + * Speed +* [POTIONS] + * STREGTH + * 1p +* [POTIONS] + * 8::2 + * 2p + * Jump + +### API + +* API + - getMoney + - setMoney + - grantMoney + +### Configuration + +Configuration is through the `config.yml` file. +The following sections are defined: + +#### defaults + + +Default values for paying players by tapping +* payment: default payment when tapping on a player +* timeout: how long a transaction may last + +#### main + +* settings: features + * currency: Item to use for currency false or zero disables currency exchange. + * signs: set to true to enable shops|casino signs +* trade-goods: List of tradeable goods +* signs: Text used to identify GoldStd signs + +#### shop-keepers + +* enable: enable/disable shopkeep functionality +* range: How far away to engage players in chat +* ticks: How often to check player positions +* freq: How often to spam players (in seconds) + + +### Permission Nodes + +* goldstd.cmd.pay : Access to pay command +* goldstd.cmd.balance : Show your current balance +* goldstd.cmd.shopkeep : ShopKeepr management + (Defaults to Op) +* goldstd.shopkeep.shop : Allow buying from shop keeper +* goldstd.signs.use : Allow access to signs +* goldstd.signs.use.casino : Allow access to casino signs +* goldstd.signs.use.shop : Allow access to shopping signs +* goldstd.signs.use.trade : Allow access to trading signs +* goldstd.signs.use.effects : Allow access to Effects signs +* goldstd.signs.place : Allow placing signs + (Defaults to Op) +* goldstd.signs.place.casino : Allow placing casino signs + (Defaults to Op) +* goldstd.signs.place.shop : Allow placing shopping signs + (Defaults to Op) +* goldstd.signs.place.trade : Allow placing trading signs + (Defaults to Op) +* goldstd.signs.place.effects : Allow placing Effects signs + (Defaults to Op) + + +## Translations + +This plugin will honour the server language configuration. The +languages currently available are: + +* English +* Spanish + +You can provide your own message file by creating a file called +`messages.ini` in the plugin config directory. Check +[github](https://github.com/alejandroliu/pocketmine-plugins/tree/master/GoldStd) +for sample files. + +The contents of these "ini" files are key-value pairs: + + "Base text"="Translated Text" + +## FAQ + +* Q: What do I do if the item name does not fit in the sign? +* A: For those case you should use the Item ID and if you need more descrption add it to line 4, or on a different sign. +* Q: Where do I find a list of the proper item names? +* A: This uses PocketMine definitions (like the /give command). You can find the list here: + * [PocketMine Source](https://github.com/PocketMine/PocketMine-MP/blob/master/src/pocketmine/item/Item.php#L39) + * Note that item names are case insensitive. You can use the names or the ids. +* Q: Where do I find a list of the proper effect names? + * [PocketMine Source](https://github.com/PocketMine/PocketMine-MP/blob/master/src/pocketmine/entity/Effect.php#L32) + * You can use these names or the ids. +* Q: How do I set a staring amount of money for players? +* A: Use SpawnMgr or ItemSpawn to define an initial inventory which should include gold ingots. +* Q: How can I use a different money plugin? +* A: Set the currency to false in config.yml. Then GoldStd will search for an + alternate money plugin. + +# Changes + +* 1.2.2: Bug fixes + * Fixing Effects permissions (reported by @may) +* 1.2.1: Bug fixes + * Weapons are detected using isSword, isAxe and isPickaxe. + * Fixed bug that caused inventory to be lost (Thanks @reidq7 for figuring it out) + * Tweaked the priority of event listeners. + * Changed MPMU::itemName to ItemName::str +* 1.2.0: + * MoneyAPI fixes (Thanks @vertx) + * Effects Shop + * Can specify location in spawn command + * Fixed spamming with multiple shops. +* 1.1.2 : + * @Achak request + * Added goods trading + * Added casino, shop and trading signs + * Configuration uses strings instead of codes + * Compatible with other economy plugins + * ShopKeep functionality +* 1.0.0 : First submission + +# Copyright + + GoldStd + Copyright (C) 2015 Alejandro Liu + All Rights Reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + diff --git a/media/GoldStd1-icon.png b/media/GoldStd1-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..89b7a4c45a9930a3c05a9ca931acc80eb86f5b74 GIT binary patch literal 8565 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 Thq`a^D)z9*fBx&=(currency; } + + public function onEnable(){ + if (!is_dir($this->getDataFolder())) mkdir($this->getDataFolder()); + mc::plugin_init($this,$this->getFile()); + + $defaults = [ + "version" => $this->getDescription()->getVersion(), + "# settings" => "features", + "settings" => [ + "# currency" => "Item to use for currency",// false or zero disables currency exchange. + "currency" => "GOLD_INGOT", + "# signs" => "set to true to enable shops|casino signs", + "signs" => true, + ], + "# trade-goods" => "List of tradeable goods", + "trade-goods" => [], + "defaults" => TradingMgr::defaults(), + "# signs" => "Text used to identify GoldStd signs", + "signs" => SignMgr::defaults(), + "shop-keepers" => ShopKeep::defaults(), + ]; + $this->saveResource("shops.yml"); + $cf = (new Config($this->getDataFolder()."config.yml", + Config::YAML,$defaults))->getAll(); + if ($cf["settings"]["currency"]) { + $item = Item::fromString($cf["settings"]["currency"]); + if ($item->getId() == Item::AIR) { + $this->getLogger()->error(TextFormat::RED. + mc::_("Invalid currency item")); + $this->currency = Item::GOLD_INGOT; + } else { + $this->currency = $item->getId(); + } + $this->api = null; + } else { + // No currency defined, so we use an external API + $pm = $this->getServer()->getPluginManager(); + if(!($money = $pm->getPlugin("PocketMoney")) + && !($money = $pm->getPlugin("EconomyAPI")) + && !($money = $pm->getPlugin("MassiveEconomy"))){ + $this->api = null; + $this->getLogger()->warning(TextFormat::YELLOW. + mc::_("Using GOLD_INGOT as currency")); + $this->currency = Item::GOLD_INGOT; + } else { + $this->api = $money; + $this->currency = false; + $this->getLogger()->info(TextFormat::BLUE. + mc::_("Using Money API of %1%", + $money->getFullName())); + } + } + if ($this->currency || $cf["trade-goods"]) { + $this->trading = new TradingMgr($this, + $cf["trade-goods"], + $cf["defaults"]); + } else { + $this->trading = null; + $this->getLogger()->warning(TextFormat::RED. + mc::_("Goods trading disabled!")); + } + if ($cf["signs"]) { + new SignMgr($this,$cf["signs"]); + } else { + $this->getLogger()->warning(TextFormat::RED. + mc::_("SignShops disabled")); + } + if (ShopKeep::cfEnabled($cf["shop-keepers"])) { + $this->saveResource("default.skin"); + $this->keepers = new ShopKeep($this,$cf["shop-keepers"]); + if (!$this->keepers->isEnabled()) { + $this->keepers = null; + } + } else { + $this->keepers = null; + $this->getLogger()->warning(TextFormat::RED. + mc::_("Shop-Keepers disabled")); + } + + } + public function isWeapon($item) { + return $item->isAxe() || $item->isSword() || $item->isPickaxe(); + } + ////////////////////////////////////////////////////////////////////// + // + // Command implementations + // + ////////////////////////////////////////////////////////////////////// + public function onCommand(CommandSender $sender, Command $cmd, $label, array $args) { + switch($cmd->getName()) { + case "pay": + if (!$this->trading) { + $sender->sendMessage(mc::_("PAY command has been disabled")); + return true; + } + if (!MPMU::inGame($sender)) return false; + if ($sender->isCreative() || $sender->isSpectator()) { + $sender->sendMessage(mc::_("You cannot use this in creative or specator mode")); + return true; + } + if (count($args) == 1) { + if (is_numeric($args[0])) { + $money = intval($args[0]); + if ($this->getMoney($sender->getName()) < $money) { + $sender->sendMessage(mc::_("You do not have enough money")); + return true; + } + $this->trading->setAttr($sender,"payment",$money); + $sender->sendMessage(mc::_("Next payout will be for %1%G",$money)); + return true; + } + return false; + } elseif (count($args) == 0) { + $sender->sendMessage(mc::_("Next payout will be for %1%G", + $this->trading->getAttr($sender,"payment"))); + return true; + } + return false; + case "balance": + if (!MPMU::inGame($sender)) return false; + if ($sender->isCreative() || $sender->isSpectator()) { + $sender->sendMessage(mc::_("You cannot use this in creative or specator mode")); + return true; + } + $sender->sendMessage(mc::_("You have %1%G",$this->getMoney($sender->getName()))); + return true; + case "shopkeep": + if ($this->keepers) return $this->keepers->subCmd($sender,$args); + $sender->sendMessage(mc::_("shopkeep command disabled")); + return true; + } + return false; + } + ////////////////////////////////////////////////////////////////////// + // + // Economy/Money API + // + ////////////////////////////////////////////////////////////////////// + public function giveMoney($player,$money) { + if ($this->api) return MoneyAPI::grantMoney($this->api,$player,$money); + if ($player instanceof Player) { + $pl = $player; + $player = $pl->getName(); + } else { + $pl = $this->getServer()->getPlayer($player); + if (!$pl) return false; + } + if ($pl->isCreative() || $pl->isSpectator()) return false; + while ($money > 0) { + $item = Item::get($this->currency); + if ($money > $item->getMaxStackSize()) { + $item->setCount($item->getMaxStackSize()); + } else { + $item->setCount($money); + } + $money -= $item->getCount(); + $pl->getInventory()->addItem(clone $item); + } + return true; + } + public function takeMoney($player,$money) { + if ($this->api) return MoneyAPI::grantMoney($this->api,$player,-$money); + if ($player instanceof Player) { + $pl = $player; + $player = $pl->getName(); + } else { + $pl = $this->getServer()->getPlayer($player); + if (!$pl) return false; + } + if ($pl->isCreative() || $pl->isSpectator()) return false; + foreach ($pl->getInventory()->getContents() as $slot => &$item) { + if ($item->getId() != $this->currency) continue; + if ($item->getCount() > $money) { + $item->setCount($item->getCount() - $money); + $pl->getInventory()->setItem($slot,clone $item); + break; + } + $money -= $item->getCount(); + $pl->getInventory()->clear($slot); + if (!$money) break; + } + if ($money) return $money; // They don't have enough money! + return true; + } + public function grantMoney($p,$money) { + if ($this->api) return MoneyAPI::grantMoney($this->api,$p,$money); + if ($money < 0) { + return $this->takeMoney($p,-$money); + } elseif ($money > 0) { + return $this->giveMoney($p,$money); + } else { + return true; + } + } + public function getMoney($player) { + if ($this->api) return MoneyAPI::getMoney($this->api,$player); + if ($player instanceof Player) { + $pl = $player; + $player = $pl->getName(); + } else { + $pl = $this->getServer()->getPlayer($player); + if (!$pl) return null; + } + $g = 0; + if ($pl->isCreative() || $pl->isSpectator()) return null; + foreach ($pl->getInventory()->getContents() as $slot => &$item) { + if ($item->getId() != $this->currency) continue; + $g += $item->getCount(); + } + return $g; + } + public function setMoney($player,$money) { + $now = $this->getMoney($player); + if ($money < $now) { + return $this->takeMoney($player, $now - $money); + } elseif ($money > $now) { + return $this->giveMoney($player, $money - $now); + } elseif ($money == $now) return true; // Nothing to do! + $this->getLogger()->error("INTERNAL ERROR AT ".__FILE__.",".__LINE__); + return false; + } +} diff --git a/src/aliuly/goldstd/ShopKeep.php b/src/aliuly/goldstd/ShopKeep.php new file mode 100644 index 0000000..0bb9362 --- /dev/null +++ b/src/aliuly/goldstd/ShopKeep.php @@ -0,0 +1,612 @@ +client = $client; + parent::__construct($holder,InventoryType::get(InventoryType::CHEST), + [],null,"Trader Inventory"); + } + public function getClient() { return $this->client; } +} + + +class ShopKeep implements Listener { + protected $owner; + protected $keepers; + protected $state; + + static public function defaults() { + return [ + "# enable" => "enable/disable shopkeep functionality", + "enable" => true, + "# range" => "How far away to engage players in chat", + "range" => 4, + "# ticks" => "How often to check player positions", + "ticks" => 20, + "# freq" => "How often to spam players (in seconds)", + "freq" => 60, + ]; + } + static public function cfEnabled($cf){ + return $cf["enable"]; + } + public function __construct(Plugin $plugin,$xfg) { + $this->owner = $plugin; + $this->keepers = []; + $cfg = (new Config($plugin->getDataFolder()."shops.yml", + Config::YAML))->getAll(); + + $this->state = []; + foreach ($cfg as $i=>$j) { + $this->keepers[$i] = []; + if (isset($j["messages"])) { + $this->keepers[$i]["messages"] = $j["messages"]; + } else { + $this->keepers[$i]["messages"] = []; + } + $this->keepers[$i]["attack"] = isset($j["attack"]) ? $j["attack"] : 5; + $this->keepers[$i]["slim"] = isset($j["slim"]) ? $j["slim"] : false; + $this->keepers[$i]["displayName"] = isset($j["display"]) ? $j["display"] : "default"; + // Load the skin in memory + if (is_file($plugin->getDataFolder().$j["skin"])) { + $this->keepers[$i]["skin"] = + zlib_decode(file_get_contents($plugin->getDataFolder().$j["skin"])); + } else { + $this->keepers[$i]["skin"] = null; + } + if (isset($cfg[$i]["msgs"])) + $this->keepers[$i]["msgs"] = $cfg[$i]["msgs"]; + + $items = isset($cfg[$i]["items"]) && $cfg[$i]["items"] ? + $cfg[$i]["items"] : [ "IRON_SWORD,2","APPLE,10,1" ]; + $this->keepers[$i]["items"] = []; + foreach ($items as $n) { + $t = explode(",",$n); + if (count($t) < 2 || count($t) >3) { + $plugin->getLogger()->error(mc::_("Item error: %1%",$n)); + continue; + } + $item = Item::fromString(array_shift($t)); + if ($item->getId() == Item::AIR) { + $plugin->getLogger()->error(mc::_("Unknown Item error: %1%",$n)); + continue; + } + $price = intval(array_pop($t)); + if ($price <= 0) { + $plugin->getLogger()->error(mc::_("Invalid price: %1%",$n)); + continue; + } + if (count($t)) { + $qty = intval($t[0]); + if ($qty <= 0 || $qty >= $item->getMaxStackSize()) { + $plugin->getLogger()->error(mc::_("Bad quantity: %1%",$n)); + continue; + } + $item->setCount($qty); + } + $this->keepers[$i]["items"][implode(":",[$item->getId(),$item->getDamage()])] = [ $item,$price ]; + } + if (count($this->keepers[$i]["items"])) continue; + $plugin->getLogger()->error(mc::_("ShopKeep %1% disabled!",$i)); + unset($this->keepers[$i]); + continue; + } + if (count($this->keepers) == 0) { + $plugin->getLogger()->error(mc::_("No shopkeepers found!")); + $this->keepers = null; + return; + } + Entity::registerEntity(TraderNpc::class,true); + $this->owner->getServer()->getPluginManager()->registerEvents($this, $this->owner); + + $this->owner->getServer()->getScheduler()->scheduleRepeatingTask( + new PluginCallbackTask($this->owner,[$this,"spamPlayers"],[$xfg["range"],$xfg["freq"]]),$xfg["ticks"] + ); + + } + + public function isEnabled() { + return $this->keepers !== null; + } + ////////////////////////////////////////////////////////////////////// + public function subCmd($c,$args) { + if (count($args) == 0) return false; + $cmd = strtolower(array_shift($args)); + switch ($cmd) { + case "spawn": + if (count($args) > 0) { + if (preg_match('/^(\d+),(\d+),(\d+),(\d+),(\d+)$/',$args[0],$mv)){ + $level = MPMU::inGame($c,false) ? $c->getLevel() : $this->owner->getServer()->getDefaultLevel(); + $pos = new Location($mv[1],$mv[2],$mv[3],$mv[4],$mv[5],$level); + array_shift($args); + } elseif (preg_match('/^(\d+),(\d+),(\d+),(\d+),(\d+),(\S+)$/',$args[0],$mv)){ + $level = $this->owner->getServer()->getLevelByName($mv[6]); + if ($level === null) { + $c->sendMessage(mc::_("World %1% not found",$mv[6])); + return true; + } + $pos = new Location($mv[1],$mv[2],$mv[3],$mv[4],$mv[5],$level); + array_shift($args); + } elseif (preg_match('/^(\d+),(\d+),(\d+)$/',$args[0],$mv)){ + $level = MPMU::inGame($c,false) ? $c->getLevel() : $this->owner->getServer()->getDefaultLevel(); + $pos = new Location($mv[1],$mv[2],$mv[3],0.0,0.0,$level); + array_shift($args); + } elseif (($pos = $this->owner->getServer()->getPlayer($args[0])) == null) { + if (!MPMU::inGame($c)) return true; + $pos = $c; + } else { + array_shift($args); + } + } + if (count($args) == 0) $args = ["default"]; + $shopkeep = implode(" ",$args); + $ms = $this->spawn($pos,$shopkeep); + if ($ms != "") + $c->sendMessage($ms); + else + $c->sendMessage(mc::_("Spawned shopkeep: %1%",$shopkeep)); + return true; + default: + $c->sendMessage(mc::_("%1%: Unknown sub-command",$cmd)); + } + return false; + } + + ////////////////////////////////////////////////////////////////////// + + public function spawn($pos,$name) { + if (!$this->isEnabled()) + return mc::_("ShopKeeper functionality disabled"); + if (!isset($this->keepers[$name])) { + return mc::_("ShopKeeper %1% not found",$name); + } + $trader = TraderNpc::spawnNpc($this->keepers[$name]["displayName"], + $pos,TraderNpc::class,[ + "skin" => $this->keepers[$name]["skin"], + "slim" => $this->keepers[$name]["slim"], + "shop" => ["String",$name], + ]); + $trader->spawnToAll(); + $pos->y = $trader->getFloorY()-2; + if ($pos->getY() <= 0) $pos->y = 1; + $nbt = new Compound("", [ + "id" => new String("id","Chest"), + "x" => new Int("x",$pos->getX()), + "y" => new Int("y",$pos->getY()), + "z" => new Int("z",$pos->getZ()), + "CustomName" => new String("CustomName",$name), + "Items" => new Enum("Items",[]), + ]); + $pos->getLevel()->setBlock($pos,Block::get(Block::CHEST)); + $chest = new Chest($pos->getLevel()->getChunk($pos->getX()>>4,$pos->getZ()>>4), $nbt); + return ""; + } + ////////////////////////////////////////////////////////////////////// + public function getState($label,$player,$default) { + $n = MPMU::iName($player); + if (!isset($this->state[$n])) return $default; + if (!isset($this->state[$n][$label])) return $default; + return $this->state[$n][$label]; + } + public function setState($label,$player,$val) { + $n = MPMU::iName($player); + if (!isset($this->state[$n])) $this->state[$n] = []; + $this->state[$n][$label] = $val; + } + public function unsetState($label,$player) { + $n = MPMU::iName($player); + if (!isset($this->state[$n])) return; + if (!isset($this->state[$n][$label])) return; + unset($this->state[$n][$label]); + } + + ////////////////////////////////////////////////////////////////////// + public function onQuit(PlayerQuitEvent $ev) { + $n = MPMU::iName($ev->getPlayer()); + if (isset($this->state[$n])) { + if (isset($this->state[$n]["trade-inv"])) + $this->restoreInv($ev->getPlayer()); + unset($this->state[$n]); + } + } + /** + * @priority LOW + */ + public function onEntityInteract(EntityDamageEvent $ev) { + if ($ev->isCancelled()) return; + if(!($ev instanceof EntityDamageByEntityEvent)) return; + $giver = $ev->getDamager(); + if (!($giver instanceof Player)) return; + $taker = $ev->getEntity(); + if (!($taker instanceof TraderNpc)) return; + $ev->setCancelled(); // OK, now what... + if ($giver->isCreative() || $giver->isSpectator()) { + $giver->sendMessage(mc::_("No purchases while in %1% mode.", + MPMU::gamemodeStr($giver->getGamemode()))); + return; + } + $shop = $taker->namedtag->shop->getValue(); + if (!isset($this->keepers[$shop])) { + $this->owner->getLogger()->error( + mc::_("Invalid shop %5% for NPC at %1%,%2%,%3% (%4%)", + $taker->floorX(),$taker->floorY(),$taker->floorZ(), + $taker->getLevel()->getName(),$shop)); + $giver->sendMessage(mc::_("Sorry, shop is closed!")); + return; + } + + $hand = $giver->getInventory()->getItemInHand(); + if ($this->owner->getCurrency() !== false ? + $hand->getId() == $this->owner->getCurrency() : + $hand->getId() == Item::GOLD_INGOT) { + // OK, we want to buy stuff... + + $this->owner->getServer()->getScheduler()->scheduleDelayedTask( + new PluginCallbackTask($this->owner,[$this,"startTrade"], + [$giver,$taker,$shop]),10); + } else { + if ($this->owner->isWeapon($hand)) { + $this->shopMsg($giver,$shop,"under-attack"); + $giver->attack($this->keepers[$shop]["attack"], + new EntityDamageByEntityEvent( + $taker,$giver, + EntityDamageEvent::CAUSE_ENTITY_ATTACK, + $this->keepers[$shop]["attack"],1.0)); + } else { + $this->shopMsg($giver,$shop,"help-info"); + } + } + } + /* Buy stuf...*/ + public function startTrade($buyer,$seller,$shop) { + if (!MPMU::access($buyer,"goldstd.shopkeep.shop")) return; + if ($this->getState("trade-inv",$buyer,null) !== null) { + return; + } + + $l = $seller->getLevel(); + $tile = null; + for($i=-2;$i<=0 && $tile == null;$i--) { + $pos = $seller->add(0,$i,0); + $tile = $l->getTile($pos); + if ($tile instanceof Chest) { + break; + } else { + $tile = null; + } + } + if ($tile == null) { + $this->owner->getLogger()->error( + mc::_("Error trading with NPC at %1%,%2%,%3% (%4%)", + $seller->floorX(),$seller->floorY(),$seller->floorZ(), + $seller->getLevel()->getName())); + $buyer->sendMessage(mc::_("Sorry, nothing happens...")); + return; + } + $inv = [ "player" => [], "chest" => null ]; + $inv["money"] = $this->owner->getMoney($buyer); + $inv["shop"] = $shop; + + foreach ($buyer->getInventory()->getContents() as $slot=>&$item) { + $inv["player"][$slot] = implode(":",[ $item->getId(), + $item->getDamage(), + $item->getCount() ]); + } + $inv["chest"] = new TraderInventory($tile,$buyer); + $contents = []; + foreach ($this->keepers[$shop]["items"] as $idmeta=>$it) { + $item = clone $it[0]; + $item->setCount(1); + $contents[] = $item; + } + $inv["chest"]->setContents($contents); + $this->setState("trade-inv",$buyer,$inv); + $buyer->getInventory()->clearAll(); + $buyer->addWindow($inv["chest"]); + } + public function onTransaction(InventoryTransactionEvent $ev) { + $tg = $ev->getTransaction(); + $pl = null; + $ti = null; + foreach($tg->getInventories() as $i) { + if ($i instanceof PlayerInventory) { + $pl = $i->getHolder(); + } + if ($i instanceof TraderInventory) { + $ti = $i; + } + } + if ($ti == null) return; // This does not involve us! + if ($pl == null) { + $this->owner->getLogger()->error( + mc::_("Unable to identify player in inventory transaction") + ); + return; + } + + // Calculate total $$ + $xx = $this->getState("trade-inv",$pl,null); + if ($xx == null) return; // This is a normal Chest transaction... + $added = []; + foreach ($tg->getTransactions() as $t) { + if ($t->getInventory() instanceof PlayerInventory) { + // Handling PlayerInventory changes... + foreach ($this->playerInvTransaction($t) as $nt) { + $added[] = $nt; + } + continue; + } + foreach ($this->traderInvTransaction($t) as $nt) { + $added[] = $nt; + } + } + + // Test if the transaction is valid... + // Make a copy of current inventory + $tsinv = []; + foreach ($pl->getInventory()->getContents() as $slot=>$item) { + if ($item->getId() == Item::AIR) continue; + $tsinv[$slot] = [implode(":",[$item->getId(),$item->getDamage()]), + $item->getCount()]; + } + + // Apply transactions to copy + foreach ([$tg->getTransactions(),$added] as &$tset) { + foreach ($tset as &$nt) { + if ($nt->getInventory() instanceof PlayerInventory) { + $item = clone $nt->getTargetItem(); + $slot = $nt->getSlot(); + if ($item->getId() == Item::AIR) { + if(isset($tsinv[$slot])) unset($tsinv[$slot]); + } else { + $tsinv[$slot] = [ implode(":", + [ $item->getId(), + $item->getDamage()]), + $item->getCount() ]; + } + } + } + } + $total = 0; + + foreach ($tsinv as $slot=>$item) { + list($idmeta,$cnt) = $item; + if (!isset($this->keepers[$xx["shop"]]["items"][$idmeta])) { + $this->shopMsg($pl,$xx["shop"],"inventory-error"); + $ev->setCancelled(); + return; + } + list($i,$price) = $this->keepers[$xx["shop"]]["items"][$idmeta]; + $total += round($cnt/$i->getCount())*$price; + } + if ($total > $xx["money"]) { + $this->shopMsg($pl,$xx["shop"],"not-enough-g",$total, $xx["money"]); + $ev->setCancelled(); + return; + } + foreach ($added as $nt) { + $tg->addTransaction($nt); + } + // Make sure inventory is properly synced + foreach($tg->getInventories() as $i) { + $this->owner->getServer()->getScheduler()->scheduleDelayedTask( + new PluginCallbackTask($this->owner,[$i,"sendContents"],[$pl]),5); + $this->owner->getServer()->getScheduler()->scheduleDelayedTask( + new PluginCallbackTask($this->owner,[$i,"sendContents"],[$pl]),10); + $this->owner->getServer()->getScheduler()->scheduleDelayedTask( + new PluginCallbackTask($this->owner,[$i,"sendContents"],[$pl]),15); + } + } + public function onClose(InventoryCloseEvent $ev) { + $pl = $ev->getPlayer(); + $xx = $this->getState("trade-inv",$pl,null); + if ($xx == null) return; + + // Compute shopping basket + $basket = []; + $total = 0; + foreach ($pl->getInventory()->getContents() as $slot=>$item) { + if ($item->getId() == Item::AIR) continue; + $idmeta = implode(":",[$item->getId(),$item->getDamage()]); + if (!isset($this->keepers[$xx["shop"]]["items"][$idmeta])) continue; + list($i,$price) = $this->keepers[$xx["shop"]]["items"][$idmeta]; + $total += round($item->getCount()/$i->getCount())*$price; + $basket[] = [ $item->getId(),$item->getDamage(), $item->getCount() ]; + } + // Restore original inventory... + $this->restoreInv($pl); + // Check-out + if (count($basket) == 0) { + $this->shopMsg($pl,$xx["shop"],"next-time"); + return; + } + if ($total < $this->owner->getMoney($pl)) { + $this->owner->grantMoney($pl,-$total); + if (count($basket) == 1) + $this->shopMsg($pl,$xx["shop"],"bought-items1",$total,count($basket)); + else + $this->shopMsg($pl,$xx["shop"],"bought-itemsX",$total,count($basket)); + foreach ($basket as $ck) { + list($id,$meta,$cnt) = $ck; + $pl->getInventory()->addItem(Item::get($id,$meta,$cnt)); + } + $this->shopMsg($pl,$xx["shop"],"thank-you",$total,count($basket)); + } else { + $this->shopMsg($pl,$xx["shop"],"no-money",$total,count($basket)); + } + } + public function playerInvTransaction($t) { + $src = clone $t->getSourceItem(); + $dst = clone $t->getTargetItem(); + // This becomes nothing... not much to do... + if ($dst->getCount() == 0 || $dst->getId() == Item::AIR) return []; + $srccnt = $src->getId() == Item::AIR ? 0 : $src->getCount(); + $dstcnt = $dst->getId() == Item::AIR ? 0 : $dst->getCount(); + // This is a weird transaction... + if ($srccnt == $dstcnt && $src->getId() == $dst->getId()) return []; + $idmeta = implode(":",[$dst->getId(),$dst->getDamage()]); + $pl = $t->getInventory()->getHolder(); + $xx = $this->getState("trade-inv",$pl,null); + if ($xx == null) return []; // Oops... + list($i,$price) = $this->keepers[$xx["shop"]]["items"][$idmeta]; + if ($dstcnt > $srccnt) { + // Increase + $newcnt = $srccnt+$i->getCount(); + if ($newcnt > $i->getMaxStackSize()) $newcnt -= $i->getCount(); + } elseif ($dstcnt < $srccnt) { + // Decrease + $newcnt = floor($dstcnt/$i->getCount())*$i->getCount(); + } + if ($newcnt == $dstcnt) return []; + if ($newcnt == 0) { + $dst = Item::get(Item::AIR,0,0); + } else { + $dst->setCount($newcnt); + } + return [ new BaseTransaction($t->getInventory(), + $t->getSlot(), + clone $t->getTargetItem(), + clone $dst) ]; + } + protected function getShopMsg($pl,$shop,$msg,$args) { + if (!isset($this->keepers[$shop]["messages"][$msg])) return $msg; + + $fmt = $this->keepers[$shop]["messages"][$msg]; + $msg = is_array($fmt) ? $fmt[array_rand($fmt)] : $fmt; + + if (count($args)) { + $vars = [ "%%" => "%" ]; + $i = 1; + foreach ($args as $j) { + $vars["%$i%"] = $j; + ++$i; + } + $msg = strtr($msg,$vars); + } + return $msg; + } + protected function shopMsg($pl,$shop,...$args) { + $msg = array_shift($args); + $pl->sendMessage($this->getShopMsg($pl,$shop,$msg,$args)); + } + public function traderInvTransaction($t) { + // Moving stock to buyer + $src = clone $t->getSourceItem(); + $dst = clone $t->getTargetItem(); + if ($dst->getId() == Item::AIR) { + // Inventory never runs out! + return [ new BaseTransaction($t->getInventory(), + $t->getSlot(), + clone $t->getTargetItem(), + clone $src) ]; + } + if ($src->getId() == Item::AIR) { + // Do not accept new Inventory! + return [ new BaseTransaction($t->getInventory(), + $t->getSlot(), + clone $dst, + clone $src) ]; + } + if ($dst->getCount() > 1) { + // Inventory never increases! + $dst->setCount(1); + return [ new BaseTransaction($t->getInventory(), + $t->getSlot(), + clone $t->getTargetItem(), + clone $dst) ]; + } + return []; + } + + + public function restoreInv($pl) { + $inv = $this->getState("trade-inv",$pl,null); + if ($inv === null) return; + $pl->getInventory()->clearAll(); + foreach ($inv["player"] as $slot=>$itdat) { + list($id,$meta,$cnt) = explode(":",$itdat); + $item = Item::get($id,$meta,$cnt); + $pl->getInventory()->setItem($slot,$item); + } + $this->unsetState("trade-inv",$pl); + } + + public function spamPlayers($range,$freq) { + $now = time(); + foreach ($this->owner->getServer()->getLevels() as $lv) { + if (count($lv->getPlayers()) == 0) continue; + foreach ($lv->getEntities() as $et) { + if (!($et instanceof TraderNpc)) continue; + // OK, this could be a shop... + $shop = $et->namedtag->shop->getValue(); + if (!isset($this->keepers[$shop])) continue; + $shopid = $shop."-".$et->getId(); + foreach ($lv->getPlayers() as $pl) { + if ($pl->isCreative() || $pl->isSpectator()) continue; + if ($et->distanceSquared($pl) > $range*$range) { + if ($this->getState("spam-$shopid",$pl,null) === null) continue; + $this->unsetState("spam-$shopid",$pl); + $this->shopMsg($pl,$shop,"leaving"); + continue; + } + // In range check state + if ($this->getState("trade-inv",$pl,null) !== null) continue; + $spam = $this->getState("spam-$shopid",$pl,null); + if ($spam === null) { + $this->shopMsg($pl,$shop,"welcome"); + $this->setState("spam-$shopid",$pl,$now); + continue; + } + if ($now < $spam+$freq) continue; + $this->shopMsg($pl,$shop,"buystuff"); + $this->setState("spam-$shopid",$pl,$now); + } + + } + } + } +} diff --git a/src/aliuly/goldstd/SignMgr.php b/src/aliuly/goldstd/SignMgr.php new file mode 100644 index 0000000..5864481 --- /dev/null +++ b/src/aliuly/goldstd/SignMgr.php @@ -0,0 +1,331 @@ + ["[SHOP]"], + "casino" => ["[CASINO]"], + "trade" => ["[TRADE]"], + "effects" => ["[POTIONS]"], + ]; + } + public function __construct(Plugin $plugin,$cfg) { + $this->owner = $plugin; + $this->owner->getServer()->getPluginManager()->registerEvents($this, $this->owner); + // Configure texts + $this->texts = []; + foreach ($cfg as $sn => $tab) { + foreach ($tab as $z) { + $this->texts[$z] = $sn; + } + } + + } + ////////////////////////////////////////////////////////////////////// + // Manage signs + ////////////////////////////////////////////////////////////////////// + private function parseItemLine($txt) { + $txt = preg_split('/\s+/',$txt); + if (count($txt) == 0) return null; + $cnt = $txt[count($txt)-1]; + if (preg_match('/^x(\d+)$/',$cnt,$mv)) { + $cnt = $mv[1]; + array_pop($txt); + } else { + $cnt = 1; + } + $item = Item::fromString(implode("_",$txt)); + if ($item->getId() == 0) return null; + $item->setCount($cnt); + return $item; + } + private function parseEffectLine($txt) { + $txt = preg_split('/\s*:\s*/',$txt); + if (count($txt) == 0 || count($txt) > 3) return null; + if (!isset($txt[1]) || empty($txt[1])) $txt[1] = 60; + if (!isset($txt[2]) || empty($txt[2])) $txt[2] = 1; + if (is_numeric($txt[0])) { + $effect = Effect::getEffect($txt[0]); + } else { + $effect = Effect::getEffectByName($txt[0]); + } + if ($effect === null) return null; + $effect->setDuration($txt[1]*20); + $effect->setAmplifier($txt[2]); + $effect->setVisible(true); + return $effect; + } + + private function parsePriceLine($txt) { + $n = intval(preg_replace('/[^0-9]/', '', $txt)); + if ($n == 0) return null; + return $n; + } + private function parseCasinoLine($txt) { + $txt = preg_replace('/^\s*odds:\s*/i','',$txt); + $txt = preg_split('/\s*:\s*/',$txt); + if (count($txt) != 2) return [null,null]; + list($odds,$payout) = $txt; + $payout = $this->parsePriceLine($payout); + if ($payout === null) return [null,null]; + return [$this->parsePriceLine($odds),$payout]; + } + private function activateSign($pl,$tile) { + $sign = $tile->getText(); + if (!MPMU::access($pl,"goldstd.signs.use")) return; + $sn = $this->texts[$sign[0]]; + if (!MPMU::access($pl,"goldstd.signs.use.".$sn)) return; + switch ($sn) { + case "shop": + $item = $this->parseItemLine($sign[1]); + if ($item === null) { + $pl->sendMessage(mc::_("Invalid item line")); + return; + } + $price = $this->parsePriceLine($sign[2]); + if ($price === null) { + $pl->sendMessage(mc::_("Invalid price line")); + return; + } + $money = $this->owner->getMoney($pl); + if ($money < $price) { + $pl->sendMessage(mc::_("[GoldStd] You do not have enough money")); + } else { + $this->owner->grantMoney($pl,-$price); + $pl->getInventory()->addItem(clone $item); + $pl->sendMessage(mc::_("[GoldStd] Item purchased")); + } + break; + case "effects": + $effect = $this->parseEffectLine($sign[1]); + if ($effect === null) { + $pl->sendMessage(mc::_("Invalid effects line")); + return false; + } + $price = $this->parsePriceLine($sign[2]); + if ($price === null) { + $pl->sendMessage(mc::_("Invalid price line")); + return false; + } + $money = $this->owner->getMoney($pl); + if ($money < $price) { + $pl->sendMessage(mc::_("[GoldStd] You do not have enough money")); + } else { + $this->owner->grantMoney($pl,-$price); + $pl->addEffect($effect); + $pl->sendMessage(mc::_("[GoldStd] Potion %1% purchased",$effect->getName())); + } + + break; + case "trade": + // This is what you get... + $item1 = $this->parseItemLine($sign[1]); + if ($item1 === null) { + $pl->sendMessage(mc::_("Invalid item1 line")); + return; + } + // This is what you pay... + $item2 = $this->parseItemLine($sign[2]); + if ($item2 === null) { + $pl->sendMessage(mc::_("Invalid item2 line")); + return; + } + /*** Check if the player has item2 in stock ***/ + $inv = $pl->getInventory(); + $cnt = 0; + $slots = []; + foreach ($pl->getInventory()->getContents() as $slot=>&$item) { + if ($item2->getId() != $item->getId()) continue; + if ($item2->getDamage() && + ($item2->getDamage() != $item->getDamage())) continue; + // OK, he got it... + $cnt += $item->getCount(); + $slots[] = [$slot,$item->getCount()]; + } + if ($cnt == 0) { + $pl->sendMessage(mc::_("You do not have any %1%", + ItemName::str($item2))); + return; + } + if ($cnt < $item2->getCount()) { + $pl->sendMessage(mc::_("You do not have enough %1%", + ItemName::str($item2))); + $pl->sendMessage(mc::_("You have %1%, you need %2%", + $cnt, $item2->getCount())); + return; + } + $cnt = $item2->getCount(); // Take away stock... + while ($cnt >= 0) { + list($slot,$qty) = array_pop($slots); + if ($qty > $cnt) { + // More than enough... + $newitem = clone $item2; + $newitem->setCount($qty - $cnt); + $cnt = 0; + $pl->getInventory()->setItem($slot,$newitem); + break; + } + if ($qty <= $cnt) { + // Not enough, consume that slot completely... + $cnt -= $qty; + $pl->getInventory()->clear($slot); + } + } + $pl->sendMessage(mc::n( + mc::_("Gave away one %1%",ItemName::str($item2)), + mc::_("Gave away %2% %1%s", + ItemName::str($item2),$item2->getCount()), + $item2->getCount())); + // Give new stock... + $pl->getInventory()->addItem(clone $item1); + $pl->sendMessage(mc::n( + mc::_("Got one %1%", ItemName::str($item1)), + mc::_("Got %2% %1%s", ItemName::str($item1),$item1->getCount()), + $item1->getCount())); + break; + case "casino": + list($odds,$payout) = $this->parseCasinoLine($sign[1]); + if ($odds === null) { + $pl->sendMessage(mc::_("Invalid odds line")); + return; + } + $price = $this->parsePriceLine($sign[2]); + if ($price === null) { + $pl->sendMessage(mc::_("Invalid price line")); + return; + } + $money = $this->owner->getMoney($pl); + if ($money < $price) { + $pl->sendMessage(mc::_("[GoldStd] You do not have enough moneys")); + } else { + $pl->sendMessage(mc::_("[GoldStd] Betting %1%...",$price)); + $this->owner->grantMoney($pl,-$price); + $rand = mt_rand(0,$odds); + if ($rand == 1) { + $pl->sendMessage(mc::_("[GoldStd] You WON!!! prize...%1%G", + $payout)); + $this->owner->grantMoney($pl,$payout); + } else { + $pl->sendMessage(mc::_("[GoldStd] BooooM!!! You lost")); + } + } + break; + } + } + + private function validateSign($pl,$sign) { + if (!MPMU::access($pl,"goldstd.signs.place")) return false; + $sn = $this->texts[$sign[0]]; + if (!MPMU::access($pl,"goldstd.signs.place.".$sn)) return false; + switch ($sn) { + case "shop": + $item = $this->parseItemLine($sign[1]); + if ($item === null) { + $pl->sendMessage(mc::_("Invalid item line")); + return false; + } + $price = $this->parsePriceLine($sign[2]); + if ($price === null) { + $pl->sendMessage(mc::_("Invalid price line")); + return false; + } + break; + case "effects": + $effect = $this->parseEffectLine($sign[1]); + if ($effect === null) { + $pl->sendMessage(mc::_("Invalid effects line")); + return false; + } + $price = $this->parsePriceLine($sign[2]); + if ($price === null) { + $pl->sendMessage(mc::_("Invalid price line")); + return false; + } + break; + case "trade": + $ret = true; + foreach ([1,2] as $i) { + $item = $this->parseItemLine($sign[$i]); + if ($item === null) { + $pl->sendMessage(mc::_("Invalid item%1% line",$i)); + $ret = false; + } + } + return $ret; + case "casino": + list($odds,$payout) = $this->parseCasinoLine($sign[1]); + if ($odds === null) { + $pl->sendMessage(mc::_("Invalid odds line")); + return false; + } + $price = $this->parsePriceLine($sign[2]); + if ($price === null) { + $pl->sendMessage(mc::_("Invalid price line")); + return false; + } + break; + } + return true; + } + + ////////////////////////////////////////////////////////////////////// + // + // Event handlers + // + ////////////////////////////////////////////////////////////////////// + // Sign functionality + public function placeSign(SignChangeEvent $ev){ + if($ev->getBlock()->getId() != Block::SIGN_POST && + $ev->getBlock()->getId() != Block::WALL_SIGN) return; + $sign = $ev->getPlayer()->getLevel()->getTile($ev->getBlock()); + if(!($sign instanceof Sign)) return; + + $sign = $ev->getLines(); + if (!isset($this->texts[$sign[0]])) return; + if (!$this->validateSign($ev->getPlayer(),$sign)) { + $ev->setLine(0,"[BROKEN]"); + return; + } + $ev->getPlayer()->sendMessage(mc::_("[GoldStd] placed sign")); + } + + public function playerTouchSign(PlayerInteractEvent $ev){ + if($ev->getBlock()->getId() != Block::SIGN_POST && + $ev->getBlock()->getId() != Block::WALL_SIGN) return; + //echo "TOUCHED\n"; + $sign = $ev->getPlayer()->getLevel()->getTile($ev->getBlock()); + if(!($sign instanceof Sign)) return; + //echo __METHOD__.",".__LINE__."\n"; + $lines = $sign->getText(); + //print_r($lines); + //print_r($this->texts); + if (!isset($this->texts[$lines[0]])) return; + //echo __METHOD__.",".__LINE__."\n"; + if ($ev->getPlayer()->isCreative() || $ev->getPlayer()->isSpectator()) { + $ev->getPlayer()->sendMessage(mc::_("No trading possible, while in %1% mode", + MPMU::gamemodeStr($ev->getPlayer()->getGamemode()))); + return; + } + + $this->activateSign($ev->getPlayer(),$sign); + } + +} diff --git a/src/aliuly/goldstd/TradingMgr.php b/src/aliuly/goldstd/TradingMgr.php new file mode 100644 index 0000000..c467c76 --- /dev/null +++ b/src/aliuly/goldstd/TradingMgr.php @@ -0,0 +1,189 @@ + "default payment when tapping on a player", + "payment" => 1, + "# timeout" => "how long a transaction may last", + "timeout" => 30, + ]; + } + + public function __construct(Plugin $plugin,$goods,$dfts) { + $this->owner = $plugin; + $this->owner->getServer()->getPluginManager()->registerEvents($this, $this->owner); + $this->goods = []; + foreach ($goods as $cf) { + $item = Item::fromString($cf); + if (($item = $item->getId()) == Item::AIR) { + $plugin->getLogger()->error(mc::_("Invalid trade-good: %1%",$cf)); + continue; + } + $this->goods[$item] = $item; + } + $this->defaults = $dfts; + $this->state = []; + } + + ////////////////////////////////////////////////////////////////////// + // + // Manipulate internal state + // + ////////////////////////////////////////////////////////////////////// + public function getAttr($pl,$attr, $def = null) { + if ($def === null) $def = $this->defaults[$attr]; + if ($pl instanceof Player) $pl = MPMU::iName($pl); + if (!isset($this->state[$pl])) { + $this->state[$pl] = [ $attr => $def ]; + } + if (!isset($this->state[$pl][$attr])) { + $this->state[$pl][$attr] = $def; + } + return $this->state[$pl][$attr]; + } + public function setAttr($pl,$attr, $val) { + if ($pl instanceof Player) $pl = MPMU::iName($pl); + if (!isset($this->state[$pl])) { + $this->state[$pl] = [ $attr => $val ]; + } + $this->state[$pl][$attr] = $val; + return; + } + ////////////////////////////////////////////////////////////////////// + // + // Event handlers + // + ////////////////////////////////////////////////////////////////////// + public function onPlayerQuitEvent(PlayerQuitEvent $e) { + $pl = MPMU::iName($e->getPlayer()); + if (isset($this->state[$pl])) unset($this->state[$pl]); + } + /** + * @priority LOW + */ + public function onPlayerPayment(EntityDamageEvent $ev) { + if ($ev->isCancelled()) return; + if(!($ev instanceof EntityDamageByEntityEvent)) return; + $giver = $ev->getDamager(); + $taker = $ev->getEntity(); + if (!($giver instanceof Player)) return; + if ($giver->isCreative() || $giver->isSpectator()) return; + + $hand = $giver->getInventory()->getItemInHand(); + if ($hand->getId() == $this->owner->getCurrency() + && $this->owner->getCurrency()) { + if ($taker instanceof Player) { + if ($taker->isCreative() || $taker->isSpectator()) { + $giver->sendMessage(mc::_("No trading possible, %1% is in %2% mode", + $taker->getDisplayName(), + MPMU::gamemodeStr($taker->getGamemode()))); + return; + } + $ev->setCancelled(); // OK, we want to pay, not to fight! + $this->onPlayerPaid($giver,$taker); + } //else paying an Entity! + return; + } + if (isset($this->goods[$hand->getId()])) { + if ($taker instanceof Player) { + if ($taker->isCreative() || $taker->isSpectator()) { + $giver->sendMessage(mc::_("No trading possible, %1% is in %2% mode", + $taker->getDisplayName(), + MPMU::gamemodeStr($taker->getGamemode()))); + return; + } + $ev->setCancelled(); // OK, we are trading + $this->onPlayerTrade($giver,$taker); + } //else trading with entity... + return; + } + } + + /* + * Item exchange... + */ + + /* + * Events + - $this->getServer()->getPluginManager()->callEvent(new Event(xxx)) + */ + public function onPlayerPaid(Player $giver,Player $taker) { + $gg = $this->getAttr($giver,"payment"); + $this->setAttr($giver,"payment",$this->defaults["payment"]); + if ($this->owner->getMoney($giver->getName()) < $gg) { + $giver->sendMessage(mc::_("You don't have that much money!")); + return; + } + $this->owner->grantMoney($giver->getName(),-$gg); + $this->owner->grantMoney($taker->getName(),$gg); + list($when,$amt,$ptaker) = $this->getAttr($giver,"counter",[0,0,""]); + if (time() - $when < $this->defaults["timeout"] && $ptaker == $taker->getName()) { + // Still the same transaction... + $amt += $gg; + } else { + // New transaction! + $when = time(); + $amt = $gg; + $ptaker = $taker->getName(); + } + $this->setAttr($giver,"counter",[$when,$amt,$ptaker]); + + if (MPMU::apiVersion("1.12.0")) { + $giver->sendTip(mc::_("Paid %2%G, you now have %1%G", + $this->owner->getMoney($giver->getName()),$amt)); + $taker->sendTip(mc::_("Received %2%G, you now have %1%G", + $this->owner->getMoney($taker->getName()).$amt)); + } else { + $giver->sendMessage(mc::_("Paid %2%G, you now have %1%G", + $this->owner->getMoney($giver->getName()),$amt)); + $taker->sendMessage(mc::_("Received %2%G, you now have %1%G", + $this->owner->getMoney($taker->getName()).$amt)); + } + } + public function onPlayerTrade(Player $giver,Player $taker) { + $good = clone $giver->getInventory()->getItemInHand(); + $gift = clone $good; + $gift->setCount(1); + $taker->getInventory()->addItem($gift); + $good->setCount($n = $good->getCount()-1); + $slot = $giver->getInventory()->getHeldItemSlot(); + if ($n <= 0) { + $giver->getInventory()->clear($slot); + } else { + $giver->getInventory()->setItem($slot,$good); + } + $item = ItemName::str($good); + if (MPMU::apiVersion("1.12.0")) { + $giver->sendTip(mc::_("Gave one %1%",$item)); + $taker->sendTip(mc::_("Received %1%",$item)); + } else { + $giver->sendMessage(mc::_("Gave one %1%",$item)); + $taker->sendMessage(mc::_("Received %1%",$item)); + } + + } +} diff --git a/src/aliuly/goldstd/common/ItemName.php b/src/aliuly/goldstd/common/ItemName.php new file mode 100644 index 0000000..4919b08 --- /dev/null +++ b/src/aliuly/goldstd/common/ItemName.php @@ -0,0 +1,129 @@ + [ + 0 => "Ink Sac", + 1 => "Rose Red", + 2 => "Cactus Green", + 3 => "Cocoa Beans", + 4 => "Lapis Lazuli", + 5 => "Purple Dye", + 6 => "Cyan Dye", + 7 => "Light Gray Dye", + 8 => "Gray Dye", + 9 => "Pink Dye", + 10 => "Lime Dye", + 11 => "Dandelion Yellow", + 12 => "Light Blue Dye", + 13 => "Magenta Dye", + 14 => "Orange Dye", + 15 => "Bone Meal", + "*" => "Dye", + ], + Item::SPAWN_EGG => [ + "*" => "Spawn Egg", + 32 => "Spawn Zombie", + 33 => "Spawn Creeper", + 34 => "Spawn Skeleton", + 35 => "Spawn Spider", + 36 => "Spawn Zombie Pigman", + 37 => "Spawn Slime", + 38 => "Spawn Enderman", + 39 => "Spawn Silverfish", + 40 => "Spawn Cave Spider", + 41 => "Spawn Ghast", + 42 => "Spawn Magma Cube", + 10 => "Spawn Chicken", + 11 => "Spawn Cow", + 12 => "Spawn Pig", + 13 => "Spawn Sheep", + 14 => "Spawn Wolf", + 16 => "Spawn Mooshroom", + 17 => "Spawn Squid", + 19 => "Spawn Bat", + 15 => "Spawn Villager", + ] + ]; + } + + /** + * Given an pocketmine\item\Item object, it returns a friendly name + * for it. + * + * @param Item item + * @return str + */ + static public function str(Item $item) { + $id = $item->getId(); + $meta = $item->getDamage(); + if (isset(self::$usrnames[$id.":".$meta])) return self::$usrnames[$id.":".$meta]; + if (isset(self::$usrnames[$id])) return self::$usrnames[$id]; + if (self::$xnames == null) self::initXnames(); + + if (isset(self::$xnames[$id])) { + if (isset(self::$xnames[$id][$meta])) { + return self::$xnames[$id][$meta]; + } elseif (isset(self::$xnames[$id]["*"])) { + return self::$xnames[$id]["*"]; + } else { + return self::$xnames[$id][0]; + } + } + $n = $item->getName(); + if ($n != "Unknown") return $n; + if (count(self::$items) == 0) { + $constants = array_keys((new \ReflectionClass("pocketmine\\item\\Item"))->getConstants()); + foreach ($constants as $constant) { + $cid = constant("pocketmine\\item\\Item::$constant"); + $constant = str_replace("_", " ", $constant); + self::$items[$cid] = $constant; + } + } + if (isset(self::$items[$id])) return self::$items[$id]; + return $n; + } +} diff --git a/src/aliuly/goldstd/common/MPMU.php b/src/aliuly/goldstd/common/MPMU.php new file mode 100644 index 0000000..4699b0b --- /dev/null +++ b/src/aliuly/goldstd/common/MPMU.php @@ -0,0 +1,246 @@ +=, <=, <> or !=, =, !|~, <, > + * + * @param str api Installed API version + * @param str version API version to compare against + * + * @return bool + */ + static public function apiCheck($api,$version) { + switch (substr($version,0,2)) { + case ">=": + return version_compare($api,trim(substr($version,2))) >= 0; + case "<=": + return version_compare($api,trim(substr($version,2))) <= 0; + case "<>": + case "!=": + return version_compare($api,trim(substr($version,2))) != 0; + } + switch (substr($version,0,1)) { + case "=": + return version_compare($api,trim(substr($version,1))) == 0; + case "!": + case "~": + return version_compare($api,trim(substr($version,1))) != 0; + case "<": + return version_compare($api,trim(substr($version,1))) < 0; + case ">": + return version_compare($api,trim(substr($version,1))) > 0; + } + if (intval($api) != intval($version)) return 0; + return version_compare($api,$version) >= 0; + } + /** + * Returns a localized string for the gamemode + * + * @param int mode + * @return str + */ + static public function gamemodeStr($mode) { + if (class_exists(__NAMESPACE__."\\mc",false)) { + switch ($mode) { + case 0: return mc::_("Survival"); + case 1: return mc::_("Creative"); + case 2: return mc::_("Adventure"); + case 3: return mc::_("Spectator"); + } + return mc::_("%1%-mode",$mode); + } + switch ($mode) { + case 0: return "Survival"; + case 1: return "Creative"; + case 2: return "Adventure"; + case 3: return "Spectator"; + } + return "$mode-mode"; + } + /** + * Check's player or sender's permissions and shows a message if appropriate + * + * @param CommandSender $sender + * @param str $permission + * @param bool $msg If false, no message is shown + * @return bool + */ + static public function access(CommandSender $sender, $permission,$msg=true) { + if($sender->hasPermission($permission)) return true; + if ($msg) + $sender->sendMessage(mc::_("You do not have permission to do that.")); + return false; + } + /** + * Check's if $sender is a player in game + * + * @param CommandSender $sender + * @param bool $msg If false, no message is shown + * @return bool + */ + static public function inGame(CommandSender $sender,$msg = true) { + if (!($sender instanceof Player)) { + if ($msg) $sender->sendMessage(mc::_("You can only do this in-game")); + return false; + } + return true; + } + /** + * Takes a player and creates a string suitable for indexing + * + * @param Player|str $player - Player to index + * @return str + */ + static public function iName($player) { + if ($player instanceof Player) { + $player = strtolower($player->getName()); + } + return $player; + } + /** + * Lile file_get_contents but for a Plugin resource + * + * @param Plugin $plugin + * @param str $filename + * @return str|null + */ + static public function getResourceContents($plugin,$filename) { + $fp = $plugin->getResource($filename); + if($fp === null){ + return null; + } + $contents = stream_get_contents($fp); + fclose($fp); + return $contents; + } + /** + * Call a plugin's function + * + * @param Server $server - pocketmine server instance + * @param str $plug - plugin to call + * @param str $method - method to call + * @param mixed $default - If the plugin does not exist or it is not enable, this value uis returned + * @return mixed + */ + static public function callPlugin($server,$plug,$method,$args,$default = null) { + if (($plugin = $server->getPluginManager()->getPlugin($plug)) !== null + && $plugin->isEnabled()) { + $fn = [ $plugin, $method ]; + return $fn(...$args); + } + return $default; + } + /** + * Register a command + * + * @param Plugin $plugin - plugin that "owns" the command + * @param CommandExecutor $executor - object that will be called onCommand + * @param str $cmd - Command name + * @param array $yaml - Additional settings for this command. + */ + static public function addCommand($plugin, $executor, $cmd, $yaml) { + $newCmd = new PluginCommand($cmd,$plugin); + if (isset($yaml["description"])) + $newCmd->setDescription($yaml["description"]); + if (isset($yaml["usage"])) + $newCmd->setUsage($yaml["usage"]); + if(isset($yaml["aliases"]) and is_array($yaml["aliases"])) { + $aliasList = []; + foreach($yaml["aliases"] as $alias) { + if(strpos($alias,":")!== false) { + $this->owner->getLogger()->info("Unable to load alias $alias"); + continue; + } + $aliasList[] = $alias; + } + $newCmd->setAliases($aliasList); + } + if(isset($yaml["permission"])) + $newCmd->setPermission($yaml["permission"]); + if(isset($yaml["permission-message"])) + $newCmd->setPermissionMessage($yaml["permission-message"]); + $newCmd->setExecutor($executor); + $cmdMap = $plugin->getServer()->getCommandMap(); + $cmdMap->register($plugin->getDescription()->getName(),$newCmd); + } + /** + * Unregisters a command + * @param Server|Plugin $obj - Access path to server instance + * @param str $cmd - Command name to remove + */ + static public function rmCommand($srv, $cmd) { + $cmdMap = $srv->getCommandMap(); + $oldCmd = $cmdMap->getCommand($cmd); + if ($oldCmd === null) return false; + $oldCmd->setLabel($cmd."_disabled"); + $oldCmd->unregister($cmdMap); + return true; + } + /** + * Send a PopUp, but takes care of checking if there are some + * plugins that might cause issues. + * + * Currently only supports SimpleAuth and BasicHUD. + * + * @param Player $player + * @param str $msg + */ + static public function sendPopup($player,$msg) { + $pm = $player->getServer()->getPluginManager(); + if (($sa = $pm->getPlugin("SimpleAuth")) !== null) { + // SimpleAuth also has a HUD when not logged in... + if ($sa->isEnabled() && !$sa->isPlayerAuthenticated($player)) return; + } + if (($hud = $pm->getPlugin("BasicHUD")) !== null) { + // Send pop-ups through BasicHUD + $hud->sendPopup($player,$msg); + return; + } + $player->sendPopup($msg); + } + + +} diff --git a/src/aliuly/goldstd/common/MoneyAPI.php b/src/aliuly/goldstd/common/MoneyAPI.php new file mode 100644 index 0000000..3622550 --- /dev/null +++ b/src/aliuly/goldstd/common/MoneyAPI.php @@ -0,0 +1,146 @@ +getLogger()->error($level,TextFormat::RED. + mc::_("! MISSING MONEY API PLUGIN")); + $plugin->getLogger()->error(TextFormat::BLUE. + mc::_(". Please install one of the following:")); + $plugin->getLogger()->error(TextFormat::WHITE. + mc::_("* GoldStd")); + $plugin->getLogger()->error(TextFormat::WHITE. + mc::_("* PocketMoney")); + $plugin->getLogger()->error(TextFormat::WHITE. + mc::_("* EconomyAPI or")); + $plugin->getLogger()->error(TextFormat::WHITE. + mc::_("* MassiveEconomy")); + } else { + $plugin->getLogger()->error($level,TextFormat::RED. + "! MISSING MONEY API PLUGIN"); + $plugin->getLogger()->error(TextFormat::BLUE. + ". Please install one of the following:"); + $plugin->getLogger()->error(TextFormat::WHITE. + "* GoldStd"); + $plugin->getLogger()->error(TextFormat::WHITE. + "* PocketMoney"); + $plugin->getLogger()->error(TextFormat::WHITE. + "* EconomyAPI or"); + $plugin->getLogger()->error(TextFormat::WHITE. + "* MassiveEconomy"); + } + } + /** + * Show a notice when the money API is found + * + * @param PluginBase $plugin - current plugin + * @param PluginBase $api - found plugin + * @param LogLevel $level - optional log level + */ + static public function foundMoney(PluginBase $plugin,$api,$level = LogLevel::INFO) { + if (class_exists(__NAMESPACE__."\\mc",false)) { + $plugin->getLogger()->log($level,TextFormat::BLUE. + mc::_("Using money API from %1%", + $api->getFullName())); + } else { + $plugin->getLogger()->log($level,TextFormat::BLUE. + "Using money API from ".$api->getFullName()); + } + } + /** + * Find a supported *money* plugin + * + * @param var obj - Server or Plugin object + * @return null|Plugin + */ + static public function moneyPlugin($obj) { + if ($obj instanceof Server) { + $server = $obj; + } else { + $server = $obj->getServer(); + } + $pm = $server->getPluginManager(); + if(!($money = $pm->getPlugin("PocketMoney")) + && !($money = $pm->getPlugin("GoldStd")) + && !($money = $pm->getPlugin("EconomyAPI")) + && !($money = $pm->getPlugin("MassiveEconomy"))){ + return null; + } + return $money; + } + /** + * Gives money to a player. + * + * @param Plugin api Economy plugin (from moneyPlugin) + * @param str|IPlayer p Player to pay + * @param int money Amount of money to play (can be negative) + * + * @return bool + */ + static public function grantMoney($api,$p,$money) { + if(!$api) return false; + switch($api->getName()){ + case "GoldStd": // takes IPlayer|str + $api->grantMoney($p, $money); + break; + case "PocketMoney": // takes str + if ($p instanceof IPlayer) $p = $p->getName(); + $api->grantMoney($p, $money); + break; + case "EconomyAPI": // Takes str + if ($p instanceof IPlayer) $p = $p->getName(); + $api->setMoney($p,$api->mymoney($p)+$money); + break; + case "MassiveEconomy": // Takes str + if ($p instanceof IPlayer) $p = $p->getName(); + $api->payPlayer($p->getName(),$money); + break; + default: + return false; + } + return true; + } + /** + * Gets player balance + * + * @param Plugin $api Economy plugin (from moneyPlugin) + * @param str|IPlayer $player Player to lookup + * + * @return int + */ + static public function getMoney($api,$player) { + if(!$api) return false; + switch($api->getName()){ + case "GoldStd": + return $api->getMoney($player); + break; + case "PocketMoney": + case "MassiveEconomy": + if ($player instanceof IPlayer) $player = $player->getName(); + return $api->getMoney($player); + case "EconomyAPI": + if ($player instanceof IPlayer) $player = $player->getName(); + return $api->mymoney($player); + default: + return false; + break; + } + } +} diff --git a/src/aliuly/goldstd/common/Npc.php b/src/aliuly/goldstd/common/Npc.php new file mode 100644 index 0000000..814c696 --- /dev/null +++ b/src/aliuly/goldstd/common/Npc.php @@ -0,0 +1,100 @@ +x), + new Double("", $pos->y), + new Double("", $pos->z)]); + if (isset($opts["motion"])) { + $ndat["Motion"] = new Enum("Motion", [ + new Double("",$opts["motion"][0]), + new Double("",$opts["motion"][1]), + new Double("",$opts["motion"][2])]); + unset($opts["motion"]); + } else { + $ndat["Motion"] = new Enum("Motion", [ + new Double("",0), + new Double("",0), + new Double("",0)]); + } + if (isset($opts["rotation"])) { + $ndat["Rotation"] = new Enum("Rotation", [ + new Float("",$opts["rotation"][0]), + new Float("",$opts["rotation"][1])]); + unset($opts["rotation"]); + } else { + $ndat["Rotation"] = new Enum("Rotation", [ + new Float("",$pos->yaw), + new Float("",$pos->pitch)]); + } + $ndat["Skin"] = new Compound("Skin", [ + "Data" => new String("Data", $opts["skin"]), + "Slim" => new Byte("Slim", $opts["slim"] ? 1 : 0)]); + unset($opts["skin"]); + unset($opts["slim"]); + + foreach ($opts as $k=>$v) { + if ($v instanceof Tag) { + $ndat[$k] = $v; + continue; + } + if (is_array($v) && count($v) == 2) { + list($type,$value) = $v; + $type = "pocketmine\\nbt\\tag\\".$type; + $ndat[$k] = new $type($k,$value); + continue; + } + switch (gettype($v)) { + case "boolean": + $ndat[$k] = new Byte($k,$v ? 1 : 0); + break; + case "integer": + $ndat[$k] = new Int($k, $v); + break; + case "double": + $ndat[$k] = new Double($k,$v); + break; + case "string": + $ndat[$k] = new String($k,$v); + } + } + $npc = new $classname($pos->getLevel()->getChunk($pos->getX()>>4, + $pos->getZ()>>4), + new Compound("",$ndat)); + $npc->setNameTag($name); + return $npc; + } +} diff --git a/src/aliuly/goldstd/common/PluginCallbackTask.php b/src/aliuly/goldstd/common/PluginCallbackTask.php new file mode 100644 index 0000000..a8f06e0 --- /dev/null +++ b/src/aliuly/goldstd/common/PluginCallbackTask.php @@ -0,0 +1,52 @@ +callable = $callable; + $this->args = $args; + $this->args[] = $this; + } + /** + * @return callable + */ + public function getCallable(){ + return $this->callable; + } + + public function onRun($currentTicks){ + $c = $this->callable; + $args = $this->args; + $args[] = $currentTicks; + $c(...$args); + } + +} diff --git a/src/aliuly/goldstd/common/mc.php b/src/aliuly/goldstd/common/mc.php new file mode 100644 index 0000000..1e9c35b --- /dev/null +++ b/src/aliuly/goldstd/common/mc.php @@ -0,0 +1,105 @@ +getFile()); + * * mc::_("string to translate\n") + * * mc::_("string to translate %1% %2%\n",$arg1,$arg2) + * * mc::n(mc::\_("singular form"),mc::\_("Plural form"),$count) + */ +abstract class mc { + /** @var str[] $txt Message translations */ + public static $txt = []; + /** Main translation function + * + * This translates strings. The naming of "_" is to make it compatible + * with gettext utilities. The string can contain "%1%", "%2%, etc... + * These are inserted from the following arguments. Use "%%" to insert + * a single "%". + * + * @param str[] $args - messages + * @return str translated string + */ + public static function _(...$args) { + $fmt = array_shift($args); + if (isset(self::$txt[$fmt])) $fmt = self::$txt[$fmt]; + if (count($args)) { + $vars = [ "%%" => "%" ]; + $i = 1; + foreach ($args as $j) { + $vars["%$i%"] = $j; + ++$i; + } + $fmt = strtr($fmt,$vars); + } + return $fmt; + } + /** + * Plural and singular forms. + * + * @param str $a - Singular form + * @param str $b - Plural form + * @param int $c - the number to test to select between $a or $b + * @return str - Either plural or singular forms depending on the value of $c + */ + public static function n($a,$b,$c) { + return $c == 1 ? $a : $b; + } + /** + * Load a message file for a PocketMine plugin. Only uses .ini files. + * + * @param Plugin $plugin - owning plugin + * @param str $path - output of $plugin->getFile() + */ + public static function plugin_init($plugin,$path) { + if (file_exists($plugin->getDataFolder()."messages.ini")) { + self::load($plugin->getDataFolder()."messages.ini"); + return; + } + $msgs = $path."resources/messages/". + $plugin->getServer()->getProperty("settings.language"). + ".ini"; + if (!file_exists($msgs)) return; + mc::load($msgs); + } + /** + * Load the specified message catalogue. + * Can read .ini or .po files. + * @param str $f - Filename to load + */ + public static function load($f) { + $potxt = "\n".file_get_contents($f)."\n"; + if (preg_match('/\nmsgid\s/',$potxt)) { + $potxt = preg_replace('/\\\\n"\n"/',"\\n", + preg_replace('/\s+""\s*\n\s*"/'," \"", + $potxt)); + } + foreach (['/\nmsgid "(.+)"\nmsgstr "(.+)"\n/', + '/^\s*"(.+)"\s*=\s*"(.+)"\s*$/m'] as $re) { + $c = preg_match_all($re,$potxt,$mm); + if ($c) { + for ($i=0;$i<$c;++$i) { + if ($mm[2][$i] == "") continue; + eval('$a = "'.$mm[1][$i].'";'); + eval('$b = "'.$mm[2][$i].'";'); + mc::$txt[$a] = $b; + } + return $c; + } + } + return false; + } +}