mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-24 20:05:13 -06:00
Quite a lot
- Waiting-room timer now stays visible and uses 2-minute cycles when idle, and active-game wait displays remaining game time + 5 minutes. - Game start now requires 2 Saradomin + 2 Zamorak players, with no dev-mode shortcut or team cap logic. - Removed combat-level “vacancy” substitution; substitutions now trigger only from real same-team departures. - Added spawn-room two-minute removal with vacancy offers. - Exit portals now only work for the matching team. - Active-game logout/relogin now preserves location within two minutes and sends expired logouts to the team waiting room; waiting-room logout returns to lobby and loses queue. - End-game tickets now follow 2 win / 1 draw / 0 loss, without the old 5-per-team or 10-minute gate. - Active games no longer end just because one team is temporarily empty. - Join validation now rejects food, capes/helmets, coins, noted/non-combat items while allowing combat gear, potions, runes, and Hunter butterflies.
This commit is contained in:
parent
76abee373c
commit
b417156cba
11 changed files with 609 additions and 199 deletions
|
|
@ -32,10 +32,12 @@ class CastleWarsCommands : Commands {
|
|||
}
|
||||
val saraCount = CastleWarsWaitingArea.waitingSaradominPlayers.size
|
||||
val zamCount = CastleWarsWaitingArea.waitingZamorakPlayers.size
|
||||
if (saraCount == 0 || zamCount == 0) {
|
||||
if (saraCount < CastleWarsConstants.MIN_PLAYERS_PER_TEAM ||
|
||||
zamCount < CastleWarsConstants.MIN_PLAYERS_PER_TEAM
|
||||
) {
|
||||
sendMessage(
|
||||
player,
|
||||
"Both teams need at least 1 player. Saradomin: $saraCount, Zamorak: $zamCount"
|
||||
"Both teams need at least ${CastleWarsConstants.MIN_PLAYERS_PER_TEAM} players. Saradomin: $saraCount, Zamorak: $zamCount"
|
||||
)
|
||||
return@define
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ object CastleWarsConstants {
|
|||
/** Minimum players per team required to start a game */
|
||||
const val MIN_PLAYERS_PER_TEAM = 2
|
||||
|
||||
/** Combat-level gap that opens a Castle Wars substitution vacancy */
|
||||
const val COMBAT_LEVEL_VACANCY_THRESHOLD = 60
|
||||
/** Castle Wars timing uses the server's 600ms game tick: 100 ticks = 1 minute */
|
||||
const val TICKS_PER_MINUTE = 100
|
||||
|
||||
/** Catapult targeting inaccuracy radius */
|
||||
const val CATAPULT_INACCURACY_RADIUS = 2
|
||||
|
|
@ -120,21 +120,8 @@ object CastleWarsConstants {
|
|||
const val GAME_COOLDOWN_MINUTES = 5
|
||||
|
||||
// ==================== Waiting Room ====================
|
||||
/** Authentic waiting room countdown: GAME_COOLDOWN_MINUTES in ticks (5 min = 500 ticks) */
|
||||
const val WAITING_ROOM_COUNTDOWN_TICKS = GAME_COOLDOWN_MINUTES * 100 // ticksPerMinute = 100
|
||||
|
||||
/** Dev-mode fast countdown for testing (~10 seconds) */
|
||||
const val DEV_COUNTDOWN_TICKS = 17
|
||||
|
||||
/** Post-game shortened countdown in ticks (~2 minutes) for quick re-start */
|
||||
const val POST_GAME_COUNTDOWN_TICKS = 200 // 2 minutes = 200 ticks
|
||||
|
||||
// ==================== Ticket Reward Requirements ====================
|
||||
/** Minimum play time in minutes to be eligible for tickets */
|
||||
const val MIN_PLAY_TIME_MINUTES = 10
|
||||
|
||||
/** Minimum team size for tickets to be awarded */
|
||||
const val MIN_TEAM_SIZE_FOR_TICKETS = 5
|
||||
/** Waiting-room cycle while no game is active: 2 minutes = 200 ticks */
|
||||
const val WAITING_ROOM_COUNTDOWN_TICKS = 2 * TICKS_PER_MINUTE
|
||||
|
||||
// ==================== Player Attribute Keys ====================
|
||||
/** Attribute key: world tick when player joined the current game */
|
||||
|
|
@ -145,11 +132,23 @@ object CastleWarsConstants {
|
|||
|
||||
// ==================== Logout Grace Period ====================
|
||||
/** Grace period after logout before removing player from team (2 minutes = 200 ticks) */
|
||||
const val LOGOUT_GRACE_PERIOD_TICKS = 200
|
||||
const val LOGOUT_GRACE_PERIOD_TICKS = 2 * TICKS_PER_MINUTE
|
||||
|
||||
/** Wall-clock logout grace for saved login state (2 minutes) */
|
||||
const val LOGOUT_GRACE_PERIOD_MILLIS = 120_000L
|
||||
|
||||
/** Saved attribute key: wall-clock millis when player logged out of an active match */
|
||||
const val ATTR_LOGOUT_TIME_MILLIS = "/save:cw:logout_time_millis"
|
||||
|
||||
/** Saved attribute key: team name for an active-match logout */
|
||||
const val ATTR_LOGOUT_TEAM = "/save:cw:logout_team"
|
||||
|
||||
/** Saved attribute key: exact active-match logout location */
|
||||
const val ATTR_LOGOUT_LOCATION = "/save:cw:logout_location"
|
||||
|
||||
// ==================== Flag Steal Timer ====================
|
||||
/** Time in ticks before teammates can steal the flag from a carrier (3 minutes = 300 ticks) */
|
||||
const val FLAG_STEAL_TIMER_TICKS = 300
|
||||
const val FLAG_STEAL_TIMER_TICKS = 3 * TICKS_PER_MINUTE
|
||||
|
||||
/** Player attribute key for tracking when enemy flag was picked up */
|
||||
const val ATTR_FLAG_PICKUP_TICK = "cw:flag_pickup_tick"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import core.game.world.map.Location
|
|||
import core.game.world.map.RegionManager
|
||||
import core.game.world.map.zone.ZoneBorders
|
||||
import core.game.world.update.flag.chunk.ObjectUpdateFlag
|
||||
import core.tools.ticksPerMinute
|
||||
import org.rs09.consts.Items
|
||||
|
||||
/**
|
||||
|
|
@ -226,7 +225,8 @@ object CastleWarsGameState {
|
|||
* Get the remaining game time in minutes
|
||||
*/
|
||||
val remainingMinutes: Int
|
||||
get() = (CastleWarsGameArea.ticksLeftInGame + ticksPerMinute - 1) / ticksPerMinute
|
||||
get() = (CastleWarsGameArea.ticksLeftInGame + CastleWarsConstants.TICKS_PER_MINUTE - 1) /
|
||||
CastleWarsConstants.TICKS_PER_MINUTE
|
||||
|
||||
/**
|
||||
* Reset the game state for a new game
|
||||
|
|
@ -1083,24 +1083,14 @@ object CastleWarsGameState {
|
|||
*/
|
||||
fun endGame() {
|
||||
val allPlayers = getAllGamePlayers()
|
||||
val saraCount = allPlayers.count { getPlayerTeam(it) == CastleWarsTeam.SARADOMIN }
|
||||
val zamCount = allPlayers.count { getPlayerTeam(it) == CastleWarsTeam.ZAMORAK }
|
||||
val teamsLargeEnough =
|
||||
saraCount >= CastleWarsConstants.MIN_TEAM_SIZE_FOR_TICKETS && zamCount >= CastleWarsConstants.MIN_TEAM_SIZE_FOR_TICKETS
|
||||
for (player in allPlayers) {
|
||||
val team = getPlayerTeam(player) ?: continue
|
||||
val baseTickets = when {
|
||||
val tickets = when {
|
||||
saradominScore > zamorakScore && team == CastleWarsTeam.SARADOMIN -> 2
|
||||
zamorakScore > saradominScore && team == CastleWarsTeam.ZAMORAK -> 2
|
||||
saradominScore == zamorakScore -> 1
|
||||
else -> 0
|
||||
}
|
||||
val tickets =
|
||||
if (baseTickets > 0 && teamsLargeEnough && player.hasPlayedMinimumTime(CastleWarsConstants.MIN_PLAY_TIME_MINUTES)) {
|
||||
baseTickets
|
||||
} else {
|
||||
0
|
||||
}
|
||||
if (tickets > 0) {
|
||||
addItem(player, Items.CASTLE_WARS_TICKET_4067, tickets)
|
||||
sendMessage(player, "You have received $tickets Castle Wars ticket${if (tickets > 1) "s" else ""}!")
|
||||
|
|
@ -1511,6 +1501,9 @@ object CastleWarsGameState {
|
|||
*/
|
||||
private fun getAllGamePlayers(): Set<Player> {
|
||||
val players = mutableSetOf<Player>()
|
||||
players.addAll((CastleWarsGameArea.saradominPlayers + CastleWarsGameArea.zamorakPlayers).filter {
|
||||
it.isActive && it.session != null
|
||||
})
|
||||
for (border in CastleWarsGameArea.areaBorders) {
|
||||
val centerX = (border.southWestX + border.northEastX) / 2
|
||||
val centerY = (border.southWestY + border.northEastY) / 2
|
||||
|
|
@ -1676,18 +1669,6 @@ object CastleWarsGameState {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this player has played for the minimum required time in the current game.
|
||||
* @param minMinutes Minimum minutes required
|
||||
* @return true if the player has been in the game for at least minMinutes
|
||||
*/
|
||||
fun Player.hasPlayedMinimumTime(minMinutes: Int): Boolean {
|
||||
val joinTick = getAttribute(this, CastleWarsConstants.ATTR_JOIN_TICK, 0)
|
||||
if (joinTick == 0) return false
|
||||
val elapsed = getWorldTicks() - joinTick
|
||||
return elapsed >= minMinutes * 100 // ticksPerMinute = 100
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the player has a Castle Wars bracelet equipped.
|
||||
* The bracelet gives bonus damage against flag carriers and improved bandage healing.
|
||||
|
|
|
|||
|
|
@ -373,15 +373,11 @@ class CastleWarsListeners : InteractionListener {
|
|||
}
|
||||
|
||||
on(CastleWars.saradominExitPortal, IntType.SCENERY, "Leave") { player, _ ->
|
||||
CastleWarsGameArea.removePlayerFromGame(player)
|
||||
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
|
||||
sendMessage(player, "You have left the Castle Wars game.")
|
||||
leaveGameThroughPortal(player, CastleWarsTeam.SARADOMIN)
|
||||
return@on true
|
||||
}
|
||||
on(CastleWars.zamorakExitPortal, IntType.SCENERY, "Leave") { player, _ ->
|
||||
CastleWarsGameArea.removePlayerFromGame(player)
|
||||
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
|
||||
sendMessage(player, "You have left the Castle Wars game.")
|
||||
leaveGameThroughPortal(player, CastleWarsTeam.ZAMORAK)
|
||||
return@on true
|
||||
}
|
||||
|
||||
|
|
@ -1018,14 +1014,52 @@ class CastleWarsListeners : InteractionListener {
|
|||
CastleWarsGameState.dropFlagAtLocation(player, barrierData.outsideTile)
|
||||
}
|
||||
|
||||
private fun hasNonCombatItems(container: Container): Boolean {
|
||||
private fun leaveGameThroughPortal(player: Player, portalTeam: CastleWarsTeam) {
|
||||
if (CastleWarsGameState.getPlayerTeam(player) != portalTeam) {
|
||||
sendMessage(player, "Only the ${portalTeam.teamName} team may use this portal.")
|
||||
return
|
||||
}
|
||||
CastleWarsGameArea.removePlayerFromGame(player)
|
||||
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
|
||||
sendMessage(player, "You have left the Castle Wars game.")
|
||||
}
|
||||
|
||||
private fun hasDisallowedCastleWarsItems(container: Container): Boolean {
|
||||
for (item in container.toArray()) {
|
||||
if (item?.id != null && (item.id == Items.COINS_995 || item.definition?.noteId == item.id))
|
||||
if (item != null && !isAllowedCastleWarsInventoryItem(item)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isAllowedCastleWarsInventoryItem(item: Item): Boolean {
|
||||
if (item.id == Items.COINS_995) return false
|
||||
val definition = item.definition ?: return false
|
||||
if (!definition.isUnnoted) return false
|
||||
val equipmentSlot = equipSlot(item.id)
|
||||
if (equipmentSlot != null) {
|
||||
return equipmentSlot != EquipmentSlot.HEAD && equipmentSlot != EquipmentSlot.CAPE
|
||||
}
|
||||
val itemName = definition.name.lowercase()
|
||||
return isRune(itemName) ||
|
||||
definition.hasAction("drink") ||
|
||||
itemName.contains("potion") ||
|
||||
isHunterButterfly(itemName)
|
||||
}
|
||||
|
||||
private fun isRune(itemName: String): Boolean {
|
||||
return itemName.endsWith(" rune") || itemName.endsWith(" runes")
|
||||
}
|
||||
|
||||
private fun isHunterButterfly(itemName: String): Boolean {
|
||||
return itemName.contains("butterfly") ||
|
||||
itemName.contains("ruby harvest") ||
|
||||
itemName.contains("sapphire glacialis") ||
|
||||
itemName.contains("snowy knight") ||
|
||||
itemName.contains("black warlock")
|
||||
}
|
||||
|
||||
private fun capeOrHelmetError(player: Player): String? {
|
||||
val wornCape = getItemFromEquipment(player, EquipmentSlot.CAPE)?.id ?: -1
|
||||
val wornHelmet = getItemFromEquipment(player, EquipmentSlot.HEAD)?.id ?: -1
|
||||
|
|
@ -1035,7 +1069,7 @@ class CastleWarsListeners : InteractionListener {
|
|||
}
|
||||
|
||||
private fun nonCombatItemsCheck(player: Player): String? {
|
||||
if (hasNonCombatItems(player.inventory)) return "You can't take non-combat items into the arena."
|
||||
if (hasDisallowedCastleWarsItems(player.inventory)) return "You can't take non-combat items into the arena."
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
@ -1043,7 +1077,7 @@ class CastleWarsListeners : InteractionListener {
|
|||
private fun familiarCheck(player: Player): String? {
|
||||
val familiar: BurdenBeast = player.familiarManager.familiar as? BurdenBeast ?: return null
|
||||
|
||||
if (hasNonCombatItems(familiar.container)) return "Your familiar can't take non-combat items into the arena."
|
||||
if (hasDisallowedCastleWarsItems(familiar.container)) return "Your familiar can't take non-combat items into the arena."
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import core.game.node.entity.player.Player
|
|||
*/
|
||||
object CastleWarsOverlay {
|
||||
@JvmStatic
|
||||
fun sendLobbyUpdate(player: Player, enoughPlayers: Boolean, gameStartMinutes: Int) {
|
||||
// When not enough players, send -1 to hide the timer display
|
||||
setVarp(player, 380, if (enoughPlayers) gameStartMinutes else -1)
|
||||
fun sendLobbyUpdate(player: Player, gameStartMinutes: Int) {
|
||||
setVarp(player, 380, gameStartMinutes)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package content.minigame.castlewars
|
||||
|
||||
import content.minigame.castlewars.areas.CastleWarsGameArea
|
||||
import core.api.sendMessage
|
||||
import core.game.dialogue.DialogueFile
|
||||
import core.game.dialogue.Topic
|
||||
import core.tools.END_DIALOGUE
|
||||
import core.tools.START_DIALOGUE
|
||||
|
||||
class CastleWarsSubstitutionDialogue(private val team: CastleWarsTeam) : DialogueFile() {
|
||||
|
||||
override fun handle(componentID: Int, buttonID: Int) {
|
||||
when (stage) {
|
||||
START_DIALOGUE -> showTopics(
|
||||
Topic("Yes.", ACCEPT, true),
|
||||
Topic("No.", DECLINE, true),
|
||||
title = "Do you wish to join the current game?"
|
||||
)
|
||||
|
||||
ACCEPT -> {
|
||||
if (!CastleWarsGameArea.claimVacancy(player!!, team)) {
|
||||
sendMessage(player!!, "That Castle Wars vacancy is no longer available.")
|
||||
}
|
||||
stage = END_DIALOGUE
|
||||
}
|
||||
|
||||
DECLINE -> {
|
||||
CastleWarsGameArea.declineVacancyOffer(player!!, team)
|
||||
stage = END_DIALOGUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ACCEPT = 10
|
||||
private const val DECLINE = 20
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ import core.game.interaction.IntType
|
|||
import core.game.interaction.InteractionListener
|
||||
import core.game.node.item.Item
|
||||
import core.tools.END_DIALOGUE
|
||||
import core.tools.ticksPerMinute
|
||||
import org.rs09.consts.Items
|
||||
import org.rs09.consts.NPCs
|
||||
|
||||
|
|
@ -142,7 +141,8 @@ class Lanthus: InteractionListener, TickListener {
|
|||
return
|
||||
}
|
||||
|
||||
val minutesLeft = (countdown + ticksPerMinute - 1) / ticksPerMinute
|
||||
val minutesLeft =
|
||||
(countdown + CastleWarsConstants.TICKS_PER_MINUTE - 1) / CastleWarsConstants.TICKS_PER_MINUTE
|
||||
|
||||
// Announce at minute boundaries, only once per distinct minute value
|
||||
if (minutesLeft != lastAnnouncedMinute && minutesLeft in 1..5) {
|
||||
|
|
@ -152,4 +152,4 @@ class Lanthus: InteractionListener, TickListener {
|
|||
sendChat(lanthus, "The next game will start in $minutesLeft $minuteWord!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,12 @@ import core.game.world.map.Location
|
|||
import core.game.world.map.zone.ZoneBorders
|
||||
import core.game.world.map.zone.ZoneType
|
||||
import core.tools.Log
|
||||
import core.tools.ticksPerMinute
|
||||
import org.rs09.consts.Components
|
||||
|
||||
/**
|
||||
* Handles the Castle Wars game map
|
||||
*/
|
||||
class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
||||
class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
|
||||
|
||||
companion object {
|
||||
private val saradominStandardFloor: ZoneBorders =
|
||||
|
|
@ -64,8 +63,15 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
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>()
|
||||
data class LogoutRecord(
|
||||
val player: Player,
|
||||
val team: CastleWarsTeam,
|
||||
val logoutTick: Int,
|
||||
val logoutLocation: Location
|
||||
)
|
||||
|
||||
/** Players who have logged out but are within the grace period. Maps normalized username -> logout record. */
|
||||
val loggedOutPlayers = mutableMapOf<String, LogoutRecord>()
|
||||
|
||||
var ticksLeftInGame = -1
|
||||
|
||||
|
|
@ -73,6 +79,10 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
private val declinedSubstitutionOffers = mutableMapOf<CastleWarsTeam, MutableSet<Player>>()
|
||||
|
||||
fun startGame() {
|
||||
if (!CastleWarsWaitingArea.hasMinimumPlayers()) {
|
||||
CastleWarsWaitingArea.gameStartCountdown = CastleWarsConstants.WAITING_ROOM_COUNTDOWN_TICKS
|
||||
return
|
||||
}
|
||||
clearSubstitutionState()
|
||||
CastleWarsGameState.resetGame()
|
||||
CastleWarsItemHandler.clearAllBarricades()
|
||||
|
|
@ -81,7 +91,7 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
zamorakPlayers.addAll(CastleWarsWaitingArea.waitingZamorakPlayers)
|
||||
CastleWarsWaitingArea.waitingSaradominPlayers.clear()
|
||||
CastleWarsWaitingArea.waitingZamorakPlayers.clear()
|
||||
ticksLeftInGame = CastleWars.gameTimeMinutes * ticksPerMinute
|
||||
ticksLeftInGame = CastleWars.gameTimeMinutes * CastleWarsConstants.TICKS_PER_MINUTE
|
||||
saradominPlayers.forEach { player ->
|
||||
player.properties.teleportLocation = CastleWarsRespawnArea.saradominRespawnRoom.randomWalkableLoc
|
||||
setAttribute(player, CastleWarsConstants.ATTR_JOIN_TICK, getWorldTicks())
|
||||
|
|
@ -111,8 +121,6 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
}
|
||||
|
||||
fun selectGuthixTeam(): CastleWarsTeam {
|
||||
combatLevelVacancyTeam()?.let { return it }
|
||||
|
||||
val saraTotal = CastleWarsWaitingArea.waitingSaradominPlayers.size + saradominPlayers.size
|
||||
val zamTotal = CastleWarsWaitingArea.waitingZamorakPlayers.size + zamorakPlayers.size
|
||||
return when {
|
||||
|
|
@ -123,21 +131,6 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
}
|
||||
}
|
||||
|
||||
fun combatLevelVacancyTeam(): CastleWarsTeam? {
|
||||
if (ticksLeftInGame <= 0) return null
|
||||
val saraTotal = teamCombatLevelTotal(saradominPlayers)
|
||||
val zamTotal = teamCombatLevelTotal(zamorakPlayers)
|
||||
return when {
|
||||
zamTotal - saraTotal > CastleWarsConstants.COMBAT_LEVEL_VACANCY_THRESHOLD -> CastleWarsTeam.SARADOMIN
|
||||
saraTotal - zamTotal > CastleWarsConstants.COMBAT_LEVEL_VACANCY_THRESHOLD -> CastleWarsTeam.ZAMORAK
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun teamCombatLevelTotal(players: Set<Player>): Int {
|
||||
return players.sumOf { it.properties.currentCombatLevel }
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer a vacancy on the given team to every eligible player in that team's waiting room.
|
||||
* Called when a player leaves an active game.
|
||||
|
|
@ -159,11 +152,6 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
}
|
||||
}
|
||||
|
||||
fun offerCombatLevelVacancyIfNeeded() {
|
||||
val team = combatLevelVacancyTeam() ?: return
|
||||
fillVacancy(team)
|
||||
}
|
||||
|
||||
fun claimVacancy(player: Player, team: CastleWarsTeam): Boolean {
|
||||
val offer = activeVacancyOffers[team] ?: return false
|
||||
if (!offer.contains(player)) return false
|
||||
|
|
@ -186,7 +174,6 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
}
|
||||
player.properties.teleportLocation = respawnLoc
|
||||
sendMessage(player, "A spot has opened up! You have been moved into the Castle Wars game.")
|
||||
offerCombatLevelVacancyIfNeeded()
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -209,18 +196,109 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
CastleWarsGameState.handlePlayerLeave(player)
|
||||
player.unhook(SaradominInterfaceCloseHook)
|
||||
player.unhook(ZamorakInterfaceCloseHook)
|
||||
loggedOutPlayers.remove(player)
|
||||
clearLogoutState(player)
|
||||
saradominPlayers.remove(player)
|
||||
zamorakPlayers.remove(player)
|
||||
CastleWarsGameState.clearHintIconForPlayer(player)
|
||||
player.interaction.remove(Option._P_ATTACK)
|
||||
if (offerReplacement) {
|
||||
fillVacancy(leavingTeam)
|
||||
offerCombatLevelVacancyIfNeeded()
|
||||
}
|
||||
return leavingTeam
|
||||
}
|
||||
|
||||
fun playerKey(player: Player): String {
|
||||
return player.username.lowercase()
|
||||
}
|
||||
|
||||
private fun setTeam(player: Player, team: CastleWarsTeam) {
|
||||
when (team) {
|
||||
CastleWarsTeam.SARADOMIN -> {
|
||||
zamorakPlayers.remove(player)
|
||||
saradominPlayers.add(player)
|
||||
}
|
||||
|
||||
CastleWarsTeam.ZAMORAK -> {
|
||||
saradominPlayers.remove(player)
|
||||
zamorakPlayers.add(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeFromTeam(player: Player) {
|
||||
saradominPlayers.remove(player)
|
||||
zamorakPlayers.remove(player)
|
||||
}
|
||||
|
||||
private fun clearLogoutAttributes(player: Player) {
|
||||
removeAttributes(
|
||||
player,
|
||||
CastleWarsConstants.ATTR_LOGOUT_TICK,
|
||||
CastleWarsConstants.ATTR_LOGOUT_TIME_MILLIS,
|
||||
CastleWarsConstants.ATTR_LOGOUT_TEAM,
|
||||
CastleWarsConstants.ATTR_LOGOUT_LOCATION
|
||||
)
|
||||
}
|
||||
|
||||
private fun clearLogoutState(player: Player) {
|
||||
loggedOutPlayers.remove(playerKey(player))
|
||||
clearLogoutAttributes(player)
|
||||
}
|
||||
|
||||
private fun waitingRoomFor(team: CastleWarsTeam): ZoneBorders {
|
||||
return when (team) {
|
||||
CastleWarsTeam.SARADOMIN -> CastleWarsWaitingArea.saradominWaitingRoom
|
||||
CastleWarsTeam.ZAMORAK -> CastleWarsWaitingArea.zamorakWaitingRoom
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitingSetFor(team: CastleWarsTeam): MutableSet<Player> {
|
||||
return when (team) {
|
||||
CastleWarsTeam.SARADOMIN -> CastleWarsWaitingArea.waitingSaradominPlayers
|
||||
CastleWarsTeam.ZAMORAK -> CastleWarsWaitingArea.waitingZamorakPlayers
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveLoggedOutPlayerToWaitingRoom(player: Player, team: CastleWarsTeam) {
|
||||
removeFromTeam(player)
|
||||
CastleWarsGameState.clearHintIconForPlayer(player)
|
||||
CastleWarsGameState.restoreAndCleanupPlayer(player)
|
||||
player.interaction.remove(Option._P_ATTACK)
|
||||
player.location = waitingRoomFor(team).randomWalkableLoc
|
||||
player.equipment.replace(Item(team.cloakId), 1)
|
||||
waitingSetFor(team).add(player)
|
||||
clearLogoutAttributes(player)
|
||||
}
|
||||
|
||||
fun restoreActiveLogoutOnLogin(player: Player) {
|
||||
val savedTeamName = getAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TEAM, "")
|
||||
val savedLogoutTime = getAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TIME_MILLIS, 0L)
|
||||
if (savedTeamName.isEmpty() || savedLogoutTime <= 0L) return
|
||||
val team = CastleWarsTeam.values().firstOrNull { it.name == savedTeamName } ?: return
|
||||
val key = playerKey(player)
|
||||
val record = loggedOutPlayers.remove(key)
|
||||
val previousPlayer = record?.player
|
||||
if (previousPlayer != null && previousPlayer != player) {
|
||||
removeFromTeam(previousPlayer)
|
||||
}
|
||||
val elapsedMillis = System.currentTimeMillis() - savedLogoutTime
|
||||
val logoutLocation = getAttribute(
|
||||
player,
|
||||
CastleWarsConstants.ATTR_LOGOUT_LOCATION,
|
||||
record?.logoutLocation ?: player.location
|
||||
)
|
||||
if (ticksLeftInGame > 0 && elapsedMillis < CastleWarsConstants.LOGOUT_GRACE_PERIOD_MILLIS) {
|
||||
player.location = Location.create(logoutLocation)
|
||||
setTeam(player, team)
|
||||
clearLogoutAttributes(player)
|
||||
return
|
||||
}
|
||||
moveLoggedOutPlayerToWaitingRoom(player, team)
|
||||
if (ticksLeftInGame > 0) {
|
||||
fillVacancy(team)
|
||||
}
|
||||
}
|
||||
|
||||
object SaradominInterfaceCloseHook : EventHook<InterfaceCloseEvent> { // TODO: this might be useless.
|
||||
override fun process(entity: Entity, event: InterfaceCloseEvent) {
|
||||
val player = entity as? Player ?: return
|
||||
|
|
@ -249,7 +327,6 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
}
|
||||
|
||||
private fun endGame() { // TODO: ropes, rocks in tunnels, ground items, castle doors, catapults
|
||||
CastleWarsWaitingArea.postGameCooldown = true
|
||||
CastleWarsGameState.endGame()
|
||||
CastleWarsItemHandler.clearAllBarricades()
|
||||
clearSubstitutionState()
|
||||
|
|
@ -274,13 +351,6 @@ 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))
|
||||
|
|
@ -316,20 +386,27 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
}
|
||||
|
||||
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()
|
||||
val team = when {
|
||||
saradominPlayers.contains(player) -> CastleWarsTeam.SARADOMIN
|
||||
zamorakPlayers.contains(player) -> CastleWarsTeam.ZAMORAK
|
||||
else -> null
|
||||
}
|
||||
if (ticksLeftInGame > 0 && team != null) {
|
||||
val logoutTick = getWorldTicks()
|
||||
val logoutLocation = Location.create(player.location)
|
||||
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TICK, logoutTick)
|
||||
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TIME_MILLIS, System.currentTimeMillis())
|
||||
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TEAM, team.name)
|
||||
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_LOCATION, logoutLocation)
|
||||
loggedOutPlayers[playerKey(player)] = LogoutRecord(player, team, logoutTick, logoutLocation)
|
||||
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
|
||||
return
|
||||
} else {
|
||||
// No active game or not on a team -- full cleanup
|
||||
CastleWarsGameState.handlePlayerLeave(player)
|
||||
CastleWarsGameState.clearHintIconForPlayer(player)
|
||||
saradominPlayers.remove(player)
|
||||
|
|
@ -338,6 +415,10 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
super.logout(player)
|
||||
}
|
||||
|
||||
override fun login(player: Player) {
|
||||
restoreActiveLogoutOnLogin(player)
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
if (ticksLeftInGame < 0) {
|
||||
return
|
||||
|
|
@ -346,17 +427,11 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
endGame()
|
||||
return
|
||||
}
|
||||
val previousMinutes = (ticksLeftInGame + ticksPerMinute - 1) / ticksPerMinute
|
||||
val previousMinutes =
|
||||
(ticksLeftInGame + CastleWarsConstants.TICKS_PER_MINUTE - 1) / CastleWarsConstants.TICKS_PER_MINUTE
|
||||
ticksLeftInGame--
|
||||
val currentMinutes = (ticksLeftInGame + ticksPerMinute - 1) / ticksPerMinute
|
||||
if (ticksLeftInGame > 0) {
|
||||
val saraEmpty = saradominPlayers.isEmpty()
|
||||
val zamEmpty = zamorakPlayers.isEmpty()
|
||||
if (saraEmpty || zamEmpty) {
|
||||
endGame()
|
||||
return
|
||||
}
|
||||
}
|
||||
val currentMinutes =
|
||||
(ticksLeftInGame + CastleWarsConstants.TICKS_PER_MINUTE - 1) / CastleWarsConstants.TICKS_PER_MINUTE
|
||||
if (ticksLeftInGame == 0) {
|
||||
endGame()
|
||||
return
|
||||
|
|
@ -365,7 +440,6 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
}
|
||||
CastleWarsGameState.checkFlagCarriers()
|
||||
CastleWarsGameState.checkDroppedFlagTimeouts()
|
||||
offerCombatLevelVacancyIfNeeded()
|
||||
for (player in saradominPlayers) {
|
||||
val currentOverlayId = player.interfaceManager.overlay?.id
|
||||
if (currentOverlayId == null || currentOverlayId != 58) {
|
||||
|
|
@ -393,28 +467,23 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
|||
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
|
||||
val expiredLogouts = loggedOutPlayers.filter { (_, record) ->
|
||||
currentTick - record.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)
|
||||
for ((key, record) in expiredLogouts) {
|
||||
val player = record.player
|
||||
loggedOutPlayers.remove(key)
|
||||
removeFromTeam(player)
|
||||
CastleWarsGameState.clearHintIconForPlayer(player)
|
||||
CastleWarsGameState.restoreAndCleanupPlayer(player)
|
||||
if (team != null) fillVacancy(team)
|
||||
fillVacancy(record.team)
|
||||
}
|
||||
// Remove truly inactive players (not in grace period)
|
||||
val inactiveSara = saradominPlayers.filter { player ->
|
||||
(!player.isActive || player.session == null) && !loggedOutPlayers.containsKey(player)
|
||||
(!player.isActive || player.session == null) && !loggedOutPlayers.containsKey(playerKey(player))
|
||||
}
|
||||
val inactiveZam = zamorakPlayers.filter { player ->
|
||||
(!player.isActive || player.session == null) && !loggedOutPlayers.containsKey(player)
|
||||
(!player.isActive || player.session == null) && !loggedOutPlayers.containsKey(playerKey(player))
|
||||
}
|
||||
for (player in inactiveSara) {
|
||||
saradominPlayers.remove(player)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package content.minigame.castlewars.areas
|
||||
|
||||
import content.minigame.castlewars.CastleWars
|
||||
import content.minigame.castlewars.CastleWarsConstants
|
||||
import content.minigame.castlewars.CastleWarsGameState
|
||||
import core.api.TickListener
|
||||
import core.api.sendMessage
|
||||
import core.api.*
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.item.Item
|
||||
|
|
@ -21,8 +21,8 @@ class CastleWarsRespawnArea : CastleWarsArea(), TickListener {
|
|||
val saradominRespawnRoom: ZoneBorders =
|
||||
ZoneBorders(Location.create(2423, 3080, 1), Location.create(2431, 3072, 1))
|
||||
|
||||
val zamorakPlayersInRespawnRoom = mutableSetOf<Player>()
|
||||
val saradominPlayersInRespawnRoom = mutableSetOf<Player>()
|
||||
val zamorakPlayersInRespawnRoom = mutableMapOf<Player, Int>()
|
||||
val saradominPlayersInRespawnRoom = mutableMapOf<Player, Int>()
|
||||
}
|
||||
|
||||
override fun defineAreaBorders(): Array<ZoneBorders> {
|
||||
|
|
@ -42,12 +42,47 @@ class CastleWarsRespawnArea : CastleWarsArea(), TickListener {
|
|||
}
|
||||
if (saradominRespawnRoom.insideBorder(player.location)) {
|
||||
player.equipment.replace(Item(CastleWars.saradominTeamHoodedCloak), 1)
|
||||
saradominPlayersInRespawnRoom[player] = getWorldTicks()
|
||||
} else if (zamorakRespawnRoom.insideBorder(player.location)) {
|
||||
player.equipment.replace(Item(CastleWars.zamorakTeamHoodedCloak), 1)
|
||||
zamorakPlayersInRespawnRoom[player] = getWorldTicks()
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
if (CastleWarsGameArea.ticksLeftInGame <= 0) {
|
||||
saradominPlayersInRespawnRoom.clear()
|
||||
zamorakPlayersInRespawnRoom.clear()
|
||||
return
|
||||
}
|
||||
removePlayersOverSpawnLimit(saradominPlayersInRespawnRoom)
|
||||
removePlayersOverSpawnLimit(zamorakPlayersInRespawnRoom)
|
||||
}
|
||||
|
||||
private fun removePlayersOverSpawnLimit(players: MutableMap<Player, Int>) {
|
||||
val currentTick = getWorldTicks()
|
||||
val expiredPlayers = players.filter { (player, entryTick) ->
|
||||
player.isActive &&
|
||||
player.session != null &&
|
||||
currentTick - entryTick >= CastleWarsConstants.LOGOUT_GRACE_PERIOD_TICKS
|
||||
}.keys
|
||||
for (player in expiredPlayers) {
|
||||
players.remove(player)
|
||||
CastleWarsGameArea.removePlayerFromGame(player)
|
||||
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
|
||||
sendMessage(player, "You have been removed from Castle Wars for staying in the spawn room too long.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun logout(player: Player) {
|
||||
if (!defineAreaBorders().any { it.insideBorder(player.location) }) {
|
||||
return
|
||||
}
|
||||
if (CastleWarsGameArea.ticksLeftInGame > 0 && CastleWarsGameState.getPlayerTeam(player) != null) {
|
||||
CastleWarsGameArea().logout(player)
|
||||
return
|
||||
}
|
||||
super.logout(player)
|
||||
}
|
||||
|
||||
override fun exitArea(player: Player) {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ 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
|
||||
|
||||
/**
|
||||
|
|
@ -27,7 +25,11 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
|
|||
val waitingSaradominPlayers = mutableSetOf<Player>()
|
||||
val waitingZamorakPlayers = mutableSetOf<Player>()
|
||||
internal var gameStartCountdown = -1
|
||||
var postGameCooldown = false
|
||||
|
||||
fun hasMinimumPlayers(): Boolean {
|
||||
return waitingSaradominPlayers.size >= CastleWarsConstants.MIN_PLAYERS_PER_TEAM &&
|
||||
waitingZamorakPlayers.size >= CastleWarsConstants.MIN_PLAYERS_PER_TEAM
|
||||
}
|
||||
}
|
||||
|
||||
override fun defineAreaBorders(): Array<ZoneBorders> {
|
||||
|
|
@ -105,7 +107,6 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
|
|||
// Open the white text overlay
|
||||
player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_57))
|
||||
|
||||
CastleWarsGameArea.offerCombatLevelVacancyIfNeeded()
|
||||
}
|
||||
|
||||
override fun exitArea(player: Player) {
|
||||
|
|
@ -116,44 +117,37 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
|
|||
}
|
||||
|
||||
override fun tick() {
|
||||
var nextStart = Int.MAX_VALUE
|
||||
// The game will start when there's enough players and CastleWarsGameArea.ticksLeftInGame = -5 minutes
|
||||
val nextStart: Int
|
||||
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.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
|
||||
nextStart = CastleWarsGameArea.ticksLeftInGame +
|
||||
CastleWars.gameCooldownMinutes * CastleWarsConstants.TICKS_PER_MINUTE
|
||||
} 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
|
||||
gameStartCountdown = CastleWarsConstants.WAITING_ROOM_COUNTDOWN_TICKS
|
||||
}
|
||||
nextStart = gameStartCountdown
|
||||
gameStartCountdown--
|
||||
}
|
||||
val minPlayers = if (GameWorld.settings?.isDevMode == true) 1 else CastleWarsConstants.MIN_PLAYERS_PER_TEAM
|
||||
for (player in waitingSaradominPlayers + waitingZamorakPlayers) {
|
||||
CastleWarsOverlay.sendLobbyUpdate(
|
||||
player,
|
||||
(waitingSaradominPlayers.size >= minPlayers && waitingZamorakPlayers.size >= minPlayers) || CastleWarsGameArea.ticksLeftInGame > 0,
|
||||
(nextStart - 1) / ticksPerMinute + 1 // Displays max 5 minutes, min 1 minute, seems authentic
|
||||
ticksToDisplayMinutes(nextStart)
|
||||
)
|
||||
}
|
||||
if (nextStart <= 0) {
|
||||
gameStartCountdown = -1 // Reset countdown
|
||||
if (CastleWarsGameArea.ticksLeftInGame <= 0 && nextStart <= 0) {
|
||||
gameStartCountdown = -1
|
||||
if (!hasMinimumPlayers()) {
|
||||
gameStartCountdown = CastleWarsConstants.WAITING_ROOM_COUNTDOWN_TICKS
|
||||
return
|
||||
}
|
||||
CastleWarsGameArea.startGame()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ticksToDisplayMinutes(ticks: Int): Int {
|
||||
if (ticks <= 0) return 0
|
||||
return (ticks - 1) / CastleWarsConstants.TICKS_PER_MINUTE + 1
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import content.global.skill.thieving.Pickpockets
|
|||
import content.minigame.castlewars.areas.CastleWarsGameArea
|
||||
import content.minigame.castlewars.areas.CastleWarsRespawnArea
|
||||
import content.minigame.castlewars.areas.CastleWarsWaitingArea
|
||||
import core.api.EquipmentSlot
|
||||
import core.api.*
|
||||
import core.game.node.entity.skill.Skills
|
||||
import core.game.global.action.EquipHandler
|
||||
import core.game.interaction.IntType
|
||||
|
|
@ -14,6 +14,7 @@ import core.game.interaction.InteractionListeners
|
|||
import core.game.node.entity.player.Player
|
||||
import core.game.node.item.Item
|
||||
import core.game.node.scenery.Scenery
|
||||
import core.game.world.GameWorld
|
||||
import core.game.world.map.Location
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
|
@ -50,9 +51,10 @@ class CastleWarsTests {
|
|||
CastleWarsWaitingArea.waitingSaradominPlayers.clear()
|
||||
CastleWarsWaitingArea.waitingZamorakPlayers.clear()
|
||||
CastleWarsWaitingArea.gameStartCountdown = -1
|
||||
CastleWarsWaitingArea.postGameCooldown = false
|
||||
CastleWarsGameArea.ticksLeftInGame = -1
|
||||
CastleWarsGameArea.clearSubstitutionState()
|
||||
CastleWarsRespawnArea.saradominPlayersInRespawnRoom.clear()
|
||||
CastleWarsRespawnArea.zamorakPlayersInRespawnRoom.clear()
|
||||
CastleWarsGameState.resetGame()
|
||||
}
|
||||
|
||||
|
|
@ -71,21 +73,6 @@ class CastleWarsTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun guthixPortalRoutesToCombatDisadvantagedTeamDuringActiveMatch() {
|
||||
TestUtils.getMockPlayer("cwweak").use { saradomin ->
|
||||
TestUtils.getMockPlayer("cwstrong").use { zamorak ->
|
||||
CastleWarsGameArea.ticksLeftInGame = 100
|
||||
setCombatLevel(saradomin, 50)
|
||||
setCombatLevel(zamorak, 120)
|
||||
CastleWarsGameArea.saradominPlayers.add(saradomin)
|
||||
CastleWarsGameArea.zamorakPlayers.add(zamorak)
|
||||
|
||||
assertEquals(CastleWarsTeam.SARADOMIN, CastleWarsGameArea.selectGuthixTeam())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun spawnRoomPortalOffersVacancyAndFirstAcceptanceWins() {
|
||||
TestUtils.getMockPlayer("cwleaver").use { leaver ->
|
||||
|
|
@ -129,28 +116,84 @@ class CastleWarsTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun combatLevelImbalanceAutomaticallyOffersVacancyToDisadvantagedWaitingRoom() {
|
||||
TestUtils.getMockPlayer("cwweak2").use { saradomin ->
|
||||
TestUtils.getMockPlayer("cwstrong2").use { zamorak ->
|
||||
TestUtils.getMockPlayer("cwsub3").use { waiting ->
|
||||
CastleWarsGameArea.ticksLeftInGame = 100
|
||||
setCombatLevel(saradomin, 50)
|
||||
setCombatLevel(zamorak, 120)
|
||||
CastleWarsGameArea.saradominPlayers.add(saradomin)
|
||||
CastleWarsGameArea.zamorakPlayers.add(zamorak)
|
||||
CastleWarsWaitingArea.waitingSaradominPlayers.add(waiting)
|
||||
fun activeMatchDoesNotOfferSubstitutionWithoutVacancy() {
|
||||
TestUtils.getMockPlayer("cwsubnovacancy").use { waiting ->
|
||||
CastleWarsGameArea.ticksLeftInGame = 100
|
||||
CastleWarsWaitingArea.waitingSaradominPlayers.add(waiting)
|
||||
|
||||
CastleWarsGameArea.offerCombatLevelVacancyIfNeeded()
|
||||
CastleWarsWaitingArea().areaEnter(waiting)
|
||||
|
||||
assertSubstitutionDialogue(waiting)
|
||||
assertTrue(CastleWarsGameArea.claimVacancy(waiting, CastleWarsTeam.SARADOMIN))
|
||||
assertTrue(CastleWarsGameArea.saradominPlayers.contains(waiting))
|
||||
assertFalse(CastleWarsWaitingArea.waitingSaradominPlayers.contains(waiting))
|
||||
assertFalse(waiting.dialogueInterpreter.dialogue?.file is CastleWarsSubstitutionDialogue)
|
||||
assertFalse(CastleWarsGameArea.claimVacancy(waiting, CastleWarsTeam.SARADOMIN))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun waitingRoomTimerShowsTwoMinuteMinimumWithoutEnoughPlayers() {
|
||||
TestUtils.getMockPlayer("cwminwait").use { waiting ->
|
||||
CastleWarsWaitingArea.waitingSaradominPlayers.add(waiting)
|
||||
|
||||
CastleWarsWaitingArea().tick()
|
||||
|
||||
assertEquals(2, getVarp(waiting, 380))
|
||||
assertEquals(CastleWarsConstants.WAITING_ROOM_COUNTDOWN_TICKS - 1, CastleWarsWaitingArea.gameStartCountdown)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun waitingRoomTimerCyclesWhenMinimumPlayersAreMissingAtExpiry() {
|
||||
TestUtils.getMockPlayer("cwcycle").use { waiting ->
|
||||
CastleWarsWaitingArea.waitingSaradominPlayers.add(waiting)
|
||||
CastleWarsWaitingArea.gameStartCountdown = 0
|
||||
|
||||
CastleWarsWaitingArea().tick()
|
||||
|
||||
assertEquals(-1, CastleWarsGameArea.ticksLeftInGame)
|
||||
assertTrue(CastleWarsWaitingArea.waitingSaradominPlayers.contains(waiting))
|
||||
assertEquals(CastleWarsConstants.WAITING_ROOM_COUNTDOWN_TICKS, CastleWarsWaitingArea.gameStartCountdown)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun waitingRoomTimerStartsGameWhenBothTeamsHaveTwoPlayersAtExpiry() {
|
||||
TestUtils.getMockPlayer("cwstarts1").use { sara1 ->
|
||||
TestUtils.getMockPlayer("cwstarts2").use { sara2 ->
|
||||
TestUtils.getMockPlayer("cwstartz1").use { zam1 ->
|
||||
TestUtils.getMockPlayer("cwstartz2").use { zam2 ->
|
||||
CastleWarsWaitingArea.waitingSaradominPlayers.addAll(listOf(sara1, sara2))
|
||||
CastleWarsWaitingArea.waitingZamorakPlayers.addAll(listOf(zam1, zam2))
|
||||
CastleWarsWaitingArea.gameStartCountdown = 0
|
||||
|
||||
CastleWarsWaitingArea().tick()
|
||||
|
||||
assertEquals(
|
||||
CastleWarsConstants.GAME_TIME_MINUTES * CastleWarsConstants.TICKS_PER_MINUTE,
|
||||
CastleWarsGameArea.ticksLeftInGame
|
||||
)
|
||||
assertTrue(CastleWarsGameArea.saradominPlayers.containsAll(listOf(sara1, sara2)))
|
||||
assertTrue(CastleWarsGameArea.zamorakPlayers.containsAll(listOf(zam1, zam2)))
|
||||
assertTrue(CastleWarsWaitingArea.waitingSaradominPlayers.isEmpty())
|
||||
assertTrue(CastleWarsWaitingArea.waitingZamorakPlayers.isEmpty())
|
||||
assertTrue(CastleWarsRespawnArea.saradominRespawnRoom.insideBorder(sara1.properties.teleportLocation))
|
||||
assertTrue(CastleWarsRespawnArea.zamorakRespawnRoom.insideBorder(zam1.properties.teleportLocation))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activeGameWaitingTimerShowsRemainingGamePlusFiveMinutes() {
|
||||
TestUtils.getMockPlayer("cwactivewait").use { waiting ->
|
||||
CastleWarsGameArea.ticksLeftInGame = 350
|
||||
CastleWarsWaitingArea.waitingSaradominPlayers.add(waiting)
|
||||
|
||||
CastleWarsWaitingArea().tick()
|
||||
|
||||
assertEquals(9, getVarp(waiting, 380))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun idleGameAreaTickDoesNotCountDownForever() {
|
||||
CastleWarsGameArea.ticksLeftInGame = -1
|
||||
|
|
@ -167,7 +210,19 @@ class CastleWarsTests {
|
|||
CastleWarsGameArea().tick()
|
||||
|
||||
assertEquals(-1, CastleWarsGameArea.ticksLeftInGame)
|
||||
assertTrue(CastleWarsWaitingArea.postGameCooldown)
|
||||
assertEquals(-1, CastleWarsWaitingArea.gameStartCountdown)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activeGameDoesNotEndEarlyWhenOneTeamIsTemporarilyEmpty() {
|
||||
TestUtils.getMockPlayer("cwstillrunning").use { player ->
|
||||
CastleWarsGameArea.ticksLeftInGame = 100
|
||||
CastleWarsGameArea.saradominPlayers.add(player)
|
||||
|
||||
CastleWarsGameArea().tick()
|
||||
|
||||
assertEquals(99, CastleWarsGameArea.ticksLeftInGame)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -181,11 +236,203 @@ class CastleWarsTests {
|
|||
CastleWarsGameArea().logout(player)
|
||||
|
||||
assertTrue(CastleWarsGameArea.saradominPlayers.contains(player))
|
||||
assertTrue(CastleWarsGameArea.loggedOutPlayers.containsKey(player))
|
||||
assertTrue(CastleWarsGameArea.loggedOutPlayers.containsKey(CastleWarsGameArea.playerKey(player)))
|
||||
assertEquals(startLocation, player.location)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activeGameLogoutWithinTwoMinutesRejoinsAtExactLocation() {
|
||||
TestUtils.getMockPlayer("cwquickrelog").use { player ->
|
||||
val logoutLocation = Location.create(2401, 3101, 0)
|
||||
player.location = logoutLocation
|
||||
CastleWarsGameArea.ticksLeftInGame = 500
|
||||
CastleWarsGameArea.saradominPlayers.add(player)
|
||||
|
||||
CastleWarsGameArea().logout(player)
|
||||
CastleWarsGameArea().login(player)
|
||||
|
||||
assertTrue(CastleWarsGameArea.saradominPlayers.contains(player))
|
||||
assertFalse(CastleWarsWaitingArea.waitingSaradominPlayers.contains(player))
|
||||
assertFalse(CastleWarsGameArea.loggedOutPlayers.containsKey(CastleWarsGameArea.playerKey(player)))
|
||||
assertEquals(logoutLocation, player.location)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activeGameLogoutAfterTwoMinutesReturnsPlayerToTeamWaitingRoom() {
|
||||
TestUtils.getMockPlayer("cwslowrelog").use { player ->
|
||||
player.location = Location.create(2402, 3102, 0)
|
||||
CastleWarsGameArea.ticksLeftInGame = 500
|
||||
CastleWarsGameArea.saradominPlayers.add(player)
|
||||
|
||||
CastleWarsGameArea().logout(player)
|
||||
setAttribute(
|
||||
player,
|
||||
CastleWarsConstants.ATTR_LOGOUT_TIME_MILLIS,
|
||||
System.currentTimeMillis() - CastleWarsConstants.LOGOUT_GRACE_PERIOD_MILLIS - 1
|
||||
)
|
||||
CastleWarsGameArea().login(player)
|
||||
|
||||
assertFalse(CastleWarsGameArea.saradominPlayers.contains(player))
|
||||
assertTrue(CastleWarsWaitingArea.waitingSaradominPlayers.contains(player))
|
||||
assertTrue(CastleWarsWaitingArea.saradominWaitingRoom.insideBorder(player.location))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun spawnRoomLogoutUsesActiveGameGraceInsteadOfLobbyRemoval() {
|
||||
TestUtils.getMockPlayer("cwspawnlogout").use { player ->
|
||||
val logoutLocation = CastleWarsRespawnArea.saradominRespawnRoom.randomWalkableLoc
|
||||
player.location = logoutLocation
|
||||
CastleWarsGameArea.ticksLeftInGame = 500
|
||||
CastleWarsGameArea.saradominPlayers.add(player)
|
||||
CastleWarsRespawnArea().areaEnter(player)
|
||||
|
||||
CastleWarsRespawnArea().logout(player)
|
||||
CastleWarsGameArea().login(player)
|
||||
|
||||
assertTrue(CastleWarsGameArea.saradominPlayers.contains(player))
|
||||
assertFalse(CastleWarsWaitingArea.waitingSaradominPlayers.contains(player))
|
||||
assertEquals(logoutLocation, player.location)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun waitingRoomLogoutReturnsPlayerToLobbyAndLosesQueuePosition() {
|
||||
TestUtils.getMockPlayer("cwwaitlogout").use { player ->
|
||||
player.location = CastleWarsWaitingArea.zamorakWaitingRoom.randomWalkableLoc
|
||||
CastleWarsWaitingArea().areaEnter(player)
|
||||
|
||||
CastleWarsWaitingArea().logout(player)
|
||||
|
||||
assertFalse(CastleWarsWaitingArea.waitingZamorakPlayers.contains(player))
|
||||
assertTrue(CastleWars.lobbyBankArea.insideBorder(player.location))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun spawnRoomTwoMinuteLimitRemovesPlayerAndOffersVacancy() {
|
||||
TestUtils.getMockPlayer("cwspawnidle").use { player ->
|
||||
TestUtils.getMockPlayer("cwspawnsub").use { waiting ->
|
||||
player.location = CastleWarsRespawnArea.saradominRespawnRoom.randomWalkableLoc
|
||||
CastleWarsGameArea.ticksLeftInGame = 500
|
||||
CastleWarsGameArea.saradominPlayers.add(player)
|
||||
CastleWarsWaitingArea.waitingSaradominPlayers.add(waiting)
|
||||
CastleWarsRespawnArea().areaEnter(player)
|
||||
CastleWarsRespawnArea.saradominPlayersInRespawnRoom[player] =
|
||||
getWorldTicks() - CastleWarsConstants.LOGOUT_GRACE_PERIOD_TICKS
|
||||
|
||||
CastleWarsRespawnArea().tick()
|
||||
|
||||
assertFalse(CastleWarsGameArea.saradominPlayers.contains(player))
|
||||
assertTrue(CastleWars.lobbyBankArea.insideBorder(player.properties.teleportLocation))
|
||||
assertSubstitutionDialogue(waiting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun exitPortalCanOnlyBeUsedByMatchingTeamAndOffersVacancy() {
|
||||
TestUtils.getMockPlayer("cwportalteam").use { player ->
|
||||
TestUtils.getMockPlayer("cwportalwait").use { waiting ->
|
||||
CastleWarsGameArea.ticksLeftInGame = 500
|
||||
CastleWarsGameArea.saradominPlayers.add(player)
|
||||
CastleWarsWaitingArea.waitingSaradominPlayers.add(waiting)
|
||||
|
||||
runSceneryInteraction(
|
||||
CastleWars.zamorakExitPortal,
|
||||
"Leave",
|
||||
player,
|
||||
Scenery(CastleWars.zamorakExitPortal, Location.create(2372, 3131, 1), 10, 0)
|
||||
)
|
||||
assertTrue(CastleWarsGameArea.saradominPlayers.contains(player))
|
||||
|
||||
runSceneryInteraction(
|
||||
CastleWars.saradominExitPortal,
|
||||
"Leave",
|
||||
player,
|
||||
Scenery(CastleWars.saradominExitPortal, Location.create(2427, 3076, 1), 10, 0)
|
||||
)
|
||||
|
||||
assertFalse(CastleWarsGameArea.saradominPlayers.contains(player))
|
||||
assertTrue(CastleWars.lobbyBankArea.insideBorder(player.properties.teleportLocation))
|
||||
assertSubstitutionDialogue(waiting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun gameEndAwardsDrawTicketWithoutFivePerTeamOrTenMinuteGate() {
|
||||
TestUtils.getMockPlayer("cwticket1").use { sara1 ->
|
||||
TestUtils.getMockPlayer("cwticket2").use { sara2 ->
|
||||
TestUtils.getMockPlayer("cwticket3").use { zam1 ->
|
||||
TestUtils.getMockPlayer("cwticket4").use { zam2 ->
|
||||
CastleWarsGameArea.saradominPlayers.addAll(listOf(sara1, sara2))
|
||||
CastleWarsGameArea.zamorakPlayers.addAll(listOf(zam1, zam2))
|
||||
|
||||
CastleWarsGameState.endGame()
|
||||
|
||||
assertEquals(1, sara1.inventory.getAmount(CastleWars.cwTicketRewardCurrency))
|
||||
assertEquals(1, sara2.inventory.getAmount(CastleWars.cwTicketRewardCurrency))
|
||||
assertEquals(1, zam1.inventory.getAmount(CastleWars.cwTicketRewardCurrency))
|
||||
assertEquals(1, zam2.inventory.getAmount(CastleWars.cwTicketRewardCurrency))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun joinPortalRejectsFoodAndInventoryHelmets() {
|
||||
withCastleWarsEnabled {
|
||||
TestUtils.getMockPlayer("cwfood").use { player ->
|
||||
player.inventory.add(Item(Items.SHARK_385))
|
||||
|
||||
runSceneryInteraction(
|
||||
CastleWars.joinSaradominTeamPortal,
|
||||
"Enter",
|
||||
player,
|
||||
Scenery(CastleWars.joinSaradominTeamPortal, Location.create(2440, 3089, 0), 10, 0)
|
||||
)
|
||||
|
||||
assertNull(player.properties.teleportLocation)
|
||||
player.inventory.clear()
|
||||
player.inventory.add(Item(Items.BRONZE_FULL_HELM_1155))
|
||||
|
||||
runSceneryInteraction(
|
||||
CastleWars.joinSaradominTeamPortal,
|
||||
"Enter",
|
||||
player,
|
||||
Scenery(CastleWars.joinSaradominTeamPortal, Location.create(2440, 3089, 0), 10, 0)
|
||||
)
|
||||
|
||||
assertNull(player.properties.teleportLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun joinPortalAllowsCombatEquipmentPotionsAndRunes() {
|
||||
withCastleWarsEnabled {
|
||||
TestUtils.getMockPlayer("cwallowed").use { player ->
|
||||
player.inventory.add(Item(Items.BRONZE_SWORD_1277))
|
||||
player.inventory.add(Item(Items.AIR_RUNE_556, 25))
|
||||
player.inventory.add(Item(Items.ATTACK_POTION2_123))
|
||||
|
||||
runSceneryInteraction(
|
||||
CastleWars.joinSaradominTeamPortal,
|
||||
"Enter",
|
||||
player,
|
||||
Scenery(CastleWars.joinSaradominTeamPortal, Location.create(2440, 3089, 0), 10, 0)
|
||||
)
|
||||
|
||||
assertTrue(CastleWarsWaitingArea.saradominWaitingRoom.insideBorder(player.properties.teleportLocation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun castleWarsPlayersCannotEquipHelmsAfterEntry() {
|
||||
TestUtils.getMockPlayer("cwhelm").use { player ->
|
||||
|
|
@ -377,8 +624,20 @@ class CastleWarsTests {
|
|||
assertTrue(player.dialogueInterpreter.dialogue.file is CastleWarsSubstitutionDialogue)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun setCombatLevel(player: Player, level: Int) {
|
||||
player.properties.setCombatLevel(level)
|
||||
private fun withCastleWarsEnabled(block: () -> Unit) {
|
||||
val settings = GameWorld.settings!!
|
||||
val original = settings.enable_castle_wars
|
||||
settings.enable_castle_wars = true
|
||||
try {
|
||||
block()
|
||||
} finally {
|
||||
settings.enable_castle_wars = original
|
||||
}
|
||||
}
|
||||
|
||||
private fun runSceneryInteraction(id: Int, option: String, player: Player, scenery: Scenery): Boolean {
|
||||
val handler = InteractionListeners.get(id, IntType.SCENERY.ordinal, option)
|
||||
assertNotNull(handler)
|
||||
return handler!!.invoke(player, scenery)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue