private async Task <bool> GetInVehicleLogic() { // currently we only support using an item to get into vehicle if (ItemId > 0) { if (Me.IsFalling) { return(false); } var item = Me.BagItems.FirstOrDefault(i => i.Entry == ItemId); if (item == null) { QBCLog.Fatal("No Item with ID {0} was found in bags", ItemId); return(false); } item.Use(); if (!await Coroutine.Wait(6000, Query.IsInVehicle)) { QBCLog.Warning("Could not get into vehicle by using {0}.", item.SafeName); return(false); } CycleToNearestPointInPath(); _flightTimer.Reset(); } else { QBCLog.Fatal("Not in a vehicle"); } await Ascend(3000); return(true); }
public override void OnStart() { // This reports problems, and stops BT processing if there was a problem with attributes... // We had to defer this action, as the 'profile line number' is not available during the element's // constructor call. OnStart_HandleAttributeProblem(); // If the quest is complete, this behavior is already done... // So we don't want to falsely inform the user of things that will be skipped. if (!IsDone) { PlayerQuest quest = StyxWoW.Me.QuestLog.GetQuestById((uint)QuestId); this.UpdateGoalText(QuestId); if (quest != null) { if (!quest.IsCompleted) { QBCLog.Fatal("Quest({0}, \"{1}\") is not complete.", QuestId, QuestName); _forcedDone = true; } } else { QBCLog.Warning("Quest({0}) is not in our log--skipping turn in.", QuestId); _forcedDone = true; } } }
private static void CheckWeaponStatus() { SetIDs(); if (HasMainHand) { if (EnchantID != WeaponMainHandEnchantID) { HandleEnchanting("main"); return; } if (HasOffHand && EnchantID != WeaponOffHandEnchantID) { HandleEnchanting("off"); return; } // Leave if (s_Me.ZoneId == 139) { CastDeathgate(); TakeDeathgate(); return; } IsBehaviorDone = true; } else { QBCLog.Warning("You have no main hand weapon equipped."); IsBehaviorDone = true; } }
private async Task <bool> StateCoroutine_CompletingObjectives() { // If for some reason no longer in the vehicle, go fetch another... if (!IsInBalloon()) { QBCLog.Warning("We've been jettisoned from vehicle unexpectedly--will try again."); BehaviorState = BehaviorStateType.MountingVehicle; return(true); } // If quest is complete, then head back... if (Me.IsQuestComplete(GetQuestId())) { BehaviorState = BehaviorStateType.ReturningToBase; return(true); } await(_updateUser_CompletingObjectives ?? (_updateUser_CompletingObjectives = new ThrottleCoroutineTask( Throttle.UserUpdate, async() => TreeRoot.StatusText = "Completing Quest Objectives"))); // Select new best target, if our current one is no longer useful... if (!IsViableForTargeting(SelectedTarget)) { var questId = GetQuestId(); if (!IsViableForTargeting(SelectedTarget) && !Me.IsQuestObjectiveComplete(questId, 1)) { SelectedTarget = FindBestTarget(MobId_Objective1_SteamwheedleSurvivor); WeaponChoice = WeaponLifeRocket; } if (!IsViableForTargeting(SelectedTarget) && !Me.IsQuestObjectiveComplete(questId, 2)) { SelectedTarget = FindBestTarget(MobId_Objective2_SouthseaBlockader); WeaponChoice = WeaponPirateDestroyingBomb; } } // Aim & Fire at the selected target... else { // If weapon aim cannot address selected target, blacklist target for a few seconds... if (!WeaponChoice.WeaponAim(SelectedTarget)) { _targetBlacklist.Add(SelectedTarget, TimeSpan.FromSeconds(5)); return(false); } // If weapon could not be fired, wait for it to become ready... if (!WeaponChoice.WeaponFire()) { return(false); } // Weapon was fired, blacklist target so we can choose another... _targetBlacklist.Add(SelectedTarget, TimeSpan.FromSeconds(15)); await Coroutine.Sleep((int)Delay.AfterWeaponFire.TotalMilliseconds); } return(true); }
private async Task <bool> MainBehavior() { QBCLog.Info("Using hearthstone; {0} out of {1} tries", ++_retries, MaxRetries); bool onCooldown = false; await UtilityCoroutine.UseHearthStone( UseGarrisonHearthstone, hearthOnCooldownAction : () => onCooldown = true, hearthCastFailedAction : reason => { QBCLog.Warning("Hearth failed. Reason: {0}", reason); _retries++; }); if (_retries >= MaxRetries) { BehaviorDone(string.Format("We have reached our max number of tries ({0}) without successfully hearthing", MaxRetries)); return(true); } if (onCooldown && WaitOnCd) { TreeRoot.StatusText = "Waiting for hearthstone cooldown"; return(true); } BehaviorDone(); return(true); }
private Composite CreateSpellBehavior() { string luaCastSpellCommand = string.Format("CastSpellByID({0})", SpellId); string luaCooldownCommand = string.Format("return GetSpellCooldown({0})", SpellId); string luaRetrieveSpellInfoCommand = string.Format("return GetSpellInfo({0})", SpellId); // If we have a spell to cast, one or more times... // NB: Since the spell we want to cast is associated with the vehicle, if we get auto-ejected // from the vehicle after we arrive at our destination, then there is no way to cast the spell. // If we get auto-ejected, we don't try to cast. return(new Decorator(context => IsInVehicle() && (SpellId > 0), new PrioritySelector( // Stop moving so we can cast... new Decorator(context => WoWMovement.ActiveMover.IsMoving, new Action(context => { WoWMovement.MoveStop(); })), // If we cannot retrieve the spell info, its a bad SpellId... new Decorator(context => string.IsNullOrEmpty(Lua.GetReturnVal <string>(luaRetrieveSpellInfoCommand, 0)), new Action(context => { QBCLog.Warning("SpellId({0}) is not known--ignoring the cast", SpellId); CastCounter = NumOfTimes + 1; // force 'done' })), // If the spell is on cooldown, we need to wait... new Decorator(context => Lua.GetReturnVal <double>(luaCooldownCommand, 1) > 0.0, new Action(context => { TreeRoot.StatusText = "Waiting for cooldown"; })), // Cast the required spell... new Sequence( new Action(context => { WoWSpell wowSpell = WoWSpell.FromId(SpellId); TreeRoot.StatusText = string.Format("Casting {0}", (wowSpell != null) ? wowSpell.Name : string.Format("SpellId({0})", SpellId)); // NB: we use LUA to cast the spell. As some vehicle abilities cause // a "Spell not learned" error. Apparently, HB only keeps up with // permanent spells known by the toon, and not transient spells that become // available in vehicles. Lua.DoString(luaCastSpellCommand); ++CastCounter; // If we're objective bound, the objective needs to complete regardless of the counter... if ((QuestObjectiveIndex <= 0) && (CastCounter >= NumOfTimes)) { BehaviorDone(); } }), new WaitContinue(TimeSpan.FromMilliseconds(CastTime), context => false, new ActionAlwaysSucceed()) ) ))); }
private Composite StateBehaviorPS_CompletingObjectives() { return(new PrioritySelector( // If for some reason no longer in the vehicle, go fetch another... new Decorator(context => !IsInTank(), new Action(context => { QBCLog.Warning("We've been jettisoned from vehicle unexpectedly--will try again."); BehaviorState = BehaviorStateType.MountingVehicle; })), // If quest is complete, then head back... new Decorator(context => Me.IsQuestObjectiveComplete(QuestId, 1), new Action(context => { BehaviorState = BehaviorStateType.ReturningToBase; })), new CompositeThrottle(Throttle.UserUpdate, new Action(context => { TreeRoot.StatusText = "Completing Quest Objectives"; })), // Select new best target, if our current one is no longer useful... new Decorator(context => !IsViableForTargeting(SelectedTarget), new ActionFail(context => { SelectedTarget = FindBestTarget(MobId_Objective1_DecrepitWatcher); // fall through })), // Aim & Fire at the selected target... new Decorator(context => IsViableForTargeting(SelectedTarget), new Sequence( new Action(context => { // If weapon aim cannot address selected target, blacklist target for a few seconds... if (!WeaponFireCannon.WeaponAim(SelectedTarget)) { _targetBlacklist.Add(SelectedTarget, TimeSpan.FromSeconds(5)); return RunStatus.Failure; } // If weapon could not be fired, wait for it to become ready... if (!WeaponFireCannon.WeaponFire()) { return RunStatus.Failure; } return RunStatus.Success; }), new WaitContinue(Delay.AfterWeaponFire, context => false, new ActionAlwaysSucceed()) )) )); }
private void UtilReportUnrecognizedAttributes() { var unrecognizedAttributes = (from attributeName in Attributes.Keys where !_recognizedAttributes.Contains(attributeName) orderby attributeName select attributeName); foreach (string attributeName in unrecognizedAttributes) { QBCLog.Warning(QBCLog.BuildMessageWithContext(Element, "Attribute '{1}' is not a recognized attribute--ignoring it.", Environment.NewLine, attributeName)); } }
private Composite StateBehaviorPS_RidingOutToHuntingGrounds() { return(new PrioritySelector( // If for some reason no longer in the vehicle, go fetch another... new Decorator(context => !IsInTank(), new Action(context => { QBCLog.Warning("We've been jettisoned from vehicle unexpectedly--will try again."); BehaviorState = BehaviorStateType.MountingVehicle; })), new Decorator(context => WeaponFireCannon.IsWeaponUsable(), new Action(context => { BehaviorState = BehaviorStateType.CompletingObjectives; })), new CompositeThrottle(Throttle.UserUpdate, new Action(context => { TreeRoot.StatusText = "Riding out to hunting grounds"; })) )); }
public override void OnStart() { // This reports problems, and stops BT processing if there was a problem with attributes... // We had to defer this action, as the 'profile line number' is not available during the element's // constructor call. OnStart_HandleAttributeProblem(); // If the quest is complete, this behavior is already done... // So we don't want to falsely inform the user of things that will be skipped. if (!IsDone) { PlayerQuest quest = StyxWoW.Me.QuestLog.GetQuestById((uint)QuestId); if (quest == null) { QBCLog.Warning("Cannot find quest with QuestId({0}).", QuestId); _isBehaviorDone = true; } else if (quest.IsCompleted && (Type != AbandonType.All)) { QBCLog.Warning("Quest({0}, \"{1}\") is Complete--skipping abandon.", QuestId, quest.Name); _isBehaviorDone = true; } else if (!quest.IsFailed && (Type == AbandonType.Failed)) { QBCLog.Warning("Quest({0}, \"{1}\") has not Failed--skipping abandon.", QuestId, quest.Name); _isBehaviorDone = true; } else { TreeRoot.GoalText = string.Format("Abandoning QuestId({0}): \"{1}\"", QuestId, quest.Name); StyxWoW.Me.QuestLog.AbandonQuestById((uint)QuestId); QBCLog.Info("Quest({0}, \"{1}\") successfully abandoned", QuestId, quest.Name); _waitTimerAfterAbandon.WaitTime = TimeSpan.FromMilliseconds(WaitTime); _waitTimerAfterAbandon.Reset(); } } }
private static void NavigateToTeleporter() { // Upper teleporter - 207580 // Lower teleporter - 207581 Teleporter = ObjectManager.GetObjectsOfTypeFast <WoWObject>().FirstOrDefault(teleportPad => teleportPad.IsValid && teleportPad.Entry == 207580); if (Teleporter == null) { QBCLog.Warning("No teleporter found."); return; } if (Teleporter != null && Teleporter.Location.Distance(s_Me.Location) <= 3) { TookTeleporter = true; return; } Navigator.MoveTo(Teleporter.Location); }
private async Task <bool> EnterPortal() { var portalEntryTimer = new WaitTimer(MaxTimeToPortalEntry); portalEntryTimer.Reset(); QBCLog.DeveloperInfo("Portal Entry Timer Started"); while (true) { if (TookPortal) { return(true); } // If portal entry timer expired, deal with it... if (portalEntryTimer.IsFinished) { QBCLog.Warning( "Unable to enter portal within allotted time of {0}", Utility.PrettyTime(MaxTimeToPortalEntry)); break; } // If we are within 2 yards of calculated end point we should never reach... if (Me.Location.Distance(MovePoint) < 2) { QBCLog.Warning("Seems we missed the portal. Is Portal activated? Profile needs to pick better alignment?"); break; } // If we're not moving toward portal, get busy... if (!StyxWoW.Me.IsMoving || Navigator.AtLocation(StartingPoint)) { QBCLog.DeveloperInfo("Entering portal via {0}", MovePoint); WoWMovement.ClickToMove(MovePoint); } await Coroutine.Yield(); } return(false); }
public BasicVehicleBehaviour(Dictionary <string, string> args) : base(args) { QBCLog.BehaviorLoggingContext = this; try { QBCLog.Warning("*****\n" + "* THIS BEHAVIOR IS DEPRECATED, and will be retired on July 31th 2012.\n" + "*\n" + "* BasicVehicleBehavior adds _no_ _additonal_ _value_ over the VehicleMover behavior.\n" + "* Please update the profile to use the VehicleMover behavior." + "*****"); LocationDest = GetAttributeAsNullable <Vector3>("", true, ConstrainAs.Vector3NonEmpty, new[] { "Dest" }) ?? Vector3.Zero; LocationMount = GetAttributeAsNullable <Vector3>("Mount", true, ConstrainAs.Vector3NonEmpty, null) ?? Vector3.Zero; QuestId = GetAttributeAsNullable <int>("QuestId", false, ConstrainAs.QuestId(this), null) ?? 0; QuestRequirementComplete = GetAttributeAsNullable <QuestCompleteRequirement>("QuestCompleteRequirement", false, null, null) ?? QuestCompleteRequirement.NotComplete; QuestRequirementInLog = GetAttributeAsNullable <QuestInLogRequirement>("QuestInLogRequirement", false, null, null) ?? QuestInLogRequirement.InLog; SpellCastId = GetAttributeAsNullable <int>("SpellId", false, ConstrainAs.SpellId, null) ?? 0; VehicleId = GetAttributeAsNullable <int>("VehicleId", true, ConstrainAs.VehicleId, null) ?? 0; MountedPoint = Vector3.Zero; } catch (Exception except) { // Maintenance problems occur for a number of reasons. The primary two are... // * Changes were made to the behavior, and boundary conditions weren't properly tested. // * The Honorbuddy core was changed, and the behavior wasn't adjusted for the new changes. // In any case, we pinpoint the source of the problem area here, and hopefully it // can be quickly resolved. QBCLog.Exception(except);; IsAttributeProblem = true; } }
private async Task <bool> StateCoroutine_RidingOutToHuntingGrounds() { // If for some reason no longer in the vehicle, go fetch another... if (!IsInBalloon()) { QBCLog.Warning("We've been jettisoned from vehicle unexpectedly--will try again."); BehaviorState = BehaviorStateType.MountingVehicle; return(true); } // Ride to hunting grounds complete when spells are enabled... if (WeaponLifeRocket.IsWeaponUsable()) { BehaviorState = BehaviorStateType.CompletingObjectives; return(true); } await(_updateUser_RidingOutToHuntingGrounds ?? (_updateUser_RidingOutToHuntingGrounds = new ThrottleCoroutineTask( Throttle.UserUpdate, async() => TreeRoot.StatusText = "Riding out to hunting grounds"))); return(false); }
private Composite SubBehavior_CombatWithViableMob() { return(new PrioritySelector(context => SelectedTarget = Me.CurrentTarget, // Recall pet, if necessary... new Decorator(context => (SelectedTarget.HealthPercent < RecallPetAtMobPercentHealth) && (Me.GotAlivePet && Me.Pet.GotTarget), new ActionFail(context => { QBCLog.Info("Recalling Pet from '{0}' (health: {1:F1})", SelectedTarget.SafeName, SelectedTarget.HealthPercent); PetControl.SetStance_Passive(); PetControl.Follow(); })), // If we are beyond the max range allowed to use the item, move within range... new Decorator(context => SelectedTarget.Distance > MaxRangeToUseItem, new ActionRunCoroutine( interactUnitContext => UtilityCoroutine.MoveTo( SelectedTarget.Location, string.Format("within {0} feet of {1}", MaxRangeToUseItem, SelectedTarget.SafeName), MovementBy, (float)MaxRangeToUseItem))), // If time to use the item, do so... new Decorator(context => IsUseItemNeeded(SelectedTarget), new PrioritySelector( new ActionRunCoroutine(context => CommonCoroutines.StopMoving()), // Halt combat until we are able to use the item... new Decorator(context => ((UseItemStrategy == UseItemStrategyType.UseItemContinuouslyOnTargetDontDefend) || (UseItemStrategy == UseItemStrategyType.UseItemOncePerTargetDontDefend)), new ActionFail(context => { // We use LUA to stop casting, since SpellManager.StopCasting() doesn't seem to work... if (Me.IsCasting) { Lua.DoString("SpellStopCasting()"); } if (Me.IsAutoAttacking) { Lua.DoString("StopAttack()"); } TreeRoot.StatusText = string.Format("Combat halted--waiting for {0} to become usable.", Utility.GetItemNameFromId(ItemId)); })), new Sequence( new ActionRunCoroutine(ctx => UtilityCoroutine.UseItemOnTarget( ItemId, SelectedTarget, () => BehaviorDone(string.Format("Terminating behavior due to missing {0}", Utility.GetItemNameFromId(ItemId))))), // Allow a brief time for WoWclient to apply aura to mob... new WaitContinue(TimeSpan.FromMilliseconds(5000), context => ItemUseAlwaysSucceeds || SelectedTarget.HasAura(ItemAppliesAuraId), new ActionAlwaysSucceed()), new ActionFail(context => { _waitTimerAfterUsingItem.Reset(); if (ItemUseAlwaysSucceeds || SelectedTarget.HasAura(ItemAppliesAuraId)) { // Count our success if no associated quest... if (!VariantQuestIds.Any()) { ++Counter; } // If we can only use the item once per target, blacklist this target from subsequent selection... if ((UseItemStrategy == UseItemStrategyType.UseItemOncePerTarget) || (UseItemStrategy == UseItemStrategyType.UseItemOncePerTargetDontDefend)) { SelectedTarget.BlacklistForInteracting(TimeSpan.FromSeconds(InteractBlacklistTimeInSeconds)); } // If we can't defend ourselves from the target, blacklist it for combat and move on... if (Query.IsViable(SelectedTarget) && ((UseItemStrategy == UseItemStrategyType.UseItemContinuouslyOnTargetDontDefend) || (UseItemStrategy == UseItemStrategyType.UseItemOncePerTargetDontDefend))) { SelectedTarget.BlacklistForCombat(TimeSpan.FromSeconds(InteractBlacklistTimeInSeconds)); BotPoi.Clear(); Me.ClearTarget(); SelectedTarget = null; } } if ((ItemAppliesAuraId > 0) && !SelectedTarget.HasAura(ItemAppliesAuraId)) { var auraNamesOnMob = ((SelectedTarget.Auras.Keys.Count > 0) ? string.Join(", ", SelectedTarget.Auras.Keys) : "none"); QBCLog.Warning("{1} did not acquire expected AuraId, \"{2}\"--retrying.{0}" + " Auras on {1}: {3}", Environment.NewLine, SelectedTarget.SafeName, Utility.GetSpellNameFromId(ItemAppliesAuraId), auraNamesOnMob); } }), // Prevent combat, if we're not supposed to defend... new Decorator(context => ((UseItemStrategy == UseItemStrategyType.UseItemContinuouslyOnTargetDontDefend) || (UseItemStrategy == UseItemStrategyType.UseItemOncePerTargetDontDefend)), new ActionAlwaysSucceed()) ))) )); }
/// <summary> /// Behavior for forcing train/mail/vendor/repair /// Example usage: <CustomBehavior QuestId="14324" File="ForceSetVendor" VendorType="Train" /> /// QuestId is optional, if you don't use it make sure you put this tag inside an 'If' /// </summary> public ForceSetVendor(Dictionary <string, string> args) : base(args) { QBCLog.BehaviorLoggingContext = this; try { // Deprecation warnings... if (args.ContainsKey("VendorType")) { QBCLog.Warning("The VendorType attribute has been deprecated.\n" + "Please replace it with DoMail/DoRepair/DoSell/DoTrain='true'"); } // QuestRequirement* attributes are explained here... // http://www.thebuddyforum.com/mediawiki/index.php?title=Honorbuddy_Programming_Cookbook:_QuestId_for_Custom_Behaviors // ...and also used for IsDone processing. DoMail = GetAttributeAsNullable <bool>("DoMail", false, null, null) ?? false; DoRepair = GetAttributeAsNullable <bool>("DoRepair", false, null, null) ?? false; DoSell = GetAttributeAsNullable <bool>("DoSell", false, null, null) ?? false; DoTrain = GetAttributeAsNullable <bool>("DoTrain", false, null, null) ?? false; QuestId = GetAttributeAsNullable <int>("QuestId", false, ConstrainAs.QuestId(this), null) ?? 0; QuestRequirementComplete = GetAttributeAsNullable <QuestCompleteRequirement>("QuestCompleteRequirement", false, null, null) ?? QuestCompleteRequirement.NotComplete; QuestRequirementInLog = GetAttributeAsNullable <QuestInLogRequirement>("QuestInLogRequirement", false, null, null) ?? QuestInLogRequirement.InLog; // "VendorType" attribute is required if no Do* attribute is specified VendorType?type = GetAttributeAsNullable <VendorType>("VendorType", !(DoMail || DoRepair || DoSell || DoTrain), null, null); if (type.HasValue) { switch (type.Value) { case VendorType.Mail: DoMail = true; break; case VendorType.Repair: DoRepair = true; break; case VendorType.Sell: DoSell = true; break; case VendorType.Train: DoTrain = true; break; default: IsAttributeProblem = true; throw (new NotImplementedException("Unexpected VendorType")); } } } catch (Exception except) { // Maintenance problems occur for a number of reasons. The primary two are... // * Changes were made to the behavior, and boundary conditions weren't properly tested. // * The Honorbuddy core was changed, and the behavior wasn't adjusted for the new changes. // In any case, we pinpoint the source of the problem area here, and hopefully it // can be quickly resolved. QBCLog.Exception(except); IsAttributeProblem = true; } }
private T UtilTo <T>(string attributeName, string attributeValueAsString) { Type concreteType = typeof(T); // Booleans require special handling... if (concreteType == typeof(bool)) { int tmpInt; if (int.TryParse(attributeValueAsString, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpInt)) { attributeValueAsString = (tmpInt != 0) ? "true" : "false"; QBCLog.Warning(QBCLog.BuildMessageWithContext(Element, "Attribute's '{1}' value was provided as an integer (saw '{2}')--a boolean was expected.{0}" + "The integral value '{2}' was converted to Boolean({3}).{0}" + "Please update to provide '{3}' for this value.", Environment.NewLine, attributeName, tmpInt, attributeValueAsString)); } // Fall through for normal boolean conversion } // Enums require special handling... else if (concreteType.IsEnum) { T tmpValue = default(T); try { tmpValue = (T)Enum.Parse(concreteType, attributeValueAsString); if (!Enum.IsDefined(concreteType, tmpValue)) { throw new ArgumentException(); } // If the provided value is a number instead of Enum name, ask the profile writer to fix it... // This is not fatal, so we let it go without flagging IsAttributeProblem. int tmpInt; if (int.TryParse(attributeValueAsString, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpInt)) { QBCLog.Warning(QBCLog.BuildMessageWithContext(Element, "The '{1}' attribute's value '{2}' has been implicitly converted" + " to the corresponding enumeration '{3}'.{0}" + "Please use the enumeration name '{3}' instead of a number.", Environment.NewLine, attributeName, tmpInt, tmpValue.ToString())); } } catch (Exception) { QBCLog.Error(QBCLog.BuildMessageWithContext(Element, "The value '{1}' is not a member of the {2} enumeration." + " Allowed values: {3}", Environment.NewLine, attributeValueAsString, concreteType.Name, string.Join(", ", Enum.GetNames(concreteType)))); throw; } return(tmpValue); } try { return((T)Convert.ChangeType(attributeValueAsString, concreteType, CultureInfo.InvariantCulture)); } catch (Exception except) { QBCLog.Error(QBCLog.BuildMessageWithContext(Element, "The '{1}' attribute's value (saw '{2}') is malformed. ({3})", Environment.NewLine, attributeName, attributeValueAsString, except.GetType().Name)); throw; } }
private string UtilLocateKey(bool isAttributeRequired, string primaryName, string[] aliasNames) { // Register keys as recognized UtilRecognizeAttributeNames(primaryName, aliasNames); // Make sure the key was only specified once -- // The 'dictionary' nature of Args assures that a key name will only be in the dictionary once. // However, if the key has been renamed, and an alias maintained for backward-compatibility, // then the user could specify the primary key name and one or more aliases as attributes. // If all the aliases provided the same value, then it is harmless, but we don't make the // distinction. Instead, we encourage the user to use the preferred name of the key. This // eliminates any possibility of the user specifying conflicting values for the 'same' attribute. if (UtilCountKeyNames(primaryName, aliasNames) > 1) { var keyNames = new List <string> { primaryName }; keyNames.AddRange(aliasNames); keyNames.Sort(); QBCLog.Error(QBCLog.BuildMessageWithContext(Element, "The attributes [{1}] are aliases for each other, and thus mutually exclusive.{0}" + "Please specify the attribute by its preferred name '{2}'.", Environment.NewLine, ("'" + string.Join("', '", keyNames.ToArray()) + "'"), primaryName)); IsAttributeProblem = true; return(null); } // Prefer the primary name... if (!string.IsNullOrEmpty(primaryName) && Attributes.ContainsKey(primaryName)) { return(primaryName); } if (aliasNames != null) { string keyName = (from aliasName in aliasNames where !string.IsNullOrEmpty(aliasName) && Attributes.ContainsKey(aliasName) select aliasName).FirstOrDefault(); if (!string.IsNullOrEmpty(keyName)) { QBCLog.Warning(QBCLog.BuildMessageWithContext(Element, "Found attribute via its alias name '{1}'.{0}" + "Please update to use its primary name '{2}', instead.", Environment.NewLine, keyName, primaryName)); return(keyName); } } // Attribute is required, but cannot be located... if (isAttributeRequired) { QBCLog.Error(QBCLog.BuildMessageWithContext(Element, "Attribute '{1}' is required, but was not provided.", Environment.NewLine, primaryName)); IsAttributeProblem = true; } return(null); }
private async Task <bool> MoveToEnd(bool exitVehicle, WoWUnit passenger = null) { if (!Query.IsInVehicle()) { return(false); } if (Vehicle.Location.DistanceSquared(EndLocation) >= PrecisionSqr) { await UseSpeedBuff(); Flightor.MoveTo(EndLocation); return(true); } if (exitVehicle) { Lua.DoString("VehicleExit()"); await Coroutine.Sleep(2000); await Coroutine.Wait(20000, () => !Me.IsFalling); if (Me.Combat) { QBCLog.Info("Getting in vehicle to drop combat"); return(await GetInVehicleLogic()); } return(true); } if (!Query.IsViable(passenger)) { return(false); } if (Vehicle.IsMoving) { await CommonCoroutines.StopMoving("Dropping off passenger."); await CommonCoroutines.SleepForLagDuration(); } await Coroutine.Sleep(StyxWoW.Random.Next(5000, 6000)); UseVehicleButton(DropPassengerButton); await CommonCoroutines.SleepForLagDuration(); if (!await Coroutine.Wait(10000, () => !Query.IsViable(passenger) || !UnitIsRidingMyVehicle(passenger))) { QBCLog.Warning("Failed to drop passenger off"); return(false); } if (Query.IsViable(passenger)) { Blacklist.Add(passenger, BlacklistFlags.Interact, TimeSpan.FromMinutes(10), "Rescued"); } // pause a sec to see if quest completes. if (await Coroutine.Wait(2000, () => Quest.IsCompleted)) { return(true); } CycleToNearestPointInPath(); return(true); }
private async Task <bool> ScareSpiders() { // if not in a turret than move to one and interact with it if (!Query.IsInVehicle()) { var mustang = GetMustang(); if (mustang == null) { QBCLog.Warning("No mustang was found nearby"); return(false); } TreeRoot.StatusText = "Moving To Mustang"; if (mustang.DistanceSqr > 5 * 5) { return((await CommonCoroutines.MoveTo(mustang.Location)).IsSuccessful()); } await CommonCoroutines.LandAndDismount(); QBCLog.Info("Interacting with Mustang"); mustang.Interact(); return(true); } // Find the nearest spider and if none exist then move to the spawn location if (!Query.IsViable(_currentTarget) || !_currentTarget.IsAlive) { _currentTarget = ObjectManager.GetObjectsOfType <WoWUnit>() .Where(u => u.IsAlive && u.Entry == 44284 && !Blacklist.Contains(u, BlacklistFlags.Interact)) .OrderBy(u => u.DistanceSqr).FirstOrDefault(); if (_currentTarget == null) { if (!Navigator.AtLocation(_spiderSpawnLocation)) { return((await CommonCoroutines.MoveTo(_spiderSpawnLocation)).IsSuccessful()); } TreeRoot.StatusText = "Waiting for spiders to spawn"; return(true); } _noMoveBlacklistTimer.Reset(); _blacklistTimer.Reset(); QBCLog.Info("Locked on a new target. Distance {0}", _currentTarget.Distance); } TreeRoot.StatusText = "Scaring spider towards lumber mill"; var moveToPoint = WoWMathHelper.CalculatePointFrom(_lumberMillLocation, _currentTarget.Location, -6); if (moveToPoint.DistanceSqr((WoWMovement.ActiveMover ?? StyxWoW.Me).Location) > 4 * 4) { return((await CommonCoroutines.MoveTo(moveToPoint)).IsSuccessful()); } // spider not moving? blacklist and find a new target. if (_noMoveBlacklistTimer.ElapsedMilliseconds > 20000 && _currentTarget.Location.DistanceSqr(_spiderScareLoc) < 10 * 10) { Blacklist.Add(_currentTarget, BlacklistFlags.Interact, TimeSpan.FromMinutes(3), "Spider is not moving"); _currentTarget = null; } else if (_blacklistTimer.IsFinished) { Blacklist.Add(_currentTarget, BlacklistFlags.Interact, TimeSpan.FromMinutes(3), "Took too long"); _currentTarget = null; } else if (!_currentTarget.HasAura("Fear")) { await CommonCoroutines.StopMoving(); Me.SetFacing(_lumberMillLocation); await CommonCoroutines.SleepForLagDuration(); await Coroutine.Sleep(200); if (!_noMoveBlacklistTimer.IsRunning || _currentTarget.Location.DistanceSqr(_spiderScareLoc) >= 10 * 10) { _noMoveBlacklistTimer.Restart(); _spiderScareLoc = _currentTarget.Location; } Lua.DoString("CastSpellByID(83605)"); await Coroutine.Wait(3000, () => Query.IsViable(_currentTarget) && _currentTarget.HasAura("Fear")); } return(true); }
private Composite StateBehaviorPS_CompletingObjectives() { return(new PrioritySelector( // If for some reason no longer in the vehicle, go fetch another... new Decorator(context => !Query.IsViable(DragonVehicle), new Action(context => { QBCLog.Warning("We've been jettisoned from vehicle unexpectedly--will try again."); BehaviorState = BehaviorStateType.MountingVehicle; })), // If quest is complete, then head back... new Decorator(context => Me.IsQuestComplete(GetQuestId()), new Action(context => { BehaviorState = BehaviorStateType.ReturningToBase; })), new CompositeThrottle(Throttle.UserUpdate, new ActionFail(context => { TreeRoot.StatusText = "Completing Quest Objectives"; })), SubBehaviorPS_UpdatePathWaypoint(), SubBehaviorPS_Heal(), // We go after the Ballistas first... // NB: Soldiers will be collateral damage of pursing Ballistas. // If the soldiers don't complete after doing Ballistas, we'll clean up // the Soldiers next. new Decorator(context => !IsViableTarget(SelectedTarget), new PrioritySelector( // Try to find target... new ActionFail(context => { SelectedTarget = FindMobToKill(!Me.IsQuestObjectiveComplete(GetQuestId(), 2) ? MobId_ScarletBallista : MobId_TirisfalCrusader); }), // If no target found, move toward next waypoint... new Decorator(context => !IsViableTarget(SelectedTarget), new Action(context => { Flightor.MoveTo(PathPatrol.Peek(), (float)FlightorMinHeight); })) )), new Action(context => { // NB: We would've preferred to strafe in this algorithm; however, // strafing triggers Flightor's unstuck handler too much. Probably because // this is a vehicle/transport, instead of a 'flying mount'. // Show the target we're pursuing... Utility.Target(SelectedTarget); var myLocation = WoWMovement.ActiveMover.Location; var selectedTargetLocation = SelectedTarget.Location; var distance2DSqrToTarget = myLocation.Distance2DSquared(selectedTargetLocation); // Deal with needed evasion... if (StationPoint.HasValue) { if (myLocation.Distance(StationPoint.Value) > FlyingPathPrecision) { Flightor.MoveTo(StationPoint.Value); return RunStatus.Success; } StationPoint = null; } // See if we need a new 'on station' location... if (!StationPoint.HasValue) { // If our weapon is not ready or we can't see target, move around station until it is... if (!Weapon_FrozenDeathbolt.IsWeaponReady() || (IsViableTarget(SelectedTarget) && !SelectedTarget.InLineOfSight) || IsIncomingMissile()) { StationPoint = FindNewStationPoint(SelectedTarget); return RunStatus.Success; } } // If we are too far from selected target, close the distance... if (distance2DSqrToTarget > (TargetDistance2DMax * TargetDistance2DMax)) { Flightor.MoveTo(FindDistanceClosePoint(SelectedTarget, PathPatrol.Peek().Z)); return RunStatus.Success; } // If we are too close to selected target, put some distance between us... if (distance2DSqrToTarget < (TargetDistance2DMin * TargetDistance2DMin)) { Flightor.MoveTo(FindDistanceGainPoint(SelectedTarget, TargetDistance2DMin)); return RunStatus.Success; } // If weapon is not ready, just keep on station/evading... if (!Weapon_FrozenDeathbolt.IsWeaponReady()) { return RunStatus.Success; } // If the weapon cannot address the target, blacklist target and find another... if (!Weapon_FrozenDeathbolt.WeaponAim(SelectedTarget)) { _targetBlacklist.Add(SelectedTarget, TimeSpan.FromSeconds(5)); } // If weapon cannot fire for some reason, try again... if (!Weapon_FrozenDeathbolt.WeaponFire()) { return RunStatus.Success; } return RunStatus.Failure; // fall through }), // NB: Need to delay a bit for the weapon to actually launch. Otherwise // it screws the aim up if we move again before projectile is fired. new Sleep(ctx => Delay.LagDuration + Delay.AfterWeaponFire) //new Wait(Delay.LagDuration, context => Weapon_FrozenDeathbolt.IsWeaponReady(), new ActionAlwaysSucceed()) )); }
public Composite CreateBehavior_SelectTarget(BehaviorFailIfNoTargetsDelegate failIfNoTargets) { return( new PrioritySelector( // If we haven't engaged the mob when the auto-blacklist timer expires, give up on it and move on... new Decorator(ret => ((CurrentTarget != null) && (_currentTargetAutoBlacklistTimer.Elapsed > _currentTargetAutoBlacklistTime)), new Action(delegate { QBCLog.Warning("Taking too long to engage '{0}'--blacklisting", CurrentTarget.SafeName); CurrentTarget.LocallyBlacklist(_delay_AutoBlacklist); CurrentTarget = null; })), // If we don't have a current target, select a new one... // Once we select a target, its 'locked in' (unless it gets blacklisted). This prevents us // from running back and forth between two equidistant targets. new Decorator(ret => ((CurrentTarget == null) || !CurrentTarget.IsValid || CurrentTarget.IsLocallyBlacklisted()), new PrioritySelector(context => CurrentTarget = ViableTargets().FirstOrDefault(), // If we found next target, we're done... new Decorator(ret => (CurrentTarget != null), new Action(delegate { _huntingGroundWaitPoint = WoWPoint.Empty; if (CurrentTarget is WoWUnit) { CurrentTarget.ToUnit().Target(); } _currentTargetAutoBlacklistTime = CalculateAutoBlacklistTime(CurrentTarget); _currentTargetAutoBlacklistTimer.Reset(); _currentTargetAutoBlacklistTimer.Start(); })), // If we've exhausted mob/object supply in area, and we need to wait, do so... new Decorator(ret => !failIfNoTargets(), // Move back to hunting ground anchor -- new PrioritySelector( // If we've more than one hotspot, head to the next one... new Decorator(ret => (_hotSpots.Count() > 1), new Sequence(context => FindNextHotspot(), new Action(nextHotspot => TreeRoot.StatusText = "No targets--moving to hotspot " + (WoWPoint)nextHotspot), CreateBehavior_InternalMoveTo(() => FindNextHotspot()) )), // We find a point 'near' our anchor at which to wait... // This way, if multiple people are using the same profile at the same time, // they won't be standing on top of each other. new Decorator(ret => (_huntingGroundWaitPoint == WoWPoint.Empty), new Action(delegate { _huntingGroundWaitPoint = HuntingGroundAnchor.FanOutRandom(CollectionDistance * 0.25); TreeRoot.StatusText = "No targets--moving near hunting ground anchor point to wait"; _repopWaitingTime.Reset(); _repopWaitingTime.Start(); })), // Move to our selected random point... new Decorator(ret => !Navigator.AtLocation(_huntingGroundWaitPoint), CreateBehavior_InternalMoveTo(() => _huntingGroundWaitPoint)), // Tell user what's going on... new Sequence( new Action(delegate { TreeRoot.GoalText = this.GetType().Name + ": Waiting for Repops"; TreeRoot.StatusText = "No targets in area--waiting for repops. " + BuildTimeAsString(_repopWaitingTime.Elapsed); }), new WaitContinue(_delay_RepopWait, ret => false, new ActionAlwaysSucceed())) )) )), // Re-select target, if it was lost (perhaps, due to combat)... new Decorator(ret => ((CurrentTarget is WoWUnit) && (Me.CurrentTarget != CurrentTarget)), new Action(delegate { CurrentTarget.ToUnit().Target(); })) )); }
private Composite CreateMainBehavior() { return(new PrioritySelector( // If a mob targets us, kill it... // We don't want to blindly move to destination and drag a bunch of mobs behind us... new Decorator(context => (SelectedTarget = FindMobTargetingMeOrPet()) != null, UtilityBehavior_SpankMob(context => SelectedTarget)), // Stateful Operation: new Switch <StateType_MainBehavior>(context => State_MainBehavior, #region State: DEFAULT new Action(context => // default case { QBCLog.Error("BEHAVIOR MAINTENANCE PROBLEM: StateType_MainBehavior({0}) is unhandled", State_MainBehavior); TreeRoot.Stop(); _isBehaviorDone = true; }), #endregion #region State: Assigning Task new SwitchArgument <StateType_MainBehavior>(StateType_MainBehavior.AssigningTask, new PrioritySelector( // Captain Anson... new Decorator(context => !Task_CaptainAnson.IsTaskComplete(context), new Action(context => { CurrentTask = Task_CaptainAnson; State_MainBehavior = StateType_MainBehavior.AcquiringCatapult; })), // Captain Morris... new Decorator(context => !Task_CaptainMorris.IsTaskComplete(context), new Action(context => { CurrentTask = Task_CaptainMorris; State_MainBehavior = StateType_MainBehavior.AcquiringCatapult; })), // Done with all tasks, move back to sane position to continue profile... new Decorator(context => !Navigator.AtLocation(Location_CatapultFarm), new Action(context => { Navigator.MoveTo(Location_CatapultFarm); })), new Action(context => { QBCLog.Info("Finished"); _isBehaviorDone = true; }) )), #endregion #region State: Acquiring Catapult new SwitchArgument <StateType_MainBehavior>(StateType_MainBehavior.AcquiringCatapult, new PrioritySelector( // If task complete, go get next task... new Decorator(context => CurrentTask.IsTaskComplete(context), new Action(context => { State_MainBehavior = StateType_MainBehavior.AssigningTask; })), // If we're in the catapult, start using it... new Decorator(context => Query.IsInVehicle(), new Action(context => { State_MainBehavior = StateType_MainBehavior.UsingCatapultToBoardBoat; })), // Notify user... new Action(context => { QBCLog.Info("Appropriating a Catapult"); return RunStatus.Failure; }), // If available catapult, take advantage of it... new Decorator(context => IsViable(SelectedCatapult) && (FindPlayersNearby(SelectedCatapult.Location, NonCompeteDistanceForCatapults).Count() <= 0), UtilityBehavior_InteractWithMob(context => SelectedCatapult)), // Otherwise, spank machinist and take his catapult... new Decorator(context => IsViable(SelectedMachinist) && (FindPlayersNearby(SelectedMachinist.Location, NonCompeteDistanceForCatapults).Count() <= 0), UtilityBehavior_SpankMob(context => SelectedMachinist)), // Find next catapult or machinist... // NB: Since it takes a couple of seconds for the catapult to appear after // we kill the machinist, we want to wait briefly. Without this delay, // the toon will run off to another machinist, and come back when the Catapult // spawns from the machinist we just killed. This makes us look very bottish, // and the delay prevents that. new Wait(TimeSpan.FromSeconds(3), context => ((SelectedCatapult = FindCatapult()) != null), new ActionAlwaysSucceed()), new Decorator(context => (SelectedMachinist = FindMachinist()) != null, new ActionAlwaysSucceed()), // No catapults to be had, move to center of catapult farm and wait for respawns... new Decorator(context => !Navigator.AtLocation(Location_CatapultFarm), new Action(context => { Navigator.MoveTo(Location_CatapultFarm); })), new Action(context => { QBCLog.Info("Waiting on more Catapults to respawn"); }) )), #endregion #region State: Using Catapult to Board Boat new SwitchArgument <StateType_MainBehavior>(StateType_MainBehavior.UsingCatapultToBoardBoat, new PrioritySelector( // If task complete, go fetch another... new Decorator(context => CurrentTask.IsTaskComplete(context), new Action(context => { State_MainBehavior = StateType_MainBehavior.AssigningTask; })), // If we're no longer in catapult, either launch succeeded or we need to fetch another Catapult... new Decorator(context => !Query.IsInVehicle(), new PrioritySelector( // Allow time for Launch completion, and toon to land on boat... new Wait(TimeSpan.FromSeconds(5), // TODO: Rewrite to use something else (probably IsOnTransport?) context => true /*Navigator.CanNavigateFully(Me.Location, CurrentTask.PositionToLand)*/, new ActionAlwaysFail()), new Action(context => { // If we can navigate to intended landing spot, we successfully boarded boat... // TODO: Rewrite to use something else (probably IsOnTransport?) if (/*Navigator.CanNavigateFully(Me.Location, CurrentTask.PositionToLand)*/ true) { State_MainBehavior = StateType_MainBehavior.KillingCaptain; return; } // Otherwise, we missed boarding boat, and need to try again... QBCLog.Warning("Failed in boarding {0}'s boat--trying again", CurrentTask.MobName); State_MainBehavior = StateType_MainBehavior.AcquiringCatapult; }) )), // If Catapult no longer viable, find a new one... new Decorator(context => !IsViable(SelectedCatapult), new Decorator(context => (SelectedCatapult = FindCatapult()) == null, new Action(context => { State_MainBehavior = StateType_MainBehavior.AcquiringCatapult; }))), // Try to board boat... new ActionRunCoroutine(ctx => UtilityCoroutine_MoveAndUseCatapult()) )), #endregion #region State: Kill the Captain new SwitchArgument <StateType_MainBehavior>(StateType_MainBehavior.KillingCaptain, new PrioritySelector( // If task complete, exit boat... new Decorator(context => CurrentTask.IsTaskComplete(context), new Action(context => { State_MainBehavior = StateType_MainBehavior.ExitingBoat; })), // Kill the Captain... new PrioritySelector(captainContext => FindUnitsFromIds(CurrentTask.MobId).FirstOrDefault(), new Decorator(captainContext => captainContext != null, UtilityBehavior_SpankMob(captainContext => (WoWUnit)captainContext)), new Decorator(captainContext => captainContext == null, new Action(captainContext => { QBCLog.Info("Waiting for {0} to respawn", CurrentTask.MobName); })) ) )), #endregion #region State: Exiting Boat new SwitchArgument <StateType_MainBehavior>(StateType_MainBehavior.ExitingBoat, new PrioritySelector( new Action(context => { QBCLog.Info("Exiting {0}'s boat", CurrentTask.MobName); return RunStatus.Failure; }), new Decorator(context => !Navigator.AtLocation(CurrentTask.PositionToLand), new Action(context => { Navigator.MoveTo(CurrentTask.PositionToLand); })), new Action(context => { State_MainBehavior = StateType_MainBehavior.ExitBoatJumpDown; }) )), #endregion #region State: Exit Boat Jump Down new SwitchArgument <StateType_MainBehavior>(StateType_MainBehavior.ExitBoatJumpDown, new PrioritySelector( new Action(context => { QBCLog.Info("Jumping down off of {0}'s boat", CurrentTask.MobName); return RunStatus.Failure; }), // NB: There appear to be no mesh "jump links" in the mesh to get off boat. // So, we're left with using ClickToMove to jump down from boat decks. new Decorator(context => !Navigator.AtLocation(CurrentTask.PositionToJumpDownOffBoat), new Action(context => { WoWMovement.ClickToMove(CurrentTask.PositionToJumpDownOffBoat); })), new Action(context => { State_MainBehavior = StateType_MainBehavior.AssigningTask; }) )) #endregion ))); }
private Composite CreateCombatBehavior() { // NB: We'll be running right over some hostiles while in the barrel. // Even with a PullDistance set to one, HBcore and the CombatRoutine are going to try to pull these mobs. // Thus, this behavior runs at "higher than combat" priority, and prevents the CombatRoutine from executing // while this behavior is in progress. // // NB: We need to allow lower BT nodes to run when the behavior is finished; otherwise, HB will not // process the change of _isBehaviorDone state. return(new Decorator(context => !_isBehaviorDone, new PrioritySelector(context => _combatContext.Update(), // If quest is done, behavior is done... new Decorator(context => !UtilIsProgressRequirementsMet(QuestId, QuestRequirementInLog, QuestRequirementComplete), new Action(context => { _isBehaviorDone = true; QBCLog.Info("Finished"); })), // If not in Keg Bomb Vehicle, move to it and get inside... new Decorator(context => !Query.IsInVehicle() && (_combatContext.KegBomb != null), new PrioritySelector( new Decorator(context => _combatContext.KegBomb.Distance > _combatContext.KegBomb.InteractRange, new Action(context => { Navigator.MoveTo(_combatContext.KegBomb.Location); })), new Decorator(context => Me.IsMoving, new Action(context => { WoWMovement.MoveStop(); })), new Decorator(context => !Me.IsSafelyFacing(_combatContext.KegBomb), new Action(context => { _combatContext.KegBomb.Face(); })), new Sequence( // N.B. we need to wait a bit before jumping in vehicle after a round - // due to a bug that prevents bot from leaving vehicle until a relog new WaitContinue(2, context => false, new ActionAlwaysSucceed()), new Action(context => { _combatContext.ReInitialize(); _combatContext.KegBomb.Interact(); QBCLog.Info("Started barrel roll #{0}", ++_barrelRollCount); return RunStatus.Failure; })), new Wait(TimeSpan.FromMilliseconds(1000), context => false, new ActionAlwaysSucceed()) )), // If we are in the vehicle... new Decorator(context => Query.IsInVehicle() && (_combatContext.KegBombVehicle != null), new PrioritySelector( // If we've been in the barrel too long, just blow it up... // Blacklist whatever target we were after, and try again new Decorator(context => (_combatContext.BarrelRollingTimer.ElapsedMilliseconds > BarrelRollingTimeBeforeRetrying) && !_combatContext.IsKegIgnited, new Action(context => { QBCLog.Warning("We've been in the barrel too long--we're blowing it up to try again"); IgniteKeg(_combatContext); if (_combatContext.SelectedTarget != null) { Blacklist.Add(_combatContext.SelectedTarget, BlacklistFlags.Combat, TimeSpan.FromMinutes(3)); } })), // Select target, if not present... new Decorator(context => _combatContext.SelectedTarget == null, new Action(context => ChooseTarget(_combatContext))), // If we have a target, guide barrel to target... new Decorator(context => _combatContext.SelectedTarget != null, new Action(context => { float neededFacing = WoWMathHelper.CalculateNeededFacing(_combatContext.KegBombVehicle.Location, _combatContext.SelectedTarget.Location); neededFacing = WoWMathHelper.NormalizeRadian(neededFacing); float neededRotation = Math.Abs(neededFacing - _combatContext.KegBombVehicle.RenderFacing); neededRotation = WoWMathHelper.NormalizeRadian(neededRotation); // If we need to rotate heading 'too hard' to hit the target, then we missed the target... // Blow up the current barrel, blacklist the target, and try again if (((neededRotation > (Math.PI / 2)) && (neededRotation < ((Math.PI * 2) - (Math.PI / 2)))) && !_combatContext.IsKegIgnited) { QBCLog.Warning("We passed the selected target--igniting barrel to try again."); IgniteKeg(_combatContext); Blacklist.Add(_combatContext.SelectedTarget, BlacklistFlags.Combat, TimeSpan.FromMinutes(3)); } // Ignite the keg at the appropriate time... if (_combatContext.SelectedTarget.Distance <= IgniteDistance) { IgniteKeg(_combatContext); } // Guide the keg to target... Me.SetFacing(neededFacing); return RunStatus.Success; })) )) ))); }