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