context: #region Copyright & License Information
  /*
   * Copyright 2007-2021 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 System.Linq;
  using OpenRA.GameRules;
  using OpenRA.Graphics;
  using OpenRA.Mods.Common.Effects;
  using OpenRA.Mods.Common.Graphics;
  using OpenRA.Mods.Common.Traits;
  using OpenRA.Primitives;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.Common.Projectiles
  {
  	public class BulletInfo : IProjectileInfo
  	{
  		[Desc("Projectile speed in WDist / tick, two values indicate variable velocity.")]
  		public readonly WDist[] Speed = { new WDist(17) };
  
  		[Desc("The maximum/constant/incremental inaccuracy used in conjunction with the InaccuracyType property.")]
  		public readonly WDist Inaccuracy = WDist.Zero;
  
  		[Desc("Controls the way inaccuracy is calculated. Possible values are 'Maximum' - scale from 0 to max with range, 'PerCellIncrement' - scale from 0 with range and 'Absolute' - use set value regardless of range.")]
  		public readonly InaccuracyType InaccuracyType = InaccuracyType.Maximum;
  
  		[Desc("Image to display.")]
  		public readonly string Image = null;
  
  		[SequenceReference(nameof(Image), allowNullImage: true)]
  		[Desc("Loop a randomly chosen sequence of Image from this list while this projectile is moving.")]
  		public readonly string[] Sequences = { "idle" };
  
  		[PaletteReference(nameof(IsPlayerPalette))]
  		[Desc("The palette used to draw this projectile.")]
  		public readonly string Palette = "effect";
  
  		[Desc("Palette is a player palette BaseName")]
  		public readonly bool IsPlayerPalette = false;
  
  		[Desc("Does this projectile have a shadow?")]
  		public readonly bool Shadow = false;
  
  		[Desc("Color to draw shadow if Shadow is true.")]
  		public readonly Color ShadowColor = Color.FromArgb(140, 0, 0, 0);
  
  		[Desc("Trail animation.")]
  		public readonly string TrailImage = null;
  
  		[SequenceReference(nameof(TrailImage), allowNullImage: true)]
  		[Desc("Loop a randomly chosen sequence of TrailImage from this list while this projectile is moving.")]
  		public readonly string[] TrailSequences = { "idle" };
  
  		[Desc("Interval in ticks between each spawned Trail animation.")]
  		public readonly int TrailInterval = 2;
  
  		[Desc("Delay in ticks until trail animation is spawned.")]
  		public readonly int TrailDelay = 1;
  
  		[PaletteReference(nameof(TrailUsePlayerPalette))]
  		[Desc("Palette used to render the trail sequence.")]
  		public readonly string TrailPalette = "effect";
  
  		[Desc("Use the Player Palette to render the trail sequence.")]
  		public readonly bool TrailUsePlayerPalette = false;
  
  		[Desc("Is this 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("Arc in WAngles, two values indicate variable arc.")]
  		public readonly WAngle[] LaunchAngle = { WAngle.Zero };
  
  		[Desc("Up to how many times does this bullet bounce when touching ground without hitting a target.",
  			"0 implies exploding on contact with the originally targeted position.")]
  		public readonly int BounceCount = 0;
  
  		[Desc("Modify distance of each bounce by this percentage of previous distance.")]
  		public readonly int BounceRangeModifier = 60;
  
  		[Desc("Sound to play when the projectile hits the ground, but not the target.")]
  		public readonly string BounceSound = null;
  
  		[Desc("Terrain where the projectile explodes instead of bouncing.")]
  		public readonly HashSet<string> InvalidBounceTerrain = new HashSet<string>();
  
  		[Desc("Trigger the explosion if the projectile touches an actor thats owner has these player relationships.")]
  		public readonly PlayerRelationship ValidBounceBlockerRelationships = PlayerRelationship.Enemy | PlayerRelationship.Neutral;
  
  		[Desc("Altitude above terrain below which to explode. Zero effectively deactivates airburst.")]
  		public readonly WDist AirburstAltitude = WDist.Zero;
  
  		public readonly int ContrailLength = 0;
  		public readonly int ContrailZOffset = 2047;
  		public readonly Color ContrailColor = Color.White;
  		public readonly bool ContrailUsePlayerColor = false;
  		public readonly int ContrailDelay = 1;
  		public readonly WDist ContrailWidth = new WDist(64);
  
  		public IProjectile Create(ProjectileArgs args) { return new Bullet(this, args); }
  	}
  
  	public class Bullet : IProjectile, ISync
  	{
  		readonly BulletInfo info;
  		readonly ProjectileArgs args;
  		readonly Animation anim;
  		readonly WAngle facing;
  		readonly WAngle angle;
  		readonly WDist speed;
  		readonly string trailPalette;
  
  		readonly float3 shadowColor;
  		readonly float shadowAlpha;
  
  		ContrailRenderable contrail;
  
  		[Sync]
  		WPos pos, lastPos, target, source;
  
  		int length;
  		int ticks, smokeTicks;
  		int remainingBounces;
  
  		public Bullet(BulletInfo info, ProjectileArgs args)
  		{
  			this.info = info;
  			this.args = args;
  			pos = args.Source;
  			source = args.Source;
  
  			var world = args.SourceActor.World;
  
  			if (info.LaunchAngle.Length > 1)
  				angle = new WAngle(world.SharedRandom.Next(info.LaunchAngle[0].Angle, info.LaunchAngle[1].Angle));
  			else
  				angle = info.LaunchAngle[0];
  
  			if (info.Speed.Length > 1)
  				speed = new WDist(world.SharedRandom.Next(info.Speed[0].Length, info.Speed[1].Length));
  			else
  				speed = info.Speed[0];
  
  			target = args.PassiveTarget;
  			if (info.Inaccuracy.Length > 0)
  			{
  				var maxInaccuracyOffset = Util.GetProjectileInaccuracy(info.Inaccuracy.Length, info.InaccuracyType, args);
  				target += WVec.FromPDF(world.SharedRandom, 2) * maxInaccuracyOffset / 1024;
  			}
  
  			if (info.AirburstAltitude > WDist.Zero)
  				target += new WVec(WDist.Zero, WDist.Zero, info.AirburstAltitude);
  
  			facing = (target - pos).Yaw;
  			length = Math.Max((target - pos).Length / speed.Length, 1);
  
  			if (!string.IsNullOrEmpty(info.Image))
  			{
  				anim = new Animation(world, info.Image, new Func<WAngle>(GetEffectiveFacing));
  				anim.PlayRepeating(info.Sequences.Random(world.SharedRandom));
  			}
  
  			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)
  				trailPalette += args.SourceActor.Owner.InternalName;
  
  			smokeTicks = info.TrailDelay;
  			remainingBounces = info.BounceCount;
  
  			shadowColor = new float3(info.ShadowColor.R, info.ShadowColor.G, info.ShadowColor.B) / 255f;
  			shadowAlpha = info.ShadowColor.A;
  		}
  
  		WAngle GetEffectiveFacing()
  		{
  			var at = (float)ticks / (length - 1);
  			var attitude = angle.Tan() * (1 - 2 * at) / (4 * 1024);
  
  			var u = (facing.Angle % 512) / 512f;
  			var scale = 2048 * u * (1 - u);
  
  			var effective = (int)(facing.Angle < 512
  				? facing.Angle - scale * attitude
  				: facing.Angle + scale * attitude);
  
  			return new WAngle(effective);
  		}
  
  		public void Tick(World world)
  		{
  			anim?.Tick();
  
  			lastPos = pos;
  
gt: 			pos = WPos.LerpQuadratic(source, target, angle, ticks, length);