Lua 脚本在 WoW 模拟器中的高级应用 原创

温馨提示:
本文最后更新于 2026-04-02,已超过 2 天没有更新。 若文章内的图片失效(无法正常加载),请留言反馈或直接 联系我

Lua 脚本为魔兽世界模拟器提供了强大的自定义能力。Eluna 引擎允许开发者无需编译服务器即可添加新功能。本文深入讲解 Lua 脚本的高级应用。

一、Eluna 引擎介绍

1.1 什么是 Eluna

Eluna 是 TrinityCore 和 AzerothCore 的 Lua 脚本引擎,特点:

  • 无需编译服务器即可添加功能
  • 热重载:修改脚本后立即生效
  • 丰富的 API:支持玩家、NPC、物品、事件等
  • 性能优秀:Lua 是轻量级脚本语言

1.2 安装与配置

# AzerothCore 启用 Eluna
cd azerothcore-wotlk
git clone https://github.com/azerothcore/Mod-Eluna.git modules/mod-eluna
cmake ../ -DWITH_ELUNA=1
make install

# 配置文件
etc/worldserver.conf
Eluna.Enabled = 1
Eluna.StartOnBoot = 1
Eluna.ReloadOnEvent = 1

1.3 目录结构

lua_scripts/
├── event_scripts/      # 事件脚本
├── npc_scripts/        # NPC 脚本
├── item_scripts/       # 物品脚本
├── go_scripts/         # 游戏对象脚本
├── player_scripts/     # 玩家脚本
└── global_scripts/     # 全局函数

二、事件系统

2.1 玩家事件

-- 玩家登录
function OnPlayerLogin(event, player)
    SendBroadcastMessage(player, "欢迎来到服务器!")
    AddItem(player, 1000, 1)  -- 赠送新手礼包
end
RegisterPlayerEvent(3, OnPlayerLogin)

-- 玩家升级
function OnPlayerLevelUp(event, player, level)
    SendBroadcastMessage(player, "恭喜你升到"..level.."级!")
    if level == 60 then
        SendBroadcastMessage(player, "满级奖励已发放!")
        AddItem(player, 5000, 10)
    end
end
RegisterPlayerEvent(12, OnPlayerLevelUp)

-- 玩家击杀
function OnPlayerKill(event, killer, victim)
    if victim:IsPlayer() then
        SendBroadcastMessage(killer, "你击杀了 "..victim:GetName())
        AddHonor(killer, 10)
    end
end
RegisterPlayerEvent(8, OnPlayerKill)

2.2 NPC 事件

-- NPC 对话
local NPC_ID = 12345

function OnGossipHello(event, player, object)
    ClearGossipMenuFor(player)
    AddGossipItemFor(player, 0, "开始任务", 0, 1)
    AddGossipItemFor(player, 0, "离开", 0, 2)
    SendGossipMenuFor(player, 1, object:GetGUID())
end

function OnGossipSelect(event, player, object, sender, intid, code, menu_id)
    ClearGossipMenuFor(player)
    if intid == 1 then
        SendBroadcastMessage(player, "任务开始!")
        player:SendUnitSay("加油!", 0)
    end
    CloseGossipMenuFor(player)
end

RegisterCreatureGossipEvent(NPC_ID, OnGossipHello, OnGossipSelect)

2.3 战斗事件

-- NPC 进入战斗
function OnJustEngaged(event, creature, target)
    creature:SendUnitSay("入侵者!受死吧!", 0)
    creature:CastSpell(target, 12345, false)
end

-- NPC 死亡
function OnJustDied(event, creature, killer)
    creature:SendUnitSay("我不会...这么轻易...", 0)
    if killer:IsPlayer() then
        killer:SendBroadcastMessage("你获得了胜利!")
    end
end

RegisterCreatureEvent(NPC_ID, 1, OnJustEngaged)
RegisterCreatureEvent(NPC_ID, 4, OnJustDied)

三、自定义功能实现

3.1 每日签到系统

local DAILY_REWARD = {
    item = 1000,
    count = 5
}

local lastSignTime = {}

function OnPlayerLogin(event, player)
    local guid = player:GetGUIDLow()
    local currentTime = os.time()
    
    if lastSignTime[guid] == nil or 
       currentTime - lastSignTime[guid] >= 86400 then
        -- 可以签到
        player:AddItem(DAILY_REWARD.item, DAILY_REWARD.count)
        player:SendBroadcastMessage("签到成功!获得奖励 x"..DAILY_REWARD.count)
        lastSignTime[guid] = currentTime
    end
end

RegisterPlayerEvent(3, OnPlayerLogin)

3.2 传送 NPC

local TELE_NPC = 12345
local TELE_LOCATIONS = {
    {name = "暴风城", map = 0, x = -8833, y = 663, z = 95, o = 0},
    {name = "奥格瑞玛", map = 1, x = 1552, y = -4418, z = 17, o = 0},
    {name = "达拉然", map = 571, x = 5838, y = 733, z = 658, o = 0}
}

