mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-24 20:05:13 -06:00
CW areas
This commit is contained in:
parent
89fdb871ab
commit
d427a9882f
4 changed files with 409 additions and 71 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue