Beispiel #1
0
        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));
        }
Beispiel #2
0
		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;
		}
Beispiel #3
0
        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)));
        }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        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);
        }
Beispiel #6
0
        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);
            }
        }