mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Added spell channeling support
This commit is contained in:
parent
7341613f21
commit
3b28170217
@ -17,8 +17,9 @@ namespace Artemis.Modules.Games.WoW.Models
|
|||||||
public DateTime EndTime { get; set; }
|
public DateTime EndTime { get; set; }
|
||||||
public bool NonInterruptible { get; set; }
|
public bool NonInterruptible { get; set; }
|
||||||
public float Progress { get; set; }
|
public float Progress { get; set; }
|
||||||
|
public bool IsChannel { get; set; }
|
||||||
|
|
||||||
public void ApplyJson(JToken spellJson)
|
public void ApplyJson(JToken spellJson, bool isChannel)
|
||||||
{
|
{
|
||||||
var castMs = spellJson["e"].Value<int>() - spellJson["s"].Value<int>();
|
var castMs = spellJson["e"].Value<int>() - spellJson["s"].Value<int>();
|
||||||
var tickCount = Environment.TickCount;
|
var tickCount = Environment.TickCount;
|
||||||
@ -29,6 +30,7 @@ namespace Artemis.Modules.Games.WoW.Models
|
|||||||
StartTime = new DateTime(DateTime.Now.Ticks + difference);
|
StartTime = new DateTime(DateTime.Now.Ticks + difference);
|
||||||
EndTime = StartTime.AddMilliseconds(castMs);
|
EndTime = StartTime.AddMilliseconds(castMs);
|
||||||
NonInterruptible = spellJson["ni"].Value<bool>();
|
NonInterruptible = spellJson["ni"].Value<bool>();
|
||||||
|
IsChannel = isChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateProgress()
|
public void UpdateProgress()
|
||||||
@ -39,8 +41,12 @@ namespace Artemis.Modules.Games.WoW.Models
|
|||||||
var elapsed = DateTime.Now - StartTime;
|
var elapsed = DateTime.Now - StartTime;
|
||||||
var total = EndTime - StartTime;
|
var total = EndTime - StartTime;
|
||||||
|
|
||||||
Progress = (float) (elapsed.TotalMilliseconds / total.TotalMilliseconds);
|
if (IsChannel)
|
||||||
if (Progress > 1)
|
Progress = 1 - (float) (elapsed.TotalMilliseconds / total.TotalMilliseconds);
|
||||||
|
else
|
||||||
|
Progress = (float) (elapsed.TotalMilliseconds / total.TotalMilliseconds);
|
||||||
|
|
||||||
|
if (Progress > 1 || Progress < 0)
|
||||||
Clear();
|
Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +58,7 @@ namespace Artemis.Modules.Games.WoW.Models
|
|||||||
EndTime = DateTime.MinValue;
|
EndTime = DateTime.MinValue;
|
||||||
NonInterruptible = false;
|
NonInterruptible = false;
|
||||||
Progress = 0;
|
Progress = 0;
|
||||||
|
IsChannel = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
## Interface: 70300
|
## Interface: 70300
|
||||||
## Title: Artemis
|
## Title: Artemis
|
||||||
## Notes: Transmits ingame data to Artemis
|
## Notes: Transmits ingame data to Artemis
|
||||||
## Author: SpoinkyNL
|
## Author: SpoinkyNL
|
||||||
## Version: 1.0.0
|
## Version: 1.0.0
|
||||||
|
|
||||||
embeds.xml
|
embeds.xml
|
||||||
|
|
||||||
Core.lua
|
Core.lua
|
||||||
|
|||||||
@ -1,297 +1,358 @@
|
|||||||
Artemis = LibStub("AceAddon-3.0"):NewAddon("Artemis", "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0", "AceComm-3.0")
|
Artemis = LibStub("AceAddon-3.0"):NewAddon("Artemis", "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0", "AceComm-3.0")
|
||||||
json = LibStub("json")
|
json = LibStub("json")
|
||||||
local debugging = false
|
local debugging = false
|
||||||
local lastLine = {}
|
local lastLine = {}
|
||||||
local unitUpdates = {}
|
local channeling = {}
|
||||||
local lastTransmitMessage
|
local unitUpdates = {}
|
||||||
local lastTransmitTime
|
local lastTransmitMessage
|
||||||
local lastBuffs = "";
|
local lastTransmitTime
|
||||||
local lastDebuffs = "";
|
local lastBuffs = "";
|
||||||
local prefixCounts = {}
|
local lastDebuffs = "";
|
||||||
|
local prefixCounts = {}
|
||||||
function Artemis:OnEnable()
|
|
||||||
Artemis:RegisterEvent("PLAYER_ENTERING_WORLD")
|
channeling["player"] = false
|
||||||
Artemis:RegisterEvent("PLAYER_LEVEL_UP")
|
channeling["target"] = false
|
||||||
Artemis:RegisterEvent("ACHIEVEMENT_EARNED")
|
|
||||||
Artemis:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
|
function Artemis:OnEnable()
|
||||||
Artemis:RegisterEvent("UNIT_TARGET")
|
Artemis:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||||
Artemis:RegisterEvent("UNIT_HEALTH")
|
Artemis:RegisterEvent("PLAYER_LEVEL_UP")
|
||||||
Artemis:RegisterEvent("UNIT_POWER")
|
Artemis:RegisterEvent("ACHIEVEMENT_EARNED")
|
||||||
Artemis:RegisterEvent("UNIT_AURA")
|
Artemis:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
|
||||||
Artemis:RegisterEvent("UNIT_SPELLCAST_START")
|
Artemis:RegisterEvent("UNIT_TARGET")
|
||||||
Artemis:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
|
Artemis:RegisterEvent("UNIT_HEALTH")
|
||||||
Artemis:RegisterEvent("UNIT_SPELLCAST_FAILED")
|
Artemis:RegisterEvent("UNIT_POWER")
|
||||||
Artemis:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
|
Artemis:RegisterEvent("UNIT_AURA")
|
||||||
Artemis:RegisterEvent("ZONE_CHANGED")
|
Artemis:RegisterEvent("UNIT_SPELLCAST_START")
|
||||||
Artemis:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
Artemis:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
|
||||||
|
Artemis:RegisterEvent("UNIT_SPELLCAST_FAILED")
|
||||||
Artemis:RegisterChatCommand("artemis", "HandleChatCommand")
|
Artemis:RegisterEvent("UNIT_SPELLCAST_DELAYED")
|
||||||
end
|
Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
|
||||||
|
Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
|
||||||
function Artemis:HandleChatCommand(input)
|
Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
|
||||||
if input == "debug" then
|
Artemis:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
|
||||||
debugging = not (debugging)
|
Artemis:RegisterEvent("ZONE_CHANGED")
|
||||||
if debugging then
|
Artemis:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||||
Artemis:Print("Debugging enabled.")
|
|
||||||
else
|
Artemis:RegisterChatCommand("artemis", "HandleChatCommand")
|
||||||
Artemis:Print("Debugging disabled.")
|
end
|
||||||
end
|
|
||||||
end
|
function Artemis:HandleChatCommand(input)
|
||||||
if input == "rc" then
|
if input == "debug" then
|
||||||
prefixCounts = {}
|
debugging = not (debugging)
|
||||||
Artemis:Print("Reset the send counters.")
|
if debugging then
|
||||||
end
|
Artemis:Print("Debugging enabled.")
|
||||||
if input == nill or input == "" or input == "help" then
|
else
|
||||||
Artemis:Print("Available chat commands:")
|
Artemis:Print("Debugging disabled.")
|
||||||
Artemis:Printf("|cffb7b7b7/artemis debug|r: Toggle debugging")
|
end
|
||||||
Artemis:Printf("|cffb7b7b7/artemis rc|r: Reset the debug counters")
|
end
|
||||||
end
|
if input == "rc" then
|
||||||
end
|
prefixCounts = {}
|
||||||
|
Artemis:Print("Reset the send counters.")
|
||||||
function Artemis:Transmit(prefix, data, prio)
|
end
|
||||||
local msg = "artemis(".. prefix .. "|" .. json.encode(data) ..")"
|
if input == nill or input == "" or input == "help" then
|
||||||
-- If the message is the same as the previous, make sure it wasn't sent less than 250ms ago
|
Artemis:Print("Available chat commands:")
|
||||||
if msg == lastTransmitMessage then
|
Artemis:Printf("|cffb7b7b7/artemis debug|r: Toggle debugging")
|
||||||
if not (lastTransmitTime == nil) then
|
Artemis:Printf("|cffb7b7b7/artemis rc|r: Reset the debug counters")
|
||||||
local diff = GetTime() - lastTransmitTime;
|
end
|
||||||
if (diff < 0.25) then
|
end
|
||||||
return
|
|
||||||
end
|
function Artemis:Transmit(prefix, data, prio)
|
||||||
end
|
local msg = "artemis(".. prefix .. "|" .. json.encode(data) ..")"
|
||||||
end
|
-- If the message is the same as the previous, make sure it wasn't sent less than 250ms ago
|
||||||
|
if msg == lastTransmitMessage then
|
||||||
lastTransmitTime = GetTime()
|
if not (lastTransmitTime == nil) then
|
||||||
|
local diff = GetTime() - lastTransmitTime;
|
||||||
if debugging == true then
|
if (diff < 0.25) then
|
||||||
if prefixCounts[prefix] == nill then
|
return
|
||||||
prefixCounts[prefix] = 0
|
end
|
||||||
end
|
end
|
||||||
prefixCounts[prefix] = prefixCounts[prefix] + 1
|
end
|
||||||
end
|
|
||||||
|
lastTransmitTime = GetTime()
|
||||||
if debugging == true then
|
|
||||||
Artemis:Printf("Transmitting with prefix |cfffdff71" .. prefix .. "|r (" .. prefixCounts[prefix] .. ").")
|
if debugging == true then
|
||||||
Artemis:Print(msg)
|
if prefixCounts[prefix] == nill then
|
||||||
end
|
prefixCounts[prefix] = 0
|
||||||
Artemis:SendCommMessage("(artemis)", msg, "WHISPER", UnitName("player"), prio)
|
end
|
||||||
end
|
prefixCounts[prefix] = prefixCounts[prefix] + 1
|
||||||
|
end
|
||||||
function Artemis:TransmitUnitState(unit, ignoreThrottle)
|
|
||||||
if not ignoreThrottle then
|
if debugging == true then
|
||||||
if not (unitUpdates[unit] == nil) then
|
Artemis:Printf("Transmitting with prefix |cfffdff71" .. prefix .. "|r (" .. prefixCounts[prefix] .. ").")
|
||||||
local diff = GetTime() - unitUpdates[unit]
|
Artemis:Print(msg)
|
||||||
if (diff < 0.5) then
|
end
|
||||||
return
|
Artemis:SendCommMessage("(artemis)", msg, "WHISPER", UnitName("player"), prio)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
function Artemis:TransmitUnitState(unit, ignoreThrottle)
|
||||||
|
if not ignoreThrottle then
|
||||||
local table = {
|
if not (unitUpdates[unit] == nil) then
|
||||||
h = UnitHealth(unit),
|
local diff = GetTime() - unitUpdates[unit]
|
||||||
mh = UnitHealthMax(unit),
|
if (diff < 0.5) then
|
||||||
p = UnitPower(unit),
|
return
|
||||||
mp = UnitPowerMax(unit),
|
end
|
||||||
t = UnitPowerType(unit)
|
end
|
||||||
};
|
end
|
||||||
|
|
||||||
unitUpdates[unit] = GetTime()
|
local table = {
|
||||||
Artemis:Transmit(unit .. "State", table)
|
h = UnitHealth(unit),
|
||||||
end
|
mh = UnitHealthMax(unit),
|
||||||
|
p = UnitPower(unit),
|
||||||
function Artemis:GetUnitDetails(unit)
|
mp = UnitPowerMax(unit),
|
||||||
return {
|
t = UnitPowerType(unit)
|
||||||
n = UnitName(unit),
|
};
|
||||||
c = UnitClass(unit),
|
|
||||||
l = UnitLevel(unit),
|
unitUpdates[unit] = GetTime()
|
||||||
r = UnitRace(unit),
|
Artemis:Transmit(unit .. "State", table)
|
||||||
g = UnitSex(unit),
|
end
|
||||||
f = UnitFactionGroup(unit)
|
|
||||||
};
|
function Artemis:GetUnitDetails(unit)
|
||||||
end
|
return {
|
||||||
|
n = UnitName(unit),
|
||||||
function Artemis:GetPlayerDetails()
|
c = UnitClass(unit),
|
||||||
local details = Artemis:GetUnitDetails("player")
|
l = UnitLevel(unit),
|
||||||
local id, name, _, _, role = GetSpecializationInfo(GetSpecialization())
|
r = UnitRace(unit),
|
||||||
|
g = UnitSex(unit),
|
||||||
details.realm = GetRealmName()
|
f = UnitFactionGroup(unit)
|
||||||
details.achievementPoints = GetTotalAchievementPoints(false)
|
};
|
||||||
details.s = {id = id, n = name, r = role}
|
end
|
||||||
|
|
||||||
return details
|
function Artemis:GetPlayerDetails()
|
||||||
end
|
local details = Artemis:GetUnitDetails("player")
|
||||||
|
local id, name, _, _, role = GetSpecializationInfo(GetSpecialization())
|
||||||
function Artemis:GetUnitAuras(unit, filter)
|
|
||||||
local auras = {};
|
details.realm = GetRealmName()
|
||||||
for index = 1, 40 do
|
details.achievementPoints = GetTotalAchievementPoints(false)
|
||||||
local name, _, _, count, _, duration, expires, caster, _, _, spellID = UnitAura(unit, index, filter);
|
details.s = {id = id, n = name, r = role}
|
||||||
if not (name == nil) then
|
|
||||||
local buffTable = {n = name, id = spellID}
|
return details
|
||||||
-- Leave these values out if they are 0 to save some space
|
end
|
||||||
if count > 0 then
|
|
||||||
buffTable["c"] = count
|
function Artemis:GetUnitAuras(unit, filter)
|
||||||
end
|
local auras = {};
|
||||||
if duration > 0 then
|
for index = 1, 40 do
|
||||||
buffTable["d"] = duration
|
local name, _, _, count, _, duration, expires, caster, _, _, spellID = UnitAura(unit, index, filter);
|
||||||
end
|
if not (name == nil) then
|
||||||
if expires > 0 then
|
local buffTable = {n = name, id = spellID}
|
||||||
buffTable["e"] = expires
|
-- Leave these values out if they are 0 to save some space
|
||||||
end
|
if count > 0 then
|
||||||
table.insert(auras, buffTable)
|
buffTable["c"] = count
|
||||||
end
|
end
|
||||||
end
|
if duration > 0 then
|
||||||
return auras
|
buffTable["d"] = duration
|
||||||
end
|
end
|
||||||
|
if expires > 0 then
|
||||||
function Artemis:PLAYER_ENTERING_WORLD(...)
|
buffTable["e"] = expires
|
||||||
Artemis:Transmit("player", Artemis:GetPlayerDetails())
|
end
|
||||||
Artemis:TransmitUnitState("player", true);
|
table.insert(auras, buffTable)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
function Artemis:PLAYER_LEVEL_UP(...)
|
return auras
|
||||||
Artemis:Transmit("player", Artemis:GetPlayerDetails())
|
end
|
||||||
end
|
|
||||||
|
function Artemis:PLAYER_ENTERING_WORLD(...)
|
||||||
function Artemis:ACHIEVEMENT_EARNED(...)
|
Artemis:Transmit("player", Artemis:GetPlayerDetails())
|
||||||
Artemis:Transmit("player", Artemis:GetPlayerDetails())
|
Artemis:TransmitUnitState("player", true);
|
||||||
end
|
end
|
||||||
|
|
||||||
function Artemis:ACTIVE_TALENT_GROUP_CHANGED(...)
|
function Artemis:PLAYER_LEVEL_UP(...)
|
||||||
Artemis:Transmit("player", Artemis:GetPlayerDetails())
|
Artemis:Transmit("player", Artemis:GetPlayerDetails())
|
||||||
end
|
end
|
||||||
|
|
||||||
function Artemis:UNIT_TARGET(...)
|
function Artemis:ACHIEVEMENT_EARNED(...)
|
||||||
local _, source = ...
|
Artemis:Transmit("player", Artemis:GetPlayerDetails())
|
||||||
if not (source == "player") then
|
end
|
||||||
return
|
|
||||||
end
|
function Artemis:ACTIVE_TALENT_GROUP_CHANGED(...)
|
||||||
|
Artemis:Transmit("player", Artemis:GetPlayerDetails())
|
||||||
local details = Artemis:GetUnitDetails("target")
|
end
|
||||||
|
|
||||||
Artemis:Transmit("target", details)
|
function Artemis:UNIT_TARGET(...)
|
||||||
Artemis:TransmitUnitState("target", true);
|
local _, source = ...
|
||||||
end
|
if not (source == "player") then
|
||||||
|
return
|
||||||
function Artemis:UNIT_HEALTH(...)
|
end
|
||||||
local _, source = ...
|
|
||||||
if not (source == "player") and not (source == "target") then
|
local details = Artemis:GetUnitDetails("target")
|
||||||
return
|
channeling["target"] = false
|
||||||
end
|
|
||||||
|
Artemis:Transmit("target", details)
|
||||||
Artemis:TransmitUnitState(source, false);
|
Artemis:TransmitUnitState("target", true);
|
||||||
end
|
end
|
||||||
|
|
||||||
function Artemis:UNIT_POWER(...)
|
function Artemis:UNIT_HEALTH(...)
|
||||||
local _, source = ...
|
local _, source = ...
|
||||||
if not (source == "player") and not (source == "target") then
|
if not (source == "player") and not (source == "target") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
Artemis:TransmitUnitState(source, false);
|
Artemis:TransmitUnitState(source, false);
|
||||||
end
|
end
|
||||||
|
|
||||||
function Artemis:UNIT_AURA(...)
|
function Artemis:UNIT_POWER(...)
|
||||||
local _, source = ...
|
local _, source = ...
|
||||||
if not (source == "player") then
|
if not (source == "player") and not (source == "target") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local buffs = Artemis:GetUnitAuras(source, "PLAYER|HELPFUL")
|
Artemis:TransmitUnitState(source, false);
|
||||||
local debuffs = Artemis:GetUnitAuras(source, "PLAYER|HARMFUL")
|
end
|
||||||
|
|
||||||
local newBuffs = json.encode(buffs)
|
function Artemis:UNIT_AURA(...)
|
||||||
local newDebuffs = json.encode(debuffs)
|
local _, source = ...
|
||||||
|
if not (source == "player") then
|
||||||
if not (lastBuffs == newBuffs) then
|
return
|
||||||
Artemis:Transmit("buffs", buffs)
|
end
|
||||||
end
|
|
||||||
if not (lastDebuffs == newDebuffs) then
|
local buffs = Artemis:GetUnitAuras(source, "PLAYER|HELPFUL")
|
||||||
Artemis:Transmit("debuffs", debuffs)
|
local debuffs = Artemis:GetUnitAuras(source, "PLAYER|HARMFUL")
|
||||||
end
|
|
||||||
|
local newBuffs = json.encode(buffs)
|
||||||
lastBuffs = newBuffs
|
local newDebuffs = json.encode(debuffs)
|
||||||
lastDebuffs = newDebuffs
|
|
||||||
end
|
if not (lastBuffs == newBuffs) then
|
||||||
|
Artemis:Transmit("buffs", buffs)
|
||||||
-- Detect non-instant spell casts
|
end
|
||||||
function Artemis:UNIT_SPELLCAST_START(...)
|
if not (lastDebuffs == newDebuffs) then
|
||||||
local _, unitID, spell, rank, lineID, spellID = ...
|
Artemis:Transmit("debuffs", debuffs)
|
||||||
if not (unitID == "player") and not (unitID == "target") then
|
end
|
||||||
return
|
|
||||||
end
|
lastBuffs = newBuffs
|
||||||
|
lastDebuffs = newDebuffs
|
||||||
local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID)
|
end
|
||||||
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
|
|
||||||
lastLine[unitID] = lineID
|
-- Detect non-instant spell casts
|
||||||
|
function Artemis:UNIT_SPELLCAST_START(...)
|
||||||
Artemis:Transmit("spellCast", table, "ALERT")
|
local _, unitID, spell, rank, lineID, spellID = ...
|
||||||
end
|
if not (unitID == "player") and not (unitID == "target") then
|
||||||
|
return
|
||||||
-- Detect instant spell casts
|
end
|
||||||
function Artemis:UNIT_SPELLCAST_SUCCEEDED (...)
|
|
||||||
local _, unitID, spell, rank, lineID, spellID = ...
|
local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID)
|
||||||
if not (unitID == "player") and not (unitID == "target") then
|
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
|
||||||
return
|
lastLine[unitID] = lineID
|
||||||
end
|
|
||||||
-- Many spells are irrelevant system spells, don't transmit these
|
Artemis:Transmit("spellCast", table, "ALERT")
|
||||||
if unitID == "player" and not (IsPlayerSpell(spellID)) then
|
end
|
||||||
return
|
|
||||||
end
|
-- Detect instant spell casts
|
||||||
|
function Artemis:UNIT_SPELLCAST_SUCCEEDED (...)
|
||||||
local name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unitID)
|
local _, unitID, spell, rank, lineID, spellID = ...
|
||||||
-- Don't trigger on the success of a non instant cast
|
if not (unitID == "player") and not (unitID == "target") then
|
||||||
if not (lastLine[unitID] == nil) and lastLine[unitID] == lineID then
|
return
|
||||||
return
|
end
|
||||||
end
|
if channeling[unitID] == true then
|
||||||
|
return
|
||||||
-- Set back the last line to what is currently being cast (Fireblast during Fireball per example)
|
end
|
||||||
if not (name == nil) then
|
-- Many spells are irrelevant system spells, don't transmit these
|
||||||
lastLine[unitID] = castID
|
if unitID == "player" and not (IsPlayerSpell(spellID)) then
|
||||||
else
|
return
|
||||||
lastLine[unitID] = nil
|
end
|
||||||
end
|
|
||||||
|
local name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unitID)
|
||||||
local table = {uid = unitID, n = spell, sid = spellID}
|
-- Don't trigger on the success of a non instant cast
|
||||||
|
if not (lastLine[unitID] == nil) and lastLine[unitID] == lineID then
|
||||||
Artemis:Transmit("instantSpellCast", table, "ALERT")
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Detect falure of non instant casts
|
-- Set back the last line to what is currently being cast (Fireblast during Fireball per example)
|
||||||
function Artemis:UNIT_SPELLCAST_FAILED (...)
|
if not (name == nil) then
|
||||||
local source, unitID, _, _, lineID = ...
|
lastLine[unitID] = castID
|
||||||
if not (unitID == "player") and not (unitID == "target") then
|
else
|
||||||
return
|
lastLine[unitID] = nil
|
||||||
end
|
end
|
||||||
if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then
|
|
||||||
return
|
local table = {uid = unitID, n = spell, sid = spellID}
|
||||||
end
|
|
||||||
|
Artemis:Transmit("instantSpellCast", table, "ALERT")
|
||||||
lastLine[unitID] = nil
|
end
|
||||||
|
|
||||||
Artemis:Transmit("spellCastFailed", unitID, "ALERT")
|
-- Detect falure of non instant casts
|
||||||
end
|
function Artemis:UNIT_SPELLCAST_FAILED (...)
|
||||||
|
local source, unitID, _, _, lineID = ...
|
||||||
-- Detect cancellation of non instant casts
|
if not (unitID == "player") and not (unitID == "target") then
|
||||||
function Artemis:UNIT_SPELLCAST_INTERRUPTED (...)
|
return
|
||||||
local source, unitID, _, _, lineID = ...
|
end
|
||||||
if not (unitID == "player") and not (unitID == "target") then
|
if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then
|
|
||||||
return
|
lastLine[unitID] = nil
|
||||||
end
|
|
||||||
|
Artemis:Transmit("spellCastFailed", unitID, "ALERT")
|
||||||
lastLine[unitID] = nil
|
end
|
||||||
|
|
||||||
Artemis:Transmit("spellCastInterrupted", unitID, "ALERT")
|
-- Detect falure of non instant casts
|
||||||
end
|
function Artemis:UNIT_SPELLCAST_DELAYED (...)
|
||||||
|
local _, unitID, spell, rank, lineID, spellID = ...
|
||||||
function Artemis:ZONE_CHANGED_NEW_AREA (...)
|
if not (unitID == "player") and not (unitID == "target") then
|
||||||
local pvpType, isSubZonePVP, factionName = GetZonePVPInfo()
|
return
|
||||||
|
end
|
||||||
Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName})
|
local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID)
|
||||||
end
|
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
|
||||||
function Artemis:ZONE_CHANGED (...)
|
|
||||||
local pvpType, isSubZonePVP, factionName = GetZonePVPInfo()
|
Artemis:Transmit("spellCast", table, "ALERT")
|
||||||
|
end
|
||||||
Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName})
|
|
||||||
|
-- Detect cancellation of non instant casts
|
||||||
|
function Artemis:UNIT_SPELLCAST_INTERRUPTED (...)
|
||||||
|
local source, unitID, _, _, lineID = ...
|
||||||
|
if not (unitID == "player") and not (unitID == "target") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
lastLine[unitID] = nil
|
||||||
|
|
||||||
|
Artemis:Transmit("spellCastInterrupted", unitID, "ALERT")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Detect spell channels
|
||||||
|
function Artemis:UNIT_SPELLCAST_CHANNEL_START(...)
|
||||||
|
local _, unitID, spell, rank, lineID, spellID = ...
|
||||||
|
if not (unitID == "player") and not (unitID == "target") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
channeling[unitID] = true
|
||||||
|
|
||||||
|
local name, _, _, _, startTime, endTime, _, notInterruptible = UnitChannelInfo(unitID)
|
||||||
|
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
|
||||||
|
|
||||||
|
Artemis:Transmit("spellChannel", table, "ALERT")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Artemis:UNIT_SPELLCAST_CHANNEL_UPDATE (...)
|
||||||
|
local _, unitID, spell, rank, lineID, spellID = ...
|
||||||
|
if not (unitID == "player") and not (unitID == "target") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local name, _, _, _, startTime, endTime, _, notInterruptible = UnitChannelInfo(unitID)
|
||||||
|
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
|
||||||
|
|
||||||
|
Artemis:Transmit("spellChannel", table, "ALERT")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Detect cancellation of channels
|
||||||
|
function Artemis:UNIT_SPELLCAST_CHANNEL_STOP (...)
|
||||||
|
local source, unitID, _, _, lineID = ...
|
||||||
|
if not (unitID == "player") and not (unitID == "target") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
channeling[unitID] = false
|
||||||
|
|
||||||
|
Artemis:Transmit("spellChannelInterrupted", unitID, "ALERT")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Artemis:ZONE_CHANGED_NEW_AREA (...)
|
||||||
|
local pvpType, isSubZonePVP, factionName = GetZonePVPInfo()
|
||||||
|
|
||||||
|
Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName})
|
||||||
|
end
|
||||||
|
function Artemis:ZONE_CHANGED (...)
|
||||||
|
local pvpType, isSubZonePVP, factionName = GetZonePVPInfo()
|
||||||
|
|
||||||
|
Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName})
|
||||||
end
|
end
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||||
..\FrameXML\UI.xsd">
|
..\FrameXML\UI.xsd">
|
||||||
<Script file="AceAddon-3.0.lua"/>
|
<Script file="AceAddon-3.0.lua"/>
|
||||||
</Ui>
|
</Ui>
|
||||||
|
|||||||
@ -1,301 +1,301 @@
|
|||||||
--- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels.
|
--- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels.
|
||||||
-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
|
-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
|
||||||
-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
|
-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
|
||||||
--
|
--
|
||||||
-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
|
-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
|
||||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||||
-- and can be accessed directly, without having to explicitly call AceComm itself.\\
|
-- and can be accessed directly, without having to explicitly call AceComm itself.\\
|
||||||
-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
|
-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
|
||||||
-- make into AceComm.
|
-- make into AceComm.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceComm-3.0
|
-- @name AceComm-3.0
|
||||||
-- @release $Id: AceComm-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $
|
-- @release $Id: AceComm-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $
|
||||||
|
|
||||||
--[[ AceComm-3.0
|
--[[ AceComm-3.0
|
||||||
|
|
||||||
TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited.
|
TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited.
|
||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local CallbackHandler = LibStub("CallbackHandler-1.0")
|
local CallbackHandler = LibStub("CallbackHandler-1.0")
|
||||||
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
|
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
|
||||||
|
|
||||||
local MAJOR, MINOR = "AceComm-3.0", 10
|
local MAJOR, MINOR = "AceComm-3.0", 10
|
||||||
local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceComm then return end
|
if not AceComm then return end
|
||||||
|
|
||||||
-- Lua APIs
|
-- Lua APIs
|
||||||
local type, next, pairs, tostring = type, next, pairs, tostring
|
local type, next, pairs, tostring = type, next, pairs, tostring
|
||||||
local strsub, strfind = string.sub, string.find
|
local strsub, strfind = string.sub, string.find
|
||||||
local match = string.match
|
local match = string.match
|
||||||
local tinsert, tconcat = table.insert, table.concat
|
local tinsert, tconcat = table.insert, table.concat
|
||||||
local error, assert = error, assert
|
local error, assert = error, assert
|
||||||
|
|
||||||
-- WoW APIs
|
-- WoW APIs
|
||||||
local Ambiguate = Ambiguate
|
local Ambiguate = Ambiguate
|
||||||
|
|
||||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||||
-- List them here for Mikk's FindGlobals script
|
-- List them here for Mikk's FindGlobals script
|
||||||
-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler, RegisterAddonMessagePrefix
|
-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler, RegisterAddonMessagePrefix
|
||||||
|
|
||||||
AceComm.embeds = AceComm.embeds or {}
|
AceComm.embeds = AceComm.embeds or {}
|
||||||
|
|
||||||
-- for my sanity and yours, let's give the message type bytes some names
|
-- for my sanity and yours, let's give the message type bytes some names
|
||||||
local MSG_MULTI_FIRST = "\001"
|
local MSG_MULTI_FIRST = "\001"
|
||||||
local MSG_MULTI_NEXT = "\002"
|
local MSG_MULTI_NEXT = "\002"
|
||||||
local MSG_MULTI_LAST = "\003"
|
local MSG_MULTI_LAST = "\003"
|
||||||
local MSG_ESCAPE = "\004"
|
local MSG_ESCAPE = "\004"
|
||||||
|
|
||||||
-- remove old structures (pre WoW 4.0)
|
-- remove old structures (pre WoW 4.0)
|
||||||
AceComm.multipart_origprefixes = nil
|
AceComm.multipart_origprefixes = nil
|
||||||
AceComm.multipart_reassemblers = nil
|
AceComm.multipart_reassemblers = nil
|
||||||
|
|
||||||
-- the multipart message spool: indexed by a combination of sender+distribution+
|
-- the multipart message spool: indexed by a combination of sender+distribution+
|
||||||
AceComm.multipart_spool = AceComm.multipart_spool or {}
|
AceComm.multipart_spool = AceComm.multipart_spool or {}
|
||||||
|
|
||||||
--- Register for Addon Traffic on a specified prefix
|
--- Register for Addon Traffic on a specified prefix
|
||||||
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters
|
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters
|
||||||
-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
|
-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
|
||||||
function AceComm:RegisterComm(prefix, method)
|
function AceComm:RegisterComm(prefix, method)
|
||||||
if method == nil then
|
if method == nil then
|
||||||
method = "OnCommReceived"
|
method = "OnCommReceived"
|
||||||
end
|
end
|
||||||
|
|
||||||
if #prefix > 16 then -- TODO: 15?
|
if #prefix > 16 then -- TODO: 15?
|
||||||
error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters")
|
error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters")
|
||||||
end
|
end
|
||||||
RegisterAddonMessagePrefix(prefix)
|
RegisterAddonMessagePrefix(prefix)
|
||||||
|
|
||||||
return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
|
return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
|
||||||
end
|
end
|
||||||
|
|
||||||
local warnedPrefix=false
|
local warnedPrefix=false
|
||||||
|
|
||||||
--- Send a message over the Addon Channel
|
--- Send a message over the Addon Channel
|
||||||
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
|
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
|
||||||
-- @param text Data to send, nils (\000) not allowed. Any length.
|
-- @param text Data to send, nils (\000) not allowed. Any length.
|
||||||
-- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API
|
-- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API
|
||||||
-- @param target Destination for some distributions; see SendAddonMessage API
|
-- @param target Destination for some distributions; see SendAddonMessage API
|
||||||
-- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL".
|
-- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL".
|
||||||
-- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send.
|
-- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send.
|
||||||
-- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified.
|
-- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified.
|
||||||
function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg)
|
function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg)
|
||||||
prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery!
|
prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery!
|
||||||
if not( type(prefix)=="string" and
|
if not( type(prefix)=="string" and
|
||||||
type(text)=="string" and
|
type(text)=="string" and
|
||||||
type(distribution)=="string" and
|
type(distribution)=="string" and
|
||||||
(target==nil or type(target)=="string") and
|
(target==nil or type(target)=="string") and
|
||||||
(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
|
(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
|
||||||
) then
|
) then
|
||||||
error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
|
error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
local textlen = #text
|
local textlen = #text
|
||||||
local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327
|
local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327
|
||||||
local queueName = prefix..distribution..(target or "")
|
local queueName = prefix..distribution..(target or "")
|
||||||
|
|
||||||
local ctlCallback = nil
|
local ctlCallback = nil
|
||||||
if callbackFn then
|
if callbackFn then
|
||||||
ctlCallback = function(sent)
|
ctlCallback = function(sent)
|
||||||
return callbackFn(callbackArg, sent, textlen)
|
return callbackFn(callbackArg, sent, textlen)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local forceMultipart
|
local forceMultipart
|
||||||
if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character
|
if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character
|
||||||
-- we need to escape the first character with a \004
|
-- we need to escape the first character with a \004
|
||||||
if textlen+1 > maxtextlen then -- would we go over the size limit?
|
if textlen+1 > maxtextlen then -- would we go over the size limit?
|
||||||
forceMultipart = true -- just make it multipart, no escape problems then
|
forceMultipart = true -- just make it multipart, no escape problems then
|
||||||
else
|
else
|
||||||
text = "\004" .. text
|
text = "\004" .. text
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not forceMultipart and textlen <= maxtextlen then
|
if not forceMultipart and textlen <= maxtextlen then
|
||||||
-- fits all in one message
|
-- fits all in one message
|
||||||
CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
|
CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
|
||||||
else
|
else
|
||||||
maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1)
|
maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1)
|
||||||
|
|
||||||
-- first part
|
-- first part
|
||||||
local chunk = strsub(text, 1, maxtextlen)
|
local chunk = strsub(text, 1, maxtextlen)
|
||||||
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen)
|
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen)
|
||||||
|
|
||||||
-- continuation
|
-- continuation
|
||||||
local pos = 1+maxtextlen
|
local pos = 1+maxtextlen
|
||||||
|
|
||||||
while pos+maxtextlen <= textlen do
|
while pos+maxtextlen <= textlen do
|
||||||
chunk = strsub(text, pos, pos+maxtextlen-1)
|
chunk = strsub(text, pos, pos+maxtextlen-1)
|
||||||
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
|
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
|
||||||
pos = pos + maxtextlen
|
pos = pos + maxtextlen
|
||||||
end
|
end
|
||||||
|
|
||||||
-- final part
|
-- final part
|
||||||
chunk = strsub(text, pos)
|
chunk = strsub(text, pos)
|
||||||
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen)
|
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- Message receiving
|
-- Message receiving
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
do
|
do
|
||||||
local compost = setmetatable({}, {__mode = "k"})
|
local compost = setmetatable({}, {__mode = "k"})
|
||||||
local function new()
|
local function new()
|
||||||
local t = next(compost)
|
local t = next(compost)
|
||||||
if t then
|
if t then
|
||||||
compost[t]=nil
|
compost[t]=nil
|
||||||
for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
|
for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
|
||||||
t[i]=nil
|
t[i]=nil
|
||||||
end
|
end
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local function lostdatawarning(prefix,sender,where)
|
local function lostdatawarning(prefix,sender,where)
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
|
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
|
function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
|
||||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||||
local spool = AceComm.multipart_spool
|
local spool = AceComm.multipart_spool
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
if spool[key] then
|
if spool[key] then
|
||||||
lostdatawarning(prefix,sender,"First")
|
lostdatawarning(prefix,sender,"First")
|
||||||
-- continue and overwrite
|
-- continue and overwrite
|
||||||
end
|
end
|
||||||
--]]
|
--]]
|
||||||
|
|
||||||
spool[key] = message -- plain string for now
|
spool[key] = message -- plain string for now
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender)
|
function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender)
|
||||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||||
local spool = AceComm.multipart_spool
|
local spool = AceComm.multipart_spool
|
||||||
local olddata = spool[key]
|
local olddata = spool[key]
|
||||||
|
|
||||||
if not olddata then
|
if not olddata then
|
||||||
--lostdatawarning(prefix,sender,"Next")
|
--lostdatawarning(prefix,sender,"Next")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(olddata)~="table" then
|
if type(olddata)~="table" then
|
||||||
-- ... but what we have is not a table. So make it one. (Pull a composted one if available)
|
-- ... but what we have is not a table. So make it one. (Pull a composted one if available)
|
||||||
local t = new()
|
local t = new()
|
||||||
t[1] = olddata -- add old data as first string
|
t[1] = olddata -- add old data as first string
|
||||||
t[2] = message -- and new message as second string
|
t[2] = message -- and new message as second string
|
||||||
spool[key] = t -- and put the table in the spool instead of the old string
|
spool[key] = t -- and put the table in the spool instead of the old string
|
||||||
else
|
else
|
||||||
tinsert(olddata, message)
|
tinsert(olddata, message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender)
|
function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender)
|
||||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||||
local spool = AceComm.multipart_spool
|
local spool = AceComm.multipart_spool
|
||||||
local olddata = spool[key]
|
local olddata = spool[key]
|
||||||
|
|
||||||
if not olddata then
|
if not olddata then
|
||||||
--lostdatawarning(prefix,sender,"End")
|
--lostdatawarning(prefix,sender,"End")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
spool[key] = nil
|
spool[key] = nil
|
||||||
|
|
||||||
if type(olddata) == "table" then
|
if type(olddata) == "table" then
|
||||||
-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
|
-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
|
||||||
tinsert(olddata, message)
|
tinsert(olddata, message)
|
||||||
AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
|
AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
|
||||||
compost[olddata] = true
|
compost[olddata] = true
|
||||||
else
|
else
|
||||||
-- if we've only received a "first", the spooled data will still only be a string
|
-- if we've only received a "first", the spooled data will still only be a string
|
||||||
AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
|
AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- Embed CallbackHandler
|
-- Embed CallbackHandler
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
if not AceComm.callbacks then
|
if not AceComm.callbacks then
|
||||||
AceComm.callbacks = CallbackHandler:New(AceComm,
|
AceComm.callbacks = CallbackHandler:New(AceComm,
|
||||||
"_RegisterComm",
|
"_RegisterComm",
|
||||||
"UnregisterComm",
|
"UnregisterComm",
|
||||||
"UnregisterAllComm")
|
"UnregisterAllComm")
|
||||||
end
|
end
|
||||||
|
|
||||||
AceComm.callbacks.OnUsed = nil
|
AceComm.callbacks.OnUsed = nil
|
||||||
AceComm.callbacks.OnUnused = nil
|
AceComm.callbacks.OnUnused = nil
|
||||||
|
|
||||||
local function OnEvent(self, event, prefix, message, distribution, sender)
|
local function OnEvent(self, event, prefix, message, distribution, sender)
|
||||||
if event == "CHAT_MSG_ADDON" then
|
if event == "CHAT_MSG_ADDON" then
|
||||||
sender = Ambiguate(sender, "none")
|
sender = Ambiguate(sender, "none")
|
||||||
local control, rest = match(message, "^([\001-\009])(.*)")
|
local control, rest = match(message, "^([\001-\009])(.*)")
|
||||||
if control then
|
if control then
|
||||||
if control==MSG_MULTI_FIRST then
|
if control==MSG_MULTI_FIRST then
|
||||||
AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
|
AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
|
||||||
elseif control==MSG_MULTI_NEXT then
|
elseif control==MSG_MULTI_NEXT then
|
||||||
AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender)
|
AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender)
|
||||||
elseif control==MSG_MULTI_LAST then
|
elseif control==MSG_MULTI_LAST then
|
||||||
AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender)
|
AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender)
|
||||||
elseif control==MSG_ESCAPE then
|
elseif control==MSG_ESCAPE then
|
||||||
AceComm.callbacks:Fire(prefix, rest, distribution, sender)
|
AceComm.callbacks:Fire(prefix, rest, distribution, sender)
|
||||||
else
|
else
|
||||||
-- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!)
|
-- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
|
-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
|
||||||
AceComm.callbacks:Fire(prefix, message, distribution, sender)
|
AceComm.callbacks:Fire(prefix, message, distribution, sender)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
assert(false, "Received "..tostring(event).." event?!")
|
assert(false, "Received "..tostring(event).." event?!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame")
|
AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame")
|
||||||
AceComm.frame:SetScript("OnEvent", OnEvent)
|
AceComm.frame:SetScript("OnEvent", OnEvent)
|
||||||
AceComm.frame:UnregisterAllEvents()
|
AceComm.frame:UnregisterAllEvents()
|
||||||
AceComm.frame:RegisterEvent("CHAT_MSG_ADDON")
|
AceComm.frame:RegisterEvent("CHAT_MSG_ADDON")
|
||||||
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- Base library stuff
|
-- Base library stuff
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
local mixins = {
|
local mixins = {
|
||||||
"RegisterComm",
|
"RegisterComm",
|
||||||
"UnregisterComm",
|
"UnregisterComm",
|
||||||
"UnregisterAllComm",
|
"UnregisterAllComm",
|
||||||
"SendCommMessage",
|
"SendCommMessage",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:..
|
-- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:..
|
||||||
-- @param target target object to embed AceComm-3.0 in
|
-- @param target target object to embed AceComm-3.0 in
|
||||||
function AceComm:Embed(target)
|
function AceComm:Embed(target)
|
||||||
for k, v in pairs(mixins) do
|
for k, v in pairs(mixins) do
|
||||||
target[v] = self[v]
|
target[v] = self[v]
|
||||||
end
|
end
|
||||||
self.embeds[target] = true
|
self.embeds[target] = true
|
||||||
return target
|
return target
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceComm:OnEmbedDisable(target)
|
function AceComm:OnEmbedDisable(target)
|
||||||
target:UnregisterAllComm()
|
target:UnregisterAllComm()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update embeds
|
-- Update embeds
|
||||||
for target, v in pairs(AceComm.embeds) do
|
for target, v in pairs(AceComm.embeds) do
|
||||||
AceComm:Embed(target)
|
AceComm:Embed(target)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||||
..\FrameXML\UI.xsd">
|
..\FrameXML\UI.xsd">
|
||||||
<Script file="ChatThrottleLib.lua"/>
|
<Script file="ChatThrottleLib.lua"/>
|
||||||
<Script file="AceComm-3.0.lua"/>
|
<Script file="AceComm-3.0.lua"/>
|
||||||
</Ui>
|
</Ui>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,250 +1,250 @@
|
|||||||
--- **AceConsole-3.0** provides registration facilities for slash commands.
|
--- **AceConsole-3.0** provides registration facilities for slash commands.
|
||||||
-- You can register slash commands to your custom functions and use the `GetArgs` function to parse them
|
-- You can register slash commands to your custom functions and use the `GetArgs` function to parse them
|
||||||
-- to your addons individual needs.
|
-- to your addons individual needs.
|
||||||
--
|
--
|
||||||
-- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by
|
-- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by
|
||||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||||
-- and can be accessed directly, without having to explicitly call AceConsole itself.\\
|
-- and can be accessed directly, without having to explicitly call AceConsole itself.\\
|
||||||
-- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you
|
-- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you
|
||||||
-- make into AceConsole.
|
-- make into AceConsole.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceConsole-3.0
|
-- @name AceConsole-3.0
|
||||||
-- @release $Id: AceConsole-3.0.lua 1143 2016-07-11 08:52:03Z nevcairiel $
|
-- @release $Id: AceConsole-3.0.lua 1143 2016-07-11 08:52:03Z nevcairiel $
|
||||||
local MAJOR,MINOR = "AceConsole-3.0", 7
|
local MAJOR,MINOR = "AceConsole-3.0", 7
|
||||||
|
|
||||||
local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceConsole then return end -- No upgrade needed
|
if not AceConsole then return end -- No upgrade needed
|
||||||
|
|
||||||
AceConsole.embeds = AceConsole.embeds or {} -- table containing objects AceConsole is embedded in.
|
AceConsole.embeds = AceConsole.embeds or {} -- table containing objects AceConsole is embedded in.
|
||||||
AceConsole.commands = AceConsole.commands or {} -- table containing commands registered
|
AceConsole.commands = AceConsole.commands or {} -- table containing commands registered
|
||||||
AceConsole.weakcommands = AceConsole.weakcommands or {} -- table containing self, command => func references for weak commands that don't persist through enable/disable
|
AceConsole.weakcommands = AceConsole.weakcommands or {} -- table containing self, command => func references for weak commands that don't persist through enable/disable
|
||||||
|
|
||||||
-- Lua APIs
|
-- Lua APIs
|
||||||
local tconcat, tostring, select = table.concat, tostring, select
|
local tconcat, tostring, select = table.concat, tostring, select
|
||||||
local type, pairs, error = type, pairs, error
|
local type, pairs, error = type, pairs, error
|
||||||
local format, strfind, strsub = string.format, string.find, string.sub
|
local format, strfind, strsub = string.format, string.find, string.sub
|
||||||
local max = math.max
|
local max = math.max
|
||||||
|
|
||||||
-- WoW APIs
|
-- WoW APIs
|
||||||
local _G = _G
|
local _G = _G
|
||||||
|
|
||||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||||
-- List them here for Mikk's FindGlobals script
|
-- List them here for Mikk's FindGlobals script
|
||||||
-- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList
|
-- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList
|
||||||
|
|
||||||
local tmp={}
|
local tmp={}
|
||||||
local function Print(self,frame,...)
|
local function Print(self,frame,...)
|
||||||
local n=0
|
local n=0
|
||||||
if self ~= AceConsole then
|
if self ~= AceConsole then
|
||||||
n=n+1
|
n=n+1
|
||||||
tmp[n] = "|cff33ff99"..tostring( self ).."|r:"
|
tmp[n] = "|cff33ff99"..tostring( self ).."|r:"
|
||||||
end
|
end
|
||||||
for i=1, select("#", ...) do
|
for i=1, select("#", ...) do
|
||||||
n=n+1
|
n=n+1
|
||||||
tmp[n] = tostring(select(i, ...))
|
tmp[n] = tostring(select(i, ...))
|
||||||
end
|
end
|
||||||
frame:AddMessage( tconcat(tmp," ",1,n) )
|
frame:AddMessage( tconcat(tmp," ",1,n) )
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
|
--- Print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
|
||||||
-- @paramsig [chatframe ,] ...
|
-- @paramsig [chatframe ,] ...
|
||||||
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
|
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
|
||||||
-- @param ... List of any values to be printed
|
-- @param ... List of any values to be printed
|
||||||
function AceConsole:Print(...)
|
function AceConsole:Print(...)
|
||||||
local frame = ...
|
local frame = ...
|
||||||
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
|
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
|
||||||
return Print(self, frame, select(2,...))
|
return Print(self, frame, select(2,...))
|
||||||
else
|
else
|
||||||
return Print(self, DEFAULT_CHAT_FRAME, ...)
|
return Print(self, DEFAULT_CHAT_FRAME, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--- Formatted (using format()) print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
|
--- Formatted (using format()) print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
|
||||||
-- @paramsig [chatframe ,] "format"[, ...]
|
-- @paramsig [chatframe ,] "format"[, ...]
|
||||||
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
|
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
|
||||||
-- @param format Format string - same syntax as standard Lua format()
|
-- @param format Format string - same syntax as standard Lua format()
|
||||||
-- @param ... Arguments to the format string
|
-- @param ... Arguments to the format string
|
||||||
function AceConsole:Printf(...)
|
function AceConsole:Printf(...)
|
||||||
local frame = ...
|
local frame = ...
|
||||||
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
|
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
|
||||||
return Print(self, frame, format(select(2,...)))
|
return Print(self, frame, format(select(2,...)))
|
||||||
else
|
else
|
||||||
return Print(self, DEFAULT_CHAT_FRAME, format(...))
|
return Print(self, DEFAULT_CHAT_FRAME, format(...))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Register a simple chat command
|
--- Register a simple chat command
|
||||||
-- @param command Chat command to be registered WITHOUT leading "/"
|
-- @param command Chat command to be registered WITHOUT leading "/"
|
||||||
-- @param func Function to call when the slash command is being used (funcref or methodname)
|
-- @param func Function to call when the slash command is being used (funcref or methodname)
|
||||||
-- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true)
|
-- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true)
|
||||||
function AceConsole:RegisterChatCommand( command, func, persist )
|
function AceConsole:RegisterChatCommand( command, func, persist )
|
||||||
if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end
|
if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end
|
||||||
|
|
||||||
if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk
|
if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk
|
||||||
|
|
||||||
local name = "ACECONSOLE_"..command:upper()
|
local name = "ACECONSOLE_"..command:upper()
|
||||||
|
|
||||||
if type( func ) == "string" then
|
if type( func ) == "string" then
|
||||||
SlashCmdList[name] = function(input, editBox)
|
SlashCmdList[name] = function(input, editBox)
|
||||||
self[func](self, input, editBox)
|
self[func](self, input, editBox)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
SlashCmdList[name] = func
|
SlashCmdList[name] = func
|
||||||
end
|
end
|
||||||
_G["SLASH_"..name.."1"] = "/"..command:lower()
|
_G["SLASH_"..name.."1"] = "/"..command:lower()
|
||||||
AceConsole.commands[command] = name
|
AceConsole.commands[command] = name
|
||||||
-- non-persisting commands are registered for enabling disabling
|
-- non-persisting commands are registered for enabling disabling
|
||||||
if not persist then
|
if not persist then
|
||||||
if not AceConsole.weakcommands[self] then AceConsole.weakcommands[self] = {} end
|
if not AceConsole.weakcommands[self] then AceConsole.weakcommands[self] = {} end
|
||||||
AceConsole.weakcommands[self][command] = func
|
AceConsole.weakcommands[self][command] = func
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Unregister a chatcommand
|
--- Unregister a chatcommand
|
||||||
-- @param command Chat command to be unregistered WITHOUT leading "/"
|
-- @param command Chat command to be unregistered WITHOUT leading "/"
|
||||||
function AceConsole:UnregisterChatCommand( command )
|
function AceConsole:UnregisterChatCommand( command )
|
||||||
local name = AceConsole.commands[command]
|
local name = AceConsole.commands[command]
|
||||||
if name then
|
if name then
|
||||||
SlashCmdList[name] = nil
|
SlashCmdList[name] = nil
|
||||||
_G["SLASH_" .. name .. "1"] = nil
|
_G["SLASH_" .. name .. "1"] = nil
|
||||||
hash_SlashCmdList["/" .. command:upper()] = nil
|
hash_SlashCmdList["/" .. command:upper()] = nil
|
||||||
AceConsole.commands[command] = nil
|
AceConsole.commands[command] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get an iterator over all Chat Commands registered with AceConsole
|
--- Get an iterator over all Chat Commands registered with AceConsole
|
||||||
-- @return Iterator (pairs) over all commands
|
-- @return Iterator (pairs) over all commands
|
||||||
function AceConsole:IterateChatCommands() return pairs(AceConsole.commands) end
|
function AceConsole:IterateChatCommands() return pairs(AceConsole.commands) end
|
||||||
|
|
||||||
|
|
||||||
local function nils(n, ...)
|
local function nils(n, ...)
|
||||||
if n>1 then
|
if n>1 then
|
||||||
return nil, nils(n-1, ...)
|
return nil, nils(n-1, ...)
|
||||||
elseif n==1 then
|
elseif n==1 then
|
||||||
return nil, ...
|
return nil, ...
|
||||||
else
|
else
|
||||||
return ...
|
return ...
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--- Retreive one or more space-separated arguments from a string.
|
--- Retreive one or more space-separated arguments from a string.
|
||||||
-- Treats quoted strings and itemlinks as non-spaced.
|
-- Treats quoted strings and itemlinks as non-spaced.
|
||||||
-- @param str The raw argument string
|
-- @param str The raw argument string
|
||||||
-- @param numargs How many arguments to get (default 1)
|
-- @param numargs How many arguments to get (default 1)
|
||||||
-- @param startpos Where in the string to start scanning (default 1)
|
-- @param startpos Where in the string to start scanning (default 1)
|
||||||
-- @return Returns arg1, arg2, ..., nextposition\\
|
-- @return Returns arg1, arg2, ..., nextposition\\
|
||||||
-- Missing arguments will be returned as nils. 'nextposition' is returned as 1e9 at the end of the string.
|
-- Missing arguments will be returned as nils. 'nextposition' is returned as 1e9 at the end of the string.
|
||||||
function AceConsole:GetArgs(str, numargs, startpos)
|
function AceConsole:GetArgs(str, numargs, startpos)
|
||||||
numargs = numargs or 1
|
numargs = numargs or 1
|
||||||
startpos = max(startpos or 1, 1)
|
startpos = max(startpos or 1, 1)
|
||||||
|
|
||||||
local pos=startpos
|
local pos=startpos
|
||||||
|
|
||||||
-- find start of new arg
|
-- find start of new arg
|
||||||
pos = strfind(str, "[^ ]", pos)
|
pos = strfind(str, "[^ ]", pos)
|
||||||
if not pos then -- whoops, end of string
|
if not pos then -- whoops, end of string
|
||||||
return nils(numargs, 1e9)
|
return nils(numargs, 1e9)
|
||||||
end
|
end
|
||||||
|
|
||||||
if numargs<1 then
|
if numargs<1 then
|
||||||
return pos
|
return pos
|
||||||
end
|
end
|
||||||
|
|
||||||
-- quoted or space separated? find out which pattern to use
|
-- quoted or space separated? find out which pattern to use
|
||||||
local delim_or_pipe
|
local delim_or_pipe
|
||||||
local ch = strsub(str, pos, pos)
|
local ch = strsub(str, pos, pos)
|
||||||
if ch=='"' then
|
if ch=='"' then
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
delim_or_pipe='([|"])'
|
delim_or_pipe='([|"])'
|
||||||
elseif ch=="'" then
|
elseif ch=="'" then
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
delim_or_pipe="([|'])"
|
delim_or_pipe="([|'])"
|
||||||
else
|
else
|
||||||
delim_or_pipe="([| ])"
|
delim_or_pipe="([| ])"
|
||||||
end
|
end
|
||||||
|
|
||||||
startpos = pos
|
startpos = pos
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
-- find delimiter or hyperlink
|
-- find delimiter or hyperlink
|
||||||
local ch,_
|
local ch,_
|
||||||
pos,_,ch = strfind(str, delim_or_pipe, pos)
|
pos,_,ch = strfind(str, delim_or_pipe, pos)
|
||||||
|
|
||||||
if not pos then break end
|
if not pos then break end
|
||||||
|
|
||||||
if ch=="|" then
|
if ch=="|" then
|
||||||
-- some kind of escape
|
-- some kind of escape
|
||||||
|
|
||||||
if strsub(str,pos,pos+1)=="|H" then
|
if strsub(str,pos,pos+1)=="|H" then
|
||||||
-- It's a |H....|hhyper link!|h
|
-- It's a |H....|hhyper link!|h
|
||||||
pos=strfind(str, "|h", pos+2) -- first |h
|
pos=strfind(str, "|h", pos+2) -- first |h
|
||||||
if not pos then break end
|
if not pos then break end
|
||||||
|
|
||||||
pos=strfind(str, "|h", pos+2) -- second |h
|
pos=strfind(str, "|h", pos+2) -- second |h
|
||||||
if not pos then break end
|
if not pos then break end
|
||||||
elseif strsub(str,pos, pos+1) == "|T" then
|
elseif strsub(str,pos, pos+1) == "|T" then
|
||||||
-- It's a |T....|t texture
|
-- It's a |T....|t texture
|
||||||
pos=strfind(str, "|t", pos+2)
|
pos=strfind(str, "|t", pos+2)
|
||||||
if not pos then break end
|
if not pos then break end
|
||||||
end
|
end
|
||||||
|
|
||||||
pos=pos+2 -- skip past this escape (last |h if it was a hyperlink)
|
pos=pos+2 -- skip past this escape (last |h if it was a hyperlink)
|
||||||
|
|
||||||
else
|
else
|
||||||
-- found delimiter, done with this arg
|
-- found delimiter, done with this arg
|
||||||
return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1)
|
return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink)
|
-- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink)
|
||||||
return strsub(str, startpos), nils(numargs-1, 1e9)
|
return strsub(str, startpos), nils(numargs-1, 1e9)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--- embedding and embed handling
|
--- embedding and embed handling
|
||||||
|
|
||||||
local mixins = {
|
local mixins = {
|
||||||
"Print",
|
"Print",
|
||||||
"Printf",
|
"Printf",
|
||||||
"RegisterChatCommand",
|
"RegisterChatCommand",
|
||||||
"UnregisterChatCommand",
|
"UnregisterChatCommand",
|
||||||
"GetArgs",
|
"GetArgs",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Embeds AceConsole into the target object making the functions from the mixins list available on target:..
|
-- Embeds AceConsole into the target object making the functions from the mixins list available on target:..
|
||||||
-- @param target target object to embed AceBucket in
|
-- @param target target object to embed AceBucket in
|
||||||
function AceConsole:Embed( target )
|
function AceConsole:Embed( target )
|
||||||
for k, v in pairs( mixins ) do
|
for k, v in pairs( mixins ) do
|
||||||
target[v] = self[v]
|
target[v] = self[v]
|
||||||
end
|
end
|
||||||
self.embeds[target] = true
|
self.embeds[target] = true
|
||||||
return target
|
return target
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceConsole:OnEmbedEnable( target )
|
function AceConsole:OnEmbedEnable( target )
|
||||||
if AceConsole.weakcommands[target] then
|
if AceConsole.weakcommands[target] then
|
||||||
for command, func in pairs( AceConsole.weakcommands[target] ) do
|
for command, func in pairs( AceConsole.weakcommands[target] ) do
|
||||||
target:RegisterChatCommand( command, func, false, true ) -- nonpersisting and silent registry
|
target:RegisterChatCommand( command, func, false, true ) -- nonpersisting and silent registry
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceConsole:OnEmbedDisable( target )
|
function AceConsole:OnEmbedDisable( target )
|
||||||
if AceConsole.weakcommands[target] then
|
if AceConsole.weakcommands[target] then
|
||||||
for command, func in pairs( AceConsole.weakcommands[target] ) do
|
for command, func in pairs( AceConsole.weakcommands[target] ) do
|
||||||
target:UnregisterChatCommand( command ) -- TODO: this could potentially unregister a command from another application in case of command conflicts. Do we care?
|
target:UnregisterChatCommand( command ) -- TODO: this could potentially unregister a command from another application in case of command conflicts. Do we care?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for addon in pairs(AceConsole.embeds) do
|
for addon in pairs(AceConsole.embeds) do
|
||||||
AceConsole:Embed(addon)
|
AceConsole:Embed(addon)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||||
..\FrameXML\UI.xsd">
|
..\FrameXML\UI.xsd">
|
||||||
<Script file="AceConsole-3.0.lua"/>
|
<Script file="AceConsole-3.0.lua"/>
|
||||||
</Ui>
|
</Ui>
|
||||||
|
|||||||
@ -1,126 +1,126 @@
|
|||||||
--- AceEvent-3.0 provides event registration and secure dispatching.
|
--- AceEvent-3.0 provides event registration and secure dispatching.
|
||||||
-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around
|
-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around
|
||||||
-- CallbackHandler, and dispatches all game events or addon message to the registrees.
|
-- CallbackHandler, and dispatches all game events or addon message to the registrees.
|
||||||
--
|
--
|
||||||
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
|
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
|
||||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||||
-- and can be accessed directly, without having to explicitly call AceEvent itself.\\
|
-- and can be accessed directly, without having to explicitly call AceEvent itself.\\
|
||||||
-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you
|
-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you
|
||||||
-- make into AceEvent.
|
-- make into AceEvent.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceEvent-3.0
|
-- @name AceEvent-3.0
|
||||||
-- @release $Id: AceEvent-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $
|
-- @release $Id: AceEvent-3.0.lua 1161 2017-08-12 14:30:16Z funkydude $
|
||||||
local CallbackHandler = LibStub("CallbackHandler-1.0")
|
local CallbackHandler = LibStub("CallbackHandler-1.0")
|
||||||
|
|
||||||
local MAJOR, MINOR = "AceEvent-3.0", 4
|
local MAJOR, MINOR = "AceEvent-3.0", 4
|
||||||
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR)
|
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceEvent then return end
|
if not AceEvent then return end
|
||||||
|
|
||||||
-- Lua APIs
|
-- Lua APIs
|
||||||
local pairs = pairs
|
local pairs = pairs
|
||||||
|
|
||||||
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
|
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
|
||||||
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
|
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
|
||||||
|
|
||||||
-- APIs and registry for blizzard events, using CallbackHandler lib
|
-- APIs and registry for blizzard events, using CallbackHandler lib
|
||||||
if not AceEvent.events then
|
if not AceEvent.events then
|
||||||
AceEvent.events = CallbackHandler:New(AceEvent,
|
AceEvent.events = CallbackHandler:New(AceEvent,
|
||||||
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
|
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceEvent.events:OnUsed(target, eventname)
|
function AceEvent.events:OnUsed(target, eventname)
|
||||||
AceEvent.frame:RegisterEvent(eventname)
|
AceEvent.frame:RegisterEvent(eventname)
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceEvent.events:OnUnused(target, eventname)
|
function AceEvent.events:OnUnused(target, eventname)
|
||||||
AceEvent.frame:UnregisterEvent(eventname)
|
AceEvent.frame:UnregisterEvent(eventname)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- APIs and registry for IPC messages, using CallbackHandler lib
|
-- APIs and registry for IPC messages, using CallbackHandler lib
|
||||||
if not AceEvent.messages then
|
if not AceEvent.messages then
|
||||||
AceEvent.messages = CallbackHandler:New(AceEvent,
|
AceEvent.messages = CallbackHandler:New(AceEvent,
|
||||||
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
|
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
|
||||||
)
|
)
|
||||||
AceEvent.SendMessage = AceEvent.messages.Fire
|
AceEvent.SendMessage = AceEvent.messages.Fire
|
||||||
end
|
end
|
||||||
|
|
||||||
--- embedding and embed handling
|
--- embedding and embed handling
|
||||||
local mixins = {
|
local mixins = {
|
||||||
"RegisterEvent", "UnregisterEvent",
|
"RegisterEvent", "UnregisterEvent",
|
||||||
"RegisterMessage", "UnregisterMessage",
|
"RegisterMessage", "UnregisterMessage",
|
||||||
"SendMessage",
|
"SendMessage",
|
||||||
"UnregisterAllEvents", "UnregisterAllMessages",
|
"UnregisterAllEvents", "UnregisterAllMessages",
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Register for a Blizzard Event.
|
--- Register for a Blizzard Event.
|
||||||
-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
|
-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
|
||||||
-- Any arguments to the event will be passed on after that.
|
-- Any arguments to the event will be passed on after that.
|
||||||
-- @name AceEvent:RegisterEvent
|
-- @name AceEvent:RegisterEvent
|
||||||
-- @class function
|
-- @class function
|
||||||
-- @paramsig event[, callback [, arg]]
|
-- @paramsig event[, callback [, arg]]
|
||||||
-- @param event The event to register for
|
-- @param event The event to register for
|
||||||
-- @param callback The callback function to call when the event is triggered (funcref or method, defaults to a method with the event name)
|
-- @param callback The callback function to call when the event is triggered (funcref or method, defaults to a method with the event name)
|
||||||
-- @param arg An optional argument to pass to the callback function
|
-- @param arg An optional argument to pass to the callback function
|
||||||
|
|
||||||
--- Unregister an event.
|
--- Unregister an event.
|
||||||
-- @name AceEvent:UnregisterEvent
|
-- @name AceEvent:UnregisterEvent
|
||||||
-- @class function
|
-- @class function
|
||||||
-- @paramsig event
|
-- @paramsig event
|
||||||
-- @param event The event to unregister
|
-- @param event The event to unregister
|
||||||
|
|
||||||
--- Register for a custom AceEvent-internal message.
|
--- Register for a custom AceEvent-internal message.
|
||||||
-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
|
-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
|
||||||
-- Any arguments to the event will be passed on after that.
|
-- Any arguments to the event will be passed on after that.
|
||||||
-- @name AceEvent:RegisterMessage
|
-- @name AceEvent:RegisterMessage
|
||||||
-- @class function
|
-- @class function
|
||||||
-- @paramsig message[, callback [, arg]]
|
-- @paramsig message[, callback [, arg]]
|
||||||
-- @param message The message to register for
|
-- @param message The message to register for
|
||||||
-- @param callback The callback function to call when the message is triggered (funcref or method, defaults to a method with the event name)
|
-- @param callback The callback function to call when the message is triggered (funcref or method, defaults to a method with the event name)
|
||||||
-- @param arg An optional argument to pass to the callback function
|
-- @param arg An optional argument to pass to the callback function
|
||||||
|
|
||||||
--- Unregister a message
|
--- Unregister a message
|
||||||
-- @name AceEvent:UnregisterMessage
|
-- @name AceEvent:UnregisterMessage
|
||||||
-- @class function
|
-- @class function
|
||||||
-- @paramsig message
|
-- @paramsig message
|
||||||
-- @param message The message to unregister
|
-- @param message The message to unregister
|
||||||
|
|
||||||
--- Send a message over the AceEvent-3.0 internal message system to other addons registered for this message.
|
--- Send a message over the AceEvent-3.0 internal message system to other addons registered for this message.
|
||||||
-- @name AceEvent:SendMessage
|
-- @name AceEvent:SendMessage
|
||||||
-- @class function
|
-- @class function
|
||||||
-- @paramsig message, ...
|
-- @paramsig message, ...
|
||||||
-- @param message The message to send
|
-- @param message The message to send
|
||||||
-- @param ... Any arguments to the message
|
-- @param ... Any arguments to the message
|
||||||
|
|
||||||
|
|
||||||
-- Embeds AceEvent into the target object making the functions from the mixins list available on target:..
|
-- Embeds AceEvent into the target object making the functions from the mixins list available on target:..
|
||||||
-- @param target target object to embed AceEvent in
|
-- @param target target object to embed AceEvent in
|
||||||
function AceEvent:Embed(target)
|
function AceEvent:Embed(target)
|
||||||
for k, v in pairs(mixins) do
|
for k, v in pairs(mixins) do
|
||||||
target[v] = self[v]
|
target[v] = self[v]
|
||||||
end
|
end
|
||||||
self.embeds[target] = true
|
self.embeds[target] = true
|
||||||
return target
|
return target
|
||||||
end
|
end
|
||||||
|
|
||||||
-- AceEvent:OnEmbedDisable( target )
|
-- AceEvent:OnEmbedDisable( target )
|
||||||
-- target (object) - target object that is being disabled
|
-- target (object) - target object that is being disabled
|
||||||
--
|
--
|
||||||
-- Unregister all events messages etc when the target disables.
|
-- Unregister all events messages etc when the target disables.
|
||||||
-- this method should be called by the target manually or by an addon framework
|
-- this method should be called by the target manually or by an addon framework
|
||||||
function AceEvent:OnEmbedDisable(target)
|
function AceEvent:OnEmbedDisable(target)
|
||||||
target:UnregisterAllEvents()
|
target:UnregisterAllEvents()
|
||||||
target:UnregisterAllMessages()
|
target:UnregisterAllMessages()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Script to fire blizzard events into the event listeners
|
-- Script to fire blizzard events into the event listeners
|
||||||
local events = AceEvent.events
|
local events = AceEvent.events
|
||||||
AceEvent.frame:SetScript("OnEvent", function(this, event, ...)
|
AceEvent.frame:SetScript("OnEvent", function(this, event, ...)
|
||||||
events:Fire(event, ...)
|
events:Fire(event, ...)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--- Finally: upgrade our old embeds
|
--- Finally: upgrade our old embeds
|
||||||
for target, v in pairs(AceEvent.embeds) do
|
for target, v in pairs(AceEvent.embeds) do
|
||||||
AceEvent:Embed(target)
|
AceEvent:Embed(target)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||||
..\FrameXML\UI.xsd">
|
..\FrameXML\UI.xsd">
|
||||||
<Script file="AceEvent-3.0.lua"/>
|
<Script file="AceEvent-3.0.lua"/>
|
||||||
</Ui>
|
</Ui>
|
||||||
|
|||||||
@ -1,276 +1,276 @@
|
|||||||
--- **AceTimer-3.0** provides a central facility for registering timers.
|
--- **AceTimer-3.0** provides a central facility for registering timers.
|
||||||
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
|
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
|
||||||
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
|
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
|
||||||
-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
|
-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
|
||||||
-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
|
-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
|
||||||
-- restricts us to.
|
-- restricts us to.
|
||||||
--
|
--
|
||||||
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
|
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
|
||||||
-- need to cancel the timer you just registered.
|
-- need to cancel the timer you just registered.
|
||||||
--
|
--
|
||||||
-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
|
-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
|
||||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||||
-- and can be accessed directly, without having to explicitly call AceTimer itself.\\
|
-- and can be accessed directly, without having to explicitly call AceTimer itself.\\
|
||||||
-- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you
|
-- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you
|
||||||
-- make into AceTimer.
|
-- make into AceTimer.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceTimer-3.0
|
-- @name AceTimer-3.0
|
||||||
-- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $
|
-- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $
|
||||||
|
|
||||||
local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes
|
local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes
|
||||||
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceTimer then return end -- No upgrade needed
|
if not AceTimer then return end -- No upgrade needed
|
||||||
AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
|
AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
|
||||||
local activeTimers = AceTimer.activeTimers -- Upvalue our private data
|
local activeTimers = AceTimer.activeTimers -- Upvalue our private data
|
||||||
|
|
||||||
-- Lua APIs
|
-- Lua APIs
|
||||||
local type, unpack, next, error, select = type, unpack, next, error, select
|
local type, unpack, next, error, select = type, unpack, next, error, select
|
||||||
-- WoW APIs
|
-- WoW APIs
|
||||||
local GetTime, C_TimerAfter = GetTime, C_Timer.After
|
local GetTime, C_TimerAfter = GetTime, C_Timer.After
|
||||||
|
|
||||||
local function new(self, loop, func, delay, ...)
|
local function new(self, loop, func, delay, ...)
|
||||||
if delay < 0.01 then
|
if delay < 0.01 then
|
||||||
delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us
|
delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us
|
||||||
end
|
end
|
||||||
|
|
||||||
local timer = {...}
|
local timer = {...}
|
||||||
timer.object = self
|
timer.object = self
|
||||||
timer.func = func
|
timer.func = func
|
||||||
timer.looping = loop
|
timer.looping = loop
|
||||||
timer.argsCount = select("#", ...)
|
timer.argsCount = select("#", ...)
|
||||||
timer.delay = delay
|
timer.delay = delay
|
||||||
timer.ends = GetTime() + delay
|
timer.ends = GetTime() + delay
|
||||||
|
|
||||||
activeTimers[timer] = timer
|
activeTimers[timer] = timer
|
||||||
|
|
||||||
-- Create new timer closure to wrap the "timer" object
|
-- Create new timer closure to wrap the "timer" object
|
||||||
timer.callback = function()
|
timer.callback = function()
|
||||||
if not timer.cancelled then
|
if not timer.cancelled then
|
||||||
if type(timer.func) == "string" then
|
if type(timer.func) == "string" then
|
||||||
-- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
|
-- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
|
||||||
-- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
|
-- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
|
||||||
timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount))
|
timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount))
|
||||||
else
|
else
|
||||||
timer.func(unpack(timer, 1, timer.argsCount))
|
timer.func(unpack(timer, 1, timer.argsCount))
|
||||||
end
|
end
|
||||||
|
|
||||||
if timer.looping and not timer.cancelled then
|
if timer.looping and not timer.cancelled then
|
||||||
-- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
|
-- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
|
||||||
-- due to fps differences
|
-- due to fps differences
|
||||||
local time = GetTime()
|
local time = GetTime()
|
||||||
local delay = timer.delay - (time - timer.ends)
|
local delay = timer.delay - (time - timer.ends)
|
||||||
-- Ensure the delay doesn't go below the threshold
|
-- Ensure the delay doesn't go below the threshold
|
||||||
if delay < 0.01 then delay = 0.01 end
|
if delay < 0.01 then delay = 0.01 end
|
||||||
C_TimerAfter(delay, timer.callback)
|
C_TimerAfter(delay, timer.callback)
|
||||||
timer.ends = time + delay
|
timer.ends = time + delay
|
||||||
else
|
else
|
||||||
activeTimers[timer.handle or timer] = nil
|
activeTimers[timer.handle or timer] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
C_TimerAfter(delay, timer.callback)
|
C_TimerAfter(delay, timer.callback)
|
||||||
return timer
|
return timer
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Schedule a new one-shot timer.
|
--- Schedule a new one-shot timer.
|
||||||
-- The timer will fire once in `delay` seconds, unless canceled before.
|
-- The timer will fire once in `delay` seconds, unless canceled before.
|
||||||
-- @param callback Callback function for the timer pulse (funcref or method name).
|
-- @param callback Callback function for the timer pulse (funcref or method name).
|
||||||
-- @param delay Delay for the timer, in seconds.
|
-- @param delay Delay for the timer, in seconds.
|
||||||
-- @param ... An optional, unlimited amount of arguments to pass to the callback function.
|
-- @param ... An optional, unlimited amount of arguments to pass to the callback function.
|
||||||
-- @usage
|
-- @usage
|
||||||
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
||||||
--
|
--
|
||||||
-- function MyAddOn:OnEnable()
|
-- function MyAddOn:OnEnable()
|
||||||
-- self:ScheduleTimer("TimerFeedback", 5)
|
-- self:ScheduleTimer("TimerFeedback", 5)
|
||||||
-- end
|
-- end
|
||||||
--
|
--
|
||||||
-- function MyAddOn:TimerFeedback()
|
-- function MyAddOn:TimerFeedback()
|
||||||
-- print("5 seconds passed")
|
-- print("5 seconds passed")
|
||||||
-- end
|
-- end
|
||||||
function AceTimer:ScheduleTimer(func, delay, ...)
|
function AceTimer:ScheduleTimer(func, delay, ...)
|
||||||
if not func or not delay then
|
if not func or not delay then
|
||||||
error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
|
error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
|
||||||
end
|
end
|
||||||
if type(func) == "string" then
|
if type(func) == "string" then
|
||||||
if type(self) ~= "table" then
|
if type(self) ~= "table" then
|
||||||
error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2)
|
error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2)
|
||||||
elseif not self[func] then
|
elseif not self[func] then
|
||||||
error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
|
error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return new(self, nil, func, delay, ...)
|
return new(self, nil, func, delay, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Schedule a repeating timer.
|
--- Schedule a repeating timer.
|
||||||
-- The timer will fire every `delay` seconds, until canceled.
|
-- The timer will fire every `delay` seconds, until canceled.
|
||||||
-- @param callback Callback function for the timer pulse (funcref or method name).
|
-- @param callback Callback function for the timer pulse (funcref or method name).
|
||||||
-- @param delay Delay for the timer, in seconds.
|
-- @param delay Delay for the timer, in seconds.
|
||||||
-- @param ... An optional, unlimited amount of arguments to pass to the callback function.
|
-- @param ... An optional, unlimited amount of arguments to pass to the callback function.
|
||||||
-- @usage
|
-- @usage
|
||||||
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
||||||
--
|
--
|
||||||
-- function MyAddOn:OnEnable()
|
-- function MyAddOn:OnEnable()
|
||||||
-- self.timerCount = 0
|
-- self.timerCount = 0
|
||||||
-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
|
-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
|
||||||
-- end
|
-- end
|
||||||
--
|
--
|
||||||
-- function MyAddOn:TimerFeedback()
|
-- function MyAddOn:TimerFeedback()
|
||||||
-- self.timerCount = self.timerCount + 1
|
-- self.timerCount = self.timerCount + 1
|
||||||
-- print(("%d seconds passed"):format(5 * self.timerCount))
|
-- print(("%d seconds passed"):format(5 * self.timerCount))
|
||||||
-- -- run 30 seconds in total
|
-- -- run 30 seconds in total
|
||||||
-- if self.timerCount == 6 then
|
-- if self.timerCount == 6 then
|
||||||
-- self:CancelTimer(self.testTimer)
|
-- self:CancelTimer(self.testTimer)
|
||||||
-- end
|
-- end
|
||||||
-- end
|
-- end
|
||||||
function AceTimer:ScheduleRepeatingTimer(func, delay, ...)
|
function AceTimer:ScheduleRepeatingTimer(func, delay, ...)
|
||||||
if not func or not delay then
|
if not func or not delay then
|
||||||
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
|
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
|
||||||
end
|
end
|
||||||
if type(func) == "string" then
|
if type(func) == "string" then
|
||||||
if type(self) ~= "table" then
|
if type(self) ~= "table" then
|
||||||
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2)
|
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2)
|
||||||
elseif not self[func] then
|
elseif not self[func] then
|
||||||
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
|
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return new(self, true, func, delay, ...)
|
return new(self, true, func, delay, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
|
--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
|
||||||
-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
|
-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
|
||||||
-- and the timer has not fired yet or was canceled before.
|
-- and the timer has not fired yet or was canceled before.
|
||||||
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
||||||
function AceTimer:CancelTimer(id)
|
function AceTimer:CancelTimer(id)
|
||||||
local timer = activeTimers[id]
|
local timer = activeTimers[id]
|
||||||
|
|
||||||
if not timer then
|
if not timer then
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
timer.cancelled = true
|
timer.cancelled = true
|
||||||
activeTimers[id] = nil
|
activeTimers[id] = nil
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Cancels all timers registered to the current addon object ('self')
|
--- Cancels all timers registered to the current addon object ('self')
|
||||||
function AceTimer:CancelAllTimers()
|
function AceTimer:CancelAllTimers()
|
||||||
for k,v in pairs(activeTimers) do
|
for k,v in pairs(activeTimers) do
|
||||||
if v.object == self then
|
if v.object == self then
|
||||||
AceTimer.CancelTimer(self, k)
|
AceTimer.CancelTimer(self, k)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the time left for a timer with the given id, registered by the current addon object ('self').
|
--- Returns the time left for a timer with the given id, registered by the current addon object ('self').
|
||||||
-- This function will return 0 when the id is invalid.
|
-- This function will return 0 when the id is invalid.
|
||||||
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
||||||
-- @return The time left on the timer.
|
-- @return The time left on the timer.
|
||||||
function AceTimer:TimeLeft(id)
|
function AceTimer:TimeLeft(id)
|
||||||
local timer = activeTimers[id]
|
local timer = activeTimers[id]
|
||||||
if not timer then
|
if not timer then
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
return timer.ends - GetTime()
|
return timer.ends - GetTime()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------
|
-- ---------------------------------------------------------------------
|
||||||
-- Upgrading
|
-- Upgrading
|
||||||
|
|
||||||
-- Upgrade from old hash-bucket based timers to C_Timer.After timers.
|
-- Upgrade from old hash-bucket based timers to C_Timer.After timers.
|
||||||
if oldminor and oldminor < 10 then
|
if oldminor and oldminor < 10 then
|
||||||
-- disable old timer logic
|
-- disable old timer logic
|
||||||
AceTimer.frame:SetScript("OnUpdate", nil)
|
AceTimer.frame:SetScript("OnUpdate", nil)
|
||||||
AceTimer.frame:SetScript("OnEvent", nil)
|
AceTimer.frame:SetScript("OnEvent", nil)
|
||||||
AceTimer.frame:UnregisterAllEvents()
|
AceTimer.frame:UnregisterAllEvents()
|
||||||
-- convert timers
|
-- convert timers
|
||||||
for object,timers in pairs(AceTimer.selfs) do
|
for object,timers in pairs(AceTimer.selfs) do
|
||||||
for handle,timer in pairs(timers) do
|
for handle,timer in pairs(timers) do
|
||||||
if type(timer) == "table" and timer.callback then
|
if type(timer) == "table" and timer.callback then
|
||||||
local newTimer
|
local newTimer
|
||||||
if timer.delay then
|
if timer.delay then
|
||||||
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
|
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
|
||||||
else
|
else
|
||||||
newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
|
newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
|
||||||
end
|
end
|
||||||
-- Use the old handle for old timers
|
-- Use the old handle for old timers
|
||||||
activeTimers[newTimer] = nil
|
activeTimers[newTimer] = nil
|
||||||
activeTimers[handle] = newTimer
|
activeTimers[handle] = newTimer
|
||||||
newTimer.handle = handle
|
newTimer.handle = handle
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
AceTimer.selfs = nil
|
AceTimer.selfs = nil
|
||||||
AceTimer.hash = nil
|
AceTimer.hash = nil
|
||||||
AceTimer.debug = nil
|
AceTimer.debug = nil
|
||||||
elseif oldminor and oldminor < 17 then
|
elseif oldminor and oldminor < 17 then
|
||||||
-- Upgrade from old animation based timers to C_Timer.After timers.
|
-- Upgrade from old animation based timers to C_Timer.After timers.
|
||||||
AceTimer.inactiveTimers = nil
|
AceTimer.inactiveTimers = nil
|
||||||
AceTimer.frame = nil
|
AceTimer.frame = nil
|
||||||
local oldTimers = AceTimer.activeTimers
|
local oldTimers = AceTimer.activeTimers
|
||||||
-- Clear old timer table and update upvalue
|
-- Clear old timer table and update upvalue
|
||||||
AceTimer.activeTimers = {}
|
AceTimer.activeTimers = {}
|
||||||
activeTimers = AceTimer.activeTimers
|
activeTimers = AceTimer.activeTimers
|
||||||
for handle, timer in pairs(oldTimers) do
|
for handle, timer in pairs(oldTimers) do
|
||||||
local newTimer
|
local newTimer
|
||||||
-- Stop the old timer animation
|
-- Stop the old timer animation
|
||||||
local duration, elapsed = timer:GetDuration(), timer:GetElapsed()
|
local duration, elapsed = timer:GetDuration(), timer:GetElapsed()
|
||||||
timer:GetParent():Stop()
|
timer:GetParent():Stop()
|
||||||
if timer.looping then
|
if timer.looping then
|
||||||
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount))
|
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount))
|
||||||
else
|
else
|
||||||
newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount))
|
newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount))
|
||||||
end
|
end
|
||||||
-- Use the old handle for old timers
|
-- Use the old handle for old timers
|
||||||
activeTimers[newTimer] = nil
|
activeTimers[newTimer] = nil
|
||||||
activeTimers[handle] = newTimer
|
activeTimers[handle] = newTimer
|
||||||
newTimer.handle = handle
|
newTimer.handle = handle
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Migrate transitional handles
|
-- Migrate transitional handles
|
||||||
if oldminor < 13 and AceTimer.hashCompatTable then
|
if oldminor < 13 and AceTimer.hashCompatTable then
|
||||||
for handle, id in pairs(AceTimer.hashCompatTable) do
|
for handle, id in pairs(AceTimer.hashCompatTable) do
|
||||||
local t = activeTimers[id]
|
local t = activeTimers[id]
|
||||||
if t then
|
if t then
|
||||||
activeTimers[id] = nil
|
activeTimers[id] = nil
|
||||||
activeTimers[handle] = t
|
activeTimers[handle] = t
|
||||||
t.handle = handle
|
t.handle = handle
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
AceTimer.hashCompatTable = nil
|
AceTimer.hashCompatTable = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------
|
-- ---------------------------------------------------------------------
|
||||||
-- Embed handling
|
-- Embed handling
|
||||||
|
|
||||||
AceTimer.embeds = AceTimer.embeds or {}
|
AceTimer.embeds = AceTimer.embeds or {}
|
||||||
|
|
||||||
local mixins = {
|
local mixins = {
|
||||||
"ScheduleTimer", "ScheduleRepeatingTimer",
|
"ScheduleTimer", "ScheduleRepeatingTimer",
|
||||||
"CancelTimer", "CancelAllTimers",
|
"CancelTimer", "CancelAllTimers",
|
||||||
"TimeLeft"
|
"TimeLeft"
|
||||||
}
|
}
|
||||||
|
|
||||||
function AceTimer:Embed(target)
|
function AceTimer:Embed(target)
|
||||||
AceTimer.embeds[target] = true
|
AceTimer.embeds[target] = true
|
||||||
for _,v in pairs(mixins) do
|
for _,v in pairs(mixins) do
|
||||||
target[v] = AceTimer[v]
|
target[v] = AceTimer[v]
|
||||||
end
|
end
|
||||||
return target
|
return target
|
||||||
end
|
end
|
||||||
|
|
||||||
-- AceTimer:OnEmbedDisable(target)
|
-- AceTimer:OnEmbedDisable(target)
|
||||||
-- target (object) - target object that AceTimer is embedded in.
|
-- target (object) - target object that AceTimer is embedded in.
|
||||||
--
|
--
|
||||||
-- cancel all timers registered for the object
|
-- cancel all timers registered for the object
|
||||||
function AceTimer:OnEmbedDisable(target)
|
function AceTimer:OnEmbedDisable(target)
|
||||||
target:CancelAllTimers()
|
target:CancelAllTimers()
|
||||||
end
|
end
|
||||||
|
|
||||||
for addon in pairs(AceTimer.embeds) do
|
for addon in pairs(AceTimer.embeds) do
|
||||||
AceTimer:Embed(addon)
|
AceTimer:Embed(addon)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||||
..\FrameXML\UI.xsd">
|
..\FrameXML\UI.xsd">
|
||||||
<Script file="AceTimer-3.0.lua"/>
|
<Script file="AceTimer-3.0.lua"/>
|
||||||
</Ui>
|
</Ui>
|
||||||
|
|||||||
@ -1,30 +1,30 @@
|
|||||||
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
|
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
|
||||||
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
|
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
|
||||||
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
|
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
|
||||||
local LibStub = _G[LIBSTUB_MAJOR]
|
local LibStub = _G[LIBSTUB_MAJOR]
|
||||||
|
|
||||||
if not LibStub or LibStub.minor < LIBSTUB_MINOR then
|
if not LibStub or LibStub.minor < LIBSTUB_MINOR then
|
||||||
LibStub = LibStub or {libs = {}, minors = {} }
|
LibStub = LibStub or {libs = {}, minors = {} }
|
||||||
_G[LIBSTUB_MAJOR] = LibStub
|
_G[LIBSTUB_MAJOR] = LibStub
|
||||||
LibStub.minor = LIBSTUB_MINOR
|
LibStub.minor = LIBSTUB_MINOR
|
||||||
|
|
||||||
function LibStub:NewLibrary(major, minor)
|
function LibStub:NewLibrary(major, minor)
|
||||||
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
|
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
|
||||||
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
|
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
|
||||||
|
|
||||||
local oldminor = self.minors[major]
|
local oldminor = self.minors[major]
|
||||||
if oldminor and oldminor >= minor then return nil end
|
if oldminor and oldminor >= minor then return nil end
|
||||||
self.minors[major], self.libs[major] = minor, self.libs[major] or {}
|
self.minors[major], self.libs[major] = minor, self.libs[major] or {}
|
||||||
return self.libs[major], oldminor
|
return self.libs[major], oldminor
|
||||||
end
|
end
|
||||||
|
|
||||||
function LibStub:GetLibrary(major, silent)
|
function LibStub:GetLibrary(major, silent)
|
||||||
if not self.libs[major] and not silent then
|
if not self.libs[major] and not silent then
|
||||||
error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
|
error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
|
||||||
end
|
end
|
||||||
return self.libs[major], self.minors[major]
|
return self.libs[major], self.minors[major]
|
||||||
end
|
end
|
||||||
|
|
||||||
function LibStub:IterateLibraries() return pairs(self.libs) end
|
function LibStub:IterateLibraries() return pairs(self.libs) end
|
||||||
setmetatable(LibStub, { __call = LibStub.GetLibrary })
|
setmetatable(LibStub, { __call = LibStub.GetLibrary })
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,382 +1,382 @@
|
|||||||
local json = LibStub:NewLibrary("json", 1)
|
local json = LibStub:NewLibrary("json", 1)
|
||||||
if not json then return end
|
if not json then return end
|
||||||
|
|
||||||
--
|
--
|
||||||
-- json.lua
|
-- json.lua
|
||||||
--
|
--
|
||||||
-- Copyright (c) 2015 rxi
|
-- Copyright (c) 2015 rxi
|
||||||
--
|
--
|
||||||
-- This library is free software; you can redistribute it and/or modify it
|
-- This library is free software; you can redistribute it and/or modify it
|
||||||
-- under the terms of the MIT license. See LICENSE for details.
|
-- under the terms of the MIT license. See LICENSE for details.
|
||||||
--
|
--
|
||||||
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
-- Encode
|
-- Encode
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
local encode
|
local encode
|
||||||
|
|
||||||
local escape_char_map = {
|
local escape_char_map = {
|
||||||
[ "\\" ] = "\\\\",
|
[ "\\" ] = "\\\\",
|
||||||
[ "\"" ] = "\\\"",
|
[ "\"" ] = "\\\"",
|
||||||
[ "\b" ] = "\\b",
|
[ "\b" ] = "\\b",
|
||||||
[ "\f" ] = "\\f",
|
[ "\f" ] = "\\f",
|
||||||
[ "\n" ] = "\\n",
|
[ "\n" ] = "\\n",
|
||||||
[ "\r" ] = "\\r",
|
[ "\r" ] = "\\r",
|
||||||
[ "\t" ] = "\\t",
|
[ "\t" ] = "\\t",
|
||||||
}
|
}
|
||||||
|
|
||||||
local escape_char_map_inv = { [ "\\/" ] = "/" }
|
local escape_char_map_inv = { [ "\\/" ] = "/" }
|
||||||
for k, v in pairs(escape_char_map) do
|
for k, v in pairs(escape_char_map) do
|
||||||
escape_char_map_inv[v] = k
|
escape_char_map_inv[v] = k
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function escape_char(c)
|
local function escape_char(c)
|
||||||
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function encode_nil(val)
|
local function encode_nil(val)
|
||||||
return "null"
|
return "null"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function encode_table(val, stack)
|
local function encode_table(val, stack)
|
||||||
local res = {}
|
local res = {}
|
||||||
stack = stack or {}
|
stack = stack or {}
|
||||||
|
|
||||||
-- Circular reference?
|
-- Circular reference?
|
||||||
if stack[val] then error("circular reference") end
|
if stack[val] then error("circular reference") end
|
||||||
|
|
||||||
stack[val] = true
|
stack[val] = true
|
||||||
|
|
||||||
if val[1] ~= nil or next(val) == nil then
|
if val[1] ~= nil or next(val) == nil then
|
||||||
-- Treat as array -- check keys are valid and it is not sparse
|
-- Treat as array -- check keys are valid and it is not sparse
|
||||||
local n = 0
|
local n = 0
|
||||||
for k in pairs(val) do
|
for k in pairs(val) do
|
||||||
if type(k) ~= "number" then
|
if type(k) ~= "number" then
|
||||||
error("invalid table: mixed or invalid key types")
|
error("invalid table: mixed or invalid key types")
|
||||||
end
|
end
|
||||||
n = n + 1
|
n = n + 1
|
||||||
end
|
end
|
||||||
if n ~= #val then
|
if n ~= #val then
|
||||||
error("invalid table: sparse array")
|
error("invalid table: sparse array")
|
||||||
end
|
end
|
||||||
-- Encode
|
-- Encode
|
||||||
for i, v in ipairs(val) do
|
for i, v in ipairs(val) do
|
||||||
table.insert(res, encode(v, stack))
|
table.insert(res, encode(v, stack))
|
||||||
end
|
end
|
||||||
stack[val] = nil
|
stack[val] = nil
|
||||||
return "[" .. table.concat(res, ",") .. "]"
|
return "[" .. table.concat(res, ",") .. "]"
|
||||||
|
|
||||||
else
|
else
|
||||||
-- Treat as an object
|
-- Treat as an object
|
||||||
for k, v in pairs(val) do
|
for k, v in pairs(val) do
|
||||||
if type(k) ~= "string" then
|
if type(k) ~= "string" then
|
||||||
error("invalid table: mixed or invalid key types")
|
error("invalid table: mixed or invalid key types")
|
||||||
end
|
end
|
||||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||||
end
|
end
|
||||||
stack[val] = nil
|
stack[val] = nil
|
||||||
return "{" .. table.concat(res, ",") .. "}"
|
return "{" .. table.concat(res, ",") .. "}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function encode_string(val)
|
local function encode_string(val)
|
||||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function encode_number(val)
|
local function encode_number(val)
|
||||||
-- Check for NaN, -inf and inf
|
-- Check for NaN, -inf and inf
|
||||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||||
error("unexpected number value '" .. tostring(val) .. "'")
|
error("unexpected number value '" .. tostring(val) .. "'")
|
||||||
end
|
end
|
||||||
return string.format("%.14g", val)
|
return string.format("%.14g", val)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local type_func_map = {
|
local type_func_map = {
|
||||||
[ "nil" ] = encode_nil,
|
[ "nil" ] = encode_nil,
|
||||||
[ "table" ] = encode_table,
|
[ "table" ] = encode_table,
|
||||||
[ "string" ] = encode_string,
|
[ "string" ] = encode_string,
|
||||||
[ "number" ] = encode_number,
|
[ "number" ] = encode_number,
|
||||||
[ "boolean" ] = tostring,
|
[ "boolean" ] = tostring,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
encode = function(val, stack)
|
encode = function(val, stack)
|
||||||
local t = type(val)
|
local t = type(val)
|
||||||
local f = type_func_map[t]
|
local f = type_func_map[t]
|
||||||
if f then
|
if f then
|
||||||
return f(val, stack)
|
return f(val, stack)
|
||||||
end
|
end
|
||||||
error("unexpected type '" .. t .. "'")
|
error("unexpected type '" .. t .. "'")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function json.encode(val)
|
function json.encode(val)
|
||||||
return ( encode(val) )
|
return ( encode(val) )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
-- Decode
|
-- Decode
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
local parse
|
local parse
|
||||||
|
|
||||||
local function create_set(...)
|
local function create_set(...)
|
||||||
local res = {}
|
local res = {}
|
||||||
for i = 1, select("#", ...) do
|
for i = 1, select("#", ...) do
|
||||||
res[ select(i, ...) ] = true
|
res[ select(i, ...) ] = true
|
||||||
end
|
end
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||||
local literals = create_set("true", "false", "null")
|
local literals = create_set("true", "false", "null")
|
||||||
|
|
||||||
local literal_map = {
|
local literal_map = {
|
||||||
[ "true" ] = true,
|
[ "true" ] = true,
|
||||||
[ "false" ] = false,
|
[ "false" ] = false,
|
||||||
[ "null" ] = nil,
|
[ "null" ] = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
local function next_char(str, idx, set, negate)
|
local function next_char(str, idx, set, negate)
|
||||||
for i = idx, #str do
|
for i = idx, #str do
|
||||||
if set[str:sub(i, i)] ~= negate then
|
if set[str:sub(i, i)] ~= negate then
|
||||||
return i
|
return i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return #str + 1
|
return #str + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function decode_error(str, idx, msg)
|
local function decode_error(str, idx, msg)
|
||||||
local line_count = 1
|
local line_count = 1
|
||||||
local col_count = 1
|
local col_count = 1
|
||||||
for i = 1, idx - 1 do
|
for i = 1, idx - 1 do
|
||||||
col_count = col_count + 1
|
col_count = col_count + 1
|
||||||
if str:sub(i, i) == "\n" then
|
if str:sub(i, i) == "\n" then
|
||||||
line_count = line_count + 1
|
line_count = line_count + 1
|
||||||
col_count = 1
|
col_count = 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function codepoint_to_utf8(n)
|
local function codepoint_to_utf8(n)
|
||||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||||
local f = math.floor
|
local f = math.floor
|
||||||
if n <= 0x7f then
|
if n <= 0x7f then
|
||||||
return string.char(n)
|
return string.char(n)
|
||||||
elseif n <= 0x7ff then
|
elseif n <= 0x7ff then
|
||||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||||
elseif n <= 0xffff then
|
elseif n <= 0xffff then
|
||||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||||
elseif n <= 0x10ffff then
|
elseif n <= 0x10ffff then
|
||||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||||
end
|
end
|
||||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function parse_unicode_escape(s)
|
local function parse_unicode_escape(s)
|
||||||
local n1 = tonumber( s:sub(3, 6), 16 )
|
local n1 = tonumber( s:sub(3, 6), 16 )
|
||||||
local n2 = tonumber( s:sub(9, 12), 16 )
|
local n2 = tonumber( s:sub(9, 12), 16 )
|
||||||
-- Surrogate pair?
|
-- Surrogate pair?
|
||||||
if n2 then
|
if n2 then
|
||||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||||
else
|
else
|
||||||
return codepoint_to_utf8(n1)
|
return codepoint_to_utf8(n1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function parse_string(str, i)
|
local function parse_string(str, i)
|
||||||
local has_unicode_escape = false
|
local has_unicode_escape = false
|
||||||
local has_surrogate_escape = false
|
local has_surrogate_escape = false
|
||||||
local has_escape = false
|
local has_escape = false
|
||||||
local last
|
local last
|
||||||
for j = i + 1, #str do
|
for j = i + 1, #str do
|
||||||
local x = str:byte(j)
|
local x = str:byte(j)
|
||||||
|
|
||||||
if x < 32 then
|
if x < 32 then
|
||||||
decode_error(str, j, "control character in string")
|
decode_error(str, j, "control character in string")
|
||||||
end
|
end
|
||||||
|
|
||||||
if last == 92 then -- "\\" (escape char)
|
if last == 92 then -- "\\" (escape char)
|
||||||
if x == 117 then -- "u" (unicode escape sequence)
|
if x == 117 then -- "u" (unicode escape sequence)
|
||||||
local hex = str:sub(j + 1, j + 5)
|
local hex = str:sub(j + 1, j + 5)
|
||||||
if not hex:find("%x%x%x%x") then
|
if not hex:find("%x%x%x%x") then
|
||||||
decode_error(str, j, "invalid unicode escape in string")
|
decode_error(str, j, "invalid unicode escape in string")
|
||||||
end
|
end
|
||||||
if hex:find("^[dD][89aAbB]") then
|
if hex:find("^[dD][89aAbB]") then
|
||||||
has_surrogate_escape = true
|
has_surrogate_escape = true
|
||||||
else
|
else
|
||||||
has_unicode_escape = true
|
has_unicode_escape = true
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local c = string.char(x)
|
local c = string.char(x)
|
||||||
if not escape_chars[c] then
|
if not escape_chars[c] then
|
||||||
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
|
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
|
||||||
end
|
end
|
||||||
has_escape = true
|
has_escape = true
|
||||||
end
|
end
|
||||||
last = nil
|
last = nil
|
||||||
|
|
||||||
elseif x == 34 then -- '"' (end of string)
|
elseif x == 34 then -- '"' (end of string)
|
||||||
local s = str:sub(i + 1, j - 1)
|
local s = str:sub(i + 1, j - 1)
|
||||||
if has_surrogate_escape then
|
if has_surrogate_escape then
|
||||||
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
|
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
|
||||||
end
|
end
|
||||||
if has_unicode_escape then
|
if has_unicode_escape then
|
||||||
s = s:gsub("\\u....", parse_unicode_escape)
|
s = s:gsub("\\u....", parse_unicode_escape)
|
||||||
end
|
end
|
||||||
if has_escape then
|
if has_escape then
|
||||||
s = s:gsub("\\.", escape_char_map_inv)
|
s = s:gsub("\\.", escape_char_map_inv)
|
||||||
end
|
end
|
||||||
return s, j + 1
|
return s, j + 1
|
||||||
|
|
||||||
else
|
else
|
||||||
last = x
|
last = x
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
decode_error(str, i, "expected closing quote for string")
|
decode_error(str, i, "expected closing quote for string")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function parse_number(str, i)
|
local function parse_number(str, i)
|
||||||
local x = next_char(str, i, delim_chars)
|
local x = next_char(str, i, delim_chars)
|
||||||
local s = str:sub(i, x - 1)
|
local s = str:sub(i, x - 1)
|
||||||
local n = tonumber(s)
|
local n = tonumber(s)
|
||||||
if not n then
|
if not n then
|
||||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||||
end
|
end
|
||||||
return n, x
|
return n, x
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function parse_literal(str, i)
|
local function parse_literal(str, i)
|
||||||
local x = next_char(str, i, delim_chars)
|
local x = next_char(str, i, delim_chars)
|
||||||
local word = str:sub(i, x - 1)
|
local word = str:sub(i, x - 1)
|
||||||
if not literals[word] then
|
if not literals[word] then
|
||||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||||
end
|
end
|
||||||
return literal_map[word], x
|
return literal_map[word], x
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function parse_array(str, i)
|
local function parse_array(str, i)
|
||||||
local res = {}
|
local res = {}
|
||||||
local n = 1
|
local n = 1
|
||||||
i = i + 1
|
i = i + 1
|
||||||
while 1 do
|
while 1 do
|
||||||
local x
|
local x
|
||||||
i = next_char(str, i, space_chars, true)
|
i = next_char(str, i, space_chars, true)
|
||||||
-- Empty / end of array?
|
-- Empty / end of array?
|
||||||
if str:sub(i, i) == "]" then
|
if str:sub(i, i) == "]" then
|
||||||
i = i + 1
|
i = i + 1
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
-- Read token
|
-- Read token
|
||||||
x, i = parse(str, i)
|
x, i = parse(str, i)
|
||||||
res[n] = x
|
res[n] = x
|
||||||
n = n + 1
|
n = n + 1
|
||||||
-- Next token
|
-- Next token
|
||||||
i = next_char(str, i, space_chars, true)
|
i = next_char(str, i, space_chars, true)
|
||||||
local chr = str:sub(i, i)
|
local chr = str:sub(i, i)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
if chr == "]" then break end
|
if chr == "]" then break end
|
||||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||||
end
|
end
|
||||||
return res, i
|
return res, i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function parse_object(str, i)
|
local function parse_object(str, i)
|
||||||
local res = {}
|
local res = {}
|
||||||
i = i + 1
|
i = i + 1
|
||||||
while 1 do
|
while 1 do
|
||||||
local key, val
|
local key, val
|
||||||
i = next_char(str, i, space_chars, true)
|
i = next_char(str, i, space_chars, true)
|
||||||
-- Empty / end of object?
|
-- Empty / end of object?
|
||||||
if str:sub(i, i) == "}" then
|
if str:sub(i, i) == "}" then
|
||||||
i = i + 1
|
i = i + 1
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
-- Read key
|
-- Read key
|
||||||
if str:sub(i, i) ~= '"' then
|
if str:sub(i, i) ~= '"' then
|
||||||
decode_error(str, i, "expected string for key")
|
decode_error(str, i, "expected string for key")
|
||||||
end
|
end
|
||||||
key, i = parse(str, i)
|
key, i = parse(str, i)
|
||||||
-- Read ':' delimiter
|
-- Read ':' delimiter
|
||||||
i = next_char(str, i, space_chars, true)
|
i = next_char(str, i, space_chars, true)
|
||||||
if str:sub(i, i) ~= ":" then
|
if str:sub(i, i) ~= ":" then
|
||||||
decode_error(str, i, "expected ':' after key")
|
decode_error(str, i, "expected ':' after key")
|
||||||
end
|
end
|
||||||
i = next_char(str, i + 1, space_chars, true)
|
i = next_char(str, i + 1, space_chars, true)
|
||||||
-- Read value
|
-- Read value
|
||||||
val, i = parse(str, i)
|
val, i = parse(str, i)
|
||||||
-- Set
|
-- Set
|
||||||
res[key] = val
|
res[key] = val
|
||||||
-- Next token
|
-- Next token
|
||||||
i = next_char(str, i, space_chars, true)
|
i = next_char(str, i, space_chars, true)
|
||||||
local chr = str:sub(i, i)
|
local chr = str:sub(i, i)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
if chr == "}" then break end
|
if chr == "}" then break end
|
||||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||||
end
|
end
|
||||||
return res, i
|
return res, i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local char_func_map = {
|
local char_func_map = {
|
||||||
[ '"' ] = parse_string,
|
[ '"' ] = parse_string,
|
||||||
[ "0" ] = parse_number,
|
[ "0" ] = parse_number,
|
||||||
[ "1" ] = parse_number,
|
[ "1" ] = parse_number,
|
||||||
[ "2" ] = parse_number,
|
[ "2" ] = parse_number,
|
||||||
[ "3" ] = parse_number,
|
[ "3" ] = parse_number,
|
||||||
[ "4" ] = parse_number,
|
[ "4" ] = parse_number,
|
||||||
[ "5" ] = parse_number,
|
[ "5" ] = parse_number,
|
||||||
[ "6" ] = parse_number,
|
[ "6" ] = parse_number,
|
||||||
[ "7" ] = parse_number,
|
[ "7" ] = parse_number,
|
||||||
[ "8" ] = parse_number,
|
[ "8" ] = parse_number,
|
||||||
[ "9" ] = parse_number,
|
[ "9" ] = parse_number,
|
||||||
[ "-" ] = parse_number,
|
[ "-" ] = parse_number,
|
||||||
[ "t" ] = parse_literal,
|
[ "t" ] = parse_literal,
|
||||||
[ "f" ] = parse_literal,
|
[ "f" ] = parse_literal,
|
||||||
[ "n" ] = parse_literal,
|
[ "n" ] = parse_literal,
|
||||||
[ "[" ] = parse_array,
|
[ "[" ] = parse_array,
|
||||||
[ "{" ] = parse_object,
|
[ "{" ] = parse_object,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
parse = function(str, idx)
|
parse = function(str, idx)
|
||||||
local chr = str:sub(idx, idx)
|
local chr = str:sub(idx, idx)
|
||||||
local f = char_func_map[chr]
|
local f = char_func_map[chr]
|
||||||
if f then
|
if f then
|
||||||
return f(str, idx)
|
return f(str, idx)
|
||||||
end
|
end
|
||||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function json.decode(str)
|
function json.decode(str)
|
||||||
if type(str) ~= "string" then
|
if type(str) ~= "string" then
|
||||||
error("expected argument of type string, got " .. type(str))
|
error("expected argument of type string, got " .. type(str))
|
||||||
end
|
end
|
||||||
return ( parse(str, next_char(str, 1, space_chars, true)) )
|
return ( parse(str, next_char(str, 1, space_chars, true)) )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return json
|
return json
|
||||||
@ -1,9 +1,9 @@
|
|||||||
<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
||||||
<Script file="Libs\LibStub\LibStub.lua"/>
|
<Script file="Libs\LibStub\LibStub.lua"/>
|
||||||
<Include file="Libs\AceAddon-3.0\AceAddon-3.0.xml"/>
|
<Include file="Libs\AceAddon-3.0\AceAddon-3.0.xml"/>
|
||||||
<Include file="Libs\AceConsole-3.0\AceConsole-3.0.xml"/>
|
<Include file="Libs\AceConsole-3.0\AceConsole-3.0.xml"/>
|
||||||
<Include file="Libs\AceEvent-3.0\AceEvent-3.0.xml"/>
|
<Include file="Libs\AceEvent-3.0\AceEvent-3.0.xml"/>
|
||||||
<Include file="Libs\AceTimer-3.0\AceTimer-3.0.xml"/>
|
<Include file="Libs\AceTimer-3.0\AceTimer-3.0.xml"/>
|
||||||
<Include file="Libs\AceComm-3.0\AceComm-3.0.xml"/>
|
<Include file="Libs\AceComm-3.0\AceComm-3.0.xml"/>
|
||||||
<Include file="Libs\json.lua"/>
|
<Include file="Libs\json.lua"/>
|
||||||
</Ui>
|
</Ui>
|
||||||
@ -75,7 +75,7 @@ namespace Artemis.Modules.Games.WoW
|
|||||||
ParseAuras(json, dataModel, false);
|
ParseAuras(json, dataModel, false);
|
||||||
break;
|
break;
|
||||||
case "spellCast":
|
case "spellCast":
|
||||||
ParseSpellCast(json, dataModel);
|
ParseSpellCast(json, dataModel, false);
|
||||||
break;
|
break;
|
||||||
case "instantSpellCast":
|
case "instantSpellCast":
|
||||||
ParseInstantSpellCast(json, dataModel);
|
ParseInstantSpellCast(json, dataModel);
|
||||||
@ -86,6 +86,12 @@ namespace Artemis.Modules.Games.WoW
|
|||||||
case "spellCastInterrupted":
|
case "spellCastInterrupted":
|
||||||
ParseSpellCastInterrupted(data, dataModel);
|
ParseSpellCastInterrupted(data, dataModel);
|
||||||
break;
|
break;
|
||||||
|
case "spellChannel":
|
||||||
|
ParseSpellCast(json, dataModel, true);
|
||||||
|
break;
|
||||||
|
case "spellChannelInterrupted":
|
||||||
|
ParseSpellCastInterrupted(data, dataModel);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Logger.Warn("The WoW addon sent an unknown command: {0}", command);
|
Logger.Warn("The WoW addon sent an unknown command: {0}", command);
|
||||||
break;
|
break;
|
||||||
@ -118,12 +124,12 @@ namespace Artemis.Modules.Games.WoW
|
|||||||
dataModel.Player.ApplyAuraJson(json, buffs);
|
dataModel.Player.ApplyAuraJson(json, buffs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParseSpellCast(JToken json, WoWDataModel dataModel)
|
private void ParseSpellCast(JToken json, WoWDataModel dataModel, bool isChannel)
|
||||||
{
|
{
|
||||||
if (json["uid"].Value<string>() == "player")
|
if (json["uid"].Value<string>() == "player")
|
||||||
dataModel.Player.CastBar.ApplyJson(json);
|
dataModel.Player.CastBar.ApplyJson(json, isChannel);
|
||||||
else if (json["uid"].Value<string>() == "target")
|
else if (json["uid"].Value<string>() == "target")
|
||||||
dataModel.Target.CastBar.ApplyJson(json);
|
dataModel.Target.CastBar.ApplyJson(json, isChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParseInstantSpellCast(JToken json, WoWDataModel dataModel)
|
private void ParseInstantSpellCast(JToken json, WoWDataModel dataModel)
|
||||||
|
|||||||
@ -1,125 +1,127 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ninject.Extensions.Logging;
|
using Ninject.Extensions.Logging;
|
||||||
using PcapDotNet.Core;
|
using PcapDotNet.Core;
|
||||||
using PcapDotNet.Packets;
|
using PcapDotNet.Packets;
|
||||||
|
|
||||||
namespace Artemis.Modules.Games.WoW
|
namespace Artemis.Modules.Games.WoW
|
||||||
{
|
{
|
||||||
public class WowPacketScanner
|
public class WowPacketScanner
|
||||||
{
|
{
|
||||||
private const string MsgStart = "\u0001";
|
private const string MsgStart = "\u0001";
|
||||||
private const string MsgNext = "\u0002";
|
private const string MsgNext = "\u0002";
|
||||||
private const string MsgLast = "\u0003";
|
private const string MsgLast = "\u0003";
|
||||||
private PacketCommunicator _communicator;
|
private PacketCommunicator _communicator;
|
||||||
private string _dataParts;
|
private string _dataParts;
|
||||||
|
|
||||||
public WowPacketScanner(ILogger logger)
|
public WowPacketScanner(ILogger logger)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILogger Logger { get; }
|
public ILogger Logger { get; }
|
||||||
|
|
||||||
public event EventHandler<WowDataReceivedEventArgs> RaiseDataReceived;
|
public event EventHandler<WowDataReceivedEventArgs> RaiseDataReceived;
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
// Start scanning WoW packets
|
// Start scanning WoW packets
|
||||||
// Retrieve the device list from the local machine
|
// Retrieve the device list from the local machine
|
||||||
IList<LivePacketDevice> allDevices = LivePacketDevice.AllLocalMachine;
|
IList<LivePacketDevice> allDevices = LivePacketDevice.AllLocalMachine;
|
||||||
|
|
||||||
if (allDevices.Count == 0)
|
if (allDevices.Count == 0)
|
||||||
{
|
{
|
||||||
Logger.Warn("No interfaces found! Can't scan WoW packets.");
|
Logger.Warn("No interfaces found! Can't scan WoW packets.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the selected adapter
|
// Take the selected adapter
|
||||||
PacketDevice selectedDevice = allDevices.First();
|
PacketDevice selectedDevice = allDevices.First();
|
||||||
|
|
||||||
// Open the device
|
// Open the device
|
||||||
_communicator = selectedDevice.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 40);
|
_communicator = selectedDevice.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 40);
|
||||||
Logger.Debug("Listening on " + selectedDevice.Description + " for WoW packets");
|
Logger.Debug("Listening on " + selectedDevice.Description + " for WoW packets");
|
||||||
|
|
||||||
// Compile the filter
|
// Compile the filter
|
||||||
using (var filter = _communicator.CreateFilter("tcp"))
|
using (var filter = _communicator.CreateFilter("tcp"))
|
||||||
{
|
{
|
||||||
// Set the filter
|
// Set the filter
|
||||||
_communicator.SetFilter(filter);
|
_communicator.SetFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(() => ReceivePackets());
|
Task.Run(() => ReceivePackets());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
_communicator?.Break();
|
_communicator?.Break();
|
||||||
_communicator?.Dispose();
|
_communicator?.Dispose();
|
||||||
_communicator = null;
|
_communicator = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReceivePackets()
|
private void ReceivePackets()
|
||||||
{
|
{
|
||||||
// start the capture
|
// start the capture
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_communicator.ReceivePackets(0, PacketHandler);
|
_communicator.ReceivePackets(0, PacketHandler);
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
{
|
{
|
||||||
// ignored, happens on shutdown
|
// ignored, happens on shutdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback function invoked by Pcap.Net for every incoming packet
|
// Callback function invoked by Pcap.Net for every incoming packet
|
||||||
private void PacketHandler(Packet packet)
|
private void PacketHandler(Packet packet)
|
||||||
{
|
{
|
||||||
var str = Encoding.Default.GetString(packet.Buffer);
|
var str = Encoding.Default.GetString(packet.Buffer);
|
||||||
if (!str.ToLower().Contains("artemis"))
|
if (!str.ToLower().Contains("artemis"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Split the string at the prefix
|
// Split the string at the prefix
|
||||||
var parts = str.Split(new[] {"(artemis)"}, StringSplitOptions.None);
|
var parts = str.Split(new[] {"(artemis)"}, StringSplitOptions.None);
|
||||||
var msg = parts[1];
|
if (parts.Length < 2)
|
||||||
// Start escape char
|
return;
|
||||||
if (msg.StartsWith(MsgStart))
|
var msg = parts[1];
|
||||||
_dataParts = msg.Substring(1);
|
// Start escape char
|
||||||
else if (msg.StartsWith(MsgNext))
|
if (msg.StartsWith(MsgStart))
|
||||||
_dataParts = _dataParts + msg.Substring(1);
|
_dataParts = msg.Substring(1);
|
||||||
else if (msg.StartsWith(MsgLast))
|
else if (msg.StartsWith(MsgNext))
|
||||||
{
|
_dataParts = _dataParts + msg.Substring(1);
|
||||||
_dataParts = _dataParts + msg.Substring(1);
|
else if (msg.StartsWith(MsgLast))
|
||||||
var dataParts = _dataParts.Split('|');
|
{
|
||||||
// Data is wrapped in artemis(), take this off
|
_dataParts = _dataParts + msg.Substring(1);
|
||||||
OnRaiseDataReceived(dataParts[0].Substring(8), dataParts[1].Substring(0, dataParts[1].Length - 1));
|
var dataParts = _dataParts.Split('|');
|
||||||
}
|
// Data is wrapped in artemis(), take this off
|
||||||
else
|
OnRaiseDataReceived(dataParts[0].Substring(8), dataParts[1].Substring(0, dataParts[1].Length - 1));
|
||||||
{
|
}
|
||||||
var dataParts = msg.Split('|');
|
else
|
||||||
// Data is wrapped in artemis(), take this off
|
{
|
||||||
OnRaiseDataReceived(dataParts[0].Substring(8), dataParts[1].Substring(0, dataParts[1].Length - 1));
|
var dataParts = msg.Split('|');
|
||||||
}
|
// Data is wrapped in artemis(), take this off
|
||||||
}
|
OnRaiseDataReceived(dataParts[0].Substring(8), dataParts[1].Substring(0, dataParts[1].Length - 1));
|
||||||
|
}
|
||||||
private void OnRaiseDataReceived(string command, string data)
|
}
|
||||||
{
|
|
||||||
RaiseDataReceived?.Invoke(this, new WowDataReceivedEventArgs(command, data));
|
private void OnRaiseDataReceived(string command, string data)
|
||||||
}
|
{
|
||||||
|
RaiseDataReceived?.Invoke(this, new WowDataReceivedEventArgs(command, data));
|
||||||
public class WowDataReceivedEventArgs : EventArgs
|
}
|
||||||
{
|
|
||||||
public WowDataReceivedEventArgs(string command, string data)
|
public class WowDataReceivedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
Command = command;
|
public WowDataReceivedEventArgs(string command, string data)
|
||||||
Data = data;
|
{
|
||||||
}
|
Command = command;
|
||||||
|
Data = data;
|
||||||
public string Command { get; }
|
}
|
||||||
public string Data { get; set; }
|
|
||||||
}
|
public string Command { get; }
|
||||||
}
|
public string Data { get; set; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user