Reworked grand exchange offer processing

Now much more efficient
Offers will appear to complete almost instantaneously
Players can no longer hoard bot offers as they will only be allocated at time of offer placement
This commit is contained in:
Ceikry
2022-07-03 06:24:59 +00:00
committed by Ryan
parent 81a12534be
commit c4781e8000
3 changed files with 63 additions and 88 deletions
@@ -5,7 +5,6 @@ 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 discord.Discord
import rs09.ServerConstants 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
@@ -14,12 +13,17 @@ import rs09.game.world.repository.Repository
import rs09.tools.stringtools.colorize import rs09.tools.stringtools.colorize
import java.lang.Integer.max import java.lang.Integer.max
import java.sql.ResultSet import java.sql.ResultSet
import java.util.concurrent.LinkedBlockingDeque
/** /**
* Handles the exchanging of offers, offer update thread, etc. * Handles the exchanging of offers, offer update thread, etc.
* @author Ceikry * @author Ceikry
*/ */
class GrandExchange : StartupListener, Commands { class GrandExchange : StartupListener, Commands {
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 = ?;"
/** /**
* Fallback safety check to make sure we don't start the GE twice under any circumstance * Fallback safety check to make sure we don't start the GE twice under any circumstance
*/ */
@@ -32,58 +36,65 @@ class GrandExchange : StartupListener, Commands {
fun boot(){ fun boot(){
if(isRunning) return if(isRunning) return
SystemLogger.logGE("Initializing GE...")
//OfferManager.init()
SystemLogger.logGE("GE Initialized.")
SystemLogger.logGE("Initializing GE Update Worker") SystemLogger.logGE("Initializing GE Update Worker")
val t = Thread { Thread {
Thread.currentThread().name = "GE Update Worker" Thread.currentThread().name = "GE Update Worker"
while(true) { while(true) {
GEDB.run { conn -> var offer = pendingOffers.takeFirst()
val botStmt = conn.createStatement() offer.writeNew()
val botOffers = botStmt.executeQuery("SELECT * from bot_offers") offer = getOfferByUid(offer.uid) ?: continue
while (botOffers.next()) { selectPotentialMatches(offer).asSequence()
val bot = GrandExchangeOffer.fromBotQuery(botOffers) .sortedBy { if (offer.sell) -it.offeredValue else it.offeredValue }
val buyStmt = conn.createStatement() .filter { if (offer.sell) it.offeredValue >= offer.offeredValue else it.offeredValue <= offer.offeredValue }
val buyOffer = .forEach { match -> exchange(offer, match) }
buyStmt.executeQuery("SELECT * FROM player_offers WHERE item_id = ${bot.itemID} AND offer_state < 4 AND NOT offer_state = 2 AND offered_value >= ${bot.offeredValue}") if (!offer.isBot && offer.amountLeft > 0 && !offer.sell)
val buyOffers = ArrayList<GrandExchangeOffer>() tryExchangeWithBots(offer)
while (buyOffer.next()) buyOffers.add(GrandExchangeOffer.fromQuery(buyOffer))
buyStmt.close()
for (offer in buyOffers.sortedBy { it.offeredValue }.reversed()) {
if (bot.amountLeft <= 0) break
exchange(bot, offer)
}
}
botStmt.close()
}
val activeOffers = ArrayList<GrandExchangeOffer>()
GEDB.run { conn ->
val stmt = conn.createStatement()
val activeOffer =
stmt.executeQuery("SELECT * from player_offers where offer_state < 4 AND NOT offer_state = 2 AND is_sale = true")
while (activeOffer.next()) {
val offer = GrandExchangeOffer.fromQuery(activeOffer)
if (!offer.isActive) continue
activeOffers.add(offer)
}
}
for(offer in activeOffers)
processOffer(offer)
Thread.sleep(15_000) //sleep for 15 seconds
} }
}.start() }.start()
isRunning = true isRunning = true
} }
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
}
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() { override fun defineCommands() {
define("addbotoffer", Privilege.ADMIN) {player, strings -> define("addbotoffer", Privilege.ADMIN) {player, strings ->
val id = strings[1].toInt() val id = strings[1].toInt()
@@ -113,44 +124,7 @@ class GrandExchange : StartupListener, Commands {
} }
companion object { companion object {
fun processOffer(offer: GrandExchangeOffer) val pendingOffers = LinkedBlockingDeque<GrandExchangeOffer>()
{
GEDB.run { conn ->
if(offer.isActive) {
val stmt = conn.createStatement()
val olderOffers = stmt.executeQuery("SELECT * FROM player_offers WHERE item_id = ${offer.itemID} AND is_sale = ${!offer.sell} AND offer_state < 4 AND NOT offer_state = 2 AND time_stamp < ${offer.timeStamp}")
if(tryOffers(offer, olderOffers, true)) return@run
val newerOffers = stmt.executeQuery("SELECT * FROM player_offers WHERE item_id = ${offer.itemID} AND is_sale = ${!offer.sell} AND offer_state < 4 AND NOT offer_state = 2 AND time_stamp >= ${offer.timeStamp}")
if(tryOffers(offer, newerOffers, false)) return@run
}
}
}
private fun tryOffers(offer: GrandExchangeOffer, set: ResultSet, offerBiased: Boolean) : Boolean {
var bestOffer: GrandExchangeOffer? = null
while(set.next()) {
val otherOffer = GrandExchangeOffer.fromQuery(set)
if (otherOffer.amountLeft < 1 || offer.amountLeft < 1) continue
val buyOffer = if(otherOffer.sell) offer else otherOffer
val sellOffer = if(otherOffer.sell) otherOffer else offer
if (buyOffer.offeredValue < sellOffer.offeredValue) continue
bestOffer = if (bestOffer == null) otherOffer
else compareOffers(offer, otherOffer, bestOffer, offerBiased)
}
set.close()
if(bestOffer != null) exchange(offer, bestOffer)
return bestOffer != null
}
private fun compareOffers(offer: GrandExchangeOffer, first: GrandExchangeOffer, second: GrandExchangeOffer, biased: Boolean) : GrandExchangeOffer {
return if(offer.sell) {
if(biased) if(first.offeredValue > second.offeredValue) first else second
else if(first.offeredValue < second.offeredValue) first else second
} else {
if(biased) if(first.offeredValue < second.offeredValue) first else second
else if(first.offeredValue > second.offeredValue) first else second
}
}
@JvmStatic @JvmStatic
fun getRecommendedPrice(itemID: Int, from_bot: Boolean = false): Int { fun getRecommendedPrice(itemID: Int, from_bot: Boolean = false): Int {
@@ -234,7 +208,7 @@ class GrandExchange : StartupListener, Commands {
return false return false
val offer = GrandExchangeOffer.createBotOffer(itemID, amount) val offer = GrandExchangeOffer.createBotOffer(itemID, amount)
offer.writeNew() pendingOffers.addLast(offer)
return true return true
} }
@@ -266,15 +240,17 @@ class GrandExchange : StartupListener, Commands {
Repository.sendNews(username + " just offered " + offer.amount + " " + getItemName(offer.itemID) + " on the GE.") Repository.sendNews(username + " just offered " + offer.amount + " " + getItemName(offer.itemID) + " on the GE.")
} }
offer.writeNew() pendingOffers.add(offer)
return true return true
} }
fun exchange(offer: GrandExchangeOffer, other: GrandExchangeOffer) fun exchange(offer: GrandExchangeOffer, other: GrandExchangeOffer)
{ {
if(offer.sell && other.sell) return //Don't exchange if they are both sell offers 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) 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 seller = if(offer.sell) offer else other
val buyer = if(offer == seller) other else offer val buyer = if(offer == seller) other else offer
@@ -320,10 +296,10 @@ class GrandExchange : StartupListener, Commands {
seller.update() seller.update()
val sellerPlayer = Repository.uid_map[seller.playerUID] val sellerPlayer = Repository.uid_map[seller.playerUID]
GrandExchangeRecords.getInstance(sellerPlayer).visualizeRecords() sellerPlayer?.let { GrandExchangeRecords.getInstance(sellerPlayer).visualizeRecords() }
buyer.update() buyer.update()
val buyerPlayer = Repository.uid_map[buyer.playerUID] val buyerPlayer = Repository.uid_map[buyer.playerUID]
GrandExchangeRecords.getInstance(buyerPlayer).visualizeRecords() buyerPlayer?.let { GrandExchangeRecords.getInstance(buyerPlayer).visualizeRecords() }
} }
private fun canUpdatePriceIndex(seller: GrandExchangeOffer, buyer: GrandExchangeOffer): Boolean { private fun canUpdatePriceIndex(seller: GrandExchangeOffer, buyer: GrandExchangeOffer): Boolean {
@@ -143,6 +143,7 @@ class GrandExchangeOffer() {
) )
val nowuid = stmt.executeQuery("SELECT last_insert_rowid()") val nowuid = stmt.executeQuery("SELECT last_insert_rowid()")
uid = nowuid.getLong(1) uid = nowuid.getLong(1)
GrandExchangeRecords.getInstance(player).offerRecords[index] = GrandExchangeRecords.OfferRecord(uid, index)
visualize(player) visualize(player)
stmt.close() stmt.close()
@@ -258,7 +258,6 @@ class StockMarket : InterfaceListener {
} }
if(GrandExchange.dispatch(player, offer)) if(GrandExchange.dispatch(player, offer))
{ {
GrandExchangeRecords.getInstance(player).offerRecords[offer.index] = GrandExchangeRecords.OfferRecord(offer.uid,offer.index)
player.removeAttribute("ge-temp") player.removeAttribute("ge-temp")
} }
else else
@@ -277,7 +276,6 @@ class StockMarket : InterfaceListener {
} }
if(GrandExchange.dispatch(player, offer) && removeItem(player, Item(995, total))) if(GrandExchange.dispatch(player, offer) && removeItem(player, Item(995, total)))
{ {
GrandExchangeRecords.getInstance(player).offerRecords[offer.index] = GrandExchangeRecords.OfferRecord(offer.uid, offer.index)
player.removeAttribute("ge-temp") player.removeAttribute("ge-temp")
} }
} }