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
Isaac
Posts: 3185
Joined: Fri Mar 02, 2012 10:02 pm

Re: Ask a simple question, get a simple answer

Post by Isaac »

brzrkr wrote:...while the player will have to first understand that he actually has to let some party members die to proceed.
I can tell you [from experience] that this style doesn't float well with a lot of players. ;)

BTW... A good [and plausibly acceptable] way to continually respawn (specifically non-undead) monsters ~but them too, is to fake-teleport them into position. A non-scripting method to achieve this in the editor, is to place two spawners at the intended location, each triggered at the same time; where one spawns the monster, and the other spawns the teleport_effect object.

This method can via script, be called from the monster's onDie hook, to spawn again if killed.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Ask a simple question, get a simple answer

Post by minmay »

brzrkr wrote:the player will have to first understand that he actually has to let some party members die to proceed.
So the player is just screwed if they're playing a solo character?
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
Isaac
Posts: 3185
Joined: Fri Mar 02, 2012 10:02 pm

Re: Ask a simple question, get a simple answer

Post by Isaac »

minmay wrote:
brzrkr wrote:the player will have to first understand that he actually has to let some party members die to proceed.
So the player is just screwed if they're playing a solo character?
It would depend upon whether the condition is having only one or two living PCs; rather than of having some of them die... As I recall, boyflea's room in the ORRR2, did require [on some paths], that the party be only female ~or dead; among the many other permutable paths to its ending.

*It would be bad if the solo player has to pick a victim for sacrifice in order to pass. Though I suppose that in LoG2, even that could be accommodated by inflicting [1-4] wounds; presumably the party [solo or group] would get healed later. That or including the [in this case viscous] option of recruiting NPCs for the purpose.
___________
*Question: What is the sample() function of the animation component for; and how is it used?
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Ask a simple question, get a simple answer

Post by minmay »

Isaac wrote:*Question: What is the sample() function of the animation component for; and how is it used?
AnimationComponent:sample() updates the animation if it hasn't yet been updated on the current frame (because the party is on a different level or past the maxUpdateDistance).
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.
brzrkr
Posts: 11
Joined: Wed Aug 10, 2016 4:05 pm

Re: Ask a simple question, get a simple answer

Post by brzrkr »

Isaac wrote:
brzrkr wrote:...while the player will have to first understand that he actually has to let some party members die to proceed.
I can tell you [from experience] that this style doesn't float well with a lot of players. ;)

BTW... A good [and plausibly acceptable] way to continually respawn (specifically non-undead) monsters ~but them too, is to fake-teleport them into position. A non-scripting method to achieve this in the editor, is to place two spawners at the intended location, each triggered at the same time; where one spawns the monster, and the other spawns the teleport_effect object.

This method can via script, be called from the monster's onDie hook, to spawn again if killed.
That's a good idea, thanks. By the way, about the cooldown property of spawners, I couldn't find anywhere to know for sure: am I correct in assuming it is in seconds?

Also, how would I go about modifying the spawned creature, since the spawner only allows me to choose the monster level?
minmay wrote:So the player is just screwed if they're playing a solo character?
The exact condition is that at least two members have to be down, so being alone, the player would pass easily. That gives access to the first crystal of the adventure, so the party will be easily healed up afterwards anyway.
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 »

brzrkr wrote:Also, how would I go about modifying the spawned creature, since the spawner only allows me to choose the monster level?
Anything more advanced than the basic behavior will require scripting. What you've asked is often fairly simple to do, but usually demands a custom script; with no need for a spawner.

One can make their own spawner with a script_entity. To configure it, adjust the first nine variables to suit, and connect a timer or trigger switch to the script.

Code: Select all

--custom_spawner script  [connect trigger to cSpawn]

