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