-
-
Notifications
You must be signed in to change notification settings - Fork 3
Quick Start
Install the SDK and write three small mods: a message when the world loads, a hero class that uses an NPC's body, and a companion with his own dialog and an ambush. About 15 minutes, no programming experience needed.
The same tutorial is published as a Steam guide: Sacred SDK quick start. Questions and mods: the DarkMatters thread.
Verified on Sacred Gold Steam, build 2.0.2.28 (2006-10-13). Every mod on this page was run in the game.
- What you need
- Install the SDK
- Where mods go
- Mod 1: a message when the world loads
- Mod 2: play as Wilbur
- Mod 3: a companion and an ambush
- Troubleshooting
- Next steps
- Sacred Gold from Steam, version 2.0.2.28. The GOG version is the same build.
- Windows.
- A text editor. Notepad is enough; VS Code or Notepad++ color Lua code.
- Download the latest release archive from the releases page.
- Open the game folder: in your Steam library, right-click Sacred Gold and choose
Manage > Browse local files. It is the folder with
Sacred.exe. - Extract the whole archive into it.
sdk\,install.cmdanduninstall.cmdappear next toSacred.exe. - Run
install.cmd. It renames the game'sijl15.dlltoijl15_real.dlland puts the SDK'sijl15.dllin its place, and changes nothing else. - Start the game from Steam.
It works if sdk\logs\sdk_loaded.log exists and an SDK window is drawn over the
game. F12 hides or shows that window, F11 switches the mouse and keyboard
between it and the game: if the game ignores your mouse, press F11. All the keys
are listed in Installation.
uninstall.cmd puts the original ijl15.dll back. Steam's "Verify integrity of
game files" does the same, so run install.cmd again after it.
Sacred Gold\
Sacred.exe, bin\, pak\, ... the game. The SDK does not change these.
sdk\custom\lua\ the SDK's library (lib\) and examples (examples\)
custom\lua\ your mods. You create this folder.
-
Each
.luafile incustom\lua\is one mod. Files inside folders namedliborexamples, or inside folders whose name starts with_, are not run as mods. -
Mods are loaded when the game starts. After you change a file, restart the
game. The log calls this step "baking", so its lines start with
[lua_bake]. -
Every mod ends with
return {}. A mod file can also replace one of the game's own script files; the empty table means "replace nothing", and the mod only runs its code while you play. -
Keep such mods in a folder of your own, such as
custom\lua\mods\. A file insidecustom\lua\bin\replaces the game script at the same path. -
Your files win. A file in
custom\lua\with the same path as one insdk\custom\lua\is used instead of the SDK's.
Windows hides file extensions by default, so hello.lua saved from Notepad can
end up as hello.lua.txt, which the SDK does not see. In Notepad set Save as
type to All files; in File Explorer turn on View > File name extensions.
A mod with an error leaves a lua error line in the log, with the file name and
the line number.
custom\lua\mods\hello.lua:
-- My first mod: show a message when the world has loaded
local V = require "vars"
V.on_ready(function(loaded)
if loaded then
sacred.notify("Welcome back to Ancaria!")
else
sacred.notify("A new adventure begins!")
end
end)
return {}Start the game and load a save: a gold message appears at the top of the screen.
-
require "vars"loads a part of the SDK library. -
V.on_readyruns the function once the world has finished loading, after a new game and after a save;loadedis true for a save. Create NPCs and objects from this point on: anything created earlier does not appear. (the vars wrapper) -
sacred.notifydraws in the SDK window, so the message is hidden while that window is hidden with F12. Mod 3 uses the game's own messages instead.
The SDK can give a hero class the body of any creature. The class keeps its stats, skills, combat arts, voice and intro; its body, name, description and portraits change. Reference: Hero Classes.
custom\lua\classes\wilbur.lua:
-- The Dwarf class becomes Wilbur
local C = require "classes"
local CM = require "classmod"
CM.replace(C.DWARF, {
name = "Wilbur",
info = "Wilbur sets out to make his own way through Ancaria.",
body = 83, -- the creature whose body the hero gets
parts = false, -- no Dwarf goggles on the new body
})
return {}Start the game, choose New Game and go to character selection: the Dwarf is Wilbur, with his own portrait and description. If the first start still shows the Dwarf, restart once more; the game can read its data before the SDK has written it.
-
C.DWARFis the class. The others:C.SERAPHIM,C.GLADIATOR,C.BATTLEMAGE,C.DARKELF,C.WOODELF,C.VAMPIRESS,C.DAEMON. -
bodyis a creature number fromsdk\custom\lua\lib\npc.lua: inM.WILBUR = add(83, "5300", "Wilbur", "")it is the first number, 83. - Play it with a new hero. Armor is not drawn on the new body but still protects; weapons are drawn.
- To undo it, delete the mod file,
custom\pak\Items.pakandcustom\scripts\us\global.res. The SDK builds both files again at the next start, from the game's own files and the mods that are left.
Eight bodies tested in the game are listed in Hero Classes.
A soldier, Sergeant Aldric, appears next to the hero with a "!" over his head. Talk to him and he asks to travel with you. Accept, and he joins the party with a portrait in the companion panel; right away four brigands who were chasing him attack. The game counts the kills and pays 500 gold after the last one.
custom\lua\mods\aldric.lua:
-- Sergeant Aldric asks to join you, and the brigands chasing him attack
local V = require "vars"
local T = require "text"
local S = require "sections"
local P = require "persona"
local NPC = require "npc"
local NPCo = require "npcobj"
local O = require "objectives"
local Vb = require "verbs"
local A = require "actions"
-- 1. Text. Each line gets a key, and the rest of the mod uses the key.
T.named("ALDRIC_NAME", "Sergeant Aldric")
T.named("ALDRIC_ASK", "Brigands have been on my trail since the river. "
.. "Let me walk with you, and my sword is yours.")
T.named("ALDRIC_JOINED", "Here they come. Back to back!")
T.named("AMBUSH_MSG", "Ambush! Brigands attack!")
T.named("AMBUSH_WON", "The brigands are beaten. Aldric shares their loot with you.")
-- 2. The reward. The game counts the kills and keeps the count in the save.
O.declare("aldric_ambush", function()
A.run(Vb.add_gold(500), Vb.info("AMBUSH_WON"))
end)
-- 3. The ambush: four brigands to the right of the hero.
local function ambush()
local x, y = sacred.hero_pos()
if not x then return end
for i = 1, 4 do
-- No name here: the game keeps one creature per name, so four
-- brigands with the same name would be one brigand.
local e = NPCo.spawn_template("named_enemy", { type = NPC.BRIGAND, pos = "CPOS:HERO" })
if e then
e:teleport(x + 12, y - 5 + i * 2)
e:set_level(2) -- right for a new hero; raise it for a stronger one
e:wake()
end
end
O.start_kills("aldric_ambush", NPC.BRIGAND, 4)
A.run(Vb.info("AMBUSH_MSG"))
end
-- 4. Aldric. A persona is a character the SDK finds again after you
-- load a save, instead of creating a second copy.
P.define("aldric", {
type = NPC.VALORIAN_SOLDIER,
name = "res:ALDRIC_NAME",
setup = function(o, adopted)
o:stance(1, 7) -- on the hero's side
if V.get("ALDRIC_JOINED") == 1 then -- he joined before this save
o:make_companion(0, { combat = true })
return
end
if not adopted then -- he was just created
local x, y = sacred.hero_pos()
if x then o:teleport(x + 4, y + 2) end
end
o:bind_quest("Sergeant Aldric", true) -- can be talked to, "!" above him
o:dialog{
text = "ALDRIC_ASK",
buttons = {
{ label = S.ACCEPT, on = function(npc)
V.set("ALDRIC_JOINED", 1)
npc:dialog{ text = "ALDRIC_JOINED" }
npc:make_companion(0, { combat = true })
ambush()
end },
{ label = S.REJECT }, -- no function: the window just closes
},
}
end,
})
P.watch() -- bring Aldric into the world every time a world loads
return {}Load a save or start a new game, and talk to Aldric where it is quiet (not during an intro cut scene).
| Part | What it does | Reference |
|---|---|---|
T.named(key, text) |
Adds a line of text to the game under a key. Some functions take the bare key ("ALDRIC_ASK"), others res: in front ("res:ALDRIC_NAME"). |
Quests and Dialog Authoring |
| Creature names | A name must be unique in the world. Creating a creature with a name that already exists moves the existing one instead, which is why the brigands have none. | Runtime NPCs |
P.define / P.watch
|
A persona: created once, found again after every load, so a load never adds a second Aldric. setup runs each time he is created or found; adopted is true when he was found. |
Runtime NPCs |
o:dialog{...} |
His text and up to four buttons, each with a Lua function. S.ACCEPT / S.REJECT are the game's own labels. |
Runtime NPCs |
make_companion(0, { combat = true }) |
Joins the party through the game's own follow command: follows, fights on the hero's side, portrait in the panel. | Runtime NPCs |
V.set / V.get
|
A game variable. Variables are saved with the game, so after a load the mod knows he already joined. | the vars wrapper |
O.declare / O.start_kills
|
The game's own kill counter: it shows the progress and runs the declared function when the last brigand dies. | Native Quests |
A.run(Vb.add_gold(500), Vb.info(key)) |
The game carries out its own script commands at once: gold with the "+500" popup, then a message. | Vanilla Verbs |
Things to try:
- Replace
NPC.VALORIAN_SOLDIERwith another creature fromlib\npc.lua. - More brigands: change the 4 in
for i = 1, 4and the 4 inO.start_kills; both numbers must match. - A fixed place for Aldric: walk the hero there and press F7. The hero's
coordinates go to the clipboard (for example
2793, 2284); replaceif x then o:teleport(x + 4, y + 2) endwitho:teleport(2793, 2284).
| Problem | Fix |
|---|---|
No sdk\logs\sdk_loaded.log
|
The SDK did not load. Run install.cmd again and start the game with Sacred.exe (Steam does). |
| The SDK stopped working after Steam verified the game files | Steam restored the original ijl15.dll. Run install.cmd again. |
| A mod does nothing | Check the file ends in .lua, not .lua.txt. Look for [lua_bake] baked with its name in the log, or a lua error line with the line number. Restart the game after every change. |
| The game ignores the mouse | Press F11. |
| A class keeps its old look, or the new look stays | Restart once more. To undo a class mod, delete the mod file, custom\pak\Items.pak and custom\scripts\us\global.res. |
| I want the original game back | Run uninstall.cmd, then delete the sdk and custom folders. |
-
sdk\custom\lua\examples\has 15 commented example files. - MODDING_COOKBOOK.md: complete short mods for common tasks.
- Native Quests: quests in the game's journal, with compass markers.
- Runtime NPCs: merchants, smiths, trainers, fighters, walking NPCs.
- World Objects, Zones and Barriers, Cut Scenes.
- Modding vanilla: change the game's own scripts.
When a step does not work for you, ask in the
DarkMatters thread
and include the last lines of sdk\logs\sdk_loaded.log.
Getting started
Authoring
- Quests and Dialog Authoring
- Native Quests
- Runtime NPCs
- Hero Classes
- Dialog Text
- Dialog Nodes (catalogue)
The engine's own verbs
Reference