Learning Lua scripting for GD - where to start?

Apologies if the question is redundant to some of you experienced scripters or programmers, but I have no coding / scripting experience whatsoever. Unless “Hello world” in html 15 years ago counts.

I signed up to the forum mainly to learn about mod creation. I’ve spent a good while looking through the threads, and while I’ve found a ton of tutorials to follow on World Editor and some external tools (appreciate it btw, I know writing concise tutorials can be a real time sink), I managed to find remarkably little about Lua itself. Hence - this post.
I’ve read somewhere you can do most things in the editor without use of scripts, but to delve into some more intricate quests or events, knowledge of Lua will be required. And it just so happened that’s precisely what I’d like to focus on for my own mod. Only any attempt to figure out what’s going on just by looking at any script present in the game left me in head-scratching confusion without any idea what’s what. And I’d like to avoid blindly copying a script already present in the game without understanding how / why it works.

Which leads me to the question: should I begin from scratch, find some resource that teaches basics of Lua and make my way from there? Or is that too much and it would be better to focus only on some specific terms and research those instead? And if so then which ones would be of most use?
I’m aware learning to script - even if it’s only for modding - is a long task that takes effort, but I have enough free time to commit. Frankly coding / scripting has been on my mind for a while, so might as well start here. Any guidance in the thread or through private messages would be much appreciated.

Learning Lua proper will undoubtedly allow you to make the most use out of GD’s scripts and be the most efficient in the process.

I’d recommend something like CodeAcademy for getting started with it.

I myself would not claim to know Lua, but all the same I’ve used scripts ample times in GD by looking at the way Crate has done things and copy/pasting/adjusting things from there. This was how, years ago, I made the Bounties Unlocked mod. More recently, this is how I made the not-so-secret secret-quest in Grim Armory, as well as the spawning mechanism for GA’s smith (of which I had a lot of help from the community). Both of these mods are open source if you want to peer inside.


One of my problems with learning a new programming language is finding the right environment, for me (or, in this case, you), to comfortably test things out and experiment in. (This is why I’ve never learned C++, as I’ve hated every environment thereof.)

Here’s a quick rundown of how scripts are executed in Grim Dawn:

Most, if not all, entities in Grim Dawn have a number of Script ‘hooks’ associated with them. These hooks “play” their linked script when their associated event triggers in-game. Here’s Alkamos, for instance:

This calls the /quests/homesteadstepsoftormentbosses.lua file of which the first (non-commented) line in that file looks like this:

gd.quests.homesteadStepsOfTormentBosses = {}

(This should look similar to the above)
The {} here defines a currently-empty list of functions as being associated with the ‘gd.quests.homesteadStepsOfTormentBosses’ variable. It’s a mouthful, but it is a variable declaration like any other.

This file then has a function some distance in called “Boss2Killed”:

function gd.quests.homesteadStepsOfTormentBosses.Boss2Killed()

The keyword ‘function’ tells the script that it’s a function, and this function is added to our aforementioned empty list of functions by the ‘.’ appended to the variable call. This will make more sense the more you play around with it.

What follows in this function are a different bunch of different commands from elsewhere in GD’s scripting library, being executed on different local and global variables. I’ll copy/paste the entirety of this function as it looks in Grim Armory, as it has been modified by me to be the starting point of Grim Armory’s secret quest:

--Secret Quest Drop Randomizer
local AlkRandomizer = 0
local RandomizerRan = false

function gd.quests.homesteadStepsOfTormentBosses.Boss2Killed()

	GiveTokenToLocalPlayer("HS_STEPSOFTORMENT_CLEARED")
	--- Vanilla GD script
	if Server then	
		local door01 = Door.Get(doorBossB01)
		local door02 = Door.Get(doorBossB02)
	
		if door01 != nil then
			door01:Open()
		
		end
	
		if door02 != nil then
			door02:Open()
		
		end
	
	end

	--- Grim Armory script
	if Server then
		if not RandomizerRan then
			math.randomseed(Time.Now())
			RandomizerRan = true
			AlkRandomizer = math.random(100)
			print("Ran randomizer:")
			print(AlkRandomizer)
		end
	end
	
	---33% chance to begin secret quest on killing Alkamos
	if AlkRandomizer >= 67 then
		local player = Game.GetLocalPlayer()
		
		if (player != nil && Game.GetGameDifficulty() == Game.Difficulty.Legendary) then
			local ptag = "tagMalNotif1"
			local ptoken = "PROTO_BEGIN_QUEST"
			local pitem = "records/items/gearrelic/secret_relic.dbr"
			-- holy crap Zantai this code is unreadable when you don't extract this stuff out to variables wtf
			if (player:HasToken(ptoken) == false && player:HasItem(pitem, 1, false) == false) then
				if(player:IsSpaceAvailable(pitem)) then
					UI.Notify(ptag)
					player:GiveItem(pitem, 1, false)
					player:GiveToken(ptoken)
				end
			end
		end
	end
end

Now, while this is all well and good, remember that whole ‘environment’ debacle I was talking about earlier? In order to actually attach this code to the game’s library of scripts, we need to ensure any script we want to run is “Loaded”. In this case, we’re modifying an existing script that vanilla content loads by default, so we’re all set. But if we were creating an original script file, as Grim Armory does with its “proto.lua” file in the /scripts/game/questsproto/ directory, we need to be sure to add this file to the grimdawn.lua file that Crate wrote:

gd = {}

// Endless Dungeon
Script.Load("scripts/game/endlessdungeon.lua")

// Quests
Script.Load("scripts/game/quests.lua")
--- Steps of Torment/Alkamos is loaded here, via the quests.lua file.
Script.Load("scripts/game/dungeonchestsgdx1.lua")
Script.Load("scripts/game/dungeonchestsgdx2.lua")
Script.Load("scripts/game/dungeonchests.lua")
Script.Load("scripts/game/events/waveevent.lua")

//Protosets
Script.Load("scripts/game/questsproto/proto.lua")

grimdawn.lua is itself loaded by main.lua, one directory higher:

/*
	
	GRIM DAWN
	scripts/main.lua
	
	Scripting entrypoint.
	
	For more information visit us at http://www.grimdawn.com
	
*/

-- Libs
Script.Load("scripts/libs/shared.lua")

-- Game
-- Also loads protoset scripts from quest files
Script.Load("scripts/game/grimdawn.lua")

-- Augur
Script.Load("scripts/augur/augur.lua")

main.lua is, presumably, loaded automatically when the game is launched.

Suffice to say that if you have a script of your own that you want to execute in-game, you need to both attach that script to an entity to call it when a certain trigger is met, AND you need to ensure the script is loaded into GD’s scripting engine.

1 Like

I learned what little lua I know by reading over existing lua scripts to figure out how they work and then tinker. I know that’s probably not a good idea for someone with 0 programming experience (myself) but it’s what works for me.

Youtube/Google helps if you have questions on certain lua things, and GD has a ton of existing lua already that you can get an idea for.

There’s also a modding guide that has a decent write up about many lua features and how they should be used, plus there’s a lua API on the forums somewhere that can give you some info about variables and stuff.

1 Like

Thanks very much for a detailed response. It’ll make it easier to start experimenting with some basic scripts in game. I’ll make a point of looking into other people’s mods and seeing how they went about things as well.
I’ve found a nice introduction to Lua on CodeAcademy, thanks for the suggestion.