This commit is contained in:
dam 2026-02-18 22:39:08 +02:00
parent 89fdb871ab
commit d427a9882f
No known key found for this signature in database
GPG key ID: 4AF4E722399663FB
4 changed files with 409 additions and 71 deletions

View file

@ -1,11 +1,14 @@
package rs09.game.content.activity.castlewars.areas
package content.minigame.castlewars.areas
import content.global.skill.summoning.familiar.BurdenBeast
import core.api.*
import content.minigame.castlewars.CastleWars
import core.api.LogoutListener
import core.api.MapArea
import core.api.removeTimer
import core.api.sendMessage
import core.game.interaction.InteractionListener
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import rs09.game.content.activity.castlewars.CastleWars
abstract class CastleWarsArea : MapArea, LogoutListener, InteractionListener {
@ -48,21 +51,21 @@ abstract class CastleWarsArea : MapArea, LogoutListener, InteractionListener {
// Remove teleblock
removeTimer(player, "teleblock")
// Remove any Castle Wars items
// Todo Remove any tinderboxes or other castle wars items - See Jan 2018 update: https://oldschool.runescape.wiki/w/Castle_Wars
val cwarsItems = intArrayOf(CastleWars.saradominTeamHoodedCloak,
CastleWars.zamorakTeamHoodedCloak,
CastleWars.saradominFlag,
CastleWars.zamorakFlag)
// TODO: PvPStateAudit blows, has gotta go and actual safe handling of PvP generalized to cover current inauthentic deep wildy,
// future authentic minigames and that wont result in 7000 exploits that ruin the server (yes. thats a good idea. who said that)
// PvPStateAudit.cleanupPvPState(player)
player.equipment.removeAll(cwarsItems)
player.inventory.removeAll(cwarsItems)
(player.familiarManager.familiar as? BurdenBeast)?.container?.removeAll(cwarsItems)
player.equipment.removeAll(CastleWars.CW_ITEMS_TO_REMOVE)
player.inventory.removeAll(CastleWars.CW_ITEMS_TO_REMOVE)
(player.familiarManager.familiar as? BurdenBeast)?.container?.removeAll(CastleWars.CW_ITEMS_TO_REMOVE)
}
override fun defineListeners() {
onUnequip(intArrayOf(CastleWars.saradominTeamHoodedCloak, CastleWars.zamorakTeamHoodedCloak)) { player, _ ->
defineAreaBorders().forEach { border ->
val allCastleWarsBorders = CastleWarsGameArea.areaBorders.toList() +
CastleWarsWaitingArea.areaBorders.toList() +
listOf(CastleWarsRespawnArea.saradominRespawnRoom, CastleWarsRespawnArea.zamorakRespawnRoom)
for (border in allCastleWarsBorders) {
if (border.insideBorder(player)) {
sendMessage(player, "You can't remove your team's colours")
return@onUnequip false

View file

@ -1,7 +1,12 @@
package rs09.game.content.activity.castlewars.areas
package content.minigame.castlewars.areas
import content.minigame.castlewars.*
import content.minigame.castlewars.CastleWarsConstants.PLAYER_CLEANUP_INTERVAL_TICKS
import core.api.*
import core.game.component.Component
import core.game.event.EventHook
import core.game.event.InterfaceCloseEvent
import core.game.interaction.Option
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.item.Item
@ -11,57 +16,159 @@ import core.game.world.map.zone.ZoneType
import core.tools.Log
import core.tools.ticksPerMinute
import org.rs09.consts.Components
import rs09.game.content.activity.castlewars.CastleWars
/**
* Handles the Castle Wars game map
*/
class CastleWarsGameArea : CastleWarsArea(), TickListener {
class CastleWarsGameArea : CastleWarsArea(), TickListener, PvPZone {
companion object {
private val saradominStandardFloor: ZoneBorders = ZoneBorders(Location.create(2426, 3073, 3), Location.create(2430, 3077, 3))
private val zamorakStandardFloor: ZoneBorders = ZoneBorders(Location.create(2373, 3134, 3), Location.create(2369, 3130, 3))
private val saradominUpperFloor: ZoneBorders = ZoneBorders(Location.create(2431, 3080, 2), Location.create(2423, 3072, 2))
private val zamorakUpperFloor: ZoneBorders = ZoneBorders(Location.create(2368, 3127, 2), Location.create(2376, 3135, 2))
private val saradominFloor: ZoneBorders = ZoneBorders(Location.create(2420, 3072, 1), Location.create(2431, 3083, 1))
private val zamorakFloor: ZoneBorders = ZoneBorders(Location.create(2379, 3135, 1), Location.create(2368, 3124, 1))
private val battleField: ZoneBorders = ZoneBorders(Location.create(2368, 3135, 0), Location.create(2431, 3072, 0))
private val saradominTunnels: ZoneBorders = ZoneBorders(Location.create(2430, 9481, 0), Location.create(2400, 9504, 0))
private val zamorakTunnels: ZoneBorders = ZoneBorders(Location.create(2401, 9503, 0), Location.create(2366, 9529, 0))
private val saradominStandardFloor: ZoneBorders =
ZoneBorders(Location.create(2426, 3073, 3), Location.create(2430, 3077, 3))
private val zamorakStandardFloor: ZoneBorders =
ZoneBorders(Location.create(2373, 3134, 3), Location.create(2369, 3130, 3))
private val saradominUpperFloor: ZoneBorders =
ZoneBorders(Location.create(2431, 3080, 2), Location.create(2423, 3072, 2))
private val zamorakUpperFloor: ZoneBorders =
ZoneBorders(Location.create(2368, 3127, 2), Location.create(2376, 3135, 2))
private val saradominFloor: ZoneBorders =
ZoneBorders(Location.create(2420, 3072, 1), Location.create(2431, 3083, 1))
private val zamorakFloor: ZoneBorders =
ZoneBorders(Location.create(2379, 3135, 1), Location.create(2368, 3124, 1))
private val battleField: ZoneBorders =
ZoneBorders(Location.create(2368, 3135, 0), Location.create(2431, 3072, 0))
private val saradominTunnels: ZoneBorders =
ZoneBorders(Location.create(2430, 9481, 0), Location.create(2385, 9504, 0))
private val zamorakTunnels: ZoneBorders =
ZoneBorders(Location.create(2401, 9503, 0), Location.create(2366, 9529, 0))
val areaBorders = arrayOf(saradominStandardFloor, zamorakStandardFloor, saradominUpperFloor, zamorakUpperFloor, saradominFloor, zamorakFloor, battleField, saradominTunnels, zamorakTunnels)
// Center tunnel zone to cover the gap where ladders are located (2399, 9499) and (2400, 9508)
private val centerTunnels: ZoneBorders =
ZoneBorders(Location.create(2396, 9493, 0), Location.create(2405, 9515, 0))
val areaBorders = arrayOf(
saradominStandardFloor,
zamorakStandardFloor,
saradominUpperFloor,
zamorakUpperFloor,
saradominFloor,
zamorakFloor,
battleField,
saradominTunnels,
zamorakTunnels,
centerTunnels
)
val saradominPlayers = mutableSetOf<Player>()
val zamorakPlayers = mutableSetOf<Player>()
/** Players who have logged out but are within the grace period. Maps Player -> logout tick. */
val loggedOutPlayers = mutableMapOf<Player, Int>()
var ticksLeftInGame = 0
fun startGame() {
CastleWarsGameState.resetGame()
CastleWarsItemHandler.clearAllBarricades()
saradominPlayers.addAll(CastleWarsWaitingArea.waitingSaradominPlayers)
zamorakPlayers.addAll(CastleWarsWaitingArea.waitingZamorakPlayers)
CastleWarsWaitingArea.waitingSaradominPlayers.clear()
CastleWarsWaitingArea.waitingZamorakPlayers.clear()
ticksLeftInGame = CastleWars.gameTimeMinutes * ticksPerMinute
// Put all the players in their respawn area
saradominPlayers.forEach { player ->
player.properties.teleportLocation = CastleWarsRespawnArea.saradominRespawnRoom.randomWalkableLoc
setAttribute(player, CastleWarsConstants.ATTR_JOIN_TICK, getWorldTicks())
}
zamorakPlayers.forEach { player ->
player.properties.teleportLocation = CastleWarsRespawnArea.zamorakRespawnRoom.randomWalkableLoc
setAttribute(player, CastleWarsConstants.ATTR_JOIN_TICK, getWorldTicks())
}
val allPlayers = saradominPlayers + zamorakPlayers
allPlayers.forEach { player ->
sendMessage(player, "The Castle Wars game has begun!")
CastleWarsGameState.updateOverlay(player)
}
}
/**
* Handle player death in Castle Wars - delegates to CastleWarsGameZone.
* This ensures consistent death behavior including poison preservation.
*/
fun handleDeath(player: Player, killer: Entity?): Boolean {
return CastleWarsGameZone.handleCastleWarsDeath(player)
}
/**
* Try to fill a vacancy on the given team by pulling a player from the waiting room.
* Called when a player leaves an active game.
* @param team The team that lost a player
*/
fun fillVacancy(team: CastleWarsTeam) {
if (ticksLeftInGame <= 0) return
val waitingSet = when (team) {
CastleWarsTeam.SARADOMIN -> CastleWarsWaitingArea.waitingSaradominPlayers
CastleWarsTeam.ZAMORAK -> CastleWarsWaitingArea.waitingZamorakPlayers
}
val gameSet = when (team) {
CastleWarsTeam.SARADOMIN -> saradominPlayers
CastleWarsTeam.ZAMORAK -> zamorakPlayers
}
if (waitingSet.isEmpty()) return
if (gameSet.size >= CastleWarsConstants.MAX_PLAYERS_PER_TEAM) return
val replacement = waitingSet.first()
waitingSet.remove(replacement)
gameSet.add(replacement)
setAttribute(replacement, CastleWarsConstants.ATTR_JOIN_TICK, getWorldTicks())
val respawnLoc = when (team) {
CastleWarsTeam.SARADOMIN -> CastleWarsRespawnArea.saradominRespawnRoom.randomWalkableLoc
CastleWarsTeam.ZAMORAK -> CastleWarsRespawnArea.zamorakRespawnRoom.randomWalkableLoc
}
replacement.properties.teleportLocation = respawnLoc
sendMessage(replacement, "A spot has opened up! You have been moved into the Castle Wars game.")
}
object SaradominInterfaceCloseHook : EventHook<InterfaceCloseEvent> { // TODO: this might be useless.
override fun process(entity: Entity, event: InterfaceCloseEvent) {
val player = entity as? Player ?: return
// Only restore if player is still in the game
if (!saradominPlayers.contains(player)) return
val currentOverlayId = player.interfaceManager.overlay?.id
if (currentOverlayId == null || currentOverlayId != 58) {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_SARADOMIN_58))
CastleWarsGameState.updateOverlay(player)
}
}
}
object ZamorakInterfaceCloseHook : EventHook<InterfaceCloseEvent> { // TODO: this might be useless.
override fun process(entity: Entity, event: InterfaceCloseEvent) {
val player = entity as? Player ?: return
if (!zamorakPlayers.contains(player)) return
val currentOverlayId = player.interfaceManager.overlay?.id
if (currentOverlayId == null || currentOverlayId != 59) {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_ZAMORAK_59))
CastleWarsGameState.updateOverlay(player)
}
}
}
}
private fun endGame() {
private fun endGame() { // TODO: ropes, rocks in tunnels, ground items, castle doors, catapults
CastleWarsWaitingArea.postGameCooldown = true
CastleWarsGameState.endGame()
CastleWarsItemHandler.clearAllBarricades()
saradominPlayers.forEach { player ->
player.unhook(SaradominInterfaceCloseHook)
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
zamorakPlayers.forEach { player ->
player.unhook(ZamorakInterfaceCloseHook)
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
saradominPlayers.clear()
zamorakPlayers.clear()
loggedOutPlayers.clear()
}
override fun defineAreaBorders(): Array<ZoneBorders> {
@ -70,31 +177,211 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
override fun areaEnter(entity: Entity) {
val player = entity as? Player ?: return
// Handle rejoin from logout grace period
if (loggedOutPlayers.containsKey(player)) {
loggedOutPlayers.remove(player)
player.removeAttribute(CastleWarsConstants.ATTR_LOGOUT_TICK)
// join tick is preserved from the original game start -- no changes needed
}
super.areaEnter(player)
zone.zoneType = ZoneType.CASTLE_WARS.id
registerTimer (player, spawnTimer("teleblock", (CastleWars.gameTimeMinutes)*60*2))
registerTimer(player, spawnTimer("teleblock", (CastleWars.gameTimeMinutes) * 60 * 2))
val team = CastleWarsGameState.getPlayerTeam(player)
when (team) {
CastleWarsTeam.SARADOMIN -> {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_SARADOMIN_58))
player.equipment.replace(Item(CastleWars.saradominTeamHoodedCloak), 1)
player.hook(Event.InterfaceClosed, SaradominInterfaceCloseHook)
}
if (saradominPlayers.contains(player)) {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_SARADOMIN_58))
player.equipment.replace(Item(CastleWars.saradominTeamHoodedCloak), 1)
} else if (zamorakPlayers.contains(player)) {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_ZAMORAK_59))
player.equipment.replace(Item(CastleWars.zamorakTeamHoodedCloak), 1)
CastleWarsTeam.ZAMORAK -> {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_ZAMORAK_59))
player.equipment.replace(Item(CastleWars.zamorakTeamHoodedCloak), 1)
player.hook(Event.InterfaceClosed, ZamorakInterfaceCloseHook)
}
null -> {
log(this::class.java, Log.WARN, "${player.name} entered Castle Wars but is NOT on a team?!")
}
}
player.interaction.set(Option._P_ATTACK)
}
override fun exitArea(player: Player) {
super.exitArea(player)
// Remove player from the players set (whichever one that is)
saradominPlayers.remove(player)
zamorakPlayers.remove(player)
}
override fun tick() {
ticksLeftInGame--
if (ticksLeftInGame == 0) {
endGame()
val stillInCastleWars = areaBorders.any { it.insideBorder(player.location) } ||
CastleWarsRespawnArea.saradominRespawnRoom.insideBorder(player.location) ||
CastleWarsRespawnArea.zamorakRespawnRoom.insideBorder(player.location)
if (!stillInCastleWars) {
val leavingTeam = CastleWarsGameState.getPlayerTeam(player)
CastleWarsGameState.handlePlayerLeave(player)
player.unhook(SaradominInterfaceCloseHook)
player.unhook(ZamorakInterfaceCloseHook)
saradominPlayers.remove(player)
zamorakPlayers.remove(player)
CastleWarsGameState.clearHintIconForPlayer(player)
player.interaction.remove(Option._P_ATTACK)
if (leavingTeam != null) {
fillVacancy(leavingTeam)
}
}
}
override fun logout(player: Player) {
// Store logout tick for grace period instead of immediate removal
if (ticksLeftInGame > 0 && (saradominPlayers.contains(player) || zamorakPlayers.contains(player))) {
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TICK, getWorldTicks())
loggedOutPlayers[player] = getWorldTicks()
if (CastleWarsGameState.isCarryingFlag(player)) {
CastleWarsGameState.dropFlag(player)
} else if (CastleWarsGameState.isCarryingOwnFlag(player)) {
CastleWarsGameState.dropOwnFlag(player)
}
CastleWarsGameState.clearHintIconForPlayer(player)
// Do NOT remove from team sets -- grace period keeps them on the team
} else {
// No active game or not on a team -- full cleanup
CastleWarsGameState.handlePlayerLeave(player)
CastleWarsGameState.clearHintIconForPlayer(player)
saradominPlayers.remove(player)
zamorakPlayers.remove(player)
}
super.logout(player)
}
override fun tick() {
val previousMinutes = (ticksLeftInGame + ticksPerMinute - 1) / ticksPerMinute
ticksLeftInGame--
val currentMinutes = (ticksLeftInGame + ticksPerMinute - 1) / ticksPerMinute
if (ticksLeftInGame > 0) {
val saraEmpty = saradominPlayers.isEmpty()
val zamEmpty = zamorakPlayers.isEmpty()
if (saraEmpty || zamEmpty) {
endGame()
return
}
}
if (ticksLeftInGame == 0) {
endGame()
} else if (currentMinutes != previousMinutes) {
CastleWarsGameState.updateAllOverlays()
}
CastleWarsGameState.checkFlagCarriers()
CastleWarsGameState.checkDroppedFlagTimeouts()
for (player in saradominPlayers) {
val currentOverlayId = player.interfaceManager.overlay?.id
if (currentOverlayId == null || currentOverlayId != 58) {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_SARADOMIN_58))
}
// TODO: this... should not be done lmfao CRITICAL TODO: PROPER CW MATCH UI UPDATING THATS NOT SHIT
CastleWarsGameState.updateOverlay(player)
}
for (player in zamorakPlayers) {
val currentOverlayId = player.interfaceManager.overlay?.id
if (currentOverlayId == null || currentOverlayId != 59) {
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_ZAMORAK_59))
}
// TODO: this... should not be done lmfao CRITICAL TODO: PROPER CW MATCH UI UPDATING THATS NOT SHIT
CastleWarsGameState.updateOverlay(player)
}
if (ticksLeftInGame % PLAYER_CLEANUP_INTERVAL_TICKS == 0) {
cleanupInactivePlayers()
}
}
/**
* Remove inactive players from team tracking sets.
*/
private fun cleanupInactivePlayers() {
val currentTick = getWorldTicks()
// Check logged-out players whose grace period has expired
val expiredLogouts = loggedOutPlayers.filter { (_, logoutTick) ->
currentTick - logoutTick >= CastleWarsConstants.LOGOUT_GRACE_PERIOD_TICKS
}
for ((player, _) in expiredLogouts) {
val team = when {
saradominPlayers.contains(player) -> CastleWarsTeam.SARADOMIN
zamorakPlayers.contains(player) -> CastleWarsTeam.ZAMORAK
else -> null
}
loggedOutPlayers.remove(player)
saradominPlayers.remove(player)
zamorakPlayers.remove(player)
CastleWarsGameState.clearHintIconForPlayer(player)
CastleWarsGameState.restoreAndCleanupPlayer(player)
if (team != null) fillVacancy(team)
}
// Remove truly inactive players (not in grace period)
val inactiveSara = saradominPlayers.filter { player ->
(!player.isActive || player.session == null) && !loggedOutPlayers.containsKey(player)
}
val inactiveZam = zamorakPlayers.filter { player ->
(!player.isActive || player.session == null) && !loggedOutPlayers.containsKey(player)
}
for (player in inactiveSara) {
saradominPlayers.remove(player)
CastleWarsGameState.clearHintIconForPlayer(player)
fillVacancy(CastleWarsTeam.SARADOMIN)
}
for (player in inactiveZam) {
zamorakPlayers.remove(player)
CastleWarsGameState.clearHintIconForPlayer(player)
fillVacancy(CastleWarsTeam.ZAMORAK)
}
// Also cleanup hint icons for any other inactive players
CastleWarsGameState.cleanupInactiveHintIcons()
}
/**
* Handle death in Castle Wars - respawn at team base instead of normal death
*/
fun death(entity: Entity, killer: Entity?): Boolean {
if (entity !is Player) return false
return handleDeath(entity, killer)
}
// ==================== PvPZone Implementation ==================== TODO: PvPZone general review... it might be good but need to triple-check
/**
* Check if attacker can attack victim in this zone.
* Only allows attacks between players on opposite teams.
* Uses self-healing team detection based on equipped cloak to ensure
* players are properly tracked even if they weren't added to sets correctly.
* @param attacker The player initiating the attack
* @param victim The player being attacked
* @return true if players are on opposite teams, false otherwise
*/
override fun canAttackPlayer(attacker: Player, victim: Player): Boolean {
if (ticksLeftInGame <= 0) return false
val attackerTeam = CastleWarsGameState.getPlayerTeam(attacker) ?: return false
val victimTeam = CastleWarsGameState.getPlayerTeam(victim) ?: return false
return attackerTeam != victimTeam
}
/**
* Check combat level restrictions.
* Castle Wars has no combat level restrictions.
*/
override fun checkCombatLevel(attacker: Player, victim: Player): Boolean {
return true
}
/**
* Get the message to display when attack is blocked.
* @return The message string
*/
override fun getBlockedAttackMessage(): String {
return "You can only attack players on the opposite team!" // TODO: pretty sure this is inauthentic. seen the real one in vids, needs correction here (also does this even get called huh)
}
/**
* Get the message to display when attack is blocked by combat level check.
* @return The message string
*/
override fun getCombatLevelBlockedMessage(): String {
return PvPZone.DEFAULT_COMBAT_LEVEL_MESSAGE
}
}

