1 #region Copyright & License Information
2 /*
3  * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
4  * This file is part of OpenRA, which is free software. It is made
5  * available to you under the terms of the GNU General Public License
6  * as published by the Free Software Foundation, either version 3 of
7  * the License, or (at your option) any later version. For more
8  * information, see COPYING.
9  */
10 #endregion
11 
12 using System;
13 using OpenRA.Graphics;
14 using OpenRA.Mods.Common.Traits.Render;
15 using OpenRA.Traits;
16 
17 namespace OpenRA.Mods.Common.Traits
18 {
19 	class ThrowsParticleInfo : ITraitInfo, Requires<WithSpriteBodyInfo>, Requires<BodyOrientationInfo>
20 	{
21 		[FieldLoader.Require]
22 		public readonly string Anim = null;
23 
24 		[Desc("Initial position relative to body")]
25 		public readonly WVec Offset = WVec.Zero;
26 
27 		[Desc("Minimum distance to throw the particle")]
28 		public readonly WDist MinThrowRange = new WDist(256);
29 
30 		[Desc("Maximum distance to throw the particle")]
31 		public readonly WDist MaxThrowRange = new WDist(768);
32 
33 		[Desc("Minimum angle to throw the particle")]
34 		public readonly WAngle MinThrowAngle = WAngle.FromDegrees(30);
35 
36 		[Desc("Maximum angle to throw the particle")]
37 		public readonly WAngle MaxThrowAngle = WAngle.FromDegrees(60);
38 
39 		[Desc("Speed to throw the particle (horizontal WPos/tick)")]
40 		public readonly int Velocity = 75;
41 
42 		[Desc("Speed at which the particle turns.")]
43 		public readonly int TurnSpeed = 15;
44 
Create(ActorInitializer init)45 		public object Create(ActorInitializer init) { return new ThrowsParticle(init, this); }
46 	}
47 
48 	class ThrowsParticle : ITick
49 	{
50 		WVec pos;
51 		WVec initialPos;
52 		WVec finalPos;
53 		WAngle angle;
54 
55 		int tick = 0;
56 		int length;
57 
58 		WAngle facing;
59 		WAngle rotation;
60 
ThrowsParticle(ActorInitializer init, ThrowsParticleInfo info)61 		public ThrowsParticle(ActorInitializer init, ThrowsParticleInfo info)
62 		{
63 			var self = init.Self;
64 			var rs = self.Trait<RenderSprites>();
65 			var body = self.Trait<BodyOrientation>();
66 
67 			// TODO: Carry orientation over from the parent instead of just facing
68 			var bodyFacing = init.Contains<DynamicFacingInit>() ? init.Get<DynamicFacingInit, Func<int>>()()
69 				: init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : 0;
70 			facing = WAngle.FromFacing(Turreted.TurretFacingFromInit(init, 0)());
71 
72 			// Calculate final position
73 			var throwRotation = WRot.FromFacing(Game.CosmeticRandom.Next(1024));
74 			var throwDistance = Game.CosmeticRandom.Next(info.MinThrowRange.Length, info.MaxThrowRange.Length);
75 
76 			initialPos = pos = info.Offset.Rotate(body.QuantizeOrientation(self, WRot.FromFacing(bodyFacing)));
77 			finalPos = initialPos + new WVec(throwDistance, 0, 0).Rotate(throwRotation);
78 			angle = new WAngle(Game.CosmeticRandom.Next(info.MinThrowAngle.Angle, info.MaxThrowAngle.Angle));
79 			length = (finalPos - initialPos).Length / info.Velocity;
80 
81 			// Facing rotation
82 			rotation = WAngle.FromFacing(WDist.FromPDF(Game.CosmeticRandom, 2).Length * info.TurnSpeed / 1024);
83 
84 			var anim = new Animation(init.World, rs.GetImage(self), () => facing.Angle / 4);
85 			anim.PlayRepeating(info.Anim);
86 			rs.Add(new AnimationWithOffset(anim, () => pos, null));
87 		}
88 
ITick.Tick(Actor self)89 		void ITick.Tick(Actor self)
90 		{
91 			if (tick >= length)
92 				return;
93 
94 			pos = WVec.LerpQuadratic(initialPos, finalPos, angle, tick++, length);
95 
96 			// Spin the particle
97 			facing += rotation;
98 			rotation = new WAngle(rotation.Angle * 90 / 100);
99 		}
100 	}
101 }
102