# auto_combat_bot_system.gd —— Metin2 40250 自动战斗猎杀挂机助手 1:1 # 对照 40250 服务端 char_battle.cpp, bot.cpp, item_manager.cpp class_name AutoCombatBotSystem extends RefCounted signal bot_toggled(enabled: bool) signal potion_used(potion_vnum: int, ptype: String) signal target_locked(target_id: String) signal auto_attack_fired(target_id: String, damage: int) signal auto_skill_fired(skill_vnum: int, target_id: String) signal auto_loot_collected(item_name: String, slot: int) # 红药与蓝药 VNUM const HP_POTION_VNUMS: Array = [27001, 27002, 27003] # 小/中/大红 const SP_POTION_VNUMS: Array = [27004, 27005, 27006] # 小/中/大蓝 var is_enabled: bool = false var auto_potion_enabled: bool = true var hp_threshold: float = 0.50 # 50% HP 触发喝红 var sp_threshold: float = 0.40 # 40% SP 触发喝蓝 var auto_attack_enabled: bool = true var scan_radius: float = 15.0 var attack_range: float = 3.0 var auto_skill_enabled: bool = true var assigned_skills: Array = [] var skill_cooldowns: Dictionary = {} var auto_loot_enabled: bool = true var loot_radius: float = 8.0 var current_target_id: String = "" var potion_cooldown: float = 0.0 # 开启/关闭挂机内挂 func toggle_bot(enable: bool) -> Dictionary: is_enabled = enable if not is_enabled: current_target_id = "" bot_toggled.emit(is_enabled) return {"ok": true, "enabled": is_enabled, "msg": "自动战斗猎杀助手已%s!" % ("开启" if is_enabled else "关闭")} # 帧心跳驱动挂机逻辑 func update_bot(delta: float, player_data: Dictionary, inventory: Array, nearby_mobs: Array, nearby_drops: Array) -> Dictionary: if not is_enabled: return {"status": "disabled"} if potion_cooldown > 0.0: potion_cooldown = max(0.0, potion_cooldown - delta) for sk in skill_cooldowns.keys(): if skill_cooldowns[sk] > 0.0: skill_cooldowns[sk] = max(0.0, skill_cooldowns[sk] - delta) var p_pos: Vector3 = player_data.get("pos", Vector3.ZERO) var p_hp: int = int(player_data.get("hp", 100)) var p_max_hp: int = max(1, int(player_data.get("max_hp", 100))) var p_sp: int = int(player_data.get("sp", 100)) var p_max_sp: int = max(1, int(player_data.get("max_sp", 100))) # 1. 自动智能喝药判定 if auto_potion_enabled and potion_cooldown <= 0.0: if float(p_hp) / float(p_max_hp) <= hp_threshold: if _use_potion(HP_POTION_VNUMS, inventory, player_data, "hp"): potion_cooldown = 1.0 # 喝药公共冷却 1 秒 if float(p_sp) / float(p_max_sp) <= sp_threshold: if _use_potion(SP_POTION_VNUMS, inventory, player_data, "sp"): potion_cooldown = 1.0 # 2. 自动拾取掉落物 if auto_loot_enabled: _process_auto_loot(p_pos, nearby_drops, inventory) # 3. 自动搜敌与战斗打击 if auto_attack_enabled: var act = _process_auto_combat(delta, p_pos, player_data, nearby_mobs) return act return {"status": "idle"} # 自动喝药 func _use_potion(potion_vnums: Array, inventory: Array, player_data: Dictionary, ptype: String) -> bool: for i in range(inventory.size()): if inventory[i] != null and int(inventory[i].get("vnum", 0)) in potion_vnums: var pot = inventory[i] var pvnum = int(pot["vnum"]) var cnt = int(pot.get("count", 1)) if cnt > 1: pot["count"] = cnt - 1 else: inventory[i] = null var rec = 1500 if pvnum in [27003, 27006] else 800 if ptype == "hp": player_data["hp"] = min(int(player_data["max_hp"]), int(player_data["hp"]) + rec) else: player_data["sp"] = min(int(player_data["max_sp"]), int(player_data["sp"]) + rec) potion_used.emit(pvnum, ptype) return true return false # 自动拾取 func _process_auto_loot(player_pos: Vector3, drops: Array, inventory: Array) -> void: for d in drops: if d.get("looted", false): continue var d_pos: Vector3 = d.get("pos", Vector3.ZERO) if player_pos.distance_to(d_pos) <= loot_radius: # 放入背包 for i in range(inventory.size()): if inventory[i] == null: inventory[i] = { "vnum": d.get("vnum", 1), "name": d.get("name", "战利品"), "count": int(d.get("count", 1)) } d["looted"] = true auto_loot_collected.emit(d["name"], i) break # 自动锁敌与攻击 func _process_auto_combat(delta: float, player_pos: Vector3, player_data: Dictionary, mobs: Array) -> Dictionary: var closest_mob = null var min_dist = scan_radius + 1.0 for m in mobs: if not m.get("alive", true): continue var m_pos: Vector3 = m.get("pos", Vector3.ZERO) var dist = player_pos.distance_to(m_pos) if dist <= scan_radius and dist < min_dist: min_dist = dist closest_mob = m if closest_mob == null: current_target_id = "" return {"status": "no_targets"} var target_id = str(closest_mob.get("id", "mob")) if current_target_id != target_id: current_target_id = target_id target_locked.emit(target_id) var target_pos: Vector3 = closest_mob.get("pos", Vector3.ZERO) var dist_to_target = player_pos.distance_to(target_pos) # 走位靠拢 if dist_to_target > attack_range: var new_pos = player_pos.move_toward(target_pos, 5.0 * delta) player_data["pos"] = new_pos return {"status": "moving_to_target", "target": target_id, "dist": dist_to_target} # 尝试释放技能 if auto_skill_enabled: for sk in assigned_skills: if skill_cooldowns.get(sk, 0.0) <= 0.0: skill_cooldowns[sk] = 8.0 # 默认技能 8 秒冷却 auto_skill_fired.emit(sk, target_id) var sk_dmg = int(float(player_data.get("atk", 500)) * 2.0) closest_mob["hp"] = max(0, int(closest_mob.get("hp", 1000)) - sk_dmg) if closest_mob["hp"] <= 0: closest_mob["alive"] = false return {"status": "skill_attack", "skill": sk, "damage": sk_dmg} # 普通挥砍攻击 var atk_dmg = int(player_data.get("atk", 500)) closest_mob["hp"] = max(0, int(closest_mob.get("hp", 1000)) - atk_dmg) if closest_mob["hp"] <= 0: closest_mob["alive"] = false auto_attack_fired.emit(target_id, atk_dmg) return {"status": "basic_attack", "target": target_id, "damage": atk_dmg}