#ifdef PRECOMPILEDHEADERS #include "Strategic All.h" #else #include "builddefines.h" #include #include "types.h" #include "Game Events.h" #include "Game Clock.h" #include "MemMan.h" #include "Debug.h" #include "Font Control.h" #include "message.h" #include "Text.h" #endif #ifdef JA2TESTVERSION CHAR16 gEventName[NUMBER_OF_EVENT_TYPES_PLUS_ONE][40]={ //1234567890123456789012345678901234567890 (increase size of array if necessary) L"Null", L"ChangeLightValue", L"WeatherStart", L"WeatherEnd", L"CheckForQuests", L"Ambient", L"AIMResetMercAnnoyance", L"BobbyRayPurchase", L"DailyUpdateBobbyRayInventory", L"UpdateBobbyRayInventory", //1234567890123456789012345678901234567890 (increase size of array if necessary) L"DailyUpdateOfMercSite", L"Day3AddEMailFromSpeck", L"DelayedHiringOfMerc", L"HandleInsuredMercs", L"PayLifeInsuranceForDeadMerc", L"MercDailyUpdate", L"MercAboutToLeaveComment", L"MercContractOver", L"GroupArrival", L"Day2AddEMailFromIMP", //1234567890123456789012345678901234567890 (increase size of array if necessary) L"MercComplainEquipment", L"HourlyUpdate", L"HandleMineIncome", L"SetupMineIncome", L"QueuedBattle", L"LeavingMercArriveInDrassen", L"LeavingMercArriveInOmerta", L"SetByNPCSystem", L"SecondAirportAttendantArrived", L"HelicopterHoverTooLong", //1234567890123456789012345678901234567890 (increase size of array if necessary) L"HelicopterHoverWayTooLong", L"HelicopterDoneRefuelling", L"MercLeaveEquipInOmerta", L"MercLeaveEquipInDrassen", L"DailyEarlyMorningEvents", L"GroupAboutToArrive", L"ProcessTacticalSchedule", L"BeginRainStorm", L"EndRainStorm", L"HandleTownOpinion", //1234567890123456789012345678901234567890 (increase size of array if necessary) L"SetupTownOpinion", L"DelayedDeathHandling", L"BeginAirRaid", L"TownLoyaltyUpdate", L"Meanwhile", L"BeginCreatureQuest", L"CreatureSpread", L"DecayCreatures", L"CreatureNightPlanning", L"CreatureAttack", //1234567890123456789012345678901234567890 (increase size of array if necessary) L"EvaluateQueenSituation", L"CheckEnemyControlledSector", L"TurnOnNightLights", L"TurnOffNightLights", L"TurnOnPrimeLights", L"TurnOffPrimeLights", L"MercAboutToLeaveComment", L"ForceTimeInterupt", L"EnricoEmailEvent", L"InsuranceInvestigationStarted", //1234567890123456789012345678901234567890 (increase size of array if necessary) L"InsuranceInvestigationOver", L"HandleMinuteUpdate", L"TemperatureUpdate", L"Keith going out of business", L"MERC site back online", L"Investigate Sector", L"CheckIfMineCleared", L"RemoveAssassin", L"BandageBleedingMercs", L"ShowUpdateMenu", //1234567890123456789012345678901234567890 (increase size of array if necessary) L"SetMenuReason", L"AddSoldierToUpdateBox", L"BeginContractRenewalSequence", L"RPC_WHINE_ABOUT_PAY", L"HaventMadeImpCharacterEmail", L"Rainstorm", L"Quarter Hour Update", L"MERC Merc went up level email delay", L".", #ifdef CRIPPLED_VERSION L"Crippled version end game check", #endif }; #endif void ValidateGameEvents(); STRATEGICEVENT *gpEventList = NULL; extern UINT32 guiGameClock; extern BOOLEAN gfTimeInterruptPause; BOOLEAN gfPreventDeletionOfAnyEvent = FALSE; BOOLEAN gfEventDeletionPending = FALSE; BOOLEAN gfProcessingGameEvents = FALSE; UINT32 guiTimeStampOfCurrentlyExecutingEvent = 0; //Determines if there are any events that will be processed between the current global time, //and the beginning of the next global time. BOOLEAN GameEventsPending( UINT32 uiAdjustment ) { #ifdef CRIPPLED_VERSION if( guiDay >= 8 ) { return FALSE; } #endif if( !gpEventList ) return FALSE; if( gpEventList->uiTimeStamp <= GetWorldTotalSeconds() + uiAdjustment ) return TRUE; return FALSE; } //returns TRUE if any events were deleted BOOLEAN DeleteEventsWithDeletionPending() { STRATEGICEVENT *curr, *prev, *temp; BOOLEAN fEventDeleted = FALSE; //ValidateGameEvents(); curr = gpEventList; prev = NULL; while( curr ) { //ValidateGameEvents(); if( curr->ubFlags & SEF_DELETION_PENDING ) { if( prev ) { //deleting node in middle prev->next = curr->next; temp = curr; curr = curr->next; MemFree( temp ); fEventDeleted = TRUE; //ValidateGameEvents(); continue; } else { //deleting head gpEventList = gpEventList->next; temp = curr; prev = NULL; curr = curr->next; MemFree( temp ); fEventDeleted = TRUE; //ValidateGameEvents(); continue; } } prev = curr; curr = curr->next; } gfEventDeletionPending = FALSE; return fEventDeleted; } void AdjustClockToEventStamp( STRATEGICEVENT *pEvent, UINT32 *puiAdjustment ) { UINT32 uiDiff; uiDiff = pEvent->uiTimeStamp - guiGameClock; guiGameClock += uiDiff; *puiAdjustment -= uiDiff; //Calculate the day, hour, and minutes. guiDay = ( guiGameClock / NUM_SEC_IN_DAY ); guiHour = ( guiGameClock - ( guiDay * NUM_SEC_IN_DAY ) ) / NUM_SEC_IN_HOUR; guiMin = ( guiGameClock - ( ( guiDay * NUM_SEC_IN_DAY ) + ( guiHour * NUM_SEC_IN_HOUR ) ) ) / NUM_SEC_IN_MIN; #ifdef CRIPPLED_VERSION if( guiDay >= 8 ) { guiDay = 8; guiHour = 0; guiMin = 0; return; } #endif swprintf( WORLDTIMESTR, L"%s %d, %02d:%02d", gpGameClockString[ STR_GAMECLOCK_DAY_NAME ], guiDay, guiHour, guiMin ); } //If there are any events pending, they are processed, until the time limit is reached, or //a major event is processed (one that requires the player's attention). void ProcessPendingGameEvents( UINT32 uiAdjustment, UINT8 ubWarpCode ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"ProcessPendingGameEvents"); STRATEGICEVENT *curr, *pEvent, *prev, *temp; BOOLEAN fDeleteEvent = FALSE, fDeleteQueuedEvent = FALSE; #ifdef CRIPPLED_VERSION if( guiDay >= 8 ) { return; } #endif gfTimeInterrupt = FALSE; gfProcessingGameEvents = TRUE; //While we have events inside the time range to be updated, process them... curr = gpEventList; prev = NULL; //prev only used when warping time to target time. while( !gfTimeInterrupt && curr && curr->uiTimeStamp <= guiGameClock + uiAdjustment ) { fDeleteEvent = FALSE; //Update the time by the difference, but ONLY if the event comes after the current time. //In the beginning of the game, series of events are created that are placed in the list //BEFORE the start time. Those events will be processed without influencing the actual time. if( curr->uiTimeStamp > guiGameClock && ubWarpCode != WARPTIME_PROCESS_TARGET_TIME_FIRST ) { AdjustClockToEventStamp( curr, &uiAdjustment ); } //Process the event if( ubWarpCode != WARPTIME_PROCESS_TARGET_TIME_FIRST ) { fDeleteEvent = ExecuteStrategicEvent( curr ); } else if( curr->uiTimeStamp == guiGameClock + uiAdjustment ) { //if we are warping to the target time to process that event first, if( !curr->next || curr->next->uiTimeStamp > guiGameClock + uiAdjustment ) { //make sure that we are processing the last event for that second AdjustClockToEventStamp( curr, &uiAdjustment ); fDeleteEvent = ExecuteStrategicEvent( curr ); if( curr && prev && fDeleteQueuedEvent ) { //The only case where we are deleting a node in the middle of the list prev->next = curr->next; } } else { //We are at the current target warp time however, there are still other events following in this time cycle. //We will only target the final event in this time. NOTE: Events are posted using a FIFO method prev = curr; curr = curr->next; continue; } } else { //We are warping time to the target time. We haven't found the event yet, //so continuing will keep processing the list until we find it. NOTE: Events are posted using a FIFO method prev = curr; curr = curr->next; continue; } if( fDeleteEvent ) { //Determine if event node is a special event requiring reposting switch( curr->ubEventType ) { case RANGED_EVENT: AddAdvancedStrategicEvent( ENDRANGED_EVENT, curr->ubCallbackID, curr->uiTimeStamp+curr->uiTimeOffset, curr->uiParam ); break; case PERIODIC_EVENT: pEvent = AddAdvancedStrategicEvent( PERIODIC_EVENT, curr->ubCallbackID, curr->uiTimeStamp+curr->uiTimeOffset, curr->uiParam ); if( pEvent ) pEvent->uiTimeOffset = curr->uiTimeOffset; break; case EVERYDAY_EVENT: AddAdvancedStrategicEvent( EVERYDAY_EVENT, curr->ubCallbackID, curr->uiTimeStamp+NUM_SEC_IN_DAY, curr->uiParam ); break; } if( curr == gpEventList ) { gpEventList = gpEventList->next; MemFree( curr ); curr = gpEventList; prev = NULL; //ValidateGameEvents(); } else { temp = curr; prev->next = curr->next; curr = curr->next; MemFree( temp ); //ValidateGameEvents(); } } else { prev = curr; curr = curr->next; } } gfProcessingGameEvents = FALSE; if( gfEventDeletionPending ) { DeleteEventsWithDeletionPending(); } if( uiAdjustment && !gfTimeInterrupt ) guiGameClock += uiAdjustment; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"ProcessPendingGameEvents done"); } BOOLEAN AddSameDayStrategicEvent( UINT8 ubCallbackID, UINT32 uiMinStamp, UINT32 uiParam ) { return( AddStrategicEvent( ubCallbackID, uiMinStamp + GetWorldDayInMinutes(), uiParam ) ); } BOOLEAN AddSameDayStrategicEventUsingSeconds( UINT8 ubCallbackID, UINT32 uiSecondStamp, UINT32 uiParam ) { return( AddStrategicEventUsingSeconds( ubCallbackID, uiSecondStamp + GetWorldDayInSeconds(), uiParam ) ); } BOOLEAN AddFutureDayStrategicEvent( UINT8 ubCallbackID, UINT32 uiMinStamp, UINT32 uiParam, UINT32 uiNumDaysFromPresent ) { UINT32 uiDay; uiDay = GetWorldDay(); return( AddStrategicEvent( ubCallbackID, uiMinStamp + GetFutureDayInMinutes( uiDay + uiNumDaysFromPresent ), uiParam ) ); } BOOLEAN AddFutureDayStrategicEventUsingSeconds( UINT8 ubCallbackID, UINT32 uiSecondStamp, UINT32 uiParam, UINT32 uiNumDaysFromPresent ) { UINT32 uiDay; uiDay = GetWorldDay(); return( AddStrategicEventUsingSeconds( ubCallbackID, uiSecondStamp + GetFutureDayInMinutes( uiDay + uiNumDaysFromPresent ) * 60, uiParam ) ); } STRATEGICEVENT* AddAdvancedStrategicEvent( UINT8 ubEventType, UINT8 ubCallbackID, UINT32 uiTimeStamp, UINT32 uiParam ) { STRATEGICEVENT *pNode, *pNewNode, *pPrevNode; if( gfProcessingGameEvents && uiTimeStamp <= guiTimeStampOfCurrentlyExecutingEvent ) { //Prevents infinite loops of posting events that are the same time or earlier than the event //currently being processed. #ifdef JA2TESTVERSION //if( ubCallbackID == EVENT_PROCESS_TACTICAL_SCHEDULE ) { ScreenMsg( FONT_RED, MSG_DEBUG, L"%s Event Rejected: Can't post events <= time while inside an event callback. This is a special case situation that isn't a bug.", gEventName[ ubCallbackID ] ); } //else //{ // AssertMsg( 0, String( "%S Event Rejected: Can't post events <= time while inside an event callback.", gEventName[ ubCallbackID ] ) ); //} #endif return NULL; } pNewNode = (STRATEGICEVENT *) MemAlloc( sizeof( STRATEGICEVENT ) ); Assert( pNewNode ); memset( pNewNode, 0, sizeof( STRATEGICEVENT ) ); pNewNode->ubCallbackID = ubCallbackID; pNewNode->uiParam = uiParam; pNewNode->ubEventType = ubEventType; pNewNode->uiTimeStamp = uiTimeStamp; pNewNode->uiTimeOffset = 0; // Search list for a place to insert pNode = gpEventList; // If it's the first head, do this! if( !pNode ) { gpEventList = pNewNode; pNewNode->next = NULL; } else { pPrevNode = NULL; while( pNode ) { if( uiTimeStamp < pNode->uiTimeStamp ) { break; } pPrevNode = pNode; pNode = pNode->next; } // If we are at the end, set at the end! if ( !pNode ) { pPrevNode->next = pNewNode; pNewNode->next = NULL; } else { // We have a previous node here // Insert IN FRONT! if ( pPrevNode ) { pNewNode->next = pPrevNode->next; pPrevNode->next = pNewNode; } else { // It's the head pNewNode->next = gpEventList; gpEventList = pNewNode; } } } return pNewNode ; } BOOLEAN AddStrategicEvent( UINT8 ubCallbackID, UINT32 uiMinStamp, UINT32 uiParam ) { if( AddAdvancedStrategicEvent( ONETIME_EVENT, ubCallbackID, uiMinStamp*60, uiParam ) ) return TRUE; return FALSE; } BOOLEAN AddStrategicEventUsingSeconds( UINT8 ubCallbackID, UINT32 uiSecondStamp, UINT32 uiParam ) { if( AddAdvancedStrategicEvent( ONETIME_EVENT, ubCallbackID, uiSecondStamp, uiParam ) ) return TRUE; return FALSE; } BOOLEAN AddRangedStrategicEvent( UINT8 ubCallbackID, UINT32 uiStartMin, UINT32 uiLengthMin, UINT32 uiParam ) { STRATEGICEVENT *pEvent; pEvent = AddAdvancedStrategicEvent( RANGED_EVENT, ubCallbackID, uiStartMin*60, uiParam ); if( pEvent ) { pEvent->uiTimeOffset = uiLengthMin * 60; return TRUE; } return FALSE; } BOOLEAN AddSameDayRangedStrategicEvent( UINT8 ubCallbackID, UINT32 uiStartMin, UINT32 uiLengthMin, UINT32 uiParam) { return AddRangedStrategicEvent( ubCallbackID, uiStartMin + GetWorldDayInMinutes(), uiLengthMin, uiParam ); } BOOLEAN AddFutureDayRangedStrategicEvent( UINT8 ubCallbackID, UINT32 uiStartMin, UINT32 uiLengthMin, UINT32 uiParam, UINT32 uiNumDaysFromPresent ) { return AddRangedStrategicEvent( ubCallbackID, uiStartMin + GetFutureDayInMinutes( GetWorldDay() + uiNumDaysFromPresent ), uiLengthMin, uiParam ); } BOOLEAN AddRangedStrategicEventUsingSeconds( UINT8 ubCallbackID, UINT32 uiStartSeconds, UINT32 uiLengthSeconds, UINT32 uiParam ) { STRATEGICEVENT *pEvent; pEvent = AddAdvancedStrategicEvent( RANGED_EVENT, ubCallbackID, uiStartSeconds, uiParam ); if( pEvent ) { pEvent->uiTimeOffset = uiLengthSeconds; return TRUE; } return FALSE; } BOOLEAN AddSameDayRangedStrategicEventUsingSeconds( UINT8 ubCallbackID, UINT32 uiStartSeconds, UINT32 uiLengthSeconds, UINT32 uiParam) { return AddRangedStrategicEventUsingSeconds( ubCallbackID, uiStartSeconds + GetWorldDayInSeconds(), uiLengthSeconds, uiParam ); } BOOLEAN AddFutureDayRangedStrategicEventUsingSeconds( UINT8 ubCallbackID, UINT32 uiStartSeconds, UINT32 uiLengthSeconds, UINT32 uiParam, UINT32 uiNumDaysFromPresent ) { return AddRangedStrategicEventUsingSeconds( ubCallbackID, uiStartSeconds + GetFutureDayInMinutes( GetWorldDay() + uiNumDaysFromPresent ) * 60, uiLengthSeconds, uiParam ); } BOOLEAN AddEveryDayStrategicEvent( UINT8 ubCallbackID, UINT32 uiStartMin, UINT32 uiParam ) { if( AddAdvancedStrategicEvent( EVERYDAY_EVENT, ubCallbackID, GetWorldDayInSeconds() + uiStartMin * 60, uiParam ) ) return TRUE; return FALSE; } BOOLEAN AddEveryDayStrategicEventUsingSeconds( UINT8 ubCallbackID, UINT32 uiStartSeconds, UINT32 uiParam ) { if( AddAdvancedStrategicEvent( EVERYDAY_EVENT, ubCallbackID, GetWorldDayInSeconds() + uiStartSeconds, uiParam ) ) return TRUE; return FALSE; } //NEW: Period Events //Event will get processed automatically once every X minutes. BOOLEAN AddPeriodStrategicEvent( UINT8 ubCallbackID, UINT32 uiOnceEveryXMinutes, UINT32 uiParam ) { STRATEGICEVENT *pEvent; pEvent = AddAdvancedStrategicEvent( PERIODIC_EVENT, ubCallbackID, GetWorldDayInSeconds() + uiOnceEveryXMinutes * 60, uiParam ); if( pEvent ) { pEvent->uiTimeOffset = uiOnceEveryXMinutes * 60; return TRUE; } return FALSE; } BOOLEAN AddPeriodStrategicEventUsingSeconds( UINT8 ubCallbackID, UINT32 uiOnceEveryXSeconds, UINT32 uiParam ) { STRATEGICEVENT *pEvent; pEvent = AddAdvancedStrategicEvent( PERIODIC_EVENT, ubCallbackID, GetWorldDayInSeconds() + uiOnceEveryXSeconds, uiParam ); if( pEvent ) { pEvent->uiTimeOffset = uiOnceEveryXSeconds; return TRUE; } return FALSE; } BOOLEAN AddPeriodStrategicEventWithOffset( UINT8 ubCallbackID, UINT32 uiOnceEveryXMinutes, UINT32 uiOffsetFromCurrent, UINT32 uiParam ) { STRATEGICEVENT *pEvent; pEvent = AddAdvancedStrategicEvent( PERIODIC_EVENT, ubCallbackID, GetWorldDayInSeconds() + uiOffsetFromCurrent * 60, uiParam ); if( pEvent ) { pEvent->uiTimeOffset = uiOnceEveryXMinutes * 60; return TRUE; } return FALSE; } BOOLEAN AddPeriodStrategicEventUsingSecondsWithOffset( UINT8 ubCallbackID, UINT32 uiOnceEveryXSeconds, UINT32 uiOffsetFromCurrent, UINT32 uiParam ) { STRATEGICEVENT *pEvent; pEvent = AddAdvancedStrategicEvent( PERIODIC_EVENT, ubCallbackID, GetWorldDayInSeconds() + uiOffsetFromCurrent, uiParam ); if( pEvent ) { pEvent->uiTimeOffset = uiOnceEveryXSeconds; return TRUE; } return FALSE; } void DeleteAllStrategicEventsOfType( UINT8 ubCallbackID ) { STRATEGICEVENT *curr, *prev, *temp; prev = NULL; curr = gpEventList; while( curr ) { if( curr->ubCallbackID == ubCallbackID && !(curr->ubFlags & SEF_DELETION_PENDING) ) { if( gfPreventDeletionOfAnyEvent ) { curr->ubFlags |= SEF_DELETION_PENDING; gfEventDeletionPending = TRUE; prev = curr; curr = curr->next; continue; } //Detach the node if( prev ) prev->next = curr->next; else gpEventList = curr->next; //isolate and remove curr temp = curr; curr = curr->next; MemFree( temp ); //ValidateGameEvents(); } else { //Advance all the nodes prev = curr; curr = curr->next; } } } void DeleteAllStrategicEvents() { STRATEGICEVENT *temp; while( gpEventList ) { temp = gpEventList; gpEventList = gpEventList->next; MemFree( temp ); //ValidateGameEvents(); temp = NULL; } gpEventList = NULL; } //Searches for and removes the first event matching the supplied information. There may very well be a need //for more specific event removal, so let me know (Kris), of any support needs. Function returns FALSE if //no events were found or if the event wasn't deleted due to delete lock, BOOLEAN DeleteStrategicEvent( UINT8 ubCallbackID, UINT32 uiParam ) { STRATEGICEVENT *curr, *prev; curr = gpEventList; prev = NULL; while( curr ) { //deleting middle if( curr->ubCallbackID == ubCallbackID && curr->uiParam == uiParam ) { if( !(curr->ubFlags & SEF_DELETION_PENDING) ) { if( gfPreventDeletionOfAnyEvent ) { curr->ubFlags |= SEF_DELETION_PENDING; gfEventDeletionPending = TRUE; return FALSE; } if( prev ) { prev->next = curr->next; } else { gpEventList = gpEventList->next; } MemFree( curr ); //ValidateGameEvents(); return TRUE; } } prev = curr; curr = curr->next; } return FALSE; } //part of the game.sav files (not map files) BOOLEAN SaveStrategicEventsToSavedGame( HWFILE hFile ) { UINT32 uiNumBytesWritten=0; STRATEGICEVENT sGameEvent; UINT32 uiNumGameEvents=0; STRATEGICEVENT *pTempEvent = gpEventList; //Go through the list and determine the number of events while( pTempEvent ) { pTempEvent = pTempEvent->next; uiNumGameEvents++; } //write the number of strategic events FileWrite( hFile, &uiNumGameEvents, sizeof( UINT32 ), &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( UINT32 ) ) { return(FALSE); } //loop through all the events and save them. pTempEvent = gpEventList; while( pTempEvent ) { //save the current structure memcpy( &sGameEvent, pTempEvent, sizeof( STRATEGICEVENT ) ); //write the current strategic event FileWrite( hFile, &sGameEvent, sizeof( STRATEGICEVENT ), &uiNumBytesWritten ); if( uiNumBytesWritten != sizeof( STRATEGICEVENT ) ) { return(FALSE); } pTempEvent = pTempEvent->next; } return( TRUE ); } BOOLEAN LoadStrategicEventsFromSavedGame( HWFILE hFile ) { UINT32 uiNumGameEvents; STRATEGICEVENT sGameEvent; UINT32 cnt; UINT32 uiNumBytesRead=0; STRATEGICEVENT *pTemp = NULL; //erase the old Game Event queue DeleteAllStrategicEvents(); //Read the number of strategic events FileRead( hFile, &uiNumGameEvents, sizeof( UINT32 ), &uiNumBytesRead ); if( uiNumBytesRead != sizeof( UINT32 ) ) { return(FALSE); } pTemp = NULL; //loop through all the events and save them. for( cnt=0; cntnext = pTempEvent; //advance the current node to the next node pTemp = pTemp->next; } // NULL out the next field ( cause there is no next field yet ) pTempEvent->next = NULL; } return( TRUE ); } void LockStrategicEventFromDeletion( STRATEGICEVENT *pEvent ) { pEvent->ubFlags |= SEF_PREVENT_DELETION; } void UnlockStrategicEventFromDeletion( STRATEGICEVENT *pEvent ) { pEvent->ubFlags &= ~SEF_PREVENT_DELETION; } void ValidateGameEvents() { STRATEGICEVENT *curr; curr = gpEventList; while( curr ) { curr = curr->next; if( curr == (STRATEGICEVENT*)0xdddddddd ) { return; } } }