-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Complete drop table system with specialized DSL. Just needs NPC drop …
…registration (will be done with combat)
- Loading branch information
Showing
13 changed files
with
904 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package api.item.dropTable | ||
|
||
import api.item.dropTable.DropTableHandler.rollSuccess | ||
import api.predef.* | ||
import io.luna.game.model.Entity | ||
import io.luna.game.model.item.Item | ||
import io.luna.game.model.mob.Mob | ||
import io.luna.util.RandomUtils | ||
import io.luna.util.Rational | ||
|
||
/** | ||
* Represents a standard drop table. All drop tables must maintain an immutable internal state to ensure | ||
* instances can be reused. | ||
* | ||
* @author lare96 | ||
*/ | ||
abstract class DropTable(private val chance: Rational = ALWAYS) : Iterable<DropTableItem> { | ||
|
||
/** | ||
* Rolls on this drop table and returns the selected items. | ||
*/ | ||
fun roll(mob: Mob?, source: Entity?): MutableList<Item> { | ||
val allItems = mutableListOf<Item>() | ||
if (canRollOnTable(mob, source)) { | ||
val items = computeTable(mob, source).filterNot { | ||
val alwaysDrop = it.chance == ALWAYS | ||
if (alwaysDrop) { | ||
val alwaysItem = it.toItem() | ||
if (alwaysItem != null) { | ||
allItems += alwaysItem | ||
} | ||
} | ||
alwaysDrop | ||
} | ||
val pickedItem = rollOnTable(mob, source, items) | ||
if (pickedItem != null) { | ||
allItems += pickedItem | ||
} | ||
return allItems | ||
} | ||
return allItems | ||
} | ||
|
||
/** | ||
* Rolls on the table items by building a rational table with empty slots, and returns the rolled on item. | ||
*/ | ||
private fun rollOnTable(mob: Mob?, source: Entity?, items: DropTableItemList): Item? { | ||
if(items.isEmpty()) { | ||
return null | ||
} else if (items.size == 1) { | ||
// Only one item, so our rational table is just the chance that the single item is dropped. | ||
val pickedItem = items.first() | ||
return if (rollSuccess(pickedItem)) pickedItem.toItem() else null | ||
} else { | ||
return RationalTable(items.map { it.chance to it }).roll()?.toItem() | ||
} | ||
} | ||
|
||
/** | ||
* Determines if this table can be rolled on. If `false`, `null` will always be returned from [roll]. | ||
*/ | ||
open fun canRollOnTable(mob: Mob?, source: Entity?): Boolean { | ||
return RandomUtils.rollSuccess(chance) | ||
} | ||
|
||
/** | ||
* Dynamically computes the table of items that will be rolled on. | ||
*/ | ||
abstract fun computeTable(mob: Mob?, source: Entity?): DropTableItemList | ||
|
||
/** | ||
* Returns all possible items that can be rolled on. | ||
*/ | ||
abstract fun computePossibleItems(): DropTableItemList | ||
|
||
override fun iterator(): Iterator<DropTableItem> = computePossibleItems().iterator() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package api.item.dropTable | ||
|
||
import api.item.dropTable.dsl.DropTableItemChanceReceiver | ||
import api.item.dropTable.dsl.DropTableItemReceiver | ||
import api.item.dropTable.dsl.NpcDropSetReceiver | ||
import api.item.dropTable.dsl.SpecializedTableReceiver | ||
import api.predef.* | ||
import io.luna.game.model.mob.Mob | ||
import io.luna.game.model.mob.Npc | ||
import io.luna.util.RandomUtils | ||
import io.luna.util.Rational | ||
|
||
/** | ||
* A type alias for lists of [DropTableItem] types. | ||
*/ | ||
typealias DropTableItemList = List<DropTableItem> | ||
|
||
/** | ||
* Handles global functions related to drop tables. | ||
* | ||
* @author lare96 | ||
*/ | ||
object DropTableHandler { | ||
|
||
/** | ||
* Represents a single conditional drop. | ||
*/ | ||
internal class ConditionalDrop(val condFunc: (Mob?, Npc) -> Boolean, val tableSet: NpcDropTableSet) | ||
|
||
/** | ||
* The conditional drop list. | ||
*/ | ||
private val conditionalDropList = arrayListOf<ConditionalDrop>() | ||
|
||
/** | ||
* The NPC id -> NpcDropTableSet map. | ||
*/ | ||
private val npcIdDropMap = hashMapOf<Int, NpcDropTableSet>() | ||
|
||
/** | ||
* Determines if [tableItem] will be picked based on its rarity. | ||
*/ | ||
internal fun rollSuccess(tableItem: DropTableItem): Boolean = RandomUtils.rollSuccess(tableItem.chance) | ||
|
||
/** | ||
* Creates a generic [DropTable] using our specialized DSL. | ||
*/ | ||
fun create(action: DropTableItemReceiver.() -> Unit): SpecializedTableReceiver { | ||
// Build the table. | ||
val items = arrayListOf<DropTableItem>() | ||
val builder = DropTableItemReceiver(items, false) | ||
action(builder) | ||
return SpecializedTableReceiver(builder) | ||
} | ||
|
||
/** | ||
* Creates a new [SimpleDropTable] instance using our specialized DSL. | ||
*/ | ||
fun createSimple(chance: Rational = Rational.ALWAYS, action: DropTableItemReceiver.() -> Unit): SimpleDropTable { | ||
return create(action).table { SimpleDropTable(items, chance) } | ||
} | ||
|
||
/** | ||
* Creates a new [SimpleDropTable] with a single item. | ||
*/ | ||
fun createSingleton( | ||
chance: Rational = Rational.ALWAYS, | ||
action: DropTableItemReceiver.() -> DropTableItemChanceReceiver, | ||
): SimpleDropTable { | ||
return createSimple { action(this).chance(chance) } | ||
} | ||
|
||
/** | ||
* Creates a new [List] of [DropTableItem] instances using our specialized DSL. | ||
*/ | ||
fun createList(action: DropTableItemReceiver.() -> Unit): DropTableItemList { | ||
// Build the table. | ||
val items = arrayListOf<DropTableItem>() | ||
action(DropTableItemReceiver(items, false)) | ||
return items | ||
} | ||
|
||
/** | ||
* Creates a new [NpcDropTableSet] instance using our specialized DSL. | ||
*/ | ||
fun createNpcSet(action: NpcDropSetReceiver.() -> Unit): NpcDropTableSet { | ||
// Build the tables. | ||
val tables = arrayListOf<DropTable>() | ||
action(NpcDropSetReceiver(tables)) | ||
return NpcDropTableSet(tables) | ||
} | ||
|
||
/** | ||
* Registers a [NpcDropTableSet] to trigger on [condFunc] when an [Npc] dies. | ||
*/ | ||
fun register(condFunc: (Mob?, Npc) -> Boolean, tableSet: NpcDropTableSet) = | ||
conditionalDropList.add(ConditionalDrop(condFunc, tableSet)) | ||
|
||
/** | ||
* Registers a [NpcDropTableSet] to trigger when an [Npc] with [id] dies. | ||
*/ | ||
fun register(id: Int, tableSet: NpcDropTableSet) { | ||
check(npcIdDropMap.putIfAbsent(id, tableSet) != null) | ||
{ "NPC with id[$id] already has a registered NpcDropTableSet" } | ||
} | ||
|
||
/** | ||
* Combines [tables] into one standard [MergedDropTable]. | ||
*/ | ||
fun createMerged(vararg tables: DropTable): DropTable { | ||
return MergedDropTable(tables.toList()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package api.item.dropTable | ||
|
||
import api.predef.* | ||
import com.google.common.base.MoreObjects | ||
import io.luna.game.model.def.ItemDefinition | ||
import io.luna.game.model.item.Item | ||
import io.luna.util.Rational | ||
|
||
/** | ||
* A model representing an item within a [NpcDropTableSet]. Please keep in mind that [amount] works inclusively both ways. | ||
* | ||
* @author lare96 | ||
*/ | ||
class DropTableItem(val id: Int, val amount: IntRange, val chance: Rational) { | ||
|
||
companion object { | ||
|
||
/** | ||
* Priority name -> ID mappings to avoid conflicts. | ||
*/ | ||
private val PRIORITY = mapOf( | ||
"Coins" to 995 | ||
) | ||
|
||
/** | ||
* Computes the [id] for an item with [name]. | ||
*/ | ||
fun computeId(name: String, noted: Boolean): Int { | ||
val priorityValue = PRIORITY[name] | ||
if (priorityValue != null) { | ||
return priorityValue | ||
} | ||
return ItemDefinition.ALL.lookup { it.name == name && it.isNoted == noted } | ||
.orElseThrow { NoSuchElementException("Item with name [$name] not found.") }.id | ||
} | ||
} | ||
|
||
/** | ||
* A constructor that takes [name] instead of [id]. | ||
*/ | ||
constructor(name: String, amount: IntRange, chance: Rational, noted: Boolean = false) : | ||
this(computeId(name, noted), amount, chance) | ||
|
||
/** | ||
* A constructor that takes [name] instead of [id]. | ||
*/ | ||
constructor(name: String, amount: Int, chance: Rational, noted: Boolean = false) : | ||
this(computeId(name, noted), amount..amount, chance) | ||
|
||
/** | ||
* A constructor that takes a single [Int] amount value instead of an [IntRange]. | ||
*/ | ||
constructor(id: Int, amount: Int, chance: Rational) : | ||
this(id, amount..amount, chance) | ||
|
||
override fun toString(): String { | ||
return MoreObjects.toStringHelper(this).add("id", id).add("amount", amount).add("chance", chance).toString(); | ||
} | ||
|
||
/** | ||
* Returns this as an item, with a random value within its [inclusiveAmount]. | ||
*/ | ||
fun toItem(): Item? { | ||
if (id == -1) { | ||
return null | ||
} | ||
if (amount.first == amount.last) { | ||
return Item(id, amount.first) | ||
} | ||
return Item(id, amount.random()) | ||
} | ||
|
||
/** | ||
* Determines if this item is an empty slot. | ||
*/ | ||
fun isNothing() = id == -1 | ||
} |
Oops, something went wrong.