public bool _ethalonCanCastMagicMissile(ACircularUnit opp, bool checkCooldown) { var distTo = GetDistanceTo(opp); if (distTo > CastRange + opp.Radius + MyStrategy.Game.MagicMissileRadius) { return(false); } var angleTo = GetAngleTo(opp); var deltaAngle = Math.Atan2(opp.Radius, distTo); var angles = new[] { angleTo, angleTo + deltaAngle, angleTo - deltaAngle }; foreach (var angle in angles) { if (Math.Abs(angle) > MyStrategy.Game.StaffSector / 2) { continue; } var proj = new AProjectile(this, angle, ProjectileType.MagicMissile); if (CheckProjectileCantDodge(proj, opp as ACombatUnit)) { return(true); } } return(false); }
double _getProjectilesDamage(List <AWizard> myStates) { var totalDamage = 0.0; foreach (var arr in ProjectilesPaths1) { if (arr[0].GetDistanceTo2(ASelf) > Geom.Sqr(1000)) { continue; } var fireballMinDist = 1000.0; AProjectile fireballMinDistState = null; AWizard fireballMinDistMyState = null; for (var ticksPassed = 0; ticksPassed < ProjectilesCheckTicks; ticksPassed++) { var cur = myStates[Math.Min(ticksPassed, myStates.Count - 1)]; for (var mt = 0; mt <= arr[0].MicroTicks; mt++) { var microTick = ticksPassed * arr[0].MicroTicks + mt; var proj = arr[microTick]; if (!proj.Exists) { ticksPassed = ProjectilesCheckTicks; // выход из внешнего цикла break; } if (proj.Type == ProjectileType.Fireball) { var dist = cur.GetDistanceTo(proj); if (dist < fireballMinDist && (!proj.IsFriendly || proj.RemainingDistance < Const.Eps)) // для своих фаерболов точно известно когда взорвется { fireballMinDist = dist; fireballMinDistState = proj; fireballMinDistMyState = cur; } } else { if (proj.IntersectsWith(cur) || microTick > 0 && Geom.SegmentCircleIntersects(arr[microTick - 1], proj, cur, cur.Radius + proj.Radius)) { totalDamage += proj.Damage; ticksPassed = ProjectilesCheckTicks; // выход из внешнего цикла break; } } } } if (fireballMinDistState != null) { totalDamage += AProjectile.GetFireballDamage(fireballMinDistState, fireballMinDistMyState); } } return(totalDamage); }
public bool CheckProjectileCantDodge(AProjectile proj, ACombatUnit opp) { return((opp is AWizard ? Utility.Range(-Math.PI / 2, Math.PI / 2, 6) : new[] { 0.0 }).All(changeAngle => { var path = proj.Emulate(opp, changeAngle); return path.Any(x => x.State == AProjectile.ProjectilePathState.Shot && x.Target.Faction != Faction); })); }
public AProjectile(AProjectile unit) : base(unit) { SpeedX = unit.SpeedX; SpeedY = unit.SpeedY; Speed = Geom.Hypot(SpeedX, SpeedY); Type = unit.Type; OwnerUnitId = unit.OwnerUnitId; RemainingDistance = unit.RemainingDistance; Damage = unit.Damage; MinDamage = unit.MinDamage; }
public static double GetFireballDamage(AProjectile proj, ACombatUnit unit) // без учета сколько жизней осталось { var dist = proj.GetDistanceTo(unit) - unit.Radius; double damage = 0; if (dist <= MyStrategy.Game.FireballExplosionMaxDamageRange) { damage += proj.Damage; } else if (dist <= MyStrategy.Game.FireballExplosionMinDamageRange) { var ratio = 1 - (dist - MyStrategy.Game.FireballExplosionMaxDamageRange) / (MyStrategy.Game.FireballExplosionMinDamageRange - MyStrategy.Game.FireballExplosionMaxDamageRange); damage += ratio * (proj.Damage - proj.MinDamage) + proj.MinDamage; } return(damage); }
public void InitializeProjectiles() { var projectiles = ProjectilesObserver.Projectiles; ProjectilesPaths1 = new AProjectile[projectiles.Length][]; for (var i = 0; i < projectiles.Length; i++) { var proj = projectiles[i]; ProjectilesPaths1[i] = new AProjectile[ProjectilesCheckTicks * proj.MicroTicks + 1]; ProjectilesPaths1[i][0] = new AProjectile(proj); for (var j = 1; j < ProjectilesPaths1[i].Length; j++) { ProjectilesPaths1[i][j] = new AProjectile(ProjectilesPaths1[i][j - 1]); ProjectilesPaths1[i][j].MicroMove(); } } }
bool TryCutTrees(bool cutNearest, FinalMove move) { var self = new AWizard(ASelf); var nearestTrees = TreesObserver.Trees.Where( t => self.GetDistanceTo(t) < self.CastRange + t.Radius + Game.MagicMissileRadius ).ToArray(); if (nearestTrees.Length == 0) { return(false); } if (self.RemainingActionCooldownTicks == 0) { if (self.GetStaffAttacked(nearestTrees).Length > 0) { move.Action = ActionType.Staff; return(true); } if (self.RemainingMagicMissileCooldownTicks == 0) { var proj = new AProjectile(self, 0, ProjectileType.MagicMissile); var path = EmulateProjectileWithNearest(proj); if (path.Count == 0 || path[path.Count - 1].EndDistance < self.CastRange - Const.Eps) { move.MinCastDistance = path[path.Count - 1].EndDistance; move.Action = ActionType.MagicMissile; return(true); } } } if (cutNearest) { var nearest = nearestTrees.OrderBy(t => self.GetDistanceTo2(t)).FirstOrDefault(); move.MoveTo(null, nearest); } return(false); }
List <AProjectile.ProjectilePathSegment> EmulateProjectileWithNearest(AProjectile projectile) { return(projectile.Emulate(Combats.Where(x => x.GetDistanceTo(ASelf) - x.Radius < 900).ToArray(), 0.0)); }
MovingInfo _findCastTarget2(AWizard self, Point moveTo, ProjectileType projectileType) { var move = new FinalMove(new Move()); if (projectileType == ProjectileType.MagicMissile) { AUnit mmSelTarget = null; Point mmSelFirstMoveTo = null; var mmMinTicks = int.MaxValue; double mmMinPriority = int.MaxValue; foreach (var opp in OpponentCombats) { if (self.GetDistanceTo2(opp) > Geom.Sqr(self.CastRange + opp.Radius + 40) || !opp.IsAssailable) { continue; } var nearest = Combats .Where(x => self.GetDistanceTo2(x) < Geom.Sqr(Math.Max(x.VisionRange, self.VisionRange) * 1.3)) .Select(Utility.CloneCombat) .ToArray(); var targetsSelector = new TargetsSelector(nearest) { EnableMinionsCache = true }; var nearstOpponents = nearest .Where(x => x.IsOpponent) .ToArray(); var canHitNow = opp.EthalonCanHit(self, checkCooldown: !(opp is AWizard)); var ticks = 0; var my = nearest.FirstOrDefault(x => x.Id == self.Id) as AWizard; var his = nearest.FirstOrDefault(x => x.Id == opp.Id); if (my == null || his == null) { continue; } Point firstMoveTo = null; var buildingsHit = false; while (!my.EthalonCanCastMagicMissile(his, checkCooldown: false)) { if (ticks > 40) { break; } var m = moveTo; var stopIfCannotMove = true; if (m == null && my.EthalonCanCastMagicMissile(his, checkCooldown: false, checkAngle: false)) { stopIfCannotMove = false; m = my + (my - his); var tmp = new AWizard(my); tmp.MoveTo(m, his, w => !CheckIntersectionsAndTress(w, nearest)); if (EstimateDanger(my, false) <= EstimateDanger(tmp, false)) { m = null; } } if (m == null) { m = his; } if (ticks == 0) { firstMoveTo = m; } if (!my.MoveTo(m, his, w => !CheckIntersectionsAndTress(w, nearest)) && Utility.PointsEqual(m, his) && stopIfCannotMove) { break; } foreach (var x in nearest) { if (x.Id == my.Id) { continue; } var tar = targetsSelector.Select(x); buildingsHit = buildingsHit || (x.IsOpponent && x is ABuilding && tar != null && tar.Id == my.Id && x.EthalonCanHit(my)); if (x.IsOpponent) { x.EthalonMove(tar ?? my); } else if (tar != null) { x.EthalonMove(tar); } else { x.SkipTick(); } } ticks++; } if (his is AWizard && (his as AWizard).IsBesieded) { ticks -= 15; // чтобы дать больше приоритета визарду } var priority = GetCombatPriority(self, his); if (ticks < mmMinTicks || ticks == mmMinTicks && priority < mmMinPriority) { if (my.EthalonCanCastMagicMissile(his)) { if (nearstOpponents.All(x => { if (canHitNow && x.Id == opp.Id) // он и так доставал { return(true); } if (!x.EthalonCanHit(my) && (!(x is ABuilding) || !buildingsHit)) { return(true); } if (his.Id == x.Id && CanRush(my, x)) { return(true); } var target = targetsSelector.Select(x); if (target != null && target.Id != my.Id) { return(true); } return(false); }) ) { mmMinTicks = ticks; mmMinPriority = priority; mmSelTarget = opp; mmSelFirstMoveTo = firstMoveTo; } } } } if (mmSelTarget != null) { mmMinTicks = Math.Max(0, mmMinTicks); move.MoveTo(moveTo ?? mmSelFirstMoveTo, mmSelTarget); return(new MovingInfo(mmSelTarget, mmMinTicks, move) { TargetId = mmSelTarget.Id }); } } const int walkLimit = 9; if (projectileType == ProjectileType.Fireball && self.FireballSkillLevel == 5 && Math.Max(self.RemainingActionCooldownTicks, self.RemainingFireballCooldownTicks) <= walkLimit) { var fbMaxDamage = 0.0; Point fbSelTarget = null; var fbMinTicks = int.MaxValue; foreach (var ang in Utility.Range(-Game.StaffSector, Game.StaffSector, 10)) { var nearest = Combats .Where(x => self.GetDistanceTo2(x) < Geom.Sqr(Math.Max(x.VisionRange, self.VisionRange) * 1.3)) .Select(Utility.CloneCombat) .ToArray(); var targetsSelector = new TargetsSelector(nearest) { EnableMinionsCache = true }; var ticks = 0; var my = nearest.FirstOrDefault(x => x.Id == self.Id) as AWizard; var dir = my + Point.ByAngle(my.Angle + ang) * 1000; while (ticks <= walkLimit) { if (my.CanUseFireball()) { var proj = new AProjectile(my, 0, ProjectileType.Fireball); var path = proj.Emulate(nearest, 0.0); var damage = path.Where(x => _isFireballGoodSeg(my, x)) .Select(x => x.OpponentDamage) .DefaultIfEmpty(0) .Max(); if (damage > fbMaxDamage) { fbMaxDamage = damage; fbSelTarget = dir; fbMinTicks = ticks; } } foreach (var x in nearest) { if (x.Id == my.Id) { continue; } if (x is AMinion) { x.EthalonMove(targetsSelector.Select(x)); } else { x.SkipTick(); } } if (!my.MoveTo(dir, dir, w => !CheckIntersectionsAndTress(w, nearest))) { break; } if (nearest.Any(x => x.IsOpponent && x is ABuilding && x.EthalonCanHit(my) && targetsSelector.Select(x) == my)) { break; } ticks++; } } if (fbSelTarget != null) { move.MoveTo(fbSelTarget, fbSelTarget); return(new MovingInfo(fbSelTarget, fbMinTicks, move) { Damage = fbMaxDamage }); } } return(new MovingInfo(null, int.MaxValue, move)); }
MovingInfo _findCastTarget(AWizard self, ProjectileType projectileType) { var actionType = Utility.GetActionByProjectileType(projectileType); var move = new FinalMove(new Move()); if (self.RemainingActionCooldownTicks > 0 || self.RemainingCooldownTicksByAction[(int)actionType] > 0 || self.Mana < Const.ProjectileInfo[(int)projectileType].ManaCost || !self.IsActionAvailable(actionType) ) { return(new MovingInfo(null, int.MaxValue, move)); } var angles = new List <double>(); foreach (var x in OpponentCombats) { var distTo = self.GetDistanceTo(x); if (distTo > self.CastRange + x.Radius + Const.ProjectileInfo[(int)projectileType].DamageRadius + 3) { continue; } var angleTo = self.GetAngleTo(x); if (Math.Abs(angleTo) > Math.PI / 3) { continue; } var deltaAngle = Math.Atan2(x.Radius, distTo); angles.AddRange(new[] { angleTo, angleTo + deltaAngle, angleTo - deltaAngle }.Where(a => Math.Abs(a) <= Game.StaffSector / 2)); } if (angles.Count > 0) { angles.AddRange(Utility.Range(-Game.StaffSector / 2, Game.StaffSector / 2, 16)); } ACombatUnit selTarget = null; double selMinDist = 0, selMaxDist = self.CastRange + 20, selCastAngle = 0, selMaxDamage = 0; if (projectileType == ProjectileType.Fireball) { var maxDamage = 0.0; var maxBurned = 0; foreach (var angle in angles) { var proj = new AProjectile(new AWizard(self), angle, projectileType); var path = EmulateProjectileWithNearest(proj); for (var i = 0; i < path.Count; i++) { var seg = path[i]; if (_isFireballGoodSeg(self, seg)) { if (seg.OpponentBurned > maxBurned || seg.OpponentBurned == maxBurned && seg.OpponentDamage > maxDamage //|| seg.OpponentBurned == maxBurned && Utility.Equals(seg.OpponentDamage, maxDamage) //TODO: combare by angle and priority ) { maxBurned = seg.OpponentBurned; maxDamage = seg.OpponentDamage; selCastAngle = angle; selMinDist = selMaxDist = seg.StartDistance; selTarget = seg.Target; selMaxDamage = seg.OpponentDamage; } } } } } else { double selPriority = int.MaxValue, selAngleTo = 0; foreach (var angle in angles) { var proj = new AProjectile(new AWizard(self), angle, projectileType); var path = EmulateProjectileWithNearest(proj); for (var i = 0; i < path.Count; i++) { if (path[i].State == AProjectile.ProjectilePathState.Free) { continue; } // TODO: если можно убить нескольких, убивать того, у кого больше жизней var combat = path[i].Target; if (!combat.IsAssailable) { continue; } var myAngle = self.Angle + angle; var hisAngle = self.Angle + self.GetAngleTo(combat); var angleTo = Geom.GetAngleBetween(myAngle, hisAngle); var priority = GetCombatPriority(self, combat); if (combat.IsOpponent && (priority < selPriority || Utility.Equals(priority, selPriority) && angleTo < selAngleTo) && self.CheckProjectileCantDodge(proj, Combats.FirstOrDefault(x => x.Id == combat.Id)) ) { selTarget = combat; selCastAngle = angle; selAngleTo = angleTo; selMinDist = i == 0 || path[i - 1].State == AProjectile.ProjectilePathState.Free && path[i - 1].Length < 40 ? path[i].StartDistance - 1 : path[i].StartDistance - 20; selMaxDist = i >= path.Count - 2 ? (self.CastRange + 500) : (path[i + 1].EndDistance + path[i].EndDistance) / 2; selPriority = priority; selMaxDamage = path[i].OpponentDamage; } } } } if (selTarget == null) { return(new MovingInfo(null, int.MaxValue, move)); } move.Action = actionType; move.MinCastDistance = selMinDist; move.MaxCastDistance = selMaxDist; move.CastAngle = selCastAngle; #if DEBUG _lastProjectileTick = World.TickIndex; _lastProjectilePoints = new[] { self + Point.ByAngle(self.Angle + selCastAngle) * selMinDist, self + Point.ByAngle(self.Angle + selCastAngle) * Math.Min(Self.CastRange, selMaxDist), }; #endif return(new MovingInfo(selTarget, 0, move) { Damage = selMaxDamage, TargetId = selTarget.Id }); }
private List <ProjectilePathSegment> _emulate(ACombatUnit[] nearestUnits, double wizardsChangeAngle, TargetsSelector minionsTargetsSelector) { if (Type != ProjectileType.MagicMissile && Type != ProjectileType.FrostBolt && Type != ProjectileType.Fireball) { throw new Exception("Unsupported projectile type " + Type); } // NOTE: nearestUnits уже склонированы // NOTE: nearestUnits и они же в minionsTargetsSelector - одни и те же instance var list = new List <ProjectilePathSegment>(); var projectile = new AProjectile(this); var owner = nearestUnits.FirstOrDefault(x => x.Id == OwnerUnitId); var nearestCandidates = nearestUnits.Where(x => x.Id != OwnerUnitId).ToArray(); while (projectile.Exists) { projectile.Move(proj => { if (Type == ProjectileType.Fireball) { double selfDamage = 0; double oppDamage = 0; var selfBurned = 0; var oppBurned = 0; var selfDeads = 0; var oppDeads = 0; ACombatUnit importantTarget = null; foreach (var unit in nearestCandidates) { var damage = GetFireballDamage(proj, unit); if (damage > 0) { var deads = 0; if (damage >= unit.Life - Const.Eps) { // killed deads++; damage = unit.Life; } if (unit.Faction == proj.Faction) { if (unit is AWizard) { selfDamage += damage; selfBurned++; selfDeads += deads; } } else if (Utility.HasConflicts(proj, unit)) { oppDamage += damage; oppBurned++; oppDeads += deads; if (importantTarget == null || _targetImportance(unit) > _targetImportance(importantTarget)) { importantTarget = unit; } } } } if (owner != null) { var damage = GetFireballDamage(proj, owner); if (damage > 0) { selfBurned++; if (damage >= owner.Life - Const.Eps) { // killed selfDamage += owner.Life; selfDeads++; } else { selfDamage += damage; } } } list.Add(new ProjectilePathSegment { StartDistance = list.Count == 0 ? 0 : list.Last().EndDistance, EndDistance = list.Count == 0 ? 0 : list.Last().EndDistance, OpponentDamage = oppDamage, SelfDamage = selfDamage, OpponentDeadsCount = oppDeads, SelfDeadsCount = selfDeads, State = (selfDamage + oppDamage < Const.Eps) ? ProjectilePathState.Free : ProjectilePathState.Fireball, OpponentBurned = oppBurned, SelfBurned = selfBurned, Target = importantTarget, }); } else { var inter = proj.GetFirstIntersection(nearestCandidates); if (inter != null) { if (list.Count == 0 || list.Last().State != ProjectilePathState.Shot) { var opp = inter as ACombatUnit; list.Add(new ProjectilePathSegment { StartDistance = list.Count == 0 ? 0 : list.Last().EndDistance, EndDistance = list.Count == 0 ? 0 : list.Last().EndDistance, State = ProjectilePathState.Shot, Target = Utility.CloneCombat(opp), SelfDamage = proj.Faction == inter.Faction ? Math.Min(opp.Life, Damage) : 0, OpponentDamage = Utility.HasConflicts(proj, opp) ? Math.Min(opp.Life, Damage) : 0, }); } } else { if (list.Count == 0 || list.Last().State != ProjectilePathState.Free) { list.Add(new ProjectilePathSegment { StartDistance = list.Count == 0 ? 0 : list.Last().EndDistance, EndDistance = list.Count == 0 ? 0 : list.Last().EndDistance, State = ProjectilePathState.Free, }); } } } list.Last().EndDistance += proj.Speed / proj.MicroTicks; return(true); }); foreach (var unit in nearestCandidates) { if (unit is AWizard) { var wizard = unit as AWizard; var dir = (wizard - this).RotateClockwise(wizardsChangeAngle) + wizard; // вдоль снаряда wizard.MoveTo(dir, null, w => { var tree = TreesObserver.GetNearestTree(w); return((tree == null || !w.IntersectsWith(tree)) && wizard.GetFirstIntersection(nearestCandidates) == null); }); } else if (unit is AMinion) { var target = minionsTargetsSelector.Select(unit); unit.EthalonMove(target); } } } return(list); }