- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
224 lines
5.7 KiB
GDScript
224 lines
5.7 KiB
GDScript
# security_lock_system.gd —— Metin2 40250 仓库 6 位安全密码与贵重装备防丢锁定 1:1
|
|
# 对照 40250 服务端 safebox.cpp, input_login.cpp, char.cpp
|
|
class_name SecurityLockSystem
|
|
extends RefCounted
|
|
|
|
signal safebox_unlocked()
|
|
signal safebox_closed()
|
|
signal password_changed(new_pwd: String)
|
|
signal item_locked(item_name: String)
|
|
signal item_unlocked(item_name: String)
|
|
|
|
const DEFAULT_SAFEBOX_PASSWORD := "000000"
|
|
const PASSWORD_LEN := 6
|
|
const CHAR_DELETE_CODE_LEN := 7
|
|
const MAX_FAILED_ATTEMPTS := 5
|
|
|
|
var safebox_password: String = DEFAULT_SAFEBOX_PASSWORD
|
|
var is_safebox_unlocked: bool = false
|
|
var failed_attempts: int = 0
|
|
var is_locked_out: bool = false
|
|
var char_delete_code: String = "1234567"
|
|
|
|
# 验证并开启仓库
|
|
func open_safebox(input_pwd: String) -> Dictionary:
|
|
if is_locked_out:
|
|
return {
|
|
"ok": false,
|
|
"reason": "LOCKOUT",
|
|
"msg": "连续输错5次密码,仓库已被暂时锁定!请稍后再试。"
|
|
}
|
|
|
|
if input_pwd == safebox_password:
|
|
failed_attempts = 0
|
|
is_safebox_unlocked = true
|
|
safebox_unlocked.emit()
|
|
return {
|
|
"ok": true,
|
|
"msg": "仓库密码验证成功,仓库已开启!"
|
|
}
|
|
|
|
failed_attempts += 1
|
|
if failed_attempts >= MAX_FAILED_ATTEMPTS:
|
|
is_locked_out = true
|
|
return {
|
|
"ok": false,
|
|
"reason": "WRONG_PASSWORD_LOCKOUT",
|
|
"failed_attempts": failed_attempts,
|
|
"msg": "密码错误达到上限(5次),仓库已被安全保护锁定!"
|
|
}
|
|
|
|
var remaining := MAX_FAILED_ATTEMPTS - failed_attempts
|
|
return {
|
|
"ok": false,
|
|
"reason": "WRONG_PASSWORD",
|
|
"failed_attempts": failed_attempts,
|
|
"remaining_attempts": remaining,
|
|
"msg": "仓库密码错误!还剩 %d 次尝试机会。" % remaining
|
|
}
|
|
|
|
# 关闭仓库
|
|
func close_safebox() -> void:
|
|
is_safebox_unlocked = false
|
|
safebox_closed.emit()
|
|
|
|
# 重置防爆保护锁定 (如由 GM 或系统定时重置)
|
|
func reset_lockout() -> void:
|
|
is_locked_out = false
|
|
failed_attempts = 0
|
|
|
|
# 修改仓库 6 位密码
|
|
func change_safebox_password(old_pwd: String, new_pwd: String) -> Dictionary:
|
|
if old_pwd != safebox_password:
|
|
return {
|
|
"ok": false,
|
|
"reason": "WRONG_OLD_PASSWORD",
|
|
"msg": "旧密码验证失败,无法修改!"
|
|
}
|
|
|
|
if new_pwd.length() != PASSWORD_LEN or not new_pwd.is_valid_int() or int(new_pwd) < 0:
|
|
return {
|
|
"ok": false,
|
|
"reason": "INVALID_PASSWORD_FORMAT",
|
|
"msg": "仓库密码必须为严格 6 位纯数字!"
|
|
}
|
|
|
|
if new_pwd == old_pwd:
|
|
return {
|
|
"ok": false,
|
|
"reason": "SAME_PASSWORD",
|
|
"msg": "新密码不能与旧密码相同!"
|
|
}
|
|
|
|
safebox_password = new_pwd
|
|
password_changed.emit(new_pwd)
|
|
|
|
return {
|
|
"ok": true,
|
|
"new_password": new_pwd,
|
|
"msg": "仓库密码已成功修改!"
|
|
}
|
|
|
|
# 锁定装备/贵重物品 (防丢锁定)
|
|
func lock_item(item: Dictionary, pwd: String) -> Dictionary:
|
|
if pwd != safebox_password:
|
|
return {
|
|
"ok": false,
|
|
"reason": "WRONG_SECURITY_PASSWORD",
|
|
"msg": "安全密码错误,无法加锁!"
|
|
}
|
|
|
|
if item.get("is_locked", false):
|
|
return {
|
|
"ok": false,
|
|
"reason": "ALREADY_LOCKED",
|
|
"msg": "该物品已处于锁定状态!"
|
|
}
|
|
|
|
item["is_locked"] = true
|
|
var iname = item.get("name", "物品")
|
|
item_locked.emit(iname)
|
|
|
|
return {
|
|
"ok": true,
|
|
"item": item,
|
|
"msg": "物品【%s】已安全加锁!" % iname
|
|
}
|
|
|
|
# 解除物品锁定
|
|
func unlock_item(item: Dictionary, pwd: String) -> Dictionary:
|
|
if pwd != safebox_password:
|
|
return {
|
|
"ok": false,
|
|
"reason": "WRONG_SECURITY_PASSWORD",
|
|
"msg": "安全密码错误,无法解锁!"
|
|
}
|
|
|
|
if not item.get("is_locked", false):
|
|
return {
|
|
"ok": false,
|
|
"reason": "NOT_LOCKED",
|
|
"msg": "该物品未处于锁定状态!"
|
|
}
|
|
|
|
item["is_locked"] = false
|
|
var iname = item.get("name", "物品")
|
|
item_unlocked.emit(iname)
|
|
|
|
return {
|
|
"ok": true,
|
|
"item": item,
|
|
"msg": "物品【%s】已解除锁定!" % iname
|
|
}
|
|
|
|
# 动作权限校验 —— 丢弃到地面
|
|
func can_drop_item(item: Dictionary) -> Dictionary:
|
|
if item.get("is_locked", false):
|
|
return {
|
|
"allowed": false,
|
|
"reason": "ITEM_LOCKED",
|
|
"msg": "已加锁的贵重物品无法丢弃!"
|
|
}
|
|
return {"allowed": true}
|
|
|
|
# 动作权限校验 —— 出售给 NPC
|
|
func can_sell_item(item: Dictionary) -> Dictionary:
|
|
if item.get("is_locked", false):
|
|
return {
|
|
"allowed": false,
|
|
"reason": "ITEM_LOCKED",
|
|
"msg": "已加锁的贵重物品无法出售给商人!"
|
|
}
|
|
return {"allowed": true}
|
|
|
|
# 动作权限校验 —— 彻底销毁
|
|
func can_destroy_item(item: Dictionary) -> Dictionary:
|
|
if item.get("is_locked", false):
|
|
return {
|
|
"allowed": false,
|
|
"reason": "ITEM_LOCKED",
|
|
"msg": "已加锁的贵重物品无法直接销毁!"
|
|
}
|
|
return {"allowed": true}
|
|
|
|
# 动作权限校验 —— 玩家交易
|
|
func can_trade_item(item: Dictionary) -> Dictionary:
|
|
if item.get("is_locked", false):
|
|
return {
|
|
"allowed": false,
|
|
"reason": "ITEM_LOCKED",
|
|
"msg": "已加锁的贵重物品无法用于玩家交易!"
|
|
}
|
|
return {"allowed": true}
|
|
|
|
# 动作权限校验 —— 强化或熔炼消耗
|
|
func can_refine_item(item: Dictionary) -> Dictionary:
|
|
if item.get("is_locked", false):
|
|
return {
|
|
"allowed": false,
|
|
"reason": "ITEM_LOCKED",
|
|
"msg": "已加锁的装备无法用于强化或附魔消耗!"
|
|
}
|
|
return {"allowed": true}
|
|
|
|
# 角色删除 7 位安全码验证 (对照 40250 input_login.cpp)
|
|
func verify_character_deletion(input_code: String) -> Dictionary:
|
|
if input_code.length() != CHAR_DELETE_CODE_LEN or not input_code.is_valid_int() or int(input_code) < 0:
|
|
return {
|
|
"ok": false,
|
|
"reason": "INVALID_CODE_FORMAT",
|
|
"msg": "角色删除码必须为严格 7 位纯数字!"
|
|
}
|
|
|
|
if input_code != char_delete_code:
|
|
return {
|
|
"ok": false,
|
|
"reason": "WRONG_DELETE_CODE",
|
|
"msg": "角色删除安全码错误!"
|
|
}
|
|
|
|
return {
|
|
"ok": true,
|
|
"msg": "角色删除安全码验证通过,允许删除角色。"
|
|
}
|