DeliveryActorPathInfo GetDeliveryActorPathInfo(Actor self) { var owner = self.Owner; var map = owner.World.Map; var bounds = map.Bounds; var mpStart = owner.World.WorldActor.TraitOrDefault <MPStartLocations>(); var startPos = CPos.Zero; var spawnFacing = 0; switch (info.EntryType) { case EntryType.Fixed: // Start a fixed distance away: the width of the map. // This makes the production timing independent of spawnpoint var loc = self.Location.ToMPos(map); startPos = new MPos(loc.U + map.Bounds.Width, loc.V).ToCPos(map); spawnFacing = GetSpawnFacing(self.Location, startPos); break; case EntryType.PlayerSpawnClosestEdge: if (mpStart == null) { break; } var spawn = mpStart.Start[owner]; var center = new MPos(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2).ToCPos(map); var spawnVec = spawn - center; startPos = spawn + spawnVec * (Exts.ISqrt((bounds.Height * bounds.Height + bounds.Width * bounds.Width) / (4 * spawnVec.LengthSquared))); spawnFacing = GetSpawnFacing(self.Location, startPos); break; case EntryType.DropSiteClosestEdge: startPos = self.World.Map.ChooseClosestEdgeCell(self.Location); spawnFacing = GetSpawnFacing(self.Location, startPos); bounds = self.World.Map.Bounds; if (info.SpawnOffset.X != 0 && startPos.X != bounds.X && startPos.X != bounds.X + bounds.Width) { startPos += new CVec(info.SpawnOffset.X, 0); } if (info.SpawnOffset.Y != 0 && startPos.Y != bounds.Y && startPos.Y != bounds.Y + bounds.Height) { startPos += new CVec(0, info.SpawnOffset.Y); } break; } var exitPositionFunc = GetExitPositionFunc(self, startPos, spawnFacing); return(new DeliveryActorPathInfo(startPos, spawnFacing, info.LandingFacing, exitPositionFunc)); }
int IncreaseAltitude(int predClfDist, int diffClfMslHgt, int relTarHorDist, int vFacing) { var desiredVFacing = vFacing; // If missile is below incline top height and facing downwards, bring back // its vertical facing above zero as soon as possible if ((sbyte)vFacing < 0) desiredVFacing = info.VerticalRateOfTurn.Facing; // Missile will climb around incline top if bringing vertical facing // down to zero on an arc of radius loopRadius else if (IsNearInclineTop(vFacing, loopRadius, predClfDist) && WillClimbAroundInclineTop(vFacing, loopRadius, predClfDist, diffClfMslHgt, speed)) desiredVFacing = 0; // Missile will not climb terrAltDiff w-units within hHeightChange w-units // all the while ending the ascent with vertical facing 0 else if (!WillClimbWithinDistance(vFacing, loopRadius, predClfDist, diffClfMslHgt)) // Find smallest vertical facing, attainable in the next tick, // for which the missile will be able to climb terrAltDiff w-units // within hHeightChange w-units all the while ending the ascent // with vertical facing 0 for (var vFac = System.Math.Min(vFacing + info.VerticalRateOfTurn.Facing - 1, 63); vFac >= vFacing; vFac--) if (!WillClimbWithinDistance(vFac, loopRadius, predClfDist, diffClfMslHgt) && !(predClfDist <= loopRadius * (1024 - WAngle.FromFacing(vFac).Sin()) / 1024 && WillClimbAroundInclineTop(vFac, loopRadius, predClfDist, diffClfMslHgt, speed))) { desiredVFacing = vFac + 1; break; } // Attained height after ascent as predicted from upper part of incline surmounting manoeuvre var predAttHght = loopRadius * (1024 - WAngle.FromFacing(vFacing).Cos()) / 1024 - diffClfMslHgt; // Should the missile be slowed down in order to make it more manoeuverable var slowDown = info.Acceleration.Length != 0 // Possible to decelerate && ((desiredVFacing != 0 // Lower part of incline surmounting manoeuvre // Incline will be hit before vertical facing attains 64 && (predClfDist <= loopRadius * (1024 - WAngle.FromFacing(vFacing).Sin()) / 1024 // When evaluating this the incline will be *not* be hit before vertical facing attains 64 // At current speed target too close to hit without passing it by || relTarHorDist <= 2 * loopRadius * (2048 - WAngle.FromFacing(vFacing).Sin()) / 1024 - predClfDist)) || (desiredVFacing == 0 // Upper part of incline surmounting manoeuvre && relTarHorDist <= loopRadius * WAngle.FromFacing(vFacing).Sin() / 1024 + Exts.ISqrt(predAttHght * (2 * loopRadius - predAttHght)))); // Target too close to hit at current speed if (slowDown) ChangeSpeed(-1); return desiredVFacing; }
public WDist DistanceFromEdge(WVec v) { var p = new int2(v.X, v.Y); var z = Math.Abs(v.Z); if (Points.PolygonContains(p)) { return(new WDist(z)); } var min2 = DistanceSquaredFromLineSegment(p, Points[Points.Length - 1], Points[0], squares[0]); for (var i = 1; i < Points.Length; i++) { var d2 = DistanceSquaredFromLineSegment(p, Points[i - 1], Points[i], squares[i]); if (d2 < min2) { min2 = d2; } } return(new WDist(Exts.ISqrt(min2 + z * z))); }
public override bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits, int refundableValue) { if (IsTraitDisabled || IsTraitPaused) { return(false); } var info = (ProductionAirdropInfo)Info; var owner = self.Owner; var map = owner.World.Map; var aircraftInfo = self.World.Map.Rules.Actors[info.ActorType].TraitInfo <AircraftInfo>(); CPos startPos; CPos endPos; WAngle spawnFacing; if (info.BaselineSpawn) { var bounds = map.Bounds; var center = new MPos(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2).ToCPos(map); var spawnVec = owner.HomeLocation - center; startPos = owner.HomeLocation + spawnVec * (Exts.ISqrt((bounds.Height * bounds.Height + bounds.Width * bounds.Width) / (4 * spawnVec.LengthSquared))); endPos = startPos; var spawnDirection = new WVec((self.Location - startPos).X, (self.Location - startPos).Y, 0); spawnFacing = spawnDirection.Yaw; } else { // Start a fixed distance away: the width of the map. // This makes the production timing independent of spawnpoint var loc = self.Location.ToMPos(map); startPos = new MPos(loc.U + map.Bounds.Width, loc.V).ToCPos(map); endPos = new MPos(map.Bounds.Left, loc.V).ToCPos(map); spawnFacing = info.Facing; } // Assume a single exit point for simplicity var exit = self.Info.TraitInfos <ExitInfo>().First(); foreach (var tower in self.TraitsImplementing <INotifyDelivery>()) { tower.IncomingDelivery(self); } owner.World.AddFrameEndTask(w => { if (!self.IsInWorld || self.IsDead) { owner.PlayerActor.Trait <PlayerResources>().GiveCash(refundableValue); return; } var actor = w.CreateActor(info.ActorType, new TypeDictionary { new CenterPositionInit(w.Map.CenterOfCell(startPos) + new WVec(WDist.Zero, WDist.Zero, aircraftInfo.CruiseAltitude)), new OwnerInit(owner), new FacingInit(spawnFacing) }); var exitCell = self.Location + exit.ExitCell; actor.QueueActivity(new Land(actor, Target.FromActor(self), WDist.Zero, WVec.Zero, info.Facing, clearCells: new CPos[1] { exitCell })); actor.QueueActivity(new CallFunc(() => { if (!self.IsInWorld || self.IsDead) { owner.PlayerActor.Trait <PlayerResources>().GiveCash(refundableValue); return; } foreach (var cargo in self.TraitsImplementing <INotifyDelivery>()) { cargo.Delivered(self); } self.World.AddFrameEndTask(ww => DoProduction(self, producee, exit, productionType, inits)); Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.ReadyAudio, self.Owner.Faction.InternalName); TextNotificationsManager.AddTransientLine(info.ReadyTextNotification, self.Owner); })); actor.QueueActivity(new FlyOffMap(actor, Target.FromCell(w, endPos))); actor.QueueActivity(new RemoveSelf()); }); return(true); }
int HomingInnerTick(int predClfDist, int diffClfMslHgt, int relTarHorDist, int lastHtChg, int lastHt, int nxtRelTarHorDist, int relTarHgt, int vFacing, bool targetPassedBy) { int desiredVFacing = vFacing; // Incline coming up -> attempt to reach the incline so that after predClfDist // the height above the terrain is positive but as close to 0 as possible // Also, never change horizontal facing and never travel backwards // Possible techniques to avoid close cliffs are deceleration, turning // as sharply as possible to travel directly upwards and then returning // to zero vertical facing as low as possible while still not hitting the // high terrain. A last technique (and the preferred one, normally used when // the missile hasn't been fired near a cliff) is simply finding the smallest // vertical facing that allows for a smooth climb to the new terrain's height // and coming in at predClfDist at exactly zero vertical facing if (info.TerrainHeightAware && diffClfMslHgt >= 0 && !allowPassBy) { desiredVFacing = IncreaseAltitude(predClfDist, diffClfMslHgt, relTarHorDist, vFacing); } else if (relTarHorDist <= 3 * loopRadius || state == States.Hitting) { // No longer travel at cruise altitude state = States.Hitting; if (lastHt >= targetPosition.Z) { allowPassBy = true; } if (!allowPassBy && (lastHt < targetPosition.Z || targetPassedBy)) { // Aim for the target var vDist = new WVec(-relTarHgt, -relTarHorDist, 0); desiredVFacing = (sbyte)vDist.HorizontalLengthSquared != 0 ? vDist.Yaw.Facing : vFacing; // Do not accept -1 as valid vertical facing since it is usually a numerical error // and will lead to premature descent and crashing into the ground if (desiredVFacing == -1) { desiredVFacing = 0; } // If the target has been passed by, limit the absolute value of // vertical facing by the maximum vertical rate of turn // Do this because the missile will be looping horizontally // and thus needs smaller vertical facings so as not // to hit the ground prematurely if (targetPassedBy) { desiredVFacing = desiredVFacing.Clamp(-info.VerticalRateOfTurn.Facing, info.VerticalRateOfTurn.Facing); } else if (lastHt == 0) { // Before the target is passed by, missile speed should be changed // Target's height above loop's center var tarHgt = (loopRadius * WAngle.FromFacing(vFacing).Cos() / 1024 - System.Math.Abs(relTarHgt)).Clamp(0, loopRadius); // Target's horizontal distance from loop's center var tarDist = Exts.ISqrt(loopRadius * loopRadius - tarHgt * tarHgt); // Missile's horizontal distance from loop's center var missDist = loopRadius * WAngle.FromFacing(vFacing).Sin() / 1024; // If the current height does not permit the missile // to hit the target before passing it by, lower speed // Otherwise, increase speed if (relTarHorDist <= tarDist - System.Math.Sign(relTarHgt) * missDist) { ChangeSpeed(-1); } else { ChangeSpeed(); } } } else if (allowPassBy || (lastHt != 0 && relTarHorDist - lastHtChg < loopRadius)) { // Only activate this part if target too close to cliff allowPassBy = true; // Vector from missile's current position pointing to the loop's center var radius = new WVec(loopRadius, 0, 0) .Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(64 - vFacing))); // Vector from loop's center to incline top hardcoded in height buffer zone var edgeVector = new WVec(lastHtChg, lastHt - pos.Z, 0) - radius; if (!targetPassedBy) { // Climb to critical height if (relTarHorDist > 2 * loopRadius) { // Target's distance from cliff var d1 = relTarHorDist - lastHtChg; if (d1 < 0) { d1 = 0; } if (d1 > 2 * loopRadius) { return(0); } // Find critical height at which the missile must be once it is at one loopRadius // away from the target var h1 = loopRadius - Exts.ISqrt(d1 * (2 * loopRadius - d1)) - (pos.Z - lastHt); if (h1 > loopRadius * (1024 - WAngle.FromFacing(vFacing).Cos()) / 1024) { desiredVFacing = WAngle.ArcTan(Exts.ISqrt(h1 * (2 * loopRadius - h1)), loopRadius - h1).Angle >> 2; } else { desiredVFacing = 0; } // TODO: deceleration checks!!! } else { // Avoid the cliff edge if (info.TerrainHeightAware && edgeVector.Length > loopRadius && lastHt > targetPosition.Z) { int vFac; for (vFac = vFacing + 1; vFac <= vFacing + info.VerticalRateOfTurn.Facing - 1; vFac++) { // Vector from missile's current position pointing to the loop's center radius = new WVec(loopRadius, 0, 0) .Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(64 - vFac))); // Vector from loop's center to incline top + 64 hardcoded in height buffer zone edgeVector = new WVec(lastHtChg, lastHt - pos.Z, 0) - radius; if (edgeVector.Length <= loopRadius) { break; } } desiredVFacing = vFac; } else { // Aim for the target var vDist = new WVec(-relTarHgt, -relTarHorDist, 0); desiredVFacing = (sbyte)vDist.HorizontalLengthSquared != 0 ? vDist.Yaw.Facing : vFacing; if (desiredVFacing < 0 && info.VerticalRateOfTurn.Facing < (sbyte)vFacing) { desiredVFacing = 0; } } } } else { // Aim for the target var vDist = new WVec(-relTarHgt, relTarHorDist, 0); desiredVFacing = (sbyte)vDist.HorizontalLengthSquared != 0 ? vDist.Yaw.Facing : vFacing; if (desiredVFacing < 0 && info.VerticalRateOfTurn.Facing < (sbyte)vFacing) { desiredVFacing = 0; } } } else { // Aim to attain cruise altitude as soon as possible while having the absolute value // of vertical facing bound by the maximum vertical rate of turn var vDist = new WVec(-diffClfMslHgt - info.CruiseAltitude.Length, -speed, 0); desiredVFacing = (sbyte)vDist.HorizontalLengthSquared != 0 ? vDist.Yaw.Facing : vFacing; // If the missile is launched above CruiseAltitude, it has to descend instead of climbing if (-diffClfMslHgt > info.CruiseAltitude.Length) { desiredVFacing = -desiredVFacing; } desiredVFacing = desiredVFacing.Clamp(-info.VerticalRateOfTurn.Facing, info.VerticalRateOfTurn.Facing); ChangeSpeed(); } } else { // Aim to attain cruise altitude as soon as possible while having the absolute value // of vertical facing bound by the maximum vertical rate of turn var vDist = new WVec(-diffClfMslHgt - info.CruiseAltitude.Length, -speed, 0); desiredVFacing = (sbyte)vDist.HorizontalLengthSquared != 0 ? vDist.Yaw.Facing : vFacing; // If the missile is launched above CruiseAltitude, it has to descend instead of climbing if (-diffClfMslHgt > info.CruiseAltitude.Length) { desiredVFacing = -desiredVFacing; } desiredVFacing = desiredVFacing.Clamp(-info.VerticalRateOfTurn.Facing, info.VerticalRateOfTurn.Facing); ChangeSpeed(); } return(desiredVFacing); }
public void Tick(SquadCA owner) { if (!owner.IsValid) { return; } if (!owner.IsTargetValid) { var closestEnemy = FindClosestEnemyBuilding(owner); if (closestEnemy != null) { owner.TargetActor = closestEnemy; LongRangeAttackMoveAlready = false; } else { owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true); return; } } // 1. Threat scan at beginning var teamLeader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition); if (teamLeader == null) { return; } var teamTail = owner.Units.MaxByOrDefault(a => (a.CenterPosition - owner.TargetActor.CenterPosition).LengthSquared); var attackScanRadius = WDist.FromCells(owner.SquadManager.Info.AttackScanRadius); var targetActor = ThreatScan(owner, teamLeader, attackScanRadius) ?? ThreatScan(owner, teamTail, attackScanRadius); if (targetActor != null) { owner.TargetActor = targetActor; owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackState(), true); return; } else if (!LongRangeAttackMoveAlready) { foreach (var a in owner.Units) { owner.Bot.QueueOrder(new Order("AttackMove", a, Target.FromCell(owner.World, owner.TargetActor.Location), false)); } LongRangeAttackMoveAlready = true; } // 2. Force going through very twisted path if get lost in path if (StuckedInPath <= 0) { LongRangeAttackMoveAlready = false; if (TryFindPath > 0) { if (!LongRangeAttackMoveAlready) { foreach (var a in owner.Units) { owner.Bot.QueueOrder(new Order("AttackMove", a, Target.FromCell(owner.World, owner.TargetActor.Location), false)); } LongRangeAttackMoveAlready = true; } TryFindPath--; } else { // When going through is over, restore the check and force to regroup StuckedInPath = StuckedInPathCheckTimes + ForceReproupTick; TryFindPath = FindPathTick; LongRangeAttackMoveAlready = false; ForceRegroup = true; } return; } // 3. Check if the squad is stucked due to the map has very twisted path // or currently bridge and tunnel from TS mod /* * See if a actor (always the same if found) is always in a area. * 100 is the default thresold of the length squared of distance change. * Record at least two positions to find if it stucked. * "LastPosIndex" will switch to 0 or 1 to ensure different index everytime. */ var regrouper = owner.Units.ClosestTo(new WPos[] { teamLeader.CenterPosition, teamTail.CenterPosition }.Average()); if (StuckedInPath == StuckedInPathCheckTimes || StuckedActor == null || StuckedActor.IsDead) { StuckedActor = regrouper; } if ((StuckedActor.CenterPosition - LastPos[LastPosIndex]).LengthSquared <= 100) { StuckedInPath--; } else { StuckedInPath = StuckedInPathCheckTimes; } LastPos[LastPosIndex] = StuckedActor.CenterPosition; LastPosIndex = LastPosIndex ^ 1; // 4. Since units have different movement speeds, they get separated while approaching the target. /* * Let them regroup into tighter formation towards "regrouper". * If "ForceRegroup" is on, the squad is just after a force-going-through, * it requires regrouping to the same actor in order to * avoid step back to the complex terrain they just escape from. */ if (ForceRegroup) { regrouper = StuckedActor; } var ownUnits = owner.World.FindActorsInCircle(regrouper.CenterPosition, WDist.FromCells(Exts.ISqrt(owner.Units.Count) * 2)) .Where(a => a.Owner == owner.Units.First().Owner&& owner.Units.Contains(a)).ToHashSet(); if (ownUnits.Count < owner.Units.Count) { // Advance or regroup owner.Bot.QueueOrder(new Order("Stop", regrouper, false)); foreach (var unit in owner.Units.Where(a => !ownUnits.Contains(a))) { owner.Bot.QueueOrder(new Order("AttackMove", unit, Target.FromCell(owner.World, regrouper.Location), false)); } LongRangeAttackMoveAlready = false; if (ownUnits.Count == owner.Units.Count) { ForceRegroup = false; } foreach (var a in owner.Units) { owner.Bot.QueueOrder(new Order("AttackMove", a, Target.FromCell(owner.World, owner.TargetActor.Location), false)); } } if (ShouldFlee(owner)) { owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true); } }