- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
220 lines
9.0 KiB
GDScript
220 lines
9.0 KiB
GDScript
# test_security_lock_parity.gd —— 40250 仓库安全密码与装备防丢锁定 1:1 纯净回归测试套件
|
|
extends SceneTree
|
|
|
|
const SecurityLockSystem = preload("res://security_lock_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_security_lock_parity (Direction 4: Security Lock System 1:1) ===")
|
|
_test_safebox_password_auth()
|
|
_test_safebox_password_change()
|
|
_test_item_security_lock_and_unlock()
|
|
_test_item_action_gates()
|
|
_test_character_deletion_code()
|
|
_finish()
|
|
|
|
func _test_safebox_password_auth() -> void:
|
|
print("\n--- Test 1: Safebox Password Authentication & Lockout ---")
|
|
var sec := SecurityLockSystem.new()
|
|
var unlock_data := {"fired": false}
|
|
sec.safebox_unlocked.connect(func(): unlock_data["fired"] = true)
|
|
|
|
_assert(sec.safebox_password == "000000", "Default safebox password is '000000'")
|
|
_assert(sec.is_safebox_unlocked == false, "Safebox initially closed")
|
|
|
|
# Wrong password 1st time
|
|
var res1 = sec.open_safebox("111111")
|
|
_assert(res1["ok"] == false and res1["reason"] == "WRONG_PASSWORD", "Wrong password fails")
|
|
_assert(res1["remaining_attempts"] == 4, "4 attempts remaining")
|
|
|
|
# Fail 4 more times to reach 5
|
|
sec.open_safebox("111111")
|
|
sec.open_safebox("111111")
|
|
sec.open_safebox("111111")
|
|
var res5 = sec.open_safebox("111111")
|
|
_assert(res5["ok"] == false and res5["reason"] == "WRONG_PASSWORD_LOCKOUT", "5th failure triggers lockout")
|
|
_assert(sec.is_locked_out == true, "is_locked_out is true")
|
|
|
|
# Blocked while locked out even with correct password
|
|
var locked_res = sec.open_safebox("000000")
|
|
_assert(locked_res["ok"] == false and locked_res["reason"] == "LOCKOUT", "Action blocked while locked out")
|
|
|
|
# Reset lockout
|
|
sec.reset_lockout()
|
|
_assert(sec.is_locked_out == false and sec.failed_attempts == 0, "Lockout reset successfully")
|
|
|
|
# Now correct password opens safebox
|
|
var ok_res = sec.open_safebox("000000")
|
|
_assert(ok_res["ok"] == true, "Correct password opened safebox")
|
|
_assert(sec.is_safebox_unlocked == true, "Safebox is unlocked")
|
|
_assert(unlock_data["fired"] == true, "safebox_unlocked signal fired")
|
|
|
|
# Close safebox
|
|
var close_data := {"fired": false}
|
|
sec.safebox_closed.connect(func(): close_data["fired"] = true)
|
|
sec.close_safebox()
|
|
_assert(sec.is_safebox_unlocked == false, "Safebox closed")
|
|
_assert(close_data["fired"] == true, "safebox_closed signal fired")
|
|
|
|
func _test_safebox_password_change() -> void:
|
|
print("\n--- Test 2: Password Change Validation (Strict 6 Digits) ---")
|
|
var sec := SecurityLockSystem.new()
|
|
|
|
# Wrong old password
|
|
var r1 = sec.change_safebox_password("999999", "123456")
|
|
_assert(r1["ok"] == false and r1["reason"] == "WRONG_OLD_PASSWORD", "Wrong old password rejected")
|
|
|
|
# Invalid format: 5 digits
|
|
var r2 = sec.change_safebox_password("000000", "12345")
|
|
_assert(r2["ok"] == false and r2["reason"] == "INVALID_PASSWORD_FORMAT", "5-digit password rejected")
|
|
|
|
# Invalid format: 7 digits
|
|
var r3 = sec.change_safebox_password("000000", "1234567")
|
|
_assert(r3["ok"] == false and r3["reason"] == "INVALID_PASSWORD_FORMAT", "7-digit password rejected")
|
|
|
|
# Invalid format: Contains non-digits
|
|
var r4 = sec.change_safebox_password("000000", "12a456")
|
|
_assert(r4["ok"] == false and r4["reason"] == "INVALID_PASSWORD_FORMAT", "Alphanumeric password rejected")
|
|
|
|
# Same password
|
|
var r5 = sec.change_safebox_password("000000", "000000")
|
|
_assert(r5["ok"] == false and r5["reason"] == "SAME_PASSWORD", "Identical new password rejected")
|
|
|
|
# Valid change to "888888"
|
|
var pwd_data := {"fired": false, "new_pwd": ""}
|
|
sec.password_changed.connect(func(np: String):
|
|
pwd_data["fired"] = true
|
|
pwd_data["new_pwd"] = np
|
|
)
|
|
|
|
var r6 = sec.change_safebox_password("000000", "888888")
|
|
_assert(r6["ok"] == true, "Valid 6-digit password changed successfully")
|
|
_assert(sec.safebox_password == "888888", "Active password updated to '888888'")
|
|
_assert(pwd_data["fired"] == true and pwd_data["new_pwd"] == "888888", "password_changed signal fired")
|
|
|
|
# Verify old password no longer works
|
|
var old_try = sec.open_safebox("000000")
|
|
_assert(old_try["ok"] == false, "Old password '000000' no longer valid")
|
|
|
|
# Verify new password works
|
|
var new_try = sec.open_safebox("888888")
|
|
_assert(new_try["ok"] == true, "New password '888888' valid")
|
|
|
|
func _test_item_security_lock_and_unlock() -> void:
|
|
print("\n--- Test 3: Item Anti-Theft Lock & Unlock ---")
|
|
var sec := SecurityLockSystem.new()
|
|
var test_item: Dictionary = {
|
|
"vnum": 169,
|
|
"name": "战神之剑+9",
|
|
"count": 1
|
|
}
|
|
|
|
var lock_signal := {"fired": false, "name": ""}
|
|
sec.item_locked.connect(func(n: String):
|
|
lock_signal["fired"] = true
|
|
lock_signal["name"] = n
|
|
)
|
|
|
|
var unlock_signal := {"fired": false, "name": ""}
|
|
sec.item_unlocked.connect(func(n: String):
|
|
unlock_signal["fired"] = true
|
|
unlock_signal["name"] = n
|
|
)
|
|
|
|
# Lock with wrong password
|
|
var r1 = sec.lock_item(test_item, "999999")
|
|
_assert(r1["ok"] == false and r1["reason"] == "WRONG_SECURITY_PASSWORD", "Wrong password prevents locking")
|
|
|
|
# Lock with correct password
|
|
var r2 = sec.lock_item(test_item, "000000")
|
|
_assert(r2["ok"] == true, "Item locked successfully")
|
|
_assert(test_item["is_locked"] == true, "Item is_locked flag is true")
|
|
_assert(lock_signal["fired"] == true and lock_signal["name"] == "战神之剑+9", "item_locked signal fired")
|
|
|
|
# Attempt to lock already locked item
|
|
var r3 = sec.lock_item(test_item, "000000")
|
|
_assert(r3["ok"] == false and r3["reason"] == "ALREADY_LOCKED", "Already locked item rejected")
|
|
|
|
# Unlock with wrong password
|
|
var r4 = sec.unlock_item(test_item, "999999")
|
|
_assert(r4["ok"] == false and r4["reason"] == "WRONG_SECURITY_PASSWORD", "Wrong password prevents unlocking")
|
|
|
|
# Unlock with correct password
|
|
var r5 = sec.unlock_item(test_item, "000000")
|
|
_assert(r5["ok"] == true, "Item unlocked successfully")
|
|
_assert(test_item["is_locked"] == false, "Item is_locked flag is false")
|
|
_assert(unlock_signal["fired"] == true and unlock_signal["name"] == "战神之剑+9", "item_unlocked signal fired")
|
|
|
|
# Attempt to unlock unlocked item
|
|
var r6 = sec.unlock_item(test_item, "000000")
|
|
_assert(r6["ok"] == false and r6["reason"] == "NOT_LOCKED", "Already unlocked item rejected")
|
|
|
|
func _test_item_action_gates() -> void:
|
|
print("\n--- Test 4: Action Protection Gates (Drop, Sell, Destroy, Trade, Refine) ---")
|
|
var sec := SecurityLockSystem.new()
|
|
var normal_item: Dictionary = {"vnum": 10, "name": "木剑+0", "is_locked": false}
|
|
var locked_item: Dictionary = {"vnum": 11299, "name": "黑钢甲+9", "is_locked": true}
|
|
|
|
# Normal item allowed everywhere
|
|
_assert(sec.can_drop_item(normal_item)["allowed"] == true, "Normal item can be dropped")
|
|
_assert(sec.can_sell_item(normal_item)["allowed"] == true, "Normal item can be sold")
|
|
_assert(sec.can_destroy_item(normal_item)["allowed"] == true, "Normal item can be destroyed")
|
|
_assert(sec.can_trade_item(normal_item)["allowed"] == true, "Normal item can be traded")
|
|
_assert(sec.can_refine_item(normal_item)["allowed"] == true, "Normal item can be refined")
|
|
|
|
# Locked item blocked everywhere with ITEM_LOCKED
|
|
var drop_gate = sec.can_drop_item(locked_item)
|
|
_assert(drop_gate["allowed"] == false and drop_gate["reason"] == "ITEM_LOCKED", "Locked item DROP blocked")
|
|
|
|
var sell_gate = sec.can_sell_item(locked_item)
|
|
_assert(sell_gate["allowed"] == false and sell_gate["reason"] == "ITEM_LOCKED", "Locked item SELL blocked")
|
|
|
|
var destroy_gate = sec.can_destroy_item(locked_item)
|
|
_assert(destroy_gate["allowed"] == false and destroy_gate["reason"] == "ITEM_LOCKED", "Locked item DESTROY blocked")
|
|
|
|
var trade_gate = sec.can_trade_item(locked_item)
|
|
_assert(trade_gate["allowed"] == false and trade_gate["reason"] == "ITEM_LOCKED", "Locked item TRADE blocked")
|
|
|
|
var refine_gate = sec.can_refine_item(locked_item)
|
|
_assert(refine_gate["allowed"] == false and refine_gate["reason"] == "ITEM_LOCKED", "Locked item REFINE blocked")
|
|
|
|
func _test_character_deletion_code() -> void:
|
|
print("\n--- Test 5: 7-Digit Character Deletion Security Code ---")
|
|
var sec := SecurityLockSystem.new()
|
|
_assert(sec.char_delete_code == "1234567", "Default 7-digit deletion code is '1234567'")
|
|
|
|
# Invalid length: 6 digits
|
|
var r1 = sec.verify_character_deletion("123456")
|
|
_assert(r1["ok"] == false and r1["reason"] == "INVALID_CODE_FORMAT", "6-digit deletion code rejected")
|
|
|
|
# Invalid format: non-numeric
|
|
var r2 = sec.verify_character_deletion("123456a")
|
|
_assert(r2["ok"] == false and r2["reason"] == "INVALID_CODE_FORMAT", "Alphanumeric deletion code rejected")
|
|
|
|
# Wrong 7-digit code
|
|
var r3 = sec.verify_character_deletion("7654321")
|
|
_assert(r3["ok"] == false and r3["reason"] == "WRONG_DELETE_CODE", "Wrong deletion code rejected")
|
|
|
|
# Correct 7-digit code
|
|
var r4 = sec.verify_character_deletion("1234567")
|
|
_assert(r4["ok"] == true, "Correct 7-digit code authorizes deletion")
|
|
|
|
func _finish() -> void:
|
|
print("\n==========================================")
|
|
print("Security Lock Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
|
|
print("==========================================")
|
|
if _failed > 0:
|
|
quit(1)
|
|
else:
|
|
quit(0)
|