public static Composite Cast(SpellFindDelegate ssd, SimpleBooleanDelegate checkMovement, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements, SimpleBooleanDelegate cancel = null, LagTolerance allow = LagTolerance.Yes, bool skipWowCheck = false, CanCastDelegate canCast = null, HasGcd gcd = HasGcd.Yes) { // only need to check these at creation time if (ssd == null || checkMovement == null || onUnit == null || requirements == null) return new ActionAlwaysFail(); if (canCast == null) canCast = CanCastHack; Composite comp = new PrioritySelector( // create a CastContext object to save passed in context and other values ctx => new CastContext(ctx, ssd, onUnit, gcd), new Sequence( // cast the spell, saving state information including if we queued this cast new Action(ret => { CastContext cctx = ret.CastContext(); if (cctx.spell == null) return RunStatus.Failure; if (cctx.unit == null) return RunStatus.Failure; if (!requirements(cctx.context)) return RunStatus.Failure; if (checkMovement(cctx.context) && Me.IsMoving && !AllowMovingWhileCasting(cctx.spell)) { if (SingularSettings.DebugSpellCasting) Logger.WriteDebug("skipping Spell.Cast({0},[{1}]) because we are moving", cctx.unit.SafeName(), cctx.spell.Name); return RunStatus.Failure; } // check we can cast it on target without checking for movement // if (!Spell.CanCastHack(_spell, cctx.unit, true, false, allow == LagTolerance.Yes)) if (!canCast(cctx.sfr, cctx.unit, skipWowCheck)) { if (SingularSettings.DebugSpellCasting) Logger.WriteDebug("skipping Spell.Cast({0},[{1}]) because CanCastHack failed", cctx.unit.SafeName(), cctx.spell.Name); return RunStatus.Failure; } // save status of queueing spell (lag tolerance - the prior spell still completing) cctx.IsSpellBeingQueued = allow == LagTolerance.Yes && (Spell.GcdActive || StyxWoW.Me.IsCasting || StyxWoW.Me.IsChanneling); const int PENANCE = 047540; LogCast( cctx.spell.Name, cctx.unit, cctx.health, cctx.distance, cctx.spell.IsHeal() ? true : (cctx.spell.Id == PENANCE && cctx.unit.IsFriendly) ); if (SingularSettings.DebugSpellCasting) Logger.WriteDebug("Cast('{0}'): dist:{1:F3}, need({2}), hitbox:{3:F3}", cctx.spell.Name, cctx.unit.Distance, cctx.spell.IsMeleeSpell ? "Melee" : (!cctx.spell.HasRange ? "None" : string.Format("min={0:F3},max={1:F3}", cctx.spell.MinRange, cctx.spell.MaxRange)), cctx.unit.CombatReach ); if (!Spell.CastPrimative(cctx.spell, cctx.unit)) { Logger.Write(Color.LightPink, "cast of {0} on {1} failed!", cctx.spell.Name, cctx.unit.SafeName()); return RunStatus.Failure; } SingularRoutine.UpdateDiagnosticCastingState(); return RunStatus.Success; }), new Action(r => { if (SingularSettings.DebugSpellCasting) { CastContext cctx = r.CastContext(); Logger.WriteFile("Spell.Cast[{0}]: checking to ensure cast in progress", cctx.spell.Name); } return RunStatus.Success; }), // for instant spell, wait for GCD to start // for non-instant spell, wait for .IsCasting / .IsChanneling to start new PrioritySelector( new Wait( TimeSpan.FromMilliseconds(350), until => { CastContext cctx = until.CastContext(); if (gcd == HasGcd.No) { if (SingularSettings.DebugSpellCasting) Logger.WriteFile("Spell.Cast[{0}]: has no GCD, status GCD={1}, remains={2}", cctx.spell.Name, Spell.IsGlobalCooldown(allow).ToYN(), (long)Spell.GcdTimeLeft.TotalMilliseconds); return true; } if (cctx.spell.IsInstantCast() && Spell.GcdTimeLeft.TotalMilliseconds > 650) { if (SingularSettings.DebugSpellCasting) Logger.WriteFile("Spell.Cast[{0}]: is instant, status GCD={1}, remains={2}", cctx.spell.Name, Spell.IsGlobalCooldown(allow).ToYN(), (long)Spell.GcdTimeLeft.TotalMilliseconds); return true; } if (Me.CurrentCastTimeLeft.TotalMilliseconds > 750) { if (SingularSettings.DebugSpellCasting) Logger.WriteFile( "Spell.Cast[{0}]: cast time {1} left, iscasting={2}", cctx.spell.Name, (long)Me.CurrentCastTimeLeft.TotalMilliseconds, Spell.IsCasting(allow).ToYN()); return true; } if (Me.CurrentChannelTimeLeft.TotalMilliseconds > 750) { if (SingularSettings.DebugSpellCasting) Logger.WriteFile( "Spell.Cast[{0}]: channel spell and channel has {1} left, ischanneling={2}", cctx.spell.Name, (long)Me.CurrentChannelTimeLeft.TotalMilliseconds, Spell.IsChannelling(allow).ToYN()); return true; } return false; }, new ActionAlwaysSucceed() ), new Action( r => { if (SingularSettings.DebugSpellCasting) { CastContext cctx = r.CastContext(); Logger.WriteFile( "Spell.Cast[{0}]: timed out failing to detect spell in progress - gcdleft={1}/{2}, castleft={3}/{4}, chanleft={5}/{6}", cctx.spell.Name, Spell.IsGlobalCooldown(allow).ToYN(), (long)Spell.GcdTimeLeft.TotalMilliseconds, Spell.IsCasting(allow).ToYN(), (long)Me.CurrentCastTimeLeft.TotalMilliseconds, Spell.IsCasting(allow).ToYN(), (long)Me.CurrentChannelTimeLeft.TotalMilliseconds); } return RunStatus.Success; }) ), // now check for one of the possible done casting states new PrioritySelector( // for cast already ended, assume it has no Global Cooldown new Decorator( ret => !Spell.IsGlobalCooldown() && !Spell.IsCastingOrChannelling(), new Action(r => { CastContext cctx = r.CastContext(); if (SingularSettings.DebugSpellCasting) { if (!Spell.IsGlobalCooldown()) Logger.WriteFile("Spell.Cast(\"{0}\"): complete, no gcd active, lag={0} hasgcd={1}", cctx.spell.Name, allow, gcd); else Logger.WriteFile("Spell.Cast(\"{0}\"): complete, no cast in progress", cctx.spell.Name); } return RunStatus.Success; }) ), // for instant or no cancel method given, we are done new Decorator( ret => gcd == HasGcd.No || cancel == null || ret.CastContext().spell.IsInstantCast(), new Action(r => { CastContext cctx = r.CastContext(); if (SingularSettings.DebugSpellCasting) { if (gcd == HasGcd.No) Logger.WriteFile("Spell.Cast(\"{0}\"): complete, hasgcd=No", cctx.spell.Name); else if (r.CastContext().spell.IsInstantCast()) Logger.WriteFile("Spell.Cast(\"{0}\"): complete, is instant cast", cctx.spell.Name); else Logger.WriteFile("Spell.Cast(\"{0}\"): complete, no cancel delegate given", cctx.spell.Name); } return RunStatus.Success; }) ), // while casting/channeling call the cancel method to see if we should abort new Wait(12, until => { CastContext cctx = until.CastContext(); SingularRoutine.UpdateDiagnosticCastingState(); // Interrupted or finished casting. if (!Spell.IsCastingOrChannelling(allow)) { Logger.WriteDebug("Spell.Cast(\"{0}\"): complete, iscasting=false", cctx.spell.Name); return true; } // check cancel delegate if we are finished if (cancel(cctx.context)) { SpellManager.StopCasting(); Logger.Write(LogColor.Cancel, "/cancel {0} on {1} @ {2:F1}%", cctx.spell.Name, cctx.unit.SafeName(), cctx.unit.HealthPercent); return true; } // continue casting/channeling at this point return false; }, new ActionAlwaysSucceed() ), // if we are here, we timed out after 12 seconds (very odd) new Action(r => { CastContext cctx = r.CastContext(); Logger.WriteDebug("Spell.Cast(\"{0}\"): aborting, timed out waiting on cast, gcd={1} cast={2} chanl={3}", cctx.spell.Name, Spell.IsGlobalCooldown().ToYN(), Spell.IsCasting().ToYN(), Spell.IsChannelling().ToYN()); return RunStatus.Success; }) ), // made it this far then we are RunStatus.Success, so reset wowunit reference and return new Action(ret => { CastContext cctx = ret.CastContext(); cctx.unit = null; cctx.spell = null; return RunStatus.Success; }) ), // cast Sequence failed, so only thing left is to reset cached references and report failure new Action(ret => { CastContext cctx = ret.CastContext(); cctx.unit = null; cctx.spell = null; return RunStatus.Failure; }) ); // when no cancel method in place, we will return immediately so..... // .. throttle attempts at casting this spell. note: this only limits this // .. instance of the spell.cast behavior. in other words, if this is for a cast // .. of flame shock, it would only throttle this behavior tree instance, not any // .. other trees which also call Spell.Cast("flame shock") if (cancel == null) comp = new Throttle( TimeSpan.FromMilliseconds(SingularSettings.Instance.SameSpellThrottle), comp); return comp; }
// always create passing the existing context so it is preserved for delegate usage internal CastContext(object ctx, SpellFindDelegate ssd, UnitSelectionDelegate onUnit, HasGcd gcd = HasGcd.Yes) { if (ssd == null || onUnit == null) return; if (ssd(ctx, out sfr)) { spell = sfr.Override ?? sfr.Original; name = spell.Name; context = ctx; unit = onUnit(ctx); // health/dist change quickly, so grab these now where // .. we check requirements so the log message we output // .. later reflects what they were when we were testing // .. as opposed to what they may have changed to // .. (since spell lookup, move while casting check, and cancast take time) if (unit != null && unit.IsValid) { health = unit.HealthPercent; distance = unit.SpellDistance(); } } }
public static Composite Cast(string name, SimpleBooleanDelegate checkMovement, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements, SimpleBooleanDelegate cancel = null, LagTolerance allow = LagTolerance.Yes, HasGcd gcd = HasGcd.Yes) { return Cast(n => name, checkMovement, onUnit, requirements, cancel, allow, gcd: gcd); }
/// <summary> /// Creates a behavior to cast a spell by name, with special requirements, on a specific unit. Will make sure any spell with /// a non-zero cast time (everything not instant) will stay here until passing the latency boundary (point where .IsCasting == false while cast is in progress.) /// Returns RunStatus.Success if successful, RunStatus.Failure otherwise. Note: will return as soon as spell cast is in progress, unless cancel delegate provided /// </summary> /// <remarks> /// Created 5/2/2011. /// </remarks> /// <param name = "name">The name.</param> /// <param name="checkMovement"></param> /// <param name = "onUnit">The on unit.</param> /// <param name = "requirements">The requirements.</param> /// <param name="cancel">The cancel cast in progress delegate</param> /// <param name="allow">allow next spell to queue before this one completes</param> /// <returns>.</returns> /* public static Composite Cast(SimpleStringDelegate name, SimpleBooleanDelegate checkMovement, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements, SimpleBooleanDelegate cancel = null, LagTolerance allow = LagTolerance.Yes, bool skipWowCheck = false) { return Cast(name, checkMovement, onUnit, requirements, cancel, allow, skipWowCheck, null); } */ /// <summary> /// Creates a behavior to cast a spell by name, with special requirements, on a specific unit. Will make sure any spell with /// a non-zero cast time (everything not instant) will stay here until passing the latency boundary (point where .IsCasting == false while cast is in progress.) /// Returns RunStatus.Success if successful, RunStatus.Failure otherwise. Note: will return as soon as spell cast is in progress, unless cancel delegate provided /// </summary> /// <remarks> /// Created 5/2/2011. /// </remarks> /// <param name = "name">The name.</param> /// <param name="checkMovement"></param> /// <param name = "onUnit">The on unit.</param> /// <param name = "requirements">The requirements.</param> /// <param name="cancel">The cancel cast in progress delegate</param> /// <param name="allow">allow next spell to queue before this one completes</param> /// <param name="allow">check if spell can be cast</param> /// <returns>.</returns> public static Composite Cast(SimpleStringDelegate name, SimpleBooleanDelegate checkMovement, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements, SimpleBooleanDelegate cancel = null, LagTolerance allow = LagTolerance.Yes, bool skipWowCheck = false, CanCastDelegate canCast = null, HasGcd gcd = HasGcd.Yes) { SpellFindDelegate ssd = (object ctx, out SpellFindResults sfr) => { if (name != null) { string spellName = name(ctx); if (spellName != null) { return SpellManager.FindSpell(spellName, out sfr); } else { AddUndefinedSpell(spellName); } } sfr = EmptySFR; return false; }; return Cast(ssd, checkMovement, onUnit, requirements, cancel, allow, skipWowCheck, canCast, gcd); }
public static Composite BuffSelf(SimpleIntDelegate idd, SimpleBooleanDelegate requirements, HasGcd gcd) { return Buff(idd, on => Me, requirements, gcd); }
/// <summary> /// Creates a behavior to cast a buff by name, with special requirements, on a specific unit. Returns /// RunStatus.Success if successful, RunStatus.Failure otherwise. /// </summary> /// <remarks> /// Created 5/2/2011. /// </remarks> /// <param name = "spellId">The ID of the buff</param> /// <param name = "onUnit">The on unit</param> /// <param name = "requirements">The requirements.</param> /// <returns></returns> public static Composite Buff(SimpleIntDelegate spellId, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements, HasGcd gcd = HasGcd.Yes) { return new Decorator( req => onUnit(req) != null && onUnit(req).Auras.Values.All(a => a.SpellId != spellId(req)), Cast(spellId, onUnit, requirements) ); }
public static Composite BuffSelfAndWait(SimpleStringDelegate name, SimpleBooleanDelegate requirements = null, int expirSecs = 0, CanRunDecoratorDelegate until = null, bool measure = false, HasGcd gcd = HasGcd.Yes) { if (requirements == null) requirements = req => true; if (until == null) until = u => StyxWoW.Me.HasAura(name(u)); return new Sequence( BuffSelf(name, requirements, expirSecs, gcd), new PrioritySelector( new DynaWait( time => TimeSpan.FromMilliseconds(Me.Combat ? 500 : 1000), until, new ActionAlwaysSucceed(), measure ), new Action(r => { Logger.WriteDiagnostic("BuffSelfAndWait: buff of [{0}] failed", name(r)); return RunStatus.Failure; }) ) ); }
public static Composite BuffSelfAndWaitPassive(SimpleStringDelegate name, SimpleBooleanDelegate requirements = null, int expirSecs = 0, CanRunDecoratorDelegate until = null, HasGcd gcd = HasGcd.Yes) { if (requirements == null) requirements = req => true; if (until == null) until = u => StyxWoW.Me.HasAura(u as string); return new PrioritySelector( ctx => { string spellName = name(ctx); SpellFindResults sfr; if (SpellManager.FindSpell(spellName, out sfr)) { spellName = (sfr.Override ?? sfr.Original).Name; } return spellName; }, new Decorator( req => SpellManager.HasSpell( req as string) && !Me.HasAura(req as string) && !DoubleCastContains(Me, req as string), new Sequence( Spell.Cast( sp => sp as string, mov => true, on => Me, requirements, cancel => false, LagTolerance.Yes, false, null, gcd: gcd), new PrioritySelector( new DynaWait( time => TimeSpan.FromMilliseconds(Me.Combat ? 500 : 1000), until, new Action( r => UpdateDoubleCast( r as string, Me, 3000 )) ), new Action(r => { Logger.WriteDiagnostic("BuffSelfAndWaitPassive: buff of [{0}] failed", name(r)); return RunStatus.Failure; }) ) ) ) ); }
public static Composite BuffSelfAndWait(string name, SimpleBooleanDelegate requirements = null, int expirSecs = 0, CanRunDecoratorDelegate until = null, bool measure = false, HasGcd gcd = HasGcd.Yes) { return BuffSelfAndWait(b => name, requirements, expirSecs, until, measure, gcd); }
public static Composite BuffSelfAndWait(int id, SimpleBooleanDelegate requirements = null, HasGcd gcd = HasGcd.Yes) { WoWSpell spell = WoWSpell.FromId(id); if (spell == null || !SpellManager.HasSpell(spell.Id)) return new ActionAlwaysFail(); if (requirements == null) requirements = req => true; return new Sequence( BuffSelf(idd => id, requirements, gcd), new PrioritySelector( new DynaWait( time => TimeSpan.FromMilliseconds(Me.Combat ? 500 : 1000), until => StyxWoW.Me.HasAura(id), new ActionAlwaysSucceed() ), new Action(r => { WoWSpell s = WoWSpell.FromId(id); Logger.WriteDiagnostic("BuffSelfAndWait: aura [{0}] #{1} not applied!!!", s == null ? "(null)" : s.Name, id); return RunStatus.Failure; }) ) ); }
public static Composite BuffSelf(SimpleStringDelegate name, SimpleBooleanDelegate requirements, int expirSecs, HasGcd gcd = HasGcd.Yes) { return Buff(name, expirSecs, on => Me, require: requirements, gcd:gcd); }
public static Composite BuffSelf(string name, SimpleBooleanDelegate requirements, int expirSecs = 0, HasGcd gcd = HasGcd.Yes) { return Buff(b => name, expirSecs, on => Me, requirements, false, gcd); }
public static Composite Buff(SimpleStringDelegate name, TimeSpan expires, UnitSelectionDelegate onUnit = null, SimpleBooleanDelegate require = null, bool myBuff = true, HasGcd gcd = HasGcd.Yes, params string[] buffNames) { if (onUnit == null) onUnit = u => Me.CurrentTarget; if (require == null) require = req => true; return new Decorator( ret => { if (onUnit == null || name == null || require == null) return false; _buffUnit = onUnit(ret); if (_buffUnit == null) return false; _buffName = name(ret); if (_buffName == null) return false; SpellFindResults sfr; if (!SpellManager.FindSpell(_buffName, out sfr)) { AddUndefinedSpell(_buffName); return false; } WoWSpell spell = sfr.Override ?? sfr.Original; _buffName = spell.Name; if (DoubleCastContains(_buffUnit, _buffName)) return false; if (!spell.CanCast && (sfr.Override == null || !sfr.Original.CanCast)) { if (SingularSettings.DebugSpellCasting) Logger.WriteFile("BuffCanCast[{0}]: spell specific CanCast failed (#{1})", spell.Name, spell.Id); return false; } bool hasExpired; if (!buffNames.Any()) { hasExpired = _buffUnit.HasAuraExpired(_buffName, expires, myBuff); if (SingularSettings.DebugSpellCasting) { if (hasExpired ) Logger.WriteDebug("Buff=expired: '{0}')={1}: hasspell={2}, auraleft={3:F1} secs", _buffName, hasExpired, SpellManager.HasSpell(_buffName).ToYN(), _buffUnit.GetAuraTimeLeft(_buffName, true).TotalSeconds); else Logger.WriteDebug("Buff=present: '{0}')={1}: hasspell={2}, auraleft={3:F1} secs", _buffName, hasExpired, SpellManager.HasSpell(_buffName).ToYN(), _buffUnit.GetAuraTimeLeft(_buffName, true).TotalSeconds); } return hasExpired; } hasExpired = SpellManager.HasSpell(_buffName) && buffNames.All(b => _buffUnit.HasKnownAuraExpired(b, expires, myBuff)); if (hasExpired && SingularSettings.DebugSpellCasting) Logger.WriteDebug("Spell.Buff(r=>'{0}')={1}: hasspell={2}, all auras less than {3:F1} secs", _buffName, hasExpired, SpellManager.HasSpell(_buffName).ToYN(), expires.TotalSeconds); return hasExpired; }, new Sequence( // new Action(ctx => _lastBuffCast = name), Cast(sp => _buffName, chkMov => true, onUnit, require, cancel => false /* causes cast to complete */, gcd: gcd ), new Action(ret => UpdateDoubleCast( _buffName, _buffUnit)) ) ); }
public static Composite Buff(SimpleStringDelegate name, int expirSecs, UnitSelectionDelegate onUnit = null, SimpleBooleanDelegate require = null, bool myBuff = true, HasGcd gcd = HasGcd.Yes, params string[] buffNames) { return Buff(name, TimeSpan.FromSeconds(expirSecs), onUnit, require, myBuff, gcd, buffNames); }
public static Composite Buff(string name, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements, HasGcd gcd = HasGcd.Yes, params string[] buffNames) { return Buff(name, false, onUnit, requirements, buffNames); }