1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Merge branch 'development' of https://github.com/SpoinkyNL/Artemis into development

This commit is contained in:
SpoinkyNL 2017-11-12 11:23:00 +01:00
commit 1812b2a019
26 changed files with 3147 additions and 3024 deletions

View File

@ -152,8 +152,8 @@
<HintPath>..\packages\Colore.5.1.0\lib\net35\Corale.Colore.dll</HintPath> <HintPath>..\packages\Colore.5.1.0\lib\net35\Corale.Colore.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="CSCore, Version=1.2.1.1, Culture=neutral, PublicKeyToken=5a08f2b6f4415dea, processorArchitecture=MSIL"> <Reference Include="CSCore, Version=1.2.1.2, Culture=neutral, PublicKeyToken=5a08f2b6f4415dea, processorArchitecture=MSIL">
<HintPath>..\packages\CSCore.1.2.1.1\lib\net35-client\CSCore.dll</HintPath> <HintPath>..\packages\CSCore.1.2.1.2\lib\net35-client\CSCore.dll</HintPath>
</Reference> </Reference>
<Reference Include="CUE.NET, Version=1.1.3.1, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="CUE.NET, Version=1.1.3.1, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\CUE.NET.1.1.3.1\lib\net45\CUE.NET.dll</HintPath> <HintPath>..\packages\CUE.NET.1.1.3.1\lib\net45\CUE.NET.dll</HintPath>
@ -170,8 +170,8 @@
<HintPath>..\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.PatchApi.dll</HintPath> <HintPath>..\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.PatchApi.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="DynamicExpresso.Core, Version=1.3.3.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="DynamicExpresso.Core, Version=1.3.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DynamicExpresso.Core.1.3.3.6\lib\net40\DynamicExpresso.Core.dll</HintPath> <HintPath>..\packages\DynamicExpresso.Core.1.3.4.7\lib\net40\DynamicExpresso.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="Gma.System.MouseKeyHook, Version=5.4.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Gma.System.MouseKeyHook, Version=5.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MouseKeyHook.5.4.0\lib\net40\Gma.System.MouseKeyHook.dll</HintPath> <HintPath>..\packages\MouseKeyHook.5.4.0\lib\net40\Gma.System.MouseKeyHook.dll</HintPath>
@ -267,7 +267,7 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="SpotifyAPI, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="SpotifyAPI, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SpotifyAPI-NET.2.16.0\lib\SpotifyAPI.dll</HintPath> <HintPath>..\packages\SpotifyAPI-NET.2.16.1\lib\SpotifyAPI.dll</HintPath>
</Reference> </Reference>
<Reference Include="Squirrel, Version=1.4.3.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Squirrel, Version=1.4.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\squirrel.windows.1.4.4\lib\Net45\Squirrel.dll</HintPath> <HintPath>..\packages\squirrel.windows.1.4.4\lib\Net45\Squirrel.dll</HintPath>
@ -799,16 +799,17 @@
<Compile Include="Properties\AssemblyInfo.cs"> <Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<None Include="Modules\Games\WoW\Resources\Addon\Artemis.toc" /> <None Include="Modules\Games\WoW\Resources\Addon source\Artemis.toc" />
<None Include="Modules\Games\WoW\Resources\Addon\Core.lua" /> <None Include="Modules\Games\WoW\Resources\Addon source\Core.lua" />
<None Include="Modules\Games\WoW\Resources\Addon\Libs\AceAddon-3.0\AceAddon-3.0.lua" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceAddon-3.0\AceAddon-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon\Libs\AceComm-3.0\AceComm-3.0.lua" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceComm-3.0\AceComm-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon\Libs\AceComm-3.0\ChatThrottleLib.lua" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceComm-3.0\ChatThrottleLib.lua" />
<None Include="Modules\Games\WoW\Resources\Addon\Libs\AceConsole-3.0\AceConsole-3.0.lua" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceConsole-3.0\AceConsole-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon\Libs\AceEvent-3.0\AceEvent-3.0.lua" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceEvent-3.0\AceEvent-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon\Libs\AceTimer-3.0\AceTimer-3.0.lua" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceTimer-3.0\AceTimer-3.0.lua" />
<None Include="Modules\Games\WoW\Resources\Addon\Libs\json.lua" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\json.lua" />
<None Include="Modules\Games\WoW\Resources\Addon\Libs\LibStub\LibStub.lua" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\LibStub\LibStub.lua" />
<None Include="Modules\Games\WoW\Resources\wow-addon.zip" />
<None Include="NLog.xsd"> <None Include="NLog.xsd">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</None> </None>
@ -1087,12 +1088,12 @@
<None Include="Modules\Games\EurotruckSimulator2\Resources\Win64\ets2-telemetry-server.dll" /> <None Include="Modules\Games\EurotruckSimulator2\Resources\Win64\ets2-telemetry-server.dll" />
<None Include="Resources\audio.png" /> <None Include="Resources\audio.png" />
<None Include="Resources\ambilight.png" /> <None Include="Resources\ambilight.png" />
<Resource Include="Modules\Games\WoW\Resources\Addon\embeds.xml" /> <None Include="Modules\Games\WoW\Resources\Addon source\embeds.xml" />
<Resource Include="Modules\Games\WoW\Resources\Addon\Libs\AceAddon-3.0\AceAddon-3.0.xml" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceAddon-3.0\AceAddon-3.0.xml" />
<Resource Include="Modules\Games\WoW\Resources\Addon\Libs\AceComm-3.0\AceComm-3.0.xml" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceComm-3.0\AceComm-3.0.xml" />
<Resource Include="Modules\Games\WoW\Resources\Addon\Libs\AceConsole-3.0\AceConsole-3.0.xml" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceConsole-3.0\AceConsole-3.0.xml" />
<Resource Include="Modules\Games\WoW\Resources\Addon\Libs\AceEvent-3.0\AceEvent-3.0.xml" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceEvent-3.0\AceEvent-3.0.xml" />
<Resource Include="Modules\Games\WoW\Resources\Addon\Libs\AceTimer-3.0\AceTimer-3.0.xml" /> <None Include="Modules\Games\WoW\Resources\Addon source\Libs\AceTimer-3.0\AceTimer-3.0.xml" />
<Content Include="Resources\CounterStrike\csgoGamestateConfiguration.txt" /> <Content Include="Resources\CounterStrike\csgoGamestateConfiguration.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -41,7 +41,7 @@
<!-- Game directory --> <!-- Game directory -->
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,1,0"> <StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,1,0">
<Label FontSize="20" HorizontalAlignment="Left" Content="Overwatch Directory" /> <Label FontSize="20" HorizontalAlignment="Left" Content="Overwatch directory" />
<Grid> <Grid>
<TextBox x:Name="GameDirectory" Height="23" TextWrapping="Wrap" Margin="0,0,30,0" Text="{Binding Path=Settings.GameDirectory, Mode=TwoWay}" cal:Message.Attach="[Event LostFocus] = [Action PlaceDll]" /> <TextBox x:Name="GameDirectory" Height="23" TextWrapping="Wrap" Margin="0,0,30,0" Text="{Binding Path=Settings.GameDirectory, Mode=TwoWay}" cal:Message.Attach="[Event LostFocus] = [Action PlaceDll]" />
<Button x:Name="BrowseDirectory" Content="..." RenderTransformOrigin="-0.039,-0.944" HorizontalAlignment="Right" Width="25" Style="{DynamicResource SquareButtonStyle}" Height="26" /> <Button x:Name="BrowseDirectory" Content="..." RenderTransformOrigin="-0.039,-0.944" HorizontalAlignment="Right" Width="25" Style="{DynamicResource SquareButtonStyle}" Height="26" />

