Jump to content

Unscriptable spells


devSin

Recommended Posts

I finally got a moment to check why Odren's scroll fails if the spell level is set to 1, and made some boring observations. We append triggers to the IDS files and include scriptable spells, so I'm posting this here for none to see.

 

It turns out that SpellCastInnate() will *only* return true if the spell level is correct (i.e., the spell number / 100). If the spell is any other level, the SpellCastInnate() trigger will return false. Additionally, SpellCastInnate() is the only trigger that will return true when an innate spell is cast, so it has to be used.

 

APPEND ~TRIGGER.IDS~ ~0x00A1 SpellCastOnMeRES(S:Spell*,O:Caster*)~
APPEND ~TRIGGER.IDS~ ~0x0091 SpellCastRES(S:Spell*,O:Object*)~
APPEND ~TRIGGER.IDS~ ~0x00A6 SpellCastPriestRES(S:Spell*,O:Object*)~
APPEND ~TRIGGER.IDS~ ~0x00A7 SpellCastInnateRES(S:Spell*,O:Object*)~
APPEND ~TRIGGER.IDS~ ~0x4031 HaveSpellRES(S:Spell*)~

I am unable to verify that most of these actually work. HaveSpellRES() does work, as from the following script:

IF
 HaveSpellRES("CAMDAWG")
THEN
 RESPONSE #100
   DisplayStringHead(Myself,67787) // I'd say it's time to die, demon.
   Continue()
END

IF
 HaveSpellRES("SPWI406") // Minor Globe of Invulneribility
 Global("SexBomb","LOCALS",0)
THEN
 RESPONSE #100
   CreateVisualEffectObject("SPFIREPI",Myself)
   SetGlobal("SexBomb","LOCALS",1)
END

When loaded, Imoen will never run the string, and will always play the effect (any spell she has can be substituted for 2406).

 

SpellCastInnateRES() doesn't seem to work at all. It looks like the resource reference is ignored entirely, and the game is just treating it at SpellCastInnate(O:O,I:0), from the following code:

IF
 SpellCastInnateRES("DING0",[GOODCUTOFF])
THEN
 RESPONSE #100
   DisplayStringHead(Player1,21792) // What if I'm not interested in having my eyes removed?
   Continue()
END

Any innate spell of any level can be cast, and the string will always run over Player1 (regardless of the reference specified). The behavior is the same whether a generic object ([30], [0], etc.), or an explicit object (e.g., "IMOEN", Player1) is used (although, that aspect does work as expected). The same continues to be true if SpellCastPriestRES() or SpellCastRES() are used in place of SpellCastInnateRES() (again, the matching to spell type and the caster still works, it just picks up any spell).

 

I didn't check HaveSpellPartyRES() or SpellCastOnMeRES(). I don't care.

 

Most of this doesn't really affect the fixpack, but if somebody can verify, I'd chuck the worthless appends. Additionally, I'd remove the following files from the scriptable spells patch, since they are of the correct level (by default) and are checked with SpellCastInnate() triggers in the default scripts (actually, only ever for Saladrex's script): SPCL412, SPCL414, SPCL910, SPCL911, and SPCL912.

 

