Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,7 @@ CLAUDE.md
logs/
*.log
/.cursor
/.data/raw-cache/cs2

# Extracted library sources (do not commit)
/dev/

Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -3324,23 +3324,21 @@ 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 {
return inv.count(obj)
}

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)
}

/**
Expand All @@ -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)
}
Expand Down
7 changes: 6 additions & 1 deletion build-logic/src/main/kotlin/base-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions content/other/toolbelt/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
17 changes: 17 additions & 0 deletions content/other/toolbelt/pack/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -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 }
}
}
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -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<File> {
if (!isEnabled(projectRoot)) {
return emptyList()
}
return listOf(ToolbeltPackSettings.scriptDirectory(projectRoot))
}

override fun configDirectories(projectRoot: File): List<File> =
listOf(ToolbeltPackSettings.configDirectory(projectRoot))

override fun spriteDirectories(projectRoot: File): List<File> =
listOf(ToolbeltPackSettings.spriteDirectory(projectRoot))

override fun cs2Directories(projectRoot: File): List<File> =
listOf(ToolbeltPackSettings.cs2Directory(projectRoot))

override fun dbTables(): List<DBTable> = listOf(ToolbeltTable.slots())

override fun interfaces(): List<InterfaceType> =
listOf(buildWornItemsToolbelt(), buildToolbeltInterface())
}
Loading
Loading