Stale bot references removed, energy barrier fixes, catapult interface fixes

This commit is contained in:
dam 2026-04-01 16:48:58 +03:00
parent 5f34e94c96
commit 0d7d12a331
No known key found for this signature in database
GPG key ID: 4AF4E722399663FB
7 changed files with 173 additions and 163 deletions

View file

@ -7,6 +7,7 @@ import content.minigame.castlewars.CatapultConfig.INTERFACE_ID
import core.api.*
import core.game.interaction.InterfaceListener
import core.game.node.entity.player.Player
import core.game.system.task.Pulse
import core.game.world.map.Location
class CastleWarsCatapultInterface : InterfaceListener {
@ -20,7 +21,13 @@ class CastleWarsCatapultInterface : InterfaceListener {
}
onOpen(INTERFACE_ID) { player, _ ->
renderer.resetToDefaults(player)
renderCurrentCoordinate(player)
submitWorldPulse(object : Pulse(1) {
override fun pulse(): Boolean {
renderCurrentCoordinate(player)
return true
}
})
true
}
@ -77,8 +84,7 @@ class CastleWarsCatapultInterface : InterfaceListener {
setVarbit(player, VARBIT_CATAPULT_X, newCoord.x)
setVarbit(player, VARBIT_CATAPULT_Y, newCoord.y)
renderer.updateMarkerPosition(player, newCoord)
renderer.updateDigitDisplay(player, newCoord)
renderer.renderCoordinate(player, newCoord)
return true
}
@ -89,6 +95,10 @@ class CastleWarsCatapultInterface : InterfaceListener {
return Coordinate(x, y)
}
private fun renderCurrentCoordinate(player: Player) {
renderer.renderCoordinate(player, getCurrentCoordinate(player))
}
private fun getPlayerTeam(player: Player): CastleWarsTeam? {
return CastleWarsGameState.getPlayerTeam(player)
}

View file

@ -1,14 +1,13 @@
package content.minigame.castlewars
import content.minigame.castlewars.bots.CastleWarsBotSpawner
import core.api.Commands
import core.api.sendMessage
import core.game.interaction.Option
import core.game.system.command.Privilege
import content.minigame.castlewars.areas.CastleWarsGameArea
import content.minigame.castlewars.areas.CastleWarsGameArea.Companion.saradominPlayers
import content.minigame.castlewars.areas.CastleWarsGameArea.Companion.zamorakPlayers
import content.minigame.castlewars.areas.CastleWarsWaitingArea
import core.api.Commands
import core.api.sendMessage
import core.game.interaction.Option
import core.game.system.command.Privilege
/**
* Admin commands for Castle Wars development and testing.
@ -16,91 +15,61 @@ import content.minigame.castlewars.areas.CastleWarsWaitingArea
* Commands:
* - ::cw start - Force starts a game if both teams have players
* - ::cw end - Ends the current game
* - ::cw addbot <sara|zammy> [amount] - Spawns bots for the specified team
*/
class CastleWarsCommands : Commands {
override fun defineCommands() {
define("cw", Privilege.ADMIN, "::cw <start|end|addbot>", "Castle Wars admin commands") { player, args ->
if (args.size < 2) {
sendMessage(player, "Usage: ::cw <start|end|addbot [sara|zammy] [amount]>")
return@define
}
when (args[1].lowercase()) {
"start" -> {
if (CastleWarsGameArea.ticksLeftInGame > 0) {
sendMessage(player, "A Castle Wars game is already in progress!")
return@define
}
val saraCount = CastleWarsWaitingArea.waitingSaradominPlayers.size
val zamCount = CastleWarsWaitingArea.waitingZamorakPlayers.size
if (saraCount == 0 || zamCount == 0) {
sendMessage(player, "Both teams need at least 1 player. Saradomin: $saraCount, Zamorak: $zamCount")
return@define
}
CastleWarsGameArea.startGame()
sendMessage(player, "Castle Wars game force started! Saradomin: $saraCount, Zamorak: $zamCount")
}
"end" -> {
if (CastleWarsGameArea.ticksLeftInGame <= 0) {
sendMessage(player, "No Castle Wars game is currently running.")
return@define
}
// Snapshot the sets before endGame() modifies them via teleport callbacks
val saraSnapshot = saradominPlayers.toSet()
val zamSnapshot = zamorakPlayers.toSet()
CastleWarsGameState.endGame()
CastleWarsItemHandler.clearAllBarricades()
for (p in saraSnapshot) {
p.interaction.remove(Option._P_ATTACK)
p.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
for (p in zamSnapshot) {
p.interaction.remove(Option._P_ATTACK)
p.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
saradominPlayers.clear()
zamorakPlayers.clear()
sendMessage(player, "Castle Wars game ended by admin command.")
}
"addbot" -> {
if (args.size < 3) {
sendMessage(player, "Usage: ::cw addbot <sara|zammy> [amount]")
return@define
}
val teamArg = args[2].lowercase()
val team = when {
teamArg.startsWith("sara") -> CastleWarsTeam.SARADOMIN
teamArg.startsWith("zam") -> CastleWarsTeam.ZAMORAK
else -> {
sendMessage(player, "Invalid team. Use 'sara' or 'zammy'.")
return@define
}
}
val amount = if (args.size >= 4) {
args[3].toIntOrNull() ?: run {
sendMessage(player, "Invalid amount. Must be a number.")
return@define
}
} else {
1
}
if (amount !in 1..20) {
sendMessage(player, "Amount must be between 1 and 20.")
return@define
}
for (i in 0 until amount) {
CastleWarsBotSpawner.spawnBot(team)
}
sendMessage(player, "Spawned $amount ${team.teamName} bot(s) at the Castle Wars lobby.")
}
else -> {
sendMessage(player, "Unknown subcommand. Use: start, end, or addbot")
}
}
}
}
override fun defineCommands() {
define("cw", Privilege.ADMIN, "::cw <start|end>", "Castle Wars admin commands") { player, args ->
if (args.size < 2) {
sendMessage(player, "Usage: ::cw <start|end>")
return@define
}
when (args[1].lowercase()) {
"start" -> {
if (CastleWarsGameArea.ticksLeftInGame > 0) {
sendMessage(player, "A Castle Wars game is already in progress!")
return@define
}
val saraCount = CastleWarsWaitingArea.waitingSaradominPlayers.size
val zamCount = CastleWarsWaitingArea.waitingZamorakPlayers.size
if (saraCount == 0 || zamCount == 0) {
sendMessage(
player,
"Both teams need at least 1 player. Saradomin: $saraCount, Zamorak: $zamCount"
)
return@define
}
CastleWarsGameArea.startGame()
sendMessage(player, "Castle Wars game force started! Saradomin: $saraCount, Zamorak: $zamCount")
}
"end" -> {
if (CastleWarsGameArea.ticksLeftInGame <= 0) {
sendMessage(player, "No Castle Wars game is currently running.")
return@define
}
// Snapshot the sets before endGame() modifies them via teleport callbacks
val saraSnapshot = saradominPlayers.toSet()
val zamSnapshot = zamorakPlayers.toSet()
CastleWarsGameState.endGame()
CastleWarsItemHandler.clearAllBarricades()
for (p in saraSnapshot) {
p.interaction.remove(Option._P_ATTACK)
p.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
for (p in zamSnapshot) {
p.interaction.remove(Option._P_ATTACK)
p.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
saradominPlayers.clear()
zamorakPlayers.clear()
sendMessage(player, "Castle Wars game ended by admin command.")
}
else -> {
sendMessage(player, "Unknown subcommand. Use: start or end")
}
}
}
}
}

View file

@ -58,8 +58,11 @@ object CastleWarsConstants {
const val CATAPULT_MAX_DAMAGE = 20 // TODO: says who
// ==================== Coordinate Defaults ====================
/** Default X/Y coordinate for catapult targeting (center of 0-30 range) */
const val CATAPULT_DEFAULT_COORDINATE = 15 // TODO: says who
/** Default X coordinate for catapult targeting */
const val CATAPULT_DEFAULT_X = 8
/** Default Y coordinate for catapult targeting */
const val CATAPULT_DEFAULT_Y = 13
/** Maximum catapult coordinate value */
const val CATAPULT_MAX_COORDINATE = 30

View file

@ -5,13 +5,13 @@ import content.global.skill.summoning.familiar.BurdenBeast
import content.minigame.castlewars.areas.CastleWarsGameArea
import content.minigame.castlewars.areas.CastleWarsRespawnArea
import content.minigame.castlewars.areas.CastleWarsWaitingArea
import content.minigame.castlewars.bots.CastleWarsBotSpawner
import core.api.*
import core.cache.def.impl.ItemDefinition
import core.game.component.Component
import core.game.container.Container
import core.game.global.action.ClimbActionHandler
import core.game.global.action.DoorActionHandler
import core.game.global.action.SpecialLadders
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@ -65,6 +65,48 @@ private val barrierCoordinates: Map<Int, List<BarrierData>> = mapOf(
)
)
private val castleWarsStairDestinationOverrides: Map<Location, Location> = mapOf(
Location.create(2425, 3076, 2) to Location.create(2426, 3074, 3),
Location.create(2425, 3074, 3) to Location.create(2425, 3077, 2),
Location.create(2430, 3081, 2) to Location.create(2427, 3081, 1)
)
internal fun getCastleWarsStairDestination(start: Location): Location? {
return SpecialLadders.getDestination(start) ?: castleWarsStairDestinationOverrides[start]
}
internal fun getBarrierInteractionDestination(playerLoc: Location, barrierData: BarrierData): Location {
return when (playerLoc) {
barrierData.location, barrierData.outsideTile, barrierData.insideTile -> playerLoc
else -> {
val distToOutside = playerLoc.getDistance(barrierData.outsideTile)
val distToInside = playerLoc.getDistance(barrierData.insideTile)
if (distToOutside <= distToInside) {
barrierData.outsideTile
} else {
barrierData.insideTile
}
}
}
}
internal fun getBarrierPassTiles(playerLoc: Location, barrierData: BarrierData): Pair<Location, Location> {
return when (playerLoc) {
barrierData.location -> barrierData.location to barrierData.outsideTile
barrierData.outsideTile -> barrierData.outsideTile to barrierData.insideTile
barrierData.insideTile -> barrierData.insideTile to barrierData.outsideTile
else -> {
val distToOutside = playerLoc.getDistance(barrierData.outsideTile)
val distToInside = playerLoc.getDistance(barrierData.insideTile)
if (distToOutside <= distToInside) {
barrierData.outsideTile to barrierData.insideTile
} else {
barrierData.insideTile to barrierData.outsideTile
}
}
}
}
class CastleWarsListeners : InteractionListener {
private val pickaxeIds = intArrayOf(1265, 1267, 1269, 1271, 1273, 1275)
@ -90,19 +132,7 @@ class CastleWarsListeners : InteractionListener {
val barrierDataList = barrierCoordinates[barrier.id] ?: return@setDest barrier.location
val barrierData =
barrierDataList.find { it.location == barrier.location } ?: return@setDest barrier.location
if (playerLoc == barrierData.location) {
return@setDest barrierData.outsideTile
}
if (playerLoc == barrierData.outsideTile || playerLoc == barrierData.insideTile) {
return@setDest barrierData.location
}
val distToOutside = playerLoc.getDistance(barrierData.outsideTile)
val distToInside = playerLoc.getDistance(barrierData.insideTile)
if (distToOutside <= distToInside) {
return@setDest barrierData.outsideTile
} else {
return@setDest barrierData.insideTile
}
return@setDest getBarrierInteractionDestination(playerLoc, barrierData)
}
}
@ -114,9 +144,6 @@ class CastleWarsListeners : InteractionListener {
sendMessage(player, "The team is currently full.")
return@on true
}
if (!player.isArtificial) {
CastleWarsBotSpawner.spawnBotsIfNeeded()
}
player.properties.teleportLocation = CastleWarsWaitingArea.saradominWaitingRoom.randomWalkableLoc
player.setAttribute(CastleWars.portalAttribute, CastleWars.saradominName)
return@on true
@ -128,9 +155,6 @@ class CastleWarsListeners : InteractionListener {
sendMessage(player, "The team is currently full.")
return@on true
}
if (!player.isArtificial) {
CastleWarsBotSpawner.spawnBotsIfNeeded()
}
player.properties.teleportLocation = CastleWarsWaitingArea.zamorakWaitingRoom.randomWalkableLoc
player.setAttribute(CastleWars.portalAttribute, CastleWars.zamorakName)
return@on true
@ -146,9 +170,6 @@ class CastleWarsListeners : InteractionListener {
on(CastleWars.joinGuthixTeamPortal, IntType.SCENERY, "Enter") { player, _ ->
if (GameWorld.settings?.enable_castle_wars != true) return@on false
if (!player.isArtificial && joinError(player)) return@on true
if (!player.isArtificial) {
CastleWarsBotSpawner.spawnBotsIfNeeded()
}
val saraTotal =
CastleWarsWaitingArea.waitingSaradominPlayers.size + CastleWarsGameArea.saradominPlayers.size
val zamTotal = CastleWarsWaitingArea.waitingZamorakPlayers.size + CastleWarsGameArea.zamorakPlayers.size
@ -704,8 +725,8 @@ class CastleWarsListeners : InteractionListener {
sendMessage(player, "You need rocks to fire the catapult!")
return@on true
}
setVarbit(player, CastleWarsGameState.VARBIT_CATAPULT_X, 15)
setVarbit(player, CastleWarsGameState.VARBIT_CATAPULT_Y, 15)
setVarbit(player, CastleWarsGameState.VARBIT_CATAPULT_X, CatapultConfig.DEFAULT_X)
setVarbit(player, CastleWarsGameState.VARBIT_CATAPULT_Y, CatapultConfig.DEFAULT_Y)
player.setAttribute("catapult:location", catapult.location)
player.interfaceManager.open(Component(Components.CASTLEWARS_CATAPULT_54))
return@on true
@ -868,6 +889,7 @@ class CastleWarsListeners : InteractionListener {
}
private fun getStaircaseDestination(scenery: Scenery, zDelta: Int): Location {
getCastleWarsStairDestination(scenery.location)?.let { return it }
val targetPlaneLocation = scenery.location.transform(0, 0, zDelta)
val endScenery = RegionManager.getObject(targetPlaneLocation)
if (endScenery != null) {
@ -882,7 +904,7 @@ class CastleWarsListeners : InteractionListener {
private fun passEnergyBarrier(
player: Player,
barrier: Scenery
) { // TODO: this is so weird that it might just as well be rewritten. doesnt work when occupying tiles with barriers. works otherwise
) {
val currentBarrier =
RegionManager.getObject(barrier.location.z, barrier.location.x, barrier.location.y, barrier.id)
if (currentBarrier == null || !currentBarrier.isActive) {
@ -902,19 +924,7 @@ class CastleWarsListeners : InteractionListener {
return
}
val playerLoc = player.location
val onBarrierTile = playerLoc == barrierData.location
val (startTile, endTile) = if (onBarrierTile) {
Pair(barrierData.location, barrierData.outsideTile)
} else {
val distToOutside = playerLoc.getDistance(barrierData.outsideTile)
val distToInside = playerLoc.getDistance(barrierData.insideTile)
if (distToOutside <= distToInside) {
Pair(barrierData.outsideTile, barrierData.insideTile)
} else {
Pair(barrierData.insideTile, barrierData.outsideTile)
}
}
val (startTile, endTile) = getBarrierPassTiles(playerLoc, barrierData)
if (playerLoc != startTile) {
player.walkingQueue.reset()
player.properties.teleportLocation = startTile
@ -1036,4 +1046,4 @@ class CastleWarsListeners : InteractionListener {
capeOrHelmetError(player) ?: nonCombatItemsCheck(player) ?: familiarCheck(player) ?: return false
player.sendMessage(errorMessage).also { return true }
}
}
}

View file

@ -7,7 +7,7 @@ import org.rs09.consts.Components
* Configuration constants for the Castle Wars catapult interface.
* All magic numbers and hardcoded values are centralized here.
*/
object CatapultConfig { // TODO: this is definitely fucked somehow
object CatapultConfig {
/** Interface ID for the catapult targeting interface */
const val INTERFACE_ID = Components.CASTLEWARS_CATAPULT_54
@ -15,8 +15,8 @@ object CatapultConfig { // TODO: this is definitely fucked somehow
/** Child ID for the red X marker on the map */
const val MARKER_CHILD_ID = 55
/** Enum ID for digit models (maps 0-9 to model IDs) */
const val DIGIT_MODEL_ENUM = 1595
/** Native Castle Wars dummy items used to render digits on interface 54 */
val DIGIT_ITEM_IDS = intArrayOf(4056, 4057, 4058, 4059, 4060, 4061, 4062, 4063, 4064, 4065)
const val BUTTON_FIRE = 51
const val BUTTON_CLOSE = 56
@ -25,7 +25,7 @@ object CatapultConfig { // TODO: this is definitely fucked somehow
const val BUTTON_RIGHT = 67
const val BUTTON_LEFT = 68
val DIGIT_CHILD_IDS = intArrayOf(69, 70, 71, 72)
val DIGIT_CHILD_IDS = intArrayOf(61, 62, 63, 64)
const val MAP_BASE_X = 12
const val MAP_BASE_Y = 20
@ -33,7 +33,8 @@ object CatapultConfig { // TODO: this is definitely fucked somehow
const val MAP_HEIGHT = 297
const val MAX_COORDINATE = CastleWarsConstants.CATAPULT_MAX_COORDINATE
const val DEFAULT_COORDINATE = CastleWarsConstants.CATAPULT_DEFAULT_COORDINATE
const val DEFAULT_X = CastleWarsConstants.CATAPULT_DEFAULT_X
const val DEFAULT_Y = CastleWarsConstants.CATAPULT_DEFAULT_Y
const val CENTER_OFFSET = 15
val SARADOMIN_BASE = Location.create(2426, 3073, 0)

View file

@ -1,8 +1,9 @@
package content.minigame.castlewars
import content.minigame.castlewars.CatapultConfig.DEFAULT_COORDINATE
import content.minigame.castlewars.CatapultConfig.DEFAULT_X
import content.minigame.castlewars.CatapultConfig.DEFAULT_Y
import content.minigame.castlewars.CatapultConfig.DIGIT_CHILD_IDS
import content.minigame.castlewars.CatapultConfig.DIGIT_MODEL_ENUM
import content.minigame.castlewars.CatapultConfig.DIGIT_ITEM_IDS
import content.minigame.castlewars.CatapultConfig.INTERFACE_ID
import content.minigame.castlewars.CatapultConfig.MAP_BASE_X
import content.minigame.castlewars.CatapultConfig.MAP_BASE_Y
@ -10,18 +11,24 @@ import content.minigame.castlewars.CatapultConfig.MAP_HEIGHT
import content.minigame.castlewars.CatapultConfig.MAP_WIDTH
import content.minigame.castlewars.CatapultConfig.MARKER_CHILD_ID
import content.minigame.castlewars.CatapultConfig.MAX_COORDINATE
import core.cache.def.impl.DataMap
import core.cache.def.impl.ComponentType
import core.cache.def.impl.IfaceDefinition
import core.game.node.entity.player.Player
import core.net.packet.PacketRepository
import core.net.packet.context.ChildPositionContext
import core.net.packet.context.DisplayModelContext
import core.net.packet.out.DisplayModel
import core.net.packet.out.RepositionChild
/**
* Handles rendering the catapult interface including marker position and digit displays.
*/
class CatapultInterfaceRenderer { // TODO: this is getting very close to 100%. still fucked but on the right track for sure
class CatapultInterfaceRenderer {
private val interfaceDefinition: IfaceDefinition by lazy { IfaceDefinition.forId(INTERFACE_ID) }
fun renderCoordinate(player: Player, coord: Coordinate) {
updateMarkerPosition(player, coord)
updateDigitDisplay(player, coord)
}
/**
* Updates the position of the red X marker on the interface map.
@ -60,9 +67,8 @@ class CatapultInterfaceRenderer { // TODO: this is getting very close to 100%. s
* @param player The player whose interface to reset
*/
fun resetToDefaults(player: Player) {
val defaultCoord = Coordinate(DEFAULT_COORDINATE, DEFAULT_COORDINATE)
updateMarkerPosition(player, defaultCoord)
updateDigitDisplay(player, defaultCoord)
val defaultCoord = Coordinate(DEFAULT_X, DEFAULT_Y)
renderCoordinate(player, defaultCoord)
}
private fun calculateScreenX(coordX: Int): Int {
@ -77,21 +83,32 @@ class CatapultInterfaceRenderer { // TODO: this is getting very close to 100%. s
}
private fun sendDigitModel(player: Player, childId: Int, digit: Int) {
val modelId = getDigitModelId(digit)
PacketRepository.send(
DisplayModel::class.java,
DisplayModelContext(
player,
DisplayModelContext.ModelType.MODEL,
modelId,
0,
INTERFACE_ID,
childId
)
if (!isValidDigitChild(childId)) {
return
}
val child = interfaceDefinition.children[childId] ?: return
val itemId = getDigitItemId(digit)
if (itemId < 0) {
return
}
player.packetDispatch.sendItemOnInterface(itemId, 1, INTERFACE_ID, childId)
player.packetDispatch.sendAngleOnInterface(
INTERFACE_ID,
childId,
child.modelZoom,
child.modelXAngle,
child.modelYAngle
)
}
private fun getDigitModelId(digit: Int): Int {
return DataMap.get(DIGIT_MODEL_ENUM).getInt(digit)
internal fun isValidDigitChild(childId: Int): Boolean {
if (childId !in interfaceDefinition.children.indices) {
return false
}
return interfaceDefinition.children[childId]?.type == ComponentType.MODEL
}
internal fun getDigitItemId(digit: Int): Int {
return DIGIT_ITEM_IDS.getOrNull(digit) ?: -1
}
}

View file

@ -5,7 +5,7 @@ import core.game.world.map.Location
import core.tools.LogLevel
import core.tools.mysql.Database
import core.tools.secondsToTicks
import rs09.game.content.activity.castlewars.CastleWars
import content.minigame.castlewars.CastleWars
import java.math.BigInteger
import java.util.*