Commit Graph

3752 Commits

Author SHA1 Message Date
brues-code ed06968fa3 C_NamePlate (#1)
* nameplates: drive discovery + GUID lookup via C_NamePlate

ClassicAPI now provides the modern C_NamePlate API and the matching
NAME_PLATE_CREATED / NAME_PLATE_UNIT_ADDED / NAME_PLATE_UNIT_REMOVED
events. Replace the WorldFrame:GetChildren polling + manual GUID
dictionary with event-driven plumbing.

- Drop the per-tick WorldFrame:GetNumChildren / GetChildren scan
- Delete IsNamePlate() border-texture filter (events only deliver
  nameplate frames)
- Delete the guidRegistry table + its OnShow/OnUpdate maintenance;
  every lookup now goes through C_NamePlate.GetNamePlateForGUID
- Move per-GUID cache cleanup (debuffCache / threatMemory /
  combatColorCache / libdebuff_casts) from the OnUpdate visibility
  scan to a NAME_PLATE_UNIT_REMOVED handler — fires exactly once
  per unit going out of range
- NAME_PLATE_CREATED drives nameplates.OnCreate; NAME_PLATE_UNIT_ADDED
  sets cachedGuid and drives nameplates.OnShow (so OnCreate no longer
  calls OnShow directly and no longer HookScripts the parent)
- visiblePlateCount uses table.getn(C_NamePlate.GetNamePlates())

Net -64 lines, with a meaningful reduction in per-tick CPU (no more
WorldFrame child iteration + region/texture filter every 50ms).

* nameplates: switch UNIT_ADDED/REMOVED handlers to nameplateN tokens

ClassicAPI's NAME_PLATE_UNIT_ADDED / _REMOVED events now ship the
"nameplateN" unit token as arg1 (matching modern WoW) instead of the
GUID string.

- UNIT_ADDED: resolve plate via C_NamePlate.GetNamePlateForUnit(arg1);
  derive the cache-key GUID via UnitGUID(arg1)
- UNIT_REMOVED: same lookup pattern; compute UnitGUID(arg1) inside the
  handler before the slot recycles (per docs guarantee)

The other six GetNamePlateForGUID call sites (libdebuff aura callbacks,
UNIT_FLAGS_GUID, PLAYER_TARGET_CHANGED, combo-point handler, castbar
frame) stay on the GUID flavor — they receive real GUIDs from other
event paths.

---------

Co-authored-by: Brues <5278969+brues-code@users.noreply.github.com>
2026-05-27 00:40:31 -05:00
Brues aaba09a17c GetContainerItemID directly 2026-05-25 20:22:07 -05:00
Brues 710be52f35 phase 3 removing tbc
Drop the now-vestigial expansion plumbing.

- Delete modules/thirdparty-tbc.lua + its xml Include
- Strip 10 tbc-tagged CreateConfig calls in modules/gui.lua
- Drop the expansion arg from CreateConfig() signature + the disabled-
  entry rendering path that depended on it
- Drop the showdisabled GUI toggle + its default
- Simplify pfUI:RegisterModule / pfUI:RegisterSkin to (name, func) only
- Strip the leading version arg ("vanilla:tbc", etc.) from all 114
  Register call sites
- Delete the pfUI.expansion variable
2026-05-25 18:50:53 -05:00
Brues ba566cdd53 phase 2 removing tbc
Strip dead pfUI.client > 11200 and pfUI.expansion == "tbc" branches now
that vanilla is the only supported client.

- pfUI.lua: hardcode expansion = "vanilla"; collapse force_region matrix
- compat/vanilla.lua, libs/{libcast,libdebuff,libtotem,libunitscan,
  focus}: drop always-false early-returns
- api/unitframes.lua: delete TargetBuffOnUpdate poller + tbc visibility
  and secure-template branches; click-cast keeps only vanilla path
- api/ui-widgets.lua: unconditional EffectiveScale divide
- env/tables.lua: drop unused focus/focustarget pfValidUnits entries
- modules/actionbar.lua: simplify EnablePaging, drop ButtonSwitch/
  petvisibility, unwrap prowl block, single keybind-remap path
- modules/{castbar,chat,cooldown,loot,questitem,totems}: unwrap
  always-true conditionals
- modules/nameplates.lua: cfg-only fake-cooldown gate, unwrap overlap/
  clickthrough + mouselook blocks
- modules/friends.lua + skins/blizzard/friends.lua: maxtab = 4
- skins/blizzard/{help,game_menu,questlog}: drop tbc-only branches
2026-05-25 18:40:17 -05:00
Brues 58609b8c5c phase 1 removing tbc 2026-05-25 18:16:02 -05:00
Brues 199842e220 prevent 'npctarget' invalid unit calls 2026-05-23 15:59:09 -05:00
Brues 1d27f7bed3 class color cleanup 2026-05-23 02:56:00 -05:00
Brues 90fdd8cfc3 typo 2026-05-22 22:50:41 -05:00
Brues 7c752f0264 xpbar: rested gain rate in per-minute instead of per-hour
Per-hour numbers are too coarse to feel actionable while watching a
tent fill — switching to /min so the displayed rate updates in a more
useful range for the actual sampling cadence.
2026-05-22 22:48:04 -05:00
Brues c30dfae7a6 xpbar: track rested-XP gain rate + time to cap
The data frame now subscribes to UPDATE_EXHAUSTION and stores a sliding
5-minute window of (time, exhaustion) samples. The XP tooltip uses the
first/last samples in the window to compute rest gain per hour, and
projects time to reach UnitXPMax * 1.5 at the current rate.

Useful for Turtle WoW's tent mechanic — when you stand under a tent
you accumulate rested XP at an accelerated rate, and now you can see
how long until you cap.

Samples reset on PLAYER_ENTERING_WORLD (zone change can change the
gain rate) and PLAYER_LEVEL_UP (Blizzard rescales exhaustion
proportionally on level-up so prior samples no longer describe the
same gain curve).

The rate/time lines only appear when there are at least 2 samples in
the window and the gain is positive — so they stay hidden when not
actively gaining rested XP.
2026-05-22 22:40:35 -05:00
Brues 00a3e2f691 tracking: PlayerIsDruidInCatForm via GetShapeshiftFormID
The byte-2-of-UNIT_BYTES_1 extraction this function did manually
(math.floor(b/65536) - math.floor(b/16777216)*256 on the GetUnitField
return) is precisely what GetShapeshiftFormID reads under the hood —
descriptor +0x212. Collapses to a one-liner that doesn't need to
mirror the engine's byte layout in Lua.
2026-05-21 17:30:39 -05:00
Brues 11f9cd848b tooltip:GetUnit via HasUnit + UnitTokenFromGUID
GameTooltip:HasUnit() / GameTooltip:GetUnitGUID() ask the tooltip
directly what unit it's displaying (returns the GUID), and
UnitTokenFromGUID resolves that to a unit token. So instead of
iterating mouseover/player/pet/target plus party1..4, partypet1..4,
raid1..40, raidpet1..40 and string-matching each against
GameTooltipTextLeft1:GetText(), this is now a three-line lookup.

Bonus: drops the UnitName / UnitPVPName fallback string match (which
broke on duplicate names across the realm and on tooltips showing PVP
titles that didn't exactly match a token's UnitPVPName output). GUID
is canonical.
2026-05-21 17:27:27 -05:00
Brues 2619b0bd77 autovendor: all paths through C_MerchantFrame
Two-part collapse:

Button-click sell path: was a 0.1s-per-item manual iteration via
UseContainerItem (with processed[] tracking + a merchant-state flag +
OnShow/OnUpdate/OnHide handler trio). Now calls SellJunkAndReport
which fires C_MerchantFrame.SellAllJunkItems and a 0.3s deferred
GetMoney delta reporter. Engine handles per-frame sell pacing.

Button enable/disable check: HasGreyItems() iterated bags 0..4 with
C_Item.GetItemQuality manually. Replaced with
C_MerchantFrame.GetNumJunkItems() > 0 (the engine's canonical answer
to the same question). The button's Update only runs from MERCHANT_SHOW
/ MERCHANT_UPDATE handlers so the "merchant must be open" gate on
GetNumJunkItems is always satisfied.

Side effect: the MERCHANT_SHOW auto-sell path NOW reports income to
chat. Previously only the manual button reported it.

Deleted: HasGreyItems, processed/count/gold state, GetNextGreyItem,
autovendor.merchant flag, OnShow/OnUpdate/OnHide handlers, the
MERCHANT_CLOSED branch.

170 → 104 lines.
2026-05-21 17:24:14 -05:00
Brues 802d47312f pfSellData: drop sell column, use C_Item.GetItemSellPriceByID
Each pfSellData entry was a "sell,buy" string. The sell price duplicates
what C_Item.GetItemSellPriceByID returns from the engine's item DBC, so
it's redundant in our table — only the buy price (curated from real
vendor encounters, not a static item property) needs to live here.