cooldown = 4           -- minimum time between spawns
monster = "crowern"        -- monster name/kind
lootDrop = {100,"rock", } -- %100 chance to drop, Item; can be blank for no items: {}
level = 1              -- monster level
maxHealth = nil         -- monster health (if changed)
spawnOnDeath = true        -- whether to instantly spawn a new one when one of them dies 
maxSpawns = 3            -- maximum monsters spawned ~before disabling self [ set to 0 for unlimited spawns]
exp = nil               --Change experience point value
teleportEffect = true     --Whether or not to display the teleport effect when spawning
--Auto-spawn option:  See the last line of the script


--_________________________________________________________
spawned = 0   
active = true
function cSpawn(timer,bypass) 
   if maxSpawns then  
      if maxSpawns > 0 and spawned >= maxSpawns then
          maxSpawns = nil
          return -- disable
       end
      if active or bypass then
         for obj in self.go.map:entitiesAt(self.go.x,self.go.y) do
            if obj.obstacle or obj.monster and obj.elevation == self.go.elevation then
               return  --cell is occupied, cancel spawn
            end
         end
         active = false
         local m = self.go:spawn(monster)
         spawned = spawned +1
         if teleportEffect and self.go.map:checkLineOfSight(self.go.x, self.go.y, party.x, party.y, party.elevation) then self.go:spawn('teleportation_effect') end
         if level > 1 then m.monster:setLevel(level) end
         if lootDrop then m.monster:setLootDrop(lootDrop) end
         if maxHealth then m.monster:setHealth(maxHealth) end
         if exp then m.monster:setExp(exp) end
         if spawnOnDeath then m.monster:addConnector('onDie', self.go.id, 'respawn') end
         delayedCall(self.go.id, cooldown, 'activate')
      end
   end
end

function activate() active = true end
function setMaxSpawns(self,number) if type(number) == "number" then maxSpawns = number spawned = 0 active = true else maxHealth = nil end end
function respawn() cSpawn(self, true) end

