diff --git a/game/src/main/kotlin/content/social/trade/exchange/GrandExchange.kt b/game/src/main/kotlin/content/social/trade/exchange/GrandExchange.kt index 43aa995081..7811ad1fb7 100644 --- a/game/src/main/kotlin/content/social/trade/exchange/GrandExchange.kt +++ b/game/src/main/kotlin/content/social/trade/exchange/GrandExchange.kt @@ -253,8 +253,9 @@ class GrandExchange( // if offer has more or same as other val required = offer.amount - offer.completed val available = open.remaining + val buyer = if (offer.sell) open.account else account var traded = if (required >= available) available else required - val limit = limits.limit(if (offer.sell) open.account else account, offer.item) + val limit = if (buyer.isEmpty()) -1 else limits.limit(buyer, offer.item) if (limit != -1) { traded = traded.coerceAtMost(limit) if (traded <= 0) { @@ -270,8 +271,10 @@ class GrandExchange( } claim(open.id, open.account, offer.item, traded, traderPrice, offer.price, !offer.sell) // Record the successful exchange - limits.record(account, offer.item, traded) - history.record(offer.item, traded, offer.price) + if (buyer.isNotEmpty()) { + limits.record(buyer, offer.item, traded) + } + history.record(offer.item, traded, traderPrice) return true } diff --git a/game/src/main/kotlin/content/social/trade/exchange/GrandExchangeControls.kt b/game/src/main/kotlin/content/social/trade/exchange/GrandExchangeControls.kt index ca7949597d..efe2967377 100644 --- a/game/src/main/kotlin/content/social/trade/exchange/GrandExchangeControls.kt +++ b/game/src/main/kotlin/content/social/trade/exchange/GrandExchangeControls.kt @@ -206,12 +206,11 @@ class GrandExchangeControls : Script { fun Player.totalItems(): Int { val item = Item(get("grand_exchange_item", "")) val noted = item.noted - var total = 0 - if (noted != null) { - total += inventory.count(noted.id) + var total = inventory.count(item.id).toLong() + if (noted != null && noted.id != item.id) { + total += inventory.count(noted.id).toLong() } - total += inventory.count(item.id) - return total + return total.coerceAtMost(Int.MAX_VALUE.toLong()).toInt() } /** diff --git a/game/src/main/kotlin/content/social/trade/exchange/limit/BuyLimits.kt b/game/src/main/kotlin/content/social/trade/exchange/limit/BuyLimits.kt index 89bc2df67b..7c8796606a 100644 --- a/game/src/main/kotlin/content/social/trade/exchange/limit/BuyLimits.kt +++ b/game/src/main/kotlin/content/social/trade/exchange/limit/BuyLimits.kt @@ -39,7 +39,7 @@ class BuyLimits { } val now = epochMilliseconds() for ((player, limit) in limits) { - if (TimeUnit.MILLISECONDS.toHours(now - limit.timestamp) > hours) { + if (TimeUnit.MILLISECONDS.toHours(now - limit.timestamp) >= hours) { limits.remove(player) } } diff --git a/game/src/test/kotlin/content/social/trade/exchange/GrandExchangeTest.kt b/game/src/test/kotlin/content/social/trade/exchange/GrandExchangeTest.kt index eab89f8a47..400944f49b 100644 --- a/game/src/test/kotlin/content/social/trade/exchange/GrandExchangeTest.kt +++ b/game/src/test/kotlin/content/social/trade/exchange/GrandExchangeTest.kt @@ -9,8 +9,10 @@ import npcOption import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.koin.test.get +import world.gregs.voidps.cache.definition.Params import world.gregs.voidps.engine.client.ui.dialogue.Dialogues import world.gregs.voidps.engine.data.Settings +import world.gregs.voidps.engine.data.definition.ItemDefinitions import world.gregs.voidps.engine.data.exchange.ExchangeOffer import world.gregs.voidps.engine.data.exchange.OfferState import world.gregs.voidps.engine.entity.character.npc.NPC @@ -46,6 +48,61 @@ class GrandExchangeTest : WorldTest() { clerk = createNPC("grand_exchange_clerk_short", Tile(3164, 3488)) } + @Test + fun `Edit sell quantity with a large stack of unnoteable items`() { + val seller = createPlayer(Tile(3164, 3487), "seller") + seller.inventory.add("fire_rune", 2_140_000_000) + + sell(seller, "fire_rune") + seller.interfaceOption("grand_exchange", "add_x", "Edit Quantity") + (seller.suspension as Suspension.IntEntry).resume(100_000) + + assertEquals(100_000, seller.get("grand_exchange_quantity")) + tick() + seller.interfaceOption("grand_exchange", "add_x", "Edit Quantity") + (seller.suspension as Suspension.IntEntry).resume(Int.MAX_VALUE) + assertEquals(2_140_000_000, seller.get("grand_exchange_quantity")) + } + + @Test + fun `Combined noted and unnoted sell quantities saturate at integer maximum`() { + val seller = createPlayer(Tile(3164, 3487), "seller") + seller.inventory.add("rune_longsword_noted", Int.MAX_VALUE) + seller.inventory.add("rune_longsword") + + sell(seller, "rune_longsword") + seller.interfaceOption("grand_exchange", "add_x", "Edit Quantity") + (seller.suspension as Suspension.IntEntry).resume(100_000) + + assertEquals(100_000, seller.get("grand_exchange_quantity")) + tick() + seller.interfaceOption("grand_exchange", "add_x", "Edit Quantity") + (seller.suspension as Suspension.IntEntry).resume(Int.MAX_VALUE) + assertEquals(Int.MAX_VALUE, seller.get("grand_exchange_quantity")) + } + + @Test + fun `Instant sells do not share a synthetic buyer limit`() { + Settings.load(mapOf("grandExchange.instantOffer" to "true", "grandExchange.instantSellUnderMarketPrice" to "0.0")) + val definition = ItemDefinitions.get("rune_longsword") + val originalParams = definition.params + definition.params = (originalParams ?: emptyMap()) + (Params.LIMIT to 1) + try { + for (name in listOf("sellerone", "sellertwo")) { + val seller = createPlayer(Tile(3164, 3487), name) + seller.inventory.add("rune_longsword") + sell(seller, "rune_longsword") + confirm(seller) + tick() + val offer = seller.offers[1] + assertEquals(OfferState.CompletedSell, offer.state) + assertEquals(1, offer.completed) + } + } finally { + definition.params = originalParams + } + } + @Test fun `Sell item mid price using coins in bank`() { val seller = createPlayer(Tile(3164, 3487), "seller")