mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +00:00
Refactor shops plugin
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
package org.apollo.plugin.locations.alKharid
|
||||
|
||||
import org.apollo.game.plugin.shops.shop
|
||||
import org.apollo.game.plugin.shops.builder.shop
|
||||
|
||||
shop("Al-Kharid General Store") {
|
||||
operated by "Shop keeper"(524) and "Shop assistant"(525)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.apollo.plugin.locations.edgeville
|
||||
|
||||
import org.apollo.game.plugin.shops.shop
|
||||
import org.apollo.game.plugin.shops.builder.shop
|
||||
|
||||
shop("Edgeville General Store") {
|
||||
operated by "Shop keeper"(528) and "Shop assistant"(529)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.apollo.plugin.locations.falador
|
||||
|
||||
import org.apollo.game.plugin.shops.shop
|
||||
import org.apollo.game.plugin.shops.builder.shop
|
||||
|
||||
shop("Falador General Store") {
|
||||
operated by "Shop keeper"(524) and "Shop assistant"( 525)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.apollo.plugin.locations.lumbridge
|
||||
|
||||
import org.apollo.game.plugin.shops.shop
|
||||
import org.apollo.game.plugin.shops.builder.shop
|
||||
|
||||
shop("Lumbridge General Store") {
|
||||
operated by "Shop keeper" and "Shop assistant"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.apollo.plugin.locations.varrock
|
||||
|
||||
import org.apollo.game.plugin.shops.shop
|
||||
import org.apollo.game.plugin.shops.builder.shop
|
||||
|
||||
shop("Aubury's Rune Shop.") {
|
||||
operated by "Aubury"
|
||||
|
||||
@@ -1,348 +0,0 @@
|
||||
package org.apollo.game.plugin.shops
|
||||
|
||||
import org.apollo.cache.def.NpcDefinition
|
||||
import org.apollo.game.plugin.api.Definitions
|
||||
import org.apollo.game.plugin.shops.CategoryWrapper.Affix
|
||||
|
||||
/**
|
||||
* Creates a [Shop].
|
||||
*
|
||||
* @param name The name of the shop.
|
||||
*/
|
||||
fun shop(name: String, builder: ShopBuilder.() -> Unit) {
|
||||
val shop = ShopBuilder(name)
|
||||
builder(shop)
|
||||
|
||||
val built = shop.build()
|
||||
val operators = shop.operators().map { it to built }.toMap()
|
||||
|
||||
SHOPS.putAll(operators)
|
||||
}
|
||||
|
||||
/**
|
||||
* A [DslMarker] for the shop DSL.
|
||||
*/
|
||||
@DslMarker
|
||||
annotation class ShopDslMarker
|
||||
|
||||
/**
|
||||
* A builder for a [Shop].
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class ShopBuilder(val name: String) {
|
||||
|
||||
/**
|
||||
* Overloads function invokation on strings to map `"ambiguous_npc_name"(id)` to a [Pair].
|
||||
*/
|
||||
operator fun String.invoke(id: Int): Pair<String, Int> = Pair(this, id)
|
||||
|
||||
/**
|
||||
* Adds a sequence of items to this Shop, grouped together (in the DSL) for convenience. Items will be displayed
|
||||
* in the same order they are provided.
|
||||
*
|
||||
* @param name The name of the category.
|
||||
* @param affix The method of affixation between the item and category name (see [Affix]).
|
||||
* @param depluralise Whether or not the category name should have the "s".
|
||||
* @param builder The builder used to add items to the category.
|
||||
*/
|
||||
fun category(
|
||||
name: String,
|
||||
affix: Affix = Affix.Suffix,
|
||||
depluralise: Boolean = true,
|
||||
builder: CategoryWrapper.() -> Unit
|
||||
) {
|
||||
val items = mutableListOf<Pair<String, Int>>()
|
||||
builder.invoke(CategoryWrapper(items))
|
||||
|
||||
val category = when {
|
||||
depluralise -> name.removeSuffix("s")
|
||||
else -> name
|
||||
}
|
||||
|
||||
val affixed = items.map { (name, amount) -> Pair(affix.join(name, category), amount) }
|
||||
sold.addAll(affixed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [SellBuilder] with the specified [amount].
|
||||
*/
|
||||
fun sell(amount: Int): SellBuilder = SellBuilder(amount, sold)
|
||||
|
||||
/**
|
||||
* The id on the operator npc's action menu used to open the shop.
|
||||
*/
|
||||
val action = ActionBuilder()
|
||||
|
||||
/**
|
||||
* The type of [Currency] the [Shop] makes exchanges with.
|
||||
*/
|
||||
var trades = CurrencyBuilder()
|
||||
|
||||
/**
|
||||
* The [Shop]'s policy towards purchasing items from players.
|
||||
*/
|
||||
var buys = PurchasesBuilder()
|
||||
|
||||
/**
|
||||
* Redundant variable used only to complete the [PurchasesBuilder] (e.g. `buys no items`).
|
||||
*/
|
||||
val items = Unit
|
||||
|
||||
/**
|
||||
* Places the category name before the item name (inserting a space between the names).
|
||||
*/
|
||||
val prefix = Affix.Prefix
|
||||
|
||||
/**
|
||||
* Prevents the category name from being joined to the item name in any way.
|
||||
*/
|
||||
val nothing = Affix.None
|
||||
|
||||
/**
|
||||
* The [OperatorBuilder] used to collate the [Shop]'s operators.
|
||||
*/
|
||||
val operated = OperatorBuilder()
|
||||
|
||||
/**
|
||||
* The [List] of items sold by the shop, as (name, amount) [Pair]s.
|
||||
*/
|
||||
private val sold = mutableListOf<Pair<String, Int>>()
|
||||
|
||||
/**
|
||||
* Converts this builder into a [Shop].
|
||||
*/
|
||||
internal fun build(): Shop {
|
||||
val items = sold.associateBy({ (first) -> Definitions.item(first)!!.id }, Pair<String, Int>::second)
|
||||
val npc = NpcDefinition.lookup(operators().first())
|
||||
|
||||
return Shop(name, action.action(npc), items, trades.currency, buys.policy)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the [List] of shop operator ids.
|
||||
*/
|
||||
internal fun operators(): MutableList<Int> = operated.operators
|
||||
}
|
||||
|
||||
@ShopDslMarker
|
||||
class CategoryWrapper(private val items: MutableList<Pair<String, Int>>) {
|
||||
|
||||
/**
|
||||
* The method of joining the item and category name.
|
||||
*/
|
||||
sealed class Affix(private val joiner: (item: String, category: String) -> String) {
|
||||
|
||||
/**
|
||||
* Appends the category after the item name (with a space between).
|
||||
*/
|
||||
object Suffix : Affix({ item, affix -> "$item $affix" })
|
||||
|
||||
/**
|
||||
* Prepends the category before the item name (with a space between).
|
||||
*/
|
||||
object Prefix : Affix({ item, affix -> "$affix $item" })
|
||||
|
||||
/**
|
||||
* Does not join the category at all (i.e. only returns the item name).
|
||||
*/
|
||||
object None : Affix({ item, _ -> item })
|
||||
|
||||
/**
|
||||
* Joins the item and category name in the expected manner.
|
||||
*/
|
||||
fun join(item: String, category: String): String = joiner(item, category)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [SellBuilder] with the specified [amount].
|
||||
*/
|
||||
fun sell(amount: Int): SellBuilder = SellBuilder(amount, items)
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder to provide the list of shop operators - the npcs that can be interacted with to access the shop.
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class OperatorBuilder internal constructor() {
|
||||
|
||||
/**
|
||||
* The [List] of shop operators.
|
||||
*/
|
||||
val operators: MutableList<Int> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Adds a shop operator, using the specified [name] to resolve the npc id.
|
||||
*/
|
||||
infix fun by(name: String): OperatorBuilder {
|
||||
operators.add(Definitions.npc(name)!!.id)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a shop operator, using the specified [name] to resolve the npc id.
|
||||
*/
|
||||
infix fun and(name: String): OperatorBuilder = by(name)
|
||||
|
||||
/**
|
||||
* Adds a shop operator, using the specified [name] to resolve the npc id.
|
||||
*/
|
||||
operator fun plus(name: String): OperatorBuilder = and(name)
|
||||
|
||||
/**
|
||||
* Adds a shop operator with the specified npc id. Intended to be used with the overloaded String invokation
|
||||
* operator, solely to disambiguate between npcs with the same name (e.g.
|
||||
* `"Shopkeeper"(500) vs `"Shopkeeper"(501)`). Use [by(String][by] if the npc name is unambiguous.
|
||||
*/
|
||||
infix fun by(pair: Pair<String, Int>): OperatorBuilder {
|
||||
operators.add(pair.second)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a shop operator with the specified npc id. Intended to be used with the overloaded String invokation
|
||||
* operator, solely to disambiguate between npcs with the same name (e.g.
|
||||
* `"Shopkeeper"(500) vs `"Shopkeeper"(501)`). Use [by(String][by] if the npc name is unambiguous.
|
||||
*/
|
||||
infix fun and(pair: Pair<String, Int>): OperatorBuilder = by(pair)
|
||||
|
||||
/**
|
||||
* Adds a shop operator with the specified npc id. Intended to be used with the overloaded String invokation
|
||||
* operator, solely to disambiguate between npcs with the same name (e.g.
|
||||
* `"Shopkeeper"(500) vs `"Shopkeeper"(501)`). Use [by(String][by] if the npc name is unambiguous.
|
||||
*/
|
||||
operator fun plus(pair: Pair<String, Int>): OperatorBuilder = by(pair)
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder to provide the action id used to open the shop.
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class ActionBuilder {
|
||||
|
||||
private var action: String = "Trade"
|
||||
|
||||
private var actionId: Int? = null
|
||||
|
||||
/**
|
||||
* Sets the name or id of the action used to open the shop interface with an npc. Defaults to "Trade".
|
||||
*
|
||||
* If specifying an id it must account for hidden npc menu actions (if any exist) - if "Open Shop" is the first
|
||||
* action displayed when the npc is right-clicked, it does not necessarily mean that the action id is `1`.
|
||||
*
|
||||
* @param action The `name` (as a [String]) or `id` (as an `Int`) of the npc's action menu, to open the shop.
|
||||
* @throws IllegalArgumentException If `action` is not a [String] or [Int].
|
||||
*/
|
||||
override fun equals(@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") action: Any?): Boolean {
|
||||
if (action is String) {
|
||||
this.action = action
|
||||
return true
|
||||
} else if (action is Int) {
|
||||
actionId = action
|
||||
return true
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("The Npc option must be provided as a String (the option name) or the ")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the open shop action slot.
|
||||
*
|
||||
* @throws IllegalArgumentException If the action id or name is invalid.
|
||||
*/
|
||||
internal fun action(npc: NpcDefinition): Int {
|
||||
actionId?.let { action ->
|
||||
if (npc.hasInteraction(action - 1)) { // ActionMessages are 1-based
|
||||
return action
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Npc ${npc.name} does not have an an action $action.")
|
||||
}
|
||||
|
||||
val index = npc.interactions.indexOf(action)
|
||||
when (index) {
|
||||
-1 -> throw IllegalArgumentException("Npc ${npc.name} does not have an an action $action.")
|
||||
else -> return index + 1 // ActionMessages are 1-based
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws [UnsupportedOperationException].
|
||||
*/
|
||||
override fun hashCode(): Int = throw UnsupportedOperationException("ActionBuilder is a utility class for a DSL " +
|
||||
"and improperly implements equals() - it should not be used anywhere outside of the DSL.")
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder to provide the currency used by the [Shop].
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class CurrencyBuilder {
|
||||
|
||||
internal var currency = Currency.COINS
|
||||
|
||||
/**
|
||||
* Overloads the `in` operator on [Currency] to achieve e.g. `trades in tokkul`.
|
||||
*/
|
||||
operator fun Currency.contains(builder: CurrencyBuilder): Boolean {
|
||||
builder.currency = this
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder to provide the [Shop.PurchasePolicy].
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class PurchasesBuilder {
|
||||
|
||||
internal var policy = Shop.PurchasePolicy.OWNED
|
||||
|
||||
/**
|
||||
* Instructs the shop to purchase no items, regardless of whether or not it sells it.
|
||||
*/
|
||||
infix fun no(@Suppress("UNUSED_PARAMETER") items: Unit) {
|
||||
policy = Shop.PurchasePolicy.NOTHING
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the shop to purchase any tradeable item.
|
||||
*/
|
||||
infix fun any(@Suppress("UNUSED_PARAMETER") items: Unit) {
|
||||
policy = Shop.PurchasePolicy.ANY
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder to provide the items to sell.
|
||||
*
|
||||
* @param amount The amount to sell (of each item).
|
||||
* @param items The [MutableList] to insert the given items into.
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class SellBuilder(val amount: Int, val items: MutableList<Pair<String, Int>>) {
|
||||
|
||||
infix fun of(lambda: SellBuilder.() -> Unit) = lambda.invoke(this)
|
||||
|
||||
/**
|
||||
* Provides an item with the specified name.
|
||||
*
|
||||
* @name The item name. Must be unambiguous.
|
||||
*/
|
||||
infix fun of(name: String) = items.add(Pair(name, amount))
|
||||
|
||||
/**
|
||||
* Overloads unary minus on Strings so that item names can be listed.
|
||||
*/
|
||||
operator fun String.unaryMinus() = items.add(Pair(this, amount))
|
||||
|
||||
/**
|
||||
* Overloads the unary minus on Pairs so that name+id pairs can be listed. Only intended to be used with the
|
||||
* overloaded String invokation operator.
|
||||
*/ // ShopBuilder uses the lookup plugin, which can operate on _ids tacked on the end
|
||||
operator fun Pair<String, Int>.unaryMinus() = items.add(Pair("${this.first}_${this.second}", amount))
|
||||
|
||||
/**
|
||||
* Overloads function invokation on Strings to map `"ambiguous_npc_name"(id)` to a [Pair].
|
||||
*/
|
||||
operator fun String.invoke(id: Int): Pair<String, Int> = Pair(this, id)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.apollo.game.plugin.shops
|
||||
|
||||
import org.apollo.game.plugin.api.Definitions
|
||||
|
||||
/**
|
||||
* A [Shop]'s method of payment.
|
||||
*
|
||||
* @param id The item id of the currency.
|
||||
* @param plural Whether or not the name of this currency is plural.
|
||||
*/
|
||||
data class Currency(val id: Int, val plural: Boolean = false) {
|
||||
|
||||
val name = requireNotNull(Definitions.item(id).name?.toLowerCase()) { "Currencies must have a name." }
|
||||
|
||||
fun name(amount: Int): String {
|
||||
return when {
|
||||
amount == 1 && plural -> name.removeSuffix("s")
|
||||
else -> name
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val COINS = Currency(995, plural = true)
|
||||
}
|
||||
|
||||
}
|
||||
+15
-12
@@ -1,7 +1,7 @@
|
||||
package org.apollo.game.plugin.shops
|
||||
|
||||
import org.apollo.game.action.DistancedAction
|
||||
import org.apollo.game.message.handler.ItemVerificationHandler.InventorySupplier
|
||||
import org.apollo.game.message.handler.ItemVerificationHandler
|
||||
import org.apollo.game.message.impl.SetWidgetTextMessage
|
||||
import org.apollo.game.model.entity.Mob
|
||||
import org.apollo.game.model.entity.Player
|
||||
@@ -15,16 +15,17 @@ import org.apollo.game.model.inv.SynchronizationInventoryListener
|
||||
class OpenShopAction(
|
||||
player: Player,
|
||||
private val shop: Shop,
|
||||
val npc: Mob
|
||||
) : DistancedAction<Player>(0, true, player, npc.position, 1) { // TODO this needs to follow the NPC if they move
|
||||
private val operator: Mob
|
||||
) : DistancedAction<Player>(0, true, player, operator.position, 1) { // TODO this needs to follow the NPC if they move
|
||||
|
||||
override fun executeAction() {
|
||||
mob.interactingMob = npc
|
||||
mob.interactingMob = operator
|
||||
|
||||
val closeListener = addInventoryListeners(mob, shop.inventory)
|
||||
mob.send(SetWidgetTextMessage(Interfaces.SHOP_NAME, shop.name))
|
||||
mob.send(SetWidgetTextMessage(ShopInterfaces.SHOP_NAME, shop.name))
|
||||
|
||||
mob.interfaceSet.openWindowWithSidebar(closeListener, Interfaces.SHOP_WINDOW, Interfaces.INVENTORY_SIDEBAR)
|
||||
mob.interfaceSet.openWindowWithSidebar(closeListener, ShopInterfaces.SHOP_WINDOW,
|
||||
ShopInterfaces.INVENTORY_SIDEBAR)
|
||||
stop()
|
||||
}
|
||||
|
||||
@@ -33,8 +34,8 @@ class OpenShopAction(
|
||||
* [InterfaceListener] that removes them when the interface is closed.
|
||||
*/
|
||||
private fun addInventoryListeners(player: Player, shop: Inventory): InterfaceListener {
|
||||
val invListener = SynchronizationInventoryListener(player, Interfaces.INVENTORY_CONTAINER)
|
||||
val shopListener = SynchronizationInventoryListener(player, Interfaces.SHOP_CONTAINER)
|
||||
val invListener = SynchronizationInventoryListener(player, ShopInterfaces.INVENTORY_CONTAINER)
|
||||
val shopListener = SynchronizationInventoryListener(player, ShopInterfaces.SHOP_CONTAINER)
|
||||
|
||||
player.inventory.addListener(invListener)
|
||||
player.inventory.forceRefresh()
|
||||
@@ -55,12 +56,14 @@ class OpenShopAction(
|
||||
/**
|
||||
* An [InventorySupplier] that returns a [Player]'s [Inventory] if they are browsing a shop.
|
||||
*/
|
||||
class PlayerInventorySupplier : InventorySupplier {
|
||||
object PlayerInventorySupplier : ItemVerificationHandler.InventorySupplier {
|
||||
|
||||
override fun getInventory(player: Player): Inventory? {
|
||||
return when {
|
||||
player.interfaceSet.contains(Interfaces.SHOP_WINDOW) -> player.inventory
|
||||
else -> null
|
||||
return if (Interfaces.SHOP_WINDOW in player.interfaceSet) {
|
||||
player.inventory
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+63
-82
@@ -47,35 +47,13 @@ object Interfaces {
|
||||
*/
|
||||
val SHOPS = mutableMapOf<Int, Shop>()
|
||||
|
||||
/**
|
||||
* A [Shop]'s method of payment.
|
||||
*
|
||||
* @param id The item id of the currency.
|
||||
* @param plural Whether or not the name of this currency is plural.
|
||||
*/
|
||||
data class Currency(val id: Int, val plural: Boolean = false) {
|
||||
|
||||
companion object {
|
||||
val COINS = Currency(995, plural = true)
|
||||
}
|
||||
|
||||
val name: String = ItemDefinition.lookup(id).name?.toLowerCase()
|
||||
?: throw IllegalArgumentException("Currencies must have a name.")
|
||||
|
||||
fun name(amount: Int): String {
|
||||
return when {
|
||||
amount == 1 && plural -> name.removeSuffix("s")
|
||||
else -> name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An in-game shop, operated by one or more npcs.
|
||||
*
|
||||
* @param name The name of the shop.
|
||||
* @param action The id of the NpcActionMessage sent (by the client) when a player opens this shop.
|
||||
* @param sells The [Map] from item id to amount sold.
|
||||
* @param operators The [List] of Npc ids that can open this shop.
|
||||
* @param currency The [Currency] used when making exchanges with this [Shop].
|
||||
* @param purchases This [Shop]'s attitude towards purchasing items from players.
|
||||
*/
|
||||
@@ -83,68 +61,11 @@ class Shop(
|
||||
val name: String,
|
||||
val action: Int,
|
||||
private val sells: Map<Int, Int>,
|
||||
val operators: List<Int>,
|
||||
private val currency: Currency = Currency.COINS,
|
||||
private val purchases: PurchasePolicy = OWNED
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* The amount of pulses between shop inventory restocking.
|
||||
*/
|
||||
const val RESTOCK_INTERVAL = 100
|
||||
|
||||
/**
|
||||
* The capacity of a [Shop].
|
||||
*/
|
||||
private const val CAPACITY = 30
|
||||
|
||||
/**
|
||||
* The type of exchange occurring between the [Player] and [Shop].
|
||||
*/
|
||||
private enum class ExchangeType { BUYING, SELLING }
|
||||
|
||||
/**
|
||||
* The option id for item valuation.
|
||||
*/
|
||||
private const val VALUATION_OPTION = 1
|
||||
|
||||
/**
|
||||
* Returns the amount that a player tried to buy or sell.
|
||||
*
|
||||
* @param option The id of the option the player selected.
|
||||
*/
|
||||
private fun amount(option: Int): Int {
|
||||
return when (option) {
|
||||
2 -> 1
|
||||
3 -> 5
|
||||
4 -> 10
|
||||
else -> throw IllegalArgumentException("Option must be 1-4")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The [Shop]s policy regarding purchasing items from players.
|
||||
*/
|
||||
enum class PurchasePolicy {
|
||||
|
||||
/**
|
||||
* Never purchase anything from players.
|
||||
*/
|
||||
NOTHING,
|
||||
|
||||
/**
|
||||
* Only purchase items that this Shop sells by default.
|
||||
*/
|
||||
OWNED,
|
||||
|
||||
/**
|
||||
* Purchase any tradeable items.
|
||||
*/
|
||||
ANY
|
||||
}
|
||||
|
||||
/**
|
||||
* The [Inventory] containing this [Shop]'s current items.
|
||||
*/
|
||||
@@ -183,7 +104,7 @@ class Shop(
|
||||
return
|
||||
}
|
||||
|
||||
var buying: Int = amount(option)
|
||||
var buying = amount(option)
|
||||
var unavailable = false
|
||||
|
||||
val amount = item.amount
|
||||
@@ -328,4 +249,64 @@ class Shop(
|
||||
* @param id The id of the [Item] to sell.
|
||||
*/
|
||||
private fun sells(id: Int): Boolean = sells.containsKey(id)
|
||||
|
||||
/**
|
||||
* The [Shop]s policy regarding purchasing items from players.
|
||||
*/
|
||||
enum class PurchasePolicy {
|
||||
|
||||
/**
|
||||
* Never purchase anything from players.
|
||||
*/
|
||||
NOTHING,
|
||||
|
||||
/**
|
||||
* Only purchase items that this Shop sells by default.
|
||||
*/
|
||||
OWNED,
|
||||
|
||||
/**
|
||||
* Purchase any tradeable items.
|
||||
*/
|
||||
ANY
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* The amount of pulses between shop inventory restocking.
|
||||
*/
|
||||
const val RESTOCK_INTERVAL = 100
|
||||
|
||||
/**
|
||||
* The capacity of a [Shop].
|
||||
*/
|
||||
private const val CAPACITY = 30
|
||||
|
||||
/**
|
||||
* The type of exchange occurring between the [Player] and [Shop].
|
||||
*/
|
||||
private enum class ExchangeType { BUYING, SELLING }
|
||||
|
||||
/**
|
||||
* The option id for item valuation.
|
||||
*/
|
||||
private const val VALUATION_OPTION = 1
|
||||
|
||||
/**
|
||||
* Returns the amount that a player tried to buy or sell.
|
||||
*
|
||||
* @param option The id of the option the player selected.
|
||||
*/
|
||||
private fun amount(option: Int): Int {
|
||||
return when (option) {
|
||||
2 -> 1
|
||||
3 -> 5
|
||||
4 -> 10
|
||||
else -> throw IllegalArgumentException("Option must be 1-4")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.apollo.game.plugin.shops
|
||||
|
||||
/**
|
||||
* Contains shop-related interface ids.
|
||||
*/
|
||||
internal object ShopInterfaces {
|
||||
|
||||
/**
|
||||
* The container interface id for the player's inventory.
|
||||
*/
|
||||
const val INVENTORY_CONTAINER = 3823
|
||||
|
||||
/**
|
||||
* The sidebar id for the inventory, when a Shop window is open.
|
||||
*/
|
||||
const val INVENTORY_SIDEBAR = 3822
|
||||
|
||||
/**
|
||||
* The shop window interface id.
|
||||
*/
|
||||
const val SHOP_WINDOW = 3824
|
||||
|
||||
/**
|
||||
* The container interface id for the shop's inventory.
|
||||
*/
|
||||
const val SHOP_CONTAINER = 3900
|
||||
|
||||
/**
|
||||
* The id of the text widget that displays a shop's name.
|
||||
*/
|
||||
const val SHOP_NAME = 3901
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.apollo.game.plugin.shops
|
||||
|
||||
import org.apollo.game.message.handler.ItemVerificationHandler
|
||||
import org.apollo.game.message.impl.ItemActionMessage
|
||||
import org.apollo.game.message.impl.NpcActionMessage
|
||||
import org.apollo.game.model.entity.Mob
|
||||
import org.apollo.game.scheduling.ScheduledTask
|
||||
|
||||
fun Mob.shop(): Shop? = SHOPS[definition.id]
|
||||
|
||||
start { world ->
|
||||
ItemVerificationHandler.addInventory(ShopInterfaces.SHOP_CONTAINER) { it.interactingMob?.shop()?.inventory }
|
||||
ItemVerificationHandler.addInventory(ShopInterfaces.INVENTORY_CONTAINER, PlayerInventorySupplier)
|
||||
|
||||
world.schedule(object : ScheduledTask(Shop.RESTOCK_INTERVAL, false) {
|
||||
override fun execute() = SHOPS.values.distinct().forEach(Shop::restock)
|
||||
})
|
||||
}
|
||||
|
||||
on { NpcActionMessage::class }
|
||||
.then { player ->
|
||||
val npc = player.world.npcRepository.get(index)
|
||||
val shop = npc.shop() ?: return@then
|
||||
|
||||
if (shop.action == option) {
|
||||
player.startAction(OpenShopAction(player, shop, npc))
|
||||
terminate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
on { ItemActionMessage::class }
|
||||
.where { interfaceId == ShopInterfaces.SHOP_CONTAINER || interfaceId == ShopInterfaces.INVENTORY_CONTAINER }
|
||||
.then { player ->
|
||||
if (ShopInterfaces.SHOP_WINDOW !in player.interfaceSet) {
|
||||
return@then
|
||||
}
|
||||
|
||||
val shop = player.interactingMob?.shop() ?: return@then
|
||||
when (interfaceId) {
|
||||
ShopInterfaces.INVENTORY_CONTAINER -> shop.buy(player, slot, option)
|
||||
ShopInterfaces.SHOP_CONTAINER -> shop.sell(player, slot, option)
|
||||
else -> error("Supposedly unreacheable case.")
|
||||
}
|
||||
|
||||
terminate()
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.apollo.game.plugin.shops.builder
|
||||
|
||||
import org.apollo.cache.def.NpcDefinition
|
||||
|
||||
/**
|
||||
* A builder to provide the action id used to open the shop.
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class ActionBuilder {
|
||||
|
||||
private var action: String = "Trade"
|
||||
|
||||
private var actionId: Int? = null
|
||||
|
||||
/**
|
||||
* Sets the name or id of the action used to open the shop interface with an npc. Defaults to "Trade".
|
||||
*
|
||||
* If specifying an id it must account for hidden npc menu actions (if any exist) - if "Open Shop" is the first
|
||||
* action displayed when the npc is right-clicked, it does not necessarily mean that the action id is `1`.
|
||||
*
|
||||
* @param action The `name` (as a [String]) or `id` (as an `Int`) of the npc's action menu, to open the shop.
|
||||
* @throws IllegalArgumentException If `action` is not a [String] or [Int].
|
||||
*/ // TODO this is dumb, replace it
|
||||
override fun equals(@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") action: Any?): Boolean {
|
||||
if (action is String) {
|
||||
this.action = action
|
||||
return true
|
||||
} else if (action is Int) {
|
||||
actionId = action
|
||||
return true
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("The Npc option must be provided as a String (the option name) or an Int (the option index)\"")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the open shop action slot.
|
||||
*
|
||||
* @throws IllegalArgumentException If the action id or name is invalid.
|
||||
*/
|
||||
internal fun slot(npc: NpcDefinition): Int {
|
||||
actionId?.let { action ->
|
||||
require(npc.hasInteraction(action - 1)) {
|
||||
"Npc ${npc.name} does not have an an action $action." // action - 1 because ActionMessages are 1-based
|
||||
}
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
val index = npc.interactions.indexOf(action)
|
||||
require(index != -1) { "Npc ${npc.name} does not have an an action $action." }
|
||||
|
||||
return index + 1 // ActionMessages are 1-based
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws [UnsupportedOperationException].
|
||||
*/
|
||||
override fun hashCode(): Int = throw UnsupportedOperationException("ActionBuilder is a utility class for a DSL " +
|
||||
"and improperly implements equals() - it should not be used anywhere outside of the DSL.")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.apollo.game.plugin.shops.builder
|
||||
|
||||
/**
|
||||
* A builder for a category - a collection of sold items that share a common prefix or suffix.
|
||||
*
|
||||
* ```
|
||||
* category("mould") {
|
||||
* sell(10) of "Ring"
|
||||
* sell(2) of "Necklace"
|
||||
* sell(10) of "Amulet"
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class CategoryBuilder {
|
||||
|
||||
/**
|
||||
* The items that this shop sells, as a pair of item name to amount sold.
|
||||
*/
|
||||
private val items = mutableListOf<Pair<String, Int>>()
|
||||
|
||||
/**
|
||||
* Creates a [SellBuilder] with the specified [amount].
|
||||
*/
|
||||
fun sell(amount: Int): SellBuilder = SellBuilder(amount, items)
|
||||
|
||||
/**
|
||||
* Builds this category into a list of sold items, represented as a pair of item name to amount sold.
|
||||
*/
|
||||
fun build(): List<Pair<String, Int>> = items
|
||||
|
||||
/**
|
||||
* The method of joining the item and category name.
|
||||
*/
|
||||
sealed class Affix(private val joiner: (item: String, category: String) -> String) {
|
||||
|
||||
/**
|
||||
* Appends the category after the item name (with a space between).
|
||||
*/
|
||||
object Suffix : Affix({ item, affix -> "$item $affix" })
|
||||
|
||||
/**
|
||||
* Prepends the category before the item name (with a space between).
|
||||
*/
|
||||
object Prefix : Affix({ item, affix -> "$affix $item" })
|
||||
|
||||
/**
|
||||
* Does not join the category at all (i.e. only returns the item name).
|
||||
*/
|
||||
object None : Affix({ item, _ -> item })
|
||||
|
||||
/**
|
||||
* Joins the item and category name in the expected manner.
|
||||
*/
|
||||
fun join(item: String, category: String): String = joiner(item, category)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.apollo.game.plugin.shops.builder
|
||||
|
||||
import org.apollo.game.plugin.shops.Currency
|
||||
|
||||
/**
|
||||
* A builder to provide the currency used by the [Shop].
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class CurrencyBuilder {
|
||||
|
||||
private var currency = Currency.COINS
|
||||
|
||||
/**
|
||||
* Overloads the `in` operator on [Currency] to achieve e.g. `trades in tokkul`.
|
||||
*
|
||||
* This function violates the contract for the `in` operator and is only to be used inside the Shops DSL.
|
||||
*/
|
||||
operator fun Currency.contains(builder: CurrencyBuilder): Boolean {
|
||||
builder.currency = this
|
||||
return true
|
||||
}
|
||||
|
||||
fun build(): Currency = currency
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.apollo.game.plugin.shops.builder
|
||||
|
||||
import org.apollo.game.plugin.api.Definitions
|
||||
|
||||
/**
|
||||
* A builder to provide the list of shop operators - the npcs that can be interacted with to access the shop.
|
||||
*
|
||||
* ```
|
||||
* shop("General Store.") {
|
||||
* operated by "Shopkeeper"(522) and "Shop assistant"(523) and "Shop assistant"(524)
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class OperatorBuilder internal constructor(private val shopName: String) {
|
||||
|
||||
/**
|
||||
* The [List] of shop operator ids.
|
||||
*/
|
||||
private val operators = mutableListOf<Int>()
|
||||
|
||||
/**
|
||||
* Adds a shop operator, using the specified [name] to resolve the npc id.
|
||||
*/
|
||||
infix fun by(name: String): OperatorBuilder {
|
||||
val npc = requireNotNull(Definitions.npc(name)) {
|
||||
"Failed to resolve npc named `$name` when building shop $shopName."
|
||||
}
|
||||
|
||||
operators += npc.id
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a shop operator, using the specified [name] to resolve the npc id.
|
||||
*
|
||||
* An alias for [by].
|
||||
*/
|
||||
infix fun and(name: String): OperatorBuilder = by(name)
|
||||
|
||||
/**
|
||||
* Adds a shop operator, using the specified [name] to resolve the npc id.
|
||||
*
|
||||
* An alias for [by].
|
||||
*/
|
||||
operator fun plus(name: String): OperatorBuilder = and(name)
|
||||
|
||||
/**
|
||||
* Adds a shop operator with the specified npc id. Intended to be used with the overloaded String invokation
|
||||
* operator, solely to disambiguate between npcs with the same name (e.g. `"Shopkeeper"(500) vs
|
||||
* `"Shopkeeper"(501)`). Use [by(String)][by] if the npc name is unambiguous.
|
||||
*/
|
||||
infix fun by(pair: Pair<String, Int>): OperatorBuilder {
|
||||
operators += pair.second
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a shop operator with the specified npc id. Intended to be used with the overloaded String invokation
|
||||
* operator, solely to disambiguate between npcs with the same name (e.g. `"Shopkeeper"(500) vs
|
||||
* `"Shopkeeper"(501)`). Use [by(String)][by] if the npc name is unambiguous.
|
||||
*
|
||||
* An alias for [by(Pair<String, Int>)][by].
|
||||
*/
|
||||
infix fun and(pair: Pair<String, Int>): OperatorBuilder = by(pair)
|
||||
|
||||
/**
|
||||
* Adds a shop operator with the specified npc id. Intended to be used with the overloaded String invokation
|
||||
* operator, solely to disambiguate between npcs with the same name (e.g. `"Shopkeeper"(500) vs
|
||||
* `"Shopkeeper"(501)`). Use [by(String)][by] if the npc name is unambiguous.
|
||||
*
|
||||
* An alias for [by(Pair<String, Int>)][by].
|
||||
*/
|
||||
operator fun plus(pair: Pair<String, Int>): OperatorBuilder = by(pair)
|
||||
|
||||
/**
|
||||
* Builds this [OperatorBuilder] into a [List] of operator npc ids.
|
||||
*/
|
||||
fun build(): List<Int> = operators
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.apollo.game.plugin.shops.builder
|
||||
|
||||
import org.apollo.game.plugin.shops.Shop
|
||||
|
||||
/**
|
||||
* A builder to provide the [Shop.PurchasePolicy].
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class PurchasesBuilder {
|
||||
|
||||
private var policy = Shop.PurchasePolicy.OWNED
|
||||
|
||||
/**
|
||||
* Instructs the shop to purchase no items, regardless of whether or not it sells it.
|
||||
*/
|
||||
infix fun no(@Suppress("UNUSED_PARAMETER") items: Unit) {
|
||||
policy = Shop.PurchasePolicy.NOTHING
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the shop to purchase any tradeable item.
|
||||
*/
|
||||
infix fun any(@Suppress("UNUSED_PARAMETER") items: Unit) {
|
||||
policy = Shop.PurchasePolicy.ANY
|
||||
}
|
||||
|
||||
fun build(): Shop.PurchasePolicy = policy
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.apollo.game.plugin.shops.builder
|
||||
|
||||
/**
|
||||
* A builder to provide the items to sell.
|
||||
*
|
||||
* @param amount The amount to sell (of each item).
|
||||
* @param items The [MutableList] to insert the given items into.
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class SellBuilder(val amount: Int, val items: MutableList<Pair<String, Int>>) {
|
||||
|
||||
infix fun of(lambda: SellBuilder.() -> Unit) = lambda(this)
|
||||
|
||||
/**
|
||||
* Provides an item with the specified name.
|
||||
*
|
||||
* @name The item name. Must be unambiguous.
|
||||
*/
|
||||
infix fun of(name: String) {
|
||||
items += Pair(name, amount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloads unary minus on Strings so that item names can be listed.
|
||||
*/
|
||||
operator fun String.unaryMinus() {
|
||||
of(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloads the unary minus on Pairs so that name+id pairs can be listed. Only intended to be used with the
|
||||
* overloaded String invokation operator.
|
||||
*/ // ShopBuilder uses the lookup plugin, which can operate on _ids tacked on the end
|
||||
operator fun Pair<String, Int>.unaryMinus() {
|
||||
items += Pair("${first}_$second", amount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloads function invokation on Strings to map `"ambiguous_npc_name"(id)` to a [Pair].
|
||||
*/
|
||||
operator fun String.invoke(id: Int): Pair<String, Int> = Pair(this, id)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.apollo.game.plugin.shops.builder
|
||||
|
||||
import org.apollo.cache.def.NpcDefinition
|
||||
import org.apollo.game.plugin.api.Definitions
|
||||
import org.apollo.game.plugin.shops.Currency
|
||||
import org.apollo.game.plugin.shops.SHOPS
|
||||
import org.apollo.game.plugin.shops.Shop
|
||||
import org.apollo.game.plugin.shops.builder.CategoryBuilder.Affix
|
||||
|
||||
/**
|
||||
* Creates a [Shop].
|
||||
*
|
||||
* @param name The name of the shop.
|
||||
*/
|
||||
fun shop(name: String, builder: ShopBuilder.() -> Unit) {
|
||||
val shop = ShopBuilder(name).apply(builder).build()
|
||||
|
||||
shop.operators.associateByTo(SHOPS, { it }, { shop })
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for a [Shop].
|
||||
*/
|
||||
@ShopDslMarker
|
||||
class ShopBuilder(val name: String) {
|
||||
|
||||
/**
|
||||
* The id on the operator npc's action menu used to open the shop.
|
||||
*/
|
||||
val action = ActionBuilder()
|
||||
|
||||
/**
|
||||
* The type of [Currency] the [Shop] makes exchanges with.
|
||||
*/
|
||||
var trades = CurrencyBuilder()
|
||||
|
||||
/**
|
||||
* The [OperatorBuilder] used to collate the [Shop]'s operators.
|
||||
*/
|
||||
val operated = OperatorBuilder(name)
|
||||
|
||||
/**
|
||||
* The [Shop]'s policy towards purchasing items from players.
|
||||
*/
|
||||
var buys = PurchasesBuilder()
|
||||
|
||||
/**
|
||||
* Redundant variable used in the purchases dsl, to complete the [PurchasesBuilder] (e.g. `buys no items`).
|
||||
*/
|
||||
val items = Unit
|
||||
|
||||
/**
|
||||
* Used in the category dsl. Places the category name before the item name (inserting a space between the names).
|
||||
*/
|
||||
val prefix = Affix.Prefix
|
||||
|
||||
/**
|
||||
* Used in the category dsl. Prevents the category name from being joined to the item name in any way.
|
||||
*/
|
||||
val nothing = Affix.None
|
||||
|
||||
/**
|
||||
* The [List] of items sold by the shop, as (name, amount) [Pair]s.
|
||||
*/
|
||||
private val sold = mutableListOf<Pair<String, Int>>()
|
||||
|
||||
/**
|
||||
* Overloads function invokation on strings to map `"ambiguous_npc_name"(id)` to a [Pair].
|
||||
*/
|
||||
operator fun String.invoke(id: Int): Pair<String, Int> = Pair(this, id)
|
||||
|
||||
/**
|
||||
* Adds a sequence of items to this Shop, grouped together (in the DSL) for convenience. Items will be displayed
|
||||
* in the same order they are provided.
|
||||
*
|
||||
* @param name The name of the category.
|
||||
* @param affix The method of affixation between the item and category name (see [Affix]).
|
||||
* @param depluralise Whether or not the category name should have the "s".
|
||||
* @param builder The builder that adds items to the category.
|
||||
*/
|
||||
fun category(
|
||||
name: String,
|
||||
affix: Affix = Affix.Suffix,
|
||||
depluralise: Boolean = true, // TODO search for both with and without plural
|
||||
builder: CategoryBuilder.() -> Unit
|
||||
) {
|
||||
val items = CategoryBuilder().apply(builder).build()
|
||||
|
||||
val category = when {
|
||||
depluralise -> name.removeSuffix("s")
|
||||
else -> name
|
||||
}
|
||||
|
||||
sold += items.map { (name, amount) -> Pair(affix.join(name, category), amount) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [SellBuilder] with the specified [amount].
|
||||
*/
|
||||
fun sell(amount: Int): SellBuilder = SellBuilder(amount, sold)
|
||||
|
||||
/**
|
||||
* Converts this builder into a [Shop].
|
||||
*/
|
||||
internal fun build(): Shop {
|
||||
val operators = operated.build()
|
||||
val npc = NpcDefinition.lookup(operators.first())
|
||||
|
||||
val items = sold.associateBy(
|
||||
{ requireNotNull(Definitions.item(it.first)?.id) { "Failed to find item ${it.first} in shop $name." } },
|
||||
{ it.second }
|
||||
)
|
||||
|
||||
return Shop(name, action.slot(npc), items, operators, trades.build(), buys.build())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.apollo.game.plugin.shops.builder
|
||||
|
||||
/**
|
||||
* A [DslMarker] for the shop DSL.
|
||||
*/
|
||||
@DslMarker
|
||||
internal annotation class ShopDslMarker
|
||||
@@ -1,50 +0,0 @@
|
||||
import org.apollo.game.message.handler.ItemVerificationHandler
|
||||
import org.apollo.game.message.impl.ItemActionMessage
|
||||
import org.apollo.game.message.impl.NpcActionMessage
|
||||
import org.apollo.game.model.entity.Mob
|
||||
import org.apollo.game.plugin.shops.Interfaces
|
||||
import org.apollo.game.plugin.shops.OpenShopAction
|
||||
import org.apollo.game.plugin.shops.PlayerInventorySupplier
|
||||
import org.apollo.game.plugin.shops.SHOPS
|
||||
import org.apollo.game.plugin.shops.Shop
|
||||
import org.apollo.game.scheduling.ScheduledTask
|
||||
|
||||
fun Mob.shop(): Shop? = SHOPS[definition.id]
|
||||
|
||||
start {
|
||||
ItemVerificationHandler.addInventory(Interfaces.SHOP_CONTAINER) { it.interactingMob?.shop()?.inventory }
|
||||
ItemVerificationHandler.addInventory(Interfaces.INVENTORY_CONTAINER, PlayerInventorySupplier())
|
||||
|
||||
it.schedule(object : ScheduledTask(Shop.RESTOCK_INTERVAL, false) {
|
||||
override fun execute() = SHOPS.values.distinct().forEach(Shop::restock)
|
||||
})
|
||||
}
|
||||
|
||||
on { NpcActionMessage::class }
|
||||
.then {
|
||||
val npc = it.world.npcRepository.get(index)
|
||||
val shop = npc.shop() ?: return@then
|
||||
|
||||
if (shop.action == option) {
|
||||
it.startAction(OpenShopAction(it, shop, npc))
|
||||
terminate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
on { ItemActionMessage::class }
|
||||
.where { interfaceId == Interfaces.SHOP_CONTAINER || interfaceId == Interfaces.INVENTORY_CONTAINER }
|
||||
.then {
|
||||
if (!it.interfaceSet.contains(Interfaces.SHOP_WINDOW)) {
|
||||
return@then
|
||||
}
|
||||
|
||||
val shop = it.interactingMob?.shop() ?: return@then
|
||||
when (interfaceId) {
|
||||
Interfaces.INVENTORY_CONTAINER -> shop.buy(it, slot, option)
|
||||
Interfaces.SHOP_CONTAINER -> shop.sell(it, slot, option)
|
||||
else -> throw IllegalStateException("Supposedly unreacheable case.")
|
||||
}
|
||||
|
||||
terminate()
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.apollo.game.plugin.shops
|
||||
|
||||
import org.apollo.cache.def.ItemDefinition
|
||||
import org.apollo.game.plugin.testing.junit.ApolloTestingExtension
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
|
||||
@ExtendWith(ApolloTestingExtension::class)
|
||||
class CurrencyTests {
|
||||
|
||||
@Test
|
||||
fun `items used as currencies must have names in their definitions`() {
|
||||
assertThrows<ExceptionInInitializerError>("Should not be able to create a Currency with an item missing a name") {
|
||||
Currency(id = ITEM_MISSING_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val ITEM_MISSING_NAME = 0
|
||||
|
||||
@ItemDefinitions
|
||||
private val unnamed = listOf(ItemDefinition(ITEM_MISSING_NAME))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.apollo.game.plugin.shops
|
||||
|
||||
import org.apollo.game.model.entity.Player
|
||||
import org.apollo.game.plugin.testing.junit.ApolloTestingExtension
|
||||
import org.apollo.game.plugin.testing.junit.api.ActionCapture
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.TestMock
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
|
||||
@ExtendWith(ApolloTestingExtension::class)
|
||||
class ShopActionTests {
|
||||
|
||||
@TestMock
|
||||
lateinit var player: Player
|
||||
|
||||
@TestMock
|
||||
lateinit var action: ActionCapture
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user