bool TryPreDodgeProjectile() { const int preTicks = 18; var opp = OpponentWizards .OrderBy(x => x.GetDistanceTo2(ASelf)) .FirstOrDefault(x => Math.Min(x.RemainingActionCooldownTicks, x.RemainingMagicMissileCooldownTicks) <= preTicks && x.GetDistanceTo(ASelf) <= x.CastRange + ASelf.Radius + Game.MagicMissileRadius + 7 && Math.Abs(x.GetAngleTo(ASelf)) <= Game.StaffSector /* /2*/ ); if (opp == null) { return(false); } if (Math.Abs(ASelf.GetAngleTo(opp)) < Math.PI / 2 && ASelf.GetDistanceTo(opp) > 500) { var obstacles = Combats.Where(x => x.Id != Self.Id && x.GetDistanceTo(ASelf) < 300).ToArray(); var selSign = 0; double selPriority = int.MaxValue; var requiredAngle = ASelf.GetDistanceTo(opp) <= opp.CastRange + ASelf.Radius ? Math.PI - 2 * ASelf.MaxTurnAngle - 0.001 : Math.PI / 2; for (var sign = -1; sign <= 1; sign += 2) { var my = new AWizard(ASelf); var priority = 0.0; while (Math.Abs(my.GetAngleTo(opp)) < requiredAngle) { my.Angle += sign * my.MaxTurnAngle; priority += 0.1; } for (var i = 0; i < 15; i++) { if (!my.MoveTo(my + Point.ByAngle(my.Angle), null, w => !CheckIntersectionsAndTress(w, obstacles))) { break; } priority--; } if (priority < selPriority) { selPriority = priority; selSign = sign; } } FinalMove.Turn = selSign * 10; return(true); } return(false); }
MovingInfo FindTreeTarget(AWizard self) { var res = new MovingInfo(null, int.MaxValue, new FinalMove(new Move())); var trees = TreesObserver.Trees.Where(t => self.GetDistanceTo(t) <= Game.StaffRange + t.Radius).ToArray(); var minTicks = int.MaxValue; ATree selTarget = null; foreach (var tree in trees) { var my = new AWizard(self); var ticks = 0; while (Math.Abs(my.GetAngleTo(tree)) > Game.StaffSector / 2) { my.MoveTo(null, tree); ticks++; } if (ticks < minTicks && my.CanStaffAttack(tree)) { minTicks = ticks; selTarget = tree; } } if (selTarget == null) { return(res); } res.Time = minTicks; res.Target = selTarget; res.TargetId = selTarget.Id; if (minTicks == 0) { res.Move.Action = ActionType.Staff; } else { res.Move.MoveTo(null, selTarget); } return(res); }
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 }); }
MovingInfo _findStaffTarget(AWizard self) { var potentialColliders = Combats .Where(x => x.Id != self.Id && self.GetDistanceTo2(x) < Geom.Sqr(Game.StaffRange * 6)) .ToArray(); int minTicks = int.MaxValue; var move = new FinalMove(new Move()); var attacked = self.GetStaffAttacked(potentialColliders).Cast <ACombatUnit>().ToArray(); ACircularUnit selTarget = attacked.FirstOrDefault(x => x.IsOpponent); if (selTarget != null) // если уже можно бить { move.Action = ActionType.Staff; return(new MovingInfo(selTarget, 0, move)); } if (self.MmSkillLevel == 5) { // т.к. стрелять можно без задержки // возможно, нужно сделать исключение, если прокачан посох return(new MovingInfo(null, int.MaxValue, move)); } Point selMoveTo = null; foreach (var opp in OpponentCombats) { var dist = self.GetDistanceTo(opp); if (dist > Game.StaffRange * 5 || !opp.IsAssailable) { continue; } var range = opp.Radius + Game.StaffRange; foreach (var delta in new[] { -range, -range / 2, 0, range / 2, range }) { var angle = Math.Atan2(delta, dist); var moveTo = self + (opp - self).Normalized().RotateClockwise(angle) * self.VisionRange; var nearstCombats = Combats .Where(x => x.GetDistanceTo(self) <= Math.Max(x.VisionRange, self.VisionRange) * 1.2) .Select(Utility.CloneCombat) .ToArray(); var targetsSelector = new TargetsSelector(nearstCombats) { EnableMinionsCache = true }; var nearstOpponents = nearstCombats.Where(x => x.IsOpponent).ToArray(); var my = nearstCombats.FirstOrDefault(x => x.Id == self.Id) as AWizard; var his = nearstCombats.FirstOrDefault(x => x.Id == opp.Id); var allowRush = opp is AFetish || opp is AWizard; var canHitNow = opp.EthalonCanHit(self, checkCooldown: !allowRush); var ticks = 0; var ok = true; var buildingsHit = false; while (ticks < (allowRush ? 65 : 35) && my.GetDistanceTo2(his) > Geom.Sqr(Game.StaffRange + his.Radius)) { foreach (var x in nearstOpponents) // свои как-бы стоят на месте { var tar = targetsSelector.Select(x); buildingsHit = buildingsHit || (x is ABuilding && tar != null && tar.Id == my.Id && x.EthalonCanHit(my)); x.EthalonMove(tar ?? my); } if (!my.MoveTo(moveTo, his, w => !CheckIntersectionsAndTress(w, potentialColliders))) { ok = false; break; } ticks++; } if (ok && !(opp is AOrc)) { while (Math.Abs(my.GetAngleTo(his)) > Game.StaffSector / 2) { my.MoveTo(null, his); foreach (var x in nearstOpponents) { var tar = targetsSelector.Select(x); buildingsHit = buildingsHit || (x is ABuilding && tar != null && tar.Id == my.Id && x.EthalonCanHit(my)); x.EthalonMove(tar ?? my); } ticks++; } } Func <ACombatUnit, bool> check = x => { if ((opp is AWizard) && (opp as AWizard).IsBesieded && !(x is ABuilding)) { return(true); } if (canHitNow && x.Id == opp.Id) // он и так доставал { return(true); } if (!x.EthalonCanHit(my) && (!(x is ABuilding) || !buildingsHit)) { return(true); } if (his.Id == x.Id && my.StaffDamage >= his.Life) { return(true); } var target = targetsSelector.Select(x); if (target != null && target.Id != my.Id) { return(true); } return(false); }; if (opp is AWizard) { ticks -= 5; if ((opp as AWizard).IsBesieded) { ticks -= 10; } } if (ok && ticks < minTicks) { if (my.CanStaffAttack(his)) { if (nearstOpponents.All(check)) { // успею-ли я вернуться обратно while (my.GetDistanceTo(self) > my.MaxForwardSpeed)//TODO:HACK { my.MoveTo(self, null); foreach (var x in nearstOpponents) { var tar = targetsSelector.Select(x); buildingsHit = buildingsHit || (x is ABuilding && tar != null && tar.Id == my.Id && x.EthalonCanHit(my)); if (tar != null) { x.EthalonMove(tar); } else { x.SkipTick(); } } } if (nearstOpponents.All(check)) { selTarget = opp; selMoveTo = moveTo; minTicks = ticks; } } } } } } if (selTarget != null) { bool angleOk = Math.Abs(self.GetAngleTo(selTarget)) <= Game.StaffSector / 2, distOk = self.GetDistanceTo2(selTarget) <= Geom.Sqr(Game.StaffRange + selTarget.Radius); if (!distOk) { move.MoveTo(selMoveTo, selTarget); } else if (!angleOk) { move.MoveTo(null, selTarget); } } return(new MovingInfo(selTarget, Math.Max(0, minTicks), move)); }
MovingInfo FindUltimateTarget(AWizard self) { var ret = new MovingInfo(null, int.MaxValue, new FinalMove(new Move())); const int threshold = 29; var minTicks = int.MaxValue; AWizard selTarget = null; var selAction = ActionType.None; foreach (var action in new[] { ActionType.Haste, ActionType.Shield }) { if (action == ActionType.Haste && self.HasteSkillLevel < 5 || action == ActionType.Shield && self.ShieldSkillLevel < 5 ) { continue; } var teammates = MyWizards .Where(x => x.Id != self.Id && self.GetDistanceTo(x) <= self.CastRange && x.RemainingStatusByAction(action) <= threshold ). ToArray(); foreach (var teammate in teammates) { var ticks = 0; var my = new AWizard(self); while (Math.Abs(my.GetAngleTo(teammate)) > Game.StaffSector / 2) { ticks++; my.MoveTo(null, teammate); } if (ticks < minTicks && my.CanCastUltimate(action, teammate)) { minTicks = ticks; selTarget = teammate; selAction = action; } } if (selTarget == null && teammates.Length == 0 && self.RemainingStatusByAction(action) <= threshold && self.CanUseUltimate(action) ) { minTicks = 100; selTarget = self; selAction = action; } } if (selTarget == null) { return(ret); } ret.Target = selTarget; ret.TargetId = selTarget.Id; ret.Time = minTicks; if (self.CanCastUltimate(selAction, selTarget)) { ret.Move.Action = selAction; ret.Move.StatusTargetId = selTarget.Id; } else { ret.Move.MoveTo(null, selTarget); } return(ret); }
WizardPath _goAround(ACombatUnit target, bool goAgainst) { var my = new AWizard(ASelf); var selLane = Utility.IsBase(target) ? MessagesObserver.GetLane() : RoadsHelper.GetLane(target); var nearestBuilding = OpponentBuildings.ArgMin(b => b.GetDistanceTo2(my)); var buildings = new List <ABuilding>(); if (nearestBuilding.GetDistanceTo(my) > nearestBuilding.VisionRange) { buildings.Add(nearestBuilding); } if (target.IsOpponent && target.Id != nearestBuilding.Id && target is ABuilding) { buildings.Add((ABuilding)target); } var threshold = Self.CastRange - 200; if (ASelf.GetDistanceTo(target) < Self.CastRange || !goAgainst) { threshold = 0; } var path = DijkstraFindPath(ASelf, pos => { // точка ОК, если с неё можно стрелять var dist2 = pos.GetDistanceTo2(target); if (dist2 < Geom.Sqr(Self.CastRange) && dist2 > Geom.Sqr(threshold)) { var distToLine = RoadsHelper.Roads.Where(seg => seg.LaneType == selLane).Min(seg => seg.GetDistanceTo(pos)); if (distToLine < 200 && (!goAgainst || BuildingsObserver.MyBase.GetDistanceTo2(pos) < BuildingsObserver.MyBase.GetDistanceTo2(target)) && TreesObserver.Trees .Where(x => x.GetDistanceTo2(pos) < Geom.Sqr(Self.CastRange)) .All(x => !Geom.SegmentCircleIntersects(pos, target, x, x.Radius + Game.MagicMissileRadius)) ) { return(DijkstraStopStatus.TakeAndStop); } } return(DijkstraStopStatus.Continue); }, MoveCostFunc(buildings, selLane)).FirstOrDefault(); if (path == null && my.GetDistanceTo(target) - my.Radius - target.Radius <= 1) { path = new WizardPath { my } } ; // из-за эпсилон, если стою близко у цели, то он как бы с ней пересекается, но это не так if (path == null || path.Count == 0) { return(null); } if (path.Count == 1) { FinalMove.Turn = my.GetAngleTo(target); return(null); } var obstacles = Combats.Where(x => x.Id != Self.Id).Cast <ACircularUnit>() .Where(x => my.GetDistanceTo2(x) < Geom.Sqr(my.VisionRange)) //??? .ToArray(); path.Simplify(obstacles, MagicConst.SimplifyMaxLength); var nextPoint = path[1]; var nextNextPoint = path.Count > 2 ? path[2] : target; FinalMove.MoveTo(nextPoint, my.GetDistanceTo(nextNextPoint) < Self.VisionRange * 1.2 ? nextNextPoint : nextPoint); var nextTree = path.GetNearestTree(); CutTreesInPath(nextTree, FinalMove); #if DEBUG Visualizer.Visualizer.SegmentsDrawQueue.Add(new object[] { path, Pens.Blue, 3 }); #endif return(path); } void CutTreesInPath(ATree nextTree, FinalMove move) { if (nextTree == null) { return; } var my = new AWizard(ASelf); var angleTo = my.GetAngleTo(nextTree); if (my.GetDistanceTo(nextTree) < my.VisionRange && Math.Abs(angleTo) > Game.StaffSector / 2) { move.MoveTo(null, nextTree); } if (my.RemainingActionCooldownTicks == 0 && Math.Abs(angleTo) <= Game.StaffSector / 2) { if (my.GetDistanceTo(nextTree) <= Game.StaffRange + nextTree.Radius && my.RemainingStaffCooldownTicks == 0) { move.Action = ActionType.Staff; } else if (my.GetDistanceTo(nextTree) <= my.CastRange + nextTree.Radius && my.RemainingMagicMissileCooldownTicks == 0) { move.Action = ActionType.MagicMissile; move.CastAngle = angleTo; move.MinCastDistance = Math.Min(my.CastRange - 1, my.GetDistanceTo(nextTree)); } } } MovingInfo GoToBonus() { TimerStart(); var ret = _goToBonus(); TimerEndLog("GoToBonus", 1); return(ret); } bool _skipBonusCond(ABonus bonus) { var oppFirst = BuildingsObserver.Buildings.FirstOrDefault(x => x.IsOpponent && x.Lane == MessagesObserver.GetLane() && x.Order == 0); if (oppFirst == null || ASelf.GetDistanceTo(oppFirst) <= oppFirst.CastRange) { return(true); } var myFirst = BuildingsObserver.Buildings.FirstOrDefault(x => x.IsTeammate && x.Lane == MessagesObserver.GetLane() && x.Order == 0); if (myFirst == null || OpponentWizards.Any(x => x.GetDistanceTo(myFirst) <= myFirst.Radius)) { return(true); } // TODO return(false); } ABonus SelectBonus(AWizard self) { var bonus = BonusesObserver.Bonuses.ArgMin(b => b.GetDistanceTo2(self)); if (bonus.RemainingAppearanceTicks > MagicConst.GoToBonusMaxTicks + MagicConst.BonusTimeReserve) { return(null); } if (self.GetDistanceTo(BuildingsObserver.OpponentBase) < BuildingsObserver.OpponentBase.CastRange * 1.4) { return(null); } if (Game.IsSkillsEnabled && _skipBonusCond(bonus)) { return(null); } return(bonus); } MovingInfo _goToBonus() { var bonus = SelectBonus(ASelf); var selMovingInfo = new MovingInfo(null, int.MaxValue, new FinalMove(new Move())); if (bonus == null) { return(selMovingInfo); } if (Const.IsFinal) { var teammates = MyWizards .Where(x => x.Id != ASelf.Id) .Where(x => { var b = SelectBonus(x); return(b != null && b.Id == bonus.Id); }) .ToArray(); if (teammates.Any(x => ASelf.GetDistanceTo(bonus) > x.GetDistanceTo(bonus))) { return(selMovingInfo); } } var my = new AWizard(ASelf); var nearestBuilding = OpponentBuildings.ArgMin(b => b.GetDistanceTo2(my)); var path = DijkstraFindPath(ASelf, pos => { // точка ОК, если бонус совсем близко if (pos.GetDistanceTo2(bonus) < Geom.Sqr(bonus.Radius + Self.Radius + 35)) { return(DijkstraStopStatus.TakeAndStop); } return(DijkstraStopStatus.Continue); }, MoveCostFunc(new [] { nearestBuilding }, MessagesObserver.GetLane())).FirstOrDefault(); if (path == null) { GoDirect(bonus, selMovingInfo.Move); selMovingInfo.Target = bonus; return(selMovingInfo); } var obstacles = Combats.Where(x => x.Id != Self.Id).Cast <ACircularUnit>() .Where(x => my.GetDistanceTo2(x) < Geom.Sqr(my.VisionRange)) .ToArray(); path.Add(bonus); path.Simplify(obstacles, MagicConst.SimplifyMaxLength); var time = (int)(path.GetLength() / my.MaxForwardSpeed); if (time < MagicConst.GoToBonusMaxTicks) { selMovingInfo.Time = time; var nextPoint = path[1]; var nextNextPoint = path.Count > 2 ? path[2] : nextPoint; selMovingInfo.Move = new FinalMove(new Move()); selMovingInfo.Move.MoveTo(nextPoint, my.GetDistanceTo(nextNextPoint) < my.Radius + 20 ? nextNextPoint : nextPoint); selMovingInfo.Target = nextPoint; var nextTree = path.GetNearestTree(); CutTreesInPath(nextTree, selMovingInfo.Move); } if (selMovingInfo.Time <= bonus.RemainingAppearanceTicks - MagicConst.BonusTimeReserve) { selMovingInfo.Target = null; } #if DEBUG if (selMovingInfo.Target != null) { Visualizer.Visualizer.SegmentsDrawQueue.Add(new object[] { path, Pens.Red, 3 }); } #endif return(selMovingInfo); }