///<summary> ///Adds/Updates CacheObjects inside collection by Iteration of RactorList ///This is the method that caches all live data about an object! ///</summary> public static bool UpdateCacheObjectCollection() { if (!ZetaDia.IsInGame) return false; HashSet<int> hashDoneThisRactor=new HashSet<int>(); foreach (Actor thisActor in ZetaDia.Actors.RActorList) { int tmp_raGUID; DiaObject thisObj; if (!thisActor.IsValid) continue; //Convert to DiaObject thisObj=(DiaObject)thisActor; tmp_raGUID=thisObj.RActorGuid; // See if we've already checked this ractor, this loop if (hashDoneThisRactor.Contains(tmp_raGUID)) continue; hashDoneThisRactor.Add(tmp_raGUID); //Update RactorGUID and check blacklisting.. if (ObjectCache.IsRAGUIDBlacklisted(tmp_raGUID)) continue; CacheObject tmp_CachedObj; using (ZetaDia.Memory.AcquireFrame()) { if (!ObjectCache.Objects.TryGetValue(tmp_raGUID, out tmp_CachedObj)) { Vector3 tmp_position; int tmp_acdguid; int tmp_SNOID; #region SNO //Lookup SNO try { tmp_SNOID=thisObj.ActorSNO; } catch (NullReferenceException) { Logging.WriteVerbose("Failure to get SNO from object! RaGUID: {0}", tmp_raGUID); continue; } #endregion //check our SNO blacklist if (ObjectCache.IsSNOIDBlacklisted(tmp_SNOID)&&!CacheIDLookup.hashSummonedPets.Contains(tmp_SNOID)) continue; #region Position try { tmp_position=thisObj.Position; } catch (NullReferenceException) { Logging.WriteVerbose("Failure to get position vector for RAGUID {0}", tmp_raGUID); continue; } #endregion #region AcdGUID try { tmp_acdguid=thisObj.ACDGuid; } catch (NullReferenceException) { Logging.WriteVerbose("Failure to get ACDGUID for RAGUID {0}", tmp_raGUID); continue; } #endregion tmp_CachedObj=new CacheObject(tmp_SNOID, tmp_raGUID, tmp_acdguid, tmp_position); } else //Reset unseen var tmp_CachedObj.LoopsUnseen=0; //Validate try { if (thisObj.CommonData==null||thisObj.CommonData.ACDGuid!=thisObj.ACDGuid) continue; } catch (NullReferenceException) { continue; } //Check if this object is a summoned unit by a player... #region SummonedUnits if (tmp_CachedObj.IsSummonedPet) { // Get the summoned-by info, cached if possible if (!tmp_CachedObj.SummonerID.HasValue) { try { tmp_CachedObj.SummonerID=thisObj.CommonData.GetAttribute<int>(ActorAttributeType.SummonedByACDID); } catch (Exception ex) { Logging.WriteVerbose("[Funky] Safely handled exception getting summoned-by info ["+tmp_CachedObj.SNOID.ToString()+"]"); Logging.WriteDiagnostic(ex.ToString()); continue; } } //See if this summoned unit was summoned by the bot. if (Bot.Character.iMyDynamicID==tmp_CachedObj.SummonerID.Value) { //Now modify the player data pets count.. if (Bot.Class.AC==ActorClass.Monk) Bot.Character.PetData.MysticAlly++; else if (Bot.Class.AC==ActorClass.DemonHunter&&CacheIDLookup.hashDHPets.Contains(tmp_CachedObj.SNOID)) Bot.Character.PetData.DemonHunterPet++; else if (Bot.Class.AC==ActorClass.WitchDoctor) { if (CacheIDLookup.hashZombie.Contains(tmp_CachedObj.SNOID)) Bot.Character.PetData.ZombieDogs++; else if (CacheIDLookup.hashGargantuan.Contains(tmp_CachedObj.SNOID)) Bot.Character.PetData.Gargantuan++; } else if (Bot.Class.AC==Zeta.Internals.Actors.ActorClass.Wizard) { //only count when range is within 45f (so we can summon a new one) if (CacheIDLookup.hashWizHydras.Contains(tmp_CachedObj.SNOID)&&tmp_CachedObj.CentreDistance<=45f) Bot.Character.PetData.WizardHydra++; } } //We return regardless if it was summoned by us or not since this object is not anything we want to deal with.. tmp_CachedObj.NeedsRemoved=true; continue; } #endregion //Update any SNO Data. #region SNO_Cache_Update if (tmp_CachedObj.ref_DiaObject==null||tmp_CachedObj.ContainsNullValues()) { if (!tmp_CachedObj.UpdateData(thisObj, tmp_CachedObj.RAGUID)) continue; } else if (!tmp_CachedObj.IsFinalized) {//Finalize this data by recreating it and updating the Sno cache with a new finalized entry, this also clears our all Sno cache dictionaries since we no longer need them! ObjectCache.cacheSnoCollection.FinalizeEntry(tmp_CachedObj.SNOID); } #endregion //Objects with static positions already cached don't need to be updated here. if (!tmp_CachedObj.NeedsUpdate) continue; //Obstacles -- (Not an actual object we add to targeting.) if (tmp_CachedObj.targetType.Value==TargetType.Avoidance||tmp_CachedObj.IsObstacle||tmp_CachedObj.HandleAsAvoidanceObject) { #region Obstacles bool bRequireAvoidance=false; bool bTravellingAvoidance=false; CacheObstacle thisObstacle; //Do we have this cached? if (!ObjectCache.Obstacles.TryGetValue(tmp_CachedObj.RAGUID, out thisObstacle)) { AvoidanceType AvoidanceType=AvoidanceType.Unknown; if (tmp_CachedObj.IsAvoidance) { AvoidanceType=CacheIDLookup.FindAvoidanceUsingSNOID(tmp_CachedObj.SNOID); if (AvoidanceType==AvoidanceType.Unknown) { AvoidanceType=CacheIDLookup.FindAvoidanceUsingName(tmp_CachedObj.InternalName); if (AvoidanceType==AvoidanceType.Unknown) continue; } } if (tmp_CachedObj.IsAvoidance&&tmp_CachedObj.IsProjectileAvoidance) {//Ranged Projectiles require more than simple bounding points.. so we create it as avoidance zone to cache it with properties. //Check for intersection.. ActorMovement thisMovement=thisObj.Movement; Vector2 Direction=thisMovement.DirectionVector; Ray R=new Ray(tmp_CachedObj.Position, Direction.ToVector3()); double Speed; //Lookup Cached Speed, or add new entry. if (!ObjectCache.dictProjectileSpeed.TryGetValue(tmp_CachedObj.SNOID, out Speed)) { Speed=thisMovement.DesiredSpeed; ObjectCache.dictProjectileSpeed.Add(tmp_CachedObj.SNOID, Speed); } thisObstacle=new CacheAvoidance(tmp_CachedObj, AvoidanceType, R, Speed); ObjectCache.Obstacles.Add(thisObstacle); } else if (tmp_CachedObj.IsAvoidance) { thisObstacle=new CacheAvoidance(tmp_CachedObj, AvoidanceType); ObjectCache.Obstacles.Add(thisObstacle); } else { //Obstacles. thisObstacle=new CacheServerObject(tmp_CachedObj); ObjectCache.Obstacles.Add(thisObstacle); continue; } } //Test if this avoidance requires movement now. if (thisObstacle is CacheAvoidance) { //Check last time we attempted avoidance movement (Only if its been at least a second since last time we required it..) //if (DateTime.Now.Subtract(Bot.Combat.LastAvoidanceMovement).TotalMilliseconds<1000) //continue; CacheAvoidance thisAvoidance=thisObstacle as CacheAvoidance; if (Bot.IgnoreAvoidance(thisAvoidance.AvoidanceType)) continue; //Only update position of Movement Avoidances! if (thisAvoidance.Obstacletype.Value==ObstacleType.MovingAvoidance) { //Blacklisted updates if (thisAvoidance.BlacklistRefreshCounter>0&& !thisAvoidance.CheckUpdateForProjectile) { thisAvoidance.BlacklistRefreshCounter--; } bRequireAvoidance=thisAvoidance.UpdateProjectileRayTest(tmp_CachedObj.Position); //If we need to avoid, than enable travel avoidance flag also. if (bRequireAvoidance) bTravellingAvoidance=true; } else { if (thisObstacle.CentreDistance<50f) Bot.Combat.NearbyAvoidances.Add(thisObstacle.RAGUID); if (thisAvoidance.Position.Distance(Bot.Character.Position)<=thisAvoidance.Radius) bRequireAvoidance=true; } Bot.Combat.RequiresAvoidance=bRequireAvoidance; Bot.Combat.TravellingAvoidance=bTravellingAvoidance; if (bRequireAvoidance) Bot.Combat.TriggeringAvoidances.Add((CacheAvoidance)thisObstacle); } else { //Add this server object to cell weighting in MGP //MGP.AddCellWeightingObstacle(thisObstacle.SNOID, thisObstacle.CollisionRadius.Value); //Add nearby objects to our collection (used in navblock/obstaclecheck methods to reduce queries) if (thisObstacle.CentreDistance<25f) Bot.Combat.NearbyObstacleObjects.Add((CacheServerObject)thisObstacle); } continue; #endregion } if (tmp_CachedObj.ObjectShouldBeRecreated) { //Specific updates if (tmp_CachedObj.Actortype.Value==ActorType.Item) { tmp_CachedObj=new CacheItem(tmp_CachedObj); } else if (tmp_CachedObj.Actortype.Value==ActorType.Unit) { tmp_CachedObj=new CacheUnit(tmp_CachedObj); } else if (tmp_CachedObj.Actortype.Value==ActorType.Gizmo) { if (TargetType.Interactables.HasFlag(tmp_CachedObj.targetType.Value)) tmp_CachedObj=new CacheInteractable(tmp_CachedObj); else tmp_CachedObj=new CacheDestructable(tmp_CachedObj); } } if (!tmp_CachedObj.UpdateData()) continue; //Obstacle cache if (tmp_CachedObj.Obstacletype.Value!=ObstacleType.None &&(TargetType.ServerObjects.HasFlag(tmp_CachedObj.targetType.Value))) { CacheObstacle thisObstacleObj; if (!ObjectCache.Obstacles.TryGetValue(tmp_CachedObj.RAGUID, out thisObstacleObj)) { ObjectCache.Obstacles.Add(tmp_CachedObj.RAGUID, new CacheServerObject(tmp_CachedObj)); } else { if (thisObstacleObj.targetType.Value==TargetType.Unit) { //Since units position requires updating, we update using the CacheObject thisObstacleObj.Position=tmp_CachedObj.Position; ObjectCache.Obstacles[tmp_CachedObj.RAGUID]=thisObstacleObj; } if (thisObstacleObj.CentreDistance<=25f) Bot.Combat.NearbyObstacleObjects.Add((CacheServerObject)thisObstacleObj); } //Add nearby objects to our collection (used in navblock/obstaclecheck methods to reduce queries) } //cache it if (ObjectCache.Objects.ContainsKey(tmp_CachedObj.RAGUID)) ObjectCache.Objects[tmp_CachedObj.RAGUID]=tmp_CachedObj; else ObjectCache.Objects.Add(tmp_CachedObj.RAGUID, tmp_CachedObj); } } //End of Loop //Tally up unseen objects. var UnseenObjects=ObjectCache.Objects.Keys.Where<int>(O => !hashDoneThisRactor.Contains(O)).ToList(); if (UnseenObjects.Count()>0) { for (int i=0; i<UnseenObjects.Count(); i++) { ObjectCache.Objects[UnseenObjects[i]].LoopsUnseen++; } } //Trim our collection every 5th refresh. UpdateLoopCounter++; if (UpdateLoopCounter>4) { UpdateLoopCounter=0; //Now flag any objects not seen for 5 loops. Gold/Globe only 1 loop. foreach (var item in ObjectCache.Objects.Values.Where<CacheObject>(CO => CO.LoopsUnseen>=5|| (CO.targetType.HasValue&&(CO.targetType.Value==TargetType.Gold||CO.targetType.Value==TargetType.Globe)&&CO.LoopsUnseen>0))) { item.NeedsRemoved=true; } } return true; }
//This is the 2nd step in handling.. we recheck target, get a new Ability if needed, and check potion/special movement avoidance here. public virtual bool Refresh() { // Make sure we reset unstucker stuff here Funky.PlayerMover.iTimesReachedStuckPoint=0; Funky.PlayerMover.vSafeMovementLocation=Vector3.Zero; Funky.PlayerMover.timeLastRecordedPosition=DateTime.Now; // Let's calculate whether or not we want a new target list... #region NewtargetChecks // Whether we should refresh the target list or not bool bShouldRefreshDiaObjects=false; if (!Bot.Combat.bWholeNewTarget&&!Bot.Combat.bWaitingForPower&&!Bot.Combat.bWaitingForPotion) { // Update targets at least once every 80 milliseconds if (Bot.Combat.bForceTargetUpdate ||Bot.Combat.TravellingAvoidance ||((DateTime.Now.Subtract(Bot.Refresh.lastRefreshedObjects).TotalMilliseconds>=80&&CurrentTarget.targetType.Value!=TargetType.Avoidance) ||DateTime.Now.Subtract(Bot.Refresh.lastRefreshedObjects).TotalMilliseconds>=1200)) { bShouldRefreshDiaObjects=true; } // If we AREN'T getting new targets - find out if we SHOULD because the current unit has died etc. if (!bShouldRefreshDiaObjects&&CurrentTarget.targetType.Value==TargetType.Unit&&!CurrentTarget.IsStillValid()) bShouldRefreshDiaObjects=true; } // So, after all that, do we actually want a new target list? if (!Bot.Combat.bWholeNewTarget&&!Bot.Combat.bWaitingForPower&&!Bot.Combat.bWaitingForPotion) { // If we *DO* want a new target list, do this... if (bShouldRefreshDiaObjects) { // Now call the function that refreshes targets Bot.Refresh.RefreshDiaObjects(); // No target, return success if (CurrentTarget==null) { CurrentState=RunStatus.Success; return false; } else if (Bot.Character.LastCachedTarget!=null&& Bot.Character.LastCachedTarget.RAGUID!=CurrentTarget.RAGUID&&CurrentTarget.targetType.Value==TargetType.Item) { //Reset Item Vars Bot.Combat.recheckCount=0; Bot.Combat.reCheckedFinished=false; } // Been trying to handle the same target for more than 30 seconds without damaging/reaching it? Blacklist it! // Note: The time since target picked updates every time the current target loses health, if it's a monster-target if (CurrentTarget.targetType.Value!=TargetType.Avoidance &&((CurrentTarget.targetType.Value!=TargetType.Unit&&DateTime.Now.Subtract(Bot.Combat.dateSincePickedTarget).TotalSeconds>12) ||(CurrentTarget.targetType.Value==TargetType.Unit&&!CurrentTarget.IsBoss&&DateTime.Now.Subtract(Bot.Combat.dateSincePickedTarget).TotalSeconds>40))) { // NOTE: This only blacklists if it's remained the PRIMARY TARGET that we are trying to actually directly attack! // So it won't blacklist a monster "on the edge of the screen" who isn't even being targetted // Don't blacklist monsters on <= 50% health though, as they can't be in a stuck location... can they!? Maybe give them some extra time! bool bBlacklistThis=true; // PREVENT blacklisting a monster on less than 90% health unless we haven't damaged it for more than 2 minutes if (CurrentTarget.targetType.Value==TargetType.Unit) { if (CurrentTarget.IsTreasureGoblin&&Bot.SettingsFunky.GoblinPriority>=3) bBlacklistThis=false; if (DateTime.Now.Subtract(Bot.Combat.dateSincePickedTarget).TotalSeconds<=120) bBlacklistThis=false; } if (bBlacklistThis) { if (CurrentTarget.targetType.Value==TargetType.Unit) { //Logging.WriteDiagnostic("[Funky] Blacklisting a monster because of possible stuck issues. Monster="+ObjectData.InternalName+" {"+ //ObjectData.SNOID.ToString()+"}. Range="+ObjectData.CentreDistance.ToString()+", health %="+ObjectData.CurrentHealthPct.ToString()); } CurrentTarget.NeedsRemoved=true; CurrentTarget.BlacklistFlag=BlacklistType.Temporary; } } // Make sure we start trying to move again should we need to! Bot.Combat.bPickNewAbilities=true; TargetMovement.NewTargetResetVars(); } // Ok we didn't want a new target list, should we at least update the position of the current target, if it's a monster? else if (CurrentTarget.targetType.Value==TargetType.Unit&&CurrentTarget.IsStillValid()) { try { CurrentTarget.UpdatePosition(); } catch { // Keep going anyway if we failed to get a new position from DemonBuddy Logging.WriteDiagnostic("GSDEBUG: Caught position read failure in main target handler loop."); } } } #endregion // This variable just prevents an instant 2-target update after coming here from the main decorator function above Bot.Combat.bWholeNewTarget=false; //Health Change Timer if (CurrentTarget.targetType.Value==TargetType.Unit) { //Update CurrentUnitTarget Variable. if (CurrentUnitTarget==null) CurrentUnitTarget=(CacheUnit)CurrentTarget; double HealthChangeMS=DateTime.Now.Subtract(Bot.Combat.LastHealthChange).TotalMilliseconds; if (HealthChangeMS>3000&&!CurrentTarget.ObjectIsSpecial||HealthChangeMS>6000) { Logging.WriteVerbose("Health change has not occured within 3 seconds for unit {0}", CurrentTarget.InternalName); Bot.Combat.bForceTargetUpdate=true; CurrentState=RunStatus.Running; CurrentTarget.BlacklistLoops=10; return false; } } //We are ready for the specific object type interaction return true; }
///<summary> ///Update our current object data ("Current Target") ///</summary> public bool UpdateTarget() { //Generate a vaild object list using our cached collection! Bot.ValidObjects=ObjectCache.Objects.Values.Where(obj => obj.ObjectIsValidForTargeting).ToList(); //Check avoidance requirement still valid if (Bot.Combat.RequiresAvoidance&& Bot.Combat.TriggeringAvoidances.Count==0&& (!Bot.SettingsFunky.EnableFleeingBehavior||Bot.Character.dCurrentHealthPct>0.25d)) Bot.Combat.RequiresAvoidance=false; #region Grouping Behavior Resume if (Bot.NavigationCache.groupRunningBehavior) { if (!Bot.NavigationCache.groupReturningToOrgin) { Bot.Combat.UpdateGroupClusteringVariables(); bool EndBehavior=false; if (!Bot.NavigationCache.groupingCurrentUnit.ObjectIsValidForTargeting) { if (Bot.SettingsFunky.LogGroupingOutput) Logging.WriteVerbose("[Grouping] Target is no longer valid. Starting return to Orgin."); EndBehavior=true; } else if (Bot.NavigationCache.groupingCurrentUnit.CurrentHealthPct.Value<1d &&Bot.NavigationCache.groupingCurrentUnit.IsMoving) { if (Bot.SettingsFunky.LogGroupingOutput) Logging.WriteVerbose("[Grouping] Target has been engaged. Starting return to Orgin."); EndBehavior=true; } if (!EndBehavior) { CurrentTarget=Bot.NavigationCache.groupingCurrentUnit; return true; } else { Bot.NavigationCache.groupingCurrentUnit=null; Bot.NavigationCache.groupReturningToOrgin=true; CurrentTarget=Bot.NavigationCache.groupingOrginUnit; return true; } } else { bool endBehavior=false; //Returning to Orgin Unit.. if (!Bot.NavigationCache.groupingOrginUnit.ObjectIsValidForTargeting) { endBehavior=true; if (Bot.SettingsFunky.LogGroupingOutput) Logging.WriteVerbose("[Grouping] Orgin Target is no longer valid for targeting."); } else if (Bot.NavigationCache.groupingOrginUnit.CentreDistance<(Bot.Class.IsMeleeClass?25f:45f)) { if (Bot.SettingsFunky.LogGroupingOutput) Logging.WriteVerbose("[Grouping] Orgin Target is within {0}f of the bot.", (Bot.Class.IsMeleeClass?25f:45f).ToString()); endBehavior=true; } if (!endBehavior) { CurrentTarget=Bot.NavigationCache.groupingOrginUnit; return true; } else Bot.NavigationCache.GroupingFinishBehavior(); } } #endregion Vector3 LOS=Vector3.Zero; //Check if we require avoidance movement. #region AvodianceMovementCheck if (Bot.Combat.RequiresAvoidance&&(!Bot.Combat.bAnyTreasureGoblinsPresent||Bot.SettingsFunky.GoblinPriority<2) &&(DateTime.Now.Subtract(Bot.Combat.timeCancelledEmergencyMove).TotalMilliseconds>Bot.Combat.iMillisecondsCancelledEmergencyMoveFor)) { //Reuse the last generated safe spot... if (DateTime.Now.Subtract(Bot.Combat.LastAvoidanceMovement).TotalMilliseconds>= Bot.Combat.iSecondsEmergencyMoveFor) { Vector3 reuseV3=Bot.NavigationCache.AttemptToReuseLastLocationFound(); if (reuseV3!=Vector3.Zero) { CurrentTarget=new CacheObject(reuseV3, TargetType.Avoidance, 20000f, "SafeAvoid", 2.5f, -1); return true; } } Vector3 vAnySafePoint; //if (CurrentTarget!=null&&CurrentTarget.targetType.HasValue&&TargetType.ServerObjects.HasFlag(CurrentTarget.targetType.Value)) // LOS=CurrentTarget.Position; //else // LOS=Vector3.Zero; if (Bot.NavigationCache.AttemptFindSafeSpot(out vAnySafePoint, LOS, Bot.Character.ShouldFlee)) { float distance=vAnySafePoint.Distance(Bot.Character.Position); float losdistance=0f; if (LOS!=Vector3.Zero) losdistance=LOS.Distance(Bot.Character.Position); string los=LOS!=Vector3.Zero?("\r\n using LOS location "+LOS.ToString()+" distance "+losdistance.ToString()):" "; Logging.WriteDiagnostic("Avoid Movement found AT {0} with {1} Distance", vAnySafePoint.ToString(), distance); //bFoundSafeSpot = true; //setup avoidance target if (CurrentTarget!=null) Bot.Combat.LastCachedTarget=CurrentTarget.Clone(); CurrentTarget=new CacheObject(vAnySafePoint, TargetType.Avoidance, 20000f, "SafeAvoid", 2.5f, -1); //Estimate time we will be reusing this movement vector3 Bot.Combat.iSecondsEmergencyMoveFor=1+(int)(distance/25f); //Avoidance takes priority over kiting.. Bot.Combat.timeCancelledFleeMove=DateTime.Now; Bot.Combat.iMillisecondsCancelledFleeMoveFor=((Bot.Combat.iSecondsEmergencyMoveFor+1)*1000); return true; } Bot.UpdateAvoidKiteRates(); } #endregion Bot.Combat.bStayPutDuringAvoidance=false; //cluster update Bot.Combat.UpdateTargetClusteringVariables(); //Standard weighting of valid objects -- updates current target. this.WeightEvaluationObjList(); //check Fleeing #region Fleeing if (Bot.SettingsFunky.EnableFleeingBehavior&&Bot.Character.dCurrentHealthPct<=Bot.SettingsFunky.FleeBotMinimumHealthPercent&&Bot.Combat.FleeTriggeringUnits.Count>0 &&(DateTime.Now.Subtract(Bot.Combat.timeCancelledFleeMove).TotalMilliseconds>Bot.Combat.iMillisecondsCancelledFleeMoveFor) &&(!Bot.Combat.bAnyTreasureGoblinsPresent||Bot.SettingsFunky.GoblinPriority<2) &&(Bot.Class.AC!=ActorClass.Wizard||(Bot.Class.AC==ActorClass.Wizard&&(!Bot.Class.HasBuff(SNOPower.Wizard_Archon)||!Bot.SettingsFunky.Class.bKiteOnlyArchon)))) { //Resuse last safespot until timer expires! if (DateTime.Now.Subtract(Bot.Combat.LastFleeAction).TotalSeconds<Bot.Combat.iSecondsFleeMoveFor) { Vector3 reuseV3=Bot.NavigationCache.AttemptToReuseLastLocationFound(); if (reuseV3!=Vector3.Zero) { CurrentTarget=new CacheObject(reuseV3, TargetType.Avoidance, 20000f, "FleeSpot", 2.5f, -1); return true; } } if (CurrentTarget!=null&&CurrentTarget.targetType.HasValue&&TargetType.ServerObjects.HasFlag(CurrentTarget.targetType.Value) &&(Bot.NavigationCache.CurrentGPArea==null||!Bot.NavigationCache.CurrentGPArea.AllGPRectsFailed)) LOS=CurrentTarget.Position; else LOS=Vector3.Zero; Vector3 vAnySafePoint; if (Bot.NavigationCache.AttemptFindSafeSpot(out vAnySafePoint, LOS, true)) { Logging.WriteDiagnostic("Flee Movement found AT {0} with {1} Distance", vAnySafePoint.ToString(), vAnySafePoint.Distance(Bot.Character.Position)); Bot.Combat.IsFleeing=true; if (CurrentTarget!=null) Bot.Character.LastCachedTarget=CurrentTarget; CurrentTarget=new CacheObject(vAnySafePoint, TargetType.Avoidance, 20000f, "FleeSpot", 2.5f, -1); Bot.Combat.iSecondsFleeMoveFor=1+(int)(Vector3.Distance(Bot.Character.Position, vAnySafePoint)/25f); return true; } Bot.UpdateAvoidKiteRates(); } //If we have a cached kite target.. and no current target, lets swap back! if (Bot.Combat.FleeingLastTarget&&CurrentTarget==null &&Bot.Character.LastCachedTarget!=null &&ObjectCache.Objects.ContainsKey(Bot.Character.LastCachedTarget.RAGUID)) { //Swap back to our orginal "kite" target CurrentTarget=ObjectCache.Objects[Bot.Character.LastCachedTarget.RAGUID]; Logging.WriteVerbose("Swapping back to unit {0} after fleeing", CurrentTarget.InternalName); return true; } #endregion // No valid targets but we were told to stay put? if (CurrentTarget==null&&Bot.Combat.bStayPutDuringAvoidance) { if (Bot.Combat.TriggeringAvoidances.Count==0) { CurrentTarget=new CacheObject(Bot.Character.Position, TargetType.Avoidance, 20000, "StayPutPoint", 2.5f, -1); return true; } else Bot.Combat.iMillisecondsCancelledEmergencyMoveFor=0; //reset wait time } //Final Possible Target Check if (CurrentTarget==null) { // See if we should wait for milliseconds for possible loot drops before continuing run if (DateTime.Now.Subtract(Bot.Combat.lastHadUnitInSights).TotalMilliseconds<=Bot.SettingsFunky.AfterCombatDelay&&DateTime.Now.Subtract(Bot.Combat.lastHadEliteUnitInSights).TotalMilliseconds<=10000|| //Cut the delay time in half for non-elite monsters! DateTime.Now.Subtract(Bot.Combat.lastHadUnitInSights).TotalMilliseconds<=Bot.SettingsFunky.AfterCombatDelay) { CurrentTarget=new CacheObject(Bot.Character.Position, TargetType.Avoidance, 20000, "WaitForLootDrops", 2f, -1); return true; } //Herbfunks wait after loot containers are opened. 3s for rare chests, half the settings delay for everything else. if ((DateTime.Now.Subtract(Bot.Combat.lastHadRareChestAsTarget).TotalMilliseconds<=3750)|| (DateTime.Now.Subtract(Bot.Combat.lastHadContainerAsTarget).TotalMilliseconds<=(Bot.SettingsFunky.AfterCombatDelay*1.25))) { CurrentTarget=new CacheObject(Bot.Character.Position, TargetType.Avoidance, 20000, "ContainerLootDropsWait", 2f, -1); return true; } // Finally, a special check for waiting for wrath of the berserker cooldown before engaging Azmodan if (Bot.Class.HotbarPowers.Contains(SNOPower.Barbarian_WrathOfTheBerserker)&&Bot.SettingsFunky.Class.bWaitForWrath&&!Bot.Class.AbilityUseTimer(SNOPower.Barbarian_WrathOfTheBerserker)&& ZetaDia.CurrentWorldId==121214&& (Vector3.Distance(Bot.Character.Position, new Vector3(711.25f, 716.25f, 80.13903f))<=40f||Vector3.Distance(Bot.Character.Position, new Vector3(546.8467f, 551.7733f, 1.576313f))<=40f)) { Logging.Write("[Funky] Waiting for Wrath Of The Berserker cooldown before continuing to Azmodan."); CurrentTarget=new CacheObject(Bot.Character.Position, TargetType.Avoidance, 20000, "GilesWaitForWrath", 0f, -1); InactivityDetector.Reset(); return true; } // And a special check for wizard archon if (Bot.Class.HotbarPowers.Contains(SNOPower.Wizard_Archon)&&!Bot.Class.AbilityUseTimer(SNOPower.Wizard_Archon)&&Bot.SettingsFunky.Class.bWaitForArchon&&ZetaDia.CurrentWorldId==121214&& (Vector3.Distance(Bot.Character.Position, new Vector3(711.25f, 716.25f, 80.13903f))<=40f||Vector3.Distance(Bot.Character.Position, new Vector3(546.8467f, 551.7733f, 1.576313f))<=40f)) { Logging.Write("[Funky] Waiting for Wizard Archon cooldown before continuing to Azmodan."); CurrentTarget=new CacheObject(Bot.Character.Position, TargetType.Avoidance, 20000, "GilesWaitForArchon", 0f, -1); InactivityDetector.Reset(); return true; } // And a very sexy special check for WD BigBadVoodoo if (Bot.Class.HotbarPowers.Contains(SNOPower.Witchdoctor_BigBadVoodoo)&&!PowerManager.CanCast(SNOPower.Witchdoctor_BigBadVoodoo)&&ZetaDia.CurrentWorldId==121214&& (Vector3.Distance(Bot.Character.Position, new Vector3(711.25f, 716.25f, 80.13903f))<=40f||Vector3.Distance(Bot.Character.Position, new Vector3(546.8467f, 551.7733f, 1.576313f))<=40f)) { Logging.Write("[Funky] Waiting for WD BigBadVoodoo cooldown before continuing to Azmodan."); CurrentTarget=new CacheObject(Bot.Character.Position, TargetType.Avoidance, 20000, "GilesWaitForVoodooo", 0f, -1); InactivityDetector.Reset(); return true; } //Check if our current path intersects avoidances. (When not in town, and not currently inside avoidance) if (!Bot.Character.bIsInTown&&(Bot.SettingsFunky.AttemptAvoidanceMovements||Bot.Combat.CriticalAvoidance) &&Navigation.NP.CurrentPath.Count>0 &&Bot.Combat.TriggeringAvoidances.Count==0) { Vector3 curpos=Bot.Character.Position; IndexedList<Vector3> curpath=Navigation.NP.CurrentPath; var CurrentNearbyPath=curpath.Where(v => curpos.Distance(v)<=40f); if (CurrentNearbyPath!=null&&CurrentNearbyPath.Any()) { CurrentNearbyPath.OrderBy(v => curpath.IndexOf(v)); Vector3 lastV3=Vector3.Zero; foreach (var item in CurrentNearbyPath) { if (lastV3==Vector3.Zero) lastV3=item; else if (ObjectCache.Obstacles.TestVectorAgainstAvoidanceZones(item, lastV3)) { CurrentTarget=new CacheObject(Bot.Character.Position, TargetType.Avoidance, 20000, "AvoidanceIntersection", 2.5f, -1); return true; } } } } } else { if (CurrentTarget.targetType.Equals(TargetType.Unit)) { //Update CurrentUnitTarget Variable. if (CurrentUnitTarget==null) CurrentUnitTarget=(CacheUnit)CurrentTarget; //Grouping Movements if (Bot.SettingsFunky.AttemptGroupingMovements &&CurrentUnitTarget.CurrentHealthPct.Value<1d &&DateTime.Compare(DateTime.Now,Bot.NavigationCache.groupingSuspendedDate)>0 &&!CurrentUnitTarget.IsTreasureGoblin||Bot.SettingsFunky.GoblinPriority<2) //only after we engaged the target. { Bot.Combat.UpdateGroupClusteringVariables(); if (Bot.Combat.CurrentGroupClusters.Count>0) { Cluster currentTargetCluster=CurrentUnitTarget.CurrentTargetCluster; if (currentTargetCluster!=null) { int toughUnitCount=currentTargetCluster.ListUnits.Count(unit => unit.UnitMaxHitPointAverageWeight>0&&unit.CurrentHealthPct.Value>0.50d); //Trigger for tough grouping.. if (toughUnitCount>1) { var targetableUnits=Bot.Combat.CurrentGroupClusters[0].ListUnits.Where(unit => unit.ObjectIsValidForTargeting && (unit.UnitMaxHitPointAverageWeight>0||unit.ObjectIsSpecial)); if (targetableUnits.Any()) { if (Bot.SettingsFunky.LogGroupingOutput) Logging.WriteVerbose("Starting Grouping Behavior. Triggered by Tough Group"); //Activate Behavior Bot.NavigationCache.groupRunningBehavior=true; Bot.NavigationCache.groupingOrginUnit=(CacheUnit)ObjectCache.Objects[CurrentTarget.RAGUID]; //Find initial grouping target.. CurrentTarget=targetableUnits.First(); CurrentUnitTarget=(CacheUnit)CurrentTarget; Bot.NavigationCache.groupingCurrentUnit=CurrentUnitTarget; } } } } } } return true; } return false; }
///<summary> ///Iterates through Usable objects and sets the Bot.CurrentTarget to the highest weighted object found inside the given list. ///</summary> private void WeightEvaluationObjList() { // Store if we are ignoring all units this cycle or not bool bIgnoreAllUnits=!Bot.Combat.bAnyChampionsPresent &&!Bot.Combat.bAnyMobsInCloseRange &&((!Bot.Combat.bAnyTreasureGoblinsPresent&&Bot.SettingsFunky.GoblinPriority>=2)||Bot.SettingsFunky.GoblinPriority<2) &&Bot.Character.dCurrentHealthPct>=0.85d; //clear our last "avoid" list.. ObjectCache.Objects.objectsIgnoredDueToAvoidance.Clear(); double iHighestWeightFound=0; foreach (CacheObject thisobj in Bot.ValidObjects) { thisobj.UpdateWeight(); if (thisobj.Weight==1) { // Force the character to stay where it is if there is nothing available that is out of avoidance stuff and we aren't already in avoidance stuff thisobj.Weight=0; if (!Bot.Combat.RequiresAvoidance) Bot.Combat.bStayPutDuringAvoidance=true; continue; } // Is the weight of this one higher than the current-highest weight? Then make this the new primary target! if (thisobj.Weight>iHighestWeightFound&&thisobj.Weight>0) { //Check combat looting (Demonbuddy Setting) if (iHighestWeightFound>0 &&thisobj.targetType.Value==TargetType.Item &&!Zeta.CommonBot.Settings.CharacterSettings.Instance.CombatLooting &&CurrentTarget.targetType.Value==TargetType.Unit) continue; //cache RAGUID so we can switch back if we need to int CurrentTargetRAGUID=CurrentTarget!=null?CurrentTarget.RAGUID:-1; //Set our current target to this object! CurrentTarget=ObjectCache.Objects[thisobj.RAGUID]; bool resetTarget=false; //Check for Range Classes and Unit Targets if (!Bot.Class.IsMeleeClass&&CurrentTarget.targetType.Value==TargetType.Unit&&Bot.Combat.NearbyAvoidances.Count>0) { //set unit target (for Ability selector). CurrentUnitTarget=(CacheUnit)CurrentTarget; //Generate next Ability.. Ability nextAbility=Bot.Class.AbilitySelector(); //reset unit target CurrentUnitTarget=null; //Check if we are already within interaction range. if (!thisobj.WithinInteractionRange()) { Vector3 destinationV3=nextAbility.DestinationVector; //Check if the estimated destination will also be inside avoidance zone.. if (ObjectCache.Obstacles.IsPositionWithinAvoidanceArea(destinationV3) ||ObjectCache.Obstacles.TestVectorAgainstAvoidanceZones(destinationV3)) { //Only wait if the object is special and we are not avoiding.. if (thisobj.ObjectIsSpecial) { if (!Bot.Combat.RequiresAvoidance) { Bot.Combat.bStayPutDuringAvoidance=true; resetTarget=true; } else if (!nextAbility.IsRanged&&nextAbility.Range>0) { //Non-Ranged Ability.. act like melee.. //Try to find a spot ObjectCache.Objects.objectsIgnoredDueToAvoidance.Add(thisobj); } } else resetTarget=true; } } } //Avoidance Attempt to find a location where we can attack! if (ObjectCache.Objects.objectsIgnoredDueToAvoidance.Contains(thisobj)) { //Wait if no valid target found yet.. and no avoidance movement required. if (!Bot.Combat.RequiresAvoidance) Bot.Combat.bStayPutDuringAvoidance=true; //Check Bot Navigationally blocked Bot.NavigationCache.RefreshNavigationBlocked(); if (!Bot.NavigationCache.BotIsNavigationallyBlocked) { Vector3 SafeLOSMovement; if (thisobj.GPRect.TryFindSafeSpot(Bot.Character.Position, out SafeLOSMovement, Vector3.Zero, Bot.Character.ShouldFlee, true)) { CurrentTarget=new CacheObject(SafeLOSMovement, TargetType.Avoidance, 20000, "SafetyMovement", 2.5f, -1); //Reset Avoidance Timer so we don't trigger it while moving towards the target! Bot.Combat.timeCancelledEmergencyMove=DateTime.Now; Bot.Combat.iMillisecondsCancelledEmergencyMoveFor=1000+((int)(Bot.Target.CurrentTarget.CentreDistance/25f)*1000); } else { resetTarget=true; } } } if (resetTarget) { CurrentTarget=CurrentTargetRAGUID!=-1?ObjectCache.Objects[CurrentTargetRAGUID]:null; continue; } iHighestWeightFound=thisobj.Weight; } } // Loop through all the objects and give them a weight }