- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
172 lines
5.4 KiB
GDScript
172 lines
5.4 KiB
GDScript
# 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)]
|
||
}
|