Page 1 of 1

Monster spawning monster which spawns monster etc

Posted: Thu Mar 07, 2013 9:03 pm
by alois
First and foremost: hi to all :)

Second: as a novice Grimrock scripter, I was thinking about having monsters which, when killed, spawn other monsters around (or, for what matters, open/close doors, destroy objects, and so on). I was - also - thinking of a method of achieving that which was "monster independent"; i.e., I do not want monster X to spawn monster Y when it dies: I want a general monster which spawns another general one as he dies. And the solution I've found (not a very practical one, I admit) is to give the spawned monster an id of the form (for example)

spawn{my_monster}{spawn{my_other_monster}{open{that_door}}}

In this case, I have a monster (my_monster) which, when killed, spawns a monster (my_other_monster) which, when killed, opens a door (that_door).

In order for this to work, I need to create (in monsters.lua) a cloned monster (my_monster, my_other_monster) which launches an onDie(self) function which 'parses' the monster id in its 2 (or 3, or more) parts: a 'command' (spawn, open, close, destroy) and various parameters: 1 for 'open' and 'close', the door to open, for example; 2 for 'spawn' (the name of the monster to spawn, and its id, which, as in the example above, may be another string of 'commands').

Here is the code I wrote, but I was wondering: is there another way to achieve such results?

Thanks in advance,

alois :)
---------
- In monsters.lua

Code: Select all

cloneObject{
	name = "dying_scavenger",
	baseObject = "scavenger",
	onDie = function(self)
		return ondie.onDie(self)
	end,
}
- In a script entity named ondie

Code: Select all

function findpattern(t,p,s)
	local start = string.find(t,p,s)
	print(start)
	if (start == nil) then
		return 0,0
	else
		local part = string.sub(t,string.find(t,p,s))
		return start + 1, start + #part - 2
	end
end

