LAST EDITED ON May-17-10 AT 05:53 AM (Pacific)I may have discovered the source of the spell bug that causes monsters to resist the 7th level “attack all” spells. I’m working on seeing if there is an in-game fix or a way to patch the problem.
Here’s what I’ve got:
When a caster casts a spell on a target, the game rolls a saving throw for the target:
If(1d100-1 <= target_resistance – resistance_adjustment)
{
target resists spell
}
• target_resistance is the resistance % shown in Mad God’s editor
• resistance_adjustment = (caster_level – target_level + caster_bonus)
o caster_level is the caster’s level (or hit dice for monsters)
o target_level is the target’s level (or hit dice for monsters)
o caster_bonus = 3*(spell_level + power_level – 2)
power_level is the power level selected (1-6)
EDIT: I just discovered Mad God has a new version of his editor up that allows spell editing. What he calls "spell efficiency" is the same as what I called "resistance_adjustment"
The “spell bug” occurs when the game attempts to determine target_level. it tries to do an array lookup, which in c-code is essentially struct battle_participant** BATTLE_PARTICIPANTS{target_group}{target}.
• BATTLE_PARTICIPANTS is an array of pointers located at 1110:43a8
o index 0 is the party,
o index 1 is monster group 1,
o and so on, so that
o BATTLE_PARTICIPANTS{0}{x} corresponds to individual x in the party (x=0 is upper left, x=1 is upper right, etc.)
o BATTLE_PARTICIPANTS{1}{x} corresponds to individual x in monster group 1,
o etc.
• battle_participant is a 0x2C-byte wide struct,
o offset 0x00 of which is the participant’s level or hit dice,
o offset 0x20 of which is the index in BATTLE_PARTICIPANTS of the group that participant is targeting that round, and
o offset 0x1F of which is the index in BATTLE_PARTICIPANTS{target_group} of the individual in target_group that participant is targeting that round
So if the party member in the upper right cast energy blast on monster 3 in monster group 1,
*( ((char*)BATTLE_PARTICIPANTS{0}{1})+0x1F)==1 //corresponding to monster group 1, and
*( ((char*)BATTLE_PARTICIPANTS{0}{1})+0x20)==3 //corresponding to individual monster 3
So for a party member casting a spell, the game attempts to determine the resistance_adjustment by doing the following:
NOTE: battle_participant* caster_ptr = &(battle_participants{0}{x}), where x is the index of the caster (0=upper left, 1=upper right, etc.)
function cast_spell(battle_participiant* caster_ptr, int spell_code, int power_level, …)
{
int target_group=(int) ( caster_ptr->target_group ); //executed as *( ((char*)caster_ptr)+0x1F);
int target=(int) ( caster_ptr->target ); //executed as *( ((char*)caster_ptr)+0x20)
register int caster_bonus=calculate_caster_bonus(int spell_code, int power_level);
resistance_adjustment = *caster_ptr – *(BATTLE_PARTICIPANTS{target_group}{target}) + caster_bonus;
//SHOULD == caster_level – target_level + caster_bonus. But it doesn’t always! See below for why this is.
…
}
The problem is that for character x casting an “attack all” spell, the game sets BATTLE_PARTICIPANTS{0}{x}.target_group==(char) (-1) == (int) 255
So, when the game does BATTLE_PARTICIPANTS{target_group}, this takes it to a random point in memory past the end of BATTLE_PARTICIPANTS (it happens to be the 5th-8th hex digits of the upper right character’s experience points). It then casts the result as a pointer and tries to index into it 0x2C*target bytes, which is another random point in memory. If the result is a large positive integer, target_level will be large and positive, and so resistance_adjustment will be large and negative. This makes the monster’s resistance effectively extremely high! While target_group is consistently 0xFF, target is inconsistent. It is usually 0x00, but it is occasionally 0x02.
You can test this easily. Just have the upper-right character change classes so his/her experience is zero. This should fairly consistently work, but not always, since target is sometimes 0x02. (*0x0000 == 0, but *(0x2c*2) is not necessarily == 0).
In fact, you can often avoid the bug if the 5th and 6th hex digits of your experience points are even and less than 0x56 or so (the numbers in *0x0000-*0x0056 seem to always be 0xff or less. So, divide the upper right character’s experience points by 65536 (decimal) and round down. If the result is even and less than 86 (decimal), your spells should usually work.
---
Here are some notes for others who want to verify. All memory addresses are what I got using dosbox to debug, and are only valid in the middle of a battle after you’ve selected “start fighting” and the animations have begun (I’m not sure which overlay this is in).
0138: b3e1 calls 0138: 9405 (the function described above). In this function,
• caster_ptr is at bp+4,
• spell_code is at bp+6,
• power_level is at bp+8,
• target is at bp-8,
• target_group is at bp-6,
• calculate_caster_bonus starts at 0138: 7816 (the return is stored in ax and pushed on the stack),
• resistance_adjustment is at bp-0x0a
Eventually 9405 calls 0138: 861e (call at 0138: 96ad). In 861e, resistance_adjustment is at bp+0x0e.
Eventually 861e calls 0138: 8149 (call at 0138: 864d). In 8149, resistance_adjustment is at bp+0x10.
• NOTE: 8149 is called once for each group targeted by a spell; this is where they should have calculated resistance_adjustment. I haven’t looked to see if there is any code in there that is somehow failing to overwrite the resistance_adjustment incorrectly calculated above
Eventually 8149 calls 0138: 7ddc (call at 0138: 81c2). In 7ddc, resistance_adjustment is at bp+0x10.
In 7ddc, at 0138: 7e66, the game does
If(rnd(0 to 99) <= target_resistance – resistance_adjustment)
{
//target resists spell
}
Additional note about the function calculate_caster_bonus at 0138: 7816:
This function does:
{
register int spell_level=*( ((char*)0xDF) + 6*spell_code)
return 3*(spell_level + spell_power – 2);
}
Where does the 0xFF come from?
When you select “spell” when choosing a command, the game sets 0xFF as a default value for target_group at 0138: 7b86. This is overwritten for spells that have a group or individual target at 0138: 6f9a. However, this is never executed for spells that don’t have a group or individual target as a result of a jump to 0138: 6f9d at 0138: 6f64. Note that 0xFF is the target_group for more than just the “attack all” spells. Fire/ice/air shield, blink, create life/illusion/conjuration, and haste all have target_group==0xFF, and usually target==0x00. Enchanted blade, armorplate, and magic screen have target_group==0xFF, and target==0xFF.