I use lua scripts to parse logs or run simulations. I will paste them here.
This is my charm simulator:
Code:
local RESIST_VALUE = 50;
local CASTS = 150000;
local CASTER_LEVEL = 65;
local TARGET_LEVEL = 62;
local CHARM_DURATION_TICKS = 76;
--local CHARM_DURATION_TICKS = 180;
local CHARISMA = 280;
local USE_BOW_CURVE = false; -- PvP resist curve is bow shaped
local USE_FLOOR = true;
local FLOOR_VALUE = 5;
local MIN_ROLL = 1; -- use 1 for Live, 0 for AK
local MAX_ROLL = 200;
local CharmBreakCheckChance = 50;
local CharismaEffectiveness = 8;
local TDBonusPct = 0;
--local TDBonusPct = 35; -- TD = 15%, 25%, 35% says raidloot.com
math.randomseed(os.time());
local tickResistChecks, tickResistFails = 0, 0;
function charmResist(charmTick, resistValue, targetLevel, casterLevel, charisma)
local hitStatus = false;
if ( charmTick ) then
casterLevel = casterLevel + 4;
end
local levelDiff = targetLevel - casterLevel;
local tempLevelDiff = levelDiff;
if ( targetLevel >= 67 ) then
tempLevelDiff = 66 - casterLevel;
if ( tempLevelDiff < 0 ) then
tempLevelDiff = 0;
end
end
if ( tempLevelDiff < -9 ) then
tempLevelDiff = -9;
end
local levelMod = math.floor(tempLevelDiff * tempLevelDiff / 2);
if ( tempLevelDiff < 0 ) then
levelMod = -levelMod;
end
local effectiveResistValue = resistValue + levelMod;
local bonus = 0;
if ( not charmTick ) then
if ( CHARISMA >= 75 ) then
bonus = math.floor((CHARISMA - 75) / CharismaEffectiveness);
if ( bonus > 25 ) then
bonus = 25;
end
effectiveResistValue = effectiveResistValue - bonus;
end
else
tickResistChecks = tickResistChecks + 1;
end
if ( not USE_FLOOR and charmTick and math.random(20) == 1 ) then -- 5% chance to fail
hitStatus = false;
else
local erv = effectiveResistValue;
if ( USE_BOW_CURVE and effectiveResistValue < 200 ) then
erv = -0.07868992 + 1.53452*effectiveResistValue - 0.002708188*(effectiveResistValue*effectiveResistValue);
end
if ( charmTick and USE_FLOOR) then
if ( erv < FLOOR_VALUE ) then
erv = FLOOR_VALUE;
end
end
if ( math.random(MIN_ROLL, MAX_ROLL) > erv ) then
hitStatus = true;
else
hitStatus = false;
end
end
if ( charmTick and not hitStatus ) then
tickResistFails = tickResistFails + 1;
end
return hitStatus, effectiveResistValue, levelMod;
end
local hitStatus, effectiveResistValue, levelMod, tickHitStatus, tickEffectiveResistValue = 0, 0, 0, 0, 0;
local fullResists, avgDuration, duration, lands, longestCharm, maxCharms = 0, 0, 0, 0, 0, 0;
local durationsStr = "";
local ticks, tickFails1, tdChecks, tdSuccesses = 0, 0, 0, 0;
local tickTable = { [0] = 0, [1] = 0, [2] = 0, [3] = 0, [4] = 0, [5] = 0, };
fullResists, lands, avgDuration = 0, 0, 0;
for i = 1, CASTS do
hitStatus, effectiveResistValue, levelMod = charmResist(false, RESIST_VALUE, TARGET_LEVEL, CASTER_LEVEL, CHARISMA);
if ( hitStatus ) then
duration = 0;
for j = 1, CHARM_DURATION_TICKS do
ticks = ticks + 1;
-- Mob::PassCharismaCheck() called by spell_effects.cpp does this before calling ResistSpell()
if ( math.random(1, 100) > CharmBreakCheckChance ) then
tickHitStatus = true; -- charm holds
else
tickHitStatus = false;
tickFails1 = tickFails1 + 1;
end
if ( not tickHitStatus ) then
tickHitStatus, tickEffectiveResistValue = charmResist(true, RESIST_VALUE, TARGET_LEVEL, CASTER_LEVEL, CHARISMA);
end
if ( not tickHitStatus and TDBonusPct > 0 ) then
tdChecks = tdChecks + 1;
if ( math.random(1, 100) <= TDBonusPct ) then
tickHitStatus = true;
tdSuccesses = tdSuccesses + 1;
end
end
if ( tickHitStatus ) then
duration = duration + 1;
else
if ( duration < 6 ) then
tickTable[duration] = tickTable[duration] + 1;
end
break;
end
if ( j == CHARM_DURATION_TICKS ) then
maxCharms = maxCharms + 1;
end
end
if ( duration > longestCharm ) then
longestCharm = duration;
end
avgDuration = avgDuration + duration;
lands = lands + 1;
--print(duration);
--durationsStr = durationsStr..duration..", "
else
fullResists = fullResists + 1;
end
end
avgDuration = avgDuration / lands;
print("Simulating "..CASTS.." casts at "..RESIST_VALUE.." resist value; caster level "..CASTER_LEVEL.."; CHA: "..CHARISMA..
"; target level "..TARGET_LEVEL.."; Roll: "..MIN_ROLL.."-"..MAX_ROLL.."; CharmBreakCheckChance = "..CharmBreakCheckChance..
"; Max Charm Duration: "..CHARM_DURATION_TICKS.."; TD%: "..TDBonusPct);
print("Initial Effective resist value: "..effectiveResistValue.."; tick effective value: "..tickEffectiveResistValue..
"; Floor: "..FLOOR_VALUE.." UseFloor? Bow curve?:", USE_FLOOR, USE_BOW_CURVE);
print(string.format("full resists: %i (%0.2f%%); lands: %i (%0.2f%%); avg duration ticks: %0.1f (%0.2f%%); max duration charms: %i (%0.2f%%)",
fullResists, (fullResists/CASTS*100), lands, (lands/CASTS*100), avgDuration, avgDuration/CHARM_DURATION_TICKS*100, maxCharms, maxCharms/CASTS*100));
--print(durationsStr);
print("longest charm: "..longestCharm.." ticks");
print(string.format("base tick fail%%: %0.3f%% TD success%%: %0.3f%% tick resist fail%%: %0.3f%%",
(tickFails1/ticks*100), (tdSuccesses/tdChecks*100), (tickResistFails/tickResistChecks*100)));
for i = 0, 5 do print(string.format("%i tick charms: %i (%0.2f%%)", i, tickTable[i], tickTable[i]/lands*100)); end
This is my charm log parser:
Code:
local INPUT_DIR = "I:\\Google Drive\\Classic EverQuest Preservation\\EQLive 2014 Sourced Data\\Logs\\Resist Mechanics Logs\\Charms\\";
--local INPUT_DIR = "I:\\Parse\\";
local INPUT_FILENAME = "eqlog_Torria_test - lvl65 ench CHA15 TD0 vs crystalline golem CoD.txt";
local AK_LOG = false;
local SPELL_NAME = "Command of Druzzil";
local IS_DRUID_SPELL = false;
local SPELL_DURATION = 75; -- duration in ticks (PoP charms = 75 at 65)
--local TASH_SPELL = "Tashina"; -- nil this if not using a tash spell
function parseTime(line)
local offset = line:find("%[") - 1;
return tonumber(line:sub(offset + 10, offset + 11)) * 86400 + tonumber(line:sub(offset + 13, offset + 14)) * 3600 +
tonumber(line:sub(offset + 16, offset + 17)) * 60 + tonumber(line:sub(offset + 19, offset + 20));
end
local charmLandTime, charmEndTime, charmDuration, charmMin, charmSec, charmTicks, castTime, prevLandTime, prevEndTime, lastEndTime = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
local charms, avgTicks, maxDuration, minDuration, maxCharms, resists, lands, breaks, ignored, interrupts, invisBreaks = 0, 0, 0, 9999, 0, 0, 0, 0, 0, 0, 0;
local charmActive, tashActive = false, false;
local castingSpell;
local charmList = {};
local s, ts;
local landText = " has been charmed";
local breakText = "Your "..SPELL_NAME.." spell has worn off of ";
local resistText = " resisted your "..SPELL_NAME.."!";
local tashOffText = "Your "..(TASH_SPELL or "nil").." spell has worn off of ";
if ( AK_LOG ) then
if ( IS_DRUID_SPELL ) then
landText = " blinks.";
else
landText = "You begin casting "..SPELL_NAME;
end
breakText = "Your charm spell has worn off";
resistText = "Your target resisted the "..SPELL_NAME.." spell.";
end
for line in io.lines(INPUT_DIR..INPUT_FILENAME) do
if ( AK_LOG and line:find("You begin casting ", 28, true) ) then
_, _, castingSpell = line:find("^You begin casting (.+)%.", 28);
castTime = parseTime(line);
if ( not IS_DRUID_SPELL and castingSpell == SPELL_NAME ) then
prevLandTime = charmLandTime;
prevEndTime = charmEndTime;
charmLandTime = castTime;
charmEndTime = 0;
lands = lands + 1;
if ( AK_LOG and charmActive ) then
lands = lands - 1;
end
charmActive = true;
end
elseif ( (not AK_LOG or IS_DRUID_SPELL) and line:find(landText, 28, true) and (not TASH_SPELL or tashActive) ) then
charmLandTime = parseTime(line);
charmEndTime = 0;
lands = lands + 1;
charmActive = true;
elseif ( charmActive and line:find(breakText, 28, true) ) then
charmEndTime = parseTime(line);
lastEndTime = charmEndTime;
breaks = breaks + 1;
charmActive = false;
if ( charmLandTime > 0 and charmEndTime > 0 ) then
charmDuration = charmEndTime - charmLandTime;
if ( charmDuration > ((SPELL_DURATION+3) * 6) or charmDuration < 0 ) then
ignored = ignored + 1;
elseif ( TASH_SPELL and not tashActive ) then
ignored = ignored + 1;
else
charms = charms + 1;
avgTicks = avgTicks + charmDuration;
charmLandTime = 0;
charmEndTime = 0;
charmMin = math.floor(charmDuration / 60);
charmSec = charmDuration % 60;
if ( charmSec < 10 ) then
charmSec = "0"..charmSec;
end
charmTicks = math.floor(charmDuration / 6);
if ( charmTicks > maxDuration ) then
maxDuration = charmTicks;
end
if ( minDuration > charmTicks ) then
minDuration = charmTicks;
end
table.insert(charmList, charmTicks);
end
end
if ( charmDuration > 0 ) then
--print(line.."\t"..charmMin..":"..charmSec.."\t"..charmTicks.." ticks\n");
charmDuration = 0;
end
elseif ( AK_LOG and not IS_DRUID_SPELL and castingSpell == SPELL_NAME and line:find("Your spell is interrupted", 28, true) ) then
if ( parseTime(line) - castTime < 5 ) then
charmLandTime = prevLandTime;
charmEndTime = charmEndTime;
interrupts = interrupts + 1;
--print("Possible charm cast interrupt:");
--print(line);
end
elseif ( line:find(resistText, 28, true) ) then
resists = resists + 1;
elseif ( TASH_SPELL and line:find(" glances nervously about.", 28, true) ) then
tashActive = true;
elseif ( TASH_SPELL and line:find(tashOffText, 28, true) ) then
tashActive = false;
elseif ( (line:find("You vanish.", 28, true) or line:find("Your body fades away.", 28, true) or line:find("You are no longer hidden.", 28, true))
and lastEndTime == parseTime(line)
) then
invisBreaks = invisBreaks + 1;
charmList[#charmList] = -charmList[#charmList];
end
end
if ( charms > 0 ) then
avgTicks = avgTicks / charms / 6;
s = "durations: ";
for i, tick in ipairs(charmList) do
if ( tick < 0 ) then
ts = "("..tostring(-tick)..")";
else
ts = tostring(tick)
end
s = s..ts..", ";
if ( tick == maxDuration or tick == (maxDuration - 1) ) then
maxCharms = maxCharms + 1;
end
end
print(INPUT_FILENAME);
print(s);
print(string.format(SPELL_NAME.." casts: %i; Lands: %i (%0.2f%%); Resists: %i (%0.2f%%); Breaks: %i",
resists+lands, lands, lands/(lands+resists)*100, resists, resists/(lands+resists)*100, breaks));
if ( ignored > 0 or interrupts > 0 or invisBreaks > 0 ) then
print(string.format("Ignored: %i; Interrupts: %i; Invis Breaks: %i", ignored, interrupts, invisBreaks));
end
print(string.format("min duration: %i ticks; max duration: %i ticks; avg duration: %0.1f ticks; est max charms: %i (%0.2f%%)",
minDuration, maxDuration, avgTicks, maxCharms, maxCharms/(breaks-ignored)*100));
local x;
for j = 0, 5 do
x = 0;
for i, tick in ipairs(charmList) do
if ( tick == j ) then
x = x + 1;
end
end
print(string.format("%i tick charms: %i (%0.2f%%)", j, x, x/lands*100));
end
else
print("no charms found");
end
This is my root simulator:
Code:
local RESIST_VALUE = 50;
local CASTS = 100000;
local CASTER_LEVEL = 65;
local TARGET_LEVEL = 65;
local ROOT_DURATION_TICKS = 30;
local USE_FLOOR = true;
local RootBreakCheckChance = 75;
local RootMinResist = 5;
math.randomseed(os.time());
function RootResist(rootTick, resistValue, targetLevel, casterLevel)
local hitStatus = false;
if ( rootTick ) then
casterLevel = casterLevel + 4;
end
local levelDiff = targetLevel - casterLevel;
local tempLevelDiff = levelDiff;
if ( targetLevel >= 67 ) then
tempLevelDiff = 66 - casterLevel;
if ( tempLevelDiff < 0 ) then
tempLevelDiff = 0;
end
end
if ( tempLevelDiff < -9 ) then
tempLevelDiff = -9;
end
local levelMod = math.floor(tempLevelDiff * tempLevelDiff / 2);
if ( tempLevelDiff < 0 ) then
levelMod = -levelMod;
end
local effectiveResistValue = resistValue + levelMod;
if ( rootTick ) then
if ( USE_FLOOR ) then
if ( effectiveResistValue < RootMinResist ) then
effectiveResistValue = RootMinResist;
end
else
if ( math.random(20) == 1 ) then
return false, effectiveResistValue, levelMod;
end
end
end
if ( math.random(1, 200) > effectiveResistValue ) then
hitStatus = true;
else
hitStatus = false;
end
return hitStatus, effectiveResistValue, levelMod;
end
local hitStatus, effectiveResistValue, levelMod, tickHitStatus, tickEffectiveResistValue = 0, 0, 0, 0, 0;
local fullResists, avgDuration, duration, lands, longestRoot, maxRoots = 0, 0, 0, 0, 0, 0;
local durationsStr = "";
fullResists, lands, avgDuration = 0, 0, 0;
for i = 1, CASTS do
hitStatus, effectiveResistValue, levelMod = RootResist(false, RESIST_VALUE, TARGET_LEVEL, CASTER_LEVEL);
if ( hitStatus ) then
if ( math.random(100) > 50 ) then
duration = 1;
else
duration = 0;
end
for j = 1, ROOT_DURATION_TICKS do
tickHitStatus, tickEffectiveResistValue = RootResist(true, RESIST_VALUE, TARGET_LEVEL, CASTER_LEVEL);
if ( math.random(0, 99) > RootBreakCheckChance ) then
tickHitStatus = true;
end
if ( tickHitStatus ) then
duration = duration + 1;
else
break;
end
if ( j == ROOT_DURATION_TICKS ) then
maxRoots = maxRoots + 1;
end
end
if ( duration > longestRoot ) then
longestRoot = duration;
end
avgDuration = avgDuration + duration;
lands = lands + 1;
--print(duration);
--durationsStr = durationsStr..duration..", "
else
fullResists = fullResists + 1;
end
end
avgDuration = string.format("%.2f", avgDuration / lands);
print("Simulating "..CASTS.." casts at "..RESIST_VALUE.." resist value; caster level "..CASTER_LEVEL.." target level "..TARGET_LEVEL..
"; MinResist == "..RootMinResist.."; Use floor:", USE_FLOOR);
print("Initial Effective resist value: "..effectiveResistValue.."; tick effective value: "..tickEffectiveResistValue..
"; Break Check Chance: "..RootBreakCheckChance);
print("full resists: "..fullResists.." ("..(fullResists/CASTS*100).."%); lands: "..lands.." ("..(lands/CASTS*100)..
"%); avg duration ticks: "..avgDuration.." ("..math.floor(avgDuration/(ROOT_DURATION_TICKS+1)*100)..
"%); max duration roots: "..maxRoots.." ("..math.floor(maxRoots/lands*100).."%)");
-- print(durationsStr);
print("longest root: "..longestRoot.." ticks");
This is my root log parser:
Code:
local INPUT_DIR = "I:\\Google Drive\\Classic EverQuest Preservation\\EQLive 2014 Sourced Data\\Logs\\Resist Mechanics Logs\\Roots\\";
local INPUT_FILENAME = "eqlog_Torria_test - lvl65 ench Paralyzing Earth vs a unicorn.txt";
function parseTime(line)
local offset = line:find("%[") - 1;
return tonumber(line:sub(offset + 10, offset + 11)) * 86400 + tonumber(line:sub(offset + 13, offset + 14)) * 3600 +
tonumber(line:sub(offset + 16, offset + 17)) * 60 + tonumber(line:sub(offset + 19, offset + 20));
end
local rootLandTime, rootEndTime, rootDuration, rootMin, rootsec, rootTicks, maxRoots, resists, clips = 0, 0, 0, 0, 0, 0, 0, 0, 0;
local roots, avgTime, avgTicks, maxDurationTicks, maxDurationSecs, minDurationTicks, minDurationSecs = 0, 0, 0, 0, 0, 0, 9999;
local rootTicksTbl = {};
local rootDurations = {};
local s, now;
for line in io.lines(INPUT_DIR..INPUT_FILENAME) do
if ( line:find(" adheres to the ground.", 28, true) ) then
now = parseTime(line);
if ( rootLandTime > 0 and (now - rootLandTime) < 200 ) then
clips = clips + 1;
end
rootLandTime = now;
rootEndTime = 0;
elseif ( line:find("spell has worn off of ", 28, true) ) then
rootEndTime = parseTime(line);
elseif ( line:find("Your target resisted the ", 28, true) or line:find(" resisted your ", 28, true) ) then
resists = resists + 1;
end
if ( rootLandTime > 0 and rootEndTime > 0 ) then
rootDuration = rootEndTime - rootLandTime;
roots = roots + 1;
avgTime = avgTime + rootDuration;
rootLandTime = 0;
rootEndTime = 0;
rootMin = math.floor(rootDuration / 60);
rootsec = rootDuration % 60;
if ( rootsec < 10 ) then
rootsec = "0"..rootsec;
end
rootTicks = math.floor(rootDuration / 6);
if ( rootTicks > maxDurationTicks ) then
maxDurationTicks = rootTicks;
end
if ( minDurationTicks > rootTicks ) then
minDurationTicks = rootTicks;
end
if ( rootDuration > maxDurationSecs ) then
maxDurationSecs = rootDuration;
end
if ( minDurationSecs > rootDuration ) then
minDurationSecs = rootDuration;
end
table.insert(rootTicksTbl, rootTicks);
table.insert(rootDurations, rootDuration);
end
end
if ( roots > 0 ) then
print(INPUT_FILENAME);
avgTicks = avgTime / roots / 6;
avgTicks = math.floor(avgTicks * 10) / 10;
s = "Tick durations: ";
for i, tick in ipairs(rootTicksTbl) do
s = s..tick..", ";
if ( tick == maxDurationTicks or tick == (maxDurationTicks - 1) ) then
maxRoots = maxRoots + 1;
end
end
--print(s);
print(string.format("Landed roots: %i; max duration: %i ticks; avg duration: %0.1f ticks (%0.2f%%); max roots: %i (%0.2f%%)",
roots, maxDurationTicks, avgTicks, avgTicks/maxDurationTicks*100, maxRoots, maxRoots/roots*100));
if ( resists > 0 ) then
print(string.format("Resists: %i (%0.2f%%)", resists, resists/(resists+roots)*100));
end
if ( clips > 0 ) then
print("Clips: "..clips);
end
--[[
avgTime = avgTime / roots;
avgTime = math.floor(avgTime * 10) / 10;
s = "Second durations: ";
for i, tick in ipairs(rootDurations) do
s = s..tick..", ";
end
print(s);
print(string.format("min duration: %is; max duration: %i s; avg duration: %i s (%0.2f%%)",
minDurationSecs, maxDurationSecs, avgTime, avgTime/maxDurationSecs*100));
]]
end
|