Ask a simple question, get a simple answer

Ask for help about creating mods and scripts for Grimrock 2 or share your tips, scripts, tools and assets with other modders here. Warning: forum contains spoilers!
User avatar
Marebre
Posts: 5
Joined: Wed Feb 01, 2017 4:12 pm

Re: Ask a simple question, get a simple answer

Post by Marebre »

Is it possible to change, or at least rename BaseStats and their descriptions? For example, rename Strength into Constitution, or remove resistance bonus from stats, or change their description, or add brand new stat to the game (so characters will have 5 or more stats)?
Last edited by Marebre on Thu May 04, 2017 9:58 am, edited 1 time in total.
Badgert
Posts: 258
Joined: Sun Jan 29, 2017 6:14 pm

Re: Ask a simple question, get a simple answer

Post by Badgert »

Good afternoon!
I'm trying to make an alternative ending for my mod and there was a question.
Is it possible to do so that, when entering the portal, the second video (the current where the heroes do not depart on the ship) starts to play?
And if you can, how exactly?
User avatar
Isaac
Posts: 3185
Joined: Fri Mar 02, 2012 10:02 pm

Re: Ask a simple question, get a simple answer

Post by Isaac »

Badgert wrote:Good afternoon!
I'm trying to make an alternative ending for my mod and there was a question.
Is it possible to do so that, when entering the portal, the second video (the current where the heroes do not depart on the ship) starts to play?
And if you can, how exactly?
The Portal asset is defined with the ending video; you need to redefine the default asset, or define a new portal asset.

Code: Select all

defineObject{
	name = "portal",
	baseObject = "portal",
	components = {
		{
			class = "Portal",
			onArrival = function()
				--GameMode.completeGame("assets/videos/outro1.ivf")  --original
				GameMode.completeGame("mod_assets/videos/custom_ending_1.ivf")  --custom
			end,
		},
	},
}
You can make a slideshow video for the ending. You must encode the video with the VP8 codec.
http://www.grimrock.net/modding/creating-cinematics/
Badgert
Posts: 258
Joined: Sun Jan 29, 2017 6:14 pm

Re: Ask a simple question, get a simple answer

Post by Badgert »

Isaac, many thanks!
gmsantashelper
Posts: 3
Joined: Thu May 04, 2017 1:58 am

Re: Ask a simple question, get a simple answer

Post by gmsantashelper »

Loaded up one of my dungeons in the editor after about a year and upon activating a simple dig script the play tester crashes with the error shown in the linked image. It used to work perfectly fine. Nothing has changed except maybe steam updating LoG2 over the past year. Cannot figure out what is going on here. Given it is line 55 of the init.lua, I think it will happen any time 'onRest' is called up. Fairly certain I am using the latest GrimTK available on the Nexus as it hasn't been updated since 2015. https://steamuserimages-a.akamaihd.net/ ... 996C14DEC/

What is going on/How do I fix this?

*edit*
Can confirm simply resting will cause the crash as well, same thing:

mod_assets/ext/grimtk/init.lua:55: attempt to index field 'GUI' (a nil value)

segment containing called out line:

Code: Select all

if fw_hook == nil then

	defineObject{
		name = "party",
		baseObject = "party",
		components = {
			{
				class = "Party",
				onDrawGui = function(party, g)
					
				end,
				onRest = function(party)
					if ( findEntity("GTK") ) then
						if ( GTK.Core.GUI:isPartyLocked() ) then           <--- Line 55 (this comment obviously not in the code)
							return false;
						end
					end			
				end,
				onWakeUp = function(party)
					if ( findEntity("GTK") ) then
						if ( GTK.Core.GUI:isPartyLocked() ) then
							return false;
						end
					end
				end
			}
		}
	}

end
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: Ask a simple question, get a simple answer

Post by akroma222 »

the onRest (and onWakeUp) hooks will both crash it - but they work fine
the field GUI is coming up with nil, GUI is a huge table of custom functions within core.lua ,
("mod_assets/grimtk/scripts/gtk/core.lua")
Have you checked to make sure grimtk's init file is being imported from your mods init.lua ?
grimtk's init.lua defines the "grimtk" object, which is a script entity object that creates more script components - loading a bunch of .lua files
SpoilerShow

Code: Select all

