Note: CnCNet development is currently paused. Singleplayer and Skirmish remain the primary development targets. Further CnCNet work will resume only if project adoption increases enough to justify the additional development and testing effort.
It means LuaAPI is probably for singleplayer. Don't forget I always test my mods in CnCNet, they always successful, so you can play in CnCNet.
Lua scripting API for Command & Conquer: Red Alert 2 — Yuri's Revenge 1.001
LuaAPI is a native x86 Lua 5.4 runtime injected into gamemd.exe. It exposes selected Red Alert 2 engine functionality to Lua so modders can build gameplay systems without implementing every mechanic directly in C++.
The project follows three core principles:
- 🧠 C++ handles engine integration, runtime state, and safety
- 🎮 Lua controls gameplay behavior
- 🛡️ Only implemented and tested functionality is documented as verified
Project Stage: Beta (
2.0.0; Alpha history: seeFSM/MODDB_ALPHA_RELEASE.md) Current Release Tag:v2.0.0— Beta release (v1.0.0was a historical milestone tag, not a production claim) Current Focus: Documentation/gate reset (PROJECT/GATES.md); research: Dynamic Unit Behavior Development API Line:2.0.0Target: Yuri's Revenge1.001Primary targets: Singleplayer and Skirmish
CnCNet: compatible environment and tested in practice; active CnCNet development is currently paused
- 🎯 Unit Control & Tactical AI — issue runtime orders (
MoveTo/Attack/Stop) and observe missions and targets; proven by live tactical showcases. - 👤 House & Player API — query players and access supported house state such as credits.
- 🌍 World Queries — inspect units, buildings, and aircraft; spatial and selection queries.
- 🚜 Runtime Unit Spawning — create units directly from Lua.
- 🎮 Input & Selected Units — hotkey-driven mods via
Input.WasKeyPressedandWorld.GetSelectedUnits. - 📨 Engine Messaging — display messages through the game's message system.
⚠️ Damage interception (OnPreDamage) is documented as a contract only — it is NOT wired in the current build. SeeAPI.md(Callback Model) before building anything damage-reactive.- 🛡️ Pointer & Lifecycle Safety — C++ protects Lua-facing engine access against invalid runtime objects where supported.
- 🌐 CnCNet Environment Support — injection and hook handling account for the CnCNet process environment; active CnCNet development is currently paused.
- ⏱️ Logical-Frame Callbacks — gameplay logic can be tied to logical game frames rather than render FPS.
| Property | Value |
|---|---|
| Project Stage | Beta (2.0.0) |
| Current Release Tag | v2.0.0 (Beta; v1.0.0 was historical — see FSM/MODDB_ALPHA_RELEASE.md) |
| Current Focus | Gate reset — PROJECT/GATES.md |
| Development API Line | 2.0.0 |
| Game | Yuri's Revenge 1.001 |
| Primary Process | gamemd.exe |
| CnCNet Process | gamemd-spawn.exe |
| Lua Runtime | Lua 5.4 |
| Architecture | Native x86 |
| Hooking | MinHook |
⚠️ LuaAPI is actively developed. Always use the documentation matching the current API version.
┌─────────────────────────────────────┐
│ Lua Mods │
│ │
│ Gameplay logic / AI / mechanics │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ LuaAPI SDK │
│ │
│ House / World / Engine / Game │
│ Events / Object access / Safety │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ C++ Engine Layer │
│ │
│ Hooks / pointers / runtime state │
│ Lifecycle / MinHook integration │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Red Alert 2 / YR Engine │
│ gamemd.exe │
└─────────────────────────────────────┘
C++ manages engine state. Lua decides gameplay behavior.
C++ provides the bridge to the Westwood engine and handles unsafe or engine-specific operations. Lua determines what the mod actually does.
LuaAPI is not intended to replace Ares or Phobos, or to duplicate their existing mechanics for the sake of having Lua equivalents.
Ares and Phobos can provide powerful engine-level capabilities. LuaAPI makes the gameplay logic around those capabilities programmable.
This means a modder can take an existing mechanic, unit, or engine capability and rethink how it works at runtime:
Existing mechanic
↓
Ares / Phobos / Engine capabilities
↓
LuaAPI runtime logic
↓
Custom rules, state, targets, timers, conditions
↓
New gameplay behavior
For example, a basic bounty mechanic can simply reward the player for destroying an enemy unit. With LuaAPI, the same concept can become a larger gameplay system: a hidden target, a specific hunter faction, a time limit, kill streaks, changing rewards, temporary buffs, or even a reinforcement delivered to the hunter.
The goal is therefore not to reinvent every existing mechanic. It is to give modders a programmable layer for combining, extending, and rethinking existing capabilities into new gameplay systems.
Ares / Phobos provide capabilities. LuaAPI makes the gameplay logic programmable.
This principle also guides API development: a new LuaAPI primitive should provide a meaningful runtime capability, rather than simply duplicate an existing engine, Ares, or Phobos feature.
A typical LuaAPI installation looks like:
Yuri's Revenge/
├── gamemd.exe
├── LuaAPI.dll
├── injector.exe
│
└── scripts/
├── init.lua
├── active_mods.txt
│
└── mods/
├── my_first_mod/
│ └── main.lua
│
└── command_authority/
└── main.lua
The default active stack is
target_reselect,bounty_hunter, andsmart_ai(seescripts/active_mods.txt). Other showcase and diagnostic mods remain available underscripts/mods/.
Extract the release into your Yuri's Revenge directory.
The installation should contain:
LuaAPI.dll
injector.exe
scripts/
Edit:
scripts/active_mods.txt
Add one mod ID per line (use mods that exist under scripts/mods/):
command_authority
Lines beginning with # are comments.
The loader reads the active mod list and loads each module as:
mods.<mod_id>.main
Create:
scripts/mods/my_first_mod/main.lua
A standard LuaAPI mod returns a table:
local MyMod = {}
function MyMod.Update(frame)
-- Gameplay logic. Update is the one reliably dispatched mod-table method.
end
return MyModEngine event callbacks (
OnScenarioStart,OnPreDamage,OnUnitDestroyed) are globals, not mod-table methods — definingMyMod.OnScenarioStartnever fires. See Event Model below.
Run injector.exe and start Yuri's Revenge.
Check LuaAPI.log (written next to LuaAPI.dll) for initialization and mod-loading messages.
If a mod's Update throws, the error is reported in the same log with
the mod name ([LuaAPI] Mod '<name>' Update error: ...) without
stopping the other mods — no separate debugging setup needed.
Each mod lives under scripts/mods/<mod_id>/.
scripts/mods/my_first_mod/
└── main.lua
A mod may also contain additional files as required by the implementation.
The important contract is that main.lua returns a Lua table containing the callbacks and state used by the mod.
LuaAPI currently supports two callback mechanisms.
Callbacks such as Update are methods on the table returned by the mod.
Engine event callbacks (OnScenarioStart, OnPreDamage, OnUnitDestroyed)
are looked up as globals, not mod-table methods — see API.md,
Callback Model. Note that OnPreDamage is additionally not wired in the
current build (collected, never invoked).
local MyMod = {}
-- Engine event callbacks are looked up as GLOBALS in the current build.
-- Defining them as mod-table methods does NOT wire them.
function OnScenarioStart()
-- Scenario initialization.
end
function MyMod.Update(frame)
-- Per-logical-frame logic (the one reliably dispatched mod-table method).
end
function OnUnitDestroyed(victim, killer)
-- ⚠️ Never dispatched in the current build (contract without invocation).
-- Mods must use ID-diff death detection instead; see API.md.
end
return MyModOnDebugCommand(text) is a global callback rather than a mod-table method.
function OnDebugCommand(text)
Engine.PrintMessage("Command: " .. text)
endOnly one active definition should normally provide this global callback.
⚠️ Status in the current build: the engine collects the globalOnPreDamagereference every frame but never invokes it with damage arguments — noReceiveDamagehook is installed. Live engine damage does not reach Lua. The contract below is the intended design, kept for when interception is wired; do not build damage-reactive mechanics on it yet.
The intended contract (global callback, not a mod-table method):
function OnPreDamage(attacker, target, damage, dmgType, frame, subc)
if dmgType == "energy" or dmgType == "explosive" then
return damage * 0.5
end
return nil
endIntended return values:
| Return value | Result |
|---|---|
nil |
Original damage is preserved |
number |
Incoming damage is replaced |
0 |
Damage is cancelled |
Do not return negative damage values. Authoritative status: API.md, Callback Model; evidence: FSM/CAPABILITIES.md conflict note.
Query the current player through the House namespace:
local player = House.GetPlayer()
if player then
local name = player:GetName()
local credits = player:GetCredits()
endSupported house operations can also modify game state:
player:AddCredits(500)Query units and buildings through the World namespace:
local units = World.GetUnits()
local buildings = World.GetBuildings()Spatial queries are also available:
local nearby = World.GetUnitsInRadius(x, y, radius)Example unit inspection:
for _, unit in ipairs(World.GetUnits()) do
if unit:IsAlive() then
local typeName = unit:GetTypeName()
local owner = unit:GetOwner()
local health = unit:GetHealth()
local position = unit:GetPosition()
end
endHouse objects can spawn units through Lua:
local count = player:SpawnUnit(
"APOC",
5,
100,
100,
0,
false,
"hunt"
)
Engine.PrintMessage("Spawned " .. count .. " APOC")The return value is the number of units actually created.
The native
SubTurretManager, its Lua bindings, the spawned-missile decoupling, theBulletHookDetonate hook, theEventHookmodule, andFireProjectilewere removed: zero live consumers inscripts/, plus per-frame full-array sweep and per-detonation hook cost on the game thread. History preserved in git andPROJECT/ROADMAP.md(Milestone 10). The design lesson stands: C++ maintains state, Lua decides firing — but ship it only with a live consumer (API principle №10).
Display messages through the Engine namespace:
Engine.PrintMessage("Hello, Commander!")This can be used for HUD feedback, development tools, and gameplay notifications.
CnCNet may launch the game through:
gamemd-spawn.exe
rather than the standard:
gamemd.exe
LuaAPI's injector must therefore resolve the actual game module from the running process rather than assuming a fixed executable name.
LuaAPI may also encounter an existing hook at a function already modified by Ares, Phobos, or another engine modification. A signature mismatch should be treated as a condition requiring evaluation, not automatically as an injection failure.
Gameplay logic should be tied to logical game frames rather than render FPS.
function MyMod.Update(frame)
if frame % 300 ~= 0 then
return
end
-- Execute every 300 logical frames.
endAvoid using wall-clock functions for deterministic gameplay decisions:
os.time()
os.clock()Different clients may have different wall-clock timing and render rates.
Red Alert 2 relies heavily on raw engine pointers. Objects may become invalid after destruction, map transitions, scenario changes, or other lifecycle events.
Never assume that an engine-backed object remains valid indefinitely.
Prefer validity checks before accessing object methods:
if unit and unit:IsAlive() then
local position = unit:GetPosition()
endThe C++ layer is responsible for providing safe access to engine-backed state wherever possible.
Systems that maintain references to engine objects must handle object destruction correctly.
Object created
↓
Runtime state registered
↓
Object used
↓
Object destroyed
↓
Reference invalidated
↓
Runtime state cleaned up
When maintaining C++ containers, avoid erasing entries in a way that invalidates the active iterator. Deferred cleanup is preferred.
Each match starts with a fresh Lua state: when you return to the menu, the session resets (mod state, house cache, timers, and marks are cleared) and the next match re-initializes everything. Mods therefore do not need restart guards for match transitions — but savegame loads are a different lifecycle (see limitations below).
LuaAPI is designed to keep the engine-facing layer lightweight, but performance claims should be evaluated against the current build and workload.
For empirical measurements and reproduction methodology, see BENCHMARK.md.
Do not interpret benchmark results from an older release as a permanent guarantee for every future build or mod workload.
API.md — Complete LuaAPI reference, namespaces, objects, methods, callbacks, and contracts.
TUTORIAL.md — Beginner guide for creating and testing a LuaAPI mod.
CAPABILITIES.md — Verified capabilities, case studies, reusable recipes, and engineering lessons.
PROJECT/ — Architecture, roadmap, milestones, and engineering documentation.
BENCHMARK.md — Performance measurements and benchmark methodology.
LuaAPI distinguishes between theoretical engine possibilities and functionality that has actually been implemented and tested.
A capability should only be described as VERIFIED when it has been tested against the current LuaAPI implementation.
If an API name, callback contract, or behavior changes, the related documentation should be updated together.
Identify engine capability
↓
Research engine behavior
↓
Implement C++ integration
↓
Expose safe Lua interface
↓
Build Lua prototype
↓
Test in-game
↓
Test lifecycle & edge cases
↓
Verify behavior
↓
Document the proven capability
The goal is not to expose every engine feature immediately. The goal is to expose useful functionality incrementally while keeping the boundary between unsafe engine internals and Lua gameplay logic well defined.
When developing or contributing to LuaAPI:
- Reproduce the behavior.
- Identify the actual engine boundary.
- Keep unsafe engine work inside C++.
- Expose the smallest useful Lua interface.
- Test destruction and lifecycle edge cases.
- Test savegame behavior where relevant.
- Consider multiplayer determinism.
- Document only verified behavior.
- No live damage interception —
OnPreDamageis collected but never invoked (noReceiveDamagehook). No reactive-armor/shield mechanics. - No death event with payload —
OnUnitDestroyedis never dispatched. Death detection is ID-diff polling (see Command Authority). - No production-complete events — factory queues expose counts only;
AI.QueueUnitrequests are accepted but produce zero attributable output live (BLOCKED for production-director designs). - No radar/fog control, alliance switching, or superweapon API.
- Multiplayer: powers lock with 2+ humans by design; two-client behavior otherwise unverified. Never use wall-clock time for gameplay decisions.
- Savegame loads do not fire
OnScenarioStart; Lua-state restore across loads is unverified. Restart-guard your runtime state. - No order lease: Lua orders compete with vanilla AI re-selection; a permanent override primitive does not exist.
Evidence grades for every claim: PROJECT/GATES.md,
FSM/VERIFICATION.md.
LuaAPI is a Beta-stage project under active development.
The API and internal architecture may change as engine integration becomes safer and more complete.
Project gates and their current statuses: PROJECT/GATES.md.
Evidence ledger (what is actually verified, at which level): FSM/VERIFICATION.md.
Use the documentation corresponding to the current API version.
A minimal LuaAPI mod:
local MyMod = {}
function MyMod.Update(frame)
if frame % 300 ~= 0 then
return
end
local player = House.GetPlayer()
if not player then
return
end
player:AddCredits(100)
Engine.PrintMessage("+$100")
end
return MyModThis demonstrates the core LuaAPI model:
Lua Mod
↓
Returned Mod Table
↓
Lifecycle Callback
↓
LuaAPI Namespace / Object
↓
Red Alert 2 Engine
If you are new to LuaAPI:
1. Read TUTORIAL.md
2. Use API.md as the technical reference
3. Study CAPABILITIES.md for verified examples
4. Explore the sample mods under scripts/mods/
For Beta scope, check the API classification (Stable / Experimental / Internal / Unverified / Blocked) in
PROJECT/API_FREEZE_AUDIT.md— build new mods on the Stable list; treat everything else as unsupported until proven otherwise.
🛠️ Build small. Test frequently. Verify before documenting.
LuaAPI is licensed under the MIT License.
Copyright (c) 2026 NiTeMind
See LICENSE for the full license text.
Third-party components distirbuted with or used by LuaAPI are distributed under their respective licenses. See the relevant license files and notices for details.
Red Alert 2 and Yuri's Revenge are proprietary software and trademarks of Electronic Arts. LuaAPI is an independent community project and is not affiliated with or endorsed by Electronic Arts.
Developed for the C&C modding community.
For engineering history and debugging notes, see ENGINEERING_LESSONS.md.