View File

@ -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

View File

@ -1,398 +1,398 @@
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")
-- Hook onto logout because it seems PLAYER_LOGOUT fires on reload as well -- Hook onto logout because it seems PLAYER_LOGOUT fires on reload as well
local _Logout = Logout local _Logout = Logout
local debugging = false local debugging = false
local lastLine = {} local lastLine = {}
local channeling = {} local channeling = {}
local unitUpdates = {} local unitUpdates = {}
local lastTransmitMessage local lastTransmitMessage
local lastTransmitTime local lastTransmitTime
local lastBuffs = ""; local lastBuffs = "";
local lastDebuffs = ""; local lastDebuffs = "";
local prefixCounts = {} local prefixCounts = {}
channeling["player"] = false channeling["player"] = false
channeling["target"] = false channeling["target"] = false
function Artemis:OnEnable() function Artemis:OnEnable()
-- Register all the various events that Artemis will want to know about -- Register all the various events that Artemis will want to know about
Artemis:RegisterEvent("PLAYER_ENTERING_WORLD") Artemis:RegisterEvent("PLAYER_ENTERING_WORLD")
Artemis:RegisterEvent("PLAYER_LEVEL_UP") Artemis:RegisterEvent("PLAYER_LEVEL_UP")
Artemis:RegisterEvent("PLAYER_FLAGS_CHANGED") Artemis:RegisterEvent("PLAYER_FLAGS_CHANGED")
Artemis:RegisterEvent("ACHIEVEMENT_EARNED") Artemis:RegisterEvent("ACHIEVEMENT_EARNED")
Artemis:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED") Artemis:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
Artemis:RegisterEvent("UNIT_TARGET") Artemis:RegisterEvent("UNIT_TARGET")
Artemis:RegisterEvent("UNIT_HEALTH") Artemis:RegisterEvent("UNIT_HEALTH")
Artemis:RegisterEvent("UNIT_POWER") Artemis:RegisterEvent("UNIT_POWER")
Artemis:RegisterEvent("UNIT_AURA") Artemis:RegisterEvent("UNIT_AURA")
Artemis:RegisterEvent("UNIT_SPELLCAST_START") Artemis:RegisterEvent("UNIT_SPELLCAST_START")
Artemis:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED") Artemis:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
Artemis:RegisterEvent("UNIT_SPELLCAST_FAILED") Artemis:RegisterEvent("UNIT_SPELLCAST_FAILED")
Artemis:RegisterEvent("UNIT_SPELLCAST_DELAYED") Artemis:RegisterEvent("UNIT_SPELLCAST_DELAYED")
Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START") Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP") Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE") Artemis:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
Artemis:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") Artemis:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
Artemis:RegisterEvent("ZONE_CHANGED") Artemis:RegisterEvent("ZONE_CHANGED")
Artemis:RegisterEvent("ZONE_CHANGED_NEW_AREA") Artemis:RegisterEvent("ZONE_CHANGED_NEW_AREA")
-- Register the chat command /artemis <something> -- Register the chat command /artemis <something>
Artemis:RegisterChatCommand("artemis", "HandleChatCommand") Artemis:RegisterChatCommand("artemis", "HandleChatCommand")
-- Create a loop that'll periodically send character data in case of an Artemis restart/late start -- Create a loop that'll periodically send character data in case of an Artemis restart/late start
Artemis:ScheduleRepeatingTimer("PeriodicUpdate", 30) Artemis:ScheduleRepeatingTimer("PeriodicUpdate", 30)
end end
function Artemis:HandleChatCommand(input) function Artemis:HandleChatCommand(input)
if input == "debug" then if input == "debug" then
debugging = not (debugging) debugging = not (debugging)
if debugging then if debugging then
Artemis:Print("Debugging enabled.") Artemis:Print("Debugging enabled.")
else else
Artemis:Print("Debugging disabled.") Artemis:Print("Debugging disabled.")
end end
end end
if input == "rc" then if input == "rc" then
prefixCounts = {} prefixCounts = {}
Artemis:Print("Reset the send counters.") Artemis:Print("Reset the send counters.")
end end
if input == nill or input == "" or input == "help" then if input == nill or input == "" or input == "help" then
Artemis:Print("Available chat commands:") Artemis:Print("Available chat commands:")
Artemis:Printf("|cffb7b7b7/artemis debug|r: Toggle debugging") Artemis:Printf("|cffb7b7b7/artemis debug|r: Toggle debugging")
Artemis:Printf("|cffb7b7b7/artemis rc|r: Reset the debug counters") Artemis:Printf("|cffb7b7b7/artemis rc|r: Reset the debug counters")
end end
end end
function Artemis:Transmit(prefix, data, prio) function Artemis:Transmit(prefix, data, prio)
local msg = "artemis(".. prefix .. "|" .. json.encode(data) ..")" local msg = "artemis(".. prefix .. "|" .. json.encode(data) ..")"
-- If the message is the same as the previous, make sure it wasn't sent less than 250ms ago -- If the message is the same as the previous, make sure it wasn't sent less than 250ms ago
if msg == lastTransmitMessage then if msg == lastTransmitMessage then
if not (lastTransmitTime == nil) then if not (lastTransmitTime == nil) then
local diff = GetTime() - lastTransmitTime; local diff = GetTime() - lastTransmitTime;
if (diff < 0.25) then if (diff < 0.25) then
return return
end end
end end
end end
lastTransmitTime = GetTime() lastTransmitTime = GetTime()
if debugging == true then if debugging == true then
if prefixCounts[prefix] == nill then if prefixCounts[prefix] == nill then
prefixCounts[prefix] = 0 prefixCounts[prefix] = 0
end end
prefixCounts[prefix] = prefixCounts[prefix] + 1 prefixCounts[prefix] = prefixCounts[prefix] + 1
end end
if debugging == true then if debugging == true then
Artemis:Printf("Transmitting with prefix |cfffdff71" .. prefix .. "|r (" .. prefixCounts[prefix] .. ").") Artemis:Printf("Transmitting with prefix |cfffdff71" .. prefix .. "|r (" .. prefixCounts[prefix] .. ").")
Artemis:Print(msg) Artemis:Print(msg)
end end
Artemis:SendCommMessage("(artemis)", msg, "WHISPER", UnitName("player"), prio) Artemis:SendCommMessage("(artemis)", msg, "WHISPER", UnitName("player"), prio)
end end
function Artemis:TransmitUnitState(unit, ignoreThrottle) function Artemis:TransmitUnitState(unit, ignoreThrottle)
if not ignoreThrottle then if not ignoreThrottle then
if not (unitUpdates[unit] == nil) then if not (unitUpdates[unit] == nil) then
local diff = GetTime() - unitUpdates[unit] local diff = GetTime() - unitUpdates[unit]
if (diff < 0.5) then if (diff < 0.5) then
return return
end end
end end
end end
local table = { local table = {
h = UnitHealth(unit), h = UnitHealth(unit),
mh = UnitHealthMax(unit), mh = UnitHealthMax(unit),
p = UnitPower(unit), p = UnitPower(unit),
mp = UnitPowerMax(unit), mp = UnitPowerMax(unit),
t = UnitPowerType(unit) t = UnitPowerType(unit)
}; };
unitUpdates[unit] = GetTime() unitUpdates[unit] = GetTime()
Artemis:Transmit(unit .. "State", table) Artemis:Transmit(unit .. "State", table)
end end
function Artemis:GetUnitDetails(unit) function Artemis:GetUnitDetails(unit)
return { return {
n = UnitName(unit), n = UnitName(unit),
c = UnitClass(unit), c = UnitClass(unit),
l = UnitLevel(unit), l = UnitLevel(unit),
r = UnitRace(unit), r = UnitRace(unit),
g = UnitSex(unit), g = UnitSex(unit),
f = UnitFactionGroup(unit) f = UnitFactionGroup(unit)
}; };
end end
function Artemis:GetPlayerDetails() function Artemis:GetPlayerDetails()
local details = Artemis:GetUnitDetails("player") local details = Artemis:GetUnitDetails("player")
local id, name, _, _, role = GetSpecializationInfo(GetSpecialization()) local id, name, _, _, role = GetSpecializationInfo(GetSpecialization())
details.realm = GetRealmName() details.realm = GetRealmName()
details.achievementPoints = GetTotalAchievementPoints(false) details.achievementPoints = GetTotalAchievementPoints(false)
details.s = {id = id, n = name, r = role} details.s = {id = id, n = name, r = role}
return details return details
end end
function Artemis:GetUnitAuras(unit, filter) function Artemis:GetUnitAuras(unit, filter)
local auras = {}; local auras = {};
for index = 1, 40 do for index = 1, 40 do
local name, _, _, count, _, duration, expires, caster, _, _, spellID = UnitAura(unit, index, filter); local name, _, _, count, _, duration, expires, caster, _, _, spellID = UnitAura(unit, index, filter);
if not (name == nil) then if not (name == nil) then
local buffTable = {n = name, id = spellID} local buffTable = {n = name, id = spellID}
-- Leave these values out if they are 0 to save some space -- Leave these values out if they are 0 to save some space
if count > 0 then if count > 0 then
buffTable["c"] = count buffTable["c"] = count
end end
if duration > 0 then if duration > 0 then
buffTable["d"] = duration buffTable["d"] = duration
end end
if expires > 0 then if expires > 0 then
buffTable["e"] = expires buffTable["e"] = expires
end end
table.insert(auras, buffTable) table.insert(auras, buffTable)
end end
end end
return auras return auras
end end
function Artemis:PeriodicUpdate() function Artemis:PeriodicUpdate()
-- Don't do this in combat, enough data going out at that time already -- Don't do this in combat, enough data going out at that time already
if InCombatLockdown() then if InCombatLockdown() then
return return
end end
Artemis:Transmit("player", Artemis:GetPlayerDetails()) Artemis:Transmit("player", Artemis:GetPlayerDetails())
Artemis:TransmitUnitState("player", true); Artemis:TransmitUnitState("player", true);
end end
function Artemis:PLAYER_ENTERING_WORLD(...) function Artemis:PLAYER_ENTERING_WORLD(...)
Artemis:Transmit("player", Artemis:GetPlayerDetails()) Artemis:Transmit("player", Artemis:GetPlayerDetails())
Artemis:TransmitUnitState("player", true); Artemis:TransmitUnitState("player", true);
end end
function Logout() function Logout()
Artemis:Transmit("gameState", "loggedOut") Artemis:Transmit("gameState", "loggedOut")
return _Logout() return _Logout()
end end
function Artemis:PLAYER_LEVEL_UP(...) function Artemis:PLAYER_LEVEL_UP(...)
Artemis:Transmit("player", Artemis:GetPlayerDetails()) Artemis:Transmit("player", Artemis:GetPlayerDetails())
end end
function Artemis:PLAYER_FLAGS_CHANGED(...) function Artemis:PLAYER_FLAGS_CHANGED(...)
local _, unitID = ... local _, unitID = ...
if unitID == "player" then if unitID == "player" then
-- AFK overwrites DND -- AFK overwrites DND
if UnitIsAFK("player") then if UnitIsAFK("player") then
Artemis:Transmit("gameState", "afk") Artemis:Transmit("gameState", "afk")
return return
end end
if UnitIsDND("player") then if UnitIsDND("player") then
Artemis:Transmit("gameState", "dnd") Artemis:Transmit("gameState", "dnd")
return return
end end
Artemis:Transmit("gameState", "ingame") Artemis:Transmit("gameState", "ingame")
end end
end end
function Artemis:ACHIEVEMENT_EARNED(...) function Artemis:ACHIEVEMENT_EARNED(...)
Artemis:Transmit("player", Artemis:GetPlayerDetails()) Artemis:Transmit("player", Artemis:GetPlayerDetails())
end end
function Artemis:ACTIVE_TALENT_GROUP_CHANGED(...) function Artemis:ACTIVE_TALENT_GROUP_CHANGED(...)
Artemis:Transmit("player", Artemis:GetPlayerDetails()) Artemis:Transmit("player", Artemis:GetPlayerDetails())
end end
function Artemis:UNIT_TARGET(...) function Artemis:UNIT_TARGET(...)
local _, source = ... local _, source = ...
if not (source == "player") then if not (source == "player") then
return return
end end
local details = Artemis:GetUnitDetails("target") local details = Artemis:GetUnitDetails("target")
channeling["target"] = false channeling["target"] = false
Artemis:Transmit("target", details) Artemis:Transmit("target", details)
Artemis:TransmitUnitState("target", true); Artemis:TransmitUnitState("target", true);
end end
function Artemis:UNIT_HEALTH(...) 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_POWER(...) function Artemis:UNIT_POWER(...)
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_AURA(...)
local _, source = ... local _, source = ...
if not (source == "player") then if not (source == "player") then
return return
end end
local buffs = Artemis:GetUnitAuras(source, "PLAYER|HELPFUL") local buffs = Artemis:GetUnitAuras(source, "PLAYER|HELPFUL")
local debuffs = Artemis:GetUnitAuras(source, "PLAYER|HARMFUL") local debuffs = Artemis:GetUnitAuras(source, "PLAYER|HARMFUL")
local newBuffs = json.encode(buffs) local newBuffs = json.encode(buffs)
local newDebuffs = json.encode(debuffs) local newDebuffs = json.encode(debuffs)
if not (lastBuffs == newBuffs) then if not (lastBuffs == newBuffs) then
Artemis:Transmit("buffs", buffs) Artemis:Transmit("buffs", buffs)
end end
if not (lastDebuffs == newDebuffs) then if not (lastDebuffs == newDebuffs) then
Artemis:Transmit("debuffs", debuffs) Artemis:Transmit("debuffs", debuffs)
end end
lastBuffs = newBuffs lastBuffs = newBuffs
lastDebuffs = newDebuffs lastDebuffs = newDebuffs
end end
-- Detect non-instant spell casts -- Detect non-instant spell casts
function Artemis:UNIT_SPELLCAST_START(...) function Artemis:UNIT_SPELLCAST_START(...)
local _, unitID, spell, rank, lineID, spellID = ... local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then if not (unitID == "player") and not (unitID == "target") then
return return
end end
local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID) local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID)
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible} local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
lastLine[unitID] = lineID lastLine[unitID] = lineID
Artemis:Transmit("spellCast", table, "ALERT") Artemis:Transmit("spellCast", table, "ALERT")
end end
-- Detect instant spell casts -- Detect instant spell casts
function Artemis:UNIT_SPELLCAST_SUCCEEDED (...) function Artemis:UNIT_SPELLCAST_SUCCEEDED (...)
local _, unitID, spell, rank, lineID, spellID = ... local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then if not (unitID == "player") and not (unitID == "target") then
return return
end end
if channeling[unitID] == true then if channeling[unitID] == true then
return return
end end
-- Many spells are irrelevant system spells, don't transmit these -- Many spells are irrelevant system spells, don't transmit these
if unitID == "player" and not (IsPlayerSpell(spellID)) then if unitID == "player" and not (IsPlayerSpell(spellID)) then
return return
end end
local name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unitID) local name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unitID)
-- Don't trigger on the success of a non instant cast -- Don't trigger on the success of a non instant cast
if not (lastLine[unitID] == nil) and lastLine[unitID] == lineID then if not (lastLine[unitID] == nil) and lastLine[unitID] == lineID then
return return
end end
-- Set back the last line to what is currently being cast (Fireblast during Fireball per example) -- Set back the last line to what is currently being cast (Fireblast during Fireball per example)
if not (name == nil) then if not (name == nil) then
lastLine[unitID] = castID lastLine[unitID] = castID
else else
lastLine[unitID] = nil lastLine[unitID] = nil
end end
local table = {uid = unitID, n = spell, sid = spellID} local table = {uid = unitID, n = spell, sid = spellID}
Artemis:Transmit("instantSpellCast", table, "ALERT") Artemis:Transmit("instantSpellCast", table, "ALERT")
end end
-- Detect falure of non instant casts -- Detect falure of non instant casts
function Artemis:UNIT_SPELLCAST_FAILED (...) function Artemis:UNIT_SPELLCAST_FAILED (...)
local source, unitID, _, _, lineID = ... local source, unitID, _, _, lineID = ...
if not (unitID == "player") and not (unitID == "target") then if not (unitID == "player") and not (unitID == "target") then
return return
end end
if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then
return return
end end
lastLine[unitID] = nil lastLine[unitID] = nil
Artemis:Transmit("spellCastFailed", unitID, "ALERT") Artemis:Transmit("spellCastFailed", unitID, "ALERT")
end end
-- Detect falure of non instant casts -- Detect falure of non instant casts
function Artemis:UNIT_SPELLCAST_DELAYED (...) function Artemis:UNIT_SPELLCAST_DELAYED (...)
local _, unitID, spell, rank, lineID, spellID = ... local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then if not (unitID == "player") and not (unitID == "target") then
return return
end end
local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID) local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID)
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible} local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
Artemis:Transmit("spellCast", table, "ALERT") Artemis:Transmit("spellCast", table, "ALERT")
end end
-- Detect cancellation of non instant casts -- Detect cancellation of non instant casts
function Artemis:UNIT_SPELLCAST_INTERRUPTED (...) function Artemis:UNIT_SPELLCAST_INTERRUPTED (...)
local source, unitID, _, _, lineID = ... local source, unitID, _, _, lineID = ...
if not (unitID == "player") and not (unitID == "target") then if not (unitID == "player") and not (unitID == "target") then
return return
end end
if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then
return return
end end
lastLine[unitID] = nil lastLine[unitID] = nil
Artemis:Transmit("spellCastInterrupted", unitID, "ALERT") Artemis:Transmit("spellCastInterrupted", unitID, "ALERT")
end end
-- Detect spell channels -- Detect spell channels
function Artemis:UNIT_SPELLCAST_CHANNEL_START(...) function Artemis:UNIT_SPELLCAST_CHANNEL_START(...)
local _, unitID, spell, rank, lineID, spellID = ... local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then if not (unitID == "player") and not (unitID == "target") then
return return
end end
channeling[unitID] = true channeling[unitID] = true
local name, _, _, _, startTime, endTime, _, notInterruptible = UnitChannelInfo(unitID) local name, _, _, _, startTime, endTime, _, notInterruptible = UnitChannelInfo(unitID)
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible} local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
Artemis:Transmit("spellChannel", table, "ALERT") Artemis:Transmit("spellChannel", table, "ALERT")
end end
function Artemis:UNIT_SPELLCAST_CHANNEL_UPDATE (...) function Artemis:UNIT_SPELLCAST_CHANNEL_UPDATE (...)
local _, unitID, spell, rank, lineID, spellID = ... local _, unitID, spell, rank, lineID, spellID = ...
if not (unitID == "player") and not (unitID == "target") then if not (unitID == "player") and not (unitID == "target") then
return return
end end
local name, _, _, _, startTime, endTime, _, notInterruptible = UnitChannelInfo(unitID) local name, _, _, _, startTime, endTime, _, notInterruptible = UnitChannelInfo(unitID)
local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible} local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible}
Artemis:Transmit("spellChannel", table, "ALERT") Artemis:Transmit("spellChannel", table, "ALERT")
end end
-- Detect cancellation of channels -- Detect cancellation of channels
function Artemis:UNIT_SPELLCAST_CHANNEL_STOP (...) function Artemis:UNIT_SPELLCAST_CHANNEL_STOP (...)
local source, unitID, _, _, lineID = ... local source, unitID, _, _, lineID = ...
if not (unitID == "player") and not (unitID == "target") then if not (unitID == "player") and not (unitID == "target") then
return return
end end
channeling[unitID] = false channeling[unitID] = false
Artemis:Transmit("spellChannelInterrupted", unitID, "ALERT") Artemis:Transmit("spellChannelInterrupted", unitID, "ALERT")
end end
function Artemis:ZONE_CHANGED_NEW_AREA (...) function Artemis:ZONE_CHANGED_NEW_AREA (...)
local pvpType, isSubZonePVP, factionName = GetZonePVPInfo() local pvpType, isSubZonePVP, factionName = GetZonePVPInfo()
Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName}) Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName})
end end
function Artemis:ZONE_CHANGED (...) function Artemis:ZONE_CHANGED (...)
local pvpType, isSubZonePVP, factionName = GetZonePVPInfo() local pvpType, isSubZonePVP, factionName = GetZonePVPInfo()
Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName}) Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName})
end end

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -2,22 +2,37 @@
using Artemis.Managers; using Artemis.Managers;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Artemis.Modules.Games.WoW.Models; using Artemis.Modules.Games.WoW.Models;
using Artemis.Properties;
using Artemis.Services;
using Artemis.Utilities;
using Microsoft.Win32;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System.IO;
using System.IO.Compression;
namespace Artemis.Modules.Games.WoW namespace Artemis.Modules.Games.WoW
{ {
public class WoWModel : ModuleModel public class WoWModel : ModuleModel
{ {
private readonly MetroDialogService _dialogService;
private readonly WowPacketScanner _packetScanner; private readonly WowPacketScanner _packetScanner;
public WoWModel(DeviceManager deviceManager, LuaManager luaManager, WowPacketScanner packetScanner) : base(deviceManager, luaManager) public WoWModel(DeviceManager deviceManager, LuaManager luaManager, WowPacketScanner packetScanner, MetroDialogService dialogService) : base(deviceManager, luaManager)
{ {
Settings = SettingsProvider.Load<WoWSettings>(); Settings = SettingsProvider.Load<WoWSettings>();
DataModel = new WoWDataModel(); DataModel = new WoWDataModel();
ProcessNames.Add("Wow-64"); ProcessNames.Add("Wow-64");
_packetScanner = packetScanner; _packetScanner = packetScanner;
_dialogService = dialogService;
_packetScanner.RaiseDataReceived += (sender, args) => HandleGameData(args.Command, args.Data); _packetScanner.RaiseDataReceived += (sender, args) => HandleGameData(args.Command, args.Data);
FindWoW();
// I simply cannot be sure wether this addon will bring people's accounts in trouble so
// lets remove it whenever Artemis isn't running the WoW module.
// (This also means the addon isnt left behind should the user uninstall Artemis.)
RemoveAddon();
} }
public override string Name => "WoW"; public override string Name => "WoW";
@ -26,12 +41,14 @@ namespace Artemis.Modules.Games.WoW
public override void Enable() public override void Enable()
{ {
PlaceAddon();
_packetScanner.Start(); _packetScanner.Start();
base.Enable(); base.Enable();
} }
public override void Dispose() public override void Dispose()
{ {
RemoveAddon();
_packetScanner.Stop(); _packetScanner.Stop();
base.Dispose(); base.Dispose();
} }
@ -44,6 +61,67 @@ namespace Artemis.Modules.Games.WoW
dataModel.Target.Update(); dataModel.Target.Update();
} }
public void FindWoW()
{
var gameSettings = Settings as WoWSettings;
if (gameSettings == null)
return;
// If already propertly set up, don't do anything
if (gameSettings.GameDirectory != null && File.Exists(gameSettings.GameDirectory + @"\Wow.exe"))
return;
var key = Registry.LocalMachine.OpenSubKey(
@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\World of Warcraft");
var path = key?.GetValue("DisplayIcon")?.ToString();
if (string.IsNullOrEmpty(path) || !File.Exists(path))
return;
gameSettings.GameDirectory = path.Substring(0, path.Length - 8);
gameSettings.Save();
}
public void ChangeDirectory(string directory, bool checkExe)
{
var settings = (WoWSettings) Settings;
if (checkExe && !File.Exists(directory + @"\Wow.exe"))
{
_dialogService.ShowErrorMessageBox("Please select a valid WoW directory\n\n" +
@"By default WoW is in C:\Program Files (x86)\World of Warcraft");
settings.GameDirectory = string.Empty;
settings.Save();
return;
}
settings.GameDirectory = directory;
settings.Save();
}
public void PlaceAddon()
{
var settings = (WoWSettings) Settings;
var path = settings.GameDirectory;
if (!File.Exists(path + @"\Wow.exe"))
return;
// Load the ZIP from resources
using (var stream = new MemoryStream(Resources.wow_addon))
{
using (var archive = new ZipArchive(stream))
{
archive.ExtractToDirectory(settings.GameDirectory + @"\Interface\Addons\Artemis", true);
}
}
}
public void RemoveAddon()
{
var settings = (WoWSettings) Settings;
if (Directory.Exists(settings.GameDirectory + @"\Interface\Addons\Artemis"))
Directory.Delete(settings.GameDirectory + @"\Interface\Addons\Artemis", true);
}
private void HandleGameData(string command, string data) private void HandleGameData(string command, string data)
{ {
JToken json = null; JToken json = null;
@ -180,4 +258,4 @@ namespace Artemis.Modules.Games.WoW
dataModel.Target.CastBar.Clear(); dataModel.Target.CastBar.Clear();
} }
} }
} }

