# combat_status_effect_system.gd —— Metin2 40250 官方战斗状态异常与五大负面减益结算系统 1:1 # 100% 对照 40250 服务端 battle.cpp, char_resist.cpp, affect.h, char.h (POINT_POISON_PCT, IMMUNE_STUN, IMMUNE_SLOW) class_name CombatStatusEffectSystem extends RefCounted signal status_applied(target_id: int, effect_type: String, duration: float) signal status_resisted(target_id: int, effect_type: String, reason: String) signal status_ticked(target_id: int, effect_type: String, damage: int, remaining_ticks: int) signal status_cleared(target_id: int, effect_type: String) # 40250 官方核心状态异常类型 const EFFECT_POISON := "poison" # 中毒 (AFF_POISON) const EFFECT_STUN := "stun" # 眩晕 (AFF_STUN) const EFFECT_SLOW := "slow" # 减速 (AFF_SLOW) const EFFECT_FIRE := "fire" # 烈焰灼烧 (AFF_FIRE) const EFFECT_BLEEDING := "bleeding" # 流血虚弱 (AFF_BLEEDING) # 等级压制中毒调整表 (40250 char_resist.cpp: poison_level_adjust) const POISON_LEVEL_ADJUST: Array[int] = [100, 90, 80, 70, 60, 50, 40, 30, 20] # 状态记录表: target_id -> { effect_type -> { "duration": float, "timer": float, "tick_interval": float, "ticks_left": int, "param": int } } var _target_effects: Dictionary = {} # ========================================== # 1. 状态异常施加与判定 (battle.cpp: NormalAttackAffect) # ========================================== # 尝试施加攻击异常效果 (通用结算) func try_apply_normal_attack_effects( attacker: Dictionary, victim: Dictionary, forced_rolls: Dictionary = {} ) -> Dictionary: var victim_id = int(victim.get("id", 0)) var applied: Array[String] = [] var resisted: Array[String] = [] var is_attacker_pc = bool(attacker.get("is_pc", true)) var is_victim_pc = bool(victim.get("is_pc", true)) var attacker_level = int(attacker.get("level", 1)) var victim_level = int(victim.get("level", 1)) # 1. 中毒判定 (POINT_POISON_PCT) var poison_pct = int(attacker.get("poison_pct", 0)) if poison_pct > 0 and not has_effect(victim_id, EFFECT_POISON): # 怪物只中毒一次检查 (char_resist.cpp: m_bHasPoisoned && !IsPC()) var already_poisoned_once = bool(victim.get("has_been_poisoned", false)) if not is_victim_pc and already_poisoned_once: resisted.append("poison_already_poisoned_once") else: var roll = int(forced_rolls.get("poison_pct_roll", randi_range(1, 100))) if roll <= poison_pct: # 等级差距惩罚检查 var can_poison := true if attacker_level < victim_level: var delta = min(victim_level - attacker_level, 8) var level_roll = int(forced_rolls.get("poison_level_roll", randi_range(1, 100))) if level_roll > POISON_LEVEL_ADJUST[delta]: can_poison = false resisted.append("poison_level_penalty") if can_poison: apply_poison(victim, attacker) applied.append(EFFECT_POISON) # 2. 眩晕判定 (POINT_STUN_PCT) var stun_pct = int(attacker.get("stun_pct", 0)) if stun_pct > 0 and not has_effect(victim_id, EFFECT_STUN): # 免疫眩晕判定 (IMMUNE_STUN) if bool(victim.get("immune_stun", false)): status_resisted.emit(victim_id, EFFECT_STUN, "IMMUNE_STUN") resisted.append("immune_stun") else: var roll = int(forced_rolls.get("stun_pct_roll", randi_range(1, 100))) if roll <= stun_pct: var stun_dur = 4.0 if (is_attacker_pc and not is_victim_pc) else 2.0 apply_stun(victim_id, stun_dur) applied.append(EFFECT_STUN) # 3. 减速判定 (POINT_SLOW_PCT) var slow_pct = int(attacker.get("slow_pct", 0)) if slow_pct > 0 and not has_effect(victim_id, EFFECT_SLOW): # 免疫减速判定 (IMMUNE_SLOW) if bool(victim.get("immune_slow", false)): status_resisted.emit(victim_id, EFFECT_SLOW, "IMMUNE_SLOW") resisted.append("immune_slow") else: var roll = int(forced_rolls.get("slow_pct_roll", randi_range(1, 100))) if roll <= slow_pct: apply_slow(victim_id, 20.0, 30) # 40250 官方减速 20 秒,移速 -30 applied.append(EFFECT_SLOW) return { "applied": applied, "resisted": resisted } # ========================================== # 2. 状态具体应用 (char_resist.cpp) # ========================================== # 施加中毒: 10 次跳伤害,每次间隔 3 秒,总持续 30 秒 (POISON_LENGTH + 1) func apply_poison(victim: Dictionary, attacker: Dictionary = {}) -> void: var victim_id = int(victim.get("id", 0)) victim["has_been_poisoned"] = true _add_raw_effect(victim_id, EFFECT_POISON, { "duration": 30.0, "timer": 3.0, "tick_interval": 3.0, "ticks_left": 10, "param": int(victim.get("poison_reduce", 0)), # 抗毒减免比例 (0~100) "max_hp": int(victim.get("max_hp", 1000)), "attacker_id": int(attacker.get("id", 0)) }) status_applied.emit(victim_id, EFFECT_POISON, 30.0) # 施加眩晕: 期间无法移动和释放技能 func apply_stun(victim_id: int, duration: float = 2.0) -> void: _add_raw_effect(victim_id, EFFECT_STUN, { "duration": duration, "timer": duration, "tick_interval": 0.0, "ticks_left": 0, "param": 0 }) status_applied.emit(victim_id, EFFECT_STUN, duration) # 施加减速: 移动速度扣除 speed_reduction 点 func apply_slow(victim_id: int, duration: float = 20.0, speed_reduction: int = 30) -> void: _add_raw_effect(victim_id, EFFECT_SLOW, { "duration": duration, "timer": duration, "tick_interval": 0.0, "ticks_left": 0, "param": speed_reduction }) status_applied.emit(victim_id, EFFECT_SLOW, duration) # 施加烈焰灼烧: 固定火伤每 3 秒跳一次 func apply_fire(victim_id: int, ticks: int = 5, damage_per_tick: int = 50) -> void: _add_raw_effect(victim_id, EFFECT_FIRE, { "duration": float(ticks * 3), "timer": 3.0, "tick_interval": 3.0, "ticks_left": ticks, "param": damage_per_tick }) status_applied.emit(victim_id, EFFECT_FIRE, float(ticks * 3)) # 解除特定状态 (例如解毒药水 RemovePoison) func clear_effect(victim_id: int, effect_type: String) -> bool: if not _target_effects.has(victim_id): return false if _target_effects[victim_id].has(effect_type): _target_effects[victim_id].erase(effect_type) status_cleared.emit(victim_id, effect_type) return true return false # 检查目标是否具有特定异常状态 func has_effect(victim_id: int, effect_type: String) -> bool: if not _target_effects.has(victim_id): return false return _target_effects[victim_id].has(effect_type) # 检查目标是否处于无法行动状态 (眩晕) func is_stunned(victim_id: int) -> bool: return has_effect(victim_id, EFFECT_STUN) # 获取减速移速惩罚值 func get_slow_reduction(victim_id: int) -> int: if not has_effect(victim_id, EFFECT_SLOW): return 0 return int(_target_effects[victim_id][EFFECT_SLOW].get("param", 30)) # 检查自然回血是否被阻断 (40250 char.cpp:2453 中毒状态禁止自然回血) func is_natural_hp_regen_blocked(victim_id: int) -> bool: return has_effect(victim_id, EFFECT_POISON) # ========================================== # 3. 状态心跳更新与跳伤害结算 (char_resist.cpp: poison_event, fire_event) # ========================================== func update(delta: float, targets_map: Dictionary = {}) -> void: for vid in _target_effects.keys().duplicate(): var effects: Dictionary = _target_effects[vid] var to_remove: Array[String] = [] for eff_type in effects.keys(): var data = effects[eff_type] data["duration"] -= delta if data["tick_interval"] > 0.0: # 周期跳伤害逻辑 (中毒/灼烧) data["timer"] -= delta while data["timer"] <= 0.0 and data["ticks_left"] > 0: data["timer"] += data["tick_interval"] data["ticks_left"] -= 1 var dmg := 0 if eff_type == EFFECT_POISON: # 40250 官方: GetMaxHP() * 5 / 100 * (100 - poison_reduce) / 100 var max_hp = int(data.get("max_hp", 1000)) var reduce = int(data.get("param", 0)) var base_dmg = int(floor(max_hp * 0.05)) dmg = max(1, int(floor(base_dmg * (100 - reduce) / 100.0))) elif eff_type == EFFECT_FIRE: dmg = int(data.get("param", 50)) # 扣减目标生命 (不可致死,留 1 点 HP) if targets_map.has(vid): var tgt = targets_map[vid] var cur_hp = int(tgt.get("hp", 100)) tgt["hp"] = max(1, cur_hp - dmg) status_ticked.emit(vid, eff_type, dmg, data["ticks_left"]) if data["ticks_left"] <= 0: to_remove.append(eff_type) break else: # 持续时间型 (眩晕/减速) if data["duration"] <= 0.0: to_remove.append(eff_type) for eff_type in to_remove: effects.erase(eff_type) status_cleared.emit(vid, eff_type) if effects.is_empty(): _target_effects.erase(vid) func _add_raw_effect(victim_id: int, effect_type: String, data: Dictionary) -> void: if not _target_effects.has(victim_id): _target_effects[victim_id] = {} _target_effects[victim_id][effect_type] = data # 获取目标全部当前状态 func get_target_effects(victim_id: int) -> Dictionary: return _target_effects.get(victim_id, {}).duplicate(true) # 序列化与反序列化 func serialize() -> Dictionary: return { "target_effects": _target_effects.duplicate(true) } func deserialize(data: Dictionary) -> void: _target_effects = data.get("target_effects", {}).duplicate(true)