diff --git a/.gitignore b/.gitignore index 233aaff..3064ddc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea libs +copy.sh \ No newline at end of file diff --git a/README.md b/README.md index 5ec255b..30e809a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # TranqRotate This addon is meant to help hunters to setup tranqshot rotation and give them real time visual feedback about it. + It also allow non-hunter raid leaders to easily manage, report and watch the tranq rotation live. -Even if none of your hunter use the addon! + +This addon will work even if you are the only one using it in your raid. (With some combat log range limitation, see bellow) ![Screenshot](docs/screenshots/screenshot.png "ui") ![Screenshot](docs/screenshots/drag.gif "ui") ![Screenshot](docs/screenshots/rotation.gif "ui") @@ -16,17 +18,20 @@ Please report any issue using github issues : https://github.com/Slivo-fr/TranqR - 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 +- Display offline and dead status on hunters frames - Allow player to re-order players between two groups : main rotation and backup - Synchronize rotation order between player using the addon -- Whisper backup hunters (if there is backup) or next rotation hunter if you miss your tranqshot +- Allow player to broadcast the configured rotation and backup group to the raid - Provide a real time visual feedback about the rotation status, even if no one else use the addon in your raid - Synchronize tranqshot casts to other player using the addon -- Allow player to broadcast the configured rotation and backup group to the raid -- Display offline and dead status on hunters frames +- Whisper backup hunters (if there is backup) or next rotation hunter if you miss your tranqshot - Test mode out of raid using arcane shot - Play a sound when you are next on rotation - Show an alert and play a sound when you need to use your tranqshot - Display the tranq cooldown of each hunter +- Display the frenzy cooldown of each boss +- Optional automatic backup call when incapacitated +- Optional automatic timed backup call ## Usage @@ -52,11 +57,9 @@ It will whisper all backup hunters the fail message. Here is a list of feature I want to implement at some point, no specific order is decided yet. -- Adds RL/Raid assist handling to restrict rotation groups changes - 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 -- Frenzy cooldown/duration progress bar ## Download diff --git a/TranqRotate.toc b/TranqRotate.toc index 6175cb8..ade9dd3 100644 --- a/TranqRotate.toc +++ b/TranqRotate.toc @@ -1,8 +1,8 @@ ## Interface: 11305 -## Title: TranqRotate |cff00aa001.4.0|r +## Title: TranqRotate |cff00aa001.5.0|r ## Notes: A tranqshot rotation assistant ## Author: Slivo -## Version: 1.4.0 +## Version: 1.5.0 ## SavedVariables: TranqRotateDb ## OptionalDeps: Ace3 @@ -33,4 +33,5 @@ src\dragdrop.lua src\comms.lua src\defaults.lua src\settings.lua -src\utils.lua \ No newline at end of file +src\utils.lua +src\debuff.lua diff --git a/changelog.md b/changelog.md index 4aca619..2b77202 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,15 @@ ## TranqRotate Changelog -#### v1.3.0 +#### v1.5.0 + +- Restricted ordering tranq rotation to raid leader, raid assists and hunters +- Added a frenzy cooldown progress bar under the title bar +- Added configurable optional timed backup alert +- Added configurable optional incapacitated backup alert +- Fix font for koKR player names +- Fix main window showing up when login in while already in a raid with option to mask window enabled + +#### v1.4.0 - TranqRotate now play a sound when you need to tranq (You can disable it in the options) - Added an option to not show up the window each time you join a raid diff --git a/docs/screenshots/settings-general.png b/docs/screenshots/settings-general.png index 8efee0e..1091150 100644 Binary files a/docs/screenshots/settings-general.png and b/docs/screenshots/settings-general.png differ diff --git a/docs/screenshots/settings-sounds.png b/docs/screenshots/settings-sounds.png index 7ff15e1..7b331b8 100644 Binary files a/docs/screenshots/settings-sounds.png and b/docs/screenshots/settings-sounds.png differ diff --git a/locales/enUS.lua b/locales/enUS.lua index 5de54a7..5e677f3 100644 --- a/locales/enUS.lua +++ b/locales/enUS.lua @@ -29,6 +29,18 @@ local L = { ["ARCANE_SHOT_TESTING_ENABLED"] = "Arcane shot testing mode enabled for 10 minutes", ["ARCANE_SHOT_TESTING_DISABLED"] = "Arcane shot testing mode disabled", + ["FEATURES_HEADER"] = "Optionals features", + ["DISPLAY_BOSS_FRENZY_COOLDOWN"] = "Display frenzy cooldown progress bar", + ["DISPLAY_BOSS_FRENZY_COOLDOWN_DESC"] = "A thin progress bar just under the title bar will show the progress", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED"] = "Enable automatic backup alert when incapacitated", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED_DESC"] = "TranqRotate will check for your debuffs when you should actually tranq and will call for backup if you are incapacitated for longer than the defined delay", + ["INCAPACITATED_DELAY_THRESHOLD"] = "Incapacitated alert threshold", + ["INCAPACITATED_DELAY_THRESHOLD_DESC"] = "If you are incapacitated for longer than the configured delay, TranqRotate will automatically call for backup", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT"] = "Enable timed automatic backup alert", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT_DESC"] = "TranqRotate will call for backup if the boss is on frenzy for a defined delay and you should have been tranqing it", + ["TIMED_DELAY_THRESHOLD"] = "Timed alert threshold", + ["TIMED_DELAY_THRESHOLD_DESC"] = "TranqRotate will automatically call for backup if you do not tranq within the configured threshold", + --- Announces ["SETTING_ANNOUNCES"] = "Announces", ["ENABLE_ANNOUNCES"] = "Enable announces", @@ -54,10 +66,12 @@ local L = { ["SUCCESS_MESSAGE_LABEL"] = "Successful announce message", ["FAIL_MESSAGE_LABEL"] = "Fail announce message", ["FAIL_WHISPER_LABEL"] = "Fail whisper message", + ["UNABLE_TO_TRANQ_MESSAGE_LABEL"] = "Message whispered when you cannot tranq or call for backup", ['DEFAULT_SUCCESS_ANNOUNCE_MESSAGE'] = "Tranqshot done on %s", ['DEFAULT_FAIL_ANNOUNCE_MESSAGE'] = "!!! TRANQSHOT FAILED ON %s !!!", - ['DEFAULT_FAIL_WHISPER_MESSAGE'] = "TRANQSHOT FAILED ! TRANQ NOW !", + ['DEFAULT_FAIL_WHISPER_MESSAGE'] = "TRANQSHOT MISSED ! TRANQ NOW !", + ['DEFAULT_UNABLE_TO_TRANQ_MESSAGE'] = "I'M UNABLE TO TRANQ ! TRANQ NOW !", ['TRANQ_NOW_LOCAL_ALERT_MESSAGE'] = "USE TRANQSHOT NOW !", diff --git a/locales/frFR.lua b/locales/frFR.lua index ae17d44..abaed86 100644 --- a/locales/frFR.lua +++ b/locales/frFR.lua @@ -31,6 +31,18 @@ local L = { ["ARCANE_SHOT_TESTING_ENABLED"] = "Test mode activé pour 10 minutes", ["ARCANE_SHOT_TESTING_DISABLED"] = "Test mode désactivé", + ["FEATURES_HEADER"] = "Fonctionnalités optionnelles", + ["DISPLAY_BOSS_FRENZY_COOLDOWN"] = "Afficher le cooldown de frénésie des boss", + ["DISPLAY_BOSS_FRENZY_COOLDOWN_DESC"] = "Une fine barre de progression, juste sous la barre de titre, indiquera le cooldown de la frénésie", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED"] = "Activer l'alerte automatique en cas d'incapacité", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED_DESC"] = "TranqRotate alertera automatiquement le backup si un debuff vous empeche de tranq pendant un temps supérieur au délai configuré", + ["INCAPACITATED_DELAY_THRESHOLD"] = "Délai pour l'alerte d'incapacité", + ["INCAPACITATED_DELAY_THRESHOLD_DESC"] = "Si un débuff vous empêche de tranq pour un temps supérieur à celui configuré au moment ou vous devez tranq, une alerte est envoyée au backup", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT"] = "Activer l'alerte automatique chronométrée ", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT_DESC"] = "TranqRotate enverra automatiquement une alerte au backup si vous ne tirez pas votre tir tranquilisant avant le délai configuré", + ["TIMED_DELAY_THRESHOLD"] = "Délai pour l'alerte chronométrée", + ["TIMED_DELAY_THRESHOLD_DESC"] = "Si vous ne tirez pas votre tir tranquilisant avant ce délai, une alerte sera envoyée au backup", + --- Announces ["SETTING_ANNOUNCES"] = "Annonces", ["ENABLE_ANNOUNCES"] = "Activer les annonces", @@ -56,10 +68,12 @@ local L = { ["SUCCESS_MESSAGE_LABEL"] = "Message de réussite", ["FAIL_MESSAGE_LABEL"] = "Message d'échec", ["FAIL_WHISPER_LABEL"] = "Message d'échec chuchoté", + ["UNABLE_TO_TRANQ_MESSAGE_LABEL"] = "Message chuchoté quand vous ne pouvez pas tranq ou que vous demandez un backup", ['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 !", + ['DEFAULT_UNABLE_TO_TRANQ_MESSAGE'] = "JE NE PEUX PAS TRANQ ! TRANQ MAINTENANT !", ['TRANQ_NOW_LOCAL_ALERT_MESSAGE'] = "TRANQ MAINTENANT !", diff --git a/locales/ruRU.lua b/locales/ruRU.lua index 19fc984..b38a288 100644 --- a/locales/ruRU.lua +++ b/locales/ruRU.lua @@ -31,6 +31,18 @@ local L = { ["ARCANE_SHOT_TESTING_ENABLED"] = "Тестовый режим включен на 10 минут", ["ARCANE_SHOT_TESTING_DISABLED"] = "Тестовый режим выключен", + ["FEATURES_HEADER"] = "Optionals features", + ["DISPLAY_BOSS_FRENZY_COOLDOWN"] = "Display frenzy cooldown progress bar", + ["DISPLAY_BOSS_FRENZY_COOLDOWN_DESC"] = "A thin progress bar just under the title bar will show the progress", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED"] = "Enable automatic backup alert when incapacitated", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED_DESC"] = "TranqRotate will check for your debuffs when you should actually tranq and will call for backup if you are incapacitated for longer than the defined delay", + ["INCAPACITATED_DELAY_THRESHOLD"] = "Incapacitated alert threshold", + ["INCAPACITATED_DELAY_THRESHOLD_DESC"] = "If you are incapacitated for longer than the configured delay, TranqRotate will automatically call for backup", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT"] = "Enable timed automatic backup alert", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT_DESC"] = "TranqRotate will call for backup if the boss is on frenzy for a defined delay and you should have been tranqing it", + ["TIMED_DELAY_THRESHOLD"] = "Timed alert threshold", + ["TIMED_DELAY_THRESHOLD_DESC"] = "TranqRotate will automatically call for backup if you do not tranq within the configured threshold", + --- Announces ["SETTING_ANNOUNCES"] = "Оповещения", ["ENABLE_ANNOUNCES"] = "Включить оповещения", @@ -56,10 +68,12 @@ local L = { ["SUCCESS_MESSAGE_LABEL"] = "При успехе сообщить", ["FAIL_MESSAGE_LABEL"] = "При промахе сообщить", ["FAIL_WHISPER_LABEL"] = "При промахе шепнуть запасным", + ["UNABLE_TO_TRANQ_MESSAGE_LABEL"] = "Message whispered when you cannot tranq or call for backup", ['DEFAULT_SUCCESS_ANNOUNCE_MESSAGE'] = "Усмиряющий выстрел в %s", ['DEFAULT_FAIL_ANNOUNCE_MESSAGE'] = "!!! Усмиряющий выстрел промах в %s !!!", ['DEFAULT_FAIL_WHISPER_MESSAGE'] = "!!! Усмиряющий выстрел промах !!! ! СТРЕЛЯЙ СЕЙЧАС !", + ['DEFAULT_UNABLE_TO_TRANQ_MESSAGE'] = "I'M UNABLE TO TRANQ ! TRANQ NOW !", ['TRANQ_NOW_LOCAL_ALERT_MESSAGE'] = "СТРЕЛЯЙ СЕЙЧАС !", diff --git a/locales/zhCN.lua b/locales/zhCN.lua index ccbeba9..78004e6 100644 --- a/locales/zhCN.lua +++ b/locales/zhCN.lua @@ -31,6 +31,18 @@ local L = { ["ARCANE_SHOT_TESTING_ENABLED"] = "奥术射击测试模式已启用, 持续10分钟", ["ARCANE_SHOT_TESTING_DISABLED"] = "奥术射击测试模式已禁用", + ["FEATURES_HEADER"] = "可选功能", + ["DISPLAY_BOSS_FRENZY_COOLDOWN"] = "显示激怒冷却进度条", + ["DISPLAY_BOSS_FRENZY_COOLDOWN_DESC"] = "标题栏下方显示一个细进度条", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED"] = "当无法工作时,启用自动替补通告", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED_DESC"] = "TranqRotate会检查你的Debuff,如果轮到你宁神时,你瘫痪时间超过定义的延迟时间,则会要求替补", + ["INCAPACITATED_DELAY_THRESHOLD"] = "瘫痪通告阀值", + ["INCAPACITATED_DELAY_THRESHOLD_DESC"] = "如果您瘫痪时间超过配置的延迟时间,TranqRotate将自动调用替补", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT"] = "启用定时自动替补通告", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT_DESC"] = "如果BOSS在规定的延迟时间内激怒,而你已经宁神了,那么TranqRotate会要求替补", + ["TIMED_DELAY_THRESHOLD"] = "定时通告阈值", + ["TIMED_DELAY_THRESHOLD_DESC"] = "如果你没有在配置的阈值内进行宁神, TranqRotate将自动调用替补", + --- Announces ["SETTING_ANNOUNCES"] = "通告", ["ENABLE_ANNOUNCES"] = "启用通告", @@ -56,10 +68,12 @@ local L = { ["SUCCESS_MESSAGE_LABEL"] = "施放成功通告信息", ["FAIL_MESSAGE_LABEL"] = "施放失败通告信息", ["FAIL_WHISPER_LABEL"] = "施放失败私聊信息", + ["UNABLE_TO_TRANQ_MESSAGE_LABEL"] = "Message whispered when you cannot tranq or call for backup", ['DEFAULT_SUCCESS_ANNOUNCE_MESSAGE'] = "已对 %s 施放了宁神射击!", ['DEFAULT_FAIL_ANNOUNCE_MESSAGE'] = "!!! 对 %s 宁神失败!!!", ['DEFAULT_FAIL_WHISPER_MESSAGE'] = "宁神失败 !! 赶紧补宁神!!", + ['DEFAULT_UNABLE_TO_TRANQ_MESSAGE'] = "I'M UNABLE TO TRANQ ! TRANQ NOW !", ['TRANQ_NOW_LOCAL_ALERT_MESSAGE'] = "立即使用宁神 !!", diff --git a/locales/zhTW.lua b/locales/zhTW.lua index 6a67f13..e04f62c 100644 --- a/locales/zhTW.lua +++ b/locales/zhTW.lua @@ -31,6 +31,18 @@ local L = { ["ARCANE_SHOT_TESTING_ENABLED"] = "奧術射擊測試模式已啟用, 持續10分鐘", ["ARCANE_SHOT_TESTING_DISABLED"] = "奧術射擊測試模式已禁用", + ["FEATURES_HEADER"] = "Optionals features", + ["DISPLAY_BOSS_FRENZY_COOLDOWN"] = "Display frenzy cooldown progress bar", + ["DISPLAY_BOSS_FRENZY_COOLDOWN_DESC"] = "A thin progress bar just under the title bar will show the progress", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED"] = "Enable automatic backup alert when incapacitated", + ["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED_DESC"] = "TranqRotate will check for your debuffs when you should actually tranq and will call for backup if you are incapacitated for longer than the defined delay", + ["INCAPACITATED_DELAY_THRESHOLD"] = "Incapacitated alert threshold", + ["INCAPACITATED_DELAY_THRESHOLD_DESC"] = "If you are incapacitated for longer than the configured delay, TranqRotate will automatically call for backup", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT"] = "Enable timed automatic backup alert", + ["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT_DESC"] = "TranqRotate will call for backup if the boss is on frenzy for a defined delay and you should have been tranqing it", + ["TIMED_DELAY_THRESHOLD"] = "Timed alert threshold", + ["TIMED_DELAY_THRESHOLD_DESC"] = "TranqRotate will automatically call for backup if you do not tranq within the configured threshold", + --- Announces ["SETTING_ANNOUNCES"] = "通告", ["ENABLE_ANNOUNCES"] = "啟用通告", @@ -56,10 +68,12 @@ local L = { ["SUCCESS_MESSAGE_LABEL"] = "施放成功通告資訊", ["FAIL_MESSAGE_LABEL"] = "施放失敗通告資訊", ["FAIL_WHISPER_LABEL"] = "施放失敗私聊資訊", + ["UNABLE_TO_TRANQ_MESSAGE_LABEL"] = "Message whispered when you cannot tranq or call for backup", ['DEFAULT_SUCCESS_ANNOUNCE_MESSAGE'] = "已對 %s 施放了寧神射擊!", ['DEFAULT_FAIL_ANNOUNCE_MESSAGE'] = "!!! 對 %s 寧神失敗!!!", ['DEFAULT_FAIL_WHISPER_MESSAGE'] = "寧神失敗 !! 趕緊補寧神!!", + ['DEFAULT_UNABLE_TO_TRANQ_MESSAGE'] = "I'M UNABLE TO TRANQ ! TRANQ NOW !", ['TRANQ_NOW_LOCAL_ALERT_MESSAGE'] = "立即使用寧神 !!", diff --git a/src/comms.lua b/src/comms.lua index 6a8530b..45e8218 100644 --- a/src/comms.lua +++ b/src/comms.lua @@ -26,6 +26,8 @@ function TranqRotate.OnCommReceived(prefix, data, channel, sender) TranqRotate:receiveSyncOrder(prefix, message, channel, sender) elseif (message.type == TranqRotate.constants.commsTypes.syncRequest) then TranqRotate:receiveSyncRequest(prefix, message, channel, sender) + elseif (message.type == TranqRotate.constants.commsTypes.backupRequest) then + TranqRotate:receiveBackupRequest(prefix, message, channel, sender) end end end @@ -105,6 +107,18 @@ function TranqRotate:sendSyncOrderRequest() TranqRotate:sendRaidAddonMessage(message) end +-- Broadcast a request for the current rotation configuration +function TranqRotate:sendBackupRequest(name) + + TranqRotate:printPrefixedMessage('Sending backup request to ' .. name) + + local message = { + ['type'] = TranqRotate.constants.commsTypes.backupRequest, + } + + TranqRotate:sendWhisperAddonMessage(message, name) +end + ----------------------------------------------------------------------------------------------------------------------- -- INPUT ----------------------------------------------------------------------------------------------------------------------- @@ -138,3 +152,9 @@ end function TranqRotate:receiveSyncRequest(prefix, data, channel, sender) TranqRotate:sendSyncOrder(true, sender) end + +-- Received a backup request +function TranqRotate:receiveBackupRequest(prefix, message, channel, sender) + TranqRotate:printPrefixedMessage(sender .. ' asked for backup !') + TranqRotate:throwTranqAlert() +end diff --git a/src/constants.lua b/src/constants.lua index 6daf678..67f2ee6 100644 --- a/src/constants.lua +++ b/src/constants.lua @@ -24,6 +24,7 @@ TranqRotate.constants = { ['tranqshotDone'] = 'tranqshot-done', ['syncOrder'] = 'sync-order', ['syncRequest'] = 'sync-request', + ['backupRequest'] = 'backup-request', }, ['printPrefix'] = 'TranqRotate - ', @@ -51,10 +52,31 @@ TranqRotate.constants = { }, ['bosses'] = { - [11982] = 19451, -- magmadar - [11981] = 23342, -- flamegor - [14020] = 23342, -- chromaggus - [15509] = 19451, -- huhuran --- [15932] = 19451, -- gluth - } + [11982] = { -- magmadar + ['frenzy'] = 19451, + ['cooldown'] = 18, + }, + [11981] = { -- flamegor + ['frenzy'] = 23342, + ['cooldown'] = 9, + }, + [14020] = { -- chromaggus + ['frenzy'] = 23342, + ['cooldown'] = 16, + }, + [15509] = { -- huhuran + ['frenzy'] = 19451, + ['cooldown'] = 13, + }, + [15932] = { -- gluth + ['frenzy'] = 19451, + ['cooldown'] = 10, + }, + }, + + ["incapacitatingDebuffs"] = { + 19408, -- Magmadar fear + 23171, -- Chromaggus Bronze affliction stun + 23311, -- Chromaggus Time lapse + }, } diff --git a/src/debuff.lua b/src/debuff.lua new file mode 100644 index 0000000..827a3d8 --- /dev/null +++ b/src/debuff.lua @@ -0,0 +1,21 @@ +local TranqRotate = select(2, ...) + +-- Checks if player is incapacitated by a debuff for too long +function TranqRotate:isPlayedIncapacitatedByDebuff() + + for i, debuffId in ipairs(TranqRotate.constants.incapacitatingDebuffs) do + + local name, _, _, _, _, expirationTime, _, _, _, spellId, _, isBossDebuff + = AuraUtil.FindAuraByName(GetSpellInfo(debuffId), "player") + + if (name) then + print(name, expirationTime - GetTime(), spellId, isBossDebuff) + + if (expirationTime - GetTime() > TranqRotate.constants.incapacitatedDelay) then + return true + end + end + end + + return false +end diff --git a/src/defaults.lua b/src/defaults.lua index c256467..65bb363 100644 --- a/src/defaults.lua +++ b/src/defaults.lua @@ -10,6 +10,7 @@ function TranqRotate:LoadDefaults() announceSuccessMessage = L["DEFAULT_SUCCESS_ANNOUNCE_MESSAGE"], announceFailMessage = L["DEFAULT_FAIL_ANNOUNCE_MESSAGE"], whisperFailMessage = L["DEFAULT_FAIL_WHISPER_MESSAGE"], + unableToTranqMessage = L["DEFAULT_UNABLE_TO_TRANQ_MESSAGE"], lock = false, hideNotInRaid = false, enableNextToTranqSound = true, @@ -17,6 +18,11 @@ function TranqRotate:LoadDefaults() tranqNowSound = 'alarm1', doNotShowWindowOnRaidJoin = false, showWindowWhenTargetingBoss = false, + showFrenzyCooldownProgress = true, + enableIncapacitatedBackupAlert = true, + incapacitatedDelay = 1.5, + enableTimedBackupAlertValue = true, + timedBackupAlertValueDelay = 1.5, }, } end diff --git a/src/dragdrop.lua b/src/dragdrop.lua index 0e303a4..69be684 100644 --- a/src/dragdrop.lua +++ b/src/dragdrop.lua @@ -1,16 +1,16 @@ local TranqRotate = select(2, ...) -- Enable drag & drop for all hunter frames -function TranqRotate:enableListSorting() +function TranqRotate:toggleListSorting(allowSorting) for key,hunter in pairs(TranqRotate.hunterTable) do - TranqRotate:enableHunterFrameDragging(hunter, true) + TranqRotate:toggleHunterFrameDragging(hunter, allowSorting) end end -- Enable or disable drag & drop for the hunter frame -function TranqRotate:enableHunterFrameDragging(hunter, movable) - hunter.frame:EnableMouse(movable) - hunter.frame:SetMovable(movable) +function TranqRotate:toggleHunterFrameDragging(hunter, allowSorting) + hunter.frame:EnableMouse(allowSorting) + hunter.frame:SetMovable(allowSorting) end -- configure hunter frame drag behavior @@ -181,3 +181,8 @@ function TranqRotate:handleDrop(hunter, group, position) TranqRotate:moveHunter(hunter, group, finalPosition) end + +-- Update drag and drop status to match player status +function TranqRotate:updateDragAndDrop() + TranqRotate:toggleListSorting(TranqRotate:isPlayerAllowedToSortHunterList()) +end diff --git a/src/events.lua b/src/events.lua index b8adfae..abc6594 100644 --- a/src/events.lua +++ b/src/events.lua @@ -29,7 +29,7 @@ eventFrame:SetScript( function TranqRotate:COMBAT_LOG_EVENT_UNFILTERED() - -- @todo : Improve this with register / unregister event to save ressources + -- @todo : Improve this with register / unregister event to save resources -- Avoid parsing combat log when not able to use it if not TranqRotate.raidInitialized then return end -- Avoid parsing combat log when outside instance if test mode isn't enabled @@ -54,10 +54,22 @@ function TranqRotate:COMBAT_LOG_EVENT_UNFILTERED() TranqRotate:sendAnnounceMessage(TranqRotate.db.profile.announceFailMessage, destName) end end - elseif (event == "SPELL_AURA_APPLIED" and TranqRotate:isBossFrenzy(spellName, sourceGUID) and TranqRotate:isPlayerNextTranq()) then - TranqRotate:throwTranqAlert() + elseif (event == "SPELL_AURA_APPLIED" and TranqRotate:isBossFrenzy(spellName, sourceGUID)) then + TranqRotate.frenzy = true + if (TranqRotate:isPlayerNextTranq()) then + TranqRotate:throwTranqAlert() + + if (TranqRotate.db.profile.enableIncapacitatedBackupAlert and TranqRotate:isPlayedIncapacitatedByDebuff()) then + TranqRotate:alertBackup(TranqRotate.db.profile.unableToTranqMessage) + end + end + if(TranqRotate.db.profile.showFrenzyCooldownProgress) then + local type, id = TranqRotate:getIdFromGuid(sourceGUID) + TranqRotate:startBossFrenzyCooldown(TranqRotate.constants.bosses[id].cooldown) + end elseif event == "UNIT_DIED" and TranqRotate:isTranqableBoss(destGUID) then TranqRotate:resetRotation() + TranqRotate.mainFrame.frenzyFrame:Hide() end end @@ -105,3 +117,14 @@ function TranqRotate:unregisterUnitEvents(hunter) hunter.frame:UnregisterEvent("UNIT_CONNECTION") hunter.frame:UnregisterEvent("UNIT_FLAGS") end + +-- Handle timed alert for non tranqed frenzy +function TranqRotate:handleTimedAlert() + if (TranqRotate.db.profile.enableTimedBackupAlertValue) then + C_Timer.After(TranqRotate.db.profile.timedBackupAlertValueDelay, function() + if (TranqRotate.frenzy and TranqRotate:isPlayerNextTranq()) then + TranqRotate:alertBackup(TranqRotate.db.profile.unableToTranqMessage) + end + end) + end +end diff --git a/src/frames.lua b/src/frames.lua index 4a8e62f..ae132e7 100644 --- a/src/frames.lua +++ b/src/frames.lua @@ -158,9 +158,7 @@ function TranqRotate:createHunterFrame(hunter, parentFrame) TranqRotate:createCooldownFrame(hunter) TranqRotate:configureHunterFrameDrag(hunter) - if (TranqRotate.enableDrag) then - TranqRotate:enableHunterFrameDragging(hunter, true) - end + TranqRotate:toggleHunterFrameDragging(hunter, TranqRotate:isPlayerAllowedToSortHunterList()) end -- Create the cooldown frame @@ -181,10 +179,11 @@ function TranqRotate:createCooldownFrame(hunter) local statusBar = CreateFrame("StatusBar", nil, hunter.frame.cooldownFrame) statusBar:SetAllPoints() statusBar:SetMinMaxValues(0,1) - statusBar:SetStatusBarTexture("Interface\\AddOns\\TranqRotate\\textures\\steel.tga") - statusBar:GetStatusBarTexture():SetHorizTile(false) - statusBar:GetStatusBarTexture():SetVertTile(false) - statusBar:SetStatusBarColor(1, 0, 0) + + local statusBarTexture = statusBar:CreateTexture(nil, "OVERLAY"); + statusBarTexture:SetColorTexture(1, 0, 0); + statusBar:SetStatusBarTexture(statusBarTexture); + hunter.frame.cooldownFrame.statusBar = statusBar hunter.frame.cooldownFrame:SetScript( @@ -192,11 +191,49 @@ function TranqRotate:createCooldownFrame(hunter) function(self, elapsed) self.statusBar:SetValue(GetTime()) - if (self.statusBar.exirationTime < GetTime()) then + if (self.statusBar.expirationTime < GetTime()) then self:Hide() end end ) hunter.frame.cooldownFrame:Hide() -end \ No newline at end of file +end + +-- Create the boss frenzy CD frame +function TranqRotate:createFrenzyFrame() + + -- Frame + TranqRotate.mainFrame.frenzyFrame = CreateFrame("Frame", nil, TranqRotate.mainFrame) + TranqRotate.mainFrame.frenzyFrame:SetPoint('LEFT', 0, 0) + TranqRotate.mainFrame.frenzyFrame:SetPoint('RIGHT', 0, 0) + TranqRotate.mainFrame.frenzyFrame:SetPoint('TOP', 0, -TranqRotate.constants.titleBarHeight) + TranqRotate.mainFrame.frenzyFrame:SetHeight(2) + + local statusBar = CreateFrame("StatusBar", nil, TranqRotate.mainFrame.frenzyFrame) + statusBar:SetAllPoints() + statusBar:SetMinMaxValues(0,1) + statusBar:SetValue(0) + + local statusBarTexture = statusBar:CreateTexture(nil, "OVERLAY"); + statusBarTexture:SetColorTexture(1, 0.4, 0); + statusBar:SetStatusBarTexture(statusBarTexture); + + TranqRotate.mainFrame.frenzyFrame.statusBar = statusBar + + TranqRotate.mainFrame.frenzyFrame:SetScript( + "OnUpdate", + function(self, elapsed) + + if (self.statusBar.expirationTime) then + self.statusBar:SetValue(GetTime()) + if (self.statusBar.expirationTime < GetTime()) then + statusBar:GetStatusBarTexture():SetColorTexture(1, 0, 0); + end + end + + end + ) + + TranqRotate.mainFrame.frenzyFrame:Hide() +end diff --git a/src/gui.lua b/src/gui.lua index 42d0c04..73e6d1d 100644 --- a/src/gui.lua +++ b/src/gui.lua @@ -10,6 +10,7 @@ function TranqRotate:initGui() TranqRotate:createButtons() TranqRotate:createRotationFrame() TranqRotate:createBackupFrame() + TranqRotate:createFrenzyFrame() TranqRotate:drawHunterFrames() TranqRotate:createDropHintFrame() @@ -21,7 +22,7 @@ end -- Show/Hide main window based on user settings function TranqRotate:updateDisplay() - if (TranqRotate:isInPveRaid()) then + if (not TranqRotate.db.profile.doNotShowWindowOnRaidJoin and TranqRotate:isInPveRaid()) then TranqRotate.mainFrame:Show() else if (TranqRotate.db.profile.hideNotInRaid) then @@ -61,7 +62,7 @@ function TranqRotate:drawList(hunterList, parentFrame) parentFrame:Show() end - for key,hunter in pairs(hunterList) do + for key, hunter in pairs(hunterList) do -- Using existing frame if possible if (hunter.frame == nil) then @@ -127,10 +128,25 @@ end function TranqRotate:startHunterCooldown(hunter) hunter.frame.cooldownFrame.statusBar:SetMinMaxValues(GetTime(), GetTime() + 20) - hunter.frame.cooldownFrame.statusBar.exirationTime = GetTime() + 20 + hunter.frame.cooldownFrame.statusBar.expirationTime = GetTime() + 20 hunter.frame.cooldownFrame:Show() end +-- Initialize the frenzy cooldown status bar +function TranqRotate:startBossFrenzyCooldown(cooldownDuration) + TranqRotate.mainFrame.frenzyFrame.statusBar:GetStatusBarTexture():SetColorTexture(1, 0.4, 0); + TranqRotate.mainFrame.frenzyFrame.statusBar:SetMinMaxValues(GetTime(), GetTime() + cooldownDuration) + TranqRotate.mainFrame.frenzyFrame.statusBar.expirationTime = GetTime() + cooldownDuration + TranqRotate.mainFrame.frenzyFrame:Show() +end + +-- Reinitialize the frenzy frame +function TranqRotate:resetFrenzyFrame() + TranqRotate.mainFrame.frenzyFrame.statusBar:GetStatusBarTexture():SetColorTexture(1, 0.4, 0); + TranqRotate.mainFrame.frenzyFrame.statusBar.expirationTime = nil + TranqRotate.mainFrame.frenzyFrame:Hide() +end + -- Lock/Unlock the mainFrame position function TranqRotate:lock(lock) TranqRotate.db.profile.lock = lock diff --git a/src/rotation.lua b/src/rotation.lua index 668c62f..f4a01f8 100644 --- a/src/rotation.lua +++ b/src/rotation.lua @@ -1,460 +1,488 @@ -local TranqRotate = select(2, ...) - -local L = TranqRotate.L - --- 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.nextTranq = false - hunter.lastTranqTime = 0 - - -- 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, rotateWithoutCooldown) - - -- Default value to false - fail = fail or false - - local playerName, realm = UnitName("player") - local hunterRotationTable = TranqRotate:getHunterRotationTable(lastHunter) - local hasPlayerFailed = playerName == lastHunter.name and fail - - lastHunter.lastTranqTime = GetTime() - - -- Do not trigger cooldown when rotation from a dead or disconnected status - if (rotateWithoutCooldown ~= true) then - TranqRotate:startHunterCooldown(lastHunter) - end - - if (hunterRotationTable == TranqRotate.rotationTables.rotation) then - local nextHunter = TranqRotate:getNextRotationHunter(lastHunter) - - if (nextHunter ~= nil) then - - TranqRotate:setNextTranq(nextHunter) - - if (TranqRotate:isHunterTranqCooldownReady(nextHunter)) then - if (#TranqRotate.rotationTables.backup < 1) then - if (hasPlayerFailed) then - SendChatMessage(TranqRotate.db.profile.whisperFailMessage, 'WHISPER', nil, nextHunter.name) - end - - if (fail and nextHunter.name == playerName) then - TranqRotate:throwTranqAlert() - end - end - end - end - end - - if (fail) then - if (hasPlayerFailed) then - TranqRotate:whisperBackup() - end - - if (TranqRotate:getHunterRotationTable(TranqRotate:getHunter(playerName)) == TranqRotate.rotationTables.backup) then - TranqRotate:throwTranqAlert() - end - end -end - --- Whisper fail message to all backup except player -function TranqRotate:whisperBackup() - local name, realm = UnitName("player") - for key, backupHunter in pairs(TranqRotate.rotationTables.backup) do - if (backupHunter.name ~= name) then - 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 - - if (nextHunter.name == UnitName("player")) and TranqRotate.db.profile.enableNextToTranqSound then - PlaySoundFile(TranqRotate.constants.sounds.nextToTranq) - end - else - hunter.nextTranq = false - end - - TranqRotate:refreshHunterFrame(hunter) - end -end - --- Check if the player is the next in position to tranq -function TranqRotate:isPlayerNextTranq() - - local player = TranqRotate:getHunter(nil, UnitGUID("player")) - - -- Non hunter user - if (player == nil) then - return false - end - - if (not player.nextTranq) then - - local isRotationInitialized = false; - local rotationTable = TranqRotate.rotationTables.rotation - - -- checking if a hunter is flagged nextTranq - for key, hunter in pairs(rotationTable) do - if (hunter.nextTranq) then - isRotationInitialized = true; - break - end - end - - -- First in rotation has to tranq if not one is flagged - if (not isRotationInitialized and TranqRotate:getHunterIndex(player, rotationTable) == 1) then - return true - end - - end - - return player.nextTranq -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 - - -- 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 if not last on rotation - if (lastHunterIndex < #rotationTable) then - for index = lastHunterIndex + 1 , #rotationTable, 1 do - local hunter = rotationTable[index] - if (TranqRotate:isEligibleForNextTranq(hunter)) then - nextHunter = hunter - break - end - end - end - - -- Restart search from first index - if (nextHunter == nil) then - for index = 1 , lastHunterIndex, 1 do - local hunter = rotationTable[index] - if (TranqRotate:isEligibleForNextTranq(hunter)) then - nextHunter = hunter - break - end - end - end - - -- If no hunter in the rotation match the alive/online/CD criteria - -- Pick the hunter with the lowest cooldown - if (nextHunter == nil and #rotationTable > 0) then - local latestTranq = GetTime() + 1 - for key, hunter in pairs(rotationTable) do - if (TranqRotate:isHunterAliveAndOnline(hunter) and hunter.lastTranqTime < latestTranq) then - nextHunter = hunter - latestTranq = hunter.lastTranqTime - 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 -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, false) - break - end - end -end - --- Check if a hunter is already registered -function TranqRotate:isHunterRegistered(GUID) - - -- @todo refactor this using TranqRotate:getHunter(name, 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 (TranqRotate:isInPveRaid()) 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 - TranqRotate:updateHunterStatus(hunter) - end - end - - end - end - - if (not TranqRotate.raidInitialized) then - if (not TranqRotate.db.profile.doNotShowWindowOnRaidJoin) then - TranqRotate:updateDisplay() - end - TranqRotate:sendSyncOrderRequest() - TranqRotate.raidInitialized = true - end - else - if(TranqRotate.raidInitialized == true) then - TranqRotate:updateDisplay() - TranqRotate.raidInitialized = false - end - end - - TranqRotate:purgeHunterList() -end - --- Update hunter status -function TranqRotate:updateHunterStatus(hunter) - - -- Jump to the next hunter if the current one is dead or offline - if (hunter.nextTranq and (not TranqRotate:isHunterAliveAndOnline(hunter))) then - TranqRotate:rotate(hunter, false, true) - end - - TranqRotate:refreshHunterFrame(hunter) -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 - --- Builds simple rotation tables containing only hunters names -function TranqRotate:getSimpleRotationTables() - - local simpleTables = { rotation = {}, backup = {} } - - for key, rotationTable in pairs(TranqRotate.rotationTables) do - for _, hunter in pairs(rotationTable) do - table.insert(simpleTables[key], hunter.name) - end - end - - return simpleTables -end - --- Apply a simple rotation configuration -function TranqRotate:applyRotationConfiguration(rotationsTables) - - for key, rotationTable in pairs(rotationsTables) do - - local group = 'ROTATION' - if (key == 'backup') then - group = 'BACKUP' - end - - for index, hunterName in pairs(rotationTable) do - local hunter = TranqRotate:getHunter(hunterName) - if (hunter) then - TranqRotate:moveHunter(hunter, group, index) - end - end - end -end - --- Display an alert and play a sound when the player should immediatly tranq -function TranqRotate:throwTranqAlert() - RaidNotice_AddMessage(RaidWarningFrame, L['TRANQ_NOW_LOCAL_ALERT_MESSAGE'], ChatTypeInfo["RAID_WARNING"]) - - if (TranqRotate.db.profile.enableTranqNowSound) then - PlaySoundFile(TranqRotate.constants.sounds.alarms[TranqRotate.db.profile.tranqNowSound]) - end -end +local TranqRotate = select(2, ...) + +local L = TranqRotate.L + +-- 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.nextTranq = false + hunter.lastTranqTime = 0 + + -- 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, rotateWithoutCooldown) + + -- Default value to false + fail = fail or false + + if (not fail) then + TranqRotate.frenzy = false + end + + local playerName, realm = UnitName("player") + local lastHunterRotationTable = TranqRotate:getHunterRotationTable(lastHunter) + local hasPlayerFailed = playerName == lastHunter.name and fail + + lastHunter.lastTranqTime = GetTime() + + -- Do not trigger cooldown when rotation from a dead or disconnected status + if (rotateWithoutCooldown ~= true) then + TranqRotate:startHunterCooldown(lastHunter) + end + + local nextHunter = nil + + if (lastHunterRotationTable == TranqRotate.rotationTables.rotation) then + nextHunter = TranqRotate:getNextRotationHunter(lastHunter) + + if (nextHunter ~= nil) then + TranqRotate:setNextTranq(nextHunter) + + if ((fail and nextHunter.name == playerName) and + #TranqRotate.rotationTables.backup < 1 and + TranqRotate:isHunterTranqCooldownReady(nextHunter) + ) then + TranqRotate:throwTranqAlert() + end + end + end + + if (fail) then + if (hasPlayerFailed) then + TranqRotate:alertBackup(TranqRotate.db.profile.whisperFailMessage, nextHunter, true) + end + + local playerRotationTable = TranqRotate:getHunterRotationTable(TranqRotate:getHunter(playerName)) + if (playerRotationTable == TranqRotate.rotationTables.backup and not hasPlayerFailed) then + TranqRotate:throwTranqAlert() + 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 + + if (nextHunter.name == UnitName("player")) and TranqRotate.db.profile.enableNextToTranqSound then + PlaySoundFile(TranqRotate.constants.sounds.nextToTranq) + end + else + hunter.nextTranq = false + end + + TranqRotate:refreshHunterFrame(hunter) + end +end + +-- Check if the player is the next in position to tranq +function TranqRotate:isPlayerNextTranq() + + if(not TranqRotate:isHunter("player")) then + return false + end + + local player = TranqRotate:getHunter(nil, UnitGUID("player")) + + if (not player.nextTranq) then + + local isRotationInitialized = false; + local rotationTable = TranqRotate.rotationTables.rotation + + -- checking if a hunter is flagged nextTranq + for key, hunter in pairs(rotationTable) do + if (hunter.nextTranq) then + isRotationInitialized = true; + break + end + end + + -- First in rotation has to tranq if not one is flagged + if (not isRotationInitialized and TranqRotate:getHunterIndex(player, rotationTable) == 1) then + return true + end + + end + + return player.nextTranq +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 + + -- 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 if not last on rotation + if (lastHunterIndex < #rotationTable) then + for index = lastHunterIndex + 1 , #rotationTable, 1 do + local hunter = rotationTable[index] + if (TranqRotate:isEligibleForNextTranq(hunter)) then + nextHunter = hunter + break + end + end + end + + -- Restart search from first index + if (nextHunter == nil) then + for index = 1 , lastHunterIndex, 1 do + local hunter = rotationTable[index] + if (TranqRotate:isEligibleForNextTranq(hunter)) then + nextHunter = hunter + break + end + end + end + + -- If no hunter in the rotation match the alive/online/CD criteria + -- Pick the hunter with the lowest cooldown + if (nextHunter == nil and #rotationTable > 0) then + local latestTranq = GetTime() + 1 + for key, hunter in pairs(rotationTable) do + if (TranqRotate:isHunterAliveAndOnline(hunter) and hunter.lastTranqTime < latestTranq) then + nextHunter = hunter + latestTranq = hunter.lastTranqTime + 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 +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, false) + break + end + end +end + +-- Check if a hunter is already registered +function TranqRotate:isHunterRegistered(GUID) + + -- @todo refactor this using TranqRotate:getHunter(name, 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() + for _, hunter in pairs(TranqRotate.hunterTable) do + if (not UnitInParty(hunter.name)) then + TranqRotate:unregisterUnitEvents(hunter) + TranqRotate:removeHunter(hunter) + end + end +end + +-- Iterate over all raid members to find hunters and update their status +function TranqRotate:updateRaidStatus() + + if (TranqRotate:isInPveRaid()) 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(TranqRotate:isHunter(name)) 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 + TranqRotate:updateHunterStatus(hunter) + end + end + + end + end + + TranqRotate:updateDragAndDrop() + + if (not TranqRotate.raidInitialized) then + if (not TranqRotate.db.profile.doNotShowWindowOnRaidJoin) then + TranqRotate:updateDisplay() + end + TranqRotate:sendSyncOrderRequest() + TranqRotate.raidInitialized = true + end + else + if(TranqRotate.raidInitialized == true) then + TranqRotate:updateDisplay() + TranqRotate.raidInitialized = false + end + end + + TranqRotate:purgeHunterList() +end + +-- Update hunter status +function TranqRotate:updateHunterStatus(hunter) + + -- Jump to the next hunter if the current one is dead or offline + if (hunter.nextTranq and (not TranqRotate:isHunterAliveAndOnline(hunter))) then + TranqRotate:rotate(hunter, false, true) + end + + TranqRotate:refreshHunterFrame(hunter) +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 + +-- Builds simple rotation tables containing only hunters names +function TranqRotate:getSimpleRotationTables() + + local simpleTables = { rotation = {}, backup = {} } + + for key, rotationTable in pairs(TranqRotate.rotationTables) do + for _, hunter in pairs(rotationTable) do + table.insert(simpleTables[key], hunter.name) + end + end + + return simpleTables +end + +-- Apply a simple rotation configuration +function TranqRotate:applyRotationConfiguration(rotationsTables) + + for key, rotationTable in pairs(rotationsTables) do + + local group = 'ROTATION' + if (key == 'backup') then + group = 'BACKUP' + end + + for index, hunterName in pairs(rotationTable) do + local hunter = TranqRotate:getHunter(hunterName) + if (hunter) then + TranqRotate:moveHunter(hunter, group, index) + end + end + end +end + +-- Display an alert and play a sound when the player should immediatly tranq +function TranqRotate:throwTranqAlert() + RaidNotice_AddMessage(RaidWarningFrame, L['TRANQ_NOW_LOCAL_ALERT_MESSAGE'], ChatTypeInfo["RAID_WARNING"]) + + if (TranqRotate.db.profile.enableTranqNowSound) then + PlaySoundFile(TranqRotate.constants.sounds.alarms[TranqRotate.db.profile.tranqNowSound]) + end +end + +-- Send a defined message to backup player or next rotation player if there's no backup +function TranqRotate:alertBackup(message, nextHunter, noComms) + local playerName = UnitName('player') + local player = TranqRotate:getHunter(playerName) + + -- Non hunter have no reason to ask for backup + if (not TranqRotate:isHunter('player')) then + return + end + + if (#TranqRotate.rotationTables.backup < 1) then + + if (nextHunter == nil) then + nextHunter = TranqRotate:getNextRotationHunter(player) + end + + SendChatMessage(message, 'WHISPER', nil, nextHunter.name) + if (noComms ~= true) then + TranqRotate:sendBackupRequest(nextHunter.name) + end + else + TranqRotate:whisperBackup(message) + end +end + +-- Whisper provided message of fail message to all backup except player +function TranqRotate:whisperBackup(message, noComms) + + local name = UnitName("player") + + if (message == nil) then + message = TranqRotate.db.profile.whisperFailMessage + end + + for key, backupHunter in pairs(TranqRotate.rotationTables.backup) do + if (backupHunter.name ~= name) then + SendChatMessage(message, 'WHISPER', nil, backupHunter.name) + + if (noComms ~= true) then + TranqRotate:sendBackupRequest(backupHunter.name) + end + end + end +end diff --git a/src/settings.lua b/src/settings.lua index ca070cf..cc4f472 100644 --- a/src/settings.lua +++ b/src/settings.lua @@ -110,7 +110,57 @@ function TranqRotate:CreateConfig() type = "execute", order = 13, func = function() TranqRotate.toggleArcaneShotTesting() end - } + }, + featuresHeader = { + name = L["FEATURES_HEADER"], + type = "header", + order = 20, + }, + showFrenzyCooldownProgress = { + name = L["DISPLAY_BOSS_FRENZY_COOLDOWN"], + desc = L["DISPLAY_BOSS_FRENZY_COOLDOWN_DESC"], + type = "toggle", + order = 21, + width = "full", + set = function(info, value) + set(info, value) + if (not value) then TranqRotate:resetFrenzyFrame() end + end + }, + enableIncapacitatedBackupAlert = { + name = L["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED"], + desc = L["ENABLE_AUTOMATIC_BACKUP_ALERT_WHEN_INCAPACITATED_DESC"], + type = "toggle", + order = 25, + width = "double", + }, + incapacitatedDelay = { + name = L["INCAPACITATED_DELAY_THRESHOLD"], + desc = L["INCAPACITATED_DELAY_THRESHOLD_DESC"], + type = "range", + order = 26, + width = "normal", + min = 1, + max = 4, + step = 0.1, + }, + enableTimedBackupAlertValue = { + name = L["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT"], + desc = L["ENABLE_AUTOMATIC_TIMED_BACKUP_ALERT_DESC"], + type = "toggle", + order = 30, + width = "double", + }, + timedBackupAlertValueDelay = { + name = L["TIMED_DELAY_THRESHOLD"], + desc = L["TIMED_DELAY_THRESHOLD_DESC"], + type = "range", + order = 31, + width = "normal", + min = 1, + max = 4, + step = 0.1, + }, } }, announces = { @@ -167,6 +217,12 @@ function TranqRotate:CreateConfig() order = 25, width = "double", }, + unableToTranqMessage = { + name = L["UNABLE_TO_TRANQ_MESSAGE_LABEL"], + type = "input", + order = 26, + width = "double", + }, setupBroadcastHeader = { name = L["BROADCAST_MESSAGE_HEADER"], type = "header", diff --git a/src/tranqRotate.lua b/src/tranqRotate.lua index 4982833..7260c1a 100644 --- a/src/tranqRotate.lua +++ b/src/tranqRotate.lua @@ -19,14 +19,15 @@ function TranqRotate:init() TranqRotate.hunterTable = {} TranqRotate.rotationTables = { rotation = {}, backup = {} } - TranqRotate.enableDrag = true TranqRotate.raidInitialized = false TranqRotate.testMode = false + TranqRotate.frenzy = false TranqRotate:initGui() TranqRotate:updateRaidStatus() TranqRotate:applySettings() + TranqRotate:updateDragAndDrop() TranqRotate:initComms() @@ -67,7 +68,7 @@ function TranqRotate:printPrefixedMessage(msg) TranqRotate:printMessage(TranqRotate:colorText(TranqRotate.constants.printPrefix) .. msg) end --- Send a tranq annouce message to a given channel +-- Send a tranq announce message to a given channel function TranqRotate:sendAnnounceMessage(message, targetName) if TranqRotate.db.profile.enableAnnounces then TranqRotate:sendMessage( @@ -80,7 +81,7 @@ function TranqRotate:sendAnnounceMessage(message, targetName) end -- Send a rotation broadcast message -function TranqRotate:sendRotationSetupBroacastMessage(message) +function TranqRotate:sendRotationSetupBroadcastMessage(message) if TranqRotate.db.profile.enableAnnounces then TranqRotate:sendMessage( message, @@ -112,7 +113,7 @@ SlashCmdList["TRANQROTATE"] = function(msg) elseif (cmd == 'unlock') then TranqRotate:lock(false) elseif (cmd == 'backup') then - TranqRotate:whisperBackup() + TranqRotate:alertBackup(TranqRotate.db.profile.unableToTranqMessage) elseif (cmd == 'rotate') then -- @todo decide if this should be removed or not TranqRotate:testRotation() elseif (cmd == 'test') then -- @todo: remove this @@ -138,7 +139,8 @@ end -- @todo: remove this function TranqRotate:test() TranqRotate:printMessage('test') - TranqRotate:toggleArcaneShotTesting() + + print(TranqRotate:isPlayedIncapacitatedByDebuff()) end -- Open ace settings @@ -151,18 +153,18 @@ end function TranqRotate:printRotationSetup() if (IsInRaid()) then - TranqRotate:sendRotationSetupBroacastMessage('--- ' .. TranqRotate.constants.printPrefix .. L['BROADCAST_HEADER_TEXT'] .. ' ---', channel) + TranqRotate:sendRotationSetupBroadcastMessage('--- ' .. TranqRotate.constants.printPrefix .. L['BROADCAST_HEADER_TEXT'] .. ' ---', channel) if (TranqRotate.db.profile.useMultilineRotationReport) then TranqRotate:printMultilineRotation(TranqRotate.rotationTables.rotation) else - TranqRotate:sendRotationSetupBroacastMessage( + TranqRotate:sendRotationSetupBroadcastMessage( TranqRotate:buildGroupMessage(L['BROADCAST_ROTATION_PREFIX'] .. ' : ', TranqRotate.rotationTables.rotation) ) end if (#TranqRotate.rotationTables.backup > 0) then - TranqRotate:sendRotationSetupBroacastMessage( + TranqRotate:sendRotationSetupBroadcastMessage( TranqRotate:buildGroupMessage(L['BROADCAST_BACKUP_PREFIX'] .. ' : ', TranqRotate.rotationTables.backup) ) end @@ -173,7 +175,7 @@ end function TranqRotate:printMultilineRotation(rotationTable, channel) local position = 1; for key, hunt in pairs(rotationTable) do - TranqRotate:sendRotationSetupBroacastMessage(tostring(position) .. ' - ' .. hunt.name) + TranqRotate:sendRotationSetupBroadcastMessage(tostring(position) .. ' - ' .. hunt.name) position = position + 1; end end @@ -206,22 +208,6 @@ function TranqRotate:colorText(text) return '|cffffbf00' .. text .. '|r' end --- Check if unit is promoted -function TranqRotate:isHunterPromoted(name) - - local raidIndex = UnitInRaid(name) - - if (raidIndex) then - local name, rank, subgroup, level, class, fileName, zone, online, isDead, role, isML = GetRaidRosterInfo(raidIndex) - - if (rank > 0) then - return true - end - end - - return false -end - -- Toggle arcane shot testing mode function TranqRotate:toggleArcaneShotTesting(disable) @@ -237,4 +223,4 @@ function TranqRotate:toggleArcaneShotTesting(disable) TranqRotate.testMode = false TranqRotate:printPrefixedMessage(L['ARCANE_SHOT_TESTING_DISABLED']) end -end \ No newline at end of file +end diff --git a/src/utils.lua b/src/utils.lua index 0ab12f5..93888cb 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -32,7 +32,7 @@ function TranqRotate:isHunterTranqCooldownReady(hunter) return hunter.lastTranqTime <= GetTime() - 20 end --- Checks if a hunter is elligible to tranq next +-- Checks if a hunter is eligible to tranq next function TranqRotate:isEligibleForNextTranq(hunter) local isCooldownShortEnough = hunter.lastTranqTime <= GetTime() - TranqRotate.constants.minimumCooldownElapsedForEligibility @@ -51,8 +51,11 @@ function TranqRotate:isInPveRaid() end function TranqRotate:getPlayerNameFont() - if (GetLocale() == "zhCN" or GetLocale() == "zhTW") then + local locale = GetLocale() + if (locale == "zhCN" or locale == "zhTW") then return "Fonts\\ARHei.ttf" + elseif (locale == "koKR") then + return "Fonts\\2002.ttf" end return "Fonts\\ARIALN.ttf" @@ -70,8 +73,8 @@ function TranqRotate:isBossFrenzy(spellName, guid) local type, mobId = TranqRotate:getIdFromGuid(guid) if (type == "Creature") then - for bossId, frenzy in pairs(bosses) do - if (bossId == mobId and spellName == GetSpellInfo(frenzy)) then + for bossId, bossData in pairs(bosses) do + if (bossId == mobId and spellName == GetSpellInfo(bossData.frenzy)) then return true end end @@ -87,7 +90,7 @@ function TranqRotate:isTranqableBoss(guid) local type, mobId = TranqRotate:getIdFromGuid(guid) if (type == "Creature") then - for bossId, frenzy in pairs(bosses) do + for bossId, bossData in pairs(bosses) do if (bossId == mobId) then return true end @@ -102,11 +105,41 @@ function TranqRotate:isFrenzy(spellName) local bosses = TranqRotate.constants.bosses - for bossId, frenzy in pairs(bosses) do - if (spellName == GetSpellInfo(frenzy)) then + for bossId, bossData in pairs(bosses) do + if (spellName == GetSpellInfo(bossData.frenzy)) then return true end end return false end + +-- Checks if the player is a hunter +function TranqRotate:isHunter(name) + return select(2,UnitClass(name)) == 'HUNTER' +end + +-- Check if unit is promoted (raid assist or raid leader) +function TranqRotate:isPlayerRaidAssist(name) + + if (TranqRotate:isInPveRaid()) then + + local raidIndex = UnitInRaid(name) + + if (raidIndex) then + local name, rank, subgroup, level, class, fileName, zone, online, isDead, role, isML = GetRaidRosterInfo(raidIndex) + + if (rank > 0) then + return true + end + end + end + + return false +end + +-- Checks if condition to enable drag and drop are met +function TranqRotate:isPlayerAllowedToSortHunterList() + local playerName = UnitName("player") + return TranqRotate:isHunter(playerName) or TranqRotate:isPlayerRaidAssist(playerName) +end