View File

@ -4,5 +4,6 @@ namespace Artemis.Modules.Games.WoW
{ {
public class WoWSettings : ModuleSettings public class WoWSettings : ModuleSettings
{ {
public string GameDirectory { get; set; }
} }
} }

View File

@ -7,6 +7,7 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@ -32,11 +33,20 @@
Style="{StaticResource MahApps.Metro.Styles.ToggleSwitchButton.Win10}" ToolTip="Note: You can't enable an module when Artemis is disabled" /> Style="{StaticResource MahApps.Metro.Styles.ToggleSwitchButton.Win10}" ToolTip="Note: You can't enable an module when Artemis is disabled" />
</StackPanel> </StackPanel>
<!-- Game directory -->
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,1,0">
<Label FontSize="20" HorizontalAlignment="Left" Content="World of Warcraft directory" />
<Grid>
<TextBox x:Name="GameDirectory" Height="23" TextWrapping="Wrap" Margin="0,0,30,0" Text="{Binding Path=Settings.GameDirectory, Mode=TwoWay}" cal:Message.Attach="[Event LostFocus] = [Action PlaceAddon]" />
<Button x:Name="BrowseDirectory" Content="..." RenderTransformOrigin="-0.039,-0.944" HorizontalAlignment="Right" Width="25" Style="{DynamicResource SquareButtonStyle}" Height="26" />
</Grid>
</StackPanel>
<!-- Profile editor --> <!-- Profile editor -->
<ContentControl Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" x:Name="ProfileEditor" /> <ContentControl Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" x:Name="ProfileEditor" />
<!-- Buttons --> <!-- Buttons -->
<StackPanel Grid.Column="0" Grid.Row="3" Orientation="Horizontal" VerticalAlignment="Bottom"> <StackPanel Grid.Row="4" Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Bottom">
<Button x:Name="ResetSettings" Content="Reset effect" VerticalAlignment="Top" Width="100" Style="{DynamicResource SquareButtonStyle}" /> <Button x:Name="ResetSettings" Content="Reset effect" VerticalAlignment="Top" Width="100" Style="{DynamicResource SquareButtonStyle}" />
<Button x:Name="SaveSettings" Content="Save changes" VerticalAlignment="Top" Width="100" Margin="10,0,0,0" Style="{DynamicResource SquareButtonStyle}" /> <Button x:Name="SaveSettings" Content="Save changes" VerticalAlignment="Top" Width="100" Margin="10,0,0,0" Style="{DynamicResource SquareButtonStyle}" />
</StackPanel> </StackPanel>