Transformed every [id]="X,Y" entry across env/tables.lua, compat/tbc.lua,
and modules/turtle-wow.lua to [id]=Y via sed. Format is now itemid →
buyPrice (number, copper).

sellvalue.lua: reads sell from C_Item.GetItemSellPriceByID(id), reads
buy from pfSellData[id]. Items that have only a sell price (not in
pfSellData) now display sell-only — previously they got no tooltip
addition because the lookup gated on table membership. Items with
buy-only (sell == 0 in old data) still display buy correctly.

autovendor.lua: replaces the pfSellData[id] gate (which only checked
table membership and unused the parsed sell/buy) with a direct
C_Item.GetItemSellPriceByID > 0 check — the engine's canonical
"is this item sellable to a vendor" answer.
2026-05-21 17:19:29 -05:00
Brues d27db55abf modernize monkey-patched hooks to hooksecurefunc
sellvalue.lua: SetItemRef hook follows the same pattern questitem.lua
got earlier — replaces the local-Hook + _G.SetItemRef monkey-patch with
hooksecurefunc, drops the string.find link-parser, and reads itemID
directly from ItemRefTooltip:GetItem after the original populates the
tooltip. The OnShow path also moves from libtooltip:GetItemLink/GetItemID
to GameTooltip:HasItem/GetItem (libtooltip:GetItemCount stays — it's
populated by libtooltip's own SetBagItem hook and carries the stack
count, which GetItem doesn't return).

unusable.lua: the two post-hooks (pfUI.bag.UpdateSlot and
BankFrameItemButton_UpdateLock) are textbook hooksecurefunc cases —
both ran the original then did extra work, no conditional skip or
return-value tampering. The table form of hooksecurefunc handles
pfUI.bag.UpdateSlot cleanly.
2026-05-21 17:04:00 -05:00
Brues cca7d649e1 innervatecall: ResolveTargetName via UnitTokenFromGUID
The 30-line GUID → name resolver (manually checking player, target, then
walking raid1..40 / party1..4 for a matching UnitGUID) collapses to
calling UnitTokenFromGUID and then UnitName on the returned token.
ClassicAPI's UnitTokenFromGUID walks the same engine unit-token table
internally — same correctness, less Lua.

GetPlayerGuid stays (still used by the AURA_CAST event filter).
2026-05-21 16:57:12 -05:00
Brues 554c447514 swingtimer HS/Cleave detection via spell name compare
GetActionInfo + GetMacroSpell resolve each action slot to a spell name
(or skip if it's an item / empty / non-resolvable macro). The names to
match against come from C_Spell.GetSpellName on the canonical rank-1
spellIDs (78 = Heroic Strike, 845 = Cleave) so the comparison is
locale-independent without hardcoding all ranks: every rank of Heroic
Strike returns the same localized name.

Replaces the old two-path scan (texture string match + hardcoded
English name match against "heroic strike" / "hs" / "cleave"). Closes
two correctness holes the old logic had: macros that cast HS/Cleave
with a custom icon now match correctly, and macros merely *named* "HS"
without actually casting HS no longer false-match.
2026-05-21 16:53:26 -05:00
Brues 314d89e30d librange.GetRangeSlot via GetActionInfo + GetMacroSpell
Replaces the texture-from-action + "no macro text" heuristic with a
proper resolve-to-spellID step. Macros that cast a 40y heal but
display a non-spell icon (custom macro icon, /castsequence wrappers,
etc.) now match correctly — the previous code missed them because
GetActionTexture returns the macro's icon, not the underlying spell's.

C_Spell.GetSpellTexture(spellID) returns the spell's intrinsic icon
path, which is what the class-spell table is keyed on, so the
texture-comparison logic is unchanged.
2026-05-21 16:48:02 -05:00
Brues 2f17546c1a UseAction hooks: GetActionInfo + GetMacroSpell, drop tooltip scans
The three UseAction hooks in libtotem / libpredict / libcast all did
the same dance: filter out macros via GetActionText, then tooltip-scan
the action to recover the spell name + rank. ClassicAPI's
GetActionInfo + GetMacroSpell make both steps unnecessary:

- GetActionInfo returns ("spell", spellID) or ("macro", macroSlot)
- GetMacroSpell resolves a macroSlot to (name, rank, spellID) at the
  highest known rank, parsed engine-side at macro save time

So macros are no longer skipped — they resolve to their cast spell
just like a raw spell action. libtotem queue gains a real spellID
(the previous "no spellId available, icon-based fallback" comment is
obsolete). libcast also wins: it can feed the resolved spellID
through FindSpellBookSlotByID + libspell.GetSpellInfo(slot, "spell")
without the name-with-rank-suffix string concat round-trip.

Drops libtipscan:GetScanner("prediction") usage in libtotem and
libpredict, and libtipscan:GetScanner("libcast") in libcast.
2026-05-21 16:45:57 -05:00
Brues 8e635afeed GetItemCount via C_Item, reagents via C_Spell.GetSpellReagents
Drops the legacy pfUI.api.GetItemCount wrapper (name-based bag walk +
GetItemInfo per slot) entirely — both call sites switch to direct
C_Item.GetItemCount(itemID) calls.

actionbar reagent counter: drops the SPELL_REAGENTS tooltip-scan +
libtipscan dependency. UpdateSlot now does GetActionInfo(slot) →
spellID → C_Spell.GetSpellReagents → reagent itemID, stored per slot.
Counts update via C_Item.GetItemCount(itemID). reagent_counts is
keyed by itemID instead of localized name string. Multi-reagent
spells now resolve cleanly (only first reagent counted, matching old
single-name behavior). Macro and bag-item actions skip cleanly.

questitem: switches to hooksecurefunc("SetItemRef", ...) + the
modern GameTooltip:HasItem / :GetItem pair instead of monkey-patching
_G.SetItemRef and string.find-parsing the "item:NNN" out of the link.
Drops the libtooltip indirection for the regular tooltip path —
GameTooltip:GetItem returns name + itemID directly. AddTooltip
extended to accept the itemID (both callers already had it and were
discarding it).
2026-05-21 16:20:19 -05:00
Brues 1012a67865 librange: rely on SuperWoW UnitPosition
Drops the three other range-check paths and the target-juggling
machinery they required:

- Nampower IsSpellInRange branch + spellbook-scan to find the spell name
- UnitXP_SP3 distance check (api.lua's UnitInRange wrapper still owns
  the UnitXP precise mode independently)
- Vanilla IsActionInRange fallback that briefly retargeted via
  TargetUnit / TargetLastTarget

With target-juggling gone, this also deletes the support scaffolding it
required: PlaySound override, TargetFrame_OnEvent swap, ReAttack /
lastattack restoration, the wand and combo-points detection frames
(both were suspension guards for target-juggling), the loot/inspect/
trade/combat skip-checks, librange's pfScanActive set/clear (focus.lua
still uses its own), and the librange_isLoggingOut crash workaround
the target-flip path needed.

Target case still uses IsActionInRange (vanilla-native, works for
hostile targets too); friendly party/raid/pet scan uses UnitPosition.
The friendly-only restriction on UnitPosition isn't an issue because
the scan list contains only friendlies anyway.

DPS classes (no 40y healing spell in `spells[class]`) now also get
party/raid range coloring; they only miss the target case, which is
unchanged from before.

328 → 166 lines.
2026-05-21 15:41:33 -05:00
Brues e1cd1ef179 mount cancel via Dismount
Replaces the Phase 1 buff-slot loop + tooltip-scanned mount-string match
with a single Dismount() call. The implementation in ClassicAPI scans
for SPELL_AURA_MOUNTED on the player's auras and sends the cancel
packet itself, so we don't have to maintain a localized list of mount
tooltip patterns ("Increases speed by X%", "Erhöht Tempo um X%", etc.).

Drops pfUI.autoshift.mounts (the 20-line localized-string table) and
pfUI.autoshift.scanner (its only consumer). Module is down to ~50 lines
from ~150 at session start.
2026-05-21 10:40:42 -05:00
Brues 0e0a3f3688 shapeshift cancel via CancelShapeshiftForm
Replaces autoshift's Phase 2 (form-ID → texture lookup → buff iteration →
CancelPlayerBuff(bid)) with a single CancelShapeshiftForm() call. The
implementation in ClassicAPI walks the buff slots and matches via
Spell.dbc effect arrays itself, so we don't have to maintain a form-ID
→ texture map on the Lua side.

