diff --git a/Server/src/main/java/core/cache/def/impl/ItemDefinition.java b/Server/src/main/java/core/cache/def/impl/ItemDefinition.java index 6c5e82a9c..02d76c0b4 100644 --- a/Server/src/main/java/core/cache/def/impl/ItemDefinition.java +++ b/Server/src/main/java/core/cache/def/impl/ItemDefinition.java @@ -5,7 +5,6 @@ import core.cache.def.Definition; import core.cache.misc.buffer.ByteBufferUtils; import core.game.container.Container; import core.game.content.global.action.DropItemHandler; -import core.game.ge.GrandExchangeDatabase; import core.game.interaction.OptionHandler; import core.game.node.Node; import core.game.node.entity.player.Player; @@ -1416,7 +1415,7 @@ public class ItemDefinition extends Definition { if (!getConfiguration(ItemConfigParser.TRADEABLE, false)) { return false; } - return !unnoted || GrandExchangeDatabase.getDatabase().get(getId()) != null; + return true; } /** diff --git a/Server/src/main/java/core/game/content/dialogue/GrandExchangeClerk.java b/Server/src/main/java/core/game/content/dialogue/GrandExchangeClerk.java index e0e11028f..63f88c7d0 100644 --- a/Server/src/main/java/core/game/content/dialogue/GrandExchangeClerk.java +++ b/Server/src/main/java/core/game/content/dialogue/GrandExchangeClerk.java @@ -1,6 +1,5 @@ package core.game.content.dialogue; -import core.game.ge.GrandExchangeDatabase; import core.game.node.entity.npc.NPC; import core.plugin.Initializable; import core.game.node.entity.player.Player; @@ -51,11 +50,6 @@ public final class GrandExchangeClerk extends DialoguePlugin { @Override public boolean handle(int interfaceId, int buttonId) { - if (stage < 50 && stage > 5 && !GrandExchangeDatabase.hasInitialized()) { - npc("The Grand Exchange is currently unavailable,", "it will be back soon!"); - stage = 51; - return true; - } switch (stage) { case 0: npc("Good day to you, sir, How can I help?"); diff --git a/Server/src/main/java/core/game/ge/GrandExchangeDatabase.java b/Server/src/main/java/core/game/ge/GrandExchangeDatabase.java deleted file mode 100644 index d3b426f15..000000000 --- a/Server/src/main/java/core/game/ge/GrandExchangeDatabase.java +++ /dev/null @@ -1,269 +0,0 @@ -package core.game.ge; - -import api.ShutdownListener; -import api.StartupListener; -import rs09.ServerConstants; -import core.cache.def.impl.ItemDefinition; -import rs09.game.system.SystemLogger; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -import static rs09.game.system.SystemLogger.logShutdown; -import static rs09.game.system.SystemLogger.logStartup; - -/** - * Represents the grand exchange database. - * @author Ceikry - */ -public final class GrandExchangeDatabase implements StartupListener, ShutdownListener { - - /** - * The grand exchange database mapping. - */ - private static final Map DATABASE = new HashMap<>(); - - /** - * The minimum amount of unique trades required for an entry to change its - * value. - */ - private static final int MINIMUM_TRADES = 10;// 200 - - /** - * The amount of hours between each update cycle. - */ - private static final int UPDATE_CYCLE_HOURS = 3; - - /** - * The next update. - */ - private static long nextUpdate; - - /** - * If the G.E database has initialized. - */ - private static boolean initialized; - - @Override - public void startup() { - logStartup("Parsing Grand Exchange Price Index DB"); - try { - File db = new File(ServerConstants.GRAND_EXCHANGE_DATA_PATH + "gedb.xml"); - if(!db.exists()){ - initNewDB(); - return; - } - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); - Document doc = builder.parse(db); - - doc.getDocumentElement().normalize(); - NodeList offers = doc.getElementsByTagName("offer"); - for(int i = 0; i < offers.getLength(); i++){ - Node offer = offers.item(i); - if(offer.getNodeType() == Node.ELEMENT_NODE){ - Element offerElement = (Element) offer; - int itemId = Integer.parseInt(offerElement.getElementsByTagName("id").item(0).getTextContent()); - int value = Integer.parseInt(offerElement.getElementsByTagName("value").item(0).getTextContent()); - if(value < 1){ - value = 1; - } - int uniqueTrades = Integer.parseInt(offerElement.getElementsByTagName("uniqueTrades").item(0).getTextContent()); - long totalValue = Integer.parseInt(offerElement.getElementsByTagName("totalValue").item(0).getTextContent()); - long lastUpdate = Integer.parseInt(offerElement.getElementsByTagName("lastUpdate").item(0).getTextContent()); - - - GrandExchangeEntry e = new GrandExchangeEntry(itemId); - e.setValue(value); - e.setUniqueTrades(uniqueTrades); - e.setTotalValue(totalValue); - e.setLastUpdate(lastUpdate); - NodeList valueLog = offerElement.getElementsByTagName("valueLog"); - e.setLogLength(valueLog.getLength()); - for(int logEntry = 0; logEntry < valueLog.getLength(); logEntry++){ - int val = Integer.parseInt(valueLog.item(logEntry).getTextContent()); - e.getValueLog()[logEntry] = val; - } - DATABASE.put(itemId,e); - } - } - initialized = true; - } catch (Exception e){ - e.printStackTrace(); - initNewDB(); - } - } - - public static void initNewDB(){ - SystemLogger.logWarn("Initializing new Grand Exchange DB! This may take a moment..."); - ItemDefinition.getDefinitions().values().forEach(def -> { - if(def.getId() != 2572){ - GrandExchangeEntry e = new GrandExchangeEntry(def.getId()); - e.setValue(def.getValue()); - e.setLogLength(0); - DATABASE.put(def.getId(), e); - } - }); - initialized = true; - } - - - /** - * Updates the entry values, if needed. - */ - public static void checkUpdate() { - if (nextUpdate < System.currentTimeMillis()) { - updateValues(); - } - } - - @Override - public void shutdown() { - logShutdown("Saving Grand Exchange Price Index DB"); - save(); - } - - /** - * Dumps the grand exchange database. - */ - public static void save() { - File f = new File(ServerConstants.GRAND_EXCHANGE_DATA_PATH + "gedb.xml"); - if(f.exists()){ - f.delete(); - } - try{ - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = dbFactory.newDocumentBuilder(); - Document doc = builder.newDocument(); - - Element root = doc.createElement("database"); - doc.appendChild(root); - - for(GrandExchangeEntry e : DATABASE.values()) { - Element offer = doc.createElement("offer"); - root.appendChild(offer); - - Element itemId = doc.createElement("id"); - itemId.setTextContent("" + e.getItemId()); - offer.appendChild(itemId); - - Element value = doc.createElement("value"); - value.setTextContent("" + e.getValue()); - offer.appendChild(value); - - Element uniqueTrades = doc.createElement("uniqueTrades"); - uniqueTrades.setTextContent("" + e.getUniqueTrades()); - offer.appendChild(uniqueTrades); - - Element totalValue = doc.createElement("totalValue"); - totalValue.setTextContent("" + e.getTotalValue()); - offer.appendChild(totalValue); - - Element lastUpdate = doc.createElement("lastUpdate"); - lastUpdate.setTextContent("" + e.getLastUpdate()); - offer.appendChild(lastUpdate); - - int[] vLog = e.getValueLog(); - for (int item : vLog) { - if (item == 0) { - continue; - } - Element valueLog = doc.createElement("valueLog"); - valueLog.setTextContent("" + item); - offer.appendChild(valueLog); - } - } - - TransformerFactory factory = TransformerFactory.newInstance(); - Transformer transformer = factory.newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); - DOMSource source = new DOMSource(doc); - StreamResult result = new StreamResult(f); - transformer.transform(source,result); - } catch (Exception e){ - e.printStackTrace(); - } - } - - /** - * Updates the item values. - */ - public static void updateValues() { - try { - for (GrandExchangeEntry entry : DATABASE.values()) { - if (entry.getUniqueTrades() < MINIMUM_TRADES || entry.getTotalValue() == 0) { - continue; - } - double newAverage = entry.getTotalValue() / entry.getUniqueTrades(); - double changePercentage = newAverage / (double) (entry.getValue() + .001); - if (changePercentage == 1.0) { - continue; - } else if (changePercentage > 1.15) { - changePercentage = 1.15; - } else if (changePercentage < 0.85) { - changePercentage = 0.85; - } - int newValue = (int) (entry.getValue() * changePercentage); - if (newValue == entry.getValue()) { - if (changePercentage > 1.0) { // Fixes 1gp not being - // influenced. - newValue++; - } else if (newValue > 0) { - newValue--; - } - } - entry.updateValue(newValue); - entry.setLastUpdate(nextUpdate); - } - } catch (Throwable t) { - t.printStackTrace(); - } - nextUpdate = System.currentTimeMillis() + (UPDATE_CYCLE_HOURS * (60 * 60 * 1000)); - } - - /** - * Gets the database. - * @return The database. - */ - public static Map getDatabase() { - return DATABASE; - } - - /** - * Gets the nextUpdate. - * @return The nextUpdate. - */ - public static long getNextUpdate() { - return nextUpdate; - } - - /** - * Sets the nextUpdate. - * @param nextUpdate The nextUpdate to set. - */ - public static void setNextUpdate(long nextUpdate) { - GrandExchangeDatabase.nextUpdate = nextUpdate; - } - - /** - * Checks if the grand exchange database has initialized. - * @return {@code True} if so. - */ - public static boolean hasInitialized() { - return initialized; - } - -} \ No newline at end of file diff --git a/Server/src/main/java/core/game/interaction/object/GrandExchangePlugin.java b/Server/src/main/java/core/game/interaction/object/GrandExchangePlugin.java index 930b964c4..3783e66df 100644 --- a/Server/src/main/java/core/game/interaction/object/GrandExchangePlugin.java +++ b/Server/src/main/java/core/game/interaction/object/GrandExchangePlugin.java @@ -4,7 +4,6 @@ import core.cache.def.impl.NPCDefinition; import core.cache.def.impl.SceneryDefinition; import core.plugin.Initializable; import core.game.ge.GEGuidePrice; -import core.game.ge.GrandExchangeDatabase; import core.game.ge.GEGuidePrice.GuideType; import core.game.interaction.OptionHandler; import core.game.node.Node; @@ -56,20 +55,12 @@ public final class GrandExchangePlugin extends OptionHandler { player.getDialogueInterpreter().open(6528, NPC.create(6528, player.getLocation())); break; case "exchange": - if (!GrandExchangeDatabase.hasInitialized()) { - player.getDialogueInterpreter().sendDialogue("The Grand Exchange desk seems to be closed..."); - break; - } StockMarket.openFor(player); break; case "history": records.openHistoryLog(player); break; case "collect": - if (!GrandExchangeDatabase.hasInitialized()) { - player.getDialogueInterpreter().sendDialogue("The Grand Exchange desk seems to be closed..."); - break; - } records.openCollectionBox(); break; case "info-logs": 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 2746eb667..42c1d8495 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 @@ -3,7 +3,6 @@ package core.game.node.entity.npc.drop; import static api.ContentAPIKt.*; import core.cache.def.impl.NPCDefinition; import core.game.content.global.Bones; -import core.game.ge.GrandExchangeDatabase; import core.game.node.entity.Entity; import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; @@ -18,10 +17,8 @@ import core.tools.StringUtils; import rs09.game.ai.AIPlayer; import rs09.game.ai.AIRepository; import rs09.game.ai.general.GeneralBotCreator; -import rs09.game.content.global.GlobalKillCounter; import rs09.game.content.global.NPCDropTable; import rs09.game.ge.GrandExchange; -import rs09.game.system.config.ItemConfigParser; import rs09.game.world.repository.Repository; import java.util.ArrayList; @@ -144,7 +141,7 @@ public final class NPCDropTables { */ public Player getLooter(Player player, NPC npc, Item item) { int itemId = item.getDefinition().isUnnoted() ? item.getId() : item.getNoteChange(); - if (player != null && npc.getProperties().isMultiZone() && (GrandExchangeDatabase.getDatabase().get(itemId) != null || item.getName().endsWith("charm")) && player.getCommunication().getClan() != null && player.getCommunication().isLootShare() && player.getCommunication().getLootRequirement().ordinal() >= player.getCommunication().getClan().getLootRequirement().ordinal() && !player.getIronmanManager().isIronman()) { + if (player != null && npc.getProperties().isMultiZone() && (item.getDefinition().isTradeable() || item.getName().endsWith("charm")) && player.getCommunication().getClan() != null && player.getCommunication().isLootShare() && player.getCommunication().getLootRequirement().ordinal() >= player.getCommunication().getClan().getLootRequirement().ordinal() && !player.getIronmanManager().isIronman()) { Player looter = player; List players = RegionManager.getLocalPlayers(npc, 16); List looters = new ArrayList<>(20); 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 b1f0be018..9f0a034cc 100644 --- a/Server/src/main/java/core/game/node/item/Item.java +++ b/Server/src/main/java/core/game/node/item/Item.java @@ -1,8 +1,6 @@ package core.game.node.item; import core.cache.def.impl.ItemDefinition; -import core.game.ge.GrandExchangeDatabase; -import core.game.ge.GrandExchangeEntry; import core.game.interaction.DestinationFlag; import core.game.interaction.Interaction; import core.game.interaction.OptionHandler; diff --git a/Server/src/main/java/core/game/system/SystemTermination.java b/Server/src/main/java/core/game/system/SystemTermination.java index 73a38f450..c21f9bcfa 100644 --- a/Server/src/main/java/core/game/system/SystemTermination.java +++ b/Server/src/main/java/core/game/system/SystemTermination.java @@ -2,14 +2,9 @@ package core.game.system; import api.PersistWorld; import api.ShutdownListener; -import core.game.ge.GrandExchangeDatabase; -import core.game.interaction.object.dmc.DMCHandler; import core.game.node.entity.player.Player; -import core.game.node.entity.player.info.login.PlayerParser; import rs09.Server; import rs09.ServerConstants; -import rs09.ServerStore; -import rs09.game.content.global.GlobalKillCounter; import rs09.game.system.SystemLogger; import rs09.game.world.GameWorld; import rs09.game.world.repository.Repository; diff --git a/Server/src/main/java/core/net/packet/in/GrandExchangePacket.java b/Server/src/main/java/core/net/packet/in/GrandExchangePacket.java index 94736d723..efc7d67e7 100644 --- a/Server/src/main/java/core/net/packet/in/GrandExchangePacket.java +++ b/Server/src/main/java/core/net/packet/in/GrandExchangePacket.java @@ -1,13 +1,12 @@ package core.net.packet.in; import api.ContentAPIKt; -import core.game.ge.GrandExchangeDatabase; -import core.game.ge.GrandExchangeEntry; import core.game.node.entity.player.Player; import core.net.packet.IncomingPacket; import core.net.packet.IoBuffer; import rs09.game.ge.GrandExchange; import rs09.game.ge.GrandExchangeOffer; +import rs09.game.ge.PriceIndex; import rs09.game.interaction.inter.ge.StockMarket; /** @@ -23,8 +22,7 @@ public class GrandExchangePacket implements IncomingPacket { int index = player.getAttribute("ge-index", -1); offer.setItemID(itemId); offer.setSell(false); - GrandExchangeEntry entry = GrandExchangeDatabase.getDatabase().get(itemId); - if(entry == null || !ContentAPIKt.itemDefinition(itemId).isTradeable()) + if(!PriceIndex.canTrade(offer.getItemID())) { ContentAPIKt.sendMessage(player, "That item is blacklisted from the grand exchange."); return; diff --git a/Server/src/main/kotlin/rs09/ServerConstants.kt b/Server/src/main/kotlin/rs09/ServerConstants.kt index 16fc727f9..a53abe50f 100644 --- a/Server/src/main/kotlin/rs09/ServerConstants.kt +++ b/Server/src/main/kotlin/rs09/ServerConstants.kt @@ -13,6 +13,9 @@ import java.math.BigInteger class ServerConstants { companion object { @JvmField + var BOTS_INFLUENCE_PRICE_INDEX = true + + @JvmField var SHUTDOWN_HOOK: Thread = Thread(SystemShutdownHook()) @JvmField diff --git a/Server/src/main/kotlin/rs09/game/ge/GEDB.kt b/Server/src/main/kotlin/rs09/game/ge/GEDB.kt index cc4135b9a..0571ae7f8 100644 --- a/Server/src/main/kotlin/rs09/game/ge/GEDB.kt +++ b/Server/src/main/kotlin/rs09/game/ge/GEDB.kt @@ -6,6 +6,7 @@ import org.json.simple.JSONObject import org.json.simple.parser.JSONParser import org.sqlite.SQLiteDataSource import rs09.ServerConstants +import rs09.game.system.SystemLogger import java.io.File import java.io.FileReader import java.sql.Connection @@ -16,7 +17,9 @@ import java.sql.Connection */ object GEDB { private var pathString = "" - private var connection: Connection? = null + public var connection: Connection? = null + private var initialized = false + private var connectionRefs = 0 fun init() { init(File(ServerConstants.GRAND_EXCHANGE_DATA_PATH + "grandexchange.db").absolutePath) @@ -25,13 +28,24 @@ object GEDB { //This needs to be a separate method, so we can call it after the server config has been parsed fun init(path: String) { + if(initialized) return pathString = path //Check if the grandexchange.db file already exists. If not, create it and create the tables. if(!File(path).exists()) generateAndTransfer() } - fun connect(): Connection + @JvmStatic fun run(closure: (conn: Connection) -> Unit) { + connectionRefs++ + val con = connect() + closure.invoke(con) + connectionRefs-- + if(connectionRefs == 0) { + con.close() + } + } + + private fun connect(): Connection { if (connection == null || connection!!.isClosed) { @@ -152,14 +166,14 @@ object GEDB { } reader.close() } + statement.close() //price index isn't worth transferring, so we're just going to make a new one. ItemDefinition.getDefinitions().values.forEach { def -> if(def.isTradeable){ - statement.execute("insert into price_index(item_id, value) values(${def.id},${def.value})") + PriceIndex.allowItem(def.id) } } - statement.close() } } \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt b/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt index 88da9176c..760540d99 100644 --- a/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt +++ b/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt @@ -1,11 +1,11 @@ package rs09.game.ge import api.* -import core.game.ge.GrandExchangeDatabase import core.game.ge.OfferState import core.game.node.entity.player.Player import core.game.node.entity.player.info.PlayerDetails import core.game.node.entity.player.link.audio.Audio +import rs09.ServerConstants import rs09.game.system.SystemLogger import rs09.game.system.command.Privilege import rs09.game.system.config.ItemConfigParser @@ -40,9 +40,7 @@ class GrandExchange : StartupListener, Commands { val t = Thread { Thread.currentThread().name = "GE Update Worker" while(true) { - with(GEDB.connect()) { - val conn = this - + GEDB.run { conn -> val botStmt = conn.createStatement() val botOffers = botStmt.executeQuery("SELECT * from bot_offers") while(botOffers.next()) { @@ -90,19 +88,30 @@ class GrandExchange : StartupListener, Commands { addBotOffer(id, amount) notify(player, "Added ${amount}x ${getItemName(id)} to the bot offers.") } + + define("bange", Privilege.ADMIN) {player, strings -> + val id = strings[1].toInt() + PriceIndex.banItem(id) + notify(player, "Banned ${getItemName(id)} from GE trade.") + } + + define("allowge", Privilege.ADMIN) {player, strings -> + val id = strings[1].toInt() + PriceIndex.allowItem(id) + notify(player, "Allowed ${getItemName(id)} for GE trade.") + } } companion object { fun processOffer(offer: GrandExchangeOffer) { - with (GEDB.connect()) { - val conn = this + GEDB.run { conn -> if(offer.isActive) { val stmt = conn.createStatement() val olderOffers = stmt.executeQuery("SELECT * FROM player_offers WHERE item_id = ${offer.itemID} AND is_sale = ${!offer.sell} AND offer_state < 4 AND NOT offer_state = 2 AND time_stamp < ${offer.timeStamp}") - if(tryOffers(offer, olderOffers, true)) return + if(tryOffers(offer, olderOffers, true)) return@run val newerOffers = stmt.executeQuery("SELECT * FROM player_offers WHERE item_id = ${offer.itemID} AND is_sale = ${!offer.sell} AND offer_state < 4 AND NOT offer_state = 2 AND time_stamp >= ${offer.timeStamp}") - if(tryOffers(offer, newerOffers, false)) return + if(tryOffers(offer, newerOffers, false)) return@run } } } @@ -135,7 +144,7 @@ class GrandExchange : StartupListener, Commands { @JvmStatic fun getRecommendedPrice(itemID: Int, from_bot: Boolean = false): Int { - var base = max(GrandExchangeDatabase.getDatabase()[itemID]?.value ?: 0, getItemDefPrice(itemID)) + var base = max(PriceIndex.getValue(itemID), getItemDefPrice(itemID)) if (from_bot) base = (max(BotPrices.getPrice(itemID), base) * 1.10).toInt() return base } @@ -147,69 +156,71 @@ class GrandExchange : StartupListener, Commands { @JvmStatic fun getOfferStats(itemID: Int, sale: Boolean) : String { - val conn = GEDB.connect() - - var foundOffers = 0 - var totalAmount = 0 - var bestPrice = 0 val sb = StringBuilder() - var stmt = conn.createStatement() - if(!sale) - { - var botAmt = 0 - var botPrice = 0 - val player_offers = stmt.executeQuery("SELECT * from player_offers where item_id = $itemID AND is_sale = 1 AND offer_state < 4 AND NOT offer_state = 2") + GEDB.run { conn -> + var foundOffers = 0 + var totalAmount = 0 + var bestPrice = 0 + var stmt = conn.createStatement() - while(player_offers.next()) + if(!sale) { - val o = GrandExchangeOffer.fromQuery(player_offers) - ++foundOffers - totalAmount += o.amountLeft - if(o.offeredValue < bestPrice || bestPrice == 0) - bestPrice = o.offeredValue + var botAmt = 0 + var botPrice = 0 + val player_offers = stmt.executeQuery("SELECT * from player_offers where item_id = $itemID AND is_sale = 1 AND offer_state < 4 AND NOT offer_state = 2") + + while(player_offers.next()) + { + val o = GrandExchangeOffer.fromQuery(player_offers) + ++foundOffers + totalAmount += o.amountLeft + if(o.offeredValue < bestPrice || bestPrice == 0) + bestPrice = o.offeredValue + } + + stmt.close() + stmt = conn.createStatement() + val bot_offers = stmt.executeQuery("SELECT * from bot_offers where item_id = $itemID") + if(bot_offers.next()) + { + val o = GrandExchangeOffer.fromBotQuery(bot_offers) + botAmt = o.amount + botPrice = getRecommendedPrice(itemID, true) + } + + sb.append("Player Stock: $totalAmount ") + sb.append(" Lowest Price: $bestPrice
") + sb.append("-".repeat(50)) + sb.append("
Bot Stock: $botAmt ") + sb.append(" Bot Price: $botPrice") + } + else + { + val buy_offers = stmt.executeQuery("SELECT * from player_offers where item_id = $itemID AND is_sale = 0 AND offer_state < 4 AND NOT offer_state = 2") + + while(buy_offers.next()) + { + val o = GrandExchangeOffer.fromQuery(buy_offers) + ++foundOffers + totalAmount += o.amountLeft + if(o.offeredValue > bestPrice) + bestPrice = o.offeredValue + } + + sb.append("Buy Offers: $totalAmount ") + sb.append("Highest Offer: $bestPrice") } stmt.close() - stmt = conn.createStatement() - val bot_offers = stmt.executeQuery("SELECT * from bot_offers where item_id = $itemID") - if(bot_offers.next()) - { - val o = GrandExchangeOffer.fromBotQuery(bot_offers) - botAmt = o.amount - botPrice = getRecommendedPrice(itemID, true) - } - - sb.append("Player Stock: $totalAmount ") - sb.append(" Lowest Price: $bestPrice
") - sb.append("-".repeat(50)) - sb.append("
Bot Stock: $botAmt ") - sb.append(" Bot Price: $botPrice") - } - else - { - val buy_offers = stmt.executeQuery("SELECT * from player_offers where item_id = $itemID AND is_sale = 0 AND offer_state < 4 AND NOT offer_state = 2") - - while(buy_offers.next()) - { - val o = GrandExchangeOffer.fromQuery(buy_offers) - ++foundOffers - totalAmount += o.amountLeft - if(o.offeredValue > bestPrice) - bestPrice = o.offeredValue - } - - sb.append("Buy Offers: $totalAmount ") - sb.append("Highest Offer: $bestPrice") } - stmt.close() return sb.toString() } fun addBotOffer(itemID: Int, amount: Int): Boolean { - if (GrandExchangeDatabase.getDatabase()[itemID] == null) + if (!PriceIndex.canTrade(itemID)) return false val offer = GrandExchangeOffer.createBotOffer(itemID, amount) @@ -278,8 +289,13 @@ class GrandExchange : StartupListener, Commands { if(buyer.amountLeft < 1) buyer.offerState = OfferState.COMPLETED - seller.totalCoinExchange += if(sellerBias) buyer.offeredValue else seller.offeredValue * amount - buyer.totalCoinExchange += if(sellerBias) buyer.offeredValue else seller.offeredValue * amount + val totalCoinXC = (if(sellerBias) buyer.offeredValue else seller.offeredValue) * amount + + seller.totalCoinExchange += totalCoinXC + buyer.totalCoinExchange += totalCoinXC + + if(canUpdatePriceIndex(seller, buyer)) + PriceIndex.addTrade(offer.itemID, amount, (totalCoinXC / amount)) seller.update() val sellerPlayer = Repository.uid_map[seller.playerUID] @@ -289,35 +305,44 @@ class GrandExchange : StartupListener, Commands { GrandExchangeRecords.getInstance(buyerPlayer).visualizeRecords() } + private fun canUpdatePriceIndex(seller: GrandExchangeOffer, buyer: GrandExchangeOffer): Boolean { + if(seller.playerUID == buyer.playerUID) return false + if(!ServerConstants.BOTS_INFLUENCE_PRICE_INDEX && (seller.isBot || buyer.isBot)) return false; + return true + } + fun getValidOffers(): List { - val conn = GEDB.connect() - val stmt = conn.createStatement() val offers = ArrayList() - val results = stmt.executeQuery("SELECT * FROM player_offers WHERE offer_state < 4 AND NOT offer_state = 2") - while(results.next()) - { - val o = GrandExchangeOffer.fromQuery(results) - offers.add(o) + GEDB.run { conn -> + val stmt = conn.createStatement() + + val results = + stmt.executeQuery("SELECT * FROM player_offers WHERE offer_state < 4 AND NOT offer_state = 2") + while (results.next()) { + val o = GrandExchangeOffer.fromQuery(results) + offers.add(o) + } + stmt.close() } - stmt.close() return offers } fun getBotOffers(): List { - val conn = GEDB.connect() - val stmt = conn.createStatement() val offers = ArrayList() - val results = stmt.executeQuery("SELECT item_id,amount FROM bot_offers WHERE amount > 0") - while(results.next()) - { - val o = GrandExchangeOffer.fromBotQuery(results) - offers.add(o) + GEDB.run { conn -> + val stmt = conn.createStatement() + + val results = stmt.executeQuery("SELECT item_id,amount FROM bot_offers WHERE amount > 0") + while (results.next()) { + val o = GrandExchangeOffer.fromBotQuery(results) + offers.add(o) + } + stmt.close() } - stmt.close() return offers } diff --git a/Server/src/main/kotlin/rs09/game/ge/GrandExchangeOffer.kt b/Server/src/main/kotlin/rs09/game/ge/GrandExchangeOffer.kt index 03604c49b..05c7819bb 100644 --- a/Server/src/main/kotlin/rs09/game/ge/GrandExchangeOffer.kt +++ b/Server/src/main/kotlin/rs09/game/ge/GrandExchangeOffer.kt @@ -95,61 +95,57 @@ class GrandExchangeOffer() { fun update() { - val conn = GEDB.connect() - - if(isBot) - { - val stmt = conn.prepareStatement("UPDATE bot_offers SET amount = ? WHERE item_id = ?") - stmt.setInt(1, amountLeft) - stmt.setInt(2, itemID) - stmt.executeUpdate() - stmt.close() - } - else - { - val stmt = conn.prepareStatement("UPDATE player_offers SET amount_complete = ?, offer_state = ?, total_coin_xc = ?, withdraw_items = ?, slot_index = ? WHERE uid = ?") - stmt.setInt(1, completedAmount) - stmt.setInt(2, offerState.ordinal) - stmt.setInt(3, totalCoinExchange) - stmt.setString(4, encodeWithdraw()) - stmt.setInt(5, index) - stmt.setLong(6, uid) - stmt.executeUpdate() - stmt.close() + GEDB.run {conn -> + if(isBot) + { + val stmt = conn.prepareStatement("UPDATE bot_offers SET amount = ? WHERE item_id = ?") + stmt.setInt(1, amountLeft) + stmt.setInt(2, itemID) + stmt.executeUpdate() + stmt.close() + } + else + { + val stmt = conn.prepareStatement("UPDATE player_offers SET amount_complete = ?, offer_state = ?, total_coin_xc = ?, withdraw_items = ?, slot_index = ? WHERE uid = ?") + stmt.setInt(1, completedAmount) + stmt.setInt(2, offerState.ordinal) + stmt.setInt(3, totalCoinExchange) + stmt.setString(4, encodeWithdraw()) + stmt.setInt(5, index) + stmt.setLong(6, uid) + stmt.executeUpdate() + stmt.close() + } } } /** Called when writing a brand new offer to the database. Should not be used under any other circumstance **/ fun writeNew() { - val conn = GEDB.connect() + GEDB.run { conn -> + if (isBot) { + val stmt = conn.createStatement() + val result = stmt.executeQuery("SELECT * from bot_offers where item_id = $itemID") + val isExists = result.next() - if(isBot) - { - val stmt = conn.createStatement() - val result = stmt.executeQuery("SELECT * from bot_offers where item_id = $itemID") - val isExists = result.next() - - if(isExists) - { - val oldAmount = result.getInt("amount") - stmt.executeUpdate("UPDATE bot_offers set amount = ${oldAmount + amount} where item_id = $itemID") + if (isExists) { + val oldAmount = result.getInt("amount") + stmt.executeUpdate("UPDATE bot_offers set amount = ${oldAmount + amount} where item_id = $itemID") + } else + stmt.executeUpdate("INSERT INTO bot_offers(item_id,amount) values($itemID,$amount)") + stmt.close() + } else { + val stmt = conn.createStatement() + stmt.executeUpdate( + "INSERT INTO player_offers(player_uid, item_id, amount_total, offered_value, time_stamp, offer_state, is_sale, slot_index) " + + "values($playerUID,$itemID,$amount,$offeredValue,${System.currentTimeMillis()},${offerState.ordinal},${if (sell) 1 else 0}, $index)" + ) + val nowuid = stmt.executeQuery("SELECT last_insert_rowid()") + uid = nowuid.getLong(1) + visualize(player) + stmt.close() } - else - stmt.executeUpdate("INSERT INTO bot_offers(item_id,amount) values($itemID,$amount)") - stmt.close() } - else - { - val stmt = conn.createStatement() - stmt.executeUpdate("INSERT INTO player_offers(player_uid, item_id, amount_total, offered_value, time_stamp, offer_state, is_sale, slot_index) " + - "values($playerUID,$itemID,$amount,$offeredValue,${System.currentTimeMillis()},${offerState.ordinal},${if(sell) 1 else 0}, $index)") - val nowuid = stmt.executeQuery("SELECT last_insert_rowid()") - uid = nowuid.getLong(1) - visualize(player) - stmt.close() - } - } private fun encodeWithdraw() : String diff --git a/Server/src/main/kotlin/rs09/game/ge/GrandExchangeRecords.kt b/Server/src/main/kotlin/rs09/game/ge/GrandExchangeRecords.kt index e1e3ded55..28e764a87 100644 --- a/Server/src/main/kotlin/rs09/game/ge/GrandExchangeRecords.kt +++ b/Server/src/main/kotlin/rs09/game/ge/GrandExchangeRecords.kt @@ -26,7 +26,6 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer, override fun login(player: Player) { val instance = GrandExchangeRecords(player) - instance.init() player.setAttribute("ge-records", instance) } @@ -51,21 +50,22 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer, /** * Read offers from the database */ - val conn = GEDB.connect() - val stmt = conn.createStatement() - val offer_records = stmt.executeQuery("SELECT * from player_offers where player_uid = ${player.details.uid} AND offer_state < 6") + val needsIndex = ArrayDeque() val instance = getInstance(player) - val needsIndex = ArrayDeque() + GEDB.run { conn -> + val stmt = conn.createStatement() + val offer_records = stmt.executeQuery("SELECT * from player_offers where player_uid = ${player.details.uid} AND offer_state < 6") - while (offer_records.next()) { - val offer = GrandExchangeOffer.fromQuery(offer_records) - if (offer.index == -1) //used to index old (converted from JSON) offers - needsIndex.push(offer) - else - instance.offerRecords[offer.index] = OfferRecord(offer.uid, offer.index) + while (offer_records.next()) { + val offer = GrandExchangeOffer.fromQuery(offer_records) + if (offer.index == -1) //used to index old (converted from JSON) offers + needsIndex.push(offer) + else + instance.offerRecords[offer.index] = OfferRecord(offer.uid, offer.index) + } + stmt.close() } - stmt.close() if (needsIndex.isNotEmpty()) { for ((index, offer) in offerRecords.withIndex()) { @@ -85,7 +85,7 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer, } } - instance.visualizeRecords() + instance.init() } override fun savePlayer(player: Player, save: JSONObject) { @@ -128,22 +128,22 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer, fun visualizeRecords() { - val conn = GEDB.connect() - val stmt = conn.createStatement() + GEDB.run { conn -> + val stmt = conn.createStatement() - for (record in offerRecords) { - if (record != null) { - val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}") - if(offer_raw.next()) - { - val offer = GrandExchangeOffer.fromQuery(offer_raw) - if(offer.offerState == OfferState.REMOVED) continue - offer.index = record.slot - offer.visualize(player) + for (record in offerRecords) { + if (record != null) { + val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}") + if (offer_raw.next()) { + val offer = GrandExchangeOffer.fromQuery(offer_raw) + if (offer.offerState == OfferState.REMOVED) continue + offer.index = record.slot + offer.visualize(player) + } } } + stmt.close() } - stmt.close() } fun getOffer(index: Int) : GrandExchangeOffer? @@ -155,18 +155,19 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer, fun getOffer(record: OfferRecord?) : GrandExchangeOffer? { record ?: return null - val conn = GEDB.connect() - val stmt = conn.createStatement() - val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}") - if(offer_raw.next()) - { - val offer = GrandExchangeOffer.fromQuery(offer_raw) - offer.index = record.slot + var offerToReturn: GrandExchangeOffer? = null + GEDB.run { conn -> + val stmt = conn.createStatement() + val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}") + if (offer_raw.next()) { + val offer = GrandExchangeOffer.fromQuery(offer_raw) + offer.index = record.slot + stmt.close() + offerToReturn = offer + } stmt.close() - return offer } - stmt.close() - return null + return offerToReturn } /** diff --git a/Server/src/main/kotlin/rs09/game/ge/PriceIndex.kt b/Server/src/main/kotlin/rs09/game/ge/PriceIndex.kt new file mode 100644 index 000000000..0045dc461 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/ge/PriceIndex.kt @@ -0,0 +1,135 @@ +package rs09.game.ge + +import api.itemDefinition +import java.sql.ResultSet + +object PriceIndex { + @JvmStatic fun canTrade(id: Int): Boolean { + var canTrade = false + + GEDB.run {conn -> + val stmt = conn.prepareStatement(EXISTS_QUERY) + stmt.setInt(1, id) + val res = stmt.executeQuery() + if(res.next()) { + canTrade = res.getInt(1) == 1 + } + } + + return canTrade + } + + fun banItem(id: Int) { + GEDB.run { conn -> + val stmt = conn.prepareStatement(REMOVE_QUERY) + stmt.setInt(1, id) + stmt.execute() + } + } + + fun allowItem(id: Int) { + GEDB.run { conn -> + val stmt = conn.prepareStatement(INSERT_QUERY) + stmt.setInt(1, id) + stmt.setInt(2, itemDefinition(id).getAlchemyValue(true)) + stmt.setInt(3, itemDefinition(id).getAlchemyValue(true)) + stmt.setInt(4, 0) + stmt.setInt(5, 0) + stmt.execute() + } + } + + fun getValue(id: Int): Int { + var value = itemDefinition(id).getAlchemyValue(true) + GEDB.run { conn -> + val stmt = conn.prepareStatement(GET_VALUE_QUERY) + stmt.setInt(1, id) + val res = stmt.executeQuery() + if(res.next()) { + value = res.getInt(1) + } + } + return value + } + + fun addTrade(id: Int, amount: Int, pricePerUnit: Int) { + val oldInfo = getInfo(id) ?: return + val newInfo = oldInfo.copy() + + val volumeResetThreshold = + if (pricePerUnit >= 1000000) 500 + else if (pricePerUnit >= 100000) 1000 + else if (pricePerUnit >= 25000) 2500 + else 10000 + + newInfo.totalValue += (amount * pricePerUnit) + newInfo.uniqueTrades += amount + newInfo.lastUpdate = System.currentTimeMillis() + newInfo.currentValue = (newInfo.totalValue / newInfo.uniqueTrades).toInt() + + if (newInfo.uniqueTrades > volumeResetThreshold) { + val newAmt = (0.1 * volumeResetThreshold).toInt() + newInfo.uniqueTrades = newAmt + newInfo.totalValue = newAmt.toLong() * newInfo.currentValue + } + + updateInfo(newInfo) + } + + private fun updateInfo(newInfo: PriceInfo) { + GEDB.run { conn -> + val stmt = conn.prepareStatement(UPDATE_QUERY) + stmt.setInt(1, newInfo.currentValue) + stmt.setLong(2, newInfo.totalValue) + stmt.setInt(3, newInfo.uniqueTrades) + stmt.setLong(4, newInfo.lastUpdate) + stmt.setInt(5, newInfo.itemId) + stmt.execute() + } + } + + private fun getInfo(id: Int): PriceInfo? { + var priceInfo: PriceInfo? = null + + GEDB.run { conn -> + val stmt = conn.prepareStatement(SELECT_QUERY) + stmt.setInt(1, id) + val res = stmt.executeQuery() + if(res.next()) { + priceInfo = PriceInfo.fromQuery(res) + } + } + + return priceInfo + } + + const val SELECT_QUERY = "SELECT * FROM price_index WHERE item_id = ?;" + const val UPDATE_QUERY = "UPDATE price_index SET value = ?, total_value = ?, unique_trades = ?, last_update = ? WHERE item_id = ?;" + const val EXISTS_QUERY = "SELECT EXISTS(SELECT 1 FROM price_index WHERE item_id = ?);" + const val REMOVE_QUERY = "DELETE FROM price_index WHERE item_id = ?;" + const val INSERT_QUERY = "INSERT INTO price_index (item_id, value, total_value, unique_trades, last_update) VALUES (?,?,?,?,?);" + const val GET_VALUE_QUERY = "SELECT value FROM price_index WHERE item_id = ?;" +} + +data class PriceInfo( + var itemId: Int, + var currentValue: Int, + var totalValue: Long, + var uniqueTrades: Int, + var lastUpdate: Long +) { + fun copy() : PriceInfo { + return PriceInfo(itemId, currentValue, totalValue, uniqueTrades, lastUpdate) + } + companion object { + fun fromQuery(result: ResultSet) : PriceInfo { + return PriceInfo( + result.getInt(1), + result.getInt(2), + result.getLong(3), + result.getInt(4), + result.getLong(5) + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/interaction/inter/ge/StockMarket.kt b/Server/src/main/kotlin/rs09/game/interaction/inter/ge/StockMarket.kt index b2e553765..4f73f15dd 100644 --- a/Server/src/main/kotlin/rs09/game/interaction/inter/ge/StockMarket.kt +++ b/Server/src/main/kotlin/rs09/game/interaction/inter/ge/StockMarket.kt @@ -3,8 +3,6 @@ package rs09.game.interaction.inter.ge import api.* import core.game.component.Component import core.game.container.access.BitregisterAssembler -import core.game.ge.GrandExchangeDatabase -import core.game.ge.GrandExchangeEntry import core.game.ge.OfferState import core.game.node.entity.player.Player import core.game.node.entity.player.link.audio.Audio @@ -18,6 +16,7 @@ import org.rs09.consts.Components import rs09.game.ge.GrandExchange import rs09.game.ge.GrandExchangeOffer import rs09.game.ge.GrandExchangeRecords +import rs09.game.ge.PriceIndex import rs09.game.interaction.InterfaceListener import rs09.game.system.SystemLogger @@ -69,8 +68,7 @@ class StockMarket : InterfaceListener { var id = item.id if(!item.definition.isUnnoted) id = item.noteChange - val itemDb = GrandExchangeDatabase.getDatabase()[id] - if(itemDb == null || !item.definition.isTradeable) + if(!PriceIndex.canTrade(id)) { sendMessage(player, "This item can't be sold on the Grand Exchange.") return@on true @@ -316,11 +314,10 @@ class StockMarket : InterfaceListener { fun updateVarbits(player: Player, offer: GrandExchangeOffer?, index: Int, sale: Boolean? = null) { val isSale = sale ?: offer?.sell ?: false - val entry: GrandExchangeEntry? = GrandExchangeDatabase.getDatabase()[offer?.itemID ?: -1] var lowPrice = 0 var highPrice = 0 - val recommendedPrice = GrandExchange.getRecommendedPrice(entry?.itemId ?: 0) - if (entry != null) { + val recommendedPrice = GrandExchange.getRecommendedPrice(offer?.itemID ?: 0) + if (PriceIndex.canTrade(offer?.itemID ?: 0)) { lowPrice = (recommendedPrice * 0.95).toInt() highPrice = (recommendedPrice * 1.05).toInt() } diff --git a/Server/src/main/kotlin/rs09/game/system/command/sets/MiscCommandSet.kt b/Server/src/main/kotlin/rs09/game/system/command/sets/MiscCommandSet.kt index 05b1790d7..aca18fc7f 100644 --- a/Server/src/main/kotlin/rs09/game/system/command/sets/MiscCommandSet.kt +++ b/Server/src/main/kotlin/rs09/game/system/command/sets/MiscCommandSet.kt @@ -570,7 +570,7 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ val offerAmounts = HashMap() val offerPrice = HashMap() - val offers = GrandExchange.getBotOffers().filter { getItemName(it.itemID).contains(searchTerm, true) } + val offers = GrandExchange.getBotOffers().filter { getItemName(it.itemID).contains(searchTerm, true) || getItemName(it.itemID).equals(searchTerm, true) } for(offer in offers) { @@ -682,7 +682,7 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ } fun showOffers(player: Player, searchTerm: String){ - val offers = GrandExchange.getValidOffers().filter { getItemName(it.itemID).contains(searchTerm, true) } + val offers = GrandExchange.getValidOffers().filter { getItemName(it.itemID).contains(searchTerm, true) || getItemName(it.itemID).equals(searchTerm, true) } val buyingAmount = HashMap() val buyingHighest = HashMap() val sellingAmount = HashMap() diff --git a/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt b/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt index e4ad85b4a..5e008145b 100644 --- a/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt +++ b/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt @@ -114,6 +114,7 @@ object ServerConfigParser { ServerConstants.CELEDT_DATA_PATH = data.getPath("paths.cele_drop_table_path") ServerConstants.SERVER_GE_NAME = data.getString("world.name_ge") ?: ServerConstants.SERVER_NAME ServerConstants.RULES_AND_INFO_ENABLED = data.getBoolean("world.show_rules", true) + ServerConstants.BOTS_INFLUENCE_PRICE_INDEX = data.getBoolean("world.bots_influence_ge_price", true) } diff --git a/Server/src/test/kotlin/ExchangeTests.kt b/Server/src/test/kotlin/ExchangeTests.kt index c7711498c..f682d8f3f 100644 --- a/Server/src/test/kotlin/ExchangeTests.kt +++ b/Server/src/test/kotlin/ExchangeTests.kt @@ -8,18 +8,20 @@ import rs09.game.ge.GEDB import rs09.game.ge.GrandExchange import rs09.game.ge.GrandExchangeOffer import rs09.game.system.SystemLogger +import java.io.File import kotlin.random.Random @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ExchangeTests { companion object { private const val TEST_DB_PATH = "ge_test.db" init { + TestUtils.preTestSetup() GEDB.init(TEST_DB_PATH) } - fun generateOffer(itemId: Int, amount: Int, price: Int, sale: Boolean) : GrandExchangeOffer { + fun generateOffer(itemId: Int, amount: Int, price: Int, sale: Boolean, username: String = "test ${System.currentTimeMillis()}") : GrandExchangeOffer { val offer = GrandExchangeOffer() - val uid = "test ${System.currentTimeMillis()}".hashCode() + val uid = username.hashCode() offer.offerState = OfferState.REGISTERED offer.itemID = itemId offer.offeredValue = price @@ -32,14 +34,18 @@ import kotlin.random.Random offer.writeNew() return offer } + + @AfterAll fun cleanup() { + File(TEST_DB_PATH).delete() + } } @Test fun testPlaceOffer() { val offer = generateOffer(4151, 1, 100000, true) val uid = offer.playerUID - with (GEDB.connect()) { - val stmt = this.createStatement() + GEDB.run { conn -> + val stmt = conn.createStatement() val result = stmt.executeQuery("select * from player_offers where player_uid = $uid") val thisOffer = if(result.next()) GrandExchangeOffer.fromQuery(result) else fail("Offer did not exist!") Assertions.assertEquals(offer.itemID, thisOffer.itemID) @@ -66,4 +72,32 @@ import kotlin.random.Random GrandExchange.exchange(sellOffer,buyOffer) Assertions.assertEquals(null, buyOffer.withdraw[0]) //Buyer should not get any items, sell offer is higher } + + @Test fun manyCompletedOffersAboveDefaultPriceShouldInfluencePriceUpwards() { + val defaultPrice = GrandExchange.getRecommendedPrice(4151) + val modifiedPrice = defaultPrice * 1.15 + + for(i in 0 until 100) { + val sellOffer = generateOffer(4151, 1, modifiedPrice.toInt(), true, "test1") + val buyOffer = generateOffer(4151, 1, modifiedPrice.toInt(), false, "test2") + GrandExchange.exchange(sellOffer,buyOffer) + } + + val newPrice = GrandExchange.getRecommendedPrice(4151) + + Assertions.assertEquals(true, newPrice > defaultPrice, "Price was not influenced in the expected way! New Price: $newPrice, default: $defaultPrice") + } + + @Test fun playerTradingWithThemselvesShouldNotBeAbleToInfluencePrices() { + val defaultPrice = GrandExchange.getRecommendedPrice(4151) + val modifiedPrice = defaultPrice * 1.15 + + for(i in 0 until 100) { + val sellOffer = generateOffer(4151, 1, modifiedPrice.toInt(), true, "test") + val buyOffer = generateOffer(4151, 1, modifiedPrice.toInt(), false, "test") + GrandExchange.exchange(sellOffer,buyOffer) + } + + Assertions.assertEquals(defaultPrice, GrandExchange.getRecommendedPrice(4151)) + } } \ No newline at end of file diff --git a/Server/src/test/kotlin/PriceIndexTests.kt b/Server/src/test/kotlin/PriceIndexTests.kt new file mode 100644 index 000000000..04b3f5e7e --- /dev/null +++ b/Server/src/test/kotlin/PriceIndexTests.kt @@ -0,0 +1,37 @@ +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import rs09.game.ge.GEDB +import rs09.game.ge.PriceIndex + +class PriceIndexTests { + companion object {init {TestUtils.preTestSetup(); GEDB.init("ge_test.db")}} + + @Test fun shouldAllowCheckingIfItemCanBeSoldOnGE() { + PriceIndex.allowItem(4151) + val canSellWhip = PriceIndex.canTrade(4151) + Assertions.assertEquals(true, canSellWhip) + } + + @Test fun shouldAllowBanningItems() { + PriceIndex.allowItem(1333) + Assertions.assertEquals(true, PriceIndex.canTrade(1333)) + PriceIndex.banItem(1333) + Assertions.assertEquals(false, PriceIndex.canTrade(1333)) + } + + @Test fun shouldAllowValueToBeInfluenced() { + PriceIndex.allowItem(1333) + val defaultValue = PriceIndex.getValue(1333) + PriceIndex.addTrade(1333, 100, (defaultValue * 1.15).toInt()) + Assertions.assertNotEquals(defaultValue, PriceIndex.getValue(1333)) + } + + @Test fun shouldAllowValueToGoDownWithHighVolumeOfLowerValueTrades() { + PriceIndex.allowItem(1334) + val defaultValue = PriceIndex.getValue(1334) + PriceIndex.addTrade(1334, 1000, (defaultValue * 1.15).toInt()) + Assertions.assertEquals(true, PriceIndex.getValue(1334) > defaultValue) + PriceIndex.addTrade(1334, 2000, (defaultValue * 0.85).toInt()) + Assertions.assertEquals(true, PriceIndex.getValue(1334) < defaultValue, "Old: $defaultValue, New: ${PriceIndex.getValue(1334)}") + } +} \ No newline at end of file diff --git a/Server/worldprops/default.conf b/Server/worldprops/default.conf index e04908f25..52a6ca919 100644 --- a/Server/worldprops/default.conf +++ b/Server/worldprops/default.conf @@ -48,6 +48,7 @@ max_adv_bots = 100 wild_pvp_enabled = false jad_practice_enabled = true personalized_shops = true +bots_influence_ge_price = true [paths] #path to the data folder, which contains the cache subfolder and such