266 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			266 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
-- 刷怪脚本:带 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 |