- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
131 lines
3.7 KiB
GDScript
131 lines
3.7 KiB
GDScript
# point_reset_system.gd
|
|
# 40250 官方 1:1 属性点/技能重置洗点与两阶段确认系统
|
|
# 对照: uipointreset.py, questiondialog2.py, char.cpp, cmd_general.cpp
|
|
class_name PointResetSystem
|
|
extends RefCounted
|
|
|
|
const RESET_COST_YANG: int = 500
|
|
|
|
# 40250 官方四职业初始属性基准值 [CON, INT, STR, DEX]
|
|
const START_STAT: Dictionary = {
|
|
0: [4, 3, 6, 3], # WARRIOR (战士)
|
|
1: [3, 3, 4, 6], # ASSASSIN (刺客)
|
|
2: [3, 5, 5, 3], # SURA (修罗)
|
|
3: [4, 6, 3, 3] # SHAMAN (萨满)
|
|
}
|
|
|
|
# 状态枚举
|
|
enum DialogState {
|
|
CLOSED = 0,
|
|
INITIAL_PROMPT = 1,
|
|
CONFIRMATION = 2
|
|
}
|
|
|
|
var state: DialogState = DialogState.CLOSED
|
|
|
|
var player_job: int = 0
|
|
var player_gold: int = 0
|
|
var current_stats: Dictionary = {"CON": 4, "INT": 3, "STR": 6, "DEX": 3}
|
|
var unallocated_stat_points: int = 0
|
|
|
|
# 技能重置支持
|
|
var player_level: int = 1
|
|
var current_skill_tree: int = 0 # 0=未选, 1=分支A, 2=分支B
|
|
var unallocated_skill_points: int = 0
|
|
var active_skills: Dictionary = {}
|
|
|
|
var last_packet_sent: String = ""
|
|
|
|
signal dialog_opened(cost: int)
|
|
signal confirm_dialog_opened(warning_text: String)
|
|
signal dialog_closed()
|
|
signal reset_completed(new_stats: Dictionary, refund_points: int, remaining_gold: int)
|
|
signal reset_failed(reason: String)
|
|
|
|
func init_player_data(job: int, level: int, gold: int, stats: Dictionary, stat_pts: int, skill_tree: int, skill_pts: int, skills: Dictionary) -> void:
|
|
player_job = job
|
|
player_level = level
|
|
player_gold = gold
|
|
current_stats = stats.duplicate()
|
|
unallocated_stat_points = stat_pts
|
|
current_skill_tree = skill_tree
|
|
unallocated_skill_points = skill_pts
|
|
active_skills = skills.duplicate()
|
|
|
|
func open_dialog() -> void:
|
|
state = DialogState.INITIAL_PROMPT
|
|
dialog_opened.emit(RESET_COST_YANG)
|
|
|
|
func open_confirm_dialog() -> bool:
|
|
if state != DialogState.INITIAL_PROMPT:
|
|
return false
|
|
|
|
# 检查金币是否满足 500 Yang
|
|
if player_gold < RESET_COST_YANG:
|
|
reset_failed.emit("NOT_ENOUGH_MONEY")
|
|
close_dialog()
|
|
return false
|
|
|
|
state = DialogState.CONFIRMATION
|
|
var warning_msg = "重置后所有属性点将被重置!确定继续吗?"
|
|
confirm_dialog_opened.emit(warning_msg)
|
|
return true
|
|
|
|
func accept_reset() -> bool:
|
|
if state != DialogState.CONFIRMATION:
|
|
return false
|
|
|
|
if player_gold < RESET_COST_YANG:
|
|
reset_failed.emit("NOT_ENOUGH_MONEY")
|
|
close_dialog()
|
|
return false
|
|
|
|
# 扣除 500 金币
|
|
player_gold -= RESET_COST_YANG
|
|
last_packet_sent = "/pointreset"
|
|
|
|
# 回退属性点
|
|
var base_stat = START_STAT.get(player_job, [4, 3, 6, 3])
|
|
var refund_con = maxi(0, current_stats["CON"] - base_stat[0])
|
|
var refund_int = maxi(0, current_stats["INT"] - base_stat[1])
|
|
var refund_str = maxi(0, current_stats["STR"] - base_stat[2])
|
|
var refund_dex = maxi(0, current_stats["DEX"] - base_stat[3])
|
|
var total_refund = refund_con + refund_int + refund_str + refund_dex
|
|
|
|
unallocated_stat_points += total_refund
|
|
current_stats["CON"] = base_stat[0]
|
|
current_stats["INT"] = base_stat[1]
|
|
current_stats["STR"] = base_stat[2]
|
|
current_stats["DEX"] = base_stat[3]
|
|
|
|
reset_completed.emit(current_stats, total_refund, player_gold)
|
|
close_dialog()
|
|
return true
|
|
|
|
# 官方 30 级以下老妇人技能树重洗 (Old Woman Skill Reset)
|
|
func reset_skills_at_old_woman() -> bool:
|
|
if player_level > 30:
|
|
reset_failed.emit("LEVEL_TOO_HIGH")
|
|
return false
|
|
|
|
# 40250 老妇人洗技能费用公式: level * 1000 + 2000 Yang
|
|
var cost = player_level * 1000 + 2000
|
|
if player_gold < cost:
|
|
reset_failed.emit("NOT_ENOUGH_MONEY")
|
|
return false
|
|
|
|
player_gold -= cost
|
|
var total_skill_pts = 0
|
|
for sk_id in active_skills:
|
|
total_skill_pts += active_skills[sk_id]
|
|
|
|
active_skills.clear()
|
|
unallocated_skill_points += total_skill_pts
|
|
current_skill_tree = 0 # 允许重新转职选择专精分支
|
|
|
|
return true
|
|
|
|
func close_dialog() -> void:
|
|
state = DialogState.CLOSED
|
|
dialog_closed.emit()
|