View File

@ -1,4 +1,5 @@
using Artemis.Managers; using System.Windows.Forms;
using Artemis.Managers;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Ninject; using Ninject;
@ -13,5 +14,23 @@ namespace Artemis.Modules.Games.WoW
} }
public override bool UsesProfileEditor => true; public override bool UsesProfileEditor => true;
public void PlaceAddon()
{
((WoWModel) ModuleModel).PlaceAddon();
}
public void BrowseDirectory()
{
var dialog = new FolderBrowserDialog {SelectedPath = ((WoWSettings) Settings).GameDirectory};
var result = dialog.ShowDialog();
if (result != DialogResult.OK)
return;
((WoWSettings) Settings).GameDirectory = dialog.SelectedPath;
((WoWModel) ModuleModel).PlaceAddon();
Settings.Save();
NotifyOfPropertyChange(() => Settings);
}
} }
} }

View File

@ -19,7 +19,7 @@ namespace Artemis.Properties {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {
@ -105,6 +105,7 @@ namespace Artemis.Properties {
///{ ///{
/// &quot;uri&quot; &quot;http://localhost:{{port}}/csgo_game_event&quot; /// &quot;uri&quot; &quot;http://localhost:{{port}}/csgo_game_event&quot;
/// &quot;timeout&quot; &quot;0.1&quot; /// &quot;timeout&quot; &quot;0.1&quot;
/// &quot;heartbeat&quot; &quot;0.1&quot;
/// &quot;data&quot; /// &quot;data&quot;
/// { /// {
/// &quot;provider&quot; &quot;1&quot; /// &quot;provider&quot; &quot;1&quot;
@ -437,5 +438,15 @@ namespace Artemis.Properties {
return ((byte[])(obj)); return ((byte[])(obj));
} }
} }
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] wow_addon {
get {
object obj = ResourceManager.GetObject("wow_addon", resourceCulture);
return ((byte[])(obj));
}
}
} }
} }

