Improved the GE database access to use a basic reference counter for the connection that automatically closes the connection when no one is using it anymore
Grand Exchange now uses the GE sqlite database's price index table to track price changes Removed the old price index (confusingly called GrandExchangeDatabase.java) Grand Exchange prices are now dynamic and influenced by trades (weighted stepping average price) Fixed GE interfaces not loading on login
This commit is contained in:
@@ -5,7 +5,6 @@ import core.cache.def.Definition;
|
|||||||
import core.cache.misc.buffer.ByteBufferUtils;
|
import core.cache.misc.buffer.ByteBufferUtils;
|
||||||
import core.game.container.Container;
|
import core.game.container.Container;
|
||||||
import core.game.content.global.action.DropItemHandler;
|
import core.game.content.global.action.DropItemHandler;
|
||||||
import core.game.ge.GrandExchangeDatabase;
|
|
||||||
import core.game.interaction.OptionHandler;
|
import core.game.interaction.OptionHandler;
|
||||||
import core.game.node.Node;
|
import core.game.node.Node;
|
||||||
import core.game.node.entity.player.Player;
|
import core.game.node.entity.player.Player;
|
||||||
@@ -1416,7 +1415,7 @@ public class ItemDefinition extends Definition<Item> {
|
|||||||
if (!getConfiguration(ItemConfigParser.TRADEABLE, false)) {
|
if (!getConfiguration(ItemConfigParser.TRADEABLE, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !unnoted || GrandExchangeDatabase.getDatabase().get(getId()) != null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package core.game.content.dialogue;
|
package core.game.content.dialogue;
|
||||||
|
|
||||||
import core.game.ge.GrandExchangeDatabase;
|
|
||||||
import core.game.node.entity.npc.NPC;
|
import core.game.node.entity.npc.NPC;
|
||||||
import core.plugin.Initializable;
|
import core.plugin.Initializable;
|
||||||
import core.game.node.entity.player.Player;
|
import core.game.node.entity.player.Player;
|
||||||
@@ -51,11 +50,6 @@ public final class GrandExchangeClerk extends DialoguePlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(int interfaceId, int buttonId) {
|
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) {
|
switch (stage) {
|
||||||
case 0:
|
case 0:
|
||||||
npc("Good day to you, sir, How can I help?");
|
npc("Good day to you, sir, How can I help?");
|
||||||
|
|||||||
@@ -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<Integer, GrandExchangeEntry> 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<Integer, GrandExchangeEntry> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import core.cache.def.impl.NPCDefinition;
|
|||||||
import core.cache.def.impl.SceneryDefinition;
|
import core.cache.def.impl.SceneryDefinition;
|
||||||
import core.plugin.Initializable;
|
import core.plugin.Initializable;
|
||||||
import core.game.ge.GEGuidePrice;
|
import core.game.ge.GEGuidePrice;
|
||||||
import core.game.ge.GrandExchangeDatabase;
|
|
||||||
import core.game.ge.GEGuidePrice.GuideType;
|
import core.game.ge.GEGuidePrice.GuideType;
|
||||||
import core.game.interaction.OptionHandler;
|
import core.game.interaction.OptionHandler;
|
||||||
import core.game.node.Node;
|
import core.game.node.Node;
|
||||||
@@ -56,20 +55,12 @@ public final class GrandExchangePlugin extends OptionHandler {
|
|||||||
player.getDialogueInterpreter().open(6528, NPC.create(6528, player.getLocation()));
|
player.getDialogueInterpreter().open(6528, NPC.create(6528, player.getLocation()));
|
||||||
break;
|
break;
|
||||||
case "exchange":
|
case "exchange":
|
||||||
if (!GrandExchangeDatabase.hasInitialized()) {
|
|
||||||
player.getDialogueInterpreter().sendDialogue("The Grand Exchange desk seems to be closed...");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
StockMarket.openFor(player);
|
StockMarket.openFor(player);
|
||||||
break;
|
break;
|
||||||
case "history":
|
case "history":
|
||||||
records.openHistoryLog(player);
|
records.openHistoryLog(player);
|
||||||
break;
|
break;
|
||||||
case "collect":
|
case "collect":
|
||||||
if (!GrandExchangeDatabase.hasInitialized()) {
|
|
||||||
player.getDialogueInterpreter().sendDialogue("The Grand Exchange desk seems to be closed...");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
records.openCollectionBox();
|
records.openCollectionBox();
|
||||||
break;
|
break;
|
||||||
case "info-logs":
|
case "info-logs":
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package core.game.node.entity.npc.drop;
|
|||||||
import static api.ContentAPIKt.*;
|
import static api.ContentAPIKt.*;
|
||||||
import core.cache.def.impl.NPCDefinition;
|
import core.cache.def.impl.NPCDefinition;
|
||||||
import core.game.content.global.Bones;
|
import core.game.content.global.Bones;
|
||||||
import core.game.ge.GrandExchangeDatabase;
|
|
||||||
import core.game.node.entity.Entity;
|
import core.game.node.entity.Entity;
|
||||||
import core.game.node.entity.npc.NPC;
|
import core.game.node.entity.npc.NPC;
|
||||||
import core.game.node.entity.player.Player;
|
import core.game.node.entity.player.Player;
|
||||||
@@ -18,10 +17,8 @@ import core.tools.StringUtils;
|
|||||||
import rs09.game.ai.AIPlayer;
|
import rs09.game.ai.AIPlayer;
|
||||||
import rs09.game.ai.AIRepository;
|
import rs09.game.ai.AIRepository;
|
||||||
import rs09.game.ai.general.GeneralBotCreator;
|
import rs09.game.ai.general.GeneralBotCreator;
|
||||||
import rs09.game.content.global.GlobalKillCounter;
|
|
||||||
import rs09.game.content.global.NPCDropTable;
|
import rs09.game.content.global.NPCDropTable;
|
||||||
import rs09.game.ge.GrandExchange;
|
import rs09.game.ge.GrandExchange;
|
||||||
import rs09.game.system.config.ItemConfigParser;
|
|
||||||
import rs09.game.world.repository.Repository;
|
import rs09.game.world.repository.Repository;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -144,7 +141,7 @@ public final class NPCDropTables {
|
|||||||
*/
|
*/
|
||||||
public Player getLooter(Player player, NPC npc, Item item) {
|
public Player getLooter(Player player, NPC npc, Item item) {
|
||||||
int itemId = item.getDefinition().isUnnoted() ? item.getId() : item.getNoteChange();
|
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;
|
Player looter = player;
|
||||||
List<Player> players = RegionManager.getLocalPlayers(npc, 16);
|
List<Player> players = RegionManager.getLocalPlayers(npc, 16);
|
||||||
List<Player> looters = new ArrayList<>(20);
|
List<Player> looters = new ArrayList<>(20);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package core.game.node.item;
|
package core.game.node.item;
|
||||||
|
|
||||||
import core.cache.def.impl.ItemDefinition;
|
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.DestinationFlag;
|
||||||
import core.game.interaction.Interaction;
|
import core.game.interaction.Interaction;
|
||||||
import core.game.interaction.OptionHandler;
|
import core.game.interaction.OptionHandler;
|
||||||
|
|||||||
@@ -2,14 +2,9 @@ package core.game.system;
|
|||||||
|
|
||||||
import api.PersistWorld;
|
import api.PersistWorld;
|
||||||
import api.ShutdownListener;
|
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.Player;
|
||||||
import core.game.node.entity.player.info.login.PlayerParser;
|
|
||||||
import rs09.Server;
|
import rs09.Server;
|
||||||
import rs09.ServerConstants;
|
import rs09.ServerConstants;
|
||||||
import rs09.ServerStore;
|
|
||||||
import rs09.game.content.global.GlobalKillCounter;
|
|
||||||
import rs09.game.system.SystemLogger;
|
import rs09.game.system.SystemLogger;
|
||||||
import rs09.game.world.GameWorld;
|
import rs09.game.world.GameWorld;
|
||||||
import rs09.game.world.repository.Repository;
|
import rs09.game.world.repository.Repository;
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package core.net.packet.in;
|
package core.net.packet.in;
|
||||||
|
|
||||||
import api.ContentAPIKt;
|
import api.ContentAPIKt;
|
||||||
import core.game.ge.GrandExchangeDatabase;
|
|
||||||
import core.game.ge.GrandExchangeEntry;
|
|
||||||
import core.game.node.entity.player.Player;
|
import core.game.node.entity.player.Player;
|
||||||
import core.net.packet.IncomingPacket;
|
import core.net.packet.IncomingPacket;
|
||||||
import core.net.packet.IoBuffer;
|
import core.net.packet.IoBuffer;
|
||||||
import rs09.game.ge.GrandExchange;
|
import rs09.game.ge.GrandExchange;
|
||||||
import rs09.game.ge.GrandExchangeOffer;
|
import rs09.game.ge.GrandExchangeOffer;
|
||||||
|
import rs09.game.ge.PriceIndex;
|
||||||
import rs09.game.interaction.inter.ge.StockMarket;
|
import rs09.game.interaction.inter.ge.StockMarket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,8 +22,7 @@ public class GrandExchangePacket implements IncomingPacket {
|
|||||||
int index = player.getAttribute("ge-index", -1);
|
int index = player.getAttribute("ge-index", -1);
|
||||||
offer.setItemID(itemId);
|
offer.setItemID(itemId);
|
||||||
offer.setSell(false);
|
offer.setSell(false);
|
||||||
GrandExchangeEntry entry = GrandExchangeDatabase.getDatabase().get(itemId);
|
if(!PriceIndex.canTrade(offer.getItemID()))
|
||||||
if(entry == null || !ContentAPIKt.itemDefinition(itemId).isTradeable())
|
|
||||||
{
|
{
|
||||||
ContentAPIKt.sendMessage(player, "That item is blacklisted from the grand exchange.");
|
ContentAPIKt.sendMessage(player, "That item is blacklisted from the grand exchange.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import java.math.BigInteger
|
|||||||
*/
|
*/
|
||||||
class ServerConstants {
|
class ServerConstants {
|
||||||
companion object {
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
var BOTS_INFLUENCE_PRICE_INDEX = true
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
var SHUTDOWN_HOOK: Thread = Thread(SystemShutdownHook())
|
var SHUTDOWN_HOOK: Thread = Thread(SystemShutdownHook())
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.json.simple.JSONObject
|
|||||||
import org.json.simple.parser.JSONParser
|
import org.json.simple.parser.JSONParser
|
||||||
import org.sqlite.SQLiteDataSource
|
import org.sqlite.SQLiteDataSource
|
||||||
import rs09.ServerConstants
|
import rs09.ServerConstants
|
||||||
|
import rs09.game.system.SystemLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
@@ -16,7 +17,9 @@ import java.sql.Connection
|
|||||||
*/
|
*/
|
||||||
object GEDB {
|
object GEDB {
|
||||||
private var pathString = ""
|
private var pathString = ""
|
||||||
private var connection: Connection? = null
|
public var connection: Connection? = null
|
||||||
|
private var initialized = false
|
||||||
|
private var connectionRefs = 0
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
init(File(ServerConstants.GRAND_EXCHANGE_DATA_PATH + "grandexchange.db").absolutePath)
|
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
|
//This needs to be a separate method, so we can call it after the server config has been parsed
|
||||||
fun init(path: String)
|
fun init(path: String)
|
||||||
{
|
{
|
||||||
|
if(initialized) return
|
||||||
pathString = path
|
pathString = path
|
||||||
//Check if the grandexchange.db file already exists. If not, create it and create the tables.
|
//Check if the grandexchange.db file already exists. If not, create it and create the tables.
|
||||||
if(!File(path).exists())
|
if(!File(path).exists())
|
||||||
generateAndTransfer()
|
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)
|
if (connection == null || connection!!.isClosed)
|
||||||
{
|
{
|
||||||
@@ -152,14 +166,14 @@ object GEDB {
|
|||||||
}
|
}
|
||||||
reader.close()
|
reader.close()
|
||||||
}
|
}
|
||||||
|
statement.close()
|
||||||
|
|
||||||
|
|
||||||
//price index isn't worth transferring, so we're just going to make a new one.
|
//price index isn't worth transferring, so we're just going to make a new one.
|
||||||
ItemDefinition.getDefinitions().values.forEach { def ->
|
ItemDefinition.getDefinitions().values.forEach { def ->
|
||||||
if(def.isTradeable){
|
if(def.isTradeable){
|
||||||
statement.execute("insert into price_index(item_id, value) values(${def.id},${def.value})")
|
PriceIndex.allowItem(def.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statement.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package rs09.game.ge
|
package rs09.game.ge
|
||||||
|
|
||||||
import api.*
|
import api.*
|
||||||
import core.game.ge.GrandExchangeDatabase
|
|
||||||
import core.game.ge.OfferState
|
import core.game.ge.OfferState
|
||||||
import core.game.node.entity.player.Player
|
import core.game.node.entity.player.Player
|
||||||
import core.game.node.entity.player.info.PlayerDetails
|
import core.game.node.entity.player.info.PlayerDetails
|
||||||
import core.game.node.entity.player.link.audio.Audio
|
import core.game.node.entity.player.link.audio.Audio
|
||||||
|
import rs09.ServerConstants
|
||||||
import rs09.game.system.SystemLogger
|
import rs09.game.system.SystemLogger
|
||||||
import rs09.game.system.command.Privilege
|
import rs09.game.system.command.Privilege
|
||||||
import rs09.game.system.config.ItemConfigParser
|
import rs09.game.system.config.ItemConfigParser
|
||||||
@@ -40,9 +40,7 @@ class GrandExchange : StartupListener, Commands {
|
|||||||
val t = Thread {
|
val t = Thread {
|
||||||
Thread.currentThread().name = "GE Update Worker"
|
Thread.currentThread().name = "GE Update Worker"
|
||||||
while(true) {
|
while(true) {
|
||||||
with(GEDB.connect()) {
|
GEDB.run { conn ->
|
||||||
val conn = this
|
|
||||||
|
|
||||||
val botStmt = conn.createStatement()
|
val botStmt = conn.createStatement()
|
||||||
val botOffers = botStmt.executeQuery("SELECT * from bot_offers")
|
val botOffers = botStmt.executeQuery("SELECT * from bot_offers")
|
||||||
while(botOffers.next()) {
|
while(botOffers.next()) {
|
||||||
@@ -90,19 +88,30 @@ class GrandExchange : StartupListener, Commands {
|
|||||||
addBotOffer(id, amount)
|
addBotOffer(id, amount)
|
||||||
notify(player, "Added ${amount}x ${getItemName(id)} to the bot offers.")
|
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 {
|
companion object {
|
||||||
fun processOffer(offer: GrandExchangeOffer)
|
fun processOffer(offer: GrandExchangeOffer)
|
||||||
{
|
{
|
||||||
with (GEDB.connect()) {
|
GEDB.run { conn ->
|
||||||
val conn = this
|
|
||||||
if(offer.isActive) {
|
if(offer.isActive) {
|
||||||
val stmt = conn.createStatement()
|
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}")
|
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}")
|
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
|
@JvmStatic
|
||||||
fun getRecommendedPrice(itemID: Int, from_bot: Boolean = false): Int {
|
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()
|
if (from_bot) base = (max(BotPrices.getPrice(itemID), base) * 1.10).toInt()
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
@@ -147,12 +156,12 @@ class GrandExchange : StartupListener, Commands {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getOfferStats(itemID: Int, sale: Boolean) : String
|
fun getOfferStats(itemID: Int, sale: Boolean) : String
|
||||||
{
|
{
|
||||||
val conn = GEDB.connect()
|
val sb = StringBuilder()
|
||||||
|
|
||||||
|
GEDB.run { conn ->
|
||||||
var foundOffers = 0
|
var foundOffers = 0
|
||||||
var totalAmount = 0
|
var totalAmount = 0
|
||||||
var bestPrice = 0
|
var bestPrice = 0
|
||||||
val sb = StringBuilder()
|
|
||||||
var stmt = conn.createStatement()
|
var stmt = conn.createStatement()
|
||||||
|
|
||||||
if(!sale)
|
if(!sale)
|
||||||
@@ -204,12 +213,14 @@ class GrandExchange : StartupListener, Commands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stmt.close()
|
stmt.close()
|
||||||
|
}
|
||||||
|
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addBotOffer(itemID: Int, amount: Int): Boolean
|
fun addBotOffer(itemID: Int, amount: Int): Boolean
|
||||||
{
|
{
|
||||||
if (GrandExchangeDatabase.getDatabase()[itemID] == null)
|
if (!PriceIndex.canTrade(itemID))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
val offer = GrandExchangeOffer.createBotOffer(itemID, amount)
|
val offer = GrandExchangeOffer.createBotOffer(itemID, amount)
|
||||||
@@ -278,8 +289,13 @@ class GrandExchange : StartupListener, Commands {
|
|||||||
if(buyer.amountLeft < 1)
|
if(buyer.amountLeft < 1)
|
||||||
buyer.offerState = OfferState.COMPLETED
|
buyer.offerState = OfferState.COMPLETED
|
||||||
|
|
||||||
seller.totalCoinExchange += if(sellerBias) buyer.offeredValue else seller.offeredValue * amount
|
val totalCoinXC = (if(sellerBias) buyer.offeredValue else seller.offeredValue) * amount
|
||||||
buyer.totalCoinExchange += 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()
|
seller.update()
|
||||||
val sellerPlayer = Repository.uid_map[seller.playerUID]
|
val sellerPlayer = Repository.uid_map[seller.playerUID]
|
||||||
@@ -289,35 +305,44 @@ class GrandExchange : StartupListener, Commands {
|
|||||||
GrandExchangeRecords.getInstance(buyerPlayer).visualizeRecords()
|
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<GrandExchangeOffer>
|
fun getValidOffers(): List<GrandExchangeOffer>
|
||||||
{
|
{
|
||||||
val conn = GEDB.connect()
|
|
||||||
val stmt = conn.createStatement()
|
|
||||||
val offers = ArrayList<GrandExchangeOffer>()
|
val offers = ArrayList<GrandExchangeOffer>()
|
||||||
|
|
||||||
val results = stmt.executeQuery("SELECT * FROM player_offers WHERE offer_state < 4 AND NOT offer_state = 2")
|
GEDB.run { conn ->
|
||||||
while(results.next())
|
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)
|
val o = GrandExchangeOffer.fromQuery(results)
|
||||||
offers.add(o)
|
offers.add(o)
|
||||||
}
|
}
|
||||||
stmt.close()
|
stmt.close()
|
||||||
|
}
|
||||||
return offers
|
return offers
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBotOffers(): List<GrandExchangeOffer>
|
fun getBotOffers(): List<GrandExchangeOffer>
|
||||||
{
|
{
|
||||||
val conn = GEDB.connect()
|
|
||||||
val stmt = conn.createStatement()
|
|
||||||
val offers = ArrayList<GrandExchangeOffer>()
|
val offers = ArrayList<GrandExchangeOffer>()
|
||||||
|
|
||||||
|
GEDB.run { conn ->
|
||||||
|
val stmt = conn.createStatement()
|
||||||
|
|
||||||
val results = stmt.executeQuery("SELECT item_id,amount FROM bot_offers WHERE amount > 0")
|
val results = stmt.executeQuery("SELECT item_id,amount FROM bot_offers WHERE amount > 0")
|
||||||
while(results.next())
|
while (results.next()) {
|
||||||
{
|
|
||||||
val o = GrandExchangeOffer.fromBotQuery(results)
|
val o = GrandExchangeOffer.fromBotQuery(results)
|
||||||
offers.add(o)
|
offers.add(o)
|
||||||
}
|
}
|
||||||
stmt.close()
|
stmt.close()
|
||||||
|
}
|
||||||
return offers
|
return offers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,8 +95,7 @@ class GrandExchangeOffer() {
|
|||||||
|
|
||||||
fun update()
|
fun update()
|
||||||
{
|
{
|
||||||
val conn = GEDB.connect()
|
GEDB.run {conn ->
|
||||||
|
|
||||||
if(isBot)
|
if(isBot)
|
||||||
{
|
{
|
||||||
val stmt = conn.prepareStatement("UPDATE bot_offers SET amount = ? WHERE item_id = ?")
|
val stmt = conn.prepareStatement("UPDATE bot_offers SET amount = ? WHERE item_id = ?")
|
||||||
@@ -118,38 +117,35 @@ class GrandExchangeOffer() {
|
|||||||
stmt.close()
|
stmt.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Called when writing a brand new offer to the database. Should not be used under any other circumstance **/
|
/** Called when writing a brand new offer to the database. Should not be used under any other circumstance **/
|
||||||
fun writeNew()
|
fun writeNew()
|
||||||
{
|
{
|
||||||
val conn = GEDB.connect()
|
GEDB.run { conn ->
|
||||||
|
if (isBot) {
|
||||||
if(isBot)
|
|
||||||
{
|
|
||||||
val stmt = conn.createStatement()
|
val stmt = conn.createStatement()
|
||||||
val result = stmt.executeQuery("SELECT * from bot_offers where item_id = $itemID")
|
val result = stmt.executeQuery("SELECT * from bot_offers where item_id = $itemID")
|
||||||
val isExists = result.next()
|
val isExists = result.next()
|
||||||
|
|
||||||
if(isExists)
|
if (isExists) {
|
||||||
{
|
|
||||||
val oldAmount = result.getInt("amount")
|
val oldAmount = result.getInt("amount")
|
||||||
stmt.executeUpdate("UPDATE bot_offers set amount = ${oldAmount + amount} where item_id = $itemID")
|
stmt.executeUpdate("UPDATE bot_offers set amount = ${oldAmount + amount} where item_id = $itemID")
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
stmt.executeUpdate("INSERT INTO bot_offers(item_id,amount) values($itemID,$amount)")
|
stmt.executeUpdate("INSERT INTO bot_offers(item_id,amount) values($itemID,$amount)")
|
||||||
stmt.close()
|
stmt.close()
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
val stmt = conn.createStatement()
|
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) " +
|
stmt.executeUpdate(
|
||||||
"values($playerUID,$itemID,$amount,$offeredValue,${System.currentTimeMillis()},${offerState.ordinal},${if(sell) 1 else 0}, $index)")
|
"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()")
|
val nowuid = stmt.executeQuery("SELECT last_insert_rowid()")
|
||||||
uid = nowuid.getLong(1)
|
uid = nowuid.getLong(1)
|
||||||
visualize(player)
|
visualize(player)
|
||||||
stmt.close()
|
stmt.close()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encodeWithdraw() : String
|
private fun encodeWithdraw() : String
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer,
|
|||||||
|
|
||||||
override fun login(player: Player) {
|
override fun login(player: Player) {
|
||||||
val instance = GrandExchangeRecords(player)
|
val instance = GrandExchangeRecords(player)
|
||||||
instance.init()
|
|
||||||
player.setAttribute("ge-records", instance)
|
player.setAttribute("ge-records", instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,12 +50,12 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer,
|
|||||||
/**
|
/**
|
||||||
* Read offers from the database
|
* Read offers from the database
|
||||||
*/
|
*/
|
||||||
val conn = GEDB.connect()
|
val needsIndex = ArrayDeque<GrandExchangeOffer>()
|
||||||
val stmt = conn.createStatement()
|
|
||||||
val offer_records = stmt.executeQuery("SELECT * from player_offers where player_uid = ${player.details.uid} AND offer_state < 6")
|
|
||||||
val instance = getInstance(player)
|
val instance = getInstance(player)
|
||||||
|
|
||||||
val needsIndex = ArrayDeque<GrandExchangeOffer>()
|
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()) {
|
while (offer_records.next()) {
|
||||||
val offer = GrandExchangeOffer.fromQuery(offer_records)
|
val offer = GrandExchangeOffer.fromQuery(offer_records)
|
||||||
@@ -66,6 +65,7 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer,
|
|||||||
instance.offerRecords[offer.index] = OfferRecord(offer.uid, offer.index)
|
instance.offerRecords[offer.index] = OfferRecord(offer.uid, offer.index)
|
||||||
}
|
}
|
||||||
stmt.close()
|
stmt.close()
|
||||||
|
}
|
||||||
|
|
||||||
if (needsIndex.isNotEmpty()) {
|
if (needsIndex.isNotEmpty()) {
|
||||||
for ((index, offer) in offerRecords.withIndex()) {
|
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) {
|
override fun savePlayer(player: Player, save: JSONObject) {
|
||||||
@@ -128,14 +128,13 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer,
|
|||||||
|
|
||||||
fun visualizeRecords()
|
fun visualizeRecords()
|
||||||
{
|
{
|
||||||
val conn = GEDB.connect()
|
GEDB.run { conn ->
|
||||||
val stmt = conn.createStatement()
|
val stmt = conn.createStatement()
|
||||||
|
|
||||||
for (record in offerRecords) {
|
for (record in offerRecords) {
|
||||||
if (record != null) {
|
if (record != null) {
|
||||||
val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}")
|
val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}")
|
||||||
if(offer_raw.next())
|
if (offer_raw.next()) {
|
||||||
{
|
|
||||||
val offer = GrandExchangeOffer.fromQuery(offer_raw)
|
val offer = GrandExchangeOffer.fromQuery(offer_raw)
|
||||||
if (offer.offerState == OfferState.REMOVED) continue
|
if (offer.offerState == OfferState.REMOVED) continue
|
||||||
offer.index = record.slot
|
offer.index = record.slot
|
||||||
@@ -145,6 +144,7 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer,
|
|||||||
}
|
}
|
||||||
stmt.close()
|
stmt.close()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getOffer(index: Int) : GrandExchangeOffer?
|
fun getOffer(index: Int) : GrandExchangeOffer?
|
||||||
{
|
{
|
||||||
@@ -155,18 +155,19 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer,
|
|||||||
fun getOffer(record: OfferRecord?) : GrandExchangeOffer?
|
fun getOffer(record: OfferRecord?) : GrandExchangeOffer?
|
||||||
{
|
{
|
||||||
record ?: return null
|
record ?: return null
|
||||||
val conn = GEDB.connect()
|
var offerToReturn: GrandExchangeOffer? = null
|
||||||
|
GEDB.run { conn ->
|
||||||
val stmt = conn.createStatement()
|
val stmt = conn.createStatement()
|
||||||
val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}")
|
val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}")
|
||||||
if(offer_raw.next())
|
if (offer_raw.next()) {
|
||||||
{
|
|
||||||
val offer = GrandExchangeOffer.fromQuery(offer_raw)
|
val offer = GrandExchangeOffer.fromQuery(offer_raw)
|
||||||
offer.index = record.slot
|
offer.index = record.slot
|
||||||
stmt.close()
|
stmt.close()
|
||||||
return offer
|
offerToReturn = offer
|
||||||
}
|
}
|
||||||
stmt.close()
|
stmt.close()
|
||||||
return null
|
}
|
||||||
|
return offerToReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,6 @@ package rs09.game.interaction.inter.ge
|
|||||||
import api.*
|
import api.*
|
||||||
import core.game.component.Component
|
import core.game.component.Component
|
||||||
import core.game.container.access.BitregisterAssembler
|
import core.game.container.access.BitregisterAssembler
|
||||||
import core.game.ge.GrandExchangeDatabase
|
|
||||||
import core.game.ge.GrandExchangeEntry
|
|
||||||
import core.game.ge.OfferState
|
import core.game.ge.OfferState
|
||||||
import core.game.node.entity.player.Player
|
import core.game.node.entity.player.Player
|
||||||
import core.game.node.entity.player.link.audio.Audio
|
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.GrandExchange
|
||||||
import rs09.game.ge.GrandExchangeOffer
|
import rs09.game.ge.GrandExchangeOffer
|
||||||
import rs09.game.ge.GrandExchangeRecords
|
import rs09.game.ge.GrandExchangeRecords
|
||||||
|
import rs09.game.ge.PriceIndex
|
||||||
import rs09.game.interaction.InterfaceListener
|
import rs09.game.interaction.InterfaceListener
|
||||||
import rs09.game.system.SystemLogger
|
import rs09.game.system.SystemLogger
|
||||||
|
|
||||||
@@ -69,8 +68,7 @@ class StockMarket : InterfaceListener {
|
|||||||
var id = item.id
|
var id = item.id
|
||||||
if(!item.definition.isUnnoted)
|
if(!item.definition.isUnnoted)
|
||||||
id = item.noteChange
|
id = item.noteChange
|
||||||
val itemDb = GrandExchangeDatabase.getDatabase()[id]
|
if(!PriceIndex.canTrade(id))
|
||||||
if(itemDb == null || !item.definition.isTradeable)
|
|
||||||
{
|
{
|
||||||
sendMessage(player, "This item can't be sold on the Grand Exchange.")
|
sendMessage(player, "This item can't be sold on the Grand Exchange.")
|
||||||
return@on true
|
return@on true
|
||||||
@@ -316,11 +314,10 @@ class StockMarket : InterfaceListener {
|
|||||||
fun updateVarbits(player: Player, offer: GrandExchangeOffer?, index: Int, sale: Boolean? = null)
|
fun updateVarbits(player: Player, offer: GrandExchangeOffer?, index: Int, sale: Boolean? = null)
|
||||||
{
|
{
|
||||||
val isSale = sale ?: offer?.sell ?: false
|
val isSale = sale ?: offer?.sell ?: false
|
||||||
val entry: GrandExchangeEntry? = GrandExchangeDatabase.getDatabase()[offer?.itemID ?: -1]
|
|
||||||
var lowPrice = 0
|
var lowPrice = 0
|
||||||
var highPrice = 0
|
var highPrice = 0
|
||||||
val recommendedPrice = GrandExchange.getRecommendedPrice(entry?.itemId ?: 0)
|
val recommendedPrice = GrandExchange.getRecommendedPrice(offer?.itemID ?: 0)
|
||||||
if (entry != null) {
|
if (PriceIndex.canTrade(offer?.itemID ?: 0)) {
|
||||||
lowPrice = (recommendedPrice * 0.95).toInt()
|
lowPrice = (recommendedPrice * 0.95).toInt()
|
||||||
highPrice = (recommendedPrice * 1.05).toInt()
|
highPrice = (recommendedPrice * 1.05).toInt()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -570,7 +570,7 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){
|
|||||||
val offerAmounts = HashMap<Int,Int>()
|
val offerAmounts = HashMap<Int,Int>()
|
||||||
val offerPrice = HashMap<Int,Int>()
|
val offerPrice = HashMap<Int,Int>()
|
||||||
|
|
||||||
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)
|
for(offer in offers)
|
||||||
{
|
{
|
||||||
@@ -682,7 +682,7 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun showOffers(player: Player, searchTerm: String){
|
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<Int, Int>()
|
val buyingAmount = HashMap<Int, Int>()
|
||||||
val buyingHighest = HashMap<Int, Int>()
|
val buyingHighest = HashMap<Int, Int>()
|
||||||
val sellingAmount = HashMap<Int,Int>()
|
val sellingAmount = HashMap<Int,Int>()
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ object ServerConfigParser {
|
|||||||
ServerConstants.CELEDT_DATA_PATH = data.getPath("paths.cele_drop_table_path")
|
ServerConstants.CELEDT_DATA_PATH = data.getPath("paths.cele_drop_table_path")
|
||||||
ServerConstants.SERVER_GE_NAME = data.getString("world.name_ge") ?: ServerConstants.SERVER_NAME
|
ServerConstants.SERVER_GE_NAME = data.getString("world.name_ge") ?: ServerConstants.SERVER_NAME
|
||||||
ServerConstants.RULES_AND_INFO_ENABLED = data.getBoolean("world.show_rules", true)
|
ServerConstants.RULES_AND_INFO_ENABLED = data.getBoolean("world.show_rules", true)
|
||||||
|
ServerConstants.BOTS_INFLUENCE_PRICE_INDEX = data.getBoolean("world.bots_influence_ge_price", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,18 +8,20 @@ import rs09.game.ge.GEDB
|
|||||||
import rs09.game.ge.GrandExchange
|
import rs09.game.ge.GrandExchange
|
||||||
import rs09.game.ge.GrandExchangeOffer
|
import rs09.game.ge.GrandExchangeOffer
|
||||||
import rs09.game.system.SystemLogger
|
import rs09.game.system.SystemLogger
|
||||||
|
import java.io.File
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS) class ExchangeTests {
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS) class ExchangeTests {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TEST_DB_PATH = "ge_test.db"
|
private const val TEST_DB_PATH = "ge_test.db"
|
||||||
init {
|
init {
|
||||||
|
TestUtils.preTestSetup()
|
||||||
GEDB.init(TEST_DB_PATH)
|
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 offer = GrandExchangeOffer()
|
||||||
val uid = "test ${System.currentTimeMillis()}".hashCode()
|
val uid = username.hashCode()
|
||||||
offer.offerState = OfferState.REGISTERED
|
offer.offerState = OfferState.REGISTERED
|
||||||
offer.itemID = itemId
|
offer.itemID = itemId
|
||||||
offer.offeredValue = price
|
offer.offeredValue = price
|
||||||
@@ -32,14 +34,18 @@ import kotlin.random.Random
|
|||||||
offer.writeNew()
|
offer.writeNew()
|
||||||
return offer
|
return offer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AfterAll fun cleanup() {
|
||||||
|
File(TEST_DB_PATH).delete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun testPlaceOffer() {
|
@Test fun testPlaceOffer() {
|
||||||
val offer = generateOffer(4151, 1, 100000, true)
|
val offer = generateOffer(4151, 1, 100000, true)
|
||||||
val uid = offer.playerUID
|
val uid = offer.playerUID
|
||||||
|
|
||||||
with (GEDB.connect()) {
|
GEDB.run { conn ->
|
||||||
val stmt = this.createStatement()
|
val stmt = conn.createStatement()
|
||||||
val result = stmt.executeQuery("select * from player_offers where player_uid = $uid")
|
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!")
|
val thisOffer = if(result.next()) GrandExchangeOffer.fromQuery(result) else fail("Offer did not exist!")
|
||||||
Assertions.assertEquals(offer.itemID, thisOffer.itemID)
|
Assertions.assertEquals(offer.itemID, thisOffer.itemID)
|
||||||
@@ -66,4 +72,32 @@ import kotlin.random.Random
|
|||||||
GrandExchange.exchange(sellOffer,buyOffer)
|
GrandExchange.exchange(sellOffer,buyOffer)
|
||||||
Assertions.assertEquals(null, buyOffer.withdraw[0]) //Buyer should not get any items, sell offer is higher
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ max_adv_bots = 100
|
|||||||
wild_pvp_enabled = false
|
wild_pvp_enabled = false
|
||||||
jad_practice_enabled = true
|
jad_practice_enabled = true
|
||||||
personalized_shops = true
|
personalized_shops = true
|
||||||
|
bots_influence_ge_price = true
|
||||||
|
|
||||||
[paths]
|
[paths]
|
||||||
#path to the data folder, which contains the cache subfolder and such
|
#path to the data folder, which contains the cache subfolder and such
|
||||||
|
|||||||
Reference in New Issue
Block a user