Jump to content

Fixing an attack block for a party AI script


suy

Recommended Posts

I have a humble AI script that I have been iterating on slowly by imitating other scripts that I've found, specially the ones coming from the game (e.g. thief0.bs, bard0.bs, etc.).

It does some simple checks at the start to let the user change variables with a key, then depending on those variables find traps/sing bard song/etc. That part seems to work perfectly well. The last part, attacking, it's what I get wrong. The attack block is at the very bottom, and just has this:

IF
	Global("suy#fight","LOCALS",1)
	ActionListEmpty()
	!StateCheck(Myself,STATE_INVISIBLE)
	CheckStatLT(Myself,1,SANCTUARY)
	See(NearestEnemyOf(Myself))
	!InParty(LastSeenBy(Myself))
	!Allegiance(LastSeenBy(Myself),GOODCUTOFF)
	!Class(LastSeenBy(Myself),INNOCENT)
	OR(2)
		Range(LastSeenBy(Myself),8)
		InWeaponRange(LastSeenBy(Myself))
	!ModalState(DETECTTRAPS)
	!ModalState(STEALTH)
	!ModalState(TURNUNDEAD)
	!ModalState(BATTLESONG)
THEN
	RESPONSE #100
		AttackReevaluate(LastSeenBy(Myself),30)
END

With this exact block, it more or less works well, but I get that sometimes a party member attacks another. If it matters, it's Jaheira (slot #2) that attacks Charname (in slot #1). I seem to reproduce this reliably, but it doesn't happen the other way around. I got it on a recording if it helps to show it (I need to trim it a bit to upload it though).

I've tried different variations. One is that instead of `AttackReevaluate(LastSeenBy(Myself), 30)` I use `AttackReevaluate([EVILCUTOFF],30)`. With that, never the party member is attacked, but I find that if a party member gets charmed, this might end up attacked. I've also tried `See([EVILCUTOFF])` in the trigger.

I look at the existing simple AI scripts, and they seem to do different for the attacking block. I don't know what's right. I know from other conversations that I've read that `See()` is a bit more complicated than what shows on the surface, so I guess that's my issue, but I can't find the details.

Any idea? Thanks!

Link to comment

Yep, that was one of the combinations that I tried.

With that it mostly works. It doesn't do the weird targeting of a team member by default. But has two problems:

  • If a character stands next to a charmed teammate, it doesn't go for any enemy. I guess it's fine, and I just need to repeat the block with the "second nearest". Is there no other better way?
  • But the main issue is that if an enemy gets killed, and the next close hostile creature is a team member, it goes for it! How can that be?

Here is a video to show what I mean: https://imgur.com/a/Q9UQiry It's puzzling me.

 

Edited by suy
Remove heavy attachment, and replace with link to IMGUR
Link to comment

Well, this is even funnier: after an enemy gets killed, even with the AI disabled, it goes for the next creature. So maybe it's some engine quirk, and the AI script is not to blame? Some other script beyond the party AI? Attached another short video. Note how the AI is disabled from the start. Charname and Jaheira are attacking a true enemy, with Corwin charmed in the middle. After the goblin is killed, they go for Corwin because it's the one closer, apparently. But again, the AI is disabled. At the end of the video happens again. I toggle AI with a shortcut.

Edit: I could not upload the video (sorry for spamming the forum with attachments). Here is a link: https://imgur.com/a/tkpDBDv

Edited by suy
link to video
Link to comment

I checked the IESDP:

Quote

Info: Conventional object targets (e.g., ‘NearestEnemyOf(Myself)’) are “locked in” until:

  • They die / get removed from the game
  • The creature has been chasing target for the time specified by the ‘ReevaluationPeriod’ parameter

On the other hand, object selectors (e.g., ‘FifthNearest([EVILCUTOFF.PLANT])’, ‘[PC]’, ‘SecondNearest([0.0.0.IMP])’ and so forth) constantly reevaluate the target, not being affected by the ‘ReevaluationPeriod’ at all.

I think that's what's catching you here; NearestEnemyOf(Myself) is locked in, but those later references to LastSeenBy(Myself) get reevaluated while the command to attack is still active, and the character switches targets. If you were issuing a one-time command for a single action, they would be fine - but this attack command is expected to last a full five rounds.

On my part, I'd use an AttackOneRound command here, as well as switching to use the same object selector throughout the block. Party members are expected to kill targets quickly and need to react to changing circumstances, so more than that is just too long.

Link to comment
23 minutes ago, jmerry said:

On my part, I'd use an AttackOneRound command here, as well as switching to use the same object selector throughout the block. Party members are expected to kill targets quickly and need to react to changing circumstances, so more than that is just too long.

For enemy ai, I 100% agree with this statement. For player ai, it's not necessarily a bad thing to keep a character focused for a bit longer on an enemy. Checks can be made so if the current target is unhittable to then switch immediately to a more vulnerable character. I prefer less movement downtime which might mean hitting a less optimal target for a couple extra swings until they die before moving onto a juicer target who may be a short distance away. All else fails, player intervention can get involved to manually make those directions. It's very possible with attackoneround() to take a half round to get to an enemy only to decide after a half round of attacks that something may be just a little bit better to hit somewhere else now in sight.

