onThink on predefined monster brain (needs clarification)

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!
Post Reply
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

onThink on predefined monster brain (needs clarification)

Post by Xanathar »

This is mostly a request for clarification for devs, but anybody is welcome to jump in and/or it might become a tip for better brains (braaaaainsssss).

So, the situation I have (and I'm not the only one I guess) is I want to customize the monster behavior, but only in a few select cases and let the standard brain of that monster to do its job. So I just decided to write an ad-hoc onThink handler.

Getting to the onThink docs it says:
Base-class for all monster brains. You can either use one of the built in brains (e.g. TurleBrain) or implement a custom onThink hook. When the monster is ready to perform a new action, the brain’s onThink hook is called. The brain should respond by calling monster component’s performAction() method with a valid action name. The brain class itself contains many higher level behaviors such as fleeing and pursuit which can be used in place of performAction() to delegate decision making. If the brain can not decide what to do, Brain:wait() should be called. This will put the monster’s brain to sleep for a short time (typically 0.1s) to prevent extensive CPU usage.
So I went on writing this (pseudo here) code, with the intention of amending it later for the other actions:

Code: Select all

{
	class = "SkeletonCommanderBrain",
	name = "brain",
	sight = 6,
	allAroundSight = true,
	morale = 100,	
	onThink = function(self) 
		if someSpecialCaseIWantToHandle() then
			return self:performAction(...);
		elseif partyIsQuiteFarAwayFromMeAndImLazyAsDuck() then
			return self:wait();
		end
	end,
},
And... magic! it worked as I intended, that is - if I took no action, the SkeletonCommanderBrain came in and selected an action according to its logic.

BTW, this is a great feature as it means we can easily override just some behaviours, instead of every single one.

Now, my mental model of what happens is:
  • LoG calls the brain component think() method (or whatever it's called, as we don't know)
  • As first line (or close), it sees that it has a custom onThink handler and calls that.
  • Upon returning, it checks if an action is in progress. If not, it performs its usual logic (alternative: it processes its usual logic anyway, which gets ignored because there is an action queued already).

What I want to understand is:
  • Am I right and not hallucinating ? :)
  • Has this a performance impact ? If my mental model is correct, I guess not, but a word from the devs is welcome :)
  • Is this intended ? I just won't want to have to fix all monsters when a new Log patch comes out ;)
  • If so, can the documentation be changed to the following (for whoever comes next ;) ) ?
Proposed documentation:
Base-class for all monster brains. You can either use one of the built in brains (e.g. TurleBrain) or implement a custom onThink hook or both. When the monster is ready to perform a new action, the brain’s onThink hook is called. The brain should respond by calling monster component’s performAction() method with a valid action name. The brain class itself contains many higher level behaviors such as fleeing and pursuit which can be used in place of performAction() to delegate decision making. If the brain can not decide what to do, Brain:wait() should be called. This will put the monster’s brain to sleep for a short time (typically 0.1s) to prevent extensive CPU usage. In case the onThink hook is implemented in a built-in brain, the hook may execute no action (including no calls to Brain:wait()); in this case the built-in brain default logic is executed.
EDIT-1: Edited to reflect the "return pattern" suggested by petri, so that future copy-pasters will get the right code.
Last edited by Xanathar on Thu May 14, 2015 5:10 pm, edited 3 times in total.
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
petri
Posts: 1917
Joined: Thu Mar 01, 2012 4:58 pm
Location: Finland

Re: onThink on predefined monster brain (needs clarification

Post by petri »

I think it should work. However, I recommend you use the following idiom when performing monster actions: "return self:wait()", "return self:performAction(...)", etc. The action routines return true if the action was successfully started, so the return statement in onThink will propagate this value to the higher level brain function and cause an early out. Without the 'return' the brain will try other actions for no reason.
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: onThink on predefined monster brain (needs clarification

Post by Xanathar »

Awesome, thanks! 8-)
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
petri
Posts: 1917
Joined: Thu Mar 01, 2012 4:58 pm
Location: Finland

Re: onThink on predefined monster brain (needs clarification

Post by petri »

Another nice idiom is to chain the action using the early out property of the 'or' operator in Lua. For example, the definion of one of the simplest brains, the turtle brain is simply this:

Code: Select all

function onThink(s)
	return
		s:wanderIfPartyNotDetected() or
		s:meleeAttack() or
		s:moveAttack() or
		s:alert() or
		s:waitIfBlocked() or
		s:pursuit() or
		s:wait()
end
The code tries actions one after another until one of them returns true. You can define arbitrary new functions (in script entities) as long as the function returns true on success and false on failure.
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: onThink on predefined monster brain (needs clarification

Post by Xanathar »

Thanks petri for your replies :)

I've tried and actually adding the returns as you suggested, actually breaks the built-in brain.

Probably the built-in brain stops doing its thing forever as soon as the hook returns true ?

Without the returns, it works wonders.
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
petri
Posts: 1917
Joined: Thu Mar 01, 2012 4:58 pm
Location: Finland

Re: onThink on predefined monster brain (needs clarification

Post by petri »

When your custom brain is active you should return true but if you want to fall back to the built-in brain you should take no action and return false (or no value), like this:

Code: Select all

onThink = function(self) 
      if someSpecialCaseIWantToHandle() then
         return self:performAction(...);
      end
     
      -- do nothing (fall back to the build-in brain)
      -- return false -- this line can be omitted because the implicit return value is "no value" 
end
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: onThink on predefined monster brain (needs clarification

Post by Xanathar »

You are right...

I had a little mistake (if party.level ~= self.level instead of self.go.level ... :oops: ).

Now it works. 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