I was inspired by the original DruidHealz done by JParker, so I spent the day redoing it with some new tricks.
EDIT: Tweaked it for maximum raid heals. Now it's so automated it has sucked most of the fun out of the game. :-| Definitely something you can use while watching TV tho.
To use it: Save this to a text file called 'DruidHealz.cs' in the HonorBuddy/CustomClasses folder.
Features:
Thanks JParker, for the original class!
EDIT: Tweaked it for maximum raid heals. Now it's so automated it has sucked most of the fun out of the game. :-| Definitely something you can use while watching TV tho.
To use it: Save this to a text file called 'DruidHealz.cs' in the HonorBuddy/CustomClasses folder.
Features:
- Required: Level 85 resto druid. This is not a leveling class.
- Prints all output to the third chat window, if it exists. This means you don't have to keep HonorBuddy in the foreground to see what it's up to - you can see it being displayed real-time in WoW.
- Follows tank, keeps within 20 yards of him at all times.
- Keeps 3 stacks of lifebloom on the tank at all times.
- Casts AOE heals when necessary
- Prioritizes healing targets by healer first, then rest of party, whoever is hurt the most.
- Removes debuffs with 'Remove Corruption'
- Rebirths tank only during combat, Revives all players out of combat.
- Does not move to grab a heal target! It will only hang around the tank, healing people within 40 yards. This way it won't run off the ledge, or into the mists, etc. If you want a heal, come within 40 yards of me.
- STOPS MOVING, but continues casting, when you press caps lock or scroll lock, so you can get out of the fire. Resumes automatic movement once caps lock is released.
- Doesn't change your current target. This makes it easier to intervene with manual spells when necessary.
Thanks JParker, for the original class!
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.Diagnostics;
using System.Drawing;
namespace Druidhealz
{
class Druidhealz : CombatRoutine
{
private WoWUnit tank;
List<WoWPlayer> people;
List<WoWPlayer> closePeople;
WoWPlayer healTarget;
WoWPlayer cleanseTarget;
WoWPlayer reviveTarget;
private double avgHealth;
ulong revive = 0;
DateTime debufftimer = DateTime.Now;
DateTime lastpeopledump = DateTime.Now;
public override sealed string Name { get { return "Druidhealz"; } }
public override WoWClass Class { get { return WoWClass.Druid; } }
private static LocalPlayer Me { get { return StyxWoW.Me; } }
public int ScrollLock { get { if (Me == null) return 1; if (System.Windows.Forms.Control.IsKeyLocked(System.Windows.Forms.Keys.Scroll)) return 1; return 0; } }
public int CapsLock { get { if (Me == null) return 1; if (System.Windows.Forms.Control.IsKeyLocked(System.Windows.Forms.Keys.CapsLock)) return 1; return 0; } }
public override void Pulse()
{
// Get lists of interesting people
if (Me == null || !Me.IsValid || !Me.IsAlive || Me.IsGhost || Me.Dead)
return;
people = Me.IsInRaid ? Me.RaidMembers : Me.PartyMembers;
if (!people.Contains(Me))
people.Add(Me);
tank = GetTank();
closePeople = people.Where(p => p.Distance <= 40).ToList();
avgHealth = closePeople.Average(p => p.HealthPercent);
healTarget = (from unit in closePeople where !unit.Dead && !unit.IsGhost && unit.InLineOfSight && unit.HealthPercent < 90 orderby (unit.Guid == Me.Guid || unit.Guid == tank.Guid ? unit.HealthPercent - 10 : unit.HealthPercent) select unit).FirstOrDefault();
cleanseTarget = (from unit in closePeople where !unit.Dead && !unit.IsGhost && unit.InLineOfSight && unit.HealthPercent < 90 && NeedsCleanse(unit) orderby unit.HealthPercent select unit).FirstOrDefault();
// Get the next revive target (but not the same one over and over)
List<WoWPlayer> reviveTargets = (from unit in closePeople where (unit.Dead || unit.IsGhost) && CC("Revive", unit) select unit).ToList();
if (reviveTargets.Count > 0)
{
revive += 1;
if (revive % 2 == 0)
reviveTargets.Reverse();
}
reviveTarget = reviveTargets.FirstOrDefault();
Combat();
}
public override void Combat()
{
// Display harmful debuffs
//DisplayDebuffs();
// Don't touch anything if I'm mounted or reating
if (!Me.Combat && (Me.HasAura("Food") || Me.HasAura("Drink")))
return;
if (Me.Mounted)
return;
// Keep tank in range
if ((StyxWoW.Me.Location.Distance(tank.Location) > 20 || !tank.InLineOfSight) && ScrollLock == 0 && CapsLock == 0)
{
On_MoveCloser(tank);
}
else if (StyxWoW.Me.IsMoving && ScrollLock == 0 && CapsLock == 0)
{
On_MoveStop();
}
// Rez dead tank
else if (tank.Dead && CC("Revive", tank))
{
C("Revive", tank);
}
else if (tank.Dead && CC("Rebirth", tank))
{
C("Rebirth", tank);
}
// Revive others
else if (reviveTarget != null && CC("Revive", reviveTarget))
{
C("Revive", reviveTarget);
}
// Heal group
else if (!StyxWoW.Me.HasAura("Mark of the Wild") && !StyxWoW.Me.HasAura("Blessing of Kings"))
{
C("Mark of the Wild");
}
else if (avgHealth < 75 && CC("Tranquility"))
{
C("Tranquility");
}
else if (avgHealth < 95 && CC("Wild Growth"))
{
C("Wild Growth");
}
// Buff self
else if (NeedsCleanse(Me) && CC("Remove Corruption"))
{
C("Remove Corruption");
}
//else if (Me.HealthPercent < 70 && !Me.HasAura("Nature's Grasp") && CC("Nature's Grasp"))
//{
// C("Nature's Grasp");
//}
else if (!Me.HasAura("Barkskin") && Me.HealthPercent < 90 && CC("Barkskin"))
{
C("Barkskin");
}
else if (Me.Combat && Me.ManaPercent < 50 && CC("Innervate"))
{
C("Innervate");
}
// Keep swiftmend circle on tank
else if (CC("Rejuvenation", tank) && !tank.HasAura("Rejuvenation") && tank.Combat)
{
C("Rejuvenation", tank);
}
else if (CC("Swiftmend", tank) && tank.HasAura("Rejuvenation") && tank.Combat)
{
C("Swiftmend", tank);
}
// Keep lifebloom on tank
else if (tank.Combat &&
StackCount("Lifebloom", tank) < 3 &&
CC("Lifebloom", tank))
{
C("Lifebloom", tank);
}
// Refresh lifebloom if tank's health is high
else if (tank.Combat &&
StackCount("Lifebloom", tank) == 3 &&
tank.HealthPercent > 95 &&
CC("Lifebloom", tank) &&
tank.Auras["Lifebloom"].TimeLeft.TotalMilliseconds < 2000)
{
CC("Lifebloom", tank);
}
// Heal singles
else if (healTarget != null)
{
if (!healTarget.HasAura("Rejuvenation") && CC("Rejuvenation", healTarget))
{
C("Rejuvenation", healTarget);
}
else if (healTarget.HealthPercent < 60 && healTarget.HasAura("Rejuvenation") && CC("Swiftmend", healTarget))
{
C("Swiftmend", healTarget);
}
else if (healTarget.HealthPercent < 60 && CC("Healing Touch", healTarget))
{
C("Nature's Swiftness", healTarget);
C("Healing Touch", healTarget);
}
else if (CC("Nourish", healTarget))
{
C("Nourish", healTarget);
}
else if (CC("Regrowth", healTarget) && !healTarget.HasAura("Regrowth"))
{
C("Regrowth", healTarget);
}
}
// Remove corruption
else if (cleanseTarget != null && CC("Remove Corruption", cleanseTarget))
{
C("Remove Corruption");
}
}
private uint StackCount(string auraname, WoWUnit unit)
{
if (!unit.HasAura(auraname))
return 0;
return unit.Auras[auraname].StackCount;
}
private void DisplayDebuffs()
{
if (DateTime.Now < debufftimer) return;
debufftimer = DateTime.Now.AddSeconds(2);
List<WoWAura> lst = Me.GetAllAuras().Where(a => a.IsHarmful && a.Name != "Dungeon Cooldown").ToList();
foreach (WoWAura aura in lst)
log(Color.Red, " bad aura! name={0} spell={1} timeleft={2} ms", aura.Name, aura.Spell, aura.TimeLeft.TotalMilliseconds);
}
private bool On_MoveStop()
{
if (!StyxWoW.Me.IsMoving) return false;
Navigator.PlayerMover.MoveStop();
StyxWoW.SleepForLagDuration();
return true;
}
private bool On_MoveCloser(WoWUnit unit)
{
if (unit == null) return false;
double dist = unit.Location.Distance(Me.Location);
if (dist < 5 && unit.InLineOfSight)
return false;
log(" Move closer to {0} health {1:0} dist {2:0.0}", unit.Name, unit.HealthPercent, dist);
Navigator.MoveTo(unit.Location);
return true;
}
private WoWPlayer GetTank()
{
if (RaFHelper.Leader != null)
return RaFHelper.Leader;
WoWPlayer p = people.Where(a => Role(a) == "TANK").FirstOrDefault();
if (p == null)
return Me;
return p;
}
private void DumpPeople()
{
foreach (WoWPlayer p in people)
{
string t = Role(p);
log(Color.Orange, "{0} - {1}", p.Name, t);
}
}
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 string Role(WoWPlayer p)
{
if (p == null) return null;
string s = DeUnicodify(p.Name);
string t = Lua.GetReturnValues("return UnitGroupRolesAssigned('" + DeUnicodify(p.Name) + "')").First();
return t;
}
private bool CC(string spell, WoWUnit target)
{
return SpellManager.CanCast(spell, target);
}
private bool CC(string spell)
{
return SpellManager.CanCast(spell);
}
private bool C(string spell, WoWUnit target)
{
WoWSpell sp;
if (target == null)
target = Me;
if (!SpellManager.HasSpell(spell))
{
log(Color.Red, "Don't know spell {0}", spell);
return false;
}
sp = SpellManager.Spells[spell];
if (sp.CastTime > 0)
WoWMovement.MoveStop();
if (SpellManager.Cast(spell, target))
{
log("Cast {0} on {1} health {2:0.0} dist {3:0.0}", spell, target.Name, target.HealthPercent, target.Location.Distance(StyxWoW.Me.Location));
while (StyxWoW.Me.IsCasting || StyxWoW.GlobalCooldown)
StyxWoW.SleepForLagDuration();
return true;
}
else
{
log("Can't cast {0} on {1} health {2:0.0} dist {3:0.0}", spell, target.Name, target.HealthPercent, target.Location.Distance(StyxWoW.Me.Location));
return false;
}
}
private bool C(string spell)
{
return C(spell, Me);
}
private bool NeedsCleanse(WoWPlayer p)
{
if (Me.ManaPercent < 75) return false;
return p.ActiveAuras.Where(a => a.Value.IsHarmful && (
a.Value.Spell.DispelType == WoWDispelType.Curse ||
a.Value.Spell.DispelType == WoWDispelType.Magic ||
a.Value.Spell.DispelType == WoWDispelType.Poison)
).ToList().Count > 0;
}
public override bool NeedRest
{
get
{
if (Me.Combat || Me.Dead || Me.IsGhost || Me.HealthPercent < 1.0 || Me.IsSwimming || Me.Mounted) return false;
if (Me.Auras.ContainsKey("Drink") && Me.ManaPercent < 95)
return true;
if (Me.ManaPercent < 50 && Styx.Logic.Inventory.Consumable.GetBestDrink(false) != null)
return true;
return false;
}
}
public override void Rest()
{
if (Me.Auras.ContainsKey("Drink"))
{
// keep drinking
}
else if (Me.ManaPercent < 50)
{
On_MoveStop();
Styx.Logic.Common.Rest.DrinkImmediate();
}
else if (reviveTarget != null)
{
C("Revive", reviveTarget);
}
}
public void log(string format, params object[] args)
{
log(Color.PapayaWhip, format, args);
}
public void log(Color color, string format, params object[] args)
{
string s = string.Format(string.Format("{0:hh:mm:ss} ", DateTime.Now) + format, args);
Logging.Write(color, s);
string fmt = @"getglobal(""ChatFrame3""):AddMessage(""{0}"", {1}, {2}, {3}, 0);";
Lua.DoString(string.Format(fmt, s, color.R / 255.0, color.G / 255.0, color.B / 255.0));
}
}
}
Last edited: