diff --git a/Server/src/main/content/minigame/castlewars/CastleWars.kt b/Server/src/main/content/minigame/castlewars/CastleWars.kt index 2ca5fa970..599566e95 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWars.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWars.kt @@ -94,12 +94,7 @@ object CastleWars { val burningCatapultIds: IntArray = intArrayOf(catapultBurning1, catapultBurning2) val damagedCatapultIds: IntArray = intArrayOf(catapultDamaged1, catapultDamaged2) val allCatapultIds: IntArray = intArrayOf( - catapult, - catapultAlt, - catapultBurning1, - catapultBurning2, - catapultDamaged1, - catapultDamaged2 + catapult, catapultAlt, catapultBurning1, catapultBurning2, catapultDamaged1, catapultDamaged2 ) fun getOperationalCatapultId(team: CastleWarsTeam): Int = when (team) { @@ -154,8 +149,7 @@ object CastleWars { Scenery.TABLE_36586 to Items.BANDAGES_4049 // Zamorak Bandages Table ) val cwCastleBattlementsMap = mapOf( - Scenery.BATTLEMENTS_4446 to Scenery.BATTLEMENTS_36313, - Scenery.BATTLEMENTS_4447 to Scenery.BATTLEMENTS_36314 + Scenery.BATTLEMENTS_4446 to Scenery.BATTLEMENTS_36313, Scenery.BATTLEMENTS_4447 to Scenery.BATTLEMENTS_36314 ) // Item IDs diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsBarricadeNPC.kt b/Server/src/main/content/minigame/castlewars/CastleWarsBarricadeNPC.kt index f53b06f3c..3510dad56 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsBarricadeNPC.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsBarricadeNPC.kt @@ -50,6 +50,10 @@ class CastleWarsBarricadeNPC : AbstractNPC { return } + override fun isPoisonImmune(): Boolean { + return true + } + override fun construct(id: Int, location: Location, vararg objects: Any?): AbstractNPC { return CastleWarsBarricadeNPC(id, location) } diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsCatapultInterface.kt b/Server/src/main/content/minigame/castlewars/CastleWarsCatapultInterface.kt index 8eb134c47..448e3eec3 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsCatapultInterface.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsCatapultInterface.kt @@ -75,9 +75,7 @@ class CastleWarsCatapultInterface : InterfaceListener { } private fun handleCoordinateChange( - player: Player, - currentCoord: Coordinate, - button: CatapultButton + player: Player, currentCoord: Coordinate, button: CatapultButton ): Boolean { val newCoord = currentCoord.withDelta(button.deltaX, button.deltaY).clamp() diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsCommands.kt b/Server/src/main/content/minigame/castlewars/CastleWarsCommands.kt index 0a29cd9be..aae404781 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsCommands.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsCommands.kt @@ -32,9 +32,7 @@ class CastleWarsCommands : Commands { } val saraCount = CastleWarsWaitingArea.waitingSaradominPlayers.size val zamCount = CastleWarsWaitingArea.waitingZamorakPlayers.size - if (saraCount < CastleWarsConstants.MIN_PLAYERS_PER_TEAM || - zamCount < CastleWarsConstants.MIN_PLAYERS_PER_TEAM - ) { + if (saraCount < CastleWarsConstants.MIN_PLAYERS_PER_TEAM || zamCount < CastleWarsConstants.MIN_PLAYERS_PER_TEAM) { sendMessage( player, "Both teams need at least ${CastleWarsConstants.MIN_PLAYERS_PER_TEAM} players. Saradomin: $saraCount, Zamorak: $zamCount" diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsConstants.kt b/Server/src/main/content/minigame/castlewars/CastleWarsConstants.kt index 3dc4e1bb8..689828bf9 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsConstants.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsConstants.kt @@ -89,17 +89,14 @@ object CastleWarsConstants { const val ENERGY_BARRIER_DRAIN = 4.0 // ==================== Backdoor Pick-Lock ==================== - /** Thieving level required for pick-lock, matching Knight of Ardougne pickpocketing */ - const val BACKDOOR_PICKLOCK_LEVEL = 55 + /** Backdoor pick-lock success at Thieving level 1 */ + const val BACKDOOR_PICKLOCK_LEVEL_ONE_SUCCESS = 6.3 - /** Low roll bound for pick-lock success, matching Knight of Ardougne pickpocketing */ - const val BACKDOOR_PICKLOCK_LOW = 50.0 + /** Backdoor pick-lock success at Thieving level 99 */ + const val BACKDOOR_PICKLOCK_LEVEL_NINETY_NINE_SUCCESS = 100.0 - /** High roll bound for pick-lock success, matching Knight of Ardougne pickpocketing */ - const val BACKDOOR_PICKLOCK_HIGH = 240.0 - - /** Ticks to lock player during pick-lock attempt */ - const val BACKDOOR_PICKLOCK_TICKS = 3 + /** Highest Thieving level used by the linear backdoor pick-lock curve */ + const val BACKDOOR_PICKLOCK_MAX_LEVEL = 99 // ==================== Cleanup Intervals ==================== /** How often to run inactive player cleanup (every 10 ticks = 6 seconds) */ @@ -152,4 +149,7 @@ object CastleWarsConstants { /** Player attribute key for tracking when enemy flag was picked up */ const val ATTR_FLAG_PICKUP_TICK = "cw:flag_pickup_tick" + + /** Player attribute key: team whose bracelet effect was activated when the match started */ + const val ATTR_ACTIVE_BRACELET_TEAM = "cw:active_bracelet_team" } diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsDoorAttackPulse.kt b/Server/src/main/content/minigame/castlewars/CastleWarsDoorAttackPulse.kt index 539f8f916..5d8a0269d 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsDoorAttackPulse.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsDoorAttackPulse.kt @@ -16,9 +16,7 @@ import org.rs09.consts.Sounds * - Damages door health until destroyed or player stops */ class CastleWarsDoorAttackPulse( - private val player: Player, - private val door: Scenery, - private val doorTeam: CastleWarsTeam + private val player: Player, private val door: Scenery, private val doorTeam: CastleWarsTeam ) : Pulse() { private var ticksUntilNextHit = 0 diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsEquipmentRestrictions.kt b/Server/src/main/content/minigame/castlewars/CastleWarsEquipmentRestrictions.kt index 015edc79b..5692fdee4 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsEquipmentRestrictions.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsEquipmentRestrictions.kt @@ -10,12 +10,10 @@ import core.game.node.item.GroundItemManager object CastleWarsEquipmentRestrictions { private val restrictedSlots = intArrayOf( - EquipmentSlot.HEAD.ordinal, - EquipmentSlot.CAPE.ordinal + EquipmentSlot.HEAD.ordinal, EquipmentSlot.CAPE.ordinal ) private val castleWarsTeamCapeIds = intArrayOf( - CastleWars.saradominTeamHoodedCloak, - CastleWars.zamorakTeamHoodedCloak + CastleWars.saradominTeamHoodedCloak, CastleWars.zamorakTeamHoodedCloak ) fun blocksEquip(player: Player, equipmentSlot: Int): Boolean { @@ -45,9 +43,11 @@ object CastleWarsEquipmentRestrictions { } private fun isInCastleWarsArea(player: Player): Boolean { - val allCastleWarsBorders = CastleWarsGameArea.areaBorders.toList() + - CastleWarsWaitingArea.areaBorders.toList() + - listOf(CastleWarsRespawnArea.saradominRespawnRoom, CastleWarsRespawnArea.zamorakRespawnRoom) + val allCastleWarsBorders = + CastleWarsGameArea.areaBorders.toList() + CastleWarsWaitingArea.areaBorders.toList() + listOf( + CastleWarsRespawnArea.saradominRespawnRoom, + CastleWarsRespawnArea.zamorakRespawnRoom + ) return allCastleWarsBorders.any { it.insideBorder(player.location) } } } diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsFlagHandler.kt b/Server/src/main/content/minigame/castlewars/CastleWarsFlagHandler.kt index a031e37f6..285296ec2 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsFlagHandler.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsFlagHandler.kt @@ -3,7 +3,6 @@ package content.minigame.castlewars import core.api.sendMessage import core.game.interaction.IntType import core.game.interaction.InteractionListener -import core.game.node.item.GroundItem import core.game.world.map.Location /** diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsGameState.kt b/Server/src/main/content/minigame/castlewars/CastleWarsGameState.kt index 7ccf9e916..97a9ae181 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsGameState.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsGameState.kt @@ -7,6 +7,7 @@ import content.minigame.castlewars.areas.CastleWarsRespawnArea import content.minigame.castlewars.areas.CastleWarsWaitingArea import core.api.* import core.game.container.impl.EquipmentContainer +import core.game.interaction.Option import core.game.node.entity.combat.ImpactHandler import core.game.node.entity.player.Player import core.game.node.entity.player.link.HintIconManager @@ -62,8 +63,7 @@ enum class BoulderState { } data class ActiveClimbingRope( - val rope: Scenery, - val originalWall: Scenery + val rope: Scenery, val originalWall: Scenery ) /** @@ -225,8 +225,7 @@ object CastleWarsGameState { * Get the remaining game time in minutes */ val remainingMinutes: Int - get() = (CastleWarsGameArea.ticksLeftInGame + CastleWarsConstants.TICKS_PER_MINUTE - 1) / - CastleWarsConstants.TICKS_PER_MINUTE + get() = (CastleWarsGameArea.ticksLeftInGame + CastleWarsConstants.TICKS_PER_MINUTE - 1) / CastleWarsConstants.TICKS_PER_MINUTE /** * Reset the game state for a new game @@ -896,8 +895,7 @@ object CastleWarsGameState { if (carriedFlag == playerTeam) { return false } - val ownFlagState = - if (playerTeam == CastleWarsTeam.SARADOMIN) saradominFlagState else zamorakFlagState + val ownFlagState = if (playerTeam == CastleWarsTeam.SARADOMIN) saradominFlagState else zamorakFlagState if (ownFlagState != FlagState.SAFE) { sendMessage(player, "You can't capture while your own flag is not at its stand!") return false @@ -950,16 +948,19 @@ object CastleWarsGameState { } /** - * Calculate bonus damage from Castle Wars bracelet against flag carriers. - * Returns the bonus damage amount (20% of base hit) if attacker has bracelet - * and target is carrying a flag, otherwise returns 0. + * Calculate bonus damage from an active Castle Wars bracelet against the carrier of the wearer's team flag. */ fun getBraceletBonusDamage(attacker: Player, target: Player, baseHit: Int): Int { - if (!attacker.hasCastleWarsBracelet()) return 0 - if (!isCarryingAnyFlag(target)) return 0 + if (baseHit <= 0) return 0 + val braceletTeam = attacker.activeCastleWarsBraceletTeam() ?: return 0 + if (getCarriedFlag(target) != braceletTeam) return 0 return (baseHit * CastleWarsConstants.BRACELET_FLAG_CARRIER_DAMAGE_BONUS).toInt() } + fun applyBraceletDamageBoost(attacker: Player, target: Player, hit: Int): Int { + return hit + getBraceletBonusDamage(attacker, target, hit) + } + /** * Get which enemy flag a player is carrying */ @@ -1076,6 +1077,9 @@ 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) + clearActiveCastleWarsBracelet(player) + player.interaction.remove(Option._P_ATTACK) + player.interaction.remove(Option._P_STEAL_FROM) } /** @@ -1622,7 +1626,6 @@ object CastleWarsGameState { /** * Check if a teammate can steal the flag from a flag carrier. * Returns true if the carrier has held the flag for longer than FLAG_STEAL_TIMER_TICKS (3 minutes). - * Use bandages on a teammate carrying a flag to take it from them. */ fun canTeammateStealFlag(carrier: Player): Boolean { val pickupTick = getAttribute(carrier, CastleWarsConstants.ATTR_FLAG_PICKUP_TICK, 0) @@ -1630,9 +1633,60 @@ object CastleWarsGameState { return getWorldTicks() - pickupTick >= CastleWarsConstants.FLAG_STEAL_TIMER_TICKS } + fun setActiveGamePlayerOptions(player: Player) { + player.interaction.set(Option._P_ATTACK) + player.interaction.set(Option._P_STEAL_FROM) + } + + fun activateBraceletIfEquipped(player: Player, team: CastleWarsTeam) { + if (player.hasCastleWarsBracelet()) { + setAttribute(player, CastleWarsConstants.ATTR_ACTIVE_BRACELET_TEAM, team.name) + } else { + clearActiveCastleWarsBracelet(player) + } + } + + fun clearActiveCastleWarsBracelet(player: Player) { + removeAttribute(player, CastleWarsConstants.ATTR_ACTIVE_BRACELET_TEAM) + } + + fun stealFlagFromTeammate(player: Player, carrier: Player): Boolean { + if (!isGameActive) { + sendMessage(player, "You can only steal flags during a Castle Wars game.") + return false + } + if (player == carrier) { + sendMessage(player, "You can't steal the flag from yourself.") + return false + } + val playerTeam = getPlayerTeam(player) + val carrierTeam = getPlayerTeam(carrier) + if (playerTeam == null || carrierTeam == null) { + sendMessage(player, "You can only steal flags in Castle Wars.") + return false + } + if (playerTeam != carrierTeam) { + sendMessage(player, "You can only steal the flag from a teammate.") + return false + } + if (!isCarryingFlag(carrier)) { + sendMessage(player, "That player isn't carrying the enemy flag.") + return false + } + if (!canTeammateStealFlag(carrier)) { + sendMessage(player, "That player hasn't held the flag long enough.") + return false + } + if (isCarryingAnyFlag(player)) { + sendMessage(player, "You're already carrying a flag!") + return false + } + transferFlag(carrier, player) + return true + } + /** * Transfer the enemy flag from one player to another teammate. - * Used when bandages are used on a flag carrier after the steal timer expires. */ fun transferFlag(from: Player, to: Player) { val carriedFlag = getCarriedFlag(from) ?: return @@ -1678,12 +1732,22 @@ fun Player.hasCastleWarsBracelet(): Boolean { return bracelet.id == Items.CASTLEWAR_BRACE3_11079 || bracelet.id == Items.CASTLEWAR_BRACE2_11081 || bracelet.id == Items.CASTLEWAR_BRACE1_11083 } +fun Player.activeCastleWarsBraceletTeam(): CastleWarsTeam? { + val teamName = getAttribute(CastleWarsConstants.ATTR_ACTIVE_BRACELET_TEAM, null as String?) ?: return null + return CastleWarsTeam.values().firstOrNull { it.name == teamName } +} + +fun Player.hasActiveCastleWarsBracelet(): Boolean { + return activeCastleWarsBraceletTeam() != null +} + /** * Deplete one charge from the Castle Wars bracelet. * Bracelet(3) -> Bracelet(2) -> Bracelet(1) -> removed. * Called at the end of each game for players wearing the bracelet. */ fun Player.depleteCastleWarsBracelet() { + if (!hasActiveCastleWarsBracelet()) return val bracelet = equipment.get(EquipmentContainer.SLOT_HANDS) ?: return val nextId = when (bracelet.id) { Items.CASTLEWAR_BRACE3_11079 -> Items.CASTLEWAR_BRACE2_11081 diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsItemHandler.kt b/Server/src/main/content/minigame/castlewars/CastleWarsItemHandler.kt index ac40ff057..1f80e65e2 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsItemHandler.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsItemHandler.kt @@ -63,16 +63,12 @@ class CastleWarsItemHandler : InteractionListener { fun dealExplosiveAoeDamage(attacker: Player, location: Location, radius: Int = 1) { val attackerTeam = CastleWarsGameState.getPlayerTeam(attacker) ?: return - RegionManager.getLocalPlayers(location, radius) - .filter { target -> - target != attacker && - target.isActive && - CastleWarsGameState.getPlayerTeam(target) != null && - CastleWarsGameState.getPlayerTeam(target) != attackerTeam - } - .forEach { target -> - val damage = CastleWarsConstants.EXPLOSIVE_POTION_SELF_DAMAGE - .coerceAtMost(target.skills.lifepoints) + RegionManager.getLocalPlayers(location, radius).filter { target -> + target != attacker && target.isActive && CastleWarsGameState.getPlayerTeam(target) != null && CastleWarsGameState.getPlayerTeam( + target + ) != attackerTeam + }.forEach { target -> + val damage = CastleWarsConstants.EXPLOSIVE_POTION_SELF_DAMAGE.coerceAtMost(target.skills.lifepoints) target.impactHandler.manualHit( attacker, damage, ImpactHandler.HitsplatType.NORMAL ) @@ -89,31 +85,26 @@ class CastleWarsItemHandler : InteractionListener { return@on true } - onUseWith(IntType.PLAYER, CastleWars.cwBandages, -1) { player, used, with -> - val target = with as? Player ?: return@onUseWith false + 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@onUseWith false + return@onUseWithPlayer false } if (playerTeam != targetTeam) { sendMessage(player, "You can only heal your teammates!") - return@onUseWith false - } - - if (CastleWarsGameState.isCarryingFlag(target) && CastleWarsGameState.canTeammateStealFlag(target)) { - CastleWarsGameState.transferFlag(target, player) - return@onUseWith true + return@onUseWithPlayer false } if (useBandage(player, target)) { removeItem(player, used.asItem()) } - return@onUseWith true + return@onUseWithPlayer true } on(CastleWars.cwBarricade, IntType.ITEM, "set-up") { player, node -> @@ -131,8 +122,7 @@ class CastleWarsItemHandler : InteractionListener { cleanupDeadBarricades() if (getBarricadeCount(team) >= MAX_BARRICADES_PER_TEAM) { sendMessage( - player, - "Your team already has the maximum number of barricades ($MAX_BARRICADES_PER_TEAM)." + player, "Your team already has the maximum number of barricades ($MAX_BARRICADES_PER_TEAM)." ) return@on false } @@ -161,8 +151,7 @@ class CastleWarsItemHandler : InteractionListener { onUseWith( IntType.NPC, CastleWars.cwExplosivePotion, *intArrayOf( - NPCs.BARRICADE_1532, NPCs.BARRICADE_1533, - NPCs.BARRICADE_1534, NPCs.BARRICADE_1535 + NPCs.BARRICADE_1532, NPCs.BARRICADE_1533, NPCs.BARRICADE_1534, NPCs.BARRICADE_1535 ) ) { player, used, with -> val barricade = with as? NPC ?: return@onUseWith false @@ -224,8 +213,7 @@ class CastleWarsItemHandler : InteractionListener { onUseWith( IntType.NPC, Items.TINDERBOX_590, *intArrayOf( - NPCs.BARRICADE_1532, NPCs.BARRICADE_1533, - NPCs.BARRICADE_1534, NPCs.BARRICADE_1535 + NPCs.BARRICADE_1532, NPCs.BARRICADE_1533, NPCs.BARRICADE_1534, NPCs.BARRICADE_1535 ) ) { player, _, with -> val barricade = with as? NPC ?: return@onUseWith false @@ -304,13 +292,13 @@ class CastleWarsItemHandler : InteractionListener { return false } - if (target.clocks[Clocks.NEXT_EAT] >= getWorldTicks()) { + if (target.clocks[Clocks.NEXT_EAT] > getWorldTicks()) { return false } target.clocks[Clocks.NEXT_EAT] = getWorldTicks() + BANDAGE_COOLDOWN_TICKS val maxHp = target.skills.getStaticLevel(Skills.HITPOINTS) - val healPercent = if (user.hasCastleWarsBracelet()) { + val healPercent = if (user.hasActiveCastleWarsBracelet()) { CastleWarsConstants.BRACELET_BANDAGE_HEAL_PERCENTAGE } else { BANDAGE_HEAL_PERCENTAGE @@ -338,11 +326,9 @@ class CastleWarsItemHandler : InteractionListener { private fun getLocalBarricades(location: Location, radius: Int): List { val barricadeIds = intArrayOf( - NPCs.BARRICADE_1532, NPCs.BARRICADE_1533, - NPCs.BARRICADE_1534, NPCs.BARRICADE_1535 + NPCs.BARRICADE_1532, NPCs.BARRICADE_1533, NPCs.BARRICADE_1534, NPCs.BARRICADE_1535 ) - return RegionManager.getLocalNpcs(location, radius) - .filter { it.id in barricadeIds && it.isActive } + return RegionManager.getLocalNpcs(location, radius).filter { it.id in barricadeIds && it.isActive } } } diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt index 573e9b9d2..404b59652 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt @@ -15,8 +15,8 @@ import core.game.global.action.SpecialLadders import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.node.entity.player.Player -import core.game.node.entity.skill.Skills import core.game.node.entity.player.link.TeleportManager +import core.game.node.entity.skill.Skills import core.game.node.item.Item import core.game.node.scenery.Scenery import core.game.node.scenery.SceneryBuilder @@ -34,38 +34,35 @@ import org.rs09.consts.Items import org.rs09.consts.Sounds data class BarrierData( - val location: Location, - val direction: Direction, - val outsideTile: Location, - val insideTile: Location + val location: Location, val direction: Direction, val outsideTile: Location, val insideTile: Location ) data class ClimbingRopeTraversal( - val topOfWallTile: Location, - val baseOfWallTile: Location + val topOfWallTile: Location, val baseOfWallTile: Location ) private val barrierCoordinates: Map> = mapOf( CastleWars.zamorakEnergyBarrier to listOf( BarrierData( - Location.create(2373, 3127, 1), Direction.WEST, + Location.create(2373, 3127, 1), + Direction.WEST, Location.create(2373, 3126, 1), Location.create(2373, 3127, 1) - ), - BarrierData( - Location.create(2376, 3131, 1), Direction.SOUTH, + ), BarrierData( + Location.create(2376, 3131, 1), + Direction.SOUTH, Location.create(2377, 3131, 1), Location.create(2376, 3131, 1) ) - ), - CastleWars.saradominEnergyBarrier to listOf( + ), CastleWars.saradominEnergyBarrier to listOf( BarrierData( Location.create(2426, 3080, 1), - Direction.EAST, Location.create(2426, 3081, 1), + Direction.EAST, + Location.create(2426, 3081, 1), Location.create(2426, 3080, 1) - ), - BarrierData( - Location.create(2423, 3076, 1), Direction.NORTH, + ), BarrierData( + Location.create(2423, 3076, 1), + Direction.NORTH, Location.create(2422, 3076, 1), Location.create(2423, 3076, 1) ) @@ -160,11 +157,11 @@ internal fun canClimbUpClimbingRope(playerLoc: Location, rope: Scenery): Boolean } internal fun getBackdoorPicklockSuccessChance(thievingLevel: Int): Double { - return RandomFunction.getSkillSuccessChance( - CastleWarsConstants.BACKDOOR_PICKLOCK_LOW, - CastleWarsConstants.BACKDOOR_PICKLOCK_HIGH, - thievingLevel - ) + val level = thievingLevel.coerceIn(1, CastleWarsConstants.BACKDOOR_PICKLOCK_MAX_LEVEL) + val levelProgress = (level - 1).toDouble() / (CastleWarsConstants.BACKDOOR_PICKLOCK_MAX_LEVEL - 1) + val chanceRange = + CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL_NINETY_NINE_SUCCESS - CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL_ONE_SUCCESS + return CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL_ONE_SUCCESS + chanceRange * levelProgress } class CastleWarsListeners : InteractionListener { @@ -177,9 +174,7 @@ class CastleWarsListeners : InteractionListener { } setDest( - IntType.SCENERY, - intArrayOf(CastleWars.saradominEnergyBarrier, CastleWars.zamorakEnergyBarrier), - "Pass" + IntType.SCENERY, intArrayOf(CastleWars.saradominEnergyBarrier, CastleWars.zamorakEnergyBarrier), "Pass" ) { entity, node -> val barrier = node.asScenery() val playerLoc = entity.location @@ -232,11 +227,8 @@ class CastleWarsListeners : InteractionListener { } else { val formattedItemName = - (ItemDefinition.forId(rewardItem).name.lowercase() + "s.") - .replace("pes.", "pe.") - .replace( - "bronze ", - "" + (ItemDefinition.forId(rewardItem).name.lowercase() + "s.").replace("pes.", "pe.").replace( + "bronze ", "" ) if (rewardItem == Items.TOOLKIT_4051) { @@ -249,9 +241,7 @@ class CastleWarsListeners : InteractionListener { } onUseWith( - SCENERY, - CastleWars.cwClimbingRope, - *CastleWars.cwCastleBattlementsMap.keys.toIntArray() + SCENERY, CastleWars.cwClimbingRope, *CastleWars.cwCastleBattlementsMap.keys.toIntArray() ) { player, rope, wall -> if (CastleWarsGameState.getClimbingRope(wall.location) != null) { sendDialogue(player, "There is already a rope tied to the battlements.") @@ -321,10 +311,7 @@ class CastleWarsListeners : InteractionListener { } val currentBarrier = RegionManager.getObject( - barrierScenery.location.z, - barrierScenery.location.x, - barrierScenery.location.y, - barrierScenery.id + barrierScenery.location.z, barrierScenery.location.x, barrierScenery.location.y, barrierScenery.id ) if (currentBarrier == null || !currentBarrier.isActive) { sendMessage(player, "The barrier is not working properly. Please try again.") @@ -353,10 +340,7 @@ class CastleWarsListeners : InteractionListener { } val currentBarrier = RegionManager.getObject( - barrierScenery.location.z, - barrierScenery.location.x, - barrierScenery.location.y, - barrierScenery.id + barrierScenery.location.z, barrierScenery.location.x, barrierScenery.location.y, barrierScenery.id ) if (currentBarrier == null || !currentBarrier.isActive) { sendMessage(player, "The barrier is not working properly. Please try again.") @@ -464,9 +448,7 @@ class CastleWarsListeners : InteractionListener { } onUseWith( - SCENERY, - CastleWars.cwExplosivePotion, - *(CastleWars.largeDoorIds + CastleWars.largeDoorOpenIds) + SCENERY, CastleWars.cwExplosivePotion, *(CastleWars.largeDoorIds + CastleWars.largeDoorOpenIds) ) { player, used, with -> val door = with.asScenery() val doorTeam = getDoorTeam(door.location) @@ -502,9 +484,7 @@ class CastleWarsListeners : InteractionListener { } onUseWith( - SCENERY, - CastleWars.cwToolkit, - *(CastleWars.largeDoorIds + CastleWars.largeDoorOpenIds) + SCENERY, CastleWars.cwToolkit, *(CastleWars.largeDoorIds + CastleWars.largeDoorOpenIds) ) { player, used, with -> val door = with.asScenery() val doorTeam = getDoorTeam(door.location) @@ -562,29 +542,25 @@ class CastleWarsListeners : InteractionListener { return@on true } val thievingLevel = player.skills.getLevel(Skills.THIEVING) - if (thievingLevel < CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL) { - sendMessage( - player, - "You need a Thieving level of ${CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL} to pick this lock." - ) - return@on true - } - lock(player, CastleWarsConstants.BACKDOOR_PICKLOCK_TICKS) animate(player, 2246) sendMessage(player, "You attempt to pick the lock...") val successChance = getBackdoorPicklockSuccessChance(thievingLevel) - runTask(player, CastleWarsConstants.BACKDOOR_PICKLOCK_TICKS) { - if (RandomFunction.randomDouble(1.0, 100.0) <= successChance) { - openBackdoor(player, door) - sendMessage(player, "You successfully pick the lock!") - } else { - sendMessage(player, "You fail to pick the lock.") - } + if (RandomFunction.randomDouble(1.0, 100.0) <= successChance) { + openBackdoor(player, door) + sendMessage(player, "You successfully pick the lock!") + } else { + sendMessage(player, "You fail to pick the lock.") } return@on true } + on(IntType.PLAYER, "Steal from") { player, node -> + val carrier = node as? Player ?: return@on false + CastleWarsGameState.stealFlagFromTeammate(player, carrier) + return@on true + } + on(CastleWars.backdoorOpenIds, IntType.SCENERY, "Close", "Lock") { player, node -> val door = node.asScenery() val doorTeam = if (door.id == CastleWars.saradominBackdoorOpen) CastleWarsTeam.SARADOMIN @@ -905,8 +881,7 @@ class CastleWarsListeners : InteractionListener { } private fun passEnergyBarrier( - player: Player, - barrier: Scenery + player: Player, barrier: Scenery ) { val currentBarrier = RegionManager.getObject(barrier.location.z, barrier.location.x, barrier.location.y, barrier.id) @@ -1042,10 +1017,9 @@ class CastleWarsListeners : InteractionListener { return equipmentSlot != EquipmentSlot.HEAD && equipmentSlot != EquipmentSlot.CAPE } val itemName = definition.name.lowercase() - return isRune(itemName) || - definition.hasAction("drink") || - itemName.contains("potion") || - isHunterButterfly(itemName) + return isRune(itemName) || definition.hasAction("drink") || itemName.contains("potion") || isHunterButterfly( + itemName + ) } private fun isRune(itemName: String): Boolean { @@ -1053,11 +1027,9 @@ class CastleWarsListeners : InteractionListener { } 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") + 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? { diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsOverlay.kt b/Server/src/main/content/minigame/castlewars/CastleWarsOverlay.kt index 460a208b9..f34bc805c 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsOverlay.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsOverlay.kt @@ -1,6 +1,6 @@ package content.minigame.castlewars -import core.api.* +import core.api.setVarp import core.game.node.entity.player.Player /** diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsShutdownHandler.kt b/Server/src/main/content/minigame/castlewars/CastleWarsShutdownHandler.kt index 48584b005..86dfcf41b 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsShutdownHandler.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsShutdownHandler.kt @@ -1,12 +1,12 @@ package content.minigame.castlewars +import content.minigame.castlewars.areas.CastleWarsGameArea +import content.minigame.castlewars.areas.CastleWarsRespawnArea +import content.minigame.castlewars.areas.CastleWarsWaitingArea import core.api.ShutdownListener import core.game.node.entity.player.Player import core.game.world.map.Location import core.game.world.map.RegionManager -import content.minigame.castlewars.areas.CastleWarsGameArea -import content.minigame.castlewars.areas.CastleWarsRespawnArea -import content.minigame.castlewars.areas.CastleWarsWaitingArea /** * Handles Castle Wars cleanup when the server shuts down. @@ -23,8 +23,7 @@ class CastleWarsShutdownHandler : ShutdownListener { allTrackedPlayers += CastleWarsWaitingArea.waitingZamorakPlayers val respawnBorders = arrayOf( - CastleWarsRespawnArea.saradominRespawnRoom, - CastleWarsRespawnArea.zamorakRespawnRoom + CastleWarsRespawnArea.saradominRespawnRoom, CastleWarsRespawnArea.zamorakRespawnRoom ) for (border in respawnBorders) { val centerX = (border.southWestX + border.northEastX) / 2 diff --git a/Server/src/main/content/minigame/castlewars/CatapultConfig.kt b/Server/src/main/content/minigame/castlewars/CatapultConfig.kt index 6a899a88b..286dfa969 100644 --- a/Server/src/main/content/minigame/castlewars/CatapultConfig.kt +++ b/Server/src/main/content/minigame/castlewars/CatapultConfig.kt @@ -62,12 +62,16 @@ object CatapultConfig { * Enum representing the buttons on the catapult interface. */ enum class CatapultButton(val buttonId: Int, val deltaX: Int, val deltaY: Int) { - LEFT(CatapultConfig.BUTTON_LEFT, -1, 0), - RIGHT(CatapultConfig.BUTTON_RIGHT, 1, 0), - UP(CatapultConfig.BUTTON_UP, 0, 1), - DOWN(CatapultConfig.BUTTON_DOWN, 0, -1), - FIRE(CatapultConfig.BUTTON_FIRE, 0, 0), - CLOSE(CatapultConfig.BUTTON_CLOSE, 0, 0), + LEFT(CatapultConfig.BUTTON_LEFT, -1, 0), RIGHT(CatapultConfig.BUTTON_RIGHT, 1, 0), UP( + CatapultConfig.BUTTON_UP, + 0, + 1 + ), + DOWN(CatapultConfig.BUTTON_DOWN, 0, -1), FIRE(CatapultConfig.BUTTON_FIRE, 0, 0), CLOSE( + CatapultConfig.BUTTON_CLOSE, + 0, + 0 + ), UNKNOWN(-1, 0, 0); companion object { @@ -116,8 +120,7 @@ enum class CatapultTeam { */ data class Coordinate(val x: Int, val y: Int) { fun clamp(): Coordinate = Coordinate( - x.coerceIn(0, CatapultConfig.MAX_COORDINATE), - y.coerceIn(0, CatapultConfig.MAX_COORDINATE) + x.coerceIn(0, CatapultConfig.MAX_COORDINATE), y.coerceIn(0, CatapultConfig.MAX_COORDINATE) ) fun withDelta(deltaX: Int, deltaY: Int): Coordinate = Coordinate(x + deltaX, y + deltaY) diff --git a/Server/src/main/content/minigame/castlewars/CatapultFiringHandler.kt b/Server/src/main/content/minigame/castlewars/CatapultFiringHandler.kt index 343a17405..8359d4b68 100644 --- a/Server/src/main/content/minigame/castlewars/CatapultFiringHandler.kt +++ b/Server/src/main/content/minigame/castlewars/CatapultFiringHandler.kt @@ -101,8 +101,7 @@ class CatapultFiringHandler { } private fun dealImpactDamage(attacker: Player, targetLocation: Location) { - RegionManager.getLocalPlayers(targetLocation, IMPACT_RADIUS) - .filter { isValidTarget(it, attacker) } + RegionManager.getLocalPlayers(targetLocation, IMPACT_RADIUS).filter { isValidTarget(it, attacker) } .forEach { target -> val damage = RandomFunction.random(MIN_DAMAGE, MAX_DAMAGE) target.impactHandler.manualHit(attacker, damage, ImpactHandler.HitsplatType.NORMAL) diff --git a/Server/src/main/content/minigame/castlewars/CatapultInterfaceRenderer.kt b/Server/src/main/content/minigame/castlewars/CatapultInterfaceRenderer.kt index bf4914e11..f7f80748d 100644 --- a/Server/src/main/content/minigame/castlewars/CatapultInterfaceRenderer.kt +++ b/Server/src/main/content/minigame/castlewars/CatapultInterfaceRenderer.kt @@ -40,8 +40,7 @@ class CatapultInterfaceRenderer { val screenY = calculateScreenY(coord.y) PacketRepository.send( - RepositionChild::class.java, - ChildPositionContext(player, INTERFACE_ID, MARKER_CHILD_ID, screenX, screenY) + RepositionChild::class.java, ChildPositionContext(player, INTERFACE_ID, MARKER_CHILD_ID, screenX, screenY) ) } @@ -93,11 +92,7 @@ class CatapultInterfaceRenderer { } player.packetDispatch.sendItemOnInterface(itemId, 1, INTERFACE_ID, childId) player.packetDispatch.sendAngleOnInterface( - INTERFACE_ID, - childId, - child.modelZoom, - child.modelXAngle, - child.modelYAngle + INTERFACE_ID, childId, child.modelZoom, child.modelXAngle, child.modelYAngle ) } diff --git a/Server/src/main/content/minigame/castlewars/Lanthus.kt b/Server/src/main/content/minigame/castlewars/Lanthus.kt index 9e9fb54a7..4be45c92a 100644 --- a/Server/src/main/content/minigame/castlewars/Lanthus.kt +++ b/Server/src/main/content/minigame/castlewars/Lanthus.kt @@ -2,11 +2,7 @@ package content.minigame.castlewars import content.minigame.castlewars.areas.CastleWarsGameArea import content.minigame.castlewars.areas.CastleWarsWaitingArea -import core.api.TickListener -import core.api.findNPC -import core.api.openDialogue -import core.api.sendChat -import core.api.sendMessage +import core.api.* import core.game.dialogue.DialogueFile import core.game.dialogue.FacialExpression import core.game.interaction.IntType @@ -16,48 +12,53 @@ import core.tools.END_DIALOGUE import org.rs09.consts.Items import org.rs09.consts.NPCs -class Lanthus: InteractionListener, TickListener { +class Lanthus : InteractionListener, TickListener { override fun defineListeners() { on(NPCs.LANTHUS_1526, IntType.NPC, "talk-to") { player, npcNode -> val lanthusNpc = npcNode.asNpc() - openDialogue(player, object : DialogueFile(){ + openDialogue(player, object : DialogueFile() { override fun handle(componentID: Int, buttonID: Int) { - when(stage) { + when (stage) { 0 -> npcl(FacialExpression.FRIENDLY, "Good day, how may I help you?").also { stage = 2 } 2 -> options( "What is this place?", "What do you have for trade?", "Do you have a manual? I'd like to learn how to play!" ).also { stage++ } + 3 -> when (buttonID) { 1 -> player("What is this place?").also { stage = 4 } 2 -> lanthusNpc.openShop(player).also { stage = END_DIALOGUE } 3 -> player("Do you have a manual? I'd like to learn how to play!").also { stage = 50 } } + 4 -> npcl( FacialExpression.FRIENDLY, "This is the great Castle Wars arena! Here you can fight for the glory of Saradomin or Zamorak." ).also { stage++ } + 5 -> options( - "Really, how do I do that?", - "Are there any rules?", - "What can I win?" + "Really, how do I do that?", "Are there any rules?", "What can I win?" ).also { stage++ } + 6 -> when (buttonID) { 1 -> player("Really, how do I do that?").also { stage = 7 } 2 -> player("Are there any rules?").also { stage = 80 } 3 -> player("What can I win?").also { stage = 90 } } + 7 -> npcl( FacialExpression.FRIENDLY, "Easy, you just step through one of the three portals. To join Zamorak, pass through the red portal. To join Saradomin, pass through the blue portal. If you don't mind then pass through the green portal.", - ).also { stage ++ } + ).also { stage++ } + 8 -> options( "Are there any rules?", "What can I win?", "What do you have for trade?", "Do you have a manual? I'd like to learn how to play!" ).also { stage++ } + 9 -> when (buttonID) { 1 -> player("Are there any rules?").also { stage = 80 } 2 -> player("What can I win?").also { stage = 90 } @@ -66,31 +67,37 @@ class Lanthus: InteractionListener, TickListener { } 80 -> npcl( - FacialExpression.FRIENDLY, "Of course, there are always rules. Firstly you can't wear a cape as you enter the portal, you'll be given your team colours to wear while in the arena. You're also prohibited from taking non-combat related items in") - .also { stage ++ } + FacialExpression.FRIENDLY, + "Of course, there are always rules. Firstly you can't wear a cape as you enter the portal, you'll be given your team colours to wear while in the arena. You're also prohibited from taking non-combat related items in" + ).also { stage++ } 81 -> npcl( - FacialExpression.FRIENDLY, " with you. So you should only have equipment, potions, and runes on you. Secondly, attacking your own team or your team's defences isn't allowed. You don't want to be angering" - ).also { stage ++ } + FacialExpression.FRIENDLY, + " with you. So you should only have equipment, potions, and runes on you. Secondly, attacking your own team or your team's defences isn't allowed. You don't want to be angering" + ).also { stage++ } 82 -> npcl( - FacialExpression.FRIENDLY, "your patron god, do you? Other than that, just have fun and enjoy it!" - ).also { stage ++ } + FacialExpression.FRIENDLY, + "your patron god, do you? Other than that, just have fun and enjoy it!" + ).also { stage++ } 50 -> npcl( - FacialExpression.FRIENDLY, - "Sure, here you go.").also { stage = END_DIALOGUE }.also { player.inventory.add(Item(Items.CASTLEWARS_MANUAL_4055)) } + FacialExpression.FRIENDLY, "Sure, here you go." + ).also { stage = END_DIALOGUE } + .also { player.inventory.add(Item(Items.CASTLEWARS_MANUAL_4055)) } - 83 -> player("Great! Oh, how do I win the game?").also { stage ++ } + 83 -> player("Great! Oh, how do I win the game?").also { stage++ } 84 -> npcl( FacialExpression.FRIENDLY, "The aim is to get into your opponents' castle and take their team standard. Then bring that back and capture it on your team's standard." - ).also { stage ++ } + ).also { stage++ } + 85 -> options( "What can I win?", "What do you have to trade?", "Do you have a manual? I'd like to learn how to play!" - ).also { stage ++ } + ).also { stage++ } + 86 -> when (buttonID) { 1 -> player("What can I win?").also { stage = 90 } 2 -> lanthusNpc.openShop(player).also { stage = END_DIALOGUE } @@ -100,12 +107,14 @@ class Lanthus: InteractionListener, TickListener { 90 -> npcl( FacialExpression.FRIENDLY, "Players on the winning team will receive 2 Castle Wars Tickets which you can trade back to me for other items. In the event of a draw every player will get 1 ticket. Losers receive no tickets." - ).also { stage ++ } + ).also { stage++ } + 91 -> options( "Are there any rules?", "What do you have to trade?", "Do you have a manual? I'd like to learn how to play!" - ).also { stage ++ } + ).also { stage++ } + 92 -> when (buttonID) { 1 -> player("Are there any rules?").also { stage = 80 } 2 -> lanthusNpc.openShop(player).also { stage = END_DIALOGUE } @@ -141,8 +150,7 @@ class Lanthus: InteractionListener, TickListener { return } - val minutesLeft = - (countdown + CastleWarsConstants.TICKS_PER_MINUTE - 1) / CastleWarsConstants.TICKS_PER_MINUTE + 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) { diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt index 5ac678922..a23734ff5 100644 --- a/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt @@ -64,9 +64,11 @@ abstract class CastleWarsArea : MapArea, LogoutListener, InteractionListener { override fun defineListeners() { onUnequip(intArrayOf(CastleWars.saradominTeamHoodedCloak, CastleWars.zamorakTeamHoodedCloak)) { player, _ -> - val allCastleWarsBorders = CastleWarsGameArea.areaBorders.toList() + - CastleWarsWaitingArea.areaBorders.toList() + - listOf(CastleWarsRespawnArea.saradominRespawnRoom, CastleWarsRespawnArea.zamorakRespawnRoom) + val allCastleWarsBorders = + CastleWarsGameArea.areaBorders.toList() + CastleWarsWaitingArea.areaBorders.toList() + listOf( + CastleWarsRespawnArea.saradominRespawnRoom, + CastleWarsRespawnArea.zamorakRespawnRoom + ) for (border in allCastleWarsBorders) { if (border.insideBorder(player)) { sendMessage(player, "You can't remove your team's colours") diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt index a93b4c8c3..d5d1d86d8 100644 --- a/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt @@ -64,10 +64,7 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener { val zamorakPlayers = mutableSetOf() data class LogoutRecord( - val player: Player, - val team: CastleWarsTeam, - val logoutTick: Int, - val logoutLocation: Location + 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. */ @@ -94,10 +91,14 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener { ticksLeftInGame = CastleWars.gameTimeMinutes * CastleWarsConstants.TICKS_PER_MINUTE saradominPlayers.forEach { player -> player.properties.teleportLocation = CastleWarsRespawnArea.saradominRespawnRoom.randomWalkableLoc + CastleWarsGameState.activateBraceletIfEquipped(player, CastleWarsTeam.SARADOMIN) + CastleWarsGameState.setActiveGamePlayerOptions(player) setAttribute(player, CastleWarsConstants.ATTR_JOIN_TICK, getWorldTicks()) } zamorakPlayers.forEach { player -> player.properties.teleportLocation = CastleWarsRespawnArea.zamorakRespawnRoom.randomWalkableLoc + CastleWarsGameState.activateBraceletIfEquipped(player, CastleWarsTeam.ZAMORAK) + CastleWarsGameState.setActiveGamePlayerOptions(player) setAttribute(player, CastleWarsConstants.ATTR_JOIN_TICK, getWorldTicks()) } val allPlayers = saradominPlayers + zamorakPlayers @@ -201,6 +202,7 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener { zamorakPlayers.remove(player) CastleWarsGameState.clearHintIconForPlayer(player) player.interaction.remove(Option._P_ATTACK) + player.interaction.remove(Option._P_STEAL_FROM) if (offerReplacement) { fillVacancy(leavingTeam) } @@ -283,9 +285,7 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener { } val elapsedMillis = System.currentTimeMillis() - savedLogoutTime val logoutLocation = getAttribute( - player, - CastleWarsConstants.ATTR_LOGOUT_LOCATION, - record?.logoutLocation ?: player.location + player, CastleWarsConstants.ATTR_LOGOUT_LOCATION, record?.logoutLocation ?: player.location ) if (ticksLeftInGame > 0 && elapsedMillis < CastleWarsConstants.LOGOUT_GRACE_PERIOD_MILLIS) { player.location = Location.create(logoutLocation) @@ -372,14 +372,15 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, LoginListener { log(this::class.java, Log.WARN, "${player.name} entered Castle Wars but is NOT on a team?!") } } - player.interaction.set(Option._P_ATTACK) + CastleWarsGameState.setActiveGamePlayerOptions(player) } override fun exitArea(player: Player) { super.exitArea(player) - val stillInCastleWars = areaBorders.any { it.insideBorder(player.location) } || - CastleWarsRespawnArea.saradominRespawnRoom.insideBorder(player.location) || - CastleWarsRespawnArea.zamorakRespawnRoom.insideBorder(player.location) + val stillInCastleWars = + areaBorders.any { it.insideBorder(player.location) } || CastleWarsRespawnArea.saradominRespawnRoom.insideBorder( + player.location + ) || CastleWarsRespawnArea.zamorakRespawnRoom.insideBorder(player.location) if (!stillInCastleWars) { removePlayerFromGame(player) } diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsRespawnArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsRespawnArea.kt index 1b3bdc60e..2fd8dac7d 100644 --- a/Server/src/main/content/minigame/castlewars/areas/CastleWarsRespawnArea.kt +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsRespawnArea.kt @@ -3,7 +3,9 @@ package content.minigame.castlewars.areas import content.minigame.castlewars.CastleWars import content.minigame.castlewars.CastleWarsConstants import content.minigame.castlewars.CastleWarsGameState -import core.api.* +import core.api.TickListener +import core.api.getWorldTicks +import core.api.sendMessage import core.game.node.entity.Entity import core.game.node.entity.player.Player import core.game.node.item.Item @@ -47,6 +49,7 @@ class CastleWarsRespawnArea : CastleWarsArea(), TickListener { player.equipment.replace(Item(CastleWars.zamorakTeamHoodedCloak), 1) zamorakPlayersInRespawnRoom[player] = getWorldTicks() } + CastleWarsGameState.setActiveGamePlayerOptions(player) } override fun tick() { @@ -62,9 +65,7 @@ class CastleWarsRespawnArea : CastleWarsArea(), TickListener { private fun removePlayersOverSpawnLimit(players: MutableMap) { val currentTick = getWorldTicks() val expiredPlayers = players.filter { (player, entryTick) -> - player.isActive && - player.session != null && - currentTick - entryTick >= CastleWarsConstants.LOGOUT_GRACE_PERIOD_TICKS + player.isActive && player.session != null && currentTick - entryTick >= CastleWarsConstants.LOGOUT_GRACE_PERIOD_TICKS }.keys for (player in expiredPlayers) { players.remove(player) diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt index 1d954bb99..bacd07a61 100644 --- a/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt @@ -27,8 +27,7 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener { internal var gameStartCountdown = -1 fun hasMinimumPlayers(): Boolean { - return waitingSaradominPlayers.size >= CastleWarsConstants.MIN_PLAYERS_PER_TEAM && - waitingZamorakPlayers.size >= CastleWarsConstants.MIN_PLAYERS_PER_TEAM + return waitingSaradominPlayers.size >= CastleWarsConstants.MIN_PLAYERS_PER_TEAM && waitingZamorakPlayers.size >= CastleWarsConstants.MIN_PLAYERS_PER_TEAM } } @@ -40,8 +39,7 @@ 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) * 60 * 2) ) // Set team attribute and equip the hooded cloak on the entity based on which waiting room they're in @@ -54,8 +52,10 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener { } var transformed = false - if (player.attributes[CastleWars.portalAttribute] == CastleWars.guthixName - && (hasGodItem(player, God.ZAMORAK) || hasGodItem(player, God.SARADOMIN)) + if (player.attributes[CastleWars.portalAttribute] == CastleWars.guthixName && (hasGodItem( + player, + God.ZAMORAK + ) || hasGodItem(player, God.SARADOMIN)) ) { // https://www.youtube.com/watch?v=Vg3Om-jZNIY // Turn the player into a sheep @@ -67,8 +67,10 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener { ) transformed = true } - if (player.attributes[CastleWars.portalAttribute] == CastleWars.zamorakName - && (hasGodItem(player, God.SARADOMIN) || hasGodItem(player, God.GUTHIX)) + if (player.attributes[CastleWars.portalAttribute] == CastleWars.zamorakName && (hasGodItem( + player, + God.SARADOMIN + ) || hasGodItem(player, God.GUTHIX)) ) { // https://youtu.be/hmF-CU9OEY4?t=63 // Turn the player into an imp! @@ -80,8 +82,10 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener { ) transformed = true } - if (player.attributes[CastleWars.portalAttribute] == CastleWars.saradominName - && (hasGodItem(player, God.ZAMORAK) || hasGodItem(player, God.GUTHIX)) + if (player.attributes[CastleWars.portalAttribute] == CastleWars.saradominName && (hasGodItem( + player, + God.ZAMORAK + ) || hasGodItem(player, God.GUTHIX)) ) { // https://www.youtube.com/watch?v=Vg3Om-jZNIY // Turn the player into a rabbit! @@ -120,8 +124,8 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener { val nextStart: Int if (CastleWarsGameArea.ticksLeftInGame > 0) { gameStartCountdown = -1 - nextStart = CastleWarsGameArea.ticksLeftInGame + - CastleWars.gameCooldownMinutes * CastleWarsConstants.TICKS_PER_MINUTE + nextStart = + CastleWarsGameArea.ticksLeftInGame + CastleWars.gameCooldownMinutes * CastleWarsConstants.TICKS_PER_MINUTE } else { if (gameStartCountdown < 0) { gameStartCountdown = CastleWarsConstants.WAITING_ROOM_COUNTDOWN_TICKS @@ -131,8 +135,7 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener { } for (player in waitingSaradominPlayers + waitingZamorakPlayers) { CastleWarsOverlay.sendLobbyUpdate( - player, - ticksToDisplayMinutes(nextStart) + player, ticksToDisplayMinutes(nextStart) ) } if (CastleWarsGameArea.ticksLeftInGame <= 0 && nextStart <= 0) { diff --git a/Server/src/main/core/game/interaction/Option.java b/Server/src/main/core/game/interaction/Option.java index 8c99a1b57..03b68e1cc 100644 --- a/Server/src/main/core/game/interaction/Option.java +++ b/Server/src/main/core/game/interaction/Option.java @@ -32,6 +32,8 @@ public final class Option { public static final Option _P_PICKPOCKET = new Option("Pickpocket", 4); + public static final Option _P_STEAL_FROM = new Option("Steal from", 4); + public static final Option _P_EXAMINE = new Option("Examine", 7); diff --git a/Server/src/main/core/game/node/entity/combat/CombatSwingHandler.kt b/Server/src/main/core/game/node/entity/combat/CombatSwingHandler.kt index d4324b233..908a07394 100644 --- a/Server/src/main/core/game/node/entity/combat/CombatSwingHandler.kt +++ b/Server/src/main/core/game/node/entity/combat/CombatSwingHandler.kt @@ -23,6 +23,7 @@ import core.game.world.update.flag.context.Animation import core.tools.RandomFunction import core.game.system.config.ItemConfigParser import core.tools.Log +import content.minigame.castlewars.CastleWarsGameState import org.rs09.consts.Sounds import java.util.* import kotlin.math.floor @@ -585,6 +586,9 @@ abstract class CombatSwingHandler(var type: CombatStyle?) { if (player.equipment[3] != null && player.equipment[3].id == 14726 && state.style == CombatStyle.MAGIC) { hit += (hit.toDouble() * 0.15).toInt() } + if (victim is Player) { + hit = CastleWarsGameState.applyBraceletDamageBoost(player, victim.asPlayer(), hit) + } } if (attacker is Familiar && victim is Player) { if (victim.prayer[PrayerType.PROTECT_FROM_SUMMONING]) { diff --git a/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt b/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt index d999cc05a..25792e479 100644 --- a/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt +++ b/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt @@ -1,11 +1,11 @@ package content.minigame.castlewars import TestUtils -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.* +import core.game.interaction.Option import core.game.node.entity.skill.Skills import core.game.global.action.EquipHandler import core.game.interaction.IntType @@ -544,17 +544,12 @@ class CastleWarsTests { } @Test - fun backdoorPicklockMatchesKnightOfArdougneDifficulty() { - TestUtils.getMockPlayer("cwthief").use { player -> - player.skills.setStaticLevel(Skills.THIEVING, CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL) - player.skills.setLevel(Skills.THIEVING, CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL) - - assertEquals(Pickpockets.KNIGHT_OF_ADROUGNE.requiredLevel, CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL) - assertEquals( - Pickpockets.KNIGHT_OF_ADROUGNE.getSuccessChance(player), - getBackdoorPicklockSuccessChance(player.skills.getLevel(Skills.THIEVING)) - ) - } + fun backdoorPicklockUsesLinearThievingCurveFromLevelOneToNinetyNine() { + assertEquals(6.3, getBackdoorPicklockSuccessChance(1), 0.0001) + assertEquals(53.15, getBackdoorPicklockSuccessChance(50), 0.01) + assertEquals(100.0, getBackdoorPicklockSuccessChance(99), 0.0001) + assertEquals(6.3, getBackdoorPicklockSuccessChance(0), 0.0001) + assertEquals(100.0, getBackdoorPicklockSuccessChance(120), 0.0001) } @Test @@ -620,6 +615,140 @@ class CastleWarsTests { } } + @Test + fun playerPlacedBarricadesArePoisonImmune() { + TestUtils.getMockPlayer("cwpoisoner").use { player -> + val barricade = CastleWarsBarricadeNPC.spawn(Location.create(2400, 3100, 0), CastleWarsTeam.SARADOMIN) + + applyPoison(barricade, player, 10) + + assertTrue(barricade.isPoisonImmune) + assertFalse(isPoisoned(barricade)) + barricade.clear() + } + } + + @Test + fun matchStartActivatesBraceletEffectsAndStealFromOption() { + TestUtils.getMockPlayer("cwbrace1").use { braceletPlayer -> + TestUtils.getMockPlayer("cwbrace2").use { sara2 -> + TestUtils.getMockPlayer("cwbracez1").use { zam1 -> + TestUtils.getMockPlayer("cwbracez2").use { zam2 -> + braceletPlayer.equipment.replace(Item(Items.CASTLEWAR_BRACE3_11079), EquipmentSlot.HANDS.ordinal) + CastleWarsWaitingArea.waitingSaradominPlayers.addAll(listOf(braceletPlayer, sara2)) + CastleWarsWaitingArea.waitingZamorakPlayers.addAll(listOf(zam1, zam2)) + + CastleWarsGameArea.startGame() + + assertEquals(CastleWarsTeam.SARADOMIN, braceletPlayer.activeCastleWarsBraceletTeam()) + assertFalse(sara2.hasActiveCastleWarsBracelet()) + assertNotNull(braceletPlayer.interaction[Option._P_STEAL_FROM.index]) + assertNotNull(zam1.interaction[Option._P_STEAL_FROM.index]) + } + } + } + } + } + + @Test + fun bandagesHealTenPercentRestoreRunEnergyAndCurePoison() { + TestUtils.getMockPlayer("cwbandage").use { player -> + CastleWarsGameArea.ticksLeftInGame = 100 + CastleWarsGameArea.saradominPlayers.add(player) + player.skills.setStaticLevel(Skills.HITPOINTS, 99) + player.skills.lifepoints = 50 + player.settings.runEnergy = 20.0 + applyPoison(player, player, 10) + player.inventory.add(Item(CastleWars.cwBandages)) + + InteractionListeners.run( + CastleWars.cwBandages, + IntType.ITEM, + "Heal", + player, + player.inventory[0] + ) + + assertEquals(59, player.skills.lifepoints) + assertEquals(50.0, player.settings.runEnergy) + assertFalse(isPoisoned(player)) + assertFalse(player.inventory.contains(CastleWars.cwBandages, 1)) + } + } + + @Test + fun activeBraceletBandagesHealTeammatesForFifteenPercent() { + TestUtils.getMockPlayer("cwhealer").use { healer -> + TestUtils.getMockPlayer("cwpatient").use { patient -> + CastleWarsGameArea.ticksLeftInGame = 100 + CastleWarsGameArea.saradominPlayers.addAll(listOf(healer, patient)) + setAttribute(healer, CastleWarsConstants.ATTR_ACTIVE_BRACELET_TEAM, CastleWarsTeam.SARADOMIN.name) + patient.skills.setStaticLevel(Skills.HITPOINTS, 99) + patient.skills.lifepoints = 50 + patient.settings.runEnergy = 20.0 + applyPoison(patient, patient, 10) + healer.inventory.add(Item(CastleWars.cwBandages)) + val handler = InteractionListeners.get(-1, CastleWars.cwBandages, IntType.PLAYER.ordinal) + assertNotNull(handler) + + assertTrue(handler!!.invoke(healer, healer.inventory[0], patient)) + + assertEquals(64, patient.skills.lifepoints) + assertEquals(50.0, patient.settings.runEnergy) + assertFalse(isPoisoned(patient)) + assertFalse(healer.inventory.contains(CastleWars.cwBandages, 1)) + } + } + } + + @Test + fun activeBraceletDamageBoostOnlyAppliesAgainstCarrierOfWearersTeamFlag() { + TestUtils.getMockPlayer("cwattacker").use { attacker -> + TestUtils.getMockPlayer("cwcarrier").use { carrier -> + TestUtils.getMockPlayer("cwothercarrier").use { otherCarrier -> + CastleWarsGameArea.ticksLeftInGame = 100 + CastleWarsGameArea.saradominPlayers.addAll(listOf(attacker, otherCarrier)) + CastleWarsGameArea.zamorakPlayers.add(carrier) + setAttribute(attacker, CastleWarsConstants.ATTR_ACTIVE_BRACELET_TEAM, CastleWarsTeam.SARADOMIN.name) + + assertTrue(CastleWarsGameState.pickUpFlag(carrier, CastleWarsTeam.SARADOMIN)) + assertEquals(2, CastleWarsGameState.getBraceletBonusDamage(attacker, carrier, 10)) + assertEquals(12, CastleWarsGameState.applyBraceletDamageBoost(attacker, carrier, 10)) + + CastleWarsGameState.dropFlag(carrier) + assertTrue(CastleWarsGameState.pickUpFlag(otherCarrier, CastleWarsTeam.ZAMORAK)) + assertEquals(0, CastleWarsGameState.getBraceletBonusDamage(attacker, otherCarrier, 10)) + + CastleWarsGameState.clearActiveCastleWarsBracelet(attacker) + assertEquals(0, CastleWarsGameState.getBraceletBonusDamage(attacker, carrier, 10)) + } + } + } + } + + @Test + fun teammatesCanStealEnemyFlagAfterCarrierHasHeldItForThreeMinutes() { + TestUtils.getMockPlayer("cwcarrier").use { carrier -> + TestUtils.getMockPlayer("cwstealer").use { stealer -> + CastleWarsGameArea.ticksLeftInGame = 1000 + CastleWarsGameArea.saradominPlayers.addAll(listOf(carrier, stealer)) + + assertTrue(CastleWarsGameState.pickUpFlag(carrier, CastleWarsTeam.ZAMORAK)) + assertFalse(CastleWarsGameState.stealFlagFromTeammate(stealer, carrier)) + + setAttribute( + carrier, + CastleWarsConstants.ATTR_FLAG_PICKUP_TICK, + getWorldTicks() - CastleWarsConstants.FLAG_STEAL_TIMER_TICKS + ) + + assertTrue(CastleWarsGameState.stealFlagFromTeammate(stealer, carrier)) + assertFalse(CastleWarsGameState.isCarryingFlag(carrier)) + assertEquals(CastleWarsTeam.ZAMORAK, CastleWarsGameState.getCarriedFlag(stealer)) + } + } + } + private fun assertSubstitutionDialogue(player: Player) { assertTrue(player.dialogueInterpreter.dialogue.file is CastleWarsSubstitutionDialogue) }