1 #include "Debug.h"
2 #include "LoadSaveData.h"
3 #include "Types.h"
4 #include "Game_Events.h"
5 #include "Game_Clock.h"
6 #include "MemMan.h"
7 #include "Font_Control.h"
8 #include "Message.h"
9 #include "Text.h"
10 #include "FileMan.h"
11 #include "Logger.h"
12 
13 STRATEGICEVENT									*gpEventList = NULL;
14 
15 extern UINT32 guiGameClock;
16 BOOLEAN gfPreventDeletionOfAnyEvent = FALSE;
17 static BOOLEAN gfEventDeletionPending = FALSE;
18 
19 static BOOLEAN gfProcessingGameEvents = FALSE;
20 UINT32	guiTimeStampOfCurrentlyExecutingEvent = 0;
21 
22 
GameEventsPending(UINT32 const adjustment)23 bool GameEventsPending(UINT32 const adjustment)
24 {
25 	STRATEGICEVENT* const e = gpEventList;
26 	return e && e->uiTimeStamp <= GetWorldTotalSeconds() + adjustment;
27 }
28 
29 
DeleteEventsWithDeletionPending()30 static void DeleteEventsWithDeletionPending()
31 {
32 	if (!gfEventDeletionPending) return;
33 	gfEventDeletionPending = FALSE;
34 
35 	for (STRATEGICEVENT** anchor = &gpEventList; *anchor;)
36 	{
37 		STRATEGICEVENT* const i = *anchor;
38 		if (i->ubFlags & SEF_DELETION_PENDING)
39 		{
40 			*anchor = i->next;
41 			delete i;
42 		}
43 		else
44 		{
45 			anchor = &i->next;
46 		}
47 	}
48 }
49 
50 
AdjustClockToEventStamp(STRATEGICEVENT * pEvent,UINT32 * puiAdjustment)51 static void AdjustClockToEventStamp(STRATEGICEVENT* pEvent, UINT32* puiAdjustment)
52 {
53 	UINT32 uiDiff;
54 
55 	uiDiff = pEvent->uiTimeStamp - guiGameClock;
56 	guiGameClock += uiDiff;
57 	*puiAdjustment -= uiDiff;
58 
59 	//Calculate the day, hour, and minutes.
60 	guiDay = ( guiGameClock / NUM_SEC_IN_DAY );
61 	guiHour = ( guiGameClock - ( guiDay * NUM_SEC_IN_DAY ) ) / NUM_SEC_IN_HOUR;
62 	guiMin	= ( guiGameClock - ( ( guiDay * NUM_SEC_IN_DAY ) + ( guiHour * NUM_SEC_IN_HOUR ) ) ) / NUM_SEC_IN_MIN;
63 
64 	WORLDTIMESTR = ST::format("{} {}, {02d}:{02d}", gpGameClockString, guiDay, guiHour, guiMin);
65 }
66 
67 
ProcessPendingGameEvents(UINT32 uiAdjustment,const UINT8 ubWarpCode)68 void ProcessPendingGameEvents(UINT32 uiAdjustment, const UINT8 ubWarpCode)
69 {
70 	STRATEGICEVENT *curr, *pEvent, *prev, *temp;
71 	BOOLEAN fDeleteEvent = FALSE, fDeleteQueuedEvent = FALSE;
72 
73 	gfTimeInterrupt = FALSE;
74 	gfProcessingGameEvents = TRUE;
75 
76 	//While we have events inside the time range to be updated, process them...
77 	curr = gpEventList;
78 	prev = NULL; //prev only used when warping time to target time.
79 	while( !gfTimeInterrupt && curr && curr->uiTimeStamp <= guiGameClock + uiAdjustment )
80 	{
81 		fDeleteEvent = FALSE;
82 		//Update the time by the difference, but ONLY if the event comes after the current time.
83 		//In the beginning of the game, series of events are created that are placed in the list
84 		//BEFORE the start time.  Those events will be processed without influencing the actual time.
85 		if( curr->uiTimeStamp > guiGameClock && ubWarpCode != WARPTIME_PROCESS_TARGET_TIME_FIRST )
86 		{
87 			AdjustClockToEventStamp( curr, &uiAdjustment );
88 		}
89 		//Process the event
90 		if( ubWarpCode != WARPTIME_PROCESS_TARGET_TIME_FIRST )
91 		{
92 			fDeleteEvent = ExecuteStrategicEvent( curr );
93 		}
94 		else if( curr->uiTimeStamp == guiGameClock + uiAdjustment )
95 		{ //if we are warping to the target time to process that event first,
96 			if( !curr->next || curr->next->uiTimeStamp > guiGameClock + uiAdjustment )
97 			{ //make sure that we are processing the last event for that second
98 				AdjustClockToEventStamp( curr, &uiAdjustment );
99 
100 				fDeleteEvent = ExecuteStrategicEvent( curr );
101 
102 				if( curr && prev && fDeleteQueuedEvent )
103 				{ //The only case where we are deleting a node in the middle of the list
104 					prev->next = curr->next;
105 				}
106 			}
107 			else
108 			{ //We are at the current target warp time however, there are still other events following in this time cycle.
109 				//We will only target the final event in this time.  NOTE:  Events are posted using a FIFO method
110 				prev = curr;
111 				curr = curr->next;
112 				continue;
113 			}
114 		}
115 		else
116 		{ //We are warping time to the target time.  We haven't found the event yet,
117 			//so continuing will keep processing the list until we find it.  NOTE:  Events are posted using a FIFO method
118 			prev = curr;
119 			curr = curr->next;
120 			continue;
121 		}
122 		if( fDeleteEvent )
123 		{
124 			//Determine if event node is a special event requiring reposting
125 			switch( curr->ubEventType )
126 			{
127 				case RANGED_EVENT:
128 					AddAdvancedStrategicEvent(ENDRANGED_EVENT, static_cast<StrategicEventKind>(curr->ubCallbackID), curr->uiTimeStamp + curr->uiTimeOffset, curr->uiParam);
129 					break;
130 				case PERIODIC_EVENT:
131 					pEvent = AddAdvancedStrategicEvent(PERIODIC_EVENT, static_cast<StrategicEventKind>(curr->ubCallbackID), curr->uiTimeStamp + curr->uiTimeOffset, curr->uiParam);
132 					if( pEvent )
133 						pEvent->uiTimeOffset = curr->uiTimeOffset;
134 					break;
135 				case EVERYDAY_EVENT:
136 					AddAdvancedStrategicEvent(EVERYDAY_EVENT, static_cast<StrategicEventKind>(curr->ubCallbackID), curr->uiTimeStamp + NUM_SEC_IN_DAY, curr->uiParam);
137 					break;
138 			}
139 			if( curr == gpEventList )
140 			{
141 				gpEventList = gpEventList->next;
142 				delete curr;
143 				curr = gpEventList;
144 				prev = NULL;
145 			}
146 			else
147 			{
148 				temp = curr;
149 				prev->next = curr->next;
150 				curr = curr->next;
151 				delete temp;
152 			}
153 		}
154 		else
155 		{
156 			prev = curr;
157 			curr = curr->next;
158 		}
159 	}
160 
161 	gfProcessingGameEvents = FALSE;
162 
163 	DeleteEventsWithDeletionPending();
164 
165 	if( uiAdjustment && !gfTimeInterrupt )
166 		guiGameClock += uiAdjustment;
167 }
168 
169 
AddSameDayStrategicEvent(StrategicEventKind const ubCallbackID,UINT32 const uiMinStamp,UINT32 const uiParam)170 BOOLEAN AddSameDayStrategicEvent(StrategicEventKind const ubCallbackID, UINT32 const uiMinStamp, UINT32 const uiParam)
171 {
172 	return( AddStrategicEvent( ubCallbackID, uiMinStamp + GetWorldDayInMinutes(), uiParam ) );
173 }
174 
AddFutureDayStrategicEvent(StrategicEventKind const ubCallbackID,UINT32 const uiMinStamp,UINT32 const uiParam,UINT32 const uiNumDaysFromPresent)175 BOOLEAN AddFutureDayStrategicEvent(StrategicEventKind const ubCallbackID, UINT32 const uiMinStamp, UINT32 const uiParam, UINT32 const uiNumDaysFromPresent)
176 {
177 	UINT32 uiDay;
178 	uiDay = GetWorldDay();
179 	return( AddStrategicEvent( ubCallbackID, uiMinStamp + GetFutureDayInMinutes( uiDay + uiNumDaysFromPresent ), uiParam ) );
180 }
181 
AddAdvancedStrategicEvent(StrategicEventFrequency const event_type,StrategicEventKind const callback_id,UINT32 const timestamp,UINT32 const param)182 STRATEGICEVENT* AddAdvancedStrategicEvent(StrategicEventFrequency const event_type, StrategicEventKind const callback_id, UINT32 const timestamp, UINT32 const param)
183 {
184 	if (gfProcessingGameEvents && timestamp <= guiTimeStampOfCurrentlyExecutingEvent)
185 	{ /* Prevent infinite loops of posting events that are the same time or
186 		* earlier than the event currently being processed */
187 		SLOGD("Event Rejected (id %d): Can't post events <= time while inside an event callback. This is a special case situation that isn't a bug.", callback_id);
188 		return 0;
189 	}
190 
191 	STRATEGICEVENT* const n = new STRATEGICEVENT{};
192 	n->ubCallbackID = callback_id;
193 	n->uiParam      = param;
194 	n->ubEventType  = event_type;
195 	n->uiTimeStamp  = timestamp;
196 	n->uiTimeOffset = 0;
197 
198 	// Search list for a place to insert
199 	STRATEGICEVENT** anchor = &gpEventList;
200 	for (; *anchor; anchor = &(*anchor)->next)
201 	{
202 		if (timestamp < (*anchor)->uiTimeStamp) break;
203 	}
204 	n->next = *anchor;
205 	*anchor = n;
206 
207 	return n;
208 }
209 
210 
AddStrategicEvent(StrategicEventKind const ubCallbackID,UINT32 const uiMinStamp,UINT32 const uiParam)211 BOOLEAN AddStrategicEvent(StrategicEventKind const ubCallbackID, UINT32 const uiMinStamp, UINT32 const uiParam)
212 {
213 	if( AddAdvancedStrategicEvent( ONETIME_EVENT, ubCallbackID, uiMinStamp*60, uiParam ) )
214 		return TRUE;
215 	return FALSE;
216 }
217 
AddStrategicEventUsingSeconds(StrategicEventKind const ubCallbackID,UINT32 const uiSecondStamp,UINT32 const uiParam)218 BOOLEAN AddStrategicEventUsingSeconds(StrategicEventKind const ubCallbackID, UINT32 const uiSecondStamp, UINT32 const uiParam)
219 {
220 	if( AddAdvancedStrategicEvent( ONETIME_EVENT, ubCallbackID, uiSecondStamp, uiParam ) )
221 		return TRUE;
222 	return FALSE;
223 }
224 
225 
AddRangedStrategicEvent(StrategicEventKind const ubCallbackID,UINT32 const uiStartMin,UINT32 const uiLengthMin,UINT32 const uiParam)226 static BOOLEAN AddRangedStrategicEvent(StrategicEventKind const ubCallbackID, UINT32 const uiStartMin, UINT32 const uiLengthMin, UINT32 const uiParam)
227 {
228 	STRATEGICEVENT *pEvent;
229 	pEvent = AddAdvancedStrategicEvent( RANGED_EVENT, ubCallbackID, uiStartMin*60, uiParam );
230 	if( pEvent )
231 	{
232 		pEvent->uiTimeOffset = uiLengthMin * 60;
233 		return TRUE;
234 	}
235 	return FALSE;
236 }
237 
238 
AddSameDayRangedStrategicEvent(StrategicEventKind const ubCallbackID,UINT32 const uiStartMin,UINT32 const uiLengthMin,UINT32 const uiParam)239 BOOLEAN AddSameDayRangedStrategicEvent(StrategicEventKind const ubCallbackID, UINT32 const uiStartMin, UINT32 const uiLengthMin, UINT32 const uiParam)
240 {
241 	return AddRangedStrategicEvent( ubCallbackID, uiStartMin + GetWorldDayInMinutes(), uiLengthMin, uiParam );
242 }
243 
AddEveryDayStrategicEvent(StrategicEventKind const ubCallbackID,UINT32 const uiStartMin,UINT32 const uiParam)244 BOOLEAN AddEveryDayStrategicEvent(StrategicEventKind const ubCallbackID, UINT32 const uiStartMin, UINT32 const uiParam)
245 {
246 	if( AddAdvancedStrategicEvent( EVERYDAY_EVENT, ubCallbackID, GetWorldDayInSeconds() + uiStartMin * 60, uiParam ) )
247 		return TRUE;
248 	return FALSE;
249 }
250 
251 //NEW:  Period Events
252 //Event will get processed automatically once every X minutes.
AddPeriodStrategicEvent(StrategicEventKind const ubCallbackID,UINT32 const uiOnceEveryXMinutes,UINT32 const uiParam)253 BOOLEAN AddPeriodStrategicEvent(StrategicEventKind const ubCallbackID, UINT32 const uiOnceEveryXMinutes, UINT32 const uiParam)
254 {
255 	STRATEGICEVENT *pEvent;
256 	pEvent = AddAdvancedStrategicEvent( PERIODIC_EVENT, ubCallbackID, GetWorldDayInSeconds() + uiOnceEveryXMinutes * 60, uiParam );
257 	if( pEvent )
258 	{
259 		pEvent->uiTimeOffset = uiOnceEveryXMinutes * 60;
260 		return TRUE;
261 	}
262 	return FALSE;
263 }
264 
AddPeriodStrategicEventWithOffset(StrategicEventKind const ubCallbackID,UINT32 const uiOnceEveryXMinutes,UINT32 const uiOffsetFromCurrent,UINT32 const uiParam)265 BOOLEAN AddPeriodStrategicEventWithOffset(StrategicEventKind const ubCallbackID, UINT32 const uiOnceEveryXMinutes, UINT32 const uiOffsetFromCurrent, UINT32 const uiParam)
266 {
267 	STRATEGICEVENT *pEvent;
268 	pEvent = AddAdvancedStrategicEvent( PERIODIC_EVENT, ubCallbackID, GetWorldDayInSeconds() + uiOffsetFromCurrent * 60, uiParam );
269 	if( pEvent )
270 	{
271 		pEvent->uiTimeOffset = uiOnceEveryXMinutes * 60;
272 		return TRUE;
273 	}
274 	return FALSE;
275 }
276 
DeleteAllStrategicEventsOfType(StrategicEventKind const callback_id)277 void DeleteAllStrategicEventsOfType(StrategicEventKind const callback_id)
278 {
279 	for (STRATEGICEVENT** anchor = &gpEventList; *anchor;)
280 	{
281 		STRATEGICEVENT* const e = *anchor;
282 		if (e->ubCallbackID == callback_id && !(e->ubFlags & SEF_DELETION_PENDING))
283 		{
284 			if (!gfPreventDeletionOfAnyEvent)
285 			{ // Detach and delete the node
286 				*anchor = e->next;
287 				delete e;
288 				continue;
289 			}
290 
291 			e->ubFlags |= SEF_DELETION_PENDING;
292 			gfEventDeletionPending = TRUE;
293 		}
294 		anchor = &e->next;
295 	}
296 }
297 
298 
DeleteAllStrategicEvents()299 void DeleteAllStrategicEvents()
300 {
301 	for (STRATEGICEVENT* i = gpEventList; i;)
302 	{
303 		STRATEGICEVENT* const del = i;
304 		i = i->next;
305 		delete del;
306 	}
307 	gpEventList = 0;
308 }
309 
310 
DeleteStrategicEvent(StrategicEventKind const callback_id,UINT32 const param)311 void DeleteStrategicEvent(StrategicEventKind const callback_id, UINT32 const param)
312 {
313 	for (STRATEGICEVENT** anchor = &gpEventList; *anchor; anchor = &(*anchor)->next)
314 	{
315 		STRATEGICEVENT* const e = *anchor;
316 		if (e->ubCallbackID != callback_id)    continue;
317 		if (e->uiParam != param)               continue;
318 		if (e->ubFlags & SEF_DELETION_PENDING) continue;
319 
320 		if (gfPreventDeletionOfAnyEvent)
321 		{
322 			e->ubFlags |= SEF_DELETION_PENDING;
323 			gfEventDeletionPending = TRUE;
324 		}
325 		else
326 		{
327 			*anchor = e->next;
328 			delete e;
329 		}
330 		return;
331 	}
332 }
333 
334 
335 //part of the game.sav files (not map files)
SaveStrategicEventsToSavedGame(HWFILE const f)336 void SaveStrategicEventsToSavedGame(HWFILE const f)
337 {
338 	// Determine the number of events
339 	UINT32 n_game_events = 0;
340 	for (STRATEGICEVENT* i = gpEventList; i; i = i->next)
341 	{
342 		++n_game_events;
343 	}
344 	FileWrite(f, &n_game_events, sizeof(UINT32));
345 
346 	for (STRATEGICEVENT* i = gpEventList; i; i = i->next)
347 	{
348 		BYTE  data[28];
349 		DataWriter d{data};
350 		INJ_SKIP(d, 4)
351 		INJ_U32( d, i->uiTimeStamp)
352 		INJ_U32( d, i->uiParam)
353 		INJ_U32( d, i->uiTimeOffset)
354 		INJ_U8(  d, i->ubEventType)
355 		INJ_U8(  d, i->ubCallbackID)
356 		INJ_U8(  d, i->ubFlags)
357 		INJ_SKIP(d, 9)
358 		Assert(d.getConsumed() == lengthof(data));
359 
360 		FileWrite(f, data, sizeof(data));
361 	}
362 }
363 
364 
LoadStrategicEventsFromSavedGame(HWFILE const f)365 void LoadStrategicEventsFromSavedGame(HWFILE const f)
366 {
367 	// Erase the old Game Event queue
368 	DeleteAllStrategicEvents();
369 
370 	// Read the number of strategic events
371 	UINT32 n_game_events;
372 	FileRead(f, &n_game_events, sizeof(UINT32));
373 
374 	STRATEGICEVENT** anchor = &gpEventList;
375 	for (size_t n = n_game_events; n != 0; --n)
376 	{
377 		BYTE data[28];
378 		FileRead(f, data, sizeof(data));
379 
380 		STRATEGICEVENT* const sev = new STRATEGICEVENT{};
381 		DataReader d{data};
382 		EXTR_SKIP(d, 4)
383 		EXTR_U32( d, sev->uiTimeStamp)
384 		EXTR_U32( d, sev->uiParam)
385 		EXTR_U32( d, sev->uiTimeOffset)
386 		EXTR_U8(  d, sev->ubEventType)
387 		EXTR_U8(  d, sev->ubCallbackID)
388 		EXTR_U8(  d, sev->ubFlags)
389 		EXTR_SKIP(d, 9)
390 		Assert(d.getConsumed() == lengthof(data));
391 
392 		*anchor = sev;
393 		anchor  = &sev->next;
394 	}
395 }
396