Files
mtgodot-poc/project/event_manager_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

172 lines
5.4 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# event_manager_system.gd —— Metin2 40250 单人模式全服掉落活动与倍率调度器 1:1
# 对照 40250 服务端 cmd_gm.cpp (do_priv_empire), priv_manager.cpp, event.cpp, Items 50011, 50037
class_name EventManagerSystem
extends RefCounted
signal rate_changed(rate_type: String, multiplier: float)
signal event_state_changed(event_name: String, is_active: bool)
signal event_box_dropped(box_vnum: int, box_name: String)
const ITEM_MOONLIGHT_BOX := 50011 # 月光宝盒
const ITEM_HEXAGONAL_BOX := 50037 # 六角宝盒
# 宝盒奖励奖池 (40250 官方 special_item_group.txt 1:1)
const EVENT_BOX_LOOT: Dictionary = {
50011: [
{"vnum": 25040, "name": "祝福卷轴", "count": 1, "weight": 30},
{"vnum": 71084, "name": "高级附魔石", "count": 1, "weight": 25},
{"vnum": 71085, "name": "高级洗练石", "count": 1, "weight": 25},
{"vnum": 70024, "name": "祝福大理石", "count": 1, "weight": 10},
{"vnum": 72723, "name": "月之灵药(特)", "count": 1, "weight": 10}
],
50037: [
{"vnum": 72001, "name": "经验之戒", "count": 1, "weight": 20},
{"vnum": 72002, "name": "盗贼手套", "count": 1, "weight": 20},
{"vnum": 27102, "name": "绿药水(大)", "count": 5, "weight": 30},
{"vnum": 27105, "name": "紫药水(大)", "count": 5, "weight": 30}
]
}
var exp_rate: float = 1.0
var drop_rate: float = 1.0
var gold_rate: float = 1.0
var is_moonlight_active: bool = false
var is_hexagonal_active: bool = false
# 设置倍率 (exp, drop, gold)
func set_rate(rate_type: String, multiplier: float) -> Dictionary:
if multiplier < 1.0 or multiplier > 5.0:
return {"ok": false, "reason": "INVALID_MULTIPLIER", "msg": "倍率必须在 1.0 到 5.0 之间!"}
match rate_type:
"exp":
exp_rate = multiplier
"drop":
drop_rate = multiplier
"gold":
gold_rate = multiplier
_:
return {"ok": false, "reason": "INVALID_RATE_TYPE", "msg": "未知的倍率类型!"}
rate_changed.emit(rate_type, multiplier)
return {
"ok": true,
"rate_type": rate_type,
"multiplier": multiplier,
"msg": "全服【%s】倍率已设置为 %.1fx" % [rate_type.to_upper(), multiplier]
}
# 开关经典限时掉落活动
func toggle_event(event_name: String, enable: bool) -> Dictionary:
match event_name:
"moonlight":
is_moonlight_active = enable
"hexagonal":
is_hexagonal_active = enable
_:
return {"ok": false, "reason": "INVALID_EVENT_NAME", "msg": "未知的活动名称!"}
event_state_changed.emit(event_name, enable)
var state_str := "开启" if enable else "关闭"
var ev_desc := "月光宝盒掉落狂欢" if event_name == "moonlight" else "六角宝盒冲级庆典"
return {
"ok": true,
"event_name": event_name,
"is_active": enable,
"msg": "【%s】活动已%s" % [ev_desc, state_str]
}
# 怪物击杀时判定额外活动宝盒掉落
func check_event_drops(mob_level: int, player_level: int, forced_roll: float = -1.0) -> Array:
var drops: Array = []
# 等级差在 15 级以内才掉落活动宝盒
if absi(mob_level - player_level) > 15:
return drops
# 1. 月光宝盒判定 (基础概率 10% * drop_rate)
if is_moonlight_active:
var chance := 0.10 * drop_rate
var roll := forced_roll if forced_roll >= 0.0 else randf()
if roll <= chance:
drops.append({"vnum": ITEM_MOONLIGHT_BOX, "name": "月光宝盒", "count": 1})
event_box_dropped.emit(ITEM_MOONLIGHT_BOX, "月光宝盒")
# 2. 六角宝盒判定 (基础概率 15% * drop_rate)
if is_hexagonal_active:
var chance := 0.15 * drop_rate
var roll := forced_roll if forced_roll >= 0.0 else randf()
if roll <= chance:
drops.append({"vnum": ITEM_HEXAGONAL_BOX, "name": "六角宝盒", "count": 1})
event_box_dropped.emit(ITEM_HEXAGONAL_BOX, "六角宝盒")
return drops
# 计算经倍率加成后的经验与金币
func calculate_exp(base_exp: int) -> int:
return int(round(base_exp * exp_rate))
func calculate_gold(base_gold: int) -> int:
return int(round(base_gold * gold_rate))
# 打开活动宝盒
func open_event_box(slot_idx: int, inventory: Array) -> Dictionary:
if slot_idx < 0 or slot_idx >= inventory.size():
return {"ok": false, "reason": "INVALID_SLOT", "msg": "无效的背包栏位!"}
var box = inventory[slot_idx]
if box == null:
return {"ok": false, "reason": "EMPTY_SLOT", "msg": "该格子上没有物品!"}
var box_vnum: int = int(box.get("vnum", 0))
if not EVENT_BOX_LOOT.has(box_vnum):
return {"ok": false, "reason": "NOT_AN_EVENT_BOX", "msg": "这不是活动宝盒!"}
var pool: Array = EVENT_BOX_LOOT[box_vnum]
# 权重抽奖
var total_weight := 0
for item in pool:
total_weight += int(item.get("weight", 1))
var roll := randi_range(1, total_weight)
var accumulated := 0
var reward: Dictionary = {}
for item in pool:
accumulated += int(item.get("weight", 1))
if roll <= accumulated:
reward = item.duplicate()
break
if reward.is_empty():
reward = pool[0].duplicate()
# 扣除宝盒,存入奖励
var cnt: int = int(box.get("count", 1))
if cnt <= 1:
inventory[slot_idx] = null
else:
box["count"] = cnt - 1
var stored_slot := -1
for i in range(inventory.size()):
if inventory[i] == null:
inventory[i] = {
"vnum": reward["vnum"],
"name": reward["name"],
"count": reward.get("count", 1)
}
stored_slot = i
break
return {
"ok": true,
"reward": reward,
"stored_slot": stored_slot,
"msg": "打开活动宝盒,获得了【%s x%d】!" % [reward["name"], reward.get("count", 1)]
}