Jump to content

Clearing some active effects from NPCs who leave the party


subtledoctor

Recommended Posts

One of my mods requires several hundred effects to be active on spellcasters. This is not troublesome, but I'm a bit concerned about savegame bloat where, if I cycle through lots of different party members over the course of a game, the savegame will still have to track all those effects on NPCs who are not in the party. I have a .SPL set up to remove those effects, and another one to add them back; what I'm a bit unclear on is how to apply those spells when leaving and re-joining the party.

I have some vague ideas for how this could work:

Maybe: find the dialogue for each NPC (from PDIALOG.2da etc.) and add an ApplySpellRES() to each instance of leaving or joining the party? Not totally sure how to systematically find those events within the dialogues, though.

Maybe:

  • have BALDUR.bcs constantly try to ApplySpellRES my spell to all joinable NPCs; but
  • have BALDUR.bcs make Player1-Player6 (anyone in the party) immune to application of the spell
Spoiler
IF
    [this_npc is not in party...??]
    TriggerOverride("[this_npc]",Global("NPC_OUT","LOCALS",0)
THEN
  RESPONSE #100
    ActionOverride("[this_npc]",SetGlobal("NPC_OUT",GLOBAL",1)
    ApplySpellRES("[spell]","[this_npc]")
END

IF
    TriggerOverride("Player[1-6]",Global("NPC_OUT","LOCALS",1)
THEN
  RESPONSE #100
    ActionOverride("Player[1-6]",SetGlobal("NPC_OUT",GLOBAL",0)
    ApplySpellRES("[other_spell]","[this_npc]")
END

 

...?

I'm sure there is a sensible answer here, but this is not an area I have much experience with.

Edited by subtledoctor
Link to comment

There's already something kind of like this in the base game - Story Mode gets cleared from anyone that leaves the party. Mostly, anyway. It's done through a block in the characters' override scripts - exactly jastey's suggestion. (And Imoen is missing this block in BGEE if you also have SoD)

Link to comment

Huh. I didn’t even realize there was an InParty trigger! :crazyeyes: That simplifies things.

I also didn’t know if I could rely on every NPC having an override script. That should work fine. And I can potentially whitelist NPCs who can operate as 7th members. (There are only, what, four of them?)

I don’t actually know how much difference this will make. But when I look at an NPC who has ~1,200 active effects in my savegame, it seems like it’s worth experimenting with removing them if they are not needed. 

Specifically this is for 5E spellcasting. The easiest way to set it up might be to cast the “change my prepared spells” ability when leaving the party. This would require them to sleep after re-joining in order to cast spells again - not too problematic if they are waiting at an inn or something. 

Or I could even give them a single-use innate to restart their spellcasting upon rejoining, then no sleep would be needed. 

Okay I’ve got some fairly simple ideas to work with, I’ll experiment a bit. Thanks for the guidance. 

Edited by subtledoctor
Link to comment

If the effects are applied via ability tables, they're already removed/re-applied as the NPC joins and leaves. However, I don't think this is the case for any subspells that get invoked unless it's done in a way to preserve the spell source in the effects.

Alternatively, if the concern is that you're going to get multiple sets of the same effects you can add a 321 at the top of an effect stack to prevent stacking of unnecessary effects, the same way that normal spells prevent stacking.

Link to comment
1 hour ago, CamDawg said:

If the effects are applied via ability tables, they're already removed/re-applied as the NPC joins and leaves.

They’re not. A brief explanation:

The system polls every valid memorizable wizard and priest spell in the game, and creates an innate ability that casts each spell via 146/148. So, about ~700 innates. And an innate-grant spell with 171 for each innate. And a grant-block spell with 206 vs. each innate-grant spell.

When you have spells memorized and rest, a script checks which spells you don’t have memorized, and applies the grant-block 206 spell for each one. Then the script casts all 700 innate-grant spells. So if you have 10 spells memorized, you must carry 690 constant 206 effects, and only 10 of the 700 innate-grant spells actually get through. Result: you get innate abilities corresponding to the 10 spells you had memorized. (And the upshot is, you can cast those memorized spells like a sorcerer does.)

You have a ‘change memorization’ innate ability that 1) wipes out all 690 of those grant-block 206 effects, and 2) allows you to memorize different spells to start the process over. After using this ability, but before resting, you have no 206 effects on you. 

If you have six party members with a thousand active effects each, it’s not a big deal. But, maybe, by late BG2 there might be 40 or more NPCs with a thousand active effects. Possibly, this is contributing to .GAM file bloat and saves that can’t be opened. 

But if each NPC simply casts the ‘change memorization’ spell when leaving the party, then bam - (34x700) ~24,000 active effects that no longer need to be tracked in the savegame. Presumably, maybe, that will slim down the file.

I even already have a mechanism to restore their spellcasting when they rejoin, and this mechanism can be used by 7th-member NPCs so even they should be fine. The only sticking point (in theory) is nailing that “when leaving the party” condition for casting the spell. I’ll check out the Story Mode thing when I get a chance, that sounds promising.

Edited by subtledoctor
Link to comment

For the sake of posterity, this is what I'm going with:

Spoiler


COPY ~%MOD_FOLDER%/lib/semi_spont/d5_base.spl~ ~override/d5zleav.spl~
  LPF ADD_SPELL_EFFECT INT_VAR opcode = 326 target = 1 parameter1 = 0 parameter2 = %not_prep_row% timing = 1 STR_VAR resource = ~d5zprp~ END
  LPF ADD_SPELL_EFFECT INT_VAR opcode = 326 target = 1 parameter1 = 0 parameter2 = %not_prep_row% timing = 1 STR_VAR resource = ~d5jpmem~ END

<<<<<<<< d5/leavscpt.baf
IF
	!InParty(Myself)
	Global("D5_LEFT","LOCALS",0)
THEN
	RESPONSE #100
	SetGlobal("D5_LEFT","LOCALS",1)
	ReallyForceSpellRES("D5ZLEAV",Myself)
	Continue()
END
IF
	InParty(Myself)
	Global("D5_LEFT","LOCALS",1)
THEN
	RESPONSE #100
	SetGlobal("D5_LEFT","LOCALS",0)
	Continue()
END
>>>>>>>>
COPY ~d5/leavscpt.baf~ ~weidu_external/%MOD_FOLDER%/compile/leavscpt.baf~

LAM JOINABLE_NPC_ARRAYS

ACTION_PHP_EACH JOINABLE_NPC_ARRAY AS cre => dv BEGIN
// 	PRINT ~%cre% => %dv%~ 
  COPY_EXISTING ~%cre%~ ~override~
    READ_ASCII 0x248 override_script
    SPRINT $npc_scripts(~%override_script%~)~1~
  BUT_ONLY
END
ACTION_PHP_EACH npc_scripts AS script => ind BEGIN
  ACTION_IF (FILE_EXISTS_IN_GAME ~%script%.bcs~) BEGIN
    EXTEND_TOP ~%script%.bcs~ ~weidu_external/%MOD_FOLDER%/compile/leavscpt.baf~
  END
END

The JOINABLE_NPC_ARRAYS is the k4thos macro which can be found in the useful Weidu macros thread.

EDIT - Derp. The joinable NPCs macro is not working, the spell is being applied to everyone on every map. Don't think it'll actually cause any problems... but I should try to nail that down.

EDIT 2 - I think the macro caught a false positive, who has SHOUT.bcs as their override script. And now everyone covered by SHOUT.bcs is having it applied.

Edited by subtledoctor
Link to comment

Okay here's a revised version. It avoids patching SHOUT.bcs and adds a secondary local variable (D5_PNPC) that prevents anything from happening until someone has actually been in the party. So the spell should only be cast on actual party members who subsequently leave.

Spoiler
COPY ~%MOD_FOLDER%/lib/semi_spont/d5_base.spl~ ~override/d5zleav.spl~
  LPF ADD_SPELL_EFFECT INT_VAR opcode = 326 target = 1 parameter1 = 0 parameter2 = %not_prep_row% timing = 1 STR_VAR resource = ~d5zprp~ END
  LPF ADD_SPELL_EFFECT INT_VAR opcode = 326 target = 1 parameter1 = 0 parameter2 = %not_prep_row% timing = 1 STR_VAR resource = ~d5jpmem~ END

<<<<<<<< d5/leavscpt.baf
IF
	!InParty(Myself)
	Global("D5_LEFT","LOCALS",0)
	Global("D5_PNPC","LOCALS",1)
THEN
	RESPONSE #100
	SetGlobal("D5_LEFT","LOCALS",1)
	ReallyForceSpellRES("D5ZLEAV",Myself)
	Continue()
END
IF
	InParty(Myself)
	Global("D5_LEFT","LOCALS",0)
	Global("D5_PNPC","LOCALS",0)
THEN
	RESPONSE #100
	SetGlobal("D5_PNPC","LOCALS",1)
	Continue()
END
IF
	InParty(Myself)
	Global("D5_LEFT","LOCALS",1)
THEN
	RESPONSE #100
	SetGlobal("D5_LEFT","LOCALS",0)
	Continue()
END
>>>>>>>>
COPY ~d5/leavscpt.baf~ ~weidu_external/%MOD_FOLDER%/compile/leavscpt.baf~

LAM JOINABLE_NPC_ARRAYS

ACTION_PHP_EACH JOINABLE_NPC_ARRAY AS cre => dv BEGIN
  COPY_EXISTING ~%cre%~ ~override~
    READ_ASCII 0x248 override_script
    PATCH_IF !(~%override_script%~ STRING_EQUAL_CASE ~SHOUT~) BEGIN
      SPRINT $npc_scripts(~%override_script%~)~1~
    END
  BUT_ONLY
END
ACTION_PHP_EACH npc_scripts AS script => ind BEGIN
  ACTION_IF (FILE_EXISTS_IN_GAME ~%script%.bcs~) BEGIN
    EXTEND_TOP ~%script%.bcs~ ~weidu_external/%MOD_FOLDER%/compile/leavscpt.baf~
  END
END

 

Only odd thing is, in my test, some scripts had the EXTEND_TOP applied more than once. I thought using them in the first "column" (key?) of an array should prevent them from doubling up. It probably won't make a difference... but, cleaner is better. Does UNLESS work with EXTEND_TOP?

Edited by subtledoctor
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...