LoG Framework (dynamic hooks etc.)

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
Grimfan
Posts: 369
Joined: Wed Jan 02, 2013 7:48 am

Re: LoG Framework (dynamic hooks etc.)

Post by Grimfan »

Diarmuid wrote:Hmmm, it should if monsters are correctly imported withing the LoG framework.

Are you sure that the monsters are defined using fw_defineObject rather than defineObject, and that in init.lua, LoG framework is imported BEFORE the monsters, and EXSP is imported last?
Yeah, I'll be doing all that tomorrow, just in case I have missed a monster here or there (the fw_defineObject part) since everything is loaded properly.

Does fw_cloneObject also work for those creatures that are just different stats/retextures or have you only set it up for defined objects (in other words do I need to change them from cloneObject to defineObject)?

I'm still trying to decipher your scripts with my limited skills. :shock:
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: LoG Framework (dynamic hooks etc.)

Post by JKos »

fw_defineObject has to be used only with completely new assets which are defined with defineObject-function. So no, you don't have to and you can't replace cloneObject-function calls with fw_defineObject

It's documented here:
https://sites.google.com/site/jkosgrimr ... tom-assets
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
Grimfan
Posts: 369
Joined: Wed Jan 02, 2013 7:48 am

Re: LoG Framework (dynamic hooks etc.)

Post by Grimfan »

JKos wrote:fw_defineObject has to be used only with completely new assets which are defined with defineObject-function. So no, you don't have to and you can't replace cloneObject-function calls with fw_defineObject

It's documented here:
https://sites.google.com/site/jkosgrimr ... tom-assets
Thanks JKos. I remember reading that too, but sometimes details are easy to forget (there are always a couple of rules that every person learning something new struggles to recall, unfortunately for me with lua it's most of them). :(
Grimfan
Posts: 369
Joined: Wed Jan 02, 2013 7:48 am

Re: LoG Framework (dynamic hooks etc.)

Post by Grimfan »

One thing I have noticed with the default spells:

1. Fireburst is only sort of working. The spell is dealing the normal damage and creatures are momentarily on fire, but I'm not getting the normal sound effect or burst effect. I have done nothing to change the default spells and all monsters have been changed appropriately. Fireball is working like normal (with the proper sound effect and burst effect).

All custom spells are working as intended so far.
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: LoG Framework (dynamic hooks etc.)

Post by JKos »

After a long time, a moderate update:

- GrimQ updated to newest version
- Added new features to timers-script entity (sequenceCall, delayCall, repeatCall etc...)
- init_module method is now called automatically when the module script entity is loaded
- Bug fix: eg. fw.getById('champion_1') returned champion by slot instead of ordinal.

Documentation not updated yet, but you can check out and try the examples of the new features from here:
https://github.com/JKos/framework/blob/master/README.md

Download and documentation is still here: https://sites.google.com/site/jkosgrimrock2/
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: LoG Framework (dynamic hooks etc.)

Post by Xanathar »

Thanks! That's very cool! :)
- init_module method is now called automatically when the module script entity is loaded
If you added this out of my personal suggestion, I fear I explained myself poorly, sorry! :( (Now rereading the PMs I have 100% certainty this is the case - sorry again).

I was suggesting something like

Code: Select all

if modulePreInitFunction[moduleName] then
	modulePreInitFunction[moduleName]()
end
done in the onOpen hook, instead of onClose (thus when we can still do setSource). Of course modulePreInitFunction would not be in the entity, as it does not exist yet at that phase.

Sorry once again for the confusion!
Waking Violet (Steam, PS4, PSVita, Switch) : http://www.wakingviolet.com

The Sunset Gate [MOD]: viewtopic.php?f=14&t=5563

My preciousss: http://www.moonsharp.org
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: LoG Framework (dynamic hooks etc.)

Post by JKos »

No problem, implementing that feature didn't take much time. But could you explain why you need that preInitFunction-call? Do you want to spawn multiple script entities with one loadModule-call?
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: LoG Framework (dynamic hooks etc.)

Post by Xanathar »

Yes of course I explain :)
I'm doing that AI framework you know. I'm considering for a next version to make it a framework module as there is a lot of synergy to be leveraged (god, I have a career in marketing :lol: ).

What I currently do is to create a new function defineAiSwitchMonster which is to be used in place of defineObject (with a couple of caveats) for those monsters one wants to manage through the AI switcher. For this to work I need to pass a ton of data defined in the init.lua imports to the scripting entity but the imports are executed *after* my module declares its scripting entity. So I'm generating an entity which is basically a big table on-the-fly.

I attach the code here - it will be released with some little demo soon:

Code: Select all

--[[
	AI Switcher for Grimrock - Version 1.3
	Code by Xanathar (Marco Mastropaolo), 2013
	
	Check README.txt for installation instructions :)
	Thanks to Leki for the idea, to JKos for opening the way to this kind of tricks with his framework.

]]

