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 0000000..89b7a4c Binary files /dev/null and b/media/GoldStd1-icon.png differ diff --git a/media/GoldStd2-icon.png b/media/GoldStd2-icon.png new file mode 100644 index 0000000..cc18109 Binary files /dev/null and b/media/GoldStd2-icon.png differ diff --git a/plugin.yml b/plugin.yml new file mode 100644 index 0000000..b8b7a46 --- /dev/null +++ b/plugin.yml @@ -0,0 +1,67 @@ +name: GoldStd +version: 1.2.2 +main: aliuly\goldstd\Main +api: [1.12.0] +load: POSTWORLD +website: https://github.com/alejandroliu/pocketmine-plugins/tree/master/GoldStd + +description: Gold based economy plugin +author: aliuly + +commands: + pay: + description: Will change the pay rate once + usage: "/pay [qty]" + permission: goldstd.cmd.pay + balance: + description: Display the amount of money you have + usage: "/balance" + permission: goldstd.cmd.balance + shopkeep: + description: Manage shop keepers in-game + usage: "/shopkeep [subcmd] [options]" + permission: goldstd.cmd.shopkeep + +permissions: + goldstd.cmd.pay: + default: true + description: "Access to pay command" + goldstd.cmd.balance: + default: true + description: "Show your current balance" + goldstd.cmd.shopkeep: + default: op + description: "ShopKeepr management" + goldstd.shopkeep.shop: + default: true + description: "Allow buying from shop keeper" + goldstd.signs.use: + default: true + description: "Allow access to signs" + goldstd.signs.use.casino: + default: true + description: "Allow access to casino signs" + goldstd.signs.use.shop: + default: true + description: "Allow access to shopping signs" + goldstd.signs.use.trade: + default: true + description: "Allow access to trading signs" + goldstd.signs.use.effects: + default: true + description: "Allow access to Effects signs" + goldstd.signs.place: + default: op + description: "Allow placing signs" + goldstd.signs.place.casino: + default: op + description: "Allow placing casino signs" + goldstd.signs.place.shop: + default: op + description: "Allow placing shopping signs" + goldstd.signs.place.trade: + default: op + description: "Allow placing trading signs" + goldstd.signs.place.effects: + default: op + description: "Allow placing Effects signs" diff --git a/resources/default.skin b/resources/default.skin new file mode 100644 index 0000000..3eff2db Binary files /dev/null and b/resources/default.skin differ diff --git a/resources/messages/messages.ini b/resources/messages/messages.ini new file mode 100644 index 0000000..941b4f8 --- /dev/null +++ b/resources/messages/messages.ini @@ -0,0 +1,85 @@ +; messages.ini +"! MISSING MONEY API PLUGIN"="" +"%1%-mode"="" +"%1%: Unknown sub-command"="" +"* EconomyAPI or"="" +"* GoldStd"="" +"* MassiveEconomy"="" +"* PocketMoney"="" +". Please install one of the following:"="" +"Adventure"="" +"Bad quantity: %1%"="" +"Creative"="" +"Error trading with NPC at %1%,%2%,%3% (%4%)"="" +"Gave away %2% %1%s"="" +"Gave away one %1%"="" +"Gave one %1%"="" +"Goods trading disabled!"="" +"Got %2% %1%s"="" +"Got one %1%"="" +"Invalid currency item"="" +"Invalid effects line"="" +"Invalid item%1% line"="" +"Invalid item1 line"="" +"Invalid item2 line"="" +"Invalid item line"="" +"Invalid odds line"="" +"Invalid price: %1%"="" +"Invalid price line"="" +"Invalid shop %5% for NPC at %1%,%2%,%3% (%4%)"="" +"Invalid trade-good: %1%"="" +"Item error: %1%"="" +"Next payout will be for %1%G"="" +"No purchases while in %1% mode."="" +"No shopkeepers found!"="" +"No trading possible, %1% is in %2% mode"="" +"No trading possible, while in %1% mode"="" +"PAY command has been disabled"="" +"Paid %2%G, you now have %1%G"="" +"Received %1%"="" +"Received %2%G, you now have %1%G"="" +"Shop-Keepers disabled"="" +"ShopKeep %1% disabled!"="" +"ShopKeeper %1% not found"="" +"ShopKeeper functionality disabled"="" +"SignShops disabled"="" +"Sorry, nothing happens..."="" +"Sorry, shop is closed!"="" +"Spawned shopkeep: %1%"="" +"Spectator"="" +"Survival"="" +"Unable to identify player in inventory transaction"="" +"Unknown Item error: %1%"="" +"Using GOLD_INGOT as currency"="" +"Using Money API of %1%"="" +"Using money API from %1%"="" +"World %1% not found"="" +"You cannot use this in creative or specator mode"="" +"You can only do this in-game"="" +"You don't have that much money!"="" +"You do not have any %1%"="" +"You do not have enough %1%"="" +"You do not have enough money"="" +"You do not have permission to do that."="" +"You have %1%, you need %2%"="" +"You have %1%G"="" +"[GoldStd] Betting %1%..."="" +"[GoldStd] BooooM!!! You lost"="" +"[GoldStd] Item purchased"="" +"[GoldStd] Potion %1% purchased"="" +"[GoldStd] You WON!!! prize...%1%G"="" +"[GoldStd] You do not have enough money"="" +"[GoldStd] You do not have enough moneys"="" +"[GoldStd] placed sign"="" +"shopkeep command disabled"="" +"#%1%: Invalid item %2%, using default"="" +"#Insufficient funds to complete purchase!"="" +"#Invalid weapon item: %1%"="" +"#Inventory error"="" +"# MISSING MONEY API PLUGIN"="" +"#Maybe next time..."="" +"#Registered listener..."="" +"#Spent %1%G to buy %2% good"="" +"#Spent %1%G to buy %2% goods"="" +"#Thank you for your business\nCome back again soon!"="" +"#Your shopping cart value is %1%G, you only have %2%G"="" diff --git a/resources/messages/spa.ini b/resources/messages/spa.ini new file mode 100644 index 0000000..1c36512 --- /dev/null +++ b/resources/messages/spa.ini @@ -0,0 +1,97 @@ +; spa.ini +"! MISSING MONEY API PLUGIN"="" +"%1%-mode"="modo-%1%" +"%1%: Unknown sub-command"="%1%: Sub-commando desconocido" +"* EconomyAPI or"="* EconomyAPI o" +"* GoldStd"="* GoldStd" +"* MassiveEconomy"="* MassiveEconomy" +"* PocketMoney"="* PocketMoney" +". Please install one of the following:"=". Por favor, instale uno de los siguientes:" +"Adventure"="Aventura" +"Bad quantity: %1%"="Cantidad inválida: %1%" +"Creative"="Creativo" +"Error trading with NPC at %1%,%2%,%3% (%4%)"="Error intercambiando con NPC úbicado en %1%,%2%,%3% (%4%)" +"Gave away %2% %1%s"="Regaló %2% %1%s" +"Gave away one %1%"="Regaló un %1%" +"Gave one %1%"="Dió un %1%" +"Goods trading disabled!"="Intercambio de articulos deshabilitado!" +"Got %2% %1%s"="Recibió %2% %1%s" +"Got one %1%"="Recibió %1%" +"Invalid currency item"="Unidad de Moneda inválida" +"Invalid effects line"="Línea de efectos inválido" +"Invalid item%1% line"="Objecto %1% inválido" +"Invalid item1 line"="Línea de objecto1 inválido" +"Invalid item2 line"="Línea de objecto2 inválido" +"Invalid item line"="Línea de objecto inválido" +"Invalid odds line"="Línea de premios inválido" +"Invalid price: %1%"="Precio de %1% no es válido" +"Invalid price line"="Línea de precios inválido" +"Invalid shop %5% for NPC at %1%,%2%,%3% (%4%)"="Mala tienda %5% del NPC en %1%,%2%,%3% (%4%)" +"Invalid trade-good: %1%"="Objecto de intercambio erróneo: %1%" +"Item error: %1%"="Error: Item %1%" +"Next payout will be for %1%G"="Siguiente pago será por %1%G" +"No purchases while in %1% mode."="No se puede comprar cuando se está en modo %1%" +"No shopkeepers found!"="No se encotraron tiendas" +"No trading possible, %1% is in %2% mode"="No hay intercambio posible. %1% está en modo %2%" +"No trading possible, while in %1% mode"="No hay intercambio posible estando en modo %1%" +"PAY command has been disabled"="Comando PAY deshabilitado" +"Paid %2%G, you now have %1%G"="Pagó %2%G, ahora tiene %1%G" +"Received %1%"="Recibió %1%" +"Received %2%G, you now have %1%G"="Cobró %2%G, ahora tiene %1%G" +"Shop-Keepers disabled"="Tiendas deshabilitadas" +"ShopKeep %1% disabled!"="Tienda %1% deshabilitiada" +"ShopKeeper %1% not found"="Tienda %1% no existe" +"ShopKeeper functionality disabled"="Tiendas deshabilitadas" +"SignShops disabled"="SignShops deshabilitadas" +"Sorry, nothing happens..."="Lo siento, no pasó nada..." +"Sorry, shop is closed!"="Disculpe la molestia. Esa tienda está cerrada." +"Spawned shopkeep: %1%"="Generando vendedor: %1%" +"Spectator"="Espectador" +"Survival"="Supervivencia" +"Unable to identify player in inventory transaction"="Error identificando a jugador" +"Unknown Item error: %1%"="Objecto desconocido: %1%" +"Using GOLD_INGOT as currency"="Usando GOLD_INGOT como moneda" +"Using Money API of %1%"="Usando el API de dinero de %1%" +"Using money API from %1%"="Usando el API de dinero de %1%" +"World %1% not found"="Mundo %1% no se ha encontrado" +"You cannot use this in creative or specator mode"="No se puede usar en modo creativo o espectador" +"You can only do this in-game"="Esto sólo se puede hacer dentro del juego" +"You don't have that much money!"="No tiene tanto dinero" +"You do not have any %1%"="No tiene ningún %1%" +"You do not have enough %1%"="No tiene suficientes %1%" +"You do not have enough money"="No tiene suficiente dinero" +"You do not have permission to do that."="No tiene permiso para hacer eso." +"You have %1%, you need %2%"="Tiene %1%, necesita %2%" +"You have %1%G"="Tiene %1%G" +"[GoldStd] Betting %1%..."="Apostando %1%..." +"[GoldStd] BooooM!!! You lost"="BuuuuM!!! Perdiste!" +"[GoldStd] Item purchased"="Objecto comprado" +"[GoldStd] Potion %1% purchased"="[GoldStd] Poción de %1% comprada" +"[GoldStd] You WON!!! prize...%1%G"="Ganaste!!!! Premio... %1%G" +"[GoldStd] You do not have enough money"="No tiene suficiente dinero" +"[GoldStd] You do not have enough moneys"="No tiene suficiente dinero" +"[GoldStd] placed sign"="[GoldStd] cartel instalado" +"shopkeep command disabled"="comando shopkeep deshabilitado" +"#%1%: Invalid item %2%, using default"="%1%: %2% no es valido" +"#Available sub-commands for %1%"="Subcomandos disponbles para %1%" +"#Description: "="Descripción: " +"#Enable %1% features"="Activando %1% módulos" +"#Enabled one feature"="Activando un módulo" +"#Help: "="Auyda: " +"#Insufficient funds to complete purchase!"="" +"#Invalid weapon item: %1%"="Arma errónea: %1%" +"#Inventory error"="" +"# MISSING MONEY API PLUGIN"="" +"#Maybe next time..."="" +"#NO features enabled"="NO módulos activados" +"#No help for %1%"="No hay ayuda para %1%" +"#No sub-command specified"="No sub-comando proporcionado" +"#Registered listener..."="" +"#Spent %1%G to buy %2% good"="" +"#Spent %1%G to buy %2% goods"="" +"#Thank you for your business\nCome back again soon!"="" +"#Unknown feature \"%1%\" ignored."="Módulo \"%1%\" desconocido, ignorando..." +"#Unknown sub-command %2% (try /%1% help)"="Sub-comando %2% desconocido. (Pruebe /%1% help)" +"#Usage: "="Uso: " +"#You are not allowed to do this"="No esta autorizado a hacer esto" +"#Your shopping cart value is %1%G, you only have %2%G"="" diff --git a/resources/shops.yml b/resources/shops.yml new file mode 100644 index 0000000..bf343c5 --- /dev/null +++ b/resources/shops.yml @@ -0,0 +1,35 @@ +--- +default: + skin: default.skin + slim: false + items: + - WOOD,10,1 + - DIAMOND_SWORD,20 + - IRON_SWORD,10 + - STONE_SWORD,5 + - APPLE,10,1 + - COOKED_BEEF,5,1 + display: Albert + attack: 8 + messages: + inventory-error: "Sorry we are out of stock!" + not-enough-g: "Your shopping cart value is %1%G, you only have %2%G" + next-time: + - "Maybe next time..." + - "Are you sure you don't want to buy anything?" + bought-items1: "Spent %1%G to buy %2% good" + bought-itemsX: "Spent %1%G to buy %2% goods" + thank-you: "Thank you for your business. Come back again soon!" + no-money: "Insufficient funds to complete purchase!" + help-info: "You should tap me with a Gold INGOT. Don't even think about using a weapon!" + under-attack: "Aaahh! Heeelp! Thieves!!!!" + leaving: "Have a nice day" + buystuff: + - "Welcome, please tap me with a Gold INGOT" + - "I have lots of stuff for sale" + - "My stuff is best quality" + - "NO LOITERING!" + welcome: + - "Welcome to Albert Heijn, tap me with a Gold Ingot to trade" + - "Hello, how is it? Tap me with a gold ingot to buy things" +... diff --git a/src/aliuly/goldstd/Main.php b/src/aliuly/goldstd/Main.php new file mode 100644 index 0000000..4488007 --- /dev/null +++ b/src/aliuly/goldstd/Main.php @@ -0,0 +1,249 @@ +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; + } +}