Jump to content

script block running on creature that shouldn't run it...


plainab

Recommended Posts

IF
 ActionListEmpty()
 !See([ENEMY])
 Global("ab_fight","GLOBAL",0)
 CheckStatGT(Myself,1,TRAPS)
 !GlobalTimerNotExpired("ab_trap_look","LOCALS")  //one turn timer expired
THEN
 RESPONSE #100
SetGlobalTimer("ab_trap_look","LOCALS",ONE_ROUND)  //one turn timer 
//	FloatMessage(Myself,@41)
DisplayString(Myself,@41)  
FindTraps()
END

This script block is the very last block in a script shared between fighters and thieves.

It is being ran on ToTSC/Saga. Imoen runs the block as would be expected, however Khalid also runs the block.

 

I know this by the text being displayed when the block is ran.

 

Checking Khalid's value for trap stat shows that he has, as would be expected, a value of 0. So unless CheckStatGT doesn't work or is setup wrong I'm at a loss as to why Khalid would run this block...

Link to comment

I believe these totals are dexterity-influenced for all classes, so Khalid probably has sufficient dexterity to give a positive bonus (even though you'll never see it in the GUI). (But if you checked his find traps stat with CheckStat() and it said he has 0, then I'm not sure offhand what would be the cause.)

Link to comment

i checked by looking at his save game data... didn't know that dexterity would trigger a false positive... I'll have to put in some test code where he'll say something when CheckStat() matches the value that the game is returning for him.

 

hmm or maybe I could first try going into the 2da file that shows the starting value for thieves by race and use CheckStatGT() the lowest value -1

Link to comment

so I tacked this onto my temporary trailing component to test for stutter (in my case it was to make sure the scripts were actually being properly called up)

<<<<<<<< checktrap.baf
IF
 CheckStat(Myself,%x%,TRAPS)
THEN
 RESPONSE #100
DisplayString(Myself,~Traps stat is %x%~)
END
>>>>>>>>
OUTER_FOR (x=0;x<101;x+=1) BEGIN
EXTEND_BOTTOM ~ab_figh.bcs~ ~checktrap.baf~
 EVALUATE_BUFFER
END

Khalid reports having a trap value of 5.

His dexterity is 16

according to skilldex.2da

			PICK_POCKETS	OPEN_LOCKS	  FIND_TRAPS	  STEALTH
16		  0			   5			   0			   0

he should have no bonus at dexterity 16

 

ah I see what is happening... Khalid is an elf and elfs get a bonus of 5 to the traps skill

 

hmm think I'll have to split the original block up by race and check for GT than the race bonus cause a thief character is bound to have some points added in addition to what the race provides

Link to comment

doing a GT check for the race bonus did the trick.

 

now let me ask something else...

 

IF
 ActionListEmpty()												  //not doing any user commands
 HPPercentLT(MostDamagedOf(GroupOf(Myself)),80)					 //an injured party member
 HaveSpell(CLERIC_CURE_LIGHT_WOUNDS)								//have spell
 CheckStatLT(Myself,50,SPELLFAILUREPRIEST)						  //good chance of casting spell
 //these potions are all better than this spell make sure that they are used first
 !PartyHasItem("POTN08")   //9hp
 !PartyHasItem("POTN17")   //10hp
//  !PartyHasItem("extheal")  //18hp
//  !PartyHasItem("elfwine")  //2d8 or 2-16hp
 !PartyHasItem("POTN42")   //2hp/round for 3 turns - 10 rounds/turn - 2hp * 30 rounds = 60hp max
 !GlobalTimerNotExpired("ab_heal_spell","GLOBAL")					   //global timer so certain spells don't overlap by multiple casters
 !GlobalTimerNotExpired("ab_cast_a_spell","LOCALS")					 //local timer for any spells
 !GlobalTimerNotExpired("ab_used_potion","GLOBAL")					  //used a potion timer expired
THEN
 RESPONSE #100
SetGlobalTimer("ab_heal_spell","GLOBAL",ONE_ROUND)					   //start healing timer to prevent spell overlap
SetGlobalTimer("ab_cast_a_spell","LOCALS",ONE_ROUND)					 //start personal timer
Spell(MostDamagedOf(GroupOf(Myself)),CLERIC_CURE_LIGHT_WOUNDS)   //cast spell
END

this is noticed using the script stutter patch which adds a display string to the block: why would the cleric or druid "run" the block and not actually cast the spell. I think it is waiting for the timer to actually expire to cast the spell, but everything else is true so it spouts the text line and restarts the script.

 

fyi ONE_ROUND = 6

btw how long does one round last in real time? seems it takes a long time. i counted 6-7 seconds (approx) between text output of each pass and the text was output 6 times before finally being cast.

Link to comment

I don't like talking about AI issues. Without seeing the exact placement of the DisplayString(), it's not possible to say for sure what you're seeing.

 

