g_DeltaTimeCount = 0 local frameCount = 100 local Profiler = { enabled = false, -- cumulative time based solely on GetDeltaTime() current = 0, stats = { }, active = { }, numberFilter = 0 } local function timeStringToSeconds(s) if type(s) == "number" then return s end if type(s) ~= "string" then return 0 end s = s:gsub("%s+", "") -- match H:M:S[.frac] local h, m, sec = s:match("^(%d+):(%d+):(%d+%.?%d*)") if h and m and sec then return tonumber(h) * 3600 + tonumber(m) * 60 + tonumber(sec) end -- fallback: try plain number return tonumber(s) or 0 end function Profiler:start(name) if not self.enabled then return end local s = self.stats[name] if s == nil then s = { calls = 0, time = 0, frames = 0 } self.stats[name] = s end s.calls = s.calls + 1 -- sample wall-clock using GetTime() for high-resolution timing local ok, val = pcall(GetTime) local prof = (ok and timeStringToSeconds(val)) or 0 self.active[name] = { prof = prof } end function Profiler:stop(name) if not self.enabled then return end local s = self.stats[name] if s == nil then return end local startv = self.active[name] if startv == nil then return end local ok, val = pcall(GetTime) local now = (ok and timeStringToSeconds(val)) or 0 local startprof = timeStringToSeconds(startv.prof) local d = now - startprof -- handle midnight wrap (GetTime from %time% yields seconds-of-day) if d < 0 then d = d + 24 * 3600 end if d > 0 then s.time = s.time + d else s.time = s.time + 0.000001 end self.active[name] = nil end function Profiler:report() if not self.enabled then return end Log("--- Profiler report (frame", frameCount, ") ---") for k,v in pairs(self.stats) do Log("sec=", string.format("%.6f", v.time), "calls=", v.calls, k) end Log("--- end profiler report ---") end local nameCache = setmetatable({}, { __mode = "k" }) local function inferNameFromFunction(fn) if type(fn) ~= "function" then return tostring(fn) end local cached = nameCache[fn] if cached then return cached end local ok, info = pcall(debug.getinfo, fn, "nSl") local name = tostring(fn) if ok and info then if info.name and info.name ~= "" then name = info.name elseif info.short_src and info.linedefined then name = info.short_src .. ":" .. tostring(info.linedefined) end end nameCache[fn] = name return name end local unpack = table.unpack or unpack local function Do(fn, ...) if not Profiler.enabled then return fn(...) end local name = inferNameFromFunction(fn) Profiler:start(name) local results = { pcall(fn, ...) } local ok = table.remove(results, 1) Profiler:stop(name) if not ok then error(results[1]) end return unpack(results) end local doOnce = false local doOnceLocal = false local doOnceLocalDelayed = false function ModdedUpdate() frameCount = frameCount + 1 g_DeltaTimeCount = g_DeltaTimeCount + GetDeltaTime() if not doOnce then doOnce = true act(AddSpEffect, 295940) -- Tarnished Detection for Spirit Summons end Do(TrickWeaponHandler, 4000200, 18200000, 18205000, Blightpole_Cooldown, false, false) -- Blightpole - Basic to Catalyst if IsLocalPlayer() then LocalPlayerOnly() elseif env(IsCOMPlayer) == TRUE then ModdedEnemyUpdate() end if not StaggerExecution(6) then return end Do(DragonCultDaggersOffHandler) end local aspectOfTheScorpion_timingOffset1 = { 0 } local aspectOfTheScorpion_timingOffset2 = { 0 } function LocalPlayerOnly() if not doOnceLocal then doOnceLocal = true -- Log("Player Loaded") Do(CultSpellStack_Visibility) Do(SetMagicParam, 6541, 6540) -- To make recall not active spell on player load Do(LoadingScreenHandler) Do(DateDetectorLib) end if not doOnceLocalDelayed and g_DeltaTimeCount > 0 then doOnceLocalDelayed = true Do(InventoryManagerInit) end -- if StaggerExecution(401) then -- Profiler:report() -- end -- Update rate: 60x p/second --Do(ObserveSpEffects) Do(MonitorPosition) Do(RecallAsh) Do(VerdigrisThrowPrevent) -- Update rate: 30x p/second if not StaggerExecution(2) then return end Do(TrackHP) Do(ManaShield) Do(DoubleJump) -- Kmstr is going to work this with behaviours --Do(AnimationLogger) Do(CultSpellStack_Update) Do(LivingForge_Update) Do(SaveSpEffect, weaponBuffSpEffects) -- Needs optimzation with Kmstr Do(AnimSpeedupLib) Do(NecroFistsVFXManager) -- Can be moved entirely to params (together with Kmstr) Do(AberrantJewel_AshHandler) -- In Specific Ashes their OnActivate (together with Kmstr) -- Update rate: 10x p/second if not StaggerExecution(6) then return end Do(DisableSpellsOnStatLoss) Do(DisableSpellOnUnequip) -- Can mebe be optimized with ESD and BEH edits Do(FrenzyJewel_FrenzyMonitor) Do(CycleGetInventoryItems, GAME_BASE.GAME_DATA_MAN.PLAYER_GAME_INFO.STORAGE_INVENTORY) Do(PrisonerJewelLib) Do(ForceGestureLib) --Do(DeathblightProcHandler) Do(AspectOfTheScorpion_WeaponBuff, 1729050, HAND_RIGHT, HAND_LEFT_BOTH, aspectOfTheScorpion_timingOffset1) Do(AspectOfTheScorpion_WeaponBuff, 1729055, HAND_LEFT, HAND_RIGHT_BOTH, aspectOfTheScorpion_timingOffset2) Do(BloodInitiateJewel_BloodflameBlade, 1632010, HAND_RIGHT, HAND_LEFT_BOTH, 162) Do(BloodInitiateJewel_BloodflameBlade, 1632020, HAND_LEFT, HAND_RIGHT_BOTH, 163) Do(FrostbiteProcHandler) Do(NoGreatRuneDetector) -- See DisableSpellOnUnequip Do(ToggleTutorials) -- Update rate: 2x p/second if not StaggerExecution(30) then return end Do(SummonActive_SpEffect) Do(NoxJewel_OathOfDarknessCostReduction) Do(ToggleSpellFPCostManager) Do(ShellRingMarksmanLockonRange) Do(FrostbiteProcHandler) Do(TwoHandingDetector) Do(PowerstanceDetector) Do(MountChangeLib) -- See DisableSpellOnUnequip Do(MagicWeaponDetection) Do(OpenURLLib) Do(ResetStats) -- See DisableSpellOnUnequip Do(WeaponDuplication) -- See DisableSpellOnUnequip Do(InventoryUpdateRequested) Do(InventoryUpdateRequestedExistingItem) -- Update rate: 1x p/second if not StaggerExecution(60) then return end Do(HeldWeaponTypeDetection) Do(FlamingArmamentFaithListener) Do(RL1_SpEffect) -- DoOnce + See DisableSpellOnUnequip Do(DisableRaytracing) Do(JewelSpellBan) Do(HitboxVisualizer) -- Disable on release Do(LogDivider) -- Disable on release end local doOnceEnemy = false function ModdedEnemyUpdate() frameCount = frameCount + 1 g_DeltaTimeCount = g_DeltaTimeCount + GetDeltaTime() if not doOnceEnemy and frameCount < 1000 then doOnceEnemy = true Do(InitializeImmunities) Do(HasNoSleepAnim) end -- if StaggerExecution(401) then -- Profiler:report() -- end --Do(AnimationLogger) --Do(ObserveSpEffects) if not StaggerExecution(2) then return end Do(EnemyAnimSlowdownLib) -- needs to stay here because it needs to evaluate the slowdown Do(SetRootMotionMultiEnemy) -- "" "" to evaluate the root motion Do(FreezeEffect, false) -- "" "" to handle both the freeze and unfreeze Do(FearEffect, 6950, 6955) -- "" "" to handle walking forward if not StaggerExecution(6) then return end Do(QuicksilverHuskHpCheck) -- Needs to be placed in custom HKS Do(RemoveSleepDebuffAndImmunity) Do(SleepLoopImmunityApplication) -- Can this be placed somewhere better? Do(HasNoSleepAnimDebuff) -- Can this be placed somewhere better? Do(PsudoDisableAI, 1515000, true, true) -- needs to stay here because it needs to handle both the turn on and off Do(PsudoDisableAI, 1515500, true, true) Do(PsudoDisableAI, 100656, false, false) Do(TranquilityAiToggle) Do(DeathblightProcHandler) Do(ReapplySpEffectTillIdle, 1515000, 1) -- needs to stay here because it needs to evaluate the reapplication Do(ReapplySpEffectTillIdle, 1515500, 1) Do(ReapplySpEffectTillIdle, 1749100, 10) Do(SoulTether) -- needs to stay here because it needs to evaluate hp loss if not StaggerExecution(30) then return end Do(FrostbiteProcHandler) Do(FrostWitchFreezeEffect, 1449410, 0, 1449412, 1449411) -- Freeze Solid Do(LogDivider) if not StaggerExecution(120) then return end Do(ImmunityStripping) end local blockingFuncs = { { name = "EnemyFrenzyProcHandler" }, { name = "DeathblightProcHandler" }, { name = "NecromancerJewel_DeathblightHandler" }, -- Can this be merged with the generic? { name = "FrostbiteProcHandler" }, { name = "FulminatedQuicksilver" }, { name = "CurseOfShadow" }, { name = "BloodOfTheMaiden" }, { name = "InvocationOfSaintTrina" }, { name = "Tranquility" }, { name = "SeverTheThread" }, { name = "TibiasConversionHandler" }, { name = "FrostWitchFreezeEffect", args = { 1449410, 0, 1449412, 1449411 } }, -- Freeze Solid { name = "ToxicRupture" }, { name = "PestAuxLogic", args = { 1720000, 15 } }, -- Pest Threads { name = "PestAuxLogic", args = { 1720100, 75 } }, -- Pest Darts { name = "PestAuxLogic", args = { 1720200, 35 } }, -- Pest Spears { name = "PestAuxLogic", args = { 1720300, 07 } }, -- Pest Storm } local blockingFuncCache = {} local function ResolveBlockingFunc(entry) local cached = blockingFuncCache[entry.name] if cached == dummy then return nil elseif cached ~= nil then return cached end local resolvedFunc = _G[entry.name] if type(resolvedFunc) ~= "function" or resolvedFunc == dummy then act(10001, "[WARNING] Blocking function not found: " .. entry.name .. " (in OnEnemyGetAttacked)") blockingFuncCache[entry.name] = dummy return nil end blockingFuncCache[entry.name] = resolvedFunc return resolvedFunc end function OnEnemyGetAttacked() -- blocking functions for i, func in ipairs(blockingFuncs) do local resolvedFunc = ResolveBlockingFunc(func) if resolvedFunc ~= nil then local result = nil if func.args then result = resolvedFunc(unpack(func.args)) else result = resolvedFunc() end if result == TRUE then return TRUE end end end -- non-blocking functions -- likely on weapon buff like spEffect logic ShellRingFlameProphetBetterImmolation() FrostWitchFreezeEffect(1441530, 5, 1441525, 1441520) -- Ice Guardian's Reckoning BloodflameBladeBleedHandler() BestialJewelImprovedBuffHandler(550100, 550150) -- Bestial Jewel: Rend BestialJewelImprovedBuffHandler(550200, 550250) -- Bestial Jewel: Concussion return FALSE end function StaggerExecution(operand) return frameCount % operand == 0 end function PositionTrackerReset(force) if force or env(GetSpEffectID, 2054) == TRUE then act(ClearSpEffect, 2054) POSITION_CONTAINER = {} _markRecallData = {} end end function GetTime() local handle = io.popen("echo %time%") local result = handle:read("*a") handle:close() return result end