- 桥梁与静态物体高度采样修复: - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程 - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题 - 新增 test_bridge_height_parity.gd 自动化对拍测试 - 40250 怪物击杀经验动效: - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附 - 40250 客户端全系统功能对齐(Batches 1-31): - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试 - 文档沉淀: - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
123 lines
4.8 KiB
GDScript
123 lines
4.8 KiB
GDScript
# test_pk_penalty_parity.gd —— 40250 恶名死亡惩罚与露西之戒保护 1:1 纯净回归测试套件
|
|
extends SceneTree
|
|
|
|
const PkPenaltySystem = preload("res://pk_penalty_system.gd")
|
|
|
|
var _passed := 0
|
|
var _failed := 0
|
|
|
|
func _assert(cond: bool, msg: String) -> void:
|
|
if cond:
|
|
_passed += 1
|
|
print(" [PASS] %s" % msg)
|
|
else:
|
|
_failed += 1
|
|
printerr(" [FAIL] %s" % msg)
|
|
|
|
func _init() -> void:
|
|
print("\n=== Running test_pk_penalty_parity (Direction 3: PK Penalty & Lucy Ring 1:1) ===")
|
|
_test_safe_conditions_no_drop()
|
|
_test_lucy_ring_protection()
|
|
_test_negative_karma_drop_and_item_lock()
|
|
_test_zen_bean_and_cloak()
|
|
_finish()
|
|
|
|
func _test_safe_conditions_no_drop() -> void:
|
|
print("\n--- Test 1: Safe Conditions (Level < 50 or Positive Karma) ---")
|
|
var pks := PkPenaltySystem.new()
|
|
var eq: Dictionary = {"weapon": {"vnum": 169, "name": "战神之剑+9"}}
|
|
var inv: Array = [{"vnum": 10, "name": "木剑"}]
|
|
|
|
# Level < 50 with negative karma
|
|
var r1 = pks.process_death_penalty(45, -5000, eq, inv, 0.01)
|
|
_assert(r1["dropped"] == false, "No drop when player level < 50")
|
|
|
|
# Positive karma at Level 75
|
|
var r2 = pks.process_death_penalty(75, 8000, eq, inv, 0.01)
|
|
_assert(r2["dropped"] == false, "No drop when alignment >= 0 (Noble)")
|
|
|
|
func _test_lucy_ring_protection() -> void:
|
|
print("\n--- Test 2: Lucy's Ring (70043) Shatter Protection ---")
|
|
var pks := PkPenaltySystem.new()
|
|
var eq: Dictionary = {
|
|
"weapon": {"vnum": 169, "name": "战神之剑+9"},
|
|
"ring1": {"vnum": PkPenaltySystem.ITEM_LUCY_RING, "name": "露西之戒"}
|
|
}
|
|
var inv: Array = [{"vnum": 11299, "name": "黑钢甲+9"}]
|
|
|
|
var ring_signal := {"fired": false}
|
|
pks.lucy_ring_protected.connect(func(): ring_signal["fired"] = true)
|
|
|
|
# Level 75, Cruel (-15,000 karma)
|
|
var res = pks.process_death_penalty(75, -15000, eq, inv, 0.01)
|
|
_assert(res["dropped"] == false, "No items dropped")
|
|
_assert(res["ring_shattered"] == true, "Lucy's Ring shattered")
|
|
_assert(eq["ring1"] == null, "Lucy's Ring consumed from equipment slot")
|
|
_assert(eq["weapon"] != null, "Valuable weapon preserved")
|
|
_assert(inv[0] != null, "Valuable armor in inventory preserved")
|
|
_assert(ring_signal["fired"] == true, "lucy_ring_protected signal fired")
|
|
|
|
func _test_negative_karma_drop_and_item_lock() -> void:
|
|
print("\n--- Test 3: Drop Mechanics & Item Lock Immunity ---")
|
|
var pks := PkPenaltySystem.new()
|
|
var eq: Dictionary = {
|
|
"weapon": {"vnum": 169, "name": "战神之剑+9", "is_locked": true} # Locked!
|
|
}
|
|
var inv: Array = [
|
|
{"vnum": 11299, "name": "黑钢甲+9", "is_locked": true}, # Locked!
|
|
{"vnum": 50054, "name": "胡萝卜", "is_locked": false} # Unlocked, can drop!
|
|
]
|
|
|
|
var drop_signal := {"fired": false, "name": ""}
|
|
pks.item_dropped_on_death.connect(func(it: Dictionary):
|
|
drop_signal["fired"] = true
|
|
drop_signal["name"] = it.get("name", "")
|
|
)
|
|
|
|
# Level 75, Malicious (-5000 karma, 10% chance). Forced roll 0.05 <= 0.10
|
|
var res = pks.process_death_penalty(75, -5000, eq, inv, 0.05)
|
|
_assert(res["dropped"] == true, "Death drop triggered")
|
|
_assert(res["dropped_item"]["vnum"] == 50054, "Unlocked carrot dropped")
|
|
_assert(inv[1] == null, "Carrot removed from inventory")
|
|
_assert(eq["weapon"] != null, "Locked weapon was NOT dropped")
|
|
_assert(inv[0] != null, "Locked armor was NOT dropped")
|
|
_assert(drop_signal["fired"] == true and drop_signal["name"] == "胡萝卜", "item_dropped_on_death signal fired")
|
|
|
|
func _test_zen_bean_and_cloak() -> void:
|
|
print("\n--- Test 4: Zen Bean (70102) & Peace Cloak (70048) ---")
|
|
var pks := PkPenaltySystem.new()
|
|
var inv: Array = [
|
|
{"vnum": PkPenaltySystem.ITEM_ZEN_BEAN, "name": "禅豆", "count": 2}
|
|
]
|
|
|
|
# 1. Use on positive karma rejected
|
|
var r_bad = pks.use_zen_bean(0, inv, 1000)
|
|
_assert(r_bad["ok"] == false and r_bad["reason"] == "NOT_EVIL", "Cannot use Zen Bean if alignment >= 0")
|
|
|
|
# 2. Use on -1200 karma -> -700 karma
|
|
var r1 = pks.use_zen_bean(0, inv, -1200)
|
|
_assert(r1["ok"] == true, "Zen bean consumed")
|
|
_assert(r1["new_alignment"] == -700, "Alignment restored by +500 to -700")
|
|
_assert(inv[0]["count"] == 1, "1 Zen bean remaining in stack")
|
|
|
|
# 3. Use on -200 karma -> capped at 0
|
|
var r2 = pks.use_zen_bean(0, inv, -200)
|
|
_assert(r2["ok"] == true and r2["new_alignment"] == 0, "Alignment capped at 0 (neutral)")
|
|
_assert(inv[0] == null, "Stack consumed")
|
|
|
|
# 4. Peace Cloak hides red name
|
|
var eq_with_cloak := {"body": null, "cloak": {"vnum": PkPenaltySystem.ITEM_CLOAK_OF_PEACE}}
|
|
_assert(pks.is_red_name_hidden(eq_with_cloak) == true, "Cloak of Peace hides red name")
|
|
|
|
var eq_no_cloak := {"body": null}
|
|
_assert(pks.is_red_name_hidden(eq_no_cloak) == false, "No cloak reveals red name")
|
|
|
|
func _finish() -> void:
|
|
print("\n==========================================")
|
|
print("PK Penalty Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
|
|
print("==========================================")
|
|
if _failed > 0:
|
|
quit(1)
|
|
else:
|
|
quit(0)
|