예제 #1
0
파일: Spell.cs 프로젝트: aash/Singular
        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;
        }
예제 #2
0
        private static Composite AddCommonBehaviorPrefix( Composite composite, BehaviorType behav)
        {
            if (behav == BehaviorType.LossOfControl)
            {
                composite = new Decorator(
                    ret => HaveWeLostControl,
                    new PrioritySelector(
                        new Action(r =>
                        {
                            if (!StyxWoW.IsInGame)
                            {
                                Logger.WriteDebug(Color.White, "Not in game...");
                                return RunStatus.Success;
                            }

                            return RunStatus.Failure;
                        }),
                        new ThrottlePasses(1, 1, new Decorator(ret => Me.Fleeing, new Action(r => { Logger.Write( LogColor.Hilite, "FLEEING! (loss of control)"); return RunStatus.Failure; }))),
                        new ThrottlePasses(1, 1, new Decorator(ret => Me.Stunned, new Action(r => { Logger.Write( LogColor.Hilite, "STUNNED! (loss of control)"); return RunStatus.Failure; }))),
                        new ThrottlePasses(1, 1, new Decorator(ret => Me.IsSilenced(), new Action(r => { Logger.Write( LogColor.Hilite, "SILENCED! (loss of control)"); return RunStatus.Failure; }))),
                        new Throttle(1,
                            new PrioritySelector(
                                composite ?? new ActionAlwaysFail(),
                                new Decorator(
                                    ret => SingularSettings.Instance.UseRacials,
                                    new PrioritySelector(
                                        Spell.Cast("Will of the Forsaken", on => Me, ret => Me.Race == WoWRace.Undead && Me.Fleeing),
                                        Spell.Cast("Every Man for Himself", on => Me, ret => Me.Race == WoWRace.Human && (Me.Stunned || Me.Fleeing))
                                        )
                                    ),

                                Item.UseEquippedTrinket(TrinketUsage.CrowdControlled),
                                Item.UseEquippedTrinket(TrinketUsage.CrowdControlledSilenced)
                                )
                            ),
                        new ActionAlwaysSucceed()
                        )
                    );
            }

            if (behav == BehaviorType.Rest)
            {
                Composite combatInRestCheck;
                if (!SingularSettings.Debug)
                    combatInRestCheck = new ActionAlwaysFail();
                else
                    combatInRestCheck = new Throttle(
                        1,
                        new ThrottlePasses(
                            1,
                            TimeSpan.FromSeconds(1),
                            RunStatus.Failure,
                            new Decorator(
                                req => Me.IsActuallyInCombat,
                                new Sequence(
                                    new PrioritySelector(
                                        new ThrottlePasses(
                                            1, 5,
                                            new Action(r => Logger.Write(Color.Yellow, "Bot Error: {0} or plugin called Rest behavior while in combat", SingularRoutine.GetBotName()))
                                            ),
                                        new Action(r => Logger.WriteDebug(Color.Yellow, "Bot Error: {0} or plugin called Rest behavior while in combat", SingularRoutine.GetBotName()))
                                        ),
                                    new ActionAlwaysFail()
                                    )
                                )
                            )
                        );

                composite = new LockSelector(
                    new CallWatch("Rest",
                        new Decorator(
                            ret => !Me.IsFlying && AllowBehaviorUsage() && !SingularSettings.Instance.DisableNonCombatBehaviors,
                            new PrioritySelector(

                                // TestDynaWait(),

                                // following is to allow cancelling of kiting in progress
                                new Decorator(
                                    ret => Kite.IsKitingActive(),
                                    new HookExecutor(HookName("KitingBehavior"))
                                    ),

                                new Decorator(
                                    req => ShouldWeFightDuringRest(),
                                    new PrioritySelector(
                                        Safers.EnsureTarget(),
                                        new Decorator(
                                            req => Unit.ValidUnit(Me.CurrentTarget) && Me.CurrentTarget.SpellDistance() < 45,
                                            new PrioritySelector(
                                                new ThrottlePasses(
                                                    1, TimeSpan.FromSeconds(15), RunStatus.Failure,
                                                    new Action(r => Logger.WriteDiagnostic(LogColor.Hilite, "Forcing combat from Rest Behavior")),
                                                    new ActionAlwaysFail()
                                                    ),
                                                SingularRoutine.Instance.HealBehavior,
                                                SingularRoutine.Instance.CombatBuffBehavior,
                                                SingularRoutine.Instance.CombatBehavior,
                                                new ActionAlwaysSucceed()
                                                )
                                            )
                                        )
                                    ),

                                // new Action(r => { _guidLastTarget = 0; return RunStatus.Failure; }),
                                new Decorator(
                                    req => !OkToCallBehaviorsWithCurrentCastingStatus(allow: LagTolerance.No),
                                    new ActionAlwaysSucceed()
                                    ),

                                // lost control in Rest -- force a RunStatus.Failure so we don't loop in Rest
                                new Sequence(
                                    SingularRoutine.Instance._lostControlBehavior,
                                    new ActionAlwaysFail()
                                    ),

                                // skip Rest logic if we lost control (since we had to return Fail to prevent Rest loop)
                                new Decorator(
                                    req => !HaveWeLostControl,
                                    composite ?? new ActionAlwaysFail()
                                    )
                                )
                            )
                        )
                    );
            }

            if (behav == BehaviorType.PreCombatBuffs)
            {
                Composite precombatInCombatCheck;
                if (!SingularSettings.Debug)
                    precombatInCombatCheck = new ActionAlwaysFail();
                else
                    precombatInCombatCheck = new Throttle(
                        1,
                        new ThrottlePasses(
                            1,
                            TimeSpan.FromSeconds(1),
                            RunStatus.Failure,
                            new Decorator(
                                req => Me.IsActuallyInCombat,
                                new Sequence(
                                    new PrioritySelector(
                                        new ThrottlePasses(
                                            1, 5,
                                            new Action(r => Logger.Write(Color.Yellow, "Bot Error: {0} or plugin called PreCombatBuff behavior while in combat", SingularRoutine.GetBotName()))
                                            ),
                                        new Action(r => Logger.WriteDebug(Color.Yellow, "Bot Error: {0} or plugin called PreCombatBuff behavior while in combat", SingularRoutine.GetBotName()))
                                        ),
                                    new ActionAlwaysFail()
                                    )
                                )
                            )
                        );

                composite = new LockSelector(
                    new CallWatch("PreCombat",
                        new Decorator(  // suppress non-combat buffing if standing around waiting on DungeonBuddy or BGBuddy queues
                            ret => !Me.Mounted
                                && !Me.HasAnyAura("Darkflight")
                                && !SingularSettings.Instance.DisableNonCombatBehaviors
                                && AllowNonCombatBuffing(),
                            new PrioritySelector(
                                new Decorator(
                                    req => !OkToCallBehaviorsWithCurrentCastingStatus(),
                                    Spell.WaitForGcdOrCastOrChannel()
                                    ),
                                Helpers.Common.CreateUseTableBehavior(),
                                Helpers.Common.CreateUseSoulwellBehavior(),

                                Item.CreateUseFlasksBehavior(),
                                Item.CreateUseScrollsBehavior(),

                                composite ?? new ActionAlwaysFail()
                                )
                            )
                        )
                    );
            }

            if (behav == BehaviorType.PullBuffs)
            {
                composite = new LockSelector(
                    new CallWatch("PullBuffs",
                        new Decorator(
                            ret => AllowBehaviorUsage() && OkToCallBehaviorsWithCurrentCastingStatus(),
                            composite ?? new ActionAlwaysFail()
                            )
                        )
                    );
            }

            if (behav == BehaviorType.Pull)
            {
                composite = new LockSelector(
                    new CallWatch("Pull",
                        new Decorator(
                            ret => AllowBehaviorUsage(), // && (!Me.GotTarget() || !Blacklist.Contains(Me.CurrentTargetGuid, BlacklistFlags.Combat)),
                            new PrioritySelector(
                                new Decorator(
                                    ret => !HotkeyDirector.IsCombatEnabled,
                                    new ActionAlwaysSucceed()
                                    ),
            #if BOTS_NOT_CALLING_PULLBUFFS
                            _pullBuffsBehavior,
            #endif
                                CreateLogTargetChanges(BehaviorType.Pull, "<<< PULL >>>"),
                                Item.CreateThunderLordGrappleBehavior(),
                                composite ?? new ActionAlwaysFail()
                                )
                            )
                        )
                    );
            }

            if (behav == BehaviorType.Heal)
            {
                Composite behavHealingSpheres = new ActionAlwaysFail();
                if (SingularSettings.Instance.MoveToSpheres)
                {
                    behavHealingSpheres = new ThrottlePasses(
                        1, 1,
                        new Decorator(
                            ret => Me.HealthPercent < SingularSettings.Instance.SphereHealthPercentInCombat && Singular.ClassSpecific.Monk.Common.AnySpheres(SphereType.Life, SingularSettings.Instance.SphereDistanceInCombat),
                            Singular.ClassSpecific.Monk.Common.CreateMoveToSphereBehavior(SphereType.Life, SingularSettings.Instance.SphereDistanceInCombat)
                            )
                        );
                }

                composite = new LockSelector(
                    new CallWatch("Heal",

                        // following must occur before any cast or movement during Combat
                        Generic.CreateCancelShadowmeld(),

                        SingularRoutine.Instance._lostControlBehavior,
                        new Decorator(
                            ret => Kite.IsKitingActive(),
                            new HookExecutor(HookName("KitingBehavior"))
                            ),
                        new Decorator(
                            ret => AllowBehaviorUsage() && OkToCallBehaviorsWithCurrentCastingStatus(),
                            new PrioritySelector(
                                Generic.CreatePotionAndHealthstoneBehavior(),
                                composite ?? new ActionAlwaysFail(),
                                behavHealingSpheres
                                )
                            )
                        )
                    );
            }

            if (behav == BehaviorType.CombatBuffs)
            {
                composite = new LockSelector(
                    new CallWatch("CombatBuffs",
                        new Decorator(
                            ret => AllowBehaviorUsage() && OkToCallBehaviorsWithCurrentCastingStatus(),
                            new PrioritySelector(
                                new Decorator(ret => !HotkeyDirector.IsCombatEnabled, new ActionAlwaysSucceed()),
                                Generic.CreateUseTrinketsBehaviour(),
                                Generic.CreatePotionAndHealthstoneBehavior(),
                                Item.CreateUseXPBuffPotionsBehavior(),
                                Generic.CreateRacialBehaviour(),
                                Generic.CreateGarrisonAbilityBehaviour(),
                                composite ?? new ActionAlwaysFail()
                                )
                            )
                        )
                    );
            }

            if (behav == BehaviorType.Combat)
            {
                composite = new LockSelector(
                    new CallWatch("Combat",
                        new Decorator(
                            ret => AllowBehaviorUsage(), // && (!Me.GotTarget() || !Blacklist.Contains(Me.CurrentTargetGuid, BlacklistFlags.Combat)),
                            new PrioritySelector(
                                new Decorator(
                                    ret => !HotkeyDirector.IsCombatEnabled,
                                    new ActionAlwaysSucceed()
                                    ),
                                CreatePullMorePull(),
                                CreateLogTargetChanges(BehaviorType.Combat, "<<< ADD >>>"),
                                composite ?? new ActionAlwaysFail()
                                )
                            )
                        )
                    );
            }

            if (behav == BehaviorType.Death)
            {
                composite = new LockSelector(
                    new CallWatch("Death",
                        new Decorator(
                            ret => AllowBehaviorUsage(),
                            new PrioritySelector(
                                new Action(r => { ResetCurrentTarget(); return RunStatus.Failure; }),
                                new Decorator(
                                    req => !OkToCallBehaviorsWithCurrentCastingStatus(),
                                    Spell.WaitForGcdOrCastOrChannel()
                                    ),
                                composite ?? new ActionAlwaysFail()
                                )
                            )
                        )
                    );
            }

            return composite;
        }