function OnGossipHello(event, player, creature)
    ClearGossipMenuFor(player)
    for i, loc in ipairs(TELE_LOCATIONS) do
        AddGossipItemFor(player, 1, loc.name, 0, i)
    end
    SendGossipMenuFor(player, 1, creature:GetGUID())
end

function OnGossipSelect(event, player, creature, sender, intid)
    ClearGossipMenuFor(player)
    local loc = TELE_LOCATIONS[intid]
    if loc then
        player:Teleport(loc.map, loc.x, loc.y, loc.z, loc.o)
    end
    CloseGossipMenuFor(player)
end

RegisterCreatureGossipEvent(TELE_NPC, OnGossipHello, OnGossipSelect)

3.3 自定义副本入口

local INSTANCE_PORTAL = 56789
local REQUIRED_LEVEL = 70
local REQUIRED_ITEM = 12345  -- 入场券

function OnGossipSelect(event, player, creature, sender, intid)
    if intid == 1 then
        -- 检查条件
        if player:GetLevel() < REQUIRED_LEVEL then
            player:SendBroadcastMessage("等级不足!需要"..REQUIRED_LEVEL.."级")
            return
        end
        
        if not player:HasItem(REQUIRED_ITEM) then
            player:SendBroadcastMessage("缺少入场券!")
            return
        end
        
        -- 传送进副本
        player:Teleport(550, 1234, 567, 89, 0)  -- 副本入口坐标
        player:SendBroadcastMessage("欢迎进入副本!")
    end
    CloseGossipMenuFor(player)
end

RegisterCreatureGossipEvent(INSTANCE_PORTAL, nil, OnGossipSelect)

四、定时器使用

4.1 创建定时事件

-- 每 60 秒执行一次
function TimedEvent(eventid, delay, repeats, param)
    print("定时任务执行,参数:"..tostring(param))
    -- 执行任务逻辑
end

CreateLuaEvent(TimedEvent, 60000, 0, "test_param")

-- 延迟执行(5 秒后)
function DelayedAction(eventid, delay, repeats, player_guid)
    local player = GetPlayerByGUID(player_guid)
    if player then
        player:SendBroadcastMessage("延迟任务完成!")
    end
end

CreateLuaEvent(DelayedAction, 5000, 1, player:GetGUIDLow())

4.2 周期性广播

local BROADCAST_MESSAGES = {
    "欢迎来到本服务器!",
    "加入 QQ 群:123456789 获取更多资讯",
    "周末双倍经验活动进行中!",
    "发现 BUG 请联系 GM"
}

local currentIndex = 1

function BroadcastLoop(eventid, delay, repeats)
    local msg = BROADCAST_MESSAGES[currentIndex]
    SendWorldMessage(msg)
    currentIndex = (currentIndex % #BROADCAST_MESSAGES) + 1
end

-- 每 5 分钟广播一次
CreateLuaEvent(BroadcastLoop, 300000, 0)

五、数据库操作

5.1 查询数据库

function QueryPlayerData(player_guid)
    local result = WorldDBQuery("SELECT * FROM custom_player_data WHERE guid = "..player_guid)
    if result then
        local row = result:GetRow()
        return row.points, row.vip_level
    end
    return 0, 0
end

function SavePlayerData(player_guid, points, vip)
    WorldDBExecute("REPLACE INTO custom_player_data (guid, points, vip_level) VALUES ("..player_guid..", "..points..", "..vip..")")
end

5.2 创建自定义表

-- SQL 文件:custom_tables.sql
CREATE TABLE IF NOT EXISTS custom_player_data (
    guid INT UNSIGNED NOT NULL,
    points INT DEFAULT 0,
    vip_level TINYINT DEFAULT 0,
    last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (guid)
);

-- 在 lua_scripts 中执行
WorldDBExecute("CREATE TABLE IF NOT EXISTS custom_player_data ...")

六、实用技巧

6.1 日志输出

print("普通日志信息")
print("玩家"..player:GetName().."登录了")

-- 错误处理
function SafeCall(func, ...)
    local status, result = pcall(func, ...)
    if not status then
        print("错误:"..result)
    end
    return result
end

6.2 配置管理

-- config.lua
CONFIG = {
    SERVER_NAME = "我的服务器",
    MAX_LEVEL = 80,
    START_GOLD = 10000,
    DONATION_RATE = 1.5
}

-- 在其他脚本中使用
require("config")
print("服务器名称:"..CONFIG.SERVER_NAME)

七、调试技巧

7.1 热重载

-- 在聊天框输入
.reload eluna

-- 或在控制台
reload eluna

7.2 错误排查

-- 启用详细日志
etc/worldserver.conf
Eluna.Debug = 1

-- 查看日志文件
logs/Eluna.log

八、总结

Lua 脚本让模拟器开发更简单,无需编译即可测试新功能。建议:

  1. 从简单脚本开始学习
  2. 多参考官方文档和示例
  3. 做好代码注释和版本管理
  4. 定期备份脚本文件

Lua 脚本是快速实现创意的利器。