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.Collections.Generic;
13 using System.Linq;
14 using OpenRA.Traits;
15 
16 namespace OpenRA.Mods.Common.Traits
17 {
18 	[Desc("Actor has a limited amount of ammo, after using it all the actor must reload in some way.")]
19 	public class AmmoPoolInfo : ITraitInfo
20 	{
21 		[Desc("Name of this ammo pool, used to link reload traits to this pool.")]
22 		public readonly string Name = "primary";
23 
24 		[Desc("Name(s) of armament(s) that use this pool.")]
25 		public readonly string[] Armaments = { "primary", "secondary" };
26 
27 		[Desc("How much ammo does this pool contain when fully loaded.")]
28 		public readonly int Ammo = 1;
29 
30 		[Desc("Initial ammo the actor is created with. Defaults to Ammo.")]
31 		public readonly int InitialAmmo = -1;
32 
33 		[Desc("Defaults to value in Ammo. 0 means no visible pips.")]
34 		public readonly int PipCount = -1;
35 
36 		[Desc("PipType to use for loaded ammo.")]
37 		public readonly PipType PipType = PipType.Green;
38 
39 		[Desc("PipType to use for empty ammo.")]
40 		public readonly PipType PipTypeEmpty = PipType.Transparent;
41 
42 		[Desc("How much ammo is reloaded after a certain period.")]
43 		public readonly int ReloadCount = 1;
44 
45 		[Desc("Sound to play for each reloaded ammo magazine.")]
46 		public readonly string RearmSound = null;
47 
48 		// HACK: Temporarily kept until Rearm activity is gone for good
49 		[Desc("Time to reload per ReloadCount on airfield etc.")]
50 		public readonly int ReloadDelay = 50;
51 
52 		[GrantedConditionReference]
53 		[Desc("The condition to grant to self for each ammo point in this pool.")]
54 		public readonly string AmmoCondition = null;
55 
Create(ActorInitializer init)56 		public object Create(ActorInitializer init) { return new AmmoPool(init.Self, this); }
57 	}
58 
59 	public class AmmoPool : INotifyCreated, INotifyAttack, IPips, ISync
60 	{
61 		public readonly AmmoPoolInfo Info;
62 		readonly Stack<int> tokens = new Stack<int>();
63 		ConditionManager conditionManager;
64 
65 		// HACK: Temporarily needed until Rearm activity is gone for good
66 		[Sync]
67 		public int RemainingTicks;
68 
69 		[Sync]
70 		public int CurrentAmmoCount { get; private set; }
71 
72 		public bool HasAmmo { get { return CurrentAmmoCount > 0; } }
73 		public bool HasFullAmmo { get { return CurrentAmmoCount == Info.Ammo; } }
74 
AmmoPool(Actor self, AmmoPoolInfo info)75 		public AmmoPool(Actor self, AmmoPoolInfo info)
76 		{
77 			Info = info;
78 			CurrentAmmoCount = Info.InitialAmmo < Info.Ammo && Info.InitialAmmo >= 0 ? Info.InitialAmmo : Info.Ammo;
79 		}
80 
GiveAmmo(Actor self, int count)81 		public bool GiveAmmo(Actor self, int count)
82 		{
83 			if (CurrentAmmoCount >= Info.Ammo || count < 0)
84 				return false;
85 
86 			CurrentAmmoCount = (CurrentAmmoCount + count).Clamp(0, Info.Ammo);
87 			UpdateCondition(self);
88 			return true;
89 		}
90 
TakeAmmo(Actor self, int count)91 		public bool TakeAmmo(Actor self, int count)
92 		{
93 			if (CurrentAmmoCount <= 0 || count < 0)
94 				return false;
95 
96 			CurrentAmmoCount = (CurrentAmmoCount - count).Clamp(0, Info.Ammo);
97 			UpdateCondition(self);
98 			return true;
99 		}
100 
INotifyCreated.Created(Actor self)101 		void INotifyCreated.Created(Actor self)
102 		{
103 			conditionManager = self.TraitOrDefault<ConditionManager>();
104 			UpdateCondition(self);
105 
106 			// HACK: Temporarily needed until Rearm activity is gone for good
107 			RemainingTicks = Info.ReloadDelay;
108 		}
109 
INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)110 		void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)
111 		{
112 			if (a != null && Info.Armaments.Contains(a.Info.Name))
113 				TakeAmmo(self, 1);
114 		}
115 
INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel)116 		void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
117 
UpdateCondition(Actor self)118 		void UpdateCondition(Actor self)
119 		{
120 			if (conditionManager == null || string.IsNullOrEmpty(Info.AmmoCondition))
121 				return;
122 
123 			while (CurrentAmmoCount > tokens.Count && tokens.Count < Info.Ammo)
124 				tokens.Push(conditionManager.GrantCondition(self, Info.AmmoCondition));
125 
126 			while (CurrentAmmoCount < tokens.Count && tokens.Count > 0)
127 				conditionManager.RevokeCondition(self, tokens.Pop());
128 		}
129 
GetPips(Actor self)130 		public IEnumerable<PipType> GetPips(Actor self)
131 		{
132 			var pips = Info.PipCount >= 0 ? Info.PipCount : Info.Ammo;
133 
134 			return Enumerable.Range(0, pips).Select(i =>
135 				(CurrentAmmoCount * pips) / Info.Ammo > i ?
136 				Info.PipType : Info.PipTypeEmpty);
137 		}
138 	}
139 }
140