# gm_toolkit_system.gd —— Metin2 40250 单人单机内置 GM 控制台与命令解析器 1:1 # 对照 40250 服务端 cmd_gm.cpp, cmd.cpp class_name GmToolkitSystem extends RefCounted signal command_executed(cmd: String, success: bool, result_msg: String) signal player_warped(target_pos: Vector3, map_name: String) signal mob_spawned(vnum: int, count: int, pos: Vector3) const MAP_PRESETS: Dictionary = { "village": Vector3(0.0, 0.0, 0.0), "sohan": Vector3(400.0, 0.0, 600.0), # 冰山 Mount Sohan "desert": Vector3(200.0, 0.0, 300.0), # 永壁沙漠 Yongbi Desert "temple": Vector3(100.0, 0.0, 500.0), # 黄龙神殿 Hwang Temple "flame": Vector3(600.0, 0.0, 200.0), # 烈焰原野 Doyyumhwan "catacomb": Vector3(500.0, 0.0, 500.0) # 地下墓穴 Devil's Catacomb } # 解析并执行 GM 斜杠指令 func execute_command(cmd_string: String, ctx: Dictionary) -> Dictionary: var trimmed := cmd_string.strip_edges() if not trimmed.begins_with("/"): return {"ok": false, "reason": "NOT_A_COMMAND", "msg": "指令必须以 '/' 开头!"} var parts := trimmed.substr(1).split(" ", false) if parts.is_empty(): return {"ok": false, "reason": "EMPTY_COMMAND"} var action := parts[0].to_lower() var res: Dictionary = {} match action: "item": res = _handle_item(parts, ctx) "level": res = _handle_level(parts, ctx) "gold": res = _handle_gold(parts, ctx) "set_skill", "skill": res = _handle_skill(parts, ctx) "warp", "map": res = _handle_warp(parts, ctx) "spawn_mob", "mob", "m": res = _handle_mob(parts, ctx) "help": res = _handle_help() _: res = {"ok": false, "reason": "UNKNOWN_COMMAND", "msg": "未知的 GM 指令【%s】!输入 /help 查看支持的指令列表。" % action} command_executed.emit(action, res.get("ok", false), res.get("msg", "")) return res # /item [count] func _handle_item(parts: PackedStringArray, ctx: Dictionary) -> Dictionary: if parts.size() < 2 or not parts[1].is_valid_int(): return {"ok": false, "reason": "SYNTAX_ERROR", "msg": "用法: /item <物品代码vnum> [数量count]"} var vnum: int = int(parts[1]) var count: int = 1 if parts.size() >= 3 and parts[2].is_valid_int(): count = maxi(1, int(parts[2])) var inventory: Array = ctx.get("inventory", []) var empty_slot := -1 for i in range(inventory.size()): if inventory[i] == null: empty_slot = i break if empty_slot == -1: return {"ok": false, "reason": "INVENTORY_FULL", "msg": "背包已满,无法生成物品!"} inventory[empty_slot] = { "vnum": vnum, "name": "道具_%d" % vnum, "count": count } return { "ok": true, "vnum": vnum, "count": count, "slot": empty_slot, "msg": "【GM】已成功生成物品 [vnum: %d, count: %d] 至背包格子 %d!" % [vnum, count, empty_slot] } # /level <1-105> func _handle_level(parts: PackedStringArray, ctx: Dictionary) -> Dictionary: if parts.size() < 2 or not parts[1].is_valid_int(): return {"ok": false, "reason": "SYNTAX_ERROR", "msg": "用法: /level <1-105>"} var target_lv: int = clampi(int(parts[1]), 1, 105) var player_data: Dictionary = ctx.get("player_data", {}) player_data["level"] = target_lv return { "ok": true, "level": target_lv, "msg": "【GM】角色等级已修改为 Lv %d!" % target_lv } # /gold func _handle_gold(parts: PackedStringArray, ctx: Dictionary) -> Dictionary: if parts.size() < 2 or not parts[1].is_valid_int(): return {"ok": false, "reason": "SYNTAX_ERROR", "msg": "用法: /gold <金币数额>"} var amount: int = int(parts[1]) var player_data: Dictionary = ctx.get("player_data", {}) var current: int = int(player_data.get("gold", 0)) var new_gold: int = maxi(0, current + amount) player_data["gold"] = new_gold return { "ok": true, "added": amount, "total_gold": new_gold, "msg": "【GM】金币已注入!当前金币:%d Yang。" % new_gold } # /set_skill <1-40> func _handle_skill(parts: PackedStringArray, ctx: Dictionary) -> Dictionary: if parts.size() < 3 or not parts[1].is_valid_int() or not parts[2].is_valid_int(): return {"ok": false, "reason": "SYNTAX_ERROR", "msg": "用法: /set_skill <技能ID> <等级1-40> (40=P级)"} var skill_id: int = int(parts[1]) var skill_lv: int = clampi(int(parts[2]), 1, 40) var skills: Dictionary = ctx.get("skills", {}) skills[skill_id] = skill_lv var grade_str := "普通" if skill_lv >= 40: grade_str = "P级 (Perfect Master)" elif skill_lv >= 30: grade_str = "G级 (Grand Master)" elif skill_lv >= 20: grade_str = "M级 (Master)" return { "ok": true, "skill_id": skill_id, "skill_level": skill_lv, "grade": grade_str, "msg": "【GM】技能 [%d] 等级已调整为 %d (%s)!" % [skill_id, skill_lv, grade_str] } # /warp 或 /map func _handle_warp(parts: PackedStringArray, ctx: Dictionary) -> Dictionary: var target_pos := Vector3.ZERO var map_name := "custom" if parts.size() >= 3 and parts[1].is_valid_float() and parts[2].is_valid_float(): target_pos = Vector3(float(parts[1]), 0.0, float(parts[2])) elif parts.size() >= 2: var preset_key: String = parts[1].to_lower() if MAP_PRESETS.has(preset_key): target_pos = MAP_PRESETS[preset_key] map_name = preset_key else: return {"ok": false, "reason": "UNKNOWN_MAP", "msg": "未知的预设地图!可用地图: %s" % str(MAP_PRESETS.keys())} else: return {"ok": false, "reason": "SYNTAX_ERROR", "msg": "用法: /warp 或 /map <地图名>"} player_warped.emit(target_pos, map_name) return { "ok": true, "position": target_pos, "map_name": map_name, "msg": "【GM】已瞬移传送至目标坐标 (%.1f, %.1f) [%s]!" % [target_pos.x, target_pos.z, map_name] } # /spawn_mob [count] func _handle_mob(parts: PackedStringArray, ctx: Dictionary) -> Dictionary: if parts.size() < 2 or not parts[1].is_valid_int(): return {"ok": false, "reason": "SYNTAX_ERROR", "msg": "用法: /spawn_mob <怪代码vnum> [数量count]"} var mob_vnum: int = int(parts[1]) var count: int = 1 if parts.size() >= 3 and parts[2].is_valid_int(): count = maxi(1, int(parts[2])) var player_pos: Vector3 = ctx.get("player_pos", Vector3.ZERO) mob_spawned.emit(mob_vnum, count, player_pos) return { "ok": true, "mob_vnum": mob_vnum, "count": count, "spawn_pos": player_pos, "msg": "【GM】已在身旁召唤怪物 [vnum: %d, count: %d]!" % [mob_vnum, count] } # /help func _handle_help() -> Dictionary: return { "ok": true, "commands": [ "/item [count] - 生成物品", "/level <1-105> - 设定等级", "/gold - 注入金币", "/set_skill <1-40> - 设定技能等级(40=P级)", "/map - 传送至指定地图", "/warp - 坐标瞬移", "/spawn_mob [count] - 刷怪/召唤首领" ], "msg": "【GM指令帮助】输入对应命令快速调试单机环境!" }