Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Development

Development::Development Forum for development topics and for those interested in EQEMu development. (Not a support forum)

Reply
 
Thread Tools Display Modes
  #1  
Old 06-05-2025, 03:59 AM
Torven
Sarnak
 
Join Date: Aug 2014
Posts: 76
Default

Hardcoded Immunities and Spell Level Limits

Various spell immunities are in the decompiled resist function. This confirms a lot of what was common knowledge but it also provides hard evidence for less understood immunities, such as charm on merchants, so this was a nice thing to find.

Charm Limits

Charm level limits are in the clients. They were hardcoded back then. Not only that but Sony changed the limits on some of these spells over time.

The pre-launch February 1999 client did not have any immunities or limits in it. They were added later.

The next client we have and examined is the March 21 2000 client. (very late pre-K classic era) The limits are in this client, and they are the following:

Charm: 18 (-7)
Befriend Animal: 21 (-3)
Beguile Plants: 24 (-1)
Dominate Undead: 32
Charm Animals: 33
Tunare's Request: 35
Beguile: 37
Solon's Song of the Sirens: 37
Beguild Animals: 43
Cajoling Whispers: 46
Beguile Undead: 46
Allure: 51
Allure of the Wild: 51 (+2)
Cajole Undead: 51
Call of Karana: 51 (-2)
Thrall of Bones: 51 (-2)
Boltran's Agacerie: 51 (-2)
Enslave Death: 51 (-4)
Dictate: 51 (-7)
(The number in parentheses is the difference from Al'Kabor/TAKP's spell data)

This client calculates a level limit for lower level charms using a simple formula. So it appears as though Sony first tried to level limit charm using a formula but later decided to go with individual spell hardcodes for the higher level ones. Said formula is level_spell_was_obtained * 15 / 10, capped at 24. Enslave Death and Dictate are set to 52, then right after that there is a "if higher than 51 make equal to 51" check, which appears to be vestigial. It was gone in the next client we looked at.

Thott mentions that, for a period some time prior to March 2000, charm level caps were spell_level + 10 here: https://web.archive.org/web/20040301...bard_charm.php

It's noteworthy that Boltran's originally had a cap of level 51. Necro charms also had lower limits. Druid 44 charm had a higher limit and it was nerfed at some point after Kunark. Dictate too was much lower than it ended up being later. This client also included a "Your target is too high of a level for your charm spell" message. (patched in October 28 says notes) You might ask why Boltran's and Call of Karana were useful then because they had the same level limits as the Allures and Boltran's had a shorter duration. The answer is because the Kunark charms had -10 modifiers and shorter cast times.

The March 2000 client also had charm immunity for city guard NPCs. The spell logic checked for specific race IDs in a switch case. These IDs are: 44, 67, 71, 77, 78, 81, 88, 90, 92, 93, 94, 106, 112, 139. If charm was cast on any NPC of any of these races, the spell automatically resisted. To be clear: charm immunity was added to guards at some point during the classic era, and this March 2000 client is only one of two clients in that era that we were able to obtain and decompile, the other one being before the game even launched, so it was likely added much earlier than March 2000. (perhaps December 99 since a patch note mentions guards gaining 'stats')

The next oldest client I examined was from 4 days post Kunark launch. (April 28 2000) These clients are 5 weeks apart. The differences between these clients include:
  • Merchants were made immune to charm. Class was checked for this. (class 15 at the time was merchants)
  • Body type 6 (emus call it type 'Extraplanar') was made immune to charm. Not many NPCs have this type. These NPCs include: Efreetis, Fearplane minis, PoSky bosses, planar projections, Yael and a few other things.
  • A check was put in that prevents level 38+ bards from charming NPCs that are higher in level than the bard is.
  • Enslave Death and Dictate were raised to 52 because the "if < 51, set equal to 51" check was removed.
The following spells were either added to the client or had their limits changed:
Charm: 25
Befriend Animal: 25 (+1)
Beguile Plants: 25
Solon's Bewitching Bravura: 51
Enslave Death: 52 (-3)
Dictate: 52 (-6)
Alluring Whispers: 49
Persuade: 35

The November 29 2000 client, which is the final client before Velious launch a week later, had the same limits as the client from 4 days after Kunark launch.

When did Boltran's limit increase from 51 to 53? A post in this January 30 2001 thread says it was 53: https://web.archive.org/web/20010212.../052705-4.html

The above charm level limits applied to Kunark and later era zones. In pre-K classic zones (Sky is not included in this), charm was limited to level 51 and under NPCs, meaning Dictate would fail on level 52 NPCs in these zones. Sony had stricter rules for these older zones. That may sound strange but a May 2002 patch note says this: "The Hole now uses Kunark-level Mez/Charm/Taunt rules" and this: "High level NPCs can now be taunted in Cazic Thule". It appears that at Velious launch they increased Boltran's to 53 and Dictate to 58 but limited all charms in classic zones to level 51, and Hole was limited like classic zones until May 2002. (incidentally the old taunt rules were probably that NPCs above level 50 were immune)

The decompiles don't show this pre-K charm limit. I found it in old threads. This Caster's Realm thread is quite explicit about it:

Quote:
Originally Posted by A Player on April 27 2001
OLD WORLD charms (ALL OLD WORLD CHARMS) cap at level 51. This includes the plane of hate, the plane of fear, the hole, etc. Also, all mesmerize spells cap somewhere around 51 - not sure if it's 51 exactly...Maybe more like 53.
New World charms have increased level caps - Boltran's caps at 53 in the new world, while Dictate caps at about 58. The new world includes all of velious and kunark as well as the plane of sky. Don't believe me? Try Dictating your level 55 friend in west commons, and then try it in dreadlands.
The thread can be found here: https://web.archive.org/web/20010430...c&f=9&t=004361

Fear Immunity

The March 2000 client has the fear immunity for level 53+ NPCs in it, so it existed in pre-K classic era.

I found what appear to be checks for the fear immunity discipline in the later clients, and also some other kind of immunity check that I could not decipher. It might be some kind of NPC immunity flag.

Mez Immunity

Giant and dragon mez immunity is found in the Kunark clients. It's missing from the March 21 2000 client (at least the red message for it) but is found in the April 28 client. I think prior to Kunark, the spells landed without giving a resist message but the spells just didn't function. Why I think that is because I found this comment:

Quote:
Originally Posted by Enchanter forum comment March 31 2000
Giants are completely immune to mez series and stuns.....
it will say "they are mezzed" but if you watch them, they are NOT.
https://dbsanfte.github.io/eq-archiv...html/2557.html

Specifically what blocks mez is: if the target is stun immune, or has a body type of 4 (giant) or 26 (dragon) or 29 (dragon 2).

The IsStunImmune() function found in the clients needs some explaining. This function is found in all of the clients except the February 1999 one. This function is very small and does the following: checks if the target is a NPC, and checks if the level is above level 52. If both are true then it returns true, else false. Now obviously mobs above level 52 could be stunned and mezed. Why I think the clients have this is because this function is using the pre-K classic era rules as I explained in the charm section above. In pre-K classic, nothing could be mezed or stunned above level 52. This would include the Hole until Luclin (May 2002) when they switched the zone's rules as the patch note stated.

"Enchanters are the only class that can mezz these things, so , thats what the spell is for. You dont have to use it in ANY old world zone ( including hate / fear ) because old world mez rules apply ( anything 53+ cant be mezzed or charmed at all, like in the hole )." (5/18/01)
https://web.archive.org/web/20020114...icID=325.topic


Bard Mez Secondary Resists

At some point during pre-K classic era (August 1999? I can't find a patch note) Sony nerfed bard mez because they considered it too powerful to perma-mez an NPC with a manaless ability. Abashi says this:

"There is no way that we can make a no-mana song as powerful as a mez as good as a spell that uses mana, which is the reason that it was changed to its current state last August or so."
"I think that I've effectively addressed these issues above, but I'll elaborate. The original implementation of the song made it much too powerful considering its lack of a mana control."
-- Abashi (09-07-2000)
https://web.archive.org/web/20010303.../011801-2.html

The nerf consisted of a secondary resist roll that wasn't MR based. These secondary resist rolls were found in the clients.

This September 2000 post by a player describes how bard mez resists worked:

"Bard mez songs (Lullaby, Pixie Strike, Twilight) have two resists. The first is your ordinary resist that all songs/spells get. The second resist is something GZ threw in only for bard mez. When a bard tries to mez a mob, three things can happen:

1) The mob succeeds its first resist check and the song doesn't work. Message:

"Mob resisted your <song name>."

2) The mob fails its first resist check, but succeeds its second resist check and the song doesn't work. Message:

"Mob's head nods."
"Mob resists your attempt to mesmerize it."

3) The song is not resisted and works. Message:

"Mob's head nods.""
(09-07-2000)
https://web.archive.org/web/20010303.../011801-2.html

The secondary resist check was in the HitBySpell() function in the clients but later moved into the resist check function. Sony moved it because the above post mentions the songs were resulting in a success message even when the secondary resist fails, and moving the check into the primary resist function makes the secondary resist look like a normal resist. Sony moved it not long after that post was written.

There are actually two different secondary resist checks: one for single target mezes and one for AoE mezes. The AoE songs had a harsher secondary resist check. Because Sony failed to mention this, players at the time were reporting that bard mez resists were higher than what Sony was telling them they should be, as players were testing using AoE songs which had a higher resist chance.

The secondary resist chances were the following for single target mez songs:

target level 1-24 = 5%
target level 25-29 = 25%
target level 30-49 = 34%
target level 40-44 = 50%
target level 45+ = 60%

The secondary resist chances were the following for AoE target mez songs:

target level 1-24 = 34%
target level 25-29 = 50%
target level 30-49 = 67%
target level 40-44 = 78%
target level 45+ = 82%

I found three posts from two different players who ran some meticulous resist tests during this era on bard mezes that are worth linking here. In these tests you can clearly see the green con MR floor of 3% and the resist rate on Kelin's cast on level 1 mobs being ~35%.
https://dbsanfte.github.io/eq-archiv...tml/14644.html
https://dbsanfte.github.io/eq-archiv...tml/20040.html
https://dbsanfte.github.io/eq-archiv...tml/13968.html

Those secondary resists were the case until November 15 2000. On that date the patch notes included the following:

Quote:
Originally Posted by Nov 15 2000 Patch Note
– Pixie Strike: Made it less resistible by reducing the secondary resist.
– Song of Twilight: Increased duration. Made less resistible by removing the secondary resist. Allowed it to be cast at any time of day.
After that patch the single target mez secondary resists changed to the following:

target level 1-24 = 5%
target level 25-29 = 10%
target level 30-39 = 15%
target level 40-44 = 25%
target level 45-49 = 35%
target level 50+ = 60%

Song of Twilight was hard coded to always win the secondary roll. The AoE mez secondary resists were unchanged.

Kelin's Lucid Lullaby was changed into a single target mez in the big September 4 2002 patch. Presumably the big resist revamp patch is also when they removed the secondary resists on bard mezes. I found comments as late as December 2001 complaining about resists, then after Sept 4 2002 people started mentioning that the resists were better. (Luclin era had less chatter on any given topic)

Also regarding Kelin's: I found two comments remarking about how overpowered this song was in beta, so I think it's possible that Sony gave AoE mez songs a secondary resist before single target mez songs did in what Absor says was August 1999. Neither were found in the February 1999 client however.
Reply With Quote
  #2  
Old 06-05-2025, 04:04 AM
Torven
Sarnak
 
Join Date: Aug 2014
Posts: 76
Default

Lulls

Lull spells before the September 4 2002 patch were almost useless. Similar to bard mez only worse, they were effective at earlier levels then the resist rate ramped up considerably to an unusable level, until NPCs became entirely immune at level 50. Lulls were so bad that players used the spells to pull because the aggro on resists was supposedly low.

"When you can reliably get lull to stick on 3 mobs in a 4 mob pack and pull the last one alone let me know."

"lull is the most useless spell ever. try casting it on any creature you want, doesn't matter if its green even, just wait for the resist message. we get upgrades in this line which you might think would work better, but they don't. we even get an area effect lull at lvl 51, that's the biggest joke of them all. they should just drop that spell and give us another useless spell in it's place like another in the whirl line."