function findparts(str)
	local parts = {}
	local finish = false
	local start = 1
	while (not finish) do
		local sta, fin = findpattern(str,'%b{}',start)
		if (sta * fin == 0) then
			return parts
		else
			if (start == 1) then
				table.insert(parts,string.sub(str,1,sta-2))
			end
			table.insert(parts,string.sub(str,sta,fin))
			start = fin
			if (start == (#str - 1)) then
				finish = true
			end
		end
	end
	return parts
end

function onDie(self)
	local facingDirs = {{x=0,y=1},{x=-1,y=0},{x=0,y=-1},{x=1,y=0}}
	local monsterId = self.id
	local parts = findparts(monsterId)
	if (#parts == 0) then
		return true
	else
		local doCommand = parts[1]
		if (doCommand == "open") and (#parts == 2) then
			findEntity(parts[2]):open()
		end
		if (doCommand == "close") and (#parts == 2)  then
			findEntity(parts[2]):close()
		end
		if (doCommand == "spawn") and (#parts == 3) then
			local newPos = {x = self.x+facingDirs[self.facing+1].x, y = self.y+facingDirs[self.facing+1].y}
			if not isWall(self.level,newPos.x,newPos.y) then
				spawn(parts[2],self.level,newPos.x,newPos.y,self.facing,parts[3])
			end
		end
		if (doCommand == "destroy") and (#parts == 2) then
			findEntity(parts[2]):destroy()
		end
		return true
	end
end
The first two functions 'parse' the id string in its components command{parameter1}{parameter2} etc; the third is the true onDie routine.

And then I create a dying_scavenger with "spawn{dying_scavenger}{spawn{dying_scavenger}{open{dd1}}}" as id (here "dd1" is a door in the dungeon).

Re: Monster spawning monster which spawns monster etc

Posted: Fri Mar 08, 2013 5:13 am
by Marble Mouth
Hi alois. That is some incredibly creative and functional code you've written there. Here is another way to achieve similar results:

using the same code that you used in monsters.lua
In a script entity named ondie:

Code: Select all

commands = { 
 dying_scavenger_1 = function(self)
	local id = spawnBehind(self,"dying_scavenger")
	commands[id] = function(self)
		openDoor("dd1")
 	end
 end,
}

spawnBehind = function( self , objectName )
--returns the id of the spawned monster, if monster is successfully spawned
	local x , y = getForward( ( self.facing + 2 ) % 4 )
	x = x + self.x
	y = y + self.y
    if not isWall( self.level , x , y ) then
    	return spawn(objectName,self.level,x,y,self.facing).id
    end
end

openDoor = function( doorName )
	local door = findEntity(doorName)
	if door then
		door:open()
	end
end

closeDoor = function( doorName )
	local door = findEntity(doorName)
	if door then
		door:close()
	end
end

onDie = function(self)
	local c = commands[self.id]
	if c then
		c(self)
	end
end
To use this script, you would place a dying_scavenger with id dying_scavenger_1 in your dungeon. When it dies, then onDie will look up its id in the table commands and call this function:

Code: Select all

function(self)
	local id = spawnBehind(self,"dying_scavenger")
	commands[id] = function(self)
		openDoor("dd1")
 	end
 end,
This function will spawn a new dying_scavenger behind the one that was just killed. It will also add a new entry to the table commands so that when this new dying_scavenger dies, onDie will call this function:

Code: Select all

function(self)
   openDoor("dd1")
end
I think that this framework may be easier to expand upon than the one you have presented. I hope this helps :)

Edited to remove some unnecessary code from rough draft

Re: Monster spawning monster which spawns monster etc

Posted: Fri Mar 08, 2013 10:38 am
by alois
Marble Mouth, thanks a lot!

Your idea, the one of 'indexing' the commands with the monster id, is very nice, and much more elegant than mine! I wonder if there is a way of not 'hardwiring' the commands, however. Just to explain: in my framework, one can create a monster whose id is "open{dd1}" which, when killed, opens a door; another with id "spawn{a_monster}{close{dd2}}" which, when killed, spawns a_monster which - once killed - closes a door. Or even more complex commands (i.e.: when killed, spawns an object, and opens a door, and switched a light off, and etc.). Maybe, using the id as "string of commands", one can append to the 'commands' list the ids of the monsters with the corresponding commands. I.e., if the spawned monster has "spawn{a_monster}{close{dd2}}" as string, one should append to commands the following:

- "spawn{a_monster}{close{dd2}} = executeSpawn(a_monster,close{dd2}}
- "close{dd2}" = executeClose(dd2)

where executeSpawn(monster,id) and executeClose(door) are two pre-defined functions; in this way, the onDie(self) function should only be something as execute the 'commands[self.id]' command.

alois :)

Re: Monster spawning monster which spawns monster etc

Posted: Sat Mar 09, 2013 2:16 am
by Marble Mouth
Hi alois. First, I have to point out that there's a bug in the code I posted before. The commands table should look like this:

Code: Select all

commands = { 
 dying_scavenger_1 = function(self)
	local id = spawnBehind(self,"dying_scavenger","dying_scavenger_2")
	if id then
		commands[id] = function(self)
			openDoor("dd1")
 		end
	end
 end,
}
The code that I posted yesterday will crash if it cannot spawn the monster because it is obstructed by a wall.

One of my goals in writing this code was to avoid writing the commands into the monster's id. Each monster must have a unique id, but there's no good reason why you should not be able to create two monsters that execute the same code when they die. Also, the line editor for an object's id is much harder to work with than the Inspector where you can write code into a script_entity.

Using my code, you can create monsters which, when killed, execute functions of arbitrary complexity. In the example I presented, killing the first dying_scavenger spawns the second dying_scavenger. Killing the second dying_scavenger opens the door "dd1". I tried to reproduce the exact behaviors of the monster "spawn{dying_scavenger}{spawn{dying_scavenger}{open{dd1}}}" from your example. Was there some part I missed? I don't understand what you mean about not "hardwiring" the commands. In your model, the commands are hardwired into the id. In mine, they are hardwired into functions inside the table commands. They have to be written somewhere.

- MM :)

Re: Monster spawning monster which spawns monster etc

Posted: Sat Mar 09, 2013 11:15 am
by alois
Hi MM. And, actually, you are right; you have to code somewhere... After spending several hours writing code which was capable to accept commands like

Code: Select all

makeMonster("dying_scavenger",1,11,15,3,{
	openDoor("dd3"),
	switchLight("tc1","off")
	makeMonster("dying_snail",1,12,15,3,{
		openDoor("dd1"), 
		closeDoor("dd2"),
		doTeleport("tlprt_1",1,6,12,3,1,18,13,1,"none"),
		makeMonster("dying_scavenger",1,12,15,3,{
			doPrint("It was tough, but you managed it!"),
			makeMonster("brass_key",1,12,15,3,{"brass_key_door"})
			})
		},
		false), -- do not spawn directly, save commands in id
	},
	true -- spawn directly the monster
)
I realized that - in your way - it will be much more efficient (and flexible):

Code: Select all

commands = {
	dying_scavenger_1 = function(self)
		dd3:open()
		tc1:deactivate()
		local id = spawn("dying_snail",1,12,15,3).id
		commands[id] = function(self)
			dd1:open()
			dd2:close()
			spawn("teleporter",1,6,12,3):setTeleportTarget(18,13,1,1)
			local id = spawn("dying_scavenger",1,12,15,3).id
			commands[id] = function(self)
				hudPrint("It was tough, but you managed it!")
				spawn("brass_key",1,12,15,3,"brass_key_door")
			end
		end
	end	
}
Therefore, thanks for the idea! It spared me a lot of nightmares! :)

alois :)

PS: for example, here is the code to spawn a monster which, if not killed exactly on a spot (say, for a sacrifice), spawns a copy of itself

Code: Select all

commands = {
	dying_scavenger_1 = function(self)
		if (self.x == xpos) and (self.y == ypos) then
			hudPrint("The gods are satisfied!")
			doorX:open()
		else
			hudPrint("The gods are not satisfied!")
			spawn("dying_scavenger",startLevel,startposX,startposY,startFacing,"dying_scavenger_2")
		end
	end,
	dying_scavenger_2 = function(self)
		if (self.x == xpos) and (self.y == ypos) then
			hudPrint("The gods are satisfied!")
			doorX:open()
		else
			hudPrint("The gods are not satisfied!")
			spawn("dying_scavenger",startLevel,startposX,startposY,startFacing,"dying_scavenger_1")
			end
		end
	end,
}

Re: Monster spawning monster which spawns monster etc

Posted: Sat Mar 09, 2013 12:20 pm
by Komag
good stuff, saved :)

Re: Monster spawning monster which spawns monster etc

Posted: Sat Mar 09, 2013 3:49 pm
by Marble Mouth
Oh, now I see what you mean about not "hardwiring" the commands. You were working with code that allowed you to create a table of "proxy commands" that would become interpreted as actual commands at the time the monster dies. This amounts to an "inner platform", where you basically have to rewrite every command from the Grimrock API into your proxy command API. In many other languages, that sort of coding is downright necessary. That's actually one of my favorite features of lua: functions are first class values. They can be constructed at runtime (though the Grimrock API doesn't support absolutely all of this feature) and manipulated just like any other value.

