Page 1 of 1

onThink on predefined monster brain (needs clarification)

Posted: Thu May 14, 2015 10:29 am
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.

Re: onThink on predefined monster brain (needs clarification

Posted: Thu May 14, 2015 11:07 am
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.

Re: onThink on predefined monster brain (needs clarification

Posted: Thu May 14, 2015 11:09 am
by Xanathar
Awesome, thanks! 8-)

Re: onThink on predefined monster brain (needs clarification

Posted: Thu May 14, 2015 11:11 am
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.

Re: onThink on predefined monster brain (needs clarification

Posted: Thu May 14, 2015 2:19 pm
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.

Re: onThink on predefined monster brain (needs clarification

Posted: Thu May 14, 2015 4:46 pm
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

Re: onThink on predefined monster brain (needs clarification

Posted: Thu May 14, 2015 5:10 pm
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!