"And when you let him know you had better take a screen shot and post it too because I'm gonna read it and call you a liar. Lull does nothing, I use it to pull in hate since it's agro isn't very high and it's easy to taunt off me."

"I read on a different board once that they should either fix the Lull line or change the first letter to P. /rofl"

"put this one way at the back of your spell book, and do the same with any upgrades should you happen to purchase them. There's a very good reason Lull is usually referred to as "Pull" and the level 51 AE version Wake of Tranquility is usually referred to as "Wake of Train-quility"."


(April 2001 thread on Caster's Realm)
https://web.archive.org/web/20010430...c&f=9&t=004347


Classic lulls had an MR floor that increased as the target's level increased. This variable floor is in the clients so we can know what they were. (it was not in the Feb 1999 client however) This floor was applied after the level difference modifier was applied, like the other floors.

The resist rates for lulls in classic were the following: (assuming the NPC did not have higher MR than these floors)

On targets level 1-14: 11% (10 MR floor)
On targets level 15-24: 21% (20 MR floor)
On targets level 25-34: 34% (33 MR floor)
On targets level 35-39: 43% (42 MR floor)
On targets level 40-49: 55% (54 MR floor)
On targets level 50+: 100% (100 MR floor)

To be clear: this is a tiered floor, not an override. If you cast Lull on a level 1 NPC as a level 1 player, the resist rate would be 26% because the standard MR at level 1 is 25. So even at lower levels the spells weren't that good.

Late into Luclin, lulls were turned into an override of 15 MR. (before level difference) Sony did not mention this massive improvement to the lull line in a patch note however, so the date is technically unknown however it likely was in the September 4 2002 patch. EQ's lead designer at the time, Rich Waters, said this about seven weeks after the September 4 2002 patch:

Quote:
Originally Posted by Rich Waters, October 2002
Traditionally, most lull spells haven't really worked very well. Lull had a high resist rate and wasn't very reliable. We've recently looked at lulls and improved the way they work to make them more useful to players. You should find that the lull line of spells works more often than it did, though it will still fail some of the time.
The song Kelin`s Lugubrious Lament was hardcoded to be special. What this song did was halve the target's effective resist value, and it had a floor of only 5 MR. This made it the best lull spell by far and the only one that could be used on level 50+ NPCs. It's only a few ticks and I suppose Sony wanted to give bards something unique so that's probably why they made this exception.

Druid Harmony (the level 5 targeted AoE) was supposedly unresistable but I found several comments by players saying that it could be resisted, however it would still never aggro on a resist. I presume that the players are referring to lull immune NPCs. This spell was heavily nerfed on PoP's launch day (October 21 2002), after which the spell was unusable on NPCs above level 40. Rich Waters says this at the time:

Quote:
Originally Posted by Rich Waters, October 2002
We've had the ability for awhile now to make it so certain monsters were totally immune to being lulled. This is especially common in higher level outdoor zones such as Cazic-Thule, since Harmony would take any challenge out of splitting groups of monsters. Having a such powerful game dynamic with no risk of failure made these encounters too easy, so we were forced to make many monsters totally lull immune to compensate. With this change to Harmony, we'll now be able to remove the lull immunity from many monsters, and turn this ability into a useful spell line that gives great benefits, but with some risk that your spell may be resisted.
Literally the next day after PoP's launch they put in the single target spell Harmony of Nature.

Lull Early Fade Chance

Lulls could fade early in a manner similar to charm and root. This lull break chance was found in all clients and still exists on Live. (it was not removed on Sept 4 '02) I ran tests on Live servers years ago to come up with data so I could mimic it on TAKP.

The clients have a routine in them that appears to be called on a debuff's tick, but only for a handful of SEs. (similar to EQEmu's Mob:oBuffTic()) This function has the break early chance for lulls in it, but it does not have the break early rolls for charm, root, fear, blind. This function shows lulls having a preliminary roll just like root, fear and blind which is a 3 in 4 chance to do a resistance roll. It also has the +4 caster level in the resist function call, so this is a nice verification of that existing. This is the same as Live servers today. However the February 1999 client has this preliminary roll being 2 of 3 instead, so it appears at the end of beta at least lulls were a bit less likely to fade early.

The floors do apply in the resist checks done on the lull's tick saves, so even if lulls were to land they wouldn't have lasted very long. The spells may have a listed duration of multiple minutes but practically speaking they were extremely unlikely to last the full duration on anything but the lowest level NPCs.

Incidentally I did find a January 2000 post mentioning that critical fails occurred back then: https://dbsanfte.github.io/eq-archiv...tml/13483.html

The critical fail chance in PoP and Live was and is: 90 - CHA / 4. See my other thread for where I found this. I can't say if it was the same in classic but if I had to guess I'd say yes.


Spell Resist Modifiers

The resist function had the hardcodes for individual spell resist modifiers. These were all hardcoded prior to September 4 2002, and after that patch they finally put in a proper spell field for it. This is the 'lure' number everybody checks for in the spell data. Some of the spells prior to that patch weren't just a simple negative number however. Classic era lure spells I explained earlier were more complicated and so are a few other things in this section. I will also include some other non-lure related resist modifiers here.


Wizard DDs

Wizard DDs gained their micro-lure (-10) behavior on January 20th 2000. Sony simply subtracted 10 resist on wizard DD spells in the resist algorithm to do this.

The qualifications for spells to get this -10 are simply: the spell must be MR, FR, or CR; it must be level 35 or higher; and if it's a MR spell, the first spell effect must be 0 or 79.


The Charisma Resist Modifier

Some spells got a resist reduction to them if the caster had high charisma. I mentioned this in my Charm, Root and Lulls thread regarding charm. Most people have heard about this but I found some more information about it.

First of all, it applied to all classes, not just enchanters. NPC casters were excluded.

Secondly it applied to Charm, Mez, Mem Blur, and SE 34 which EQEmu labels as 'Confuse'. SE 34 seems to be an unused and abandoned SE.

Thirdly this did apply to charm tick saves in classic from what I can tell, but it of course did not take the resist below the floors. The floors made charm much worse back then, however this charisma modifier working on tick saves often removed the need to keep tash on the pet.

The formula is the same in all clients: reduce the resist by 1 point for every 8 charisma above 75. This reduction is capped however.

The reduction cap was not always -25. The cap was -10 until some time between September 2000 and November 2000. The September 19 client has a -10 cap and the November 15 client has a -25 cap. This means any charisma above 155 did nothing for resists until late Kunark.


Damage Spells Penalty on Yellows and Reds

There is a resist penalty when casting damage spells on level 17+ NPC targets above your level. This penalty is level_diff * 2 and can be debuffed away.

For example: a level 60 caster attacking a level 70 NPC will suffer a resist penalty of 20 added to the target's resist value.

This penalty is only applied if the first spell effect ID is 0 or 79. So it may affect spells with a stun component and such if damage is in the first slot.

This penalty was mentioned in the Prathun pseudocode post, so it seemingly never went away.


Other Spells With Micro-lures

Six spells had a -10 micro-lure applied to them like wizard DDs. In the case of charm these did apply to tick saves.

The spells are:

Enforced Reverence
Call of Karana
Thrall of Bones
Fascination
Glamour of Kintaz
Boltran's Agacerie

All six were in the March 2000 client.


Fear Spell Modifiers

For spells Invoke Fear, Panic the Dead, Terrorize Animal:

If the target was higher in level than the caster, the resist adjust was -5.
If the target was equal to or lower in level than the caster, the resist adjust was -(caster_level / 2).

For spells Trepidation, Repulse Animal, Dread of Night:

If the target was higher in level than the caster, the resist adjust was -15.
If the target was equal to or lower in level than the caster, the resist adjust was -5 - caster_level / 2.

All six of these spells had an MR floor of 5. These are all found in the March 2000 client.

These resist modifiers are why fear spells have the same or similar durations and cast times but cost more mana and are obtained at higher levels, although the resist reduction isn't really worth the increased mana costs, channeling benefits of lower level spells and reduced fizzles.


Harm Touch

Harm Touch was hardcoded to resist less but it wasn't a resist value adjustment. Instead it was a mitigation reduction done after the partial damage was calculated. This means that Harm Touch would still fully resist every time at 151 effective resist value but at 150 or less the damage would hit for full damage much more often and mitigate less.

In the February 1999 client this reduction was only 15%. In the March 2000 and later clients this reduction was 45%.


Misc Modifiers

Three specific tash spells (the Enchanter MR reduction line) are hard coded to reduce the target's MR to 2/3rds before landing. This appears to be vestigial because tash spells are unresistable. Sony oddly left this in for a long time. (it's in the November 29 2000 client)

Lycanthyropy spells (SE 44) get a +30 resist adjustment to them, making them easier to resist. Only one spell in TAKP's spell data uses this, which is the 'Lycanthropy' spell. It appears to be some kind of unfinished effect as the debuff seemingly does nothing in the TAKP client. Google tells me that later versions of EQ (after TAKP's time) seem to have made this functional.


Resists in PvP

PvP resists were the same as NPC resists for the most part, but there were some important distinctions.

PvP damage was reduced and this reduction was handled outside the resist function. There is a Test server patch note from August 26 1999 which mentions a 60% reduction for the teams server, and a September 13 1999 patch note says: "All damage spells cast in PVP combat will do less damage to the PC than the same
spell would do to an NPC"
so that may have been when it went in. I don't know the specifics of when the mitigation went in or how it may have changed. Solar got the precise PvP mitigation percentages (it varied depending on level and class) from one of the clients and I think these numbers were in use for the vast majority of the older eras. I'll outline it here:

Hybrid (4 classes) spells were all 80% of normal.
Caster+priest spells obtained before level 14 were 88% of normal.
Caster+priest spells obtained in levels 14-23 were 78% of normal.
Caster+priest spells obtained in levels 24-38 were 68% of normal.
Caster+priest spells obtained after level 38 were 63% of normal.
Harm Touch was 68% of normal.

Harm Touch was changed to 68% in this patch:

Quote:
Originally Posted by February 21 2001 Patch Note
— Due to the recent improvements to “Harmtouch”, it is doing much
more damage than it would before and unbalances PvP. As such it will
now do less damage in PvP (68% of PvE, down from 80%).
Before October 8 2001, a major difference in PvP resists compared to PvE was that the NPC partial damage modifiers only applied to NPC targets, so higher level players didn't resist DD spells more and the lowest level players didn't resist less like NPCs did. See the partial damage section for details on these modifiers.

The first significant PvP resists patch that I'm aware of was May 31 2001. This patch made debuffs 50% stronger in PvP:

Quote:
Originally Posted by May 31 2001 Patch Note
- Resist-debuffs will do 1.5 times their normal value for PvP
encounters. In other words, if the spell did -60MR in PvP before, it
does -90MR in PvP now. PvE (combat versus NPCs) remains unaffected.
Late Luclin, the September 4 2002 patch radically changed the PvP game because resists essentially became half as powerful, and you needed twice as much resist value to resist at the same rate as you did prior to the patch. This angered a lot of PvP players. (I quoted some of these players in the first section of this document) Sony responded by changing the resist scale for PvP resists. This PvP scale is bow shaped instead if linear such that resists under 200 made spells resist more than they would in the linear PvE scale but the resist rates ended up the same near 200. This new scale was put in a few weeks later:

Quote:
Originally Posted by September 26 2002 Patch Note
- The recent resist changes have been adjusted for PvP. They will not
be exactly as they were prior to the resist change, but they should be
reasonable now. Please let us know if you feel they need further
adjustment.
For TAKP I mimicked this PvP scale by doing this:

Code:
// PvP
if (caster->IsClient() && target && target->IsClient() && !use_classic_resists) {
	if (resist_chance > 1 && resist_chance < 200) {
		resist_chance = resist_chance * 400 / (200 + resist_chance);
	}
	if (resist_chance > 196) {
		resist_chance = 196;		// minimum 2% chance for spells to land
	}
}
With that PvP scale patch, Sony introduced a bug. If players had very high resists, north of 300, then the resist rate started to go down. The cause seems to be that Sony's PvP resist curve was parabolic and they failed to prevent higher resists from using this curve, which peaked around 275 then went down. The problem was so bad that at 500 resist value, the resist rate for all-or-nothing spells was about equal to that of somebody with about 50 resist. I parsed this on Al'Kabor and I also found some old comments about it. I don't know when they fixed it but it was seemingly after June 2003.

This Graffe's thread from June 20 2003 was started by a player who noticed spells landing frequently with very high resist values:
https://web.archive.org/web/20030829...ID=27020.topic

An April 2003 patch put a cap on DD spells in PvP:

Quote:
Originally Posted by April 8 2003 Patch Note
- Direct Damage spells in Player vs. Player combat can only do up to
75% damage of the target’s maximum hit points in a single hit. This
modification is done before resists are taken into account. For
example, if a player has 1000 hit points when fully healed, no single
direct damage spell will do more than 750 damage to him in a single
hit.
That patch was particularly important for Harm Touch because prior to that Harm Touch could do 4896 damage to players in PvP. (7200 * 0.68 )

In August 2004 Sony added more PvP related fields to spells to allow them to land on high resist payers.

Reply With Quote
  #3  
Old 06-05-2025, 04:09 AM
Torven
Sarnak
 
Join Date: Aug 2014
Posts: 76
Default

Bash Resists and Stun Immunity

Bash and kick stuns aren't spells but since we found the logic in the client and it's somewhat similar I'll go over it here.

The chance to 'resist' a bash stun is 55%, modified by the same level difference modifier as spells. I.e. level^2/2. It uses the same function.

The bash stun immunity check is the same function used by spells.

The check for level 55+ warriors using kick to stun and ogre race immunity is found in the decompiles.

Bashes that don't actually stun the target due to failing the roll will do a second roll to determine if the bash will interrupt a spell cast without stunning the target. These stunless interrupts still check for stun immunity however and will not happen on stun immune targets. This chance is 7 in 9, or about 78%.

Spells 859 to 1023 are excluded from this stunless interrupt chance.

There is a 1% minimum chance to full stun the target if it's not stun immune. (level difference would make deep reds immune otherwise)

The logic is this with some irrelevant things removed for clarity:

Code:
bash_resist_chance = GetBashResistChance(attacker_level);
roll = Rand0(0,100);	// random number from 0 to 99 inclusive

if (roll < 99 && roll <= bash_resist_chance) {
	roll = Rand0(0,9);	// random number from 0 to 8 inclusive
	if (roll > 1 && (spell_id < 859 || spell_id > 1023) && !this->IsStunImmune())
	{
	  InterruptCast();
	}
}
else {
	StunMe(2000);		// Stun for 2000 milliseconds.  This function has a stun immunity check inside it as well
}
Pre-Velious if a target was stun immune then it was also always mez immune. Before Velious this immunity was solely hardcoded by race and level as far as I know. But in Velious they added a mez immune flag, so a target could be made mez immune but not stun immune. I started finding mobs in PoP that were stun immune and not mez immune but I can't say when exactly they added a stun immunity flag.

Interestingly the stun immunity check was added in the late pre-K classic period. My earliest log is from December 31 1999, and is of my wizard on a Vox raid. In this log Lady Vox is interrupted by bashes 19 times, and her melee damage is less frequent than expected, so the bashes may have actually been stunning. Even bashes from pets were interrupting Vox's casts. I have more Lady Vox logs from late pre-K classic era and there are no Vox interrupts in them, the next log being February 19 2000, so Sony added the stun immunity check to dragons between Dec 31 and Feb 19.

The February 1999 client handles bash a little differently. There is no stun immunity check and the chance to stun is reduced on targets above level 30 by an amount that increases the higher level the target is. The base stun chance is also 50 instead of 55.


Enchanter NPC Resist Override

Level 25+ Enchanter NPCs were hardcoded to be able to land spells on players no matter how much MR the players had. This was in all the decompiles except the February 1999 one.

The Enchanter NPC resist override reduced the effective resist to the target's level if the level was lower than the resist, then capped it at 40. So technically this would be a very low ceiling but since players are unlikely to face enchanter NPCs before they reach level 40 aside from evil eyes I just labeled it an override.

For the most part it caused green enchanter NPCs in Velious zones to grief players. I found several old threads to confirm this override:

"Um Sirens can charm through 200MR when they are green ... (trust me after that long soloing those things for the shawl) ...
Their charm is lured ... because this is the only spell that will land ... and being chanters all spells are MR based"
"I guess you could call it a lure. What's really wrong with sirens charm is the same thing as every other mobs charm. It's broken.
It doesn't matter what your mr is, because according to popular opinion on these boards, the NPC charm spell doesn't do a check on your MR, it just sticks.
Whats even worse than that is you can be charmed by a mob that already has a pet."

(Feb 2002)
https://web.archive.org/web/20020214...&f=19&t=001372

"By the way, if invis pops you could have 200 mr and those green freaking sirens can still charm a lvl 60. Talk about overpowered." (04-25-2001)
https://web.archive.org/web/20040928...p/t-10835.html

"They charm you right away as soon as they are within casting range. My Magic Resist with druid buff was at 170 and I was charmed just as often as my druid partner was with his MR at 60." (Jan 3rd 2002)
https://web.archive.org/web/20030130....shtml?id=6153

"As a level 60 monk...with 120-130 MR, green con sirens in CS have little trouble charming me... h8 those things!" (22 Apr 2002)
https://dbsanfte.github.io/eq-archiv...msg-108955.txt


This June 2002 post seems to indicate this override being removed mid-Luclin:

Quote:
Originally Posted by A Player on June 7 2002
"There used to be the same problem with Enchanter mobs,
they could Charm you with ease, even if they were *way*
green to you.

But that was mentioned as changed in one of the recent
flurry of patch messages -- now you get a normal
level/MR check against being Charmed.

And I can verify it. Last night I soloed the Sirens outside
of Sirens Grotto to clear them away before our raid
group came through, and although they tried to Charm
me a total of about 30 times, not one stuck (58 Paladin
with 91 MR). Hooray."
https://dbsanfte.github.io/eq-archiv...msg-121620.txt

Although I could not find a patch note mentioning this.

Al'Kabor logs do not show any enchanter NPC resist override. I do see some charms on players in AK logs but these are Dictate casts. (Silverwing, Lady Nevederia and level 60+ PoP NPCs cast Dictate)

Why did Sony add this? Because reaching 100 MR, which is very easy to hit, would make enchanter NPCs entirely unthreatening as all spells would be resisted without it. That is my guess anyway. (although enchanter NPCs do cast tash)


More Post September 4 2002 Issues

When Sony switched to the new resist system about six weeks before PoP launched, they failed to account for some things which resulted in peculiar behavior.

One of the most significant issues is that many of the CHA -4 lures were made much harder to resist, because before the patch all lures were all-or-nothing rolls and after the patch some lures became partial hittable. Not only that but the partial damage scale became 600, so these spells went from a resist cap of 75% with somewhere around 200 resist value, to resisting ~64% with 500 resist value. Luclin added bard songs which hit outside of the group so this wasn't quite as bad as it sounds, but resist rates still went way down for these spells after the revamp.

The CHA -4 issue was very preventable however because Sony added a no_partial_save field to spells. They just neglected to flip this on for CHA -4 spells for whatever reason. They did apply this flag to the CHA -6 dragon breaths. Notably Freezing Breath (Ventani's AoE) and Stream of Acid (Nexona's AoE) were flagged to not partial save. If this flag were not flipped on then these AoEs would have resisted at a rate of 15% if the player had 192 resist value at level 60 whereas the rate is 45% using classic rules.

CHA -4 AoEs were more prevalent than CHA -6 AoEs in NToV. This made NToV harder than before the patch although by then it was fairly easy content due to AAs, Luclin gear and spells, spoilers and players having experienced it before. Vyemm's AoE is a CHA -4 AoE however.


"Is a lot tougher and longer fight. With nearly 300 mr i only resisted about 3 of his 50+ aoes, the fight usually takes us a couple mins, today it took 30! But nonetheless it was pretty fun fighting for 30 mins against nev"

"This fight is utter BS now. Some of the people in our guild had 350+ MR and were resisting 1 out of 30"

"We did Vyemm and Lady N and with at least 180+ in MR at all times I never resisted the Gflux and I only once resisted the "spin the bottle thingy""

"Fight was three times as long as before for us tonight. With 177 MR after she tash'd me I resisted about 20 to 25% of her AOEs. Now she also seemed to be AOEing more as well. Lord Koi was also longer, but not nearly the same upgrade that Lady N got"

Thread title: Lady N with new resists
https://web.archive.org/web/20021120...cID=1237.topic


Stun Breath is Phara Dar's AoE and Zlandicar's proc spell. This spell became much easier to resist after the revamp because Sony either neglected to put a -150 resist adjust on it or decided not to. Unfortunately not doing that made those fights trivial and the fights would have still been much easier even if they had applied a -150 adjust on the spell because bards can raise MR by over 200 and the lure caps were removed.

Immolating Breath is Talendor's AoE and this had its no_partial_save field set true because it would have used the partial scale otherwise. This spell also unfortunately did not get a -150 adjust to it when it should have, so resisting it became very easy and it trivialized the encounter.

Ceticious Cloud is the weirdest spell of all of these. This is Severilous' and Wuoshi's AoE. The spell data for this spell had a direct damage component but the spell didn't actually do any damage in-game. It also had no recast timer/cooldown on it. Sev and Wuoshi in fact spammed this spell every 3-5 seconds and this was the case even on Al'Kabor. Sony seems to have "fixed" this spell's incorrect recast timer by making it not do damage. Why they did not just give it a recast time I don't know. Anyway this spell used to be a dragon breath lure before the revamp but all spells with damage in the first slot were made partialable and that includes this spell, so it needed the no-partial flag set to true which Sony did set, but they neglected to apply a -150 adjust on the spell. The result is that it became very easy to resist and these two dragons became loot pinatas.

The revamp removed the lure caps, and so players could be made immune from all the CHA -6 dragon breaths using bards. This made the encounters using these spells much easier if bards were available, as Sony had put a low cap (45% or not much higher) on these AoEs because the spells are what made the encounters difficult. It also made resists much less effective per-unit, so if your raid didn't have a bard then you had a harder time. The revamp made bards much more valuable for raids.


Memory Blur Resists

The resist function checks for SE 63 (mem blur) and applies the charisma modifier on these spells. This appears to be vestigial.

Blur effects are most likely handled outside of the resist function but I'll include what I know about them for completeness.

Developer posts and patch notes mention blurs:

May 18, 1999
"High level creatures get a better save vs. Memory Blur (Enchanter) and Atone (Cleric) spells"
https://web.archive.org/web/20001022...i/news.pl?id=7

May 21, 1999 GZ comments on Enchanter spell changes pt.1
"Only monsters with level 40 or higher gain resistance to Blur."
http://web.archive.org/web/200105010...ws/99-5-16.htm

May 24th, 1999
"The saving throw against Memory Blur and Atone was reduced (harder to save against) from the last patch."
https://groups.yahoo.com/api/v1/grou...t/messages/335

EverQuest Third Anniversary State of the Game part 2 (March 2002)
"Enchanters' Memory Blur line, and aspects of Rapture and Glamour of Kintaz have been improved."
http://boards.station.sony.com/ubb/e...ML/000521.html

March 19, 2002
"- Memory Blur, Mind Wipe, Blanket of Forgetfulness, Memory Flux, Glamour of Kintaz and Rapture have been given a greater chance of clearing the ‘hate list’"

A player I've corresponded with named Ravenwing who played on Al'Kabor and TAKP is extremely knowledgeable about the game and is somebody I and the community consider to be of high character. His input has been invaluable to my emu work and I consider any claims he makes to be highly credible. He posted this on the TAKP board:

Quote:
Originally Posted by Ravenwing
I personally confirmed that any memory blur - even a 1% blur, such as that on the level 4 mez - against a low-level NPC had a 100% success rate on Al'Kabor.

In 2010, long after the Al'Kabor era, a player named Crystilla (the webmaster at eqclerics.org, who seems to have been part of some kind of volunteer player committee with access to the developers) posted the following about Atone:

Quote:
Originally Posted by Crystilla
I talked to a Dev and it does. For those curious here's the formula and summary.

Description: Clears the affected NPC's entire hatelist.

Base effect 1 is a % bonus for the effect to occur.
The player is given a percentage bonus based on level, lower is better. < 17, bonus = 100, > 53 bonus = 25. Del's note: Referring to mob level with this part
Another bonus is applied for charisma. (Cha - 150) / 10 up to a maximum of 15.
Now, granted, this post is about eight years post Al'Kabor. However, I think the formula is accurate. First, because it matches my subjective impression of memory blur's success; second, because Pithy (one of Al'Kabor's smarter and more empirically-minded players) did a bit of testing of Blanket of Forgetfulness and Memory Flux on AK and found their success rates on high-level NPCs to be around 85% and 70% with ~300 cha, respectively; and third, because the mob levels specified in the formula suggest that it was written early on in the game's development.
Presumably that's how mem blur's chance to succeed worked from March 19 2002 onward.

I found an older comment regarding mem blur success rates from 2000:

Quote:
Originally Posted by Caster's Realm Poster, Sept 2000
I disagree that the entire memory blur line is useless at the higher levels, and find your figure of 12.5% success rate to be much, much lower than what I've experienced with the "real" blur spell, blanket of forgetfulness. Even against NPCs level 57 to 62, this spell is effective 30-50% of the time. That is extremely powerful, when you consider what the spell is really doing.
He claims that Blanket of Forgetfulness worked about half as often back then. The Luclin patch probably removed the level 40+ increased resistance. OG Sony liked to make things not work at higher levels, so them having made mem blur not work well at higher levels is unsurprising.

I also ran some mem blur tests on Live servers in 2021. The results did not quite match Crystilla's claim:

Memory Blur cast on a level 69 target by a level 65 enchanter with 277 charisma 401 times resulted in about 146 blurs, or 36%.
Memory Blur cast on a level 62 target by a level 65 enchanter with 277 charisma 478 times resulted in about 203 blurs, or 42.5%.
Expected chance would be 10%+25%+12% = 47%

Memory Flux cast on a level 62 target by a level 65 enchanter with 277 charisma 335 times resulted in about 199 blurs, or 59.5%.
Expected chance would be 30%+25%+12% = 67%

Blanket of Forgetfulness cast on a level 62 target by a level 65 enchanter with 277 charisma 354 times resulted in about 254 blurs, or 72%.
Expected chance would be 20%+25%+12%=57%+24.5% = 81.5%

So either Sony/Darkpaw nerfed mem blur somewhat after 2010, or Crystilla's claim is incorrect, or we're misinterpreting Crystilla's explanation or my tests were faulty. The tests were done by standing on top of a KOS NPC while an enchanter was blurring from outside of aggro range using an automated push button tool. NPCs will do their "I'm aggro" emote every 5.5 minutes and also when they aggro again after a blur, so I had to manually account for these extra emotes which is why I say "about" X blurs.


Miscellaneous

Spells That Didn't Roll

Several spell IDs were hardcoded to bypass the resist roll. These spells are: Dictate, Rapture, Enslave Death. Lifetaps (spell type 13) also bypassed the resist roll. All of these spells however could be blocked by the 151 limit, so all of these spells could still resist on mobs with high enough MR, and they all resisted exactly the same way. If the target had 150 or less effective resist then the spells landed, for full. (assuming they met level requirements)

CHA -5 spells (mostly wizard lures) and Poison Breath were also found in the "IsUnresistableSpell()" (I'm guessing at the name) function that Dictate, Rapture and Enslave Death were in. It appears as though Sony had early plans to make wizard lures (and Takanon's AoE) resist like lifetaps but instead decided to create the separate roll just for lures which I outlined earlier. This "IsUnresistableSpell()" function would never be called for lures because lures branch away from the main resist logic early in the function and do their own rolls.

In regards to lifetaps, apparently they were made resistable at some point in late 1999. I found old comments mentioning that time frame. The Feb 1999 client doesn't have an exception for lifetap spells in it but the March 2000 client does. Players mentioned something about Sony not liking newbie necros taking down Sand Giants. They also mention the "six level" rule applying.

I need to make this clear: lifetaps were not unresistable in 2000 and later. In fact they resisted on so many raid NPCs that necros made it a primary issue for their class. See this thread: https://web.archive.org/web/20010124.../045291-5.html

I did find an old comment which mentioned lifetaps not landing on Aaryonar until malo was applied, and that agrees with the logic and data I outlined.

Cazic Touch is also hardcoded right at the top of the resist algorithm to always land.


Mage Pet Level Advantage

At the top of the resist function there is a +5 to the caster level if something equals 27. We think that this may be a body type check but we're not entirely sure. If it is then this would give summoned mage pets a +5 to their caster level for their spells.


Resistant Discipline

The November 29 2000 client is the first client with the resist discipline in it. In that client the discipline always granted +3 to all resists, which is terrible. Sony buffed the discipline on December 6 2000. I checked the April 4 2001 client and can confirm that client scales from 3 to 10, with +4 starting at level 35.
Reply With Quote
  #4  
Old 06-05-2025, 04:16 AM
Torven
Sarnak
 
Join Date: Aug 2014
Posts: 76
Default

Noticeably Absent Things

The Six Level Limit (1.25x Limit)

The "six level limit" was a 1.25x level limit with a minimum of 5 hittable levels above the caster. At 6 or more levels above the caster the targets became immune.

Quote:
Originally Posted by September 4 2002 Patch Note
- The hard level limit involving players casting on NPCs has been
removed. This used to be referred to in EQ folklore as the “Six Level
Limit” (It was actually 1.25 times the caster’s level, but more people
likely thought about it the other way.) This means that in the vast
majority of cases, there is at least a small chance that a person will
be able to connect a spell with an NPC, even if they are out of that
NPC’s traditional level range.
The September 4 2002 patch partially removed this limit for players. This limit was not found in the resist function, so it had to be outside of it. The function it was in was probably not in the client although we could have easily missed it.

I can confirm that this limit had five hittable levels because I have a 2001 log with a level 14 player (Shortok) casting nukes on level 20 Oasis NPCs and they fully resisted. The chance of a full resist in those circumstances would be 4% if the limit didn't exist. The log had three full resists and the player was a cleric so these stood out a lot as he had virtually zero full resists on other NPCs.

This same cleric at level 26 got four of four full resists on a level 33 goblin NPC in Highkeep. 26 x 1.25 = 32.5, so this confirms that the multiplier is 1.25x and not 1.3x. I found an old Sony developer quote from early 2002 which stated 1.3x but he was incorrect.

I have logs of myself powerleveling characters on Al'Kabor, which is post-patch. I found that I was able to land spells beyond this limit however the resist rate went up dramatically. The patch didn't remove this limit but instead turned it into a soft resist barrier at lower levels. I had a level 19 cleric casting AoEs on a large Solusek's Eye goblin pull which had the king in it, which is a level 30 mob. At level 19 I cast ten spells on the king, and 3 landed for minimal damage. During this single pull my level went from 19 to 24. My resists on the king were significantly elevated until level 24 when they began hitting for full damage. I estimate that Sony may have been adding around 150 points to the king's effective resist value.

At higher levels this 1.25x hard limit remained in place. This is easily found in logs because this limit applied to pets. Beastlord pets cast a lot of spells, and I have logs of level 60 beastlords doing raid content. These pets are level 47. I found that the pets could not hit NPCs which were above level 58. In Kael for example the pets could hit Kallis Stormcaller easily but they could not hit the Statue of Rallos Zek (level 59) or the temple guardians (level 60) or anything else above 58.

The mage epic pet is level 49 unfocused. I found logs of an epic pet in Plane of Valor and the pet could hit the level 61 mobs but none of the level 62 golems. Since direct damage spells use the 600 scale after the patch, the fact that literally zero pet spells landed indicates that the 1.25x limit at upper levels is a hard limit.

In my 2014 resists thread, I mentioned hard level limits existing at the time on Live servers which prevented all spells from landing. These limits appear to have been the same since the September 4 2002 patch, which seems to have put in place additional limits beyond the 1.25x limit. (either then or PoP launch) Note that, as of now, these limits have been raised on Live servers as of 2025. On Live servers right now a level 50 may land full damage spells on a level 70. But these were the limits in 2014:

Level 50 may hit up to 62 (+12) (this prevents level 50s from hitting level 63 PoSky bosses btw)
Level 55 may hit up to 68 (+13)
Level 60 may hit up to 70 (+10)
Level 61-63 may hit up to 75 (+14 to +12)
Level 64 may hit up to 80 (+16)
Level 65 may hit up to 86 (+21)

So it appears that Sony limited 60 and under players to level 70 targets, and after that limited targets to level 75, 80 and 86. How do I know this was the case since late 2002? Because the pet summons for level 61-65 players could not land spells on mobs above level 70. I.e. your level 65 necro specter pet (a level 60 pet) which casts SpectreLifetap could never land this spell on a level 71, ever. I found zero cases of this in old logs, and this spell has a -200 adjust on it. (nor did I find a beastlord pet spell landing) In fact I find pet spells landing on the fake Vallon spawns (which are level 70) in PoTimeB but not the real one. It's very obvious.

To summarize:
  • The "six level limit" means only three levels of red are hittable until the caster reaches level 24 when the 1.25x limit allows four levels of red.
  • The 1.25x limit still existed after Sept 4 2002, it just turned into a soft limit for lower level players only.
  • There are other level limits in place restricting who may damage level 71+ mobs with spells. (the attacker must be level 61 or higher)
  • Pets are subjected to these limits.
  • These limits apply to lifetaps.

I did find an old comment that raises an issue however. This level 53 wizard claims to have landed a lure nuke on the Velious revamp Cazic Thule, which is a level 70 NPC:

Quote:
Originally Posted by Graffe Poster
Was just on a CT raid and the higher level wizards reported ice based droughts hitting. They stopped using lures completely. At level 53 nothing but my lure of frost would hit though. Oh well, good reason to lvl now. Dots were also landing for my necro friend at lvl 57.
https://web.archive.org/web/20011225...picID=45.topic

This suggests that lures were exempted from the level limits but that seems weird to me. Maybe they were. A single comment isn't much to go by.


Rain Spell Handicaps

Rain spells in classic had a 20% resist penalty on them when casted on NPCs. This resist penalty is not found in the resist function, oddly.

Quote:
Originally Posted by February 21 2001 Patch Note
— Rain spells no longer count pets against total number of entities they can damage (all rain spells)
Quote:
Originally Posted by August 26 2003 Patch Note
- AE Rain Spells were resisted 100% of the time when an NPC was above level 20 and had less than 10% of its hit points left. This will no longer happen.
Quote:
Originally Posted by April 12 2005 Patch Note
- NPC’s innate 20% resistance against rain spells has been removed.*
I attempted to find the earliest comments remarking on rain resists being unusually high. I found several threads from the late Kunark era mentioning this, with the earliest comment being August 2nd 2000. I can't say when the 20% rain resists went in exactly. There aren't a lot of comments from before that time.

Incidentally I found several comments about rains being low aggro, so it's possible Sony's rain beacon logic was intentionally or unintentionally resulting in near zero aggro. This does make some sense considering the elevated resist rate and resists otherwise doing full hate on typical spells. Some players at the time theorized that the under 10% hitpoints immunity thing was to prevent corpses from poofing, but I don't know why Sony added that.


Belly Casters

"Belly caster" NPCs are NPCs that require the player to be inside of melee range in order to land spells on them, otherwise the spells will always fully resist. The check for this flag was not found in the resist function.

Some people may ask why this exists at all. As somebody who was actively raiding in 1999, I can give the likely explanation. Sony NPCs could get stuck on the zone geometry, and large NPCs could get stuck in doorways. Nagafen and Vox in particular used to often get suck in the doors. While these NPCs could still summon, this allowed players to avoid much of the melee damage. The summon timer is 11 seconds for NPCs under level 66 and players' pets would distract the dragons as well, preventing summons. This seems the most likely reason.

I suspect that belly caster checks were done by race like stun immunity is.


Green NPC Caster Partial Resist Modifier

Prathun's pseudocode includes this:

Code:
     If caster is an NPC... 
          If target is at least 20 levels higher than caster, add (level difference * 1.5) to partial resist modifier.
That was not found in the decompiles, but the neighboring modifiers were. Mid-Luclin seems the most likely date for this:

Quote:
Originally Posted by June 12 2002 Patch Note
- NPCs that are much lower level than their target have had their
potential casting damage reduced using a similar mechanic to the one
that player characters’ level differences use

Files, Links and Notes

I uploaded most of the examined decompiled functions in the form of text files to my Google Drive, which can be found here: https://drive.google.com/drive/folde...PV?usp=sharing

I have a large text file full of research notes (mostly old player comments) which is here: https://drive.google.com/file/d/19SQ...usp=drive_link

My large NPC statistics spreadsheet which includes parsed NPC resist values from Live servers from 2014 onward: https://docs.google.com/spreadsheets...usp=drive_link

My saved webpage archive which includes old Sony developer posts and informative player comments:
https://drive.google.com/drive/folde...usp=drive_link

Kicnlag found the tipa16384 archive on Github which was a very rare and valuable source of pre Sept 4 2002 logs:
https://github.com/tipa16384/harcour.../tree/main/DSR

An Internet Archive link which has the old client binaries we decompiled with Ghidra:
https://web.archive.org/web/*/everqu...wnload/patch/*

Internet Archive also has EverQuest ISOs which are easy to find.

My simulation scripts can be found here:
https://drive.google.com/drive/folde...usp=drive_link

Using Ghidra isn't difficult and I would encourage anybody with technical skill who is interested enough to download that and use it. Interpreting the output is the hard part. We certainly did not come close to finding everything interesting.

I also encourage people to download and store local copies of my work, because I know better than most that the Internet is not forever and links often break. Some Internet Archive pages have been lost already. Redundancy is the only way to preserve digital information.
Reply With Quote
  #5  
Old 06-05-2025, 04:20 AM
Torven
Sarnak
 
Join Date: Aug 2014
Posts: 76
Default

I will paste my translations of several resist function decompiles here for convenience. These and more are found on my public Google Drive.

Code:
// CheckResistSpell() from February 16 1999 client. (original launch CD)  Decompiled with Ghidra
// Note that many of the subclass references and NULL checks were removed for readability
// Also note that the compiler reuses variables.  I have renamed some variables with multiple names for readability
// A return of 100 means full resist.  0 means full hit

// param_1 = spell_data
// param_2 = caster level
// param_3 = no_resist_floor
// param_4 = caster_object
// param_5 = spell ID
// this + 0x38 = level
// uVar5 = roll
// uVar6 = final_resist
// sVar7 = level_diff
// 0x94 = IsNPC()
// iVar10 = start_resist


uint __thiscall
FUN_00416fd0(int this, int spell_data, byte caster_level, int no_resist_floor, undefined4 caster_object, int spell_id)
{
  char cVar1;
  byte level;
  int iVar3;
  int iVar4;
  uint roll;
  uint final_resist;
  short level_diff;
  int iVar8;
  int iVar9;
  int start_resist;
  int local_4;
  
  level = caster_level;
  start_resist = 0;
  level_diff = this->GetLevel() - caster_level;
  temp_resist = 0;
  local_4 = -1;
  if (spell_data == nullptr) {
    return 100;
  }
  iVar8 = -1;
  if (*(int *)(this + 0xa0) == nullptr) {
    return 100;
  }
  do {
    if (((iVar8 != -1) ||
        ((iVar3 = FUN_00416f50(spell_data,start_resist), iVar9 = start_resist, iVar3 == 0 && (iVar9 = iVar8, false)
         ))) && (iVar4 = FUN_00416f50(spell_data,start_resist), iVar9 = iVar8, iVar3 = start_resist, iVar4 != 0))
    break;
    start_resist = start_resist + 1;		// compiler optimizer reusing variables; this is basically 'i' for this loop
    iVar8 = iVar9;
    iVar3 = local_4;				// -1
  } while (start_resist < 4);		// only 4 spell slots max in this era
  local_4 = iVar3;
  if (iVar9 == -1) {
    return 100;			// no valid spell slots
  }
  switch(*(undefined *)(spell_data + 0x1f9)) {		// spell resist type
  case SPELL_RESIST_TYPE_1_MAGIC:
    start_resist = GetMR(level);	// attacker's level; needed to modify resist by level difference
    break;
  case SPELL_RESIST_TYPE_2_FIRE:
    start_resist = GetFR(level);
    break;
  case SPELL_RESIST_TYPE_3_COLD:
    start_resist = GetCR(level);
    break;
  case SPELL_RESIST_TYPE_4_POISON:
    start_resist = GetPR(level);
    break;
  case SPELL_RESIST_TYPE_5_DISEASE:
    start_resist = GetDR(level);
    break;
  default:
    goto LAB_LAND_SPELL;
  }
  iVar8 = *(int *)(this + 0xa0);	// used in IsNPC() lines that I removed for readability
  if (!IsNPC()) {
    if (start_resist > 99) {
      start_resist = 99;		// players capped at 99
    }
  }
  else if (start_resist > 150) {
    return 100; // guaranteed full resist if resist > 150
  }
  temp_resist = temp_resist + start_resist;
  if (true) {
    switch(*(undefined *)(iVar9 + 0x1fa + spell_data)) {		// first spell slot effect
    case SE_0_CURRENTHP:
    case SE_79_CURRENT_HP_ONCE:
      if (IsNPC() && level_diff > 0 && this->GetLevel() > 16) {
        temp_resist = temp_resist + level_diff * 2;
      }
      break;
    case SE_22_CHARM:
      ModResistCHA(&temp_resist,caster_object);
    case SE_3_MOVEMENTSPEED:
    case SE_20_BLIND:
    case SE_23_FEAR:
      if (no_resist_floor == 0 && temp_resist < 5) {
        temp_resist = 5;
      }
      break;
    case SE_31_MEZ:
    case SE_34_CONFUSE:
    case SE_63_BLUR:
      ModResistCHA(&temp_resist,caster_object);
      break;
    case SE_44_LYCANTHROPY:
      temp_resist = temp_resist + 30;
    }
  }
  
  // NPC target resist floors
  if (IsNPC()) {
    if (level_diff > -11 && this->GetLevel() > 14 && temp_resist < 10) {
      temp_resist = 10;
    }
    if ((level_diff < -20) || this->GetLevel() < 15)) {
      if (temp_resist < 2) {
        temp_resist = 2;
      }
    }
    else if (temp_resist < 5) {
      temp_resist = 5;
    }
  }
  else if (temp_resist < 1) {
    temp_resist = 1;				// player targets floor at 1
  }
  if (temp_resist > 100) {
    temp_resist = 100;				// resist capped at 100 here for everything
  }
  final_resist = temp_resist;
  roll = RandNum(0,100);			// roll a number from 0 to 99 inclusive
  if (roll < 99 && roll <= final_resist)
  {
    if (local_4 != -1 &&	// -1 means there is only one valid spell effect
       (cVar1 = *(char *)(iVar9 + 0x1fa + spell_data), cVar1 == SE_0_CURRENTHP || cVar1 == SE_79_CURRENT_HP_ONCE))
	{
      return 100;	// if there are multiple effects in the spell, and the first effect is damage, then full resist
    }
    cVar1 = *(char *)(iVar9 + 0x1fa + spell_data);						// get first spell effect
    if (cVar1 == SE_0_CURRENTHP || cVar1 == SE_79_CURRENT_HP_ONCE)		// only pure DD spells may partial hit
	{
	  // calculate possible partial damage; still may full resist.  Only single effect spells with SE 0 or 79 end up here
	
      final_resist = (final_resist * 100 + roll * -100) / final_resist;
      if (IsNPC())
	  {
        if (level_diff > 0 && this->GetLevel() > 16) {
          final_resist = final_resist + 5;				// yellows and reds resist more
        }
        level = this->GetLevel();		// target's level
        if (level > 29) {
          final_resist = (final_resist - 25) + level;	// level 30+ mobs resist more; the higher the level the more it resists
        }
        if (level < 15) {
          final_resist = final_resist - 5;				// newbie mobs resist less
        }
      }
      if (spell_id == SPELL_88_HARM_TOUCH) {
        final_resist = final_resist - 15;
      }
      if (final_resist < 0) {
        final_resist = 0;
      }
      if (final_resist < 101) {
        return final_resist;
      }
    }
    return 100;		// 100 means zero damage/full resist
  }
LAB_LAND_SPELL:
  return 0;		// 0 means full damage
}
Reply With Quote
  #6  
Old 06-05-2025, 04:23 AM
Torven
Sarnak
 
Join Date: Aug 2014
Posts: 76
Default

Code:
// CheckResistSpell() from March 21 2000 client. (a month before Kunark launch)  Decompiled with Ghidra
// Note that many of the subclass references and NULL checks were removed for readability
// Also note that the compiler reuses variables.  I have renamed some variables with multiple names for readability
// A return of 100 means full resist.  0 means full hit

uint __fastcall
FUN_0041a9a0(int param_1_00, undefined4 param_2_00, int spell_data, byte caster_level, int param_3, int caster, int spell_id)
{
  char cVar1;
  byte bVar2;
  undefined4 in_EAX;
  int iVar3;
  int iVar4;
  uint roll_result;
  uint charm_level_limit;
  byte bVarLevel;
  int iVar8;
  int iVar9;
  int iVar10;
  short sVar11;
  uint temp_resist;
  int local_10;
  int local_c;
  int level_diff;
  int local_4;
  
  level_diff = CONCAT22((short)((uint)in_EAX >> 0x10),GetLevel()) -
            (CONCAT31((int3)((uint)param_2_00 >> 8),caster_level) & 0xffff00ff);
  iVar10 = 0;
  iVar8 = -1;
  temp_resist = 0;
  local_c = 0xffffffff;
  local_4 = -1;
  if (spell_id == SPELL_982_CAZIC_TOUCH) {
    return 0;
  }
  if (spell_data == 0) {
    return 100;
  }
  if (*(int *)(param_1_00 + 0xa0) == 0) {
    return 100;
  }
  local_10 = param_1_00;
  bVarLevel = caster_level;
  if ((caster != 0) && (*(int *)(caster + 0xf0) == 27)) {		// probably body type.  27 is elementals
    caster_level = caster_level + 5;
    bVarLevel = caster_level;
  }
  do {
    if (((iVar8 != -1) ||
        ((iVar3 = FUN_0041a8d0(spell_data,iVar10), iVar9 = iVar10, iVar3 == 0 && (iVar9 = iVar8, false)
         ))) && (iVar4 = FUN_0041a8d0(spell_data,iVar10), iVar9 = iVar8, iVar3 = iVar10, iVar4 != 0))
    break;
    iVar10 = iVar10 + 1;
    iVar8 = iVar9;
    iVar3 = local_4;
  } while (iVar10 < 4);
  local_4 = iVar3;
  if (iVar9 == -1) {
    return 100;
  }
  local_c = iVar9;
  switch(*(undefined *)(spell_data + 0x1f9)) {
  case SPELL_RESIST_TYPE_1_MAGIC:
    iVar8 = FUN_004155e0(caster_level);
    if (((bVarLevel > 34) && (caster != 0)) && caster->GetClass() == WIZARD)) {
      if (*(char *)(spell_data + 0x1fa) == SPELL_EFFECT_TYPE_0_HP) goto LAB_0041ab3e;
      if (*(char *)(spell_data + 0x1fa) == SPELL_EFFECT_TYPE_79_INSTANTHP) {
        iVar8 = iVar8 + -10;
      }
    }
    break;
  case SPELL_RESIST_TYPE_2_FIRE:
    iVar8 = FUN_004156d0(caster_level);
    if (((bVarLevel > 34) && (caster != 0)) && caster->GetClass() == WIZARD)) {
      iVar8 = iVar8 + -10;
    }
    break;
  case SPELL_RESIST_TYPE_3_COLD:
    iVar8 = FUN_004157d0(caster_level);
    if (((bVarLevel > 34) && (caster != 0)) && caster->GetClass() == WIZARD)) {
LAB_0041ab3e:
      iVar8 = iVar8 + -10;
    }
    break;
  case SPELL_RESIST_TYPE_4_POISON:
    iVar8 = FUN_00415960(caster_level);
    break;
  case SPELL_RESIST_TYPE_5_DISEASE:
    iVar8 = FUN_004158a0(caster_level);
    break;
  default:
    goto LAB_LAND_SPELL;
  }
  cVar1 = *(char *)(iVar9 + 0x1fa + spell_data);
  if (cVar1 == SE_23_FEAR && IsNPC() && GetLevel() > 52)
  {
    return 100;
  }
  if (cVar1 == SE_22_CHARM && caster != 0 && !caster->IsNPC())
  {
    bVar2 = *(byte *)(spell_data + 0x204 + (uint)*(byte *)(caster + 0x95));		// level the caster gets this charm spell
    switch(GetRace()) {
    case RACE_44_FREEPORT_GUARD:
    case RACE_67_HIGHPASS_CITIZEN:
    case RACE_71_QEYNOS_CITIZEN:
    case RACE_77_NERIAK_CITIZEN:
    case RACE_78_ERUDITE_CITIZEN:
    case RACE_81_RIVERVALE_CITIZEN:
    case RACE_88_CLOCKWORK_GNOME:
    case RACE_90_HALAS_CITIIZEN:
    case RACE_92_GROB_CITIZEN:
    case RACE_93_OGGOK_CITIZEN:
    case RACE_94_KALADIM_CITIZEN:
    case RACE_106_FELGUARD:
    case RACE_112_FAYGUARD:
    case RACE_139_IKSAR_CITIZEN:
      goto LAB_RESIST_SPELL;			// guards are immune to charm casted by players
    case 0x2d:	// 45
    case 0x2e:
    case 0x2f:
    case 0x30:
    case 0x31:
    case 0x32:
    case 0x33:
    case 0x34:
    case 0x35:
    case 0x36:
    case 0x37:
    case 0x38:
    case 0x39:
    case 0x3a:
    case 0x3b:
    case 0x3c:
    case 0x3d:
    case 0x3e:
    case 0x3f:
    case 0x40:
    case 0x41:
    case 0x42:
    case 0x44:
    case 0x45:
    case 0x46:
    case 0x48:
    case 0x49:
    case 0x4a:
    case 0x4b:
    case 0x4c:
    case 0x4f:
    case 0x50:
    case 0x52:
    case 0x53:
    case 0x54:
    case 0x55:
    case 0x56:
    case 0x57:
    case 0x59:
    case 0x5b:
    case 0x5f:
    case 0x60:
    case 0x61:
    case 0x62:
    case 99:
    case 100:
    case 0x65:
    case 0x66:
    case 0x67:
    case 0x68:
    case 0x69:
    case 0x6b:
    case 0x6c:
    case 0x6d:
    case 0x6e:
    case 0x6f:
    case 0x71:
    case 0x72:
    case 0x73:
    case 0x74:
    case 0x75:
    case 0x76:
    case 0x77:
    case 0x78:
    case 0x79:
    case 0x7a:
    case 0x7b:
    case 0x7c:
    case 0x7d:
    case 0x7e:
    case 0x7f:
    case 0x80:
    case 0x81:
    case 0x82:
    case 0x83:
    case 0x84:
    case 0x85:
    case 0x86:
    case 0x87:
    case 0x88:
    case 0x89:
    case 0x8a:	// race 138 Yeti;  highest Kunark race is 167 though?
      bVarLevel = caster_level;		// this doesn't appear to do anything.  maybe vestigial
    }
    if (bVar2 > 50) {
      bVar2 = caster->GetLevel() / 2;
    }
    charm_level_limit = bVar2;
    if (caster->GetClass() != ENCHANTER) {
      bVar2 = *(byte *)(spell_data + 0x212);		// level enchanter gets the spell
      if (bVar2 != 0 && bVar2 < 51) {
        charm_level_limit = bVar2;
      }
    }
    charm_level_limit = (charm_level_limit * 15) / 10;
    if (charm_level_limit < 24) {
      charm_level_limit = 24;
    }
    if (spell_id < 183) {
      if (spell_id == SPELL_182_BEGUILE) {
LAB_0041acde:
        charm_level_limit = 37;
      }
      else if (spell_id == SPELL_141_BEGUILE_ANIMALS) {
        charm_level_limit = 43;
      }
      else if (spell_id == SPELL_142_ALLURE_OF_THE_WILD) {
        charm_level_limit = 51;
      }
    }
    else if (spell_id < 197) {
      if (spell_id == SPELL_196_DOMINATE_UNDEAD) {
        charm_level_limit = 32;
      }
      else {
        iVar10 = spell_id - 183;
LAB_0041acb4:
        if (iVar10 == 0) {				// Cajoling Whispers is spell ID 183;  Beguile Undead is 197
          charm_level_limit = 46;
        }
        else if (iVar10 == 1) {			// Allure is spell ID 184;  Cajole Undead is 198
          charm_level_limit = 51;
        }
      }
    }
    else if (spell_id < 261) {
      if (spell_id != SPELL_260_CHARM_ANIMALS) {
        iVar10 = spell_id - 197;
        goto LAB_0041acb4;
      }
      charm_level_limit = 33;
    }
    else if (spell_id < 1554) {
      if (spell_id == SPELL_1553_CALL_OF_KARANA) {
switchD_0041acf9_caseD_658:
        charm_level_limit = 51;
      }
      else if (spell_id == SPELL_725_SOLONS_SONG_OF_THE_SIRENS) goto LAB_0041acde;
    }
    else if (true) {
      switch(spell_id) {
      case SPELL_1556_TUNARES_REQUEST:
        charm_level_limit = 35;
        break;
      case SPELL_1624_THRALL_OF_BONES:
      case SPELL_1705_BOLTRANS_AGACERIE:
        goto switchD_0041acf9_caseD_658;
      case SPELL_1629_ENSLAVE_DEATH:
      case SPELL_1707_DICTATE:
        charm_level_limit = 52;
      }
    }
    if (charm_level_limit > 51) {
      charm_level_limit = 51;
    }
    iVar9 = local_c;
    if (charm_level_limit < GetLevel()) {
      if (*(int *)(caster + 0x74) != 0) {	// presumably some kind of IsClient() subclass check
        FUN_004ca2a0(*(int *)(caster + 0x74),s_Target_Too_High_level_for_your_c_005a8b14,0xd);
        return 100;
      }
      return 100;
    }
  }
  
  if (!IsNPC()) {
    if (iVar8 > 99) {
      iVar8 = 99;
    }
  }
  else if (iVar8 > 150) {
    return 100;
  }
  
  if (spell_id == SPELL_127_INVOKE_FEAR || spell_id == SPELL_59_PANIC_THE_DEAD || spell_id == SPELL_514_TERRORIZE_ANIMAL)
  {
    if (bVarLevel < GetLevel()) {
      iVar10 = -5;
    }
    else {
      iVar10 = -(uint)(caster_level >> 1);
    }
    iVar8 = iVar8 + iVar10;
    if (iVar8 < 5) {
      iVar8 = 5;
    }
  }
  
  if (spell_id == SPELL_1527_TREPIDATION || spell_id == SPELL_1550_REPULSE_ANIMAL || spell_id == SPELL_1532_DREAD_OF_NIGHT)
  {
    if (bVarLevel < GetLevel()) {
      iVar10 = -15;
    }
    else {
      iVar10 = -5 - caster_level / 2;
    }
    iVar8 = iVar8 + iVar10;
    if (iVar8 < 5) {
      iVar8 = 5;
    }
  }
  
  sVar11 = (short)iVar8;
  if (spell_id == SPELL_1544_ENFORCED_REVERENCE || spell_id == SPELL_1553_CALL_OF_KARANA || spell_id == SPELL_1624_THRALL_OF_BONES 
	|| spell_id == SPELL_1690_FASCINATION || spell_id == SPELL_1691_GLAMOUR_OF_KINTAZ || spell_id == SPELL_1705_BOLTRANS_AGACERIE)
  {
    sVar11 = sVar11 - 10;
  }
  
  iVar8 = IsUnresistableSpell(spell_data,spell_id);
  if (iVar8 == 0 && *(char *)(spell_data + 0x1fe) != 13) {		// spell type 13 (lifetap) will be false here and land
    temp_resist = temp_resist + (int)sVar11;
    if (true) {
      switch(*(undefined *)(iVar9 + 0x1fa + spell_data)) {
      case SE_0_CURRENTHP:
      case SE_79_CURRENT_HP_ONCE:
        if (IsNPC() && level_diff > 0 && GetLevel() > 16) {
          temp_resist = temp_resist + level_diff * 2;
        }
        break;
      case SE_22_CHARM:
        CharismaMod(&temp_resist,caster);
      case SE_3_MOVEMENTSPEED:
      case SE_20_BLIND:
      case SE_23_FEAR:
        if (param_3 == 0 && temp_resist < 5) {
          temp_resist = 5;
        }
        break;
      case SE_31_MEZ:
      case SE_34_CONFUSE:
      case SE_63_BLUR:
        CharismaMod(&temp_resist,caster);
        break;
      case SE_44_LYCANTHROPY:
        temp_resist = temp_resist + 0x1e;
      }
    }
    iVar8 = local_10;
    if (spell_id == SPELL_676_TASHAN || spell_id == SPELL_677_TASHANI || spell_id == SPELL_678_TASHANIA) {
      temp_resist = temp_resist * 2 / 3;
    }
	
	// resist floors
    if (IsNPC()) {
      if (level_diff > -11 && GetLevel() > 14 && (temp_resist < 10)) {
        temp_resist = 10;
      }
      if (level_diff < -20 || GetLevel() < 15) {
        if (temp_resist < 2) {
          temp_resist = 2;
        }
      }
      else if (temp_resist < 5) {
        temp_resist = 5;
      }
    }
    else if (temp_resist < 1) {
      temp_resist = 1;
    }
    if (temp_resist > 100) {
      temp_resist = 100;
    }
	
    iVar10 = IsLullSpell(spell_id);
    if (iVar10 != 0) {
      if (spell_id == SPELL_728_KELINS_LUGUBRIOUS_LAMENT) {
        temp_resist = temp_resist / 2;
        if (temp_resist < 5) {
          temp_resist = 5;
        }
        effective_resist = 5;			// note: the decompile code reuses the charm_level_limit variable for this
      }									// but I renamed it starting from here for readability
      else {
        bVarLevel = GetLevel();
        if (bVarLevel < 15) {
          effective_resist = 10;
        }
        else if (bVarLevel < 25) {
          effective_resist = 20;
        }
        else if (bVarLevel < 35) {
          effective_resist = 33;
        }
        else if (bVarLevel < 40) {
          effective_resist = 42;
        }
        else {
          effective_resist = (-(uint)(bVarLevel < 50) & 0xffffffd2) + 100;
        }
      }
      if (temp_resist < effective_resist) {
        temp_resist = effective_resist;
      }
    }
	
    if (caster != nullptr && caster->IsNPC()) &&
       (caster->GetClass() == ENCHANTER || caster->GetClass() == CLASS_30_ENCHANTER_GM) &&
       caster->GetLevel() > 24)
	{
      effective_resist = GetLevel();
      if (effective_resist < temp_resist) {
        temp_resist = effective_resist;
      }
      if (temp_resist > 40) {
        temp_resist = 40;
      }
    }
    effective_resist = temp_resist;
    roll_result = Roll0(0,100);
	
    if (roll_result < 99 && roll_result <= effective_resist)
	{
      if ((local_4 == -1 || // -1 means there is only one valid spell effect
          (((cVar1 = *(char *)(iVar9 + 0x1fa + spell_data), cVar1 != SE_0_CURRENTHP && (cVar1 != SE_79_CURRENT_HP_ONCE)) ||
           (*(byte *)(spell_data + 0x212) < 13)))) || (*(char *)(local_4 + 0x1fa + spell_data) == SE_27_CANCELMAGIC)
         )
	  {
        cVar1 = *(char *)(iVar9 + 0x1fa + spell_data);		// first spell effect
        if (cVar1 != SE_0_CURRENTHP && cVar1 != SE_79_CURRENT_HP_ONCE) {
          return 100;
        }
        effective_resist = (effective_resist * 100 + roll_result * -100) / effective_resist;
        if (IsNPC()) {
          if (level_diff > 0 && GetLevel() > 16) {
            effective_resist = effective_resist + 5;
          }
          bVarLevel = GetLevel();
          if (bVarLevel > 29) {
            effective_resist = (effective_resist - 25) + bVarLevel;
          }
          if (bVarLevel < 15) {
            effective_resist = effective_resist - 5;
          }
        }
        if (spell_id == SPELL_88_HARM_TOUCH) {
          effective_resist = effective_resist - 45;
        }
        if (effective_resist < 0) {
          effective_resist = 0;
        }
        if (effective_resist > 100) {
          return 100;
        }
        return effective_resist;
      }
LAB_RESIST_SPELL:
      return 100;
    }
  }
LAB_LAND_SPELL:
  return 0;
}
Reply With Quote
  #7  
Old 06-05-2025, 04:25 AM
Torven
Sarnak
 
Join Date: Aug 2014
Posts: 76
Default

Code:
// CheckResistSpell() from November 29 2000 client.  Decompiled with Ghidra
// Note that many of the subclass references and NULL checks were removed for readability
// Also note that the compiler reuses variables.  I have renamed some variables with multiple names for readability
// A return of 100 means full resist.  0 means full hit

uint __thiscall CheckResistSpell(int this,int spell,byte caster_level,int no_resist_floor,int caster,int spell_id)
{
  byte *first_effect;
  char cVar2;
  ushort race;
  char *spell_slot;
  int iVar5;		// renamed to roll in some places
  int iVar6;
  uint roll;
  short sVar8;
  int iVar9;		// renamed to success_pct, slot and resist_pct depending on use
  bool bVar10;
  byte bVar11;		// renamed to this_level, charm_limit and spell_effect depending on use
  uint resist;
  int local_1c;
  int local_18;
  short level_diff;
  int temp_resist;
  uint final_resist;
  
  final_resist = 0;
  this_level = this->GetLevel();
  local_1c = -1;
  local_18 = -1;
  level_diff = this_level - caster_level;
  if (spell_id == 982) {	// Cazic Touch
    return 0;
  }
  if (spell == 0) {
    return 100;
  }
  if ((*(int *)(caster + 0xf4) == 27)) {	// we think this is body type but not 100% sure.  body type 27 is elementals, so this may be for pets
    caster_level = caster_level + 5;
  }
  
  // Guaranteed resist if spell is a fear and target has discipline 31 active.  This was added in the Nov 15 2000 client
  iVar9 = *(int *)(*(int *)(this + 0xa0) + 0x70);
  if (iVar9 != 0 && *(int *)(iVar9 + 0x2a4) == 31) {	// Fearless disc
    spell_slot = (char *)(spell + 0x1fa);
    do {
      if (*spell_slot != -2 && *spell_slot == SE_23_FEAR) {	// SE -2 seems to denote blank but SE_10_CHARISMA with value 0 is also used to denote blank
        return 100;
      }
      spell_slot = spell_slot + 1;
    } while ((int)(spell_slot + (-0x1fa - spell)) < 4);	// check 4 slots; return 100 if fear is one of the SEs
  }
  
  // Check for mez immunity.  This was moved here from HitBySpell() in the Nov 15 2000 client
  if (caster != NULL) {
    spell_slot = (char *)(spell + 0x1fa);	// first slot
    do {
      if (*spell_slot != -2 && *spell_slot == SE_31_MEZ) {		// -2 means blank
        if (caster->GetClass() == CLASS_8_BARD) {
          if (spell->type_number == 5) {						// single target
            if (spell_id == SPELL_1753_SONG_OF_TWILIGHT) {
              success_pct = 101;
            }
            else if (this_level < 25) {
              success_pct = 95;
            }
            else if (this_level < 30) {
              success_pct = 90;
            }
            else if (this_level < 40) {
              success_pct = 85;
            }
            else if (this_level < 45) {
              success_pct = 75;
            }
            else {
              success_pct = (-(uint)(this_level < 50) & 25) + 40;		// if level < 50 then value is 65, else value is 40
            }
          }
		  // AoE Mez
          else if (this_level < 25) {
            success_pct = 66;
          }
          else if (this_level < 30) {
            success_pct = 50;
          }
          else if (this_level < 40) {
            success_pct = 33;
          }
          else {
            success_pct = (-(uint)(this_level < 45) & 4) + 18;		// if level < 45 then value is 22, else value is 18
          }
          roll = Roll0(0,99);
          if (success_pct <= roll) {
            return 100;
          }
        }
		
		// HitBySpell() still does something like this, oddly, which would theoretically result in duplicate messages.  it does the StunMe() call
        DAT_005a82f4 = 0;
        if (spell_id == SPELL_1692_RAPTURE) {
          DAT_005a82f4 = 1;
        }
        else if (spell_id == SPELL_1691_GLAMOUR_OF_KINTAZ) {
          DAT_005a82f4 = 2;
        }
        iVar9 = IsStunImmune(this);
        if (iVar9 != 0 || (cVar2 = this->GetBodyType(), cVar2 == BODY_29_DRAGON2) || cVar2 == BODY_4_GIANT || cVar2 == BODY_26_DRAGON)
		{
          if (caster->IsNPC() == 0 && caster->IsClient()) {
            if (DAT_005a82f4 == 1) {
              spell_slot = "Your target cannot be mesmerized.";
            }
            else {
              spell_slot = "Your target cannot be mesmerized (with this spell)."
            }
            StringToClient(*(int *)(caster + 0x74),spell_slot,0x121);
          }
          DAT_005a82f4 = 0;
          return 100;
        }
        DAT_005a82f4 = 0;
        break;
      }
      spell_slot = spell_slot + 1;
    } while ((int)(spell_slot + (-0x1fa - spell)) < 4);
  }
  
  // Check for NPC fear immunity and resist all spells under Sancitification disc
  iVar9 = *(int *)(this + 0xa0);
  if (iVar9 != NULL) {
    if (this->IsNPC() == 1 && (*(char *)(this + 0x9eb) == 1)) {		// seems to be some kind of NPC fear immunity flag?
      spell_slot = (char *)(spell + 0x1fa);
      do {
        if (*spell_slot != -2 && *spell_slot == SE_23_FEAR) {
          return 100;
        }
        spell_slot = spell_slot + 1;
      } while ((int)(spell_slot + (-0x1fa - spell)) < 4);
    }
    if (iVar9 != NULL && *(int *)(iVar9 + 0x70) != NULL &&
       *(int *)(*(int *)(iVar9 + 0x70) + 0x2a4) == 23 &&		// Sanctification discipline
       spell->resist_type != 0 && spell->resist_type < 6)		// checking to make sure spell is MR FR CR PR DR
	{
      return 100;
    }
  }
  
  // check for valid spell effects in the first four spell slots (this era is probably limited to 4)
  slot = 0;
  do {
    if (local_1c == -1) {
      iVar5 = IsValidSpellSlot(spell,slot);
      if (iVar5 != 0) {
        local_1c = slot;
      }
    }
    else {
      iVar6 = IsValidSpellSlot(spell,slot);
      iVar5 = slot;
      if (iVar6 != 0) break;		// exit loop as soon as we have found two valid spell slots
    }
    slot = slot + 1;
    iVar5 = -1;				// I changed local_18 to -1 here for readability because local_18 is always -1 here
  } while (slot < 4);
  local_18 = iVar5;			// if iVar5 is -1 here that means the spell has only one effect in it
  if (local_1c == -1) {		// resist spell if no valid spell effects found
    return 100;
  }
  
  cVar2 = spell->resist_type;
  if (cVar2 == SPELL_TYPE_1_MAGIC) {
    temp_resist = GetMR(caster_level);
    if (34 < caster_level && caster->GetClass() == CLASS_12_WIZARD) {	// wizard class resist modifier; added January 20 2000
      if (*(char *)(spell + 0x1fa) != SE_0_HP) {
        bVar10 = *(char *)(spell + 0x1fa) == SE_79_INSTANTHP;		// these are checking the first slot for DD effects
        goto LAB_WIZ_CONTINUE;
      }
LAB_WIZ_DO_REDUCTION:
      temp_resist = temp_resist - 10;
    }
  }
  else {
    if (cVar2 == SPELL_TYPE_2_FIRE) {
      temp_resist = GetFR(caster_level);
    }
    else {
      if (cVar2 != SPELL_TYPE_3_COLD) {
        if (cVar2 == SPELL_TYPE_4_POISON) {
          temp_resist = GetPR(caster_level);
        }
        else {
          if (cVar2 != SPELL_TYPE_5_DISEASE) {
            return 0;							// if type is not 1-5 then spell just lands
          }
          temp_resist = GetDR(caster_level);
        }
        goto LAB_CHECK_LURE;
      }
      temp_resist = GetCR(caster_level);
    }
    if (34 < caster_level) {
      bVar10 = caster->GetClass() == CLASS_12_WIZARD;
LAB_WIZ_CONTINUE:
      if (bVar10) goto LAB_WIZ_DO_REDUCTION;
    }
  }

LAB_CHECK_LURE:
  iVar9 = IsStrongLureSpell(spell,spell_id);		// CHA -5 and -6
  if (iVar9 != 0) {
    if (temp_resist < 100) {
      return 0;
    }
    resist_pct = (temp_resist + -100) / 2 + 5;
    if (45 < resist_pct) {
      resist_pct = 45;
    }
LAB_DO_LURE_ROLL:
    roll = Roll0(1,100);
    return (resist_pct <= roll) - 1 & 100;		// if resist_pct >= roll then return 0 else 100
  }
  iVar9 = IsWeakerLureSpell(spell,spell_id);		// CHA -4
  if (iVar9 != 0) {
    if (temp_resist < 50) {
      return 0;
    }
    resist_pct = (temp_resist + -50) / 2 + 5;
    if (75 < resist_pct) {
      resist_pct = 75;
    }
    goto LAB_DO_LURE_ROLL;
  }
  iVar9 = IsWeakestLureSpell(spell,spell_id);		// CHA -3
  if (iVar9 != 0) {
    if (temp_resist < 25) {
      return 0;
    }
    resist_pct = (temp_resist + -25) / 2 + 5;
    if (85 < resist_pct) {
      resist_pct = 85;
    }
    goto LAB_DO_LURE_ROLL;
  }
  
  first_effect = (byte *)(local_1c + 0x1fa + spell);
  if (*first_effect == SE_23_FEAR && this->IsNPC() == true && this->GetLevel() > 52)
  {
    return 100;
  }
  
  if (*first_effect == SE_22_CHARM && caster->IsNPC() == false)
  {
    if (this->GetClass() == CLASS_15_MERCHANT) {		// 15 is beastlord in newer clients but back then was merchants
      return 100;
    }
    if (this->GetBodyType() == BODY_6_EXTRAPLANAR) {
      return 100;
    }
    race = *(ushort *)(this + 0x34);
    if (race < 89) {
      if (race == RACE_88_CLOCKWORK_GNOME) {
        return 100;
      }
      if (race == RACE_44_FREEPORT_GUARD) {
        return 100;
      }
      if (race == RACE_67_HIGHPASS_CITIZEN) {
        return 100;
      }
      if (race == RACE_71_QEYNOS_CITIZEN) {
        return 100;
      }
      if (76 < race) {
        if (race < 79) {
          return 100;
        }
        bVar10 = race == RACE_81_RIVERVALE_CITIZEN;
LAB_CHECK_RACE:
        if (bVar10) {
          return 100;
        }
      }
    }
    else {
      if (race == RACE_90_HALAS_CITIIZEN) {
        return 100;
      }
      if (91 < race) {
        if (race < 95) {
          return 100;
        }
        if (race == RACE_106_FELGUARD) {
          return 100;
        }
        if (race == RACE_112_FAYGUARD) {
          return 100;
        }
        bVar10 = race == RACE_139_IKSAR_CITIZEN;
        goto LAB_CHECK_RACE;
      }
    }
    if (spell_id < 726) {
      if (spell_id == SPELL_725_SOLONS_SONG_OF_THE_SIRENS) {
LAB_SET_CHARM_LIMIT_37:
        charm_limit = 37;
      }
      else {
        if (spell_id < SPELL_185_TEPID_DEEDS) {
          if (spell_id != SPELL_184_ALLURE) {
            if (spell_id == SPELL_141_BEGUILE_ANIMALS) {
              charm_limit = 43;
              goto LAB_CHECK_CHARM_LIMIT;
            }
            if (spell_id != SPELL_142_ALLURE_OF_THE_WILD) {
              if (spell_id != SPELL_182_BEGUILE) {
                if (spell_id != SPELL_183_CAJOLING_WHISPERS) goto LAB_SET_CHARM_LIMIT_25;
                goto LAB_SET_CHARM_LIMIT_46;
              }
              goto LAB_SET_CHARM_LIMIT_37;
            }
          }
          goto LAB_SET_CHARM_LIMIT_51;
        }
        if (spell_id == SPELL_196_DOMINATE_UNDEAD) {
          charm_limit = 32;
        }
        else if (spell_id == SPELL_197_BEGUILE_UNDEAD) {
LAB_SET_CHARM_LIMIT_46:
          charm_limit = 46;
        }
        else {
          if (spell_id == SPELL_198_CAJOLE_UNDEAD) goto LAB_SET_CHARM_LIMIT_51;
          if (spell_id != SPELL_260_CHARM_ANIMALS) goto LAB_SET_CHARM_LIMIT_25;
          charm_limit = 33;
        }
      }
    }
    else {
      if (spell_id < 1630) {
        if (spell_id == SPELL_1629_ENSLAVE_DEATH) {
LAB_SET_CHARM_LIMIT_52:
          charm_limit = 52;
          goto LAB_CHECK_CHARM_LIMIT;
        }
        if (spell_id != SPELL_750_SOLONS_BEWITCHING_BRAVURA && spell_id != SPELL_1553_CALL_OF_KARANA) {
          if (spell_id == SPELL_1556_TUNARES_REQUEST) goto LAB_SET_CHARM_LIMIT_35;
          if (spell_id != SPELL_1624_THRALL_OF_BONES) {
LAB_SET_CHARM_LIMIT_25:
            charm_limit = 25;
            goto LAB_CHECK_CHARM_LIMIT;
          }
        }
      }
      else if (spell_id != SPELL_1705_BOLTRANS_AGACERIE) {
        if (spell_id != SPELL_1707_DICTATE) {
          if (spell_id == SPELL_1817_ALLURING_WHISPERS) {
            charm_limit = 49;
            goto LAB_CHECK_CHARM_LIMIT;
          }
          if (spell_id != SPELL_1822_PERSUADE) goto LAB_SET_CHARM_LIMIT_25;
LAB_SET_CHARM_LIMIT_35:
          charm_limit = 35;
          goto LAB_CHECK_CHARM_LIMIT;
        }
        goto LAB_SET_CHARM_LIMIT_52;
      }
LAB_SET_CHARM_LIMIT_51:
      charm_limit = 51;
    }
LAB_CHECK_CHARM_LIMIT:
    if (caster->GetClass() == CLASS_8_BARD && caster->GetLevel() < this->GetLevel() && 37 < caster->GetLevel()) {
      return 100;							// bards prevented from charming yellows and reds
    }
    if (charm_limit < this->GetLevel()) {
      if (*(int *)(caster + 0x74) == 0) {	// checking if we're an NPC
        return 100;
      }
      StringToClient(*(int *)(caster + 0x74),s_Target_Too_High_level_for_your_c_0054f770,0x121);
      return 100;
    }
  }
  
  if (this->IsNPC() == 0) {
    if (temp_resist > 99) {
      temp_resist = 99;		// players/clients are capped at 99 here
    }
  }
  else if (temp_resist > 150) {
    return 100;				// an effective resist of 151+ will make everything resist, including lifetaps
  }
  
  if ( spell_id == SPELL_127_INVOKE_FEAR) || spell_id == SPELL_59_PANIC_THE_DEAD || spell_id == SPELL_514_TERRORIZE_ANIMAL )
  {
    if (caster_level < this->GetLevel()) {
      iVar9 = -5;
    }
    else {
      iVar9 = -(caster_level / 2);
    }
    temp_resist = temp_resist + iVar9;
    if (temp_resist < 5) {
      temp_resist = 5;
    }
  }
  if ( spell_id == SPELL_1527_TREPIDATION || spell_id == SPELL_1550_REPULSE_ANIMAL || spell_id == SPELL_1532_DREAD_OF_NIGHT )
  {
    if (caster_level < this->GetLevel()) {
      iVar9 = -15;
    }
    else {
      iVar9 = -5 - (caster_level / 2);
    }
    temp_resist = temp_resist + iVar9;
    if (temp_resist < 5) {
      temp_resist = 5;			// fear has a resist floor of 5 like charm
    }
  }
  
  sVar8 = temp_resist;
  if ( spell_id == SPELL_1544_ENFORCED_REVERENCE ||
    spell_id == SPELL_1553_CALL_OF_KARANA ||
    spell_id == SPELL_1624_THRALL_OF_BONES ||
    spell_id == SPELL_1690_FASCINATION ||
    spell_id == SPELL_1691_GLAMOUR_OF_KINTAZ ||
    spell_id == SPELL_1705_BOLTRANS_AGACERIE
  ) {
    sVar8 = sVar8 - 10;		// these spells get a resist bonus/reduction
  }
  iVar9 = IsUnresistableSpell(spell,spell_id);	// this returns true for Dictate, Rapture, Enslave Death
  if (iVar9 != 0) {
    return 0;
  }
  if (spell->type_number == 13) {	// type 13 is lifetap.  lifetaps land at this point.  they never did partial damage until Sept 4 2002
    return 0;
  }
  final_resist = final_resist + sVar8;
  spell_effect = *first_effect;
  if (spell_effect < 32) {
    if (spell_effect != SE_31_MEZ) {
      if (spell_effect == SE_0_HP) {
LAB_YELLOW_RED_DD_MOD:
        if (*(int *)(this + 0xa0) == NULL || this->IsNPC() != 1 || level_diff < 1 || this->GetLevel() < 17) goto LAB_TASH_SPELLS;
        resist = final_resist + level_diff * 2;		// this is adding +2 resist value for each level the target is above the player caster
      }
      else {
        if (spell_effect != SE_3_MOVEMENTSPEED && spell_effect != SE_20_BLIND) {
          if (spell_effect == SE_22_CHARM) {
            CharismaMod(&final_resist,caster);		// modify the resist value by the charisma bonus, which is -1 for every 8 CHA above 75
          }
          else if (spell_effect != SE_23_FEAR) goto LAB_TASH_SPELLS;
        }
        if (no_resist_floor != 0 || (resist = 5, 4 < final_resist)) goto LAB_TASH_SPELLS;
      }
      final_resist = resist;	// Charm, Root, Blind, Fear have a resist floor of 5 which is set here but the function has an override parameter
      goto LAB_TASH_SPELLS;
    }
  }
  else if (spell_effect != SE_34_CONFUSE) {
    if (spell_effect == SE_44_LYCANTHROPY) {
      final_resist = final_resist + 30;
      goto LAB_TASH_SPELLS;
    }
    if (spell_effect != SE_63_BLUR) {
      if (spell_effect != SE_79_CURRENT_HP_ONCE) goto LAB_TASH_SPELLS;
      goto LAB_YELLOW_RED_DD_MOD;
    }
  }
  CharismaMod(&final_resist,caster);		// Mez, Confuse and Blur spells call this.  They get the same CHA bonus charm does
  
LAB_TASH_SPELLS:
  if ( spell_id == SPELL_676_TASHAN || spell_id == SPELL_677_TASHANI || spell_id == SPELL_678_TASHANIA ) {
    final_resist = (final_resist * 2) / 3;	// this is weird since these spells are unresistable.  vestigial?
  }
  
  // Level based resist floor
  if (this->IsNPC() == 1) {
    if (level_diff > -11 && this->GetLevel() > 14 && final_resist < 10) {	// ten levels under the caster or higher unless it's a newbie mob
      final_resist = 10;
    }
    if (level_diff < -20 || this->GetLevel() < 15) {		// deep greens and newbie mobs
      resist = 2;
    }
    else {
      resist = 5;		// everything in between
    }
  }
  else {
    resist = 1;		// on players the floor is always 1
  }
  
  if (final_resist < resist) {
    final_resist = resist;
  }
  if (final_resist > 100) {		// this will cap players and NPCs but players were already capped at 99 before this
    final_resist = 100;
  }
  
  iVar9 = IsLullSpell(spell_id);
  if (iVar9 != 0) {
    if (spell_id == SPELL_728_KELINS_LUGUBRIOUS_LAMENT) {		// bard lull was good because it's only 3 ticks I guess
      resist = 5;
      final_resist = final_resist / 2;		// bard lull gets a big resist bonus here
      if (final_resist < 5) {
        final_resist = 5;
      }
    }
    else {					// lull/pacify resist floors; lull spells were bad apparently, except bard
      this_level = this->GetLevel();
      if (this_level < 15) {
        resist = 10;
      }
      else if (this_level < 25) {
        resist = 20;
      }
      else if (this_level < 35) {
        resist = 33;
      }
      else if (this_level < 40) {
        resist = 42;
      }
      else {
        resist = (-(uint)(this_level < 50) & 0xffffffd2) + 100;	// if level < 50 then value = 54 else value is 100.  50+ mobs were immune to non-bard lull
      }
    }
    if (final_resist < resist) {
      final_resist = resist;
    }
  }
  
  // our class list has necro GM as 30.  Sony's Kunark list had enchanter GM as 30
  if (caster->IsNPC() == 1 
    && (caster->GetClass() == CLASS_14_ENCHANTER || caster->GetClass() == CLASS_30_ENCHANTER_GM)
    && caster->GetLevel() > 24)
  {
    if (this->GetLevel() < final_resist) {
      final_resist = this->GetLevel();
    }
    if (final_resist > 40) {
      final_resist = 40;			// enchanter NPCs basically ignored your resists for the most part
    }
  }
  
  resist = final_resist;
  roll = Roll0(0,100);				// the 2nd number is really n - 1, so random range would be 0 to 99
  if (roll > 98 || resist < roll) {	// the attacker is doing the roll; a 99 and attacker auto-wins; roll a 0 or 1 and resist
    return 0;	// full hit
  }
  if (local_18 != -1									// two or more spell effects?
     && (*first_effect == 0 || *first_effect == 79) 			// first slot is DD effect
     && (12 < *(byte *)(spell + 0x212))					// enchanter minimum spell level > 12
     && ((cVar2 = *(char *)(local_18 + 0x1fa + spell), cVar2 != 27 && cVar2 != 92)))	// first effect is not SE 27 and not SE 92 (cancel magic, insta hate)
  {
    return 100;		// full resist
  }
  if (*first_effect == 0 || *first_effect == 79)
  {
    resist = (resist * 100 + roll * -100) / resist;
    if (this->IsNPC())
	{
      if (level_diff > 0 && 16 < this->GetLevel()) {
        resist = resist + 5;
      }
      this_level = this->GetLevel();
      if (this_level > 29) {
        resist = (resist - 25) + this_level;
      }
      if (this_level < 15) {
        resist = resist - 5;
      }
    }
    if (spell_id == 88) {		// Harm Touch
      resist = resist - 45;
    }
    if (resist < 0) {
      resist = 0;
    }
    if (resist < 101) {
      return resist;
    }
  }
  return 100;
}
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 04:50 PM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3