View File

@ -223,4 +223,7 @@
<data name="k95_platinum" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="k95_platinum" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Keyboards\k95-platinum.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\Keyboards\k95-platinum.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>
<data name="wow_addon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Modules\Games\WoW\Resources\wow-addon.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root> </root>

View File

@ -4,10 +4,10 @@
<package id="Caliburn.Micro" version="3.1.0" targetFramework="net461" /> <package id="Caliburn.Micro" version="3.1.0" targetFramework="net461" />
<package id="Caliburn.Micro.Core" version="3.1.0" targetFramework="net461" /> <package id="Caliburn.Micro.Core" version="3.1.0" targetFramework="net461" />
<package id="Colore" version="5.1.0" targetFramework="net461" /> <package id="Colore" version="5.1.0" targetFramework="net461" />
<package id="CSCore" version="1.2.1.1" targetFramework="net461" /> <package id="CSCore" version="1.2.1.2" targetFramework="net461" />
<package id="CUE.NET" version="1.1.3.1" targetFramework="net461" /> <package id="CUE.NET" version="1.1.3.1" targetFramework="net461" />
<package id="DeltaCompressionDotNet" version="1.1.0" targetFramework="net461" /> <package id="DeltaCompressionDotNet" version="1.1.0" targetFramework="net461" />
<package id="DynamicExpresso.Core" version="1.3.3.6" targetFramework="net461" /> <package id="DynamicExpresso.Core" version="1.3.4.7" targetFramework="net461" />
<package id="gong-wpf-dragdrop" version="0.1.4.3" targetFramework="net461" /> <package id="gong-wpf-dragdrop" version="0.1.4.3" targetFramework="net461" />
<package id="Hardcodet.NotifyIcon.Wpf" version="1.0.8" targetFramework="net452" /> <package id="Hardcodet.NotifyIcon.Wpf" version="1.0.8" targetFramework="net452" />
<package id="log4net" version="2.0.8" targetFramework="net461" /> <package id="log4net" version="2.0.8" targetFramework="net461" />
@ -28,7 +28,7 @@
<package id="SharpDX" version="4.0.1" targetFramework="net461" /> <package id="SharpDX" version="4.0.1" targetFramework="net461" />
<package id="SharpDX.Direct3D9" version="4.0.1" targetFramework="net461" /> <package id="SharpDX.Direct3D9" version="4.0.1" targetFramework="net461" />
<package id="Splat" version="2.0.0" targetFramework="net461" /> <package id="Splat" version="2.0.0" targetFramework="net461" />
<package id="SpotifyAPI-NET" version="2.16.0" targetFramework="net461" /> <package id="SpotifyAPI-NET" version="2.16.1" targetFramework="net461" />
<package id="squirrel.windows" version="1.4.4" targetFramework="net461" /> <package id="squirrel.windows" version="1.4.4" targetFramework="net461" />
<package id="WpfExceptionViewer" version="1.0.0.0" targetFramework="net452" /> <package id="WpfExceptionViewer" version="1.0.0.0" targetFramework="net452" />
</packages> </packages>