[(BG2) Bug] Offensive, defensive spin; Boon of Lathander, Melf's Minute Meteors
Sorry to lump these together, but there's a lot of interconnected fixes here.
First, part of this is to hedge against the current attacks per round opcode (1) behavior.
Basically, this splits the ApR bonuses of Boon of Lathander and Offensive Spin into separate spells so they can be specifically blocked by Melf's Minute Meteors.
So first, if a character is wielding MMM, it makes them immune to these two new spells. melfmet.itm isn't currently in the game, so be sure to apply these when they get imported:
First, part of this is to hedge against the current attacks per round opcode (1) behavior.
Basically, this splits the ApR bonuses of Boon of Lathander and Offensive Spin into separate spells so they can be specifically blocked by Melf's Minute Meteors.
So first, if a character is wielding MMM, it makes them immune to these two new spells. melfmet.itm isn't currently in the game, so be sure to apply these when they get imported:
Next, some fixes for offensive spin. The THAC0 bonus was being applied but not showing on the character record. The blade could be hasted while spinning, contrary to the description, and the haste effect killed offhand attacks.
COPY_EXISTING ~melfmet.itm~ ~override~
LPF ~ADD_ITEM_EQEFFECT~ INT_VAR
opcode = 206 // protection from spell
target = 1 // target self
timing = 2 // instant/while equipped
parameter1 = 0xffffff // no string
STR_VAR resource = spcl521d
END
LPF ~ADD_ITEM_EQEFFECT~ INT_VAR
opcode = 206 // protection from spell
target = 1 // target self
timing = 2 // instant/while equipped
parameter1 = 0xffffff // no string
STR_VAR resource = spcl741d
END
BUT_ONLY
// offensive spin fixesBoon of Lathander lacked ability headers, meaning it was acting as if the caster was level 10 if the caster was actually level 1-9. The ApR is changed to a cast spell.
COPY_EXISTING ~spcl521.spl~ ~override~
LAUNCH_PATCH_FUNCTION ~DELETE_SPELL_EFFECT~ INT_VAR
opcode_to_delete = 177 END // delete apply eff
LAUNCH_PATCH_FUNCTION ~DELETE_SPELL_EFFECT~ INT_VAR
opcode_to_delete = 16 END // delete haste
LAUNCH_PATCH_FUNCTION ~ADD_SPELL_EFFECT~ INT_VAR
opcode = 54 // thac0 bonus
target = 1 // target self
parameter1 = 2 // +2
duration = 24 // duration
resist_dispel = 2 // not dispel/bypass MR
insert_point = 0 // add as first effect
END
LAUNCH_PATCH_FUNCTION ~ADD_SPELL_EFFECT~ INT_VAR
opcode = 101 // immunity to effect
target = 1 // target self
parameter2 = 16 // haste
duration = 24 // duration
resist_dispel = 2 // not dispel/bypass MR
insert_point = 0 // add as first effect
END
LAUNCH_PATCH_FUNCTION ~ADD_SPELL_EFFECT~ INT_VAR
opcode = 126 // movement rate bonus
target = 1 // target self
parameter1 = 200 // 200%
parameter2 = 2 // set to %
duration = 24 // duration
resist_dispel = 2 // not dispel/bypass MR
insert_point = 0 // add as first effect
END
LAUNCH_PATCH_FUNCTION ~ADD_SPELL_EFFECT~ INT_VAR
opcode = 146 // cast spell
target = 1 // target self
timing = 1 // instant/permanent
parameter2 = 1 // cast instantly
resist_dispel = 2 // not dispel/bypass MR
insert_point = 0 // add as first effect
STR_VAR resource = spcl521d
END
BUT_ONLY
// extend boon of lathander headers down to level 1And finally, we create the ApR sub-spells for Boon & Offensive spin. These add the ApR and make the caster immune to MMM for the duration.
// change ApR to cast shell spell instead
COPY_EXISTING ~spcl741.spl~ ~override~
LAUNCH_PATCH_FUNCTION ~DELETE_SPELL_EFFECT~ INT_VAR
opcode_to_delete = 1 END // delete ApR
LAUNCH_PATCH_FUNCTION ~ADD_SPELL_EFFECT~ INT_VAR
opcode = 146 // cast spell
target = 1 // target self
timing = 1 // instant/permanent
parameter2 = 1 // cast instantly
insert_point = 0 // add as first effect
STR_VAR resource = spcl741d
END
READ_LONG 0x64 "abil_off"
READ_SHORT 0x68 "abil_num"
WRITE_SHORT 0x68 20
READ_LONG 0x6a "fx_off"
READ_ASCII "%abil_off%" "abil_clone" (0x28) // reads ability #1 for cloning
READ_SHORT ("%abil_off%" + 0x1e) "abil_fx_num1" // from abil #1
READ_ASCII "%fx_off%" "fx_clone" (0x30 * "%abil_fx_num1%") // reads fx of abil #1 for cloning
WRITE_SHORT ("%abil_off%" + 0x10) 10 // turns level 1 header into 10
FOR (index = 0 ; index < abil_num ; index = index + 1) BEGIN // adjust indices on existing abil for new abil/fx
READ_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "abil_fx_idx"
WRITE_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) ("%abil_fx_idx%" + (9 * "%abil_fx_num1%"))
END
FOR (index = 9 ; index > 0 ; index = index - 1) BEGIN // add new abilities
INSERT_BYTES "%abil_off%" 0x28
WRITE_ASCIIE "%abil_off%" "%abil_clone%" // clones lev 10 ability
WRITE_SHORT ("%abil_off%" + 0x10) "%index%" // min lev
WRITE_SHORT ("%abil_off%" + 0x1e) "%abil_fx_num1%"
WRITE_SHORT ("%abil_off%" + 0x20) ("%abil_fx_num1%" * ("%index%" - 1))
SET "fx_off" = ("%fx_off%" + 0x28)
INSERT_BYTES "%fx_off%" (0x30 * "%abil_fx_num1%")
WRITE_ASCIIE "%fx_off%" "%fx_clone%" // clones effects
FOR (index2 = 0 ; index2 < abil_fx_num1 ; index2 = index2 + 1) BEGIN
READ_LONG ("%fx_off%" + 0x0e + (0x30 * "%index2%")) "duration"
PATCH_IF ("%duration%" = 60) BEGIN
WRITE_LONG ("%fx_off%" + 0x0e + (0x30 * "%index2%")) (6 * "%index%")
END
END
END
WRITE_LONG 0x6a "%fx_off%"
BUT_ONLY
// split ApR from offensive spin/boon into their own spells to avoid MMM issues
COPY_EXISTING ~spcl521.spl~ ~override/spcl521d.spl~
~spcl741.spl~ ~override/spcl741d.spl~
WRITE_ASCII 0x10 ~~ #8 // remove casting sound
WRITE_ASCII 0x22 0 // remove casting animation
LAUNCH_PATCH_FUNCTION ~DELETE_SPELL_EFFECT~ INT_VAR
opcode_to_delete = `0x0 END // delete all effects
PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "spcl521" = 0) BEGIN
SET base_dur = 18
END ELSE BEGIN
SET base_dur = 0
END
READ_SHORT 0x68 "abil_num"
FOR (index = 1 ; index <= abil_num ; index = index + 1) BEGIN
LAUNCH_PATCH_FUNCTION ~ADD_SPELL_EFFECT~ INT_VAR
opcode = 1 // attacks per round
target = 1 // target self
parameter1 = 1 // bonus
resist_dispel = 2 // not dispel/bypass MR
duration = ("%base_dur%" + (6 * "%index%")) // duration
header = "%index%"
END
LAUNCH_PATCH_FUNCTION ~ADD_SPELL_EFFECT~ INT_VAR
opcode = 206 // protection from spell
target = 1 // target self
parameter1 = 0xffffffff // no string
resist_dispel = 2 // not dispel/bypass MR
duration = ("%base_dur%" + (6 * "%index%")) // duration
header = "%index%"
STR_VAR resource = spwi325 // MMM
END
END
BUT_ONLY</pre>
And finally, closing a defensive spin exploit. Using other spells/items that changed movement rate, the blade could go walkabout contrary to the description.
// defensive spin fixes
COPY_EXISTING ~spcl522.spl~ ~override~
READ_LONG 0x64 "abil_off"
READ_SHORT 0x68 "abil_num"
READ_LONG 0x6a "fx_off"
SET "fx_delta" = 0
FOR ("index" = 0; "%index%" < "%abil_num%"; "index" = ("%index%" + 1)) BEGIN // fix existing effects
READ_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) "abil_fx_num"
READ_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "abil_fx_idx"
SET "abil_fx_idx" = ("%abil_fx_idx%" + "%fx_delta%")
WRITE_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) ("%abil_fx_idx%")
FOR (loops = abil_fx_num ; loops > 0 ; loops = loops - 1) BEGIN
READ_SHORT ("%fx_off%" + (0x30 * ("%abil_fx_idx%" + ("%loops%" - 1)))) "opcode"
PATCH_IF ("%opcode%" = 206) BEGIN // insert right before spell immunities
WRITE_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) ("%abil_fx_num%" + 1)
SET "fx_delta" = ("%fx_delta%" + 1)
INSERT_BYTES ("%fx_off%" + (0x30 * ("%abil_fx_idx%" + ("%loops%" - 1)))) 0x30 // insert new effect
WRITE_SHORT ("%fx_off%" + (0x30 * ("%abil_fx_idx%" + ("%loops%" - 1)))) 101 // immunity to effect
WRITE_BYTE ("%fx_off%" + 0x02 + (0x30 * ("%abil_fx_idx%" + ("%loops%" - 1)))) 1 // target self
WRITE_LONG ("%fx_off%" + 0x08 + (0x30 * ("%abil_fx_idx%" + ("%loops%" - 1)))) 126 // movement rate bonue
WRITE_BYTE ("%fx_off%" + 0x0d + (0x30 * ("%abil_fx_idx%" + ("%loops%" - 1)))) 2 // not dispel/not bypass
WRITE_LONG ("%fx_off%" + 0x0e + (0x30 * ("%abil_fx_idx%" + ("%loops%" - 1)))) 24 // duration
WRITE_BYTE ("%fx_off%" + 0x12 + (0x30 * ("%abil_fx_idx%" + ("%loops%" - 1)))) 100 // prob 1
SET "loops" = 0
END
END
END
BUT_ONLY_IF_IT_CHANGES
Post edited by Tanthalas on
3
Comments
I'll... get right on that....
@CamDawg, @Nathan
You can still use the Ring of Free Action to bypass the Defensive Spin resctriction to movement. Is this intended?
Not sure how to test the MMM issues since the scroll isn't in the game.
Boon of Lathander now has the correct duration for levels 1-9.
Blades can no longer be hasted while Offensive Spin is active. The THAC0 bonus of Offensive Spin is also now reflected on the Records page.
Note: Is Offensive Spin supposed to increase the movement rate of the Blade? Its not in the description of the ability.
@Wisp, @Avenger_teambg, anything come to mind that would react badly to defensive spin using 176 instead of 126?
Might be worth adding to the description, sure.
Off-hand I can't think of any problems. It is already used by Otiluke's for essentially the same purpose.
Defensive Spin not being blocked by Free Action (since DS is the Blade choosing to hold his ground) is a change we have been meaning to add to Fixpack 10, but it hasn't been gotten around to yet.
1. You should probably warn people not to cast Melf's Minute Meteors while under an effect that is incompatible with MMM - or remove the extra attack from Offensive Spin as MMM takes effect. As it is, it just leaves people wondering why their spell failed.
2. MMM followed by OS works as intended.
5. You can activate Offensive Spin while under the effect of Haste. @Camdawg
6. The opposite is impossible.
7. Using Defensive Spin is impossible while under the effect of Free Action. I'd like some sort of warning here, as well. (if not in advance, then at least a notification why the ability didn't take effect).
8. Boon of Lathander now takes note of the caster's level. At level 1, its duration is 6 seconds.
spcl521d and spcl741d can be outright deleted and we can move ApR back into the spells proper and remove a lot of the spell immunities: As for haste -> offensive spin, here we go: Also, this: ...is utterly useless, as the free action unstun opcode also wipes 176s. I suppose we could add immunity to the free action unstun opcode but I think we'd be asking for trouble.
edit: unstun, not free action
Could someone else try going into a defensive spin and then equipping the ring of free action? Ideally you should still be stuck in place.
Oh, and a ping for @coriander or @Nathan for the new stuff above. Ignore the last bit about spcl522 for now.
Another thing: from what I understand, you decided to prevent the use of OS when the character is under the effects of haste/improved haste and inversely. But you could also have decided that the ability/spell would remove the active one. So, why did you choose the first solution over the second one?
* So, this is a BG2 Fixpack fix that's very roundabout because we couldn't fix the underlying engine behavior. The good news is that BGEE can, and did, so all of the roundabout hacks I'm about to describe do not apply to BGEE, only to BG2. (In other words, I'm warning you now that a tl;dr is about to follow.) The blade kit description explicitly states that haste and improved haste don't work with offensive spin, and that is enforced in BGEE and BG2 Fixpack.
So... (deep breath):
The haste spell works by using the haste opcode in the engine, which is a bit wonky. It can up to double your main hand attacks while completely eliminating offhand attacks. The haste opcode also prevents itself from stacking so if you already under the effect of haste you can not get another haste added.
The problems here really come about because the original devs used the haste opcode for spells/items which really should use two other opcodes instead--movement speed and attacks per round--and one such spell is offensive spin. They relied on the haste opcode not being allowed to stack with itself to prevent the offensive spin + haste combo, but it was poorly done. If you cast offensive spin while hasted, you didn't get double-hasted, yes, but all of the other bonuses from offensive spin (i.e. thac0 and damage bonuses) would still apply. Offensive spin would also eliminate offhand attacks and not necessarily add attacks, which is fairly suboptimal all around.
So Fixpack changes offensive spin to use a combination of movement speed increase + attack per round bonuses and prevent haste through spell protections, and now everything works as it should--haste and offensive spin don't stack, at all, and offensive spin now provides a consistent extra attack, keeps the offhand attack, and the same movement speed bonus still applies. A win-win all around, let's go home.
...except we now run into another problem with the engine, namely how the engine handles the stacking of attacks per round increases. Melf's Minute Meteors sets attacks per round to five, but when stacked with other attack per round increases (such as those from offensive spin or Boon of Lathander), it would actually wrap around and result in 0.5 attacks per round.
BGEE simply fixes the behavior of how attacks per round stacking, as does TobEx for Windows-based BG2 games. (TobEx came about long after we had to deal with this for Fixpack, and Fixpack still has to provide a fix for OSX and Linux games.) For BG2FP, we had to use the elaborate workaround from above. Attack per round bonuses from Boon and spin were farmed out to separate spells, and those parts--and those parts only--were blocked by MMM so as to keep the 5 attacks/round. So once again, everything works as it should and only modders or really interested players get to see how elaborate we had to hack to make it work.
Like CamDawg explained, the engine doesn't really have a good way for Haste or Offensive Spin to erase the previous effect when activated. So yes, if you have Haste active, the Blade will not be able to activate Offensive Spin.
It doesn't need to be Haste, just Offensive Spin that temporarily cancels out/suppresses all other haste effects while active/if activated. I thought the actual wording meant that they were not supposed to stack, much like Barkskin's AC bonus doesn't stack with Full Plate Armor +3, not that they couldn't both be active at the same time (ie, remove Full Plate Armor +3, and you still have Barkskin's effect). Because following it word for word would make things unnecessarily tedious (eg, if someone screwed up buffing and accidentally hasted the Blade, then he would actually have to reload/wait for a few minutes for the buff to run its course).
I understand that this is due to IE limitations, but hopefully a better solution may yet be found in the future if things in BG:EE currently are as they are.
Well, the current situation is honestly better than the previous situation where Haste and Offensive Spin were activaed at the same time but not actually giving you the bonuses that they should.