diff --git a/Server/src/main/java/core/game/ge/GEGuidePrice.java b/Server/src/main/java/core/game/ge/GEGuidePrice.java index 7500c90e8..b077d94ce 100644 --- a/Server/src/main/java/core/game/ge/GEGuidePrice.java +++ b/Server/src/main/java/core/game/ge/GEGuidePrice.java @@ -3,6 +3,7 @@ package core.game.ge; import core.game.component.Component; import core.game.node.entity.player.Player; import core.tools.StringUtils; +import rs09.game.ge.OfferManager; /** * Represents the glass used to open the guide prices for the different type of @@ -139,7 +140,7 @@ public final class GEGuidePrice { player.getPacketDispatch().sendInterfaceConfig(642, i, false); } for (GuideItem item : getItems()) { - player.getPacketDispatch().sendString("" + GrandExchangeDatabase.getDatabase().get(item.getItem()).getValue() + " gp", COMPONENT.getId(), item.getChildData()[0]); + player.getPacketDispatch().sendString("" + OfferManager.getRecommendedPrice(item.item, false) + " gp", COMPONENT.getId(), item.getChildData()[0]); } } } diff --git a/Server/src/main/java/core/game/interaction/inter/GrandExchangeInterface.java b/Server/src/main/java/core/game/interaction/inter/GrandExchangeInterface.java index 87ab55a33..e5714afd8 100644 --- a/Server/src/main/java/core/game/interaction/inter/GrandExchangeInterface.java +++ b/Server/src/main/java/core/game/interaction/inter/GrandExchangeInterface.java @@ -22,6 +22,7 @@ import core.plugin.Initializable; import core.plugin.Plugin; import kotlin.Unit; import rs09.game.ge.GrandExchangeOffer; +import rs09.game.ge.OfferManager; import rs09.game.ge.PlayerGrandExchange; import rs09.game.interaction.npc.BogrogPouchSwapper; import rs09.game.world.GameWorld; @@ -69,7 +70,7 @@ public class GrandExchangeInterface extends ComponentPlugin { if (offer == null || value < 1 || offer.getOfferState() != OfferState.PENDING) { return; } - if (value == GrandExchangeDatabase.getDatabase().get(offer.getItemID()).getValue()) { + if (value == OfferManager.getRecommendedPrice(offer.getItemID(), false)) { player.getAudioManager().send(new Audio(4043, 1, 1)); } else if (value > offer.getOfferedValue()) { player.getAudioManager().send(new Audio(4041, 1, 1)); @@ -77,7 +78,7 @@ public class GrandExchangeInterface extends ComponentPlugin { player.getAudioManager().send(new Audio(4045, 1, 1)); } offer.setOfferedValue(value); - player.getConfigManager().send(1111, offer.getOfferedValue()); + player.varpManager.get(1111).setVarbit(0,offer.getOfferedValue()).send(player); } @Override @@ -348,14 +349,14 @@ public class GrandExchangeInterface extends ComponentPlugin { return false; case 180: if (offer != null) { - setOfferValue(player, offer, GrandExchangeDatabase.getDatabase().get(offer.getItemID()).getValue()); + setOfferValue(player, offer, OfferManager.getRecommendedPrice(offer.getItemID(), false)); return true; } return false; case 177: // mid - 5% value case 183: // mid + 5% value if (offer != null) { - setOfferValue(player, offer, (int) (GrandExchangeDatabase.getDatabase().get(offer.getItemID()).getValue() * (button == 177 ? 0.95 : 1.05))); + setOfferValue(player, offer, (int) (OfferManager.getRecommendedPrice(offer.getItemID(), false) * (button == 177 ? 0.95 : 1.05))); return true; } return false; diff --git a/Server/src/main/java/core/game/node/entity/npc/drop/NPCDropTables.java b/Server/src/main/java/core/game/node/entity/npc/drop/NPCDropTables.java index e50428254..cf651066b 100644 --- a/Server/src/main/java/core/game/node/entity/npc/drop/NPCDropTables.java +++ b/Server/src/main/java/core/game/node/entity/npc/drop/NPCDropTables.java @@ -19,6 +19,7 @@ import rs09.game.ai.AIPlayer; import rs09.game.ai.AIRepository; import rs09.game.ai.general.GeneralBotCreator; import rs09.game.content.global.NPCDropTable; +import rs09.game.ge.OfferManager; import rs09.game.system.config.ItemConfigParser; import rs09.game.world.repository.Repository; @@ -177,7 +178,7 @@ public final class NPCDropTables { } } player.sendMessage(player.getInterfaceManager().isResizable()+""); - int price = item.getName().endsWith("charm") ? 100 : GrandExchangeDatabase.getDatabase().get(itemId).getValue(); + int price = item.getName().endsWith("charm") ? 100 : OfferManager.getRecommendedPrice(itemId, false); looter.getGlobalData().setLootSharePoints(looter.getGlobalData().getLootSharePoints() - (price) + ((price / looters.size()))); looter.sendMessage((player.getInterfaceManager().isResizable() ? "" : "") + "You received: " + item.getAmount() + " " + item.getName()); for (Player p : looters) { diff --git a/Server/src/main/java/core/game/node/item/Item.java b/Server/src/main/java/core/game/node/item/Item.java index 14c5845d9..39d771ad1 100644 --- a/Server/src/main/java/core/game/node/item/Item.java +++ b/Server/src/main/java/core/game/node/item/Item.java @@ -8,6 +8,7 @@ import core.game.interaction.Interaction; import core.game.interaction.OptionHandler; import core.game.node.Node; import core.game.node.entity.combat.equipment.DegradableEquipment; +import rs09.game.ge.OfferManager; /** * Represents an item. @@ -99,10 +100,7 @@ public class Item extends Node{ */ public long getValue() { long value = 1; - GrandExchangeEntry entry = GrandExchangeDatabase.getDatabase().get(getId()); - if (entry != null) { - value = entry.getValue(); - } + value = OfferManager.getRecommendedPrice(getId(), false); if (definition.getValue() > value) { value = definition.getValue(); } diff --git a/Server/src/main/java/core/gui/tab/GrandExchangeTab.java b/Server/src/main/java/core/gui/tab/GrandExchangeTab.java index 2d4d10c41..c6fe68e39 100644 --- a/Server/src/main/java/core/gui/tab/GrandExchangeTab.java +++ b/Server/src/main/java/core/gui/tab/GrandExchangeTab.java @@ -161,7 +161,7 @@ public class GrandExchangeTab extends ConsoleTab { } catch (NumberFormatException e) { } - for (GrandExchangeOffer o : OfferManager.Companion.getOffersForItem(itemId)) { + for (GrandExchangeOffer o : OfferManager.getOffersForItem(itemId)) { if (o == null) { continue; } @@ -184,7 +184,7 @@ public class GrandExchangeTab extends ConsoleTab { JOptionPane.showMessageDialog(null, "Error! No data in DB yet. Press load."); return; } - for (GrandExchangeOffer offer : OfferManager.Companion.getOFFER_MAPPING().values()) { + for (GrandExchangeOffer offer : OfferManager.getOFFER_MAPPING().values()) { model.addElement(offer); } } diff --git a/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt b/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt new file mode 100644 index 000000000..6933fc243 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt @@ -0,0 +1,46 @@ +package rs09.game.ge + +import core.game.world.callback.CallBack +import rs09.game.system.SystemLogger +import rs09.tools.secondsToTicks + +object GrandExchange : CallBack { + /** + * Fallback safety check to make sure we don't start the GE twice under any circumstance + */ + var isRunning = false + + /** + * Initializes the offer manager and spawns an update thread. + * @param local whether or not the GE should be the local in-code server rather than some hypothetical remote implementation. + */ + fun boot(local: Boolean){ + if(isRunning) return + + if(!local){ + TODO("Remote GE server stuff") + } + + SystemLogger.logGE("Initializing GE...") + OfferManager.init() + SystemLogger.logGE("GE Initialized.") + + SystemLogger.logGE("Initializing GE Update Worker") + + val t = Thread { + Thread.currentThread().name = "GE Update Worker" + while(true) { + SystemLogger.logGE("Updating offers...") + OfferManager.update() + Thread.sleep(60_000) //sleep for 60 seconds + } + }.start() + + isRunning = true + } + + override fun call(): Boolean { + boot(true) + return true + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/ge/OfferManager.kt b/Server/src/main/kotlin/rs09/game/ge/OfferManager.kt index 372b4d04e..9042c7a7a 100644 --- a/Server/src/main/kotlin/rs09/game/ge/OfferManager.kt +++ b/Server/src/main/kotlin/rs09/game/ge/OfferManager.kt @@ -1,5 +1,6 @@ package rs09.game.ge +import api.ContentAPI import rs09.ServerConstants import core.cache.def.impl.ItemDefinition import core.game.content.eco.EcoStatus @@ -23,524 +24,507 @@ import org.json.simple.JSONArray import org.json.simple.JSONObject import org.json.simple.parser.JSONParser import rs09.game.ai.AIPlayer +import rs09.game.system.config.ItemConfigParser import java.io.File import java.io.FileReader import java.io.FileWriter import java.io.IOException +import java.lang.Integer.max import java.lang.Integer.min import java.util.* import java.util.concurrent.locks.ReentrantLock import javax.script.ScriptEngineManager +import kotlin.collections.ArrayList -class OfferManager : Pulse(), CallBack { +object OfferManager { /** - * How often in ticks should ge offers that hit the buying limit be reprocessed? + * The update notification. */ - private val RESET_BUYING_LIMIT_INTERVAL = 24000 + private const val UPDATE_NOTIFICATION = "One or more of your grand exchange offers have been updated." /** - * How often should the database be saved to disk? + * The database path. */ - private val SAVE_EVERY = ServerConstants.GE_AUTOSAVE_FREQUENCY + private val DB_PATH = ServerConstants.GRAND_EXCHANGE_DATA_PATH + "offer_dispatch.json" - override fun call(): Boolean { - init() - delay = 1 - GameWorld.Pulser.submit(this) + /** + * Bot DB path + */ + private val BOT_DB_PATH = ServerConstants.GRAND_EXCHANGE_DATA_PATH + "bot_offers.json" + + /** + * The offset of the offer UIDs. + */ + private var offsetUID: Long = 1 + + /** + * The mapping of all current offers. Stored in multiple maps. + * + * First map is offsetID, Offer + * Second is itemID, offsetID, Offer + * Final is playerID, offsetID, Offer + */ + @JvmStatic + val OFFER_MAPPING: MutableMap = HashMap() + val OFFERS_BY_ITEMID: MutableMap> = HashMap() + private val GE_OFFER_LOCK = ReentrantLock() + + /** + * Bot offers are sorted by itemID. + * the second int shows the offer amount. Negative is buying positive selling. + */ + public val BOT_OFFERS: HashMap = HashMap() + + /** + * If the database should be dumped. + */ + public var dumpDatabase = false + + /** + * Initializes the Grand Exchange. + */ + fun init() { + GE_OFFER_LOCK.lock() + val file = File(DB_PATH) + + if(file.exists() && file.length() != 0L) { + val parser = JSONParser() + val reader: FileReader? = FileReader(DB_PATH) + val saveFile = parser.parse(reader) as JSONObject + + offsetUID = saveFile["offsetUID"].toString().toLong() + + if (saveFile.containsKey("offers")) { + val offers = saveFile["offers"] as JSONArray + for (offer in offers) { + val o = offer as JSONObject + // Copy all the bot offers from the file + if (o["playerUID"].toString().toInt() == 0) { + addBotOffer(o["itemId"].toString().toInt(), o["amount"].toString().toInt() - o["completedAmount"].toString().toInt()) + } + val no = GrandExchangeOffer() + no.itemID = o["itemId"].toString().toInt() + no.sell = o["sale"] as Boolean + no.offeredValue = o["offeredValue"].toString().toInt() + no.amount = o["amount"].toString().toInt() + no.timeStamp = o["timeStamp"].toString().toLong() + no.uid = o["uid"].toString().toLong() + no.completedAmount = o["completedAmount"].toString().toInt() + no.playerUID = o["playerUID"].toString().toInt() + no.offerState = OfferState.values()[o["offerState"].toString().toInt()] + no.totalCoinExchange = o["totalCoinExchange"].toString().toInt() + val withdrawData = o["withdrawItems"] as JSONArray + for ((index, data) in withdrawData.withIndex()) { + val item = data as JSONObject + val it = Item(item["id"].toString().toInt(), item["amount"].toString().toInt()) + no.withdraw[index] = it + } + addEntry(no) + } + } + } + + if(File(BOT_DB_PATH).exists()) { + try { + val botReader: FileReader? = FileReader(BOT_DB_PATH) + val botSave = JSONParser().parse(botReader) as JSONObject + if (botSave.containsKey("offers")) { + val offers = botSave["offers"] as JSONArray + for (offer in offers) { + val o = offer as JSONObject + addBotOffer(o["item"].toString().toInt(), o["qty"].toString().toInt()) + } + } + } catch (e: IOException) { + SystemLogger.logWarn("Unable to load bot offers. Perhaps it doesn't exist?") + } + } + GE_OFFER_LOCK.unlock() + } + + fun update(){ + for (offer in OFFER_MAPPING.values) { + if (offer.isActive) { + updateOffer(offer) + } + } + } + + fun buyFromBots(offer: GrandExchangeOffer) { + if (BOT_OFFERS[offer.itemID] == null) { + return + } + val botPrice = getRecommendedPrice(offer.itemID, true) + if (offer.offeredValue < botPrice) { + return + } + val amount = min(BOT_OFFERS[offer.itemID]!!, getBuylimitAmount(offer)) + val botOffer = GrandExchangeOffer() + botOffer.sell = true + botOffer.amount = amount + botOffer.offerState = OfferState.REGISTERED + botOffer.offeredValue = botPrice + exchange(offer, botOffer) + BOT_OFFERS[offer.itemID] = BOT_OFFERS[offer.itemID]!! - amount + } + + private fun buyFromBotsWithItem(itemID: Int) { + if (OFFERS_BY_ITEMID[itemID] == null || BOT_OFFERS[itemID] == null) { + return + } + for (trade in OFFERS_BY_ITEMID[itemID]!!) { + if (!trade.sell) { + buyFromBots(trade) + } + } + } + + fun addBotOffer(itemID: Int, qty: Int): Boolean { + if (GrandExchangeDatabase.getDatabase()[itemID] == null) { + SystemLogger.logWarn("Bot attempted to sell invalid item $itemID") + return false + } + + if (BOT_OFFERS[itemID] == null) { + BOT_OFFERS[itemID] = qty + } else { + BOT_OFFERS[itemID] = (qty + BOT_OFFERS[itemID]!!) + } + buyFromBotsWithItem(itemID) return true } - override fun pulse(): Boolean { - // TODO: Update offers code - if (GameWorld.ticks % RESET_BUYING_LIMIT_INTERVAL == 0) { - BuyingLimitation.clear() - for (offer in OFFER_MAPPING.values) { - if (offer.isActive && offer.isLimitation) { - updateOffer(offer) - } - } + fun amtBotsSelling(itemID: Int): Int { + if (BOT_OFFERS[itemID] == null) { + return 0 } - - if (dumpDatabase && (GameWorld.ticks % SAVE_EVERY == 0)) { - //save() - dumpDatabase = false + if (BOT_OFFERS[itemID]!! <= 0) { + return 0 } - - return false + return BOT_OFFERS[itemID]!! } + fun setIndex(offerID: Long, idx: Int) { + if (!OFFER_MAPPING.containsKey(offerID)) { + println("ERROR. GE Entry $offerID not found in database. Playerdata may be corrupted.") + return + } + OFFER_MAPPING[offerID]!!.index = idx + } - companion object { - /** - * The update notification. - */ - private const val UPDATE_NOTIFICATION = "One or more of your grand exchange offers have been updated." - - /** - * The database path. - */ - private val DB_PATH = ServerConstants.GRAND_EXCHANGE_DATA_PATH + "offer_dispatch.json" - - /** - * Bot DB path - */ - private val BOT_DB_PATH = ServerConstants.GRAND_EXCHANGE_DATA_PATH + "bot_offers.json" - - /** - * The offset of the offer UIDs. - */ - private var offsetUID: Long = 1 - - /** - * The mapping of all current offers. Stored in multiple maps. - * - * First map is offsetID, Offer - * Second is itemID, offsetID, Offer - * Final is playerID, offsetID, Offer - */ - val OFFER_MAPPING: MutableMap = HashMap() - val OFFERS_BY_ITEMID: MutableMap> = HashMap() - private val GE_OFFER_LOCK = ReentrantLock() - - /** - * Bot offers are sorted by itemID. - * the second int shows the offer amount. Negative is buying positive selling. - */ - public val BOT_OFFERS: HashMap = HashMap() - - /** - * If the database should be dumped. - */ - public var dumpDatabase = false - - /** - * Initializes the Grand Exchange. - */ - fun init() { - GE_OFFER_LOCK.lock() - val file = File(DB_PATH) - - if(file.exists() && file.length() != 0L) { - val parser = JSONParser() - val reader: FileReader? = FileReader(DB_PATH) - val saveFile = parser.parse(reader) as JSONObject - - offsetUID = saveFile["offsetUID"].toString().toLong() - - if (saveFile.containsKey("offers")) { - val offers = saveFile["offers"] as JSONArray - for (offer in offers) { - val o = offer as JSONObject - // Copy all the bot offers from the file - if (o["playerUID"].toString().toInt() == 0) { - addBotOffer(o["itemId"].toString().toInt(), o["amount"].toString().toInt() - o["completedAmount"].toString().toInt()) - } - val no = GrandExchangeOffer() - no.itemID = o["itemId"].toString().toInt() - no.sell = o["sale"] as Boolean - no.offeredValue = o["offeredValue"].toString().toInt() - no.amount = o["amount"].toString().toInt() - no.timeStamp = o["timeStamp"].toString().toLong() - no.uid = o["uid"].toString().toLong() - no.completedAmount = o["completedAmount"].toString().toInt() - no.playerUID = o["playerUID"].toString().toInt() - no.offerState = OfferState.values()[o["offerState"].toString().toInt()] - no.totalCoinExchange = o["totalCoinExchange"].toString().toInt() - val withdrawData = o["withdrawItems"] as JSONArray - for ((index, data) in withdrawData.withIndex()) { - val item = data as JSONObject - val it = Item(item["id"].toString().toInt(), item["amount"].toString().toInt()) - no.withdraw[index] = it - } - addEntry(no) - } - } - } - - if(File(BOT_DB_PATH).exists()) { - try { - val botReader: FileReader? = FileReader(BOT_DB_PATH) - val botSave = JSONParser().parse(botReader) as JSONObject - if (botSave.containsKey("offers")) { - val offers = botSave["offers"] as JSONArray - for (offer in offers) { - val o = offer as JSONObject - addBotOffer(o["item"].toString().toInt(), o["qty"].toString().toInt()) - } - } - } catch (e: IOException) { - SystemLogger.logWarn("Unable to load bot offers. Perhaps it doesn't exist?") - } - } + fun removeEntry(offer: GrandExchangeOffer): Boolean{ + println("REMOVING ENTRY of ID " + offer.itemID) + GE_OFFER_LOCK.lock() + if (!OFFER_MAPPING.containsKey(offer.uid)){ GE_OFFER_LOCK.unlock() + return false } + OFFER_MAPPING.remove(offer.uid) + OFFERS_BY_ITEMID[offer.itemID]!!.remove(offer) + GE_OFFER_LOCK.unlock() + return true + } - fun buyFromBots(offer: GrandExchangeOffer) { - if (BOT_OFFERS[offer.itemID] == null) { - return - } - val botPrice = BotPrices.getPrice(offer.itemID) - if (offer.offeredValue < botPrice) { - return - } - val amount = min(BOT_OFFERS[offer.itemID]!!, getBuylimitAmount(offer)) - val botOffer = GrandExchangeOffer() - botOffer.sell = true - botOffer.amount = amount - botOffer.offerState = OfferState.REGISTERED - botOffer.offeredValue = botPrice - exchange(offer, botOffer) - BOT_OFFERS[offer.itemID] = BOT_OFFERS[offer.itemID]!! - amount + fun addEntry(offer: GrandExchangeOffer){ + GE_OFFER_LOCK.lock() + OFFER_MAPPING[offer.uid] = offer + if (!OFFERS_BY_ITEMID.containsKey(offer.itemID)) { + OFFERS_BY_ITEMID[offer.itemID] = mutableListOf() } + OFFERS_BY_ITEMID[offer.itemID]!!.add(offer) + GE_OFFER_LOCK.unlock() + } - private fun buyFromBotsWithItem(itemID: Int) { - if (OFFERS_BY_ITEMID[itemID] == null || BOT_OFFERS[itemID] == null) { - return - } - for (trade in OFFERS_BY_ITEMID[itemID]!!) { - if (!trade.sell) { - buyFromBots(trade) - } + fun getQuantitySoldForItem(item: Int): Int { + var qty = 0 + val offs = getOffersForItem(item) + for (o in offs) { + if (o.sell) { + qty += o.amountLeft } } + qty += amtBotsSelling(item) + return qty + } - fun addBotOffer(itemID: Int, qty: Int): Boolean { - if (GrandExchangeDatabase.getDatabase()[itemID] == null) { - SystemLogger.logWarn("Bot attempted to sell invalid item $itemID") - return false - } + @JvmStatic + fun getOffersForItem(item: Int): MutableList { + if (OFFERS_BY_ITEMID.containsKey(item)) { + return OFFERS_BY_ITEMID[item]!! + } + return mutableListOf() + } - if (BOT_OFFERS[itemID] == null) { - BOT_OFFERS[itemID] = qty - } else { - BOT_OFFERS[itemID] = (qty + BOT_OFFERS[itemID]!!) - } - buyFromBotsWithItem(itemID) - return true + @JvmStatic + fun save(){ + GE_OFFER_LOCK.lock() + val root = JSONObject() + val offers = JSONArray() + + if(OFFER_MAPPING.isEmpty() && BOT_OFFERS.isEmpty()){ + return } - fun amtBotsSelling(itemID: Int): Int { - if (BOT_OFFERS[itemID] == null) { - return 0 + for(entry in OFFER_MAPPING){ + val offer = entry.value + if (offer.offerState == OfferState.REMOVED || entry.value.playerUID == PlayerDetails.getDetails("2009scape").uid) { + continue } - if (BOT_OFFERS[itemID]!! <= 0) { - return 0 + val o = JSONObject() + o["uid"] = entry.key.toString() + o["itemId"] = offer.itemID.toString() + o["sale"] = offer.sell + o["amount"] = offer.amount.toString() + o["completedAmount"] = offer.completedAmount.toString() + o["offeredValue"] = offer.offeredValue.toString() + o["timeStamp"] = offer.timeStamp.toString() + o["offerState"] = offer.offerState.ordinal.toString() + o["totalCoinExchange"] = offer.totalCoinExchange.toString() + o["playerUID"] = offer.playerUID.toString() + val withdrawItems = JSONArray() + for(item in offer.withdraw){ + item ?: continue + val it = JSONObject() + it["id"] = item.id.toString() + it["amount"] = item.amount.toString() + withdrawItems.add(it) } - return BOT_OFFERS[itemID]!! + o["withdrawItems"] = withdrawItems + offers.add(o) } + root["offsetUID"] = offsetUID.toString() + root["offers"] = offers - fun setIndex(offerID: Long, idx: Int) { - if (!OFFER_MAPPING.containsKey(offerID)) { - println("ERROR. GE Entry $offerID not found in database. Playerdata may be corrupted.") - return - } - OFFER_MAPPING[offerID]!!.index = idx + val manager = ScriptEngineManager() + val scriptEngine = manager.getEngineByName("JavaScript") + scriptEngine.put("jsonString", root.toJSONString()) + scriptEngine.eval("result = JSON.stringify(JSON.parse(jsonString), null, 2)") + val prettyPrintedJson = scriptEngine["result"] as String + + val botRoot = JSONObject() + val botOffers = JSONArray() + + for ((item, qty) in BOT_OFFERS) { + val o = JSONObject() + o["item"] = item + o["qty"] = qty + botOffers.add(o) } + botRoot["offers"] = botOffers - fun removeEntry(offer: GrandExchangeOffer): Boolean{ - println("REMOVING ENTRY of ID " + offer.itemID) - GE_OFFER_LOCK.lock() - if (!OFFER_MAPPING.containsKey(offer.uid)){ - GE_OFFER_LOCK.unlock() - return false + scriptEngine.put("jsonString", botRoot.toJSONString()) + scriptEngine.eval("result = JSON.stringify(JSON.parse(jsonString), null, 2)") + val botJson = scriptEngine["result"] as String + + + try { + FileWriter(DB_PATH).use { file -> + file.write(prettyPrintedJson) + file.flush() + file.close() } - OFFER_MAPPING.remove(offer.uid) - OFFERS_BY_ITEMID[offer.itemID]!!.remove(offer) - GE_OFFER_LOCK.unlock() - return true + FileWriter(BOT_DB_PATH).use { file -> + file.write(botJson) + file.flush() + file.close() + } + } catch (e: Exception) { + e.printStackTrace() } + GE_OFFER_LOCK.unlock() + } - fun addEntry(offer: GrandExchangeOffer){ - GE_OFFER_LOCK.lock() - OFFER_MAPPING[offer.uid] = offer - if (!OFFERS_BY_ITEMID.containsKey(offer.itemID)) { - OFFERS_BY_ITEMID[offer.itemID] = mutableListOf() - } - OFFERS_BY_ITEMID[offer.itemID]!!.add(offer) - GE_OFFER_LOCK.unlock() + /** + * Dispatches an offer. + * @param player The player. + * @param offer The grand exchange offer. + * @return `True` if successful. + */ + @JvmStatic + fun dispatch(player: Player, offer: GrandExchangeOffer): Boolean { + if (offer.amount < 1) { + player.packetDispatch.sendMessage("You must choose the quantity you wish to buy!") + println("amountthing") + return false } - - fun getQuantitySoldForItem(item: Int): Int { - var qty = 0 - val offs = getOffersForItem(item) - for (o in offs) { - if (o.sell) { - qty += o.amountLeft - } - } - qty += amtBotsSelling(item) - return qty + if (offer.offeredValue < 1) { + player.packetDispatch.sendMessage("You must choose the price you wish to buy for!") + println("pricethng") + return false } - - fun getOffersForItem(item: Int): MutableList { - if (OFFERS_BY_ITEMID.containsKey(item)) { - return OFFERS_BY_ITEMID[item]!! - } - return mutableListOf() + if (offer.offerState != OfferState.PENDING || offer.uid != 0L) { + println("pendingthing") + return false } - - @JvmStatic - fun save(){ - GE_OFFER_LOCK.lock() - val root = JSONObject() - val offers = JSONArray() - - if(OFFER_MAPPING.isEmpty() && BOT_OFFERS.isEmpty()){ - return - } - - for(entry in OFFER_MAPPING){ - val offer = entry.value - if (offer.offerState == OfferState.REMOVED || entry.value.playerUID == PlayerDetails.getDetails("2009scape").uid) { - continue - } - val o = JSONObject() - o["uid"] = entry.key.toString() - o["itemId"] = offer.itemID.toString() - o["sale"] = offer.sell - o["amount"] = offer.amount.toString() - o["completedAmount"] = offer.completedAmount.toString() - o["offeredValue"] = offer.offeredValue.toString() - o["timeStamp"] = offer.timeStamp.toString() - o["offerState"] = offer.offerState.ordinal.toString() - o["totalCoinExchange"] = offer.totalCoinExchange.toString() - o["playerUID"] = offer.playerUID.toString() - val withdrawItems = JSONArray() - for(item in offer.withdraw){ - item ?: continue - val it = JSONObject() - it["id"] = item.id.toString() - it["amount"] = item.amount.toString() - withdrawItems.add(it) - } - o["withdrawItems"] = withdrawItems - offers.add(o) - } - root["offsetUID"] = offsetUID.toString() - root["offers"] = offers - - val manager = ScriptEngineManager() - val scriptEngine = manager.getEngineByName("JavaScript") - scriptEngine.put("jsonString", root.toJSONString()) - scriptEngine.eval("result = JSON.stringify(JSON.parse(jsonString), null, 2)") - val prettyPrintedJson = scriptEngine["result"] as String - - val botRoot = JSONObject() - val botOffers = JSONArray() - - for ((item, qty) in BOT_OFFERS) { - val o = JSONObject() - o["item"] = item - o["qty"] = qty - botOffers.add(o) - } - botRoot["offers"] = botOffers - - scriptEngine.put("jsonString", botRoot.toJSONString()) - scriptEngine.eval("result = JSON.stringify(JSON.parse(jsonString), null, 2)") - val botJson = scriptEngine["result"] as String - - - try { - FileWriter(DB_PATH).use { file -> - file.write(prettyPrintedJson) - file.flush() - file.close() - } - FileWriter(BOT_DB_PATH).use { file -> - file.write(botJson) - file.flush() - file.close() - } - } catch (e: Exception) { - e.printStackTrace() - } - GE_OFFER_LOCK.unlock() + if (player.isArtificial) { + offer.playerUID = PlayerDetails.getDetails("2009scape").uid + // Repository.sendNews("2009scape wants " + offer.amount + " " + ItemDefinition.forId(offer.itemID).name.toLowerCase() + " for " + offer.offeredValue + "each.") + } else { + offer.playerUID = player.details.uid } - - /** - * Dispatches an offer. - * @param player The player. - * @param offer The grand exchange offer. - * @return `True` if successful. - */ - @JvmStatic - fun dispatch(player: Player, offer: GrandExchangeOffer): Boolean { - if (offer.amount < 1) { - player.packetDispatch.sendMessage("You must choose the quantity you wish to buy!") - println("amountthing") - return false - } - if (offer.offeredValue < 1) { - player.packetDispatch.sendMessage("You must choose the price you wish to buy for!") - println("pricethng") - return false - } - if (offer.offerState != OfferState.PENDING || offer.uid != 0L) { - println("pendingthing") - return false - } - if (player.isArtificial) { - offer.playerUID = PlayerDetails.getDetails("2009scape").uid - // Repository.sendNews("2009scape wants " + offer.amount + " " + ItemDefinition.forId(offer.itemID).name.toLowerCase() + " for " + offer.offeredValue + "each.") - } else { - offer.playerUID = player.details.uid - } - offer.uid = nextUID() - offer.offerState = OfferState.REGISTERED - addEntry(offer) - offer.timeStamp = System.currentTimeMillis() - player.playerGrandExchange.update(offer) - if (offer.sell) { - Repository.sendNews(player.username + " just offered " + offer.amount + " " + ItemDefinition.forId(offer.itemID).name.toLowerCase() + " on the GE.") - } - if(player !is AIPlayer) { - SystemLogger.logTrade("[GE] ${player.username} ${if (offer.sell) "listed for sale" else "listed buy offer for"} ${offer.amount} ${ItemDefinition.forId(offer.itemID).name.toLowerCase()}") - } - dumpDatabase = true - return true + offer.uid = nextUID() + offer.offerState = OfferState.REGISTERED + addEntry(offer) + offer.timeStamp = System.currentTimeMillis() + player.playerGrandExchange.update(offer) + if (offer.sell) { + Repository.sendNews(player.username + " just offered " + offer.amount + " " + ItemDefinition.forId(offer.itemID).name.toLowerCase() + " on the GE.") } - - /** - * Updates the offer. - * @param offer The G.E. offer to update. - */ - @JvmStatic - fun updateOffer(offer: GrandExchangeOffer) { - if (!offer.isActive) { - return - } - GE_OFFER_LOCK.lock() - for (o in OFFERS_BY_ITEMID[offer.itemID]!!) { - if (o.sell != offer.sell && o.isActive) { - exchange(offer, o) - if (offer.offerState == OfferState.COMPLETED) { - break - } - } - } - buyFromBots(offer) - GE_OFFER_LOCK.unlock() + if(player !is AIPlayer) { + SystemLogger.logTrade("[GE] ${player.username} ${if (offer.sell) "listed for sale" else "listed buy offer for"} ${offer.amount} ${ItemDefinition.forId(offer.itemID).name.toLowerCase()}") } + dumpDatabase = true + return true + } - private fun getBuylimitAmount(offer: GrandExchangeOffer): Int { - var left = offer.amountLeft - if (!offer.sell && left > 0) { - val maximum = BuyingLimitation.getMaximumBuy(offer.itemID, offer.playerUID) - if (left >= maximum) { - left = maximum - offer.isLimitation = true - } - } - return left + /** + * Updates the offer. + * @param offer The G.E. offer to update. + */ + @JvmStatic + fun updateOffer(offer: GrandExchangeOffer) { + if (!offer.isActive) { + return } - - /** - * Exchanges between 2 offers. - * @param offer The grand exchange offer to update. - * @param o The other offer to exchange with. - */ - private fun exchange(offer: GrandExchangeOffer, o: GrandExchangeOffer) { - if (o.sell == offer.sell) { - return - } - if (offer.sell && o.offeredValue < offer.offeredValue || !offer.sell && o.offeredValue > offer.offeredValue) { - return - } - var amount = min(getBuylimitAmount(offer), getBuylimitAmount(o)) - if (amount < 1) { - return - } - var coinDifference = if (offer.sell) o.offeredValue - offer.offeredValue else offer.offeredValue - o.offeredValue - if (coinDifference < 0) { - return - } - if (EconomyManagement.getEcoState() == EcoStatus.DRAINING) { - coinDifference *= (1.0 - EconomyManagement.getModificationRate()).toInt() - } - offer.completedAmount = offer.completedAmount + amount - o.completedAmount = o.completedAmount + amount - offer.offerState = if (offer.amountLeft < 1) OfferState.COMPLETED else OfferState.UPDATED - o.offerState = if (o.amountLeft < 1) OfferState.COMPLETED else OfferState.UPDATED - if (offer.sell) { - if (offer.amountLeft < 1 && offer.player != null) { - offer.player!!.audioManager.send(Audio(4042, 1, 1)) - } - addWithdraw(offer,995, amount * offer.offeredValue) - addWithdraw(o, o.itemID, amount) - BuyingLimitation.updateBoughtAmount(o.itemID, o.playerUID, amount) - } else { - if (o.amountLeft < 1 && o.player != null) { - o.player!!.audioManager.send(Audio(4042, 1, 1)) - } - addWithdraw(offer, offer.itemID, amount) - addWithdraw(o, 995, amount * o.offeredValue) - BuyingLimitation.updateBoughtAmount(offer.itemID, offer.playerUID, amount) - } - if (coinDifference > 0) { - if (offer.sell) { - addWithdraw(o, 995, coinDifference * amount) - } else { - addWithdraw(offer, 995, coinDifference * amount) - } - } - GrandExchangeDatabase.getDatabase()[o.itemID]?.influenceValue(o.offeredValue) - offer.player?.packetDispatch?.sendMessage(UPDATE_NOTIFICATION) - o.player?.packetDispatch?.sendMessage(UPDATE_NOTIFICATION) - o.player?.playerGrandExchange?.update(o) - offer.player?.playerGrandExchange?.update(offer) - dumpDatabase = true - } - - /** - * Adds a new item to withdraw. - * @param itemId The item id. - * @param amount The amount to add. - * @param abort If the item is added due to abort. - */ - fun addWithdraw(offer: GrandExchangeOffer, itemId: Int, amount: Int, abort: Boolean = false) { - if (!abort) { - if (offer.sell) { - if (itemId == 995) { - offer.totalCoinExchange += amount - } - } else { - if (itemId == 995) { - offer.totalCoinExchange -= amount - } else { - offer.totalCoinExchange += offer.offeredValue * amount - } - } - } - for (i in offer.withdraw.indices) { - if (offer.withdraw[i] == null) { - offer.withdraw[i] = Item(itemId, amount) - break - } - if (offer.withdraw[i]!!.id == itemId) { - offer.withdraw[i]!!.amount = offer.withdraw[i]!!.amount + amount + GE_OFFER_LOCK.lock() + for (o in OFFERS_BY_ITEMID[offer.itemID]!!) { + if (o.sell != offer.sell && o.isActive) { + exchange(offer, o) + if (offer.offerState == OfferState.COMPLETED) { break } } - if (offer.player != null) { - PacketRepository.send( - ContainerPacket::class.java, - ContainerContext(offer.player, -1, -1757, 523 + offer.index, offer.withdraw, false) - ) + } + buyFromBots(offer) + GE_OFFER_LOCK.unlock() + } + + private fun getBuylimitAmount(offer: GrandExchangeOffer): Int { + var left = offer.amountLeft + if (!offer.sell && left > 0) { + val maximum = BuyingLimitation.getMaximumBuy(offer.itemID, offer.playerUID) + if (left >= maximum) { + left = maximum + offer.isLimitation = true } } + return left + } - /** - * Gets the next UID. - * @return The UID. - */ - private fun nextUID(): Long { - val id = offsetUID++ - return if (id == 0L) { - nextUID() - } else id + /** + * Exchanges between 2 offers. + * @param offer The grand exchange offer to update. + * @param o The other offer to exchange with. + */ + private fun exchange(offer: GrandExchangeOffer, o: GrandExchangeOffer) { + if (o.sell == offer.sell) { + return + } + if (offer.sell && o.offeredValue < offer.offeredValue || !offer.sell && o.offeredValue > offer.offeredValue) { + return + } + var amount = min(getBuylimitAmount(offer), getBuylimitAmount(o)) + if (amount < 1) { + return + } + var coinDifference = if (offer.sell) o.offeredValue - offer.offeredValue else offer.offeredValue - o.offeredValue + if (coinDifference < 0) { + return + } + if (EconomyManagement.getEcoState() == EcoStatus.DRAINING) { + coinDifference *= (1.0 - EconomyManagement.getModificationRate()).toInt() + } + offer.completedAmount = offer.completedAmount + amount + o.completedAmount = o.completedAmount + amount + offer.offerState = if (offer.amountLeft < 1) OfferState.COMPLETED else OfferState.UPDATED + o.offerState = if (o.amountLeft < 1) OfferState.COMPLETED else OfferState.UPDATED + if (offer.sell) { + if (offer.amountLeft < 1 && offer.player != null) { + offer.player!!.audioManager.send(Audio(4042, 1, 1)) + } + addWithdraw(offer,995, amount * offer.offeredValue) + addWithdraw(o, o.itemID, amount) + BuyingLimitation.updateBoughtAmount(o.itemID, o.playerUID, amount) + } else { + if (o.amountLeft < 1 && o.player != null) { + o.player!!.audioManager.send(Audio(4042, 1, 1)) + } + addWithdraw(offer, offer.itemID, amount) + addWithdraw(o, 995, amount * o.offeredValue) + BuyingLimitation.updateBoughtAmount(offer.itemID, offer.playerUID, amount) + } + if (coinDifference > 0) { + if (offer.sell) { + addWithdraw(o, 995, coinDifference * amount) + } else { + addWithdraw(offer, 995, coinDifference * amount) + } + } + GrandExchangeDatabase.getDatabase()[o.itemID]?.influenceValue(o.offeredValue) + offer.player?.packetDispatch?.sendMessage(UPDATE_NOTIFICATION) + o.player?.packetDispatch?.sendMessage(UPDATE_NOTIFICATION) + o.player?.playerGrandExchange?.update(o) + offer.player?.playerGrandExchange?.update(offer) + dumpDatabase = true + } + + /** + * Adds a new item to withdraw. + * @param itemId The item id. + * @param amount The amount to add. + * @param abort If the item is added due to abort. + */ + fun addWithdraw(offer: GrandExchangeOffer, itemId: Int, amount: Int, abort: Boolean = false) { + if (!abort) { + if (offer.sell) { + if (itemId == 995) { + offer.totalCoinExchange += amount + } + } else { + if (itemId == 995) { + offer.totalCoinExchange -= amount + } else { + offer.totalCoinExchange += offer.offeredValue * amount + } + } + } + for (i in offer.withdraw.indices) { + if (offer.withdraw[i] == null) { + offer.withdraw[i] = Item(itemId, amount) + break + } + if (offer.withdraw[i]!!.id == itemId) { + offer.withdraw[i]!!.amount = offer.withdraw[i]!!.amount + amount + break + } + } + if (offer.player != null) { + PacketRepository.send( + ContainerPacket::class.java, + ContainerContext(offer.player, -1, -1757, 523 + offer.index, offer.withdraw, false) + ) } } + /** + * Gets the next UID. + * @return The UID. + */ + private fun nextUID(): Long { + val id = offsetUID++ + return if (id == 0L) { + nextUID() + } else id + } + private fun getItemDefPrice(itemID: Int): Int { + return max(ContentAPI.itemDefinition(itemID).getConfiguration(ItemConfigParser.GE_PRICE) ?: 0, ContentAPI.itemDefinition(itemID).value) + } + + @JvmStatic + fun getRecommendedPrice(itemID: Int, from_bot: Boolean = false): Int { + val base = max(GrandExchangeDatabase.getDatabase()[itemID]?.value ?: 0, getItemDefPrice(itemID)) + return if(from_bot) (base + (base * 0.1).toInt()) + else base + } } diff --git a/Server/src/main/kotlin/rs09/game/ge/PlayerGrandExchange.kt b/Server/src/main/kotlin/rs09/game/ge/PlayerGrandExchange.kt index ad1021b89..867cf6cdc 100644 --- a/Server/src/main/kotlin/rs09/game/ge/PlayerGrandExchange.kt +++ b/Server/src/main/kotlin/rs09/game/ge/PlayerGrandExchange.kt @@ -1,5 +1,6 @@ package rs09.game.ge +import api.ContentAPI import core.cache.def.impl.ItemDefinition import core.game.component.CloseEvent import core.game.component.Component @@ -27,8 +28,6 @@ import core.net.packet.out.GrandExchangePacket import org.json.simple.JSONArray import org.json.simple.JSONObject import org.rs09.consts.Components -import rs09.game.ge.OfferManager.Companion.dispatch -import rs09.game.ge.OfferManager.Companion.updateOffer import rs09.game.system.SystemLogger import java.text.DecimalFormat import java.text.NumberFormat @@ -319,13 +318,13 @@ class PlayerGrandExchange(private val player: Player) { temporaryOffer!!.itemID = itemId temporaryOffer!!.sell = false var itemDb = GrandExchangeDatabase.getDatabase()[itemId] - if (itemDb == null) { + if (itemDb == null || !ContentAPI.itemDefinition(itemId).isTradeable) { player.packetDispatch.sendMessage("This item has been blacklisted from the Grand Exchange.") return } temporaryOffer!!.player = player temporaryOffer!!.amount = 1 - temporaryOffer!!.offeredValue = itemDb.value + temporaryOffer!!.offeredValue = OfferManager.getRecommendedPrice(itemId) temporaryOffer!!.index = openedIndex sendConfiguration(temporaryOffer, false) } @@ -350,7 +349,7 @@ class PlayerGrandExchange(private val player: Player) { id = item.noteChange } var itemDb = GrandExchangeDatabase.getDatabase()[id] - if (itemDb == null) { + if (itemDb == null || !item.definition.isTradeable) { player.packetDispatch.sendMessage("This item can't be sold on the Grand Exchange.") return } @@ -358,7 +357,7 @@ class PlayerGrandExchange(private val player: Player) { temporaryOffer!!.itemID = id temporaryOffer!!.sell = true temporaryOffer!!.player = player - temporaryOffer!!.offeredValue = itemDb.value + temporaryOffer!!.offeredValue = OfferManager.getRecommendedPrice(id) temporaryOffer!!.amount = item.amount temporaryOffer!!.index = openedIndex sendConfiguration(temporaryOffer, true) @@ -425,9 +424,9 @@ class PlayerGrandExchange(private val player: Player) { return } } - if (dispatch(player, temporaryOffer!!)) { + if (OfferManager.dispatch(player, temporaryOffer!!)) { offers[openedIndex] = temporaryOffer - updateOffer(temporaryOffer!!) + OfferManager.updateOffer(temporaryOffer!!) } } else { val total: Int = temporaryOffer!!.amount * temporaryOffer!!.offeredValue @@ -436,9 +435,9 @@ class PlayerGrandExchange(private val player: Player) { player.packetDispatch.sendMessage("You do not have enough coins to cover the offer.") return } - if (dispatch(player, temporaryOffer!!) && player.inventory.remove(Item(995, total))) { + if (OfferManager.dispatch(player, temporaryOffer!!) && player.inventory.remove(Item(995, total))) { offers[openedIndex] = temporaryOffer - updateOffer(temporaryOffer!!) + OfferManager.updateOffer(temporaryOffer!!) } } player.monitor.log( @@ -546,7 +545,7 @@ class PlayerGrandExchange(private val player: Player) { val botSales = OfferManager.amtBotsSelling(offer.itemID) if (botSales > 0) { foundAmounts.add(botSales) - foundOffers.add(BotPrices.getPrice(offer.itemID)) + foundOffers.add(OfferManager.getRecommendedPrice(offer.itemID, true)) count++ } if (foundOffers.isNotEmpty()) { @@ -564,16 +563,17 @@ class PlayerGrandExchange(private val player: Player) { player.packetDispatch.sendString(if (offer != null && !offer.sell) text.toString() else examine, 105, 142) var lowPrice = 0 var highPrice = 0 + val recommendedPrice = OfferManager.getRecommendedPrice(entry?.itemId ?: 0) if (entry != null) { - lowPrice = (entry.value * 0.95).toInt() - highPrice = (entry.value * 1.05).toInt() + lowPrice = (recommendedPrice * 0.95).toInt() + highPrice = (recommendedPrice * 1.05).toInt() } player.varpManager.get(1109).setVarbit(0, offer?.itemID ?: -1).send(player) player.varpManager.get(1110).setVarbit(0, offer?.amount ?: 0).send(player) player.varpManager.get(1111).setVarbit(0, offer?.offeredValue ?: 0).send(player) player.varpManager.get(1112).setVarbit(0, openedIndex).send(player) player.varpManager.get(1113).setVarbit(0, if (sell) 1 else 0).send(player) - player.varpManager.get(1114).setVarbit(0, entry?.value ?: 0).send(player) + player.varpManager.get(1114).setVarbit(0, recommendedPrice).send(player) player.varpManager.get(1115).setVarbit(0, lowPrice).send(player) player.varpManager.get(1116).setVarbit(0, highPrice).send(player) if (offer != null) { diff --git a/Server/src/main/kotlin/rs09/game/system/SystemLogger.kt b/Server/src/main/kotlin/rs09/game/system/SystemLogger.kt index 83d5e372e..61755329d 100644 --- a/Server/src/main/kotlin/rs09/game/system/SystemLogger.kt +++ b/Server/src/main/kotlin/rs09/game/system/SystemLogger.kt @@ -69,6 +69,11 @@ object SystemLogger { if(message.isNotBlank()) t.println("${getTime()}: ${TextColors.gray("[RAND] $message")}") } + @JvmStatic + fun logGE(message: String){ + if(message.isNotBlank()) t.println("${getTime()}: ${TextColors.gray("[ GE] $message")}") + } + @JvmStatic fun logTrade(message: String){ if(message.isNotBlank()){ diff --git a/Server/src/main/kotlin/rs09/game/world/callback/CallbackHub.kt b/Server/src/main/kotlin/rs09/game/world/callback/CallbackHub.kt index cf26afe74..c04369b71 100644 --- a/Server/src/main/kotlin/rs09/game/world/callback/CallbackHub.kt +++ b/Server/src/main/kotlin/rs09/game/world/callback/CallbackHub.kt @@ -3,6 +3,7 @@ package rs09.game.world.callback import core.game.node.entity.skill.hunter.ImpetuousImpulses import core.game.world.callback.CallBack import core.game.world.map.zone.ZoneBuilder +import rs09.game.ge.GrandExchange import rs09.game.ge.OfferManager import rs09.game.system.SystemLogger import java.util.* @@ -16,7 +17,7 @@ object CallbackHub { fun call(): Boolean { calls.add(ZoneBuilder()) - calls.add(OfferManager()) + calls.add(GrandExchange) calls.add(ImpetuousImpulses()) for (call in calls) { if (!call.call()) {