IEnumerable <IRenderable> RenderArmaments(Actor self, AttackBase attack) { // Fire ports on garrisonable structures if (attack is AttackGarrisoned garrison) { var bodyOrientation = coords.Value.QuantizeOrientation(self, self.Orientation); foreach (var p in garrison.Info.Ports) { var pos = self.CenterPosition + coords.Value.LocalToWorld(p.Offset.Rotate(bodyOrientation)); var da = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw + p.Cone)).Rotate(bodyOrientation)); var db = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw - p.Cone)).Rotate(bodyOrientation)); yield return(new LineAnnotationRenderable(pos, pos + da * 224 / da.Length, 1, Color.White)); yield return(new LineAnnotationRenderable(pos, pos + db * 224 / da.Length, 1, Color.White)); } yield break; } foreach (var a in attack.Armaments) { if (a.IsTraitDisabled) { continue; } foreach (var b in a.Barrels) { var barrelEnd = new Barrel { Offset = b.Offset + new WVec(224, 0, 0), Yaw = b.Yaw }; var muzzle = self.CenterPosition + a.MuzzleOffset(self, b); var endMuzzle = self.CenterPosition + a.MuzzleOffset(self, barrelEnd); yield return(new LineAnnotationRenderable(muzzle, endMuzzle, 1, Color.White)); } } }
public IEnumerable <IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p) { if (!EnabledByDefault) { yield break; } if (Palette != null) { p = init.WorldRenderer.Palette(Palette); } Func <WAngle> facing; var dynamicfacingInit = init.GetOrDefault <DynamicFacingInit>(); if (dynamicfacingInit != null) { facing = dynamicfacingInit.Value; } else { var f = init.GetValue <FacingInit, WAngle>(WAngle.Zero); facing = () => f; } var anim = new Animation(init.World, image, facing); anim.IsDecoration = IsDecoration; anim.PlayRepeating(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), Sequence)); var body = init.Actor.TraitInfo <BodyOrientationInfo>(); Func <WRot> orientation = () => body.QuantizeOrientation(WRot.FromYaw(facing()), facings); Func <WVec> offset = () => body.LocalToWorld(Offset.Rotate(orientation())); Func <int> zOffset = () => { var tmpOffset = offset(); return(tmpOffset.Y + tmpOffset.Z + 1); }; yield return(new SpriteActorPreview(anim, offset, zOffset, p, rs.Scale)); }
public IEnumerable <ModelAnimation> RenderPreviewVoxels( ActorPreviewInitializer init, RenderVoxelsInfo rv, string image, Func <WRot> orientation, int facings, PaletteReference p) { if (!EnabledByDefault) { yield break; } var body = init.Actor.TraitInfo <BodyOrientationInfo>(); var t = init.Actor.TraitInfos <TurretedInfo>() .First(tt => tt.Turret == Turret); var model = init.World.ModelCache.GetModelSequence(image, Sequence); Func <WVec> turretOffset = () => body.LocalToWorld(t.Offset.Rotate(orientation())); var turretFacing = Turreted.TurretFacingFromInit(init, t.InitialFacing, Turret); Func <WRot> turretBodyOrientation = () => WRot.FromYaw(WAngle.FromFacing(turretFacing()) - orientation().Yaw); yield return(new ModelAnimation(model, turretOffset, () => new[] { turretBodyOrientation(), body.QuantizeOrientation(orientation(), facings) }, () => false, () => 0, ShowShadow)); }
public IEnumerable <VoxelAnimation> RenderPreviewVoxels( ActorPreviewInitializer init, RenderVoxelsInfo rv, string image, Func <WRot> orientation, int facings, PaletteReference p) { if (UpgradeMinEnabledLevel > 0) { yield break; } var body = init.Actor.TraitInfo <BodyOrientationInfo>(); var t = init.Actor.TraitInfos <TurretedInfo>() .First(tt => tt.Turret == Turret); var voxel = VoxelProvider.GetVoxel(image, Sequence); Func <WVec> turretOffset = () => body.LocalToWorld(t.Offset.Rotate(orientation())); var turretFacing = Turreted.TurretFacingFromInit(init, t.InitialFacing, Turret); Func <WRot> turretBodyOrientation = () => WRot.FromYaw(WAngle.FromFacing(turretFacing()) - orientation().Yaw); yield return(new VoxelAnimation(voxel, turretOffset, () => new[] { turretBodyOrientation(), body.QuantizeOrientation(orientation(), facings) }, () => false, () => 0)); }
public AreaBeam(AreaBeamInfo info, ProjectileArgs args, Color color) { this.info = info; this.args = args; this.color = color; actorAttackBase = args.SourceActor.Trait <AttackBase>(); var 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]; } // Both the head and tail start at the source actor, but initially only the head is travelling. headPos = args.Source; tailPos = headPos; target = args.PassiveTarget; if (info.Inaccuracy.Length > 0) { var inaccuracy = Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers); var maxOffset = inaccuracy * (target - headPos).Length / args.Weapon.Range.Length; target += WVec.FromPDF(world.SharedRandom, 2) * maxOffset / 1024; } towardsTargetFacing = (target - headPos).Yaw; // Update the target position with the range we shoot beyond the target by // I.e. we can deliberately overshoot, so aim for that position var dir = new WVec(0, -1024, 0).Rotate(WRot.FromYaw(towardsTargetFacing)); target += dir * info.BeyondTargetRange.Length / 1024; length = Math.Max((target - headPos).Length / speed.Length, 1); }
void DrawArmaments(Actor self, AttackBase attack, WorldRenderer wr, RgbaColorRenderer wcr, float iz) { var c = Color.White; // Fire ports on garrisonable structures var garrison = attack as AttackGarrisoned; if (garrison != null) { var bodyOrientation = coords.Value.QuantizeOrientation(self, self.Orientation); foreach (var p in garrison.Info.Ports) { var pos = self.CenterPosition + coords.Value.LocalToWorld(p.Offset.Rotate(bodyOrientation)); var da = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw + p.Cone)).Rotate(bodyOrientation)); var db = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw - p.Cone)).Rotate(bodyOrientation)); var o = wr.ScreenPosition(pos); var a = wr.ScreenPosition(pos + da * 224 / da.Length); var b = wr.ScreenPosition(pos + db * 224 / db.Length); wcr.DrawLine(o, a, iz, c); wcr.DrawLine(o, b, iz, c); } return; } foreach (var a in attack.Armaments) { foreach (var b in a.Barrels) { var muzzle = self.CenterPosition + a.MuzzleOffset(self, b); var dirOffset = new WVec(0, -224, 0).Rotate(a.MuzzleOrientation(self, b)); var sm = wr.ScreenPosition(muzzle); var sd = wr.ScreenPosition(muzzle + dirOffset); wcr.DrawLine(sm, sd, iz, c); TargetLineRenderable.DrawTargetMarker(wr, c, sm); } } }
void UpdateCenterLocation(Actor self, Mobile mobile) { // Avoid division through zero if (MoveFractionTotal != 0) { WPos pos; if (EnableArc) { var angle = WAngle.Lerp(ArcFromAngle, ArcToAngle, moveFraction, MoveFractionTotal); var length = int2.Lerp(ArcFromLength, ArcToLength, moveFraction, MoveFractionTotal); var height = int2.Lerp(From.Z, To.Z, moveFraction, MoveFractionTotal); pos = ArcCenter + new WVec(0, length, height).Rotate(WRot.FromYaw(angle)); } else { pos = WPos.Lerp(From, To, moveFraction, MoveFractionTotal); } if (self.Location.Layer == 0) { pos -= new WVec(WDist.Zero, WDist.Zero, self.World.Map.DistanceAboveTerrain(pos)); } mobile.SetVisualPosition(self, pos); } else { mobile.SetVisualPosition(self, To); } if (moveFraction >= MoveFractionTotal) { mobile.Facing = ToFacing; } else { mobile.Facing = WAngle.Lerp(FromFacing, ToFacing, moveFraction, MoveFractionTotal); } }
public IEnumerable <IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, string image, int facings, PaletteReference p) { if (!EnabledByDefault) { yield break; } var body = init.Actor.TraitInfo <BodyOrientationInfo>(); var t = init.Actor.TraitInfos <TurretedInfo>() .First(tt => tt.Turret == Turret); var turretFacing = t.WorldFacingFromInit(init); var anim = new Animation(init.World, image, turretFacing); anim.Play(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), Sequence)); var facing = init.GetFacing(); Func <WRot> orientation = () => body.QuantizeOrientation(WRot.FromYaw(facing()), facings); Func <WVec> offset = () => body.LocalToWorld(t.Offset.Rotate(orientation())); Func <int> zOffset = () => { var tmpOffset = offset(); return(-(tmpOffset.Y + tmpOffset.Z) + 1); }; if (IsPlayerPalette) { p = init.WorldRenderer.Palette(Palette + init.Get <OwnerInit>().InternalName); } else if (Palette != null) { p = init.WorldRenderer.Palette(Palette); } yield return(new SpriteActorPreview(anim, offset, zOffset, p)); }
public GravityBomb(GravityBombInfo info, ProjectileArgs args) { this.info = info; this.args = args; pos = args.Source; var convertedVelocity = new WVec(info.Velocity.Y, -info.Velocity.X, info.Velocity.Z); velocity = convertedVelocity.Rotate(WRot.FromYaw(args.Facing)); acceleration = new WVec(info.Acceleration.Y, -info.Acceleration.X, info.Acceleration.Z); if (!string.IsNullOrEmpty(info.Image)) { anim = new Animation(args.SourceActor.World, info.Image, () => args.Facing); if (!string.IsNullOrEmpty(info.OpenSequence)) { anim.PlayThen(info.OpenSequence, () => anim.PlayRepeating(info.Sequences.Random(args.SourceActor.World.SharedRandom))); } else { anim.PlayRepeating(info.Sequences.Random(args.SourceActor.World.SharedRandom)); } } }
WVec BarrelOffset() { var b = self.Orientation; var qb = body.QuantizeOrientation(self, b); var localOffset = Info.LocalOffset + new WVec(-armament.Recoil, WDist.Zero, WDist.Zero); var turretLocalOffset = turreted != null ? turreted.Offset : WVec.Zero; var turretOrientation = turreted != null?turreted.WorldOrientation(self) - b + WRot.FromYaw(b.Yaw - qb.Yaw) : WRot.None; return(body.LocalToWorld((turretLocalOffset + localOffset.Rotate(turretOrientation)).Rotate(qb))); }
public virtual WVec GetRepulsionForce() { if (!Info.Repulsable) { return(WVec.Zero); } if (reservation != null) { var distanceFromReservationActor = (ReservedActor.CenterPosition - self.CenterPosition).HorizontalLength; if (distanceFromReservationActor < Info.WaitDistanceFromResupplyBase.Length) { return(WVec.Zero); } } // Repulsion only applies when we're flying at CruiseAltitude! if (!cruising) { return(WVec.Zero); } // PERF: Avoid LINQ. var repulsionForce = WVec.Zero; foreach (var actor in self.World.FindActorsInCircle(self.CenterPosition, Info.IdealSeparation)) { if (actor.IsDead) { continue; } var ai = actor.Info.TraitInfoOrDefault <AircraftInfo>(); if (ai == null || !ai.Repulsable || ai.CruiseAltitude != Info.CruiseAltitude) { continue; } repulsionForce += GetRepulsionForce(actor); } // Actors outside the map bounds receive an extra nudge towards the center of the map if (!self.World.Map.Contains(self.Location)) { // The map bounds are in projected coordinates, which is technically wrong for this, // but we avoid the issues in practice by guessing the middle of the map instead of the edge var center = WPos.Lerp(self.World.Map.ProjectedTopLeft, self.World.Map.ProjectedBottomRight, 1, 2); repulsionForce += new WVec(1024, 0, 0).Rotate(WRot.FromYaw((self.CenterPosition - center).Yaw)); } if (Info.CanHover) { return(repulsionForce); } // Non-hovering actors mush always keep moving forward, so they need extra calculations. var currentDir = FlyStep(Facing); var length = currentDir.HorizontalLength * repulsionForce.HorizontalLength; if (length == 0) { return(WVec.Zero); } var dot = WVec.Dot(currentDir, repulsionForce) / length; // avoid stalling the plane return(dot >= 0 ? repulsionForce : WVec.Zero); }
public Turreted(ActorInitializer init, TurretedInfo info) : base(info) { LocalOrientation = WRot.FromYaw(info.LocalFacingFromInit(init)()); }
public Actor[] SendAirstrike(Actor self, WPos target, WAngle?facing = null) { var aircraft = new List <Actor>(); if (!facing.HasValue) { facing = new WAngle(1024 * self.World.SharedRandom.Next(info.QuantizedFacings) / info.QuantizedFacings); } var altitude = self.World.Map.Rules.Actors[info.UnitType].TraitInfo <AircraftInfo>().CruiseAltitude.Length; var attackRotation = WRot.FromYaw(facing.Value); 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 the range. // If at the map's edge, they may be removed from world before leaving. if (aircraftInRange.All(kv => !kv.Key.IsInWorld)) { 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(attackRotation); var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(attackRotation); var a = self.World.CreateActor(false, info.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(facing.Value), }); aircraft.Add(a); aircraftInRange.Add(a, false); var attack = a.Trait <AttackBomber>(); attack.SetTarget(target + targetOffset); attack.OnEnteredAttackRange += onEnterRange; attack.OnExitedAttackRange += onExitRange; attack.OnRemovedFromWorld += onRemovedFromWorld; } self.World.AddFrameEndTask(w => { PlayLaunchSounds(); var j = 0; 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 a = aircraft[j++]; w.Add(a); a.QueueActivity(new Fly(a, Target.FromPos(target + spawnOffset))); a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset))); a.QueueActivity(new RemoveSelf()); 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); } }); return(aircraft.ToArray()); }
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 (rangeLimit >= WDist.Zero && distanceCovered > rangeLimit) { 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.Weapon.TargetActorCenter ? args.GuidedTarget.CenterPosition : args.GuidedTarget.Positions.PositionClosestTo(args.Source)) + new WVec(WDist.Zero, WDist.Zero, info.AirburstAltitude); } // Compute target's predicted velocity vector (assuming uniform circular motion) var yaw1 = tarVel.HorizontalLengthSquared != 0 ? tarVel.Yaw : WAngle.FromFacing(hFacing); tarVel = newTarPos - targetPosition; var yaw2 = tarVel.HorizontalLengthSquared != 0 ? tarVel.Yaw : WAngle.FromFacing(hFacing); predVel = tarVel.Rotate(WRot.FromYaw(yaw2 - yaw1)); 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 = new WVec(move.X, move.Y - move.Z, 0).Yaw.Facing; // Move the missile var lastPos = pos; if (info.AllowSnapping && state != States.Freefall && relTarDist < move.Length) { pos = targetPosition + offset; } else { pos += move; } // Check for walls or other blocking obstacles var shouldExplode = false; WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, lastPos, pos, info.Width, out blockedPos)) { pos = blockedPos; shouldExplode = true; } // Create the sprite trail effect if (!string.IsNullOrEmpty(info.TrailImage) && --ticksToNextSmoke < 0 && (state != States.Freefall || info.TrailWhenDeactivated)) { world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos - 3 * move / 2, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette, facing: renderFacing))); ticksToNextSmoke = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(pos); } distanceCovered += new WDist(speed); var cell = world.Map.CellContaining(pos); var height = world.Map.DistanceAboveTerrain(pos); shouldExplode |= height.Length < 0 || // Hit the ground relTarDist < info.CloseEnough.Length || // Within range (info.ExplodeWhenEmpty && rangeLimit >= WDist.Zero && distanceCovered > rangeLimit) || // Ran out of fuel !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); } }
void ITick.Tick(Actor self) { if (IsTraitDisabled || IsTraitPaused || !self.IsInWorld || --ticks > 0) { return; } ticks = Util.RandomDelay(self.World, Info.Delay); 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.HasRelationship(self.Owner.RelationshipWith(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 = Util.RandomDelay(self.World, Info.Amount); for (var i = 0; i < amount; i++) { var 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 = self.Orientation; if (Info.ThrowAngle == WAngle.Zero) { rotation = WRot.FromFacing(world.SharedRandom.Next(1024)); } else { rotation.Rotate(WRot.FromYaw(Info.ThrowAngle)); } 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); } } foreach (var animation in animations) { animation.Trigger(self); } } }
public virtual WVec GetRepulsionForce() { if (!Info.Repulsable) { return(WVec.Zero); } if (reservation != null) { var distanceFromReservationActor = (ReservedActor.CenterPosition - self.CenterPosition).HorizontalLength; if (distanceFromReservationActor < Info.WaitDistanceFromResupplyBase.Length) { return(WVec.Zero); } } var altitude = self.World.Map.DistanceAboveTerrain(CenterPosition).Length; if (altitude != Info.CruiseAltitude.Length) { return(WVec.Zero); } var repulsionForce = WVec.Zero; foreach (var actor in self.World.FindActorsInCircle(self.CenterPosition, Info.IdealSeparation)) { if (actor.IsDead) { continue; } var ai = actor.Info.TraitInfoOrDefault <AircraftInfo>(); if (ai == null || !ai.Repulsable || ai.CruiseAltitude != Info.CruiseAltitude) { continue; } repulsionForce += GetRepulsionForce(actor); } if (!self.World.Map.Contains(self.Location)) { var center = WPos.Lerp(self.World.Map.ProjectedTopLeft, self.World.Map.ProjectedBottomRight, 1, 2); repulsionForce += new WVec(1024, 0, 0).Rotate(WRot.FromYaw((self.CenterPosition - center).Yaw)); } if (Info.CanHover) { return(repulsionForce); } var currentDir = FlyStep(Facing); var length = currentDir.HorizontalLength * repulsionForce.HorizontalLength; if (length == 0) { return(WVec.Zero); } var dot = WVec.Dot(currentDir, repulsionForce) / length; //avoid stalling the plane return(dot >= 0 ? repulsionForce : WVec.Zero); }
public void RenderAfterWorld(WorldRenderer wr, Actor self) { if (devMode == null || !devMode.ShowCombatGeometry) { return; } var wcr = Game.Renderer.WorldRgbaColorRenderer; var iz = 1 / wr.Viewport.Zoom; if (healthInfo != null) { healthInfo.Shape.DrawCombatOverlay(wr, wcr, self); } if (blockInfo != null) { var hc = Color.Orange; var height = new WVec(0, 0, blockInfo.Height.Length); var ha = wr.ScreenPosition(self.CenterPosition); var hb = wr.ScreenPosition(self.CenterPosition + height); wcr.DrawLine(ha, hb, iz, hc); TargetLineRenderable.DrawTargetMarker(wr, hc, ha); TargetLineRenderable.DrawTargetMarker(wr, hc, hb); } // No armaments to draw if (attack.Value == null) { return; } var c = Color.White; // Fire ports on garrisonable structures var garrison = attack.Value as AttackGarrisoned; if (garrison != null) { var bodyOrientation = coords.Value.QuantizeOrientation(self, self.Orientation); foreach (var p in garrison.Info.Ports) { var pos = self.CenterPosition + coords.Value.LocalToWorld(p.Offset.Rotate(bodyOrientation)); var da = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw + p.Cone)).Rotate(bodyOrientation)); var db = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw - p.Cone)).Rotate(bodyOrientation)); var o = wr.ScreenPosition(pos); var a = wr.ScreenPosition(pos + da * 224 / da.Length); var b = wr.ScreenPosition(pos + db * 224 / db.Length); wcr.DrawLine(o, a, iz, c); wcr.DrawLine(o, b, iz, c); } return; } foreach (var a in attack.Value.Armaments) { foreach (var b in a.Barrels) { var muzzle = self.CenterPosition + a.MuzzleOffset(self, b); var dirOffset = new WVec(0, -224, 0).Rotate(a.MuzzleOrientation(self, b)); var sm = wr.ScreenPosition(muzzle); var sd = wr.ScreenPosition(muzzle + dirOffset); wcr.DrawLine(sm, sd, iz, c); TargetLineRenderable.DrawTargetMarker(wr, c, sm); } } }
protected virtual WRot CalculateMuzzleOrientation(Actor self, Barrel b) { return(WRot.FromYaw(b.Yaw).Rotate(turret != null ? turret.WorldOrientation : self.Orientation)); }
public Actor[] SendAirstrike(Actor self, WPos target, WAngle?facing = null) { var aircraft = new List <Actor>(); if (!facing.HasValue) { facing = new WAngle(1024 * self.World.SharedRandom.Next(info.QuantizedFacings) / info.QuantizedFacings); } var altitude = self.World.Map.Rules.Actors[info.UnitTypes.First(ut => ut.Key == GetLevel()).Value].TraitInfo <AircraftInfo>().CruiseAltitude.Length; var attackRotation = WRot.FromYaw(facing.Value); 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; 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), }); }); } 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 the range. // If at the map's edge, they may be removed from world before leaving. if (aircraftInRange.All(kv => !kv.Key.IsInWorld)) { RemoveCamera(camera); } }; // Create the actors immediately so they can be returned var squadSize = info.SquadSizes.First(ss => ss.Key == GetLevel()).Value; for (var i = -squadSize / 2; i <= squadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (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 = self.World.CreateActor(false, info.UnitTypes.First(ut => ut.Key == GetLevel()).Value, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(facing.Value), }); aircraft.Add(a); aircraftInRange.Add(a, false); var attack = a.Trait <AttackBomber>(); attack.SetTarget(self.World, target + targetOffset); attack.OnEnteredAttackRange += onEnterRange; attack.OnExitedAttackRange += onExitRange; attack.OnRemovedFromWorld += onRemovedFromWorld; } self.World.AddFrameEndTask(w => { PlayLaunchSounds(); var effect = new AirstrikePowerRVEffect(self.World, self.Owner, target, startEdge, finishEdge, attackRotation, altitude, GetLevel(), aircraft.ToArray(), this, info); self.World.Add(effect); }); return(aircraft.ToArray()); }
public Pair <Actor[], Actor[]> SendParatroopers(Actor self, WPos target, WAngle?facing = null) { var aircraft = new List <Actor>(); var units = new List <Actor>(); var info = Info as ParatroopersPowerInfo; if (!facing.HasValue) { facing = new WAngle(1024 * 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.FromYaw(facing.Value); 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 the range. // If at the map's edge, they may be removed from world before leaving. if (aircraftInRange.All(kv => !kv.Key.IsInWorld)) { 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.Value), })); } 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())); }
protected virtual void FireBarrel(Actor self, IFacing facing, Target target, Barrel barrel) { foreach (var na in notifyAttacks) { na.PreparingAttack(self, target, this, barrel); } Func <WPos> muzzlePosition = () => self.CenterPosition + MuzzleOffset(self, barrel); Func <WAngle> muzzleFacing = () => MuzzleOrientation(self, barrel).Yaw; var muzzleOrientation = WRot.FromYaw(muzzleFacing()); 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 initialOffset = new WVec(initialOffset.Y, -initialOffset.X, initialOffset.Z); passiveTarget += initialOffset.Rotate(muzzleOrientation); } 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(muzzleOrientation); } var args = new ProjectileArgs { Weapon = Weapon, Facing = muzzleFacing(), CurrentMuzzleFacing = muzzleFacing, DamageModifiers = damageModifiers.ToArray(), InaccuracyModifiers = inaccuracyModifiers.ToArray(), RangeModifiers = rangeModifiers.ToArray(), Source = muzzlePosition(), CurrentSource = muzzlePosition, SourceActor = self, PassiveTarget = passiveTarget, GuidedTarget = target }; 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()) { Game.Sound.Play(SoundType.World, args.Weapon.Report, self.World, self.CenterPosition); } if (Burst == args.Weapon.Burst && args.Weapon.StartBurstReport != null && args.Weapon.StartBurstReport.Any()) { Game.Sound.Play(SoundType.World, args.Weapon.StartBurstReport, self.World, self.CenterPosition); } foreach (var na in notifyAttacks) { na.Attacking(self, target, this, barrel); } Recoil = Info.Recoil; } }); }
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); var isLanded = dat <= aircraft.LandAltitude; // HACK: Prevent paused (for example, EMP'd) aircraft from taking off. // This is necessary until the TODOs in the IsCanceling block below are adressed. if (isLanded && aircraft.IsTraitPaused) { return(false); } 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 (isLanded) { QueueChild(new TakeOff(self)); } else { VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude); } return(false); } return(true); } else if (isLanded) { QueueChild(new TakeOff(self)); return(false); } target = target.Recalculate(self.Owner, out var 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; // 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 desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw : aircraft.Facing; var move = isSlider ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing); // Inside the minimum range, so reverse if we CanSlide, otherwise face away from the target. if (insideMinRange) { if (isSlider) { FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, -move); } else { FlyTick(self, aircraft, desiredFacing + new WAngle(512), 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 + new WAngle(Util.GetTurnDirection(aircraft.Facing, desiredFacing) * 256); var turnCenterDir = new WVec(0, -1024, 0).Rotate(WRot.FromYaw(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); }
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); } if (desiredFacing.HasValue && desiredFacing.Value != aircraft.Facing) { QueueChild(new Turn(self, desiredFacing.Value)); 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.HasValue) { rotation = WRot.FromYaw(desiredFacing.Value); } 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.TurnSpeed); // Find the center of the turning circles for clockwise and counterclockwise turns var angle = 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.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, landingAlt); return(false); }
public void SendDropPods(Actor self, Order order, WAngle facing) { var actorInfo = self.World.Map.Rules.Actors[info.UnitTypes.First().ToLowerInvariant()]; var aircraftInfo = actorInfo.TraitInfo <AircraftInfo>(); var altitude = aircraftInfo.CruiseAltitude.Length; var approachRotation = WRot.FromYaw(facing); 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(facing) }); 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); } } }); }
// Orientation in unit-space public WRot LocalOrientation(Actor self) { // Hack: turretFacing is relative to the world, so subtract the body yaw return(WRot.FromYaw(WAngle.FromFacing(turretFacing) - self.Orientation.Yaw)); }
public override bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits, int refundableValue) { if (IsTraitDisabled || IsTraitPaused) { return(false); } var info = (ProductionAirdropASInfo)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 rotation = WRot.FromYaw(info.Facing); var distance = new WVec(0, map.Bounds.Width * -1024, 0).Rotate(rotation); startPos = self.World.Map.CellContaining(self.CenterPosition - distance); endPos = self.World.Map.CellContaining(self.CenterPosition + distance); 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) { 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, 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); })); actor.QueueActivity(new FlyOffMap(actor, Target.FromCell(w, endPos))); actor.QueueActivity(new RemoveSelf()); }); return(true); }
WVec MoveStep(int speed, WAngle facing) { var dir = new WVec(0, -1024, 0).Rotate(WRot.FromYaw(facing)); return(speed * dir / 1024); }
public new IEnumerable <IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, string image, int facings, PaletteReference p) { if (!this.EnabledByDefault) { yield break; } var body = init.Actor.TraitInfoOrDefault <BodyOrientationInfo>(); var turretedInfo = init.Actor.TraitInfos <TurretedInfo>().FirstOrDefault(tt => tt.Turret == this.Turret); if (turretedInfo == null) { yield break; } var facing = init.GetFacing(); var offset = new Func <WVec>(() => body.LocalToWorld(turretedInfo.Offset.Rotate(body.QuantizeOrientation(WRot.FromYaw(facing()), facings)))); var bodyAnim = new Animation(init.World, image, init.GetFacing()); bodyAnim.PlayRepeating(RenderSprites.NormalizeSequence(bodyAnim, init.GetDamageState(), "idle")); if (bodyAnim.CurrentSequence is OffsetsSpriteSequence bodySequence && bodySequence.EmbeddedOffsets.TryGetValue(bodyAnim.Image, out var imageOffset)) { var point = imageOffset.FirstOrDefault(p1 => p1.Id == 0); if (point != null) { offset = () => new(point.X * 32, point.Y * 32, 0); } } if (this.IsPlayerPalette) { p = init.WorldRenderer.Palette(this.Palette + init.Get <OwnerInit>().InternalName); } else if (this.Palette != null) { p = init.WorldRenderer.Palette(this.Palette); } var turretFacing = turretedInfo.WorldFacingFromInit(init); var anim = new Animation(init.World, image, turretFacing); anim.Play(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), this.Sequence)); yield return(new SpriteActorPreview( anim, offset, () => { var tmpOffset = offset(); return -(tmpOffset.Y + tmpOffset.Z) + 1; }, p )); }