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