Files
mtgodot-poc/project/point_reset_system.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

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()