diff --git a/README.md b/README.md index 6c6c53d..89d1df8 100644 --- a/README.md +++ b/README.md @@ -2,31 +2,61 @@ This addon is meant to help hunters to setup tranqshot rotation and give them real time visual feedback about it. -This first version is missing a lot of features I want to implement, and will only let you configure automatic success and miss tranqshot announces to a defined channel, hopefully you won't have to bother with that crappy macro anymore ! +**TranqRotate DOES NOT synchronize anything with other hunters (yet), even if they use the addon too. +Considering this fact and the Blizzard 45m combat log limit, I would advice to not have your hunters spread too much until sync is added.** -## Roadmap +This addon is still in a (stable) beta state, a stable release should come soon. -Here is a list of feature I want to implement at some point, no specific order is decided yet. +## Feedback -- Display of hunter's tranqhot cooldowns -- Transhot rotation configuration (raid assist required, order hunters, make groupe of multiple hunters, leave some hunt out of rotation as backup) -- Transhot rotation display (Synchronized list will rotate as hunters tranq successfully or miss, showing at top the next hunter or hunter group that will have to tranq) +I'm looking for feedback ! Feel free to contact me ingame (Slivo@Sulfuron). + +Please report any issue using github issues : https://github.com/Slivo-fr/TranqRotate/issues -### Download +## Features -Do not use github download button on this page, get the latest release zip file from https://github.com/Slivo-fr/TranqRotate/releases +- Automatically send messages to notify others player about your tranq success or fail, hopefully you won't have to bother with that crappy macro anymore ! +- Display the list of raid hunters +- Allow player to re-order players between two groups : main rotation and backup. +- Whisper the next hunter on the rotation as well as all backup hunters if you miss your tranqshot +- Provide a real time visual feedback about the rotation status, even if no one else use the addon in your raid +- Allow player to broadcast the configured rotation and backup group to the raid +- Display offline and dead status on hunters frames -Also available here https://www.curseforge.com/wow/addons/tranqrotate and there https://wowclassicui.com/fr/addons/tranqrotate +## Usage + +Use `/tranq` for options -### Credits +You must be in a raid for hunters to get registered and displayed by the addon. -I've been learning and using some code of https://github.com/Aviana/YaHT to build this first codebase. It helped a lot ! +First step is to set your tranq order using drag & drop on hunters. +You may use the trumpet button to get it written in raid chat so others hunters can know what you planned (And set their TranqRotate order if they use it). +Please note the backup group is hidden if empty but you can still drag hunters into it. -### About me +You can now just pull the boss and start shooting your tranqshots, TranqRotate will track the rotation and use a purple tranq-like color on the next hunter that should tranq. -I work as developer in a very different field, I have been wanting to make some addon for a long time but I'm a total newbie on this topic. +Once boss has died (or you wiped), just use the reset button in the top bar to reset the rotation state -Here is my first try, don't be too harsh but let me know if somethings looks wrong to you ! -Please report any issue using github issues : https://github.com/Slivo-fr/TranqRotate/issues +## Roadmap + +Here is a list of feature I want to implement at some point, no specific order is decided yet. + +- Display of every hunter tranqhot cooldowns +- Data sync between all users, allowing raid leader or raid assistant to setup the rotation for everyone +- Automatic handling of death and disconnection of hunters on the rotation group (swap with a backup, send an alert about it) +- Use raid symbols to mark hunters that need to tranq, or that need to backup a failed tranqshot +- Automatic reset of rotation when raid wipe or boss die +- Encounter detection and frenzy cooldown progress bar +- Handling of battleground raids + +## Download + +Do not use github download button on this page, get the latest release zip file from https://github.com/Slivo-fr/TranqRotate/releases + +Also available here https://www.curseforge.com/wow/addons/tranqrotate and there https://wowclassicui.com/fr/addons/tranqrotate + +## Credits +I've been learning and using some code of https://github.com/Aviana/YaHT to build this first codebase. Thanks for his work. +Many thanks to the wow addon discord members that helped me a lot too. diff --git a/TranqRotate.lua b/TranqRotate.lua deleted file mode 100644 index 35113a9..0000000 --- a/TranqRotate.lua +++ /dev/null @@ -1,73 +0,0 @@ -TranqRotate = select(2, ...) - -local L = TranqRotate.L -local ACR = LibStub("AceConfigRegistry-3.0", true) -TranqRotate.version = "1.0.1" - -local TranqShot = GetSpellInfo(19801) ---local TranqShot = GetSpellInfo(14287) --Arcane shot for testing -local canTranq = IsUsableSpell(TranqShot) - -function TranqRotate:init() - self:LoadDefaults() - - self.db = LibStub:GetLibrary("AceDB-3.0"):New("TranqRotateDb", self.defaults, true) - self.db.RegisterCallback(self, "OnProfileChanged", "ProfilesChanged") - self.db.RegisterCallback(self, "OnProfileCopied", "ProfilesChanged") - - self:CreateConfig() - - printMessage(L['LOADED_MESSAGE']) -end - -function TranqRotate:ProfilesChanged() - self.db:RegisterDefaults(self.defaults) -end - -function printMessage(msg) - print(msg); -end - -function TranqRotate:COMBAT_LOG_EVENT_UNFILTERED() - - local timestamp, event, _, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = CombatLogGetCurrentEventInfo() - local spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand = select(12, CombatLogGetCurrentEventInfo()) - - if event == "SPELL_CAST_SUCCESS" and spellName == TranqShot and sourceGUID == UnitGUID("player") then - TranqRotate:sendAnnounceMessage(TranqRotate.db.profile.announceSuccessMessage, destName) - elseif event == "SPELL_MISSED" and spellName == TranqShot and sourceGUID == UnitGUID("player") then - TranqRotate:sendAnnounceMessage(TranqRotate.db.profile.announceFailMessage, destName) - end -end - -function TranqRotate:sendAnnounceMessage(message, destName) - if TranqRotate.db.profile.enableAnnounces then - local channelNumber - if TranqRotate.db.profile.channelType == "CHANNEL" then - channelNumber = GetChannelName(TranqRotate.db.profile.targetChannel) - end - SendChatMessage(string.format(message,destName), TranqRotate.db.profile.channelType, nil, channelNumber or TranqRotate.db.profile.targetChannel) - end -end - -SLASH_TRANQROTATE1 = "/tranq" -SLASH_TRANQROTATE2 = "/tranqrotate" -SlashCmdList["TRANQROTATE"] = function(msg) - local AceConfigDialog = LibStub("AceConfigDialog-3.0") - AceConfigDialog:Open("TranqRotate") -end - -local frame = CreateFrame("Frame") -frame:RegisterEvent("PLAYER_LOGIN") -frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") -frame:SetScript( - "OnEvent", - function(self, event, ...) - if( event == "PLAYER_LOGIN" ) then - TranqRotate:init() - self:UnregisterEvent("PLAYER_LOGIN") - else - TranqRotate[event](TranqRotate, ...) - end - end -) diff --git a/TranqRotate.toc b/TranqRotate.toc index e36246f..28b620a 100644 --- a/TranqRotate.toc +++ b/TranqRotate.toc @@ -1,4 +1,4 @@ -## Interface: 11302 +## Interface: 11303 ## Title: TranqRotate ## Notes: A tranqshot rotation assistant ## Author: Slivo @@ -20,6 +20,13 @@ locales\frFR.lua locales\zhCN.lua locales\zhTW.lua -TranqRotate.lua -defaults.lua -settings.lua \ No newline at end of file +src\tranqRotate.lua +src\events.lua +src\rotation.lua +src\constants.lua +src\frames.lua +src\gui.lua +src\dragdrop.lua +src\defaults.lua +src\settings.lua +src\utils.lua \ No newline at end of file diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..81ea064 --- /dev/null +++ b/changelog.md @@ -0,0 +1,40 @@ +## TranqRotate Changelog + +#### v1.1.0 + +- Adds TranqRotate window +- Adds raid hunters detection +- Adds rotation handling +- Adds main window position change using drag & drop +- Adds main window position lock setting +- Adds hunter death and disconnection display on hunter frames +- Adds hunter group and order change using drag & drop +- Adds lock/unlock slash command option +- Adds toggle slash command option +- Adds "broadcast rotation setup to raid chat" +- Adds rotation reset button to re-initialize betweeen/after pulls +- Adds whisper message to next hunter and all backup hunters when you miss +- Adds close button to top bar +- Adds option to hide main window when not in a raid + +- Change base slash command to display available options +- Change slash command settings to `/tranq settings` + +- Splits code in multiple files +- Moves all lua files to src/ +- Use first target change event to update raid status if player login while already in raid +- Updates game interface version to 11303 + +#### v1.0.1 + +- Fix missing libs in toc file + +#### v1.0.0 + +- Adds slash command +- Adds settings +- Adds base combat log handling +- Adds automatic success and fail tranqshot messages +- Adds frFR locale +- Adds znTW locale +- Adds znTW locale diff --git a/defaults.lua b/defaults.lua deleted file mode 100644 index c1f193e..0000000 --- a/defaults.lua +++ /dev/null @@ -1,12 +0,0 @@ -local L = TranqRotate.L - -function TranqRotate:LoadDefaults() - self.defaults = { - profile = { - enableAnnounces = true, - channelType = "YELL", - announceSuccessMessage = L["DEFAULT_SUCCESS_ANNOUNCE_MESSAGE"], - announceFailMessage = L["DEFAULT_FAIL_ANNOUNCE_MESSAGE"], - }, - } -end \ No newline at end of file diff --git a/locales/enUS.lua b/locales/enUS.lua index ad7d809..361b480 100644 --- a/locales/enUS.lua +++ b/locales/enUS.lua @@ -2,13 +2,15 @@ local TranqRotate = select(2, ...) local L = { - ["LOADED_MESSAGE"] = "TranqRotate loaded, type /tranq for settings", + ["LOADED_MESSAGE"] = "TranqRotate loaded, type /tranq for options", + ["TRANQ_WINDOW_HIDDEN"] = "Tranqrotate window hidden. Use /tranq toggle to get it back", -- Settings ["SETTING_GENERAL"] = "General", ["SETTING_GENERAL_REPORT"] = "Please report any issue at", - ["SETTING_GENERAL_DESC"] = "This first version will only allow you to get automatic tranq annouce messages\n".. - "I'm planning to add more features giving visual feedback about tranqshot rotation", + ["SETTING_GENERAL_DESC"] = "New : TranqRotate now has a window allowing hunters to track rotation status\n".. + "It still automatically announce tranq or miss and now whispers next and backup when you miss\n".. + "It doesn't sync rotation order between hunters yet, you have to manually set the correct groups and order.", --- Announces ["SETTING_ANNOUNCES"] = "Announces", @@ -35,12 +37,27 @@ local L = { ["ANNOUNCES_MESSAGE_HEADER"] = "Announce messages", ["SUCCESS_MESSAGE_LABEL"] = "Successful announce message", ["FAIL_MESSAGE_LABEL"] = "Fail announce message", + ["FAIL_WHISPER_LABEL"] = "Fail whisper message", ['DEFAULT_SUCCESS_ANNOUNCE_MESSAGE'] = "Tranqshot done on %s", ['DEFAULT_FAIL_ANNOUNCE_MESSAGE'] = "!!! TRANQSHOT FAILED ON %s !!!", + ['DEFAULT_FAIL_WHISPER_MESSAGE'] = "TRANQSHOT FAILED ! TRANQ NOW !", + + --- Rotation + ["LOCK_WINDOW"] = "Lock window", + ["LOCK_WINDOW_DESC"] = "Lock window", + ["HIDE_WINDOW_NOT_IN_RAID"] = "Hide the window when not in a raid", + ["HIDE_WINDOW_NOT_IN_RAID_DESC"] = "Hide the window when not in a raid", + ["WINDOW_LOCKED"] = "TranqRotate: Window locked", + ["WINDOW_UNLOCKED"] = "TranqRotate: Window unlocked", --- Profiles - ["SETTING_PROFILES"] = "Profiles" + ["SETTING_PROFILES"] = "Profiles", + + --- Raid broadcast messages + ["BROADCAST_HEADER_TEXT"] = "Hunter tranqshot setup", + ["BROADCAST_ROTATION_PREFIX"] = "Rotation", + ["BROADCAST_BACKUP_PREFIX"] = "Backup", } TranqRotate.L = L diff --git a/locales/frFR.lua b/locales/frFR.lua index 7c066e1..ed9cdde 100644 --- a/locales/frFR.lua +++ b/locales/frFR.lua @@ -5,12 +5,14 @@ local TranqRotate = select(2, ...) local L = { ["LOADED_MESSAGE"] = "TranqRotate chargé, utilisez /tranq pour les options", + ["TRANQ_WINDOW_HIDDEN"] = "Tranqrotate window hidden. Use /tranq toggle to get it back", -- Settings ["SETTING_GENERAL"] = "Général", ["SETTING_GENERAL_REPORT"] = "Merci de signaler tout bug rencontré sur", - ["SETTING_GENERAL_DESC"] = "Cette première version permet uniquement des annonces automatique pour le tir tranquillisant\n".. - "D'autres fonctionnalités sont prévues, permettant entre autre un affichage temps réel de la rotation et des cooldowns", + ["SETTING_GENERAL_DESC"] = "Nouveau : Une fenetre permet maintenant de régler et suivre la rotation\n".. + "TranqRotate va également et automatiquement chuchoter le chasseur suivant et le backup en cas de raté !\n".. + "Aucune synchronisation n'est encore en place, chaque chasseur dois régler l'ordre de son coté.", --- Announces ["SETTING_ANNOUNCES"] = "Annonces", @@ -37,12 +39,27 @@ local L = { ["ANNOUNCES_MESSAGE_HEADER"] = "Messages", ["SUCCESS_MESSAGE_LABEL"] = "Message de réussite", ["FAIL_MESSAGE_LABEL"] = "Message d'échec", + ["FAIL_WHISPER_LABEL"] = "Message d'échec cdhuchoté", ['DEFAULT_SUCCESS_ANNOUNCE_MESSAGE'] = "Tir tranquillisant fait sur %s", ['DEFAULT_FAIL_ANNOUNCE_MESSAGE'] = "!!! TIR TRANQUILLISANT RATE SUR %s !!!", + ['DEFAULT_FAIL_WHISPER_MESSAGE'] = "TIR TRANQUILISANT RATE ! TRANQ MAINTENANT !", + + --- Rotation + ["LOCK_WINDOW"] = "Verrouiller la position de la fênetre", + ["LOCK_WINDOW_DESC"] = "Verrouiller la position de la fênetre", + ["HIDE_WINDOW_NOT_IN_RAID"] = "Masquer la fenêtre principale hors raid", + ["HIDE_WINDOW_NOT_IN_RAID_DESC"] = "Masquer la fenêtre principale hors raid", + ["WINDOW_LOCKED"] = "TranqRotate: Fenêtre verrouillée", + ["WINDOW_UNLOCKED"] = "TranqRotate: Fenêtre déverrouillée", --- Profiles - ["SETTING_PROFILES"] = "Profils" + ["SETTING_PROFILES"] = "Profils", + + --- Raid broadcast messages + ["BROADCAST_HEADER_TEXT"] = "Hunter tranqshot setup", + ["BROADCAST_ROTATION_PREFIX"] = "Rotation", + ["BROADCAST_BACKUP_PREFIX"] = "Backup", } TranqRotate.L = L diff --git a/locales/zhCN.lua b/locales/zhCN.lua index 35b1539..a404c00 100644 --- a/locales/zhCN.lua +++ b/locales/zhCN.lua @@ -4,13 +4,15 @@ local TranqRotate = select(2, ...) local L = { - ["LOADED_MESSAGE"] = "TranqRotate 已加载, 输入 /tranq 进入设置", + ["LOADED_MESSAGE"] = "TranqRotate loaded, type /tranq for options", + ["TRANQ_WINDOW_HIDDEN"] = "Tranqrotate window hidden. Use /tranq toggle to get it back", -- Settings ["SETTING_GENERAL"] = "General", ["SETTING_GENERAL_REPORT"] = "Please report any issue at", - ["SETTING_GENERAL_DESC"] = "This first version will only allow you to get automatic tranq annouce messages\n".. - "I'm planning to add more features giving visual feedback about tranqshot rotation", + ["SETTING_GENERAL_DESC"] = "New : TranqRotate now has a window allowing hunters to track rotation status\n".. + "It still automatically announce tranq or miss and now whispers next and backup when you miss\n".. + "It doesn't sync rotation order between hunters yet, you have to manually set the correct groups and order.", --- Announces ["SETTING_ANNOUNCES"] = "通告", @@ -37,12 +39,27 @@ local L = { ["ANNOUNCES_MESSAGE_HEADER"] = "通告信息", ["SUCCESS_MESSAGE_LABEL"] = "施放成功通告信息", ["FAIL_MESSAGE_LABEL"] = "施放失败通告信息", + ["FAIL_WHISPER_LABEL"] = "Fail whisper message", ['DEFAULT_SUCCESS_ANNOUNCE_MESSAGE'] = "已对 %s 施放了宁神射击!", ['DEFAULT_FAIL_ANNOUNCE_MESSAGE'] = "!!! 对 %s 宁神失败!!!", + ['DEFAULT_FAIL_WHISPER_MESSAGE'] = "TRANQSHOT FAILED ! TRANQ NOW !", + + --- Rotation + ["LOCK_WINDOW"] = "Lock window", + ["LOCK_WINDOW_DESC"] = "Lock window", + ["HIDE_WINDOW_NOT_IN_RAID"] = "Hide the window when not in a raid", + ["HIDE_WINDOW_NOT_IN_RAID_DESC"] = "Hide the window when not in a raid", + ["WINDOW_LOCKED"] = "TranqRotate: Window locked", + ["WINDOW_UNLOCKED"] = "TranqRotate: Window unlocked", --- Profiles - ["SETTING_PROFILES"] = "配置" + ["SETTING_PROFILES"] = "配置", + + --- Raid broadcast messages + ["BROADCAST_HEADER_TEXT"] = "Hunter tranqshot setup", + ["BROADCAST_ROTATION_PREFIX"] = "Rotation", + ["BROADCAST_BACKUP_PREFIX"] = "Backup", } TranqRotate.L = L diff --git a/locales/zhTW.lua b/locales/zhTW.lua index 6f55e73..9bd1b01 100644 --- a/locales/zhTW.lua +++ b/locales/zhTW.lua @@ -4,13 +4,15 @@ local TranqRotate = select(2, ...) local L = { - ["LOADED_MESSAGE"] = "TranqRotate 已加載, 輸入 /tranq 進入設置", + ["LOADED_MESSAGE"] = "TranqRotate loaded, type /tranq for options", + ["TRANQ_WINDOW_HIDDEN"] = "Tranqrotate window hidden. Use /tranq toggle to get it back", -- Settings ["SETTING_GENERAL"] = "General", ["SETTING_GENERAL_REPORT"] = "Please report any issue at", - ["SETTING_GENERAL_DESC"] = "This first version will only allow you to get automatic tranq annouce messages\n".. - "I'm planning to add more features giving visual feedback about tranqshot rotation", + ["SETTING_GENERAL_DESC"] = "New : TranqRotate now has a window allowing hunters to track rotation status\n".. + "It still automatically announce tranq or miss and now whispers next and backup when you miss\n".. + "It doesn't sync rotation order between hunters yet, you have to manually set the correct groups and order.", --- Announces ["SETTING_ANNOUNCES"] = "通告", @@ -37,12 +39,27 @@ local L = { ["ANNOUNCES_MESSAGE_HEADER"] = "通告資訊", ["SUCCESS_MESSAGE_LABEL"] = "施放成功通告資訊", ["FAIL_MESSAGE_LABEL"] = "施放失敗通告資訊", + ["FAIL_WHISPER_LABEL"] = "Fail whisper message", ['DEFAULT_SUCCESS_ANNOUNCE_MESSAGE'] = "已對 %s 施放了寧神射擊!", ['DEFAULT_FAIL_ANNOUNCE_MESSAGE'] = "!!! 對 %s 寧神失敗!!!", + ['DEFAULT_FAIL_WHISPER_MESSAGE'] = "TRANQSHOT FAILED ! TRANQ NOW !", + + --- Rotation + ["LOCK_WINDOW"] = "Lock window", + ["LOCK_WINDOW_DESC"] = "Lock window", + ["HIDE_WINDOW_NOT_IN_RAID"] = "Hide the window when not in a raid", + ["HIDE_WINDOW_NOT_IN_RAID_DESC"] = "Hide the window when not in a raid", + ["WINDOW_LOCKED"] = "TranqRotate: Window locked", + ["WINDOW_UNLOCKED"] = "TranqRotate: Window unlocked", --- Profiles - ["SETTING_PROFILES"] = "配置" + ["SETTING_PROFILES"] = "配置", + + --- Raid broadcast messages + ["BROADCAST_HEADER_TEXT"] = "Hunter tranqshot setup", + ["BROADCAST_ROTATION_PREFIX"] = "Rotation", + ["BROADCAST_BACKUP_PREFIX"] = "Backup", } TranqRotate.L = L diff --git a/src/constants.lua b/src/constants.lua new file mode 100644 index 0000000..aad7eef --- /dev/null +++ b/src/constants.lua @@ -0,0 +1,19 @@ +local TranqRotate = select(2, ...) + +TranqRotate.colors = { + ['green'] = CreateColor(0.67, 0.83, 0.45), + ['darkGreen'] = CreateColor(0.1, 0.4, 0.1), + ['blue'] = CreateColor(0.3, 0.3, 0.7), + ['red'] = CreateColor(0.7, 0.3, 0.3), + ['gray'] = CreateColor(0.3, 0.3, 0.3), + ['purple'] = CreateColor(0.71,0.45,0.75), + ['white'] = CreateColor(1,1,1) +} + +TranqRotate.constants = { + ['hunterFrameHeight'] = 20, + ['hunterFrameSpacing'] = 4, + ['titleBarHeight'] = 18, + ['mainFrameWidth'] = 130, + ['rotationFramesBaseHeight'] = 20, +} diff --git a/src/defaults.lua b/src/defaults.lua new file mode 100644 index 0000000..fef3f86 --- /dev/null +++ b/src/defaults.lua @@ -0,0 +1,15 @@ +local L = TranqRotate.L + +function TranqRotate:LoadDefaults() + self.defaults = { + profile = { + enableAnnounces = true, + channelType = "YELL", + announceSuccessMessage = L["DEFAULT_SUCCESS_ANNOUNCE_MESSAGE"], + announceFailMessage = L["DEFAULT_FAIL_ANNOUNCE_MESSAGE"], + whisperFailMessage = L["DEFAULT_FAIL_WHISPER_MESSAGE"], + lock = false, + hideNotInRaid = false, + }, + } +end diff --git a/src/dragdrop.lua b/src/dragdrop.lua new file mode 100644 index 0000000..5b20f50 --- /dev/null +++ b/src/dragdrop.lua @@ -0,0 +1,183 @@ +local TranqRotate = select(2, ...) + +-- Enable drag & drop for all hunter frames +function TranqRotate:enableListSorting() + for key,hunter in pairs(TranqRotate.hunterTable) do + TranqRotate:enableHunterFrameDragging(hunter, true) + end +end + +-- Enable or disable drag & drop for the hunter frame +function TranqRotate:enableHunterFrameDragging(hunter, movable) + hunter.frame:EnableMouse(movable) + hunter.frame:SetMovable(movable) +end + +-- configure hunter frame drag behavior +function TranqRotate:configureHunterFrameDrag(hunter) + + hunter.frame:RegisterForDrag("LeftButton") + hunter.frame:SetClampedToScreen(true) + + hunter.frame:SetScript( + "OnDragStart", + function() + hunter.frame:StartMoving() + hunter.frame:SetFrameStrata("HIGH") + TranqRotate.mainFrame.rulerFrame:SetPoint('BOTTOMRIGHT', hunter.frame, 'TOPLEFT', 0, 0) + TranqRotate.mainFrame.dropHintFrame:Show() + TranqRotate.mainFrame.backupFrame:Show() + end + ) + + hunter.frame:SetScript( + "OnDragStop", + function() + hunter.frame:StopMovingOrSizing() + hunter.frame:SetFrameStrata(TranqRotate.mainFrame:GetFrameStrata()) + TranqRotate.mainFrame.dropHintFrame:Hide() + + if (#TranqRotate.rotationTables.backup < 1) then + TranqRotate.mainFrame.backupFrame:Hide() + end + + local group, position = TranqRotate:getDropPosition(TranqRotate.mainFrame.rulerFrame:GetHeight()) + TranqRotate:handleDrop(hunter, group, position) + + end + ) +end + +-- create and initialize the drop hint frame +function TranqRotate:createDropHintFrame() + + local hintFrame = CreateFrame("Frame", nil, TranqRotate.mainFrame.rotationFrame) + + hintFrame:SetPoint('TOP', TranqRotate.mainFrame.rotationFrame, 'TOP', 0, 0) + hintFrame:SetHeight(TranqRotate.constants.hunterFrameHeight) + hintFrame:SetWidth(TranqRotate.constants.mainFrameWidth - 10) + + hintFrame.texture = hintFrame:CreateTexture(nil, "BACKGROUND") + hintFrame.texture:SetColorTexture(TranqRotate.colors.white:GetRGB()) + hintFrame.texture:SetAlpha(0.7) + hintFrame.texture:SetPoint('LEFT') + hintFrame.texture:SetPoint('RIGHT') + hintFrame.texture:SetHeight(2) + + hintFrame:Hide() + + TranqRotate.mainFrame.dropHintFrame = hintFrame +end + +-- Create and initialize the 'ruler' frame. +-- It's height will be used as a ruler for position calculation +function TranqRotate:createRulerFrame() + + local rulerFrame = CreateFrame("Frame", nil, TranqRotate.mainFrame.rotationFrame) + TranqRotate.mainFrame.rulerFrame = rulerFrame + + rulerFrame:SetPoint('TOPLEFT', TranqRotate.mainFrame.rotationFrame, 'TOPLEFT', 0, 0) + + rulerFrame:SetScript( + "OnSizeChanged", + function (self, width, height) + TranqRotate:setDropHintPosition(self, width, height) + end + ) + +end + +-- Set the drop hint frame position to match dragged frame position +function TranqRotate:setDropHintPosition(self, width, height) + + local hunterFrameHeight = TranqRotate.constants.hunterFrameHeight + local hunterFrameSpacing = TranqRotate.constants.hunterFrameSpacing + local hintPosition = 0 + + local group, position = TranqRotate:getDropPosition(height) + + if (group == 'ROTATION') then + if (position == 0) then + hintPosition = -2 + else + hintPosition = (position) * (hunterFrameHeight + hunterFrameSpacing) - hunterFrameSpacing / 2; + end + else + hintPosition = TranqRotate.mainFrame.rotationFrame:GetHeight() + + if (position == 0) then + hintPosition = hintPosition - 2 + else + hintPosition = hintPosition + (position) * (hunterFrameHeight + hunterFrameSpacing) - hunterFrameSpacing / 2; + end + end + + TranqRotate.mainFrame.dropHintFrame:SetPoint('TOP', 0 , -hintPosition) +end + +-- Compute drop group and position from ruler height +function TranqRotate:getDropPosition(rulerHeight) + + local group = 'ROTATION' + local position = 0 + + local hunterFrameHeight = TranqRotate.constants.hunterFrameHeight + local hunterFrameSpacing = TranqRotate.constants.hunterFrameSpacing + + -- Dragged frame is above rotation frames + if (TranqRotate.mainFrame.rulerFrame:GetTop() > TranqRotate.mainFrame.rotationFrame:GetTop()) then + rulerHeight = 0 + end + + position = floor(rulerHeight / (hunterFrameHeight + hunterFrameSpacing)) + + -- Dragged frame is bellow rotation frame + if (rulerHeight > TranqRotate.mainFrame.rotationFrame:GetHeight()) then + + group = 'BACKUP' + + -- Removing rotation frame size from calculation, using it's height as base hintPosition offset + rulerHeight = rulerHeight - TranqRotate.mainFrame.rotationFrame:GetHeight() + + if (rulerHeight > TranqRotate.mainFrame.backupFrame:GetHeight()) then + -- Dragged frame is bellow backup frame + position = #TranqRotate.rotationTables.backup + else + position = floor(rulerHeight / (hunterFrameHeight + hunterFrameSpacing)) + end + end + + return group, position +end + +-- Compute the table final position from the drop position +function TranqRotate:handleDrop(hunter, group, position) + + local originTable = TranqRotate:getHunterRotationTable(hunter) + local originIndex = TranqRotate:getHunterIndex(hunter, originTable) + + local destinationTable = TranqRotate.rotationTables.rotation + local finalPosition = 1 + + if (group == "BACKUP") then + destinationTable = TranqRotate.rotationTables.backup + end + + if (destinationTable == originTable) then + + if (position == originIndex or position == originIndex - 1 ) then + finalPosition = originIndex + else + if (position > originIndex) then + finalPosition = position + else + finalPosition = position + 1 + end + end + + else + finalPosition = position + 1 + end + + TranqRotate:moveHunter(hunter, group, finalPosition) +end diff --git a/src/events.lua b/src/events.lua new file mode 100644 index 0000000..ade9f4e --- /dev/null +++ b/src/events.lua @@ -0,0 +1,89 @@ +local TranqRotate = select(2, ...) + +local TranqShot = GetSpellInfo(19801) +--local TranqShot = GetSpellInfo(14287) --Arcane shot for testing + +local eventFrame = CreateFrame("Frame") +eventFrame:RegisterEvent("PLAYER_LOGIN") +eventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") +eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE") +eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED") + +-- @todo: clean this +--PLAYER_ROLES_ASSIGNED + +eventFrame:SetScript( + "OnEvent", + function(self, event, ...) + if (event == "PLAYER_LOGIN") then + TranqRotate:init() + self:UnregisterEvent("PLAYER_LOGIN") + elseif (event == "PLAYER_TARGET_CHANGED") then + -- Ugly hack to initialize hunter list when player login right into raid + -- Raid members data is unreliable on PLAYER_LOGIN and PLAYER_ENTERING_WORLD events + TranqRotate:updateRaidStatus() + self:UnregisterEvent("PLAYER_TARGET_CHANGED") + else + TranqRotate[event](TranqRotate, ...) + end + end +) + +function TranqRotate:COMBAT_LOG_EVENT_UNFILTERED() + + local timestamp, event, _, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = CombatLogGetCurrentEventInfo() + local spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand = select(12, CombatLogGetCurrentEventInfo()) + + if (spellName == TranqShot) then + if (event == "SPELL_CAST_SUCCESS") then + TranqRotate:rotate(TranqRotate:getHunter(nil, sourceGUID), false) + if (sourceGUID == UnitGUID("player")) then + TranqRotate:sendAnnounceMessage(TranqRotate.db.profile.announceSuccessMessage, destName) + end + elseif (event == "SPELL_MISSED") then + TranqRotate:rotate(TranqRotate:getHunter(nil, sourceGUID), true) + if (sourceGUID == UnitGUID("player")) then + TranqRotate:sendAnnounceMessage(TranqRotate.db.profile.announceFailMessage, destName) + end + end + end +end + +function TranqRotate:GROUP_ROSTER_UPDATE() + TranqRotate:updateRaidStatus() + TranqRotate:updateDisplay() +end + +function TranqRotate:PLAYER_TARGET_CHANGED() + TranqRotate:updateRaidStatus() + self:UnregisterEvent("PLAYER_TARGET_CHANGED") +end + + +-- Register single unit events for a given hunter +function TranqRotate:registerUnitEvents(hunter) + + hunter.frame:RegisterUnitEvent("PARTY_MEMBER_DISABLE", hunter.name) + hunter.frame:RegisterUnitEvent("PARTY_MEMBER_ENABLE", hunter.name) + --hunter.frame:RegisterUnitEvent("UNIT_HEALTH_FREQUENT", hunter.name) -- Could be needed with UNIT_HEALTH throtle + hunter.frame:RegisterUnitEvent("UNIT_HEALTH", hunter.name) + hunter.frame:RegisterUnitEvent("UNIT_CONNECTION", hunter.name) + hunter.frame:RegisterUnitEvent("UNIT_FLAGS", hunter.name) + + hunter.frame:SetScript( + "OnEvent", + function(self, event, ...) + TranqRotate:updateHuntersStatus() + end + ) + +end + +-- Unregister single unit events for a given hunter +function TranqRotate:unregisterUnitEvents(hunter) + hunter.frame:UnregisterEvent("PARTY_MEMBER_DISABLE") + hunter.frame:UnregisterEvent("PARTY_MEMBER_ENABLE") + hunter.frame:UnregisterEvent("UNIT_HEALTH_FREQUENT") + hunter.frame:UnregisterEvent("UNIT_CONNECTION") + hunter.frame:UnregisterEvent("UNIT_FLAGS") +end diff --git a/src/frames.lua b/src/frames.lua new file mode 100644 index 0000000..b7fd5c3 --- /dev/null +++ b/src/frames.lua @@ -0,0 +1,159 @@ +local TranqRotate = select(2, ...) + +-- Create main window +function TranqRotate:createMainFrame() + TranqRotate.mainFrame = CreateFrame("Frame", 'mainFrame', UIParent) + TranqRotate.mainFrame:SetWidth(TranqRotate.constants.mainFrameWidth) + TranqRotate.mainFrame:SetHeight(TranqRotate.constants.rotationFramesBaseHeight * 2 + TranqRotate.constants.titleBarHeight) + TranqRotate.mainFrame:Show() + + TranqRotate.mainFrame:RegisterForDrag("LeftButton") + TranqRotate.mainFrame:SetClampedToScreen(true) + TranqRotate.mainFrame:SetScript("OnDragStart", function() TranqRotate.mainFrame:StartMoving() end) + + TranqRotate.mainFrame:SetScript( + "OnDragStop", + function() + local config = TranqRotate.db.profile + TranqRotate.mainFrame:StopMovingOrSizing() + + config.point = 'TOPLEFT' + config.y = TranqRotate.mainFrame:GetTop() + config.x = TranqRotate.mainFrame:GetLeft() + end + ) +end + +-- Create Title frame +function TranqRotate:createTitleFrame() + TranqRotate.mainFrame.titleFrame = CreateFrame("Frame", 'rotationFrame', TranqRotate.mainFrame) + TranqRotate.mainFrame.titleFrame:SetPoint('TOPLEFT') + TranqRotate.mainFrame.titleFrame:SetPoint('TOPRIGHT') + TranqRotate.mainFrame.titleFrame:SetHeight(TranqRotate.constants.titleBarHeight) + + TranqRotate.mainFrame.titleFrame.texture = TranqRotate.mainFrame.titleFrame:CreateTexture(nil, "BACKGROUND") + TranqRotate.mainFrame.titleFrame.texture:SetColorTexture(TranqRotate.colors.darkGreen:GetRGB()) + TranqRotate.mainFrame.titleFrame.texture:SetAllPoints() + + TranqRotate.mainFrame.titleFrame.text = TranqRotate.mainFrame.titleFrame:CreateFontString(nil, "ARTWORK") + TranqRotate.mainFrame.titleFrame.text:SetFont("Fonts\\ARIALN.ttf", 12) + TranqRotate.mainFrame.titleFrame.text:SetShadowColor(0,0,0,0.5) + TranqRotate.mainFrame.titleFrame.text:SetShadowOffset(1,-1) + TranqRotate.mainFrame.titleFrame.text:SetPoint("LEFT",5,0) + TranqRotate.mainFrame.titleFrame.text:SetText('TranqRotate') + TranqRotate.mainFrame.titleFrame.text:SetTextColor(1,1,1,1) +end + +-- Create title bar buttons +function TranqRotate:createButtons() + + local buttons = { + { + ['texture'] = 'Interface/Buttons/UI-Panel-MinimizeButton-Up', + ['callback'] = TranqRotate.toggleDisplay, + ['textCoord'] = {0.18, 0.8, 0.2, 0.8} + }, + { + ['texture'] = 'Interface/GossipFrame/BinderGossipIcon', + ['callback'] = TranqRotate.openSettings + }, + { + ['texture'] = 'Interface/Buttons/UI-RefreshButton', + ['callback'] = TranqRotate.resetRotation + }, + { + ['texture'] = 'Interface/Buttons/UI-GuildButton-MOTD-Up', + ['callback'] = TranqRotate.broadcastToRaid + }, + } + + local position = 5 + + for key, button in pairs(buttons) do + TranqRotate:createButton(position, button.texture, button.callback, button.textCoord) + position = position + 13 + end +end + +-- Create a single button in the title bar +function TranqRotate:createButton(position, texture, callback, textCoord) + + local button = CreateFrame("Button", nil, TranqRotate.mainFrame.titleFrame) + button:SetPoint('RIGHT', -position, 0) + button:SetWidth(10) + button:SetHeight(10) + + local normal = button:CreateTexture() + normal:SetTexture(texture) + normal:SetAllPoints() + button:SetNormalTexture(normal) + + local highlight = button:CreateTexture() + highlight:SetTexture(texture) + highlight:SetAllPoints() + button:SetHighlightTexture(highlight) + + if (textCoord) then + normal:SetTexCoord(unpack(textCoord)) + highlight:SetTexCoord(unpack(textCoord)) + end + + button:SetScript("OnClick", callback) +end + +-- Create rotation frame +function TranqRotate:createRotationFrame() + TranqRotate.mainFrame.rotationFrame = CreateFrame("Frame", 'rotationFrame', TranqRotate.mainFrame) + TranqRotate.mainFrame.rotationFrame:SetPoint('LEFT') + TranqRotate.mainFrame.rotationFrame:SetPoint('RIGHT') + TranqRotate.mainFrame.rotationFrame:SetPoint('TOP', 0, -TranqRotate.constants.titleBarHeight) + TranqRotate.mainFrame.rotationFrame:SetHeight(TranqRotate.constants.rotationFramesBaseHeight) + + TranqRotate.mainFrame.rotationFrame.texture = TranqRotate.mainFrame.rotationFrame:CreateTexture(nil, "BACKGROUND") + TranqRotate.mainFrame.rotationFrame.texture:SetColorTexture(0,0,0,0.5) + TranqRotate.mainFrame.rotationFrame.texture:SetAllPoints() +end + +-- Create backup frame +function TranqRotate:createBackupFrame() + -- Backup frame + TranqRotate.mainFrame.backupFrame = CreateFrame("Frame", 'backupFrame', TranqRotate.mainFrame) + TranqRotate.mainFrame.backupFrame:SetPoint('TOPLEFT', TranqRotate.mainFrame.rotationFrame, 'BOTTOMLEFT', 0, 0) + TranqRotate.mainFrame.backupFrame:SetPoint('TOPRIGHT', TranqRotate.mainFrame.rotationFrame, 'BOTTOMRIGHT', 0, 0) + TranqRotate.mainFrame.backupFrame:SetHeight(TranqRotate.constants.rotationFramesBaseHeight) + + -- Set Texture + TranqRotate.mainFrame.backupFrame.texture = TranqRotate.mainFrame.backupFrame:CreateTexture(nil, "BACKGROUND") + TranqRotate.mainFrame.backupFrame.texture:SetColorTexture(0,0,0,0.5) + TranqRotate.mainFrame.backupFrame.texture:SetAllPoints() + + -- Visual separator + TranqRotate.mainFrame.backupFrame.texture = TranqRotate.mainFrame.backupFrame:CreateTexture(nil, "BACKGROUND") + TranqRotate.mainFrame.backupFrame.texture:SetColorTexture(0.8,0.8,0.8,0.8) + TranqRotate.mainFrame.backupFrame.texture:SetHeight(1) + TranqRotate.mainFrame.backupFrame.texture:SetWidth(60) + TranqRotate.mainFrame.backupFrame.texture:SetPoint('TOP') +end + +-- Create single hunter frame +function TranqRotate:createHunterFrame(hunter, parentFrame) + hunter.frame = CreateFrame("Frame", nil, parentFrame) + hunter.frame:SetHeight(TranqRotate.constants.hunterFrameHeight) + + -- Set Texture + hunter.frame.texture = hunter.frame:CreateTexture(nil, "ARTWORK") + hunter.frame.texture:SetTexture("Interface\\AddOns\\TranqRotate\\textures\\steel.tga") + hunter.frame.texture:SetAllPoints() + + -- Set Text + hunter.frame.text = hunter.frame:CreateFontString(nil, "ARTWORK") + hunter.frame.text:SetFont("Fonts\\ARIALN.ttf", 12) + hunter.frame.text:SetPoint("LEFT",5,0) + hunter.frame.text:SetText(hunter.name) + + TranqRotate:configureHunterFrameDrag(hunter) + + if (TranqRotate.enableDrag) then + TranqRotate:enableHunterFrameDragging(hunter, true) + end +end \ No newline at end of file diff --git a/src/gui.lua b/src/gui.lua new file mode 100644 index 0000000..b56e76d --- /dev/null +++ b/src/gui.lua @@ -0,0 +1,143 @@ +local TranqRotate = select(2, ...) + +local L = TranqRotate.L + +-- Initialize GUI frames. Shouldn't be called more than once +function TranqRotate:initGui() + + TranqRotate:createMainFrame() + TranqRotate:createTitleFrame() + TranqRotate:createButtons() + TranqRotate:createRotationFrame() + TranqRotate:createBackupFrame() + + TranqRotate:drawHunterFrames() + TranqRotate:createDropHintFrame() + TranqRotate:createRulerFrame() + + TranqRotate.manuallyHiddenWhileInRaid = false + TranqRotate:updateDisplay() +end + +-- Show/Hide main window based on user settings +function TranqRotate:updateDisplay() + if (TranqRotate.db.profile.hideNotInRaid and not IsInRaid()) then + TranqRotate.mainFrame:Hide() + else + if (not TranqRotate.manuallyHiddenWhileInRaid) then + TranqRotate.mainFrame:Show() + end + end + + -- Reset manual hide during the raid when leaving it + if (not IsInRaid()) then + TranqRotate.manuallyHiddenWhileInRaid = false + end +end + +-- render / re-render hunter frames to reflect table changes. +function TranqRotate:drawHunterFrames() + + -- Different height to reduce spacing between both groups + TranqRotate.mainFrame:SetHeight(TranqRotate.constants.rotationFramesBaseHeight + TranqRotate.constants.titleBarHeight) + TranqRotate.mainFrame.rotationFrame:SetHeight(TranqRotate.constants.rotationFramesBaseHeight) + + TranqRotate:drawList(TranqRotate.rotationTables.rotation, TranqRotate.mainFrame.rotationFrame) + + if (#TranqRotate.rotationTables.backup > 0) then + TranqRotate.mainFrame:SetHeight(TranqRotate.mainFrame:GetHeight() + TranqRotate.constants.rotationFramesBaseHeight) + end + + TranqRotate.mainFrame.backupFrame:SetHeight(TranqRotate.constants.rotationFramesBaseHeight) + TranqRotate:drawList(TranqRotate.rotationTables.backup, TranqRotate.mainFrame.backupFrame) + +end + +-- Handle the render of a single hunter frames group +function TranqRotate:drawList(hunterList, parentFrame) + + local index = 1 + local hunterFrameHeight = TranqRotate.constants.hunterFrameHeight + local hunterFrameSpacing = TranqRotate.constants.hunterFrameSpacing + + if (#hunterList < 1 and parentFrame == TranqRotate.mainFrame.backupFrame) then + parentFrame:Hide() + else + parentFrame:Show() + end + + for key,hunter in pairs(hunterList) do + + -- Using existing frame if possible + if (hunter.frame == nil) then + TranqRotate:createHunterFrame(hunter, parentFrame) + else + hunter.frame:SetParent(parentFrame) + end + + hunter.frame:ClearAllPoints() + hunter.frame:SetPoint('LEFT', 10, 0) + hunter.frame:SetPoint('RIGHT', -10, 0) + + -- Setting top margin + local marginTop = 10 + (index - 1) * (hunterFrameHeight + hunterFrameSpacing) + hunter.frame:SetPoint('TOP', parentFrame, 'TOP', 0, -marginTop) + + -- Handling parent windows height increase + if (index == 1) then + parentFrame:SetHeight(parentFrame:GetHeight() + hunterFrameHeight) + TranqRotate.mainFrame:SetHeight(TranqRotate.mainFrame:GetHeight() + hunterFrameHeight) + else + parentFrame:SetHeight(parentFrame:GetHeight() + hunterFrameHeight + hunterFrameSpacing) + TranqRotate.mainFrame:SetHeight(TranqRotate.mainFrame:GetHeight() + hunterFrameHeight + hunterFrameSpacing) + end + + -- SetColor + setHunterFrameColor(hunter) + + hunter.frame:Show() + hunter.frame.hunter = hunter + + index = index + 1 + end +end + +-- Hide the hunter frame +function TranqRotate:hideHunter(hunter) + if (hunter.frame ~= nil) then + hunter.frame:Hide() + end +end + +-- Refresh a single hunter frame +function TranqRotate:refreshHunterFrame(hunter) + setHunterFrameColor(hunter) +end + +-- Set the hunter frame color regarding it's status +function setHunterFrameColor(hunter) + + local color = TranqRotate.colors.green + + if (hunter.offline) then + color = TranqRotate.colors.gray + elseif (not hunter.alive) then + color = TranqRotate.colors.red + elseif (hunter.nextTranq) then + color = TranqRotate.colors.purple + end + + hunter.frame.texture:SetVertexColor(color:GetRGB()) +end + +-- Lock/Unlock the mainFrame position +function TranqRotate:lock(lock) + TranqRotate.db.profile.lock = lock + TranqRotate:applySettings() + + if (lock) then + TranqRotate:printMessage(L['WINDOW_LOCKED']) + else + TranqRotate:printMessage(L['WINDOW_UNLOCKED']) + end +end diff --git a/src/rotation.lua b/src/rotation.lua new file mode 100644 index 0000000..9f000d2 --- /dev/null +++ b/src/rotation.lua @@ -0,0 +1,330 @@ +local TranqRotate = select(2, ...) + +-- Adds hunter to global table and one of the two rotation tables +function TranqRotate:registerHunter(hunterName) + + -- Initialize hunter 'object' + local hunter = {} + hunter.name = hunterName + hunter.GUID = UnitGUID(hunterName) + hunter.frame = nil + hunter.offline = false + hunter.alive = true + hunter.nextTranq = false + + -- Add to global list + table.insert(TranqRotate.hunterTable, hunter) + + -- Add to rotation or backup group depending on rotation group size + if (#TranqRotate.rotationTables.rotation > 2) then + table.insert(TranqRotate.rotationTables.backup, hunter) + else + table.insert(TranqRotate.rotationTables.rotation, hunter) + end + + TranqRotate:drawHunterFrames() + + return hunter +end + +-- Removes a hunter from all lists +function TranqRotate:removeHunter(deletedHunter) + + -- Clear from global list + for key, hunter in pairs(TranqRotate.hunterTable) do + if (hunter.name == deletedHunter.name) then + TranqRotate:hideHunter(hunter) + table.remove(TranqRotate.hunterTable, key) + break + end + end + + -- clear from rotation lists + for key, hunterTable in pairs(TranqRotate.rotationTables) do + for subkey, hunter in pairs(hunterTable) do + if (hunter.name == deletedHunter.name) then + table.remove(hunterTable, subkey) + end + end + end + + TranqRotate:drawHunterFrames() +end + +-- Update the rotation list once a tranq has been done. +-- The parameter is the hunter that used it's tranq (successfully or not) +function TranqRotate:rotate(lastHunter, fail) + + -- Default value to false + fail = fail or false + local nextHunter = TranqRotate:getNextRotationHunter(lastHunter) + + TranqRotate:setNextTranq(nextHunter) + local name, realm = UnitName("player") + + if (name == lastHunter.name and fail) then + SendChatMessage(TranqRotate.db.profile.whisperFailMessage, 'WHISPER', nil, nextHunter.name) + for key, backupHunter in pairs(TranqRotate.rotationTables.backup) do + SendChatMessage(TranqRotate.db.profile.whisperFailMessage, 'WHISPER', nil, backupHunter.name) + end + end +end + +-- Removes all nextTranq flags and set it true for next shooter +function TranqRotate:setNextTranq(nextHunter) + for key, hunter in pairs(TranqRotate.rotationTables.rotation) do + if (hunter.name == nextHunter.name) then + hunter.nextTranq = true + else + hunter.nextTranq = false + end + + TranqRotate:refreshHunterFrame(hunter) + end +end + +-- Find and returns the next hunter that will tranq base on last shooter +function TranqRotate:getNextRotationHunter(lastHunter) + + local rotationTable = TranqRotate.rotationTables.rotation + local nextHunter + local lastHunterIndex = 1 + local nextHunterIndex = 1 + + -- Finding last hunter index in rotation + for key, hunter in pairs(rotationTable) do + if (hunter.name == lastHunter.name) then + lastHunterIndex = key + break + end + end + + -- Search from last hunter index + for index = lastHunterIndex + 1 , #rotationTable, 1 do + local hunter = rotationTable[index] + if (hunter.alive and not hunter.offline) then + nextHunter = hunter + break + end + end + + -- Restart search from first index + if (nextHunter == nil) then + for index = 1 , lastHunterIndex, 1 do + local hunter = rotationTable[index] + if (hunter.alive and not hunter.offline) then + nextHunter = hunter + break + end + end + end + + return nextHunter +end + +-- Init/Reset rotation status, next tranq is the first hunter on the list +function TranqRotate:resetRotation() + + for key, hunter in pairs(TranqRotate.rotationTables.rotation) do + hunter.nextTranq = false + TranqRotate:refreshHunterFrame(hunter) + end + + -- @todo remove this if neutral reset is fine + --if (#TranqRotate.rotationTables.rotation > 0) then + -- local firstHunter = TranqRotate.rotationTables.rotation[1] + -- + -- firstHunter.nextTranq = true + -- TranqRotate:refreshHunterFrame(firstHunter) + --end + +end + +-- @todo: remove this | TEST FUNCTION - Manually rotate hunters for test purpose +function TranqRotate:testRotation() + + for key, hunter in pairs(TranqRotate.rotationTables.rotation) do + if (hunter.nextTranq) then + TranqRotate:rotate(hunter) + break + end + end +end + +-- Check if a hunter is already registered +function TranqRotate:isHunterRegistered(GUID) + + for key,hunter in pairs(TranqRotate.hunterTable) do + if (hunter.GUID == GUID) then + return true + end + end + + return false +end + +-- Return our hunter object from name or GUID +function TranqRotate:getHunter(name, GUID) + + for key,hunter in pairs(TranqRotate.hunterTable) do + if ((GUID ~= nil and hunter.GUID == GUID) or (name ~= nil and hunter.name == name)) then + return hunter + end + end + + return nil +end + +-- Iterate over hunter list and purge hunter that aren't in the group anymore +function TranqRotate:purgeHunterList() + + local change = false + + for key,hunter in pairs(TranqRotate.hunterTable) do + if (not UnitInParty(hunter.name)) then + TranqRotate:unregisterUnitEvents(hunter) + TranqRotate:removeHunter(hunter) + change = true + end + end + + if (change) then + TranqRotate:drawHunterFrames() + end + +end + +-- Iterate over all raid members to find hunters and update their status +function TranqRotate:updateRaidStatus() + + if (IsInRaid()) then + + local playerCount = GetNumGroupMembers() + + for index = 1, playerCount, 1 do + + local name, rank, subgroup, level, class, fileName, zone, online, isDead, role, isML = GetRaidRosterInfo(index) + + -- Players name might be nil at loading + if (name ~= nil) then + local GUID = UnitGUID(name) + local hunter + + if(select(2,UnitClass(name)) == 'HUNTER') then + + local registered = TranqRotate:isHunterRegistered(GUID) + + if (not registered) then + if (not InCombatLockdown()) then + hunter = TranqRotate:registerHunter(name) + TranqRotate:registerUnitEvents(hunter) + registered = true + end + else + hunter = TranqRotate:getHunter(nil, GUID) + end + + if (registered) then + hunter.offline = not online + hunter.alive = TranqRotate:isHunterAlive(hunter.name) + + TranqRotate:refreshHunterFrame(hunter) + end + end + + end + end + end + + TranqRotate:purgeHunterList() +end + +-- Update registered hunters status to reflect dead/offline players +function TranqRotate:updateHuntersStatus() + + for key,hunter in pairs(TranqRotate.hunterTable) do + + hunter.alive = TranqRotate:isHunterAlive(hunter.name) + hunter.offline = not UnitIsConnected(hunter.name) + + TranqRotate:refreshHunterFrame(hunter) + end +end + +function TranqRotate:isHunterAlive(name) + return UnitIsFeignDeath(name) or not UnitIsDeadOrGhost(name) +end + +-- Moves given hunter to the given position in the given group (ROTATION or BACKUP) +function TranqRotate:moveHunter(hunter, group, position) + + local originTable = TranqRotate:getHunterRotationTable(hunter) + local originIndex = TranqRotate:getHunterIndex(hunter, originTable) + + local destinationTable = TranqRotate.rotationTables.rotation + local finalIndex = position + + if (group == 'BACKUP') then + destinationTable = TranqRotate.rotationTables.backup + -- Remove nextTranq flag when moved to backup + hunter.nextTranq = false + end + + -- Setting originalIndex + local sameTableMove = originTable == destinationTable + + -- Defining finalIndex + if (sameTableMove) then + if (position > #destinationTable or position == 0) then + if (#destinationTable > 0) then + finalIndex = #destinationTable + else + finalIndex = 1 + end + end + else + if (position > #destinationTable + 1 or position == 0) then + if (#destinationTable > 0) then + finalIndex = #destinationTable + 1 + else + finalIndex = 1 + end + end + end + + if (sameTableMove) then + if (originIndex ~= finalIndex) then + table.remove(originTable, originIndex) + table.insert(originTable, finalIndex, hunter) + end + else + table.remove(originTable, originIndex) + table.insert(destinationTable, finalIndex, hunter) + end + + TranqRotate:drawHunterFrames() +end + +-- Find the table that contains given hunter (rotation or backup) +function TranqRotate:getHunterRotationTable(hunter) + if (TranqRotate:tableContains(TranqRotate.rotationTables.rotation, hunter)) then + return TranqRotate.rotationTables.rotation + end + if (TranqRotate:tableContains(TranqRotate.rotationTables.backup, hunter)) then + return TranqRotate.rotationTables.backup + end +end + +-- Returns a hunter's index in the given table +function TranqRotate:getHunterIndex(hunter, table) + local originIndex = 0 + + for key, loopHunter in pairs(table) do + if (hunter.name == loopHunter.name) then + originIndex = key + break + end + end + + return originIndex +end diff --git a/settings.lua b/src/settings.lua similarity index 78% rename from settings.lua rename to src/settings.lua index 8b45594..92ef0c1 100644 --- a/settings.lua +++ b/src/settings.lua @@ -13,6 +13,7 @@ function TranqRotate:CreateConfig() local function set(info, value) TranqRotate.db.profile[info[#info]] = value + TranqRotate:applySettings() end local options = { @@ -45,6 +46,27 @@ function TranqRotate:CreateConfig() width = "full", order = 3, }, + -- @todo : find a way to space widget properly + spacer = { + name = ' ', + type = "description", + width = "full", + order = 4, + }, + lock = { + name = L["LOCK_WINDOW"], + desc = L["LOCK_WINDOW_DESC"], + type = "toggle", + order = 5, + width = "double", + }, + hideNotInRaid = { + name = L["HIDE_WINDOW_NOT_IN_RAID"], + desc = L["HIDE_WINDOW_NOT_IN_RAID_DESC"], + type = "toggle", + order = 6, + width = "double", + } } }, announces = { @@ -104,8 +126,14 @@ function TranqRotate:CreateConfig() order = 22, width = "double", }, + whisperFailMessage = { + name = L["FAIL_WHISPER_LABEL"], + type = "input", + order = 23, + width = "double", + }, } - } + }, } } diff --git a/src/tranqRotate.lua b/src/tranqRotate.lua new file mode 100644 index 0000000..96f0cea --- /dev/null +++ b/src/tranqRotate.lua @@ -0,0 +1,169 @@ +TranqRotate = select(2, ...) + +local L = TranqRotate.L +TranqRotate.version = "1.1.0" + +-- Initialize addon - Shouldn't be call more than once +function TranqRotate:init() + + self:LoadDefaults() + + self.db = LibStub:GetLibrary("AceDB-3.0"):New("TranqRotateDb", self.defaults, true) + self.db.RegisterCallback(self, "OnProfileChanged", "ProfilesChanged") + self.db.RegisterCallback(self, "OnProfileCopied", "ProfilesChanged") + self.db.RegisterCallback(self, "OnProfileReset", "ProfilesChanged") + + self:CreateConfig() + + TranqRotate.hunterTable = {} + TranqRotate.rotationTables = { rotation = {}, backup = {} } + TranqRotate.enableDrag = true + + TranqRotate:initGui() + TranqRotate:updateRaidStatus() + TranqRotate:applySettings() + + TranqRotate:printMessage(L['LOADED_MESSAGE']) +end + +-- Apply setting on profile change +function TranqRotate:ProfilesChanged() + self.db:RegisterDefaults(self.defaults) + self:applySettings() +end + +-- Apply settings +function TranqRotate:applySettings() + + TranqRotate.mainFrame:ClearAllPoints() + + local config = TranqRotate.db.profile + if config.point then + TranqRotate.mainFrame:SetPoint(config.point, UIParent, 'BOTTOMLEFT', config.x, config.y) + else + TranqRotate.mainFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) + end + + TranqRotate:updateDisplay() + + TranqRotate.mainFrame:EnableMouse(not TranqRotate.db.profile.lock) + TranqRotate.mainFrame:SetMovable(not TranqRotate.db.profile.lock) +end + +-- Print wrapper, just in case +function TranqRotate:printMessage(msg) + print(msg) +end + +-- Send a message to a given channel +function TranqRotate:sendAnnounceMessage(message, destName) + if TranqRotate.db.profile.enableAnnounces then + local channelNumber + if TranqRotate.db.profile.channelType == "CHANNEL" then + channelNumber = GetChannelName(TranqRotate.db.profile.targetChannel) + end + SendChatMessage(string.format(message,destName), TranqRotate.db.profile.channelType, nil, channelNumber or TranqRotate.db.profile.targetChannel) + end +end + +SLASH_TRANQROTATE1 = "/tranq" +SLASH_TRANQROTATE2 = "/tranqrotate" +SlashCmdList["TRANQROTATE"] = function(msg) + local _, _, cmd, args = string.find(msg, "%s?(%w+)%s?(.*)") + + if (cmd == 'redraw') then -- @todo decide if this should be removed or not + TranqRotate:drawHunterFrames() + elseif (cmd == 'toggle') then -- @todo: remove this + TranqRotate:toggleDisplay() + elseif (cmd == 'init') then -- @todo: remove this + TranqRotate:resetRotation() + elseif (cmd == 'lock') then + TranqRotate:lock(true) + elseif (cmd == 'unlock') then + TranqRotate:lock(false) + elseif (cmd == 'rotate') then -- @todo decide if this should be removed or not + TranqRotate:testRotation() + elseif (cmd == 'raid') then -- @todo: (Maybe) remove once raid members are properly updated + TranqRotate:updateRaidStatus() + elseif (cmd == 'test') then -- @todo: remove this + TranqRotate:test() + elseif (cmd == 'settings') then -- @todo: remove this + TranqRotate:openSettings() + else + TranqRotate:printHelp() + end +end + +function TranqRotate:toggleDisplay() + if (TranqRotate.mainFrame:IsShown()) then + TranqRotate.mainFrame:Hide() + + if (IsInRaid()) then + TranqRotate.manuallyHiddenWhileInRaid = true + end + + TranqRotate:printMessage(L['TRANQ_WINDOW_HIDDEN']) + else + TranqRotate.mainFrame:Show() + end +end + +-- @todo: remove this +function TranqRotate:test() + TranqRotate:printMessage('test') +-- TranqRotate:enableListSorting() +end + +-- Open ace settings +function TranqRotate:openSettings() + local AceConfigDialog = LibStub("AceConfigDialog-3.0") + AceConfigDialog:Open("TranqRotate") +end + +-- Sends rotation setup to raid channel +function TranqRotate:broadcastToRaid() + local channel = 'RAID' + + if (IsInRaid()) then + + SendChatMessage('--- ' .. L['BROADCAST_HEADER_TEXT'] .. ' ---', channel) + SendChatMessage( + TranqRotate:buildGroupMessage(L['BROADCAST_ROTATION_PREFIX'] .. ' : ', TranqRotate.rotationTables.rotation), + channel + ) + + if (#TranqRotate.rotationTables.backup > 0) then + SendChatMessage( + TranqRotate:buildGroupMessage(L['BROADCAST_BACKUP_PREFIX'] .. ' : ', TranqRotate.rotationTables.backup), + channel + ) + end + end + +end + +-- Serialize hunters names of a given rotation group +function TranqRotate:buildGroupMessage(prefix, rotationTable) + local hunters = {} + + for key, hunt in pairs(rotationTable) do + table.insert(hunters, hunt.name) + end + + return prefix .. table.concat(hunters, ', ') +end + +-- Print command options to chat +function TranqRotate:printHelp() + local spacing = ' ' + TranqRotate:printMessage(TranqRotate:colorText('/tranqrotate') .. ' commands options :') + TranqRotate:printMessage(spacing .. TranqRotate:colorText('toggle') .. ' : Show/Hide the main window') + TranqRotate:printMessage(spacing .. TranqRotate:colorText('lock') .. ' : Lock the main window position') + TranqRotate:printMessage(spacing .. TranqRotate:colorText('unlock') .. ' : Unlock the main window position') + TranqRotate:printMessage(spacing .. TranqRotate:colorText('settings') .. ' : Open TranqRotate settings') +end + +-- Adds color to given text +function TranqRotate:colorText(text) + return '|cffffbf00' .. text .. '|r' +end diff --git a/src/utils.lua b/src/utils.lua new file mode 100644 index 0000000..f23ca2f --- /dev/null +++ b/src/utils.lua @@ -0,0 +1,13 @@ +local TranqRotate = select(2, ...) + +-- Check if a table contains the given element +function TranqRotate:tableContains(table, element) + + for _, value in pairs(table) do + if value == element then + return true + end + end + + return false +end \ No newline at end of file diff --git a/textures/steel.tga b/textures/steel.tga new file mode 100644 index 0000000..3f8aee4 Binary files /dev/null and b/textures/steel.tga differ