- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
219 lines
7.4 KiB
GDScript
219 lines
7.4 KiB
GDScript
# consumable_system.gd —— 高级消耗品与瞬移定位系统(40250 官方 1:1 对齐)
|
||
# 对齐源码:
|
||
# - 40250 char_item.cpp:3950-4050 (AFFECT_AUTO_HP/SP_RECOVERY, 日月神水开关)
|
||
# - 40250 char_item.cpp:7326-7390 (AutoRecoveryItemProcess, 自动补充与容量耗尽)
|
||
# - 40250 affect.h:95-96 (AFFECT_AUTO_HP_RECOVERY = 534, AFFECT_AUTO_SP_RECOVERY = 535)
|
||
# - 40250 char_item.cpp 龙神药水 (71027~71030: 攻/防/血/魔法比率加成, 30 分钟)
|
||
# - 40250 char_item.cpp 卷轴 (22001 回城卷轴, 22010 地标传送卷轴)
|
||
class_name ConsumableSystem
|
||
extends RefCounted
|
||
|
||
# 日月神水 Vnum 范围(40250 官方标准)
|
||
const VNUM_SUN_ELIXIR_S := 72723 # 太阳神水 (小) - 1,000,000 HP
|
||
const VNUM_SUN_ELIXIR_M := 72724 # 太阳神水 (中) - 3,000,000 HP
|
||
const VNUM_SUN_ELIXIR_L := 72725 # 太阳神水 (大) - 5,000,000 HP
|
||
const VNUM_SUN_ELIXIR_EX := 72726 # 太阳神水 (特别) - 10,000,000 HP
|
||
|
||
const VNUM_MOON_ELIXIR_S := 72727 # 月亮神水 (小) - 100,000 SP
|
||
const VNUM_MOON_ELIXIR_M := 72728 # 月亮神水 (中) - 300,000 SP
|
||
const VNUM_MOON_ELIXIR_L := 72729 # 月亮神水 (大) - 500,000 SP
|
||
const VNUM_MOON_ELIXIR_EX := 72730 # 月亮神水 (特别) - 1,000,000 SP
|
||
|
||
# 龙神药水 Vnum
|
||
const VNUM_DRAGON_GOD_ATTACK := 71027 # 龙神攻击 (+15% 伤害)
|
||
const VNUM_DRAGON_GOD_DEFENSE := 71028 # 龙神防御 (+15% 防御)
|
||
const VNUM_DRAGON_GOD_LIFE := 71029 # 龙神生命 (+20% HP)
|
||
const VNUM_DRAGON_GOD_MANA := 71030 # 龙神法力 (+20% SP)
|
||
|
||
# 卷轴 Vnum
|
||
const VNUM_TOWN_SCROLL := 22001 # 瞬间回城卷轴
|
||
const VNUM_RETURN_SCROLL := 22010 # 地标记忆/传送卷轴
|
||
|
||
# 40250 Affect 编号
|
||
const AFFECT_AUTO_HP_RECOVERY := 534
|
||
const AFFECT_AUTO_SP_RECOVERY := 535
|
||
const DRAGON_GOD_DURATION := 1800.0 # 30 分钟持续时间
|
||
|
||
# 神水默认容量表 (Vnum -> 容量)
|
||
const ELIXIR_CAPACITIES := {
|
||
72723: 1000000,
|
||
72724: 3000000,
|
||
72725: 5000000,
|
||
72726: 10000000,
|
||
72727: 100000,
|
||
72728: 300000,
|
||
72729: 500000,
|
||
72730: 1000000,
|
||
}
|
||
|
||
## 检查是否为日月神水
|
||
static func is_elixir(vnum: int) -> bool:
|
||
return (vnum >= 72723 and vnum <= 72730)
|
||
|
||
## 检查是否为生命神水(太阳神水)
|
||
static func is_hp_elixir(vnum: int) -> bool:
|
||
return (vnum >= 72723 and vnum <= 72726)
|
||
|
||
## 检查是否为法力神水(月亮神水)
|
||
static func is_sp_elixir(vnum: int) -> bool:
|
||
return (vnum >= 72727 and vnum <= 72730)
|
||
|
||
## 检查是否为龙神特药
|
||
static func is_dragon_god_potion(vnum: int) -> bool:
|
||
return vnum in [VNUM_DRAGON_GOD_ATTACK, VNUM_DRAGON_GOD_DEFENSE, VNUM_DRAGON_GOD_LIFE, VNUM_DRAGON_GOD_MANA]
|
||
|
||
## 初始化神水数据(若插槽容量未设定)
|
||
static func init_elixir_if_needed(item_data: Dictionary) -> void:
|
||
var vnum := int(item_data.get("vnum", 0))
|
||
var sockets: Array = item_data.get("sockets", [0, 0, 0]).duplicate()
|
||
while sockets.size() < 3:
|
||
sockets.append(0)
|
||
# socket[0]: 0=未激活, 1=激活
|
||
# socket[1]: 已使用量
|
||
# socket[2]: 总容量
|
||
if int(sockets[2]) <= 0:
|
||
sockets[2] = ELIXIR_CAPACITIES.get(vnum, 1000000)
|
||
sockets[0] = 0
|
||
sockets[1] = 0
|
||
item_data["sockets"] = sockets
|
||
|
||
## 切换神水激活状态 (40250 char_item.cpp:3950)
|
||
static func toggle_elixir(item_data: Dictionary) -> Dictionary:
|
||
init_elixir_if_needed(item_data)
|
||
var sockets: Array = item_data["sockets"]
|
||
var cur_active := (int(sockets[0]) != 0)
|
||
var new_active := not cur_active
|
||
sockets[0] = 1 if new_active else 0
|
||
item_data["sockets"] = sockets
|
||
|
||
var is_hp := is_hp_elixir(int(item_data.get("vnum", 0)))
|
||
var kind := "生命" if is_hp else "法力"
|
||
var msg := ("已激活%s神水自充能!" % kind) if new_active else ("已关闭%s神水自充能。" % kind)
|
||
|
||
return {
|
||
"ok": true,
|
||
"active": new_active,
|
||
"affect_type": AFFECT_AUTO_HP_RECOVERY if is_hp else AFFECT_AUTO_SP_RECOVERY,
|
||
"msg": msg,
|
||
}
|
||
|
||
## 自充能 Tick 处理 (40250 char_item.cpp:7326 AutoRecoveryItemProcess)
|
||
## 返回消耗与恢复量,若耗尽返回 exhausted=true
|
||
static func process_elixir_recovery(item_data: Dictionary, cur_val: int, max_val: int) -> Dictionary:
|
||
init_elixir_if_needed(item_data)
|
||
var sockets: Array = item_data["sockets"]
|
||
if int(sockets[0]) == 0:
|
||
return {"recovered": 0, "exhausted": false} # 未激活
|
||
|
||
var used := int(sockets[1])
|
||
var cap := int(sockets[2])
|
||
var avail := maxi(0, cap - used)
|
||
if avail <= 0:
|
||
return {"recovered": 0, "exhausted": true}
|
||
|
||
var deficit := maxi(0, max_val - cur_val)
|
||
if deficit <= 0:
|
||
return {"recovered": 0, "exhausted": false}
|
||
|
||
var heal: int = mini(deficit, avail)
|
||
sockets[1] = used + heal
|
||
item_data["sockets"] = sockets
|
||
|
||
var is_depleted := (int(sockets[1]) >= cap)
|
||
return {
|
||
"recovered": heal,
|
||
"exhausted": is_depleted,
|
||
"remaining": maxi(0, cap - int(sockets[1])),
|
||
"capacity": cap,
|
||
}
|
||
|
||
## 使用龙神特药 (40250 char_item.cpp)
|
||
## 返回 Buff 增益配置
|
||
static func use_dragon_god_potion(vnum: int) -> Dictionary:
|
||
match vnum:
|
||
VNUM_DRAGON_GOD_ATTACK:
|
||
return {
|
||
"ok": true,
|
||
"vnum": vnum,
|
||
"name": "龙神攻击",
|
||
"duration": DRAGON_GOD_DURATION,
|
||
"bonus_type": "POINT_ATT_BONUS",
|
||
"bonus_value": 15,
|
||
"msg": "使用了龙神攻击,攻击力提升 15%,持续 30 分钟!"
|
||
}
|
||
VNUM_DRAGON_GOD_DEFENSE:
|
||
return {
|
||
"ok": true,
|
||
"vnum": vnum,
|
||
"name": "龙神防御",
|
||
"duration": DRAGON_GOD_DURATION,
|
||
"bonus_type": "POINT_DEF_BONUS",
|
||
"bonus_value": 15,
|
||
"msg": "使用了龙神防御,防御力提升 15%,持续 30 分钟!"
|
||
}
|
||
VNUM_DRAGON_GOD_LIFE:
|
||
return {
|
||
"ok": true,
|
||
"vnum": vnum,
|
||
"name": "龙神生命",
|
||
"duration": DRAGON_GOD_DURATION,
|
||
"bonus_type": "POINT_MAX_HP_PCT",
|
||
"bonus_value": 20,
|
||
"msg": "使用了龙神生命,最大生命值提升 20%,持续 30 分钟!"
|
||
}
|
||
VNUM_DRAGON_GOD_MANA:
|
||
return {
|
||
"ok": true,
|
||
"vnum": vnum,
|
||
"name": "龙神法力",
|
||
"duration": DRAGON_GOD_DURATION,
|
||
"bonus_type": "POINT_MAX_SP_PCT",
|
||
"bonus_value": 20,
|
||
"msg": "使用了龙神法力,最大法力值提升 20%,持续 30 分钟!"
|
||
}
|
||
return {"ok": false, "msg": "未知的龙神药水"}
|
||
|
||
## 使用回城卷轴 / 地标卷轴 (40250 char_item.cpp)
|
||
static func use_warp_scroll(vnum: int, item_data: Dictionary, cur_x: float, cur_y: float, cur_map_id := 1) -> Dictionary:
|
||
if vnum == VNUM_TOWN_SCROLL:
|
||
return {
|
||
"ok": true,
|
||
"action": "TOWN_WARP",
|
||
"consumed": true,
|
||
"msg": "使用了回城卷轴,即将传送回城!"
|
||
}
|
||
elif vnum == VNUM_RETURN_SCROLL:
|
||
var sockets: Array = item_data.get("sockets", [0, 0, 0]).duplicate()
|
||
while sockets.size() < 3:
|
||
sockets.append(0)
|
||
|
||
# 若未记录坐标(socket[0] == 0),则记录当前位置,不消耗卷轴!
|
||
if int(sockets[0]) == 0 and int(sockets[1]) == 0:
|
||
sockets[0] = int(cur_x)
|
||
sockets[1] = int(cur_y)
|
||
sockets[2] = cur_map_id
|
||
item_data["sockets"] = sockets
|
||
return {
|
||
"ok": true,
|
||
"action": "RECORDED",
|
||
"consumed": false,
|
||
"coords": Vector2(cur_x, cur_y),
|
||
"map_id": cur_map_id,
|
||
"msg": "成功将当前坐标 (%d, %d) 记录到地标卷轴!" % [int(cur_x), int(cur_y)]
|
||
}
|
||
else:
|
||
# 已记录坐标:传送至记录位置并消耗卷轴
|
||
var target_x := int(sockets[0])
|
||
var target_y := int(sockets[1])
|
||
var target_map := int(sockets[2])
|
||
return {
|
||
"ok": true,
|
||
"action": "WARP_TO_COORDS",
|
||
"consumed": true,
|
||
"target_x": target_x,
|
||
"target_y": target_y,
|
||
"target_map": target_map,
|
||
"msg": "使用了地标卷轴,正在传送至 (%d, %d)!" % [target_x, target_y]
|
||
}
|
||
return {"ok": false, "msg": "未知的传送卷轴"}
|