------------------------------------------ -- DYNAMIC SPKIND TIER BUFF COUNTER -- + TIER REDUCTION (2602) -- + ON-HIT -> APPLY 2602 (DamageCount via ExecDamage / c0000.lua) -- -- How it works: -- - Active mode picked by equipped weapon spkind -- - Trigger Speffect: 2600 (counts, pulse-based) -- - Reset Speffect: 2601 (hard reset) -- - Reduce Speffect: 2602 (drops tier by mode.tierReduce) with cooldown anti-double-fire -- - NEW: if mode.hitReduceOnDamage = true, then when DamageCount increases (player got hit) -- it will act(AddSpEffect, 2602) ONCE per hit, and the existing reduce logic consumes it. ------------------------------------------ local SPEFFECT_COUNTER_TRIGGER = 2600 local SPEFFECT_COUNTER_RESET = 2601 local SPEFFECT_TIER_REDUCE = 2602 local TRIGGER_PULSE_INTERVAL_SEC = 0.20 local REDUCE_COOLDOWN_SEC = 0.35 -- EASY TO EXTEND: add more entries here local COUNTER_MODES = { { spkind = 648, -- Gustave windowSec = 30.0, tiers = 5, tierStart = 2610, triggersPerTier = 3, tierReduce = 1, baseBuff = -1, powerBuff = -1, -- NEW: hitReduceOnDamage = true, -- getting hit applies 2602 hitReduceSpeffect = 2602 }, { spkind = 642, -- Frenzy Katana windowSec = 30.0, tiers = -1, tierStart = -1, triggersPerTier = -1, tierReduce = 1, baseBuff = 430010, powerBuff = 430012, -- NEW: hitReduceOnDamage = false, hitReduceSpeffect = -1 }, { spkind = 643, -- Gunblade windowSec = 30.0, tiers = -1, tierStart = -1, triggersPerTier = -1, tierReduce = 1, baseBuff = 430000, powerBuff = 430002, -- NEW: hitReduceOnDamage = false, hitReduceSpeffect = -1 } } -- State g_CounterActiveSpkind = 0 g_CounterTimer = 0.0 g_CounterTriggers = 0 g_CounterTriggerHoldT = 0.0 g_CounterLastTierApplied = 0 g_ReduceCooldownT = 0.0 -- NEW (DamageCount-based hit detect) g_OnHit_LastDamageCount = -1 local function DBG(msg) ExposePrint("[COUNTER DEBUG] " .. tostring(msg)) end local function GetHandSpkind(hand) return env(GetEquipWeaponSpecialCategoryNumber, hand) end local function FindModeBySpkind(spkind) for _, m in ipairs(COUNTER_MODES) do if m.spkind == spkind then return m end end return nil end local function GetActiveMode() local r = GetHandSpkind(HAND_RIGHT) local m = FindModeBySpkind(r) if m ~= nil then return m end local l = GetHandSpkind(HAND_LEFT) return FindModeBySpkind(l) end local function ClearTierRange(mode) if mode == nil then return end for i = 0, (mode.tiers - 1) do act(ClearSpEffect, mode.tierStart + i) end end local function ClearAllModeTiers() for _, m in ipairs(COUNTER_MODES) do ClearTierRange(m) end end local function ClearAllBaseBuffs() for _, m in ipairs(COUNTER_MODES) do if m.baseBuff ~= nil and m.baseBuff >= 0 then act(ClearSpEffect, m.baseBuff) end end end local function SyncBaseBuff(mode) if mode == nil or mode.baseBuff == nil or mode.powerBuff == nil then return end if mode.baseBuff < 0 or mode.powerBuff < 0 then return end if env(GetSpEffectID, mode.powerBuff) == TRUE then act(ClearSpEffect, mode.baseBuff) else act(AddSpEffect, mode.baseBuff) end end local function HardReset(reason) DBG("RESET -> " .. tostring(reason)) g_CounterTimer = 0.0 g_CounterTriggers = 0 g_CounterTriggerHoldT = 0.0 g_CounterLastTierApplied = 0 g_CounterActiveSpkind = 0 g_ReduceCooldownT = 0.0 g_OnHit_LastDamageCount = -1 ClearAllModeTiers() ClearAllBaseBuffs() act(ClearSpEffect, SPEFFECT_COUNTER_TRIGGER) act(ClearSpEffect, SPEFFECT_COUNTER_RESET) act(ClearSpEffect, SPEFFECT_TIER_REDUCE) end -- Bucket math: every tier needs triggersPerTier to advance (tier = floor(triggers / tpt)) local function GetTierFromTriggers(triggers, mode) local tpt = mode.triggersPerTier or 3 if triggers <= 0 then return 0 end local tier = math.floor(triggers / tpt) if tier > mode.tiers then tier = mode.tiers end return tier end local function ApplyTierIfChanged(mode) local tier = GetTierFromTriggers(g_CounterTriggers, mode) if tier <= 0 then if g_CounterLastTierApplied ~= 0 then ClearTierRange(mode) g_CounterLastTierApplied = 0 end return end if tier ~= g_CounterLastTierApplied then DBG("TIER CHANGE | spkind=" .. tostring(mode.spkind) .. " | " .. tostring(g_CounterLastTierApplied) .. " -> " .. tostring(tier)) ClearTierRange(mode) act(AddSpEffect, mode.tierStart + (tier - 1)) g_CounterLastTierApplied = tier end end local function CountOneTrigger(mode) local prev = g_CounterTriggers local tpt = mode.triggersPerTier or 3 local maxTriggers = tpt * mode.tiers local next = prev + 1 if next > maxTriggers then next = maxTriggers end g_CounterTriggers = next g_CounterTimer = mode.windowSec DBG("TRIGGER COUNTED | spkind=" .. tostring(mode.spkind) .. " | triggers: " .. tostring(prev) .. " -> " .. tostring(next) .. " | tier=" .. tostring(GetTierFromTriggers(next, mode)) .. " | window reset=" .. tostring(mode.windowSec)) ApplyTierIfChanged(mode) end local function TriggersFromTier(tier, mode) local tpt = mode.triggersPerTier or 3 if tier <= 0 then return 0 end return tier * tpt end local function TryReduceTier(mode, dt) -- cooldown tick if g_ReduceCooldownT > 0.0 then g_ReduceCooldownT = g_ReduceCooldownT - dt if g_ReduceCooldownT < 0.0 then g_ReduceCooldownT = 0.0 end end local active = env(GetSpEffectID, SPEFFECT_TIER_REDUCE) if active ~= TRUE then return end if g_ReduceCooldownT > 0.0 then return end local curTier = GetTierFromTriggers(g_CounterTriggers, mode) if curTier <= 0 then g_ReduceCooldownT = REDUCE_COOLDOWN_SEC act(ClearSpEffect, SPEFFECT_TIER_REDUCE) return end local reduce = mode.tierReduce or 1 if reduce < 1 then reduce = 1 end local newTier = curTier - reduce if newTier < 0 then newTier = 0 end DBG("TIER REDUCE | spkind=" .. tostring(mode.spkind) .. " | " .. tostring(curTier) .. " -> " .. tostring(newTier) .. " (reduce=" .. tostring(reduce) .. ")") if newTier == 0 then g_CounterTriggers = 0 g_CounterTimer = 0.0 else g_CounterTriggers = TriggersFromTier(newTier, mode) g_CounterTimer = mode.windowSec end ApplyTierIfChanged(mode) g_ReduceCooldownT = REDUCE_COOLDOWN_SEC act(ClearSpEffect, SPEFFECT_TIER_REDUCE) end -- NEW: On-hit -> apply 2602 once per damage event (DamageCount increments) local function TryHitReduce(mode) if mode == nil or mode.hitReduceOnDamage ~= true then return end local dc = GetVariable("DamageCount") if dc == nil then dc = 0 end if g_OnHit_LastDamageCount < 0 then g_OnHit_LastDamageCount = dc return end -- handle ResetDamageCount / wrap if dc < g_OnHit_LastDamageCount then g_OnHit_LastDamageCount = dc return end if dc > g_OnHit_LastDamageCount then act(AddSpEffect, mode.hitReduceSpeffect or SPEFFECT_TIER_REDUCE) g_OnHit_LastDamageCount = dc end end function CounterBuffTiersDynamic() local dt = GetDeltaTime() local mode = GetActiveMode() if mode == nil then if g_CounterTimer > 0.0 or g_CounterTriggers > 0 or g_CounterActiveSpkind ~= 0 then HardReset("No active spkind mode") else act(ClearSpEffect, SPEFFECT_COUNTER_TRIGGER) act(ClearSpEffect, SPEFFECT_TIER_REDUCE) ClearAllBaseBuffs() end return end if g_CounterActiveSpkind ~= 0 and g_CounterActiveSpkind ~= mode.spkind then HardReset("Mode changed " .. tostring(g_CounterActiveSpkind) .. " -> " .. tostring(mode.spkind)) mode = GetActiveMode() if mode == nil then return end end g_CounterActiveSpkind = mode.spkind -- base buff SyncBaseBuff(mode) -- NEW: on-hit applies 2602 when allowed by mode (seamless with reduce handler below) TryHitReduce(mode) -- window tick if g_CounterTimer > 0.0 then g_CounterTimer = g_CounterTimer - dt if g_CounterTimer <= 0.0 then HardReset("Window expired (spkind=" .. tostring(mode.spkind) .. ")") return end end -- reset speffect if env(GetSpEffectID, SPEFFECT_COUNTER_RESET) == TRUE then HardReset("Reset speffect 2601 active") return end -- reduce speffect (2602) TryReduceTier(mode, dt) -- trigger speffect (2600) overlap-safe local trigActive = env(GetSpEffectID, SPEFFECT_COUNTER_TRIGGER) if trigActive == TRUE then g_CounterTriggerHoldT = g_CounterTriggerHoldT + dt if g_CounterTriggerHoldT < dt + 0.0001 then CountOneTrigger(mode) end while g_CounterTriggerHoldT >= TRIGGER_PULSE_INTERVAL_SEC do g_CounterTriggerHoldT = g_CounterTriggerHoldT - TRIGGER_PULSE_INTERVAL_SEC CountOneTrigger(mode) end act(ClearSpEffect, SPEFFECT_COUNTER_TRIGGER) else g_CounterTriggerHoldT = 0.0 end -- base buff again (in case powerBuff toggled this frame) SyncBaseBuff(mode) end