diff --git a/.gitignore b/.gitignore index 7f6d6899..84555139 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /.gradle/ +.vscode/ build/ g=out/ gen/ diff --git a/cache/bin/main/dev/openrune/ServerCacheManager.kt b/cache/bin/main/dev/openrune/ServerCacheManager.kt new file mode 100644 index 00000000..f3ffdb9a --- /dev/null +++ b/cache/bin/main/dev/openrune/ServerCacheManager.kt @@ -0,0 +1,140 @@ +package dev.openrune + +import dev.openrune.OsrsCacheProvider.* +import dev.openrune.cache.CacheManager +import dev.openrune.cache.filestore.definition.ComponentDecoder +import dev.openrune.cache.filestore.definition.FontDecoder +import dev.openrune.cache.filestore.definition.InterfaceType +import dev.openrune.filesystem.Cache +import java.nio.BufferUnderflowException +import dev.openrune.cache.getOrDefault +import dev.openrune.definition.type.* +import dev.openrune.codec.osrs.ItemDecoder +import dev.openrune.codec.osrs.ObjectDecoder +import dev.openrune.codec.osrs.HealthBarDecoder +import dev.openrune.codec.osrs.InventoryDecoder +import dev.openrune.codec.osrs.NpcDecoder +import dev.openrune.codec.osrs.SequenceDecoder +import dev.openrune.codec.osrs.impl.InventoryServerCodec +import dev.openrune.definition.type.widget.ComponentType +import dev.openrune.server.impl.item.ItemRenderDataManager +import dev.openrune.types.* +import io.github.oshai.kotlinlogging.KotlinLogging +import org.alter.rscm.RSCM.asRSCM +import java.nio.file.Path + +object ServerCacheManager { + + private val items: MutableMap = mutableMapOf() + private val npcs: MutableMap = mutableMapOf() + private val objects: MutableMap = mutableMapOf() + private val healthBars: MutableMap = mutableMapOf() + private val structs: MutableMap = mutableMapOf() + private val dbrows: MutableMap = mutableMapOf() + private val dbtables: MutableMap = mutableMapOf() + private val enums: MutableMap = mutableMapOf() + private val varbits: MutableMap = mutableMapOf() + private val varps: MutableMap = mutableMapOf() + private val sequences = mutableMapOf() + private var fonts = mutableMapOf() + private var interfaces = mutableMapOf() + private var inv = mutableMapOf() + + val logger = KotlinLogging.logger {} + + fun init(cachePath : Path, rev : Int) { + val cache = Cache.load(cachePath) + init(cache,rev) + } + + fun init(cache : Cache, rev : Int) { + ItemRenderDataManager.init() + CacheManager.init(OsrsCacheProvider(cache,rev)) + + fonts = FontDecoder(cache).loadAllFonts() + + try { + EnumDecoder().load(cache, enums) + ObjectDecoder().load(cache, objects) + HealthBarDecoder().load(cache, healthBars) + NpcDecoder().load(cache, npcs) + ItemDecoder().load(cache, items) + InventoryDecoder().load(cache,inv) + SequenceDecoder().load(cache,sequences) + VarBitDecoder().load(cache, varbits) + VarDecoder().load(cache, varps) + StructDecoder().load(cache, structs) + DBRowDecoder().load(cache, dbrows) + DBTableDecoder().load(cache, dbtables) + ComponentDecoder(cache).load(interfaces) + } catch (e: BufferUnderflowException) { + logger.error(e) { "Error reading definitions" } + throw e + } + } + + fun getNpc(id: Int) = npcs[id] + fun getFont(id: Int) = fonts[id] + fun getObject(id: Int) = objects[id] + fun getItem(id: Int) = items[id] + fun getVarbit(id: Int) = varbits[id] + fun getVarp(id: Int) = varps[id] + fun getAnim(id: Int) = sequences[id] + fun getEnum(id: Int) = enums[id] + fun getHealthBar(id: Int) = healthBars[id] + fun getStruct(id: Int) = structs[id] + fun getDbrow(id: Int) = dbrows[id] + fun getDbtable(id: Int) = dbtables[id] + fun getInventory(id: Int) = inv[id] + + fun getNpcOrDefault(id: Int) = getOrDefault(npcs, id, NpcServerType(), "Npc") + fun getObjectOrDefault(id: Int) = getOrDefault(objects, id, ObjectServerType(), "Object") + + fun getItemOrDefault(id: Int) = getOrDefault(items, id, ItemServerType(), "Item") + fun getVarbitOrDefault(id: Int) = getOrDefault(varbits, id, VarBitType(), "Varbit") + fun getVarpOrDefault(id: Int) = getOrDefault(varps, id, VarpType(), "Varp") + fun getEnumOrDefault(id: Int) = getOrDefault(enums, id, EnumType(), "Enum") + fun getHealthBarOrDefault(id: Int) = getOrDefault(healthBars, id, HealthBarServerType(), "HealthBar") + fun getStructOrDefault(id: Int) = getOrDefault(structs, id, StructType(), "Struct") + fun getDbrowOrDefault(id: Int) = getOrDefault(dbrows, id, DBRowType(), "DBRow") + fun getDbtableOrDefault(id: Int) = getOrDefault(dbtables, id, DBTableType(), "DBTable") + + // Size methods + fun npcSize() = npcs.size + fun objectSize() = objects.size + fun itemSize() = items.size + fun varbitSize() = varbits.size + fun varpSize() = varps.size + fun enumSize() = enums.size + fun healthBarSize() = healthBars.size + fun structSize() = structs.size + fun animSize() = sequences.size + + // Bulk getters + fun getNpcs() = npcs.toMap() + fun getObjects() = objects.toMap() + fun getItems() = items.toMap() + fun getVarbits() = varbits.toMap() + fun getVarps() = varps.toMap() + fun getEnums() = enums.toMap() + fun getHealthBars() = healthBars.toMap() + fun getStructs() = structs.toMap() + fun getAnims() = sequences.toMap() + fun getRows() = dbrows.toMap() + + fun fromInterface(id: Int): InterfaceType = + interfaces[id] ?: error("Interface $id not found") + + fun fromInterface(id: String): InterfaceType = + fromInterface(id.asRSCM()) + + fun fromComponent(packed: Int): ComponentType { + val interfaceId = packed ushr 16 + val childId = packed and 0xFFFF + return fromInterface(interfaceId).components[childId] + ?: error("Component $childId not found in interface $interfaceId") + } + + fun fromComponent(id: String) = fromComponent(id.asRSCM()) + +} diff --git a/cache/bin/main/dev/openrune/codec/osrs/CodecsOsrs.kt b/cache/bin/main/dev/openrune/codec/osrs/CodecsOsrs.kt new file mode 100644 index 00000000..570bf7d2 --- /dev/null +++ b/cache/bin/main/dev/openrune/codec/osrs/CodecsOsrs.kt @@ -0,0 +1,12 @@ +package dev.openrune.codec.osrs + +import dev.openrune.cache.filestore.definition.ConfigDefinitionDecoder +import dev.openrune.codec.osrs.impl.* +import dev.openrune.types.* + +class ObjectDecoder : ConfigDefinitionDecoder(ObjectServerCodec(), 55) +class HealthBarDecoder : ConfigDefinitionDecoder(HealthBarServerCodec(), 56) +class SequenceDecoder : ConfigDefinitionDecoder(SequenceServerCodec(), 57) +class NpcDecoder : ConfigDefinitionDecoder(NpcServerCodec(), 58) +class ItemDecoder : ConfigDefinitionDecoder(ItemServerCodec(), 59) +class InventoryDecoder : ConfigDefinitionDecoder(InventoryServerCodec(), 60) diff --git a/cache/bin/main/dev/openrune/codec/osrs/impl/HealthBarServerCodec.kt b/cache/bin/main/dev/openrune/codec/osrs/impl/HealthBarServerCodec.kt new file mode 100644 index 00000000..0c2cf552 --- /dev/null +++ b/cache/bin/main/dev/openrune/codec/osrs/impl/HealthBarServerCodec.kt @@ -0,0 +1,27 @@ +package dev.openrune.codec.osrs.impl + +import dev.openrune.definition.opcode.DefinitionOpcode +import dev.openrune.definition.opcode.OpcodeDefinitionCodec +import dev.openrune.definition.opcode.OpcodeList +import dev.openrune.definition.opcode.OpcodeType +import dev.openrune.definition.type.HealthBarType +import dev.openrune.types.HealthBarServerType + + +class HealthBarServerCodec(val health: Map? = null) : OpcodeDefinitionCodec() { + + override val definitionCodec = OpcodeList().apply { + add(DefinitionOpcode(1, OpcodeType.BYTE, HealthBarServerType::width)) + } + + override fun HealthBarServerType.createData() { + if (health == null) return + + val health = health[id]?: return + + width = health.width + } + + override fun createDefinition(): HealthBarServerType = HealthBarServerType() + +} diff --git a/cache/bin/main/dev/openrune/codec/osrs/impl/InventoryServerCodec.kt b/cache/bin/main/dev/openrune/codec/osrs/impl/InventoryServerCodec.kt new file mode 100644 index 00000000..ee52e8a5 --- /dev/null +++ b/cache/bin/main/dev/openrune/codec/osrs/impl/InventoryServerCodec.kt @@ -0,0 +1,51 @@ +package dev.openrune.codec.osrs.impl + +import dev.openrune.definition.opcode.DefinitionOpcode +import dev.openrune.definition.opcode.OpcodeDefinitionCodec +import dev.openrune.definition.opcode.OpcodeList +import dev.openrune.definition.opcode.OpcodeType +import dev.openrune.definition.opcode.OpcodeType.BOOLEAN.enumType +import dev.openrune.definition.opcode.propertyChain +import dev.openrune.definition.type.InventoryType +import dev.openrune.server.impl.item.WeaponTypes +import dev.openrune.types.HealthBarServerType +import dev.openrune.types.InvScope +import dev.openrune.types.InvStackType +import dev.openrune.types.InvStock +import dev.openrune.types.InventoryServerType +import dev.openrune.types.InventoryServerType.Companion.pack +import dev.openrune.types.ItemServerType +import dev.openrune.types.Weapon + + +class InventoryServerCodec( + val types: Map? = null, + val custom : Map? = emptyMap() + +) : OpcodeDefinitionCodec() { + + override val definitionCodec = OpcodeList().apply { + add(DefinitionOpcode(1, OpcodeType.USHORT, InventoryServerType::size)) + add(DefinitionOpcode(2, OpcodeType.USHORT, InventoryServerType::flags)) + add(DefinitionOpcode(3, enumType(), InventoryServerType::stack)) + add(DefinitionOpcode(4, enumType(), InventoryServerType::scope)) + } + + override fun InventoryServerType.createData() { + if (types == null) return + val inventoryType = types[id]?: return + size = inventoryType.size + val customData = custom!![id] + + if (customData != null) { + scope = customData.scope + stack = customData.stack + flags = customData.flags + } + + } + + + override fun createDefinition(): InventoryServerType = InventoryServerType() + +} diff --git a/cache/bin/main/dev/openrune/codec/osrs/impl/ItemServerCodec.kt b/cache/bin/main/dev/openrune/codec/osrs/impl/ItemServerCodec.kt new file mode 100644 index 00000000..deb23b4d --- /dev/null +++ b/cache/bin/main/dev/openrune/codec/osrs/impl/ItemServerCodec.kt @@ -0,0 +1,219 @@ +package dev.openrune.codec.osrs.impl + +import dev.openrune.cache.util.ItemParam +import dev.openrune.definition.* +import dev.openrune.definition.opcode.* +import dev.openrune.definition.type.EnumType +import dev.openrune.definition.type.ItemType +import dev.openrune.definition.opcode.OpcodeType.BOOLEAN.enumType +import dev.openrune.definition.opcode.impl.DefinitionOpcodeListActions +import dev.openrune.definition.opcode.impl.DefinitionOpcodeMap +import dev.openrune.definition.opcode.impl.DefinitionOpcodeParams +import dev.openrune.server.impl.item.ItemRenderDataManager +import dev.openrune.server.impl.item.WeaponTypes +import dev.openrune.server.infobox.InfoBoxItem +import dev.openrune.types.Equipment +import dev.openrune.types.ItemServerType +import dev.openrune.types.Weapon +import kotlin.reflect.KMutableProperty1 +import dev.openrune.types.EquipmentStats +import dev.openrune.types.getInt + +class ItemServerCodec( + val items: Map? = null, + val enums : Map?= null, + val osrs : Map? = null, + val custom : Map? = emptyMap() + +) : OpcodeDefinitionCodec() { + + override val definitionCodec = OpcodeList().apply { + add(DefinitionOpcode(2, OpcodeType.INT, ItemServerType::cost)) + add(DefinitionOpcode(4, OpcodeType.STRING, ItemServerType::name)) + add(DefinitionOpcode(7, OpcodeType.DOUBLE, ItemServerType::weight)) + add(DefinitionOpcode(8, OpcodeType.BOOLEAN, ItemServerType::isTradeable)) + add(DefinitionOpcode(9, OpcodeType.INT, ItemServerType::category)) + add(DefinitionOpcodeListActions(10, OpcodeType.STRING, ItemServerType::options, 5)) + add(DefinitionOpcodeListActions(11, OpcodeType.STRING, ItemServerType::interfaceOptions, 5)) + add(DefinitionOpcode(12, OpcodeType.INT, ItemServerType::noteLinkId)) + add(DefinitionOpcode(13, OpcodeType.INT, ItemServerType::noteTemplateId)) + add(DefinitionOpcode(14, OpcodeType.INT, ItemServerType::placeholderLink)) + add(DefinitionOpcode(15, OpcodeType.INT, ItemServerType::placeholderTemplate)) + add(DefinitionOpcode(16, OpcodeType.INT, ItemServerType::stacks)) + add(DefinitionOpcode(18, OpcodeType.INT, ItemServerType::appearanceOverride1)) + add(DefinitionOpcode(19, OpcodeType.INT, ItemServerType::appearanceOverride2)) + add(DefinitionOpcodeParams(20, ItemServerType::params)) + + add(DefinitionOpcode(21, OpcodeType.BYTE, propertyChain( + ItemServerType::equipment, + Equipment::equipSlot + ))) + + add(DefinitionOpcode(22, OpcodeType.SHORT, propertyChain( + ItemServerType::weapon, + Weapon::specAmount + ))) + + add(DefinitionOpcode(23, OpcodeType.BYTE, propertyChain( + ItemServerType::weapon, + Weapon::attackRange))) + + add(DefinitionOpcode(24, OpcodeType.BYTE, propertyChain( + ItemServerType::weapon, + Weapon::attackSpeed + ))) + + add(DefinitionOpcodeMap(25, OpcodeType.STRING, OpcodeType.INT, propertyChain( + ItemServerType::equipment, + Equipment::requirements + ))) + + add(DefinitionOpcode(26, OpcodeType.STRING, ItemServerType::examine)) + add(DefinitionOpcode(27, OpcodeType.STRING, ItemServerType::destroy)) + add(DefinitionOpcode(28, OpcodeType.BOOLEAN, ItemServerType::alchable)) + add(DefinitionOpcode(29, OpcodeType.INT, ItemServerType::exchangeCost)) + + add(DefinitionOpcodeMap(30, OpcodeType.STRING, OpcodeType.INT, propertyChain( + ItemServerType::equipment, + Equipment::requirements + ))) + + + add(DefinitionOpcode(31, enumType(), propertyChain( + ItemServerType::weapon, + Weapon::weaponType + ))) + + add(DefinitionOpcode(32, OpcodeType.STRING, propertyChain( + ItemServerType::weapon, + Weapon::weaponTypeRenderData + ))) + + ItemParam.entries.forEachIndexed { index, itemParam -> + if (itemParam.toProperty() != null) { + add(DefinitionOpcode(33 + index, OpcodeType.BYTE, propertyChain( + ItemServerType::equipment, + Equipment::stats, + itemParam.toProperty()!! + ))) + } + } + + add(DefinitionOpcode(60, OpcodeType.INT, propertyChain( + ItemServerType::equipment, + Equipment::equipType + ))) + + } + + private fun ItemParam.toProperty(): KMutableProperty1? = when (this) { + ItemParam.ATTACK_STAB -> EquipmentStats::attackStab + ItemParam.ATTACK_SLASH -> EquipmentStats::attackSlash + ItemParam.ATTACK_CRUSH -> EquipmentStats::attackCrush + ItemParam.ATTACK_MAGIC -> EquipmentStats::attackMagic + ItemParam.ATTACK_RANGED -> EquipmentStats::attackRanged + ItemParam.DEFENCE_STAB -> EquipmentStats::defenceStab + ItemParam.DEFENCE_SLASH -> EquipmentStats::defenceSlash + ItemParam.DEFENCE_CRUSH -> EquipmentStats::defenceCrush + ItemParam.DEFENCE_MAGIC -> EquipmentStats::defenceMagic + ItemParam.DEFENCE_RANGED -> EquipmentStats::defenceRanged + ItemParam.MELEE_STRENGTH -> EquipmentStats::meleeStrength + ItemParam.PRAYER -> EquipmentStats::prayer + ItemParam.RANGED_STRENGTH -> EquipmentStats::rangedStrength + ItemParam.MAGIC_STRENGTH -> EquipmentStats::magicStrength + ItemParam.RANGED_DAMAGE -> EquipmentStats::rangedDamage + ItemParam.MAGIC_DAMAGE -> EquipmentStats::magicDamage + ItemParam.DEMON_DAMAGE -> EquipmentStats::demonDamage + ItemParam.DEGRADEABLE -> EquipmentStats::degradeable + ItemParam.SILVER_STRENGTH -> EquipmentStats::silverStrength + ItemParam.CORP_BOOST -> EquipmentStats::corpBoost + ItemParam.GOLEM_DAMAGE -> EquipmentStats::golemDamage + ItemParam.KALPHITE_DAMAGE -> EquipmentStats::kalphiteDamage + else -> null + } + + private fun getItemRequirements(params: Map?): Map { + if (params == null) return emptyMap() + + val enum81 = enums?.get(81) ?: return emptyMap() + val enum108 = enums[108] ?: return emptyMap() + return ItemParam.entries.filter { it.isSkillParam }.mapNotNull { skillParam -> + val skillId = params.getInt(skillParam.id) + val skillNameId = enum81.values.getInt(skillId) + val skillName = enum108.getString(skillNameId) + val linkedLevel = params.getInt(skillParam.linkedLevelReqId ?: return@mapNotNull null) + if (linkedLevel > 0) skillName to linkedLevel else null + }.toMap() + } + + override fun ItemServerType.createData() { + if (items == null) return + val item = items[id] ?: return + val osrsData = osrs!![id] + val customData = custom!![id] + + id = item.id + examine = customData?.examine?.takeIf { it.isNotEmpty() } ?: osrsData?.examine?.takeIf { it.isNotEmpty() } ?: "" + + destroy = osrsData?.destroy.takeIf { it.isNullOrEmpty() } ?: "" + alchable = osrsData?.alchable ?: true + cost = osrsData?.cost?.takeIf { it != -1 } ?: cost + exchangeCost = osrsData?.exchangeCost?.takeIf { it != -1 } ?: cost + + name = item.name + weight = item.weight + isTradeable = item.isTradeable + category = item.category + options = item.options + interfaceOptions = item.interfaceOptions + noteLinkId = item.noteLinkId + noteTemplateId = item.noteTemplateId + placeholderLink = item.placeholderLink + placeholderTemplate = item.placeholderTemplate + stacks = item.stacks + appearanceOverride1 = item.appearanceOverride1 + appearanceOverride2 = item.appearanceOverride2 + params = item.params + + if (item.equipSlot != -1) { + + equipment = Equipment().apply { + equipSlot = item.equipSlot + equipType = appearanceOverride1 + stats = EquipmentStats().apply { + ItemParam.entries.forEach { stat -> + stat.toProperty()?.set(this, params?.getInt(stat.id) ?: 0) + } + } + requirements = getItemRequirements(params).takeIf { it.isNotEmpty() } ?: osrsData?.itemReq ?: emptyMap() + + } + + if (item.equipSlot == 3 || item.equipSlot == 5) { + weapon = Weapon().apply { + + val combatStyle = customData?.weapon?.weaponType?.name + ?.takeIf { it.isNotEmpty() && it != "UNARMED" } + ?: osrsData?.combatStyle?.takeIf { it.isNotEmpty() } + ?: "" + + fun findWeaponTypeRenderData(id: Int, weaponType: WeaponTypes): String? = + ItemRenderDataManager.getItemRenderAnimationByItem(id)?.name + ?: ItemRenderDataManager.getItemRenderAnimationById(weaponType.fallbackRenderID)?.name + + weaponType = WeaponTypes.entries.find { it.name == combatStyle.uppercase().replace(" ", "_") } ?: WeaponTypes.UNARMED + weaponTypeRenderData = findWeaponTypeRenderData(id, weaponType) + + attackSpeed = params?.getInt(14)?: 4 + attackRange = params?.getInt(13) ?: osrsData?.attackRange ?: 0 + specAmount = enums?.get(906)?.values?.takeIf { it.containsKey(id.toString()) }?.getInt(id) ?: -1 + } + } + + } + + } + + override fun createDefinition(): ItemServerType = ItemServerType() + +} diff --git a/cache/bin/main/dev/openrune/codec/osrs/impl/NpcServerCodec.kt b/cache/bin/main/dev/openrune/codec/osrs/impl/NpcServerCodec.kt new file mode 100644 index 00000000..30db4ac5 --- /dev/null +++ b/cache/bin/main/dev/openrune/codec/osrs/impl/NpcServerCodec.kt @@ -0,0 +1,94 @@ +package dev.openrune.codec.osrs.impl + +import dev.openrune.definition.type.NpcType +import dev.openrune.definition.opcode.OpcodeDefinitionCodec +import dev.openrune.definition.opcode.DefinitionOpcode +import dev.openrune.definition.opcode.OpcodeList +import dev.openrune.definition.opcode.OpcodeType +import dev.openrune.definition.opcode.impl.DefinitionOpcodeListActions +import dev.openrune.definition.opcode.impl.DefinitionOpcodeParams +import dev.openrune.definition.opcode.impl.DefinitionOpcodeTransforms +import dev.openrune.types.NpcServerType + + +class NpcServerCodec(val npcs: Map? = null) : OpcodeDefinitionCodec() { + + override val definitionCodec = OpcodeList().apply { + add(DefinitionOpcode(1, OpcodeType.STRING, NpcServerType::name)) + add(DefinitionOpcode(2, OpcodeType.INT, NpcServerType::size)) + add(DefinitionOpcode(3, OpcodeType.INT, NpcServerType::category)) + add(DefinitionOpcode(4, OpcodeType.INT, NpcServerType::standAnim)) + add(DefinitionOpcode(5, OpcodeType.INT, NpcServerType::rotateLeftAnim)) + add(DefinitionOpcode(6, OpcodeType.INT, NpcServerType::rotateRightAnim)) + add(DefinitionOpcode(7, OpcodeType.INT, NpcServerType::walkAnim)) + add(DefinitionOpcode(8, OpcodeType.INT, NpcServerType::rotateBackAnim)) + add(DefinitionOpcode(9, OpcodeType.INT, NpcServerType::walkLeftAnim)) + add(DefinitionOpcode(10, OpcodeType.INT, NpcServerType::walkRightAnim)) + add(DefinitionOpcodeListActions(11, OpcodeType.STRING, NpcServerType::actions, 5)) + add(DefinitionOpcodeTransforms(IntRange(12, 13), NpcServerType::transforms, NpcServerType::varbit, NpcServerType::varp)) + add(DefinitionOpcode(14, OpcodeType.INT, NpcServerType::combatLevel)) + add(DefinitionOpcode(15, OpcodeType.INT, NpcServerType::renderPriority)) + add(DefinitionOpcode(16, OpcodeType.BOOLEAN, NpcServerType::lowPriorityFollowerOps)) + add(DefinitionOpcode(17, OpcodeType.BOOLEAN, NpcServerType::isFollower)) + add(DefinitionOpcode(18, OpcodeType.INT, NpcServerType::runSequence)) + add(DefinitionOpcode(19, OpcodeType.BOOLEAN, NpcServerType::isInteractable)) + add(DefinitionOpcode(20, OpcodeType.INT, NpcServerType::runBackSequence)) + add(DefinitionOpcode(21, OpcodeType.INT, NpcServerType::runRightSequence)) + add(DefinitionOpcode(22, OpcodeType.INT, NpcServerType::runLeftSequence)) + add(DefinitionOpcode(23, OpcodeType.INT, NpcServerType::crawlSequence)) + add(DefinitionOpcode(24, OpcodeType.INT, NpcServerType::crawlBackSequence)) + add(DefinitionOpcode(25, OpcodeType.INT, NpcServerType::crawlRightSequence)) + add(DefinitionOpcode(26, OpcodeType.INT, NpcServerType::crawlLeftSequence)) + add(DefinitionOpcodeParams(27, NpcServerType::params)) + add(DefinitionOpcode(28, OpcodeType.USHORT, NpcServerType::height)) + add(DefinitionOpcode(29, OpcodeType.USHORT, NpcServerType::attack)) + add(DefinitionOpcode(30, OpcodeType.USHORT, NpcServerType::defence)) + add(DefinitionOpcode(31, OpcodeType.USHORT, NpcServerType::strength)) + add(DefinitionOpcode(32, OpcodeType.USHORT, NpcServerType::hitpoints)) + add(DefinitionOpcode(33, OpcodeType.USHORT, NpcServerType::ranged)) + add(DefinitionOpcode(34, OpcodeType.USHORT, NpcServerType::magic)) + } + + override fun NpcServerType.createData() { + if (npcs == null) return + val obj = npcs[id] ?: return + + name = obj.name + size = obj.size + category = obj.category + standAnim = obj.standAnim + rotateLeftAnim = obj.rotateLeftAnim + rotateRightAnim = obj.rotateRightAnim + walkAnim = obj.walkAnim + rotateBackAnim = obj.rotateBackAnim + walkLeftAnim = obj.walkLeftAnim + walkRightAnim = obj.walkRightAnim + actions = obj.actions + varbit = obj.varbit + varp = obj.varp + transforms = obj.transforms + combatLevel = obj.combatLevel + renderPriority = obj.renderPriority + lowPriorityFollowerOps = obj.lowPriorityFollowerOps + isFollower = obj.isFollower + runSequence = obj.runSequence + isInteractable = obj.isInteractable + runBackSequence = obj.runBackSequence + runRightSequence = obj.runRightSequence + runLeftSequence = obj.runLeftSequence + crawlSequence = obj.crawlSequence + crawlBackSequence = obj.crawlBackSequence + crawlRightSequence = obj.crawlRightSequence + crawlLeftSequence = obj.crawlLeftSequence + height = obj.height + attack = obj.attack + defence = obj.defence + strength = obj.strength + hitpoints = obj.hitpoints + ranged = obj.ranged + magic = obj.magic + params = obj.params + } + + override fun createDefinition(): NpcServerType = NpcServerType() +} diff --git a/cache/bin/main/dev/openrune/codec/osrs/impl/ObjectServerCodec.kt b/cache/bin/main/dev/openrune/codec/osrs/impl/ObjectServerCodec.kt new file mode 100644 index 00000000..d39b7efb --- /dev/null +++ b/cache/bin/main/dev/openrune/codec/osrs/impl/ObjectServerCodec.kt @@ -0,0 +1,82 @@ +package dev.openrune.codec.osrs.impl + +import dev.openrune.definition.type.ObjectType +import dev.openrune.definition.opcode.OpcodeDefinitionCodec +import dev.openrune.definition.opcode.DefinitionOpcode +import dev.openrune.definition.opcode.OpcodeList +import dev.openrune.definition.opcode.OpcodeType +import dev.openrune.definition.opcode.impl.DefinitionOpcodeListActions +import dev.openrune.definition.opcode.impl.DefinitionOpcodeParams +import dev.openrune.definition.opcode.impl.DefinitionOpcodeTransforms +import dev.openrune.server.infobox.InfoBoxItem +import dev.openrune.server.infobox.InfoBoxObject +import dev.openrune.types.ItemServerType +import dev.openrune.types.ObjectServerType + + +class ObjectServerCodec( + val objects: Map? = null, + val osrs : Map? = null, +) : OpcodeDefinitionCodec() { + + override val definitionCodec = OpcodeList().apply { + add(DefinitionOpcode(1, OpcodeType.STRING, ObjectServerType::name)) + add(DefinitionOpcode(3, OpcodeType.BYTE, ObjectServerType::sizeX)) + add(DefinitionOpcode(4, OpcodeType.BYTE, ObjectServerType::sizeY)) + add(DefinitionOpcode(5, OpcodeType.USHORT, ObjectServerType::offsetX)) + add(DefinitionOpcode(6, OpcodeType.BYTE, ObjectServerType::interactive)) + add(DefinitionOpcode(7, OpcodeType.INT, ObjectServerType::solid)) + add(DefinitionOpcode(9, OpcodeType.SHORT, ObjectServerType::modelSizeX)) + add(DefinitionOpcode(10, OpcodeType.SHORT, ObjectServerType::modelSizeZ)) + add(DefinitionOpcode(11, OpcodeType.SHORT, ObjectServerType::modelSizeY)) + add(DefinitionOpcode(12, OpcodeType.SHORT, ObjectServerType::offsetZ)) + add(DefinitionOpcode(13, OpcodeType.SHORT, ObjectServerType::offsetY)) + add(DefinitionOpcode(14, OpcodeType.BYTE, ObjectServerType::clipMask)) + add(DefinitionOpcode(15, OpcodeType.BOOLEAN, ObjectServerType::obstructive)) + add(DefinitionOpcode(16, OpcodeType.USHORT, ObjectServerType::category)) + add(DefinitionOpcode(17, OpcodeType.BYTE, ObjectServerType::supportsItems)) + add(DefinitionOpcode(18, OpcodeType.BOOLEAN, ObjectServerType::isRotated)) + add(DefinitionOpcode(19, OpcodeType.BOOLEAN, ObjectServerType::impenetrable)) + add(DefinitionOpcode(20, OpcodeType.USHORT, ObjectServerType::replacementId)) + add(DefinitionOpcodeTransforms(IntRange(23, 24),ObjectServerType::transforms,ObjectServerType::varbit,ObjectServerType::varp)) + add(DefinitionOpcodeListActions(25, OpcodeType.STRING, ObjectServerType::actions, 5)) + add(DefinitionOpcodeParams(26, ObjectServerType::params)) + add(DefinitionOpcode(27, OpcodeType.STRING, ObjectServerType::examine)) + + } + + override fun ObjectServerType.createData() { + if (objects == null) return + + val obj = objects[id]?: return + + name = obj.name + examine = osrs?.get(id)?.examine?.takeIf { it.isNotEmpty() } ?: "" + sizeX = obj.sizeX + sizeY = obj.sizeY + offsetX = obj.offsetX + interactive = obj.interactive + solid = obj.solid + actions = obj.actions + modelSizeX = obj.modelSizeX + modelSizeZ = obj.modelSizeZ + modelSizeY = obj.modelSizeY + offsetZ = obj.offsetZ + offsetY = obj.offsetY + clipMask = obj.clipMask + obstructive = obj.obstructive + category = obj.category + supportsItems = obj.supportsItems + isRotated = obj.isRotated + impenetrable = obj.impenetrable + replacementId = obj.oppositeDoorId(objects) + varbit = obj.varbitId + varp = obj.varp + transforms = obj.transforms + params = obj.params + + } + + override fun createDefinition(): ObjectServerType = ObjectServerType() + +} diff --git a/cache/bin/main/dev/openrune/codec/osrs/impl/SequenceServerCodec.kt b/cache/bin/main/dev/openrune/codec/osrs/impl/SequenceServerCodec.kt new file mode 100644 index 00000000..c07d69fd --- /dev/null +++ b/cache/bin/main/dev/openrune/codec/osrs/impl/SequenceServerCodec.kt @@ -0,0 +1,28 @@ +package dev.openrune.codec.osrs.impl + +import dev.openrune.definition.type.SequenceType +import dev.openrune.definition.opcode.OpcodeDefinitionCodec +import dev.openrune.definition.opcode.DefinitionOpcode +import dev.openrune.definition.opcode.OpcodeList +import dev.openrune.definition.opcode.OpcodeType +import dev.openrune.types.SequenceServerType + + +class SequenceServerCodec(val sequences: Map? = null) : OpcodeDefinitionCodec() { + + override val definitionCodec = OpcodeList().apply { + add(DefinitionOpcode(1, OpcodeType.USHORT, SequenceServerType::animationLength)) + + } + + override fun SequenceServerType.createData() { + if (sequences == null) return + + val seq = sequences[id]?: return + + animationLength = seq.lengthInCycles + } + + override fun createDefinition(): SequenceServerType = SequenceServerType() + +} diff --git a/cache/bin/main/dev/openrune/tools/MinifyServerCache.kt b/cache/bin/main/dev/openrune/tools/MinifyServerCache.kt new file mode 100644 index 00000000..fa277cbd --- /dev/null +++ b/cache/bin/main/dev/openrune/tools/MinifyServerCache.kt @@ -0,0 +1,60 @@ +package dev.openrune.tools + +import com.displee.cache.CacheLibrary +import com.displee.cache.index.archive.file.File +import dev.openrune.cache.ANIMATIONS +import dev.openrune.cache.ANIMAYAS +import dev.openrune.cache.ARCHIVE_17 +import dev.openrune.cache.CacheDelegate +import dev.openrune.cache.DBTABLEINDEX +import dev.openrune.cache.MODELS +import dev.openrune.cache.MUSIC_JINGLES +import dev.openrune.cache.MUSIC_PATCHES +import dev.openrune.cache.MUSIC_SAMPLES +import dev.openrune.cache.MUSIC_TRACKS +import dev.openrune.cache.SOUNDEFFECTS +import dev.openrune.cache.TEXTURES +import dev.openrune.cache.WORLDMAPAREAS +import dev.openrune.cache.WORLDMAP_GEOGRAPHY +import dev.openrune.cache.WORLDMAP_GROUND +import dev.openrune.filesystem.Cache +import org.alter.ParamMapper.npc.SLAYER_CATEGORIES.SKELETONS + +class MinifyServerCache() { + + + fun init(loc : String) { + val cache = CacheLibrary(loc) + + emptyArchive(ANIMATIONS, cache) + emptyArchive(SKELETONS, cache) + emptyArchive(SOUNDEFFECTS, cache) + emptyArchive(MUSIC_TRACKS, cache) + emptyArchive(MODELS, cache) + emptyArchive(TEXTURES, cache) + emptyArchive(MUSIC_JINGLES, cache) + emptyArchive(MUSIC_SAMPLES, cache) + emptyArchive(MUSIC_PATCHES, cache) + emptyArchive(ARCHIVE_17, cache) + emptyArchive(WORLDMAP_GEOGRAPHY, cache) + emptyArchive(WORLDMAPAREAS, cache) + emptyArchive(WORLDMAP_GROUND, cache) + emptyArchive(DBTABLEINDEX, cache) + emptyArchive(ANIMAYAS, cache) + + val loc = java.io.File(loc) + val temp = java.io.File(loc,"temp") + temp.mkdirs() + cache.rebuild(temp) + cache.close() + temp.copyRecursively(loc,true) + temp.deleteRecursively() + + } + + fun emptyArchive(id : Int, cache: CacheLibrary) { + cache.index(id).clear() + cache.index(id).update() + } + +} \ No newline at end of file diff --git a/cache/bin/main/dev/openrune/tools/PackServerConfigOSRS.kt b/cache/bin/main/dev/openrune/tools/PackServerConfigOSRS.kt new file mode 100644 index 00000000..6123e281 --- /dev/null +++ b/cache/bin/main/dev/openrune/tools/PackServerConfigOSRS.kt @@ -0,0 +1,245 @@ +package dev.openrune.tools + +import cc.ekblad.toml.TomlMapper +import cc.ekblad.toml.model.TomlValue +import cc.ekblad.toml.serialization.from +import cc.ekblad.toml.tomlMapper +import cc.ekblad.toml.util.InternalAPI +import dev.openrune.OsrsCacheProvider +import dev.openrune.cache.* +import dev.openrune.cache.tools.TaskPriority +import org.alter.getCacheLocation +import dev.openrune.cache.tools.tasks.CacheTask +import dev.openrune.cache.util.getFiles +import dev.openrune.cache.util.progress +import dev.openrune.codec.osrs.impl.* +import dev.openrune.definition.Definition +import dev.openrune.definition.constants.ConstantProvider +import dev.openrune.definition.type.InventoryType +import dev.openrune.definition.type.ItemType +import dev.openrune.filesystem.Cache +import dev.openrune.server.impl.item.ItemRenderDataManager +import dev.openrune.server.infobox.InfoBoxItem +import dev.openrune.server.infobox.InfoBoxObject +import dev.openrune.server.infobox.Load +import dev.openrune.types.* +import dev.openrune.types.InventoryServerType.Companion.pack +import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.File +import java.nio.file.Path +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +class PackType( + val index: Int, + val name: String, + val tomlMapper: TomlMapper, + val kType: KType +) + + +class PackServerConfig( + private val directory : File, + private val tokenizedReplacement: Map = emptyMap() +) : CacheTask(serverTaskOnly = true) { + + override val priority: TaskPriority + get() = TaskPriority.END + + fun Map.bool(key: String, defualt : Boolean) = + this.containsKey(key) ?: defualt + + init { + registerPackType("item",kType = typeOf>()) + registerPackType("object",kType = typeOf>()) + registerPackType("npc", kType = typeOf>()) + registerPackType("health", kType = typeOf>()) + registerPackType("anims", kType = typeOf>()) + registerPackType("inventory", kType = typeOf>(), tomlMapper = tomlMapper { + addDecoder { content, def: InventoryServerType -> + def.apply { + flags = pack( + protect = content.bool("protect",true), + allStock = content.bool("allStock",false), + restock = content.bool("restock",false), + runWeight = content.bool("runWeight",false), + dummyInv = content.bool("dummyInv",false), + placeholders = content.bool("placeholders",false), + ) + } + } + }) + } + + + private val logger = KotlinLogging.logger {} + + @OptIn(InternalAPI::class) + override fun init(cache: Cache) { + val parsedDefinitions = mutableMapOf>() + CacheManager.init(OsrsCacheProvider(Cache.load(Path.of(getCacheLocation())), revision)) + + ItemRenderDataManager.init() + + val files = getFiles(directory, "toml") + + for (file in files) { + val document = TomlValue.from(processDocumentChanges(file.readText())) + + document.properties.forEach { prop -> + val packType = packTypes[prop.key] ?: return@forEach + + val decodedDefs: List = packType.tomlMapper.decode(packType.kType, prop.value) + parsedDefinitions.getOrPut(packType.name) { mutableListOf() }.addAll(decodedDefs) + } + + } + + val progress = progress("Packing Server Configs", parsedDefinitions.size) + packTypes.forEach { (type) -> + progress.extraMessage = type + when(type) { + "object" -> { + val codec = ObjectServerCodec( + CacheManager.getObjects(), + InfoBoxObject.load(File("../data/raw-cache/extra-dump/object-examines.csv").toPath()) + ) + CacheManager.getObjects().forEach { + cache.write(CONFIGS, 55, it.key, codec.encodeToBuffer(ObjectServerType(it.key))) + } + } + "npc" -> { + val codec = NpcServerCodec(CacheManager.getNpcs()) + CacheManager.getNpcs().forEach { + cache.write(CONFIGS, 58, it.key, codec.encodeToBuffer(NpcServerType(it.key))) + } + } + "health" -> { + val codec = HealthBarServerCodec(CacheManager.getHealthBars()) + CacheManager.getHealthBars().forEach { + cache.write(CONFIGS, 56, it.key, codec.encodeToBuffer(HealthBarServerType(it.key))) + } + } + "item" -> { + val codec = ItemServerCodec( + CacheManager.getItems(), + CacheManager.getEnums(), + InfoBoxItem.load(File("../data/raw-cache/extra-dump/item-data.json").toPath())) + + CacheManager.getItems().forEach { + cache.write(CONFIGS, 59, it.key, codec.encodeToBuffer(ItemServerType(it.key))) + } + } + "anims" -> { + val codec3 = SequenceServerCodec(CacheManager.getAnims()) + CacheManager.getAnims().forEach { + cache.write(CONFIGS, 57, it.key, codec3.encodeToBuffer(SequenceServerType(it.key))) + } + } + "inventory" -> { + val inventoryTypes = mutableMapOf().apply { + OsrsCacheProvider.InventoryDecoder().load(cache, this) + } + + val serverTypes: Map? = + parsedDefinitions["inventory"] + ?.filterIsInstance() + ?.associateBy { it.id } + + val codec = InventoryServerCodec( + inventoryTypes, + serverTypes + ) + + inventoryTypes.forEach { (id, _) -> + cache.write(CONFIGS, 60, id, codec.encodeToBuffer(InventoryServerType(id))) + } + } + else -> println("Missing Type: $type") + } + progress.step() + } + progress.close() + + } + + private fun processDocumentChanges(content: String): String { + val tokenMap = tokenizedReplacement.toMutableMap() + val regex = Regex("""\[\[tokenizedReplacement]](.*?)(?=\n\[\[|\z)""", RegexOption.DOT_MATCHES_ALL) + + regex.find(content)?.groups?.get(1)?.value + ?.lineSequence() + ?.map { it.trim() } + ?.filter { it.isNotEmpty() } + ?.forEach { parseInlineTableString(it).forEach { (k, v) -> tokenMap[k] = v.removeSurrounding("\"") } } + + var updated = content + tokenMap.forEach { (key, value) -> + updated = updated.replace(Regex("%${Regex.escape(key)}%", RegexOption.IGNORE_CASE), value) + } + + return processRSCMModifier(updated) + } + + private fun parseInlineTableString(input: String): List> = + input.removePrefix("{").removeSuffix("}") + .split(",") + .map { it.split("=").map { it.trim() } } + .map { it[0].lowercase() to it[1] } + + private fun processRSCMModifier(input: String): String { + val allowedPrefixes = ConstantProvider.types + val output = StringBuilder() + var debugNameAdded = false + + val quotedStringRegex = Regex(""""([^"]+)"""") + + input.lines().forEach { line -> + val trimmed = line.trim() + + if (trimmed.startsWith("[[")) { + debugNameAdded = false + output.appendLine(line) + return@forEach + } + + var modifiedLine = line + val matches = quotedStringRegex.findAll(line) + + for (match in matches) { + val fullValue = match.groupValues[1] + if (allowedPrefixes.any { fullValue.startsWith(it) }) { + val resolved = ConstantProvider.getMapping(fullValue) + modifiedLine = modifiedLine.replace("\"$fullValue\"", resolved.toString()) + + if (!debugNameAdded && trimmed.startsWith("id") && fullValue == match.groupValues[1]) { + output.appendLine("debugName = \"${fullValue.substringAfter(".")}\"") + debugNameAdded = true + } + } + } + + output.appendLine(modifiedLine) + } + + return output.toString() + } + + companion object { + + private val tomlMapperDefault = tomlMapper { } + val packTypes = mutableMapOf() + + fun registerPackType( + name: String, + index: Int = CONFIGS, + tomlMapper: TomlMapper = tomlMapperDefault, + kType: KType, + ) { + val packType = PackType(index, name,tomlMapper,kType) + packTypes[packType.name] = packType + } + + } + +} \ No newline at end of file diff --git a/cache/bin/main/dev/openrune/types/HealthBarServerType.kt b/cache/bin/main/dev/openrune/types/HealthBarServerType.kt new file mode 100644 index 00000000..35cc7345 --- /dev/null +++ b/cache/bin/main/dev/openrune/types/HealthBarServerType.kt @@ -0,0 +1,13 @@ +package dev.openrune.types + +import dev.openrune.definition.Definition + +data class HealthBarServerType( + override var id: Int = -1, + var width: Int = 30, +) : Definition { + + fun barWidth(curHealth : Int, maxHealth : Int): Int { + return ((curHealth.toDouble() / maxHealth) * width).toInt() + } +} diff --git a/cache/bin/main/dev/openrune/types/InventoryServerType.kt b/cache/bin/main/dev/openrune/types/InventoryServerType.kt new file mode 100644 index 00000000..f804c88c --- /dev/null +++ b/cache/bin/main/dev/openrune/types/InventoryServerType.kt @@ -0,0 +1,98 @@ +package dev.openrune.types + +import dev.openrune.definition.Definition +import org.alter.rscm.RSCM +import org.alter.rscm.RSCMType + +data class InventoryServerType( + override var id: Int = -1, + var stack: InvStackType = InvStackType.Normal, + var size: Int = 1, + var scope : InvScope = InvScope.Temp, + var flags: Int = pack( + protect = true, + allStock = false, + restock = false, + runWeight = false, + dummyInv = false, + placeholders = false, + ), + val stock: Array = emptyArray(), +) : Definition { + + public val restock: Boolean + get() = flags and RESTOCK_FLAG != 0 + + public val allStock: Boolean + get() = flags and ALL_STOCK_FLAG != 0 + + public val protect: Boolean + get() = flags and PROTECT_FLAG != 0 + + public val runWeight: Boolean + get() = flags and RUN_WEIGHT_FLAG != 0 + + public val dummyInv: Boolean + get() = flags and DUMMY_INV_FLAG != 0 + + public val placeholders: Boolean + get() = flags and PLACEHOLDERS_FLAG != 0 + + + public companion object { + public const val PROTECT_FLAG: Int = 0x1 + public const val ALL_STOCK_FLAG: Int = 0x2 + public const val RESTOCK_FLAG: Int = 0x4 + public const val RUN_WEIGHT_FLAG: Int = 0x8 + public const val DUMMY_INV_FLAG: Int = 0x10 + public const val PLACEHOLDERS_FLAG: Int = 0x20 + + fun pack( + protect: Boolean, + allStock: Boolean, + restock: Boolean, + runWeight: Boolean, + dummyInv: Boolean, + placeholders: Boolean, + ): Int { + var flags = 0 + if (protect) { + flags = flags or PROTECT_FLAG + } + if (allStock) { + flags = flags or ALL_STOCK_FLAG + } + if (restock) { + flags = flags or RESTOCK_FLAG + } + if (runWeight) { + flags = flags or RUN_WEIGHT_FLAG + } + if (dummyInv) { + flags = flags or DUMMY_INV_FLAG + } + if (placeholders) { + flags = flags or PLACEHOLDERS_FLAG + } + return flags + } + } + +} + +data class InvStock constructor(public val obj: Int, public val count: Int, public val restockCycles: Int) + +public enum class InvStackType(public val id: Int) { + Normal(0), + Always(1), + Never(2), +} + +public enum class InvScope(public val id: Int) { + /** Temporary inventories that do not save. (e.g., clue scrolls) */ + Temp(0), + /** Persistent inventories that will save. (e.g., bank) */ + Perm(1), + /** Global Inventories that are shared. (e.g., shops) */ + Shared(2), +} diff --git a/cache/bin/main/dev/openrune/types/ItemServerType.kt b/cache/bin/main/dev/openrune/types/ItemServerType.kt new file mode 100644 index 00000000..1d4ebe06 --- /dev/null +++ b/cache/bin/main/dev/openrune/types/ItemServerType.kt @@ -0,0 +1,128 @@ +package dev.openrune.types + +import dev.openrune.definition.Definition +import dev.openrune.server.impl.item.WeaponTypes + +data class ItemServerType( + override var id: Int = -1, + var cost: Int = -1, + var name: String = "", + var weight: Double = 0.0, + var isTradeable: Boolean = false, + var category: Int = -1, + var options: MutableList = mutableListOf(null, null, "Take", null, null), + var interfaceOptions: MutableList = mutableListOf(null, null, null, null, "Drop"), + var noteLinkId: Int = -1, + var noteTemplateId: Int = -1, + var placeholderLink: Int = -1, + var dummyitem : Int = 0, + var placeholderTemplate: Int = -1, + var stacks: Int = 0, + var appearanceOverride1: Int = -1, + var appearanceOverride2: Int = -1, + var examine : String = "", + var destroy: String = "", + var alchable: Boolean = true, + var exchangeCost: Int = -1, + + var transformlink : Int = -1, + var transformtemplate: Int = -1, + var equipment: Equipment? = null, + var weapon: Weapon? = null, + var params: MutableMap? = null, +) : Definition { + + public val resolvedDummyitem: Dummyitem? + get() = Dummyitem[dummyitem] + + + val stackable: Boolean + get() = stacks == 1 || noteTemplateId > 0 + + val noted: Boolean + get() = noteTemplateId > 0 + + /** + * Whether or not the object is a placeholder. + */ + val isPlaceholder + get() = placeholderTemplate > 0 && placeholderLink > 0 + +} + +data class EquipmentStats( + var attackStab: Int = 0, + var attackSlash: Int = 0, + var attackCrush: Int = 0, + var attackMagic: Int = 0, + var attackRanged: Int = 0, + var defenceStab: Int = 0, + var defenceSlash: Int = 0, + var defenceCrush: Int = 0, + var defenceMagic: Int = 0, + var defenceRanged: Int = 0, + var meleeStrength: Int = 0, + var prayer: Int = 0, + var rangedStrength: Int = 0, + var magicStrength: Int = 0, + var rangedDamage: Int = 0, + var magicDamage: Int = 0, + var demonDamage: Int = 0, + var degradeable: Int = 0, + var silverStrength: Int = 0, + var corpBoost: Int = 0, + var golemDamage: Int = 0, + var kalphiteDamage: Int = 0 +) + + +data class Equipment( + var equipSlot: Int = -1, + var equipType : Int = -1, + var requirements: Map = emptyMap(), + var stats: EquipmentStats? = null +) { + + //val equipmentOptions: List by lazy { + // (0 until 7).map { cachedParams.getString(451 + it).takeIf { it.isNotEmpty() } } + //} +} + + +data class Weapon( + var weaponTypeRenderData: String? = "default_player", + var weaponType: WeaponTypes = WeaponTypes.UNARMED, + var attackSpeed: Int = 0, + var attackRange: Int = 0, + var specAmount: Int = -1 +) { + fun hasSpec() = specAmount != -1 +} + + +private fun Map.getString(key: Int): String = + this[key.toString()]?.toString() ?: "" + +fun Map.getInt(key: Int): Int = when (val v = this[key.toString()]) { + is Int -> v + is Number -> v.toInt() + is String -> v.toIntOrNull() ?: 0 + else -> 0 +} + +public enum class Dummyitem(public val id: Int) { + /** Cannot be added into inventories or dropped on floor. */ + GraphicOnly(id = 1), + /** Can be added into inventories, but cannot be dropped on floor. */ + InvOnly(id = 2); + + public companion object { + public operator fun get(id: Int): Dummyitem? = + when (id) { + GraphicOnly.id -> GraphicOnly + InvOnly.id -> InvOnly + else -> null + } + } +} + diff --git a/cache/bin/main/dev/openrune/types/NpcServerType.kt b/cache/bin/main/dev/openrune/types/NpcServerType.kt new file mode 100644 index 00000000..f6a3d6be --- /dev/null +++ b/cache/bin/main/dev/openrune/types/NpcServerType.kt @@ -0,0 +1,44 @@ +package dev.openrune.types + +import dev.openrune.definition.Definition + +data class NpcServerType( + override var id: Int = -1, + var name : String = "", + var size: Int = 1, + var category: Int = -1, + var standAnim: Int = -1, + var rotateLeftAnim: Int = -1, + var rotateRightAnim: Int = -1, + var walkAnim: Int = -1, + var rotateBackAnim: Int = -1, + var walkLeftAnim: Int = -1, + var walkRightAnim: Int = -1, + var actions: MutableList = mutableListOf(null, null, null, null, null), + var varbit: Int = -1, + var varp: Int = -1, + var transforms: MutableList? = null, + var combatLevel: Int = -1, + var renderPriority: Int = 0, + var lowPriorityFollowerOps: Boolean = false, + var isFollower: Boolean = false, + var runSequence: Int = -1, + var isInteractable : Boolean = true, + var runBackSequence: Int = -1, + var runRightSequence: Int = -1, + var runLeftSequence: Int = -1, + var crawlSequence: Int = -1, + var crawlBackSequence: Int = -1, + var crawlRightSequence: Int = -1, + var crawlLeftSequence: Int = -1, + var height: Int = -1, + var attack : Int = 1, + var defence : Int = 1, + var strength : Int = 1, + var hitpoints : Int = 1, + var ranged : Int = 1, + var magic : Int = 1, + var params: MutableMap? = null, +) : Definition { + fun isAttackable(): Boolean = combatLevel > 0 && actions.any { it == "Attack" } +} diff --git a/cache/bin/main/dev/openrune/types/ObjectServerType.kt b/cache/bin/main/dev/openrune/types/ObjectServerType.kt new file mode 100644 index 00000000..27a7844c --- /dev/null +++ b/cache/bin/main/dev/openrune/types/ObjectServerType.kt @@ -0,0 +1,31 @@ +package dev.openrune.types + +import dev.openrune.definition.Definition + +data class ObjectServerType( + override var id: Int = -1, + var name : String = "", + var examine : String = "", + var sizeX: Int = 1, + var sizeY: Int = 1, + var offsetX: Int = 0, + var interactive: Int = -1, + var solid: Int = 2, + var actions: MutableList = mutableListOf(null, null, null, null, null), + var modelSizeX: Int = 128, + var modelSizeZ: Int = 128, + var modelSizeY: Int = 128, + var offsetZ: Int = 0, + var offsetY: Int = 0, + var clipMask: Int = 0, + var obstructive: Boolean = false, + var category: Int = -1, + var supportsItems: Int = -1, + var isRotated: Boolean = false, + var impenetrable: Boolean = true, + var replacementId: Int = -1, + var varbit: Int = -1, + var varp: Int = -1, + var transforms: MutableList? = null, + var params: MutableMap? = null +) : Definition diff --git a/cache/bin/main/dev/openrune/types/SequenceServerType.kt b/cache/bin/main/dev/openrune/types/SequenceServerType.kt new file mode 100644 index 00000000..02fc3197 --- /dev/null +++ b/cache/bin/main/dev/openrune/types/SequenceServerType.kt @@ -0,0 +1,8 @@ +package dev.openrune.types + +import dev.openrune.definition.Definition + +data class SequenceServerType( + override var id: Int = -1, + var animationLength: Int = 0, +) : Definition diff --git a/cache/bin/main/dev/openrune/util/Coords.kt b/cache/bin/main/dev/openrune/util/Coords.kt new file mode 100644 index 00000000..c4fe0f4a --- /dev/null +++ b/cache/bin/main/dev/openrune/util/Coords.kt @@ -0,0 +1,3 @@ +package dev.openrune.util + +fun Coords(x: Int, y: Int, z: Int = 0): Int = (z and 3) shl 28 or ((x and 16383) shl 14) or (y and 16383) \ No newline at end of file diff --git a/cache/bin/main/dev/openrune/util/TextAlignment.kt b/cache/bin/main/dev/openrune/util/TextAlignment.kt new file mode 100644 index 00000000..8fd60f38 --- /dev/null +++ b/cache/bin/main/dev/openrune/util/TextAlignment.kt @@ -0,0 +1,204 @@ +package dev.openrune.util + +import dev.openrune.ServerCacheManager +import dev.openrune.cache.filestore.definition.FontDecoder +import dev.openrune.definition.type.FontType +import dev.openrune.filesystem.Cache +import org.alter.rscm.RSCM.asRSCM +import kotlin.math.ceil + +object TextAlignment { + + + public fun generateChatPageList(text: String): List { + return generatePageList(text, CHAT_MAX_LINE_PIXEL_WIDTH, "fonts.q8_full") + } + + public fun generateMesPageList(text: String): List { + return generatePageList(text, MESBOX_MAX_LINE_PIXEL_WIDTH, "fonts.q8_full") + } + + public fun generatePageList( + text: String, + lineWidth: Int, + fontName: String, + ): List { + + val font = ServerCacheManager.getFont(fontName.asRSCM())!! + + val lineBuffers = Array(MAX_TOTAL_LINE_COUNT) { "" } + val lineCount = font.splitText(text, lineWidth, lineBuffers) + val pageCount = ceil(lineCount.toDouble() / LINES_PER_PAGE).toInt() + return when (pageCount) { + 1 -> { + val page1 = lineBuffers.joinToPageString(0 until lineCount) + listOf(Page(page1, lineCount)) + } + 2 -> { + val page1 = lineBuffers.joinToPageString(0 until LINES_PER_PAGE) + + val page2LineCount = lineCount - LINES_PER_PAGE + val page2 = + lineBuffers.joinToPageString( + LINES_PER_PAGE until LINES_PER_PAGE + page2LineCount + ) + return listOf(Page(page1, LINES_PER_PAGE), Page(page2, page2LineCount)) + } + // Don't see a point in supporting more than two pages from one single text string. + // There will be extremely niche situations where one text will be split into two + // pages, such as when the player display name is long enough to cause the dialogue + // to need a second page. + else -> throw NotImplementedError("Page list of size `$pageCount` is not implemented.") + } + } + + private fun FontType.splitText( + text: String, + widthPerLine: Int, + lineBuffer: Array, + ): Int { + val lineBuilder = StringBuilder(100) + var lineWidth = 0 + var lineStart = 0 + var lastWordIndex = -1 + var wordWidth = 0 + var extraSpacing: Byte = 0 + var tagStart: Int = -1 + var prevChar: Char = 0.toChar() + var lineCount = 0 + for (index in text.indices) { + var char = text[index] + + if (char == '<') { + tagStart = index + continue + } + + if (char == '>' && tagStart != -1) { + val tag = text.substring(tagStart + 1, index) + tagStart = -1 + lineBuilder.append('<').append(tag).append('>') + when (tag) { + "br" -> { + lineBuffer[lineCount] = lineBuilder.substring(lineStart) + lineCount++ + lineStart = lineBuilder.length + lineWidth = 0 + lastWordIndex = -1 + prevChar = 0.toChar() + } + "lt" -> { + lineWidth += getAdjustedGlyphAdvance('<') + if (kerning.isNotEmpty() && prevChar != 0.toChar()) { + lineWidth += kerning[(prevChar.code shl 8) + 60] + } + prevChar = '<' + } + "gt" -> { + lineWidth += getAdjustedGlyphAdvance('>') + if (kerning.isNotEmpty() && prevChar != 0.toChar()) { + lineWidth += kerning[(prevChar.code shl 8) + 62] + } + prevChar = '>' + } + else -> + if (tag.startsWith("img=")) { + // TODO: load width from mod_icons + // val imgId = tag.substring(4).toInt() + // glyphs[imgId].getWidth() + lineWidth += 13 + prevChar = 0.toChar() + } + } + char = 0.toChar() + } + + if (tagStart == -1) { + if (char != 0.toChar()) { + lineBuilder.append(char) + lineWidth += getAdjustedGlyphAdvance(char) + if (kerning.isNotEmpty() && prevChar != 0.toChar()) { + lineWidth += kerning[(prevChar.code shl 8) or char.code] + } + prevChar = char + } + + if (char == ' ') { + lastWordIndex = lineBuilder.length + wordWidth = lineWidth + extraSpacing = 1 + } + + if (lineWidth > widthPerLine && lastWordIndex >= 0) { + lineBuffer[lineCount] = + lineBuilder.substring(lineStart, lastWordIndex - extraSpacing) + lineCount++ + lineStart = lastWordIndex + lastWordIndex = -1 + lineWidth -= wordWidth + prevChar = 0.toChar() + } + + if (char == '-') { + lastWordIndex = lineBuilder.length + wordWidth = lineWidth + extraSpacing = 0 + } + } + } + val line = lineBuilder.toString() + if (line.length > lineStart) { + lineBuffer[lineCount++] = line.substring(lineStart) + } + return lineCount + } + + private fun FontType.getAdjustedGlyphAdvance(char: Char): Int { + val adjustedChar = if (char == 160.toChar()) ' ' else char + return glyphAdvances[adjustedChar.code and 0xFF] + } + + private fun Array.joinToPageString(indexRangeExclusive: IntRange): String = + indexRangeExclusive.joinToString(separator = "") { index -> + this[index] + if (index < indexRangeExclusive.last) LINE_SEPARATOR else "" + } + + public fun chatLineHeight(lineCount: Int): Int = + when (lineCount) { + 2 -> CHAT_TWO_LINE_LINE_HEIGHT + 3 -> CHAT_THREE_LINE_LINE_HEIGHT + else -> CHAT_DEFAULT_LINE_HEIGHT + } + + public fun mesLineHeight(lineCount: Int): Int = + when (lineCount) { + 2 -> MESBOX_TWO_LINE_LINE_HEIGHT + 3 -> MESBOX_THREE_LINE_LINE_HEIGHT + 4 -> MESBOX_FOUR_LINE_LINE_HEIGHT + else -> MESBOX_DEFAULT_LINE_HEIGHT + } + + public data class Page(val text: String, val lineCount: Int) + + private const val LINES_PER_PAGE: Int = 4 + private const val MAX_PAGE_COUNT: Int = 2 + private const val MAX_TOTAL_LINE_COUNT: Int = LINES_PER_PAGE * MAX_PAGE_COUNT + + private const val CHAT_MAX_LINE_PIXEL_WIDTH: Int = 380 + private const val MESBOX_MAX_LINE_PIXEL_WIDTH: Int = 470 + + private const val CHAT_DEFAULT_LINE_HEIGHT: Int = 16 + private const val CHAT_TWO_LINE_LINE_HEIGHT: Int = 28 + private const val CHAT_THREE_LINE_LINE_HEIGHT: Int = 20 + + private const val MESBOX_DEFAULT_LINE_HEIGHT: Int = 0 + private const val MESBOX_TWO_LINE_LINE_HEIGHT: Int = 31 + private const val MESBOX_THREE_LINE_LINE_HEIGHT: Int = 24 + private const val MESBOX_FOUR_LINE_LINE_HEIGHT: Int = 17 + + /** + * Used as a separator for each line in a dialogue page. This _can_ be left as whitespace as + * this is handled on the client's end as well. However, we send it for emulation. + */ + private const val LINE_SEPARATOR: String = "
" +} diff --git a/cache/bin/main/logback.xml b/cache/bin/main/logback.xml new file mode 100644 index 00000000..a7c4e080 --- /dev/null +++ b/cache/bin/main/logback.xml @@ -0,0 +1,20 @@ + + + + + [%d{HH:mm:ss.SSS}] %highlight(%-5level) %cyan(%logger{0}) - %msg %n + + + + + + + + + + + + + + + diff --git a/cache/bin/main/org/alter/BufferReader.kt b/cache/bin/main/org/alter/BufferReader.kt new file mode 100644 index 00000000..1bf15372 --- /dev/null +++ b/cache/bin/main/org/alter/BufferReader.kt @@ -0,0 +1,174 @@ +package org.alter + +import java.nio.ByteBuffer + +class BufferReader( + val buffer: ByteBuffer +) { + + constructor(array: ByteArray) : this(buffer = ByteBuffer.wrap(array)) + + val length: Int = buffer.remaining() + val remaining: Int + get() = buffer.remaining() + private var bitIndex = 0 + + fun readByte(): Int { + return buffer.get().toInt() + } + + fun readByteAdd(): Int { + return (readByte() - 128).toByte().toInt() + } + + fun readByteInverse(): Int { + return -readByte() + } + + fun readByteSubtract(): Int { + return (readByteInverse() + 128).toByte().toInt() + } + + fun readUnsignedByte(): Int { + return readByte() and 0xff + } + + fun readShort(): Int { + return (readByte() shl 8) or readUnsignedByte() + } + + fun readShortAdd(): Int { + return (readByte() shl 8) or readUnsignedByteAdd() + } + + fun readUnsignedShortAdd(): Int { + return (readByte() shl 8) or ((readByte() - 128) and 0xff) + } + + fun readShortLittle(): Int { + return readUnsignedByte() or (readByte() shl 8) + } + + fun readShortAddLittle(): Int { + return readUnsignedByteAdd() or (readByte() shl 8) + } + + fun readShortSmart() : Int { + val peek = readUnsignedByte() + return if (peek < 128) peek - 64 else (peek shl 8 or readUnsignedByte()) - 49152 + } + + fun readUnsignedByteAdd(): Int { + return (readByte() - 128).toByte().toInt() + } + + fun readUnsignedShort(): Int { + return (readUnsignedByte() shl 8) or readUnsignedByte() + } + + fun readUnsignedShortLittle(): Int { + return readUnsignedByte() or (readUnsignedByte() shl 8) + } + + fun readMedium(): Int { + return (readByte() shl 16) or (readByte() shl 8) or readUnsignedByte() + } + + fun readUnsignedMedium(): Int { + return (readUnsignedByte() shl 16) or (readUnsignedByte() shl 8) or readUnsignedByte() + } + + fun readInt(): Int { + return (readUnsignedByte() shl 24) or (readUnsignedByte() shl 16) or (readUnsignedByte() shl 8) or readUnsignedByte() + } + + fun readIntInverseMiddle(): Int { + return (readByte() shl 16) or (readByte() shl 24) or readUnsignedByte() or (readByte() shl 8) + } + + fun readIntLittle(): Int { + return readUnsignedByte() or (readByte() shl 8) or (readByte() shl 16) or (readByte() shl 24) + } + + fun readUnsignedIntMiddle(): Int { + return (readUnsignedByte() shl 8) or readUnsignedByte() or (readUnsignedByte() shl 24) or (readUnsignedByte() shl 16) + } + + fun readSmart(): Int { + val peek = readUnsignedByte() + return if (peek < 128) { + peek and 0xFF + } else { + (peek shl 8 or readUnsignedByte()) - 32768 + } + } + + fun readBigSmart(): Int { + val peek = readByte() + return if (peek < 0) { + ((peek shl 24) or (readUnsignedByte() shl 16) or (readUnsignedByte() shl 8) or readUnsignedByte()) and 0x7fffffff + } else { + val value = (peek shl 8) or readUnsignedByte() + if (value == 32767) -1 else value + } + } + + fun readLargeSmart(): Int { + var baseValue = 0 + var lastValue = readSmart() + while (lastValue == 32767) { + lastValue = readSmart() + baseValue += 32767 + } + return baseValue + lastValue + } + + fun readLong(): Long { + val first = readInt().toLong() and 0xffffffffL + val second = readInt().toLong() and 0xffffffffL + return second + (first shl 32) + } + + fun readString(): String { + val sb = StringBuilder() + var b: Int + while (buffer.hasRemaining()) { + b = readUnsignedByte() + if (b == 0) { + break + } + sb.append(b.toChar()) + } + return sb.toString() + } + + fun readBytes(value: ByteArray) { + buffer.get(value) + } + + fun readBytes(array: ByteArray, offset: Int, length: Int) { + buffer.get(array, offset, length) + } + + fun skip(amount: Int) { + buffer.position(buffer.position() + amount) + } + + fun position(): Int { + return buffer.position() + } + + fun position(index: Int) { + buffer.position(index) + } + + fun array(): ByteArray { + return buffer.array() + } + + fun readableBytes(): Int { + return buffer.remaining() + } + + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/CacheTools.kt b/cache/bin/main/org/alter/CacheTools.kt new file mode 100644 index 00000000..3e681250 --- /dev/null +++ b/cache/bin/main/org/alter/CacheTools.kt @@ -0,0 +1,206 @@ +package org.alter + +import com.displee.cache.CacheLibrary +import dev.openrune.OsrsCacheProvider +import dev.openrune.cache.gameval.GameValHandler +import dev.openrune.cache.tools.Builder +import dev.openrune.cache.tools.CacheEnvironment +import dev.openrune.cache.tools.tasks.CacheTask +import dev.openrune.cache.tools.tasks.TaskType +import dev.openrune.cache.tools.tasks.impl.PackDBTables +import dev.openrune.cache.tools.tasks.impl.defs.PackConfig +import dev.openrune.definition.GameValGroupTypes +import dev.openrune.definition.type.DBRowType +import dev.openrune.definition.util.VarType +import dev.openrune.filesystem.Cache +import dev.openrune.tools.MinifyServerCache +import dev.openrune.tools.PackServerConfig +import io.github.oshai.kotlinlogging.KotlinLogging +import org.alter.codegen.startGeneration +import org.alter.gamevals.GameValProvider +import org.alter.gamevals.GamevalDumper +import org.alter.impl.GameframeTable +import org.alter.impl.skills.cooking.CookingTables +import org.alter.impl.skills.Firemaking +import org.alter.impl.misc.FoodTable +import org.alter.impl.skills.PrayerTable +import org.alter.impl.StatComponents +import org.alter.impl.misc.TeleTabs +import org.alter.impl.skills.Woodcutting +import org.alter.impl.skills.Herblore +import org.alter.impl.skills.Mining +import org.alter.impl.skills.Smithing +import org.alter.impl.skills.runecrafting.Alters +import org.alter.impl.skills.runecrafting.CombinationRune +import org.alter.impl.skills.runecrafting.RunecraftRune +import org.alter.impl.skills.runecrafting.Tiara +import java.io.File +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import kotlin.system.exitProcess + +fun getCacheLocation() = File("../data/", "cache/LIVE").path +fun getServerCacheLocation() = File("../data/", "cache/SERVER").path +fun getRawCacheLocation(dir: String) = File("../data/", "raw-cache/$dir/") + +fun tablesToPack() = listOf( + PrayerTable.skillTable(), + TeleTabs.teleTabs(), + StatComponents.statsComponents(), + FoodTable.consumableFood(), + CookingTables.actionOutcomes(), + CookingTables.actionInputs(), + CookingTables.actions(), + Firemaking.logs(), + Woodcutting.trees(), + Woodcutting.axes(), + Herblore.unfinishedPotions(), + Herblore.finishedPotions(), + Herblore.cleaningHerbs(), + Herblore.barbarianMixes(), + Herblore.swampTar(), + Herblore.crushing(), + Mining.pickaxes(), + Mining.rocks(), + Mining.miningEnhancers(), + Alters.altars(), + Tiara.tiara(), + RunecraftRune.runecraftRune(), + CombinationRune.runecraftComboRune(), + GameframeTable.gameframe(), + Smithing.bars(), + Smithing.cannonBalls(), + Smithing.dragonForge(), + Smithing.crystalSinging() +) + +private val logger = KotlinLogging.logger {} + +fun main(args: Array) { + if (args.isEmpty()) { + println("Usage: ") + exitProcess(1) + } + downloadRev(TaskType.valueOf(args.first().uppercase())) +} + +fun downloadRev(type: TaskType) { + + val rev = readRevision() + + logger.info { "Using Revision: $rev" } + + when (type) { + TaskType.FRESH_INSTALL -> { + + val builder = Builder(type = TaskType.FRESH_INSTALL, + cacheLocation = File(getCacheLocation()), + serverCacheLocation = File(getServerCacheLocation()) + ) + builder.revision(rev.first) + builder.subRevision(rev.second) + builder.removeXteas(false) + builder.environment(CacheEnvironment.valueOf(rev.third)) + + builder.build().initialize() + + Files.move( + File(getCacheLocation(), "xteas.json").toPath(), + File("../data/cache/", "xteas.json").toPath(), + StandardCopyOption.REPLACE_EXISTING + ) + + File(getServerCacheLocation(), "xteas.json").delete() + + val cache = Cache.load(File(getCacheLocation()).toPath()) + + GamevalDumper.dumpGamevals(cache,rev.first) + + buildCache(TaskType.BUILD,rev) + } + TaskType.SERVER_CACHE_BUILD -> buildCache(TaskType.SERVER_CACHE_BUILD,rev) + TaskType.BUILD -> buildCache(TaskType.BUILD,rev) + + } +} + + +data class ColInfo( + val types: MutableMap = mutableMapOf(), + var optional: Boolean = false, + var noData: Boolean = false +) + +fun buildCache(taskType: TaskType,rev: Triple) { + GameValProvider.load(autoAssignIds = true) + + val tasks: List = listOf( + PackConfig(File("../data/raw-cache/server")) + ).toMutableList() + + val builder = Builder(type = taskType, + cacheLocation = File(getCacheLocation()), + serverCacheLocation = File(getServerCacheLocation()) + ) + builder.revision(rev.first) + + val tasksNew = tasks.toMutableList() + tasksNew.add(PackDBTables(tablesToPack())) + + builder.extraTasks(*tasksNew.toTypedArray()).build().initialize() + if (taskType == TaskType.BUILD) { + builder.type = TaskType.SERVER_CACHE_BUILD + builder.extraTasks( + PackServerConfig(File("../data/raw-cache/server")), + *tasksNew.toTypedArray() + ).build().initialize() + } + + if (builder.type == TaskType.SERVER_CACHE_BUILD) { + + MinifyServerCache().init(getServerCacheLocation()) + + val cache = Cache.load(File(getServerCacheLocation()).toPath()) + + GamevalDumper.dumpCols(cache,rev.first) + + val type = GameValHandler.readGameVal(GameValGroupTypes.TABLETYPES, cache = cache, rev.first) + + val rows : MutableMap = emptyMap().toMutableMap() + + OsrsCacheProvider.DBRowDecoder().load(cache,rows) + + startGeneration(type,rows) + } +} + +fun readRevision(): Triple { + val file = listOf("../game.yml", "../game.example.yml") + .map(::File) + .firstOrNull { it.exists() } + ?: error("No game.yml or game.example.yml found") + + return file.useLines { lines -> + val revisionLine = lines.firstOrNull { it.trimStart().startsWith("revision:") } + ?: error("No revision line found in ${file.name}") + + val revisionStr = revisionLine.substringAfter("revision:").trim() + val match = Regex("""^(\d+)(?:\.(\d+))?$""").matchEntire(revisionStr) + ?: error("Invalid revision format: '$revisionStr'") + + val major = match.groupValues[1].toInt() + val minor = match.groupValues.getOrNull(2)?.toIntOrNull() ?: -1 + + val envLine = file.readLines() + .firstOrNull { it.trimStart().startsWith("environment:") } + + val environment = envLine + ?.substringAfter("environment:") + ?.trim() + ?.removeSurrounding("\"") + ?.ifBlank { "live" } + ?: "live" + + Triple(major, minor, environment.uppercase()) + } +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/ParamMapper.kt b/cache/bin/main/org/alter/ParamMapper.kt new file mode 100644 index 00000000..bb5768f1 --- /dev/null +++ b/cache/bin/main/org/alter/ParamMapper.kt @@ -0,0 +1,170 @@ +package org.alter + +object ParamMapper { + object item { + val STAB_ATTACK_BONUS = 0 + val SLASH_ATTACK_BONUS = 1 + val CRUSH_ATTACK_BONUS = 2 + val MAGIC_ATTACK_BONUS = 3 + val RANGED_ATTACK_BONUS = 4 + val STAB_DEFENCE_BONUS = 5 + val SLASH_DEFENCE_BONUS = 6 + val CRUSH_DEFENCE_BONUS = 7 + val MAGIC_DEFENCE_BONUS = 8 + val RANGED_DEFENCE_BONUS = 9 + val MELEE_STRENGTH = 10 + val PRAYER_BONUS = 11 + val ATTACK_RATE = 14 + val MAGIC_DAMAGE_BONUS_SALAMANDER = 65 + val MAGIC_DAMAGE_STRENGTH = 299 // Should be divided by 10 + val RANGED_STRENGTH_BONUS = 189 + val PRIMARY_SKILL = 434 + val PRIMARY_LEVEL = 436 + val SECONDARY_SKILL = 435 + val SECONDARY_LEVEL = 437 + val TERTIARY_SKILL = 191 + val TERTIARY_LEVEL = 613 + val QUATERNARY_SKILL = 579 + val QUATERNARY_LEVEL = 614 + } + object npc { + const val STAB_ATTACK_BONUS = 0 + const val SLASH_ATTACK_BONUS = 1 + const val CRUSH_ATTACK_BONUS = 2 + const val MAGIC_ATTACK_BONUS = 3 + const val RANGED_ATTACK_BONUS = 4 + const val STAB_DEFENCE_BONUS = 5 + const val SLASH_DEFENCE_BONUS = 6 + const val CRUSH_DEFENCE_BONUS = 7 + const val MAGIC_DEFENCE_BONUS = 8 + const val RANGED_DEFENCE_BONUS = 9 + const val MELEE_STRENGTH_BONUS = 10 + const val RANGED_STRENGTH_BONUS = 12 + const val ATTACK_RATE = 14 // Attack rate in ticks + const val MAGIC_DAMAGE_BONUS = 65 + const val DRACONIC = 190 // 1 = If is draconic + const val GOLEM = 1178 // 1 = If is Golem + const val KALPHITE = 1353 // 1 = If is Kalphite + const val DEATH_DROP = 46 // What to drop on death Bones/Ashes etc. + const val PRIMARY_SLAYER_CATEGORY = 50 + + object SLAYER_CATEGORIES { + const val MONKEYS = 1 + const val GOBLINS = 2 + const val RATS = 3 + const val SPIDERS = 4 + const val BIRDS = 5 + const val COWS = 6 + const val SCORPIONS = 7 + const val BATS = 8 + const val WOLVES = 9 + const val ZOMBIES = 10 + const val SKELETONS = 11 + const val GHOSTS = 12 + const val BEARS = 13 + const val HILL_GIANTS = 14 + const val ICE_GIANTS_ICE_GIANTS = 15 + const val FIRE_GIANTS = 16 + const val MOSS_GIANTS = 17 + const val TROLLS = 18 + const val ICE_WARRIORS = 19 + const val OGRES = 20 + const val HOBGOBLINS = 21 + const val DOGS = 22 + const val GHOULS = 23 + const val GREEN_DRAGONS = 24 + const val BLUE_DRAGONS = 25 + const val RED_DRAGONS = 26 + const val BLACK_DRAGONS = 27 + const val LESSER_DEMONS = 28 + const val GREATER_DEMONS = 29 + const val BLACK_DEMONS = 30 + const val HELLHOUNDS = 31 + const val SHADOW_WARRIORS = 32 + const val WEREWOLVES = 33 + const val VAMPYRES = 34 + const val DAGANNOTH = 35 + const val TUROTH = 36 + const val CAVE_CRAWLERS = 37 + const val BANSHEES = 38 + const val CRAWLING_HANDS = 39 + const val INFERNAL_MAGES = 40 + const val ABERRANT_SPECTRES = 41 + const val ABYSSAL_DEMONS = 42 + const val BASILISKS = 43 + const val COCKATRICE = 44 + const val KURASK = 45 + const val GARGOYLES = 46 + const val PYREFIENDS = 47 + const val BLOODVELD = 48 + const val DUST_DEVILS = 49 + const val JELLIES = 50 + const val ROCKSLUGS = 51 + const val NECHRYAEL = 52 + const val KALPHITE = 53 + const val EARTH_WARRIORS = 54 + const val OTHERWORLDLY_BEINGS = 55 + const val ELVES = 56 + const val DWARVES = 57 + const val BRONZE_DRAGONS = 58 + const val IRON_DRAGONS = 59 + const val STEEL_DRAGONS = 60 + const val WALL_BEASTS = 61 + const val CAVE_SLIMES = 62 + const val CAVE_BUGS = 63 + const val SHADES = 64 + const val CROCODILES = 65 + const val DARK_BEASTS = 66 + const val MOGRES = 67 + const val LIZARDS = 68 + const val HARPIE_BUG_SWARMS = 70 + const val SEA_SNAKES = 71 + const val SKELETAL_WYVERNS = 72 + const val KILLERWATTS = 73 + const val MUTATED_ZYGOMITES = 74 + const val ICEFIENDS = 75 + const val MINOTAURS = 76 + const val FLESH_CRAWLERS = 77 + const val CATABLEPON = 78 + const val ANKOU = 79 + const val CAVE_HORRORS = 80 + const val JUNGLE_HORRORS = 81 + const val GORAKS_REMOVE = 82 + const val SUQAHS = 83 + const val SCABARITES = 85 + const val TERROR_DOGS = 86 + const val MOLANISK = 87 + const val WATERFIENDS = 88 + const val SPIRITUAL_CREATURES = 89 + const val LIZARDMEN = 90 + const val MAGIC_AXES = 91 + const val CAVE_KRAKEN = 92 + const val MITHRIL_DRAGONS = 93 + const val AVIANSIE = 94 + const val SMOKE_DEVILS = 95 + const val TZHAAR = 96 + const val MAMMOTHS = 99 + const val ROGUES = 100 + const val ENTS = 101 + const val BANDITS = 102 + const val DARK_WARRIORS = 103 + const val LAVA_DRAGONS = 104 + const val FOSSIL_ISLAND_WYVERNS = 106 + const val REVENANTS = 107 + const val ADAMANT_DRAGONS = 108 + const val RUNE_DRAGONS = 109 + const val CHAOS_DRUIDS = 110 + const val WYRMS = 111 + const val DRAKES = 112 + const val HYDRAS = 113 + const val SAND_CRARBS = 118 + const val BLACK_KNIGHTS = 119 + const val PIRATES = 120 + const val SOURHOGS = 121 + const val WARPED_CREATURES = 122 + const val LESSER_NAGUA = 123 + } + } + + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/codegen/TableGenerater.kt b/cache/bin/main/org/alter/codegen/TableGenerater.kt new file mode 100644 index 00000000..f57cee21 --- /dev/null +++ b/cache/bin/main/org/alter/codegen/TableGenerater.kt @@ -0,0 +1,377 @@ +package org.alter.codegen + +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import dev.openrune.cache.gameval.GameValElement +import dev.openrune.cache.gameval.GameValHandler.elementAs +import dev.openrune.cache.gameval.impl.Table +import dev.openrune.definition.type.DBRowType +import dev.openrune.definition.util.BaseVarType +import dev.openrune.definition.util.VarType +import org.alter.ColInfo +import org.alter.game.util.DbException +import org.alter.game.util.DbHelper +import org.alter.game.util.DbQueryCache +import java.io.File + +data class TableColumn( + val name: String, + val simpleName: String, + val varTypes: Map? = null, + val optional: Boolean = false, + val maxValues : Int = 0 +) + +data class TableDef( + val tableName: String, + val className: String, + val columns: List +) + +fun startGeneration(elements: List, rows: MutableMap) { + elements.forEach { element -> + val table = element.elementAs() ?: return@forEach + val colInfoMap = table.columns.associate { it.name to ColInfo() }.toMutableMap() + + table(table.id, rows).forEach { row -> + table.columns.forEach { col -> + val info = colInfoMap[col.name]!! + try { + row.getColumn(col.id).types.forEachIndexed { index, type -> + info.types[index] = type + } + } catch (e: DbException.MissingColumn) { + info.optional = true + } + } + } + + val maxSizesByColumn: Map = + table.columns.associate { col -> + val maxSize = table(table.id, rows).maxOfOrNull { row -> + try { + row.getColumn(col.id).column.values?.size ?: 0 + } catch (e: DbException.MissingColumn) { + 0 + } + } ?: 0 + + col.name to maxSize + } + + val generatedColumns = table.columns.map { col -> + val info = colInfoMap[col.name]!! + TableColumn( + name = "columns.${table.name}:${col.name}", + simpleName = col.name, + varTypes = info.types, + optional = info.optional, + maxValues = maxSizesByColumn[col.name] ?: 0 + ) + } + + generateTable( + TableDef( + tableName = table.name, + className = formatClassName(table.name), + columns = generatedColumns + ), + File("../content/src/main/kotlin/org/") + ) + } +} + +private fun table(tableId: Int, rows: MutableMap): List { + return DbQueryCache.getTable(tableId.toString()) { + rows.asSequence() + .filter { it.value.tableId == tableId } + .map { DbHelper(it.value) } + .distinctBy { it.id } + .toList() + } +} + +private fun formatClassName(tableName: String): String { + return tableName + .split('_', '-', '.', ':') + .filter { it.isNotBlank() } + .joinToString("") { it.replaceFirstChar { c -> c.uppercase() } } + "Row" +} + +private const val BASE_PACKAGE = "org.generated.tables" +private const val VAR_TYPES_PACKAGE = "org.alter.game.util.vars" +private val PACKAGE_PREFIXES = listOf( + "fletching", "cluehelper", "fsw", "herblore", "woodcutting", "mining", + "fishing", "cooking", "smithing", "crafting", "runecrafting", + "agility", "thieving", "slayer", "construction", "hunter", + "farming", "prayer", "magic", "ranged", "melee", "combat", "sailing" +) + +private fun findMatchingPrefix(tableName: String): String? { + val lower = tableName.lowercase() + return PACKAGE_PREFIXES.sortedByDescending { it.length } + .firstOrNull { lower.startsWith(it) || lower.contains(it) } +} + +private fun toCamelCase(name: String): String { + val result = name.split("_").joinToString("") { + it.lowercase().replaceFirstChar { c -> c.uppercase() } + }.replaceFirstChar { it.lowercase() } + return if (result == "object") "objectID" else result +} + +private fun getKotlinType(varType: BaseVarType, optional: Boolean, isList: Boolean, isBooleanType: Boolean): TypeName { + val baseType = if (isBooleanType) BOOLEAN else when (varType) { + BaseVarType.INTEGER -> INT + BaseVarType.STRING -> STRING + BaseVarType.LONG -> LONG + else -> LONG + } + return if (isList) { + val elemType = if (optional) baseType.copy(nullable = true) else baseType + LIST.parameterizedBy(elemType) + } else { + if (optional) baseType.copy(nullable = true) else baseType + } +} + +private fun getVarTypeImplClass(varType: VarType): ClassName { + return ClassName(VAR_TYPES_PACKAGE, when (varType) { + VarType.BOOLEAN -> "BooleanType" + VarType.INT -> "IntType" + VarType.STRING -> "StringType" + VarType.LONG -> "LongType" + VarType.NPC -> "NpcVarType" + VarType.LOC -> "LocType" + VarType.OBJ -> "ObjType" + VarType.COORDGRID -> "CoordType" + VarType.MAPELEMENT -> "MapElementType" + VarType.DBROW -> "RowType" + VarType.NAMEDOBJ -> "NamedObjType" + VarType.GRAPHIC -> "GraphicType" + VarType.SEQ -> "SeqType" + VarType.MODEL -> "ModelType" + VarType.STAT -> "StatType" + VarType.CATEGORY -> "CategoryType" + VarType.COMPONENT -> "ComponentVarType" + VarType.INV -> "InvVarType" + VarType.IDKIT -> "IdkVarType" + VarType.ENUM -> "EnumVarType" + VarType.MIDI -> "MidiType" + VarType.VARP -> "VarpVarType" + VarType.STRUCT -> "StructType" + VarType.DBTABLE -> "TableVarType" + VarType.SYNTH -> "SynthType" + VarType.LOCSHAPE -> "LocShapeType" + else -> error("Unmapped Type: $varType") + }) +} + +private val tuplesToGen = mutableSetOf() + +fun generateTable(table: TableDef, outputDir: File) { + val dbHelper = ClassName("org.alter.game.util", "DbHelper") + val listType = ClassName("kotlin.collections", "List") + val tileType = ClassName("org.alter.game.model", "Tile") + val rscmType = ClassName("org.alter.rscm", "RSCMType") + val rscm = ClassName("org.alter.rscm", "RSCM") + + val packageName = findMatchingPrefix(table.tableName)?.let { "$BASE_PACKAGE.$it" } ?: BASE_PACKAGE + val rowClassName = ClassName(packageName, table.className) + val tableOutputDir = outputDir.parentFile + + val usedColumnFunctions = mutableSetOf() + var needsTileImport = false + val tuplesUsedInThisTable = mutableSetOf() + + val rowClassBuilder = TypeSpec.classBuilder(table.className) + .primaryConstructor(FunSpec.constructorBuilder().addParameter("row", dbHelper).build()) + + val fileBuilder = FileSpec.builder(packageName, table.className) + .addFileComment( + """ + |This file is AUTO-GENERATED. Do NOT edit manually. + |Generated for table: ${table.tableName} + """.trimMargin() + ) + .addImport("org.alter.game.util", "DbHelper") + .addImport("org.alter.rscm", "RSCM", "RSCMType") + .addImport("org.alter.rscm.RSCM", "asRSCM") + + table.columns.forEach { col -> + val sortedVarTypes = col.varTypes?.toSortedMap()?.values?.toList() ?: emptyList() + if (sortedVarTypes.isEmpty()) return@forEach + + val firstVarType = sortedVarTypes.first() + val isList = col.maxValues > 1 + + val isMixed = sortedVarTypes.toSet().size > 1 + val isCoordType = firstVarType == VarType.COORDGRID + val propertyName = toCamelCase(col.simpleName) + + val columnFunc = when { + isList -> if (col.optional) "multiColumnOptional" else "multiColumn" + else -> if (col.optional) "columnOptional" else "column" + } + usedColumnFunctions.add(columnFunc) + if (isMixed) usedColumnFunctions.add("multiColumnMixed") + + if (isMixed) { + val arity = sortedVarTypes.size + tuplesToGen.add(arity) + tuplesUsedInThisTable.add(arity) + val tupleClass = ClassName("org.generated", "Tuple$arity") + val typesArray = sortedVarTypes.map { + getKotlinType(it.baseType!!, col.optional, false, it == VarType.BOOLEAN) + }.toTypedArray() + val tupleType = tupleClass.parameterizedBy(*typesArray) + val nullableTupleType = if (col.optional) tupleType.copy(nullable = true) else tupleType + + val initializer = if (col.optional) { + CodeBlock.of( + "row.multiColumnMixed(%S" + sortedVarTypes.joinToString("") { ", %T" } + ").toTuple$arity()", + col.name, + *sortedVarTypes.map { getVarTypeImplClass(it) }.toTypedArray() + ) + } else { + CodeBlock.of( + "row.multiColumnMixed(%S" + sortedVarTypes.joinToString("") { ", %T" } + ").toTuple$arity() ?: error(\"Column ${'$'}{%S} returned empty list but is not optional\")", + col.name, + *sortedVarTypes.map { getVarTypeImplClass(it) }.toTypedArray(), + col.name + ) + } + + rowClassBuilder.addProperty( + PropertySpec.builder(propertyName, nullableTupleType) + .initializer(initializer) + .build() + ) + return@forEach + } + + val kotlinType = if (isCoordType && !isList) { + needsTileImport = true + if (col.optional) tileType.copy(nullable = true) else tileType + } else { + getKotlinType(firstVarType.baseType!!, col.optional, isList, firstVarType == VarType.BOOLEAN) + } + + val initializer = if (isCoordType && !isList) { + val columnFunc = if (col.optional) "columnOptional" else "column" + usedColumnFunctions.add(columnFunc) + if (col.optional) { + CodeBlock.of( + "row.$columnFunc(%S, %T)?.let { %T.from30BitHash(it) }", + col.name, + getVarTypeImplClass(firstVarType), + tileType + ) + } else { + CodeBlock.of("%T.from30BitHash(row.$columnFunc(%S, %T))", tileType, col.name, getVarTypeImplClass(firstVarType)) + } + } else { + val fmt = when { + isList && col.optional -> "row.multiColumnOptional(%S" + sortedVarTypes.joinToString("") { ", %T" } + ")" + isList -> "row.multiColumn(%S" + sortedVarTypes.joinToString("") { ", %T" } + ")" + col.optional -> "row.columnOptional(%S, %T)" + else -> "row.column(%S, %T)" + } + CodeBlock.of(fmt, col.name, *sortedVarTypes.map { getVarTypeImplClass(it) }.toTypedArray()) + } + + rowClassBuilder.addProperty( + PropertySpec.builder(propertyName, kotlinType) + .initializer(initializer) + .build() + ) + } + + val companion = TypeSpec.companionObjectBuilder() + .addFunction(FunSpec.builder("all") + .returns(listType.parameterizedBy(rowClassName)) + .addStatement("return %T.table(%S).map { %T(it) }", dbHelper, "tables.${table.tableName}", rowClassName) + .build()) + .addFunction(FunSpec.builder("getRow") + .addParameter("row", INT) + .returns(rowClassName) + .addStatement("return %T(%T.row(row))", rowClassName, dbHelper) + .build()) + .addFunction(FunSpec.builder("getRow") + .addParameter("column", String::class) + .returns(rowClassName) + .addStatement("%T.requireRSCM(%T.COLUMNS, column)", rscm, rscmType) + .addStatement("return getRow(column.asRSCM() and 0xFFFF)") + .build()) + .build() + + rowClassBuilder.addType(companion) + + val imports = mutableListOf("DbHelper") + imports.addAll(usedColumnFunctions.sorted()) + fileBuilder.addImport("org.alter.game.util", *imports.toTypedArray()) + if (needsTileImport) fileBuilder.addImport("org.alter.game.model", "Tile") + + tuplesUsedInThisTable.forEach { arity -> + fileBuilder.addImport("org.generated", "Tuple$arity") + fileBuilder.addImport("org.generated", "toTuple$arity") + } + + fileBuilder.addType(rowClassBuilder.build()) + fileBuilder.build().writeTo(tableOutputDir) + generateAllTuples(tableOutputDir, tuplesToGen) +} + +fun generateAllTuples(outputDir: File, tuplesToGen: MutableSet) { + val packageName = "org.generated" + val fileBuilder = FileSpec.builder(packageName, "Tuples") + .addFileComment( + """ + |WARNING: This file is AUTO-GENERATED. Do NOT edit manually. + |This file contains tuple classes and extension functions used by generated table classes. + """.trimMargin() + ) + + tuplesToGen.forEach { n -> + val typeParams = (0 until n).map { "T$it" } + val tupleClassName = ClassName(packageName, "Tuple$n") + + val tupleClassBuilder = TypeSpec.classBuilder("Tuple$n") + .addModifiers(KModifier.PUBLIC, KModifier.DATA) + .primaryConstructor( + FunSpec.constructorBuilder().apply { + typeParams.forEach { tp -> addParameter(tp.lowercase(), TypeVariableName(tp)) } + }.build() + ) + + typeParams.forEach { tp -> + val typeVar = TypeVariableName(tp) + tupleClassBuilder.addTypeVariable(typeVar) + tupleClassBuilder.addProperty( + PropertySpec.builder(tp.lowercase(), typeVar) + .initializer(tp.lowercase()) + .build() + ) + } + + fileBuilder.addType(tupleClassBuilder.build()) + + val extFun = FunSpec.builder("toTuple$n") + .receiver(ClassName("kotlin.collections", "List").parameterizedBy(STAR)) + .addTypeVariables(typeParams.map { TypeVariableName(it) }) + .returns(tupleClassName.parameterizedBy(typeParams.map { TypeVariableName(it) }).copy(nullable = true)) + .addCode(buildCodeBlock { + add("if (size < %L) return null\n", n) + add("return %T(", tupleClassName) + typeParams.forEachIndexed { index, tp -> + if (index > 0) add(", ") + add("this[%L] as %T", index, TypeVariableName(tp)) + } + add(")\n") + }) + + fileBuilder.addFunction(extFun.build()) + } + + fileBuilder.build().writeTo(outputDir) +} diff --git a/cache/bin/main/org/alter/game/util/DbHelper.kt b/cache/bin/main/org/alter/game/util/DbHelper.kt new file mode 100644 index 00000000..a3ae6006 --- /dev/null +++ b/cache/bin/main/org/alter/game/util/DbHelper.kt @@ -0,0 +1,269 @@ +package org.alter.game.util + +import dev.openrune.ServerCacheManager +import dev.openrune.definition.type.DBRowType +import dev.openrune.definition.type.DBColumnType +import dev.openrune.definition.util.VarType +import org.alter.game.util.vars.NumericBooleanVarType +import org.alter.game.util.vars.VarTypeImpl +import org.alter.rscm.RSCM.asRSCM +import org.alter.rscm.RSCM.requireRSCM +import org.alter.rscm.RSCMType +import java.util.concurrent.ConcurrentHashMap + +fun DbHelper.column(name: String, type: VarTypeImpl): V = + getNValue(name, type, 0) + +fun DbHelper.columnOptional(name: String, type: VarTypeImpl): V? = + getNValueOrNull(name, type, 0) + + +fun row(row: String) = DbHelper.row(row) + +/** + * Splits a single column into consecutive pairs. + * + * Example: + * column.values = [8, 35, 4, 30] + * returns [[8, 35], [4, 30]] + */ + +@Suppress("UNCHECKED_CAST") +fun DbHelper.multiColumn( + columnName: String, + vararg types: VarTypeImpl +): List { + val column = getColumn(columnName) + val values = column.column.values ?: return emptyList() + + require(types.isNotEmpty()) { "At least one VarTypeImpl must be provided" } + + return values.mapIndexed { i, raw -> + val type = + if (types.size == 1) types[0] + else types.getOrNull(i) + ?: throw ArrayIndexOutOfBoundsException( + "No VarTypeImpl supplied for index $i (types.size=${types.size})" + ) + + val value = column.get(i, type) + type.convertTo(value as K) + } +} + +fun DbHelper.multiColumnMixed(columnName: String, vararg types: VarTypeImpl<*, *>): List { + val column = try { + getColumn(columnName) + } catch (e: DbException.MissingColumn) { + return emptyList() + } catch (e: DbException) { + throw e + } catch (_: Exception) { + return emptyList() + } + + val values = column.column.values ?: return emptyList() + require(types.isNotEmpty()) { "At least one VarTypeImpl must be provided" } + + return values.mapIndexed { i, raw -> + val type = types[i] + val value = column.get(i, type as VarTypeImpl) + type.convertTo(value) + } +} + +@Suppress("UNCHECKED_CAST") +fun DbHelper.multiColumnOptional( + columnName: String, + vararg types: VarTypeImpl +): List { + val column = try { + getColumn(columnName) + } catch (e: DbException.MissingColumn) { + return emptyList() + } catch (e: DbException) { + throw e + } catch (_: Exception) { + return emptyList() + } + + val values = column.column.values ?: return emptyList() + require(types.isNotEmpty()) { "At least one VarTypeImpl must be provided" } + + return values.mapIndexed { i, raw -> + val type = if (types.size == 1) types[0] else types.getOrNull(i) + ?: return@mapIndexed null + + try { + val value = column.get(i, type) + type.convertTo(value as K) + } catch (e: DbException) { + throw e + } catch (_: Exception) { + null + } + } +} + +@Suppress("UNCHECKED_CAST") +class DbHelper(private val row: DBRowType) { + + val id: Int get() = row.id + val tableId: Int get() = row.tableId + + override fun toString(): String = + "DbHelper(id=$id, table=$tableId, columns=${row.columns.keys.joinToString()})" + + fun DbHelper.getNValueOrNull(name: String, type: VarTypeImpl, index: Int = 0): V? = + try { + val value = getColumn(name).get(index, type) + @Suppress("UNCHECKED_CAST") + if (type is NumericBooleanVarType) type.convertToAny(value) as V else type.convertTo(value as K) + } catch (_: Exception) { + null + } + + fun getNValue(name: String, type: VarTypeImpl, index: Int): V { + val value = getColumn(name).get(index, type) + @Suppress("UNCHECKED_CAST") + return if (type is NumericBooleanVarType) type.convertToAny(value) as V else type.convertTo(value as K) + } + + fun getColumn(name: String): Column { + requireRSCM(RSCMType.COLUMNS,name) + return getColumn(name.asRSCM() and 0xFFFF) + } + + fun getColumn(id: Int): Column { + val col = row.columns[id] ?: throw DbException.MissingColumn(tableId, id, id) + return Column(col, rowId = id, columnId = id, tableId = tableId) + } + + + class Column( + val column: DBColumnType, + private val rowId: Int, + val columnId: Int, + val tableId: Int + ) { + val types: Array get() = column.types + + val size: Int get() = column.values?.size ?: 0 + + fun get(index: Int = 0, type: VarTypeImpl): Any { + val values = column.values + ?: throw DbException.EmptyColumnValues(tableId, rowId, columnId) + + if (index !in values.indices) { + throw DbException.IndexOutOfRange(tableId, rowId, columnId, index, values.size) + } + + val actualType = types.getOrNull(index % types.size) + ?: throw DbException.MissingVarType(tableId, rowId, columnId, index) + + if (actualType != type.type) { + throw DbException.TypeMismatch( + tableId, rowId, columnId, expected = actualType, actual = type.type + ) + } + + return values[index] + } + + override fun toString(): String { + val vals = column.values?.joinToString(", ") ?: "empty" + return "Column(id=$columnId, row=$rowId, size=$size, values=[$vals])" + } + } + + companion object { + + fun table(table: String): List { + requireRSCM(RSCMType.TABLETYPES,table) + + return DbQueryCache.getTable(table) { + val tableId = table.asRSCM() + ServerCacheManager.getRows() + .asSequence() + .filter { it.value.tableId == tableId } + .map { DbHelper(it.value) } + .distinctBy { it.id } + .toList() + } + } + + fun dbFind(column: String, value: V, type: VarTypeImpl): List { + requireRSCM(RSCMType.COLUMNS,column) + + return DbQueryCache.getColumn(column, value, type) { + val tableName = "tables." + column.removePrefix("columns.").substringBefore(':') + val tableId = tableName.asRSCM() + val columnId = column.asRSCM() and 0xFFFF + + ServerCacheManager.getRows() + .asSequence() + .filter { it.value.tableId == tableId } + .filter { (_, row) -> + val col = row.columns[columnId] ?: return@filter false + val values = col.values ?: return@filter false + values.any { raw -> type.convertTo(raw as K) == value } + } + .map { (_, row) -> DbHelper(row) } + .distinctBy { it.id } + .toList() + } + } + + private fun load(rowId: Int): DbHelper = + ServerCacheManager.getDbrow(rowId)?.let(::DbHelper) + ?: throw DbException.MissingRow(rowId) + + fun row(ref: String): DbHelper = load(ref.asRSCM()) + fun row(rowId: Int): DbHelper = load(rowId) + } +} + +object DbQueryCache { + private val tableCache = ConcurrentHashMap>() + private val columnCache = ConcurrentHashMap>, List>() + + fun getTable(table: String, supplier: () -> List): List { + return tableCache.computeIfAbsent(table) { supplier() } + } + + fun getColumn(column: String, value: V, type: VarTypeImpl, supplier: () -> List): List { + val key = Triple(column, value as Any, type as VarTypeImpl<*, *>) + return columnCache.computeIfAbsent(key) { supplier() } + } + + fun clear() { + tableCache.clear() + columnCache.clear() + } +} + +sealed class DbException(message: String) : RuntimeException(message) { + + class MissingColumn(tableId: Int, rowId: Int, columnId: Int) : + DbException("Column $columnId not found in row $rowId (table $tableId)") + + class EmptyColumnValues( + tableId: Int, + rowId: Int, + columnId: Int + ) : DbException("No values found in column $columnId (row $rowId, table $tableId)") + + class IndexOutOfRange(tableId: Int, rowId: Int, columnId: Int, index: Int, max: Int) : + DbException("Index $index out of bounds (size=$max) in column $columnId (row $rowId, table $tableId)") + + class MissingVarType(tableId: Int, rowId: Int, columnId: Int, index: Int) : + DbException("No VarType available at index $index in column $columnId (row $rowId, table $tableId)") + + class TypeMismatch(tableId: Int, rowId: Int, columnId: Int, expected: VarType, actual: VarType) : + DbException("Type mismatch in table $tableId, row $rowId, column $columnId: expected $expected but found $actual") + + class MissingRow(rowId: Int) : + DbException("DBRow $rowId not found") + + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/game/util/EnumManager.kt b/cache/bin/main/org/alter/game/util/EnumManager.kt new file mode 100644 index 00000000..366c3f0f --- /dev/null +++ b/cache/bin/main/org/alter/game/util/EnumManager.kt @@ -0,0 +1,150 @@ +package org.alter.game.util + +import dev.openrune.ServerCacheManager +import dev.openrune.definition.type.EnumType +import dev.openrune.definition.util.BaseVarType +import dev.openrune.definition.util.VarType +import io.github.oshai.kotlinlogging.KotlinLogging +import org.alter.game.util.vars.VarTypeImpl +import org.alter.rscm.RSCM.asRSCM +import org.alter.rscm.RSCM.requireRSCM +import org.alter.rscm.RSCMType + +private val logger = KotlinLogging.logger {} + +/** + * Represents a typed key/value pair from an enum. + */ +data class EnumPair(val key: K, val value: V) + +/** + * Top-level helper to retrieve a type-safe list of enum entries by name. + * + * Example: + * ``` + * val entries = enum("enums.bone_data", ObjType, RowType) + * entries.forEach { (key, value) -> + * println("Key: $key, Value: $value") + * } + * ``` + */ +fun enum( + enumName: String, + keyType: VarTypeImpl, + valueType: VarTypeImpl +): List> = + EnumHelper.of(enumName).getEnum(keyType, valueType) + +fun enum( + enumName: Int, + keyType: VarTypeImpl, + valueType: VarTypeImpl +): List> = + EnumHelper.of(enumName).getEnum(keyType, valueType) + + + +fun enumKey( + enumName: String, + key: K, + keyType: VarTypeImpl<*, K>, + valueType: VarTypeImpl<*, V> +): V? = enum(enumName, keyType, valueType).firstOrNull { it.key == key }?.value + +/** + * Type-safe helper for working with an EnumType. + * + * Example: + * ``` + * val helper = EnumHelper.of("enums.bone_data") + * val entries = helper.getEnum(ObjType, RowType) + * val value = helper.getByKey("DRAGON_BONE", ObjType, RowType) + * val key = helper.getByValue(35, ObjType, RowType) + * ``` + */ +@Suppress("UNCHECKED_CAST") +class EnumHelper private constructor(val enum: EnumType) { + + val id: Int get() = enum.id + val keyType: VarType get() = enum.keyType + val valueType: VarType get() = enum.valueType + + override fun toString(): String = + "EnumHelper(id=$id, size=${enum.values.size}, keys=${enum.values.keys.joinToString()})" + + /** + * Returns a type-safe list of key/value pairs from the enum. + * + * Example: + * ``` + * val entries = helper.getEnum(ObjType, RowType) + * entries.forEach { (key, value) -> + * println("Key: $key, Value: $value") + * } + * ``` + */ + fun getEnum( + keyVarType: VarTypeImpl, + valueVarType: VarTypeImpl + ): List> { + val entries = enum.values.toList() + if (entries.isEmpty()) return emptyList() + + return entries.mapIndexed { index, (rawKey, rawValue) -> + val actualKeyType = enum.keyType + val actualValueType = enum.valueType + + if (actualKeyType != keyVarType.type) { + throw IllegalArgumentException( + "Type mismatch for key at index $index: expected '${keyVarType.type}', found '$actualKeyType'" + ) + } + + if (actualValueType != valueVarType.type) { + throw IllegalArgumentException( + "Type mismatch for value at index $index: expected '${valueVarType.type}', found '$actualValueType'" + ) + } + + val key: K1 = if (keyVarType.type.baseType == BaseVarType.STRING) { + rawKey as K1 + } else { + (rawKey.toIntOrNull() as K1?) + ?: throw IllegalArgumentException("Cannot convert key '$rawKey' to Int") + } + + val value: K2 = if (valueVarType.type.baseType == BaseVarType.STRING) { + rawValue as K2 + } else { + (rawValue.toString().toIntOrNull() as K2?) + ?: throw IllegalArgumentException("Cannot convert value '$rawValue' to Int") + } + + EnumPair(keyVarType.convertTo(key), valueVarType.convertTo(value)) + } + } + + companion object { + /** + * Load an EnumType from the server cache by name. + */ + + fun load(enum: Int): EnumType { + return ServerCacheManager.getEnum(enum) + ?: throw NoSuchElementException("Enum '$enum' not found") + } + + fun load(name: String): EnumType { + requireRSCM(RSCMType.ENUMS,name) + return ServerCacheManager.getEnum(name.asRSCM()) + ?: throw NoSuchElementException("Enum '$name' not found") + } + + /** + * Creates an [EnumHelper] from an enum name. + */ + fun of(name: String): EnumHelper = EnumHelper(load(name)) + + fun of(name: Int): EnumHelper = EnumHelper(load(name)) + } +} diff --git a/cache/bin/main/org/alter/game/util/vars/GenericStringVarType.kt b/cache/bin/main/org/alter/game/util/vars/GenericStringVarType.kt new file mode 100644 index 00000000..8c1c7ea3 --- /dev/null +++ b/cache/bin/main/org/alter/game/util/vars/GenericStringVarType.kt @@ -0,0 +1,19 @@ +package org.alter.game.util.vars + +import dev.openrune.definition.util.BaseVarType +import dev.openrune.definition.util.VarType + +abstract class GenericStringVarType( + protected val varType: VarType +) : StringVarType { + override val type: VarType get() = varType + override val char: Char get() = varType.ch + override val id: Int get() = varType.id + + override fun convertTo(variable: String) = variable + override fun convertFrom(variable: String) = variable +} + +internal interface StringVarType : VarTypeImpl { + override val baseVarType: BaseVarType get() = BaseVarType.STRING +} diff --git a/cache/bin/main/org/alter/game/util/vars/NumericBooleanVarType.kt b/cache/bin/main/org/alter/game/util/vars/NumericBooleanVarType.kt new file mode 100644 index 00000000..bf61abc7 --- /dev/null +++ b/cache/bin/main/org/alter/game/util/vars/NumericBooleanVarType.kt @@ -0,0 +1,24 @@ +package org.alter.game.util.vars + +import dev.openrune.definition.util.BaseVarType +import dev.openrune.definition.util.VarType + +abstract class NumericBooleanVarType( + protected val varType: VarType +) : IntegerVarType { + + override val type: VarType get() = varType + override val baseVarType: BaseVarType get() = BaseVarType.INTEGER + override val char: Char get() = varType.ch + override val id: Int get() = varType.id + + override fun convertTo(variable: Int): Boolean = variable != 0 + + fun convertToAny(variable: Any): Boolean = when (variable) { + is Number -> variable.toInt() != 0 + is String -> variable.toIntOrNull()?.let { it != 0 } ?: false + else -> false + } + + override fun convertFrom(variable: Boolean): Int = if (variable) 1 else 0 +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/game/util/vars/NumericIntegerVarType.kt b/cache/bin/main/org/alter/game/util/vars/NumericIntegerVarType.kt new file mode 100644 index 00000000..cf278b5a --- /dev/null +++ b/cache/bin/main/org/alter/game/util/vars/NumericIntegerVarType.kt @@ -0,0 +1,21 @@ +package org.alter.game.util.vars + +import dev.openrune.definition.util.BaseVarType +import dev.openrune.definition.util.VarType + +abstract class NumericIntegerVarType( + protected val varType: VarType +) : IntegerVarType { + override val type: VarType get() = varType + override val char: Char get() = varType.ch + override val id: Int get() = varType.id + + + + override fun convertTo(variable: Int) = variable + override fun convertFrom(variable: Int) = variable +} + +internal interface IntegerVarType : VarTypeImpl { + override val baseVarType: BaseVarType get() = BaseVarType.INTEGER +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/game/util/vars/NumericLongVarType.kt b/cache/bin/main/org/alter/game/util/vars/NumericLongVarType.kt new file mode 100644 index 00000000..f308b0c5 --- /dev/null +++ b/cache/bin/main/org/alter/game/util/vars/NumericLongVarType.kt @@ -0,0 +1,21 @@ +package org.alter.game.util.vars + +import dev.openrune.definition.util.BaseVarType +import dev.openrune.definition.util.VarType + +abstract class NumericLongVarType( + protected val varType: VarType +) : LongVarType { + + override val type: VarType get() = varType + override val char: Char get() = varType.ch + override val id: Int get() = varType.id + override val baseVarType: BaseVarType get() = BaseVarType.LONG + + override fun convertTo(variable: Long) = variable + override fun convertFrom(variable: Long) = variable +} + +internal interface LongVarType : VarTypeImpl { + override val baseVarType: BaseVarType get() = BaseVarType.LONG +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/game/util/vars/VarTypeImpl.kt b/cache/bin/main/org/alter/game/util/vars/VarTypeImpl.kt new file mode 100644 index 00000000..2a73dc73 --- /dev/null +++ b/cache/bin/main/org/alter/game/util/vars/VarTypeImpl.kt @@ -0,0 +1,15 @@ +package org.alter.game.util.vars + +import dev.openrune.definition.util.BaseVarType +import dev.openrune.definition.util.VarType + +interface VarTypeImpl { + val char: Char + val id: Int + val type: VarType + val baseVarType: BaseVarType? + get() = type.baseType + + fun convertTo(variable: K): V + fun convertFrom(variable: V): K +} diff --git a/cache/bin/main/org/alter/game/util/vars/VarTypes.kt b/cache/bin/main/org/alter/game/util/vars/VarTypes.kt new file mode 100644 index 00000000..73d407f7 --- /dev/null +++ b/cache/bin/main/org/alter/game/util/vars/VarTypes.kt @@ -0,0 +1,32 @@ +package org.alter.game.util.vars + +import dev.openrune.definition.util.VarType + +data object IntType : NumericIntegerVarType(VarType.INT) +data object LocShapeType : NumericIntegerVarType(VarType.LOCSHAPE) +data object SynthType : NumericIntegerVarType(VarType.SYNTH) +data object BooleanType : NumericBooleanVarType(VarType.BOOLEAN) +data object StringType : GenericStringVarType(VarType.STRING) +data object MapElementType : NumericIntegerVarType(VarType.MAPELEMENT) +data object LongType : NumericLongVarType(VarType.LONG) +data object ComponentVarType : NumericIntegerVarType(VarType.COMPONENT) +data object ObjType : NumericIntegerVarType(VarType.OBJ) +data object NpcVarType : NumericIntegerVarType(VarType.NPC) +data object LocType : NumericIntegerVarType(VarType.LOC) +data object EnumVarType : NumericIntegerVarType(VarType.ENUM) +data object RowType : NumericIntegerVarType(VarType.DBROW) +data object TableVarType : NumericIntegerVarType(VarType.DBTABLE) +data object SeqType : NumericIntegerVarType(VarType.SEQ) +data object GraphicType : NumericIntegerVarType(VarType.GRAPHIC) +data object HeadBarType : NumericIntegerVarType(VarType.HEADBAR) +data object HitMarkType : NumericIntegerVarType(VarType.HITMARK) +data object CoordType : NumericIntegerVarType(VarType.COORDGRID) +data object StatType : NumericIntegerVarType(VarType.STAT) +data object CategoryType : NumericIntegerVarType(VarType.CATEGORY) +data object StructType : NumericIntegerVarType(VarType.STRUCT) +data object NamedObjType : NumericIntegerVarType(VarType.NAMEDOBJ) +data object IdkVarType : NumericIntegerVarType(VarType.IDKIT) +data object ModelType : NumericIntegerVarType(VarType.MODEL) +data object VarpVarType : NumericIntegerVarType(VarType.VARP) +data object MidiType : NumericIntegerVarType(VarType.MIDI) +data object InvVarType : NumericIntegerVarType(VarType.INV) \ No newline at end of file diff --git a/cache/bin/main/org/alter/gamevals/GameValAutoAssigner.kt b/cache/bin/main/org/alter/gamevals/GameValAutoAssigner.kt new file mode 100644 index 00000000..66f6743c --- /dev/null +++ b/cache/bin/main/org/alter/gamevals/GameValAutoAssigner.kt @@ -0,0 +1,143 @@ +package org.alter.gamevals + +import org.alter.rscm.RSCMType +import java.io.File + +/** + * Scans TOML and RSCM files for gamevals = -1, assigns next available IDs (preferring gaps near + * existing IDs in the same file), and rewrites the files. + */ +class GameValAutoAssigner( + private val mappings: Map>, + private val maxBaseID: Map, +) { + fun run(contentDir: File?, gamevalsDir: File?) { + val scanResult = scanForUnassigned(contentDir, gamevalsDir) + if (scanResult.unassigned.isEmpty()) return + + val replacements = assignIds(scanResult) + rewriteFiles(replacements) + } + + private data class ScanResult( + val usedIds: Map>, + val idsInSameFile: Map, MutableSet>, + val unassigned: List, + ) + + private data class UnassignedEntry( + val file: File, + val lineIndex: Int, + val line: String, + val table: String, + val key: String, + ) + + private fun scanForUnassigned(contentDir: File?, gamevalsDir: File?): ScanResult { + val usedIds = mutableMapOf>() + val idsInSameFile = mutableMapOf, MutableSet>() + val unassigned = mutableListOf() + + fun recordEntry(file: File, lineIndex: Int, line: String, table: String, key: String, value: Int) { + val tableUsed = usedIds.getOrPut(table) { + mutableSetOf().also { mappings[table]?.values?.forEach(it::add) } + } + if (value == -1) { + idsInSameFile.getOrPut(file to table) { mutableSetOf() } + unassigned.add(UnassignedEntry(file, lineIndex, line, table, key)) + } else { + tableUsed.add(value) + idsInSameFile.getOrPut(file to table) { mutableSetOf() }.add(value) + } + } + + contentDir?.walk()?.filter { it.isFile && it.name == "gamevals.toml" }?.forEach { file -> + var currentTable: String? = null + file.readLines().forEachIndexed { index, line -> + GAMEVALS_SECTION_REGEX.find(line.trim())?.let { + currentTable = it.groupValues[1].takeIf { t -> t in RSCMType.RSCM_PREFIXES } + } + if (currentTable != null && line.contains("=")) { + parseKeyValue(line)?.let { (key, value) -> + recordEntry(file, index, line, currentTable!!, key, value) + } + } + } + } + + gamevalsDir?.walk()?.filter { it.isFile }?.forEach { file -> + val table = file.nameWithoutExtension.takeIf { it in RSCMType.RSCM_PREFIXES } ?: return@forEach + file.readLines().forEachIndexed { index, line -> + if (line.isNotBlank()) { + parseKeyValue(line)?.let { (key, value) -> + recordEntry(file, index, line, table, key, value) + } + } + } + } + + return ScanResult(usedIds, idsInSameFile, unassigned) + } + + private fun parseKeyValue(line: String): Pair? { + val trimmed = line.trim() + if (trimmed.isEmpty() || trimmed.startsWith("#") || trimmed.startsWith("[")) return null + return when { + KEY_VALUE_REGEX.matches(trimmed) -> { + val m = KEY_VALUE_REGEX.matchEntire(trimmed)!! + m.groupValues[1].trim() to m.groupValues[2].toInt() + } + KEY_SUBPROP_REGEX.matches(trimmed) -> { + val m = KEY_SUBPROP_REGEX.matchEntire(trimmed)!! + m.groupValues[1].trim() to m.groupValues[3].toInt() + } + else -> null + } + } + + private fun assignIds(scanResult: ScanResult): Map>> { + val (usedIds, idsInSameFile, unassigned) = scanResult + val unassignedCount = unassigned.groupingBy { it.file to it.table }.eachCount() + val replacements = mutableMapOf>>() + + for (entry in unassigned.sortedWith(compareBy({ it.file.absolutePath }, { it.lineIndex }))) { + val tableUsed = usedIds[entry.table]!! + val floor = maxOf(MIN_ID, (maxBaseID[entry.table] ?: -1) + 1) + val sameFileIds = idsInSameFile[entry.file to entry.table].orEmpty().filter { it >= floor } + + val id = when { + sameFileIds.isNotEmpty() -> { + val (minInFile, maxInFile) = sameFileIds.minOrNull()!! to sameFileIds.maxOrNull()!! + val count = unassignedCount[entry.file to entry.table] ?: 1 + val gapLo = maxOf(floor, minInFile - count) + (gapLo..minInFile - 1).firstOrNull { it !in tableUsed } + ?: (maxInFile + 1..Int.MAX_VALUE).firstOrNull { it !in tableUsed } + ?: (floor..Int.MAX_VALUE).first { it !in tableUsed } + } + else -> (floor..Int.MAX_VALUE).first { it !in tableUsed } + } + tableUsed.add(id) + replacements.getOrPut(entry.file) { mutableListOf() } + .add(entry.lineIndex to entry.line.replaceFirst(NEGATIVE_ONE_REGEX, "= $id")) + } + return replacements + } + + private fun rewriteFiles(replacements: Map>>) { + for ((file, lineReplacements) in replacements) { + val lines = file.readLines().toMutableList() + lineReplacements.forEach { (lineIndex, newLine) -> + if (lineIndex < lines.size) lines[lineIndex] = newLine + } + file.writeText(lines.joinToString("\n")) + } + } + + private companion object { + const val MIN_ID = 65536 // 64k floor + val KEY_VALUE_REGEX = Regex("^([^=]+)=\\s*(-?\\d+)\\s*$") + val KEY_SUBPROP_REGEX = Regex("^([^:]+):([^=]+)=\\s*(-?\\d+)\\s*$") + val GAMEVALS_SECTION_REGEX = Regex("^\\s*\\[gamevals\\.([^.\\]]+)\\]\\s*$") + val NEGATIVE_ONE_REGEX = Regex("=\\s*-1\\s*$") + } +} diff --git a/cache/bin/main/org/alter/gamevals/GameValProvider.kt b/cache/bin/main/org/alter/gamevals/GameValProvider.kt new file mode 100644 index 00000000..ebd1f0bc --- /dev/null +++ b/cache/bin/main/org/alter/gamevals/GameValProvider.kt @@ -0,0 +1,188 @@ +package org.alter.gamevals + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.toml.TomlFactory +import dev.openrune.definition.constants.MappingProvider +import dev.openrune.definition.constants.use +import org.alter.rscm.RSCMType +import java.io.DataInputStream +import java.io.File +import java.io.FileInputStream +import java.nio.file.Paths +import kotlin.io.use + +class GameValProvider : MappingProvider { + + private val tomlMapper = ObjectMapper(TomlFactory()).findAndRegisterModules() + override val mappings: MutableMap> = mutableMapOf() + val maxBaseID: MutableMap = mutableMapOf() + + /** When true, scans for -1 gamevals and auto-assigns non-conflicting IDs before loading. */ + var autoAssignIds: Boolean = false + + companion object { + fun load(rootDir: String = "../", autoAssignIds: Boolean = false) { + val provider = GameValProvider() + provider.autoAssignIds = autoAssignIds + provider.use( + Paths.get("${rootDir}data", "cfg", "gamevals-binary", "gamevals.dat").toFile(), + Paths.get("${rootDir}data", "cfg", "gamevals-binary", "gamevals_columns.dat").toFile(), + Paths.get("${rootDir}content", "src", "main", "resources", "org", "alter").toFile(), + Paths.get("${rootDir}data", "cfg", "gamevals").toFile() + ) + } + } + + override fun load(vararg files: File) { + require(files.size >= 2) { "Expected at least two files for loading: gamevals.dat and gamevals_columns.dat" } + + decodeGameValDat(files[0]) + decodeGameValDat(files[1]) + + if (autoAssignIds) { + val contentDir = files.getOrNull(2)?.takeIf { it.exists() } + val gamevalsDir = files.getOrNull(3)?.takeIf { it.exists() } + if (contentDir != null || gamevalsDir != null) { + GameValAutoAssigner(mappings, maxBaseID).run(contentDir, gamevalsDir) + } + } + + // Load TOML + files.getOrNull(2)?.takeIf { it.exists() }?.walk() + ?.filter { it.isFile && it.name == "gamevals.toml" } + ?.forEach { processGameValToml(it) } + + // Load RSCM directories + files.drop(3).forEach { dir -> + require(dir.isDirectory) { "Expected a directory for RSCM mappings, got file: ${dir.absolutePath}" } + dir.walk().filter { it.isFile }.forEach { processRSCMFile(it) } + } + } + + private fun processGameValToml(file: File) { + val root: Map = tomlMapper.readValue(file, object : TypeReference>() {}) + val gamevalsSection = root["gamevals"] as? Map<*, *> ?: return + + gamevalsSection.forEach { (tableNameAny, tableValuesAny) -> + val tableName = tableNameAny as? String ?: return@forEach + val tableValues = tableValuesAny as? Map<*, *> ?: return@forEach + + require(tableName in RSCMType.RSCM_PREFIXES) { + "Invalid TOML table '$tableName' in ${file.name}. Expected one of: ${RSCMType.RSCM_PREFIXES}" + } + + mappings.putIfAbsent(tableName, mutableMapOf()) + + tableValues.forEach { (k, v) -> + val key = k.toString() + val value = when (v) { + is Number -> v.toInt() + is String -> v.toIntOrNull() ?: return@forEach + else -> return@forEach + } + + val (parsedKey, parsedValue) = parseRSCMV2Line("$key=$value", 0) + putMapping(tableName, parsedKey, parsedValue, file.name) + } + } + } + + private fun processRSCMFile(file: File) { + val table = file.nameWithoutExtension + val lines = file.readLines().filter { it.isNotBlank() } + if (lines.isEmpty()) return + + mappings.putIfAbsent(table, mutableMapOf()) + + lines.forEachIndexed { lineNumber, line -> + try { + val (key, value) = parseRSCMV2Line(line, lineNumber + 1) + putMapping(table, key, value, file.name) + } catch (e: Exception) { + throw IllegalArgumentException("Failed to parse line ${lineNumber + 1} in ${file.name}: '$line'", e) + } + } + } + + private fun putMapping(table: String, key: String, value: Int, file: String) { + val tableMappings = mappings[table] + ?: throw IllegalArgumentException("Table '$table' does not exist in mappings.") + + val qualifiedKey = "$table.$key" + + val maxID = maxBaseID[table] ?: -1 + require(value > maxID) { + "Custom value '$value' for key '$key' in table '$table must exceed the current max base ID $maxID. " + + "Cannot override existing osrs IDs." + } + + if (tableMappings.containsKey(qualifiedKey)) { + throw IllegalArgumentException( + "Mapping conflict in table '$table: key '$key' already exists. Keys must be unique." + ) + } + + tableMappings.entries.find { it.value == value }?.let { existing -> + throw IllegalArgumentException( + "Mapping conflict in table '$table: value '$value' is already mapped to key '${existing.key}'. Values must be unique." + ) + } + + tableMappings[qualifiedKey] = value + } + + private fun parseRSCMV2Line(line: String, lineNumber: Int): Pair = when { + line.contains("=") -> { + val parts = line.split("=") + require(parts.size == 2) { "Invalid line format at $lineNumber: '$line'. Expected 'key=value'" } + parts[0].trim() to parts[1].trim().toInt() + } + line.contains(":") -> { + val parts = line.split(":") + require(parts.size == 2) { "Invalid sub-property format at $lineNumber: '$line'. Expected 'key:subprop=value'" } + val key = parts[0].trim() + val valueParts = parts[1].trim().split("=") + require(valueParts.size == 2) { "Invalid sub-property value format at $lineNumber: '${parts[1]}'" } + key to valueParts[1].trim().toInt() + } + else -> throw IllegalArgumentException( + "Invalid line format at $lineNumber: '$line'. Expected 'key=value' or 'key:subprop=value'" + ) + } + + private fun decodeGameValDat(datFile: File) { + DataInputStream(FileInputStream(datFile)).use { input -> + val tableCount = input.readInt() + repeat(tableCount) { + val nameLength = input.readShort().toInt() + val nameBytes = ByteArray(nameLength) + input.readFully(nameBytes) + val tableName = String(nameBytes, Charsets.UTF_8) + + val itemCount = input.readInt() + mappings.putIfAbsent(tableName, mutableMapOf()) + + repeat(itemCount) { + val itemLength = input.readShort().toInt() + val itemBytes = ByteArray(itemLength) + input.readFully(itemBytes) + val itemString = String(itemBytes, Charsets.UTF_8) + + try { + val (key, value) = parseRSCMV2Line(itemString, 0) + mappings[tableName]?.putIfAbsent("$tableName.$key", value) + } catch (e: Exception) { + throw IllegalArgumentException( + "Failed to parse item in table '$tableName' from ${datFile.name}: '$itemString'", e + ) + } + } + + maxBaseID[tableName] = mappings[tableName]?.values?.maxOrNull() ?: -1 + } + } + } + + override fun getSupportedExtensions(): List = listOf(".rscm", ".rscm2") +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/gamevals/GamevalDumper.kt b/cache/bin/main/org/alter/gamevals/GamevalDumper.kt new file mode 100644 index 00000000..4aadd6b7 --- /dev/null +++ b/cache/bin/main/org/alter/gamevals/GamevalDumper.kt @@ -0,0 +1,92 @@ +package org.alter.gamevals + +import dev.openrune.cache.gameval.GameValHandler +import dev.openrune.cache.gameval.GameValHandler.elementAs +import dev.openrune.cache.gameval.impl.Interface +import dev.openrune.cache.gameval.impl.Sprite +import dev.openrune.cache.gameval.impl.Table +import dev.openrune.definition.GameValGroupTypes +import dev.openrune.filesystem.Cache +import java.io.DataOutputStream +import java.io.File +import java.io.FileOutputStream + +object GamevalDumper { + + fun dumpGamevals(cache: Cache, rev: Int) { + val gamevals = mutableMapOf>() + + GameValGroupTypes.entries.forEach { groupType -> + val elements = GameValHandler.readGameVal(groupType, cache = cache, rev) + + when (groupType) { + GameValGroupTypes.SPRITETYPES -> { + val sprites = elements.mapNotNull { it.elementAs()?.let { e -> + if (e.index == -1) "${e.name}=${e.id}" else "${e.name}:${e.index}=${e.id}" + } } + gamevals["sprites"] = sprites + } + + GameValGroupTypes.IFTYPES_V2 -> { + val interfaces = elements.mapNotNull { it.elementAs()?.let { iface -> "${iface.name}=${iface.id}" } } + + val components = elements.flatMap { elem -> + elem.elementAs()?.components?.map { comp -> + "${elem.elementAs()?.name}:${comp.name}=${comp.packed}" + } ?: emptyList() + } + + gamevals["interfaces"] = interfaces + gamevals["components"] = components + } + + GameValGroupTypes.IFTYPES -> Unit + + else -> { + val key = groupType.groupName.replace("dbtables", "tables") + gamevals[key] = elements.map { "${it.name}=${it.id}" } + } + } + } + + if (!File("../data/cfg/gamevals-binary/").exists()) { + File("../data/cfg/gamevals-binary/").mkdirs() + } + + encodeGameValDat("../data/cfg/gamevals-binary/gamevals.dat", gamevals) + dumpCols(cache, rev) + } + + fun dumpCols(cache: Cache, rev: Int) { + val elements = GameValHandler.readGameVal(GameValGroupTypes.TABLETYPES, cache = cache, rev) + val data = mutableListOf() + + elements.forEach { gameValElement -> + val table = gameValElement.elementAs
() ?: return@forEach + table.columns.forEach { column -> + data.add("${table.name}:${column.name}=${(gameValElement.id shl 16) or column.id}") + } + } + + encodeGameValDat("../data/cfg/gamevals-binary/gamevals_columns.dat", mapOf("columns" to data)) + } + + private fun encodeGameValDat(output: String, tables: Map>) { + DataOutputStream(FileOutputStream(output)).use { out -> + out.writeInt(tables.size) + + tables.forEach { (name, items) -> + val nameBytes = name.toByteArray(Charsets.UTF_8) + out.writeShort(nameBytes.size) + out.write(nameBytes) + + out.writeInt(items.size) + items.forEach { entry -> + val bytes = entry.toByteArray(Charsets.UTF_8) + out.writeShort(bytes.size) + out.write(bytes) + } + } + } + } +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/GameframeTable.kt b/cache/bin/main/org/alter/impl/GameframeTable.kt new file mode 100644 index 00000000..ddf17baa --- /dev/null +++ b/cache/bin/main/org/alter/impl/GameframeTable.kt @@ -0,0 +1,69 @@ +package org.alter.impl + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object GameframeTable { + + const val COL_TOP_LEVEL = 0 + const val COL_MAPPINGS = 1 + const val COL_CLIENT_MODE = 2 + const val COL_RESIZABLE = 3 + const val COL_IS_DEFAULT = 4 + const val COL_STONE_ARRANGEMENT = 5 + + fun gameframe() = dbTable("tables.gameframe", serverOnly = true) { + + + column("toplevel", COL_TOP_LEVEL, VarType.INT) + column("mappings", COL_MAPPINGS, VarType.ENUM) + column("client_mode", COL_CLIENT_MODE, VarType.INT) + column("resizable", COL_RESIZABLE, VarType.BOOLEAN) + column("default", COL_IS_DEFAULT, VarType.BOOLEAN) + column("stone_arrangement", COL_STONE_ARRANGEMENT, VarType.BOOLEAN) + + row("dbrows.gameframe_toplevel") { + columnRSCM(COL_TOP_LEVEL,"interfaces.toplevel") + columnRSCM(COL_MAPPINGS,"enums.fixed_pane_redirect") + column(COL_CLIENT_MODE,0) + + column(COL_RESIZABLE,false) + column(COL_IS_DEFAULT,true) + column(COL_STONE_ARRANGEMENT,false) + } + + row("dbrows.gameframe_osrs_stretch") { + columnRSCM(COL_TOP_LEVEL,"interfaces.toplevel_osrs_stretch") + columnRSCM(COL_MAPPINGS,"enums.resizable_basic_pane_redirect") + column(COL_CLIENT_MODE,1) + + column(COL_RESIZABLE,true) + column(COL_IS_DEFAULT,false) + column(COL_STONE_ARRANGEMENT,false) + } + + row("dbrows.gameframe_pre_eoc") { + columnRSCM(COL_TOP_LEVEL,"interfaces.toplevel_pre_eoc") + columnRSCM(COL_MAPPINGS,"enums.side_panels_resizable_pane_redirect") + column(COL_CLIENT_MODE,2) + + column(COL_RESIZABLE,true) + column(COL_IS_DEFAULT,false) + column(COL_STONE_ARRANGEMENT,true) + } + + row("dbrows.gameframe_fullscreen") { + columnRSCM(COL_TOP_LEVEL,"interfaces.toplevel_display") + columnRSCM(COL_MAPPINGS,"enums.fullscreen_pane") + column(COL_CLIENT_MODE,2) + + column(COL_RESIZABLE,true) + column(COL_IS_DEFAULT,false) + column(COL_STONE_ARRANGEMENT,true) + } + + + + } + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/StatComponents.kt b/cache/bin/main/org/alter/impl/StatComponents.kt new file mode 100644 index 00000000..66e2de24 --- /dev/null +++ b/cache/bin/main/org/alter/impl/StatComponents.kt @@ -0,0 +1,60 @@ +package org.alter.impl + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +data class StatRow( + val rowName: String, + val componentId: String, + val statString: String, + val bit: Int +) + +object StatComponents { + + const val COL_COMPONENT = 0 + const val COL_STAT = 1 + const val COL_BIT = 2 + + fun statsComponents() = dbTable("tables.stat_components", serverOnly = true) { + + column("component", COL_COMPONENT, VarType.COMPONENT) + column("stat", COL_STAT, VarType.STAT) + column("bit", COL_BIT, VarType.INT) + + val skillsWithBits = listOf( + StatRow("dbrows.agility_stat", "components.stats:agility", "stats.agility", 8), + StatRow("dbrows.attack_stat", "components.stats:attack", "stats.attack", 1), + StatRow("dbrows.construction_stat", "components.stats:construction", "stats.construction", 22), + StatRow("dbrows.cooking_stat", "components.stats:cooking", "stats.cooking", 16), + StatRow("dbrows.crafting_stat", "components.stats:crafting", "stats.crafting", 11), + StatRow("dbrows.defence_stat", "components.stats:defence", "stats.defence", 5), + StatRow("dbrows.farming_stat", "components.stats:farming", "stats.farming", 21), + StatRow("dbrows.firemaking_stat", "components.stats:firemaking", "stats.firemaking", 17), + StatRow("dbrows.fishing_stat", "components.stats:fishing", "stats.fishing", 15), + StatRow("dbrows.fletching_stat", "components.stats:fletching", "stats.fletching", 19), + StatRow("dbrows.herblore_stat", "components.stats:herblore", "stats.herblore", 9), + StatRow("dbrows.hitpoints_stat", "components.stats:hitpoints", "stats.hitpoints", 6), + StatRow("dbrows.hunter_stat", "components.stats:hunter", "stats.hunter", 23), + StatRow("dbrows.magic_stat", "components.stats:magic", "stats.magic", 4), + StatRow("dbrows.mining_stat", "components.stats:mining", "stats.mining", 13), + StatRow("dbrows.prayer_stat", "components.stats:prayer", "stats.prayer", 7), + StatRow("dbrows.ranged_stat", "components.stats:ranged", "stats.ranged", 3), + StatRow("dbrows.runecraft_stat", "components.stats:runecraft", "stats.runecraft", 12), + StatRow("dbrows.slayer_stat", "components.stats:slayer", "stats.slayer", 20), + StatRow("dbrows.smithing_stat", "components.stats:smithing", "stats.smithing", 14), + StatRow("dbrows.strength_stat", "components.stats:strength", "stats.strength", 2), + StatRow("dbrows.thieving_stat", "components.stats:thieving", "stats.thieving", 10), + StatRow("dbrows.woodcutting_stat", "components.stats:woodcutting", "stats.woodcutting", 18), + StatRow("dbrows.sailing_stat", "components.stats:sailing", "stats.sailing", 24) + ) + + skillsWithBits.forEach { row -> + row(row.rowName) { + columnRSCM(COL_COMPONENT, row.componentId) + columnRSCM(COL_STAT, row.statString) + column(COL_BIT, row.bit) + } + } + } +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/misc/FoodTable.kt b/cache/bin/main/org/alter/impl/misc/FoodTable.kt new file mode 100644 index 00000000..eb39907b --- /dev/null +++ b/cache/bin/main/org/alter/impl/misc/FoodTable.kt @@ -0,0 +1,522 @@ +package org.alter.impl.misc + +import dev.openrune.cache.VARBIT +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +enum class Food( + vararg val items: String, + val heal: Int, + val overheal: Boolean = false, + val comboFood: Boolean = false, + val hasEffect: Boolean = false, + val eatDelay: List = listOf(), + val combatDelay: List = listOf(), + val dbRowId: String +) { + SHRIMPS("items.shrimp", heal = 3, dbRowId = "dbrows.shrimps_food"), + COOKED_CHICKEN("items.cooked_chicken", heal = 3, dbRowId = "dbrows.cooked_chicken_food"), + COOKED_MEAT("items.cooked_meat", heal = 3, dbRowId = "dbrows.cooked_meat_food"), + BREAD("items.bread", heal = 5, dbRowId = "dbrows.bread_food"), + HERRING("items.herring", heal = 5, dbRowId = "dbrows.herring_food"), + MACKEREL("items.mackerel", heal = 6, dbRowId = "dbrows.mackerel_food"), + TROUT("items.trout", heal = 7, dbRowId = "dbrows.trout_food"), + PIKE("items.pike", heal = 8, dbRowId = "dbrows.pike_food"), + PEACH("items.peach", heal = 8, dbRowId = "dbrows.peach_food"), + SALMON("items.salmon", heal = 9, dbRowId = "dbrows.salmon_food"), + TUNA("items.tuna", heal = 10, dbRowId = "dbrows.tuna_food"), + JUG_OF_WINE("items.jug_wine", heal = 11, hasEffect = true, dbRowId = "dbrows.jug_of_wine_food"), + LOBSTER("items.lobster", heal = 12, dbRowId = "dbrows.lobster_food"), + BASS("items.bass", heal = 13, dbRowId = "dbrows.bass_food"), + SWORDFISH("items.swordfish", heal = 14, dbRowId = "dbrows.swordfish_food"), + IXCOZTIC_WHITE("items.ixcoztic_white", heal = 16, hasEffect = true, dbRowId = "dbrows.ixcoztic_white_food"), + POTATO_WITH_CHEESE("items.potato_cheese", heal = 16, dbRowId = "dbrows.potato_with_cheese_food"), + MONKFISH("items.monkfish", heal = 16, dbRowId = "dbrows.monkfish_food"), + CURRY("items.curry", "items.bowl_empty", heal = 19, dbRowId = "dbrows.curry_food"), + COOKED_PYRE_FOX("items.curry", heal = 11, dbRowId = "dbrows.cooked_pyre_fox_food"), + SHARK("items.shark", heal = 20, dbRowId = "dbrows.shark_food"), + SEA_TURTLE("items.seaturtle", heal = 21, dbRowId = "dbrows.sea_turtle_food"), + MANTA_RAY("items.mantaray", heal = 22, dbRowId = "dbrows.manta_ray_food"), + TUNA_POTATO("items.potato_tuna+sweetcorn", heal = 22, dbRowId = "dbrows.tuna_potato_food"), + DARK_CRAB("items.dark_crab", heal = 22, dbRowId = "dbrows.dark_crab_food"), + ANGLERFISH("items.anglerfish", heal = -1, overheal = true, dbRowId = "dbrows.anglerfish_food"), + ONION("items.onion", heal = 1, dbRowId = "dbrows.onion_food"), + + // Cakes + CAKE( + "items.cake", + "items.partial_cake", + "items.cake_slice", + heal = 4, + eatDelay = listOf(2, 2, 3), + combatDelay = listOf(2, 2, 3), + dbRowId = "dbrows.cake_food" + ), + CHOCOLATE_CAKE( + "items.chocolate_cake", + "items.partial_chocolate_cake", + "items.chocolate_slice", + heal = 5, + eatDelay = listOf(2, 2, 3), + combatDelay = listOf(2, 2, 3), + dbRowId = "dbrows.chocolate_cake_food" + ), + + // Pies + REDBERRY_PIE( + "items.redberry_pie", + "items.half_a_redberry_pie", + "items.piedish", + heal = 5, + eatDelay = listOf(1, 2), + combatDelay = listOf(1, 2), + dbRowId = "dbrows.redberry_pie_food" + ), + MEAT_PIE( + "items.meat_pie", + "items.half_a_meat_pie", + "items.piedish", + heal = 6, + eatDelay = listOf(1, 2), + combatDelay = listOf(1, 2), + dbRowId = "dbrows.meat_pie_food" + ), + GARDEN_PIE( + "items.garden_pie", + "items.half_garden_pie", + "items.piedish", + heal = 6, + eatDelay = listOf(1, 1), + combatDelay = listOf(1, 1), + hasEffect = true, + dbRowId = "dbrows.garden_pie_food" + ), + FISH_PIE( + "items.fish_pie", + "items.half_fish_pie", + "items.piedish", + heal = 6, + eatDelay = listOf(1, 1), + combatDelay = listOf(1, 1), + hasEffect = true, + dbRowId = "dbrows.fish_pie_food" + ), + APPLE_PIE( + "items.apple_pie", + "items.half_an_apple_pie", + "items.piedish", + heal = 7, + eatDelay = listOf(1, 2), + combatDelay = listOf(1, 2), + dbRowId = "dbrows.apple_pie_food" + ), + BOTANICAL_PIE( + "items.botanical_pie", + "items.half_botanical_pie", + "items.piedish", + heal = 7, + eatDelay = listOf(1, 1), + combatDelay = listOf(1, 1), + hasEffect = true, + dbRowId = "dbrows.botanical_pie_food" + ), + MUSHROOM_PIE( + "items.mushroom_pie", + "items.half_mushroom_pie", + "items.piedish", + heal = 8, + eatDelay = listOf(1, 1), + combatDelay = listOf(1, 1), + hasEffect = true, + dbRowId = "dbrows.mushroom_pie_food" + ), + ADMIRAL_PIE( + "items.admiral_pie", + "items.half_admiral_pie", + "items.piedish", + heal = 8, + eatDelay = listOf(1, 1), + combatDelay = listOf(1, 1), + hasEffect = true, + dbRowId = "dbrows.admiral_pie_food" + ), + + // Pizzas + PLAIN_PIZZA( + "items.plain_pizza", + "items.half_plain_pizza", + heal = 3, + eatDelay = listOf(1, 2), + combatDelay = listOf(3), + dbRowId = "dbrows.plain_pizza_food" + ), + MEAT_PIZZA( + "items.meat_pizza", + "items.half_meat_pizza", + heal = 3, + eatDelay = listOf(1, 2), + combatDelay = listOf(3), + dbRowId = "dbrows.meat_pizza_food" + ), + ANCHOVY_PIZZA( + "items.anchovie_pizza", + "items.half_anchovie_pizza", + heal = 2, + eatDelay = listOf(1, 2), + combatDelay = listOf(3), + dbRowId = "dbrows.anchovy_pizza_food" + ), + DRAGONFRUIT_PIE( + "items.dragonfruit_pie", + "items.half_dragonfruit_pie", + heal = 10, + eatDelay = listOf(1, 1), + combatDelay = listOf(1, 1), + hasEffect = true, + dbRowId = "dbrows.dragonfruit_pie_food" + ), + PINEAPPLE_PIZZA( + "items.pineapple_pizza", + "items.half_pineapple_pizza", + heal = 11, + eatDelay = listOf(1, 2), + combatDelay = listOf(3), + dbRowId = "dbrows.pineapple_pizza_food" + ), + WILD_PIE( + "items.wild_pie", + "items.half_wild_pie", + heal = 11, + eatDelay = listOf(1, 1), + combatDelay = listOf(1, 1), + hasEffect = true, + dbRowId = "dbrows.wild_pie_food" + ), + SUMMER_PIE( + "items.summer_pie", + "items.half_summer_pie", + heal = 11, + eatDelay = listOf(1, 1), + combatDelay = listOf(1, 1), + hasEffect = true, + dbRowId = "dbrows.summer_pie_food" + ), + + // Crunchies (combo foods) + TOAD_CRUNCHIES( + "items.toad_crunchies", + heal = 8, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.toad_crunchies_food" + ), + PREMADE_TD_CRUNCH( + "items.premade_toad_crunchies", + heal = 8, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_td_crunch_food" + ), + SPICY_CRUNCHIES( + "items.spicy_crunchies", + heal = 8, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.spicy_crunchies_food" + ), + PREMADE_SY_CRUNCH( + "items.premade_spicy_crunchies", + heal = 8, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_sy_crunch_food" + ), + WORM_CRUNCHIES( + "items.worm_crunchies", + heal = 8, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.worm_crunchies_food" + ), + PREMADE_WM_CRUN( + "items.premade_worm_crunchies", + heal = 8, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_wm_crun_food" + ), + CHOCCHIP_CRUNCHIES( + "items.chocchip_crunchies", + heal = 7, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.chocchip_crunchies_food" + ), + PREMADE_CH_CRUNCH( + "items.premade_chocchip_crunchies", + heal = 7, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_ch_crunch_food" + ), + + // Batta + FRUIT_BATTA( + "items.fruit_batta", + heal = 11, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.fruit_batta_food" + ), + PREMADE_FRT_BATTA( + "items.premade_fruit_batta", + heal = 11, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_frt_batta_food" + ), + TOAD_BATTA( + "items.toad_batta", + heal = 11, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.toad_batta_food" + ), + PREMADE_TD_BATTA( + "items.premade_toad_batta", + heal = 11, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_td_batta_food" + ), + WORM_BATTA( + "items.worm_batta", + heal = 2, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.worm_batta_food" + ), + PREMADE_WM_BATTA( + "items.premade_worm_batta", + heal = 11, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_wm_batta_food" + ), + VEGETABLE_BATTA( + "items.vegetable_batta", + heal = 11, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.vegetable_batta_food" + ), + PREMADE_VEG_BATTA( + "items.premade_vegetable_batta", + heal = 11, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_veg_batta_food" + ), + CHEESE_TOM_BATTA( + "items.cheese+tom_batta", + heal = 11, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.cheese_tom_batta_food" + ), + PREMADE_CT_BATTA( + "items.premade_cheese+tom_batta", + heal = 11, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_ct_batta_food" + ), + + // Special combo foods + WORM_HOLE( + "items.worm_hole", + heal = 12, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.worm_hole_food" + ), + PREMADE_WORM_HOLE( + "items.premade_worm_hole", + heal = 12, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_worm_hole_food" + ), + VEG_BALL( + "items.veg_ball", + heal = 12, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.veg_ball_food" + ), + PREMADE_VEG_BALL( + "items.premade_veg_ball", + heal = 12, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_veg_ball_food" + ), + CHOCOLATE_BOMB( + "items.chocolate_bomb", + heal = 15, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.chocolate_bomb_food" + ), + PREMADE_CHOC_BOMB( + "items.premade_chocolate_bomb", + heal = 15, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_choc_bomb_food" + ), + TANGLED_TOADS_LEGS( + "items.tangled_toads_legs", + heal = 15, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.tangled_toads_legs_food" + ), + PREMADE_TTL( + "items.premade_tangled_toads_legs", + heal = 15, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.premade_ttl_food" + ), + TOADS_LEGS( + "items.toads_legs", + heal = 3, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.toads_legs_food" + ), + CRYSTAL_PADDLEFISH( + "items.gauntlet_combo_food", + heal = 16, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.crystal_paddlefish_food" + ), + CORRUPTED_PADDLEFISH( + "items.gauntlet_combo_food_hm", + heal = 16, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.corrupted_paddlefish_food" + ), + COOKED_KARAMBWAN( + "items.tbwt_cooked_karambwan", + heal = 18, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.cooked_karambwan_food" + ), + BLIGHTED_KARAMBWAN( + "items.blighted_karambwan", + heal = 18, + eatDelay = listOf(2), + combatDelay = listOf(3), + comboFood = true, + dbRowId = "dbrows.blighted_karambwan_food" + ), + + // Blighted and other special foods + BLIGHTED_MANTA_RAY("items.blighted_mantaray", heal = 22, dbRowId = "dbrows.blighted_manta_ray_food"), + BLIGHTED_ANGLERFISH( + "items.blighted_anglerfish", + heal = -1, + overheal = true, + dbRowId = "dbrows.blighted_anglerfish_food" + ), + SWEETS("items.trail_sweets", heal = -1, dbRowId = "dbrows.sweets_food"), + MOONLIGHT_MEAD( + "items.keg_mature_moonlight_mead_1", + "items.keg_mature_moonlight_mead_2", + "items.keg_mature_moonlight_mead_3", + "items.cert_keg_mature_moonlight_mead_4", + heal = 6, + dbRowId = "dbrows.moonlight_mead_food" + ); +} + + +object FoodTable { + + + const val ITEMS = 0 + const val HEAL = 1 + const val COMBO_FOOD = 2 + const val HAS_EFFECT = 3 + const val OVERHEAL = 4 + const val EAT_DELAY = 5 + const val COMBAT_DELAY = 6 + + fun consumableFood() = dbTable("tables.consumable_food", serverOnly = true) { + + column("items", ITEMS, VarType.OBJ.count(Food.entries.maxOf { it.items.size })) + column("heal", HEAL, VarType.INT) + + column("combo", COMBO_FOOD, VarType.BOOLEAN) + column("effect", HAS_EFFECT, VarType.BOOLEAN) + column("overheal", OVERHEAL, VarType.BOOLEAN) + column("eatDelay", EAT_DELAY, VarType.INT) + column("combatDelay", COMBAT_DELAY, VarType.INT) + + Food.entries.forEach { food -> + row(food.dbRowId) { + + columnRSCM(ITEMS, *food.items) + + column(HEAL, food.heal) + column(COMBO_FOOD, food.comboFood) + column(HAS_EFFECT, food.hasEffect) + column(OVERHEAL, food.overheal) + + if (food.eatDelay.isNotEmpty()) { + column(EAT_DELAY, food.eatDelay) + } + if (food.combatDelay.isNotEmpty()) { + column(COMBAT_DELAY, food.combatDelay) + } + + } + } + } + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/misc/TeleTabs.kt b/cache/bin/main/org/alter/impl/misc/TeleTabs.kt new file mode 100644 index 00000000..dcf2f964 --- /dev/null +++ b/cache/bin/main/org/alter/impl/misc/TeleTabs.kt @@ -0,0 +1,199 @@ +package org.alter.impl.misc + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object TeleTabs { + + const val COL_ITEM = 0 + const val COL_MAGIC_LEVEL = 1 + const val COL_LOCATION = 2 + + fun teleTabs() = dbTable("tables.teleport_tablets", serverOnly = true) { + + column("item", COL_ITEM, VarType.OBJ) + column("magic_level", COL_MAGIC_LEVEL, VarType.INT) + column("location", COL_LOCATION, VarType.COORDGRID) + + // Standard Teleport Tablets + row("dbrows.varrock_teleport_tablet") { + columnRSCM(COL_ITEM, "items.poh_tablet_varrockteleport") + column(COL_MAGIC_LEVEL, 25) + } + row("dbrows.lumbridge_teleport_tablet") { + columnRSCM(COL_ITEM, "items.poh_tablet_lumbridgeteleport") + column(COL_MAGIC_LEVEL, 31) + column(COL_LOCATION, 52776082) + } + row("dbrows.falador_teleport_tablet") { + columnRSCM(COL_ITEM, "items.poh_tablet_faladorteleport") + column(COL_MAGIC_LEVEL, 37) + column(COL_LOCATION, 48565554) + } + row("dbrows.camelot_teleport_tablet") { + columnRSCM(COL_ITEM, "items.poh_tablet_camelotteleport") + column(COL_MAGIC_LEVEL, 45) + column(COL_LOCATION, 45174167) + } + row("dbrows.kourend_castle_teleport_tablet") { + columnRSCM(COL_ITEM, "items.poh_tablet_kourendteleport") + column(COL_MAGIC_LEVEL, 48) + column(COL_LOCATION, 26857052) + } + row("dbrows.ardougne_teleport_tablet") { + columnRSCM(COL_ITEM, "items.poh_tablet_ardougneteleport") + column(COL_MAGIC_LEVEL, 51) + column(COL_LOCATION, 43617513) + } + row("dbrows.civitas_illa_fortis_teleport_tablet") { + columnRSCM(COL_ITEM, "items.poh_tablet_fortisteleport") + column(COL_MAGIC_LEVEL, 54) + column(COL_LOCATION, 27544637) + } + row("dbrows.watchtower_teleport_tablet") { + columnRSCM(COL_ITEM, "items.poh_tablet_watchtowerteleport") + column(COL_MAGIC_LEVEL, 58) + } + + // Arceuus Teleport Tablets + row("dbrows.arceuus_library_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_lumbridge") + column(COL_MAGIC_LEVEL, 6) + column(COL_LOCATION, 26758910) + } + row("dbrows.draynor_manor_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_draynor") + column(COL_MAGIC_LEVEL, 17) + column(COL_LOCATION, 50924825) + } + row("dbrows.battlefront_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_battlefront") + column(COL_MAGIC_LEVEL, 23) + column(COL_LOCATION, 22072988) + } + row("dbrows.mind_altar_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_mind_altar") + column(COL_MAGIC_LEVEL, 28) + column(COL_LOCATION, 48827829) + } + row("dbrows.salve_graveyard_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_salve") + column(COL_MAGIC_LEVEL, 40) + column(COL_LOCATION, 56233349) + } + row("dbrows.fenkenstrains_castle_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_fenk") + column(COL_MAGIC_LEVEL, 48) + column(COL_LOCATION, 58133961) + } + row("dbrows.west_ardougne_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_westardy") + column(COL_MAGIC_LEVEL, 61) + column(COL_LOCATION, 40963291) + } + row("dbrows.harmony_island_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_harmony") + column(COL_MAGIC_LEVEL, 65) + column(COL_LOCATION, 62212915) + } + row("dbrows.cemetery_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_cemetery") + column(COL_MAGIC_LEVEL, 71) + column(COL_LOCATION, 48828083) + } + row("dbrows.barrows_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_barrows") + column(COL_MAGIC_LEVEL, 83) + column(COL_LOCATION, 58412274) + } + row("dbrows.ape_atoll_teleport_tablet") { + columnRSCM(COL_ITEM, "items.teletab_ape") + column(COL_MAGIC_LEVEL, 90) + column(COL_LOCATION, 45409166) + } + + // Ancient Magicks Teleport Tablets + row("dbrows.paddewwa_teleport_tablet") { + columnRSCM(COL_ITEM, "items.tablet_paddewa") + column(COL_MAGIC_LEVEL, 54) + column(COL_LOCATION, 50800283) + } + row("dbrows.senntisten_teleport_tablet") { + columnRSCM(COL_ITEM, "items.tablet_senntisten") + column(COL_MAGIC_LEVEL, 60) + column(COL_LOCATION, 54398217) + } + row("dbrows.kharyrll_teleport_tablet") { + columnRSCM(COL_ITEM, "items.tablet_kharyll") + column(COL_MAGIC_LEVEL, 66) + column(COL_LOCATION, 57249169) + } + row("dbrows.lassar_teleport_tablet") { + columnRSCM(COL_ITEM, "items.tablet_lassar") + column(COL_MAGIC_LEVEL, 72) + column(COL_LOCATION, 49188238) + } + row("dbrows.dareeyak_teleport_tablet") { + columnRSCM(COL_ITEM, "items.tablet_dareeyak") + column(COL_MAGIC_LEVEL, 78) + column(COL_LOCATION, 48631408) + } + row("dbrows.carrallanger_teleport_tablet") { + columnRSCM(COL_ITEM, "items.tablet_carrallangar") + column(COL_MAGIC_LEVEL, 84) + column(COL_LOCATION, 51744338) + } + row("dbrows.annakarl_teleport_tablet") { + columnRSCM(COL_ITEM, "items.tablet_annakarl") + column(COL_MAGIC_LEVEL, 90) + column(COL_LOCATION, 53858096) + } + row("dbrows.ghorrock_teleport_tablet") { + columnRSCM(COL_ITEM, "items.tablet_ghorrock") + column(COL_MAGIC_LEVEL, 96) + column(COL_LOCATION, 48729889) + } + + // Lunar Teleport Tablets + row("dbrows.moonclan_teleport_tablet") { + columnRSCM(COL_ITEM, "items.lunar_tablet_moonclan_teleport") + column(COL_MAGIC_LEVEL, 69) + column(COL_LOCATION, 34623307) + } + row("dbrows.ourania_teleport_tablet") { + columnRSCM(COL_ITEM, "items.lunar_tablet_ourania_teleport") + column(COL_MAGIC_LEVEL, 71) + column(COL_LOCATION, 40438958) + } + row("dbrows.waterbirth_teleport_tablet") { + columnRSCM(COL_ITEM, "items.lunar_tablet_waterbirth_teleport") + column(COL_MAGIC_LEVEL, 72) + column(COL_LOCATION, 41717420) + } + row("dbrows.barbarian_teleport_tablet") { + columnRSCM(COL_ITEM, "items.lunar_tablet_barbarian_teleport") + column(COL_MAGIC_LEVEL, 75) + column(COL_LOCATION, 41668081) + } + row("dbrows.khazard_teleport_tablet") { + columnRSCM(COL_ITEM, "items.lunar_tablet_khazard_teleport") + column(COL_MAGIC_LEVEL, 78) + column(COL_LOCATION, 43191391) + } + row("dbrows.fishing_guild_teleport_tablet") { + columnRSCM(COL_ITEM, "items.lunar_tablet_fishing_guild_teleport") + column(COL_MAGIC_LEVEL, 85) + column(COL_LOCATION, 42814783) + } + row("dbrows.catherby_teleport_tablet") { + columnRSCM(COL_ITEM, "items.lunar_tablet_catherby_teleport") + column(COL_MAGIC_LEVEL, 87) + column(COL_LOCATION, 45895033) + } + row("dbrows.ice_plateau_teleport_tablet") { + columnRSCM(COL_ITEM, "items.lunar_tablet_ice_plateau_teleport") + column(COL_MAGIC_LEVEL, 89) + column(COL_LOCATION, 48746338) + } + } +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/skills/Firemaking.kt b/cache/bin/main/org/alter/impl/skills/Firemaking.kt new file mode 100644 index 00000000..66fd55ac --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/Firemaking.kt @@ -0,0 +1,163 @@ +package org.alter.impl.skills + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object Firemaking { + + const val COL_ITEM = 0 + const val COL_LEVEL = 1 + const val COL_XP = 2 + const val COL_INITIAL_TICKS = 3 + const val COL_PER_LOG_TICKS = 4 + const val COL_PER_ANIMATION = 5 + + fun logs() = dbTable("tables.firemaking_logs", serverOnly = true) { + + column("item", COL_ITEM, VarType.OBJ) + column("level", COL_LEVEL, VarType.INT) + column("xp", COL_XP, VarType.INT) + column("forester_initial_ticks", COL_INITIAL_TICKS, VarType.INT) + column("forester_log_ticks", COL_PER_LOG_TICKS, VarType.INT) + column("forester_animation", COL_PER_ANIMATION, VarType.SEQ) + + row("dbrows.firemaking_normal_logs") { + columnRSCM(COL_ITEM, "items.logs") + column(COL_LEVEL, 1) + column(COL_XP, 40) + column(COL_INITIAL_TICKS, 102) + column(COL_PER_LOG_TICKS, 3) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_logs") + } + + row("dbrows.firemaking_achey_tree_logs") { + columnRSCM(COL_ITEM, "items.achey_tree_logs") + column(COL_LEVEL, 1) + column(COL_XP, 40) + column(COL_INITIAL_TICKS, 102) + column(COL_PER_LOG_TICKS, 3) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_achey_tree_logs") + } + + row("dbrows.firemaking_oak_logs") { + columnRSCM(COL_ITEM, "items.oak_logs") + column(COL_LEVEL, 15) + column(COL_XP, 60) + column(COL_INITIAL_TICKS, 109) + column(COL_PER_LOG_TICKS, 10) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_oak_logs") + } + + row("dbrows.firemaking_willow_logs") { + columnRSCM(COL_ITEM, "items.willow_logs") + column(COL_LEVEL, 30) + column(COL_XP, 90) + column(COL_INITIAL_TICKS, 116) + column(COL_PER_LOG_TICKS, 17) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_willow_logs") + } + + row("dbrows.firemaking_teak_logs") { + columnRSCM(COL_ITEM, "items.teak_logs") + column(COL_LEVEL, 35) + column(COL_XP, 105) + column(COL_INITIAL_TICKS, 118) + column(COL_PER_LOG_TICKS, 19) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_teak_logs") + } + + row("dbrows.firemaking_arctic_pine_logs") { + columnRSCM(COL_ITEM, "items.arctic_pine_log") + column(COL_LEVEL, 42) + column(COL_XP, 125) + column(COL_INITIAL_TICKS, 121) + column(COL_PER_LOG_TICKS, 22) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_arctic_pine_log") + } + + row("dbrows.firemaking_maple_logs") { + columnRSCM(COL_ITEM, "items.maple_logs") + column(COL_LEVEL, 45) + column(COL_XP, 135) + column(COL_INITIAL_TICKS, 123) + column(COL_PER_LOG_TICKS, 24) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_maple_logs") + } + + row("dbrows.firemaking_mahogany_logs") { + columnRSCM(COL_ITEM, "items.mahogany_logs") + column(COL_LEVEL, 50) + column(COL_XP, 157) + column(COL_INITIAL_TICKS, 125) + column(COL_PER_LOG_TICKS, 26) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_mahogany_logs") + } + + row("dbrows.firemaking_yew_logs") { + columnRSCM(COL_ITEM, "items.yew_logs") + column(COL_LEVEL, 60) + column(COL_XP, 202) + column(COL_INITIAL_TICKS, 130) + column(COL_PER_LOG_TICKS, 31) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_yew_logs") + } + + row("dbrows.firemaking_blisterwood_logs") { + columnRSCM(COL_ITEM, "items.blisterwood_logs") + column(COL_LEVEL, 62) + column(COL_XP, 96) + column(COL_INITIAL_TICKS, 131) + column(COL_PER_LOG_TICKS, 32) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_blisterwood_logs") + } + + row("dbrows.firemaking_magic_logs") { + columnRSCM(COL_ITEM, "items.magic_logs") + column(COL_LEVEL, 75) + column(COL_XP, 305) + column(COL_INITIAL_TICKS, 137) + column(COL_PER_LOG_TICKS, 38) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_magic_logs") + } + + row("dbrows.firemaking_redwood_logs") { + columnRSCM(COL_ITEM, "items.redwood_logs") + column(COL_LEVEL, 90) + column(COL_XP, 350) + column(COL_INITIAL_TICKS, 144) + column(COL_PER_LOG_TICKS, 45) + columnRSCM(COL_PER_ANIMATION, "sequences.forestry_campfire_burning_redwood_logs") + } + + // Colored logs — same stats as normal logs + row("dbrows.firemaking_blue_logs") { + columnRSCM(COL_ITEM, "items.blue_logs") + column(COL_LEVEL, 1) + column(COL_XP, 50) + } + + row("dbrows.firemaking_green_logs") { + columnRSCM(COL_ITEM, "items.green_logs") + column(COL_LEVEL, 1) + column(COL_XP, 50) + } + + row("dbrows.firemaking_purple_logs") { + columnRSCM(COL_ITEM, "items.trail_logs_purple") + column(COL_LEVEL, 1) + column(COL_XP, 50) + } + + row("dbrows.firemaking_red_logs") { + columnRSCM(COL_ITEM, "items.red_logs") + column(COL_LEVEL, 1) + column(COL_XP, 50) + } + + row("dbrows.firemaking_white_logs") { + columnRSCM(COL_ITEM, "items.trail_logs_white") + column(COL_LEVEL, 1) + column(COL_XP, 50) + } + } +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/skills/Herblore.kt b/cache/bin/main/org/alter/impl/skills/Herblore.kt new file mode 100644 index 00000000..50ec2226 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/Herblore.kt @@ -0,0 +1,855 @@ +package org.alter.impl.skills + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object Herblore { + + // Unfinished potions table columns (in order of definition) + const val COL_HERB_ITEM = 0 + const val COL_LEVEL = 1 + const val COL_XP = 2 + const val COL_UNFINISHED_POTION = 3 + + // Finished potions table columns (in order of definition) + const val COL_UNF_POT = 0 + const val COL_SECONDARIES = 1 + const val COL_LEVEL_REQUIRED = 2 + const val COL_XP_FINISHED = 3 + const val COL_OUTPUT_POTION = 4 + + // Cleaning herbs table columns (in order of definition) + const val COL_GRIMY_HERB = 0 + const val COL_CLEAN_LEVEL = 1 + const val COL_CLEAN_XP = 2 + const val COL_CLEAN_HERB = 3 + + // Barbarian mixes table columns (in order of definition) + const val COL_TWO_DOSE_POTION = 0 + const val COL_MIX_INGREDIENT = 1 + const val COL_MIX_LEVEL = 2 + const val COL_MIX_XP = 3 + const val COL_BARBARIAN_MIX = 4 + + // Swamp tar table columns (in order of definition) + const val COL_TAR_HERB = 0 + const val COL_TAR_LEVEL = 1 + const val COL_TAR_XP = 2 + const val COL_TAR_FINISHED = 3 + + // Crushing table columns (in order of definition) + const val COL_CRUSH_ITEM = 0 + const val COL_CRUSH_LEVEL = 1 + const val COL_CRUSH_XP = 2 + const val COL_CRUSHED_ITEM = 3 + + /** + * Table for creating unfinished potions (herb + vial of water) + */ + fun unfinishedPotions() = dbTable("tables.herblore_unfinished", serverOnly = true) { + column("herb_item", COL_HERB_ITEM, VarType.OBJ) + column("level", COL_LEVEL, VarType.INT) + column("xp", COL_XP, VarType.INT) + column("unfinished_potion", COL_UNFINISHED_POTION, VarType.OBJ) + + // Guam leaf + row("dbrows.herblore_guam_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.guam_leaf") + column(COL_LEVEL, 3) + column(COL_XP, 2) + columnRSCM(COL_UNFINISHED_POTION, "items.guamvial") + } + + // Marrentill + row("dbrows.herblore_marrentill_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.marentill") + column(COL_LEVEL, 5) + column(COL_XP, 3) + columnRSCM(COL_UNFINISHED_POTION, "items.marrentillvial") + } + + // Tarromin + row("dbrows.herblore_tarromin_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.tarromin") + column(COL_LEVEL, 11) + column(COL_XP, 5) + columnRSCM(COL_UNFINISHED_POTION, "items.tarrominvial") + } + + // Harralander + row("dbrows.herblore_harralander_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.harralander") + column(COL_LEVEL, 20) + column(COL_XP, 6) + columnRSCM(COL_UNFINISHED_POTION, "items.harralandervial") + } + + // Ranarr weed + row("dbrows.herblore_ranarr_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.ranarr_weed") + column(COL_LEVEL, 25) + column(COL_XP, 8) + columnRSCM(COL_UNFINISHED_POTION, "items.ranarrvial") + } + + // Toadflax + row("dbrows.herblore_toadflax_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.toadflax") + column(COL_LEVEL, 30) + column(COL_XP, 8) + columnRSCM(COL_UNFINISHED_POTION, "items.toadflaxvial") + } + + // Irit leaf + row("dbrows.herblore_irit_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.irit_leaf") + column(COL_LEVEL, 40) + column(COL_XP, 9) + columnRSCM(COL_UNFINISHED_POTION, "items.iritvial") + } + + // Avantoe + row("dbrows.herblore_avantoe_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.avantoe") + column(COL_LEVEL, 48) + column(COL_XP, 10) + columnRSCM(COL_UNFINISHED_POTION, "items.avantoevial") + } + + // Kwuarm + row("dbrows.herblore_kwuarm_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.kwuarm") + column(COL_LEVEL, 54) + column(COL_XP, 11) + columnRSCM(COL_UNFINISHED_POTION, "items.kwuarmvial") + } + + // Snapdragon + row("dbrows.herblore_snapdragon_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.snapdragon") + column(COL_LEVEL, 59) + column(COL_XP, 12) + columnRSCM(COL_UNFINISHED_POTION, "items.snapdragonvial") + } + + // Cadantine + row("dbrows.herblore_cadantine_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.cadantine") + column(COL_LEVEL, 65) + column(COL_XP, 13) + columnRSCM(COL_UNFINISHED_POTION, "items.cadantinevial") + } + + // Lantadyme + row("dbrows.herblore_lantadyme_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.lantadyme") + column(COL_LEVEL, 67) + column(COL_XP, 13) + columnRSCM(COL_UNFINISHED_POTION, "items.lantadymevial") + } + + // Dwarf weed + row("dbrows.herblore_dwarf_weed_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.dwarf_weed") + column(COL_LEVEL, 70) + column(COL_XP, 13) + columnRSCM(COL_UNFINISHED_POTION, "items.dwarfweedvial") + } + + // Torstol + row("dbrows.herblore_torstol_unfinished") { + columnRSCM(COL_HERB_ITEM, "items.torstol") + column(COL_LEVEL, 75) + column(COL_XP, 14) + columnRSCM(COL_UNFINISHED_POTION, "items.torstolvial") + } + } + + /** + * Table for creating finished potions (unfinished potion + secondary ingredient) + * Supports multi-step potions with additional ingredients + */ + fun finishedPotions() = dbTable("tables.herblore_finished", serverOnly = true) { + column("unf_pot", COL_UNF_POT, VarType.OBJ) + column("secondaries", COL_SECONDARIES, VarType.OBJ) + column("level_required", COL_LEVEL_REQUIRED, VarType.INT) + column("xp", COL_XP_FINISHED, VarType.INT) + column("output_potion", COL_OUTPUT_POTION, VarType.OBJ) + + // Attack potion (Guam + Eye of newt) + row("dbrows.herblore_attack_potion") { + columnRSCM(COL_UNF_POT, "items.guamvial") + columnRSCM(COL_SECONDARIES, "items.eye_of_newt") + column(COL_LEVEL_REQUIRED, 3) + column(COL_XP_FINISHED, 25) + columnRSCM(COL_OUTPUT_POTION, "items.3dose1attack") + } + + // Antipoison (Marrentill + Unicorn horn dust) + row("dbrows.herblore_antipoison") { + columnRSCM(COL_UNF_POT, "items.marrentillvial") + columnRSCM(COL_SECONDARIES, "items.unicorn_horn_dust") + column(COL_LEVEL_REQUIRED, 5) + column(COL_XP_FINISHED, 38) + columnRSCM(COL_OUTPUT_POTION, "items.3doseantipoison") + } + + // Strength potion (Tarromin + Limpwurt root) + row("dbrows.herblore_strength_potion") { + columnRSCM(COL_UNF_POT, "items.tarrominvial") + columnRSCM(COL_SECONDARIES, "items.limpwurt_root") + column(COL_LEVEL_REQUIRED, 12) + column(COL_XP_FINISHED, 50) + columnRSCM(COL_OUTPUT_POTION, "items.3dose1strength") + } + + // Restore potion (Harralander + Red spiders' eggs) + row("dbrows.herblore_restore_potion") { + columnRSCM(COL_UNF_POT, "items.harralandervial") + columnRSCM(COL_SECONDARIES, "items.red_spiders_eggs") + column(COL_LEVEL_REQUIRED, 22) + column(COL_XP_FINISHED, 63) + columnRSCM(COL_OUTPUT_POTION, "items.3dosestatrestore") + } + + // Energy potion (Harralander + Chocolate dust) + row("dbrows.herblore_energy_potion") { + columnRSCM(COL_UNF_POT, "items.harralandervial") + columnRSCM(COL_SECONDARIES, "items.chocolate_dust") + column(COL_LEVEL_REQUIRED, 26) + column(COL_XP_FINISHED, 68) + columnRSCM(COL_OUTPUT_POTION, "items.3dose1energy") + } + + // Prayer potion (Ranarr + Snape grass) + row("dbrows.herblore_prayer_potion") { + columnRSCM(COL_UNF_POT, "items.ranarrvial") + columnRSCM(COL_SECONDARIES, "items.snape_grass") + column(COL_LEVEL_REQUIRED, 38) + column(COL_XP_FINISHED, 88) + columnRSCM(COL_OUTPUT_POTION, "items.3doseprayerrestore") + } + + // Super attack (Irit + Eye of newt) + row("dbrows.herblore_super_attack") { + columnRSCM(COL_UNF_POT, "items.iritvial") + columnRSCM(COL_SECONDARIES, "items.eye_of_newt") + column(COL_LEVEL_REQUIRED, 45) + column(COL_XP_FINISHED, 100) + columnRSCM(COL_OUTPUT_POTION, "items.3dose2attack") + } + + // Superantipoison (Irit + Unicorn horn dust) + row("dbrows.herblore_superantipoison") { + columnRSCM(COL_UNF_POT, "items.iritvial") + columnRSCM(COL_SECONDARIES, "items.unicorn_horn_dust") + column(COL_LEVEL_REQUIRED, 48) + column(COL_XP_FINISHED, 105) + columnRSCM(COL_OUTPUT_POTION, "items.3dose2antipoison") + } + + // Fishing potion (Avantoe + Snape grass) + row("dbrows.herblore_fishing_potion") { + columnRSCM(COL_UNF_POT, "items.avantoevial") + columnRSCM(COL_SECONDARIES, "items.snape_grass") + column(COL_LEVEL_REQUIRED, 50) + column(COL_XP_FINISHED, 113) + columnRSCM(COL_OUTPUT_POTION, "items.3dosefisherspotion") + } + + // Super energy (Avantoe + Mort myre fungus) + row("dbrows.herblore_super_energy") { + columnRSCM(COL_UNF_POT, "items.avantoevial") + columnRSCM(COL_SECONDARIES, "items.mortmyremushroom") + column(COL_LEVEL_REQUIRED, 52) + column(COL_XP_FINISHED, 118) + columnRSCM(COL_OUTPUT_POTION, "items.3dose2energy") + } + + // Super strength (Kwuarm + Limpwurt root) + row("dbrows.herblore_super_strength") { + columnRSCM(COL_UNF_POT, "items.kwuarmvial") + columnRSCM(COL_SECONDARIES, "items.limpwurt_root") + column(COL_LEVEL_REQUIRED, 55) + column(COL_XP_FINISHED, 125) + columnRSCM(COL_OUTPUT_POTION, "items.3dose2strength") + } + + // Weapon poison (Kwuarm + Dragon scale dust) + row("dbrows.herblore_weapon_poison") { + columnRSCM(COL_UNF_POT, "items.kwuarmvial") + columnRSCM(COL_SECONDARIES, "items.dragon_scale_dust") + column(COL_LEVEL_REQUIRED, 60) + column(COL_XP_FINISHED, 138) + columnRSCM(COL_OUTPUT_POTION, "items.weapon_poison") + } + + // Super restore (Snapdragon + Red spiders' eggs) + row("dbrows.herblore_super_restore") { + columnRSCM(COL_UNF_POT, "items.snapdragonvial") + columnRSCM(COL_SECONDARIES, "items.red_spiders_eggs") + column(COL_LEVEL_REQUIRED, 63) + column(COL_XP_FINISHED, 143) + columnRSCM(COL_OUTPUT_POTION, "items.3dose2restore") + } + + // Super defence (Cadantine + White berries) + row("dbrows.herblore_super_defence") { + columnRSCM(COL_UNF_POT, "items.cadantinevial") + columnRSCM(COL_SECONDARIES, "items.white_berries") + column(COL_LEVEL_REQUIRED, 66) + column(COL_XP_FINISHED, 150) + columnRSCM(COL_OUTPUT_POTION, "items.3dose2defense") + } + + // Antifire (Lantadyme + Dragon scale dust) + row("dbrows.herblore_antifire") { + columnRSCM(COL_UNF_POT, "items.lantadymevial") + columnRSCM(COL_SECONDARIES, "items.dragon_scale_dust") + column(COL_LEVEL_REQUIRED, 69) + column(COL_XP_FINISHED, 158) + columnRSCM(COL_OUTPUT_POTION, "items.3dose1antidragon") + } + + // Super antifire (Lantadyme + Crushed superior dragon bones) + row("dbrows.herblore_super_antifire") { + columnRSCM(COL_UNF_POT, "items.lantadymevial") + columnRSCM(COL_SECONDARIES, "items.crushed_dragon_bones") + column(COL_LEVEL_REQUIRED, 92) + column(COL_XP_FINISHED, 180) + columnRSCM(COL_OUTPUT_POTION, "items.3dose2antidragon") + } + + // Ranging potion (Dwarf weed + Wine of zamorak) + row("dbrows.herblore_ranging_potion") { + columnRSCM(COL_UNF_POT, "items.dwarfweedvial") + columnRSCM(COL_SECONDARIES, "items.wine_of_zamorak") + column(COL_LEVEL_REQUIRED, 72) + column(COL_XP_FINISHED, 163) + columnRSCM(COL_OUTPUT_POTION, "items.3doserangerspotion") + } + + // Magic potion (Lantadyme + Potato cactus) + row("dbrows.herblore_magic_potion") { + columnRSCM(COL_UNF_POT, "items.lantadymevial") + columnRSCM(COL_SECONDARIES, "items.cactus_potato") + column(COL_LEVEL_REQUIRED, 76) + column(COL_XP_FINISHED, 173) + columnRSCM(COL_OUTPUT_POTION, "items.3dose1magic") + } + + // Zamorak brew (Torstol + Jangerberries) + row("dbrows.herblore_zamorak_brew") { + columnRSCM(COL_UNF_POT, "items.torstolvial") + columnRSCM(COL_SECONDARIES, "items.jangerberries") + column(COL_LEVEL_REQUIRED, 78) + column(COL_XP_FINISHED, 175) + columnRSCM(COL_OUTPUT_POTION, "items.3dosepotionofzamorak") + } + + // Saradomin brew (Toadflax + Crushed nest) + row("dbrows.herblore_saradomin_brew") { + columnRSCM(COL_UNF_POT, "items.toadflaxvial") + columnRSCM(COL_SECONDARIES, "items.crushed_bird_nest") + column(COL_LEVEL_REQUIRED, 81) + column(COL_XP_FINISHED, 180) + columnRSCM(COL_OUTPUT_POTION, "items.3dosepotionofsaradomin") + } + + // Defence potion (Ranarr + White berries) + row("dbrows.herblore_defence_potion") { + columnRSCM(COL_UNF_POT, "items.ranarrvial") + columnRSCM(COL_SECONDARIES, "items.white_berries") + column(COL_LEVEL_REQUIRED, 30) + column(COL_XP_FINISHED, 75) + columnRSCM(COL_OUTPUT_POTION, "items.3dose1defense") + } + + // Agility potion (Toadflax + Toad's legs) + row("dbrows.herblore_agility_potion") { + columnRSCM(COL_UNF_POT, "items.toadflaxvial") + columnRSCM(COL_SECONDARIES, "items.toads_legs") + column(COL_LEVEL_REQUIRED, 34) + column(COL_XP_FINISHED, 80) + columnRSCM(COL_OUTPUT_POTION, "items.3dose1agility") + } + + // Combat potion (Harralander + Goat horn dust) + row("dbrows.herblore_combat_potion") { + columnRSCM(COL_UNF_POT, "items.harralandervial") + columnRSCM(COL_SECONDARIES, "items.ground_desert_goat_horn") + column(COL_LEVEL_REQUIRED, 36) + column(COL_XP_FINISHED, 84) + columnRSCM(COL_OUTPUT_POTION, "items.3dosecombat") + } + + // Hunter potion (Avantoe + Kebbit teeth) + row("dbrows.herblore_hunter_potion") { + columnRSCM(COL_UNF_POT, "items.avantoevial") + columnRSCM(COL_SECONDARIES, "items.huntingbeast_sabreteeth") + column(COL_LEVEL_REQUIRED, 53) + column(COL_XP_FINISHED, 120) + columnRSCM(COL_OUTPUT_POTION, "items.3dosehunting") + } + + // Bastion potion: Cadantine (unf) + Wine of zamorak + Crushed superior dragon bones → Bastion (3) + row("dbrows.herblore_bastion_potion") { + columnRSCM(COL_UNF_POT, "items.cadantinevial") + columnRSCM(COL_SECONDARIES, "items.wine_of_zamorak", "items.crushed_dragon_bones") + column(COL_LEVEL_REQUIRED, 80) + column(COL_XP_FINISHED, 155) + columnRSCM(COL_OUTPUT_POTION, "items.3dosebastion") + } + + // Battlemage potion: Cadantine (unf) + Potato cactus + Crushed superior dragon bones → Battlemage (3) + row("dbrows.herblore_battlemage_potion") { + columnRSCM(COL_UNF_POT, "items.cadantinevial") + columnRSCM(COL_SECONDARIES, "items.cactus_potato", "items.crushed_dragon_bones") + column(COL_LEVEL_REQUIRED, 79) + column(COL_XP_FINISHED, 155) + columnRSCM(COL_OUTPUT_POTION, "items.3dosebattlemage") + } + + // Super combat potion: Torstol (unf) + Super attack (3) + Super strength (3) + Super defence (3) = Super combat (3) + row("dbrows.herblore_super_combat_potion") { + columnRSCM(COL_UNF_POT, "items.torstol") + columnRSCM(COL_SECONDARIES, "items.3dose2attack", "items.3dose2strength", "items.3dose2defense") + column(COL_LEVEL_REQUIRED, 90) + column(COL_XP_FINISHED, 150) + columnRSCM(COL_OUTPUT_POTION, "items.3dose2combat") + } + + // Extended anti-venom+ (Anti-venom+ (3) + Zulrah's scales) + row("dbrows.herblore_extended_antivenom_plus") { + columnRSCM(COL_UNF_POT, "items.antivenom+3") + columnRSCM(COL_SECONDARIES, "items.snakeboss_scale") + column(COL_LEVEL_REQUIRED, 94) + column(COL_XP_FINISHED, 160) + columnRSCM(COL_OUTPUT_POTION, "items.extended_antivenom+3") + } + } + + /** + * Table for cleaning grimy herbs (unidentified -> clean) + * One-time action, no repeatable delay + */ + fun cleaningHerbs() = dbTable("tables.herblore_cleaning", serverOnly = true) { + column("grimy_herb", COL_GRIMY_HERB, VarType.OBJ) + column("level", COL_CLEAN_LEVEL, VarType.INT) + column("xp", COL_CLEAN_XP, VarType.INT) + column("clean_herb", COL_CLEAN_HERB, VarType.OBJ) + + // Guam + row("dbrows.herblore_clean_guam") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_guam") + column(COL_CLEAN_LEVEL, 3) + column(COL_CLEAN_XP, 2) + columnRSCM(COL_CLEAN_HERB, "items.guam_leaf") + } + + // Marrentill + row("dbrows.herblore_clean_marrentill") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_marentill") + column(COL_CLEAN_LEVEL, 5) + column(COL_CLEAN_XP, 3) + columnRSCM(COL_CLEAN_HERB, "items.marentill") + } + + // Tarromin + row("dbrows.herblore_clean_tarromin") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_tarromin") + column(COL_CLEAN_LEVEL, 11) + column(COL_CLEAN_XP, 5) + columnRSCM(COL_CLEAN_HERB, "items.tarromin") + } + + // Harralander + row("dbrows.herblore_clean_harralander") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_harralander") + column(COL_CLEAN_LEVEL, 20) + column(COL_CLEAN_XP, 6) + columnRSCM(COL_CLEAN_HERB, "items.harralander") + } + + // Ranarr + row("dbrows.herblore_clean_ranarr") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_ranarr") + column(COL_CLEAN_LEVEL, 25) + column(COL_CLEAN_XP, 8) + columnRSCM(COL_CLEAN_HERB, "items.ranarr_weed") + } + + // Toadflax + row("dbrows.herblore_clean_toadflax") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_toadflax") + column(COL_CLEAN_LEVEL, 30) + column(COL_CLEAN_XP, 8) + columnRSCM(COL_CLEAN_HERB, "items.toadflax") + } + + // Irit + row("dbrows.herblore_clean_irit") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_irit") + column(COL_CLEAN_LEVEL, 40) + column(COL_CLEAN_XP, 9) + columnRSCM(COL_CLEAN_HERB, "items.irit_leaf") + } + + // Avantoe + row("dbrows.herblore_clean_avantoe") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_avantoe") + column(COL_CLEAN_LEVEL, 48) + column(COL_CLEAN_XP, 10) + columnRSCM(COL_CLEAN_HERB, "items.avantoe") + } + + // Kwuarm + row("dbrows.herblore_clean_kwuarm") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_kwuarm") + column(COL_CLEAN_LEVEL, 54) + column(COL_CLEAN_XP, 11) + columnRSCM(COL_CLEAN_HERB, "items.kwuarm") + } + + // Snapdragon + row("dbrows.herblore_clean_snapdragon") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_snapdragon") + column(COL_CLEAN_LEVEL, 59) + column(COL_CLEAN_XP, 12) + columnRSCM(COL_CLEAN_HERB, "items.snapdragon") + } + + // Cadantine + row("dbrows.herblore_clean_cadantine") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_cadantine") + column(COL_CLEAN_LEVEL, 65) + column(COL_CLEAN_XP, 13) + columnRSCM(COL_CLEAN_HERB, "items.cadantine") + } + + // Lantadyme + row("dbrows.herblore_clean_lantadyme") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_lantadyme") + column(COL_CLEAN_LEVEL, 67) + column(COL_CLEAN_XP, 13) + columnRSCM(COL_CLEAN_HERB, "items.lantadyme") + } + + // Dwarf weed + row("dbrows.herblore_clean_dwarf_weed") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_dwarf_weed") + column(COL_CLEAN_LEVEL, 70) + column(COL_CLEAN_XP, 13) + columnRSCM(COL_CLEAN_HERB, "items.dwarf_weed") + } + + // Torstol + row("dbrows.herblore_clean_torstol") { + columnRSCM(COL_GRIMY_HERB, "items.unidentified_torstol") + column(COL_CLEAN_LEVEL, 75) + column(COL_CLEAN_XP, 14) + columnRSCM(COL_CLEAN_HERB, "items.torstol") + } + } + + /** + * Table for creating barbarian mixes (two-dose potion + roe/caviar) + */ + fun barbarianMixes() = dbTable("tables.herblore_barbarian_mixes", serverOnly = true) { + column("two_dose_potion", COL_TWO_DOSE_POTION, VarType.OBJ) + column("mix_ingredient", COL_MIX_INGREDIENT, VarType.OBJ) + column("level", COL_MIX_LEVEL, VarType.INT) + column("xp", COL_MIX_XP, VarType.INT) + column("barbarian_mix", COL_BARBARIAN_MIX, VarType.OBJ) + + // Attack mix (2-dose attack + roe) + row("dbrows.herblore_attack_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose1attack") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_roe") + column(COL_MIX_LEVEL, 3) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose1attack") + } + + // Antipoison mix (2-dose antipoison + roe) + row("dbrows.herblore_antipoison_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2doseantipoison") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_roe") + column(COL_MIX_LEVEL, 5) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2doseantipoison") + } + + // Attack mix (2-dose attack + caviar) - Alternative to roe + row("dbrows.herblore_attack_mix_caviar") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose1attack") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 3) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose1attack") + } + + // Strength mix (2-dose strength + roe) + row("dbrows.herblore_strength_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose1strength") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_roe") + column(COL_MIX_LEVEL, 12) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose1strength") + } + + // Stat restore mix (2-dose restore + roe) + row("dbrows.herblore_stat_restore_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dosestatrestore") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_roe") + column(COL_MIX_LEVEL, 22) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dosestatrestore") + } + + // Energy mix (2-dose energy + roe) + row("dbrows.herblore_energy_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose1energy") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_roe") + column(COL_MIX_LEVEL, 26) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose1energy") + } + + // Defence mix (2-dose defence + roe) + row("dbrows.herblore_defence_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose1defense") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_roe") + column(COL_MIX_LEVEL, 30) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose1defense") + } + + // Agility mix (2-dose agility + roe) + row("dbrows.herblore_agility_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose1agility") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_roe") + column(COL_MIX_LEVEL, 34) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose1agility") + } + + // Prayer mix (2-dose prayer + roe) + row("dbrows.herblore_prayer_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2doseprayerrestore") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_roe") + column(COL_MIX_LEVEL, 38) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2doseprayerrestore") + } + + // Super attack mix (2-dose super attack + caviar) + row("dbrows.herblore_super_attack_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose2attack") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 45) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose2attack") + } + + // Super antipoison mix (2-dose super antipoison + caviar) + row("dbrows.herblore_super_antipoison_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose2antipoison") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 48) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose2antipoison") + } + + // Fishing mix (2-dose fishing + caviar) + row("dbrows.herblore_fishing_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dosefisherspotion") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 50) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dosefisherspotion") + } + + // Super energy mix (2-dose super energy + caviar) + row("dbrows.herblore_super_energy_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose2energy") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 52) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose2energy") + } + + // Super strength mix (2-dose super strength + caviar) + row("dbrows.herblore_super_strength_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose2strength") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 55) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose2strength") + } + + // Super restore mix (2-dose super restore + caviar) + row("dbrows.herblore_super_restore_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose2restore") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 63) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose2restore") + } + + // Super defence mix (2-dose super defence + caviar) + row("dbrows.herblore_super_defence_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose2defense") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 66) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose2defense") + } + + // Antifire mix (2-dose antifire + caviar) + row("dbrows.herblore_antifire_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose1antidragon") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 69) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose1antidragon") + } + + // Ranging mix (2-dose ranging + caviar) + row("dbrows.herblore_ranging_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2doserangerspotion") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 72) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2doserangerspotion") + } + + // Magic mix (2-dose magic + caviar) + row("dbrows.herblore_magic_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dose1magic") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 76) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dose1magic") + } + + // Zamorak mix (2-dose zamorak + caviar) + row("dbrows.herblore_zamorak_mix") { + columnRSCM(COL_TWO_DOSE_POTION, "items.2dosepotionofzamorak") + columnRSCM(COL_MIX_INGREDIENT, "items.brut_caviar") + column(COL_MIX_LEVEL, 78) + column(COL_MIX_XP, 0) + columnRSCM(COL_BARBARIAN_MIX, "items.brutal_2dosepotionofzamorak") + } + } + + /** + * Table for creating swamp tar + */ + fun swampTar() = dbTable("tables.herblore_swamp_tar", serverOnly = true) { + column("herb", COL_TAR_HERB, VarType.OBJ) + column("level", COL_TAR_LEVEL, VarType.INT) + column("xp", COL_TAR_XP, VarType.INT) + column("finished_tar", COL_TAR_FINISHED, VarType.OBJ) + + // Guam tar (Guam leaf + 15x swamp tar = 15x green tar) + row("dbrows.herblore_guam_tar") { + columnRSCM(COL_TAR_HERB, "items.guam_leaf") + column(COL_TAR_LEVEL, 19) + column(COL_TAR_XP, 30) + columnRSCM(COL_TAR_FINISHED, "items.salamander_tar_green") + } + + // Marrentill tar (Marrentill + 15x swamp tar = 15x orange tar) + row("dbrows.herblore_marrentill_tar") { + columnRSCM(COL_TAR_HERB, "items.marentill") + column(COL_TAR_LEVEL, 31) + column(COL_TAR_XP, 42) + columnRSCM(COL_TAR_FINISHED, "items.salamander_tar_orange") + } + + // Tarromin tar (Tarromin + 15x swamp tar = 15x red tar) + row("dbrows.herblore_tarromin_tar") { + columnRSCM(COL_TAR_HERB, "items.tarromin") + column(COL_TAR_LEVEL, 39) + column(COL_TAR_XP, 55) + columnRSCM(COL_TAR_FINISHED, "items.salamander_tar_red") + } + + // Harralander tar (Harralander + 15x swamp tar = 15x black tar) + row("dbrows.herblore_harralander_tar") { + columnRSCM(COL_TAR_HERB, "items.harralander") + column(COL_TAR_LEVEL, 44) + column(COL_TAR_XP, 72) + columnRSCM(COL_TAR_FINISHED, "items.salamander_tar_black") + } + + // Irit tar (Irit leaf + 15x swamp tar = 15x mountain tar) + row("dbrows.herblore_irit_tar") { + columnRSCM(COL_TAR_HERB, "items.irit_leaf") + column(COL_TAR_LEVEL, 50) + column(COL_TAR_XP, 84) + columnRSCM(COL_TAR_FINISHED, "items.salamander_tar_mountain") + } + } + + /** + * Table for crushing items with pestle and mortar + * Auto-crushes every 3 ticks when multiple items are available + */ + fun crushing() = dbTable("tables.herblore_crushing", serverOnly = true) { + column("item", COL_CRUSH_ITEM, VarType.OBJ) + column("level", COL_CRUSH_LEVEL, VarType.INT) + column("xp", COL_CRUSH_XP, VarType.INT) + column("crushed_item", COL_CRUSHED_ITEM, VarType.OBJ) + + // Bird nest (empty) → Crushed bird nest + row("dbrows.herblore_crush_bird_nest") { + columnRSCM(COL_CRUSH_ITEM, "items.bird_nest_empty") + column(COL_CRUSH_LEVEL, 1) + column(COL_CRUSH_XP, 0) + columnRSCM(COL_CRUSHED_ITEM, "items.crushed_bird_nest") + } + + // Chocolate bar → Chocolate dust + row("dbrows.herblore_crush_chocolate") { + columnRSCM(COL_CRUSH_ITEM, "items.chocolate_bar") + column(COL_CRUSH_LEVEL, 1) + column(COL_CRUSH_XP, 0) + columnRSCM(COL_CRUSHED_ITEM, "items.chocolate_dust") + } + + // Unicorn horn → Unicorn horn dust + row("dbrows.herblore_crush_unicorn_horn") { + columnRSCM(COL_CRUSH_ITEM, "items.unicorn_horn") + column(COL_CRUSH_LEVEL, 1) + column(COL_CRUSH_XP, 0) + columnRSCM(COL_CRUSHED_ITEM, "items.unicorn_horn_dust") + } + + // Blue dragon scale → Dragon scale dust + row("dbrows.herblore_crush_dragon_scale") { + columnRSCM(COL_CRUSH_ITEM, "items.blue_dragon_scale") + column(COL_CRUSH_LEVEL, 1) + column(COL_CRUSH_XP, 0) + columnRSCM(COL_CRUSHED_ITEM, "items.dragon_scale_dust") + } + + // Desert goat horn → Ground desert goat horn + row("dbrows.herblore_crush_goat_horn") { + columnRSCM(COL_CRUSH_ITEM, "items.desert_goat_horn") + column(COL_CRUSH_LEVEL, 1) + column(COL_CRUSH_XP, 0) + columnRSCM(COL_CRUSHED_ITEM, "items.ground_desert_goat_horn") + } + + // Superior dragon bones → Crushed superior dragon bones + row("dbrows.herblore_crush_superior_dragon_bones") { + columnRSCM(COL_CRUSH_ITEM, "items.dragon_bones_superior") + column(COL_CRUSH_LEVEL, 1) + column(COL_CRUSH_XP, 0) + columnRSCM(COL_CRUSHED_ITEM, "items.crushed_dragon_bones") + } + } +} + diff --git a/cache/bin/main/org/alter/impl/skills/Mining.kt b/cache/bin/main/org/alter/impl/skills/Mining.kt new file mode 100644 index 00000000..98829953 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/Mining.kt @@ -0,0 +1,503 @@ +package org.alter.impl.skills + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object Mining { + + const val COL_ROCK_OBJECT = 0 + const val COL_LEVEL = 1 + const val COL_XP = 2 + const val COL_ORE_ITEM = 3 + const val COL_RESPAWN_CYCLES = 4 + const val COL_SUCCESS_RATE_LOW = 5 + const val COL_SUCCESS_RATE_HIGH = 6 + const val COL_DEPLETE_MECHANIC = 7 + const val COL_EMPTY_ROCK = 8 + const val CLUE_BASE_CHANCE = 9 + const val COL_TYPE = 10 + const val COL_DEPLETE_MIN_AMOUNT = 11 + const val COL_DEPLETE_MAX_AMOUNT = 12 + const val COL_MINING_ENHANCERS = 13 + + val PICKAXE_DATA = mapOf( + "items.bronze_pickaxe" to Triple(1, 8, Triple("sequences.human_mining_bronze_pickaxe", "sequences.human_mining_bronze_pickaxe_wall", "dbrows.mining_bronze_pickaxe")), + "items.iron_pickaxe" to Triple(1, 7, Triple("sequences.human_mining_iron_pickaxe", "sequences.human_mining_iron_pickaxe_wall","dbrows.mining_iron_pickaxe")), + "items.steel_pickaxe" to Triple(6, 6, Triple("sequences.human_mining_steel_pickaxe", "sequences.human_mining_steel_pickaxe_wall", "dbrows.mining_steel_pickaxe")), + "items.black_pickaxe" to Triple(11, 5, Triple("sequences.human_mining_black_pickaxe", "sequences.human_mining_black_pickaxe_wall", "dbrows.mining_black_pickaxe")), + "items.mithril_pickaxe" to Triple(21, 5, Triple("sequences.human_mining_mithril_pickaxe","sequences.human_mining_mithril_pickaxe_wall", "dbrows.mining_mithril_pickaxe")), + "items.adamant_pickaxe" to Triple(31, 4, Triple("sequences.human_mining_adamant_pickaxe","sequences.human_mining_adamant_pickaxe_wall", "dbrows.mining_adamant_pickaxe")), + "items.rune_pickaxe" to Triple(41, 3, Triple("sequences.human_mining_rune_pickaxe", "sequences.human_mining_rune_pickaxe_wall", "dbrows.mining_rune_pickaxe")), + "items.dragon_pickaxe" to Triple(61, 2, Triple("sequences.human_mining_dragon_pickaxe", "sequences.human_mining_dragon_pickaxe_wall", "dbrows.mining_dragon_pickaxe")), + "items.3a_pickaxe" to Triple(61, 2, Triple("sequences.human_mining_3a_pickaxe", "sequences.human_mining_3a_pickaxe_wall", "dbrows.mining_3a_pickaxe")), + "items.infernal_pickaxe" to Triple(61, 2, Triple("sequences.human_mining_infernal_pickaxe", "sequences.human_mining_infernal_pickaxe_wall", "dbrows.mining_infernal_pickaxe")), + "items.crystal_pickaxe" to Triple(71, 2, Triple("sequences.human_mining_crystal_pickaxe", "sequences.human_mining_crystal_pickaxe_wall", "dbrows.mining_crystal_pickaxe")) + ) + + const val ITEM = 0 + const val LEVEL = 1 + const val DELAY = 2 + const val ANIMATION = 3 + const val WALL_ANIMATION = 4 + + + fun pickaxes() = dbTable("tables.mining_pickaxes", serverOnly = true) { + column("item", ITEM, VarType.OBJ) + column("level", LEVEL, VarType.INT) + column("delay", DELAY, VarType.INT) + column("animation", ANIMATION, VarType.SEQ) + column("wall_animation", WALL_ANIMATION, VarType.SEQ) + + PICKAXE_DATA.forEach { (item, data) -> + // data = Triple(level, delay, Triple(animation, wallAnimation, dbrow)) + val (level, delay, animTriple) = data + val (animation, wallAnimation, dbrow) = animTriple + + row(dbrow) { + columnRSCM(ITEM, item) + column(LEVEL, level) + column(DELAY, delay) + columnRSCM(ANIMATION, animation) + columnRSCM(WALL_ANIMATION, wallAnimation) + } + } + } + + + fun rocks() = dbTable("tables.mining_rocks", serverOnly = true) { + + column("rock_object", COL_ROCK_OBJECT, VarType.LOC) + column("level", COL_LEVEL, VarType.INT) + column("xp", COL_XP, VarType.INT) + column("ore_item", COL_ORE_ITEM, VarType.OBJ) + column("respawn_cycles", COL_RESPAWN_CYCLES, VarType.INT) + column("success_rate_low", COL_SUCCESS_RATE_LOW, VarType.INT) + column("success_rate_high", COL_SUCCESS_RATE_HIGH, VarType.INT) + column("deplete_mechanic", COL_DEPLETE_MECHANIC, VarType.INT) + column("empty_rock_object", COL_EMPTY_ROCK, VarType.LOC) + column("clue_base_chance", CLUE_BASE_CHANCE, VarType.INT) + column("type", COL_TYPE, VarType.STRING) + column("deplete_min_amount", COL_DEPLETE_MIN_AMOUNT, VarType.INT) + column("deplete_max_amount", COL_DEPLETE_MAX_AMOUNT, VarType.INT) + column("mining_enhancers", COL_MINING_ENHANCERS, VarType.DBROW) + + // Clayrocks (level 1) + row("dbrows.mining_clayrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.clayrock1", "objects.clayrock2") + column(COL_LEVEL, 1) + column(COL_XP, 5) + columnRSCM(COL_ORE_ITEM, "items.clay") + column(COL_RESPAWN_CYCLES, 2) + column(COL_SUCCESS_RATE_LOW, 128) + column(COL_SUCCESS_RATE_HIGH, 400) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 741600) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_clay") + } + // Copper (level 1) + row("dbrows.mining_copperrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.copperrock1", "objects.copperrock2") + column(COL_LEVEL, 1) + column(COL_XP, 17.5) + columnRSCM(COL_ORE_ITEM, "items.copper_ore") + column(COL_RESPAWN_CYCLES, 4) + column(COL_SUCCESS_RATE_LOW, 100) + column(COL_SUCCESS_RATE_HIGH, 350) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 741600) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_copper") + } + // Tin (level 1) + row("dbrows.mining_tinrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.tinrock1", "objects.tinrock2") + column(COL_LEVEL, 1) + column(COL_XP, 17.5) + columnRSCM(COL_ORE_ITEM, "items.tin_ore") + column(COL_RESPAWN_CYCLES, 4) + column(COL_SUCCESS_RATE_LOW, 100) + column(COL_SUCCESS_RATE_HIGH, 350) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 741600) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_tin") + } + // Blurite (level 10) + row("dbrows.mining_bluriterock") { + columnRSCM(COL_ROCK_OBJECT, "objects.blurite_rock_1", "objects.blurite_rock_2") + column(COL_LEVEL, 10) + column(COL_XP, 17.5) + columnRSCM(COL_ORE_ITEM, "items.blurite_ore") + column(COL_RESPAWN_CYCLES, 42) + column(COL_SUCCESS_RATE_LOW, 90) + column(COL_SUCCESS_RATE_HIGH, 350) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 741600) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_blurite") + } + // Iron (level 15) + row("dbrows.mining_ironrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.ironrock1", "objects.ironrock2") + column(COL_LEVEL, 15) + column(COL_XP, 35) + columnRSCM(COL_ORE_ITEM, "items.iron_ore") + column(COL_RESPAWN_CYCLES, 9) + column(COL_SUCCESS_RATE_LOW, 96) + column(COL_SUCCESS_RATE_HIGH, 350) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 741600) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_iron") + } + // Silver (level 20) + row("dbrows.mining_silverrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.silverrock1", "objects.silverrock2") + column(COL_LEVEL, 20) + column(COL_XP, 40) + columnRSCM(COL_ORE_ITEM, "items.silver_ore") + column(COL_RESPAWN_CYCLES, 100) + column(COL_SUCCESS_RATE_LOW, 25) + column(COL_SUCCESS_RATE_HIGH, 200) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 741600) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_silver") + } + // Lead (level 25) + row("dbrows.mining_leadrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.leadrock1", "objects.poh_deadman_rugcorner") //Temp. added poh_deadman_rugcorner cause it cant read only one object. + column(COL_LEVEL, 25) + column(COL_XP, 40.5) + columnRSCM(COL_ORE_ITEM, "items.lead_ore") + column(COL_RESPAWN_CYCLES, 10) + column(COL_SUCCESS_RATE_LOW, 110) + column(COL_SUCCESS_RATE_HIGH, 255) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.leadrock1_empty") + column(CLUE_BASE_CHANCE, 290641) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_lead") + } + // Coal (level 30) + row("dbrows.mining_coalrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.coalrock1", "objects.coalrock2") + column(COL_LEVEL, 30) + column(COL_XP, 50) + columnRSCM(COL_ORE_ITEM, "items.coal") + column(COL_RESPAWN_CYCLES, 50) + column(COL_SUCCESS_RATE_LOW, 16) + column(COL_SUCCESS_RATE_HIGH, 100) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 290640) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_coal") + } + // Gem (level 40) + row("dbrows.mining_gemrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.gemrock1", "objects.gemrock") + column(COL_LEVEL, 40) + column(COL_XP, 65) + column(COL_RESPAWN_CYCLES, 99) + column(COL_SUCCESS_RATE_LOW, 28) + column(COL_SUCCESS_RATE_HIGH, 70) + column(COL_DEPLETE_MECHANIC, 2) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks1") + column(CLUE_BASE_CHANCE, 211866) + column(COL_TYPE, "gemrock") + column(COL_DEPLETE_MIN_AMOUNT, 3) + column(COL_DEPLETE_MAX_AMOUNT, 3) + } + //Gold (level 40) + row("dbrows.mining_goldrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.goldrock1", "objects.goldrock2") + column(COL_LEVEL, 40) + column(COL_XP, 65) + columnRSCM(COL_ORE_ITEM, "items.gold_ore") + column(COL_RESPAWN_CYCLES, 100) + column(COL_SUCCESS_RATE_LOW, 7) + column(COL_SUCCESS_RATE_HIGH, 75) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 296640) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_gold") + } + //Mith (level 55) + row("dbrows.mining_mithrilrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.mithrilrock1", "objects.mithrilrock2") + column(COL_LEVEL, 55) + column(COL_XP, 80) + columnRSCM(COL_ORE_ITEM, "items.mithril_ore") + column(COL_RESPAWN_CYCLES, 200) + column(COL_SUCCESS_RATE_LOW, 4) + column(COL_SUCCESS_RATE_HIGH, 50) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 148320) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_mithril") + } + //Lovakite (level 65) + row("dbrows.mining_lovakiterock") { + columnRSCM(COL_ROCK_OBJECT, "objects.lovakite_rock1", "objects.lovakite_rock2") + column(COL_LEVEL, 65) + column(COL_XP, 60) + columnRSCM(COL_ORE_ITEM, "items.lovakite_ore") + column(COL_RESPAWN_CYCLES, 59) + column(COL_SUCCESS_RATE_LOW, 2) + column(COL_SUCCESS_RATE_HIGH, 50) + column(COL_DEPLETE_MECHANIC, 1) + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 245562) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_lovakite") + } + //Addy (Level 70) + row("dbrows.mining_adamantiterock") { + columnRSCM(COL_ROCK_OBJECT, "objects.adamantiterock1", "objects.adamantiterock2") + column(COL_LEVEL, 70) + column(COL_XP, 95) + columnRSCM(COL_ORE_ITEM, "items.adamantite_ore") + column(COL_RESPAWN_CYCLES, 400) + column(COL_SUCCESS_RATE_LOW, 2) + column(COL_SUCCESS_RATE_HIGH, 25) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 59328) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_adamantite") + } + //Nickel (Level 74) + row("dbrows.mining_nickelrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.nickelrock1", "objects.deadman_final_wallsupport")//Temp. added deadman_final_wallsupport cause it cant read only one object. + column(COL_LEVEL, 70) + column(COL_XP, 95) + columnRSCM(COL_ORE_ITEM, "items.nickel_ore") + column(COL_RESPAWN_CYCLES, 200) + column(COL_SUCCESS_RATE_LOW, -1) + column(COL_SUCCESS_RATE_HIGH, 25) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.nickelrock1_empty") + column(CLUE_BASE_CHANCE, 59328) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_nickel") + } + row("dbrows.mining_runiterock") { + columnRSCM(COL_ROCK_OBJECT, "objects.runiterock1", "objects.runiterock2") + column(COL_LEVEL, 85) + column(COL_XP, 125) + columnRSCM(COL_ORE_ITEM, "items.runite_ore") + column(COL_RESPAWN_CYCLES, 312) + column(COL_SUCCESS_RATE_LOW, 1) + column(COL_SUCCESS_RATE_HIGH, 18) + column(COL_DEPLETE_MECHANIC, 1) // Normal + columnRSCM(COL_EMPTY_ROCK, "objects.rocks2") + column(CLUE_BASE_CHANCE, 42377) + column(COL_TYPE, "rock") + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_runite") + + } + // Amethystrock (level 92 + row("dbrows.mining_amethystrock") { + columnRSCM(COL_ROCK_OBJECT, "objects.amethystrock1", "objects.amethystrock2") + column(COL_LEVEL, 92) + column(COL_XP, 240) + columnRSCM(COL_ORE_ITEM, "items.amethyst") + column(COL_RESPAWN_CYCLES, 125) + column(COL_SUCCESS_RATE_LOW, -18) + column(COL_SUCCESS_RATE_HIGH, 10) + column(COL_DEPLETE_MECHANIC, 2) + columnRSCM(COL_EMPTY_ROCK, "objects.amethystrock_empty") + column(CLUE_BASE_CHANCE, 46350) + column(COL_TYPE, "wall") + column(COL_DEPLETE_MIN_AMOUNT, 2) + column(COL_DEPLETE_MAX_AMOUNT, 3) + columnRSCM(COL_MINING_ENHANCERS,"dbrows.mining_enhancers_amethyst") + } + // Essence (level ) + row("dbrows.mining_essence") { + columnRSCM(COL_ROCK_OBJECT, "objects.blankrunestone") + column(COL_LEVEL, 1) + column(COL_XP, 5) + columnRSCM(COL_ORE_ITEM, "items.blankrune") + column(COL_RESPAWN_CYCLES, 0) + column(COL_SUCCESS_RATE_LOW, 256) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DEPLETE_MECHANIC, 3) // Always + column(CLUE_BASE_CHANCE, 317647) + column(COL_TYPE, "wall") + } + } + + + const val MINING_GLOVES = 0 + const val VARROCK_ARMOUR = 1 + const val RINGORSIGNET = 2 + const val MINING_CAPE = 3 + + fun miningEnhancers() = dbTable("tables.mining_enhancers", serverOnly = true) { + column("mining_gloves", MINING_GLOVES, VarType.STRING) + column("varrock_armour_level", VARROCK_ARMOUR, VarType.INT) + column("ring_or_signet", RINGORSIGNET, VarType.BOOLEAN) + column("mining_cape", MINING_CAPE, VarType.BOOLEAN) + + row("dbrows.mining_enhancers_clay") { + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_copper") { + column(MINING_GLOVES, "standard") + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_tin") { + column(MINING_GLOVES, "standard") + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_guardian_remains") { + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_tephra") { + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_blurite") { + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_limestone") { + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_iron") { + column(MINING_GLOVES, "standard") + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_silver") { + column(MINING_GLOVES, "standard") + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_lead") { + column(MINING_GLOVES, "standard") + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_coal") { + column(MINING_GLOVES, "standard") + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_sandstone") { + column(MINING_GLOVES, "expert") + column(VARROCK_ARMOUR, 2) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_gold") { + column(MINING_GLOVES, "standard") + column(VARROCK_ARMOUR, 1) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_volcanic_sulphur") { + column(MINING_CAPE, true) + column(RINGORSIGNET, true) + } + + row("dbrows.mining_enhancers_granite") { + column(VARROCK_ARMOUR, 2) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_mithril") { + column(MINING_GLOVES, "superior") + column(VARROCK_ARMOUR, 2) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_lovakite") { + column(VARROCK_ARMOUR, 2) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_amalgamation") { + column(VARROCK_ARMOUR, 3) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_adamantite") { + column(MINING_GLOVES, "superior") + column(VARROCK_ARMOUR, 3) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_soft_clay") { + column(VARROCK_ARMOUR, 2) + column(RINGORSIGNET, true) + column(MINING_CAPE, true) + } + + row("dbrows.mining_enhancers_nickel") { + column(VARROCK_ARMOUR, 4) + column(RINGORSIGNET, false) + column(MINING_CAPE, false) + } + + row("dbrows.mining_enhancers_runite") { + column(MINING_GLOVES, "expert") + column(VARROCK_ARMOUR, 4) + column(RINGORSIGNET, false) + column(MINING_CAPE, false) + } + + row("dbrows.mining_enhancers_amethyst") { + column(MINING_GLOVES, "expert") + column(VARROCK_ARMOUR, 4) + column(RINGORSIGNET, false) + column(MINING_CAPE, false) + } + } +} + diff --git a/cache/bin/main/org/alter/impl/skills/PrayerTable.kt b/cache/bin/main/org/alter/impl/skills/PrayerTable.kt new file mode 100644 index 00000000..d6a72858 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/PrayerTable.kt @@ -0,0 +1,230 @@ +package org.alter.impl.skills + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object PrayerTable { + + fun skillTable() = dbTable("tables.skill_prayer", serverOnly = true) { + + column("item", 0, VarType.OBJ) + column("exp", 1, VarType.INT) + column("ashes", 2, VarType.BOOLEAN) + + row("dbrows.bones") { + columnRSCM(0, "items.bones") + column(1, 5) + column(2, false) + } + + row("dbrows.wolfbones") { + columnRSCM(0, "items.wolf_bones") + column(1, 5) + column(2, false) + } + + row("dbrows.burntbones") { + columnRSCM(0, "items.bones_burnt") + column(1, 5) + column(2, false) + } + + row("dbrows.monkeybones") { + columnRSCM(0, "items.mm_normal_monkey_bones") + column(1, 5) + column(2, false) + } + + row("dbrows.batbones") { + columnRSCM(0, "items.bat_bones") + column(1, 5) + column(2, false) + } + + row("dbrows.bigbones") { + columnRSCM(0, "items.big_bones") + column(1, 15) + column(2, false) + } + + row("dbrows.jogrebones") { + columnRSCM(0, "items.tbwt_jogre_bones") + column(1, 15) + column(2, false) + } + + row("dbrows.wyrmlingbones") { + columnRSCM(0, "items.babywyrm_bones") + column(1, 21) + column(2, false) + } + + row("dbrows.zogrebones") { + columnRSCM(0, "items.zogre_bones") + column(1, 23) + column(2, false) + } + + row("dbrows.shaikahanbones") { + columnRSCM(0, "items.tbwt_beast_bones") + column(1, 25) + column(2, false) + } + + row("dbrows.babydragonbones") { + columnRSCM(0, "items.babydragon_bones") + column(1, 30) + column(2, false) + } + + row("dbrows.wyrmbones") { + columnRSCM(0, "items.wyrm_bones") + column(1, 50) + column(2, false) + } + + row("dbrows.wyvernbones") { + columnRSCM(0, "items.wyvern_bones") + column(1, 72) + column(2, false) + } + + row("dbrows.dragonbones") { + columnRSCM(0, "items.dragon_bones") + column(1, 72) + column(2, false) + } + + row("dbrows.drakebones") { + columnRSCM(0, "items.drake_bones") + column(1, 80) + column(2, false) + } + + row("dbrows.fayrgbones") { + columnRSCM(0, "items.zogre_ancestral_bones_fayg") + column(1, 84) + column(2, false) + } + + row("dbrows.lavadragonbones") { + columnRSCM(0, "items.lava_dragon_bones") + column(1, 85) + column(2, false) + } + + row("dbrows.raurgbones") { + columnRSCM(0, "items.zogre_ancestral_bones_raurg") + column(1, 96) + column(2, false) + } + + row("dbrows.hydrabones") { + columnRSCM(0, "items.hydra_bones") + column(1, 110) + column(2, false) + } + + row("dbrows.dagannothbones") { + columnRSCM(0, "items.dagannoth_king_bones") + column(1, 125) + column(2, false) + } + + row("dbrows.ourgbones") { + columnRSCM(0, "items.zogre_ancestral_bones_ourg") + column(1, 140) + column(2, false) + } + + row("dbrows.superiordragonbones") { + columnRSCM(0, "items.dragon_bones_superior") + column(1, 150) + column(2, false) + } + + row("dbrows.alansbones") { + columnRSCM(0, "items.alan_bones") + column(1, 3) + column(2, false) + } + + row("dbrows.bonesapeatoll") { + columnRSCM(0, "items.mm_skeleton_bones") + column(1, 3) + column(2, false) + } + + row("dbrows.bleachedbones") { + columnRSCM(0, "items.shade_bleached_bones") + column(1, 5) + column(2, false) + } + + row("dbrows.smallzombiemonkeybones") { + columnRSCM(0, "items.mm_small_zombie_monkey_bones") + column(1, 5) + column(2, false) + } + + row("dbrows.largezombiemonkeybones") { + columnRSCM(0, "items.mm_large_zombie_monkey_bones") + column(1, 5) + column(2, false) + } + + row("dbrows.smallninjamonkeybones") { + columnRSCM(0, "items.mm_small_ninja_monkey_bones") + column(1, 16) + column(2, false) + } + + row("dbrows.mediumninjamonkeybones") { + columnRSCM(0, "items.mm_medium_ninja_monkey_bones") + column(1, 18) + column(2, false) + } + + row("dbrows.gorillabones") { + columnRSCM(0, "items.mm_normal_gorilla_monkey_bones") + column(1, 18) + column(2, false) + } + + row("dbrows.beardedgorillabones") { + columnRSCM(0, "items.mm_bearded_gorilla_monkey_bones") + column(1, 18) + column(2, true) + } + + row("dbrows.fiendishashes") { + columnRSCM(0, "items.fiendish_ashes") + column(1, 10) + column(2, true) + } + + row("dbrows.vileashes") { + columnRSCM(0, "items.vile_ashes") + column(1, 25) + column(2, true) + } + + row("dbrows.maliciousashes") { + columnRSCM(0, "items.malicious_ashes") + column(1, 65) + column(2, true) + } + + row("dbrows.abyssalashes") { + columnRSCM(0, "items.abyssal_ashes") + column(1, 85) + column(2, true) + } + + row("dbrows.infernalashes") { + columnRSCM(0, "items.infernal_ashes") + column(1, 110) + column(2, true) + } + } +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/skills/Smithing.kt b/cache/bin/main/org/alter/impl/skills/Smithing.kt new file mode 100644 index 00000000..1bedb65a --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/Smithing.kt @@ -0,0 +1,465 @@ +package org.alter.impl.skills + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object Smithing { + + const val COL_OUTPUT = 0 + const val COL_LEVEL = 1 + const val COL_SMELT_XP = 2 + const val COL_SMITH_XP = 3 + const val COL_SMELT_XP_ALTERNATE = 4 + const val COL_INPUT_PRIMARY = 5 + const val COL_INPUT_SECONDARY = 6 + const val COL_INPUT_PRIMARY_AMT = 7 + const val COL_INPUT_SECONDARY_AMT = 8 + const val COL_INPUT_PREFIX = 9 + + const val COL_CANNONBALL_BAR = 0 + const val COL_CANNONBALL_OUTPUT = 1 + const val COL_CANNONBALL_LEVEL = 2 + const val COL_CANNONBALL_XP = 3 + + fun cannonBalls() = dbTable("tables.smithing_cannon_balls",serverOnly = true) { + column("bar", COL_CANNONBALL_BAR, VarType.OBJ) + column("output", COL_CANNONBALL_OUTPUT, VarType.OBJ) + column("level", COL_CANNONBALL_LEVEL, VarType.INT) + column("xp", COL_CANNONBALL_XP, VarType.INT) + + row("dbrows.bronze_cannon_ball") { + columnRSCM(COL_CANNONBALL_BAR,"items.bronze_bar") + columnRSCM(COL_CANNONBALL_OUTPUT,"items.bronze_cannonball") + column(COL_CANNONBALL_LEVEL,5) + column(COL_CANNONBALL_XP,9) + } + + row("dbrows.iron_cannon_ball") { + columnRSCM(COL_CANNONBALL_BAR,"items.bronze_bar") + columnRSCM(COL_CANNONBALL_OUTPUT,"items.iron_cannonball") + column(COL_CANNONBALL_LEVEL,20) + column(COL_CANNONBALL_XP,17) + } + + row("dbrows.steel_cannon_ball") { + columnRSCM(COL_CANNONBALL_BAR,"items.steel_bar") + columnRSCM(COL_CANNONBALL_OUTPUT,"items.mcannonball") + column(COL_CANNONBALL_LEVEL,35) + column(COL_CANNONBALL_XP,27) + } + + row("dbrows.mithril_cannon_ball") { + columnRSCM(COL_CANNONBALL_BAR,"items.mithril_bar") + columnRSCM(COL_CANNONBALL_OUTPUT,"items.mithril_cannonball") + column(COL_CANNONBALL_LEVEL,55) + column(COL_CANNONBALL_XP,34) + } + + row("dbrows.adamantite_cannon_ball") { + columnRSCM(COL_CANNONBALL_BAR,"items.adamantite_bar") + columnRSCM(COL_CANNONBALL_OUTPUT,"items.adamant_cannonball") + column(COL_CANNONBALL_LEVEL,75) + column(COL_CANNONBALL_XP,43) + } + + row("dbrows.runite_cannon_ball") { + columnRSCM(COL_CANNONBALL_BAR,"items.runite_bar") + columnRSCM(COL_CANNONBALL_OUTPUT,"items.rune_cannonball") + column(COL_CANNONBALL_LEVEL,90) + column(COL_CANNONBALL_XP,51) + } + + + } + + const val COL_DRAGON_OUTPUT = 0 + const val COL_DRAGON_OUTPUT_AMT = 1 + const val COL_DRAGON_LEVEL = 2 + const val COL_DRAGON_XP = 3 + const val COL_DRAGON_INPUT_PRIMARY = 4 + const val COL_DRAGON_INPUT_PRIMARY_AMT = 5 + + + fun dragonForge() = dbTable("tables.smithing_dragon_forge",serverOnly = true) { + column("output", COL_DRAGON_OUTPUT, VarType.OBJ) + column("output_amt", COL_DRAGON_OUTPUT_AMT, VarType.INT) + column("level", COL_DRAGON_LEVEL, VarType.INT) + column("xp", COL_DRAGON_XP, VarType.INT) + column("input_primary", COL_DRAGON_INPUT_PRIMARY, VarType.OBJ) + column("input_primary_amt", COL_DRAGON_INPUT_PRIMARY_AMT, VarType.INT) + + row("dbrows.dragon_keel_parts") { + columnRSCM(COL_DRAGON_OUTPUT, "items.sailing_boat_keel_part_dragon") + column(COL_DRAGON_OUTPUT_AMT, 1) + column(COL_DRAGON_LEVEL, 94) + column(COL_DRAGON_XP, 700) + columnRSCM(COL_DRAGON_INPUT_PRIMARY, "items.dragon_sheet") + column(COL_DRAGON_INPUT_PRIMARY_AMT, 2) + } + + row("dbrows.dragon_key") { + columnRSCM(COL_DRAGON_OUTPUT, "items.dragonkin_key") + column(COL_DRAGON_OUTPUT_AMT, 1) + column(COL_DRAGON_LEVEL, 70) + column(COL_DRAGON_XP, 0) + column(COL_DRAGON_INPUT_PRIMARY_AMT, 1) + columnRSCM(COL_DRAGON_INPUT_PRIMARY, "items.dragonkin_key_frem","items.dragonkin_key_mory", "items.dragonkin_key_zeah","items.dragonkin_key_karam") + } + + row("dbrows.dragon_kiteshield") { + columnRSCM(COL_DRAGON_OUTPUT, "items.dragon_kiteshield") + column(COL_DRAGON_OUTPUT_AMT, 1) + column(COL_DRAGON_LEVEL, 75) + column(COL_DRAGON_XP, 1000) + columnRSCM(COL_DRAGON_INPUT_PRIMARY, "items.dragon_sq_shield","items.dragon_slice", "items.dragon_shard") + column(COL_DRAGON_INPUT_PRIMARY_AMT, 1) + } + + row("dbrows.dragon_nails") { + columnRSCM(COL_DRAGON_OUTPUT, "items.nails_dragon") + column(COL_DRAGON_OUTPUT_AMT, 15) + column(COL_DRAGON_LEVEL, 92) + column(COL_DRAGON_XP, 350) + columnRSCM(COL_DRAGON_INPUT_PRIMARY, "items.dragon_sheet") + column(COL_DRAGON_INPUT_PRIMARY_AMT, 1) + } + + row("dbrows.dragon_platebody") { + columnRSCM(COL_DRAGON_OUTPUT, "items.dragon_platebody") + column(COL_DRAGON_OUTPUT_AMT, 1) + column(COL_DRAGON_LEVEL, 90) + column(COL_DRAGON_XP, 2000) + column(COL_DRAGON_INPUT_PRIMARY_AMT, 1) + columnRSCM(COL_DRAGON_INPUT_PRIMARY, "items.dragon_chainbody","items.dragon_lump", "items.dragon_shard") + } + + row("dbrows.large_dragon_keel_parts") { + columnRSCM(COL_DRAGON_OUTPUT, "items.sailing_boat_large_keel_part_dragon") + column(COL_DRAGON_OUTPUT_AMT, 1) + column(COL_DRAGON_LEVEL, 94) + column(COL_DRAGON_XP, 500) + columnRSCM(COL_DRAGON_INPUT_PRIMARY, "items.sailing_boat_keel_part_dragon") + column(COL_DRAGON_INPUT_PRIMARY_AMT, 2) + } + } + + fun bars() = dbTable("tables.smithing_bars",serverOnly = true) { + + column("output", COL_OUTPUT, VarType.OBJ) + column("level", COL_LEVEL, VarType.INT) + column("smeltXp", COL_SMELT_XP, VarType.INT) + column("smithXp", COL_SMITH_XP, VarType.INT) + column("smithXpAlternate", COL_SMELT_XP_ALTERNATE, VarType.INT) + column("input_primary", COL_INPUT_PRIMARY, VarType.OBJ) + column("input_secondary", COL_INPUT_SECONDARY, VarType.OBJ) + column("input_primary_amt", COL_INPUT_PRIMARY_AMT, VarType.INT) + column("input_secondary_amt", COL_INPUT_SECONDARY_AMT, VarType.INT) + column("prefix", COL_INPUT_PREFIX, VarType.STRING) + + row("dbrows.bronze") { + columnRSCM(COL_OUTPUT,"items.bronze_bar") + column(COL_LEVEL,1) + column(COL_SMELT_XP,6) + column(COL_SMITH_XP,12) + columnRSCM(COL_INPUT_PRIMARY,"items.tin_ore") + columnRSCM(COL_INPUT_SECONDARY,"items.copper_ore") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_SECONDARY_AMT,1) + column(COL_INPUT_PREFIX,"bronze") + + } + + row("dbrows.blurite") { + columnRSCM(COL_OUTPUT,"items.blurite_bar") + column(COL_LEVEL,13) + column(COL_SMELT_XP,8) + column(COL_SMELT_XP_ALTERNATE,10) + column(COL_SMITH_XP,17) + columnRSCM(COL_INPUT_PRIMARY,"items.blurite_ore") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_PREFIX,"blurite") + } + + row("dbrows.iron") { + columnRSCM(COL_OUTPUT,"items.iron_bar") + column(COL_LEVEL,15) + column(COL_SMELT_XP,12) + column(COL_SMITH_XP,25) + columnRSCM(COL_INPUT_PRIMARY,"items.iron_ore") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_PREFIX,"iron") + } + + row("dbrows.silver") { + columnRSCM(COL_OUTPUT,"items.silver_bar") + column(COL_LEVEL,20) + column(COL_SMELT_XP,14) + column(COL_SMITH_XP,50) + columnRSCM(COL_INPUT_PRIMARY,"items.silver_ore") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_PREFIX,"silver") + } + + row("dbrows.lead") { + columnRSCM(COL_OUTPUT,"items.lead_bar") + column(COL_LEVEL,25) + column(COL_SMELT_XP,15) + column(COL_SMITH_XP,0) + columnRSCM(COL_INPUT_PRIMARY,"items.lead_ore") + column(COL_INPUT_PRIMARY_AMT,2) + column(COL_INPUT_PREFIX,"lead") + } + + row("dbrows.steel") { + columnRSCM(COL_OUTPUT,"items.steel_bar") + column(COL_LEVEL,30) + column(COL_SMELT_XP,17) + column(COL_SMITH_XP,37) + columnRSCM(COL_INPUT_PRIMARY,"items.iron_ore") + columnRSCM(COL_INPUT_SECONDARY,"items.coal") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_SECONDARY_AMT,2) + column(COL_INPUT_PREFIX,"steel") + } + + row("dbrows.gold") { + columnRSCM(COL_OUTPUT,"items.gold_bar") + column(COL_LEVEL,30) + column(COL_SMELT_XP,22) + column(COL_SMELT_XP_ALTERNATE,56) + column(COL_SMITH_XP,90) + columnRSCM(COL_INPUT_PRIMARY,"items.gold_ore") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_PREFIX,"gold") + } + + row("dbrows.lovakite") { + columnRSCM(COL_OUTPUT,"items.lovakite_bar") + column(COL_LEVEL,45) + column(COL_SMELT_XP,20) + column(COL_SMITH_XP,60) + columnRSCM(COL_INPUT_PRIMARY,"items.lovakite_ore") + columnRSCM(COL_INPUT_SECONDARY,"items.coal") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_SECONDARY_AMT,2) + column(COL_INPUT_PREFIX,"shayzien") + } + + row("dbrows.mithril") { + columnRSCM(COL_OUTPUT,"items.mithril_bar") + column(COL_LEVEL,50) + column(COL_SMELT_XP,30) + column(COL_SMITH_XP,50) + columnRSCM(COL_INPUT_PRIMARY,"items.mithril_ore") + columnRSCM(COL_INPUT_SECONDARY,"items.coal") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_SECONDARY_AMT,4) + column(COL_INPUT_PREFIX,"mithril") + } + + row("dbrows.adamantite") { + columnRSCM(COL_OUTPUT,"items.adamantite_bar") + column(COL_LEVEL,70) + column(COL_SMELT_XP,37) + column(COL_SMITH_XP,62) + columnRSCM(COL_INPUT_PRIMARY,"items.adamantite_ore") + columnRSCM(COL_INPUT_SECONDARY,"items.coal") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_SECONDARY_AMT,6) + column(COL_INPUT_PREFIX,"adamant") + } + + row("dbrows.cupronickel") { + columnRSCM(COL_OUTPUT,"items.cupronickel_bar") + column(COL_LEVEL,74) + column(COL_SMELT_XP,42) + column(COL_SMITH_XP,0) + columnRSCM(COL_INPUT_PRIMARY,"items.nickel_ore") + columnRSCM(COL_INPUT_SECONDARY,"items.copper_ore") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_SECONDARY_AMT,2) + column(COL_INPUT_PREFIX,"cupronickel") + } + + row("dbrows.runite") { + columnRSCM(COL_OUTPUT,"items.runite_bar") + column(COL_LEVEL,85) + column(COL_SMELT_XP,50) + column(COL_SMITH_XP,75) + columnRSCM(COL_INPUT_PRIMARY,"items.runite_ore") + columnRSCM(COL_INPUT_SECONDARY,"items.coal") + column(COL_INPUT_PRIMARY_AMT,1) + column(COL_INPUT_SECONDARY_AMT,8) + column(COL_INPUT_PREFIX,"rune") + } + } + + const val COL_CRYSTAL_OUTPUT = 0 + const val COL_CRYSTAL_XP = 1 + const val COL_CRYSTAL_LEVEL = 2 + const val COL_CRYSTAL_MATERIALS = 3 + const val COL_CRYSTAL_MATERIALS_AMT = 4 + const val COL_CRYSTAL_MATERIALS_SHORT_NAME = 5 + + fun crystalSinging() = dbTable("tables.smithing_crystal_singing",serverOnly = true) { + column("output", COL_CRYSTAL_OUTPUT, VarType.OBJ) + column("xp", COL_CRYSTAL_XP, VarType.INT) + column("level", COL_CRYSTAL_LEVEL, VarType.INT) + column("materials", COL_CRYSTAL_MATERIALS, VarType.OBJ) + column("materialsCount", COL_CRYSTAL_MATERIALS_AMT, VarType.INT) + column("shortName", COL_CRYSTAL_MATERIALS_SHORT_NAME, VarType.STRING) + + row("dbrows.crystal_celestial_signet") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.celestial_signet") + column(COL_CRYSTAL_XP, 5000) + column(COL_CRYSTAL_LEVEL, 70) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.star_dust", "items.celestial_ring", "items.elven_signet") + column(COL_CRYSTAL_MATERIALS_AMT, 100, 1000, 1, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "ring") + } + + row("dbrows.crystal_helm") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_helmet") + column(COL_CRYSTAL_XP, 2500) + column(COL_CRYSTAL_LEVEL, 70) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_armour_seed") + column(COL_CRYSTAL_MATERIALS_AMT, 50, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "helmet") + } + + row("dbrows.crystal_legs") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_platelegs") + column(COL_CRYSTAL_XP, 5000) + column(COL_CRYSTAL_LEVEL, 72) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_armour_seed") + column(COL_CRYSTAL_MATERIALS_AMT, 100, 2) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "platelegs") + } + + row("dbrows.crystal_body") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_chestplate") + column(COL_CRYSTAL_XP, 7500) + column(COL_CRYSTAL_LEVEL, 74) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_armour_seed") + column(COL_CRYSTAL_MATERIALS_AMT, 150, 3) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "platelegs") + } + + row("dbrows.crystal_axe") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_axe") + column(COL_CRYSTAL_XP, 6000) + column(COL_CRYSTAL_LEVEL, 76) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_tool_seed", "items.dragon_axe") + column(COL_CRYSTAL_MATERIALS_AMT, 120, 1, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "axe") + } + + row("dbrows.crystal_felling_axe") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_axe_2h") + column(COL_CRYSTAL_XP, 6000) + column(COL_CRYSTAL_LEVEL, 76) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_tool_seed", "items.dragon_axe_2h") + column(COL_CRYSTAL_MATERIALS_AMT, 120, 1, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "axe") + } + + row("dbrows.crystal_harpoon") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_harpoon") + column(COL_CRYSTAL_XP, 6000) + column(COL_CRYSTAL_LEVEL, 76) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_tool_seed", "items.dragon_harpoon") + column(COL_CRYSTAL_MATERIALS_AMT, 120, 1, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "harpoon") + } + + row("dbrows.crystal_pickaxe") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_pickaxe") + column(COL_CRYSTAL_XP, 6000) + column(COL_CRYSTAL_LEVEL, 76) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_tool_seed", "items.dragon_pickaxe") + column(COL_CRYSTAL_MATERIALS_AMT, 120, 1, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "pickaxe") + } + + row("dbrows.crystal_bow") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_bow") + column(COL_CRYSTAL_XP, 2000) + column(COL_CRYSTAL_LEVEL, 78) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.crystal_seed_old") + column(COL_CRYSTAL_MATERIALS_AMT, 40, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "bow") + } + + row("dbrows.crystal_halberd") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_halberd") + column(COL_CRYSTAL_XP, 2000) + column(COL_CRYSTAL_LEVEL, 78) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.crystal_seed_old") + column(COL_CRYSTAL_MATERIALS_AMT, 40, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "halberd") + } + + row("dbrows.crystal_shield") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.crystal_shield") + column(COL_CRYSTAL_XP, 2000) + column(COL_CRYSTAL_LEVEL, 78) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.crystal_seed_old") + column(COL_CRYSTAL_MATERIALS_AMT, 40, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "halberd") + } + + row("dbrows.enhanced_crystal_key") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.prif_crystal_key") + column(COL_CRYSTAL_XP, 500) + column(COL_CRYSTAL_LEVEL, 80) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.crystal_key") + column(COL_CRYSTAL_MATERIALS_AMT, 10, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "key") + } + + row("dbrows.eternal_teleport_crystal") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.prif_teleport_crystal") + column(COL_CRYSTAL_XP, 5000) + column(COL_CRYSTAL_LEVEL, 80) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_teleport_seed") + column(COL_CRYSTAL_MATERIALS_AMT, 100, 1) + } + + row("dbrows.blade_of_saeldor") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.blade_of_saeldor") + column(COL_CRYSTAL_XP, 5000) + column(COL_CRYSTAL_LEVEL, 82) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_weapon_seed_enhanced") + column(COL_CRYSTAL_MATERIALS_AMT, 100, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "saeldor") + } + + row("dbrows.bow_of_faerdhinen") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.bow_of_faerdhinen") + column(COL_CRYSTAL_XP, 5000) + column(COL_CRYSTAL_LEVEL, 82) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.prif_weapon_seed_enhanced") + column(COL_CRYSTAL_MATERIALS_AMT, 100, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "bow") + } + + row("dbrows.blade_of_saeldor_charged") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.blade_of_saeldor_infinite") + column(COL_CRYSTAL_XP, 0) + column(COL_CRYSTAL_LEVEL, 82) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.blade_of_saeldor_inactive") + column(COL_CRYSTAL_MATERIALS_AMT, 1000, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "blade") + } + + row("dbrows.bow_of_faerdhinen_charged") { + columnRSCM(COL_CRYSTAL_OUTPUT, "items.bow_of_faerdhinen_infinite") + column(COL_CRYSTAL_XP, 0) + column(COL_CRYSTAL_LEVEL, 82) + columnRSCM(COL_CRYSTAL_MATERIALS, "items.prif_crystal_shard", "items.bow_of_faerdhinen_inactive") + column(COL_CRYSTAL_MATERIALS_AMT, 2000, 1) + column(COL_CRYSTAL_MATERIALS_SHORT_NAME, "saeldor") + } + } + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/skills/Woodcutting.kt b/cache/bin/main/org/alter/impl/skills/Woodcutting.kt new file mode 100644 index 00000000..492c2965 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/Woodcutting.kt @@ -0,0 +1,307 @@ +package org.alter.impl.skills + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object Woodcutting { + + const val COL_TREE_OBJECT = 0 + const val COL_LEVEL = 1 + const val COL_XP = 2 + const val COL_LOG_ITEM = 3 + const val COL_RESPAWN_CYCLES = 4 + const val COL_SUCCESS_RATE_LOW = 5 + const val COL_SUCCESS_RATE_HIGH = 6 + const val COL_DESPAWN_TICKS = 7 + const val COL_DEPLETE_MECHANIC = 8 + const val COL_STUMP = 9 + const val CLUE_BASE_CHANCE = 10 + const val TREE_TYPE = 11 + + val AXE_DATA = mapOf( + "items.bronze_axe" to Triple(1, 4, Pair("sequences.human_woodcutting_bronze_axe", "dbrows.woodcutting_bronze_axe")), + "items.iron_axe" to Triple(1, 3, Pair("sequences.human_woodcutting_iron_axe", "dbrows.woodcutting_iron_axe")), + "items.steel_axe" to Triple(6, 3, Pair("sequences.human_woodcutting_steel_axe", "dbrows.woodcutting_steel_axe")), + "items.mithril_axe" to Triple(21, 2, Pair("sequences.human_woodcutting_mithril_axe", "dbrows.woodcutting_mithril_axe")), + "items.adamant_axe" to Triple(31, 2, Pair("sequences.human_woodcutting_adamant_axe", "dbrows.woodcutting_adamant_axe")), + "items.rune_axe" to Triple(41, 2, Pair("sequences.human_woodcutting_rune_axe", "dbrows.woodcutting_rune_axe")), + "items.dragon_axe" to Triple(61, 2, Pair("sequences.human_woodcutting_dragon_axe", "dbrows.woodcutting_dragon_axe")), + "items.3a_axe" to Triple(61, 2, Pair("sequences.human_woodcutting_3a_axe", "dbrows.woodcutting_3a_axe")), + "items.infernal_axe" to Triple(61, 2, Pair("sequences.human_woodcutting_infernal_axe", "dbrows.woodcutting_infernal_axe")), + "items.crystal_axe" to Triple(71, 2, Pair("sequences.human_woodcutting_crystal_axe", "dbrows.woodcutting_crystal_axe")), + "items.bronze_axe_2h" to Triple(1, 4, Pair("sequences.human_woodcutting_bronze_axe", "dbrows.woodcutting_bronze_axe_2h")), + "items.iron_axe_2h" to Triple(1, 3, Pair("sequences.human_woodcutting_iron_axe", "dbrows.woodcutting_iron_axe_2h")), + "items.steel_axe_2h" to Triple(6, 3, Pair("sequences.human_woodcutting_steel_axe", "dbrows.woodcutting_steel_axe_2h")), + "items.mithril_axe_2h" to Triple(21, 2, Pair("sequences.human_woodcutting_mithril_axe", "dbrows.woodcutting_mithril_axe_2h")), + "items.adamant_axe_2h" to Triple(31, 2, Pair("sequences.human_woodcutting_adamant_axe", "dbrows.woodcutting_adamant_axe_2h")), + "items.rune_axe_2h" to Triple(41, 2, Pair("sequences.human_woodcutting_rune_axe", "dbrows.woodcutting_rune_axe_2h")), + "items.dragon_axe_2h" to Triple(61, 2, Pair("sequences.human_woodcutting_dragon_axe", "dbrows.woodcutting_dragon_axe_2h")), + "items.3a_axe_2h" to Triple(61, 2, Pair("sequences.human_woodcutting_3a_axe", "dbrows.woodcutting_3a_axe_2h")) + ) + + const val ITEM = 0 + const val LEVEL = 1 + const val DELAY = 2 + const val ANIMATION = 3 + + + fun axes() = dbTable("tables.woodcutting_axes", serverOnly = true) { + column("item", ITEM, VarType.OBJ) + column("level", LEVEL, VarType.INT) + column("delay", DELAY, VarType.INT) + column("animation", ANIMATION, VarType.SEQ) + + AXE_DATA.forEach { + row(it.value.third.second) { + columnRSCM(ITEM,it.key) + column(LEVEL,it.value.first) + column(DELAY,it.value.second) + columnRSCM(ANIMATION,it.value.third.first) + } + } + + } + + fun trees() = dbTable("tables.woodcutting_trees", serverOnly = true) { + + column("tree_object", COL_TREE_OBJECT, VarType.LOC) + column("level", COL_LEVEL, VarType.INT) + column("xp", COL_XP, VarType.INT) + column("log_item", COL_LOG_ITEM, VarType.OBJ) + column("respawn_cycles", COL_RESPAWN_CYCLES, VarType.INT) + column("success_rate_low", COL_SUCCESS_RATE_LOW, VarType.INT) + column("success_rate_high", COL_SUCCESS_RATE_HIGH, VarType.INT) + column("despawn_ticks", COL_DESPAWN_TICKS, VarType.INT) + column("deplete_mechanic", COL_DEPLETE_MECHANIC, VarType.INT) + column("stump_object", COL_STUMP, VarType.LOC) + column("clue_base_chance", CLUE_BASE_CHANCE, VarType.INT) + column("tree_type", TREE_TYPE, VarType.STRING) + + // Regular trees (level 1) + row("dbrows.woodcutting_regular_tree") { + columnRSCM(COL_TREE_OBJECT, + "objects.tree", "objects.lighttree", + "objects.tree2", "objects.tree3", + "objects.tree4", "objects.tree5", + "objects.lighttree2", "objects.evergreen", + "objects.evergreen_large", "objects.jungletree1", + "objects.jungletree2", "objects.jungletree1_karamja", + "objects.jungletree2_karamja", "objects.achey_tree", + "objects.hollowtree", "objects.hollow_tree", + "objects.hollow_tree_big", "objects.arctic_pine", + "objects.arctic_pine_snowy", "objects.deadtree1", + "objects.deadtree1_large", "objects.lightdeadtree1", + "objects.deadtree2", "objects.deadtree2_web_r", + "objects.deadtree2_web_l", "objects.deadtree2_dark", + "objects.deadtree3", "objects.deadtree2_snowy", + "objects.deadtree_with_vine", "objects.deadtree2_swamp", + "objects.deadtree4", "objects.deadtree6", + "objects.deadtree_burnt", "objects.deadtree4swamp", + "objects.deadtree3_snowy" + ) + column(COL_LEVEL, 1) + column(COL_XP, 25) + columnRSCM(COL_LOG_ITEM, "items.logs") + column(COL_RESPAWN_CYCLES, 60) + column(COL_SUCCESS_RATE_LOW, 64) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 0) + column(COL_DEPLETE_MECHANIC, 0) // Always + columnRSCM(COL_STUMP, "objects.treestump") + column(CLUE_BASE_CHANCE, 317647) + column(TREE_TYPE, "normal_tree") + } + + // Oak trees + row("dbrows.woodcutting_oak_tree") { + columnRSCM(COL_TREE_OBJECT, + "objects.oaktree", + "objects.oaktree", "objects.oak_tree_1", + "objects.oak_tree_2", "objects.oak_tree_3", + "objects.oak_tree_3_top", "objects.oak_tree_fullygrown_1", + "objects.oak_tree_fullygrown_2" + ) + column(COL_LEVEL, 15) + column(COL_XP, 37) + columnRSCM(COL_LOG_ITEM, "items.oak_logs") + column(COL_RESPAWN_CYCLES, 60) + column(COL_SUCCESS_RATE_LOW, 64) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 45) + column(COL_DEPLETE_MECHANIC, 1) // Countdown + columnRSCM(COL_STUMP, "objects.oaktree_stump") + column(CLUE_BASE_CHANCE, 361146) + column(TREE_TYPE, "oak_tree") + } + + // Willow trees + row("dbrows.woodcutting_willow_tree") { + columnRSCM(COL_TREE_OBJECT, + "objects.willowtree", "objects.willow_tree_1", + "objects.willow_tree_2", "objects.willow_tree_3", + "objects.willow_tree_4", "objects.willow_tree_5", + "objects.willow_tree_fullygrown_1", "objects.willow_tree_fullygrown_2", + "objects.willow_tree2", "objects.willow_tree3", + "objects.willow_tree4" + ) + column(COL_LEVEL, 30) + column(COL_XP, 67) + columnRSCM(COL_LOG_ITEM, "items.willow_logs") + column(COL_RESPAWN_CYCLES, 100) + column(COL_SUCCESS_RATE_LOW, 32) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 50) + column(COL_DEPLETE_MECHANIC, 1) // Countdown + columnRSCM(COL_STUMP, "objects.willow_tree_stump_new") + column(CLUE_BASE_CHANCE, 289286) + column(TREE_TYPE, "willow_tree") + } + + // Teak trees + row("dbrows.woodcutting_teak_tree") { + columnRSCM(COL_TREE_OBJECT, + "objects.teaktree", "objects.teak_tree_1", + "objects.teak_tree_2", "objects.teak_tree_3", + "objects.teak_tree_4", "objects.teak_tree_5", + "objects.teak_tree_6", "objects.teak_tree_5_top", + "objects.teak_tree_6_top", "objects.teak_tree_fullygrown", + "objects.teak_tree_fullygrown_top" + ) + column(COL_LEVEL, 35) + column(COL_XP, 85) + columnRSCM(COL_LOG_ITEM, "items.teak_logs") + column(COL_RESPAWN_CYCLES, 100) + column(COL_SUCCESS_RATE_LOW, 20) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 50) + column(COL_DEPLETE_MECHANIC, 1) // Countdown + columnRSCM(COL_STUMP, "objects.teak_tree_stump") + column(CLUE_BASE_CHANCE, 264336) + column(TREE_TYPE, "teak_tree") + } + + // Juniper trees + row("dbrows.woodcutting_juniper_tree") { + columnRSCM(COL_TREE_OBJECT, "objects.mature_juniper_tree") + column(COL_LEVEL, 42) + column(COL_XP, 35) + columnRSCM(COL_LOG_ITEM, "items.juniper_logs") + column(COL_RESPAWN_CYCLES, 100) + column(COL_SUCCESS_RATE_LOW, 18) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 50) + column(COL_DEPLETE_MECHANIC, 1) // Countdown + columnRSCM(COL_STUMP, "objects.mature_juniper_tree_stump") + column(CLUE_BASE_CHANCE, 360000) + column(TREE_TYPE, "juniper_tree") + } + + // Maple trees + row("dbrows.woodcutting_maple_tree") { + columnRSCM(COL_TREE_OBJECT, + "objects.mapletree", "objects.maple_tree_1", + "objects.maple_tree_2", "objects.maple_tree_3", + "objects.maple_tree_4", "objects.maple_tree_5", + "objects.maple_tree_6", "objects.maple_tree_7", + "objects.maple_tree_fullygrown_1", "objects.maple_tree_fullygrown_2" + ) + column(COL_LEVEL, 45) + column(COL_XP, 100) + columnRSCM(COL_LOG_ITEM, "items.maple_logs") + column(COL_RESPAWN_CYCLES, 100) + column(COL_SUCCESS_RATE_LOW, 16) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 100) + column(COL_DEPLETE_MECHANIC, 1) // Countdown + columnRSCM(COL_STUMP, "objects.maple_tree_stump_new") + column(CLUE_BASE_CHANCE, 221918) + column(TREE_TYPE, "maple_tree") + } + + // Mahogany trees + row("dbrows.woodcutting_mahogany_tree") { + columnRSCM(COL_TREE_OBJECT, + "objects.mahoganytree", "objects.mahogany_tree_1", + "objects.mahogany_tree_2", "objects.mahogany_tree_3", + "objects.mahogany_tree_4", "objects.mahogany_tree_5", + "objects.mahogany_tree_6", "objects.mahogany_tree_7", + "objects.mahogany_tree_8", "objects.mahogany_tree_9", + "objects.mahogany_tree_fullygrown" + ) + column(COL_LEVEL, 50) + column(COL_XP, 125) + columnRSCM(COL_LOG_ITEM, "items.mahogany_logs") + column(COL_RESPAWN_CYCLES, 120) + column(COL_SUCCESS_RATE_LOW, 12) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 100) + column(COL_DEPLETE_MECHANIC, 1) // Countdown + columnRSCM(COL_STUMP, "objects.mahogany_tree_stump") + column(CLUE_BASE_CHANCE, 220623) + column(TREE_TYPE, "mahogany_tree") + } + + // Yew trees + row("dbrows.woodcutting_yew_tree") { + columnRSCM(COL_TREE_OBJECT, + "objects.yewtree", "objects.yew_tree_1", + "objects.yew_tree_2", "objects.yew_tree_3", + "objects.yew_tree_4", "objects.yew_tree_5", + "objects.yew_tree_6", "objects.yew_tree_7", + "objects.yew_tree_8", "objects.yew_tree_9", + "objects.yew_tree_fullygrown_1", "objects.yew_tree_fullygrown_2" + ) + column(COL_LEVEL, 60) + column(COL_XP, 175) + columnRSCM(COL_LOG_ITEM, "items.yew_logs") + column(COL_RESPAWN_CYCLES, 120) + column(COL_SUCCESS_RATE_LOW, 8) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 190) + column(COL_DEPLETE_MECHANIC, 1) // Countdown + columnRSCM(COL_STUMP, "objects.yew_tree_stump_new") + column(CLUE_BASE_CHANCE, 145013) + column(TREE_TYPE, "yew_tree") + } + + // Magic trees + row("dbrows.woodcutting_magic_tree") { + columnRSCM(COL_TREE_OBJECT, + "objects.magictree", "objects.magic_tree_1", + "objects.magic_tree_2", "objects.magic_tree_3", + "objects.magic_tree_4", "objects.magic_tree_5", + "objects.magic_tree_6", "objects.magic_tree_7", + "objects.magic_tree_8", "objects.magic_tree_9", + "objects.magic_tree_10", "objects.magic_tree_11", + "objects.magic_tree_fullygrown_1", "objects.magic_tree_fullygrown_2" + ) + column(COL_LEVEL, 75) + column(COL_XP, 250) + columnRSCM(COL_LOG_ITEM, "items.magic_logs") + column(COL_RESPAWN_CYCLES, 120) + column(COL_SUCCESS_RATE_LOW, 4) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 390) + column(COL_DEPLETE_MECHANIC, 1) // Countdown + columnRSCM(COL_STUMP, "objects.magic_tree_stump_new") + column(CLUE_BASE_CHANCE, 72321) + column(TREE_TYPE, "magic_tree") + } + + // Blisterwood trees + row("dbrows.woodcutting_blisterwood_tree") { + columnRSCM(COL_TREE_OBJECT, "objects.blisterwood_tree") + column(COL_LEVEL, 62) + column(COL_XP, 76) + columnRSCM(COL_LOG_ITEM, "items.blisterwood_logs") + column(COL_RESPAWN_CYCLES, 0) + column(COL_SUCCESS_RATE_LOW, 10) + column(COL_SUCCESS_RATE_HIGH, 256) + column(COL_DESPAWN_TICKS, 50) + column(COL_DEPLETE_MECHANIC, 1) // Countdown + column(CLUE_BASE_CHANCE, 0) + column(TREE_TYPE, "blisterwood_tree") + } + } +} + diff --git a/cache/bin/main/org/alter/impl/skills/cooking/CookingConstants.kt b/cache/bin/main/org/alter/impl/skills/cooking/CookingConstants.kt new file mode 100644 index 00000000..880f38eb --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/CookingConstants.kt @@ -0,0 +1,58 @@ +package org.alter.impl.skills.cooking + +/** + * Cooking skill constants used across recipe definitions and table builders. + */ +object CookingConstants { + + /** + * Trigger types determine how a cooking action is initiated. + */ + object Trigger { + /** Action triggered by using item on a heat source (fire/range). */ + const val HEAT_SOURCE = 0 + /** Action triggered by using one item on another in inventory. */ + const val ITEM_ON_ITEM = 1 + } + + /** + * Outcome kinds determine what type of result a cooking action produces. + */ + object OutcomeKind { + /** Successful cooking result. */ + const val SUCCESS = 0 + /** Failed cooking result (burnt). */ + const val FAIL = 1 + /** Always produced regardless of success/failure (e.g., return containers). */ + const val ALWAYS = 2 + } + + /** Default variant for single-step actions. */ + const val DEFAULT_VARIANT = 0 + + /** Station mask for fire-only cooking. */ + const val STATION_FIRE = 1 + + /** Station mask for range-only cooking. */ + const val STATION_RANGE = 2 + + /** Station mask for cooking on both fire and range. */ + const val STATION_ANY = STATION_FIRE or STATION_RANGE + + /** + * Modifier flags for burn chance profiles. + * Combined as bitmasks to represent equipment/location bonuses. + */ + object ChanceModifier { + /** No modifier (base chance). */ + const val NONE = 0 + /** Cooking gauntlets equipped. */ + const val GAUNTLETS = 1 + /** Hosidius Kitchen range (+5% burn reduction). */ + const val HOSIDIUS_5 = 2 + /** Hosidius Kitchen range with Kourend Hard Diary (+10% burn reduction). */ + const val HOSIDIUS_10 = 4 + /** Lumbridge Castle range (reduced burn for low-level foods). */ + const val LUMBRIDGE = 8 + } +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/CookingHelpers.kt b/cache/bin/main/org/alter/impl/skills/cooking/CookingHelpers.kt new file mode 100644 index 00000000..2fc839fb --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/CookingHelpers.kt @@ -0,0 +1,228 @@ +package org.alter.impl.skills.cooking + +import org.alter.impl.skills.cooking.CookingConstants.DEFAULT_VARIANT +import org.alter.impl.skills.cooking.CookingConstants.OutcomeKind +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_FIRE +import org.alter.impl.skills.cooking.CookingConstants.Trigger + +/** + * Helper functions for building cooking recipe definitions. + */ +object CookingHelpers { + + /** + * Creates a [ChanceDef] for burn chance profile definitions. + * + * @param label Human-readable label (e.g., "base_any", "gauntlets", "hosidius_5"). + * @param stationMask Bitmask of stations this profile applies to. + * @param modifierMask Bitmask of required modifiers (default: none). + * @param low The low value for the statrandom calculation. + * @param high The high value for the statrandom calculation. + */ + fun chance( + label: String, + stationMask: Int, + modifierMask: Int = 0, + low: Int, + high: Int + ): ChanceDef = ChanceDef(label, stationMask, modifierMask, low, high) + + /** + * Creates a simple heat-cooking action (raw -> cooked/burnt). + */ + fun heatCook( + rowKey: String, + raw: String, + cooked: String, + burnt: String, + level: Int, + xp: Int, + stopBurnFire: Int = 0, + stopBurnRange: Int = 0, + stationMask: Int = STATION_ANY, + chances: List = emptyList() + ): ActionDef = ActionDef( + rowId = "dbrows.$rowKey", + key = raw, + level = level, + stopBurnFire = stopBurnFire, + stopBurnRange = stopBurnRange, + stationMask = stationMask, + trigger = Trigger.HEAT_SOURCE, + inputs = listOf(InputDef(raw, 1)), + outcomes = listOf( + OutcomeDef(rowSuffix = "success", kind = OutcomeKind.SUCCESS, item = cooked, xp = xp, weight = 1), + OutcomeDef(rowSuffix = "fail", kind = OutcomeKind.FAIL, item = burnt, xp = 0, weight = 1) + ), + chances = chances + ) + + /** + * Creates a spit-roasting recipe (2 actions: skewer + roast). + * + * @param skewerRowKey DB row key for the skewering step. + * @param roastRowKey DB row key for the roasting step. + * @param rawMeat RSCM key for the raw meat. + * @param skewered RSCM key for the skewered meat. + * @param cooked RSCM key for the roasted meat. + * @param burnt RSCM key for the burnt meat. + * @param cookingLevel Required Cooking level for roasting. + * @param xp Experience awarded on successful roast. + * @param stopBurnFire Level at which burning stops on fires. + * @param stopBurnRange Level at which burning stops on ranges. + * @param spitItem RSCM key for the spit item (default: iron spit). + */ + fun spitRoast( + skewerRowKey: String, + roastRowKey: String, + rawMeat: String, + skewered: String, + cooked: String, + burnt: String, + cookingLevel: Int, + xp: Int, + stopBurnFire: Int = 0, + stopBurnRange: Int = 0, + spitItem: String = "items.spit_iron", + chances: List = emptyList() + ): List = listOf( + // Raw meat + iron spit -> skewered meat (inventory prep step) + ActionDef( + rowId = "dbrows.$skewerRowKey", + key = skewered, + variant = 1, + level = 1, + stopBurnFire = 1, + stopBurnRange = 1, + stationMask = STATION_ANY, + trigger = Trigger.ITEM_ON_ITEM, + inputs = listOf( + InputDef(rawMeat, 1), + InputDef(spitItem, 1) + ), + outcomes = listOf( + OutcomeDef(rowSuffix = "success", kind = OutcomeKind.SUCCESS, item = skewered, xp = 0, weight = 1) + ) + ), + + // Skewered meat -> roasted/burnt (fire only) + ActionDef( + rowId = "dbrows.$roastRowKey", + key = skewered, + variant = DEFAULT_VARIANT, + level = cookingLevel, + stopBurnFire = stopBurnFire, + stopBurnRange = stopBurnRange, + stationMask = STATION_FIRE, + trigger = Trigger.HEAT_SOURCE, + inputs = listOf(InputDef(skewered, 1)), + outcomes = listOf( + OutcomeDef(rowSuffix = "success", kind = OutcomeKind.SUCCESS, item = cooked, xp = xp, weight = 1), + OutcomeDef(rowSuffix = "fail", kind = OutcomeKind.FAIL, item = burnt, xp = 0, weight = 1) + ), + chances = chances + ) + ) + + /** + * Creates a multi-step cooking recipe with preparation and optional baking. + * + * @param key RSCM key identifying this recipe group. + * @param level Required Cooking level. + * @param prepSteps List of inventory preparation steps. + * @param heatStep Optional final heat-source cooking step. + * @param prepVariantStart Starting variant number for prep steps. + * @param heatVariant Variant number for the heat step. + */ + fun multiStepCook( + key: String, + level: Int, + prepSteps: List, + heatStep: HeatStepDef? = null, + prepVariantStart: Int = 1, + heatVariant: Int = DEFAULT_VARIANT + ): List = buildList { + prepSteps.forEachIndexed { index, step -> + val alwaysOutcomes = step.always.mapIndexed { alwaysIndex, (item, count) -> + OutcomeDef( + rowSuffix = "always_$alwaysIndex", + kind = OutcomeKind.ALWAYS, + item = item, + countMin = count, + countMax = count, + xp = 0, + weight = 1 + ) + } + + add( + ActionDef( + rowId = "dbrows.${step.rowKey}", + key = key, + variant = prepVariantStart + index, + level = level, + stopBurnFire = level, + stopBurnRange = level, + stationMask = STATION_ANY, + trigger = Trigger.ITEM_ON_ITEM, + inputs = step.inputs.map { (item, count) -> InputDef(item, count) }, + outcomes = listOf( + OutcomeDef( + rowSuffix = "success", + kind = OutcomeKind.SUCCESS, + item = step.output, + xp = step.xp, + weight = 1 + ) + ) + alwaysOutcomes + ) + ) + } + + heatStep?.let { step -> + val alwaysOutcomes = step.always.mapIndexed { alwaysIndex, (item, count) -> + OutcomeDef( + rowSuffix = "always_$alwaysIndex", + kind = OutcomeKind.ALWAYS, + item = item, + countMin = count, + countMax = count, + xp = 0, + weight = 1 + ) + } + + add( + ActionDef( + rowId = "dbrows.${step.rowKey}", + key = key, + variant = heatVariant, + level = level, + stopBurnFire = step.stopBurnFire, + stopBurnRange = step.stopBurnRange, + stationMask = step.stationMask, + trigger = Trigger.HEAT_SOURCE, + inputs = listOf(InputDef(step.raw, 1)), + outcomes = listOf( + OutcomeDef( + rowSuffix = "success", + kind = OutcomeKind.SUCCESS, + item = step.cooked, + xp = step.xp, + weight = 1 + ), + OutcomeDef( + rowSuffix = "fail", + kind = OutcomeKind.FAIL, + item = step.burnt, + xp = 0, + weight = 1 + ) + ) + alwaysOutcomes, + chances = step.chances + ) + ) + } + } +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/CookingModels.kt b/cache/bin/main/org/alter/impl/skills/cooking/CookingModels.kt new file mode 100644 index 00000000..40ee6df4 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/CookingModels.kt @@ -0,0 +1,129 @@ +package org.alter.impl.skills.cooking + +/** + * Data models for cooking recipe definitions. + */ + +/** + * Defines a burn chance profile for a specific station and modifier combination. + * + * At runtime, the system selects the most specific matching profile based on the + * player's station and active modifiers, then uses [low]/[high] with the + * `computeSkillingSuccess` function to determine burn probability. + * + * @property label Human-readable label for this profile (e.g., "base_any", "gauntlets"). + * @property stationMask Bitmask of stations this profile applies to. + * @property modifierMask Bitmask of required modifiers (see [CookingConstants.ChanceModifier]). + * @property low The low value for the statrandom calculation. + * @property high The high value for the statrandom calculation. + */ +data class ChanceDef( + val label: String, + val stationMask: Int, + val modifierMask: Int = 0, + val low: Int, + val high: Int +) + +/** + * Defines an input ingredient for a cooking action. + * + * @property item RSCM key for the item (e.g., "items.raw_shrimp"). + * @property count Number of this item consumed per action. + */ +data class InputDef( + val item: String, + val count: Int = 1 +) + +/** + * Defines a possible outcome of a cooking action. + * + * @property rowSuffix Unique suffix for the DB row (e.g., "success", "fail"). + * @property kind The outcome type (SUCCESS, FAIL, or ALWAYS). + * @property item RSCM key for the produced item. + * @property countMin Minimum quantity produced. + * @property countMax Maximum quantity produced. + * @property xp Experience awarded for this outcome. + * @property weight Weighted chance for this outcome when multiple exist. + */ +data class OutcomeDef( + val rowSuffix: String, + val kind: Int, + val item: String, + val countMin: Int = 1, + val countMax: Int = 1, + val xp: Int = 0, + val weight: Int = 1 +) + +/** + * Defines a complete cooking action with inputs, outcomes, and requirements. + * + * @property rowId DB row identifier (e.g., "dbrows.cooking_shrimps"). + * @property key RSCM key used to identify this action group. + * @property variant Variant number for multi-step recipes. + * @property level Required Cooking level. + * @property stopBurnFire Level at which burning stops on fires. + * @property stopBurnRange Level at which burning stops on ranges. + * @property stationMask Bitmask for allowed cooking stations. + * @property trigger How the action is initiated (HEAT_SOURCE or ITEM_ON_ITEM). + * @property inputs List of required input items. + * @property outcomes List of possible outcomes. + */ +data class ActionDef( + val rowId: String, + val key: String, + val variant: Int = CookingConstants.DEFAULT_VARIANT, + val level: Int, + val stopBurnFire: Int = 0, + val stopBurnRange: Int = 0, + val stationMask: Int = CookingConstants.STATION_ANY, + val trigger: Int = CookingConstants.Trigger.HEAT_SOURCE, + val inputs: List, + val outcomes: List, + val chances: List = emptyList() +) + +/** + * Defines a preparation step for multi-step cooking (inventory item-on-item). + * + * @property rowKey DB row key suffix. + * @property inputs List of (item RSCM key, count) pairs consumed. + * @property output RSCM key for the produced item. + * @property xp Experience awarded. + * @property always Items always returned (e.g., empty containers). + */ +data class PrepStepDef( + val rowKey: String, + val inputs: List>, + val output: String, + val xp: Int = 0, + val always: List> = emptyList() +) + +/** + * Defines the final heat-source step for multi-step cooking. + * + * @property rowKey DB row key suffix. + * @property raw RSCM key for the uncooked item. + * @property cooked RSCM key for the successfully cooked item. + * @property burnt RSCM key for the burnt item. + * @property xp Experience awarded on success. + * @property stopBurnFire Level at which burning stops on fires. + * @property stopBurnRange Level at which burning stops on ranges. + * @property stationMask Bitmask for allowed cooking stations. + * @property always Items always returned (e.g., cake tin). + */ +data class HeatStepDef( + val rowKey: String, + val raw: String, + val cooked: String, + val burnt: String, + val xp: Int, + val stopBurnFire: Int = 0, + val stopBurnRange: Int = 0, + val stationMask: Int = CookingConstants.STATION_ANY, + val always: List> = emptyList(), + val chances: List = emptyList() +) diff --git a/cache/bin/main/org/alter/impl/skills/cooking/CookingRecipeRegistry.kt b/cache/bin/main/org/alter/impl/skills/cooking/CookingRecipeRegistry.kt new file mode 100644 index 00000000..646630e6 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/CookingRecipeRegistry.kt @@ -0,0 +1,36 @@ +package org.alter.impl.skills.cooking + +import org.alter.impl.skills.cooking.recipes.BakedGoodsRecipes +import org.alter.impl.skills.cooking.recipes.BreadRecipes +import org.alter.impl.skills.cooking.recipes.FishRecipes +import org.alter.impl.skills.cooking.recipes.MeatRecipes +import org.alter.impl.skills.cooking.recipes.MiscFoodRecipes +import org.alter.impl.skills.cooking.recipes.PieRecipes +import org.alter.impl.skills.cooking.recipes.PizzaRecipes +import org.alter.impl.skills.cooking.recipes.PotatoRecipes +import org.alter.impl.skills.cooking.recipes.StewRecipes +import org.alter.impl.skills.cooking.recipes.WineRecipes + +/** + * Central registry of all cooking recipes. + * + * Add new recipe categories here as they are implemented. + */ +object CookingRecipeRegistry { + + /** + * All cooking action definitions from all recipe categories. + */ + val allRecipes: List by lazy { + FishRecipes.recipes + + MeatRecipes.recipes + + BakedGoodsRecipes.recipes + + BreadRecipes.recipes + + PieRecipes.recipes + + PizzaRecipes.recipes + + StewRecipes.recipes + + PotatoRecipes.recipes + + MiscFoodRecipes.recipes + + WineRecipes.recipes + } +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/CookingTables.kt b/cache/bin/main/org/alter/impl/skills/cooking/CookingTables.kt new file mode 100644 index 00000000..71a3caaa --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/CookingTables.kt @@ -0,0 +1,110 @@ +package org.alter.impl.skills.cooking + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +/** + * DB table builders for cooking skill data. + * + * These functions generate the cache tables used by the game client and server + * to look up cooking recipes, inputs, and outcomes. + */ +object CookingTables { + + private const val ACTION_KEY = 0 + private const val ACTION_VARIANT = 1 + private const val ACTION_LEVEL = 2 + private const val ACTION_STOP_BURN_FIRE = 3 + private const val ACTION_STOP_BURN_RANGE = 4 + private const val ACTION_STATION_MASK = 5 + private const val ACTION_TRIGGER = 6 + + /** + * Builds the cooking_actions DB table containing action metadata. + */ + fun actions() = dbTable("tables.cooking_actions") { + column("key", ACTION_KEY, VarType.OBJ) + column("variant", ACTION_VARIANT, VarType.INT) + column("level", ACTION_LEVEL, VarType.INT) + column("stop_burn_fire", ACTION_STOP_BURN_FIRE, VarType.INT) + column("stop_burn_range", ACTION_STOP_BURN_RANGE, VarType.INT) + column("station_mask", ACTION_STATION_MASK, VarType.INT) + column("trigger", ACTION_TRIGGER, VarType.INT) + + CookingRecipeRegistry.allRecipes.forEach { action -> + row(action.rowId) { + columnRSCM(ACTION_KEY, action.key) + column(ACTION_VARIANT, action.variant) + column(ACTION_LEVEL, action.level) + column(ACTION_STOP_BURN_FIRE, action.stopBurnFire) + column(ACTION_STOP_BURN_RANGE, action.stopBurnRange) + column(ACTION_STATION_MASK, action.stationMask) + column(ACTION_TRIGGER, action.trigger) + } + } + } + + private const val INPUT_KEY = 0 + private const val INPUT_VARIANT = 1 + private const val INPUT_ITEM = 2 + private const val INPUT_COUNT = 3 + + /** + * Builds the cooking_action_inputs DB table containing input requirements. + */ + fun actionInputs() = dbTable("tables.cooking_action_inputs") { + column("key", INPUT_KEY, VarType.OBJ) + column("variant", INPUT_VARIANT, VarType.INT) + column("item", INPUT_ITEM, VarType.OBJ) + column("count", INPUT_COUNT, VarType.INT) + + CookingRecipeRegistry.allRecipes.forEach { action -> + action.inputs.forEachIndexed { index, input -> + row("${action.rowId}_input_$index") { + columnRSCM(INPUT_KEY, action.key) + column(INPUT_VARIANT, action.variant) + columnRSCM(INPUT_ITEM, input.item) + column(INPUT_COUNT, input.count) + } + } + } + } + + private const val OUTCOME_KEY = 0 + private const val OUTCOME_VARIANT = 1 + private const val OUTCOME_KIND = 2 + private const val OUTCOME_ITEM = 3 + private const val OUTCOME_COUNT_MIN = 4 + private const val OUTCOME_COUNT_MAX = 5 + private const val OUTCOME_XP = 6 + private const val OUTCOME_WEIGHT = 7 + + /** + * Builds the cooking_action_outcomes DB table containing possible results. + */ + fun actionOutcomes() = dbTable("tables.cooking_action_outcomes") { + column("key", OUTCOME_KEY, VarType.OBJ) + column("variant", OUTCOME_VARIANT, VarType.INT) + column("kind", OUTCOME_KIND, VarType.INT) + column("item", OUTCOME_ITEM, VarType.OBJ) + column("count_min", OUTCOME_COUNT_MIN, VarType.INT) + column("count_max", OUTCOME_COUNT_MAX, VarType.INT) + column("xp", OUTCOME_XP, VarType.INT) + column("weight", OUTCOME_WEIGHT, VarType.INT) + + CookingRecipeRegistry.allRecipes.forEach { action -> + action.outcomes.forEach { outcome -> + row("${action.rowId}_outcome_${outcome.rowSuffix}") { + columnRSCM(OUTCOME_KEY, action.key) + column(OUTCOME_VARIANT, action.variant) + column(OUTCOME_KIND, outcome.kind) + columnRSCM(OUTCOME_ITEM, outcome.item) + column(OUTCOME_COUNT_MIN, outcome.countMin) + column(OUTCOME_COUNT_MAX, outcome.countMax) + column(OUTCOME_XP, outcome.xp) + column(OUTCOME_WEIGHT, outcome.weight) + } + } + } + } +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/BakedGoodsRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/BakedGoodsRecipes.kt new file mode 100644 index 00000000..a2298457 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/BakedGoodsRecipes.kt @@ -0,0 +1,106 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Baked goods recipes - pies, cakes, and other range-baked items. + */ +object BakedGoodsRecipes { + + /** Pie recipes (multi-step prep + range bake). */ + val pieRecipes: List = multiStepCook( + key = "items.uncooked_garden_pie", + level = 34, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_garden_pie_add_tomato", + inputs = listOf( + "items.pie_shell" to 1, + "items.tomato" to 1 + ), + output = "items.unfinished_garden_pie_1" + ), + PrepStepDef( + rowKey = "cooking_garden_pie_add_onion", + inputs = listOf( + "items.unfinished_garden_pie_1" to 1, + "items.onion" to 1 + ), + output = "items.unfinished_garden_pie_2" + ), + PrepStepDef( + rowKey = "cooking_garden_pie_add_cabbage", + inputs = listOf( + "items.unfinished_garden_pie_2" to 1, + "items.cabbage" to 1 + ), + output = "items.uncooked_garden_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_garden_pie_bake", + raw = "items.uncooked_garden_pie", + cooked = "items.garden_pie", + burnt = "items.burnt_pie", + xp = 138, + stopBurnFire = 68, + stopBurnRange = 64, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 48, high = 352), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 60, high = 364), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 73, high = 377) + ) + ) + ) + + /** Cake recipes (multi-step prep + range bake). */ + val cakeRecipes: List = multiStepCook( + key = "items.uncooked_cake", + level = 40, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_cake_mix", + inputs = listOf( + "items.cake_tin" to 1, + "items.egg" to 1, + "items.bucket_milk" to 1, + "items.pot_flour" to 1 + ), + output = "items.uncooked_cake", + always = listOf( + "items.bucket_empty" to 1, + "items.pot_empty" to 1 + ) + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_cake_bake", + raw = "items.uncooked_cake", + cooked = "items.cake", + burnt = "items.burnt_cake", + xp = 180, + stopBurnFire = 74, + stopBurnRange = 70, + stationMask = STATION_RANGE, + always = listOf( + "items.cake_tin" to 1 + ), + chances = listOf( + chance("range", STATION_RANGE, low = 38, high = 332), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357) + ) + ) + ) + + /** All baked goods recipes combined. */ + val recipes: List = pieRecipes + cakeRecipes +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/BreadRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/BreadRecipes.kt new file mode 100644 index 00000000..879233fe --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/BreadRecipes.kt @@ -0,0 +1,47 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook + +/** + * Bread cooking recipes — baked on a range. + * + * Chance profiles sourced from OSRS Wiki skill_chances data. + */ +object BreadRecipes { + + val recipes: List = listOf( + heatCook( + rowKey = "cooking_bread", + raw = "items.bread_dough", + cooked = "items.bread", + burnt = "items.burnt_bread", + level = 1, xp = 40, stopBurnFire = 35, stopBurnRange = 35, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 118, high = 492), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 128, high = 512), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 130, high = 504), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 143, high = 517) + ) + ), + heatCook( + rowKey = "cooking_pitta_bread", + raw = "items.uncooked_pitta_bread", + cooked = "items.pitta_bread", + burnt = "items.burnt_pitta_bread", + level = 58, xp = 40, stopBurnFire = 82, stopBurnRange = 82, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 118, high = 492), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 130, high = 504), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 143, high = 517) + ) + ) + ) +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/FishRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/FishRecipes.kt new file mode 100644 index 00000000..1b324950 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/FishRecipes.kt @@ -0,0 +1,381 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.GAUNTLETS +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_FIRE +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook + +/** + * Fish cooking recipes — standard heat-source cooking. + * + * Chance profiles sourced from OSRS Wiki skill_chances data. + */ +object FishRecipes { + + val recipes: List = listOf( + // ---- Low-level fish (shared fire/range curve + Lumbridge) ---- + heatCook( + rowKey = "cooking_shrimps", + raw = "items.raw_shrimp", + cooked = "items.shrimp", + burnt = "items.burnt_shrimp", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_anchovies", + raw = "items.raw_anchovies", + cooked = "items.anchovies", + burnt = "items.burntfish1", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_sardine", + raw = "items.raw_sardine", + cooked = "items.sardine", + burnt = "items.burntfish5", + level = 1, xp = 40, stopBurnFire = 38, stopBurnRange = 38, + chances = listOf( + chance("base_any", STATION_ANY, low = 118, high = 492), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 128, high = 512), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 130, high = 504), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 143, high = 517) + ) + ), + heatCook( + rowKey = "cooking_herring", + raw = "items.raw_herring", + cooked = "items.herring", + burnt = "items.burntfish3", + level = 5, xp = 50, stopBurnFire = 41, stopBurnRange = 41, + chances = listOf( + chance("base_any", STATION_ANY, low = 108, high = 472), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 118, high = 492), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 120, high = 484), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 133, high = 497) + ) + ), + heatCook( + rowKey = "cooking_mackerel", + raw = "items.raw_mackerel", + cooked = "items.mackerel", + burnt = "items.burntfish3", + level = 10, xp = 60, stopBurnFire = 45, stopBurnRange = 45, + chances = listOf( + chance("base_any", STATION_ANY, low = 98, high = 452), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 108, high = 472), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 110, high = 464), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 123, high = 477) + ) + ), + heatCook( + rowKey = "cooking_trout", + raw = "items.raw_trout", + cooked = "items.trout", + burnt = "items.burntfish2", + level = 15, xp = 70, stopBurnFire = 49, stopBurnRange = 49, + chances = listOf( + chance("base_any", STATION_ANY, low = 88, high = 432), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 98, high = 452), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 100, high = 444), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 113, high = 457) + ) + ), + + // ---- Mid-level fish ---- + heatCook( + rowKey = "cooking_cod", + raw = "items.raw_cod", + cooked = "items.cod", + burnt = "items.burntfish2", + level = 18, xp = 75, stopBurnFire = 51, stopBurnRange = 49, + chances = listOf( + chance("fire", STATION_FIRE, low = 83, high = 422), + chance("range", STATION_RANGE, low = 88, high = 432), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 93, high = 442), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 100, high = 444), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 113, high = 457) + ) + ), + heatCook( + rowKey = "cooking_pike", + raw = "items.raw_pike", + cooked = "items.pike", + burnt = "items.burntfish5", + level = 20, xp = 80, stopBurnFire = 54, stopBurnRange = 54, + chances = listOf( + chance("base_any", STATION_ANY, low = 78, high = 412), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 88, high = 432), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 90, high = 424), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 103, high = 437) + ) + ), + heatCook( + rowKey = "cooking_salmon", + raw = "items.raw_salmon", + cooked = "items.salmon", + burnt = "items.burntfish2", + level = 25, xp = 90, stopBurnFire = 58, stopBurnRange = 58, + chances = listOf( + chance("base_any", STATION_ANY, low = 68, high = 392), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 78, high = 402), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 80, high = 404), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 93, high = 417) + ) + ), + heatCook( + rowKey = "cooking_slimy_eel", + raw = "items.mort_slimey_eel", + cooked = "items.mort_slimey_eel_cooked", + burnt = "items.burnt_eel", + level = 28, xp = 95, stopBurnFire = 61, stopBurnRange = 61, + chances = listOf( + chance("base_any", STATION_ANY, low = 63, high = 382), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 75, high = 394), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 88, high = 407) + ) + ), + heatCook( + rowKey = "cooking_tuna", + raw = "items.raw_tuna", + cooked = "items.tuna", + burnt = "items.burntfish4", + level = 30, xp = 100, stopBurnFire = 63, stopBurnRange = 63, + chances = listOf( + chance("base_any", STATION_ANY, low = 58, high = 372), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 70, high = 384), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 83, high = 397) + ) + ), + + // ---- Mid-high fish (gauntlet-affected) ---- + heatCook( + rowKey = "cooking_lobster", + raw = "items.raw_lobster", + cooked = "items.lobster", + burnt = "items.burnt_lobster", + level = 40, xp = 120, stopBurnFire = 74, stopBurnRange = 74, + chances = listOf( + chance("base_any", STATION_ANY, low = 38, high = 332), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 55, high = 368), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357), + chance("hosidius_5_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_5 or GAUNTLETS, low = 67, high = 380), + chance("hosidius_10_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_10 or GAUNTLETS, low = 80, high = 393) + ) + ), + heatCook( + rowKey = "cooking_bass", + raw = "items.raw_bass", + cooked = "items.bass", + burnt = "items.burntfish3", + level = 43, xp = 130, stopBurnFire = 79, stopBurnRange = 79, + chances = listOf( + chance("base_any", STATION_ANY, low = 33, high = 312), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 45, high = 324), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 58, high = 337) + ) + ), + heatCook( + rowKey = "cooking_swordfish", + raw = "items.raw_swordfish", + cooked = "items.swordfish", + burnt = "items.burnt_swordfish", + level = 45, xp = 140, stopBurnFire = 86, stopBurnRange = 80, + chances = listOf( + chance("fire", STATION_FIRE, low = 18, high = 292), + chance("range", STATION_RANGE, low = 30, high = 310), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 30, high = 310), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 42, high = 322), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 55, high = 335) + ) + ), + + // ---- High-level fish ---- + heatCook( + rowKey = "cooking_monkfish", + raw = "items.raw_monkfish", + cooked = "items.monkfish", + burnt = "items.burnt_monkfish", + level = 62, xp = 150, stopBurnFire = 92, stopBurnRange = 90, + chances = listOf( + chance("fire", STATION_FIRE, low = 11, high = 275), + chance("range", STATION_RANGE, low = 13, high = 280), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 24, high = 290), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 25, high = 292), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 38, high = 305), + chance("hosidius_5_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_5 or GAUNTLETS, low = 36, high = 302), + chance("hosidius_10_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_10 or GAUNTLETS, low = 49, high = 315) + ) + ), + heatCook( + rowKey = "cooking_shark", + raw = "items.raw_shark", + cooked = "items.shark", + burnt = "items.burnt_shark", + level = 80, xp = 210, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 202), + chance("range", STATION_RANGE, low = 1, high = 232), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 15, high = 270), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 244), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 257), + chance("hosidius_5_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_5 or GAUNTLETS, low = 27, high = 282), + chance("hosidius_10_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_10 or GAUNTLETS, low = 40, high = 295) + ) + ), + heatCook( + rowKey = "cooking_sea_turtle", + raw = "items.raw_seaturtle", + cooked = "items.seaturtle", + burnt = "items.burnt_seaturtle", + level = 82, xp = 211, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 202), + chance("range", STATION_RANGE, low = 1, high = 222), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 234), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 247) + ) + ), + heatCook( + rowKey = "cooking_anglerfish", + raw = "items.raw_anglerfish", + cooked = "items.anglerfish", + burnt = "items.burnt_anglerfish", + level = 84, xp = 230, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 200), + chance("range", STATION_RANGE, low = 1, high = 220), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 12, high = 260), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 232), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 245), + chance("hosidius_5_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_5 or GAUNTLETS, low = 24, high = 272), + chance("hosidius_10_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_10 or GAUNTLETS, low = 37, high = 285) + ) + ), + heatCook( + rowKey = "cooking_dark_crab", + raw = "items.raw_dark_crab", + cooked = "items.dark_crab", + burnt = "items.burnt_dark_crab", + level = 90, xp = 215, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("base_any", STATION_ANY, low = 10, high = 222), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 22, high = 234), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 35, high = 247) + ) + ), + heatCook( + rowKey = "cooking_manta_ray", + raw = "items.raw_mantaray", + cooked = "items.mantaray", + burnt = "items.burnt_mantaray", + level = 91, xp = 216, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 202), + chance("range", STATION_RANGE, low = 1, high = 222), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 234), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 247) + ) + ), + + // ---- Special fish ---- + heatCook( + rowKey = "cooking_karambwan", + raw = "items.tbwt_raw_karambwan", + cooked = "items.tbwt_cooked_karambwan", + burnt = "items.tbwt_burnt_karambwan", + level = 30, xp = 190, stopBurnFire = 99, stopBurnRange = 93, + chances = listOf( + chance("base_any", STATION_ANY, low = 70, high = 255), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 82, high = 267), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 95, high = 280) + ) + ), + heatCook( + rowKey = "cooking_rainbow_fish", + raw = "items.hunting_raw_fish_special", + cooked = "items.hunting_fish_special", + burnt = "items.burntfish2", + level = 35, xp = 110, stopBurnFire = 64, stopBurnRange = 60, + chances = listOf( + chance("base_any", STATION_ANY, low = 56, high = 370), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 68, high = 382), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 81, high = 395) + ) + ), + heatCook( + rowKey = "cooking_cave_eel", + raw = "items.raw_cave_eel", + cooked = "items.cave_eel", + burnt = "items.burnt_cave_eel", + level = 38, xp = 115, stopBurnFire = 74, stopBurnRange = 70, + chances = listOf( + chance("base_any", STATION_ANY, low = 38, high = 332), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357) + ) + ), + + // ---- Snails ---- + heatCook( + rowKey = "cooking_thin_snail", + raw = "items.snail_corpse1", + cooked = "items.snail_corpse_cooked1", + burnt = "items.burnt_snail", + level = 12, xp = 70, stopBurnFire = 47, stopBurnRange = 47, + stationMask = STATION_FIRE, + chances = listOf( + chance("base_any", STATION_ANY, low = 93, high = 444), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 103, high = 464), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 105, high = 456), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 118, high = 469) + ) + ), + heatCook( + rowKey = "cooking_lean_snail", + raw = "items.snail_corpse2", + cooked = "items.snail_corpse_cooked2", + burnt = "items.burnt_snail", + level = 17, xp = 80, stopBurnFire = 47, stopBurnRange = 50, + chances = listOf( + chance("fire", STATION_FIRE, low = 93, high = 444), + chance("range", STATION_RANGE, low = 85, high = 428), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 95, high = 448), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 97, high = 440), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 110, high = 453) + ) + ), + heatCook( + rowKey = "cooking_fat_snail", + raw = "items.snail_corpse3", + cooked = "items.snail_corpse_cooked3", + burnt = "items.burnt_snail", + level = 22, xp = 95, stopBurnFire = 56, stopBurnRange = 56, + stationMask = STATION_FIRE, + chances = listOf( + chance("base_any", STATION_ANY, low = 73, high = 402), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 83, high = 422), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 85, high = 414), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 98, high = 427) + ) + ) + ) +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/MeatRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/MeatRecipes.kt new file mode 100644 index 00000000..5b9ac7ba --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/MeatRecipes.kt @@ -0,0 +1,237 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.GAUNTLETS +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_FIRE +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook +import org.alter.impl.skills.cooking.CookingHelpers.spitRoast + +/** + * Meat cooking recipes — standard cooking and spit roasting. + * + * Chance profiles sourced from OSRS Wiki skill_chances data. + */ +object MeatRecipes { + + /** Basic meat cooking recipes (fire/range). */ + val basicMeatRecipes: List = listOf( + heatCook( + rowKey = "cooking_chompy", + raw = "items.raw_chompy", + cooked = "items.cooked_chompy", + burnt = "items.ruined_chompy", + level = 30, xp = 100, stopBurnFire = 63, stopBurnRange = 63, + stationMask = STATION_FIRE, + chances = listOf( + chance("fire", STATION_FIRE, low = 200, high = 255) + ) + ), + heatCook( + rowKey = "cooking_beef", + raw = "items.raw_beef", + cooked = "items.cooked_meat", + burnt = "items.burnt_meat", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_chicken", + raw = "items.raw_chicken", + cooked = "items.cooked_chicken", + burnt = "items.burnt_chicken", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_rat_meat", + raw = "items.raw_rat_meat", + cooked = "items.cooked_meat", + burnt = "items.burnt_meat", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_bear_meat", + raw = "items.raw_bear_meat", + cooked = "items.cooked_meat", + burnt = "items.burnt_meat", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_rabbit", + raw = "items.raw_rabbit", + cooked = "items.cooked_rabbit", + burnt = "items.burnt_meat", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_ugthanki_meat", + raw = "items.raw_ugthanki_meat", + cooked = "items.cooked_ugthanki_meat", + burnt = "items.burnt_meat", + level = 1, xp = 40, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("fire", STATION_FIRE, low = 40, high = 252), + chance("range", STATION_RANGE, low = 30, high = 253), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 42, high = 265), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 55, high = 278) + ) + ) + ) + + /** Spit roasting recipes (skewer + fire-only roast). */ + val spitRoastRecipes: List = + spitRoast( + skewerRowKey = "cooking_skewer_bird_meat", + roastRowKey = "cooking_roast_bird_meat", + rawMeat = "items.spit_raw_bird_meat", + skewered = "items.spit_skewered_bird_meat", + cooked = "items.spit_roasted_bird_meat", + burnt = "items.spit_burned_bird_meat", + cookingLevel = 11, xp = 60, stopBurnFire = 44, stopBurnRange = 44, + chances = listOf(chance("fire", STATION_FIRE, low = 155, high = 255)) + ) + + spitRoast( + skewerRowKey = "cooking_skewer_beast_meat", + roastRowKey = "cooking_roast_beast_meat", + rawMeat = "items.spit_raw_beast_meat", + skewered = "items.spit_skewered_beast_meat", + cooked = "items.spit_roasted_beast_meat", + burnt = "items.spit_burned_beast_meat", + cookingLevel = 21, xp = 82, stopBurnFire = 55, stopBurnRange = 55, + chances = listOf(chance("fire", STATION_FIRE, low = 180, high = 255)) + ) + + spitRoast( + skewerRowKey = "cooking_skewer_rabbit_meat", + roastRowKey = "cooking_roast_rabbit_meat", + rawMeat = "items.raw_rabbit", + skewered = "items.spit_skewered_rabbit_meat", + cooked = "items.spit_roasted_rabbit_meat", + burnt = "items.spit_burned_rabbit_meat", + cookingLevel = 16, xp = 70, stopBurnFire = 99, stopBurnRange = 99, + chances = listOf(chance("fire", STATION_FIRE, low = 160, high = 255)) + ) + + /** + * Hunter meat recipes — meats obtained from pitfall trapping. + * + * Note: Small kebbits (wild, barb-tailed, dashing) are eaten raw and do NOT + * require cooking. Only pitfall-trapped animals have raw→cooked transitions. + */ + val hunterMeatRecipes: List = listOf( + heatCook( + rowKey = "cooking_larupia", + raw = "items.hunting_larupia_meat", + cooked = "items.larupia_cooked", + burnt = "items.burnt_largebeast", + level = 31, xp = 92, stopBurnFire = 59, stopBurnRange = 59, + stationMask = STATION_FIRE, + chances = listOf( + chance("base_any", STATION_ANY, low = 65, high = 390), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 77, high = 402), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 90, high = 415) + ) + ), + heatCook( + rowKey = "cooking_graahk", + raw = "items.hunting_graahk_meat", + cooked = "items.graahk_cooked", + burnt = "items.burnt_largebeast", + level = 41, xp = 124, stopBurnFire = 75, stopBurnRange = 75, + stationMask = STATION_FIRE, + chances = listOf( + chance("base_any", STATION_ANY, low = 32, high = 328), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 44, high = 340), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 57, high = 353) + ) + ), + heatCook( + rowKey = "cooking_kyatt", + raw = "items.hunting_kyatt_meat", + cooked = "items.kyatt_cooked", + burnt = "items.burnt_largebeast", + level = 51, xp = 143, stopBurnFire = 86, stopBurnRange = 80, + chances = listOf( + chance("fire", STATION_FIRE, low = 18, high = 292), + chance("range", STATION_RANGE, low = 30, high = 310), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 30, high = 310), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 42, high = 322), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 55, high = 335) + ) + ), + heatCook( + rowKey = "cooking_pyre_fox", + raw = "items.hunting_fennecfox_meat", + cooked = "items.fennecfox_cooked", + burnt = "items.burnt_foxmeat", + level = 59, xp = 154, stopBurnFire = 93, stopBurnRange = 92, + chances = listOf( + chance("fire", STATION_FIRE, low = 10, high = 273), + chance("range", STATION_RANGE, low = 11, high = 276), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 23, high = 288), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 36, high = 301) + ) + ), + heatCook( + rowKey = "cooking_sunlight_antelope", + raw = "items.hunting_antelopesun_meat", + cooked = "items.antelopesun_cooked", + burnt = "items.burnt_antelope", + level = 68, xp = 175, stopBurnFire = 100, stopBurnRange = 95, + chances = listOf( + chance("fire", STATION_FIRE, low = 8, high = 254), + chance("range", STATION_RANGE, low = 8, high = 265), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 20, high = 277), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 33, high = 290) + ) + ), + heatCook( + rowKey = "cooking_moonlight_antelope", + raw = "items.hunting_antelopemoon_meat", + cooked = "items.antelopemoon_cooked", + burnt = "items.burnt_antelope", + level = 92, xp = 220, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 185), + chance("range", STATION_RANGE, low = 1, high = 200), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 212), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 225) + ) + ) + ) + + /** All meat recipes combined. */ + val recipes: List = basicMeatRecipes + spitRoastRecipes + hunterMeatRecipes +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/MiscFoodRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/MiscFoodRecipes.kt new file mode 100644 index 00000000..e34c1b8d --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/MiscFoodRecipes.kt @@ -0,0 +1,155 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Miscellaneous cooking recipes — vegetable side dishes, dairy, scrambled egg, etc. + */ +object MiscFoodRecipes { + + /** Sweetcorn: raw sweetcorn on fire/range. */ + val sweetcorn: List = listOf( + heatCook( + rowKey = "cooking_sweetcorn", + raw = "items.sweetcorn", + cooked = "items.sweetcorn_cooked", + burnt = "items.sweetcorn_burnt", + level = 28, xp = 104, stopBurnFire = 61, stopBurnRange = 61, + chances = listOf( + chance("base_any", STATION_ANY, low = 78, high = 412), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 90, high = 424), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 103, high = 437) + ) + ) + ) + + /** Scrambled egg: egg + bowl on range. */ + val scrambledEgg: List = multiStepCook( + key = "items.bowl_egg_scrambled", + level = 13, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_scrambled_egg_mix", + inputs = listOf( + "items.egg" to 1, + "items.bowl_empty" to 1 + ), + output = "items.bowl_egg_raw" + ) + ) + ) + listOf( + heatCook( + rowKey = "cooking_scrambled_egg_cook", + raw = "items.bowl_egg_raw", + cooked = "items.bowl_egg_scrambled", + burnt = "items.bowl_egg_burnt", + level = 13, xp = 50, stopBurnFire = 46, stopBurnRange = 46, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 90, high = 438), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 102, high = 450), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 115, high = 463) + ) + ) + ) + + /** Fried onions: chopped onion + bowl on fire/range. */ + val friedOnions: List = listOf( + heatCook( + rowKey = "cooking_fried_onions", + raw = "items.bowl_onion", + cooked = "items.bowl_onion_fried", + burnt = "items.bowl_onion_burnt", + level = 42, xp = 60, stopBurnFire = 74, stopBurnRange = 74, + chances = listOf( + chance("base_any", STATION_ANY, low = 36, high = 322), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 48, high = 334), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 61, high = 347) + ) + ) + ) + + /** Fried mushrooms: sliced mushrooms + bowl on fire/range. */ + val friedMushrooms: List = listOf( + heatCook( + rowKey = "cooking_fried_mushrooms", + raw = "items.bowl_mushroom_raw", + cooked = "items.bowl_mushroom_fried", + burnt = "items.bowl_mushroom_burnt", + level = 46, xp = 60, stopBurnFire = 80, stopBurnRange = 80, + chances = listOf( + chance("base_any", STATION_ANY, low = 16, high = 282), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 28, high = 294), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 41, high = 307) + ) + ) + ) + + /** Mushroom & onion: fried mushrooms + fried onions. */ + val mushroomAndOnion: List = multiStepCook( + key = "items.bowl_mushroom+onion", + level = 57, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_mushroom_onion_combine", + inputs = listOf( + "items.bowl_mushroom_fried" to 1, + "items.bowl_onion_fried" to 1 + ), + output = "items.bowl_mushroom+onion", + always = listOf("items.bowl_empty" to 1) + ) + ) + ) + + /** Tuna and corn: cooked tuna + cooked sweetcorn + bowl. */ + val tunaAndCorn: List = multiStepCook( + key = "items.bowl_tuna+sweetcorn", + level = 67, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_tuna_corn_combine", + inputs = listOf( + "items.tuna" to 1, + "items.sweetcorn_cooked" to 1, + "items.bowl_empty" to 1 + ), + output = "items.bowl_tuna+sweetcorn" + ) + ) + ) + + /** Chocolate cake: cake + chocolate bar. */ + val chocolateCake: List = multiStepCook( + key = "items.chocolate_cake", + level = 50, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_chocolate_cake", + inputs = listOf( + "items.cake" to 1, + "items.chocolate_bar" to 1 + ), + output = "items.chocolate_cake" + ) + ) + ) + + /** All misc cooking recipes combined. */ + val recipes: List = + sweetcorn + + scrambledEgg + + friedOnions + + friedMushrooms + + mushroomAndOnion + + tunaAndCorn + + chocolateCake +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/PieRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/PieRecipes.kt new file mode 100644 index 00000000..2bdf04a4 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/PieRecipes.kt @@ -0,0 +1,461 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Remaining pie recipes beyond garden pie. + * + * All pies follow the same flow: + * 1. Prep steps: add fillings to a pie shell (item-on-item) + * 2. Heat step: bake the uncooked pie on a range + */ +object PieRecipes { + + /** Redberry pie (level 10). */ + val redberryPie: List = multiStepCook( + key = "items.uncooked_redberry_pie", + level = 10, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_redberry_pie_add_berries", + inputs = listOf( + "items.pie_shell" to 1, + "items.redberries" to 1 + ), + output = "items.uncooked_redberry_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_redberry_pie_bake", + raw = "items.uncooked_redberry_pie", + cooked = "items.redberry_pie", + burnt = "items.burnt_pie", + xp = 78, + stopBurnFire = 44, + stopBurnRange = 44, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 98, high = 452), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 108, high = 462), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 110, high = 464), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 123, high = 477) + ) + ) + ) + + /** Meat pie (level 20). */ + val meatPie: List = multiStepCook( + key = "items.uncooked_meat_pie", + level = 20, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_meat_pie_add_meat", + inputs = listOf( + "items.pie_shell" to 1, + "items.cooked_meat" to 1 + ), + output = "items.uncooked_meat_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_meat_pie_bake", + raw = "items.uncooked_meat_pie", + cooked = "items.meat_pie", + burnt = "items.burnt_pie", + xp = 110, + stopBurnFire = 54, + stopBurnRange = 54, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 78, high = 412), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 88, high = 432), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 90, high = 424), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 103, high = 437) + ) + ) + ) + + /** Mud pie (level 29). */ + val mudPie: List = multiStepCook( + key = "items.uncooked_mud_pie", + level = 29, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_mud_pie_add_compost", + inputs = listOf( + "items.pie_shell" to 1, + "items.bucket_compost" to 1 + ), + output = "items.unfinished_mud_pie_1" + ), + PrepStepDef( + rowKey = "cooking_mud_pie_add_water", + inputs = listOf( + "items.unfinished_mud_pie_1" to 1, + "items.bucket_water" to 1 + ), + output = "items.unfinished_mud_pie_2", + always = listOf("items.bucket_empty" to 1) + ), + PrepStepDef( + rowKey = "cooking_mud_pie_add_clay", + inputs = listOf( + "items.unfinished_mud_pie_2" to 1, + "items.clay" to 1 + ), + output = "items.uncooked_mud_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_mud_pie_bake", + raw = "items.uncooked_mud_pie", + cooked = "items.mud_pie", + burnt = "items.burnt_pie", + xp = 128, + stopBurnFire = 63, + stopBurnRange = 63, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 58, high = 372), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 70, high = 384), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 83, high = 397) + ) + ) + ) + + /** Apple pie (level 30). */ + val applePie: List = multiStepCook( + key = "items.uncooked_apple_pie", + level = 30, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_apple_pie_add_apple", + inputs = listOf( + "items.pie_shell" to 1, + "items.cooking_apple" to 1 + ), + output = "items.uncooked_apple_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_apple_pie_bake", + raw = "items.uncooked_apple_pie", + cooked = "items.apple_pie", + burnt = "items.burnt_pie", + xp = 130, + stopBurnFire = 64, + stopBurnRange = 64, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 58, high = 372), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 70, high = 384), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 83, high = 397) + ) + ) + ) + + /** Fish pie (level 47). */ + val fishPie: List = multiStepCook( + key = "items.uncooked_fish_pie", + level = 47, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_fish_pie_add_trout", + inputs = listOf( + "items.pie_shell" to 1, + "items.trout" to 1 + ), + output = "items.unfinished_fish_pie_1" + ), + PrepStepDef( + rowKey = "cooking_fish_pie_add_cod", + inputs = listOf( + "items.unfinished_fish_pie_1" to 1, + "items.cod" to 1 + ), + output = "items.unfinished_fish_pie_2" + ), + PrepStepDef( + rowKey = "cooking_fish_pie_add_potato", + inputs = listOf( + "items.unfinished_fish_pie_2" to 1, + "items.potato" to 1 + ), + output = "items.uncooked_fish_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_fish_pie_bake", + raw = "items.uncooked_fish_pie", + cooked = "items.fish_pie", + burnt = "items.burnt_pie", + xp = 164, + stopBurnFire = 81, + stopBurnRange = 81, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 38, high = 332), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357) + ) + ) + ) + + /** Botanical pie (level 52). */ + val botanicalPie: List = multiStepCook( + key = "items.uncooked_botanical_pie", + level = 52, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_botanical_pie_add_golovanova", + inputs = listOf( + "items.pie_shell" to 1, + "items.golovanova_top" to 1 + ), + output = "items.uncooked_botanical_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_botanical_pie_bake", + raw = "items.uncooked_botanical_pie", + cooked = "items.botanical_pie", + burnt = "items.burnt_pie", + xp = 180, + stopBurnFire = 86, + stopBurnRange = 86, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 20, high = 300), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 108, high = 462), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 32, high = 312), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 45, high = 325) + ) + ) + ) + + /** Mushroom pie (level 60). */ + val mushroomPie: List = multiStepCook( + key = "items.uncooked_mushroom_pie", + level = 60, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_mushroom_pie_add_sulliuscep", + inputs = listOf( + "items.pie_shell" to 1, + "items.fossil_sulliuscep_cap" to 1 + ), + output = "items.uncooked_mushroom_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_mushroom_pie_bake", + raw = "items.uncooked_mushroom_pie", + cooked = "items.mushroom_pie", + burnt = "items.burnt_pie", + xp = 200, + stopBurnFire = 94, + stopBurnRange = 94, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 17, high = 285), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 140, high = 450), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 29, high = 297), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 42, high = 310) + ) + ) + ) + + /** Admiral pie (level 70). */ + val admiralPie: List = multiStepCook( + key = "items.uncooked_admiral_pie", + level = 70, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_admiral_pie_add_salmon", + inputs = listOf( + "items.pie_shell" to 1, + "items.salmon" to 1 + ), + output = "items.unfinished_admiral_pie_1" + ), + PrepStepDef( + rowKey = "cooking_admiral_pie_add_tuna", + inputs = listOf( + "items.unfinished_admiral_pie_1" to 1, + "items.tuna" to 1 + ), + output = "items.unfinished_admiral_pie_2" + ), + PrepStepDef( + rowKey = "cooking_admiral_pie_add_potato", + inputs = listOf( + "items.unfinished_admiral_pie_2" to 1, + "items.potato" to 1 + ), + output = "items.uncooked_admiral_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_admiral_pie_bake", + raw = "items.uncooked_admiral_pie", + cooked = "items.admiral_pie", + burnt = "items.burnt_pie", + xp = 210, + stopBurnFire = 100, + stopBurnRange = 94, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 15, high = 270), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 27, high = 282), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 40, high = 295) + ) + ) + ) + + /** Dragonfruit pie (level 73). */ + val dragonfruitPie: List = multiStepCook( + key = "items.uncooked_dragonfruit_pie", + level = 73, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_dragonfruit_pie_add_fruit", + inputs = listOf( + "items.pie_shell" to 1, + "items.dragonfruit" to 1 + ), + output = "items.uncooked_dragonfruit_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_dragonfruit_pie_bake", + raw = "items.uncooked_dragonfruit_pie", + cooked = "items.dragonfruit_pie", + burnt = "items.burnt_pie", + xp = 220, + stopBurnFire = 100, + stopBurnRange = 97, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 8, high = 250), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 20, high = 262), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 33, high = 275) + ) + ) + ) + + /** Wild pie (level 85). */ + val wildPie: List = multiStepCook( + key = "items.uncooked_wild_pie", + level = 85, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_wild_pie_add_bear", + inputs = listOf( + "items.pie_shell" to 1, + "items.raw_bear_meat" to 1 + ), + output = "items.unfinished_wild_pie_1" + ), + PrepStepDef( + rowKey = "cooking_wild_pie_add_chompy", + inputs = listOf( + "items.unfinished_wild_pie_1" to 1, + "items.raw_chompy" to 1 + ), + output = "items.unfinished_wild_pie_2" + ), + PrepStepDef( + rowKey = "cooking_wild_pie_add_rabbit", + inputs = listOf( + "items.unfinished_wild_pie_2" to 1, + "items.raw_rabbit" to 1 + ), + output = "items.uncooked_wild_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_wild_pie_bake", + raw = "items.uncooked_wild_pie", + cooked = "items.wild_pie", + burnt = "items.burnt_pie", + xp = 240, + stopBurnFire = 100, + stopBurnRange = 100, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 1, high = 222), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 234), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 247) + ) + ) + ) + + /** Summer pie (level 95). */ + val summerPie: List = multiStepCook( + key = "items.uncooked_summer_pie", + level = 95, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_summer_pie_add_watermelon", + inputs = listOf( + "items.pie_shell" to 1, + "items.watermelon" to 1 + ), + output = "items.unfinished_summer_pie_1" + ), + PrepStepDef( + rowKey = "cooking_summer_pie_add_apple", + inputs = listOf( + "items.unfinished_summer_pie_1" to 1, + "items.cooking_apple" to 1 + ), + output = "items.unfinished_summer_pie_2" + ), + PrepStepDef( + rowKey = "cooking_summer_pie_add_strawberry", + inputs = listOf( + "items.unfinished_summer_pie_2" to 1, + "items.strawberry" to 1 + ), + output = "items.uncooked_summer_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_summer_pie_bake", + raw = "items.uncooked_summer_pie", + cooked = "items.summer_pie", + burnt = "items.burnt_pie", + xp = 260, + stopBurnFire = 100, + stopBurnRange = 100, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 1, high = 212), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 224), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 237) + ) + ) + ) + + /** All pie recipes combined. */ + val recipes: List = + redberryPie + + meatPie + + mudPie + + applePie + + fishPie + + botanicalPie + + mushroomPie + + admiralPie + + dragonfruitPie + + wildPie + + summerPie +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/PizzaRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/PizzaRecipes.kt new file mode 100644 index 00000000..f6f4dd54 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/PizzaRecipes.kt @@ -0,0 +1,115 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Pizza recipes — dough + toppings + range bake. + * + * Flow: pizza base → uncooked pizza (item-on-item) → plain pizza (bake) → topped pizza (item-on-item) + */ +object PizzaRecipes { + + /** Plain pizza: pizza base on range. */ + val plainPizzaRecipes: List = multiStepCook( + key = "items.uncooked_pizza", + level = 35, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_pizza_add_tomato", + inputs = listOf( + "items.pizza_base" to 1, + "items.tomato" to 1 + ), + output = "items.incomplete_pizza" + ), + PrepStepDef( + rowKey = "cooking_pizza_add_cheese", + inputs = listOf( + "items.incomplete_pizza" to 1, + "items.cheese" to 1 + ), + output = "items.uncooked_pizza" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_pizza_bake", + raw = "items.uncooked_pizza", + cooked = "items.plain_pizza", + burnt = "items.burnt_pizza", + xp = 143, + stopBurnFire = 68, + stopBurnRange = 68, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 48, high = 352), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 60, high = 364), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 73, high = 377) + ) + ) + ) + + /** Meat pizza topping: plain pizza + cooked meat. */ + val meatPizzaRecipes: List = multiStepCook( + key = "items.meat_pizza", + level = 45, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_pizza_add_meat", + inputs = listOf( + "items.plain_pizza" to 1, + "items.cooked_meat" to 1 + ), + output = "items.meat_pizza", + xp = 26 + ) + ) + ) + + /** Anchovy pizza topping: plain pizza + anchovies. */ + val anchovyPizzaRecipes: List = multiStepCook( + key = "items.anchovie_pizza", + level = 55, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_pizza_add_anchovies", + inputs = listOf( + "items.plain_pizza" to 1, + "items.anchovies" to 1 + ), + output = "items.anchovie_pizza", + xp = 39 + ) + ) + ) + + /** Pineapple pizza topping: plain pizza + pineapple ring/chunks. */ + val pineapplePizzaRecipes: List = multiStepCook( + key = "items.pineapple_pizza", + level = 65, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_pizza_add_pineapple", + inputs = listOf( + "items.plain_pizza" to 1, + "items.pineapple_ring" to 1 + ), + output = "items.pineapple_pizza", + xp = 52 + ) + ) + ) + + /** All pizza recipes combined. */ + val recipes: List = + plainPizzaRecipes + + meatPizzaRecipes + + anchovyPizzaRecipes + + pineapplePizzaRecipes +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/PotatoRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/PotatoRecipes.kt new file mode 100644 index 00000000..795b3b27 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/PotatoRecipes.kt @@ -0,0 +1,126 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Potato-based recipes — baked potato and toppings. + */ +object PotatoRecipes { + + /** Baked potato: raw potato on a range. */ + val bakedPotato: List = listOf( + heatCook( + rowKey = "cooking_baked_potato", + raw = "items.potato", + cooked = "items.potato_baked", + burnt = "items.potato_burnt", + level = 7, xp = 15, stopBurnFire = 40, stopBurnRange = 40, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 108, high = 472), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 120, high = 484), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 133, high = 497) + ) + ) + ) + + /** Potato with butter: baked potato + pat of butter. */ + val potatoWithButter: List = multiStepCook( + key = "items.potato_butter", + level = 39, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_butter", + inputs = listOf( + "items.potato_baked" to 1, + "items.pot_of_butter" to 1 + ), + output = "items.potato_butter" + ) + ) + ) + + /** Potato with cheese: potato with butter + cheese. */ + val potatoWithCheese: List = multiStepCook( + key = "items.potato_cheese", + level = 47, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_cheese", + inputs = listOf( + "items.potato_butter" to 1, + "items.cheese" to 1 + ), + output = "items.potato_cheese" + ) + ) + ) + + /** Egg potato: potato with butter + scrambled egg. */ + val eggPotato: List = multiStepCook( + key = "items.potato_egg+tomato", + level = 51, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_egg", + inputs = listOf( + "items.potato_butter" to 1, + "items.bowl_egg_scrambled" to 1 + ), + output = "items.potato_egg+tomato", + always = listOf("items.bowl_empty" to 1) + ) + ) + ) + + /** Mushroom potato: potato with butter + mushroom & onion. */ + val mushroomPotato: List = multiStepCook( + key = "items.potato_mushroom+onion", + level = 64, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_mushroom_onion", + inputs = listOf( + "items.potato_butter" to 1, + "items.bowl_mushroom+onion" to 1 + ), + output = "items.potato_mushroom+onion", + always = listOf("items.bowl_empty" to 1) + ) + ) + ) + + /** Tuna potato: potato with butter + tuna and corn. */ + val tunaPotato: List = multiStepCook( + key = "items.potato_tuna+sweetcorn", + level = 68, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_tuna_corn", + inputs = listOf( + "items.potato_butter" to 1, + "items.bowl_tuna+sweetcorn" to 1 + ), + output = "items.potato_tuna+sweetcorn", + always = listOf("items.bowl_empty" to 1) + ) + ) + ) + + /** All potato recipes combined. */ + val recipes: List = + bakedPotato + + potatoWithButter + + potatoWithCheese + + eggPotato + + mushroomPotato + + tunaPotato +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/StewRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/StewRecipes.kt new file mode 100644 index 00000000..590ffca9 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/StewRecipes.kt @@ -0,0 +1,106 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Stew recipes — bowl of water + ingredients on heat source. + */ +object StewRecipes { + + /** Basic stew: bowl of water + cooked meat + potato on fire/range. */ + val stewRecipes: List = multiStepCook( + key = "items.uncooked_stew", + level = 25, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_stew_add_potato", + inputs = listOf( + "items.bowl_water" to 1, + "items.potato" to 1 + ), + output = "items.stew1" + ), + PrepStepDef( + rowKey = "cooking_stew_add_meat", + inputs = listOf( + "items.stew1" to 1, + "items.cooked_meat" to 1 + ), + output = "items.uncooked_stew" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_stew_cook", + raw = "items.uncooked_stew", + cooked = "items.stew", + burnt = "items.burnt_stew", + xp = 117, + stopBurnFire = 58, + stopBurnRange = 58, + chances = listOf( + chance("base_any", STATION_ANY, low = 68, high = 392), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 78, high = 412), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 80, high = 404), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 93, high = 417) + ) + ) + ) + + /** Curry: bowl of water + potato + cooked meat + spice on fire/range. */ + val curryRecipes: List = multiStepCook( + key = "items.uncooked_curry", + level = 60, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_curry_add_potato", + inputs = listOf( + "items.bowl_water" to 1, + "items.potato" to 1 + ), + output = "items.stew1" + ), + PrepStepDef( + rowKey = "cooking_curry_add_meat", + inputs = listOf( + "items.stew1" to 1, + "items.cooked_meat" to 1 + ), + output = "items.uncooked_stew" + ), + PrepStepDef( + rowKey = "cooking_curry_add_spice", + inputs = listOf( + "items.uncooked_stew" to 1, + "items.spicespot" to 1 + ), + output = "items.uncooked_curry" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_curry_cook", + raw = "items.uncooked_curry", + cooked = "items.curry", + burnt = "items.burnt_curry", + xp = 280, + stopBurnFire = 74, + stopBurnRange = 74, + chances = listOf( + chance("base_any", STATION_ANY, low = 38, high = 332), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357) + ) + ) + ) + + /** All stew recipes combined. */ + val recipes: List = stewRecipes + curryRecipes +} diff --git a/cache/bin/main/org/alter/impl/skills/cooking/recipes/WineRecipes.kt b/cache/bin/main/org/alter/impl/skills/cooking/recipes/WineRecipes.kt new file mode 100644 index 00000000..b9bf86ca --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/cooking/recipes/WineRecipes.kt @@ -0,0 +1,55 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.OutcomeKind +import org.alter.impl.skills.cooking.CookingConstants.Trigger +import org.alter.impl.skills.cooking.InputDef +import org.alter.impl.skills.cooking.OutcomeDef + +/** + * Wine recipes — fermentation mechanic. + * + * Wine is made by using grapes on a jug of water. The fermentation occurs + * automatically after a delay (12 seconds / ~20 ticks). XP is awarded only + * on success. The success/failure is determined at the time of combining, + * not at fermentation completion. + * + * Since wine doesn't use a heat source, it's modeled as an ITEM_ON_ITEM + * trigger. The actual fermentation delay is handled by CookingEvents. + */ +object WineRecipes { + + val jugOfWine: ActionDef = ActionDef( + rowId = "dbrows.cooking_wine", + key = "items.grapes", + variant = 1, + level = 35, + stopBurnFire = 68, + stopBurnRange = 68, + stationMask = 0, // wine doesn't use a station; marker for event handler + trigger = Trigger.ITEM_ON_ITEM, + inputs = listOf( + InputDef("items.grapes", 1), + InputDef("items.jug_water", 1) + ), + outcomes = listOf( + OutcomeDef( + rowSuffix = "success", + kind = OutcomeKind.SUCCESS, + item = "items.jug_unfermented_wine", + xp = 200, + weight = 1 + ), + OutcomeDef( + rowSuffix = "fail", + kind = OutcomeKind.FAIL, + item = "items.jug_unfermented_wine", + xp = 0, + weight = 1 + ) + ) + ) + + /** All wine recipes. */ + val recipes: List = listOf(jugOfWine) +} diff --git a/cache/bin/main/org/alter/impl/skills/runecrafting/Alters.kt b/cache/bin/main/org/alter/impl/skills/runecrafting/Alters.kt new file mode 100644 index 00000000..8af92767 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/runecrafting/Alters.kt @@ -0,0 +1,262 @@ +package org.alter.impl.skills.runecrafting + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType +import dev.openrune.util.Coords +import org.alter.impl.skills.runecrafting.Tiara.ITEM + +enum class AltarData( + val ruins: List? = null, + val altar: String, + val exitPortal: String? = null, + val talisman: String? = null, + val tiara: String? = null, + val varbit: String? = null, + val rune: Rune, + val entrance: Int? = null, + val exit: Int? = null, + val option: String = "craft-rune", + val row : String, + val combo : List = emptyList() +) { + AIR( + ruins = listOf("objects.airtemple_ruined_old", "objects.airtemple_ruined_new"), + altar = "objects.air_altar", + exitPortal = "objects.airtemple_exit_portal", + talisman = "items.air_talisman", + tiara = "dbrows.runecrafting_tiara_air", + varbit = "varbits.rc_no_tally_required_air", + rune = Rune.AIR, + entrance = Coords(2841, 4830), + exit = Coords(2983, 3288), + row = "dbrows.runecrafting_altar_air", + combo = listOf(CombinationRuneData.MIST_AIR, CombinationRuneData.SMOKE_AIR) + ), + MIND( + ruins = listOf("objects.mindtemple_ruined_old", "objects.mindtemple_ruined_new"), + altar = "objects.mind_altar", + exitPortal = "objects.mindtemple_exit_portal", + talisman = "items.mind_talisman", + tiara = "dbrows.runecrafting_tiara_mind", + varbit = "varbits.rc_no_tally_required_mind", + rune = Rune.MIND, + entrance = Coords(2793, 4829), + exit = Coords(2980, 3511), + row = "dbrows.runecrafting_altar_mind" + ), + WATER( + ruins = listOf("objects.watertemple_ruined_old", "objects.watertemple_ruined_new"), + altar = "objects.water_altar", + exitPortal = "objects.watertemple_exit_portal", + talisman = "items.water_talisman", + tiara = "dbrows.runecrafting_tiara_water", + varbit = "varbits.rc_no_tally_required_water", + rune = Rune.WATER, + entrance = Coords(2725, 4832), + exit = Coords(3182, 3162), + row = "dbrows.runecrafting_altar_water", + combo = listOf(CombinationRuneData.MUD_WATER, CombinationRuneData.MIST_WATER,CombinationRuneData.STEAM_WATER) + ), + EARTH( + ruins = listOf("objects.earthtemple_ruined_old", "objects.earthtemple_ruined_new"), + altar = "objects.earth_altar", + exitPortal = "objects.earthtemple_exit_portal", + talisman = "items.earth_talisman", + tiara = "dbrows.runecrafting_tiara_earth", + varbit = "varbits.rc_no_tally_required_earth", + rune = Rune.EARTH, + entrance = Coords(2657, 4830), + exit = Coords(3302, 3477), + row = "dbrows.runecrafting_altar_earth", + combo = listOf(CombinationRuneData.DUST_EARTH, CombinationRuneData.MUD_EARTH,CombinationRuneData.LAVA_EARTH) + ), + FIRE( + ruins = listOf("objects.firetemple_ruined_old", "objects.firetemple_ruined_new"), + altar = "objects.fire_altar", + exitPortal = "objects.firetemple_exit_portal", + talisman = "items.fire_talisman", + tiara = "dbrows.runecrafting_tiara_fire", + varbit = "varbits.rc_no_tally_required_fire", + rune = Rune.FIRE, + entrance = Coords(2576, 4848), + exit = Coords(3310, 3252), + row = "dbrows.runecrafting_altar_fire", + combo = listOf(CombinationRuneData.LAVA_FIRE, CombinationRuneData.SMOKE_FIRE,CombinationRuneData.STEAM_FIRE) + ), + BODY( + ruins = listOf("objects.bodytemple_ruined_old", "objects.bodytemple_ruined_new"), + altar = "objects.body_altar", + exitPortal = "objects.bodytemple_exit_portal", + talisman = "items.body_talisman", + tiara = "dbrows.runecrafting_tiara_body", + varbit = "varbits.rc_no_tally_required_body", + rune = Rune.BODY, + entrance = Coords(2519, 4847), + exit = Coords(3050, 3442), + row = "dbrows.runecrafting_altar_body" + ), + COSMIC( + ruins = listOf("objects.cosmictemple_ruined_old", "objects.cosmictemple_ruined_new"), + altar = "objects.cosmic_altar", + exitPortal = "objects.cosmictemple_exit_portal", + talisman = "items.cosmic_talisman", + tiara = "dbrows.runecrafting_tiara_cosmic", + varbit = "varbits.rc_no_tally_required_cosmic", + rune = Rune.COSMIC, + entrance = Coords(2142, 4813), + exit = Coords(2405, 4381), + row = "dbrows.runecrafting_altar_cosmic" + ), + CHAOS( + ruins = listOf("objects.chaostemple_ruined_old", "objects.chaostemple_ruined_new"), + altar = "objects.chaos_altar", + exitPortal = "objects.chaostemple_exit_portal", + talisman = "items.chaos_talisman", + tiara = "dbrows.runecrafting_tiara_chaos", + varbit = "varbits.rc_no_tally_required_chaos", + rune = Rune.CHAOS, + entrance = Coords(2280, 4837), + exit = Coords(3060, 3585), + row = "dbrows.runecrafting_altar_chaos" + ), + ASTRAL( + altar = "objects.astral_altar", + rune = Rune.ASTRAL, + row = "dbrows.runecrafting_altar_astral" + ), + NATURE( + ruins = listOf("objects.naturetemple_ruined_old", "objects.naturetemple_ruined_new"), + altar = "objects.nature_altar", + exitPortal = "objects.naturetemple_exit_portal", + talisman = "items.nature_talisman", + tiara = "dbrows.runecrafting_tiara_nature", + varbit = "varbits.rc_no_tally_required_nature", + rune = Rune.NATURE, + entrance = Coords(2400, 4835), + exit = Coords(2865, 3022), + row = "dbrows.runecrafting_altar_nature" + ), + LAW( + ruins = listOf("objects.lawtemple_ruined_old", "objects.lawtemple_ruined_new"), + altar = "objects.law_altar", + exitPortal = "objects.lawtemple_exit_portal", + talisman = "items.law_talisman", + tiara = "dbrows.runecrafting_tiara_law", + varbit = "varbits.rc_no_tally_required_law", + rune = Rune.LAW, + entrance = Coords(2464, 4819), + exit = Coords(2858, 3378), + row = "dbrows.runecrafting_altar_law" + ), + DEATH( + ruins = listOf("objects.deathtemple_ruined_old", "objects.deathtemple_ruined_new"), + altar = "objects.death_altar", + exitPortal = "objects.deathtemple_exit_portal", + talisman = "items.death_talisman", + tiara = "dbrows.runecrafting_tiara_death", + varbit = "varbits.rc_no_tally_required_death", + rune = Rune.DEATH, + entrance = Coords(2208, 4830), + exit = Coords(1863, 4639), + row = "dbrows.runecrafting_altar_death" + ), + BLOOD( + altar = "objects.blood_altar", + rune = Rune.BLOOD, + option = "bind", + row = "dbrows.runecrafting_altar_blood" + ), + + SOUL( + altar = "objects.archeus_altar_soul", + rune = Rune.SOUL, + option = "bind", + row = "dbrows.runecrafting_altar_soul" + ), + WRATH( + ruins = listOf("objects.wrathtemple_ruined_0op", "objects.wrathtemple_ruined_1op"), + altar = "objects.wrath_altar", + exitPortal = "objects.wrathtemple_exit_portal", + talisman = "items.wrath_talisman", + tiara = "dbrows.runecrafting_tiara_wrath", + varbit = "varbits.rc_no_tally_required_wrath", + rune = Rune.WRATH, + entrance = Coords(2335, 4826), + exit = Coords(2447, 2822), + row = "dbrows.runecrafting_altar_wrath" + ); + + companion object { + val values = enumValues() + } +} + +object Alters { + + const val ALTAR_OBJECT = 0 + const val EXIT_PORTAL = 1 + const val TALISMAN = 2 + const val TIARA_ITEM = 3 + const val VARBIT = 4 + const val RUNE = 5 + const val ENTRANCE = 6 + const val EXIT = 7 + const val RUINS = 8 + const val COMBO = 9 + + fun altars() = dbTable("tables.runecrafting_altars", serverOnly = true) { + + column("altar_object", ALTAR_OBJECT, VarType.LOC) + column("exit_portal", EXIT_PORTAL, VarType.LOC) + column("talisman", TALISMAN, VarType.OBJ) + column("tiara", TIARA_ITEM, VarType.DBROW) + column("varbit", VARBIT, VarType.INT) + column("rune", RUNE, VarType.DBROW) + column("entrance", ENTRANCE, VarType.COORDGRID) + column("exit", EXIT, VarType.COORDGRID) + column("ruins", RUINS, VarType.LOC) + column("combo", COMBO, VarType.DBROW) + + AltarData.values.forEach { + row(it.row) { + columnRSCM(ALTAR_OBJECT, it.altar) + columnRSCM(RUNE, it.rune.dbId) + + if (it.ruins != null) { + columnRSCM(RUINS, *it.ruins.toTypedArray()) + } + + if (it.exit != null) { + column(EXIT, it.exit) + } + + if (it.exitPortal != null) { + columnRSCM(EXIT_PORTAL, it.exitPortal) + } + + if (it.talisman != null) { + columnRSCM(TALISMAN, it.talisman) + } + + if (it.tiara != null) { + columnRSCM(TIARA_ITEM, it.tiara) + } + + if (it.entrance != null) { + column(ENTRANCE, it.entrance) + } + + if (it.varbit != null) { + columnRSCM(VARBIT, it.varbit) + } + + if (it.combo.isNotEmpty()) { + columnRSCM(COMBO, *it.combo.map { combo -> combo.row }.toTypedArray()) + } + + } + } + + } + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/skills/runecrafting/CombinationRune.kt b/cache/bin/main/org/alter/impl/skills/runecrafting/CombinationRune.kt new file mode 100644 index 00000000..65b1ac90 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/runecrafting/CombinationRune.kt @@ -0,0 +1,58 @@ +package org.alter.impl.skills.runecrafting + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType +import org.alter.impl.skills.runecrafting.RunecraftRune.ESSENCE + +enum class CombinationRuneData(val runeOutput: String, val level: Int, val xp: Double, val runeInput: String, val row : String, val talisman : String) { + MIST_AIR(runeOutput = "items.mistrune", level = 6, xp = 8.0, runeInput = "items.waterrune","dbrows.mist_from_wateraltar","items.water_talisman"), + MIST_WATER(runeOutput = "items.mistrune", level = 6, xp = 8.5, runeInput = "items.airrune","dbrows.mist_from_airaltar","items.air_talisman"), + + DUST_AIR(runeOutput = "items.dustrune", level = 10, xp = 8.3, runeInput = "items.earthrune","dbrows.dust_from_earthaltar","items.earth_talisman"), + DUST_EARTH(runeOutput = "items.dustrune", level = 10, xp = 9.0, runeInput = "items.airrune","dbrows.dust_from_airaltar","items.air_talisman"), + + MUD_WATER(runeOutput = "items.mudrune", level = 13, xp = 9.3, runeInput = "items.earthrune","dbrows.mud_from_earthaltar","items.earth_talisman"), + MUD_EARTH(runeOutput = "items.mudrune", level = 13, xp = 9.5, runeInput = "items.waterrune","dbrows.mud_from_wateraltar","items.water_talisman"), + + SMOKE_AIR(runeOutput = "items.smokerune", level = 15, xp = 8.5, runeInput = "items.firerune","dbrows.smoke_from_firealtar","items.fire_talisman"), + SMOKE_FIRE(runeOutput = "items.smokerune", level = 15, xp = 9.5, runeInput = "items.airrune","dbrows.smoke_from_airaltar","items.air_talisman"), + + STEAM_WATER(runeOutput = "items.steamrune", level = 19, xp = 9.5, runeInput = "items.firerune","dbrows.steam_from_firealtar","items.fire_talisman"), + STEAM_FIRE(runeOutput = "items.steamrune", level = 19, xp = 10.0, runeInput = "items.waterrune","dbrows.steam_from_wateraltar","items.water_talisman"), + + LAVA_EARTH(runeOutput = "items.lavarune", level = 23, xp = 10.0, runeInput = "items.firerune","dbrows.lava_from_firealtar","items.fire_talisman"), + LAVA_FIRE(runeOutput = "items.lavarune", level = 23, xp = 10.5, runeInput = "items.earthrune","dbrows.lava_from_earthaltar","items.earth_talisman"); + +} + +object CombinationRune { + + const val RUNE_OUTPUT = 0 + const val LEVEL = 1 + const val XP = 2 + const val RUNE_INPUT = 3 + const val TALISMAN = 4 + + fun runecraftComboRune() = dbTable("tables.comborune_recipe", serverOnly = true) { + + column("rune_output", RUNE_OUTPUT, VarType.OBJ) + column("level", LEVEL, VarType.INT) + column("xp", XP, VarType.INT) + column("rune_input", RUNE_INPUT, VarType.OBJ) + column("talisman", TALISMAN, VarType.OBJ) + + CombinationRuneData.entries.forEach { + row(it.row) { + columnRSCM(RUNE_OUTPUT, it.runeOutput) + column(LEVEL, it.level) + column(XP, it.xp) + columnRSCM(RUNE_INPUT, it.runeInput) + columnRSCM(TALISMAN, it.talisman) + } + } + + } + + + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/skills/runecrafting/RunecraftRune.kt b/cache/bin/main/org/alter/impl/skills/runecrafting/RunecraftRune.kt new file mode 100644 index 00000000..afff6099 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/runecrafting/RunecraftRune.kt @@ -0,0 +1,167 @@ +package org.alter.impl.skills.runecrafting + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +enum class Rune( + val id: String, + val essence: List, + val level: Int, + val xp: Int, + val dbId: String, + val extract: String +) { + AIR( + id = "items.mindrune", + essence = listOf("items.blankrune", "items.blankrune_high"), + level = 1, + xp = 5, + dbId = "dbrows.runecrafting_rune_air", + extract = "items.scar_extract_warped" + ), + MIND( + id = "items.mindrune", + essence = listOf("items.blankrune", "items.blankrune_high"), + level = 2, + xp = 5, + dbId = "dbrows.runecrafting_rune_mind", + extract = "items.scar_extract_warped" + ), + WATER( + id = "items.waterrune", + essence = listOf("items.blankrune", "items.blankrune_high"), + level = 5, + xp = 6, + dbId = "dbrows.runecrafting_rune_water", + extract = "items.scar_extract_warped" + ), + EARTH( + id = "items.earthrune", + essence = listOf("items.blankrune", "items.blankrune_high"), + level = 9, + xp = 6, + dbId = "dbrows.runecrafting_rune_earth", + extract = "items.scar_extract_warped" + ), + FIRE( + id = "items.firerune", + essence = listOf("items.blankrune", "items.blankrune_high"), + level = 14, + xp = 7, + dbId = "dbrows.runecrafting_rune_fire", + extract = "items.scar_extract_warped" + ), + BODY( + id = "items.bodyrune", + essence = listOf("items.blankrune", "items.blankrune_high"), + level = 20, + xp = 7, + dbId = "dbrows.runecrafting_rune_body", + extract = "items.scar_extract_warped" + ), + COSMIC( + id = "items.cosmicrune", + essence = listOf("items.blankrune_high"), + level = 27, + xp = 8, + dbId = "dbrows.runecrafting_rune_cosmic", + extract = "items.scar_extract_twisted" + ), + CHAOS( + id = "items.chaosrune", + essence = listOf("items.blankrune_high"), + level = 35, + xp = 8, + dbId = "dbrows.runecrafting_rune_chaos", + extract = "items.scar_extract_twisted" + ), + ASTRAL( + id = "items.astralrune", + essence = listOf("items.blankrune_high"), + level = 40, + xp = 9, + dbId = "dbrows.runecrafting_rune_astral", + extract = "items.scar_extract_mangled" + ), + NATURE( + id = "items.naturerune", + essence = listOf("items.blankrune_high"), + level = 44, + xp = 9, + dbId = "dbrows.runecrafting_rune_nature", + extract = "items.scar_extract_mangled" + ), + LAW( + id = "items.lawrune", + essence = listOf("items.blankrune_high"), + level = 54, + xp = 9, + dbId = "dbrows.runecrafting_rune_law", + extract = "items.scar_extract_mangled" + ), + DEATH( + id = "items.deathrune", + essence = listOf("items.blankrune_high"), + level = 65, + xp = 10, + dbId = "dbrows.runecrafting_rune_death", + extract = "items.scar_extract_mangled" + ), + BLOOD( + id = "items.bloodrune", + essence = listOf("items.blankrune_high"), + level = 77, + xp = 24, + dbId = "dbrows.runecrafting_rune_blood", + extract = "items.scar_extract_scarred" + ), + SOUL( + id = "items.soulrune", + essence = listOf("items.bigblankrune"), + level = 90, + xp = 30, + dbId = "dbrows.runecrafting_rune_soul", + extract = "items.scar_extract_scarred" + ), + WRATH( + id = "items.wrathrune", + essence = listOf("items.blankrune_high"), + level = 95, + xp = 8, + dbId = "dbrows.runecrafting_rune_wrath", + extract = "items.scar_extract_scarred" + ); + + companion object { + val values = enumValues() + } +} + +object RunecraftRune { + + val ITEM = 0 + val ESSENCE = 1 + val LEVEL = 2 + val XP = 3 + val EXTRACT = 4 + + fun runecraftRune() = dbTable("tables.runecrafting_runes", serverOnly = true) { + column("rune_output", ITEM, VarType.OBJ) + column("valid_essences", ESSENCE, VarType.OBJ) + column("xp", XP, VarType.INT) + column("level", LEVEL, VarType.INT) + column("extract", EXTRACT, VarType.OBJ) + Rune.entries.forEach { + row(it.dbId) { + columnRSCM(ITEM, it.id) + columnRSCM(ESSENCE, *it.essence.toTypedArray()) + column(LEVEL, it.level) + column(XP, it.xp) + columnRSCM(EXTRACT, it.extract) + } + } + + + } + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/impl/skills/runecrafting/Tiara.kt b/cache/bin/main/org/alter/impl/skills/runecrafting/Tiara.kt new file mode 100644 index 00000000..d6ff4645 --- /dev/null +++ b/cache/bin/main/org/alter/impl/skills/runecrafting/Tiara.kt @@ -0,0 +1,91 @@ +package org.alter.impl.skills.runecrafting + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object Tiara { + + const val ITEM = 0 + const val ALTER = 1 + const val XP = 2 + + fun tiara() = dbTable("tables.runecrafting_tiara", serverOnly = true) { + column("item", ITEM, VarType.OBJ) + column("alter", ALTER, VarType.LOC) + column("xp", XP, VarType.INT) + + row("dbrows.runecrafting_tiara_air") { + columnRSCM(ITEM, "items.tiara_air") + columnRSCM(ALTER, "objects.air_altar") + column(XP, 25) + } + + row("dbrows.runecrafting_tiara_mind") { + columnRSCM(ITEM, "items.tiara_mind") + columnRSCM(ALTER, "objects.mind_altar") + column(XP, 27) + } + + row("dbrows.runecrafting_tiara_water") { + columnRSCM(ITEM, "items.tiara_water") + columnRSCM(ALTER, "objects.water_altar") + column(XP, 30) + } + + row("dbrows.runecrafting_tiara_earth") { + columnRSCM(ITEM, "items.tiara_earth") + columnRSCM(ALTER, "objects.earth_altar") + column(XP, 32) + } + + row("dbrows.runecrafting_tiara_fire") { + columnRSCM(ITEM, "items.tiara_fire") + columnRSCM(ALTER, "objects.fire_altar") + column(XP, 35) + } + + row("dbrows.runecrafting_tiara_body") { + columnRSCM(ITEM, "items.tiara_body") + columnRSCM(ALTER, "objects.body_altar") + column(XP, 37) + } + + row("dbrows.runecrafting_tiara_cosmic") { + columnRSCM(ITEM, "items.tiara_cosmic") + columnRSCM(ALTER, "objects.cosmic_altar") + column(XP, 40) + } + + row("dbrows.runecrafting_tiara_chaos") { + columnRSCM(ITEM, "items.tiara_chaos") + columnRSCM(ALTER, "objects.chaos_altar") + column(XP, 42) + } + + row("dbrows.runecrafting_tiara_nature") { + columnRSCM(ITEM, "items.tiara_nature") + columnRSCM(ALTER, "objects.nature_altar") + column(XP, 45) + } + + row("dbrows.runecrafting_tiara_law") { + columnRSCM(ITEM, "items.tiara_law") + columnRSCM(ALTER, "objects.law_altar") + column(XP, 47) + } + + row("dbrows.runecrafting_tiara_death") { + columnRSCM(ITEM, "items.tiara_death") + columnRSCM(ALTER, "objects.death_altar") + column(XP, 50) + } + + row("dbrows.runecrafting_tiara_wrath") { + columnRSCM(ITEM, "items.tiara_wrath") + columnRSCM(ALTER, "objects.wrath_altar") + column(XP, 52) + } + + } + +} \ No newline at end of file diff --git a/cache/bin/main/org/alter/rscm/RSCM.kt b/cache/bin/main/org/alter/rscm/RSCM.kt new file mode 100644 index 00000000..8a3b8123 --- /dev/null +++ b/cache/bin/main/org/alter/rscm/RSCM.kt @@ -0,0 +1,62 @@ +package org.alter.rscm + +import dev.openrune.definition.constants.ConstantProvider +import io.github.oshai.kotlinlogging.KotlinLogging +import org.alter.rscm.RSCMType.Companion.RSCM_PREFIXES +import kotlin.system.exitProcess + +enum class RSCMType(val prefix: String) { + OBJTYPES("items"), + NPCTYPES("npcs"), + INVTYPES("inv"), + VARPTYPES("varp"), + VARBITTYPES("varbits"), + LOCTYPES("objects"), + SEQTYPES("sequences"), + SPOTTYPES("spotanims"), + ROWTYPES("dbrows"), + JINGLES("jingles"), + TABLETYPES("tables"), + COMPONENTS("components"), + ENUMS("enums"), + COLUMNS("columns"), + FONTS("fonts"), + INTERFACES("interfaces"); + + companion object { + val RSCM_PREFIXES = RSCMType.entries.map { it.prefix }.toSet() + } + +} + +object RSCM { + + val logger = KotlinLogging.logger {} + + val NONE = "NONE" + + fun getRSCM(entity: Array): List = entity.map { getRSCM(it) } + + fun String.asRSCM(): Int = getRSCM(this) + + fun requireRSCM(type: RSCMType, vararg entities: String) { + for (entity in entities) { + if (!entity.startsWith(type.prefix) && entity != NONE) { + error("Invalid RSCM key. Expected prefix '${type.prefix}', got '${entity.substringBefore(".")}'") + } + } + } + + fun getReverseMapping(table: RSCMType, value: Int): String { + if (value == -1) { + return "-1" + } + return ConstantProvider.getReverseMapping(table.prefix,value) + } + + fun getRSCM(entity: String): Int { + if (entity == NONE) return -1 + require(RSCM_PREFIXES.any { entity.startsWith(it) }) { "Prefix not found for '${entity.substringBefore(".")}'" } + return ConstantProvider.getMapping(entity) + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/org/alter/CacheTools.kt b/cache/src/main/kotlin/org/alter/CacheTools.kt index 9decc0d8..3e681250 100644 --- a/cache/src/main/kotlin/org/alter/CacheTools.kt +++ b/cache/src/main/kotlin/org/alter/CacheTools.kt @@ -20,6 +20,7 @@ import org.alter.codegen.startGeneration import org.alter.gamevals.GameValProvider import org.alter.gamevals.GamevalDumper import org.alter.impl.GameframeTable +import org.alter.impl.skills.cooking.CookingTables import org.alter.impl.skills.Firemaking import org.alter.impl.misc.FoodTable import org.alter.impl.skills.PrayerTable @@ -47,6 +48,9 @@ fun tablesToPack() = listOf( TeleTabs.teleTabs(), StatComponents.statsComponents(), FoodTable.consumableFood(), + CookingTables.actionOutcomes(), + CookingTables.actionInputs(), + CookingTables.actions(), Firemaking.logs(), Woodcutting.trees(), Woodcutting.axes(), diff --git a/cache/src/main/kotlin/org/alter/gamevals/GameValProvider.kt b/cache/src/main/kotlin/org/alter/gamevals/GameValProvider.kt index 5e31c85b..ebd1f0bc 100644 --- a/cache/src/main/kotlin/org/alter/gamevals/GameValProvider.kt +++ b/cache/src/main/kotlin/org/alter/gamevals/GameValProvider.kt @@ -109,13 +109,15 @@ class GameValProvider : MappingProvider { val tableMappings = mappings[table] ?: throw IllegalArgumentException("Table '$table' does not exist in mappings.") + val qualifiedKey = "$table.$key" + val maxID = maxBaseID[table] ?: -1 require(value > maxID) { "Custom value '$value' for key '$key' in table '$table must exceed the current max base ID $maxID. " + "Cannot override existing osrs IDs." } - if (tableMappings.containsKey(key)) { + if (tableMappings.containsKey(qualifiedKey)) { throw IllegalArgumentException( "Mapping conflict in table '$table: key '$key' already exists. Keys must be unique." ) @@ -127,7 +129,7 @@ class GameValProvider : MappingProvider { ) } - tableMappings["$table.$key"] = value + tableMappings[qualifiedKey] = value } private fun parseRSCMV2Line(line: String, lineNumber: Int): Pair = when { diff --git a/cache/src/main/kotlin/org/alter/impl/misc/FoodTable.kt b/cache/src/main/kotlin/org/alter/impl/misc/FoodTable.kt index eb39907b..21b12b6d 100644 --- a/cache/src/main/kotlin/org/alter/impl/misc/FoodTable.kt +++ b/cache/src/main/kotlin/org/alter/impl/misc/FoodTable.kt @@ -14,33 +14,176 @@ enum class Food( val combatDelay: List = listOf(), val dbRowId: String ) { + // Basic fish SHRIMPS("items.shrimp", heal = 3, dbRowId = "dbrows.shrimps_food"), + ANCHOVIES("items.anchovies", heal = 1, dbRowId = "dbrows.anchovies_food"), + SARDINE("items.sardine", heal = 4, dbRowId = "dbrows.sardine_food"), COOKED_CHICKEN("items.cooked_chicken", heal = 3, dbRowId = "dbrows.cooked_chicken_food"), COOKED_MEAT("items.cooked_meat", heal = 3, dbRowId = "dbrows.cooked_meat_food"), BREAD("items.bread", heal = 5, dbRowId = "dbrows.bread_food"), HERRING("items.herring", heal = 5, dbRowId = "dbrows.herring_food"), MACKEREL("items.mackerel", heal = 6, dbRowId = "dbrows.mackerel_food"), + COD("items.cod", heal = 7, dbRowId = "dbrows.cod_food"), TROUT("items.trout", heal = 7, dbRowId = "dbrows.trout_food"), PIKE("items.pike", heal = 8, dbRowId = "dbrows.pike_food"), PEACH("items.peach", heal = 8, dbRowId = "dbrows.peach_food"), SALMON("items.salmon", heal = 9, dbRowId = "dbrows.salmon_food"), TUNA("items.tuna", heal = 10, dbRowId = "dbrows.tuna_food"), + CAVE_EEL("items.cave_eel", heal = 10, dbRowId = "dbrows.cave_eel_food"), + LAVA_EEL("items.lava_eel", heal = 11, dbRowId = "dbrows.lava_eel_food"), JUG_OF_WINE("items.jug_wine", heal = 11, hasEffect = true, dbRowId = "dbrows.jug_of_wine_food"), LOBSTER("items.lobster", heal = 12, dbRowId = "dbrows.lobster_food"), BASS("items.bass", heal = 13, dbRowId = "dbrows.bass_food"), SWORDFISH("items.swordfish", heal = 14, dbRowId = "dbrows.swordfish_food"), + SWORDTIP_SQUID("items.swordtip_squid", heal = 15, dbRowId = "dbrows.swordtip_squid_food"), IXCOZTIC_WHITE("items.ixcoztic_white", heal = 16, hasEffect = true, dbRowId = "dbrows.ixcoztic_white_food"), POTATO_WITH_CHEESE("items.potato_cheese", heal = 16, dbRowId = "dbrows.potato_with_cheese_food"), MONKFISH("items.monkfish", heal = 16, dbRowId = "dbrows.monkfish_food"), + JUMBO_SQUID("items.jumbo_squid", heal = 17, dbRowId = "dbrows.jumbo_squid_food"), + GIANT_KRILL("items.giant_krill", heal = 17, dbRowId = "dbrows.giant_krill_food"), + HADDOCK("items.haddock", heal = 18, dbRowId = "dbrows.haddock_food"), CURRY("items.curry", "items.bowl_empty", heal = 19, dbRowId = "dbrows.curry_food"), COOKED_PYRE_FOX("items.curry", heal = 11, dbRowId = "dbrows.cooked_pyre_fox_food"), + UGTHANKI_KEBAB("items.ugthanki_kebab", heal = 19, dbRowId = "dbrows.ugthanki_kebab_food"), + YELLOWFIN("items.yellowfin", heal = 19, dbRowId = "dbrows.yellowfin_food"), SHARK("items.shark", heal = 20, dbRowId = "dbrows.shark_food"), + HALIBUT("items.halibut", heal = 20, dbRowId = "dbrows.halibut_food"), SEA_TURTLE("items.seaturtle", heal = 21, dbRowId = "dbrows.sea_turtle_food"), MANTA_RAY("items.mantaray", heal = 22, dbRowId = "dbrows.manta_ray_food"), TUNA_POTATO("items.potato_tuna+sweetcorn", heal = 22, dbRowId = "dbrows.tuna_potato_food"), DARK_CRAB("items.dark_crab", heal = 22, dbRowId = "dbrows.dark_crab_food"), + BLUEFIN("items.bluefin", heal = 22, dbRowId = "dbrows.bluefin_food"), + MARLIN("items.marlin", heal = 24, dbRowId = "dbrows.marlin_food"), + CAVEFISH("items.cavefish", heal = 20, dbRowId = "dbrows.cavefish_food"), + TETRA("items.tetra", heal = 22, dbRowId = "dbrows.tetra_food"), ANGLERFISH("items.anglerfish", heal = -1, overheal = true, dbRowId = "dbrows.anglerfish_food"), + + // Basic foods & produce ONION("items.onion", heal = 1, dbRowId = "dbrows.onion_food"), + POTATO("items.potato", heal = 1, dbRowId = "dbrows.potato_food"), + CABBAGE("items.cabbage", heal = 1, dbRowId = "dbrows.cabbage_food"), + BANANA("items.banana", heal = 2, dbRowId = "dbrows.banana_food"), + TOMATO("items.tomato", heal = 2, dbRowId = "dbrows.tomato_food"), + CHEESE("items.cheese", heal = 2, dbRowId = "dbrows.cheese_food"), + LEMON("items.lemon", heal = 2, dbRowId = "dbrows.lemon_food"), + ORANGE("items.orange", heal = 2, dbRowId = "dbrows.orange_food"), + LIME("items.lime", heal = 2, dbRowId = "dbrows.lime_food"), + PINEAPPLE("items.pineapple", heal = 2, dbRowId = "dbrows.pineapple_food"), + DWELLBERRIES("items.dwellberries", heal = 2, dbRowId = "dbrows.dwellberries_food"), + JANGERBERRIES("items.jangerberries", heal = 2, dbRowId = "dbrows.jangerberries_food"), + CAERULA_BERRIES("items.caerula_berries", heal = 2, dbRowId = "dbrows.caerula_berries_food"), + STRAWBERRY("items.strawberry", heal = 5, dbRowId = "dbrows.strawberry_food"), + WATERMELON_SLICE("items.watermelon_slice", heal = 2, dbRowId = "dbrows.watermelon_slice_food"), + PAPAYA_FRUIT("items.papaya", heal = 8, dbRowId = "dbrows.papaya_fruit_food"), + DRAGONFRUIT("items.dragonfruit", heal = 10, dbRowId = "dbrows.dragonfruit_food"), + CHOCOLATE_BAR("items.chocolate_bar", heal = 3, dbRowId = "dbrows.chocolate_bar_food"), + EDIBLE_SEAWEED("items.edible_seaweed", heal = 4, dbRowId = "dbrows.edible_seaweed_food"), + + // Cooked meats & misc + KEBAB("items.kebab", heal = 3, hasEffect = true, dbRowId = "dbrows.kebab_food"), + LOCUST_MEAT("items.locust_meat", heal = 3, dbRowId = "dbrows.locust_meat_food"), + ROE("items.brut_roe", heal = 3, dbRowId = "dbrows.roe_food"), + STEW("items.stew", "items.bowl_empty", heal = 11, dbRowId = "dbrows.stew_food"), + COOKED_RABBIT("items.cooked_rabbit", heal = 5, dbRowId = "dbrows.cooked_rabbit_food"), + COOKED_MYSTERY_MEAT("items.cooked_mystery_meat", heal = 5, dbRowId = "dbrows.cooked_mystery_meat_food"), + SCRAMBLED_EGG("items.scrambled_egg", heal = 5, dbRowId = "dbrows.scrambled_egg_food"), + CAVIAR("items.brut_caviar", heal = 5, dbRowId = "dbrows.caviar_food"), + BAGUETTE("items.baguette", heal = 6, dbRowId = "dbrows.baguette_food"), + SEASONED_SARDINE("items.seasoned_sardine", heal = 4, dbRowId = "dbrows.seasoned_sardine_food"), + GIANT_FROG_LEGS("items.giant_frog_legs", heal = 6, dbRowId = "dbrows.giant_frog_legs_food"), + COOKED_CHOMPY("items.cooked_chompy", heal = 10, dbRowId = "dbrows.cooked_chompy_food"), + CHOC_ICE("items.elid_choc_ice", heal = 7, dbRowId = "dbrows.choc_ice_food"), + PUMPKIN("items.pumpkin", heal = 14, dbRowId = "dbrows.pumpkin_food"), + EASTER_EGG("items.easter_egg", heal = 14, dbRowId = "dbrows.easter_egg_food"), + WRAPPED_OOMLIE("items.wrapped_oomlie", heal = 14, dbRowId = "dbrows.wrapped_oomlie_food"), + COOKED_SWEETCORN("items.sweetcorn_cooked", heal = 10, dbRowId = "dbrows.cooked_sweetcorn_food"), + + // Roast meats (spit-roasted) + ROAST_BIRD_MEAT("items.spit_roasted_bird_meat", heal = 6, dbRowId = "dbrows.roast_bird_meat_food"), + ROAST_RABBIT("items.spit_roasted_rabbit_meat", heal = 7, dbRowId = "dbrows.roast_rabbit_food"), + ROAST_BEAST_MEAT("items.spit_roasted_beast_meat", heal = 8, dbRowId = "dbrows.roast_beast_meat_food"), + + // Snails + THIN_SNAIL_MEAT("items.snail_corpse_cooked1", heal = 5, dbRowId = "dbrows.thin_snail_meat_food"), + LEAN_SNAIL_MEAT("items.snail_corpse_cooked2", heal = 6, dbRowId = "dbrows.lean_snail_meat_food"), + FAT_SNAIL_MEAT("items.snail_corpse_cooked3", heal = 8, dbRowId = "dbrows.fat_snail_meat_food"), + + // Spider on stick/shaft + SPIDER_ON_STICK("items.tbw_spider_on_stick_cooked", heal = 7, dbRowId = "dbrows.spider_on_stick_food"), + SPIDER_ON_SHAFT("items.tbw_spider_on_shaft_cooked", heal = 7, dbRowId = "dbrows.spider_on_shaft_food"), + + // Cooked jubbly + COOKED_JUBBLY("items.100_jubbly_meat_cooked", heal = 15, dbRowId = "dbrows.cooked_jubbly_food"), + + // Hunter meats + COOKED_WILD_KEBBIT("items.wildkebbit_cooked", heal = 4, dbRowId = "dbrows.cooked_wild_kebbit_food"), + COOKED_LARUPIA("items.larupia_cooked", heal = 6, dbRowId = "dbrows.cooked_larupia_food"), + COOKED_BARB_TAILED_KEBBIT("items.barbkebbit_cooked", heal = 7, dbRowId = "dbrows.cooked_barb_tailed_kebbit_food"), + COOKED_GRAAHK("items.graahk_cooked", heal = 8, dbRowId = "dbrows.cooked_graahk_food"), + COOKED_KYATT("items.kyatt_cooked", heal = 9, dbRowId = "dbrows.cooked_kyatt_food"), + COOKED_DASHING_KEBBIT("items.dashingkebbit_cooked", heal = 13, dbRowId = "dbrows.cooked_dashing_kebbit_food"), + + // Potatoes + BAKED_POTATO("items.potato_baked", heal = 4, dbRowId = "dbrows.baked_potato_food"), + POTATO_WITH_BUTTER("items.potato_butter", heal = 14, dbRowId = "dbrows.potato_with_butter_food"), + CHILLI_POTATO("items.potato_chilli+carne", heal = 14, dbRowId = "dbrows.chilli_potato_food"), + EGG_POTATO("items.potato_egg+tomato", heal = 16, dbRowId = "dbrows.egg_potato_food"), + MUSHROOM_POTATO("items.potato_mushroom+onion", heal = 20, dbRowId = "dbrows.mushroom_potato_food"), + + // Bowls + BOWL_OF_CHILLI_CON_CARNE("items.bowl_chilli+carne", "items.bowl_empty", heal = 5, dbRowId = "dbrows.bowl_of_chilli_con_carne_food"), + EGG_AND_TOMATO("items.bowl_egg+tomato", "items.bowl_empty", heal = 8, dbRowId = "dbrows.egg_and_tomato_food"), + MUSHROOM_AND_ONION("items.bowl_mushroom+onion", "items.bowl_empty", heal = 11, dbRowId = "dbrows.mushroom_and_onion_food"), + TUNA_AND_CORN("items.bowl_tuna+sweetcorn", "items.bowl_empty", heal = 13, dbRowId = "dbrows.tuna_and_corn_food"), + + // Dorgesh-Kaan foods + BAT_SHISH("items.dorgesh_bat_shish", heal = 2, dbRowId = "dbrows.bat_shish_food"), + GREEN_GLOOP_SOUP("items.dorgesh_green_gloop_soup", heal = 2, dbRowId = "dbrows.green_gloop_soup_food"), + FROG_SPAWN_GUMBO("items.dorgesh_frog_spawn_gumbo", heal = 3, dbRowId = "dbrows.frog_spawn_gumbo_food"), + FRIED_MUSHROOMS("items.bowl_mushroom_fried", "items.bowl_empty", heal = 5, dbRowId = "dbrows.fried_mushrooms_food"), + SAUTEED_MUSHROOMS("items.dorgesh_sauteed_mushrooms", heal = 6, dbRowId = "dbrows.sauteed_mushrooms_food"), + CAVE_EEL_SUSHI("items.dorgesh_cave_eel_sushi", heal = 7, dbRowId = "dbrows.cave_eel_sushi_food"), + FROG_BURGER("items.dorgesh_frog_burger", heal = 6, dbRowId = "dbrows.frog_burger_food"), + + // Crab meats + RED_CRAB_MEAT("items.red_crab_meat", heal = 8, dbRowId = "dbrows.red_crab_meat_food"), + BLUE_CRAB_MEAT("items.blue_crab_meat", heal = 14, dbRowId = "dbrows.blue_crab_meat_food"), + RAINBOW_CRAB_MEAT("items.rainbow_crab_meat", heal = 19, dbRowId = "dbrows.rainbow_crab_meat_food"), + + // Drinks & ales (most have stat effects) + BEER("items.beer", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.beer_food"), + ASGARNIAN_ALE("items.asgarnian_ale", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.asgarnian_ale_food"), + WIZARDS_MIND_BOMB("items.wizards_mind_bomb", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.wizards_mind_bomb_food"), + GREENMANS_ALE("items.greenmans_ale", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.greenmans_ale_food"), + DRAGON_BITTER("items.dragon_bitter", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.dragon_bitter_food"), + DWARVEN_STOUT("items.dwarven_stout", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.dwarven_stout_food"), + CIDER("items.cider", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.cider_food"), + AXEMANS_FOLLY("items.axemans_folly", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.axemans_folly_food"), + CHEFS_DELIGHT("items.chefs_delight", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.chefs_delight_food"), + SLAYERS_RESPITE("items.slayers_respite", "items.beer_glass", heal = 1, hasEffect = true, dbRowId = "dbrows.slayers_respite_food"), + GROG("items.grog", heal = 3, hasEffect = true, dbRowId = "dbrows.grog_food"), + VODKA("items.vodka", heal = 5, hasEffect = true, dbRowId = "dbrows.vodka_food"), + WHISKY("items.whisky", heal = 5, hasEffect = true, dbRowId = "dbrows.whisky_food"), + GIN("items.gin", heal = 5, hasEffect = true, dbRowId = "dbrows.gin_food"), + BRANDY("items.brandy", heal = 5, hasEffect = true, dbRowId = "dbrows.brandy_food"), + KEG_OF_BEER("items.keg_of_beer", heal = 1, hasEffect = true, dbRowId = "dbrows.keg_of_beer_food"), + KOVACS_GROG("items.kovacs_grog", heal = 3, hasEffect = true, dbRowId = "dbrows.kovacs_grog_food"), + + // Gnome cocktails (combo foods) + FRUIT_BLAST("items.fruit_blast", heal = 9, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.fruit_blast_food"), + PREMADE_FRUIT_BLAST("items.premade_fruit_blast", heal = 9, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.premade_fruit_blast_food"), + PINEAPPLE_PUNCH("items.pineapple_punch", heal = 9, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.pineapple_punch_food"), + PREMADE_PINEAPPLE_PUNCH("items.premade_pineapple_punch", heal = 9, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.premade_pineapple_punch_food"), + SHORT_GREEN_GUY("items.sgg", heal = 5, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.short_green_guy_food"), + PREMADE_SGG("items.premade_sgg", heal = 5, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.premade_sgg_food"), + BLURBERRY_SPECIAL("items.blurberry_special", heal = 7, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.blurberry_special_food"), + PREMADE_BLURBERRY_SPECIAL("items.premade_blurberry_special", heal = 7, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.premade_blurberry_special_food"), + DRUNK_DRAGON("items.drunk_dragon", heal = 5, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.drunk_dragon_food"), + PREMADE_DRUNK_DRAGON("items.premade_drunk_dragon", heal = 5, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.premade_drunk_dragon_food"), + WIZARD_BLIZZARD("items.wizard_blizzard", heal = 5, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.wizard_blizzard_food"), + PREMADE_WIZARD_BLIZZARD("items.premade_wizard_blizzard", heal = 5, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.premade_wizard_blizzard_food"), + CHOCOLATE_SATURDAY("items.aluft_choc_saturday", heal = 5, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.chocolate_saturday_food"), + PREMADE_CHOC_SATURDAY("items.premade_choc_saturday", heal = 5, comboFood = true, eatDelay = listOf(2), combatDelay = listOf(3), dbRowId = "dbrows.premade_choc_saturday_food"), // Cakes CAKE( diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingConstants.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingConstants.kt new file mode 100644 index 00000000..880f38eb --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingConstants.kt @@ -0,0 +1,58 @@ +package org.alter.impl.skills.cooking + +/** + * Cooking skill constants used across recipe definitions and table builders. + */ +object CookingConstants { + + /** + * Trigger types determine how a cooking action is initiated. + */ + object Trigger { + /** Action triggered by using item on a heat source (fire/range). */ + const val HEAT_SOURCE = 0 + /** Action triggered by using one item on another in inventory. */ + const val ITEM_ON_ITEM = 1 + } + + /** + * Outcome kinds determine what type of result a cooking action produces. + */ + object OutcomeKind { + /** Successful cooking result. */ + const val SUCCESS = 0 + /** Failed cooking result (burnt). */ + const val FAIL = 1 + /** Always produced regardless of success/failure (e.g., return containers). */ + const val ALWAYS = 2 + } + + /** Default variant for single-step actions. */ + const val DEFAULT_VARIANT = 0 + + /** Station mask for fire-only cooking. */ + const val STATION_FIRE = 1 + + /** Station mask for range-only cooking. */ + const val STATION_RANGE = 2 + + /** Station mask for cooking on both fire and range. */ + const val STATION_ANY = STATION_FIRE or STATION_RANGE + + /** + * Modifier flags for burn chance profiles. + * Combined as bitmasks to represent equipment/location bonuses. + */ + object ChanceModifier { + /** No modifier (base chance). */ + const val NONE = 0 + /** Cooking gauntlets equipped. */ + const val GAUNTLETS = 1 + /** Hosidius Kitchen range (+5% burn reduction). */ + const val HOSIDIUS_5 = 2 + /** Hosidius Kitchen range with Kourend Hard Diary (+10% burn reduction). */ + const val HOSIDIUS_10 = 4 + /** Lumbridge Castle range (reduced burn for low-level foods). */ + const val LUMBRIDGE = 8 + } +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingHelpers.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingHelpers.kt new file mode 100644 index 00000000..2fc839fb --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingHelpers.kt @@ -0,0 +1,228 @@ +package org.alter.impl.skills.cooking + +import org.alter.impl.skills.cooking.CookingConstants.DEFAULT_VARIANT +import org.alter.impl.skills.cooking.CookingConstants.OutcomeKind +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_FIRE +import org.alter.impl.skills.cooking.CookingConstants.Trigger + +/** + * Helper functions for building cooking recipe definitions. + */ +object CookingHelpers { + + /** + * Creates a [ChanceDef] for burn chance profile definitions. + * + * @param label Human-readable label (e.g., "base_any", "gauntlets", "hosidius_5"). + * @param stationMask Bitmask of stations this profile applies to. + * @param modifierMask Bitmask of required modifiers (default: none). + * @param low The low value for the statrandom calculation. + * @param high The high value for the statrandom calculation. + */ + fun chance( + label: String, + stationMask: Int, + modifierMask: Int = 0, + low: Int, + high: Int + ): ChanceDef = ChanceDef(label, stationMask, modifierMask, low, high) + + /** + * Creates a simple heat-cooking action (raw -> cooked/burnt). + */ + fun heatCook( + rowKey: String, + raw: String, + cooked: String, + burnt: String, + level: Int, + xp: Int, + stopBurnFire: Int = 0, + stopBurnRange: Int = 0, + stationMask: Int = STATION_ANY, + chances: List = emptyList() + ): ActionDef = ActionDef( + rowId = "dbrows.$rowKey", + key = raw, + level = level, + stopBurnFire = stopBurnFire, + stopBurnRange = stopBurnRange, + stationMask = stationMask, + trigger = Trigger.HEAT_SOURCE, + inputs = listOf(InputDef(raw, 1)), + outcomes = listOf( + OutcomeDef(rowSuffix = "success", kind = OutcomeKind.SUCCESS, item = cooked, xp = xp, weight = 1), + OutcomeDef(rowSuffix = "fail", kind = OutcomeKind.FAIL, item = burnt, xp = 0, weight = 1) + ), + chances = chances + ) + + /** + * Creates a spit-roasting recipe (2 actions: skewer + roast). + * + * @param skewerRowKey DB row key for the skewering step. + * @param roastRowKey DB row key for the roasting step. + * @param rawMeat RSCM key for the raw meat. + * @param skewered RSCM key for the skewered meat. + * @param cooked RSCM key for the roasted meat. + * @param burnt RSCM key for the burnt meat. + * @param cookingLevel Required Cooking level for roasting. + * @param xp Experience awarded on successful roast. + * @param stopBurnFire Level at which burning stops on fires. + * @param stopBurnRange Level at which burning stops on ranges. + * @param spitItem RSCM key for the spit item (default: iron spit). + */ + fun spitRoast( + skewerRowKey: String, + roastRowKey: String, + rawMeat: String, + skewered: String, + cooked: String, + burnt: String, + cookingLevel: Int, + xp: Int, + stopBurnFire: Int = 0, + stopBurnRange: Int = 0, + spitItem: String = "items.spit_iron", + chances: List = emptyList() + ): List = listOf( + // Raw meat + iron spit -> skewered meat (inventory prep step) + ActionDef( + rowId = "dbrows.$skewerRowKey", + key = skewered, + variant = 1, + level = 1, + stopBurnFire = 1, + stopBurnRange = 1, + stationMask = STATION_ANY, + trigger = Trigger.ITEM_ON_ITEM, + inputs = listOf( + InputDef(rawMeat, 1), + InputDef(spitItem, 1) + ), + outcomes = listOf( + OutcomeDef(rowSuffix = "success", kind = OutcomeKind.SUCCESS, item = skewered, xp = 0, weight = 1) + ) + ), + + // Skewered meat -> roasted/burnt (fire only) + ActionDef( + rowId = "dbrows.$roastRowKey", + key = skewered, + variant = DEFAULT_VARIANT, + level = cookingLevel, + stopBurnFire = stopBurnFire, + stopBurnRange = stopBurnRange, + stationMask = STATION_FIRE, + trigger = Trigger.HEAT_SOURCE, + inputs = listOf(InputDef(skewered, 1)), + outcomes = listOf( + OutcomeDef(rowSuffix = "success", kind = OutcomeKind.SUCCESS, item = cooked, xp = xp, weight = 1), + OutcomeDef(rowSuffix = "fail", kind = OutcomeKind.FAIL, item = burnt, xp = 0, weight = 1) + ), + chances = chances + ) + ) + + /** + * Creates a multi-step cooking recipe with preparation and optional baking. + * + * @param key RSCM key identifying this recipe group. + * @param level Required Cooking level. + * @param prepSteps List of inventory preparation steps. + * @param heatStep Optional final heat-source cooking step. + * @param prepVariantStart Starting variant number for prep steps. + * @param heatVariant Variant number for the heat step. + */ + fun multiStepCook( + key: String, + level: Int, + prepSteps: List, + heatStep: HeatStepDef? = null, + prepVariantStart: Int = 1, + heatVariant: Int = DEFAULT_VARIANT + ): List = buildList { + prepSteps.forEachIndexed { index, step -> + val alwaysOutcomes = step.always.mapIndexed { alwaysIndex, (item, count) -> + OutcomeDef( + rowSuffix = "always_$alwaysIndex", + kind = OutcomeKind.ALWAYS, + item = item, + countMin = count, + countMax = count, + xp = 0, + weight = 1 + ) + } + + add( + ActionDef( + rowId = "dbrows.${step.rowKey}", + key = key, + variant = prepVariantStart + index, + level = level, + stopBurnFire = level, + stopBurnRange = level, + stationMask = STATION_ANY, + trigger = Trigger.ITEM_ON_ITEM, + inputs = step.inputs.map { (item, count) -> InputDef(item, count) }, + outcomes = listOf( + OutcomeDef( + rowSuffix = "success", + kind = OutcomeKind.SUCCESS, + item = step.output, + xp = step.xp, + weight = 1 + ) + ) + alwaysOutcomes + ) + ) + } + + heatStep?.let { step -> + val alwaysOutcomes = step.always.mapIndexed { alwaysIndex, (item, count) -> + OutcomeDef( + rowSuffix = "always_$alwaysIndex", + kind = OutcomeKind.ALWAYS, + item = item, + countMin = count, + countMax = count, + xp = 0, + weight = 1 + ) + } + + add( + ActionDef( + rowId = "dbrows.${step.rowKey}", + key = key, + variant = heatVariant, + level = level, + stopBurnFire = step.stopBurnFire, + stopBurnRange = step.stopBurnRange, + stationMask = step.stationMask, + trigger = Trigger.HEAT_SOURCE, + inputs = listOf(InputDef(step.raw, 1)), + outcomes = listOf( + OutcomeDef( + rowSuffix = "success", + kind = OutcomeKind.SUCCESS, + item = step.cooked, + xp = step.xp, + weight = 1 + ), + OutcomeDef( + rowSuffix = "fail", + kind = OutcomeKind.FAIL, + item = step.burnt, + xp = 0, + weight = 1 + ) + ) + alwaysOutcomes, + chances = step.chances + ) + ) + } + } +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingModels.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingModels.kt new file mode 100644 index 00000000..40ee6df4 --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingModels.kt @@ -0,0 +1,129 @@ +package org.alter.impl.skills.cooking + +/** + * Data models for cooking recipe definitions. + */ + +/** + * Defines a burn chance profile for a specific station and modifier combination. + * + * At runtime, the system selects the most specific matching profile based on the + * player's station and active modifiers, then uses [low]/[high] with the + * `computeSkillingSuccess` function to determine burn probability. + * + * @property label Human-readable label for this profile (e.g., "base_any", "gauntlets"). + * @property stationMask Bitmask of stations this profile applies to. + * @property modifierMask Bitmask of required modifiers (see [CookingConstants.ChanceModifier]). + * @property low The low value for the statrandom calculation. + * @property high The high value for the statrandom calculation. + */ +data class ChanceDef( + val label: String, + val stationMask: Int, + val modifierMask: Int = 0, + val low: Int, + val high: Int +) + +/** + * Defines an input ingredient for a cooking action. + * + * @property item RSCM key for the item (e.g., "items.raw_shrimp"). + * @property count Number of this item consumed per action. + */ +data class InputDef( + val item: String, + val count: Int = 1 +) + +/** + * Defines a possible outcome of a cooking action. + * + * @property rowSuffix Unique suffix for the DB row (e.g., "success", "fail"). + * @property kind The outcome type (SUCCESS, FAIL, or ALWAYS). + * @property item RSCM key for the produced item. + * @property countMin Minimum quantity produced. + * @property countMax Maximum quantity produced. + * @property xp Experience awarded for this outcome. + * @property weight Weighted chance for this outcome when multiple exist. + */ +data class OutcomeDef( + val rowSuffix: String, + val kind: Int, + val item: String, + val countMin: Int = 1, + val countMax: Int = 1, + val xp: Int = 0, + val weight: Int = 1 +) + +/** + * Defines a complete cooking action with inputs, outcomes, and requirements. + * + * @property rowId DB row identifier (e.g., "dbrows.cooking_shrimps"). + * @property key RSCM key used to identify this action group. + * @property variant Variant number for multi-step recipes. + * @property level Required Cooking level. + * @property stopBurnFire Level at which burning stops on fires. + * @property stopBurnRange Level at which burning stops on ranges. + * @property stationMask Bitmask for allowed cooking stations. + * @property trigger How the action is initiated (HEAT_SOURCE or ITEM_ON_ITEM). + * @property inputs List of required input items. + * @property outcomes List of possible outcomes. + */ +data class ActionDef( + val rowId: String, + val key: String, + val variant: Int = CookingConstants.DEFAULT_VARIANT, + val level: Int, + val stopBurnFire: Int = 0, + val stopBurnRange: Int = 0, + val stationMask: Int = CookingConstants.STATION_ANY, + val trigger: Int = CookingConstants.Trigger.HEAT_SOURCE, + val inputs: List, + val outcomes: List, + val chances: List = emptyList() +) + +/** + * Defines a preparation step for multi-step cooking (inventory item-on-item). + * + * @property rowKey DB row key suffix. + * @property inputs List of (item RSCM key, count) pairs consumed. + * @property output RSCM key for the produced item. + * @property xp Experience awarded. + * @property always Items always returned (e.g., empty containers). + */ +data class PrepStepDef( + val rowKey: String, + val inputs: List>, + val output: String, + val xp: Int = 0, + val always: List> = emptyList() +) + +/** + * Defines the final heat-source step for multi-step cooking. + * + * @property rowKey DB row key suffix. + * @property raw RSCM key for the uncooked item. + * @property cooked RSCM key for the successfully cooked item. + * @property burnt RSCM key for the burnt item. + * @property xp Experience awarded on success. + * @property stopBurnFire Level at which burning stops on fires. + * @property stopBurnRange Level at which burning stops on ranges. + * @property stationMask Bitmask for allowed cooking stations. + * @property always Items always returned (e.g., cake tin). + */ +data class HeatStepDef( + val rowKey: String, + val raw: String, + val cooked: String, + val burnt: String, + val xp: Int, + val stopBurnFire: Int = 0, + val stopBurnRange: Int = 0, + val stationMask: Int = CookingConstants.STATION_ANY, + val always: List> = emptyList(), + val chances: List = emptyList() +) diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingRecipeRegistry.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingRecipeRegistry.kt new file mode 100644 index 00000000..646630e6 --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingRecipeRegistry.kt @@ -0,0 +1,36 @@ +package org.alter.impl.skills.cooking + +import org.alter.impl.skills.cooking.recipes.BakedGoodsRecipes +import org.alter.impl.skills.cooking.recipes.BreadRecipes +import org.alter.impl.skills.cooking.recipes.FishRecipes +import org.alter.impl.skills.cooking.recipes.MeatRecipes +import org.alter.impl.skills.cooking.recipes.MiscFoodRecipes +import org.alter.impl.skills.cooking.recipes.PieRecipes +import org.alter.impl.skills.cooking.recipes.PizzaRecipes +import org.alter.impl.skills.cooking.recipes.PotatoRecipes +import org.alter.impl.skills.cooking.recipes.StewRecipes +import org.alter.impl.skills.cooking.recipes.WineRecipes + +/** + * Central registry of all cooking recipes. + * + * Add new recipe categories here as they are implemented. + */ +object CookingRecipeRegistry { + + /** + * All cooking action definitions from all recipe categories. + */ + val allRecipes: List by lazy { + FishRecipes.recipes + + MeatRecipes.recipes + + BakedGoodsRecipes.recipes + + BreadRecipes.recipes + + PieRecipes.recipes + + PizzaRecipes.recipes + + StewRecipes.recipes + + PotatoRecipes.recipes + + MiscFoodRecipes.recipes + + WineRecipes.recipes + } +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingTables.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingTables.kt new file mode 100644 index 00000000..71a3caaa --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingTables.kt @@ -0,0 +1,110 @@ +package org.alter.impl.skills.cooking + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +/** + * DB table builders for cooking skill data. + * + * These functions generate the cache tables used by the game client and server + * to look up cooking recipes, inputs, and outcomes. + */ +object CookingTables { + + private const val ACTION_KEY = 0 + private const val ACTION_VARIANT = 1 + private const val ACTION_LEVEL = 2 + private const val ACTION_STOP_BURN_FIRE = 3 + private const val ACTION_STOP_BURN_RANGE = 4 + private const val ACTION_STATION_MASK = 5 + private const val ACTION_TRIGGER = 6 + + /** + * Builds the cooking_actions DB table containing action metadata. + */ + fun actions() = dbTable("tables.cooking_actions") { + column("key", ACTION_KEY, VarType.OBJ) + column("variant", ACTION_VARIANT, VarType.INT) + column("level", ACTION_LEVEL, VarType.INT) + column("stop_burn_fire", ACTION_STOP_BURN_FIRE, VarType.INT) + column("stop_burn_range", ACTION_STOP_BURN_RANGE, VarType.INT) + column("station_mask", ACTION_STATION_MASK, VarType.INT) + column("trigger", ACTION_TRIGGER, VarType.INT) + + CookingRecipeRegistry.allRecipes.forEach { action -> + row(action.rowId) { + columnRSCM(ACTION_KEY, action.key) + column(ACTION_VARIANT, action.variant) + column(ACTION_LEVEL, action.level) + column(ACTION_STOP_BURN_FIRE, action.stopBurnFire) + column(ACTION_STOP_BURN_RANGE, action.stopBurnRange) + column(ACTION_STATION_MASK, action.stationMask) + column(ACTION_TRIGGER, action.trigger) + } + } + } + + private const val INPUT_KEY = 0 + private const val INPUT_VARIANT = 1 + private const val INPUT_ITEM = 2 + private const val INPUT_COUNT = 3 + + /** + * Builds the cooking_action_inputs DB table containing input requirements. + */ + fun actionInputs() = dbTable("tables.cooking_action_inputs") { + column("key", INPUT_KEY, VarType.OBJ) + column("variant", INPUT_VARIANT, VarType.INT) + column("item", INPUT_ITEM, VarType.OBJ) + column("count", INPUT_COUNT, VarType.INT) + + CookingRecipeRegistry.allRecipes.forEach { action -> + action.inputs.forEachIndexed { index, input -> + row("${action.rowId}_input_$index") { + columnRSCM(INPUT_KEY, action.key) + column(INPUT_VARIANT, action.variant) + columnRSCM(INPUT_ITEM, input.item) + column(INPUT_COUNT, input.count) + } + } + } + } + + private const val OUTCOME_KEY = 0 + private const val OUTCOME_VARIANT = 1 + private const val OUTCOME_KIND = 2 + private const val OUTCOME_ITEM = 3 + private const val OUTCOME_COUNT_MIN = 4 + private const val OUTCOME_COUNT_MAX = 5 + private const val OUTCOME_XP = 6 + private const val OUTCOME_WEIGHT = 7 + + /** + * Builds the cooking_action_outcomes DB table containing possible results. + */ + fun actionOutcomes() = dbTable("tables.cooking_action_outcomes") { + column("key", OUTCOME_KEY, VarType.OBJ) + column("variant", OUTCOME_VARIANT, VarType.INT) + column("kind", OUTCOME_KIND, VarType.INT) + column("item", OUTCOME_ITEM, VarType.OBJ) + column("count_min", OUTCOME_COUNT_MIN, VarType.INT) + column("count_max", OUTCOME_COUNT_MAX, VarType.INT) + column("xp", OUTCOME_XP, VarType.INT) + column("weight", OUTCOME_WEIGHT, VarType.INT) + + CookingRecipeRegistry.allRecipes.forEach { action -> + action.outcomes.forEach { outcome -> + row("${action.rowId}_outcome_${outcome.rowSuffix}") { + columnRSCM(OUTCOME_KEY, action.key) + column(OUTCOME_VARIANT, action.variant) + column(OUTCOME_KIND, outcome.kind) + columnRSCM(OUTCOME_ITEM, outcome.item) + column(OUTCOME_COUNT_MIN, outcome.countMin) + column(OUTCOME_COUNT_MAX, outcome.countMax) + column(OUTCOME_XP, outcome.xp) + column(OUTCOME_WEIGHT, outcome.weight) + } + } + } + } +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/BakedGoodsRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/BakedGoodsRecipes.kt new file mode 100644 index 00000000..a2298457 --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/BakedGoodsRecipes.kt @@ -0,0 +1,106 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Baked goods recipes - pies, cakes, and other range-baked items. + */ +object BakedGoodsRecipes { + + /** Pie recipes (multi-step prep + range bake). */ + val pieRecipes: List = multiStepCook( + key = "items.uncooked_garden_pie", + level = 34, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_garden_pie_add_tomato", + inputs = listOf( + "items.pie_shell" to 1, + "items.tomato" to 1 + ), + output = "items.unfinished_garden_pie_1" + ), + PrepStepDef( + rowKey = "cooking_garden_pie_add_onion", + inputs = listOf( + "items.unfinished_garden_pie_1" to 1, + "items.onion" to 1 + ), + output = "items.unfinished_garden_pie_2" + ), + PrepStepDef( + rowKey = "cooking_garden_pie_add_cabbage", + inputs = listOf( + "items.unfinished_garden_pie_2" to 1, + "items.cabbage" to 1 + ), + output = "items.uncooked_garden_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_garden_pie_bake", + raw = "items.uncooked_garden_pie", + cooked = "items.garden_pie", + burnt = "items.burnt_pie", + xp = 138, + stopBurnFire = 68, + stopBurnRange = 64, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 48, high = 352), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 60, high = 364), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 73, high = 377) + ) + ) + ) + + /** Cake recipes (multi-step prep + range bake). */ + val cakeRecipes: List = multiStepCook( + key = "items.uncooked_cake", + level = 40, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_cake_mix", + inputs = listOf( + "items.cake_tin" to 1, + "items.egg" to 1, + "items.bucket_milk" to 1, + "items.pot_flour" to 1 + ), + output = "items.uncooked_cake", + always = listOf( + "items.bucket_empty" to 1, + "items.pot_empty" to 1 + ) + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_cake_bake", + raw = "items.uncooked_cake", + cooked = "items.cake", + burnt = "items.burnt_cake", + xp = 180, + stopBurnFire = 74, + stopBurnRange = 70, + stationMask = STATION_RANGE, + always = listOf( + "items.cake_tin" to 1 + ), + chances = listOf( + chance("range", STATION_RANGE, low = 38, high = 332), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357) + ) + ) + ) + + /** All baked goods recipes combined. */ + val recipes: List = pieRecipes + cakeRecipes +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/BreadRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/BreadRecipes.kt new file mode 100644 index 00000000..879233fe --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/BreadRecipes.kt @@ -0,0 +1,47 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook + +/** + * Bread cooking recipes — baked on a range. + * + * Chance profiles sourced from OSRS Wiki skill_chances data. + */ +object BreadRecipes { + + val recipes: List = listOf( + heatCook( + rowKey = "cooking_bread", + raw = "items.bread_dough", + cooked = "items.bread", + burnt = "items.burnt_bread", + level = 1, xp = 40, stopBurnFire = 35, stopBurnRange = 35, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 118, high = 492), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 128, high = 512), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 130, high = 504), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 143, high = 517) + ) + ), + heatCook( + rowKey = "cooking_pitta_bread", + raw = "items.uncooked_pitta_bread", + cooked = "items.pitta_bread", + burnt = "items.burnt_pitta_bread", + level = 58, xp = 40, stopBurnFire = 82, stopBurnRange = 82, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 118, high = 492), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 130, high = 504), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 143, high = 517) + ) + ) + ) +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/FishRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/FishRecipes.kt new file mode 100644 index 00000000..1b324950 --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/FishRecipes.kt @@ -0,0 +1,381 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.GAUNTLETS +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_FIRE +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook + +/** + * Fish cooking recipes — standard heat-source cooking. + * + * Chance profiles sourced from OSRS Wiki skill_chances data. + */ +object FishRecipes { + + val recipes: List = listOf( + // ---- Low-level fish (shared fire/range curve + Lumbridge) ---- + heatCook( + rowKey = "cooking_shrimps", + raw = "items.raw_shrimp", + cooked = "items.shrimp", + burnt = "items.burnt_shrimp", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_anchovies", + raw = "items.raw_anchovies", + cooked = "items.anchovies", + burnt = "items.burntfish1", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_sardine", + raw = "items.raw_sardine", + cooked = "items.sardine", + burnt = "items.burntfish5", + level = 1, xp = 40, stopBurnFire = 38, stopBurnRange = 38, + chances = listOf( + chance("base_any", STATION_ANY, low = 118, high = 492), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 128, high = 512), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 130, high = 504), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 143, high = 517) + ) + ), + heatCook( + rowKey = "cooking_herring", + raw = "items.raw_herring", + cooked = "items.herring", + burnt = "items.burntfish3", + level = 5, xp = 50, stopBurnFire = 41, stopBurnRange = 41, + chances = listOf( + chance("base_any", STATION_ANY, low = 108, high = 472), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 118, high = 492), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 120, high = 484), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 133, high = 497) + ) + ), + heatCook( + rowKey = "cooking_mackerel", + raw = "items.raw_mackerel", + cooked = "items.mackerel", + burnt = "items.burntfish3", + level = 10, xp = 60, stopBurnFire = 45, stopBurnRange = 45, + chances = listOf( + chance("base_any", STATION_ANY, low = 98, high = 452), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 108, high = 472), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 110, high = 464), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 123, high = 477) + ) + ), + heatCook( + rowKey = "cooking_trout", + raw = "items.raw_trout", + cooked = "items.trout", + burnt = "items.burntfish2", + level = 15, xp = 70, stopBurnFire = 49, stopBurnRange = 49, + chances = listOf( + chance("base_any", STATION_ANY, low = 88, high = 432), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 98, high = 452), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 100, high = 444), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 113, high = 457) + ) + ), + + // ---- Mid-level fish ---- + heatCook( + rowKey = "cooking_cod", + raw = "items.raw_cod", + cooked = "items.cod", + burnt = "items.burntfish2", + level = 18, xp = 75, stopBurnFire = 51, stopBurnRange = 49, + chances = listOf( + chance("fire", STATION_FIRE, low = 83, high = 422), + chance("range", STATION_RANGE, low = 88, high = 432), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 93, high = 442), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 100, high = 444), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 113, high = 457) + ) + ), + heatCook( + rowKey = "cooking_pike", + raw = "items.raw_pike", + cooked = "items.pike", + burnt = "items.burntfish5", + level = 20, xp = 80, stopBurnFire = 54, stopBurnRange = 54, + chances = listOf( + chance("base_any", STATION_ANY, low = 78, high = 412), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 88, high = 432), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 90, high = 424), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 103, high = 437) + ) + ), + heatCook( + rowKey = "cooking_salmon", + raw = "items.raw_salmon", + cooked = "items.salmon", + burnt = "items.burntfish2", + level = 25, xp = 90, stopBurnFire = 58, stopBurnRange = 58, + chances = listOf( + chance("base_any", STATION_ANY, low = 68, high = 392), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 78, high = 402), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 80, high = 404), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 93, high = 417) + ) + ), + heatCook( + rowKey = "cooking_slimy_eel", + raw = "items.mort_slimey_eel", + cooked = "items.mort_slimey_eel_cooked", + burnt = "items.burnt_eel", + level = 28, xp = 95, stopBurnFire = 61, stopBurnRange = 61, + chances = listOf( + chance("base_any", STATION_ANY, low = 63, high = 382), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 75, high = 394), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 88, high = 407) + ) + ), + heatCook( + rowKey = "cooking_tuna", + raw = "items.raw_tuna", + cooked = "items.tuna", + burnt = "items.burntfish4", + level = 30, xp = 100, stopBurnFire = 63, stopBurnRange = 63, + chances = listOf( + chance("base_any", STATION_ANY, low = 58, high = 372), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 70, high = 384), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 83, high = 397) + ) + ), + + // ---- Mid-high fish (gauntlet-affected) ---- + heatCook( + rowKey = "cooking_lobster", + raw = "items.raw_lobster", + cooked = "items.lobster", + burnt = "items.burnt_lobster", + level = 40, xp = 120, stopBurnFire = 74, stopBurnRange = 74, + chances = listOf( + chance("base_any", STATION_ANY, low = 38, high = 332), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 55, high = 368), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357), + chance("hosidius_5_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_5 or GAUNTLETS, low = 67, high = 380), + chance("hosidius_10_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_10 or GAUNTLETS, low = 80, high = 393) + ) + ), + heatCook( + rowKey = "cooking_bass", + raw = "items.raw_bass", + cooked = "items.bass", + burnt = "items.burntfish3", + level = 43, xp = 130, stopBurnFire = 79, stopBurnRange = 79, + chances = listOf( + chance("base_any", STATION_ANY, low = 33, high = 312), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 45, high = 324), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 58, high = 337) + ) + ), + heatCook( + rowKey = "cooking_swordfish", + raw = "items.raw_swordfish", + cooked = "items.swordfish", + burnt = "items.burnt_swordfish", + level = 45, xp = 140, stopBurnFire = 86, stopBurnRange = 80, + chances = listOf( + chance("fire", STATION_FIRE, low = 18, high = 292), + chance("range", STATION_RANGE, low = 30, high = 310), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 30, high = 310), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 42, high = 322), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 55, high = 335) + ) + ), + + // ---- High-level fish ---- + heatCook( + rowKey = "cooking_monkfish", + raw = "items.raw_monkfish", + cooked = "items.monkfish", + burnt = "items.burnt_monkfish", + level = 62, xp = 150, stopBurnFire = 92, stopBurnRange = 90, + chances = listOf( + chance("fire", STATION_FIRE, low = 11, high = 275), + chance("range", STATION_RANGE, low = 13, high = 280), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 24, high = 290), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 25, high = 292), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 38, high = 305), + chance("hosidius_5_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_5 or GAUNTLETS, low = 36, high = 302), + chance("hosidius_10_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_10 or GAUNTLETS, low = 49, high = 315) + ) + ), + heatCook( + rowKey = "cooking_shark", + raw = "items.raw_shark", + cooked = "items.shark", + burnt = "items.burnt_shark", + level = 80, xp = 210, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 202), + chance("range", STATION_RANGE, low = 1, high = 232), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 15, high = 270), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 244), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 257), + chance("hosidius_5_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_5 or GAUNTLETS, low = 27, high = 282), + chance("hosidius_10_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_10 or GAUNTLETS, low = 40, high = 295) + ) + ), + heatCook( + rowKey = "cooking_sea_turtle", + raw = "items.raw_seaturtle", + cooked = "items.seaturtle", + burnt = "items.burnt_seaturtle", + level = 82, xp = 211, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 202), + chance("range", STATION_RANGE, low = 1, high = 222), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 234), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 247) + ) + ), + heatCook( + rowKey = "cooking_anglerfish", + raw = "items.raw_anglerfish", + cooked = "items.anglerfish", + burnt = "items.burnt_anglerfish", + level = 84, xp = 230, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 200), + chance("range", STATION_RANGE, low = 1, high = 220), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 12, high = 260), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 232), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 245), + chance("hosidius_5_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_5 or GAUNTLETS, low = 24, high = 272), + chance("hosidius_10_gauntlets", STATION_RANGE, modifierMask = HOSIDIUS_10 or GAUNTLETS, low = 37, high = 285) + ) + ), + heatCook( + rowKey = "cooking_dark_crab", + raw = "items.raw_dark_crab", + cooked = "items.dark_crab", + burnt = "items.burnt_dark_crab", + level = 90, xp = 215, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("base_any", STATION_ANY, low = 10, high = 222), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 22, high = 234), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 35, high = 247) + ) + ), + heatCook( + rowKey = "cooking_manta_ray", + raw = "items.raw_mantaray", + cooked = "items.mantaray", + burnt = "items.burnt_mantaray", + level = 91, xp = 216, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 202), + chance("range", STATION_RANGE, low = 1, high = 222), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 234), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 247) + ) + ), + + // ---- Special fish ---- + heatCook( + rowKey = "cooking_karambwan", + raw = "items.tbwt_raw_karambwan", + cooked = "items.tbwt_cooked_karambwan", + burnt = "items.tbwt_burnt_karambwan", + level = 30, xp = 190, stopBurnFire = 99, stopBurnRange = 93, + chances = listOf( + chance("base_any", STATION_ANY, low = 70, high = 255), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 82, high = 267), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 95, high = 280) + ) + ), + heatCook( + rowKey = "cooking_rainbow_fish", + raw = "items.hunting_raw_fish_special", + cooked = "items.hunting_fish_special", + burnt = "items.burntfish2", + level = 35, xp = 110, stopBurnFire = 64, stopBurnRange = 60, + chances = listOf( + chance("base_any", STATION_ANY, low = 56, high = 370), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 68, high = 382), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 81, high = 395) + ) + ), + heatCook( + rowKey = "cooking_cave_eel", + raw = "items.raw_cave_eel", + cooked = "items.cave_eel", + burnt = "items.burnt_cave_eel", + level = 38, xp = 115, stopBurnFire = 74, stopBurnRange = 70, + chances = listOf( + chance("base_any", STATION_ANY, low = 38, high = 332), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357) + ) + ), + + // ---- Snails ---- + heatCook( + rowKey = "cooking_thin_snail", + raw = "items.snail_corpse1", + cooked = "items.snail_corpse_cooked1", + burnt = "items.burnt_snail", + level = 12, xp = 70, stopBurnFire = 47, stopBurnRange = 47, + stationMask = STATION_FIRE, + chances = listOf( + chance("base_any", STATION_ANY, low = 93, high = 444), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 103, high = 464), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 105, high = 456), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 118, high = 469) + ) + ), + heatCook( + rowKey = "cooking_lean_snail", + raw = "items.snail_corpse2", + cooked = "items.snail_corpse_cooked2", + burnt = "items.burnt_snail", + level = 17, xp = 80, stopBurnFire = 47, stopBurnRange = 50, + chances = listOf( + chance("fire", STATION_FIRE, low = 93, high = 444), + chance("range", STATION_RANGE, low = 85, high = 428), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 95, high = 448), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 97, high = 440), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 110, high = 453) + ) + ), + heatCook( + rowKey = "cooking_fat_snail", + raw = "items.snail_corpse3", + cooked = "items.snail_corpse_cooked3", + burnt = "items.burnt_snail", + level = 22, xp = 95, stopBurnFire = 56, stopBurnRange = 56, + stationMask = STATION_FIRE, + chances = listOf( + chance("base_any", STATION_ANY, low = 73, high = 402), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 83, high = 422), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 85, high = 414), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 98, high = 427) + ) + ) + ) +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/MeatRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/MeatRecipes.kt new file mode 100644 index 00000000..5b9ac7ba --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/MeatRecipes.kt @@ -0,0 +1,237 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.GAUNTLETS +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_FIRE +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook +import org.alter.impl.skills.cooking.CookingHelpers.spitRoast + +/** + * Meat cooking recipes — standard cooking and spit roasting. + * + * Chance profiles sourced from OSRS Wiki skill_chances data. + */ +object MeatRecipes { + + /** Basic meat cooking recipes (fire/range). */ + val basicMeatRecipes: List = listOf( + heatCook( + rowKey = "cooking_chompy", + raw = "items.raw_chompy", + cooked = "items.cooked_chompy", + burnt = "items.ruined_chompy", + level = 30, xp = 100, stopBurnFire = 63, stopBurnRange = 63, + stationMask = STATION_FIRE, + chances = listOf( + chance("fire", STATION_FIRE, low = 200, high = 255) + ) + ), + heatCook( + rowKey = "cooking_beef", + raw = "items.raw_beef", + cooked = "items.cooked_meat", + burnt = "items.burnt_meat", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_chicken", + raw = "items.raw_chicken", + cooked = "items.cooked_chicken", + burnt = "items.burnt_chicken", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_rat_meat", + raw = "items.raw_rat_meat", + cooked = "items.cooked_meat", + burnt = "items.burnt_meat", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_bear_meat", + raw = "items.raw_bear_meat", + cooked = "items.cooked_meat", + burnt = "items.burnt_meat", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 138, high = 532), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_rabbit", + raw = "items.raw_rabbit", + cooked = "items.cooked_rabbit", + burnt = "items.burnt_meat", + level = 1, xp = 30, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("base_any", STATION_ANY, low = 128, high = 512), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 140, high = 524), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 153, high = 537) + ) + ), + heatCook( + rowKey = "cooking_ugthanki_meat", + raw = "items.raw_ugthanki_meat", + cooked = "items.cooked_ugthanki_meat", + burnt = "items.burnt_meat", + level = 1, xp = 40, stopBurnFire = 34, stopBurnRange = 34, + chances = listOf( + chance("fire", STATION_FIRE, low = 40, high = 252), + chance("range", STATION_RANGE, low = 30, high = 253), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 42, high = 265), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 55, high = 278) + ) + ) + ) + + /** Spit roasting recipes (skewer + fire-only roast). */ + val spitRoastRecipes: List = + spitRoast( + skewerRowKey = "cooking_skewer_bird_meat", + roastRowKey = "cooking_roast_bird_meat", + rawMeat = "items.spit_raw_bird_meat", + skewered = "items.spit_skewered_bird_meat", + cooked = "items.spit_roasted_bird_meat", + burnt = "items.spit_burned_bird_meat", + cookingLevel = 11, xp = 60, stopBurnFire = 44, stopBurnRange = 44, + chances = listOf(chance("fire", STATION_FIRE, low = 155, high = 255)) + ) + + spitRoast( + skewerRowKey = "cooking_skewer_beast_meat", + roastRowKey = "cooking_roast_beast_meat", + rawMeat = "items.spit_raw_beast_meat", + skewered = "items.spit_skewered_beast_meat", + cooked = "items.spit_roasted_beast_meat", + burnt = "items.spit_burned_beast_meat", + cookingLevel = 21, xp = 82, stopBurnFire = 55, stopBurnRange = 55, + chances = listOf(chance("fire", STATION_FIRE, low = 180, high = 255)) + ) + + spitRoast( + skewerRowKey = "cooking_skewer_rabbit_meat", + roastRowKey = "cooking_roast_rabbit_meat", + rawMeat = "items.raw_rabbit", + skewered = "items.spit_skewered_rabbit_meat", + cooked = "items.spit_roasted_rabbit_meat", + burnt = "items.spit_burned_rabbit_meat", + cookingLevel = 16, xp = 70, stopBurnFire = 99, stopBurnRange = 99, + chances = listOf(chance("fire", STATION_FIRE, low = 160, high = 255)) + ) + + /** + * Hunter meat recipes — meats obtained from pitfall trapping. + * + * Note: Small kebbits (wild, barb-tailed, dashing) are eaten raw and do NOT + * require cooking. Only pitfall-trapped animals have raw→cooked transitions. + */ + val hunterMeatRecipes: List = listOf( + heatCook( + rowKey = "cooking_larupia", + raw = "items.hunting_larupia_meat", + cooked = "items.larupia_cooked", + burnt = "items.burnt_largebeast", + level = 31, xp = 92, stopBurnFire = 59, stopBurnRange = 59, + stationMask = STATION_FIRE, + chances = listOf( + chance("base_any", STATION_ANY, low = 65, high = 390), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 77, high = 402), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 90, high = 415) + ) + ), + heatCook( + rowKey = "cooking_graahk", + raw = "items.hunting_graahk_meat", + cooked = "items.graahk_cooked", + burnt = "items.burnt_largebeast", + level = 41, xp = 124, stopBurnFire = 75, stopBurnRange = 75, + stationMask = STATION_FIRE, + chances = listOf( + chance("base_any", STATION_ANY, low = 32, high = 328), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 44, high = 340), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 57, high = 353) + ) + ), + heatCook( + rowKey = "cooking_kyatt", + raw = "items.hunting_kyatt_meat", + cooked = "items.kyatt_cooked", + burnt = "items.burnt_largebeast", + level = 51, xp = 143, stopBurnFire = 86, stopBurnRange = 80, + chances = listOf( + chance("fire", STATION_FIRE, low = 18, high = 292), + chance("range", STATION_RANGE, low = 30, high = 310), + chance("gauntlets", STATION_ANY, modifierMask = GAUNTLETS, low = 30, high = 310), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 42, high = 322), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 55, high = 335) + ) + ), + heatCook( + rowKey = "cooking_pyre_fox", + raw = "items.hunting_fennecfox_meat", + cooked = "items.fennecfox_cooked", + burnt = "items.burnt_foxmeat", + level = 59, xp = 154, stopBurnFire = 93, stopBurnRange = 92, + chances = listOf( + chance("fire", STATION_FIRE, low = 10, high = 273), + chance("range", STATION_RANGE, low = 11, high = 276), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 23, high = 288), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 36, high = 301) + ) + ), + heatCook( + rowKey = "cooking_sunlight_antelope", + raw = "items.hunting_antelopesun_meat", + cooked = "items.antelopesun_cooked", + burnt = "items.burnt_antelope", + level = 68, xp = 175, stopBurnFire = 100, stopBurnRange = 95, + chances = listOf( + chance("fire", STATION_FIRE, low = 8, high = 254), + chance("range", STATION_RANGE, low = 8, high = 265), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 20, high = 277), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 33, high = 290) + ) + ), + heatCook( + rowKey = "cooking_moonlight_antelope", + raw = "items.hunting_antelopemoon_meat", + cooked = "items.antelopemoon_cooked", + burnt = "items.burnt_antelope", + level = 92, xp = 220, stopBurnFire = 100, stopBurnRange = 100, + chances = listOf( + chance("fire", STATION_FIRE, low = 1, high = 185), + chance("range", STATION_RANGE, low = 1, high = 200), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 212), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 225) + ) + ) + ) + + /** All meat recipes combined. */ + val recipes: List = basicMeatRecipes + spitRoastRecipes + hunterMeatRecipes +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/MiscFoodRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/MiscFoodRecipes.kt new file mode 100644 index 00000000..e34c1b8d --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/MiscFoodRecipes.kt @@ -0,0 +1,155 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Miscellaneous cooking recipes — vegetable side dishes, dairy, scrambled egg, etc. + */ +object MiscFoodRecipes { + + /** Sweetcorn: raw sweetcorn on fire/range. */ + val sweetcorn: List = listOf( + heatCook( + rowKey = "cooking_sweetcorn", + raw = "items.sweetcorn", + cooked = "items.sweetcorn_cooked", + burnt = "items.sweetcorn_burnt", + level = 28, xp = 104, stopBurnFire = 61, stopBurnRange = 61, + chances = listOf( + chance("base_any", STATION_ANY, low = 78, high = 412), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 90, high = 424), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 103, high = 437) + ) + ) + ) + + /** Scrambled egg: egg + bowl on range. */ + val scrambledEgg: List = multiStepCook( + key = "items.bowl_egg_scrambled", + level = 13, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_scrambled_egg_mix", + inputs = listOf( + "items.egg" to 1, + "items.bowl_empty" to 1 + ), + output = "items.bowl_egg_raw" + ) + ) + ) + listOf( + heatCook( + rowKey = "cooking_scrambled_egg_cook", + raw = "items.bowl_egg_raw", + cooked = "items.bowl_egg_scrambled", + burnt = "items.bowl_egg_burnt", + level = 13, xp = 50, stopBurnFire = 46, stopBurnRange = 46, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 90, high = 438), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 102, high = 450), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 115, high = 463) + ) + ) + ) + + /** Fried onions: chopped onion + bowl on fire/range. */ + val friedOnions: List = listOf( + heatCook( + rowKey = "cooking_fried_onions", + raw = "items.bowl_onion", + cooked = "items.bowl_onion_fried", + burnt = "items.bowl_onion_burnt", + level = 42, xp = 60, stopBurnFire = 74, stopBurnRange = 74, + chances = listOf( + chance("base_any", STATION_ANY, low = 36, high = 322), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 48, high = 334), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 61, high = 347) + ) + ) + ) + + /** Fried mushrooms: sliced mushrooms + bowl on fire/range. */ + val friedMushrooms: List = listOf( + heatCook( + rowKey = "cooking_fried_mushrooms", + raw = "items.bowl_mushroom_raw", + cooked = "items.bowl_mushroom_fried", + burnt = "items.bowl_mushroom_burnt", + level = 46, xp = 60, stopBurnFire = 80, stopBurnRange = 80, + chances = listOf( + chance("base_any", STATION_ANY, low = 16, high = 282), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 28, high = 294), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 41, high = 307) + ) + ) + ) + + /** Mushroom & onion: fried mushrooms + fried onions. */ + val mushroomAndOnion: List = multiStepCook( + key = "items.bowl_mushroom+onion", + level = 57, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_mushroom_onion_combine", + inputs = listOf( + "items.bowl_mushroom_fried" to 1, + "items.bowl_onion_fried" to 1 + ), + output = "items.bowl_mushroom+onion", + always = listOf("items.bowl_empty" to 1) + ) + ) + ) + + /** Tuna and corn: cooked tuna + cooked sweetcorn + bowl. */ + val tunaAndCorn: List = multiStepCook( + key = "items.bowl_tuna+sweetcorn", + level = 67, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_tuna_corn_combine", + inputs = listOf( + "items.tuna" to 1, + "items.sweetcorn_cooked" to 1, + "items.bowl_empty" to 1 + ), + output = "items.bowl_tuna+sweetcorn" + ) + ) + ) + + /** Chocolate cake: cake + chocolate bar. */ + val chocolateCake: List = multiStepCook( + key = "items.chocolate_cake", + level = 50, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_chocolate_cake", + inputs = listOf( + "items.cake" to 1, + "items.chocolate_bar" to 1 + ), + output = "items.chocolate_cake" + ) + ) + ) + + /** All misc cooking recipes combined. */ + val recipes: List = + sweetcorn + + scrambledEgg + + friedOnions + + friedMushrooms + + mushroomAndOnion + + tunaAndCorn + + chocolateCake +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/PieRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/PieRecipes.kt new file mode 100644 index 00000000..2bdf04a4 --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/PieRecipes.kt @@ -0,0 +1,461 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Remaining pie recipes beyond garden pie. + * + * All pies follow the same flow: + * 1. Prep steps: add fillings to a pie shell (item-on-item) + * 2. Heat step: bake the uncooked pie on a range + */ +object PieRecipes { + + /** Redberry pie (level 10). */ + val redberryPie: List = multiStepCook( + key = "items.uncooked_redberry_pie", + level = 10, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_redberry_pie_add_berries", + inputs = listOf( + "items.pie_shell" to 1, + "items.redberries" to 1 + ), + output = "items.uncooked_redberry_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_redberry_pie_bake", + raw = "items.uncooked_redberry_pie", + cooked = "items.redberry_pie", + burnt = "items.burnt_pie", + xp = 78, + stopBurnFire = 44, + stopBurnRange = 44, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 98, high = 452), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 108, high = 462), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 110, high = 464), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 123, high = 477) + ) + ) + ) + + /** Meat pie (level 20). */ + val meatPie: List = multiStepCook( + key = "items.uncooked_meat_pie", + level = 20, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_meat_pie_add_meat", + inputs = listOf( + "items.pie_shell" to 1, + "items.cooked_meat" to 1 + ), + output = "items.uncooked_meat_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_meat_pie_bake", + raw = "items.uncooked_meat_pie", + cooked = "items.meat_pie", + burnt = "items.burnt_pie", + xp = 110, + stopBurnFire = 54, + stopBurnRange = 54, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 78, high = 412), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 88, high = 432), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 90, high = 424), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 103, high = 437) + ) + ) + ) + + /** Mud pie (level 29). */ + val mudPie: List = multiStepCook( + key = "items.uncooked_mud_pie", + level = 29, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_mud_pie_add_compost", + inputs = listOf( + "items.pie_shell" to 1, + "items.bucket_compost" to 1 + ), + output = "items.unfinished_mud_pie_1" + ), + PrepStepDef( + rowKey = "cooking_mud_pie_add_water", + inputs = listOf( + "items.unfinished_mud_pie_1" to 1, + "items.bucket_water" to 1 + ), + output = "items.unfinished_mud_pie_2", + always = listOf("items.bucket_empty" to 1) + ), + PrepStepDef( + rowKey = "cooking_mud_pie_add_clay", + inputs = listOf( + "items.unfinished_mud_pie_2" to 1, + "items.clay" to 1 + ), + output = "items.uncooked_mud_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_mud_pie_bake", + raw = "items.uncooked_mud_pie", + cooked = "items.mud_pie", + burnt = "items.burnt_pie", + xp = 128, + stopBurnFire = 63, + stopBurnRange = 63, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 58, high = 372), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 70, high = 384), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 83, high = 397) + ) + ) + ) + + /** Apple pie (level 30). */ + val applePie: List = multiStepCook( + key = "items.uncooked_apple_pie", + level = 30, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_apple_pie_add_apple", + inputs = listOf( + "items.pie_shell" to 1, + "items.cooking_apple" to 1 + ), + output = "items.uncooked_apple_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_apple_pie_bake", + raw = "items.uncooked_apple_pie", + cooked = "items.apple_pie", + burnt = "items.burnt_pie", + xp = 130, + stopBurnFire = 64, + stopBurnRange = 64, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 58, high = 372), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 70, high = 384), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 83, high = 397) + ) + ) + ) + + /** Fish pie (level 47). */ + val fishPie: List = multiStepCook( + key = "items.uncooked_fish_pie", + level = 47, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_fish_pie_add_trout", + inputs = listOf( + "items.pie_shell" to 1, + "items.trout" to 1 + ), + output = "items.unfinished_fish_pie_1" + ), + PrepStepDef( + rowKey = "cooking_fish_pie_add_cod", + inputs = listOf( + "items.unfinished_fish_pie_1" to 1, + "items.cod" to 1 + ), + output = "items.unfinished_fish_pie_2" + ), + PrepStepDef( + rowKey = "cooking_fish_pie_add_potato", + inputs = listOf( + "items.unfinished_fish_pie_2" to 1, + "items.potato" to 1 + ), + output = "items.uncooked_fish_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_fish_pie_bake", + raw = "items.uncooked_fish_pie", + cooked = "items.fish_pie", + burnt = "items.burnt_pie", + xp = 164, + stopBurnFire = 81, + stopBurnRange = 81, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 38, high = 332), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357) + ) + ) + ) + + /** Botanical pie (level 52). */ + val botanicalPie: List = multiStepCook( + key = "items.uncooked_botanical_pie", + level = 52, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_botanical_pie_add_golovanova", + inputs = listOf( + "items.pie_shell" to 1, + "items.golovanova_top" to 1 + ), + output = "items.uncooked_botanical_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_botanical_pie_bake", + raw = "items.uncooked_botanical_pie", + cooked = "items.botanical_pie", + burnt = "items.burnt_pie", + xp = 180, + stopBurnFire = 86, + stopBurnRange = 86, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 20, high = 300), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 108, high = 462), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 32, high = 312), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 45, high = 325) + ) + ) + ) + + /** Mushroom pie (level 60). */ + val mushroomPie: List = multiStepCook( + key = "items.uncooked_mushroom_pie", + level = 60, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_mushroom_pie_add_sulliuscep", + inputs = listOf( + "items.pie_shell" to 1, + "items.fossil_sulliuscep_cap" to 1 + ), + output = "items.uncooked_mushroom_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_mushroom_pie_bake", + raw = "items.uncooked_mushroom_pie", + cooked = "items.mushroom_pie", + burnt = "items.burnt_pie", + xp = 200, + stopBurnFire = 94, + stopBurnRange = 94, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 17, high = 285), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 140, high = 450), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 29, high = 297), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 42, high = 310) + ) + ) + ) + + /** Admiral pie (level 70). */ + val admiralPie: List = multiStepCook( + key = "items.uncooked_admiral_pie", + level = 70, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_admiral_pie_add_salmon", + inputs = listOf( + "items.pie_shell" to 1, + "items.salmon" to 1 + ), + output = "items.unfinished_admiral_pie_1" + ), + PrepStepDef( + rowKey = "cooking_admiral_pie_add_tuna", + inputs = listOf( + "items.unfinished_admiral_pie_1" to 1, + "items.tuna" to 1 + ), + output = "items.unfinished_admiral_pie_2" + ), + PrepStepDef( + rowKey = "cooking_admiral_pie_add_potato", + inputs = listOf( + "items.unfinished_admiral_pie_2" to 1, + "items.potato" to 1 + ), + output = "items.uncooked_admiral_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_admiral_pie_bake", + raw = "items.uncooked_admiral_pie", + cooked = "items.admiral_pie", + burnt = "items.burnt_pie", + xp = 210, + stopBurnFire = 100, + stopBurnRange = 94, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 15, high = 270), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 27, high = 282), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 40, high = 295) + ) + ) + ) + + /** Dragonfruit pie (level 73). */ + val dragonfruitPie: List = multiStepCook( + key = "items.uncooked_dragonfruit_pie", + level = 73, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_dragonfruit_pie_add_fruit", + inputs = listOf( + "items.pie_shell" to 1, + "items.dragonfruit" to 1 + ), + output = "items.uncooked_dragonfruit_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_dragonfruit_pie_bake", + raw = "items.uncooked_dragonfruit_pie", + cooked = "items.dragonfruit_pie", + burnt = "items.burnt_pie", + xp = 220, + stopBurnFire = 100, + stopBurnRange = 97, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 8, high = 250), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 20, high = 262), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 33, high = 275) + ) + ) + ) + + /** Wild pie (level 85). */ + val wildPie: List = multiStepCook( + key = "items.uncooked_wild_pie", + level = 85, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_wild_pie_add_bear", + inputs = listOf( + "items.pie_shell" to 1, + "items.raw_bear_meat" to 1 + ), + output = "items.unfinished_wild_pie_1" + ), + PrepStepDef( + rowKey = "cooking_wild_pie_add_chompy", + inputs = listOf( + "items.unfinished_wild_pie_1" to 1, + "items.raw_chompy" to 1 + ), + output = "items.unfinished_wild_pie_2" + ), + PrepStepDef( + rowKey = "cooking_wild_pie_add_rabbit", + inputs = listOf( + "items.unfinished_wild_pie_2" to 1, + "items.raw_rabbit" to 1 + ), + output = "items.uncooked_wild_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_wild_pie_bake", + raw = "items.uncooked_wild_pie", + cooked = "items.wild_pie", + burnt = "items.burnt_pie", + xp = 240, + stopBurnFire = 100, + stopBurnRange = 100, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 1, high = 222), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 234), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 247) + ) + ) + ) + + /** Summer pie (level 95). */ + val summerPie: List = multiStepCook( + key = "items.uncooked_summer_pie", + level = 95, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_summer_pie_add_watermelon", + inputs = listOf( + "items.pie_shell" to 1, + "items.watermelon" to 1 + ), + output = "items.unfinished_summer_pie_1" + ), + PrepStepDef( + rowKey = "cooking_summer_pie_add_apple", + inputs = listOf( + "items.unfinished_summer_pie_1" to 1, + "items.cooking_apple" to 1 + ), + output = "items.unfinished_summer_pie_2" + ), + PrepStepDef( + rowKey = "cooking_summer_pie_add_strawberry", + inputs = listOf( + "items.unfinished_summer_pie_2" to 1, + "items.strawberry" to 1 + ), + output = "items.uncooked_summer_pie" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_summer_pie_bake", + raw = "items.uncooked_summer_pie", + cooked = "items.summer_pie", + burnt = "items.burnt_pie", + xp = 260, + stopBurnFire = 100, + stopBurnRange = 100, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 1, high = 212), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 13, high = 224), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 26, high = 237) + ) + ) + ) + + /** All pie recipes combined. */ + val recipes: List = + redberryPie + + meatPie + + mudPie + + applePie + + fishPie + + botanicalPie + + mushroomPie + + admiralPie + + dragonfruitPie + + wildPie + + summerPie +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/PizzaRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/PizzaRecipes.kt new file mode 100644 index 00000000..f6f4dd54 --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/PizzaRecipes.kt @@ -0,0 +1,115 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Pizza recipes — dough + toppings + range bake. + * + * Flow: pizza base → uncooked pizza (item-on-item) → plain pizza (bake) → topped pizza (item-on-item) + */ +object PizzaRecipes { + + /** Plain pizza: pizza base on range. */ + val plainPizzaRecipes: List = multiStepCook( + key = "items.uncooked_pizza", + level = 35, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_pizza_add_tomato", + inputs = listOf( + "items.pizza_base" to 1, + "items.tomato" to 1 + ), + output = "items.incomplete_pizza" + ), + PrepStepDef( + rowKey = "cooking_pizza_add_cheese", + inputs = listOf( + "items.incomplete_pizza" to 1, + "items.cheese" to 1 + ), + output = "items.uncooked_pizza" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_pizza_bake", + raw = "items.uncooked_pizza", + cooked = "items.plain_pizza", + burnt = "items.burnt_pizza", + xp = 143, + stopBurnFire = 68, + stopBurnRange = 68, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 48, high = 352), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 60, high = 364), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 73, high = 377) + ) + ) + ) + + /** Meat pizza topping: plain pizza + cooked meat. */ + val meatPizzaRecipes: List = multiStepCook( + key = "items.meat_pizza", + level = 45, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_pizza_add_meat", + inputs = listOf( + "items.plain_pizza" to 1, + "items.cooked_meat" to 1 + ), + output = "items.meat_pizza", + xp = 26 + ) + ) + ) + + /** Anchovy pizza topping: plain pizza + anchovies. */ + val anchovyPizzaRecipes: List = multiStepCook( + key = "items.anchovie_pizza", + level = 55, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_pizza_add_anchovies", + inputs = listOf( + "items.plain_pizza" to 1, + "items.anchovies" to 1 + ), + output = "items.anchovie_pizza", + xp = 39 + ) + ) + ) + + /** Pineapple pizza topping: plain pizza + pineapple ring/chunks. */ + val pineapplePizzaRecipes: List = multiStepCook( + key = "items.pineapple_pizza", + level = 65, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_pizza_add_pineapple", + inputs = listOf( + "items.plain_pizza" to 1, + "items.pineapple_ring" to 1 + ), + output = "items.pineapple_pizza", + xp = 52 + ) + ) + ) + + /** All pizza recipes combined. */ + val recipes: List = + plainPizzaRecipes + + meatPizzaRecipes + + anchovyPizzaRecipes + + pineapplePizzaRecipes +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/PotatoRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/PotatoRecipes.kt new file mode 100644 index 00000000..795b3b27 --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/PotatoRecipes.kt @@ -0,0 +1,126 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.heatCook +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Potato-based recipes — baked potato and toppings. + */ +object PotatoRecipes { + + /** Baked potato: raw potato on a range. */ + val bakedPotato: List = listOf( + heatCook( + rowKey = "cooking_baked_potato", + raw = "items.potato", + cooked = "items.potato_baked", + burnt = "items.potato_burnt", + level = 7, xp = 15, stopBurnFire = 40, stopBurnRange = 40, + stationMask = STATION_RANGE, + chances = listOf( + chance("range", STATION_RANGE, low = 108, high = 472), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 120, high = 484), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 133, high = 497) + ) + ) + ) + + /** Potato with butter: baked potato + pat of butter. */ + val potatoWithButter: List = multiStepCook( + key = "items.potato_butter", + level = 39, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_butter", + inputs = listOf( + "items.potato_baked" to 1, + "items.pot_of_butter" to 1 + ), + output = "items.potato_butter" + ) + ) + ) + + /** Potato with cheese: potato with butter + cheese. */ + val potatoWithCheese: List = multiStepCook( + key = "items.potato_cheese", + level = 47, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_cheese", + inputs = listOf( + "items.potato_butter" to 1, + "items.cheese" to 1 + ), + output = "items.potato_cheese" + ) + ) + ) + + /** Egg potato: potato with butter + scrambled egg. */ + val eggPotato: List = multiStepCook( + key = "items.potato_egg+tomato", + level = 51, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_egg", + inputs = listOf( + "items.potato_butter" to 1, + "items.bowl_egg_scrambled" to 1 + ), + output = "items.potato_egg+tomato", + always = listOf("items.bowl_empty" to 1) + ) + ) + ) + + /** Mushroom potato: potato with butter + mushroom & onion. */ + val mushroomPotato: List = multiStepCook( + key = "items.potato_mushroom+onion", + level = 64, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_mushroom_onion", + inputs = listOf( + "items.potato_butter" to 1, + "items.bowl_mushroom+onion" to 1 + ), + output = "items.potato_mushroom+onion", + always = listOf("items.bowl_empty" to 1) + ) + ) + ) + + /** Tuna potato: potato with butter + tuna and corn. */ + val tunaPotato: List = multiStepCook( + key = "items.potato_tuna+sweetcorn", + level = 68, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_potato_add_tuna_corn", + inputs = listOf( + "items.potato_butter" to 1, + "items.bowl_tuna+sweetcorn" to 1 + ), + output = "items.potato_tuna+sweetcorn", + always = listOf("items.bowl_empty" to 1) + ) + ) + ) + + /** All potato recipes combined. */ + val recipes: List = + bakedPotato + + potatoWithButter + + potatoWithCheese + + eggPotato + + mushroomPotato + + tunaPotato +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/StewRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/StewRecipes.kt new file mode 100644 index 00000000..590ffca9 --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/StewRecipes.kt @@ -0,0 +1,106 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_ANY +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.impl.skills.cooking.CookingHelpers.chance +import org.alter.impl.skills.cooking.CookingHelpers.multiStepCook +import org.alter.impl.skills.cooking.HeatStepDef +import org.alter.impl.skills.cooking.PrepStepDef + +/** + * Stew recipes — bowl of water + ingredients on heat source. + */ +object StewRecipes { + + /** Basic stew: bowl of water + cooked meat + potato on fire/range. */ + val stewRecipes: List = multiStepCook( + key = "items.uncooked_stew", + level = 25, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_stew_add_potato", + inputs = listOf( + "items.bowl_water" to 1, + "items.potato" to 1 + ), + output = "items.stew1" + ), + PrepStepDef( + rowKey = "cooking_stew_add_meat", + inputs = listOf( + "items.stew1" to 1, + "items.cooked_meat" to 1 + ), + output = "items.uncooked_stew" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_stew_cook", + raw = "items.uncooked_stew", + cooked = "items.stew", + burnt = "items.burnt_stew", + xp = 117, + stopBurnFire = 58, + stopBurnRange = 58, + chances = listOf( + chance("base_any", STATION_ANY, low = 68, high = 392), + chance("lumbridge", STATION_RANGE, modifierMask = LUMBRIDGE, low = 78, high = 412), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 80, high = 404), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 93, high = 417) + ) + ) + ) + + /** Curry: bowl of water + potato + cooked meat + spice on fire/range. */ + val curryRecipes: List = multiStepCook( + key = "items.uncooked_curry", + level = 60, + prepSteps = listOf( + PrepStepDef( + rowKey = "cooking_curry_add_potato", + inputs = listOf( + "items.bowl_water" to 1, + "items.potato" to 1 + ), + output = "items.stew1" + ), + PrepStepDef( + rowKey = "cooking_curry_add_meat", + inputs = listOf( + "items.stew1" to 1, + "items.cooked_meat" to 1 + ), + output = "items.uncooked_stew" + ), + PrepStepDef( + rowKey = "cooking_curry_add_spice", + inputs = listOf( + "items.uncooked_stew" to 1, + "items.spicespot" to 1 + ), + output = "items.uncooked_curry" + ) + ), + heatStep = HeatStepDef( + rowKey = "cooking_curry_cook", + raw = "items.uncooked_curry", + cooked = "items.curry", + burnt = "items.burnt_curry", + xp = 280, + stopBurnFire = 74, + stopBurnRange = 74, + chances = listOf( + chance("base_any", STATION_ANY, low = 38, high = 332), + chance("hosidius_5", STATION_RANGE, modifierMask = HOSIDIUS_5, low = 50, high = 344), + chance("hosidius_10", STATION_RANGE, modifierMask = HOSIDIUS_10, low = 63, high = 357) + ) + ) + ) + + /** All stew recipes combined. */ + val recipes: List = stewRecipes + curryRecipes +} diff --git a/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/WineRecipes.kt b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/WineRecipes.kt new file mode 100644 index 00000000..b9bf86ca --- /dev/null +++ b/cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/WineRecipes.kt @@ -0,0 +1,55 @@ +package org.alter.impl.skills.cooking.recipes + +import org.alter.impl.skills.cooking.ActionDef +import org.alter.impl.skills.cooking.CookingConstants.OutcomeKind +import org.alter.impl.skills.cooking.CookingConstants.Trigger +import org.alter.impl.skills.cooking.InputDef +import org.alter.impl.skills.cooking.OutcomeDef + +/** + * Wine recipes — fermentation mechanic. + * + * Wine is made by using grapes on a jug of water. The fermentation occurs + * automatically after a delay (12 seconds / ~20 ticks). XP is awarded only + * on success. The success/failure is determined at the time of combining, + * not at fermentation completion. + * + * Since wine doesn't use a heat source, it's modeled as an ITEM_ON_ITEM + * trigger. The actual fermentation delay is handled by CookingEvents. + */ +object WineRecipes { + + val jugOfWine: ActionDef = ActionDef( + rowId = "dbrows.cooking_wine", + key = "items.grapes", + variant = 1, + level = 35, + stopBurnFire = 68, + stopBurnRange = 68, + stationMask = 0, // wine doesn't use a station; marker for event handler + trigger = Trigger.ITEM_ON_ITEM, + inputs = listOf( + InputDef("items.grapes", 1), + InputDef("items.jug_water", 1) + ), + outcomes = listOf( + OutcomeDef( + rowSuffix = "success", + kind = OutcomeKind.SUCCESS, + item = "items.jug_unfermented_wine", + xp = 200, + weight = 1 + ), + OutcomeDef( + rowSuffix = "fail", + kind = OutcomeKind.FAIL, + item = "items.jug_unfermented_wine", + xp = 0, + weight = 1 + ) + ) + ) + + /** All wine recipes. */ + val recipes: List = listOf(jugOfWine) +} diff --git a/content/src/main/kotlin/org/alter/commands/developer/CookingTestCommandsPlugin.kt b/content/src/main/kotlin/org/alter/commands/developer/CookingTestCommandsPlugin.kt new file mode 100644 index 00000000..de2c8216 --- /dev/null +++ b/content/src/main/kotlin/org/alter/commands/developer/CookingTestCommandsPlugin.kt @@ -0,0 +1,297 @@ +package org.alter.commands.developer + +import org.alter.api.Skills +import org.alter.api.ext.message +import org.alter.game.model.priv.Privilege +import org.alter.game.model.Tile +import org.alter.game.model.move.moveTo +import org.alter.game.pluginnew.PluginEvent +import org.alter.game.pluginnew.event.impl.CommandEvent +import org.alter.rscm.RSCM.asRSCM + +class CookingTestCommandsPlugin : PluginEvent() { + + private enum class CookTestSet( + val key: String, + val level: Int, + val qty: Int, + ) { + LOW("low", level = 1, qty = 5), // 4 items × 5 = 20 slots + MID("mid", level = 25, qty = 5), // 3 items × 5 = 15 slots + HIGH("high", level = 80, qty = 3), // 3 items × 3 = 9 slots + FISH_LOW("fishlow", level = 15, qty = 4), // 6 items × 4 = 24 slots + FISH_MID("fishmid", level = 50, qty = 5), // 5 items × 5 = 25 slots + FISH_HIGH("fishhigh", level = 99, qty = 3), // 8 items × 3 = 24 slots + FISH_SPECIAL("fishspec", level = 70, qty = 5), // 4 items × 5 = 20 slots + PREP_FISH("prepfish", level = 99, qty = 5), // 5 items × 5 = 25 slots + MEAT("meat", level = 1, qty = 3), // 7 items × 3 = 21 slots + SPIT("spit", level = 1, qty = 3), // 6 items × 3 = 18 slots + PIES_INGR("piesingr", level = 30, qty = 3), // 7 items × 3 = 21 slots + PIES_BAKE("piesbake", level = 95, qty = 5), // 4 items × 5 = 20 slots + CAKES("cakes", level = 50, qty = 3), // 7 items × 3 = 21 slots + PIZZA("pizza", level = 65, qty = 3), // 8 items × 3 = 24 slots + STEW("stew", level = 60, qty = 5), // 5 items × 5 = 25 slots + BREAD("bread", level = 58, qty = 5), // 2 items × 5 = 10 slots + POTATO("potato", level = 68, qty = 3), // 8 items × 3 = 24 slots + WINE("wine", level = 35, qty = 5), // 2 items × 5 = 10 slots + MISC("misc", level = 67, qty = 3), // 7 items × 3 = 21 slots + SNAILS("snails", level = 22, qty = 5), // 3 items × 5 = 15 slots + HUNTER("hunter", level = 92, qty = 3); // 6 items × 3 = 18 slots + + companion object { + fun fromKey(raw: String): CookTestSet? { + val token = raw.removePrefix("--").trim().lowercase() + return entries.firstOrNull { it.key == token } + } + } + } + + override fun init() { + on { + where { + command.equals("cooktest", ignoreCase = true) && + player.world.privileges.isEligible(player.privilege, Privilege.DEV_POWER) + } + then { + val args = args.orEmpty().map { it.trim() }.filter { it.isNotEmpty() } + + val set = parseCookTestSet(args) + if (set == null) { + sendCookTestHelp(player) + return@then + } + + player.getSkills().setBaseLevel(Skills.COOKING, set.level) + + val keys = keysFor(set) + if (keys.isEmpty()) { + player.message("No items configured for preset '${set.key}'.") + return@then + } + + var addedAny = false + var failed = 0 + for (key in keys) { + val id = runCatching { key.asRSCM() }.getOrNull() ?: continue + val result = player.inventory.add(id, set.qty) + if (result.hasSucceeded()) { + addedAny = true + } else { + failed++ + } + } + + if (!addedAny) { + player.message("Couldn't add any items (inventory full?).") + } else if (failed > 0) { + player.message("Added cooking test items (some didn't fit: $failed).") + } else { + player.message("Applied cooktest preset '${set.key}' (x${set.qty} each, Cooking=${set.level}).") + } + } + } + } + + private fun parseCookTestSet(args: List): CookTestSet? { + if (args.isEmpty() || args.any { it.equals("help", ignoreCase = true) || it == "-h" || it == "--help" }) { + return null + } + + return CookTestSet.fromKey(args.first()) + } + + private fun keysFor(set: CookTestSet): List = + when (set) { + CookTestSet.LOW -> listOf( + "items.raw_shrimp", + "items.raw_anchovies", + "items.raw_sardine", + "items.raw_herring" + ) + + CookTestSet.MID -> listOf( + "items.raw_mackerel", + "items.raw_trout", + "items.raw_salmon" + ) + + CookTestSet.HIGH -> listOf( + "items.raw_lobster", + "items.raw_swordfish", + "items.raw_shark" + ) + + CookTestSet.FISH_LOW -> listOf( + "items.raw_shrimp", + "items.raw_anchovies", + "items.raw_sardine", + "items.raw_herring", + "items.raw_mackerel", + "items.raw_trout" + ) + + CookTestSet.FISH_MID -> listOf( + "items.raw_cod", + "items.raw_pike", + "items.raw_salmon", + "items.raw_tuna", + "items.raw_lobster" + ) + + CookTestSet.FISH_HIGH -> listOf( + "items.raw_bass", + "items.raw_swordfish", + "items.raw_monkfish", + "items.raw_shark", + "items.raw_seaturtle", + "items.raw_anglerfish", + "items.raw_dark_crab", + "items.raw_mantaray" + ) + + CookTestSet.FISH_SPECIAL -> listOf( + "items.hunting_raw_fish_special", + "items.mort_slimey_eel", + "items.raw_cave_eel", + "items.tbwt_raw_karambwan" + ) + + CookTestSet.PREP_FISH -> listOf( + "items.knife", + "items.raw_guppy", + "items.raw_cavefish", + "items.raw_tetra", + "items.raw_catfish" + ) + + CookTestSet.MEAT -> listOf( + "items.raw_beef", + "items.raw_chicken", + "items.raw_rat_meat", + "items.raw_bear_meat", + "items.raw_rabbit", + "items.raw_ugthanki_meat", + "items.raw_chompy" + ) + + CookTestSet.SPIT -> listOf( + "items.spit_iron", + "items.spit_raw_bird_meat", + "items.spit_raw_beast_meat", + "items.spit_skewered_bird_meat", + "items.spit_skewered_beast_meat", + "items.spit_skewered_rabbit_meat" + ) + + CookTestSet.PIES_INGR -> listOf( + "items.pie_shell", + "items.tomato", + "items.onion", + "items.cabbage", + "items.redberries", + "items.cooked_meat", + "items.cooking_apple" + ) + + CookTestSet.PIES_BAKE -> listOf( + "items.uncooked_garden_pie", + "items.uncooked_redberry_pie", + "items.uncooked_meat_pie", + "items.uncooked_apple_pie" + ) + + CookTestSet.CAKES -> listOf( + "items.cake_tin", + "items.egg", + "items.bucket_milk", + "items.pot_flour", + "items.uncooked_cake", + "items.cake", + "items.chocolate_bar" + ) + + CookTestSet.PIZZA -> listOf( + "items.pizza_base", + "items.tomato", + "items.cheese", + "items.cooked_meat", + "items.anchovies", + "items.pineapple_ring", + "items.uncooked_pizza", + "items.plain_pizza" + ) + + CookTestSet.STEW -> listOf( + "items.bowl_water", + "items.potato", + "items.cooked_meat", + "items.spice", + "items.uncooked_stew" + ) + + CookTestSet.BREAD -> listOf( + "items.bread_dough", + "items.pitta_dough" + ) + + CookTestSet.POTATO -> listOf( + "items.potato", + "items.potato_baked", + "items.pat_of_butter", + "items.potato_butter", + "items.cheese", + "items.bowl_egg_scrambled", + "items.bowl_mushroom_onion", + "items.bowl_tuna_corn" + ) + + CookTestSet.WINE -> listOf( + "items.grapes", + "items.jug_water" + ) + + CookTestSet.MISC -> listOf( + "items.sweetcorn_raw", + "items.egg", + "items.bowl_empty", + "items.bowl_onion_chopped", + "items.bowl_mushroom_sliced", + "items.tuna", + "items.sweetcorn_cooked" + ) + + CookTestSet.SNAILS -> listOf( + "items.snail_corpse1", + "items.snail_corpse2", + "items.snail_corpse3" + ) + + CookTestSet.HUNTER -> listOf( + "items.hunting_larupia_meat", + "items.hunting_graahk_meat", + "items.hunting_kyatt_meat", + "items.hunting_fennecfox_meat", + "items.hunting_antelopesun_meat", + "items.hunting_antelopemoon_meat" + ) + } + + private fun sendCookTestHelp(player: org.alter.game.model.entity.Player) { + player.message("Usage: ::cooktest ") + player.message("Presets: ${CookTestSet.entries.joinToString(", ") { it.key }}") + val examples = CookTestSet.entries.map { "::cooktest ${it.key}" } + val lines = mutableListOf() + var current = "Examples: " + for ((i, ex) in examples.withIndex()) { + val separator = if (i == 0) "" else " | " + if (current.length + separator.length + ex.length > 250) { + lines.add(current) + current = ex + } else { + current += separator + ex + } + } + if (current.isNotEmpty()) lines.add(current) + lines.forEach { player.message(it) } + } +} diff --git a/content/src/main/kotlin/org/alter/interfaces/settings/SettingsSideEvents.kt b/content/src/main/kotlin/org/alter/interfaces/settings/SettingsSideEvents.kt index 35b0a3fe..b2018db0 100644 --- a/content/src/main/kotlin/org/alter/interfaces/settings/SettingsSideEvents.kt +++ b/content/src/main/kotlin/org/alter/interfaces/settings/SettingsSideEvents.kt @@ -6,6 +6,7 @@ import org.alter.game.model.entity.Player import org.alter.game.pluginnew.PluginEvent import org.alter.game.pluginnew.event.impl.onButton import org.alter.game.pluginnew.event.impl.onIfOpen +import org.alter.interfaces.ifCloseOverlay import org.alter.interfaces.ifOpenOverlay import org.alter.interfaces.ifSetEvents import org.alter.interfaces.settings.configs.setting_components @@ -16,6 +17,11 @@ class SettingsSideScript : PluginEvent() { override fun init() { onIfOpen("interfaces.settings_side") { player.updateIfEvents() } + onIfOpen("interfaces.settings") { + player.ifSetEvents("components.settings:close", 0..0, IfEvent.Op1) + player.ifSetEvents("components.settings:dropdown_close", 0..0, IfEvent.Op1) + } + SettingsTabView.entries.forEach { onButton(it.component) { player.setVarbit("varbits.settings_side_panel_tab",it.varValue) @@ -25,6 +31,14 @@ class SettingsSideScript : PluginEvent() { onButton(setting_components.settings_open) { player.ifOpenOverlay("interfaces.settings") } + + onButton("components.settings:close") { + player.ifCloseOverlay("interfaces.settings") + } + + onButton("components.settings:dropdown_close") { + player.ifCloseOverlay("interfaces.settings") + } } private fun Player.updateIfEvents() { diff --git a/content/src/main/kotlin/org/alter/skills/cooking/events/CamdozaalPrepTable.kt b/content/src/main/kotlin/org/alter/skills/cooking/events/CamdozaalPrepTable.kt new file mode 100644 index 00000000..6f9bdf43 --- /dev/null +++ b/content/src/main/kotlin/org/alter/skills/cooking/events/CamdozaalPrepTable.kt @@ -0,0 +1,148 @@ +package org.alter.skills.cooking.events + +import dev.openrune.ServerCacheManager.getItem +import org.alter.api.Skills +import org.alter.api.computeSkillingSuccess +import org.alter.api.ext.filterableMessage +import org.alter.api.ext.message +import org.alter.game.model.entity.GameObject +import org.alter.game.model.entity.Player +import org.alter.game.model.queue.QueueTask +import org.alter.skills.cooking.events.CookingUtils.isStillAtStation +import org.alter.rscm.RSCM +import org.alter.rscm.RSCM.asRSCM +import kotlin.random.Random/** + * Handles cooking at the Camdozaal Preparation Table (knife-based prep, not heat). + */ +object CamdozaalPrepTable { + + private data class PrepFishDef( + val raw: String, + val prepared: String, + val ruined: String, + val level: Int, + val xp: Int + ) + + val prepTableId: Int = "objects.camdozaal_preparation_table".asRSCM() + + private val knifeId: Int? = runCatching { "items.knife".asRSCM() }.getOrNull() + + private val prepFishByRaw: Map = listOf( + PrepFishDef( + raw = "items.raw_guppy", + prepared = "items.guppy", + ruined = "items.ruined_guppy", + level = 7, + xp = 12 + ), + PrepFishDef( + raw = "items.raw_cavefish", + prepared = "items.cavefish", + ruined = "items.ruined_cavefish", + level = 20, + xp = 23 + ), + PrepFishDef( + raw = "items.raw_tetra", + prepared = "items.tetra", + ruined = "items.ruined_tetra", + level = 33, + xp = 31 + ), + PrepFishDef( + raw = "items.raw_catfish", + prepared = "items.catfish", + ruined = "items.ruined_catfish", + level = 46, + xp = 43 + ) + ).mapNotNull { def -> + val rawId = runCatching { def.raw.asRSCM() }.getOrNull() ?: return@mapNotNull null + rawId to def + }.toMap() + + /** + * Checks if the given item ID is a raw hunter fish for the prep table. + */ + fun isRawPrepFish(itemId: Int): Boolean = prepFishByRaw.containsKey(itemId) + + /** + * Handles using a raw fish on the preparation table. + */ + suspend fun QueueTask.prepareFish( + player: Player, + gameObject: GameObject, + rawItemId: Int + ) { + val def = prepFishByRaw[rawItemId] ?: return + val knife = knifeId + if (knife == null || player.inventory.getItemCount(knife) <= 0) { + player.message("You need a knife to prepare this.") + return + } + + val cookingLevel = player.getSkills().getCurrentLevel(Skills.COOKING) + if (cookingLevel < def.level) { + player.filterableMessage("You need a Cooking level of ${def.level} to prepare this.") + return + } + + val objectTile = gameObject.tile + val objectId = gameObject.internalID + val preparedId = runCatching { def.prepared.asRSCM() }.getOrNull() ?: return + val ruinedId = runCatching { def.ruined.asRSCM() }.getOrNull() ?: return + + var prepared = 0 + player.filterableMessage("You begin preparing the fish...") + + repeatWhile(delay = 3, immediate = true, canRepeat = { + player.inventory.getItemCount(rawItemId) > 0 && + player.inventory.getItemCount(knife) > 0 && + isStillAtStation(player, objectTile, objectId) + }) { + runCatching { player.animate("sequences.human_cutting", interruptable = true) } + .onFailure { player.animate(RSCM.NONE) } + + val removed = player.inventory.remove(rawItemId, 1) + if (!removed.hasSucceeded()) { + stop() + return@repeatWhile + } + + val successChance = computeSkillingSuccess( + low = 64, + high = 256, + level = (cookingLevel - def.level + 1).coerceIn(1, 99) + ).coerceIn(0.0, 1.0) + + val success = Random.nextDouble() < successChance + + if (success) { + if (!player.inventory.add(preparedId, 1).hasSucceeded()) { + player.inventory.add(rawItemId, 1) + player.filterableMessage("You don't have enough inventory space.") + stop() + return@repeatWhile + } + player.addXp(Skills.COOKING, def.xp.toDouble()) + val name = getItem(preparedId)?.name?.lowercase() ?: "fish" + player.filterableMessage("You prepare the $name.") + } else { + if (!player.inventory.add(ruinedId, 1).hasSucceeded()) { + player.inventory.add(rawItemId, 1) + player.filterableMessage("You don't have enough inventory space.") + stop() + return@repeatWhile + } + val name = getItem(ruinedId)?.name?.lowercase() ?: "fish" + player.filterableMessage("You ruin the $name.") + } + + prepared++ + wait(1) + } + + player.animate(RSCM.NONE) + } +} diff --git a/content/src/main/kotlin/org/alter/skills/cooking/events/CookingEvents.kt b/content/src/main/kotlin/org/alter/skills/cooking/events/CookingEvents.kt new file mode 100644 index 00000000..28e066ec --- /dev/null +++ b/content/src/main/kotlin/org/alter/skills/cooking/events/CookingEvents.kt @@ -0,0 +1,501 @@ +package org.alter.skills.cooking.events + +import dev.openrune.ServerCacheManager.getItem +import org.alter.api.Skills +import org.alter.api.ext.filterableMessage +import org.alter.api.ext.message +import org.alter.api.ext.produceItemBox +import org.alter.game.model.attr.AttributeKey +import org.alter.game.model.entity.GameObject +import org.alter.game.model.entity.Player +import org.alter.game.model.queue.QueueTask +import org.alter.game.model.timer.TimerKey +import org.alter.game.pluginnew.PluginEvent +import org.alter.game.pluginnew.event.impl.ItemOnObject +import org.alter.game.pluginnew.event.impl.ItemOnItemEvent +import org.alter.game.pluginnew.event.impl.ObjectClickEvent +import org.alter.game.pluginnew.event.impl.onTimer +import org.alter.rscm.RSCM +import org.alter.rscm.RSCM.asRSCM +import org.alter.skills.cooking.events.CookingUtils.CookStation +import org.alter.skills.cooking.events.CookingUtils.SPIT_ROAST_FIREMAKING_LEVEL +import org.alter.skills.cooking.events.CookingUtils.hasInputs +import org.alter.skills.cooking.events.CookingUtils.isSpitRoastAction +import org.alter.skills.cooking.events.CookingUtils.isStationAllowed +import org.alter.skills.cooking.events.CookingUtils.isStillAtStation +import org.alter.skills.cooking.events.CookingUtils.maxProducible +import org.alter.skills.cooking.events.CookingUtils.meetsExtraRequirements +import org.alter.skills.cooking.events.CookingUtils.rollCookSuccess +import org.alter.skills.cooking.events.CookingUtils.rollWineSuccess +import org.alter.skills.cooking.events.CookingUtils.stationFor +import org.alter.skills.cooking.runtime.CookingAction +import org.alter.skills.cooking.runtime.CookingActionRegistry +import org.alter.skills.cooking.runtime.OutcomeKind +import org.alter.skills.cooking.runtime.Trigger + +/** + * Main event handler for the Cooking skill. + * + * Handles: + * - Using raw food on fires/ranges (heat-source cooking) + * - Clicking "Cook" on fires/ranges + * - Item-on-item preparation (e.g., combining ingredients) + * - Camdozaal preparation table (knife-based prep) + */ +class CookingEvents : PluginEvent() { + + companion object { + /** Timer for wine fermentation delay (~12 seconds = 20 ticks). */ + val WINE_FERMENT_TIMER = TimerKey() + + /** Number of pending wine fermentations that will succeed. */ + val WINE_SUCCESS_COUNT = AttributeKey() + + /** Number of pending wine fermentations that will fail. */ + val WINE_FAIL_COUNT = AttributeKey() + + /** Fermentation delay in game ticks (12 seconds). */ + const val WINE_FERMENT_TICKS = 20 + } + + override fun init() { + registerCamdozaalPrepTableEvents() + registerHeatSourceCookingEvents() + registerItemOnItemPrepEvents() + registerWineFermentationTimer() + } + + // ======================================== + // Camdozaal Preparation Table + // ======================================== + + private fun registerCamdozaalPrepTableEvents() { + on { + where { + gameObject.internalID == CamdozaalPrepTable.prepTableId && + CamdozaalPrepTable.isRawPrepFish(item.id) + } + then { + player.queue { + with(CamdozaalPrepTable) { + prepareFish(player, gameObject, item.id) + } + } + } + } + } + + // ======================================== + // Heat-Source Cooking (Fire/Range) + // ======================================== + + private fun registerHeatSourceCookingEvents() { + // Click "Cook" on range/fire -> open cooking menu + on { + where { optionName.equals("cook", ignoreCase = true) } + then { + val station = stationFor(gameObject) + openCookingMenu(player, station, gameObject) + } + } + + // Use raw food on range/fire -> open cooking menu for that item + on { + where { + CookingActionRegistry.byRaw.containsKey(item.id) && + CookingUtils.isHeatSource(gameObject) + } + then { + val actions = CookingActionRegistry.byRaw[item.id] ?: return@then + val station = stationFor(gameObject) + openCookingMenu(player, station, gameObject, actions) + } + } + } + + // ======================================== + // Item-on-Item Preparation + // ======================================== + + private fun registerItemOnItemPrepEvents() { + onEvent { + val cookingLevel = player.getSkills().getCurrentLevel(Skills.COOKING) + val item1 = fromItem.id + val item2 = toItem.id + + // Use pre-indexed lookup: find actions that use either item as an input + val actionsForItem1 = CookingActionRegistry.itemOnItemByInput[item1] ?: emptyList() + val actionsForItem2 = CookingActionRegistry.itemOnItemByInput[item2] ?: emptyList() + + // Intersect: only actions that have BOTH items as inputs + val possibleActions = if (actionsForItem1.size <= actionsForItem2.size) { + actionsForItem1.filter { it in actionsForItem2 } + } else { + actionsForItem2.filter { it in actionsForItem1 } + } + + val candidates = possibleActions + .filter { cookingLevel >= it.row.level && hasInputs(player, it) } + + if (candidates.isEmpty()) { + return@onEvent + } + + // Wine actions (stationMask == 0) use the special fermentation path + val wineActions = candidates.filter { it.row.stationMask == 0 } + val normalActions = candidates.filter { it.row.stationMask != 0 } + + if (wineActions.isNotEmpty() && normalActions.isEmpty()) { + // Only wine candidates — use fermentation flow + player.queue { + val action = wineActions.first() + val maxCount = maxProducible(player, action) + performWineAction(player, action, maxCount) + } + return@onEvent + } + + // Normal item-on-item prep (non-wine candidates) + val activeCandidates = normalActions.ifEmpty { candidates } + val maxProducibleCount = activeCandidates.maxOf { action -> maxProducible(player, action) } + val options = activeCandidates + .sortedWith(compareBy { it.row.level }.thenBy { it.primaryOutputItem() }) + .take(18) + + player.queue { + val producedItems = options.map { it.primaryOutputItem() }.toIntArray() + + // If only one option, skip the UI and just do 1. + if (options.size == 1) { + performPrepAction(player, options.first(), 1) + return@queue + } + + produceItemBox( + player, + *producedItems, + title = "What would you like to make?", + maxProducable = maxProducibleCount + ) { selectedItemId, quantity -> + val action = options.firstOrNull { it.primaryOutputItem() == selectedItemId } + ?: return@produceItemBox + performPrepAction(player, action, quantity) + } + } + } + } + + // ======================================== + // Cooking Menu & Cooking Logic + // ======================================== + + private fun openCookingMenu( + player: Player, + station: CookStation, + gameObject: GameObject, + only: List? = null + ) { + val cookingLevel = player.getSkills().getCurrentLevel(Skills.COOKING) + + val candidates = (only ?: CookingActionRegistry.allActions) + .filter { action -> + action.row.trigger == Trigger.HEAT_SOURCE && + cookingLevel >= action.row.level && + isStationAllowed(action, station) && + meetsExtraRequirements(player, action) && + hasInputs(player, action) + } + + if (candidates.isEmpty()) { + player.message("You have nothing you can cook.") + return + } + + val maxOptions = 18 + val menuCandidates = candidates + .sortedWith( + compareBy { it.row.level } + .thenBy { getItem(it.primaryOutputItem())?.name ?: "" } + ) + .take(maxOptions) + + val maxProducibleCount = menuCandidates.maxOf { action -> maxProducible(player, action) } + + player.queue { + val cookedItems = menuCandidates.map { it.primaryOutputItem() }.toIntArray() + + produceItemBox( + player, + *cookedItems, + title = "What would you like to cook?", + maxProducable = maxProducibleCount + ) { cookedItemId, qty -> + val action = menuCandidates.firstOrNull { it.primaryOutputItem() == cookedItemId } + ?: return@produceItemBox + + cook(player, station, gameObject, action, qty) + } + } + } + + private fun cook( + player: Player, + station: CookStation, + gameObject: GameObject, + action: CookingAction, + quantity: Int + ) { + val cookingLevel = player.getSkills().getCurrentLevel(Skills.COOKING) + if (cookingLevel < action.row.level) { + player.filterableMessage("You need a Cooking level of ${action.row.level} to cook this.") + return + } + + if (!meetsExtraRequirements(player, action)) { + if (isSpitRoastAction(action)) { + player.filterableMessage("You need a Firemaking level of $SPIT_ROAST_FIREMAKING_LEVEL to roast this on an iron spit.") + } + return + } + + val objectTile = gameObject.tile + val objectId = gameObject.internalID + + player.queue { + var cooked = 0 + + player.filterableMessage("You begin cooking...") + + repeatWhile(delay = 4, immediate = true, canRepeat = { + cooked < quantity && hasInputs(player, action) && isStillAtStation(player, objectTile, objectId) + }) { + runCatching { player.animate("sequences.human_cooking", interruptable = true) } + .onFailure { player.animate(RSCM.NONE) } + + // Consume inputs + val inputs = action.inputs + val removedInputs = mutableListOf>() + for (input in inputs) { + val removed = player.inventory.remove(input.item, input.count) + if (!removed.hasSucceeded()) { + removedInputs.forEach { (itemId, count) -> player.inventory.add(itemId, count) } + stop() + return@repeatWhile + } + removedInputs.add(input.item to input.count) + } + + val success = rollCookSuccess(player, station, action) + val outcomes = CookingOutcomes.resolveOutcomes(action, success) + val addedOutputs = mutableListOf>() + + for (outcome in outcomes) { + val producedCount = CookingOutcomes.rollCount(outcome) + val addResult = player.inventory.add(outcome.item, producedCount) + if (!addResult.hasSucceeded()) { + addedOutputs.forEach { (itemId, count) -> player.inventory.remove(itemId, count) } + removedInputs.forEach { (itemId, count) -> player.inventory.add(itemId, count) } + player.filterableMessage("You don't have enough inventory space.") + stop() + return@repeatWhile + } + addedOutputs.add(outcome.item to producedCount) + + if (outcome.xp > 0) { + player.addXp(Skills.COOKING, outcome.xp.toDouble()) + } + } + + val primary = outcomes.firstOrNull { it.kind != OutcomeKind.ALWAYS } ?: outcomes.firstOrNull() + if (primary != null) { + if (primary.kind == OutcomeKind.SUCCESS) { + val cookedName = getItem(primary.item)?.name?.lowercase() ?: "food" + player.filterableMessage("You cook the $cookedName.") + } else { + val burntName = getItem(primary.item)?.name?.lowercase() ?: "food" + player.filterableMessage("You accidentally burn the $burntName.") + } + } + + cooked++ + wait(1) + } + + player.animate(RSCM.NONE) + } + } + + private fun performPrepAction( + player: Player, + action: CookingAction, + quantity: Int + ) { + val cookingLevel = player.getSkills().getCurrentLevel(Skills.COOKING) + if (cookingLevel < action.row.level) { + player.filterableMessage("You need a Cooking level of ${action.row.level} to make this.") + return + } + + player.queue { + var made = 0 + repeatWhile(delay = 2, immediate = true, canRepeat = { + made < quantity && hasInputs(player, action) + }) { + val removedInputs = mutableListOf>() + for (input in action.inputs) { + val removed = player.inventory.remove(input.item, input.count) + if (!removed.hasSucceeded()) { + removedInputs.forEach { (itemId, count) -> player.inventory.add(itemId, count) } + stop() + return@repeatWhile + } + removedInputs.add(input.item to input.count) + } + + val outcomes = CookingOutcomes.resolveOutcomes(action, success = true) + val addedOutputs = mutableListOf>() + + for (outcome in outcomes) { + val producedCount = CookingOutcomes.rollCount(outcome) + val addResult = player.inventory.add(outcome.item, producedCount) + if (!addResult.hasSucceeded()) { + addedOutputs.forEach { (itemId, count) -> player.inventory.remove(itemId, count) } + removedInputs.forEach { (itemId, count) -> player.inventory.add(itemId, count) } + player.filterableMessage("You don't have enough inventory space.") + stop() + return@repeatWhile + } + addedOutputs.add(outcome.item to producedCount) + + if (outcome.xp > 0) { + player.addXp(Skills.COOKING, outcome.xp.toDouble()) + } + } + + val primary = outcomes.firstOrNull { it.kind != OutcomeKind.ALWAYS } ?: outcomes.firstOrNull() + val name = primary?.let { getItem(it.item)?.name?.lowercase() } ?: "item" + player.filterableMessage("You make $name.") + made++ + } + } + } + + // ======================================== + // Wine Fermentation + // ======================================== + + /** + * Handles combining grapes with a jug of water to create unfermented wine. + * + * In OSRS, wine fermentation works as follows: + * 1. Grapes + jug of water → unfermented wine (instant) + * 2. Success/failure is rolled at combine time based on cooking level + * 3. A 12-second (~20 tick) timer starts/resets for ALL pending wines + * 4. When the timer fires, all unfermented wines convert to jug_of_wine + * (success) or jug_of_bad_wine (fail). XP is awarded only for successes. + */ + private suspend fun QueueTask.performWineAction( + player: Player, + action: CookingAction, + quantity: Int + ) { + val cookingLevel = player.getSkills().getCurrentLevel(Skills.COOKING) + if (cookingLevel < action.row.level) { + player.filterableMessage("You need a Cooking level of ${action.row.level} to do that.") + return + } + + var made = 0 + repeatWhile(delay = 2, immediate = true, canRepeat = { + made < quantity && hasInputs(player, action) + }) { + // Remove inputs (grapes + jug of water) + val removedInputs = mutableListOf>() + for (input in action.inputs) { + val removed = player.inventory.remove(input.item, input.count) + if (!removed.hasSucceeded()) { + removedInputs.forEach { (itemId, count) -> player.inventory.add(itemId, count) } + stop() + return@repeatWhile + } + removedInputs.add(input.item to input.count) + } + + // Add unfermented wine + val unfermentedWineId = action.outcomes + .firstOrNull { it.kind == OutcomeKind.SUCCESS }?.item + ?: action.primaryOutputItem() + val addResult = player.inventory.add(unfermentedWineId, 1) + if (!addResult.hasSucceeded()) { + removedInputs.forEach { (itemId, count) -> player.inventory.add(itemId, count) } + player.filterableMessage("You don't have enough inventory space.") + stop() + return@repeatWhile + } + + // Roll success/fail at combine time — store for fermentation + val success = rollWineSuccess(player, action) + if (success) { + val count = player.attr[WINE_SUCCESS_COUNT] ?: 0 + player.attr[WINE_SUCCESS_COUNT] = count + 1 + } else { + val count = player.attr[WINE_FAIL_COUNT] ?: 0 + player.attr[WINE_FAIL_COUNT] = count + 1 + } + + // Reset fermentation timer (each new wine resets the clock for all pending wines) + player.timers[WINE_FERMENT_TIMER] = WINE_FERMENT_TICKS + + player.filterableMessage("You squeeze the grapes into the jug of water.") + made++ + } + } + + /** + * Registers the timer handler that completes wine fermentation. + * + * When the timer fires, all pending unfermented wines are converted: + * - Successes → jug_of_wine (200 XP each) + * - Failures → jug_of_bad_wine (0 XP) + */ + private fun registerWineFermentationTimer() { + onTimer(WINE_FERMENT_TIMER) { + val p = player as Player + val successes = p.attr[WINE_SUCCESS_COUNT] ?: 0 + val fails = p.attr[WINE_FAIL_COUNT] ?: 0 + + val unfermentedId = runCatching { "items.jug_unfermented_wine".asRSCM() }.getOrNull() ?: return@onTimer + val wineId = runCatching { "items.jug_wine".asRSCM() }.getOrNull() ?: return@onTimer + val badWineId = runCatching { "items.jug_bad_wine".asRSCM() }.getOrNull() ?: return@onTimer + + // Process successes: replace unfermented wines with jug_of_wine + award XP + var successConverted = 0 + repeat(successes) { + if (p.inventory.contains(unfermentedId)) { + p.inventory.remove(unfermentedId, 1) + p.inventory.add(wineId, 1) + p.addXp(Skills.COOKING, 200.0) + successConverted++ + } + } + + // Process failures: replace unfermented wines with jug_of_bad_wine + var failConverted = 0 + repeat(fails) { + if (p.inventory.contains(unfermentedId)) { + p.inventory.remove(unfermentedId, 1) + p.inventory.add(badWineId, 1) + failConverted++ + } + } + + // Clean up attributes + p.attr.remove(WINE_SUCCESS_COUNT) + p.attr.remove(WINE_FAIL_COUNT) + + if (successConverted > 0 || failConverted > 0) { + p.filterableMessage("The wine has fermented.") + } + } + } +} diff --git a/content/src/main/kotlin/org/alter/skills/cooking/events/CookingOutcomes.kt b/content/src/main/kotlin/org/alter/skills/cooking/events/CookingOutcomes.kt new file mode 100644 index 00000000..6aa9e101 --- /dev/null +++ b/content/src/main/kotlin/org/alter/skills/cooking/events/CookingOutcomes.kt @@ -0,0 +1,68 @@ +package org.alter.skills.cooking.events + +import org.alter.skills.cooking.runtime.CookingAction +import org.alter.skills.cooking.runtime.OutcomeKind +import org.alter.skills.cooking.runtime.CookingOutcome +import kotlin.random.Random + +/** + * Outcome selection and resolution for cooking actions. + */ +object CookingOutcomes { + + /** + * Selects the primary outcome based on success/failure. + */ + fun selectOutcome(action: CookingAction, success: Boolean): CookingOutcome { + val outcomes = action.outcomes + val preferredKind = if (success) OutcomeKind.SUCCESS else OutcomeKind.FAIL + val candidates = outcomes.filter { it.kind == preferredKind } + if (candidates.isNotEmpty()) { + return weightedPick(candidates) + } + + // Fallback (misconfigured data): allow ANY non-ALWAYS outcome. + val any = outcomes.filter { it.kind != OutcomeKind.ALWAYS } + if (any.isNotEmpty()) { + return weightedPick(any) + } + + val alwaysOnly = outcomes.filter { it.kind == OutcomeKind.ALWAYS } + require(alwaysOnly.isNotEmpty()) { + "Cooking action has no outcomes for key=${action.key} variant=${action.variant}" + } + return weightedPick(alwaysOnly) + } + + /** + * Resolves all outcomes for an action (ALWAYS outcomes plus the main outcome). + */ + fun resolveOutcomes(action: CookingAction, success: Boolean): List { + val always = action.outcomes.filter { it.kind == OutcomeKind.ALWAYS } + val main = selectOutcome(action, success) + return if (main.kind == OutcomeKind.ALWAYS) always else always + main + } + + /** + * Picks an outcome from candidates based on weights. + */ + fun weightedPick(candidates: List): CookingOutcome { + if (candidates.size == 1) return candidates.first() + val total = candidates.sumOf { it.weight.coerceAtLeast(1) } + var roll = Random.nextInt(total) + for (candidate in candidates) { + roll -= candidate.weight.coerceAtLeast(1) + if (roll < 0) return candidate + } + return candidates.last() + } + + /** + * Rolls the production count for an outcome. + */ + fun rollCount(outcome: CookingOutcome): Int { + val min = outcome.countMin.coerceAtLeast(1) + val max = outcome.countMax.coerceAtLeast(min) + return if (min == max) min else Random.nextInt(min, max + 1) + } +} diff --git a/content/src/main/kotlin/org/alter/skills/cooking/events/CookingUtils.kt b/content/src/main/kotlin/org/alter/skills/cooking/events/CookingUtils.kt new file mode 100644 index 00000000..1c520eee --- /dev/null +++ b/content/src/main/kotlin/org/alter/skills/cooking/events/CookingUtils.kt @@ -0,0 +1,381 @@ +package org.alter.skills.cooking.events + +import dev.openrune.ServerCacheManager.getObject +import org.alter.api.EquipmentType +import org.alter.api.Skills +import org.alter.api.computeSkillingSuccess +import org.alter.api.ext.hasEquipped +import org.alter.game.model.entity.GameObject +import org.alter.game.model.entity.Player +import org.alter.impl.skills.cooking.ChanceDef +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.GAUNTLETS +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_10 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.HOSIDIUS_5 +import org.alter.impl.skills.cooking.CookingConstants.ChanceModifier.LUMBRIDGE +import org.alter.impl.skills.cooking.CookingConstants.STATION_FIRE +import org.alter.impl.skills.cooking.CookingConstants.STATION_RANGE +import org.alter.skills.cooking.runtime.CookingAction +import org.alter.skills.cooking.runtime.CookingActionRegistry +import org.alter.skills.cooking.runtime.OutcomeKind +import org.alter.skills.cooking.runtime.Trigger +import org.alter.skills.firemaking.ColoredLogs +import org.alter.rscm.RSCM.asRSCM +import kotlin.random.Random + +/** + * Utility functions for cooking events. + */ +object CookingUtils { + + /** + * Types of cooking stations. + */ + enum class CookStation { + FIRE, + RANGE, + /** Hosidius Kitchen range — 5% reduced burn chance. */ + HOSIDIUS_RANGE, + /** Lumbridge Castle range — reduced burn level for certain foods. */ + LUMBRIDGE_RANGE + } + + /** IDs of spit-skewered items that require firemaking level to roast. */ + val spitSkeweredIds: Set = setOf( + "items.spit_skewered_bird_meat", + "items.spit_skewered_beast_meat", + "items.spit_skewered_rabbit_meat", + "items.spit_skewered_chompy" + ).mapNotNull { key -> runCatching { key.asRSCM() }.getOrNull() }.toSet() + + /** Firemaking level required for spit roasting. */ + const val SPIT_ROAST_FIREMAKING_LEVEL: Int = 20 + + private val fireObjectIds: Set by lazy { buildFireObjectIds() } + + /** + * Object IDs for the Hosidius Kitchen range (5% burn reduction). + * In OSRS this is object 21302. + */ + private val hosidiusRangeIds: Set by lazy { + listOfNotNull( + runCatching { "objects.hosidius_range".asRSCM() }.getOrNull() + ).toSet() + } + + /** + * Object IDs for the Lumbridge Castle range (reduced stop-burn for low-level foods). + * In OSRS this is object 114 (post-Cook's Assistant). + */ + private val lumbridgeRangeIds: Set by lazy { + listOfNotNull( + runCatching { "objects.lumbridge_range".asRSCM() }.getOrNull() + ).toSet() + } + + // ------------------------------------------------------- + // Cooking gauntlets: items affected & lowered stop-burn + // ------------------------------------------------------- + + /** + * Items whose stop-burn level is lowered by the Cooking gauntlets. + * Maps raw item RSCM key -> (gauntlet stop-burn fire, gauntlet stop-burn range). + */ + private val gauntletStopBurnOverrides: Map> by lazy { + listOf( + "items.raw_lobster" to (64 to 64), + "items.raw_swordfish" to (81 to 81), + "items.raw_monkfish" to (90 to 87), + "items.raw_shark" to (94 to 94), + "items.raw_anglerfish" to (98 to 93) + ).mapNotNull { (key, stops) -> + val id = runCatching { key.asRSCM() }.getOrNull() ?: return@mapNotNull null + id to stops + }.toMap() + } + + /** Checks whether the player is wearing cooking gauntlets. */ + fun hasCookingGauntlets(player: Player): Boolean = + runCatching { player.hasEquipped(EquipmentType.GLOVES, "items.gauntlets_of_cooking") }.getOrDefault(false) + + /** Checks whether the player is wearing the cooking cape (or max cape). */ + fun hasCookingCape(player: Player): Boolean = + runCatching { + player.equipment.containsAny( + "items.skillcape_cooking", + "items.skillcape_cooking_trimmed", + "items.skillcape_max" + ) + }.getOrDefault(false) + + /** + * Determines the cooking station type for a given game object, or null if it is not a heat source. + */ + fun heatSourceStationFor(gameObject: GameObject): CookStation? { + val id = gameObject.internalID + if (fireObjectIds.contains(id)) return CookStation.FIRE + + // Check for special named ranges first + if (hosidiusRangeIds.contains(id)) return CookStation.HOSIDIUS_RANGE + if (lumbridgeRangeIds.contains(id)) return CookStation.LUMBRIDGE_RANGE + + val def = getObject(id) ?: return null + val hasCookAction = def.actions.any { it?.equals("cook", ignoreCase = true) == true } + return if (hasCookAction) CookStation.RANGE else null + } + + fun isHeatSource(gameObject: GameObject): Boolean = heatSourceStationFor(gameObject) != null + + /** + * Determines the cooking station type for a given game object. + */ + fun stationFor(gameObject: GameObject): CookStation { + return heatSourceStationFor(gameObject) ?: CookStation.RANGE + } + + /** + * Checks if a station type is allowed for the given action. + * Hosidius and Lumbridge ranges count as regular ranges for permission checks. + */ + fun isStationAllowed(action: CookingAction, station: CookStation): Boolean { + val mask = action.row.stationMask + return when (station) { + CookStation.FIRE -> (mask and 1) != 0 + CookStation.RANGE, + CookStation.HOSIDIUS_RANGE, + CookStation.LUMBRIDGE_RANGE -> (mask and 2) != 0 + } + } + + /** + * Checks if the player meets extra requirements (e.g., firemaking for spit roasting). + */ + fun meetsExtraRequirements(player: Player, action: CookingAction): Boolean { + if (isSpitRoastAction(action)) { + val fm = player.getSkills().getCurrentLevel(Skills.FIREMAKING) + return fm >= SPIT_ROAST_FIREMAKING_LEVEL + } + return true + } + + /** + * Checks if this action is a spit-roast action (fire-only, skewered input). + */ + fun isSpitRoastAction(action: CookingAction): Boolean { + if (action.row.trigger != Trigger.HEAT_SOURCE) return false + return action.inputs.any { it.item in spitSkeweredIds } + } + + /** + * Checks if the player has all required inputs for the action. + */ + fun hasInputs(player: Player, action: CookingAction): Boolean { + if (action.inputs.isEmpty()) return false + return action.inputs.all { input -> player.inventory.getItemCount(input.item) >= input.count } + } + + /** + * Calculates the maximum number of items that can be produced. + */ + fun maxProducible(player: Player, action: CookingAction): Int { + if (action.inputs.isEmpty()) return 0 + return action.inputs + .map { input -> player.inventory.getItemCount(input.item) / input.count.coerceAtLeast(1) } + .minOrNull() + ?: 0 + } + + /** + * Rolls to determine if cooking succeeds. + * + * Uses wiki-sourced [ChanceDef] profiles when available (preferred), falling + * back to the legacy stopBurn approach for recipes without chance data. + * + * The chance-based system: + * 1. Cooking cape / max cape → never burn. + * 2. Build the player's station mask and modifier mask from equipment/location. + * 3. Find the most specific matching [ChanceDef] profile. + * 4. Call [computeSkillingSuccess] with that profile's low/high values. + */ + fun rollCookSuccess( + player: Player, + station: CookStation, + action: CookingAction + ): Boolean { + // Cooking cape / max cape → never burn + if (hasCookingCape(player)) return true + + val cookingLevel = player.getSkills().getCurrentLevel(Skills.COOKING) + + // Try chance-based success (wiki-accurate profiles) + val chances = CookingActionRegistry.chancesByAction[action.key to action.variant] + if (!chances.isNullOrEmpty()) { + val stationMask = when (station) { + CookStation.FIRE -> STATION_FIRE + else -> STATION_RANGE + } + val modifierMask = buildModifierMask(player, station) + val best = findBestChance(chances, stationMask, modifierMask) + if (best != null) { + val chance = computeSkillingSuccess( + low = best.low, + high = best.high, + level = cookingLevel.coerceIn(1, 99) + ) + return Random.nextDouble() < chance + } + } + + // Legacy fallback: stopBurn system + return rollWithStopBurn(player, station, action, cookingLevel) + } + + /** + * Builds the player's modifier bitmask from equipment and station type. + */ + private fun buildModifierMask(player: Player, station: CookStation): Int { + var mask = 0 + if (hasCookingGauntlets(player)) mask = mask or GAUNTLETS + when (station) { + CookStation.HOSIDIUS_RANGE -> { + // TODO: Check Kourend Hard Diary for +10% — default to +5% + mask = mask or if (hasKourendHardDiary(player)) HOSIDIUS_10 else HOSIDIUS_5 + } + CookStation.LUMBRIDGE_RANGE -> mask = mask or LUMBRIDGE + else -> {} + } + return mask + } + + /** + * Returns true if the player has completed the Kourend Hard Diary. + * TODO: Implement actual diary achievement check. + */ + private fun hasKourendHardDiary(@Suppress("UNUSED_PARAMETER") player: Player): Boolean = false + + /** + * Selects the most specific [ChanceDef] that matches the given station and + * modifier mask. A profile matches if: + * 1. Its station mask overlaps with the player's station. + * 2. Its modifier mask is a subset of the player's active modifiers. + * + * Among matching profiles, the one with the most modifier bits set wins + * (most specific). Ties are broken by highest chance (low + high sum). + */ + private fun findBestChance( + chances: List, + stationMask: Int, + modifierMask: Int + ): ChanceDef? { + val candidates = chances + .filter { (it.stationMask and stationMask) != 0 } + .filter { (it.modifierMask and modifierMask) == it.modifierMask } + + if (candidates.isEmpty()) { + // No modifier-compatible match — try base profile (modifierMask == 0) + return chances.firstOrNull { + (it.stationMask and stationMask) != 0 && it.modifierMask == 0 + } + } + + return candidates.maxWithOrNull( + compareBy({ Integer.bitCount(it.modifierMask) }, { it.low + it.high }) + ) + } + + /** + * Legacy stop-burn based success roll. + * Used as fallback when no [ChanceDef] profiles are defined for a recipe. + */ + private fun rollWithStopBurn( + player: Player, + station: CookStation, + action: CookingAction, + cookingLevel: Int + ): Boolean { + val wearingGauntlets = hasCookingGauntlets(player) + + val baseStation = when (station) { + CookStation.FIRE -> CookStation.FIRE + else -> CookStation.RANGE + } + + val stopBurn: Int = if (wearingGauntlets) { + val override = gauntletStopBurnOverrides[action.key] + if (override != null) { + when (baseStation) { + CookStation.FIRE -> override.first + else -> override.second + } + } else { + when (baseStation) { + CookStation.FIRE -> action.row.stopBurnFire + else -> action.row.stopBurnRange + } + } + } else { + when (baseStation) { + CookStation.FIRE -> action.row.stopBurnFire + else -> action.row.stopBurnRange + } + }.coerceAtLeast(action.row.level) + + if (cookingLevel >= stopBurn) return true + + val chance = computeSkillingSuccess( + low = action.row.level, + high = stopBurn, + level = cookingLevel.coerceIn(1, 99) + ) + + val adjustedChance = when (station) { + CookStation.HOSIDIUS_RANGE -> (chance * 1.05).coerceAtMost(1.0) + else -> chance + } + + return Random.nextDouble() < adjustedChance + } + + /** + * Rolls wine fermentation success. Wine has no station; uses fire stop-burn values. + * Cooking cape never burns. Gauntlets have no effect on wine. + */ + fun rollWineSuccess(player: Player, action: CookingAction): Boolean { + if (hasCookingCape(player)) return true + + val cookingLevel = player.getSkills().getCurrentLevel(Skills.COOKING) + val stopBurn = action.row.stopBurnFire.coerceAtLeast(action.row.level) + if (cookingLevel >= stopBurn) return true + + val chance = computeSkillingSuccess( + low = action.row.level, + high = stopBurn, + level = cookingLevel.coerceIn(1, 99) + ) + return Random.nextDouble() < chance + } + + /** + * Checks if the player is still at the cooking station. + */ + fun isStillAtStation( + player: Player, + objectTile: org.alter.game.model.Tile, + objectId: Int + ): Boolean { + if (player.tile.getDistance(objectTile) > 1) return false + + val world = player.world + val obj = world.getObject(objectTile, type = 10) ?: world.getObject(objectTile, type = 11) + return obj != null && obj.internalID == objectId + } + + private fun buildFireObjectIds(): Set { + val fireKeys = ColoredLogs.COLOURED_LOGS.values.map { it.second } + + "objects.fire" + + "objects.forestry_fire" + + return fireKeys.mapNotNull { key -> + runCatching { key.asRSCM() }.getOrNull() + }.toSet() + } +} diff --git a/content/src/main/kotlin/org/alter/skills/cooking/runtime/CookingActionRegistry.kt b/content/src/main/kotlin/org/alter/skills/cooking/runtime/CookingActionRegistry.kt new file mode 100644 index 00000000..fbf9a752 --- /dev/null +++ b/content/src/main/kotlin/org/alter/skills/cooking/runtime/CookingActionRegistry.kt @@ -0,0 +1,138 @@ +package org.alter.skills.cooking.runtime + +import org.alter.impl.skills.cooking.ChanceDef +import org.alter.impl.skills.cooking.CookingRecipeRegistry +import org.alter.skills.cooking.runtime.OutcomeKind +import org.generated.tables.cooking.CookingActionInputsRow +import org.generated.tables.cooking.CookingActionOutcomesRow +import org.generated.tables.cooking.CookingActionsRow +import org.alter.rscm.RSCM.asRSCM + +/** + * Runtime models for loaded cooking recipe data. + */ + +/** + * An input requirement for a cooking action. + */ +data class CookingInput( + val item: Int, + val count: Int +) + +/** + * A possible outcome of a cooking action. + */ +data class CookingOutcome( + val kind: Int, + val item: Int, + val countMin: Int, + val countMax: Int, + val xp: Int, + val weight: Int +) + +/** + * A complete cooking action with loaded data. + */ +data class CookingAction( + val row: CookingActionsRow, + val inputs: List, + val outcomes: List +) { + val key: Int get() = row.key + val variant: Int get() = row.variant + + /** + * Returns the primary output item (first SUCCESS outcome, else first ALWAYS, else first). + */ + fun primaryOutputItem(): Int { + val success = outcomes.firstOrNull { it.kind == OutcomeKind.SUCCESS } + if (success != null) return success.item + val always = outcomes.firstOrNull { it.kind == OutcomeKind.ALWAYS } + if (always != null) return always.item + return outcomes.firstOrNull()?.item ?: -1 + } +} + +/** + * Registry of all cooking actions loaded from DB tables. + */ +object CookingActionRegistry { + + /** + * All cooking actions loaded from DB tables. + */ + val allActions: List by lazy { + val actions = CookingActionsRow.all() + val inputsByKey = CookingActionInputsRow.all().groupBy { it.key to it.variant } + val outcomesByKey = CookingActionOutcomesRow.all().groupBy { it.key to it.variant } + + actions.map { actionRow -> + val joinKey = actionRow.key to actionRow.variant + val inputs = (inputsByKey[joinKey] ?: emptyList()) + .map { CookingInput(item = it.item, count = it.count.coerceAtLeast(1)) } + val outcomes = (outcomesByKey[joinKey] ?: emptyList()) + .map { + CookingOutcome( + kind = it.kind, + item = it.item, + countMin = it.countMin.coerceAtLeast(1), + countMax = it.countMax.coerceAtLeast(1), + xp = it.xp, + weight = it.weight.coerceAtLeast(1) + ) + } + + CookingAction(row = actionRow, inputs = inputs, outcomes = outcomes) + } + } + + /** + * Actions indexed by their raw item (key). + */ + val byRaw: Map> by lazy { + allActions.groupBy { it.key } + } + + /** + * Actions indexed by their cooked item (primary output). + */ + val byCooked: Map by lazy { + allActions + .mapNotNull { action -> + val cooked = action.primaryOutputItem() + if (cooked <= 0) null else cooked to action + } + .toMap() + } + + /** + * Item-on-item actions indexed by each input item ID. + * For any item involved in an ITEM_ON_ITEM action, returns + * the list of actions that use it as an input. + */ + val itemOnItemByInput: Map> by lazy { + allActions + .filter { it.row.trigger == Trigger.ITEM_ON_ITEM } + .flatMap { action -> action.inputs.map { it.item to action } } + .groupBy({ it.first }, { it.second }) + .mapValues { (_, actions) -> actions.distinct() } + } + + /** + * Chance profiles indexed by (key, variant). + * + * Built directly from [CookingRecipeRegistry.allRecipes] at startup. + * This avoids needing a separate DB table for chance data. + */ + val chancesByAction: Map, List> by lazy { + CookingRecipeRegistry.allRecipes + .filter { it.chances.isNotEmpty() } + .mapNotNull { action -> + val id = runCatching { action.key.asRSCM() }.getOrNull() ?: return@mapNotNull null + (id to action.variant) to action.chances + } + .toMap() + } +} diff --git a/content/src/main/kotlin/org/alter/skills/cooking/runtime/CookingConstants.kt b/content/src/main/kotlin/org/alter/skills/cooking/runtime/CookingConstants.kt new file mode 100644 index 00000000..d2810afb --- /dev/null +++ b/content/src/main/kotlin/org/alter/skills/cooking/runtime/CookingConstants.kt @@ -0,0 +1,23 @@ +package org.alter.skills.cooking.runtime + +/** + * Trigger types determine how a cooking action is initiated. + */ +object Trigger { + /** Action triggered by using item on a heat source (fire/range). */ + const val HEAT_SOURCE = 0 + /** Action triggered by using one item on another in inventory. */ + const val ITEM_ON_ITEM = 1 +} + +/** + * Outcome kinds determine what type of result a cooking action produces. + */ +object OutcomeKind { + /** Successful cooking result. */ + const val SUCCESS = 0 + /** Failed cooking result (burnt). */ + const val FAIL = 1 + /** Always produced regardless of success/failure. */ + const val ALWAYS = 2 +} diff --git a/content/src/main/resources/org/alter/items/consumables/gamevals.toml b/content/src/main/resources/org/alter/items/consumables/gamevals.toml index aeafc2f6..1efe750e 100644 --- a/content/src/main/resources/org/alter/items/consumables/gamevals.toml +++ b/content/src/main/resources/org/alter/items/consumables/gamevals.toml @@ -7,6 +7,7 @@ shrimps_food=98030 cooked_chicken_food=98031 cooked_meat_food=98032 bread_food=98033 +anchovies_food= 97096 herring_food=98034 mackerel_food=98035 trout_food=98036 @@ -80,4 +81,119 @@ blighted_karambwan_food=98104 blighted_manta_ray_food=98105 blighted_anglerfish_food=98106 sweets_food=98107 -moonlight_mead_food=98108 \ No newline at end of file +moonlight_mead_food=98108 +asgarnian_ale_food= 97038 +axemans_folly_food= 97039 +baguette_food= 97040 +baked_potato_food= 97041 +banana_food= 97042 +bat_shish_food= 97043 +beer_food= 97044 +blue_crab_meat_food= 97045 +bluefin_food= 97046 +blurberry_special_food= 97047 +bowl_of_chilli_con_carne_food= 97048 +brandy_food= 97049 +cabbage_food= 97050 +caerula_berries_food= 97051 +cave_eel_food= 97052 +cave_eel_sushi_food= 97053 +cavefish_food= 97054 +caviar_food= 97055 +cheese_food= 97056 +chefs_delight_food= 97057 +chilli_potato_food= 97058 +choc_ice_food= 97059 +chocolate_bar_food= 97060 +chocolate_saturday_food= 97061 +cider_food= 97062 +cod_food= 97063 +cooked_barb_tailed_kebbit_food= 97064 +cooked_chompy_food= 97065 +cooked_dashing_kebbit_food= 97066 +cooked_graahk_food= 97067 +cooked_jubbly_food= 97068 +cooked_kyatt_food= 97069 +cooked_larupia_food= 97070 +cooked_mystery_meat_food= 97071 +cooked_rabbit_food= 97072 +cooked_sweetcorn_food= 97073 +cooked_wild_kebbit_food= 97074 +dragon_bitter_food= 97075 +dragonfruit_food= 97076 +drunk_dragon_food= 97077 +dwarven_stout_food= 97078 +dwellberries_food= 97079 +easter_egg_food= 97080 +edible_seaweed_food= 97081 +egg_and_tomato_food= 97082 +egg_potato_food= 97083 +fat_snail_meat_food= 97084 +fried_mushrooms_food= 97085 +frog_burger_food= 97086 +frog_spawn_gumbo_food= 97087 +fruit_blast_food= 97088 +giant_frog_legs_food= 97089 +giant_krill_food= 97090 +gin_food= 97091 +green_gloop_soup_food= 97092 +greenmans_ale_food= 97093 +grog_food= 97094 +haddock_food= 97095 +halibut_food= 98155 +jangerberries_food= 98156 +jumbo_squid_food= 98157 +kebab_food= 98186 +keg_of_beer_food= 98210 +kovacs_grog_food= 98211 +lava_eel_food= 98212 +lean_snail_meat_food= 98213 +lemon_food= 98214 +lime_food= 98215 +locust_meat_food= 98216 +marlin_food= 98217 +mushroom_and_onion_food= 98218 +mushroom_potato_food= 98219 +orange_food= 98220 +papaya_fruit_food= 98221 +pineapple_food= 98222 +pineapple_punch_food= 98223 +potato_food= 98224 +potato_with_butter_food= 98225 +premade_blurberry_special_food= 98226 +premade_choc_saturday_food= 98227 +premade_drunk_dragon_food= 98228 +premade_fruit_blast_food= 98229 +premade_pineapple_punch_food= 98230 +premade_sgg_food= 98231 +premade_wizard_blizzard_food= 98232 +pumpkin_food= 98233 +rainbow_crab_meat_food= 98234 +red_crab_meat_food= 98235 +roast_beast_meat_food= 98236 +roast_bird_meat_food= 98237 +roast_rabbit_food= 98238 +roe_food= 98239 +sardine_food= 98240 +sauteed_mushrooms_food= 98241 +scrambled_egg_food= 98242 +seasoned_sardine_food= 98243 +short_green_guy_food= 98244 +slayers_respite_food= 98245 +spider_on_shaft_food= 98246 +spider_on_stick_food= 98247 +stew_food= 98248 +strawberry_food= 98249 +swordtip_squid_food= 98250 +tetra_food= 98251 +thin_snail_meat_food= 98252 +tomato_food= 98253 +tuna_and_corn_food= 98254 +ugthanki_kebab_food= 98255 +vodka_food= 98256 +watermelon_slice_food= 98257 +whisky_food= 98258 +wizard_blizzard_food= 98259 +wizards_mind_bomb_food= 98260 +wrapped_oomlie_food= 98261 +yellowfin_food= 98262 \ No newline at end of file diff --git a/content/src/main/resources/org/alter/skills/cooking/gamevals.toml b/content/src/main/resources/org/alter/skills/cooking/gamevals.toml new file mode 100644 index 00000000..f8b01d64 --- /dev/null +++ b/content/src/main/resources/org/alter/skills/cooking/gamevals.toml @@ -0,0 +1,569 @@ +[gamevals.tables] +cooking_recipes=1236 +cooking_actions=1237 +cooking_action_inputs=1238 +cooking_action_outcomes=1239 + +[gamevals.dbrows] +cooking_shrimps=982014 +cooking_anchovies=982015 +cooking_sardine=982016 +cooking_herring=982017 +cooking_mackerel=982018 +cooking_trout=982019 +cooking_salmon=982020 +cooking_lobster=982021 +cooking_swordfish=982022 +cooking_shark=982023 +cooking_beef=982024 +cooking_chicken=982025 + +cooking_cod=982026 +cooking_pike=982027 +cooking_tuna=982028 +cooking_bass=982029 +cooking_monkfish=982030 +cooking_sea_turtle=982031 +cooking_anglerfish=982032 +cooking_dark_crab=982033 +cooking_manta_ray=982034 + +cooking_karambwan=982127 +cooking_rainbow_fish=982137 +cooking_cave_eel=982141 + +cooking_slimy_eel=982145 +cooking_rat_meat=982146 +cooking_bear_meat=982147 +cooking_rabbit=982148 +cooking_ugthanki_meat=982149 +cooking_roast_bird_meat=982150 + +cooking_guppy=982169 +cooking_cavefish=982170 +cooking_roast_beast_meat=982171 +cooking_chompy=982172 +cooking_tetra=982173 +cooking_catfish=982174 + +cooking_garden_pie_add_tomato=982098 +cooking_garden_pie_add_onion=982099 +cooking_garden_pie_add_cabbage=982100 +cooking_garden_pie_bake=982101 + +cooking_shrimps_input_0=982035 +cooking_anchovies_input_0=982036 +cooking_sardine_input_0=982037 +cooking_herring_input_0=982038 +cooking_mackerel_input_0=982039 +cooking_trout_input_0=982040 +cooking_salmon_input_0=982041 +cooking_lobster_input_0=982042 +cooking_swordfish_input_0=982043 +cooking_shark_input_0=982044 +cooking_beef_input_0=982045 +cooking_chicken_input_0=982046 +cooking_cod_input_0=982047 +cooking_pike_input_0=982048 +cooking_tuna_input_0=982049 +cooking_bass_input_0=982050 +cooking_monkfish_input_0=982051 +cooking_sea_turtle_input_0=982052 +cooking_anglerfish_input_0=982053 +cooking_dark_crab_input_0=982054 +cooking_manta_ray_input_0=982055 + +cooking_karambwan_input_0=982130 +cooking_rainbow_fish_input_0=982138 +cooking_cave_eel_input_0=982142 + +cooking_slimy_eel_input_0=982151 +cooking_rat_meat_input_0=982152 +cooking_bear_meat_input_0=982153 +cooking_rabbit_input_0=982154 +cooking_ugthanki_meat_input_0=982155 +cooking_roast_bird_meat_input_0=982156 + +cooking_guppy_input_0=982175 +cooking_cavefish_input_0=982176 +cooking_roast_beast_meat_input_0=982177 +cooking_chompy_input_0=982178 +cooking_tetra_input_0=982179 +cooking_catfish_input_0=982180 + +cooking_garden_pie_add_tomato_input_0=982102 +cooking_garden_pie_add_tomato_input_1=982103 +cooking_garden_pie_add_onion_input_0=982104 +cooking_garden_pie_add_onion_input_1=982105 +cooking_garden_pie_add_cabbage_input_0=982106 +cooking_garden_pie_add_cabbage_input_1=982107 +cooking_garden_pie_bake_input_0=982108 + +cooking_shrimps_outcome_success=982056 +cooking_anchovies_outcome_success=982057 +cooking_sardine_outcome_success=982058 +cooking_herring_outcome_success=982059 +cooking_mackerel_outcome_success=982060 +cooking_trout_outcome_success=982061 +cooking_salmon_outcome_success=982062 +cooking_lobster_outcome_success=982063 +cooking_swordfish_outcome_success=982064 +cooking_shark_outcome_success=982065 +cooking_beef_outcome_success=982066 +cooking_chicken_outcome_success=982067 +cooking_cod_outcome_success=982068 +cooking_pike_outcome_success=982069 +cooking_tuna_outcome_success=982070 +cooking_bass_outcome_success=982071 +cooking_monkfish_outcome_success=982072 +cooking_sea_turtle_outcome_success=982073 +cooking_anglerfish_outcome_success=982074 +cooking_dark_crab_outcome_success=982075 +cooking_manta_ray_outcome_success=982076 + +cooking_karambwan_outcome_success=982133 +cooking_rainbow_fish_outcome_success=982139 +cooking_cave_eel_outcome_success=982143 + +cooking_slimy_eel_outcome_success=982157 +cooking_rat_meat_outcome_success=982158 +cooking_bear_meat_outcome_success=982159 +cooking_rabbit_outcome_success=982160 +cooking_ugthanki_meat_outcome_success=982161 +cooking_roast_bird_meat_outcome_success=982162 + +cooking_guppy_outcome_success=982181 +cooking_cavefish_outcome_success=982182 +cooking_roast_beast_meat_outcome_success=982183 +cooking_chompy_outcome_success=982184 +cooking_tetra_outcome_success=982185 +cooking_catfish_outcome_success=982186 + +cooking_garden_pie_add_tomato_outcome_success=982109 +cooking_garden_pie_add_onion_outcome_success=982110 +cooking_garden_pie_add_cabbage_outcome_success=982111 +cooking_garden_pie_bake_outcome_success=982112 + +cooking_shrimps_outcome_fail=982077 +cooking_anchovies_outcome_fail=982078 +cooking_sardine_outcome_fail=982079 +cooking_herring_outcome_fail=982080 +cooking_mackerel_outcome_fail=982081 +cooking_trout_outcome_fail=982082 +cooking_salmon_outcome_fail=982083 +cooking_lobster_outcome_fail=982084 +cooking_swordfish_outcome_fail=982085 +cooking_shark_outcome_fail=982086 +cooking_beef_outcome_fail=982087 +cooking_chicken_outcome_fail=982088 +cooking_cod_outcome_fail=982089 +cooking_pike_outcome_fail=982090 +cooking_tuna_outcome_fail=982091 +cooking_bass_outcome_fail=982092 +cooking_monkfish_outcome_fail=982093 +cooking_sea_turtle_outcome_fail=982094 +cooking_anglerfish_outcome_fail=982095 +cooking_dark_crab_outcome_fail=982096 +cooking_manta_ray_outcome_fail=982097 + +cooking_karambwan_outcome_fail=982136 +cooking_rainbow_fish_outcome_fail=982140 +cooking_cave_eel_outcome_fail=982144 + +cooking_slimy_eel_outcome_fail=982163 +cooking_rat_meat_outcome_fail=982164 +cooking_bear_meat_outcome_fail=982165 +cooking_rabbit_outcome_fail=982166 +cooking_ugthanki_meat_outcome_fail=982167 +cooking_roast_bird_meat_outcome_fail=982168 + +cooking_guppy_outcome_fail=982187 +cooking_cavefish_outcome_fail=982188 +cooking_roast_beast_meat_outcome_fail=982189 +cooking_chompy_outcome_fail=982190 +cooking_tetra_outcome_fail=982191 +cooking_catfish_outcome_fail=982192 + +cooking_garden_pie_bake_outcome_fail=982113 + +cooking_cake_mix=982114 +cooking_cake_bake=982115 + +cooking_cake_mix_input_0=982116 +cooking_cake_mix_input_1=982117 +cooking_cake_mix_input_2=982118 +cooking_cake_mix_input_3=982119 +cooking_cake_bake_input_0=982120 + +cooking_cake_mix_outcome_success=982121 +cooking_cake_bake_outcome_success=982122 + +cooking_cake_bake_outcome_fail=982123 + +cooking_cake_mix_outcome_always_0=982124 +cooking_cake_mix_outcome_always_1=982125 +cooking_cake_bake_outcome_always_0=982126 + +# --- Skewer (spit-roast prep) entries --- +cooking_skewer_bird_meat=982193 +cooking_skewer_beast_meat=982194 +cooking_skewer_bird_meat_input_0=982195 +cooking_skewer_bird_meat_input_1=982196 +cooking_skewer_beast_meat_input_0=982197 +cooking_skewer_beast_meat_input_1=982198 +cooking_skewer_bird_meat_outcome_success=982199 +cooking_skewer_beast_meat_outcome_success=982200 + +# --- Bread --- +cooking_bread=982201 +cooking_pitta_bread=982202 + +cooking_bread_input_0=982203 +cooking_pitta_bread_input_0=982204 + +cooking_bread_outcome_success=982205 +cooking_bread_outcome_fail=982206 +cooking_pitta_bread_outcome_success=982207 +cooking_pitta_bread_outcome_fail=982208 + +# --- Pies --- +cooking_redberry_pie_add_berries=982209 +cooking_redberry_pie_bake=982210 +cooking_redberry_pie_add_berries_input_0=982211 +cooking_redberry_pie_add_berries_input_1=982212 +cooking_redberry_pie_bake_input_0=982213 +cooking_redberry_pie_add_berries_outcome_success=982214 +cooking_redberry_pie_bake_outcome_success=982215 +cooking_redberry_pie_bake_outcome_fail=982216 + +cooking_meat_pie_add_meat=982217 +cooking_meat_pie_bake=982218 +cooking_meat_pie_add_meat_input_0=982219 +cooking_meat_pie_add_meat_input_1=982220 +cooking_meat_pie_bake_input_0=982221 +cooking_meat_pie_add_meat_outcome_success=982222 +cooking_meat_pie_bake_outcome_success=982223 +cooking_meat_pie_bake_outcome_fail=982224 + +cooking_mud_pie_add_compost=982225 +cooking_mud_pie_add_water=982226 +cooking_mud_pie_add_clay=982227 +cooking_mud_pie_bake=982228 +cooking_mud_pie_add_compost_input_0=982229 +cooking_mud_pie_add_compost_input_1=982230 +cooking_mud_pie_add_water_input_0=982231 +cooking_mud_pie_add_water_input_1=982232 +cooking_mud_pie_add_clay_input_0=982233 +cooking_mud_pie_add_clay_input_1=982234 +cooking_mud_pie_bake_input_0=982235 +cooking_mud_pie_add_compost_outcome_success=982236 +cooking_mud_pie_add_water_outcome_success=982237 +cooking_mud_pie_add_water_outcome_always_0=982238 +cooking_mud_pie_add_clay_outcome_success=982239 +cooking_mud_pie_bake_outcome_success=982240 +cooking_mud_pie_bake_outcome_fail=982241 + +cooking_apple_pie_add_apple=982242 +cooking_apple_pie_bake=982243 +cooking_apple_pie_add_apple_input_0=982244 +cooking_apple_pie_add_apple_input_1=982245 +cooking_apple_pie_bake_input_0=982246 +cooking_apple_pie_add_apple_outcome_success=982247 +cooking_apple_pie_bake_outcome_success=982248 +cooking_apple_pie_bake_outcome_fail=982249 + +cooking_fish_pie_add_trout=982250 +cooking_fish_pie_add_cod=982251 +cooking_fish_pie_add_potato=982252 +cooking_fish_pie_bake=982253 +cooking_fish_pie_add_trout_input_0=982254 +cooking_fish_pie_add_trout_input_1=982255 +cooking_fish_pie_add_cod_input_0=982256 +cooking_fish_pie_add_cod_input_1=982257 +cooking_fish_pie_add_potato_input_0=982258 +cooking_fish_pie_add_potato_input_1=982259 +cooking_fish_pie_bake_input_0=982260 +cooking_fish_pie_add_trout_outcome_success=982261 +cooking_fish_pie_add_cod_outcome_success=982262 +cooking_fish_pie_add_potato_outcome_success=982263 +cooking_fish_pie_bake_outcome_success=982264 +cooking_fish_pie_bake_outcome_fail=982265 + +cooking_botanical_pie_add_golovanova=982266 +cooking_botanical_pie_bake=982267 +cooking_botanical_pie_add_golovanova_input_0=982268 +cooking_botanical_pie_add_golovanova_input_1=982269 +cooking_botanical_pie_bake_input_0=982270 +cooking_botanical_pie_add_golovanova_outcome_success=982271 +cooking_botanical_pie_bake_outcome_success=982272 +cooking_botanical_pie_bake_outcome_fail=982273 + +cooking_mushroom_pie_add_sulliuscep=982274 +cooking_mushroom_pie_bake=982275 +cooking_mushroom_pie_add_sulliuscep_input_0=982276 +cooking_mushroom_pie_add_sulliuscep_input_1=982277 +cooking_mushroom_pie_bake_input_0=982278 +cooking_mushroom_pie_add_sulliuscep_outcome_success=982279 +cooking_mushroom_pie_bake_outcome_success=982280 +cooking_mushroom_pie_bake_outcome_fail=982281 + +cooking_admiral_pie_add_salmon=982282 +cooking_admiral_pie_add_tuna=982283 +cooking_admiral_pie_add_potato=982284 +cooking_admiral_pie_bake=982285 +cooking_admiral_pie_add_salmon_input_0=982286 +cooking_admiral_pie_add_salmon_input_1=982287 +cooking_admiral_pie_add_tuna_input_0=982288 +cooking_admiral_pie_add_tuna_input_1=982289 +cooking_admiral_pie_add_potato_input_0=982290 +cooking_admiral_pie_add_potato_input_1=982291 +cooking_admiral_pie_bake_input_0=982292 +cooking_admiral_pie_add_salmon_outcome_success=982293 +cooking_admiral_pie_add_tuna_outcome_success=982294 +cooking_admiral_pie_add_potato_outcome_success=982295 +cooking_admiral_pie_bake_outcome_success=982296 +cooking_admiral_pie_bake_outcome_fail=982297 + +cooking_dragonfruit_pie_add_fruit=982298 +cooking_dragonfruit_pie_bake=982299 +cooking_dragonfruit_pie_add_fruit_input_0=982300 +cooking_dragonfruit_pie_add_fruit_input_1=982301 +cooking_dragonfruit_pie_bake_input_0=982302 +cooking_dragonfruit_pie_add_fruit_outcome_success=982303 +cooking_dragonfruit_pie_bake_outcome_success=982304 +cooking_dragonfruit_pie_bake_outcome_fail=982305 + +cooking_wild_pie_add_bear=982306 +cooking_wild_pie_add_chompy=982307 +cooking_wild_pie_add_rabbit=982308 +cooking_wild_pie_bake=982309 +cooking_wild_pie_add_bear_input_0=982310 +cooking_wild_pie_add_bear_input_1=982311 +cooking_wild_pie_add_chompy_input_0=982312 +cooking_wild_pie_add_chompy_input_1=982313 +cooking_wild_pie_add_rabbit_input_0=982314 +cooking_wild_pie_add_rabbit_input_1=982315 +cooking_wild_pie_bake_input_0=982316 +cooking_wild_pie_add_bear_outcome_success=982317 +cooking_wild_pie_add_chompy_outcome_success=982318 +cooking_wild_pie_add_rabbit_outcome_success=982319 +cooking_wild_pie_bake_outcome_success=982320 +cooking_wild_pie_bake_outcome_fail=982321 + +cooking_summer_pie_add_watermelon=982322 +cooking_summer_pie_add_apple=982323 +cooking_summer_pie_add_strawberry=982324 +cooking_summer_pie_bake=982325 +cooking_summer_pie_add_watermelon_input_0=982326 +cooking_summer_pie_add_watermelon_input_1=982327 +cooking_summer_pie_add_apple_input_0=982328 +cooking_summer_pie_add_apple_input_1=982329 +cooking_summer_pie_add_strawberry_input_0=982330 +cooking_summer_pie_add_strawberry_input_1=982331 +cooking_summer_pie_bake_input_0=982332 +cooking_summer_pie_add_watermelon_outcome_success=982333 +cooking_summer_pie_add_apple_outcome_success=982334 +cooking_summer_pie_add_strawberry_outcome_success=982335 +cooking_summer_pie_bake_outcome_success=982336 +cooking_summer_pie_bake_outcome_fail=982337 + +# --- Pizzas --- +cooking_pizza_add_tomato=982338 +cooking_pizza_add_cheese=982339 +cooking_pizza_bake=982340 +cooking_pizza_add_tomato_input_0=982341 +cooking_pizza_add_tomato_input_1=982342 +cooking_pizza_add_cheese_input_0=982343 +cooking_pizza_add_cheese_input_1=982344 +cooking_pizza_bake_input_0=982345 +cooking_pizza_add_tomato_outcome_success=982346 +cooking_pizza_add_cheese_outcome_success=982347 +cooking_pizza_bake_outcome_success=982348 +cooking_pizza_bake_outcome_fail=982349 + +cooking_pizza_add_meat=982350 +cooking_pizza_add_meat_input_0=982351 +cooking_pizza_add_meat_input_1=982352 +cooking_pizza_add_meat_outcome_success=982353 + +cooking_pizza_add_anchovies=982354 +cooking_pizza_add_anchovies_input_0=982355 +cooking_pizza_add_anchovies_input_1=982356 +cooking_pizza_add_anchovies_outcome_success=982357 + +cooking_pizza_add_pineapple=982358 +cooking_pizza_add_pineapple_input_0=982359 +cooking_pizza_add_pineapple_input_1=982360 +cooking_pizza_add_pineapple_outcome_success=982361 + +# --- Stews --- +cooking_stew_add_potato=982362 +cooking_stew_add_meat=982363 +cooking_stew_cook=982364 +cooking_stew_add_potato_input_0=982365 +cooking_stew_add_potato_input_1=982366 +cooking_stew_add_meat_input_0=982367 +cooking_stew_add_meat_input_1=982368 +cooking_stew_cook_input_0=982369 +cooking_stew_add_potato_outcome_success=982370 +cooking_stew_add_meat_outcome_success=982371 +cooking_stew_cook_outcome_success=982372 +cooking_stew_cook_outcome_fail=982373 + +cooking_curry_add_potato=982374 +cooking_curry_add_meat=982375 +cooking_curry_add_spice=982376 +cooking_curry_cook=982377 +cooking_curry_add_potato_input_0=982378 +cooking_curry_add_potato_input_1=982379 +cooking_curry_add_meat_input_0=982380 +cooking_curry_add_meat_input_1=982381 +cooking_curry_add_spice_input_0=982382 +cooking_curry_add_spice_input_1=982383 +cooking_curry_cook_input_0=982384 +cooking_curry_add_potato_outcome_success=982385 +cooking_curry_add_meat_outcome_success=982386 +cooking_curry_add_spice_outcome_success=982387 +cooking_curry_cook_outcome_success=982388 +cooking_curry_cook_outcome_fail=982389 + +# --- Potatoes --- +cooking_baked_potato=982390 +cooking_baked_potato_input_0=982391 +cooking_baked_potato_outcome_success=982392 +cooking_baked_potato_outcome_fail=982393 + +cooking_potato_add_butter=982394 +cooking_potato_add_butter_input_0=982395 +cooking_potato_add_butter_input_1=982396 +cooking_potato_add_butter_outcome_success=982397 + +cooking_potato_add_cheese=982398 +cooking_potato_add_cheese_input_0=982399 +cooking_potato_add_cheese_input_1=982400 +cooking_potato_add_cheese_outcome_success=982401 + +cooking_potato_add_egg=982402 +cooking_potato_add_egg_input_0=982403 +cooking_potato_add_egg_input_1=982404 +cooking_potato_add_egg_outcome_success=982405 +cooking_potato_add_egg_outcome_always_0=982406 + +cooking_potato_add_mushroom_onion=982407 +cooking_potato_add_mushroom_onion_input_0=982408 +cooking_potato_add_mushroom_onion_input_1=982409 +cooking_potato_add_mushroom_onion_outcome_success=982410 +cooking_potato_add_mushroom_onion_outcome_always_0=982411 + +cooking_potato_add_tuna_corn=982412 +cooking_potato_add_tuna_corn_input_0=982413 +cooking_potato_add_tuna_corn_input_1=982414 +cooking_potato_add_tuna_corn_outcome_success=982415 +cooking_potato_add_tuna_corn_outcome_always_0=982416 + +# --- Misc foods --- +cooking_sweetcorn=982417 +cooking_sweetcorn_input_0=982418 +cooking_sweetcorn_outcome_success=982419 +cooking_sweetcorn_outcome_fail=982420 + +cooking_scrambled_egg_mix=982421 +cooking_scrambled_egg_cook=982422 +cooking_scrambled_egg_mix_input_0=982423 +cooking_scrambled_egg_mix_input_1=982424 +cooking_scrambled_egg_cook_input_0=982425 +cooking_scrambled_egg_mix_outcome_success=982426 +cooking_scrambled_egg_cook_outcome_success=982427 +cooking_scrambled_egg_cook_outcome_fail=982428 + +cooking_fried_onions=982429 +cooking_fried_onions_input_0=982430 +cooking_fried_onions_outcome_success=982431 +cooking_fried_onions_outcome_fail=982432 + +cooking_fried_mushrooms=982433 +cooking_fried_mushrooms_input_0=982434 +cooking_fried_mushrooms_outcome_success=982435 +cooking_fried_mushrooms_outcome_fail=982436 + +cooking_mushroom_onion_combine=982437 +cooking_mushroom_onion_combine_input_0=982438 +cooking_mushroom_onion_combine_input_1=982439 +cooking_mushroom_onion_combine_outcome_success=982440 +cooking_mushroom_onion_combine_outcome_always_0=982441 + +cooking_tuna_corn_combine=982442 +cooking_tuna_corn_combine_input_0=982443 +cooking_tuna_corn_combine_input_1=982444 +cooking_tuna_corn_combine_input_2=982445 +cooking_tuna_corn_combine_outcome_success=982446 + +cooking_chocolate_cake=982447 +cooking_chocolate_cake_input_0=982448 +cooking_chocolate_cake_input_1=982449 +cooking_chocolate_cake_outcome_success=982450 + +# --- Wine --- +cooking_wine=982451 +cooking_wine_input_0=982452 +cooking_wine_input_1=982453 +cooking_wine_outcome_success=982454 +cooking_wine_outcome_fail=982455 + +# --- Snails --- +cooking_thin_snail=982456 +cooking_thin_snail_input_0=982457 +cooking_thin_snail_outcome_success=982458 +cooking_thin_snail_outcome_fail=982459 + +cooking_lean_snail=982460 +cooking_lean_snail_input_0=982461 +cooking_lean_snail_outcome_success=982462 +cooking_lean_snail_outcome_fail=982463 + +cooking_fat_snail=982464 +cooking_fat_snail_input_0=982465 +cooking_fat_snail_outcome_success=982466 +cooking_fat_snail_outcome_fail=982467 + +# --- Spit-Roast Rabbit --- +cooking_skewer_rabbit_meat=982468 +cooking_skewer_rabbit_meat_input_0=982469 +cooking_skewer_rabbit_meat_input_1=982470 +cooking_skewer_rabbit_meat_outcome_success=982471 + +cooking_roast_rabbit_meat=982472 +cooking_roast_rabbit_meat_input_0=982473 +cooking_roast_rabbit_meat_outcome_success=982474 +cooking_roast_rabbit_meat_outcome_fail=982475 + +# --- Hunter Meats (Pitfall Trapping) --- +cooking_larupia=982476 +cooking_larupia_input_0=982477 +cooking_larupia_outcome_success=982478 +cooking_larupia_outcome_fail=982479 + +cooking_graahk=982480 +cooking_graahk_input_0=982481 +cooking_graahk_outcome_success=982482 +cooking_graahk_outcome_fail=982483 + +cooking_kyatt=982484 +cooking_kyatt_input_0=982485 +cooking_kyatt_outcome_success=982486 +cooking_kyatt_outcome_fail=982487 + +cooking_pyre_fox=982488 +cooking_pyre_fox_input_0=982489 +cooking_pyre_fox_outcome_success=982490 +cooking_pyre_fox_outcome_fail=982491 + +cooking_sunlight_antelope=982492 +cooking_sunlight_antelope_input_0=982493 +cooking_sunlight_antelope_outcome_success=982494 +cooking_sunlight_antelope_outcome_fail=982495 + +cooking_moonlight_antelope=982496 +cooking_moonlight_antelope_input_0=982497 +cooking_moonlight_antelope_outcome_success=982498 +cooking_moonlight_antelope_outcome_fail=982499 diff --git a/docs/cooking/osrs_food_all.csv b/docs/cooking/osrs_food_all.csv new file mode 100644 index 00000000..2d21cda7 --- /dev/null +++ b/docs/cooking/osrs_food_all.csv @@ -0,0 +1,351 @@ +Name,Heals,Skills_Needed,Notes,GP_Per_Heal,Price,Members +Cup of tea,3,5,Boosts Attack by 2% + 2,28.33,85,1 +Bruised banana,0,,Taken from the spooky cauldron behind Zaff's Superior Staves. It was used in the 2022 and 2023 Halloween events.,N/A,N/A,0 +Tea flask,3 × 5,,Reusable item obtained via Creature CreationMust be filled with Cups of teaBoosts Attack by 3[confirmation needed],N/A,N/A,1 +Poison chalice,Random,,Has a chance to apply a variety of random effects which can be beneficial or harmfulHas a chance to either heal or severely harm the player,"75.93 – 1,063","1,063",1 +Caerula berries,2,,"Found in Caerula bushes, found north of the Twilight Temple",N/A,N/A,1 +Jangerberries,2,,,186.50,373,1 +Shrimps,3,1 1,Caught via small net fishing.,9.00,27,0 +Anchovies,1,,,58.00,58,0 +Sardine,4,1 5,Caught via bait fishing.,5.50,22,0 +Salmon,9,25 30,Caught via fly fishing.,6.89,62,0 +Trout,7,15 20,Caught via fly fishing.,5.00,35,0 +Giant carp,6,,,N/A,N/A,1 +Cod,7,18 23,Caught via big net fishing.,4.57,32,1 +Herring,5,5 10,Caught via bait fishing.,14.00,70,0 +Pike,8,20 25,Caught via bait fishing.,23.25,186,0 +Mackerel,6,10 16,Caught via big net fishing.,5.33,32,1 +Tuna,10,30 35,Caught with a harpoon.,8.90,89,0 +Bass,13,43 46,Caught via big net fishing.,7.08,92,1 +Swordfish,14,45 50,Caught with a harpoon.,14.50,203,0 +Lobster,12,40 40,Caught with a lobster pot.,12.50,150,0 +Shark,20,80 76,Caught with a harpoon or traded for with minnows.,41.30,826,1 +Manta ray,22,91 81,"Can be caught with fishing from Fishing Trawler minigame, drift net fishing, Tempoross Drops from Zulrah, Vorkath, and the Tombs of Amascut.",78.05,"1,717",1 +Sea turtle,21,82 79,"Can only be obtained from the Fishing Trawler minigame, Tempoross, and drift net fishing.",53.95,"1,133",1 +Edible seaweed,4,,,13.00,52,1 +Karamjan rum,5,,,6.00,30,0 +Cup of tea (nettle),3,20,restores 5% Run Energy,N/A,N/A,1 +Tenti pineapple,2 × 4,,Quest item that can act as a normal Pineapple,N/A,N/A,1 +Ugthanki meat,3,,Better used as an ingredient to make Ugthanki kebabs,334.00,"1,002",1 +Chopped tomato,2,,,31.50,63,1 +Chopped onion,1,,,110.00,110,1 +Onion & tomato,3,,,5.33,16,1 +Ugthanki kebab,19,58,"The player's character will say ""Yum!"" when eaten.",31.68,602,1 +Ugthanki kebab (bad),0,58,Can sometimes heal 9Can still be used to make a Super kebab,N/A,N/A,1 +Cake,4 × 3,40,"Baked by players, see Cooking skill.Heals 4 Hitpoints per bite (3 bites total).Can be stolen from East Ardougne, Keldagrim, Kourend Castle and Hosidius Farmers' Market.",22.25,267,0 +Chocolate cake,5 × 3,50,"Made by adding chocolate dust to cake.Heals 5 Hitpoints per bite (3 bites total).1/3 Slice can be thieved from bakery stalls, except the Sophanem one.",24.67,370,0 +Asgarnian ale,1,,,110.00,110,0 +Wizard's mind bomb,1,,,119.00,119,0 +Greenman's ale,1,,,235.00,235,1 +Dragon bitter,1,,,428.00,428,1 +Dwarven stout,1,,,114.00,114,0 +Grog,3,,,112.67,338,1 +Beer,1,,,85.00,85,0 +Potato,1,,,20.00,20,0 +Onion,1,,,15.00,15,0 +Pumpkin,14,,,866.07,"12,125",0 +Easter egg,14,,,551.00,"7,714",0 +Banana,2,,,23.50,47,0 +Cabbage,1,,,21.00,21,0 +Spinach roll,2,,,45.50,91,0 +Kebab,3 + 7%,,"Amount healed uses most likely resultCan heal 0 or up to 7 + 24%Rare chances to apply buffs, drains, or no effects at all",2.45 – 49,49,0 +Chocolate bar,3,,,13.00,39,0 +Chocolatey milk,4,,,N/A,N/A,1 +Tomato,2,,,9.50,19,0 +Cheese,2,,,26.00,52,0 +Half full wine jug,7,,,"2,196.14","15,373",0 +Jug of wine,11[3],35,"Upon consumption, Attack is temporarily decreased by 2 points and leaves a jug behind.",0.36,4,0 +Stew,11,25,"Once eaten, an empty bowl remains in the inventory.",5.45,60,0 +Damiana tea,4,36,Also heals for 6 run energy.,N/A,N/A,1 +Damiana tea (milky),4,N/A,Also heals for 6 run energy.,N/A,N/A,1 +Cup of tea (damiana),4,36,Also heals for 6 run energy.,N/A,N/A,1 +Cup of tea (milky damiana),4,N/A,Also heals for 6 run energy.,N/A,N/A,1 +Curry,19,60,"Commonly used for fighting the Chaos Elemental. Once eaten, an empty bowl remains in the inventory.",18.89,359,1 +Vodka,5,,,21.20,106,1 +Whisky,5,,,26.80,134,1 +Gin,5,,,34.40,172,1 +Brandy,5,,,25.60,128,1 +Premade blurb' sp.,7,,,92.86,650,1 +Premade choc s'dy,5,,,11.60,58,1 +Premade dr' dragon,5,,,6.40,32,1 +Premade fr' blast,9,,,3.89,35,1 +Premade p' punch,9,8,Purchased at Tree gnome stronghold,3.11,28,1 +Premade sgg,5,,,5.40,27,1 +Premade wiz blz'd,5,,,5.20,26,1 +Pineapple punch,9,8,See Gnome cooking for more information.,11.33,102,1 +Wizard blizzard,5,,,21.20,106,1 +Blurberry special,7,,,589.86,"4,129",1 +Choc saturday,5,,,13.80,69,1 +Short green guy,5,,,19.60,98,1 +Fruit blast,9,,,270.00,"2,430",1 +Drunk dragon,5,,,14.80,74,1 +Lemon,2,,,37.00,74,1 +Lemon chunks,2,,,58.00,116,1 +Lemon slices,2,,,67.00,134,1 +Orange,2,,,85.50,171,1 +Orange chunks,2,,,46.50,93,1 +Orange slices,2,,,160.50,321,1 +Pineapple,2 × 4,,,22.75,182,1 +Pineapple chunks,2,,,82.00,164,1 +Pineapple ring,2,,,41.00,82,1 +Lime,2,,,36.50,73,1 +Lime chunks,2,,,93.00,186,1 +Lime slices,2,,,89.50,179,1 +Dwellberries,2,,,22.00,44,1 +Equa leaves,1,,,370.00,370,1 +Pot of cream,1,,,10.00,10,1 +Cooked chicken,3,1,Raw chicken is obtained by killing chickens.,25.00,75,0 +Cooked meat,3,1,Raw meat can be obtained by killing cows or giant rats.,26.33,79,0 +Lava eel,11,,,N/A,N/A,1 +Toad's legs,3,,,147.00,441,1 +King worm,2,,,261.50,523,1 +Chocolate bomb,15,42,Made using Gnome cooking,48.60,729,1 +Tangled toad's legs,15,40,Made using Gnome cooking,55.47,832,1 +Worm hole,12,30,Made using Gnome cooking,312.17,"3,746",1 +Veg ball,12,36,Made using Gnome cooking,174.25,"2,091",1 +Worm crunchies,8,14,Made using Gnome cooking,468.88,"3,751",1 +Chocchip crunchies,7,16,Made using Gnome cooking,277.00,"1,939",1 +Spicy crunchies,7,12,Made using Gnome cooking,305.43,"2,138",1 +Toad crunchies,8,10,Made using Gnome cooking,598.13,"4,785",1 +Premade w'm batta,11,N/A,Purchased from Gnome waiter in the Grand tree for 120,4.00,44,1 +Premade t'd batta,11,N/A,Purchased from Gnome waiter in the Grand tree for 120,3.91,43,1 +Premade c+t batta,11,N/A,Purchased from Gnome waiter in the Grand tree for 120,4.36,48,1 +Premade fr't batta,11,N/A,Purchased from Gnome waiter in the Grand tree for 120,3.91,43,1 +Premade veg batta,11,N/A,Purchased from Gnome waiter in the Grand tree for 120,3.91,43,1 +Premade choc bomb,15,N/A,Purchased from Gnome waiter in the Grand tree for 160,8.20,123,1 +Premade ttl,15,N/A,Purchased from Gnome waiter in the Grand tree for 160,6.13,92,1 +Premade worm hole,12,N/A,Purchased from Gnome waiter in the Grand tree for 150,4.33,52,1 +Premade veg ball,12,N/A,Purchased from Gnome waiter in the Grand tree for 120,4.17,50,1 +Premade w'm crun',8,N/A,Purchased from Gnome waiter in the Grand tree for 85,5.88,47,1 +Premade ch' crunch,8,N/A,Purchased from Gnome waiter in the Grand tree for 85,6.88,55,1 +Premade s'y crunch,7,N/A,Purchased from Gnome waiter in the Grand tree for 85,6.71,47,1 +Premade t'd crunch,8,N/A,Purchased from Gnome waiter in the Grand tree for 85,125.88,"1,007",1 +Worm batta,11,27,"Made using Gnome cooking, Dropped by Tortoise",212.09,"2,333",1 +Toad batta,11,26,Made using Gnome cooking,130.55,"1,436",1 +Cheese+tom batta,11,29,Made using Gnome cooking,183.00,"2,013",1 +Fruit batta,11,25,Made using Gnome cooking,340.18,"3,742",1 +Vegetable batta,11,28,Made using Gnome cooking,294.18,"3,236",1 +Plain pizza,7 × 2,35,"Baked by players, see cooking skill.Heals 7 Hitpoints per bite (2 bites total).",26.93,377,0 +Meat pizza,8 × 2,45,Made by using cooked meat or chicken on a plain pizza.Heals 8 Hitpoints per bite (2 bites total).,17.50,280,0 +Anchovy pizza,9 × 2,55,Made by using anchovies on a plain pizza.Heals 9 Hitpoints per bite (2 bites total).,25.56,460,0 +Pineapple pizza,11 × 2,65,Made by using pineapple chunks or rings on a plain pizza.Heals 11 Hitpoints per bite (2 bites total).,28.32,623,1 +Bread,5,1,"Baked by players, see Cooking skill.",36.20,181,0 +Apple pie,7 × 2,30,Made by using a cooking apple on a pie shell to make an uncooked apple pie and cooking it.Heals 7 Hitpoints per bite (2 bites total).,7.86,110,0 +Chichilihui rosé,16,,"Purchased from Moonrise Wines for 100 gp. Upon consumption, Attack is temporarily decreased by 5, Farming by 1, and Herblore is temporarily boosted by 1.",18.13,290,1 +Ixcoztic white,16,,"Purchased from Moonrise Wines for 100 gp. Upon consumption, Attack is temporarily decreased by 5, Herblore by 1, and Farming is temporarily boosted by 1.",10.69,171,1 +Redberry pie,5 × 2,10,"Baked by players, see Cooking skill.Heals 5 Hitpoints per bite (2 bites total).",20.20,202,0 +Meat pie,6 × 2,20,"Baked by players, see Cooking skill.Heals 6 Hitpoints per bite (2 bites total).",3.75,45,0 +Cooked oomlie wrap,14,,,416.86,"5,836",1 +Cooked chompy,11,,,6.55,72,1 +Moonlight mead,4,,,28.25,113,1 +Cooked karambwan,18,30 65,"A karambwan vessel and completion of Tai Bwo Wannai Trio required to fish and cook it properly.Can be eaten in the same tick as another piece of food, allowing for combo eating.Shorter attack delay than most other food (2 ticks instead of 4).",28.33,510,1 +Sliced banana,2,,,20.50,41,0 +Cooked rabbit,5,,,46.20,231,1 +Thin snail meat,5 – 7,,,64.57 – 90.40,452,1 +Lean snail meat,5 – 8,,,441.13 – 705.80,"3,529",1 +Fat snail meat,7 – 9,,,281.89 – 362.43,"2,537",1 +Cooked slimy eel,6 – 10,,,2.70 – 4.50,27,1 +Keg of beer,15,,,47.07,706,1 +Beer tankard,4,,,98.75,395,1 +Monkey nuts,4,,,40.50,162,1 +Monkey bar,5,,,136.80,684,1 +Banana stew,11,N/A,"Once eaten, an empty bowl remains in the inventory.",3.36,37,1 +Nettle-water,1,,,N/A,N/A,1 +Nettle tea,3,,,N/A,N/A,1 +Guthix rest,5 × 4,18,Requires partial completion of One Small Favour to make and consume.Can boost 5 Hitpoints above max. Restores 5% run energy and reduces poison/venom damage by 1.It is not considered food as it is a potion made with the Herblore skill. Will not increase the delay until your next attack.,145.75,"2,915",1 +White pearl,2,,,N/A,N/A,1 +Giant frog legs,6,,,771.33,"4,628",1 +Purple sweets,1 – 3,N/A,The only stackable food item in the game.,"2,143.00 – 6,429.00","6,429",1 +Super kebab,3 + 7%,N/A,Amount healed is most likely resultUncommon chance to heal 0Rare chance to drain a random skill by -3,168.90 – 563.00,"1,689",1 +Bandit's brew,1,,,"2,088.00","2,088",1 +Cave eel,8 – 12,,,7.75 – 11.63,93,1 +Frog spawn,3 – 6,,,N/A,N/A,1 +Strawberry,1 – 6,,1 + 6% of maximum Hitpoints,18.50 – 111.00,111,1 +Basket of strawberries,(1 - 6) × 5,31,"Contains 5 strawberries, each of which heals 1-6 Hitpoints (1 + 6% of hp).Strawberry spawn at Shayziens' Wall in Great Kourend for no requirements.",13.83 – 83.00,415,1 +Asgarnian ale(m),2,,,"11,790.00","23,580",1 +Mature wmb,2,,,"13,832.00","27,664",1 +Greenman's ale(m),2,,,"29,816.50","59,633",1 +Dragon bitter(m),2,,,357.50,715,1 +Dwarven stout(m),2,,,"8,056.50","16,113",1 +Moonlight mead(m),6,,,915.67,"5,494",1 +Axeman's folly,1,,,248.00,248,1 +Axeman's folly(m),2,,,"25,145.50","50,291",1 +Chef's delight,1,,,284.00,284,1 +Chef's delight(m),2,,,"21,012.50","42,025",1 +Slayer's respite,1,,,40.00,40,1 +Slayer's respite(m),2,,,"19,594.00","39,188",1 +Cider,1,,,121.00,121,1 +Mature cider,2,,,"87,350.00","174,700",1 +Greenmans ale(4),1 × 4,,,"5,932.75","23,731",1 +Mind bomb(4),1 × 4,,,"8,725.00","34,900",1 +Dwarven stout(m4),2 × 4,,,"28,215.00","225,720",1 +Asgarnian ale(m4),2 × 4,,,691.50,"5,532",1 +Greenmans ale(m4),2 × 4,,,"2,247.34","193,271",1 +Mind bomb(m4),2 × 4,,,"3,825.38","30,603",1 +Dragon bitter(m4),2 × 4,,,"2,325.13","18,601",1 +Moonlight mead(m4),6 × 4,,,"5,730.00","137,520",1 +Axeman's folly(m4),2 × 4,,,"27,431.50","219,452",1 +Chef's delight(m4),2 × 4,,,"25,345.13","202,761",1 +Slayer's respite(m4),2 × 4,,,"24,659.63","197,277",1 +Cider(m4),2 × 4,,,"10,966.75","87,734",1 +Papaya fruit,8,,,206.25,"1,650",1 +Watermelon,(1 - 5) × 3,47,Must be sliced with a knife before eating.Heals for Hitpoints*120+1 per slice.,1.27 – 6.33,19,1 +Watermelon slice,1 – 5,47,Heals for Hitpoints*120+1 per slice.,15.80 – 79.00,79,1 +Cooked sweetcorn,1 – 10,,"1+10% of total hitpoints, rounded down",12.90 – 129.00,129,1 +Spider on stick,7 – 10,,,33.50 – 47.86,335,1 +Spider on shaft,7 – 10,,,27.90 – 39.86,279,1 +Gout tuber,12,,,"72,961.08","875,533",1 +White tree fruit,3,,,N/A,N/A,1 +Saradomin brew,(3 - 16) × 4,81,"Drunk in 4 sips, each healing for Hitpoints*15100+2 and can boost health above the maximum value.Due to it lowering offensive stats, it is typically used with super restore potions.It is not considered food as it is a potion made with the Herblore skill.Will not increase the delay before your next attack.",128.45 – 685.08,"8,221",1 +Armadyl brew,(3 - 11) × 4,89,"Drunk in 4 sips, each healing for Hitpoints*10100+2 and can boost health above the maximum value.Due to it lowering offensive stats, it is typically used with super restore potions.It is not considered food as it is a potion made with the Herblore skill.Will not increase the delay before your next attack.",62.70 – 229.92,"2,759",1 +Baked potato,4,,,42.50,170,1 +Potato with butter,14,39,Made by using pat of butter on a baked potato.,22.29,312,1 +Potato with cheese,16,47,Made by using cheese on a potato with butter.,15.50,248,1 +Choc-ice,7,N/A,Can be bought from Rokuh in Nardah. Negates damage from desert heat.,21.29,149,1 +Peach,8,,,N/A,N/A,1 +Baguette,6,,,N/A,N/A,0 +Triangle sandwich,6,,,92.50,555,0 +Roll,6,,,N/A,N/A,0 +Square sandwich,6,,,N/A,N/A,0 +Chilli potato,14,,,14.57,204,1 +Egg potato,16,,,15.94,255,1 +Mushroom potato,20,64,Made by using mushroom & onion on a potato with butter.,42.10,842,1 +Tuna potato,22,68,Made by using tuna and corn on a potato with butter.,80.05,"1,761",1 +Chilli con carne,5,,,8.20,41,1 +Egg and tomato,8,,,22.25,178,1 +Mushroom & onion,11,,,29.27,322,1 +Tuna and corn,13,,,76.85,999,1 +Minced meat,13,,,23.08,300,1 +Spicy sauce,2,,,380.50,761,1 +Scrambled egg,5,,,7.20,36,1 +Fried mushrooms,5,,,13.80,69,1 +Fried onions,5,,,5.80,29,1 +Chopped tuna,10,,,27.40,274,1 +Braindeath 'rum',14,,,2.14,30,1 +Garden pie,6 × 2,34,Made using CookingTemporary Farming boost of +3 Both slices are a 1-tick eat delay.,7.33,88,1 +Fish pie,6 × 2,47,Made using CookingTemporary Fishing boost of +3 Both slices are a 1-tick eat delay.,18.33,220,1 +Admiral pie,8 × 2,70,Heals 8 Hitpoints per bite (2 bites total).Boosts Fishing by 5.,5.94,95,1 +Wild pie,11 × 2,85,"Heals 11 Hitpoints per bite (2 bites total).Boosts Slayer by 5, and Ranged by 4.",28.91,636,1 +Summer pie,11 × 2,95,"Heals 11 Hitpoints per bite (2 bites total).Boosts Agility by 5, and restores 10% run energy.",36.91,812,1 +Roast rabbit,7,,,13.14,92,1 +Spicy stew,0,,,N/A,N/A,1 +Cooked giant crab meat,2 × 5,21,Made using CookingAll five bites have 2-tick eat/attack delay.,134.70,"1,347",1 +Cooked fishcake,11,,,N/A,N/A,1 +Cooked jubbly,15,,,67.53,"1,013",1 +Red banana,5,,,N/A,N/A,1 +Tchiki monkey nuts,5,,,N/A,N/A,1 +Sliced red banana,5,,,N/A,N/A,1 +Stuffed snake,20,,,N/A,N/A,1 +Bottle of wine,14,,,46.93,657,1 +Field ration,10,,,N/A,N/A,1 +Fresh monkfish,1,,,N/A,N/A,1 +Monkfish,16,62 62,Caught via small net fishing in Piscatoris Fishing Colony after the completion of the Swan Song.,17.94,287,1 +Locust meat,3,,,18.33,55,1 +Roast bird meat,6,,,2.67,16,1 +Roast beast meat,8,21,An iron spit must be used along with a fire to cook it.,2.88,23,1 +Spicy tomato,2,,,305.00,610,1 +Spicy minced meat,3,,,208.67,626,1 +Rainbow fish,11,35 38,Caught via fly fishing by using stripy feathers.,9.09,100,1 +Spring sq'irkjuice,0[confirmation needed],25,Created via Sorceress's Garden+1 Thieving and restores 10% Run Energy,N/A,N/A,1 +Summer sq'irkjuice,0[confirmation needed],65,Created via Sorceress's Garden+3 Thieving and restores 20% Run Energy,N/A,N/A,1 +Autumn sq'irkjuice,0[confirmation needed],45,Created via Sorceress's Garden+2 Thieving and restores 15% Run Energy,N/A,N/A,1 +Winter sq'irkjuice,0[confirmation needed],1,Created via Sorceress's GardenRestores 5% Run Energy,N/A,N/A,1 +Green gloop soup,2,,,N/A,N/A,1 +Frogspawn gumbo,2,,,N/A,N/A,1 +Frogburger,2,,,N/A,N/A,1 +Coated frogs' legs,2,,,N/A,N/A,1 +Bat shish,2,,,N/A,N/A,1 +Fingers,2,,,N/A,N/A,1 +Grubs à la mode,2,,,N/A,N/A,1 +Roast frog,2,,,N/A,N/A,1 +Mushrooms,2,,,N/A,N/A,1 +Fillets,2,,,N/A,N/A,1 +Loach,3,,,N/A,N/A,1 +Eel sushi,10,,,N/A,N/A,1 +Roe,3,,,32.67,98,1 +Caviar,5,,,12.40,62,1 +Attack mix(2),3 × 2,,,19.00,114,1 +Antipoison mix(2),3 × 2,,,36.00,216,1 +Relicym's mix(2),3 × 2,,,78.50,471,1 +Strength mix(2),3 × 2,,,10.00,60,1 +Combat mix(2),3 × 2,,,38.50,231,1 +Restore mix(2),3 × 2,,,15.67,94,1 +Energy mix(2),3 × 2,,,44.67,268,1 +Defence mix(2),6 × 2,,,7.17,86,1 +Agility mix(2),6 × 2,,,26.17,314,1 +Prayer mix(2),6 × 2,,,318.58,"3,823",1 +Superattack mix(2),6 × 2,,,20.92,251,1 +Anti-poison supermix(2),6 × 2,,,112.08,"1,345",1 +Fishing mix(2),6 × 2,,,9.08,109,1 +Super energy mix(2),6 × 2,,,166.50,"1,998",1 +Super str. mix(2),6 × 2,,,100.50,"1,206",1 +Magic essence mix(2),6 × 2,,,210.58,"2,527",1 +Super restore mix(2),6 × 2,,,198.92,"2,387",1 +Super def. mix(2),6 × 2,,,127.75,"1,533",1 +Antidote+ mix(2),6 × 2,,,120.92,"1,451",1 +Antifire mix(2),6 × 2,,,11.67,140,1 +Ranging mix(2),6 × 2,,,105.75,"1,269",1 +Magic mix(2),6 × 2,,,8.58,103,1 +Hunting mix(2),6 × 2,,,79.08,949,1 +Zamorak mix(2),6 × 2,,,39.25,471,1 +Dark crab,22,90 85,Caught by using a lobster pot with dark fishing bait.Can only be fished in the Wilderness and the Resource Area.,69.64,"1,532",1 +Extended antifire mix(2),6 × 2,,,17.50,210,1 +Stamina mix(2),6 × 2,,,310.83,"3,730",1 +Anglerfish,3 - 22,84 82,"Caught by using a fishing rod with sandworms.Healing dependent on the player's Hitpoints level (complex, see page)Can boost Hitpoints above maximum equal to the amount it heals.",90.27 – 662.00,"1,986",1 +Botanical pie,7 × 2,52,Made using CookingTemporary Herblore boost of +4 Both slices are a 1-tick eat delay.,52.93,741,1 +Pysk fish (0),5,,,N/A,N/A,1 +Suphi fish (1),8,,,N/A,N/A,1 +Leckish fish (2),11,,,N/A,N/A,1 +Brawk fish (3),14,,,N/A,N/A,1 +Mycil fish (4),17,,,N/A,N/A,1 +Roqed fish (5),20,,,N/A,N/A,1 +Kyren fish (6),23,,,N/A,N/A,1 +Guanic bat (0),5,,,N/A,N/A,1 +Prael bat (1),8,,,N/A,N/A,1 +Giral bat (2),11,,,N/A,N/A,1 +Phluxia bat (3),14,,,N/A,N/A,1 +Kryket bat (4),17,,,N/A,N/A,1 +Murng bat (5),20,,,N/A,N/A,1 +Psykk bat (6),23,,,N/A,N/A,1 +Xeric's aid (-),(1 - 7) × 4,,"Drunk in 4 sips, each healing for ⌊Hitpoints×7100⌋+1 and can boost health above the maximum value.Due to it lowering offensive stats, it is typically used with revitalisation potions or overloads.It can only be obtained and used within the Chambers of Xeric.Will not increase the delay before your next attack.",N/A,N/A,1 +Xeric's aid,(3 - 13) × 4,,"Drunk in 4 sips, each healing for ⌊Hitpoints×12100⌋+2 and can boost health above the maximum value.Due to it lowering offensive stats, it is typically used with revitalisation potions or overloads.It can only be obtained and used within the Chambers of Xeric.Will not increase the delay before your next attack.",N/A,N/A,1 +Xeric's aid (+),(6 - 19) × 4,,"Drunk in 4 sips, each healing for ⌊Hitpoints×15100⌋+5 and can boost health above the maximum value.Due to it lowering offensive stats, it is typically used with revitalisation potions or overloads.It can only be obtained and used within the Chambers of Xeric.Will not increase the delay before your next attack.",N/A,N/A,1 +Honey locust,20,,,N/A,N/A,1 +Ambrosia,100%,,"Completely restores Hitpoints, Prayer, and Run EnergyAlso applies the Antidote++ effect, overheals by 25% + 2 and boosts Prayer by 20% + 5",N/A,N/A,1 +Nectar,(4 - 17) × 4,,"Drunk in 4 sips, each healing for ⌊Hitpoints×15100⌋+3 and can boost health above the maximum value.Due to it lowering offensive stats, it is typically used with tears of elidinis or smelling salts.It can only be obtained and used within the Tombs of Amascut.Will not increase the delay before your next attack.",N/A,N/A,1 +Silk dressing (2),5 x 20,,"5 every 5 ticks, 20 timesAlso overheals by 5",N/A,N/A,1 +Mushroom pie,8 × 2,60,Made using CookingTemporary Crafting boost of +3 Both slices are a 1-tick eat delay.,508.13,"8,130",1 +Bloody bracer,2,,,606.50,"1,213",1 +Dragonfruit pie,10 × 2,73,Heals 10 hitpoints per bite (2 bites total).Boosts Fletching by 4.,21.25,425,1 +Dragonfruit,10,,,104.80,"1,048",1 +Paddlefish,20,,,N/A,N/A,1 +Elven dawn,1,,,319.00,319,1 +Cooked mystery meat,5,,,28.80,144,1 +Steak sandwich,6,,,N/A,N/A,0 +Corrupted paddlefish,16,,,N/A,N/A,1 +Crystal paddlefish,16,,,N/A,N/A,1 +Ancient mix(2),6 × 2,,,114.25,"1,371",1 +Kovac's grog,1,,,"51,108.00","51,108",1 +Cooked sunlight antelope,12 + 9,,"Heals 12 hitpoints immediately, then an additional 9 hitpoints after three seconds, for a total of 21 hitpoints. Raw sunlight antelopes cannot be cooked until 50 Hunters' Rumours have been completed.",33.67,707,1 +Cooked graahk,8 + 6,,"Heals 8 hitpoints immediately, then an additional 6 hitpoints after three seconds, for a total of 14 hitpoints. Raw graahks cannot be cooked until 25 Hunters' Rumours have been completed.",41.71,584,1 +Cooked dashing kebbit,13 + 10,,"Heals 13 hitpoints immediately, then an additional 10 hitpoints after three seconds, for a total of 23 hitpoints. Raw dashing kebbits cannot be cooked until 50 Hunters' Rumours have been completed.",47.00,"1,081",1 +Cooked moonlight antelope,14 + 12,,"Heals 14 hitpoints immediately, then an additional 12 hitpoints after three seconds, for a total of 26 hitpoints. Raw moonlight antelopes cannot be cooked until 50 Hunters' Rumours have been completed.",76.96,"2,001",1 +Cooked pyre fox,11 + 8,,"Heals 11 hitpoints immediately, then an additional 8 hitpoints after three seconds, for a total of 19 hitpoints. Raw pyre foxex cannot be cooked until 25 Hunters' Rumours have been completed.",19.89,378,1 +Cooked kyatt,9 + 8,,"Heals 9 hitpoints immediately, then an additional 8 hitpoints after three seconds, for a total of 17 hitpoints. Raw kyatts cannot be cooked until 25 Hunters' Rumours have been completed.",13.29,226,1 +Cooked barb-tailed kebbit,7 + 5,,"Heals 7 hitpoints immediately, then an additional 5 hitpoints after three seconds, for a total of 12 hitpoints.",47.08,565,1 +Cooked larupia,6 + 5,,"Heals 6 hitpoints immediately, then an additional 5 hitpoints after three seconds, for a total of 11 hitpoints.",32.82,361,1 +Cooked bream,Up to 33,,Cooked bream can only be used as food whilst fighting the Moons of Peril. The amount of Hitpoints restored appear to be 1/3rd of the player's Cooking or Fishing level whichever skill is the lowest.,N/A,N/A,1 +Cooked moss lizard,Up to 33,,"Cooked moss lizard can only be used as food whilst fighting the Moons of Peril. The amount of Hitpoints restored appear to be 1/3rd of the player's Cooking level or 1/2 of their Hunter level, whichever one heals less.",N/A,N/A,1 +Cooked wild kebbit,4 + 4,,"Heals 4 hitpoints immediately, then an additional 4 hitpoints after three seconds, for a total of 8 hitpoints.",79.75,638,1 +Varlamorian kebab,4 + 10%,,"Variable amount between 3-30 hitpoints. Number shown is average result. Has a chance to boost Attack, Defence, and Strength levels by 2. No chance of skill drain.",3.47 - 34.67,104,1 +Giant krill,17,69 69 ( 69 ),Caught with a fishing rod or via deep sea trawling.,33.06,562,1 +Haddock,18,73 73 ( 73 ),Caught with a fishing rod or via deep sea trawling. Can boost Hitpoints above maximum up to 109.,34.39,619,1 +Yellowfin,19,79 79 ( 79 ),Caught with a fishing rod or via deep sea trawling. Restores 20% Energy when eaten.,45.95,873,1 +Halibut,20,83 83 ( 83 ),"Caught with a fishing rod or via deep sea trawling. Can be eaten in the same tick as another piece of food, allowing for combo eating. Shorter attack delay than most other food (2 ticks instead of 4).",158.05,"3,161",1 +Bluefin,22,87 87 ( 86 ),Caught with a fishing rod or via deep sea trawling.,92.77,"2,041",1 +Marlin,24,93 93 ( 91 ),Caught with a fishing rod or via deep sea trawling.,276.17,"6,628",1 +Swordtip squid,15,56 56 47,Caught via lantern harpooning.,26.87,403,1 +Jumbo squid,17,56 56 47,Caught via lantern harpooning.,55.41,942,1 +Red crab meat,8,21 21,Caught via Crab trapping. Requires partial completion of Pandemonium to access.,20.00,160,1 +Blue crab meat,14,48 48,Caught via Crab trapping. Requires partial completion of Troubled Tortugans to access.,21.57,302,1 +Rainbow crab meat,19,77 77 64,Caught via Crab trapping.,33.95,645,1 diff --git a/docs/cooking/plan.md b/docs/cooking/plan.md new file mode 100644 index 00000000..78e01152 --- /dev/null +++ b/docs/cooking/plan.md @@ -0,0 +1,61 @@ +# Cooking implementation plan + +## Goal +Implement Cooking to match wikidata (levels, XP, burn logic, station rules, prep steps, and outcome items) across cache + content. + +## Current status (as of 2026-02-01) +- Heat-source cooking for core fish/meats is implemented with per-item chance profiles (added spider on stick/shaft, snail meats, fishcake, red/giant crab meat, hunter meats, antelope, jubbly, kebbits, and late-game fish). Sacred eel remains blocked by missing item key in cache. +- Bread and pitta bread baking implemented (range-only). +- Pies (redberry/meat/mud/apple/fish/botanical/mushroom/admiral/dragonfruit/wild/summer) baked on range with chance profiles. +- Stew and curry (fire/range) implemented. +- Pizzas (plain bake + topping steps) implemented. +- Chocolate cake topping step implemented. +- Wine (jug of wine, wine of zamorak) implemented with item-on-item success chances. +- Hot drinks (nettle tea), vegetable dishes, dairy, and ugthanki kebab prep implemented. +- Spit roasting and Camdozaal prep table are implemented (bird + beast + rabbit roast; guppy/cavefish/tetra/catfish prep). +- Multi-step prep + bake exists for garden pie and cake; other pies currently bake-only from uncooked items. +- Dough/base prep added for bread dough, pastry dough, pie shell, pizza base, incomplete pizza, and uncooked pizza. +- Burn success now uses chance profiles loaded from cache tables; stop-burn flags removed. +- Station/equipment modifiers implemented for Lumbridge range, Hosidius +5/+10, and cooking gauntlets. +- Cooking cape (no-burn) implemented; Hosidius +10 diary wiring still missing in runtime. +- Large recipe gaps remain per wikidata. + +## Plan (phased) +### Phase 1 — Recipe coverage (core) +1) Add missing Meat/Fish recipes from wikidata. + - Start with remaining meat/fish from meat-fish.md. +2) Add remaining prep steps (pitta, pie fillings, other bases) and topping flows (where missing). + +### Phase 2 — Burn rules + chance data +1) Parse burn thresholds from burn_level.wikisrc. +2) Integrate skill_chances.json curves per item where available. (in progress; automated extract in tmp/) +3) Add station modifiers and equipment modifiers: + - Lumbridge Castle range + - Hosidius range (+5%, +10% with diary) + - Cooking gauntlets + - Cooking cape (no burn) + +### Phase 3 — Special/side recipes +1) Hot drinks, vegetable dishes, dairy, kebabs. +2) Hunter foods: kebbits, snails, antelope, pyre fox. +3) Fishcake, giant crab meat, karambwan poison variants. + +### Phase 4 — QA and balancing +1) Update cooking test presets to cover new recipes. +2) Add sanity checks for missing RSCM keys. +3) Verify outcomes/messages and inventory space handling. + +## Primary files to change +- [cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/FishRecipes.kt](../../cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/FishRecipes.kt) +- [cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/MeatRecipes.kt](../../cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/MeatRecipes.kt) +- [cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/BakedGoodsRecipes.kt](../../cache/src/main/kotlin/org/alter/impl/skills/cooking/recipes/BakedGoodsRecipes.kt) +- [cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingRecipeRegistry.kt](../../cache/src/main/kotlin/org/alter/impl/skills/cooking/CookingRecipeRegistry.kt) +- [content/src/main/kotlin/org/alter/skills/cooking/events/CookingUtils.kt](../../content/src/main/kotlin/org/alter/skills/cooking/events/CookingUtils.kt) +- [content/src/main/kotlin/org/alter/skills/cooking/events/CookingEvents.kt](../../content/src/main/kotlin/org/alter/skills/cooking/events/CookingEvents.kt) + +## Reference data +- [wikidata/cooking.wikisrc](../../wikidata/cooking.wikisrc) +- [wikidata/burnt_food.wikisrc](../../wikidata/burnt_food.wikisrc) +- [wikidata/burn_level.wikisrc](../../wikidata/burn_level.wikisrc) +- [wikidata/skill_chances.json](../../wikidata/skill_chances.json) +- [wikidata/meat-fish.md](../../wikidata/meat-fish.md) diff --git a/docs/cooking/recipe-gap.md b/docs/cooking/recipe-gap.md new file mode 100644 index 00000000..c76b4dac --- /dev/null +++ b/docs/cooking/recipe-gap.md @@ -0,0 +1,102 @@ +# Cooking recipe gaps (from wikidata) + +## Meat / Fish (missing) +Source: [wikidata/meat-fish.md](../../wikidata/meat-fish.md) + +- [x] Sinew +- [x] Poison karambwan +- [x] Thin snail +- [x] Spider on stick +- [x] Spider on shaft +- [x] Roast rabbit +- [x] Lean snail +- [x] Cavefish (prep table) +- [x] Red crab meat +- [x] Cooked giant crab meat +- [x] Fat snail +- [x] Cooked wild kebbit +- [x] Cooked fishcake +- [x] Cooked larupia +- [x] Cooked barb-tailed kebbit +- [x] Tetra (prep table) +- [x] Cooked jubbly +- [x] Cooked graahk +- [x] Catfish (prep table) +- [x] Cooked kyatt +- [x] Lava eel +- [x] Swordtip squid +- [x] Cooked pyre fox +- [x] Cooked sunlight antelope +- [x] Giant krill +- [x] Jumbo squid +- [ ] Sacred eel +- [x] Haddock +- [x] Yellowfin +- [x] Cooked dashing kebbit +- [x] Halibut +- [x] Bluefin +- [x] Marlin +- [x] Cooked moonlight antelope + +## Bread +- [x] Bread (bake range-only) +- [x] Pitta bread (bake range-only) + +## Pies +- [x] Redberry pie +- [x] Meat pie +- [x] Mud pie +- [x] Apple pie +- [x] Fish pie +- [x] Botanical pie +- [x] Mushroom pie +- [x] Admiral pie +- [x] Dragonfruit pie +- [x] Wild pie +- [x] Summer pie + +## Stews +- [x] Stew +- [x] Curry + +## Pizzas +- [x] Plain pizza +- [x] Meat pizza +- [x] Anchovy pizza +- [x] Pineapple pizza + +## Cakes +- [x] Chocolate cake + +## Wine +- [x] Jug of wine +- [x] Wine of zamorak + +## Hot drinks +- [x] Cup of tea + +## Vegetable dishes +- [x] Baked potato +- [x] Spicy sauce +- [x] Chilli con carne +- [x] Scrambled egg +- [x] Egg and tomato +- [x] Cooked sweetcorn +- [x] Potato with butter +- [x] Chilli potato +- [x] Fried onions +- [x] Fried mushrooms +- [x] Potato with cheese +- [x] Egg potato +- [x] Mushroom & onion +- [x] Mushroom potato +- [x] Tuna and corn +- [x] Tuna potato + +## Dairy +- [x] Pot of cream +- [x] Pat of butter +- [x] Cheese + +## Kebabs +- [x] Kebab (and variants) diff --git a/game-api/src/main/kotlin/org/alter/api/ext/QueueTaskExt.kt b/game-api/src/main/kotlin/org/alter/api/ext/QueueTaskExt.kt index 2f5e4838..40ef27ad 100644 --- a/game-api/src/main/kotlin/org/alter/api/ext/QueueTaskExt.kt +++ b/game-api/src/main/kotlin/org/alter/api/ext/QueueTaskExt.kt @@ -290,22 +290,44 @@ suspend fun QueueTask.doubleItemMessageBox( * Prompts the player with skill menu for making things. * * @param items - * The possible [Item] products the menu presents as options. - * Note| max is 10 + * The possible product item ids the menu presents as options. + * This UI supports up to 18 items (components a..r). Extra items are ignored. * * @param title - * Title String to display atop the prompt. + * Title string to display atop the prompt. * * @param maxProducable * The possible number of products which could be made from the available input mats. * Note| defaults to full inventory as being possible * * @param logic - * The logic to be executed upon response using selected [Item] from prompt - * and quantity as indicated by response slot message. + * Callback invoked with `(selectedItemId, quantity)` after the player responds. + * + * ## ClientScript contract + * This function is a server-side wrapper around the clientscript `skillmulti_setup`. + * The reference dump for the script (signature + behaviour) is: + * https://github.com/Joshua-F/osrs-dumps/blob/ef7ba91167f84b05792373056ad4c9d374041394/script/%5Bclientscript%2Cskillmulti_setup%5D.cs2 + * + * The script expects arguments in this shape: + * - `int0`: mode/type (we pass `0`) + * - `string0`: payload formatted as `Title|Name1|Name2|...` + * - `int1`: max producible (used for quantity button setup/clamping) + * - `obj2..obj19`: up to 18 item ids; unused obj slots should be sent as `-1` (treated as null obj) + * - `int20`: suggested quantity + * + * If the argument count/order is wrong, the interface can open but appear blank. + * + * ## Ordering / race avoidance + * The interface must be opened client-side before `skillmulti_setup` runs. Since dialog open events + * are posted through an async event bus, we open the interface synchronously (`postAndWait`) to + * avoid races where the script runs before the modal exists. + * + * ## Selection mapping + * Mapping from the incoming resume packet (`ResumePauseButton.componentId`) to an item index is + * revision-dependent. The current implementation assumes item components start at child id `15`. * * @return - * The id of the option chosen. The id can range from [1] inclusive to [9] inclusive. + * The selected item id and quantity are delivered to [logic]. */ suspend fun QueueTask.produceItemBox( player: Player, @@ -344,6 +366,7 @@ suspend fun QueueTask.produceItemBox( player.runClientScript(CommonClientScripts.CHATBOX_RESET_BACKGROUND) player.sendTempVarbit("varbits.chatmodal_unclamp", 1) + EventManager.postAndWait(DialogSkillMulti(player)) player.runClientScript( diff --git a/game-server/src/test/kotlin/org/alter/game/model/skill/CookingRecipeTests.kt b/game-server/src/test/kotlin/org/alter/game/model/skill/CookingRecipeTests.kt new file mode 100644 index 00000000..46fdddba --- /dev/null +++ b/game-server/src/test/kotlin/org/alter/game/model/skill/CookingRecipeTests.kt @@ -0,0 +1,31 @@ +package org.alter.game.model.skill + +import kotlin.test.Test +import kotlin.test.assertTrue +import org.alter.impl.skills.cooking.CookingConstants +import org.alter.impl.skills.cooking.CookingRecipeRegistry + +class CookingRecipeTests { + + @Test + fun `heat-source recipes have chance profiles`() { + val actions = CookingRecipeRegistry.allRecipes + .filter { it.trigger == CookingConstants.Trigger.HEAT_SOURCE } + + actions.forEach { action -> + val stationMask = action.stationMask + if ((stationMask and CookingConstants.STATION_FIRE) != 0) { + assertTrue( + action.chances.any { it.low > 0 && it.high > 0 && (it.stationMask and CookingConstants.STATION_FIRE) != 0 }, + "Missing fire chance profile for action ${action.rowId}" + ) + } + if ((stationMask and CookingConstants.STATION_RANGE) != 0) { + assertTrue( + action.chances.any { it.low > 0 && it.high > 0 && (it.stationMask and CookingConstants.STATION_RANGE) != 0 }, + "Missing range chance profile for action ${action.rowId}" + ) + } + } + } +} diff --git a/util/bin/main/gg/rsmod/util/AabbUtil.kt b/util/bin/main/gg/rsmod/util/AabbUtil.kt new file mode 100644 index 00000000..2c698ae0 --- /dev/null +++ b/util/bin/main/gg/rsmod/util/AabbUtil.kt @@ -0,0 +1,124 @@ +package gg.rsmod.util + +/** + * Utility methods for axis-aligned bounding boxes. + * + * @author Tom + */ +object AabbUtil { + data class Box(val x: Int, val z: Int, val width: Int, val length: Int) { + val x1: Int get() = x + + val x2: Int get() = x + width + + val z1: Int get() = z + + val z2: Int get() = z + length + } + + /** + * Checks to see if two AABB are bordering, but not overlapping. + */ + fun areBordering( + x1: Int, + y1: Int, + width1: Int, + length1: Int, + x2: Int, + y2: Int, + width2: Int, + length2: Int, + ): Boolean { + val a = Box(x1, y1, width1 - 1, length1 - 1) + val b = Box(x2, y2, width2 - 1, length2 - 1) + + if (b.x1 in a.x1..a.x2 && b.z1 in a.z1..a.z2 || b.x2 in a.x1..a.x2 && b.z2 in a.z1..a.z2) { + return false + } + + if (b.x1 > a.x2 + 1) { + return false + } + + if (b.x2 < a.x1 - 1) { + return false + } + + if (b.z1 > a.z2 + 1) { + return false + } + + if (b.z2 < a.z1 - 1) { + return false + } + return true + } + + fun areDiagonal( + x1: Int, + y1: Int, + width1: Int, + length1: Int, + x2: Int, + y2: Int, + width2: Int, + length2: Int, + ): Boolean { + val a = Box(x1, y1, width1 - 1, length1 - 1) + val b = Box(x2, y2, width2 - 1, length2 - 1) + + /** + * South-west diagonal tile. + */ + if (a.x1 - 1 == b.x2 && a.z1 - 1 == b.z2) { + return true + } + + /** + * South-east diagonal tile. + */ + if (a.x2 + 1 == b.x2 && a.z1 - 1 == b.z2) { + return true + } + + /** + * North-west diagonal tile. + */ + if (a.x1 - 1 == b.x2 && a.z2 + 1 == b.z2) { + return true + } + + /** + * North-east diagonal tile. + */ + if (a.x2 + 1 == b.x2 && a.z2 + 1 == b.z2) { + return true + } + + return false + } + + fun areOverlapping( + x1: Int, + y1: Int, + width1: Int, + length1: Int, + x2: Int, + y2: Int, + width2: Int, + length2: Int, + ): Boolean { + val a = Box(x1, y1, width1 - 1, length1 - 1) + val b = Box(x2, y2, width2 - 1, length2 - 1) + + if (a.x1 > b.x2 || b.x1 > a.x2) { + return false + } + + if (a.z1 > b.z2 || b.z1 > a.z2) { + return false + } + + return true + } +} diff --git a/util/bin/main/gg/rsmod/util/BitManipulation.kt b/util/bin/main/gg/rsmod/util/BitManipulation.kt new file mode 100644 index 00000000..74a98a8b --- /dev/null +++ b/util/bin/main/gg/rsmod/util/BitManipulation.kt @@ -0,0 +1,32 @@ +package gg.rsmod.util + +/** + * @author Tom + */ +object BitManipulation { + /** + * Extracts the value from [packed] from the bits found in [startBit] to [endBit]. + */ + fun getBit( + packed: Int, + startBit: Int, + endBit: Int, + ): Int { + val position = DataConstants.BIT_SIZES[endBit - startBit] + return ((packed shr startBit) and position) + } + + /** + * Calculates the new [packed] value after editing the value in between the + * [startBit] and [endBit]. + */ + fun setBit( + packed: Int, + startBit: Int, + endBit: Int, + value: Int, + ): Int { + val maxValue = DataConstants.BIT_SIZES[endBit - startBit] shl startBit + return (packed and maxValue.inv()) or ((value shl startBit) and maxValue) + } +} diff --git a/util/bin/main/gg/rsmod/util/DataConstants.kt b/util/bin/main/gg/rsmod/util/DataConstants.kt new file mode 100644 index 00000000..905e9707 --- /dev/null +++ b/util/bin/main/gg/rsmod/util/DataConstants.kt @@ -0,0 +1,28 @@ +package gg.rsmod.util + +/** + * A class holding data-related constants. + * + * @author Graham + * @author Tom + */ +object DataConstants { + /** + * An array of bit masks. The element `n` is equal to `2n - 1`. + */ + val BIT_MASK = + IntArray(32).apply { + for (i in indices) { + set(i, (1 shl i) - 1) + } + } + + val BIT_SIZES = + IntArray(32).apply { + var size = 2 + for (i in indices) { + set(i, size - 1) + size += size + } + } +} diff --git a/util/bin/main/gg/rsmod/util/ExtensionFunctions.kt b/util/bin/main/gg/rsmod/util/ExtensionFunctions.kt new file mode 100644 index 00000000..7057065b --- /dev/null +++ b/util/bin/main/gg/rsmod/util/ExtensionFunctions.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package gg.rsmod.util + +/** + * Extension function that creates an instance of [ToStringHelper]. + * + * This is helpful for implementing [Object.toString]. Specification by example: + * + * toStringHelper().toString(); // Returns "ClassName{}" + * + * toStringHelper().add("x", 1).toString(); // Returns "ClassName{x=1}" + * + * + * toStringHelper().add("x", 1).add("y", "foo").toString(); // Returns "ClassName{x=1, y=foo}" + * + */ +fun Any.toStringHelper(): ToStringHelper { + return ToStringHelper(javaClass.simpleName) +} + +/** + * Support class for [toStringHelper]. + * + * Use [toStringHelper()] to create an instance. Do not instantiate this directly. + * + * @author Jason Lee + * @since 18.0 (since 2.0 as `Objects.ToStringHelper`). + * + * Changes: 5/27/2024 converted com.google.common.base.MoreObjects.ToStringHelper to kotlin + * Streamlined functionality and removed excluding nulls. + * Original file from commit c4b883de9679dae7da831e49dd9adaca71cc1991 + */ +class ToStringHelper constructor(private val className: String) { + private val holderHead = ValueHolder() + private var holderTail: ValueHolder? = holderHead + + /** + * Adds a name/value pair to the formatted output in `name=value` format. If `value` + * is `null`, the string `"null"` is used + */ + fun add( + name: String, + value: Any?, + ): ToStringHelper { + val valueHolder = ValueHolder() + holderTail!!.next = valueHolder + holderTail = holderTail!!.next + valueHolder.value = value + valueHolder.name = name + return this + } + + /** + * Returns a string in the format specified by [toStringHelper.toStringHelper]. + * + * + * After calling this method, you can keep adding more properties to later call toString() + * again and get a more complete representation of the same object; but properties cannot be + * removed, so this only allows limited reuse of the helper instance. The helper allows + * duplication of properties (multiple name/value pairs with the same name can be added). + */ + override fun toString(): String { + // create a copy to keep it consistent in case value changes + var nextSeparator = "" + val builder = StringBuilder(32).append(className).append('{') + var valueHolder = holderHead.next + while (valueHolder != null) { + val value = valueHolder.value + builder.append(nextSeparator) + nextSeparator = ", " + if (valueHolder.name != null) { + builder.append(valueHolder.name).append('=') + } + if (value != null && value.javaClass.isArray) { + val arrayString = arrayOf(value).contentDeepToString() + builder.append(arrayString, 1, arrayString.length - 1) + } else { + builder.append(value) + } + valueHolder = valueHolder.next + } + return builder.append('}').toString() + } + + // Holder object for values that might be null and/or empty. + private class ValueHolder { + var name: String? = null + var value: Any? = null + var next: ValueHolder? = null + } +} diff --git a/util/bin/main/gg/rsmod/util/Misc.kt b/util/bin/main/gg/rsmod/util/Misc.kt new file mode 100644 index 00000000..a3da87b6 --- /dev/null +++ b/util/bin/main/gg/rsmod/util/Misc.kt @@ -0,0 +1,41 @@ +package gg.rsmod.util + +/** + * @author Tom + */ +object Misc { + fun IntRange.toArray(): Array { + return toList().toTypedArray() + } + + fun getIndefiniteArticle(word: String): String { + val first = word.lowercase().first() + val vowel = first == 'a' || first == 'e' || first == 'i' || first == 'o' || first == 'u' + val numeric = word.first().equals(Regex(".*[0-9].*")) + val special = + listOf( + "bolts", + "arrows", + "coins", + "vambraces", + "chaps", + "grapes", + "silk", + "bread", + "grey wolf fur", + "spice", + ).filter { it in word } + val some = special.isNotEmpty() + return ( + if (numeric) { + "" + } else if (vowel) { + "an" + } else if (some) { + "some" + } else { + "a" + } + ) + " " + word + } +} diff --git a/util/bin/main/gg/rsmod/util/Namer.kt b/util/bin/main/gg/rsmod/util/Namer.kt new file mode 100644 index 00000000..9b15c485 --- /dev/null +++ b/util/bin/main/gg/rsmod/util/Namer.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, Adam + * Copyright (c) 2018, Joshua Filby + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package gg.rsmod.util + +import java.util.Locale + +class Namer { + private val used: MutableSet = HashSet() + + fun name( + name: String?, + id: Int, + ): String? { + var name = name + name = sanitize(name) + if (name == null) { + return null + } + if (used.contains(name)) { + name = name + "_" + id + assert(!used.contains(name)) + } + used.add(name) + return name + } + + companion object { + private fun sanitize(`in`: String?): String? { + val s = + removeTags(`in`) + .uppercase(Locale.getDefault()) + .replace(' ', '_') + .replace("[^a-zA-Z0-9_]".toRegex(), "") + if (s.isEmpty()) { + return null + } + return if (Character.isDigit(s[0])) { + "_$s" + } else { + s + } + } + + fun removeTags(str: String?): String { + val builder = StringBuilder(str!!.length) + var inTag = false + for (i in 0 until str.length) { + val currentChar = str[i] + if (currentChar == '<') { + inTag = true + } else if (currentChar == '>') { + inTag = false + } else if (!inTag) { + builder.append(currentChar) + } + } + return builder.toString() + } + } +} diff --git a/util/bin/main/gg/rsmod/util/RscmRemapper.kt b/util/bin/main/gg/rsmod/util/RscmRemapper.kt new file mode 100644 index 00000000..c56399de --- /dev/null +++ b/util/bin/main/gg/rsmod/util/RscmRemapper.kt @@ -0,0 +1,84 @@ +package gg.rsmod.util + +import java.io.File + +fun main() { + // Helper function to load mappings from a file + fun loadMap(path: String, sep: String = ":"): Map { + return File(path).readLines() + .map { it.split(sep) } + .associate { it[0] to it[1].toInt() } + } + + // Load old and new maps for items + val oldItemMap = loadMap("C:\\Users\\Home\\Desktop\\Alter-feature-support-231\\Alter-feature-support-231\\data\\cfg\\rscm\\item.rscm") + val newItemMap = File("C:\\Users\\Home\\Desktop\\Alter-feature-support-231\\Alter-feature-support-231\\data\\cfg\\rscm2\\items.rscm") + .readLines() + .map { it.split("=") } + .associate { it[1].toInt() to it[0] } + + val itemLookup = oldItemMap.mapNotNull { (oldName, id) -> + val newName = newItemMap[id] + if (newName != null) oldName to newName else null + }.toMap() + + // Load old and new maps for NPCs + val oldNpcMap = loadMap("C:\\Users\\Home\\Desktop\\Alter-feature-support-231\\Alter-feature-support-231\\data\\cfg\\rscm\\npc.rscm") + val newNpcMap = File("C:\\Users\\Home\\Desktop\\Alter-feature-support-231\\Alter-feature-support-231\\data\\cfg\\rscm2\\npcs.rscm") + .readLines() + .map { it.split("=") } + .associate { it[1].toInt() to it[0] } + + val npcLookup = oldNpcMap.mapNotNull { (oldName, id) -> + val newName = newNpcMap[id] + if (newName != null) oldName to newName else null + }.toMap() + + // Load old and new maps for objects + val oldObjMap = loadMap("C:\\Users\\Home\\Desktop\\Alter-feature-support-231\\Alter-feature-support-231\\data\\cfg\\rscm\\object.rscm") + val newObjMap = File("C:\\Users\\Home\\Desktop\\Alter-feature-support-231\\Alter-feature-support-231\\data\\cfg\\rscm2\\objects.rscm") + .readLines() + .map { it.split("=") } + .associate { it[1].toInt() to it[0] } + + val objLookup = oldObjMap.mapNotNull { (oldName, id) -> + val newName = newObjMap[id] + if (newName != null) oldName to newName else null + }.toMap() + + val rootDir = File("C:\\Users\\Home\\Desktop\\Alter-feature-support-231\\Alter-feature-support-231") + + rootDir.walkTopDown() + .filter { it.isFile && (it.extension == "kt" || it.extension == "kts") } + .forEach { file -> + var text = file.readText() + var replaced = false + + // Regex to match string literals like "item.*", "npc.*", "object.*" + val regex = """"(item|npc|object)\.([^"]+)"""".toRegex() + + text = regex.replace(text) { matchResult -> + val prefix = matchResult.groupValues[1] + val oldStr = matchResult.groupValues[2] + val newStr = when (prefix) { + "item" -> itemLookup[oldStr] + "npc" -> npcLookup[oldStr] + "object" -> objLookup[oldStr] + else -> null + } + + if (newStr != null) { + replaced = true + "\"$prefix.$newStr\"" + } else { + println("Unknown mapping in ${file.path}: \"${prefix}.${oldStr}\"") + matchResult.value // keep original + } + } + + if (replaced) { + println("Updated file: ${file.path}") + file.writeText(text) + } + } +} diff --git a/util/bin/main/gg/rsmod/util/ServerProperties.kt b/util/bin/main/gg/rsmod/util/ServerProperties.kt new file mode 100644 index 00000000..85182bc7 --- /dev/null +++ b/util/bin/main/gg/rsmod/util/ServerProperties.kt @@ -0,0 +1,73 @@ +package gg.rsmod.util + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import java.io.File + +/** + * Holds a map of properties that can be used by the server + * at any point. + * + * @author Tom + */ +class ServerProperties { + private val properties = hashMapOf() + + /** + * Gets the property associated with the [key]. If it cannot be found, it will + * return the [default] value instead. + */ + @Suppress("UNCHECKED_CAST") + fun getOrDefault( + key: String, + default: T, + ): T = properties[key] as? T ?: default + + /** + * Gets the property associated with the [key]. If it cannot be found, it will + * return null instead. + */ + @Suppress("UNCHECKED_CAST") + fun get(key: String): T? = properties[key] as? T + + /** + * Checks if [properties] contains a value associated with [key]. + */ + fun has(key: String): Boolean = properties.containsKey(key) + + /** + * Loads a YAML (.yml) file and puts all the found keys & values + * into the [properties] map. + */ + fun loadYaml(file: File): ServerProperties { + check(properties.isEmpty()) + + val mapper = ObjectMapper(YAMLFactory()) + val values = mapper.readValue(file, HashMap().javaClass) + + values.forEach { key, value -> + if (value is String && value.isEmpty()) { + properties[key] = null + } else { + properties[key] = value + } + } + return this + } + + fun loadMap(data: Map): ServerProperties { + check(properties.isEmpty()) + + data.forEach { key, value -> + if (value is String && value.isEmpty()) { + properties[key] = null + } else { + properties[key] = value + } + } + return this + } + + @Suppress("UNCHECKED_CAST") + fun extract(key: String): ServerProperties = ServerProperties().loadMap(get>(key) as Map) +} diff --git a/util/bin/main/gg/rsmod/util/Stopwatch.kt b/util/bin/main/gg/rsmod/util/Stopwatch.kt new file mode 100644 index 00000000..03a16de1 --- /dev/null +++ b/util/bin/main/gg/rsmod/util/Stopwatch.kt @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package gg.rsmod.util + +import java.time.Duration +import java.util.* +import java.util.concurrent.TimeUnit + +/** + * An object that accurately measures *elapsed time*: the measured duration between two + * successive readings of "now" in the same process. + * + * + * In contrast, *wall time* is a reading of "now" as given by a method like + * [System.currentTimeMillis], best represented as an [java.time.Instant]. Such values + * *can* be subtracted to obtain a `Duration` (such as by `Duration.between`), but + * doing so does *not* give a reliable measurement of elapsed time, because wall time readings + * are inherently approximate, routinely affected by periodic clock corrections. Because this class + * uses [System.nanoTime], it is unaffected by these changes. + * + * + * Use this class instead of direct calls to [System.nanoTime] for the following reason: + * + * + * * The raw `long` values returned by `nanoTime` are meaningless and unsafe to use + * in any other way than how `Stopwatch` uses them. + * + * + * + * The one downside of `Stopwatch` relative to [System.nanoTime] is that `Stopwatch` requires object allocation and additional method calls, which can reduce the accuracy + * of the elapsed times reported. `Stopwatch` is still suitable for logging and metrics where + * reasonably accurate values are sufficient. If the uncommon case that you need to maximize + * accuracy, use `System.nanoTime()` directly instead. + * + * + * Basic usage: + * + *
`Stopwatch stopwatch = Stopwatch.createStarted();
+ * doSomething();
+ * stopwatch.stop(); // optional
+ *
+ * Duration duration = stopwatch.elapsed();
+ *
+ * log.info("time: " + stopwatch); // formatted string like "12.3 ms"
+`
* + * + * + * The state-changing methods are not idempotent; it is an error to start or stop a stopwatch + * that is already in the desired state. + * + * + * **Note:** This class is not thread-safe. + * + * + * @author Kevin Bourrillion + * @since 10.0 + * + * Changes: 5/27/2024 converted com.google.common.base.Stopwatch to kotlin + * Original file from commit c4b883de9679dae7da831e49dd9adaca71cc1991 + */ +class Stopwatch { + /** + * Returns `true` if [.start] has been called on this stopwatch, and [.stop] + * has not been called since the last call to `start()`. + */ + private var isRunning = false + private var elapsedNanos: Long = 0 + private var startTick: Long = 0 + + /** + * Starts the stopwatch. + * + * @return this `Stopwatch` instance + * @throws IllegalStateException if the stopwatch is already running. + */ + fun start(): Stopwatch { + check(!isRunning) { "This stopwatch is already running." } + isRunning = true + startTick = System.nanoTime() + return this + } + + /** + * Stops the stopwatch. Future reads will return the fixed duration that had elapsed up to this + * point. + * + * @return this `Stopwatch` instance + * @throws IllegalStateException if the stopwatch is already stopped. + */ + fun stop(): Stopwatch { + val tick = System.nanoTime() + check(isRunning) { "This stopwatch is already stopped." } + isRunning = false + elapsedNanos += tick - startTick + return this + } + + /** + * Sets the elapsed time for this stopwatch to zero, and places it in a stopped state. + * + * @return this `Stopwatch` instance + */ + fun reset(): Stopwatch { + elapsedNanos = 0 + isRunning = false + return this + } + + private fun elapsedNanos(): Long { + return if (isRunning) System.nanoTime() - startTick + elapsedNanos else elapsedNanos + } + + /** + * Returns the current elapsed time shown on this stopwatch, expressed in the desired time unit, + * with any fraction rounded down. + * + * + * **Note:** the overhead of measurement can be more than a microsecond, so it is generally + * not useful to specify [TimeUnit.NANOSECONDS] precision here. + * + * + * It is generally not a good idea to use an ambiguous, unitless `long` to represent + * elapsed time. Therefore, we recommend using [.elapsed] instead, which returns a + * strongly-typed `Duration` instance. + * + * @since 14.0 (since 10.0 as `elapsedTime()`) + */ + fun elapsed(desiredUnit: TimeUnit): Long { + return desiredUnit.convert(elapsedNanos(), TimeUnit.NANOSECONDS) + } + + /** + * Returns the current elapsed time shown on this stopwatch as a [Duration]. Unlike [ ][.elapsed], this method does not lose any precision due to rounding. + * + * @since 22.0 + */ + fun elapsed(): Duration { + return Duration.ofNanos(elapsedNanos()) + } + + /** Returns a string representation of the current elapsed time. */ + override fun toString(): String { + val nanos = elapsedNanos() + val unit = chooseUnit(nanos) + val value = nanos.toDouble() / TimeUnit.NANOSECONDS.convert(1, unit) + + // Too bad this functionality is not exposed as a regular method call + return formatCompact4Digits(value) + " " + abbreviate(unit) + } + + companion object { + /** + * Creates (but does not start) a new stopwatch using [System.nanoTime] as its time source. + * + * @since 15.0 + */ + fun createUnstarted(): Stopwatch { + return Stopwatch() + } + + /** + * Creates (and starts) a new stopwatch using [System.nanoTime] as its time source. + * + * @since 15.0 + */ + fun createStarted(): Stopwatch { + return Stopwatch().start() + } + + private fun chooseUnit(nanos: Long): TimeUnit { + if (TimeUnit.DAYS.convert(nanos, TimeUnit.NANOSECONDS) > 0) { + return TimeUnit.DAYS + } + if (TimeUnit.HOURS.convert(nanos, TimeUnit.NANOSECONDS) > 0) { + return TimeUnit.HOURS + } + if (TimeUnit.MINUTES.convert(nanos, TimeUnit.NANOSECONDS) > 0) { + return TimeUnit.MINUTES + } + if (TimeUnit.SECONDS.convert(nanos, TimeUnit.NANOSECONDS) > 0) { + return TimeUnit.SECONDS + } + if (TimeUnit.MILLISECONDS.convert(nanos, TimeUnit.NANOSECONDS) > 0) { + return TimeUnit.MILLISECONDS + } + return if (TimeUnit.MICROSECONDS.convert(nanos, TimeUnit.NANOSECONDS) > 0) { + TimeUnit.MICROSECONDS + } else { + TimeUnit.NANOSECONDS + } + } + + private fun abbreviate(unit: TimeUnit): String { + return when (unit) { + TimeUnit.NANOSECONDS -> "ns" + TimeUnit.MICROSECONDS -> "\u03bcs" // μs + TimeUnit.MILLISECONDS -> "ms" + TimeUnit.SECONDS -> "s" + TimeUnit.MINUTES -> "min" + TimeUnit.HOURS -> "h" + TimeUnit.DAYS -> "d" + else -> throw AssertionError() + } + } + + fun formatCompact4Digits(value: Double): String { + return String.format(Locale.ROOT, "%.4g", value) + } + } +} diff --git a/util/bin/main/gg/rsmod/util/concurrency/PhasedTask.kt b/util/bin/main/gg/rsmod/util/concurrency/PhasedTask.kt new file mode 100644 index 00000000..829db8bc --- /dev/null +++ b/util/bin/main/gg/rsmod/util/concurrency/PhasedTask.kt @@ -0,0 +1,21 @@ +package gg.rsmod.util.concurrency + +import io.github.oshai.kotlinlogging.KotlinLogging +import java.util.concurrent.Phaser + +object PhasedTask { + private val logger = KotlinLogging.logger {} + + fun run( + phaser: Phaser, + task: () -> Unit, + ) { + try { + task() + } catch (e: Exception) { + logger.error(e) { "Error with phased task." } + } finally { + phaser.arriveAndDeregister() + } + } +} diff --git a/util/bin/main/gg/rsmod/util/concurrency/ThreadFactoryBuilder.kt b/util/bin/main/gg/rsmod/util/concurrency/ThreadFactoryBuilder.kt new file mode 100644 index 00000000..05fb1ffa --- /dev/null +++ b/util/bin/main/gg/rsmod/util/concurrency/ThreadFactoryBuilder.kt @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package gg.rsmod.util.concurrency + +import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.ThreadFactory +import java.util.concurrent.atomic.AtomicLong + +/** + * A ThreadFactory builder, providing any combination of these features: + * + * + * * whether threads should be marked as [daemon][Thread.setDaemon] threads + * * a [naming format][ThreadFactoryBuilder.setNameFormat] + * * a [thread priority][Thread.setPriority] + * * an [uncaught exception handler][Thread.setUncaughtExceptionHandler] + * * a [backing thread factory][ThreadFactory.newThread] + * + * + * + * If no backing thread factory is provided, a default backing thread factory is used as if by + * calling `setThreadFactory(`[Executors.defaultThreadFactory]`)`. + * + * @author Kurt Alfred Kluever + * @since 4.0 + * + * Changes: 5/27/2024 converted com.google.common.util.concurrent.ThreadFactoryBuilder to kotlin + * Original file from commit c4b883de9679dae7da831e49dd9adaca71cc1991 + */ +class ThreadFactoryBuilder { +/** Creates a new [ThreadFactory] builder. */ + private var nameFormat: String? = null + private var daemon: Boolean? = null + private var priority: Int? = null + private var uncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null + private var backingThreadFactory: ThreadFactory? = null + + /** + * Sets the naming format to use when naming threads ([Thread.setName]) which are created + * with this ThreadFactory. + * + * @param nameFormat a [String.format]-compatible format String, to which + * a unique integer (0, 1, etc.) will be supplied as the single parameter. This integer will + * be unique to the built instance of the ThreadFactory and will be assigned sequentially. For + * example, `"rpc-pool-%d"` will generate thread names like `"rpc-pool-0"`, `"rpc-pool-1"`, `"rpc-pool-2"`, etc. + * @return this for the builder pattern + */ + fun setNameFormat(nameFormat: String): ThreadFactoryBuilder { + val unused = format(nameFormat, 0) // fail fast if the format is bad or null + this.nameFormat = nameFormat + return this + } + + /** + * Sets daemon or not for new threads created with this ThreadFactory. + * + * @param daemon whether or not new Threads created with this ThreadFactory will be daemon threads + * @return this for the builder pattern + */ + fun setDaemon(daemon: Boolean): ThreadFactoryBuilder { + this.daemon = daemon + return this + } + + /** + * Sets the priority for new threads created with this ThreadFactory. + * + * + * **Warning:** relying on the thread scheduler is [discouraged](http://errorprone.info/bugpattern/ThreadPriorityCheck). + * + * @param priority the priority for new Threads created with this ThreadFactory + * @return this for the builder pattern + */ + fun setPriority(priority: Int): ThreadFactoryBuilder { + // Thread#setPriority() already checks for validity. These error messages + // are nicer though and will fail-fast. + require(priority >= Thread.MIN_PRIORITY) { + format( + "Thread priority (%s) must be >= %s", + priority, + Thread.MIN_PRIORITY, + ) + } + require(priority <= Thread.MAX_PRIORITY) { + format( + "Thread priority (%s) must be <= %s", + priority, + Thread.MAX_PRIORITY, + ) + } + this.priority = priority + return this + } + + /** + * Sets the [UncaughtExceptionHandler] for new threads created with this ThreadFactory. + * + * @param uncaughtExceptionHandler the uncaught exception handler for new Threads created with + * this ThreadFactory + * @return this for the builder pattern + */ + fun setUncaughtExceptionHandler(uncaughtExceptionHandler: Thread.UncaughtExceptionHandler): ThreadFactoryBuilder { + this.uncaughtExceptionHandler = uncaughtExceptionHandler + return this + } + + /** + * Sets the backing [ThreadFactory] for new threads created with this ThreadFactory. Threads + * will be created by invoking #newThread(Runnable) on this backing [ThreadFactory]. + * + * @param backingThreadFactory the backing [ThreadFactory] which will be delegated to during + * thread creation. + * @return this for the builder pattern + */ + fun setThreadFactory(backingThreadFactory: ThreadFactory): ThreadFactoryBuilder { + this.backingThreadFactory = backingThreadFactory + return this + } + + /** + * Returns a new thread factory using the options supplied during the building process. After + * building, it is still possible to change the options used to build the ThreadFactory and/or + * build again. State is not shared amongst built instances. + * + * @return the fully constructed [ThreadFactory] + */ + fun build(): ThreadFactory { + return doBuild(this) + } + + companion object { + // Split out so that the anonymous ThreadFactory can't contain a reference back to the builder. + // At least, I assume that's why. TODO(cpovirk): Check, and maybe add a test for this. + private fun doBuild(builder: ThreadFactoryBuilder): ThreadFactory { + val nameFormat = builder.nameFormat + val daemon = builder.daemon + val priority = builder.priority + val uncaughtExceptionHandler = builder.uncaughtExceptionHandler + val backingThreadFactory = builder.backingThreadFactory ?: Executors.defaultThreadFactory() + val count: AtomicLong? = if (nameFormat != null) AtomicLong(0) else null + return ThreadFactory { runnable -> + val thread: Thread = backingThreadFactory.newThread(runnable) + // TODO(b/139735208): Figure out what to do when the factory returns null. + Objects.requireNonNull(thread) + if (nameFormat != null) { + // this is safe because we create `count` if (and only if) we have a nameFormat. + thread.name = format(nameFormat, count!!.getAndIncrement()) + } + if (daemon != null) { + thread.isDaemon = daemon + } + if (priority != null) { + thread.priority = priority + } + if (uncaughtExceptionHandler != null) { + thread.uncaughtExceptionHandler = uncaughtExceptionHandler + } + thread + } + } + + private fun format( + format: String, + vararg args: Any, + ): String { + return String.format(Locale.ROOT, format, *args) + } + } +} diff --git a/util/bin/main/gg/rsmod/util/io/BufferUtils.kt b/util/bin/main/gg/rsmod/util/io/BufferUtils.kt new file mode 100644 index 00000000..e2cccae8 --- /dev/null +++ b/util/bin/main/gg/rsmod/util/io/BufferUtils.kt @@ -0,0 +1,88 @@ +package gg.rsmod.util.io + +import io.netty.buffer.ByteBuf + +/** + * @author Tom + */ +object BufferUtils { + fun ByteBuf.toArray(length: Int = readableBytes()): ByteArray { + val bytes = ByteArray(length) + duplicate().readBytes(bytes) + return bytes + } + + fun ByteBuf.readString(): String { + if (isReadable) { + val start = readerIndex() + while (readByte().toInt() != 0); + val size = readerIndex() - start + + val data = ByteArray(size) + readerIndex(start) + readBytes(data) + + return String(data, 0, size - 1) + } else { + return "" + } + } + + fun ByteBuf.readJagexString(): String { + if (isReadable && readByte().toInt() != 0) { + val start = readerIndex() + while (readByte().toInt() != 0); + val size = readerIndex() - start + + val data = ByteArray(size) + readerIndex(start) + readBytes(data) + + return String(data, 0, size - 1) + } else { + return "" + } + } + + /** + * Gets a 32-bit integer at the current {@code readerIndex} + * in Litte endian format (DCBA) + * and increases the {@code readerIndex} by {@code 4} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 4} + * + * Note| this method now is natively implemented in netty and shouldn't be needed + * commented out for posterity and to keep it from showing in code completion + */ + fun ByteBuf.readIntLE(): Int { + if (readableBytes() < 4) throw IndexOutOfBoundsException("buffer does not contain enough bytes to read an int") + return (readByte().toInt() and 0xFF) + ((readByte().toInt() and 0xFF) shl 8) + ((readByte().toInt() and 0xFF) shl 16) + ((readByte().toInt() and 0xFF) shl 24) + } + + /** + * Gets a 32-bit integer at the current {@code readerIndex} + * in Inverse Middle endian format (BADC) + * and increases the {@code readerIndex} by {@code 4} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 4} + */ + fun ByteBuf.readIntIME(): Int { + if (readableBytes() < 4) throw IndexOutOfBoundsException("buffer does not contain enough bytes to read an int") + return ((readByte().toInt() and 0xFF) shl 16) + ((readByte().toInt() and 0xFF) shl 24) + (readByte().toInt() and 0xFF) + ((readByte().toInt() and 0xFF) shl 8) + } + + /** + * Gets a 32-bit integer at the current {@code readerIndex} + * in Middle endian format (CDAB) + * and increases the {@code readerIndex} by {@code 4} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 4} + */ + fun ByteBuf.readIntME(): Int { + if (readableBytes() < 4) throw IndexOutOfBoundsException("buffer does not contain enough bytes to read an int") + return ((readByte().toInt() and 0xFF) shl 8) + (readByte().toInt() and 0xFF) + ((readByte().toInt() and 0xFF) shl 24) + ((readByte().toInt() and 0xFF) shl 16) + } +}