defineObject{
	name = "grimtk",
	baseObject = "script_entity",
	components = {
		{
			class = "Null",
			onInit = function(self)
				if ( findEntity("GTK") == nil ) then
					local gtk = spawn("script_entity", 1, 1, 1, 1, 1, "GTK");
					gtk.script:loadFile("mod_assets/grimtk/scripts/hook.lua");
					gtk:createComponent("Script", "Constants"):loadFile("mod_assets/grimtk/scripts/gtk/constants.lua");
					gtk:createComponent("Script", "Input"):loadFile("mod_assets/grimtk/scripts/gtk/input.lua");					
					gtk:createComponent("Script", "Core"):loadFile("mod_assets/grimtk/scripts/gtk/core.lua");
					gtk:createComponent("Script", "Widgets"):loadFile("mod_assets/grimtk/scripts/gtk/widgets.lua");

					if ( Editor.isRunning() ) then
						gtk:createComponent("Script", "Debug"):loadFile("mod_assets/grimtk/scripts/gtk/debug.lua");
					end

					delayedCall("GTK", 0.01, "emitGtkDidLoad");
				end
				
				if ( findEntity("GTKGui") == nil ) then
					local gtkgui = spawn("script_entity", 1, 1, 1, 1, 1, "GTKGui");
					gtkgui:createComponent("Script", "Basic"):loadFile("mod_assets/grimtk/scripts/gtkgui/basic.lua");
					gtkgui:createComponent("Script", "Basic_modded"):loadFile("mod_assets/grimtk/scripts/gtkgui/basic_modded.lua");
					gtkgui:createComponent("Script", "Dialogue"):loadFile("mod_assets/grimtk/scripts/gtkgui/dialogue.lua");
					gtkgui:createComponent("Script", "Cinematic"):loadFile("mod_assets/grimtk/scripts/gtkgui/cinematic.lua");					
				end
			end
		}
	}
}
double check that that is all still there and working
Last edited by akroma222 on Thu May 04, 2017 9:42 am, edited 1 time in total.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Ask a simple question, get a simple answer

Post by minmay »

akroma222 wrote:Have you checked to make sure grimtk's init file is being imported?
It would be hard for an error to occur in grimtk/init.lua if grimtk/init.lua wasn't being imported...

Definitely look at core.lua.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: Ask a simple question, get a simple answer

Post by akroma222 »

minmay wrote:
akroma222 wrote:Have you checked to make sure grimtk's init file is being imported?
It would be hard for an error to occur in grimtk/init.lua if grimtk/init.lua wasn't being imported...
:lol: you're probably right, ya know! :lol:
Honestly, I think Ive rewritten that^^ message 7 times - and still missed that!
(its one of those days)
gmsantashelper
Posts: 3
Joined: Thu May 04, 2017 1:58 am

Re: Ask a simple question, get a simple answer

Post by gmsantashelper »

The core.lua does define GUI, and it is a lot of stuff. Not sure why it would be seen as nil if it has an abundance of values. What does it even mean by trying to index a nil value? I feel like this is turning out to not be a simple question, and needs more than just a simple answer, hah.

For sake of clarity, here is the entire grimtk init.lua, and yeah, it is definitely imported correctly, lol.

Code: Select all

defineObject{
	name = "grimtk",
	baseObject = "script_entity",
	components = {
		{
			class = "Null",
			onInit = function(self)
				if ( findEntity("GTK") == nil ) then
					local gtk = spawn("script_entity", 1, 1, 1, 1, 1, "GTK");
					gtk.script:loadFile("mod_assets/ext/grimtk/scripts/hook.lua");
					gtk:createComponent("Script", "Constants"):loadFile("mod_assets/ext/grimtk/scripts/gtk/constants.lua");
					gtk:createComponent("Script", "Input"):loadFile("mod_assets/ext/grimtk/scripts/gtk/input.lua");					
					gtk:createComponent("Script", "Core"):loadFile("mod_assets/ext/grimtk/scripts/gtk/core.lua");
					gtk:createComponent("Script", "Widgets"):loadFile("mod_assets/ext/grimtk/scripts/gtk/widgets.lua");

					if ( Editor.isRunning() ) then
						gtk:createComponent("Script", "Debug"):loadFile("mod_assets/ext/grimtk/scripts/gtk/debug.lua");
					end

					delayedCall("GTK", 0.01, "emitGtkDidLoad");
				end
				
				if ( findEntity("GTKGui") == nil ) then
					local gtkgui = spawn("script_entity", 1, 1, 1, 1, 1, "GTKGui");
					gtkgui:createComponent("Script", "Basic"):loadFile("mod_assets/ext/grimtk/scripts/gtkgui/basic.lua");
					gtkgui:createComponent("Script", "Dialogue"):loadFile("mod_assets/ext/grimtk/scripts/gtkgui/dialogue.lua");
					gtkgui:createComponent("Script", "Cinematic"):loadFile("mod_assets/ext/grimtk/scripts/gtkgui/cinematic.lua");					
				end
			end
		}
	}
}