View file

@ -1,12 +1,14 @@
package rs09.game.content.activity.castlewars.areas
package content.minigame.castlewars.areas
import content.minigame.castlewars.CastleWars
import content.minigame.castlewars.CastleWarsGameState
import core.api.TickListener
import core.api.sendMessage
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.world.map.Location
import core.game.world.map.zone.ZoneBorders
import rs09.game.content.activity.castlewars.CastleWars
/**
* Handles the Castle Wars respawn rooms
@ -14,8 +16,10 @@ import rs09.game.content.activity.castlewars.CastleWars
class CastleWarsRespawnArea : CastleWarsArea(), TickListener {
companion object {
val zamorakRespawnRoom: ZoneBorders = ZoneBorders(Location.create(2376, 3127, 1), Location.create(2368, 3135, 1))
val saradominRespawnRoom: ZoneBorders = ZoneBorders(Location.create(2423, 3080, 1), Location.create(2431, 3072, 1))
val zamorakRespawnRoom: ZoneBorders =
ZoneBorders(Location.create(2376, 3127, 1), Location.create(2368, 3135, 1))
val saradominRespawnRoom: ZoneBorders =
ZoneBorders(Location.create(2423, 3080, 1), Location.create(2431, 3072, 1))
val zamorakPlayersInRespawnRoom = mutableSetOf<Player>()
val saradominPlayersInRespawnRoom = mutableSetOf<Player>()
@ -27,6 +31,14 @@ class CastleWarsRespawnArea : CastleWarsArea(), TickListener {
override fun areaEnter(entity: Entity) {
val player = entity as? Player ?: return
if (CastleWarsGameState.isCarryingAnyFlag(player)) {
if (CastleWarsGameState.isCarryingFlag(player)) {
CastleWarsGameState.dropFlag(player)
} else if (CastleWarsGameState.isCarryingOwnFlag(player)) {
CastleWarsGameState.dropOwnFlag(player)
}
sendMessage(player, "You cannot take the flag into your respawn room!")
}
if (saradominRespawnRoom.insideBorder(player.location)) {
player.equipment.replace(Item(CastleWars.saradominTeamHoodedCloak), 1)
} else if (zamorakRespawnRoom.insideBorder(player.location)) {
@ -37,4 +49,11 @@ class CastleWarsRespawnArea : CastleWarsArea(), TickListener {
override fun tick() {
}
override fun exitArea(player: Player) {
super.exitArea(player)
// Remove player from respawn room tracking
saradominPlayersInRespawnRoom.remove(player)
zamorakPlayersInRespawnRoom.remove(player)
}
}

View file

@ -1,15 +1,17 @@
package rs09.game.content.activity.castlewars.areas
package content.minigame.castlewars.areas
import CastleWarsOverlay
import content.minigame.castlewars.CastleWars
import content.minigame.castlewars.CastleWarsConstants
import content.minigame.castlewars.CastleWarsOverlay
import core.api.*
import core.game.component.Component
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.world.GameWorld
import core.game.world.map.zone.ZoneBorders
import core.tools.ticksPerMinute
import org.rs09.consts.Components
import rs09.game.content.activity.castlewars.CastleWars
/**
* Handles the Castle Wars "waiting room"
@ -24,6 +26,8 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
val waitingSaradominPlayers = mutableSetOf<Player>()
val waitingZamorakPlayers = mutableSetOf<Player>()
internal var gameStartCountdown = -1
var postGameCooldown = false
}
override fun defineAreaBorders(): Array<ZoneBorders> {
@ -33,7 +37,10 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
override fun areaEnter(entity: Entity) {
val player = entity as? Player ?: return
super.areaEnter(player)
registerTimer(player, spawnTimer("teleblock", (CastleWars.gameCooldownMinutes + CastleWars.gameTimeMinutes)*60*2))
registerTimer(
player,
spawnTimer("teleblock", (CastleWars.gameCooldownMinutes + CastleWars.gameTimeMinutes) * 60 * 2)
)
// Set team attribute and equip the hooded cloak on the entity based on which waiting room they're in
if (zamorakWaitingRoom.insideBorder(player.location)) {
@ -46,32 +53,42 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
var transformed = false
if (player.attributes[CastleWars.portalAttribute] == CastleWars.guthixName
&& (hasGodItem(player, God.ZAMORAK) || hasGodItem(player, God.SARADOMIN))) {
&& (hasGodItem(player, God.ZAMORAK) || hasGodItem(player, God.SARADOMIN))
) {
// https://www.youtube.com/watch?v=Vg3Om-jZNIY
// Turn the player into a sheep
player.appearance.transformNPC(CastleWars.sheep)
// Send the player a chat dialog
sendDialogue(player, "I pity your faith in my brothers. I shall bless you with some time in the most holy of forms; maybe its wisdom will rub off on you and you'll see the error of your ways.")
sendDialogue(
player,
"I pity your faith in my brothers. I shall bless you with some time in the most holy of forms; maybe its wisdom will rub off on you and you'll see the error of your ways."
)
transformed = true
}
if (player.attributes[CastleWars.portalAttribute] == CastleWars.zamorakName
&& hasGodItem(player, God.SARADOMIN) || hasGodItem(player, God.GUTHIX))
{
&& (hasGodItem(player, God.SARADOMIN) || hasGodItem(player, God.GUTHIX))
) {
// https://youtu.be/hmF-CU9OEY4?t=63
// Turn the player into an imp!
player.appearance.transformNPC(CastleWars.imp)
// Send the player a chat dialog
sendDialogue(player, "You're wearing objects of my ignorant brothers and you come to me? Such treachery must be rewarded! Enjoy some time in the most mischievous of forms.")
sendDialogue(
player,
"You're wearing objects of my ignorant brothers and you come to me? Such treachery must be rewarded! Enjoy some time in the most mischievous of forms."
)
transformed = true
}
if (player.attributes[CastleWars.portalAttribute] == CastleWars.saradominName
&& hasGodItem(player, God.ZAMORAK) || hasGodItem(player, God.GUTHIX))
{
&& (hasGodItem(player, God.ZAMORAK) || hasGodItem(player, God.GUTHIX))
) {
// https://www.youtube.com/watch?v=Vg3Om-jZNIY
// Turn the player into a rabbit!
player.appearance.transformNPC(CastleWars.rabbit)
// Send the player a chat dialog
sendDialogue(player, "You wear objects of my foolish brothers? Perhaps some time spent as the lowliest of forms will help you appreciate the gifts that I can bestow upon my followers.")
sendDialogue(
player,
"You wear objects of my foolish brothers? Perhaps some time spent as the lowliest of forms will help you appreciate the gifts that I can bestow upon my followers."
)
transformed = true
}
@ -101,26 +118,38 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
// The game will start when there's enough players and CastleWarsGameArea.ticksLeftInGame = -5 minutes
if (CastleWarsGameArea.ticksLeftInGame >= 0) {
// A game is going on, display 5 minutes + remaining game time
gameStartCountdown = -1 // Reset countdown while game is active
nextStart = CastleWarsGameArea.ticksLeftInGame + CastleWars.gameCooldownMinutes * ticksPerMinute
}
else if (waitingSaradominPlayers.isEmpty() || waitingZamorakPlayers.isEmpty()) {
// A game is not going and there aren't enough players - Keep the game over time to the current value
} else if (waitingSaradominPlayers.size < (if (GameWorld.settings?.isDevMode == true) 1 else CastleWarsConstants.MIN_PLAYERS_PER_TEAM)
|| waitingZamorakPlayers.size < (if (GameWorld.settings?.isDevMode == true) 1 else CastleWarsConstants.MIN_PLAYERS_PER_TEAM)
) {
// A game is not going and there aren't enough players - Reset countdown
gameStartCountdown = -1
CastleWarsGameArea.ticksLeftInGame = -1
} else {
// There are enough players - start or continue countdown
if (gameStartCountdown < 0) {
// Start new countdown: dev mode uses short timer, post-game uses 2-min, otherwise standard 5-min
gameStartCountdown = when {
GameWorld.settings?.isDevMode == true -> CastleWarsConstants.DEV_COUNTDOWN_TICKS
postGameCooldown -> CastleWarsConstants.POST_GAME_COUNTDOWN_TICKS
else -> CastleWarsConstants.WAITING_ROOM_COUNTDOWN_TICKS
}
postGameCooldown = false // Reset after consuming
}
nextStart = gameStartCountdown
gameStartCountdown--
}
else {
// There are enough players - Set nextStart to gameCooldownMinutes + ticksLeftInGame
nextStart = CastleWars.gameCooldownMinutes * ticksPerMinute + CastleWarsGameArea.ticksLeftInGame
}
val minPlayers = if (GameWorld.settings?.isDevMode == true) 1 else CastleWarsConstants.MIN_PLAYERS_PER_TEAM
for (player in waitingSaradominPlayers + waitingZamorakPlayers) {
CastleWarsOverlay.sendLobbyUpdate(
player,
waitingSaradominPlayers.isNotEmpty() && waitingZamorakPlayers.isNotEmpty() || CastleWarsGameArea.ticksLeftInGame >= 0,
(waitingSaradominPlayers.size >= minPlayers && waitingZamorakPlayers.size >= minPlayers) || CastleWarsGameArea.ticksLeftInGame >= 0,
(nextStart - 1) / ticksPerMinute + 1 // Displays max 5 minutes, min 1 minute, seems authentic
)
}
if (nextStart <= 0) {
gameStartCountdown = -1 // Reset countdown
CastleWarsGameArea.startGame()
}
}