adjusted price calculation formula used when selling items to shops

This commit is contained in:
Ryan
2022-05-06 22:54:32 +00:00
committed by Ceikry
parent 2b0c35a420
commit d91396d0ea
3 changed files with 185 additions and 40 deletions
@@ -15,11 +15,11 @@ import org.rs09.consts.Components
import org.rs09.consts.Items import org.rs09.consts.Items
import rs09.ServerConstants import rs09.ServerConstants
import rs09.game.content.global.shops.Shops.Companion.logShop import rs09.game.content.global.shops.Shops.Companion.logShop
import rs09.game.system.SystemLogger
import rs09.game.world.GameWorld import rs09.game.world.GameWorld
import java.lang.Integer.max import java.lang.Integer.max
import java.lang.Integer.min import java.lang.Integer.min
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.roundToInt
data class ShopItem(var itemId: Int, var amount: Int, val restockRate: Int = 100) data class ShopItem(var itemId: Int, var amount: Int, val restockRate: Int = 100)
@@ -168,7 +168,14 @@ class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean =
{ {
val shopCont = getAttribute<Container?>(player, "shop-cont", null) ?: return Pair(null, Item(-1,-1)) val shopCont = getAttribute<Container?>(player, "shop-cont", null) ?: return Pair(null, Item(-1,-1))
val item = player.inventory[slot] val item = player.inventory[slot]
var (isPlayerStock, shopSlot) = getStockSlot(item.id) val shopItemId = if (item.definition.isUnnoted) {
item.id
}
else
{
item.noteChange
}
var (isPlayerStock, shopSlot) = getStockSlot(shopItemId)
val stockAmt = val stockAmt =
if(isPlayerStock) if(isPlayerStock)
@@ -178,7 +185,7 @@ class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean =
else 0 else 0
} }
val currentAmt = val currentAmt =
if(isPlayerStock) playerStock.getAmount(item.id) if(isPlayerStock) playerStock.getAmount(shopItemId)
else { else {
if(shopSlot != -1) shopCont[shopSlot].amount if(shopSlot != -1) shopCont[shopSlot].amount
else { else {
@@ -191,7 +198,7 @@ class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean =
{ {
Items.TOKKUL_6529 -> item.definition.getConfiguration("tokkul_price", 1) Items.TOKKUL_6529 -> item.definition.getConfiguration("tokkul_price", 1)
Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration("archery_ticket_price", 1) Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration("archery_ticket_price", 1)
else -> getGPSell(Item(item.id, 1), stockAmt, currentAmt) else -> getGPSell(Item(shopItemId, 1), stockAmt, currentAmt)
} }
if(!general && stockAmt == 0 && shopSlot == -1) if(!general && stockAmt == 0 && shopSlot == -1)
@@ -202,7 +209,8 @@ class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean =
return Pair(if (isPlayerStock) playerStock else shopCont, Item(currency, price)) return Pair(if (isPlayerStock) playerStock else shopCont, Item(currency, price))
} }
private fun getGPCost(item: Item, stockAmount: Int, currentAmt: Int): Int{ private fun getGPCost(item: Item, stockAmount: Int, currentAmt: Int): Int
{
var mod: Int var mod: Int
mod = if(stockAmount == 0) 100 mod = if(stockAmount == 0) 100
else if(currentAmt == 0) 130 else if(currentAmt == 0) 130
@@ -219,22 +227,21 @@ class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean =
return max(price, 1) return max(price, 1)
} }
private fun getGPSell(item: Item, stockAmount: Int, currentAmt: Int): Int{ private fun getGPSell(item: Item, stockAmount: Int, currentAmt: Int): Int
if(!item.definition.isUnnoted) {
item.id = item.noteChange val base = item.definition.getAlchemyValue(highAlch)
var mod: Int var overstock = currentAmt - stockAmount
mod = if(stockAmount == 0) 70 if (overstock < 0) {
else if(currentAmt == 0) 100 return base
else if(currentAmt >= stockAmount) 70 }
else 100 - (100 - 70) * currentAmt / stockAmount if (overstock > 10) {
if(mod < 1) mod = 1 overstock = 10
mod = max(70, min(100, mod)) }
val price = (base - (item.definition.value * 0.03 * overstock)).roundToInt()
var base = if (highAlch) item.definition.getAlchemyValue(true) else item.definition.value if (price < 1) {
base = max(base, item.definition.value) return 1
}
val price: Int = ceil(base * mod.toDouble() / 100.0).toInt() return price
return max(price, 1)
} }
fun buy(player: Player, slot: Int, amount: Int) : TransactionStatus fun buy(player: Player, slot: Int, amount: Int) : TransactionStatus
+150 -17
View File
@@ -1,23 +1,45 @@
import core.game.node.entity.player.link.IronmanMode import core.game.node.entity.player.link.IronmanMode
import core.game.node.item.Item import core.game.node.item.Item
import org.junit.Assert
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.rs09.consts.Items
import rs09.game.content.global.shops.Shop import rs09.game.content.global.shops.Shop
import rs09.game.content.global.shops.Shops import rs09.game.content.global.shops.Shops
import rs09.game.system.config.ShopParser import kotlin.math.min
import kotlin.math.roundToInt
class ShopTests { class ShopTests {
val testPlayer = TestUtils.getMockPlayer("test") companion object {
val testIronman = TestUtils.getMockPlayer("test2", IronmanMode.STANDARD) init {TestUtils.preTestSetup()}
val nonGeneral = TestUtils.getMockShop("Not General", false, Item(4151, 1)) }
val general = TestUtils.getMockShop("General", true, Item(4151, 1))
private val testPlayer = TestUtils.getMockPlayer("test")
private val testIronman = TestUtils.getMockPlayer("test2", IronmanMode.STANDARD)
var nonGeneral = TestUtils.getMockShop("Not General", false, false, Item(4151, 1))
var general = TestUtils.getMockShop("General", true, false, Item(4151, 1))
var highAlch = TestUtils.getMockShop("High(af) Alch", true, true, Item(4151, 1))
@BeforeEach fun beforeEach() {
val testPlayers = arrayOf(testPlayer, testIronman)
for (player in testPlayers) {
player.inventory.clear()
player.attributes.remove("shop-cont")
}
nonGeneral.playerStock.clear()
general.playerStock.clear()
highAlch.playerStock.clear()
}
private fun assertTransactionSuccess(status: Shop.TransactionStatus) {
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success, "Transaction failure: ${if(status is Shop.TransactionStatus.Failure) status.reason else ""}")
}
@Test fun shouldSellItemToStore() { @Test fun shouldSellItemToStore() {
testPlayer.inventory.add(Item(4151, 1)) testPlayer.inventory.add(Item(4151, 1))
testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer)) testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer))
val status = general.sell(testPlayer, 0, 1) val status = general.sell(testPlayer, 0, 1)
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success, "Transaction failed: ${if(status is Shop.TransactionStatus.Failure) status.reason else ""}") assertTransactionSuccess(status)
} }
@Test fun shouldNotSellUnstockedItemToStandardStore() { @Test fun shouldNotSellUnstockedItemToStandardStore() {
@@ -31,28 +53,139 @@ class ShopTests {
testPlayer.inventory.add(Item(1, 1)) testPlayer.inventory.add(Item(1, 1))
testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer)) testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer))
val status = general.sell(testPlayer, 0, 1) val status = general.sell(testPlayer, 0, 1)
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success, "Transaction failure: ${if(status is Shop.TransactionStatus.Failure) status.reason else ""}") assertTransactionSuccess(status)
}
@Test fun shouldSellUnstockedItemToGeneralStoreUsingLowAlchBaseValue() {
//starts at 40% value - a.k.a. low alch price
//drops 3% value per item stocked
//bottoms out at 10% value (25% of the low alch price) after 10 items stocked
val saleItemId = Items.RUNE_MED_HELM_1147
val shopContainer = general.getContainer(testPlayer)
Assertions.assertFalse(shopContainer.containItems(saleItemId), "Pre-assertion, shop container should not have the unstocked item to begin with.")
testPlayer.setAttribute("shop-cont", shopContainer)
val playerStock = general.playerStock
Assertions.assertFalse(playerStock.containItems(saleItemId), "Pre-assertion, player stock should not have the unstocked item to begin with.")
val saleItem = Item(saleItemId, 1)
val alchValue = saleItem.definition.getAlchemyValue(false)
val value = saleItem.definition.value
Assertions.assertEquals((value * 0.4).roundToInt(), alchValue, "Pre-assertion, low alch value should be 40% value.")
for (i in 0..14) {
val expectedCoins = (alchValue.toDouble() - value * min(0.03 * i, 0.30)).roundToInt()
testPlayer.inventory.add(saleItem.copy())
Assertions.assertEquals(saleItemId, testPlayer.inventory.getId(0), "Pre-assertion, should have item in inventory slot 0.")
val status = general.sell(testPlayer, 0, 1)
assertTransactionSuccess(status)
val coinItem = testPlayer.inventory[0]
Assertions.assertEquals(Items.COINS_995, coinItem.id)
Assertions.assertEquals(
expectedCoins,
coinItem.amount,
"Selling item $i should yield the expected price."
)
testPlayer.inventory.clear()
}
}
@Test fun shouldSellUnstockedItemToHighAlchStoreUsingHighAlchBaseValue() {
//starts at 60% value - a.k.a. high alch price
//drops 3% value per item stocked
//bottoms out at 30% value (50% of the high alch price) after 10 items stocked
val saleItemId = Items.RUNE_MED_HELM_1147
val shopContainer = highAlch.getContainer(testPlayer)
Assertions.assertFalse(shopContainer.containItems(saleItemId), "Pre-assertion, shop container should not have the unstocked item to begin with.")
testPlayer.setAttribute("shop-cont", shopContainer)
val playerStock = highAlch.playerStock
Assertions.assertFalse(playerStock.containItems(saleItemId), "Pre-assertion, player stock should not have the unstocked item to begin with.")
val saleItem = Item(saleItemId, 1)
val alchValue = saleItem.definition.getAlchemyValue(true)
val value = saleItem.definition.value
Assertions.assertEquals((value * 0.6).roundToInt(), alchValue, "Pre-assertion, high alch value should be 60% value.")
for (i in 0..14) {
val expectedCoins = (alchValue.toDouble() - value * min(0.03 * i, 0.30)).roundToInt()
testPlayer.inventory.add(saleItem.copy())
Assertions.assertEquals(saleItemId, testPlayer.inventory.getId(0), "Pre-assertion, should have item in inventory slot 0.")
val status = highAlch.sell(testPlayer, 0, 1)
assertTransactionSuccess(status)
val coinItem = testPlayer.inventory[0]
Assertions.assertEquals(Items.COINS_995, coinItem.id)
Assertions.assertEquals(
expectedCoins,
coinItem.amount,
"Selling item $i should yield the expected price."
)
testPlayer.inventory.clear()
}
}
@Test fun shouldSellNotedUnstockedItemForSamePriceAsUnnoted() {
val saleItemId = Items.RUNE_MED_HELM_1147
var notedSaleItemId = Items.RUNE_MED_HELM_1148
val shopContainer = highAlch.getContainer(testPlayer)
Assertions.assertFalse(shopContainer.containItems(saleItemId), "Pre-assertion, shop container should not have the unstocked item to begin with.")
testPlayer.setAttribute("shop-cont", shopContainer)
val playerStock = highAlch.playerStock
Assertions.assertFalse(playerStock.containItems(saleItemId), "Pre-assertion, player stock should not have the unstocked item to begin with.")
testPlayer.inventory.add(Item(saleItemId, 1))
var status = highAlch.sell(testPlayer, 0, 1)
assertTransactionSuccess(status)
testPlayer.inventory.clear()
val notedSaleItem = Item(notedSaleItemId, 1)
testPlayer.inventory.add(notedSaleItem)
val alchValue = notedSaleItem.definition.getAlchemyValue(true)
val value = notedSaleItem.definition.value
status = highAlch.sell(testPlayer, 0, 1)
assertTransactionSuccess(status)
val expectedCoins = (alchValue.toDouble() - value * 0.03).roundToInt()
val coinItem = testPlayer.inventory[0]
Assertions.assertEquals(Items.COINS_995, coinItem.id)
Assertions.assertEquals(
expectedCoins,
coinItem.amount,
"Selling noted item should yield the same devalued price as base item."
)
}
@Test fun minimumSellPriceShouldBe1Coin() {
testPlayer.inventory.add(Item(Items.EMPTY_POT_1931, 1))
testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer))
val status = general.sell(testPlayer, 0, 1)
assertTransactionSuccess(status)
val coinItem = testPlayer.inventory[0]
Assertions.assertEquals(Items.COINS_995, coinItem.id)
Assertions.assertEquals(
1,
coinItem.amount,
"1 coin should be the minimum selling price."
)
} }
@Test fun shouldSellUnstockedItemToGeneralStoreAsIronman() { @Test fun shouldSellUnstockedItemToGeneralStoreAsIronman() {
testIronman.inventory.add(Item(1, 1)) testIronman.inventory.add(Item(1, 1))
testIronman.setAttribute("shop-cont", general.getContainer(testPlayer)) testIronman.setAttribute("shop-cont", general.getContainer(testIronman))
val status = general.sell(testIronman, 0, 1) val status = general.sell(testIronman, 0, 1)
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success, "Transaction failure: ${if(status is Shop.TransactionStatus.Failure) status.reason else ""}") assertTransactionSuccess(status)
} }
@Test fun shouldSellStackOfUnstockedItemsToPlayerStock() { @Test fun shouldSellStackOfUnstockedItemsToPlayerStock() {
testPlayer.inventory.add(Item(1, 20)) testPlayer.inventory.add(Item(1, 20))
testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer)) testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer))
val status = general.sell(testPlayer, 0, 20) val status = general.sell(testPlayer, 0, 20)
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success, "Transaction failure: ${if(status is Shop.TransactionStatus.Failure) status.reason else ""}") assertTransactionSuccess(status)
} }
@Test fun shouldPutSoldUnstockedItemsInPlayerStock() { @Test fun shouldPutSoldUnstockedItemsInPlayerStock() {
testPlayer.inventory.add(Item(2,1)) testPlayer.inventory.add(Item(2,1))
testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer)) testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer))
val status = general.sell(testPlayer, 0, 1) val status = general.sell(testPlayer, 0, 1)
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success, "Transaction failure: ${if(status is Shop.TransactionStatus.Failure) status.reason else ""}") assertTransactionSuccess(status)
Assertions.assertEquals(1, general.playerStock.getAmount(2)) Assertions.assertEquals(1, general.playerStock.getAmount(2))
Assertions.assertEquals(0, general.getContainer(testPlayer).getAmount(2)) Assertions.assertEquals(0, general.getContainer(testPlayer).getAmount(2))
} }
@@ -62,7 +195,7 @@ class ShopTests {
testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer)) testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer))
testPlayer.setAttribute("shop-main", true) testPlayer.setAttribute("shop-main", true)
val status = general.buy(testPlayer, 0, 1) val status = general.buy(testPlayer, 0, 1)
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success, "Transaction failure: ${if(status is Shop.TransactionStatus.Failure) status.reason else ""}") assertTransactionSuccess(status)
} }
@Test fun shouldAllowStandardPlayerToBuyOverstock() { @Test fun shouldAllowStandardPlayerToBuyOverstock() {
@@ -71,7 +204,7 @@ class ShopTests {
testPlayer.setAttribute("shop-main", true) testPlayer.setAttribute("shop-main", true)
general.getContainer(testPlayer).add(Item(4151, 100)) general.getContainer(testPlayer).add(Item(4151, 100))
val status = general.buy(testPlayer, 0, 1) val status = general.buy(testPlayer, 0, 1)
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success, "Transaction failure: ${if(status is Shop.TransactionStatus.Failure) status.reason else ""}") assertTransactionSuccess(status)
} }
@Test fun shouldAllowStandardPlayerToBuyPlayerStock() { @Test fun shouldAllowStandardPlayerToBuyPlayerStock() {
@@ -80,7 +213,7 @@ class ShopTests {
testPlayer.setAttribute("shop-main", false) testPlayer.setAttribute("shop-main", false)
general.playerStock.add(Item(4151, 100)) general.playerStock.add(Item(4151, 100))
val status = general.buy(testPlayer, 0, 1) val status = general.buy(testPlayer, 0, 1)
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success, "Transaction failure: ${if(status is Shop.TransactionStatus.Failure) status.reason else ""}") assertTransactionSuccess(status)
} }
@Test fun shouldNotAllowIronmanToBuyOverstock() { @Test fun shouldNotAllowIronmanToBuyOverstock() {
@@ -128,11 +261,11 @@ class ShopTests {
testPlayer.inventory.add(Item(995, 100000)) testPlayer.inventory.add(Item(995, 100000))
testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer)) testPlayer.setAttribute("shop-cont", general.getContainer(testPlayer))
testPlayer.setAttribute("shop-main", false) testPlayer.setAttribute("shop-main", false)
var status: Shop.TransactionStatus = Shop.TransactionStatus.Success() var status: Shop.TransactionStatus = Shop.TransactionStatus.Failure("Test did not assign transaction status.")
Assertions.assertDoesNotThrow({ Assertions.assertDoesNotThrow({
status = general.buy(testPlayer, 36, 1) status = general.buy(testPlayer, 36, 1)
}, "Error selling to shop: ${general.playerStock}") }, "Error selling to shop: ${general.playerStock}")
Assertions.assertEquals(true, status is Shop.TransactionStatus.Success) assertTransactionSuccess(status)
} }
@Test fun invalidStockJsonShouldNotCauseItemShift() { @Test fun invalidStockJsonShouldNotCauseItemShift() {
+7 -2
View File
@@ -22,8 +22,13 @@ object TestUtils {
return p return p
} }
fun getMockShop(name: String, general: Boolean, vararg stock: Item) : Shop { fun getMockShop(name: String, general: Boolean, highAlch: Boolean, vararg stock: Item) : Shop {
return Shop(name, stock.map { ShopItem(it.id, it.amount, 100) }.toTypedArray(), general) return Shop(
name,
stock.map { ShopItem(it.id, it.amount, 100) }.toTypedArray(),
general,
highAlch = highAlch
)
} }
fun preTestSetup() { fun preTestSetup() {