Learning Lua scripting for GD - where to start?

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