1 #include "Font.h"
2 #include "Local.h"
3 #include "Game_Clock.h"
4 #include "Font_Control.h"
5 #include "Render_Dirty.h"
6 #include "Timer_Control.h"
7 #include "Overhead.h"
8 #include "Environment.h"
9 #include "Message.h"
10 #include "Game_Events.h"
11 #include "Assignments.h"
12 #include "MercTextBox.h"
13 #include "RenderWorld.h"
14 #include "Lighting.h"
15 #include "Map_Screen_Interface.h"
16 #include "PreBattle_Interface.h"
17 #include "Event_Pump.h"
18 #include "Text.h"
19 #include "Map_Screen_Interface_Map.h"
20 #include "Map_Screen_Interface_Bottom.h"
21 #include "MapScreen.h"
22 #include "GameScreen.h"
23 #include "Fade_Screen.h"
24 #include "Map_Information.h"
25 #include "Debug.h"
26 #include "JAScreens.h"
27 #include "Video.h"
28 #include "VSurface.h"
29 #include "Button_System.h"
30 #include "ScreenIDs.h"
31 #include "FileMan.h"
32 #include "UILayout.h"
33
34 #include <string_theory/format>
35 #include <string_theory/string>
36
37
38 //#define DEBUG_GAME_CLOCK
39
40 extern BOOLEAN gfFadeOut;
41
42
43 // is the clock pause region created currently?
44 static BOOLEAN fClockMouseRegionCreated = FALSE;
45
46 static BOOLEAN fTimeCompressHasOccured = FALSE;
47
48 //This value represents the time that the sector was loaded. If you are in sector A9, and leave
49 //the game clock at that moment will get saved into the temp file associated with it. The next time you
50 //enter A9, this value will contain that time. Used for scheduling purposes.
51 UINT32 guiTimeCurrentSectorWasLastLoaded = 0;
52
53 // did we JUST finish up a game pause by the player
54 BOOLEAN gfJustFinishedAPause = FALSE;
55
56 // clock mouse region
57 static MOUSE_REGION gClockMouseRegion;
58 static MOUSE_REGION gClockScreenMaskMouseRegion;
59
60
61 #define SECONDS_PER_COMPRESSION 1 // 1/2 minute passes every 1 second of real time
62
63 #define CLOCK_X (g_ui.get_CLOCK_X())
64 #define CLOCK_Y (g_ui.get_CLOCK_Y())
65 #define CLOCK_HEIGHT 13
66 #define CLOCK_WIDTH 66
67 #define CLOCK_FONT COMPFONT
68
69
70 //These contain all of the information about the game time, rate of time, etc.
71 //All of these get saved and loaded.
72 INT32 giTimeCompressMode = TIME_COMPRESS_X0;
73 static UINT8 gubClockResolution = 1;
74 BOOLEAN gfGamePaused = TRUE;
75 BOOLEAN gfTimeInterrupt = FALSE;
76 static BOOLEAN gfTimeInterruptPause = FALSE;
77 static BOOLEAN fSuperCompression = FALSE;
78 UINT32 guiGameClock = STARTING_TIME;
79 static UINT32 guiPreviousGameClock = 0; // used only for error-checking purposes
80 static UINT32 guiGameSecondsPerRealSecond;
81 static UINT32 guiTimesThisSecondProcessed = 0;
82 static MercPopUpBox* g_paused_popup_box;
83 UINT32 guiDay;
84 UINT32 guiHour;
85 UINT32 guiMin;
86 ST::string gswzWorldTimeStr;
87 INT32 giTimeCompressSpeeds[ NUM_TIME_COMPRESS_SPEEDS ] = { 0, 1, 5 * 60, 30 * 60, 60 * 60 };
88 static UINT16 usPausedActualWidth;
89 static UINT16 usPausedActualHeight;
90 UINT32 guiTimeOfLastEventQuery = 0;
91 BOOLEAN gfLockPauseState = FALSE;
92 BOOLEAN gfPauseDueToPlayerGamePause = FALSE;
93 BOOLEAN gfResetAllPlayerKnowsEnemiesFlags = FALSE;
94 static BOOLEAN gfTimeCompressionOn = FALSE;
95 UINT32 guiLockPauseStateLastReasonId = 0;
96 //***When adding new saved time variables, make sure you remove the appropriate amount from the paddingbytes and
97 // more IMPORTANTLY, add appropriate code in Save/LoadGameClock()!
98 #define TIME_PADDINGBYTES 20
99
100
101 extern UINT32 guiEnvTime;
102 extern UINT32 guiEnvDay;
103
104
105
InitNewGameClock()106 void InitNewGameClock( )
107 {
108 guiGameClock = STARTING_TIME;
109 guiPreviousGameClock = STARTING_TIME;
110 guiDay = ( guiGameClock / NUM_SEC_IN_DAY );
111 guiHour = ( guiGameClock - ( guiDay * NUM_SEC_IN_DAY ) ) / NUM_SEC_IN_HOUR;
112 guiMin = ( guiGameClock - ( ( guiDay * NUM_SEC_IN_DAY ) + ( guiHour * NUM_SEC_IN_HOUR ) ) ) / NUM_SEC_IN_MIN;
113 WORLDTIMESTR = ST::format("{} {}, {02d}:{02d}", pDayStrings, guiDay, guiHour, guiMin);
114 guiTimeCurrentSectorWasLastLoaded = 0;
115 guiGameSecondsPerRealSecond = 0;
116 gubClockResolution = 1;
117 }
118
GetWorldTotalMin()119 UINT32 GetWorldTotalMin( )
120 {
121 return( guiGameClock / NUM_SEC_IN_MIN );
122 }
123
GetWorldTotalSeconds()124 UINT32 GetWorldTotalSeconds( )
125 {
126 return( guiGameClock );
127 }
128
129
GetWorldHour()130 UINT32 GetWorldHour( )
131 {
132 return( guiHour );
133 }
134
GetWorldMinutesInDay()135 UINT32 GetWorldMinutesInDay( )
136 {
137 return( ( guiHour * 60 ) + guiMin );
138 }
139
GetWorldDay()140 UINT32 GetWorldDay( )
141 {
142 return( guiDay);
143 }
144
GetWorldDayInSeconds()145 UINT32 GetWorldDayInSeconds( )
146 {
147 return( guiDay * NUM_SEC_IN_DAY );
148 }
149
GetWorldDayInMinutes()150 UINT32 GetWorldDayInMinutes( )
151 {
152 return( ( guiDay * NUM_SEC_IN_DAY ) / NUM_SEC_IN_MIN );
153 }
154
GetFutureDayInMinutes(UINT32 uiDay)155 UINT32 GetFutureDayInMinutes( UINT32 uiDay )
156 {
157 return( ( uiDay * NUM_SEC_IN_DAY ) / NUM_SEC_IN_MIN );
158 }
159
160 //this function returns the amount of minutes there has been from start of game to midnight of the uiDay.
GetMidnightOfFutureDayInMinutes(UINT32 uiDay)161 UINT32 GetMidnightOfFutureDayInMinutes( UINT32 uiDay )
162 {
163 return( GetWorldTotalMin() + ( uiDay * 1440 ) - GetWorldMinutesInDay( ) );
164 }
165
166
167 static void AdvanceClock(UINT8 ubWarpCode);
168
169
170 // Not to be used too often by things other than internally
WarpGameTime(UINT32 uiAdjustment,UINT8 ubWarpCode)171 void WarpGameTime( UINT32 uiAdjustment, UINT8 ubWarpCode )
172 {
173 UINT32 uiSaveTimeRate;
174 uiSaveTimeRate = guiGameSecondsPerRealSecond;
175 guiGameSecondsPerRealSecond = uiAdjustment;
176 AdvanceClock( ubWarpCode );
177 guiGameSecondsPerRealSecond = uiSaveTimeRate;
178 }
179
180
AdvanceClock(UINT8 ubWarpCode)181 static void AdvanceClock(UINT8 ubWarpCode)
182 {
183 if( ubWarpCode != WARPTIME_NO_PROCESSING_OF_EVENTS )
184 {
185 guiTimeOfLastEventQuery = guiGameClock;
186 //First of all, events are posted for movements, pending attacks, equipment arrivals, etc. This time
187 //adjustment using time compression can possibly pass one or more events in a single pass. So, this list
188 //is looked at and processed in sequential order, until the uiAdjustment is fully applied.
189 if( GameEventsPending( guiGameSecondsPerRealSecond ) )
190 {
191 //If a special event, justifying the cancellation of time compression is reached, the adjustment
192 //will be shortened to the time of that event, and will stop processing events, otherwise, all
193 //of the events in the time slice will be processed. The time is adjusted internally as events
194 //are processed.
195 ProcessPendingGameEvents( guiGameSecondsPerRealSecond, ubWarpCode );
196 }
197 else
198 {
199 //Adjust the game clock now.
200 guiGameClock += guiGameSecondsPerRealSecond;
201 }
202 }
203 else
204 {
205 guiGameClock += guiGameSecondsPerRealSecond;
206 }
207
208
209 if ( guiGameClock < guiPreviousGameClock )
210 {
211 AssertMsg( FALSE, String( "AdvanceClock: TIME FLOWING BACKWARDS!!! guiPreviousGameClock %d, now %d", guiPreviousGameClock, guiGameClock ) );
212
213 // fix it if assertions are disabled
214 guiGameClock = guiPreviousGameClock;
215 }
216
217 // store previous game clock value (for error-checking purposes only)
218 guiPreviousGameClock = guiGameClock;
219
220
221 //Calculate the day, hour, and minutes.
222 guiDay = ( guiGameClock / NUM_SEC_IN_DAY );
223 guiHour = ( guiGameClock - ( guiDay * NUM_SEC_IN_DAY ) ) / NUM_SEC_IN_HOUR;
224 guiMin = ( guiGameClock - ( ( guiDay * NUM_SEC_IN_DAY ) + ( guiHour * NUM_SEC_IN_HOUR ) ) ) / NUM_SEC_IN_MIN;
225
226 WORLDTIMESTR = ST::format("{} {}, {02d}:{02d}", gpGameClockString, guiDay, guiHour, guiMin);
227
228 if( gfResetAllPlayerKnowsEnemiesFlags && !gTacticalStatus.fEnemyInSector )
229 {
230 ClearAnySectorsFlashingNumberOfEnemies();
231
232 gfResetAllPlayerKnowsEnemiesFlags = FALSE;
233 }
234
235 ForecastDayEvents( );
236 }
237
238
AdvanceToNextDay()239 void AdvanceToNextDay()
240 {
241 INT32 uiDiff;
242 UINT32 uiTomorrowTimeInSec;
243
244 uiTomorrowTimeInSec = (guiDay+1)*NUM_SEC_IN_DAY + 8*NUM_SEC_IN_HOUR + 15*NUM_SEC_IN_MIN;
245 uiDiff = uiTomorrowTimeInSec - guiGameClock;
246 WarpGameTime( uiDiff, WARPTIME_PROCESS_EVENTS_NORMALLY );
247
248 ForecastDayEvents( );
249 }
250
251
252
253 // set the flag that time compress has occured
SetFactTimeCompressHasOccured(void)254 void SetFactTimeCompressHasOccured( void )
255 {
256 fTimeCompressHasOccured = TRUE;
257 }
258
259 //reset fact the time compress has occured
ResetTimeCompressHasOccured(void)260 void ResetTimeCompressHasOccured( void )
261 {
262 fTimeCompressHasOccured = FALSE;
263 }
264
265 // has time compress occured?
HasTimeCompressOccured(void)266 BOOLEAN HasTimeCompressOccured( void )
267 {
268 return( fTimeCompressHasOccured );
269 }
270
271
RenderClock(void)272 void RenderClock(void)
273 {
274 // Are we in combat?
275 UINT8 const foreground = gTacticalStatus.uiFlags & INCOMBAT ?
276 FONT_FCOLOR_NICERED : FONT_LTGREEN;
277 SetFontAttributes(CLOCK_FONT, foreground);
278
279 // Erase first!
280 INT16 x = CLOCK_X;
281 INT16 y = CLOCK_Y;
282 RestoreExternBackgroundRect(x, y, CLOCK_WIDTH, CLOCK_HEIGHT);
283
284 ST::string str = (gfPauseDueToPlayerGamePause ? pPausedGameText[0] : WORLDTIMESTR);
285 FindFontCenterCoordinates(x, y, CLOCK_WIDTH, CLOCK_HEIGHT, str, CLOCK_FONT, &x, &y);
286 MPrint(x, y, str);
287 }
288
DidGameJustStart()289 bool DidGameJustStart()
290 {
291 return gTacticalStatus.fDidGameJustStart;
292 }
293
294
295 static void SetClockResolutionToCompressMode(INT32 iCompressMode);
296
297
StopTimeCompression(void)298 void StopTimeCompression( void )
299 {
300 if ( gfTimeCompressionOn )
301 {
302 // change the clock resolution to no time passage, but don't actually change the compress mode (remember it)
303 SetClockResolutionToCompressMode( TIME_COMPRESS_X0 );
304 }
305 }
306
307
StartTimeCompression(void)308 void StartTimeCompression( void )
309 {
310 if ( !gfTimeCompressionOn )
311 {
312 if ( GamePaused() )
313 {
314 // first have to be allowed to unpause the game
315 UnPauseGame( );
316
317 // if we couldn't, ignore this request
318 if ( GamePaused() )
319 {
320 return;
321 }
322 }
323
324
325 // check that we can start compressing
326 if ( !AllowedToTimeCompress( ) )
327 {
328 // not allowed to compress time
329 TellPlayerWhyHeCantCompressTime();
330 return;
331 }
332
333
334 // if no compression mode is set, increase it first
335 if ( giTimeCompressMode <= TIME_COMPRESS_X1 )
336 {
337 IncreaseGameTimeCompressionRate();
338 }
339
340 // change clock resolution to the current compression mode
341 SetClockResolutionToCompressMode( giTimeCompressMode );
342
343 // if it's the first time we're doing this since entering map screen (which reset the flag)
344 if ( !HasTimeCompressOccured( ) )
345 {
346 // set fact that we have compressed time during this map screen session
347 SetFactTimeCompressHasOccured( );
348
349 ClearTacticalStuffDueToTimeCompression( );
350 }
351 }
352 }
353
354
355 // returns FALSE if time isn't currently being compressed for ANY reason (various pauses, etc.)
IsTimeBeingCompressed(void)356 BOOLEAN IsTimeBeingCompressed( void )
357 {
358 if( !gfTimeCompressionOn || ( giTimeCompressMode == TIME_COMPRESS_X0 ) || gfGamePaused )
359 return( FALSE );
360 else
361 return ( TRUE );
362 }
363
364
365 // returns TRUE if the player currently doesn't want time to be compressing
IsTimeCompressionOn(void)366 BOOLEAN IsTimeCompressionOn( void )
367 {
368 return( gfTimeCompressionOn );
369 }
370
371
IncreaseGameTimeCompressionRate()372 void IncreaseGameTimeCompressionRate( )
373 {
374 // if not already at maximum time compression rate
375 if( giTimeCompressMode < TIME_COMPRESS_60MINS )
376 {
377 // check that we can
378 if ( !AllowedToTimeCompress( ) )
379 {
380 // not allowed to compress time
381 TellPlayerWhyHeCantCompressTime();
382 return;
383 }
384
385
386 giTimeCompressMode++;
387
388 // in map screen, we wanna have to skip over x1 compression and go straight to 5x
389 if( ( guiCurrentScreen == MAP_SCREEN ) && ( giTimeCompressMode == TIME_COMPRESS_X1 ) )
390 {
391 giTimeCompressMode++;
392 }
393
394 SetClockResolutionToCompressMode( giTimeCompressMode );
395 }
396 }
397
398
DecreaseGameTimeCompressionRate()399 void DecreaseGameTimeCompressionRate()
400 {
401 // if not already at minimum time compression rate
402 if( giTimeCompressMode > TIME_COMPRESS_X0 )
403 {
404 // check that we can
405 if ( !AllowedToTimeCompress( ) )
406 {
407 // not allowed to compress time
408 TellPlayerWhyHeCantCompressTime();
409 return;
410 }
411
412 giTimeCompressMode--;
413
414 // in map screen, we wanna have to skip over x1 compression and go straight to 5x
415 if( ( guiCurrentScreen == MAP_SCREEN ) && ( giTimeCompressMode == TIME_COMPRESS_X1 ) )
416 {
417 giTimeCompressMode--;
418 }
419
420 SetClockResolutionToCompressMode( giTimeCompressMode );
421 }
422 }
423
424
SetGameTimeCompressionLevel(UINT32 uiCompressionRate)425 void SetGameTimeCompressionLevel( UINT32 uiCompressionRate )
426 {
427 Assert( uiCompressionRate < NUM_TIME_COMPRESS_SPEEDS );
428
429 if( guiCurrentScreen == GAME_SCREEN )
430 {
431 if( uiCompressionRate != TIME_COMPRESS_X1 )
432 {
433 uiCompressionRate = TIME_COMPRESS_X1;
434 }
435 }
436
437 if( guiCurrentScreen == MAP_SCREEN )
438 {
439 if( uiCompressionRate == TIME_COMPRESS_X1 )
440 {
441 uiCompressionRate = TIME_COMPRESS_X0;
442 }
443 }
444
445 // if we're attempting time compression
446 if ( uiCompressionRate >= TIME_COMPRESS_5MINS )
447 {
448 // check that we can
449 if ( !AllowedToTimeCompress( ) )
450 {
451 // not allowed to compress time
452 TellPlayerWhyHeCantCompressTime();
453 return;
454 }
455 }
456
457 giTimeCompressMode = uiCompressionRate;
458 SetClockResolutionToCompressMode( giTimeCompressMode );
459 }
460
461
462 static void SetClockResolutionPerSecond(UINT8 ubNumTimesPerSecond);
463
464
SetClockResolutionToCompressMode(INT32 iCompressMode)465 static void SetClockResolutionToCompressMode(INT32 iCompressMode)
466 {
467 guiGameSecondsPerRealSecond = giTimeCompressSpeeds[ iCompressMode ] * SECONDS_PER_COMPRESSION;
468
469 // ok this is a bit confusing, but for time compression (e.g. 30x60) we want updates
470 // 30x per second, but for standard unpaused time, like in tactical, we want 1x per second
471 if ( guiGameSecondsPerRealSecond == 0 )
472 {
473 SetClockResolutionPerSecond( 0 );
474 }
475 else
476 {
477 SetClockResolutionPerSecond( (UINT8) MAX( 1, (UINT8)(guiGameSecondsPerRealSecond / 60) ) );
478 }
479
480 // if the compress mode is X0 or X1
481 if ( iCompressMode <= TIME_COMPRESS_X1 )
482 {
483 gfTimeCompressionOn = FALSE;
484 }
485 else
486 {
487 gfTimeCompressionOn = TRUE;
488
489 // handle the player just starting a game
490 HandleTimeCompressWithTeamJackedInAndGearedToGo( );
491 }
492
493 fMapScreenBottomDirty = TRUE;
494 }
495
496
497
SetGameHoursPerSecond(UINT32 uiGameHoursPerSecond)498 void SetGameHoursPerSecond( UINT32 uiGameHoursPerSecond )
499 {
500 giTimeCompressMode = NOT_USING_TIME_COMPRESSION;
501 guiGameSecondsPerRealSecond = uiGameHoursPerSecond * 3600;
502 if( uiGameHoursPerSecond == 1 )
503 {
504 SetClockResolutionPerSecond( 60 );
505 }
506 else
507 {
508 SetClockResolutionPerSecond( 59 );
509 }
510 }
511
SetGameMinutesPerSecond(UINT32 uiGameMinutesPerSecond)512 void SetGameMinutesPerSecond( UINT32 uiGameMinutesPerSecond )
513 {
514 giTimeCompressMode = NOT_USING_TIME_COMPRESSION;
515 guiGameSecondsPerRealSecond = uiGameMinutesPerSecond * 60;
516 SetClockResolutionPerSecond( (UINT8)uiGameMinutesPerSecond );
517 }
518
519
520 // call this to prevent player from changing the time compression state via the interface
521
LockPauseState(LockPauseReason const uiUniqueReasonId)522 void LockPauseState(LockPauseReason const uiUniqueReasonId)
523 {
524 gfLockPauseState = TRUE;
525
526 // if adding a new call, please choose a new uiUniqueReasonId, this helps track down the cause when it's left locked
527 // Highest # used was 21 on Feb 15 '99.
528 guiLockPauseStateLastReasonId = uiUniqueReasonId;
529 }
530
531 // call this to allow player to change the time compression state via the interface once again
UnLockPauseState()532 void UnLockPauseState()
533 {
534 gfLockPauseState = FALSE;
535 }
536
537 // tells you whether the player is currently locked out from messing with the time compression state
PauseStateLocked()538 BOOLEAN PauseStateLocked()
539 {
540 return gfLockPauseState;
541 }
542
543
PauseGame(void)544 void PauseGame(void)
545 {
546 // always allow pausing, even if "locked". Locking applies only to trying to compress time, not to pausing it
547 if( !gfGamePaused )
548 {
549 gfGamePaused = TRUE;
550 fMapScreenBottomDirty = TRUE;
551 }
552 }
553
554
UnPauseGame(void)555 void UnPauseGame(void)
556 {
557 // if we're paused
558 if( gfGamePaused )
559 {
560 // ignore request if locked
561 if ( gfLockPauseState )
562 {
563 SLOGW("Call to UnPauseGame() while Pause State is LOCKED!");
564 return;
565 }
566
567 gfGamePaused = FALSE;
568 fMapScreenBottomDirty = TRUE;
569 }
570 }
571
572
GamePaused()573 BOOLEAN GamePaused()
574 {
575 return gfGamePaused;
576 }
577
578
579 //ONLY APPLICABLE INSIDE EVENT CALLBACKS!
InterruptTime()580 void InterruptTime()
581 {
582 gfTimeInterrupt = TRUE;
583 }
584
PauseTimeForInterupt()585 void PauseTimeForInterupt()
586 {
587 gfTimeInterruptPause = TRUE;
588 }
589
590
591 //USING CLOCK RESOLUTION
592 //Note, that changing the clock resolution doesn't effect the amount of game time that passes per
593 //real second, but how many times per second the clock is updated. This rate will break up the actual
594 //time slices per second into smaller chunks. This is useful for animating strategic movement under
595 //fast time compression, so objects don't warp around.
596 //Valid range is 0 - 60 times per second.
SetClockResolutionPerSecond(UINT8 ubNumTimesPerSecond)597 static void SetClockResolutionPerSecond(UINT8 ubNumTimesPerSecond)
598 {
599 ubNumTimesPerSecond = (UINT8)(MAX( 0, MIN( 60, ubNumTimesPerSecond ) ));
600 gubClockResolution = ubNumTimesPerSecond;
601 }
602
603
604 static void CreateDestroyScreenMaskForPauseGame(void);
605
606
607 //There are two factors that influence the flow of time in the game.
608 //-Speed: The speed is the amount of game time passes per real second of time. The higher this
609 // value, the faster the game time flows.
610 //-Resolution: The higher the resolution, the more often per second the clock is actually updated.
611 // This value doesn't affect how much game time passes per real second, but allows for
612 // a more accurate representation of faster time flows.
UpdateClock()613 void UpdateClock()
614 {
615 UINT32 uiNewTime;
616 UINT32 uiThousandthsOfThisSecondProcessed;
617 UINT32 uiTimeSlice;
618 UINT32 uiNewTimeProcessed;
619 static UINT8 ubLastResolution = 1;
620 static UINT32 uiLastSecondTime = 0;
621 static UINT32 uiLastTimeProcessed = 0;
622 #ifdef DEBUG_GAME_CLOCK
623 UINT32 uiAmountToAdvanceTime;
624 UINT32 uiOrigNewTime;
625 UINT32 uiOrigLastSecondTime;
626 UINT32 uiOrigThousandthsOfThisSecondProcessed;
627 UINT8 ubOrigClockResolution;
628 UINT32 uiOrigTimesThisSecondProcessed;
629 UINT8 ubOrigLastResolution;
630 #endif
631 // check game state for pause screen masks
632 CreateDestroyScreenMaskForPauseGame( );
633
634 if( guiCurrentScreen != GAME_SCREEN && guiCurrentScreen != MAP_SCREEN && guiCurrentScreen != GAME_SCREEN )
635 {
636 uiLastSecondTime = GetJA2Clock( );
637 gfTimeInterruptPause = FALSE;
638 return;
639 }
640
641 if( gfGamePaused || gfTimeInterruptPause || ( gubClockResolution == 0 ) || !guiGameSecondsPerRealSecond || ARE_IN_FADE_IN( ) || gfFadeOut )
642 {
643 uiLastSecondTime = GetJA2Clock( );
644 gfTimeInterruptPause = FALSE;
645 return;
646 }
647
648 if(gTacticalStatus.uiFlags & INCOMBAT)
649 return; //time is currently stopped!
650
651
652 uiNewTime = GetJA2Clock();
653
654 #ifdef DEBUG_GAME_CLOCK
655 uiOrigNewTime = uiNewTime;
656 uiOrigLastSecondTime = uiLastSecondTime;
657 uiOrigThousandthsOfThisSecondProcessed = uiThousandthsOfThisSecondProcessed;
658 ubOrigClockResolution = gubClockResolution;
659 uiOrigTimesThisSecondProcessed = guiTimesThisSecondProcessed;
660 ubOrigLastResolution = ubLastResolution;
661 #endif
662
663 //Because we debug so much, breakpoints tend to break the game, and cause unnecessary headaches.
664 //This line ensures that no more than 1 real-second passes between frames. This otherwise has
665 //no effect on anything else.
666 uiLastSecondTime = MAX( uiNewTime - 1000, uiLastSecondTime );
667
668 //1000's of a second difference since last second.
669 uiThousandthsOfThisSecondProcessed = uiNewTime - uiLastSecondTime;
670
671 if( uiThousandthsOfThisSecondProcessed >= 1000 && gubClockResolution == 1 )
672 {
673 uiLastSecondTime = uiNewTime;
674 guiTimesThisSecondProcessed = uiLastTimeProcessed = 0;
675 AdvanceClock( WARPTIME_PROCESS_EVENTS_NORMALLY );
676 }
677 else if( gubClockResolution > 1 )
678 {
679 if( gubClockResolution != ubLastResolution )
680 {
681 //guiTimesThisSecondProcessed = guiTimesThisSecondProcessed * ubLastResolution / gubClockResolution % gubClockResolution;
682 guiTimesThisSecondProcessed = guiTimesThisSecondProcessed * gubClockResolution / ubLastResolution;
683 uiLastTimeProcessed = uiLastTimeProcessed * gubClockResolution / ubLastResolution;
684 ubLastResolution = gubClockResolution;
685 }
686 uiTimeSlice = 1000000/gubClockResolution;
687 if( uiThousandthsOfThisSecondProcessed >= uiTimeSlice * (guiTimesThisSecondProcessed+1)/1000 )
688 {
689 guiTimesThisSecondProcessed = uiThousandthsOfThisSecondProcessed*1000 / uiTimeSlice;
690 uiNewTimeProcessed = guiGameSecondsPerRealSecond * guiTimesThisSecondProcessed / gubClockResolution;
691
692 uiNewTimeProcessed = MAX( uiNewTimeProcessed, uiLastTimeProcessed );
693
694 #ifdef DEBUG_GAME_CLOCK
695 uiAmountToAdvanceTime = uiNewTimeProcessed - uiLastTimeProcessed;
696
697 if( uiAmountToAdvanceTime > 0x80000000 || guiGameClock + uiAmountToAdvanceTime < guiPreviousGameClock )
698 {
699 uiNewTimeProcessed = uiNewTimeProcessed;
700 }
701 #endif
702
703 WarpGameTime( uiNewTimeProcessed - uiLastTimeProcessed, WARPTIME_PROCESS_EVENTS_NORMALLY );
704 if( uiNewTimeProcessed < guiGameSecondsPerRealSecond )
705 { //Processed the same real second
706 uiLastTimeProcessed = uiNewTimeProcessed;
707 }
708 else
709 { //We have moved into a new real second.
710 uiLastTimeProcessed = uiNewTimeProcessed % guiGameSecondsPerRealSecond;
711 if ( gubClockResolution > 0 )
712 {
713 guiTimesThisSecondProcessed %= gubClockResolution;
714 }
715 else
716 {
717 // this branch occurs whenever an event during WarpGameTime stops time compression!
718 guiTimesThisSecondProcessed = 0;
719 }
720 uiLastSecondTime = uiNewTime;
721 }
722 }
723 }
724 }
725
726
SaveGameClock(HWFILE const hFile,BOOLEAN const fGamePaused,BOOLEAN const fLockPauseState)727 void SaveGameClock(HWFILE const hFile, BOOLEAN const fGamePaused, BOOLEAN const fLockPauseState)
728 {
729 FileWrite(hFile, &giTimeCompressMode, sizeof(INT32));
730 FileWrite(hFile, &gubClockResolution, sizeof(UINT8));
731 FileWrite(hFile, &fGamePaused, sizeof(BOOLEAN));
732 FileWrite(hFile, &gfTimeInterrupt, sizeof(BOOLEAN));
733 FileWrite(hFile, &fSuperCompression, sizeof(BOOLEAN));
734 FileWrite(hFile, &guiGameClock, sizeof(UINT32));
735 FileWrite(hFile, &guiGameSecondsPerRealSecond, sizeof(UINT32));
736 FileWrite(hFile, &ubAmbientLightLevel, sizeof(UINT8));
737 FileWrite(hFile, &guiEnvTime, sizeof(UINT32));
738 FileWrite(hFile, &guiEnvDay, sizeof(UINT32));
739 FileWrite(hFile, &gubEnvLightValue, sizeof(UINT8));
740 FileWrite(hFile, &guiTimeOfLastEventQuery, sizeof(UINT32));
741 FileWrite(hFile, &fLockPauseState, sizeof(BOOLEAN));
742 FileWrite(hFile, &gfPauseDueToPlayerGamePause, sizeof(BOOLEAN));
743 FileWrite(hFile, &gfResetAllPlayerKnowsEnemiesFlags, sizeof(BOOLEAN));
744 FileWrite(hFile, &gfTimeCompressionOn, sizeof(BOOLEAN));
745 FileWrite(hFile, &guiPreviousGameClock, sizeof(UINT32));
746 FileWrite(hFile, &guiLockPauseStateLastReasonId, sizeof(UINT32));
747
748 FileSeek(hFile, TIME_PADDINGBYTES, FILE_SEEK_FROM_CURRENT);
749 }
750
751
LoadGameClock(HWFILE const hFile)752 void LoadGameClock(HWFILE const hFile)
753 {
754 FileRead(hFile, &giTimeCompressMode, sizeof(INT32));
755 FileRead(hFile, &gubClockResolution, sizeof(UINT8));
756 FileRead(hFile, &gfGamePaused, sizeof(BOOLEAN));
757 FileRead(hFile, &gfTimeInterrupt, sizeof(BOOLEAN));
758 FileRead(hFile, &fSuperCompression, sizeof(BOOLEAN));
759 FileRead(hFile, &guiGameClock, sizeof(UINT32));
760 FileRead(hFile, &guiGameSecondsPerRealSecond, sizeof(UINT32));
761 FileRead(hFile, &ubAmbientLightLevel, sizeof(UINT8));
762 FileRead(hFile, &guiEnvTime, sizeof(UINT32));
763 FileRead(hFile, &guiEnvDay, sizeof(UINT32));
764 FileRead(hFile, &gubEnvLightValue, sizeof(UINT8));
765 FileRead(hFile, &guiTimeOfLastEventQuery, sizeof(UINT32));
766 FileRead(hFile, &gfLockPauseState, sizeof(BOOLEAN));
767 FileRead(hFile, &gfPauseDueToPlayerGamePause, sizeof(BOOLEAN));
768 FileRead(hFile, &gfResetAllPlayerKnowsEnemiesFlags, sizeof(BOOLEAN));
769 FileRead(hFile, &gfTimeCompressionOn, sizeof(BOOLEAN));
770 FileRead(hFile, &guiPreviousGameClock, sizeof(UINT32));
771 FileRead(hFile, &guiLockPauseStateLastReasonId, sizeof(UINT32));
772
773 FileSeek(hFile, TIME_PADDINGBYTES, FILE_SEEK_FROM_CURRENT);
774
775 //Update the game clock
776 guiDay = ( guiGameClock / NUM_SEC_IN_DAY );
777 guiHour = ( guiGameClock - ( guiDay * NUM_SEC_IN_DAY ) ) / NUM_SEC_IN_HOUR;
778 guiMin = ( guiGameClock - ( ( guiDay * NUM_SEC_IN_DAY ) + ( guiHour * NUM_SEC_IN_HOUR ) ) ) / NUM_SEC_IN_MIN;
779
780 WORLDTIMESTR = ST::format("{} {}, {02d}:{02d}", pDayStrings, guiDay, guiHour, guiMin);
781
782 if( !gfBasement && !gfCaves )
783 gfDoLighting = TRUE;
784 }
785
786
787 static void PauseOfClockBtnCallback(MOUSE_REGION* pRegion, INT32 iReason);
788
789
CreateMouseRegionForPauseOfClock(void)790 void CreateMouseRegionForPauseOfClock(void)
791 {
792 if (!fClockMouseRegionCreated)
793 {
794 // create a mouse region for pausing of game clock
795 MSYS_DefineRegion(
796 &gClockMouseRegion,
797 CLOCK_X, CLOCK_Y, CLOCK_X + CLOCK_WIDTH, CLOCK_Y + CLOCK_HEIGHT,
798 MSYS_PRIORITY_HIGHEST, MSYS_NO_CURSOR, MSYS_NO_CALLBACK, PauseOfClockBtnCallback
799 );
800
801 fClockMouseRegionCreated = TRUE;
802
803 ST::string help = gfGamePaused ?
804 pPausedGameText[1] : pPausedGameText[2];
805 gClockMouseRegion.SetFastHelpText(help);
806 }
807 }
808
809
RemoveMouseRegionForPauseOfClock(void)810 void RemoveMouseRegionForPauseOfClock( void )
811 {
812 // remove pause region
813 if (fClockMouseRegionCreated)
814 {
815 MSYS_RemoveRegion( &gClockMouseRegion );
816 fClockMouseRegionCreated = FALSE;
817
818 }
819 }
820
821
PauseOfClockBtnCallback(MOUSE_REGION * pRegion,INT32 iReason)822 static void PauseOfClockBtnCallback(MOUSE_REGION* pRegion, INT32 iReason)
823 {
824 if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
825 {
826 HandlePlayerPauseUnPauseOfGame( );
827 }
828 }
829
830
HandlePlayerPauseUnPauseOfGame(void)831 void HandlePlayerPauseUnPauseOfGame( void )
832 {
833 if ( gTacticalStatus.uiFlags & ENGAGED_IN_CONV )
834 {
835 return;
836 }
837
838 // check if the game is paused BY THE PLAYER or not and reverse
839 if( gfGamePaused && gfPauseDueToPlayerGamePause )
840 {
841 // If in game screen...
842 if ( guiCurrentScreen == GAME_SCREEN )
843 {
844 if( giTimeCompressMode == TIME_COMPRESS_X0 )
845 {
846 giTimeCompressMode++;
847 }
848
849 // ATE: re-render
850 SetRenderFlags( RENDER_FLAG_FULL );
851 }
852
853 UnPauseGame( );
854 PauseTime( FALSE );
855 gfIgnoreScrolling = FALSE;
856 gfPauseDueToPlayerGamePause = FALSE;
857 }
858 else
859 {
860 // pause game
861 PauseGame( );
862 PauseTime( TRUE );
863 gfIgnoreScrolling = TRUE;
864 gfPauseDueToPlayerGamePause = TRUE;
865 }
866 }
867
868
869 static void ScreenMaskForGamePauseBtnCallBack(MOUSE_REGION* pRegion, INT32 iReason);
870
871
CreateDestroyScreenMaskForPauseGame(void)872 static void CreateDestroyScreenMaskForPauseGame(void)
873 {
874 static BOOLEAN fCreated = FALSE;
875
876 if ((!fClockMouseRegionCreated || !gfGamePaused || !gfPauseDueToPlayerGamePause) && fCreated)
877 {
878 fCreated = FALSE;
879 MSYS_RemoveRegion( &gClockScreenMaskMouseRegion );
880 RemoveMercPopupBox(g_paused_popup_box);
881 g_paused_popup_box = 0;
882 SetRenderFlags( RENDER_FLAG_FULL );
883 fTeamPanelDirty = TRUE;
884 fMapPanelDirty = TRUE;
885 fMapScreenBottomDirty = TRUE;
886 gfJustFinishedAPause = TRUE;
887 MarkButtonsDirty();
888 SetRenderFlags( RENDER_FLAG_FULL );
889 }
890 else if (gfPauseDueToPlayerGamePause && !fCreated)
891 {
892 // create a mouse region for pausing of game clock
893 MSYS_DefineRegion(&gClockScreenMaskMouseRegion, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, MSYS_PRIORITY_HIGHEST, 0, MSYS_NO_CALLBACK, ScreenMaskForGamePauseBtnCallBack);
894 fCreated = TRUE;
895
896 //re create region on top of this
897 RemoveMouseRegionForPauseOfClock( );
898 CreateMouseRegionForPauseOfClock();
899
900 gClockMouseRegion.SetFastHelpText(pPausedGameText[1]);
901
902 fMapScreenBottomDirty = TRUE;
903
904 //UnMarkButtonsDirty( );
905
906 // now create the pop up box to say the game is paused
907 g_paused_popup_box = PrepareMercPopupBox(0, BASIC_MERC_POPUP_BACKGROUND, BASIC_MERC_POPUP_BORDER, pPausedGameText[0], 300, 0, 0, 0, &usPausedActualWidth, &usPausedActualHeight);
908 }
909 }
910
911
ScreenMaskForGamePauseBtnCallBack(MOUSE_REGION * pRegion,INT32 iReason)912 static void ScreenMaskForGamePauseBtnCallBack(MOUSE_REGION* pRegion, INT32 iReason)
913 {
914 if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
915 {
916 // unpause the game
917 HandlePlayerPauseUnPauseOfGame( );
918 }
919 }
920
RenderPausedGameBox(void)921 void RenderPausedGameBox( void )
922 {
923 if (gfPauseDueToPlayerGamePause && gfGamePaused && g_paused_popup_box)
924 {
925 const INT32 x = (SCREEN_WIDTH - usPausedActualWidth) / 2;
926 const INT32 y = 200 - usPausedActualHeight / 2;
927 RenderMercPopUpBox(g_paused_popup_box, x, y, FRAME_BUFFER);
928 InvalidateRegion(x, y, x + usPausedActualWidth, y + usPausedActualHeight);
929 }
930
931 // reset we've just finished a pause by the player
932 gfJustFinishedAPause = FALSE;
933 }
934
DayTime()935 BOOLEAN DayTime()
936 { //between 7AM and 9PM
937 return ( guiHour >= 7 && guiHour < 21 );
938 }
939
NightTime()940 BOOLEAN NightTime()
941 { //before 7AM or after 9PM
942 return ( guiHour < 7 || guiHour >= 21 );
943 }
944
945
946
ClearTacticalStuffDueToTimeCompression(void)947 void ClearTacticalStuffDueToTimeCompression( void )
948 {
949 // is this test the right thing? ARM
950 if (!fInMapMode) return; // XXX necessary?
951
952 // clear tactical event queue
953 ClearEventQueue( );
954
955 // clear tactical message queue
956 ClearTacticalMessageQueue( );
957
958 if( gfWorldLoaded )
959 {
960 // clear tactical actions
961 CencelAllActionsForTimeCompression( );
962 }
963 }
964