Unforeseen consequences

Messageless teleblock, hard ass helmet/cape slot blocking in Castle Wars, many CW fixes
This commit is contained in:
dam 2026-05-27 13:31:07 +03:00
parent 029870a1bd
commit 9716d6caf5
No known key found for this signature in database
GPG key ID: 4AF4E722399663FB
16 changed files with 1043 additions and 142 deletions

View file

@ -9,6 +9,7 @@ import core.game.interaction.InterfaceListener
import core.game.node.entity.player.Player
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.map.RegionManager
class CastleWarsCatapultInterface : InterfaceListener {
@ -67,18 +68,58 @@ class CastleWarsCatapultInterface : InterfaceListener {
sendMessage(player, "Error: Catapult location not found.")
return true
}
val catapultTeam = getCatapultTeam(catapultLocation)
if (!canFireCatapult(player, team, catapultTeam, catapultLocation)) {
return closeAndReturn(player, true)
}
if (!removeItem(player, CastleWars.cwRock)) {
sendMessage(player, "You don't have any rocks to fire!")
return true
}
val catapultTeam = CatapultTeam.fromCastleWarsTeam(team)
firingHandler.fire(player, coord, catapultTeam, catapultLocation)
firingHandler.fire(player, coord, CatapultTeam.fromCastleWarsTeam(catapultTeam), catapultLocation)
closeInterface(player)
return true
}
private fun canFireCatapult(
player: Player,
playerTeam: CastleWarsTeam,
catapultTeam: CastleWarsTeam,
catapultLocation: Location
): Boolean {
if (playerTeam != catapultTeam) {
sendMessage(player, "You can't use the enemy's catapult!")
return false
}
val currentCatapult = RegionManager.getObject(catapultLocation)
if (currentCatapult == null || !currentCatapult.isActive || currentCatapult.id !in CastleWars.allCatapultIds) {
sendMessage(player, "The catapult is no longer there.")
return false
}
if (currentCatapult.id in CastleWars.damagedCatapultIds || !CastleWarsGameState.isCatapultOperational(
catapultTeam
)
) {
sendMessage(player, "The catapult is destroyed! Use a toolkit to repair it.")
return false
}
if (currentCatapult.id in CastleWars.burningCatapultIds || CastleWarsGameState.isCatapultOnFire(
catapultTeam
)
) {
sendMessage(player, "The catapult is on fire! Use a bucket of water to extinguish it.")
return false
}
return true
}
private fun getCatapultTeam(location: Location): CastleWarsTeam {
return if (location.y < 3100) CastleWarsTeam.SARADOMIN else CastleWarsTeam.ZAMORAK
}
private fun handleCoordinateChange(
player: Player, currentCoord: Coordinate, button: CatapultButton
): Boolean {

View file

@ -1,12 +1,9 @@
package content.minigame.castlewars
import content.minigame.castlewars.areas.CastleWarsGameArea
import content.minigame.castlewars.areas.CastleWarsGameArea.Companion.saradominPlayers
import content.minigame.castlewars.areas.CastleWarsGameArea.Companion.zamorakPlayers
import content.minigame.castlewars.areas.CastleWarsWaitingArea
import core.api.Commands
import core.api.sendMessage
import core.game.interaction.Option
import core.game.system.command.Privilege
/**
@ -48,23 +45,7 @@ class CastleWarsCommands : Commands {
sendMessage(player, "No Castle Wars game is currently running.")
return@define
}
// Snapshot the sets before endGame() modifies them via teleport callbacks
val saraSnapshot = saradominPlayers.toSet()
val zamSnapshot = zamorakPlayers.toSet()
CastleWarsGameState.endGame()
CastleWarsItemHandler.clearAllBarricades()
CastleWarsGameArea.ticksLeftInGame = -1
CastleWarsWaitingArea.gameStartCountdown = -1
for (p in saraSnapshot) {
p.interaction.remove(Option._P_ATTACK)
p.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
for (p in zamSnapshot) {
p.interaction.remove(Option._P_ATTACK)
p.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
saradominPlayers.clear()
zamorakPlayers.clear()
CastleWarsGameArea.endCurrentGame()
sendMessage(player, "Castle Wars game ended by admin command.")
}

View file

@ -120,6 +120,9 @@ object CastleWarsConstants {
/** Waiting-room cycle while no game is active: 2 minutes = 200 ticks */
const val WAITING_ROOM_COUNTDOWN_TICKS = 2 * TICKS_PER_MINUTE
/** How long a substitution offer can block later vacancy offers */
const val VACANCY_OFFER_TIMEOUT_TICKS = TICKS_PER_MINUTE
// ==================== Player Attribute Keys ====================
/** Attribute key: world tick when player joined the current game */
const val ATTR_JOIN_TICK = "cw:join_tick"

View file

@ -3,11 +3,14 @@ package content.minigame.castlewars
import core.api.animate
import core.api.playAudio
import core.game.container.impl.EquipmentContainer
import core.game.node.entity.combat.equipment.WeaponInterface
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.game.node.scenery.Scenery
import core.game.system.task.Pulse
import core.tools.RandomFunction
import org.rs09.consts.Sounds
import kotlin.math.floor
/**
* Handles attacking the Castle Wars front doors.
@ -54,18 +57,24 @@ class CastleWarsDoorAttackPulse(
}
private fun calculateDamage(): Int {
// Doors are weak to crush > slash > stab
val strengthLevel = player.skills.getLevel(Skills.STRENGTH)
var effectiveStrength = player.skills.getLevel(Skills.STRENGTH).toDouble()
when (player.properties.attackStyle.style) {
WeaponInterface.STYLE_AGGRESSIVE -> effectiveStrength += 3.0
WeaponInterface.STYLE_CONTROLLED -> effectiveStrength += 1.0
}
effectiveStrength += 8.0
val strengthBonus = player.properties.bonuses[11] + 64
val maxHit = floor(0.5 + (effectiveStrength * strengthBonus / 640.0)).toInt().coerceAtLeast(1)
val bonusType = player.properties.attackStyle.bonusType
val styleBonus = when (bonusType) {
2 -> 10 // Crush
1 -> 4 // Slash
0 -> 1 // Stab
else -> 3 // Default
} // Calculate damage: base from strength + style bonus
val damage =
(strengthLevel / 12) + styleBonus // Clamp to 1-25 range (max 25% of door health per hit) TODO: 100% inauthentic
return damage.coerceIn(1, 25)
val styleMultiplier = when (bonusType) {
WeaponInterface.BONUS_CRUSH -> 1.25
WeaponInterface.BONUS_SLASH -> 1.0
WeaponInterface.BONUS_STAB -> 0.85
else -> 0.75
}
val styleAdjustedMax = floor(maxHit * styleMultiplier).toInt().coerceAtLeast(1)
return RandomFunction.random(styleAdjustedMax + 1).coerceIn(0, 25)
}
private fun getAttackAnimation(): Int {

View file

@ -117,10 +117,12 @@ class CastleWarsFlagHandler : InteractionListener {
onUnequip(intArrayOf(CastleWars.saradominFlag, CastleWars.zamorakFlag)) { player, _ ->
if (CastleWarsGameState.isCarryingFlag(player)) {
CastleWarsGameState.dropFlag(player)
return@onUnequip false
} else if (CastleWarsGameState.isCarryingOwnFlag(player)) {
CastleWarsGameState.dropOwnFlag(player)
return@onUnequip false
}
return@onUnequip false
return@onUnequip true
}
on(CastleWars.droppedFlagSceneryIds, IntType.SCENERY, "take") { player, node ->
if (!CastleWarsGameState.isGameActive) {
@ -158,4 +160,3 @@ class CastleWarsFlagHandler : InteractionListener {
}
}
}

View file

@ -13,6 +13,7 @@ import core.game.node.entity.player.Player
import core.game.node.entity.player.link.HintIconManager
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import core.game.node.scenery.Constructed
import core.game.node.scenery.Scenery
import core.game.node.scenery.SceneryBuilder
import core.game.world.map.Location
@ -165,6 +166,8 @@ object CastleWarsGameState {
private set
var zamorakCatapultOnFire: Boolean = false
private set
private var saradominCatapultFireGeneration: Int = 0
private var zamorakCatapultFireGeneration: Int = 0
// Boulder locations for lookup
val boulderLocations = mapOf(
@ -262,6 +265,8 @@ object CastleWarsGameState {
zamorakCatapultOperational = true
saradominCatapultOnFire = false
zamorakCatapultOnFire = false
invalidateCatapultFire(CastleWarsTeam.SARADOMIN)
invalidateCatapultFire(CastleWarsTeam.ZAMORAK)
for (key in boulderLocations.keys) {
updateBoulderScenery(key, BoulderState.COLLAPSED)
}
@ -331,28 +336,62 @@ object CastleWarsGameState {
2418..2432 to 3070..3085, // Saradomin castle area
2368..2382 to 3125..3140 // Zamorak castle area
)
val resetDoorOpenIds = CastleWars.largeDoorOpenIds + CastleWars.backdoorOpenIds
for ((xRange, yRange) in doorResetAreas) {
for (x in xRange) {
for (y in yRange) {
val loc = Location.create(x, y, 0)
val obj = RegionManager.getObject(loc)
if (obj != null && obj.id in CastleWars.largeDoorOpenIds) {
val closedId = when (obj.id) {
4425 -> 4423
4426 -> 4424
4429 -> 4427
4430 -> 4428
else -> null
}
if (closedId != null) {
SceneryBuilder.replace(obj, Scenery(closedId, loc, obj.type, obj.rotation))
}
if (obj != null && obj.id in resetDoorOpenIds) {
closeResetDoor(obj)
}
}
}
}
}
private fun closeResetDoor(obj: Scenery) {
if (obj is Constructed) {
val replaced = obj.replaced
if (replaced != null) {
SceneryBuilder.replace(obj, replaced)
return
}
}
val closedId = getClosedDoorId(obj.id) ?: getClosedBackdoorId(obj.id) ?: return
SceneryBuilder.replace(obj, Scenery(closedId, obj.location, obj.type, obj.rotation))
}
private fun getClosedDoorId(openDoorId: Int): Int? = when (openDoorId) {
4425 -> 4423
4426 -> 4424
4429 -> 4427
4430 -> 4428
else -> null
}
private fun getClosedBackdoorId(openDoorId: Int): Int? = when (openDoorId) {
CastleWars.saradominBackdoorOpen -> CastleWars.saradominBackdoor
CastleWars.zamorakBackdoorOpen -> CastleWars.zamorakBackdoor
else -> null
}
fun clearCastleWarsGroundItems() {
GroundItemManager.getItems().toList()
.filter { item ->
val location = item.location ?: return@filter false
item.id in CastleWars.CW_ITEMS_TO_REMOVE && isCastleWarsGroundCleanupLocation(location)
}
.forEach { GroundItemManager.destroy(it) }
}
private fun isCastleWarsGroundCleanupLocation(location: Location): Boolean {
return CastleWarsGameArea.areaBorders.any { it.insideBorder(location) } ||
CastleWarsWaitingArea.areaBorders.any { it.insideBorder(location) } ||
CastleWarsRespawnArea.saradominRespawnRoom.insideBorder(location) ||
CastleWarsRespawnArea.zamorakRespawnRoom.insideBorder(location)
}
/**
* Get the team a player is on.
* Includes a self-healing mechanism if the player is in the area but not in the sets.
@ -566,7 +605,7 @@ object CastleWarsGameState {
* Drop a flag (when a player dies or leaves) - creates scenery object
* @param player The player dropping the flag
*/
fun dropFlag(player: Player) {
fun dropFlag(player: Player, broadcast: Boolean = true) {
val carriedFlag = getCarriedFlag(player) ?: return
val dropLocation = Location.create(player.location)
val shouldReturnToStand = CastleWars.isSteppingStoneTile(dropLocation)
@ -593,8 +632,8 @@ object CastleWarsGameState {
}
removeAttribute(player, CastleWarsConstants.ATTR_FLAG_PICKUP_TICK)
if (shouldReturnToStand) {
returnFlagToStand(carriedFlag)
} else {
returnFlagToStand(carriedFlag, broadcast)
} else if (broadcast) {
announceToAllPlayers("${carriedFlag.teamName} flag has been dropped!")
updateAllOverlays()
updateAllHintIcons()
@ -605,7 +644,7 @@ object CastleWarsGameState {
* Drop own flag (when a player carrying their own team's flag dies or leaves)
* @param player The player dropping the flag
*/
fun dropOwnFlag(player: Player) {
fun dropOwnFlag(player: Player, broadcast: Boolean = true) {
val carriedFlag = getOwnCarriedFlag(player) ?: return
val dropLocation = Location.create(player.location)
val shouldReturnToStand = CastleWars.isSteppingStoneTile(dropLocation)
@ -631,8 +670,8 @@ object CastleWarsGameState {
player.inventory.remove(Item(carriedFlag.flagItemId))
}
if (shouldReturnToStand) {
returnFlagToStand(carriedFlag)
} else {
returnFlagToStand(carriedFlag, broadcast)
} else if (broadcast) {
announceToAllPlayers("${carriedFlag.teamName} flag has been dropped!")
updateAllOverlays()
updateAllHintIcons()
@ -859,7 +898,7 @@ object CastleWarsGameState {
* Return a dropped flag directly to its stand (instant, no player involved).
* Used for auto-return (timeout, stepping stone) scenarios.
*/
fun returnFlagToStand(flagTeam: CastleWarsTeam) {
fun returnFlagToStand(flagTeam: CastleWarsTeam, broadcast: Boolean = true) {
when (flagTeam) {
CastleWarsTeam.SARADOMIN -> {
saradominDroppedFlagScenery?.let { SceneryBuilder.remove(it) }
@ -879,9 +918,11 @@ object CastleWarsGameState {
updateFlagStandObject(CastleWarsTeam.ZAMORAK, FlagState.SAFE)
}
}
announceToAllPlayers("The ${flagTeam.teamName} flag has been returned to its stand!")
updateAllOverlays()
updateAllHintIcons()
if (broadcast) {
announceToAllPlayers("The ${flagTeam.teamName} flag has been returned to its stand!")
updateAllOverlays()
updateAllHintIcons()
}
}
/**
@ -1077,6 +1118,7 @@ object CastleWarsGameState {
player.inventory.removeAll(CastleWars.CW_ITEMS_TO_REMOVE)
player.equipment.removeAll(CastleWars.CW_ITEMS_TO_REMOVE)
(player.familiarManager.familiar as? BurdenBeast)?.container?.removeAll(CastleWars.CW_ITEMS_TO_REMOVE)
removeTimer(player, "teleblock")
clearActiveCastleWarsBracelet(player)
player.interaction.remove(Option._P_ATTACK)
player.interaction.remove(Option._P_STEAL_FROM)
@ -1085,27 +1127,34 @@ object CastleWarsGameState {
/**
* End the game and distribute rewards
*/
fun endGame() {
val allPlayers = getAllGamePlayers()
for (player in allPlayers) {
fun endGame(extraRewardRecipients: Map<Player, CastleWarsTeam> = emptyMap()) {
val rewardRecipients = linkedMapOf<Player, CastleWarsTeam>()
for (player in getAllGamePlayers()) {
val team = getPlayerTeam(player) ?: continue
rewardRecipients[player] = team
}
for ((player, team) in extraRewardRecipients) {
if (!rewardRecipients.containsKey(player)) {
rewardRecipients[player] = team
}
}
for ((player, team) in rewardRecipients) {
val tickets = when {
saradominScore > zamorakScore && team == CastleWarsTeam.SARADOMIN -> 2
zamorakScore > saradominScore && team == CastleWarsTeam.ZAMORAK -> 2
saradominScore == zamorakScore -> 1
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 ""}!")
}
if (isCarryingFlag(player)) {
dropFlag(player)
dropFlag(player, broadcast = false)
} else if (isCarryingOwnFlag(player)) {
dropOwnFlag(player)
dropOwnFlag(player, broadcast = false)
}
player.depleteCastleWarsBracelet()
restoreAndCleanupPlayer(player)
if (tickets > 0) {
awardCastleWarsTickets(player, tickets)
}
}
val resultMessage = when {
saradominScore > zamorakScore -> "Saradomin wins the game!"
@ -1116,6 +1165,17 @@ object CastleWarsGameState {
resetGame()
}
private fun awardCastleWarsTickets(player: Player, tickets: Int) {
val reward = Item(Items.CASTLE_WARS_TICKET_4067, tickets)
val ticketText = "Castle Wars ticket${if (tickets > 1) "s" else ""}"
if (player.inventory.add(reward)) {
sendMessage(player, "You have received $tickets $ticketText!")
return
}
GroundItemManager.create(reward, CastleWars.lobbyBankArea.randomWalkableLoc, player)
sendMessage(player, "Your $ticketText dropped to the ground because your inventory is full.")
}
/**
* Handle a player leaving the game
*/
@ -1389,11 +1449,13 @@ object CastleWarsGameState {
CastleWarsTeam.SARADOMIN -> {
saradominCatapultOperational = false
saradominCatapultOnFire = false
invalidateCatapultFire(team)
}
CastleWarsTeam.ZAMORAK -> {
zamorakCatapultOperational = false
zamorakCatapultOnFire = false
invalidateCatapultFire(team)
}
}
updateCatapultScenery(team)
@ -1404,13 +1466,21 @@ object CastleWarsGameState {
* Set a team's catapult on fire (degrades over time, not instantly destroyed).
* Tinderbox sets fire, catapult degrades until extinguished or destroyed.
*/
fun setCatapultOnFire(team: CastleWarsTeam) {
fun setCatapultOnFire(team: CastleWarsTeam): Int {
when (team) {
CastleWarsTeam.SARADOMIN -> saradominCatapultOnFire = true
CastleWarsTeam.ZAMORAK -> zamorakCatapultOnFire = true
CastleWarsTeam.SARADOMIN -> {
saradominCatapultOnFire = true
invalidateCatapultFire(team)
}
CastleWarsTeam.ZAMORAK -> {
zamorakCatapultOnFire = true
invalidateCatapultFire(team)
}
}
updateCatapultScenery(team)
updateAllOverlays()
return getCatapultFireGeneration(team)
}
/**
@ -1418,13 +1488,34 @@ object CastleWarsGameState {
*/
fun extinguishCatapult(team: CastleWarsTeam) {
when (team) {
CastleWarsTeam.SARADOMIN -> saradominCatapultOnFire = false
CastleWarsTeam.ZAMORAK -> zamorakCatapultOnFire = false
CastleWarsTeam.SARADOMIN -> {
saradominCatapultOnFire = false
invalidateCatapultFire(team)
}
CastleWarsTeam.ZAMORAK -> {
zamorakCatapultOnFire = false
invalidateCatapultFire(team)
}
}
updateCatapultScenery(team)
updateAllOverlays()
}
fun getCatapultFireGeneration(team: CastleWarsTeam): Int {
return when (team) {
CastleWarsTeam.SARADOMIN -> saradominCatapultFireGeneration
CastleWarsTeam.ZAMORAK -> zamorakCatapultFireGeneration
}
}
private fun invalidateCatapultFire(team: CastleWarsTeam) {
when (team) {
CastleWarsTeam.SARADOMIN -> saradominCatapultFireGeneration++
CastleWarsTeam.ZAMORAK -> zamorakCatapultFireGeneration++
}
}
/**
* Check if a team's catapult is on fire
*/

View file

@ -8,6 +8,7 @@ import content.minigame.castlewars.CastleWarsConstants.BURNING_BARRICADE_MAX_TIC
import content.minigame.castlewars.CastleWarsConstants.BURNING_BARRICADE_PULSE_INTERVAL
import content.minigame.castlewars.CastleWarsConstants.EXPLOSIVE_POTION_SELF_DAMAGE
import content.minigame.castlewars.CastleWarsConstants.MAX_BARRICADES_PER_TEAM
import content.minigame.castlewars.areas.CastleWarsGameArea
import core.api.*
import core.game.interaction.Clocks
import core.game.interaction.IntType
@ -87,20 +88,6 @@ class CastleWarsItemHandler : InteractionListener {
onUseWithPlayer(CastleWars.cwBandages) { player, used, with ->
val target = with as? Player ?: return@onUseWithPlayer false
val playerTeam = CastleWarsGameState.getPlayerTeam(player)
val targetTeam = CastleWarsGameState.getPlayerTeam(target)
if (playerTeam == null || targetTeam == null) {
sendMessage(player, "You can only use bandages in Castle Wars.")
return@onUseWithPlayer false
}
if (playerTeam != targetTeam) {
sendMessage(player, "You can only heal your teammates!")
return@onUseWithPlayer false
}
if (useBandage(player, target)) {
removeItem(player, used.asItem())
}
@ -231,6 +218,7 @@ class CastleWarsItemHandler : InteractionListener {
playAudio(player, 2599)
burningBarricadePulses.remove(barricade)?.stop()
barricade.transform(burningId)
sendMessage(player, "You set the barricade on fire!")
@ -272,6 +260,7 @@ class CastleWarsItemHandler : InteractionListener {
}
barricade.transform(originalId)
burningBarricadePulses.remove(barricade)?.stop()
replaceSlot(player, used.asItem().slot, Item(Items.BUCKET_1925))
@ -283,6 +272,21 @@ class CastleWarsItemHandler : InteractionListener {
}
private fun useBandage(user: Player, target: Player): Boolean {
if (!CastleWarsGameState.isGameActive) {
sendMessage(user, "You can only use bandages during a Castle Wars game.")
return false
}
val userTeam = getActiveGameTeam(user)
val targetTeam = getActiveGameTeam(target)
if (userTeam == null || targetTeam == null) {
sendMessage(user, "You can only use bandages during a Castle Wars game.")
return false
}
if (userTeam != targetTeam) {
sendMessage(user, "You can only heal your teammates!")
return false
}
if (CastleWarsGameState.isCarryingOwnFlag(target)) {
if (user == target) {
sendMessage(user, "You can't heal while carrying your team's flag!")
@ -324,6 +328,14 @@ class CastleWarsItemHandler : InteractionListener {
return true
}
private fun getActiveGameTeam(player: Player): CastleWarsTeam? {
return when {
CastleWarsGameArea.saradominPlayers.contains(player) -> CastleWarsTeam.SARADOMIN
CastleWarsGameArea.zamorakPlayers.contains(player) -> CastleWarsTeam.ZAMORAK
else -> null
}
}
private fun getLocalBarricades(location: Location, radius: Int): List<NPC> {
val barricadeIds = intArrayOf(
NPCs.BARRICADE_1532, NPCs.BARRICADE_1533, NPCs.BARRICADE_1534, NPCs.BARRICADE_1535

View file

@ -473,7 +473,7 @@ class CastleWarsListeners : InteractionListener {
}
val previousHealth = CastleWarsGameState.getDoorHealth(doorTeam)
CastleWarsGameState.damageDoor(doorTeam, 25)
CastleWarsGameState.damageDoor(doorTeam, CastleWarsConstants.EXPLOSIVE_POTION_DOOR_DAMAGE)
removeItem(player, used.asItem())
playAudio(player, 2599)
animate(player, 827)
@ -508,7 +508,7 @@ class CastleWarsListeners : InteractionListener {
sendMessage(player, "The door is already fully repaired.")
return@onUseWith false
}
CastleWarsGameState.repairDoor(doorTeam, 10)
CastleWarsGameState.repairDoor(doorTeam, CastleWarsConstants.TOOLKIT_DOOR_REPAIR)
animate(player, 898)
sendMessage(player, "You repair some of the door's damage.")
return@onUseWith true
@ -734,16 +734,15 @@ class CastleWarsListeners : InteractionListener {
sendMessage(player, "The catapult is already on fire!")
return@onUseWith false
}
CastleWarsGameState.setCatapultOnFire(catapultTeam)
val fireGeneration = CastleWarsGameState.setCatapultOnFire(catapultTeam)
playAudio(player, 2599)
sendMessage(player, "You set the catapult on fire!")
GameWorld.Pulser.submit(object : Pulse(50) {
override fun pulse(): Boolean {
if (CastleWarsGameState.isCatapultOnFire(catapultTeam) && CastleWarsGameState.isCatapultOperational(
if (CastleWarsGameState.getCatapultFireGeneration(catapultTeam) == fireGeneration && CastleWarsGameState.isCatapultOnFire(catapultTeam) && CastleWarsGameState.isCatapultOperational(
catapultTeam
)
) {
CastleWarsGameState.extinguishCatapult(catapultTeam)
CastleWarsGameState.destroyCatapult(catapultTeam)
}
return true

View file

@ -11,6 +11,7 @@ import core.game.node.Node
import core.game.node.entity.Entity
import core.game.node.entity.combat.CombatStyle
import core.game.node.entity.player.Player
import core.game.node.entity.player.info.login.PlayerParser
import core.game.node.item.Item
import core.game.world.map.Location
import core.game.world.map.zone.ZoneBorders
@ -73,7 +74,9 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
var ticksLeftInGame = -1
private val activeVacancyOffers = mutableMapOf<CastleWarsTeam, MutableSet<Player>>()
private val activeVacancyOfferTicks = mutableMapOf<CastleWarsTeam, Int>()
private val declinedSubstitutionOffers = mutableMapOf<CastleWarsTeam, MutableSet<Player>>()
private val openVacancies = mutableMapOf<CastleWarsTeam, Int>()
fun startGame() {
if (!CastleWarsWaitingArea.hasMinimumPlayers()) {
@ -118,7 +121,9 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
fun clearSubstitutionState() {
activeVacancyOffers.clear()
activeVacancyOfferTicks.clear()
declinedSubstitutionOffers.clear()
openVacancies.clear()
}
fun selectGuthixTeam(): CastleWarsTeam {
@ -139,15 +144,18 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
*/
fun fillVacancy(team: CastleWarsTeam) {
if (ticksLeftInGame <= 0) return
if (activeVacancyOffers[team]?.isNotEmpty() == true) return
if ((openVacancies[team] ?: 0) <= 0) return
val waitingSet = when (team) {
CastleWarsTeam.SARADOMIN -> CastleWarsWaitingArea.waitingSaradominPlayers
CastleWarsTeam.ZAMORAK -> CastleWarsWaitingArea.waitingZamorakPlayers
}
pruneVacancyOffer(team, waitingSet)
if (activeVacancyOffers[team]?.isNotEmpty() == true) return
val declined = declinedSubstitutionOffers[team].orEmpty()
val candidates = waitingSet.filter { it !in declined }
if (candidates.isEmpty()) return
activeVacancyOffers[team] = candidates.toMutableSet()
activeVacancyOfferTicks[team] = getWorldTicks()
for (candidate in candidates) {
openDialogue(candidate, CastleWarsSubstitutionDialogue(team))
}
@ -160,14 +168,30 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
CastleWarsTeam.SARADOMIN -> CastleWarsWaitingArea.waitingSaradominPlayers
CastleWarsTeam.ZAMORAK -> CastleWarsWaitingArea.waitingZamorakPlayers
}
if (!waitingSet.remove(player)) return false
if (!waitingSet.remove(player)) {
offer.remove(player)
if (offer.isEmpty()) {
activeVacancyOffers.remove(team)
activeVacancyOfferTicks.remove(team)
}
return false
}
activeVacancyOffers.remove(team)
activeVacancyOfferTicks.remove(team)
declinedSubstitutionOffers[team]?.remove(player)
val remainingVacancies = ((openVacancies[team] ?: 0) - 1).coerceAtLeast(0)
if (remainingVacancies > 0) {
openVacancies[team] = remainingVacancies
} else {
openVacancies.remove(team)
}
val gameSet = when (team) {
CastleWarsTeam.SARADOMIN -> saradominPlayers
CastleWarsTeam.ZAMORAK -> zamorakPlayers
}
gameSet.add(player)
CastleWarsGameState.activateBraceletIfEquipped(player, team)
CastleWarsGameState.setActiveGamePlayerOptions(player)
setAttribute(player, CastleWarsConstants.ATTR_JOIN_TICK, getWorldTicks())
val respawnLoc = when (team) {
CastleWarsTeam.SARADOMIN -> CastleWarsRespawnArea.saradominRespawnRoom.randomWalkableLoc
@ -175,6 +199,9 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
}
player.properties.teleportLocation = respawnLoc
sendMessage(player, "A spot has opened up! You have been moved into the Castle Wars game.")
if ((openVacancies[team] ?: 0) > 0) {
fillVacancy(team)
}
return true
}
@ -183,6 +210,42 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
declinedSubstitutionOffers.getOrPut(team) { mutableSetOf() }.add(player)
if (activeVacancyOffers[team]?.isEmpty() == true) {
activeVacancyOffers.remove(team)
activeVacancyOfferTicks.remove(team)
fillVacancy(team)
}
}
fun removeVacancyOffer(player: Player) {
val teamsToRefill = mutableSetOf<CastleWarsTeam>()
for (team in CastleWarsTeam.values()) {
activeVacancyOffers[team]?.remove(player)
declinedSubstitutionOffers[team]?.remove(player)
if (activeVacancyOffers[team]?.isEmpty() == true) {
activeVacancyOffers.remove(team)
activeVacancyOfferTicks.remove(team)
teamsToRefill.add(team)
}
if (declinedSubstitutionOffers[team]?.isEmpty() == true) {
declinedSubstitutionOffers.remove(team)
}
}
for (team in teamsToRefill) {
fillVacancy(team)
}
}
private fun pruneVacancyOffer(team: CastleWarsTeam, waitingSet: Set<Player>) {
val offer = activeVacancyOffers[team] ?: return
val createdTick = activeVacancyOfferTicks[team] ?: getWorldTicks()
if (getWorldTicks() - createdTick >= CastleWarsConstants.VACANCY_OFFER_TIMEOUT_TICKS) {
activeVacancyOffers.remove(team)
activeVacancyOfferTicks.remove(team)
return
}
offer.removeAll { it !in waitingSet || !it.isActive }
if (offer.isEmpty()) {
activeVacancyOffers.remove(team)
activeVacancyOfferTicks.remove(team)
}
}
@ -204,11 +267,16 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
player.interaction.remove(Option._P_ATTACK)
player.interaction.remove(Option._P_STEAL_FROM)
if (offerReplacement) {
openVacancy(leavingTeam)
fillVacancy(leavingTeam)
}
return leavingTeam
}
private fun openVacancy(team: CastleWarsTeam) {
openVacancies[team] = (openVacancies[team] ?: 0) + 1
}
fun playerKey(player: Player): String {
return player.username.lowercase()
}
@ -272,6 +340,18 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
clearLogoutAttributes(player)
}
private fun moveLoggedOutPlayerToLobby(player: Player) {
removeFromTeam(player)
CastleWarsGameState.clearHintIconForPlayer(player)
CastleWarsGameState.restoreAndCleanupPlayer(player)
player.interaction.remove(Option._P_ATTACK)
player.interaction.remove(Option._P_STEAL_FROM)
val lobbyLocation = CastleWars.lobbyBankArea.randomWalkableLoc
player.location = lobbyLocation
player.properties.teleportLocation = lobbyLocation
clearLogoutState(player)
}
fun restoreActiveLogoutOnLogin(player: Player) {
val savedTeamName = getAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TEAM, "")
val savedLogoutTime = getAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TIME_MILLIS, 0L)
@ -287,6 +367,10 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
val logoutLocation = getAttribute(
player, CastleWarsConstants.ATTR_LOGOUT_LOCATION, record?.logoutLocation ?: player.location
)
if (ticksLeftInGame <= 0) {
moveLoggedOutPlayerToLobby(player)
return
}
if (ticksLeftInGame > 0 && elapsedMillis < CastleWarsConstants.LOGOUT_GRACE_PERIOD_MILLIS) {
player.location = Location.create(logoutLocation)
setTeam(player, team)
@ -295,6 +379,7 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
}
moveLoggedOutPlayerToWaitingRoom(player, team)
if (ticksLeftInGame > 0) {
openVacancy(team)
fillVacancy(team)
}
}
@ -324,24 +409,41 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
}
}
}
}
private fun endGame() { // TODO: ropes, rocks in tunnels, ground items, castle doors, catapults
CastleWarsGameState.endGame()
CastleWarsItemHandler.clearAllBarricades()
clearSubstitutionState()
ticksLeftInGame = -1
saradominPlayers.forEach { player ->
player.unhook(SaradominInterfaceCloseHook)
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
fun endCurrentGame(startCooldown: Boolean = true) {
val saraSnapshot = saradominPlayers.toSet()
val zamSnapshot = zamorakPlayers.toSet()
val loggedOutSnapshot = loggedOutPlayers.values.toList()
val loggedOutRewardRecipients = loggedOutSnapshot.associate { it.player to it.team }
CastleWarsGameState.endGame(loggedOutRewardRecipients)
for (record in loggedOutSnapshot) {
moveLoggedOutPlayerToLobby(record.player)
if (!record.player.isActive || record.player.session == null) {
PlayerParser.saveImmediately(record.player)
}
}
CastleWarsItemHandler.clearAllBarricades()
CastleWarsGameState.clearCastleWarsGroundItems()
clearSubstitutionState()
ticksLeftInGame = -1
CastleWarsWaitingArea.gameStartCountdown = if (startCooldown) {
CastleWars.gameCooldownMinutes * CastleWarsConstants.TICKS_PER_MINUTE
} else {
-1
}
saraSnapshot.forEach { player ->
player.unhook(SaradominInterfaceCloseHook)
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
zamSnapshot.forEach { player ->
player.unhook(ZamorakInterfaceCloseHook)
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
saradominPlayers.clear()
zamorakPlayers.clear()
CastleWarsRespawnArea.saradominPlayersInRespawnRoom.clear()
CastleWarsRespawnArea.zamorakPlayersInRespawnRoom.clear()
}
zamorakPlayers.forEach { player ->
player.unhook(ZamorakInterfaceCloseHook)
player.properties.teleportLocation = CastleWars.lobbyBankArea.randomWalkableLoc
}
saradominPlayers.clear()
zamorakPlayers.clear()
loggedOutPlayers.clear()
}
override fun defineAreaBorders(): Array<ZoneBorders> {
@ -353,7 +455,10 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
super.areaEnter(player)
zone.zoneType = ZoneType.CASTLE_WARS.id
registerTimer(player, spawnTimer("teleblock", (CastleWars.gameTimeMinutes) * 60 * 2))
registerTimer(
player,
spawnTimer("teleblock", CastleWars.gameTimeMinutes * CastleWarsConstants.TICKS_PER_MINUTE, false)
)
val team = CastleWarsGameState.getPlayerTeam(player)
when (team) {
CastleWarsTeam.SARADOMIN -> {
@ -425,7 +530,7 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
return
}
if (ticksLeftInGame == 0) {
endGame()
endCurrentGame()
return
}
val previousMinutes =
@ -434,7 +539,7 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
val currentMinutes =
(ticksLeftInGame + CastleWarsConstants.TICKS_PER_MINUTE - 1) / CastleWarsConstants.TICKS_PER_MINUTE
if (ticksLeftInGame == 0) {
endGame()
endCurrentGame()
return
} else if (currentMinutes != previousMinutes) {
CastleWarsGameState.updateAllOverlays()
@ -474,9 +579,11 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
for ((key, record) in expiredLogouts) {
val player = record.player
loggedOutPlayers.remove(key)
clearLogoutAttributes(player)
removeFromTeam(player)
CastleWarsGameState.clearHintIconForPlayer(player)
CastleWarsGameState.restoreAndCleanupPlayer(player)
openVacancy(record.team)
fillVacancy(record.team)
}
// Remove truly inactive players (not in grace period)
@ -489,11 +596,13 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener {
for (player in inactiveSara) {
saradominPlayers.remove(player)
CastleWarsGameState.clearHintIconForPlayer(player)
openVacancy(CastleWarsTeam.SARADOMIN)
fillVacancy(CastleWarsTeam.SARADOMIN)
}
for (player in inactiveZam) {
zamorakPlayers.remove(player)
CastleWarsGameState.clearHintIconForPlayer(player)
openVacancy(CastleWarsTeam.ZAMORAK)
fillVacancy(CastleWarsTeam.ZAMORAK)
}
// Also cleanup hint icons for any other inactive players

View file

@ -63,6 +63,7 @@ class CastleWarsRespawnArea : CastleWarsArea(), TickListener {
}
private fun removePlayersOverSpawnLimit(players: MutableMap<Player, Int>) {
players.entries.removeIf { (player, _) -> !player.isActive || player.session == null }
val currentTick = getWorldTicks()
val expiredPlayers = players.filter { (player, entryTick) ->
player.isActive && player.session != null && currentTick - entryTick >= CastleWarsConstants.LOGOUT_GRACE_PERIOD_TICKS
@ -79,6 +80,8 @@ class CastleWarsRespawnArea : CastleWarsArea(), TickListener {
if (!defineAreaBorders().any { it.insideBorder(player.location) }) {
return
}
saradominPlayersInRespawnRoom.remove(player)
zamorakPlayersInRespawnRoom.remove(player)
if (CastleWarsGameArea.ticksLeftInGame > 0 && CastleWarsGameState.getPlayerTeam(player) != null) {
CastleWarsGameArea().logout(player)
return

View file

@ -3,6 +3,7 @@ package content.minigame.castlewars.areas
import content.minigame.castlewars.CastleWars
import content.minigame.castlewars.CastleWarsConstants
import content.minigame.castlewars.CastleWarsOverlay
import content.minigame.castlewars.CastleWarsTeam
import core.api.*
import core.game.component.Component
import core.game.node.entity.Entity
@ -39,16 +40,28 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
val player = entity as? Player ?: return
super.areaEnter(player)
registerTimer(
player, spawnTimer("teleblock", (CastleWars.gameCooldownMinutes + CastleWars.gameTimeMinutes) * 60 * 2)
player,
spawnTimer(
"teleblock",
(CastleWars.gameCooldownMinutes + CastleWars.gameTimeMinutes) * CastleWarsConstants.TICKS_PER_MINUTE,
false
)
)
// Set team attribute and equip the hooded cloak on the entity based on which waiting room they're in
if (zamorakWaitingRoom.insideBorder(player.location)) {
val joinedTeam = if (zamorakWaitingRoom.insideBorder(player.location)) {
player.equipment.replace(Item(CastleWars.zamorakTeamHoodedCloak), 1)
waitingZamorakPlayers.add(player)
CastleWarsTeam.ZAMORAK
} else if (saradominWaitingRoom.insideBorder(player.location)) {
player.equipment.replace(Item(CastleWars.saradominTeamHoodedCloak), 1)
waitingSaradominPlayers.add(player)
CastleWarsTeam.SARADOMIN
} else {
null
}
if (joinedTeam != null && CastleWarsGameArea.ticksLeftInGame > 0) {
CastleWarsGameArea.fillVacancy(joinedTeam)
}
var transformed = false
@ -118,6 +131,7 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener {
// Remove player from waiting room set (whichever one that is)
waitingSaradominPlayers.remove(player)
waitingZamorakPlayers.remove(player)
CastleWarsGameArea.removeVacancyOffer(player)
}
override fun tick() {

View file

@ -2,6 +2,7 @@ package core.game.container.impl;
import content.global.skill.skillcapeperks.SkillcapeEquipmentPlugin;
import content.global.skill.skillcapeperks.SkillcapePerks;
import content.minigame.castlewars.CastleWars;
import core.game.container.Container;
import core.game.container.ContainerEvent;
import core.game.container.ContainerListener;
@ -146,9 +147,17 @@ public final class EquipmentContainer extends Container {
}
boolean listenersSayWeCanUnequip = runUnequipHooks(current, newItem);
if(!listenersSayWeCanUnequip) {
return false;
}
ArrayList<Item> itemsToMoveToInventory = new ArrayList<>(current);
itemsToMoveToInventory.removeIf(item -> isCastleWarsFlag(item) && !containsItem(item));
if(itemsToMoveToInventory.isEmpty()) {
return true;
}
boolean allRemoved = true;
for(Item item : current) {
for(Item item : itemsToMoveToInventory) {
if(!remove(item)) {
allRemoved = false;
break;
@ -157,7 +166,7 @@ public final class EquipmentContainer extends Container {
boolean allAdded = allRemoved;
if(allRemoved) {
for (Item item : current) {
for (Item item : itemsToMoveToInventory) {
if (!player.getInventory().add(item, true, preferredSlot)) {
allAdded = false;
break;
@ -168,7 +177,7 @@ public final class EquipmentContainer extends Container {
if (listenersSayWeCanUnequip && allRemoved && allAdded) return true;
else {
//put things back if we couldn't remove everything
for(Item item : current) {
for(Item item : itemsToMoveToInventory) {
if(!containsItem(item)) {
add(item);
}
@ -182,6 +191,9 @@ public final class EquipmentContainer extends Container {
int neededSlots = 0;
for(Item item : current) {
if(isCastleWarsFlag(item)) {
continue;
}
if(!item.getDefinition().isStackable()) {
neededSlots++;
} else {
@ -193,6 +205,10 @@ public final class EquipmentContainer extends Container {
return neededSlots;
}
private boolean isCastleWarsFlag(Item item) {
return item.getId() == CastleWars.saradominFlag || item.getId() == CastleWars.zamorakFlag;
}
@Nullable
private Item getSecondaryEquipIfApplicable(Item newItem, int equipmentSlot) {
Item secondaryEquipItem = null;
@ -211,19 +227,28 @@ public final class EquipmentContainer extends Container {
for(Item currentItem : currentItems) {
Plugin<Object> plugin = currentItem.getDefinition().getConfiguration("equipment", null);
if (plugin != null) {
Object object = plugin.fireEvent("unequip", player, currentItem);
if (object != null && !((Boolean) object)) {
canContinue = false;
if (plugin != null) {
Object object = plugin.fireEvent("unequip", player, currentItem);
if (object != null && !((Boolean) object)) {
if (isCastleWarsFlag(currentItem) && !containsItem(currentItem)) {
continue;
}
canContinue = false;
break;
}
}
canContinue = InteractionListeners.run(currentItem.getId(), player, currentItem, false);
if(!canContinue) {
if (isCastleWarsFlag(currentItem) && !containsItem(currentItem)) {
canContinue = true;
continue;
}
break;
}
}
canContinue = InteractionListeners.run(currentItem.getId(), player, currentItem, false);
if(!canContinue) break;
}
return canContinue;
}

View file

@ -1,5 +1,6 @@
package core.game.global.action
import content.minigame.castlewars.CastleWars
import content.minigame.castlewars.CastleWarsEquipmentRestrictions
import core.game.event.ItemEquipEvent
import core.game.event.ItemUnequipEvent
@ -47,7 +48,11 @@ class EquipHandler : InteractionListener {
if(currentEquippedItem != null){
if(!InteractionListeners.run(currentEquippedItem.id, player, currentEquippedItem, false)){
return
val droppedCastleWarsFlag =
isCastleWarsFlag(currentEquippedItem.id) && player.equipment[itemEquipmentSlot] == null
if (!droppedCastleWarsFlag) {
return
}
}
}
@ -123,10 +128,12 @@ class EquipHandler : InteractionListener {
if (slot == EquipmentContainer.SLOT_WEAPON) {
player.packetDispatch.sendString("", 92, 0)
}
val maximumAdd = player.inventory.getMaximumAdd(item)
if (maximumAdd < item.amount) {
player.packetDispatch.sendMessage("Not enough free space in your inventory.")
return
if (!isCastleWarsFlag(item.id)) {
val maximumAdd = player.inventory.getMaximumAdd(item)
if (maximumAdd < item.amount) {
player.packetDispatch.sendMessage("Not enough free space in your inventory.")
return
}
}
val plugin = item.definition.getConfiguration<Plugin<Any>>("equipment", null)
if (plugin != null) {
@ -137,6 +144,11 @@ class EquipHandler : InteractionListener {
if (!InteractionListeners.run(itemId, player, item, false)) {
return
}
val maximumAdd = player.inventory.getMaximumAdd(item)
if (maximumAdd < item.amount) {
player.packetDispatch.sendMessage("Not enough free space in your inventory.")
return
}
if (player.equipment.remove(item)) {
/* TODO: Send different unequip sound based on what is being unequipped.*/
playAudio(player, Sounds.EQUIP_FUN_2238)
@ -150,5 +162,9 @@ class EquipHandler : InteractionListener {
SlayerEquipmentFlags.updateFlags(player)
}
}
private fun isCastleWarsFlag(itemId: Int): Boolean {
return itemId == CastleWars.saradominFlag || itemId == CastleWars.zamorakFlag
}
}
}

View file

@ -7,18 +7,20 @@ import core.game.system.timer.PersistTimer
import core.game.system.timer.RSTimer
import core.game.system.timer.TimerFlag
class Teleblock : PersistTimer (1, "teleblock", flags = arrayOf(TimerFlag.ClearOnDeath)) {
class Teleblock(private val sendChatboxMessage: Boolean = true) :
PersistTimer(1, "teleblock", flags = arrayOf(TimerFlag.ClearOnDeath)) {
override fun run (entity: Entity) : Boolean {
return false
}
override fun onRegister (entity: Entity) {
if (entity !is Player) return
if (!sendChatboxMessage) return
sendMessage (entity, "You have been teleblocked.")
}
override fun getTimer (vararg args: Any) : RSTimer {
val t = Teleblock()
val t = Teleblock(args.getOrNull(1) as? Boolean ?: true)
t.runInterval = args.getOrNull(0) as? Int ?: 100
return t
}

View file

@ -17,7 +17,12 @@ import core.game.node.entity.player.info.Rights
class EquipTests {
companion object {
init {TestUtils.preTestSetup(); EquipHandler().defineListeners()}
init {
TestUtils.preTestSetup()
if (InteractionListeners.get("equip", IntType.ITEM.ordinal) == null) {
EquipHandler().defineListeners()
}
}
}
@Test fun equipShouldFireEquipListeners() {
@ -52,6 +57,26 @@ class EquipTests {
Assertions.assertEquals(true, didRun)
}
@Test fun fullInventoryUnequipShouldNotFireUnequipListeners() {
var didRun = false
val listener = object : InteractionListener {
override fun defineListeners() {
onUnequip(Items.ABYSSAL_WHIP_4151) { _, _ -> didRun = true; return@onUnequip true }
}
}
listener.defineListeners()
TestUtils.getMockPlayer("fullunequip").use { player ->
player.equipment.replace(Item(Items.ABYSSAL_WHIP_4151), EquipmentSlot.WEAPON.ordinal)
player.inventory.add(Item(Items.CUP_OF_TEA_1978, player.inventory.capacity()))
EquipHandler.unequip(player, EquipmentSlot.WEAPON.ordinal, Items.ABYSSAL_WHIP_4151)
Assertions.assertEquals(false, didRun)
Assertions.assertEquals(Items.ABYSSAL_WHIP_4151, player.equipment[EquipmentSlot.WEAPON.ordinal].id)
}
}
@Test fun equippingItemThatReplacesAnotherItemShouldCallUnequipListenersForTheReplacedItem() {
var didRun = false
val listener = object : InteractionListener {

View file

@ -1,6 +1,7 @@
package content.minigame.castlewars
import TestUtils
import MockSession
import content.minigame.castlewars.areas.CastleWarsGameArea
import content.minigame.castlewars.areas.CastleWarsRespawnArea
import content.minigame.castlewars.areas.CastleWarsWaitingArea
@ -8,12 +9,18 @@ import core.api.*
import core.game.component.Component
import core.game.global.action.EquipHandler
import core.game.interaction.*
import core.game.node.Node
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import core.game.node.scenery.Scenery
import core.game.node.scenery.SceneryBuilder
import core.game.system.timer.TimerRegistry
import core.game.system.timer.impl.Teleblock
import core.game.world.GameWorld
import core.game.world.map.Location
import core.game.world.map.RegionManager
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
@ -30,6 +37,7 @@ class CastleWarsTests {
if (InteractionListeners.get(CastleWars.saradominBackdoorOpen, IntType.SCENERY.ordinal, "lock") == null) {
CastleWarsListeners().defineDestinationOverrides()
CastleWarsListeners().defineListeners()
CastleWarsFlagHandler().defineListeners()
}
if (InteractionListeners.get(CastleWars.cwExplosivePotion, IntType.ITEM.ordinal, "drop") == null) {
CastleWarsItemHandler().defineListeners()
@ -37,6 +45,9 @@ class CastleWarsTests {
if (InterfaceListeners.get(CatapultConfig.INTERFACE_ID) == null) {
CastleWarsCatapultInterface().defineInterfaceListeners()
}
if (!TimerRegistry.timerMap.containsKey("teleblock")) {
TimerRegistry.registerTimer(Teleblock())
}
}
}
@ -52,6 +63,7 @@ class CastleWarsTests {
CastleWarsGameArea.clearSubstitutionState()
CastleWarsRespawnArea.saradominPlayersInRespawnRoom.clear()
CastleWarsRespawnArea.zamorakPlayersInRespawnRoom.clear()
CastleWarsItemHandler.clearAllBarricades()
CastleWarsGameState.resetGame()
}
@ -97,18 +109,125 @@ class CastleWarsTests {
}
}
@Test
fun enteringWaitingRoomAfterVacancyReceivesSubstitutionOffer() {
TestUtils.getMockPlayer("cwlateleaver").use { leaver ->
TestUtils.getMockPlayer("cwlatesub").use { waiting ->
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.saradominPlayers.add(leaver)
CastleWarsGameArea.removePlayerFromGame(leaver)
waiting.location = CastleWarsWaitingArea.saradominWaitingRoom.randomWalkableLoc
CastleWarsWaitingArea().areaEnter(waiting)
assertSubstitutionDialogue(waiting)
}
}
}
@Test
fun decliningSubstitutionKeepsPlayerInWaitingRoom() {
TestUtils.getMockPlayer("cwdecline").use { waiting ->
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsWaitingArea.waitingZamorakPlayers.add(waiting)
TestUtils.getMockPlayer("cwdeclineleave").use { leaver ->
TestUtils.getMockPlayer("cwdecline").use { waiting ->
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.zamorakPlayers.add(leaver)
CastleWarsWaitingArea.waitingZamorakPlayers.add(waiting)
CastleWarsGameArea.fillVacancy(CastleWarsTeam.ZAMORAK)
CastleWarsGameArea.declineVacancyOffer(waiting, CastleWarsTeam.ZAMORAK)
CastleWarsGameArea.removePlayerFromGame(leaver)
CastleWarsGameArea.declineVacancyOffer(waiting, CastleWarsTeam.ZAMORAK)
assertTrue(CastleWarsWaitingArea.waitingZamorakPlayers.contains(waiting))
assertFalse(CastleWarsGameArea.zamorakPlayers.contains(waiting))
assertFalse(CastleWarsGameArea.claimVacancy(waiting, CastleWarsTeam.ZAMORAK))
assertTrue(CastleWarsWaitingArea.waitingZamorakPlayers.contains(waiting))
assertFalse(CastleWarsGameArea.zamorakPlayers.contains(waiting))
assertFalse(CastleWarsGameArea.claimVacancy(waiting, CastleWarsTeam.ZAMORAK))
}
}
}
@Test
fun decliningLastSubstitutionOfferReissuesToAvailableWaitingPlayer() {
TestUtils.getMockPlayer("cwdeclinereissueleave").use { leaver ->
TestUtils.getMockPlayer("cwdeclinereissueold").use { declined ->
TestUtils.getMockPlayer("cwdeclinereissuenew").use { replacement ->
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.zamorakPlayers.add(leaver)
CastleWarsWaitingArea.waitingZamorakPlayers.add(declined)
CastleWarsGameArea.removePlayerFromGame(leaver)
assertSubstitutionDialogue(declined)
CastleWarsWaitingArea.waitingZamorakPlayers.add(replacement)
CastleWarsGameArea.declineVacancyOffer(declined, CastleWarsTeam.ZAMORAK)
assertSubstitutionDialogue(replacement)
assertFalse(CastleWarsGameArea.claimVacancy(declined, CastleWarsTeam.ZAMORAK))
assertTrue(CastleWarsGameArea.claimVacancy(replacement, CastleWarsTeam.ZAMORAK))
}
}
}
}
@Test
fun leavingWaitingRoomClearsStaleSubstitutionOffer() {
TestUtils.getMockPlayer("cwstaleleaver").use { leaver ->
TestUtils.getMockPlayer("cwstalesub").use { stale ->
TestUtils.getMockPlayer("cwfreshsub").use { fresh ->
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.saradominPlayers.add(leaver)
stale.location = CastleWarsWaitingArea.saradominWaitingRoom.randomWalkableLoc
CastleWarsWaitingArea.waitingSaradominPlayers.add(stale)
CastleWarsGameArea.removePlayerFromGame(leaver)
CastleWarsWaitingArea().exitArea(stale)
CastleWarsWaitingArea.waitingSaradominPlayers.add(fresh)
CastleWarsGameArea.fillVacancy(CastleWarsTeam.SARADOMIN)
assertSubstitutionDialogue(fresh)
assertTrue(CastleWarsGameArea.claimVacancy(fresh, CastleWarsTeam.SARADOMIN))
}
}
}
}
@Test
fun leavingLastSubstitutionOfferReissuesToAvailableWaitingPlayer() {
TestUtils.getMockPlayer("cwleavereissueleaver").use { leaver ->
TestUtils.getMockPlayer("cwleavereissueold").use { stale ->
TestUtils.getMockPlayer("cwleavereissuenew").use { replacement ->
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.saradominPlayers.add(leaver)
stale.location = CastleWarsWaitingArea.saradominWaitingRoom.randomWalkableLoc
CastleWarsWaitingArea.waitingSaradominPlayers.add(stale)
CastleWarsGameArea.removePlayerFromGame(leaver)
assertSubstitutionDialogue(stale)
CastleWarsWaitingArea.waitingSaradominPlayers.add(replacement)
CastleWarsWaitingArea().exitArea(stale)
assertSubstitutionDialogue(replacement)
assertTrue(CastleWarsGameArea.claimVacancy(replacement, CastleWarsTeam.SARADOMIN))
}
}
}
}
@Test
fun expiredSubstitutionOfferDoesNotBlockLaterVacancy() {
TestUtils.getMockPlayer("cwexpireleaver").use { leaver ->
TestUtils.getMockPlayer("cwignoredsub").use { ignored ->
TestUtils.getMockPlayer("cwlatersub").use { later ->
CastleWarsGameArea.ticksLeftInGame = 200
CastleWarsGameArea.zamorakPlayers.add(leaver)
CastleWarsWaitingArea.waitingZamorakPlayers.add(ignored)
CastleWarsGameArea.removePlayerFromGame(leaver)
TestUtils.advanceTicks(CastleWarsConstants.VACANCY_OFFER_TIMEOUT_TICKS, false)
CastleWarsWaitingArea.waitingZamorakPlayers.add(later)
CastleWarsGameArea.fillVacancy(CastleWarsTeam.ZAMORAK)
assertSubstitutionDialogue(later)
}
}
}
}
@ -201,13 +320,16 @@ class CastleWarsTests {
}
@Test
fun matchEndReturnsLifecycleToIdle() {
fun matchEndStartsPostGameCooldown() {
CastleWarsGameArea.ticksLeftInGame = 1
CastleWarsGameArea().tick()
assertEquals(-1, CastleWarsGameArea.ticksLeftInGame)
assertEquals(-1, CastleWarsWaitingArea.gameStartCountdown)
assertEquals(
CastleWars.gameCooldownMinutes * CastleWarsConstants.TICKS_PER_MINUTE,
CastleWarsWaitingArea.gameStartCountdown
)
}
@Test
@ -256,6 +378,91 @@ class CastleWarsTests {
}
}
@Test
fun endedGameClearsLoggedOutPlayerState() {
TestUtils.getMockPlayer("cwendedlogout").use { player ->
player.location = Location.create(2401, 3101, 0)
CastleWarsGameArea.ticksLeftInGame = 500
CastleWarsGameArea.saradominPlayers.add(player)
player.inventory.add(Item(CastleWars.cwBandages))
registerTimer(player, spawnTimer("teleblock", 100, false))
CastleWarsGameArea().logout(player)
assertEquals(CastleWarsTeam.SARADOMIN.name, getAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TEAM, ""))
CastleWarsGameArea.endCurrentGame(startCooldown = false)
assertFalse(CastleWarsGameArea.loggedOutPlayers.containsKey(CastleWarsGameArea.playerKey(player)))
assertEquals("", getAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TEAM, ""))
assertFalse(player.inventory.contains(CastleWars.cwBandages, 1))
assertFalse(hasTimerActive(player, "teleblock"))
assertTrue(CastleWars.lobbyBankArea.insideBorder(player.location))
}
}
@Test
fun expiredLogoutCleanupClearsSavedStateBeforePlayerRelogs() {
TestUtils.getMockPlayer("cwexpiredlogout").use { player ->
TestUtils.getMockPlayer("cwexpiredfirstsub").use { firstReplacement ->
TestUtils.getMockPlayer("cwexpiredsecondsub").use { secondReplacement ->
val logoutLocation = Location.create(2400, 3100, 0)
player.location = logoutLocation
CastleWarsGameArea.ticksLeftInGame = CastleWarsConstants.PLAYER_CLEANUP_INTERVAL_TICKS + 1
CastleWarsGameArea.saradominPlayers.add(player)
CastleWarsGameArea.loggedOutPlayers[CastleWarsGameArea.playerKey(player)] =
CastleWarsGameArea.Companion.LogoutRecord(
player,
CastleWarsTeam.SARADOMIN,
getWorldTicks() - CastleWarsConstants.LOGOUT_GRACE_PERIOD_TICKS,
logoutLocation
)
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TEAM, CastleWarsTeam.SARADOMIN.name)
setAttribute(
player,
CastleWarsConstants.ATTR_LOGOUT_TIME_MILLIS,
System.currentTimeMillis() - CastleWarsConstants.LOGOUT_GRACE_PERIOD_MILLIS - 1
)
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_LOCATION, Location.create(logoutLocation))
CastleWarsWaitingArea.waitingSaradominPlayers.add(firstReplacement)
CastleWarsGameArea().tick()
assertEquals("", getAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TEAM, ""))
assertFalse(CastleWarsGameArea.loggedOutPlayers.containsKey(CastleWarsGameArea.playerKey(player)))
assertSubstitutionDialogue(firstReplacement)
assertTrue(CastleWarsGameArea.claimVacancy(firstReplacement, CastleWarsTeam.SARADOMIN))
CastleWarsWaitingArea.waitingSaradominPlayers.add(secondReplacement)
CastleWarsGameArea().login(player)
assertFalse(secondReplacement.dialogueInterpreter.dialogue?.file is CastleWarsSubstitutionDialogue)
assertFalse(CastleWarsGameArea.claimVacancy(secondReplacement, CastleWarsTeam.SARADOMIN))
}
}
}
}
@Test
fun staleLogoutAttributesAfterEndedGameRestoreToLobbyOnLogin() {
TestUtils.getMockPlayer("cwstalelogin").use { player ->
player.location = Location.create(2401, 3101, 0)
CastleWarsGameArea.ticksLeftInGame = -1
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TEAM, CastleWarsTeam.SARADOMIN.name)
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TIME_MILLIS, System.currentTimeMillis())
setAttribute(player, CastleWarsConstants.ATTR_LOGOUT_LOCATION, Location.create(player.location))
player.inventory.add(Item(CastleWars.cwBandages))
registerTimer(player, spawnTimer("teleblock", 100, false))
CastleWarsGameArea().login(player)
assertFalse(CastleWarsWaitingArea.waitingSaradominPlayers.contains(player))
assertEquals("", getAttribute(player, CastleWarsConstants.ATTR_LOGOUT_TEAM, ""))
assertFalse(player.inventory.contains(CastleWars.cwBandages, 1))
assertFalse(hasTimerActive(player, "teleblock"))
assertTrue(CastleWars.lobbyBankArea.insideBorder(player.location))
}
}
@Test
fun activeGameLogoutAfterTwoMinutesReturnsPlayerToTeamWaitingRoom() {
TestUtils.getMockPlayer("cwslowrelog").use { player ->
@ -295,6 +502,21 @@ class CastleWarsTests {
}
}
@Test
fun spawnRoomLogoutClearsRespawnRoomTracking() {
TestUtils.getMockPlayer("cwspawntrack").use { player ->
player.location = CastleWarsRespawnArea.saradominRespawnRoom.randomWalkableLoc
CastleWarsGameArea.ticksLeftInGame = 500
CastleWarsGameArea.saradominPlayers.add(player)
CastleWarsRespawnArea().areaEnter(player)
CastleWarsRespawnArea().logout(player)
assertFalse(CastleWarsRespawnArea.saradominPlayersInRespawnRoom.containsKey(player))
assertFalse(CastleWarsRespawnArea.zamorakPlayersInRespawnRoom.containsKey(player))
}
}
@Test
fun waitingRoomLogoutReturnsPlayerToLobbyAndLosesQueuePosition() {
TestUtils.getMockPlayer("cwwaitlogout").use { player ->
@ -410,6 +632,122 @@ class CastleWarsTests {
}
}
@Test
fun loggedOutGracePlayerReceivesEndGameTicket() {
TestUtils.getMockPlayer("cwlogoutticket").use { player ->
TestUtils.getMockPlayer("cwlogoutticketz").use { opponent ->
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.saradominPlayers.add(player)
CastleWarsGameArea.zamorakPlayers.add(opponent)
CastleWarsGameArea.loggedOutPlayers[CastleWarsGameArea.playerKey(player)] =
CastleWarsGameArea.Companion.LogoutRecord(
player,
CastleWarsTeam.SARADOMIN,
getWorldTicks(),
Location.create(2400, 3100, 0)
)
player.isActive = false
try {
CastleWarsGameArea.endCurrentGame(startCooldown = false)
} finally {
player.isActive = true
}
assertEquals(1, player.inventory.getAmount(CastleWars.cwTicketRewardCurrency))
assertTrue(CastleWars.lobbyBankArea.insideBorder(player.location))
}
}
}
@Test
fun gameEndCleansCastleWarsSuppliesBeforeAwardingTickets() {
TestUtils.getMockPlayer("cwfullticket").use { player ->
TestUtils.getMockPlayer("cwfullticketz").use { opponent ->
player.inventory.add(Item(CastleWars.cwBandages, player.inventory.capacity()))
CastleWarsGameArea.saradominPlayers.add(player)
CastleWarsGameArea.zamorakPlayers.add(opponent)
CastleWarsGameState.endGame()
assertEquals(1, player.inventory.getAmount(CastleWars.cwTicketRewardCurrency))
assertFalse(player.inventory.contains(CastleWars.cwBandages, 1))
}
}
}
@Test
fun matchEndClearsCastleWarsGroundItemsInArena() {
TestUtils.getMockPlayer("cwgroundcleanup").use { player ->
val arenaItem = GroundItemManager.create(
Item(CastleWars.cwBandages),
Location.create(2400, 3100, 0),
player
)
assertTrue(GroundItemManager.getItems().contains(arenaItem))
CastleWarsGameArea.endCurrentGame(startCooldown = false)
assertFalse(GroundItemManager.getItems().contains(arenaItem))
}
}
@Test
fun fullInventoryEndGameTicketsDropAtLobbyBank() {
TestUtils.getMockPlayer("cwfullnormal").use { player ->
TestUtils.getMockPlayer("cwfullnormalz").use { opponent ->
player.location = Location.create(2400, 3100, 0)
player.inventory.add(Item(Items.CUP_OF_TEA_1978, player.inventory.capacity()))
CastleWarsGameArea.saradominPlayers.add(player)
CastleWarsGameArea.zamorakPlayers.add(opponent)
CastleWarsGameState.endGame()
val droppedTickets = GroundItemManager.getItems().filter {
it.id == CastleWars.cwTicketRewardCurrency && it.droppedBy(player)
}
assertTrue(droppedTickets.isNotEmpty())
assertTrue(droppedTickets.all { CastleWars.lobbyBankArea.insideBorder(it.location) })
droppedTickets.forEach { GroundItemManager.destroy(it) }
}
}
}
@Test
fun gameEndCleansCarriedFlagsWithoutDropOrReturnMessages() {
TestUtils.getMockPlayer("cwquietflag").use { carrier ->
TestUtils.getMockPlayer("cwquietflagz").use { opponent ->
val carrierSession = carrier.session as MockSession
val opponentSession = opponent.session as MockSession
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.saradominPlayers.add(carrier)
CastleWarsGameArea.zamorakPlayers.add(opponent)
assertTrue(CastleWarsGameState.pickUpFlag(carrier, CastleWarsTeam.ZAMORAK))
carrierSession.clear()
opponentSession.clear()
CastleWarsGameState.endGame()
val messages = gameMessagePayloads(carrierSession) + gameMessagePayloads(opponentSession)
assertFalse(messages.any { it.contains("flag has been dropped") })
assertFalse(messages.any { it.contains("flag has been returned") })
}
}
}
@Test
fun cleanupRemovesCastleWarsTeleblock() {
TestUtils.getMockPlayer("cwcleanblock").use { player ->
registerTimer(player, spawnTimer("teleblock", 100, false))
assertTrue(hasTimerActive(player, "teleblock"))
CastleWarsGameState.restoreAndCleanupPlayer(player)
assertFalse(hasTimerActive(player, "teleblock"))
}
}
@Test
fun joinPortalRejectsFoodAndInventoryHelmets() {
withCastleWarsEnabled {
@ -548,6 +886,42 @@ class CastleWarsTests {
}
}
@Test
fun fullInventoryFlagCarrierCanUnequipToDropFlag() {
TestUtils.getMockPlayer("cwfulldrop").use { carrier ->
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.saradominPlayers.add(carrier)
assertTrue(CastleWarsGameState.pickUpFlag(carrier, CastleWarsTeam.ZAMORAK))
carrier.inventory.add(Item(Items.CUP_OF_TEA_1978, carrier.inventory.capacity()))
EquipHandler.unequip(carrier, EquipmentSlot.WEAPON.ordinal, CastleWars.zamorakFlag)
assertFalse(CastleWarsGameState.isCarryingFlag(carrier))
assertNull(carrier.equipment[EquipmentSlot.WEAPON.ordinal])
}
}
@Test
fun equippingWeaponWhileCarryingFlagDropsFlagAndEquipsWeapon() {
TestUtils.getMockPlayer("cwflagswap").use { carrier ->
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.saradominPlayers.add(carrier)
carrier.inventory.add(Item(Items.BRONZE_SWORD_1277))
assertTrue(CastleWarsGameState.pickUpFlag(carrier, CastleWarsTeam.ZAMORAK))
InteractionListeners.run(
Items.BRONZE_SWORD_1277, IntType.ITEM, "wield", carrier, carrier.inventory[0]
)
assertFalse(CastleWarsGameState.isCarryingFlag(carrier))
assertEquals(Items.BRONZE_SWORD_1277, carrier.equipment[EquipmentSlot.WEAPON.ordinal]?.id)
assertFalse(carrier.inventory.contains(CastleWars.zamorakFlag, 1))
assertEquals(FlagState.DROPPED, CastleWarsGameState.zamorakFlagState)
}
}
@Test
fun ownTeamBackdoorLockOptionIsHandled() {
assertNotNull(
@ -558,6 +932,27 @@ class CastleWarsTests {
)
}
@Test
fun resetGameRestoresOpenedBackdoorsToClosedScenery() {
val loc = (2418..2432).asSequence()
.flatMap { x -> (3070..3085).asSequence().map { y -> Location.create(x, y, 0) } }
.first { RegionManager.getObject(it) == null }
val closed = Scenery(CastleWars.saradominBackdoor, loc, 10, 0)
val open = Scenery(CastleWars.saradominBackdoorOpen, loc, 10, 0)
try {
SceneryBuilder.add(closed)
SceneryBuilder.replace(requireNotNull(RegionManager.getObject(loc)), open, -1, true)
assertEquals(CastleWars.saradominBackdoorOpen, RegionManager.getObject(loc)?.id)
CastleWarsGameState.resetGame()
assertEquals(CastleWars.saradominBackdoor, RegionManager.getObject(loc)?.id)
} finally {
RegionManager.getObject(loc)?.let { SceneryBuilder.remove(it) }
}
}
@Test
fun backdoorPicklockUsesLinearThievingCurveFromLevelOneToNinetyNine() {
assertEquals(6.3, getBackdoorPicklockSuccessChance(1), 0.0001)
@ -595,6 +990,7 @@ class CastleWarsTests {
10,
0
)
ensureCatapultObject(catapult)
val operateCatapult = InteractionListeners.get(
catapult.id, IntType.SCENERY.ordinal, "operate"
)!!
@ -638,6 +1034,84 @@ class CastleWarsTests {
}
}
@Test
fun staleCatapultInterfaceCannotFireAfterCatapultIsDestroyed() {
TestUtils.getMockPlayer("cwcatstale").use { shooter ->
val catapult = Scenery(
CastleWars.getOperationalCatapultId(CastleWarsTeam.SARADOMIN),
CastleWarsGameState.saradominCatapultLocation,
10,
0
)
ensureCatapultObject(catapult)
val operateCatapult = InteractionListeners.get(
catapult.id, IntType.SCENERY.ordinal, "operate"
)!!
CastleWarsGameArea.saradominPlayers.add(shooter)
shooter.inventory.add(Item(CastleWars.cwRock))
operateCatapult(shooter, catapult)
assertTrue(shooter.interfaceManager.hasMainComponent(CatapultConfig.INTERFACE_ID))
CastleWarsGameState.destroyCatapult(CastleWarsTeam.SARADOMIN)
InterfaceListeners.run(
shooter, Component(CatapultConfig.INTERFACE_ID), 0, CatapultConfig.BUTTON_FIRE, 0, 0
)
assertEquals(1, shooter.inventory.getAmount(CastleWars.cwRock))
assertFalse(CatapultFiringHandler.hasShotInFlight(shooter))
assertFalse(shooter.interfaceManager.hasMainComponent(CatapultConfig.INTERFACE_ID))
}
}
@Test
fun staleCatapultInterfaceCannotFireWhenCatapultObjectIsMissing() {
TestUtils.getMockPlayer("cwcatmissing").use { shooter ->
CastleWarsGameArea.saradominPlayers.add(shooter)
shooter.inventory.add(Item(CastleWars.cwRock))
shooter.setAttribute(CatapultConfig.ATTR_LOCATION, Location.create(2400, 3090, 0))
shooter.interfaceManager.open(Component(CatapultConfig.INTERFACE_ID))
InterfaceListeners.run(
shooter, Component(CatapultConfig.INTERFACE_ID), 0, CatapultConfig.BUTTON_FIRE, 0, 0
)
assertEquals(1, shooter.inventory.getAmount(CastleWars.cwRock))
assertFalse(CatapultFiringHandler.hasShotInFlight(shooter))
assertFalse(shooter.interfaceManager.hasMainComponent(CatapultConfig.INTERFACE_ID))
}
}
@Test
fun extinguishedCatapultFirePulseDoesNotDestroyRelitCatapult() {
TestUtils.getMockPlayer("cwcatfirez").use { attacker ->
TestUtils.getMockPlayer("cwcatfires").use { defender ->
val catapult = Scenery(
CastleWars.getOperationalCatapultId(CastleWarsTeam.SARADOMIN),
CastleWarsGameState.saradominCatapultLocation,
10,
0
)
ensureCatapultObject(catapult)
CastleWarsGameArea.zamorakPlayers.add(attacker)
CastleWarsGameArea.saradominPlayers.add(defender)
CastleWarsGameArea.ticksLeftInGame = 100
defender.inventory.add(Item(Items.BUCKET_OF_WATER_1929))
assertTrue(runUseWith(Item(Items.TINDERBOX_590), catapult, IntType.SCENERY, attacker))
TestUtils.advanceTicks(25, false)
assertTrue(runUseWith(defender.inventory[0], catapult, IntType.SCENERY, defender))
assertFalse(CastleWarsGameState.isCatapultOnFire(CastleWarsTeam.SARADOMIN))
assertTrue(runUseWith(Item(Items.TINDERBOX_590), catapult, IntType.SCENERY, attacker))
TestUtils.advanceTicks(25, false)
assertTrue(CastleWarsGameState.isCatapultOperational(CastleWarsTeam.SARADOMIN))
assertTrue(CastleWarsGameState.isCatapultOnFire(CastleWarsTeam.SARADOMIN))
}
}
}
@Test
fun climbingRopeOnPlayerTileCanBeClimbed() {
val rope = Scenery(CastleWars.cwCastleClimbingRope, Location.create(2420, 3078, 1), 4, 0)
@ -692,6 +1166,33 @@ class CastleWarsTests {
}
}
@Test
fun extinguishingBarricadeStopsPreviousBurnPulseBeforeRelight() {
TestUtils.getMockPlayer("cwbarrfire").use { player ->
val barricade = CastleWarsBarricadeNPC.spawn(Location.create(2400, 3100, 0), CastleWarsTeam.SARADOMIN)
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsItemHandler.saradominBarricades.add(barricade)
player.inventory.add(Item(Items.BUCKET_OF_WATER_1929))
assertTrue(runUseWith(Item(Items.TINDERBOX_590), barricade, IntType.NPC, player))
val firstPulse = CastleWarsItemHandler.burningBarricadePulses[barricade]
assertNotNull(firstPulse)
assertTrue(firstPulse!!.isRunning)
assertTrue(runUseWith(player.inventory[0], barricade, IntType.NPC, player))
assertFalse(firstPulse.isRunning)
assertFalse(CastleWarsItemHandler.burningBarricadePulses.containsKey(barricade))
assertTrue(runUseWith(Item(Items.TINDERBOX_590), barricade, IntType.NPC, player))
val secondPulse = CastleWarsItemHandler.burningBarricadePulses[barricade]
assertNotNull(secondPulse)
assertNotSame(firstPulse, secondPulse)
assertFalse(firstPulse.isRunning)
barricade.clear()
}
}
@Test
fun matchStartActivatesBraceletEffectsAndStealFromOption() {
TestUtils.getMockPlayer("cwbrace1").use { braceletPlayer ->
@ -716,6 +1217,24 @@ class CastleWarsTests {
}
}
@Test
fun substitutionActivatesBraceletEffects() {
TestUtils.getMockPlayer("cwsubbraceleave").use { leaver ->
TestUtils.getMockPlayer("cwsubbrace").use { substitute ->
substitute.equipment.replace(Item(Items.CASTLEWAR_BRACE3_11079), EquipmentSlot.HANDS.ordinal)
CastleWarsGameArea.ticksLeftInGame = 100
CastleWarsGameArea.saradominPlayers.add(leaver)
CastleWarsWaitingArea.waitingSaradominPlayers.add(substitute)
CastleWarsGameArea.removePlayerFromGame(leaver)
assertTrue(CastleWarsGameArea.claimVacancy(substitute, CastleWarsTeam.SARADOMIN))
assertEquals(CastleWarsTeam.SARADOMIN, substitute.activeCastleWarsBraceletTeam())
assertNotNull(substitute.interaction[Option._P_STEAL_FROM.index])
}
}
}
@Test
fun bandagesHealTenPercentRestoreRunEnergyAndCurePoison() {
TestUtils.getMockPlayer("cwbandage").use { player ->
@ -738,6 +1257,36 @@ class CastleWarsTests {
}
}
@Test
fun bandagesCannotBeUsedOutsideActiveCastleWarsGame() {
TestUtils.getMockPlayer("cwbandageoutside").use { player ->
player.skills.setStaticLevel(Skills.HITPOINTS, 99)
player.skills.lifepoints = 50
player.inventory.add(Item(CastleWars.cwBandages))
InteractionListeners.run(
CastleWars.cwBandages, IntType.ITEM, "Heal", player, player.inventory[0]
)
assertEquals(50, player.skills.lifepoints)
assertTrue(player.inventory.contains(CastleWars.cwBandages, 1))
}
}
@Test
fun castleWarsTeleblockDoesNotSendChatboxMessage() {
TestUtils.getMockPlayer("cwsilentteleblock").use { player ->
val session = player.session as MockSession
player.location = CastleWarsWaitingArea.saradominWaitingRoom.randomWalkableLoc
session.clear()
CastleWarsWaitingArea().areaEnter(player)
assertTrue(hasTimerActive(player, "teleblock"))
assertFalse(session.hasPacketReady(70))
}
}
@Test
fun activeBraceletBandagesHealTeammatesForFifteenPercent() {
TestUtils.getMockPlayer("cwhealer").use { healer ->
@ -815,6 +1364,27 @@ class CastleWarsTests {
assertTrue(player.dialogueInterpreter.dialogue.file is CastleWarsSubstitutionDialogue)
}
private fun ensureCatapultObject(catapult: Scenery) {
val current = RegionManager.getObject(catapult.location)
if (current != null && current.id in CastleWars.allCatapultIds) {
if (current.id != catapult.id) {
SceneryBuilder.replace(current, catapult)
}
return
}
SceneryBuilder.add(catapult)
}
private fun runUseWith(used: Item, with: Node, type: IntType, player: Player): Boolean {
val handler = InteractionListeners.get(used.id, with.id, type.ordinal)
assertNotNull(handler)
return handler!!.invoke(player, used, with)
}
private fun gameMessagePayloads(session: MockSession): List<String> {
return session.getPacketsWithOpcode(70).map { String(it, Charsets.ISO_8859_1) }
}
private fun withCastleWarsEnabled(block: () -> Unit) {
val settings = GameWorld.settings!!
val original = settings.enable_castle_wars