Jump to content

how do creature scripts work?


subtledoctor

Recommended Posts

I might finally dabble in some scripting... just a little!  My familiar mod bypasses the EET system for familiars, because I don't fully know what it does.  In order for it to work in all phases of the game, from BG1 through ToB, I need to make familiars advance as the player gains experience.  Just a little!

I looked at the 'More Style For Mages' familiar component, and it puts a script into the .CRE file's "override script" entry.  The script has stuff like this:

IF
    XPGT(Player1,88999)
    Global("rgftupgr","LOCALS",0)
THEN
    RESPONSE #100
        DisplayStringHead(Myself,@487)
        AddSpecialAbility("RGFAT08")
        AddSpecialAbility("RGFAT07")
        AddSpecialAbility("RGFAT06")
        AddSpecialAbility("RGFAT05")
        ApplySpellRES("RGFAHP1",Myself)
        SetGlobal("rgftupgr","LOCALS",1)
END

Seems simple enough - I can replicate something like that.  But, I want to really understand what I would be doing.  I'm willing to put thousands of effects into a spell that fires repeatedly (literally) because spells don't slow down the game very much, and the modern engine is extremely efficient at processing spell effects.  But (perhaps from bad experiences a decade or two ago) I'm very leery of adding stuff to scripts that constantly run.  In my experience, scripts are far more likely to bog down the game and degrade the play experience.

So, given that the above code really only needs to run once over the course of a ~100-hour game, I don't want the engine to check for it every 6 seconds.  That's just silly.  Is that what happens if I put it in the .CRE's override script?  Is there a way to trigger the check less frequently?

Hm, maybe I should go back to thinking about spells instead.  Given my mod already sets a spellstate ("IS_FAMILIAR") on familiars, maybe I should just blast out an AoE spell at certain level-ups, in the CLAB table of every arcane spellcaster (and the Beast Master).  Suppress the spell unless and until the caster casts Find Familiar, so it only ever actually gets cast by someone with a familiar.  That way, the effects that bump the familiar's stats only ever have to processed once.

Of course, if the familiar is in your pack at the time, maybe the effect would miss them.  That would be bad...

I know there are several mods out there that do this sort of thing - More Style for Mages, Ulb's Animal Companions, the Djinni Companion, maybe Grey the Dog... Haiass el Lobo... probably even more.  Does anyone have advice for the best way to achieve it?

Edited by subtledoctor
Link to comment

Adding a few script blocks won't ruin or slow down anything. It's adding a few hundred blocks when you should stop and ask yourself if you're doing it right. Furthermore, if the script in question is your mod's and not e.g. baldur.bcs, then there's not much reason to back down even from a few hundreds.

Apart from that, there are random execution delays up to a couple seconds (but rarely above half a second) when scripts run, so it's a poor choice when you need very precise timing and have no control over game's state (aka not in cutscene mode).

If you absolutely insist on optimizing it to perfection, then you can order trigger lines from top to bottom to be increasingly less critical and more CPU heavy - i.e. instead of checking for ten nearest enemies in range, then for having the appropriate spell, and then for spell cooldown and/or for being in combat, you can do it in reverse order and avoid running more CPU-expensive range checks when you don't need them anyway.

Likewise, you can do some high level general stuff at the top of combat script and then cut off the rest of it with e.g. ~IF /* not in combat */ THEN RESPONSE NoAction() END~ and save yourself putting hundreds of individual in-combat checks in every block. But you may shoot yourself in the foot with this unless you really know what you're doing and keep the structure firmly in mind.

Edited by Ardanis
Link to comment

I wouldn't worry about quantity of a lot of lines until you hit a performance impacting level personally, and I wouldn't hesitate on it being in a .cre file for similiar reasons. Is the player having the Familiar out really going to be out even that much to be of a concern even in the worst-case possibility of tons of creatures around? 

 

That said optimization should be practiced where possible, but I don't think it needs to be factored in to a decision on putting a script in the cre file or not.

 

Edited by Skitia
Link to comment
On 2/19/2020 at 3:50 AM, Ardanis said:

If you absolutely insist on optimizing it to perfection, then you can order trigger lines from top to bottom to be increasingly less critical and more CPU heavy - i.e. instead of checking for ten nearest enemies in range, then for having the appropriate spell, and then for spell cooldown and/or for being in combat, you can do it in reverse order and avoid running more CPU-expensive range checks when you don't need them anyway.

  • What are the most demanding script triggers for the CPU?
  • Generally speaking, which is the maximum number of script triggers a script block should have? You know, when casting a spell which is not party-friendly, you might easily have more than 40 script triggers...