In general, actions aren't blocking. Spell() is a non-blocking action, so the engine is free to check for other things to do. Because this is a touch spell, the character has to path to the target (during which time the engine is still looking for blocks to execute), and because the block is no longer true after the first time (both of the timers, which are set before the call to Spell(), will keep it false for the next six seconds, and ActionListEmpty() will keep it false until the action list is cleared), the engine is free to scan further down the script.

 

But I can't tell what you're asking. If you're saying it fails a bunch of times, it's because the engine found something else to do. If you're saying that it seems to repeat a bunch of times, it's because it changes from true to false before the actions have all been executed (so it's essentially interrupting itself every six seconds, and eventually the caster will be close enough to the target to finally get the cure off before it interrupts itself again).

Link to comment

the display string gets put in right after the response #100 so it's the first thing to fire.

 

Some instances have been where the character was on path to the target and stopped midway to spout the display string and start moving again. Other instances have been where the character was right next to the target the entire time with what appears to be no other actions taking place since no other script block displays text in the interum.

 

In general, actions aren't blocking. Spell() is a non-blocking action, so the engine is free to check for other things to do. Because this is a touch spell, the character has to path to the target (during which time the engine is still looking for blocks to execute), and because the block is no longer true after the first time (both of the timers, which are set before the call to Spell(), will keep it false for the next six seconds, and ActionListEmpty() will keep it false until the action list is cleared), the engine is free to scan further down the script
so because the triggers are initially true, the character starts to perform the actions which are tucked into their list of actions while the script is restarted? Thus the script runs across the block again finding it to be true again and tucking the actions back into the character's list.

 

would it be better to set the timers AFTER the spell was cast? or better yet to put in SetInterrupt(FALSE) before the spell cast and SetInterrupt(TRUE) after? or would both be best? Guess I need to experiment...

 

this is the decompiled block rather than my initial block posted earlier

IF
ActionListEmpty()
HPPercentLT(MostDamagedOf(GroupOf(Myself)),80)
HaveSpell(CLERIC_CURE_LIGHT_WOUNDS)
CheckStatLT(Myself,50,SPELLFAILUREPRIEST)
!PartyHasItem("POTN08") // Potion of Healing
!PartyHasItem("POTN17") // Elixir of Health
!PartyHasItem("POTN42") // Potion of Regeneration
!GlobalTimerNotExpired("ab_heal_spell","GLOBAL")
!GlobalTimerNotExpired("ab_cast_a_spell","LOCALS")
!GlobalTimerNotExpired("ab_used_potion","GLOBAL")
THEN
RESPONSE #100
	ActionOverride(Myself,DisplayString(Myself,24312)) // Running block 13 of ab_prie.BCS
	SetGlobalTimer("ab_heal_spell","GLOBAL",THIRTY_SIX_SEC)
	SetGlobalTimer("ab_cast_a_spell","LOCALS",THIRTY_SIX_SEC)
	Spell(MostDamagedOf(GroupOf(Myself)),CLERIC_CURE_LIGHT_WOUNDS)
END

As you can see I've been playing around with the length of the timer to see what happens. Thirty_six_sec = 3 since 5 is supposed to equal one game minute.

Link to comment
so because the triggers are initially true, the character starts to perform the actions which are tucked into their list of actions while the script is restarted? Thus the script runs across the block again finding it to be true again and tucking the actions back into the character's list.
Pretty much (when it's next true again, the current action list is flushed and the actions of the block added to the now-cleared queue). The intent of the system is that given:
Block A
Block B
Block C
Block D

then when you reach and execute Block B, processing starts over (using the first script in the chain and evaluating down), and if Block B was still executing its actions, it would remain true (the engine knows it's doing Block B, so it doesn't repeat it and instead just restarts evaluation). The engine is always evaluating the triggers, and if it finds anything else for the character to do, it will flush the object's queue and add the new actions (unless it's a blocking action or the interrupt state is off, which will prevent modification of the queue).

 

Your block breaks this behavior, because it's no longer true even though the actions are still being executed (ActionListEmpty() and the timers here really stretch how the system was designed to work).

 

(As for the case with the DisplayString() addition, ActionOverride() scrubs the action queue of the target, so it's not surprising you see additional funky behavior.)

 

BioWare used SetInterrupt() in blocks like this in ToB because the behavior is so flaky with touch spells (because so much time is spent pathing to the target), but this is ugly (you can respond to nothing else until the character successfully paths to the target and casts the spell); you could set the timers after the spell, but this is not really a solution because ActionListEmpty() prevents the block from returning true when it should.

 

You'll need to look at people who spent a lot of time doing player AI to find the best solutions for stuff like this. I never liked dealing with it.

 

For BG2, also note that GroupOf() doesn't return anything worthwhile. MostDamagedOf() automatically returns the most damaged PC (it can only return party members), although you don't have control over how it calculates "most damaged".

 

As you can see I've been playing around with the length of the timer to see what happens. Thirty_six_sec = 3 since 5 is supposed to equal one game minute.
The timers are in real seconds. Six seconds (real time) is a round, and 10 rounds (sixty seconds) is a turn. These aren't forced into game-time equivalents. SetGlobalTimer(6) is one round; SetGlobalTimer(60) is one turn. Game time is simply on a made-up scale of real seconds.

 

Combat is happening on a real-time scale, so you should just ignore game time when writing your scripts.

Link to comment
As you can see I've been playing around with the length of the timer to see what happens. Thirty_six_sec = 3 since 5 is supposed to equal one game minute.
The timers are in real seconds. Six seconds (real time) is a round, and 10 rounds (sixty seconds) is a turn. These aren't forced into game-time equivalents. SetGlobalTimer(6) is one round; SetGlobalTimer(60) is one turn. Game time is simply on a made-up scale of real seconds.

 

Combat is happening on a real-time scale, so you should just ignore game time when writing your scripts.

And here I was under the impression that there was no real time in BG1. So the timer does indeed count in actual seconds. Then why is there all this talk about how SetGlobalTimer only works in game time and SetRealGlobalTimer handles the real time.

 

hmm now I'm guessing that the display string from the stutter test code is the real culprit, since it is using action override it is making the character stop and print the text before returning to it's mission of casting the spell. However, putting in the set interrupt allowed the caster to do their spell etc...

Link to comment
You'll need to look at people who spent a lot of time doing player AI to find the best solutions for stuff like this. I never liked dealing with it.

 

For BG2, also note that GroupOf() doesn't return anything worthwhile.

If that last bit is true then I think the people who spent a lot of time dealing with AI need to revise a load of their scripts, as I think it crops up quite a bit in AI mods.
Link to comment
And here I was under the impression that there was no real time in BG1. So the timer does indeed count in actual seconds. Then why is there all this talk about how SetGlobalTimer only works in game time and SetRealGlobalTimer handles the real time.
They both count actual seconds. Real time is counted as seconds spent with the game loaded (no matter what is happening), while game time is influenced by the passage of time in the game. Since most everything in the game world happens according to game time, that is the primary counter (real time is largely useless, and it isn't even counted in BG/TotSC that I can recall).

 

Regardless, the value of a timer is always specified in real seconds. This simply creates a variable with the value of the expiry time (which is the sum of either the current game time or real time, as appropriate, and the value you specified). The value of the variable is simply tested against one of the counters when you use one of the triggers to check if the timer expired.

 

If that last bit is true then I think the people who spent a lot of time dealing with AI need to revise a load of their scripts, as I think it crops up quite a bit in AI mods.
I think it returns something like Player1 or [PC] or something, so it probably doesn't matter (you'd have to get in there and test to be sure, or find a post where it was stated for sure). I just don't recall it doing anything that would ever make you want to use it.

 

In this case, it means nothing because MostDamagedOf just ignores all of it and tests the party.

Link to comment
If that last bit is true then I think the people who spent a lot of time dealing with AI need to revise a load of their scripts, as I think it crops up quite a bit in AI mods.
I think it returns something like Player1 or [PC] or something, so it probably doesn't matter (you'd have to get in there and test to be sure, or find a post where it was stated for sure). I just don't recall it doing anything that would ever make you want to use it.

 

In this case, it means nothing because MostDamagedOf just ignores all of it and tests the party.

for readability purposes. [PC] does return any in the player party, but saying GroupOf(Myself) instead sounds better LOL
Link to comment

I thought GroupOf() refers to CRE's specifics.

 

ah I see what is happening... Khalid is an elf and elfs get a bonus of 5 to the traps skill

 

hmm think I'll have to split the original block up by race and check for GT than the race bonus cause a thief character is bound to have some points added in addition to what the race provides

Why not to add a thief class check? Looks simplier to me.
Link to comment

figured out the wonkiness in my script, the reason why it let me think the block was being acted out several times without doing anything. It was the stutter test script.

 

the code from the bigg was like this:

COPY list of files
DECOMPILE_BCS_TO_BAF
	REPLACE_EVALUATE ~\(RESPONSE #[0-9]+\)~ BEGIN
		x += 1
	END "\1
	ActionOverride(Myself,DisplayString(Myself,~Running block %x% of %SOURCE_RES%.BCS~))"
COMPILE_BAF_TO_BCS

 

I changed it to this:

	DECOMPILE_BCS_TO_BAF
	REPLACE_EVALUATE ~END~ BEGIN
		x += 1
	END "ActionOverride(Myself,DisplayString(Myself,~Running block %x% of %SOURCE_RES%.BCS~))
			END"
COMPILE_BAF_TO_BCS

and the problems went away

 

odd how a stutter test script can cause stutter :)

Link to comment
I thought GroupOf() refers to CRE's specifics.
I don't believe this object matches anything; from what I can remember, it's junk that was never used so is not guaranteed to work (I'm not surprised it works here, however, because MostDamagedOf just chucks all the rest of the crap you try to stick with it).

 

NearestMyGroup and InMyGroup() do compare specifics, but I'm not sure that's what they intended with this object (party members don't have specifics, though, so it wouldn't work right even if that's what it does, unless you gave every other CRE in the game a specifics value).

Link to comment

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...