Drops pfUI.autoshift.shapeshifts entirely (no longer consulted) and the
turtle-wow.lua block that conditionally appended Tree of Life / Stag
Form textures — Turtle's added DBC rows (form IDs 9 and 11) are handled
natively by the same scan.
2026-05-21 10:31:25 -05:00
Brues 0fead7d696 shapeshift detection via GetShapeshiftFormID + UPDATE_SHAPESHIFT_FORM
actionbar.lua: pageswitch now listens to UPDATE_SHAPESHIFT_FORM as the
authoritative form-change signal. PLAYER_AURAS_CHANGED becomes a
narrowly-scoped prowl-detection trigger that runs after form state is
already known. The Nampower SPELL_GO_SELF hook drops its Cat Form
(spellID 768) branch — UPDATE_SHAPESHIFT_FORM covers form changes
without per-spellID watchlists. Prowl IDs stay hooked for zero-latency
detection that PLAYER_AURAS_CHANGED can't match.

autoshift.lua: the texture-list shapeshift detection becomes a form-ID-
keyed map. GetShapeshiftFormID returns the active form directly so we
don't have to texture-match against every possible form pattern. The
moonkin_scan frame disappears entirely — its talent-conditional was a
workaround for the agility buff sharing Moonkin's icon, which form ID
makes irrelevant (form 31 is only reported when the player is genuinely
in Moonkin Form). Loop also splits into two phases for clarity: mount
priority pass, then form cancel.
2026-05-21 04:25:50 -05:00
Brues 6be11baf29 HasCatForm via GetShapeshiftFormID
Drops the C_UnitAuras helpful-aura iteration in favor of a direct engine
state read. GetShapeshiftFormID returns vanilla 1.12.1's stable per-form
DBC ID (Cat = 1), so the helper collapses to a one-liner that's
locale-independent, talent-independent, and works without scanning any
buff list.
2026-05-21 04:04:31 -05:00
Brues 8818d68aea druid prowl detection via IsStealthed
FullScan/HasProwlBuff stop iterating GetPlayerBuffTexture(0..31) for the
prowl buff — that's exactly what ClassicAPI's IsStealthed() returns,
locale-independent and zero allocations. Cat-form detection still needs
the icon (GetShapeshiftForm's index varies by talents/learned forms) but
moves to one C_UnitAuras.GetUnitAuras iteration over populated auras via
the new HasCatForm helper.

The inline cat-form recheck after a prowl ends collapses to one
HasCatForm() call. HasProwlBuff is inlined to IsStealthed() at its
single call site.
2026-05-21 03:39:20 -05:00
Brues 8ad932b529 removed some hardcoded strings 2026-05-21 03:32:18 -05:00
Brues 8c416ae713 indicator system via C_UnitAuras
Both indicator scan loops (preset HoT indicators + custom name-based
indicators) now iterate the populated aura array from
C_UnitAuras.GetUnitAuras instead of brute-forcing UnitBuff(1..32). The
Rejuvenation/Renew/Regrowth disambiguation that previously required
scanner:SetUnitBuff + Line(1) tooltip scans now reads aura.name natively.

HoT-icon → name+predict mappings lifted into a HOT_INDICATORS table at
file scope so future additions are one-line edits.

The custom-debuff loop still defers to libdebuff for caster correlation
when available; C_UnitAuras provides the texture/name backstop. Also
unifies the non-selfdebuff debuff display branches through one
GetDebuffDataByIndex call.

Local `scanner` declaration removed (no remaining tooltip-scan uses in
this file).
2026-05-21 03:26:02 -05:00
Brues 9b2001114d dedup shot-haste loops behind libcast.ApplyShotHaste
The three customcast handlers (Aimed Shot, Multi-Shot, Steady Shot) each
iterated 1..32 UnitBuff slots checking the same four icon paths for
haste-providing buffs. Lifted into libcast.ApplyShotHaste which iterates
the populated auras once via C_UnitAuras.GetUnitAuras and reads from a
single SHOT_HASTE lookup table. Three loop copies collapse to one helper.

Kept texture-keyed matching (rather than spellID) because the Naxx
trinket and Quick Shots procs have spell-ID variants across server
flavors but stable icons.
2026-05-21 03:20:20 -05:00
Brues 2d67781c6f unit-frame tooltip/click handlers via SetUnitAura
BuffOnClick/DebuffOnClick switch to lazy bid lookup before CancelPlayerBuff
(matching the pattern in buff.lua/buffwatch.lua). DebuffOnEnter unifies its
player and non-player branches through GameTooltip:SetUnitAura — the only
divergent path is the selfdebuff caster-correlation lookup that still
needs libdebuff.
2026-05-21 03:20:08 -05:00
Brues 9ba4b3e408 buffwatch via C_UnitAuras
Player branch in GetBuffData now reads from C_UnitAuras.GetAuraDataByIndex
directly, dropping the GetPlayerBuff chain plus the tooltip-scan for the
buff name. Lazy bid resolution for CancelPlayerBuff. All four tooltip
calls (SetPlayerBuff/SetUnitBuff/SetUnitDebuff x2) collapse to the unified
GameTooltip:SetUnitAura. UnitDebuff dispel-color lookup at line 312 reads
aura.dispelName instead. libdebuff path preserved for non-player units
that still need caster correlation.
2026-05-21 03:10:59 -05:00
Brues c733d1b868 use GetItemInfoInstant 2026-05-21 03:10:37 -05:00
Brues 0fe0a64121 libspell uses ClassicAPI GetSpellInfo
GetSpellInfo internals now delegate to ClassicAPI's GetSpellInfo for the
DBC metadata read instead of scraping castTime/range out of the tooltip.
Drops the libtipscan dependency from libspell and the regex-based parsing
of SPELL_CAST_TIME_SEC/MIN/SPELL_RANGE. Same 8-value return shape so all
call sites (libtotem, libpredict, libcast, actionbar) continue working.
2026-05-21 02:56:58 -05:00
Brues 432ed546f5 debuff display via C_UnitAuras
Mirror the buff-side cleanup: drop the legacy DebuffOnUpdate poller from
player debuff frames, swap the player branch to GetDebuffDataByIndex with
the same future-start cap, and switch the dispel-indicator scan to
AuraData. libdebuff still owns non-player timers. Deletes the now-orphaned
BuffOnUpdate/DebuffOnUpdate/maxdurations.
2026-05-21 02:38:48 -05:00
Brues b18e6ffa67 refresh buff durations on player 2026-05-21 02:35:46 -05:00
Brues b7a0912e07 fixed target buffs not showing 2026-05-21 01:18:01 -05:00
Brues 8b04c53621 cleanup 2026-05-21 01:17:46 -05:00
Brues 2eb7506f3c dont need to scan tooltip 2026-05-21 00:55:22 -05:00
Brues 82743b0f9a UnitAUra cleanup 2026-05-18 23:42:51 -05:00
Brues b9343ec601 utilize SetUnitAura 2026-05-18 22:43:50 -05:00
Brues bcb71c8cca swpping to GetActionInfo 2026-05-18 13:20:09 -05:00
Brues 29e841d9c4 libtooltip can just use GetItem from ClassicAPI 2026-05-18 13:19:59 -05:00
Brues 67146ef3a4 begin utilizing ClassicAPI 2026-05-16 13:02:38 -05:00
Meow a8ee751b42 adjusted readme text 2026-04-11 23:22:47 +02:00
Meow 0ef3bd0148 fixed a bug where targeting an offline player predicted heals on them 2026-04-11 21:49:54 +02:00
Meow 93c4166052 possible fix for raid frames not showing up when pfui raid frames are hidden 2026-04-11 21:35:22 +02:00
Meow f49bf4f1a9 Skip firstrun if importing shared profile 2026-04-11 21:24:36 +02:00
Meow 164582fb82 Innervatecall module 2026-04-11 21:18:29 +02:00
Meow 5ef01174d6 New module - InnervateCall (only for druids)
- This module will call in group/raid and battleground if you innervated yourself or someone else.
- The module will inform your group/raid/battleground if innervate is ready again.
2026-04-11 21:10:57 +02:00
Meow 85dce25804 added slam and hammer of wrath to swingtimer 2026-04-11 20:46:38 +02:00