What you did there sort of reminds me of a side project I've been working on, which I call "sequence". It can be used like this for example:

Code: Select all

mySequence = newSequence()
mySequence:addEvent{
	event = function()
		spawn( "snail" , 1 , 15 , 15 , 0 )
	end,
	delayAfter = 1,
}
mySequence:addEvent{
	event = function()
		hudPrint("Where did that snail come from?")
	end,
	delayAfter = 0.5,
}
mySequence:addEvent{
	event = function()
		gate_1:open()
	end,
}


anotherSequence = newSequence()
anotherSequence:addEvent{
	event = function()
		hudPrint("The air behind the gate appears to be congealing")
	end,
}
anotherSequence:addEvent{
	delayBefore = 3,
	event = function()
		mySequence:execute()
	end,
}

function onPressurePlate()
	anotherSequence:execute()
end
I'll post the infrastructure code that makes this work when I'm actually satisfied with it.

Re: Monster spawning monster which spawns monster etc

Posted: Sat Apr 27, 2013 8:50 pm
by Damonya
Your scripts are very interesting.

But I don't understand why you don't do just that:


monsters.lua

Code: Select all

cloneObject{
	name = "scavenger_die",
	baseObject = "scavenger",

	onDie = function(self)
	local k = killcounter
      k:increment()
      return ondie.onDie(self)
   end,
}
a counter with ID: "killcounter" and 0 value

Code: Select all

function onDie(self)

	local x , y = getForward( ( self.facing + 2 ) % 4 )
	x = x + self.x
	y = y + self.y
	if killcounter:getValue() == 1 then
		if not isWall( self.level , x , y ) then			
			spawn("scavenger_die",party.level,x,y,self.facing)			
		end
	
	elseif killcounter:getValue() == 2 then
		dd1:open()
	end
end
The result is the same. No? :?: