Fractured: Paragon core hooks, mod-paragon, mod-ale, Docker build cap
- Track mod-paragon and mod-ale (un-ignore modules in .gitignore). - Ship docker-compose.override.yml with CMAKE_EXTRA_OPTIONS for LuaJIT (mod-ale). - Dockerfile: CBUILD_PARALLEL default to limit OOM under Docker/WSL2. - Core: CLASS_PARAGON sticky combo points (DetachComboTarget), selection rebind, Spell::CheckPower rune path for multi-resource Paragon. - spell_dk_death_rune: IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY) for Blood of the North / Reaping / DRM on Paragon. - Remove temporary Paragon CheckPower logging. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
<div align="center">
|
||||
|
||||
# ⚙️ ALE Implementation Details
|
||||
|
||||
*Advanced features and technical documentation for ALE*
|
||||
|
||||
[](https://discord.com/invite/ZKSVREE7)
|
||||
[](http://www.azerothcore.org/)
|
||||
|
||||
---
|
||||
</div>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This document covers advanced implementation details and best practices for ALE (AzerothCore Lua Engine). For basic usage, see the [Usage Guide](USAGE.md).
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
- [Configuration](#-configuration)
|
||||
- [Script Management](#-script-management)
|
||||
- [Advanced Features](#-advanced-features)
|
||||
- [Database Integration](#-database-integration)
|
||||
- [Performance Tips](#-performance-tips)
|
||||
- [Debugging](#-debugging)
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Server Configuration File
|
||||
|
||||
ALE settings are located in the AzerothCore server configuration file.
|
||||
|
||||
> [!WARNING]
|
||||
> **Important:** Always use the new configuration file generated after compiling with ALE. Without it, error logging and output may not function correctly.
|
||||
|
||||
### Available Settings
|
||||
|
||||
- **Enable/Disable ALE**: Toggle the Lua engine on or off
|
||||
- **Traceback Function**: Enable detailed debug information in error messages
|
||||
- **Script Folder Location**: Configure where ALE looks for script files
|
||||
- **Logging Settings**: Control log verbosity and output destinations
|
||||
|
||||
## 🔄 Script Management
|
||||
|
||||
### Script Reloading
|
||||
|
||||
Reload scripts during development with:
|
||||
|
||||
```
|
||||
.reload ale
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> **Development Only:** This command is for testing purposes only. For production use or troubleshooting, always restart the server.
|
||||
|
||||
**Limitations:**
|
||||
- Events are not re-triggered for existing entities (e.g., logged-in players)
|
||||
- Some state may persist from the previous load
|
||||
- Race conditions may occur with active scripts
|
||||
|
||||
### Script Loading
|
||||
|
||||
#### Default Behavior
|
||||
|
||||
- **Default Folder**: `lua_scripts` (configurable in server config)
|
||||
- **Hidden Folders**: Ignored during loading
|
||||
- **File Names**: Must be unique across all subdirectories
|
||||
- **Loading Order**: Not guaranteed to be alphabetical
|
||||
|
||||
#### Load Priority
|
||||
|
||||
Files with `.ext` extension load before standard `.lua` files:
|
||||
- `init.ext` loads before `script.lua`
|
||||
|
||||
> [!TIP]
|
||||
> Instead of using `.ext`, prefer the standard Lua `require()` function for better maintainability.
|
||||
|
||||
#### Using Require
|
||||
|
||||
The entire script folder structure is added to Lua's require path:
|
||||
|
||||
```lua
|
||||
-- Require file: lua_scripts/utilities/helpers.lua
|
||||
require("utilities/helpers")
|
||||
|
||||
-- Or simply (if in root)
|
||||
require("helpers")
|
||||
```
|
||||
|
||||
**Note:** Omit the `.lua` extension when using `require()`.
|
||||
|
||||
## 🎯 Advanced Features
|
||||
|
||||
### Automatic Type Conversion
|
||||
|
||||
In C++, you must explicitly cast between types:
|
||||
```cpp
|
||||
Unit* unit = ...;
|
||||
Player* player = unit->ToPlayer(); // Manual cast required
|
||||
```
|
||||
|
||||
In ALE, this happens automatically:
|
||||
```lua
|
||||
-- unit is automatically converted to the most specific type
|
||||
-- No manual casting needed!
|
||||
local name = unit:GetName() -- Works for Unit, Player, Creature, etc.
|
||||
```
|
||||
|
||||
All objects are automatically converted to their most specific type, giving you full access to all available methods.
|
||||
|
||||
### Storing Userdata Objects
|
||||
|
||||
> [!CAUTION]
|
||||
> **Critical:** Never store C++-managed userdata objects in global variables or across events!
|
||||
|
||||
#### The Problem
|
||||
|
||||
C++ manages object lifetimes. A stored pointer can become invalid when:
|
||||
- A player logs out
|
||||
- A creature despawns
|
||||
- An object is deleted by the core
|
||||
|
||||
Accessing invalid pointers causes crashes.
|
||||
|
||||
#### The Solution
|
||||
|
||||
Objects are automatically set to `nil` when they become unsafe (usually when the hook function ends).
|
||||
|
||||
**Instead of storing objects:**
|
||||
|
||||
```lua
|
||||
-- ❌ WRONG: Don't do this
|
||||
local savedPlayer = nil
|
||||
|
||||
local function OnLogin(event, player)
|
||||
savedPlayer = player -- Bad! Will be nil after function ends
|
||||
end
|
||||
|
||||
local function OnLogout(event, player)
|
||||
savedPlayer:SendMessage("Test") -- CRASH! savedPlayer is nil
|
||||
end
|
||||
```
|
||||
|
||||
**Store GUIDs instead:**
|
||||
|
||||
```lua
|
||||
-- ✅ CORRECT: Store GUID and retrieve object when needed
|
||||
local playerGUID = nil
|
||||
|
||||
local function OnLogin(event, player)
|
||||
playerGUID = player:GetGUID()
|
||||
end
|
||||
|
||||
local function SomeLaterEvent(event, ...)
|
||||
local player = GetPlayerByGUID(playerGUID)
|
||||
if player then
|
||||
player:SendMessage("Test") -- Safe!
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### Safe to Store
|
||||
|
||||
These userdata objects are Lua-managed and safe to store:
|
||||
- Query results (`ALEQuery`)
|
||||
- World packets (`WorldPacket`)
|
||||
- 64-bit numbers (`uint64`, `int64`)
|
||||
|
||||
### Userdata Metamethods
|
||||
|
||||
#### ToString Support
|
||||
|
||||
All userdata objects implement `tostring`:
|
||||
|
||||
```lua
|
||||
print(player) -- Outputs: Player (Name: "John", GUID: 123456)
|
||||
print(creature) -- Outputs: Creature (Entry: 1234, GUID: 789012)
|
||||
```
|
||||
|
||||
#### Global Metatables
|
||||
|
||||
Each class has a global table containing its methods:
|
||||
|
||||
```lua
|
||||
-- These global tables exist:
|
||||
Player = { GetName = function(...) end, ... }
|
||||
Creature = { GetEntry = function(...) end, ... }
|
||||
GameObject = { GetDisplayId = function(...) end, ... }
|
||||
```
|
||||
|
||||
#### Custom Methods
|
||||
|
||||
You can extend classes with custom methods:
|
||||
|
||||
```lua
|
||||
function Player:CustomGreeting()
|
||||
self:SendBroadcastMessage("Welcome, " .. self:GetName() .. "!")
|
||||
end
|
||||
|
||||
function GameObject:IsChest()
|
||||
return self:GetGoType() == 3
|
||||
end
|
||||
|
||||
-- Usage:
|
||||
player:CustomGreeting()
|
||||
if gameobject:IsChest() then
|
||||
print("Found a chest!")
|
||||
end
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Avoid modifying or deleting global class tables in normal code, as this can break other scripts.
|
||||
|
||||
## 🗄️ Database Integration
|
||||
|
||||
### Query Performance
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Database queries are slow! The entire server waits while data is fetched from disk.
|
||||
|
||||
#### Synchronous vs Asynchronous
|
||||
|
||||
**Use `Execute` for non-SELECT queries:**
|
||||
```lua
|
||||
-- Asynchronous - doesn't block server
|
||||
WorldDBExecute("UPDATE creature SET level = 80 WHERE entry = 1234")
|
||||
```
|
||||
|
||||
**Use `Query` only when you need results:**
|
||||
```lua
|
||||
-- Synchronous - blocks server until complete
|
||||
local result = WorldDBQuery("SELECT name FROM creature_template WHERE entry = 1234")
|
||||
```
|
||||
|
||||
#### Best Practices
|
||||
|
||||
1. **Cache at Startup**: Load data once during server start or script load
|
||||
2. **Use Tables**: Store frequently accessed data in Lua tables
|
||||
3. **Batch Operations**: Combine multiple queries when possible
|
||||
4. **Async When Possible**: Use `Execute` instead of `Query` if you don't need results
|
||||
|
||||
```lua
|
||||
-- ✅ Good: Cache data at startup
|
||||
local creatureNames = {}
|
||||
|
||||
local function LoadCreatureNames()
|
||||
local query = WorldDBQuery("SELECT entry, name FROM creature_template")
|
||||
if query then
|
||||
repeat
|
||||
local entry = query:GetUInt32(0)
|
||||
local name = query:GetString(1)
|
||||
creatureNames[entry] = name
|
||||
until not query:NextRow()
|
||||
end
|
||||
end
|
||||
|
||||
-- Call once at server start
|
||||
RegisterServerEvent(33, LoadCreatureNames) -- SERVER_EVENT_ON_CONFIG_LOAD
|
||||
|
||||
-- Now use cached data
|
||||
local function OnSpawn(event, creature)
|
||||
local name = creatureNames[creature:GetEntry()]
|
||||
print("Spawned:", name)
|
||||
end
|
||||
```
|
||||
|
||||
### Database Types
|
||||
|
||||
> [!CAUTION]
|
||||
> **Critical:** Use the correct getter function for each database type!
|
||||
|
||||
MySQL performs math in specific formats. Using the wrong getter can return incorrect values on different systems.
|
||||
|
||||
#### Type Mapping Table
|
||||
|
||||
| Base Type | Defined Type | Database Type | Query Getter |
|
||||
|---------------------------|--------------|-----------------------|-------------------|
|
||||
| char | int8 | tinyint(3) | `GetInt8()` |
|
||||
| short int | int16 | smallint(5) | `GetInt16()` |
|
||||
| (long int / int) | int32 | mediumint(8) | `GetInt32()` |
|
||||
| (long int / int) | int32 | int(10) | `GetInt32()` |
|
||||
| long long int | int64 | bigint(20) | `GetInt64()` |
|
||||
| unsigned char | uint8 | tinyint(3) unsigned | `GetUInt8()` |
|
||||
| unsigned short int | uint16 | smallint(5) unsigned | `GetUInt16()` |
|
||||
| unsigned (long int / int) | uint32 | mediumint(8) unsigned | `GetUInt32()` |
|
||||
| unsigned (long int / int) | uint32 | int(10) unsigned | `GetUInt32()` |
|
||||
| unsigned long long int | uint64 | bigint(20) unsigned | `GetUInt64()` |
|
||||
| float | float | float | `GetFloat()` |
|
||||
| double | double | double, decimal | `GetDouble()` |
|
||||
| std::string | std::string | varchar, text, etc. | `GetString()` |
|
||||
|
||||
#### Example
|
||||
|
||||
```lua
|
||||
-- ❌ WRONG: Can return 0 or 1 depending on system
|
||||
local result = WorldDBQuery("SELECT 1")
|
||||
local value = result:GetUInt32(0) -- Incorrect type!
|
||||
|
||||
-- ✅ CORRECT: Always returns 1
|
||||
local result = WorldDBQuery("SELECT 1")
|
||||
local value = result:GetInt64(0) -- Correct type for literal numbers
|
||||
```
|
||||
|
||||
## ⚡ Performance Tips
|
||||
|
||||
### Variable Scope
|
||||
|
||||
```lua
|
||||
-- ✅ Fast: Local variables
|
||||
local count = 0
|
||||
for i = 1, 1000 do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
-- ❌ Slow: Global variables
|
||||
count = 0
|
||||
for i = 1, 1000 do
|
||||
count = count + 1
|
||||
end
|
||||
```
|
||||
|
||||
### Table Efficiency
|
||||
|
||||
```lua
|
||||
-- ❌ Avoid: Creating tables in loops
|
||||
for i = 1, 1000 do
|
||||
local data = {i, i*2, i*3} -- 1000 table allocations!
|
||||
end
|
||||
|
||||
-- ✅ Better: Reuse tables
|
||||
local data = {}
|
||||
for i = 1, 1000 do
|
||||
data[1], data[2], data[3] = i, i*2, i*3
|
||||
end
|
||||
```
|
||||
|
||||
### Cache Frequently Used Values
|
||||
|
||||
```lua
|
||||
-- ❌ Avoid: Repeated method calls
|
||||
for i = 1, 100 do
|
||||
player:GetName() -- Calls C++ function 100 times
|
||||
end
|
||||
|
||||
-- ✅ Better: Cache the value
|
||||
local playerName = player:GetName()
|
||||
for i = 1, 100 do
|
||||
-- Use playerName
|
||||
end
|
||||
```
|
||||
|
||||
### Minimize Database Access
|
||||
|
||||
```lua
|
||||
-- ❌ Bad: Query in a loop
|
||||
for entry = 1, 100 do
|
||||
local query = WorldDBQuery("SELECT name FROM creature_template WHERE entry = " .. entry)
|
||||
end
|
||||
|
||||
-- ✅ Good: Single query with IN clause
|
||||
local query = WorldDBQuery("SELECT entry, name FROM creature_template WHERE entry BETWEEN 1 AND 100")
|
||||
```
|
||||
|
||||
## 🐛 Debugging
|
||||
|
||||
### Print Debugging
|
||||
|
||||
```lua
|
||||
-- Basic output
|
||||
print("Debug: Function called")
|
||||
|
||||
-- With variables
|
||||
print("Player:", player:GetName(), "Level:", player:GetLevel())
|
||||
|
||||
-- Object inspection
|
||||
print(player) -- Uses tostring metamethod
|
||||
```
|
||||
|
||||
### Error Logs
|
||||
|
||||
Check these locations for errors:
|
||||
- **Server Console**: Real-time output
|
||||
- **Log File**: Persistent record in server folder
|
||||
|
||||
### Traceback
|
||||
|
||||
Enable traceback in the server config for detailed error information:
|
||||
```
|
||||
ALE.TraceBack = 1
|
||||
```
|
||||
|
||||
This adds call stack information to errors.
|
||||
|
||||
### Incremental Testing
|
||||
|
||||
1. **Start Small**: Test basic functionality first
|
||||
2. **Add Gradually**: Implement features one at a time
|
||||
3. **Test Each Step**: Verify each addition works before moving on
|
||||
4. **Use Reload**: Use `.reload ale` for quick iteration (dev only)
|
||||
5. **Full Restart**: Always do final testing with a server restart
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Objects becoming nil:**
|
||||
- You're storing userdata objects instead of GUIDs
|
||||
- See [Storing Userdata Objects](#storing-userdata-objects)
|
||||
|
||||
**Wrong database values:**
|
||||
- Using incorrect getter function for database type
|
||||
- See [Database Types](#database-types)
|
||||
|
||||
**Script not loading:**
|
||||
- Check for duplicate filenames
|
||||
- Check log for syntax errors
|
||||
- Verify script folder configuration
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Acknowledgements
|
||||
|
||||
ALE is built upon the foundation of the [Eluna Lua Engine](https://github.com/ElunaLuaEngine/Eluna). We acknowledge and thank the Eluna team for their pioneering work in Lua scripting for World of Warcraft server emulators.
|
||||
|
||||
- **[Original Eluna Repository](https://github.com/ElunaLuaEngine/Eluna)**
|
||||
- **[Eluna Discord Community](https://discord.gg/bjkCVWqqfX)**
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<sub>Developed with ❤️ by the AzerothCore and ALE community</sub>
|
||||
|
||||
[⬆ Back to Top](#-ale-implementation-details)
|
||||
</div>
|
||||
Reference in New Issue
Block a user