mirror of
https://github.com/brues-code/pfUI.git
synced 2026-10-26 22:00:49 +00:00
Compare commits
7
Commits
v9.0.18
...
dee325ddee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dee325ddee | ||
|
|
19de30fe72 | ||
|
|
6365f0cdb1 | ||
|
|
faa801e27d | ||
|
|
421a103f22 | ||
|
|
ecb28fb194 | ||
|
|
94268b1162 |
@@ -956,6 +956,7 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("questlog", nil, "showQuestLevels", "0")
|
||||
|
||||
pfUI:UpdateConfig("character", "inventory", "durability", "1")
|
||||
pfUI:UpdateConfig("character", "inventory", "equipflyout", "0")
|
||||
pfUI:UpdateConfig("character", "reputation", "repRequired", "1")
|
||||
pfUI:UpdateConfig("thirdparty", nil, "chatbg", "1")
|
||||
pfUI:UpdateConfig("thirdparty", nil, "showmeter", "0")
|
||||
|
||||
+3
-5
@@ -376,11 +376,9 @@ local function GetDebuffSlotMap(guid)
|
||||
local texture = libdebuff:GetSpellIcon(spellId)
|
||||
local stacks = (auraApps and auraApps[auraSlot] or 0) + 1
|
||||
local dtype = nil
|
||||
if GetSpellRecField then
|
||||
local dispelId = GetSpellRecField(spellId, "dispel")
|
||||
if dispelId and dispelId > 0 then
|
||||
dtype = dispelTypeMap[dispelId]
|
||||
end
|
||||
local dispelId = C_Spell.GetSpellDispelType(spellId)
|
||||
if dispelId and dispelId > 0 then
|
||||
dtype = dispelTypeMap[dispelId]
|
||||
end
|
||||
map[displaySlot] = {
|
||||
auraSlot = auraSlot,
|
||||
|
||||
+6
-5
@@ -83,12 +83,13 @@ end
|
||||
-- [number] Casting time of the spell in milliseconds
|
||||
-- [number] Minimum range from the target required to cast the spell
|
||||
-- [number] Maximum range from the target at which you can cast the spell
|
||||
-- [number] The numeric spell-id of the spell
|
||||
-- [number] Spell's index in the book
|
||||
-- [number] The type of the spellbook that the spell is in
|
||||
-- [number] The numeric spell-id of the spell
|
||||
local spellinfo = {}
|
||||
function libspell.GetSpellInfo(index, bookType)
|
||||
local cache = spellinfo[index]
|
||||
if cache then return cache[1], cache[2], cache[3], cache[4], cache[5], cache[6], cache[7], cache[8] end
|
||||
if cache then return unpack(cache) end
|
||||
|
||||
local slot
|
||||
if type(index) == "string" then
|
||||
@@ -108,10 +109,10 @@ function libspell.GetSpellInfo(index, bookType)
|
||||
-- ClassicAPI's GetSpellInfo returns: name, rank, icon, cost, isFunnel, powerType,
|
||||
-- castTime(ms), minRange, maxRange, spellID. Keep libspell's historical positional
|
||||
-- shape (castingTime at 4, ranges at 5/6, slot+bookType at 7/8).
|
||||
local name, rank, icon, _, _, _, castingTime, minRange, maxRange = GetSpellInfo(slot, bookType)
|
||||
local name, rank, icon, _, _, _, castingTime, minRange, maxRange, spellId = GetSpellInfo(slot, bookType)
|
||||
|
||||
spellinfo[index] = { name, rank, icon, castingTime, minRange, maxRange, slot, bookType }
|
||||
return name, rank, icon, castingTime, minRange, maxRange, slot, bookType
|
||||
spellinfo[index] = SafePack(name, rank, icon, castingTime, minRange, maxRange, slot, bookType, spellId)
|
||||
return name, rank, icon, castingTime, minRange, maxRange, slot, bookType, spellId
|
||||
end
|
||||
|
||||
-- Reset all spell caches whenever new spells are learned/unlearned
|
||||
|
||||
+11
-12
@@ -371,12 +371,12 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
-- active conditional dynamically, instead of being frozen to the first
|
||||
-- statically-scanned spell.
|
||||
if CleveRoids and CleveRoids.IsManagedAction and CleveRoids.IsManagedAction(self.id) then
|
||||
self.spellslot, self.booktype = nil, nil
|
||||
self.spellslot, self.booktype, self.spellID = nil, nil, nil
|
||||
return
|
||||
end
|
||||
|
||||
local kind, slot = GetActionInfo(self.id)
|
||||
self.spellslot, self.booktype = nil, nil
|
||||
self.spellslot, self.booktype, self.spellID = nil, nil, nil
|
||||
if kind == 'macro' then
|
||||
local name, _, body = GetMacroInfo(slot)
|
||||
|
||||
@@ -414,10 +414,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
end
|
||||
|
||||
if match then
|
||||
local _, _, spell, rank = string.find(match, '(.+)%((.+)%)')
|
||||
spell = spell or match
|
||||
self.spellslot, self.booktype = libspell.GetSpellIndex(spell, rank)
|
||||
|
||||
self.spellslot, self.booktype, self.spellID = select(7, libspell.GetSpellInfo(match))
|
||||
if self.spellslot and self.spellslot > 0 then return end
|
||||
end
|
||||
end
|
||||
@@ -426,7 +423,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
end
|
||||
|
||||
local function ButtonEnter(self)
|
||||
local self = self or this
|
||||
self = self or this
|
||||
|
||||
-- indicate that dragging could get enabled
|
||||
drag_await = true
|
||||
@@ -444,6 +441,8 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
else
|
||||
GameTooltip:SetPetAction(self.id)
|
||||
end
|
||||
elseif self.spellID then
|
||||
GameTooltip:SetSpellByID(self.spellID)
|
||||
elseif self.spellslot and self.booktype then
|
||||
GameTooltip:SetSpell(self.spellslot, self.booktype)
|
||||
else
|
||||
@@ -454,7 +453,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
end
|
||||
|
||||
local function ButtonLeave(self)
|
||||
local self = self or this
|
||||
self = self or this
|
||||
|
||||
-- no longer wait for a drag event
|
||||
drag_await = nil
|
||||
@@ -467,7 +466,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
local grid, sid, id, bar, active, texture, _
|
||||
local function ButtonSlotUpdate(self)
|
||||
if not self then return end
|
||||
local self = self or this
|
||||
self = self or this
|
||||
sid = self.id -- 1 to 120
|
||||
|
||||
-- reset shared variables
|
||||
@@ -586,7 +585,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
|
||||
local sid, usable, oom, _
|
||||
local function ButtonUsableUpdate(self)
|
||||
local self = self or this
|
||||
self = self or this
|
||||
sid = self.id -- 1 to 120
|
||||
|
||||
if self.bar == 11 then
|
||||
@@ -622,7 +621,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
end
|
||||
|
||||
local function ButtonRangeUpdate(self)
|
||||
local self = self or this
|
||||
self = self or this
|
||||
|
||||
-- update range display
|
||||
if C.bars.glowrange == "1" and self.bar ~= 11 and self.bar ~= 12 and HasAction(self.id) and ActionHasRange(self.id) and IsActionInRange(self.id) == 0 then
|
||||
@@ -700,7 +699,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
end
|
||||
|
||||
local function BarsEvent(self)
|
||||
local self = self or this
|
||||
self = self or this
|
||||
|
||||
-- refresh only specific slots
|
||||
if event == "ACTIONBAR_SLOT_CHANGED" and arg1 and arg1 ~= 0 then
|
||||
|
||||
@@ -13,6 +13,7 @@ pfUI:RegisterModule("equipmentmanager", function()
|
||||
local pendingAction = nil -- "new" | "save" | "rename" — what the popups apply to
|
||||
local slotOverlays = {} -- [invSlotID] = ignored-overlay texture on the character slot
|
||||
local popoutButtons = {} -- popout arrow buttons; toggled with the EM frame
|
||||
local UpdatePopouts -- forward decl; assigned once popouts + flyout exist
|
||||
|
||||
-- Pending ignored-slot toggles per set: a slotID here means the
|
||||
-- effective state is flipped from what's persisted. Committed on
|
||||
@@ -76,7 +77,7 @@ pfUI:RegisterModule("equipmentmanager", function()
|
||||
else
|
||||
frame:SetPoint("TOPLEFT", CharacterFrame, "TOPRIGHT", 0, 0)
|
||||
end
|
||||
for _, b in ipairs(popoutButtons) do b:Show() end
|
||||
UpdatePopouts()
|
||||
end)
|
||||
CreateBackdrop(frame, nil, nil, .9)
|
||||
CreateBackdropShadow(frame)
|
||||
@@ -452,13 +453,13 @@ pfUI:RegisterModule("equipmentmanager", function()
|
||||
-- ============================================================
|
||||
|
||||
local flyout = CreateFrame("Frame", "pfEqMgrFlyout", UIParent)
|
||||
-- Everything tied to the EM sidecar closes with it: the popout arrows,
|
||||
-- the per-slot flyout, and the name/icon popup. (OnShow re-shows the
|
||||
-- popout arrows.)
|
||||
-- The name/icon popup is sidecar-only, so it closes with the sidecar.
|
||||
-- Popout arrows and the flyout are reconciled by UpdatePopouts: by
|
||||
-- default they follow the sidecar, but with the "Always Show Equipment
|
||||
-- Slot Flyouts" option they stay while the paperdoll is open.
|
||||
frame:SetScript("OnHide", function()
|
||||
for _, b in ipairs(popoutButtons) do b:Hide() end
|
||||
flyout:Hide()
|
||||
namePopup:Hide()
|
||||
UpdatePopouts()
|
||||
end)
|
||||
flyout:SetFrameStrata("DIALOG")
|
||||
flyout:Hide()
|
||||
@@ -750,6 +751,27 @@ pfUI:RegisterModule("equipmentmanager", function()
|
||||
end
|
||||
end
|
||||
|
||||
-- Popout arrows follow the EM sidecar by default. With the
|
||||
-- "Always Show Equipment Slot Flyouts" option they follow the paperdoll
|
||||
-- instead, so gear can be swapped without opening the equipment manager.
|
||||
local function PopoutsActive()
|
||||
if C.character.inventory.equipflyout == "1" then
|
||||
return PaperDollFrame:IsShown()
|
||||
end
|
||||
return frame:IsShown()
|
||||
end
|
||||
|
||||
function UpdatePopouts()
|
||||
local show = PopoutsActive()
|
||||
for _, b in ipairs(popoutButtons) do
|
||||
if show then b:Show() else b:Hide() end
|
||||
end
|
||||
if not show then flyout:Hide() end
|
||||
end
|
||||
|
||||
PaperDollFrame:HookScript("OnShow", UpdatePopouts)
|
||||
PaperDollFrame:HookScript("OnHide", UpdatePopouts)
|
||||
|
||||
-- ============================================================
|
||||
-- Refresh
|
||||
-- ============================================================
|
||||
|
||||
@@ -2412,6 +2412,7 @@ pfUI:RegisterModule("gui", function ()
|
||||
|
||||
CreateGUIEntry(T["Character"], T["Inventory"], function()
|
||||
CreateConfig(nil, T["Show Durability"], C.character.inventory, "durability", "checkbox")
|
||||
CreateConfig(nil, T["Always Show Equipment Slot Flyouts"], C.character.inventory, "equipflyout", "checkbox")
|
||||
end)
|
||||
|
||||
CreateGUIEntry(T["Character"], T["Reputation"], function()
|
||||
|
||||
+34
-9
@@ -1,10 +1,8 @@
|
||||
pfUI:RegisterModule("hunterbar", function ()
|
||||
local class = UnitClassBase("player")
|
||||
if class ~= "HUNTER" or C.bars.hunterbar == "0" then return end
|
||||
if UnitClassBase("player") ~= "HUNTER" or C.bars.hunterbar == "0" then return end
|
||||
|
||||
-- Wing Clip (any rank) and Arcane Shot (any rank) spell IDs.
|
||||
-- IsSpellInRange(spellId) works with any spell ID via Nampower,
|
||||
-- no actionbar slot needed.
|
||||
-- C_Spell.IsSpellInRange works with any spell ID, no actionbar slot needed.
|
||||
local WINGCLIP_ID = 2974 -- melee range indicator (~5 yd)
|
||||
local ARCANESHOT_ID = 3044 -- ranged range indicator (~35 yd)
|
||||
|
||||
@@ -12,22 +10,37 @@ pfUI:RegisterModule("hunterbar", function ()
|
||||
-- Only swap BACK to melee bar when Wing Clip is actually in range.
|
||||
-- This prevents rapid bar-flipping in the transition zone.
|
||||
|
||||
local THROTTLE = 0.02 -- seconds between range checks (was every frame)
|
||||
|
||||
pfUI.hunterbar = CreateFrame("Frame", "pfHunterBar", UIParent)
|
||||
|
||||
-- track which page we last forced so we don't spam ChangeActionBarPage()
|
||||
pfUI.hunterbar.lastPage = nil
|
||||
pfUI.hunterbar.elapsed = 0
|
||||
|
||||
-- Idle until there's a live, attackable target. A hidden frame runs no
|
||||
-- OnUpdate, so the range polling only ticks while it can matter.
|
||||
pfUI.hunterbar:Hide()
|
||||
|
||||
pfUI.hunterbar:SetScript("OnUpdate", function()
|
||||
-- only act when there is a live, attackable target
|
||||
this.elapsed = this.elapsed + arg1
|
||||
if this.elapsed < THROTTLE then return end
|
||||
this.elapsed = 0
|
||||
|
||||
-- Target died or turned unattackable without a target-change event
|
||||
-- (it dropped to a corpse); stop ticking until the next target.
|
||||
if not UnitExists("target") or not UnitCanAttack("player", "target") then
|
||||
this:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
local wingclipInRange = IsSpellInRange(WINGCLIP_ID, "target")
|
||||
local arcaneshotInRange = IsSpellInRange(ARCANESHOT_ID, "target")
|
||||
-- C_Spell.IsSpellInRange returns true / false / nil (rangeless). The
|
||||
-- explicit == true / == false tests leave the bar unchanged on nil.
|
||||
local wingclipInRange = C_Spell.IsSpellInRange(WINGCLIP_ID, "target")
|
||||
local arcaneshotInRange = C_Spell.IsSpellInRange(ARCANESHOT_ID, "target")
|
||||
|
||||
-- swap to ranged bar: out of melee range AND arcane shot (8yd) in range
|
||||
if wingclipInRange == 0 and arcaneshotInRange == 1 then
|
||||
if wingclipInRange == false and arcaneshotInRange == true then
|
||||
if this.lastPage ~= 2 then
|
||||
this.lastPage = 2
|
||||
_G.CURRENT_ACTIONBAR_PAGE = 2
|
||||
@@ -35,7 +48,7 @@ pfUI:RegisterModule("hunterbar", function ()
|
||||
end
|
||||
|
||||
-- swap to melee bar: in melee range AND arcane shot (8yd) out of range
|
||||
elseif wingclipInRange == 1 and arcaneshotInRange == 0 then
|
||||
elseif wingclipInRange == true and arcaneshotInRange == false then
|
||||
if this.lastPage ~= 1 then
|
||||
this.lastPage = 1
|
||||
_G.CURRENT_ACTIONBAR_PAGE = 1
|
||||
@@ -43,4 +56,16 @@ pfUI:RegisterModule("hunterbar", function ()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Start ticking only when we gain an attackable target.
|
||||
pfUI.hunterbar:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
pfUI.hunterbar:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
pfUI.hunterbar:SetScript("OnEvent", function()
|
||||
if UnitExists("target") and UnitCanAttack("player", "target") then
|
||||
this.elapsed = THROTTLE -- run the first check on the next frame
|
||||
this:Show()
|
||||
else
|
||||
this:Hide()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
@@ -75,14 +75,14 @@ pfUI:RegisterModule("innervatecall", function ()
|
||||
|
||||
SendChatMessage(">> Innervate casted on " .. targetName .. " <<", channel)
|
||||
|
||||
-- Schedule "ready" announcement when cooldown expires
|
||||
-- Use GetSpellIdCooldown for precise remaining time, fallback to 360s
|
||||
-- Schedule "ready" announcement when the cooldown expires. Read the
|
||||
-- precise remaining time from C_Spell.GetSpellCooldown (startTime and
|
||||
-- duration are seconds from the GetTime epoch); fall back to 360s.
|
||||
local cdRemaining = 360
|
||||
if GetSpellIdCooldown then
|
||||
local cd = GetSpellIdCooldown(INNERVATE_SPELLID)
|
||||
if cd and cd.cooldownRemainingMs and cd.cooldownRemainingMs > 0 then
|
||||
cdRemaining = cd.cooldownRemainingMs / 1000
|
||||
end
|
||||
local cd = C_Spell.GetSpellCooldown(INNERVATE_SPELLID)
|
||||
if cd and cd.duration and cd.duration > 0 then
|
||||
local remaining = cd.startTime + cd.duration - GetTime()
|
||||
if remaining > 0 then cdRemaining = remaining end
|
||||
end
|
||||
|
||||
C_Timer.After(cdRemaining, function()
|
||||
|
||||
+14
-25
@@ -9,11 +9,6 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
local ON_SWING_QUEUED = 0
|
||||
local ON_SWING_QUEUE_POPPED = 1
|
||||
|
||||
-- Spell.dbc bits used to mirror server-side swing-reset rules.
|
||||
local FLAG_AUTOATTACK = tonumber("0x08", 16) -- SPELL_INTERRUPT_FLAG_AUTOATTACK
|
||||
local ATTR_KEEP_SWINGS = tonumber("0x20000", 16) -- SPELL_ATTR_EX2_NOT_RESET_AUTO_ACTIONS
|
||||
local ATTR_ON_NEXT_SWING = tonumber("0x04", 16) -- SPELL_ATTR_ON_NEXT_SWING
|
||||
|
||||
-- Consolidate state into a table to avoid Lua 5.0 upvalue limit (32 max)
|
||||
local S = {
|
||||
mhTimer = 0, mhTimerMax = 1,
|
||||
@@ -43,12 +38,11 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
local WAND_SHOOT_SPELLID = 5019
|
||||
local THROW_SPELLID = 2764 -- one-shot ranged, not auto-repeat
|
||||
|
||||
-- ATTR_ON_NEXT_SWING: spell replaces next auto-attack swing.
|
||||
-- On-next-swing spells replace the next auto-attack swing.
|
||||
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
|
||||
local function IsOnSwingSpell(spellId)
|
||||
if S.onSwingCache[spellId] ~= nil then return S.onSwingCache[spellId] end
|
||||
local attr = GetSpellRecField(spellId, "attributes") or 0
|
||||
local result = bit.band(attr, ATTR_ON_NEXT_SWING) ~= 0
|
||||
local result = C_Spell.IsNextMeleeSpell(spellId)
|
||||
S.onSwingCache[spellId] = result
|
||||
return result
|
||||
end
|
||||
@@ -734,13 +728,12 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
S.pendingCastSpellId = arg1
|
||||
-- Freeze the swing timer for cast-time spells that DON'T reset auto-
|
||||
-- attack on completion (Slam, Hammer of Wrath on Turtle, etc.) — those
|
||||
-- let the swing resume from where it paused. Detect dynamically via the
|
||||
-- absent AUTOATTACK interrupt flag (8); spells with that bit reset on
|
||||
-- SPELL_GO_SELF so freezing isn't necessary. Subsumes the old hardcoded
|
||||
-- swingDelaySpells list (no list maintenance for new Slam-style spells).
|
||||
-- let the swing resume from where it paused. C_Spell.ResetsMeleeSwing
|
||||
-- mirrors the server rule; spells that reset don't need freezing (they
|
||||
-- reset on SPELL_GO_SELF). Subsumes the old hardcoded swingDelaySpells
|
||||
-- list (no list maintenance for new Slam-style spells).
|
||||
if S.mhActive then
|
||||
local iflags = GetSpellRecField(arg1, "interruptFlags") or 0
|
||||
if bit.band(iflags, FLAG_AUTOATTACK) == 0 then
|
||||
if not C_Spell.ResetsMeleeSwing(arg1) then
|
||||
S.mhFrozenAt = GetTime()
|
||||
end
|
||||
end
|
||||
@@ -777,17 +770,13 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
|
||||
ResetMH()
|
||||
else
|
||||
-- Mirror the server rule for "does this spell reset the auto-attack
|
||||
-- swing" (Spell::IsMeleeAttackResetSpell in Turtle's core):
|
||||
-- InterruptFlags has SPELL_INTERRUPT_FLAG_AUTOATTACK (0x08)
|
||||
-- AND AttributesEx2 lacks NOT_RESET_AUTO_ACTIONS (0x20000).
|
||||
-- If neither path resets and we're holding a frozen-swing-during-cast
|
||||
-- (mhFrozenAt set by SPELL_START_SELF for non-AUTOATTACK spells), this
|
||||
-- is a Slam-style cast — push the timer forward by the cast duration
|
||||
-- so the bar resumes from where it paused.
|
||||
local iflags = GetSpellRecField(spellId, "interruptFlags") or 0
|
||||
if bit.band(iflags, FLAG_AUTOATTACK) ~= 0
|
||||
and bit.band(GetSpellRecField(spellId, "attributesEx2") or 0, ATTR_KEEP_SWINGS) == 0 then
|
||||
-- C_Spell.ResetsMeleeSwing mirrors the server rule (Turtle's
|
||||
-- Spell::IsMeleeAttackResetSpell): InterruptFlags has AUTOATTACK and
|
||||
-- AttributesEx2 lacks NOT_RESET_AUTO_ACTIONS. When the spell resets the
|
||||
-- swing, snap the timers to full. Otherwise (elseif) a frozen-swing-
|
||||
-- during-cast is a Slam-style cast — push the timer forward by the cast
|
||||
-- duration so the bar resumes from where it paused.
|
||||
if C_Spell.ResetsMeleeSwing(spellId) then
|
||||
if S.mhActive and S.mhSpeed > 0 then
|
||||
UpdateWeaponSpeeds()
|
||||
S.mhTimerMax = S.mhSpeed
|
||||
|
||||
@@ -23,7 +23,7 @@ do
|
||||
-- ClassicAPI dependency check.
|
||||
-- pfUI relies pervasively on the modern C_* / SuperWoW / nameplate / focus
|
||||
-- API surface that ClassicAPI polyfills, so presence is required.
|
||||
local PFUI_CLASSIC_API_MIN = 10903 -- (X*10000 + Y*100 + Z)
|
||||
local PFUI_CLASSIC_API_MIN = 10905 -- (X*10000 + Y*100 + Z)
|
||||
local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN
|
||||
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
|
||||
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest"
|
||||
@@ -425,7 +425,7 @@ pfUI:SetScript("OnEvent", function()
|
||||
pfUI.version.major, pfUI.version.minor, pfUI.version.fix = 0, 0, 0
|
||||
pfUI.version.string = "dev"
|
||||
else
|
||||
local major, minor, fix = pfUI.api.strsplit(".", raw)
|
||||
local major, minor, fix = pfUI.api.strsplit(".", string.gsub(raw, "^[vV]", ""))
|
||||
pfUI.version.major = tonumber(major) or 0
|
||||
pfUI.version.minor = tonumber(minor) or 0
|
||||
pfUI.version.fix = tonumber(fix) or 0
|
||||
|
||||
Reference in New Issue
Block a user