/// <summary> /// Returns the possible targets of a prediction input source. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns><see cref="PossibleTarget" /> list.</returns> internal static List<PossibleTarget> GetPossibleTargets(PredictionInput input) { var result = new List<PossibleTarget>(); foreach (var enemy in GameObjects.EnemyHeroes.Where( h => !h.Compare(input.Unit) && h.LSIsValidTarget(input.Range + 200 + input.RealRadius, true, input.RangeCheckFrom))) { var inputs = input.Clone() as PredictionInput; if (inputs == null) { continue; } inputs.Unit = enemy; var prediction = Movement.GetPrediction(inputs, false, true); if (prediction.Hitchance >= HitChance.High) { result.Add(new PossibleTarget { Position = prediction.UnitPosition.ToVector2(), Unit = enemy }); } } return result; }
/// <summary> /// Returns an Area of Effect Prediction /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> public static PredictionOutput GetAoEPrediction(PredictionInput input) { switch (input.Type) { case SkillshotType.SkillshotCircle: return Circle.GetCirclePrediction(input); case SkillshotType.SkillshotCone: return Cone.GetConePrediction(input); case SkillshotType.SkillshotLine: return Line.GetLinePrediction(input); } return new PredictionOutput(); }
/// <summary> /// Returns the possible targets of a prediction input source. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns><see cref="PossibleTarget" /> list.</returns> internal static List<PossibleTarget> GetPossibleTargets(PredictionInput input) { var result = new List<PossibleTarget>(); var originalUnit = input.Unit; foreach (var enemy in GameObjects.EnemyHeroes.Where( h => h.NetworkId != originalUnit.NetworkId && h.IsValidTarget(input.Range + 200 + input.RealRadius, true, input.RangeCheckFrom))) { input.Unit = enemy; var prediction = Movement.GetPrediction(input, false, false); if (prediction.Hitchance >= HitChance.High) { result.Add(new PossibleTarget { Position = prediction.UnitPosition.ToVector2(), Unit = enemy }); } } return result; }
/// <summary> /// Returns Immobile Prediction /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <param name="remainingImmobileT">Remaining Immobile Time</param> /// <returns><see cref="PredictionOutput" /> output</returns> internal static PredictionOutput GetImmobilePrediction(PredictionInput input, double remainingImmobileT) { var result = new PredictionOutput { Input = input, CastPosition = input.Unit.ServerPosition, UnitPosition = input.Unit.ServerPosition, Hitchance = HitChance.High }; var timeToReachTargetPosition = input.Delay + (Math.Abs(input.Speed - float.MaxValue) > float.Epsilon ? input.Unit.Distance(input.From) / input.Speed : 0); if (timeToReachTargetPosition <= remainingImmobileT + input.RealRadius / input.Unit.MoveSpeed) { result.UnitPosition = input.Unit.Position; result.Hitchance = HitChance.Immobile; } return result; }
/// <summary> /// Returns the list of the units that the skill-shot will hit before reaching the set positions. /// </summary> /// <param name="positions"> /// The positions. /// </param> /// <param name="input"> /// The input. /// </param> /// <returns> /// A list of <c>Obj_AI_Base</c>s which the input collides with. /// </returns> public static List <Obj_AI_Base> GetCollision(List <Vector3> positions, PredictionInput input) { var result = new List <Obj_AI_Base>(); foreach (var position in positions) { if (input.CollisionObjects.HasFlag(CollisionableObjects.Minions)) { result.AddRange( GameObjects.EnemyMinions.Where(i => i.IsMinion() || i.IsPet()) .Concat(GameObjects.Jungle) .Where( minion => minion.IsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom) && IsHitCollision(minion, input, position, 20))); } if (input.CollisionObjects.HasFlag(CollisionableObjects.Heroes)) { result.AddRange( GameObjects.EnemyHeroes.Where( hero => hero.IsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom) && IsHitCollision(hero, input, position, 50))); } if (input.CollisionObjects.HasFlag(CollisionableObjects.Walls)) { var step = position.Distance(input.From) / 20; for (var i = 0; i < 20; i++) { if (input.From.ToVector2().Extend(position, step * i).IsWall()) { result.Add(GameObjects.Player); } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.YasuoWall)) { if (yasuoWallLeft == null || yasuoWallRight == null) { continue; } yasuoWallPoly = new RectanglePoly(yasuoWallLeft.Position, yasuoWallRight.Position, 75); var intersections = new List <Vector2>(); for (var i = 0; i < yasuoWallPoly.Points.Count; i++) { var inter = yasuoWallPoly.Points[i].Intersection( yasuoWallPoly.Points[i != yasuoWallPoly.Points.Count - 1 ? i + 1 : 0], input.From.ToVector2(), position.ToVector2()); if (inter.Intersects) { intersections.Add(inter.Point); } } if (intersections.Count > 0) { result.Add(GameObjects.Player); } } } return(result.Distinct().ToList()); }
private static HitChance GetHitChance(PredictionInput input) { var hero = input.Unit as AIHeroClient; if (hero == null || !hero.IsValid || input.Radius <= 1f) { return(HitChance.VeryHigh); } if (hero.IsCastingInterruptableSpell(true) || hero.LSIsRecalling() || (UnitTracker.GetLastStopTick(hero) < 0.1d && hero.IsRooted)) { return(HitChance.VeryHigh); } var wayPoints = hero.GetWaypoints(); var lastWaypoint = wayPoints.Last(); var heroServerPos = hero.ServerPosition.ToVector2(); var heroPos = hero.Position; var distHeroToWaypoint = heroServerPos.Distance(lastWaypoint); var distHeroToFrom = heroServerPos.Distance(input.From); var distFromToWaypoint = input.From.Distance(lastWaypoint); var angle = (lastWaypoint - heroPos.ToVector2()).AngleBetween(input.From - heroPos); var delay = input.Delay + (Math.Abs(input.Speed - float.MaxValue) > float.Epsilon ? distHeroToFrom / input.Speed : 0); var moveArea = hero.MoveSpeed * delay; var fixRange = moveArea * 0.35f; var minPath = 800 + moveArea; if (input.Type == SkillshotType.SkillshotCircle) { fixRange -= input.Radius / 2; } if (distFromToWaypoint <= distHeroToFrom && distHeroToFrom > input.Range - fixRange) { return(HitChance.Medium); } if (distHeroToWaypoint > 0 && distHeroToWaypoint < 50) { return(HitChance.Medium); } if (wayPoints.Count == 1) { return(hero.Spellbook.IsAutoAttacking || UnitTracker.GetLastStopTick(hero) < 0.8d ? HitChance.High : HitChance.VeryHigh); } if (UnitTracker.IsSpamSamePos(hero)) { return(HitChance.VeryHigh); } if (distHeroToFrom < 250 || hero.MoveSpeed < 250 || distFromToWaypoint < 250) { return(HitChance.VeryHigh); } if (distHeroToWaypoint > minPath) { return(HitChance.VeryHigh); } if (hero.HealthPercent < 20 || GameObjects.Player.HealthPercent < 20) { return(HitChance.VeryHigh); } if (input.Type == SkillshotType.SkillshotCircle && GamePath.PathTracker.GetCurrentPath(hero).Time < 0.1d && distHeroToWaypoint > fixRange) { return(HitChance.VeryHigh); } if (distHeroToWaypoint > 0) { if (angle < 20 || angle > 150) { return(HitChance.VeryHigh); } var wallPoints = new List <Vector2>(); for (var i = 1; i <= 15; i++) { var circleAngle = i * 2 * Math.PI / 15; var point = new Vector2( heroPos.X + 450 * (float)Math.Cos(circleAngle), heroPos.Y + 450 * (float)Math.Sin(circleAngle)); if (point.IsWall()) { wallPoints.Add(point); } } if (wallPoints.Count > 2 && !wallPoints.Any(i => heroPos.Distance(i) > lastWaypoint.Distance(i))) { return(HitChance.VeryHigh); } } return(HitChance.Medium); }
/// <summary> /// Get Position on Unit's Path. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <param name="path">Path in Vector2 List</param> /// <param name="speed">Unit Speed</param> /// <returns><see cref="PredictionOutput" /> output</returns> internal static PredictionOutput GetPositionOnPath(PredictionInput input, List <Vector2> path, float speed = -1) { speed = Math.Abs(speed - -1) < float.Epsilon ? input.Unit.MoveSpeed : speed; if (path.Count <= 1) { return(new PredictionOutput { Input = input, UnitPosition = input.Unit.ServerPosition, CastPosition = input.Unit.ServerPosition, Hitchance = HitChance.High }); } var pLength = path.PathLength(); var dist = input.Delay * speed - input.RealRadius; // Skillshots with only a delay if (pLength >= dist && Math.Abs(input.Speed - float.MaxValue) < float.Epsilon) { var tDistance = dist; for (var i = 0; i < path.Count - 1; i++) { var a = path[i]; var b = path[i + 1]; var d = a.Distance(b); if (d >= tDistance) { var direction = (b - a).LSNormalized(); var cp = a + direction * tDistance; var p = a + direction * (i == path.Count - 2 ? Math.Min(tDistance + input.RealRadius, d) : tDistance + input.RealRadius); return(new PredictionOutput { Input = input, CastPosition = cp.ToVector3(), UnitPosition = p.ToVector3(), Hitchance = HitChance.High }); } tDistance -= d; } } // Skillshot with a delay and speed. if (pLength >= dist && Math.Abs(input.Speed - float.MaxValue) > float.Epsilon) { var tDistance = dist; if ((input.Type == SkillshotType.SkillshotLine || input.Type == SkillshotType.SkillshotCone) && input.Unit.DistanceSquared(input.From) < 200 * 200) { tDistance = dist - input.RealRadius; } path = path.CutPath(tDistance); var tT = 0f; for (var i = 0; i < path.Count - 1; i++) { var a = path[i]; var b = path[i + 1]; var tB = a.Distance(b) / speed; var direction = (b - a).LSNormalized(); a = a - speed * tT * direction; var sol = a.VectorMovementCollision(b, speed, input.From.ToVector2(), input.Speed, tT); var t = (float)sol[0]; var pos = (Vector2)sol[1]; if (pos.IsValid() && t >= tT && t <= tT + tB) { if (pos.LSDistanceSquared(b) < 20) { break; } var p = pos + input.RealRadius * direction; /*if (input.Type == SkillshotType.SkillshotLine) * { * var alpha = (input.From.ToVector2() - p).AngleBetween(a - b); * * if (alpha > 30 && alpha < 180 - 30) * { * var beta = (float)Math.Asin(input.RealRadius / p.Distance(input.From)); * var cp1 = input.From.ToVector2() + (p - input.From.ToVector2()).Rotated(beta); * var cp2 = input.From.ToVector2() + (p - input.From.ToVector2()).Rotated(-beta); * * pos = cp1.DistanceSquared(pos) < cp2.DistanceSquared(pos) ? cp1 : cp2; * } * }*/ return(new PredictionOutput { Input = input, CastPosition = pos.ToVector3(), UnitPosition = p.ToVector3(), Hitchance = HitChance.High }); } tT += tB; } } var position = path.Last().ToVector3(); return(new PredictionOutput { Input = input, CastPosition = position, UnitPosition = position, Hitchance = HitChance.Medium }); }
/// <summary> /// Get Position on Unit's Path. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <param name="path">Path in Vector2 List</param> /// <param name="speed">Unit Speed</param> /// <returns><see cref="PredictionOutput" /> output</returns> internal static PredictionOutput GetPositionOnPath(PredictionInput input, List<Vector2> path, float speed = -1) { speed = (Math.Abs(speed - (-1)) < float.Epsilon) ? input.Unit.MoveSpeed : speed; if (path.Count <= 1) { return new PredictionOutput { Input = input, UnitPosition = input.Unit.ServerPosition, CastPosition = input.Unit.ServerPosition, Hitchance = HitChance.VeryHigh }; } var pLength = path.PathLength(); // Skillshots with only a delay if (pLength >= (input.Delay * speed) - input.RealRadius && Math.Abs(input.Speed - float.MaxValue) < float.Epsilon) { var tDistance = (input.Delay * speed) - input.RealRadius; for (var i = 0; i < path.Count - 1; i++) { var a = path[i]; var b = path[i + 1]; var d = a.Distance(b); if (d >= tDistance) { var direction = (b - a).Normalized(); var cp = a + (direction * tDistance); var p = a + (direction * ((i == path.Count - 2) ? Math.Min(tDistance + input.RealRadius, d) : (tDistance + input.RealRadius))); return new PredictionOutput { Input = input, CastPosition = cp.ToVector3(), UnitPosition = p.ToVector3(), Hitchance = GamePath.PathTracker.GetCurrentPath(input.Unit).Time < 0.1d ? HitChance.VeryHigh : HitChance.High }; } tDistance -= d; } } // Skillshot with a delay and speed. if (pLength >= (input.Delay * speed) - input.RealRadius && Math.Abs(input.Speed - float.MaxValue) > float.Epsilon) { path = path.CutPath(Math.Max(0, (input.Delay * speed) - input.RealRadius)); var tT = 0f; for (var i = 0; i < path.Count - 1; i++) { var a = path[i]; var b = path[i + 1]; var tB = a.Distance(b) / speed; var direction = (b - a).Normalized(); a = a - (speed * tT * direction); var sol = a.VectorMovementCollision(b, speed, input.From.ToVector2(), input.Speed, tT); var t = (float)sol[0]; var pos = (Vector2)sol[1]; if (pos.IsValid() && t >= tT && t <= tT + tB) { var p = pos + (input.RealRadius * direction); if (input.Type == SkillshotType.SkillshotLine) { var alpha = (input.From.ToVector2() - p).AngleBetween(a - b); if (alpha > 30 && alpha < 180 - 30) { var beta = (float)Math.Asin(input.RealRadius / p.Distance(input.From)); var cp1 = input.From.ToVector2() + (p - input.From.ToVector2()).Rotated(beta); var cp2 = input.From.ToVector2() + (p - input.From.ToVector2()).Rotated(-beta); pos = cp1.DistanceSquared(pos) < cp2.DistanceSquared(pos) ? cp1 : cp2; } } return new PredictionOutput { Input = input, CastPosition = pos.ToVector3(), UnitPosition = p.ToVector3(), Hitchance = GamePath.PathTracker.GetCurrentPath(input.Unit).Time < 0.1d ? HitChance.VeryHigh : HitChance.High }; } tT += tB; } } var position = path.Last(); return new PredictionOutput { Input = input, CastPosition = position.ToVector3(), UnitPosition = position.ToVector3(), Hitchance = HitChance.Medium }; }
/// <summary> /// Returns Dashing Prediction /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns><see cref="PredictionOutput" /> output</returns> internal static PredictionOutput GetDashingPrediction(PredictionInput input) { var dashData = input.Unit.GetDashInfo(); var result = new PredictionOutput { Input = input }; input.Delay += 0.1f; // Normal dashes. if (!dashData.IsBlink) { // Mid air: var dashPred = GetPositionOnPath( input, new List<Vector2> { input.Unit.ServerPosition.ToVector2(), dashData.Path.Last() }, dashData.Speed); if (dashPred.Hitchance >= HitChance.High) { dashPred.CastPosition = dashPred.UnitPosition; dashPred.Hitchance = HitChance.Dashing; return dashPred; } // At the end of the dash: if (dashData.Path.PathLength() > 200) { var endP = dashData.Path.Last(); var timeToPoint = input.Delay + (input.From.Distance(endP) / input.Speed); if (timeToPoint <= (input.Unit.Distance(endP) / dashData.Speed) + (input.RealRadius / input.Unit.MoveSpeed)) { return new PredictionOutput { CastPosition = endP.ToVector3(), UnitPosition = endP.ToVector3(), Hitchance = HitChance.Dashing }; } } result.CastPosition = dashData.Path.Last().ToVector3(); result.UnitPosition = result.CastPosition; // Figure out where the unit is going. } return result; }
/// <summary> /// Returns the list of the units that the skill-shot will hit before reaching the set positions. /// </summary> /// <param name="positions"> /// The positions. /// </param> /// <param name="input"> /// The input. /// </param> /// <returns> /// A list of <c>Obj_AI_Base</c>s which the input collides with. /// </returns> public static List<Obj_AI_Base> GetCollision(List<Vector3> positions, PredictionInput input) { var result = new List<Obj_AI_Base>(); foreach (var position in positions) { if (input.CollisionObjects.HasFlag(CollisionableObjects.Minions)) { foreach (var minion in GameObjects.EnemyMinions.Where( minion => minion.IsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom))) { input.Unit = minion; var minionPrediction = Movement.GetPrediction(input, false, false); if (minionPrediction.UnitPosition.ToVector2() .DistanceSquared(input.From.ToVector2(), position.ToVector2(), true) <= Math.Pow(input.Radius + 15 + minion.BoundingRadius, 2)) { result.Add(minion); } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.Heroes)) { foreach (var hero in GameObjects.EnemyHeroes.Where( hero => hero.IsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom))) { input.Unit = hero; var prediction = Movement.GetPrediction(input, false, false); if (prediction.UnitPosition.ToVector2() .DistanceSquared(input.From.ToVector2(), position.ToVector2(), true) <= Math.Pow(input.Radius + 50 + hero.BoundingRadius, 2)) { result.Add(hero); } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.Walls)) { var step = position.Distance(input.From) / 20; for (var i = 0; i < 20; i++) { var p = input.From.ToVector2().Extend(position.ToVector2(), step * i); if (NavMesh.GetCollisionFlags(p.X, p.Y).HasFlag(CollisionFlags.Wall)) { result.Add(GameObjects.Player); } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.YasuoWall)) { if (Variables.TickCount - wallCastT > 4000) { continue; } GameObject wall = null; foreach (var gameObject in GameObjects.AllGameObjects.Where( gameObject => gameObject.IsValid && Regex.IsMatch(gameObject.Name, "_w_windwall_enemy_0.\\.troy", RegexOptions.IgnoreCase))) { wall = gameObject; } if (wall == null) { break; } var level = wall.Name.Substring(wall.Name.Length - 6, 1); var wallWidth = 300 + (50 * Convert.ToInt32(level)); var wallDirection = (wall.Position.ToVector2() - yasuoWallCastedPos).Normalized().Perpendicular(); var wallStart = wall.Position.ToVector2() + (wallWidth / 2f * wallDirection); var wallEnd = wallStart - (wallWidth * wallDirection); if (wallStart.Intersection(wallEnd, position.ToVector2(), input.From.ToVector2()).Intersects) { var t = Variables.TickCount + (((wallStart.Intersection(wallEnd, position.ToVector2(), input.From.ToVector2()) .Point.Distance(input.From) / input.Speed) + input.Delay) * 1000); if (t < wallCastT + 4000) { result.Add(GameObjects.Player); } } } } return result.Distinct().ToList(); }
/// <summary> /// Returns an Area-of-Effect line prediction from a prediction input source. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> public static PredictionOutput GetLinePrediction(PredictionInput input) { var mainTargetPrediction = Movement.GetPrediction(input, false, true); var posibleTargets = new List<PossibleTarget> { new PossibleTarget { Position = mainTargetPrediction.UnitPosition.ToVector2(), Unit = input.Unit } }; if (mainTargetPrediction.Hitchance >= HitChance.Medium) { // Add the posible targets in range: posibleTargets.AddRange(GetPossibleTargets(input)); } if (posibleTargets.Count > 1) { var candidates = new List<Vector2>(); foreach (var targetCandidates in posibleTargets.Select( target => GetCandidates(input.From.ToVector2(), target.Position, input.Radius, input.Range)) ) { candidates.AddRange(targetCandidates); } var bestCandidateHits = -1; var bestCandidate = default(Vector2); var bestCandidateHitPoints = new List<Vector2>(); var positionsList = posibleTargets.Select(t => t.Position).ToList(); foreach (var candidate in candidates) { if ( GetHits( input.From.ToVector2(), candidate, input.Radius + (input.Unit.BoundingRadius / 3) - 10, new List<Vector2> { posibleTargets[0].Position }).Count() == 1) { var hits = GetHits(input.From.ToVector2(), candidate, input.Radius, positionsList).ToList(); var hitsCount = hits.Count; if (hitsCount >= bestCandidateHits) { bestCandidateHits = hitsCount; bestCandidate = candidate; bestCandidateHitPoints = hits.ToList(); } } } if (bestCandidateHits > 1) { float maxDistance = -1; Vector2 p1 = default(Vector2), p2 = default(Vector2); // Center the position for (var i = 0; i < bestCandidateHitPoints.Count; i++) { for (var j = 0; j < bestCandidateHitPoints.Count; j++) { var startP = input.From.ToVector2(); var endP = bestCandidate; var proj1 = positionsList[i].LSProjectOn(startP, endP); var proj2 = positionsList[j].LSProjectOn(startP, endP); var dist = bestCandidateHitPoints[i].LSDistanceSquared(proj1.LinePoint) + bestCandidateHitPoints[j].LSDistanceSquared(proj2.LinePoint); if (dist >= maxDistance && (proj1.LinePoint - positionsList[i]).AngleBetween( proj2.LinePoint - positionsList[j]) > 90) { maxDistance = dist; p1 = positionsList[i]; p2 = positionsList[j]; } } } return new PredictionOutput { Hitchance = mainTargetPrediction.Hitchance, AoeHitCount = bestCandidateHits, UnitPosition = mainTargetPrediction.UnitPosition, CastPosition = ((p1 + p2) * 0.5f).ToVector3(), Input = input }; } } return mainTargetPrediction; }
/// <summary> /// Returns an Area-of-Effect cone prediction from a prediction input source. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> public static PredictionOutput GetConePrediction(PredictionInput input) { var mainTargetPrediction = Movement.GetPrediction(input, false, true); var posibleTargets = new List<PossibleTarget> { new PossibleTarget { Position = mainTargetPrediction.UnitPosition.ToVector2(), Unit = input.Unit } }; if (mainTargetPrediction.Hitchance >= HitChance.Medium) { // Add the posible targets in range: posibleTargets.AddRange(GetPossibleTargets(input)); } if (posibleTargets.Count > 1) { var candidates = new List<Vector2>(); foreach (var target in posibleTargets) { target.Position = target.Position - input.From.ToVector2(); } for (var i = 0; i < posibleTargets.Count; i++) { for (var j = 0; j < posibleTargets.Count; j++) { if (i != j) { var p = (posibleTargets[i].Position + posibleTargets[j].Position) * 0.5f; if (!candidates.Contains(p)) { candidates.Add(p); } } } } var bestCandidateHits = -1; var bestCandidate = default(Vector2); var positionsList = posibleTargets.Select(t => t.Position).ToList(); foreach (var candidate in candidates) { var hits = GetHits(candidate, input.Range, input.Radius, positionsList); if (hits > bestCandidateHits) { bestCandidate = candidate; bestCandidateHits = hits; } } if (bestCandidateHits > 1 && input.From.LSDistanceSquared(bestCandidate) > 50 * 50) { return new PredictionOutput { Hitchance = mainTargetPrediction.Hitchance, AoeHitCount = bestCandidateHits, UnitPosition = mainTargetPrediction.UnitPosition, CastPosition = bestCandidate.ToVector3(), Input = input }; } } return mainTargetPrediction; }
private static bool IsDead(PredictionInput input, Obj_AI_Base minion, float distance) { var delay = (distance / input.Speed) + input.Delay; if (Math.Abs(input.Speed - float.MaxValue) < float.Epsilon) { delay = input.Delay; } var convert = (int)(delay * 1000); return Health.GetPrediction(minion, convert, 0, HealthPredictionType.Simulated) <= 0; }
/// <summary> /// Returns Calculated Prediction based off given data values. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <param name="ft">Add Delay</param> /// <param name="checkCollision">Check Collision</param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> internal static PredictionOutput GetPrediction(PredictionInput input, bool ft, bool checkCollision) { if (!input.Unit.IsValidTarget(float.MaxValue, false)) { return(new PredictionOutput()); } if (ft) { input.Delay += Game.Ping / 2000f + 0.05f; if (input.AoE) { return(Cluster.GetAoEPrediction(input)); } } // Target too far away. if (Math.Abs(input.Range - float.MaxValue) > float.Epsilon && input.Unit.DistanceSquared(input.RangeCheckFrom) > Math.Pow(input.Range * 1.5, 2)) { return(new PredictionOutput { Input = input }); } PredictionOutput result = null; // Unit is dashing. if (input.Unit.IsDashing()) { result = GetDashingPrediction(input); } else { // Unit is immobile. var remainingImmobileT = UnitIsImmobileUntil(input.Unit); if (remainingImmobileT >= 0d) { result = GetImmobilePrediction(input, remainingImmobileT); } } // Normal prediction if (result == null) { result = GetStandardPrediction(input); } if (!((Player.Instance).Distance(input.Unit, true) < input.Range * input.Range)) { result.Hitchance = HitChance.OutOfRange; } // Check for collision if (checkCollision && input.Collision && Math.Abs(input.Speed - float.MaxValue) > float.Epsilon) { var positions = new List <Vector3> { result.UnitPosition, input.Unit.Position }; result.CollisionObjects = Collision.GetCollision(positions, input); result.CollisionObjects.RemoveAll(x => x.Compare(input.Unit)); if (result.CollisionObjects.Count > 0) { result.Hitchance = HitChance.Collision; } } // Calc hitchance again if (result.Hitchance == HitChance.High) { result.Hitchance = GetHitChance(input); } return(result); }
/// <summary> /// Returns Calculated Prediction based off given data values. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <param name="ft">Add Delay</param> /// <param name="checkCollision">Check Collision</param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> internal static PredictionOutput GetPrediction(PredictionInput input, bool ft, bool checkCollision) { if (!input.Unit.LSIsValidTarget(float.MaxValue, false)) { return new PredictionOutput(); } if (ft) { // Increase the delay due to the latency and server tick: input.Delay += Game.Ping / 2000f + 0.06f; if (input.AoE) { return Cluster.GetAoEPrediction(input); } } // Target too far away. if (Math.Abs(input.Range - float.MaxValue) > float.Epsilon && input.Unit.LSDistanceSquared(input.RangeCheckFrom) > Math.Pow(input.Range * 1.5, 2)) { return new PredictionOutput { Input = input }; } PredictionOutput result = null; // Unit is dashing. if (input.Unit.IsDashing()) { result = GetDashingPrediction(input); } else { // Unit is immobile. var remainingImmobileT = UnitIsImmobileUntil(input.Unit); if (remainingImmobileT >= 0d) { result = GetImmobilePrediction(input, remainingImmobileT); } } // Normal prediction if (result == null) { result = GetStandardPrediction(input); } // Check if the unit position is in range if (Math.Abs(input.Range - float.MaxValue) > float.Epsilon) { if (result.Hitchance >= HitChance.High && input.RangeCheckFrom.LSDistanceSquared(input.Unit.Position) > Math.Pow(input.Range + input.RealRadius * 3 / 4, 2)) { result.Hitchance = HitChance.Medium; } if (input.RangeCheckFrom.LSDistanceSquared(result.UnitPosition) > Math.Pow(input.Range + (input.Type == SkillshotType.SkillshotCircle ? input.RealRadius : 0), 2)) { result.Hitchance = HitChance.OutOfRange; } if (input.RangeCheckFrom.LSDistanceSquared(result.CastPosition) > Math.Pow(input.Range, 2) && result.Hitchance != HitChance.OutOfRange) { result.CastPosition = input.RangeCheckFrom + input.Range * (result.UnitPosition - input.RangeCheckFrom).ToVector2() .LSNormalized() .ToVector3(); } } // Check for collision if (checkCollision && input.Collision && Math.Abs(input.Speed - float.MaxValue) > float.Epsilon) { var positions = new List<Vector3> { result.UnitPosition, input.Unit.Position }; result.CollisionObjects = Collision.GetCollision(positions, input); result.CollisionObjects.RemoveAll(x => x.Compare(input.Unit)); if (result.CollisionObjects.Count > 0) { result.Hitchance = HitChance.Collision; } } // Calc hitchance again if (result.Hitchance == HitChance.High) { result.Hitchance = GetHitChance(input); } return result; }
/// <summary> /// Returns the list of the units that the skill-shot will hit before reaching the set positions. /// </summary> /// <param name="positions"> /// The positions. /// </param> /// <param name="input"> /// The input. /// </param> /// <returns> /// A list of <c>Obj_AI_Base</c>s which the input collides with. /// </returns> /// public static List <Obj_AI_Base> GetCollision(List <Vector3> positions, PredictionInput input) { var result = new List <Obj_AI_Base>(); foreach (var position in positions) { if (input.CollisionObjects.HasFlag(CollisionableObjects.Minions)) { result.AddRange( EntityManager.MinionsAndMonsters.EnemyMinions.Where(i => i.IsMinion || i.Pet != null) .Concat(GameObjects.Jungle) .Where( minion => minion.IsValidTarget(Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom) && IsHitCollision(minion, input, position, 20))); } if (input.CollisionObjects.HasFlag(CollisionableObjects.Heroes)) { result.AddRange( GameObjects.EnemyHeroes.Where( hero => hero.IsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom) && IsHitCollision(hero, input, position, 50))); } if (input.CollisionObjects.HasFlag(CollisionableObjects.Walls)) { var step = position.Distance(input.From) / 20; for (var i = 0; i < 20; i++) { if (input.From.ToVector2().Extend(position, step * i).IsWall()) { result.Add(GameObjects.Player); } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.YasuoWall)) { /*if (Variables.TickCount - wallCastT > 4000) * { * continue; * }*/ var wall = GameObjects.AllGameObjects.FirstOrDefault( gameObject => gameObject.IsValid && Regex.IsMatch(gameObject.Name, "_w_windwall_enemy_0.\\.troy", RegexOptions.IgnoreCase)); if (wall == null) { continue; } Chat.Print(wall.Name + " => " + wall.Type + " | " + wall.Team); var level = wall.Name.Substring(wall.Name.Length - 6, 1); var wallWidth = 300 + (50 * Convert.ToInt32(level)); var wallDirection = (wall.Position.ToVector2() - yasuoWallCastedPos).LSNormalized().Perpendicular(); var wallStart = wall.Position.ToVector2() + (wallWidth / 2f * wallDirection); var wallEnd = wallStart - (wallWidth * wallDirection); var wallPolygon = new RectanglePoly(wallStart, wallEnd, 75); var intersections = new List <Vector2>(); for (var i = 0; i < wallPolygon.Points.Count; i++) { var inter = wallPolygon.Points[i].Intersection( wallPolygon.Points[i != wallPolygon.Points.Count - 1 ? i + 1 : 0], input.From.ToVector2(), position.ToVector2()); if (inter.Intersects) { intersections.Add(inter.Point); } } wallPolygon.Draw(Color.Red); if (intersections.Count > 0) { result.Add(GameObjects.Player); } } } return(result.Distinct().ToList()); }
private static HitChance GetHitChance(PredictionInput input) { var hero = input.Unit as AIHeroClient; if (hero == null || !hero.IsValid || input.Radius <= 1f) { return HitChance.VeryHigh; } if (hero.IsCastingInterruptableSpell(true) || hero.LSIsRecalling() || (UnitTracker.GetLastStopTick(hero) < 0.1d && hero.IsRooted)) { return HitChance.VeryHigh; } var wayPoints = hero.GetWaypoints(); var lastWaypoint = wayPoints.Last(); var heroPos = hero.Position; var heroServerPos = hero.ServerPosition.ToVector2(); var distHeroToWaypoint = heroServerPos.Distance(lastWaypoint); var distHeroToFrom = heroServerPos.Distance(input.From); var distFromToWaypoint = input.From.Distance(lastWaypoint); var angle = (lastWaypoint - heroPos.ToVector2()).AngleBetween(input.From - heroPos); var delay = input.Delay + (Math.Abs(input.Speed - float.MaxValue) > float.Epsilon ? distHeroToFrom / input.Speed : 0); var moveArea = hero.MoveSpeed * delay; var fixRange = moveArea * 0.35f; var minPath = 1000; if (input.Type == SkillshotType.SkillshotCircle) { fixRange -= input.Radius / 2; } if (distFromToWaypoint <= distHeroToFrom && distHeroToFrom > input.Range - fixRange) { return HitChance.Medium; } if (distHeroToWaypoint > 0) { if (angle < 20 || angle > 160 || (angle > 130 && distHeroToWaypoint > 400)) { return HitChance.VeryHigh; } var wallPoints = new List<Vector2>(); for (var i = 1; i <= 15; i++) { var circleAngle = i * 2 * Math.PI / 15; var point = new Vector2( heroPos.X + 350 * (float)Math.Cos(circleAngle), heroPos.Y + 350 * (float)Math.Sin(circleAngle)); if (point.IsWall()) { wallPoints.Add(point); } } if (wallPoints.Count > 2 && !wallPoints.Any(i => heroPos.Distance(i) > lastWaypoint.Distance(i))) { return HitChance.VeryHigh; } } if (distHeroToWaypoint > 0 && distHeroToWaypoint < 100) { return HitChance.Medium; } if (wayPoints.Count == 1) { return hero.Spellbook.IsAutoAttacking || UnitTracker.GetLastStopTick(hero) < 0.8d ? HitChance.High : HitChance.VeryHigh; } if (UnitTracker.IsSpamSamePos(hero)) { return HitChance.VeryHigh; } if (distHeroToFrom < 250 || hero.MoveSpeed < 250 || distFromToWaypoint < 250) { return HitChance.VeryHigh; } if (GamePath.PathTracker.GetCurrentPath(hero).Time > 0.25d) { return HitChance.VeryHigh; } if (distHeroToWaypoint > minPath) { return HitChance.VeryHigh; } if (hero.HealthPercent < 20 || GameObjects.Player.HealthPercent < 20) { return HitChance.VeryHigh; } if (input.Type == SkillshotType.SkillshotCircle && GamePath.PathTracker.GetCurrentPath(hero).Time < 0.1d && distHeroToWaypoint > fixRange) { return HitChance.VeryHigh; } return HitChance.Medium; }
private static HitChance GetHitChance(PredictionInput input) { var hero = input.Unit as AIHeroClient; if (hero == null || input.Radius <= 1f) { return(HitChance.VeryHigh); } if (hero.IsCastingInterruptableSpell(true) || hero.LSIsRecalling()) { return(HitChance.VeryHigh); } if (hero.Path.Length > 0 != hero.IsMoving) { return(HitChance.Medium); } var wayPoint = input.Unit.GetWaypoints().Last(); var delay = input.Delay + (Math.Abs(input.Speed - float.MaxValue) > float.Epsilon ? hero.Distance(input.From) / input.Speed : 0); var moveArea = hero.MoveSpeed * delay; var fixRange = moveArea * 0.4f; var minPath = 900 + moveArea; var moveAngle = 31d; if (input.Radius > 70) { moveAngle++; } else if (input.Radius <= 60) { moveAngle--; } if (input.Delay < 0.3) { moveAngle++; } if (GamePath.PathTracker.GetCurrentPath(input.Unit).Time < 0.1d) { fixRange = moveArea * 0.3f; minPath = 700 + moveArea; moveAngle += 1.5; } if (input.Type == SkillshotType.SkillshotCircle) { fixRange -= input.Radius / 2; } if (input.From.Distance(wayPoint) <= hero.Distance(input.From)) { if (hero.Distance(input.From) > input.Range - fixRange) { return(HitChance.Medium); } } else if (hero.Distance(wayPoint) > 350) { moveAngle += 1.5; } if (hero.Distance(input.From) < 250 || hero.MoveSpeed < 250 || input.From.Distance(wayPoint) < 250) { return(HitChance.VeryHigh); } if (hero.Distance(wayPoint) > minPath) { return(HitChance.VeryHigh); } if (hero.HealthPercent < 20 || GameObjects.Player.HealthPercent < 20) { return(HitChance.VeryHigh); } if (GetAngle(input.From.ToVector2(), hero.ServerPosition.ToVector2(), wayPoint) < moveAngle && hero.Distance(wayPoint) > 260) { return(HitChance.VeryHigh); } if (input.Type == SkillshotType.SkillshotCircle && GamePath.PathTracker.GetCurrentPath(input.Unit).Time < 0.1d && hero.Distance(wayPoint) > fixRange) { return(HitChance.VeryHigh); } return(HitChance.High); }
/// <summary> /// Calculates the position to cast a spell according to unit movement. /// </summary> /// <param name="input">PredictionInput type</param> /// <param name="additionalSpeed">Additional Speed (Multiplicative)</param> /// <returns>The <see cref="PredictionOutput" /></returns> internal static PredictionOutput GetAdvancedPrediction(PredictionInput input, float additionalSpeed = 0) { var speed = Math.Abs(additionalSpeed) < float.Epsilon ? input.Speed : input.Speed * additionalSpeed; if (Math.Abs(speed - int.MaxValue) < float.Epsilon) { speed = 90000; } var unit = input.Unit; var position = PositionAfter(unit, 1, unit.MoveSpeed - 100); var prediction = position + (speed * (input.Delay / 1000)); return new PredictionOutput() { UnitPosition = new Vector3(position.X, position.Y, unit.ServerPosition.Z), CastPosition = new Vector3(prediction.X, prediction.Y, unit.ServerPosition.Z), Hitchance = HitChance.High }; }
/// <summary> /// Returns the list of the units that the skill-shot will hit before reaching the set positions. /// </summary> /// <param name="positions"> /// The positions. /// </param> /// <param name="input"> /// The input. /// </param> /// <returns> /// A list of <c>Obj_AI_Base</c>s which the input collides with. /// </returns> public static List<Obj_AI_Base> GetCollision(List<Vector3> positions, PredictionInput input) { var result = new List<Obj_AI_Base>(); foreach (var position in positions) { if (input.CollisionObjects.HasFlag(CollisionableObjects.Minions)) { result.AddRange( GameObjects.EnemyMinions.Where(i => i.IsMinion() || i.IsPet()) .Concat(GameObjects.Jungle) .Where( minion => minion.LSIsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom) && IsHitCollision(minion, input, position, 15))); } if (input.CollisionObjects.HasFlag(CollisionableObjects.Heroes)) { result.AddRange( GameObjects.EnemyHeroes.Where( hero => hero.LSIsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom) && IsHitCollision(hero, input, position, 50))); } if (input.CollisionObjects.HasFlag(CollisionableObjects.Walls)) { var step = position.Distance(input.From) / 20; for (var i = 0; i < 20; i++) { if (input.From.ToVector2().Extend(position, step * i).IsWall()) { result.Add(GameObjects.Player); } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.YasuoWall)) { if (yasuoWallLeft == null || yasuoWallRight == null) { continue; } yasuoWallPoly = new RectanglePoly(yasuoWallLeft.Position, yasuoWallRight.Position, 75); var intersections = new List<Vector2>(); for (var i = 0; i < yasuoWallPoly.Points.Count; i++) { var inter = yasuoWallPoly.Points[i].LSIntersection( yasuoWallPoly.Points[i != yasuoWallPoly.Points.Count - 1 ? i + 1 : 0], input.From.ToVector2(), position.ToVector2()); if (inter.Intersects) { intersections.Add(inter.Point); } } if (intersections.Count > 0) { result.Add(GameObjects.Player); } } } return result.Distinct().ToList(); }
/// <summary> /// Returns Immobile Prediction /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <param name="remainingImmobileT">Remaining Immobile Time</param> /// <returns><see cref="PredictionOutput" /> output</returns> internal static PredictionOutput GetImmobilePrediction(PredictionInput input, double remainingImmobileT) { var timeToReachTargetPosition = input.Delay + (input.Unit.Distance(input.From) / input.Speed); if (timeToReachTargetPosition <= remainingImmobileT + (input.RealRadius / input.Unit.MoveSpeed)) { return new PredictionOutput { CastPosition = input.Unit.ServerPosition, UnitPosition = input.Unit.Position, Hitchance = HitChance.Immobile }; } return new PredictionOutput { Input = input, CastPosition = input.Unit.ServerPosition, UnitPosition = input.Unit.ServerPosition, Hitchance = HitChance.High }; }
internal static List<Obj_AI_Base> GetCollision(List<Vector3> positions, PredictionInput input) { var result = new List<Obj_AI_Base>(); foreach (var position in positions) { if (input.CollisionObjects.HasFlag(CollisionableObjects.Minions)) { GameObjects.EnemyMinions.Where(i => i.IsMinion() || i.IsPet()) .Concat(GameObjects.Jungle) .Where( i => i.IsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom) && !result.Any(a => a.Compare(i))) .ForEach( i => { input.Unit = i; if ( input.GetPrediction(false, false) .UnitPosition.ToVector2() .DistanceSquared(input.From.ToVector2(), position.ToVector2(), true) <= Math.Pow(input.Radius + 20 + i.BoundingRadius, 2)) { result.Add(i); } }); } if (input.CollisionObjects.HasFlag(CollisionableObjects.Heroes)) { GameObjects.EnemyHeroes.Where( i => i.IsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom) && !result.Any(a => a.Compare(i))).ForEach( i => { input.Unit = i; if ( input.GetPrediction(false, false) .UnitPosition.ToVector2() .DistanceSquared(input.From.ToVector2(), position.ToVector2(), true) <= Math.Pow(input.Radius + 50 + i.BoundingRadius, 2)) { result.Add(i); } }); } if (input.CollisionObjects.HasFlag(CollisionableObjects.Walls) && !result.Any(i => i.IsMe)) { var step = position.Distance(input.From) / 20; for (var i = 0; i < 20; i++) { if (input.From.Extend(position, step * i).IsWall()) { result.Add(ObjectManager.Player); break; } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.YasuoWall) && !result.Any(i => i.IsMe) && Variables.TickCount - yasuoWallCastT <= 4000) { var wall = GameObjects.AllGameObjects.FirstOrDefault( i => i.IsValid && Regex.IsMatch(i.Name, "_w_windwall_enemy_0.\\.troy", RegexOptions.IgnoreCase)); if (wall == null) { continue; } var wallWidth = 300 + 50 * Convert.ToInt32(wall.Name.Substring(wall.Name.Length - 6, 1)); var wallDirection = (yasuoWallCastPos - wall.Position.ToVector2()).Normalized().Perpendicular(); var wallStart = wall.Position.ToVector2() + wallWidth / 2f * wallDirection; var wallEnd = wallStart - wallWidth * wallDirection; var wallIntersect = wallStart.Intersection( wallEnd, position.ToVector2(), input.From.ToVector2()); if (wallIntersect.Intersects) { var t = Variables.TickCount + (wallIntersect.Point.Distance(input.From) / input.Speed + input.Delay) * 1000; if (t < yasuoWallCastT + 4000) { result.Add(ObjectManager.Player); } } } } return result.Distinct().ToList(); }
/// <summary> /// Returns Standard Prediction /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns><see cref="PredictionOutput" /> output</returns> internal static PredictionOutput GetStandardPrediction(PredictionInput input) { var speed = input.Unit.MoveSpeed; if (input.Unit.DistanceSquared(input.From) < 200 * 200) { // input.Delay /= 2; speed /= 1.5f; } var result = GetPositionOnPath(input, input.Unit.GetWaypoints(), speed); if (result.Hitchance >= HitChance.High && input.Unit is Obj_AI_Hero) { } return result; }
private static List<PossibleTarget> GetPossibleTargets(PredictionInput input) { var result = new List<PossibleTarget>(); var originalUnit = input.Unit; GameObjects.EnemyHeroes.Where( i => i.NetworkId != originalUnit.NetworkId && i.IsValidTarget(input.Range + 200 + input.RealRadius, true, input.RangeCheckFrom)).ForEach( i => { input.Unit = i; var prediction = input.GetPrediction(false, false); if (prediction.Hitchance >= HitChance.High) { result.Add( new PossibleTarget { Position = prediction.UnitPosition.ToVector2(), Unit = i }); } }); return result; }
/// <summary> /// Returns Calculated Prediction based off given data values. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <param name="ft">Add Delay</param> /// <param name="checkCollision">Check Collision</param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> internal static PredictionOutput GetPrediction(PredictionInput input, bool ft, bool checkCollision) { if (!input.Unit.LSIsValidTarget(float.MaxValue, false)) { return(new PredictionOutput()); } if (ft) { // Increase the delay due to the latency and server tick: input.Delay += Game.Ping / 2000f + 0.06f; if (input.AoE) { return(Cluster.GetAoEPrediction(input)); } } // Target too far away. if (Math.Abs(input.Range - float.MaxValue) > float.Epsilon && input.Unit.DistanceSquared(input.RangeCheckFrom) > Math.Pow(input.Range * 1.5, 2)) { return(new PredictionOutput { Input = input }); } PredictionOutput result = null; // Unit is dashing. if (input.Unit.IsDashing()) { result = GetDashingPrediction(input); } else { // Unit is immobile. var remainingImmobileT = UnitIsImmobileUntil(input.Unit); if (remainingImmobileT >= 0d) { result = GetImmobilePrediction(input, remainingImmobileT); } } // Normal prediction if (result == null) { result = GetStandardPrediction(input); } // Check if the unit position is in range if (Math.Abs(input.Range - float.MaxValue) > float.Epsilon) { if (result.Hitchance >= HitChance.High && input.RangeCheckFrom.DistanceSquared(input.Unit.Position) > Math.Pow(input.Range + input.RealRadius * 3 / 4, 2)) { result.Hitchance = HitChance.Medium; } if (input.RangeCheckFrom.DistanceSquared(result.UnitPosition) > Math.Pow(input.Range + (input.Type == SkillshotType.SkillshotCircle ? input.RealRadius : 0), 2)) { result.Hitchance = HitChance.OutOfRange; } if (input.RangeCheckFrom.DistanceSquared(result.CastPosition) > Math.Pow(input.Range, 2) && result.Hitchance != HitChance.OutOfRange) { result.CastPosition = input.RangeCheckFrom + input.Range * (result.UnitPosition - input.RangeCheckFrom).ToVector2() .LSNormalized() .ToVector3(); } } // Check for collision if (checkCollision && input.Collision && Math.Abs(input.Speed - float.MaxValue) > float.Epsilon) { var positions = new List <Vector3> { result.UnitPosition, input.Unit.Position }; result.CollisionObjects = Collision.GetCollision(positions, input); result.CollisionObjects.RemoveAll(x => x.Compare(input.Unit)); if (result.CollisionObjects.Count > 0) { result.Hitchance = HitChance.Collision; } } // Calc hitchance again if (result.Hitchance == HitChance.High) { result.Hitchance = GetHitChance(input); } return(result); }
internal static PredictionOutput GetCirclePrediction(PredictionInput input) { var mainTargetPrediction = input.GetPrediction(false, true); var posibleTargets = new List<PossibleTarget> { new PossibleTarget { Position = mainTargetPrediction.UnitPosition.ToVector2(), Unit = input.Unit } }; if (mainTargetPrediction.Hitchance >= HitChance.Medium) { posibleTargets.AddRange(GetPossibleTargets(input)); } while (posibleTargets.Count > 1) { var mecCircle = ConvexHull.GetMec(posibleTargets.Select(i => i.Position).ToList()); if (mecCircle.Radius <= input.RealRadius - 10 && mecCircle.Center.DistanceSquared(input.RangeCheckFrom) < input.Range * input.Range) { return new PredictionOutput { AoeTargetsHit = posibleTargets.Select(i => (Obj_AI_Hero)i.Unit).ToList(), CastPosition = mecCircle.Center.ToVector3(), UnitPosition = mainTargetPrediction.UnitPosition, Hitchance = mainTargetPrediction.Hitchance, Input = input, AoeHitCount = posibleTargets.Count }; } float maxdist = -1; var maxdistindex = 1; for (var i = 1; i < posibleTargets.Count; i++) { var distance = posibleTargets[i].Position.DistanceSquared(posibleTargets[0].Position); if (distance > maxdist || maxdist.CompareTo(-1) == 0) { maxdistindex = i; maxdist = distance; } } posibleTargets.RemoveAt(maxdistindex); } return mainTargetPrediction; }
/// <summary> /// Returns Calculated Prediction based off given data values. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> public static PredictionOutput GetPrediction(PredictionInput input) { return(GetPrediction(input, true, true)); }
internal static PredictionOutput GetConePrediction(PredictionInput input) { var mainTargetPrediction = input.GetPrediction(false, true); var posibleTargets = new List<PossibleTarget> { new PossibleTarget { Position = mainTargetPrediction.UnitPosition.ToVector2(), Unit = input.Unit } }; if (mainTargetPrediction.Hitchance >= HitChance.Medium) { posibleTargets.AddRange(GetPossibleTargets(input)); } if (posibleTargets.Count > 1) { var candidates = new List<Vector2>(); posibleTargets.ForEach(i => i.Position -= input.From.ToVector2()); for (var i = 0; i < posibleTargets.Count; i++) { for (var j = 0; j < posibleTargets.Count; j++) { if (i != j) { var p = (posibleTargets[i].Position + posibleTargets[j].Position) * 0.5f; if (!candidates.Contains(p)) { candidates.Add(p); } } } } var bestCandidateHits = -1; var bestCandidate = new Vector2(); var positionsList = posibleTargets.Select(i => i.Position).ToList(); candidates.ForEach( i => { var hits = GetHits(i, input.Range, input.Radius, positionsList); if (hits > bestCandidateHits) { bestCandidate = i; bestCandidateHits = hits; } }); if (bestCandidateHits > 1 && input.From.DistanceSquared(bestCandidate) > 50 * 50) { return new PredictionOutput { Hitchance = mainTargetPrediction.Hitchance, AoeHitCount = bestCandidateHits, UnitPosition = mainTargetPrediction.UnitPosition, CastPosition = bestCandidate.ToVector3(), Input = input }; } } return mainTargetPrediction; }
/// <summary> /// Returns Dashing Prediction /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns><see cref="PredictionOutput" /> output</returns> internal static PredictionOutput GetDashingPrediction(PredictionInput input) { var dashData = input.Unit.GetDashInfo(); var result = new PredictionOutput { Input = input, Hitchance = HitChance.Medium }; // Normal dashes. if (!dashData.IsBlink) { var endP = dashData.EndPos.ToVector3(); // Mid air: var dashPred = GetPositionOnPath( input, new List<Vector3> { input.Unit.ServerPosition, endP }.ToVector2(), dashData.Speed); if (dashPred.Hitchance >= HitChance.High && dashPred.UnitPosition.ToVector2() .Distance(input.Unit.Position.ToVector2(), endP.ToVector2(), true) < 200) { dashPred.CastPosition = dashPred.UnitPosition; dashPred.Hitchance = HitChance.Dashing; return dashPred; } // At the end of the dash: if (dashData.Path.LSPathLength() > 200) { var timeToPoint = input.Delay / 2f + (Math.Abs(input.Speed - float.MaxValue) > float.Epsilon ? input.From.Distance(endP) / input.Speed : 0) - 0.25f; if (timeToPoint <= input.Unit.Distance(endP) / dashData.Speed + input.RealRadius / input.Unit.MoveSpeed) { return new PredictionOutput { Input = input, CastPosition = endP, UnitPosition = endP, Hitchance = HitChance.Dashing }; } } result.CastPosition = endP; result.UnitPosition = result.CastPosition; // Figure out where the unit is going. } return result; }
internal static PredictionOutput GetLinePrediction(PredictionInput input) { var mainTargetPrediction = input.GetPrediction(false, true); var posibleTargets = new List<PossibleTarget> { new PossibleTarget { Position = mainTargetPrediction.UnitPosition.ToVector2(), Unit = input.Unit } }; if (mainTargetPrediction.Hitchance >= HitChance.Medium) { posibleTargets.AddRange(GetPossibleTargets(input)); } if (posibleTargets.Count > 1) { var candidates = new List<Vector2>(); posibleTargets.ForEach( i => candidates.AddRange( GetCandidates(input.From.ToVector2(), i.Position, input.Radius, input.Range))); var bestCandidateHits = -1; var bestCandidate = new Vector2(); var bestCandidateHitPoints = new List<Vector2>(); var positionsList = posibleTargets.Select(i => i.Position).ToList(); foreach (var candidate in candidates.Where( i => GetHits( input.From.ToVector2(), i, input.Radius + input.Unit.BoundingRadius / 3 - 10, new List<Vector2> { posibleTargets[0].Position }).Count == 1)) { var hits = GetHits(input.From.ToVector2(), candidate, input.Radius, positionsList); var hitsCount = hits.Count; if (hitsCount >= bestCandidateHits) { bestCandidateHits = hitsCount; bestCandidate = candidate; bestCandidateHitPoints = hits; } } if (bestCandidateHits > 1) { float maxDistance = -1; Vector2 p1 = new Vector2(), p2 = new Vector2(); for (var i = 0; i < bestCandidateHitPoints.Count; i++) { for (var j = 0; j < bestCandidateHitPoints.Count; j++) { var startP = input.From.ToVector2(); var endP = bestCandidate; var proj1 = positionsList[i].ProjectOn(startP, endP); var proj2 = positionsList[j].ProjectOn(startP, endP); var dist = bestCandidateHitPoints[i].DistanceSquared(proj1.LinePoint) + bestCandidateHitPoints[j].DistanceSquared(proj2.LinePoint); if (dist >= maxDistance && (proj1.LinePoint - positionsList[i]).AngleBetween( proj2.LinePoint - positionsList[j]) > 90) { maxDistance = dist; p1 = positionsList[i]; p2 = positionsList[j]; } } } return new PredictionOutput { Hitchance = mainTargetPrediction.Hitchance, AoeHitCount = bestCandidateHits, UnitPosition = mainTargetPrediction.UnitPosition, CastPosition = ((p1 + p2) * 0.5f).ToVector3(), Input = input }; } } return mainTargetPrediction; }
/// <summary> /// Get Position on Unit's Path. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <param name="path">Path in Vector2 List</param> /// <param name="speed">Unit Speed</param> /// <returns><see cref="PredictionOutput" /> output</returns> internal static PredictionOutput GetPositionOnPath(PredictionInput input, List<Vector2> path, float speed = -1) { speed = Math.Abs(speed - -1) < float.Epsilon ? input.Unit.MoveSpeed : speed; if (path.Count <= 1) { return new PredictionOutput { Input = input, UnitPosition = input.Unit.ServerPosition, CastPosition = input.Unit.ServerPosition, Hitchance = HitChance.VeryHigh }; } var pLength = path.LSPathLength(); var dist = input.Delay * speed - input.RealRadius; // Skillshots with only a delay if (pLength >= dist && Math.Abs(input.Speed - float.MaxValue) < float.Epsilon) { var tDistance = dist; for (var i = 0; i < path.Count - 1; i++) { var a = path[i]; var b = path[i + 1]; var d = a.Distance(b); if (d >= tDistance) { var direction = (b - a).LSNormalized(); var cp = a + direction * tDistance; var p = a + direction * (i == path.Count - 2 ? Math.Min(tDistance + input.RealRadius, d) : tDistance + input.RealRadius); return new PredictionOutput { Input = input, CastPosition = cp.ToVector3(), UnitPosition = p.ToVector3(), Hitchance = HitChance.High }; } tDistance -= d; } } // Skillshot with a delay and speed. if (pLength >= dist && Math.Abs(input.Speed - float.MaxValue) > float.Epsilon) { var tDistance = dist; if ((input.Type == SkillshotType.SkillshotLine || input.Type == SkillshotType.SkillshotCone) && input.Unit.LSDistanceSquared(input.From) < 200 * 200) { tDistance += input.RealRadius; } path = path.CutPath(tDistance); var tT = 0f; for (var i = 0; i < path.Count - 1; i++) { var a = path[i]; var b = path[i + 1]; var tB = a.Distance(b) / speed; var direction = (b - a).LSNormalized(); a = a - speed * tT * direction; var sol = a.VectorMovementCollision(b, speed, input.From.ToVector2(), input.Speed, tT); var t = (float)sol[0]; var pos = (Vector2)sol[1]; if (pos.IsValid() && t >= tT && t <= tT + tB) { if (pos.LSDistanceSquared(b) < 20) { break; } var p = pos + input.RealRadius * direction; /*if (input.Type == SkillshotType.SkillshotLine) { var alpha = (input.From.ToVector2() - p).AngleBetween(a - b); if (alpha > 30 && alpha < 180 - 30) { var beta = (float)Math.Asin(input.RealRadius / p.Distance(input.From)); var cp1 = input.From.ToVector2() + (p - input.From.ToVector2()).Rotated(beta); var cp2 = input.From.ToVector2() + (p - input.From.ToVector2()).Rotated(-beta); pos = cp1.LSDistanceSquared(pos) < cp2.LSDistanceSquared(pos) ? cp1 : cp2; } }*/ return new PredictionOutput { Input = input, CastPosition = pos.ToVector3(), UnitPosition = p.ToVector3(), Hitchance = HitChance.High }; } tT += tB; } } var position = path.Last().ToVector3(); return new PredictionOutput { Input = input, CastPosition = position, UnitPosition = position, Hitchance = HitChance.Medium }; }
/// <summary> /// Returns an Area-of-Effect cone prediction from a prediction input source. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> public static PredictionOutput GetConePrediction(PredictionInput input) { var mainTargetPrediction = Movement.GetPrediction(input, false, true); var posibleTargets = new List <PossibleTarget> { new PossibleTarget { Position = mainTargetPrediction.UnitPosition.ToVector2(), Unit = input.Unit } }; if (mainTargetPrediction.Hitchance >= HitChance.High) { // Add the posible targets in range: posibleTargets.AddRange(GetPossibleTargets(input)); } if (posibleTargets.Count > 1) { var candidates = new List <Vector2>(); foreach (var target in posibleTargets) { target.Position = target.Position - input.From.ToVector2(); } for (var i = 0; i < posibleTargets.Count; i++) { for (var j = 0; j < posibleTargets.Count; j++) { if (i == j) { continue; } var p = (posibleTargets[i].Position + posibleTargets[j].Position) * 0.5f; if (!candidates.Contains(p)) { candidates.Add(p); } } } var bestCandidateHits = -1; var bestCandidate = default(Vector2); var positionsList = posibleTargets.Select(t => t.Position).ToList(); foreach (var candidate in candidates) { var hits = GetHits(candidate, input.Range, input.Radius, positionsList); if (hits > bestCandidateHits) { bestCandidate = candidate; bestCandidateHits = hits; } } if (bestCandidateHits > 1 && input.From.DistanceSquared(bestCandidate) > 50 * 50) { return(new PredictionOutput { Hitchance = mainTargetPrediction.Hitchance, AoeHitCount = bestCandidateHits, UnitPosition = mainTargetPrediction.UnitPosition, CastPosition = bestCandidate.ToVector3(), Input = input }); } } return(mainTargetPrediction); }
/// <summary> /// Returns Standard Prediction /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns><see cref="PredictionOutput" /> output</returns> internal static PredictionOutput GetStandardPrediction(PredictionInput input) { var speed = input.Unit.MoveSpeed; if (input.Unit.LSDistanceSquared(input.From) < 200 * 200) { // input.Delay /= 2; speed /= 1.5f; } return GetPositionOnPath(input, input.Unit.GetWaypoints(), speed); }
/// <summary> /// Returns an Area-of-Effect line prediction from a prediction input source. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> public static PredictionOutput GetLinePrediction(PredictionInput input) { var mainTargetPrediction = Movement.GetPrediction(input, false, true); var posibleTargets = new List <PossibleTarget> { new PossibleTarget { Position = mainTargetPrediction.UnitPosition.ToVector2(), Unit = input.Unit } }; if (mainTargetPrediction.Hitchance >= HitChance.High) { // Add the posible targets in range: posibleTargets.AddRange(GetPossibleTargets(input)); } if (posibleTargets.Count > 1) { var candidates = new List <Vector2>(); foreach (var targetCandidates in posibleTargets.Select( target => GetCandidates(input.From.ToVector2(), target.Position, input.Radius, input.Range)) ) { candidates.AddRange(targetCandidates); } var bestCandidateHits = -1; var bestCandidate = default(Vector2); var bestCandidateHitPoints = new List <Vector2>(); var positionsList = posibleTargets.Select(t => t.Position).ToList(); foreach (var candidate in candidates) { if ( GetHits( input.From.ToVector2(), candidate, input.Radius + /*(input.Unit.BoundingRadius / 3) -*/ 10, new List <Vector2> { posibleTargets[0].Position }).Count() == 1) { var hits = GetHits(input.From.ToVector2(), candidate, input.Radius, positionsList).ToList(); var hitsCount = hits.Count; if (hitsCount >= bestCandidateHits) { bestCandidateHits = hitsCount; bestCandidate = candidate; bestCandidateHitPoints = hits.ToList(); } } } if (bestCandidateHits > 1) { float maxDistance = -1; Vector2 p1 = default(Vector2), p2 = default(Vector2); // Center the position for (var i = 0; i < bestCandidateHitPoints.Count; i++) { for (var j = 0; j < bestCandidateHitPoints.Count; j++) { var startP = input.From.ToVector2(); var endP = bestCandidate; var proj1 = positionsList[i].ProjectOn(startP, endP); var proj2 = positionsList[j].ProjectOn(startP, endP); var dist = bestCandidateHitPoints[i].DistanceSquared(proj1.LinePoint) + bestCandidateHitPoints[j].DistanceSquared(proj2.LinePoint); if (dist >= maxDistance && (proj1.LinePoint - positionsList[i]).AngleBetween( proj2.LinePoint - positionsList[j]) > 90) { maxDistance = dist; p1 = positionsList[i]; p2 = positionsList[j]; } } } return(new PredictionOutput { Hitchance = mainTargetPrediction.Hitchance, AoeHitCount = bestCandidateHits, UnitPosition = mainTargetPrediction.UnitPosition, CastPosition = ((p1 + p2) * 0.5f).ToVector3(), Input = input }); } } return(mainTargetPrediction); }
/// <summary> /// Returns Calculated Prediction based off given data values. /// </summary> /// <param name="input"> /// <see cref="PredictionInput" /> input /// </param> /// <returns> /// <see cref="PredictionOutput" /> output /// </returns> public static PredictionOutput GetPrediction(PredictionInput input) { return GetPrediction(input, true, true); }
/// <summary> /// Returns the list of the units that the skill-shot will hit before reaching the set positions. /// </summary> /// <param name="positions"> /// The positions. /// </param> /// <param name="input"> /// The input. /// </param> /// <returns> /// A list of <c>Obj_AI_Base</c>s which the input collides with. /// </returns> public static List <Obj_AI_Base> GetCollision(List <Vector3> positions, PredictionInput input) { var result = new List <Obj_AI_Base>(); foreach (var position in positions) { if (input.CollisionObjects.HasFlag(CollisionableObjects.Minions)) { foreach (var minion in GameObjects.EnemyMinions.Where( minion => minion.IsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom))) { input.Unit = minion; var minionPrediction = Movement.GetPrediction(input, false, false); if (minionPrediction.UnitPosition.ToVector2() .DistanceSquared(input.From.ToVector2(), position.ToVector2(), true) <= Math.Pow(input.Radius + 15 + minion.BoundingRadius, 2)) { result.Add(minion); } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.Heroes)) { foreach (var hero in GameObjects.EnemyHeroes.Where( hero => hero.IsValidTarget( Math.Min(input.Range + input.Radius + 100, 2000), true, input.RangeCheckFrom))) { input.Unit = hero; var prediction = Movement.GetPrediction(input, false, false); if (prediction.UnitPosition.ToVector2() .DistanceSquared(input.From.ToVector2(), position.ToVector2(), true) <= Math.Pow(input.Radius + 50 + hero.BoundingRadius, 2)) { result.Add(hero); } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.Walls)) { var step = position.Distance(input.From) / 20; for (var i = 0; i < 20; i++) { var p = input.From.ToVector2().Extend(position.ToVector2(), step * i); if (NavMesh.GetCollisionFlags(p.X, p.Y).HasFlag(CollisionFlags.Wall)) { result.Add(GameObjects.Player); } } } if (input.CollisionObjects.HasFlag(CollisionableObjects.YasuoWall)) { if (Variables.TickCount - wallCastT > 4000) { continue; } GameObject wall = null; foreach (var gameObject in GameObjects.AllGameObjects.Where( gameObject => gameObject.IsValid && Regex.IsMatch(gameObject.Name, "_w_windwall_enemy_0.\\.troy", RegexOptions.IgnoreCase))) { wall = gameObject; } if (wall == null) { break; } var level = wall.Name.Substring(wall.Name.Length - 6, 1); var wallWidth = 300 + (50 * Convert.ToInt32(level)); var wallDirection = (wall.Position.ToVector2() - yasuoWallCastedPos).Normalized().Perpendicular(); var wallStart = wall.Position.ToVector2() + (wallWidth / 2f * wallDirection); var wallEnd = wallStart - (wallWidth * wallDirection); if (wallStart.Intersection(wallEnd, position.ToVector2(), input.From.ToVector2()).Intersects) { var t = Variables.TickCount + (((wallStart.Intersection(wallEnd, position.ToVector2(), input.From.ToVector2()) .Point.Distance(input.From) / input.Speed) + input.Delay) * 1000); if (t < wallCastT + 4000) { result.Add(GameObjects.Player); } } } } return(result.Distinct().ToList()); }