mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-24 20:05:13 -06:00
Replaced silly PvP scaffolding with OvenBread's actual engineer solution
This commit is contained in:
parent
dcc4fe2ccf
commit
caa2d6dab6
12 changed files with 90 additions and 439 deletions
|
|
@ -1,33 +0,0 @@
|
|||
package content.global.handlers
|
||||
|
||||
import core.api.LogoutListener
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.link.PvPStateAudit
|
||||
|
||||
/**
|
||||
* Global logout handler that ensures PvP state is always cleaned up on logout.
|
||||
* This is a safety net to prevent PvP state leakage (Falador Massacre-style bugs).
|
||||
*
|
||||
* Even if a minigame's leave() or logout() method fails to clean up properly,
|
||||
* this handler ensures the player's PvP flags are reset before they rejoin.
|
||||
*
|
||||
* This works alongside PvPStateAudit (which runs periodically) to provide
|
||||
* comprehensive protection against PvP state leaking out of designated areas.
|
||||
*
|
||||
* @see core.game.node.entity.player.link.PvPStateAudit
|
||||
* @see core.api.PvPZone
|
||||
*/
|
||||
class PvPLogoutHandler : LogoutListener {
|
||||
|
||||
/**
|
||||
* Called when a player logs out.
|
||||
* Always cleans up PvP state as a final safety measure.
|
||||
*/
|
||||
override fun logout(player: Player) {
|
||||
// Always clean up PvP state on logout as final safety net.
|
||||
// This ensures that even if a minigame's leave() method fails,
|
||||
// the player won't have PvP flags enabled when they log back in.
|
||||
PvPStateAudit.cleanupPvPState(player)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,10 +51,6 @@ abstract class CastleWarsArea : MapArea, LogoutListener, InteractionListener {
|
|||
// Remove teleblock
|
||||
removeTimer(player, "teleblock")
|
||||
|
||||
// TODO: PvPStateAudit blows, has gotta go and actual safe handling of PvP generalized to cover current inauthentic deep wildy,
|
||||
// future authentic minigames and that wont result in 7000 exploits that ruin the server (yes. thats a good idea. who said that)
|
||||
// PvPStateAudit.cleanupPvPState(player)
|
||||
|
||||
player.equipment.removeAll(CastleWars.CW_ITEMS_TO_REMOVE)
|
||||
player.inventory.removeAll(CastleWars.CW_ITEMS_TO_REMOVE)
|
||||
(player.familiarManager.familiar as? BurdenBeast)?.container?.removeAll(CastleWars.CW_ITEMS_TO_REMOVE)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import core.game.component.Component
|
|||
import core.game.event.EventHook
|
||||
import core.game.event.InterfaceCloseEvent
|
||||
import core.game.interaction.Option
|
||||
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.item.Item
|
||||
import core.game.world.map.Location
|
||||
|
|
@ -20,7 +22,7 @@ import org.rs09.consts.Components
|
|||
/**
|
||||
* Handles the Castle Wars game map
|
||||
*/
|
||||
class CastleWarsGameArea : CastleWarsArea(), TickListener, PvPZone {
|
||||
class CastleWarsGameArea : CastleWarsArea(), TickListener {
|
||||
|
||||
companion object {
|
||||
private val saradominStandardFloor: ZoneBorders =
|
||||
|
|
@ -342,46 +344,21 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener, PvPZone {
|
|||
return handleDeath(entity, killer)
|
||||
}
|
||||
|
||||
// ==================== PvPZone Implementation ==================== TODO: PvPZone general review... it might be good but need to triple-check
|
||||
|
||||
/**
|
||||
* Check if attacker can attack victim in this zone.
|
||||
* Only allows attacks between players on opposite teams.
|
||||
* Uses self-healing team detection based on equipped cloak to ensure
|
||||
* players are properly tracked even if they weren't added to sets correctly.
|
||||
* @param attacker The player initiating the attack
|
||||
* @param victim The player being attacked
|
||||
* @return true if players are on opposite teams, false otherwise
|
||||
*/
|
||||
override fun canAttackPlayer(attacker: Player, victim: Player): Boolean {
|
||||
if (ticksLeftInGame <= 0) return false
|
||||
override fun isPvpAllowed(entity: Entity, target: Node?, style: CombatStyle?, message: Boolean): Boolean? {
|
||||
val attacker = entity as? Player ?: return null
|
||||
val victim = target as? Player ?: return null
|
||||
if (ticksLeftInGame <= 0) {
|
||||
return false
|
||||
}
|
||||
val attackerTeam = CastleWarsGameState.getPlayerTeam(attacker) ?: return false
|
||||
val victimTeam = CastleWarsGameState.getPlayerTeam(victim) ?: return false
|
||||
return attackerTeam != victimTeam
|
||||
}
|
||||
|
||||
/**
|
||||
* Check combat level restrictions.
|
||||
* Castle Wars has no combat level restrictions.
|
||||
*/
|
||||
override fun checkCombatLevel(attacker: Player, victim: Player): Boolean {
|
||||
if (attackerTeam == victimTeam) {
|
||||
if (message) {
|
||||
sendMessage(attacker, "You can only attack players on the opposite team!")
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message to display when attack is blocked.
|
||||
* @return The message string
|
||||
*/
|
||||
override fun getBlockedAttackMessage(): String {
|
||||
return "You can only attack players on the opposite team!" // TODO: pretty sure this is inauthentic. seen the real one in vids, needs correction here (also does this even get called huh)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message to display when attack is blocked by combat level check.
|
||||
* @return The message string
|
||||
*/
|
||||
override fun getCombatLevelBlockedMessage(): String {
|
||||
return PvPZone.DEFAULT_COMBAT_LEVEL_MESSAGE
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package core.api
|
||||
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.combat.CombatStyle
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.zone.MapZone
|
||||
import core.game.world.map.zone.RegionZone
|
||||
|
|
@ -25,6 +27,18 @@ interface MapArea : ContentInterface {
|
|||
fun areaEnter(entity: Entity) {}
|
||||
fun areaLeave(entity: Entity, logout: Boolean) {}
|
||||
fun entityStep(entity: Entity, location: Location, lastLocation: Location) {}
|
||||
/**
|
||||
* Extends MapZone to cater to pvp, team games and all other unique targeting systems.
|
||||
* Return true/false if you want to override the controls.
|
||||
* Return null if you want the default check in MapZone. (default)
|
||||
**/
|
||||
fun isPvpAllowed(entity: Entity, target: Node?, style: CombatStyle?, message: Boolean): Boolean? { return null }
|
||||
/**
|
||||
* Extends MapZone to cater to pvp, team games and all other unique targeting systems.
|
||||
* Return true/false if you want to allow the deaths, especially false for "safe" areas.
|
||||
* Return null if you want the default check in MapZone. (default)
|
||||
**/
|
||||
fun canStartDeath(entity: Entity, killer: Entity): Boolean? { return null }
|
||||
|
||||
companion object {
|
||||
val zoneMaps = HashMap<String, MapZone>()
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
package core.api
|
||||
|
||||
import core.game.node.entity.player.Player
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Utility object providing combat level range checking for PvP zones.
|
||||
* Used by zones that implement the PvPZone interface to determine
|
||||
* if two players are within the allowed combat level range for an area.
|
||||
*
|
||||
* @see PvPZone
|
||||
*/
|
||||
object PvPCombatUtils {
|
||||
/**
|
||||
* Check if two players are within a combat level range.
|
||||
* Used for wilderness-style level restrictions where the allowed
|
||||
* combat level difference is determined by the area's level.
|
||||
*
|
||||
* @param attacker The player initiating the attack
|
||||
* @param victim The player being attacked
|
||||
* @param range The maximum allowed combat level difference
|
||||
* @return true if within allowed range, false otherwise
|
||||
*/
|
||||
@JvmStatic
|
||||
fun withinCombatRange(attacker: Player, victim: Player, range: Int): Boolean {
|
||||
val attackerLevel = attacker.properties.currentCombatLevel
|
||||
val victimLevel = victim.properties.currentCombatLevel
|
||||
return abs(attackerLevel - victimLevel) <= range
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate wilderness-style combat range based on wilderness level.
|
||||
* In the wilderness, the combat level range equals the wilderness level.
|
||||
* For example, in level 5 wilderness, you can attack players within
|
||||
* 5 combat levels of your own level.
|
||||
*
|
||||
* @param wildernessLevel The wilderness level of the area
|
||||
* @return The combat level range (same as wilderness level)
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getWildernessStyleRange(wildernessLevel: Int): Int {
|
||||
return wildernessLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if combat between two players is allowed based on wilderness-style rules.
|
||||
* Combines the wilderness level lookup with combat range checking.
|
||||
*
|
||||
* @param attacker The player initiating the attack
|
||||
* @param victim The player being attacked
|
||||
* @param wildernessLevel The wilderness level of the area
|
||||
* @return true if combat is allowed, false otherwise
|
||||
*/
|
||||
@JvmStatic
|
||||
fun checkWildernessStyleCombat(attacker: Player, victim: Player, wildernessLevel: Int): Boolean {
|
||||
val range = getWildernessStyleRange(wildernessLevel)
|
||||
return withinCombatRange(attacker, victim, range)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted message explaining why combat was blocked due to level difference.
|
||||
*
|
||||
* @param attacker The attacking player
|
||||
* @param victim The victim player
|
||||
* @param allowedRange The maximum allowed combat level difference
|
||||
* @return A descriptive message explaining the level restriction
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getCombatLevelBlockedMessage(attacker: Player, victim: Player, allowedRange: Int): String {
|
||||
val attackerLevel = attacker.properties.currentCombatLevel
|
||||
val victimLevel = victim.properties.currentCombatLevel
|
||||
val actualDifference = abs(attackerLevel - victimLevel)
|
||||
return "The combat level difference ($actualDifference) exceeds the allowed range ($allowedRange)."
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package core.api
|
||||
|
||||
import core.game.node.entity.player.Player
|
||||
|
||||
/**
|
||||
* Interface for zones that allow player-vs-player combat.
|
||||
* Zones implementing this interface must explicitly define PvP rules.
|
||||
* This provides a centralized, explicit mechanism for PvP permission checking
|
||||
* to prevent combat state leakage (e.g., Falador Massacre-style bugs).
|
||||
*/
|
||||
interface PvPZone {
|
||||
/**
|
||||
* Check if attacker can attack victim in this zone.
|
||||
* This is the primary permission check for PvP combat.
|
||||
* @param attacker The player initiating the attack
|
||||
* @param victim The player being attacked
|
||||
* @return true if attack is permitted, false otherwise
|
||||
*/
|
||||
fun canAttackPlayer(attacker: Player, victim: Player): Boolean
|
||||
|
||||
/**
|
||||
* Check if combat level restrictions apply between two players.
|
||||
* Override this for zones with level-based restrictions (e.g., wilderness level range).
|
||||
* @param attacker The player initiating the attack
|
||||
* @param victim The player being attacked
|
||||
* @return true if within allowed combat level range, false otherwise
|
||||
*/
|
||||
fun checkCombatLevel(attacker: Player, victim: Player): Boolean
|
||||
|
||||
/**
|
||||
* Get the message to display when attack is blocked by canAttackPlayer().
|
||||
* @return The message string to send to the attacking player
|
||||
*/
|
||||
fun getBlockedAttackMessage(): String
|
||||
|
||||
/**
|
||||
* Get the message to display when attack is blocked by combat level check.
|
||||
* @return The message string to send to the attacking player
|
||||
*/
|
||||
fun getCombatLevelBlockedMessage(): String
|
||||
|
||||
/**
|
||||
* Companion object providing default values for the interface methods.
|
||||
* Use these in Java implementations to get default behavior.
|
||||
*/
|
||||
companion object {
|
||||
const val DEFAULT_BLOCKED_MESSAGE = "You cannot attack that player here." // TODO: correct
|
||||
const val DEFAULT_COMBAT_LEVEL_MESSAGE = "The level difference between you and your opponent is too great."
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,11 +47,8 @@ import core.game.system.task.Pulse;
|
|||
import core.game.world.map.*;
|
||||
import core.game.world.map.build.DynamicRegion;
|
||||
import core.game.world.map.path.Pathfinder;
|
||||
import core.game.world.map.zone.RegionZone;
|
||||
import core.game.world.map.zone.ZoneRestriction;
|
||||
import core.game.world.map.zone.ZoneType;
|
||||
import core.api.MapArea;
|
||||
import core.api.PvPZone;
|
||||
import core.game.node.entity.combat.DeathContext;
|
||||
import core.game.world.update.flag.PlayerFlags;
|
||||
import core.game.world.update.flag.*;
|
||||
|
|
@ -614,7 +611,6 @@ public class Player extends Entity {
|
|||
@Override
|
||||
public void commenceDeath(Entity killer) {
|
||||
if (!isPlaying()) return;
|
||||
// TODO: review DeathContext, PvPStateAudit, PvPZone and related components. theyre all cool but also... cant this break a lot of shit? 100%. does it? uhhh... lemme check (later
|
||||
DeathContext context = new DeathContext(
|
||||
getLocation(),
|
||||
getZoneMonitor().getType(),
|
||||
|
|
@ -794,26 +790,6 @@ public class Player extends Entity {
|
|||
}
|
||||
if (entity instanceof Player) {
|
||||
Player attacker = (Player) entity;
|
||||
|
||||
// NEW: Check for PvPZone-based permission (minigames, etc.)
|
||||
PvPZone pvpZone = findCommonPvPZone(attacker, this);
|
||||
if (pvpZone != null) {
|
||||
if (!pvpZone.canAttackPlayer(attacker, this)) {
|
||||
if (message) {
|
||||
attacker.getPacketDispatch().sendMessage(pvpZone.getBlockedAttackMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!pvpZone.checkCombatLevel(attacker, this)) {
|
||||
if (message) {
|
||||
attacker.getPacketDispatch().sendMessage(pvpZone.getCombatLevelBlockedMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// LEGACY: Wilderness fallback (kept for backward compatibility)
|
||||
if (attacker.getSkullManager().isWilderness() && skullManager.isWilderness()) {
|
||||
if (!GameWorld.getSettings().getWild_pvp_enabled())
|
||||
return false;
|
||||
|
|
@ -821,77 +797,13 @@ public class Player extends Entity {
|
|||
return false;
|
||||
if (skullManager.hasWildernessProtection())
|
||||
return false;
|
||||
return super.isAttackable(entity, style, message);
|
||||
} else return false;
|
||||
} else if (!getZoneMonitor().isPvPable(entity, this, style, message)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.isAttackable(entity, style, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a PvPZone that both the attacker and this player (victim) are in.
|
||||
* @param attacker The attacking player
|
||||
* @param victim The player being attacked (this)
|
||||
* @return The common PvPZone, or null if no common zone exists
|
||||
*/
|
||||
private PvPZone findCommonPvPZone(Player attacker, Player victim) {
|
||||
// First check: Direct PvPZone implementations (e.g., ActivityPlugin subclasses)
|
||||
for (RegionZone zone : attacker.getZoneMonitor().getZones()) {
|
||||
if (zone.getZone() instanceof PvPZone) {
|
||||
for (RegionZone victimZone : victim.getZoneMonitor().getZones()) {
|
||||
if (zone.getZone() == victimZone.getZone()) {
|
||||
return (PvPZone) zone.getZone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second check: MapArea implementations that implement PvPZone
|
||||
// ClassScanner wraps MapAreas in anonymous MapZone classes, so we need to
|
||||
// look up the original MapArea from MapArea.zoneMaps
|
||||
for (RegionZone zone : attacker.getZoneMonitor().getZones()) {
|
||||
String zoneName = zone.getZone().getName();
|
||||
// MapArea zones are named with "MapArea" suffix by ClassScanner
|
||||
if (zoneName != null && zoneName.endsWith("MapArea")) {
|
||||
// Look up the original MapArea class that implements PvPZone
|
||||
for (java.util.Map.Entry<String, core.game.world.map.zone.MapZone> entry : MapArea.Companion.getZoneMaps().entrySet()) {
|
||||
if (entry.getKey().equals(zoneName) && entry.getValue() == zone.getZone()) {
|
||||
// Find the MapArea instance - we need to check all loaded ContentInterfaces
|
||||
// The MapArea interface stores zones but not the instances themselves
|
||||
// We need to iterate GameWorld's content to find the matching MapArea
|
||||
PvPZone pvpZone = findMapAreaPvPZone(zoneName, attacker, victim);
|
||||
if (pvpZone != null) {
|
||||
return pvpZone;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a MapArea that implements PvPZone and matches the zone name.
|
||||
* This is needed because ClassScanner wraps MapAreas in anonymous classes.
|
||||
*/
|
||||
private PvPZone findMapAreaPvPZone(String zoneName, Player attacker, Player victim) {
|
||||
// Check TickListeners - MapAreas often implement TickListener
|
||||
for (Object listener : GameWorld.getTickListeners()) {
|
||||
if (listener instanceof MapArea && listener instanceof PvPZone) {
|
||||
String mapAreaZoneName = listener.getClass().getSimpleName() + "MapArea";
|
||||
if (mapAreaZoneName.equals(zoneName)) {
|
||||
// Verify victim is also in this zone
|
||||
for (RegionZone victimZone : victim.getZoneMonitor().getZones()) {
|
||||
if (victimZone.getZone().getName().equals(zoneName)) {
|
||||
return (PvPZone) listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueAttack(Entity target, CombatStyle style, boolean message) {
|
||||
if (target instanceof NPC) {
|
||||
|
|
@ -913,7 +825,7 @@ public class Player extends Entity {
|
|||
return false;
|
||||
}
|
||||
return !skullManager.hasWildernessProtection();
|
||||
} else {
|
||||
} else if (!getZoneMonitor().isPvPable(this, target, style, message)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
package core.game.node.entity.player.link
|
||||
|
||||
import core.api.PvPZone
|
||||
import core.api.TickListener
|
||||
import core.api.log
|
||||
import core.game.interaction.Option
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.zone.ZoneRestriction
|
||||
import core.game.world.map.zone.impl.WildernessZone
|
||||
import core.game.world.repository.Repository
|
||||
import core.tools.Log
|
||||
|
||||
/**
|
||||
* Periodic audit that detects and fixes PvP state leakage.
|
||||
* Runs every 10 ticks (6 seconds) to catch players who have
|
||||
* PvP flags enabled outside valid PvP zones.
|
||||
*/
|
||||
class PvPStateAudit : TickListener {
|
||||
private var tickCounter = 0
|
||||
|
||||
override fun tick() {
|
||||
tickCounter++
|
||||
if (tickCounter < 10) return
|
||||
tickCounter = 0
|
||||
|
||||
for (player in Repository.players) {
|
||||
if (player == null || !player.isActive || player.isArtificial) continue
|
||||
auditPlayer(player)
|
||||
}
|
||||
}
|
||||
|
||||
private fun auditPlayer(player: Player) {
|
||||
val hasPvPEnabled = player.skullManager.isWilderness
|
||||
val inPvPZone = isInValidPvPZone(player)
|
||||
if (hasPvPEnabled && !inPvPZone) {
|
||||
log(this::class.java, Log.WARN,
|
||||
"Player ${player.username} has PvP enabled but is not in a PvP zone! " +
|
||||
"Location: ${player.location}, Cleaning up state.")
|
||||
cleanupPvPState(player)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInValidPvPZone(player: Player): Boolean {
|
||||
for (zone in player.zoneMonitor.zones) {
|
||||
if (zone.zone is PvPZone) return true
|
||||
}
|
||||
if (player.zoneMonitor.isRestricted(ZoneRestriction.PVP_ZONE)) return true
|
||||
return WildernessZone.isInZone(player)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Clean up all PvP-related state from a player.
|
||||
* Called when a player is found to have PvP flags enabled outside a PvP zone.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun cleanupPvPState(player: Player) {
|
||||
player.skullManager.isWilderness = false
|
||||
player.skullManager.isSkullCheckDisabled = false
|
||||
player.skullManager.level = 0
|
||||
player.properties.isSafeZone = false
|
||||
player.properties.isMultiZone = false
|
||||
player.interaction.remove(Option._P_ATTACK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +128,21 @@ public abstract class MapZone implements Zone {
|
|||
public boolean actionButton(Player player, int interfaceId, int buttonId, int slot, int itemId, int opcode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the entity is able to continue attacking another player within this zone.
|
||||
* This is added like continueAttack for player vs player fighting.
|
||||
*
|
||||
* @param e the attacking entity
|
||||
* @param target the target node being attacked
|
||||
* @param style the combat style being used
|
||||
* @param message whether to send a message to the player explaining why the attack cannot continue
|
||||
* @return {@code true} if the zone allows the attack to continue
|
||||
*/
|
||||
public boolean continuePvp(Entity e, Node target, CombatStyle style, boolean message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the entity is able to continue attacking the target within this zone.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package core.game.world.map.zone;
|
||||
|
||||
import core.api.MapArea;
|
||||
import core.api.PvPZone;
|
||||
import core.game.interaction.Option;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.Entity;
|
||||
|
|
@ -10,7 +8,6 @@ import core.game.node.entity.player.Player;
|
|||
import core.game.node.entity.player.link.music.MusicZone;
|
||||
import core.game.node.entity.player.link.request.RequestType;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.world.GameWorld;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.Region;
|
||||
import org.rs09.consts.Items;
|
||||
|
|
@ -150,6 +147,23 @@ public final class ZoneMonitor {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any current zone explicitly allows PvP against the target.
|
||||
*
|
||||
* @param player The attacking entity.
|
||||
* @param target The target.
|
||||
* @param style The combat style used.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public boolean isPvPable(Entity player, Node target, CombatStyle style, boolean message) {
|
||||
for (RegionZone z : zones) {
|
||||
if (z.getZone().continuePvp(player, target, style, message)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the entity is able to continue attacking the target.
|
||||
*
|
||||
|
|
@ -168,72 +182,12 @@ public final class ZoneMonitor {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (entity instanceof Player && target instanceof Player) {
|
||||
Player attacker = (Player) entity;
|
||||
Player victim = (Player) target;
|
||||
// Skip wilderness check if both players are in a PvPZone (e.g., Castle Wars, Duel Arena)
|
||||
// PvPZone handles its own attack permissions via canAttackPlayer()
|
||||
if (!isInCommonPvPZone(attacker, victim)) {
|
||||
if (!attacker.getSkullManager().isWilderness() || !victim.getSkullManager().isWilderness()) {
|
||||
if (message) {
|
||||
attacker.getPacketDispatch().sendMessage("You can only attack other players in the wilderness.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target instanceof Entity && !MapZone.checkMulti(entity, (Entity) target, message)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if both players are in a common PvPZone.
|
||||
* Used to bypass the wilderness check for minigames like Castle Wars and Duel Arena.
|
||||
*
|
||||
* @param attacker The attacking player
|
||||
* @param victim The player being attacked
|
||||
* @return true if both players share a PvPZone
|
||||
*/
|
||||
private boolean isInCommonPvPZone(Player attacker, Player victim) {
|
||||
// Check for direct PvPZone implementations (e.g., ActivityPlugin subclasses)
|
||||
for (RegionZone zone : attacker.getZoneMonitor().getZones()) {
|
||||
if (zone.getZone() instanceof PvPZone) {
|
||||
for (RegionZone victimZone : victim.getZoneMonitor().getZones()) {
|
||||
if (zone.getZone() == victimZone.getZone()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for MapArea-based PvPZones (ClassScanner wraps MapAreas in anonymous MapZone classes)
|
||||
for (RegionZone zone : attacker.getZoneMonitor().getZones()) {
|
||||
String zoneName = zone.getZone().getName();
|
||||
if (zoneName != null && zoneName.endsWith("MapArea")) {
|
||||
// Look up the original MapArea instance from TickListeners
|
||||
for (Object listener : GameWorld.getTickListeners()) {
|
||||
if (listener instanceof MapArea && listener instanceof PvPZone) {
|
||||
String mapAreaZoneName = listener.getClass().getSimpleName() + "MapArea";
|
||||
if (mapAreaZoneName.equals(zoneName)) {
|
||||
for (RegionZone victimZone : victim.getZoneMonitor().getZones()) {
|
||||
if (victimZone.getZone().getName().equals(zoneName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for PVP_ZONE restriction flag
|
||||
if (attacker.getZoneMonitor().isRestricted(ZoneRestriction.PVP_ZONE) &&
|
||||
victim.getZoneMonitor().isRestricted(ZoneRestriction.PVP_ZONE)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the entity can interact with the target.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -47,20 +47,6 @@ public enum ZoneRestriction {
|
|||
* Dynamic regions are implicitly off-map and do not require this attribute.
|
||||
*/
|
||||
OFF_MAP,
|
||||
|
||||
/**
|
||||
* This zone allows player-vs-player combat.
|
||||
* Used to mark zones where PvP is explicitly permitted.
|
||||
* Zones with this restriction should implement the PvPZone interface
|
||||
* to define specific combat rules (who can attack whom, combat level ranges, etc.).
|
||||
*
|
||||
* This flag is used by PvPStateAudit as an additional validation mechanism
|
||||
* to ensure PvP combat state doesn't leak out of designated areas.
|
||||
*
|
||||
* @see core.api.PvPZone
|
||||
* @see core.game.node.entity.player.link.PvPStateAudit
|
||||
*/
|
||||
PVP_ZONE,
|
||||
;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import core.game.activity.ActivityPlugin
|
|||
import core.game.bots.PlayerScripts
|
||||
import core.game.interaction.InteractionListener
|
||||
import core.game.interaction.InterfaceListener
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.combat.CombatStyle
|
||||
import core.game.node.entity.npc.NPCBehavior
|
||||
import core.game.node.entity.player.info.login.LoginConfiguration
|
||||
import core.game.node.entity.player.info.login.PlayerSaveParser
|
||||
|
|
@ -128,6 +130,29 @@ object ClassScanner {
|
|||
if(e != null && from != null && to != null) clazz.entityStep(e, to, from)
|
||||
return super.move(e, from, to)
|
||||
}
|
||||
|
||||
// Additional hook for continueAttack for PVP area controls
|
||||
override fun continuePvp(e: Entity, target: Node?, style: CombatStyle?, message: Boolean): Boolean {
|
||||
val clazzAttack = clazz.isPvpAllowed(e, target, style, message)
|
||||
if (clazzAttack == null) { // If clazz returns null, continue the usual check.
|
||||
return super.continuePvp(e, target, style, message)
|
||||
} else {
|
||||
return clazzAttack // Otherwise return whatever function.
|
||||
}
|
||||
}
|
||||
|
||||
// Additional hook for startDeath for PVP area controls
|
||||
override fun startDeath(e: Entity?, killer: Entity?): Boolean {
|
||||
if (e == null) {
|
||||
return super.startDeath(null, killer)
|
||||
}
|
||||
val clazzAttack = clazz.canStartDeath(e, killer)
|
||||
if (clazzAttack == null) { // If clazz returns null, continue the usual check.
|
||||
return super.startDeath(e, killer)
|
||||
} else {
|
||||
return clazzAttack // Otherwise return whatever function.
|
||||
}
|
||||
}
|
||||
}
|
||||
for(border in clazz.defineAreaBorders()) zone.register(border)
|
||||
ZoneBuilder.configure(zone)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue