From afc005f123459642ce468c0d8213e8b6ac9e51cc Mon Sep 17 00:00:00 2001 From: Mark7625 <72366279+Mark7625@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:27:07 +0100 Subject: [PATCH 1/4] Toolbelt Toolbelt --- .gitignore | 4 + .../api/inv/storage/PlayerItemStorage.kt | 3 + .../api/inv/storage/PlayerItemStorageHook.kt | 5 + .../api/player/protect/ProtectedAccess.kt | 40 +- .../main/kotlin/base-conventions.gradle.kts | 7 +- .../equipment/EquipmentTabScript.kt | 1 + .../equipment/death/ItemsKeptOnDeathScript.kt | 1 + .../equipment/prices/GuidePriceScript.kt | 1 + .../equipment/stats/EquipmentStats.kt | 1 + content/other/toolbelt/build.gradle.kts | 14 + content/other/toolbelt/pack/build.gradle.kts | 17 + .../other/toolbelt/pack/ToolbeltInterface.kt | 52 + .../toolbelt/pack/ToolbeltPackSettings.kt | 34 + .../other/toolbelt/pack/ToolbeltPluginPack.kt | 33 + .../other/toolbelt/pack/ToolbeltTable.kt | 390 + .../content/other/toolbelt/pack/WornItems.kt | 106 + .../rsmod/content/other/toolbelt/Toolbelt.kt | 236 + .../other/toolbelt/ToolbeltBonecrusher.kt | 144 + .../toolbelt/ToolbeltBonecrusherUnlock.kt | 87 + .../content/other/toolbelt/ToolbeltModule.kt | 10 + .../content/other/toolbelt/ToolbeltScript.kt | 461 + .../other/toolbelt/ToolbeltSettings.kt | 34 + .../other/toolbelt/ToolbeltSlotInfo.kt | 68 + .../content/other/toolbelt/ToolbeltSlots.kt | 13 + .../other/toolbelt/ToolbeltStorageHook.kt | 26 + .../content/other/toolbelt/ToolbeltUi.kt | 17 + .../content/other/toolbelt/ToolbeltUnlocks.kt | 39 + .../content/other/toolbelt/ToolbeltVars.kt | 31 + .../other/toolbelt/ToolbeltWornItems.kt | 18 + .../toolbelt/src/main/resources/gamevals.toml | 114 + .../src/main/resources/pack/configs/enum.toml | 11 + .../main/resources/pack/configs/varbit.toml | 168 + .../src/main/resources/pack/configs/varp.toml | 115 + .../script/[clientscript,toolbelt_filter].cs2 | 189 + .../script/[clientscript,toolbelt_init].cs2 | 43 + .../pack/cs2/script/[proc,toolbelt_build].cs2 | 82 + .../script/[proc,toolbelt_count_category].cs2 | 18 + .../pack/cs2/script/[proc,toolbelt_draw].cs2 | 156 + .../script/[proc,toolbelt_stored_for_row].cs2 | 32 + .../pack/cs2/symbols/clientscript.sym | 6 + .../main/resources/pack/cs2/symbols/enum.sym | 1 + .../resources/pack/cs2/symbols/graphic.sym | 1 + .../resources/pack/cs2/symbols/sprite.sym | 4 + .../resources/pack/cs2/symbols/varbit.sym | 28 + .../main/resources/pack/cs2/symbols/varp.sym | 29 + .../resources/pack/scripts/toolbelt-items.rp | 18 + .../main/resources/pack/sprites/manifest.toml | 5 + .../resources/pack/sprites/square_button.png | Bin 0 -> 50177 bytes .../pack/sprites/square_button_clicked.png | Bin 0 -> 51435 bytes .../pack/sprites/square_button_disabled.png | Bin 0 -> 49540 bytes .../pack/sprites/square_button_hover.png | Bin 0 -> 51434 bytes .../main/resources/pack/sprites/tool_belt.png | Bin 0 -> 52386 bytes .../toolbelt/src/main/resources/toolbelt.toml | 2 + content/skills/mining/build.gradle.kts | 1 + .../content/skills/mining/scripts/Mining.kt | 6 + content/skills/prayer/build.gradle.kts | 1 + .../bonecrusher/BonecrusherNpcDropHook.kt | 53 +- .../prayer/npcs/EctoGhostDiscipleDialogue.kt | 85 +- content/skills/woodcutting/build.gradle.kts | 1 + .../skills/woodcutting/scripts/Woodcutting.kt | 5 + .../org/rsmod/game/inv/InvVirtualStorage.kt | 6 +- .../kotlin/org/rsmod/game/inv/Inventory.kt | 49 +- gradle/libs.versions.toml | 2 +- or-cache/build.gradle.kts | 5 + or-cache/pack-api/build.gradle.kts | 9 + .../kotlin/dev/openrune/pack/PluginPack.kt | 40 + .../main/kotlin/dev/openrune/CacheTools.kt | 250 +- .../codec/osrs/impl/ItemServerCodec.kt | 2 +- .../dev/openrune/gamevals/GameValProvider.kt | 25 +- .../dev/openrune/gamevals/GamevalDumper.kt | 26 +- .../dev/openrune/pack/PluginPackLoader.kt | 26 + .../dev/openrune/packscript/PackLangAst.kt | 99 + .../dev/openrune/packscript/PackLangParser.kt | 547 + .../openrune/packscript/PackLangRuntime.kt | 417 + .../openrune/packscript/PackScriptPhase.kt | 12 + .../dev/openrune/packscript/PackScripts.kt | 93 + .../openrune/packscript/RunePackValidator.kt | 131 + .../src/main/kotlin/dev/openrune/rscm/RSCM.kt | 1 + .../openrune/tools/PackServerConfigOSRS.kt | 2 +- osrs-dumps/dump.npc | 379695 --------------- settings.gradle.kts | 2 + 81 files changed, 4699 insertions(+), 379807 deletions(-) create mode 100644 content/other/toolbelt/build.gradle.kts create mode 100644 content/other/toolbelt/pack/build.gradle.kts create mode 100644 content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltInterface.kt create mode 100644 content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltPackSettings.kt create mode 100644 content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltPluginPack.kt create mode 100644 content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltTable.kt create mode 100644 content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/WornItems.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/Toolbelt.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltBonecrusher.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltBonecrusherUnlock.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltModule.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltScript.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSettings.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSlotInfo.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSlots.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltStorageHook.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltUi.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltUnlocks.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltVars.kt create mode 100644 content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltWornItems.kt create mode 100644 content/other/toolbelt/src/main/resources/gamevals.toml create mode 100644 content/other/toolbelt/src/main/resources/pack/configs/enum.toml create mode 100644 content/other/toolbelt/src/main/resources/pack/configs/varbit.toml create mode 100644 content/other/toolbelt/src/main/resources/pack/configs/varp.toml create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/script/[clientscript,toolbelt_filter].cs2 create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/script/[clientscript,toolbelt_init].cs2 create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_build].cs2 create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_count_category].cs2 create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_draw].cs2 create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_stored_for_row].cs2 create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/symbols/clientscript.sym create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/symbols/enum.sym create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/symbols/graphic.sym create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/symbols/sprite.sym create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/symbols/varbit.sym create mode 100644 content/other/toolbelt/src/main/resources/pack/cs2/symbols/varp.sym create mode 100644 content/other/toolbelt/src/main/resources/pack/scripts/toolbelt-items.rp create mode 100644 content/other/toolbelt/src/main/resources/pack/sprites/manifest.toml create mode 100644 content/other/toolbelt/src/main/resources/pack/sprites/square_button.png create mode 100644 content/other/toolbelt/src/main/resources/pack/sprites/square_button_clicked.png create mode 100644 content/other/toolbelt/src/main/resources/pack/sprites/square_button_disabled.png create mode 100644 content/other/toolbelt/src/main/resources/pack/sprites/square_button_hover.png create mode 100644 content/other/toolbelt/src/main/resources/pack/sprites/tool_belt.png create mode 100644 content/other/toolbelt/src/main/resources/toolbelt.toml create mode 100644 or-cache/pack-api/build.gradle.kts create mode 100644 or-cache/pack-api/src/main/kotlin/dev/openrune/pack/PluginPack.kt create mode 100644 or-cache/src/main/kotlin/dev/openrune/pack/PluginPackLoader.kt create mode 100644 or-cache/src/main/kotlin/dev/openrune/packscript/PackLangAst.kt create mode 100644 or-cache/src/main/kotlin/dev/openrune/packscript/PackLangParser.kt create mode 100644 or-cache/src/main/kotlin/dev/openrune/packscript/PackLangRuntime.kt create mode 100644 or-cache/src/main/kotlin/dev/openrune/packscript/PackScriptPhase.kt create mode 100644 or-cache/src/main/kotlin/dev/openrune/packscript/PackScripts.kt create mode 100644 or-cache/src/main/kotlin/dev/openrune/packscript/RunePackValidator.kt delete mode 100644 osrs-dumps/dump.npc diff --git a/.gitignore b/.gitignore index 5bbbda25f..518a5c6e1 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,7 @@ logs/ *.log /.cursor /.data/raw-cache/cs2 + +# Extracted library sources (do not commit) +/dev/ + diff --git a/api/inv-storage/src/main/kotlin/org/rsmod/api/inv/storage/PlayerItemStorage.kt b/api/inv-storage/src/main/kotlin/org/rsmod/api/inv/storage/PlayerItemStorage.kt index 74d00a198..9345b488f 100644 --- a/api/inv-storage/src/main/kotlin/org/rsmod/api/inv/storage/PlayerItemStorage.kt +++ b/api/inv-storage/src/main/kotlin/org/rsmod/api/inv/storage/PlayerItemStorage.kt @@ -28,6 +28,9 @@ constructor(private val hooks: Set<@JvmSuppressWildcards PlayerItemStorageHook>) return total } + override fun providesContentGroup(player: Player, inventory: Inventory, contentGroup: Int): Boolean = + hooks.any { it.providesContentGroup(player, contentGroup) } + public fun consumePolicy( player: Player, inventory: Inventory, diff --git a/api/inv-storage/src/main/kotlin/org/rsmod/api/inv/storage/PlayerItemStorageHook.kt b/api/inv-storage/src/main/kotlin/org/rsmod/api/inv/storage/PlayerItemStorageHook.kt index 974a54bdb..818154e74 100644 --- a/api/inv-storage/src/main/kotlin/org/rsmod/api/inv/storage/PlayerItemStorageHook.kt +++ b/api/inv-storage/src/main/kotlin/org/rsmod/api/inv/storage/PlayerItemStorageHook.kt @@ -1,5 +1,7 @@ package org.rsmod.api.inv.storage +import org.rsmod.game.entity.Player + /** * Virtual item storage (coal bag, rune pouch, etc.). [PlayerItemStorage] calls [shouldProcess] * before every operation — only implement the storage logic here. @@ -15,4 +17,7 @@ public interface PlayerItemStorageHook { public fun remove(ctx: PlayerItemStorageContext, amount: Int): Int public fun add(ctx: PlayerItemStorageContext, amount: Int): Int + + /** True when this storage provides any item in [contentGroup] for [player]. */ + public fun providesContentGroup(player: Player, contentGroup: Int): Boolean = false } diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/protect/ProtectedAccess.kt b/api/player/src/main/kotlin/org/rsmod/api/player/protect/ProtectedAccess.kt index 89af046f7..442943574 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/protect/ProtectedAccess.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/protect/ProtectedAccess.kt @@ -3324,15 +3324,12 @@ public class ProtectedAccess( return invTotal(inv, "obj.coins") } - public fun invContentTotal(inv: Inventory, content: String): Int { - var count = 0 - for (obj in inv) { - val filtered = obj ?: continue - if (getInvObj(filtered).isContentType(content)) { - count += filtered.count - } + public fun invContentTotal(inv: Inventory, query: String): Int { + return when { + query.startsWith("obj.") -> inv.count(query) + query.startsWith("content.") -> inv.contentTotal(query) + else -> error("Expected obj.* or content.*: $query") } - return count } public fun invTotal(inv: Inventory, obj: String): Int { @@ -3340,7 +3337,8 @@ public class ProtectedAccess( } public fun invContains(inv: Inventory, content: String): Boolean { - return inv.any { it != null && getInvObj(it).contentGroup == content.asRSCM(RSCMType.CONTENT) } + // Inventory.contains handles content groups + virtual storage (toolbelt, etc.). + return inv.contains(content) } /** @@ -3349,32 +3347,32 @@ public class ProtectedAccess( * as shops are excluded. */ public fun playerContainsContent(content: String): Boolean { - val contentId = content.asRSCM(RSCMType.CONTENT) - return playerContainsInInventories { getInvObj(it).contentGroup == contentId } + for (inventory in player.invMap.values) { + if (inventory.type.scope == InvScope.Shared) continue + if (inventory.contains(content)) { + return true + } + } + return false } /** * Returns whether any stack of [obj] exists in any of the player's inventories. Shared - * inventories such as shops are excluded. + * inventories such as shops are excluded. Includes virtual storage (toolbelt, etc.). */ public fun playerContainsObj(obj: String): Boolean { - val objId = obj.asRSCM(RSCMType.OBJ) - return playerContainsInInventories { getInvObj(it).id == objId } - } - - /** @see [playerContainsObj] */ - public fun playerContainsAnyObj(vararg objs: String): Boolean = objs.any(::playerContainsObj) - - private inline fun playerContainsInInventories(crossinline predicate: (InvObj) -> Boolean): Boolean { for (inventory in player.invMap.values) { if (inventory.type.scope == InvScope.Shared) continue - if (inventory.any { slot -> slot != null && predicate(slot) }) { + if (inventory.count(obj) > 0) { return true } } return false } + /** @see [playerContainsObj] */ + public fun playerContainsAnyObj(vararg objs: String): Boolean = objs.any(::playerContainsObj) + public fun inv(inv: String): Inventory { return player.invMap.getOrPut(inv) } diff --git a/build-logic/src/main/kotlin/base-conventions.gradle.kts b/build-logic/src/main/kotlin/base-conventions.gradle.kts index 3830bc795..ffc737e3e 100644 --- a/build-logic/src/main/kotlin/base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/base-conventions.gradle.kts @@ -5,7 +5,12 @@ plugins { id("test-conventions") } -if (path != ":or-cache" && path != ":engine:map" && path != ":engine:routefinder") { +if (path != ":or-cache" && + path != ":or-cache:pack-api" && + path != ":engine:map" && + path != ":engine:routefinder" && + !path.endsWith(":pack") +) { dependencies { implementation(project(":or-cache")) } diff --git a/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/EquipmentTabScript.kt b/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/EquipmentTabScript.kt index db0a6a7d8..f52858156 100644 --- a/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/EquipmentTabScript.kt +++ b/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/EquipmentTabScript.kt @@ -9,6 +9,7 @@ import org.rsmod.plugin.scripts.ScriptContext class EquipmentTabScript : PluginScript() { override fun ScriptContext.startup() { onIfOverlayButton("component.wornitems:call_follower") { player.callFollower() } + onIfOverlayButton("component.wornitems:tb_call_follower") { player.callFollower() } } private fun Player.callFollower() { diff --git a/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/death/ItemsKeptOnDeathScript.kt b/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/death/ItemsKeptOnDeathScript.kt index a7d7051f1..b0478b99c 100644 --- a/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/death/ItemsKeptOnDeathScript.kt +++ b/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/death/ItemsKeptOnDeathScript.kt @@ -40,6 +40,7 @@ constructor( override fun ScriptContext.startup() { onIfOverlayButton("component.wornitems:deathkeep") { player.selectKeptOnDeath() } + onIfOverlayButton("component.wornitems:tb_deathkeep") { player.selectKeptOnDeath() } onIfClose("interface.deathkeep") { player.closeKeptOnDeath() } } diff --git a/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/prices/GuidePriceScript.kt b/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/prices/GuidePriceScript.kt index 203419bc1..bfe776095 100644 --- a/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/prices/GuidePriceScript.kt +++ b/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/prices/GuidePriceScript.kt @@ -40,6 +40,7 @@ constructor( override fun ScriptContext.startup() { onIfOverlayButton("component.wornitems:pricechecker") { player.selectGuidePrices() } + onIfOverlayButton("component.wornitems:tb_pricechecker") { player.selectGuidePrices() } onIfModalButton("component.ge_pricechecker:all") { addAllFromInv() } onIfModalButton("component.ge_pricechecker_side:items") { addFromSlot(it.comsub, it.op) } onIfModalButton("component.ge_pricechecker:items") { takeFromSlot(it.comsub, it.op) } diff --git a/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/stats/EquipmentStats.kt b/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/stats/EquipmentStats.kt index 433eeb83d..55a55b289 100644 --- a/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/stats/EquipmentStats.kt +++ b/content/interfaces/equipment/src/main/kotlin/org/rsmod/content/interfaces/equipment/stats/EquipmentStats.kt @@ -38,6 +38,7 @@ constructor( ) : PluginScript() { override fun ScriptContext.startup() { onIfOverlayButton("component.wornitems:equipment") { player.selectStats() } + onIfOverlayButton("component.wornitems:tb_equipment") { player.selectStats() } val componentWornSlots = equipment_stats_to_slots_map.filterValuesNotNull().map { it.key to RSCM.getReverseMapping(RSCMType.COMPONENT,it.value.packed) } for ((slot, component) in componentWornSlots) { diff --git a/content/other/toolbelt/build.gradle.kts b/content/other/toolbelt/build.gradle.kts new file mode 100644 index 000000000..14e5fba27 --- /dev/null +++ b/content/other/toolbelt/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) + implementation(projects.api.attr) + implementation(projects.api.invStorage) + implementation(projects.content.other.toolbelt.pack) + implementation(projects.content.skills.utils) + implementation(libs.jackson.databind) + implementation(libs.jackson.dataformat.toml) + implementation(libs.jackson.module.kotlin) +} diff --git a/content/other/toolbelt/pack/build.gradle.kts b/content/other/toolbelt/pack/build.gradle.kts new file mode 100644 index 000000000..370971205 --- /dev/null +++ b/content/other/toolbelt/pack/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.orCache.packApi) + implementation(libs.or2.all.cache) + implementation(libs.or2.tools) + implementation(libs.or2.definition) + implementation(libs.or2.filestore) + implementation(libs.or2.filesystem) + implementation(libs.jackson.databind) + implementation(libs.jackson.dataformat.toml) + implementation("io.netty:netty-buffer:4.1.107.Final") + implementation("me.tongfei:progressbar:0.9.2") + implementation("com.michael-bull.kotlin-inline-logger:kotlin-inline-logger:1.0.6") +} diff --git a/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltInterface.kt b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltInterface.kt new file mode 100644 index 000000000..4262fa74a --- /dev/null +++ b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltInterface.kt @@ -0,0 +1,52 @@ +package org.rsmod.content.other.toolbelt.pack + +import dev.openrune.cache.tools.iftype.dsl.buildInterface +import dev.openrune.cache.tools.iftype.dsl.impl.layer +import dev.openrune.definition.constants.ConstantProvider +import dev.openrune.definition.type.widget.IfEvent + +private const val WIDTH = 419 +private const val HEIGHT = 291 +private const val INSET = 10 +private const val TITLE_H = 36 +private const val SEARCH_H = 24 +private const val SEARCH_GAP = 4 +private const val SCROLL_W = 16 +private const val CONTENT_TOP = TITLE_H + SEARCH_H + SEARCH_GAP + +fun buildToolbeltInterface() = + buildInterface(internalName = "interface.toolbelt", width = WIDTH, height = HEIGHT) { + val iface = ConstantProvider.getMapping("interface.toolbelt") + val initCs = ConstantProvider.getMapping("clientscript.toolbelt_init") + fun comp(child: Int) = (iface shl 16) or child + + onLoadListener { arrayOf(initCs, comp(1), comp(2), comp(3), comp(4)) } + + layer("border") { + position { 0 to 0 } + size { WIDTH to HEIGHT } + noClickThrough { true } + } + + layer("search") { + position { INSET to TITLE_H } + size { WIDTH - INSET * 2 to SEARCH_H } + } + + layer("content") { + position { INSET to CONTENT_TOP } + size { WIDTH - INSET * 2 - SCROLL_W - 2 to HEIGHT - CONTENT_TOP - INSET } + scrollHeight { 0 } + events = (IfEvent.Op1.bitmask or IfEvent.Op10.bitmask).toInt() + } + + layer("scrollbar") { + position { WIDTH - INSET - SCROLL_W to CONTENT_TOP } + size { SCROLL_W to HEIGHT - CONTENT_TOP - INSET } + } + + layer("highlight") { + position { 0 to 0 } + size { WIDTH to HEIGHT } + } + } diff --git a/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltPackSettings.kt b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltPackSettings.kt new file mode 100644 index 000000000..33bd4471b --- /dev/null +++ b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltPackSettings.kt @@ -0,0 +1,34 @@ +package org.rsmod.content.other.toolbelt.pack + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.toml.TomlFactory +import java.io.File + +object ToolbeltPackSettings { + private val mapper = ObjectMapper(TomlFactory()) + + private const val PLUGIN = "content/other/toolbelt/src/main/resources" + + fun isEnabled(projectRoot: File = File("..")): Boolean { + val file = File(projectRoot, "$PLUGIN/toolbelt.toml") + if (!file.isFile) { + return false + } + return runCatching { + val root = mapper.readTree(file) + root.path("is_enabled").asBoolean(true) + }.getOrDefault(false) + } + + fun configDirectory(projectRoot: File = File("..")): File = + File(projectRoot, "$PLUGIN/pack/configs") + + fun spriteDirectory(projectRoot: File = File("..")): File = + File(projectRoot, "$PLUGIN/pack/sprites") + + fun scriptDirectory(projectRoot: File = File("..")): File = + File(projectRoot, "$PLUGIN/pack/scripts") + + fun cs2Directory(projectRoot: File = File("..")): File = + File(projectRoot, "$PLUGIN/pack/cs2") +} diff --git a/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltPluginPack.kt b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltPluginPack.kt new file mode 100644 index 000000000..c329c9bc6 --- /dev/null +++ b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltPluginPack.kt @@ -0,0 +1,33 @@ +package org.rsmod.content.other.toolbelt.pack + +import dev.openrune.cache.filestore.definition.InterfaceType +import dev.openrune.definition.dbtables.DBTable +import dev.openrune.pack.PluginPack +import java.io.File + +class ToolbeltPluginPack : PluginPack() { + override fun shouldAlwaysPack(): Boolean = true + + override fun isEnabled(projectRoot: File): Boolean = ToolbeltPackSettings.isEnabled(projectRoot) + + override fun packScriptDirectories(projectRoot: File): List { + if (!isEnabled(projectRoot)) { + return emptyList() + } + return listOf(ToolbeltPackSettings.scriptDirectory(projectRoot)) + } + + override fun configDirectories(projectRoot: File): List = + listOf(ToolbeltPackSettings.configDirectory(projectRoot)) + + override fun spriteDirectories(projectRoot: File): List = + listOf(ToolbeltPackSettings.spriteDirectory(projectRoot)) + + override fun cs2Directories(projectRoot: File): List = + listOf(ToolbeltPackSettings.cs2Directory(projectRoot)) + + override fun dbTables(): List = listOf(ToolbeltTable.slots()) + + override fun interfaces(): List = + listOf(buildWornItemsToolbelt(), buildToolbeltInterface()) +} diff --git a/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltTable.kt b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltTable.kt new file mode 100644 index 000000000..8efbbf99b --- /dev/null +++ b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/ToolbeltTable.kt @@ -0,0 +1,390 @@ +package org.rsmod.content.other.toolbelt.pack + +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +object ToolbeltTable { + const val COL_SLOT_INDEX = 0 + const val COL_DISPLAY_NAME = 1 + const val COL_ALLOWED_ITEMS = 2 + const val COL_REMOVABLE = 3 + const val COL_TIERED = 4 + const val COL_SKILL_STAT = 5 + const val COL_REQUIRE_LEVEL = 6 + const val COL_CATEGORY = 7 + const val COL_UNLOCK_HINT = 8 + const val COL_HAS_SETTINGS = 9 + const val COL_UNLOCK_BIT = 10 + const val COL_STORE_VARBIT = 11 + + fun slots() = + dbTable("dbtable.toolbelt_slots", serverOnly = false) { + column("slot_index", COL_SLOT_INDEX, VarType.INT) + column("display_name", COL_DISPLAY_NAME, VarType.STRING) + column("allowed_items", COL_ALLOWED_ITEMS, VarType.OBJ) + column("removable", COL_REMOVABLE, VarType.BOOLEAN) + column("tiered", COL_TIERED, VarType.BOOLEAN) + column("skill_stat", COL_SKILL_STAT, VarType.STAT) + column("require_level", COL_REQUIRE_LEVEL, VarType.INT) + column("category", COL_CATEGORY, VarType.INT) + column("unlock_hint", COL_UNLOCK_HINT, VarType.STRING) + column("has_settings", COL_HAS_SETTINGS, VarType.BOOLEAN) + column("unlock_bit", COL_UNLOCK_BIT, VarType.INT) + column("store_varbit", COL_STORE_VARBIT, VarType.VARP) + + row("dbrow.toolbelt_pickaxe") { + column(COL_SLOT_INDEX, 0) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_0") + column(COL_DISPLAY_NAME, "Pickaxe") + columnRSCM( + COL_ALLOWED_ITEMS, + "obj.bronze_pickaxe", + "obj.iron_pickaxe", + "obj.steel_pickaxe", + "obj.black_pickaxe", + "obj.mithril_pickaxe", + "obj.adamant_pickaxe", + "obj.rune_pickaxe", + "obj.dragon_pickaxe", + "obj.dragon_pickaxe_pretty", + "obj.infernal_pickaxe", + "obj.infernal_pickaxe_empty", + "obj.crystal_pickaxe", + "obj.crystal_pickaxe_inactive", + "obj.3a_pickaxe", + "obj.trailblazer_pickaxe", + "obj.trailblazer_pickaxe_empty", + "obj.trailblazer_pickaxe_no_infernal", + "obj.trailblazer_reloaded_pickaxe", + "obj.trailblazer_reloaded_pickaxe_empty", + "obj.trailblazer_reloaded_pickaxe_no_infernal", + ) + column(COL_REMOVABLE, true) + column(COL_TIERED, true) + columnRSCM(COL_SKILL_STAT, "stat.mining") + column(COL_CATEGORY, 0) + } + + row("dbrow.toolbelt_axe") { + column(COL_SLOT_INDEX, 1) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_1") + column(COL_DISPLAY_NAME, "Axe") + columnRSCM( + COL_ALLOWED_ITEMS, + "obj.bronze_axe", + "obj.iron_axe", + "obj.steel_axe", + "obj.black_axe", + "obj.mithril_axe", + "obj.adamant_axe", + "obj.rune_axe", + "obj.dragon_axe", + "obj.infernal_axe", + "obj.infernal_axe_empty", + "obj.crystal_axe", + "obj.crystal_axe_inactive", + "obj.3a_axe", + "obj.trailblazer_axe", + "obj.trailblazer_axe_empty", + "obj.trailblazer_axe_no_infernal", + "obj.trailblazer_reloaded_axe", + "obj.trailblazer_reloaded_axe_empty", + "obj.trailblazer_reloaded_axe_no_infernal", + ) + column(COL_REMOVABLE, true) + column(COL_TIERED, true) + columnRSCM(COL_SKILL_STAT, "stat.woodcutting") + column(COL_CATEGORY, 0) + } + + row("dbrow.toolbelt_hammer") { + column(COL_SLOT_INDEX, 2) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_2") + column(COL_DISPLAY_NAME, "Hammer") + columnRSCM( + COL_ALLOWED_ITEMS, + "obj.hammer", + "obj.imcando_hammer", + "obj.imcando_hammer_offhand", + ) + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 0) + } + + row("dbrow.toolbelt_chisel") { + column(COL_SLOT_INDEX, 3) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_3") + column(COL_DISPLAY_NAME, "Chisel") + columnRSCM(COL_ALLOWED_ITEMS, "obj.chisel") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 0) + } + + row("dbrow.toolbelt_knife") { + column(COL_SLOT_INDEX, 4) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_4") + column(COL_DISPLAY_NAME, "Knife") + columnRSCM(COL_ALLOWED_ITEMS, "obj.knife") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 0) + } + + row("dbrow.toolbelt_tinderbox") { + column(COL_SLOT_INDEX, 5) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_5") + column(COL_DISPLAY_NAME, "Tinderbox") + columnRSCM(COL_ALLOWED_ITEMS, "obj.tinderbox") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 0) + } + + row("dbrow.toolbelt_pestle_and_mortar") { + column(COL_SLOT_INDEX, 6) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_6") + column(COL_DISPLAY_NAME, "Pestle and mortar") + columnRSCM(COL_ALLOWED_ITEMS, "obj.pestle_and_mortar") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 0) + } + + row("dbrow.toolbelt_spade") { + column(COL_SLOT_INDEX, 7) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_7") + column(COL_DISPLAY_NAME, "Spade") + columnRSCM(COL_ALLOWED_ITEMS, "obj.spade") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 1) + } + + row("dbrow.toolbelt_rake") { + column(COL_SLOT_INDEX, 8) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_8") + column(COL_DISPLAY_NAME, "Rake") + columnRSCM(COL_ALLOWED_ITEMS, "obj.rake") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 1) + } + + row("dbrow.toolbelt_dibber") { + column(COL_SLOT_INDEX, 9) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_9") + column(COL_DISPLAY_NAME, "Seed dibber") + columnRSCM(COL_ALLOWED_ITEMS, "obj.dibber") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 1) + } + + row("dbrow.toolbelt_secateurs") { + column(COL_SLOT_INDEX, 10) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_10") + column(COL_DISPLAY_NAME, "Secateurs") + columnRSCM(COL_ALLOWED_ITEMS, "obj.secateurs", "obj.fairy_queen_secateurs") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 1) + } + + row("dbrow.toolbelt_gardening_trowel") { + column(COL_SLOT_INDEX, 11) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_11") + column(COL_DISPLAY_NAME, "Gardening trowel") + columnRSCM(COL_ALLOWED_ITEMS, "obj.gardening_trowel") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 1) + } + + row("dbrow.toolbelt_watering_can") { + column(COL_SLOT_INDEX, 12) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_12") + column(COL_DISPLAY_NAME, "Watering can") + columnRSCM( + COL_ALLOWED_ITEMS, + "obj.watering_can_0", + "obj.watering_can_1", + "obj.watering_can_2", + "obj.watering_can_3", + "obj.watering_can_4", + "obj.watering_can_5", + "obj.watering_can_6", + "obj.watering_can_7", + "obj.watering_can_8", + "obj.zeah_wateringcan", + ) + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 1) + } + + row("dbrow.toolbelt_shears") { + column(COL_SLOT_INDEX, 13) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_13") + column(COL_DISPLAY_NAME, "Shears") + columnRSCM(COL_ALLOWED_ITEMS, "obj.shears") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 0) + } + + row("dbrow.toolbelt_needle") { + column(COL_SLOT_INDEX, 14) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_14") + column(COL_DISPLAY_NAME, "Needle") + columnRSCM(COL_ALLOWED_ITEMS, "obj.needle") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 3) + } + + row("dbrow.toolbelt_glassblowing_pipe") { + column(COL_SLOT_INDEX, 15) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_15") + column(COL_DISPLAY_NAME, "Glassblowing pipe") + columnRSCM(COL_ALLOWED_ITEMS, "obj.glassblowingpipe") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 3) + } + + row("dbrow.toolbelt_small_net") { + column(COL_SLOT_INDEX, 16) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_16") + column(COL_DISPLAY_NAME, "Small fishing net") + columnRSCM(COL_ALLOWED_ITEMS, "obj.net") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 2) + } + + row("dbrow.toolbelt_big_net") { + column(COL_SLOT_INDEX, 17) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_17") + column(COL_DISPLAY_NAME, "Big fishing net") + columnRSCM(COL_ALLOWED_ITEMS, "obj.big_net") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 2) + } + + row("dbrow.toolbelt_fishing_rod") { + column(COL_SLOT_INDEX, 18) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_18") + column(COL_DISPLAY_NAME, "Fishing rod") + columnRSCM(COL_ALLOWED_ITEMS, "obj.fishing_rod") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 2) + } + + row("dbrow.toolbelt_fly_fishing_rod") { + column(COL_SLOT_INDEX, 19) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_19") + column(COL_DISPLAY_NAME, "Fly fishing rod") + columnRSCM(COL_ALLOWED_ITEMS, "obj.fly_fishing_rod") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 2) + } + + row("dbrow.toolbelt_harpoon") { + column(COL_SLOT_INDEX, 20) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_20") + column(COL_DISPLAY_NAME, "Harpoon") + columnRSCM( + COL_ALLOWED_ITEMS, + "obj.harpoon", + "obj.infernal_harpoon", + "obj.infernal_harpoon_empty", + ) + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 2) + } + + row("dbrow.toolbelt_lobster_pot") { + column(COL_SLOT_INDEX, 21) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_21") + column(COL_DISPLAY_NAME, "Lobster pot") + columnRSCM(COL_ALLOWED_ITEMS, "obj.lobster_pot") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 2) + } + + row("dbrow.toolbelt_amulet_mould") { + column(COL_SLOT_INDEX, 22) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_22") + column(COL_DISPLAY_NAME, "Amulet mould") + columnRSCM(COL_ALLOWED_ITEMS, "obj.amulet_mould") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 3) + } + + row("dbrow.toolbelt_necklace_mould") { + column(COL_SLOT_INDEX, 23) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_23") + column(COL_DISPLAY_NAME, "Necklace mould") + columnRSCM(COL_ALLOWED_ITEMS, "obj.necklace_mould") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 3) + } + + row("dbrow.toolbelt_ring_mould") { + column(COL_SLOT_INDEX, 24) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_24") + column(COL_DISPLAY_NAME, "Ring mould") + columnRSCM(COL_ALLOWED_ITEMS, "obj.ring_mould") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 3) + } + + row("dbrow.toolbelt_bracelet_mould") { + column(COL_SLOT_INDEX, 25) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_25") + column(COL_DISPLAY_NAME, "Bracelet mould") + columnRSCM(COL_ALLOWED_ITEMS, "obj.jewl_bracelet_mould") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 3) + } + + row("dbrow.toolbelt_tiara_mould") { + column(COL_SLOT_INDEX, 26) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_26") + column(COL_DISPLAY_NAME, "Tiara mould") + columnRSCM(COL_ALLOWED_ITEMS, "obj.tiara_mould") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 3) + } + + row("dbrow.toolbelt_bonecrusher") { + column(COL_SLOT_INDEX, 27) + columnRSCM(COL_STORE_VARBIT, "varp.toolbelt_slot_27") + column(COL_DISPLAY_NAME, "Bonecrusher") + columnRSCM(COL_ALLOWED_ITEMS, "obj.bonecrusher") + column(COL_REMOVABLE, true) + column(COL_TIERED, false) + column(COL_CATEGORY, 4) + columnRSCM(COL_SKILL_STAT, "stat.prayer") + column(COL_REQUIRE_LEVEL, 40) + column( + COL_UNLOCK_HINT, + "Speak to the Ghost Disciple at the Ectofuntus while carrying a bonecrusher and pay 2,000 Ectotokens to unlock adding it to your tool belt.", + ) + column(COL_HAS_SETTINGS, true) + column(COL_UNLOCK_BIT, 0) + } + } +} diff --git a/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/WornItems.kt b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/WornItems.kt new file mode 100644 index 000000000..0f61bf19b --- /dev/null +++ b/content/other/toolbelt/pack/src/main/kotlin/org/rsmod/content/other/toolbelt/pack/WornItems.kt @@ -0,0 +1,106 @@ +package org.rsmod.content.other.toolbelt.pack + +import dev.openrune.cache.tools.iftype.dsl.InterfaceBuilder +import dev.openrune.cache.tools.iftype.dsl.buildInterface +import dev.openrune.cache.tools.iftype.dsl.impl.graphic +import dev.openrune.cache.tools.iftype.dsl.impl.layer +import dev.openrune.definition.constants.ConstantProvider + +private const val BTN = 34 +private const val ICON = 26 +private const val BTN_Y = 212 +private const val START_X = 4 +private const val STEP = 37 + +const val WORN_WIDTH = 190 + +const val WORN_HEIGHT = 261 + +val WORN_FOOTER_VANILLA = listOf( + "component.wornitems:equipment", + "component.wornitems:equipment_icon", + "component.wornitems:pricechecker", + "component.wornitems:com_4", + "component.wornitems:deathkeep", + "component.wornitems:com_6", + "component.wornitems:call_follower", + "component.wornitems:com_8" +) + +val WORN_FOOTER_TOOLBELT_ROW = listOf( + "component.wornitems:tb_equipment", + "component.wornitems:tb_equipment_icon", + "component.wornitems:tb_pricechecker", + "component.wornitems:tb_pricechecker_icon", + "component.wornitems:tb_deathkeep", + "component.wornitems:tb_deathkeep_icon", + "component.wornitems:tb_call_follower", + "component.wornitems:tb_call_follower_icon", + "component.wornitems:tb_toolbelt", + "component.wornitems:tb_toolbelt_icon" +) + +fun buildWornItemsToolbelt() = + buildInterface(internalName = "interface.wornitems", width = WORN_WIDTH, height = WORN_HEIGHT) { + inherit("interface.wornitems") + + tbFooterButton("tb_equipment", "equipment", 0) + tbFooterIcon("tb_equipment_icon", "equipment_icon", 0) + + tbFooterButton("tb_pricechecker", "pricechecker", 1) + tbFooterIcon("tb_pricechecker_icon", "com_4", 1) + + tbFooterButton("tb_deathkeep", "deathkeep", 2) + tbFooterIcon("tb_deathkeep_icon", "com_6", 2) + + tbFooterButton("tb_call_follower", "call_follower", 3) + tbFooterIcon("tb_call_follower_icon", "com_8", 3) + + layer("tb_toolbelt") { + from("equipment") + hide { true } + position { btnPos(4) } + size { BTN to BTN } + addOption("View toolbelt") + } + graphic("tb_toolbelt_icon") { + from("com_8") + hide { true } + position { iconPos(4) } + size { ICON to ICON } + spriteId { ConstantProvider.getMapping("sprites.tool_belt") } + } + } + +private fun InterfaceBuilder.tbFooterButton( + name: String, + donor: String, + index: Int, +) { + layer(name) { + from(donor) + hide { true } + position { btnPos(index) } + size { BTN to BTN } + } +} + +private fun InterfaceBuilder.tbFooterIcon( + name: String, + donor: String, + index: Int, +) { + graphic(name) { + from(donor) + hide { true } + position { iconPos(index) } + size { ICON to ICON } + } +} + +fun btnPos(index: Int): Pair = (START_X + index * STEP) to BTN_Y + +fun iconPos(index: Int): Pair { + val pad = (BTN - ICON) / 2 + return (START_X + index * STEP + pad) to (BTN_Y + pad) +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/Toolbelt.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/Toolbelt.kt new file mode 100644 index 000000000..a8cf58f50 --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/Toolbelt.kt @@ -0,0 +1,236 @@ +package org.rsmod.content.other.toolbelt + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import org.rsmod.api.config.refs.params +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.table.ToolbeltSlotsRow +import org.rsmod.game.entity.Player +import org.rsmod.game.inv.InvObj + +object Toolbelt { + const val ADD_OPTION = "Add-to-toolbelt" + const val OWNED_COUNT = 1 + + private val settings: ToolbeltSettings + get() = ToolbeltSettings.load() + + fun isEnabled(): Boolean = settings.isEnabled + + fun allowHigherTierUnlock(): Boolean = settings.allowHigherTierUnlock + + fun validateStorageOnLogin(player: Player) { + if (!isEnabled()) { + return + } + for (slot in rows()) { + val index = storedItemIndex(player, slot) ?: continue + val type = slot.allowedItems.getOrNull(index) ?: run { + ToolbeltVars.clearSlot(player, slot.slotIndex) + continue + } + if (!slot.accepts(type)) { + ToolbeltVars.clearSlot(player, slot.slotIndex) + } + } + } + + fun slotFor(type: ItemServerType): ToolbeltSlotsRow? { + if (type.isCert || type.isPlaceholder || type.isDummyItem) { + return null + } + return ToolbeltSlotsRow.all().firstOrNull { it.accepts(type) } + } + + fun slotFor(internalName: String): ToolbeltSlotsRow? { + val name = + if (internalName.startsWith("obj.")) { + internalName + } else { + "obj.$internalName" + } + return ToolbeltSlotsRow.all().firstOrNull { it.accepts(name) } + } + + fun has(player: Player, slot: ToolbeltSlotsRow): Boolean = storedType(player, slot) != null + + fun storedType(player: Player, slot: ToolbeltSlotsRow): ItemServerType? { + val index = storedItemIndex(player, slot) ?: return null + return slot.allowedItems.getOrNull(index) + } + + fun displayType(player: Player, slot: ToolbeltSlotsRow): ItemServerType { + return storedType(player, slot) ?: slot.defaultItem + } + + fun provides(player: Player, itemInternal: String): Boolean { + if (!isEnabled()) { + return false + } + val type = ServerCacheManager.getItem(itemInternal.asObjId()) ?: return false + val slot = slotFor(type) ?: return false + if (!ToolbeltUnlocks.isUnlocked(player, slot)) { + return false + } + val stored = storedType(player, slot) ?: return false + if (stored.id == type.id) { + return true + } + if (!allowHigherTierUnlock() || !slot.tiered) { + return false + } + return slot.accepts(type) && + toolTier(type) <= toolTier(stored) && + meetsLevelReq(player, slot, type) + } + + fun providesContentGroup(player: Player, contentGroup: Int): Boolean { + if (!isEnabled() || contentGroup < 0) { + return false + } + return occupiedSlots(player).any { (_, type) -> type.contentGroup == contentGroup } + } + + fun resolveAxe(player: Player): InvObj? = + ToolbeltSlotsRow.getRow("dbrow.toolbelt_axe").let { resolveTieredTool(player, it) } + + fun resolvePickaxe(player: Player): InvObj? = + ToolbeltSlotsRow.getRow("dbrow.toolbelt_pickaxe").let { resolveTieredTool(player, it) } + + fun resolveExact(player: Player, slot: ToolbeltSlotsRow): InvObj? { + if (!isEnabled()) { + return null + } + val stored = storedType(player, slot) ?: return null + return InvObj(stored, OWNED_COUNT) + } + + fun occupiedSlots(player: Player): List> = + rows().mapNotNull { slot -> + storedType(player, slot)?.let { slot to it } + } + + sealed class AddResult { + data class Added(val item: ItemServerType) : AddResult() + + data class Replaced(val added: ItemServerType, val returned: ItemServerType) : AddResult() + + data class Failed(val message: String) : AddResult() + } + + fun tryAdd(player: Player, type: ItemServerType): AddResult = + addInternal(player, type, bypassRequirements = false) + + fun grantOwned(player: Player, type: ItemServerType, bypassRequirements: Boolean = false): AddResult = + addInternal(player, type, bypassRequirements = bypassRequirements) + + fun tryRemove(player: Player, slot: ToolbeltSlotsRow): ItemServerType? { + if (!isEnabled() || !slot.removable) { + return null + } + val existing = storedType(player, slot) ?: return null + ToolbeltVars.clearSlot(player, slot.slotIndex) + return existing + } + + fun allEligibleItems(): List = rows().flatMap { it.allowedItems } + + private fun addInternal( + player: Player, + type: ItemServerType, + bypassRequirements: Boolean, + ): AddResult { + if (!isEnabled()) { + return AddResult.Failed("The tool belt is currently disabled.") + } + val slot = + slotFor(type) ?: return AddResult.Failed("You can't add that to your tool belt.") + if (!bypassRequirements) { + if (!ToolbeltUnlocks.isUnlocked(player, slot)) { + return AddResult.Failed(ToolbeltUnlocks.unlockMessage(slot)) + } + if (!meetsSlotAddRequirement(player, slot)) { + val req = slot.requireLevel ?: 1 + val skill = slot.skillStat?.displayName?.ifBlank { null } ?: "skill" + return AddResult.Failed("You need a $skill level of $req to add that to your tool belt.") + } + } + + val existing = storedType(player, slot) + if (existing != null && existing.id == type.id) { + return AddResult.Failed("Your tool belt already contains that.") + } + + setStored(player, slot, type) + return if (existing != null) { + AddResult.Replaced(type, existing) + } else { + AddResult.Added(type) + } + } + + private fun setStored(player: Player, slot: ToolbeltSlotsRow, type: ItemServerType) { + val index = slot.allowedItems.indexOfFirst { it.id == type.id } + require(index >= 0) { "Item ${type.internalName} not in ${slot.displayName} slot list" } + ToolbeltVars.setStoredAllowedIndex(player, slot.slotIndex, index + 1) + } + + private fun storedItemIndex(player: Player, slot: ToolbeltSlotsRow): Int? { + val stored = ToolbeltVars.readStoredAllowedIndex(player, slot.slotIndex) + if (stored <= 0) { + return null + } + val index = stored - 1 + if (index !in slot.allowedItems.indices) { + return null + } + return index + } + + private fun rows(): List = + ToolbeltSlotsRow.all().sortedBy { it.slotIndex } + + private fun resolveTieredTool(player: Player, slot: ToolbeltSlotsRow): InvObj? { + if (!isEnabled()) { + return null + } + val stored = storedType(player, slot) ?: return null + if (!allowHigherTierUnlock() || !slot.tiered) { + return if (meetsLevelReq(player, slot, stored)) InvObj(stored, OWNED_COUNT) else null + } + + val storedTier = toolTier(stored) + val candidates = + slot.allowedItems + .filter { toolTier(it) <= storedTier } + .filter { meetsLevelReq(player, slot, it) } + + val best = + candidates.maxWithOrNull(compareBy { toolTier(it) }.thenBy { it.id }) + ?: return null + return InvObj(best, OWNED_COUNT) + } + + private fun toolTier(type: ItemServerType): Int = levelReq(type) + + private fun levelReq(type: ItemServerType): Int = type.paramOrNull(params.levelrequire) ?: 1 + + private fun meetsSlotAddRequirement(player: Player, slot: ToolbeltSlotsRow): Boolean { + val level = slot.requireLevel ?: return true + val skill = slot.skillStat ?: return true + return player.statBase(skill.internalName) >= level + } + + private fun meetsLevelReq( + player: Player, + slot: ToolbeltSlotsRow, + type: ItemServerType, + ): Boolean { + val skill = slot.skillStat ?: return true + return player.statBase(skill.internalName) >= levelReq(type) + } +} + +private fun String.asObjId(): Int = asRSCM(RSCMType.OBJ) diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltBonecrusher.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltBonecrusher.kt new file mode 100644 index 000000000..c1d635cb6 --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltBonecrusher.kt @@ -0,0 +1,144 @@ +package org.rsmod.content.other.toolbelt + +import org.rsmod.api.config.constants +import org.rsmod.api.invtx.invAdd +import org.rsmod.api.invtx.invDel +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.vars.boolVarBit +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.api.table.ToolbeltSlotsRow +import org.rsmod.game.entity.Player +import org.rsmod.game.inv.isType + +object ToolbeltBonecrusher { + const val ITEM = "obj.bonecrusher" + private const val ECTO_TOKEN = "obj.ectotoken" + private const val CHARGES_PER_TOKEN = 25 + private const val MAX_CHARGES = 60_000 + + private fun bonecrusherSlot(): ToolbeltSlotsRow? = Toolbelt.slotFor(ITEM) + + fun hasOnToolbelt(player: Player): Boolean { + val slot = bonecrusherSlot() ?: return false + return Toolbelt.has(player, slot) + } + + fun checkCharges(access: ProtectedAccess): Boolean { + if (!hasOnToolbelt(access.player)) { + return false + } + val charges = access.player.bonecrusherCharges + when { + charges == 0 -> + access.mes("The bonecrusher has no charges. It can be charged with ectotokens.") + isActivityEnabled(access.player) -> + access.mes( + "The bonecrusher has $charges charges. It is active and ready to crush bones.", + ) + else -> + access.mes( + "The bonecrusher has $charges charges. It has been deactivated, and will not crush bones now.", + ) + } + return true + } + + fun charge(access: ProtectedAccess): Boolean { + if (!hasOnToolbelt(access.player)) { + return false + } + val tokenSlot = access.inv.indexOfFirst { it?.isType(ECTO_TOKEN) == true } + if (tokenSlot < 0) { + access.mes("You need ectotokens to charge the bonecrusher.") + return true + } + val tokenCount = access.inv.count(ECTO_TOKEN) + val removed = access.invDel(access.inv, ECTO_TOKEN, count = tokenCount, slot = tokenSlot) + if (removed.failure) { + return true + } + val add = tokenCount * CHARGES_PER_TOKEN + val next = (access.player.bonecrusherCharges + add).coerceAtMost(MAX_CHARGES) + if (next == access.player.bonecrusherCharges) { + access.invAdd(access.inv, ECTO_TOKEN, count = tokenCount, strict = false) + access.mes("The bonecrusher cannot hold any more charges.") + return true + } + access.player.bonecrusherCharges = next + access.mes(chargeStatusMessage(access.player, access.player.bonecrusherCharges)) + return true + } + + fun toggleActivity(access: ProtectedAccess): Boolean { + if (!hasOnToolbelt(access.player)) { + return false + } + val enabled = toggleActivity(access.player) + access.mes(activityToggleMessage(enabled)) + return true + } + + suspend fun uncharge(access: ProtectedAccess): Boolean { + if (!hasOnToolbelt(access.player)) { + return false + } + val stored = access.player.bonecrusherCharges + when { + stored == 0 -> access.mes("The bonecrusher has no charges.") + stored < CHARGES_PER_TOKEN -> + access.mes("The bonecrusher does not have enough charges for you to remove any ectotokens.") + else -> { + val redeemableTokens = stored / CHARGES_PER_TOKEN + val chargesToRemove = redeemableTokens * CHARGES_PER_TOKEN + val added = access.invAdd(access.inv, ECTO_TOKEN, count = redeemableTokens) + if (added.failure) { + access.mes(constants.dm_invspace) + return true + } + val remaining = stored - chargesToRemove + access.player.bonecrusherCharges = remaining.coerceAtLeast(0) + if (remaining == 0) { + access.doubleobjbox( + ITEM, + ECTO_TOKEN, + "You remove all the charges from the bonecrusher.", + ) + } else { + access.doubleobjbox( + ITEM, + ECTO_TOKEN, + "The bonecrusher has $remaining charges left.", + ) + } + } + } + return true + } + + fun toggleActivity(player: Player): Boolean { + val next = !player.bonecrusherActivityEnabled + player.bonecrusherActivityEnabled = next + return next + } + + fun isActivityEnabled(player: Player): Boolean = player.bonecrusherActivityEnabled + + fun activityToggleMessage(enabled: Boolean): String = + if (enabled) { + "The bonecrusher is active and ready to crush bones." + } else { + "The bonecrusher has been deactivated, and will not crush bones now." + } + + private fun chargeStatusMessage(player: Player, charges: Int): String = + if (isActivityEnabled(player)) { + "The bonecrusher has $charges charges. It is active and ready to crush bones." + } else { + "The bonecrusher has $charges charges. It has been deactivated, and will not crush bones now." + } + + private var Player.bonecrusherActivityEnabled by boolVarBit("varbit.bonecrusher_activity_enabled") + + private var Player.bonecrusherCharges by intVarBit("varbit.charges_bonecrusher_quantity") +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltBonecrusherUnlock.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltBonecrusherUnlock.kt new file mode 100644 index 000000000..8b43dad6e --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltBonecrusherUnlock.kt @@ -0,0 +1,87 @@ +package org.rsmod.content.other.toolbelt + +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.table.ToolbeltSlotsRow +import org.rsmod.game.entity.Player +import org.rsmod.game.inv.InvObj +import org.rsmod.game.type.getInvObj + +object ToolbeltBonecrusherUnlock { + const val ECTO_TOKEN = "obj.ectotoken" + const val ECTO_TOKEN_COST = 2000 + + fun bonecrusherSlot(): ToolbeltSlotsRow? = Toolbelt.slotFor(ToolbeltBonecrusher.ITEM) + + fun canOfferUnlock(player: Player): Boolean { + if (!Toolbelt.isEnabled()) { + return false + } + val slot = bonecrusherSlot() ?: return false + if (ToolbeltUnlocks.isUnlocked(player, slot)) { + return false + } + if (!meetsSlotSkillRequirement(player, slot)) { + return false + } + return playerCarriesBonecrusher(player) + } + + fun playerCarriesBonecrusher(player: Player): Boolean { + if (player.inv.any { it.isBonecrusher() }) { + return true + } + return player.worn.any { it.isBonecrusher() } + } + + fun tryUnlock(access: ProtectedAccess): PayResult { + if (!Toolbelt.isEnabled()) { + return PayResult.NotEnabled + } + val slot = bonecrusherSlot() ?: return PayResult.NoSlot + if (ToolbeltUnlocks.isUnlocked(access.player, slot)) { + return PayResult.AlreadyUnlocked + } + if (!meetsSlotSkillRequirement(access.player, slot)) { + val required = slot.requireLevel ?: 1 + return PayResult.InsufficientPrayer(required) + } + if (!playerCarriesBonecrusher(access.player)) { + return PayResult.NeedsBonecrusher + } + val tokens = access.invContentTotal(access.inv, ECTO_TOKEN) + if (tokens < ECTO_TOKEN_COST) { + return PayResult.InsufficientTokens(tokens) + } + if (access.invDel(access.inv, ECTO_TOKEN, ECTO_TOKEN_COST).failure) { + return PayResult.InsufficientTokens(tokens) + } + ToolbeltUnlocks.setUnlocked(access.player, slot, unlocked = true) + return PayResult.Success + } + + private fun meetsSlotSkillRequirement(player: Player, slot: ToolbeltSlotsRow): Boolean { + val level = slot.requireLevel ?: return true + val skill = slot.skillStat ?: return true + return player.statBase(skill.internalName) >= level + } + + sealed class PayResult { + data object Success : PayResult() + + data object NotEnabled : PayResult() + + data object NoSlot : PayResult() + + data object AlreadyUnlocked : PayResult() + + data object NeedsBonecrusher : PayResult() + + data class InsufficientPrayer(val required: Int) : PayResult() + + data class InsufficientTokens(val held: Int) : PayResult() + } +} + +private fun InvObj?.isBonecrusher(): Boolean = + this != null && getInvObj(this).internalName == ToolbeltBonecrusher.ITEM diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltModule.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltModule.kt new file mode 100644 index 000000000..51e247678 --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltModule.kt @@ -0,0 +1,10 @@ +package org.rsmod.content.other.toolbelt + +import org.rsmod.api.inv.storage.PlayerItemStorageHook +import org.rsmod.plugin.module.PluginModule + +class ToolbeltModule : PluginModule() { + override fun bind() { + addSetBinding(ToolbeltStorageHook::class.java) + } +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltScript.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltScript.kt new file mode 100644 index 000000000..a1c6bf93f --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltScript.kt @@ -0,0 +1,461 @@ +package org.rsmod.content.other.toolbelt + +import dev.openrune.ServerCacheManager +import dev.openrune.definition.type.widget.IfEvent +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.aconverted.interf.IfButtonOp +import jakarta.inject.Inject +import org.rsmod.api.invtx.invAdd +import org.rsmod.api.invtx.invDel +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.output.objExamine +import org.rsmod.api.player.vars.resyncVar +import org.rsmod.api.script.onCommand +import org.rsmod.api.script.onIfOpen +import org.rsmod.api.script.onIfModalButton +import org.rsmod.api.script.onIfOverlayButton +import org.rsmod.api.script.onOpHeld1 +import org.rsmod.api.script.onOpHeld2 +import org.rsmod.api.script.onOpHeld3 +import org.rsmod.api.script.onOpHeld4 +import org.rsmod.api.script.onPlayerInit +import org.rsmod.api.table.ToolbeltSlotsRow +import org.rsmod.events.EventBus +import org.rsmod.game.cheat.Cheat +import org.rsmod.game.entity.Player +import org.rsmod.game.type.getInvObj +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class ToolbeltScript @Inject constructor( + val eventBus: EventBus, +) : PluginScript() { + override fun ScriptContext.startup() { + onIfOpen("interface.wornitems") { + ToolbeltWornItems.sync(player) + } + + onPlayerInit { + if (Toolbelt.isEnabled()) { + Toolbelt.validateStorageOnLogin(player) + } + ToolbeltWornItems.sync(player) + } + + onIfOverlayButton("component.wornitems:tb_toolbelt") { + if (Toolbelt.isEnabled()) { + openToolbelt() + } + } + + if (!Toolbelt.isEnabled()) { + return + } + + installAddOptions() + onIfModalButton(ToolbeltUi.CONTENT) { contentOp(it.comsub, it.op) } + onCommand("toolbelt") { + desc = "Tool belt: list|add|grant|remove|unlock|lock" + invalidArgs = + "Usage: ::toolbelt list | add | grant | remove | unlock|lock " + cheat { handleToolbeltCommand() } + } + } + + private fun ProtectedAccess.openToolbelt() { + player.syncToolbeltVarsToClient() + ifOpenMainModal(ToolbeltUi.INTERFACE) + ifSetEvents( + ToolbeltUi.CONTENT, + ToolbeltUi.ITEM_COM_BASE until (ToolbeltUi.ITEM_COM_BASE + ToolbeltUi.SLOT_COUNT), + IfEvent.Op1, + IfEvent.Op2, + IfEvent.Op3, + IfEvent.Op4, + IfEvent.Op5, + IfEvent.Op10, + ) + ifSetEvents( + ToolbeltUi.CONTENT, + ToolbeltUi.LOCK_COM_BASE until (ToolbeltUi.LOCK_COM_BASE + ToolbeltUi.SLOT_COUNT), + IfEvent.Op1, + ) + } + + private fun Player.syncToolbeltVarsToClient() { + resyncVar("varp.toolbelt_unlocks") + for (slot in 0 until ToolbeltVars.SLOT_COUNT) { + resyncVar(ToolbeltVars.varpName(slot)) + } + } + + private suspend fun ProtectedAccess.contentOp(comsub: Int, op: IfButtonOp) { + val slotIndex = ToolbeltUi.slotFromComsub(comsub) ?: return + val slot = + ToolbeltSlotsRow.all().firstOrNull { it.slotIndex == slotIndex } ?: return + if (!ToolbeltUnlocks.isUnlocked(player, slot)) { + when (op) { + IfButtonOp.Op1 -> showSlotInfo(slot) + IfButtonOp.Op10 -> examineSlot(slot) + else -> Unit + } + return + } + when (op) { + IfButtonOp.Op10 -> examineSlot(slot) + IfButtonOp.Op1 -> primarySlotOp(slot) + IfButtonOp.Op2, + IfButtonOp.Op3, + IfButtonOp.Op4, + IfButtonOp.Op5, + -> bonecrusherSlotOp(slot, op) + else -> Unit + } + } + + private suspend fun ProtectedAccess.bonecrusherSlotOp(slot: ToolbeltSlotsRow, op: IfButtonOp) { + if (slot.slotIndex != ToolbeltUi.BONE_CRUSHER_SLOT || !Toolbelt.has(player, slot)) { + return + } + when (op) { + IfButtonOp.Op2 -> ToolbeltBonecrusher.checkCharges(this) + IfButtonOp.Op3 -> ToolbeltBonecrusher.charge(this) + IfButtonOp.Op4 -> ToolbeltBonecrusher.toggleActivity(this) + IfButtonOp.Op5 -> ToolbeltBonecrusher.uncharge(this) + else -> Unit + } + } + + private fun ProtectedAccess.examineSlot(slot: ToolbeltSlotsRow) { + val type = Toolbelt.displayType(player, slot) + player.objExamine(type, Toolbelt.OWNED_COUNT, 0) + } + + private fun ProtectedAccess.primarySlotOp(slot: ToolbeltSlotsRow) { + if (Toolbelt.has(player, slot)) { + removeFromSlot(slot) + } + } + + private suspend fun ProtectedAccess.showSlotInfo(slot: ToolbeltSlotsRow) { + ToolbeltSlotInfo.show(this, slot, eventBus) + } + + private fun ProtectedAccess.removeFromSlot(slot: ToolbeltSlotsRow) { + val removed = Toolbelt.tryRemove(player, slot) + if (removed == null) { + mes("You can't remove that from your tool belt.") + return + } + val given = + invAdd( + inv, + removed.internalName, + 1, + strict = false, + ignoreVirtualStorage = true, + ) + if (given.completed() <= 0) { + Toolbelt.tryAdd(player, removed) + mes("You need inventory space to remove that from your tool belt.") + return + } + mes("You remove the ${removed.name} from your tool belt.") + } + + private fun ScriptContext.installAddOptions() { + for (type in Toolbelt.allEligibleItems()) { + val slotIndex = type.interfaceOptions.indexOfFirst { it == Toolbelt.ADD_OPTION } + if (slotIndex < 0) { + continue + } + val internal = type.internalName + when (slotIndex) { + 0 -> onOpHeld1(internal) { addFromInventory(it.slot) } + 1 -> onOpHeld2(internal) { addFromInventory(it.slot) } + 2 -> onOpHeld3(internal) { addFromInventory(it.slot) } + 3 -> onOpHeld4(internal) { addFromInventory(it.slot) } + } + } + } + + private fun ProtectedAccess.addFromInventory(invSlot: Int) { + val obj = inv[invSlot] ?: return + val type = getInvObj(obj) + val slot = Toolbelt.slotFor(type) + if (slot == null) { + mes("You can't add that to your tool belt.") + return + } + + when (val result = Toolbelt.tryAdd(player, type)) { + is Toolbelt.AddResult.Failed -> mes(result.message) + is Toolbelt.AddResult.Added -> { + if ( + invDel(inv, type.internalName, 1, slot = invSlot, ignoreVirtualStorage = true) + .failure + ) { + Toolbelt.tryRemove(player, slot) + mes("You don't have enough inventory space.") + return + } + mes("You add the ${type.name} to your tool belt.") + player.syncToolbeltVarsToClient() + } + is Toolbelt.AddResult.Replaced -> { + if ( + invDel(inv, type.internalName, 1, slot = invSlot, ignoreVirtualStorage = true) + .failure + ) { + Toolbelt.tryAdd(player, result.returned) + mes("You don't have enough inventory space.") + return + } + val given = + invAdd( + inv, + result.returned.internalName, + 1, + strict = false, + ignoreVirtualStorage = true, + ) + if (given.completed() <= 0) { + invAdd(inv, type.internalName, 1, strict = false, ignoreVirtualStorage = true) + Toolbelt.tryAdd(player, result.returned) + mes("You don't have enough inventory space to swap tools.") + return + } + mes( + "You add the ${result.added.name} to your tool belt and receive your ${result.returned.name} back.", + ) + player.syncToolbeltVarsToClient() + } + } + } + + private fun Cheat.handleToolbeltCommand() { + when (args.getOrNull(0)?.lowercase()) { + null, + "list" -> listToolbelt() + "add" -> addFromFirstSlot() + "grant" -> grantToSlot() + "remove", + "take", -> removeSlot() + "unlock" -> setSlotUnlock(locked = false) + "lock" -> setSlotUnlock(locked = true) + else -> + player.mes( + "Usage: ::toolbelt list | add | grant | remove | unlock|lock ", + ) + } + } + + private fun Cheat.setSlotUnlock(locked: Boolean) { + val key = args.getOrNull(1) + if (key.isNullOrBlank()) { + player.mes("Usage: ::toolbelt ${if (locked) "lock" else "unlock"} ") + listUnlockableSlots() + return + } + val slot = slotArg(1) + if (slot == null) { + player.mes("Unknown slot '${args.getOrNull(1)}'.") + return + } + if (slot.unlockBit == null) { + player.mes("The ${slot.displayName} slot is not lockable.") + return + } + ToolbeltUnlocks.setUnlocked(player, slot, unlocked = !locked) + player.mes( + if (locked) { + "Locked the ${slot.displayName.lowercase()} tool belt slot." + } else { + "Unlocked the ${slot.displayName.lowercase()} tool belt slot." + }, + ) + } + + private fun Cheat.listUnlockableSlots() { + val unlockable = ToolbeltUnlocks.slotsWithUnlock() + if (unlockable.isEmpty()) { + return + } + player.mes("Unlockable slots: " + unlockable.joinToString(", ") { "${it.slotIndex} ${it.displayName}" }) + } + + private fun Cheat.listToolbelt() { + val occupied = Toolbelt.occupiedSlots(player) + if (occupied.isEmpty()) { + player.mes("Your tool belt is empty.") + return + } + player.mes("Tool belt contents:") + for ((slot, type) in occupied) { + player.mes("- ${slot.displayName}: ${type.name}") + } + } + + private fun Cheat.grantToSlot() { + val arg = args.getOrNull(1)?.trim().orEmpty() + if (arg.isEmpty()) { + player.mes("Usage: ::toolbelt grant ") + return + } + val slotIndex = arg.toIntOrNull() + val slot = slotArg(1) + val type = + when { + slotIndex != null && slot != null -> slot.defaultItem + else -> { + val name = if (arg.startsWith("obj.")) arg else "obj.$arg" + ServerCacheManager.getItem(name.asRSCM(RSCMType.OBJ)) + } + } + if (type == null) { + player.mes("Unknown item or slot '$arg'.") + return + } + val resolvedSlot = Toolbelt.slotFor(type) + if (resolvedSlot == null) { + player.mes("That item cannot go on the tool belt.") + return + } + when (val result = Toolbelt.grantOwned(player, type, bypassRequirements = true)) { + is Toolbelt.AddResult.Failed -> player.mes(result.message) + is Toolbelt.AddResult.Added -> { + player.mes("Granted ${type.name} to your tool belt.") + player.syncToolbeltVarsToClient() + } + is Toolbelt.AddResult.Replaced -> { + player.mes("Granted ${type.name} (replaced ${result.returned.name}).") + player.syncToolbeltVarsToClient() + } + } + } + + private fun Cheat.addFromFirstSlot() { + val invSlot = + player.inv.indexOfFirst { obj -> + obj != null && Toolbelt.slotFor(getInvObj(obj)) != null + } + if (invSlot < 0) { + player.mes("You need a toolbelt-compatible tool in your inventory.") + return + } + val obj = player.inv[invSlot] ?: return + val type = getInvObj(obj) + val slot = Toolbelt.slotFor(type) ?: return + + when (val result = Toolbelt.tryAdd(player, type)) { + is Toolbelt.AddResult.Failed -> player.mes(result.message) + is Toolbelt.AddResult.Added -> { + if ( + player + .invDel( + player.inv, + type.internalName, + 1, + slot = invSlot, + ignoreVirtualStorage = true, + ) + .failure + ) { + Toolbelt.tryRemove(player, slot) + player.mes("Failed to remove the tool from your inventory.") + return + } + player.mes("You add the ${type.name} to your tool belt.") + } + is Toolbelt.AddResult.Replaced -> { + if ( + player + .invDel( + player.inv, + type.internalName, + 1, + slot = invSlot, + ignoreVirtualStorage = true, + ) + .failure + ) { + Toolbelt.tryAdd(player, result.returned) + player.mes("Failed to remove the tool from your inventory.") + return + } + val given = + player.invAdd( + player.inv, + result.returned.internalName, + 1, + strict = false, + ignoreVirtualStorage = true, + ) + if (given.completed() <= 0) { + player.invAdd( + player.inv, + type.internalName, + 1, + strict = false, + ignoreVirtualStorage = true, + ) + Toolbelt.tryAdd(player, result.returned) + player.mes("You don't have enough inventory space to swap tools.") + return + } + player.mes( + "You add the ${result.added.name} to your tool belt and receive your ${result.returned.name} back.", + ) + } + } + } + + private fun Cheat.removeSlot() { + val key = args.getOrNull(1) + if (key.isNullOrBlank()) { + player.mes("Usage: ::toolbelt remove ") + player.mes( + "Slots: " + + ToolbeltSlotsRow.all().sortedBy { it.slotIndex }.joinToString { "${it.slotIndex}=${it.displayName}" }, + ) + return + } + val slot = slotArg(1) + if (slot == null) { + player.mes("Unknown slot '${args.getOrNull(1)}'.") + return + } + val removed = Toolbelt.tryRemove(player, slot) + if (removed == null) { + player.mes("Your tool belt does not have a ${slot.displayName.lowercase()}.") + return + } + val given = + player.invAdd( + player.inv, + removed.internalName, + 1, + strict = false, + ignoreVirtualStorage = true, + ) + if (given.completed() <= 0) { + Toolbelt.tryAdd(player, removed) + player.mes("You need inventory space to remove that from your tool belt.") + return + } + player.mes("You remove the ${removed.name} from your tool belt.") + } + + private fun Cheat.slotArg(index: Int): ToolbeltSlotsRow? { + val arg = args.getOrNull(index)?.trim().orEmpty() + if (arg.isEmpty()) { + return null + } + arg.toIntOrNull()?.let { slotIndex -> + return ToolbeltSlotsRow.all().firstOrNull { it.slotIndex == slotIndex } + } + return Toolbelt.slotFor(arg) + } +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSettings.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSettings.kt new file mode 100644 index 000000000..8e49a3afb --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSettings.kt @@ -0,0 +1,34 @@ +package org.rsmod.content.other.toolbelt + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.toml.TomlFactory +import com.fasterxml.jackson.module.kotlin.readValue +import com.fasterxml.jackson.module.kotlin.registerKotlinModule + +@JsonIgnoreProperties(ignoreUnknown = true) +data class ToolbeltSettings( + @JsonProperty("is_enabled") val isEnabled: Boolean = true, + @JsonProperty("allow_higher_tier_unlock") val allowHigherTierUnlock: Boolean = true, +) { + companion object { + private const val RESOURCE = "toolbelt.toml" + + private val mapper: ObjectMapper = ObjectMapper(TomlFactory()).registerKotlinModule() + + @Volatile private var cached: ToolbeltSettings? = null + + fun load(): ToolbeltSettings { + cached?.let { + return it + } + val stream = + ToolbeltSettings::class.java.classLoader.getResourceAsStream(RESOURCE) + ?: error("Missing plugin config resource: $RESOURCE") + return stream.use { input -> + mapper.readValue(input).also { cached = it } + } + } + } +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSlotInfo.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSlotInfo.kt new file mode 100644 index 000000000..fcc62c007 --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSlotInfo.kt @@ -0,0 +1,68 @@ +package org.rsmod.content.other.toolbelt + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.definition.type.widget.IfEvent +import org.rsmod.api.config.constants +import org.rsmod.api.player.output.runClientScript +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.ui.ifCloseOverlay +import org.rsmod.api.player.ui.ifSetEvents +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.api.table.ToolbeltSlotsRow +import org.rsmod.events.EventBus +import org.rsmod.game.entity.Player + +object ToolbeltSlotInfo { + private const val HIGHLIGHT = "component.toolbelt:highlight" + private const val SCREEN_HIGHLIGHT = "interface.screenhighlight" + + private const val CS_HIGHLIGHT_START = 3408 + private const val CS_HIGHLIGHT_SCREEN = 3409 + private const val CS_HIGHLIGHT_TEXTBOX = 3413 + private const val CS_HIGHLIGHT_HIDE = 3412 + + private var Player.tutorialPage by intVarBit("varbit.hnt_hint_step") + private var Player.tutorialTotalPages by intVarBit("varbit.hnt_hint_max_step") + + suspend fun show(access: ProtectedAccess, slot: ToolbeltSlotsRow, eventBus: EventBus) { + val player = access.player + val text = ToolbeltUnlocks.unlockMessage(slot) + val highlight = HIGHLIGHT.asRSCM(RSCMType.COMPONENT) + + player.tutorialTotalPages = 1 + player.tutorialPage = 0 + + player.runClientScript(CS_HIGHLIGHT_START, highlight) + access.ifOpenOverlay(SCREEN_HIGHLIGHT, HIGHLIGHT) + player.runClientScript( + CS_HIGHLIGHT_SCREEN, + highlight, + 0, + 0, + 0, + 0, + constants.setpos_abs_right, + constants.setpos_abs_bottom, + 100, + 1, + ) + player.runClientScript( + CS_HIGHLIGHT_TEXTBOX, + text, + -1, + 1, + 0, + highlight, + ) + access.ifSetEvents("component.screenhighlight:continue", 9..9, IfEvent.Op1) + access.ifSetEvents("component.screenhighlight:previous", 9..9, IfEvent.Op1) + access.ifSetEvents("component.screenhighlight:pausebutton", -1..-1, IfEvent.PauseButton) + try { + access.pauseButton() + } finally { + player.runClientScript(CS_HIGHLIGHT_HIDE, highlight) + player.ifCloseOverlay(SCREEN_HIGHLIGHT, eventBus) + } + } +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSlots.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSlots.kt new file mode 100644 index 000000000..f05dd5d6d --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltSlots.kt @@ -0,0 +1,13 @@ +package org.rsmod.content.other.toolbelt + +import dev.openrune.types.ItemServerType +import org.rsmod.api.table.ToolbeltSlotsRow + +val ToolbeltSlotsRow.defaultItem: ItemServerType + get() = allowedItems.first() + +fun ToolbeltSlotsRow.accepts(internalName: String): Boolean = + allowedItems.any { it.internalName == internalName } + +fun ToolbeltSlotsRow.accepts(type: ItemServerType): Boolean = + allowedItems.any { it.id == type.id } diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltStorageHook.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltStorageHook.kt new file mode 100644 index 000000000..7ac3efd96 --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltStorageHook.kt @@ -0,0 +1,26 @@ +package org.rsmod.content.other.toolbelt + +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.inv.storage.PlayerItemStorageContext +import org.rsmod.api.inv.storage.PlayerItemStorageHook +import org.rsmod.api.inv.storage.VirtualItemConsumePolicy +import org.rsmod.game.entity.Player + +@Singleton +internal class ToolbeltStorageHook @Inject constructor() : PlayerItemStorageHook { + override val consumePolicy: VirtualItemConsumePolicy = VirtualItemConsumePolicy.InventoryFirst + + override fun shouldProcess(ctx: PlayerItemStorageContext): Boolean = + Toolbelt.isEnabled() && Toolbelt.provides(ctx.player, ctx.itemInternal) + + override fun contains(ctx: PlayerItemStorageContext): Int = + if (Toolbelt.provides(ctx.player, ctx.itemInternal)) 1 else 0 + + override fun remove(ctx: PlayerItemStorageContext, amount: Int): Int = 0 + + override fun add(ctx: PlayerItemStorageContext, amount: Int): Int = 0 + + override fun providesContentGroup(player: Player, contentGroup: Int): Boolean = + Toolbelt.providesContentGroup(player, contentGroup) +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltUi.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltUi.kt new file mode 100644 index 000000000..46552bf5a --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltUi.kt @@ -0,0 +1,17 @@ +package org.rsmod.content.other.toolbelt + +object ToolbeltUi { + const val INTERFACE = "interface.toolbelt" + const val CONTENT = "component.toolbelt:content" + const val SLOT_COUNT = 28 + const val ITEM_COM_BASE = 300 + const val LOCK_COM_BASE = 400 + const val BONE_CRUSHER_SLOT = 27 + + fun slotFromComsub(comsub: Int): Int? = when (comsub) { + in ITEM_COM_BASE until (ITEM_COM_BASE + SLOT_COUNT) -> comsub - ITEM_COM_BASE + in LOCK_COM_BASE until (LOCK_COM_BASE + SLOT_COUNT) -> comsub - LOCK_COM_BASE + in 0 until SLOT_COUNT -> comsub + else -> null + } +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltUnlocks.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltUnlocks.kt new file mode 100644 index 000000000..ef79cc602 --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltUnlocks.kt @@ -0,0 +1,39 @@ +package org.rsmod.content.other.toolbelt + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import org.rsmod.api.player.vars.VarPlayerIntMapSetter +import org.rsmod.api.table.ToolbeltSlotsRow +import org.rsmod.game.entity.Player + +object ToolbeltUnlocks { + private const val UNLOCK_VARP = "varp.toolbelt_unlocks" + + fun isUnlocked(player: Player, slot: ToolbeltSlotsRow): Boolean { + val bit = slot.unlockBit ?: return true + val varp = ServerCacheManager.getVarp(UNLOCK_VARP.asRSCM(RSCMType.VARP)) ?: return true + return player.vars[varp] and (1 shl bit) != 0 + } + + fun setUnlocked(player: Player, slot: ToolbeltSlotsRow, unlocked: Boolean) { + val bit = slot.unlockBit ?: return + val varp = ServerCacheManager.getVarp(UNLOCK_VARP.asRSCM(RSCMType.VARP)) ?: return + val flag = 1 shl bit + val current = player.vars[varp] + val value = + if (unlocked) { + current or flag + } else { + current and flag.inv() + } + VarPlayerIntMapSetter.set(player, varp, value) + } + + fun unlockMessage(slot: ToolbeltSlotsRow): String = + slot.unlockHint?.takeIf { it.isNotBlank() } + ?: "You cannot add this tool to your tool belt yet." + + fun slotsWithUnlock(): List = + ToolbeltSlotsRow.all().filter { it.unlockBit != null } +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltVars.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltVars.kt new file mode 100644 index 000000000..1335f8f46 --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltVars.kt @@ -0,0 +1,31 @@ +package org.rsmod.content.other.toolbelt + +import org.rsmod.api.player.vars.VarPlayerIntMapSetter +import org.rsmod.game.entity.Player + +object ToolbeltVars { + const val EMPTY_STORED = 0 + const val SLOT_COUNT = 28 + + fun varBitName(slotIndex: Int): String { + require(slotIndex in 0 until SLOT_COUNT) { "Invalid toolbelt slot: $slotIndex" } + return "varbit.toolbelt_slot_$slotIndex" + } + + fun varpName(slotIndex: Int): String { + require(slotIndex in 0 until SLOT_COUNT) { "Invalid toolbelt slot: $slotIndex" } + return "varp.toolbelt_slot_$slotIndex" + } + + fun readStoredAllowedIndex(player: Player, slotIndex: Int): Int = + player.vars[varBitName(slotIndex)] + + fun setStoredAllowedIndex(player: Player, slotIndex: Int, allowedIndex: Int) { + val value = if (allowedIndex <= EMPTY_STORED) EMPTY_STORED else allowedIndex + VarPlayerIntMapSetter.set(player, varBitName(slotIndex), value) + } + + fun clearSlot(player: Player, slotIndex: Int) { + setStoredAllowedIndex(player, slotIndex, EMPTY_STORED) + } +} diff --git a/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltWornItems.kt b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltWornItems.kt new file mode 100644 index 000000000..57b220f2e --- /dev/null +++ b/content/other/toolbelt/src/main/kotlin/org/rsmod/content/other/toolbelt/ToolbeltWornItems.kt @@ -0,0 +1,18 @@ +package org.rsmod.content.other.toolbelt + +import org.rsmod.api.player.ui.ifSetHide +import org.rsmod.content.other.toolbelt.pack.WORN_FOOTER_TOOLBELT_ROW +import org.rsmod.content.other.toolbelt.pack.WORN_FOOTER_VANILLA +import org.rsmod.game.entity.Player + +object ToolbeltWornItems { + fun sync(player: Player) { + val enabled = Toolbelt.isEnabled() + for (name in WORN_FOOTER_VANILLA) { + player.ifSetHide(name, hide = enabled) + } + for (name in WORN_FOOTER_TOOLBELT_ROW) { + player.ifSetHide(name, hide = !enabled) + } + } +} diff --git a/content/other/toolbelt/src/main/resources/gamevals.toml b/content/other/toolbelt/src/main/resources/gamevals.toml new file mode 100644 index 000000000..2c4f01852 --- /dev/null +++ b/content/other/toolbelt/src/main/resources/gamevals.toml @@ -0,0 +1,114 @@ +[gamevals.interface] +toolbelt = 15000 + +[gamevals.clientscript] +toolbelt_init = 45400 +toolbelt_build = 45401 +toolbelt_stored_for_row = 45402 +toolbelt_draw = 45403 +toolbelt_count_category = 45404 +toolbelt_filter = 45405 + +[gamevals.dbtable] +toolbelt_slots = 55521 + +[gamevals.enum] +toolbelt_categories = 65541 + +[gamevals.dbrow] +toolbelt_pickaxe = 55300 +toolbelt_axe = 55301 +toolbelt_hammer = 55302 +toolbelt_chisel = 55303 +toolbelt_knife = 55304 +toolbelt_tinderbox = 55305 +toolbelt_pestle_and_mortar = 55306 +toolbelt_spade = 55307 +toolbelt_rake = 55308 +toolbelt_dibber = 55309 +toolbelt_secateurs = 55310 +toolbelt_gardening_trowel = 55311 +toolbelt_watering_can = 55312 +toolbelt_shears = 55313 +toolbelt_needle = 55314 +toolbelt_glassblowing_pipe = 55315 +toolbelt_small_net = 55316 +toolbelt_big_net = 55317 +toolbelt_fishing_rod = 55318 +toolbelt_fly_fishing_rod = 55319 +toolbelt_harpoon = 55320 +toolbelt_lobster_pot = 55321 +toolbelt_amulet_mould = 55322 +toolbelt_necklace_mould = 55323 +toolbelt_ring_mould = 55324 +toolbelt_bracelet_mould = 55325 +toolbelt_tiara_mould = 55326 +toolbelt_bonecrusher = 55327 + +[gamevals.varp] +toolbelt_unlocks = 65489 +toolbelt_slot_0 =65488 +toolbelt_slot_1 =65487 +toolbelt_slot_2 =65486 +toolbelt_slot_3 =65480 +toolbelt_slot_4 =65479 +toolbelt_slot_5 =65478 +toolbelt_slot_6 =65477 +toolbelt_slot_7 =65476 +toolbelt_slot_8 =65475 +toolbelt_slot_9 =65474 +toolbelt_slot_10 =65473 +toolbelt_slot_11 =65472 +toolbelt_slot_12 =65471 +toolbelt_slot_13 =65470 +toolbelt_slot_14 =65469 +toolbelt_slot_15 =65468 +toolbelt_slot_16 =65467 +toolbelt_slot_17 =65466 +toolbelt_slot_18 =65465 +toolbelt_slot_19 =65464 +toolbelt_slot_20 =65463 +toolbelt_slot_21 =65462 +toolbelt_slot_22 =65461 +toolbelt_slot_23 =65460 +toolbelt_slot_24 =65459 +toolbelt_slot_25 =65458 +toolbelt_slot_26 =65457 +toolbelt_slot_27 =65456 + +[gamevals.varbit] +toolbelt_slot_0 =65525 +toolbelt_slot_1 =65524 +toolbelt_slot_2 =65523 +toolbelt_slot_3 =65522 +toolbelt_slot_4 =65521 +toolbelt_slot_5 =65520 +toolbelt_slot_6 =65458 +toolbelt_slot_7 =65456 +toolbelt_slot_8 =65455 +toolbelt_slot_9 =65454 +toolbelt_slot_10 =65453 +toolbelt_slot_11 =65452 +toolbelt_slot_12 =65451 +toolbelt_slot_13 =65450 +toolbelt_slot_14 =65449 +toolbelt_slot_15 =65448 +toolbelt_slot_16 =65447 +toolbelt_slot_17 =65446 +toolbelt_slot_18 =65445 +toolbelt_slot_19 =65444 +toolbelt_slot_20 =65443 +toolbelt_slot_21 =65442 +toolbelt_slot_22 =65441 +toolbelt_slot_23 =65440 +toolbelt_slot_24 =65439 +toolbelt_slot_25 =65438 +toolbelt_slot_26 =65437 +toolbelt_slot_27 =65436 + +[gamevals.sprites] +tool_belt = 21535 +square_button = 21536 +square_button_hover = 21537 +square_button_clicked = 21538 +square_button_disabled = 21539 \ No newline at end of file diff --git a/content/other/toolbelt/src/main/resources/pack/configs/enum.toml b/content/other/toolbelt/src/main/resources/pack/configs/enum.toml new file mode 100644 index 000000000..d907cdf80 --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/configs/enum.toml @@ -0,0 +1,11 @@ +[[enum]] +id = "enum.toolbelt_categories" +keyType = "INT" +valueType = "STRING" + +[enum.values] +0 = "General" +1 = "Farming" +2 = "Fishing" +3 = "Crafting" +4 = "Miscellaneous" diff --git a/content/other/toolbelt/src/main/resources/pack/configs/varbit.toml b/content/other/toolbelt/src/main/resources/pack/configs/varbit.toml new file mode 100644 index 000000000..1d25b5c1c --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/configs/varbit.toml @@ -0,0 +1,168 @@ +[[varbit]] +id = "varbit.toolbelt_slot_0" +varp = "varp.toolbelt_slot_0" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_1" +varp = "varp.toolbelt_slot_1" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_2" +varp = "varp.toolbelt_slot_2" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_3" +varp = "varp.toolbelt_slot_3" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_4" +varp = "varp.toolbelt_slot_4" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_5" +varp = "varp.toolbelt_slot_5" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_6" +varp = "varp.toolbelt_slot_6" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_7" +varp = "varp.toolbelt_slot_7" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_8" +varp = "varp.toolbelt_slot_8" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_9" +varp = "varp.toolbelt_slot_9" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_10" +varp = "varp.toolbelt_slot_10" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_11" +varp = "varp.toolbelt_slot_11" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_12" +varp = "varp.toolbelt_slot_12" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_13" +varp = "varp.toolbelt_slot_13" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_14" +varp = "varp.toolbelt_slot_14" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_15" +varp = "varp.toolbelt_slot_15" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_16" +varp = "varp.toolbelt_slot_16" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_17" +varp = "varp.toolbelt_slot_17" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_18" +varp = "varp.toolbelt_slot_18" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_19" +varp = "varp.toolbelt_slot_19" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_20" +varp = "varp.toolbelt_slot_20" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_21" +varp = "varp.toolbelt_slot_21" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_22" +varp = "varp.toolbelt_slot_22" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_23" +varp = "varp.toolbelt_slot_23" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_24" +varp = "varp.toolbelt_slot_24" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_25" +varp = "varp.toolbelt_slot_25" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_26" +varp = "varp.toolbelt_slot_26" +startBit = 0 +endBit = 15 + +[[varbit]] +id = "varbit.toolbelt_slot_27" +varp = "varp.toolbelt_slot_27" +startBit = 0 +endBit = 15 + diff --git a/content/other/toolbelt/src/main/resources/pack/configs/varp.toml b/content/other/toolbelt/src/main/resources/pack/configs/varp.toml new file mode 100644 index 000000000..c4084603c --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/configs/varp.toml @@ -0,0 +1,115 @@ +[[varp]] +id = "varp.toolbelt_unlocks" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_0" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_1" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_2" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_3" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_4" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_5" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_6" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_7" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_8" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_9" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_10" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_11" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_12" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_13" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_14" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_15" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_16" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_17" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_18" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_19" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_20" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_21" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_22" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_23" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_24" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_25" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_26" +scope = "Perm" + +[[varp]] +id = "varp.toolbelt_slot_27" +scope = "Perm" diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/script/[clientscript,toolbelt_filter].cs2 b/content/other/toolbelt/src/main/resources/pack/cs2/script/[clientscript,toolbelt_filter].cs2 new file mode 100644 index 000000000..7adaead43 --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/script/[clientscript,toolbelt_filter].cs2 @@ -0,0 +1,189 @@ +[clientscript,toolbelt_filter](component $content, component $search, component $scrollbar, component $border) +def_string $needle = ""; +def_obj $obj = null; +def_int $slot = 0; +def_int $item = 0; +def_int $y = 0; +def_int $step = 40; +def_boolean $match = false; +def_boolean $any_general = false; +def_boolean $any_farming = false; +def_boolean $any_fishing = false; +def_boolean $any_crafting = false; +def_boolean $any_misc = false; +def_int $cat = 0; +def_int $c = 0; +def_int $header = 0; +def_boolean $any = false; +def_int $cols = 0; +def_int $col = 0; +def_int $bg = 0; +def_int $lock = 0; +def_int $x = 0; +def_dbrow $row = null; +def_obj $ghost = null; +def_boolean $owned = false; +def_int $stored = 0; +def_int $allowed = 0; +def_boolean $locked = false; +if (cc_find($search, 3) = true) { + $needle = lowercase(~string_trim(removetags(cc_gettext))); +} +if (compare($needle, "") = 0) { + ~toolbelt_build($content, $scrollbar); + ~toolbelt_draw($content, $border); + return; +} +while ($slot < 28) { + $match = false; + $item = calc(300 + $slot); + $obj = null; + $ghost = null; + $owned = false; + $row = null; + if (db_find(toolbelt_slots:slot_index, $slot) = 1) { + $row = db_findnext; + if ($row ! null) { + if (db_getfieldcount($row, toolbelt_slots:allowed_items) > 0) { + $ghost = db_getfield($row, toolbelt_slots:allowed_items, 0); + } + } + } + $stored = 0; + if ($row ! null) { + $stored = ~toolbelt_stored_for_row($row); + if ($stored > 0) { + $allowed = db_getfieldcount($row, toolbelt_slots:allowed_items); + if ($stored <= $allowed) { + $obj = db_getfield($row, toolbelt_slots:allowed_items, calc($stored - 1)); + $owned = true; + } + } + } + if ($owned = false) { + $obj = $ghost; + } + if (cc_find($content, $item) = true) { + if ($obj ! null & string_indexof_string(lowercase(oc_name($obj)), $needle, 0) ! -1) { + $match = true; + } + if ($match = true) { + cc_sethide(false); + if (cc_find($content, calc(200 + $slot)) = true) { + cc_sethide(false); + } + $lock = calc(400 + $slot); + $locked = false; + if ($slot = 27 & testbit(%toolbelt_unlocks, 0) = ^false) { + $locked = true; + } + if (cc_find($content, $lock) = true) { + if ($locked = true) { + cc_sethide(false); + } else { + cc_sethide(true); + } + } + if ($row ! null) { + $c = db_getfield($row, toolbelt_slots:category, 0); + if ($c = 0) { + $any_general = true; + } else if ($c = 1) { + $any_farming = true; + } else if ($c = 2) { + $any_fishing = true; + } else if ($c = 3) { + $any_crafting = true; + } else { + $any_misc = true; + } + } + } else { + cc_sethide(true); + if (cc_find($content, calc(200 + $slot)) = true) { + cc_sethide(true); + } + if (cc_find($content, calc(400 + $slot)) = true) { + cc_sethide(true); + } + } + } + $slot = calc($slot + 1); +} +while ($cat < 5) { + $header = calc(100 + $cat); + $cols = ~toolbelt_count_category($cat); + if ($cols > 8) { + $cols = 8; + } + if ($cat = 0) { + $any = $any_general; + } else if ($cat = 1) { + $any = $any_farming; + } else if ($cat = 2) { + $any = $any_fishing; + } else if ($cat = 3) { + $any = $any_crafting; + } else { + $any = $any_misc; + } + if ($any = false) { + if (cc_find($content, $header) = true) { + cc_sethide(true); + } + } else { + if (cc_find($content, $header) = true) { + cc_sethide(false); + cc_setposition(0, $y, ^setpos_abs_left, ^setpos_abs_top); + } + $y = calc($y + 18 + 6); + $col = 0; + $slot = 0; + while ($slot < 28) { + if (db_find(toolbelt_slots:slot_index, $slot) = 1) { + $row = db_findnext; + if ($row ! null & db_getfield($row, toolbelt_slots:category, 0) = $cat) { + $item = calc(300 + $slot); + $bg = calc(200 + $slot); + $lock = calc(400 + $slot); + if (cc_find($content, $item) = true & cc_gethide = false) { + $x = calc($col * $step); + cc_setposition($x, $y, ^setpos_abs_left, ^setpos_abs_top); + if (cc_find($content, $bg) = true) { + cc_sethide(false); + cc_setposition($x, $y, ^setpos_abs_left, ^setpos_abs_top); + } + if (cc_find($content, $lock) = true) { + if ($slot = 27 & testbit(%toolbelt_unlocks, 0) = ^false) { + cc_sethide(false); + cc_setposition(calc($x + 6), calc($y + 2), ^setpos_abs_left, ^setpos_abs_top); + } else { + cc_sethide(true); + } + } + $col = calc($col + 1); + if ($col >= $cols) { + $col = 0; + $y = calc($y + $step); + } + } else if (cc_find($content, $bg) = true) { + cc_sethide(true); + } + } + } + $slot = calc($slot + 1); + } + if ($col ! 0) { + $y = calc($y + $step); + } + $y = calc($y + 6); + } + $cat = calc($cat + 1); +} +if ($y > if_getheight($content)) { + if_setscrollsize(0, $y, $content); + ~scrollbar_vertical($scrollbar, $content, "scrollbar_dragger_v2,3", "scrollbar_dragger_v2,0", "scrollbar_dragger_v2,1", "scrollbar_dragger_v2,2", "scrollbar_v2,0", "scrollbar_v2,1"); +} else { + if_setscrollsize(0, 0, $content); + cc_deleteall($scrollbar); +} diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/script/[clientscript,toolbelt_init].cs2 b/content/other/toolbelt/src/main/resources/pack/cs2/script/[clientscript,toolbelt_init].cs2 new file mode 100644 index 000000000..d152dc9f4 --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/script/[clientscript,toolbelt_init].cs2 @@ -0,0 +1,43 @@ +[clientscript,toolbelt_init](component $border, component $search, component $content, component $scrollbar) +if (cc_find($content, 100) = true) { + ~toolbelt_draw($content, $border); + return; +} +def_component $universe = if_getlayer($border); +if_setposition(0, 0, ^setpos_abs_centre, ^setpos_abs_centre, $universe); +~stoneborder($border, "Toolbelt", 0); +cc_deleteall($search); +cc_create($search, ^iftype_graphic, 0); +cc_setsize(8, 0, ^setsize_minus, ^setsize_minus); +cc_setposition(0, 0, ^setpos_abs_centre, ^setpos_abs_centre); +cc_setgraphic("ge_textbackdrop,1"); +cc_create($search, ^iftype_graphic, 1); +cc_setsize(4, 0, ^setsize_abs, ^setsize_minus); +cc_setposition(0, 0, ^setpos_abs_left, ^setpos_abs_centre); +cc_setgraphic("ge_textbackdrop,0"); +cc_create($search, ^iftype_graphic, 2); +cc_setsize(4, 0, ^setsize_abs, ^setsize_minus); +cc_setposition(0, 0, ^setpos_abs_right, ^setpos_abs_centre); +cc_setgraphic("ge_textbackdrop,2"); +cc_create($search, ^iftype_inputfield, 3); +cc_setposition(4, 4, ^setpos_abs_left, ^setpos_abs_top); +cc_setsize(8, 8, ^setsize_minus, ^setsize_minus); +cc_input_setlinewrappingwidth(cc_getwidth); +cc_input_setacceptmode(0); +cc_settextfont(p12_full); +cc_setcolour(^white); +cc_settextshadow(true); +cc_settextalign(^settextalign_left, ^settextalign_top, 16); +cc_input_setselectcolour(^black); +cc_input_setselectbgcolour(^white); +cc_input_setwrapmode(0); +cc_input_setsubmitmode(0); +cc_input_setlinecountlimit(1); +cc_input_setplaceholdertext("Search tools..."); +cc_input_setplaceholdertextcolour(0x808080); +cc_settext(""); +cc_input_setonchange("toolbelt_filter($content, $search, $scrollbar, $border)"); +cc_input_setonfocuschanged("toolbelt_filter($content, $search, $scrollbar, $border)"); +~toolbelt_build($content, $scrollbar); +~toolbelt_draw($content, $border); +if_setonvartransmit("toolbelt_init($border, $search, $content, $scrollbar){toolbelt_unlocks,toolbelt_slot_0,toolbelt_slot_1,toolbelt_slot_2,toolbelt_slot_3,toolbelt_slot_4,toolbelt_slot_5,toolbelt_slot_6,toolbelt_slot_7,toolbelt_slot_8,toolbelt_slot_9,toolbelt_slot_10,toolbelt_slot_11,toolbelt_slot_12,toolbelt_slot_13,toolbelt_slot_14,toolbelt_slot_15,toolbelt_slot_16,toolbelt_slot_17,toolbelt_slot_18,toolbelt_slot_19,toolbelt_slot_20,toolbelt_slot_21,toolbelt_slot_22,toolbelt_slot_23,toolbelt_slot_24,toolbelt_slot_25,toolbelt_slot_26,toolbelt_slot_27}", $content); diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_build].cs2 b/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_build].cs2 new file mode 100644 index 000000000..1eff6101d --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_build].cs2 @@ -0,0 +1,82 @@ +[proc,toolbelt_build](component $content, component $scrollbar) +cc_deleteall($content); +def_int $y = 0; +def_int $step = 40; +def_int $cat = 0; +def_int $cols = 0; +def_int $max = 0; +def_int $col = 0; +def_int $x = 0; +def_int $slot = 0; +def_int $bg = 0; +def_int $item = 0; +def_int $lock = 0; +def_int $header = 0; +def_string $name = ""; +def_string $title = ""; +def_dbrow $row = null; +while ($cat < 5) { + $max = ~toolbelt_count_category($cat); + $header = calc(100 + $cat); + $cols = $max; + if ($cols > 8) { + $cols = 8; + } + $name = enum(int, string, toolbelt_categories, $cat); + $title = "<$name> - 0/"; + cc_create($content, ^iftype_text, $header); + cc_setposition(0, $y, ^setpos_abs_left, ^setpos_abs_top); + cc_setsize(0, 18, ^setsize_minus, ^setsize_abs); + cc_settextfont(b12_full); + cc_settextshadow(true); + cc_settextalign(^settextalign_left, ^settextalign_centre, 0); + cc_setcolour(0xffcb1f); + cc_settext($title); + $y = calc($y + 18 + 6); + $col = 0; + $slot = 0; + while ($slot < 28) { + if (db_find(toolbelt_slots:slot_index, $slot) = 1) { + $row = db_findnext; + if ($row ! null & db_getfield($row, toolbelt_slots:category, 0) = $cat) { + $bg = calc(200 + $slot); + $item = calc(300 + $slot); + $lock = calc(400 + $slot); + $x = calc($col * $step); + cc_create($content, ^iftype_graphic, $bg); + cc_setsize(36, 36, ^setsize_abs, ^setsize_abs); + cc_setposition($x, $y, ^setpos_abs_left, ^setpos_abs_top); + cc_setgraphic(square_button_disabled); + cc_create($content, ^iftype_graphic, $item); + cc_setsize(36, 36, ^setsize_abs, ^setsize_abs); + cc_setposition($x, $y, ^setpos_abs_left, ^setpos_abs_top); + cc_setoutline(1); + cc_setgraphicshadow(0x000000); + cc_setonop("cc_settrans_temporarily(event_com, event_comsubid, 125, 0)"); + cc_create($content, ^iftype_graphic, $lock); + cc_setposition(calc($x + 6), calc($y + 2), ^setpos_abs_left, ^setpos_abs_top); + cc_setgraphic("magic_spell_padlocks,0"); + cc_setsize(30, 30, ^setsize_abs, ^setsize_abs); + cc_sethide(true); + $col = calc($col + 1); + if ($col >= $cols) { + $col = 0; + $y = calc($y + $step); + } + } + } + $slot = calc($slot + 1); + } + if ($col ! 0) { + $y = calc($y + $step); + } + $y = calc($y + 6); + $cat = calc($cat + 1); +} +if ($y > if_getheight($content)) { + if_setscrollsize(0, $y, $content); + ~scrollbar_vertical($scrollbar, $content, "scrollbar_dragger_v2,3", "scrollbar_dragger_v2,0", "scrollbar_dragger_v2,1", "scrollbar_dragger_v2,2", "scrollbar_v2,0", "scrollbar_v2,1"); +} else { + if_setscrollsize(0, 0, $content); + cc_deleteall($scrollbar); +} diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_count_category].cs2 b/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_count_category].cs2 new file mode 100644 index 000000000..f530ea5fd --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_count_category].cs2 @@ -0,0 +1,18 @@ +[proc,toolbelt_count_category](int $cat)(int) +def_int $count = 0; +def_int $slot = 0; +def_dbrow $row = null; +def_int $row_cat = 0; +while ($slot < 28) { + if (db_find(toolbelt_slots:slot_index, $slot) = 1) { + $row = db_findnext; + if ($row ! null) { + $row_cat = db_getfield($row, toolbelt_slots:category, 0); + if ($row_cat = $cat) { + $count = calc($count + 1); + } + } + } + $slot = calc($slot + 1); +} +return($count); diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_draw].cs2 b/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_draw].cs2 new file mode 100644 index 000000000..4a0eb5cdc --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_draw].cs2 @@ -0,0 +1,156 @@ +[proc,toolbelt_draw](component $content, component $border) +def_int $slot = 0; +def_int $bg = 0; +def_int $item = 0; +def_int $lock = 0; +def_int $slot_x = 0; +def_int $slot_y = 0; +def_obj $obj = null; +def_obj $ghost = null; +def_boolean $empty = false; +def_boolean $locked = false; +def_boolean $owned = false; +def_int $owned_total = 0; +def_int $general = 0; +def_int $farming = 0; +def_int $fishing = 0; +def_int $crafting = 0; +def_int $misc = 0; +def_int $cat = 0; +def_int $stored = 0; +def_int $allowed = 0; +def_dbrow $row = null; +while ($slot < 28) { + $row = null; + $bg = calc(200 + $slot); + $item = calc(300 + $slot); + $lock = calc(400 + $slot); + $ghost = null; + $obj = null; + $owned = false; + $locked = false; + if ($slot = 27) { + if (testbit(%toolbelt_unlocks, 0) = ^false) { + $locked = true; + } + } + if (db_find(toolbelt_slots:slot_index, $slot) = 1) { + $row = db_findnext; + if ($row ! null) { + if (db_getfieldcount($row, toolbelt_slots:allowed_items) > 0) { + $ghost = db_getfield($row, toolbelt_slots:allowed_items, 0); + } + } + } + $stored = 0; + if ($row ! null) { + $stored = ~toolbelt_stored_for_row($row); + if ($stored > 0) { + $allowed = db_getfieldcount($row, toolbelt_slots:allowed_items); + if ($stored <= $allowed) { + $obj = db_getfield($row, toolbelt_slots:allowed_items, calc($stored - 1)); + $owned = true; + } + } + } + if ($owned = false) { + $obj = $ghost; + } + if ($obj ! null) { + $empty = true; + if ($owned = true & $locked = false) { + $empty = false; + } + if (cc_find($content, $bg) = true) { + $slot_x = cc_getx; + $slot_y = cc_gety; + cc_sethide(false); + if ($empty = true | $locked = true) { + cc_setgraphic(square_button_disabled); + } else { + cc_setgraphic(square_button); + } + cc_setonmouseover(null); + cc_setonmouseleave(null); + cc_setonop(null); + } + if (cc_find($content, $item) = true) { + cc_sethide(false); + cc_setopbase(""); + cc_setop(1, ""); + cc_setop(2, ""); + cc_setop(10, "Examine"); + if ($locked = true) { + cc_setobject($obj, -1); + cc_setoutline(1); + cc_settrans(140); + cc_setop(1, "Info"); + } else if ($empty = true) { + cc_setobject($obj, -1); + cc_setoutline(1); + cc_settrans(140); + } else { + cc_setobject($obj, 1); + cc_setoutline(1); + cc_setop(1, "Remove"); + cc_settrans(0); + if ($slot = 27) { + cc_setop(2, "Check"); + cc_setop(3, "Charge"); + cc_setop(4, "Activity"); + cc_setop(5, "Uncharge"); + } + $owned_total = calc($owned_total + 1); + $cat = db_getfield($row, toolbelt_slots:category, 0); + if ($cat = 0) { + $general = calc($general + 1); + } else if ($cat = 1) { + $farming = calc($farming + 1); + } else if ($cat = 2) { + $fishing = calc($fishing + 1); + } else if ($cat = 4) { + $misc = calc($misc + 1); + } else { + $crafting = calc($crafting + 1); + } + } + } + if ($locked = true) { + if (cc_find($content, $lock) = true) { + cc_sethide(false); + cc_setposition(calc($slot_x + 6), calc($slot_y + 2), ^setpos_abs_left, ^setpos_abs_top); + cc_setgraphic("magic_spell_padlocks,0"); + cc_setsize(30, 30, ^setsize_abs, ^setsize_abs); + } + } else if (cc_find($content, $lock) = true) { + cc_sethide(true); + } + } + $slot = calc($slot + 1); +} +def_int $ci = 0; +def_string $name = ""; +def_int $max = 0; +def_int $have = 0; +while ($ci < 5) { + if (cc_find($content, calc(100 + $ci)) = true) { + $name = enum(int, string, toolbelt_categories, $ci); + $max = ~toolbelt_count_category($ci); + if ($ci = 0) { + $have = $general; + } else if ($ci = 1) { + $have = $farming; + } else if ($ci = 2) { + $have = $fishing; + } else if ($ci = 3) { + $have = $crafting; + } else { + $have = $misc; + } + cc_settext("<$name> - /"); + } + $ci = calc($ci + 1); +} +if (cc_find($border, 1) = true) { + cc_settext("Toolbelt - /28"); +} diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_stored_for_row].cs2 b/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_stored_for_row].cs2 new file mode 100644 index 000000000..d7e58ce7a --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/script/[proc,toolbelt_stored_for_row].cs2 @@ -0,0 +1,32 @@ +[proc,toolbelt_stored_for_row](dbrow $row)(int) +switch_dbrow ($row) { + case toolbelt_pickaxe : return(%toolbelt_slot_0); + case toolbelt_axe : return(%toolbelt_slot_1); + case toolbelt_hammer : return(%toolbelt_slot_2); + case toolbelt_chisel : return(%toolbelt_slot_3); + case toolbelt_knife : return(%toolbelt_slot_4); + case toolbelt_tinderbox : return(%toolbelt_slot_5); + case toolbelt_pestle_and_mortar : return(%toolbelt_slot_6); + case toolbelt_spade : return(%toolbelt_slot_7); + case toolbelt_rake : return(%toolbelt_slot_8); + case toolbelt_dibber : return(%toolbelt_slot_9); + case toolbelt_secateurs : return(%toolbelt_slot_10); + case toolbelt_gardening_trowel : return(%toolbelt_slot_11); + case toolbelt_watering_can : return(%toolbelt_slot_12); + case toolbelt_shears : return(%toolbelt_slot_13); + case toolbelt_needle : return(%toolbelt_slot_14); + case toolbelt_glassblowing_pipe : return(%toolbelt_slot_15); + case toolbelt_small_net : return(%toolbelt_slot_16); + case toolbelt_big_net : return(%toolbelt_slot_17); + case toolbelt_fishing_rod : return(%toolbelt_slot_18); + case toolbelt_fly_fishing_rod : return(%toolbelt_slot_19); + case toolbelt_harpoon : return(%toolbelt_slot_20); + case toolbelt_lobster_pot : return(%toolbelt_slot_21); + case toolbelt_amulet_mould : return(%toolbelt_slot_22); + case toolbelt_necklace_mould : return(%toolbelt_slot_23); + case toolbelt_ring_mould : return(%toolbelt_slot_24); + case toolbelt_bracelet_mould : return(%toolbelt_slot_25); + case toolbelt_tiara_mould : return(%toolbelt_slot_26); + case toolbelt_bonecrusher : return(%toolbelt_slot_27); + case default : return(0); +} diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/symbols/clientscript.sym b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/clientscript.sym new file mode 100644 index 000000000..de368da80 --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/clientscript.sym @@ -0,0 +1,6 @@ +45400 [clientscript,toolbelt_init] +45401 [proc,toolbelt_build] +45402 [proc,toolbelt_stored_for_row] +45403 [proc,toolbelt_draw] +45404 [proc,toolbelt_count_category] +45405 [clientscript,toolbelt_filter] diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/symbols/enum.sym b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/enum.sym new file mode 100644 index 000000000..02e96fb8a --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/enum.sym @@ -0,0 +1 @@ +65541 toolbelt_categories diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/symbols/graphic.sym b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/graphic.sym new file mode 100644 index 000000000..f9c94e9bb --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/graphic.sym @@ -0,0 +1 @@ +740 emotes_locked,0 diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/symbols/sprite.sym b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/sprite.sym new file mode 100644 index 000000000..685aa29ea --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/sprite.sym @@ -0,0 +1,4 @@ +21536 square_button +21537 square_button_hover +21538 square_button_clicked +21539 square_button_disabled diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/symbols/varbit.sym b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/varbit.sym new file mode 100644 index 000000000..6577e2776 --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/varbit.sym @@ -0,0 +1,28 @@ +65525 toolbelt_slot_0 int +65524 toolbelt_slot_1 int +65523 toolbelt_slot_2 int +65522 toolbelt_slot_3 int +65521 toolbelt_slot_4 int +65520 toolbelt_slot_5 int +65458 toolbelt_slot_6 int +65456 toolbelt_slot_7 int +65455 toolbelt_slot_8 int +65454 toolbelt_slot_9 int +65453 toolbelt_slot_10 int +65452 toolbelt_slot_11 int +65451 toolbelt_slot_12 int +65450 toolbelt_slot_13 int +65449 toolbelt_slot_14 int +65448 toolbelt_slot_15 int +65447 toolbelt_slot_16 int +65446 toolbelt_slot_17 int +65445 toolbelt_slot_18 int +65444 toolbelt_slot_19 int +65443 toolbelt_slot_20 int +65442 toolbelt_slot_21 int +65441 toolbelt_slot_22 int +65440 toolbelt_slot_23 int +65439 toolbelt_slot_24 int +65438 toolbelt_slot_25 int +65437 toolbelt_slot_26 int +65436 toolbelt_slot_27 int diff --git a/content/other/toolbelt/src/main/resources/pack/cs2/symbols/varp.sym b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/varp.sym new file mode 100644 index 000000000..fc4c563f8 --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/cs2/symbols/varp.sym @@ -0,0 +1,29 @@ +65489 toolbelt_unlocks int +65488 toolbelt_slot_0 int +65487 toolbelt_slot_1 int +65486 toolbelt_slot_2 int +65480 toolbelt_slot_3 int +65479 toolbelt_slot_4 int +65478 toolbelt_slot_5 int +65477 toolbelt_slot_6 int +65476 toolbelt_slot_7 int +65475 toolbelt_slot_8 int +65474 toolbelt_slot_9 int +65473 toolbelt_slot_10 int +65472 toolbelt_slot_11 int +65471 toolbelt_slot_12 int +65470 toolbelt_slot_13 int +65469 toolbelt_slot_14 int +65468 toolbelt_slot_15 int +65467 toolbelt_slot_16 int +65466 toolbelt_slot_17 int +65465 toolbelt_slot_18 int +65464 toolbelt_slot_19 int +65463 toolbelt_slot_20 int +65462 toolbelt_slot_21 int +65461 toolbelt_slot_22 int +65460 toolbelt_slot_23 int +65459 toolbelt_slot_24 int +65458 toolbelt_slot_25 int +65457 toolbelt_slot_26 int +65456 toolbelt_slot_27 int diff --git a/content/other/toolbelt/src/main/resources/pack/scripts/toolbelt-items.rp b/content/other/toolbelt/src/main/resources/pack/scripts/toolbelt-items.rp new file mode 100644 index 000000000..89175ca82 --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/scripts/toolbelt-items.rp @@ -0,0 +1,18 @@ +[after_db.toolbelt_items] +for obj in db.toolbelt_slots.allowed_items { + val ops = oc.iops(obj) + + var anchor = ops.indexOfFirst { it == "Wear" || it == "Wield" } + if (anchor == -1) { + anchor = ops.indexOfFirst { it == "Use" } + } + + var slot = ops.indexOfFirstBlank(anchor + 1) + if (slot == -1) { + slot = ops.indexOfFirstBlank() + } + + if (slot != -1) { + oc.addiop(obj, "Add-to-toolbelt", slot) + } +} diff --git a/content/other/toolbelt/src/main/resources/pack/sprites/manifest.toml b/content/other/toolbelt/src/main/resources/pack/sprites/manifest.toml new file mode 100644 index 000000000..891a24dbe --- /dev/null +++ b/content/other/toolbelt/src/main/resources/pack/sprites/manifest.toml @@ -0,0 +1,5 @@ +tool_belt = "sprites.tool_belt" +square_button = "sprites.square_button" +square_button_hover = "sprites.square_button_hover" +square_button_clicked = "sprites.square_button_clicked" +square_button_disabled = "sprites.square_button_disabled" diff --git a/content/other/toolbelt/src/main/resources/pack/sprites/square_button.png b/content/other/toolbelt/src/main/resources/pack/sprites/square_button.png new file mode 100644 index 0000000000000000000000000000000000000000..f06e17d909511c317274ea2a258919702071e777 GIT binary patch literal 50177 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 z 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 literal 0 HcmV?d00001 diff --git a/content/other/toolbelt/src/main/resources/pack/sprites/square_button_disabled.png b/content/other/toolbelt/src/main/resources/pack/sprites/square_button_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..077560fc5b248828edae7899a5967dbe44ceb740 GIT binary patch literal 49540 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