--[[Auto-spawn OPTION:  If the cSpawn() call [below] is  uncommented, (by deleting the two dashes on it's left), 
   then the script doesn't need a timer or trigger. It will spawn a monster on load, and if spawnOnDeath
   is set to true, it will spawn another when that one is killed. --]]

--cSpawn()
*Script updated.
Last edited by Isaac on Sat Aug 13, 2016 8:14 pm, edited 5 times in total.
brzrkr
Posts: 11
Joined: Wed Aug 10, 2016 4:05 pm

Re: Ask a simple question, get a simple answer

Post by brzrkr »

Isaac wrote:
brzrkr wrote:Also, how would I go about modifying the spawned creature, since the spawner only allows me to choose the monster level?
Anything more advanced than the basic behavior will require scripting. What you've asked is often fairly simple to do, but usually demands a custom script; with no need for a spawner.

One can make their own spawner with a script_entity. To configure it, adjust the first nine variables to suit, and connect a timer or trigger switch to the script.

Code: Select all

--custom_spawner script  [connect trigger to cSpawn]

cooldown = 4			  -- minimum time between spawns
monster = "crowern"		  -- monster name/kind
lootDrop = {100,"rock", } -- %100 chance to drop, Item; can be blank for no items: {}
level = 1				  -- monster level
maxHealth = 10 		  -- monster health (if changed)
spawnOnDeath = true		  -- whether to instantly spawn a new one when one of them dies 
maxSpawns = 2			   -- maximum monsters spawned ~before disabling self
exp = nil			 	  --Change experience point value
teleportEffect = true	  --Whether or not to display the teleport effect when spawning
--Auto-spawn option:  See the last line of the script


--_________________________________________________________
spawned = 0	
active = true
function cSpawn(timer,bypass) 
	if maxSpawns then
		if maxSpawns ~= 0 and spawned >= maxSpawns then if timer:getName() == 'timer' then timer.go.timer:stop() end return end -- disable
		if active or bypass then
			for obj in self.go.map:entitiesAt(self.go.x,self.go.y) do
				if obj.obstacle or obj.monster and obj.elevation == self.go.elevation then
					return  --cell is occupied, cancel spawn
				end
			end
			active = false
			local m = self.go:spawn(monster)
			spawned = spawned +1
			if teleportEffect and self.go.map:checkLineOfSight(self.go.x, self.go.y, party.x, party.y, party.elevation) then self.go:spawn('teleportation_effect') end
			if level > 1 then m.monster:setLevel(level) end
			if lootDrop then m.monster:setLootDrop(lootDrop) end
			if maxHealth then m.monster:setHealth(maxHealth) end
			if exp then m.monster:setExp(exp) end
			if spawnOnDeath then m.monster:addConnector('onDie', self.go.id, 'respawn') end
			delayedCall(self.go.id, cooldown, 'activate')
		end
	end
end

function activate() active = true end
function respawn() cSpawn(self, true) end

--[[Auto-spawn OPTION:  If the cSpawn() call [below] is  uncommented, (by deleting the two dashes on it's left), 
   then the script doesn't need a timer or trigger. It will spawn a monster on load, and if spawnOnDeath
   is set to true, it will spawn another when that one is killed. --]]

--cSpawn()
*Script updated to detect obstacles (like breakable jars), and won't spawn if they occupy the same cell; but they spawn as soon as the object is broken/removed.
Thanks, that's a very useful snippet.
Is the timer is expressed in seconds or minutes?

Also, and this is more of a general question, how would I go about passing arguments to a function when using a hook or a connector?
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 »

The timer is in seconds.

* When I updated the script, I pasted it in with maxHealth set to 10; that needs to be maxHealth = nil ,or else every monster it spawns will have 10 hitpoints. :o

Passing arguments in lua [script] is pretty relaxed. Lua will pass along almost anything in the braces; just separate them by commas, and give raw strings quotes. For practical use though, the script function should [usually] expect what you send it, and in the case of the hooks, you don't have control of the arguments sent; you are limited to using what arguments the hook provides. In Grimrock 2, most functions [afaik] are given the script itself as the first argument; it's why you see most functions accepting (self,) as their first.

Also mistakes in the hooks very often crash the game, and sometimes lack any useful error message; you really need to be able to read it for errors, almost unaided. The scripting reference(s) tell what arguments the hooks support/or provide.

http://www.grimrock.net/modding/scripting-reference/
https://github.com/JKos/log2doc/wiki << better

Passing arguments with connectors is a bit of a hack, outside of using the first argument to know what connector triggered the script. Notice in my spawner script, that cSpawn accepts 'timer' as it's first argument; this is to be able to distinguish/ reference [,and stop] a calling timer.

*BTW: Function argument names can be whatever [syntax legal] names you want; it is useful to make them descriptive, but this has no bearing on content. Calling it 'timer', doesn't mean that it is one. Labeling it what you know [or usually expect] it to be, makes it easy to use in the function.

It is technically possible to store data arguments in the trigger's id string and parse them when it calls the function. Such arguments would need to be added to the id when hand-placed, or by the function that spawned the trigger; but it's a bit of a hack.

An often better option is to have the connector trigger a (proxy) function, one that then calls the intended function with the arguments. For instance, the proxy function could check the cell for monsters, then call the other function, and include the monster id (or ids) as arguments ~that you could not get from the trigger alone; this could let you know who stepped on the plate... this would let you extract the id of a dynamically spawned monster from a regular spawner object with a plate under it... and that could even allow one to assign an onDie() connector to whatever was spawned... because as soon as it spawns, it steps on the plate.

**Updated the script again; removed a no longer used condition check.

***Updated again... The original script was meant to disable itself, but then I got the idea to shut off the timer that called it; this has the unfortunate side effect of [predictably] stopping anything else the timer is connected to. :(

So here is the script again, but without the timer interaction:

Code: Select all

--custom_spawner script  [connect trigger to cSpawn]

cooldown = 4			  -- minimum time between spawns
monster = "crowern"		  -- monster name/kind
lootDrop = {100,"rock", } -- %100 chance to drop, Item; can be blank for no items: {}
level = 1				  -- monster level
maxHealth = nil 		  -- monster health (if changed)
spawnOnDeath = true		  -- whether to instantly spawn a new one when one of them dies 
maxSpawns = 3			   -- maximum monsters spawned ~before disabling self [ set to 0 for unlimited spawns]
exp = nil			 	  --Change experience point value
teleportEffect = true	  --Whether or not to display the teleport effect when spawning
--Auto-spawn option:  See the last line of the script


--_________________________________________________________
spawned = 0	
active = true
function cSpawn(timer,bypass) 
	if maxSpawns then  
		if maxSpawns > 0 and spawned >= maxSpawns then
 			maxSpawns = nil
			 return -- disable
		 end
		if active or bypass then
			for obj in self.go.map:entitiesAt(self.go.x,self.go.y) do
				if obj.obstacle or obj.monster and obj.elevation == self.go.elevation then
					return  --cell is occupied, cancel spawn
				end
			end
			active = false
			local m = self.go:spawn(monster)
			spawned = spawned +1
			if teleportEffect and self.go.map:checkLineOfSight(self.go.x, self.go.y, party.x, party.y, party.elevation) then self.go:spawn('teleportation_effect') end
			if level > 1 then m.monster:setLevel(level) end
			if lootDrop then m.monster:setLootDrop(lootDrop) end
			if maxHealth then m.monster:setHealth(maxHealth) end
			if exp then m.monster:setExp(exp) end
			if spawnOnDeath then m.monster:addConnector('onDie', self.go.id, 'respawn') end
			delayedCall(self.go.id, cooldown, 'activate')
		end
	end
end

function activate() active = true end
function setMaxSpawns(self,number) if type(number) == "number" then maxSpawns = number spawned = 0 active = true else maxHealth = nil end end
function respawn() cSpawn(self, true) end

--[[Auto-spawn OPTION:  If the cSpawn() call [below] is  uncommented, (by deleting the two dashes on it's left), 
   then the script doesn't need a timer or trigger. It will spawn a monster on load, and if spawnOnDeath
   is set to true, it will spawn another when that one is killed. --]]

--cSpawn()
Last edited by Isaac on Sat Aug 13, 2016 8:13 pm, edited 2 times 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 »

Isaac wrote:Also mistakes in the hooks very often crash the game, and sometimes lack any useful error message; you really need to be able to read it for errors, almost unaided.
What? You still get an error message in stdout if an error appears in one of your hooks. Just run the game from a terminal if you want to see the error messages.
Isaac wrote:Passing arguments with connectors is a bit of a hack, outside of using the first argument to know what connector triggered the script. Notice in my spawner script, that cSpawn accepts 'timer' as it's first argument; this is to be able to distinguish/ reference [,and stop] a calling timer.
Connectors pass all the arguments that the hook has. A SurfaceComponent onInsertItem connector passes the SurfaceComponent and the ItemComponent.
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
Isaac
Posts: 3185
Joined: Fri Mar 02, 2012 10:02 pm

Re: Ask a simple question, get a simple answer

Post by Isaac »

minmay wrote:
Isaac wrote:Also mistakes in the hooks very often crash the game, and sometimes lack any useful error message; you really need to be able to read it for errors, almost unaided.
What? You still get an error message in stdout if an error appears in one of your hooks. Just run the game from a terminal if you want to see the error messages.
I believe you, but for some reason, I've never been able to see that for myself; having tried to run the game from the terminal; is there some command-line switch, or output redirection that goes with it? [OS Win7]

But to someone unfamiliar with it, the game can just seem to crash, or it doesn't... and the editor preview simply doesn't run when clicked; or the editor doesn't load the map...
Connectors pass all the arguments that the hook has. A SurfaceComponent onInsertItem connector passes the SurfaceComponent and the ItemComponent.
Anytime a hook fires, its arguments are passed [afaik], but what's an example of a connector triggering a hook? (~besides onInsertItem /acceptItem, or script assigned.)

I may have misunderstood, but I took the question to mean passing arguments to script_entities via connectors and/or hooks.
Post Reply