-- 刷怪脚本:带 AI 与等级设置 local commands = {} -- 全局设置:是否把坐标保留 2 位小数 local stripCoordinates = true -- 设为 false 显示完整坐标 -- 全局设置:是否在游戏内聊天输出 local showInChat = false -- 设为 false 仅控制台输出 -- 全局设置:网格间距(按网格铺开生成时的距离) local gridSpacing = 3 -- 默认 3 米 -- 全局设置:是否全部生成在同一坐标 local spawnInSameLocation = false -- 设为 true 则同点生成 -- 回调序列号(用于生成唯一的回调名称) local callbackSeq = 0 -- 工具函数 local function round2(n) return math.floor(n * 100 + 0.5) / 100 end local function countTable(t) local n = 0 for _ in pairs(t) do n = n + 1 end return n end -- 加载外部敌人名称映射表(使用 loadfile) local function loadNameOverrides() local paths = { "OpenWF/Scripts/EnemyNameOverrides.pluto", "Scripts/EnemyNameOverrides.pluto", } for _, path in ipairs(paths) do local chunk, err = loadfile(path) if chunk then local success, data = pcall(chunk) if success and type(data) == "table" then print("✓ 已加载 " .. countTable(data) .. " 个敌人名称映射(" .. path .. ")") return data end end end print("⚠ 警告:无法加载 EnemyNameOverrides.pluto,所有敌人需要使用完整路径") return {} end local nameOverrides = loadNameOverrides() -- 基于覆盖表构建 名称<->路径 的双向索引(兼容旧格式:路径->名称,或新格式:名称->路径) local nameToPath = {} local pathToName = {} local function normalizeName(s) if not s then return s end -- 去掉所有空白字符,避免“苦 难”“苦难 ”等变体匹配失败 return (s:gsub("%s+", "")) end local function addIndex(name, path) if not name or name == "" or not path or path == "" then return end if not nameToPath[name] then nameToPath[name] = path end local n2 = normalizeName(name) if n2 ~= name and not nameToPath[n2] then nameToPath[n2] = path end if not pathToName[path] then pathToName[path] = name end end local function rebuildIndexes() nameToPath = {} pathToName = {} for k, v in pairs(nameOverrides) do if type(k) == "string" and type(v) == "string" and k ~= "" and v ~= "" then if k:sub(1,1) == "/" then addIndex(v, k) else addIndex(k, v) end end end end rebuildIndexes() local function logSpawn(enemyName, pos, level, note) local x, y, z = pos.x, pos.y, pos.z if stripCoordinates then x = round2(x) y = round2(y) z = round2(z) end local tag = note and (" " .. note) or "" local msg = string.format("已生成 '%s'%s 坐标: %.2f, %.2f, %.2f 等级: %d", enemyName, tag, x, y, z, level) print(msg) if showInChat then chat_system_reply(msg) end end local function resolveDisplayName(path, agent, fallback) return pathToName[path] or fallback end local function computeSpawnPosition(basePosition, index, count) if spawnInSameLocation then return basePosition end local gridSize = math.ceil(math.sqrt(count)) local i0 = index - 1 -- zero-based for grid math local col = i0 % gridSize local row = math.floor(i0 / gridSize) return basePosition + Vector(col * gridSpacing, 0, row * gridSpacing) end -- 实际生成敌人的函数(提取重复代码) local function spawnEnemies(enemyType, path, count, level, note) local basePosition = gRegion:GetLocalPlayerAvatar():GetPosition() local npcMgr = gRegion:GetNpcMgr() local agentType = Type(path) for i = 1, count do local spawnPosition = computeSpawnPosition(basePosition, i, count) local agent = npcMgr:CreateAgentAtPosition(agentType, spawnPosition, ZERO_ROTATION, EMPTY_SYMBOL, level) local displayName = resolveDisplayName(path, agent, enemyType) logSpawn(displayName, spawnPosition, level, note) end end -- 请求资源并在回调后生成 local function requestAndSpawn(enemyType, path, count, level, callbackName) print("请求加载资源: " .. path) gGameRules:RequestResource(path, callbackName) local callbackReceived = false local fallbackTriggered = false local fallbackTimeout = 180 -- 超时 180 帧(约 3 秒 @ 60fps) repeat while evt := owf_next_event() do if evt.type == OWF_EVT_CALLBACK and evt.name == callbackName then callbackReceived = true print("已接收回调,资源加载完成: " .. path) spawnEnemies(enemyType, path, count, level, nil) return end end fallbackTimeout = fallbackTimeout - 1 if fallbackTimeout <= 0 and not callbackReceived then fallbackTriggered = true print("未在超时时间内收到回调,进入直接生成回退路径。") break end yield() until callbackReceived if fallbackTriggered then spawnEnemies(enemyType, path, count, level, "(回退)") elseif not callbackReceived then print("错误:资源加载失败或不可用: " .. path) end end commands["生成"] = function(text) local parts = text:split(" ") local enemyOrPath = parts[2] -- 参数验证 if not enemyOrPath or enemyOrPath == "" then local usage = "用法: /生成 <名称或路径> [数量] [等级]\n示例: /生成 苦难 5 100" print(usage) if showInChat then chat_system_reply(usage) end return end local count = tonumber(parts[3]) or 1 local level = tonumber(parts[4]) or 10 local path = enemyOrPath -- 若输入不是路径,尝试按"名称->路径"映射解析 if path and path:sub(1, 1) ~= "/" then local mapped = nameToPath[path] or nameToPath[normalizeName(path)] if mapped then path = mapped else print("警告:未找到名称 '" .. enemyOrPath .. "' 的映射,将尝试作为路径使用") end end print(string.format("开始生成 | 路径: %s | 数量: %d | 等级: %d", path, count, level)) callbackSeq = callbackSeq + 1 local callbackName = "streamed_enemy_" .. tostring(callbackSeq) requestAndSpawn(enemyOrPath, path, count, level, callbackName) end -- 调试:查询名称映射情况 commands["查名"] = function(text) local parts = text:split(" ") local key = parts[2] if not key or key == "" then local usage = "用法: /查名 <名称>" print(usage) if showInChat then chat_system_reply(usage) end return end local norm = normalizeName(key) local direct = nameOverrides[key] local directNorm = nameOverrides[norm] local mapped = nameToPath[key] or nameToPath[norm] print(string.format("查名 | key='%s' norm='%s' | 覆盖表[key]=%s 覆盖表[norm]=%s | 映射=%s", key, norm, tostring(direct), tostring(directNorm), tostring(mapped))) end -- 重载命令(重新从外部文件加载) commands["重载名表"] = function(text) local newData = loadNameOverrides() if newData and countTable(newData) > 0 then nameOverrides = newData rebuildIndexes() local msg = "✓ 名称映射已重载,条目: " .. tostring(countTable(nameOverrides)) print(msg) if showInChat then chat_system_reply(msg) end else local msg = "✗ 重载失败:无法加载 EnemyNameOverrides.pluto" print(msg) if showInChat then chat_system_reply(msg) end end end -- 帮助命令 commands["帮助"] = function(text) local help = [[ 可用命令: /生成 <名称或路径> [数量] [等级] - 生成敌人 /查名 <名称> - 查询敌人名称映射 /重载名表 - 重新加载外部名称映射文件 /帮助 - 显示此帮助信息 示例: /生成 苦难 5 100 /生成 /Lotus/Types/Enemies/Acolytes/AreaCasterAcolyteAgent 1 150 /查名 苦难 ]] print(help) if showInChat then chat_system_reply(help) end end -- 订阅聊天命令并处理事件 for prefix in commands do chat_subscribe_prefix("/" .. prefix, true) end repeat while evt := owf_next_event() do if evt.type == OWF_EVT_SUBMIT_CHAT_MESSAGE and evt.text and evt.text:sub(1,1) == "/" then local cmd = evt.text:match("^/([^%s]+)") local f = cmd and commands[cmd] if f then f(evt.text) end end end until yield() -- 示例:/生成 苦难 5 100