I did a very brief check of the actions; they all work except for the two RangeRES() actions (I don't even know if the normal ones work, though).

 

I didn't check RemoveSpellRES(). I don't care.

IF
 HotKey(B)
 Global("HotKeyB","LOCALS",0)
THEN
 RESPONSE #100
   ApplySpellRES("SPCL907",Myself) // Hardiness
   SetGlobal("HotKeyB","LOCALS",1)
END

IF
 HotKey(D)
 Global("HotKeyD","LOCALS",0)
THEN
 RESPONSE #100
   ForceSpellPointRangeRES("SPWI205",[3622.2851]) // Horror
   SetGlobal("HotKeyD","LOCALS",1)
END

IF
 HotKey(E)
 Global("HotKeyE","LOCALS",0)
THEN
 RESPONSE #100
   ForceSpellPointRES("SPPR208",[3720.2923]) // Hold Person
   SetGlobal("HotKeyE","LOCALS",1)
END

IF
 HotKey(F)
 Global("HotKeyF","LOCALS",0)
THEN
 RESPONSE #100
   ForceSpellRangeRES("SPWI206",Player1) // Invisibility
   SetGlobal("HotKeyF","LOCALS",1)
END

IF
 HotKey(S)
 Global("HotKeyS","LOCALS",0)
THEN
 RESPONSE #100
   ForceSpellRES("SPWI201",Myself) // Blur
   SetGlobal("HotKeyS","LOCALS",1)
END

IF
 HotKey(B)
 Global("HotKeyB","LOCALS",1)
THEN
 RESPONSE #100
   ReallyForceSpellDeadRES("SPIN101",Player1) // Cure Light Wounds
   SetGlobal("HotKeyB","LOCALS",2)
END

IF
 HotKey(D)
 Global("HotKeyD","LOCALS",1)
THEN
 RESPONSE #100
   ReallyForceSpellPointRES("SPPR203",[3622.2851]) // Chant
   SetGlobal("HotKeyD","LOCALS",2)
END

IF
 HotKey(E)
 Global("HotKeyE","LOCALS",1)
THEN
 RESPONSE #100
   ReallyForceSpellRES("SPIN795",Player1) // No such index
   SetGlobal("HotKeyE","LOCALS",0)
END

IF
 HotKey(F)
 Global("HotKeyF","LOCALS",1)
THEN
 RESPONSE #100
   SpellNoDecRES("SPWI408",Myself) // Stoneskin
   SetGlobal("HotKeyF","LOCALS",0)
END

IF
 HotKey(S)
 Global("HotKeyS","LOCALS",1)
THEN
 RESPONSE #100
   SpellPointNoDecRES("SPWI305",[3670.2882]) // Haste
   SetGlobal("HotKeyS","LOCALS",0)
END

IF
 HotKey(B)
 Global("HotKeyB","LOCALS",2)
THEN
 RESPONSE #100
   SpellPointRES("SPWI309",[3483.2871]) // Monster Summoning I
   SetGlobal("HotKeyB","LOCALS",0)
END

IF
 HotKey(D)
 Global("HotKeyD","LOCALS",2)
THEN
 RESPONSE #100
   SpellRES("SPWI102",Myself) // Armor
   SetGlobal("HotKeyD","LOCALS",0)
END

Link to comment

SpellCastInnate() will only return true if the innate spell has the right level (e.g., SPCL412 needs to be level 4).

 

SpellCast*RES() triggers simply do not work. The engine is treating this like SpellCast*(O:O,0), ignoring the resref completely.

Link to comment

IF
 SpellCastInnate(Myself,4414)
THEN
 RESPONSE #100
   DisplayStringHead(Myself,22724) // say "Gong Mallet"
END  

 

devSin is correct. The string was only displayed when the spell level was set to 4.

 

Checking the biffs, this is what I found.

 

Set Snare - SPCL412 - is set to lvl 0 by default (default meaning straight from the biff)

Set Special Snare - SPCL414 - is set to lvl 0 by default

Set Spike Trap - SPCL910 - is set to lvl 9 by default
SPCL910B - is set to lvl 0 by default

Set Exploding Trap - SPCL911 - is set to lvl 9 by default
SPCL911B - is set to lvl 0 by default

Set Time Trap - SPCL912 - is set to lvl 9 by default
SPCL912B - set to lvl 0 by default

 

Setting the spell level to 0

 

String is not displayed

No crash when cast from a script, the spell is simply not cast (it was in series with a couple of other spells to be cast, the Set Snares with its level set to 1 was cast, Set Special Snares with its level set to 0 was not cast.

 

Setting the spell level to 1

 

String is not displayed

No crash when cast from a script, both Set Snares and Set Special Snares were cast as expected.

 

Setting the spell to level 4

 

String was displayed

Crash to desktop when cast from a script.

 

Conclusion

We are at a bit of an impasse. Saldrex needs SpellCastInnate for his/its scripts to work. If we set the level to 4, then it causes AI Scripts that cast the spell via HaveSpell or HaveSpellRES() to CtD.

 

For Further Research

 

Anyone have clean install of BG2:SoA + ToB? I'm curious to see whether or not Set Snares and Set Special Snares are set to the proper level (4) and are in the override courtesy of the ToB install, otherwise, Saldrex's script never worked properly from the start.

 

Thanks,

Link to comment

They exist in the override and have a level of 4.

 

Does casting the spell crash, or is it just when HaveSpell() goes crawling through the memorized spells?

 

After-the-fact spells (like the actual trap part of set traps), and AP_ special abilities are usually set to level 0 (but these are never spells the player would cast in-game). This would correspond to the SPCL*Bs you posted, and also to SPCL411 and SPCL415.

Link to comment
They exist in the override and have a level of 4.

 

Does casting the spell crash, or is it just when HaveSpell() goes crawling through the memorized spells?

 

After-the-fact spells (like the actual trap part of set traps), and AP_ special abilities are usually set to level 0 (but these are never spells the player would cast in-game). This would correspond to the SPCL*Bs you posted, and also to SPCL411 and SPCL415.

 

It is hard to tell because no information is dumped to the Text Box before the crash. I think when HaveSpell() or HaveSpellRES() go looking for a spell and find an innate reference with the incorrect level, the game first freezes as it tries to digest this inconsistency, and then the games CtDs when it can't resolve the issue internally.

Link to comment
It is hard to tell because no information is dumped to the Text Box before the crash. I think when HaveSpell() or HaveSpellRES() go looking for a spell and find an innate reference with the incorrect level, the game first freezes as it tries to digest this inconsistency, and then the games CtDs when it can't resolve the issue internally.
If you ever look at a stack trace, you'll notice it running through trigger evaluation in the AI thread. Since the hard crash is so inconsistent here, my guess was that the crash occurs when the engine goes to walk through the memorized spells in the creature definition (I guess it would end up walking out in the weeds if it tried to read innate spell levels > 1 from the memorization table). It may be interesting to someday have somebody try patching in a table that has all 9 levels (0-8) for innate spells, but this might cause general crashes with normal spellcasting (if it works at all).

 

EDIT: I though I had one laying around, but I don't. I've got a crash dump from a HaveSpell() crash, though. You can roughly see how the game is handling running AI scripts, and the steps it's going through to process the HaveSpell() call. Examining the registers around the time of the crash would likely yield more insight, but I'm not that concerned about it.

0   <Unknown disk fragment>       	 0x02a2d7c0 _eq__7CResRefCFPc + 16
1   <Unknown disk fragment>       	 0x029fcda0 HaveSpell__11CGameSpriteFRC7CString + 832
2   <Unknown disk fragment>       	 0x029fbf80 EvaluateStatusTrigger__11CGameSpriteFRC10CAITrigger + 432
3   <Unknown disk fragment>       	 0x025fe458 TriggerHolds__12CAIConditionFP10CAITriggerR38CTypedPtrList<8CPtrList,P10CAITrigger>P11CGameAIBase + 72
4   <Unknown disk fragment>       	 0x025fe5e0 Hold__12CAIConditionFR38CTypedPtrList<8CPtrList,P10CAITrigger>P11CGameAIBase + 128
5   <Unknown disk fragment>       	 0x0260eab4 Find__9CAIScriptFR38CTypedPtrList<8CPtrList,P10CAITrigger>P11CGameAIBase + 100
6   <Unknown disk fragment>       	 0x0265a390 ProcessPendingTriggers__11CGameAIBaseFi + 1040
7   <Unknown disk fragment>       	 0x02a09570 ProcessPendingTriggers__11CGameSpriteFi + 416
8   <Unknown disk fragment>       	 0x02a214e0 MainActionPicking__11CGameSpriteFv + 448
9   <Unknown disk fragment>       	 0x02a05ad0 ProcessAI__11CGameSpriteFv + 6464
10  <Unknown disk fragment>       	 0x029b957c AIUpdate__11CGameSpriteFv + 3020
11  <Unknown disk fragment>       	 0x0269144c AIUpdate__9CGameAreaFv + 5340
12  <Unknown disk fragment>       	 0x0291d340 AsynchronousUpdate__12CScreenWorldFi + 3440

Link to comment
If you ever look at a stack trace, you'll notice it running through trigger evaluation in the AI thread. Since the hard crash is so inconsistent here, my guess was that the crash occurs when the engine goes to walk through the memorized spells in the creature definition (I guess it would end up walking out in the weeds if it tried to read innate spell levels > 1 from the memorization table). It may be interesting to someday have somebody try patching in a table that has all 9 levels (0-8) for innate spells, but this might cause general crashes with normal spellcasting (if it works at all).

Now that sounds like a fun experiment.

Link to comment

It occurs to me that there may be a simpler solution--shell spells, like we do for Larloch's Minor Drain and Vampiric Touch. For example, we copy spcl910 to spcl910a, and alter spcl910 to only have one effect, Cast Spell: spcl910a, and make it level 1. I'm thinking that this would allow us to script spcl910 (since it would be the spell memorized and level 1) while allowing us to detect its casting via spcl910a. If someone wants to test the theory, here's code to do just that:

 

COPY_EXISTING ~spcl910.spl~ ~override/spcl910a.spl~ // spike trap
 SAY NAME1 #-1 // suppresses batle text
 SAY NAME2 #-1 // suppresses batle text
 WRITE_LONG 0x34 1 // set to level 1
 READ_LONG  0x64 "abil_off"
 READ_SHORT 0x68 "abil_num"
 FOR (index = 0; index < abil_num; index = index + 1) BEGIN
   WRITE_SHORT ("%abil_off%" + 0x26 + (0x28 * "%index%")) 1 // removes projectile
 END

COPY_EXISTING ~spcl910.spl~ ~override~ // spike trap shell
 WRITE_LONG 0x34 9 // set to level 9
 READ_LONG  0x64 "abil_off"
 READ_SHORT 0x68 "abil_num"
 READ_LONG  0x6a "fx_off"
 READ_SHORT 0x70 "fx_num"
 FOR (index = 0; index < abil_num; index = index + 1) BEGIN
   READ_SHORT  ("%abil_off%" + 0x10 + (0x28 * "%index%")) "level"
   READ_SHORT  ("%abil_off%" + 0x1e + (0x28 * "%index%")) "abil_fx_num"
   WRITE_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) 1
   READ_SHORT  ("%abil_off%" + 0x20 + (0x28 * "%index%")) "abil_fx_idx"
   WRITE_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "%index%"
   DELETE_BYTES  ("%fx_off%" +        (0x30 * "%abil_fx_idx%")) (0x30 * "%abil_fx_num%")  // deletes all effects
   INSERT_BYTES            ("%fx_off%" +        (0x30 * "%abil_fx_idx%")) 0x30            // cast actual spell
     WRITE_SHORT           ("%fx_off%" +        (0x30 * "%abil_fx_idx%")) 146             // cast spell
     WRITE_BYTE            ("%fx_off%" + 0x02 + (0x30 * "%abil_fx_idx%")) 2               // target: preset target
     WRITE_BYTE            ("%fx_off%" + 0x03 + (0x30 * "%abil_fx_idx%")) 9               // power
     WRITE_LONG            ("%fx_off%" + 0x04 + (0x30 * "%abil_fx_idx%")) "%level%"       // cast at level
     WRITE_LONG            ("%fx_off%" + 0x08 + (0x30 * "%abil_fx_idx%")) 1               // cast instantly
     WRITE_BYTE            ("%fx_off%" + 0x0c + (0x30 * "%abil_fx_idx%")) 1               // instant/permanent
     WRITE_BYTE            ("%fx_off%" + 0x0d + (0x30 * "%abil_fx_idx%")) 1               // dispel/not bypass
     WRITE_BYTE            ("%fx_off%" + 0x12 + (0x30 * "%abil_fx_idx%")) 100             // probability
     WRITE_EVALUATED_ASCII ("%fx_off%" + 0x14 + (0x30 * "%abil_fx_idx%")) ~%SOURCE_RES%a~ // spell
 END
 PATCH_IF ("%fx_num%" > 0) BEGIN // eliminates global fx
   DELETE_BYTES "%fx_off%" (0x30 * "%fx_num%")
   WRITE_SHORT 0x70 0
 END
 BUT_ONLY_IF_IT_CHANGES

Link to comment
I sooo love our games of 20 questions.

 

Is it an animal, vegetable or mineral?

I really don't know how to respond to that, sorry.

 

Since SpellCastRES() triggers don't work, the SPCL90n spell needs to level 9. Since the kit applies SPCL90n, HaveSpell() will crash if not level 1. I don't see an obvious solution outside of changing the kit bonus and telling everybody to now check HaveSpellRES("SPCL90nA").

 

My suggestion is still to not patch SPCL411, SPCL412, SPCL910, SPCL911, and SPCL912 in the fixpack, since these spells are checked with SpellCastInnate() in a default script.

Link to comment

Archived

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

×
×
  • Create New...