From 083909bf8e5b2b9ada119accdbca076bc1b2a07a Mon Sep 17 00:00:00 2001 From: pepopo978 Date: Wed, 12 Jun 2024 17:38:05 -0700 Subject: [PATCH 1/4] add more options and new priority --- README.md | 36 ++++++++++++++++------------- commands.lua | 65 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 66 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index f5ab831..e4ae100 100644 --- a/README.md +++ b/README.md @@ -25,17 +25,32 @@ EXAMPLE: `/cursive curse Corruption|target` will attempt to cast Corruption on y ### Multicurse `/cursive multicurse ||`: Picks target based on priority and casts spell if not already on target and they aren't cc'ed. -Priority options: HIGHEST_HP, RAID_MARK EXAMPLE: `/cursive multicurse Corruption|HIGHEST_HP` will attempt to cast Corruption picking the target with the highest HP that doesn't already have it and will warn you if it does nothing. +## Priority Options +- HIGHEST_HP - Target highest HP enemy without a curse first. +- RAID_MARK - Target largest raid mark enemy without a curse first. Raid mark priority is as follows: + - Skull = 8 (1st priority) + - Cross = 7 + - Square = 6 + - Moon = 5 + - Triangle = 4 + - Circle = 3 + - Star = 2 + - Diamond = 1 (8th priority) + - No mark = 0 +- HIGHEST_HP_RAID_MARK - Target highest HP enemy with a raid (use raid mark priorities for identical hp), then highest HP enemy without a mark. + ## Command Options All commands can take the following options separated by commas: -- ["warnings"] : "Display text warnings when a curse fails to cast.", -- ["resistsound"] : "Play a sound when a curse is resisted.", -- ["expiringsound"] : "Play a sound when a curse is about to expire.", +- `warnings` : "Display text warnings when a curse fails to cast.", +- `resistsound` : "Play a sound when a curse is resisted.", +- `expiringsound` : "Play a sound when a curse is about to expire.", +- `allowooc` : "Allow out of combat targets to be multicursed. Would only consider using this solo to avoid potentially griefing raids/dungeons by pulling unintended mobs.", +- `minhp=` : "Minimum HP for a target to be considered.", -EXAMPLE: `/cursive multicurse Corruption|HIGHEST_HP|warnings,resistsound,expiringsound` +EXAMPLE: `/cursive multicurse Corruption|HIGHEST_HP|warnings,resistsound,expiringsound,minhp=10000` ## Important info @@ -53,14 +68,3 @@ All commands will ignore targets with the following CCs on them: - Freezing Trap Multicurse will only ever target enemies that are already in combat (except if you target a mob directly first) to prevent pulling things you didn't intend like marked patrols. - -Raid mark priority is based on the following order: -- Skull = 8 -- Cross = 7 -- Square = 6 -- Moon = 5 -- Triangle = 4 -- Circle = 3 -- Star = 2 -- Diamond = 1 -- No mark = 0 diff --git a/commands.lua b/commands.lua index 4cd86f6..a208ff4 100644 --- a/commands.lua +++ b/commands.lua @@ -2,14 +2,16 @@ local curseCommands = "|cffffcc00Cursive:|cffffaaaa Commands:" local curseOptions = "Options (separate with ,):" local commandOptions = { - ["warnings"] = "Display text warnings when a curse fails to cast.", - ["resistsound"] = "Play a sound when a curse is resisted.", - ["expiringsound"] = "Play a sound when a curse is about to expire.", + warnings = "Display text warnings when a curse fails to cast.", + resistsound = "Play a sound when a curse is resisted.", + expiringsound = "Play a sound when a curse is about to expire.", + allowooc = "Allow out of combat targets to be multicursed. Would only consider using this solo to avoid potentially griefing raids/dungeons by pulling unintended mobs.", + minhp = "Minimum HP for a target to be considered. Example usage minhp=10000. ", } local commands = { ["curse"] = "/cursive curse ||>: Casts spell if not already on target/guid", - ["multicurse"] = "/cursive multicurse ||>: Picks target based on priority and casts spell if not already on target. Priority options: HIGHEST_HP, RAID_MARK.", + ["multicurse"] = "/cursive multicurse ||>: Picks target based on priority and casts spell if not already on target. Priority options: HIGHEST_HP, RAID_MARK, HIGHEST_HP_RAID_MARK.", } local curseNoTarget = "|cffffcc00Cursive:|cffffaaaa Couldn't find a target to curse." @@ -19,7 +21,13 @@ local function parseOptions(optionsStr) if optionsStr then for option, _ in pairs(commandOptions) do - if string.find(optionsStr, option) then + -- special case for minhp as it takes a param + if option == "minhp" then + local _, _, minHp = string.find(optionsStr, "minhp=(%d+)") + if minHp then + options["minhp"] = tonumber(minHp) + end + elseif string.find(optionsStr, option) then options[option] = true end end @@ -131,33 +139,52 @@ local function isMobCrowdControlled(guid) return false end -local function pickTarget(selectedPriority, spellNameNoRank, checkRange) +local function pickTarget(selectedPriority, spellNameNoRank, checkRange, minHp, ignoreInFight) -- Curse the target that best matches the selected priority - local highestPriorityValue = -1 + local highestPrimaryValue = -1 + local highestSecondaryValue = -1 local targetedGuid = nil local _, currentTargetGuid = UnitExists("target") + local seenRaidMark = nil -- if we have seen a raid mark + for guid, time in pairs(Cursive.core.guids) do -- apply filters local shouldDisplay = Cursive:ShouldDisplayGuid(guid) -- check if target displayed if shouldDisplay then -- check if in combat already or player is actively targeting the mob - if Cursive.filter.infight(guid) or guid == currentTargetGuid then + if ignoreInFight or Cursive.filter.infight(guid) or guid == currentTargetGuid then + -- prioritize targets within 28 yards first to improve chances of being in range if checkRange == false or CheckInteractDistance(guid, 4) then -- check if the target has the curse if not Cursive.curses:HasCurse(spellNameNoRank, guid) and not isMobCrowdControlled(guid) then - local value - if selectedPriority == "HIGHEST_HP" then - value = UnitHealth(guid) - elseif selectedPriority == "RAID_MARK" then - value = GetRaidTargetIndex(guid) or 0 - end + local mobHp = UnitHealth(guid) + if not minHp or mobHp >= minHp then + local primaryValue + local secondaryValue = -1 + if selectedPriority == "HIGHEST_HP" then + primaryValue = UnitHealth(guid) + elseif selectedPriority == "RAID_MARK" then + primaryValue = GetRaidTargetIndex(guid) or 0 + elseif selectedPriority == "HIGHEST_HP_RAID_MARK" then + secondaryValue = GetRaidTargetIndex(guid) or 0 + if secondaryValue > 0 and not seenRaidMark then + highestPrimaryValue = -1 -- reset highestPriorityValue if this is the first raid mark we've seen + seenRaidMark = true + end + primaryValue = UnitHealth(guid) + end - if value > highestPriorityValue then - highestPriorityValue = value - targetedGuid = guid + if primaryValue > highestPrimaryValue then + highestPrimaryValue = primaryValue + highestSecondaryValue = secondaryValue + targetedGuid = guid + elseif primaryValue == highestPrimaryValue and secondaryValue > highestSecondaryValue then + highestSecondaryValue = secondaryValue + targetedGuid = guid + end end end end @@ -167,7 +194,7 @@ local function pickTarget(selectedPriority, spellNameNoRank, checkRange) -- run again if no target found ignoring range if not targetedGuid and checkRange == true then - targetedGuid = pickTarget(selectedPriority, spellNameNoRank, false) + targetedGuid = pickTarget(selectedPriority, spellNameNoRank, false, minHp, ignoreInFight) end return targetedGuid @@ -226,7 +253,7 @@ function Cursive:Multicurse(spellName, priority, options) -- remove (Rank x) from spellName if it exists local spellNameNoRank = string.gsub(spellName, "%(.+%)", "") - local targetedGuid = pickTarget(selectedPriority, spellNameNoRank, true) + local targetedGuid = pickTarget(selectedPriority, spellNameNoRank, true, options["minhp"], options["allowooc"]) if targetedGuid then castSpellWithOptions(spellName, spellNameNoRank, targetedGuid, options) From 7bcc6cd44e31c09ba5cb23a0d9b38986f96f0582 Mon Sep 17 00:00:00 2001 From: pepopo978 Date: Wed, 12 Jun 2024 21:23:20 -0700 Subject: [PATCH 2/4] add refreshtime option --- README.md | 3 ++- commands.lua | 20 +++++++++++++++----- curses.lua | 12 +++++++++--- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e4ae100..62bac48 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,9 @@ All commands can take the following options separated by commas: - `expiringsound` : "Play a sound when a curse is about to expire.", - `allowooc` : "Allow out of combat targets to be multicursed. Would only consider using this solo to avoid potentially griefing raids/dungeons by pulling unintended mobs.", - `minhp=` : "Minimum HP for a target to be considered.", +- `refreshtime=` : "Time threshold below which to allow refreshing a curse. Default is 0 seconds.", -EXAMPLE: `/cursive multicurse Corruption|HIGHEST_HP|warnings,resistsound,expiringsound,minhp=10000` +EXAMPLE: `/cursive multicurse Corruption|HIGHEST_HP|warnings,resistsound,expiringsound,minhp=10000,refreshtime=2` ## Important info diff --git a/commands.lua b/commands.lua index a208ff4..c41b6ca 100644 --- a/commands.lua +++ b/commands.lua @@ -7,6 +7,7 @@ local commandOptions = { expiringsound = "Play a sound when a curse is about to expire.", allowooc = "Allow out of combat targets to be multicursed. Would only consider using this solo to avoid potentially griefing raids/dungeons by pulling unintended mobs.", minhp = "Minimum HP for a target to be considered. Example usage minhp=10000. ", + refreshtime = "Time threshold below which to allow refreshing a curse. Default is 0 seconds." } local commands = { @@ -27,6 +28,11 @@ local function parseOptions(optionsStr) if minHp then options["minhp"] = tonumber(minHp) end + elseif option == "refreshtime" then + local _, _, refreshTime = string.find(optionsStr, "refreshtime=(%d+)") + if refreshTime then + options["refreshtime"] = tonumber(refreshTime) + end elseif string.find(optionsStr, option) then options[option] = true end @@ -139,12 +145,16 @@ local function isMobCrowdControlled(guid) return false end -local function pickTarget(selectedPriority, spellNameNoRank, checkRange, minHp, ignoreInFight) +local function pickTarget(selectedPriority, spellNameNoRank, checkRange, options) -- Curse the target that best matches the selected priority local highestPrimaryValue = -1 local highestSecondaryValue = -1 local targetedGuid = nil + local minHp = options["minhp"] + local ignoreInFight = options["allowooc"] + local refreshTime = options["refreshtime"] + local _, currentTargetGuid = UnitExists("target") local seenRaidMark = nil -- if we have seen a raid mark @@ -159,7 +169,7 @@ local function pickTarget(selectedPriority, spellNameNoRank, checkRange, minHp, -- prioritize targets within 28 yards first to improve chances of being in range if checkRange == false or CheckInteractDistance(guid, 4) then -- check if the target has the curse - if not Cursive.curses:HasCurse(spellNameNoRank, guid) and not isMobCrowdControlled(guid) then + if not Cursive.curses:HasCurse(spellNameNoRank, guid, refreshTime) and not isMobCrowdControlled(guid) then local mobHp = UnitHealth(guid) if not minHp or mobHp >= minHp then local primaryValue @@ -194,7 +204,7 @@ local function pickTarget(selectedPriority, spellNameNoRank, checkRange, minHp, -- run again if no target found ignoring range if not targetedGuid and checkRange == true then - targetedGuid = pickTarget(selectedPriority, spellNameNoRank, false, minHp, ignoreInFight) + targetedGuid = pickTarget(selectedPriority, spellNameNoRank, false, options) end return targetedGuid @@ -230,7 +240,7 @@ function Cursive:Curse(spellName, targetedGuid, options) -- remove (Rank x) from spellName if it exists local spellNameNoRank = string.gsub(spellName, "%(.+%)", "") - if targetedGuid and not Cursive.curses:HasCurse(spellNameNoRank, targetedGuid) and not isMobCrowdControlled(targetedGuid) then + if targetedGuid and not Cursive.curses:HasCurse(spellNameNoRank, targetedGuid, options["refreshtime"]) and not isMobCrowdControlled(targetedGuid) then castSpellWithOptions(spellName, spellNameNoRank, targetedGuid, options) elseif options["warnings"] then DEFAULT_CHAT_FRAME:AddMessage(curseNoTarget) @@ -253,7 +263,7 @@ function Cursive:Multicurse(spellName, priority, options) -- remove (Rank x) from spellName if it exists local spellNameNoRank = string.gsub(spellName, "%(.+%)", "") - local targetedGuid = pickTarget(selectedPriority, spellNameNoRank, true, options["minhp"], options["allowooc"]) + local targetedGuid = pickTarget(selectedPriority, spellNameNoRank, true, options) if targetedGuid then castSpellWithOptions(spellName, spellNameNoRank, targetedGuid, options) diff --git a/curses.lua b/curses.lua index 54f32e4..6b85a58 100644 --- a/curses.lua +++ b/curses.lua @@ -172,12 +172,18 @@ function curses:ShouldPlayResistSound(guid) return false end -function curses:HasCurse(spellName, targetGuid) +function curses:HasCurse(spellName, targetGuid, minRemaining) + if not minRemaining then + minRemaining = 0 -- default to 0 + end + if curses.guids[targetGuid] and curses.guids[targetGuid][spellName] then local remaining = Cursive.curses:TimeRemaining(curses.guids[targetGuid][spellName]) - return remaining > 0 + if remaining > minRemaining then + return true + end end - return false + return nil end function curses:ApplyCurse(spellID, targetGuid, startTime) From 36ef01cf6de7f8d511df8f2e63e8b3901281efc4 Mon Sep 17 00:00:00 2001 From: pepopo978 Date: Thu, 13 Jun 2024 08:07:34 -0700 Subject: [PATCH 3/4] fix HIGHEST_HP_RAID_MARK --- README.md | 2 +- commands.lua | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 62bac48..d98e2d8 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ All commands can take the following options separated by commas: - `expiringsound` : "Play a sound when a curse is about to expire.", - `allowooc` : "Allow out of combat targets to be multicursed. Would only consider using this solo to avoid potentially griefing raids/dungeons by pulling unintended mobs.", - `minhp=` : "Minimum HP for a target to be considered.", -- `refreshtime=` : "Time threshold below which to allow refreshing a curse. Default is 0 seconds.", +- `refreshtime=` : "Time threshold at which to allow refreshing a curse. Default is 0 seconds.", EXAMPLE: `/cursive multicurse Corruption|HIGHEST_HP|warnings,resistsound,expiringsound,minhp=10000,refreshtime=2` diff --git a/commands.lua b/commands.lua index c41b6ca..118cc9c 100644 --- a/commands.lua +++ b/commands.lua @@ -1,4 +1,5 @@ local curseCommands = "|cffffcc00Cursive:|cffffaaaa Commands:" +local priorityChoices = "Priority choices:" local curseOptions = "Options (separate with ,):" local commandOptions = { @@ -7,12 +8,18 @@ local commandOptions = { expiringsound = "Play a sound when a curse is about to expire.", allowooc = "Allow out of combat targets to be multicursed. Would only consider using this solo to avoid potentially griefing raids/dungeons by pulling unintended mobs.", minhp = "Minimum HP for a target to be considered. Example usage minhp=10000. ", - refreshtime = "Time threshold below which to allow refreshing a curse. Default is 0 seconds." + refreshtime = "Time threshold at which to allow refreshing a curse. Default is 0 seconds." } local commands = { ["curse"] = "/cursive curse ||>: Casts spell if not already on target/guid", - ["multicurse"] = "/cursive multicurse ||>: Picks target based on priority and casts spell if not already on target. Priority options: HIGHEST_HP, RAID_MARK, HIGHEST_HP_RAID_MARK.", + ["multicurse"] = "/cursive multicurse ||>: Picks target based on priority and casts spell if not already on target", +} + +local priorities = { + ["HIGHEST_HP"] = "Target with the highest HP.", + ["RAID_MARK"] = "Target with the highest raid mark.", + ["HIGHEST_HP_RAID_MARK"] = "Target with the highest HP and raid mark.", } local curseNoTarget = "|cffffcc00Cursive:|cffffaaaa Couldn't find a target to curse." @@ -48,6 +55,11 @@ local function handleSlashCommands(msg, editbox) for command, description in pairs(commands) do DEFAULT_CHAT_FRAME:AddMessage(description) end + DEFAULT_CHAT_FRAME:AddMessage(priorityChoices) + for priority, description in pairs(priorities) do + DEFAULT_CHAT_FRAME:AddMessage(priority .. ": " .. description) + end + DEFAULT_CHAT_FRAME:AddMessage(curseOptions) for option, description in pairs(commandOptions) do DEFAULT_CHAT_FRAME:AddMessage(option .. ": " .. description) @@ -253,8 +265,11 @@ function Cursive:Multicurse(spellName, priority, options) return end - if priority and (priority ~= "HIGHEST_HP" and priority ~= "RAID_MARK") then - DEFAULT_CHAT_FRAME:AddMessage(commands["multicurse"]) + if priority and not priorities[priority] then + DEFAULT_CHAT_FRAME:AddMessage(priorityChoices) + for choice, description in pairs(priorities) do + DEFAULT_CHAT_FRAME:AddMessage(choice .. ": " .. description) + end return end From 0731e67d0c4d737283ad7e4a28f909e2d7e24dcb Mon Sep 17 00:00:00 2001 From: pepopo978 Date: Mon, 17 Jun 2024 09:00:16 -0700 Subject: [PATCH 4/4] add banish to cc --- README.md | 1 + commands.lua | 3 +++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index d98e2d8..13b82c8 100644 --- a/README.md +++ b/README.md @@ -67,5 +67,6 @@ All commands will ignore targets with the following CCs on them: - Sap - Gouge - Freezing Trap +- Banish Multicurse will only ever target enemies that are already in combat (except if you target a mob directly first) to prevent pulling things you didn't intend like marked patrols. diff --git a/commands.lua b/commands.lua index 118cc9c..1d8fa5d 100644 --- a/commands.lua +++ b/commands.lua @@ -125,6 +125,9 @@ local crowdControlledSpellIds = { [3355] = { name = "Freezing Trap", rank = 1, duration = 10 }, [14308] = { name = "Freezing Trap", rank = 2, duration = 15 }, [14309] = { name = "Freezing Trap", rank = 3, duration = 20 }, + + [710] = { name = "Banish", rank = 1, duration = 30 }, + [18647] = { name = "Banish", rank = 2, duration = 30 }, } local function isMobCrowdControlled(guid)