Repository reorganisation
Unified kotlin and java into just src/main Unified the rs09 and core packages Took all content out of the core package, and placed it into the new content package Reorganised all source code relating to content to be easier to find and explore
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import org.rs09.consts.Items
|
||||
|
||||
class BotPrices {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getPrice(id: Int): Int {
|
||||
return getPriceOverrides(id) ?: ItemDefinition.forId(id).value
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun getPriceOverrides(id: Int): Int? {
|
||||
return when (id) {
|
||||
Items.PURE_ESSENCE_7936 -> 50
|
||||
Items.BOW_STRING_1777 -> 250
|
||||
Items.MAGIC_LOGS_1513 -> 750
|
||||
Items.COWHIDE_1739 -> 250
|
||||
Items.DRAGON_BONES_536 -> 1250
|
||||
Items.GREEN_DRAGONHIDE_1753 -> 550
|
||||
Items.GRIMY_RANARR_207 -> 1214
|
||||
Items.GRIMY_AVANTOE_211 -> 453
|
||||
Items.GRIMY_CADANTINE_215 -> 232
|
||||
Items.GRIMY_DWARF_WEED_217 -> 86
|
||||
Items.GRIMY_GUAM_199 -> 50
|
||||
Items.GRIMY_HARRALANDER_205 -> 115
|
||||
Items.GRIMY_IRIT_209 -> 860
|
||||
Items.GRIMY_KWUARM_213 -> 334
|
||||
Items.GRIMY_LANTADYME_2485 -> 115
|
||||
Items.GRIMY_MARRENTILL_201 -> 250
|
||||
Items.LOBSTER_379 -> 268
|
||||
Items.RAW_LOBSTER_377 -> 265
|
||||
Items.LOOP_HALF_OF_A_KEY_987 -> 5250
|
||||
Items.TOOTH_HALF_OF_A_KEY_985 -> 4263
|
||||
Items.SWORDFISH_373 -> 400
|
||||
Items.RAW_SWORDFISH_371 -> 390
|
||||
Items.SHARK_385 -> 720
|
||||
Items.RAW_SHARK_383 -> 710
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.api.StartupListener
|
||||
import org.json.simple.JSONArray
|
||||
import org.json.simple.JSONObject
|
||||
import org.json.simple.parser.JSONParser
|
||||
import core.ServerConstants
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
|
||||
class GEAutoStock : StartupListener {
|
||||
override fun startup() {
|
||||
autostock()
|
||||
}
|
||||
|
||||
companion object {
|
||||
// autostock format should be identical to the botoffers json format.
|
||||
private val DB_PATH = "data" + File.separator + "eco" + File.separator + "autostock.json"
|
||||
|
||||
fun autostock() {
|
||||
if(ServerConstants.GE_AUTOSTOCK_ENABLED) {
|
||||
val parser = JSONParser()
|
||||
val botReader: FileReader? = FileReader(DB_PATH)
|
||||
val botSave = parser.parse(botReader) as JSONObject
|
||||
if (botSave.containsKey("offers")) {
|
||||
val offers = botSave["offers"] as JSONArray
|
||||
for (offer in offers) {
|
||||
val o = offer as JSONObject
|
||||
GrandExchange.addBotOffer(o["item"].toString().toInt(), o["qty"].toString().toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import org.json.simple.JSONArray
|
||||
import org.json.simple.JSONObject
|
||||
import org.json.simple.parser.JSONParser
|
||||
import org.sqlite.SQLiteDataSource
|
||||
import core.ServerConstants
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.sql.Connection
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
/**
|
||||
* Collection of methods for interacting with the grand exchange databases
|
||||
* @author Ceikry
|
||||
*/
|
||||
object GEDB {
|
||||
private var pathString = ""
|
||||
public var connection: Connection? = null
|
||||
private var initialized = false
|
||||
private var connectionRefs = 0
|
||||
private var obtainConnectionLock = ReentrantLock()
|
||||
private var dbRunLock = ReentrantLock()
|
||||
|
||||
fun init() {
|
||||
init(File(ServerConstants.GRAND_EXCHANGE_DATA_PATH + "grandexchange.db").absolutePath)
|
||||
}
|
||||
|
||||
//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()
|
||||
}
|
||||
|
||||
@JvmStatic fun run(closure: (conn: Connection) -> Unit) {
|
||||
dbRunLock.tryLock(10000L, TimeUnit.MILLISECONDS)
|
||||
|
||||
connectionRefs++
|
||||
val con = connect()
|
||||
closure.invoke(con)
|
||||
connectionRefs--
|
||||
|
||||
if(connectionRefs == 0) {
|
||||
con.close()
|
||||
}
|
||||
|
||||
dbRunLock.unlock()
|
||||
}
|
||||
|
||||
private fun connect(): Connection
|
||||
{
|
||||
obtainConnectionLock.tryLock(10000L, TimeUnit.MILLISECONDS)
|
||||
|
||||
if (connection == null || connection!!.isClosed)
|
||||
{
|
||||
val ds = SQLiteDataSource()
|
||||
ds.url = "jdbc:sqlite:$pathString"
|
||||
connection = ds.connection
|
||||
}
|
||||
|
||||
obtainConnectionLock.unlock()
|
||||
return connection!!
|
||||
}
|
||||
|
||||
private fun convertJsonArray(arr: JSONArray): String
|
||||
{
|
||||
val sb = StringBuilder()
|
||||
for ((index, data) in arr.withIndex())
|
||||
{
|
||||
val item = data as JSONObject
|
||||
sb.append("$index")
|
||||
sb.append(",")
|
||||
sb.append(item["id"])
|
||||
sb.append(",")
|
||||
sb.append(item["amount"])
|
||||
if(index + 1 < arr.size)
|
||||
sb.append(":")
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun generateAndTransfer()
|
||||
{
|
||||
val conn = connect() //for sqlite jdbc, attempting to connect to a non-existing .db file creates it.
|
||||
val statement = conn.createStatement()
|
||||
|
||||
//table for tracking player offers - replaces offer_dispatch.json
|
||||
statement.execute(
|
||||
"CREATE TABLE player_offers(" +
|
||||
"uid INTEGER PRIMARY KEY ASC," +
|
||||
"player_uid INTEGER," +
|
||||
"item_id INTEGER," +
|
||||
"amount_total INTEGER," +
|
||||
"amount_complete INTEGER," +
|
||||
"offered_value INTEGER," +
|
||||
"time_stamp INTEGER," +
|
||||
"offer_state INTEGER," +
|
||||
"is_sale INTEGER," +
|
||||
"withdraw_items STRING ," +
|
||||
"slot_index INTEGER," +
|
||||
"total_coin_xc INTEGER)"
|
||||
)
|
||||
|
||||
//table for tracking bot offers - replaces bot_offers.json
|
||||
statement.execute(
|
||||
"CREATE TABLE bot_offers(" +
|
||||
"item_id INTEGER," +
|
||||
"amount INTEGER)"
|
||||
)
|
||||
|
||||
//table for tracking price index data - replaces gedb.xml
|
||||
statement.execute(
|
||||
"CREATE TABLE price_index(" +
|
||||
"item_id INTEGER," +
|
||||
"value INTEGER," +
|
||||
"total_value INTEGER," +
|
||||
"unique_trades INTEGER," +
|
||||
"last_update INTEGER)"
|
||||
)
|
||||
|
||||
//check if the old .json and .xml files exist, and if they do, read them into the sqlite database and remove them.
|
||||
val playerJson = ServerConstants.GRAND_EXCHANGE_DATA_PATH + "offer_dispatch.json"
|
||||
val botJson = ServerConstants.GRAND_EXCHANGE_DATA_PATH + "bot_offers.json"
|
||||
|
||||
if(File(playerJson).exists())
|
||||
{
|
||||
val parser = JSONParser()
|
||||
val reader = FileReader(playerJson)
|
||||
val saveFile = parser.parse(reader) as JSONObject
|
||||
|
||||
if (saveFile.containsKey("offers")) {
|
||||
val offers = saveFile["offers"] as JSONArray
|
||||
|
||||
for (offer in offers) {
|
||||
val o = offer as JSONObject
|
||||
statement.execute("insert into player_offers(player_uid,item_id,amount_total,amount_complete,offered_value,time_stamp,offer_state,is_sale,withdraw_items,total_coin_xc,slot_index) " +
|
||||
"values(" +
|
||||
"${o["playerUID"]}," +
|
||||
"${o["itemId"]}," +
|
||||
"${o["amount"]}," +
|
||||
"${o["completedAmount"]}," +
|
||||
"${o["offeredValue"]}," +
|
||||
"${o["timeStamp"]}," +
|
||||
"${o["offerState"]}," +
|
||||
"${if (o["sale"] as Boolean) 1 else 0}," +
|
||||
"'" + convertJsonArray(o["withdrawItems"] as JSONArray) + "'," +
|
||||
"${o["totalCoinExchange"]}," +
|
||||
"-1)"
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
reader.close()
|
||||
//File(playerJson).delete()
|
||||
}
|
||||
|
||||
if(File(botJson).exists())
|
||||
{
|
||||
val parser = JSONParser()
|
||||
val reader = FileReader(botJson)
|
||||
val saveFile = parser.parse(reader) as JSONObject
|
||||
|
||||
if (saveFile.containsKey("offers")) {
|
||||
val offers = saveFile["offers"] as JSONArray
|
||||
|
||||
for (offer in offers) {
|
||||
val o = offer as JSONObject
|
||||
statement.execute("insert into bot_offers " +
|
||||
"values(${o["item"]},${o["qty"]})")
|
||||
}
|
||||
}
|
||||
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){
|
||||
PriceIndex.allowItem(def.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package core.game.ge;
|
||||
|
||||
import core.game.component.Component;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.tools.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents the glass used to open the guide prices for the different type of
|
||||
* trade(herbs, logs, runes, etc...)
|
||||
* @author 'Vexia
|
||||
* @date 30/11/2013
|
||||
*/
|
||||
public final class GEGuidePrice {
|
||||
|
||||
/**
|
||||
* Represents the guide price component.
|
||||
*/
|
||||
private static final Component COMPONENT = new Component(642);
|
||||
|
||||
/**
|
||||
* Method used to open a Grand Exchange guide price type.
|
||||
* @param player the player.
|
||||
* @param type the type.
|
||||
*/
|
||||
public static final void open(final Player player, final GuideType type) {
|
||||
player.getInterfaceManager().open(COMPONENT);
|
||||
type.display(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to clear the current items being showed.
|
||||
* @param player the player.
|
||||
*/
|
||||
public static final void clear(final Player player) {
|
||||
for (int i = 135; i < 165; i++) {
|
||||
player.getPacketDispatch().sendInterfaceConfig(COMPONENT.getId(), i, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a guide item shown on the Guide Price interface.
|
||||
* @author 'Vexia
|
||||
* @date 01/12/2013
|
||||
*/
|
||||
public static class GuideItem {
|
||||
|
||||
/**
|
||||
* Represents the item of this type.
|
||||
*/
|
||||
private final int item;
|
||||
|
||||
/**
|
||||
* Represents the unlock child.
|
||||
*/
|
||||
private final int[] childData;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code GEGuidePrice} {@code Object}.
|
||||
* @param item the item.
|
||||
* @param childData the child data.
|
||||
*/
|
||||
public GuideItem(final int item, final int... childData) {
|
||||
this.item = item;
|
||||
this.childData = childData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item.
|
||||
* @return The item.
|
||||
*/
|
||||
public int getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the childData.
|
||||
* @return The childData.
|
||||
*/
|
||||
public int[] getChildData() {
|
||||
return childData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a guide price type of trade(herbs, logs, etc...)
|
||||
* @author 'Vexia
|
||||
* @date 30/11/2013
|
||||
*/
|
||||
public enum GuideType {
|
||||
LOGS(new int[] { 0, 0 }, new GuideItem(1511, 155), new GuideItem(2862, 156), new GuideItem(1521, 157), new GuideItem(1519, 158), new GuideItem(6333, 159), new GuideItem(1517, 160), new GuideItem(6332, 161), new GuideItem(12581, 162), new GuideItem(1515, 163), new GuideItem(1513, 164)), ORES(new int[] { 40, 44 }, new GuideItem(436, 33), new GuideItem(438, 34), new GuideItem(440, 35), new GuideItem(442, 36), new GuideItem(453, 37), new GuideItem(444, 38), new GuideItem(447, 39), new GuideItem(449, 40), new GuideItem(451, 41)), RUNES(new int[] { 215, 216 }, new GuideItem(1436, 183), new GuideItem(7936, 184), new GuideItem(556, 185), new GuideItem(558, 186), new GuideItem(555, 187), new GuideItem(557, 188), new GuideItem(554, 189), new GuideItem(559, 190), new GuideItem(564, 191), new GuideItem(562, 192), new GuideItem(9075, 193), new GuideItem(561, 194), new GuideItem(563, 195), new GuideItem(560, 196), new GuideItem(565, 197), new GuideItem(566, 198)), HERBS(new int[] { 130, 135 }, new GuideItem(249, 119), new GuideItem(251, 120), new GuideItem(253, 121), new GuideItem(255, 122), new GuideItem(257, 123), new GuideItem(2998, 124), new GuideItem(259, 125), new GuideItem(12172, 126), new GuideItem(261, 127), new GuideItem(263, 128), new GuideItem(3000, 129), new GuideItem(265, 130), new GuideItem(2481, 131), new GuideItem(267, 132), new GuideItem(269, 133)), WEAPONS_AND_ARMOUR(new int[] { 88, 89 }, new GuideItem(11834, 73), new GuideItem(11838, 74), new GuideItem(11842, 75), new GuideItem(11864, 76), new GuideItem(11870, 77), new GuideItem(11846, 78), new GuideItem(11848, 79), new GuideItem(11850, 80), new GuideItem(11856, 81), new GuideItem(11732, 82), new GuideItem(4151, 83), new GuideItem(11235, 84), new GuideItem(6739, 85), new GuideItem(4587, 86), new GuideItem(4153, 87));
|
||||
|
||||
/**
|
||||
* Constructs a new {@code GEGuidePrice} {@code Object}.
|
||||
* @param childData the childData.
|
||||
* @param items the guide items.
|
||||
*/
|
||||
GuideType(final int[] childData, final GuideItem... items) {
|
||||
this.childData = childData;
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the guide items.
|
||||
*/
|
||||
private final GuideItem[] items;
|
||||
|
||||
/**
|
||||
* Represents the child data for the guide type.
|
||||
*/
|
||||
private final int childData[];
|
||||
|
||||
/**
|
||||
* Gets the items.
|
||||
* @return The items.
|
||||
*/
|
||||
public GuideItem[] getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the childData.
|
||||
* @return The childData.
|
||||
*/
|
||||
public int[] getChildData() {
|
||||
return childData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to display the guide type.
|
||||
* @param player the player.
|
||||
*/
|
||||
public void display(final Player player) {
|
||||
player.setAttribute("guide-price", this);
|
||||
if (this != LOGS) {
|
||||
clear(player);
|
||||
}
|
||||
player.getPacketDispatch().sendString("Guide Prices: " + StringUtils.formatDisplayName(name()), COMPONENT.getId(), 14);
|
||||
for (int i = getChildData()[0]; i < getChildData()[1]; i++) {
|
||||
player.getPacketDispatch().sendInterfaceConfig(642, i, false);
|
||||
}
|
||||
for (GuideItem item : getItems()) {
|
||||
player.getPacketDispatch().sendString("" + GrandExchange.getRecommendedPrice(item.item, false) + " gp", COMPONENT.getId(), item.getChildData()[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package core.game.ge;
|
||||
|
||||
import core.game.node.item.Item;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Holds the Grand Exchange item sets.
|
||||
* @author Emperor
|
||||
*/
|
||||
public enum GEItemSet {
|
||||
|
||||
BRONZE_L(11814, 1155, 1117, 1075, 1189), BRONZE_SK(11816, 1155, 1117, 1087, 1189), IRON_L(11818, 1153, 1115, 1067, 1191), IRON_SK(11820, 1153, 1115, 1081, 1191), STEEL_L(11822, 1157, 1119, 1069, 1193), STEEL_SK(11824, 1157, 1119, 1083, 1193), BLACK_L(11826, 1165, 1125, 1077, 1195), BLACK_SK(11828, 1165, 1125, 1089, 1195), MITHRIL_L(11830, 1159, 1121, 1071, 1197), MITHRIL_SK(11832, 1159, 1121, 1085, 1197), ADAMANT_L(11834, 1161, 1123, 1073, 1199), ADAMANT_SK(11836, 1161, 1123, 1091, 1199), RUNE_L(11838, 1163, 1127, 1079, 1201), RUNE_SK(11840, 1163, 1127, 1093, 1201), DRAGON_L(11842, 1149, 3140, 4087), DRAGON_SK(11844, 1149, 3140, 4585),
|
||||
// NULL_1(-1), //This would create the spaces between the sets (uncomment
|
||||
// when needed)
|
||||
AHRIMS(11846, 4708, 4712, 4714, 4710), DHAROKS(11848, 4716, 4720, 4722, 4718), GUTHANS(11850, 4724, 4728, 4730, 4726), KARILS(11852, 4732, 4736, 4738, 4734), TORAGS(11854, 4745, 4749, 4751, 4747), VERACS(11856, 4753, 4757, 4759, 4755), THIRD_AGE_MELEE(11858, 10350, 10348, 10346, 10352), THIRD_AGE_RANGE(11860, 10334, 10330, 10332, 10336), THIRD_AGE_MAGE(11862, 10342, 10338, 10340, 10344), GREEN_DHIDE(11864, 1135, 1099, 1065), BLUE_DHIDE(11866, 2499, 2493, 2487), RED_DHIDE(11868, 2501, 2495, 2489), BLACK_DHIDE(11870, 2503, 2497, 2491), MYSTIC(11872, 4089, 4091, 4093, 4095, 4097), LIGHT_MYSTIC(11960, 4109, 4111, 4113, 4115, 4117), DARK_MYSTIC(11962, 4099, 4101, 4103, 4105, 4107), INFINITY(11874, 6918, 6916, 6924, 6922, 6920), SPLITBARK(11876, 3385, 3387, 3389, 3391, 3393), BLACK_TRIMMED_L(11878, 2587, 2583, 2585, 2589), BLACK_TRIMMED_SK(11880, 2587, 2583, 3472, 2589), BLACK_GOLD_TRIMMED_L(11882, 2595, 2591, 2593, 2597), BLACK_GOLD_TRIMMED_SK(11884, 2595, 2591, 3473, 2597), ADAMANT_TRIMMED_L(11886, 2605, 2599, 2601, 2603), ADAMANT_TRIMMED_SK(11888, 2605, 2599, 3474), ADAMANT_GOLD_TRIMMED_L(11890, 2613, 2607, 2609, 2611), ADAMANT_GOLD_TRIMMED_SK(11892, 2613, 3475, 2611), RUNE_TRIMMED_L(11894, 2627, 2623, 2625, 2629), RUNE_TRIMMED_SK(11896, 2627, 2623, 3477, 2629), RUNE_GOLD_TRIMMED_L(11898, 2619, 2615, 2617, 2621), RUNE_GOLD_TRIMMED_SK(11900, 2619, 2615, 3476, 2621), ENCHANTED(11902, 7400, 7399, 7398), TRIMMED_BLUE_WIZARD(11904, 7396, 7392, 7388), GOLD_TRIMMED_BLUE_WIZARD(11906, 7394, 7390, 7386), TRIMMED_LEATHER(11908, 7364, 7368), GOLD_TRIMMED_LEATHER(11910, 7362, 7366), GREEN_DHIDE_T(11912, 7372, 7380), GREEN_DHIDE_G(11914, 7370, 7378), BLUE_DHIDE_T(11916, 7376, 7384), BLUE_DHIDE_G(11918, 7374, 7382), GREEN_DHIDE_BLESSED(11920, 10382, 10378, 10380, 10376), BLUE_DHIDE_BLESSED(11922, 10390, 10386, 10388, 10384), RED_DHIDE_BLESSED(11924, 10374, 10370, 10372, 10368), GUTHIX_L(11926, 2673, 2669, 2671, 2675), SARADOMIN_L(11928, 2665, 2661, 2663, 2667), ZAMORAK_L(11930, 2657, 2653, 2655, 2659), GUTHIX_SK(11932, 2673, 2669, 3480, 2675), SARADOMIN_SK(11934, 2665, 2661, 3479, 2667), ZAMORAK_SK(11936, 2657, 2653, 3478, 2659), GILDED_L(11938, 3486, 3481, 3483, 3488), GILDED_SK(11940, 3486, 3481, 3485, 3488), ROCKSHELL(11942, 6128, 6129, 6130, 6151, 6145), SPINED(11944, 6131, 6133, 6135, 6149, 6143), SKELETAL(11946, 6137, 6139, 6141, 6153, 6147), CANNON(11967, 6, 8, 10, 12);
|
||||
|
||||
/**
|
||||
* The item sets mapping.
|
||||
*/
|
||||
private static final Map<Integer, GEItemSet> ITEM_SETS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The items array.
|
||||
*/
|
||||
private static Item[] itemArray;
|
||||
|
||||
/**
|
||||
* Populate the mapping.
|
||||
*/
|
||||
static {
|
||||
itemArray = new Item[values().length];
|
||||
for (int i = 0; i < itemArray.length; i++) {
|
||||
GEItemSet set = values()[i];
|
||||
itemArray[i] = set.itemId == -1 ? new Item() : new Item(set.itemId);
|
||||
ITEM_SETS.put(set.itemId, set);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item set for the given id.
|
||||
* @param setId The set item id.
|
||||
* @return The item set object.
|
||||
*/
|
||||
public static GEItemSet forId(int setId) {
|
||||
return ITEM_SETS.get(setId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The item id.
|
||||
*/
|
||||
private int itemId;
|
||||
|
||||
/**
|
||||
* The components.
|
||||
*/
|
||||
private int[] components;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code GEItemSet} {@code Object}.
|
||||
* @param itemId The item id.
|
||||
* @param components The components.
|
||||
*/
|
||||
private GEItemSet(int itemId, int... components) {
|
||||
this.itemId = itemId;
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the itemId.
|
||||
* @return The itemId.
|
||||
*/
|
||||
public int getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the components.
|
||||
* @return The components.
|
||||
*/
|
||||
public int[] getComponents() {
|
||||
return Arrays.copyOf(components, components.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item array.
|
||||
* @return The item array.
|
||||
*/
|
||||
public static Item[] getItemArray() {
|
||||
return Arrays.copyOf(itemArray, itemArray.length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.api.*
|
||||
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 core.game.system.task.Pulse
|
||||
import core.ServerConstants
|
||||
import core.tools.SystemLogger
|
||||
import core.game.system.command.Privilege
|
||||
import core.game.system.config.ItemConfigParser
|
||||
import core.game.world.GameWorld
|
||||
import core.game.world.repository.Repository
|
||||
import core.tools.colorize
|
||||
import java.lang.Integer.max
|
||||
import java.util.concurrent.LinkedBlockingDeque
|
||||
|
||||
/**
|
||||
* Handles the exchanging of offers, offer update thread, etc.
|
||||
* @author Ceikry
|
||||
*/
|
||||
class GrandExchange : StartupListener, Commands {
|
||||
|
||||
/**
|
||||
* Fallback safety check to make sure we don't start the GE twice under any circumstance
|
||||
*/
|
||||
var isRunning = false
|
||||
|
||||
/**
|
||||
* Initializes the offer manager and spawns an update thread.
|
||||
* @param local whether or not the GE should be the local in-code server rather than some hypothetical remote implementation.
|
||||
*/
|
||||
fun boot(){
|
||||
if(isRunning) return
|
||||
|
||||
SystemLogger.logGE("Initializing GE Update Worker")
|
||||
|
||||
Thread {
|
||||
Thread.currentThread().name = "GE Update Worker"
|
||||
while(true) {
|
||||
var offer = pendingOffers.takeFirst()
|
||||
offer.writeNew()
|
||||
offer = getOfferByUid(offer.uid) ?: continue
|
||||
selectPotentialMatches(offer).asSequence()
|
||||
.sortedBy { if (offer.sell) -it.offeredValue else it.offeredValue }
|
||||
.filter { if (offer.sell) it.offeredValue >= offer.offeredValue else it.offeredValue <= offer.offeredValue }
|
||||
.forEach { match -> exchange(offer, match) }
|
||||
if (!offer.isBot && offer.amountLeft > 0 && !offer.sell)
|
||||
tryExchangeWithBots(offer)
|
||||
}
|
||||
}.start()
|
||||
|
||||
isRunning = true
|
||||
}
|
||||
|
||||
|
||||
private fun tryExchangeWithBots(offer: GrandExchangeOffer) {
|
||||
GEDB.run { conn ->
|
||||
val query = conn.prepareStatement(GET_MATCH_FROM_BOT_OFFERS)
|
||||
query.setInt(1, offer.itemID)
|
||||
val res = query.executeQuery()
|
||||
|
||||
if (res.next()) {
|
||||
exchange(offer, GrandExchangeOffer.fromBotQuery(res).also { it.timeStamp = offer.timeStamp - 1L })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectPotentialMatches(offer: GrandExchangeOffer): List<GrandExchangeOffer> {
|
||||
val matches = ArrayList<GrandExchangeOffer>()
|
||||
GEDB.run { conn ->
|
||||
val query = conn.prepareStatement(GET_MATCHES_FROM_PLAYER_OFFERS)
|
||||
query.setInt(1, offer.itemID)
|
||||
query.setBoolean(2, !offer.sell)
|
||||
val res = query.executeQuery()
|
||||
while (res.next()) {
|
||||
matches.add(GrandExchangeOffer.fromQuery(res))
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
override fun defineCommands() {
|
||||
define("addbotoffer", Privilege.ADMIN) {player, strings ->
|
||||
val id = strings[1].toInt()
|
||||
val amount = strings[2].toInt()
|
||||
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.")
|
||||
}
|
||||
|
||||
define("geprivacy", Privilege.STANDARD) {player, _ ->
|
||||
val current = getAttribute(player, "ge-exclude", false)
|
||||
val new = !current
|
||||
notify(player, "Your name is now ${if (new) colorize("%RHIDDEN") else colorize("%RSHOWN")}.")
|
||||
setAttribute(player, "/save:ge-exclude", new)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val pendingOffers = LinkedBlockingDeque<GrandExchangeOffer>()
|
||||
private val GET_SPECIFIC_OFFER_BY_UID = "SELECT * FROM player_offers WHERE uid = ?;"
|
||||
private val GET_MATCHES_FROM_PLAYER_OFFERS = "SELECT * FROM player_offers WHERE item_id = ? AND is_sale = ? AND offer_state < 4 AND NOT offer_state = 2;"
|
||||
private val GET_MATCH_FROM_BOT_OFFERS = "SELECT * FROM bot_offers WHERE item_id = ?;"
|
||||
|
||||
private fun getOfferByUid(uid: Long): GrandExchangeOffer? {
|
||||
var offer: GrandExchangeOffer? = null
|
||||
GEDB.run { conn ->
|
||||
val query = conn.prepareStatement(GET_SPECIFIC_OFFER_BY_UID)
|
||||
query.setLong(1, uid)
|
||||
val res = query.executeQuery()
|
||||
|
||||
if (res.next())
|
||||
offer = GrandExchangeOffer.fromQuery(res)
|
||||
}
|
||||
return offer
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getRecommendedPrice(itemID: Int, from_bot: Boolean = false): Int {
|
||||
var base = max(PriceIndex.getValue(itemID), getItemDefPrice(itemID))
|
||||
if (from_bot) base = (max(BotPrices.getPrice(itemID), base) * 1.10).toInt()
|
||||
return base
|
||||
}
|
||||
|
||||
private fun getItemDefPrice(itemID: Int): Int {
|
||||
return max(itemDefinition(itemID).getConfiguration(ItemConfigParser.GE_PRICE) ?: 0, itemDefinition(itemID).value)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOfferStats(itemID: Int, sale: Boolean) : String
|
||||
{
|
||||
val sb = StringBuilder()
|
||||
|
||||
GEDB.run { conn ->
|
||||
var foundOffers = 0
|
||||
var totalAmount = 0
|
||||
var bestPrice = 0
|
||||
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")
|
||||
|
||||
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: <col=FFFFFF>$totalAmount ")
|
||||
sb.append("</col> Lowest Price: <col=FFFFFF>$bestPrice<br>")
|
||||
sb.append("-".repeat(50))
|
||||
sb.append("</col><br>Bot Stock: <col=FFFFFF>$botAmt ")
|
||||
sb.append("</col> Bot Price: <col=FFFFFF>$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: <col=FFFFFF>$totalAmount ")
|
||||
sb.append("</col>Highest Offer: <col=FFFFFF>$bestPrice</col>")
|
||||
}
|
||||
|
||||
stmt.close()
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun addBotOffer(itemID: Int, amount: Int): Boolean
|
||||
{
|
||||
if (!PriceIndex.canTrade(itemID))
|
||||
return false
|
||||
|
||||
val offer = GrandExchangeOffer.createBotOffer(itemID, amount)
|
||||
pendingOffers.addLast(offer)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun dispatch(player: Player, offer: GrandExchangeOffer) : Boolean
|
||||
{
|
||||
if ( offer.amount < 1 )
|
||||
sendMessage(player, "You must choose the quantity you wish to buy!").also { return false }
|
||||
|
||||
if ( offer.offeredValue < 1 )
|
||||
sendMessage(player, "You must choose the price you wish to buy for!").also { return false }
|
||||
|
||||
if ( offer.offerState != OfferState.PENDING || offer.uid != 0L )
|
||||
{
|
||||
SystemLogger.logWarn(this::class.java, "[GE] DISPATCH FAILURE: ${offer.offerState.name}, UID: ${offer.uid}")
|
||||
return false
|
||||
}
|
||||
|
||||
if ( player.isArtificial )
|
||||
offer.playerUID = PlayerDetails.getDetails("2009scape").uid.also { offer.isBot = true }
|
||||
else
|
||||
offer.playerUID = player.details.uid
|
||||
|
||||
offer.offerState = OfferState.REGISTERED
|
||||
//GrandExchangeRecords.getInstance(player).update(offer)
|
||||
|
||||
if (offer.sell && !player.isArtificial) {
|
||||
val username = if (getAttribute(player, "ge-exclude", false)) "?????" else player.username
|
||||
Repository.sendNews(username + " just offered " + offer.amount + " " + getItemName(offer.itemID) + " on the GE.")
|
||||
}
|
||||
|
||||
if (ServerConstants.I_AM_A_CHEATER) {
|
||||
val otherO = GrandExchangeOffer()
|
||||
otherO.itemID = offer.itemID
|
||||
otherO.amount = offer.amount
|
||||
otherO.sell = !offer.sell
|
||||
otherO.offeredValue = offer.offeredValue
|
||||
offer.writeNew()
|
||||
GameWorld.Pulser.submit(object : Pulse(5) {
|
||||
override fun pulse(): Boolean {
|
||||
val offer2 = getOfferByUid(offer.uid) ?: return false
|
||||
exchange(offer2, otherO)
|
||||
return true
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
pendingOffers.add(offer)
|
||||
return true
|
||||
}
|
||||
|
||||
fun exchange(offer: GrandExchangeOffer, other: GrandExchangeOffer)
|
||||
{
|
||||
if(offer.sell == other.sell) return //Don't exchange if they are both buy/sell offers
|
||||
val amount = Integer.min(offer.amount - offer.completedAmount, other.amount - other.completedAmount)
|
||||
|
||||
if (amount == 0) return
|
||||
|
||||
val seller = if(offer.sell) offer else other
|
||||
val buyer = if(offer == seller) other else offer
|
||||
|
||||
val sellerBias = seller.timeStamp > buyer.timeStamp
|
||||
|
||||
//If the buyer is buying for less than the seller is selling for, don't exchange
|
||||
if(seller.offeredValue > buyer.offeredValue) return
|
||||
|
||||
seller.completedAmount += amount
|
||||
buyer.completedAmount += amount
|
||||
|
||||
if(seller.amountLeft < 1 && seller.player != null)
|
||||
seller.player!!.audioManager.send(Audio(4042,1,1))
|
||||
|
||||
seller.addWithdrawItem(995, amount * if(sellerBias) buyer.offeredValue else seller.offeredValue)
|
||||
buyer.addWithdrawItem(seller.itemID, amount)
|
||||
|
||||
if(!sellerBias)
|
||||
buyer.addWithdrawItem(995, amount * (buyer.offeredValue - seller.offeredValue))
|
||||
|
||||
if(seller.amountLeft < 1)
|
||||
seller.offerState = OfferState.COMPLETED
|
||||
if(buyer.amountLeft < 1)
|
||||
buyer.offerState = OfferState.COMPLETED
|
||||
|
||||
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))
|
||||
|
||||
/*
|
||||
if (seller.amountLeft > 0) {
|
||||
Discord.postOfferUpdate(true, seller.itemID, seller.offeredValue, seller.amountLeft)
|
||||
}
|
||||
|
||||
if (buyer.amountLeft > 0) {
|
||||
Discord.postOfferUpdate(false, buyer.itemID, buyer.offeredValue, buyer.amountLeft)
|
||||
}
|
||||
*/
|
||||
|
||||
seller.update()
|
||||
val sellerPlayer = Repository.uid_map[seller.playerUID]
|
||||
sellerPlayer?.let { GrandExchangeRecords.getInstance(sellerPlayer).visualizeRecords() }
|
||||
buyer.update()
|
||||
val buyerPlayer = Repository.uid_map[buyer.playerUID]
|
||||
buyerPlayer?.let { 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>
|
||||
{
|
||||
val offers = ArrayList<GrandExchangeOffer>()
|
||||
|
||||
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()
|
||||
}
|
||||
return offers
|
||||
}
|
||||
|
||||
fun getBotOffers(): List<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")
|
||||
while (results.next()) {
|
||||
val o = GrandExchangeOffer.fromBotQuery(results)
|
||||
offers.add(o)
|
||||
}
|
||||
stmt.close()
|
||||
}
|
||||
return offers
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun startup(){
|
||||
GEDB.init()
|
||||
boot()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.api.getAttribute
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.item.Item
|
||||
import core.net.packet.PacketRepository
|
||||
import core.net.packet.context.ContainerContext
|
||||
import core.net.packet.context.GrandExchangeContext
|
||||
import core.net.packet.out.ContainerPacket
|
||||
import core.net.packet.out.GrandExchangePacket
|
||||
import core.integrations.discord.Discord
|
||||
import core.game.world.repository.Repository
|
||||
import java.sql.ResultSet
|
||||
|
||||
|
||||
/**
|
||||
* A struct holding all the data for grand exchange offers.
|
||||
*
|
||||
* @author Ceikry
|
||||
*/
|
||||
|
||||
class GrandExchangeOffer() {
|
||||
var itemID = 0
|
||||
var amount = 0
|
||||
var completedAmount = 0
|
||||
var offeredValue = 0
|
||||
var index = 0
|
||||
var sell = false
|
||||
var offerState = OfferState.PENDING
|
||||
var uid: Long = 0
|
||||
var timeStamp: Long = 0
|
||||
var withdraw = arrayOfNulls<Item>(2)
|
||||
var totalCoinExchange = 0
|
||||
var player: Player? = null
|
||||
var playerUID = 0
|
||||
var isLimitation = false
|
||||
var isBot = false
|
||||
|
||||
/**
|
||||
* Gets the total amount of money entered.
|
||||
* @return The total value.
|
||||
*/
|
||||
val totalValue: Int
|
||||
get() = offeredValue * amount
|
||||
|
||||
/**
|
||||
* Gets the amount of this item left to buy.
|
||||
* @return The amount.
|
||||
*/
|
||||
val amountLeft: Int
|
||||
get() = amount - completedAmount
|
||||
|
||||
/**
|
||||
* Checks if this offer is still active for dispatching.
|
||||
* @return `True` if so.
|
||||
*/
|
||||
val isActive: Boolean
|
||||
get() = offerState != OfferState.ABORTED && offerState != OfferState.PENDING && offerState != OfferState.COMPLETED && offerState != OfferState.REMOVED
|
||||
|
||||
fun addWithdrawItem(id: Int, amount: Int)
|
||||
{
|
||||
if(amount == 0) return
|
||||
//loop checking if the item is already present first
|
||||
for(item in withdraw)
|
||||
if(item != null && item.id == id)
|
||||
{
|
||||
item.amount += amount
|
||||
return
|
||||
}
|
||||
|
||||
//if we make it to this point, the item was not present. Loop to find first null slot and stick item there.
|
||||
for((index,item) in withdraw.withIndex())
|
||||
if(item == null)
|
||||
{
|
||||
withdraw[index] = Item(id, amount)
|
||||
return
|
||||
}
|
||||
|
||||
//send container update packet to player if they exist (are online)
|
||||
if ( player != null )
|
||||
visualize(player)
|
||||
}
|
||||
|
||||
fun visualize(player: Player?)
|
||||
{
|
||||
player ?: return
|
||||
PacketRepository.send(
|
||||
GrandExchangePacket::class.java,
|
||||
GrandExchangeContext(player, index.toByte(), offerState.ordinal.toByte(), itemID.toShort(),
|
||||
sell, offeredValue, amount, completedAmount, totalCoinExchange)
|
||||
)
|
||||
PacketRepository.send(ContainerPacket::class.java, ContainerContext(player, -1, -1757, 523 + index, withdraw, false))
|
||||
}
|
||||
|
||||
fun update()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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 (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)
|
||||
GrandExchangeRecords.getInstance(player).offerRecords[index] = GrandExchangeRecords.OfferRecord(uid, index)
|
||||
visualize(player)
|
||||
stmt.close()
|
||||
|
||||
val username = if (getAttribute(player ?: return@run, "ge-exclude", false)) "?????"
|
||||
else player?.username ?: "?????"
|
||||
|
||||
Discord.postNewOffer(sell, itemID, offeredValue, amount, username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun encodeWithdraw() : String
|
||||
{
|
||||
val sb = StringBuilder()
|
||||
for((index, item) in withdraw.withIndex())
|
||||
{
|
||||
sb.append(index)
|
||||
sb.append(",")
|
||||
if(item == null)
|
||||
sb.append("null")
|
||||
else
|
||||
sb.append(item.id)
|
||||
sb.append(",")
|
||||
if(item == null)
|
||||
sb.append("null")
|
||||
else
|
||||
sb.append(item.amount)
|
||||
|
||||
if(index + 1 < withdraw.size)
|
||||
sb.append(":")
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "[name=" + ItemDefinition.forId(itemID).name + ", itemId=" + itemID + ", amount=" + amount + ", completedAmount=" + completedAmount + ", offeredValue=" + offeredValue + ", index=" + index + ", sell=" + sell + ", state=" + offerState + ", withdraw=" + withdraw.contentToString() + ", totalCoinExchange=" + totalCoinExchange + ", playerUID=" + playerUID + "]"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromQuery(result: ResultSet): GrandExchangeOffer
|
||||
{
|
||||
val o = GrandExchangeOffer()
|
||||
o.itemID = result.getInt("item_id")
|
||||
o.amount = result.getInt("amount_total")
|
||||
o.completedAmount = result.getInt("amount_complete")
|
||||
o.offeredValue = result.getInt("offered_value")
|
||||
o.sell = result.getInt("is_sale") == 1
|
||||
o.offerState = OfferState.values()[result.getInt("offer_state")]
|
||||
o.uid = result.getLong("uid")
|
||||
o.timeStamp = result.getLong("time_stamp")
|
||||
|
||||
val itemString = result.getString("withdraw_items")
|
||||
if(itemString != null && itemString.isNotEmpty()) {
|
||||
val items = itemString.split(":")
|
||||
for (item in items) {
|
||||
if(item.isEmpty()) continue
|
||||
val tokens = item.split(",")
|
||||
val index = tokens[0].toInt()
|
||||
if (tokens[1] == "null") continue //Skip null slots
|
||||
o.withdraw[index] = Item(tokens[1].toInt(), tokens[2].toInt())
|
||||
}
|
||||
}
|
||||
|
||||
o.totalCoinExchange = result.getInt("total_coin_xc")
|
||||
o.playerUID = result.getInt("player_uid")
|
||||
o.index = result.getInt("slot_index")
|
||||
o.player = Repository.players.firstOrNull { it.details.accountInfo.uid == o.playerUID }
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
fun fromBotQuery(result: ResultSet): GrandExchangeOffer
|
||||
{
|
||||
val o = GrandExchangeOffer()
|
||||
o.sell = true
|
||||
o.amount = result.getInt("amount")
|
||||
o.offerState = OfferState.REGISTERED
|
||||
o.itemID = result.getInt("item_id")
|
||||
o.offeredValue = GrandExchange.getRecommendedPrice(o.itemID, true)
|
||||
o.isBot = true
|
||||
o.timeStamp = System.currentTimeMillis()
|
||||
return o
|
||||
}
|
||||
|
||||
fun createBotOffer(itemId: Int, amount: Int, sale: Boolean = true) : GrandExchangeOffer
|
||||
{
|
||||
val o = GrandExchangeOffer()
|
||||
o.sell = sale
|
||||
o.itemID = itemId
|
||||
o.amount = amount
|
||||
o.offeredValue = GrandExchange.getRecommendedPrice(itemId, true)
|
||||
o.offerState = OfferState.REGISTERED
|
||||
o.isBot = true
|
||||
return o
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.api.*
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import core.game.component.Component
|
||||
import core.game.node.entity.player.Player
|
||||
import org.json.simple.JSONArray
|
||||
import org.json.simple.JSONObject
|
||||
import org.rs09.consts.Components
|
||||
import core.tools.SystemLogger
|
||||
import java.text.NumberFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Handles the grand exchange interfaces for the player.
|
||||
*
|
||||
* @author Emperor
|
||||
* @author Angle
|
||||
*/
|
||||
|
||||
class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer, LoginListener {
|
||||
var history = arrayOfNulls<GrandExchangeOffer>(5)
|
||||
val offerRecords = arrayOfNulls<OfferRecord>(6)
|
||||
|
||||
override fun login(player: Player) {
|
||||
val instance = GrandExchangeRecords(player)
|
||||
player.setAttribute("ge-records", instance)
|
||||
}
|
||||
|
||||
override fun parsePlayer(player: Player, data: JSONObject) {
|
||||
/**
|
||||
* Parse history from JSON
|
||||
*/
|
||||
val historyRaw = data["ge-history"]
|
||||
if(historyRaw != null){
|
||||
val history = historyRaw as JSONArray
|
||||
for (i in history.indices) {
|
||||
val offer = history[i] as JSONObject
|
||||
val o = GrandExchangeOffer()
|
||||
o.itemID = offer["itemId"].toString().toInt()
|
||||
o.sell = offer["isSell"] as Boolean
|
||||
o.totalCoinExchange = (offer["totalCoinExchange"].toString().toInt())
|
||||
o.completedAmount = (offer["completedAmount"].toString().toInt())
|
||||
getInstance(player).history[i] = o
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read offers from the database
|
||||
*/
|
||||
val needsIndex = ArrayDeque<GrandExchangeOffer>()
|
||||
val instance = getInstance(player)
|
||||
|
||||
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)
|
||||
}
|
||||
stmt.close()
|
||||
}
|
||||
|
||||
if (needsIndex.isNotEmpty()) {
|
||||
for ((index, offer) in offerRecords.withIndex()) {
|
||||
if (offer == null) {
|
||||
val o = needsIndex.pop()
|
||||
o.index = index
|
||||
instance.offerRecords[o.index] = OfferRecord(o.uid, o.index)
|
||||
o.update() //write the new index to the database
|
||||
}
|
||||
}
|
||||
|
||||
while(needsIndex.isNotEmpty()) //If we enter this loop, there were more offers than can fit inside the 6 slots. This should never happen - at least, not anymore. Can't speak for JSON offers.
|
||||
{
|
||||
val o = needsIndex.pop()
|
||||
SystemLogger.logGE("[WARN] PLAYER HAD EXTRA OFFER - RECOMMEND IMMEDIATE REFUND OF CONTENTS -> OFFER UID: ${o.uid}")
|
||||
SystemLogger.logGE("[WARN] AS PER ABOVE MESSAGE, REFUND CONTENTS OF OFFER AND MANUALLY SET offer_state = 6")
|
||||
}
|
||||
}
|
||||
|
||||
instance.init()
|
||||
}
|
||||
|
||||
override fun savePlayer(player: Player, save: JSONObject) {
|
||||
/**
|
||||
* Save history to JSON
|
||||
*/
|
||||
val history = JSONArray()
|
||||
getInstance(player).history.map {
|
||||
if(it != null){
|
||||
val historyEntry = JSONObject()
|
||||
historyEntry["isSell"] = it.sell
|
||||
historyEntry["itemId"] = it.itemID.toString()
|
||||
historyEntry["totalCoinExchange"] = it.totalCoinExchange.toString()
|
||||
historyEntry["completedAmount"] = it.completedAmount.toString()
|
||||
history.add(historyEntry)
|
||||
}
|
||||
}
|
||||
save["ge-history"] = history
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the collection box.
|
||||
*/
|
||||
fun openCollectionBox() {
|
||||
if (!player!!.bankPinManager.isUnlocked) {
|
||||
player.bankPinManager.openType(3)
|
||||
return
|
||||
}
|
||||
player.interfaceManager.openComponent(Components.STOCKCOLLECT_109)
|
||||
player.packetDispatch.sendIfaceSettings(6, 18, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 23, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 28, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 36, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 44, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 52, 109, 0, 2)
|
||||
|
||||
visualizeRecords()
|
||||
}
|
||||
|
||||
fun visualizeRecords()
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
stmt.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun getOffer(index: Int) : GrandExchangeOffer?
|
||||
{
|
||||
if(index == -1) return getOffer(null)
|
||||
return getOffer(offerRecords[index])
|
||||
}
|
||||
|
||||
fun getOffer(record: OfferRecord?) : GrandExchangeOffer?
|
||||
{
|
||||
record ?: return null
|
||||
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 offerToReturn
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the history log.
|
||||
*
|
||||
* @param p The player to open it for.
|
||||
*/
|
||||
fun openHistoryLog(p: Player) {
|
||||
p.interfaceManager.open(Component(643))
|
||||
for (i in history.indices) {
|
||||
val o: GrandExchangeOffer? = history[i]
|
||||
if (o == null) {
|
||||
for (j in 0..3) {
|
||||
p.packetDispatch.sendString("-", 643, 25 + i + j * 5)
|
||||
}
|
||||
continue
|
||||
}
|
||||
p.packetDispatch.sendString(if (o.sell) "You sold" else "You bought", 643, 25 + i)
|
||||
p.packetDispatch.sendString(
|
||||
NumberFormat.getNumberInstance(Locale.US).format(o.completedAmount.toLong()),
|
||||
643,
|
||||
30 + i
|
||||
)
|
||||
p.packetDispatch.sendString(ItemDefinition.forId(o.itemID).name, 643, 35 + i)
|
||||
p.packetDispatch.sendString(
|
||||
NumberFormat.getNumberInstance(Locale.US).format(o.totalCoinExchange.toLong()) + " gp", 643, 40 + i
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the grand exchange.
|
||||
*/
|
||||
fun init() {
|
||||
// Were trades made while gone?
|
||||
var updated = false
|
||||
for (record in offerRecords) {
|
||||
if (record != null) {
|
||||
val offer = getOffer(record) ?: continue
|
||||
if (!updated && (offer.withdraw[0] != null || offer.withdraw[1] != null)) {
|
||||
updated = true
|
||||
}
|
||||
offer.visualize(player)
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
sendMessage(player!!, "You have items from the Grand Exchange waiting in your collection box.")
|
||||
}
|
||||
}
|
||||
|
||||
fun hasActiveOffer(): Boolean {
|
||||
for (i in offerRecords) {
|
||||
if (i != null)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the grand exchange.
|
||||
*
|
||||
* @return the formatted offer for the SQL database.
|
||||
*/
|
||||
fun format(): String? {
|
||||
var log = ""
|
||||
for (record in offerRecords) {
|
||||
if (record != null) {
|
||||
val offer = getOffer(record) ?: continue
|
||||
log += offer.itemID.toString() + "," + offer.amount + "," + offer.sell + "|"
|
||||
}
|
||||
}
|
||||
if (log.isNotEmpty() && log[log.length - 1] == '|') {
|
||||
log = log.substring(0, log.length - 1)
|
||||
}
|
||||
return log
|
||||
}
|
||||
|
||||
data class OfferRecord(val uid: Long, val slot: Int)
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun getInstance(player: Player? = null): GrandExchangeRecords
|
||||
{
|
||||
return player?.getAttribute("ge-records", GrandExchangeRecords()) ?: GrandExchangeRecords()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package core.game.ge;
|
||||
|
||||
/**
|
||||
* Represents the state of a Grand Exchange offer.
|
||||
* @author Emperor
|
||||
*/
|
||||
public enum OfferState {
|
||||
|
||||
/**
|
||||
* The player is still constructing the offer.
|
||||
*/
|
||||
PENDING,
|
||||
|
||||
/**
|
||||
* The player has confirmed the offer (the offer is being dispatched).
|
||||
*/
|
||||
REGISTERED,
|
||||
|
||||
/**
|
||||
* The offer has been aborted.
|
||||
*/
|
||||
ABORTED,
|
||||
|
||||
/**
|
||||
* The offer has been updated.
|
||||
*/
|
||||
UPDATED,
|
||||
|
||||
/**
|
||||
* The offer is completed.
|
||||
*/
|
||||
COMPLETED,
|
||||
|
||||
/**
|
||||
* The offer is outdated.
|
||||
*/
|
||||
OUTDATED,
|
||||
|
||||
/**
|
||||
* The offer has been removed.
|
||||
*/
|
||||
REMOVED;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.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) {
|
||||
if(canTrade(id)) return
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user