diff --git a/GameSettings.cpp b/GameSettings.cpp index 45426830..233aa2f9 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -2314,6 +2314,11 @@ void LoadGameExternalOptions() gGameExternalOptions.fAIDecisionInfo = iniReader.ReadBoolean("Tactical AI Settings", "AI_DECISION_INFO", FALSE); gGameExternalOptions.fAIShootUnseen = iniReader.ReadBoolean("Tactical AI Settings", "AI_SHOOT_UNSEEN", FALSE); gGameExternalOptions.fAISafeSuppression = iniReader.ReadBoolean("Tactical AI Settings", "AI_SAFE_SUPPRESSION", TRUE); + + // Mini Events + gGameExternalOptions.fMiniEventsEnabled = iniReader.ReadBoolean("Mini Events Settings", "MINI_EVENTS_ENABLED", FALSE); + gGameExternalOptions.fMiniEventsMinHoursBetweenEvents = iniReader.ReadInteger("Mini Events Settings", "MINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS", 120, 24, 24000); + gGameExternalOptions.fMiniEventsMaxHoursBetweenEvents = iniReader.ReadInteger("Mini Events Settings", "MINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS", 240, 24, 24000); } diff --git a/GameSettings.h b/GameSettings.h index b45ebb51..ab97c8f3 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -1579,6 +1579,11 @@ typedef struct BOOLEAN fAIShootUnseen; BOOLEAN fAISafeSuppression; + // Mini Events + BOOLEAN fMiniEventsEnabled; + UINT16 fMiniEventsMinHoursBetweenEvents; + UINT16 fMiniEventsMaxHoursBetweenEvents; + } GAME_EXTERNAL_OPTIONS; typedef struct diff --git a/Laptop/finances.cpp b/Laptop/finances.cpp index b02b4c97..26d5a3ad 100644 --- a/Laptop/finances.cpp +++ b/Laptop/finances.cpp @@ -1600,6 +1600,10 @@ void ProcessTransactionString(STR16 pString, FinanceUnitPtr pFinance) swprintf( pString, pTransactionText[PROMOTE_MILITIA], str ); } break; + + case MINI_EVENT: + swprintf(pString, L"%s", pTransactionText[MINI_EVENT]); + break; } } diff --git a/Laptop/finances.h b/Laptop/finances.h index e369b893..259bdde0 100644 --- a/Laptop/finances.h +++ b/Laptop/finances.h @@ -59,6 +59,7 @@ enum SAM_REPAIR, // Flugente: repair SAM site WORKERS_TRAINED, // Flugente: train workers PROMOTE_MILITIA, // Flugente: drill militia + MINI_EVENT, // rftr: mini events TEXT_NUM_FINCANCES }; diff --git a/MPChatScreen.cpp b/MPChatScreen.cpp index 48a86b22..6cd03a9c 100644 --- a/MPChatScreen.cpp +++ b/MPChatScreen.cpp @@ -1606,4 +1606,4 @@ void ClearWrappedStringsCHAT( WRAPPED_STRING *pStringWrapperHead ) pStringWrapperHead = NULL; -} +} diff --git a/MessageBoxScreen.cpp b/MessageBoxScreen.cpp index 616813db..e9ebadc9 100644 --- a/MessageBoxScreen.cpp +++ b/MessageBoxScreen.cpp @@ -197,6 +197,19 @@ INT32 DoMessageBox( UINT8 ubStyle, const STR16 zString, UINT32 uiExitScreen, UIN break; + case MSG_BOX_MINIEVENT_STYLE: + + ubMercBoxBackground = DIALOG_MERC_POPUP_BACKGROUND; + ubMercBoxBorder = DIALOG_MERC_POPUP_BORDER; + + // Add button images + gMsgBox.iButtonImages = LoadButtonImage( "INTERFACE\\msgboxbuttonwide.sti", -1,0,-1,1,-1 ); + ubFontColor = FONT_MCOLOR_WHITE; + ubFontShadowColor = DEFAULT_SHADOW; + usCursor = CURSOR_NORMAL; + + break; + default: ubMercBoxBackground = BASIC_MERC_POPUP_BACKGROUND; ubMercBoxBorder = BASIC_MERC_POPUP_BORDER; @@ -267,6 +280,14 @@ INT32 DoMessageBox( UINT8 ubStyle, const STR16 zString, UINT32 uiExitScreen, UIN usMBWidth = MSGBOX_BUTTON_WIDTH * 4; bFixedWidth = TRUE; } + + // rftr - bigger msg box. I guess these max out the box? + if (usFlags & MSG_BOX_FLAG_BIGGER) + { + heightincrease = 140; + usMBWidth = MSGBOX_BUTTON_WIDTH * 5; + bFixedWidth = TRUE; + } // Init message box gMsgBox.iBoxId = PrepareMercPopupBox( iId, ubMercBoxBackground, ubMercBoxBorder, zString, usMBWidth, 40, 10, 30 + heightincrease, &usTextBoxWidth, &usTextBoxHeight, bFixedWidth ); @@ -873,6 +894,31 @@ INT32 DoMessageBox( UINT8 ubStyle, const STR16 zString, UINT32 uiExitScreen, UIN ForceButtonUnDirty( gMsgBox.uiNOButton ); } + if (usFlags & MSG_BOX_FLAG_WIDE_BUTTONS) + { + sButtonX = 10;//usTextBoxWidth - (MSGBOX_BUTTON_WIDTH + MSGBOX_BUTTON_WIDTH + MSGBOX_BUTTON_X_SEP); + sButtonY = usTextBoxHeight - 10; + + gMsgBox.uiYESButton = CreateIconAndTextButton( gMsgBox.iButtonImages, gzUserDefinedButton1, FONT12ARIAL, + ubFontColor, ubFontShadowColor, + ubFontColor, ubFontShadowColor, + TEXT_LJUSTIFIED, + (INT16)(gMsgBox.sX + sButtonX ), (INT16)(gMsgBox.sY + sButtonY - 5 - MSGBOX_BUTTON_HEIGHT * 2 ), BUTTON_TOGGLE ,MSYS_PRIORITY_HIGHEST, + DEFAULT_MOVE_CALLBACK, (GUI_CALLBACK)YESMsgBoxCallback ); + SetButtonCursor(gMsgBox.uiYESButton, usCursor); + ForceButtonUnDirty( gMsgBox.uiYESButton ); + + + gMsgBox.uiNOButton = CreateIconAndTextButton( gMsgBox.iButtonImages, gzUserDefinedButton2, FONT12ARIAL, + ubFontColor, ubFontShadowColor, + ubFontColor, ubFontShadowColor, + TEXT_LJUSTIFIED, + (INT16)(gMsgBox.sX + sButtonX ), (INT16)(gMsgBox.sY + sButtonY - MSGBOX_BUTTON_HEIGHT ), BUTTON_TOGGLE ,MSYS_PRIORITY_HIGHEST, + DEFAULT_MOVE_CALLBACK, (GUI_CALLBACK)NOMsgBoxCallback ); + SetButtonCursor(gMsgBox.uiNOButton, usCursor); + ForceButtonUnDirty( gMsgBox.uiNOButton ); + } + if ( usFlags & MSG_BOX_FLAG_YESNOLIE ) { sButtonX = ( usTextBoxWidth - ( MSGBOX_BUTTON_WIDTH + MSGBOX_BUTTON_WIDTH + MSGBOX_BUTTON_X_SEP ) ) / 3; @@ -1263,6 +1309,12 @@ UINT32 ExitMsgBox( INT8 ubExitCode ) RemoveButton( gMsgBox.uiYESButton ); RemoveButton( gMsgBox.uiNOButton ); } + + if ( gMsgBox.usFlags & MSG_BOX_FLAG_WIDE_BUTTONS ) + { + RemoveButton( gMsgBox.uiYESButton ); + RemoveButton( gMsgBox.uiNOButton ); + } } if ( gMsgBox.usFlags & MSG_BOX_FLAG_IMAGE ) @@ -1521,6 +1573,12 @@ UINT32 MessageBoxScreenHandle( ) MarkAButtonDirty( gMsgBox.uiNOButton ); } + if ( gMsgBox.usFlags & MSG_BOX_FLAG_WIDE_BUTTONS ) + { + MarkAButtonDirty( gMsgBox.uiYESButton ); + MarkAButtonDirty( gMsgBox.uiNOButton ); + } + RenderMercPopUpBoxFromIndex( gMsgBox.iBoxId, gMsgBox.sX, gMsgBox.sY, FRAME_BUFFER ); //gMsgBox.fRenderBox = FALSE; @@ -1725,6 +1783,11 @@ UINT32 MessageBoxScreenShutdown( ) return( FALSE ); } +void DoScreenIndependantMessageBoxFullScreen( const STR16 zString, UINT32 usFlags, MSGBOX_CALLBACK ReturnCallback ) +{ + SGPRect CenteringRect= {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT }; + DoScreenIndependantMessageBoxWithRect( zString, usFlags, ReturnCallback, &CenteringRect ); +} // a basic box that don't care what screen we came from void DoScreenIndependantMessageBox( const STR16 zString, UINT32 usFlags, MSGBOX_CALLBACK ReturnCallback ) diff --git a/MessageBoxScreen.h b/MessageBoxScreen.h index cb6181c4..79827396 100644 --- a/MessageBoxScreen.h +++ b/MessageBoxScreen.h @@ -28,6 +28,10 @@ #define MSG_BOX_FLAG_IMAGE 0x00040000 // Displays an image #define MSG_BOX_BUTTONS_HORIZONTAL_ORIENTATION 0x00080000 // MSG_BOX_FLAG_GENERIC_FOUR_BUTTONS sets the buttons horizontally +// rftr - bigger msg box +#define MSG_BOX_FLAG_BIGGER 0x00100000 // Bigger msg box +#define MSG_BOX_FLAG_WIDE_BUTTONS 0x00200000 // Wide buttons + // message box return codes #define MSG_BOX_RETURN_OK 1 // ENTER or on OK button #define MSG_BOX_RETURN_YES 2 // ENTER or YES button @@ -46,6 +50,7 @@ enum MSG_BOX_IMP_STYLE, MSG_BOX_LAPTOP_DEFAULT, MSG_BOX_BASIC_MEDIUM_BUTTONS, + MSG_BOX_MINIEVENT_STYLE, }; enum @@ -133,6 +138,7 @@ extern CHAR16 gszMsgBoxInputString[255]; //////////////////////////////// INT32 DoMessageBox( UINT8 ubStyle, const STR16 zString, UINT32 uiExitScreen, UINT32 usFlags, MSGBOX_CALLBACK ReturnCallback, SGPRect *pCenteringRect, UINT8 ubDefaultButton = MSG_BOX_DEFAULT_BUTTON_NONE ); +void DoScreenIndependantMessageBoxFullScreen( const STR16 zString, UINT32 usFlags, MSGBOX_CALLBACK ReturnCallback ); void DoScreenIndependantMessageBox( const STR16 zString, UINT32 usFlags, MSGBOX_CALLBACK ReturnCallback ); void DoUpperScreenIndependantMessageBox( const STR16 zString, UINT32 usFlags, MSGBOX_CALLBACK ReturnCallback ); diff --git a/SaveLoadGame.cpp b/SaveLoadGame.cpp index d1054395..8473d3a9 100644 --- a/SaveLoadGame.cpp +++ b/SaveLoadGame.cpp @@ -2352,7 +2352,7 @@ BOOLEAN SOLDIERTYPE::Load(HWFILE hFile) numBytesRead = ReadFieldByField( hFile, &this->sDiseasePoints, sizeof(sDiseasePoints), sizeof(INT32), numBytesRead ); numBytesRead = ReadFieldByField( hFile, &this->sDiseaseFlag, sizeof(sDiseaseFlag), sizeof(UINT8), numBytesRead ); numBytesRead = ReadFieldByField( hFile, &this->ubFiller, sizeof( ubFiller ), sizeof( UINT8 ), numBytesRead ); - numBytesRead = ReadFieldByField( hFile, &this->ubFiller1, sizeof( ubFiller1 ), sizeof( UINT8 ), numBytesRead ); + numBytesRead = ReadFieldByField( hFile, &this->ubHoursRemainingOnMiniEvent, sizeof( ubHoursRemainingOnMiniEvent), sizeof( UINT8 ), numBytesRead ); numBytesRead = ReadFieldByField( hFile, &this->usGLDelayMode, sizeof( usGLDelayMode ), sizeof( UINT8 ), numBytesRead ); numBytesRead = ReadFieldByField( hFile, &this->usBarrelMode, sizeof( usBarrelMode ), sizeof( UINT8 ), numBytesRead ); numBytesRead = ReadFieldByField( hFile, &this->usBarrelCounter, sizeof( usBarrelCounter ), sizeof( UINT8 ), numBytesRead ); @@ -2373,7 +2373,7 @@ BOOLEAN SOLDIERTYPE::Load(HWFILE hFile) numBytesRead = ReadFieldByField( hFile, &this->ubFiller, sizeof(ubFiller), sizeof(UINT8), numBytesRead ); - buffer += sizeof( ubFiller1 ); + buffer += sizeof( ubHoursRemainingOnMiniEvent ); buffer += sizeof( usGLDelayMode ); buffer += sizeof( usBarrelMode ); buffer += sizeof( usBarrelCounter ); @@ -2421,7 +2421,7 @@ BOOLEAN SOLDIERTYPE::Load(HWFILE hFile) while((buffer%4) > 0) buffer++; - buffer += sizeof( ubFiller1 ); + buffer += sizeof( ubHoursRemainingOnMiniEvent ); buffer += sizeof( usGLDelayMode ); buffer += sizeof( usBarrelMode ); buffer += sizeof( usBarrelCounter ); diff --git a/Strategic/Assignments.cpp b/Strategic/Assignments.cpp index a93d41df..e83cbfc8 100644 --- a/Strategic/Assignments.cpp +++ b/Strategic/Assignments.cpp @@ -75,6 +75,7 @@ #include "Militia Control.h" // added by Flugente #include "ASD.h" // added by Flugente #include "Strategic AI.h" + #include "MiniEvents.h" #endif #include #include @@ -515,6 +516,8 @@ void HandleAdministrationAssignments(); // Flugente: handle exploration assignements void HandleExplorationAssignments(); +void HandleMiniEventAssignments(); + // is the character between sectors in mvt BOOLEAN CharacterIsBetweenSectors( SOLDIERTYPE *pSoldier ); @@ -800,6 +803,11 @@ BOOLEAN BasicCanCharacterAssignment( SOLDIERTYPE * pSoldier, BOOLEAN fNotInComba if (pSoldier->bAssignment == ASSIGNMENT_POW) return(FALSE); + if ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT && pSoldier->ubHoursRemainingOnMiniEvent > 0 ) + { + return( FALSE ); + } + return( TRUE ); } @@ -2478,7 +2486,7 @@ BOOLEAN CanCharacterSleep( SOLDIERTYPE *pSoldier, BOOLEAN fExplainWhyNot ) } // POW? - if( pSoldier->bAssignment == ASSIGNMENT_POW ) + if( pSoldier->bAssignment == ASSIGNMENT_POW || pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) { return( FALSE ); } @@ -2696,7 +2704,7 @@ INT8 CanCharacterSquad( SOLDIERTYPE *pSoldier, INT8 bSquadValue ) return ( CHARACTER_CANT_JOIN_SQUAD ); } - if ( pSoldier->bAssignment == ASSIGNMENT_POW ) + if ( pSoldier->bAssignment == ASSIGNMENT_POW || (pSoldier->bAssignment == ASSIGNMENT_MINIEVENT && pSoldier->ubHoursRemainingOnMiniEvent > 0)) { // not allowed to be put on a squad return( CHARACTER_CANT_JOIN_SQUAD ); @@ -3050,6 +3058,8 @@ void UpdateAssignments() // handle exploration HandleExplorationAssignments(); + HandleMiniEventAssignments(); + // check to see if anyone is done healing? UpdatePatientsWhoAreDoneHealing( ); @@ -5815,7 +5825,7 @@ void FatigueCharacter( SOLDIERTYPE *pSoldier ) } // POW? - if( pSoldier->bAssignment == ASSIGNMENT_POW ) + if( pSoldier->bAssignment == ASSIGNMENT_POW || pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) { return; } @@ -7075,6 +7085,29 @@ void HandleExplorationAssignments() } } +void HandleMiniEventAssignments() +{ + SOLDIERTYPE *pSoldier = NULL; + UINT32 uiCnt = 0; + const UINT32 firstid = gTacticalStatus.Team[gbPlayerNum].bFirstID; + const UINT32 lastid = gTacticalStatus.Team[gbPlayerNum].bLastID; + + for ( uiCnt = firstid, pSoldier = MercPtrs[uiCnt]; uiCnt <= lastid; ++uiCnt, ++pSoldier ) + { + if ( pSoldier && pSoldier->bAssignment == ASSIGNMENT_MINIEVENT && EnoughTimeOnAssignment( pSoldier ) ) + { + if (--pSoldier->ubHoursRemainingOnMiniEvent == 0) + { + pSoldier->bSectorZ -= MINI_EVENT_Z_OFFSET; + pSoldier->ubInsertionDirection = DIRECTION_IRRELEVANT; + pSoldier->ubStrategicInsertionCode = INSERTION_CODE_CENTER; + AssignmentDone(pSoldier, TRUE, FALSE); + AddCharacterToAnySquad(pSoldier); + } + } + } +} + // handle snitch spreading propaganda assignment // totally not a copy of HandleRadioScanInSector void HandleSpreadingPropagandaInSector( INT16 sMapX, INT16 sMapY, INT8 bZ ) @@ -16251,7 +16284,7 @@ void HandleRestFatigueAndSleepStatus( void ) continue; } - if( ( pSoldier->bAssignment == ASSIGNMENT_POW ) || ( pSoldier->bAssignment == IN_TRANSIT ) ) + if( ( pSoldier->bAssignment == ASSIGNMENT_POW ) || ( pSoldier->bAssignment == IN_TRANSIT ) || ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) ) { continue; } @@ -16400,7 +16433,7 @@ void HandleRestFatigueAndSleepStatus( void ) continue; } - if( ( pSoldier->bAssignment == ASSIGNMENT_POW ) || ( pSoldier->bAssignment == IN_TRANSIT ) ) + if( ( pSoldier->bAssignment == ASSIGNMENT_POW ) || ( pSoldier->bAssignment == IN_TRANSIT ) || ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) ) { continue; } @@ -19380,7 +19413,8 @@ BOOLEAN CanCharacterRepairAnotherSoldiersStuff( SOLDIERTYPE *pSoldier, SOLDIERTY ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) || ( AM_A_ROBOT( pSoldier ) ) || ( pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__EPC ) || - ( pOtherSoldier->bAssignment == ASSIGNMENT_DEAD ) ) + ( pOtherSoldier->bAssignment == ASSIGNMENT_DEAD ) || + ( pOtherSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) ) { return( FALSE ); } diff --git a/Strategic/Assignments.h b/Strategic/Assignments.h index 917d2621..a3c731ae 100644 --- a/Strategic/Assignments.h +++ b/Strategic/Assignments.h @@ -99,6 +99,7 @@ enum BURIAL, // merc removes corpses in this sector ADMINISTRATION, // merc boosts the effectiveness of other mercs EXPLORATION, // merc searches the sector for undiscovered items + ASSIGNMENT_MINIEVENT, NUM_ASSIGNMENTS, }; diff --git a/Strategic/Game Event Hook.cpp b/Strategic/Game Event Hook.cpp index 4a92be05..e951f765 100644 --- a/Strategic/Game Event Hook.cpp +++ b/Strategic/Game Event Hook.cpp @@ -50,6 +50,7 @@ #include "ASD.h" // added by Flugente #include "Player Command.h" // added by Flugente #include "LuaInitNPCs.h" // added by Flugente + #include "MiniEvents.h" #endif #include "connect.h" @@ -672,6 +673,13 @@ BOOLEAN ExecuteStrategicEvent( STRATEGICEVENT *pEvent ) giReinforcementPool += (INT32)pEvent->uiParam; giTotalRecruitsInTraining -= (INT32)pEvent->uiParam; break; + + case EVENT_MINIEVENT: + if (!DelayEventIfBattleInProgress(pEvent)) + { + CheckMiniEvents(pEvent->uiParam); + } + break; } gfPreventDeletionOfAnyEvent = fOrigPreventFlag; return TRUE; diff --git a/Strategic/Game Event Hook.h b/Strategic/Game Event Hook.h index e290b329..20077c04 100644 --- a/Strategic/Game Event Hook.h +++ b/Strategic/Game Event Hook.h @@ -150,6 +150,8 @@ enum EVENT_BANDIT_ATTACK, // Flugente: like a creature attack, but with bandits EVENT_ARMY_FINISH_TRAINING, // Another bunch of Queen Army recruits is about to finish training + EVENT_MINIEVENT, + NUMBER_OF_EVENT_TYPES_PLUS_ONE, NUMBER_OF_EVENT_TYPES = NUMBER_OF_EVENT_TYPES_PLUS_ONE - 1 }; diff --git a/Strategic/Game Events.cpp b/Strategic/Game Events.cpp index 1a62807a..a698d903 100644 --- a/Strategic/Game Events.cpp +++ b/Strategic/Game Events.cpp @@ -10,6 +10,7 @@ #include "Debug.h" #include "Font Control.h" #include "message.h" + #include "MiniEvents.h" #include "Text.h" #endif @@ -120,6 +121,7 @@ CHAR16 gEventName[NUMBER_OF_EVENT_TYPES_PLUS_ONE][40]={ L"ASDPurchaseJeep", L"ASDPurchaseTank", L"ASDPurchaseHeli", + L"ASDPurchaseRobot", L"EnemyHeliUpdate", L"EnemyHeliRepair", L"EnemyHeliRefuel", @@ -137,6 +139,7 @@ CHAR16 gEventName[NUMBER_OF_EVENT_TYPES_PLUS_ONE][40]={ L"zombie attack", L"bandit attack", L"ArmyFinishTraining", + L"MiniEvent", }; #endif @@ -805,6 +808,8 @@ BOOLEAN LoadStrategicEventsFromSavedGame( HWFILE hFile ) pTempEvent->next = NULL; } + InitMiniEvents(); + return( TRUE ); } diff --git a/Strategic/Game Init.cpp b/Strategic/Game Init.cpp index dacd8005..50540a01 100644 --- a/Strategic/Game Init.cpp +++ b/Strategic/Game Init.cpp @@ -62,6 +62,7 @@ #include "CampaignStats.h" // added by Flugente #include "PMC.h" // added by Flugente #include "ASD.h" // added by Flugente + #include "MiniEvents.h" #endif #include "Vehicles.h" @@ -818,6 +819,8 @@ fFirstTimeInMapScreen = TRUE; RequestTriggerExitFromMapscreen( MAP_EXIT_TO_TACTICAL ); #endif + InitMiniEvents(); + return( TRUE ); } diff --git a/Strategic/Hourly Update.cpp b/Strategic/Hourly Update.cpp index ec0cb0f4..6398b920 100644 --- a/Strategic/Hourly Update.cpp +++ b/Strategic/Hourly Update.cpp @@ -707,6 +707,7 @@ void HourlyStealUpdate() && !pSoldier->flags.fMercAsleep && pSoldier->bAssignment != IN_TRANSIT && pSoldier->bAssignment != ASSIGNMENT_POW + && pSoldier->bAssignment != ASSIGNMENT_MINIEVENT && !( ( ( gWorldSectorX == pSoldier->sSectorX ) && ( gWorldSectorY == pSoldier->sSectorY ) && ( gbWorldSectorZ == pSoldier->bSectorZ ) ) && ( gTacticalStatus.fEnemyInSector || guiCurrentScreen == GAME_SCREEN ) ) ) { UINT8 ubSectorId = SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ); @@ -735,6 +736,7 @@ void HourlyStealUpdate() && !pOtherSoldier->flags.fBetweenSectors && pOtherSoldier->bAssignment != IN_TRANSIT && pOtherSoldier->bAssignment != ASSIGNMENT_POW + && pOtherSoldier->bAssignment != ASSIGNMENT_MINIEVENT && !SPY_LOCATION( pOtherSoldier->bAssignment ) && pOtherSoldier->bActive && !pOtherSoldier->flags.fMercAsleep diff --git a/Strategic/Map Screen Interface Bottom.cpp b/Strategic/Map Screen Interface Bottom.cpp index c89bd1ff..324e4c24 100644 --- a/Strategic/Map Screen Interface Bottom.cpp +++ b/Strategic/Map Screen Interface Bottom.cpp @@ -1714,6 +1714,7 @@ BOOLEAN AnyUsableRealMercenariesOnTeam( void ) !( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && !AM_A_ROBOT( pSoldier ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && ( pSoldier->bAssignment != ASSIGNMENT_DEAD ) && + ( pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ) && ( pSoldier->ubWhatKindOfMercAmI != MERC_TYPE__EPC ) ) { return( TRUE ); diff --git a/Strategic/Map Screen Interface Map.cpp b/Strategic/Map Screen Interface Map.cpp index 0c5dd026..733cbb07 100644 --- a/Strategic/Map Screen Interface Map.cpp +++ b/Strategic/Map Screen Interface Map.cpp @@ -1320,6 +1320,7 @@ INT32 ShowAssignedTeam(INT16 sMapX, INT16 sMapY, INT32 iCount) ( pSoldier->bAssignment >= ON_DUTY ) && ( pSoldier->bAssignment != VEHICLE ) && ( pSoldier->bAssignment != IN_TRANSIT ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && + ( pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ) && ( pSoldier->stats.bLife > 0 ) && ( !PlayerIDGroupInMotion( pSoldier->ubGroupID ) ) ) { @@ -6919,6 +6920,7 @@ BOOLEAN CanMercsScoutThisSector( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ ) if ( ( pSoldier->bAssignment == IN_TRANSIT ) || ( pSoldier->bAssignment == ASSIGNMENT_POW ) || ( pSoldier->bAssignment == ASSIGNMENT_DEAD ) || + ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) || ( pSoldier->flags.fMercAsleep == TRUE ) || ( pSoldier->stats.bLife < OKLIFE ) ) { diff --git a/Strategic/Map Screen Interface.cpp b/Strategic/Map Screen Interface.cpp index c5ccd99e..41d80d3f 100644 --- a/Strategic/Map Screen Interface.cpp +++ b/Strategic/Map Screen Interface.cpp @@ -1913,7 +1913,7 @@ void UpdateCharRegionHelpText( void ) pSoldier = MercPtrs[ gCharactersList[ bSelectedInfoChar ].usSolID ]; // health/energy/morale - if( pSoldier->bAssignment != ASSIGNMENT_POW ) + if( pSoldier->bAssignment != ASSIGNMENT_POW && pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ) { if ( pSoldier->stats.bLife != 0 ) { @@ -1955,7 +1955,7 @@ void UpdateCharRegionHelpText( void ) } else { - // POW - stats unknown + // POW/mini event - stats unknown swprintf( sString, L"%s: ??, %s: ??, %s: ??", pMapScreenStatusStrings[ 0 ], pMapScreenStatusStrings[ 1 ], pMapScreenStatusStrings[ 2 ] ); } @@ -3688,7 +3688,7 @@ void SetUpMovingListsForSector( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ ) pSoldier = MercPtrs[ gCharactersList[ iCounter ].usSolID ]; if( ( pSoldier->bActive ) && - ( pSoldier->bAssignment != IN_TRANSIT ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && !SPY_LOCATION( pSoldier->bAssignment ) && + ( pSoldier->bAssignment != IN_TRANSIT ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && !SPY_LOCATION( pSoldier->bAssignment ) && ( pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ) && ( pSoldier->sSectorX == sSectorX ) && ( pSoldier->sSectorY == sSectorY ) && ( pSoldier->bSectorZ == sSectorZ ) ) { if ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) @@ -5961,6 +5961,13 @@ BOOLEAN CanCharacterMoveInStrategic( SOLDIERTYPE *pSoldier, INT8 *pbErrorNumber return( FALSE ); } + // mini event? + if ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + { + *pbErrorNumber = 29; + return( FALSE ); + } + // underground? (can't move strategically, must use tactical traversal ) if( pSoldier->bSectorZ != 0 ) { diff --git a/Strategic/MiniEvents.cpp b/Strategic/MiniEvents.cpp new file mode 100644 index 00000000..c0553cdb --- /dev/null +++ b/Strategic/MiniEvents.cpp @@ -0,0 +1,1507 @@ +/* +Mini events are a feature where the player can receive short text blurbs, and pick one of two outcomes. +Based on game implementations like Gloomhaven (road/city events) or Battletech (travel events). + +The general idea is that every so often, the game checks to see if an event should be triggered. These shouldn't be very +frequent so that the usual gameplay isn't interrupted that often. + +Mini events are set up in MiniEvents.lua. This file handles mini event triggers and defines functions for the lua file +to call into. +*/ + +#ifdef PRECOMPILEDHEADERS + #include "Strategic All.h" + #include "GameSettings.h" +#else +#include "MiniEvents.h" + +#include "Assignments.h" +#include "Campaign.h" +#include "Campaign Types.h" +#include "connect.h" +#include "finances.h" +#include "Game Clock.h" +#include "Game Event Hook.h" +#include "GameSettings.h" +#include "LaptopSave.h" +#include "mapscreen.h" +#include "Map Screen Helicopter.h" +#include "message.h" +#include "MessageBoxScreen.h" +#include "MilitiaIndividual.h" +#include "Morale.h" +#include "Overhead.h" +#include "Overhead Types.h" +#include "random.h" +#include "Soldier Control.h" +#include "Soldier macros.h" +#include "Soldier Profile.h" +#include "Squads.h" +#include "strategic.h" +#include "strategicmap.h" +#include "Strategic Movement.h" +#include "Strategic Town Loyalty.h" +#include "Town Militia.h" +#include "Vehicles.h" +#endif + +extern "C" { +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" +} + +#include +#include +#include +#include + +extern CHAR16 gzUserDefinedButton1[ 128 ]; +extern CHAR16 gzUserDefinedButton2[ 128 ]; +extern GROUP *gpGroupList; + +static size_t MAX_BUTTON_LENGTH = 60; +static size_t MAX_BODY_LENGTH = 450; + +static UINT32 guiMiniEventsCachedScreen; +static LuaState gLS; +static std::vector gAllMercs; + +static void QueueNextMiniEvent(UINT32 nextEventId, UINT32 hoursToNextMiniEvent); + +// LUA STUFF +static void MiniEventsLua(UINT32 eventId); +static int MiniEventsLua_MessageBox(lua_State* LS); +static void MiniEventsLua_MessageBoxCallback(UINT8 ubExitValue); +static int MiniEventsLua_ResolveEvent(lua_State* LS); +static int MiniEventsLua_ScreenMsg(lua_State* LS); + +namespace MiniEventHelpers +{ + enum Skills + { + SKILL_START = 0, + SKILL_AUTO_WEAPONS, + SKILL_HEAVY_WEAPONS, + SKILL_MARKSMAN, + SKILL_HUNTER, + SKILL_GUNSLINGER, + SKILL_HAND_TO_HAND, + SKILL_DEPUTY, + SKILL_TECHNICIAN, + SKILL_PARAMEDIC, + SKILL_AMBIDEXTROUS, + SKILL_MELEE, + SKILL_THROWING, + SKILL_NIGHT_OPS, + SKILL_STEALTHY, + SKILL_ATHLETICS, + SKILL_BODYBUILDING, + SKILL_DEMOLITIONS, + SKILL_TEACHING, + SKILL_SCOUTING, + SKILL_COVERT_OPS, + SKILL_RADIO_OPERATOR, + SKILL_SNITCH, + SKILL_SURVIVAL, + SKILL_END + }; + + enum Stats + { + STAT_START = -1, + STAT_LIFE = 0, + STAT_STRENGTH, + STAT_AGILITY, + STAT_DEXTERITY, + STAT_WISDOM, + STAT_LEADERSHIP, + STAT_MARKSMANSHIP, + STAT_MECHANICAL, + STAT_EXPLOSIVE, + STAT_MEDICAL, + STAT_EXPLEVEL, + STAT_MAX + }; + + static BOOLEAN IsMajorSkill(const INT8 skillId) + { + return skillId >= Skills::SKILL_AUTO_WEAPONS && skillId <= Skills::SKILL_PARAMEDIC || skillId == Skills::SKILL_COVERT_OPS; + } + + static int l_AddMoneyToPlayerAccount(lua_State* LS) + { + const INT32 currentBalance = LaptopSaveInfo.iCurrentBalance; + const INT32 amount = lua_tointeger(LS, 1); + bool forceToZero; + + if (lua_gettop(LS) == 2) + forceToZero = lua_toboolean(LS, 2); + + if (currentBalance + amount < 0) + { + if (forceToZero) + { + AddTransactionToPlayersBook(MINI_EVENT, 0, GetWorldTotalMin(), -currentBalance); + lua_pushboolean(LS, true); + } + else + { + lua_pushboolean(LS, false); + } + } + else + { + AddTransactionToPlayersBook(MINI_EVENT, 0, GetWorldTotalMin(), amount); + lua_pushboolean(LS, true); + } + + return 1; + } + + static int l_AddIntel(lua_State* LS) + { + INT32 amount = lua_tointeger(LS, 1); + + if (!gGameExternalOptions.fIntelResource) + { + // intel is disabled. give some cash instead + amount *= 500; + const INT32 currentBalance = LaptopSaveInfo.iCurrentBalance; + if (currentBalance + amount < 0) + { + lua_pushboolean(LS, false); + } + else + { + AddTransactionToPlayersBook(MINI_EVENT, 0, GetWorldTotalMin(), amount); + lua_pushboolean(LS, true); + } + + return 1; + } + + const float currentIntel = LaptopSaveInfo.dIntelPool; + + if (currentIntel + amount < 0) + { + lua_pushboolean(LS, false); + } + else + { + AddIntel(static_cast(amount), TRUE); + lua_pushboolean(LS, true); + } + + lua_pushboolean(LS, true); + return 1; + } + + static int l_AddSkill(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + const INT8 skillId = static_cast(lua_tointeger(LS, 2)); + + std::for_each(gAllMercs.begin(), gAllMercs.end(), [profileId, skillId](SOLDIERTYPE* merc) { + if (merc->ubProfile != profileId) + return; + + int firstAvailableIndex = sizeof(gMercProfiles[merc->ubProfile].bSkillTraits) / sizeof(gMercProfiles[merc->ubProfile].bSkillTraits[0]); + int skillCount = 0; + INT8* skillTraits = gMercProfiles[merc->ubProfile].bSkillTraits; + + for (int i = 0; i < sizeof(gMercProfiles[merc->ubProfile].bSkillTraits) / sizeof(gMercProfiles[merc->ubProfile].bSkillTraits[0]); ++i) + { + if (*(skillTraits + i) == 0 && firstAvailableIndex > i) + firstAvailableIndex = i; + + if (*(skillTraits + i) == skillId) + skillCount++; + } + + if (skillCount == 0 || (IsMajorSkill(skillId) && skillCount < 2)) + *(skillTraits + firstAvailableIndex) = skillId; + }); + + + return 0; + } + + static int l_AddTownLoyalty(lua_State* LS) + { + const INT8 townId = static_cast(lua_tointeger(LS, 1)); + const int points = lua_tointeger(LS, 2); + + if (points > 0) + { + IncrementTownLoyalty(townId, static_cast(points)); + } + else if (points < 0) + { + DecrementTownLoyalty(townId, static_cast(std::abs(points))); + } + + return 0; + } + + static int l_AdjustBreathMax(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + const int val = lua_tointeger(LS, 2); + + if (val == 0) + return 0; + + std::for_each(gAllMercs.begin(), gAllMercs.end(), [profileId, val](SOLDIERTYPE* merc) { + if (merc->ubProfile == profileId) + { + int newBreathValue = merc->bBreathMax + val; + newBreathValue = max(min(newBreathValue, 100), 0); + + merc->bBreathMax = newBreathValue; + merc->bBreath = max(min(merc->bBreath, merc->bBreathMax), 0); + } + }); + + return 0; + } + + static int l_AdjustEnemyStrengthInSector(lua_State* LS) + { + const INT16 x = lua_tointeger(LS, 1); + const INT16 y = lua_tointeger(LS, 2); + const INT8 adminAdjustment = static_cast(lua_tointeger(LS, 3)); + const INT8 troopAdjustment = static_cast(lua_tointeger(LS, 4)); + const INT8 eliteAdjustment = static_cast(lua_tointeger(LS, 5)); + const INT8 robotAdjustment = gGameExternalOptions.fASDActive && gGameExternalOptions.fASDAssignsRobots ? static_cast(lua_tointeger(LS, 6)) : 0; + const INT8 jeepAdjustment = gGameExternalOptions.fASDActive && gGameExternalOptions.fASDAssignsRobots ? static_cast(lua_tointeger(LS, 7)) : 0; + const INT8 tankAdjustment = gGameExternalOptions.fASDActive && gGameExternalOptions.fASDAssignsRobots ? static_cast(lua_tointeger(LS, 8)) : 0; + + SECTORINFO *sector = &(SectorInfo[SECTOR(x,y)]); + if (!sector) + return 0; + + sector->ubNumAdmins = min(max(0, sector->ubNumAdmins + adminAdjustment), 30); + sector->ubNumTroops = min(max(0, sector->ubNumTroops + troopAdjustment), 30); + sector->ubNumElites = min(max(0, sector->ubNumElites + eliteAdjustment), 30); + sector->ubNumRobots = min(max(0, sector->ubNumRobots + robotAdjustment), 30); + sector->ubNumJeeps = min(max(0, sector->ubNumJeeps + jeepAdjustment), 30); + sector->ubNumTanks = min(max(0, sector->ubNumTanks + tankAdjustment), 30); + + return 0; + } + + static int l_AdjustMorale(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1));; + const INT8 val = static_cast(lua_tointeger(LS, 2)); + + if (val == 0) + return 0; + + std::for_each(gAllMercs.begin(), gAllMercs.end(), [val, profileId](SOLDIERTYPE* merc) { + if (merc->ubProfile == profileId) + { + merc->aiData.bStrategicMoraleMod += val; + merc->aiData.bStrategicMoraleMod = min(merc->aiData.bStrategicMoraleMod, gMoraleSettings.bModifiers[MORALE_MOD_MAX]); + merc->aiData.bStrategicMoraleMod = max(merc->aiData.bStrategicMoraleMod, -gMoraleSettings.bModifiers[MORALE_MOD_MAX]); + RefreshSoldierMorale(merc); + } + }); + + return 0; + } + + static int l_AdjustStat(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1));; + const UINT16 stat = lua_tointeger(LS, 2); + const INT16 val = lua_tointeger(LS, 3); + + if (stat <= STAT_START || stat >= STAT_EXPLEVEL) + return 0; + + std::for_each(gAllMercs.begin(), gAllMercs.end(), [stat, val, profileId](SOLDIERTYPE* merc) { + if (merc->ubProfile != profileId) + return; + + INT16 amount = val; + + switch (stat) + { + case STAT_LIFE: + amount = max(min(100 - merc->stats.bLifeMax, amount), -merc->stats.bLifeMax); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, HEALTHAMT, amount); + break; + case STAT_STRENGTH: + amount = max(min(100 - merc->stats.bStrength, amount), -merc->stats.bStrength); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, STRAMT, amount); + break; + case STAT_AGILITY: + amount = max(min(100 - merc->stats.bAgility, amount), -merc->stats.bAgility); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, AGILAMT, amount); + break; + case STAT_DEXTERITY: + amount = max(min(100 - merc->stats.bDexterity, amount), -merc->stats.bDexterity); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, DEXTAMT, amount); + break; + case STAT_WISDOM: + amount = max(min(100 - merc->stats.bWisdom, amount), -merc->stats.bWisdom); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, WISDOMAMT, amount); + break; + case STAT_LEADERSHIP: + amount = max(min(100 - merc->stats.bLeadership, amount), -merc->stats.bLeadership); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, LDRAMT, amount); + break; + case STAT_MARKSMANSHIP: + amount = max(min(100 - merc->stats.bMarksmanship, amount), -merc->stats.bMarksmanship); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, MARKAMT, amount); + break; + case STAT_MECHANICAL: + amount = max(min(100 - merc->stats.bMechanical, amount), -merc->stats.bMechanical); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, MECHANAMT, amount); + break; + case STAT_EXPLOSIVE: + amount = max(min(100 - merc->stats.bExplosive, amount), -merc->stats.bExplosive); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, EXPLODEAMT, amount); + break; + case STAT_MEDICAL: + amount = max(min(100 - merc->stats.bMedical, amount), -merc->stats.bMedical); + ChangeStat(&(gMercProfiles[merc->ubProfile]), merc, MEDICALAMT, amount); + break; + } + }); + + return 0; + } + + static int l_AdjustVehicleFuel(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1));; + const INT16 val = lua_tointeger(LS, 2); + INT32 vehicleId = -1; + + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if ((*iter)->ubProfile == profileId) + { + vehicleId = (*iter)->iVehicleId; + break; + + } + } + + if (vehicleId == -1) + { + // invalid vehicle id + lua_pushboolean(LS, false); + lua_pushstring(LS, ""); + return 2; + } + + for (int index = 0; index < ubNumberOfVehicles; ++index) + { + if (pVehicleList[index].fValid == TRUE) + { + SOLDIERTYPE* vehicle = GetSoldierStructureForVehicle(index); + + if (vehicle) + { + SpendVehicleFuel(vehicle, -(100*val)); + + lua_pushboolean(LS, true); + MERCPROFILESTRUCT mps = gMercProfiles[vehicle->ubProfile]; + CHAR8 nickname[50]; + sprintf(nickname, "%ls", mps.zNickname); + lua_pushstring(LS, nickname); + return 2; + } + } + } + + lua_pushboolean(LS, false); + lua_pushstring(LS, ""); + return 2; + } + + static int l_AdjustVehicleHealth(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1));; + const INT16 val = lua_tointeger(LS, 2); + INT32 vehicleId = -1; + + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if ((*iter)->ubProfile == profileId) + { + vehicleId = (*iter)->iVehicleId; + break; + + } + } + + if (vehicleId == -1) + { + // invalid vehicle id + lua_pushboolean(LS, false); + lua_pushstring(LS, ""); + return 2; + } + + for (int index = 0; index < ubNumberOfVehicles; ++index) + { + if (pVehicleList[index].fValid == TRUE) + { + SOLDIERTYPE* vehicle = GetSoldierStructureForVehicle(index); + + if (vehicle) + { + vehicle->stats.bLife += val; + vehicle->stats.bLife = max(min(vehicle->stats.bLife, 100), 0); + + lua_pushboolean(LS, true); + MERCPROFILESTRUCT mps = gMercProfiles[vehicle->ubProfile]; + CHAR8 nickname[50]; + sprintf(nickname, "%ls", mps.zNickname); + lua_pushstring(LS, nickname); + return 2; + } + } + } + + lua_pushboolean(LS, false); + lua_pushstring(LS, ""); + return 2; + } + + static int l_ApplyPermanentStatDamage(lua_State* LS) + { + // note that we're intentionally NOT incrementing the damaged stat array (ubCriticalStatDamage) as we do not want these penalties to be doctorable! + + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + const UINT16 stat = static_cast(lua_tointeger(LS, 2)); + const int amount = lua_tointeger(LS, 3); + + if (amount <= 0) + return 0; + + std::for_each(gAllMercs.begin(), gAllMercs.end(), [profileId, stat, amount](SOLDIERTYPE* merc) { + if (merc->ubProfile != profileId) + return; + + int loss = amount; + + switch (stat) + { + case STAT_LIFE: + if (loss >= merc->stats.bLifeMax) + { + loss = merc->stats.bLifeMax - 1; + } + merc->stats.bLifeMax -= loss; + merc->stats.bLife = min(merc->stats.bLife, merc->stats.bLifeMax); + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[merc->ubProfile].bLifeMax = merc->stats.bLifeMax; + gMercProfiles[merc->ubProfile].bLife = min(gMercProfiles[merc->ubProfile].bLife, gMercProfiles[merc->ubProfile].bLifeMax); + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeHealthTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( HEALTH_INCREASE ); + } + break; + case STAT_STRENGTH: + if (loss >= merc->stats.bStrength) + { + loss = merc->stats.bStrength - 1; + } + merc->stats.bStrength -= loss; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[ merc->ubProfile ].bStrength = merc->stats.bStrength; + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeStrengthTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( STRENGTH_INCREASE ); + } + break; + case STAT_AGILITY: + if (loss >= merc->stats.bAgility) + { + loss = merc->stats.bAgility - 1; + } + merc->stats.bAgility -= loss; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[ merc->ubProfile ].bAgility = merc->stats.bAgility; + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeAgilityTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( AGIL_INCREASE ); + } + break; + case STAT_DEXTERITY: + if (loss >= merc->stats.bDexterity) + { + loss = merc->stats.bDexterity - 1; + } + merc->stats.bDexterity -= loss; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[ merc->ubProfile ].bDexterity = merc->stats.bDexterity; + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeDexterityTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( DEX_INCREASE ); + } + break; + case STAT_WISDOM: + if (loss >= merc->stats.bWisdom) + { + loss = merc->stats.bWisdom - 1; + } + merc->stats.bWisdom -= loss; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[ merc->ubProfile ].bWisdom = merc->stats.bWisdom; + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeWisdomTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( WIS_INCREASE ); + } + break; + case STAT_LEADERSHIP: + if (loss >= merc->stats.bLeadership) + { + loss = merc->stats.bLeadership - 1; + } + merc->stats.bLeadership -= loss; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[ merc->ubProfile ].bLeadership = merc->stats.bLeadership; + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeLeadershipTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( LDR_INCREASE ); + } + break; + case STAT_MARKSMANSHIP: + if (loss >= merc->stats.bMarksmanship) + { + loss = merc->stats.bMarksmanship - 1; + } + merc->stats.bMarksmanship -= loss; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[ merc->ubProfile ].bMarksmanship = merc->stats.bMarksmanship; + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeMarksmanshipTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( MRK_INCREASE ); + } + break; + case STAT_MECHANICAL: + if (loss >= merc->stats.bMechanical) + { + loss = merc->stats.bMechanical - 1; + } + merc->stats.bMechanical -= loss; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[ merc->ubProfile ].bMechanical = merc->stats.bMechanical; + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeMechanicalTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( MECH_INCREASE ); + } + break; + case STAT_EXPLOSIVE: + if (loss >= merc->stats.bExplosive) + { + loss = merc->stats.bExplosive - 1; + } + merc->stats.bExplosive -= loss; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[ merc->ubProfile ].bExplosive = merc->stats.bExplosive; + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeExplosivesTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( EXP_INCREASE ); + } + break; + case STAT_MEDICAL: + if (loss >= merc->stats.bMedical) + { + loss = merc->stats.bMedical - 1; + } + merc->stats.bMedical -= loss; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[ merc->ubProfile ].bMedical = merc->stats.bMedical; + } + + if (merc->name[0] && merc->bVisible == TRUE) + { + merc->timeChanges.uiChangeMedicalTime = GetJA2Clock(); + merc->usValueGoneUp &= ~( MED_INCREASE ); + } + break; + } + }); + + return 0; + } + + static int l_ApplyDamage(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + const int amount = lua_tointeger(LS, 2); + + std::for_each(gAllMercs.begin(), gAllMercs.end(), [profileId, amount](SOLDIERTYPE* merc) { + if (merc->ubProfile != profileId) + return; + + int newLifeValue = merc->stats.bLife - amount; + newLifeValue = max(min(merc->stats.bLifeMax, newLifeValue), 0); + + merc->stats.bLife = newLifeValue; + + if (merc->ubProfile != NO_PROFILE) + { + gMercProfiles[merc->ubProfile].bLife = merc->stats.bLife; + } + + if (merc->stats.bLife <= 0) + { + HandleStrategicDeath(merc); + } + else if (merc->stats.bLife < 15) + { + merc->stats.bLife = 15; + } + }); + + return 0; + } + + static int l_CheckForAssignment(lua_State* LS) + { + const INT8 assignment = lua_tointeger(LS, 1); + INT16 sectorX = 0; + INT16 sectorY = 0; + INT8 sectorZ = 0; + bool globalSearch = true; + + if (lua_gettop(LS) == 4) + { + globalSearch = false; + sectorX = lua_tointeger(LS, 2); + sectorY = lua_tointeger(LS, 3); + sectorZ = lua_tointeger(LS, 4); + } + + std::vector foundMercs; + + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if (globalSearch || ((*iter)->sSectorX == sectorX && (*iter)->sSectorY == sectorY && (*iter)->bSectorZ == sectorZ)) + { + if ((*iter)->bAssignment == assignment) + { + foundMercs.push_back(*iter); + } + else if (assignment == ON_DUTY && (*iter)->bAssignment < ON_DUTY) + { + foundMercs.push_back(*iter); + } + } + } + + if (foundMercs.size() == 0) + { + lua_pushboolean(LS, false); + lua_pushstring(LS, ""); + lua_pushinteger(LS, 0); + } + else + { + const UINT8 index = Random(foundMercs.size()); + + MERCPROFILESTRUCT merc = gMercProfiles[foundMercs[index]->ubProfile]; + CHAR8 nickname[50]; + sprintf(nickname, "%ls", merc.zNickname); + lua_pushboolean(LS, true); + lua_pushstring(LS, nickname); + lua_pushinteger(LS, foundMercs[index]->ubProfile); + } + + return 3; + } + + static int l_CheckForSkill(lua_State* LS) + { + const INT8 skill = lua_tointeger(LS, 1); + INT16 sectorX = 0; + INT16 sectorY = 0; + INT8 sectorZ = 0; + INT8 profileId = 0; + bool globalSearch = true; + bool searchAllMercs = true; + + if (lua_gettop(LS) == 4) + { + globalSearch = false; + sectorX = lua_tointeger(LS, 2); + sectorY = lua_tointeger(LS, 3); + sectorZ = lua_tointeger(LS, 4); + } + + if (lua_gettop(LS) == 2) + { + searchAllMercs = false; + profileId = static_cast(lua_tointeger(LS, 2)); + } + + std::vector foundMercs; + + if (gGameOptions.fNewTraitSystem) + { + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + MERCPROFILESTRUCT merc = gMercProfiles[(*iter)->ubProfile]; + if (searchAllMercs || (*iter)->ubProfile == profileId) + { + for (int i = 0; i < sizeof(merc.bSkillTraits) / sizeof(merc.bSkillTraits[0]); ++i) + { + if (globalSearch || ((*iter)->sSectorX == sectorX && (*iter)->sSectorY == sectorY && (*iter)->bSectorZ == sectorZ)) + { + if(merc.bSkillTraits[i] == skill) + { + foundMercs.push_back(*iter); + } + } + } + } + } + } + + if (foundMercs.size() == 0) + { + lua_pushboolean(LS, false); + lua_pushstring(LS, ""); + lua_pushinteger(LS, 0); + } + else + { + const UINT8 index = Random(foundMercs.size()); + + MERCPROFILESTRUCT merc = gMercProfiles[foundMercs[index]->ubProfile]; + CHAR8 nickname[50]; + sprintf(nickname, "%ls", merc.zNickname); + lua_pushboolean(LS, true); + lua_pushstring(LS, nickname); + lua_pushinteger(LS, foundMercs[index]->ubProfile); + } + + return 3; + } + + static int l_CheckForSleep(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if ((*iter)->ubProfile == profileId && (*iter)->flags.fMercAsleep) + { + lua_pushboolean(LS, true); + return 1; + } + } + + lua_pushboolean(LS, false); + return 1; + } + + static int l_CheckForTravel(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if ((*iter)->ubProfile == profileId && (*iter)->flags.fBetweenSectors) + { + lua_pushboolean(LS, true); + return 1; + } + } + + lua_pushboolean(LS, false); + return 1; + } + + static int l_CheckForTravelOnFoot(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if ((*iter)->ubProfile == profileId && (*iter)->flags.fBetweenSectors && (*iter)->bAssignment != VEHICLE) + { + lua_pushboolean(LS, true); + return 1; + } + } + + lua_pushboolean(LS, false); + return 1; + } + + static int l_CheckForTravelInHelicopter(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if ((*iter)->ubProfile == profileId && SoldierAboardAirborneHeli(*iter)) + { + lua_pushboolean(LS, true); + return 1; + } + } + + lua_pushboolean(LS, false); + return 1; + } + + static int l_CreateMilitia(lua_State* LS) + { + const int greenMilitia = lua_tointeger(LS, 1); + const int regularMilitia = lua_tointeger(LS, 2); + const int eliteMilitia = lua_tointeger(LS, 3); + const INT16 sectorX = lua_tointeger(LS, 4); + const INT16 sectorY = lua_tointeger(LS, 5); + + if (greenMilitia > 0) + { + StrategicAddMilitiaToSector(sectorX, sectorY, GREEN_MILITIA, greenMilitia); + for (int i = 0; i < greenMilitia; ++i) + { + CreateNewIndividualMilitia( GREEN_MILITIA, MO_ARULCO, SECTOR(sectorX, sectorY) ); + } + } + + if (regularMilitia > 0) + { + StrategicAddMilitiaToSector(sectorX, sectorY, REGULAR_MILITIA, regularMilitia); + for (int i = 0; i < regularMilitia; ++i) + { + CreateNewIndividualMilitia( REGULAR_MILITIA, MO_ARULCO, SECTOR(sectorX, sectorY) ); + } + } + + if (eliteMilitia > 0) + { + StrategicAddMilitiaToSector(sectorX, sectorY, ELITE_MILITIA, eliteMilitia); + for (int i = 0; i < eliteMilitia; ++i) + { + CreateNewIndividualMilitia( ELITE_MILITIA, MO_ARULCO, SECTOR(sectorX, sectorY) ); + } + } + + return 0; + } + + static int l_GetCoordinates(lua_State* LS) + { + const UINT8 profileId = lua_tointeger(LS, 1); + + INT16 x = 0; + INT16 y = 0; + INT8 z = 0; + for (UINT32 i = gTacticalStatus.Team[OUR_TEAM].bFirstID; i <= gTacticalStatus.Team[OUR_TEAM].bLastID; ++i) + { + const SOLDIERTYPE* merc = MercPtrs[i]; + if (merc && merc->ubProfile == profileId) + { + x = merc->sSectorX; + y = merc->sSectorY; + z = merc->bSectorZ; + break; + } + } + + // invalid profileid + lua_pushinteger(LS, x); + lua_pushinteger(LS, y); + lua_pushinteger(LS, z); + return 3; + } + + static int l_GetHealth(lua_State* LS) + { + const UINT8 profileId = lua_tointeger(LS, 1); + + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if ((*iter)->ubProfile == profileId) + { + lua_pushinteger(LS, (*iter)->stats.bLife); + lua_pushinteger(LS, (*iter)->stats.bLifeMax); + return 2; + } + } + + lua_pushinteger(LS, 0); + lua_pushinteger(LS, 0); + return 2; + } + + static int l_GetHoursRemainingOnMiniEvent(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if ((*iter)->ubProfile == profileId) + { + lua_pushinteger(LS, (*iter)->ubHoursRemainingOnMiniEvent); + return 1; + } + } + + lua_pushinteger(LS, -1); + return 1; + } + + static int l_GetSectorIDString(lua_State* LS) + { + const INT16 x = lua_tointeger(LS, 1); + const INT16 y = lua_tointeger(LS, 2); + const INT8 z = lua_tointeger(LS, 3); + CHAR16 sectorName[512]; + GetSectorIDString(x, y, z, sectorName, FALSE); + std::wstring wsSectorName(sectorName); + std::string strSectorName(wsSectorName.begin(), wsSectorName.end()); + + lua_pushstring(LS, strSectorName.c_str()); + return 1; + } + + static int l_GetSkills(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + + lua_newtable(LS); + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if ((*iter)->ubProfile == profileId) + { + const INT8* skillTraits = gMercProfiles[(*iter)->ubProfile].bSkillTraits; + for (int i = 0; i < sizeof(gMercProfiles[(*iter)->ubProfile].bSkillTraits) / sizeof(gMercProfiles[(*iter)->ubProfile].bSkillTraits[0]) ;++i) + { + const int skillId = *(skillTraits + i); + if (skillId > 0) + { + lua_pushinteger(LS, i + 1); + lua_pushinteger(LS, skillId); + lua_settable(LS, -3); + } + else + { + break; + } + } + } + } + + return 1; + } + + static int l_GetStat(lua_State* LS) + { + const UINT16 stat = lua_tointeger(LS, 1); + INT16 sectorX = 0; + INT16 sectorY = 0; + INT8 sectorZ = 0; + UINT8 profileId = 0; + bool globalSearch = true; + bool lookAtAllMercs = true; + + if (lua_gettop(LS) == 4) + { + globalSearch = false; + sectorX = lua_tointeger(LS, 2); + sectorY = lua_tointeger(LS, 3); + sectorZ = lua_tointeger(LS, 4); + } + + if (lua_gettop(LS) == 2) + { + lookAtAllMercs = false; + profileId = static_cast(lua_tointeger(LS, 2)); + } + + INT8 bestStat = 0; + SOLDIERTYPE* bestSoldier = gAllMercs[0]; + for (auto iter = gAllMercs.begin(); iter != gAllMercs.end(); ++iter) + { + if (lookAtAllMercs || ((*iter)->ubProfile == profileId)) + { + if (globalSearch || ((*iter)->sSectorX == sectorX && (*iter)->sSectorY == sectorY && (*iter)->bSectorZ == sectorZ)) + { + switch (stat) + { + case STAT_LIFE: + if ((*iter)->stats.bLifeMax > bestStat) + { + bestStat = (*iter)->stats.bLifeMax; + bestSoldier = *iter; + } + break; + case STAT_STRENGTH: + if ((*iter)->stats.bStrength > bestStat) + { + bestStat = (*iter)->stats.bStrength; + bestSoldier = *iter; + } + break; + case STAT_AGILITY: + if ((*iter)->stats.bAgility > bestStat) + { + bestStat = (*iter)->stats.bAgility; + bestSoldier = *iter; + } + break; + case STAT_DEXTERITY: + if ((*iter)->stats.bDexterity > bestStat) + { + bestStat = (*iter)->stats.bDexterity; + bestSoldier = *iter; + } + break; + case STAT_WISDOM: + if ((*iter)->stats.bWisdom > bestStat) + { + bestStat = (*iter)->stats.bWisdom; + bestSoldier = *iter; + } + break; + case STAT_LEADERSHIP: + if ((*iter)->stats.bLeadership > bestStat) + { + bestStat = (*iter)->stats.bLeadership; + bestSoldier = *iter; + } + break; + case STAT_MARKSMANSHIP: + if ((*iter)->stats.bMarksmanship > bestStat) + { + bestStat = (*iter)->stats.bMarksmanship; + bestSoldier = *iter; + } + break; + case STAT_MECHANICAL: + if ((*iter)->stats.bMechanical > bestStat) + { + bestStat = (*iter)->stats.bMechanical; + bestSoldier = *iter; + } + break; + case STAT_EXPLOSIVE: + if ((*iter)->stats.bExplosive > bestStat) + { + bestStat = (*iter)->stats.bExplosive; + bestSoldier = *iter; + } + break; + case STAT_MEDICAL: + if ((*iter)->stats.bMedical > bestStat) + { + bestStat = (*iter)->stats.bMedical; + bestSoldier = *iter; + } + break; + case STAT_EXPLEVEL: + if ((*iter)->stats.bExpLevel > bestStat) + { + bestStat = (*iter)->stats.bExpLevel; + bestSoldier = *iter; + } + break; + } + } + } + } + + CHAR8 nickname[50]; + sprintf(nickname, "%ls", gMercProfiles[bestSoldier->ubProfile].zNickname); + lua_pushinteger(LS, bestStat); + lua_pushstring(LS, nickname); + lua_pushinteger(LS, bestSoldier->ubProfile); + return 3; + } + + static int l_GetProgress(lua_State* LS) + { + lua_pushinteger(LS, HighestPlayerProgressPercentage()); + return 1; + } + + static int l_GetTownId(lua_State* LS) + { + const INT16 x = lua_tointeger(LS, 1); + const INT16 y = lua_tointeger(LS, 2); + + const UINT8 townId = GetTownIdForSector(x, y); + + lua_pushinteger(LS, townId); + return 1; + } + + static int l_SendMercOnMiniEvent(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + const UINT16 hoursOnMiniEvent = static_cast(lua_tointeger(LS, 2)); + + if (hoursOnMiniEvent == 0) + return 0; + + std::for_each(gAllMercs.begin(), gAllMercs.end(), [profileId, hoursOnMiniEvent](SOLDIERTYPE* merc) { + if (merc->ubProfile != profileId) + return; + + TakeSoldierOutOfVehicle(merc); + RemoveCharacterFromSquads(merc); + merc->ubHoursRemainingOnMiniEvent = hoursOnMiniEvent; + merc->bSectorZ += MINI_EVENT_Z_OFFSET; + merc->bBleeding = 0; + SetTimeOfAssignmentChangeForMerc(merc); + ChangeSoldiersAssignment(merc, ASSIGNMENT_MINIEVENT); + + // see HandleMiniEventAssignments() in Assignments.cpp for returning merc to normal + }); + + return 0; + } + + static int l_SetEnemyGroupVisibility(lua_State* LS) + { + const bool visible = lua_toboolean(LS, 1); + INT16 x = 0; + INT16 y = 0; + + if (lua_gettop(LS) == 3) + { + x = lua_tointeger(LS, 2); + y = lua_tointeger(LS, 3); + } + + if (x == 0 && y == 0) + { + for ( INT16 sX = 1; sX < (MAP_WORLD_X - 1); ++sX ) + { + for ( INT16 sY = 1; sY < (MAP_WORLD_Y - 1); ++sY ) + { + if (visible) + { + SectorInfo[SECTOR(sX, sY)].ubDetectionLevel |= (1 << 2); + } + else + { + SectorInfo[SECTOR(sX, sY)].ubDetectionLevel &= ~(1 << 2); + } + } + } + } + else + { + if (visible) + { + SectorInfo[SECTOR(x,y)].ubDetectionLevel |= (1 << 2); + } + else + { + SectorInfo[SECTOR(x,y)].ubDetectionLevel &= ~(1 << 2); + } + } + + fMapPanelDirty = true; + + return 0; + } + + static int l_SetMercCoordinates(lua_State* LS) + { + const UINT8 profileId = static_cast(lua_tointeger(LS, 1)); + const INT16 sectorX = lua_tointeger(LS, 2); + const INT16 sectorY = lua_tointeger(LS, 3); + const INT8 sectorZ = lua_tointeger(LS, 4); + + std::for_each(gAllMercs.begin(), gAllMercs.end(), [profileId, sectorX, sectorY, sectorZ](SOLDIERTYPE* merc) { + if (merc->ubProfile != profileId) + return; + + merc->sSectorX = sectorX; + merc->sSectorY = sectorY; + merc->bSectorZ = sectorZ; + + TakeSoldierOutOfVehicle(merc); + RemoveCharacterFromSquads(merc); + merc->ubInsertionDirection = DIRECTION_IRRELEVANT; + merc->ubStrategicInsertionCode = INSERTION_CODE_CENTER; + AddCharacterToAnySquad(merc); + }); + + return 0; + } +} + +void InitMiniEvents() +{ + // if a user really wants to reschedule an event by disabling this flag, saving their game, re-enabling the flag, then loading their game, then fine. it's a single player game, be my guest + if (gGameExternalOptions.fMiniEventsEnabled == false || is_networked) + { + DeleteAllStrategicEventsOfType(EVENT_MINIEVENT); + } + else + { + if (GetAllStrategicEventsOfType(EVENT_MINIEVENT).size() == 0) + QueueNextMiniEvent(0, 0); + + const char* filename = "scripts\\MiniEvents.lua"; + + if (gLS() != nullptr) + { + // clear old lua state (on load or new game) + LuaState::CLOSE(gLS); + } + gLS = LuaState::INIT(true); + + SGP_THROW_IFFALSE( gLS.EvalFile(filename), _BS("Cannot open file: ") << filename << _BS::cget ); + + lua_register(gLS(), "CScreenMsg", MiniEventsLua_ScreenMsg); + lua_register(gLS(), "CMsgBox", MiniEventsLua_MessageBox); + lua_register(gLS(), "CResolveEvent", MiniEventsLua_ResolveEvent); + + using namespace MiniEventHelpers; + { + lua_register(gLS(), "CAddIntel", l_AddIntel); + lua_register(gLS(), "CAddMoneyToPlayerAccount", l_AddMoneyToPlayerAccount); + lua_register(gLS(), "CAddTownLoyalty", l_AddTownLoyalty); + lua_register(gLS(), "CAddSkill", l_AddSkill); + lua_register(gLS(), "CAdjustBreathMax", l_AdjustBreathMax); + lua_register(gLS(), "CAdjustMorale", l_AdjustMorale); + lua_register(gLS(), "CAdjustEnemyStrengthInSector", l_AdjustEnemyStrengthInSector); + lua_register(gLS(), "CAdjustStat", l_AdjustStat); + lua_register(gLS(), "CAdjustVehicleFuel", l_AdjustVehicleFuel); + lua_register(gLS(), "CAdjustVehicleHealth", l_AdjustVehicleHealth); + lua_register(gLS(), "CApplyDamage", l_ApplyDamage); + lua_register(gLS(), "CApplyPermanentStatDamage", l_ApplyPermanentStatDamage); + lua_register(gLS(), "CCheckForAssignment", l_CheckForAssignment); + lua_register(gLS(), "CCheckForSkill", l_CheckForSkill); + lua_register(gLS(), "CCheckForSleep", l_CheckForSleep); + lua_register(gLS(), "CCheckForTravel", l_CheckForTravel); + lua_register(gLS(), "CCheckForTravelOnFoot", l_CheckForTravelOnFoot); + lua_register(gLS(), "CCheckForTravelInHelicopter", l_CheckForTravelInHelicopter); + lua_register(gLS(), "CCreateMilitia", l_CreateMilitia); + lua_register(gLS(), "CGetCoordinates", l_GetCoordinates); + lua_register(gLS(), "CGetHealth", l_GetHealth); + lua_register(gLS(), "CGetHoursRemainingOnMiniEvent", l_GetHoursRemainingOnMiniEvent); + lua_register(gLS(), "CGetSectorIDString", l_GetSectorIDString); + lua_register(gLS(), "CGetSkills", l_GetSkills); + lua_register(gLS(), "CGetStat", l_GetStat); + lua_register(gLS(), "CGetProgress", l_GetProgress); + lua_register(gLS(), "CGetTownId", l_GetTownId); + lua_register(gLS(), "CSendMercOnMiniEvent", l_SendMercOnMiniEvent); + lua_register(gLS(), "CSetEnemyGroupVisibility", l_SetEnemyGroupVisibility); + lua_register(gLS(), "CSetMercCoordinates", l_SetMercCoordinates); + } + } +} + +void CheckMiniEvents(UINT32 nextEventId) +{ + if (gGameExternalOptions.fMiniEventsEnabled == false || is_networked) + return; + + // no events if we're in combat or a hostile sector + if ((gTacticalStatus.uiFlags & INCOMBAT) || gTacticalStatus.fEnemyInSector) + return; + + StopTimeCompression(); + MiniEventsLua(nextEventId); +} + +static void QueueNextMiniEvent(UINT32 nextEventId, UINT32 hoursToNextMiniEvent) +{ + const UINT32 timestamp = GetWorldTotalMin() + gGameExternalOptions.fMiniEventsMinHoursBetweenEvents * 60 + Random((gGameExternalOptions.fMiniEventsMaxHoursBetweenEvents - gGameExternalOptions.fMiniEventsMinHoursBetweenEvents) * 60); + AddStrategicEvent(EVENT_MINIEVENT, hoursToNextMiniEvent > 0 ? hoursToNextMiniEvent : timestamp, nextEventId); +} + +// LUA STUFF FOLLOWS + +void MiniEventsLua(UINT32 eventId) +{ + gAllMercs.clear(); + + // get all mercs eligible to get a mini event + for (UINT32 cnt = gTacticalStatus.Team[OUR_TEAM].bFirstID; cnt <= gTacticalStatus.Team[OUR_TEAM].bLastID; ++cnt) + { + SOLDIERTYPE* pSoldier = MercPtrs[ cnt ]; + + if (pSoldier && pSoldier->bActive + && pSoldier->stats.bLife > 0 + && pSoldier->bAssignment != IN_TRANSIT + && pSoldier->bAssignment != ASSIGNMENT_POW + && !(pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE)) + { + gAllMercs.push_back(pSoldier); + } + } + + if (eventId > 0) + { + LuaFunction f = LuaFunction(gLS, "BeginSpecificEvent"); + // first param: the event to trigger + f.Param(eventId); + + // second param: a table containing basic info about all of the player's mercs ({ nickname = profileid }) + f.TableOpen(); + for (UINT32 i = gTacticalStatus.Team[OUR_TEAM].bFirstID; i <= gTacticalStatus.Team[OUR_TEAM].bLastID; ++i) + { + const SOLDIERTYPE* merc = MercPtrs[i]; + if (merc && merc->bActive && merc->bAssignment != IN_TRANSIT && !(merc->flags.uiStatusFlags & SOLDIER_VEHICLE) && !(AM_A_ROBOT(merc))) + { + std::wstring ws(gMercProfiles[merc->ubProfile].zNickname); + std::string str(ws.begin(), ws.end()); + f.TParam(str.c_str(), (int)merc->ubProfile); + } + } + f.TableClose(); + + SGP_THROW_IFFALSE(f.Call(2), "call to lua function BeginSpecificEvent failed"); + } + else + { + LuaFunction f = LuaFunction(gLS, "BeginRandomEvent"); + // first param: a table containing basic info about all of the player's mercs ({ nickname = profileid }) + f.TableOpen(); + for (UINT32 i = gTacticalStatus.Team[OUR_TEAM].bFirstID; i <= gTacticalStatus.Team[OUR_TEAM].bLastID; ++i) + { + const SOLDIERTYPE* merc = MercPtrs[i]; + if (merc && merc->bActive && merc->bAssignment != IN_TRANSIT && !(merc->flags.uiStatusFlags & SOLDIER_VEHICLE) && !(AM_A_ROBOT(merc))) + { + std::wstring ws(gMercProfiles[merc->ubProfile].zNickname); + std::string str(ws.begin(), ws.end()); + f.TParam(str.c_str(), (int)merc->ubProfile); + } + } + f.TableClose(); + + SGP_THROW_IFFALSE(f.Call(1), "call to lua function BeginRandomEvent failed"); + } +} + +static int MiniEventsLua_MessageBox(lua_State* LS) +{ + size_t len = 0; + CHAR16 w_str[500]; + + std::string str = lua_tolstring(LS, 1, &len); + MultiByteToWideChar( CP_UTF8, 0, len > MAX_BUTTON_LENGTH ? str.substr(0, MAX_BUTTON_LENGTH).c_str() : str.c_str(), -1, w_str, sizeof(w_str) / sizeof(w_str[0]) ); + w_str[sizeof(w_str) / sizeof(w_str[0]) - 1] = '\0'; + wcscpy( gzUserDefinedButton1, w_str ); + + str = lua_tolstring(LS, 2, &len); + MultiByteToWideChar( CP_UTF8, 0, len > MAX_BUTTON_LENGTH ? str.substr(0, MAX_BUTTON_LENGTH).c_str() : str.c_str(), -1, w_str, sizeof(w_str) / sizeof(w_str[0]) ); + w_str[sizeof(w_str) / sizeof(w_str[0]) - 1] = '\0'; + wcscpy( gzUserDefinedButton2, w_str ); + + str = lua_tolstring(LS, 3, &len); + MultiByteToWideChar( CP_UTF8, 0, len > MAX_BODY_LENGTH ? str.substr(0, MAX_BODY_LENGTH).c_str() : str.c_str(), -1, w_str, sizeof(w_str) / sizeof(w_str[0]) ); + w_str[sizeof(w_str) / sizeof(w_str[0]) - 1] = '\0'; + + // we need to cache the screen here so that the second msgbox doesn't keep the global screen state in MSG_BOX_SCREEN (causes infinite recursion) + guiMiniEventsCachedScreen = guiCurrentScreen; + DoMessageBox(MSG_BOX_MINIEVENT_STYLE, w_str, + guiCurrentScreen, MSG_BOX_FLAG_WIDE_BUTTONS | MSG_BOX_FLAG_BIGGER, MiniEventsLua_MessageBoxCallback , NULL); + + return 0; +} + +static void MiniEventsLua_MessageBoxCallback(UINT8 ubExitValue) +{ + // appears to be first button = 2, second button = 3 + gfDontOverRideSaveBuffer = FALSE; + LuaFunction(gLS, "ResolveMsgBox" ).Param(ubExitValue == 2).Call(1); +} + +static int MiniEventsLua_ResolveEvent(lua_State* LS) +{ + size_t len = 0; + std::string str = lua_tolstring(LS, 1, &len); + + UINT32 nextEventId = 0; + if (lua_gettop(LS) >= 2) + nextEventId = static_cast(lua_tointeger(LS, 2)); + + UINT32 hoursToNextMiniEvent = 0; + if (lua_gettop(LS) == 3) + hoursToNextMiniEvent = static_cast(lua_tointeger(LS, 3)); + + CHAR16 w_str[500]; + MultiByteToWideChar( CP_UTF8, 0, len > MAX_BODY_LENGTH ? str.substr(0, MAX_BODY_LENGTH).c_str() : str.c_str(), -1, w_str, sizeof(w_str) / sizeof(w_str[0]) ); + w_str[sizeof(w_str) / sizeof(w_str[0]) - 1] = '\0'; + + MSYS_RemoveRegion(&(gMsgBox.BackRegion)); + DoMessageBox(MSG_BOX_MINIEVENT_STYLE, w_str, + guiMiniEventsCachedScreen, MSG_BOX_FLAG_OK | MSG_BOX_FLAG_BIGGER, [](UINT8 ubExitValue) { gfDontOverRideSaveBuffer = FALSE; }, NULL); + + QueueNextMiniEvent(nextEventId, hoursToNextMiniEvent); + + return 0; +} + +static int MiniEventsLua_ScreenMsg(lua_State* LS) +{ + size_t len = 0; + std::string str = lua_tolstring(LS, 1, &len); + + CHAR16 w_str[250]; + MultiByteToWideChar( CP_UTF8, 0, len > 250 ? str.substr(0, 250).c_str() : str.c_str(), -1, w_str, sizeof(w_str) / sizeof(w_str[0]) ); + w_str[sizeof(w_str) / sizeof(w_str[0]) - 1] = '\0'; + + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s", w_str ); + return 0; +} + diff --git a/Strategic/MiniEvents.h b/Strategic/MiniEvents.h new file mode 100644 index 00000000..5ed48a4e --- /dev/null +++ b/Strategic/MiniEvents.h @@ -0,0 +1,11 @@ +#ifndef _MINIEVENTS_H +#define _MINIEVENTS_H + +#define MINI_EVENT_Z_OFFSET 9 + +#include "Types.h" + +void InitMiniEvents(); +void CheckMiniEvents(UINT32 nextEventId); + +#endif diff --git a/Strategic/PreBattle Interface.cpp b/Strategic/PreBattle Interface.cpp index 638d0aac..8ce9d796 100644 --- a/Strategic/PreBattle Interface.cpp +++ b/Strategic/PreBattle Interface.cpp @@ -2360,6 +2360,7 @@ BOOLEAN PlayerMercInvolvedInThisCombat( SOLDIERTYPE *pSoldier ) pSoldier->bAssignment != IN_TRANSIT && pSoldier->bAssignment != ASSIGNMENT_POW && pSoldier->bAssignment != ASSIGNMENT_DEAD && + pSoldier->bAssignment != ASSIGNMENT_MINIEVENT && !(pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) && // Robot is involved if it has a valid controller with it, uninvolved otherwise ( !AM_A_ROBOT( pSoldier ) || ( pSoldier->ubRobotRemoteHolderID != NOBODY ) ) && diff --git a/Strategic/Queen Command.cpp b/Strategic/Queen Command.cpp index 35cee345..e1aa3262 100644 --- a/Strategic/Queen Command.cpp +++ b/Strategic/Queen Command.cpp @@ -299,7 +299,7 @@ UINT16 NumPlayerTeamMembersInSector( INT16 sSectorX, INT16 sSectorY, INT8 sSecto // we test several conditions before we allow adding an opinion // other merc must be active, have a profile, be someone else and not be in transit or dead if ( pTeamSoldier->bActive && !pTeamSoldier->flags.fBetweenSectors && pTeamSoldier->stats.bLife > 0 && !(pTeamSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) && - !(pTeamSoldier->bAssignment == IN_TRANSIT || pTeamSoldier->bAssignment == ASSIGNMENT_DEAD || pTeamSoldier->bAssignment == ASSIGNMENT_POW) && + !(pTeamSoldier->bAssignment == IN_TRANSIT || pTeamSoldier->bAssignment == ASSIGNMENT_DEAD || pTeamSoldier->bAssignment == ASSIGNMENT_POW || pTeamSoldier->bAssignment == ASSIGNMENT_MINIEVENT) && (pTeamSoldier->sSectorX == sSectorX && pTeamSoldier->sSectorY == sSectorY && pTeamSoldier->bSectorZ == sSectorZ) ) { ++teammemberspresent; @@ -2789,6 +2789,12 @@ void EnemyCapturesPlayerSoldier( SOLDIERTYPE *pSoldier ) return; } + // can't capture mercs on mini event adventures + if (pSoldier->bAssignment == ASSIGNMENT_MINIEVENT) + { + return; + } + if ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) { return; diff --git a/Strategic/Strategic All.h b/Strategic/Strategic All.h index fc01b826..a16433a4 100644 --- a/Strategic/Strategic All.h +++ b/Strategic/Strategic All.h @@ -227,4 +227,5 @@ #include "_JA25EnglishText.h" #include "Debug Control.h" #include "expat.h" +#include "MiniEvents.h" #endif \ No newline at end of file diff --git a/Strategic/Strategic Movement.cpp b/Strategic/Strategic Movement.cpp index eba40c8e..7c3cd145 100644 --- a/Strategic/Strategic Movement.cpp +++ b/Strategic/Strategic Movement.cpp @@ -5469,7 +5469,7 @@ BOOLEAN TestForBloodcatAmbush( GROUP *pGroup ) { if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->stats.bLife && !(MercPtrs[ i ]->flags.uiStatusFlags & SOLDIER_VEHICLE) ) { - if ( MercPtrs[ i ]->sSectorX == pGroup->ubSectorX && MercPtrs[ i ]->sSectorY == pGroup->ubSectorY && MercPtrs[ i ]->bAssignment != ASSIGNMENT_POW && MercPtrs[ i ]->stats.bLife >= OKLIFE ) + if ( MercPtrs[ i ]->sSectorX == pGroup->ubSectorX && MercPtrs[ i ]->sSectorY == pGroup->ubSectorY && MercPtrs[ i ]->bAssignment != ASSIGNMENT_POW && MercPtrs[ i ]->bAssignment != ASSIGNMENT_MINIEVENT && MercPtrs[ i ]->stats.bLife >= OKLIFE ) { if( HAS_SKILL_TRAIT( MercPtrs[ i ], SCOUTING_NT ) && MercPtrs[ i ]->ubProfile != NO_PROFILE ) { @@ -5873,6 +5873,7 @@ BOOLEAN GroupHasInTransitDeadOrPOWMercs( GROUP *pGroup ) { if( ( pPlayer->pSoldier->bAssignment == IN_TRANSIT ) || ( pPlayer->pSoldier->bAssignment == ASSIGNMENT_POW ) || + ( pPlayer->pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) || SPY_LOCATION( pPlayer->pSoldier->bAssignment ) || ( pPlayer->pSoldier->bAssignment == ASSIGNMENT_DEAD ) ) { diff --git a/Strategic/Strategic_VS2005.vcproj b/Strategic/Strategic_VS2005.vcproj index c911743c..6478ef84 100644 --- a/Strategic/Strategic_VS2005.vcproj +++ b/Strategic/Strategic_VS2005.vcproj @@ -442,6 +442,10 @@ RelativePath=".\MilitiaSquads.h" > + + @@ -656,6 +660,10 @@ RelativePath=".\MilitiaSquads.cpp" > + + diff --git a/Strategic/Strategic_VS2008.vcproj b/Strategic/Strategic_VS2008.vcproj index 7d96282b..6d371b77 100644 --- a/Strategic/Strategic_VS2008.vcproj +++ b/Strategic/Strategic_VS2008.vcproj @@ -442,6 +442,10 @@ RelativePath="MilitiaSquads.h" > + + @@ -654,6 +658,10 @@ RelativePath="MilitiaSquads.cpp" > + + diff --git a/Strategic/Strategic_VS2010.vcxproj b/Strategic/Strategic_VS2010.vcxproj index 5f2869af..021fb0e0 100644 --- a/Strategic/Strategic_VS2010.vcxproj +++ b/Strategic/Strategic_VS2010.vcxproj @@ -48,6 +48,7 @@ + @@ -102,6 +103,7 @@ + diff --git a/Strategic/Strategic_VS2010.vcxproj.filters b/Strategic/Strategic_VS2010.vcxproj.filters index 08ac88a7..f55f73fa 100644 --- a/Strategic/Strategic_VS2010.vcxproj.filters +++ b/Strategic/Strategic_VS2010.vcxproj.filters @@ -77,6 +77,9 @@ Header Files + + + Header Files Header Files @@ -235,6 +238,9 @@ Source Files + + + Source Files Source Files diff --git a/Strategic/Strategic_VS2013.vcxproj b/Strategic/Strategic_VS2013.vcxproj index 0c90891f..3e4558b5 100644 --- a/Strategic/Strategic_VS2013.vcxproj +++ b/Strategic/Strategic_VS2013.vcxproj @@ -50,6 +50,7 @@ + @@ -104,6 +105,7 @@ + diff --git a/Strategic/Strategic_VS2013.vcxproj.filters b/Strategic/Strategic_VS2013.vcxproj.filters index 87da8955..9e782c76 100644 --- a/Strategic/Strategic_VS2013.vcxproj.filters +++ b/Strategic/Strategic_VS2013.vcxproj.filters @@ -74,6 +74,9 @@ Header Files + + + Header Files Header Files @@ -232,6 +235,9 @@ Source Files + + + Source Files Source Files diff --git a/Strategic/Strategic_VS2017.vcxproj b/Strategic/Strategic_VS2017.vcxproj index a45fc06c..b1835d6d 100644 --- a/Strategic/Strategic_VS2017.vcxproj +++ b/Strategic/Strategic_VS2017.vcxproj @@ -50,6 +50,7 @@ + @@ -104,6 +105,7 @@ + diff --git a/Strategic/Strategic_VS2019.vcxproj b/Strategic/Strategic_VS2019.vcxproj index 655ca1cf..f2a59618 100644 --- a/Strategic/Strategic_VS2019.vcxproj +++ b/Strategic/Strategic_VS2019.vcxproj @@ -70,6 +70,7 @@ + @@ -124,6 +125,7 @@ + diff --git a/Strategic/mapscreen.cpp b/Strategic/mapscreen.cpp index 3e41bb31..d7288f08 100644 --- a/Strategic/mapscreen.cpp +++ b/Strategic/mapscreen.cpp @@ -2312,10 +2312,11 @@ void DrawCharBars( void ) return; } - // skip POWs, dead guys + // skip POWs, dead guys, mini events if( ( pSoldier->stats.bLife == 0 ) || ( pSoldier->bAssignment == ASSIGNMENT_DEAD ) || - ( pSoldier->bAssignment == ASSIGNMENT_POW ) ) + ( pSoldier->bAssignment == ASSIGNMENT_POW ) || + ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) ) { return; } @@ -2670,7 +2671,7 @@ void DrawCharHealth( INT16 sCharNum ) pSoldier = &Menptr[gCharactersList[sCharNum].usSolID]; - if( pSoldier->bAssignment != ASSIGNMENT_POW ) + if( pSoldier->bAssignment != ASSIGNMENT_POW && pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ) { // find starting X coordinate by centering all 3 substrings together, then print them separately (different colors)! swprintf( sString, L"%d/%d", pSoldier->stats.bLife, pSoldier->stats.bLifeMax ); @@ -10837,7 +10838,7 @@ void TeamListInfoRegionBtnCallBack(MOUSE_REGION *pRegion, INT32 iReason ) fPlotForMilitia = FALSE; // if not dead or POW, select his sector - if( ( pSoldier->stats.bLife > 0 ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) )//&& !SPY_LOCATION( pSoldier->bAssignment ) ) + if( ( pSoldier->stats.bLife > 0 ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && ( pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ))//&& !SPY_LOCATION( pSoldier->bAssignment ) ) { ChangeSelectedMapSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ); } @@ -10890,7 +10891,7 @@ void TeamListInfoRegionBtnCallBack(MOUSE_REGION *pRegion, INT32 iReason ) fPlotForMilitia = FALSE; // if not dead or POW, select his sector - if( ( pSoldier->stats.bLife > 0 ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && !SPY_LOCATION( pSoldier->bAssignment ) ) + if( ( pSoldier->stats.bLife > 0 ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && !SPY_LOCATION( pSoldier->bAssignment ) && ( pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ) ) { ChangeSelectedMapSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ); } @@ -11073,7 +11074,7 @@ void TeamListAssignmentRegionBtnCallBack(MOUSE_REGION *pRegion, INT32 iReason ) fShownAssignmentMenu = FALSE; // if not dead or POW, select his sector - if( ( pSoldier->stats.bLife > 0 ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) ) + if( ( pSoldier->stats.bLife > 0 ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && ( pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ) ) { ChangeSelectedMapSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ); } @@ -14546,6 +14547,7 @@ BOOLEAN MapCharacterHasAccessibleInventory( INT8 bCharNumber ) if( ( pSoldier->bAssignment == IN_TRANSIT ) || ( pSoldier->bAssignment == ASSIGNMENT_POW ) || + ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) || // Kaiden: Vehicle Inventory change - Commented the following line // ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) || // And added this instead: @@ -14734,7 +14736,7 @@ BOOLEAN CanChangeSleepStatusForSoldier( SOLDIERTYPE *pSoldier ) // if a vehicle, robot, in transit, or a POW if( ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) || AM_A_ROBOT( pSoldier ) || - ( pSoldier->bAssignment == IN_TRANSIT ) || ( pSoldier->bAssignment == ASSIGNMENT_POW ) ) + ( pSoldier->bAssignment == IN_TRANSIT ) || ( pSoldier->bAssignment == ASSIGNMENT_POW ) || ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) ) { // can't change the sleep status of such mercs return ( FALSE ); @@ -15287,7 +15289,7 @@ INT16 CalcLocationValueForChar( INT32 iCounter ) pSoldier = MercPtrs[ gCharactersList[ iCounter ].usSolID ]; // don't reveal location of POWs! - if( pSoldier->bAssignment != ASSIGNMENT_POW ) + if( pSoldier->bAssignment != ASSIGNMENT_POW && pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ) { sLocValue = SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ); // underground: add 1000 per sublevel @@ -15380,6 +15382,7 @@ BOOLEAN AnyMovableCharsInOrBetweenThisSector( INT16 sSectorX, INT16 sSectorY, IN ( pSoldier->bAssignment == ASSIGNMENT_POW ) || SPY_LOCATION( pSoldier->bAssignment ) || ( pSoldier->bAssignment == ASSIGNMENT_DEAD ) || + ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) || ( pSoldier->stats.bLife == 0 ) ) { continue; @@ -15977,6 +15980,11 @@ void GetMapscreenMercLocationString( SOLDIERTYPE *pSoldier, CHAR16 sString[] ) // POW - location unknown swprintf( sString, L"%s", pPOWStrings[ 1 ] ); } + else if ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + { + // mini event - unknown location, use the same string as POWs + swprintf( sString, L"%s", pPOWStrings[ 1 ] ); + } else if ( SPY_LOCATION( pSoldier->bAssignment ) ) { swprintf( pTempString, L"%s%s%s", @@ -16016,6 +16024,7 @@ void GetMapscreenMercDestinationString( SOLDIERTYPE *pSoldier, CHAR16 sString[] // if dead or POW - has no destination (no longer part of a group, for that matter) if( ( pSoldier->bAssignment == ASSIGNMENT_DEAD ) || ( pSoldier->bAssignment == ASSIGNMENT_POW ) || + ( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) || SPY_LOCATION( pSoldier->bAssignment ) || ( pSoldier->stats.bLife == 0 ) ) { diff --git a/Strategic/strategicmap.cpp b/Strategic/strategicmap.cpp index 6978b775..48c938fe 100644 --- a/Strategic/strategicmap.cpp +++ b/Strategic/strategicmap.cpp @@ -5229,7 +5229,7 @@ BOOLEAN CanGoToTacticalInSector( INT16 sX, INT16 sY, UINT8 ubZ ) { // ARM: now allows loading of sector with all mercs below OKLIFE as long as they're alive if( ( pSoldier->bActive && pSoldier->stats.bLife ) && !( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && - ( pSoldier->bAssignment != IN_TRANSIT ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && + ( pSoldier->bAssignment != IN_TRANSIT ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) && ( pSoldier->bAssignment != ASSIGNMENT_MINIEVENT ) && ( pSoldier->bAssignment != ASSIGNMENT_DEAD ) && !SoldierAboardAirborneHeli( pSoldier ) ) @@ -6369,7 +6369,7 @@ void HandleSlayDailyEvent( void ) } // valid soldier? - if ( (pSoldier->bActive == FALSE) || (pSoldier->stats.bLife == 0) || (pSoldier->bAssignment == IN_TRANSIT) || (pSoldier->bAssignment == ASSIGNMENT_POW) ) + if ( (pSoldier->bActive == FALSE) || (pSoldier->stats.bLife == 0) || (pSoldier->bAssignment == IN_TRANSIT) || (pSoldier->bAssignment == ASSIGNMENT_POW) || (pSoldier->bAssignment == ASSIGNMENT_MINIEVENT) ) { // no return; diff --git a/Tactical/Campaign.cpp b/Tactical/Campaign.cpp index abbbd912..273d7b4a 100644 --- a/Tactical/Campaign.cpp +++ b/Tactical/Campaign.cpp @@ -475,7 +475,7 @@ void ProfileUpdateStats( MERCPROFILESTRUCT *pProfile, UINT8 ubReason ) } -void ChangeStat( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UINT8 ubStat, INT16 sPtsChanged, UINT8 ubReason = 0 ) +void ChangeStat( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UINT8 ubStat, INT16 sPtsChanged, UINT8 ubReason ) { // this function changes the stat a given amount... INT16 *psStatGainPtr = NULL; diff --git a/Tactical/Campaign.h b/Tactical/Campaign.h index c47b144b..6d457765 100644 --- a/Tactical/Campaign.h +++ b/Tactical/Campaign.h @@ -66,6 +66,7 @@ void StatChange(SOLDIERTYPE *pSoldier, UINT8 ubStat, UINT16 usNumChances, UINT8 void ProfileStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumChances, UINT8 ubReason); void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumChances, UINT8 ubReason); +void ChangeStat(MERCPROFILESTRUCT* pProfile, SOLDIERTYPE* pSoldier, UINT8 ubStat, INT16 sPtsChanged, UINT8 ubReason = 0); void UpdateStats( SOLDIERTYPE *pSoldier, UINT8 ubReason = 0 ); void ProfileUpdateStats( MERCPROFILESTRUCT *pProfile, UINT8 ubReason = 0 ); void ProcessUpdateStats( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UINT8 ubReason = 0 ); diff --git a/Tactical/Dialogue Control.cpp b/Tactical/Dialogue Control.cpp index 110fbe49..2d2efb52 100644 --- a/Tactical/Dialogue Control.cpp +++ b/Tactical/Dialogue Control.cpp @@ -1512,6 +1512,9 @@ BOOLEAN DelayedTacticalCharacterDialogue( SOLDIERTYPE *pSoldier, UINT16 usQuoteN if( pSoldier->bAssignment == ASSIGNMENT_POW ) return( FALSE ); + if( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + return( FALSE ); + return( CharacterDialogue( pSoldier->ubProfile, usQuoteNum, pSoldier->iFaceIndex, DIALOGUE_TACTICAL_UI, TRUE, TRUE ) ); } @@ -1569,6 +1572,8 @@ BOOLEAN TacticalCharacterDialogueWithSpecialEventEx( SOLDIERTYPE *pSoldier, UINT return( FALSE ); } + if( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + return( FALSE ); } return( CharacterDialogueWithSpecialEventEx( pSoldier->ubProfile, usQuoteNum, pSoldier->iFaceIndex, DIALOGUE_TACTICAL_UI, TRUE, FALSE, uiFlag, uiData1, uiData2, uiData3 ) ); @@ -1617,6 +1622,9 @@ BOOLEAN TacticalCharacterDialogue( SOLDIERTYPE *pSoldier, UINT16 usQuoteNum ) return( FALSE ); } + if( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + return( FALSE ); + // OK, let's check if this is the exact one we just played, if so, skip. if ( pSoldier->ubProfile == gTacticalStatus.ubLastQuoteProfileNUm && usQuoteNum == gTacticalStatus.ubLastQuoteSaid ) @@ -1706,6 +1714,9 @@ BOOLEAN SnitchTacticalCharacterDialogue( SOLDIERTYPE *pSoldier, UINT16 usQuoteNu return( FALSE ); } + if( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + return( FALSE ); + // OK, let's check if this is the exact one we just played, if so, skip. //if ( pSoldier->ubProfile == gTacticalStatus.ubLastQuoteProfileNUm && // usQuoteNum == gTacticalStatus.ubLastQuoteSaid ) @@ -1760,6 +1771,9 @@ BOOLEAN AdditionalTacticalCharacterDialogue_CallsLua( SOLDIERTYPE *pSoldier, UIN if ( pSoldier->bAssignment == ASSIGNMENT_POW ) return( FALSE ); + if( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + return( FALSE ); + if ( AM_AN_EPC( pSoldier ) && !( gMercProfiles[pSoldier->ubProfile].ubMiscFlags & PROFILE_MISC_FLAG_FORCENPCQUOTE ) ) return( FALSE ); @@ -1788,7 +1802,7 @@ void AdditionalTacticalCharacterDialogue_AllInSector(INT16 aSectorX, INT16 aSect if ( pSoldier->stats.bLife >= OKLIFE && pSoldier->bActive && pSoldier->ubProfile != ausIgnoreProfile && pSoldier->sSectorX == aSectorX && pSoldier->sSectorY == aSectorY && pSoldier->bSectorZ == aSectorZ && - pSoldier->bAssignment != ASSIGNMENT_POW && pSoldier->bAssignment != IN_TRANSIT && + pSoldier->bAssignment != ASSIGNMENT_POW && pSoldier->bAssignment != IN_TRANSIT && pSoldier->bAssignment != ASSIGNMENT_MINIEVENT && (aAroundGridno == NOWHERE || PythSpacesAway( pSoldier->sGridNo, aAroundGridno ) <= aRadius ) && !pSoldier->flags.fBetweenSectors ) { @@ -2088,6 +2102,9 @@ BOOLEAN ExecuteCharacterDialogue( UINT8 ubCharacterNum, UINT16 usQuoteNum, INT32 return( FALSE ); } + if( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + return( FALSE ); + // sleeping guys don't talk.. go to standby to talk if( pSoldier->flags.fMercAsleep == TRUE ) { diff --git a/Tactical/DynamicDialogue.cpp b/Tactical/DynamicDialogue.cpp index 8b81ffc7..68b69b1d 100644 --- a/Tactical/DynamicDialogue.cpp +++ b/Tactical/DynamicDialogue.cpp @@ -957,6 +957,9 @@ BOOLEAN DynamicOpinionTacticalCharacterDialogue( DynamicOpinionSpeechEvent& aEve if ( pSoldier->bAssignment == ASSIGNMENT_POW ) return(FALSE); + if( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + return( FALSE ); + CHAR16 gzQuoteStr[500]; // remove old box, in case that still exists @@ -2017,7 +2020,7 @@ UINT8 GetFittingInterjectorProfile( UINT8 usEvent, UINT8 usProfileVictim, UINT8 for ( pTeamSoldier = MercPtrs[bMercID]; bMercID <= bLastTeamID; ++bMercID, pTeamSoldier++ ) { // only people that are here - if ( !pTeamSoldier->bActive || pTeamSoldier->bAssignment == IN_TRANSIT || pTeamSoldier->bAssignment == ASSIGNMENT_DEAD || pTeamSoldier->bAssignment == ASSIGNMENT_POW ) + if ( !pTeamSoldier->bActive || pTeamSoldier->bAssignment == IN_TRANSIT || pTeamSoldier->bAssignment == ASSIGNMENT_DEAD || pTeamSoldier->bAssignment == ASSIGNMENT_POW || pTeamSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) continue; // if fSameSector is TRUE then the teammate must be in the same sector diff --git a/Tactical/Food.cpp b/Tactical/Food.cpp index 2c7650f0..2d28ba20 100644 --- a/Tactical/Food.cpp +++ b/Tactical/Food.cpp @@ -595,8 +595,8 @@ void HourlyFoodAutoDigestion( SOLDIERTYPE *pSoldier ) // if we're a prisoner, we can't feed ourself, and the player can't do that either. Instead the army provides food (not much and of bad quality) if (pSoldier->bAssignment == ASSIGNMENT_POW) { - INT16 powwater = gGameExternalOptions.usFoodDigestionHourlyBaseDrink * gGameExternalOptions.sFoodDigestionAssignment * FOOD_POW_MULTIPLICATOR; - INT16 powfoodadd = powwater * gGameExternalOptions.usFoodDigestionHourlyBaseFood / max(1, gGameExternalOptions.usFoodDigestionHourlyBaseDrink); + const INT16 powwater = gGameExternalOptions.usFoodDigestionHourlyBaseDrink * gGameExternalOptions.sFoodDigestionAssignment * FOOD_POW_MULTIPLICATOR; + const INT16 powfoodadd = powwater * gGameExternalOptions.usFoodDigestionHourlyBaseFood / max(1, gGameExternalOptions.usFoodDigestionHourlyBaseDrink); // if we're thirsty or hungry, and this is nutritious, consume it if ( pSoldier->bDrinkLevel < FoodMoraleMods[FOOD_VERY_LOW].bThreshold ) @@ -605,6 +605,18 @@ void HourlyFoodAutoDigestion( SOLDIERTYPE *pSoldier ) if ( pSoldier->bFoodLevel < FoodMoraleMods[FOOD_VERY_LOW].bThreshold ) AddFoodpoints(pSoldier->bDrinkLevel, powfoodadd); } + // while on a minievent, assume that we can feed ourselves.. somehow + else if (pSoldier->bAssignment == ASSIGNMENT_MINIEVENT) + { + const INT16 water = gGameExternalOptions.usFoodDigestionHourlyBaseDrink * gGameExternalOptions.sFoodDigestionAssignment; + const INT16 foodadd = water * gGameExternalOptions.usFoodDigestionHourlyBaseFood / max(1, gGameExternalOptions.usFoodDigestionHourlyBaseDrink); + + if ( pSoldier->bDrinkLevel < FoodMoraleMods[FOOD_NORMAL].bThreshold ) + AddFoodpoints(pSoldier->bDrinkLevel, water); + + if ( pSoldier->bFoodLevel < FoodMoraleMods[FOOD_NORMAL].bThreshold ) + AddFoodpoints(pSoldier->bDrinkLevel, foodadd); + } else { // no eating if not able to! diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index 13a124fa..17f5d35f 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -6305,7 +6305,7 @@ void SOLDIERTYPE::EVENT_SoldierGotHit( UINT16 usWeaponIndex, INT16 sDamage, INT1 // If anything other than on a squad or guard, make them guard.... if ( this->bTeam == gbPlayerNum ) { - if ( this->bAssignment >= ON_DUTY && this->bAssignment != ASSIGNMENT_POW ) + if ( this->bAssignment >= ON_DUTY && this->bAssignment != ASSIGNMENT_POW && this->bAssignment != ASSIGNMENT_MINIEVENT ) { if ( this->flags.fMercAsleep ) { diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index 2437b3c7..37d93327 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -1575,8 +1575,8 @@ public: // Flugente: Decrease this filler by 1 for each new UINT8 / BOOLEAN variable, so we can maintain savegame compatibility!! // Note that we also have to account for padding, so you might need to substract more than just the size of the new variables - UINT8 ubFiller[12]; - UINT8 ubFiller1; + UINT8 ubFiller[10]; + UINT16 ubHoursRemainingOnMiniEvent; // Flugente: modifiers to fire modes UINT8 usGLDelayMode; // if > 0, delay GL grenade explosions diff --git a/Tactical/Squads.cpp b/Tactical/Squads.cpp index 9bf4b2a7..dc6176c9 100644 --- a/Tactical/Squads.cpp +++ b/Tactical/Squads.cpp @@ -1384,6 +1384,11 @@ BOOLEAN IsSquadInSector( SOLDIERTYPE *pSoldier, UINT8 ubSquad ) return( FALSE ); } + if( pSoldier->bAssignment == ASSIGNMENT_MINIEVENT ) + { + return( FALSE ); + } + if( SquadIsEmpty( ubSquad ) == TRUE ) { return( TRUE ); diff --git a/Tactical/Strategic Exit GUI.cpp b/Tactical/Strategic Exit GUI.cpp index 9f731787..b56531d0 100644 --- a/Tactical/Strategic Exit GUI.cpp +++ b/Tactical/Strategic Exit GUI.cpp @@ -238,7 +238,7 @@ BOOLEAN InternalInitSectorExitMenu( UINT8 ubDirection, INT32 sAdditionalData )// pSoldier->stats.bLife >= OKLIFE && ( pSoldier->bAssignment != MercPtrs[ gusSelectedSoldier ]->bAssignment || ( pSoldier->bAssignment == VEHICLE && pSoldier->iVehicleId != MercPtrs[ gusSelectedSoldier ]->iVehicleId ) ) && - pSoldier->bAssignment != ASSIGNMENT_POW && pSoldier->bAssignment != IN_TRANSIT && pSoldier->bAssignment != ASSIGNMENT_DEAD + pSoldier->bAssignment != ASSIGNMENT_POW && pSoldier->bAssignment != IN_TRANSIT && pSoldier->bAssignment != ASSIGNMENT_DEAD && pSoldier->bAssignment != ASSIGNMENT_MINIEVENT && !(pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) ) { //KM: We need to determine if there are more than one squad (meaning other concious mercs in a different squad or assignment) // These conditions were done to the best of my knowledge, so if there are other situations that require modification, diff --git a/Tactical/opplist.cpp b/Tactical/opplist.cpp index 85403e4c..1b67b607 100644 --- a/Tactical/opplist.cpp +++ b/Tactical/opplist.cpp @@ -6013,7 +6013,7 @@ void ProcessNoise(UINT8 ubNoiseMaker, INT32 sGridNo, INT8 bLevel, UINT8 ubTerrTy continue; // skip } - if ( bTeam == gbPlayerNum && pSoldier->bAssignment == ASSIGNMENT_POW ) + if ( bTeam == gbPlayerNum && (pSoldier->bAssignment == ASSIGNMENT_POW || pSoldier->bAssignment == ASSIGNMENT_MINIEVENT) ) { // POWs should not be processed for noise continue; diff --git a/TileEngine/Tactical Placement GUI.cpp b/TileEngine/Tactical Placement GUI.cpp index ea459a83..705b3701 100644 --- a/TileEngine/Tactical Placement GUI.cpp +++ b/TileEngine/Tactical Placement GUI.cpp @@ -337,6 +337,7 @@ void InitTacticalPlacementGUI() CurrentBattleSectorIs( MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY, MercPtrs[i]->bSectorZ ) && !( MercPtrs[ i ]->flags.uiStatusFlags & ( SOLDIER_VEHICLE ) ) && // ATE Ignore vehicles MercPtrs[ i ]->bAssignment != ASSIGNMENT_POW && + MercPtrs[ i ]->bAssignment != ASSIGNMENT_MINIEVENT && !( MercPtrs[i]->usSoldierFlagMask2 & SOLDIER_CONCEALINSERTION ) && MercPtrs[ i ]->bAssignment != IN_TRANSIT ) { @@ -353,6 +354,7 @@ void InitTacticalPlacementGUI() if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->stats.bLife && !MercPtrs[ i ]->flags.fBetweenSectors && CurrentBattleSectorIs( MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY, MercPtrs[i]->bSectorZ ) && MercPtrs[ i ]->bAssignment != ASSIGNMENT_POW && + MercPtrs[ i ]->bAssignment != ASSIGNMENT_MINIEVENT && !( MercPtrs[i]->usSoldierFlagMask2 & SOLDIER_CONCEALINSERTION ) && MercPtrs[ i ]->bAssignment != IN_TRANSIT && !( MercPtrs[ i ]->flags.uiStatusFlags & ( SOLDIER_VEHICLE ) ) ) // ATE Ignore vehicles diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index 88f09abe..550ff241 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -2480,6 +2480,7 @@ STR16 pAssignmentStrings[] = L"掩埋尸体", //L"Burial", L"管理", //L"Admin", L"探索", //L"Explore" TODO.Translate + L"Event"// rftr: merc is on a mini event // TODO: translate }; @@ -4638,6 +4639,7 @@ STR16 pTransactionText[] = L"修理SAM基地",//L"SAM site repair", // Flugente: SAM repair L"培训工人",//L"Trained workers", // Flugente: train workers L"在%s区域训练民兵", //L"Drill militia in %s", Flugente: drill militia + L"Mini event", // rftr: mini events // TODO: translate }; STR16 pTransactionAlternateText[] = diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index 1b934412..b08f0540 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -2479,6 +2479,7 @@ STR16 pAssignmentStrings[] = L"Burial", L"Admin", // TODO.Translate L"Explore", // TODO.Translate + L"Event"// rftr: merc is on a mini event // TODO: translate }; @@ -4628,6 +4629,7 @@ STR16 pTransactionText[] = L"SAM site repair", // Flugente: SAM repair // TODO.Translate L"Trained workers", // Flugente: train workers L"Drill militia in %s", // Flugente: drill militia // TODO.Translate + L"Mini event", // rftr: mini events // TODO: translate }; STR16 pTransactionAlternateText[] = diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index 62d605d4..70919308 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -2480,6 +2480,7 @@ STR16 pAssignmentStrings[] = L"Burial", L"Admin", L"Explore", + L"Event"// rftr: merc is on a mini event }; @@ -4638,6 +4639,7 @@ STR16 pTransactionText[] = L"SAM site repair", // Flugente: SAM repair L"Trained workers", // Flugente: train workers L"Drill militia in %s", // Flugente: drill militia + L"Mini event", // rftr: mini events }; STR16 pTransactionAlternateText[] = diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index 9a04f4ae..92eecfb5 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -2488,6 +2488,7 @@ STR16 pAssignmentStrings[] = L"Burial", L"Admin", // TODO.Translate L"Explore", // TODO.Translate + L"Event"// rftr: merc is on a mini event // TODO: translate }; @@ -4632,6 +4633,7 @@ STR16 pTransactionText[] = L"SAM site repair", // Flugente: SAM repair // TODO.Translate L"Trained workers", // Flugente: train workers L"Drill militia in %s", // Flugente: drill militia // TODO.Translate + L"Mini event", // rftr: mini events // TODO: translate }; STR16 pTransactionAlternateText[] = diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index a32a3748..103ad090 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -2519,6 +2519,7 @@ STR16 pAssignmentStrings[] = L"Burial", L"Admin", // TODO.Translate L"Explore", // TODO.Translate + L"Event"// rftr: merc is on a mini event // TODO: translate }; STR16 pMilitiaString[] = @@ -4630,6 +4631,7 @@ STR16 pTransactionText[] = L"SAM reparieren", // Flugente: SAM repair L"Arbeiter trainiert", // Flugente: train workers L"Miliz in %s ausbilden", // Flugente: drill militia + L"Mini event", // rftr: mini events // TODO: translate }; STR16 pTransactionAlternateText[] = diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index a6ea87b6..dc2b9e14 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -2474,6 +2474,7 @@ STR16 pAssignmentStrings[] = L"Burial", L"Admin", // TODO.Translate L"Explore", // TODO.Translate + L"Event"// rftr: merc is on a mini event // TODO: translate }; @@ -4622,6 +4623,7 @@ STR16 pTransactionText[] = L"SAM site repair", // Flugente: SAM repair // TODO.Translate L"Trained workers", // Flugente: train workers L"Drill militia in %s", // Flugente: drill militia // TODO.Translate + L"Mini event", // rftr: mini events // TODO: translate }; STR16 pTransactionAlternateText[] = diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index 6c9f55e0..189c1122 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -2486,6 +2486,7 @@ STR16 pAssignmentStrings[] = L"Burial", L"Admin", // TODO.Translate L"Explore", // TODO.Translate + L"Event"// rftr: merc is on a mini event // TODO: translate }; @@ -4633,6 +4634,7 @@ STR16 pTransactionText[] = L"SAM site repair", // Flugente: SAM repair // TODO.Translate L"Trained workers", // Flugente: train workers L"Drill militia in %s", // Flugente: drill militia // TODO.Translate + L"Mini event", // rftr: mini events // TODO: translate }; STR16 pTransactionAlternateText[] = diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index 8a4f404e..1a455181 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -2480,6 +2480,7 @@ STR16 pAssignmentStrings[] = L"Burial", L"Admin", // TODO.Translate L"Explore", // TODO.Translate + L"Event"// rftr: merc is on a mini event // TODO: translate }; @@ -4638,6 +4639,7 @@ STR16 pTransactionText[] = L"Стоимость ремонта базы ПВО", // Flugente: SAM repair L"Trained workers", // Flugente: train workers // TODO.Translate L"Drill militia in %s", // Flugente: drill militia // TODO.Translate + L"Mini event", // rftr: mini events // TODO: translate }; STR16 pTransactionAlternateText[] =