public void Tick(Actor self) { var bombHeight = self.World.Map.DistanceAboveTerrain(self.CenterPosition); var bombTarget = Target.FromPos(self.CenterPosition - new WVec(WDist.Zero, WDist.Zero, bombHeight)); var wasInAttackRange = inAttackRange; var wasFacingTarget = facingTarget; inAttackRange = false; var f = facing.Value.Facing; var delta = target.CenterPosition - self.CenterPosition; var facingToTarget = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : f; facingTarget = Math.Abs(facingToTarget - f) % 256 <= info.FacingTolerance; // Bombs drop anywhere in range foreach (var a in Armaments.Where(a => a.Info.Name == info.Bombs)) { if (!target.IsInRange(self.CenterPosition, a.MaxRange())) { continue; } inAttackRange = true; a.CheckFire(self, facing.Value, bombTarget); } // Guns only fire when approaching the target if (facingTarget) { foreach (var a in Armaments.Where(a => a.Info.Name == info.Guns)) { if (!target.IsInRange(self.CenterPosition, a.MaxRange())) { continue; } inAttackRange = true; var gunPos = self.CenterPosition - new WVec(0, a.MaxRange().Length / 2, 0).Rotate(WRot.FromFacing(f)); var gunHeight = self.World.Map.DistanceAboveTerrain(gunPos); a.CheckFire(self, facing.Value, Target.FromPos(gunPos - new WVec(WDist.Zero, WDist.Zero, gunHeight))); } } // Actors without armaments may want to trigger an action when it passes the target if (!Armaments.Any()) { inAttackRange = !wasInAttackRange && !facingTarget && wasFacingTarget; } if (inAttackRange && !wasInAttackRange) { OnEnteredAttackRange(self); } if (!inAttackRange && wasInAttackRange) { OnExitedAttackRange(self); } }
public WVec FlyStep(int speed, int facing) { var dir = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing)); return(speed * dir / 1024); }
public override Activity Tick(Actor self) { if (ChildActivity != null) { ChildActivity = ActivityUtils.RunActivity(self, ChildActivity); if (ChildActivity != null) { return(this); } } if (IsCanceling || carryall.State != Carryall.CarryallState.Carrying || carryall.Carryable.IsDead) { return(NextActivity); } // Drop the actor at the current position if (destination.Type == TargetType.Invalid) { destination = Target.FromCell(self.World, self.Location); } var target = FindDropLocation(destination, carryall.Info.DropRange); // Can't land, so wait at the target until something changes if (target.Type == TargetType.Invalid) { QueueChild(self, new Fly(self, destination), true); QueueChild(self, new Wait(25)); return(this); } // Move to drop-off location var localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, self.Orientation)); var carryablePosition = self.CenterPosition + body.LocalToWorld(localOffset); if ((carryablePosition - target.CenterPosition).HorizontalLengthSquared != 0) { // For non-zero offsets the drop position depends on the carryall facing // We therefore need to predict/correct for the facing *at the drop point* if (carryall.CarryableOffset.HorizontalLengthSquared != 0) { var dropFacing = (target.CenterPosition - self.CenterPosition).Yaw.Facing; localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, WRot.FromFacing(dropFacing))); QueueChild(self, new Fly(self, Target.FromPos(target.CenterPosition - body.LocalToWorld(localOffset))), true); QueueChild(self, new Turn(self, dropFacing)); return(this); } QueueChild(self, new Fly(self, target), true); return(this); } // Make sure that the carried actor is on the ground before releasing it if (self.World.Map.DistanceAboveTerrain(carryablePosition) != WDist.Zero) { QueueChild(self, new Land(self), true); } // Pause briefly before releasing for visual effect if (carryall.Info.UnloadingDelay > 0) { QueueChild(self, new Wait(carryall.Info.UnloadingDelay, false), true); } // Release carried actor QueueChild(self, new ReleaseUnit(self)); QueueChild(self, new Fly(self, Target.FromPos(self.CenterPosition))); return(this); }
void PreSpawnClouds(World world) { var facing = 256 * info.WindDirection / 32; var delta = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing)); /* * The following codes find the middle point in map. Because clouds must * start from an edge to another edge, the paths of clouds * which contain this point will always be the longest path no matter the direction, * as long as the map is a symmetrical shape. * * For example, the middle point in map is "@", @ is (u = X/2, v = Y/2) * You can imagine any wind direction with all edges clouds possibly spawn. * * ____X____ * | | * | | * | @ Y * | | * |_______| * * By using this longest path, we can figure out the number of clouds should * spawn when a certain cloud completely goes over a longest path, which should be * neither too little nor too big compared with the clouds per map-cell later * spawned by `SpawnCloud`. */ var middlePoint = new MPos(world.Map.MapSize.X / 2, world.Map.MapSize.Y / 2); var middlePointTarget = world.Map.CenterOfCell(middlePoint.ToCPos(world.Map)); // lDistance and averageSpeed used are for loop condition below. var longestCloudDistance = System.Math.Abs( (world.Map.DistanceToEdge(middlePointTarget, -delta) + info.Cordon).Length + (world.Map.DistanceToEdge(middlePointTarget, delta) + info.Cordon).Length); var averageCloudSpeed = System.Math.Abs((int)info.Speed.Average(s => s.Length)); var stepPerSpawn = averageCloudSpeed * info.SpawnInterval; var stepPerSpawnVector = stepPerSpawn * delta / 1024; /* * Spawn clouds. * * Try to make clouds spawning cover the entire map, meanwhile * with some randomization. Choose random spawning point and * find startEdge, then add offset to let they go further to cover * the map. The offset will be increased to simulate the movement * the cloud would have made otherwise. */ var offset = WVec.Zero; while (longestCloudDistance > 0) { var position = world.Map.ChooseRandomCell(world.SharedRandom); var target = world.Map.CenterOfCell(position) + new WVec(0, 0, info.CruiseAltitude.Length); var startEdge = target - (world.Map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; startEdge += offset; var finishEdge = target + (world.Map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; var animation = new Animation(world, info.Image, () => WAngle.FromFacing(facing)); animation.PlayRepeating(info.Sequences.Random(world.SharedRandom)); world.AddFrameEndTask(w => w.Add(new Cloud(world, animation, startEdge, finishEdge, facing, info))); offset += stepPerSpawnVector; longestCloudDistance -= stepPerSpawn; } }
public void SendAirstrike(Actor self, WPos target, bool randomize = true, int attackFacing = 0) { var info = Info as AirstrikePowerInfo; if (randomize) { attackFacing = 256 * self.World.SharedRandom.Next(info.QuantizedFacings) / info.QuantizedFacings; } var altitude = self.World.Map.Rules.Actors[info.UnitType].TraitInfo <AircraftInfo>().CruiseAltitude.Length; var attackRotation = WRot.FromFacing(attackFacing); var delta = new WVec(0, -1024, 0).Rotate(attackRotation); target = target + new WVec(0, 0, altitude); var startEdge = target - (self.World.Map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; var finishEdge = target + (self.World.Map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; Actor camera = null; Beacon beacon = null; var aircraftInRange = new Dictionary <Actor, bool>(); Action <Actor> onEnterRange = a => { // Spawn a camera and remove the beacon when the first plane enters the target area if (info.CameraActor != null && !aircraftInRange.Any(kv => kv.Value)) { self.World.AddFrameEndTask(w => { camera = w.CreateActor(info.CameraActor, new TypeDictionary { new LocationInit(self.World.Map.CellContaining(target)), new OwnerInit(self.Owner), }); }); } if (beacon != null) { self.World.AddFrameEndTask(w => { w.Remove(beacon); beacon = null; }); } aircraftInRange[a] = true; }; Action <Actor> onExitRange = a => { aircraftInRange[a] = false; // Remove the camera when the final plane leaves the target area if (!aircraftInRange.Any(kv => kv.Value)) { if (camera != null) { camera.QueueActivity(new Wait(info.CameraRemoveDelay)); camera.QueueActivity(new RemoveSelf()); } camera = null; if (beacon != null) { self.World.AddFrameEndTask(w => { w.Remove(beacon); beacon = null; }); } } }; self.World.AddFrameEndTask(w => { var isAllied = self.Owner.IsAlliedWith(self.World.RenderPlayer); Game.Sound.Play(isAllied ? Info.LaunchSound : Info.IncomingSound); var speech = isAllied ? Info.LaunchSpeechNotification : Info.IncomingSpeechNotification; Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", speech, self.Owner.Faction.InternalName); Actor distanceTestActor = null; for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (info.SquadSize & 1) == 0) { continue; } // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(attackRotation); var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(attackRotation); var a = w.CreateActor(info.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(attackFacing), }); var attack = a.Trait <AttackBomber>(); attack.SetTarget(w, target + targetOffset); attack.OnEnteredAttackRange += onEnterRange; attack.OnExitedAttackRange += onExitRange; attack.OnRemovedFromWorld += onExitRange; a.QueueActivity(new Fly(a, Target.FromPos(target + spawnOffset))); a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset))); a.QueueActivity(new RemoveSelf()); aircraftInRange.Add(a, false); distanceTestActor = a; } if (Info.DisplayBeacon) { var distance = (target - startEdge).HorizontalLength; beacon = new Beacon( self.Owner, target - new WVec(0, 0, altitude), Info.BeaconPaletteIsPlayerPalette, Info.BeaconPalette, Info.BeaconImage, Info.BeaconPoster, Info.BeaconPosterPalette, Info.ArrowSequence, Info.CircleSequence, Info.ClockSequence, () => 1 - ((distanceTestActor.CenterPosition - target).HorizontalLength - info.BeaconDistanceOffset.Length) * 1f / distance); w.Add(beacon); } }); }
public override bool Tick(Actor self) { // Refuse to take off if it would land immediately again. if (aircraft.ForceLanding) { Cancel(self); } var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); if (IsCanceling) { // We must return the actor to a sensible height before continuing. // If the aircraft is on the ground we queue TakeOff to manage the influence reservation and takeoff sounds etc. // TODO: It would be better to not take off at all, but we lack the plumbing to detect current airborne/landed state. // If the aircraft lands when idle and is idle, we let the default idle handler manage this. // TODO: Remove this after fixing all activities to work properly with arbitrary starting altitudes. var landWhenIdle = aircraft.Info.IdleBehavior == IdleBehaviorType.Land; var skipHeightAdjustment = landWhenIdle && self.CurrentActivity.IsCanceling && self.CurrentActivity.NextActivity == null; if (aircraft.Info.CanHover && !skipHeightAdjustment && dat != aircraft.Info.CruiseAltitude) { if (dat <= aircraft.LandAltitude) { QueueChild(new TakeOff(self)); } else { VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude); } return(false); } return(true); } else if (dat <= aircraft.LandAltitude) { QueueChild(new TakeOff(self)); return(false); } bool targetIsHiddenActor; target = target.Recalculate(self.Owner, out targetIsHiddenActor); if (!targetIsHiddenActor && target.Type == TargetType.Actor) { lastVisibleTarget = Target.FromTargetPositions(target); } useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self); // Target is hidden or dead, and we don't have a fallback position to move towards if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self)) { return(true); } var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target; var pos = aircraft.GetPosition(); var delta = checkTarget.CenterPosition - pos; var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : aircraft.Facing; // Inside the target annulus, so we're done var insideMaxRange = maxRange.Length > 0 && checkTarget.IsInRange(pos, maxRange); var insideMinRange = minRange.Length > 0 && checkTarget.IsInRange(pos, minRange); if (insideMaxRange && !insideMinRange) { return(true); } var isSlider = aircraft.Info.CanSlide; var move = isSlider ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing); // Inside the minimum range, so reverse if we CanSlide if (isSlider && insideMinRange) { FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, -move); return(false); } // HACK: Consider ourselves blocked if we have moved by less than 64 WDist in the last five ticks // Stop if we are blocked and close enough if (positionBuffer.Count >= 5 && (positionBuffer.Last() - positionBuffer[0]).LengthSquared < 4096 && delta.HorizontalLengthSquared <= nearEnough.LengthSquared) { return(true); } // The next move would overshoot, so consider it close enough or set final position if we CanSlide if (delta.HorizontalLengthSquared < move.HorizontalLengthSquared) { // For VTOL landing to succeed, it must reach the exact target position, // so for the final move it needs to behave as if it had CanSlide. if (isSlider || aircraft.Info.VTOL) { // Set final (horizontal) position if (delta.HorizontalLengthSquared != 0) { // Ensure we don't include a non-zero vertical component here that would move us away from CruiseAltitude var deltaMove = new WVec(delta.X, delta.Y, 0); FlyTick(self, aircraft, desiredFacing, dat, deltaMove); } // Move to CruiseAltitude, if not already there if (dat != aircraft.Info.CruiseAltitude) { Fly.VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude); return(false); } } return(true); } if (!isSlider) { // Using the turn rate, compute a hypothetical circle traced by a continuous turn. // If it contains the destination point, it's unreachable without more complex manuvering. var turnRadius = CalculateTurnRadius(aircraft.MovementSpeed, aircraft.TurnSpeed); // The current facing is a tangent of the minimal turn circle. // Make a perpendicular vector, and use it to locate the turn's center. var turnCenterFacing = aircraft.Facing; turnCenterFacing += Util.GetNearestFacing(aircraft.Facing, desiredFacing) > 0 ? 64 : -64; var turnCenterDir = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(turnCenterFacing)); turnCenterDir *= turnRadius; turnCenterDir /= 1024; // Compare with the target point, and keep flying away if it's inside the circle. var turnCenter = aircraft.CenterPosition + turnCenterDir; if ((checkTarget.CenterPosition - turnCenter).HorizontalLengthSquared < turnRadius * turnRadius) { desiredFacing = aircraft.Facing; } } positionBuffer.Add(self.CenterPosition); if (positionBuffer.Count > 5) { positionBuffer.RemoveAt(0); } FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude); return(false); }
CPos?PickTargetLocation() { var target = self.CenterPosition + new WVec(0, -1024 * effectiveMoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); var targetCell = self.World.Map.CellContaining(target); if (!self.World.Map.Contains(targetCell)) { // If MoveRadius is too big there might not be a valid cell to order the attack to (if actor is on a small island and can't leave) if (++ticksIdle % info.ReduceMoveRadiusDelay == 0) { effectiveMoveRadius--; } // We'll be back the next tick; better to sit idle for a few seconds than prolong this tick indefinitely with a loop return(null); } if (info.AvoidTerrainTypes.Count > 0) { var terrainType = self.World.Map.GetTerrainInfo(targetCell).Type; if (Info.AvoidTerrainTypes.Contains(terrainType)) { return(null); } } ticksIdle = 0; effectiveMoveRadius = info.WanderMoveRadius; return(targetCell); }
public void Tick(World world) { ticks++; if (anim != null) { anim.Tick(); } // Switch from freefall mode to homing mode if (ticks == info.HomingActivationDelay + 1) { state = States.Homing; speed = velocity.Length; // Compute the vertical loop radius loopRadius = LoopRadius(speed, info.VerticalRateOfTurn); } // Switch from homing mode to freefall mode if (info.RangeLimit != 0 && ticks == info.RangeLimit + 1) { state = States.Freefall; velocity = new WVec(0, -speed, 0) .Rotate(new WRot(WAngle.FromFacing(vFacing), WAngle.Zero, WAngle.Zero)) .Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(hFacing))); } // Check if target position should be updated (actor visible & locked on) var newTarPos = targetPosition; if (args.GuidedTarget.IsValidFor(args.SourceActor) && lockOn) { newTarPos = args.GuidedTarget.CenterPosition + new WVec(WDist.Zero, WDist.Zero, info.AirburstAltitude); } // Compute target's predicted velocity vector (assuming uniform circular motion) var fac1 = OpenRA.Traits.Util.GetFacing(tarVel, hFacing); tarVel = newTarPos - targetPosition; var fac2 = OpenRA.Traits.Util.GetFacing(tarVel, hFacing); predVel = tarVel.Rotate(WRot.FromFacing(fac2 - fac1)); targetPosition = newTarPos; // Compute current distance from target position var tarDistVec = targetPosition + offset - pos; var relTarDist = tarDistVec.Length; var relTarHorDist = tarDistVec.HorizontalLength; WVec move; if (state == States.Freefall) { move = FreefallTick(); } else { move = HomingTick(world, tarDistVec, relTarHorDist); } renderFacing = WAngle.ArcTan(move.Z - move.Y, move.X).Angle / 4 - 64; // Move the missile pos += move; // Create the smoke trail effect if (!string.IsNullOrEmpty(info.TrailImage) && --ticksToNextSmoke < 0 && (state != States.Freefall || info.TrailWhenDeactivated)) { world.AddFrameEndTask(w => w.Add(new Smoke(w, pos - 3 * move / 2, info.TrailImage, trailPalette, info.TrailSequence))); ticksToNextSmoke = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(pos); } var cell = world.Map.CellContaining(pos); // NOTE: High speeds might cause the missile to miss the target or fly through obstacles // In that case, big moves should probably be decomposed into multiple smaller ones with hit checks var height = world.Map.DistanceAboveTerrain(pos); var shouldExplode = (height.Length < 0) || // Hit the ground (relTarDist < info.CloseEnough.Length) || // Within range (info.ExplodeWhenEmpty && info.RangeLimit != 0 && ticks > info.RangeLimit) || // Ran out of fuel (info.Blockable && BlocksProjectiles.AnyBlockingActorAt(world, pos)) || // Hit a wall or other blocking obstacle !world.Map.Contains(cell) || // This also avoids an IndexOutOfRangeException in GetTerrainInfo below. (!string.IsNullOrEmpty(info.BoundToTerrainType) && world.Map.GetTerrainInfo(cell).Type != info.BoundToTerrainType) || // Hit incompatible terrain (height.Length < info.AirburstAltitude.Length && relTarHorDist < info.CloseEnough.Length); // Airburst if (shouldExplode) { Explode(world); } }
CPos PickTargetLocation() { var target = self.CenterPosition + new WVec(0, -1024 * effectiveMoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); var targetCell = self.World.Map.CellContaining(target); if (!self.World.Map.Contains(targetCell)) { if (++ticksIdle % info.ReduceMoveRadiusDelay == 0) { effectiveMoveRadius--; } return(CPos.Zero); } ticksIdle = 0; effectiveMoveRadius = info.WanderMoveRadius; return(targetCell); }
public Pair <Actor[], Actor[]> SendParatroopers(Actor self, WPos target, int facing = -1) { var aircraft = new List <Actor>(); var units = new List <Actor>(); var info = Info as ParatroopersPowerInfo; if (facing < 0) { facing = 256 * self.World.SharedRandom.Next(info.QuantizedFacings) / info.QuantizedFacings; } var utLower = info.UnitType.ToLowerInvariant(); ActorInfo unitType; if (!self.World.Map.Rules.Actors.TryGetValue(utLower, out unitType)) { throw new YamlException("Actors ruleset does not include the entry '{0}'".F(utLower)); } var altitude = unitType.TraitInfo <AircraftInfo>().CruiseAltitude.Length; var dropRotation = WRot.FromFacing(facing); var delta = new WVec(0, -1024, 0).Rotate(dropRotation); target = target + new WVec(0, 0, altitude); var startEdge = target - (self.World.Map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; var finishEdge = target + (self.World.Map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; Actor camera = null; Beacon beacon = null; var aircraftInRange = new Dictionary <Actor, bool>(); Action <Actor> onEnterRange = a => { // Spawn a camera and remove the beacon when the first plane enters the target area if (info.CameraActor != null && camera == null && !aircraftInRange.Any(kv => kv.Value)) { self.World.AddFrameEndTask(w => { camera = w.CreateActor(info.CameraActor, new TypeDictionary { new LocationInit(self.World.Map.CellContaining(target)), new OwnerInit(self.Owner), }); }); } RemoveBeacon(beacon); if (!aircraftInRange.Any(kv => kv.Value)) { Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.ReinforcementsArrivedSpeechNotification, self.Owner.Faction.InternalName); } aircraftInRange[a] = true; }; Action <Actor> onExitRange = a => { aircraftInRange[a] = false; // Remove the camera when the final plane leaves the target area if (!aircraftInRange.Any(kv => kv.Value)) { RemoveCamera(camera); } }; Action <Actor> onRemovedFromWorld = a => { aircraftInRange[a] = false; // Checking for attack range is not relevant here because // aircraft may be shot down before entering. Thus we remove // the camera and beacon only if the whole squad is dead. if (aircraftInRange.All(kv => kv.Key.IsDead)) { RemoveCamera(camera); RemoveBeacon(beacon); } }; // Create the actors immediately so they can be returned for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (info.SquadSize & 1) == 0) { continue; } // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(dropRotation); aircraft.Add(self.World.CreateActor(false, info.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(facing), })); } foreach (var p in info.DropItems) { units.Add(self.World.CreateActor(false, p.ToLowerInvariant(), new TypeDictionary { new OwnerInit(self.Owner) })); } self.World.AddFrameEndTask(w => { PlayLaunchSounds(); Actor distanceTestActor = null; var passengersPerPlane = (info.DropItems.Length + info.SquadSize - 1) / info.SquadSize; var added = 0; var j = 0; for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (info.SquadSize & 1) == 0) { continue; } // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(dropRotation); var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(dropRotation); var a = aircraft[j++]; w.Add(a); var drop = a.Trait <ParaDrop>(); drop.SetLZ(w.Map.CellContaining(target + targetOffset), !info.AllowImpassableCells); drop.OnEnteredDropRange += onEnterRange; drop.OnExitedDropRange += onExitRange; drop.OnRemovedFromWorld += onRemovedFromWorld; var cargo = a.Trait <Cargo>(); foreach (var unit in units.Skip(added).Take(passengersPerPlane)) { cargo.Load(a, unit); added++; } a.QueueActivity(new Fly(a, Target.FromPos(target + spawnOffset))); a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset))); a.QueueActivity(new RemoveSelf()); aircraftInRange.Add(a, false); distanceTestActor = a; } // Dispose any unused units for (var i = added; i < units.Count; i++) { units[i].Dispose(); } if (Info.DisplayBeacon) { var distance = (target - startEdge).HorizontalLength; beacon = new Beacon( self.Owner, target - new WVec(0, 0, altitude), Info.BeaconPaletteIsPlayerPalette, Info.BeaconPalette, Info.BeaconImage, Info.BeaconPoster, Info.BeaconPosterPalette, Info.BeaconSequence, Info.ArrowSequence, Info.CircleSequence, Info.ClockSequence, () => 1 - ((distanceTestActor.CenterPosition - target).HorizontalLength - info.BeaconDistanceOffset.Length) * 1f / distance, Info.BeaconDelay); w.Add(beacon); } }); return(Pair.New(aircraft.ToArray(), units.ToArray())); }
public void Tick(World world) { // Fade the trail out gradually if (exploded && info.ContrailLength > 0) { trail.Update(pos); return; } ticks++; anim.Tick(); // Missile tracks target if (args.GuidedTarget.IsValidFor(args.SourceActor)) { targetPosition = args.GuidedTarget.CenterPosition; } var dist = targetPosition + offset - pos; var desiredFacing = Traits.Util.GetFacing(dist, facing); var desiredAltitude = targetPosition.Z; var jammed = info.Jammable && world.ActorsWithTrait <JamsMissiles>().Any(j => JammedBy(j)); if (jammed) { desiredFacing = facing + world.SharedRandom.Next(-20, 21); desiredAltitude = world.SharedRandom.Next(-43, 86); } else if (!args.GuidedTarget.IsValidFor(args.SourceActor)) { desiredFacing = facing; } facing = Traits.Util.TickFacing(facing, desiredFacing, info.ROT); var move = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing)) * speed / 1024; if (targetPosition.Z > 0 && info.TurboBoost) { move = (move * 3) / 2; } if (pos.Z != desiredAltitude) { var delta = move.HorizontalLength * info.MaximumPitch.Tan() / 1024; var dz = (targetPosition.Z - pos.Z).Clamp(-delta, delta); move += new WVec(0, 0, dz); } pos += move; if (info.Trail != null && --ticksToNextSmoke < 0) { world.AddFrameEndTask(w => w.Add(new Smoke(w, pos - 3 * move / 2, info.Trail))); ticksToNextSmoke = info.TrailInterval; } if (info.ContrailLength > 0) { trail.Update(pos); } var shouldExplode = (pos.Z < 0) || // Hit the ground (dist.LengthSquared < MissileCloseEnough.Range * MissileCloseEnough.Range) || // Within range (info.RangeLimit != 0 && ticks > info.RangeLimit) || // Ran out of fuel (!info.High && world.ActorMap.GetUnitsAt(pos.ToCPos()) .Any(a => a.HasTrait <IBlocksBullets>())); // Hit a wall if (shouldExplode) { Explode(world); } }
public override void DoImpact(Target target, WarheadArgs args) { var firedBy = args.SourceActor; if (!target.IsValidFor(firedBy)) { return; } var world = firedBy.World; var map = world.Map; if (target.Type == TargetType.Invalid) { return; } var pos = target.CenterPosition; var actorAtImpact = ImpactActors ? ActorTypeAtImpact(world, pos, firedBy) : ImpactActorType.None; // If there's either a) an invalid actor, or b) no actor and invalid terrain, we don't trigger the effect(s). if (actorAtImpact == ImpactActorType.Invalid) { return; } else if (actorAtImpact == ImpactActorType.None && !IsValidAgainstTerrain(world, pos)) { return; } var epicenter = AroundTarget && args.WeaponTarget.Type != TargetType.Invalid ? args.WeaponTarget.CenterPosition : target.CenterPosition; var amount = Amount.Length == 2 ? world.SharedRandom.Next(Amount[0], Amount[1]) : Amount[0]; var offset = 256 / amount; for (var i = 0; i < amount; i++) { Target radiusTarget = Target.Invalid; var rotation = WRot.FromFacing(i * offset); var targetpos = epicenter + new WVec(weapon.Range.Length, 0, 0).Rotate(rotation); var tpos = Target.FromPos(new WPos(targetpos.X, targetpos.Y, map.CenterOfCell(map.CellContaining(targetpos)).Z)); if (weapon.IsValidAgainst(tpos, firedBy.World, firedBy)) { radiusTarget = tpos; } if (radiusTarget.Type == TargetType.Invalid) { continue; } var projectileArgs = new ProjectileArgs { Weapon = weapon, Facing = (radiusTarget.CenterPosition - target.CenterPosition).Yaw, CurrentMuzzleFacing = () => (radiusTarget.CenterPosition - target.CenterPosition).Yaw, DamageModifiers = args.DamageModifiers, InaccuracyModifiers = !firedBy.IsDead ? firedBy.TraitsImplementing <IInaccuracyModifier>() .Select(a => a.GetInaccuracyModifier()).ToArray() : new int[0], RangeModifiers = !firedBy.IsDead ? firedBy.TraitsImplementing <IRangeModifier>() .Select(a => a.GetRangeModifier()).ToArray() : new int[0], Source = target.CenterPosition, CurrentSource = () => target.CenterPosition, SourceActor = firedBy, GuidedTarget = radiusTarget, PassiveTarget = radiusTarget.CenterPosition }; if (projectileArgs.Weapon.Projectile != null) { var projectile = projectileArgs.Weapon.Projectile.Create(projectileArgs); if (projectile != null) { firedBy.World.AddFrameEndTask(w => w.Add(projectile)); } if (projectileArgs.Weapon.Report != null && projectileArgs.Weapon.Report.Any()) { Game.Sound.Play(SoundType.World, projectileArgs.Weapon.Report.Random(firedBy.World.SharedRandom), target.CenterPosition); } } } }
public void SendDropPods(Actor self, Order order, int podFacing) { var actorInfo = self.World.Map.Rules.Actors[info.UnitTypes.First().ToLowerInvariant()]; var aircraftInfo = actorInfo.TraitInfo <AircraftInfo>(); var altitude = aircraftInfo.CruiseAltitude.Length; var approachRotation = WRot.FromFacing(podFacing); var fallsToEarthInfo = actorInfo.TraitInfo <FallsToEarthInfo>(); var delta = new WVec(0, -altitude * aircraftInfo.Speed / fallsToEarthInfo.Velocity.Length, 0).Rotate(approachRotation); self.World.AddFrameEndTask(w => { var target = order.Target.CenterPosition; var targetCell = self.World.Map.CellContaining(target); var podLocations = self.World.Map.FindTilesInCircle(targetCell, info.PodScatter) .Where(c => aircraftInfo.LandableTerrainTypes.Contains(w.Map.GetTerrainInfo(c).Type) && !self.World.ActorMap.GetActorsAt(c).Any()); if (!podLocations.Any()) { return; } if (info.CameraActor != null) { var camera = w.CreateActor(info.CameraActor, new TypeDictionary { new LocationInit(targetCell), new OwnerInit(self.Owner), }); camera.QueueActivity(new Wait(info.CameraRemoveDelay)); camera.QueueActivity(new RemoveSelf()); } PlayLaunchSounds(); var drops = self.World.SharedRandom.Next(info.Drops.X, info.Drops.Y); for (var i = 0; i < drops; i++) { var unitType = info.UnitTypes.Random(self.World.SharedRandom); var podLocation = podLocations.Random(self.World.SharedRandom); var podTarget = Target.FromCell(w, podLocation); var location = self.World.Map.CenterOfCell(podLocation) - delta + new WVec(0, 0, altitude); var pod = w.CreateActor(false, unitType, new TypeDictionary { new CenterPositionInit(location), new OwnerInit(self.Owner), new FacingInit(podFacing) }); var aircraft = pod.Trait <Aircraft>(); if (!aircraft.CanLand(podLocation)) { pod.Dispose(); } else { w.Add(new DropPodImpact(self.Owner, info.WeaponInfo, w, location, podTarget, info.WeaponDelay, info.EntryEffect, info.EntryEffectSequence, info.EntryEffectPalette)); w.Add(pod); } } }); }
public override bool Tick(Actor self) { if (IsCanceling || target.Type == TargetType.Invalid) { if (landingInitiated) { // We must return the actor to a sensible height before continuing. // If the aircraft lands when idle and is idle, continue landing, // otherwise climb back to CruiseAltitude. // TODO: Remove this after fixing all activities to work properly with arbitrary starting altitudes. var shouldLand = aircraft.Info.IdleBehavior == IdleBehaviorType.Land; var continueLanding = shouldLand && self.CurrentActivity.IsCanceling && self.CurrentActivity.NextActivity == null; if (!continueLanding) { var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); if (dat > aircraft.LandAltitude && dat < aircraft.Info.CruiseAltitude) { QueueChild(new TakeOff(self)); return(false); } aircraft.RemoveInfluence(); return(true); } } else { return(true); } } var pos = aircraft.GetPosition(); // Reevaluate target position in case the target has moved. targetPosition = target.CenterPosition + offset; landingCell = self.World.Map.CellContaining(targetPosition); // We are already at the landing location. if ((targetPosition - pos).LengthSquared == 0) { return(true); } // Look for free landing cell if (target.Type == TargetType.Terrain && !landingInitiated) { var newLocation = aircraft.FindLandingLocation(landingCell, landRange); // Cannot land so fly towards the last target location instead. if (!newLocation.HasValue) { QueueChild(aircraft.MoveTo(landingCell, 0)); return(true); } if (newLocation.Value != landingCell) { target = Target.FromCell(self.World, newLocation.Value); targetPosition = target.CenterPosition + offset; landingCell = self.World.Map.CellContaining(targetPosition); } } // Move towards landing location/facing if (aircraft.Info.VTOL) { if ((pos - targetPosition).HorizontalLengthSquared != 0) { QueueChild(new Fly(self, Target.FromPos(targetPosition))); return(false); } else if (desiredFacing != -1 && desiredFacing != aircraft.Facing) { QueueChild(new Turn(self, desiredFacing)); return(false); } } if (!aircraft.Info.VTOL && !finishedApproach) { // Calculate approach trajectory var altitude = aircraft.Info.CruiseAltitude.Length; // Distance required for descent. var landDistance = altitude * 1024 / aircraft.Info.MaximumPitch.Tan(); // Approach landing from the opposite direction of the desired facing // TODO: Calculate sensible trajectory without preferred facing. var rotation = WRot.Zero; if (desiredFacing != -1) { rotation = WRot.FromFacing(desiredFacing); } var approachStart = targetPosition + new WVec(0, landDistance, altitude).Rotate(rotation); // Add 10% to the turning radius to ensure we have enough room var speed = aircraft.MovementSpeed * 32 / 35; var turnRadius = Fly.CalculateTurnRadius(speed, aircraft.Info.TurnSpeed); // Find the center of the turning circles for clockwise and counterclockwise turns var angle = WAngle.FromFacing(aircraft.Facing); var fwd = -new WVec(angle.Sin(), angle.Cos(), 0); // Work out whether we should turn clockwise or counter-clockwise for approach var side = new WVec(-fwd.Y, fwd.X, fwd.Z); var approachDelta = self.CenterPosition - approachStart; var sideTowardBase = new[] { side, -side } .MinBy(a => WVec.Dot(a, approachDelta)); // Calculate the tangent line that joins the turning circles at the current and approach positions var cp = self.CenterPosition + turnRadius * sideTowardBase / 1024; var posCenter = new WPos(cp.X, cp.Y, altitude); var approachCenter = approachStart + new WVec(0, turnRadius * Math.Sign(self.CenterPosition.Y - approachStart.Y), 0); var tangentDirection = approachCenter - posCenter; var tangentLength = tangentDirection.Length; var tangentOffset = WVec.Zero; if (tangentLength != 0) { tangentOffset = new WVec(-tangentDirection.Y, tangentDirection.X, 0) * turnRadius / tangentLength; } // TODO: correctly handle CCW <-> CW turns if (tangentOffset.X > 0) { tangentOffset = -tangentOffset; } var w1 = posCenter + tangentOffset; var w2 = approachCenter + tangentOffset; var w3 = approachStart; turnRadius = Fly.CalculateTurnRadius(aircraft.Info.Speed, aircraft.Info.TurnSpeed); // Move along approach trajectory. QueueChild(new Fly(self, Target.FromPos(w1), WDist.Zero, new WDist(turnRadius * 3))); QueueChild(new Fly(self, Target.FromPos(w2))); // Fix a problem when the airplane is sent to land near the landing cell QueueChild(new Fly(self, Target.FromPos(w3), WDist.Zero, new WDist(turnRadius / 2))); finishedApproach = true; return(false); } if (!landingInitiated) { var blockingCells = clearCells.Append(landingCell); if (!aircraft.CanLand(blockingCells, target.Actor)) { // Maintain holding pattern. QueueChild(new FlyIdle(self, 25)); self.NotifyBlocker(blockingCells); finishedApproach = false; return(false); } if (aircraft.Info.LandingSounds.Length > 0) { Game.Sound.Play(SoundType.World, aircraft.Info.LandingSounds, self.World, aircraft.CenterPosition); } aircraft.AddInfluence(landingCell); aircraft.EnteringCell(self); landingInitiated = true; } // Final descent. if (aircraft.Info.VTOL) { var landAltitude = self.World.Map.DistanceAboveTerrain(targetPosition) + aircraft.LandAltitude; if (Fly.VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, landAltitude)) { return(false); } return(true); } var d = targetPosition - pos; // The next move would overshoot, so just set the final position var move = aircraft.FlyStep(aircraft.Facing); if (d.HorizontalLengthSquared < move.HorizontalLengthSquared) { var landingAltVec = new WVec(WDist.Zero, WDist.Zero, aircraft.LandAltitude); aircraft.SetPosition(self, targetPosition + landingAltVec); return(true); } var landingAlt = self.World.Map.DistanceAboveTerrain(targetPosition) + aircraft.LandAltitude; Fly.FlyTick(self, aircraft, d.Yaw.Facing, landingAlt); return(false); }
public void SendAirstrike(Actor self, WPos target, bool randomize = true, int attackFacing = 0) { if (randomize) { attackFacing = 256 * self.World.SharedRandom.Next(info.QuantizedFacings) / info.QuantizedFacings; } var altitude = self.World.Map.Rules.Actors[info.UnitType].TraitInfo <AircraftInfo>().CruiseAltitude.Length; var attackRotation = WRot.FromFacing(attackFacing); var delta = new WVec(0, -1024, 0).Rotate(attackRotation); target = target + new WVec(0, 0, altitude); var startEdge = target - (self.World.Map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; var finishEdge = target + (self.World.Map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; Actor camera = null; Beacon beacon = null; var aircraftInRange = new Dictionary <Actor, bool>(); Action <Actor> onEnterRange = a => { // Spawn a camera and remove the beacon when the first plane enters the target area if (info.CameraActor != null && camera == null && !aircraftInRange.Any(kv => kv.Value)) { self.World.AddFrameEndTask(w => { camera = w.CreateActor(info.CameraActor, new TypeDictionary { new LocationInit(self.World.Map.CellContaining(target)), new OwnerInit(self.Owner), }); }); } RemoveBeacon(beacon); aircraftInRange[a] = true; }; Action <Actor> onExitRange = a => { aircraftInRange[a] = false; // Remove the camera when the final plane leaves the target area if (!aircraftInRange.Any(kv => kv.Value)) { RemoveCamera(camera); } }; Action <Actor> onRemovedFromWorld = a => { aircraftInRange[a] = false; // Checking for attack range is not relevant here because // aircraft may be shot down before entering. Thus we remove // the camera and beacon only if the whole squad is dead. if (aircraftInRange.All(kv => !kv.Key.IsInWorld)) { RemoveCamera(camera); RemoveBeacon(beacon); } }; self.World.AddFrameEndTask(w => { PlayLaunchSounds(); Actor distanceTestActor = null; for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (info.SquadSize & 1) == 0) { continue; } // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(attackRotation); var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(attackRotation); var a = w.CreateActor(info.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(attackFacing), }); var attack = a.Trait <AttackBomberCA>(); attack.SetTarget(w, target + targetOffset); a.QueueActivity(new Fly(a, Target.FromPos(target + spawnOffset))); attack.OnEnteredAttackRange += onEnterRange; a.QueueActivity(new AttackMoveActivity(a, () => new FlyIdle(a, info.GuardDuration))); attack.OnExitedAttackRange += onExitRange; a.QueueActivity(new FlyOffMap(a)); a.QueueActivity(new RemoveSelf()); aircraftInRange.Add(a, false); distanceTestActor = a; } if (Info.DisplayBeacon) { var distance = (target - startEdge).HorizontalLength; beacon = new Beacon( self.Owner, target - new WVec(0, 0, altitude), Info.BeaconPaletteIsPlayerPalette, Info.BeaconPalette, Info.BeaconImage, Info.BeaconPoster, Info.BeaconPosterPalette, Info.BeaconSequence, Info.ArrowSequence, Info.CircleSequence, Info.ClockSequence, () => 1 - ((distanceTestActor.CenterPosition - target).HorizontalLength - info.BeaconDistanceOffset.Length) * 1f / distance, Info.BeaconDelay); w.Add(beacon); } }); }
public VortexProjectile(VortexProjectileInfo info, ProjectileArgs args) { this.info = info; this.args = args; sourcepos = args.Source; var firedBy = args.SourceActor; world = args.SourceActor.World; if (info.Speed.Length > 1) { speed = new WDist(world.SharedRandom.Next(info.Speed[0].Length, info.Speed[1].Length)); } else { speed = info.Speed[0]; } targetpos = GetTargetPos(); mindelay = args.Weapon.MinRange.Length / speed.Length; projectiles = new VortexProjectileEffect[info.NumProjectiles]; var mainFacing = (targetpos - sourcepos).Yaw; // used for lerping projectiles at the same pace var estimatedLifespan = Math.Max(args.Weapon.Range.Length / speed.Length, 1); // target that will be assigned Target target; // subprojectiles facing int slice = 256 / info.NumProjectiles; for (int i = 0; i < info.NumProjectiles; i++) { target = Target.FromPos(targetpos); // If it's true then lifespan is counted from source position to target instead of max range. lifespan = info.KillProjectilesWhenReachedTargetLocation ? Math.Max((args.PassiveTarget - args.Source).Length / speed.Length, 1) : estimatedLifespan; var facing = mainFacing + (new WAngle(i * slice)); var newRotation = WRot.FromFacing(facing.Angle); var rotatedTarget = (targetpos - sourcepos).Rotate(newRotation); var dx = rotatedTarget.X - sourcepos.X; var dy = rotatedTarget.Y - sourcepos.Y; var normal = new WVec(-dy, dx, 0); target = Target.FromPos(sourcepos + rotatedTarget); var normalizedVec = WVec.Lerp(WVec.Zero, rotatedTarget, 512, rotatedTarget.Length); var projectileArgs = new VortexProjectileArgs { Weapon = args.Weapon, DamageModifiers = args.DamageModifiers, Facing = facing, Source = sourcepos, CurrentSource = () => sourcepos, SourceActor = firedBy, GuidedTarget = target, PassiveTarget = sourcepos + rotatedTarget, VecNormalized = normalizedVec, Normal = normal }; projectiles[i] = new VortexProjectileEffect(info, projectileArgs, lifespan, estimatedLifespan); } foreach (var p in projectiles) { world.AddFrameEndTask(w => w.Add(p)); } }
public override Activity Tick(Actor self) { // Refuse to take off if it would land immediately again. if (aircraft.ForceLanding) { Cancel(self); } if (IsCanceling) { return(NextActivity); } bool targetIsHiddenActor; target = target.Recalculate(self.Owner, out targetIsHiddenActor); if (!targetIsHiddenActor && target.Type == TargetType.Actor) { lastVisibleTarget = Target.FromTargetPositions(target); } var oldUseLastVisibleTarget = useLastVisibleTarget; useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self); // Update target lines if required if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue) { self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false); } // Target is hidden or dead, and we don't have a fallback position to move towards if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self)) { return(NextActivity); } var pos = self.CenterPosition; var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target; if (!soundPlayed && aircraft.Info.TakeoffSounds.Length > 0 && self.IsAtGroundLevel()) { Game.Sound.Play(SoundType.World, aircraft.Info.TakeoffSounds, self.World, aircraft.CenterPosition); soundPlayed = true; } // We are taking off, so remove influence in ground cells. if (self.IsAtGroundLevel()) { aircraft.RemoveInfluence(); } // Inside the target annulus, so we're done var insideMaxRange = maxRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, maxRange); var insideMinRange = minRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, minRange); if (insideMaxRange && !insideMinRange) { return(NextActivity); } var delta = checkTarget.CenterPosition - self.CenterPosition; // The next move would overshoot, so consider it close enough var move = aircraft.FlyStep(aircraft.Facing); if (delta.HorizontalLengthSquared < move.HorizontalLengthSquared) { return(NextActivity); } // Don't turn until we've reached the cruise altitude var desiredFacing = delta.Yaw.Facing; var targetAltitude = aircraft.CenterPosition.Z + aircraft.Info.CruiseAltitude.Length - self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length; if (aircraft.CenterPosition.Z < targetAltitude) { desiredFacing = aircraft.Facing; } else { // Using the turn rate, compute a hypothetical circle traced by a continuous turn. // If it contains the destination point, it's unreachable without more complex manuvering. var turnRadius = CalculateTurnRadius(aircraft.MovementSpeed, aircraft.TurnSpeed); // The current facing is a tangent of the minimal turn circle. // Make a perpendicular vector, and use it to locate the turn's center. var turnCenterFacing = aircraft.Facing; turnCenterFacing += Util.GetNearestFacing(aircraft.Facing, desiredFacing) > 0 ? 64 : -64; var turnCenterDir = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(turnCenterFacing)); turnCenterDir *= turnRadius; turnCenterDir /= 1024; // Compare with the target point, and keep flying away if it's inside the circle. var turnCenter = aircraft.CenterPosition + turnCenterDir; if ((checkTarget.CenterPosition - turnCenter).HorizontalLengthSquared < turnRadius * turnRadius) { desiredFacing = aircraft.Facing; } } FlyToward(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude); return(this); }
public override Activity Tick(Actor self) { if (innerActivity != null) { innerActivity = ActivityUtils.RunActivity(self, innerActivity); return(this); } if (IsCanceled) { return(NextActivity); } if ((carryall.State == Carryall.CarryallState.Idle || carryall.Carryable.IsDead) && state != State.TakeOff) { state = State.Aborted; } switch (state) { case State.Transport: { var targetLocation = FindDropLocation(destination, carryall.Info.DropRange); // Can't land, so wait at the target until something changes if (!targetLocation.HasValue) { innerActivity = ActivityUtils.SequenceActivities( new HeliFly(self, Target.FromCell(self.World, destination)), new Wait(25)); return(this); } var targetPosition = self.World.Map.CenterOfCell(targetLocation.Value); var localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, self.Orientation)); var carryablePosition = self.CenterPosition + body.LocalToWorld(localOffset); if ((carryablePosition - targetPosition).HorizontalLengthSquared != 0) { // For non-zero offsets the drop position depends on the carryall facing // We therefore need to predict/correct for the facing *at the drop point* if (carryall.CarryableOffset.HorizontalLengthSquared != 0) { var facing = (targetPosition - self.CenterPosition).Yaw.Facing; localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, WRot.FromFacing(facing))); innerActivity = ActivityUtils.SequenceActivities( new HeliFly(self, Target.FromPos(targetPosition - body.LocalToWorld(localOffset))), new Turn(self, facing)); return(this); } innerActivity = new HeliFly(self, Target.FromPos(targetPosition)); return(this); } state = State.Land; return(this); } case State.Land: { if (!CanDropHere()) { state = State.Transport; return(this); } // Make sure that the carried actor is on the ground before releasing it var localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, self.Orientation)); var carryablePosition = self.CenterPosition + body.LocalToWorld(localOffset); if (self.World.Map.DistanceAboveTerrain(carryablePosition) != WDist.Zero) { innerActivity = new HeliLand(self, false, -new WDist(carryall.CarryableOffset.Z)); return(this); } state = carryall.Info.UnloadingDelay > 0 ? State.Wait : State.Release; return(this); } case State.Wait: state = State.Release; innerActivity = new Wait(carryall.Info.UnloadingDelay, false); return(this); case State.Release: if (!CanDropHere()) { state = State.Transport; return(this); } Release(); state = State.TakeOff; return(this); case State.TakeOff: return(ActivityUtils.SequenceActivities(new HeliFly(self, Target.FromPos(self.CenterPosition)), NextActivity)); case State.Aborted: carryall.UnreserveCarryable(self); break; } return(NextActivity); }
public WarheadTrailProjectile(WarheadTrailProjectileInfo info, ProjectileArgs args) { this.info = info; this.args = args; projectilepos = args.Source; sourcepos = args.Source; var firedBy = args.SourceActor; world = args.SourceActor.World; if (info.Speed.Length > 1) { speed = new WDist(world.SharedRandom.Next(info.Speed[0].Length, info.Speed[1].Length)); } else { speed = info.Speed[0]; } targetpos = GetTargetPos(); mindelay = args.Weapon.MinRange.Length / speed.Length; projectiles = new WarheadTrailProjectileEffect[info.Offsets.Count()]; var range = Common.Util.ApplyPercentageModifiers(args.Weapon.Range.Length, args.RangeModifiers); var mainFacing = (targetpos - sourcepos).Yaw.Facing + 64; // used for lerping projectiles at the same pace var estimatedLifespan = Math.Max(args.Weapon.Range.Length / speed.Length, 1); // target that will be assigned Target target; for (int i = 0; i < info.Offsets.Count(); i++) { switch (info.FireMode) { case FireMode.Focus: offsetRotation = WRot.FromFacing(mainFacing); offsetTargetPos = sourcepos + new WVec(range, 0, 0).Rotate(offsetRotation); offsetSourcePos = sourcepos + info.Offsets[i].Rotate(offsetRotation); break; case FireMode.Line: offsetRotation = WRot.FromFacing(mainFacing); offsetTargetPos = sourcepos + new WVec(range + info.Offsets[i].X, info.Offsets[i].Y, info.Offsets[i].Z).Rotate(offsetRotation); offsetSourcePos = sourcepos + info.Offsets[i].Rotate(offsetRotation); break; case FireMode.Spread: offsetRotation = WRot.FromFacing(info.Offsets[i].Yaw.Facing - 64) + WRot.FromFacing(mainFacing); offsetSourcePos = sourcepos + info.Offsets[i].Rotate(offsetRotation); offsetTargetPos = sourcepos + new WVec(range + info.Offsets[i].X, info.Offsets[i].Y, info.Offsets[i].Z).Rotate(offsetRotation); break; } if (info.Inaccuracy.Length > 0) { var inaccuracy = Common.Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers); var maxOffset = inaccuracy * (args.PassiveTarget - projectilepos).Length / range; var inaccuracyOffset = WVec.FromPDF(world.SharedRandom, 2) * maxOffset / 1024; offsetTargetPos += inaccuracyOffset; } target = Target.FromPos(offsetTargetPos); // If it's true then lifespan is counted from source position to target instead of max range. lifespan = info.KillProjectilesWhenReachedTargetLocation ? Math.Max((args.PassiveTarget - args.Source).Length / speed.Length, 1) : estimatedLifespan; var facing = (offsetTargetPos - offsetSourcePos).Yaw; var projectileArgs = new ProjectileArgs { Weapon = info.WeaponInfo, DamageModifiers = args.DamageModifiers, Facing = facing, Source = offsetSourcePos, CurrentSource = () => offsetSourcePos, SourceActor = firedBy, GuidedTarget = target, PassiveTarget = target.CenterPosition }; projectiles[i] = new WarheadTrailProjectileEffect(info, projectileArgs, lifespan, estimatedLifespan, info.ForceAtGroundLevel); } foreach (var p in projectiles) { world.AddFrameEndTask(w => w.Add(p)); } }
public void Tick(World world) { ticks++; anim.Tick(); // Missile tracks target if (args.GuidedTarget.IsValidFor(args.SourceActor) && lockOn) { targetPosition = args.GuidedTarget.CenterPosition; } var dist = targetPosition + offset - pos; var desiredFacing = OpenRA.Traits.Util.GetFacing(dist, facing); var desiredAltitude = targetPosition.Z; var jammed = info.Jammable && world.ActorsWithTrait <JamsMissiles>().Any(JammedBy); if (jammed) { desiredFacing = facing + world.SharedRandom.Next(-20, 21); desiredAltitude = world.SharedRandom.Next(-43, 86); } else if (!args.GuidedTarget.IsValidFor(args.SourceActor)) { desiredFacing = facing; } facing = OpenRA.Traits.Util.TickFacing(facing, desiredFacing, info.ROT); var move = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing)) * info.Speed.Range / 1024; if (targetPosition.Z > 0 && info.TurboBoost) { move = (move * 3) / 2; } if (pos.Z != desiredAltitude) { var delta = move.HorizontalLength * info.MaximumPitch.Tan() / 1024; var dz = (targetPosition.Z - pos.Z).Clamp(-delta, delta); move += new WVec(0, 0, dz); } pos += move; if (info.Trail != null && --ticksToNextSmoke < 0) { world.AddFrameEndTask(w => w.Add(new Smoke(w, pos - 3 * move / 2, info.Trail))); ticksToNextSmoke = info.TrailInterval; } if (info.ContrailLength > 0) { trail.Update(pos); } var cell = world.Map.CellContaining(pos); var shouldExplode = (pos.Z < 0) || // Hit the ground (dist.LengthSquared < info.CloseEnough.Range * info.CloseEnough.Range) || // Within range (info.RangeLimit != 0 && ticks > info.RangeLimit) || // Ran out of fuel (!info.High && world.ActorMap.GetUnitsAt(cell).Any(a => a.HasTrait <IBlocksBullets>())) || // Hit a wall !world.Map.Contains(cell) || // This also avoids an IndexOutOfRangeException in GetTerrainInfo below. (!string.IsNullOrEmpty(info.BoundToTerrainType) && world.Map.GetTerrainInfo(cell).Type != info.BoundToTerrainType); // Hit incompatible terrain if (shouldExplode) { Explode(world); } }
public override void Activate(Actor self, Order order, SupportPowerManager manager) { base.Activate(self, order, manager); var info = Info as AirstrikePowerInfo; var attackFacing = Util.QuantizeFacing(self.World.SharedRandom.Next(256), info.QuantizedFacings) * (256 / info.QuantizedFacings); var attackRotation = WRot.FromFacing(attackFacing); var delta = new WVec(0, -1024, 0).Rotate(attackRotation); var altitude = self.World.Map.Rules.Actors[info.UnitType].Traits.Get <PlaneInfo>().CruiseAltitude.Range; var target = order.TargetLocation.CenterPosition + new WVec(0, 0, altitude); var startEdge = target - (self.World.DistanceToMapEdge(target, -delta) + info.Cordon).Range * delta / 1024; var finishEdge = target + (self.World.DistanceToMapEdge(target, delta) + info.Cordon).Range * delta / 1024; Actor flare = null; Actor camera = null; Beacon beacon = null; Dictionary <Actor, bool> aircraftInRange = new Dictionary <Actor, bool>(); Action <Actor> onEnterRange = a => { // Spawn a camera and remove the beacon when the first plane enters the target area if (info.CameraActor != null && !aircraftInRange.Any(kv => kv.Value)) { self.World.AddFrameEndTask(w => { camera = w.CreateActor(info.CameraActor, new TypeDictionary { new LocationInit(order.TargetLocation), new OwnerInit(self.Owner), }); }); } if (beacon != null) { self.World.AddFrameEndTask(w => { w.Remove(beacon); beacon = null; }); } aircraftInRange[a] = true; }; Action <Actor> onExitRange = a => { aircraftInRange[a] = false; // Remove the camera and flare when the final plane leaves the target area if (!aircraftInRange.Any(kv => kv.Value)) { if (camera != null) { camera.QueueActivity(new Wait(info.CameraRemoveDelay)); camera.QueueActivity(new RemoveSelf()); } if (flare != null) { flare.QueueActivity(new Wait(info.FlareRemoveDelay)); flare.QueueActivity(new RemoveSelf()); } camera = flare = null; } }; self.World.AddFrameEndTask(w => { if (info.FlareActor != null) { flare = w.CreateActor(info.FlareActor, new TypeDictionary { new LocationInit(order.TargetLocation), new OwnerInit(self.Owner), }); } var notification = self.Owner.IsAlliedWith(self.World.RenderPlayer) ? Info.LaunchSound : Info.IncomingSound; Sound.Play(notification); Actor distanceTestActor = null; for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (info.SquadSize & 1) == 0) { continue; } // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(attackRotation); var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(attackRotation); var a = w.CreateActor(info.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(attackFacing), }); var attack = a.Trait <AttackBomber>(); attack.SetTarget(target + targetOffset); attack.OnEnteredAttackRange += onEnterRange; attack.OnExitedAttackRange += onExitRange; attack.OnRemovedFromWorld += onExitRange; a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset))); a.QueueActivity(new RemoveSelf()); aircraftInRange.Add(a, false); distanceTestActor = a; } if (Info.DisplayBeacon) { var distance = (target - startEdge).HorizontalLength; beacon = new Beacon( order.Player, order.TargetLocation.CenterPosition, Info.BeaconPalettePrefix, Info.BeaconPoster, Info.BeaconPosterPalette, () => 1 - ((distanceTestActor.CenterPosition - target).HorizontalLength - info.BeaconDistanceOffset.Range) * 1f / distance ); w.Add(beacon); } }); }
void ITick.Tick(Actor self) { if (IsTraitDisabled || IsTraitPaused || !self.IsInWorld || --ticks > 0) { return; } ticks = Info.Delay.Length == 2 ? world.SharedRandom.Next(Info.Delay[0], Info.Delay[1]) : Info.Delay[0]; var localoffset = body != null ? body.LocalToWorld(Info.LocalOffset.Rotate(body.QuantizeOrientation(self, self.Orientation))) : Info.LocalOffset; var position = self.CenterPosition + localoffset; var availableTargetActors = world.FindActorsOnCircle(position, Info.WeaponInfo.Range) .Where(x => (Info.AllowSelfHit || x != self) && Info.WeaponInfo.IsValidAgainst(Target.FromActor(x), world, self) && Info.AimTargetStances.HasStance(self.Owner.Stances[x.Owner])) .Where(x => { var activeShapes = x.TraitsImplementing <HitShape>().Where(Exts.IsTraitEnabled); if (!activeShapes.Any()) { return(false); } var distance = activeShapes.Min(t => t.DistanceFromEdge(x, position)); if (distance < Info.WeaponInfo.Range) { return(true); } return(false); }) .Shuffle(world.SharedRandom); var targetActor = availableTargetActors.GetEnumerator(); var amount = Info.Amount.Length == 2 ? world.SharedRandom.Next(Info.Amount[0], Info.Amount[1]) : Info.Amount[0]; for (var i = 0; i < amount; i++) { Target shrapnelTarget = Target.Invalid; if (world.SharedRandom.Next(100) < Info.AimChance && targetActor.MoveNext()) { shrapnelTarget = Target.FromActor(targetActor.Current); } if (Info.ThrowWithoutTarget && shrapnelTarget.Type == TargetType.Invalid) { var rotation = WRot.FromFacing(world.SharedRandom.Next(1024)); var range = world.SharedRandom.Next(Info.WeaponInfo.MinRange.Length, Info.WeaponInfo.Range.Length); var targetpos = position + new WVec(range, 0, 0).Rotate(rotation); var tpos = Target.FromPos(new WPos(targetpos.X, targetpos.Y, world.Map.CenterOfCell(world.Map.CellContaining(targetpos)).Z)); if (Info.WeaponInfo.IsValidAgainst(tpos, world, self)) { shrapnelTarget = tpos; } } if (shrapnelTarget.Type == TargetType.Invalid) { continue; } var args = new ProjectileArgs { Weapon = Info.WeaponInfo, Facing = (shrapnelTarget.CenterPosition - position).Yaw, DamageModifiers = !self.IsDead ? self.TraitsImplementing <IFirepowerModifier>() .Select(a => a.GetFirepowerModifier()).ToArray() : new int[0], InaccuracyModifiers = !self.IsDead ? self.TraitsImplementing <IInaccuracyModifier>() .Select(a => a.GetInaccuracyModifier()).ToArray() : new int[0], RangeModifiers = !self.IsDead ? self.TraitsImplementing <IRangeModifier>() .Select(a => a.GetRangeModifier()).ToArray() : new int[0], Source = position, CurrentSource = () => position, SourceActor = self, GuidedTarget = shrapnelTarget, PassiveTarget = shrapnelTarget.CenterPosition }; if (args.Weapon.Projectile != null) { var projectile = args.Weapon.Projectile.Create(args); if (projectile != null) { world.AddFrameEndTask(w => w.Add(projectile)); } if (args.Weapon.Report != null && args.Weapon.Report.Any()) { Game.Sound.Play(SoundType.World, args.Weapon.Report.Random(world.SharedRandom), position); } } } }
/// <summary> /// 开火 /// </summary> /// <param name="self"></param> /// <param name="facing"></param> /// <param name="target"></param> /// <param name="barrel"></param> protected virtual void FireBarrel(Actor self, IFacing facing, Target target, Barrel barrel) { Func <WPos> muzzlePosition = () => self.CenterPosition + MuzzleOffset(self, barrel);//枪口位置 var legacyFacing = MuzzleOrientation(self, barrel).Yaw.Angle / 4; var passiveTarget = Weapon.TargetActorCenter ? target.CenterPosition : target.Positions.PositionClosestTo(muzzlePosition()); var initialOffset = Weapon.FirstBurstTargetOffset; if (initialOffset != WVec.Zero) { //We want this to match Armament.LocalOffset,so we need to convert it to forward,right,up. //我们希望这个匹配Armament.LocalOffset,所以我们需要将它转换为向前,向上,向右。 initialOffset = new WVec(initialOffset.Y, -initialOffset.X, initialOffset.Z); passiveTarget += initialOffset.Rotate(WRot.FromFacing(legacyFacing)); } var followingOffset = Weapon.FollowingBurstTargetOffset; if (followingOffset != WVec.Zero) { //We want this to match Armament.LocalOffset,so we need to convert it to forward,right,up. followingOffset = new WVec(followingOffset.Y, -followingOffset.X, followingOffset.Z); passiveTarget += ((Weapon.Burst - Burst) * followingOffset).Rotate(WRot.FromFacing(legacyFacing)); } var args = new ProjectileArgs { Weapon = Weapon, Facing = legacyFacing, DamagedModifiers = damageModifiers.ToArray(), InaccuracyModifiers = inaccuracyModifiers.ToArray(), RangeModifiers = rangeModifiers.ToArray(), Source = muzzlePosition(), CurrentSource = muzzlePosition, SourceActor = self, PassiveTarget = passiveTarget, GuidedTarget = target }; foreach (var na in notifyAttacks) { na.PreparingAttack(self, target, this, barrel); } ScheduleDelayedAction(Info.FireDelay, () => { if (args.Weapon.Projectile != null) { var projectile = args.Weapon.Projectile.Create(args); if (projectile != null) { self.World.Add(projectile); } if (args.Weapon.Report != null && args.Weapon.Report.Any()) { WarGame.Sound.Play(SoundType.World, args.Weapon.Report.Random(self.World.SharedRandom), self.CenterPosition); } if (Burst == args.Weapon.Burst && args.Weapon.StartBurstReport != null && args.Weapon.StartBurstReport.Any()) { WarGame.Sound.Play(SoundType.World, args.Weapon.StartBurstReport.Random(self.World.SharedRandom), self.CenterPosition); } foreach (var na in notifyAttacks) { na.Attacking(self, target, this, barrel); } Recoil = Info.Recoil; } }); }
public override void Activate(Actor self, Order order, SupportPowerManager manager) { base.Activate(self, order, manager); var info = Info as ParatroopersPowerInfo; var dropFacing = Util.QuantizeFacing(self.World.SharedRandom.Next(256), info.QuantizedFacings) * (256 / info.QuantizedFacings); var dropRotation = WRot.FromFacing(dropFacing); var delta = new WVec(0, -1024, 0).Rotate(dropRotation); var altitude = self.World.Map.Rules.Actors[info.UnitType].Traits.Get <PlaneInfo>().CruiseAltitude.Range; var target = self.World.Map.CenterOfCell(order.TargetLocation) + new WVec(0, 0, altitude); var startEdge = target - (self.World.Map.DistanceToEdge(target, -delta) + info.Cordon).Range * delta / 1024; var finishEdge = target + (self.World.Map.DistanceToEdge(target, delta) + info.Cordon).Range * delta / 1024; Actor camera = null; Beacon beacon = null; var aircraftInRange = new Dictionary <Actor, bool>(); Action <Actor> onEnterRange = a => { // Spawn a camera and remove the beacon when the first plane enters the target area if (info.CameraActor != null && !aircraftInRange.Any(kv => kv.Value)) { self.World.AddFrameEndTask(w => { camera = w.CreateActor(info.CameraActor, new TypeDictionary { new LocationInit(order.TargetLocation), new OwnerInit(self.Owner), }); }); } if (beacon != null) { self.World.AddFrameEndTask(w => { w.Remove(beacon); beacon = null; }); } aircraftInRange[a] = true; }; Action <Actor> onExitRange = a => { aircraftInRange[a] = false; // Remove the camera when the final plane leaves the target area if (!aircraftInRange.Any(kv => kv.Value)) { if (camera != null) { camera.QueueActivity(new Wait(info.CameraRemoveDelay)); camera.QueueActivity(new RemoveSelf()); } camera = null; } }; self.World.AddFrameEndTask(w => { var notification = self.Owner.IsAlliedWith(self.World.RenderPlayer) ? Info.LaunchSound : Info.IncomingSound; Sound.Play(notification); Actor distanceTestActor = null; var passengersPerPlane = (info.DropItems.Length + info.SquadSize - 1) / info.SquadSize; var added = 0; for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (info.SquadSize & 1) == 0) { continue; } // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(dropRotation); var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(dropRotation); var a = w.CreateActor(info.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(dropFacing), }); var drop = a.Trait <ParaDrop>(); drop.SetLZ(w.Map.CellContaining(target + targetOffset), !info.AllowImpassableCells); drop.OnEnteredDropRange += onEnterRange; drop.OnExitedDropRange += onExitRange; drop.OnRemovedFromWorld += onExitRange; var cargo = a.Trait <Cargo>(); var passengers = info.DropItems.Skip(added).Take(passengersPerPlane); added += passengersPerPlane; foreach (var p in passengers) { cargo.Load(a, self.World.CreateActor(false, p.ToLowerInvariant(), new TypeDictionary { new OwnerInit(a.Owner) })); } a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset))); a.QueueActivity(new RemoveSelf()); aircraftInRange.Add(a, false); distanceTestActor = a; } if (Info.DisplayBeacon) { var distance = (target - startEdge).HorizontalLength; beacon = new Beacon( order.Player, self.World.Map.CenterOfCell(order.TargetLocation), Info.BeaconPalettePrefix, Info.BeaconPoster, Info.BeaconPosterPalette, () => 1 - ((distanceTestActor.CenterPosition - target).HorizontalLength - info.BeaconDistanceOffset.Range) * 1f / distance ); w.Add(beacon); } }); }
public override void DoImpact(Target target, Actor firedBy, IEnumerable <int> damageModifiers) { var world = firedBy.World; var map = world.Map; if (!IsValidImpact(target.CenterPosition, firedBy)) { return; } var directActors = world.FindActorsInCircle(target.CenterPosition, TargetSearchRadius); var availableTargetActors = world.FindActorsInCircle(target.CenterPosition, weapon.Range) .Where(x => (AllowDirectHit || !directActors.Contains(x)) && weapon.IsValidAgainst(Target.FromActor(x), firedBy.World, firedBy) && AimTargetStances.HasStance(firedBy.Owner.Stances[x.Owner])) .Shuffle(world.SharedRandom); var targetActor = availableTargetActors.GetEnumerator(); var amount = Amount.Length == 2 ? world.SharedRandom.Next(Amount[0], Amount[1]) : Amount[0]; for (var i = 0; i < amount; i++) { Target shrapnelTarget = Target.Invalid; if (world.SharedRandom.Next(100) < AimChance && targetActor.MoveNext()) { shrapnelTarget = Target.FromActor(targetActor.Current); } if (ThrowWithoutTarget && shrapnelTarget.Type == TargetType.Invalid) { var rotation = WRot.FromFacing(world.SharedRandom.Next(1024)); var range = world.SharedRandom.Next(weapon.MinRange.Length, weapon.Range.Length); var targetpos = target.CenterPosition + new WVec(range, 0, 0).Rotate(rotation); var tpos = Target.FromPos(new WPos(targetpos.X, targetpos.Y, map.CenterOfCell(map.CellContaining(targetpos)).Z)); if (weapon.IsValidAgainst(tpos, firedBy.World, firedBy)) { shrapnelTarget = tpos; } } if (shrapnelTarget.Type == TargetType.Invalid) { continue; } var args = new ProjectileArgs { Weapon = weapon, Facing = (shrapnelTarget.CenterPosition - target.CenterPosition).Yaw.Facing, DamageModifiers = !firedBy.IsDead ? firedBy.TraitsImplementing <IFirepowerModifier>() .Select(a => a.GetFirepowerModifier()).ToArray() : new int[0], InaccuracyModifiers = !firedBy.IsDead ? firedBy.TraitsImplementing <IInaccuracyModifier>() .Select(a => a.GetInaccuracyModifier()).ToArray() : new int[0], RangeModifiers = !firedBy.IsDead ? firedBy.TraitsImplementing <IRangeModifier>() .Select(a => a.GetRangeModifier()).ToArray() : new int[0], Source = target.CenterPosition, SourceActor = firedBy, GuidedTarget = shrapnelTarget, PassiveTarget = shrapnelTarget.CenterPosition }; if (args.Weapon.Projectile != null) { var projectile = args.Weapon.Projectile.Create(args); if (projectile != null) { firedBy.World.AddFrameEndTask(w => w.Add(projectile)); } if (args.Weapon.Report != null && args.Weapon.Report.Any()) { Game.Sound.Play(SoundType.World, args.Weapon.Report.Random(firedBy.World.SharedRandom), target.CenterPosition); } } } }
CPos PickTargetLocation() { var target = self.CenterPosition + new WVec(0, -1024 * effectiveMoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); var targetCell = self.World.Map.CellContaining(target); if (!self.World.Map.Contains(targetCell)) { // If MoveRadius is too big there might not be a valid cell to order the attack to (if actor is on a small island and can't leave) if (++ticksIdle % info.TicksToWaitBeforeReducingMoveRadius == 0) { effectiveMoveRadius--; } return(CPos.Zero); // We'll be back the next tick; better to sit idle for a few seconds than prolong this tick indefinitely with a loop } ticksIdle = 0; effectiveMoveRadius = info.WanderMoveRadius; return(targetCell); }
public Actor[] SendParatroopers(Actor self, WPos target, bool randomize = true, int dropFacing = 0) { var units = new List<Actor>(); var info = Info as ParatroopersPowerInfo; if (randomize) dropFacing = 256 * self.World.SharedRandom.Next(info.QuantizedFacings) / info.QuantizedFacings; var utLower = info.UnitType.ToLowerInvariant(); ActorInfo unitType; if (!self.World.Map.Rules.Actors.TryGetValue(utLower, out unitType)) throw new YamlException("Actors ruleset does not include the entry '{0}'".F(utLower)); var altitude = unitType.TraitInfo<AircraftInfo>().CruiseAltitude.Length; var dropRotation = WRot.FromFacing(dropFacing); var delta = new WVec(0, -1024, 0).Rotate(dropRotation); target = target + new WVec(0, 0, altitude); var startEdge = target - (self.World.Map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; var finishEdge = target + (self.World.Map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; Actor camera = null; Beacon beacon = null; var aircraftInRange = new Dictionary<Actor, bool>(); Action<Actor> onEnterRange = a => { // Spawn a camera and remove the beacon when the first plane enters the target area if (info.CameraActor != null && !aircraftInRange.Any(kv => kv.Value)) { self.World.AddFrameEndTask(w => { camera = w.CreateActor(info.CameraActor, new TypeDictionary { new LocationInit(self.World.Map.CellContaining(target)), new OwnerInit(self.Owner), }); }); } if (beacon != null) { self.World.AddFrameEndTask(w => { w.Remove(beacon); beacon = null; }); } aircraftInRange[a] = true; }; Action<Actor> onExitRange = a => { aircraftInRange[a] = false; // Remove the camera when the final plane leaves the target area if (!aircraftInRange.Any(kv => kv.Value)) { if (camera != null) { camera.QueueActivity(new Wait(info.CameraRemoveDelay)); camera.QueueActivity(new RemoveSelf()); } camera = null; if (beacon != null) { self.World.AddFrameEndTask(w => { w.Remove(beacon); beacon = null; }); } } }; foreach (var p in info.DropItems) { var unit = self.World.CreateActor(false, p.ToLowerInvariant(), new TypeDictionary { new OwnerInit(self.Owner) }); units.Add(unit); } self.World.AddFrameEndTask(w => { PlayLaunchSounds(); Actor distanceTestActor = null; var passengersPerPlane = (info.DropItems.Length + info.SquadSize - 1) / info.SquadSize; var added = 0; for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (info.SquadSize & 1) == 0) continue; // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(dropRotation); var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(dropRotation); var a = w.CreateActor(info.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(dropFacing), }); var drop = a.Trait<ParaDrop>(); drop.SetLZ(w.Map.CellContaining(target + targetOffset), !info.AllowImpassableCells); drop.OnEnteredDropRange += onEnterRange; drop.OnExitedDropRange += onExitRange; drop.OnRemovedFromWorld += onExitRange; var cargo = a.Trait<Cargo>(); var passengers = units.Skip(added).Take(passengersPerPlane); added += passengersPerPlane; foreach (var p in passengers) cargo.Load(a, p); a.QueueActivity(new Fly(a, Target.FromPos(target + spawnOffset))); a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset))); a.QueueActivity(new RemoveSelf()); aircraftInRange.Add(a, false); distanceTestActor = a; } if (Info.DisplayBeacon) { var distance = (target - startEdge).HorizontalLength; beacon = new Beacon( self.Owner, target - new WVec(0, 0, altitude), Info.BeaconPaletteIsPlayerPalette, Info.BeaconPalette, Info.BeaconImage, Info.BeaconPoster, Info.BeaconPosterPalette, Info.ArrowSequence, Info.CircleSequence, Info.ClockSequence, () => 1 - ((distanceTestActor.CenterPosition - target).HorizontalLength - info.BeaconDistanceOffset.Length) * 1f / distance, Info.BeaconDelay); w.Add(beacon); } }); return units.ToArray(); }
public override void Activate(Actor self, Order order) { var info = Info as AirstrikePowerInfo; var attackFacing = Util.QuantizeFacing(self.World.SharedRandom.Next(256), info.QuantizedFacings) * (256 / info.QuantizedFacings); var attackRotation = WRot.FromFacing(attackFacing); var delta = new WVec(0, -1024, 0).Rotate(attackRotation); var altitude = Rules.Info[info.UnitType].Traits.Get <PlaneInfo>().CruiseAltitude * 1024 / Game.CellSize; var target = order.TargetLocation.CenterPosition + new WVec(0, 0, altitude); var startEdge = target - (self.World.DistanceToMapEdge(target, -delta) + info.Cordon).Range * delta / 1024; var finishEdge = target + (self.World.DistanceToMapEdge(target, delta) + info.Cordon).Range * delta / 1024; self.World.AddFrameEndTask(w => { var notification = self.Owner.IsAlliedWith(self.World.RenderPlayer) ? Info.LaunchSound : Info.IncomingSound; Sound.Play(notification); Actor flare = null; if (info.FlareType != null) { flare = w.CreateActor(info.FlareType, new TypeDictionary { new LocationInit(order.TargetLocation), new OwnerInit(self.Owner), }); flare.QueueActivity(new Wait(info.FlareTime)); flare.QueueActivity(new RemoveSelf()); } for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (info.SquadSize & 1) == 0) { continue; } // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(attackRotation); var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(attackRotation); var a = w.CreateActor(info.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(attackFacing), }); a.Trait <AttackBomber>().SetTarget(target + targetOffset); if (flare != null) { a.QueueActivity(new CallFunc(() => flare.Destroy())); } a.QueueActivity(Fly.ToPos(finishEdge + spawnOffset)); a.QueueActivity(new RemoveSelf()); } }); }
public override void Tick(Actor self) { base.Tick(self); var facing = self.TraitOrDefault <IFacing>(); var cp = self.CenterPosition; var bombTarget = Target.FromPos(cp - new WVec(0, 0, cp.Z)); // Bombs drop anywhere in range foreach (var a in Armaments.Where(a => a.Info.Name == info.Bombs)) { var range = new WRange((int)(1024 * a.Weapon.Range)); if (!target.IsInRange(self.CenterPosition, range)) { continue; } a.CheckFire(self, this, facing, bombTarget); } // Guns only fire when approaching the target var facingToTarget = Util.GetFacing(target.CenterPosition - self.CenterPosition, facing.Facing); if (Math.Abs(facingToTarget - facing.Facing) % 256 > info.FacingTolerance) { return; } foreach (var a in Armaments.Where(a => a.Info.Name == info.Guns)) { var range = new WRange((int)(1024 * a.Weapon.Range)); if (!target.IsInRange(self.CenterPosition, range)) { continue; } var t = Target.FromPos(cp - new WVec(0, range.Range / 2, cp.Z).Rotate(WRot.FromFacing(facing.Facing))); a.CheckFire(self, this, facing, t); } }
public void Tick(Actor self) { var cp = self.CenterPosition; var bombTarget = Target.FromPos(cp - new WVec(0, 0, cp.Z)); var wasInAttackRange = inAttackRange; var wasFacingTarget = facingTarget; inAttackRange = false; var f = facing.Value.Facing; var facingToTarget = Util.GetFacing(target.CenterPosition - self.CenterPosition, f); facingTarget = Math.Abs(facingToTarget - f) % 256 <= info.FacingTolerance; // Bombs drop anywhere in range foreach (var a in Armaments.Where(a => a.Info.Name == info.Bombs)) { if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) { continue; } inAttackRange = true; a.CheckFire(self, facing.Value, bombTarget); } // Guns only fire when approaching the target if (facingTarget) { foreach (var a in Armaments.Where(a => a.Info.Name == info.Guns)) { if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) { continue; } var t = Target.FromPos(cp - new WVec(0, a.Weapon.Range.Length / 2, cp.Z).Rotate(WRot.FromFacing(f))); inAttackRange = true; a.CheckFire(self, facing.Value, t); } } // Actors without armaments may want to trigger an action when it passes the target if (!Armaments.Any()) { inAttackRange = !wasInAttackRange && !facingTarget && wasFacingTarget; } if (inAttackRange && !wasInAttackRange) { OnEnteredAttackRange(self); } if (!inAttackRange && wasInAttackRange) { OnExitedAttackRange(self); } }