context: #region Copyright & License Information
  /*
   * Copyright 2007-2015 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. For more information,
   * see COPYING.
   */
  #endregion
  
  using System.Collections.Generic;
  using System.Drawing;
  using System.Linq;
  using OpenRA.Effects;
  using OpenRA.GameRules;
  using OpenRA.Graphics;
  using OpenRA.Mods.Common.Graphics;
  using OpenRA.Mods.Common.Traits;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.Common.Effects
  {
  	public class MissileInfo : IProjectileInfo
  	{
  		[Desc("Name of the image containing the projectile sequence.")]
  		public readonly string Image = null;
  
  		[Desc("Projectile sequence name.")]
  		[SequenceReference("Image")] public readonly string Sequence = "idle";
  
  		[Desc("Palette used to render the projectile sequence.")]
  		[PaletteReference] public readonly string Palette = "effect";
  
  		[Desc("Should the projectile's shadow be rendered?")]
  		public readonly bool Shadow = false;
  
  		[Desc("Minimum vertical launch angle (pitch).")]
  		public readonly WAngle MinimumLaunchAngle = new WAngle(-64);
  
  		[Desc("Maximum vertical launch angle (pitch).")]
  		public readonly WAngle MaximumLaunchAngle = new WAngle(128);
  
  		[Desc("Minimum launch speed in WDist / tick")]
  		public readonly WDist MinimumLaunchSpeed = new WDist(75);
  
  		[Desc("Maximum launch speed in WDist / tick")]
  		public readonly WDist MaximumLaunchSpeed = new WDist(200);
  
  		[Desc("Maximum projectile speed in WDist / tick")]
  		public readonly WDist MaximumSpeed = new WDist(384);
  
  		[Desc("Projectile acceleration when propulsion activated.")]
  		public readonly WDist Acceleration = new WDist(5);
  
  		[Desc("How many ticks before this missile is armed and can explode.")]
  		public readonly int Arm = 0;
  
  		[Desc("Is the missile blocked by actors with BlocksProjectiles: trait.")]
  		public readonly bool Blockable = true;
  
  		[Desc("Width of projectile (used for finding blocking actors).")]
  		public readonly WDist Width = new WDist(1);
  
  		[Desc("Extra search radius beyond path for blocking actors.")]
  		public readonly WDist TargetExtraSearchRadius = new WDist(1536);
  
  		[Desc("Maximum offset at the maximum range")]
  		public readonly WDist Inaccuracy = WDist.Zero;
  
  		[Desc("Probability of locking onto and following target.")]
  		public readonly int LockOnProbability = 100;
  
  		[Desc("Horizontal rate of turn.")]
  		public readonly int HorizontalRateOfTurn = 5;
  
  		[Desc("Vertical rate of turn.")]
  		public readonly int VerticalRateOfTurn = 6;
  
  		[Desc("Gravity applied while in free fall.")]
  		public readonly int Gravity = 10;
  
  		[Desc("Run out of fuel after being activated this many ticks. Zero for unlimited fuel.")]
  		public readonly int RangeLimit = 0;
  
  		[Desc("Explode when running out of fuel.")]
  		public readonly bool ExplodeWhenEmpty = true;
  
  		[Desc("Altitude above terrain below which to explode. Zero effectively deactivates airburst.")]
  		public readonly WDist AirburstAltitude = WDist.Zero;
  
  		[Desc("Cruise altitude. Zero means no cruise altitude used.")]
  		public readonly WDist CruiseAltitude = new WDist(512);
  
  		[Desc("Activate homing mechanism after this many ticks.")]
  		public readonly int HomingActivationDelay = 0;
  
  		[Desc("Image that contains the trail animation.")]
  		public readonly string TrailImage = null;
  
  		[Desc("Smoke sequence name.")]
  		[SequenceReference("TrailImage")] public readonly string TrailSequence = "idle";
  
  		[Desc("Palette used to render the smoke sequence.")]
  		[PaletteReference("TrailUsePlayerPalette")] public readonly string TrailPalette = "effect";
  
  		[Desc("Use the Player Palette to render the smoke sequence.")]
  		public readonly bool TrailUsePlayerPalette = false;
  
  		[Desc("Interval in ticks between spawning smoke animation.")]
  		public readonly int TrailInterval = 2;
  
  		[Desc("Should smoke animation be spawned when the propulsion is not activated.")]
  		public readonly bool TrailWhenDeactivated = false;
  
  		public readonly int ContrailLength = 0;
  
  		public readonly int ContrailZOffset = 2047;
  
  		public readonly WDist ContrailWidth = new WDist(64);
  
  		public readonly Color ContrailColor = Color.White;
  
  		public readonly bool ContrailUsePlayerColor = false;
  
  		public readonly int ContrailDelay = 1;
  
  		[Desc("Should missile targeting be thrown off by nearby actors with JamsMissiles.")]
  		public readonly bool Jammable = true;
  
  		[Desc("Range of facings by which jammed missiles can stray from current path.")]
  		public readonly int JammedDiversionRange = 20;
  
  		[Desc("Explodes when leaving the following terrain type, e.g., Water for torpedoes.")]
  		public readonly string BoundToTerrainType = "";
  
  		[Desc("Explodes when inside this proximity radius to target.",
  			"Note: If this value is lower than the missile speed, this check might",
  			"not trigger fast enough, causing the missile to fly past the target.")]
  		public readonly WDist CloseEnough = new WDist(298);
  
  		public IEffect Create(ProjectileArgs args) { return new Missile(this, args); }
  	}
  
  	// TODO: double check square roots!!!
  	public class Missile : IEffect, ISync
  	{
  		enum States
  		{
  			Freefall,
  			Homing,
  			Hitting
  		}
  
  		readonly MissileInfo info;
  		readonly ProjectileArgs args;
  		readonly Animation anim;
  
  		readonly WVec gravity;
  
  		int ticks;
  
  		int ticksToNextSmoke;
  		ContrailRenderable contrail;
  		string trailPalette;
  
  		States state;
  		bool targetPassedBy;
  		bool lockOn = false;
  		bool allowPassBy; // TODO: use this also with high minimum launch angle settings
  
  		WPos targetPosition;
  		WVec offset;
  
  		WVec tarVel;
  		WVec predVel;
  
  		[Sync] WPos pos;
  		WVec velocity;
  		int speed;
  		int loopRadius;
  
  		int renderFacing;
  		[Sync] int hFacing;
  		[Sync] int vFacing;
  
  		public Actor SourceActor { get { return args.SourceActor; } }
  		public Target GuidedTarget { get { return args.GuidedTarget; } }
  
  		public Missile(MissileInfo info, ProjectileArgs args)
  		{
  			this.info = info;
  			this.args = args;
  
  			pos = args.Source;
  			hFacing = args.Facing;
  			gravity = new WVec(0, 0, -info.Gravity);
  			targetPosition = args.PassiveTarget;
  
  			var world = args.SourceActor.World;
  
  			if (info.Inaccuracy.Length > 0)
  			{
  				var inaccuracy = OpenRA.Traits.Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers);
  				offset = WVec.FromPDF(world.SharedRandom, 2) * inaccuracy / 1024;
  			}
  
  			DetermineLaunchSpeedAndAngle(world, out speed, out vFacing);
  
  			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)));
  
  			if (world.SharedRandom.Next(100) <= info.LockOnProbability)
  				lockOn = true;
  
  			if (!string.IsNullOrEmpty(info.Image))
  			{
  				anim = new Animation(world, info.Image, () => renderFacing);
  				anim.PlayRepeating(info.Sequence);
  			}
  
  			if (info.ContrailLength > 0)
  			{
  				var color = info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.SourceActor) : info.ContrailColor;
  				contrail = new ContrailRenderable(world, color, info.ContrailWidth, info.ContrailLength, info.ContrailDelay, info.ContrailZOffset);
  			}
  
  			trailPalette = info.TrailPalette;
  			if (info.TrailUsePlayerPalette)
  
gt: 				trailPalette += args.SourceActor.Owner.InternalName;