For what it is worth, my enemy targeting ai looks like this for non-mages (cycles from nearest enemy to sixth nearest enemy):

IF
  ActionListEmpty()
  Global("BDAI_DISABLE_ATTACK","LOCALS",0)
  !Global("BDAI_SKILL_MODE","LOCALS",4)
  !Global("BDAI_SKILL_MODE","LOCALS",3)
  !Global("BDAI_SKILL_MODE","LOCALS",1)
  Global("MO_MyDetectEnemyIllusion","LOCALS",0)
  !ModalState(SHAMANDANCE)
  !CheckStat(Myself,1,SANCTUARY)
  !StateCheck(Myself,STATE_INVISIBLE)
  See(NearestEnemyOf(Myself))
  WeaponEffectiveVs(NearestEnemyOf(Myself),MAINHAND)
  WeaponCanDamage(NearestEnemyOf(Myself),MAINHAND)
  OR(7)
    Class(Myself,FIGHTER_ALL)
    Class(Myself,CLERIC_ALL)
    Class(Myself,THIEF_ALL)
    Class(Myself,BARD_ALL)
    Class(Myself,PALADIN_ALL)
    Class(Myself,DRUID_ALL)
    Class(Myself,RANGER_ALL)
  !Class(Myself,CLERIC_MAGE)
THEN
  RESPONSE #100
    Attack(NearestEnemyOf(Myself))
END

And for my mages, I only want them to attack if they have a missile weapon or tenser's active (again, it cycles from the nearest to sixth nearest enemy):

IF
  ActionListEmpty()
  Global("BDAI_DISABLE_ATTACK","LOCALS",0)
  !Global("BDAI_SKILL_MODE","LOCALS",4)
  !Global("BDAI_SKILL_MODE","LOCALS",3)
  !Global("BDAI_SKILL_MODE","LOCALS",1)
  Global("MO_MyDetectEnemyIllusion","LOCALS",0)
  !ModalState(SHAMANDANCE)
  !CheckStat(Myself,1,SANCTUARY)
  !StateCheck(Myself,STATE_INVISIBLE)
  See(NearestEnemyOf(Myself))
  OR(3)
    HasItem("melfmet",Myself)  // Melf's Minute Meteor
    IsWeaponRanged(Myself)
    CheckSpellState(Myself,TENSERS_TRANSFORMATION)
  WeaponEffectiveVs(NearestEnemyOf(Myself),MAINHAND)
  WeaponCanDamage(NearestEnemyOf(Myself),MAINHAND)
  Class(Myself,MAGE_ALL)
THEN
  RESPONSE #100
    Attack(NearestEnemyOf(Myself))
END

 

Edited by morpheus562
Link to comment

And since the player can stop the action at any moment(via the SPACE button), there's nothing that tells that the party member script cannot just move to the hostile target next to them if the current one is dead, as that would be the expected behaviour...

Link to comment

Thank you everyone for the feedback. I'll try what you all suggested, and see if I can get a clearer picture. I see that bdefai.bcs uses AttackOneRound, while SCS's bddefai.ssl uses Attack. I'll try both and see how they feel differently.

A quick question though:

On 11/23/2022 at 10:47 PM, jmerry said:

If you were issuing a one-time command for a single action, they would be fine - but this attack command is expected to last a full five rounds.

I may be misunderstanding something. IESDP says that the reevaluation period "is measured in AI updates (which default to 15 per second). (...) Just to give an idea, AttackOneRound() would be equivalent to AttackReevaluate() with a 100 tick reevaluation period".

I thought that, at 30 FPS, the period being 30 would be 2 seconds (since the AI runs each other frame).

 

Link to comment
2 hours ago, suy said:

while SCS's bddefai.ssl uses Attack.

Thing is, the .ssl doesn't translate that to be just Attack... as it's no where near to one to one transition, as it uses it's own extensions to what is read-able script phrases in .ssl code to bbe some tricky .bcs code. Just like any music is just binary data in a .mp3 file.

Link to comment

Oh, maybe I've looked at it wrong. But I mean this block:

BEGIN_ACTION_DEFINITION
	Name(Attack)
	TRIGGER
                ActionListEmpty()
	        !StateCheck(Myself,STATE_INVISIBLE)
	        !CheckStatGT(Myself,0,SANCTUARY)
	        !ModalState(TURNUNDEAD)
         	!HasItem("eneblade",Myself)
	        !HasItem("melfmet",Myself)
	        See(scstarget)
	        Allegiance(scstarget,ENEMY)
	        !InParty(scstarget)
	        InParty(Myself)
	        !StateCheck(scstarget,STATE_REALLY_DEAD)
                WeaponEffectiveVs(scstarget,MAINHAND)
                WeaponCanDamage(scstarget,MAINHAND)
	ACTION
		RESPONSE #scsprob1
		Attack(scstarget)
END

The action at the end seems will just expand to Attack(NearestEnemyOf(Myself)) (or 2nd, 3rd nearest, etc, as it's used with 3 targets).

Link to comment

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

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...