Greetings fellow EverQuest lovers. I am Torven of Al'Kabor/Project 1999 aka Torrid of Druzzil Ro/rerolled.org and this is my first post here. Since Al'Kabor's shutdown I have been motivated to assist in the preservation of my favorite game, and my contributions to the Al'Kabor Project have been primarily in the form of data collecting with a focus on combat mechanics accuracy and NPC statistics.
As Al'Kabor no longer exists, my collection efforts require me to obtain data from EQLive, (which is what I call the current modern incarnation of EverQuest to distinguish it from Al'Kabor or earlier eras) and since TAKP is merely an EQEmu fork with some classic reversions, most of my research also applies to EQEmulator's project. I concluded that this forum would probably be the best place to start posting my data, as I want all emus to benefit. I also want the information to be preserved and available on a long lived forum.
This post will be about spell resists. If this is well received I will author more posts on other game mechanics.
edit: to my great disappointment, img tags don't appear to be working. I will instead provide a link to each image where they are intended to be embedded. The entire album can be viewed here:
http://imgur.com/a/Gtu2U
Abstract
This post is the result of four months of data collecting and analysis gathered from EQLive this year and some PvP data from Al'Kabor in the month before it was shut down. I have logged literally hundreds of hours of spell casts on dozens of NPCs and PCs of various levels. The goal was to collect enough data to determine a formula that would return a good estimate of an NPC's resist value from parsing logs of many spell casts on the NPC. It turned out that the prediction curve for spell resists is actually fairly simple for NPCs. (it's linear) The data I have collected will allow for an extremely accurate resist algorithm recreation.
This pastebin link:
http://pastebin.com/CTFnUqcB is a text file with all of the processed results from my raw log files. The data consists of resist rates at incremental resist values of many NPCs.
Data Collection Methods and Tools
The reason my work was possible is because of two simple facts: Resist debuff values are known; and on targets equal in level to the caster, spells will always land if resist value == 0.
Knowing that, one can obtain the precise resist value of an NPC by incrementally reducing its resist value to the point of zero spell resists. Plotting a full curve of resist rates at many points of resist values then allows you to determine a NPC's resist value from one collection of spell casts, which is of course information of value for EQ database projects.
The procedure was simple: I leveled a shaman, a bard, and an enchanter to debuff mobs with. I equipped the bard with multiple percussion mod items and determined how much both Harmony of Sound and Occlusion of Sound lowered resists with each instrument. This allowed me to debuff mobs to a large number of resist values up to a maximum of -163 for non-magic resists. (my shaman is only level 90 and bard 80; level 100s could debuff further) In addition I had my original 15 year old 65 wizard copied over to the test server (where I collected my data) which allowed me to add the resist adjust of lure spells to the debuff value, allowing for a total debuff value of -463 for fire cold and magic. (tash would decrease that further for MR)
The next step was to cast thousands of spells on a NPC at incrementally reduced debuff values, to determine the mob's absolute resist value. Due to the nature of the RNG, you really need something approaching like 10k casts to reduce the margin of error to 1%. It quickly became apparent that 30 minute parses would not suffice. Most plots you see will have been from 90 minute or longer parses. (some as long as 4 hours) Since spells took about 5 seconds each, a 1% margin of error parse would take half a day, so the lines won't be perfectly straight or gracefully curved. (particularly for the lure parses, which required me to lose aggro and allow the mob to heal several times)
My first tool I created was an autoit script I wrote to automate spell casting. I wrote it with multiple casting modes to include also possibly slowing the NPC and casting an aggro spell (disempower generally) to allow my other spell casters to not overaggro my level 90 shaman. It has an adjustable casting delay.
My second tool was a parser in the form of a lua script to read the logs and spit out the relevant numbers. I wrote three versions: one to parse spells done to the player, one to parse all-or-nothing spells done to a target, and one to parse direct damage spells done to a target.
My third tool was the in-game item Staff of Temperate Flux. This item is an instant cast clickable that places a small debuff on the target (-6 CR and FR), usable only by wizards. This item allows for a near order of magnitude more spell casts over time compared to any cast spell, allowing for much more precise resist rate data. The spell it casts is called 'Lower Element I'.
To obtain NPC vs PC data, I created a magician to summon Fireblades, which is a level 66 summoned weapon that procs a fire DD at twice the normal rate. I handed two of those to an NPC and let it beat on me for four hours (or more) per log at specific fire resist values (usually 100).
The raw data (the log files) and the scripts I wrote can be obtained from my google drive here:
https://drive.google.com/folderview?...p=sharing#grid
Analysis - PC vs NPC
As you may have seen already, this post will contain many line graphs. This is easily visualized data, and graphs make certain conclusions obvious.
The first graph I will present is one of a level 65 EarthB zone NPC: A Bloodthirsty Moss Warlord. I selected this NPC because it has a relatively high magic resist for a readily available static level NPC. (it's not easy finding mobs with a MR high enough to resist many casts undebuffed, but low enough that obtaining the precise resist value is doable) I favor MR so the temperate flux staff can be used; also many classes have an all-or-nothing MR spell, allowing my enchanter, wizard, and shaman to combine data on the same targets.
http://i.imgur.com/Rjme44p.png
The first thing you will notice is that the resist function on NPCs is linear, even at different levels from the target. This is significant because it means the 'curve' is as simple as it gets, making estimating the resist value of NPCs from the resist rate a very simple calculation.
Secondly you will notice that every two resist points translates to 1 percent of resist. This means that NPCs (without debuffs on) with over 200 resist become immune to all-or-nothing spells, assuming said spells had no 'lure' adjustments.
Third, as expected, resist rates change when the caster is not the same level as the NPC-- at least on level 65 NPCs.
Fourth, the resist chance hits zero at -71 for an equal level caster. 71 is an odd number, but I got resists at 70, so I'll just have to accept that 71 may be the value even if most NPCs are usually a multiple of 5.
There is a problem with using a Temperate Flux staff however; the staff's debuff does not stack with bard debuffs. To get parses at debuff levels that a shaman alone could not reduce to, I had to stack a bard debuff with the shaman debuff, which made the staff unusable. In that case I used a low level magic DD instead.
Full hits on direct damage spells have the same resist rate as an all-or-nothing spell hit.
Evidence to back that claim can be found in the pastebin link above, but here are a couple of examples from this particular NPC:
[ 0]Lower Element I lvl65 - Hits: 10216 (64.641%) Resists: 5588 (35.358%)
[ 0]*Shock of Lightning lvl65 - Full hits: 774 (63.494%) Partials: 299 (24.528%) Full Resists: 146 (11.977%) Actual vs. Potential Dmg: 76.2%
[-70]Lower Element I lvl65 Malosinia(2) - Hits: 7259 (98.963%) Resists: 76 (1.036%)
[-70]*Shock of Lightning lvl65 Malosinia - Full hits: 833 (99.522%) Partials: 4 (0.477%) Full Resists: 0 (0%) Actual vs. Potential Dmg: 99.6%
Note that the DD parses of course have a larger margin of error, having much fewer casts.
Here's a graph of another NPC showing similar results:
http://i.imgur.com/znuamSO.png
Data for NPCs not included in this post can be found in the pastebin link.
Next, a graph showing resist rates of casters casting on equal level NPCs at various levels:
http://i.imgur.com/g1yxqUQ.png
This suggests that the per point effectiveness of resist does not change with level.
My next graph will be one of direct damage spells:
http://i.imgur.com/S9g62Cj.png
This NPC has very high fire resist rate, but not so high as to make it immune, which makes it useful to parse large debuff values on.
The 'Realized Dmg%' line is how much damage was done over how much potential damage could have been done had every spell landed for full damage. For example, if a spell does 250 damage, and I cast two spells that hit for 150 and 0 (full resist) then the realized damage would be 150/500 or 30%.
Lure spells were cast on this NPC which dramatically reduced the number of spells cast at the more extreme resist debuff values, resulting in an increased margin of error, but the linear nature is still evident. From this we can see that the ratio of full resist percent / resist value is 1/6, meaning that
for DD spells, every 6 resist points increases the chance to full resist by 1%. The inverse being that every 6 debuff value increases the chance to hit (full or partial) by 1%. This means that
NPCs become immune to non-lure DD spells above 600 resist.
http://i.imgur.com/aiyY9Tv.png
Here's a NPC with ~200 cold resist, which I selected because it allowed me to debuff the entire full hit range. The data has too much error to know if the actual resist value is 210 or 215 or somewhere in between, but we can be reasonably certain it's around there. Note that realized damage becomes much more linear below 200 resist value.
Now data of a level 68 NPC:
http://i.imgur.com/mwHC4CW.png
Notice that the level 65 and level 90 characters have a nearly identical resist rate. (within the margin of error) This is because the rules change at level 67. NPCs level 67 or above stop being more or less susceptible to PCs level 65 or above; meaning that starting at level 65, everybody's resists end up being the same on NPCs level 67+. Lower levels also have some special considerations according to the developer posted pseudocode (see below), but I did not parse low level NPCs.
Raid bosses follow the same rules.
http://i.imgur.com/v9KcZrg.png
How Level Affects Resist Rate
Most of the above graphs were on level 65 mobs; that was to eliminate level difference as a factor using my 65 wizard. Now I'll show you how level difference affects the rate spells are resisted. These two graphs are the result of many logs.
http://i.imgur.com/aLPicCS.png
Here we see resist rates on various NPCs by PCs level 50 to 70. Some of those plots were obtained from an enchanter so they will not be as precise as the wizard parses.
What we can determine from this is that there is a limit as to how much advantage a player gets from being a higher level than the NPC-- that limit is 20% resist rate, or 40 resist value. Resist rate flattens starting at +9 levels above the NPC-- the level advantage players get over NPCs is capped at 9 levels, and 9 levels corresponds with 20% resist rate. The second item of note is that the rate of change is not linear, and gets stronger the farther away from the NPC's level one is. The warlord and soldier resist rates hit 100% at their two points due to the maximum allowable level hit range.
The sentry and soldier lines flatten at 65 and stay flat; my level 90 had the same hit rate on those NPCs. The warlord however, as it is below level 67, still becomes more susceptible to spells beyond level 65 up to a cap of 20% as expected. (my level 90 shaman parsed a hit rate of at 83.9% on a warlord)
Here is the same data graphed another way:
http://i.imgur.com/FgQnaiB.png
At first glance this looks like a cubic function. I ran it though a curve fitting program and came up with a cube function that matched it pretty closely. (
Y = -X + -0.014 * X^3) However it's actually a square function with the sign reversed for positive X:
Y = X^2 / 4.
67+ mobs seem to follow a slightly different curve however. Note that higher level NPCs will not have a curve at all, as the highest level NPC a level 65 can hit is 86.
Resist Level Caps
It is commonly known that NPCs far above the player in level will resist 100% of spells. I took the liberty to figure out what the cap is at certain levels.
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)
This information is very time-consuming to gather, so I didn't bother getting more than this. It's worth noting that the highest raid boss level in PoP is level 80, many other PoP raid bosses are level 75, and tier 1 PoP raid bosses are level 70, suggesting that this hasn't been altered since then. Also, the top raid bosses in Velious are level 70.
Maximum Resist?
It seems SOE liked to use 1000 resist value to make mobs 'immune'. Several spells have a -1000 resist adjust modifier (such as Dictate or Seru's AoE), and Lure of Flame began to (barely) hit a Sentry of Ro in the Tower of Solusek Ro after debuffing its fire resist -163 points. Assuming it has 1000 fire resist, that combined resist adjust would bring it to 537 which is below the immunity threshold for direct damage spells (600).
[-463]Lure of Flame lvl65 MaloseneRk2+-58HoS - Full Hits: 0 (0%) Partials: 13 (6.074%) Full Resists: 201 (93.925%) Actual vs. Potential Dmg: 0.1%
Analysis - NPC vs PC
So, we now have a good idea of what PC vs. NPC resists look like, but what about NPC vs. PC? Is it the same function/curve?
http://i.imgur.com/52E5D93.png
It appears so.
I obtained this data by handing NPCs procing weapons, and tanked the NPCs for hours. These parses were more difficult to get so I didn't feel the need to do more than this. There is one observable difference however: unlike PCs, NPCs do not have a level limit that caps the advantage or disadvantage they get from level difference. (or if there is a cap, it's significantly larger)
Analysis - PvP
Spells cast in PvP have extra constraints and different calculations done which result in a different curve than spells cast on NPCs. Allakhazam's Lucy spell database lists three pvp fields: 'pvpresistbase', 'pvpresistcalc', and 'pvpresistcap'. Lucy added these fields on May 12, 2004, but the PvP curve was different than the PvE curve even before then.
In the month before Al'Kabor shut down, I gathered as much data as I could in an attempt to preserve the classic game for emulation. One of the things I did was parse spell resists in the arena. I parsed Ensnare and Ignite (a druid DD) on an equal level player for 1 hour at various resist levels. Later I parsed Ensnare on EQLive.
http://i.imgur.com/dbF05ux.png
As you can see, PvP resists follow a curve instead of a line, and the curves from both servers match to a large degree, but only up to about 200 resist. The first unexpected result is that Ensnare actually resisted LESS at very high resist levels on Al'Kabor; it appears that PvP resists were bugged there-- or poorly implemented at least.
The EQLive ensnare curve has two differences: the rate of resist is capped at 95%, and resists don't begin until 10 MR. Ensnare has a -10 'pvpresistcap' value in the Lucy database. 'pvpresistcap' is not actually a cap at all-- it's a resist adjust similar to lure spells. One graph will not satisfy as definitive proof of that claim, so here's a graph of Disempower from EQLive:
http://i.imgur.com/tvCfdgF.png
Note that the resist rate at 0 resist value is about 13% (parsed at 12.879%). Disempower has a pvpresistcap of 17.
Here is a graph of disempower adjusted (moved forward) by 17 resist, along with Al'Kabor's Ensnare, and a parse done on a NPC by a caster 13 levels below the NPC's level:
http://i.imgur.com/MUtrNGo.png
Here is a PvP graph of Lower Element I:
http://i.imgur.com/sTGoHjV.png
It's nice and smooth since the spell is instant cast, allowing for large data sets to be collected in a short period of time. Since Lower Element I has a pvpresistbase of 0, a pvpresistcalc of 100, and a pvpresistcap of 0, you can see that it begins at 0 and the curve matches the Al'Kabor curve up to around 190 resist, where it still caps at 95% resist rate.
Here is a graph showing the change in resist rate as a function of level difference from the target:
http://i.imgur.com/C0KRt7Q.png
Again note that the resist rate caps out at 95%, so the last four plots actually wouldn't be flat if the resist value I had chosen were lower. (perhaps I should have left them out)
Now for some direct damage graphs.
http://i.imgur.com/EkcpYIf.pnghttp:/...om/cs8ueSp.png
The Frost Rift parses are 4 hours long and consist of 3,500+ casts each. The Ignite parses were about 1 hour and 700 casts each, which is why those lines are so crooked. (the server was shutting down in days and I didn't have the time) The margin of error at 700 is something like 4%, but I think the algorithm the server used was also pretty wonky.
Frost Rift has a significant resist adjust (pvpresistcap) of 37, and caps out around 93% resist rate. It has a 'pvpresistbase' of 50 but I couldn't figure out how that number applied to the results I was seeing.
Al'Kabor's bugged resist curve is also evident in the Ignite results.
http://i.imgur.com/O9KzFq0.png
Here are full hit plots of Frost Rift shifted over to the right by 37 resist, along with Ignite and the great white shark plot shifted left by 15 for comparison.
EQLive2014 vs. Old EQ
Since my task is to assist in the recreation of an earlier era EQ (PoP specifically), I needed the answer to one question: are the resists in today's EQ the same as they were in PoP era EQ? Unfortunately it did not occur to me that the PvE and PvP resist curve might be different when I was hurrying to collect as much data as I could before the Al'Kabor shutdown, so I only did PvP parses. I do however have many raid logs from my time playing EQ in 2003.
I have almost every time raid that my guild did logged, which spans 9 months or so. Since I was also either the MT or 2nd in line, I almost always (if not always) had a bard playing Psalm of Veeshan, and my gear was rather good, so my resists were almost always capped. This means I have a significant sample size of raid boss AoEs on a (mostly) known resist value character to search from. A simple grep tool can easily reveal and count the desired text. Here's the tally:
Level 71 NPCs
-------------
Time Terris AoE Phantasmal Torment resist adjust: -300 (magic); resists: 115 (85.2%); hits: 20 (14.8%)
Time Saryrn AoE Horrifying Affliction resist adjust: -300 (disease); resists: 232 (82.3%); hits: 50 (17.7%)
Time Saryrn AoE Torrent of Agony resist adjust: -300 (poison); full resists: 66 (33.2%); partials: 121 (60.8%); full hits: 12 (6%)
Time Terris AoE Quivering Nightmares resist adjust: -400 (magic); resists: 119 (36.6%); hits: 206 (63.4%)
Time Saryrn proc Torrential Torment resist adjust: -450 (poison); resists: 67 (19%); hits: 286 (81%)
Level 75 NPCs
-------------
Black Plague: Time Bertoxxulous AoE; 60s recast; -300 disease resist adjust; resists: 73 (70.9%); hits: 30 (29.1%)
Rain of Bile: Time Bertoxxulous) AoE (rain); 60s recast; -300 poison resist adjust; resists: 209 (66.8%); hits: 104 (33.2%)
Rage of Zek: Time Rallos Zek and Drunder Rallos Zek the Warlord (both lvl75) AoE; 35-48s recast; -100 fire resist adjust; resists: 216 (97.3%); hits: 6 (2.7%)
Now you know why I selected level 71 and 75 NPCs to parse NPC vs PC resists. It's a simple matter to plot these on a EQ2014 graph:
http://i.imgur.com/V5E9GmT.png
The evidence is convincing that the resist function has changed little in all this time. But wait, the Rallos AoE should be 0%, you say. Well that bothered me too, so I checked for instances of bard song dropping before the AoEs landed, and sure enough I found them:
time again.txt 152 [Tue Jan 13 19:04:31 2004] The crystalline scales fall away.
time again.txt 167 [Tue Jan 13 19:04:37 2004] The chaos of war consumes your soul.
time other's hits.txt 47232 [Sat Jan 24 20:27:56 2004] The crystalline scales fall away.
time other's hits.txt 47314 [Sat Jan 24 20:28:02 2004] The chaos of war consumes your soul.
time other's hits.txt 52463 [Sat Jan 24 20:30:52 2004] The crystalline scales fall away.
time other's hits.txt 52484 [Sat Jan 24 20:30:53 2004] The chaos of war consumes your soul.
time yet again.txt 50689 [Fri Jan 30 21:26:33 2004] The crystalline scales fall away.
time yet again.txt 50909 [Fri Jan 30 21:26:37 2004] The chaos of war consumes your soul.
The other two hits I took were after I was newly resurrected before Rallos was killed. All 6 hits were explainable.
Further Evidence: Rydda`Dar
Rydda`Dar is the dragon in Halls of Honor. It's slowable, but highly resistant; Rydda is notoriously hard to land slow on, and this claim is easily verified by googling. This makes it a good candidate to test the hypothesis that the current resist curve is the same as the PoP era, because knowing it was hard to slow gives us an idea as to its resist value back then.
Resist debuffs have remained unchanged, so if Rydda still remains hard to slow with PoP era debuffs on in 2014, then that is evidence that the curve was unaltered. Is that the case? I parsed it to find out.
[-153]Lower Element I lvl65 -48Tash+MaloseneRk2 - Hits: 1005 (8.636%) Resists: 10632 (91.363%)
[-172]Disempower lvl90 -48Tash+Malis+-58HoS - Hits: 266 (17.627%) Resists: 1243 (82.372%)
These parses indicate a Magic Resist value of 335 for Rydda`Dar. (91.363 * 2 + 153 = 335.726)
A PoP era debuff level would be -50 from Howl of Tashan (my enchanter is only level 61), -55 from Malos, and ~-50 from Harmony of Sound. I don't have a 16 focus drum to get the exact value, but 50 should be very close. As far as I know, 16 was the best Luclin era drum. (drums of the beast) Malos and Harmony of Sound are both rune turn-in (level 65) spells. The EQ2014 curve would put Rydda at a 10% chance for slow to land with -155 MR debuffs.
It is important to highlight that making an NPC very difficult (and still possble) to slow requires giving it an MR value in a narrow range; since 2 resist value == 1 resist rate %, you have to know the maximum possible debuff value combined, add it to 200, then subtract 2 for every 1% chance you want to allow for the possibility of slow landing. The difference between unslowable and 10% chance for slow to land is a mere 20 resist value.
Earlier Eras
But what about eras earlier than PoP? This is easy to answer: the above does not apply to older eras. Shortly before PoP launched, Sony revamped the resist system. Here are some relevant patch notes regarding changes to the resist system:
Quote:
- The level-based spell resistance bonus inherent in super-high level NPCs has been reduced significantly. (October 8, 2001)
|
Quote:
- Higher level PCs will be more resistant to lower level NPCs' spells. (September 4, 2002)
|
Quote:
September 4, 2002
** Resistance Changes **
We've made some fairly drastic changes to the way the spell resistance
system works. Previously, there was only the smallest benefit to having
resists over a certain value. We've reworked resistance in its
entirety, completely replacing the old system with one that is more
logical.
The idea behind the changes is pretty simple: Resists should matter in
a way that makes sense.
Important things to note about the new resistance system:
- Resists matter more for PCs. There are now tangible differences
between having 50, 150, and 250 in a given resistance, for example.
Resistance buffs, bard songs, and resist gear have actual value, all
the way up the line.
- Conversely, resistances also matter more for NPCs. Some NPCs became
more vulnerable to things they have always been vulnerable to, other
NPCs became more resistant to things that they were inclined to be
somewhat resistant to.
- Resistance debuffs should also have more value, all the way up the
line. For the first time, resistance debuffs now have the ability to
bring NPCs that were lure-style only down into the range of being hit
by normal spells.
- 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.
- Overall, against NPCs that have medium-to-high resistances of a given
type, expect to see more full hits, fewer partials, but more full
resists in the new system. Taken over time, the damage done by PC
casters to semi-high resistance NPCs should be approximately the same,
but will definitely improve when the proper debuffs are applied (we
wanted to make sure that this did not turn into a universal nerf of
casters).
September 26, 2002
- 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.
|
Speculation: could the PvP curve be the pre-revamp PvE curve? (were they once the same?) I don't think this is possible to prove without the old source, sadly.
Conclusions- Spells landing on players from players have a different resist curve than spells landing on or cast from NPCs.
- The PvP curve is slightly convex or concave (depending on how you graph it) while the PvE curve/function is linear.
- Al'Kabor (EQ circa early PoP) and modern EQ have the same PvP resist curve up to 200 resist. Beyond that is dissimilar due to Al'Kabor's seemingly bugged upper end curve.
- Allakhazam's 'pvpresistcap' field in its Lucy spells database appears to not be a cap at all, but rather a resist modifier added to spells when cast on players, similar to 'resist adjust' for lure-type spells.
- NPCs casting on PCs follow the same resist curve/line as PCs on NPCs, but NPCs are not level advantage capped at 9 as PCs are.
- NPCs gain the resists from the items in their inventories, including armor, but may be limited to one per equipment slot. (unconfirmed but likely)
- There is a hard level limit above the player after which mobs become completely immune to any and all spells; this limit varies with level.
- The hit chance of an all-or-nothing spell is the same chance a direct damage spell will full hit. Direct damage spells will never full hit beyond 200 resist.
- On NPCs of equal level to the caster, the chance to fully resist is mob resist value / 2.
- This means that mobs of equal level having a resist value over 200 will always resist an all-or-nothing spell, and any DD spell that hits after 200 resist will always be a parial, non-full hit.
- On NPCs of equal level to the caster, the chance to fully resist direct damage spells is mob resist value / 6.
- This means that mobs of equal level with greater than 600 resist value will always fully resist a direct damage spell.
- The advantage or disadvantage from level difference is: resist adjust = level difference^2 / 2, with the sign flipped if the caster is higher level.
- Level advantage is capped at 9 levels for PCs casting on NPCs, which translates to 20% resist chance or 40 resist value.
- On NPCs at or above level 67, if the caster is level 66 or higher, then the level difference between the two is ignored. i.e. it calculates as a white con.
- EQ's resist algorithm has not changed much since the resist system revamp in late Luclin.
Resist Algorithm Pseudocode And EQEmu's Implementation
EQ's (now) Lead Designer, Prathun, posted some pseudocode (or something close to pseudocode) of the resist function on the EQ boards a few years ago. The post is gone now (or moved somewhere I don't know where) but Allakhazam archived it here:
http://everquest.allakhazam.com/foru...01109310546959
It's a bit long but I'll quote it here for preservation and discussion purposes.
Code:
Pull the spell's resist modifier from the spell (or the spell list override, if one exists).
Adjust the resist modifier for applicable focus effects.
Check for fear immunity. If roll is made, resist spell.
Check for resistance to the spell effect. If roll is made, resist spell.
Check for Sanctification. If roll is made and spell is not no_resist, resist spell.
Calculate target's resistance chance applicable to this spell.
If spell's resist type is no_save, spell lands.
Otherwise, magic checks against magic, fire against fire, chromatic checks lowest, prismatic checks average, etc. The capped resistance score is used.
Set resist chance to 15 if the spell effect is a lull.
Adjust resist chance for level difference between caster and target.
Set temp level difference to (target level - caster level).
If target is at least level 67 and target is an NPC, temp level difference is set to (66 - caster level) or 0, whichever is greater.
If target is a PC, and caster level is at least 21, and temp level difference is greater than 15, set temp level difference to 15.
If target is an NPC, and temp level difference is less than -9, set temp level difference to -9.
Set level modifier to (temp level difference * temp level difference / 2)
If temp level difference is negative, make level modifier negative.
If target is an NPC and caster is far below target's level, set level modifier to 1000.
Add level modifier to resist chance.
Adjust resist chance for spell's resist modifier.
If effect is damage and target is a non-mercenary NPC...
If target is at least level 67, level difference is set to (66 - caster level) or 0, whichever is greater.
If target is at least level 17 and level difference is greater than 0, add (2 * level difference) to resist chance.
If resist chance is greater than spell's max resist and the max resist is not 0, set the resist chance to max resist.
If resist chance is less than spell's min resist and the min resist is not 0, set the resist chance to min resist.
Roll a random number between 0 and 200.
If the roll is greater than the resist chance, spell lands.
If the roll is not greater than the resist chance and the spell does not allow partial resists, resist spell.
If spell effect does not apply damage, spell lands.
Otherwise, spell effect applies damage. Calculate partial resist.
If the resist chance is less than 1, set the resist chance to 1.
Partial resist modifier is set to ((150 * (resist chance - roll)) / resist chance).
If target is a non-mercenary NPC...
If target is higher level than caster, and target is at least level 17, and caster is level 50 or below, add 5 to partial resist modifier.
If target is at least level 30 and caster is level 50 or below, add (casterlevel - 25) to partial resist modifier.
If target's level is less than 15, subtract 5 from partial resist modifier.
If caster is an NPC...
If target is at least 20 levels higher than caster, add (level difference * 1.5) to partial resist modifier.
If partial resist modifier is less than 0, set partial resist modifier to 0.
If partial resist modifier is greater than 100, set partial resist modifier to 100.
Spell lands. Partial resist modifier is used to calculate resulting damage.
In the above pseudcode, 'resist chance' is actually the resist value. Since SOE uses a simple linear function, the two are closely related but the distinction is important.
I am aware that efforts were made to change EQEmu's algorithm to match this, and I must admit that I thought EQEmu's implementation wasn't as accurate as it is before I had spent all this time. However I did some cursory parses on my local server, and while the all-or-nothing resist rates seem to be correct, partial resists are way off. Spells are landing for full even after 200 resist, realized damage is off, partials always seem to do near full damage, and mobs seem to become immune to direct damage spells somewhere around 400 resist.
Using my research and examining this pseudocode allowed me to construct a basic but accurate resist simulator. Here is the core function in lua:
Code:
function resist(resistValue, maxSpellDmg)
local roll = math.random(0, 200);
if ( roll >= resistValue ) then -- needs to be >= so 0 rolls do full damage at 0 resist
return maxSpellDmg;
else
partialResistMod = (150 * (resistValue - roll)) / resistValue;
if ( partialResistMod <= 0 ) then
return maxSpellDmg;
elseif ( partialResistMod >= 100 ) then
return 0;
end
return maxSpellDmg - (maxSpellDmg * (partialResistMod / 100));
end
end
Obviously I left out a whole bunch of stuff; this is just to generate the curves for direct damage spells.
Here is the output of that function, with 100,000 casts at every 10 resist values:
http://i.imgur.com/fWQj9XQ.png
Fixing EQEmu's spells.cpp for DD partials on NPCs should be straightforward at this point, but I've not used github before so I'll leave it to more capable hands. I've not tested PvP resists on EQEmu however, so I have no idea what state that is in.
How to Determine an NPC's Resist Value
Here is how you determine an NPC's resist value for Project EQ, TAKP, or other databases:
Log hundreds if not thousands of spell casts on the NPC with zero debuffs on it. The more casts, the better. I'm not a statistician, but if I'm reading wikipedia right, getting a margin of error within 1% at a 95% confidence requires nearly 10 thousand casts, 2% requires 2,401 casts, 3% requires 1067 casts, 4% 600 casts, and 5% 384 casts. Low damage direct damage spells are preferable as they can indicate a resist value up to 600 instead of 200.
The following is true if the NPC is a white con, or the NPC is level 67 or higher and the caster is level 65 or higher:
If the spell is an all-or-nothing spell
If resist rate < 100%
resist value = resist rate * 2.
Else spell was 100% resisted
resist value is > 200
Else the spell is a direct damage spell
If full resist rate < 100%
resist value = full resist rate * 6.
Else spell was 100% fully resisted
resist value is > 600
To get resist values > 200 or 600, debuff the NPC as much as possible while logging casts on it. (be very careful to not let any debuffs drop) Then calculate the resist rate. You simply add the absolute value of the resist debuffs to the resist value you get. I.e. if the debuff does -70 resist, add 70. If spells still 100% resist, then you can be sure the resist value is either 200+abs(debuff value) or 600+abs(debuff value). Be sure to factor in resist adjusts of the spell cast (i.e. -300 for wizard lure spells).
If the NPC is not equal level to the caster, and the NPC is below level 67 or the caster is below level 65 you need to factor in the level difference. The easiest way to do this is use a caster 9 or more levels higher than the NPC, and add 40 resist to the resist value. The level advantage caps at +9, and 9 levels translates to 40 resist. If your caster is 1-8 levels higher, add the result of this:
level difference * level difference / 2.
Here's an example:
The Statue of Rallos Zek (level 59) has a magic resist of about 260. Here's how I got that number.
I parsed it with a -105 debuff on, as it was resisting 100% without debuffs:
Statue of Rallos Zek Lower Element I lvl65 MaloseneRk2 - Hits: 4834 (30.664%) Resists: 10930 (69.335%)
69.335 * 2 + 105 = 243.67
The caster I used is 6 levels above the NPC, so I calculate the resist advatage from level: 6 * 6 / 2. 6 levels translates to 18 resist value, or 9% resist chance. So I add 9 to the resist rate, which tells me how it would look if I were the same level as the NPC:
78.335 * 2 + 105 = 261.67
or you can just add 18 resist value:
69.335 * 2 + 123 = 261.67
Lastly I round to the nearest multiple of 5, since these numbers tend to be round.