Jump to content
Gimble

User Scripting: continuous actions like Attack()

Recommended Posts

Code first. Moderators, please move if in wrong forum.

IF
 TimerExpired(11) // Continuous action timer
THEN
 // Continuous actions, like Attack() or FollowObjectFormation()
 // has been running for a while.  We want to break the continuous
 // action without aborting anything else that might be going on
 // instead (e.g. a user-selected and cast spell, for example).
 // Experimentation seems to indicate that starting a timer is
 // sufficient to break the continuous action without disrupting
 // useful activities.
 RESPONSE #100
StartTimer(12,6) // Timer 12 unused, just started to break the continuous action
END

IF
 ActionListEmpty()
 Range(Player2,0)
 !See(NearestEnemyOf(Myself))
 !See([EVILCUTOFF])
THEN
 RESPONSE #100
StartTimer(11,2) // Start the continuous action timer
FollowObjectFormation(Player1,3,2)
END

IF
 ActionListEmpty()
 OR(2)
See(NearestEnemyOf(Myself))
See([EVILCUTOFF])
THEN
 RESPONSE #100
StartTimer(11,6) // Start the continuous action timer
Attack(LastSeenBy(Myself))
END

A lot of the AI Scripting code easily findable is geared towards monster AI (don't have to deal with user input). For user scripts, you have to do your best to avoid stopping an action the user requested.

 

However, the two common methods used for attacking ( AttackOneRound() and AttackeReevaluate() ) are both known to have issues from posts gathered here and elsewhere. It's frustrating to watch a user script sit there, attack foes, but not actually get an attack role for some percentage of the time.

 

The above code is something I figured out yesterday as an acceptable solution. Continuous actions (like Attack() ) are broken by almost any script action, including setting timers or globals. However, most "valuable" actions like casting spells are not broken by some actions.

 

As a result, a local timer becomes an easy way to interrupt a continuous action without interrupting an overridden action. Early testing seems very promising: with my AI script (larger than the sample): the team follows the leader, attacks when in range, and I can override with spells or movement from the keyboard without issue.

 

Hope this helps!

Edited by Gimble

Share this post


Link to post

I have done similar things for monster AI scripts but not quite the same. Would this work similarly for that?

Share this post


Link to post

After typing "continuous action timer"several times, I've decided to abbreviate it to CAT for this post at least.

 

I believe so. I'm compiling my user scripts to .bcs ( I have a slot-loading system, with the user script loading position & class specific scripts in the RACE and GENERAL slots ), and so far in testing I have been... very happy with the results.

 

The follow and move to combat code works like a charm, once I converted to MoveToObject() with a range check from FollowObjectFormation(). I had already worked out a solution for stealth / search combinations that does pretty well, and the BardSong ability actually works as stolen from the eSeries (with a Delay(2) IIRC).

 

Attack() as mentioned above works like a charm. Since I also set a second CAT on spellcasting, it also makes "cast and attack" work: the Attack() action will also be broken whenever the spell CAT expires.

 

Overall, I'm very happy with how the user scripts work with this model. The biggest problem is right after the end of combat: for a round or so, you still get expiring CATs. This makes looting a little annoying, because looting is also apparently a continuous action timer and can be broken by an expiring timer. You kill the last dude, click on the loot, and usually manage to walk to the corpse and open the container to loot. Then, the CAT expires from the last Attack(), and the container closes again... and you're standing on top of the loot. So, you have to either right-click into inventory, or move away and come back to the container to get the stuff.

 

Yes, just a few second delay before you start looting avoids the whole mess, but I was surprised at how ingrained that natural "combat is over, let's start looting!" behavior is in my personality at least.

Share this post


Link to post

I think my only nitpick with this is StartTimer/TimerExpires--it tends to be a bit flaky, i.e. it gets lost in saves. Is there any reason it couldn't be replaced with a more robust global timer in the LOCALS space?

Share this post


Link to post

That could be done, when coupled with a local variable to denote the timer was started. It's a slight performance penalty because of string checks, but probably doable. I haven't had any problems with the StartTimer(s) just because they're very transient and are just used to interrupt continuous actions in progress: something that is (automatically) interrupted during program loads and frequently during saves.

 

If I encounter any problems, I'll use that as a fallback position. Thanks for the thought.

Share this post


Link to post

I've never experienced an action being interrupted when a timer expired. Could it be because you use StartTimer/TimerExpires instead of SetGlobalTimer/GlobalTimerNotExpired ?

Share this post


Link to post
Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...