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); }
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); })); }
WizardPath GoAgainst(ACombatUnit to) { TimerStart(); var ret = _goAround(to, true); TimerEndLog("Dijkstra", 1); return(ret); }
private static int _targetImportance(ACombatUnit unit) { if (unit is AWizard) { return(3); } if (unit is ABuilding) { return(2); } return(1); }
public AProjectile(ACombatUnit self, double castAngle, ProjectileType type) { Type = type; Speed = Const.ProjectileInfo[(int)type].Speed; Radius = Const.ProjectileInfo[(int)type].Radius; X = self.X; Y = self.Y; SpeedX = Math.Cos(self.Angle + castAngle) * Speed; SpeedY = Math.Sin(self.Angle + castAngle) * Speed; RemainingDistance = self.CastRange; OwnerUnitId = self.Id; Faction = self.Faction; SetupDamage(self); }
protected ACombatUnit(ACombatUnit unit) : base(unit) { IsTeammate = unit.IsTeammate; Life = unit.Life; MaxLife = unit.MaxLife; VisionRange = unit.VisionRange; CastRange = unit.CastRange; RemainingActionCooldownTicks = unit.RemainingActionCooldownTicks; RemainingHastened = unit.RemainingHastened; RemainingEmpowered = unit.RemainingEmpowered; RemainingFrozen = unit.RemainingFrozen; RemainingShielded = unit.RemainingShielded; IsBurning = unit.IsBurning; }
public ACombatUnit Select(ACombatUnit unit) { if (_minionsSelectionsCache.ContainsKey(unit.Id)) { return(_minionsSelectionsCache[unit.Id]); } var res = unit.SelectTarget(_combats); if (EnableMinionsCache && unit is AMinion) { _minionsSelectionsCache[unit.Id] = res; } return(res); }
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 static ACombatUnit CloneCombat(ACombatUnit combat) { if (combat is AWizard) { return(new AWizard(combat as AWizard)); } if (combat is ABuilding) { return(new ABuilding(combat as ABuilding)); } if (combat is AOrc) { return(new AOrc(combat as AOrc)); } if (combat is AFetish) { return(new AFetish(combat as AFetish)); } throw new Exception("unknown combat type"); }
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); }
public override ACombatUnit SelectTarget(ACombatUnit[] candidates) { var accessible = candidates .Where(x => x.Faction != Faction.Neutral && x.Faction != Faction && x.IsAlive && GetDistanceTo2(x) <= Geom.Sqr(CastRange)) .ToArray(); ACombatUnit sel = null; foreach (var x in accessible) { if (x.Life > Damage) { if (sel == null || x.Life < sel.Life || Utility.Equals(x.Life, sel.Life) && x.Id == MyStrategy.Self.Id) { sel = x; } } } if (sel != null) { return(sel); } foreach (var x in accessible) { if (x.Life <= Damage) { if (sel == null || x.Life > sel.Life || Utility.Equals(x.Life, sel.Life) && x.Id == MyStrategy.Self.Id) { sel = x; } } } return(sel); }
public void SetupDamage(ACombatUnit unit) { var self = unit as AWizard; switch (Type) { case ProjectileType.Fireball: Damage = self.FireballMaxDamage; MinDamage = self.FireballMinDamage; break; case ProjectileType.MagicMissile: Damage = self.MagicMissileDamage; break; case ProjectileType.FrostBolt: Damage = self.FrostBoltDamage; break; case ProjectileType.Dart: Damage = MyStrategy.Game.DartDirectDamage; break; } }
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 }); }
public static bool IsBase(ACombatUnit unit) { return(unit is ABuilding && ((ABuilding)unit).IsBase); }
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 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); }
public List <ProjectilePathSegment> Emulate(ACombatUnit target, double wizardsChangeAngle) { target = Utility.CloneCombat(target); // NOTE: target и он же в minionsTargetsSelector - разные instance. Этим можно пренебречь return(_emulate(new [] { target }, wizardsChangeAngle, MyStrategy.MinionsTargetsSelector /*TODO: HACK*/)); }