VesCA&k6 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�YG_BM2C|r;zC{B6RSCOx}`N{nggL+{r|y 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

e|cfueE+oGgS4vvYB)Lrm)uLDb^fkbbAXNVa zS`Z+0|AM{dL%P53{gBZL?V$zObADff@h?@q#WUwzsT6fvY##4+T zMfmDk!jBf`a0KoHqeg~W@y-HIw}rys0y3{%U)Ur67TM`H&gTLD(w z33SC!(?3<&LVa3(-pIH0nL@UAguulYDzs(jFR3)4?_dQUL2OTjw2O*9q;bvkGM6xv zN^sqW@&pe|<$L63clp__?2!0R?wMgK&;iv4)M{mj#xZnv64ZhKtp(W2T=2u(pwpeC zOYZ)XZ5CDa(~t~&H5mE(@G= zV`Vf*$gfXg_&lSF$}Q#c`9Z(k?n@zyd!tuahK+sx|v{T3bFW&;%4 z{Xz!O|BQtlz#iD{5<;)#OLLdm7CWK~ve0we&lad_e$Uxbl4w3Xxs23LmiWN*Lr`0F z-7=Xuani=U2fA>NlBDXJ9~isb9)~d=hgNh^gWPw6GDMkAroK|SYHm^NmT&nP4o=`L zEZZF$blPavc3OhK{>r<(cI|QC+uX zHN5)7b4kwiu3TV<=Gp0`-$1!7Hg4G*_tk6YbN&nB#zGK>TvF~yu+e@CdR2HDgSn9} zBe&YTa~i<9bU^Qck#Sd$N9U#wecikD#(Xvj$H%T*n|4d?86tbQJmhP)+OYLp(7}Gz+sDt+v^luWJ5r zQpcB{H-B>8I_7V&NRaq$@y~=pCXy^uEJpQObtdzLJ(JX0 z$rDTb<@;NMcQQ68WL1A(oSh|3^uEOxdew<^x-G7`xDyr<7fL}PwXRL>xlJV8o1P!w zm51MF;zy6X^55A4;rq6BwFHLbUGY25$;w=Uz7%>Zu36P}lrz^H(M$(O)Xu}3i=Q1C zh9(|W>Q_BEyU*9z(yd%YUxDpjwu0SmUMdWGWscKBLc(gDf!JGtzrb8p%>*ZRvapwd zhC4JpRhGZe1@FWN#RVP-ZDc!)80Xfb823hi^bgj#(JI`qGKjjd^~is5m<(8jEgU_N z2h3>tfp?@8oA76$D$>nE6ISJJtbD;!cwa)l9K8Chdw<32TYWCdb3CnUtlmOio-k@N z70S%ld5Hz@JP6V1h`QKZzj%%$vgyH_fFmuOluYM`7b;HO0=l1agegl_OlunjvhEcZ;0ukAQ3e zwg1l!L0qz^=U8Y4%~k=B+wZsUvH#<@wO=aS9;8kAZJhGddDXS`5n8Imk`N-lI%5rF zj9TJH2+j#sV9hCT`dOuRMY+e5c+*!pCQ+o9ly&kwZ$UxR^eJ#WkmNSzK*!{;PmB?5#PCcvoj|di+VkWud zJrS~Vmt>_GTk*G{wn$WKY(ZuDsXmUUd10@AbYt!ITRhZu1Znz)wdml}i&YE<;yuvj zd!?-i+1eKzpWBHE68sl zM@TXYi6)?EE>PYzK}WA)y!X|1cXk(he}@K}LZT)_%j>h_k!cpy9NTc?bXau+9edRYy_^P4`cfK++qXP8DF=RsL zWwc@T_AdlNFZ%-L#f?OD9+|^{mE5o|K6!6UTEPD2pT~j(U!Il7m8}?s%nXNl%2^e4 z5e5G4(r#Zlfh{f)kAqN|)!U$CXFRb_DdK~HoXB5HNXc|9IIuxy$tqO0`p6b?mHs!i zb2%Bgjyv|zTF)~OFZ6t;OgJ2KVzt9Hh-Pw)?yb~ZD*E7I*D3 z`{>YUbZpIH=d5A`kJXjw65&?pf^R8=`KYl>V5Qs2vAg=Rnkt71-K-raN3(AAD2433=y3iu<(O1rXKxXSq<=?E;wJ6UdobR z3jBQC>P06sv5%d;ANY4NUdP=H{BITayZHZ-iklPXdhnd~rp5owv>6K!zqFQhQD3aJ zxy&J>^hw#Q9aVnR2<0nD*Vo|v#|Z~Pp-}jP180pWXk;CGmKNsjo|S=N;i{X&&e@qa z0gXm`hmHD{vK!6|7)JukmPLmwpG0FM{biJ?6{b|4tqu25y!W!N`J{vi{CBU$$I4fVxV-*fU z9JnP`$SZH`s<Odbx`)bo@(z z%Y+taXD*RmU0}9P?K$)ST?L+c<87+~@gAK-BnG z_dVE^Tlit^d*lH;_q;Pif;o}lNDN}MKyx^i`&sX?)t&JQbc&(EBBMy2-Euv^0I-RWz)L75Q#O1=7*?ie&@1Lm;Zg}|w(@GBc)$SRJ`jmd z@!QPnyq|O_N*Fe#4|D27=Vb+xTn4=C_~?x10<%h}7bz^a z#)DhgWX{9B_zGN{@P| zl3)g-V5)%^&zBUS@mehOfU|;(R#c;=gJY+HT-SN`zZvqMTxp>#L-)Uaq{?h_#AGEo zEy^V25fKp^{I?J)iTYm>Lh~Drk?-zB>ydfVk$KWGdD8ylrnV!3qWR}T!spwt2<2Q| z%z?Jcgu$1`ul^s2W6F~i)`if8NsTu8qq^I4QSKHHZ&r|Z&QA~+&Wz(n&35`Sj@HRq zf}T?XyJCD0b=Dc7_3y7;^DuIaz&VL-eBzN+(?RfCse{6up!`tG;9_^1&XZo;9*P_S-XcQu+Ff+|NTRL%Kg@>J#u*&xpURW&r~A3IO| z&yGt4z@4j#7X77Xc0@$anEso*v&Yu}i2r9m{@>sFM}T#{7C6s5@{2nE-MBxGQq>nE=oNuloDN3)oWYS%m(yKZmkLn`)I~1 zSS)5887Iy^k8T<0>HX+RUBGt3SNGc`qD;!e{Bi~-E5t%01n9svx9I$lzFUx{-IsgQ zgSUx+lL7fBPk?ofUBsh@n&~7nBup-$`f@X>*w0`q=WM9m%Ktg zjt*&dUoYlvPqi3F%a4q(mfamy867y?;$_I3m8Id|6GwW#7})3m&Wq&+t$#QM{P?cH zalDQ+XOX?H%c}iEmy1VYTYO!*lu_<>{_lOOQk|-)oIFMAo40uG1gUnU z@&3@2lC^REQ_xq9PI{jkw(Wu=$h+7w@NH2+W+z*obTD967LYF>y!Y56Uzb>p2nP(f z_uy^<1z`zyo5Mm@!>6yP`K^6!&byFT{Ic6E8KLW{XViL57L)Ez%wE5r!V$-cvr(QhgI}D}wg?(_^}bYUkHT z$y}A@6xY+s+fItQ*(b9Q^jdg%0jqo%{!8@E%KAcdYTKbhLz=- ztklZ=ehp3^4rsI}c6VUP9VCeG!0MYt6fpKgnIEo9P86>NXK!+5U2Y*>I-fA~?fc`4 z3<)ZIec__#D`mausTp4+0}c#(qBKUZt=@N&i|@u2n18A#r`{bdOyZNd6-su)yOLRz zc8g7qhh6hkB}id z^BN6p!FPJ&ca9KtPI%eD6s>~GSTe@Tt;Y3nMA@JvL~7y_B1lOMT<}d#j$zQvj&|Gm z)60Eig{{mT*)rTEDEh=1AZjtzb!Ecy4476$HDhu-c*0&Q?|Z~UN#}tR>aMIPT6Oa zWl`#DB`zfXF1w+ zXq#@W*52*KI10MrnB01~Ajoo8?tE0UVddz6d_ZB07{sB3KO_1 zPoL2%kBx}o+SBk8ik6U0ku1Jl+`73)Gm`UgjW@+*MY4I`$A$SOYp;T-pi*aeF@pI< zhV4mId}5@&0rWg#Fw#}t=Ig77J-iG4{FGaV>V34$pYLj-txpo8JGjbu0lOXG$2W<+ zIar~$Nv*`>=<5zmSV2nqTmsIIJk?$cOxoVXKZ4m+_LBE)JemI`08SHSm)M`&9vhOJ z)t1V>9|8V+|1&>J#qPG%GdCIakG<$Y6-K4O+Gw_UYnSe@%ZZ~}7}aRLS!9xs_xnQo zmRAf+D&!(N{6VDC!b?m(5iHVQoV5mLU=zK!ipCFFhqB3Oc8cTGc>l7_2H%w!aX%g% zaI-19{IT17ypa*C;C1o(t32(XbDw{LGo-Vth=Qwy#@U$3E->}(yb1`FGo<)yT&eFm zx}`R4&GOod*K(~^H)dSEqzu{{C08xPtm{p&-4gQR&0=x8yXb4)*vkg9b;~*Nt@)+1t#_OPIW|x4ZakA~dh(}d#Lnc4 zY`5i?$c5&2R^s_((4i0qwwAGjo1zOv^5LIEIPbr1qeXiXAQEH@;G}vA+#EHQU&p)4 zsa-W&PO0%T_8mppVMKj~a_K4ZhGUZ97hYohy_94HtpVt*>8elhgg7>mW>Bf?2U!t% zRrNG|&zd^c{#Mma_MNRulrd|YW8!vF-G#1eg_$mX8y3o z-(|y|2{@y4n+7PdEB`ZY*iY6zC}}HU_r?k534vT#^_iz@wt!3;)vMFQdqfgc0Ma$~ zO0>X2{)_{YqDaBzR0fUbuIn`lJ9vb%EjJSOxa<@ zO$B>}?_(RT^rab(SMD7V&`)%uwcV=zR9^=^I3eh*Nv*`1NX!%$P zx9^?y%KBb}s@!=w6y(x(^7x+m3>`HeLWi8qia`Y4?M-G)yj(4C>ZP9gR`A>X`jXi5 z2ZV{)`DE_!R765{6i+C{y8G?ALurfMiqd8AQdI{R|Jvvdg@oy#!C>*W*H+m8O5!Fe zl>?UPRpv>D`tL|+YjI@1-}k?o<&;7grM*Ym)A%|EnI6c=37QFZW3sl$z59UIUZ2F3 zv;T30ST{dsx=LgKmIv@#n!d@t$MB#rib=KOMM3s{59jUObw={DMrdol%8P`ETMskq zJpr^X+h562E)=USsACHmO|Jg~^p{Dy<{m8zD_+$`ZFPg$2E)|h1^G90S=)z zfAth!N~IN-P211GjSi33pO}~;s?5U+m)_5o{MZsp(XDjyDx(>lI{9_!`$%eQUTGo7 zBXX=_-p{3^(nXFUk~W#0VYtNvz6%O0WLW7E^C87W{hX$wfViqeMg$e`VSlqBv{`Dq z;^oVkS>vjNX!)sIel$ty`4EdEh@N+^S4Nj{VqOgjU#l9*dpowTDVhe!DzV zcBh~Vj^ggtbMxjirJb9hUle`SQTjzczR}&9AR(HwKR|swdZ5j5L{TOy5ksjP-z+_J zIAVr!756>#Lnf_)WJHtxHp&fQkNny8O8pbu*mp72giCaC){P%OL_IX|6WZT@G?he_ zl?R^*68TV6Jt`tSbMwi00H9w#NVIW0srfLbEM9*mS2Ln=yZ-(+_Dc3S>L~v35|*hd z-Jv^mc^A)H9q@iJqt_b77a4ZBHpGSY+b1RTNL22~?^rKvDN)aM--~>8W-2yGukFRz zTbl61@Zo^ALq*ne=@AjCB|6(4!Cw``kk!KHI${npFMH`2TD=X0&i7&SkrKm!g z%FK^MGhKsUJhYG^Y_wNGRy1C??cH0T7t@&W{^b90#gg*(%R-bx8O_JU^MoMe$M&XT zC{B`)vCjJXAl)J>D4*&0tWCIyC7@(8jEM-_s3FN^-Ss2_+(lp2Y^@q>bC$)j3MFN0 z>vU&B#VYs@Jde}!eqFuQdN;?4VJu>T6F@E+s3h_yB{-yua8s<$v2L2p>XZX`MgdEZ(euo}{MUB+8M<>fZ zJWDjAEojm4Up#Mm|7Yd0>APuoQcpmfxxSBI4)L_5sm9^zdvhM*5%wX|?AI!p$p?fZ zPT|ivYsEK<9Fo>i`S8G7&rt?MDOwMQ0;$LKNMF;870@1X=_}0CK7CRBzEFymgj-~o zxZqFxdebY03YF_1SHqXDyypJgc~M56`7E#i_kjZpj34coc_73UM-@=BI<+D*D58H& z&)@OH7n}FzH*_Vyhu<9#X>(rWMrWFLH+A<+if>ZHESXt+Xj0 z7vM{LGfv*ee>@UCwU9%f$UeRA6D$fyC?>B6(YeLzAwl;cdzJ)vw6zDAegDI8`z3v& zkqbHS3nOt+=W(oYRv}4`>5{yR?`1yk4`+8$UM{1Jp>cy8w<>G>&VcjuV6z@?V?W^^ z6;fhxbXFA)DxPcZF<}i z%6=3qzuas?%oSO5Nln?-tw$vH$Tt+3I_LrZ`Ya&v19C2|?Px1&i_1})ul`D*-SlC~ zZG1DhD$Hqp+9Z9of`K`;sA2jsJ3#Dn#y8hnKKs707Mw9pk`$6qt5MR_FB~M7BU`=} zwfp$OnM8YJ(03+Mb$SkBo&BD;r4d#Ad`-&pU;18eSw7*Ctf6(TeV4j`9K zTJM}E>WDUvt83~X54(KU%zTvV$wqUU&K_rJv~r`~A)-Ur-{*>b>l=lG0vSnZ#gyo= z?OhIWyiQ2w7pA7(U-7SG0*{i<034)}K4!Nf&GsIqQlLv3AM@r4l!*?J z$TSmVU~POkuYAa)m{&-*%@yYe`n%^sz*fhB3yWUOS4=XVl5d`^alGvI?6lxpW42Yf zopG9JRW2a#Ej#ij#Zry+FV@`pU0Ar~Wp|0z`Q5LcwZdU^2eis5P~|j2`elJ~np9B| zxxG#z-S!!dfO(L1p+0m&Qi;*oVINkm(VN6&`a|Fy`fY@JX}0$0drm1YW;&+FMD#~< zZTCn`1v(tgoE=U%?r;E<(;he9##m;a`20ymbxYasq)IZrUX8L?YRV~w_?zARPVDc( zya$$POt8tAb<2W8pSw?JmhUFoTv4h&___9p=Owy2s`?ryH|(o1PHn^%){2MdDx!n> z{_%snFU&{BV!Yya)rF83VYbIhM&gdCQzJ)40r%ty z%sDo8X*Y;rPsQtjSZt@I3Xnk~(>4C?z2AF2YeT^$%2|!Mony)sB-Zabj~1VgMES9c zvGw-_$ZkiTnM;|FG_Mk@H!zUN?NH&R=i-kb4G-s&&k7Hu_HA6Gxicv-!s?HDWz}_> zNS<0y{1F2qlf|2)Ko1#T9SX}lN*QLqJTeJT1Vc0hvH~xFt|1OUjvPuGR;urhERL`D zV_!aKB{yspeD7Rt|KWqb$cu`^pSZ7)Jx&6xoO;y1$NGqLZ88PsZ_M{f>wLa<+eWQ4 zYqUgr5npn$bBQW?b^Gn5nq0lnCGa7!m-t?;|CGAuSuJxrBWBv;(Fq%5Ii876oD)ly7pd@+FoW}pOSB`m@$h}D zU8K46ifz?e2a0ngto5~2&d#*(Av7_${tl8O`l&>cq+Zc`58DgS2_kwI87syqm`?wVhC`qPT0tUCXp@@cUPF|)lEH7DWu zi?~`~bD#l=q$D&1@3~mN<~(@}CtgI4eW>!5Cbnzi7MjFZn(Eh_`ss8o3h0 z1(Exs@xvSRPBaT>#AzEOoA~;b+9{24+Q%x)^D>3q7l$BBHwEe38aqa;g+$E0F(ZIr zopku7TkU)WV|f3ZtNMCZLuKVbfR9Z8i1j@BQtMs9V1#7p#lfD9z4x0<0FAbIC_g2M zRenwyCr76FN9VikJPxI76;#Mcu zdw0b?YNf^zWM2$NB2LXe-292Trc^Xt)=Pkeop$hB@n;_mwq;{-7ng0b6QA>cdsx0e zrPwgK(A5D$Mr{`%6Es`M3tfNYx#sr{LKFMF;`2MNZ6wNRl|OQ@9U^ge$T?8J`3-s_d{`iJkz>p8*#xxNeNGOtq1rT#`X7rpv!oPkPlogbyU2kgL;os^9Qf=?o* zE(${s*k{k(6~0HXSS+W-S@OJlO+z>0d05^T>DN?gYu8M?2lhMS&e2trQwqnhCq%A( z^w>ryr)hBA=_sS6oG&OyE+u$o{1vOE(x2)-VrmONi12^=^e~E-a`so`qML$;J0cNQ zXR$gKEL!2$Bu#;)W(J`fqu2UPQg_Ggp1n6hOk2e~<-Vig{M@Z1?tAsimVn)443Hw- zTu41_Wo-B*<%TcmgeQ$@rukBW8sAPvqcH^#Zd&q2K8Tx6;z}u|y$ z376)kzTl~la#}mt+xZoHkRD{HqHJex>x{#kVv`pBuB91Zv$D)299W;eK|gamyPB7u zybL0q%wT3us6KWO<&Nm7>bp#DPP+vP?qIz~T${trE+aD^ruq%3c_b<^>6~*fbIezk z+ymKYGm^Bk8_TC$2E3p1gFKruQGbIb6{Q{2I+zUWmSEv^Bbp8;|Mm9aU6RpQ#=7k7?YCAh4jc~U}-xp=b8@h(pF=ej4yI=E;!N-xfAvr$;q zx$J0Fr(b@JkY(^BJ$Ni*4`4mk&9Lp^7aBe-9Z8#vAa$TR4{IJ&M5` z!;v(oy|qF-OUM>SAzKP@A#C&*)m=EEsu|a2i8br231FW7VIz`5TTx>rtemF zV>~XwmhOh~2Xw&A*|?gB-+SU}z4gCPY^fEAdQtN>A`<3f3*|O>2B|E=a^~%-B&BxG zI-c*PvXL$q6Rl9|z9vmUgT#2eKAA$Z=>zmqX-ygB$xFi2zA4(W>j+$IT@jp2&Q~O| zrn}r^&~AX4)<>_Ks{^J(8Dp9=9x&3f39ajFBbkZTjHFjTKW)f@bVV;p2Rx$OuPm90 zq?L*OqYt}xn3UeiL>WirvciDF;+RxiT-EwY-^(RaFgcK)GWL}zsagH? zjjD&3xS7JsD^*RrInNFE8XqwPX|p^Xl^$JHb88_6`yL8jyflt`5IHPn>~@ber?&`6 zk}I5A&< zb@qb*u@`PmfsB^0Xb&zyJU6q-qhihYA=~uHW+)hc%q5;;<6s8 z6t~u^yUj-{TIFt-tJYAMUUe?se=SJ;$7p7iVFXH2)kAAqC^oZ>)lq` zN7o_T%1sV$om45L*@b>BX&NTL241T;Rl5!?`AH-6hS=F+yKf+-*SvJPs?Gez&hvxT zpCM~Mi?Nc&TLZp3aTF4EnA0oL?eZlW8?ykKR#OjN=TC9-b9$TdxWAzaN!{;HGZ%QW zznlIA_%XWrcm9*_VN?sjg9RHw|NFs_ydZa~cmg}Qbj}<=1GSW!7ABY+b>l(V> zMn4|Rj%(h@f3P6wH?i_H6dMN2=;2~rcB+!9InG`Oix{r&9{Uu7c31&dAC%g@iJzaR z&RwRKg28L!hH@#BHr5eg_=5%B2Y$JfF-AzDpWi9iHXZ);h~#aKN4gPGaEi-n^Y1l! zF>kV52x+`^oeK9NCbS!NRc(4D2hv5kH0PjluBXo4AI`C5H9zMZ-yvvkw2-Zr*jel{ z8EhlF)7kg_)XbvTCoZh$6aKYroUs0Sjb?>gN(yFXjgO0!JI_HcIq@P!XMDe*Sp-`38W;i^XWvC4@;yU8k9c`I5 zn(hPQJ8Q@l$Ca~2MQ?KP>0I$IBi|uEzRP~`&{Nv3z*Zw1wWXrII&H-t!fT@LqVO#4 zGo_Q|=nc^V!6=SggTA3pkPSB&oQh?y_;_1xgS?&5SIK7_pf*>(-?oIi!lAgg@yWcz zSrkw^3U7s9p#gOOwx2J>oOq{s*2rMGQ2h2{9Yops<$7oJJr=k#zq6xoZ z?e=62>15H8ha3DNpy#;OAF1lP)XPLP{chfSq!m5-d3{yt;Kw$vz1MBM-rO|cjEq@S za}hnZ&1*9Be&~&<8(Tw?jddTn@1Ahs?4Z_N2)BQWvDmp3*!x`NpzP$i5J_RxwZbj| zt}a{J7yW(E5NB`-@Zy_uhvd(Yscv1VS|16SGrv9E4IRJ~U1{gt8$9V|YaVbkT8xs- z*=lq!SFYSICFAmlcA*X;7%x*!=)I+rOY^9l(4!8YY4&+h)$*dpL-+dWl^|_<@(SUp z&W_#SilYKtV*dSlS1N09!bAFf`*O~O90Z$a^62(U`#Zw(8*?O{Q1~nHDih8tQz9GI zE)FMQhl8Y9giktr$cn`anXQ7Oqu%`yiP(Ma=`|UVc`eRG2a>x(sfE^k?h4y~&Ed{K znjc^*S!&DVt)@pV6yH4*QX#`XEt%*rv^hm^f1Tt_x^&1e=FVkFTK>UW|5Co;;VlIx zv7>|Y4^3lwcC`j@HD89-29y=J*YCB_p4CnVUvD*umyT%J;~$twc*&EdYlAXf<)bu@_5w% z{`j!CP-U)=hazPHji{6N%Y)=dQAD=CBy@qFYHxotV+KonnN`}38$wFc<01Kw&080k8=BzYgADT;qCX22Sa(^%?m%v%g`|L z8vAgKtdd%|si;HXxbA~A0{Z(|Rfge_n&$iW?csg}de8&Zl+4 zJ{5#H;ounz+p_uu*X$hMQNl@Y8Hs>TmX@|@<~M9fEiB!~PseA_U%y5!Od3PD?~L%t zrO9GVJczVr|@PThgg43|z1`54i^)FVuR>ZeHkX zAJANeDo?#z*~M9S1kO)kk3H&k0tc@wcUi8-k^GVr%kd^GPAd_4;VX=uGUvf4*~OnPLxzMSmk2^w0v zjezuWyc_ob=ULrBtO$Oh3Ic8QyEP;2#sCuj#@Aw7Kf0H3YxQqt!7j z8Qus0`OfupY$ASa~vmQ!pfv+aR%PoA)NDRBN)5s1NDe(4u{eDTtL(f(P2tPW{o zo@#v~Hf(l2Q`#uQ@L{2+?1`jktD8eX2G}VPmyeZEP$gY2@55z{^D$|U=w~;t%sE7T zbvTu-Cqv8Uk@`!Np=VuP`%lRkNk(9f`dgR6XK21L7Kmr>v)ba|#}hJDz{CAj;;|R1 zt{^dp`)|#XDl5Db>*h*(+f#da*t$gsX)-z?P1WhTGZ^6UlI4=QVuIa}<>%?OzzNpH zQWsSWH5Rw0f45i4UVsF(U!nk6(#Hd9#7dmyDi4au_C^!mUM^O^w)!Is&-igAH$_^f z2|=z$1i`iv_2t=b&6hQuyyB*ntf={ztI~R(nxv-|i0rwznnX&!K0}qlR$lr5;1+)? zZ!!9cq7XQk%Tf;NOPjvHa(k;*9tPuk-NjW<_8bS0&YPZ?n;(clo8mX^&(U^8C~r2C z&iV0pn^yzw2$PIi4OO?s!zQ(GJIR{cj=>(O=DlnrgUR0IiZtJ*qIcpRx7|-=3I}b) zdiNG54IE$(KXXB$m*;Kouk;`4F4<#h?DF}m(e@Vo$q-Z+#DrZ*m z9PMlW8SlL>mG(^4g_;eGk|b!9=2v<~c5B&)rr!9l*V7uC@A!LFETk(lX7udlui4n$ zob=afySHNGPOa}+Xj>o5Z!-Bq_m}Ae;y`$Hq#qkRq<%YsKcGjx;!T%6f6FtwCwcd` zDb0iTqo{9|)H_E#veeJ+KJ6$PF`sns*}>*`23hI#mJ7fHx$|o}gNiC-!=GwC6KXt} zIrmc94)P zo96(O?1#p@LaHxEw|47UPdjF|fA)R7d9ZfLE7U?`1qJX<`Abi9bl!lEMPW@$~dHaQDL;k=ZP&NwHu64?v?{U4w(9Wow?@<0;- zPH2o9RtxyCwgCumbJhYP)J&93@CImCH{$>wv_*iaB`UxJrQr31`dT0Ulz7+&mo0tI%a6V{&nv$v_N*N3WsH-c1A!_RC zUf{6%}sB?e4JgN=5WIw<>=qEfUZO$9tr}HNF*ha zsuIoz0|IMkXnaefqM}GwC=&d!L?l@eOOX3B$-nc!(FBx_8=mNf!venLMLOYpiCRG5 zw?aR+AM^6U|15+h{9s3?2qGi#Ah41$=x1hnAdJogYT$!L5^+A3IGm^UpEEaf^Ffnv zJ{|;sfrUEY7{U$fj3W__!$AL(|5g95OlUZgh}Kq7R#8_}KB1@ru~b%pDxZX^LN5L- z{!h_A!cB0_ZZ7`+OE?$|1*`lB|C{JXI1Ka+$=?ut=l^YX|LFg}&^e=ilEeG@cz)kO z&L|Mt6YYh@5(#vA;QvX_83lF0`FJ6T+HPJ*3>t*TVql=ZI{)MN{v0*kAarknewy>& zL;tx`|0|8Zxc^tu|Cy9ogdw<0TufXH5nxEnbQcpBLj)KSGu_3+#Sj68#7uWFaWO=I zAu-ckOk4~RU`Wh#7ZVpl1Q-%C-NnSk5CMk7Om{JHF+_kNG1Fa4TnrIlNX&E>6Bk1S z7!otx#l*!B0fxj(cQJ7>M1UbN(_Ktl3=v>R%ybtM7efRX5;NV!#KjN+hQv&FF>x_O zfFUu{T})gI5nxEnbQcpBLj)KSGu_3+#Sj68#Q)J<+`N-709q#+orkY7%6{@iW zTG6A6`rw%4$(Bk4&mZI3d%x(2-{?7)7vl2;Mt%(Wjn6e@Orx&x)zST@!@it#;=r!V zE(c!R&TkDD+LvnKfv&T^7A~#o9dS`URe0ZFc8&9Uj2A;?m76`+`1Ok21|(VY)qt9N zhonv?>>9p-i0!$0uS#f(zr7$z+?bd32FKyMrK|$_Il7QjvS9}{svFgJFO=L(=nouy zZgAEl%cvN9-yYS`epXK0$!>MR=TP<;S=W8qhu}RKDaK+#9Rh|o1PcnmkyHLG$h!MD zuP*h}k)p40yZiG?M>e+-c!!B60h|Q22TE6F-$~rAd5m znmeXX#Tz(pFPQ&!=%%y;NJfS<>EX1N6zLP(PsW=+4OHn3NQ_*KZ^XhP6kew982&HpPZ`Pp literal 0 HcmV?d00001 diff --git a/content/other/toolbelt/src/main/resources/pack/sprites/tool_belt.png b/content/other/toolbelt/src/main/resources/pack/sprites/tool_belt.png new file mode 100644 index 0000000000000000000000000000000000000000..b708e3c10f039f108a92a689ff32b064a31e2a27 GIT binary patch literal 52386 zcmcHA1#sL>+aP#D%*+r&9COTZ%#35l%*<@Z%*@O&GgHjW9y2quJ!WQx@#g=&yLbD2 zyI)=1ZJkstb?fQwl1epG^UO~QmzM>gAQB=%KtP~KN{A}{-4p&(;9>u6%YI5v{@s0Y z6qZzm|GRj>8;AXEBiKo3I6^=mVg6GfXE%J_{vIT95>t0lvNd&bHE=M25HhwkH2ETK z;b7uw>tN;hMMR1HsG}|l0s=Y1&OuepE%RJU>lcw40l!7vwHv#Q+0;s-S%vI1>B8C91sLNR|UvdXrp^EB1^FP60tv+Ac1%KyBdHC5Hr6t=mK(aIu85tvYKq^CjL_VbQW zaIZKLb7Xt3rFeT2t#$t6MY#0kh~fV9vQ{|~< zAc?R5Kzd~U^;{bbp@4P#--?3#53rVzmYS;`xyPVq-PKF`f?ds z53${n8$l4*w`i9s3q5jBuH-!b+HCMJIb%Xhi$7v8Ry+3j?`%4Ve?uvIe?wL8hh=>2 zqEm@}`Pa5s#I^Le{c!!>JE=6mzU{Hx)k4PJlF69bZ9ZODsMRUCo6=|0@rnIvu=9>&YI&aU|ZGU z!}~f~BI&1=Tk5PA1@=jvAUy|2Z-}07$R%1U*ii_c;iDKh@s^hONy1hkdmFVuuA)!j<+&e9uv$y~8{&33gx;eh7bhb!=I-0(W%gfgcZP2{53)oA@}EY``la z2|bGrqne0Yt3N!w0=$pqeuZYhXGM=&OL&)j=SJoY6Jh`cX`}1CrMB3-xbzeU95y99 zXZe?pMFsv&nOVBBtGFcd%|+SI(~JG8g07RH9|pn3mx!phfSFmMutU~e z%Ps_<9uaQHJ)Z>#t8XVvBu#C;btistiHDqg)Ax1a8Yfw-EL07ScuhCyyBb0r_E@4| zf$iX?^&2G{B!%3%>Bg>QUch+5H~BXDK@TjrC$(77+)(>o2FCZwb>zR1Jt{;b_FV;g z(%;N_$HC&lOn;_A+jLn_o$%ZJ-JANyWdoi9s)0TYz-Q%aaSE|dS{lOt`hl`ZXO?_{ z+xa}uIC@wZ8|i0YZ=(V{`OtX`u>Nf;QQ~RQjKUfmPJj~OfHHEx^^t@zjtFfxM zn!j)gU2Adg({tn|8#wrSbE>1jdp}X5GtvqGg#D&<}j;9g&f8>GPOZ7 zn&V>>0v=)vn~6nsn;)Rz!eq5EM$@vqH{*tssy?uXnvVCY0z}iyL)Q4d=w#)-2t6!$ z!;PYGe5=#b{rvc@n(L`>%%KU<9smZYY+F5(8=H%eh<>O!;5+)w@&Sz;{ElC=i?b%* z(vX*pN4ZiH3vUG`0Cop};jv=Kou{FRnP=Z_$!;E-PfUSMt5Pt+l*JHP?@2Pg6d!p> z^RG-1-t&HpKzLGHo(-YTTd>^ULJ)4@U=t%DJO$E#7go^56(5!iw2}$@cY>#KUCZf}i}n?8&${jH;OW;~&yQ+Gn9#3zi;D*IS=&u2IqRsF^U3;1*x2$yIu{6V7Y1d9Xp{az1u6@&UpNll zsuaIFi!OIz!31;&E1q5?+zOEAd?wU%ck6Q8TW*R&+32$;B$60a!COttr-W(?B$#|Obh%HR(+U%#B)MlFV2~BHNmXH{&b*PAi$xcVTB;g46NI0)?p{gbmnz}nUYfV#<2>N z9;RN&J#^##cK->&HbxQBkj-R-^!|RdeOl<*EB$b_JY zfgl5`b2+eUWAa6kh60{@K7^o$g)sT!t?Tz6Ta#1KgV1{cC-hx0AIKj_Q95*8eK!S& zynK-Ju&6t;gr3wHtXm#gvI6`Q@wHG#S4tRQtfbeekg*mpdMZwk-(Is+PEi+P&hiVA zJU5R5c>i9|WJ$Pw&#VswSNX`?!HOQ)m6%Z^8jH!ABuJda`}h@BT;`MU_Uqt7=@)JA z%vh`}?Shp_*wzvPO($r};zDf4@kvk6C&Remt>ftrq%x3rXTx@fWL4V?kPa7jI`bxX z+_=sE)-RF5-+5)hG!8}dVpin)z9L=k1f0-Ih}Fgy(C~Erz+({D(TsyGL%`eaASO87 znq}^eoT~GEe>Z%xhebv@*bZif`^;wU#JVRppYJy}XaZ_qe`$*ckJ0e zrEXreuPbo7ANTgRvCwQZ3Q}k>R9;-XksIB}9roJLf0%B! zzZLh@)8%ICKE|q&TOf2@mZaKA-u?g~nji1iy`c@|-oHgGw&GRxB}Y^F`J?Qocy!WD zJ7&|rx$ea{g2**4x0VNS9w2|Wj7QckqT?{4r(Fur#6x~kmZON9=V^vyuNL6ssc+WzVQMZL*+SVAyWr(j~NS>jeUB5D^d%1YoO|zl2?iQ zGM6W9uoG#OcOEf+j=+XdBVY}L^mmrrhiI24L$H&8&Z{Sn7p-p_oaKj2C~z|8qO$Sc z<*DvygWPn0=v}V#Sn3Fsk-ylgx~Wg?=J4^oacK=?dsEV0?jpkTW({`gTy^7l(*Zk4 zHF}?4Exa9p@sj7qSVzqtn*XLV64qA2;xC({;D7R;zvcgEp)F+1gMi?QwYE}L`kVTA zueh#>C-YMg55;#-J9>~Dh#iiyV}s&()okP`QGYU@5R!DCpF~D!mmTm=F5386mcyuscuka zIdtv@-k03c`U9r>rb~j>QMCCG8$+!ggZ9gIW#+xsyH>y5GqeI0?c=#1AtA%WwjX@= zNS%{B`Z%g=pS1K-&OT9YRM`G~w_d5~ooo%9Z7pE9R-KquSCU!Nd70EIK$0n!H~Y?7 z;hDp>lB(rnYuBNk?sJ*g=sk(pk&x}}2e%^95}fTF1oWU*ev1X+agPMYN3t~5smXJwKSjA%lnwvag74@qi=$t+pv^I zUC&!^bXZh5ce;Qe+a~oKg*N-mmyBDii>H8 zOs%ifjgVvsEm-7CcUJ1mbCi0XWeDe;Q*Ns1+Ue1xThKlUhWo;1h9k z+s`!XfN@YN0c(B3dBqm(3e0`7h+sfjSi$A5C8p7zWEe!WX<+p zW~em;Vjyye-N*%P_L4v8L1^4a1u*T7qHoE;;;C$lWhmgw=5nhIx=!+8aS}jWG%cZA zrGoxU9R{VD|F~f(-~BQssbIU$JzS;R85??GFGYO>-{-JwaXUMY|1JB1GItFj4;twP z#@ZrlnVulsAWWY1QK6Xp6!%e)nT9qzId5#*5=+xsxLigj4IaHqnlFzoFq(i=rTg0F zsFHKk7KRHmQvM7x;wXPWURh0Sh5}o1ppdzVs8?aCr?%+khhWMfWE+uWm78^yyEQA4 z^eQHF0$0{TqC>9-x02+3^S92HMAhWm?0w7dECS?vr}MW^SlCxPh&@gli{S_C@2B~B$NrWhB3Z9mJ?sz$|+%dm?esUC{QVWut@U2&+rr8s5O1$1k!_N)d* z)Oloi$Ye=V0S=u5{dOd=&N}s`af31eKc`DSMgMD~iJ3m6h_h3`vamosa)0sw(7PEf z6@zbt&s+SN=p8h3DkF5~p$5R`{5ft&F3k{7CdSP5DM5spOL5B=9z=!OEbV7SG3qq1 zUd^|k=$k>OW};pDttxOaJ%!+xe36jPJ|nd&Q45b1hB7YK$B)IhEc}t73wSJPTetjU zlsXkmo-0BYYXl7RpGq>Eb=Vw5L>d-mqB`^$7_RIQI_{+bB<`5PiIwJt;EAMriD6B% zSyk+UB?&5P>8Twb??g!`IUH8W#SWwQdX3ehm8^e%3j1w8IHKHMn}>LM31 zfnAtO-xZM;okTN^yPE9A2m<;9c8@W9o(Vc4Oc}fKk+ObSl7v~e>Rk<|dYf1+nmG3> zeofv@;~MsJs=Oh0Eqb3B3o@C}rv$!VcS^(;N&9Q?fOLI2NLu4MhY(ZkP{9SR(kjNi zB~xC2Xh{2)UgJMfHJU_fk$%n$P>cKBZOQo6eJS==lrudRYEP6B%+Z6qRK&4?*d~B_A4m*~s1JL4>mCJ2* zzC@4v4#yc-woEnY)^7>a+HRMM7^`y**XoWho^n)T)OU|Rrp5b?fzjG*-Xt_Q0}Z5# zBafBk=#}l5$1+kC1q}pXz*1|tSG)$XGA3LS(2m~tvb^ekJ)7fq2F)EAx)x1o+ujZ@ zj_?wpehrE-+^&4P^%IzWgOFbRkY3>=6Pai-`KXA{g!^?Iy&av*Rg5X!{$zs@tN1Ux zN8c-d(9?>2Ct{6-U1e6o@FK_=8gKf!C@MdDgP-#G$1Foywjz{yG(4PEApqj z-(BA9OvwlD4{aJN~HVjHMqM#VqoGgIQT;??c0d=a0y6Cj8}KD^m1B{3b{axad72y%rQG>JEzGRTSyJ;z@fNoH>9;m( z$!w22+3#e{l!|`QMdY+SWa+91d=|3`m{xuHPJqZe0r6Eg18_*_N1{;lv31t>Q}fZF zAL6qEl2F6SV!tD3QrPDhBpIK=emUV}x{zn86S;-iOkO^RNuo%%76BlQHESnZ6`>0z zOpHsdaq2?z;BH~5B}G;ZveP}7dKasB4sw%7VUUe*3Us^`^|#@1#k>Ljr(TIJS&_M( zxU1{UEhhyh%z<7(cFqfZ&wI7@uz(X8xKTAKEILiYvKttZ2~Hw<2_9w>o-3|3pJ;^Y z{QZ*CqwdNZ1a-(ho7x%p;u6G*yYx2O+(2dyU;ozL=u>@5svDa0T@^DgBp>|#NmAP5C<-fAS&!Ko6iIEfC9cgGZcO!)j$PT5 z5-{4m^%zPWn6b|cE`&rIFfW@~linppJo)N~hBp)kL4e7Dj1UTfw7jY-izwo!%eYv$(FwvLSr2!#z7@O+$5{%qAOxx`@A9=_AY4yZWTyB1C-dJB z6XJe!chZ>sY%k(!kiUed5=k8uc2bjQ)aD3B7UO>H`EYR#P-#XUT`TzMzlse$cqm%M z&ud0ctD>*MZ^&>w^wY*33I&|w5v-jeREh|n{t(s@G8g$|irb3`Prd3BU)z~IO^=zr z0ACq0O=6!%d}TJ{B|01V$`sp@Y7EfECQ*xc3`X`*1pgX@*o8UY)r>SUA7OesLA<}AWTdBI5Q}faWwgZEdSNk&YTIq z53K>P?yFD()Fo%j<_mGi9q=PYz3XQpzaEo*OOJyu&vUH@gVByTlnZKJn_(z-1 z^;0QncPO21XxEt1H(&@Q|? z0NC+&r3=dyU>fa%s#Z!AyqRb-d~Yql|tokGv^lPQ!O>@c7XJpW{yu1wKd95 ziSr>m8*f^RA#i!@UVDzj+xG;Q5kG`Y-|nAeXLdOXTux3CO-kfnLnm+?qR-MOUO0X%4* zCyzNP$wlHe=r7JAwyWf)2yaK>`g$gA^zlE-W|7Mdal(D}S|fS&3`FZj7z7=F9KHft zAJD`fCLE>(`~(EZXT5|z^PeM#?^m&;yoOKRIufJ@f{|SIfV33|wXCko1h%zI<4NH+ zFr!iAt_7;JO!ksbD+9o8&1;=Y%H%@@#sgK z_uG&bPTw79c_V73c>B;N!zhTx^V`2hl3JPr;x@=#0Ib8apg9IRJ`PLV$ZxD`&OyWX zH6}I58lra8;a}$PFa}DSvv$_ktiXw9Fq-+>ZJIk3LrnBc7RXt~} z@x#B}Ra~CiHEr6t^sS(nab#}TxiFet7IJw?Ay={?Z!kOcO{0i#AZLwDw`b=03I+KZ zEIlQ;H?M)r>p@2$1)pPVINLx+in@2H1~5y3;2Po(8;<{bm`+5Vk?vhG{;D~Co54{? z-scb-{I>aZRQ2RnAFH!<%jH&s!;q}cE;h>=?hGRNY3|JiF1SNN{~Y)m5kTSrxHcp5 zaIjtyPTFv`gU&+gfh3bQrknrQnQf_r{L>`buyg90K>=*(#QcY-^S`41<9xQ*{?$BN zuW{^~MB&*Y;Xzwg_|mE#i1FobKYFg65( zm9lS}##9|T%L@F_u-GbD%Qce1HP;=lr_CUziu?Gfc)%`qt&SYBMDAq@VLHJ3Dx)x zS}M$J`WHLTtfl42V0G?IEZ6CTtef#}_A~-={G-1ce?%>$4VI3l$<1iS=M1&h&bw#K zWPSfGck}u=YD*W89q(sN-&=0#b?6W zz8bjo&01h}Z50jFq`-}uNxJ=>&CYO{@TGDB>FXE!v+AE$;yTYWaw|ICf>id`BqK{n zSt$qePwuuYlSNZ+ND0DHHI+Lm1@#G!R6MS%@8ZQpl3T7J-pAY_@icHMS3dcyf7Z7W zq<`%Ye&xBf51~)sE=6Sj>aDjB8*635j%eb18jjui-GkClO;$0saEVTsM@|;Qr;fG0 zD#cf|$qD9;uJ#tX>4Wtk>2Zfo-y~#FIE4M5kGKZ|`JID|wR)Suu>iN!WetP;Axe`u zpTYuf)SoCGJQY58C&<&6TzY24=RMLGeB;o9k%^m|LbS^>Z?Rxf>p3lkRYW~6{{jU> zt7@!4o<)trV&3%zR7x)Lv-a{W8On1oBRH{IGaK$s1gri;=fd5utBEo@)T2Cm$dn?Z zznbu&eFnEFW%XY5ds8x78^hT0mf7PC#t;=~vMcCU8VSZmcyy0&u|aYIkYO=^z|z0Z z1kuQbd$Lp76zG4Q*$*!eL%(}A{!CcEl|k4XH$q2y>sjIB=CR3}c%zj4fskF;JoH}b z?eYK^7Fs%To?b$Jz>{}nCf}`_?Vp)+tF{6?3AYm9=qXjx4K_%8YhTWP^lHd7m75Rm z;LU8z&*$Y8-RQgCH(v*cN&5M0+3Yoz@sstjgoB$w>X-(k?^S`#gAeXUpfrQR&u+@D zw6hz?n;0(7_yU6jWXy*U;sg%?opgJCkgm`}C(_15#HVnBEcQ;h!4+sn0Zy8~^zb@$ zy=ElBi$bd2oYO=WLh^26Z*OOR!6S*SmQD-~?Iw9A4`t^WDmfr<*RsWz#KhU|t?bvg%=xd;`*4X)n53 zO$RXQ^}ssVhNgWbekH50$;ij`%Whyn%*cmz!%sQJ_x3*oqA<9P#^kkES#sq_PSKmy zbFk^rNCilaY#lm=SJ|=Oz1p1{IaoQDwGWq}%XFWKMKGAhcyTu!y+N+1iPj*Wpe1Oj z)%g%k5G#}oC?t#mbDRJE2CX#Hy?#x1v5DqX0ClhDT&pu8FHysXmJ zah?e0@KBbeU^icuz}#7fI;t6(5R-Hk_esi)m%q6X=rEfd=}G5a|! zW#>;!g|kJ*P7b|B#GA+|YJy{5pcxvGL8L%M10z}MAov1k|C3$K)0+-uU^OEtq`7uh zo`{7>(*j<%-c2@*jdDlYZ>KRN-A=rI@hY}q@oFs!hsOaIHG9)NE`B@hX>MQiP-6Aod7^Y5L3u1RqMEM!0>u$j!guzi+G~Vstnc~L2Ih@-NN2#s zHsUM((eY-4Vva#_YasoGSXr~Za&t!93pRkb4WX>>gM_3#>pE1X@|8~f-o+UNm+5{T z+#Ine>1(hh!-$ev>y7SVRN}T|ey@uE-WX?bxLi-mWDR;!=4IL#Tmitw2=(Z_pzI?z zdv|h^iPUyU?8o)1~`d$Rho757JOz z$bAsK5w_kSknMy#O0=r>qf_B%C3_pVrSBW5$8(_r`+{cr!J~LyzpEYc9$Pz!yHKS3>POFmU<{ncNSO5pp4panlp)?Px_ezX z)+bh9Ud|jw6=1V(Q+KZ}eRS*oBkWKlW&7SStrY~_vgy#}3Y=RUU%&SQ25u+T`tmD5 zB$%LjTR#jqgvD#VTwoaWdCI(iPm{jg8pv@BTKu0eN6BT7pyB5|+~KOBMXm6E(j0*( z=2sE`ogQFH7xA5$DG8^2c)jnMP%$IaDMiG*V$uiXjWPtyYs_2h4N%q=2FWpca!9pZ z@!>LK&UBeYT-{FG?1vE>m4Pvs{yyne!CwrQVB~_Au-jv}a&1~1T3$?*(s!@CN2(oT zliW9lurB;zj687k>C(6%Ho`rijU_-we#E@jHz!c~0YI(1Y&ZA%>iyOz(A{ggFuoEG z%?U#Prt%Jq1=p%K8igmUypNHQc)CZN z=S0~f+))cucu@uGALi`)tkKOdB6Smrktn0j~-)oGIvv}AF?;DTE?ig zTQn&JJr-EcPsFh@M8ZMP8MK3GBYOMViNssHB~p3%_+WpkPwI;zYnx2n7v8L#)BHB_ z9M%;T`<%SV^|ABFgkkt`7sYmY_sh@urJ#=$*$EI4Xlfpt*14;My?pc(gvfaZY_&4Z zy|vWm&Y#FC&S926-w8rVPd>|?$#!&=q9m2rV5VS$IE!JVg~nOE?UTqw6#B6sE+VxIsJnoV-(VTv7T z=wWqD%?eY&#HgAwZ1J4Y$1(x*!7vf(ReES(j!`=*96*pL5YH~p@pJgw`e4jXEUdT7 z3QKxB-mX&qtxpC1M__!8>Az&v$ANfzY}dbeehrLqN2mH1RYMvsV$YF&7>0VA!!#Lu zdfbToO#S_c@5lH!2(d(7y3hZXj>tzq$kj93szv7e`Ylmkr7H9KpK-g-HWq!ARLIRr zqjo9X2a<>x_S3{jIvJSoD)!bFG{XK5pP)(U%wrGI8t(xoPbz|dwF)CI6K8N(%8iTdD2vMAd>GMT(jp#afl1{S9>^4Z|r}YItI4 zPrlUsK(SiVW&cY#(zJlOK#`Ls(U*EL2n!{l<^bdHpZKlr#5nW$>dkTJMx2ltP_m`U zpCP_gY8e13*Mx>rlLJv-_|+!VIqOvUw5k<0qO!e*SOUI0Xhz5aO4C?%S-ea<3-A~t`_eP7 z3~&%lNdF?Hjqvw}gaKD)w?lw{0_Jes8fV;5LIo+nZwj>o;|qtwU&ZvJf2Qn7u{R03 zqz;Vyc9lOtgG?=-MYqdyn2nH$3}N)*T?r9OQ1#*^n;I@oPFl~6T9=7^uUZv&cCzs5 zc0|(b6cEEqADYF^Bom#tbDYie`R2F)<~g9LLH1WN@&vPJY1%8zT)cCnCoO(rqUhMP ziFqSJC41gNah3EZc)jB6>7<^)sMv4YZUT3zMElJFMB!pIE(l^yGln#O%Ja=JBMJ=_ z@wHGvrH9+gitdEDW491GF%)VY>`NU@X)sjQ;r=6;la@cZddy(kMr~C*>}f0M5BbIx zFnL($7p^w5n5(E(aXLz9UX5zvPZ(nI^uj`#UA6vg#y7&nnUW zgqA*=8TYHk_?Kha;r0(z<5C-)bDjrrdUvzYtVtmsUaK#$S;1PYQ~VFY{Og#MdG7Qo zWc-9mI^Msvt8IMmC}X2EgXAO8Eh|M=0A3+NKK~_RhOz#xvd<>lB@F+r!}4eqJ9YYw zGIT4|VyL1GSINf2x}5tn@NHB__JY&Sb@6K{y#v^yf{cgdckBj+UIJEUIiMf4LtI)* z_*2lVGFA-zfDA1_b(KH?Z_KnmE80+vFew1i!6D9fl3t;fbb7J0qw@?pk8h zREc%B5vea>?m9nxNA05E<~&CPenKdoNgXO=mP}I2bm7*a#uAGwTtj>ttiXN~j$jlO za9w93S2J_~PEnkt1qLk}S?GsLU_~ev&V)gCmmV1@zSUh_gj~(0?>@HtsSNtSLbilU zVEAFC3$^tfEGMNOMNtp6waTf$_<6la17#h8a{|ec0zOxF99v-2aoCGnoC|zDzqh4m z8d37V3rHpJ2K^{OcwsZS``k@?BN{nm&pg`qd~xV<0-z*4z@2@8gH&FTfKB!KbeE2 z$gkH2@hzo^VcWfzBxBSe4))-MIn&LrJd>T(dzs;~SS{U}$}J}oOawJc_N6khU{Yu5 z=~w-yTzd#odkDSRK9i?u<2M+xtxzGCw6kbtTc)V;F-klAu-bg~5Pq~aX*v|CpYg~; z;|2cg<0_x@1`k>4D}S?LBsTC4?!!hqNfnnQLTHlr_N2yRM7=hRLet06qO7E1Je*Z@ z>7fjM6KWmm1TY=*dB`H5j|F9vQ~B+E`!j}KSjtNR6C7y0gFNELG_>9a&h5Z@`knm> zUCu_{r9EAX26zA_8>oNEbv(2r6@A>!0oZ?O~hYx^HoeYu$1jHuJP@o2!}Df2`uh~^V&?18l?k@KIb7Zr z=eER8kHo$osd$|IL#a~eS?S^1+|ym(W}K@!#gR=CF!ruVhvwr9PBd@)ipJ`#bok2( z){3PuLMVIi-I)-T0$3c$c(PFpv1R7!$F5toTmOFpNq;yR;Qtqp#OKv#z+)-%r)|EY z@sp-o#VtMIs!Hm8Mu|#8`INCE&We-s~n{X^!#B127%`E~9_TC^x(m0;Li*msP ztk&LGJ2z(ylsaGG+~e?fJd!X{=_@He24&&=#o+m4$ex&_cg_ro^S_6r;#brxv-Qu= zqZV1kq6t=L_re=s_MS^n74A}yuA_S)gy@N{rv+&hLawB`(#8n}uZZG%luj!|89_VYYt0yVvR~7} zXCqLOG)CgTUp>BqMz&4e4@p?qq5$^uU%<<7Vaq{O1CQFq7iCKnS?T=|eKlfJ>YnR% zyoQ5~*WB!Tx3qhPDO1mGV>!*Y---SWi+#Isx&O!_dbUM>*P~IaT;%y|fX>@9UCkxV znUY#Hpj<<&Ch}eU=q>1o#BS#P+5(D^f0KuyYq?wk=6c|99xF#^hn4Vp0e#Dev5Uob zleCkKg|>evuhe)*JCU7QnYE1x8cxlN(GakGHQ01|4TlF zGZLl`{>o?A0$^rY10VY=#K+j{-&cGm;isE#fF@!cK%Ij2U$WwsAj>osFsh%4_1a1p zinG7at)Q}~p%GU$mP@wA46zCi74y+4ps2GF59 z^lIF%9?&juRVi7R*ba-)A}s&g1*2lH1^X1;Y`VABqKRei~I zdgKqS^Y0nCX8v)r!!4i5io#@c_F}fZ{8$z2V-RJ_k<;@)@3B0UV&&}uP z1FI)wV&8v%ersZNRbd$0E}>iO#|_7Pdr9{lYpd;Z?i+-wn4@b(Y{;{6(#RPOn41SB zz#KNPu@!6LWdvlaWZWUFlp@V zv{uWpa*#OK{B<%pi`kh)wH~kgv&)fNc4Qdcm?P4&{c8)|eEnd@O^=;jeN?COGlpi_ z(kRPh59}7Z*P1&gDhFB$7T2ir$;RkZ0F3oh12uk9`=Is(HUHEU$`<{bZ7)yCe{w{l zOMTyPsr0HO4z5dw3lMjfRpy*%W!t2M*tAdWffL;?FVdUK(bn%O;LtPA9-eepLk6a{ zp(Fd4*VMl7O^A0qkH%8F6!xm<41-AF&I7Y`HE<-*nM43*%Xy=x)7q<=t)q`n*KhPM z6&~pjMPvRSqP zyqPE_s_BeQ`~VtT1NrcU!V{yDTN_N(q4MV& zW+b+bf0ipJ&qo0KM{m{j>3`HX?vy;&IpoSCt)2e}dR#Fqj6=)Ga2x-V(X}ea7>(Bi zoC^6aqlz^D%kut>dXpwcq?{sa6&KrSMozqwp^8V*32UHVo&SK{=YPwcqONmPeRKb` z=D*46U$8i8MSwXm-i}gVZ|+a{zmnfSR`}2Sm)}JG5x{>-V@SazBjBIrzx#(|U4H$S z5B{gl{}mT9iu_OHhfMq*$&Yv6@W3v$$`vQU+Z37AiebF==Z9}*%NBk6eEgM{9V(w0 z%L(a@o5kDm8$pER*&LB=&8w}o*dvtps?^a>l?^0u(f>|<3ICn^j#%Z^Dg?rt_zqTa z&3?^%e$PJryoa2c;|zC55SbQSJ%ELCc+8f4WbKyUw$1jplCy@j@CjuVSwJA_DUrpg zB8!gsE>*|CA994vKPnt^Be{DZ{8kC8=N5th|v4*6YSplG|z%=!+*e&@h)9;a^k&A4n-VeA95E-am zTwCW2r7RtspX6!Vo|tdVfTZX;^;PtIA?zR!&vQv1;LGm)G&2P8dGifyZ)N58L(R9*nbji>xTDu zHR^fAp>X;*T47ve#rmQ^F;ke#Yn;w&MDlF*>smKTDc~{xocz^C|5aA_;hn;u_$vYw zN9r$N=w@}udVXy?hf;R!0OipaILO!W<&SM*=jN|H#Ko0PP0$*!QU$KUtEMx2qU6>$IP`~L%My9v@>JDUFjHr@n}QbZs1m##mg zq)VH>ut!gpuxK4joTU1zG7Cv0tJQ*BvNONF8Jn5vFnv_~Iz~KFGdu=<$L&BQ7rO|g z^TqN$L(b40-+(nT(BGo<@Z3XjS=q2a0HQxYQ+?Ak-@afJFJW!yHB>cXU4M^iB#Z91 zGDhrv_r|vfd#NW-{X>;jXxi4xlIE%)spevIIlH#&`l`BfXtJMXjIrn)G7T8VgwWM< zaKPfJ{RO{1MnAO0_&_6AFMVgL9pz6vUf>*T4)bF{a&MJ+C~&v%mQ9>4BZAfB<`2~^ zaT5&ZA?&>^A3ZagMt6%N{neKm9Q7qQ6aV+41Yb_WIN}&8j{*K6N92}np8QVs4 zIxd~gKrU-{&gsm9 zM(O!W*4%@Rsg}f2WF?W?yG7J!<}*f4KdmaSdXv2!92y(T(ZSY@{Ba;Kc# za$ta4lO_6eQZCgqgVtJ7n`n>ChMI9RiX!1jrO~QZqS{4H&XnqonRUkUJC=;8Awx22 zWs!_({>V?SbB%D%3c2pK0r%k{Id|>P6$b0O&n@1nyMSTQC4IA+2-sI7lBB@1v%m7^ z<(rwM#fhk5BR;TXOd83;@OzZ8>4=Y%p=1Ti`TG;eq^5!b2}%A12wv(3cWFMwmp@%P zJgsFC+95`m@3wsyfL{I=9XQ+Q^V=G^bWoTiy89L-(3sT?u)J^7f%Rs}z%#Hq$9n!J zzw+aFIyo;j_El*=ZsCf6yay)6#uEI<5k-iMgH-2Wx3Et{; zhhXl*d}Bk3wZmqKY^MzeMsB!kuda}T4UH32L_A_dUFqBty)1AodQ+{qjAa*R9{v4M zG9^WjD~#T8Euo^=8|ATbLp@88SntH7jiJ#zf$calPb_C!c8B&{;Dn@@8+I1d8x&@h zoj20&Q*wuMT?Vc##4^JP|8S(TkAl9~TRWS%P)fBvG^}|jmn!i&^I!M-`yZoGgZ-IV zg!VSbJT68<MXNsn+cV+sruQ7I{a-rxV zpDolJ>YzpOmyqM$T&>t4+L3oSe6rBHfy3#~zh4!%2fX7b^0;l0oizzJ?Ez^yzVvVP zwe;lS)D?q|EtvifXTi7mr*-<@30W@fe%z?-wH!Gvi548ZmWpw01!XscbVrr?*&-WQuc1EdGI5!?r}CXA$6c~&KMT6r1ur60 z^!~$ed2$;|G7{bqGcFut&QDu?M$K5RwwAC$EPtBIkp%R!pOI{FDz~0` zyE3#1nWvupP9?ldaNMpM9IAaNYz(?gV)!yU?2&F+?QJ`vjTm7M^tYMb*%flSY&M4f zF*ld#F1;D)Uaf?Qe`u~QK2KebN-pSa>5~@Zc_-gH zZ{Ym`YK*J+>spN$xAZsSaS<10QUu>Mb2_afV(-oyNu$)nVd#FJ7{}J04-``( zI64$@{4pJJ>fYv9nmm}gechc^i|ReChE|(#fSf>OorDiM^wdnkqn$D7cN|Av^0=J9 zO-ne>gi*7Jb?5jJ2kcEoSK>5>7MDH z?$gsVQ*Q2w#V;s00)6OB&dP6PhKMFSESOZx*-AaN`s5{9M1B{(vF4JlIVj0%x?#63 zv}v~>$$p!M$ahrt)=dWdd&7MxE|!y(-eR8jK;JXFi#YUVH{=oVB>q%8-&I8SB&;;8 zJji=_Q|hE$kgb*4zKHZXEUL03C}kcEJnEcK-rIWTQN}*kTSXYV<8{6YBN`275q`2SZvKM1`uz(7W$j`k6U?m5aYZHTz@{yL{;C=iqi zLQ|Q~SLMkm_>%E+sh0h`WJ&<*2y3P^L9zVJ6*w<_YRP%W4X|vL;+JCP3M49!AzF#! z?pNoDRyEv}=E8Y41G);?dP9l`+$}wYoh5Hc=UVskfH#}9r8JM`Cf`p{Q{I2QQ*i0@ zW1jzaoPL>MPL(fns*jA|WzpNc7Q89>2>aChBpX#98kw&bgw?A&)2NcUvoKt1KwT>6 zTDiY*NVBG=+Q)m@9vJD=B$X#QF~ z7OZE*@z;L-ppUi{x4~a9iu#E^bU7)bP>SU#V9|HT zjmp5~KOBF*Mg77<**#koc{je=KHAWZ>QF{MN|rat!DeP5j8<~5l?rA=%msqJyh{Fg zaOm3c5~X8`TAuiOP-lZi^^0eW)3)=&AXj#j8{6prX4e`x+0yFP8==}Rfs%N2V*B$bpaTEc@22}Q-19Ot z(^_wY{@r6n#2D<5nJi|FX}B-0`gR3VXLLP{nQq?aF=B_v9V)m)MsDHQ;3FyXneb=D zuhwoW?K!n4=-4wNcLg#v0M{$VIg?ZJ_un#1O*NlVyrZX6o#N1`^sPH;x~A#<$;*NB zT9q>|+Ci+@QF+Mey4?Ith;@e9_~6sqw;HUH(N;S7PFTxemjic5X(=-y=A|F6I9iJa zXPDsrM~nB}e7So3;NxLm7L3SM-wb7LQtb_kt6G);{_MQZU%UZ zlkQQeE$YGPv{6?#D1D|S(Ip~M{0GLF4|^JJ6a=?#mtuylObc4)>C{|+H5D=*xTd%p zD6gBuyQF3*hQz!#8K!}t7&y;%UTE0bjO`p{T~q4JoFmBWolY)%Lw4rP{Puqab#^^l z3~tg(HU%&(5|hl}7!9de>oOO~`bHyuoa?fZ;ckdVm@kn9sDT&aUNWh+RZiJk&k9{B zO5VjLRAf7Cwo9|jmi_q5>Wo;dnvI%v5h+M=CDDUkZSve`ai2NNP7|E90=}G%b=5ui zIa6QrsPMMVTemU4J-5Rj9jh%BZ_hG&zMM=o5Go8kk4!_*DCUn~p<%rPiFnh7<>k!Rn#_FPUale+eTOeNDA_c`EsC&1zknd^bDt(p)0 zAS=ximfLPZ(9iuKiEuC3rwf;^f#8TQ}CGn48xCt$h1Uh z-%J(f>*$FO`mQ)!UO}q-j9%gj3i&$aMAC>rJ5?jQil#bFd{#mq)QCv!&%(D_^GSCP zR*b%{$iY!6^&0bw_WR=dBr%JzpbgR+OYMq1=wF$QUzaUQd^|>#&ZjiU)*9*Scx#yG=tFJ+9F6*3#Tsu=ch)(M{jopdQVW z{QX(T{#l_kDrjMm2jfe`@g89wuPCz2R=2gX>Ob>e2<_b-s$jKEakXpL@X~+iZ+GwK zMV~$xWB?D^EKbd;=$NZK@L@Qj`hOv0{wItmQVW3G2aysW z|2yxOQmrsKeMy#1!&vL~^SgrglfR|i`lMl`<(ZZrbb_SS8nUS6UJ8~hn%#^4Mt$=u zSsM1jk3G)rzf&mCtV*d zx)ZX2KFlVzbu_2Udhr8mG|g^=DM#Yx=Vo6JktBB@_H-8t^MZ8~@WJ<+5mLYCb&(NRXzB9GcDCLQ@?yM@C?#h;OL^o9NR0npiLvTVSa*n zWwK#=C2_>U5UGl&e-VVD+X>mS0al9Kfi5-+6GqL4V4-9?xU8apz6n3w3qI&46YG6t zEnCdzo{)2kPvF4zyGsdYN!zv_e_ZHDMgbc{zhAu8W99`OyydpeI6(`GRY3-<$6sq` zh;U9rmB-?p&^Ym-y}bXTWw1Is7?z<~2Q4R^c!{Abj#sceR(o#k(q}cn??-kHT&W;` zowW)Nv(eoh*_e|;JY@bTH9Rh)2ouo#N3<&oPl_X+uv!sM8|@%{-&1rS5`uP@`q;MI zcKHe+Y1sRKaqA08UAWh`B&$@yV7Ks+*a2*@Ea$j|cf)W=Wf&GI&Fcpcj7S(4Zo9+j z-i?44oYKxao8RF>1YU(3!hbPjpq*Q|7wIsnMY*iUNW?;}%`hzBv|=;5hC~_gwrj21 z1CSCCW}P%48aWs-5on7cy-?T>hI@@Vzd-MwT3CNtZQKW{H1AT1A}R%Q*S&mzYT0;D z1|R~atOCZF7x#?AJ|FXo;K{b8he!flX6XP38k^rx-MISK`VW|zUd>T;(5FrGi54M|TJSJz;2@#5#gZA6n@3TPS z5g$6s1GbNX|LTOUe&^vaA@)6{`$sacw?yRQ6_r8GR!Zn74to%9bHq`jZg(PW7Ynct z-{`z#ZWP^sKAH%VpHsuk~&8Sy@&l%c7o)zuq zoInsQCsI2fc1dvaAz&HFEb#EOtX!X)Q}Ey(uHOFfIM+y^#ahOEKP?>z{^}SN9=XGSn6_UU!)zHZioTAQl|QHn%?RcZRp!z1AWJAaPe3j4szot#nu& zSaxlOgs(18u=l~w{v^v5&Olbqi=D}~aG;UldY!N>z<IWlY?p4OgrYB)tibPVFCt%XIhrb5MJVHf8zm z$8p&KZvx!^U~JSjvtUetZq-%uU3@ibm&91jM~@-soq+4bz2DuJgrqyLu)tr)e8t>2 zC}Ur3{Ecom8EcZ5`?Ni$Zt%wtrku6Io=3_-BvRY(IMlp@ax=NfYo>Dz|;u#=92A#+vXSWpo9MA^u@qY0BpMs_Tv!q?2r_l zdhu#0<50v$EE@o8JW>MfDKd?WMm8tq$MX?j3xBIg3x7M5o2M5v+(=1F_k4)wp>^cG z)gZ$iFB-XlKfd3E&p#lm%RR>|EETv8%Dw)|lM4b>7+OVML(4_xWfJiMVdI?ju-bg% zse|$rY==ti@1||oS+VvON{)92iJ$ud8fwh|Dl^Czd-h81vcRv~T9RxZ0rx%p>+C%c zeQL^EKHo^j#9zre8Rv{(YQ+Ws{HCJk*VM^c&%31G%1RjcisEsnG#IFGu?em`^}}O! zh|B*yU4^X$k0j+<^#fNwuHg2(xr5=&fXC3pPBuQ0R1s|LJoGPkGBr2G9%TK!v7scG z-Uhqp9)H~VC5vGKEWiwPx~M0~NAebbADLb#wIO%^;=<7H375JpSKIi5t;h@viEOb_ zxU~cbBzmUg9CV=JpQIf}eam2z1kj1e8{^2pi*jPAO&i99PG3e}U^iSr=)xGXt9gol z5)PPmI^g|9n!q$a0)Tx}axi9*5DAD^zaBArp?LUG8WHy0^G3MNTu9x+cAn)R0tP%Po9Jfvy|e92Nl|bA7MUP-45>Fn)z?_mKsDNAv-g2a40?d=NCkPN7m7KkB^a?p z1mRD1)c)sFh~CpxPVlaC2T_ffLSwa#(<5w{4{Pt`_zCjw+GvxUts*zz5)t{*2N5%k zpq6!%U>nb}r?q|u&UcmDhI16t=o%jxcdrE*#_RpJZR0{hE+|SmlWSINLX@jGR3z{~ z9w$TqlS)q-bi-E7a(D=-xEB^}wXZPM!+T9%Uq5I`x^Xb|&0Q`vw(}-D>!7GPmut2b z!lw^02^HGuH6Z~MkObxnzZCegLtZ3L;L)01OMd*bLb%K$Q44q_r%<5$bcmzE>ACDk z#P`Tu(n@%2OM_5f`f98mQ4UzTw$c-k6{b2+#OHslcK0(h7@4*?TkV%>J_K^BO){?H zH9g43i_OA20$wP2?a^rxK!=KnvmHA8q|$~HovLGbhOKAT4bsc5RZfMu$Id#R_pgK& ziq#6ucUgv|kf7)7W1f41EeJUp?(O zO?<#MW+Lx=oA%|Mwrt(t-p#EEIVU9cpH?8`gsoi)&dQZh1n=)V-$ zPtpI5!umh!{HFrTB}f;`{eju%4*j15Sw^ajXIz6eyL-ye4(I185D3w{*45Ry!-erC z^+ZfjPEPOZ$Lw_1V*Yts1K_b|``d6xF)(~X9`h5v;a65g=6H_jB^mBsd?ZSSlZb@v z>2m%5$d(AdINpXqM7tw!Xa!WAjN_G4oTkPM?pIM(k^UKoLVA)|gSI_0#0F-4&ww7l zE()z*9#?H$vA*49Q#MlLe9#%D?ClD#4BbcYZo9z(+J1S3@#HatT>VL`4RJh6!4(65 zL`>L@$Q0rHXcN;F9M@hUM*B>@i)UBz8L{Z$@rTYfHpfaEEjH*`$lezhJ~4ew?$c0w zGUO)-4Eu-99f`aykMq-YoIK{!&4fr=6!R%zByb&chDA%%`C!;Y?)L;#lBJcOlKSn! z6WL!8>dN0V*#>{?zC@mXpWJ40@j z5wi#p+#5a3^GZYw%R84SM#fd08ZIWPo|Ne2iXOIz3LT`k**F3nX5d|T9WNnEt9}u5 zIk#H}YU_pr+6)+v8p8@itPoQ7q>(-=g!|omjeyVz>FizA=^tY*Y=bB?7;vnE-{u&T zskyr;iO{z-jF>1U4>&{Zd2ztF@_2H0EwM^PWeomqWrK;hbKM?N`T*)Ke}ow2*+9HQ z`-HE=#gHvn4I58FHB{i`bAFuewsZe;n8?dwa#nz2$6mckt?z|oMNXbX^e+|tw%`6! zW)5-ii4J>XLfl(mDW3EBocjC6c+F1hh6n0y37MZ-P7m56WKWM+s5Od<6(!&5=zyO$fMxJVHzmpw^QMeCL0#m?=o#w@}W&&2gY`m4g#Wr%Lr|HK4S2RN&lBZZ2& z^a#D;vF#4_=u6*e5jh85c{^a+ETr0@YPhPVcFG}?>z~Z?XlL09 zU?$t6S^8-N%F9a**`ToK!10XE%D*E_L7uTVSEB8^ofQ*lN6`{O4Xshgg;@fLf8VT$ zrCz$L$j5WOIIM`ucP9XTFQ`><>ZRN1xM(iVsnr+XZ&}@*+noAqIMl4N2_p3~bEJJd z-%CL0kxHIL*#(w`aLs;X05ZesCV5#eytZt=0t~_lL z+WbhX*gH((6N(VqGzpSb9XEPw3mHajO@?c zIxcTh1WoK(;;OL0wfpp%6{x0BEGPwLO@h?}oE6T5;hwOmF3Lr(AlY{7n#L(;7O@rr zdjrIf29cWpELh)kw^Qk&_0@vy$@l!+4(%S|3sH*~&-CeRV6Z*|MfMwV*vLzUs5L7Imzxb<7rhPNrrtS_=zf-e_>HRht4 zzA(tysuxex|Jb-PtsG^`432vw0 zUOL?j6WNIII_=_GHs*GKX4#Gup~j&x!^Crm?HO(bmzNKVgC4=ggRt=}xf$};X0ElD zgTcgEh%qJGIHmES00)irfxuO0Q^RkCHI?MSV|?p#gFx1$E3!h*S@)3(td>jjd` ze|7SC$CrI}Ysj)2&%(j;q=2+;A;)qZ>&ly`@ws;>a<8X0sqi|af|90DNh6?uY&Q^5 zThBVDpon2T4SCmc$BQ-fz8+GX6aMRil02-;0BO3(LS7V5T&nmBd`IzNB_6aVsa+Yu ztoEaoU5xGGNp>lhFIdn;sfqYVEPOo0Ce3=puuX5GP~0a)vyD2rEJ(SpEo?h$omNrf z=h?|g@_4T)vDmjkqRVq()!mCUpFCd<4zKrU_R4Rj;NS3hgQ!0EI-NLz^euet2t@2T zI@Oa{Qg@`UKc=d15B*f^_jJ{^p|g^`4#_YTEK@&+XeoV!G>J$?RvXnkIN=cLY6Yp) zFjwJ0OSW*Dmc`=8=T-zmG&Hi_4T!%M@&m$W+d^{nqKbSLVzSN9Q)BZJTl89rR9fVn z)K0a{igRm2#(OmZB}eo(0hL|p=;zZ7CBOW7&LJV9f!s#IKA$38WENRlezYlc|thRgzQNjbANGX=#aU=}G8OEmh8apmo*_i7o)9*@T^TwH7`b~nIw*AqlR&`JiI+x*=!t^hcSMR zbEP3(2Y2N{snWx2XYY7WBEa?=I=PA@J71feLL^Y#)+DDJztN9Y~V`+pHde)*YUxmWgnoz!(6XOg@11BEioJtO$?qcL4b zD>$&4AO6X|;H5WwY?rJ~mehyFTi1;*0FKCjhC z2cxcBPJ~~Q*?c3r%XODVz^pheY43= z{3_juO_fN0P0n$=e0wGSCp>I>B_Mj3xF}_xe_>;lSgk*O`MRs7+b*RIa$*#_+BUUCVAc*Z0BZ zhlhlmMc8)y{Hogo65YLV1y?&Q*A@Mm_TO%moGxsVp)P|6`< z5QgF3Y0`hz>9Gae6$cdFdp$LC>2VMF7v5i`|9znUs(FcbUV_K|W5%o2|3BZ<2Y~ds zy`sC;LcPOz9u=K`+D83|+Px;2P+6wY3&DT<5TFiueSP>gu~&)UIbKy0!m3PILI0)S6Nv( za*@oB+@rKcC@HWvAa*wWoMpVrA}?T6w(%FXF+h)F3h>R$KzUgFh2OW2W-g{AI6j?V z8=!rt&Pz&ww0;otEf~Eh{r$mqBhOho%gh>DUz9thZozYG?>>AYZ27(TWq}2!R>#Zk z%fF*<&c7fX92^{`e257CjsffNMLf;F(>b+KfY!tpnR1WFbmpYnU?P+=T&9ZJ8${q- z=R3q=m%k*0LPU|yi}N~!`N2c*%V7hIB;n;MU+pQZg4>x?bNGUnckaTS{tl1h7J(n7 z^l|PS5IgqSYZrdu8Ff%^hu%lzpLT`IuqU%zDCZFX;Ivbd><1$Xm8hGkU#sz{Vn-h6 zrXoF{*v{tjl%@rY(~03+3_dh$@JMmmVRTn|*CE&QLSd(NahO!rVAxHW?STsm!$=+6 zC-mU1xJ-Tz{(Fn$tT=H0_}-XJ_XDLpeC>bqRFQGcB7Dpr4}@bh}@ngep9>yl)1VSnhh1M zHUvFHx{tY-{sny`G%VxzZ08h@3(&tM+s2DL?qh7^oNwfW$vvzKYJAwaBtMe7pAe9G z<}{awZ8wU=$j^1@2zi)+PdU=eY)W~`|AaokJkRMumPr(sB6HlmN{NtHoXt*vZnr0B zv(9C($PQoCNi_|?R1A@0Z$oOii@D1f#t|^uH2llt`@+fXg%%R?(}GDtI!V(}Su=tN z=qGUi(ZsaCje;(YlygbakU4XtYZta4Cye4e^kwA!zyNvud{d7WqQH1bn`$k~x#L=5P|h+?zJxp;>&d-E1S9&R__z47KUawe?sToNOY zniT_dJG$$^?=f7oFi0wuC_WJ#jI^8+jL?LQx4%i9G*@5Nsf9xc3%K4q@88*bS#~oq zZQpP*@Z(7tb_fQp5pwo;Q3aN$!^sRdDavZYG-TI(t>{Xf6b z6}EWeb5dLv6jBPv$jFWVC4|ai|0PQP{ijPIG`H>&^ZHhtA(amkl@BAE592=`>N}%p zTfW~VeWZa$tL5wC_I2IIjX&Lg{y!4$qz@Oo8>J7I8)*u{fZFvjUe-`QF0fzTcQ6FO zP7ub-bOo@EG$`7DAJYN5Oh9FWwFn zfZA=Q2_t^pC|eQ~9um0-fFei{j!U6PC~B#hfE~raK?G1OmIsQd#HiHF2H^_SXN$R^ zZvWjhEao4ln*E<$=PH2f7geoBi;tYh$R4r%i@Y<3mjKBB&w%`|OI?yTLZ}rXFo*u6 zC44jC&%Jc5`>Y(7cfNG<`!_ifl9Q5>l7(}DhPVP`QPJ+j3~A=WE5BW3lQ1YvohYh9 z^yOc8gme+vVq?0<09503d}$wrw{`zjH!@gkWZ%gf?zQOajO5MNIVXzl_Xe$GEY!d6 zJsJ!5!yl=6k!9ttJQbStk!)@2K{|o1n7S`M^(sqTb)l66va3h!~mW6mR7w# z@;8ezbbsex^%iVqxx<3~Qy{`M%Om04!_N>OpzZUTqWjmFZOhu?-yYssvDRFiH2w*< zVkB8C?Jph~8RI?9Xz;q;%B-p(bbapNWWT&3GeCC@6PB8SlNx*=s`_1T=p!w=n>;&> zTfSaTr$@BhJOdt1@u)l?+N3{Rq9ShLfx;w;DLTKByVY7_-46Om5JLOYNd1A9nf+s3 zXUYNPj&ngVf0wrzPM^2_y&4oKm@j+E*i9L5wlk!&=!#-!WKoJ*VOV?+6!YqXOQ^=n;A_#0 zr|<7GMc29+pk?#bThcs^&uLtr>gOKKK(VV)l|@|2k;G4N+skY7ap~>Xc2(uDO&c_E zv~%AmWKF6owYlik`T~EszS(Ecq21Yrt94SKqC#q~me9g^k`=yru({G2h|XNSlXJd_ zdggY-@_R3cSYkp_|NT2k@^rbPH#0r!lWg$5Nl)yHVSJn4&D7GH2}M@#8>#7chKf^! z6s|>3UGb}C*I?Y_HWc8|eqN2ld^caoe?L%hy-l2U1WOdsPQ_O2IG88#S}VNxQ<{RZ zr~VLpzp(t-B(!CffwAZ-v-xWmD9;^4#r`Ctiu-6P&eyZf<9>9-fDKe`{5>jERTEP5 z#ZZZ5z|)Cw%kBNMJ#>|$!ga+8!a1wmG+tS_Y2#~AzUu(WE&j1;v8A`b*~WbkH(7f; z_)Gc{deocngwAt>W5nlmrujg|1r0V3(uCe&Ul0vG)ACHhC42c6zvzyFNL3y6u939X zxB`A89PRDL=cLj)t(CLV9YCRW2==VF1j|=!9P-MFU|p3LrE(_}1d6JS`_n$wEaW9* z5lULItaNTyCWw*)!|d&wwHLb$?fxLcjLR59a$y>o0r0gZSO)>I2lu=2M>%cttK@kz zYIRxK^82K7Rz(h-kxue_^3RG3rfL9NivV5n!=M#W88yC0)iCOJ*0s~up&L0SGiDSG zbTYY9;y_oo$ajvJ_L?0aU)B-u1@FY>vw2a@n@Xo6+P{{O`_%m^D`qR;dfl6pxX0U3 zX_T9|gPpC;$KzJl7LS90X(u&-5FeP{tFN@md{+i;#Jo>hQ&rEQ$t@Gide{CiWd3P{ z|29Asw?}J%@DTWI_S}0tdZ_+5>WH>Ayh|cSXos+7CDDSubFeCy=DsZ3GUxBkewC|N z<&~&vS5zsA{YsYOQEXyzjFBB)0%FgJ@<#I$rK5~2%=B(P;vVG*Kp!Kd7JD|?0I#3_SJ!kI@iaZ}j*28a% z6k5ili22zSJGVY(VN<7;*cJ|@oDyGT^N;3~|Kz4KFb%(quq_!o;2O-OX4o!G)D-;7 zH4}DIX4va+q~Ft_;{4lA%i(%fn2PV2!DnU00k_{lNp8@t?h;zQRt7h7Hm8vEn{(=5 zOy1zrA7iS&<$ed~GS)4vJ~2>gv%NCy{wZz1*(|kYK7K_n>((q^wykGwye8_KH>EeN z@T*v!n+ir+GyyDHQOZct$@c*7yoN>e*~^8}4lhXq!4K!ZEZ1yir8nmmPc~oQ5y^9Scys-c zQuBj9J;P2GpANZG#q^IF-Lr``10A;93J zlASVT_5nsR;gYP@T1iqO=QG4B>clqnqv3%X@+lvmL&Us{H)k}UHD)krRlDk18m9jN z{b2e1Xa$CiP5=+Vu*#P`S-R2NfKujlr(JGb>#mK`d#d+@AtQLREnD1KZlj<_>3gtp z&ofCrp5LW(owocp;LnM_xreF)7{(ISdWS`fl06w6H*4QFHb72z{rs|ekUEf2#ldPZ z=B71HBf)STe_Qd6-Q!-xfU^ko>(2&5-G3k5zoj+JL@$KWqvp0{kwAd_sO-s?YDJEH zwbD&Rzi>2`eK@^M8lRa<<&R27CFRBnM9{8z(KH;$TklkrFG-hcymb$%kLy%Pnz}sZ zRJz^3HWxrg-h4xSpL1%3ed2B7S2V1xG^Q``>tF3kIx(E;?mgWpV#8a79_Y~#mJNPo zqQ1lnv@hsvMB#C#?|zjONcc{sdQ3200Ps_ux!JkLWWOnvO{4QkQSM&P9h#jrR_c=` zSX-a^lceZtce5LP0F3TipQ+N$p4Obve<)_PxLnBPC+}gz^HV^Rid6Rs7P6@6^Ijy+!T_U3ln~(?fZu1Wvx+RY0$|Z4&0L(dXdK z=i%aW^DdIiPX)p+$^#jl#QQ0k%jRzAX-9p?)9D$jyuBz|yg$7UnDSk)?(b&q;*C6r=gBRcU$?H`QA)Mlj1hpo&QgBr3AK8!s30S{#a-e2XqFi}vYDEZ`m zCsboU7@BgeG5TwwZa!w@m{vI4z=_WEZ0-x%?Oi&5zFzz3o6$_MlKbj91nOsI`#gy|b!tCI1?SaKBRE~J5jkp2jm7;O7!)mdy-6ff!Hg@-x6~sFyyKC_}(P;L>k1gfA>^{ zS$?Upa<*g%E2)hC!t zEmQIEema~OjJ01p@0)mBOdFk2Oxoct>0o_Qs2yID%J-U{ErQLi*p10ZZ-exO zKPU3J$=%;>$7bYF(K<=_ub^koK3~z(&gw0@iWx02*}O(qS^)WjA*?9;%5xsQiPDL* z6R_T{UA?R78n`1c>2wS}<1GcXom#b=sBT}4`1CYD3u9FB?F-YjaSF0o=Y7m)4P$6G$w!e=LcVWIa$xv z?^Hb&eYX~7u4yBZqlSXp51w+J$`4E2SY&eS5&cm`4qYjJtf%N)xW5&1p3DE? z`!I4$x^y@GL2sYtsQBDmi$?^q#KyJ8^I)oL+L-W|PA!x?5qzLy9w$jC7&M7Nzt>f; zxw=8LVTm-iuj@*QBDW*Iy%R{OsdY8r-a7n-_{S4BJ*YO}LJQdrPuj(sIfKhKBH76BO)q5m?7JUCL%Nx3 zTI@B#98PjLS74Of?Ok5nm=7vJ{f`q2{S0b1+ivFBvW!NL-vLldhNw#XNec__CS8?k zEN_jE7*P%m^AWHEH7rfOvnv5yjY^py8fH8=E+2L8twKLJefJIWF4)8DIW#Ux^Pu*- z+Rp8%Q-t`rX0}vmb>N5#I1ZfQciW)3?R= z5^e<7txPT}3`iJV((`8``Pl}?+`7IDT;*)hj?1K*%GjF7kY^t*41gkH%cpJ&Qo6hov z5S5OKd+i6tyKRhV@8%JULes8(hkv}2-nUZ29wNTG^J-C9A>h1F@SB?#r68Z#`rw#xo@b4{ zQCIM3W|(Dnqys*-8UCIMpa!yaz4l*QDx)%_EP9N}we0~(1{02Rn(2m^9 z+2nK46>7Xt>3DTFjfU6)sfl!5o3hAUsbXPIFZnffp9diIG3$%RHUGVU57u|$AEc7TrnU5aV_T+-nmfMApCQNUgwPuKq@{#4925p<hvO#4(GAWr?2N@0HVt(Z1@ zk*O;G85JEqP~Y<=F9+m`hVk*c&LO(9YXjf*>QJMrhG9x>Z)I|O*mSpRdxFdjFTC+q zfvB;JaeXmTvuHnh@u=SswJm+#s8$U4d{l2A0cp@2+x-5O~C(D;9Bf{R~DJ(`U z`kmpGZO}vIR?E~*zU!)FT4UfYSXf|*ek<&e9NK&pur<>6S<+f)X+j+IPV z#&%&9|GFmhVBhRJoux2u>R%jLc-{oD`9~u<_mXcp*oM&YqJYJ2!cUO2nxol-$v4lG zg?k~QUT!z<5uPc;h0A1Hin4GuJ)2WIV0&6n%(TUq-~#@;>rTSgCP0cyp3hZHupU!y zoUHOb1Nn4W3$3y{s?%g0XWLeahcBQ*{5DSl4+k& zEe)oYLCQQYQp=DlNuhSuOJ>?SA&_vlGcHe`yCSQ~>h^XIUiqRoh40ljk=Iz$Xs_~I z-4VMxa=z?LZ1>5Sk+bc$C|`+mzCCe!d(3;C7oe7Lzl8>8lYQj>Clv#da}Y?EWi?od zwO(w_D}@GG-uy})6=<&=(ACaG4c4;JN1S}f0NvJg*`{+aOPTF z)z^j-4(ZclhDQLmlu7J)4o(?Y$l(vA8-aLymyJ4*}w>cl}of!^+fhn)16w z)v74$Uw0uF9uLO`@#iaD{8v!G~MA=$6vpc7jW;){=^??Hdvol89_-j~~R zaF^%Lro)MA-Ro7<(rc!8XifV^3W83RZk7YzWqp1iu5d4Hi02$>5&RSa)fUMKIRkox zzXkH<(K&G4_%&0|x~5l{ zE@H#9-<}oQD2{3XSrW`{a4T*#>GZ^b3GzYr!|S{(q6$O|treMvE5{b^uHW4*YQr6B zKzMvq7$YQ|N{r4QaCdx8_G0;S2`r^_;+k*mv$NwPB7@L~UO2*H%mSaAB294R%<-iZ zpA=_tdvcC-;Wr=Rz&piQ$uB$Btaf7VEJwB($mMNMi66j{QyZ_NdE*|+q{teU*m*mi zfsaseJLnHmcjUbMzUwZARx2{KN0I3>CG7 zch!zd-O1UVb?EPq9zRK_2eyP5V<^fZ!ihc$jjL`GhY0co?C6^sKY4Pec7Cx5{JS1{ z?#<3dmAgdi2W>ISp?pxKKQF%dfnQ7J0L|_=hAJi-Jl8yCP|J8%gL_<|vh(Bsj04e9 z&aU#{q*^JYoSU+ON%koRFF~58%QzE`Q@&cG8BR5|0f4_lFqrE!?p)_}(m=Fq`Pu%i zgR|eu4FH3#bc8S+g>7M8#vR^lt9Nb-RaaKei`@Ok-@DiPeMyw}xF-SKlxlfH?r3FA zZFNjHlE1ta7HkL8@oGynoCdlZ1Zm48BX%zVA2UHVAWGOEAj z;X5S~u2b`3fOG47F@4_r_kU@mjeu0{PK7-#Vl#cu^xeYj$NX-i7!uu=%Z3BO33%u59Fu4B`7KqR+lUJDdI!-BR-Wt9ceC&0}ta z=@zh)NOe>(8Ui_ro;)iKN8ukm_EPy8&1t=qkzgb6+JJ#+*yo_~cT8Y&wWCuD`7R_7 z$)BhHR7o`o$CDJZ^3Hn;rIzu6?|NqiBi&q4QEEBKC+n|N{SBkZJ|tUv)P8i3>BECq zLAseAF$LTZy1Rli>YOFP?^pxJA)0OgH;vVyPGGxR=(+KnbI*D2J9qA# ze7H0BL-ypu+I#-b^Q`sT|IFTdcGk)kiqgcp!wuTHn{1y24g}Y4EN`;q^i`ioEFN=; zw}AaGcir4i>CWK1@bL}K%Vmh>N9Uje+bum6&z!T&=L!ssVB3eC+qSpt>y5C5YYgvm zHkjPhN){4RbFT4Lj#`z(Xtyl`5Th$}XlMRYt~RL}cLltzBah4^KjNzxxBZ=lF`>HY zI!5p+>b{tVP-bck`vc7Bk(kQRS8K95t=N}%?xfQAJCUOfl5*C)<0TGHjFULKRID4d zPd#@O#EGvZaZ`ORq?ls7ZAg`P&?zI}8DTk_$%46)#AL~)3MdZJ$pb@q3`BMp_X#c* zJ5zG$!_9D|1Gw7929RN01!g#qExIo45*rh@*qp(apBxl3W{T6X{MG4W%~2DI7!dt> zS@BRflTuW>(aMF*N2v|$^r7XDFWHepnY*vQPq7{!9kXk>x$Cq&w^K==V|N1lU~7A! zu4B9G9f=*YfLneoDbuz<0e4K-YCx=h`keediapah@OsJt?@iHNP4k#b;axK3@C#g7 zt@%GsWh>1D){c~1F~6(fofwpxY<6Ge+Q1;%5TEUUOxnyOJ0V@R2fCVTxnTg#$A6opVs zI!LRq0jmoijRMnVy4p?Kp-YBOhpq|(QI-M-6NZ4K^>-=O zB3IT|QcbmnUc5j+>qfVj{Gj6g3-_7Y~XpclkRdJ4Jr)1wDi9 zy}QJ$f(B#|pJ?(9Kczp2jZi!IxJKx$P4Yf(!0qHe1!F(AeW2#$@hazm`=;uw z-lA=L65&NSlP|Ora)GzR$#HT;RPx@}=?1lE{SpbC?adpt&CI6Ivak}G zE5}bA27261ablhKLgOY+JWkT}QT!{indaGOHWB`Q9yW{3=13`l;<8K~g``q{$XICa zXlW<$dayjLR9H~9#^p2f?H~jm3QF$>P;T1Sa5r3|Zc+{(Ywnx+9u2kqCQ-}Eee?vm zx{6;3#Ft1&>`ZI(CCyrxhle6ri##q|x|cqMdPKax6Siu*BFN8`f9Cb|aBfYU2^0j`lWc7W1~BA=7itId1-p`4ZfElNMgwj5~x#W%yqkPSzYD1VdvncS0 z0@YBSXDMD#<^T*uX~$OeZwNwY8k!U@VLqcH0*d-_&}HdYj-gIQu*#+J1Ip#8Wrw#4 zE7eE6X|HL4NPs}uSU}1cgBpn zDC?q1N1g<%GB8WtX+2Vqekt(1N$s}Ig|s_T(vb{~8xRP#))0ZDG6}J;3x8}TP^fyj zlW@JvYtdZ#!ToQybe>J*AkzSGT@j0RTZ>umE8Tdki>wsFAcvB&+Y6?Q!SwpFj>2zZ zf8cs3u4c0rF-M-s(HI>4F1_chtcizHS8e6K2K$a7?e-pZi!8`XRt8Ae%mTuIagp9zRJ@`O>I%w9}%E<3(6bCMg zrbCA{hp)mXDLrsfwOGw82zj{zHIS#4LvY2;fbnBD-+8{TX@B4Edi}B9w*XZe%r{iM z+OhSgZ}){YIaj~z_9j!ld{{|*Z(l*P_SB4uJ>m0FmrWtd>fQ>PE=2R=#TtE@2lH%u z=8m@V%2xt-C|Klezh=fD&l_$_L`5$C9$|LU;^saZo{fc64DN7omW)(YJ(QZ0u0j zp5yAL)k=ft{u7lEM8nU~oe^?|1R*WB zWdPh2k4?43k0qvkD;dPU<@47RQfE@maR@>rdo0zKRq#^qc2*hM=w4H8M$~M4ND?h?#{rT?YE^W7 z8!yUL8&`|ypK79=CtfmlTJ5(4+pM{<~5AAMDxn6EB1p^<|vKyMTKka%Y+?FVlXxr%ET?1hfB1+s=CYeHV3B z(##0OdJ{&qDLVsLUSYV3lJ~gG>G$2l#46P&+X~%I(RWQRE?r?y22=cK%FN|>l7TOk z7j7WrXPDeRnaZ+ow)G|xY@w=k#zgC(Fijg}>wf2k8n)Pf&BiGBh30gzN;S1nvA2!i zyK+cH@^fr2O)Ygh;>bcrv*?I4$T9k*3ba8#qINs&=C6 z+@10p276-8s|d&gW0eF(>34dXmuq=I*>)8v&yLEb2^po1+K7G z>7>nA{`^PDGh+eG8pkQaQjP8SEZDvAprey)GnmU6Cu2QxQ^u4ekOWvpnOQC|))6-YVWa zJ0^5_HG@YdUAwZ_jaTkbl;6~r{_MX$?)8Pt-UG+6D(Oe>UM;n{H`I6Zgt9k8VhNQ&UP|d}dyEPB& zU}mLrhL5mS9`Q{pB?BLl2k5B!*ifmL)q>k<)2=VUK-XSvzFK*SvK}@0v9RMmL%Ck! zC+8g{bpZ%iH81IIARxC{+Wn=4WNN+*HIcZtL5R&}omC@Z50hy{|ypi*rK8 zpFiS+{XgYUREQ0xF5IQaKWr3IR%RIQ1r(LHAb>o%3o|RLBhgR;q!U-F0V*k-? zA36`CT3{gyT9;B=>To)ARt0gKpmcOU$Tit$fC{ZKJFr=m>fQEi@uF+Ki&h3pz+sHX zz{^J?7pTK--W498n;Q@$eo;>_uJcoYcM>tJrVVQ>XP$`Ze?FR_YHynlRe4HR^r;eg z+}ypavJ#sJ;OV!)9H_JPB;eU+CU0U&o`1al%_{Lnzg}kZh=uw+Hj`J+7IG44G80w^ z2SYbKPIrl(nr{j*9Q};uB;ekC1nXGuWSO-M#D$J8DC{pX4oDjFVAGet)~hKN)=y@A zRXSgM({aH!_pWvBeky3w|NZ);NbOOJ5x zK9xq|CpKhzKD%bh-a4u8el}?|YwLLof9e)s3LdCnQf8(tXy^%eR>d1uuk_}8t9+)d z#1w1|H50$zp^+O+c>4uNr@DSuJtk;bxVEAEVqQ;%ErH)L^UEg}nC% zZ6GeIQkVX`RM+RjlV(c2`xzg8c#rS1HtyC-toIdl9o7Lp4B!dDOG38$-Db@{(kcS} z|2J7f2sG1K7c`FF=*mFi1OyE7S{U z4~03yRX|&fZ$Ut32NjT+jJ~)&QUmJbtn23qHTE+wf%v&X6dXWmseGEvmse;`u_Lf(h`4#|BL9aaAmPyNd5)UAN+rf?%$68Z|EE#f0ILc zd%FE`f*c@XP&cSM6z+v0(v$eF^c*0HjtEb8J1707+u9yU4gmB0!Ru>@G4ck_eC_ zCcBG_izEUhiOKFF<06RwNn*0Q$hb%%K$4j3E;25X2#_QuyNisABmyLf$?hWKB8dP= zVzRr)xJV*El9=o+GA@z`kR&F%i;RmT0wjsa?jqwNi2zArvb)H*NFqRznCvbxE|Lh4 zBqqCyjEf`!B#FuHBI6>707+u9yU4gmB0!Ru>@G4ck_eC_{vX{%`_FU_D4aOQ15KRF z5%MLxj)H>fvYsZ`L^o#hu?pgz7E@Gi<^-x@bG)imFHwN0ZLmEF5XN>XB5z*K@ZIga zygBy4LC}!lH!6YEVNe89EFhT$$jp47CU`0V^}9l&l7>gcE#U)`9GAFH%3J--QkP-? zIohkn`PN~g;M>}#n3ySo_UzS8GNmtwT|hWB_I6n~1{T_ln%!U<{ve-y=5lb5PR^~J z{yrEaD>A-_dd{XH5N|FTLJ@Ry5MPhC$~(n;qYP}z!1>OL65H>bC83jGwLGm=v-LJe z#KzwvebHAt#5%mj!``9&`0etPpL6uikbgyc^j7mNxy<>}?<;l#iBztsIHY8o6Lnv` zoyf)P?3nP`+nDW+PZ&6i0ThzQ`8e$?T9f*9ln(%otJGJbzLrkgGI=0+p6dQo6S{XO zA3{sH69`ql@uJP}>2KXXqY~qcUBMx11%nLGT?Vij8-4Nc&yI0m-ywa==qSJx=5K}B z>wK70W*p1%>;>54>JwB3#T9l?7?ZdRuuyQM3YVb*q?Q2lMF8ecDxTM~^Qf+mH@!K1 z$|Y7_6EL6H3t-PGq7D-+IDYtYD!7p~*=SWfGyILp4j-$#|oSY1xI9&)Mj9 zg|yH|wZ5d_0fx%=vQe>ul?_>ZYDeX>VJ;q1)FKT=o=bD!0SDC(8h?MK3uvzAskb~< zQTZ5gcYk$i8ia?5H3Tfr#$Fs}|@p_B61xb@a53VFCvxUIw1Ffx@zj(sxsa3oO-xdWmYzyrMW&WBqrKe?}S+0IR>^}fZ CcfBS6 literal 0 HcmV?d00001 diff --git a/content/other/toolbelt/src/main/resources/toolbelt.toml b/content/other/toolbelt/src/main/resources/toolbelt.toml new file mode 100644 index 000000000..467cf376f --- /dev/null +++ b/content/other/toolbelt/src/main/resources/toolbelt.toml @@ -0,0 +1,2 @@ +is_enabled = false +allow_higher_tier_unlock = true diff --git a/content/skills/mining/build.gradle.kts b/content/skills/mining/build.gradle.kts index 68f2d54e8..343b567f0 100644 --- a/content/skills/mining/build.gradle.kts +++ b/content/skills/mining/build.gradle.kts @@ -9,4 +9,5 @@ dependencies { implementation(projects.api.dropTablePlugin) implementation(projects.content.quest) implementation(projects.content.skills.utils) + implementation(projects.content.other.toolbelt) } diff --git a/content/skills/mining/src/main/kotlin/org/rsmod/content/skills/mining/scripts/Mining.kt b/content/skills/mining/src/main/kotlin/org/rsmod/content/skills/mining/scripts/Mining.kt index fed50dcc0..592155594 100644 --- a/content/skills/mining/src/main/kotlin/org/rsmod/content/skills/mining/scripts/Mining.kt +++ b/content/skills/mining/src/main/kotlin/org/rsmod/content/skills/mining/scripts/Mining.kt @@ -29,6 +29,7 @@ import org.rsmod.api.script.onOpContentU import org.rsmod.api.stats.levelmod.InvisibleLevels import org.rsmod.api.stats.xpmod.XpModifiers import org.rsmod.api.table.mining.MiningRocksRow +import org.rsmod.content.other.toolbelt.Toolbelt import org.rsmod.content.quest.manager.QuestRequirements import org.rsmod.content.skills.mining.MiningEquipment.miningGloveExtras import org.rsmod.content.skills.mining.MiningEquipment.wearingChargedGlory @@ -423,12 +424,17 @@ constructor( fun findPickaxe(player: Player): InvObj? { val worn = player.wornPickaxe() val carried = player.carriedPickaxes() + val toolbelt = + Toolbelt.resolvePickaxe(player)?.takeIf { getInvObj(it).isUsablePickaxe(player) } val candidates = buildList { if (worn != null) { add(worn) } addAll(carried) + if (toolbelt != null) { + add(toolbelt) + } } return candidates.maxWithOrNull(pickaxeComparator) } diff --git a/content/skills/prayer/build.gradle.kts b/content/skills/prayer/build.gradle.kts index 24a14af58..779f3be13 100644 --- a/content/skills/prayer/build.gradle.kts +++ b/content/skills/prayer/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { implementation(projects.api.objCharges) implementation(projects.api.pluginCommons) implementation(projects.api.registry) + implementation(projects.content.other.toolbelt) implementation(projects.content.skills.utils) implementation(projects.engine.events) implementation(projects.engine.utilsBits) diff --git a/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/items/bonecrusher/BonecrusherNpcDropHook.kt b/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/items/bonecrusher/BonecrusherNpcDropHook.kt index 061f5b002..34111cdf3 100644 --- a/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/items/bonecrusher/BonecrusherNpcDropHook.kt +++ b/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/items/bonecrusher/BonecrusherNpcDropHook.kt @@ -6,16 +6,17 @@ import dev.openrune.util.Wearpos import jakarta.inject.Inject import jakarta.inject.Singleton import org.rsmod.api.area.checker.AreaChecker +import org.rsmod.api.death.NpcDeathDropContext +import org.rsmod.api.death.NpcDeathDropHook import org.rsmod.api.player.events.prayer.PrayerSkillAction import org.rsmod.api.player.events.skilling.SkillingActionCompleteEvent import org.rsmod.api.player.events.skilling.SkillingActionContext -import org.rsmod.api.player.vars.boolVarBit -import org.rsmod.api.player.vars.intVarBit -import org.rsmod.api.death.NpcDeathDropContext -import org.rsmod.api.death.NpcDeathDropHook import org.rsmod.api.player.stat.statAdvance import org.rsmod.api.player.stat.statBoost +import org.rsmod.api.player.vars.boolVarBit +import org.rsmod.api.player.vars.intVarBit import org.rsmod.api.table.prayer.SkillPrayerRow +import org.rsmod.content.other.toolbelt.Toolbelt import org.rsmod.events.EventBus import org.rsmod.game.entity.Player import org.rsmod.game.inv.InvObj @@ -55,12 +56,18 @@ constructor( val prayerXp = row.exp.toDouble() / 2 player.statAdvance("stat.prayer", prayerXp) - eventBus.publish(SkillingActionCompleteEvent(player = player, context = - SkillingActionContext.Prayer(PrayerSkillAction.BonecrusherCrushComplete( - boneItemInternal = internal, - experienceGranted = prayerXp, - catacombsBonePrayerRestore = row.prayerRestore - ))), + eventBus.publish( + SkillingActionCompleteEvent( + player = player, + context = + SkillingActionContext.Prayer( + PrayerSkillAction.BonecrusherCrushComplete( + boneItemInternal = internal, + experienceGranted = prayerXp, + catacombsBonePrayerRestore = row.prayerRestore, + ), + ), + ), ) applyPrayerRestoreFromCrush(player, row, source) @@ -71,7 +78,7 @@ constructor( private fun applyPrayerRestoreFromCrush( player: Player, row: SkillPrayerRow, - source: CrusherSource + source: CrusherSource, ) { val restore = row.prayerRestore if (restore <= 0) { @@ -91,10 +98,20 @@ constructor( } } - private fun Player.isBonecrusherNecklacePrayerSource(source: CrusherSource): Boolean = - source.inventory === worn && worn[source.slot]?.isType("obj.bonecrusher_necklace") == true + private fun Player.isBonecrusherNecklacePrayerSource(source: CrusherSource): Boolean { + if (source.fromToolbelt) { + return false + } + if (source.inventory !== worn) { + return false + } + return worn[source.slot]?.isType("obj.bonecrusher_necklace") == true + } private fun Player.isDragonboneNecklaceInvCrushPrayer(source: CrusherSource): Boolean { + if (source.fromToolbelt || source.inventory == null) { + return false + } if (source.inventory !== inv) { return false } @@ -118,6 +135,10 @@ constructor( return CrusherSource(inv, slot) } } + + if (Toolbelt.isEnabled() && Toolbelt.provides(this, "obj.bonecrusher") && bonecrusherCharges > 0) { + return CrusherSource(fromToolbelt = true) + } return null } @@ -129,7 +150,11 @@ constructor( return true } - private data class CrusherSource(val inventory: Inventory, val slot: Int) + private data class CrusherSource( + val inventory: Inventory? = null, + val slot: Int = -1, + val fromToolbelt: Boolean = false, + ) } private var Player.bonecrusherCharges by intVarBit("varbit.charges_bonecrusher_quantity") diff --git a/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/npcs/EctoGhostDiscipleDialogue.kt b/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/npcs/EctoGhostDiscipleDialogue.kt index 3a3c5e9ae..d69eaf432 100644 --- a/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/npcs/EctoGhostDiscipleDialogue.kt +++ b/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/npcs/EctoGhostDiscipleDialogue.kt @@ -5,6 +5,7 @@ import dev.openrune.rscm.RSCMType import org.rsmod.api.player.dialogue.Dialogue import org.rsmod.api.player.protect.ProtectedAccess import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.other.toolbelt.ToolbeltBonecrusherUnlock import org.rsmod.content.skills.prayer.ecto.ectoTokens import org.rsmod.game.entity.Npc import org.rsmod.plugin.scripts.PluginScript @@ -29,6 +30,24 @@ class EctoGhostDiscipleDialogue : PluginScript() { worn.any { ITEM_GHOSTSPEAK_AMULET.contains(it?.id ?: -1) } private suspend fun Dialogue.mainMenu() { + if (ToolbeltBonecrusherUnlock.canOfferUnlock(access.player)) { + when ( + choice2( + "Can you unlock the bonecrusher slot on my tool belt?", + 1, + "Ask about something else.", + 2, + ) + ) { + 1 -> unlockBonecrusherToolbeltSlot() + 2 -> standardMainMenu() + } + return + } + standardMainMenu() + } + + private suspend fun Dialogue.standardMainMenu() { when (choice5( "What is this strange fountain?", 1, @@ -39,7 +58,7 @@ class EctoGhostDiscipleDialogue : PluginScript() { "How do I receive Ectotokens?", 4, "Can I have the tokens I have earned?", - 5 + 5, )) { 1 -> explainFountain() 2 -> explainEctoplasm() @@ -49,6 +68,70 @@ class EctoGhostDiscipleDialogue : PluginScript() { } } + private suspend fun Dialogue.unlockBonecrusherToolbeltSlot() { + chatPlayer( + quiz, + "Can you unlock the bonecrusher slot on my tool belt?", + ) + chatNpc( + neutral, + "Ah, you carry a bonecrusher. For two thousand Ectotokens I can unlock the bonecrusher slot on your tool belt.", + ) + chatNpc( + neutral, + "That only unlocks the slot, mortal. You must still place the bonecrusher on your tool belt yourself.", + ) + when ( + choice2( + "Yes, unlock the bonecrusher tool belt slot. (${ToolbeltBonecrusherUnlock.ECTO_TOKEN_COST} Ectotokens)", + 1, + "No, not yet.", + 2, + ) + ) { + 1 -> confirmUnlockBonecrusherToolbeltSlot() + 2 -> mainMenu() + } + } + + private suspend fun Dialogue.confirmUnlockBonecrusherToolbeltSlot() { + when (val result = ToolbeltBonecrusherUnlock.tryUnlock(access)) { + ToolbeltBonecrusherUnlock.PayResult.Success -> { + chatNpc( + happy, + "It is done. The bonecrusher slot on your tool belt is unlocked. Add the bonecrusher to your tool belt when you are ready.", + ) + } + ToolbeltBonecrusherUnlock.PayResult.AlreadyUnlocked -> { + chatNpc(neutral, "That tool belt slot is already unlocked, mortal.") + } + is ToolbeltBonecrusherUnlock.PayResult.InsufficientTokens -> { + chatNpc( + neutral, + "You need ${ToolbeltBonecrusherUnlock.ECTO_TOKEN_COST} Ectotokens. You only have ${result.held}.", + ) + } + ToolbeltBonecrusherUnlock.PayResult.NeedsBonecrusher -> { + chatNpc( + neutral, + "You must carry a bonecrusher before I can unlock that tool belt slot.", + ) + } + is ToolbeltBonecrusherUnlock.PayResult.InsufficientPrayer -> { + chatNpc( + neutral, + "You need a Prayer level of ${result.required} before I can unlock that tool belt slot.", + ) + } + ToolbeltBonecrusherUnlock.PayResult.NotEnabled, + ToolbeltBonecrusherUnlock.PayResult.NoSlot, + -> { + chatNpc(neutral, "I cannot unlock that tool belt slot for you right now.") + } + } + mainMenu() + } + private suspend fun Dialogue.explainFountain() { chatPlayer(quiz, "What is this strange fountain?") chatNpc( diff --git a/content/skills/woodcutting/build.gradle.kts b/content/skills/woodcutting/build.gradle.kts index 7eca2dc86..746319809 100644 --- a/content/skills/woodcutting/build.gradle.kts +++ b/content/skills/woodcutting/build.gradle.kts @@ -7,4 +7,5 @@ dependencies { implementation(projects.api.pluginCommons) implementation(projects.content.quest) implementation(projects.content.skills.utils) + implementation(projects.content.other.toolbelt) } diff --git a/content/skills/woodcutting/src/main/kotlin/org/rsmod/content/skills/woodcutting/scripts/Woodcutting.kt b/content/skills/woodcutting/src/main/kotlin/org/rsmod/content/skills/woodcutting/scripts/Woodcutting.kt index 391d92e16..65fd8c363 100644 --- a/content/skills/woodcutting/src/main/kotlin/org/rsmod/content/skills/woodcutting/scripts/Woodcutting.kt +++ b/content/skills/woodcutting/src/main/kotlin/org/rsmod/content/skills/woodcutting/scripts/Woodcutting.kt @@ -32,6 +32,7 @@ import org.rsmod.api.script.onOpContentLoc3 import org.rsmod.api.script.onOpContentU import org.rsmod.api.stats.levelmod.InvisibleLevels import org.rsmod.api.stats.xpmod.XpModifiers +import org.rsmod.content.other.toolbelt.Toolbelt import org.rsmod.content.quest.manager.QuestRequirements import org.rsmod.content.skills.woodcutting.configs.WoodcuttingParams import org.rsmod.game.MapClock @@ -277,12 +278,16 @@ constructor( fun findAxe(player: Player, tree: ObjectServerType): InvObj? { val worn = player.wornAxe() val carried = player.carriedAxes() + val toolbelt = Toolbelt.resolveAxe(player)?.takeIf { getInvObj(it).isUsableAxe(player) } val candidates = buildList { if (worn != null) { add(worn) } addAll(carried) + if (toolbelt != null) { + add(toolbelt) + } } return candidates.maxWithOrNull(axeComparator(tree)) } diff --git a/engine/game/src/main/kotlin/org/rsmod/game/inv/InvVirtualStorage.kt b/engine/game/src/main/kotlin/org/rsmod/game/inv/InvVirtualStorage.kt index 9b1cfb365..5fbc58001 100644 --- a/engine/game/src/main/kotlin/org/rsmod/game/inv/InvVirtualStorage.kt +++ b/engine/game/src/main/kotlin/org/rsmod/game/inv/InvVirtualStorage.kt @@ -2,8 +2,12 @@ package org.rsmod.game.inv import org.rsmod.game.entity.Player -public fun interface InvVirtualStorage { +public interface InvVirtualStorage { public fun additionalCount(player: Player, inventory: Inventory, itemInternal: String): Int + + /** True when virtual storage provides any item belonging to [contentGroup]. */ + public fun providesContentGroup(player: Player, inventory: Inventory, contentGroup: Int): Boolean = + false } public object InvVirtualStorageHolder { diff --git a/engine/game/src/main/kotlin/org/rsmod/game/inv/Inventory.kt b/engine/game/src/main/kotlin/org/rsmod/game/inv/Inventory.kt index 130f91e84..72faa33d9 100644 --- a/engine/game/src/main/kotlin/org/rsmod/game/inv/Inventory.kt +++ b/engine/game/src/main/kotlin/org/rsmod/game/inv/Inventory.kt @@ -94,30 +94,59 @@ public class Inventory(public val type: InventoryServerType, public val objs: Ar public operator fun contains(type: String): Boolean { if (type.startsWith("content.")) { val content = type.asRSCM(RSCMType.CONTENT) - return objs.any { obj -> - obj != null && ServerCacheManager.getItem(obj.id)?.contentGroup == content + if ( + objs.any { obj -> + obj != null && + obj.count > 0 && + ServerCacheManager.getItem(obj.id)?.contentGroup == content + } + ) { + return true } + return virtualProvidesContent(content) } return physicalCount(type) > 0 || virtualCount(type) > 0 } public operator fun contains(type: ItemServerType): Boolean = - objs.any { type.isAssociatedWith(it) } + objs.any { it != null && it.count > 0 && type.isAssociatedWith(it) } || + virtualCount(type.internalName) > 0 public fun count(internal: String): Int = physicalCount(internal) + virtualCount(internal) - /** Slots-only count; ignores [InvVirtualStorage]. */ + public fun contentTotal(content: String): Int { + require(content.startsWith("content.")) { "Expected content.*: $content" } + val contentGroup = content.asRSCM(RSCMType.CONTENT) + var total = 0 + for (obj in objs) { + val stack = obj ?: continue + if (stack.count <= 0) { + continue + } + val type = ServerCacheManager.getItem(stack.id) ?: continue + if (type.contentGroup != contentGroup) { + continue + } + total += count(stack, type) + } + if (total > 0) { + return total + } + return if (virtualProvidesContent(contentGroup)) 1 else 0 + } + + /** Slots-only count; ignores [InvVirtualStorage]. Counts of 0 or less are treated as absent. */ public fun physicalCount(internal: String): Int { val objType = ServerCacheManager.getItem(internal.asRSCM(RSCMType.OBJ)) ?: error("Unable to find item: $internal") - val obj = objs.firstOrNull { it?.id == objType.id } ?: return 0 + val obj = objs.firstOrNull { it?.id == objType.id && (it?.count ?: 0) > 0 } ?: return 0 val singleStack = type.stack == InvStackType.Always || objType.isStackable if (singleStack) { - return obj.count + return obj.count.coerceAtLeast(0) } - return individualCount(obj) + return individualCount(obj).coerceAtLeast(0) } private fun virtualCount(internal: String): Int { @@ -126,6 +155,12 @@ public class Inventory(public val type: InventoryServerType, public val objs: Ar return storage.additionalCount(player, this, internal) } + private fun virtualProvidesContent(contentGroup: Int): Boolean { + val player = owner ?: return false + val storage = InvVirtualStorageHolder.instance ?: return false + return storage.providesContentGroup(player, this, contentGroup) + } + public fun count(obj: InvObj, objType: ItemServerType): Int { val singleStack = type.stack == InvStackType.Always || objType.isStackable if (singleStack) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 491b84f1e..cfcbc62f2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ shadow = "8.3.7" spotless = "7.0.4" postgresql-jdbc = "42.7.5" embedded-postgres = "2.2.2" -or2 = "2.4.12" +or2 = "2.4.14" openruneCentral = "2.0.1" or2ServerUtils = "0.8" jodatime = "2.14.0" diff --git a/or-cache/build.gradle.kts b/or-cache/build.gradle.kts index f9b69d3b5..a50d3ff01 100644 --- a/or-cache/build.gradle.kts +++ b/or-cache/build.gradle.kts @@ -13,6 +13,8 @@ dependencies { api(libs.or2.definition) api(libs.or2.filestore) api(libs.or2.filesystem) + api(projects.orCache.packApi) + implementation(projects.content.other.toolbelt.pack) implementation(projects.engine.map) implementation(projects.engine.routefinder) implementation("com.michael-bull.kotlin-inline-logger:kotlin-inline-logger:1.0.6") @@ -23,6 +25,7 @@ dependencies { implementation(libs.jackson.databind) implementation("dev.or2:toml-rsconfig:1.0") implementation(libs.fastutil) + implementation(libs.classgraph) } tasks { @@ -55,3 +58,5 @@ tasks { } } + + diff --git a/or-cache/pack-api/build.gradle.kts b/or-cache/pack-api/build.gradle.kts new file mode 100644 index 000000000..41a1ce969 --- /dev/null +++ b/or-cache/pack-api/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("base-conventions") +} + +dependencies { + api(libs.or2.definition) + api(libs.or2.filestore) + api(libs.or2.tools) +} diff --git a/or-cache/pack-api/src/main/kotlin/dev/openrune/pack/PluginPack.kt b/or-cache/pack-api/src/main/kotlin/dev/openrune/pack/PluginPack.kt new file mode 100644 index 000000000..c6014ff64 --- /dev/null +++ b/or-cache/pack-api/src/main/kotlin/dev/openrune/pack/PluginPack.kt @@ -0,0 +1,40 @@ +package dev.openrune.pack + +import dev.openrune.cache.filestore.definition.InterfaceType +import dev.openrune.cache.tools.tasks.CacheTask +import dev.openrune.definition.dbtables.DBTable +import java.io.File + +abstract class PluginPack { + /** + * When true, the full plugin ([configDirectories], sprites, cs2, scripts, interfaces, [dbTables]) + * is packed even if [isEnabled] is false. + */ + open fun shouldAlwaysPack(): Boolean = false + + open fun isEnabled(projectRoot: File): Boolean = true + + fun shouldPack(projectRoot: File): Boolean = shouldAlwaysPack() || isEnabled(projectRoot) + + /** + * Packed when [shouldPack] is false. Use for varbits, enums, and dbtables the server/client still + * need while feature scripts stay off. + */ + open fun configDirectoriesWhenDisabled(projectRoot: File): List = emptyList() + + open fun dbTablesWhenDisabled(): List = emptyList() + + open fun configDirectories(projectRoot: File): List = emptyList() + + open fun spriteDirectories(projectRoot: File): List = emptyList() + + open fun packScriptDirectories(projectRoot: File): List = emptyList() + + open fun cs2Directories(projectRoot: File): List = emptyList() + + open fun dbTables(): List = emptyList() + + open fun interfaces(): List = emptyList() + + open fun extraTasks(): List = emptyList() +} diff --git a/or-cache/src/main/kotlin/dev/openrune/CacheTools.kt b/or-cache/src/main/kotlin/dev/openrune/CacheTools.kt index 3eb4da0a5..265360f50 100644 --- a/or-cache/src/main/kotlin/dev/openrune/CacheTools.kt +++ b/or-cache/src/main/kotlin/dev/openrune/CacheTools.kt @@ -5,14 +5,17 @@ import dev.openrune.cache.gameval.GameValHandler import dev.openrune.cache.tools.Builder import dev.openrune.cache.tools.CacheEnvironment import dev.openrune.cache.tools.cs2.PackCs2 +import dev.openrune.cache.tools.cs2.SymbolsCustomConflictStrip 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.PackModels +import dev.openrune.cache.tools.tasks.impl.PackSprites import dev.openrune.cache.tools.tasks.impl.defs.PackConfig import dev.openrune.codegen.startEnumGeneration import dev.openrune.codegen.startGeneration import dev.openrune.definition.GameValGroupTypes +import dev.openrune.definition.dbtables.DBTable import dev.openrune.definition.type.DBRowType import dev.openrune.definition.type.DBTableType import dev.openrune.definition.type.EnumType @@ -33,15 +36,20 @@ import dev.openrune.tables.skills.Cooking import dev.openrune.tables.skills.Firemaking import dev.openrune.tables.skills.Herblore import dev.openrune.tables.skills.Mining -import dev.openrune.tables.skills.Slayer import dev.openrune.tables.skills.Runecrafting import dev.openrune.tables.skills.ShootingStars +import dev.openrune.tables.skills.Slayer import dev.openrune.tables.skills.Smithing import dev.openrune.tables.skills.prayer.EctofuntusBonemeal import dev.openrune.tables.skills.prayer.PrayerBlessedBone import dev.openrune.tables.skills.prayer.PrayerTable import dev.openrune.tools.MinifyServerCache import dev.openrune.tools.PackServerConfig +import dev.openrune.cache.tools.iftype.PackIfType +import dev.openrune.pack.PluginPack +import dev.openrune.pack.PluginPackLoader +import dev.openrune.packscript.PackScriptPhase +import dev.openrune.packscript.PackScripts import java.io.File import kotlin.system.exitProcess @@ -66,42 +74,44 @@ fun main(args: Array) { downloadRev(TaskType.valueOf(args.first().uppercase())) } -fun tablesToPack() = listOf( - GameframeTable.gameframe(), - Music.musicClassic(), - Music.musicModern(), - Firemaking.logs(), - Firemaking.firelighters(), - Firemaking.sources(), - PrayerTable.skillTable(), - PrayerBlessedBone.table(), - EctofuntusBonemeal.table(), - StatComponents.statsComponents(), - PickableObjects.pickableObjects(), - Mining.rocks(), - Cooking.foods(), - Cooking.ales(), - Herblore.unfinishedPotions(), - Herblore.finishedPotions(), - Herblore.cleaningHerbs(), - Herblore.barbarianMixes(), - Herblore.swampTar(), - Herblore.crushing(), - Smithing.bars(), - Smithing.cannonBalls(), - Smithing.dragonForge(), - Smithing.crystalSinging(), - Slayer.masters(), - Runecrafting.altars(), - Runecrafting.runes(), - Runecrafting.tiara(), - Runecrafting.combo(), - SettingConfigs.settings(), - DidYouKnow.didYouknow(), - InstanceSettingsTable.instanceSettings(), - ShootingStars.locations(), - ShopCurrencyTable.shopCurrencies(), -) +fun tablesToPack(): List { + return listOf( + GameframeTable.gameframe(), + Music.musicClassic(), + Music.musicModern(), + Firemaking.logs(), + Firemaking.firelighters(), + Firemaking.sources(), + PrayerTable.skillTable(), + PrayerBlessedBone.table(), + EctofuntusBonemeal.table(), + StatComponents.statsComponents(), + PickableObjects.pickableObjects(), + Mining.rocks(), + Cooking.foods(), + Cooking.ales(), + Herblore.unfinishedPotions(), + Herblore.finishedPotions(), + Herblore.cleaningHerbs(), + Herblore.barbarianMixes(), + Herblore.swampTar(), + Herblore.crushing(), + Smithing.bars(), + Smithing.cannonBalls(), + Smithing.dragonForge(), + Smithing.crystalSinging(), + Slayer.masters(), + Runecrafting.altars(), + Runecrafting.runes(), + Runecrafting.tiara(), + Runecrafting.combo(), + SettingConfigs.settings(), + DidYouKnow.didYouknow(), + InstanceSettingsTable.instanceSettings(), + ShootingStars.locations(), + ShopCurrencyTable.shopCurrencies(), + ) +} fun downloadRev(type: TaskType) { @@ -139,12 +149,50 @@ fun downloadRev(type: TaskType) { fun buildCache(taskType: TaskType) { GameValProvider.load("../", autoAssignIds = true) - val tasks: List = - listOf( - PackModels(File("../.data/raw-cache/models")), - PackConfig(File("../.data/raw-cache/server")), - PackDBTables(tablesToPack()) - ).toMutableList() + val projectRoot = File("..") + val pluginPacks = PluginPackLoader.load() + val activePacks = pluginPacks.filter { it.shouldPack(projectRoot) } + val disabledPacks = pluginPacks.filter { !it.shouldPack(projectRoot) } + + val pluginTables = + activePacks.flatMap { it.dbTables() } + disabledPacks.flatMap { it.dbTablesWhenDisabled() } + val pluginInterfaces = activePacks.flatMap { it.interfaces() } + val pluginTasks = activePacks.flatMap { it.extraTasks() } + val pluginConfigDirs = + ( + activePacks.flatMap { it.configDirectories(projectRoot) } + + disabledPacks.flatMap { it.configDirectoriesWhenDisabled(projectRoot) } + ).filter { it.isDirectory } + val pluginSpriteDirs = + activePacks.flatMap { it.spriteDirectories(projectRoot) }.filter { it.isDirectory } + val pluginScriptDirs = + activePacks.flatMap { it.packScriptDirectories(projectRoot) }.filter { it.isDirectory } + + syncPluginCs2(projectRoot, pluginPacks, activePacks) + + val allTables = tablesToPack() + pluginTables + + val tasks = mutableListOf( + PackModels(File("../.data/raw-cache/models")), + PackConfig(File("../.data/raw-cache/server")), + ) + pluginConfigDirs.forEach { tasks += PackConfig(it) } + + val legacySpritesDir = File("../.data/raw-cache/sprites") + if (legacySpritesDir.isDirectory) { + tasks += PackSprites(legacySpritesDir) + } + pluginSpriteDirs.forEach { tasks += PackSprites(it) } + + tasks += pluginTasks + if (pluginInterfaces.isNotEmpty()) { + tasks += PackIfType(pluginInterfaces) + } + tasks += PackCs2(File("../.data/raw-cache/cs2")) + tasks += PackDBTables(allTables) + if (pluginScriptDirs.isNotEmpty()) { + tasks += PackScripts(pluginScriptDirs, PackScriptPhase.AFTER_DB, allTables) + } val builder = Builder( @@ -157,7 +205,7 @@ fun buildCache(taskType: TaskType) { builder.extraTasks(*tasks.toTypedArray()).build().initialize() if (taskType == TaskType.BUILD) { - val serverTasks = tasks.filterNot { it is PackCs2 } + val serverTasks = tasks.filterNot { it is PackCs2 || it is PackIfType || it is PackScripts } builder.type = TaskType.SERVER_CACHE_BUILD @@ -178,6 +226,7 @@ fun buildCache(taskType: TaskType) { MinifyServerCache().init(getServerCacheLocation()) val cache = Cache.load(File(getServerCacheLocation()).toPath()) GamevalDumper.dumpCols(cache, revision.first) + GamevalDumper.dumpComponents(cache, revision.first) val type = GameValHandler.readGameVal(GameValGroupTypes.TABLETYPES, cache = cache, revision.first) @@ -223,3 +272,116 @@ fun readRevision(): Triple { Triple(major, minor, environment.uppercase()) } } + +private fun syncPluginCs2( + projectRoot: File, + allPacks: List, + activePacks: List, +) { + val cs2Root = File(projectRoot, ".data/raw-cache/cs2") + val customRoot = File(cs2Root, "custom").also { it.mkdirs() } + val symbolsCustom = File(cs2Root, "symbols_custom").also { it.mkdirs() } + + fun packName(pack: PluginPack): String = + pack::class.java.name.substringAfterLast('.').removeSuffix("PluginPack").lowercase() + + val activeNames = activePacks.map(::packName).toSet() + val allNames = allPacks.map(::packName).toSet() + + customRoot.listFiles()?.forEach { child -> + if (child.isDirectory && child.name.lowercase() in allNames && child.name.lowercase() !in activeNames) { + child.deleteRecursively() + } + } + File(customRoot, "_plugins").takeIf { it.exists() }?.deleteRecursively() + File(symbolsCustom, "_plugins").takeIf { it.exists() }?.deleteRecursively() + + for (pack in allPacks) { + for (dir in pack.cs2Directories(projectRoot).filter { it.isDirectory }) { + val symbols = File(dir, "symbols") + if (!symbols.isDirectory) continue + symbols.listFiles()?.filter { it.isFile && it.extension.equals("sym", true) }?.forEach { sym -> + stripSymbolLines(File(symbolsCustom, sym.name), readSymbolLines(sym)) + } + } + } + + for (pack in activePacks) { + val name = packName(pack) + val scriptDest = + File(customRoot, name).also { + it.deleteRecursively() + it.mkdirs() + } + + for (dir in pack.cs2Directories(projectRoot).filter { it.isDirectory }) { + val scripts = File(dir, "script") + if (scripts.isDirectory) { + scripts.walkTopDown().filter { it.isFile && it.extension.equals("cs2", true) }.forEach { src -> + src.copyTo(File(scriptDest, src.name), overwrite = true) + } + } + dir.listFiles()?.filter { it.isFile && it.extension.equals("cs2", true) }?.forEach { src -> + src.copyTo(File(scriptDest, src.name), overwrite = true) + } + + val symbols = File(dir, "symbols") + if (symbols.isDirectory) { + symbols.listFiles()?.filter { it.isFile && it.extension.equals("sym", true) }?.forEach { sym -> + mergeSymbolLines(File(symbolsCustom, sym.name), readSymbolLines(sym)) + } + } + } + } + + SymbolsCustomConflictStrip.strip(cs2Root) +} + +private val SYMBOL_LINE = Regex("""^\s*(\S+)\s+(.+?)\s*$""") + +/** id → remainder of the line (name, and for dbcolumn also the type). */ +private fun readSymbolLines(file: File): List> = + file.readLines().mapNotNull { line -> + val trimmed = line.trim() + if (trimmed.isEmpty() || trimmed.startsWith("#")) return@mapNotNull null + val match = SYMBOL_LINE.matchEntire(trimmed) ?: return@mapNotNull null + match.groupValues[1] to match.groupValues[2].trim() + } + +private fun symbolName(rest: String): String = rest.substringBefore('\t').substringBefore(' ').trim() + +private fun stripSymbolLines(target: File, owned: List>) { + if (!target.exists() || owned.isEmpty()) return + val ownedIds = owned.map { it.first }.toSet() + val ownedNames = owned.map { symbolName(it.second) }.toSet() + val kept = + target.readLines().filter { line -> + val trimmed = line.trim() + if (trimmed.isEmpty()) return@filter false + val match = SYMBOL_LINE.matchEntire(trimmed) ?: return@filter true + val id = match.groupValues[1] + val name = symbolName(match.groupValues[2]) + id !in ownedIds && name !in ownedNames + } + if (kept.isEmpty()) { + target.delete() + } else { + target.writeText(kept.joinToString("\n", postfix = "\n")) + } +} + +private fun mergeSymbolLines(target: File, lines: List>) { + if (lines.isEmpty()) return + stripSymbolLines(target, lines) + val existing = if (target.exists()) target.readText().trimEnd() else "" + val body = lines.joinToString("\n") { (id, rest) -> "$id\t$rest" } + target.parentFile?.mkdirs() + target.writeText( + buildString { + if (existing.isNotBlank()) { + appendLine(existing) + } + appendLine(body) + }, + ) +} diff --git a/or-cache/src/main/kotlin/dev/openrune/codec/osrs/impl/ItemServerCodec.kt b/or-cache/src/main/kotlin/dev/openrune/codec/osrs/impl/ItemServerCodec.kt index e6be8f061..88a8bf6eb 100644 --- a/or-cache/src/main/kotlin/dev/openrune/codec/osrs/impl/ItemServerCodec.kt +++ b/or-cache/src/main/kotlin/dev/openrune/codec/osrs/impl/ItemServerCodec.kt @@ -96,7 +96,7 @@ class ItemServerCodec( stockmarket = item.stockMarket category = item.category options = item.options - interfaceOptions = item.interfaceOptions + interfaceOptions = item.interfaceOptions.toMutableList() certlink = item.noteLinkId certtemplate = item.noteTemplateId placeholderLink = item.placeholderLink diff --git a/or-cache/src/main/kotlin/dev/openrune/gamevals/GameValProvider.kt b/or-cache/src/main/kotlin/dev/openrune/gamevals/GameValProvider.kt index 453e29f7d..3303dc49c 100644 --- a/or-cache/src/main/kotlin/dev/openrune/gamevals/GameValProvider.kt +++ b/or-cache/src/main/kotlin/dev/openrune/gamevals/GameValProvider.kt @@ -21,6 +21,7 @@ class GameValProvider : MappingProvider { fun sourceFiles(rootDir: String): Array = arrayOf( Paths.get("${rootDir}.data", "gamevals-binary", "gamevals.dat").toFile(), + Paths.get("${rootDir}.data", "gamevals-binary", "gamevals_components.dat").toFile(), Paths.get("${rootDir}.data", "gamevals-binary", "gamevals_columns.dat").toFile(), Paths.get("${rootDir}content").toFile(), Paths.get("${rootDir}api").toFile(), @@ -42,16 +43,17 @@ class GameValProvider : MappingProvider { } override fun load(vararg files: File) { - require(files.size >= 2) { - "Expected at least two files for loading: gamevals.dat and gamevals_columns.dat" + require(files.size >= 3) { + "Expected at least three files for loading: gamevals.dat, gamevals_components.dat and gamevals_columns.dat" } decodeGameValDat(files[0]) decodeGameValDat(files[1]) + decodeGameValDat(files[2]) - val contentDir = files.getOrNull(2)?.takeIf { it.exists() && it.isDirectory } - val apiDir = files.getOrNull(3)?.takeIf { it.exists() && it.isDirectory } - val gamevalsDir = files.getOrNull(4)?.takeIf { it.exists() && it.isDirectory } + val contentDir = files.getOrNull(3)?.takeIf { it.exists() && it.isDirectory } + val apiDir = files.getOrNull(4)?.takeIf { it.exists() && it.isDirectory } + val gamevalsDir = files.getOrNull(5)?.takeIf { it.exists() && it.isDirectory } if (autoAssignIds && (contentDir != null || apiDir != null || gamevalsDir != null)) { GameValAutoAssigner(mappings, maxBaseID).run(contentDir, apiDir, gamevalsDir) @@ -102,16 +104,16 @@ class GameValProvider : MappingProvider { } private fun parseGameValTomlEntry(line: String, source: String): Pair? { - if (line.startsWith("[")) { - return null - } - val equalsIndex = line.indexOf('=') if (equalsIndex <= 0) { return null } - val key = line.substring(0, equalsIndex).trim() + val key = + line.substring(0, equalsIndex) + .trim() + .removeSurrounding("\"") + .removeSurrounding("'") val value = line.substring(equalsIndex + 1).trim().toIntOrNull() if (value == null) { return null @@ -194,6 +196,9 @@ class GameValProvider : MappingProvider { } private fun decodeGameValDat(datFile: File) { + if (!datFile.isFile) { + return + } DataInputStream(FileInputStream(datFile)).use { input -> val tableCount = input.readInt() repeat(tableCount) { diff --git a/or-cache/src/main/kotlin/dev/openrune/gamevals/GamevalDumper.kt b/or-cache/src/main/kotlin/dev/openrune/gamevals/GamevalDumper.kt index b78b51641..e31c27743 100644 --- a/or-cache/src/main/kotlin/dev/openrune/gamevals/GamevalDumper.kt +++ b/or-cache/src/main/kotlin/dev/openrune/gamevals/GamevalDumper.kt @@ -40,16 +40,7 @@ object GamevalDumper { GameValGroupTypes.IFTYPES_V2 -> { val interfaces = elements.mapNotNull { it.elementAs() } - - gamevals["interfaces"] = - interfaces.map { "${it.name}=${it.id}" } - - gamevals["components"] = - interfaces.flatMap { iface -> - iface.components.map { comp -> - "${iface.name}:${comp.name}=${comp.packed}" - } - } + gamevals["interfaces"] = interfaces.map { "${it.name}=${it.id}" } } GameValGroupTypes.IFTYPES -> Unit @@ -65,6 +56,21 @@ object GamevalDumper { encodeGameValDat(File(outputDir, "gamevals.dat").path, gamevals) dumpCols(cache, rev) + dumpComponents(cache, rev) + } + + fun dumpComponents(cache: Cache, rev: Int) { + val elements = GameValHandler.readGameVal(GameValGroupTypes.IFTYPES_V2, cache = cache, rev) + val data = mutableListOf() + + elements.forEach { gameValElement -> + val iftype = gameValElement.elementAs() ?: return@forEach + iftype.components.forEach { comp -> + data.add("${iftype.name}:${comp.name}=${comp.packed}") + } + } + + encodeGameValDat("../.data/gamevals-binary/gamevals_components.dat", mapOf("component" to data)) } fun dumpCols(cache: Cache, rev: Int) { diff --git a/or-cache/src/main/kotlin/dev/openrune/pack/PluginPackLoader.kt b/or-cache/src/main/kotlin/dev/openrune/pack/PluginPackLoader.kt new file mode 100644 index 000000000..b800c570b --- /dev/null +++ b/or-cache/src/main/kotlin/dev/openrune/pack/PluginPackLoader.kt @@ -0,0 +1,26 @@ +package dev.openrune.pack + +import io.github.classgraph.ClassGraph + +object PluginPackLoader { + private val packages = arrayOf("dev.openrune.pack", "org.rsmod.content") + + fun load(): List { + val packs = mutableListOf() + ClassGraph() + .ignoreClassVisibility() + .enableClassInfo() + .disableNestedJarScanning() + .disableModuleScanning() + .acceptPackages(*packages) + .scan() + .use { result -> + result.getSubclasses(PluginPack::class.java).directOnly().forEach { info -> + val clazz = info.loadClass(PluginPack::class.java) + val ctor = clazz.getConstructor() + packs += ctor.newInstance() + } + } + return packs + } +} diff --git a/or-cache/src/main/kotlin/dev/openrune/packscript/PackLangAst.kt b/or-cache/src/main/kotlin/dev/openrune/packscript/PackLangAst.kt new file mode 100644 index 000000000..9e7b375ad --- /dev/null +++ b/or-cache/src/main/kotlin/dev/openrune/packscript/PackLangAst.kt @@ -0,0 +1,99 @@ +package dev.openrune.packscript + +/** + * RunePack — tiny Kotlin-flavoured pack-time scripting. + * + * ``` + * [after_db.toolbelt_items] + * for obj in db.toolbelt_slots.allowed_items { + * val ops = oc.iops(obj) + * val anchor = ops.indexOfFirst { it == "Wear" || it == "Wield" } + * oc.addiop(obj, "Add-to-toolbelt", slot) + * } + * ``` + */ +data class PackScript( + val phase: PackScriptPhase, + val name: String, + val body: List, + val source: String, +) + +sealed interface Stmt { + data class ForIn( + val binding: String, + val iterable: Expr, + val body: List, + ) : Stmt + + data class While( + val cond: Expr, + val body: List, + ) : Stmt + + data class If( + val cond: Expr, + val thenBody: List, + val elseBody: List, + ) : Stmt + + data class Val( + val name: String, + val value: Expr, + val mutable: Boolean, + ) : Stmt + + data class Assign( + val name: String, + val value: Expr, + ) : Stmt + + data class Call(val path: List, val args: List) : Stmt +} + +sealed interface Expr { + data class StringLit(val value: String) : Expr + + data class IntLit(val value: Int) : Expr + + data class BoolLit(val value: Boolean) : Expr + + data object NullLit : Expr + + data class Ident(val name: String) : Expr + + data class Path(val parts: List) : Expr + + data class Call(val path: List, val args: List) : Expr + + /** `{ it -> expr }` or `{ expr }` (param defaults to `it`). */ + data class Lambda(val param: String, val body: Expr) : Expr + + data class Unary(val op: UnaryOp, val expr: Expr) : Expr + + data class Binary(val op: BinaryOp, val left: Expr, val right: Expr) : Expr + + data class IfExpr(val cond: Expr, val thenExpr: Expr, val elseExpr: Expr) : Expr +} + +/** Runtime closure created when a lambda expression is evaluated. */ +data class LambdaValue( + val param: String, + val body: Expr, + val closure: Map, +) + +enum class UnaryOp { NOT, NEG } + +enum class BinaryOp { + EQ, + NEQ, + LT, + LTE, + GT, + GTE, + AND, + OR, + ADD, + SUB, +} diff --git a/or-cache/src/main/kotlin/dev/openrune/packscript/PackLangParser.kt b/or-cache/src/main/kotlin/dev/openrune/packscript/PackLangParser.kt new file mode 100644 index 000000000..0838ec929 --- /dev/null +++ b/or-cache/src/main/kotlin/dev/openrune/packscript/PackLangParser.kt @@ -0,0 +1,547 @@ +package dev.openrune.packscript + +import java.io.File + +/** + * Parser for RunePack (Kotlin-lite): + * + * ``` + * [after_db.name] + * val x = oc.equipable(obj) + * if (x) { ... } else { ... } + * for obj in db.table.col { oc.addiop(obj, "...") } + * ``` + */ +object PackLangParser { + fun parseFile(file: File): PackScript { + val text = file.readText() + return parse(text, file.name) + } + + fun parse(source: String, sourceName: String = "