Skip to content

Quick Start

Borys Stelmakh edited this page Sep 16, 2026 · 1 revision

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.

Contents

What you need

  • 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.

Install the SDK

  1. Download the latest release archive from the releases page.
  2. 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.
  3. Extract the whole archive into it. sdk\, install.cmd and uninstall.cmd appear next to Sacred.exe.
  4. Run install.cmd. It renames the game's ijl15.dll to ijl15_real.dll and puts the SDK's ijl15.dll in its place, and changes nothing else.
  5. 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.

Where mods go

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.
  1. Each .lua file in custom\lua\ is one mod. Files inside folders named lib or examples, or inside folders whose name starts with _, are not run as mods.
  2. 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].
  3. 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.
  4. Keep such mods in a folder of your own, such as custom\lua\mods\. A file inside custom\lua\bin\ replaces the game script at the same path.
  5. Your files win. A file in custom\lua\ with the same path as one in sdk\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.

Mod 1: a message when the world loads

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_ready runs the function once the world has finished loading, after a new game and after a save; loaded is true for a save. Create NPCs and objects from this point on: anything created earlier does not appear. (the vars wrapper)
  • sacred.notify draws 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.

Mod 2: play as Wilbur

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.DWARF is the class. The others: C.SERAPHIM, C.GLADIATOR, C.BATTLEMAGE, C.DARKELF, C.WOODELF, C.VAMPIRESS, C.DAEMON.
  • body is a creature number from sdk\custom\lua\lib\npc.lua: in M.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.pak and custom\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.

Mod 3: a companion and an ambush

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_SOLDIER with another creature from lib\npc.lua.
  • More brigands: change the 4 in for i = 1, 4 and the 4 in O.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); replace if x then o:teleport(x + 4, y + 2) end with o:teleport(2793, 2284).

Troubleshooting

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.

Next steps

When a step does not work for you, ask in the DarkMatters thread and include the last lines of sdk\logs\sdk_loaded.log.

Clone this wiki locally