void _rushTo(AWizard self, AWizard opp) { // TODO: check angle if (self.CanUseFrostBolt() && self.GetDistanceTo(opp) <= Game.WizardCastRange + opp.Radius) { opp.ApplyMagicalDamage(self.FrostBoltDamage); opp.RemainingFrozen = Game.FrozenDurationTicks; self.RemainingFrostBoltCooldownTicks = Game.FrostBoltCooldownTicks; self.RemainingActionCooldownTicks = Game.WizardActionCooldownTicks; } if (self.CanUseStaff() && self.GetDistanceTo(opp) <= Game.StaffRange + opp.Radius) { opp.ApplyDamage(self.StaffDamage); self.RemainingStaffCooldownTicks = Game.StaffCooldownTicks; self.RemainingActionCooldownTicks = Game.WizardActionCooldownTicks; } if (self.CanUseMagicMissile() && self.GetDistanceTo(opp) <= Game.WizardCastRange + opp.Radius) { opp.ApplyMagicalDamage(self.MagicMissileDamage); self.RemainingMagicMissileCooldownTicks = self.MmSkillLevel == 5 ? 0 : Game.MagicMissileCooldownTicks; self.RemainingActionCooldownTicks = Game.WizardActionCooldownTicks; } if (self.GetDistanceTo(opp) > Game.StaffRange + opp.Radius) { self.MoveTo(opp, opp); } self.SkipTick(); }
bool CanRush(AWizard self, ACombatUnit opp) { var wizard = opp as AWizard; var minion = opp as AMinion; if (wizard != null) { if (wizard.IsBesieded) { return(true); } if (wizard.Life <= self.MagicMissileDamage) { return(true); } if (self.Life <= wizard.MagicMissileDamage) { return(false); } if (self.Life >= wizard.Life + 3 * self.MagicMissileDamage) { return(true); } } else if (minion != null) { if (minion.Life <= self.MagicMissileDamage) { return(true); } } return(false); }
List <Tuple <Point, double> > CalculateDangerMap() { double range = Self.VisionRange * 1.1, left = Self.X - range, right = Self.X + range, top = Self.Y - range, bottom = Self.Y + range; int grid = 60; var res = new List <Tuple <Point, double> >(); var my = new AWizard(ASelf); for (int i = 0; i <= grid; i++) { for (int j = 0; j <= grid; j++) { var pt = new Point((right - left) / grid * i + left, (bottom - top) / grid * j + top); if (pt.X < 0 || pt.Y < 0 || pt.X > Const.MapSize || pt.Y > Const.MapSize) { continue; } my.X = pt.X; my.Y = pt.Y; res.Add(new Tuple <Point, double>(pt, EstimateDanger(my))); } } return(res); }
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); }
Target FindTarget(AWizard self, Point moveTo = null) { TimerStart(); var ret = _findTarget(self, moveTo); TimerEndLog("FindTarget", 1); return(ret); }
double EmulateRush(AWizard self, AWizard opp) { TimerStart(); var ret = _emulateRush(self, opp); TimerEndLog("EmulateRush", 1); return(ret); }
MovingInfo FindStaffTarget(AWizard self) { TimerStart(); var ret = _findStaffTarget(self); TimerEndLog("FindStaffTarget", 1); return(ret); }
MovingInfo FindCastTarget(AWizard self, ProjectileType projectileType) { TimerStart(); var ret = _findCastTarget(self, projectileType); TimerEndLog("FindCastTarget", 1); return(ret); }
MovingInfo FindCastTarget2(AWizard self, Point moveTo, ProjectileType projectileType) { TimerStart(); var ret = _findCastTarget2(self, moveTo, projectileType); TimerEndLog("FindCastTarget2", 1); return(ret); }
bool CheckIntersectionsAndTress(AWizard self, IEnumerable <ACircularUnit> units) { if (self.GetFirstIntersection(units) != null) { return(true); } var nearestTree = TreesObserver.GetNearestTree(self); return(nearestTree != null && self.IntersectsWith(nearestTree)); }
public bool CanCastUltimate(ActionType ultimate, AWizard teammate) { if (!CanUseUltimate(ultimate)) { return(false); } return(Id == teammate.Id || GetDistanceTo2(teammate) <= Geom.Sqr(CastRange) && Math.Abs(GetAngleTo(teammate)) <= MyStrategy.Game.StaffSector / 2); }
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); }
private static SkillType?_getNearestSkill(AWizard self, SkillType skill) { var skillGroup = Utility.GetSkillGroup(skill); var skillOrder = Utility.GetSkillOrder(skill); if (self.SkillsLearnedArr[skillGroup] > skillOrder) { return(null); } return(Utility.GetSkill(skillGroup, self.SkillsLearnedArr[skillGroup])); }
public AWizard(AWizard unit) : base(unit) { IsMaster = unit.IsMaster; RemainingCooldownTicksByAction = unit.RemainingCooldownTicksByAction.ToArray(); Level = unit.Level; Xp = unit.Xp; Mana = unit.Mana; SkillsLearnedArr = unit.SkillsLearnedArr.ToArray(); SkillsFactorsArr = unit.SkillsFactorsArr.ToArray(); AurasFactorsArr = unit.AurasFactorsArr.ToArray(); MaxMana = unit.MaxMana; IsBesieded = unit.IsBesieded; }
public static WizardPath[] DijkstraFindPath(AWizard start, DijkstraPointStopFunc stopFunc, DijkstraPointCostFunc costFunc) { start = new AWizard(start); _startState = start; _obstacles = Combats .Where(x => !x.IsOpponent && x.Id != start.Id && x.GetDistanceTo2(start) < Geom.Sqr(start.VisionRange)) // (нейтральные включительно) .ToArray(); var startCell = FindNearestCell(start); if (startCell == null) { return new WizardPath[] {} } ; var endCells = new List <Cell>(); DijkstraStart(startCell, cell => { var point = _points[cell.I, cell.J]; var status = stopFunc(point); if (status == DijkstraStopStatus.Take || status == DijkstraStopStatus.TakeAndStop) { endCells.Add(cell); } if (status == DijkstraStopStatus.Stop || status == DijkstraStopStatus.TakeAndStop) { return(true); } return(false); }, costFunc); return(endCells.Select(endCell => { var cellsPath = DijkstraGeneratePath(startCell, endCell); var res = new WizardPath { start }; res.AddRange(cellsPath.Select(cell => _points[cell.I, cell.J])); return res; }).ToArray()); } }
public void MoveTo(Point to, Point turnTo) { var self = MyStrategy.ASelf; if (turnTo != null) { _move.Turn = Utility.EnsureInterval(MyStrategy.Self.GetAngleTo(turnTo.X, turnTo.Y), self.MaxTurnAngle); } if (to != null && !Utility.PointsEqual(self, to)) { var angle = self.GetAngleTo(to); var d = AWizard._getHalfEllipseDxDy(self.MaxStrafeSpeed, self.MaxForwardSpeed, self.MaxBackwardSpeed, angle); _move.Speed = d.Y; _move.StrafeSpeed = d.X; } }
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); }
private bool _isFireballGoodSeg(AWizard self, AProjectile.ProjectilePathSegment seg) { if (seg.State == AProjectile.ProjectilePathState.Free) { return(false); } if (seg.SelfDamage > 0) // TODO: можно и пожертвовать { return(false); } return(seg.OpponentBurned > 2 || seg.OpponentBurned > 0 && seg.Target is AWizard || seg.OpponentBurned > 0 && seg.Target is ABuilding && self.Mana >= 2 * Game.FireballManacost && seg.OpponentDamage > Game.FireballExplosionMaxDamage - 1 //TODO: костыль ); }
bool PostDodgeProjectile() { var self = new AWizard(ASelf); var curDamage = _getProjectilesDamage(new List <AWizard> { self }); self.Move(FinalMove.Speed, FinalMove.StrafeSpeed); var newDamage = _getProjectilesDamage(new List <AWizard> { self }); if (Utility.Less(curDamage, newDamage)) { FinalMove.Speed = 0; FinalMove.StrafeSpeed = 0; return(true); } return(false); }
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); }
static double GetCombatPriority(AWizard self, ACombatUnit unit) { // чем меньше - тем важнее стрелять в него первого var res = unit.Life; if (unit is AWizard) { res /= 4; if ((unit as AWizard).IsBesieded) { res /= 4; } res -= 50; } var dist = self.GetDistanceTo(unit); if (dist <= Game.StaffRange + unit.Radius + 10) { res -= 60; res += Math.Log(dist); } return(res); }
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); }
private bool HasAnyTarget(AWizard self, bool skipBuildings) { var my = new AWizard(self); foreach (var opp in OpponentCombats) { if (!opp.IsAssailable) { continue; } var prevCastRange = my.CastRange; var bld = opp as ABuilding; if (opp is AWizard) { if (my.CastRange <= opp.CastRange) { if (GoAwayCond(my, opp as AWizard)) { my.CastRange = opp.CastRange + GoAwaySafeDist; } else if (my.CastRange < opp.CastRange) { my.CastRange = my.CastRange + 25; // HACK: чтобы не бояться подходить к тем у кого прокачан range } } } if (bld != null && (skipBuildings || bld.IsBase)) { if (!bld.IsBase && bld.Lane != MessagesObserver.GetLane()) { continue; } if (!bld.IsBesieded) { // чтобы не подходить близко к одиноким башням if (my.GetDistanceTo(bld) < bld.CastRange + 6) { return(true); } } } if (my.GetDistanceTo(opp) <= my.CastRange + opp.Radius + Game.MagicMissileRadius) { var tmp = opp.RemainingFrozen; opp.RemainingFrozen = 100500; var canCast = my.EthalonCanCastMagicMissile(opp, checkCooldown: false, checkAngle: false); opp.RemainingFrozen = tmp; if (canCast) { return(true); } } my.CastRange = prevCastRange; } return(false); }
double _emulateRush(AWizard self, AWizard opp) { var nearest = Combats.Where(x => x.GetDistanceTo(ASelf) < ASelf.VisionRange * 1.4).Select(Utility.CloneCombat).ToArray(); self = nearest.FirstOrDefault(x => x.Id == self.Id) as AWizard; opp = nearest.FirstOrDefault(x => x.Id == opp.Id) as AWizard; if (self == null || opp == null) { return(int.MinValue); } var tergetsSelector = new TargetsSelector(nearest); if (opp.Id == _LastMmTarget && World.Projectiles.Any(x => x.OwnerUnitId == self.Id)) { opp.ApplyMagicalDamage(self.MagicMissileDamage); } while (true) { _rushTo(self, opp); foreach (var unit in nearest) { if (unit.Id == self.Id) { continue; } if (unit is AWizard) { if (unit.IsOpponent) { _rushTo(unit as AWizard, self); } else { if (Const.IsFinal) { _rushTo(unit as AWizard, opp); } else { unit.SkipTick(); } } } else if (unit is AMinion) { var tar = tergetsSelector.Select(unit); unit.EthalonMove(tar); if (tar != null && unit.EthalonCanHit(tar)) { if (unit is AFetish) { unit.RemainingActionCooldownTicks = Game.FetishBlowdartActionCooldownTicks - 1; tar.ApplyDamage(Game.DartDirectDamage); } else { unit.RemainingActionCooldownTicks = Game.OrcWoodcutterActionCooldownTicks - 1; tar.ApplyDamage(Game.OrcWoodcutterDamage); } } } } if (!self.IsAlive) { return(-opp.Life); } if (!opp.IsAlive) { var mines = nearest.Where(x => x.IsTeammate && x is AWizard).ToArray(); if (mines.Length >= 2 && Const.IsFinal) { if (!mines.All(x => x.Life > 10)) { return(int.MinValue); } return(50.0);//hack } else { if (nearest.Where(x => x.IsOpponent && x is AWizard).Sum(x => x.Life) > self.Life) { return(int.MinValue); } return(self.Life); } } } }
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 }); }
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 FindBonusTarget(AWizard self) { var minTime = int.MaxValue; var selGo = 0; Point selMoveTo = null; foreach (var _bonus in BonusesObserver.Bonuses) { if (_bonus.GetDistanceTo(self) - self.Radius - _bonus.Radius > Game.StaffRange * 3) { continue; } if (_bonus.RemainingAppearanceTicks > 60) { continue; } var nearest = Combats .Where(x => x.Id != self.Id && self.GetDistanceTo2(x) < Geom.Sqr(self.VisionRange)) .ToArray(); foreach (var angle in Utility.Range(self.Angle, Math.PI * 2 + self.Angle, 24, false)) { var bonus = new ABonus(_bonus); var my = new AWizard(self); var moveTo = my + Point.ByAngle(angle) * self.VisionRange; int time = 0; int go = 0; while (my.GetDistanceTo(bonus) > my.Radius + bonus.Radius && time < 60) { if (!my.MoveTo(moveTo, null, w => !CheckIntersectionsAndTress(w, nearest))) { break; } var wait = !bonus.Exists; bonus.SkipTick(); time++; if (my.GetDistanceTo(bonus) <= my.Radius + bonus.Radius) { while (!bonus.Exists) { bonus.SkipTick(); time++; } if (wait) { time++; } if (time < minTime) { minTime = time; selMoveTo = moveTo; selGo = go; } break; } go++; } } } var moving = new MovingInfo(selMoveTo, minTime, new FinalMove(new Move())); if (selMoveTo != null) { if (minTime == 1 || selGo > 0) { moving.Move.MoveTo(selMoveTo, null); } else { moving.Target = self; } } return(moving); }
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); }
Target _findTarget(AWizard self, Point moveTo) { var t0 = FindBonusTarget(self); var tfrost = FindCastTarget(self, ProjectileType.FrostBolt); var tfball = FindCastTarget(self, ProjectileType.Fireball); var tmm = FindCastTarget(self, ProjectileType.MagicMissile); var t2 = FindStaffTarget(self); var t3 = FindCastTarget2(self, t0.Target ?? moveTo, ProjectileType.MagicMissile); var t3fball = FindCastTarget2(self, t0.Target ?? moveTo, ProjectileType.Fireball); var ult = FindUltimateTarget(self); Point ret = null; if (t0.Target != null) { FinalMove.Apply(t0.Move); ret = t0.Target; } if (tfball.Target != null && tfball.Damage + 1 >= t3fball.Damage && tfball.Damage > tmm.Damage) { FinalMove.Action = tfball.Move.Action; FinalMove.MinCastDistance = tfball.Move.MinCastDistance; FinalMove.MaxCastDistance = tfball.Move.MaxCastDistance; FinalMove.CastAngle = tfball.Move.CastAngle; return(new Target { Type = ret == null ? TargetType.Opponent : TargetType.Bonus }); } if (tfrost.Target != null && tfrost.Time <= Math.Min(t2.Time, t3.Time)) { FinalMove.Action = tfrost.Move.Action; FinalMove.MinCastDistance = tfrost.Move.MinCastDistance; FinalMove.MaxCastDistance = tfrost.Move.MaxCastDistance; FinalMove.CastAngle = tfrost.Move.CastAngle; return(new Target { Type = ret == null ? TargetType.Opponent : TargetType.Bonus }); } if (tmm.Target != null && tmm.Time <= Math.Min(t2.Time, t3.Time)) { var wiz = t3.Target as AWizard; if (wiz != null && CanRush(ASelf, wiz) && t3.TargetId != tmm.TargetId) { } else { FinalMove.Action = tmm.Move.Action; FinalMove.MinCastDistance = tmm.Move.MinCastDistance; FinalMove.MaxCastDistance = tmm.Move.MaxCastDistance; FinalMove.CastAngle = tmm.Move.CastAngle; _LastMmTarget = tmm.TargetId; return(new Target { Type = ret == null ? TargetType.Opponent : TargetType.Bonus }); } } if (t0.Target == null && t2.Target != null && t2.Time <= Math.Min(tmm.Time, t3.Time)) { // иначе вообще не кидает хасту/щит if (ult.Target == null || t2.Target is AWizard) { FinalMove.Apply(t2.Move); return(new Target { Type = TargetType.Opponent }); } } if (t3fball.Target != null) { FinalMove.Apply(t3fball.Move); return(new Target { Type = TargetType.Opponent }); } if (t3.Target != null && t3.Time <= Math.Min(tmm.Time, t2.Time)) { FinalMove.Apply(t3.Move); return(new Target { Type = TargetType.Opponent }); } if (ult.Target != null) { FinalMove.Apply(ult.Move); return(new Target { Type = TargetType.Teammate }); } if (ret == null) { return(null); } return(new Target { Type = TargetType.Bonus }); }