Edit: I've now been running heroics for a few days with with IB and this CC, and I consider it mature and the best hpally healbot available, but that's just me . I've been running my 75~ paladin through dungeons as holy spec with this, with InstanceBuddy. It probably has some issues but has been completing VH/DTK reasonably. Most of the thresholds are harcoded, and furthermore it does not use the AoE heal because it was driving me oom, so thats commented out. It will also only beacon the tank (as defined by lfd roles). Code: using System; using System.Collections.Generic; using System.Linq; using System.Text; using Styx.Combat.CombatRoutine; using Styx.WoWInternals.WoWObjects; using Styx.WoWInternals; using Styx.Logic.Combat; using Styx.Helpers; using Styx.Logic.Pathing; using Styx; using Styx.Logic; using System.Threading; using TreeSharp; using Action = TreeSharp.Action; namespace HolyPvE { class HolyPvE : CombatRoutine { private WoWUnit lastCast; private WoWUnit tank; private Random rng; private void Log(params string[] list) { string msg = "[" + DateTime.Now.ToString("hh:mm:ss") + "] "; foreach (string s in list) { msg += s + " "; } Logging.Write(msg); } public override void Combat() { if (Me != null && Me.IsValid && Me.IsAlive && Me.IsInInstance) { ObjectManager.Update(); tank = GetTank(); if (tank == null) { tank = Me; } if (StyxWoW.GlobalCooldown || CancelHeal()) { //Log("gcd/cancelheal"); return; } else if (Self()) { //Log("self"); return; } else if (Healing()) { //Log("heal"); return; } else if (Cleansing()) { //Log("cleanse"); return; } else if (Judge()) { //Log("judge"); return; } else if (Buff()) { //Log("buff"); return; } else { //Log("nothing"); } } } private bool Judge() { //if (!isAuraActive("Judgements of the Pure")) //{ WoWUnit u = (from unit in ObjectManager.GetObjectsOfType<WoWUnit>(false, false) where unit.IsHostile where !unit.Dead where unit.CurrentTargetGuid == tank.Guid where unit.Distance < 20 where unit.InLineOfSight select unit ).FirstOrDefault(); if (u != null && CC("Judgement", u)) { C("Judgement", u); return true; } //} return false; } private bool MoveTo(WoWUnit u) { if (rng == null) { rng = new Random(); } if (!Me.IsMoving && u != null && u.Distance > 10) { Navigator.MoveTo(WoWMathHelper.CalculatePointAtSide(u.Location, u.Rotation, rng.Next(10), rng.Next(2) == 1)); return true; } else { return false; } } private bool CancelHeal() { if (Me.IsCasting && (lastCast != null && !lastCast.Dead && lastCast.HealthPercent >= 85)) { lastCast = null; SpellManager.StopCasting(); return true; } else if (Me.IsCasting) { return true; } else { return false; } } private WoWPlayer GetTank() { foreach (WoWPlayer p in Me.PartyMembers) { if (IsTank(p)) { return p; } } return null; } private string DeUnicodify(string s) { StringBuilder sb = new StringBuilder(); byte[] bytes = Encoding.UTF8.GetBytes(s); foreach (byte b in bytes) { if (b != 0) sb.Append("\\" + b); } return sb.ToString(); } private bool IsTank(WoWPlayer p) { return Lua.GetReturnValues("return UnitGroupRolesAssigned('" + DeUnicodify(p.Name) + "')").First() == "TANK"; } private bool Self() { if (Me.HealthPercent < 50 && CC("Divine Shield") && !isAuraActive("Forbearance")) { C("Divine Shield"); return true; } else if (Me.ManaPercent <= 75 && CC("Divine Plea")) { C("Divine Plea"); return true; } else { return false; } } private bool Healing() { WoWPlayer tar = GetHealTarget(); if (tar != null) { if (tar.Distance > 40 || !tar.InLineOfSight) { MoveTo(tar); return true; } else { String s = null; bool needCast = false; double hp = tar.HealthPercent; if (Me.Combat && hp < 50 && ((!isAuraActive("Divine Favor") && CC("Divine Favor")) || (!isAuraActive("Aura Mastery") && CC("Aura Mastery")) || (!isAuraActive("Avenging Wrath") && CC("Avenging Wrath"))) ) { Log("Anti-Wipe mode engaged!"); ChainSpells("Divine Favor", "Aura Mastery", "Avenging Wrath"); } if (hp < 30 && !tar.ActiveAuras.ContainsKey("Forbearance") && CC("Lay on Hands", tar)) { s = "Lay on Hands"; } else if (hp < 30 && tar.Guid != tank.Guid && !tar.ActiveAuras.ContainsKey("Forbearance") && CC("Hand of Protection", tar)) { s = "Hand of Protection"; } else if (tar.Guid == tank.Guid && BeaconNeedsRefresh(tank)) { s = "Beacon of Light"; } else if (Me.CurrentHolyPower == 3) { s = "Word of Glory"; } else if (CC("Holy Shock", tar)) { s = "Holy Shock"; } else if (Me.IsMoving) { WoWMovement.MoveStop(); Log("Stopping movement"); return true; } else if (isAuraActive("Infusion of Light") || isAuraActive("Speed of Light")) { s = "Holy Light"; needCast = true; } else if (hp < 50) { s = "Flash of Light"; needCast = true; } else { s = "Holy Light"; needCast = true; } if (s != null && CC(s, tar)) { C(s, tar); if (!needCast) { MoveTo(tar); } return true; } else { MoveTo(tar); return false; } } } else { return false; } } private bool CC(string spell, WoWUnit target) { return SpellManager.CanCast(spell, target); } private bool CC(string spell) { return SpellManager.CanCast(spell); } private void ChainSpells(params string[] spells) { string macro = ""; foreach (string s in spells) { macro += "CastSpellByName(\"" + s + "\", true);"; } Lua.DoString(macro); } private bool C(string spell, WoWUnit target) { if (SpellManager.Cast(spell, target)) { Log(spell + " @ " + target); lastCast = target; return true; } else { return false; } } private bool C(string spell) { lastCast = null; Log("Self-cast: " + spell); return SpellManager.Cast(spell); } private bool Cleansing() { WoWPlayer p = GetCleanseTarget(); if (p != null) { if (p.Distance > 40 || !p.InLineOfSight) { MoveTo(p); return true; } else if (CC("Cleanse", p)) { C("Cleanse", p); return true; } else { return false; } } else { return false; } } private WoWPlayer GetCleanseTarget() { return (from unit in ObjectManager.GetObjectsOfType<WoWPlayer>(true, true) orderby unit.HealthPercent ascending where !unit.Dead where !unit.IsGhost where unit.Distance < 80 where NeedsCleanse(unit) select unit).FirstOrDefault(); } private bool NeedsCleanse(WoWPlayer p) { foreach (WoWAura a in p.ActiveAuras.Values) { if (a.IsHarmful) { WoWDispelType t = a.Spell.DispelType; if (t == WoWDispelType.Disease || t == WoWDispelType.Magic || t == WoWDispelType.Poison) { return true; } } } return false; } private WoWPlayer GetHealTarget() { return (from unit in ObjectManager.GetObjectsOfType<WoWPlayer>(true, true) orderby unit.HealthPercent ascending where !unit.Dead where !unit.IsGhost where unit.Distance < 80 where unit.HealthPercent < 85 select unit).FirstOrDefault(); } private IEnumerable<WoWPlayer> GetResurrectTargets() { return (from unit in ObjectManager.GetObjectsOfType<WoWPlayer>(false, false) orderby unit.Distance ascending where unit.Dead where unit.IsInMyPartyOrRaid where !unit.IsGhost where unit.Distance < 100 select unit); } private bool Resurrecting() { foreach(WoWPlayer p in GetResurrectTargets()) { if(Blacklist.Contains(p.Guid, true)) { continue; } else { if (p.Distance > 40 || !p.InLineOfSight) { MoveTo(p); return true; } else if (CC("Redemption", p) && C("Redemption", p)) { Blacklist.Add(p, new TimeSpan(0, 0, 15)); return true; } else { return false; } } } return false; } private bool Buff() { if (Resurrecting()) { return true; } if (!isAuraActive("Seal of Insight")) { C("Seal of Insight"); return true; } if (!IsPaladinAura("Concentration Aura")) { C("Concentration Aura"); return true; } if (BeaconNeedsRefresh(tank)) { C("Beacon of Light", tank); return true; } foreach (WoWPlayer p in Me.PartyMembers) { if (p.Distance < 0 || p.Distance > 40 || p.Dead || p.IsGhost) continue; if (isAuraActive("Mark of the Wild", p) && !isAuraActive("Blessing of Might", p)) { C("Blessing of Might", p); return true; } else if (isAuraActive("Blessing of Kings", p) && !isAuraActive("Blessing of Might", p) && p.ActiveAuras["Blessing of Kings"].CreatorGuid != Me.Guid) { C("Blessing of Might", p); return true; } else if (!isAuraActive("Blessing of Kings", p) && !isAuraActive("Mark of the Wild", p)) { C("Blessing of Kings", p); return true; } } return false; } private bool isAuraActive(string name) { return isAuraActive(name, Me); } private bool isAuraActive(string name, WoWUnit u) { return u.ActiveAuras.ContainsKey(name); } private bool BeaconNeedsRefresh(WoWUnit u) { if (isAuraActive("Beacon of Light", u)) { return u.ActiveAuras["Beacon of Light"].TimeLeft.TotalSeconds <= 5; } else { return true; } } private bool IsPaladinAura(string aura) { string s = Lua.GetReturnVal<string>("return UnitAura(\"player\", \"" + aura + "\")", 0); return s != null; } public override sealed string Name { get { return "HolyPvE"; } } public override WoWClass Class { get { return WoWClass.Paladin; } } private static LocalPlayer Me { get { return ObjectManager.Me; } } public override bool NeedRest { get { return false; } } public override bool NeedPullBuffs { get { Combat(); return false; } } public override bool NeedCombatBuffs { get { Combat(); return false; } } public override bool NeedPreCombatBuffs { get { Combat(); return false; } } } }
Changelog I added rudimentary (read: dirty hack) resurrection logic: Code: moving to res Vessiran Distance: 45 moving to res Vessiran Distance: 43 moving to res Vessiran Distance: 41 cast Redemption Vessiran Distance: 36 added Vessiran Distance: 36 to res blacklist for 60s Unfortunately IB will fight over movement if the dead players are too far away from the tank. This is an IB bug that cannot be worked around. So hope your tank moves near the corpse. ----- LFD role identification is broken for UTF-16 names ... am working on it. ----- Finally solved that shit. It should properly beacon tanks with funny chars (?) now ----- Made a lot of changes. Oh-shit button is now compressed into one GCD. It should move around during Combat now but not to the detriment of healing (which should solve a lot of LoS problems that might come up). Buffing others should work now ----- It will now judge mobs when expedient (not at the cost of healing), in an attempt to keep Judgements of the Pure up. ----- Tried to seperate its execution from the combat state a bit ----- Some nice stuff that you will like ----- Won't blow antiwipe mode out of combat anymore lol
works fine with IB in heroics ! i'm using this one for holy because Convalesce is doing strange movement, when group is fighting and tank is not moving. nice work !
I've created one but I'm not comfortable releasing it publicly just yet. Perhaps soon. (Though it's a Bot not a CC, since it's nearly impossible to make a healbot inside a CC).
Hey there, Not certain why you would get that message. You might want to consider renaming the namespace and class name to Druidhealz, and make sure its in a file called Druidhealz.cs in the folder "CustomClasses\Druidhealz\". Also, you have a typo in Nature's Swiftness.
exemplar, found something I need help with. Ive noticed that with IB sometimes my toon doesnt go into combat when the tank pulls and the combat sequence never initiates. The bot just stands there doing nothing until he goes into combat. Can you think of any work around for this?
Yes. In NeedPreCombatBuff check if the Tank is in combat, and if he is, make it move to him. I am going to implement this in my CC later. Thanks for reporting this, I have noticed this a few times but did not understand why it was happening. Edit: In retrospect, there might not be a workaround if IB is blocking the thread with something dumb
exemplar, I found a way to do it (thanks mord). I included a snippet of the code he uses. this checks for heals needed prior to combat. This is pulled from convalescence so you will need to tailor it for your own use. I am doing it for the druid CC now. Code: public void SelfHeal() { if (InBg()) //Are we in a Battle Ground? { if ((Me.GetPowerPercent(WoWPowerType.Health) <= PvPRestWoGHealth)) { if ((SpellManager.CanCast("Word of Glory")) && (Me.CurrentHolyPower >= PvPRestWoGhp)) { Slog("#Out of Combat - HP% :" + Me.HealthPercent + ", Holy Power Count :" + Me.CurrentHolyPower + ", **Word of Glory**#"); SpellManager.Cast("Word of Glory", Me); //Do It! return; } } if ((Me.GetPowerPercent(WoWPowerType.Health) <= PvPRestLoDHealth)) { if ((SpellManager.CanCast("Light of Dawn")) && (Me.CurrentHolyPower >= PvPRestLoDhp)) { Slog("#Out of Combat - HP% :" + Me.HealthPercent + ", Holy Power Count :" + Me.CurrentHolyPower + ", **Light of Dawn**#"); SpellManager.Cast("Light of Dawn", Me); //Do It! return; } } if (Me.HealthPercent <= PvPRestDl) { if (SpellManager.CanCast("Divine Light")) { if (Me.IsMoving) WoWMovement.MoveStop(); Slog("#Out of Combat - HP% :" + Me.HealthPercent + ", **Divine Light**#"); SpellManager.Cast("Divine Light", Me); //Do It! Thread.Sleep(3250); return; } } if (Me.HealthPercent <= PvPRestHr) { if (SpellManager.CanCast("Holy Radiance")) { Slog("#Out of Combat - HP% :" + Me.HealthPercent + ", **Holy Radiance**#"); SpellManager.Cast("Holy Radiance", Me); //Do It! return; } } if (Me.HealthPercent <= PvPRestHl) { if (SpellManager.CanCast("Holy Light")) { if (Me.IsMoving) WoWMovement.MoveStop(); Slog("#Out of Combat - HP% :" + Me.HealthPercent + ", **Holy Light**#"); SpellManager.Cast("Holy Light", Me); //Do It! Thread.Sleep(3250); return; } } if (Me.ManaPercent <= PvPDivinePleaMana) { if (PvPDivinePleaRest) { if (Me.ManaPercent >= PvPRestMana) { if (SpellManager.CanCast("Divine Plea")) { Slog("#Out of Combat - Mana is at " + Me.ManaPercent + "%. **Divine Plea**#"); DivinePlea(); return; } } } } } else { if ((Me.GetPowerPercent(WoWPowerType.Health) <= RestWoGHealth)) { if ((SpellManager.CanCast("Word of Glory")) && (Me.CurrentHolyPower >= RestWoGhp)) { Slog("#Out of Combat - HP% :" + Me.HealthPercent + ", Holy Power Count :" + Me.CurrentHolyPower + ", **Word of Glory**#"); SpellManager.Cast("Word of Glory", Me); //Do It! return; } } if ((Me.GetPowerPercent(WoWPowerType.Health) <= RestLoDHealth)) { if ((SpellManager.CanCast("Light of Dawn")) && (Me.CurrentHolyPower >= RestLoDhp)) { Slog("#Out of Combat - HP% :" + Me.HealthPercent + ", Holy Power Count :" + Me.CurrentHolyPower + ", **Light of Dawn**#"); SpellManager.Cast("Light of Dawn", Me); //Do It! return; } } if (Me.HealthPercent <= RestDl) { if (SpellManager.CanCast("Divine Light")) { if (Me.IsMoving) WoWMovement.MoveStop(); Slog("#HP% :" + Me.HealthPercent + ", **Divine Light**#"); SpellManager.Cast("Divine Light", Me); //Do It! Thread.Sleep(3250); return; } } if (Me.HealthPercent <= RestHr) { if (SpellManager.CanCast("Holy Radiance")) { Slog("#Out of Combat - HP% :" + Me.HealthPercent + ", **Holy Radiance**#"); SpellManager.Cast("Holy Radiance", Me); //Do It! return; } } if (Me.HealthPercent <= RestHl) { if (SpellManager.CanCast("Holy Light")) { if (Me.IsMoving) WoWMovement.MoveStop(); Slog("#Out of Combat - HP% :" + Me.HealthPercent + ", **Holy Light**#"); SpellManager.Cast("Holy Light", Me); //Do It! Thread.Sleep(3250); return; } } if (Me.ManaPercent <= DivinePleaMana) { if (DivinePleaRest) { if (Me.ManaPercent >= RestMana) { if (SpellManager.CanCast("Divine Plea")) { Slog("#Out of Combat - Mana is at " + Me.ManaPercent + "%. **Divine Plea**#"); DivinePlea(); return; } } } } } }
I'm not sure why that would solve the problem you describe, as it only describes self-heals OOC, and thus would not serve to re-engage the tank if IB stopped following for some reason
ya, I pasted the code in haste. Basically, what is needed is a method that will check HP levels while NOT in combat and heal accordingly. Currently the bot does absolutely nothing while out of combat. codenameG was helping out but decided to just make his own framework. Anyway you could help me out with some code since you understand your own code =D? Like I said, it just needs code to check HP levels while outside of the combat portion of the CC and heal the tank who is IN combat. once that happens, the combat portion will take over and all will be good.
I am testing Code: else if (tank.ThreatInfo.ThreatStatus != ThreatStatus.UnitNotInThreatTable && !Me.Combat) { Logging.Write("Manually moving to tank"); MoveTo(tank); } in NeedPreCombatBuffs, but have yet to see the bug occur so that I can see whether it works or not - which is why I won't add it. I am not even sure that I am checking the threat status correctly. Basically it all depends on whether IB is blocking the CombatRoutine from executing completely, or whether something else is at play. In the first instance nothing can be done short of traversing their BT and rewriting chunks of their logic
are you trying to get into combat by proximity there? Really, the only thing that needs to be done is after the tnak has entered combat, throw a heal on him. That places you in combat. I am sure there is a way to do something like if tank is in combat, cast heal. and just put it in the pull or rest area. I will talk with some people about it. I know that codenameg is making his healing framework a BT.
What action to be done is a trivial point. I am more concerned with whether it is even possible to execute CombatRoutine code in the 'bugged situation'. I've been running IB unattended for hours and have yet to see it detect such a state
Wow, thanks for sharing it! Just did a few runs with it (VH nhc) and it's performing great. Sometimes less configuration doesn't mean it's not as good as those with a dozen options *g*. Very mana conservative (that's what I really like most)! This one as a bg healer *dream*... Regards + rep, Highend
This Thing heals better in Instances than i could ever do it, and gets to master situations in which I would normally cause a wipe (because i couldn't heal that good) Awesome CC for Instances, thanks a bunch, +rep for you!