openwf/Scripts/敌人生成.pluto

266 lines
8.6 KiB
Plaintext
Raw Normal View History

2025-10-11 22:01:07 +08:00
-- 刷怪脚本:带 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
2025-10-12 00:04:56 +08:00
-- 加载外部敌人名称映射表(使用 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()
2025-10-11 22:01:07 +08:00
-- 基于覆盖表构建 名称<->路径 的双向索引(兼容旧格式:路径->名称,或新格式:名称->路径)
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
2025-10-12 00:04:56 +08:00
-- 重载命令(重新从外部文件加载)
2025-10-11 22:01:07 +08:00
commands["重载名表"] = function(text)
2025-10-12 00:04:56 +08:00
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
2025-10-11 22:01:07 +08:00
end
-- 帮助命令
commands["帮助"] = function(text)
local help = [[
可用命令:
/生成 <名称或路径> [数量] [等级] - 生成敌人
/查名 <名称> - 查询敌人名称映射
2025-10-12 00:04:56 +08:00
/重载名表 - 重新加载外部名称映射文件
2025-10-11 22:01:07 +08:00
/帮助 - 显示此帮助信息
示例:
/生成 苦难 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()
2025-10-12 09:01:22 +08:00
-- 示例:/生成 苦难 5 100