Link to comment
On 3/15/2020 at 2:20 PM, Luke said:
  • What are the most demanding script triggers for the CPU?
  • Generally speaking, which is the maximum number of script triggers a script block should have? You know, when casting a spell which is not party-friendly, you might easily have more than 40 script triggers...

Probably NearestXXX().

There aren't any real limits - SCS scripts are extremely heavyweight, but the game still runs fine (I think). The only slowdown issue I'm aware of are SoD mass battles on mobile platform, and I don't remember if giving them individual scripts instead of sharing one universal actually improved the performance.

If you want to recreate SoD's scale, then consider keeping AI minimalistic, otherwise you might not need to care.

Link to comment
16 hours ago, Ardanis said:

The only slowdown issue I'm aware of are SoD mass battles on mobile platform,

Device specs? I've noticed no slowdowns so far (Apple A9X chip...)

16 hours ago, Ardanis said:

Probably NearestXXX().

Are you talking about 'See()' triggers?

Link to comment
3 hours ago, Luke said:

Are you talking about 'See()' triggers?

Probably not. cause there's:

29 SecondNearestEnemyOf
30 ThirdNearestEnemyOf
31 FourthNearestEnemyOf
32 FifthNearestEnemyOf
33 SixthNearestEnemyOf
34 SeventhNearestEnemyOf
35 EighthNearestEnemyOf
36 NinthNearestEnemyOf
37 TenthNearestEnemyOf
38 SecondNearest
39 ThirdNearest
40 FourthNearest
41 FifthNearest
42 SixthNearest
43 SeventhNearest
44 EighthNearest
45 NinthNearest
46 TenthNearest

And lot of others like that in objects in 50 - 114.

Link to comment
5 hours ago, Luke said:

Device specs? I've noticed no slowdowns so far (Apple A9X chip...)

No idea, it's been a while ago, and I don't use any myself. But iirc something on the older side.

5 hours ago, Luke said:

Are you talking about 'See()' triggers?

Any trigger that calls Nearest()/Farthest() object. Obviously, it only has a possibility to become an issue if you routinely run hundreds of checks, simultaneously for many actors. In practice, I never had any slowdown on PC, so the precautions taken with SoD were mainly to minimize the CPU load during siege events, in case for some devices it would in fact become too intense.

Edited by Ardanis
Link to comment

@Ardanis

So, to sum up:

what would be the best alternative (performance-wise) between something like this

Spoiler
IF
	!StateCheck(NearestEnemyOf(Myself),STATE_INVISIBLE)
	See(NearestEnemyOf(Myself))
	WeaponEffectiveVs(NearestEnemyOf(Myself),MAINHAND)
	WeaponCanDamage(NearestEnemyOf(Myself),MAINHAND)
	OR(2)
		!CurrentAmmo("AROW01",Myself)
		CheckStatLT(NearestEnemyOf(Myself),65,RESISTMISSILE)
	OR(2)
		!CurrentAmmo("AROW02",Myself)
		CheckStatLT(NearestEnemyOf(Myself),75,RESISTMISSILE)
	!StateCheck(NearestEnemyOf(Myself),STATE_REALLY_DEAD)
THEN
	RESPONSE #100
		AttackOneRound(NearestEnemyOf(Myself))
END

 

and this

Spoiler
IF
	CurrentAmmo("AROW01",Myself)
	!StateCheck(NearestEnemyOf(Myself),STATE_INVISIBLE)
	See(NearestEnemyOf(Myself))
	WeaponEffectiveVs(NearestEnemyOf(Myself),MAINHAND)
	WeaponCanDamage(NearestEnemyOf(Myself),MAINHAND)
	CheckStatLT(NearestEnemyOf(Myself),65,RESISTMISSILE)
	!StateCheck(NearestEnemyOf(Myself),STATE_REALLY_DEAD)
THEN
	RESPONSE #100
		AttackOneRound(NearestEnemyOf(Myself))
END

IF
	CurrentAmmo("AROW02",Myself)
	!StateCheck(NearestEnemyOf(Myself),STATE_INVISIBLE)
	See(NearestEnemyOf(Myself))
	WeaponEffectiveVs(NearestEnemyOf(Myself),MAINHAND)
	WeaponCanDamage(NearestEnemyOf(Myself),MAINHAND)
	CheckStatLT(NearestEnemyOf(Myself),75,RESISTMISSILE)
	!StateCheck(NearestEnemyOf(Myself),STATE_REALLY_DEAD)
THEN
	RESPONSE #100
		AttackOneRound(NearestEnemyOf(Myself))
END

 

 

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