1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 1999-2004  Eidos Interactive
4 	Copyright (C) 2005-2020  Warzone 2100 Project
5 
6 	Warzone 2100 is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 2 of the License, or
9 	(at your option) any later version.
10 
11 	Warzone 2100 is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with Warzone 2100; if not, write to the Free Software
18 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /**
21  * @file power.c
22  *
23  * Store PlayerPower and other power related stuff!
24  *
25  */
26 #include <string.h>
27 #include "objectdef.h"
28 #include "power.h"
29 #include "lib/gamelib/gtime.h"
30 #include "lib/sound/audio.h"
31 #include "objmem.h"
32 #include "frontend.h"
33 #include "lib/netplay/netplay.h"
34 #include "multiplay.h"
35 #include "multiint.h"
36 #include "feature.h"
37 #include "structure.h"
38 #include "mission.h"
39 #include "intdisplay.h"
40 
41 #define EXTRACT_POINTS      1
42 #define MAX_POWER           1000000
43 
44 #define FP_ONE ((int64_t)1 << 32)
45 
46 //flag used to check for power calculations to be done or not
47 bool	powerCalculated;
48 
49 /* Updates the current power based on the extracted power and a Power Generator*/
50 static void updateCurrentPower(STRUCTURE *psStruct, UDWORD player, int ticks);
51 static int64_t updateExtractedPower(STRUCTURE *psBuilding);
52 
53 //returns the relevant list based on OffWorld or OnWorld
54 static STRUCTURE *powerStructList(int player);
55 
56 struct PowerRequest
57 {
58 	int64_t  amount;              ///< Amount of power being requested.
59 	unsigned id;                  ///< Structure which is requesting power.
60 };
61 
62 struct PlayerPower
63 {
64 	// All fields are 32.32 fixed point.
65 	int64_t currentPower;                  ///< The current amount of power available to the player.
66 	std::vector<PowerRequest> powerQueue;  ///< Requested power.
67 	int powerModifier;                     ///< Percentage modifier on power from each derrick.
68 	int64_t maxStorage;                    ///< Maximum storage of power, in total.
69 	int64_t extractedPower;                ///< Total amount of extracted power in this game.
70 	int64_t wastedPower;                   ///< Total amount of wasted power in this game.
71 };
72 
73 static PlayerPower asPower[MAX_PLAYERS];
74 
setPowerModifier(int player,int modifier)75 void setPowerModifier(int player, int modifier)
76 {
77 	asPower[player].powerModifier = modifier;
78 }
79 
setPowerMaxStorage(int player,int max)80 void setPowerMaxStorage(int player, int max)
81 {
82 	asPower[player].maxStorage = max * FP_ONE;
83 	asPower[player].currentPower = std::min<int64_t>(asPower[player].maxStorage, asPower[player].currentPower);
84 }
85 
86 /*allocate the space for the playerPower*/
allocPlayerPower()87 bool allocPlayerPower()
88 {
89 	clearPlayerPower();
90 	powerCalculated = true;
91 	return true;
92 }
93 
94 /*clear the playerPower */
clearPlayerPower()95 void clearPlayerPower()
96 {
97 	for (unsigned player = 0; player < MAX_PLAYERS; player++)
98 	{
99 		asPower[player].currentPower = 0;
100 		asPower[player].extractedPower = 0;
101 		asPower[player].wastedPower = 0;
102 		asPower[player].powerModifier = 100;
103 		asPower[player].powerQueue.clear();
104 		asPower[player].maxStorage = MAX_POWER * FP_ONE;
105 	}
106 }
107 
108 /// Returns true iff the power is available. New requests replace old ones (without losing the position in the queue).
addPowerRequest(unsigned player,unsigned id,int64_t amount)109 static bool addPowerRequest(unsigned player, unsigned id, int64_t amount)
110 {
111 	PlayerPower *p = &asPower[player];
112 
113 	int64_t requiredPower = amount;
114 	size_t n;
115 	for (n = 0; n < p->powerQueue.size() && p->powerQueue[n].id != id; ++n)
116 	{
117 		requiredPower += p->powerQueue[n].amount;
118 	}
119 	if (n == p->powerQueue.size())
120 	{
121 		p->powerQueue.resize(n + 1);
122 		p->powerQueue[n].id = id;
123 	}
124 	p->powerQueue[n].amount = amount;
125 	return requiredPower <= p->currentPower;
126 }
127 
delPowerRequest(STRUCTURE * psStruct)128 void delPowerRequest(STRUCTURE *psStruct)
129 {
130 	ASSERT_NOT_NULLPTR_OR_RETURN(, psStruct);
131 	PlayerPower *p = &asPower[psStruct->player];
132 
133 	for (size_t n = 0; n < p->powerQueue.size(); ++n)
134 	{
135 		if (p->powerQueue[n].id == psStruct->id)
136 		{
137 			p->powerQueue.erase(p->powerQueue.begin() + n);
138 			return;
139 		}
140 	}
141 }
142 
checkPrecisePowerRequest(STRUCTURE * psStruct)143 static int64_t checkPrecisePowerRequest(STRUCTURE *psStruct)
144 {
145 	ASSERT_NOT_NULLPTR_OR_RETURN(-1, psStruct);
146 	PlayerPower const *p = &asPower[psStruct->player];
147 
148 	int64_t requiredPower = 0;
149 	for (size_t n = 0; n < p->powerQueue.size(); ++n)
150 	{
151 		requiredPower += p->powerQueue[n].amount;
152 		if (p->powerQueue[n].id == psStruct->id)
153 		{
154 			if (requiredPower <= p->currentPower)
155 			{
156 				return -1;  // Have enough power.
157 			}
158 			return requiredPower - p->currentPower;
159 		}
160 	}
161 
162 	return -1;
163 }
164 
checkPowerRequest(STRUCTURE * psStruct)165 int32_t checkPowerRequest(STRUCTURE *psStruct)
166 {
167 	int64_t power = checkPrecisePowerRequest(psStruct);
168 	return power != -1 ? power / FP_ONE : -1;
169 }
170 
getPreciseQueuedPower(unsigned player)171 static int64_t getPreciseQueuedPower(unsigned player)
172 {
173 	PlayerPower const *p = &asPower[player];
174 
175 	int64_t requiredPower = 0;
176 	for (size_t n = 0; n < p->powerQueue.size(); ++n)
177 	{
178 		requiredPower += p->powerQueue[n].amount;
179 	}
180 	return requiredPower;
181 }
182 
getQueuedPower(int player)183 int getQueuedPower(int player)
184 {
185 	PlayerPower const *p = &asPower[player];
186 
187 	int64_t requiredPower = 0;
188 	for (size_t n = 0; n < p->powerQueue.size(); ++n)
189 	{
190 		requiredPower += p->powerQueue[n].amount;
191 	}
192 	return requiredPower / FP_ONE;
193 }
194 
syncDebugEconomy(unsigned player,char ch)195 static void syncDebugEconomy(unsigned player, char ch)
196 {
197 	ASSERT_OR_RETURN(, player < MAX_PLAYERS, "Bad player (%d)", player);
198 
199 	syncDebug("%c economy%u = %" PRId64"", ch, player, asPower[player].currentPower);
200 }
201 
usePower(int player,uint32_t quantity)202 void usePower(int player, uint32_t quantity)
203 {
204 	ASSERT_OR_RETURN(, player < MAX_PLAYERS, "Invalid player (%d)", player);
205 	syncDebug("usePower%d %" PRId64"-=%u", player, asPower[player].currentPower, quantity);
206 	asPower[player].currentPower = MAX(0, asPower[player].currentPower - quantity * FP_ONE);
207 }
208 
addPower(int player,int32_t quantity)209 void addPower(int player, int32_t quantity)
210 {
211 	ASSERT_OR_RETURN(, player < MAX_PLAYERS, "Bad player (%d)", player);
212 	syncDebug("addPower%d %" PRId64"+=%d", player, asPower[player].currentPower, quantity);
213 	asPower[player].currentPower += quantity * FP_ONE;
214 	if (asPower[player].currentPower > asPower[player].maxStorage)
215 	{
216 		asPower[player].wastedPower += asPower[player].currentPower - asPower[player].maxStorage;
217 		asPower[player].currentPower = asPower[player].maxStorage;
218 	}
219 }
220 
221 /*resets the power calc flag for all players*/
powerCalc(bool on)222 void powerCalc(bool on)
223 {
224 	powerCalculated = on;
225 }
226 
227 /** Each Resource Extractor yields EXTRACT_POINTS per second FOREVER */
updateExtractedPower(STRUCTURE * psBuilding)228 static int64_t updateExtractedPower(STRUCTURE *psBuilding)
229 {
230 	RES_EXTRACTOR		*pResExtractor;
231 	int64_t                 extractedPoints;
232 
233 	pResExtractor = (RES_EXTRACTOR *) psBuilding->pFunctionality;
234 	extractedPoints = 0;
235 
236 	//only extracts points whilst its active ie associated with a power gen
237 	//and has got some power to extract
238 	if (pResExtractor->psPowerGen != nullptr)
239 	{
240 		// include modifier as a %
241 		extractedPoints = asPower[psBuilding->player].powerModifier * EXTRACT_POINTS * FP_ONE / (100 * GAME_UPDATES_PER_SEC);
242 		syncDebug("updateExtractedPower%d = %" PRId64"", psBuilding->player, extractedPoints);
243 	}
244 	ASSERT(extractedPoints >= 0, "extracted negative amount of power");
245 	return extractedPoints;
246 }
247 
248 //returns the relevant list based on OffWorld or OnWorld
powerStructList(int player)249 STRUCTURE *powerStructList(int player)
250 {
251 	ASSERT_OR_RETURN(nullptr, player < MAX_PLAYERS, "Invalid player %d", player);
252 	if (offWorldKeepLists)
253 	{
254 		return (mission.apsStructLists[player]);
255 	}
256 	else
257 	{
258 		return (apsStructLists[player]);
259 	}
260 }
261 
262 /* Update current power based on what Power Generators exist */
updatePlayerPower(int player,int ticks)263 void updatePlayerPower(int player, int ticks)
264 {
265 	STRUCTURE		*psStruct;//, *psList;
266 	int64_t powerBefore = asPower[player].currentPower;
267 
268 	ASSERT_OR_RETURN(, player < MAX_PLAYERS, "Invalid player %d", player);
269 
270 	syncDebugEconomy(player, '<');
271 
272 	for (psStruct = powerStructList(player); psStruct != nullptr; psStruct = psStruct->psNext)
273 	{
274 		if (psStruct->pStructureType->type == REF_POWER_GEN && psStruct->status == SS_BUILT)
275 		{
276 			updateCurrentPower(psStruct, player, ticks);
277 		}
278 	}
279 	syncDebug("updatePlayerPower%u %" PRId64"->%" PRId64"", player, powerBefore, asPower[player].currentPower);
280 
281 	syncDebugEconomy(player, '<');
282 }
283 
284 /* Updates the current power based on the extracted power and a Power Generator*/
updateCurrentPower(STRUCTURE * psStruct,UDWORD player,int ticks)285 static void updateCurrentPower(STRUCTURE *psStruct, UDWORD player, int ticks)
286 {
287 	POWER_GEN *psPowerGen = (POWER_GEN *)psStruct->pFunctionality;
288 
289 	ASSERT_OR_RETURN(, player < MAX_PLAYERS, "Invalid player %u", player);
290 
291 	//each power gen can cope with its associated resource extractors
292 	int64_t extractedPower = 0;
293 	for (int i = 0; i < NUM_POWER_MODULES; ++i)
294 	{
295 		auto &extractor = psPowerGen->apResExtractors[i];
296 		if (extractor && extractor->died) {
297 			syncDebugStructure(extractor, '-');
298 			extractor = nullptr;  // Clear pointer.
299 		}
300 		if (extractor)
301 		{
302 			extractedPower += updateExtractedPower(extractor);
303 		}
304 	}
305 
306 	int multiplier = getBuildingPowerPoints(psStruct);
307 	syncDebug("updateCurrentPower%d = %" PRId64",%u", player, extractedPower, multiplier);
308 
309 	asPower[player].currentPower += (extractedPower * multiplier) / 100 * ticks;
310 	asPower[player].extractedPower += (extractedPower * multiplier) / 100 * ticks;
311 	ASSERT(asPower[player].currentPower >= 0, "negative power");
312 	if (asPower[player].currentPower > asPower[player].maxStorage)
313 	{
314 		asPower[player].wastedPower += asPower[player].currentPower - asPower[player].maxStorage;
315 		asPower[player].currentPower = asPower[player].maxStorage;
316 	}
317 }
318 
setPower(unsigned player,int32_t power)319 void setPower(unsigned player, int32_t power)
320 {
321 	ASSERT_OR_RETURN(, player < MAX_PLAYERS, "Invalid player (%u)", player);
322 
323 	syncDebug("setPower%d %" PRId64"->%d", player, asPower[player].currentPower, power);
324 	asPower[player].currentPower = power * FP_ONE;
325 	ASSERT(asPower[player].currentPower >= 0, "negative power");
326 }
327 
getPower(unsigned player)328 int32_t getPower(unsigned player)
329 {
330 	ASSERT_OR_RETURN(0, player < MAX_PLAYERS, "Invalid player (%u)", player);
331 
332 	return asPower[player].currentPower / FP_ONE;
333 }
334 
getPrecisePower(unsigned player)335 int64_t getPrecisePower(unsigned player)
336 {
337 	ASSERT_OR_RETURN(0, player < MAX_PLAYERS, "Invalid player (%u)", player);
338 
339 	return asPower[player].currentPower;
340 }
341 
getExtractedPower(unsigned player)342 int64_t getExtractedPower(unsigned player)
343 {
344 	return asPower[player].extractedPower / FP_ONE;
345 }
346 
getWastedPower(unsigned player)347 int64_t getWastedPower(unsigned player)
348 {
349 	return asPower[player].wastedPower / FP_ONE;
350 }
351 
getPowerMinusQueued(unsigned player)352 int32_t getPowerMinusQueued(unsigned player)
353 {
354 	ASSERT_OR_RETURN(0, player < MAX_PLAYERS, "Invalid player (%u)", player);
355 
356 	return (asPower[player].currentPower - getPreciseQueuedPower(player)) / FP_ONE;
357 }
358 
requestPowerFor(STRUCTURE * psStruct,int32_t amount)359 bool requestPowerFor(STRUCTURE *psStruct, int32_t amount)
360 {
361 	return requestPrecisePowerFor(psStruct, amount * FP_ONE);
362 }
363 
requestPrecisePowerFor(STRUCTURE * psStruct,int64_t amount)364 bool requestPrecisePowerFor(STRUCTURE *psStruct, int64_t amount)
365 {
366 	if (amount <= 0 || !powerCalculated)
367 	{
368 		return true;
369 	}
370 
371 	bool haveEnoughPower = addPowerRequest(psStruct->player, psStruct->id, amount);
372 	if (haveEnoughPower)
373 	{
374 		// you can have it
375 		asPower[psStruct->player].currentPower -= amount;
376 		delPowerRequest(psStruct);
377 		syncDebug("requestPrecisePowerFor%d,%u amount%" PRId64"", psStruct->player, psStruct->id, amount);
378 		return true;
379 	}
380 	syncDebug("requestPrecisePowerFor%d,%u wait,amount%" PRId64"", psStruct->player, psStruct->id, amount);
381 	return false;  // Not enough power in the queue.
382 }
383