#region Copyright & License Information /* * Copyright 2007-2017 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { public class TurretedInfo : ITraitInfo, UsesInit, Requires { public readonly string Turret = "primary"; [Desc("Speed at which the turret turns.")] public readonly int TurnSpeed = 255; public readonly int InitialFacing = 0; [Desc("Number of ticks before turret is realigned. (-1 turns off realignment)")] public readonly int RealignDelay = 40; [Desc("Muzzle position relative to turret or body. (forward, right, up) triples")] public readonly WVec Offset = WVec.Zero; public virtual object Create(ActorInitializer init) { return new Turreted(init, this); } } public class Turreted : ITick, ISync, INotifyCreated, IDeathActorInitModifier, IActorPreviewInitModifier { readonly TurretedInfo info; AttackTurreted attack; IFacing facing; BodyOrientation body; [Sync] public int QuantizedFacings = 0; [Sync] public int TurretFacing = 0; public int? DesiredFacing; int realignTick = 0; // For subclasses that want to move the turret relative to the body protected WVec localOffset = WVec.Zero; public WVec Offset { get { return info.Offset + localOffset; } } public string Name { get { return info.Turret; } } public static Func TurretFacingFromInit(IActorInitializer init, int def, string turret = null) { if (turret != null && init.Contains()) { Func facing; if (init.Get>>().TryGetValue(turret, out facing)) return facing; } if (turret != null && init.Contains()) { int facing; if (init.Get>().TryGetValue(turret, out facing)) return () => facing; } if (init.Contains()) { var facing = init.Get(); return () => facing; } if (init.Contains()) return init.Get>(); if (init.Contains()) { var facing = init.Get(); return () => facing; } return () => def; } public Turreted(ActorInitializer init, TurretedInfo info) { this.info = info; TurretFacing = TurretFacingFromInit(init, info.InitialFacing, info.Turret)(); } public void Created(Actor self) { attack = self.TraitOrDefault(); facing = self.TraitOrDefault(); body = self.Trait(); } public virtual void Tick(Actor self) { // NOTE: FaceTarget is called in AttackTurreted.CanAttack if the turret has a target. if (attack != null) { if (!attack.IsAttacking) { if (realignTick < info.RealignDelay) realignTick++; else if (info.RealignDelay > -1) DesiredFacing = null; MoveTurret(); } } else { realignTick = 0; MoveTurret(); } } void MoveTurret() { var df = DesiredFacing ?? (facing != null ? facing.Facing : TurretFacing); TurretFacing = Util.TickFacing(TurretFacing, df, info.TurnSpeed); } public bool FaceTarget(Actor self, Target target) { if (self.IsDisabled()) return false; var delta = target.CenterPosition - self.CenterPosition; DesiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : TurretFacing; MoveTurret(); return HasAchievedDesiredFacing; } public virtual bool HasAchievedDesiredFacing { get { return DesiredFacing == null || TurretFacing == DesiredFacing.Value; } } // Turret offset in world-space public WVec Position(Actor self) { var bodyOrientation = body.QuantizeOrientation(self, self.Orientation); return body.LocalToWorld(Offset.Rotate(bodyOrientation)); } // Orientation in world-space public WRot WorldOrientation(Actor self) { // Hack: turretFacing is relative to the world, so subtract the body yaw var world = WRot.FromYaw(WAngle.FromFacing(TurretFacing)); if (QuantizedFacings == 0) return world; // Quantize orientation to match a rendered sprite // Implies no pitch or yaw var facing = body.QuantizeFacing(world.Yaw.Angle / 4, QuantizedFacings); return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facing)); } public void ModifyDeathActorInit(Actor self, TypeDictionary init) { var facings = init.GetOrDefault(); if (facings == null) { facings = new TurretFacingsInit(); init.Add(facings); } if (!facings.Value(self.World).ContainsKey(Name)) facings.Value(self.World).Add(Name, TurretFacing); } void IActorPreviewInitModifier.ModifyActorPreviewInit(Actor self, TypeDictionary inits) { var facings = inits.GetOrDefault(); if (facings == null) { facings = new DynamicTurretFacingsInit(); inits.Add(facings); } Func bodyFacing = () => facing.Facing; var dynamicFacing = inits.GetOrDefault(); var staticFacing = inits.GetOrDefault(); if (dynamicFacing != null) bodyFacing = dynamicFacing.Value(self.World); else if (staticFacing != null) bodyFacing = () => staticFacing.Value(self.World); // Freeze the relative turret facing to its current value var facingOffset = TurretFacing - bodyFacing(); facings.Value(self.World).Add(Name, () => bodyFacing() + facingOffset); } } public class TurretFacingInit : IActorInit { [FieldFromYamlKey] readonly int value = 128; public TurretFacingInit() { } public TurretFacingInit(int init) { value = init; } public int Value(World world) { return value; } } public class TurretFacingsInit : IActorInit> { [DictionaryFromYamlKey] readonly Dictionary value = new Dictionary(); public TurretFacingsInit() { } public TurretFacingsInit(Dictionary init) { value = init; } public Dictionary Value(World world) { return value; } } public class DynamicTurretFacingsInit : IActorInit>> { readonly Dictionary> value = new Dictionary>(); public DynamicTurretFacingsInit() { } public DynamicTurretFacingsInit(Dictionary> init) { value = init; } public Dictionary> Value(World world) { return value; } } }