--
-- If the Log2 Framework has been included before this script, then we will use the framework instead.
-- Otherwise, the default behaviour is to override these hooks.
-- NOTE: If you are using the framework, you MUST define an onDrawGui hook SOMEWHERE (even an empty one).
--

if fw_hook == nil then

	defineObject{
		name = "party",
		baseObject = "party",
		components = {
			{
				class = "Party",
				onDrawGui = function(party, g)
					
				end,
				onRest = function(party)
					if ( findEntity("GTK") ) then
						if ( GTK.Core.GUI:isPartyLocked() ) then
							return false;
						end
					end			
				end,
				onWakeUp = function(party)
					if ( findEntity("GTK") ) then
						if ( GTK.Core.GUI:isPartyLocked() ) then
							return false;
						end
					end
				end
			}
		}
	}

end
Also, here is the segment in the core.lua that defines GUI:

Code: Select all

GUI = {
	_tkContext = TKContext.create(),
	_debugMessages = { },
	_windows = { },
	_images = { },
	_updateables = { },
	_shortcuts = { },
	_nextAutoId = 1,
	_focusWidget = nil,
	_partyLocks = 0,
	_keyboardLocks = 0,
	_aiLocks = 0,
	_lastUpdateTime = 0,
	_deltaTime = 0,
	_maxDeltaTime = 0.25,
	_delayedCalls = { },
	isEnabled = true,
	dimBackground = false,
	mouseState = GTK.Input.MouseState.create(),
	keyboardState = GTK.Input.KeyboardState.create(),
	
	getContext = function(self)
		return self._tkContext;
	end,

	addWindow = function(self, window)
		if ( window:name() == nil ) then 
			Console.warn("Cannot create Root window without a name.");
			return false;
		end

		if ( self._windows[window:name()] ~= nil ) then
			Console.warn("Root window already exists with that name.");
			return false;
		end

		self._windows[window:name()] = window;
		self:showWindow(window:name());
		return true;
	end,
	
	createWindow = function(self, args)
		local window = GTK.Widgets.GWindow.create(args);
		
		if ( self:addWindow(window) == true ) then
			return window;
		end

		return nil;
	end,

	destroyWindow = function(self, window)
		local windowName = nil;

		if ( type(window) == "table" ) then
			windowName = window:name();
		elseif ( type(window) == "string" ) then
			windowName = window;
		end

		if ( windowName and self._windows[windowName] ) then
			self:hideWindow(windowName);
			self._windows[windowName] = nil;
		end
	end,
	
	getWindow = function(self, windowName) 
		return self._windows[windowName];
	end,
	
	showWindow = function(self, windowName)
		local window = self._windows[windowName];
		
		if ( window ) then 
			window:setVisible(true);
		end
	end,
	
	hideWindow = function(self, windowName)
		local window = self._windows[windowName];
		
		if ( window ) then 
			window:setVisible(false);
		end
	end,

	addUpdateable = function(self, updateable)
		if ( updateable.update == nil or type(updateable.update) ~= "function" or updateable.updateableId == nil ) then
			Console.warn("[GTK] Attempted to register invalid updateable");
			return;
		end

		if ( self._updateables[updateable.updateableId] ~= nil ) then
			Console.warn("[GTK] An updateable with that name already exists.");
			return;
		end

		if ( updateable.startUpdates ~= nil ) then
			updateable:startUpdates();
		end

		self._updateables[updateable.updateableId] = updateable;
	end,

	removeUpdateable = function(self, updateable)
		local id = nil;

		if ( type(updateable) == "string" ) then
			id = updateable;
		elseif ( updateable.updateableId ~= nil ) then
			id = updateable.updateableId;
		end

		if ( id and self._updateables[id] ~= nil ) then
			if ( self._updateables[id].endUpdates ~= nil ) then
				self._updateables[id]:endUpdates();
			end

			self._updateables[id] = nil;
		end
	end,
	
	update = function(self, guiContext)
		if ( self.isEnabled == false ) then
			return
		end

		local playTime = GameMode.getStatistic("play_time");
		self._deltaTime = playTime - self._lastUpdateTime;
		self._lastUpdateTime = playTime;

		if ( self._deltaTime > self._maxDeltaTime ) then 
			self._deltaTime = self._maxDeltaTime; 
		end

		if ( #self._delayedCalls > 0 ) then
			local toRemove = { };

			for i=#self._delayedCalls,1,-1 do
				local v = self._delayedCalls[i];
				v["delay"] = v["delay"] - self._deltaTime;

				if ( v["delay"] < 0 ) then
					if ( v["func"] ) then
						v["func"](v["param"]);
					end

					table.remove(self._delayedCalls, i);
				end
			end
		end

		for _,updateable in pairs(self._updateables) do
			updateable:update(self._deltaTime);
		end
						
		self._tkContext:start(guiContext);
		self.mouseState:reset(self._tkContext);
		self.keyboardState:reset(self._tkContext);

		local dimScreen = false;
		local screenSize = self._tkContext:size();
		
		for _,window in pairs(self._windows) do
			if ( window.data.dimScreen ) then
				dimScreen = true;
			end
		end
		
		if ( dimScreen ) then
			self._tkContext:drawRect({ position={0,0}, size=screenSize, bgColor={0, 0, 0, 160} });
		end
		
		for _,window in pairs(self._windows) do
			if ( window.data.visible == true ) then
				if ( (window.data.fullscreen == true) and ((window.data.size[1] == screenSize[1]) or (window.data.size[2] ~= screenSize[2])) ) then
					window:setSize(screenSize[1], screenSize[2]);
				end
				
				self:visit(window);				
			end
		end
		
		if ( #self._debugMessages > 0 ) then
			self._tkContext:drawText({ position={10, 10}, size={1200, screenSize[2]-20}, text=table.concat(self._debugMessages, "\n"), textColor={255,255,255,255}, font="tiny" });
		end

		self.keyboardState:commit();
		self.mouseState:commit();
		self._tkContext:finish();
	end,
		
	visit = function(self, widget)		
		if ( widget == nil or widget.data.visible == false ) then
			return
		end
				
		widget:updatePositionInParent(self._tkContext);

		-- Slightly annoying that we do mouse stuff here (and it's not completely separate)
		-- but it's more efficient to only walk the tree of workspaces once...
		self.mouseState:visitWidget(self._tkContext, widget);
		self.keyboardState:visitWidget(self._tkContext, widget);

		widget:update(self._tkContext, self._deltaTime);
		
		self._tkContext:setWorkspaceOpacity(widget.data.opacity);
		widget:draw(self._tkContext);
	
		self._tkContext:pushWorkspace(widget.data.position, widget.data.size);
		
		for _,child in ipairs(widget._children) do
			self:visit(child);
		end

		self._tkContext:popWorkspace();
	end,

	addImage = function(self, args)
		local image = { };

		if ( (args.name == nil) or (args.path == nil) or (args.size == nil)) then
			Console.warn("[addImage] requires at least a name, a path and a size.");
		end

		if ( args.path:sub(-4):lower() ~= ".tga" ) then
			Console.warn("[addImage] '" .. args.path .. "' does not end in .tga");
		end

		if ( args.path:find("//") ) then
			Console.warn("[addImage] '" .. args.path .. "' conains '//' which WILL FAIL when exported!");
		end

		if ( (args.path:sub(1, 6) ~= "assets") and (args.path:sub(1, 10) ~= "mod_assets") ) then
			Console.warn("[addImage] '" .. args.path .. "' does not start with assets or mod_assets." );
		end

		image.name = args.name;
		image.path = args.path;
		image.size = args.size;
		image.margin = iff(args.margin ~= nil, args.margin, {8, 8, 8, 8});
		image.origin = iff(args.origin ~= nil, args.origin, {0,0});		

		self._images[image.name] = image;
	end,

	getImage = function(self, name) 
		return self._images[name];
	end,
	
	getImageSize = function(self, name)
		local image = self._images[name];
		if ( image == nil ) then return {0, 0}; end
		return image.size;
	end,

	nextAutoWidgetId = function(self)
		local id = "widget_" .. self._nextAutoId;
		self._nextAutoId = self._nextAutoId + 1;
		return id;
	end,
	
	setFocus = function(self, widget)
		if ( self._focusWidget ~= nil ) then
			self:unlockKeyboard();
			self:unlockParty();
			self.keyboardState:finishTextEntry();
			self._focusWidget:_triggerPlug("onLoseFocus");
			self._focusWidget:doLoseFocus();
			self._focusWidget = nil;			
		end
			
		if ( widget ~= nil ) then
			self:lockKeyboard();
			self:lockParty();
			self.keyboardState:startTextEntry(widget);
			self._focusWidget = widget;
			self._focusWidget:doGainFocus();
			self._focusWidget:_triggerPlug("onGainFocus");
			GameMode.setGameFlag("DisableKeyboardShortcuts", true);
		end
	end,

	focusWidget = function(self)
		return self._focusWidget;
	end,

	delayedCall = function(self, delay, param, func)
		table.insert(self._delayedCalls, {delay = delay, func = func, param = param});
	end,
	
	lockParty = function(self)
		self._partyLocks = self._partyLocks + 1;
		GameMode.setGameFlag("DisableMovement", true);
		GameMode.setGameFlag("DisableMouseLook", true);
	end,
	
	unlockParty = function(self)
		self._partyLocks = self._partyLocks - 1;

		if ( self._partyLocks <= 0 ) then
			GameMode.setGameFlag("DisableMovement", false);
			GameMode.setGameFlag("DisableMouseLook", false);
			self._partyLocks = 0;
		end
	end,
	
	isPartyLocked = function(self)
		return self._partyLocks > 0;
	end,

	lockKeyboard = function(self)
		self._keyboardLocks = self._keyboardLocks + 1;
		GameMode.setGameFlag("DisableKeyboardShortcuts", true);
	end,

	unlockKeyboard = function(self)
		self._keyboardLocks = self._keyboardLocks - 1;

		if ( self._keyboardLocks <= 0 ) then
			self._keyboardLocks = 0;

			self:delayedCall(0.1, self, function(self) 
				if ( self._keyboardLocks == 0 ) then
					GameMode.setGameFlag("DisableKeyboardShortcuts", false); 
				end
			end);
		end
	end,

	isKeyboardLocked = function(self)
		return self._keyboardLocks > 0;
	end,

	freezeAI = function(self)
		self._aiLocks = self._aiLocks + 1;
		GameMode.setGameFlag("DisableMonsterAI", true);
	end,

	unfreezeAI = function(self)
		self._aiLocks = self._aiLocks - 1;

		if ( self._aiLocks <= 0 ) then
			GameMode.setGameFlag("DisableMonsterAI", false);
			self._aiLocks = 0;
		end
	end,

	isAIFrozen = function(self)
		return self._aiLocks > 0;
	end,

	log = function(self, message)
		if ( #self._debugMessages > 50 ) then
			table.remove(self._debugMessages, 1);
		end

		table.insert(self._debugMessages, message);
	end,
	
	clearDebug = function(self)
		self._debugMessages = { };
	end,

	toggleDebug = function(self)
		if ( self._tkContext.debugMode == false) then
			self._tkContext.debugMode = true;

			if ( Editor.isRunning() )  then
				GTK.Debug.showDebugWindow();
			end
		else
			self._tkContext.debugMode = false;

			if ( Editor.isRunning() ) then
				GTK.Debug.destroyDebugWindow();
			end
		end
	end,
	
}
I'd simply pull out GrimTK but I planned to use it for dialogue popups to give hints/story elements without having to locate and read a note in places that don't really have that option. Honestly I don't remember what all uses it already, been so long.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Ask a simple question, get a simple answer

Post by minmay »

It means you're trying to index nil. Indexing operators are ., :, and []. It is happening because GTK.Core.GUI is nil at the time that line executes.
This problem arises from a weakness in GrimTK, specifically in how it converts its source files to ScriptComponents. In certain contexts, after createComponent() returns, it's already too late to call loadFile(); the source you "load" won't actually load and run, because the ScriptComponent has already compiled. As a general rule, createComponent [...] loadFile will work at dungeon initialization time, but not afterwards. So if you're spawning the grimtk object dynamically instead of placing it in the dungeon editor, for example, you will probably run into this bug.

In short, place the grimtk object using the dungeon editor instead of trying to spawn it later.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Post Reply