-- All defined switchsets
local g_AiSwitcher_AiSwitchSets = { };
-- This will contain all generated hooks
local g_AiSwitcher_HooksCode = "";
-- This will contain all the names of the party projectile hooks
local g_AiSwitcher_PartyProjectileHooks = { };
-- This will contain all warnings
local g_AiSwitcher_Warnings = "";
-- Source code of the aiswitcher scripting entity
local g_AiSwitcher_LuaSource = [[

-- Table of all monsters to be checked for AI switching. This is needed because we can't destroy a monster in its hook, 
-- so the hook simply queues the monster here, and then an onDrawGui handler does the processing as soon as possible
g_MonstersToCheck = { }
-- This table associates all monster names to their switch-set 
g_SwitchSetsByName = { }
-- This table associates all monster names to their default AI - used to relay hooks to the framework
g_MasterMonsterByName = { }
-- This will be set to true once the above tables are initialized correctly
g_InitDone = false

-- Initializes the tables
function initTables()
	for _1, switchset in ipairs(aiswitchdata.ai) do
		for _2, m in ipairs(switchset) do
			g_SwitchSetsByName[ m[1] ] = switchset
			g_MasterMonsterByName[ m[1] ] = switchset[#switchset][1]
		end
	end
end

-- Changes a monster to a new name. m=the monster to change. newname=the name of the new monster.
-- newidx=the index of the monster in the switchset (0 for default). switch=the switchset
function changeMonster(m, newname, newidx, switch)
	if (m.name == newname) then return; end
	
	local oldidx = -1
	
	for nidx, n in ipairs(switch) do
		if (n[1] == m.name) then
			oldidx = nidx
			break
		end
	end
	
	local x = m.x
	local y = m.y
	local f = m.facing
	local l = m.level
	local id = m.id
	local hp = m:getHealth()
	local lvl = m:getLevel()
	
	if (tonumber(id) ~= nil) then
		id = nil
	end
	
	local hookres = processhook(m, "onAiSwitch", oldidx, newidx)
	
	if (hookres ~= nil) and (not hookres) then return; end
	
	m:destroy()
	spawn(newname, l, x, y, f, id)
		:setLevel(lvl)
		:setHealth(hp)
end

-- Entry point of the onDrawGui hook
function onTick()
	if (#g_MonstersToCheck > 0) then
		for _, id in ipairs(g_MonstersToCheck) do
			local m = findEntity(id)
			if (m ~= nil) then
				realCheck(m)
			end
		end
	end
	g_MonstersToCheck = { }
end

-- Checks the specified monster for any AI change
function realCheck(m)
	if (m.level ~= party.level) then return; end

	if (not g_InitDone) then
		initTables()
		g_InitDone = true
	end

	local switch = g_SwitchSetsByName[m.name];
	
	if (switch == nil) then return; end
	
	local distance = math.abs(m.x - party.x) + math.abs(m.y - party.y)
	local directdistance = (m.x == party.x) or (m.y == party.y)
	
	for idx, n in ipairs(switch) do
		if (n[2] == "distance") then
			if (distance >= n[3]) and  (distance <= n[4]) then
				changeMonster(m, n[1], idx, switch)
				break;
			end
		elseif (n[2] == "directdistance") and directdistance then
			if (distance >= n[3]) and  (distance <= n[4]) then
				changeMonster(m, n[1], idx, switch)
				break;
			end
		elseif (n[2] == "health") then
			local hp = m:getHealth()
			if (hp >= n[3]) and  (hp <= n[4]) then
				changeMonster(m, n[1], idx, switch)
				break;
			end
		elseif (n[2] == "function") then
			local chg = process_cond(m, n[1], "condition")
			if (chg) then
				changeMonster(m, n[1], idx, switch)
				break;
			end
		elseif (n[2] == "default") then
			changeMonster(m, n[1], 0, switch)
			break;
		else
			changeMonster(m, n[1], idx, switch)
			break;		
		end
	end
end

-- processes an AI switching hook (onMove, onTurn, onAttack, onRangedAttack)
function process(m, param, method)
	table.insert(g_MonstersToCheck, m.id)
	return processhook(m, method, param)
end 

-- processes hooks for a given monster
function processhook(m, method, param1, param2, param3, param4)
	local fn = "hook_" .. m.name .. "_" .. method;

	if (aiswitchdata ~= nil) and (aiswitchdata[fn] ~= nil) then
		local res = aiswitchdata[fn](m, param1, param2, param3, param4)
		if (res ~= nil) then
			return res;
		end
	end
	
	if ((fw ~= nil) and (fw.executeHooks ~= nil)) then 
		local mastername = g_MasterMonsterByName[m.name]
		if (mastername ~= nil) then
			return fw.executeHooks(mastername, method, param1, param2, param3, param4) 
		end
	end	
end

-- processes a condition function for AI switch
function process_cond(m, mname, method, param1, param2, param3, param4)
	local fn = "hook_" .. mname .. "_" .. method;

	if (aiswitchdata ~= nil) and (aiswitchdata[fn] ~= nil) then
		return aiswitchdata[fn](m, param1, param2, param3, param4)
	end
end

function onPartyProjectileHit(champ, proj, damage, damtype)
	return aiswitchdata.onPartyProjectileHit(champ, proj, damage, damtype)
end

 
]];
 
-- Create the code for a given hook 
function _aiswitcher_createHook(monster, hookname)
	if (monster[hookname] ~= nil) then
		local fnhooknm = "hook_" .. monster.name .. "_" .. hookname;
		local code = monster[hookname];
		
		if (type(code) == "string") then
			code = "\n\n" .. fnhooknm .. " = " .. code .. "\n\n";
			g_AiSwitcher_HooksCode = g_AiSwitcher_HooksCode .. code;
			return fnhooknm;
		else
			g_AiSwitcher_Warnings = g_AiSwitcher_Warnings .. "AISWITCHER WARNING: Hook " .. monster.name .. "." .. hookname .. " is not defined as a string!\n";
			return nil;
		end
	end
	return nil;
end

-- Defines a given monster brain as a monster object in LoG
function _aiswitcher_defineMonsterObject(monster, conditions, idx)
	if (monster.condition == nil) then
		conditions[#conditions + 1] = { monster.name, "default" }
	elseif (type(monster.condition) == "table") then
		monster.name = monster.name .. "_ai_" .. idx;
		conditions[#conditions + 1] = { monster.name, monster.condition[1], monster.condition[2], monster.condition[3], monster.condition[4] }
	else
		monster.name = monster.name .. "_ai_" .. idx;
		_aiswitcher_createHook(monster, "condition")
		conditions[#conditions + 1] = { monster.name, "function" }
	end
	monster.condition = nil
	monster.brains = nil

	_aiswitcher_createHook(monster, "onMove")
	_aiswitcher_createHook(monster, "onTurn")
	_aiswitcher_createHook(monster, "onAttack")
	_aiswitcher_createHook(monster, "onRangedAttack")
	_aiswitcher_createHook(monster, "onDealDamage")
	_aiswitcher_createHook(monster, "onDamage")
	_aiswitcher_createHook(monster, "onProjectileHit")
	_aiswitcher_createHook(monster, "onDie")
	
	_aiswitcher_createHook(monster, "onAiSwitch")
	local partyhook = _aiswitcher_createHook(monster, "onPartyProjectileHit")
	if (partyhook ~= nil) then
		g_AiSwitcher_PartyProjectileHooks[#g_AiSwitcher_PartyProjectileHooks + 1] = partyhook
	end
	
	monster.onMove = function(self, _1) if ((aiswitcher ~= nil) and (aiswitcher.process ~= nil)) then return aiswitcher.process(self, _1, "onMove"); end; end;
	monster.onTurn = function(self, _1) if ((aiswitcher ~= nil) and (aiswitcher.process ~= nil)) then return aiswitcher.process(self, _1, "onTurn"); end; end;
	monster.onAttack = function(self, _1) if ((aiswitcher ~= nil) and (aiswitcher.process ~= nil)) then return aiswitcher.process(self, _1, "onAttack"); end; end;
	monster.onRangedAttack = function(self, _1) if ((aiswitcher ~= nil) and (aiswitcher.process ~= nil)) then return aiswitcher.process(self, _1, "onRangedAttack"); end; end;
	monster.onDamage = function(self, _1, _2) if ((aiswitcher ~= nil) and (aiswitcher.process ~= nil)) then return aiswitcher.processhook(self, "onDamage", _1, _2); end; end;
	monster.onDealDamage = function(self, _1, _2) if ((aiswitcher ~= nil) and (aiswitcher.process ~= nil)) then return aiswitcher.processhook(self, "onDealDamage", _1, _2); end; end;
	monster.onProjectileHit = function(self, _1, _2, _3) if ((aiswitcher ~= nil) and (aiswitcher.process ~= nil)) then return aiswitcher.processhook(self, "onProjectileHit", _1, _2, _3); end; end;
	monster.onDie = function(self, _1, _2) if ((aiswitcher ~= nil) and (aiswitcher.process ~= nil)) then return aiswitcher.processhook(self, "onDie"); end; end;
	
	monster.onAiSwitch = nil;
	monster.onPartyProjectileHit = nil;
	
	defineObject(monster);
end	

-- Takes a table and creates a flat clone of it
function _aiswitcher_cloneTable(src)
	local dst = { }
	for k, v in pairs(src) do
		dst[k] = v
	end
	return dst
end

-- Takes two table, creates a flat clone of the first and merges fields from the second in it
function _aiswitcher_mergeTable(t1, t2)
	local dst = _aiswitcher_cloneTable(t1)
	for k, v in pairs(t2) do
		dst[k] = v
	end
	return dst
end

-- Defines a monster with AI switching facility. User callable.
function defineAiSwitchMonster(monster)
	local conditions = { }
	for idx, b in ipairs(monster.brains) do
		local m = _aiswitcher_mergeTable(monster, b)
		m.animations = _aiswitcher_mergeTable(monster.animations, m.animations)
		_aiswitcher_defineMonsterObject(m, conditions, idx)
	end

	_aiswitcher_cloneTable(monster)
	_aiswitcher_defineMonsterObject(monster, conditions)
	
	g_AiSwitcher_AiSwitchSets[#g_AiSwitcher_AiSwitchSets + 1] = conditions
end

-- Add onDrawGui hooks to the party. Forward to grimwidgets if present. Forward to LoG-fw (even if this is custom behaviour).
cloneObject {
	name = "party",
	baseObject = "party",
	
	onDrawGui = function(g)
		if ((aiswitcher ~= nil) and (aiswitcher.onTick ~= nil)) then 
			aiswitcher.onTick()
		end		
		if ((fw ~= nil) and (fw.executeHooks ~= nil)) then 
			fw.executeHooks("party","onDrawGui", g) 
		end
		if ((gw ~= nil) and (gw._drawGUI ~= nil)) then 
			gw._drawGUI(g)
		end		
	end,
	onProjectileHit = function(champ, proj, damage, damtype)
		if ((aiswitcher ~= nil) and (aiswitcher.onTick ~= nil)) then 
			return aiswitcher.onPartyProjectileHit(champ, proj, damage, damtype)
		end		
		if ((fw ~= nil) and (fw.executeHooks ~= nil)) then 
			fw.executeHooks("party","onProjectileHit", champ, proj, damage, damtype) 
		end
	end,	
}

-- Creates the aiswitcher_engine object, to initialize the scripting entities
cloneObject {
	name = "aiswitcher_engine",
	baseObject = "dungeon_door_metal",
	openSound = "spider_walk",
	closeSound = "spider_walk",
	lockSound = "spider_walk",	
	onOpen = function()
		-- creates a scripting entity called aiswitchdata containing the serialized switchsets and the hooks of defined monsters
		local ai = "ai = {";
		
		for _1, switchSet in ipairs(g_AiSwitcher_AiSwitchSets) do
			ai = ai .. "{";

			for _2, switch in ipairs(switchSet) do
				ai = ai .. "{";
				
				for _3, entry in ipairs(switch) do
					if (type(entry) == "number") then
						ai = ai .. entry .. ", ";
					elseif (type(entry) == "string") then
						ai = ai .. "\"" .. entry .. "\"" .. ", ";
					end
				end
				ai = ai .. "}, "
			end
			ai = ai .. "}, "
		end
		
		ai = ai .. "}\n\n" .. g_AiSwitcher_HooksCode 
		
		-- creates the relayer for onPartyProjectileHit hooks
		local hookrelay = "function onPartyProjectileHit(c, p, d, t)\n";

		for _1, projhook in ipairs(g_AiSwitcher_PartyProjectileHooks) do
			hookrelay = hookrelay .. projhook .. "(c, p, d, t);\n";
		end
		
		hookrelay = hookrelay .. "end\n\n";
		
		ai = ai .. hookrelay;
		
		print(g_AiSwitcher_Warnings);
		
		spawn("script_entity", 1,1,1,1,"aiswitchdata"):setSource(ai) 
	
		-- creates a scripting entity called aiswitcher with the main code (iif it does not exist)
		if (aiswitcher == nil) then
			spawn("script_entity", 1,1,1,1,"aiswitcher"):setSource(g_AiSwitcher_LuaSource) 
		end
	end,
}


As you can see I defined aiswitcher_engine which is basically the same as the LoG framework object but specific. If I could rely on the framework for initialization I would probably rewrite hooks so that they would go through fw themselves and avoid some pain :) Plus all of this is so heavy on hooks that it screams for being an fw module.

(as for why the hooks are to be defined in the monster declaration - I wanted to make it so that who creates monsters and AI for the community could make a deployment with a single import (once the framework is installed) instead of requiring that plus entities).
Waking Violet (Steam, PS4, PSVita, Switch) : http://www.wakingviolet.com

The Sunset Gate [MOD]: viewtopic.php?f=14&t=5563

My preciousss: http://www.moonsharp.org
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: LoG Framework (dynamic hooks etc.)

Post by JKos »

Ok :) thanks for the explanation, I hope I understood what you wanted. I tried to implement it, but spawn function is not available in that preInitFunction for some reason, i have to pass it as an argument like this:

Code: Select all

fw_addModulePreInitCallback("module_name",function(spawn)
	spawn("script_entity", 1,1,1,0,"test"):setSource('print("ok")') 
end
I don't really understand why it is not available, or any other LoG functions, but that is how it is, only native lua functions do work. I think it's better that you try to implement that feature to my framework and I will add it if you get it working like you wanted. Here is the modified version:

Code: Select all

local showWarnings = true

local modules = {}	
local loadOrder = {}
local moduleInitFunction = {}
local modulePreInitFunction = {}

--  Set to false if you dont wan't to see warnings about module scripts copy pasted to dungeon
function fw_setShowWarnings(show)
	showWarnings = show
end

function fw_addModule(name,script)
	modules[name] = script
	loadOrder[#loadOrder+1] = name 
end

function fw_addModulePreInitCallback(modulename,callback)
	modulePreInitFunction[modulename] = callback
end

function fw_addModuleInitCallback(modulename,callback)
	moduleInitFunction[modulename] = callback
end

function fw_loadModule(name)
	if modules[name] then return end
	import('mod_assets/framework/modules/'..name..'.lua')
end

function tableToSet(list)
  local set = {}
  for _, l in ipairs(list) do set[l] = true end
  return set
end
fw_loadModule('timers')
fw_loadModule('grimq')
fw_loadModule('data')
fw_loadModule('help')
fw_loadModule('fw')
fw_loadModule('fw_default_hooks')




cloneObject{
	name = "LoGFramework",
	baseObject = "dungeon_door_metal",
	onOpen = function()
		-- load modules
		
		for moduleName,source in pairs(modules) do
			local script = findEntity(moduleName)
			if not script then 
				spawn("script_entity", party.level,1,1,0,moduleName) 
				script = findEntity(moduleName)
				script:setSource(source)
			else
				if showWarnings then
					print('script entity "'..moduleName..'" found from dungeon, the module from lua file was not loaded')
				end
			end
			if modulePreInitFunction[moduleName] then
				modulePreInitFunction[moduleName](spawn)
			end		
			
		end
		spawn("pressure_plate_hidden", party.level, party.x, party.y, 0,'logfw_init_plate')
		:setTriggeredByParty(true)
		:setTriggeredByMonster(false)
		:setTriggeredByItem(false)
		:setActivateOnce(true)
		:setSilent(true)
		:addConnector('activate','logfw_init','main')		
		
	end,
	onClose = function()
		for _,moduleName in ipairs(loadOrder) do
			local moduleEntity = findEntity(moduleName)	
			if moduleInitFunction[moduleName] then
				moduleInitFunction[moduleName](moduleEntity,grimq)
			end
			if moduleEntity and moduleEntity.init_module then
				moduleEntity.init_module()
			end			
			if moduleEntity and moduleEntity.activate then
				moduleEntity.activate()
			end
		end		
		fwInit:destroy()	
	end
}
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: LoG Framework (dynamic hooks etc.)

Post by Xanathar »

It worked! :) thanks! :)
Waking Violet (Steam, PS4, PSVita, Switch) : http://www.wakingviolet.com

The Sunset Gate [MOD]: viewtopic.php?f=14&t=5563

My preciousss: http://www.moonsharp.org
Post Reply