From 119c6fe6bbac3be63520879b940b9fc6aee1b62d Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Fri, 26 May 2023 21:28:00 -0700 Subject: [PATCH 01/50] Fix FindBestNearbyCover() and delete global var (#156) --- TacticalAI/AIInternals.h | 3 +-- TacticalAI/AIMain.cpp | 1 - TacticalAI/AIUtils.cpp | 40 +++++++++++++++++++----------------- TacticalAI/Attacks.cpp | 7 ++++--- TacticalAI/FindLocations.cpp | 2 +- TacticalAI/ai.h | 6 +++--- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h index cc7afa94..1e72dedc 100644 --- a/TacticalAI/AIInternals.h +++ b/TacticalAI/AIInternals.h @@ -174,7 +174,6 @@ extern THREATTYPE Threat[MAXMERCS]; extern int ThreatPercent[10]; extern UINT8 SkipCoverCheck; extern INT8 GameOption[MAXGAMEOPTIONS]; -extern UINT32 guiThreatCnt; typedef enum { @@ -322,4 +321,4 @@ BOOLEAN GetBestAoEGridNo(SOLDIERTYPE *pSoldier, INT32* pGridNo, INT16 aRadius, U BOOLEAN GetFarthestOpponent(SOLDIERTYPE *pSoldier, UINT8* puID, INT16 sRange); // are there more allies than friends in adjacent sectors? -BOOLEAN MoreFriendsThanEnemiesinNearbysectors(UINT8 ausTeam, INT16 aX, INT16 aY, INT8 aZ); \ No newline at end of file +BOOLEAN MoreFriendsThanEnemiesinNearbysectors(UINT8 ausTeam, INT16 aX, INT16 aY, INT8 aZ); diff --git a/TacticalAI/AIMain.cpp b/TacticalAI/AIMain.cpp index b195326e..f60ab755 100644 --- a/TacticalAI/AIMain.cpp +++ b/TacticalAI/AIMain.cpp @@ -1430,7 +1430,6 @@ void ActionDone(SOLDIERTYPE *pSoldier) UINT8 SkipCoverCheck = FALSE; THREATTYPE Threat[MAXMERCS]; -UINT32 guiThreatCnt = 0; // threat percentage is based on the certainty of opponent knowledge: // opplist value: -4 -3 -2 -1 SEEN 1 2 3 4 5 diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index a511bc89..2f947a9f 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -5938,7 +5938,7 @@ INT32 RandomizeOpponentLocation(INT32 sSpot, SOLDIERTYPE *pOpponent, INT16 sMaxD } // first call PrepareThreatlist to make threat list -UINT8 ClosestKnownThreatID(SOLDIERTYPE *pSoldier) +UINT8 ClosestKnownThreatID(SOLDIERTYPE *pSoldier, UINT32 uiThreatCnt) { CHECKF(pSoldier); @@ -5948,7 +5948,7 @@ UINT8 ClosestKnownThreatID(SOLDIERTYPE *pSoldier) UINT8 ubClosestOpponentID = NOBODY; // use global defined threat list - for (uiLoop = 0; uiLoop < guiThreatCnt; uiLoop++) + for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++) { // if for some reason we have incorrect location if (TileIsOutOfBounds(Threat[uiLoop].sGridNo)) @@ -5968,7 +5968,7 @@ UINT8 ClosestKnownThreatID(SOLDIERTYPE *pSoldier) } // first call PrepareThreatlist to make threat list -UINT8 ClosestSeenThreatID(SOLDIERTYPE *pSoldier, UINT8 ubMax) +UINT8 ClosestSeenThreatID(SOLDIERTYPE *pSoldier, UINT32 uiThreatCnt, UINT8 ubMax) { CHECKF(pSoldier); @@ -5978,7 +5978,7 @@ UINT8 ClosestSeenThreatID(SOLDIERTYPE *pSoldier, UINT8 ubMax) UINT8 ubClosestOpponentID = NOBODY; // use global defined threat list - for (uiLoop = 0; uiLoop < guiThreatCnt; uiLoop++) + for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++) { // if for some reason we have incorrect location if (TileIsOutOfBounds(Threat[uiLoop].sGridNo)) @@ -6001,7 +6001,7 @@ UINT8 ClosestSeenThreatID(SOLDIERTYPE *pSoldier, UINT8 ubMax) return(ubClosestOpponentID); } -void PrepareThreatlist(SOLDIERTYPE *pSoldier) +UINT32 PrepareThreatlist(SOLDIERTYPE *pSoldier) { SOLDIERTYPE *pOpponent; INT32 iThreatRange, iClosestThreatRange = 1500; @@ -6014,10 +6014,10 @@ void PrepareThreatlist(SOLDIERTYPE *pSoldier) INT32 iThreatCertainty; INT32 iMaxThreatRange = MAX_THREAT_RANGE + AI_PATHCOST_RADIUS; - guiThreatCnt = 0; + UINT32 uiThreatCnt = 0; if (!pSoldier) - return; + return 0; // look through all opponents for those we know of for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) @@ -6065,35 +6065,37 @@ void PrepareThreatlist(SOLDIERTYPE *pSoldier) } // remember this opponent as a current threat, but DON'T REDUCE FOR COVER! - Threat[guiThreatCnt].iValue = CalcManThreatValue(pOpponent, pSoldier->sGridNo, FALSE, pSoldier); + Threat[uiThreatCnt].iValue = CalcManThreatValue(pOpponent, pSoldier->sGridNo, FALSE, pSoldier); // if the opponent is no threat at all for some reason - if (Threat[guiThreatCnt].iValue == -999) + if (Threat[uiThreatCnt].iValue == -999) { continue; // check next opponent } - Threat[guiThreatCnt].pOpponent = pOpponent; - Threat[guiThreatCnt].sGridNo = sThreatLoc; - Threat[guiThreatCnt].iCertainty = iThreatCertainty; - Threat[guiThreatCnt].iOrigRange = iThreatRange; + Threat[uiThreatCnt].pOpponent = pOpponent; + Threat[uiThreatCnt].sGridNo = sThreatLoc; + Threat[uiThreatCnt].iCertainty = iThreatCertainty; + Threat[uiThreatCnt].iOrigRange = iThreatRange; // calculate how many APs he will have at the start of the next turn - Threat[guiThreatCnt].iAPs = pOpponent->CalcActionPoints(); + Threat[uiThreatCnt].iAPs = pOpponent->CalcActionPoints(); // sevenfm: more information - Threat[guiThreatCnt].bLevel = bThreatLevel; - Threat[guiThreatCnt].bKnowledge = bKnowledge; - Threat[guiThreatCnt].bPersonalKnowledge = bPersonalKnowledge; - Threat[guiThreatCnt].bPublicKnowledge = bPublicKnowledge; + Threat[uiThreatCnt].bLevel = bThreatLevel; + Threat[uiThreatCnt].bKnowledge = bKnowledge; + Threat[uiThreatCnt].bPersonalKnowledge = bPersonalKnowledge; + Threat[uiThreatCnt].bPublicKnowledge = bPublicKnowledge; if (iThreatRange < iClosestThreatRange) { iClosestThreatRange = iThreatRange; } - guiThreatCnt++; + uiThreatCnt++; } + + return uiThreatCnt; } UINT8 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 sDistance) diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index 2b77b69f..d84d2668 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -3755,6 +3755,7 @@ void CheckTossSelfSmoke(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) { INT16 ubMinAPcost; INT8 bGrenadeIn = NO_SLOT; + UINT32 uiThreatCnt = 0; // initialize pBestThrow->ubPossible = FALSE; @@ -3771,7 +3772,7 @@ void CheckTossSelfSmoke(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) bGrenadeIn = FindThrowableGrenade(pSoldier, EXPLOSV_SMOKE); // prepare threat list for ClosestSeenThreatID(), ClosestKnownThreatID() - PrepareThreatlist(pSoldier); + uiThreatCnt = PrepareThreatlist(pSoldier); if (bGrenadeIn != NO_SLOT) { @@ -3812,7 +3813,7 @@ void CheckTossSelfSmoke(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) if (TileIsOutOfBounds(sTargetSpot)) { - ubClosestThreatID = ClosestSeenThreatID(pSoldier, SEEN_LAST_TURN); + ubClosestThreatID = ClosestSeenThreatID(pSoldier, uiThreatCnt, SEEN_LAST_TURN); if (ubClosestThreatID != NOBODY && MercPtrs[ubClosestThreatID] && @@ -3826,7 +3827,7 @@ void CheckTossSelfSmoke(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow) if (TileIsOutOfBounds(sTargetSpot)) { - ubClosestThreatID = ClosestKnownThreatID(pSoldier); + ubClosestThreatID = ClosestKnownThreatID(pSoldier, uiThreatCnt); if (ubClosestThreatID != NOBODY && MercPtrs[ubClosestThreatID] && diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index 826c45c6..37fcd984 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -753,7 +753,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB iMyThreatValue = CalcManThreatValue(pSoldier, NOWHERE, FALSE, pSoldier); // prepare threat list from known enemies - PrepareThreatlist(pSoldier); + uiThreatCnt = PrepareThreatlist(pSoldier); // if no known opponents were found to threaten us, can't worry about cover if (!uiThreatCnt) diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index c30be8cb..c97d1936 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -314,9 +314,9 @@ BOOLEAN AICheckFriendsNoContact( SOLDIERTYPE *pSoldier ); BOOLEAN AICheckIsFlanking( SOLDIERTYPE *pSoldier ); INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier); -void PrepareThreatlist(SOLDIERTYPE *pSoldier); -UINT8 ClosestSeenThreatID(SOLDIERTYPE *pSoldier, UINT8 ubMax = SEEN_CURRENTLY); // first call PrepareThreatlist to make threat list -UINT8 ClosestKnownThreatID(SOLDIERTYPE *pSoldier); // first call PrepareThreatlist to make threat list +UINT32 PrepareThreatlist(SOLDIERTYPE *pSoldier); +UINT8 ClosestSeenThreatID(SOLDIERTYPE *pSoldier, UINT32 uiThreatCnt, UINT8 ubMax = SEEN_CURRENTLY); // first call PrepareThreatlist to make threat list +UINT8 ClosestKnownThreatID(SOLDIERTYPE *pSoldier, UINT32 uiThreatCnt); // first call PrepareThreatlist to make threat list BOOLEAN ProneSightCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot, BOOLEAN fUnlimited); BOOLEAN SightCoverAtSpot(SOLDIERTYPE *pSoldier, INT32 sSpot, BOOLEAN fUnlimited); From 9fc9d1c6e87d01dce7d3289b9be264bab4800183 Mon Sep 17 00:00:00 2001 From: sun-alf Date: Sat, 27 May 2023 02:04:21 +0300 Subject: [PATCH 02/50] [Fix] Incorrect displaying info and tooltips in Advances Properties Tab * Tooltips were shifted by one position due to wrong handling "Accuracy modifier". * DrawAdvancedValues(): if ( iFloatModifier[0] > 1.0 || ( fComparisonMode && iComparedFloatModifier[0] > 1.0 ) ) it checks number of laser range tiles (iFloatModifier[0] > 1.0, "meters" / CELL_X_SIZE) right before actual drawing a line with values. So if someone put 1..9 into XML () it will not print that value. But number of "meters" was checked (like x > 0) everywhere prior to this code. so all the code before decided to draw line with icon for laser range, but the guilty code line didn't I suppose not to draw laser info at all if a modder screwed up putting a 1..9 value so all values 0..9 basically mean 0 tiles, i.e. there is no laser ability. --- Tactical/DisplayCover.cpp | 4 +- Tactical/Interface Enhanced.cpp | 68 ++++++++++++++++++--------------- Tactical/Items.cpp | 23 +++++------ 3 files changed, 49 insertions(+), 46 deletions(-) diff --git a/Tactical/DisplayCover.cpp b/Tactical/DisplayCover.cpp index 28bb8030..3d35841d 100644 --- a/Tactical/DisplayCover.cpp +++ b/Tactical/DisplayCover.cpp @@ -1823,8 +1823,8 @@ void CalculateWeapondata() pObjUsed = pObjPlatform; } - gunrange = GunRange( pObjUsed, pSoldier ) / 10; - laserrange = GetBestLaserRange( pObjPlatform ) / 10; + gunrange = GunRange( pObjUsed, pSoldier ) / CELL_X_SIZE; + laserrange = GetBestLaserRange( pObjPlatform ) / CELL_X_SIZE; if ( Item[pObjUsed->usItem].usItemClass & IC_LAUNCHER ) { diff --git a/Tactical/Interface Enhanced.cpp b/Tactical/Interface Enhanced.cpp index 11def4c4..3247e2f1 100644 --- a/Tactical/Interface Enhanced.cpp +++ b/Tactical/Interface Enhanced.cpp @@ -1621,8 +1621,8 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr /////////////////// PROJECTION FACTOR / NCTH BEST LASER RANGE // with the reworked NCTH code and the laser performance factor we will display BestLaserRange instead of ProjectionFactor but we still need the mouse region - if (UsingNewCTHSystem() && ( GetProjectionFactor( gpItemDescObject ) > 1.0 || GetBestLaserRange( gpItemDescObject ) > 0 - && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) ) + if (UsingNewCTHSystem() && ( GetProjectionFactor( gpItemDescObject ) > 1.0 || (GetBestLaserRange(gpItemDescObject) / CELL_X_SIZE > 0 + && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0)) ) ) { ubRegionOffset = 6; MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); @@ -1637,7 +1637,7 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr } /////////////////// OCTH BEST LASER RANGE - if (!UsingNewCTHSystem() && (Item[gpItemDescObject->usItem].bestlaserrange > 0 || GetAverageBestLaserRange( gpItemDescObject ) > 0 ) ) + if ( UsingNewCTHSystem() == false && (GetAverageBestLaserRange(gpItemDescObject) / CELL_X_SIZE) > 0 ) { ubRegionOffset = 7; MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); @@ -2874,10 +2874,10 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr ///////////////////// ACCURACY MODIFIER if (GetAccuracyModifier( gpItemDescObject ) != 0 ) { - if (cnt >= sFirstLine && cnt < sLastLine) - { - if( UsingNewCTHSystem() == true ) + if (UsingNewCTHSystem() == true) { + if (cnt >= sFirstLine && cnt < sLastLine) + { if (Item[ gpItemDescObject->usItem ].usItemClass & (IC_WEAPON|IC_PUNCH)) { swprintf( pStr, L"%s%s", szUDBAdvStatsTooltipText[ 0 ], szUDBAdvStatsExplanationsTooltipTextForWeapons[ 0 ]); @@ -3255,9 +3255,9 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr ///////////////////// PROJECTION FACTOR / BEST LASER RANGE // with the reworked NCTH code and the laser performance factor we will display BestLaserRange instead of ProjectionFactor - if ( ( UsingNewCTHSystem() && GetBestLaserRange( gpItemDescObject ) > 0 + if ( ( UsingNewCTHSystem() && (GetBestLaserRange(gpItemDescObject) / CELL_X_SIZE) > 0 && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) - || ( !UsingNewCTHSystem() && GetBestLaserRange( gpItemDescObject ) > 0 ) ) + || ( !UsingNewCTHSystem() && (GetBestLaserRange(gpItemDescObject) / CELL_X_SIZE) > 0 ) ) { if (cnt >= sFirstLine && cnt < sLastLine) { @@ -4434,10 +4434,12 @@ void DrawWeaponStats( OBJECTTYPE * gpItemDescObject ) //////////////////// PROJECTION FACTOR / NCTH BEST LASER RANGE // with the reworked NCTH code and the laser performance factor we will display BestLaserRange instead of ProjectionFactor but we use the same icon - if ( (UsingNewCTHSystem() && ( GetProjectionFactor( gpItemDescObject ) > 1.0 || ( GetBestLaserRange( gpItemDescObject ) > 0 + INT16 itemLaserRangeTiles = GetBestLaserRange(gpItemDescObject) / CELL_X_SIZE; + INT16 comparedLaserRangeTiles = fComparisonMode ? GetBestLaserRange(gpComparedItemDescObject) / CELL_X_SIZE : 0; + if ( (UsingNewCTHSystem() && ( GetProjectionFactor( gpItemDescObject ) > 1.0 || ( itemLaserRangeTiles > 0 && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) ) ) || ( fComparisonMode && UsingNewCTHSystem() && ( (GetProjectionFactor( gpItemDescObject ) > 1.0 || GetProjectionFactor( gpComparedItemDescObject ) > 1.0) || - ( GetBestLaserRange( gpComparedItemDescObject ) > 0 + ( comparedLaserRangeTiles > 0 && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) ) ) ) { ubNumLine = 6; @@ -4455,8 +4457,8 @@ void DrawWeaponStats( OBJECTTYPE * gpItemDescObject ) } //////////////////// OCTH BEST LASER RANGE - if ( !UsingNewCTHSystem() && ( GetBestLaserRange( gpItemDescObject ) || - ( fComparisonMode && GetBestLaserRange( gpComparedItemDescObject ) ) ) ) + if ( UsingNewCTHSystem() == false && + (itemLaserRangeTiles != 0 || (fComparisonMode && comparedLaserRangeTiles != 0)) ) { ubNumLine = 7; BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 14, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); @@ -5347,11 +5349,13 @@ void DrawAdvancedStats( OBJECTTYPE * gpItemDescObject ) ///////////////////// PROJECTION FACTOR / BEST LASER RANGE // with the reworked NCTH code and the laser performance factor we will display BestLaserRange instead of ProjectionFactor but we use the same icon - if ( ( UsingNewCTHSystem() && ( GetProjectionFactor( gpItemDescObject ) > 1.0 || ( GetBestLaserRange( gpItemDescObject ) > 0 + INT16 itemLaserRangeTiles = GetBestLaserRange(gpItemDescObject) / CELL_X_SIZE; + INT16 comparedLaserRangeTiles = fComparisonMode ? GetBestLaserRange(gpComparedItemDescObject) / CELL_X_SIZE : 0; + if ( ( UsingNewCTHSystem() && ( GetProjectionFactor( gpItemDescObject ) > 1.0 || ( itemLaserRangeTiles > 0 && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) ) || - ( fComparisonMode && ( GetProjectionFactor( gpComparedItemDescObject ) > 1.0 || ( GetBestLaserRange( gpComparedItemDescObject ) > 0 + ( fComparisonMode && ( GetProjectionFactor( gpComparedItemDescObject ) > 1.0 || ( comparedLaserRangeTiles > 0 && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) ) ) ) || - ( !UsingNewCTHSystem() && ( GetBestLaserRange( gpItemDescObject ) > 0 || ( fComparisonMode && GetBestLaserRange( gpComparedItemDescObject ) > 0 ) ) ) ) + ( !UsingNewCTHSystem() && ( itemLaserRangeTiles > 0 || ( fComparisonMode && comparedLaserRangeTiles > 0 ) ) ) ) { if (cnt >= sFirstLine && cnt < sLastLine) { @@ -7532,9 +7536,11 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) ///////////////// (LASER) PROJECTION FACTOR // with the reworked NCTH code and the laser performance factor we will display BestLaserRange instead of ProjectionFactor + INT16 itemLaserRangeTiles = GetBestLaserRange(gpItemDescObject) / CELL_X_SIZE; + INT16 comparedLaserRangeTiles = fComparisonMode ? GetBestLaserRange(gpComparedItemDescObject) / CELL_X_SIZE : 0; if (UsingNewCTHSystem() == true && ( (Item[gpItemDescObject->usItem].projectionfactor > 1.0 || GetProjectionFactor( gpItemDescObject ) > 1.0) || - ( GetBestLaserRange( gpItemDescObject ) > 0 + ( itemLaserRangeTiles > 0 && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) ) ) { // Set line to draw into @@ -7545,13 +7551,13 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) FLOAT iFinalProjectionValue = 0; BOOLEAN bNewCode = FALSE; - if ( GetBestLaserRange( gpItemDescObject ) > 0 - && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) + if ( itemLaserRangeTiles > 0 && + (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) { // Get base laser range iProjectionValue = __max(0, Item[ gpItemDescObject->usItem ].bestlaserrange * gItemSettings.fBestLaserRangeModifier / CELL_X_SIZE); // Get best laser range - iProjectionModifier = ((FLOAT)GetBestLaserRange( gpItemDescObject ) / CELL_X_SIZE); + iProjectionModifier = (FLOAT)itemLaserRangeTiles; // Get final laser range iFinalProjectionValue = __max( iProjectionValue, iProjectionModifier ); bNewCode = TRUE; @@ -7609,7 +7615,7 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) // Get base laser range iComparedProjectionValue = __max(0, Item[ gpComparedItemDescObject->usItem ].bestlaserrange * gItemSettings.fBestLaserRangeModifier / CELL_X_SIZE); // Get best laser range - iComparedProjectionModifier = ((FLOAT)GetBestLaserRange( gpComparedItemDescObject ) / CELL_X_SIZE); + iComparedProjectionModifier = (FLOAT)comparedLaserRangeTiles; // Get final laser range iComparedFinalProjectionValue = __max( iComparedProjectionValue, iComparedProjectionModifier ); } @@ -7632,7 +7638,7 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) } else if( fComparisonMode && UsingNewCTHSystem() == true && ( (Item[gpComparedItemDescObject->usItem].projectionfactor > 1.0 || GetProjectionFactor( gpComparedItemDescObject ) > 1.0) || - ( GetBestLaserRange( gpItemDescObject ) > 0 + ( itemLaserRangeTiles > 0 && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) ) ) { ubNumLine = 6; @@ -7642,13 +7648,13 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) FLOAT iFinalProjectionValue = 0; BOOLEAN bNewCode = FALSE; - if ( GetBestLaserRange( gpComparedItemDescObject ) > 0 + if ( comparedLaserRangeTiles > 0 && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) { // Get base laser range iProjectionValue = __max(0, Item[ gpComparedItemDescObject->usItem ].bestlaserrange * gItemSettings.fBestLaserRangeModifier / CELL_X_SIZE); // Get best laser range - iProjectionModifier = ((FLOAT)GetBestLaserRange( gpComparedItemDescObject ) / CELL_X_SIZE); + iProjectionModifier = (FLOAT)comparedLaserRangeTiles; // Get final laser range iFinalProjectionValue = __max( iProjectionValue, iProjectionModifier ); bNewCode = TRUE; @@ -7752,7 +7758,7 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) } ///////////////// OCTH BEST LASER RANGE - if ( !UsingNewCTHSystem() && GetBestLaserRange( gpItemDescObject ) > 0 ) + if ( !UsingNewCTHSystem() && itemLaserRangeTiles > 0 ) { // Set line to draw into ubNumLine = 7; @@ -7789,7 +7795,7 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) DrawPropertyValueInColour( iComparedFinalBestLaserRangeValue - iFinalBestLaserRangeValue, ubNumLine, 3, fComparisonMode, FALSE, TRUE ); } } - else if( !UsingNewCTHSystem() && ( fComparisonMode && GetBestLaserRange( gpComparedItemDescObject ) > 0 ) ) + else if( !UsingNewCTHSystem() && ( fComparisonMode && comparedLaserRangeTiles > 0 ) ) { // Set line to draw into ubNumLine = 7; @@ -11366,14 +11372,16 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) cnt++; } - BOOLEAN bNewCode = FALSE; ///////////////////// PROJECTION FACTOR / BEST LASER RANGE // with the reworked NCTH code and the laser performance factor we will display BestLaserRange instead of ProjectionFactor - if ( ( UsingNewCTHSystem() && ( GetBestLaserRange( gpItemDescObject ) > 0 || ( fComparisonMode && GetBestLaserRange( gpComparedItemDescObject ) > 0 + BOOLEAN bNewCode = FALSE; + INT16 itemLaserRangeTiles = GetBestLaserRange(gpItemDescObject) / CELL_X_SIZE; + INT16 comparedLaserRangeTiles = fComparisonMode ? GetBestLaserRange(gpComparedItemDescObject) / CELL_X_SIZE : 0; + if ( ( UsingNewCTHSystem() && (itemLaserRangeTiles > 0 || ( fComparisonMode && comparedLaserRangeTiles > 0 && (gGameCTHConstants.LASER_PERFORMANCE_BONUS_HIP + gGameCTHConstants.LASER_PERFORMANCE_BONUS_IRON + gGameCTHConstants.LASER_PERFORMANCE_BONUS_SCOPE != 0) ) ) ) - || ( !UsingNewCTHSystem() && ( GetBestLaserRange( gpItemDescObject ) > 0 || ( fComparisonMode && GetBestLaserRange( gpComparedItemDescObject ) > 0 ) ) ) ) + || ( !UsingNewCTHSystem() && (itemLaserRangeTiles > 0 || ( fComparisonMode && comparedLaserRangeTiles > 0 ) ) ) ) { - iFloatModifier[0] = ((FLOAT)GetBestLaserRange( gpItemDescObject ) / CELL_X_SIZE); + iFloatModifier[0] = (FLOAT)itemLaserRangeTiles; bNewCode = TRUE; } else @@ -11384,7 +11392,7 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) if( fComparisonMode ) { if ( bNewCode ) - iComparedFloatModifier[0] = ((FLOAT)GetBestLaserRange( gpComparedItemDescObject ) / CELL_X_SIZE); + iComparedFloatModifier[0] = (FLOAT)comparedLaserRangeTiles; else iComparedFloatModifier[0] = GetProjectionFactor( gpComparedItemDescObject ); iComparedFloatModifier[1] = iComparedFloatModifier[0]; diff --git a/Tactical/Items.cpp b/Tactical/Items.cpp index c1aff043..ba9d4e00 100644 --- a/Tactical/Items.cpp +++ b/Tactical/Items.cpp @@ -14078,29 +14078,29 @@ INT16 GetFlatToHitBonus( OBJECTTYPE * pObj ) // HEADROCK: Function to get average of all "best laser range" attributes from weapon and attachments INT16 GetAverageBestLaserRange( OBJECTTYPE * pObj ) { - INT16 bonus=0; + FLOAT bonus = 0.0; INT16 numModifiers=0; if (Item[pObj->usItem].bestlaserrange > 0) { numModifiers++; - bonus += Item[pObj->usItem].bestlaserrange; + bonus += (FLOAT) Item[pObj->usItem].bestlaserrange; } for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { if (Item[iter->usItem].bestlaserrange > 0 && iter->exists()) { numModifiers++; - bonus += Item[iter->usItem].bestlaserrange; + bonus += (FLOAT) Item[iter->usItem].bestlaserrange; } } - if (numModifiers>0) + if (numModifiers > 1) { bonus = bonus / numModifiers; } - return( bonus * gItemSettings.fBestLaserRangeModifier ); + return (INT16)(bonus * gItemSettings.fBestLaserRangeModifier); } // get the best laser range from the weapon and attachments @@ -14120,7 +14120,7 @@ INT16 GetBestLaserRange( OBJECTTYPE * pObj ) } } - return( range * gItemSettings.fBestLaserRangeModifier ); + return (INT16) ((FLOAT)range * gItemSettings.fBestLaserRangeModifier); } // HEADROCK: This function determines the bipod bonii of the gun or its attachments @@ -15642,14 +15642,9 @@ BOOLEAN GetItemFromRandomItem( UINT16 usRandomItem, UINT16* pusNewItem ) // as it is also possible to reference to other random items, we also have to check for them // clear the random item arrays and reset the counters - for ( int i = 0; i < RANDOM_ITEM_MAX_NUMBER; ++i) - randomitemarray[i] = 0; - - for ( int i = 0; i < RANDOM_TABOO_MAX; ++i) - { - randomitemtabooarray[i] = 0; - randomitemclasstabooarray[i] = 0; - } + memset(randomitemarray, 0, sizeof(randomitemarray)); + memset(randomitemtabooarray, 0, sizeof(randomitemtabooarray)); + memset(randomitemclasstabooarray, 0, sizeof(randomitemclasstabooarray)); itemcnt = 0; rdtaboocnt = 0; From c13a44c44525df4a962487212da294c1c50f73a7 Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Sat, 27 May 2023 12:02:54 -0700 Subject: [PATCH 03/50] Add version to asserts screen (#158) * Add version info to asserts * Add gMAXITEMS_READ to arms dealer init assert --- Standard Gaming Platform/DEBUG.cpp | 5 +++++ Tactical/Arms Dealer Init.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Standard Gaming Platform/DEBUG.cpp b/Standard Gaming Platform/DEBUG.cpp index 3fd27413..8463e9fb 100644 --- a/Standard Gaming Platform/DEBUG.cpp +++ b/Standard Gaming Platform/DEBUG.cpp @@ -43,6 +43,9 @@ #endif #include "GameSettings.h" #include "SaveLoadGame.h" +#include "Font.h" +#include "GameVersion.h" +#include "Text.h" #include "debug_util.h" @@ -408,6 +411,8 @@ void _FailMessage(const char* message, unsigned lineNum, const char * functionNa sgp::dumpStackTrace(message); + mprintf( 10, 10, L"%s: %s %S %s", pMessageStrings[ MSG_VERSION ], zProductLabel, czVersionString, zBuildInformation ); + std::stringstream basicInformation; basicInformation << "Assertion Failure [Line " << lineNum; if (functionName) { diff --git a/Tactical/Arms Dealer Init.cpp b/Tactical/Arms Dealer Init.cpp index 1a086221..51f5643d 100644 --- a/Tactical/Arms Dealer Init.cpp +++ b/Tactical/Arms Dealer Init.cpp @@ -1120,7 +1120,7 @@ UINT32 GetArmsDealerItemTypeFromItemNumber( UINT16 usItem ) return( 0 ); break; default: - AssertMsg( FALSE, String( "GetArmsDealerItemTypeFromItemNumber(), invalid class %d for item %d. DF 0.", Item[ usItem ].usItemClass, usItem ) ); + AssertMsg( FALSE, String( "GetArmsDealerItemTypeFromItemNumber(), invalid class %d for item %d. gMAXITEMS_READ = %d.", Item[ usItem ].usItemClass, usItem, gMAXITEMS_READ ) ); break; } return( 0 ); From 456cfa18cf9ccb7a03a51479dde046043c4c0095 Mon Sep 17 00:00:00 2001 From: NorthFury Date: Tue, 23 May 2023 22:12:51 +0200 Subject: [PATCH 04/50] added option to scale tooltip rendering --- Screens.cpp | 1 + Standard Gaming Platform/Font.cpp | 2 +- Standard Gaming Platform/WinFont.cpp | 106 +++++------ Standard Gaming Platform/WinFont.h | 8 +- Standard Gaming Platform/mousesystem.cpp | 231 +++++++++++++++-------- Standard Gaming Platform/sgp.cpp | 4 + Strategic/Map Screen Interface.cpp | 79 -------- Tactical/SoldierTooltips.cpp | 44 ++--- Utils/Font Control.cpp | 2 + local.h | 3 +- 10 files changed, 232 insertions(+), 248 deletions(-) diff --git a/Screens.cpp b/Screens.cpp index 723e2126..6ddd8c47 100644 --- a/Screens.cpp +++ b/Screens.cpp @@ -4,6 +4,7 @@ int iResolution; // INI file int iPlayIntro; int iDisableMouseScrolling; int iUseWinFonts; +float fTooltipScaleFactor; /* WANNE, Sgt.Kolja * INI file (Windowed or Fullscreen) * REPLACE all defines WINDOWED_MODE with this variable diff --git a/Standard Gaming Platform/Font.cpp b/Standard Gaming Platform/Font.cpp index 89bb60fc..5caa433b 100644 --- a/Standard Gaming Platform/Font.cpp +++ b/Standard Gaming Platform/Font.cpp @@ -721,7 +721,7 @@ UINT16 GetFontHeight(INT32 FontNum) MapFont = WinFontMap[FontNum]; if (FontNum != -1) { - return (GetWinFontHeight(L"A", MapFont)); + return (GetWinFontHeight(MapFont)); } } return((UINT16)GetHeight(FontObjs[FontNum], 0)); diff --git a/Standard Gaming Platform/WinFont.cpp b/Standard Gaming Platform/WinFont.cpp index 73689871..aa97f278 100644 --- a/Standard Gaming Platform/WinFont.cpp +++ b/Standard Gaming Platform/WinFont.cpp @@ -26,11 +26,6 @@ #include INT32 FindFreeWinFont( void ); -BOOLEAN gfEnumSucceed = FALSE; - -#ifdef CHINESE -#define DEC_INTERNAL_LEADING -#endif // Private struct not to be exported // to other modules @@ -41,13 +36,12 @@ typedef struct COLORVAL ForeColor; COLORVAL BackColor; UINT8 Height; - UINT8 Width[0x80]; -#ifdef DEC_INTERNAL_LEADING UINT8 InternalLeading; +#ifdef CHINESE + UINT8 Width[0x80]; #endif } HWINFONT; -LOGFONT gLogFont; LONG gWinFontAdjust; HWINFONT WinFonts[WIN_LASTFONT]; @@ -80,6 +74,10 @@ struct { {"HugeFont", {-19, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, VARIABLE_PITCH | FF_DONTCARE, "ja2font3"}, FROMRGB(0, 255, 0)} }; +LOGFONT TooltipLogFont = { -11, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, VARIABLE_PITCH | FF_DONTCARE, "Arial" }; +INT32 TOOLTIP_IFONT = -1; +INT32 TOOLTIP_IFONT_BOLD = -1; + void Convert16BitStringTo8Bit( CHAR8 *dst, CHAR16 *src ) { //hope 'dst' is big enough @@ -288,6 +286,29 @@ void ShutdownWinFonts( ) {DeleteWinFont(i);} } +void InitTooltipFonts() +{ + LOGFONT adjustedLogFont = TooltipLogFont; + adjustedLogFont.lfHeight = adjustedLogFont.lfHeight * fTooltipScaleFactor; + + LOGFONT adjustedBoldTooltipLogFont = adjustedLogFont; + adjustedBoldTooltipLogFont.lfWeight = FW_BOLD; + + + TOOLTIP_IFONT = CreateWinFont(adjustedLogFont); + TOOLTIP_IFONT_BOLD = CreateWinFont(adjustedBoldTooltipLogFont); + + COLORVAL regularColor = FROMRGB(201, 197, 143); + COLORVAL boldColor = FROMRGB(223, 176, 1); + SetWinFontForeColor(TOOLTIP_IFONT, ®ularColor); + SetWinFontForeColor(TOOLTIP_IFONT_BOLD, &boldColor); +} + +void ShutdownTooltipFonts() +{ + ShutdownWinFonts(); +} + INT32 FindFreeWinFont( void ) { INT32 iCount; @@ -347,8 +368,10 @@ INT32 CreateWinFont( LOGFONT &logfont ) WinFonts[ iFont ].hFont = hFont; HDC hdc = GetDC(NULL); + SelectObject(hdc, hFont); + +#ifdef CHINESE SIZE RectSize; - SelectObject(hdc, hFont ); wchar_t str[2]=L"\1"; for (int i = 1; i<0x80; i++) { @@ -359,12 +382,12 @@ INT32 CreateWinFont( LOGFONT &logfont ) str[0] = L'啊'; GetTextExtentPoint32W( hdc, str, 1, &RectSize ); WinFonts[iFont].Width[0] = (UINT8)RectSize.cx; +#endif + TEXTMETRIC tm; GetTextMetrics(hdc, &tm); WinFonts[ iFont ].Height = (UINT8)tm.tmAscent; -#ifdef DEC_INTERNAL_LEADING WinFonts[ iFont ].InternalLeading = (UINT8)tm.tmInternalLeading; -#endif ReleaseDC(NULL, hdc); return( iFont ); @@ -444,12 +467,10 @@ void PrintWinFont( UINT32 uiDestBuf, INT32 iFont, INT32 x, INT32 y, STR16 pFontS SetBkMode(hdc, TRANSPARENT); SetTextAlign(hdc, TA_TOP|TA_LEFT); -#ifdef DEC_INTERNAL_LEADING if (y - pWinFont->InternalLeading >=0) { y -= pWinFont->InternalLeading; } -#endif TextOutW( hdc, x, y, string, len ); IDirectDrawSurface2_ReleaseDC( pDDSurface, hdc ); @@ -458,17 +479,10 @@ void PrintWinFont( UINT32 uiDestBuf, INT32 iFont, INT32 x, INT32 y, STR16 pFontS INT16 WinFontStringPixLength( STR16 string2, INT32 iFont ) { - HWINFONT *pWinFont; -#ifndef CHINESE - HDC hdc; - SIZE RectSize; -#endif + HWINFONT *pWinFont; pWinFont = GetWinFont( iFont ); - if ( pWinFont == NULL ) - { - return( 0 ); - } + if (pWinFont == NULL) return(0); #ifdef CHINESE wchar_t *p=string2; @@ -486,7 +500,9 @@ INT16 WinFontStringPixLength( STR16 string2, INT32 iFont ) } return size; #else - hdc = GetDC(NULL); + SIZE RectSize; + HDC hdc = GetDC(NULL); + SelectObject(hdc, pWinFont->hFont ); GetTextExtentPoint32W( hdc, string2, lstrlenW(string2), &RectSize ); ReleaseDC(NULL, hdc); @@ -496,44 +512,18 @@ INT16 WinFontStringPixLength( STR16 string2, INT32 iFont ) } -INT16 GetWinFontHeight( STR16 string2, INT32 iFont ) +INT16 GetWinFontHeight(INT32 iFont) { - HWINFONT *pWinFont; -// HDC hdc; -// SIZE RectSize; + HWINFONT* pWinFont; - pWinFont = GetWinFont( iFont ); + pWinFont = GetWinFont(iFont); - if ( pWinFont == NULL ) - { - return( 0 ); - } - else - { + if (pWinFont == NULL) return(0); #ifdef CHINESE //zwwooooo: Correct tactical interface font height to fixed Chinese characters smearing bug - if (iFont==WIN_TINYFONT1 || iFont==WIN_SMALLFONT1 || iFont==WIN_14POINTARIAL) return pWinFont->Height+2; + if (iFont == WinFontMap[TINYFONT1] || iFont == WinFontMap[SMALLFONT1] || iFont == WinFontMap[WIN_14POINTARIAL]) + { + return pWinFont->Height + 2; + } #endif - return pWinFont->Height; - } - /* - hdc = GetDC(NULL); - SelectObject(hdc, pWinFont->hFont ); - GetTextExtentPoint32W( hdc, string2, lstrlenW(string2), &RectSize ); - ReleaseDC(NULL, hdc); - - return( (INT16)RectSize.cy );*/ -} - -UINT32 WinFont_mprintf( INT32 iFont, INT32 x, INT32 y, STR16 pFontString, ...) -{ - va_list argptr; - CHAR16 string[512]; - - va_start(argptr, pFontString); // Set up variable argument pointer - vswprintf(string, pFontString, argptr); // process gprintf string (get output str) - va_end(argptr); - - PrintWinFont( FontDestBuffer, iFont, x, y, string ); - - return( 1 ); + return pWinFont->Height; } diff --git a/Standard Gaming Platform/WinFont.h b/Standard Gaming Platform/WinFont.h index 77fceb6a..751e42e3 100644 --- a/Standard Gaming Platform/WinFont.h +++ b/Standard Gaming Platform/WinFont.h @@ -5,6 +5,9 @@ void InitWinFonts( ); void ShutdownWinFonts( ); +void InitTooltipFonts(); +void ShutdownTooltipFonts(); + INT32 CreateWinFont( LOGFONT &logfont ); void DeleteWinFont( INT32 iFont ); @@ -14,8 +17,7 @@ void SetWinFontForeColor( INT32 iFont, COLORVAL *pColor ); void PrintWinFont( UINT32 uiDestBuf, INT32 iFont, INT32 x, INT32 y, STR16 pFontString, ...); INT16 WinFontStringPixLength( STR16 string, INT32 iFont ); -INT16 GetWinFontHeight( STR16 string, INT32 iFont ); -UINT32 WinFont_mprintf( INT32 iFont, INT32 x, INT32 y, STR16 pFontString, ...); +INT16 GetWinFontHeight( INT32 iFont ); //if you cahnge this enum, you must change FontInfo struct in WinFont.cpp too. enum { @@ -43,5 +45,7 @@ WIN_LASTFONT }; #define MAX_WINFONTMAP 25 extern INT32 WinFontMap[MAX_WINFONTMAP]; +extern INT32 TOOLTIP_IFONT; +extern INT32 TOOLTIP_IFONT_BOLD; #endif diff --git a/Standard Gaming Platform/mousesystem.cpp b/Standard Gaming Platform/mousesystem.cpp index d6cb3828..ffa479b8 100644 --- a/Standard Gaming Platform/mousesystem.cpp +++ b/Standard Gaming Platform/mousesystem.cpp @@ -1361,32 +1361,32 @@ void SetRegionFastHelpText( MOUSE_REGION *region, const STR16 szText ) //region->FastHelpTimer = gsFastHelpDelay; } -INT16 GetNumberOfLinesInHeight( const STR16 pStringA ) +bool isScalingEnabled() { + return fTooltipScaleFactor > 1; +} + +UINT16 GetScaledFontHeight() { - STR16 pToken; - INT16 sCounter = 0; - CHAR16 pString[ 4096 ]; + return isScalingEnabled() + ? GetWinFontHeight(TOOLTIP_IFONT) + : GetFontHeight(FONT10ARIAL); +} - wcscpy( pString, pStringA ); +// this function returns the number of lines the input string has +// that can be renderered within the screen height +INT16 GetNumberOfLinesInHeight(const STR16 inputString) { + INT32 fontHeight = GetScaledFontHeight(); - // tokenize - pToken = wcstok( pString, L"\n" ); - - while( pToken != NULL ) - { - // WANNE: Fix by Headrock - if ( (sCounter+1) * (GetFontHeight(FONT10ARIAL)+1) > (SCREEN_HEIGHT - 10) ) - { - break; - } - pToken = wcstok( NULL, L"\n" ); - sCounter++; - - /*pToken = wcstok( NULL, L"\n" ); - sCounter++;*/ + INT32 i; + INT32 count = 1; + INT32 stringLength = (INT32)wcslen(inputString); + for (i = 0; i < stringLength && count * (fontHeight + 1) < (SCREEN_HEIGHT - 10); i++) { + if (inputString[i] == '\n' && i + 1 < stringLength) { + count++; + } } - return( sCounter ); + return(count); } @@ -1398,17 +1398,14 @@ INT16 GetNumberOfLinesInHeight( const STR16 pStringA ) // void DisplayFastHelp( MOUSE_REGION *region ) { - UINT16 usFillColor; INT32 iX,iY,iW,iH; if ( region->uiFlags & MSYS_FASTHELP ) { - usFillColor = Get16BPPColor(FROMRGB(250, 240, 188)); + iW = (INT32)GetWidthOfString(region->FastHelpText) + 10 * fTooltipScaleFactor; + iH = (INT32)(GetNumberOfLinesInHeight(region->FastHelpText) * (GetScaledFontHeight() + 1) + 8 * fTooltipScaleFactor); - iW = (INT32)GetWidthOfString( region->FastHelpText ) + 10; - iH = (INT32)( GetNumberOfLinesInHeight( region->FastHelpText ) * (GetFontHeight(FONT10ARIAL)+1) + 8 ); - - iX = (INT32)region->RegionTopLeftX + 10; + iX = (INT32)region->RegionTopLeftX + 10 * fTooltipScaleFactor; if (iX < 0) iX = 0; @@ -1424,7 +1421,7 @@ void DisplayFastHelp( MOUSE_REGION *region ) iY = 0; if ( (iY + iH) >= SCREEN_HEIGHT ) - iY = (SCREEN_HEIGHT - iH - 15); + iY = (SCREEN_HEIGHT - iH - 10); if ( !(region->uiFlags & MSYS_GOT_BACKGROUND) ) { @@ -1445,83 +1442,151 @@ void DisplayFastHelp( MOUSE_REGION *region ) ShadowVideoSurfaceRect( FRAME_BUFFER, iX + 2, iY + 2, iX + iW - 3, iY + iH - 3 ); ShadowVideoSurfaceRect( FRAME_BUFFER, iX + 2, iY + 2, iX + iW - 3, iY + iH - 3 ); - SetFont( FONT10ARIAL ); - SetFontShadow( FONT_NEARBLACK ); - DisplayHelpTokenizedString( region->FastHelpText ,( INT16 )( iX + 5 ), ( INT16 )( iY + 5 ) ); + DisplayHelpTokenizedString( + region->FastHelpText, + (INT16)(iX + (isScalingEnabled() ? 5 * fTooltipScaleFactor : 5)), + (INT16)(iY + (isScalingEnabled() ? 4 * fTooltipScaleFactor : 5)) + ); InvalidateRegion( iX, iY, (iX + iW) , (iY + iH) ); } } } -INT16 GetWidthOfString( const STR16 pStringA ) +INT16 GetWidthOfString(const STR16 inputString) { - CHAR16 pString[ 4096 ]; - STR16 pToken; - INT16 sWidth = 0; - wcscpy( pString, pStringA ); + INT16 width = 0; + CHAR16 stringBuffer[256] = L""; + INT32 bufferIndex = 0; + bool isBold = false; - // tokenize - pToken = wcstok( pString, L"\n" ); + INT32 iFontHeight = GetScaledFontHeight(); - while( pToken != NULL ) - { - if( sWidth < StringPixLength( pToken, FONT10ARIAL ) ) - { - sWidth = StringPixLength( pToken, FONT10ARIAL ); + INT16 lineWidth = 0; + + INT32 lineCounter = 0; + INT32 i; + INT32 stringLength = (INT32)wcslen(inputString); + for (i = 0; i < stringLength; i++) { + if (inputString[i] == '\n') { + // if the lines don't fit the screen the last line is ... + if ((lineCounter + 2) * (iFontHeight + 1) > (SCREEN_HEIGHT - 10)) { + lineWidth = isScalingEnabled() + ? WinFontStringPixLength(L"...", TOOLTIP_IFONT) + : StringPixLength(L"...", FONT10ARIAL); + if (width < lineWidth) { + width = lineWidth; + } + break; + } + lineWidth = 0; + lineCounter++; } + else if (inputString[i] == '|') { + isBold = true; + } + else { + stringBuffer[bufferIndex++] = inputString[i]; - pToken = wcstok( NULL, L"\n" ); + // look ahead to see if we need to flush the string buffer + if (i + 1 >= stringLength + || inputString[i + 1] == '\n' + || (isBold && inputString[i + 1] != '|') + || (!isBold && inputString[i + 1] == '|')) { + + // set string ending character + stringBuffer[bufferIndex] = '\0'; + + if (isScalingEnabled()) { + INT32 iFont = isBold ? TOOLTIP_IFONT_BOLD : TOOLTIP_IFONT; + lineWidth += WinFontStringPixLength(stringBuffer, iFont); + } + else { + INT32 iFont = isBold ? FONT10ARIALBOLD : FONT10ARIAL; + SetFont(iFont); + lineWidth += StringPixLength(stringBuffer, iFont); + } + bufferIndex = 0; + isBold = false; + + if (i + 1 >= stringLength || inputString[i + 1] == '\n') { + if (width < lineWidth) { + width = lineWidth; + } + } + } + } } - return( sWidth ); - + return width; } -void DisplayHelpTokenizedString( const STR16 pStringA, INT16 sX, INT16 sY ) +void DisplayHelpTokenizedString(const STR16 inputString, INT16 sX, INT16 sY) { - STR16 pToken; - INT32 iCounter = 0, i; - UINT32 uiCursorXPos; - CHAR16 pString[ 4096 ]; - INT32 iLength; + SetFontShadow(FONT_NEARBLACK); - wcscpy( pString, pStringA ); + INT32 fontHeight = GetScaledFontHeight(); - // tokenize - pToken = wcstok( pString, L"\n" ); + CHAR16 stringBuffer[256] = L""; + INT32 bufferIndex = 0; + bool isBold = false; - while( pToken != NULL ) - { - // WANNE: Fix by Headrock - if ( (iCounter+2) * (GetFontHeight(FONT10ARIAL)+1) > (SCREEN_HEIGHT - 10) ) - { - mprintf( sX, sY + iCounter * (GetFontHeight(FONT10ARIAL)+1), L"..." ); - break; - } - iLength = (INT32)wcslen( pToken ); + INT16 xDelta = 0; - //iLength = (INT32)wcslen( pToken ); - for( i = 0; i < iLength; i++ ) - { - uiCursorXPos = StringPixLengthArgFastHelp( FONT10ARIAL, FONT10ARIALBOLD, i, pToken ); - if( pToken[ i ] == '|' ) - { - i++; - SetFont( FONT10ARIALBOLD ); - SetFontForeground( 146 ); + INT32 lineCounter = 0; + INT32 i; + INT32 stringLength = (INT32)wcslen(inputString); + for (i = 0; i < stringLength; i++) { + if (inputString[i] == '\n') { + // if the lines don't fit the screen the last line is ... + if ((lineCounter + 2) * (fontHeight + 1) > (SCREEN_HEIGHT - 10)) { + if (isScalingEnabled()) { + PrintWinFont(FontDestBuffer, TOOLTIP_IFONT, sX, sY + lineCounter * (fontHeight + 1), L"..."); + } + else { + SetFont(FONT10ARIAL); + mprintf(sX, sY + lineCounter * (fontHeight + 1), L"..."); + } + break; + } + xDelta = 0; + lineCounter++; + } + else if (inputString[i] == '|') { + isBold = true; + } + else { + stringBuffer[bufferIndex++] = inputString[i]; + + // look ahead to see if we need to flush the string buffer + if (i + 1 >= stringLength + || inputString[i + 1] == '\n' + || (isBold && inputString[i + 1] != '|') + || (!isBold && inputString[i + 1] == '|')) { + + // set string ending character + stringBuffer[bufferIndex] = '\0'; + + if (isScalingEnabled()) { + // the font color is set on font initialization + INT32 iFont = isBold ? TOOLTIP_IFONT_BOLD : TOOLTIP_IFONT; + PrintWinFont(FontDestBuffer, iFont, sX + xDelta, sY + lineCounter * (fontHeight + 1), L"%s", stringBuffer); + xDelta += WinFontStringPixLength(stringBuffer, iFont); + } + else { + INT32 iFont = isBold ? FONT10ARIALBOLD : FONT10ARIAL; + SetFont(iFont); + SetFontForeground(isBold ? 146 : FONT_BEIGE); + mprintf(sX + xDelta, sY + lineCounter * (fontHeight + 1), L"%s", stringBuffer); + xDelta += StringPixLength(stringBuffer, iFont); + } + bufferIndex = 0; + isBold = false; } - else - { - SetFont( FONT10ARIAL ); - SetFontForeground( FONT_BEIGE ); - } - mprintf( sX + uiCursorXPos, sY + iCounter * (GetFontHeight(FONT10ARIAL)+1), L"%c", pToken[ i ] ); } - pToken = wcstok( NULL, L"\n" ); - iCounter++; } - SetFontDestBuffer( FRAME_BUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); + + SetFontDestBuffer(FRAME_BUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE); } void RenderFastHelp() @@ -1819,4 +1884,4 @@ MOUSE_REGION *get_next_entry_in_MSYS_RegList(MOUSE_REGION *current_region) void ResetWheelState( MOUSE_REGION *region ) { region->WheelState = 0; -} \ No newline at end of file +} diff --git a/Standard Gaming Platform/sgp.cpp b/Standard Gaming Platform/sgp.cpp index daa082fe..91f5d545 100644 --- a/Standard Gaming Platform/sgp.cpp +++ b/Standard Gaming Platform/sgp.cpp @@ -1329,6 +1329,8 @@ void GetRuntimeSettings( ) // WANNE: Should we play the intro? iPlayIntro = (int) GetPrivateProfileInt( "Ja2 Settings","PLAY_INTRO", iPlayIntro, INIFile ); iUseWinFonts = (int) GetPrivateProfileInt( "Ja2 Settings","USE_WINFONTS", iUseWinFonts, INIFile,); + fTooltipScaleFactor = ((int)GetPrivateProfileInt("Ja2 Settings", "TOOLTIP_SCALE_FACTOR", 100, INIFile, )) / 100; + if (fTooltipScaleFactor < 1) fTooltipScaleFactor = 1; // haydent: mouse scrolling iDisableMouseScrolling = (int) GetPrivateProfileInt( "Ja2 Settings","DISABLE_MOUSE_SCROLLING", iDisableMouseScrolling, INIFile ); @@ -1371,6 +1373,8 @@ void GetRuntimeSettings( ) iPlayIntro = (int)oProps.getIntProperty("Ja2 Settings","PLAY_INTRO", iPlayIntro); iUseWinFonts= (int)oProps.getIntProperty("Ja2 Settings","USE_WINFONTS", iUseWinFonts); + fTooltipScaleFactor = ((float)oProps.getFloatProperty("Ja2 Settings", "TOOLTIP_SCALE_FACTOR", 100)) / 100; + if (fTooltipScaleFactor < 1) fTooltipScaleFactor = 1; // haydent: mouse scrolling iDisableMouseScrolling = (int)oProps.getIntProperty("Ja2 Settings","DISABLE_MOUSE_SCROLLING", iDisableMouseScrolling); diff --git a/Strategic/Map Screen Interface.cpp b/Strategic/Map Screen Interface.cpp index 95315861..37eb3087 100644 --- a/Strategic/Map Screen Interface.cpp +++ b/Strategic/Map Screen Interface.cpp @@ -2959,85 +2959,6 @@ void DisplayUserDefineHelpTextRegions( FASTHELPREGION *pRegion ) -extern void DisplayHelpTokenizedString( const STR16 pStringA, INT16 sX, INT16 sY ); -extern INT16 GetNumberOfLinesInHeight( const STR16 pStringA ); -extern INT16 GetWidthOfString( const STR16 pStringA ); - -void DisplaySoldierToolTip( FASTHELPREGION *pRegion ) -{ - UINT16 usFillColor; - INT32 iX,iY,iW,iH; - UINT8 *pDestBuf; - UINT32 uiDestPitchBYTES; - - - // grab the color for the background region - usFillColor = Get16BPPColor(FROMRGB(250, 240, 188)); - - - iX = pRegion->iX; - iY = pRegion->iY; - // get the width and height of the string - //iW = (INT32)( pRegion->iW ) + 14; - iW = (INT32)GetWidthOfString( pRegion->FastHelpText ) + 10; - - //iH = IanWrappedStringHeight( ( UINT16 )iX, ( UINT16 )iY, ( UINT16 )( pRegion->iW ), 0, FONT10ARIAL, FONT_BLACK, pRegion->FastHelpText, FONT_BLACK, TRUE, 0 ); - iH = (INT32)( GetNumberOfLinesInHeight( pRegion->FastHelpText ) * (GetFontHeight(FONT10ARIAL)+1) + 8 ); - - // tack on the outer border - iH += 14; - - // gone not far enough? - if ( iX < 0 ) - iX = 0; - - // gone too far - if ( ( pRegion->iX + iW ) >= SCREEN_WIDTH ) - iX = (SCREEN_WIDTH - iW - 4); - - // what about the y value? - iY = (INT32)pRegion->iY - ( iH * 3 / 4); - - // not far enough - if (iY < 0) - iY = 0; - - // too far - if ( (iY + iH) >= SCREEN_HEIGHT ) - iY = (SCREEN_HEIGHT - iH - 15); - - pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES ); - SetClippingRegionAndImageWidth( uiDestPitchBYTES, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); - RectangleDraw( TRUE, iX + 1, iY + 1, iX + iW - 1, iY + iH - 1, Get16BPPColor( FROMRGB( 65, 57, 15 ) ), pDestBuf ); - RectangleDraw( TRUE, iX, iY, iX + iW - 2, iY + iH - 2, Get16BPPColor( FROMRGB( 227, 198, 88 ) ), pDestBuf ); - UnLockVideoSurface( FRAME_BUFFER ); - ShadowVideoSurfaceRect( FRAME_BUFFER, iX + 2, iY + 2, iX + iW - 3, iY + iH - 3 ); - ShadowVideoSurfaceRect( FRAME_BUFFER, iX + 2, iY + 2, iX + iW - 3, iY + iH - 3 ); - - // fillt he video surface areas - //ColorFillVideoSurfaceArea(FRAME_BUFFER, iX, iY, (iX + iW), (iY + iH), 0); - //ColorFillVideoSurfaceArea(FRAME_BUFFER, (iX + 1), (iY + 1), (iX + iW - 1), (iY + iH - 1), usFillColor); - - SetFont( FONT10ARIAL ); - SetFontForeground( FONT_BEIGE ); - - //iH = ( INT32 )DisplayWrappedString( ( INT16 )( iX + 10 ), ( INT16 )( iY + 6 ), ( INT16 )pRegion->iW, 0, FONT10ARIAL, FONT_BEIGE, pRegion->FastHelpText, FONT_NEARBLACK, TRUE, 0 ); - - DisplayHelpTokenizedString( pRegion->FastHelpText ,( INT16 )( iX + 5 ), ( INT16 )( iY + 5 ) ); - - iHeightOfInitFastHelpText = iH + 20; - - InvalidateRegion( iX, iY, (iX + iW) , (iY + iH + 20 ) ); -} - - - - - - - - - void DisplayFastHelpForInitialTripInToMapScreen( FASTHELPREGION *pRegion ) { if( gTacticalStatus.fDidGameJustStart ) diff --git a/Tactical/SoldierTooltips.cpp b/Tactical/SoldierTooltips.cpp index 06517851..706d64e8 100644 --- a/Tactical/SoldierTooltips.cpp +++ b/Tactical/SoldierTooltips.cpp @@ -752,29 +752,23 @@ void DrawMouseTooltip() extern void DisplayTooltipString( const STR16 pStringA, INT16 sX, INT16 sY ); extern void j_log(PTR,...); + UINT16 fontHeight = fTooltipScaleFactor > 1 + ? GetWinFontHeight(TOOLTIP_IFONT) + : GetFontHeight(FONT10ARIAL); + iX = mouseTT.iX;iY = mouseTT.iY; - iW = (INT32)GetWidthOfString( mouseTT.FastHelpText ) + 10; - iH = (INT32)( GetNumberOfLinesInHeight( mouseTT.FastHelpText ) * (GetFontHeight(FONT10ARIAL)+1) + 8 ); + iW = (INT32)(GetWidthOfString(mouseTT.FastHelpText) + 10 * fTooltipScaleFactor); + iH = (INT32)(GetNumberOfLinesInHeight(mouseTT.FastHelpText) * (fontHeight + 1) + 8 * fTooltipScaleFactor); - if(1)//draw at cursor - { - iY -= (iH / 2); - if (gusMouseXPos > (SCREEN_WIDTH / 2)) - iX = gusMouseXPos - iW - 24; - else - iX = gusMouseXPos + 24; - //if (gusMouseYPos > (SCREEN_HEIGHT / 2)) - // iY -= 32; - - if (iY <= 0) iY += 32; - } + iY -= (iH / 2); + if (gusMouseXPos > (SCREEN_WIDTH / 2)) + iX = gusMouseXPos - iW - 24; else - { //draw in panel - //502,485 658,596 160*110 580,540 - iX = 580 - (iW / 2); - iY = 540 - (iH/2); - if (iY + iH > SCREEN_HEIGHT) iY = SCREEN_HEIGHT - iH - 3 ; - } + iX = gusMouseXPos + 24; + //if (gusMouseYPos > (SCREEN_HEIGHT / 2)) + // iY -= 32; + + if (iY <= 0) iY += 32; pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES ); SetClippingRegionAndImageWidth( uiDestPitchBYTES, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); @@ -784,10 +778,12 @@ void DrawMouseTooltip() ShadowVideoSurfaceRect( FRAME_BUFFER, iX + 2, iY + 2, iX + iW - 3, iY + iH - 3 ); ShadowVideoSurfaceRect( FRAME_BUFFER, iX + 2, iY + 2, iX + iW - 3, iY + iH - 3 ); - SetFont( FONT10ARIAL ); - SetFontShadow( FONT_NEARBLACK ); - DisplayHelpTokenizedString( mouseTT.FastHelpText ,( INT16 )( iX + 5 ), ( INT16 )( iY + 5 ) ); + DisplayHelpTokenizedString( + mouseTT.FastHelpText, + (INT16)(iX + (fTooltipScaleFactor > 1 ? 5 * fTooltipScaleFactor : 5)), + (INT16)(iY + (fTooltipScaleFactor > 1 ? 4 * fTooltipScaleFactor : 5)) + ); InvalidateRegion( iX, iY, (iX + iW) , (iY + iH) ); //InvalidateScreen(); -} \ No newline at end of file +} diff --git a/Utils/Font Control.cpp b/Utils/Font Control.cpp index 945fe2a3..87c140f5 100644 --- a/Utils/Font Control.cpp +++ b/Utils/Font Control.cpp @@ -231,6 +231,7 @@ BOOLEAN InitializeFonts( ) if ( iUseWinFonts ) { InitWinFonts( ); } + InitTooltipFonts(); return( TRUE ); } @@ -261,6 +262,7 @@ void ShutdownFonts( ) if ( iUseWinFonts ) { ShutdownWinFonts(); } + ShutdownTooltipFonts(); } // Set shades for fonts diff --git a/local.h b/local.h index cf0bba6f..c52e262f 100644 --- a/local.h +++ b/local.h @@ -32,6 +32,7 @@ extern UINT16 SCREEN_HEIGHT; extern int iResolution; // Resolution id from the ini file extern int iPlayIntro; extern int iUseWinFonts; +extern float fTooltipScaleFactor; extern int iDisableMouseScrolling; extern INT16 iScreenWidthOffset; extern INT16 iScreenHeightOffset; @@ -82,4 +83,4 @@ extern BOOLEAN fDisplayOverheadMap; #define PIXEL_DEPTH 16 -#endif \ No newline at end of file +#endif From e6d0b5ae0e49b86030f2ff285c1f48406972175b Mon Sep 17 00:00:00 2001 From: NorthFury Date: Sat, 27 May 2023 16:56:27 +0200 Subject: [PATCH 05/50] fix tooltip scaling when winfonts are enabled --- Standard Gaming Platform/WinFont.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Standard Gaming Platform/WinFont.cpp b/Standard Gaming Platform/WinFont.cpp index aa97f278..cb60ab16 100644 --- a/Standard Gaming Platform/WinFont.cpp +++ b/Standard Gaming Platform/WinFont.cpp @@ -43,7 +43,7 @@ typedef struct } HWINFONT; LONG gWinFontAdjust; -HWINFONT WinFonts[WIN_LASTFONT]; +HWINFONT WinFonts[WIN_LASTFONT + 2]; // extra space for the tooltip fonts INT32 WinFontMap[MAX_WINFONTMAP]; @@ -327,7 +327,7 @@ INT32 FindFreeWinFont( void ) HWINFONT *GetWinFont( INT32 iFont ) { - if ( iFont == -1 || iFont >=WIN_LASTFONT) + if (iFont == -1 || iFont >= sizeof(WinFonts) ) { return( NULL ); } From 77faa11a0f59373cf56ac23724a55bc14d319603 Mon Sep 17 00:00:00 2001 From: NorthFury Date: Sat, 27 May 2023 17:10:57 +0200 Subject: [PATCH 06/50] inline the tooltip log font --- Standard Gaming Platform/WinFont.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Standard Gaming Platform/WinFont.cpp b/Standard Gaming Platform/WinFont.cpp index cb60ab16..bfed0c1b 100644 --- a/Standard Gaming Platform/WinFont.cpp +++ b/Standard Gaming Platform/WinFont.cpp @@ -74,7 +74,6 @@ struct { {"HugeFont", {-19, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, VARIABLE_PITCH | FF_DONTCARE, "ja2font3"}, FROMRGB(0, 255, 0)} }; -LOGFONT TooltipLogFont = { -11, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, VARIABLE_PITCH | FF_DONTCARE, "Arial" }; INT32 TOOLTIP_IFONT = -1; INT32 TOOLTIP_IFONT_BOLD = -1; @@ -288,15 +287,17 @@ void ShutdownWinFonts( ) void InitTooltipFonts() { - LOGFONT adjustedLogFont = TooltipLogFont; - adjustedLogFont.lfHeight = adjustedLogFont.lfHeight * fTooltipScaleFactor; + LOGFONT logFont = { -11, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, VARIABLE_PITCH | FF_DONTCARE, + "Arial" + }; + logFont.lfHeight = logFont.lfHeight * fTooltipScaleFactor; - LOGFONT adjustedBoldTooltipLogFont = adjustedLogFont; - adjustedBoldTooltipLogFont.lfWeight = FW_BOLD; + LOGFONT boldTooltipLogFont = logFont; + boldTooltipLogFont.lfWeight = FW_BOLD; - - TOOLTIP_IFONT = CreateWinFont(adjustedLogFont); - TOOLTIP_IFONT_BOLD = CreateWinFont(adjustedBoldTooltipLogFont); + TOOLTIP_IFONT = CreateWinFont(logFont); + TOOLTIP_IFONT_BOLD = CreateWinFont(boldTooltipLogFont); COLORVAL regularColor = FROMRGB(201, 197, 143); COLORVAL boldColor = FROMRGB(223, 176, 1); From 52d81357096301fc2da4985fb12c137a853f60b6 Mon Sep 17 00:00:00 2001 From: NorthFury Date: Sat, 27 May 2023 17:17:14 +0200 Subject: [PATCH 07/50] inline for loop variable --- Standard Gaming Platform/mousesystem.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Standard Gaming Platform/mousesystem.cpp b/Standard Gaming Platform/mousesystem.cpp index ffa479b8..55109c57 100644 --- a/Standard Gaming Platform/mousesystem.cpp +++ b/Standard Gaming Platform/mousesystem.cpp @@ -1377,10 +1377,9 @@ UINT16 GetScaledFontHeight() INT16 GetNumberOfLinesInHeight(const STR16 inputString) { INT32 fontHeight = GetScaledFontHeight(); - INT32 i; INT32 count = 1; INT32 stringLength = (INT32)wcslen(inputString); - for (i = 0; i < stringLength && count * (fontHeight + 1) < (SCREEN_HEIGHT - 10); i++) { + for (int i = 0; i < stringLength && count * (fontHeight + 1) < (SCREEN_HEIGHT - 10); i++) { if (inputString[i] == '\n' && i + 1 < stringLength) { count++; } @@ -1465,9 +1464,8 @@ INT16 GetWidthOfString(const STR16 inputString) INT16 lineWidth = 0; INT32 lineCounter = 0; - INT32 i; INT32 stringLength = (INT32)wcslen(inputString); - for (i = 0; i < stringLength; i++) { + for (int i = 0; i < stringLength; i++) { if (inputString[i] == '\n') { // if the lines don't fit the screen the last line is ... if ((lineCounter + 2) * (iFontHeight + 1) > (SCREEN_HEIGHT - 10)) { @@ -1534,9 +1532,8 @@ void DisplayHelpTokenizedString(const STR16 inputString, INT16 sX, INT16 sY) INT16 xDelta = 0; INT32 lineCounter = 0; - INT32 i; INT32 stringLength = (INT32)wcslen(inputString); - for (i = 0; i < stringLength; i++) { + for (int i = 0; i < stringLength; i++) { if (inputString[i] == '\n') { // if the lines don't fit the screen the last line is ... if ((lineCounter + 2) * (fontHeight + 1) > (SCREEN_HEIGHT - 10)) { From 1c82efb79707cf0f04ac7d8aadd32e983e1760ff Mon Sep 17 00:00:00 2001 From: NorthFury Date: Sat, 27 May 2023 17:41:38 +0200 Subject: [PATCH 08/50] use isTooltipScalingEnabled for soldier tooltips --- Standard Gaming Platform/mousesystem.cpp | 17 +++++++++-------- Tactical/SoldierTooltips.cpp | 7 ++++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Standard Gaming Platform/mousesystem.cpp b/Standard Gaming Platform/mousesystem.cpp index 55109c57..a9fb22c2 100644 --- a/Standard Gaming Platform/mousesystem.cpp +++ b/Standard Gaming Platform/mousesystem.cpp @@ -56,6 +56,7 @@ extern void ReleaseAnchorMode(); //private function used here (implemented in Bu INT16 GetNumberOfLinesInHeight( const STR16 pStringA ); INT16 GetWidthOfString( const STR16 pStringA ); void DisplayHelpTokenizedString( const STR16 pStringA, INT16 sX, INT16 sY ); +bool isTooltipScalingEnabled(); @@ -1361,13 +1362,13 @@ void SetRegionFastHelpText( MOUSE_REGION *region, const STR16 szText ) //region->FastHelpTimer = gsFastHelpDelay; } -bool isScalingEnabled() { +bool isTooltipScalingEnabled() { return fTooltipScaleFactor > 1; } UINT16 GetScaledFontHeight() { - return isScalingEnabled() + return isTooltipScalingEnabled() ? GetWinFontHeight(TOOLTIP_IFONT) : GetFontHeight(FONT10ARIAL); } @@ -1443,8 +1444,8 @@ void DisplayFastHelp( MOUSE_REGION *region ) DisplayHelpTokenizedString( region->FastHelpText, - (INT16)(iX + (isScalingEnabled() ? 5 * fTooltipScaleFactor : 5)), - (INT16)(iY + (isScalingEnabled() ? 4 * fTooltipScaleFactor : 5)) + (INT16)(iX + (isTooltipScalingEnabled() ? 5 * fTooltipScaleFactor : 5)), + (INT16)(iY + (isTooltipScalingEnabled() ? 4 * fTooltipScaleFactor : 5)) ); InvalidateRegion( iX, iY, (iX + iW) , (iY + iH) ); } @@ -1469,7 +1470,7 @@ INT16 GetWidthOfString(const STR16 inputString) if (inputString[i] == '\n') { // if the lines don't fit the screen the last line is ... if ((lineCounter + 2) * (iFontHeight + 1) > (SCREEN_HEIGHT - 10)) { - lineWidth = isScalingEnabled() + lineWidth = isTooltipScalingEnabled() ? WinFontStringPixLength(L"...", TOOLTIP_IFONT) : StringPixLength(L"...", FONT10ARIAL); if (width < lineWidth) { @@ -1495,7 +1496,7 @@ INT16 GetWidthOfString(const STR16 inputString) // set string ending character stringBuffer[bufferIndex] = '\0'; - if (isScalingEnabled()) { + if (isTooltipScalingEnabled()) { INT32 iFont = isBold ? TOOLTIP_IFONT_BOLD : TOOLTIP_IFONT; lineWidth += WinFontStringPixLength(stringBuffer, iFont); } @@ -1537,7 +1538,7 @@ void DisplayHelpTokenizedString(const STR16 inputString, INT16 sX, INT16 sY) if (inputString[i] == '\n') { // if the lines don't fit the screen the last line is ... if ((lineCounter + 2) * (fontHeight + 1) > (SCREEN_HEIGHT - 10)) { - if (isScalingEnabled()) { + if (isTooltipScalingEnabled()) { PrintWinFont(FontDestBuffer, TOOLTIP_IFONT, sX, sY + lineCounter * (fontHeight + 1), L"..."); } else { @@ -1564,7 +1565,7 @@ void DisplayHelpTokenizedString(const STR16 inputString, INT16 sX, INT16 sY) // set string ending character stringBuffer[bufferIndex] = '\0'; - if (isScalingEnabled()) { + if (isTooltipScalingEnabled()) { // the font color is set on font initialization INT32 iFont = isBold ? TOOLTIP_IFONT_BOLD : TOOLTIP_IFONT; PrintWinFont(FontDestBuffer, iFont, sX + xDelta, sY + lineCounter * (fontHeight + 1), L"%s", stringBuffer); diff --git a/Tactical/SoldierTooltips.cpp b/Tactical/SoldierTooltips.cpp index 706d64e8..b1b08ccf 100644 --- a/Tactical/SoldierTooltips.cpp +++ b/Tactical/SoldierTooltips.cpp @@ -743,6 +743,7 @@ void DrawMouseTooltip() UINT32 uiDestPitchBYTES; static INT32 iX, iY, iW, iH; + extern bool isTooltipScalingEnabled(); extern INT16 GetWidthOfString(const STR16); extern INT16 GetNumberOfLinesInHeight(const STR16); extern void DisplayHelpTokenizedString(const STR16,INT16,INT16); @@ -752,7 +753,7 @@ void DrawMouseTooltip() extern void DisplayTooltipString( const STR16 pStringA, INT16 sX, INT16 sY ); extern void j_log(PTR,...); - UINT16 fontHeight = fTooltipScaleFactor > 1 + UINT16 fontHeight = isTooltipScalingEnabled() ? GetWinFontHeight(TOOLTIP_IFONT) : GetFontHeight(FONT10ARIAL); @@ -780,8 +781,8 @@ void DrawMouseTooltip() DisplayHelpTokenizedString( mouseTT.FastHelpText, - (INT16)(iX + (fTooltipScaleFactor > 1 ? 5 * fTooltipScaleFactor : 5)), - (INT16)(iY + (fTooltipScaleFactor > 1 ? 4 * fTooltipScaleFactor : 5)) + (INT16)(iX + (isTooltipScalingEnabled() ? 5 * fTooltipScaleFactor : 5)), + (INT16)(iY + (isTooltipScalingEnabled() ? 4 * fTooltipScaleFactor : 5)) ); InvalidateRegion( iX, iY, (iX + iW) , (iY + iH) ); From 744e9c92ec498713d9a5c4b5103c087e6db8c82f Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Sun, 28 May 2023 15:05:43 +0300 Subject: [PATCH 09/50] Prevent illegal array access (#159) When using mousewheel to switch between pages in features screen, the HandleHighLightedText() function sometimes would retain valid bHighlight index, but toggle_box_array[bHighlight] == -1, which is used to access z113FeaturesToggleText[] and would later trigger Assertion in gprintfdirty() with pFontString == NULL --- FeaturesScreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FeaturesScreen.cpp b/FeaturesScreen.cpp index 5865c4a2..92d0b60f 100644 --- a/FeaturesScreen.cpp +++ b/FeaturesScreen.cpp @@ -1517,7 +1517,7 @@ void HandleHighLightedText(BOOLEAN fHighLight) bLastRegion = -1; } - if (bHighLight != -1) + if (bHighLight != -1 && toggle_box_array[bHighLight] != -1) { if (bHighLight < OPT_FIRST_COLUMN_TOGGLE_CUT_OFF) { From 986d28019327ccac224d8a409026855c39a1cec3 Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Sun, 28 May 2023 16:32:12 +0300 Subject: [PATCH 10/50] Enable bigger squad sizes for 720p resolution (#160) --- GameInitOptionsScreen.cpp | 4 ++-- SaveLoadGame.cpp | 2 +- SaveLoadScreen.cpp | 2 +- Tactical/Interface Panels.cpp | 14 ++++++++++++-- TileEngine/Radar Screen.cpp | 13 +++++++++---- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/GameInitOptionsScreen.cpp b/GameInitOptionsScreen.cpp index 1a063ae2..fcad1e9d 100644 --- a/GameInitOptionsScreen.cpp +++ b/GameInitOptionsScreen.cpp @@ -4153,7 +4153,7 @@ void BtnGIOSquadSizeSelectionRightCallback( GUI_BUTTON *btn,INT32 reason ) if( reason & MSYS_CALLBACK_REASON_LBUTTON_REPEAT ) { UINT8 maxSquadSize = GIO_SQUAD_SIZE_10; - if (iResolution >= _800x600 && iResolution < _1024x768) + if (iResolution >= _800x600 && iResolution < _1280x720) maxSquadSize = GIO_SQUAD_SIZE_8; if ( iCurrentSquadSize < maxSquadSize ) @@ -4173,7 +4173,7 @@ void BtnGIOSquadSizeSelectionRightCallback( GUI_BUTTON *btn,INT32 reason ) btn->uiFlags|=(BUTTON_CLICKED_ON); UINT8 maxSquadSize = GIO_SQUAD_SIZE_10; - if (iResolution >= _800x600 && iResolution < _1024x768) + if (iResolution >= _800x600 && iResolution < _1280x720) maxSquadSize = GIO_SQUAD_SIZE_8; if ( iCurrentSquadSize < maxSquadSize ) diff --git a/SaveLoadGame.cpp b/SaveLoadGame.cpp index eeb71e54..5c245e84 100644 --- a/SaveLoadGame.cpp +++ b/SaveLoadGame.cpp @@ -4926,7 +4926,7 @@ BOOLEAN LoadSavedGame( int ubSavedGameID ) } if ((gGameOptions.ubSquadSize == 8 && iResolution < _800x600) || - (gGameOptions.ubSquadSize == 10 && iResolution < _1024x768)) + (gGameOptions.ubSquadSize == 10 && iResolution < _1280x720)) { FileClose( hFile ); return(FALSE); diff --git a/SaveLoadScreen.cpp b/SaveLoadScreen.cpp index ac9228f1..5fe319ff 100644 --- a/SaveLoadScreen.cpp +++ b/SaveLoadScreen.cpp @@ -2512,7 +2512,7 @@ void DoneFadeOutForSaveLoadScreen( void ) NextLoopCheckForEnoughFreeHardDriveSpace(); } else if ((gGameOptions.ubSquadSize == 8 && iResolution < _800x600) || - (gGameOptions.ubSquadSize == 10 && iResolution < _1024x768)) + (gGameOptions.ubSquadSize == 10 && iResolution < _1280x720)) { DoSaveLoadMessageBox( MSG_BOX_BASIC_STYLE, zSaveLoadText[SLG_SQUAD_SIZE_RES_ERROR], SAVE_LOAD_SCREEN, MSG_BOX_FLAG_OK, FailedLoadingGameCallBack ); NextLoopCheckForEnoughFreeHardDriveSpace(); diff --git a/Tactical/Interface Panels.cpp b/Tactical/Interface Panels.cpp index e3b27cd9..e3757a03 100644 --- a/Tactical/Interface Panels.cpp +++ b/Tactical/Interface Panels.cpp @@ -5199,7 +5199,7 @@ BOOLEAN InitializeTEAMPanel( ) if (iResolution >= _640x480 && iResolution < _800x600) FilenameForBPP("INTERFACE\\bottom_bar.sti", VObjectDesc.ImageFile); - else if (iResolution < _1024x768) + else if (iResolution < _1280x720) { if (gGameOptions.ubSquadSize > 6) { @@ -5240,6 +5240,16 @@ BOOLEAN InitializeTEAMPanel( ) memset( gfTEAM_HandInvDispText, 0, sizeof( gfTEAM_HandInvDispText ) ); + // Offset button coordinates to correct positions if squadsize is 10 + if (iResolution == _1280x720) + { + UINT16 offset = 223; + TM_ENDTURN_X = xResOffset + (xResSize - 131) + offset; + TM_ROSTERMODE_X = xResOffset + (xResSize - 131) + offset; + TM_DISK_X = xResOffset + (xResSize - 131) + offset; + INTERFACE_CLOCK_TM_X = xResOffset + (xResSize - 86) + offset; + LOCATION_NAME_TM_X = xResOffset + (xResSize - 92) + offset; + } // Create buttons CHECKF( CreateTEAMPanelButtons( ) ); @@ -5358,7 +5368,7 @@ BOOLEAN ShutdownTEAMPanel( ) if( fRenderRadarScreen == FALSE ) { // start rendering radar region again, - fRenderRadarScreen = TRUE; + fRenderRadarScreen = TRUE; // remove squad panel CreateDestroyMouseRegionsForSquadList( ); diff --git a/TileEngine/Radar Screen.cpp b/TileEngine/Radar Screen.cpp index b2bcb1a6..b27ce927 100644 --- a/TileEngine/Radar Screen.cpp +++ b/TileEngine/Radar Screen.cpp @@ -82,13 +82,14 @@ void InitRadarScreenCoords( ) { RADAR_WINDOW_STRAT_X = UI_BOTTOM_X + 1182; RADAR_WINDOW_STRAT_Y = UI_BOTTOM_Y + 9; + RADAR_WINDOW_TM_X = xResOffset + (xResSize - 97) + 223; } else { RADAR_WINDOW_STRAT_X = xResOffset + (xResSize - 97); RADAR_WINDOW_STRAT_Y = (SCREEN_HEIGHT - 107); + RADAR_WINDOW_TM_X = xResOffset + (xResSize - 97); } - RADAR_WINDOW_TM_X = xResOffset + (xResSize - 97); RADAR_WINDOW_SM_X = xResOffset + (xResSize - 97); RADAR_WINDOW_TM_Y = (INTERFACE_START_Y + 13); @@ -767,9 +768,13 @@ BOOLEAN CreateDestroyMouseRegionsForSquadList( void ) CHECKF(AddVideoObject(&VObjectDesc, &uiHandle)); GetVideoObject(&hHandle, uiHandle); - - BltVideoObject( guiSAVEBUFFER , hHandle, 0,(xResOffset + xResSize - 102 - 1), gsVIEWPORT_END_Y, VO_BLT_SRCTRANSPARENCY,NULL ); - RestoreExternBackgroundRect ((xResOffset + xResSize - 102 - 1), gsVIEWPORT_END_Y, 102,( INT16 ) ( SCREEN_HEIGHT - gsVIEWPORT_END_Y ) ); + INT32 xCoord = (xResOffset + xResSize - 102 - 1); + if (isWidescreenUI()) + { + xCoord += 224; + } + BltVideoObject( guiSAVEBUFFER, hHandle, 0, xCoord, gsVIEWPORT_END_Y, VO_BLT_SRCTRANSPARENCY,NULL ); + RestoreExternBackgroundRect(xCoord, gsVIEWPORT_END_Y, 102,( INT16 ) ( SCREEN_HEIGHT - gsVIEWPORT_END_Y ) ); for( sCounter = 0; sCounter < NUMBER_OF_SQUADS; sCounter++ ) { From 764d2f9389263f1b12b0b60acbe0e3973c1878db Mon Sep 17 00:00:00 2001 From: Marco Antonio Jaguaribe Costa Date: Sun, 28 May 2023 09:36:58 -0300 Subject: [PATCH 11/50] remove `int AStarPathFinder::CalcG(int*)` function not being called by anyone at the moment --- Tactical/PATHAI.cpp | 262 -------------------------------------------- 1 file changed, 262 deletions(-) diff --git a/Tactical/PATHAI.cpp b/Tactical/PATHAI.cpp index fa31d259..45c76ca8 100644 --- a/Tactical/PATHAI.cpp +++ b/Tactical/PATHAI.cpp @@ -1454,268 +1454,6 @@ INT16 AStarPathfinder::CalcAP(int const terrainCost, UINT8 const direction) return movementAPCost; } -int AStarPathfinder::CalcG(int* pPrevCost) -{ - //how much is admission to the next tile - if ( gfPathAroundObstacles == false) - { - return TRAVELCOST_FLAT; - } - - - int nextCost = gubWorldMovementCosts[ CurrentNode ][ direction ][ onRooftop ]; - *pPrevCost = nextCost; - - //if we are holding down shift and finding a direct path, count non obstacles as flat terrain - if (gfPlotDirectPath && nextCost < NOPASS && nextCost != 0) - { - return TRAVELCOST_FLAT; - } - - //performance: if nextCost is low then do not do many many if == checks - if ( nextCost >= TRAVELCOST_FENCE ) - { - //ATE: Check for differences from reality - // Is next cost an obstcale - if ( nextCost == TRAVELCOST_HIDDENOBSTACLE ) - { - if ( fPathingForPlayer ) - { - // Is this obstacle a hidden tile that has not been revealed yet? - BOOLEAN fHiddenStructVisible; - if( DoesGridNoContainHiddenStruct( CurrentNode, &fHiddenStructVisible ) ) - { - // Are we not visible, if so use terrain costs! - if ( !fHiddenStructVisible ) - { - // Set cost of terrain! - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ]; - } - } - } - } - else if ( nextCost == TRAVELCOST_NOT_STANDING ) - { - // for path plotting purposes, use the terrain value - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ]; - } - else if ( nextCost == TRAVELCOST_EXITGRID ) - { - if (gfPlotPathToExitGrid) - { - // replace with terrain cost so that we can plot path, otherwise is obstacle - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ]; - } - } - - //ddd: window. { check 2 conditions: 1. if next tile is a window and we will have to hump through it - //2. if current tile is a window and we should jump through the window to reach another tile - else if ( nextCost == TRAVELCOST_JUMPABLEWINDOW - || nextCost == TRAVELCOST_JUMPABLEWINDOW_N - || nextCost == TRAVELCOST_JUMPABLEWINDOW_W) - { - - // don't let anyone path diagonally through doors! - if (IsDiagonal(direction) == true) - { - return -1; - } - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];//? - - } - //ddd: window } - - else if ( nextCost == TRAVELCOST_FENCE && fNonFenceJumper ) - { - return -1; - } - else if ( IS_TRAVELCOST_DOOR( nextCost ) ) - { - // don't let anyone path diagonally through doors! - if (IsDiagonal(direction) == true) - { - return -1; - } - - INT32 iDoorGridNo = CurrentNode; - bool fDoorIsObstacleIfClosed = FALSE; - bool fDoorIsOpen = false; - switch( nextCost ) - { - case TRAVELCOST_DOOR_CLOSED_HERE: - fDoorIsObstacleIfClosed = TRUE; - iDoorGridNo = CurrentNode; - break; - case TRAVELCOST_DOOR_CLOSED_N: - fDoorIsObstacleIfClosed = TRUE; - iDoorGridNo = CurrentNode + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_CLOSED_W: - fDoorIsObstacleIfClosed = TRUE; - iDoorGridNo = CurrentNode + dirDelta[ WEST ]; - break; - case TRAVELCOST_DOOR_OPEN_HERE: - iDoorGridNo = CurrentNode; - break; - case TRAVELCOST_DOOR_OPEN_N: - iDoorGridNo = CurrentNode + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_OPEN_NE: - iDoorGridNo = CurrentNode + dirDelta[ NORTHEAST ]; - break; - case TRAVELCOST_DOOR_OPEN_E: - iDoorGridNo = CurrentNode + dirDelta[ EAST ]; - break; - case TRAVELCOST_DOOR_OPEN_SE: - iDoorGridNo = CurrentNode + dirDelta[ SOUTHEAST ]; - break; - case TRAVELCOST_DOOR_OPEN_S: - iDoorGridNo = CurrentNode + dirDelta[ SOUTH ]; - break; - case TRAVELCOST_DOOR_OPEN_SW: - iDoorGridNo = CurrentNode + dirDelta[ SOUTHWEST ]; - break; - case TRAVELCOST_DOOR_OPEN_W: - iDoorGridNo = CurrentNode + dirDelta[ WEST ]; - break; - case TRAVELCOST_DOOR_OPEN_NW: - iDoorGridNo = CurrentNode + dirDelta[ NORTHWEST ]; - break; - case TRAVELCOST_DOOR_OPEN_N_N: - iDoorGridNo = CurrentNode + dirDelta[ NORTH ] + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_OPEN_NW_N: - iDoorGridNo = CurrentNode + dirDelta[ NORTHWEST ] + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_OPEN_NE_N: - iDoorGridNo = CurrentNode + dirDelta[ NORTHEAST ] + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_OPEN_W_W: - iDoorGridNo = CurrentNode + dirDelta[ WEST ] + dirDelta[ WEST ]; - break; - case TRAVELCOST_DOOR_OPEN_SW_W: - iDoorGridNo = CurrentNode + dirDelta[ SOUTHWEST ] + dirDelta[ WEST ]; - break; - case TRAVELCOST_DOOR_OPEN_NW_W: - iDoorGridNo = CurrentNode + dirDelta[ NORTHWEST ] + dirDelta[ WEST ]; - break; - default: - break; - } - - if ( fPathingForPlayer && gpWorldLevelData[ iDoorGridNo ].ubExtFlags[0] & MAPELEMENT_EXT_DOOR_STATUS_PRESENT ) - { - // check door status - DOOR_STATUS* pDoorStatus = GetDoorStatus( iDoorGridNo ); - if (pDoorStatus) - { - fDoorIsOpen = (pDoorStatus->ubFlags & DOOR_PERCEIVED_OPEN) != 0; - } - else - { - // door destroyed? - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ]; - } - } - else - { - // check door structure - STRUCTURE* pDoorStructure = FindStructure( iDoorGridNo, STRUCTURE_ANYDOOR ); - if (pDoorStructure) - { - fDoorIsOpen = (pDoorStructure->fFlags & STRUCTURE_OPEN) != 0; - } - else - { - // door destroyed? - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ]; - } - } - - // now determine movement cost... if it hasn't been changed already - if ( IS_TRAVELCOST_DOOR( nextCost ) ) - { - if (fDoorIsOpen) - { - if (fDoorIsObstacleIfClosed) - { - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ]; - } - else - { - nextCost = TRAVELCOST_OBSTACLE; - } - } - else - { - if (fDoorIsObstacleIfClosed) - { - // door is closed and this should be an obstacle, EXCEPT if we are calculating - // a path for an enemy or NPC with keys - if ( fPathingForPlayer || ( pSoldier && (pSoldier->flags.uiStatusFlags & (SOLDIER_MONSTER | SOLDIER_ANIMAL) ) ) ) - { - nextCost = TRAVELCOST_OBSTACLE; - } - else - { - // have to check if door is locked and NPC does not have keys! - // This function has an inaccurate name. It is actually checking if the door has LOCK info. - DOOR* pDoor = FindDoorInfoAtGridNo( iDoorGridNo ); - if (pDoor) - { - if (!pDoor->fLocked || pSoldier->flags.bHasKeys) - { - // add to AP cost - //if (maxAPBudget) - { - fGoingThroughDoor = TRUE; - } - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ]; - } - else - { - nextCost = TRAVELCOST_OBSTACLE; - } - } - else - { - // The door is closed, so we still have to open it - fGoingThroughDoor = TRUE; - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ]; - } - } - } - else - { - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ]; - } - } - } - } - else if ( fNonSwimmer && ISWATER( gpWorldLevelData[ CurrentNode ].ubTerrainID)) - { - // creatures and animals can't go in water - nextCost = TRAVELCOST_OBSTACLE; - } - } - - // Apr. '96 - moved up be ahead of AP_Budget stuff - if ( nextCost >= NOPASS ) // || ( nextCost == TRAVELCOST_DOOR ) ) - { - return -1; - } - - // make water cost attractive for water to water paths - // Why? If a path through water gets you there sooner, you shouldn't need - // artificial inflation to figure that out. And if not, then get out of the water! - //if (bWaterToWater && ISWATER(nextCost) ) - //{ - // nextCost = EASYWATERCOST; - //} - - return nextCost; -}//end CalcG - int AStarPathfinder::CalcH() { if ( fCopyReachable ) From 2571979f8a224908414914f390230b2fe5308f3f Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Mon, 12 Jun 2023 14:47:24 +0300 Subject: [PATCH 12/50] Fix message box text drawing in 720p when map inventory is up (#168) Old 720p resolution had character inventory drawn over the message box so messages were hidden when inventory is up. New widescreen UI is similar to all the other higher resolutions where this is not the case anymore. --- Strategic/Map Screen Interface Bottom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Strategic/Map Screen Interface Bottom.cpp b/Strategic/Map Screen Interface Bottom.cpp index f2c1845f..0c5530ab 100644 --- a/Strategic/Map Screen Interface Bottom.cpp +++ b/Strategic/Map Screen Interface Bottom.cpp @@ -340,7 +340,7 @@ void RenderMapScreenInterfaceBottom( BOOLEAN fForceMapscreenFullRender ) // HEADROCK HAM 3.6: OK, let's always render this panel, as long as the team inventory screen isn't open. // sevenfm: improved r8524 fix to work with 1280x720 resolution //if (fMapScreenBottomDirty || ((!fShowInventoryFlag || iResolution > _1024x600) && fForceMapscreenFullRender)) - if ((!fShowInventoryFlag || iResolution > _1280x720) && (fMapScreenBottomDirty || fForceMapscreenFullRender)) + if ( (!fShowInventoryFlag || iResolution > _1024x600) && (fMapScreenBottomDirty || fForceMapscreenFullRender)) { // get and blt panel GetVideoObject(&hHandle, guiMAPBOTTOMPANEL ); From 82aa1a6f7d6ab5d2f765f532918873c4d6617ce1 Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Thu, 22 Jun 2023 15:59:27 +0300 Subject: [PATCH 13/50] Fix bug that prevented arms dealer inventory from restocking (#169) if (numTotalItems.count(usItemIndex) == 1 ) would prevent dealers to reorder items if their current stock was zero --- Tactical/Arms Dealer Init.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Tactical/Arms Dealer Init.cpp b/Tactical/Arms Dealer Init.cpp index 51f5643d..b7573fce 100644 --- a/Tactical/Arms Dealer Init.cpp +++ b/Tactical/Arms Dealer Init.cpp @@ -493,15 +493,20 @@ void DailyCheckOnItemQuantities() if ( itemsAreOnOrder.count( usItemIndex ) == 0 ) { ubMaxSupply = GetDealersMaxItemAmount( ubArmsDealer, usItemIndex ); - + UINT16 halfSupply = (UINT16)(ubMaxSupply / 2); + UINT16 itemsInStock = 0; + if ( numTotalItems.count(usItemIndex) > 0 ) + { + itemsInStock = numTotalItems[usItemIndex]; + } //if the qty on hand is half the desired amount or fewer - if(numTotalItems.count(usItemIndex) == 1 && numTotalItems[ usItemIndex ] <= (INT32)( ubMaxSupply / 2 ) ) + if( itemsInStock <= halfSupply ) { //determine if the item can be restocked (assume new, use items aren't checked for until the stuff arrives) if (ItemTransactionOccurs( ubArmsDealer, usItemIndex, DEALER_BUYING, FALSE )) { // figure out how many items to reorder (items are reordered an entire batch at a time) - ubNumItems = HowManyItemsToReorder( ubMaxSupply, numTotalItems[ usItemIndex ] ); + ubNumItems = HowManyItemsToReorder( ubMaxSupply, itemsInStock); #ifdef JA2UB //if the dealer is betty, and we are to ADD the stuff instantly if( ubArmsDealer == ARMS_DEALER_BETTY && fInstallyHaveItemsAppear && From 8d3e6fce4070adf6c79b0ae77cf6c4e249945d14 Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Mon, 26 Jun 2023 02:22:40 -0700 Subject: [PATCH 14/50] Change initial militia orders from STATIONARY to ONGUARD (#171) --- Tactical/Soldier Init List.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tactical/Soldier Init List.cpp b/Tactical/Soldier Init List.cpp index 15a4e002..785359da 100644 --- a/Tactical/Soldier Init List.cpp +++ b/Tactical/Soldier Init List.cpp @@ -1849,7 +1849,7 @@ void AddSoldierInitListMilitia( UINT8 ubNumGreen, UINT8 ubNumRegs, UINT8 ubNumEl else Assert(0); curr->pBasicPlacement->bTeam = MILITIA_TEAM; - curr->pBasicPlacement->bOrders = STATIONARY; + curr->pBasicPlacement->bOrders = ONGUARD; curr->pBasicPlacement->bAttitude = (INT8) Random( MAXATTITUDES ); // silversurfer: Replace body type. Militia tanks are not allowed. From fb03cba2fa38c5b95be093056f0d37f8e37226ef Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Tue, 4 Jul 2023 20:58:56 -0700 Subject: [PATCH 15/50] New feature: transport groups (#172) https://thepit.ja-galaxy-forum.com/index.php?t=msg&goto=365395 --- GameSettings.cpp | 9 + GameSettings.h | 7 + Laptop/history.cpp | 1 + Laptop/history.h | 1 + Strategic/Auto Resolve.cpp | 5 +- Strategic/CMakeLists.txt | 1 + Strategic/Game Event Hook.cpp | 6 + Strategic/Game Event Hook.h | 5 +- Strategic/Game Events.cpp | 1 + Strategic/Map Screen Interface Map.cpp | 22 + Strategic/PreBattle Interface.cpp | 38 +- Strategic/PreBattle Interface.h | 4 +- Strategic/Queen Command.cpp | 57 +- Strategic/Rebel Command.cpp | 62 +- Strategic/Rebel Command.h | 1 + Strategic/Reinforcement.cpp | 6 +- Strategic/Reinforcement.h | 2 +- Strategic/Strategic AI.cpp | 175 ++-- Strategic/Strategic AI.h | 4 +- Strategic/Strategic Movement.cpp | 13 +- Strategic/Strategic Movement.h | 4 + Strategic/Strategic Transport Groups.cpp | 1025 ++++++++++++++++++++++ Strategic/Strategic Transport Groups.h | 31 + Tactical/Interface Dialogue.cpp | 1 + Tactical/Item Types.h | 4 + Tactical/Rotting Corpses.cpp | 4 +- Tactical/interface Dialogue.h | 4 + Utils/Text.h | 2 + Utils/XML_Items.cpp | 22 +- Utils/_ChineseText.cpp | 7 + Utils/_DutchText.cpp | 7 + Utils/_EnglishText.cpp | 7 + Utils/_FrenchText.cpp | 7 + Utils/_GermanText.cpp | 7 + Utils/_ItalianText.cpp | 7 + Utils/_PolishText.cpp | 7 + Utils/_RussianText.cpp | 7 + 37 files changed, 1471 insertions(+), 102 deletions(-) create mode 100644 Strategic/Strategic Transport Groups.cpp create mode 100644 Strategic/Strategic Transport Groups.h diff --git a/GameSettings.cpp b/GameSettings.cpp index 8516a802..4ba69e16 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -248,6 +248,7 @@ void UpdateFeatureFlags() gGameExternalOptions.gfAllowSnow = gGameSettings.fFeatures[FF_ALLOW_SNOW]; gGameExternalOptions.fMiniEventsEnabled = gGameSettings.fFeatures[FF_MINI_EVENTS]; gGameExternalOptions.fRebelCommandEnabled = gGameSettings.fFeatures[FF_REBEL_COMMAND]; + gGameExternalOptions.fStrategicTransportGroupsEnabled = gGameSettings.fFeatures[FF_STRATEGIC_TRANSPORT_GROUPS]; } else { @@ -497,6 +498,7 @@ BOOLEAN LoadFeatureFlags() gGameSettings.fFeatures[FF_ALLOW_SNOW] = iniReader.ReadBoolean("JA2 Feature Flags", "FF_ALLOW_SNOW", TRUE, FALSE); gGameSettings.fFeatures[FF_MINI_EVENTS] = iniReader.ReadBoolean("JA2 Feature Flags", "FF_MINI_EVENTS", FALSE, FALSE); gGameSettings.fFeatures[FF_REBEL_COMMAND] = iniReader.ReadBoolean("JA2 Feature Flags", "FF_REBEL_COMMAND", FALSE, FALSE); + gGameSettings.fFeatures[FF_STRATEGIC_TRANSPORT_GROUPS] = iniReader.ReadBoolean("JA2 Feature Flags", "FF_STRATEGIC_TRANSPORT_GROUPS", FALSE, FALSE); } } catch(vfs::Exception) @@ -725,6 +727,7 @@ BOOLEAN SaveFeatureFlags() settings << "FF_ALLOW_SNOW = " << (gGameSettings.fFeatures[FF_ALLOW_SNOW] ? "TRUE" : "FALSE") << endl; settings << "FF_MINI_EVENTS = " << (gGameSettings.fFeatures[FF_MINI_EVENTS] ? "TRUE" : "FALSE") << endl; settings << "FF_REBEL_COMMAND = " << (gGameSettings.fFeatures[FF_REBEL_COMMAND] ? "TRUE" : "FALSE") << endl; + settings << "FF_STRATEGIC_TRANSPORT_GROUPS = " << (gGameSettings.fFeatures[FF_STRATEGIC_TRANSPORT_GROUPS] ? "TRUE" : "FALSE") << endl; try { @@ -2161,6 +2164,10 @@ void LoadGameExternalOptions() gGameExternalOptions.fAlternativeHelicopterFuelSystem = iniReader.ReadBoolean("Strategic Gameplay Settings","ALTERNATIVE_HELICOPTER_FUEL_SYSTEM", TRUE); gGameExternalOptions.fHelicopterPassengersCanGetHit = iniReader.ReadBoolean("Strategic Gameplay Settings","HELICOPTER_PASSENGERS_CAN_GET_HIT", TRUE); + gGameExternalOptions.fStrategicTransportGroupsDebug = iniReader.ReadBoolean("Strategic Gameplay Settings", "STRATEGIC_TRANSPORT_GROUPS_DEBUG", FALSE, FALSE); + gGameExternalOptions.fStrategicTransportGroupsEnabled = iniReader.ReadBoolean("Strategic Gameplay Settings", "STRATEGIC_TRANSPORT_GROUPS_ENABLED", FALSE); + gGameExternalOptions.iMaxSimultaneousTransportGroups = iniReader.ReadInteger("Strategic Gameplay Settings", "MAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", 5, 1, 10); + //################# Morale Settings ################## gGameExternalOptions.sMoraleModAppearance = iniReader.ReadInteger("Morale Settings","MORALE_MOD_APPEARANCE", 1, 0, 5); gGameExternalOptions.sMoraleModRefinement = iniReader.ReadInteger("Morale Settings","MORALE_MOD_REFINEMENT", 2, 0, 5); @@ -4187,6 +4194,8 @@ void LoadRebelCommandSettings() gRebelCommandSettings.iDisruptAsdDuration_Bonus_Nightops = iniReader.ReadInteger("Rebel Command Settings", "DISRUPT_ASD_DURATION_BONUS_NIGHTOPS", 48, 0, 255); gRebelCommandSettings.iDisruptAsdDuration_Bonus_Technician = iniReader.ReadInteger("Rebel Command Settings", "DISRUPT_ASD_DURATION_BONUS_TECHNICIAN", 48, 0, 255); + gRebelCommandSettings.iForgeTransportOrdersSuccessChance = iniReader.ReadInteger("Rebel Command Settings", "FORGE_TRANSPORT_ORDERS_SUCCESS_CHANCE", 50, 0, 100); + gRebelCommandSettings.iGetEnemyMovementTargetsSuccessChance = iniReader.ReadInteger("Rebel Command Settings", "STRATEGIC_INTEL_SUCCESS_CHANCE", 50, 0, 100); gRebelCommandSettings.iGetEnemyMovementTargetsDuration = iniReader.ReadInteger("Rebel Command Settings", "STRATEGIC_INTEL_DURATION", 72, 0, 255); gRebelCommandSettings.iGetEnemyMovementTargetsDuration_Bonus_Covert = iniReader.ReadInteger("Rebel Command Settings", "STRATEGIC_INTEL_DURATION_BONUS_COVERT", 48, 0, 255); diff --git a/GameSettings.h b/GameSettings.h index 8be6f7ca..8ca4097e 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -181,6 +181,7 @@ enum FF_ALLOW_SNOW, FF_MINI_EVENTS, FF_REBEL_COMMAND, + FF_STRATEGIC_TRANSPORT_GROUPS, NUM_FEATURE_FLAGS, }; @@ -1602,6 +1603,10 @@ typedef struct BOOLEAN fAlternativeHelicopterFuelSystem; BOOLEAN fHelicopterPassengersCanGetHit; + BOOLEAN fStrategicTransportGroupsDebug; + BOOLEAN fStrategicTransportGroupsEnabled; + INT8 iMaxSimultaneousTransportGroups; + UINT16 usHelicopterHoverCostOnGreenTile; UINT16 usHelicopterHoverCostOnRedTile; @@ -1878,6 +1883,8 @@ typedef struct UINT8 iDisruptAsdDuration_Bonus_Nightops; UINT8 iDisruptAsdDuration_Bonus_Technician; + INT8 iForgeTransportOrdersSuccessChance; + INT8 iGetEnemyMovementTargetsSuccessChance; UINT8 iGetEnemyMovementTargetsDuration; UINT8 iGetEnemyMovementTargetsDuration_Bonus_Covert; diff --git a/Laptop/history.cpp b/Laptop/history.cpp index 558029ca..6067098d 100644 --- a/Laptop/history.cpp +++ b/Laptop/history.cpp @@ -1259,6 +1259,7 @@ void ProcessHistoryTransactionString(STR16 pString, HistoryUnitPtr pHistory) case HISTORY_SLAY_MYSTERIOUSLY_LEFT: case HISTORY_WALDO: case HISTORY_HELICOPTER_REPAIR_STARTED: + case HISTORY_INTERCEPTED_TRANSPORT_GROUP: //swprintf( pString, pHistoryStrings[ pHistory->ubCode ], pHistory->ubSecondCode ); swprintf( pString, HistoryName[ pHistory->ubCode ].sHistory, pHistory->ubSecondCode ); break; diff --git a/Laptop/history.h b/Laptop/history.h index 6fecf8d2..5a0a3d63 100644 --- a/Laptop/history.h +++ b/Laptop/history.h @@ -113,6 +113,7 @@ enum{ HISTORY_MERC_KILLED_CHARACTER, HISTORY_WALDO, HISTORY_HELICOPTER_REPAIR_STARTED, + HISTORY_INTERCEPTED_TRANSPORT_GROUP, TEXT_NUM_HISTORY }; diff --git a/Strategic/Auto Resolve.cpp b/Strategic/Auto Resolve.cpp index c602e12c..04fa2d56 100644 --- a/Strategic/Auto Resolve.cpp +++ b/Strategic/Auto Resolve.cpp @@ -633,6 +633,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve1"); switch( GetEnemyEncounterCode() ) { case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: gpAR->ubPlayerDefenceAdvantage = 21; //Skewed to the player's advantage for convenience purposes. break; case ENEMY_INVASION_CODE: @@ -1745,6 +1746,7 @@ void RenderAutoResolve() swprintf( str, gpStrategicString[STR_AR_ATTACK_HEADER] ); break; case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: swprintf( str, gpStrategicString[STR_AR_ENCOUNTER_HEADER] ); break; case ENEMY_INVASION_CODE: @@ -6058,6 +6060,5 @@ BOOLEAN IndividualMilitiaInUse_AutoResolve( UINT32 aMilitiaId ) } } } - return FALSE; -} \ No newline at end of file +} diff --git a/Strategic/CMakeLists.txt b/Strategic/CMakeLists.txt index 8e15ba34..295ff41a 100644 --- a/Strategic/CMakeLists.txt +++ b/Strategic/CMakeLists.txt @@ -47,6 +47,7 @@ set(StrategicSrc "${CMAKE_CURRENT_SOURCE_DIR}/Strategic Status.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Strategic Town Loyalty.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/strategic town reputation.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/Strategic Transport Groups.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Strategic Turns.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/strategic.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/strategicmap.cpp" diff --git a/Strategic/Game Event Hook.cpp b/Strategic/Game Event Hook.cpp index 6c2ea428..6a64799a 100644 --- a/Strategic/Game Event Hook.cpp +++ b/Strategic/Game Event Hook.cpp @@ -48,6 +48,7 @@ #include "LuaInitNPCs.h" // added by Flugente #include "MiniEvents.h" #include "Rebel Command.h" + #include "interface Dialogue.h" #include "connect.h" @@ -668,6 +669,11 @@ BOOLEAN ExecuteStrategicEvent( STRATEGICEVENT *pEvent ) case EVENT_REBELCOMMAND: RebelCommand::HandleStrategicEvent(pEvent->uiParam); break; + + case EVENT_RETURN_TRANSPORT_GROUP: + // for this action, we only care about the groupid, which is in the event param + ExecuteStrategicAIAction(NPC_ACTION_RETURN_TRANSPORT_GROUP, 0, 0, pEvent->uiParam); + break; } gfPreventDeletionOfAnyEvent = fOrigPreventFlag; return TRUE; diff --git a/Strategic/Game Event Hook.h b/Strategic/Game Event Hook.h index 29e08bb5..fa9cacd9 100644 --- a/Strategic/Game Event Hook.h +++ b/Strategic/Game Event Hook.h @@ -154,6 +154,9 @@ enum EVENT_REBELCOMMAND, + EVENT_RETURN_TRANSPORT_GROUP, + EVENT_TRANSPORT_GROUP_DEFEATED, + NUMBER_OF_EVENT_TYPES_PLUS_ONE, NUMBER_OF_EVENT_TYPES = NUMBER_OF_EVENT_TYPES_PLUS_ONE - 1 }; @@ -217,4 +220,4 @@ void DeleteAllStrategicEvents(); // Flugente: return vector of all events of type ubCallbackID with time and param std::vector< std::pair > GetAllStrategicEventsOfType( UINT8 ubCallbackID ); -#endif \ No newline at end of file +#endif diff --git a/Strategic/Game Events.cpp b/Strategic/Game Events.cpp index 48ebbb29..d6fc6d9b 100644 --- a/Strategic/Game Events.cpp +++ b/Strategic/Game Events.cpp @@ -137,6 +137,7 @@ CHAR16 gEventName[NUMBER_OF_EVENT_TYPES_PLUS_ONE][40]={ L"ArmyFinishTraining", L"MiniEvent", L"ARC_Event", + L"ReturnTransportGroup", }; #endif diff --git a/Strategic/Map Screen Interface Map.cpp b/Strategic/Map Screen Interface Map.cpp index cfa3d83b..b709c3a0 100644 --- a/Strategic/Map Screen Interface Map.cpp +++ b/Strategic/Map Screen Interface Map.cpp @@ -46,6 +46,7 @@ #include "LuaInitNPCs.h" // added by Flugente #include "Game Event Hook.h" // added by Flugente #include "Rebel Command.h" + #include "Strategic Transport Groups.h" #include "Quests.h" #include "connect.h" @@ -841,6 +842,8 @@ void fillMapColoursForVisitedSectors(INT32(&colorMap)[ MAXIMUM_VALID_Y_COORDINAT } } + FillMapColoursForTransportGroups(colorMap); + if (RebelCommand::ShowEnemyMovementTargets()) { const auto targetColor = MAP_SHADE_LT_RED; @@ -8584,6 +8587,25 @@ void DetermineMapIntelData( INT32 asSectorZ ) } } + // transport groups + std::map map = GetTransportGroupSectorInfo(); + for (const auto iter : map) + { + CHAR16 str[128]; + + switch (iter.second) + { + case TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedGroup: + swprintf( str, gpStrategicString[STR_PB_TRANSPORT_GROUP]); + break; + + case TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedDestination: + swprintf( str, gpStrategicString[STR_PB_TRANSPORT_GROUP_EN_ROUTE]); + break; + } + AddIntelAndQuestMapDataForSector( SECTORX(iter.first), SECTORY(iter.first), MAP_SHADE_LT_YELLOW, -1, str, L"" ); + } + // uncovered terrorists we know of for ( int cnt = 0; cnt < 6; ++cnt ) { diff --git a/Strategic/PreBattle Interface.cpp b/Strategic/PreBattle Interface.cpp index 81909888..f05cf42e 100644 --- a/Strategic/PreBattle Interface.cpp +++ b/Strategic/PreBattle Interface.cpp @@ -47,6 +47,7 @@ #include "CampaignStats.h" // added by Flugente #include "militiasquads.h" // added by Flugente #include "SkillCheck.h" // added by Flugente + #include "Strategic Transport Groups.h" #ifdef JA2UB #include "ub_config.h" @@ -438,7 +439,7 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) } else if ( pBattleGroup && pBattleGroup->usGroupTeam != OUR_TEAM && NumNonPlayerTeamMembersInSector( pBattleGroup->ubSectorX, pBattleGroup->ubSectorY, MILITIA_TEAM ) > 0 ) { - SetEnemyEncounterCode( ENEMY_ENCOUNTER_CODE ); + SetEnemyEncounterCode( pBattleGroup->usGroupTeam == ENEMY_TEAM && pBattleGroup->pEnemyGroup->ubIntention == TRANSPORT ? TRANSPORT_INTERCEPT_CODE : ENEMY_ENCOUNTER_CODE ); } else if( GetEnemyEncounterCode() == ENTERING_ENEMY_SECTOR_CODE || GetEnemyEncounterCode() == ENEMY_ENCOUNTER_CODE || @@ -452,7 +453,8 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) GetEnemyEncounterCode() == CONCEALINSERTION_CODE || GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE || GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE || - GetEnemyEncounterCode() == BANDIT_ATTACK_CODE ) + GetEnemyEncounterCode() == BANDIT_ATTACK_CODE || + GetEnemyEncounterCode() == TRANSPORT_INTERCEPT_CODE ) { //use same code SetExplicitEnemyEncounterCode( GetEnemyEncounterCode() ); @@ -679,8 +681,24 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) { SetEnemyEncounterCode( ENEMY_ENCOUNTER_CODE ); + GROUP* pGroup = gpGroupList; + BOOLEAN encounteredTransportGroup = FALSE; + while (pGroup) + { + if (pGroup->usGroupTeam == ENEMY_TEAM && pGroup->pEnemyGroup->ubIntention == TRANSPORT && pGroup->ubSectorX == gpBattleGroup->ubSectorX && pGroup->ubSectorY == gpBattleGroup->ubSectorY && pGroup->ubSectorZ == gpBattleGroup->ubSectorZ) + { + encounteredTransportGroup = TRUE; + break; + } + + pGroup = pGroup->next; + } + if (encounteredTransportGroup) + { + SetEnemyEncounterCode( TRANSPORT_INTERCEPT_CODE ); + } // Flugente: no ambushes on an airdrop - if ( !fAirDrop ) + else if ( !fAirDrop ) { //Don't consider ambushes until the player has reached 25% (normal) progress if( gfHighPotentialForAmbush ) @@ -798,7 +816,9 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) } else { //Are enemies invading a town, or just encountered the player. - if( GetTownIdForSector( gubPBSectorX, gubPBSectorY ) ) + if (pBattleGroup && pBattleGroup->usGroupTeam == ENEMY_TEAM && pBattleGroup->pEnemyGroup->ubIntention == TRANSPORT) + SetEnemyEncounterCode( TRANSPORT_INTERCEPT_CODE ); + else if( GetTownIdForSector( gubPBSectorX, gubPBSectorY ) ) SetEnemyEncounterCode( ENEMY_INVASION_CODE ); //SAM sites not in towns will also be considered to be important else if( pSector->uiFlags & SF_SAM_SITE ) @@ -866,7 +886,7 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) } HideButton( giMapContractButton ); - if( GetEnemyEncounterCode() == ENEMY_ENCOUNTER_CODE ) + if( GetEnemyEncounterCode() == ENEMY_ENCOUNTER_CODE || GetEnemyEncounterCode() == TRANSPORT_INTERCEPT_CODE ) { //we know how many enemies are here, so until we leave the sector, we will continue to display the value. //the flag will get cleared when time advances after the fEnemyInSector flag is clear. @@ -955,6 +975,7 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) { case CREATURE_ATTACK_CODE: case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: case ENEMY_INVASION_CODE: case ENEMY_INVASION_AIRDROP_CODE: case BLOODCAT_ATTACK_CODE: @@ -1213,6 +1234,7 @@ void RenderPBHeader( INT32 *piX, INT32 *piWidth) swprintf( str, gpStrategicString[ STR_PB_ENEMYINVASION_HEADER ] ); break; case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: swprintf( str, gpStrategicString[ STR_PB_ENEMYENCOUNTER_HEADER ] ); break; case ENEMY_AMBUSH_CODE: @@ -2520,6 +2542,10 @@ void LogBattleResults( UINT8 ubVictoryCode) break; case CONCEALINSERTION_CODE: break; + case TRANSPORT_INTERCEPT_CODE: + AddHistoryToPlayersLog( HISTORY_INTERCEPTED_TRANSPORT_GROUP, 0, GetWorldTotalMin(), sSectorX, sSectorY ); + NotifyTransportGroupDefeated(); + break; } // Flugente: campaign stats @@ -2534,6 +2560,7 @@ void LogBattleResults( UINT8 ubVictoryCode) AddHistoryToPlayersLog( HISTORY_LOSTTOWNSECTOR, 0, GetWorldTotalMin(), sSectorX, sSectorY ); break; case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: AddHistoryToPlayersLog( HISTORY_LOSTBATTLE, 0, GetWorldTotalMin(), sSectorX, sSectorY ); break; case ENEMY_AMBUSH_CODE: @@ -2567,6 +2594,7 @@ void LogBattleResults( UINT8 ubVictoryCode) gCurrentIncident.usIncidentFlags |= INCIDENT_ATTACK_ENEMY; break; case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: case ENTERING_ENEMY_SECTOR_CODE: case CREATURE_ATTACK_CODE: case ENTERING_BLOODCAT_LAIR_CODE: diff --git a/Strategic/PreBattle Interface.h b/Strategic/PreBattle Interface.h index d8bc045e..78070486 100644 --- a/Strategic/PreBattle Interface.h +++ b/Strategic/PreBattle Interface.h @@ -47,6 +47,8 @@ enum BLOODCAT_ATTACK_CODE, // Flugente: like CREATURE_ATTACK_CODE, but with cats ZOMBIE_ATTACK_CODE, // Flugente: like CREATURE_ATTACK_CODE, but with zombies BANDIT_ATTACK_CODE, // Flugente: like CREATURE_ATTACK_CODE, but with bandits + + TRANSPORT_INTERCEPT_CODE, // rftr: like ENEMY_ENCOUNTER_CODE }; extern BOOLEAN gfAutoAmbush; @@ -112,4 +114,4 @@ enum }; void LogBattleResults( UINT8 ubVictoryCode); -#endif \ No newline at end of file +#endif diff --git a/Strategic/Queen Command.cpp b/Strategic/Queen Command.cpp index b964eed5..074ef6c3 100644 --- a/Strategic/Queen Command.cpp +++ b/Strategic/Queen Command.cpp @@ -42,6 +42,7 @@ #include "CampaignStats.h" // added by Flugente #include "ASD.h" // added by Flugente #include "Interface Panels.h" + #include "Strategic Transport Groups.h" #ifdef JA2BETAVERSION extern BOOLEAN gfClearCreatureQuest; @@ -98,7 +99,6 @@ void HandleBloodCatDeaths( SECTORINFO *pSector ); extern void Ensure_RepairedGarrisonGroup( GARRISON_GROUP **ppGarrison, INT32 *pGarraySize ); - void ValidateEnemiesHaveWeapons() { #ifdef JA2BETAVERSION @@ -613,6 +613,9 @@ BOOLEAN PrepareEnemyForSectorBattle() gfPendingNonPlayerTeam[ENEMY_TEAM] = FALSE; + // rftr: clear cached transport groups + ClearTransportGroupMap(); + if( gbWorldSectorZ > 0 ) return PrepareEnemyForUndergroundBattle(); @@ -640,9 +643,9 @@ BOOLEAN PrepareEnemyForSectorBattle() for( unsigned ubIndex = 0; ubIndex < ubDirNumber; ++ubIndex ) { - while ( NumMobileEnemiesInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ) ) && GetNonPlayerGroupInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), ENEMY_TEAM ) ) + while ( NumMobileEnemiesInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ) ) && GetNonPlayerGroupInSectorForReinforcement( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), ENEMY_TEAM ) ) { - pGroup = GetNonPlayerGroupInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), ENEMY_TEAM ); + pGroup = GetNonPlayerGroupInSectorForReinforcement( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), ENEMY_TEAM ); pGroup->ubPrevX = pGroup->ubSectorX; pGroup->ubPrevY = pGroup->ubSectorY; @@ -669,10 +672,23 @@ BOOLEAN PrepareEnemyForSectorBattle() HandleArrivalOfReinforcements( pGroup ); } + // for transport groups, track how many enemies of each type we're adding so we can update drops for them + if (pGroup->usGroupTeam == ENEMY_TEAM && pGroup->pEnemyGroup->ubIntention == TRANSPORT && pGroup->ubSectorX == gWorldSectorX && pGroup->ubSectorY == gWorldSectorY && !gbWorldSectorZ) + { + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ADMINISTRATOR, pGroup->pEnemyGroup->ubNumAdmins); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ARMY, pGroup->pEnemyGroup->ubNumTroops); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ELITE, pGroup->pEnemyGroup->ubNumElites); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ROBOT, pGroup->pEnemyGroup->ubNumRobots); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_JEEP, pGroup->pEnemyGroup->ubNumJeeps); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_TANK, pGroup->pEnemyGroup->ubNumTanks); + } + pGroup = pGroup->next; } } + UpdateTransportGroupInventory(); + ValidateEnemiesHaveWeapons(); UnPauseGame(); return ( ( BOOLEAN) ( gpBattleGroup->ubGroupSize > 0 ) ); @@ -852,6 +868,7 @@ BOOLEAN PrepareEnemyForSectorBattle() if ( pGroup->usGroupTeam == ENEMY_TEAM && !pGroup->fVehicle && pGroup->ubSectorX == gWorldSectorX && pGroup->ubSectorY == gWorldSectorY && !gbWorldSectorZ ) { //Process enemy group in sector. + const BOOLEAN isTransportGroup = pGroup->pEnemyGroup->ubIntention == TRANSPORT; if( sNumSlots > 0 ) { AssertGE(pGroup->pEnemyGroup->ubNumAdmins, pGroup->pEnemyGroup->ubAdminsInBattle); @@ -865,6 +882,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubAdminsInBattle += ubNumAdmins; ubTotalAdmins += ubNumAdmins; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ADMINISTRATOR, ubNumAdmins); } if( sNumSlots > 0 ) { //Add regular army forces. @@ -879,6 +899,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubTroopsInBattle += ubNumTroops; ubTotalTroops += ubNumTroops; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ARMY, ubNumTroops); } if( sNumSlots > 0 ) { //Add elite troops @@ -893,6 +916,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubElitesInBattle += ubNumElites; ubTotalElites += ubNumElites; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ELITE, ubNumElites); } if( sNumSlots > 0 ) { //Add robots @@ -907,6 +933,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubRobotsInBattle += ubNumRobots; ubTotalRobots += ubNumRobots; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ROBOT, ubNumRobots); } if( sNumSlots > 0 ) { //Add tanks @@ -921,6 +950,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubTanksInBattle += ubNumTanks; ubTotalTanks += ubNumTanks; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_TANK, ubNumTanks); } if ( sNumSlots > 0 ) { @@ -936,6 +968,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubJeepsInBattle += ubNumJeeps; ubTotalJeeps += ubNumJeeps; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_JEEP, ubNumJeeps); } //NOTE: //no provisions for profile troop leader or retreat groups yet. @@ -976,6 +1011,7 @@ BOOLEAN PrepareEnemyForSectorBattle() unsigned firstSlot = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; unsigned lastSlot = gTacticalStatus.Team[ ENEMY_TEAM ].bLastID; unsigned slotsAvailable = lastSlot-firstSlot+1; + while( pGroup && sNumSlots > 0 ) { if ( pGroup->usGroupTeam != OUR_TEAM && !pGroup->fVehicle && @@ -1088,6 +1124,7 @@ BOOLEAN PrepareEnemyForSectorBattle() } break; } + } // Flugente: instead of just crashing the game without any explanation to the user, ignore this issue if it still exists. @@ -1103,6 +1140,8 @@ BOOLEAN PrepareEnemyForSectorBattle() pGroup = pGroup->next; } + UpdateTransportGroupInventory(); + ValidateEnemiesHaveWeapons(); UnPauseGame(); @@ -2145,6 +2184,16 @@ void AddEnemiesToBattle( GROUP *pGroup, UINT8 ubStrategicInsertionCode, UINT8 ub UINT8 ubTotalSoldiers; UINT8 bDesiredDirection=0; + // while transport groups can't normally reinforce, this covers the case where a transport group enters a sector (via normal movement) + // where a battle is in progress. + if (pGroup && pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ADMINISTRATOR, ubNumAdmins); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ARMY, ubNumTroops); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ELITE, ubNumElites); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_JEEP, ubNumJeeps); + } + switch( ubStrategicInsertionCode ) { case INSERTION_CODE_NORTH: bDesiredDirection = SOUTHEAST; break; @@ -3594,3 +3643,5 @@ void CorrectTurncoatCount( INT16 sSectorX, INT16 sSectorY ) pGroup = pGroup->next; } } + + diff --git a/Strategic/Rebel Command.cpp b/Strategic/Rebel Command.cpp index f7d257ec..b5ce30aa 100644 --- a/Strategic/Rebel Command.cpp +++ b/Strategic/Rebel Command.cpp @@ -41,8 +41,10 @@ How to add a new admin action: - if effect applies outside of towns, add help text range band as appropriate to SetupAdminActionBox How to add a new mission: -- add to the RebelCommandAgentMissions enum in the header -- add strings to text files (szRebelCommandAgentMissionsText) +- add strings to text files (szRebelCommandText (trait bonuses), szRebelCommandAgentMissionsText (title/description)) +- add to the RebelCommandText and RebelCommandAgentMissions enums in the header +- add to the RebelCommandAgentMissionsText enums in the cpp +- add mission variables to GameSettings - add values to MissionHelpers::missionInfo table in SetupInfo() - add to valid check in HandleStrategicEvent() (allows advance from first event/prepare to second event/active effect) - add to SetupMissionAgentBox() (mission description and merc bonus text) @@ -91,6 +93,7 @@ Points of interest: #include "Strategic Mines.h" #include "Strategic Movement.h" #include "Strategic Town Loyalty.h" +#include "Strategic Transport Groups.h" #include "Structure Wrap.h" #include "Tactical Save.h" #include "Text.h" @@ -405,6 +408,7 @@ enum RebelCommandAgentMissionsText // keep this synced with szRebelCommandAgentM { MISSION_TEXT(DEEP_DEPLOYMENT) MISSION_TEXT(DISRUPT_ASD) + MISSION_TEXT(FORGE_TRANSPORT_ORDERS) MISSION_TEXT(GET_ENEMY_MOVEMENT_TARGETS) MISSION_TEXT(IMPROVE_LOCAL_SHOPS) MISSION_TEXT(REDUCE_STRATEGIC_DECISION_SPEED) @@ -2221,6 +2225,7 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) { case RCAM_DEEP_DEPLOYMENT: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_DEEP_DEPLOYMENT_TITLE]); break; case RCAM_DISRUPT_ASD: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_DISRUPT_ASD_TITLE]); break; + case RCAM_FORGE_TRANSPORT_ORDERS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_FORGE_TRANSPORT_ORDERS_TITLE]); break; case RCAM_GET_ENEMY_MOVEMENT_TARGETS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_GET_ENEMY_MOVEMENT_TARGETS_TITLE]); break; case RCAM_IMPROVE_LOCAL_SHOPS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_IMPROVE_LOCAL_SHOPS_TITLE]); break; case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_REDUCE_STRATEGIC_DECISION_SPEED_TITLE]); break; @@ -2241,6 +2246,7 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) { case RCAM_DEEP_DEPLOYMENT: missionDurationBase = gRebelCommandSettings.iDeepDeploymentDuration; break; case RCAM_DISRUPT_ASD: missionDurationBase = gRebelCommandSettings.iDisruptAsdDuration; break; + case RCAM_FORGE_TRANSPORT_ORDERS: missionDurationBase = 1; break; // instant effect case RCAM_GET_ENEMY_MOVEMENT_TARGETS: missionDurationBase = gRebelCommandSettings.iGetEnemyMovementTargetsDuration; break; case RCAM_IMPROVE_LOCAL_SHOPS: missionDurationBase = gRebelCommandSettings.iImproveLocalShopsDuration; break; case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: missionDurationBase = gRebelCommandSettings.iReduceStrategicDecisionSpeedDuration; break; @@ -2264,6 +2270,7 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) { case RCAM_DEEP_DEPLOYMENT: missionSuccessChanceBase = gRebelCommandSettings.iDeepDeploymentSuccessChance; break; case RCAM_DISRUPT_ASD: missionSuccessChanceBase = gRebelCommandSettings.iDisruptAsdSuccessChance; break; + case RCAM_FORGE_TRANSPORT_ORDERS: missionSuccessChanceBase = gRebelCommandSettings.iForgeTransportOrdersSuccessChance; break; case RCAM_GET_ENEMY_MOVEMENT_TARGETS: missionSuccessChanceBase = gRebelCommandSettings.iGetEnemyMovementTargetsSuccessChance; break; case RCAM_IMPROVE_LOCAL_SHOPS: missionSuccessChanceBase = gRebelCommandSettings.iImproveLocalShopsSuccessChance; break; case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: missionSuccessChanceBase = gRebelCommandSettings.iReduceStrategicDecisionSpeedSuccessChance; break; @@ -2284,6 +2291,7 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) { case RCAM_DEEP_DEPLOYMENT: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_DEEP_DEPLOYMENT_DESC]); break; case RCAM_DISRUPT_ASD: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_DISRUPT_ASD_DESC]); break; + case RCAM_FORGE_TRANSPORT_ORDERS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_FORGE_TRANSPORT_ORDERS_DESC]); break; case RCAM_GET_ENEMY_MOVEMENT_TARGETS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_GET_ENEMY_MOVEMENT_TARGETS_DESC]); break; case RCAM_IMPROVE_LOCAL_SHOPS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_IMPROVE_LOCAL_SHOPS_DESC]); break; case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_REDUCE_STRATEGIC_DECISION_SPEED_DESC]); break; @@ -2454,6 +2462,12 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) } } + case RCAM_FORGE_TRANSPORT_ORDERS: + { + // no special modifiers. included for completeness. + } + break; + case RCAM_GET_ENEMY_MOVEMENT_TARGETS: { // no special modifiers. included for completeness. @@ -2610,7 +2624,13 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) swprintf(sText, szRebelCommandText[RCT_MISSION_CANT_START_CONTRACT_EXPIRING]); } } - else if (agentIndex[index] == mercs.size() && rebelCommandSaveInfo.availableMissions[index] == RCAM_SEND_SUPPLIES_TO_TOWN) + else if (agentIndex[index] == mercs.size() && + ( + rebelCommandSaveInfo.availableMissions[index] == RCAM_SEND_SUPPLIES_TO_TOWN || + rebelCommandSaveInfo.availableMissions[index] == RCAM_FORGE_TRANSPORT_ORDERS + ) + + ) { canStartMission = FALSE; swprintf(sText, szRebelCommandText[RCT_MISSION_CANT_USE_REBEL_AGENT]); @@ -2847,6 +2867,14 @@ void PrepareMission(INT8 index) } break; + case RCAM_FORGE_TRANSPORT_ORDERS: + { + missionTitle = RCAMT_FORGE_TRANSPORT_ORDERS_TITLE; + missionSuccessChance = gRebelCommandSettings.iForgeTransportOrdersSuccessChance; + missionDuration = 12; + } + break; + case RCAM_GET_ENEMY_MOVEMENT_TARGETS: { missionTitle = RCAMT_GET_ENEMY_MOVEMENT_TARGETS_TITLE; @@ -3897,6 +3925,7 @@ void DailyUpdate() { if (i == RCAM_SOLDIER_BOUNTIES_KINGPIN && !(CheckFact(FACT_KINGPIN_INTRODUCED_SELF, 0) == TRUE && CheckFact(FACT_KINGPIN_DEAD, 0) == FALSE && CheckFact(FACT_KINGPIN_IS_ENEMY, 0) == FALSE && CurrentPlayerProgressPercentage() >= 30)) continue; else if (i == RCAM_DISRUPT_ASD && gGameExternalOptions.fASDActive == FALSE) continue; + else if (i == RCAM_FORGE_TRANSPORT_ORDERS && gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) continue; validMissions.insert(static_cast(i)); } @@ -4299,6 +4328,16 @@ void SetupInfo() {0, 0, 0, 0}, {0, MissionHelpers::DISRUPT_ASD_STEAL_FUEL, MissionHelpers::DISRUPT_ASD_DESTROY_RESERVES, 0} }); + //RCAM_FORGE_TRANSPORT_ORDERS + MissionHelpers::missionInfo.push_back( + { + {COVERT_NT }, + {-1}, + {0}, + {0.f}, + {0}, + {0} + }); //RCAM_GET_ENEMY_MOVEMENT_TARGETS MissionHelpers::missionInfo.push_back( { @@ -4902,6 +4941,7 @@ void HandleStrategicEvent(const UINT32 eventParam) { case RCAM_DEEP_DEPLOYMENT: case RCAM_DISRUPT_ASD: + case RCAM_FORGE_TRANSPORT_ORDERS: case RCAM_GET_ENEMY_MOVEMENT_TARGETS: case RCAM_IMPROVE_LOCAL_SHOPS: case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: @@ -4926,7 +4966,15 @@ void HandleStrategicEvent(const UINT32 eventParam) if (validMission) { const UINT32 activatedMissionParam = SerialiseMissionSecondEvent(evt1.sentGenericRebelAgent, evt1.mercProfileId, mission, extraBits); - AddStrategicEvent(EVENT_REBELCOMMAND, GetWorldTotalMin() + 60 * evt1.missionDurationInHours, activatedMissionParam); + if (mission == RCAM_FORGE_TRANSPORT_ORDERS) + { + // don't send a follow-up event for instant-result missions + } + else + { + AddStrategicEvent(EVENT_REBELCOMMAND, GetWorldTotalMin() + 60 * evt1.missionDurationInHours, activatedMissionParam); + missionMap.insert(std::make_pair(mission, activatedMissionParam)); + } if (!evt1.sentGenericRebelAgent) { @@ -4935,6 +4983,11 @@ void HandleStrategicEvent(const UINT32 eventParam) SOLDIERTYPE* pSoldier = MercPtrs[i]; if (pSoldier->ubProfile == evt1.mercProfileId) { + if (mission == RCAM_FORGE_TRANSPORT_ORDERS) + { + ForceDeployTransportGroup(SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)); + } + // mission successful! give some experience pts StatChange(pSoldier, LDRAMT, 20, FROM_SUCCESS); StatChange(pSoldier, WISDOMAMT, 15, FROM_SUCCESS); @@ -4943,7 +4996,6 @@ void HandleStrategicEvent(const UINT32 eventParam) } } - missionMap.insert(std::make_pair(mission, activatedMissionParam)); swprintf(msgBoxText, szRebelCommandText[RCT_MISSION_SUCCESS], szRebelCommandAgentMissionsText[evt1.missionId * 2]); swprintf(screenMsgText, szRebelCommandText[RCT_MISSION_SUCCESS], szRebelCommandAgentMissionsText[evt1.missionId * 2]); ScreenMsg(FONT_MCOLOR_LTGREEN, MSG_INTERFACE, screenMsgText); diff --git a/Strategic/Rebel Command.h b/Strategic/Rebel Command.h index f8c9bc24..22b6fd3b 100644 --- a/Strategic/Rebel Command.h +++ b/Strategic/Rebel Command.h @@ -78,6 +78,7 @@ enum RebelCommandAgentMissions RCAM_NONE = -1, RCAM_DEEP_DEPLOYMENT = 0, RCAM_DISRUPT_ASD, // only available if ASD enabled + RCAM_FORGE_TRANSPORT_ORDERS, RCAM_GET_ENEMY_MOVEMENT_TARGETS, // aka Strategic Intel RCAM_IMPROVE_LOCAL_SHOPS, RCAM_REDUCE_STRATEGIC_DECISION_SPEED, // aka Slower Strategic Decisions diff --git a/Strategic/Reinforcement.cpp b/Strategic/Reinforcement.cpp index c5077b8e..2adaa4f9 100644 --- a/Strategic/Reinforcement.cpp +++ b/Strategic/Reinforcement.cpp @@ -246,13 +246,13 @@ BOOLEAN ARMoveBestMilitiaManFromAdjacentSector(INT16 sMapX, INT16 sMapY) return TRUE; } -GROUP* GetNonPlayerGroupInSector( INT16 sMapX, INT16 sMapY, UINT8 usTeam ) +GROUP* GetNonPlayerGroupInSectorForReinforcement( INT16 sMapX, INT16 sMapY, UINT8 usTeam ) { GROUP *curr; curr = gpGroupList; while( curr ) { - if ( curr->ubSectorX == sMapX && curr->ubSectorY == sMapY && curr->usGroupTeam == usTeam && curr->ubGroupID ) + if ( curr->ubSectorX == sMapX && curr->ubSectorY == sMapY && curr->usGroupTeam == usTeam && curr->ubGroupID && (curr->usGroupTeam != ENEMY_TEAM || curr->pEnemyGroup->ubIntention != TRANSPORT) ) return curr; curr = curr->next; } @@ -280,7 +280,7 @@ UINT8 DoReinforcementAsPendingNonPlayer( INT16 sMapX, INT16 sMapY, UINT8 usTeam for( ubIndex = 0; ubIndex < ubDirNumber; ++ubIndex ) { - while ( (pGroup = GetNonPlayerGroupInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), usTeam )) != NULL ) + while ( (pGroup = GetNonPlayerGroupInSectorForReinforcement( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), usTeam )) != NULL ) { pGroup->ubPrevX = pGroup->ubSectorX; pGroup->ubPrevY = pGroup->ubSectorY; diff --git a/Strategic/Reinforcement.h b/Strategic/Reinforcement.h index 543ff041..e6017b51 100644 --- a/Strategic/Reinforcement.h +++ b/Strategic/Reinforcement.h @@ -12,6 +12,6 @@ UINT8 NumEnemiesInFiveSectors( INT16 sMapX, INT16 sMapY ); //For Tactical UINT8 DoReinforcementAsPendingNonPlayer( INT16 sMapX, INT16 sMapY, UINT8 usTeam ); void AddPossiblePendingMilitiaToBattle(); -GROUP* GetNonPlayerGroupInSector( INT16 sMapX, INT16 sMapY, UINT8 usTeam ); +GROUP* GetNonPlayerGroupInSectorForReinforcement( INT16 sMapX, INT16 sMapY, UINT8 usTeam ); #endif \ No newline at end of file diff --git a/Strategic/Strategic AI.cpp b/Strategic/Strategic AI.cpp index 569dfec6..c64d85ed 100644 --- a/Strategic/Strategic AI.cpp +++ b/Strategic/Strategic AI.cpp @@ -33,6 +33,9 @@ #include "interface dialogue.h" #include "ASD.h" // added by Flugente #include "Rebel Command.h" + #include "Game Event Hook.h" + #include "Strategic Town Loyalty.h" + #include "Strategic Transport Groups.h" #include "GameInitOptionsScreen.h" @@ -488,7 +491,6 @@ extern INT16 sWorldSectorLocationOfFirstBattle; void ReassignAIGroup( GROUP **pGroup ); void TransferGroupToPool( GROUP **pGroup ); -void SendGroupToPool( GROUP **pGroup ); //Simply orders all garrisons to take troops from the patrol groups and send the closest troops from them. Any garrison, //whom there request isn't fulfilled (due to lack of troops), will recieve their reinforcements from the queen (P3). @@ -2418,6 +2420,11 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Strategic5"); } } } + else if (pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + ProcessTransportGroupReachedDestination(pGroup); + return TRUE; + } else { //This is a floating group at his final destination... if( pGroup->pEnemyGroup->ubIntention != STAGING && pGroup->pEnemyGroup->ubIntention != REINFORCEMENTS ) @@ -3458,7 +3465,7 @@ void EvaluateQueenSituation() dEnemyGeneralsSpeedupFactor *= RebelCommand::GetStrategicDecisionSpeedModifier(); uiOffset += dEnemyGeneralsSpeedupFactor * (zDiffSetting[gGameOptions.ubDifficultyLevel].iBaseDelayInMinutesBetweenEvaluations + Random( zDiffSetting[gGameOptions.ubDifficultyLevel].iEvaluationDelayVariance )); - + // Check/update reinforcements pool if old behavior is enabled if ( !gfUnlimitedTroops && zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolIncrementDaysPerDifficultyLevel == 0 ) { @@ -3488,88 +3495,88 @@ void EvaluateQueenSituation() // Gradually promote any remaining admins into troops UpgradeAdminsToTroops(); - if( ( giRequestPoints <= 0 ) || ( ( giReinforcementPoints <= 0 ) && ( giReinforcementPool <= 0 ) ) ) - { //we either have no reinforcements or request for reinforcements. - return; - } + // consider deploying a transport group + ExecuteStrategicAIAction( NPC_ACTION_DEPLOY_TRANSPORT_GROUP, 0, 0 ); - // anv: only consider garrisons and patrols that can be reinforced - // otherwise unreinforcable groups will stall the rest, effectively breaking entire system - - Ensure_RepairedGarrisonGroup( &gGarrisonGroup, &giGarrisonArraySize ); /* added NULL fix, 2007-03-03, Sgt. Kolja */ - - for( i = 0; i < giGarrisonArraySize; i++ ) + // we either have reinforcements or a request for reinforcements + if (giRequestPoints > 0 && (giReinforcementPoints > 0 || giReinforcementPool > 0)) { - RecalculateGarrisonWeight( i ); - iWeight = gGarrisonGroup[ i ].bWeight; - if( iWeight > 0 ) + // anv: only consider garrisons and patrols that can be reinforced + // otherwise unreinforcable groups will stall the rest, effectively breaking entire system + + Ensure_RepairedGarrisonGroup( &gGarrisonGroup, &giGarrisonArraySize ); /* added NULL fix, 2007-03-03, Sgt. Kolja */ + + for( i = 0; i < giGarrisonArraySize; i++ ) { - if( !gGarrisonGroup[ i ].ubPendingGroupID && - EnemyPermittedToAttackSector( NULL, gGarrisonGroup[ i ].ubSectorID ) && - GarrisonRequestingMinimumReinforcements( i ) ) + RecalculateGarrisonWeight( i ); + iWeight = gGarrisonGroup[ i ].bWeight; + if( iWeight > 0 ) { - if( ReinforcementsApproved( i, &usDefencePoints ) ) + if( !gGarrisonGroup[ i ].ubPendingGroupID && + EnemyPermittedToAttackSector( NULL, gGarrisonGroup[ i ].ubSectorID ) && + GarrisonRequestingMinimumReinforcements( i ) ) { - iApplicableGarrisonIds[iApplicableGarrisons] = i; - iApplicableGarrisons++; - iApplicableRequestPoints += gGarrisonGroup[ i ].bWeight; + if( ReinforcementsApproved( i, &usDefencePoints ) ) + { + iApplicableGarrisonIds[iApplicableGarrisons] = i; + iApplicableGarrisons++; + iApplicableRequestPoints += gGarrisonGroup[ i ].bWeight; + } } } } - } - for( i = 0; i < giPatrolArraySize; i++ ) - { - RecalculatePatrolWeight( i ); - iWeight = gPatrolGroup[ i ].bWeight; - if( iWeight > 0 ) + for( i = 0; i < giPatrolArraySize; i++ ) { - if( !gPatrolGroup[ i ].ubPendingGroupID && PatrolRequestingMinimumReinforcements( i ) ) + RecalculatePatrolWeight( i ); + iWeight = gPatrolGroup[ i ].bWeight; + if( iWeight > 0 ) { - iApplicablePatrolIds[iApplicablePatrols] = i; - iApplicablePatrols++; - iApplicableRequestPoints += gPatrolGroup[ i ].bWeight; + if( !gPatrolGroup[ i ].ubPendingGroupID && PatrolRequestingMinimumReinforcements( i ) ) + { + iApplicablePatrolIds[iApplicablePatrols] = i; + iApplicablePatrols++; + iApplicableRequestPoints += gPatrolGroup[ i ].bWeight; + } } } - } - if( !iApplicableRequestPoints ) - { - return; - } - - //now randomly choose who gets the reinforcements. - // giRequestPoints is the combined sum of all the individual weights of all garrisons and patrols requesting reinforcements - //iRandom = Random( giRequestPoints ); - iRandom = Random( iApplicableRequestPoints ); - - iOrigRequestPoints = giRequestPoints; // debug only! - - //go through garrisons first - for( i = 0; i < iApplicableGarrisons; i++ ) - { - iSumOfAllWeights += iWeight; // debug only! - iWeight = gGarrisonGroup[ iApplicableGarrisonIds[i] ].bWeight; - if( iRandom < iWeight ) + if( iApplicableRequestPoints ) { - //This is the group that gets the reinforcements! - if ( SendReinforcementsForGarrison( iApplicableGarrisonIds[i] , usDefencePoints, NULL ) ) - return; - } - iRandom -= iWeight; - } + //now randomly choose who gets the reinforcements. + // giRequestPoints is the combined sum of all the individual weights of all garrisons and patrols requesting reinforcements + //iRandom = Random( giRequestPoints ); + iRandom = Random( iApplicableRequestPoints ); - //go through the patrol groups - for( i = 0; i < iApplicablePatrols; i++ ) - { - iSumOfAllWeights += iWeight; // debug only! - iWeight = gPatrolGroup[ iApplicablePatrolIds[i] ].bWeight; - if( iRandom < iWeight ) - { - //This is the group that gets the reinforcements! - if ( SendReinforcementsForPatrol( iApplicablePatrolIds[i], NULL ) ) - return; + iOrigRequestPoints = giRequestPoints; // debug only! + + //go through garrisons first + for( i = 0; i < iApplicableGarrisons; i++ ) + { + iSumOfAllWeights += iWeight; // debug only! + iWeight = gGarrisonGroup[ iApplicableGarrisonIds[i] ].bWeight; + if( iRandom < iWeight ) + { + //This is the group that gets the reinforcements! + if ( SendReinforcementsForGarrison( iApplicableGarrisonIds[i] , usDefencePoints, NULL ) ) + return; + } + iRandom -= iWeight; + } + + //go through the patrol groups + for( i = 0; i < iApplicablePatrols; i++ ) + { + iSumOfAllWeights += iWeight; // debug only! + iWeight = gPatrolGroup[ iApplicablePatrolIds[i] ].bWeight; + if( iRandom < iWeight ) + { + //This is the group that gets the reinforcements! + if ( SendReinforcementsForPatrol( iApplicablePatrolIds[i], NULL ) ) + return; + } + iRandom -= iWeight; + } } - iRandom -= iWeight; } ValidateWeights( 27 ); @@ -5125,7 +5132,7 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto if ( ubNumSoldiers ) { InitializeGroup(GROUP_TYPE_ATTACK, ubNumSoldiers, grouptroops[0], groupelites[0], grouprobots[0], groupjeeps[0], grouptanks[0], Random(10) < difficultyMod); - totalusedsoldiers += grouptroops[0] + groupelites[0] + grouprobots[0], grouptanks[0] + groupjeeps[0]; + totalusedsoldiers += grouptroops[0] + groupelites[0] + grouprobots[0] + grouptanks[0] + groupjeeps[0]; } pGroup = CreateNewEnemyGroupDepartingFromSector( SECTOR( gModSettings.ubSAISpawnSectorX, gModSettings.ubSAISpawnSectorY ), 0, grouptroops[0], groupelites[0], grouprobots[0], grouptanks[0], groupjeeps[0] ); @@ -5386,6 +5393,14 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto gubNumAwareBattles = zDiffSetting[gGameOptions.ubDifficultyLevel].iNumAwareBattles; break; + case NPC_ACTION_DEPLOY_TRANSPORT_GROUP: + DeployTransportGroup(); + break; + + case NPC_ACTION_RETURN_TRANSPORT_GROUP: + ReturnTransportGroup(option1); + break; + default: ScreenMsg( FONT_RED, MSG_DEBUG, L"QueenAI failed to handle action code %d.", usActionCode ); break; @@ -6403,8 +6418,14 @@ void UpgradeAdminsToTroops() // if there are any admins currently in this group if ( pGroup->pEnemyGroup->ubNumAdmins > 0 ) { + // skip transport groups + if (pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + pGroup = pGroup->next; + continue; + } // if it's a patrol group - if ( pGroup->pEnemyGroup->ubIntention == PATROL ) + else if ( pGroup->pEnemyGroup->ubIntention == PATROL ) { sPatrolIndex = FindPatrolGroupIndexForGroupID( pGroup->ubGroupID ); Assert( sPatrolIndex != -1 ); @@ -6508,9 +6529,17 @@ INT16 FindGarrisonIndexForGroupIDPending( UINT8 ubGroupID ) void TransferGroupToPool( GROUP **pGroup ) { - //Madd: unlimited reinforcements? - if ( !gfUnlimitedTroops ) - giReinforcementPool += (*pGroup)->ubGroupSize; + if ((*pGroup)->usGroupTeam == ENEMY_TEAM) + { + //Madd: unlimited reinforcements? + if ( !gfUnlimitedTroops ) + giReinforcementPool += (*pGroup)->ubGroupSize; + + AddStrategicAIResources(ASD_ROBOT, (*pGroup)->pEnemyGroup->ubNumRobots); + AddStrategicAIResources(ASD_JEEP, (*pGroup)->pEnemyGroup->ubNumJeeps); + AddStrategicAIResources(ASD_TANK, (*pGroup)->pEnemyGroup->ubNumTanks); + } + RemovePGroup( *pGroup ); *pGroup = NULL; diff --git a/Strategic/Strategic AI.h b/Strategic/Strategic AI.h index 77d9c3a6..5c7986af 100644 --- a/Strategic/Strategic AI.h +++ b/Strategic/Strategic AI.h @@ -38,6 +38,7 @@ BOOLEAN StrategicAILookForAdjacentGroups( GROUP *pGroup ); void RemoveGroupFromStrategicAILists( UINT8 ubGroupID ); void RecalculateSectorWeight( UINT8 ubSectorID ); void RecalculateGroupWeight( GROUP *pGroup ); +void SendGroupToPool( GROUP **pGroup ); BOOLEAN OkayForEnemyToMoveThroughSector( UINT8 ubSectorID ); BOOLEAN EnemyPermittedToAttackSector( GROUP **pGroup, UINT8 ubSectorID ); @@ -78,7 +79,8 @@ BOOLEAN PermittedToFillPatrolGroup( INT32 iPatrolID ); enum GROUP_TYPE { GROUP_TYPE_ATTACK, - GROUP_TYPE_PATROL + GROUP_TYPE_PATROL, + GROUP_TYPE_TRANSPORT }; void ASDInitializePatrolGroup(GROUP *pGroup); diff --git a/Strategic/Strategic Movement.cpp b/Strategic/Strategic Movement.cpp index 5b884b56..881783a1 100644 --- a/Strategic/Strategic Movement.cpp +++ b/Strategic/Strategic Movement.cpp @@ -50,6 +50,7 @@ #include "Creature Spreading.h" // added by Flugente #include "MilitiaIndividual.h" // added by Flugente #include "Rebel Command.h" + #include "Strategic Transport Groups.h" #include "MilitiaSquads.h" #include "Vehicles.h" @@ -3635,7 +3636,10 @@ INT32 GetSectorMvtTimeForGroup( UINT8 ubSector, UINT8 ubDirection, GROUP *pGroup dEnemyGeneralsSpeedupFactor = max( 0.75f, dEnemyGeneralsSpeedupFactor - gStrategicStatus.usVIPsLeft * gGameExternalOptions.fEnemyGeneralStrategicMovementSpeedBonus ); } - iBestTraverseTime = dEnemyGeneralsSpeedupFactor * iBestTraverseTime; + // rftr: transport groups move slower than normal + const FLOAT transportSpeedFactor = pGroup->pEnemyGroup->ubIntention == TRANSPORT ? 2.0f : 1.0f; + + iBestTraverseTime = dEnemyGeneralsSpeedupFactor * transportSpeedFactor * iBestTraverseTime; iBestTraverseTime = iBestTraverseTime * (100 + RebelCommand::GetHarriersSpeedPenalty(ubSector)) / 100; } @@ -3816,6 +3820,13 @@ void HandleArrivalOfReinforcements( GROUP *pGroup ) ResetMortarsOnTeamCount(); ResetNumSquadleadersInArmyGroup(); // added by SANDRO AddPossiblePendingEnemiesToBattle(); + + if (pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + // normally, transport groups can't reinforce, but this can be hit normally if a battle is occuring in a sector + // where a transport group is moving into. + UpdateTransportGroupInventory(); + } } else if ( pGroup->usGroupTeam == MILITIA_TEAM ) { diff --git a/Strategic/Strategic Movement.h b/Strategic/Strategic Movement.h index d8bdfacd..03f0d3ff 100644 --- a/Strategic/Strategic Movement.h +++ b/Strategic/Strategic Movement.h @@ -15,6 +15,7 @@ enum //enemy intentions, PATROL, //enemy is moving around determining safe areas. REINFORCEMENTS, //enemy group has intentions to fortify position at final destination. ASSAULT, //enemy is ready to fight anything they encounter. + TRANSPORT, //rftr: enemy is carrying out non-combat tasks, but still has an escort NUM_ENEMY_INTENTIONS }; @@ -109,6 +110,9 @@ typedef struct ENEMYGROUP #define GROUPFLAG_KNOWN_DIRECTION 0x00000080 #define GROUPFLAG_KNOWN_NUMBER 0x00000100 +// rftr: strategic transport group direction flag +#define GROUPFLAG_TRANSPORT_ENROUTE 0x00000200 + typedef struct GROUP { BOOLEAN fDebugGroup; //for testing purposes -- handled differently in certain cases. diff --git a/Strategic/Strategic Transport Groups.cpp b/Strategic/Strategic Transport Groups.cpp new file mode 100644 index 00000000..4451b5a6 --- /dev/null +++ b/Strategic/Strategic Transport Groups.cpp @@ -0,0 +1,1025 @@ +//#pragma optimize("",off) +/* +Strategic Transport Groups +by rftr + +Strategic transport groups are a type of enemy strategic group in addition to ATTACK and PATROL groups. The primary purpose +of transport groups is to function as a loot pinata for the player. However, they will generally be behind enemy lines, so +the player will have to seek them out. + +Behaviourally, transport groups will spawn at the AI's HQ and then travel to a friendly town. Once the group reaches its +destination, it will wait for a few hours before returning home, where it despawns. The AI will receive small bonuses +upon the group successfully reaching both its destination and HQ. + +A transport group always flags its members to drop everything they have regardless of the player's DROP_ALL setting. In +addition, transport groups will also be carrying supplies that the player may find useful. + +Transport group compositions will vary based on the player's progress, how many interceptions have been completed recently, +and the difficulty of the game. + +*/ +#include "Strategic Transport Groups.h" + +#include "ASD.h" +#include "Assignments.h" +#include "Campaign.h" +#include "Game Clock.h" +#include "Game Event Hook.h" +#include "GameSettings.h" +#include "Inventory Choosing.h" +#include "Map Screen Interface Map.h" +#include "message.h" +#include "Overhead.h" +#include "Overhead Types.h" +#include "Queen Command.h" +#include "random.h" +#include "Soldier Control.h" +#include "strategic.h" +#include "Strategic AI.h" +#include "strategicmap.h" +#include "Strategic Mines.h" +#include "Strategic Movement.h" +#include "Strategic Town Loyalty.h" + +#define TRANSPORT_GROUP_DEBUG(x, ...) if (gGameExternalOptions.fStrategicTransportGroupsDebug) {ScreenMsg(FONT_RED, MSG_INTERFACE, x, __VA_ARGS__);} + +// how many turncoats are required to monitor a town for transport groups? +#define ELITE_TURNCOAT_MONITOR_REQUIREMENT 1 +#define TROOP_TURNCOAT_MONITOR_REQUIREMENT 3 +#define ADMIN_TURNCOAT_MONITOR_REQUIREMENT 5 + +extern ARMY_GUN_CHOICE_TYPE gExtendedArmyGunChoices[SOLDIER_GUN_CHOICE_SELECTIONS][ARMY_GUN_LEVELS]; +extern ARMY_GUN_CHOICE_TYPE gArmyItemChoices[SOLDIER_GUN_CHOICE_SELECTIONS][MAX_ITEM_TYPES]; +extern BOOLEAN gfTownUsesLoyalty[MAX_TOWNS]; + +std::map> transportGroupIdToSoldierMap; +std::map transportGroupSectorInfo; + +void PopulateTransportGroup(UINT8& admins, UINT8& troops, UINT8& elites, UINT8& jeeps, UINT8& tanks, UINT8& robots, UINT8 progress, int difficulty, BOOLEAN trySpecialCase); + +BOOLEAN DeployTransportGroup() +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + return FALSE; + + if (giReinforcementPool <= 0) + return FALSE; + + const UINT8 difficulty = gGameOptions.ubDifficultyLevel; + + // is there a mine here? + std::vector mineSectorIds; + for (INT8 i = 0; i < NUM_TOWNS; ++i) + { + // skip towns that have no loyalty + if (!gfTownUsesLoyalty[i]) continue; + + // filter by TOWN ownership - skip contested towns on expert/insane + if (IsTownUnderCompleteControlByPlayer(i)) continue; + if ((difficulty == DIF_LEVEL_HARD || difficulty == DIF_LEVEL_INSANE) && IsTownUnderCompleteControlByEnemy(i) == FALSE) continue; + + // skip towns with a shut down mine + const INT8 mineIndex = GetMineIndexForTown(i); + if (mineIndex == -1) continue; + if (IsMineShutDown(mineIndex) == TRUE) continue; + + // filter by MINE ownership - for novice/experienced, as hard/insane would have ignored this town above + const INT16 mineSector = (GetMineSectorForTown(i)); + if (StrategicMap[mineSector].fEnemyControlled == FALSE) continue; + + mineSectorIds.push_back(STRATEGIC_INDEX_TO_SECTOR_INFO(mineSector)); + } + + for (int a = 0; a < mineSectorIds.size(); ++a) + { + TRANSPORT_GROUP_DEBUG(L"DeployTransportGroup valid town destination: %d (%d/%d)", mineSectorIds[a], SECTORX(mineSectorIds[a]), SECTORY(mineSectorIds[a])); + } + + // no valid destinations + if (mineSectorIds.size() == 0) return FALSE; + + INT8 transportGroupCount = 0; + GROUP* pGroup = gpGroupList; + std::vector> groupIds; + while (pGroup) + { + if (pGroup->usGroupTeam == ENEMY_TEAM && pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + groupIds.emplace_back(pGroup->ubGroupID, pGroup->ubSectorX, pGroup->ubSectorY); + transportGroupCount++; + } + pGroup = pGroup->next; + } + + for (int a = 0; a < groupIds.size(); ++a) + { + TRANSPORT_GROUP_DEBUG(L"DeployTransportGroup found existing transport groupid: %d at %d/%d", std::get<0>(groupIds[a]), std::get<1>(groupIds[a]), std::get<2>(groupIds[a])); + } + + // track recent transport group interceptions + const INT8 recentLossCount = min(5, GetAllStrategicEventsOfType(EVENT_TRANSPORT_GROUP_DEFEATED).size()); + + // if there are too many active transport groups, don't deploy any more + // maximum number of active groups is the number of valid destinations at queen decision time + const INT8 maxGroups = gGameExternalOptions.iMaxSimultaneousTransportGroups - recentLossCount; + if (transportGroupCount >= min(max(maxGroups, 0), mineSectorIds.size())) return FALSE; + + const UINT8 ubSectorID = (UINT8)mineSectorIds[Random(mineSectorIds.size())]; + + TRANSPORT_GROUP_DEBUG(L"DeployTransportGroup sending group to sectorId: %d (%d/%d)", ubSectorID, SECTORX(ubSectorID), SECTORY(ubSectorID)); + + UINT8 admins, troops, elites, robots, jeeps, tanks; + const UINT8 progress = min(125, HighestPlayerProgressPercentage() + recentLossCount * 5); + PopulateTransportGroup(admins, troops, elites, jeeps, tanks, robots, progress, difficulty, mineSectorIds.size() == 1); + + // varying transport group quality/compositions + pGroup = CreateNewEnemyGroupDepartingFromSector( SECTOR( gModSettings.ubSAISpawnSectorX, gModSettings.ubSAISpawnSectorY ), admins, troops, elites, robots, tanks, jeeps ); + + //Madd: unlimited reinforcements? + if ( !gfUnlimitedTroops ) + { + giReinforcementPool -= (admins + troops + elites + robots + jeeps + tanks); + + giReinforcementPool = max( giReinforcementPool, 0 ); + } + + MoveSAIGroupToSector( &pGroup, ubSectorID, EVASIVE, TRANSPORT ); + + pGroup->uiFlags |= GROUPFLAG_TRANSPORT_ENROUTE; + + return TRUE; +} + +BOOLEAN ForceDeployTransportGroup(UINT8 sectorId) +{ + UINT8 admins, troops, elites, robots, jeeps, tanks; + const INT8 recentLossCount = min(5, GetAllStrategicEventsOfType(EVENT_TRANSPORT_GROUP_DEFEATED).size()); + const UINT8 progress = min(125, HighestPlayerProgressPercentage() + recentLossCount * 5); + const UINT8 difficulty = gGameOptions.ubDifficultyLevel; + PopulateTransportGroup(admins, troops, elites, jeeps, tanks, robots, progress, difficulty, FALSE); + return TRUE; +} + +BOOLEAN ReturnTransportGroup(INT32 groupId) +{ + GROUP* pGroup = gpGroupList; + while (pGroup) + { + if (pGroup->ubGroupID == groupId) + { + MoveSAIGroupToSector( &pGroup, SECTOR( gModSettings.ubSAISpawnSectorX, gModSettings.ubSAISpawnSectorY ), EVASIVE, TRANSPORT ); + if (pGroup) + pGroup->uiFlags &= ~GROUPFLAG_TRANSPORT_ENROUTE; + break; + } + pGroup = pGroup->next; + } + + if (pGroup == nullptr) + { + TRANSPORT_GROUP_DEBUG(L"RETURN_TRANSPORT_GROUP failed to find groupid %d", groupId); + return FALSE; + } + + return TRUE; +} + +void FillMapColoursForTransportGroups(INT32(&colorMap)[MAXIMUM_VALID_Y_COORDINATE][MAXIMUM_VALID_X_COORDINATE]) +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + return; + + const auto debugColor = MAP_SHADE_LT_BLUE; + const auto targetColor = MAP_SHADE_LT_YELLOW; + const INT8 DETECTION_RANGE_SCOUT = 1; + const INT8 DETECTION_RANGE_RADIO = 3; + const INT8 DETECTION_RANGE_COVERT = 0; + GROUP* pGroup = gpGroupList; + transportGroupSectorInfo.clear(); + + enum class MonitoredSectorState { + Unmonitored, + Monitored, + GroupIncoming, + } monitoredSectorState; + + // build map of detection sectors + ranges + std::map, INT8> detectionMap; + std::map monitoredTowns; + for( INT16 i = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; i <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; i++ ) + { + if( MercPtrs[ i ]->bActive && + MercPtrs[ i ]->stats.bLife >= OKLIFE && + (MercPtrs[ i ]->bAssignment < ON_DUTY || MercPtrs[ i ]->bAssignment == GATHERINTEL) && + !MercPtrs[ i ]->flags.fMercAsleep) + { + if (gGameOptions.fNewTraitSystem) + { + if (HAS_SKILL_TRAIT(MercPtrs[i], SCOUTING_NT)) + { + detectionMap[std::pair(MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY)] = DETECTION_RANGE_SCOUT; + } + else if (HAS_SKILL_TRAIT(MercPtrs[i], RADIO_OPERATOR_NT) && MercPtrs[i]->CanUseRadio(FALSE)) + { + detectionMap[std::pair(MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY)] = DETECTION_RANGE_RADIO; + } + else if (HAS_SKILL_TRAIT(MercPtrs[i], COVERT_NT)) + { + if (MercPtrs[i]->bAssignment == GATHERINTEL) + { + detectionMap[std::pair(MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY)] = DETECTION_RANGE_COVERT; + monitoredTowns[GetTownIdForSector(MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY)] = MonitoredSectorState::Monitored; + } + } + } + } + } + + // turncoats in towns can detect incoming transport groups + for (int x = MINIMUM_VALID_X_COORDINATE; x <= MAXIMUM_VALID_X_COORDINATE; ++x) + { + for (int y = MINIMUM_VALID_Y_COORDINATE; y <= MAXIMUM_VALID_Y_COORDINATE; ++y) + { + const UINT8 townId = GetTownIdForSector(x, y); + if (townId == BLANK_SECTOR) continue; + + CorrectTurncoatCount(x, y); + const UINT16 adminTurncoats = NumTurncoatsOfClassInSector(x, y, SOLDIER_CLASS_ADMINISTRATOR); + const UINT16 troopTurncoats = NumTurncoatsOfClassInSector(x, y, SOLDIER_CLASS_ARMY); + const UINT16 eliteTurncoats = NumTurncoatsOfClassInSector(x, y, SOLDIER_CLASS_ELITE); + + if (gGameExternalOptions.fStrategicTransportGroupsDebug + || (adminTurncoats >= ADMIN_TURNCOAT_MONITOR_REQUIREMENT) + || (troopTurncoats >= TROOP_TURNCOAT_MONITOR_REQUIREMENT) + || (eliteTurncoats >= ELITE_TURNCOAT_MONITOR_REQUIREMENT)) monitoredTowns[townId] = MonitoredSectorState::Monitored; + } + } + + // colour all groups + while (pGroup) + { + if (pGroup->usGroupTeam == ENEMY_TEAM) + { + const UINT8 intention = pGroup->pEnemyGroup->ubIntention; + if (intention == TRANSPORT ) + { + // check if current location is known + const INT16 gx = pGroup->ubSectorX; + const INT16 gy = pGroup->ubSectorY; + for (const auto key : detectionMap) + { + const std::pair sector = key.first; + const INT8 range = key.second; + + const INT8 dist = abs((gx - sector.first)) + abs((gy - sector.second)); + if (dist <= range) + { + colorMap[pGroup->ubSectorY-1][pGroup->ubSectorX-1] = targetColor; + transportGroupSectorInfo[SECTOR(pGroup->ubSectorX, pGroup->ubSectorY)] = TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedGroup; + } + } + + // turncoats reveal their group's location + if (pGroup->pEnemyGroup->ubNumAdmins_Turncoat >= ADMIN_TURNCOAT_MONITOR_REQUIREMENT + || pGroup->pEnemyGroup->ubNumTroops_Turncoat >= TROOP_TURNCOAT_MONITOR_REQUIREMENT + || pGroup->pEnemyGroup->ubNumElites_Turncoat >= ELITE_TURNCOAT_MONITOR_REQUIREMENT) + { + colorMap[pGroup->ubSectorY-1][pGroup->ubSectorX-1] = targetColor; + transportGroupSectorInfo[SECTOR(pGroup->ubSectorX, pGroup->ubSectorY)] = TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedGroup; + } + + // check if target location is monitored + WAYPOINT* wp = pGroup->pWaypoints; + + while (wp) + { + if (wp->next == nullptr) + break; + + wp = wp->next; + } + + if (wp == nullptr) + { + // ignore this group - it doesn't have a waypoint (?) + continue; + } + + const UINT8 townId = GetTownIdForSector(wp->x, wp->y); + if (monitoredTowns.find(townId) != monitoredTowns.end() && monitoredTowns[townId] == MonitoredSectorState::Monitored) + { + monitoredTowns[townId] = MonitoredSectorState::GroupIncoming; + } + + // debug: colour all group locations + if (gGameExternalOptions.fStrategicTransportGroupsDebug) + { + colorMap[pGroup->ubSectorY-1][pGroup->ubSectorX-1] = debugColor; + } + } + } + + pGroup = pGroup->next; + } + + // color all monitored towns if there's a transport group en route + for (int x = MINIMUM_VALID_X_COORDINATE; x <= MAXIMUM_VALID_X_COORDINATE; ++x) + { + for (int y = MINIMUM_VALID_Y_COORDINATE; y <= MAXIMUM_VALID_Y_COORDINATE; ++y) + { + const UINT8 townId = GetTownIdForSector(x, y); + if (monitoredTowns.find(townId) != monitoredTowns.end() && monitoredTowns[townId] == MonitoredSectorState::GroupIncoming) + { + colorMap[y-1][x-1] = targetColor; + transportGroupSectorInfo[SECTOR(x, y)] = TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedDestination; + } + } + } + +} + +void ProcessTransportGroupReachedDestination(GROUP* pGroup) +{ + const UINT8 difficulty = gGameOptions.ubDifficultyLevel; + + // just arrived, let's go home + if (pGroup->ubSectorX != gModSettings.ubSAISpawnSectorX && pGroup->ubSectorY != gModSettings.ubSAISpawnSectorY) + { + pGroup->ubSectorIDOfLastReassignment = (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ); + + // global loyalty loss + INT32 loyaltyLoss = 0; + switch (difficulty) + { + case DIF_LEVEL_EASY: loyaltyLoss = 0; break; + case DIF_LEVEL_MEDIUM: loyaltyLoss = 0; break; + case DIF_LEVEL_HARD: loyaltyLoss = -100; break; + case DIF_LEVEL_INSANE: loyaltyLoss = -250; break; + } + + AffectAllTownsLoyaltyByDistanceFrom(loyaltyLoss, pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ); + + // queue up return home order + AddStrategicEvent(EVENT_RETURN_TRANSPORT_GROUP, GetWorldTotalMin() + 60 * 6, pGroup->ubGroupID); + } + else + { + // asd income injection and bonus update + if (gGameExternalOptions.fASDActive) + { + INT32 moneyAmt = 0; + INT32 fuelAmt = 0; + + switch (difficulty) + { + case DIF_LEVEL_EASY: + moneyAmt = 0; + fuelAmt = 0; + break; + + case DIF_LEVEL_MEDIUM: + moneyAmt = gGameExternalOptions.gASDResource_Cost[ASD_JEEP] * 0.25f; + fuelAmt = gGameExternalOptions.gASDResource_Fuel_Jeep * 0.25f; + break; + + case DIF_LEVEL_HARD: + moneyAmt = gGameExternalOptions.gASDResource_Cost[ASD_JEEP] * 0.5f; + fuelAmt = gGameExternalOptions.gASDResource_Fuel_Jeep * 0.5f; + break; + + case DIF_LEVEL_INSANE: + moneyAmt = gGameExternalOptions.gASDResource_Cost[ASD_TANK]; + fuelAmt = gGameExternalOptions.gASDResource_Fuel_Tank; + break; + } + + AddStrategicAIResources(ASD_MONEY, moneyAmt); + AddStrategicAIResources(ASD_FUEL, fuelAmt); + UpdateASD(); + } + + // reinforcement pool increase + if (!gfUnlimitedTroops) + { + INT32 poolAmt = 0; + switch (difficulty) + { + case DIF_LEVEL_EASY: poolAmt = 0; break; + case DIF_LEVEL_MEDIUM: poolAmt = 10; break; + case DIF_LEVEL_HARD: poolAmt = 15; break; + case DIF_LEVEL_INSANE: poolAmt = 40; break; + } + + giReinforcementPool += poolAmt; + } + + // successfully returned home. give the strategic ai some rewards! + SendGroupToPool(&pGroup); + + if (difficulty != DIF_LEVEL_EASY) + { + // immediately do a queen evaluation + DeleteAllStrategicEventsOfType(EVENT_EVALUATE_QUEEN_SITUATION); + EvaluateQueenSituation(); + } + } +} + +void UpdateTransportGroupInventory() +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + return; + + const int firstSlot = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; + const int lastSlot = gTacticalStatus.Team[ ENEMY_TEAM ].bLastID; + const UINT8 progress = CurrentPlayerProgressPercentage(); + + enum ItemTypes + { + GAS_CANS, + MEDICAL_FIRSTAIDKITS, + MEDICAL_MEDKITS, + MEDICAL_OTHER, + TOOL_KITS, + BACKPACKS, + RADIOS, + ATTACHMENTS, + CAMO_KITS, + MISC, + GRENADE_THROWN, + GUNS, + AMMO_BOXES, + GRENADELAUNCHERS, + ROCKETLAUNCHERS, + + + TRANSPORT_LOOT_START = GAS_CANS, + TRANSPORT_LOOT_END = ROCKETLAUNCHERS, + }; + + std::map> itemMap; + + // item cache build + { + // let's be nice to the player and only drop ammo for guns their mercs have in inventory + std::set playerCalibres; + for (INT16 i = gTacticalStatus.Team[OUR_TEAM].bFirstID; i <= gTacticalStatus.Team[OUR_TEAM].bLastID; i++) + { + if (MercPtrs[i]->bActive && !(MercPtrs[i]->flags.uiStatusFlags & SOLDIER_VEHICLE)) + { + for (int j = 0 ; j < MercPtrs[i]->inv.size(); ++j) + { + OBJECTTYPE& obj = MercPtrs[i]->inv[j]; + if (obj.exists() + && Item[obj.usItem].usItemClass == IC_GUN) + { + playerCalibres.insert(Weapon[Item[obj.usItem].ubClassIndex].ubCalibre); + } + } + } + } + + for (UINT16 i = 0; i < gMAXITEMS_READ; ++i) + { + if (!ItemIsLegal(i, TRUE)) continue; + if ((Item[i].usItemClass & IC_AMMO) == 0 && (Item[i].iTransportGroupMaxProgress == 0 || Item[i].iTransportGroupMinProgress > progress || progress > Item[i].iTransportGroupMaxProgress)) continue; + + if (Item[i].medical) + { + if (Item[i].firstaidkit) itemMap[MEDICAL_FIRSTAIDKITS].push_back(i); + else if (Item[i].medicalkit) itemMap[MEDICAL_MEDKITS].push_back(i); + else itemMap[MEDICAL_OTHER].push_back(i); + } + else if (Item[i].gascan) itemMap[GAS_CANS].push_back(i); + else if (Item[i].toolkit) itemMap[TOOL_KITS].push_back(i); + else if (HasItemFlag(i, RADIO_SET)) itemMap[RADIOS].push_back(i); + else if (Item[i].usItemClass & IC_GRENADE) + { + if (Item[i].glgrenade == 0 + && Item[i].attachmentclass != AC_GRENADE // not for a grenade launcher + && Item[i].attachmentclass != AC_ROCKET) // not for a rocket launcher + itemMap[GRENADE_THROWN].push_back(i); + } + else if (Item[i].usItemClass & IC_LBEGEAR) + { + if (LoadBearingEquipment[Item[i].ubClassIndex].lbeClass == BACKPACK && !HasItemFlag(i, RADIO_SET)) // make sure radios don't get added here + { + itemMap[BACKPACKS].push_back(i); + } + } + else if (Item[i].camouflagekit) itemMap[CAMO_KITS].push_back(i); + else if (Item[i].usItemClass & IC_MISC) + { + switch (Item[i].attachmentclass) + { + case AC_BIPOD: + case AC_MUZZLE: + case AC_LASER: + case AC_SIGHT: + case AC_SCOPE: + case AC_FOREGRIP: + itemMap[ATTACHMENTS].push_back(i); + break; + default: + itemMap[MISC].push_back(i); + break; + } + } + else if (Item[i].usItemClass & IC_AMMO) + { + if (Magazine[Item[i].ubClassIndex].ubMagType == AMMO_BOX && playerCalibres.find(Magazine[Item[i].ubClassIndex].ubCalibre) != playerCalibres.end()) + { + if (Item[i].ubCoolness <= ((progress+5) / 10)+2) + { + itemMap[AMMO_BOXES].push_back(i); + } + } + } + else if (Item[i].usItemClass & IC_GUN) + { + itemMap[GUNS].push_back(i); + } + else if (Item[i].grenadelauncher) + { + itemMap[GRENADELAUNCHERS].push_back(i); + } + else if (Item[i].rocketlauncher) + { + itemMap[ROCKETLAUNCHERS].push_back(i); + } + } + } + + auto addItemToInventory = [](SOLDIERTYPE* pSoldier, UINT16 itemId, UINT8 amount) + { + OBJECTTYPE itemToAdd; + CreateItems(itemId, 100, amount, &itemToAdd); + + itemToAdd.fFlags &= ~OBJECT_UNDROPPABLE; + + if (FitsInSmallPocket(&itemToAdd)) + { + for(INT8 i = SMALLPOCKSTART; i < SMALLPOCKFINAL; i++ ) + { + if( pSoldier->inv[ i ].exists() == false && !(pSoldier->inv[ i ].fFlags & OBJECT_NO_OVERWRITE) ) + { + pSoldier->inv[ i ] = itemToAdd; + break; + } + } + } + else + { + for(INT8 i = BIGPOCKSTART; i < BIGPOCKFINAL; i++ ) + { //no space free in small pockets, so put it into a large pocket. + if( pSoldier->inv[ i ].exists() == false && !(pSoldier->inv[ i ].fFlags & OBJECT_NO_OVERWRITE) ) + { + pSoldier->inv[ i ] = itemToAdd; + break; + } + } + } + }; + + // cache the initial jeep count of every group we find + std::map cachedGroupJeepCount; + for (int slot = firstSlot; (slot <= lastSlot); ++slot) + { + SOLDIERTYPE* pSoldier = &Menptr[slot]; + + const std::map>::iterator groupIter = transportGroupIdToSoldierMap.find(pSoldier->ubGroupID); + if (groupIter != transportGroupIdToSoldierMap.end()) + { + // let's find out if this group is coming home or still outgoing to its target destination + GROUP* pGroup = gpGroupList; + BOOLEAN outgoing = FALSE; + while (pGroup) + { + if (pGroup->ubGroupID == groupIter->first) + { + outgoing = pGroup->uiFlags & GROUPFLAG_TRANSPORT_ENROUTE; + break; + } + pGroup = pGroup->next; + } + + // found a matching transport groupid + std::map::iterator soldierClassIter = groupIter->second.find(SOLDIER_CLASS_JEEP); + if (cachedGroupJeepCount.find(groupIter->first) == cachedGroupJeepCount.end()) + { + cachedGroupJeepCount[groupIter->first] = soldierClassIter == groupIter->second.end() ? 0 : groupIter->second[SOLDIER_CLASS_JEEP]; + } + + if (soldierClassIter != groupIter->second.end() && cachedGroupJeepCount.find(groupIter->first) != cachedGroupJeepCount.end() && cachedGroupJeepCount[groupIter->first] > 0) + { + TRANSPORT_GROUP_DEBUG(L"Found groupid[%d] with admin[%d] troop[%d] elite[%d] jeep[%d]", groupIter->first, groupIter->second[SOLDIER_CLASS_ADMINISTRATOR], groupIter->second[SOLDIER_CLASS_ARMY], groupIter->second[SOLDIER_CLASS_ELITE], groupIter->second[SOLDIER_CLASS_JEEP]); + // this group has a jeep in it! + // only jeeps carry things + // but give a little extra, since the jeep exploding can outright destroy things + if (pSoldier->ubSoldierClass == SOLDIER_CLASS_JEEP) + { + //if (outgoing) + { + // en route to target destination - carrying ammo, supplies, etc + for (int i = TRANSPORT_LOOT_START; i <= TRANSPORT_LOOT_END; ++i) + { + const ItemTypes itemType = static_cast(i); + if (itemMap[itemType].size() > 0) + { + const UINT16 id = itemMap[itemType][Random(itemMap[itemType].size())]; + switch (itemType) + { + case BACKPACKS: + case RADIOS: + case MISC: + case AMMO_BOXES: + // intentionally do nothing + break; + + case GAS_CANS: + addItemToInventory(pSoldier, id, 1); + break; + + case MEDICAL_MEDKITS: + case MEDICAL_OTHER: + case TOOL_KITS: + addItemToInventory(pSoldier, id, 2); + break; + + case MEDICAL_FIRSTAIDKITS: + addItemToInventory(pSoldier, id, 10); + break; + + case GRENADE_THROWN: + addItemToInventory(pSoldier, id, 12); + break; + + case GUNS: + { + for (int loop = 0; loop < 3; ++loop) + { + const UINT16 gunId = itemMap[itemType][Random(itemMap[itemType].size())]; + addItemToInventory(pSoldier, gunId, 1); + + UINT16 ammoId = RandomMagazine(gunId, 0, 100, SOLDIER_CLASS_ELITE); + if (ammoId == 0) continue; // no ammo matches, skip + + BOOLEAN convertedToBox = FALSE; + for (INT32 itemId = 0; itemId < (INT32)gMAXITEMS_READ; ++itemId) + { + if( ItemIsLegal(itemId) + && Item[itemId].usItemClass == IC_AMMO + && Magazine[Item[itemId].ubClassIndex].ubMagType == AMMO_BOX + && Magazine[Item[itemId].ubClassIndex].ubCalibre == Magazine[Item[ammoId].ubClassIndex].ubCalibre + && Magazine[Item[itemId].ubClassIndex].ubAmmoType == Magazine[Item[ammoId].ubClassIndex].ubAmmoType) + { + // replace mag with box + convertedToBox = TRUE; + ammoId = itemId; + break; + } + } + addItemToInventory(pSoldier, ammoId, convertedToBox ? 2 : 10); + } + } + break; + + case GRENADELAUNCHERS: + { + addItemToInventory(pSoldier, id, 1); + + const UINT16 launchableId = PickARandomLaunchable(id); + if (launchableId == 0) continue; // no launchable matches, skip + + addItemToInventory(pSoldier, launchableId, 8); + } + break; + + case ROCKETLAUNCHERS: + { + addItemToInventory(pSoldier, id, Item[id].singleshotrocketlauncher ? 3 : 1); + + const UINT16 launchableId = PickARandomLaunchable(id); + if (launchableId == 0) continue; // no launchable matches, skip + + addItemToInventory(pSoldier, launchableId, 3); + } + break; + + case ATTACHMENTS: + for (int loop = 0; loop < 5; ++loop) + { + const UINT16 attachmentId = itemMap[itemType][Random(itemMap[itemType].size())]; + addItemToInventory(pSoldier, attachmentId, 2); + } + break; + + case CAMO_KITS: + addItemToInventory(pSoldier, id, 6); + break; + + default: + TRANSPORT_GROUP_DEBUG(L"Warning: ignoring unhandled transport group loot type: %d", itemType); + // nothing! + break; + } + } + } + } + //else // returning home + { + // I can't really think of a good different loot set for returning transport groups, so we'll have the same loot + // regardless of whether the group is outgoing or incoming. I'll keep the in/out flag in case that changes + } + + transportGroupIdToSoldierMap[pSoldier->ubGroupID][SOLDIER_CLASS_JEEP]--; + } + else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR + || pSoldier->ubSoldierClass == SOLDIER_CLASS_ARMY + || pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE) + { + // jeep is carrying most things, so soldiers just have ammo + if (itemMap[AMMO_BOXES].size() > 0) + { + const UINT16 ammoId = itemMap[AMMO_BOXES][Random(itemMap[AMMO_BOXES].size())]; + addItemToInventory(pSoldier, ammoId, 1); + } + + if (itemMap[BACKPACKS].size() > 0 && UsingNewInventorySystem()) + { + OBJECTTYPE obj; + CreateItem(itemMap[BACKPACKS][Random(itemMap[BACKPACKS].size())], 100, &obj); + obj.fFlags |= OBJECT_UNDROPPABLE; + pSoldier->inv[BPACKPOCKPOS] = obj; + } + + // force inventory to be dropped! + for (int i = 0; i < pSoldier->inv.size(); ++i) + { + OBJECTTYPE* item = &pSoldier->inv[i]; + if (item->exists() && Item[item->usItem].defaultundroppable == FALSE) + { + item->fFlags &= ~OBJECT_UNDROPPABLE; + } + } + transportGroupIdToSoldierMap[pSoldier->ubGroupID][pSoldier->ubSoldierClass]--; + } + } + else + { + TRANSPORT_GROUP_DEBUG(L"Found jeepless groupid[%d] with admin[%d] troop[%d] elite[%d] jeep[%d]", groupIter->first, groupIter->second[SOLDIER_CLASS_ADMINISTRATOR], groupIter->second[SOLDIER_CLASS_ARMY], groupIter->second[SOLDIER_CLASS_ELITE], groupIter->second[SOLDIER_CLASS_JEEP]); + // no jeep in group, add things normally + soldierClassIter = groupIter->second.find(pSoldier->ubSoldierClass); + if (soldierClassIter != groupIter->second.end()) + { + // found a matching soldierclass + if (soldierClassIter->second > 0) + { + //if (outgoing) + { + for (int i = TRANSPORT_LOOT_START; i <= TRANSPORT_LOOT_END; ++i) + { + const ItemTypes itemType = static_cast(i); + if (itemMap[itemType].size() > 0) + { + const UINT16 id = itemMap[itemType][Random(itemMap[itemType].size())]; + switch (itemType) + { + case GAS_CANS: + case MEDICAL_MEDKITS: + case MEDICAL_OTHER: + case TOOL_KITS: + case GUNS: + case GRENADELAUNCHERS: + case ROCKETLAUNCHERS: + case GRENADE_THROWN: + case MISC: + // skip for foot soldiers! + break; + + case BACKPACKS: + { + if (UsingNewInventorySystem()) + { + OBJECTTYPE obj; + CreateItem(id, 100, &obj); + obj.fFlags |= OBJECT_UNDROPPABLE; + pSoldier->inv[BPACKPOCKPOS] = obj; + } + } + break; + + case MEDICAL_FIRSTAIDKITS: + addItemToInventory(pSoldier, id, 1); + break; + + case AMMO_BOXES: + addItemToInventory(pSoldier, id, 1); + break; + + case CAMO_KITS: + addItemToInventory(pSoldier, id, 1); + break; + + case ATTACHMENTS: + // low chance of attachments + if (Random(100) < 25) + addItemToInventory(pSoldier, id, 1); + break; + + default: + TRANSPORT_GROUP_DEBUG(L"Warning: ignoring unhandled transport group loot type: %d", itemType); + // nothing! + break; + } + } + } + } + //else // returning home + { + // I can't really think of a good different loot set for returning transport groups, so we'll have the same loot + // regardless of whether the group is outgoing or incoming. I'll keep the in/out flag in case that changes + } + + // force inventory to be dropped! + for (int i = 0; i < pSoldier->inv.size(); ++i) + { + OBJECTTYPE* item = &pSoldier->inv[i]; + if (item->exists() && Item[item->usItem].defaultundroppable == FALSE) + { + item->fFlags &= ~OBJECT_UNDROPPABLE; + } + } + transportGroupIdToSoldierMap[pSoldier->ubGroupID][pSoldier->ubSoldierClass]--; + } + } + } + } + } +} + +void AddToTransportGroupMap(UINT8 groupId, int soldierClass, UINT8 amount) +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + { + ClearTransportGroupMap(); + return; + } + + // only update admins/troops/elites/jeeps + + switch (soldierClass) + { + case SOLDIER_CLASS_ADMINISTRATOR: + case SOLDIER_CLASS_ARMY: + case SOLDIER_CLASS_ELITE: + case SOLDIER_CLASS_JEEP: + transportGroupIdToSoldierMap[groupId][soldierClass] += amount; + break; + default: + // do nothing! + break; + } +} + +void ClearTransportGroupMap() +{ + transportGroupIdToSoldierMap.clear(); +} + +void NotifyTransportGroupDefeated() +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + return; + + const UINT32 hoursToRememberDefeat = 24 * 7; // 7 days + + AddStrategicEvent(EVENT_TRANSPORT_GROUP_DEFEATED, GetWorldTotalMin() + 60 * hoursToRememberDefeat, 0); +} + +void PopulateTransportGroup(UINT8& admins, UINT8& troops, UINT8& elites, UINT8& jeeps, UINT8& tanks, UINT8& robots, UINT8 progress, int difficulty, BOOLEAN trySpecialCase) +{ + admins = troops = elites = robots = jeeps = tanks = 0; + + // special case for only one valid destination - expert/insane only + if (trySpecialCase && (difficulty == DIF_LEVEL_HARD || difficulty == DIF_LEVEL_INSANE)) + { + admins = 1; + elites = difficulty == DIF_LEVEL_HARD ? 14 : 19; + + if (elites > 0 && gGameExternalOptions.fASDAssignsJeeps && ASDSoldierUpgradeToJeep()) + { + jeeps++; + elites--; + } + + if (gGameExternalOptions.fASDAssignsTanks) + { + const int numTanks = difficulty == DIF_LEVEL_INSANE ? 2 : 1; + for (int i = 0; i < numTanks; ++i) + { + if (elites > 0 && ASDSoldierUpgradeToTank()) + { + tanks++; + elites--; + } + } + } + + if (gGameExternalOptions.fASDAssignsRobots) + { + const int numRobots = Random(5); + for (int i = 0; i < numRobots; ++i) + { + if (elites > 0 && ASDSoldierUpgradeToRobot()) + { + robots++; + elites--; + } + } + } + } + else // normal case + { + UINT8 difficultyMod = 1; + switch (difficulty) + { + case DIF_LEVEL_EASY: difficultyMod = 1; break; + case DIF_LEVEL_MEDIUM: difficultyMod = 2; break; + case DIF_LEVEL_HARD: difficultyMod = 3; break; + case DIF_LEVEL_INSANE: difficultyMod = 4; break; + default: break; + } + + // default composition + if (progress < 25) + { + admins = 8 - difficultyMod; + troops = difficultyMod; + } + else if (progress < 50) + { + admins = 10 - difficultyMod * 2; + troops = difficultyMod; + elites = difficultyMod; + } + else if (progress < 75) + { + admins = 2; + troops = 10 - difficultyMod * 2; + elites = difficultyMod * 2; + } + else if (progress <= 100) // intentional equality + { + admins = 2; + troops = 13 - difficultyMod * 3; + elites = difficultyMod * 3; + } + else // at least one recent interception at max progress + { + admins = 2; + troops = 18 - difficultyMod * 4; + elites = difficultyMod * 4; + } + + // add some vehicles, if possible + if (progress >= gGameExternalOptions.usJeepMinimumProgress) + { + if (elites > 0 && gGameExternalOptions.fASDAssignsJeeps && ASDSoldierUpgradeToJeep()) + { + jeeps++; + elites--; + } + } + + if (progress >= gGameExternalOptions.usTankMinimumProgress && Random(100) < (20 + difficultyMod * 10)) + { + if (elites > 0 && gGameExternalOptions.fASDAssignsTanks && ASDSoldierUpgradeToTank()) + { + tanks++; + elites--; + } + } + + if (progress >= gGameExternalOptions.usRobotMinimumProgress && Random(100) < (20 + difficultyMod * 10)) + { + const int numRobots = Random(difficultyMod + 1); + for (int i = 0; i < numRobots; ++i) + { + if (elites > 0 && gGameExternalOptions.fASDAssignsRobots && ASDSoldierUpgradeToRobot()) + { + robots++; + elites--; + } + } + } + } +} + +const std::map GetTransportGroupSectorInfo() +{ + return transportGroupSectorInfo; +} + +#undef TRANSPORT_GROUP_DEBUG + diff --git a/Strategic/Strategic Transport Groups.h b/Strategic/Strategic Transport Groups.h new file mode 100644 index 00000000..3fd873c5 --- /dev/null +++ b/Strategic/Strategic Transport Groups.h @@ -0,0 +1,31 @@ +#ifndef _STRATEGIC_TRANSPORT_GROUPS_H +#define _STRATEGIC_TRANSPORT_GROUPS_H + +#include "Campaign Types.h" +#include "Types.h" +#include + +enum TransportGroupSectorInfo +{ + TransportGroupSectorInfo_LocatedGroup = 0, + TransportGroupSectorInfo_LocatedDestination, +}; + +struct GROUP; + +BOOLEAN DeployTransportGroup(); +BOOLEAN ForceDeployTransportGroup(UINT8 sectorId); +BOOLEAN ReturnTransportGroup(INT32 groupId); +void FillMapColoursForTransportGroups(INT32(&colorMap)[MAXIMUM_VALID_Y_COORDINATE][MAXIMUM_VALID_X_COORDINATE]); +void ProcessTransportGroupReachedDestination(GROUP* pGroup); +void UpdateTransportGroupInventory(); + +const std::map GetTransportGroupSectorInfo(); + +void AddToTransportGroupMap(UINT8 groupId, int soldierClass, UINT8 amount); +void ClearTransportGroupMap(); + +void NotifyTransportGroupDefeated(); + +#endif + diff --git a/Tactical/Interface Dialogue.cpp b/Tactical/Interface Dialogue.cpp index 16a8bfdf..00e4334a 100644 --- a/Tactical/Interface Dialogue.cpp +++ b/Tactical/Interface Dialogue.cpp @@ -3590,6 +3590,7 @@ void HandleNPCDoAction( UINT8 ubTargetNPC, UINT16 usActionCode, UINT8 ubQuoteNum case NPC_ACTION_SEND_SOLDIERS_TO_BALIME: case NPC_ACTION_GLOBAL_OFFENSIVE_1: case NPC_ACTION_GLOBAL_OFFENSIVE_2: + case NPC_ACTION_DEPLOY_TRANSPORT_GROUP: break; case NPC_ACTION_TRIGGER_QUEEN_BY_SAM_SITES_CONTROLLED: diff --git a/Tactical/Item Types.h b/Tactical/Item Types.h index 3e91212c..67e761d4 100644 --- a/Tactical/Item Types.h +++ b/Tactical/Item Types.h @@ -1220,6 +1220,10 @@ typedef struct BOOLEAN fProvidesRobotLaserBonus; //shadooow: bitflag controlling what system needs to be in play for item to appear UINT8 usLimitedToSystem; + + // rftr: the progress bounds that allow a transport group to drop an item + INT8 iTransportGroupMinProgress; + INT8 iTransportGroupMaxProgress; } INVTYPE; diff --git a/Tactical/Rotting Corpses.cpp b/Tactical/Rotting Corpses.cpp index 93882d89..35c4101b 100644 --- a/Tactical/Rotting Corpses.cpp +++ b/Tactical/Rotting Corpses.cpp @@ -2469,7 +2469,9 @@ void ReduceAmmoDroppedByNonPlayerSoldiers( SOLDIERTYPE *pSoldier, INT32 iInvSlot OBJECTTYPE *pObj = &( pSoldier->inv[ iInvSlot ] ); // if it's ammo - if ( Item[ pObj->usItem ].usItemClass == IC_AMMO ) + if ( Item[ pObj->usItem ].usItemClass == IC_AMMO + && Magazine[Item[pObj->usItem].ubClassIndex].ubMagType != AMMO_BOX + && Magazine[Item[pObj->usItem].ubClassIndex].ubMagType != AMMO_CRATE) { //don't drop all the clips, just a random # of them between 1 and how many there are pObj->ubNumberOfObjects = ( UINT8 ) ( 1 + Random( pObj->ubNumberOfObjects ) ); diff --git a/Tactical/interface Dialogue.h b/Tactical/interface Dialogue.h index 2ddc0605..9ca78408 100644 --- a/Tactical/interface Dialogue.h +++ b/Tactical/interface Dialogue.h @@ -387,6 +387,10 @@ enum NPC_ACTION_GLOBAL_OFFENSIVE_1, NPC_ACTION_GLOBAL_OFFENSIVE_2, + + // rftr: transport groups + NPC_ACTION_DEPLOY_TRANSPORT_GROUP, + NPC_ACTION_RETURN_TRANSPORT_GROUP, NPC_ACTION_RECRUIT_PROFILE_TO_EPC = 700, NPC_ACTION_UNRECRUIT_EPC = 701, diff --git a/Utils/Text.h b/Utils/Text.h index 74bd5f6b..21037737 100644 --- a/Utils/Text.h +++ b/Utils/Text.h @@ -1912,6 +1912,8 @@ enum STR_PB_ZOMBIE, STR_PB_BANDIT, STR_PB_BANDIT_KILLCIVS_IN_SECTOR, + STR_PB_TRANSPORT_GROUP, + STR_PB_TRANSPORT_GROUP_EN_ROUTE, TEXT_NUM_STRATEGIC_TEXT }; diff --git a/Utils/XML_Items.cpp b/Utils/XML_Items.cpp index 7a576d56..c48b2cb2 100644 --- a/Utils/XML_Items.cpp +++ b/Utils/XML_Items.cpp @@ -307,7 +307,9 @@ itemStartElementHandle(void *userData, const XML_Char *name, const XML_Char **at strcmp(name, "ProvidesRobotNightVision") == 0 || strcmp(name, "ProvidesRobotLaserBonus") == 0 || strcmp(name, "FoodSystemExclusive") == 0 || - strcmp(name, "DiseaseSystemExclusive") == 0 + strcmp(name, "DiseaseSystemExclusive") == 0 || + strcmp(name, "TransportGroupMinProgress") == 0 || + strcmp(name, "TransportGroupMaxProgress") == 0 ) { pData->curElement = ELEMENT_PROPERTY; @@ -1505,26 +1507,36 @@ itemEndElementHandle(void *userData, const XML_Char *name) } else if (strcmp(name, "ProvidesRobotNightVision") == 0) { - pData->curElement == ELEMENT; + pData->curElement = ELEMENT; pData->curItem.fProvidesRobotNightVision = (BOOLEAN)atol(pData->szCharData); } else if (strcmp(name, "ProvidesRobotLaserBonus") == 0) { - pData->curElement == ELEMENT; + pData->curElement = ELEMENT; pData->curItem.fProvidesRobotLaserBonus = (BOOLEAN)atol(pData->szCharData); } else if (strcmp(name, "FoodSystemExclusive") == 0) { - pData->curElement == ELEMENT; + pData->curElement = ELEMENT; if (atol(pData->szCharData)) pData->curItem.usLimitedToSystem|= FOOD_SYSTEM_FLAG; } else if (strcmp(name, "DiseaseSystemExclusive") == 0) { - pData->curElement == ELEMENT; + pData->curElement = ELEMENT; if (atol(pData->szCharData)) pData->curItem.usLimitedToSystem|= DISEASE_SYSTEM_FLAG; } + else if (strcmp(name, "TransportGroupMinProgress") == 0) + { + pData->curElement = ELEMENT; + pData->curItem.iTransportGroupMinProgress = (INT8)atoi(pData->szCharData); + } + else if (strcmp(name, "TransportGroupMaxProgress") == 0) + { + pData->curElement = ELEMENT; + pData->curItem.iTransportGroupMaxProgress = (INT8)atoi(pData->szCharData); + } --pData->maxReadDepth; } diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index 49f8ee18..2a943df5 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -3568,6 +3568,8 @@ STR16 gpStrategicString[] = L"僵尸", //L"Zombie", L"土匪", //L"Bandit", L"土匪杀死了%d名平民,在%s分区。", //注:这里的%d和%s不可以随意放前面或后面,一定要按英文顺序,不然会出错。(%d和%s 在中文中不能反过来。) L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6262,6 +6264,7 @@ STR16 z113FeaturesToggleText[] = L"天气功能:暴风雪", //L"Weather: Snow", L"随机事件功能", //L"Mini Events", L"反抗军司令部功能", //L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6309,6 +6312,7 @@ STR16 z113FeaturesHelpText[] = L"|天|气|功|能|:|暴|风|雪\n \n覆盖 [Tactical Weather Settings] ALLOW_SNOW\n \n暴风雪降低了能见度。\n \n配置选项:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW\n \n", //L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|随|机|事|件|功|能\n \n覆盖 [Mini Events Settings] MINI_EVENTS_ENABLED\n \n可能发生一些随机互动事件。\n \n配置选项:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \n详细信息请查看MiniEvents.lua。\n \n", //L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|反|抗|军|司|令|部|功|能\n \n覆盖 [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \n允许你升级占领的城镇,控制反抗军在战略层面上运作。\n \n详细的内容设定请查看RebelCommand_Settings.ini。\n \n", //L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6356,6 +6360,7 @@ STR16 z113FeaturesPanelText[] = L"启用暴风雪功能。在暴风雪中,更难被看到,武器退化更快,呼吸也更困难。", //L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"在游戏过程中,可能会弹出简短的事件。您可以从两个选项中选择一个,这可能会产生积极或消极的影响。事件可以影响各种各样的事情,但主要是你的佣兵。", //L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"在完成反抗军食物运送任务后,你可以访问他们的(A.R.C)指挥部网站。在这里你可以设定反抗军的政策,也可以为占领区单独设置地方政策。这将带来丰厚的奖励。作为代价,城镇的民忠会上升得更慢,所以你需要更加努力地让当地人信任你。", //L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12098,6 +12103,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"协同行动,悄悄地抵进敌军,但是要小心:这可能会让你部署在劣势区域。当进攻敌军部队时,部署区会更大。", //L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"扰乱ASD", //L"Disrupt ASD", L"破坏Arulco特种部门(ASD)的日常行动。临时阻止ASD部署更多的机械化单位,并且大幅度降低他们的每日收入。", //L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"战略情报", //L"Strategic Intel", L"侦听敌人,发现敌军的攻击目标。当在战略地图上观察队伍时,敌军优先进攻的目标区域会被标红。", //L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"强化本地商店", //L"Improve Local Shops", diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index 04da5bd5..00abf9fc 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -3568,6 +3568,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6265,6 +6267,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6312,6 +6315,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6359,6 +6363,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12108,6 +12113,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index 40ce7ef8..5f4b9f5e 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -3568,6 +3568,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6262,6 +6264,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6309,6 +6312,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6356,6 +6360,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12098,6 +12103,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index 95357d59..f97f68de 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -3576,6 +3576,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6270,6 +6272,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6317,6 +6320,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6364,6 +6368,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12090,6 +12095,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index cf3841ba..0e86f7f6 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -3605,6 +3605,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6133,6 +6135,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6180,6 +6183,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6227,6 +6231,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12012,6 +12017,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index cefb1944..272ff9da 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -3563,6 +3563,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6251,6 +6253,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6298,6 +6301,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6345,6 +6349,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12099,6 +12104,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index 5d67fe3c..35548ba1 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -3574,6 +3574,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6266,6 +6268,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6313,6 +6316,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6360,6 +6364,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12112,6 +12117,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index cdf39d8d..0345b830 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -3568,6 +3568,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6258,6 +6260,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6305,6 +6308,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6352,6 +6356,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12093,6 +12098,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", From 66ba2c8fbc3bec71cef12d24e6caa99f27cefe22 Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Tue, 4 Jul 2023 23:18:34 -0700 Subject: [PATCH 16/50] Fix forged group not actually spawning (#173) --- Strategic/Strategic Transport Groups.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Strategic/Strategic Transport Groups.cpp b/Strategic/Strategic Transport Groups.cpp index 4451b5a6..f8e19dab 100644 --- a/Strategic/Strategic Transport Groups.cpp +++ b/Strategic/Strategic Transport Groups.cpp @@ -157,6 +157,22 @@ BOOLEAN ForceDeployTransportGroup(UINT8 sectorId) const UINT8 progress = min(125, HighestPlayerProgressPercentage() + recentLossCount * 5); const UINT8 difficulty = gGameOptions.ubDifficultyLevel; PopulateTransportGroup(admins, troops, elites, jeeps, tanks, robots, progress, difficulty, FALSE); + + // varying transport group quality/compositions + GROUP* pGroup = CreateNewEnemyGroupDepartingFromSector( SECTOR( gModSettings.ubSAISpawnSectorX, gModSettings.ubSAISpawnSectorY ), admins, troops, elites, robots, tanks, jeeps ); + + //Madd: unlimited reinforcements? + if ( !gfUnlimitedTroops ) + { + giReinforcementPool -= (admins + troops + elites + robots + jeeps + tanks); + + giReinforcementPool = max( giReinforcementPool, 0 ); + } + + MoveSAIGroupToSector( &pGroup, sectorId, EVASIVE, TRANSPORT ); + + pGroup->uiFlags |= GROUPFLAG_TRANSPORT_ENROUTE; + return TRUE; } From 0e4d0a9bfb90969121d2a31913eac0d440982453 Mon Sep 17 00:00:00 2001 From: Shad000w Date: Fri, 7 Jul 2023 10:40:33 +0200 Subject: [PATCH 17/50] fix for issue #105, turned out that the issue happened due to the vanilla code that was using gTacticalStatus.uiFlags & GODMODE despite it was never actually implemented by SirTech. I changed it to only work gainst player mercs so militia and npcs can damage each other again. (#174) --- Tactical/LOS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp index 276aaf69..1911493e 100644 --- a/Tactical/LOS.cpp +++ b/Tactical/LOS.cpp @@ -3018,7 +3018,7 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend // Determine damage, checking guy's armour, etc sRange = GetRangeInCellCoordsFromGridNoDiff( pBullet->sOrigGridNo, pTarget->sGridNo ); - if ( gTacticalStatus.uiFlags & GODMODE && pBullet->ubFirerID != NOBODY && !(pFirer->flags.uiStatusFlags & SOLDIER_PC)) + if ( gTacticalStatus.uiFlags & GODMODE && pTarget->bTeam == OUR_TEAM && pBullet->ubFirerID != NOBODY && !(pFirer->flags.uiStatusFlags & SOLDIER_PC)) { // in god mode, and firer is computer controlled iImpact = 0; From 1150fb53529b6df16a7521c7cfac082efcccf264 Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Tue, 11 Jul 2023 22:26:31 -0700 Subject: [PATCH 18/50] Update simultaneous combat check for group arrivals (#176) The previous check didn't take group team into account, so it was possible for a simultaneous battle to be triggered by an enemy group moving into a friendly sector --- Strategic/Strategic Movement.cpp | 69 ++++++++++++++++---------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/Strategic/Strategic Movement.cpp b/Strategic/Strategic Movement.cpp index 881783a1..68eb7123 100644 --- a/Strategic/Strategic Movement.cpp +++ b/Strategic/Strategic Movement.cpp @@ -1892,47 +1892,48 @@ void GroupArrivedAtSector( UINT8 ubGroupID, BOOLEAN fCheckForBattle, BOOLEAN fNe fExceptionQueue = TRUE; } } - //First check if the group arriving is going to queue another battle. //KM : Aug 11, 1999 -- Patch fix: Added additional checks to prevent a 2nd battle in the case - //NOTE: We can't have more than one battle ongoing at a time. //where the player is involved in a potential battle with bloodcats/civilians - if( fExceptionQueue || (fCheckForBattle && (gTacticalStatus.fEnemyInSector || HostileCiviliansPresent() || HostileBloodcatsPresent()) && - FindMovementGroupInSector( (UINT8)gWorldSectorX, (UINT8)gWorldSectorY, OUR_TEAM ) && - (pGroup->ubNextX != gWorldSectorX || pGroup->ubNextY != gWorldSectorY || gbWorldSectorZ > 0 ) && NumHostilesInSector(pGroup->ubNextX, pGroup->ubNextY, pGroup->ubSectorZ) > 0) - #ifdef JA2UB - //Ja25: NO meanwhiles - #else + + // if this group arrival would cause a simultaneous combat, then delay it! + // we can't have more that one battle at a time. + if( fExceptionQueue + || ((fCheckForBattle && (gTacticalStatus.fEnemyInSector || HostileCiviliansPresent() || HostileBloodcatsPresent())) // if there is an active battle + && (pGroup->ubNextX != gWorldSectorX || pGroup->ubNextY != gWorldSectorY || gbWorldSectorZ > 0)) // and the group is arriving at a different sector +#ifndef JA2UB || AreInMeanwhile() - #endif +#endif ) { - //QUEUE BATTLE! - //Delay arrival by a random value ranging from 3-5 minutes, so it doesn't get the player - //too suspicious after it happens to him a few times, which, by the way, is a rare occurrence. -#ifdef JA2UB -/*Ja25: No meanwhiles*/ -#else - if( AreInMeanwhile() ) + if (((pGroup->usGroupTeam == OUR_TEAM || pGroup->usGroupTeam == MILITIA_TEAM) && NumHostilesInSector(pGroup->ubNextX, pGroup->ubNextY, pGroup->ubSectorZ) > 0) // if a friendly movement group will arrive at an enemy sector + || (pGroup->usGroupTeam == ENEMY_TEAM && (NumPlayerTeamMembersInSector(pGroup->ubNextX, pGroup->ubNextY, pGroup->ubSectorZ) + NumNonPlayerTeamMembersInSector(pGroup->ubNextX, pGroup->ubNextY, MILITIA_TEAM) > 0))) // or an enemy movement group will arrive at a friendly sector { - pGroup->uiArrivalTime ++; //tack on only 1 minute if we are in a meanwhile scene. This effectively - //prevents any battle from occurring while inside a meanwhile scene. - } - else -#endif - { - pGroup->uiArrivalTime += Random(3) + 3; - } - - if( !AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ) ) - AssertMsg( 0, "Failed to add movement event." ); - - if ( pGroup->usGroupTeam == OUR_TEAM ) - { - if( pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY > GetWorldTotalMin( ) ) + //QUEUE BATTLE! + //Delay arrival by a random value ranging from 3-5 minutes, so it doesn't get the player + //too suspicious after it happens to him a few times, which, by the way, is a rare occurrence. +#ifndef JA2UB + if( AreInMeanwhile() ) { - AddStrategicEvent( EVENT_GROUP_ABOUT_TO_ARRIVE, pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY, pGroup->ubGroupID ); + pGroup->uiArrivalTime ++; //tack on only 1 minute if we are in a meanwhile scene. This effectively + //prevents any battle from occurring while inside a meanwhile scene. + } + else +#endif + { + pGroup->uiArrivalTime += Random(3) + 3; } - } - return; + if( !AddStrategicEvent( EVENT_GROUP_ARRIVAL, pGroup->uiArrivalTime, pGroup->ubGroupID ) ) + AssertMsg( 0, "Failed to add movement event." ); + + if ( pGroup->usGroupTeam == OUR_TEAM ) + { + if( pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY > GetWorldTotalMin( ) ) + { + AddStrategicEvent( EVENT_GROUP_ABOUT_TO_ARRIVE, pGroup->uiArrivalTime - ABOUT_TO_ARRIVE_DELAY, pGroup->ubGroupID ); + } + } + + return; + } } //Update the position of the group From c03616095910138cb5f00f74b9ef5fb9f0b8ccdd Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Fri, 14 Jul 2023 07:56:27 +0300 Subject: [PATCH 19/50] fix radarmap drawing in tactical inventory view (#177) 720p resolution with 10 man squads had radarscreen rendered in a wrong location --- TileEngine/Radar Screen.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/TileEngine/Radar Screen.cpp b/TileEngine/Radar Screen.cpp index b27ce927..6146818e 100644 --- a/TileEngine/Radar Screen.cpp +++ b/TileEngine/Radar Screen.cpp @@ -83,14 +83,15 @@ void InitRadarScreenCoords( ) RADAR_WINDOW_STRAT_X = UI_BOTTOM_X + 1182; RADAR_WINDOW_STRAT_Y = UI_BOTTOM_Y + 9; RADAR_WINDOW_TM_X = xResOffset + (xResSize - 97) + 223; + RADAR_WINDOW_SM_X = xResOffset + (xResSize - 97); } else { RADAR_WINDOW_STRAT_X = xResOffset + (xResSize - 97); RADAR_WINDOW_STRAT_Y = (SCREEN_HEIGHT - 107); RADAR_WINDOW_TM_X = xResOffset + (xResSize - 97); + RADAR_WINDOW_SM_X = xResOffset + (xResSize - 97); } - RADAR_WINDOW_SM_X = xResOffset + (xResSize - 97); RADAR_WINDOW_TM_Y = (INTERFACE_START_Y + 13); RADAR_WINDOW_SM_Y = ((UsingNewInventorySystem() == false)) ? (INV_INTERFACE_START_Y + 33) : (INV_INTERFACE_START_Y + 116); @@ -436,8 +437,8 @@ void RenderRadarScreen( ) sWidth = ( RADAR_WINDOW_WIDTH ); sHeight = ( RADAR_WINDOW_HEIGHT ); - sX = RADAR_WINDOW_TM_X; - sY = gsRadarY; + sX = gsRadarX; + sY = gsRadarY; sRadarTLX = (INT16)( ( sTopLeftWorldX * gdScaleX ) - sRadarCX + sX + ( sWidth /2 ) ); @@ -602,7 +603,7 @@ void RenderRadarScreen( ) } // Add starting relative to interface - sXSoldRadar += RADAR_WINDOW_TM_X; + sXSoldRadar += gsRadarX; sYSoldRadar += gsRadarY; if(gbPixelDepth==16) From 06d4b53d3e4b4a63204310b61cdd28db033d80bc Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Fri, 21 Jul 2023 22:53:40 -0700 Subject: [PATCH 20/50] New option: Reduced stat growth at high levels (#183) Add optional stat growth reduction at 80+ and 90+ This makes mercs with high base stats more valuable as it is harder to grow to their levels --- GameSettings.cpp | 5 +++++ GameSettings.h | 4 ++++ Tactical/Campaign.cpp | 10 ++++++++++ 3 files changed, 19 insertions(+) diff --git a/GameSettings.cpp b/GameSettings.cpp index 4ba69e16..3791ae0c 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -1428,6 +1428,11 @@ void LoadGameExternalOptions() gGameExternalOptions.usLeadershipSubpointsToImprove = iniReader.ReadInteger("Tactical Difficulty Settings","LEADERSHIP_SUBPOINTS_TO_IMPROVE", 25, 1, 1000 ); gGameExternalOptions.usLevelSubpointsToImprove = iniReader.ReadInteger("Tactical Difficulty Settings","LEVEL_SUBPOINTS_TO_IMPROVE", 350, 1, 6500); + // rftr: optionally slow stat growth at 80+ and 90+. this gives more value to mercs with high base stats + gGameExternalOptions.ubMaxGrowthChanceAt80 = iniReader.ReadInteger("Tactical Difficulty Settings", "MAX_GROWTH_CHANCE_AT_80", 100, 1, 100); + gGameExternalOptions.ubMaxGrowthChanceAt90 = iniReader.ReadInteger("Tactical Difficulty Settings", "MAX_GROWTH_CHANCE_AT_90", 100, 1, 100); + + // Alternate algorithm for choosing equipment level. Mostly disregards soldier's class and puts less emphasis on distance from Sector P3. // SANDRO - moved into the game //gGameExternalOptions.fSlowProgressForEnemyItemsChoice = iniReader.ReadBoolean("Tactical Difficulty Settings", "SLOW_PROGRESS_FOR_ENEMY_ITEMS_CHOICE", TRUE); diff --git a/GameSettings.h b/GameSettings.h index 8ca4097e..5bdeb6dc 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -1149,6 +1149,10 @@ typedef struct UINT16 usLeadershipSubpointsToImprove; UINT16 usLevelSubpointsToImprove; + // rftr: optionally slow stat growth at 80+ and 90+. this gives more value to mercs with high base stats + UINT8 ubMaxGrowthChanceAt80; + UINT8 ubMaxGrowthChanceAt90; + // HEADROCK HAM B2.7: When turned on, this will give a CTH approximation instead of an exact value, on CTH Bars and "F" key feedback. BOOLEAN fApproximateCTH; diff --git a/Tactical/Campaign.cpp b/Tactical/Campaign.cpp index 3da28dd6..d128fb75 100644 --- a/Tactical/Campaign.cpp +++ b/Tactical/Campaign.cpp @@ -305,6 +305,16 @@ void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumCh usChance += (usChance * (pProfile->bWisdom + (pProfile->sWisdomGain / SubpointsPerPoint(WISDOMAMT, pProfile->bExpLevel)) - 50)) / 100; } + // rftr: reduced growth rates at 80+ and 90+ (to make mercs with higher base stats more valuable) + if (bCurrentRating >= 90) + { + usChance = min(min(gGameExternalOptions.ubMaxGrowthChanceAt80, gGameExternalOptions.ubMaxGrowthChanceAt90), usChance); + } + else if (bCurrentRating >= 80) + { + usChance = min(gGameExternalOptions.ubMaxGrowthChanceAt80, usChance); + } + /* // if the stat is Marksmanship, and the guy is a hopeless shot if ((ubStat == MARKAMT) && (pProfile->bSpecialTrait == HOPELESS_SHOT)) From 2163a6616992e8348bafc69d42e9eed55e0848f0 Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Tue, 25 Jul 2023 14:19:30 +0300 Subject: [PATCH 21/50] Prevent writing past array capacity (#185) Game would crash with stack-based buffer overrun when a MOLLE item had enough attachments and their texts were written past attachString capacity --- Tactical/Interface Items.cpp | 37 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Tactical/Interface Items.cpp b/Tactical/Interface Items.cpp index 69a9b0a9..d71aa3ca 100644 --- a/Tactical/Interface Items.cpp +++ b/Tactical/Interface Items.cpp @@ -12648,28 +12648,39 @@ void GetHelpTextForItem( STR16 pzStr, OBJECTTYPE *pObject, SOLDIERTYPE *pSoldier // Add attachment string.... CHAR16 attachString[ 300 ]; + CHAR16 tempString[ 120 ]; memset(attachString,0,sizeof(attachString)); - for (attachmentList::iterator iter = (*pObject)[subObject]->attachments.begin(); iter != (*pObject)[subObject]->attachments.end(); ++iter) { - if(iter->exists()){ - - //Break off if it's too long. - if(wcslen(attachString)>270){ - wcscat( attachString, L"\n...." ); - break; - } + for (attachmentList::iterator iter = (*pObject)[subObject]->attachments.begin(); iter != (*pObject)[subObject]->attachments.end(); ++iter) + { + if(iter->exists()) + { + memset(tempString, 0, sizeof(tempString)); iNumAttachments++; - - if ( iNumAttachments == 1 ) + if (iNumAttachments == 1) { - swprintf( attachString, L"\n \n%s:\n", Message[ STR_ATTACHMENTS ] ); + swprintf(tempString, L"\n \n%s:\n", Message[STR_ATTACHMENTS]); } else { - wcscat( attachString, L"\n" ); + swprintf(tempString, L"\n"); } + wcscat(tempString, ItemNames[iter->usItem]); - wcscat( attachString, ItemNames[ iter->usItem ] ); + auto attachStringLength = wcslen(attachString); + auto tempStringLength = wcslen(tempString); + auto totalLength = attachStringLength + tempStringLength; + // Break off if the string to be added does not fit. + // attachStringLength[300] - L"\n...." -> 294 + if (totalLength > 294) + { + wcscat(attachString, L"\n...."); + break; + } + else + { + wcscat(attachString, tempString); + } } } From 0decb148560e7305dc11aebe99d3a06bc9ec36ff Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Wed, 26 Jul 2023 10:18:13 +0300 Subject: [PATCH 22/50] Prevent endless clock (#186) Stop AI from attempting to steal a weapon that has a sling attachment, which prevents stealing. (by Seven) --- TacticalAI/Attacks.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index d84d2668..c2dc03cd 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -3308,6 +3308,10 @@ BOOLEAN AIDetermineStealingWeaponAttempt( SOLDIERTYPE * pSoldier, SOLDIERTYPE * UINT16 dfgvdfv = Item[pOpponent->inv[HANDPOS].usItem].usItemClass; return( FALSE ); } + if (HasAttachmentOfClass(&(pOpponent->inv[HANDPOS]), AC_SLING)) + { + return FALSE; + } uiSuccessChance = CalcChanceToSteal(pSoldier, pOpponent, 0); if ( uiSuccessChance >= 100 ) From 7868ddf1c9bb6d77580cddb07202b80ee3c81d2e Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Wed, 26 Jul 2023 23:44:25 -0700 Subject: [PATCH 23/50] Pablo speech fix (#187) Courtesy of Seven Fixes #181 --- Laptop/PostalService.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Laptop/PostalService.cpp b/Laptop/PostalService.cpp index e131c47f..b1fbf9f7 100644 --- a/Laptop/PostalService.cpp +++ b/Laptop/PostalService.cpp @@ -539,6 +539,23 @@ BOOLEAN CPostalService::DeliverShipment(UINT16 usShipmentID) SetFactFalse( FACT_PABLOS_STOLE_FROM_LATEST_SHIPMENT ); } + // set fact according to shipment size (item count, not weight) + if (usNumberOfItems - uiStolenCount <= 5) + { + SetFactFalse(FACT_MEDIUM_SIZED_SHIPMENT_WAITING); + SetFactFalse(FACT_LARGE_SIZED_SHIPMENT_WAITING); + } + else if (usNumberOfItems - uiStolenCount <= 15) + { + SetFactTrue(FACT_MEDIUM_SIZED_SHIPMENT_WAITING); + SetFactFalse(FACT_LARGE_SIZED_SHIPMENT_WAITING); + } + else + { + SetFactFalse(FACT_MEDIUM_SIZED_SHIPMENT_WAITING); + SetFactTrue(FACT_LARGE_SIZED_SHIPMENT_WAITING); + } + SetFactFalse( FACT_PLAYER_FOUND_ITEMS_MISSING ); SetFactFalse( FACT_LARGE_SIZED_OLD_SHIPMENT_WAITING ); @@ -1146,4 +1163,4 @@ RefToDestinationStruct CPostalService::_GetDestination(UINT16 usDestinationID) } return DESTINATION(dli); -} \ No newline at end of file +} From d14c92e2a9278dbc0b75c68109eec23499246dbf Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:07:35 +0300 Subject: [PATCH 24/50] Add /lib/ folder to gitignore (#188) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 07d506ec..e91f3742 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /out/ /CMakeSettings.json /CMakeUserPresets.json +/lib/ From a0ac81aa659ba0acb85eb832ab6906a07388a39b Mon Sep 17 00:00:00 2001 From: majcosta <34732933+majcosta@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:15:53 -0300 Subject: [PATCH 25/50] Add support for AddressSanitizer instrumentation (#189) To use, just set `ADDRESS_SANITIZER` to `ON` on the command-line or your CMakeUserPresets.json file --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b1c2357..b196f87e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,12 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +option(ADDRESS_SANITIZER OFF) +if(ADDRESS_SANITIZER) + message(STATUS "AddressSanitizer ENABLED for non-Release builds") + add_compile_options($,$>,-fsanitize=address,>) +endif() + # whether we are using MSBuild as a generator set(usingMsBuild $) From c8c7066969686a08318a8b9d63b091925ec3af34 Mon Sep 17 00:00:00 2001 From: majcosta <34732933+majcosta@users.noreply.github.com> Date: Fri, 28 Jul 2023 12:07:25 -0300 Subject: [PATCH 26/50] Refactor merc backpack checking in `FindBestPath` (#162) * remove unused fNonFenceJumper from AstarPathfinder::GetPath * Extract backpack check into function and flip logic Avoiding double negatives is nice --- Tactical/PATHAI.H | 1 - Tactical/PATHAI.cpp | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Tactical/PATHAI.H b/Tactical/PATHAI.H index c48a06a3..cf80a125 100644 --- a/Tactical/PATHAI.H +++ b/Tactical/PATHAI.H @@ -76,7 +76,6 @@ private: SOLDIERTYPE* pSoldier; INT8 onRooftop;//aka ubLevel, not sure if this bool is logically reversed yet - bool fNonFenceJumper; bool fNonSwimmer; bool fPathingForPlayer; bool fPathAroundPeople; diff --git a/Tactical/PATHAI.cpp b/Tactical/PATHAI.cpp index 45c76ca8..c8543b59 100644 --- a/Tactical/PATHAI.cpp +++ b/Tactical/PATHAI.cpp @@ -454,6 +454,11 @@ UINT32 guiFailedPathChecks = 0; UINT32 guiUnsuccessfulPathChecks = 0; #endif + +static auto canJumpFences(SOLDIERTYPE* pSoldier) -> bool { + return IS_MERC_BODY_TYPE(pSoldier) && pSoldier->CanClimbWithCurrentBackpack(); +} + //ADB the extra cover feature is supposed to pick a path of the same distance as one calculated with the feature off, //but a safer path, usually farther away from an enemy or following behind some cover. //however it has not been tested and it may need some work, I haven't touched it in a while @@ -638,7 +643,6 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s , fTurnBased = ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ); fPathingForPlayer = ( (pSoldier->bTeam == gbPlayerNum) && (!gTacticalStatus.fAutoBandageMode) && !(pSoldier->flags.uiStatusFlags & SOLDIER_PCUNDERAICONTROL) ); - fNonFenceJumper = !( IS_MERC_BODY_TYPE( pSoldier ) ); fNonSwimmer = !( IS_MERC_BODY_TYPE( pSoldier ) ); fPathAroundPeople = ( (fFlags & PATH_THROUGH_PEOPLE) == 0 ); fCloseGoodEnough = ( (fFlags & PATH_CLOSE_GOOD_ENOUGH) != 0); @@ -2244,7 +2248,6 @@ INT32 FindBestPath(SOLDIERTYPE *s , INT32 sDestination, INT8 bLevel, INT16 usMov DOOR * pDoor; STRUCTURE * pDoorStructure; BOOLEAN fDoorIsOpen = FALSE; - BOOLEAN fNonFenceJumper; BOOLEAN fNonSwimmer; BOOLEAN fPathAroundPeople; BOOLEAN fConsiderPersonAtDestAsObstacle; @@ -2316,7 +2319,6 @@ if(!GridNoOnVisibleWorldTile(iDestination)) fTurnBased = ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ); fPathingForPlayer = ( (s->bTeam == gbPlayerNum) && (!gTacticalStatus.fAutoBandageMode) && !(s->flags.uiStatusFlags & SOLDIER_PCUNDERAICONTROL) ); - fNonFenceJumper = !( IS_MERC_BODY_TYPE( s ) ) || (!s->CanClimbWithCurrentBackpack());//Moa: added backpack check // Flugente: nonswimmers are those who are not mercs and not boats fNonSwimmer = !(IS_MERC_BODY_TYPE( s ) ); @@ -2930,7 +2932,7 @@ if(!GridNoOnVisibleWorldTile(iDestination)) nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ]; } } - else if ( nextCost == TRAVELCOST_FENCE && fNonFenceJumper ) + else if ( nextCost == TRAVELCOST_FENCE && !canJumpFences(s)) { goto NEXTDIR; } From f16f137f2b53411945ba339cce9fa149af150b38 Mon Sep 17 00:00:00 2001 From: CptMoore <39010654+CptMoore@users.noreply.github.com> Date: Sun, 30 Jul 2023 09:48:11 +0200 Subject: [PATCH 27/50] Create latest tag explicitly Apparently the github API does too much magic, lets hope removing a step will make it behave --- .github/workflows/build.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5529cd97..4ce3003e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -190,22 +190,28 @@ jobs: mkdir dist/ mv artifacts/*_release/* dist/ - - name: Delete Latest - if: startsWith(github.ref, 'refs/tags/v') == false + - name: Delete Latest Release and Tag + if: github.ref == 'refs/heads/master' uses: dev-drprasad/delete-tag-and-release@v1.0 with: + delete_release: true tag_name: "latest" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create Latest Tag + if: github.ref == 'refs/heads/master' + working-directory: ${{ inputs.checkout-directory }} + run: | + git tag -d latest || true + git tag latest + git push origin latest - id: release_latest name: Release Latest - if: startsWith(github.ref, 'refs/tags/v') == false + if: github.ref == 'refs/heads/master' uses: ncipollo/release-action@v1 with: - allowUpdates: false artifactErrorsFailBuild: true artifacts: dist/* - draft: false generateReleaseNotes: true makeLatest: true name: "Latest (unstable)" From 2345dd0ae6d2dbfbec0d919c12354a2aa2f732c6 Mon Sep 17 00:00:00 2001 From: CptMoore <39010654+CptMoore@users.noreply.github.com> Date: Sun, 30 Jul 2023 19:22:23 +0200 Subject: [PATCH 28/50] Update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ce3003e..0151d820 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -200,7 +200,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create Latest Tag if: github.ref == 'refs/heads/master' - working-directory: ${{ inputs.checkout-directory }} + working-directory: source run: | git tag -d latest || true git tag latest From 32e49784a022f93d90d5f83a4f26bc7769aef45a Mon Sep 17 00:00:00 2001 From: CptMoore <39010654+CptMoore@users.noreply.github.com> Date: Sun, 30 Jul 2023 23:33:28 +0200 Subject: [PATCH 29/50] Update build.yml --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0151d820..eef17f2e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -200,8 +200,12 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create Latest Tag if: github.ref == 'refs/heads/master' - working-directory: source run: | + # filter tree is what makes this fast + git clone --config transfer.fsckobjects=false --tags --no-checkout --filter=tree:0 \ + "https://github.com/$GITHUB_REPOSITORY" \ + source + cd source git tag -d latest || true git tag latest git push origin latest From 1bb8a214c9ffd2a794dc4e87cb147bfdd7df430c Mon Sep 17 00:00:00 2001 From: CptMoore <39010654+CptMoore@users.noreply.github.com> Date: Mon, 31 Jul 2023 00:28:42 +0200 Subject: [PATCH 30/50] Update build.yml --- .github/workflows/build.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eef17f2e..d05169d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -198,14 +198,17 @@ jobs: tag_name: "latest" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Checkout Repo + if: github.ref == 'refs/heads/master' + uses: actions/checkout@v3 + with: + path: source + fetch-depth: 1 + sparse-checkout: 'README.md' - name: Create Latest Tag if: github.ref == 'refs/heads/master' + working-directory: source run: | - # filter tree is what makes this fast - git clone --config transfer.fsckobjects=false --tags --no-checkout --filter=tree:0 \ - "https://github.com/$GITHUB_REPOSITORY" \ - source - cd source git tag -d latest || true git tag latest git push origin latest From 52ccc7cc3ccc3fb17f6aef44606e58b0a26aeead Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:45:54 +0300 Subject: [PATCH 31/50] Fix widescreen UI militia popup box y-coordinate (#196) --- Strategic/Map Screen Interface Map.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Strategic/Map Screen Interface Map.cpp b/Strategic/Map Screen Interface Map.cpp index b709c3a0..0ec76fd7 100644 --- a/Strategic/Map Screen Interface Map.cpp +++ b/Strategic/Map Screen Interface Map.cpp @@ -1792,11 +1792,16 @@ void InitializeMilitiaPopup(void) const UINT16 xVal = 330 + xResOffset; const UINT16 yVal = 25 + yResOffset; - if (isWidescreenUI() || iResolution >= _1024x768) + if (iResolution >= _1024x768) { MAP_MILITIA_BOX_POS_X = xVal + 190; MAP_MILITIA_BOX_POS_Y = yVal + 285; } + else if (isWidescreenUI()) + { + MAP_MILITIA_BOX_POS_X = xVal + 190; + MAP_MILITIA_BOX_POS_Y = yVal + 116; + } else if (iResolution >= _800x600) { MAP_MILITIA_BOX_POS_X = xVal + 77; From 43c6943399b13c730a86fac83182999d44405365 Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Fri, 4 Aug 2023 14:25:35 +0300 Subject: [PATCH 32/50] Don't modify gMAXITEM_READ when reading localized file (#198) --- Utils/XML_Items.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Utils/XML_Items.cpp b/Utils/XML_Items.cpp index c48b2cb2..5c1c37ae 100644 --- a/Utils/XML_Items.cpp +++ b/Utils/XML_Items.cpp @@ -1600,9 +1600,12 @@ BOOLEAN ReadInItemStats(STR fileName, BOOLEAN localizedVersion ) MemFree(lpcBuffer); return FALSE; } - - // item read was x -> x+1 items - ++gMAXITEMS_READ; + + if (localizedTextOnly == false) + { + // item read was x -> x+1 items + ++gMAXITEMS_READ; + } MemFree(lpcBuffer); From aaeea9aac8d5009c7f6f1a172f6e64248012aa51 Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Wed, 9 Aug 2023 18:36:44 -0700 Subject: [PATCH 33/50] Replace evolution with growth rates (#201) --- GameSettings.cpp | 5 +- GameSettings.h | 7 +- GameVersion.h | 3 +- Laptop/personnel.cpp | 194 ++++++++++++++++++++++++++++++-- SaveLoadGame.cpp | 121 +++++++++++++++++++- Strategic/mapscreen.cpp | 20 ++-- Tactical/Campaign.cpp | 70 ++++++------ Tactical/Campaign.h | 4 +- Tactical/Interface Panels.cpp | 20 ++-- Tactical/Soldier Control.cpp | 2 +- Tactical/Soldier Profile.cpp | 47 +++++++- Tactical/Soldier Profile.h | 13 ++- Tactical/XML_Profiles.cpp | 94 +++++++++++++++- Tactical/soldier profile type.h | 28 ++--- TacticalAI/NPC.cpp | 12 +- Utils/Text.h | 30 +++++ Utils/_ChineseText.cpp | 22 ++-- Utils/_DutchText.cpp | 22 ++-- Utils/_EnglishText.cpp | 22 ++-- Utils/_FrenchText.cpp | 22 ++-- Utils/_GermanText.cpp | 22 ++-- Utils/_ItalianText.cpp | 22 ++-- Utils/_PolishText.cpp | 22 ++-- Utils/_RussianText.cpp | 22 ++-- 24 files changed, 685 insertions(+), 161 deletions(-) diff --git a/GameSettings.cpp b/GameSettings.cpp index 3791ae0c..28bf1151 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -1079,6 +1079,9 @@ void LoadGameExternalOptions() gGameExternalOptions.ubMercRandomExpRange = iniReader.ReadInteger("Recruitment Settings", "MERCS_RANDOM_EXP_RANGE", 1, 0, 4); gGameExternalOptions.fMercRandomStartSalary = iniReader.ReadBoolean("Recruitment Settings", "MERCS_RANDOM_START_SALARY", FALSE); gGameExternalOptions.ubMercRandomStartSalaryPercentMod = iniReader.ReadInteger("Recruitment Settings", "MERCS_RANDOM_START_SALARY_PERCENTAGE_MAX_MODIFIER", 30, 0, 100); + gGameExternalOptions.fMercGrowthModifiersEnabled = iniReader.ReadBoolean("Recruitment Settings", "MERCS_GROWTH_MODIFIERS_ENABLED", FALSE); + gGameExternalOptions.fMercRandomGrowthModifiers = iniReader.ReadBoolean("Recruitment Settings", "MERCS_RANDOM_GROWTH_MODIFIERS", FALSE); + gGameExternalOptions.iMercRandomGrowthModifiersRange = iniReader.ReadInteger("Recruitment Settings", "MERCS_RANDOM_GROWTH_MODIFIERS_RANGE", 5, -50, 50); //################# Financial Settings ################# @@ -2480,8 +2483,6 @@ void LoadGameExternalOptions() gGameExternalOptions.ubTeachBonusToTrain = iniReader.ReadInteger("Strategic Assignment Settings","TEACHER_TRAIT_BONUS_TO_TRAINING_EFFICIENCY",30, 0, 100); gGameExternalOptions.ubMinSkillToTeach = iniReader.ReadInteger("Strategic Assignment Settings","MIN_SKILL_REQUIRED_TO_TEACH_OTHER",25, 0, 100); - gGameExternalOptions.bDisableEvolution = iniReader.ReadBoolean("Strategic Assignment Settings", "DISABLE_EVOLUTION", TRUE ); - // HEADROCK HAM B2.8: New Trainer Relations: 2 = Trainees will go to sleep when their trainer goes to sleep. 3 = Trainer will go to sleep if all trainees are asleep. 1 = Both. 0 = Neither. gGameExternalOptions.ubSmartTrainingSleep = iniReader.ReadInteger("Strategic Assignment Settings","SYNCHRONIZED_SLEEPING_HOURS_WHEN_TRAINING_TOGETHER", 0, 0, 3); diff --git a/GameSettings.h b/GameSettings.h index 5bdeb6dc..735538f3 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -816,9 +816,6 @@ typedef struct INT32 ubRpcBonusToTrainMilitia; INT32 ubMinSkillToTeach; - // Flugente: disable evolution setting in MercProfiles.xml - BOOLEAN bDisableEvolution; - INT32 ubLowActivityLevel; INT32 ubMediumActivityLevel; INT32 ubHighActivityLevel; @@ -1440,6 +1437,10 @@ typedef struct UINT8 ubMercRandomStartSalaryPercentMod; + BOOLEAN fMercGrowthModifiersEnabled; + BOOLEAN fMercRandomGrowthModifiers; + INT16 iMercRandomGrowthModifiersRange; + BOOLEAN fBobbyRayFastShipments; BOOLEAN fGridExitInTurnBased; diff --git a/GameVersion.h b/GameVersion.h index d322980c..b516b225 100644 --- a/GameVersion.h +++ b/GameVersion.h @@ -23,6 +23,7 @@ extern CHAR16 zBuildInformation[256]; // Keeps track of the saved game version. Increment the saved game version whenever // you will invalidate the saved game file +#define GROWTH_MODIFIERS 184 #define REBELCOMMAND 183 #define DRAGSTRUCTURE 182 // Flugente: we can drag structures behind us #define DISABILITYFLAGMASK 181 // Flugente: disabilities get a flagmask @@ -104,7 +105,7 @@ extern CHAR16 zBuildInformation[256]; #define AP100_SAVEGAME_DATATYPE_CHANGE 105 // Before this, we didn't have the 100AP structure changes #define NIV_SAVEGAME_DATATYPE_CHANGE 102 // Before this, we used the old structure system -#define SAVE_GAME_VERSION REBELCOMMAND +#define SAVE_GAME_VERSION GROWTH_MODIFIERS //#define RUSSIANGOLD #ifdef __cplusplus diff --git a/Laptop/personnel.cpp b/Laptop/personnel.cpp index beb0e873..e2576e33 100644 --- a/Laptop/personnel.cpp +++ b/Laptop/personnel.cpp @@ -2170,7 +2170,7 @@ void DisplayCharPersonality(INT32 iId, INT32 iSlot) for ( int i = APPROACH_FRIENDLY; i <= APPROACH_RECRUIT; ++i ) { - mprintf( (INT16)(pPersonnelScreenPoints[loc].x + (iSlot*TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + 15), szLaptopStatText[7 + i] ); + mprintf( (INT16)(pPersonnelScreenPoints[loc].x + (iSlot*TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + 15), szLaptopStatText[LAPTOP_STAT_TEXT_FRIENDLY_APPROACH-1 + i] ); // APPROACH_FRIENDLY is 1 so fix the offset CHAR16 sStr[200]; swprintf( sStr, L"" ); @@ -2180,7 +2180,7 @@ void DisplayCharPersonality(INT32 iId, INT32 iSlot) swprintf( sStr, L"%d", val ); - FindFontRightCoordinates( (INT16)(pPersonnelScreenPoints[loc].x + (iSlot*TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH - 20, 0, sStr, PERS_FONT, &sX, &sY ); + FindFontRightCoordinates( (INT16)(pPersonnelScreenPoints[loc].x + (iSlot*TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, sStr, PERS_FONT, &sX, &sY ); mprintf( sX, (pPersonnelScreenPoints[loc].y + 15), sStr ); // Add specific region for fast help window @@ -2205,10 +2205,10 @@ void DisplayCharPersonality(INT32 iId, INT32 iSlot) if ( (gMercProfiles[pSoldier->ubProfile].ubMiscFlags3 & PROFILE_MISC_FLAG3_GOODGUY) ) { CHAR16 sStr1[200]; - swprintf( sStr1, szLaptopStatText[6] ); + swprintf( sStr1, szLaptopStatText[LAPTOP_STAT_TEXT_GOOD_GUY] ); CHAR16 sStr2[200]; - swprintf( sStr2, szLaptopStatText[7], pSoldier->GetName( ) ); + swprintf( sStr2, szLaptopStatText[LAPTOP_STAT_TEXT_REFUSES_TO_ATTACK_NON_HOSTILES], pSoldier->GetName( ) ); sX = pPersonnelScreenPoints[loc].x + (iSlot*TEXT_BOX_WIDTH); @@ -2233,15 +2233,189 @@ void DisplayCharPersonality(INT32 iId, INT32 iSlot) ++region; } - if ( !gGameExternalOptions.bDisableEvolution ) + if (gGameExternalOptions.fMercGrowthModifiersEnabled) { - CHAR16 sStr2[200]; - swprintf( sStr2, szLaptopStatText[12 + gMercProfiles[pSoldier->ubProfile].bEvolution], pSoldier->GetName() ); + if (gMercProfiles[pSoldier->ubProfile].fRegresses) + { + CHAR16 sStr2[200]; + swprintf(sStr2, szLaptopStatText[LAPTOP_STAT_TEXT_MERC_REGRESSES]); - mprintf( (INT16)( pPersonnelScreenPoints[loc].x + ( iSlot*TEXT_BOX_WIDTH ) ), ( pPersonnelScreenPoints[loc].y + 15 ), sStr2 ); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + 15), sStr2); - ++loc; - ++region; + ++loc; + ++region; + } + else + { + const int THRESHOLD_FAST = -3; // this value and below: "fast" + const int THRESHOLD_SLOW = 3; // this value and above: "slow" + int yOffset = 15; + CHAR16 statTxt[200]; + // health + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_HEALTH_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierLife <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierLife >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // strength + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_STRENGTH_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierStrength <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierStrength >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // agility + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_AGILITY_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierAgility <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierAgility >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // dexterity + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_DEXTERITY_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierDexterity <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierDexterity >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // wisdom + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_WISDOM_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierWisdom <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierWisdom >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // marksmanship + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_MARKSMANSHIP_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierMarksmanship <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierMarksmanship >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // explosives + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_EXPLOSIVES_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierExplosive <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierExplosive >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // leadership + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_LEADERSHIP_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierLeadership <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierLeadership >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // medical + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_MEDICAL_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierMedical <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierMedical >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // mechanical + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_MECHANICAL_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierMechanical <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierMechanical >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + yOffset += 10; + } + // exp level + { + swprintf(statTxt, szLaptopStatText[LAPTOP_STAT_TEXT_EXPERIENCE_SPEED]); + mprintf((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + + if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierExpLevel <= THRESHOLD_FAST) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_FAST]); + else if (gMercProfiles[pSoldier->ubProfile].bGrowthModifierExpLevel >= THRESHOLD_SLOW) + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_SLOW]); + else + swprintf(statTxt, L"%s", szLaptopStatText[LAPTOP_STAT_TEXT_AVERAGE]); + FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[loc].x + (iSlot * TEXT_BOX_WIDTH)), 0, TEXT_BOX_WIDTH, 0, statTxt, PERS_FONT, &sX, &sY); + mprintf(sX, (pPersonnelScreenPoints[loc].y + yOffset), statTxt); + } + } } } diff --git a/SaveLoadGame.cpp b/SaveLoadGame.cpp index 5c245e84..37eca4c5 100644 --- a/SaveLoadGame.cpp +++ b/SaveLoadGame.cpp @@ -1294,7 +1294,16 @@ BOOLEAN MERCPROFILESTRUCT::Load(HWFILE hFile, bool forceLoadOldVersion, bool for numBytesRead = ReadFieldByField( hFile, &this->bSex, sizeof(this->bSex), sizeof(INT8), numBytesRead); numBytesRead = ReadFieldByField( hFile, &this->bArmourAttractiveness, sizeof(this->bArmourAttractiveness), sizeof(INT8), numBytesRead); numBytesRead = ReadFieldByField( hFile, &this->ubMiscFlags2, sizeof(this->ubMiscFlags2), sizeof(UINT8), numBytesRead); - numBytesRead = ReadFieldByField( hFile, &this->bEvolution, sizeof(this->bEvolution), sizeof(INT8), numBytesRead); + if (guiCurrentSaveGameVersion < GROWTH_MODIFIERS) + { + numBytesRead = ReadFieldByField( hFile, &this->fRegresses, sizeof(this->fRegresses), sizeof(INT8), numBytesRead); + // convert old evolution to regresses boolean + this->fRegresses = this->fRegresses == 2 ? TRUE : FALSE; // 2 == CharacterEvolution::DEVOLVES + } + else + { + numBytesRead = ReadFieldByField( hFile, &this->fRegresses, sizeof(this->fRegresses), sizeof(INT8), numBytesRead); + } numBytesRead = ReadFieldByField( hFile, &this->ubMiscFlags, sizeof(this->ubMiscFlags), sizeof(UINT8), numBytesRead); numBytesRead = ReadFieldByField( hFile, &this->bSexist, sizeof(this->bSexist), sizeof(UINT8), numBytesRead); numBytesRead = ReadFieldByField( hFile, &this->bLearnToHate, sizeof(this->bLearnToHate), sizeof(UINT8), numBytesRead); @@ -1637,6 +1646,70 @@ BOOLEAN MERCPROFILESTRUCT::Load(HWFILE hFile, bool forceLoadOldVersion, bool for return( FALSE ); } } + + // rftr: growth modifiers + if (guiCurrentSaveGameVersion >= GROWTH_MODIFIERS) + { + if (!FileRead(hFile, &this->bGrowthModifierLife, sizeof(INT8), &uiNumBytesRead)) + { + return(FALSE); + } + if ( !FileRead( hFile, &this->bGrowthModifierStrength, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + if ( !FileRead( hFile, &this->bGrowthModifierAgility, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + if ( !FileRead( hFile, &this->bGrowthModifierDexterity, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + if ( !FileRead( hFile, &this->bGrowthModifierWisdom, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + if ( !FileRead( hFile, &this->bGrowthModifierMarksmanship, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + if ( !FileRead( hFile, &this->bGrowthModifierExplosive, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + if ( !FileRead( hFile, &this->bGrowthModifierLeadership, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + if ( !FileRead( hFile, &this->bGrowthModifierMedical, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + if ( !FileRead( hFile, &this->bGrowthModifierMechanical, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + if ( !FileRead( hFile, &this->bGrowthModifierExpLevel, sizeof(INT8), &uiNumBytesRead) ) + { + return( FALSE ); + } + } + else + { + // zero out growth modifiers for upgrading savegames + this->bGrowthModifierLife = 0; + this->bGrowthModifierStrength = 0; + this->bGrowthModifierAgility = 0; + this->bGrowthModifierDexterity = 0; + this->bGrowthModifierWisdom = 0; + this->bGrowthModifierMarksmanship = 0; + this->bGrowthModifierExplosive = 0; + this->bGrowthModifierLeadership = 0; + this->bGrowthModifierMedical = 0; + this->bGrowthModifierMechanical = 0; + this->bGrowthModifierExpLevel = 0; + } } if ( this->uiProfileChecksum != this->GetChecksum() ) @@ -1753,6 +1826,52 @@ BOOLEAN MERCPROFILESTRUCT::Save(HWFILE hFile) { return( FALSE ); } + + // rftr: growth modifiers + if (!FileWrite(hFile, &this->bGrowthModifierLife, sizeof(INT8), &uiNumBytesWritten)) + { + return(FALSE); + } + if ( !FileWrite( hFile, &this->bGrowthModifierStrength, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } + if ( !FileWrite( hFile, &this->bGrowthModifierAgility, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } + if ( !FileWrite( hFile, &this->bGrowthModifierDexterity, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } + if ( !FileWrite( hFile, &this->bGrowthModifierWisdom, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } + if ( !FileWrite( hFile, &this->bGrowthModifierMarksmanship, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } + if ( !FileWrite( hFile, &this->bGrowthModifierExplosive, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } + if ( !FileWrite( hFile, &this->bGrowthModifierLeadership, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } + if ( !FileWrite( hFile, &this->bGrowthModifierMedical, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } + if ( !FileWrite( hFile, &this->bGrowthModifierMechanical, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } + if ( !FileWrite( hFile, &this->bGrowthModifierExpLevel, sizeof(INT8), &uiNumBytesWritten) ) + { + return( FALSE ); + } return TRUE; } diff --git a/Strategic/mapscreen.cpp b/Strategic/mapscreen.cpp index 11c39563..4d387161 100644 --- a/Strategic/mapscreen.cpp +++ b/Strategic/mapscreen.cpp @@ -3372,7 +3372,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.AGL.iX; const auto y = UI_CHARPANEL.Attr.AGL.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sAgilityGain+1)) / SubpointsPerPoint(AGILAMT,0); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sAgilityGain+1)) / SubpointsPerPoint(AGILAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; @@ -3387,7 +3387,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.DEX.iX; const auto y = UI_CHARPANEL.Attr.DEX.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sDexterityGain+1)) / SubpointsPerPoint(DEXTAMT,0); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sDexterityGain+1)) / SubpointsPerPoint(DEXTAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; @@ -3402,7 +3402,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.STR.iX; const auto y = UI_CHARPANEL.Attr.STR.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sStrengthGain+1)) / SubpointsPerPoint(STRAMT,0); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sStrengthGain+1)) / SubpointsPerPoint(STRAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; @@ -3417,7 +3417,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.WIS.iX; const auto y = UI_CHARPANEL.Attr.WIS.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sWisdomGain+1)) / SubpointsPerPoint(WISDOMAMT,0); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sWisdomGain+1)) / SubpointsPerPoint(WISDOMAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; @@ -3432,7 +3432,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.MRK.iX; const auto y = UI_CHARPANEL.Attr.MRK.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sMarksmanshipGain+1)) / SubpointsPerPoint(MARKAMT,0); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sMarksmanshipGain+1)) / SubpointsPerPoint(MARKAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; @@ -3447,7 +3447,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.LDR.iX; const auto y = UI_CHARPANEL.Attr.LDR.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sLeadershipGain+1)) / SubpointsPerPoint(LDRAMT,0); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sLeadershipGain+1)) / SubpointsPerPoint(LDRAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; @@ -3462,7 +3462,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.MEC.iX; const auto y = UI_CHARPANEL.Attr.MEC.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sMechanicGain+1)) / SubpointsPerPoint(MECHANAMT,0); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sMechanicGain+1)) / SubpointsPerPoint(MECHANAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; @@ -3477,7 +3477,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.EXP.iX; const auto y = UI_CHARPANEL.Attr.EXP.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sExplosivesGain+1)) / SubpointsPerPoint(EXPLODEAMT,0); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sExplosivesGain+1)) / SubpointsPerPoint(EXPLODEAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; @@ -3492,7 +3492,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.MED.iX; const auto y = UI_CHARPANEL.Attr.MED.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sMedicalGain+1)) / SubpointsPerPoint(MEDICALAMT,0); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sMedicalGain+1)) / SubpointsPerPoint(MEDICALAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; @@ -3507,7 +3507,7 @@ void DisplayCharacterInfo( void ) const auto x = UI_CHARPANEL.Attr.LVL.iX; const auto y = UI_CHARPANEL.Attr.LVL.iY; - ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sExpLevelGain+1)) / SubpointsPerPoint(EXPERAMT, pSoldier->stats.bExpLevel); + ubBarWidth = (STAT_WIDTH * (gMercProfiles[ pSoldier->ubProfile ].sExpLevelGain+1)) / SubpointsPerPoint(EXPERAMT, &gMercProfiles[pSoldier->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, STAT_WIDTH)); ClipRect.iTop = (y-1); ClipRect.iBottom = (y-1) + STAT_HEIGHT; diff --git a/Tactical/Campaign.cpp b/Tactical/Campaign.cpp index d128fb75..54d1c78c 100644 --- a/Tactical/Campaign.cpp +++ b/Tactical/Campaign.cpp @@ -167,25 +167,14 @@ void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumCh INT8 bCurrentRating; UINT16 *psStatGainPtr; BOOLEAN fAffectedByWisdom = TRUE; - INT8 evolution = NORMAL_EVOLUTION; Assert(pProfile != NULL); - if ( !gGameExternalOptions.bDisableEvolution ) - evolution = pProfile->bEvolution; - - if ( evolution == NO_EVOLUTION ) - return; // No change possible, quit right away - - // if this is a Reverse-Evolving merc who attempting to train - if ( ( ubReason == FROM_TRAINING ) && ( evolution == DEVOLVE ) ) - return; // he doesn't get any benefit, but isn't penalized either - if (usNumChances == 0) return; - usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile->bExpLevel); - usSubpointsPerLevel = SubpointsPerPoint(EXPERAMT, pProfile->bExpLevel); + usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile); + usSubpointsPerLevel = SubpointsPerPoint(EXPERAMT, pProfile); switch (ubStat) { @@ -255,7 +244,6 @@ void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumCh return; } - if (ubReason == FROM_TRAINING) { // training always affected by wisdom @@ -271,7 +259,7 @@ void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumCh // loop once for each chance to improve for (uiCnt = 0; uiCnt < usNumChances; ++uiCnt) { - if ( evolution != DEVOLVE) // Evolves! + if (pProfile->fRegresses == FALSE) // Evolves! { // if this is improving from a failure, and a successful roll would give us enough to go up a point if ((ubReason == FROM_FAILURE) && ((*psStatGainPtr + 1) >= usSubpointsPerPoint)) @@ -302,7 +290,7 @@ void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumCh // if there IS a usChance, adjust it for high or low wisdom (50 is avg) if (usChance > 0 && fAffectedByWisdom) { - usChance += (usChance * (pProfile->bWisdom + (pProfile->sWisdomGain / SubpointsPerPoint(WISDOMAMT, pProfile->bExpLevel)) - 50)) / 100; + usChance += (usChance * (pProfile->bWisdom + (pProfile->sWisdomGain / SubpointsPerPoint(WISDOMAMT, pProfile)) - 50)) / 100; } // rftr: reduced growth rates at 80+ and 90+ (to make mercs with higher base stats more valuable) @@ -315,14 +303,6 @@ void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumCh usChance = min(gGameExternalOptions.ubMaxGrowthChanceAt80, usChance); } -/* - // if the stat is Marksmanship, and the guy is a hopeless shot - if ((ubStat == MARKAMT) && (pProfile->bSpecialTrait == HOPELESS_SHOT)) - { - usChance /= 5; // MUCH slower to improve, divide usChance by 5 - } -*/ - // SANDRO - penalty for primitive people, they get lesser chance to gain point for certain skills if ( gGameOptions.fNewTraitSystem && (usChance > 10) && (ubStat != EXPERAMT) && (pProfile->bCharacterTrait == CHAR_TRAIT_PRIMITIVE) ) { @@ -338,14 +318,6 @@ void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumCh } } - // Buggler: more evolution rate choices - if ( evolution == THREEQUARTER_EVOLUTION) - usChance = max(1, usChance * 0.75); - else if ( evolution == HALF_EVOLUTION) - usChance = max(1, usChance * 0.5); - else if ( evolution == ONEQUARTER_EVOLUTION) - usChance = max(1, usChance * 0.25); - // maximum possible usChance is 99% if (usChance > 99) { @@ -413,7 +385,7 @@ void ProcessStatChange(MERCPROFILESTRUCT *pProfile, UINT8 ubStat, UINT16 usNumCh // if there IS a usChance, adjust it for high or low wisdom (50 is avg) if (usChance > 0 && fAffectedByWisdom) { - usChance -= (usChance * (pProfile->bWisdom + (pProfile->sWisdomGain / SubpointsPerPoint(WISDOMAMT, pProfile->bExpLevel)) - 50)) / 100; + usChance -= (usChance * (pProfile->bWisdom + (pProfile->sWisdomGain / SubpointsPerPoint(WISDOMAMT, pProfile)) - 50)) / 100; } // if there's ANY usChance, minimum usChance is 1% regardless of wisdom @@ -497,7 +469,7 @@ void ChangeStat( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UINT8 ubSta UINT16 usSubpointsPerPoint; INT8 bDamagedStatToRaise = -1; // added by SANDRO - usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile->bExpLevel ); + usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile ); // build ptrs to appropriate profiletype stat fields switch( ubStat ) @@ -923,7 +895,7 @@ void ProcessUpdateStats( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UIN // set default min & max, subpoints/pt. bMinStatValue = 1; bMaxStatValue = MAX_STAT_VALUE; - usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile->bExpLevel); + usSubpointsPerPoint = SubpointsPerPoint(ubStat, pProfile); // build ptrs to appropriate profiletype stat fields switch( ubStat ) @@ -1166,7 +1138,7 @@ UINT32 RoundOffSalary(UINT32 uiSalary) } -UINT16 SubpointsPerPoint(UINT8 ubStat, INT8 bExpLevel) +UINT16 SubpointsPerPoint(UINT8 ubStat, MERCPROFILESTRUCT* pProfile) { UINT16 usSubpointsPerPoint; @@ -1187,40 +1159,62 @@ UINT16 SubpointsPerPoint(UINT8 ubStat, INT8 bExpLevel) // Attributes case HEALTHAMT: usSubpointsPerPoint = HEALTH_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierLife; + usSubpointsPerPoint = max(usSubpointsPerPoint, HEALTH_SUBPOINTS_TO_IMPROVE/2); break; case AGILAMT: usSubpointsPerPoint = AGILITY_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierAgility; + usSubpointsPerPoint = max(usSubpointsPerPoint, AGILITY_SUBPOINTS_TO_IMPROVE/2); break; case DEXTAMT: usSubpointsPerPoint = DEXTERITY_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierDexterity; + usSubpointsPerPoint = max(usSubpointsPerPoint, DEXTERITY_SUBPOINTS_TO_IMPROVE/2); break; case WISDOMAMT: usSubpointsPerPoint = WISDOM_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierWisdom; + usSubpointsPerPoint = max(usSubpointsPerPoint, WISDOM_SUBPOINTS_TO_IMPROVE/2); break; case STRAMT: usSubpointsPerPoint = STRENGTH_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierStrength; + usSubpointsPerPoint = max(usSubpointsPerPoint, STRENGTH_SUBPOINTS_TO_IMPROVE/2); break; // Skills case MEDICALAMT: usSubpointsPerPoint = MEDICAL_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierMedical; + usSubpointsPerPoint = max(usSubpointsPerPoint, MEDICAL_SUBPOINTS_TO_IMPROVE/2); break; case EXPLODEAMT: usSubpointsPerPoint = EXPLOSIVES_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierExplosive; + usSubpointsPerPoint = max(usSubpointsPerPoint, EXPLOSIVES_SUBPOINTS_TO_IMPROVE/2); break; case MECHANAMT: usSubpointsPerPoint = MECHANICAL_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierMechanical; + usSubpointsPerPoint = max(usSubpointsPerPoint, MECHANICAL_SUBPOINTS_TO_IMPROVE/2); break; case MARKAMT: usSubpointsPerPoint = MARKSMANSHIP_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierMarksmanship; + usSubpointsPerPoint = max(usSubpointsPerPoint, MARKSMANSHIP_SUBPOINTS_TO_IMPROVE/2); break; case LDRAMT: usSubpointsPerPoint = LEADERSHIP_SUBPOINTS_TO_IMPROVE; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierLeadership; + usSubpointsPerPoint = max(usSubpointsPerPoint, LEADERSHIP_SUBPOINTS_TO_IMPROVE/2); break; // Experience case EXPERAMT: - usSubpointsPerPoint = LEVEL_SUBPOINTS_TO_IMPROVE * bExpLevel; + usSubpointsPerPoint = LEVEL_SUBPOINTS_TO_IMPROVE * pProfile->bExpLevel; + if (gGameExternalOptions.fMercGrowthModifiersEnabled) usSubpointsPerPoint += pProfile->bGrowthModifierExpLevel; + usSubpointsPerPoint = max(usSubpointsPerPoint, LEVEL_SUBPOINTS_TO_IMPROVE * pProfile->bExpLevel / 2); break; default: @@ -1797,7 +1791,7 @@ void TestDumpStatChanges(void) // print days served fprintf(FDump, "%3d ", pProfile->usTotalDaysServed); // print evolution type - fprintf(FDump, "%c ", cEvolutionChars[ pProfile->bEvolution ]); + fprintf(FDump, "%c ", cEvolutionChars[ pProfile->fRegresses ]); // now print all non-zero stats for( ubStat = FIRST_CHANGEABLE_STAT; ubStat <= LAST_CHANGEABLE_STAT; ubStat++ ) diff --git a/Tactical/Campaign.h b/Tactical/Campaign.h index a5f54276..7523fb11 100644 --- a/Tactical/Campaign.h +++ b/Tactical/Campaign.h @@ -73,7 +73,7 @@ void ProcessUpdateStats( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UIN UINT32 CalcNewSalary(UINT32 uiOldSalary, BOOLEAN fIncrease, UINT32 uiMaxLimit, UINT32 uiIncreaseCap); UINT32 RoundOffSalary(UINT32 uiSalary); -UINT16 SubpointsPerPoint(UINT8 ubStat, INT8 bExpLevel); +UINT16 SubpointsPerPoint(UINT8 ubStat, MERCPROFILESTRUCT* pProfile); void HandleUnhiredMercImprovement( MERCPROFILESTRUCT *pProfile ); void HandleUnhiredMercDeaths( INT32 iProfileID ); @@ -92,4 +92,4 @@ void BuildStatChangeString( STR16 wString, STR16 wName, BOOLEAN fIncrease, INT16 void MERCMercWentUpALevelSendEmail( UINT8 ubMercMercIdValue ); -#endif \ No newline at end of file +#endif diff --git a/Tactical/Interface Panels.cpp b/Tactical/Interface Panels.cpp index e3757a03..4bbbeebd 100644 --- a/Tactical/Interface Panels.cpp +++ b/Tactical/Interface Panels.cpp @@ -2598,7 +2598,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // AGI if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sAgilityGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sAgilityGain+1)) / SubpointsPerPoint(AGILAMT,0); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sAgilityGain+1)) / SubpointsPerPoint(AGILAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_AGI_Y-1); ClipRect.iBottom = (SM_AGI_Y-1) + SM_STATS_HEIGHT; @@ -2610,7 +2610,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // DEX if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sDexterityGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sDexterityGain+1)) / SubpointsPerPoint(DEXTAMT,0); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sDexterityGain+1)) / SubpointsPerPoint(DEXTAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_DEX_Y-1); ClipRect.iBottom = (SM_DEX_Y-1) + SM_STATS_HEIGHT; @@ -2622,7 +2622,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // STR if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sStrengthGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sStrengthGain+1)) / SubpointsPerPoint(STRAMT,0); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sStrengthGain+1)) / SubpointsPerPoint(STRAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_STR_Y-1); ClipRect.iBottom = (SM_STR_Y-1) + SM_STATS_HEIGHT; @@ -2634,7 +2634,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // WIS if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sWisdomGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sWisdomGain+1)) / SubpointsPerPoint(WISDOMAMT,0); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sWisdomGain+1)) / SubpointsPerPoint(WISDOMAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_WIS_Y-1); ClipRect.iBottom = (SM_WIS_Y-1) + SM_STATS_HEIGHT; @@ -2646,7 +2646,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // MRK if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMarksmanshipGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMarksmanshipGain+1)) / SubpointsPerPoint(MARKAMT,0); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMarksmanshipGain+1)) / SubpointsPerPoint(MARKAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_MRKM_Y-1); ClipRect.iBottom = (SM_MRKM_Y-1) + SM_STATS_HEIGHT; @@ -2658,7 +2658,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // LDR if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sLeadershipGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sLeadershipGain+1)) / SubpointsPerPoint(LDRAMT,0); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sLeadershipGain+1)) / SubpointsPerPoint(LDRAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_CHAR_Y-1); ClipRect.iBottom = (SM_CHAR_Y-1) + SM_STATS_HEIGHT; @@ -2670,7 +2670,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // MECH if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMechanicGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMechanicGain+1)) / SubpointsPerPoint(MECHANAMT,0); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMechanicGain+1)) / SubpointsPerPoint(MECHANAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_MECH_Y-1); ClipRect.iBottom = (SM_MECH_Y-1) + SM_STATS_HEIGHT; @@ -2682,7 +2682,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // EXPLO if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExplosivesGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExplosivesGain+1)) / SubpointsPerPoint(EXPLODEAMT,0); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExplosivesGain+1)) / SubpointsPerPoint(EXPLODEAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_EXPL_Y-1); ClipRect.iBottom = (SM_EXPL_Y-1) + SM_STATS_HEIGHT; @@ -2694,7 +2694,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // MED if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMedicalGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMedicalGain+1)) / SubpointsPerPoint(MEDICALAMT,0); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMedicalGain+1)) / SubpointsPerPoint(MEDICALAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_MED_Y-1); ClipRect.iBottom = (SM_MED_Y-1) + SM_STATS_HEIGHT; @@ -2706,7 +2706,7 @@ void RenderSMPanel( BOOLEAN *pfDirty ) // EXPLEVEL if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExpLevelGain) { - ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExpLevelGain+1)) / SubpointsPerPoint(EXPERAMT, gpSMCurrentMerc->stats.bExpLevel); + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExpLevelGain+1)) / SubpointsPerPoint(EXPERAMT, &gMercProfiles[gpSMCurrentMerc->ubProfile]); ubBarWidth = __max(0, __min(ubBarWidth, SM_STATS_WIDTH)); ClipRect.iTop = (SM_EXPLVL_Y-1); ClipRect.iBottom = (SM_EXPLVL_Y-1) + SM_STATS_HEIGHT; diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index d6c3738c..9a594ee2 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -1339,7 +1339,7 @@ MERCPROFILESTRUCT& MERCPROFILESTRUCT::operator=(const OLD_MERCPROFILESTRUCT_101& this->bSex = src.bSex; this->bArmourAttractiveness = src.bArmourAttractiveness; this->ubMiscFlags2 = src.ubMiscFlags2; - this->bEvolution = src.bEvolution; + this->fRegresses = src.bEvolution == 2; // formerly, 2 == CharacterEvolution::DEVOLVES this->ubMiscFlags = src.ubMiscFlags; this->bSexist = src.bSexist; this->bLearnToHate = src.bLearnToHate; diff --git a/Tactical/Soldier Profile.cpp b/Tactical/Soldier Profile.cpp index c307da6d..37052fb0 100644 --- a/Tactical/Soldier Profile.cpp +++ b/Tactical/Soldier Profile.cpp @@ -98,6 +98,7 @@ extern UINT8 gubItemDroppableFlag[NUM_INV_SLOTS]; //Random Stats RANDOM_STATS_VALUES gRandomStatsValue[NUM_PROFILES]; void RandomStats(); +void RandomGrowthModifiers(); void RandomStartSalary(); //Jenilee @@ -459,6 +460,35 @@ void RandomStats() ExitRandomMercs(); } } +void RandomGrowthModifiers() +{ + UINT32 cnt; + MERCPROFILESTRUCT * pProfile; + BOOLEAN useBellCurve = TRUE; + + if (gGameExternalOptions.fMercRandomGrowthModifiers == TRUE) + { + for (cnt = 0; cnt < NUM_PROFILES; cnt++) + { + pProfile = &(gMercProfiles[cnt]); + + // cap minimum growth modifier to negative half subpoints. this is effectively double speed using default values (subpoints = 50) + // cap maximum growth modifier to 30000. this would make a merc take 600x as long to level up a stat using default values + pProfile->bGrowthModifierExpLevel = RandomAbsoluteRange( pProfile->bGrowthModifierExpLevel, -gGameExternalOptions.usLevelSubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierLife = RandomAbsoluteRange( pProfile->bGrowthModifierLife, -gGameExternalOptions.usHealthSubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierAgility = RandomAbsoluteRange( pProfile->bGrowthModifierAgility, -gGameExternalOptions.usAgilitySubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierDexterity = RandomAbsoluteRange( pProfile->bGrowthModifierDexterity, -gGameExternalOptions.usDexteritySubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierStrength = RandomAbsoluteRange( pProfile->bGrowthModifierStrength, -gGameExternalOptions.usStrengthSubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierLeadership = RandomAbsoluteRange( pProfile->bGrowthModifierLeadership, -gGameExternalOptions.usLeadershipSubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierWisdom = RandomAbsoluteRange( pProfile->bGrowthModifierWisdom, -gGameExternalOptions.usWisdomSubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierMarksmanship = RandomAbsoluteRange( pProfile->bGrowthModifierMarksmanship, -gGameExternalOptions.usMarksmanshipSubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierMechanical = RandomAbsoluteRange( pProfile->bGrowthModifierMechanical, -gGameExternalOptions.usMechanicalSubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierExplosive = RandomAbsoluteRange( pProfile->bGrowthModifierExplosive, -gGameExternalOptions.usExplosivesSubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + pProfile->bGrowthModifierMedical = RandomAbsoluteRange( pProfile->bGrowthModifierMedical, -gGameExternalOptions.usMedicalSubpointsToImprove/2, 30000, gGameExternalOptions.iMercRandomGrowthModifiersRange, useBellCurve ); + } + } +} + void RandomStartSalary() { UINT32 cnt; @@ -996,6 +1026,8 @@ for( int i = 0; i < NUM_PROFILES; i++ ) // --------------- RandomStats (); //random stats by Jazz + + RandomGrowthModifiers(); // Buggler: random starting salary RandomStartSalary (); @@ -2522,7 +2554,7 @@ void OverwriteMercProfileWithXMLData( UINT32 uiLoop ) gMercProfiles[ uiLoop ].bMechanical = tempProfiles[ uiLoop ].bMechanical ; gMercProfiles[ uiLoop ].bExpLevel = tempProfiles[ uiLoop ].bExpLevel ; - gMercProfiles[ uiLoop ].bEvolution = tempProfiles[ uiLoop ].bEvolution ; + gMercProfiles[uiLoop].fRegresses = tempProfiles[ uiLoop ].fRegresses; ////////////////////////////////////////////////////////////////////////////////////// // SANDRO - Check old/new traits and repair possible errors if (gGameOptions.fNewTraitSystem) @@ -2637,6 +2669,19 @@ void OverwriteMercProfileWithXMLData( UINT32 uiLoop ) gMercProfiles[ uiLoop ].usVoiceIndex = tempProfiles[uiLoop].usVoiceIndex; gMercProfiles[ uiLoop ].Type = tempProfiles[uiLoop].Type; + gMercProfiles[uiLoop].fRegresses = tempProfiles[uiLoop].fRegresses; + gMercProfiles[uiLoop].bGrowthModifierLife = tempProfiles[uiLoop].bGrowthModifierLife; + gMercProfiles[uiLoop].bGrowthModifierStrength = tempProfiles[uiLoop].bGrowthModifierStrength; + gMercProfiles[uiLoop].bGrowthModifierAgility = tempProfiles[uiLoop].bGrowthModifierAgility; + gMercProfiles[uiLoop].bGrowthModifierDexterity = tempProfiles[uiLoop].bGrowthModifierDexterity; + gMercProfiles[uiLoop].bGrowthModifierWisdom = tempProfiles[uiLoop].bGrowthModifierWisdom; + gMercProfiles[uiLoop].bGrowthModifierMarksmanship = tempProfiles[uiLoop].bGrowthModifierMarksmanship; + gMercProfiles[uiLoop].bGrowthModifierExplosive = tempProfiles[uiLoop].bGrowthModifierExplosive; + gMercProfiles[uiLoop].bGrowthModifierLeadership = tempProfiles[uiLoop].bGrowthModifierLeadership; + gMercProfiles[uiLoop].bGrowthModifierMedical = tempProfiles[uiLoop].bGrowthModifierMedical; + gMercProfiles[uiLoop].bGrowthModifierMechanical = tempProfiles[uiLoop].bGrowthModifierMechanical; + gMercProfiles[uiLoop].bGrowthModifierExpLevel = tempProfiles[uiLoop].bGrowthModifierExpLevel; + gProfileType[uiLoop] = gMercProfiles[uiLoop].Type; switch ( tempProfiles[uiLoop].Type ) diff --git a/Tactical/Soldier Profile.h b/Tactical/Soldier Profile.h index 9eef6079..39e0860e 100644 --- a/Tactical/Soldier Profile.h +++ b/Tactical/Soldier Profile.h @@ -314,7 +314,18 @@ typedef struct INT8 bExpLevel; - INT8 bEvolution; + BOOLEAN fRegresses; + INT16 bGrowthModifierLife; + INT16 bGrowthModifierStrength; + INT16 bGrowthModifierAgility; + INT16 bGrowthModifierDexterity; + INT16 bGrowthModifierWisdom; + INT16 bGrowthModifierMarksmanship; + INT16 bGrowthModifierExplosive; + INT16 bGrowthModifierLeadership; + INT16 bGrowthModifierMedical; + INT16 bGrowthModifierMechanical; + INT16 bGrowthModifierExpLevel; // changed by SANDRO INT8 bOldSkillTrait; INT8 bOldSkillTrait2; diff --git a/Tactical/XML_Profiles.cpp b/Tactical/XML_Profiles.cpp index ba3aafbb..380fffb1 100644 --- a/Tactical/XML_Profiles.cpp +++ b/Tactical/XML_Profiles.cpp @@ -101,7 +101,19 @@ profileStartElementHandle(void *userData, const XML_Char *name, const XML_Char * strcmp(name, "bMedical") == 0 || strcmp(name, "bMechanical") == 0 || strcmp(name, "bExpLevel") == 0 || - strcmp(name, "bEvolution") == 0 || + strcmp(name, "fRegresses") == 0 || + // rftr: growth rates are intended to replace the tag, letting each stat have its own growth modifier + strcmp(name, "bGrowthModifierLife") == 0 || + strcmp(name, "bGrowthModifierStrength") == 0 || + strcmp(name, "bGrowthModifierAgility") == 0 || + strcmp(name, "bGrowthModifierDexterity") == 0 || + strcmp(name, "bGrowthModifierWisdom") == 0 || + strcmp(name, "bGrowthModifierMarksmanship") == 0 || + strcmp(name, "bGrowthModifierExplosive") == 0 || + strcmp(name, "bGrowthModifierLeadership") == 0 || + strcmp(name, "bGrowthModifierMedical") == 0 || + strcmp(name, "bGrowthModifierMechanical") == 0 || + strcmp(name, "bGrowthModifierExpLevel") == 0 || // added by SANDRO strcmp(name, "bOldSkillTrait") == 0 || strcmp(name, "bOldSkillTrait2") == 0 || @@ -280,7 +292,18 @@ profileEndElementHandle(void *userData, const XML_Char *name) tempProfiles[pData->curIndex].bMechanical = pData->curProfile.bMechanical; tempProfiles[pData->curIndex].bExpLevel = pData->curProfile.bExpLevel; - tempProfiles[pData->curIndex].bEvolution = pData->curProfile.bEvolution; + tempProfiles[pData->curIndex].fRegresses = pData->curProfile.fRegresses; + tempProfiles[pData->curIndex].bGrowthModifierLife = pData->curProfile.bGrowthModifierLife; + tempProfiles[pData->curIndex].bGrowthModifierStrength = pData->curProfile.bGrowthModifierStrength; + tempProfiles[pData->curIndex].bGrowthModifierAgility = pData->curProfile.bGrowthModifierAgility; + tempProfiles[pData->curIndex].bGrowthModifierDexterity = pData->curProfile.bGrowthModifierDexterity; + tempProfiles[pData->curIndex].bGrowthModifierWisdom = pData->curProfile.bGrowthModifierWisdom; + tempProfiles[pData->curIndex].bGrowthModifierMarksmanship = pData->curProfile.bGrowthModifierMarksmanship; + tempProfiles[pData->curIndex].bGrowthModifierExplosive = pData->curProfile.bGrowthModifierExplosive; + tempProfiles[pData->curIndex].bGrowthModifierLeadership = pData->curProfile.bGrowthModifierLeadership; + tempProfiles[pData->curIndex].bGrowthModifierMedical = pData->curProfile.bGrowthModifierMedical; + tempProfiles[pData->curIndex].bGrowthModifierMechanical = pData->curProfile.bGrowthModifierMechanical; + tempProfiles[pData->curIndex].bGrowthModifierExpLevel = pData->curProfile.bGrowthModifierExpLevel; // added by SANDRO tempProfiles[pData->curIndex].bOldSkillTrait = pData->curProfile.bOldSkillTrait; tempProfiles[pData->curIndex].bOldSkillTrait2 = pData->curProfile.bOldSkillTrait2; @@ -586,11 +609,70 @@ profileEndElementHandle(void *userData, const XML_Char *name) pData->curElement = ELEMENT; pData->curProfile.bExpLevel = (UINT32) strtoul(pData->szCharData, NULL, 0); } - - else if(strcmp(name, "bEvolution") == 0) + + else if(strcmp(name, "fRegresses") == 0) { pData->curElement = ELEMENT; - pData->curProfile.bEvolution = (UINT32) strtoul(pData->szCharData, NULL, 0); + pData->curProfile.fRegresses = (BOOLEAN) strtoul(pData->szCharData, NULL, 0); + } + + else if (strncmp(name, "bGrowthModifier", 11) == 0) // doing this to avoid C1061 + { + if (strcmp(name, "bGrowthModifierLife") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierLife = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierStrength") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierStrength = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierAgility") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierAgility = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierDexterity") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierDexterity = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierWisdom") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierWisdom = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierMarksmanship") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierMarksmanship = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierExplosive") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierExplosive = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierLeadership") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierLeadership = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierMedical") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierMedical = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierMechanical") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierMechanical = (UINT32)strtoul(pData->szCharData, NULL, 0); + } + else if(strcmp(name, "bGrowthModifierExpLevel") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bGrowthModifierExpLevel = (UINT32)strtoul(pData->szCharData, NULL, 0); + } } //////////////////////////////////////////////////////////////////////////// // SANDRO was here - messed this a bit @@ -1600,7 +1682,7 @@ BOOLEAN WriteMercProfiles() FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bMedical); FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bMechanical); FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bExpLevel); - FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bEvolution); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].fRegresses); //////////////////////////////////////////////////////////////////////////////////////////// // SANDRO - old/new traits if (gGameOptions.fNewTraitSystem) diff --git a/Tactical/soldier profile type.h b/Tactical/soldier profile type.h index a85653ea..8d427a5b 100644 --- a/Tactical/soldier profile type.h +++ b/Tactical/soldier profile type.h @@ -402,18 +402,7 @@ typedef enum NUM_SEXIST } SexistLevels; - - -// training defines for evolution, no stat increase, stat decrease( de-evolve ) -typedef enum -{ - NORMAL_EVOLUTION =0, - NO_EVOLUTION, - DEVOLVE, - THREEQUARTER_EVOLUTION, - HALF_EVOLUTION, - ONEQUARTER_EVOLUTION, -} CharacterEvolution; +// rftr: removed the CharacterEvolution enum as it was replaced by growth modifiers #define BUDDY_MERC( prof, bud ) ((prof)->bBuddy[0] == (bud) || (prof)->bBuddy[1] == (bud) || (prof)->bBuddy[2] == (bud) || (prof)->bBuddy[3] == (bud) || (prof)->bBuddy[4] == (bud) || ( (prof)->bLearnToLike == (bud) && (prof)->bLearnToLikeCount == 0 ) ) #define HATED_MERC( prof, hat ) ((prof)->bHated[0] == (hat) || (prof)->bHated[1] == (hat) || (prof)->bHated[2] == (hat) || (prof)->bHated[3] == (hat) || (prof)->bHated[4] == (hat) || ( (prof)->bLearnToHate == (hat) && (prof)->bLearnToHateCount == 0 ) ) @@ -789,7 +778,7 @@ public: INT8 bSex; INT8 bArmourAttractiveness; UINT8 ubMiscFlags2; - INT8 bEvolution; + BOOLEAN fRegresses; UINT8 ubMiscFlags; UINT8 bSexist; UINT8 bLearnToHate; @@ -1009,6 +998,19 @@ public: // Flugente: type of profile UINT32 Type; + + // rftr: growth modifiers - ignored if fRegresses is TRUE + INT16 bGrowthModifierLife; + INT16 bGrowthModifierStrength; + INT16 bGrowthModifierAgility; + INT16 bGrowthModifierDexterity; + INT16 bGrowthModifierWisdom; + INT16 bGrowthModifierMarksmanship; + INT16 bGrowthModifierExplosive; + INT16 bGrowthModifierLeadership; + INT16 bGrowthModifierMedical; + INT16 bGrowthModifierMechanical; + INT16 bGrowthModifierExpLevel; }; // MERCPROFILESTRUCT; // WANNE - BMP: DONE! diff --git a/TacticalAI/NPC.cpp b/TacticalAI/NPC.cpp index cf8d86c4..55606e27 100644 --- a/TacticalAI/NPC.cpp +++ b/TacticalAI/NPC.cpp @@ -808,7 +808,7 @@ INT32 GetEffectiveApproachValue( UINT8 usProfile, UINT8 usApproach, CHAR16* apSt if ( apStr ) { - swprintf( atStr, szLaptopStatText[0], threateneffectiveness ); + swprintf( atStr, szLaptopStatText[LAPTOP_STAT_TEXT_THREATEN_EFFECTIVENESS], threateneffectiveness ); wcscat( apStr, atStr ); } @@ -818,7 +818,7 @@ INT32 GetEffectiveApproachValue( UINT8 usProfile, UINT8 usApproach, CHAR16* apSt { if ( apStr ) { - swprintf( atStr, szLaptopStatText[1], gMercProfiles[usProfile].bLeadership ); + swprintf( atStr, szLaptopStatText[LAPTOP_STAT_TEXT_LEADERSHIP], gMercProfiles[usProfile].bLeadership ); wcscat( apStr, atStr ); } @@ -829,7 +829,7 @@ INT32 GetEffectiveApproachValue( UINT8 usProfile, UINT8 usApproach, CHAR16* apSt if ( apStr ) { - swprintf( atStr, szLaptopStatText[2], approachfactor ); + swprintf( atStr, szLaptopStatText[LAPTOP_STAT_TEXT_APPROACH_MODIFIER], approachfactor ); wcscat( apStr, atStr ); } @@ -855,7 +855,7 @@ INT32 GetEffectiveApproachValue( UINT8 usProfile, UINT8 usApproach, CHAR16* apSt if ( apStr ) { - swprintf( atStr, szLaptopStatText[3], bgmodifier ); + swprintf( atStr, szLaptopStatText[LAPTOP_STAT_TEXT_BACKGROUND_MODIFIER], bgmodifier ); wcscat( apStr, atStr ); } @@ -868,7 +868,7 @@ INT32 GetEffectiveApproachValue( UINT8 usProfile, UINT8 usApproach, CHAR16* apSt swprintf( atStr, L" \n" ); wcscat( apStr, atStr ); - swprintf( atStr, szLaptopStatText[4] ); + swprintf( atStr, szLaptopStatText[LAPTOP_STAT_TEXT_ASSERTIVE] ); wcscat( apStr, atStr ); } else if ( DoesMercHavePersonality( pSoldier, CHAR_TRAIT_MALICIOUS ) ) @@ -876,7 +876,7 @@ INT32 GetEffectiveApproachValue( UINT8 usProfile, UINT8 usApproach, CHAR16* apSt swprintf( atStr, L" \n" ); wcscat( apStr, atStr ); - swprintf( atStr, szLaptopStatText[5] ); + swprintf( atStr, szLaptopStatText[LAPTOP_STAT_TEXT_MALICIOUS] ); wcscat( apStr, atStr ); } } diff --git a/Utils/Text.h b/Utils/Text.h index 21037737..e2c3251c 100644 --- a/Utils/Text.h +++ b/Utils/Text.h @@ -3084,6 +3084,36 @@ extern STR16 szSMilitiaResourceText[]; extern STR16 szInteractiveActionText[]; extern STR16 szLaptopStatText[]; +enum +{ + LAPTOP_STAT_TEXT_THREATEN_EFFECTIVENESS, + LAPTOP_STAT_TEXT_LEADERSHIP, + LAPTOP_STAT_TEXT_APPROACH_MODIFIER, + LAPTOP_STAT_TEXT_BACKGROUND_MODIFIER, + LAPTOP_STAT_TEXT_ASSERTIVE, + LAPTOP_STAT_TEXT_MALICIOUS, + LAPTOP_STAT_TEXT_GOOD_GUY, + LAPTOP_STAT_TEXT_REFUSES_TO_ATTACK_NON_HOSTILES, + LAPTOP_STAT_TEXT_FRIENDLY_APPROACH, + LAPTOP_STAT_TEXT_DIRECT_APPROACH, + LAPTOP_STAT_TEXT_THREATEN_APPROACH, + LAPTOP_STAT_TEXT_RECRUIT_APPROACH, + LAPTOP_STAT_TEXT_MERC_REGRESSES, + LAPTOP_STAT_TEXT_FAST, + LAPTOP_STAT_TEXT_AVERAGE, + LAPTOP_STAT_TEXT_SLOW, + LAPTOP_STAT_TEXT_HEALTH_SPEED, + LAPTOP_STAT_TEXT_STRENGTH_SPEED, + LAPTOP_STAT_TEXT_AGILITY_SPEED, + LAPTOP_STAT_TEXT_DEXTERITY_SPEED, + LAPTOP_STAT_TEXT_WISDOM_SPEED, + LAPTOP_STAT_TEXT_MARKSMANSHIP_SPEED, + LAPTOP_STAT_TEXT_EXPLOSIVES_SPEED, + LAPTOP_STAT_TEXT_LEADERSHIP_SPEED, + LAPTOP_STAT_TEXT_MEDICAL_SPEED, + LAPTOP_STAT_TEXT_MECHANICAL_SPEED, + LAPTOP_STAT_TEXT_EXPERIENCE_SPEED, +}; // Flugente: gear templates extern STR16 szGearTemplateText[]; diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index 2a943df5..e587df2d 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -11655,13 +11655,21 @@ STR16 szLaptopStatText[] = L"威胁对话", //L"Threaten approach", L"招募对话", //L"Recruit approach", - L"%s正以正常速度学习。", //L"%s learns with normal speed.", - L"%s根本没有在学习。", //L"%s does not learn at all.", - L"%s遗忘了技能。", //L"%s unlearns his skills.", - L"%s正以3/4的速度学习。", //L"%s learns with 3/4 speed.", - - L"%s正以1/2的速度学习。", //L"%s learns with 1/2 speed.", - L"%s正以1/4的速度学习。", //L"%s learns with 1/4 speed.", + L"Stats will regress.", + L"Fast", + L"Average", + L"Slow", + L"Health growth", + L"Strength growth", + L"Agility growth", + L"Dexterity growth", + L"Wisdom growth", + L"Marksmanship growth", + L"Explosives growth", + L"Leadership growth", + L"Medical growth", + L"Mechanical growth", + L"Experience growth", }; STR16 szGearTemplateText[] = diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index 00abf9fc..4c56a70b 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -11664,13 +11664,21 @@ STR16 szLaptopStatText[] = // TODO.Translate L"Threaten approach", L"Recruit approach", - L"%s learns with normal speed.", // TODO.Translate - L"%s does not learn at all.", - L"%s unlearns his skills.", - L"%s learns with 3/4 speed.", - - L"%s learns with 1/2 speed.", - L"%s learns with 1/4 speed.", + L"Stats will regress.", + L"Fast", + L"Average", + L"Slow", + L"Health growth", + L"Strength growth", + L"Agility growth", + L"Dexterity growth", + L"Wisdom growth", + L"Marksmanship growth", + L"Explosives growth", + L"Leadership growth", + L"Medical growth", + L"Mechanical growth", + L"Experience growth", }; STR16 szGearTemplateText[] = // TODO.Translate diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index 5f4b9f5e..d2684b28 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -11655,13 +11655,21 @@ STR16 szLaptopStatText[] = L"Threaten approach", L"Recruit approach", - L"%s learns with normal speed.", - L"%s does not learn at all.", - L"%s unlearns his skills.", - L"%s learns with 3/4 speed.", - - L"%s learns with 1/2 speed.", - L"%s learns with 1/4 speed.", + L"Stats will regress.", + L"Fast", + L"Average", + L"Slow", + L"Health growth", + L"Strength growth", + L"Agility growth", + L"Dexterity growth", + L"Wisdom growth", + L"Marksmanship growth", + L"Explosives growth", + L"Leadership growth", + L"Medical growth", + L"Mechanical growth", + L"Experience growth", }; STR16 szGearTemplateText[] = diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index f97f68de..77fc295f 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -11646,13 +11646,21 @@ STR16 szLaptopStatText[] = // TODO.Translate L"Threaten approach", L"Recruit approach", - L"%s learns with normal speed.", // TODO.Translate - L"%s does not learn at all.", - L"%s unlearns his skills.", - L"%s learns with 3/4 speed.", - - L"%s learns with 1/2 speed.", - L"%s learns with 1/4 speed.", + L"Stats will regress.", + L"Fast", + L"Average", + L"Slow", + L"Health growth", + L"Strength growth", + L"Agility growth", + L"Dexterity growth", + L"Wisdom growth", + L"Marksmanship growth", + L"Explosives growth", + L"Leadership growth", + L"Medical growth", + L"Mechanical growth", + L"Experience growth", }; STR16 szGearTemplateText[] = // TODO.Translate diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index 0e86f7f6..62bc2deb 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -11560,13 +11560,21 @@ STR16 szLaptopStatText[] = L"Threaten approach", L"Recruit approach", - L"%s learns with normal speed.", // TODO.Translate - L"%s does not learn at all.", - L"%s unlearns his skills.", - L"%s learns with 3/4 speed.", - - L"%s learns with 1/2 speed.", - L"%s learns with 1/4 speed.", + L"Stats will regress.", + L"Fast", + L"Average", + L"Slow", + L"Health growth", + L"Strength growth", + L"Agility growth", + L"Dexterity growth", + L"Wisdom growth", + L"Marksmanship growth", + L"Explosives growth", + L"Leadership growth", + L"Medical growth", + L"Mechanical growth", + L"Experience growth", }; // TODO.Translate diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index 272ff9da..1968ae29 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -11655,13 +11655,21 @@ STR16 szLaptopStatText[] = // TODO.Translate L"Threaten approach", L"Recruit approach", - L"%s learns with normal speed.", // TODO.Translate - L"%s does not learn at all.", - L"%s unlearns his skills.", - L"%s learns with 3/4 speed.", - - L"%s learns with 1/2 speed.", - L"%s learns with 1/4 speed.", + L"Stats will regress.", + L"Fast", + L"Average", + L"Slow", + L"Health growth", + L"Strength growth", + L"Agility growth", + L"Dexterity growth", + L"Wisdom growth", + L"Marksmanship growth", + L"Explosives growth", + L"Leadership growth", + L"Medical growth", + L"Mechanical growth", + L"Experience growth", }; STR16 szGearTemplateText[] = // TODO.Translate diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index 35548ba1..379f3f5a 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -11668,13 +11668,21 @@ STR16 szLaptopStatText[] = // TODO.Translate L"Threaten approach", L"Recruit approach", - L"%s learns with normal speed.", // TODO.Translate - L"%s does not learn at all.", - L"%s unlearns his skills.", - L"%s learns with 3/4 speed.", - - L"%s learns with 1/2 speed.", - L"%s learns with 1/4 speed.", + L"Stats will regress.", + L"Fast", + L"Average", + L"Slow", + L"Health growth", + L"Strength growth", + L"Agility growth", + L"Dexterity growth", + L"Wisdom growth", + L"Marksmanship growth", + L"Explosives growth", + L"Leadership growth", + L"Medical growth", + L"Mechanical growth", + L"Experience growth", }; STR16 szGearTemplateText[] = // TODO.Translate diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index 0345b830..9b9b99e2 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -11650,13 +11650,21 @@ STR16 szLaptopStatText[] = // TODO.Translate L"Threaten approach", L"Recruit approach", - L"%s learns with normal speed.", // TODO.Translate - L"%s does not learn at all.", - L"%s unlearns his skills.", - L"%s learns with 3/4 speed.", - - L"%s learns with 1/2 speed.", - L"%s learns with 1/4 speed.", + L"Stats will regress.", + L"Fast", + L"Average", + L"Slow", + L"Health growth", + L"Strength growth", + L"Agility growth", + L"Dexterity growth", + L"Wisdom growth", + L"Marksmanship growth", + L"Explosives growth", + L"Leadership growth", + L"Medical growth", + L"Mechanical growth", + L"Experience growth", }; STR16 szGearTemplateText[] = // TODO.Translate From b91871b518af86c4b0c231495f6edebf2c55b1a8 Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:27:29 -0700 Subject: [PATCH 34/50] Move grenade bonuses from Demolitions to Throwing (#202) * Copy grenade settings from demolitions to throwing * Fix setting strings * Add throwing bonuses to the same spots as demolitions * Delete grenade bonuses from Demolitions * Update text * Reapply throwing check --- GameSettings.cpp | 6 +++--- GameSettings.h | 6 +++--- Laptop/personnel.cpp | 40 +++++++++++++++++++------------------- Tactical/Points.cpp | 4 ++-- Tactical/Weapons.cpp | 11 +++++------ Utils/_Ja25ChineseText.cpp | 6 +++--- Utils/_Ja25DutchText.cpp | 6 +++--- Utils/_Ja25EnglishText.cpp | 6 +++--- Utils/_Ja25FrenchText.cpp | 6 +++--- Utils/_Ja25GermanText.cpp | 6 +++--- Utils/_Ja25ItalianText.cpp | 6 +++--- Utils/_Ja25PolishText.cpp | 6 +++--- Utils/_Ja25RussianText.cpp | 6 +++--- 13 files changed, 57 insertions(+), 58 deletions(-) diff --git a/GameSettings.cpp b/GameSettings.cpp index 28bf1151..3263243b 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -2785,6 +2785,9 @@ void LoadSkillTraitsExternalSettings() gSkillTraitValues.ubTHBladesSilentCriticalHitChance = iniReader.ReadInteger("Throwing","TH_BLADES_SILENT_CRITICAL_HIT_CHANCE", 20, 0, 100); gSkillTraitValues.ubTHBladesCriticalHitMultiplierBonus = iniReader.ReadInteger("Throwing","SILENT_CRITICAL_HIT_MULTIPLIER_BONUS", 1, 0, 50); gSkillTraitValues.ubTHBladesAimClicksAdded = iniReader.ReadInteger("Throwing","POSSIBLE_AIM_CLICK_ADDED_TH_KNIVES", 1, 0, 5); + gSkillTraitValues.ubTHAPsNeededToThrowGrenadesReduction = iniReader.ReadInteger("Throwing","APS_NEEDED_TO_THROW_GRENADES_REDUCTION", 25, 0, 90); + gSkillTraitValues.ubTHMaxRangeToThrowGrenades = iniReader.ReadInteger("Throwing","MAX_RANGE_TO_THROW_GRENADES", 20, 0, 250); + gSkillTraitValues.ubTHCtHWhenThrowingGrenades = iniReader.ReadInteger("Throwing","CTH_WHEN_THROWING_GRENADES", 30, 0, 100); // NIGHT OPS gSkillTraitValues.ubNOeSightRangeBonusInDark = iniReader.ReadInteger("Night Ops","SIGHT_RANGE_BONUS_IN_DARK", 1, 0, 100); @@ -2811,9 +2814,6 @@ void LoadSkillTraitsExternalSettings() gSkillTraitValues.usBBIncreasedNeededDamageToFallDown = iniReader.ReadInteger("Bodybuilding","INCREASE_DAMAGE_NEEDED_TO_FALL_DOWN_IF_HIT_TO_LEGS", 100, 0, 500); // DEMOLITIONS - gSkillTraitValues.ubDEAPsNeededToThrowGrenadesReduction = iniReader.ReadInteger("Demolitions","APS_NEEDED_TO_THROW_GRENADES_REDUCTION", 25, 0, 90); - gSkillTraitValues.ubDEMaxRangeToThrowGrenades = iniReader.ReadInteger("Demolitions","MAX_RANGE_TO_THROW_GRENADES", 20, 0, 250); - gSkillTraitValues.ubDECtHWhenThrowingGrenades = iniReader.ReadInteger("Demolitions","CTH_WHEN_THROWING_GRENADES", 30, 0, 100); gSkillTraitValues.ubDEDamageOfBombsAndMines = iniReader.ReadInteger("Demolitions","DAMAGE_OF_PLACED_BOMBS_AND_MINES", 25, 0, 250); gSkillTraitValues.ubDEAttachDetonatorCheckBonus = iniReader.ReadInteger("Demolitions","ATTACH_DETONATOR_CHECK_BONUS", 50, 0, 250); gSkillTraitValues.ubDEPlantAndRemoveBombCheckBonus = iniReader.ReadInteger("Demolitions","PLANT_AND_REMOVE_BOMBS_AND_MINES_BONUS", 50, 0, 250); diff --git a/GameSettings.h b/GameSettings.h index 735538f3..207fa3da 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -2177,6 +2177,9 @@ typedef struct UINT8 ubTHBladesSilentCriticalHitChance; UINT8 ubTHBladesCriticalHitMultiplierBonus; UINT8 ubTHBladesAimClicksAdded; + UINT8 ubTHAPsNeededToThrowGrenadesReduction; + UINT8 ubTHMaxRangeToThrowGrenades; + UINT8 ubTHCtHWhenThrowingGrenades; // NIGHT OPS UINT8 ubNOeSightRangeBonusInDark; @@ -2203,9 +2206,6 @@ typedef struct UINT16 usBBIncreasedNeededDamageToFallDown; // DEMOLITIONS - UINT8 ubDEAPsNeededToThrowGrenadesReduction; - UINT8 ubDEMaxRangeToThrowGrenades; - UINT8 ubDECtHWhenThrowingGrenades; UINT8 ubDEDamageOfBombsAndMines; UINT8 ubDEAttachDetonatorCheckBonus; UINT8 ubDEPlantAndRemoveBombCheckBonus; diff --git a/Laptop/personnel.cpp b/Laptop/personnel.cpp index e2576e33..935b5cf0 100644 --- a/Laptop/personnel.cpp +++ b/Laptop/personnel.cpp @@ -8165,6 +8165,21 @@ void AssignPersonnelSkillTraitHelpText( UINT8 ubTraitNumber, BOOLEAN fExpertLeve wcscat( apStr, atStr ); } + if( gSkillTraitValues.ubTHAPsNeededToThrowGrenadesReduction != 0 ) + { + swprintf( atStr, gzIMPMinorTraitsHelpTextsThrowing[10], gSkillTraitValues.ubTHAPsNeededToThrowGrenadesReduction, sSpecialCharacters[0]); + wcscat( apStr, atStr ); + } + if( gSkillTraitValues.ubTHMaxRangeToThrowGrenades != 0 ) + { + swprintf( atStr, gzIMPMinorTraitsHelpTextsThrowing[11], gSkillTraitValues.ubTHMaxRangeToThrowGrenades, sSpecialCharacters[0]); + wcscat( apStr, atStr ); + } + if( gSkillTraitValues.ubTHCtHWhenThrowingGrenades != 0 ) + { + swprintf( atStr, gzIMPMinorTraitsHelpTextsThrowing[12], gSkillTraitValues.ubTHCtHWhenThrowingGrenades, sSpecialCharacters[0]); + wcscat( apStr, atStr ); + } break; } case NIGHT_OPS_NT: @@ -8270,44 +8285,29 @@ void AssignPersonnelSkillTraitHelpText( UINT8 ubTraitNumber, BOOLEAN fExpertLeve case DEMOLITIONS_NT: { swprintf( apStr, L"" ); - if( gSkillTraitValues.ubDEAPsNeededToThrowGrenadesReduction != 0 ) - { - swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[0], gSkillTraitValues.ubDEAPsNeededToThrowGrenadesReduction, sSpecialCharacters[0]); - wcscat( apStr, atStr ); - } - if( gSkillTraitValues.ubDEMaxRangeToThrowGrenades != 0 ) - { - swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[1], gSkillTraitValues.ubDEMaxRangeToThrowGrenades, sSpecialCharacters[0]); - wcscat( apStr, atStr ); - } - if( gSkillTraitValues.ubDECtHWhenThrowingGrenades != 0 ) - { - swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[2], gSkillTraitValues.ubDECtHWhenThrowingGrenades, sSpecialCharacters[0]); - wcscat( apStr, atStr ); - } if( gSkillTraitValues.ubDEDamageOfBombsAndMines != 0 ) { - swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[3], gSkillTraitValues.ubDEDamageOfBombsAndMines, sSpecialCharacters[0]); + swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[0], gSkillTraitValues.ubDEDamageOfBombsAndMines, sSpecialCharacters[0]); wcscat( apStr, atStr ); } if( gSkillTraitValues.ubDEAttachDetonatorCheckBonus != 0 ) { - swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[4], gSkillTraitValues.ubDEAttachDetonatorCheckBonus, sSpecialCharacters[0]); + swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[1], gSkillTraitValues.ubDEAttachDetonatorCheckBonus, sSpecialCharacters[0]); wcscat( apStr, atStr ); } if( gSkillTraitValues.ubDEPlantAndRemoveBombCheckBonus != 0 ) { - swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[5], gSkillTraitValues.ubDEPlantAndRemoveBombCheckBonus, sSpecialCharacters[0]); + swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[2], gSkillTraitValues.ubDEPlantAndRemoveBombCheckBonus, sSpecialCharacters[0]); wcscat( apStr, atStr ); } if( gSkillTraitValues.ubDEPlacedBombLevelBonus != 0 ) { - swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[6], gSkillTraitValues.ubDEPlacedBombLevelBonus); + swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[3], gSkillTraitValues.ubDEPlacedBombLevelBonus); wcscat( apStr, atStr ); } if( gSkillTraitValues.ubDEShapedChargeDamageMultiplier != 0 ) { - swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[7], gSkillTraitValues.ubDEShapedChargeDamageMultiplier); + swprintf( atStr, gzIMPMinorTraitsHelpTextsDemolitions[4], gSkillTraitValues.ubDEShapedChargeDamageMultiplier); wcscat( apStr, atStr ); } break; diff --git a/Tactical/Points.cpp b/Tactical/Points.cpp index 020d8df1..f16aacc8 100644 --- a/Tactical/Points.cpp +++ b/Tactical/Points.cpp @@ -3640,11 +3640,11 @@ INT16 MinAPsToThrow( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubAddTurningCos iAPCost += ( ( ( 100 * iTop ) / iBottom) + 1) / 2; // SANDRO - STOMP traits - reduce APs needed to throw grenades if having Demolitions skill - if( HAS_SKILL_TRAIT( pSoldier, DEMOLITIONS_NT ) && gGameOptions.fNewTraitSystem ) + if( HAS_SKILL_TRAIT( pSoldier, THROWING_NT ) && gGameOptions.fNewTraitSystem ) { if ( grenadAPreductionpossible ) { - iAPCost = max( 1, (INT32)(iAPCost * (100 - gSkillTraitValues.ubDEAPsNeededToThrowGrenadesReduction) / 100)); + iAPCost = max( 1, (INT32)(iAPCost * (100 - gSkillTraitValues.ubTHAPsNeededToThrowGrenadesReduction) / 100)); } } diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index b635debe..55acbf88 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -10175,10 +10175,10 @@ INT32 CalcMaxTossRange( SOLDIERTYPE * pSoldier, UINT16 usItem, BOOLEAN fArmed, O iRange += ((iRange * gSkillTraitValues.ubTHBladesMaxRange ) / 100); } // sevenfm: add range only for hand grenades and not launched grenades - else if ( (Item[ usItem ].usItemClass == IC_GRENADE) && Item[usItem].ubCursor == TOSSCURS && (HAS_SKILL_TRAIT( pSoldier, DEMOLITIONS_NT )) ) + else if ( (Item[ usItem ].usItemClass == IC_GRENADE) && Item[usItem].ubCursor == TOSSCURS && HAS_SKILL_TRAIT( pSoldier, THROWING_NT ) ) { // better max range due to expertise - iRange += ((iRange * gSkillTraitValues.ubDEMaxRangeToThrowGrenades) / 100); + iRange += ((iRange * gSkillTraitValues.ubTHMaxRangeToThrowGrenades) / 100); } } else @@ -10260,9 +10260,8 @@ UINT32 CalcThrownChanceToHit(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTi else { iChance += gSkillTraitValues.bCtHModifierThrowingGrenades; // -10% for untrained mercs - - if ( HAS_SKILL_TRAIT( pSoldier, DEMOLITIONS_NT ) ) - iChance += gSkillTraitValues.ubDECtHWhenThrowingGrenades; // +30% chance + if ( HAS_SKILL_TRAIT( pSoldier, THROWING_NT ) ) + iChance += gSkillTraitValues.ubTHCtHWhenThrowingGrenades; } } else @@ -12196,4 +12195,4 @@ BOOLEAN ArtilleryStrike( UINT16 usItem, UINT8 ubOwnerID, UINT32 usStartingGridNo //REAL_OBJECT* pObject = &( ObjectSlots[ iID ] ); return TRUE; -} \ No newline at end of file +} diff --git a/Utils/_Ja25ChineseText.cpp b/Utils/_Ja25ChineseText.cpp index 1a6feedd..919f2d14 100644 --- a/Utils/_Ja25ChineseText.cpp +++ b/Utils/_Ja25ChineseText.cpp @@ -310,6 +310,9 @@ STR16 gzIMPMinorTraitsHelpTextsThrowing[]= L"飞刀致命一击的额外伤害倍率 +%d\n",// L"+%d critical hit by throwing blade multiplier\n", L"飞刀的最大精瞄次数 +%d\n",// L"Adds %d more aim click for throwing blades\n", L"飞刀的最大精瞄次数 +%d\n",// L"Adds %d more aim clicks for throwing blades\n", + L"投掷手榴弹所需行动点 -%d%s\n",// L"-%d%s APs needed to throw grenades\n", + L"手榴弹最远投掷距离 +%d%s\n",// L"+%d%s max range when throwing grenades\n", + L"手榴弹的命中率 +%d%s\n",// L"+%d%s chance to hit when throwing grenades\n", }; STR16 gzIMPMinorTraitsHelpTextsNightOps[]= @@ -346,9 +349,6 @@ STR16 gzIMPMinorTraitsHelpTextsBodybuilding[]= STR16 gzIMPMinorTraitsHelpTextsDemolitions[]= { - L"投掷手榴弹所需行动点 -%d%s\n",// L"-%d%s APs needed to throw grenades\n", - L"手榴弹最远投掷距离 +%d%s\n",// L"+%d%s max range when throwing grenades\n", - L"手榴弹的命中率 +%d%s\n",// L"+%d%s chance to hit when throwing grenades\n", L"安置的炸弹和地雷的伤害 +%d%s\n",// L"+%d%s damage of set bombs and mines\n", L"组合炸弹的成功率 +%d%s\n",// L"+%d%s to attaching detonators check\n", L"安置/拆除炸弹成功率 +%d%s\n",// L"+%d%s to planting/removing bombs check\n", diff --git a/Utils/_Ja25DutchText.cpp b/Utils/_Ja25DutchText.cpp index 4548c46e..a2234862 100644 --- a/Utils/_Ja25DutchText.cpp +++ b/Utils/_Ja25DutchText.cpp @@ -310,6 +310,9 @@ STR16 gzIMPMinorTraitsHelpTextsThrowing[]= L"+%d critical hit with throwing blade multiplier\n", L"Adds %d more aim click for throwing blades\n", L"Adds %d more aim clicks for throwing blades\n", + L"-%d%s APs to throw grenades\n", + L"+%d%s max range when throwing grenades\n", + L"+%d%s CtH when throwing grenades\n", }; STR16 gzIMPMinorTraitsHelpTextsNightOps[]= @@ -346,9 +349,6 @@ STR16 gzIMPMinorTraitsHelpTextsBodybuilding[]= STR16 gzIMPMinorTraitsHelpTextsDemolitions[]= { - L"-%d%s APs to throw grenades\n", - L"+%d%s max range when throwing grenades\n", - L"+%d%s CtH when throwing grenades\n", L"+%d%s damage for set bombs and mines\n", L"+%d%s to attaching detonators check\n", L"+%d%s to planting/removing bombs check\n", diff --git a/Utils/_Ja25EnglishText.cpp b/Utils/_Ja25EnglishText.cpp index 63eb232a..b58a080d 100644 --- a/Utils/_Ja25EnglishText.cpp +++ b/Utils/_Ja25EnglishText.cpp @@ -310,6 +310,9 @@ STR16 gzIMPMinorTraitsHelpTextsThrowing[]= L"+%d critical hit with throwing blade multiplier\n", L"Adds %d more aim click for throwing blades\n", L"Adds %d more aim clicks for throwing blades\n", + L"-%d%s APs to throw grenades\n", + L"+%d%s max range when throwing grenades\n", + L"+%d%s CtH when throwing grenades\n", }; STR16 gzIMPMinorTraitsHelpTextsNightOps[]= @@ -346,9 +349,6 @@ STR16 gzIMPMinorTraitsHelpTextsBodybuilding[]= STR16 gzIMPMinorTraitsHelpTextsDemolitions[]= { - L"-%d%s APs to throw grenades\n", - L"+%d%s max range when throwing grenades\n", - L"+%d%s CtH when throwing grenades\n", L"+%d%s damage for set bombs and mines\n", L"+%d%s to attaching detonators check\n", L"+%d%s to planting/removing bombs check\n", diff --git a/Utils/_Ja25FrenchText.cpp b/Utils/_Ja25FrenchText.cpp index 290158fc..f20af04a 100644 --- a/Utils/_Ja25FrenchText.cpp +++ b/Utils/_Ja25FrenchText.cpp @@ -309,6 +309,9 @@ STR16 gzIMPMinorTraitsHelpTextsThrowing[]= L"+%d de dégâts critiques, si vous lancez plusieurs couteaux\n", L"Ajoute %d niveau(x) de visée pour lancer un couteau\n", L"Ajoute %d niveau(x) de visée pour lancer un couteau\n", + L"-%d%s du nombre de PA nécessaire pour lancer une grenade\n", + L"+%d%s de la portée maximale d'une grenade\n", + L"+%d%s de chance de toucher votre cible avec une grenade\n", }; STR16 gzIMPMinorTraitsHelpTextsNightOps[]= @@ -344,9 +347,6 @@ STR16 gzIMPMinorTraitsHelpTextsBodybuilding[]= }; STR16 gzIMPMinorTraitsHelpTextsDemolitions[]= { - L"-%d%s du nombre de PA nécessaire pour lancer une grenade\n", - L"+%d%s de la portée maximale d'une grenade\n", - L"+%d%s de chance de toucher votre cible avec une grenade\n", L"+%d%s de dégâts causés par un explosif ou une mine\n", L"+%d%s pour contrôler un détonateur\n", L"+%d%s pour placer/retirer un contrôleur d'explosif\n", diff --git a/Utils/_Ja25GermanText.cpp b/Utils/_Ja25GermanText.cpp index c1e9666e..a19fa526 100644 --- a/Utils/_Ja25GermanText.cpp +++ b/Utils/_Ja25GermanText.cpp @@ -310,6 +310,9 @@ STR16 gzIMPMinorTraitsHelpTextsThrowing[]= L"+%d Multiplikator für kritische Treffer durch Wurfwaffen\n", L"Gibt einen weiteren Zielklick beim Einsatz von Wurfwaffen\n", L"Gibt %d weitere Zielklicks beim Einsatz von Wurfwaffen\n", + L"-%d%s APs benötigt um Handgranaten (und ähnliche Objekte) zu werfen\n", + L"+%d%s höhere Reichweite beim Werfen von Handgranaten (und ähnliche Objekten)\n", + L"+%d%s höhere Trefferwahrscheinlichkeit beim Werfen von Handgranaten (und ähnlichen Objekten)\n", }; STR16 gzIMPMinorTraitsHelpTextsNightOps[]= @@ -346,9 +349,6 @@ STR16 gzIMPMinorTraitsHelpTextsBodybuilding[]= STR16 gzIMPMinorTraitsHelpTextsDemolitions[]= { - L"-%d%s APs benötigt um Handgranaten (und ähnliche Objekte) zu werfen\n", - L"+%d%s höhere Reichweite beim Werfen von Handgranaten (und ähnliche Objekten)\n", - L"+%d%s höhere Trefferwahrscheinlichkeit beim Werfen von Handgranaten (und ähnlichen Objekten)\n", L"+%d%s höherer Schaden für gelegte Bomben und Minen\n", L"+%d%s mehr Erfolg beim Anbringen von Zündern\n", L"+%d%s mehr Erfolg beim Legen und Entschärfen von Bomben\n", diff --git a/Utils/_Ja25ItalianText.cpp b/Utils/_Ja25ItalianText.cpp index 5bd9c790..4ffb9454 100644 --- a/Utils/_Ja25ItalianText.cpp +++ b/Utils/_Ja25ItalianText.cpp @@ -308,6 +308,9 @@ STR16 gzIMPMinorTraitsHelpTextsThrowing[]= L"+%d critical hit with throwing blade multiplier\n", L"Adds %d more aim click for throwing blades\n", L"Adds %d more aim clicks for throwing blades\n", + L"-%d%s APs to throw grenades\n", + L"+%d%s max range when throwing grenades\n", + L"+%d%s CtH when throwing grenades\n", }; STR16 gzIMPMinorTraitsHelpTextsNightOps[]= @@ -344,9 +347,6 @@ STR16 gzIMPMinorTraitsHelpTextsBodybuilding[]= STR16 gzIMPMinorTraitsHelpTextsDemolitions[]= { - L"-%d%s APs to throw grenades\n", - L"+%d%s max range when throwing grenades\n", - L"+%d%s CtH when throwing grenades\n", L"+%d%s damage for set bombs and mines\n", L"+%d%s to attaching detonators check\n", L"+%d%s to planting/removing bombs check\n", diff --git a/Utils/_Ja25PolishText.cpp b/Utils/_Ja25PolishText.cpp index 506e37c1..9446a5b2 100644 --- a/Utils/_Ja25PolishText.cpp +++ b/Utils/_Ja25PolishText.cpp @@ -310,6 +310,9 @@ STR16 gzIMPMinorTraitsHelpTextsThrowing[]= L"+%d critical hit with throwing blade multiplier\n", L"Adds %d more aim click for throwing blades\n", L"Adds %d more aim clicks for throwing blades\n", + L"-%d%s APs to throw grenades\n", + L"+%d%s max range when throwing grenades\n", + L"+%d%s CtH when throwing grenades\n", }; STR16 gzIMPMinorTraitsHelpTextsNightOps[]= @@ -346,9 +349,6 @@ STR16 gzIMPMinorTraitsHelpTextsBodybuilding[]= STR16 gzIMPMinorTraitsHelpTextsDemolitions[]= { - L"-%d%s APs to throw grenades\n", - L"+%d%s max range when throwing grenades\n", - L"+%d%s CtH when throwing grenades\n", L"+%d%s damage for set bombs and mines\n", L"+%d%s to attaching detonators check\n", L"+%d%s to planting/removing bombs check\n", diff --git a/Utils/_Ja25RussianText.cpp b/Utils/_Ja25RussianText.cpp index 022b1f59..8ce7b35f 100644 --- a/Utils/_Ja25RussianText.cpp +++ b/Utils/_Ja25RussianText.cpp @@ -310,6 +310,9 @@ STR16 gzIMPMinorTraitsHelpTextsThrowing[]= L"+%d к множителю критического урона при броске ножа\n", L"+%d клик прицеливания для метательных ножей\n", L"+%d кликов прицеливания для метательных ножей\n", + L"-%d%s ОД для броска гранаты\n", + L"+%d%s к максимальной дальности броска гранаты\n", + L"+%d%s к точности броска гранаты\n", }; STR16 gzIMPMinorTraitsHelpTextsNightOps[]= @@ -346,9 +349,6 @@ STR16 gzIMPMinorTraitsHelpTextsBodybuilding[]= STR16 gzIMPMinorTraitsHelpTextsDemolitions[]= { - L"-%d%s ОД для броска гранаты\n", - L"+%d%s к максимальной дальности броска гранаты\n", - L"+%d%s к точности броска гранаты\n", L"+%d%s к урону для установленных бомб и мин\n", L"+%d%s к умению устанавливать детонатор\n", L"+%d%s к установке/разминированию бомб\n", From 5400fb019dcd4c97f7445474fa73cc94ce720981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Fa=C5=82kowski?= Date: Sat, 19 Aug 2023 22:30:57 +0200 Subject: [PATCH 35/50] Loading screen multiple resolutions support and stretch modes, loading bar fixes --- GameSettings.cpp | 4 +- GameSettings.h | 1 + Loading Screen.cpp | 127 ++++++++++++++++++++++++--------- Loading Screen.h | 2 + SaveLoadGame.cpp | 18 ++--- Utils/Animated ProgressBar.cpp | 57 ++++++++++----- 6 files changed, 149 insertions(+), 60 deletions(-) diff --git a/GameSettings.cpp b/GameSettings.cpp index 3791ae0c..349fa561 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -1147,7 +1147,9 @@ void LoadGameExternalOptions() giTimerIntervals[ NEXTSCROLL ] = (INT16)(giTimerIntervals[ NEXTSCROLL ] / gGameExternalOptions.fScrollSpeedFactor); gGameExternalOptions.gfUseExternalLoadscreens = iniReader.ReadBoolean("Graphics Settings","USE_EXTERNALIZED_LOADSCREENS", FALSE); - + + gGameExternalOptions.ubLoadscreenStretchMode = iniReader.ReadInteger("Graphics Settings", "LOADSCREEN_STRETCH_MODE", 0, 0, 2); + if (!is_networked) gGameExternalOptions.gfUseLoadScreenHints = iniReader.ReadBoolean("Graphics Settings","USE_LOADSCREENHINTS", TRUE); else diff --git a/GameSettings.h b/GameSettings.h index 5bdeb6dc..523066f5 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -781,6 +781,7 @@ typedef struct INT32 ubEnemiesItemDrop; BOOLEAN gfUseExternalLoadscreens; + UINT32 ubLoadscreenStretchMode; // added by anv BOOLEAN gfUseLoadScreenHints; // added by Flugente UINT32 ubAdditionalDelayUntilLoadScreenDisposal; // added by WANNE to have time to read the load screen hints diff --git a/Loading Screen.cpp b/Loading Screen.cpp index a0641d3b..2650cc9f 100644 --- a/Loading Screen.cpp +++ b/Loading Screen.cpp @@ -23,6 +23,7 @@ extern BOOLEAN gfSchedulesHosed; #include "Ja25 Strategic Ai.h" #endif UINT8 gubLastLoadingScreenID = LOADINGSCREEN_NOTHING; +FLOAT fLoadingScreenAspectRatio; //BOOLEAN bShowSmallImage = FALSE; SECTOR_LOADSCREENS gSectorLoadscreens[MAX_SECTOR_LOADSCREENS]; @@ -343,6 +344,62 @@ static void BuildLoadscreenFilename(std::string& dst, const char* path, int reso dst.append(".sti"); } +std::string GetResolutionSuffix(SCREEN_RESOLUTION resolution) +{ + switch (resolution) + { + case _960x540: return "_960x540"; + case _800x600: return "_800x600"; + case _1024x600: return "_1024x600"; + case _1280x720: return "_1280x720"; + case _1024x768: return "_1024x768"; + case _1280x768: return "_1280x768"; + case _1360x768: return "_1360x768"; + case _1366x768: return "_1366x768"; + case _1280x800: return "_1280x800"; + case _1440x900: return "_1440x900"; + case _1600x900: return "_1600x900"; + case _1280x960: return "_1280x960"; + case _1440x960: return "_1440x960"; + case _1770x1000: return "_1770x1000"; + case _1280x1024: return "_1280x1024"; + case _1360x1024: return "_1360x1024"; + case _1600x1024: return "_1600x1024"; + case _1440x1050: return "_1440x1050"; + case _1680x1050: return "_1680x1050"; + case _1920x1080: return "_1920x1080"; + case _1600x1200: return "_1600x1200"; + case _1920x1200: return "_1920x1200"; + case _2560x1440: return "_2560x1440"; + case _2560x1600: return "_2560x1600"; + default: return ""; + } +} + +std::string FindBestFittingLoadscreenFilename(const std::string& baseName, SCREEN_RESOLUTION resolution) +{ + for (SCREEN_RESOLUTION res = resolution; res <= _2560x1600; res = (SCREEN_RESOLUTION)(res + 1)) + { + std::string fileName = baseName + GetResolutionSuffix(res); + if (FileExists((CHAR8*)((fileName + ".png").c_str()))) + { + return fileName + ".png"; + } + + if (FileExists((CHAR8*)((fileName + ".sti").c_str()))) + { + return fileName + ".sti"; + } + } + + if (FileExists((CHAR8*)((baseName + ".png").c_str()))) + { + return baseName + ".png"; + } + + return baseName + ".sti"; +} + //sets up the loadscreen with specified ID, and draws it to the FRAME_BUFFER, //and refreshing the screen with it. void DisplayLoadScreenWithID( UINT8 ubLoadScreenID ) @@ -420,27 +477,8 @@ void DisplayLoadScreenWithID( UINT8 ubLoadScreenID ) } } - std::string strImage; - - BuildLoadscreenFilename(strImage, imagePath.c_str(), 0, imageFormat.c_str()); - strImage.copy(vs_desc.ImageFile, sizeof(vs_desc.ImageFile)-1); - - - if ( !FileExists(vs_desc.ImageFile) ) - { - std::string strImage; - BuildLoadscreenFilename(strImage, imagePath.c_str(), 0, "png"); - strImage.copy(vs_desc.ImageFile, sizeof(vs_desc.ImageFile) - 1); - - if (!FileExists(vs_desc.ImageFile)) - { - std::string strImage("LOADSCREENS\\"); - - BuildLoadscreenFilename(strImage, LoadScreenNames[1], 0, imageFormat.c_str()); - - strImage.copy(vs_desc.ImageFile, sizeof(vs_desc.ImageFile) - 1); - } - } + std::string strImage = FindBestFittingLoadscreenFilename(imagePath, (SCREEN_RESOLUTION)iResolution); + strImage.copy(vs_desc.ImageFile, sizeof(vs_desc.ImageFile) - 1); } else { @@ -475,18 +513,41 @@ void DisplayLoadScreenWithID( UINT8 ubLoadScreenID ) //Blit the background image GetVideoSurface(&hVSurface, uiLoadScreen); - - // Stretch the background image - SrcRect.iLeft = 0; - SrcRect.iTop = 0; - SrcRect.iRight = hVSurface->usWidth; - SrcRect.iBottom = hVSurface->usHeight; - - DstRect.iLeft = 0; - DstRect.iTop = 0; - DstRect.iRight = SCREEN_WIDTH; - DstRect.iBottom = SCREEN_HEIGHT; - + + fLoadingScreenAspectRatio = (FLOAT)hVSurface->usWidth / (FLOAT)hVSurface->usHeight; + FLOAT fScreenAspectRatio = (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT; + + if (gGameExternalOptions.ubLoadscreenStretchMode == 1 || + (gGameExternalOptions.ubLoadscreenStretchMode == 2 && fLoadingScreenAspectRatio > fScreenAspectRatio)) + { + // match height, preserve aspect ratio + INT32 iCalculatedWidth = (INT32)(SCREEN_HEIGHT * fLoadingScreenAspectRatio + 0.5f); + + SrcRect.iLeft = 0; + SrcRect.iTop = 0; + SrcRect.iRight = hVSurface->usWidth; + SrcRect.iBottom = hVSurface->usHeight; + + DstRect.iLeft = (SCREEN_WIDTH - iCalculatedWidth) / 2; + DstRect.iTop = 0; + DstRect.iRight = SCREEN_WIDTH - ((SCREEN_WIDTH - iCalculatedWidth) / 2); + DstRect.iBottom = SCREEN_HEIGHT; + } + else + { + // vanilla (stretch to fit) + // Stretch the background image + SrcRect.iLeft = 0; + SrcRect.iTop = 0; + SrcRect.iRight = hVSurface->usWidth; + SrcRect.iBottom = hVSurface->usHeight; + + DstRect.iLeft = 0; + DstRect.iTop = 0; + DstRect.iRight = SCREEN_WIDTH; + DstRect.iBottom = SCREEN_HEIGHT; + } + BltStretchVideoSurface( FRAME_BUFFER, uiLoadScreen, 0, 0, 0, &SrcRect, &DstRect ); DeleteVideoSurfaceFromIndex( uiLoadScreen ); diff --git a/Loading Screen.h b/Loading Screen.h index fa5bc658..831c9e2b 100644 --- a/Loading Screen.h +++ b/Loading Screen.h @@ -84,6 +84,8 @@ enum //For use by the game loader, before it can possibly know the situation. extern UINT8 gubLastLoadingScreenID; +extern FLOAT fLoadingScreenAspectRatio; + //returns the UINT8 ID for the specified sector. UINT8 GetLoadScreenID( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ ); diff --git a/SaveLoadGame.cpp b/SaveLoadGame.cpp index 5c245e84..844e49d6 100644 --- a/SaveLoadGame.cpp +++ b/SaveLoadGame.cpp @@ -6694,15 +6694,6 @@ BOOLEAN LoadSavedGame( int ubSavedGameID ) //Reset the Ai Timer clock giRTAILastUpdateTime = 0; - //if we are in tactical - if( guiScreenToGotoAfterLoadingSavedGame == GAME_SCREEN ) - { - //Initialize the current panel - InitializeCurrentPanel( ); - - SelectSoldier( gusSelectedSoldier, FALSE, TRUE ); - } - uiRelEndPerc += 1; SetRelativeStartAndEndPercentage( 0, uiRelStartPerc, uiRelEndPerc, L"Final Checks..." ); RenderProgressBar( 0, 100 ); @@ -6831,6 +6822,15 @@ BOOLEAN LoadSavedGame( int ubSavedGameID ) RemoveLoadingScreenProgressBar(); + //if we are in tactical + if (guiScreenToGotoAfterLoadingSavedGame == GAME_SCREEN) + { + //Initialize the current panel + InitializeCurrentPanel(); + + SelectSoldier(gusSelectedSoldier, FALSE, TRUE); + } + // sevenfm: reset sound map ResetSoundMap(); diff --git a/Utils/Animated ProgressBar.cpp b/Utils/Animated ProgressBar.cpp index 652d5f05..5cdfa0ab 100644 --- a/Utils/Animated ProgressBar.cpp +++ b/Utils/Animated ProgressBar.cpp @@ -14,6 +14,7 @@ #include "WordWrap.h" #include "Message.h" #include "Text.h" + #include "Loading Screen.h" double rStart, rEnd; double rActual; @@ -63,10 +64,31 @@ void CreateLoadingScreenProgressBar(BOOLEAN resetLoadScreenHint) // CreateProgressBar(0, 259 + ((SCREEN_WIDTH - 1024) / 2), 683 + ((SCREEN_HEIGHT - 768) / 2), 767 + ((SCREEN_WIDTH - 1024) / 2), 708 + ((SCREEN_HEIGHT - 768) / 2)); // } //} - - CreateProgressBar(0, SCREEN_WIDTH*162/640, SCREEN_HEIGHT*427/480, SCREEN_WIDTH*480/640, SCREEN_HEIGHT*443/480); + FLOAT fScreenAspectRatio = (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT; + if (gGameExternalOptions.ubLoadscreenStretchMode == 1 || + (gGameExternalOptions.ubLoadscreenStretchMode == 2 && fLoadingScreenAspectRatio > fScreenAspectRatio)) + { + // match height, preserve aspect ratioernalOptions.ubLoadscreenStretchMode == 2 && fLoadingScreenAspectRatio > fScreenAspectRatio)) + INT32 iCalculatedWidth = (INT32)(SCREEN_HEIGHT * fLoadingScreenAspectRatio + 0.5f); + + UINT16 usLeft = (UINT16)((SCREEN_WIDTH - iCalculatedWidth) / 2 + (iCalculatedWidth * 162.0f / 640.0f) + 0.5f); + UINT16 usTop = (UINT16)((SCREEN_HEIGHT * 427.0f / 480.0f) + 0.5f); + UINT16 usRight = (UINT16)((SCREEN_WIDTH - iCalculatedWidth) / 2 + (iCalculatedWidth * 478.0f / 640.0f) + 0.5f); + UINT16 usBottom = (UINT16)((SCREEN_HEIGHT * 443.0f / 480.0f) + 0.5f); + + CreateProgressBar(0, usLeft, usTop, usRight, usBottom); + } + else + { + UINT16 usLeft = (UINT16)((SCREEN_WIDTH * 162.0f / 640.0f) + 0.5f); + UINT16 usTop = (UINT16)((SCREEN_HEIGHT * 427.0f / 480.0f) + 0.5f); + UINT16 usRight = (UINT16)((SCREEN_WIDTH * 478.0f / 640.0f) + 0.5f); + UINT16 usBottom = (UINT16)((SCREEN_HEIGHT * 443.0f / 480.0f) + 0.5f); + + CreateProgressBar(0, usLeft, usTop, usRight, usBottom); + } SetProgressBarUseBorder(0, FALSE ); } @@ -334,6 +356,8 @@ void SetRelativeStartAndEndPercentage( UINT8 ubID, UINT16 uiRelStartPerc, UINT16 pCurr->rStart = (double)uiRelStartPerc*0.01f; pCurr->rEnd = (double)uiRelEndPerc*0.01f; + UINT8 yTextOffset = (UINT8)(3.0f * SCREEN_HEIGHT / 480.0f + 0.5f); + //Render the entire panel now, as it doesn't need update during the normal rendering if( pCurr->fPanel ) { @@ -352,7 +376,7 @@ void SetRelativeStartAndEndPercentage( UINT8 ubID, UINT16 uiRelStartPerc, UINT16 usStartX = pCurr->usPanelLeft + // left position (pCurr->usPanelRight - pCurr->usPanelLeft)/2 - // + half width StringPixLength( pCurr->swzTitle, pCurr->usTitleFont ) / 2; // - half string width - usStartY = pCurr->usPanelTop + 3; + usStartY = pCurr->usPanelTop + yTextOffset; SetFont( pCurr->usTitleFont ); SetFontForeground( pCurr->ubTitleFontForeColor ); SetFontShadow( pCurr->ubTitleFontShadowColor ); @@ -370,21 +394,21 @@ void SetRelativeStartAndEndPercentage( UINT8 ubID, UINT16 uiRelStartPerc, UINT16 { UINT16 usFontHeight = GetFontHeight( pCurr->usMsgFont ); - RestoreExternBackgroundRect( pCurr->usBarLeft, pCurr->usBarBottom, (INT16)(pCurr->usBarRight-pCurr->usBarLeft), (INT16)(usFontHeight + 3) ); + RestoreExternBackgroundRect( pCurr->usBarLeft, pCurr->usBarBottom, (INT16)(pCurr->usBarRight-pCurr->usBarLeft), (INT16)(usFontHeight + yTextOffset) ); } SetFont( pCurr->usMsgFont ); SetFontForeground( pCurr->ubMsgFontForeColor ); SetFontShadow( pCurr->ubMsgFontShadowColor ); SetFontBackground( 0 ); - mprintf( pCurr->usBarLeft, pCurr->usBarBottom + 3, str ); + mprintf( pCurr->usBarLeft, pCurr->usBarBottom + yTextOffset, str ); } } // Flugente: loadscreen hints if (gGameExternalOptions.gfUseLoadScreenHints && usCurrentLoadScreenHint ) { - ShowLoadScreenHintInLoadScreen(pCurr->usBarBottom + 3 - 100); + ShowLoadScreenHintInLoadScreen(pCurr->usBarBottom + yTextOffset - 100); } } @@ -408,25 +432,24 @@ void RenderProgressBar( UINT8 ubID, UINT32 uiPercentage ) if( pCurr ) { - rActual = pCurr->rStart+(pCurr->rEnd-pCurr->rStart)*uiPercentage*0.01; + rActual = pCurr->rStart + (pCurr->rEnd - pCurr->rStart) * uiPercentage * 0.01; - if( fabs(rActual - pCurr->rLastActual) < 0.01 ) + pCurr->rLastActual = (DOUBLE)((INT32)(std::round(rActual * 100)) * 0.01); + + end = (INT32)(pCurr->usBarLeft + std::round(rActual * (pCurr->usBarRight - pCurr->usBarLeft))); + if (end < pCurr->usBarLeft) { - return; + end = pCurr->usBarLeft; } - - pCurr->rLastActual = ( DOUBLE )( ( INT32)( rActual * 100 ) * 0.01 ); - - end = (INT32)(pCurr->usBarLeft+2.0+rActual*(pCurr->usBarRight-pCurr->usBarLeft-4)); - if( end < pCurr->usBarLeft+2 || end > pCurr->usBarRight-2 ) + else if (end > pCurr->usBarRight) { - return; + end = pCurr->usBarRight; } if( !pCurr->fDrawBorder ) { ColorFillVideoSurfaceArea( pCurr->uiFrameBuffer, //FRAME_BUFFER, pCurr->usBarLeft, pCurr->usBarTop, end, pCurr->usBarBottom, - Get16BPPColor(FROMRGB( pCurr->ubColorFillRed, pCurr->ubColorFillGreen, pCurr->ubColorFillBlue )) ); + Get16BPPColor(FROMRGB(pCurr->ubColorFillRed, pCurr->ubColorFillGreen, pCurr->ubColorFillBlue))); //if( pCurr->usBarRight > gusLeftmostShaded ) //{ // ShadowVideoSurfaceRect( FRAME_BUFFER, gusLeftmostShaded+1, pCurr->usBarTop, end, pCurr->usBarBottom ); @@ -493,7 +516,7 @@ void SetProgressBarTextDisplayFlag( UINT8 ubID, BOOLEAN fDisplayText, BOOLEAN fU //if we are to use the save buffer, blit the portion of the screen to the save buffer if( fSaveScreenToFrameBuffer ) { - UINT16 usFontHeight = GetFontHeight( pCurr->usMsgFont )+3; + UINT16 usFontHeight = GetFontHeight(pCurr->usMsgFont) + (UINT8)(3.0f * SCREEN_HEIGHT / 480.f + 0.5f); //blit everything to the save buffer ( cause the save buffer can bleed through ) BlitBufferToBuffer(guiRENDERBUFFER, guiSAVEBUFFER, pCurr->usBarLeft, pCurr->usBarBottom, (UINT16)(pCurr->usBarRight-pCurr->usBarLeft), usFontHeight ); From 2060022e944e6171abd8937cc60ee19cf184af2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Fa=C5=82kowski?= Date: Sun, 20 Aug 2023 19:52:34 +0200 Subject: [PATCH 36/50] Battle Panel Multi Resolution (#203) * Battle panel interface with support for multiple resolutions and scrollable list * Support for localized versions of battle panel * Removed commented out code * Fixed wrong copypasta in German version --- Strategic/Auto Resolve.cpp | 4 +- Strategic/Map Screen Interface Bottom.cpp | 2 +- Strategic/PreBattle Interface.cpp | 324 +++++++++++++--------- Strategic/PreBattle Interface.h | 1 + Strategic/mapscreen.cpp | 38 ++- Utils/Multi Language Graphic Utils.cpp | 36 +++ Utils/Multi Language Graphic Utils.h | 3 + 7 files changed, 268 insertions(+), 140 deletions(-) diff --git a/Strategic/Auto Resolve.cpp b/Strategic/Auto Resolve.cpp index 04fa2d56..2029541e 100644 --- a/Strategic/Auto Resolve.cpp +++ b/Strategic/Auto Resolve.cpp @@ -681,7 +681,7 @@ UINT32 AutoResolveScreenHandle() SGPRect ClipRect; gpAR->fEnteringAutoResolve = FALSE; //Take the framebuffer, shade it, and save it to the SAVEBUFFER. - ClipRect.iLeft = 0 + xResOffset; + ClipRect.iLeft = 0; ClipRect.iTop = 0; /*ClipRect.iRight = 640; ClipRect.iBottom = 480;*/ @@ -692,7 +692,7 @@ UINT32 AutoResolveScreenHandle() Blt16BPPBufferShadowRect( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect ); UnLockVideoSurface( FRAME_BUFFER ); //BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 0, 0, 640, 480 ); - BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 0 + xResOffset, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); + BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); KillPreBattleInterface(); CalculateAutoResolveInfo(); CalculateSoldierCells( FALSE ); diff --git a/Strategic/Map Screen Interface Bottom.cpp b/Strategic/Map Screen Interface Bottom.cpp index 0c5530ab..1cb96593 100644 --- a/Strategic/Map Screen Interface Bottom.cpp +++ b/Strategic/Map Screen Interface Bottom.cpp @@ -1936,7 +1936,7 @@ BOOLEAN AllowedToExitFromMapscreenTo( INT8 bExitToWhere ) } // battle about to occur? - if( ( fDisableDueToBattleRoster ) || ( fDisableMapInterfaceDueToBattle ) ) + if( ( fDisableDueToBattleRoster ) || ( fDisableMapInterfaceDueToBattle ) || ( gfPreBattleInterfaceActive )) { return( FALSE ); } diff --git a/Strategic/PreBattle Interface.cpp b/Strategic/PreBattle Interface.cpp index f05cf42e..a26c7ab8 100644 --- a/Strategic/PreBattle Interface.cpp +++ b/Strategic/PreBattle Interface.cpp @@ -48,6 +48,7 @@ #include "militiasquads.h" // added by Flugente #include "SkillCheck.h" // added by Flugente #include "Strategic Transport Groups.h" + #include "Utilities.h" #ifdef JA2UB #include "ub_config.h" @@ -99,13 +100,23 @@ enum //GraphicIDs for the panel #define ROW_HEIGHT 10 //The start of the black space #define TOP_Y 113 +#define TOP_Y_TEXT_BUFFER 1 //The end of the black space -//#define BOTTOM_Y (349+(OUR_TEAM_SIZE_NO_VEHICLE-18)*ROW_HEIGHT) -#define BOTTOM_Y 349 +#define BOTTOM_HEIGHT 8 //The internal height of the uninvolved panel -#define INTERNAL_HEIGHT 27 -//The actual height of the uninvolved panel -#define ACTUAL_HEIGHT 24 +#define UNINVOLVED_RELEVANT_HEIGHT 28 +#define UNINVOLVED_OFFSET_HEIGHT 7 + +#define PREBATTLE_INTERFACE_WIDTH 261 +INT32 iPrebattleInterfaceHeight = 360; +UINT16 xOffset; +UINT16 yOffset; +UINT16 blanketStartX; +UINT16 blanketStartY; + +INT16 bListOffset = 0; +UINT16 ubDesiredListHeight = 0; +UINT16 ubAllowedListHeight = 0; BOOLEAN gfDisplayPotentialRetreatPaths = FALSE; UINT16 gusRetreatButtonLeft, gusRetreatButtonTop, gusRetreatButtonRight, gusRetreatButtonBottom; @@ -300,6 +311,8 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) gAmbushRadiusModifier = 0.0f; + bListOffset = 0; + // ARM: Feb01/98 - Cancel out of mapscreen movement plotting if PBI subscreen is coming up if ( ( GetSelectedDestChar() != -1) || fPlotForHelicopter || fPlotForMilitia ) { @@ -481,32 +494,69 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) fMapScreenBottomDirty = TRUE; ChangeSelectedMapSector( gubPBSectorX, gubPBSectorY, gubPBSectorZ ); - // Headrock: Added FALSE argument, We might need TRUE but not sure. Will need to initiate battle :) - RenderMapScreenInterfaceBottom( FALSE ); if( !fShowTeamFlag ) { ToggleShowTeamsMode(); } - //Define the blanket region to cover all of the other regions used underneath the panel. - MSYS_DefineRegion( &PBInterfaceBlanket, 0 + xResOffset, 0 + yResOffset, 261 + xResOffset, 359 + yResOffset, MSYS_PRIORITY_HIGHEST - 5, 0, 0, 0 ); - //Create the panel VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE; - GetMLGFilename( VObjectDesc.ImageFile, MLG_PREBATTLEPANEL ); + + // anv: prebattle interface per vertical resolution + if (isWidescreenUI()) + { + GetMLGFilename(VObjectDesc.ImageFile, MLG_PREBATTLEPANEL_1280x720); + iPrebattleInterfaceHeight = 600; + xOffset = xResOffset + 15; + yOffset = 0; + blanketStartX = 0; + blanketStartY = 0; + } + else if (iResolution >= _640x480 && iResolution < _800x600) + { + GetMLGFilename(VObjectDesc.ImageFile, MLG_PREBATTLEPANEL); + iPrebattleInterfaceHeight = 360; + xOffset = xResOffset; + yOffset = yResOffset; + blanketStartX = yOffset; + blanketStartY = yOffset; + } + else if (iResolution < _1024x768) + { + GetMLGFilename(VObjectDesc.ImageFile, MLG_PREBATTLEPANEL_800x600); + iPrebattleInterfaceHeight = 478; + xOffset = xResOffset; + yOffset = yResOffset; + blanketStartX = yOffset; + blanketStartY = yOffset; + } + else + { + GetMLGFilename(VObjectDesc.ImageFile, MLG_PREBATTLEPANEL_1024x768); + iPrebattleInterfaceHeight = 647; + xOffset = xResOffset; + yOffset = yResOffset; + blanketStartX = yOffset; + blanketStartY = yOffset; + } + ubAllowedListHeight = iPrebattleInterfaceHeight - TOP_Y - BOTTOM_HEIGHT; + if( !AddVideoObject( &VObjectDesc, &uiInterfaceImages ) ) AssertMsg( 0, "Failed to load interface\\PreBattlePanel.sti" ); + //Define the blanket region to cover all of the other regions used underneath the panel. + MSYS_DefineRegion( &PBInterfaceBlanket, blanketStartX, blanketStartY, PREBATTLE_INTERFACE_WIDTH + xOffset, iPrebattleInterfaceHeight + yOffset, MSYS_PRIORITY_HIGHEST, 0, 0, 0 ); + //Create the 3 buttons iPBButtonImage[0] = LoadButtonImage( "INTERFACE\\PreBattleButton.sti", -1, 0, -1, 1, -1 ); if( iPBButtonImage[ 0 ] == -1 ) AssertMsg( 0, "Failed to load interface\\PreBattleButton.sti" ); iPBButtonImage[1] = UseLoadedButtonImage( iPBButtonImage[ 0 ], -1, 0, -1, 1, -1 ); iPBButtonImage[2] = UseLoadedButtonImage( iPBButtonImage[ 0 ], -1, 0, -1, 1, -1 ); - iPBButton[0] = QuickCreateButton( iPBButtonImage[0], 27 + xResOffset, 54 + yResOffset, BUTTON_NO_TOGGLE, MSYS_PRIORITY_HIGHEST - 2, DEFAULT_MOVE_CALLBACK, AutoResolveBattleCallback ); - iPBButton[1] = QuickCreateButton( iPBButtonImage[1], 98 + xResOffset, 54 + yResOffset, BUTTON_NO_TOGGLE, MSYS_PRIORITY_HIGHEST - 2, DEFAULT_MOVE_CALLBACK, GoToSectorCallback ); - iPBButton[2] = QuickCreateButton( iPBButtonImage[2], 169 + xResOffset, 54 + yResOffset, BUTTON_NO_TOGGLE, MSYS_PRIORITY_HIGHEST - 2, DEFAULT_MOVE_CALLBACK, RetreatMercsCallback ); + iPBButton[0] = QuickCreateButton( iPBButtonImage[0], 27 + xOffset, 54 + yOffset, BUTTON_NO_TOGGLE, MSYS_PRIORITY_HIGHEST, DEFAULT_MOVE_CALLBACK, AutoResolveBattleCallback ); + iPBButton[1] = QuickCreateButton( iPBButtonImage[1], 98 + xOffset, 54 + yOffset, BUTTON_NO_TOGGLE, MSYS_PRIORITY_HIGHEST, DEFAULT_MOVE_CALLBACK, GoToSectorCallback ); + iPBButton[2] = QuickCreateButton( iPBButtonImage[2], 169 + xOffset, 54 + yOffset, BUTTON_NO_TOGGLE, MSYS_PRIORITY_HIGHEST, DEFAULT_MOVE_CALLBACK, RetreatMercsCallback ); SpecifyGeneralButtonTextAttributes( iPBButton[0], gpStrategicString[ STR_PB_AUTORESOLVE_BTN ], BLOCKFONT, FONT_BEIGE, 141 ); SpecifyGeneralButtonTextAttributes( iPBButton[1], gpStrategicString[ STR_PB_GOTOSECTOR_BTN ], BLOCKFONT, FONT_BEIGE, 141 ); @@ -1050,7 +1100,7 @@ void DoTransitionFromMapscreenToPreBattleInterface() INT32 iPercentage, iFactor; UINT32 uiTimeRange; INT16 sStartLeft, sEndLeft, sStartTop, sEndTop; - INT32 iLeft, iTop, iWidth, iHeight; + INT32 iLeft, iTop, iWidth; BOOLEAN fEnterAutoResolveMode = FALSE; if( !gfExtraBuffer ) @@ -1058,12 +1108,11 @@ void DoTransitionFromMapscreenToPreBattleInterface() PauseTime( FALSE ); - PBIRect.iLeft = 0 + xResOffset; - PBIRect.iTop = 0 + yResOffset; - PBIRect.iRight = 261 + xResOffset; - PBIRect.iBottom = 359 + yResOffset; - iWidth = 261; - iHeight = 359; + PBIRect.iLeft = 0 + xOffset; + PBIRect.iTop = 0 + yOffset; + PBIRect.iRight = PREBATTLE_INTERFACE_WIDTH + xOffset; + PBIRect.iBottom = iPrebattleInterfaceHeight + yOffset; + iWidth = PREBATTLE_INTERFACE_WIDTH; uiTimeRange = 1000; iPercentage = 0; @@ -1072,8 +1121,8 @@ void DoTransitionFromMapscreenToPreBattleInterface() GetScreenXYFromMapXY( gubPBSectorX, gubPBSectorY, &sStartLeft, &sStartTop ); sStartLeft += UI_MAP.GridSize.iX / 2; sStartTop += UI_MAP.GridSize.iY / 2; - sEndLeft = 131 + xResOffset; - sEndTop = 180 + yResOffset; + sEndLeft = PBIRect.iLeft; + sEndTop = PBIRect.iTop; //save the mapscreen buffer BlitBufferToBuffer( FRAME_BUFFER, guiEXTRABUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); @@ -1095,17 +1144,23 @@ void DoTransitionFromMapscreenToPreBattleInterface() gfEnterAutoResolveMode = TRUE; } - BlitBufferToBuffer( guiSAVEBUFFER, FRAME_BUFFER, 27 + xResOffset, 54 + yResOffset, 209, 32 ); + BlitBufferToBuffer( guiSAVEBUFFER, FRAME_BUFFER, 27 + xOffset, 54 + yOffset, 209, 32 ); RenderButtons(); - BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 27 + xResOffset, 54 + yResOffset, 209, 32 ); + BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 27 + xOffset, 54 + yOffset, 209, 32 ); gfRenderPBInterface = TRUE; //hide the prebattle interface - BlitBufferToBuffer( guiEXTRABUFFER, FRAME_BUFFER, 0 + xResOffset, 0 + yResOffset, 261 + xResOffset, 359 + yResOffset ); + BlitBufferToBuffer( guiEXTRABUFFER, FRAME_BUFFER, 0 + xOffset, 0 + yOffset, PREBATTLE_INTERFACE_WIDTH + xOffset, iPrebattleInterfaceHeight + yOffset ); PlayJA2SampleFromFile( "SOUNDS\\Laptop power up (8-11).wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN ); InvalidateScreen(); RefreshScreen( NULL ); + SGPRect PrevRect; + PrevRect.iLeft = sStartLeft; + PrevRect.iRight = sStartLeft + 1; + PrevRect.iTop = sStartTop; + PrevRect.iBottom = sStartTop + 1; + while( iPercentage < 100 ) { uiCurrTime = GetJA2Clock(); @@ -1117,19 +1172,12 @@ void DoTransitionFromMapscreenToPreBattleInterface() if( iPercentage < 50 ) iPercentage = (UINT32)(iPercentage + iPercentage * iFactor * 0.01 + 0.5); else - iPercentage = (UINT32)(iPercentage + (100-iPercentage) * iFactor * 0.01 + 0.05); + iPercentage = (UINT32)(iPercentage + (100 - iPercentage) * iFactor * 0.01 + 0.05); - //Calculate the center point. - iLeft = sStartLeft - (sStartLeft-sEndLeft+1) * iPercentage / 100; - if( sStartTop > sEndTop ) - iTop = sStartTop - (sStartTop-sEndTop+1) * iPercentage / 100; - else - iTop = sStartTop + (sEndTop-sStartTop+1) * iPercentage / 100; - - DstRect.iLeft = iLeft - iWidth * iPercentage / 200; + DstRect.iLeft = sStartLeft + (sEndLeft - sStartLeft) * iPercentage / 100; DstRect.iRight = DstRect.iLeft + max( iWidth * iPercentage / 100, 1 ); - DstRect.iTop = iTop - iHeight * iPercentage / 200; - DstRect.iBottom = DstRect.iTop + max( iHeight * iPercentage / 100, 1 ); + DstRect.iTop = sStartTop + (sEndTop - sStartTop) * iPercentage / 100; + DstRect.iBottom = DstRect.iTop + max(iPrebattleInterfaceHeight * iPercentage / 100, 1); BltStretchVideoSurface( FRAME_BUFFER, guiSAVEBUFFER, 0, 0, 0, &PBIRect, &DstRect ); @@ -1137,14 +1185,35 @@ void DoTransitionFromMapscreenToPreBattleInterface() RefreshScreen( NULL ); //Restore the previous rect. - BlitBufferToBuffer( guiEXTRABUFFER, FRAME_BUFFER, (UINT16)DstRect.iLeft, (UINT16)DstRect.iTop, - (UINT16)(DstRect.iRight-DstRect.iLeft+1), (UINT16)(DstRect.iBottom-DstRect.iTop+1) ); + BlitBufferToBuffer(guiEXTRABUFFER, FRAME_BUFFER, (UINT16)PrevRect.iLeft, (UINT16)PrevRect.iTop, + (UINT16)PrevRect.iRight, (UINT16)PrevRect.iBottom); + + PrevRect.iLeft = DstRect.iLeft; + PrevRect.iRight = DstRect.iRight; + PrevRect.iTop = DstRect.iTop; + PrevRect.iBottom = DstRect.iBottom; } BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); gfZoomDone = TRUE; } +void ScrollPreBattleInterface( BOOLEAN fUp ) +{ + if ( ubDesiredListHeight <= ubAllowedListHeight ) + return; + + if ( fUp ) + { + bListOffset = max( 0, bListOffset - ubAllowedListHeight ); + } + else + { + bListOffset = min( bListOffset + ubAllowedListHeight, ubDesiredListHeight - ubAllowedListHeight ); + } + gfRenderPBInterface = TRUE; +} + void KillPreBattleInterface() { if( !gfPreBattleInterfaceActive ) @@ -1184,7 +1253,7 @@ void KillPreBattleInterface() //Enable the options button when the auto resolve screen comes up EnableDisAbleMapScreenOptionsButton( TRUE ); - ColorFillVideoSurfaceArea( guiSAVEBUFFER, 0, 0, 261 + xResOffset, 359 + yResOffset, 0 ); + ColorFillVideoSurfaceArea( guiSAVEBUFFER, 0, 0, PREBATTLE_INTERFACE_WIDTH + xOffset, iPrebattleInterfaceHeight + yOffset, 0 ); EnableTeamInfoPanels(); if( ButtonList[ giMapContractButton ] ) @@ -1277,8 +1346,8 @@ void RenderPBHeader( INT32 *piX, INT32 *piWidth) } width = StringPixLength( str, FONT10ARIALBOLD ); x = 130 - width / 2; - mprintf( x + xResOffset, 4 + yResOffset, str ); - InvalidateRegion( 0, 0, 231 + xResOffset, 12 + yResOffset ); + mprintf( x + xOffset, 4 + yOffset, str ); + InvalidateRegion( 0, 0, 231 + xOffset, 12 + yOffset ); *piX = x; *piWidth = width; } @@ -1293,10 +1362,15 @@ void RenderPreBattleInterface() UINT8 ubHPPercent, ubBPPercent; BOOLEAN fMouseInRetreatButtonArea; UINT8 ubJunk; + SGPRect ClipRect; + UINT16 ubDesiredParticipantsListHeight = 0; + UINT16 ubDesiredUninvolvedListHeight = 0; + UINT16 ubUninvolvedStartY = 0; + //PLAYERGROUP *pPlayer; // Make the background black! - //ColorFillVideoSurfaceArea( guiSAVEBUFFER, 0, 0, 261 + xResOffset, SCREEN_HEIGHT - 120, 0 ); + //ColorFillVideoSurfaceArea( guiSAVEBUFFER, 0, 0, 261 + xOffset, SCREEN_HEIGHT - 120, 0 ); //This code determines if the cursor is inside the rectangle consisting of the //retreat button. If it is inside, then we set up the variables so that the retreat @@ -1337,33 +1411,21 @@ void RenderPreBattleInterface() gfRenderPBInterface = FALSE; GetVideoObject( &hVObject, uiInterfaceImages ); //main panel - BltVideoObject( guiSAVEBUFFER, hVObject, MAINPANEL, xResOffset, yResOffset, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObject( guiSAVEBUFFER, hVObject, MAINPANEL, xOffset, yOffset, VO_BLT_SRCTRANSPARENCY, NULL ); //main title RenderPBHeader( &x, &width ); //now draw the title bars up to the text. for( i = x - 12; i > 20; i -= 10 ) { - BltVideoObject( guiSAVEBUFFER, hVObject, TITLE_BAR_PIECE, i + xResOffset, 6 + yResOffset, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObject( guiSAVEBUFFER, hVObject, TITLE_BAR_PIECE, i + xOffset, 6 + yOffset, VO_BLT_SRCTRANSPARENCY, NULL ); } for( i = x + width + 2; i < 231; i += 10 ) { - BltVideoObject( guiSAVEBUFFER, hVObject, TITLE_BAR_PIECE, i + xResOffset, 6 + yResOffset, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObject( guiSAVEBUFFER, hVObject, TITLE_BAR_PIECE, i + xOffset, 6 + yOffset, VO_BLT_SRCTRANSPARENCY, NULL ); } - BltVideoObject(guiSAVEBUFFER, hVObject, BOTTOM_LINE, 0 + xResOffset, BOTTOM_Y + yResOffset, VO_BLT_SRCTRANSPARENCY, NULL); - BltVideoObject(guiSAVEBUFFER, hVObject, BOTTOM_LINE, 0 + xResOffset, BOTTOM_Y + yResOffset + 10, VO_BLT_SRCTRANSPARENCY, NULL); - BltVideoObject(guiSAVEBUFFER, hVObject, BOTTOM_LINE, 0 + xResOffset, BOTTOM_Y + yResOffset + 20, VO_BLT_SRCTRANSPARENCY, NULL); - BltVideoObject(guiSAVEBUFFER, hVObject, BOTTOM_LINE, 0 + xResOffset, BOTTOM_Y + yResOffset + 30, VO_BLT_SRCTRANSPARENCY, NULL); - //Draw the bottom edges - for (i = 0; i < max(guiNumUninvolved, 1); i++) - { - y = BOTTOM_Y + ROW_HEIGHT * (i + 1) + 30; - BltVideoObject(guiSAVEBUFFER, hVObject, BOTTOM_LINE, 0 + xResOffset, y + yResOffset, VO_BLT_SRCTRANSPARENCY, NULL); - } - BltVideoObject(guiSAVEBUFFER, hVObject, UNINVOLVED_HEADER, 8 + xResOffset, BOTTOM_Y + yResOffset, VO_BLT_SRCTRANSPARENCY, NULL); - BltVideoObject(guiSAVEBUFFER, hVObject, BOTTOM_END, 0 + xResOffset, BOTTOM_Y + yResOffset + 35 + ROW_HEIGHT * max(guiNumUninvolved, 1), VO_BLT_SRCTRANSPARENCY, NULL); - + // header SetFont( BLOCKFONT ); SetFontForeground( FONT_BEIGE ); swprintf( str, gpStrategicString[ STR_PB_LOCATION ] ); @@ -1373,7 +1435,7 @@ void RenderPreBattleInterface() SetFont( BLOCKFONTNARROW ); width = StringPixLength( str, BLOCKFONTNARROW ); } - mprintf( 65 - width + xResOffset , 17 + yResOffset, str ); + mprintf( 65 - width + xOffset , 17 + yOffset, str ); SetFont( BLOCKFONT ); if( GetEnemyEncounterCode() == CREATURE_ATTACK_CODE ) @@ -1405,7 +1467,7 @@ void RenderPreBattleInterface() SetFont( BLOCKFONTNARROW ); width = StringPixLength( str, BLOCKFONTNARROW ); } - mprintf( 54 + xResOffset - width , 38 + yResOffset, str ); + mprintf( 54 + xOffset - width , 38 + yOffset, str ); SetFont( BLOCKFONT ); swprintf( str, gpStrategicString[ STR_PB_MERCS ] ); @@ -1415,7 +1477,7 @@ void RenderPreBattleInterface() SetFont( BLOCKFONTNARROW ); width = StringPixLength( str, BLOCKFONTNARROW ); } - mprintf( 139 + xResOffset - width , 38 + yResOffset, str ); + mprintf( 139 + xOffset - width , 38 + yOffset, str ); SetFont( BLOCKFONT ); swprintf( str, gpStrategicString[ STR_PB_MILITIA ] ); @@ -1425,29 +1487,53 @@ void RenderPreBattleInterface() SetFont( BLOCKFONTNARROW ); width = StringPixLength( str, BLOCKFONTNARROW ); } - mprintf( 224 + xResOffset - width , 38 + yResOffset, str ); + mprintf( 224 + xOffset - width , 38 + yOffset, str ); - //Draw the bottom columns - for( i = 0; i < (INT32)max( guiNumUninvolved, 1 ); i++ ) + ubDesiredParticipantsListHeight = guiNumInvolved * ROW_HEIGHT; + ubDesiredUninvolvedListHeight = UNINVOLVED_RELEVANT_HEIGHT + max(guiNumUninvolved, 1) * ROW_HEIGHT; + + ubDesiredListHeight = ubDesiredParticipantsListHeight + ubDesiredUninvolvedListHeight; + + if (ubDesiredListHeight >= ubAllowedListHeight) { - y = BOTTOM_Y + ROW_HEIGHT * (i+1) + 1 + ACTUAL_HEIGHT; - BltVideoObject( guiSAVEBUFFER, hVObject, BOTTOM_COLUMN, 161 + xResOffset, y + yResOffset, VO_BLT_SRCTRANSPARENCY, NULL ); + ubUninvolvedStartY = ubDesiredParticipantsListHeight; + } + else + { + ubUninvolvedStartY = ubAllowedListHeight - ubDesiredUninvolvedListHeight; } - // WDS - make number of mercenaries, etc. be configurable - for( i = 0; i < (INT32)(25/*3+ OUR_TEAM_SIZE_NO_VEHICLE - max( guiNumUninvolved, 1 )*/); i++ ) + ClipRect.iLeft = xOffset; + ClipRect.iTop = yOffset + TOP_Y; + ClipRect.iRight = xOffset + PREBATTLE_INTERFACE_WIDTH; + ClipRect.iBottom = yOffset + TOP_Y + ubAllowedListHeight;// + TOP_Y_BUFFER; + SetClippingRect(&ClipRect); + + // Draw the top columns + // Draw from the top to uninvolved header + for ( y = TOP_Y - bListOffset; y < TOP_Y - bListOffset + ubUninvolvedStartY; y += ROW_HEIGHT ) { - y = TOP_Y + ROW_HEIGHT * i; - BltVideoObject( guiSAVEBUFFER, hVObject, TOP_COLUMN, 186 + xResOffset, y + yResOffset, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObject(guiSAVEBUFFER, hVObject, TOP_COLUMN, 186 + xOffset, y + yOffset, VO_BLT_CLIP | VO_BLT_SRCTRANSPARENCY, NULL); } + // Draw extra empty participants rows to close off bottom of the content area + for ( y = TOP_Y - bListOffset + ubUninvolvedStartY + ROW_HEIGHT; y < TOP_Y - bListOffset + ubUninvolvedStartY + ubDesiredUninvolvedListHeight; y += ROW_HEIGHT ) + { + BltVideoObject(guiSAVEBUFFER, hVObject, BOTTOM_COLUMN, 161 + xOffset, y + yOffset, VO_BLT_CLIP | VO_BLT_SRCTRANSPARENCY, NULL); + } + + // Draw uninvolved header + BltVideoObject( guiSAVEBUFFER, hVObject, UNINVOLVED_HEADER, 8 + xOffset, yOffset + TOP_Y + ubUninvolvedStartY - UNINVOLVED_OFFSET_HEIGHT - bListOffset, VO_BLT_CLIP | VO_BLT_SRCTRANSPARENCY, NULL ); + + RestoreClipRegionToFullScreen(); + //location SetFont( FONT10ARIAL ); SetFontForeground( FONT_YELLOW ); SetFontShadow( FONT_NEARBLACK ); GetSectorIDString( gubPBSectorX, gubPBSectorY, gubPBSectorZ, pSectorName, TRUE ); - mprintf( 70 + xResOffset, 17 + yResOffset, L"%s %s", gpStrategicString[ STR_PB_SECTOR ], pSectorName ); + mprintf( 70 + xOffset, 17 + yOffset, L"%s %s", gpStrategicString[STR_PB_SECTOR], pSectorName ); //enemy SetFont( FONT14ARIAL ); @@ -1472,58 +1558,55 @@ void RenderPreBattleInterface() } x = 57 + (27 - StringPixLength( str, FONT14ARIAL )) / 2; y = 36; - mprintf( x + xResOffset, y + yResOffset, str ); + mprintf( x + xOffset, y + yOffset, str ); //player swprintf( str, L"%d", guiNumInvolved ); x = 142 + (27 - StringPixLength( str, FONT14ARIAL )) / 2; - mprintf( x + xResOffset, y + yResOffset, str ); + mprintf( x + xOffset, y + yOffset, str ); //militia swprintf( str, L"%d", NumNonPlayerTeamMembersInSector( gubPBSectorX, gubPBSectorY, MILITIA_TEAM ) ); x = 227 + (27 - StringPixLength( str, FONT14ARIAL )) / 2; - mprintf( x + xResOffset, y + yResOffset, str ); + mprintf( x + xOffset, y + yOffset, str ); SetFontShadow( FONT_NEARBLACK ); SetFont( BLOCKFONT2 ); SetFontForeground( FONT_YELLOW ); + SetFontDestBuffer( guiSAVEBUFFER, xOffset, yOffset + TOP_Y, + xOffset + PREBATTLE_INTERFACE_WIDTH, yOffset + TOP_Y + ubAllowedListHeight, FALSE ); + //print out the participants of the battle. // | NAME | ASSIGN | COND | HP | BP | line = 0; - y = TOP_Y + 1; - for( i = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; i <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; i++ ) + y = TOP_Y + TOP_Y_TEXT_BUFFER - bListOffset; + for( i = gTacticalStatus.Team[OUR_TEAM].bFirstID; i <= gTacticalStatus.Team[OUR_TEAM].bLastID; i++) { - if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->stats.bLife && !(MercPtrs[ i ]->flags.uiStatusFlags & SOLDIER_VEHICLE) ) + if( MercPtrs[i]->bActive && MercPtrs[i]->stats.bLife && !(MercPtrs[i]->flags.uiStatusFlags & SOLDIER_VEHICLE) ) { - if ( PlayerMercInvolvedInThisCombat( MercPtrs[ i ] ) ) - { //involved - if( line == giHilitedInvolved ) - SetFontForeground( FONT_WHITE ); - else - SetFontForeground( FONT_YELLOW ); + if( PlayerMercInvolvedInThisCombat( MercPtrs[ i ] ) ) + { //NAME wcscpy( str, MercPtrs[ i ]->name ); - x = 17 + (52-StringPixLength( str, BLOCKFONT2)) / 2; - mprintf( x + xResOffset , y + yResOffset, str ); + x = 17 + (52 - StringPixLength(str, BLOCKFONT2)) / 2; + mprintf( x + xOffset, y + yOffset, str ); //ASSIGN GetMapscreenMercAssignmentString( MercPtrs[ i ], str ); - x = 72 + (54-StringPixLength( str, BLOCKFONT2)) / 2; - mprintf( x + xResOffset, y + yResOffset, str ); + x = 72 + (54 - StringPixLength(str, BLOCKFONT2)) / 2; + mprintf( x + xOffset, y + yOffset, str ); //COND GetSoldierConditionInfo( MercPtrs[ i ], str, &ubHPPercent, &ubBPPercent ); - x = 129 + (58-StringPixLength( str, BLOCKFONT2)) / 2; - mprintf( x + xResOffset, y + yResOffset, str ); + x = 129 + (58 - StringPixLength(str, BLOCKFONT2)) / 2; + mprintf( x + xOffset, y + yOffset, str ); //HP swprintf( str, L"%d%%", ubHPPercent ); - x = 189 + (25-StringPixLength( str, BLOCKFONT2)) / 2; + x = 189 + (25 - StringPixLength(str, BLOCKFONT2)) / 2; wcscat( str, sSpecialCharacters[0] ); - mprintf( x + xResOffset, y + yResOffset, str ); + mprintf( x + xOffset, y + yOffset, str ); //BP swprintf( str, L"%d%%", ubBPPercent ); x = 217 + (25-StringPixLength( str, BLOCKFONT2)) / 2; wcscat( str, sSpecialCharacters[0] ); - mprintf( x + xResOffset, y + yResOffset, str ); - - line++; + mprintf( x + xOffset, y + yOffset, str ); y += ROW_HEIGHT; } } @@ -1533,51 +1616,44 @@ void RenderPreBattleInterface() // | NAME | ASSIGN | LOC | DEST | DEP | if( !guiNumUninvolved ) { - SetFontForeground( FONT_YELLOW ); wcscpy( str, gpStrategicString[ STR_PB_NONE ] ); - x = 17 + (52-StringPixLength( str, BLOCKFONT2)) / 2; - y = BOTTOM_Y + ROW_HEIGHT + 2 + ACTUAL_HEIGHT; - mprintf( x + xResOffset, y + yResOffset, str ); + x = 17 + (52 - StringPixLength( str, BLOCKFONT2)) / 2; + mprintf( x + xOffset, yOffset + TOP_Y + TOP_Y_TEXT_BUFFER + ubUninvolvedStartY + UNINVOLVED_RELEVANT_HEIGHT - bListOffset, str ); } else { pGroup = gpGroupList; - y = BOTTOM_Y + ROW_HEIGHT + 2 + ACTUAL_HEIGHT; - for( i = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; i <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; i++ ) + y = TOP_Y + TOP_Y_TEXT_BUFFER + ubUninvolvedStartY + UNINVOLVED_RELEVANT_HEIGHT - bListOffset; + for( i = gTacticalStatus.Team[OUR_TEAM].bFirstID; i <= gTacticalStatus.Team[OUR_TEAM].bLastID; i++ ) { if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->stats.bLife && !(MercPtrs[ i ]->flags.uiStatusFlags & SOLDIER_VEHICLE) ) { - if ( !PlayerMercInvolvedInThisCombat( MercPtrs[ i ] ) ) + if( !PlayerMercInvolvedInThisCombat(MercPtrs[ i ]) ) { - // uninvolved - if( line == giHilitedUninvolved ) - SetFontForeground( FONT_WHITE ); - else - SetFontForeground( FONT_YELLOW ); //NAME wcscpy( str, MercPtrs[ i ]->name ); - x = 17 + (52-StringPixLength( str, BLOCKFONT2)) / 2; - mprintf( x + xResOffset, y + yResOffset, str ); + x = 17 + (52 - StringPixLength(str, BLOCKFONT2)) / 2; + mprintf( x + xOffset, y + yOffset, str ); //ASSIGN GetMapscreenMercAssignmentString( MercPtrs[ i ], str ); - x = 72 + (54-StringPixLength( str, BLOCKFONT2)) / 2; - mprintf( x + xResOffset, y + yResOffset, str ); + x = 72 + (54 - StringPixLength(str, BLOCKFONT2)) / 2; + mprintf( x + xOffset, y + yOffset, str ); //LOC GetMapscreenMercLocationString( MercPtrs[ i ], str ); - x = 128 + (33-StringPixLength( str, BLOCKFONT2)) / 2; - mprintf( x + xResOffset, y + yResOffset, str ); + x = 128 + (33 - StringPixLength(str, BLOCKFONT2)) / 2; + mprintf( x + xOffset, y + yOffset, str ); //DEST GetMapscreenMercDestinationString( MercPtrs[ i ], str ); - if( wcslen( str ) > 0 ) + if (wcslen(str) > 0) { - x = 164 + (41-StringPixLength( str, BLOCKFONT2)) / 2; - mprintf( x + xResOffset, y + yResOffset, str ); + x = 164 + (41 - StringPixLength(str, BLOCKFONT2)) / 2; + mprintf( x + xOffset, y + yOffset, str ); } //DEP GetMapscreenMercDepartureString( MercPtrs[ i ], str, &ubJunk ); - x = 208 + (34-StringPixLength( str, BLOCKFONT2)) / 2; - mprintf( x + xResOffset, y + yResOffset, str ); - line++; + x = 208 + (34 - StringPixLength(str, BLOCKFONT2)) / 2; + mprintf(x + xOffset, y + yOffset, str); + y += ROW_HEIGHT; } } @@ -1587,12 +1663,7 @@ void RenderPreBattleInterface() // mark any and ALL pop up boxes as altered MarkAllBoxesAsAltered( ); - if(!gfZoomDone) - RestoreExternBackgroundRect( 0 + xResOffset, 0 + yResOffset, 261 + xResOffset, 359 + yResOffset ); - else if(!guiNumUninvolved) - RestoreExternBackgroundRect( 0 + xResOffset, 0 + yResOffset, 261 + xResOffset, 389 + yResOffset ); - else - RestoreExternBackgroundRect( 0 + xResOffset, 0 + yResOffset, 261 + xResOffset, y + yResOffset ); + RestoreExternBackgroundRect( 0 + xOffset, 0 + yOffset, PREBATTLE_INTERFACE_WIDTH, iPrebattleInterfaceHeight ); // restore font destinanation buffer to the frame buffer SetFontDestBuffer( FRAME_BUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); @@ -1602,7 +1673,7 @@ void RenderPreBattleInterface() RenderPBHeader( &x, &width ); //the text is important enough to blink. } - //InvalidateRegion( 0, 0, 261, 359 ); + InvalidateRegion( 0, 0, PREBATTLE_INTERFACE_WIDTH, iPrebattleInterfaceHeight ); if( gfEnterAutoResolveMode ) { gfEnterAutoResolveMode = FALSE; @@ -1611,7 +1682,6 @@ void RenderPreBattleInterface() } gfIgnoreAllInput = FALSE; - } void AutoResolveBattleCallback( GUI_BUTTON *btn, INT32 reason ) diff --git a/Strategic/PreBattle Interface.h b/Strategic/PreBattle Interface.h index 78070486..f0406674 100644 --- a/Strategic/PreBattle Interface.h +++ b/Strategic/PreBattle Interface.h @@ -7,6 +7,7 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ); void KillPreBattleInterface(); void RenderPreBattleInterface(); +void ScrollPreBattleInterface( BOOLEAN fUp ); extern BOOLEAN gfPreBattleInterfaceActive; extern BOOLEAN gfDisplayPotentialRetreatPaths; diff --git a/Strategic/mapscreen.cpp b/Strategic/mapscreen.cpp index 4d387161..655d6b50 100644 --- a/Strategic/mapscreen.cpp +++ b/Strategic/mapscreen.cpp @@ -5708,7 +5708,7 @@ UINT32 MapScreenHandle(void) HandleCharBarRender( ); } - if( (fShowInventoryFlag && !isWidescreenUI()) || fDisableDueToBattleRoster ) + if( ( fShowInventoryFlag || fDisableDueToBattleRoster ) && !isWidescreenUI() ) { for( iCounter = 0; iCounter < MAX_SORT_METHODS; iCounter++ ) { @@ -5824,9 +5824,6 @@ UINT32 MapScreenHandle(void) HandleContractRenewalSequence( ); - // handle dialog - HandleDialogue( ); - // handle display of inventory pop up // HEADROCK HAM 3.5: Externalize! HandleDisplayOfItemPopUpForSector( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ ); @@ -6065,6 +6062,8 @@ UINT32 MapScreenHandle(void) //InvalidateRegion( 0,0, 640, 480); EndFrameBufferRender( ); + // handle dialog + HandleDialogue(); // if not going anywhere else if ( guiPendingScreen == NO_PENDING_SCREEN ) @@ -7225,14 +7224,29 @@ void GetMapKeyboardInput( UINT32 *puiNewEvent ) break; case PGUP: - // WANNE: Jump to first merc in list - fResetMapCoords = TRUE; - GoToFirstCharacterInList( ); + if (gfPreBattleInterfaceActive) + { + ScrollPreBattleInterface(TRUE); + } + else + { + // WANNE: Jump to first merc in list + fResetMapCoords = TRUE; + GoToFirstCharacterInList(); + } + break; case PGDN: - // WANNE: Jump to last merc in list - fResetMapCoords = TRUE; - GoToLastCharacterInList( ); + if (gfPreBattleInterfaceActive) + { + ScrollPreBattleInterface(FALSE); + } + else + { + // WANNE: Jump to last merc in list + fResetMapCoords = TRUE; + GoToLastCharacterInList(); + } break; case SHIFT_PGUP: @@ -10927,6 +10941,10 @@ void BlitBackgroundToSaveBuffer( void ) ForceButtonUnDirty( giMapContractButton ); ForceButtonUnDirty( giCharInfoButton[ 0 ] ); ForceButtonUnDirty( giCharInfoButton[ 1 ] ); + if (isWidescreenUI()) + { + ForceButtonUnDirty(giMapInvDoneButton); + } RenderPreBattleInterface(); } diff --git a/Utils/Multi Language Graphic Utils.cpp b/Utils/Multi Language Graphic Utils.cpp index 0371e72e..102c7d3f 100644 --- a/Utils/Multi Language Graphic Utils.cpp +++ b/Utils/Multi Language Graphic Utils.cpp @@ -76,6 +76,15 @@ BOOLEAN GetMLGFilename( SGPFILENAME filename, UINT16 usMLGGraphicID ) case MLG_PREBATTLEPANEL: sprintf( filename, "INTERFACE\\PreBattlePanel.sti" ); return TRUE; + case MLG_PREBATTLEPANEL_800x600: + sprintf(filename, "INTERFACE\\PreBattlePanel_800x600.sti"); + return TRUE; + case MLG_PREBATTLEPANEL_1024x768: + sprintf(filename, "INTERFACE\\PreBattlePanel_1024x768.sti"); + return TRUE; + case MLG_PREBATTLEPANEL_1280x720: + sprintf(filename, "INTERFACE\\PreBattlePanel_1280x720.sti"); + return TRUE; case MLG_SMALLTITLE: sprintf( filename, "LAPTOP\\SmallTitle.sti" ); return TRUE; @@ -203,6 +212,15 @@ BOOLEAN GetMLGFilename( SGPFILENAME filename, UINT16 usMLGGraphicID ) case MLG_PREBATTLEPANEL: sprintf( filename, "GERMAN\\PreBattlePanel_german.sti" ); return TRUE; + case MLG_PREBATTLEPANEL_800x600: + sprintf(filename, "GERMAN\\PreBattlePanel_800x600_german.sti"); + return TRUE; + case MLG_PREBATTLEPANEL_1024x768: + sprintf(filename, "GERMAN\\PreBattlePanel_1024x768_german.sti"); + return TRUE; + case MLG_PREBATTLEPANEL_1280x720: + sprintf(filename, "GERMAN\\PreBattlePanel_1280x720_german.sti"); + return TRUE; case MLG_SMALLTITLE: sprintf( filename, "GERMAN\\SmallTitle_german.sti" ); return TRUE; @@ -368,6 +386,15 @@ BOOLEAN GetMLGFilename( SGPFILENAME filename, UINT16 usMLGGraphicID ) case MLG_PREBATTLEPANEL: sprintf( filename, "%s\\PreBattlePanel_%s.sti", zLanguage, zLanguage ); break; + case MLG_PREBATTLEPANEL_800x600: + sprintf(filename, "%s\\PreBattlePanel_800x600_%s.sti", zLanguage, zLanguage); + break; + case MLG_PREBATTLEPANEL_1024x768: + sprintf(filename, "%s\\PreBattlePanel_1024x768_%s.sti", zLanguage, zLanguage); + break; + case MLG_PREBATTLEPANEL_1280x720: + sprintf(filename, "%s\\PreBattlePanel_1280x720_%s.sti", zLanguage, zLanguage); + break; case MLG_SMALLTITLE: sprintf( filename, "%s\\SmallTitle_%s.sti", zLanguage, zLanguage ); break; @@ -492,6 +519,15 @@ BOOLEAN GetMLGFilename( SGPFILENAME filename, UINT16 usMLGGraphicID ) case MLG_PREBATTLEPANEL: sprintf( filename, "INTERFACE\\PreBattlePanel.sti" ); return TRUE; + case MLG_PREBATTLEPANEL_800x600: + sprintf(filename, "INTERFACE\\PreBattlePanel_800x600.sti"); + return TRUE; + case MLG_PREBATTLEPANEL_1024x768: + sprintf(filename, "INTERFACE\\PreBattlePanel_1024x768.sti"); + return TRUE; + case MLG_PREBATTLEPANEL_1280x720: + sprintf(filename, "INTERFACE\\PreBattlePanel_1280x720.sti"); + return TRUE; case MLG_SMALLTITLE: sprintf( filename, "LAPTOP\\SmallTitle.sti" ); return TRUE; diff --git a/Utils/Multi Language Graphic Utils.h b/Utils/Multi Language Graphic Utils.h index 12f74fd5..dcedbcc1 100644 --- a/Utils/Multi Language Graphic Utils.h +++ b/Utils/Multi Language Graphic Utils.h @@ -26,6 +26,9 @@ enum MLG_OPTIONHEADER, //OptionScreenAddOns MLG_ORDERGRID, MLG_PREBATTLEPANEL, + MLG_PREBATTLEPANEL_800x600, + MLG_PREBATTLEPANEL_1024x768, + MLG_PREBATTLEPANEL_1280x720, MLG_SECTORINVENTORY, MLG_SMALLFLORISTSYMBOL, //SmallSymbol MLG_SMALLTITLE, From 1e1d456fd27c9fe70e51f4e2da76347b138a64a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Fa=C5=82kowski?= Date: Mon, 21 Aug 2023 18:44:02 +0200 Subject: [PATCH 37/50] Prevent deadlocks due to self-healing interactive spots and self-attacking (#205) --- Tactical/Handle Items.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Tactical/Handle Items.cpp b/Tactical/Handle Items.cpp index 9c9b47fc..a865d703 100644 --- a/Tactical/Handle Items.cpp +++ b/Tactical/Handle Items.cpp @@ -279,7 +279,8 @@ INT32 HandleItem( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel, UINT16 usHa { pTargetSoldier = MercPtrs[ usSoldierIndex ]; - if ( fFromUI ) + // anv: don't try to heal interactive spots + if (fFromUI && Item[usHandItem].usItemClass != IC_MEDKIT) { INT32 sInteractiveGridNo; @@ -325,6 +326,13 @@ INT32 HandleItem( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel, UINT16 usHa { if (pTargetSoldier->bTeam == gbPlayerNum || pTargetSoldier->aiData.bNeutral) { + // anv: don't try to attack yourself, it will only cause deadlock + if (pSoldier == pTargetSoldier) + { + TacticalCharacterDialogue(pSoldier, QUOTE_REFUSING_ORDER); + return(ITEM_HANDLE_REFUSAL); + } + // nice mercs won't shoot other nice guys or neutral civilians if ((gMercProfiles[pSoldier->ubProfile].ubMiscFlags3 & PROFILE_MISC_FLAG3_GOODGUY) && ((pTargetSoldier->ubProfile == NO_PROFILE && pTargetSoldier->aiData.bNeutral && pTargetSoldier->ubBodyType != CROW) || From 1289c399ce8bffcaac099dd50c169339dabddef4 Mon Sep 17 00:00:00 2001 From: kitty624 <58940527+kitty624@users.noreply.github.com> Date: Tue, 22 Aug 2023 00:22:20 +0200 Subject: [PATCH 38/50] Update XML.h - added path to sub-directory for Extra Items - to avoid cluttering up the parent directory when using this feature - it allows to place extra items in sector via xml and based on difficulty level - the new directory "ExtraItems" will contain more details on how-to-use --- Tactical/XML.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tactical/XML.h b/Tactical/XML.h index 3505f1bb..03a95811 100644 --- a/Tactical/XML.h +++ b/Tactical/XML.h @@ -157,8 +157,8 @@ typedef PARSE_STAGE; #define ALTSECTORSFILENAME "Map\\AltSectors.xml" #define SAMSITESFILENAME "Map\\SamSites.xml" #define HELISITESFILENAME "Map\\HeliSites.xml" -#define EXTRAITEMSFILENAME "Map\\A9_0_ExtraItems" // ".xml" will be added @runtime -#define EXTRAITEMSFILENAME2 "Map\\A11_0_ExtraItems" // ".xml" will be added @runtime +#define EXTRAITEMSFILENAME "Map\\ExtraItems\\A9_0_ExtraItems" // ".xml" will be added @runtime +#define EXTRAITEMSFILENAME2 "Map\\ExtraItems\\A11_0_ExtraItems" // ".xml" will be added @runtime #define SHIPPINGDESTINATIONSFILENAME "Map\\ShippingDestinations.xml" #define DELIVERYMETHODSFILENAME "Map\\DeliveryMethods.xml" #define DELIVERYMETHODSFILENAME "Map\\DeliveryMethods.xml" From 6bae48658e66dc7bc732874f9c4cf163c38ba2ff Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Wed, 23 Aug 2023 00:34:54 -0700 Subject: [PATCH 39/50] Fix personality check for backgrounds during IMP creation (#208) --- Laptop/IMP Background.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Laptop/IMP Background.cpp b/Laptop/IMP Background.cpp index 9ea5c40e..b7cddab4 100644 --- a/Laptop/IMP Background.cpp +++ b/Laptop/IMP Background.cpp @@ -15,6 +15,7 @@ #include "CharProfile.h" #include "soldier profile type.h" #include "IMP Compile Character.h" + #include "IMP Disability Trait.h" #include "GameSettings.h" #include "Interface.h" @@ -671,7 +672,7 @@ BOOLEAN IsBackGroundAllowed( UINT16 ubNumber ) return FALSE; } - switch ( iPersonality ) + switch ( iChosenDisabilityTrait() ) { case HEAT_INTOLERANT: if ( zBackground[ ubNumber ].value[BG_DESERT] > 0 ) @@ -807,4 +808,4 @@ void BtnIMPBackgroundPreviousCallback(GUI_BUTTON *btn,INT32 reason) usBackground = 0; } } -} \ No newline at end of file +} From 8ec02b2d40b0c81eb1e3a6a58188b384b0c8dae4 Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Wed, 23 Aug 2023 01:21:22 -0700 Subject: [PATCH 40/50] Add version info to main menu (#209) --- MainMenuScreen.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MainMenuScreen.cpp b/MainMenuScreen.cpp index 1db5e174..bebe6193 100644 --- a/MainMenuScreen.cpp +++ b/MainMenuScreen.cpp @@ -855,6 +855,9 @@ void RenderMainMenu() DrawTextToScreen( L"BLOCKFONTNARROW: "/*gzCopyrightText[ 0 ]*/, 0, 445, 640, BLOCKFONTNARROW, FONT_MCOLOR_WHITE, FONT_MCOLOR_BLACK, FALSE, LEFT_JUSTIFIED ); DrawTextToScreen( L"FONT14HUMANIST: "/*gzCopyrightText[ 0 ]*/, 0, 465, 640, FONT14HUMANIST, FONT_MCOLOR_WHITE, FONT_MCOLOR_BLACK, FALSE, LEFT_JUSTIFIED ); #else + CHAR16 text[128]; + swprintf(text, L"%s: %s %S %s", pMessageStrings[ MSG_VERSION ], zProductLabel, czVersionString, zBuildInformation ); + DrawTextToScreen( text, 10, 10, SCREEN_WIDTH, TINYFONT1, FONT_MCOLOR_WHITE, FONT_MCOLOR_BLACK, FALSE, LEFT_JUSTIFIED ); DrawTextToScreen( gzCopyrightText[ 0 ], 0, SCREEN_HEIGHT - 20, SCREEN_WIDTH, FONT10ARIAL, FONT_MCOLOR_WHITE, FONT_MCOLOR_BLACK, FALSE, CENTER_JUSTIFIED ); #endif From 3ddacf1a44fd5e378fbca8dac8845f2dfa4f1f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Fa=C5=82kowski?= Date: Mon, 28 Aug 2023 15:26:02 +0200 Subject: [PATCH 41/50] Fix vehicles consuming fuel after instant movement cancel (#213) --- Strategic/Strategic Movement.cpp | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Strategic/Strategic Movement.cpp b/Strategic/Strategic Movement.cpp index 68eb7123..cbabfed6 100644 --- a/Strategic/Strategic Movement.cpp +++ b/Strategic/Strategic Movement.cpp @@ -1951,33 +1951,33 @@ void GroupArrivedAtSector( UINT8 ubGroupID, BOOLEAN fCheckForBattle, BOOLEAN fNe SectorInfo[SECTOR( pGroup->ubSectorX, pGroup->ubSectorY )].bLastKnownEnemies = NumNonPlayerTeamMembersInSector( pGroup->ubSectorX, pGroup->ubSectorY, ENEMY_TEAM ); } - // award life 'experience' for travelling, based on travel time! - if ( !pGroup->fVehicle ) + // Flugente: do not award experience gain if we never left + if (!fNeverLeft) { - // Flugente: do not award experience gain if we never left - if ( !fNeverLeft ) + // award life 'experience' for travelling, based on travel time! + if (!pGroup->fVehicle) { // gotta be walking to get tougher - AwardExperienceForTravelling( pGroup ); + AwardExperienceForTravelling(pGroup); } - } - else if( !IsGroupTheHelicopterGroup( pGroup ) ) - { - SOLDIERTYPE *pSoldier; - INT32 iVehicleID; - iVehicleID = GivenMvtGroupIdFindVehicleId( pGroup->ubGroupID ); - AssertMsg( iVehicleID != -1, "GroupArrival for vehicle group. Invalid iVehicleID. " ); - - pSoldier = GetSoldierStructureForVehicle( iVehicleID ); - AssertMsg( pSoldier, "GroupArrival for vehicle group. Invalid soldier pointer." ); - - SpendVehicleFuel( pSoldier, (INT16)(pGroup->uiTraverseTime*6) ); - - if( !VehicleFuelRemaining( pSoldier ) ) + else if (!IsGroupTheHelicopterGroup(pGroup)) { - ReportVehicleOutOfGas( iVehicleID, pGroup->ubSectorX, pGroup->ubSectorY ); - //Nuke the group's path, so they don't continue moving. - ClearMercPathsAndWaypointsForAllInGroup( pGroup ); + SOLDIERTYPE* pSoldier; + INT32 iVehicleID; + iVehicleID = GivenMvtGroupIdFindVehicleId(pGroup->ubGroupID); + AssertMsg(iVehicleID != -1, "GroupArrival for vehicle group. Invalid iVehicleID. "); + + pSoldier = GetSoldierStructureForVehicle(iVehicleID); + AssertMsg(pSoldier, "GroupArrival for vehicle group. Invalid soldier pointer."); + + SpendVehicleFuel(pSoldier, (INT16)(pGroup->uiTraverseTime * 6)); + + if (!VehicleFuelRemaining(pSoldier)) + { + ReportVehicleOutOfGas(iVehicleID, pGroup->ubSectorX, pGroup->ubSectorY); + //Nuke the group's path, so they don't continue moving. + ClearMercPathsAndWaypointsForAllInGroup(pGroup); + } } } } From d56288e313729b2c6eecde92eb65e04ea01bec1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Fa=C5=82kowski?= Date: Mon, 28 Aug 2023 15:26:34 +0200 Subject: [PATCH 42/50] Fixed incorrect vehicle group id after reloading the game and vehicle movement plot issues. (#207) --- Strategic/Map Screen Interface.cpp | 42 ++++++++++++++++++++++++------ Tactical/Squads.cpp | 15 ++++++++++- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/Strategic/Map Screen Interface.cpp b/Strategic/Map Screen Interface.cpp index 37eb3087..678c4fd7 100644 --- a/Strategic/Map Screen Interface.cpp +++ b/Strategic/Map Screen Interface.cpp @@ -4582,6 +4582,28 @@ void HandleSettingTheSelectedListOfMercs( void ) INT8 pbErrorNumber = -1; pSoldier = MercPtrs[gCharactersList[GetSelectedDestChar()].usSolID]; INT8 bSquadValue = pSoldier->bAssignment; + if (bSquadValue == VEHICLE) + { + for (INT8 bCounter = 0; bCounter < NUMBER_OF_SQUADS; ++bCounter) + { + if (Squad[bCounter][0] != NULL && IsVehicle(Squad[bCounter][0]) && + Squad[bCounter][0]->bVehicleID == pSoldier->iVehicleId) + { + bSquadValue = bCounter; + break; + } + } + } + if (bSquadValue >= NUMBER_OF_SQUADS) + { + if (pbErrorNumber != -1) + { + ReportMapScreenMovementError(pbErrorNumber); + } + SetSelectedDestChar(-1); + giDestHighLine = -1; + return; + } // find number of characters in particular squad. for (INT8 bCounter = 0; bCounter < NUMBER_OF_SOLDIERS_PER_SQUAD; ++bCounter) @@ -4693,17 +4715,21 @@ INT8 FindSquadThatSoldierCanJoin( SOLDIERTYPE *pSoldier ) // run through the list of squads for( bCounter = 0; bCounter < NUMBER_OF_SQUADS; bCounter++ ) { - // is this squad in this sector - if( IsThisSquadInThisSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ, bCounter ) ) + // anv: don't automatically put people in vehicle squads + if (Squad[bCounter][0] == NULL || !IsVehicle(Squad[bCounter][0])) { - // does it have room? - if( IsThisSquadFull( bCounter ) == FALSE ) + // is this squad in this sector + if (IsThisSquadInThisSector(pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ, bCounter)) { - // is it doing the same thing as the soldier is (staying or going) ? - if( IsSquadSelectedForMovement( bCounter ) == IsSoldierSelectedForMovement( pSoldier ) ) + // does it have room? + if (IsThisSquadFull(bCounter) == FALSE) { - // go ourselves a match, then - return( bCounter ); + // is it doing the same thing as the soldier is (staying or going) ? + if (IsSquadSelectedForMovement(bCounter) == IsSoldierSelectedForMovement(pSoldier)) + { + // go ourselves a match, then + return(bCounter); + } } } } diff --git a/Tactical/Squads.cpp b/Tactical/Squads.cpp index 7fb7c6b3..a7dda1b1 100644 --- a/Tactical/Squads.cpp +++ b/Tactical/Squads.cpp @@ -1700,7 +1700,20 @@ void CheckSquadMovementGroups( void ) for (INT8 iSoldier = 0; iSoldier < NUMBER_OF_SOLDIERS_PER_SQUAD; iSoldier++) { if (Squad[iSquad][iSoldier] != NULL) { - Squad[iSquad][iSoldier]->ubGroupID = pGroup->ubGroupID; + if (IsVehicle(Squad[iSquad][iSoldier])) + { + INT32 iCounter = 0; + for (iCounter = 0; iCounter < ubNumberOfVehicles; iCounter++) + { + if (pVehicleList[iCounter].ubProfileID == Squad[iSquad][iSoldier]->ubProfile) + break; + } + Squad[iSquad][iSoldier]->ubGroupID = pVehicleList[iCounter].ubMovementGroup; + } + else + { + Squad[iSquad][iSoldier]->ubGroupID = pGroup->ubGroupID; + } } } } From 7edecd68495723af86bf62fd6b1c18a592d96615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Fa=C5=82kowski?= Date: Mon, 28 Aug 2023 15:26:46 +0200 Subject: [PATCH 43/50] Fixed vehicle body not properly removed when destroyed during ramming (#210) --- Tactical/Soldier Control.cpp | 20 ++++++++++++++++++++ Tactical/Soldier Control.h | 8 ++++++++ TileEngine/structure.cpp | 4 ++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index 9a594ee2..271368d0 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -10700,6 +10700,22 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr return(ubCombinedLoss); } +void SOLDIERTYPE::SoldierTakeDelayedDamage(INT8 bHeight, INT16 sLifeDeduct, INT16 sBreathLoss, UINT8 ubReason, UINT8 ubAttacker, INT32 sSourceGrid, INT16 sSubsequent, BOOLEAN fShowDamage) +{ + delayedDamageFunction = [this, bHeight, sLifeDeduct, sBreathLoss, ubReason, ubAttacker, sSourceGrid, sSubsequent, fShowDamage]() + { + this->SoldierTakeDamage(bHeight, sLifeDeduct, sBreathLoss, ubReason, ubAttacker, sSourceGrid, sSubsequent, fShowDamage); + }; +} + +void SOLDIERTYPE::ResolveDelayedDamage() +{ + if (delayedDamageFunction) + { + delayedDamageFunction(); + delayedDamageFunction = nullptr; + } +} extern BOOLEAN IsMercSayingDialogue( UINT8 ubProfileID ); @@ -11499,6 +11515,8 @@ void SOLDIERTYPE::MoveMerc( FLOAT dMovementChange, FLOAT dAngle, BOOLEAN fCheckR // OK, set new position this->EVENT_InternalSetSoldierPosition( dXPos, dYPos, FALSE, FALSE, FALSE ); + this->ResolveDelayedDamage(); + // Flugente: drag people if ( currentlydragging ) { @@ -26234,4 +26252,6 @@ void SOLDIERTYPE::InitializeExtraData(void) this->ubQuickItemSlot = 0; this->usGrenadeItem = 0; + + this->delayedDamageFunction = nullptr; } diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index e02e1de0..69745618 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -20,6 +20,7 @@ #include #include "GameSettings.h" // added by Flugente #include "Disease.h" // added by Flugente +#include #define PTR_CIVILIAN (pSoldier->bTeam == CIV_TEAM) #define PTR_CROUCHED (gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_CROUCH) @@ -1656,6 +1657,10 @@ public: UINT8 ubQuickItemSlot; UINT16 usGrenadeItem; + + // anv: resolve damage with delay, e.g. damage applied mid movement that would cause issues with world data if applied immediately + std::function delayedDamageFunction; + public: // CREATION FUNCTIONS BOOLEAN DeleteSoldier( void ); @@ -1719,6 +1724,9 @@ public: void ReviveSoldier( void ); UINT8 SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBreathDeduct, UINT8 ubReason, UINT8 ubAttacker, INT32 sSourceGrid, INT16 sSubsequent, BOOLEAN fShowDamage ); + // anv: resolve damage with delay, e.g. damage applied mid movement that would cause issues with world data if applied immediately + void SoldierTakeDelayedDamage(INT8 bHeight, INT16 sLifeDeduct, INT16 sBreathDeduct, UINT8 ubReason, UINT8 ubAttacker, INT32 sSourceGrid, INT16 sSubsequent, BOOLEAN fShowDamage); + void ResolveDelayedDamage(); // Palette functions for soldiers BOOLEAN CreateSoldierPalettes( void ); diff --git a/TileEngine/structure.cpp b/TileEngine/structure.cpp index 49264a51..1d8b2fc2 100644 --- a/TileEngine/structure.cpp +++ b/TileEngine/structure.cpp @@ -1914,10 +1914,10 @@ INT8 DamageStructure( STRUCTURE * pStructure, UINT8 ubDamage, UINT8 ubReason, IN //Since the structure is being damaged, set the map element that a structure is damaged gpWorldLevelData[ tmpgridno ].uiFlags |= MAPELEMENT_STRUCTURE_DAMAGED; - // handle structure revenge - damage to vehicle + // handle structure revenge - damage to vehicle - to be resolved after movement if ( ubOwner != NOBODY && MercPtrs[ubOwner] && !ARMED_VEHICLE( MercPtrs[ubOwner] ) ) { - MercPtrs[ ubOwner ]->SoldierTakeDamage( 0, Random(max(0,(ubBaseArmour-10)/5))+max(0,(ubBaseArmour-10)/5), 0, TAKE_DAMAGE_STRUCTURE_EXPLOSION, NOBODY, MercPtrs[ ubOwner ]->sGridNo, 0, TRUE ); + MercPtrs[ubOwner]->SoldierTakeDelayedDamage(0, Random(max(0,(ubBaseArmour-10)/5)) + max(0,(ubBaseArmour-10)/5), 0, TAKE_DAMAGE_STRUCTURE_EXPLOSION, NOBODY, MercPtrs[ ubOwner ]->sGridNo, 0, TRUE); } // recompile = TRUE means that we destroyed something From ee3d2472e720739add6445ad9bf01e2dc613b8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Fa=C5=82kowski?= Date: Tue, 29 Aug 2023 21:25:14 +0200 Subject: [PATCH 44/50] Removed unnecessary offset for face gear --- Tactical/Faces.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tactical/Faces.cpp b/Tactical/Faces.cpp index aad59742..059ba827 100644 --- a/Tactical/Faces.cpp +++ b/Tactical/Faces.cpp @@ -1467,8 +1467,8 @@ void GetXYForRightIconPlacement_FaceGera( FACETYPE *pFace, UINT16 ubIndex, INT16 usHeight = pTrav->usHeight; usWidth = pTrav->usWidth; - sX = sFaceX + ( usWidth * bNumIcons ) + 1; - sY = sFaceY + pFace->usFaceHeight - usHeight - 1; + sX = sFaceX + ( usWidth * bNumIcons ); + sY = sFaceY + pFace->usFaceHeight - usHeight; *psX = sX; *psY = sY; From 7910be0ab7119895b1ab2fc166ad909e8d59f7b6 Mon Sep 17 00:00:00 2001 From: kitty624 <58940527+kitty624@users.noreply.github.com> Date: Wed, 30 Aug 2023 19:34:30 +0200 Subject: [PATCH 45/50] Fix personality check for backgrounds during IMP creation --- Laptop/IMP Background.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Laptop/IMP Background.cpp b/Laptop/IMP Background.cpp index b7cddab4..ec269423 100644 --- a/Laptop/IMP Background.cpp +++ b/Laptop/IMP Background.cpp @@ -16,6 +16,7 @@ #include "soldier profile type.h" #include "IMP Compile Character.h" #include "IMP Disability Trait.h" + #include "IMP Character Trait.h" #include "GameSettings.h" #include "Interface.h" @@ -708,7 +709,7 @@ BOOLEAN IsBackGroundAllowed( UINT16 ubNumber ) break; } - switch ( iAttitude ) + switch ( iChosenCharacterTrait() ) { case CHAR_TRAIT_SOCIABLE: if ( zBackground[ ubNumber ].uiFlags & BACKGROUND_XENOPHOBIC ) From 28aa73a859d71f802d961dfed617a7ea8b01cf8a Mon Sep 17 00:00:00 2001 From: rftrdev <102184004+rftrdev@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:01:17 -0700 Subject: [PATCH 46/50] Clamp coolness to valid values when generating ARC ammo cache (#220) Fixes #217 --- Strategic/Rebel Command.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Strategic/Rebel Command.cpp b/Strategic/Rebel Command.cpp index b5ce30aa..84f6f69f 100644 --- a/Strategic/Rebel Command.cpp +++ b/Strategic/Rebel Command.cpp @@ -4429,7 +4429,8 @@ void SetupInfo() && (gGameOptions.ubGameStyle == STYLE_SCIFI || !Item[i].scifi)) { // coolness runs from 1-10, so apply offset - ItemIdCache::ammo[Item[i].ubCoolness-1].push_back(i); + const UINT8 coolness = min(max(1, Item[i].ubCoolness), 10); + ItemIdCache::ammo[coolness-1].push_back(i); } } } From 23ceb0fa4c7bfa3946f85fb881eee7709ad2c568 Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Tue, 5 Sep 2023 17:33:42 +0300 Subject: [PATCH 47/50] Fix bug in OCTH prone CTH calculation (by Seven) (#221) GetToHitBonus() expects BOOLEAN for fProneStance argument, with no comparison to ANIM_PRONE, the ubEndHeight ends up adding bipod bonuses to *all* stances --- Tactical/Weapons.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index 55acbf88..5e9350a6 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -7480,7 +7480,7 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, else { INT16 moda = GetToHitBonus(pInHand, iRange, bLightLevel, stance && iRange > MIN_PRONE_RANGE); - INT16 modb = GetToHitBonus(pInHand, iRange, bLightLevel, gAnimControl[pSoldier->usAnimState].ubEndHeight && iRange > MIN_PRONE_RANGE); + INT16 modb = GetToHitBonus(pInHand, iRange, bLightLevel, gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE && iRange > MIN_PRONE_RANGE); iChance += (INT32)((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb) / 100); } From fc5573f73d7425070f8d6fc33fee6ab4c7f1894e Mon Sep 17 00:00:00 2001 From: Asdow <20314541+Asdow@users.noreply.github.com> Date: Tue, 5 Sep 2023 18:57:40 +0300 Subject: [PATCH 48/50] Correct check for stance (#222) Same issue as in commit 23ceb0f --- Tactical/Weapons.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index 5e9350a6..667f9be4 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -7479,7 +7479,7 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, } else { - INT16 moda = GetToHitBonus(pInHand, iRange, bLightLevel, stance && iRange > MIN_PRONE_RANGE); + INT16 moda = GetToHitBonus(pInHand, iRange, bLightLevel, stance == ANIM_PRONE && iRange > MIN_PRONE_RANGE); INT16 modb = GetToHitBonus(pInHand, iRange, bLightLevel, gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE && iRange > MIN_PRONE_RANGE); iChance += (INT32)((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb) / 100); } From 4c861e441af4dc4fca87f04f428f98ab192d2c5b Mon Sep 17 00:00:00 2001 From: sun-alf Date: Fri, 8 Sep 2023 15:55:01 +0300 Subject: [PATCH 49/50] [Fix] Revive FPS drawing overlay To enable built-in FPS, change gbFPSDisplay to SHOW_FULL_FPS (1) in debugger (or change it in src code and rebuild). --- TileEngine/Render Dirty.cpp | 6 ++++++ TileEngine/Render Dirty.h | 3 ++- gamescreen.cpp | 5 +++-- jascreens.cpp | 43 +++++++++++++------------------------ 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/TileEngine/Render Dirty.cpp b/TileEngine/Render Dirty.cpp index f90b3d25..1e22feb6 100644 --- a/TileEngine/Render Dirty.cpp +++ b/TileEngine/Render Dirty.cpp @@ -1009,6 +1009,12 @@ BOOLEAN UpdateVideoOverlay( VIDEO_OVERLAY_DESC *pTopmostDesc, UINT32 iBlitterInd } } + if ( uiFlags & VOVERLAY_DESC_FONT ) + { + gVideoOverlays[iBlitterIndex].uiFontID = pTopmostDesc->uiFontID; + gVideoOverlays[iBlitterIndex].ubFontBack = pTopmostDesc->ubFontBack; + gVideoOverlays[iBlitterIndex].ubFontFore = pTopmostDesc->ubFontFore; + } if ( uiFlags & VOVERLAY_DESC_DISABLED ) { diff --git a/TileEngine/Render Dirty.h b/TileEngine/Render Dirty.h index e3171deb..113f0766 100644 --- a/TileEngine/Render Dirty.h +++ b/TileEngine/Render Dirty.h @@ -15,9 +15,10 @@ #define VOVERLAY_STARTDISABLED 0x00000002 -#define VOVERLAY_DESC_TEXT 0x00001000 +#define VOVERLAY_DESC_TEXT 0x00001000 #define VOVERLAY_DESC_DISABLED 0x00002000 #define VOVERLAY_DESC_POSITION 0x00004000 +#define VOVERLAY_DESC_FONT 0x00008000 // STRUCTURES diff --git a/gamescreen.cpp b/gamescreen.cpp index 67e70d99..28fd812d 100644 --- a/gamescreen.cpp +++ b/gamescreen.cpp @@ -166,6 +166,7 @@ UINT32 MainGameScreenInit(void) UnLockVideoSurface( FRAME_BUFFER); InitializeBackgroundRects(); + InitializeBaseDirtyRectQueue(); //EnvSetTimeInHours(ENV_TIME_12); @@ -188,8 +189,8 @@ UINT32 MainGameScreenInit(void) giFPSOverlay = RegisterVideoOverlay( ( VOVERLAY_STARTDISABLED | VOVERLAY_DIRTYBYTEXT ), &VideoOverlayDesc ); // SECOND, PERIOD COUNTER - VideoOverlayDesc.sLeft = 30; - VideoOverlayDesc.sTop = 0; + VideoOverlayDesc.sLeft = 0; + VideoOverlayDesc.sTop = 12; VideoOverlayDesc.sX = VideoOverlayDesc.sLeft; VideoOverlayDesc.sY = VideoOverlayDesc.sTop; swprintf( VideoOverlayDesc.pzText, L"Levelnodes: 100000" ); diff --git a/jascreens.cpp b/jascreens.cpp index 80f5188a..f921cb0b 100644 --- a/jascreens.cpp +++ b/jascreens.cpp @@ -122,46 +122,33 @@ void DisplayFrameRate( ) uiFrameCount = 0; } - // Create string - SetFont( SMALLFONT1 ); - - //DebugMsg(TOPIC_JA2, DBG_LEVEL_0, String( "FPS: %d ", __min( uiFPS, 1000 ) ) ); - - if ( uiFPS < 20 ) - { - SetFontBackground( FONT_MCOLOR_BLACK ); - SetFontForeground( FONT_MCOLOR_LTRED ); - } - else - { - SetFontBackground( FONT_MCOLOR_BLACK ); - SetFontForeground( FONT_MCOLOR_DKGRAY ); - } - if ( gbFPSDisplay == SHOW_FULL_FPS ) { + memset(&VideoOverlayDesc, 0, sizeof(VideoOverlayDesc)); + // FRAME RATE - memset( &VideoOverlayDesc, 0, sizeof( VideoOverlayDesc ) ); - swprintf( VideoOverlayDesc.pzText, L"%ld", __min( uiFPS, 1000 ) ); - VideoOverlayDesc.uiFlags = VOVERLAY_DESC_TEXT; + VideoOverlayDesc.uiFontID = SMALLFONT1; + VideoOverlayDesc.ubFontBack = FONT_MCOLOR_BLACK; + VideoOverlayDesc.ubFontFore = uiFPS < 20 ? FONT_MCOLOR_LTRED : FONT_MCOLOR_DKGRAY; + swprintf( VideoOverlayDesc.pzText, L"FPS: %ld", __min( uiFPS, 1000 ) ); + VideoOverlayDesc.uiFlags = VOVERLAY_DESC_TEXT | VOVERLAY_DESC_FONT | VOVERLAY_DESC_DISABLED; UpdateVideoOverlay( &VideoOverlayDesc, giFPSOverlay, FALSE ); // TIMER COUNTER - swprintf( VideoOverlayDesc.pzText, L"%ld", __min( giTimerDiag, 1000 ) ); - VideoOverlayDesc.uiFlags = VOVERLAY_DESC_TEXT; + swprintf( VideoOverlayDesc.pzText, L"Frame: %04ld ms", __min( giTimerDiag, 10000 ) ); + VideoOverlayDesc.uiFlags = VOVERLAY_DESC_TEXT | VOVERLAY_DESC_DISABLED; UpdateVideoOverlay( &VideoOverlayDesc, giCounterPeriodOverlay, FALSE ); - - if( GetMouseMapPos( &usMapPos) ) - { + //if( GetMouseMapPos( &usMapPos) ) + //{ //gprintfdirty( 0, 315, L"(%d)",sMapPos); //mprintf( 0,315,L"(%d)",sMapPos); - } - else - { + //} + //else + //{ //gprintfdirty( 0, 315, L"(%d %d)",gusMouseXPos, gusMouseYPos - INTERFACE_START_Y ); //mprintf( 0,315,L"(%d %d)",gusMouseXPos, gusMouseYPos - INTERFACE_START_Y ); - } + //} } if ( ( gTacticalStatus.uiFlags & GODMODE ) ) From f8637e59725ff7af3445d5aa687fed48720c5ff9 Mon Sep 17 00:00:00 2001 From: sun-alf Date: Fri, 8 Sep 2023 21:53:38 +0300 Subject: [PATCH 50/50] [~] Performance optimizations around Attachment[] and Launchable[] arrays AIMNAS with its ~50K elements in Attachment[] suffered of performance drop when MOLLE stuff is in the visible inventory. To resolve it the following is done: * Introduce gMAXATTACHMENTS_READ with actual number of elements in Attachment[]; according refactoring. * Introduce gMAXLAUNCHABLES_READ with actual number of elements in Launchable[]; according refactoring. * Introduce std::multimap AttachmentBackmap for quick access to attachments using itemId as a key. * Sort Attachment[] by attachmentIndex right after loading from XML. According order in XML is not needed anymore. * Introduce FindAttachmentRange() for quick access to attachments using attachmentId as a key (binary search in Attachment[]). In a few words, GetHelpTextForItemInLaptop(), ValidAttachment(), SetAttachmentSlotsFlag() were heavily optimized. --- Laptop/BobbyRGuns.cpp | 155 +++++++++++++++-------------------- Tactical/Interface Items.cpp | 34 +++----- Tactical/Item Types.h | 24 +++++- Tactical/Items.cpp | 133 +++++++++++++++++++----------- Tactical/Items.h | 1 + Tactical/XML_Attachments.cpp | 41 ++++++--- Tactical/XML_Launchable.cpp | 6 +- 7 files changed, 224 insertions(+), 170 deletions(-) diff --git a/Laptop/BobbyRGuns.cpp b/Laptop/BobbyRGuns.cpp index 479a1ef7..01136a57 100644 --- a/Laptop/BobbyRGuns.cpp +++ b/Laptop/BobbyRGuns.cpp @@ -362,6 +362,29 @@ void GetHelpTextForItemInLaptop( STR16 pzStr, UINT16 usItemNumber ); void HandleBobbyRGunsKeyBoardInput(); void HandleBobbyRayMouseWheel(void); +// Appends source STR16 to target STR16 using decorators ("\n" and "..."). +// Returns TRUE if everything fits target, FALSE otherwise. +static BOOLEAN DecorateAppendString(STR16 target, size_t targetCapacity, STR16 source, UINT32 frontDecoratorsCnt = 1) +{ + const CHAR16 DECORATOR0[] = L"\n"; + const CHAR16 DECORATOR1[] = L"\n..."; + BOOLEAN result = FALSE; + size_t decoratorLen = wcslen(DECORATOR0) * frontDecoratorsCnt; + + if (wcslen(target) + decoratorLen + wcslen(source) + 1 < targetCapacity) + { + for (UINT32 i = 0; i < frontDecoratorsCnt; i++) + wcscat(target, DECORATOR0); + wcscat(target, source); + result = TRUE; + } + else if (wcslen(target) + wcslen(DECORATOR1) + 1 < targetCapacity) + { + wcscat(target, DECORATOR1); + } // otherwise don't even touch the target + return result; +} + void GameInitBobbyRGuns() { guiTempCurrentMode=0; @@ -4088,7 +4111,8 @@ void HandleBobbyRayMouseWheel(void) } void GetHelpTextForItemInLaptop( STR16 pzStr, UINT16 usItemNumber ) -{ +{ + const size_t ATTACHMENTS_STRBUF_SIZE = 3800; CHAR16 zItemName[ SIZE_ITEM_NAME ]; UINT8 ubItemCount=0; @@ -4115,12 +4139,9 @@ void GetHelpTextForItemInLaptop( STR16 pzStr, UINT16 usItemNumber ) // HEADROCK HAM 3: Variables for "Possible Attachment List" BOOLEAN fAttachmentsFound = FALSE; // Contains entire string of attachment names - CHAR16 attachStr[3900]; - // Contains current attachment string - CHAR16 attachStr2[100]; + CHAR16 attachStr[ATTACHMENTS_STRBUF_SIZE]; // Contains temporary attachment list before added to string constant from text.h - CHAR16 attachStr3[3900]; - UINT16 usAttachment; + CHAR16 attachStr3[ATTACHMENTS_STRBUF_SIZE]; CreateItem(usItemNumber, 100, &pObject); INT16 ubAttackAPs = BaseAPsToShootOrStab( APBPConstants[DEFAULT_APS], APBPConstants[DEFAULT_AIMSKILL], &pObject, NULL ); @@ -4146,109 +4167,69 @@ void GetHelpTextForItemInLaptop( STR16 pzStr, UINT16 usItemNumber ) else wcscat( apStr, L" / -" ); - // HEADROCK HAM 3: Empty these strings first, to avoid crashes. Please keep this here. - swprintf( attachStr, L"" ); - swprintf( attachStr2, L"" ); - swprintf( attachStr3, L"" ); + attachStr[0] = 0; + attachStr3[0] = 0; // HEADROCK HAM 3: Generate list of possible attachments to a gun (Guns only!) if (gGameExternalOptions.fBobbyRayTooltipsShowAttachments) { - UINT16 iLoop = 0; - // Check entire attachment list - while( 1 ) + if (UsingNewAttachmentSystem()) { - //Madd: Common Attachment Framework - //TODO: Note that the items in this list will be duplicated if they are present in both the CAF and the old attachment method - //need to refactor this to work more like the NAS attachment slots method - usAttachment = 0; - if ( IsAttachmentPointAvailable(Item[usItemNumber].uiIndex, iLoop) ) - { - usAttachment = iLoop; - // If the attachment is not hidden - if (usAttachment > 0 && !Item[ usAttachment ].hiddenaddon && !Item[ usAttachment ].hiddenattachment) - { - if (wcslen( attachStr3 ) + wcslen(Item[usAttachment].szItemName) > 3800) - { - // End list early to avoid stack overflow - wcscat( attachStr3, L"\n..." ); - break; - } - else - {// Add the attachment's name to the list. - fAttachmentsFound = TRUE; - swprintf( attachStr2, L"\n%s", Item[ usAttachment ].szItemName ); - wcscat( attachStr3, attachStr2); - } - } - } - - // Is the weapon we're checking the same as the one we're tooltipping? - usAttachment = 0; - if (Attachment[iLoop][1] == Item[usItemNumber].uiIndex) + std::pair::iterator, std::multimap::iterator> range; + std::multimap::iterator it; + range = AttachmentBackmap.equal_range(Item[usItemNumber].uiIndex); + for (it = range.first; it != range.second; it++) { - usAttachment = Attachment[iLoop][0]; - } - - // If the attachment is not hidden - if (usAttachment > 0 && !Item[ usAttachment ].hiddenaddon && !Item[ usAttachment ].hiddenattachment) - { - if (wcslen( attachStr3 ) + wcslen(Item[usAttachment].szItemName) > 3800) + UINT16 attachmentId = it->second.attachmentIndex; + if (!Item[attachmentId].hiddenaddon && !Item[attachmentId].hiddenattachment && ItemIsLegal(attachmentId, TRUE)) { - // End list early to avoid stack overflow - wcscat( attachStr3, L"\n..." ); - break; - } - else - {// Add the attachment's name to the list. fAttachmentsFound = TRUE; - swprintf( attachStr2, L"\n%s", Item[ usAttachment ].szItemName ); - wcscat( attachStr3, attachStr2); + if (DecorateAppendString(attachStr3, ATTACHMENTS_STRBUF_SIZE, Item[attachmentId].szItemName) == FALSE) + break; } } - - - iLoop++; - if (Attachment[iLoop][0] == 0 && Item[iLoop].usItemClass == 0) - { - // Reached end of list - break; - } } + else // old attachment system + { + for (UINT32 itemId = 1; itemId < gMAXITEMS_READ; itemId++) + { + // If the attachment is not hidden and attachable to the gun (usItemNumber) + if (!Item[itemId].hiddenaddon && !Item[itemId].hiddenattachment && + ItemIsLegal(itemId, TRUE) && IsAttachmentPointAvailable(Item[usItemNumber].uiIndex, itemId)) + { + fAttachmentsFound = TRUE; + if (DecorateAppendString(attachStr3, ATTACHMENTS_STRBUF_SIZE, Item[itemId].szItemName) == FALSE) + break; + } + } + } + if (fAttachmentsFound) { - // Add extra empty line and attachment list title - swprintf( attachStr, L"\n \n%s", gWeaponStatsDesc[ 14 ] ); - wcscat( attachStr, attachStr3 ); + DecorateAppendString(attachStr, ATTACHMENTS_STRBUF_SIZE, gWeaponStatsDesc[14], 2); // 2 new lines and "Attachments:" title + DecorateAppendString(attachStr, ATTACHMENTS_STRBUF_SIZE, attachStr3, 0); // no new line, list of attachments (starts with new line) } } //Sum up default attachments. BOOLEAN fFoundDefault = FALSE; - swprintf( attachStr2, L"" ); - swprintf( attachStr3, L"" ); - for(UINT8 cnt = 0; cnt < MAX_DEFAULT_ATTACHMENTS; cnt++){ - if(Item[usItemNumber].defaultattachments[cnt] != 0){ - if (wcslen( attachStr ) + wcslen(attachStr3) + wcslen(Item[ Item[usItemNumber].defaultattachments[cnt] ].szItemName) > 3800) - { - // End list early to avoid stack overflow - wcscat( attachStr3, L"\n..." ); + attachStr3[0] = 0; + for (UINT8 cnt = 0; cnt < MAX_DEFAULT_ATTACHMENTS; cnt++) + { + if (Item[usItemNumber].defaultattachments[cnt] != 0) + { + if (DecorateAppendString(attachStr3, ATTACHMENTS_STRBUF_SIZE, Item[Item[usItemNumber].defaultattachments[cnt]].szItemName) == FALSE) break; - } fFoundDefault = TRUE; - swprintf( attachStr2, L"\n%s", Item[ Item[usItemNumber].defaultattachments[cnt] ].szItemName ); - wcscat( attachStr3, attachStr2 ); - } else { - //If we found an empty entry, we can assume the rest will be empty too. - break; } + else // If we found an empty entry, we can assume the rest will be empty too. + break; } - if(fFoundDefault){ - //Found at least one default attachment, write it to the attachment string. - CHAR16 defaultStr[50]; - swprintf( defaultStr, L"\n \n%s", gWeaponStatsDesc[ 17 ] ); - wcscat( attachStr, defaultStr ); - wcscat( attachStr, attachStr3 ); + + if (fFoundDefault) + { + DecorateAppendString(attachStr, ATTACHMENTS_STRBUF_SIZE, gWeaponStatsDesc[17], 2); // 2 new lines and "Default:" title + DecorateAppendString(attachStr, ATTACHMENTS_STRBUF_SIZE, attachStr3, 0); // no new line, list of attachments (starts with new line) } // HEADROCK HAM 3: Added last string (attachStr), for display of the possible attachment list. diff --git a/Tactical/Interface Items.cpp b/Tactical/Interface Items.cpp index d71aa3ca..9b783204 100644 --- a/Tactical/Interface Items.cpp +++ b/Tactical/Interface Items.cpp @@ -2913,20 +2913,16 @@ void HandleAnyMercInSquadHasCompatibleStuff( UINT8 ubSquad, OBJECTTYPE *pObject, BOOLEAN IsMutuallyValidAttachmentOrLaunchable(UINT16 usAttItem, UINT16 usItem)//dnl ch76 091113 { - UINT32 uiLoop = 0; - while ( Attachment[uiLoop][0] ) + for (UINT32 uiLoop = 0; uiLoop < gMAXATTACHMENTS_READ; uiLoop++) { - if(Attachment[uiLoop][0] == usAttItem && Attachment[uiLoop][1] == usItem || Attachment[uiLoop][0] == usItem && Attachment[uiLoop][1] == usAttItem ) + if (Attachment[uiLoop].attachmentIndex == usAttItem && Attachment[uiLoop].itemIndex == usItem || Attachment[uiLoop].attachmentIndex == usItem && Attachment[uiLoop].itemIndex == usAttItem) return(TRUE); - ++uiLoop; } - uiLoop = 0; - while ( Launchable[uiLoop][0] ) + for (UINT32 uiLoop = 0; uiLoop < gMAXLAUNCHABLES_READ; uiLoop++) { if ( Launchable[uiLoop][0] == usAttItem && Launchable[uiLoop][1] == usItem || Launchable[uiLoop][0] == usItem && Launchable[uiLoop][1] == usAttItem ) return(TRUE); - ++uiLoop; } return(FALSE); @@ -5765,12 +5761,8 @@ void UpdateAttachmentTooltips(OBJECTTYPE *pObject, UINT8 ubStatusIndex) } // sevenfm: check launchables - for (UINT16 usLoop = 0; usLoop < MAXITEMS + 1; usLoop++) + for (UINT16 usLoop = 0; usLoop < gMAXLAUNCHABLES_READ; usLoop++) { - // check that reached end of valid launchables - if (Launchable[usLoop][0] == 0) - break; - usAttachment = 0; if (Launchable[usLoop][1] == pObject->usItem && AttachmentSlots[usLoopSlotID].nasAttachmentClass & Item[Launchable[usLoop][0]].nasAttachmentClass) { @@ -5801,17 +5793,14 @@ void UpdateAttachmentTooltips(OBJECTTYPE *pObject, UINT8 ubStatusIndex) } // check all attachments - for (UINT16 usLoop = 0; usLoop < MAXATTACHMENTS; usLoop++) + //TODO: should be optimized using AttachmentBackmap and/or possibly FindAttachmentRange() + for (UINT32 uiLoop = 0; uiLoop < gMAXATTACHMENTS_READ; uiLoop++) { - // check that reached end of valid attachments - if (Attachment[usLoop][0] == 0) - break; - usAttachment = 0; - if (Attachment[usLoop][1] == pObject->usItem && AttachmentSlots[usLoopSlotID].nasAttachmentClass & Item[Attachment[usLoop][0]].nasAttachmentClass) + if (Attachment[uiLoop].itemIndex == pObject->usItem && AttachmentSlots[usLoopSlotID].nasAttachmentClass & Item[Attachment[uiLoop].attachmentIndex].nasAttachmentClass) { //search primary item attachments.xml - usAttachment = Attachment[usLoop][0]; + usAttachment = Attachment[uiLoop].attachmentIndex; } else { @@ -5820,8 +5809,11 @@ void UpdateAttachmentTooltips(OBJECTTYPE *pObject, UINT8 ubStatusIndex) UINT16* p = cnt ? &attachedList.front() : NULL; while (cnt) { - if (Attachment[usLoop][1] == *p && AttachmentSlots[usLoopSlotID].nasAttachmentClass & Item[Attachment[usLoop][0]].nasAttachmentClass) - usAttachment = Attachment[usLoop][0]; + if (Attachment[uiLoop].itemIndex == *p && AttachmentSlots[usLoopSlotID].nasAttachmentClass & Item[Attachment[uiLoop].attachmentIndex].nasAttachmentClass) + { + usAttachment = Attachment[uiLoop].attachmentIndex; + break; + } cnt--, p++; } diff --git a/Tactical/Item Types.h b/Tactical/Item Types.h index 67e761d4..cdd3d2a1 100644 --- a/Tactical/Item Types.h +++ b/Tactical/Item Types.h @@ -1724,9 +1724,30 @@ typedef enum MAXITEMS = 16001 } ITEMDEFINE; +struct AttachmentStruct +{ + UINT16 attachmentIndex; + UINT16 itemIndex; + UINT16 APCost; + UINT16 NASOnly; + + bool operator<(const AttachmentStruct& a) const + { + bool result = false; + if (attachmentIndex < a.attachmentIndex) + result = true; + else if (attachmentIndex == a.attachmentIndex) + result = itemIndex < a.itemIndex; + + return result; + } +}; + // Flugente: in order not to loop over MAXITEMS items if we only have a few thousand, remember the actual number of items in the xml extern UINT32 gMAXITEMS_READ; extern UINT32 gMAXAMMOTYPES_READ; +extern UINT32 gMAXATTACHMENTS_READ; +extern UINT32 gMAXLAUNCHABLES_READ; /* CHRISL: Arrays to track ic group information. These allow us to determine which LBE slots control which pockets and what LBE class the pockets are.*/ @@ -1781,7 +1802,8 @@ const INT16 icDefault[NUM_INV_SLOTS] = { #define MAXATTACHMENTS 60000 extern INVTYPE Item[MAXITEMS]; -extern UINT16 Attachment[MAXATTACHMENTS][4]; +extern AttachmentStruct Attachment[MAXATTACHMENTS]; +extern std::multimap AttachmentBackmap; //WarmSteel - Here we have some definitions for NAS typedef struct diff --git a/Tactical/Items.cpp b/Tactical/Items.cpp index ba9d4e00..dd56f7f7 100644 --- a/Tactical/Items.cpp +++ b/Tactical/Items.cpp @@ -566,7 +566,8 @@ AttachmentInfoStruct AttachmentInfo[MAXITEMS+1];// = AttachmentSlotStruct AttachmentSlots[MAXITEMS+1]; ItemReplacementStruct ItemReplacement[MAXATTACHMENTS]; -UINT16 Attachment[MAXATTACHMENTS][4];// = +AttachmentStruct Attachment[MAXATTACHMENTS];// = +std::multimap AttachmentBackmap; // key is itemId //{ // {SILENCER, GLOCK_17}, // {SILENCER, GLOCK_18}, @@ -2253,7 +2254,6 @@ INT32 GetAttachmentInfoIndex( UINT16 usItem ) //Determine if it is possible to add this attachment to the item. BOOLEAN ValidAttachment( UINT16 usAttachment, UINT16 usItem, UINT8 * pubAPCost ) { - INT32 iLoop = 0; if (pubAPCost) { *pubAPCost = (UINT8)APBPConstants[AP_RELOAD_GUN]; //default value } @@ -2269,40 +2269,26 @@ BOOLEAN ValidAttachment( UINT16 usAttachment, UINT16 usItem, UINT8 * pubAPCost ) *pubAPCost = Item[usAttachment].ubAttachToPointAPCost; return TRUE; } + // look for the section of the array pertaining to this attachment... - while( 1 ) - { - if (Attachment[iLoop][0] == usAttachment) - { - break; - } - ++iLoop; - if (Attachment[iLoop][0] == 0) - { - // the proposed item cannot be attached to anything! - return( FALSE ); - } - } + UINT32 startIndex = 0, endIndex = 0; + if (FindAttachmentRange(usAttachment, &startIndex, &endIndex) == FALSE) + return FALSE; + // now look through this section for the item in question - while( 1 ) + for (UINT32 iLoop = startIndex; iLoop <= endIndex; iLoop++) { - if (Attachment[iLoop][1] == usItem) + if (Attachment[iLoop].itemIndex == usItem) { - if ( UsingNewAttachmentSystem( ) || Attachment[iLoop][3] != 1 ) + if ( UsingNewAttachmentSystem( ) || Attachment[iLoop].NASOnly != 1 ) { if (pubAPCost) - *pubAPCost = (UINT8)Attachment[iLoop][2]; //Madd: get ap cost of attaching items :) - break; + *pubAPCost = (UINT8)Attachment[iLoop].APCost; //Madd: get ap cost of attaching items :) } - } - ++iLoop; - if (Attachment[iLoop][0] != usAttachment) - { - // the proposed item cannot be attached to the item in question - return( FALSE ); + return TRUE; } } - return( TRUE ); + return FALSE; } BOOLEAN ValidAttachment( UINT16 usAttachment, OBJECTTYPE * pObj, UINT8 * pubAPCost, UINT8 subObject, std::vector usAttachmentSlotIndexVector) @@ -5677,40 +5663,40 @@ BOOLEAN OBJECTTYPE::AttachObjectNAS( SOLDIERTYPE * pSoldier, OBJECTTYPE * pAttac UINT64 SetAttachmentSlotsFlag(OBJECTTYPE* pObj) { UINT64 uiSlotFlag = 0; - UINT32 uiLoop = 0; - UINT32 fItem; if (pObj->exists() == false) return 0; - UINT64 point = GetAvailableAttachmentPoint(pObj, 0); - - while (uiLoop < gMAXITEMS_READ && Item[uiLoop].usItemClass != 0 || - uiLoop < MAXATTACHMENTS && Attachment[uiLoop][0] != 0 || - uiLoop < MAXITEMS + 1 && Launchable[uiLoop][0] != 0) + if (UsingNewAttachmentSystem()) { - if (uiLoop > 0 && uiLoop < gMAXITEMS_READ && IsAttachmentPointAvailable(point, uiLoop, TRUE)) + std::pair::iterator, std::multimap::iterator> range; + std::multimap::iterator it; + range = AttachmentBackmap.equal_range(pObj->usItem); + for (it = range.first; it != range.second; it++) { - fItem = uiLoop; - if (fItem && ItemIsLegal(fItem, TRUE)) - uiSlotFlag |= Item[fItem].nasAttachmentClass; + UINT16 attachmentId = it->second.attachmentIndex; + if (ItemIsLegal(attachmentId, TRUE)) + uiSlotFlag |= Item[attachmentId].nasAttachmentClass; } - if (uiLoop < MAXATTACHMENTS && Attachment[uiLoop][1] == pObj->usItem) + for (UINT32 i = 0; i < gMAXLAUNCHABLES_READ; i++) { - fItem = Attachment[uiLoop][0]; - if (fItem && ItemIsLegal(fItem, TRUE)) - uiSlotFlag |= Item[fItem].nasAttachmentClass; + if (Launchable[i][1] == pObj->usItem) + { + UINT16 attachmentId = Launchable[i][0]; + if (ItemIsLegal(attachmentId, TRUE)) + uiSlotFlag |= Item[attachmentId].nasAttachmentClass; + } } - - if (uiLoop < MAXITEMS + 1 && Launchable[uiLoop][1] == pObj->usItem) + } + else + { + UINT64 point = GetAvailableAttachmentPoint(pObj, 0); + for (UINT32 itemId = 1; itemId < gMAXITEMS_READ; itemId++) { - fItem = Launchable[uiLoop][0]; - if (fItem && ItemIsLegal(fItem, TRUE)) - uiSlotFlag |= Item[fItem].nasAttachmentClass; + if (ItemIsLegal(itemId, TRUE) && IsAttachmentPointAvailable(point, itemId, TRUE)) + uiSlotFlag |= Item[itemId].nasAttachmentClass; } - - uiLoop++; } return uiSlotFlag; @@ -16028,3 +16014,52 @@ UINT16 GetLaunchableOfExplosionType(UINT16 launcher, UINT8 explosionType) } return NOTHING; } + +BOOLEAN FindAttachmentRange(UINT16 usAttachment, UINT32* pStartIndex, UINT32* pEndIndex) +{ + BOOLEAN result = FALSE; + + INT32 leftMargin = 0; + INT32 rightMargin = (INT32)gMAXATTACHMENTS_READ - 1; + INT32 middle = 0; + // use binary search to locate the group of elements for given attachment item Id (usAttachment) + while (leftMargin <= rightMargin) + { + middle = leftMargin + (rightMargin - leftMargin) / 2; + + if (Attachment[middle].attachmentIndex == usAttachment) + { + result = TRUE; + break; + } + else if (Attachment[middle].attachmentIndex < usAttachment) + leftMargin = middle + 1; + else + rightMargin = middle - 1; + } + + if (result) + { + // now middle is an index somewhere within the group, seek for beginning and ending of the group + if (pStartIndex) + { + *pStartIndex = (UINT32)middle; + for (INT32 i = middle - 1; i >= leftMargin; i--) + if (Attachment[i].attachmentIndex == usAttachment) + *pStartIndex = (UINT32)i; + else + break; + } + if (pEndIndex) + { + *pEndIndex = (UINT32)middle; + for (INT32 i = middle + 1; i <= rightMargin; i++) + if (Attachment[i].attachmentIndex == usAttachment) + *pEndIndex = (UINT32)i; + else + break; + } + } + + return result; +} diff --git a/Tactical/Items.h b/Tactical/Items.h index bc0c49c6..ce23180f 100644 --- a/Tactical/Items.h +++ b/Tactical/Items.h @@ -568,6 +568,7 @@ INT32 GetPercentRangeBonus( OBJECTTYPE * pObj ); UINT8 GetInventorySleepModifier( SOLDIERTYPE *pSoldier ); void AttachDefaultAttachments(OBJECTTYPE *pObj, BOOLEAN fAllDefaultAttachments=TRUE);//dnl ch75 261013 +BOOLEAN FindAttachmentRange(UINT16 usAttachment, UINT32* pStartIndex, UINT32* pEndIndex); // Flugente: is this object useable by militia? BOOLEAN ObjectIsMilitiaRelevant( OBJECTTYPE *pObj ); diff --git a/Tactical/XML_Attachments.cpp b/Tactical/XML_Attachments.cpp index 7eaa8936..fdb28fc9 100644 --- a/Tactical/XML_Attachments.cpp +++ b/Tactical/XML_Attachments.cpp @@ -20,6 +20,8 @@ struct } typedef attachmentParseData; +UINT32 gMAXATTACHMENTS_READ = 0; + static void XMLCALL attachmentStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts) { @@ -93,9 +95,9 @@ attachmentEndElementHandle(void *userData, const XML_Char *name) if(pData->curIndex < pData->maxArraySize) { //DebugMsg(TOPIC_JA2, DBG_LEVEL_3,"AttachmentStartElementHandle: writing attachment to array"); - Attachment[pData->curIndex][0] = pData->curAttachment[0]; //write the attachment into the table - Attachment[pData->curIndex][1] = pData->curAttachment[1]; - Attachment[pData->curIndex][2] = pData->curAttachment[2]; + Attachment[pData->curIndex].attachmentIndex = pData->curAttachment[0]; //write the attachment into the table + Attachment[pData->curIndex].itemIndex = pData->curAttachment[1]; + Attachment[pData->curIndex].APCost = pData->curAttachment[2]; } } else if(strcmp(name, "attachmentIndex") == 0) @@ -127,7 +129,25 @@ attachmentEndElementHandle(void *userData, const XML_Char *name) } +static void MapAttachments() +{ + std::list stdList; + std::list::iterator it; + UINT32 i = 0; + for (i = 0; i < gMAXATTACHMENTS_READ; i++) + { + stdList.push_back(Attachment[i]); + AttachmentBackmap.insert(std::make_pair(Attachment[i].itemIndex, Attachment[i])); + } + + stdList.sort(); + + for (it = stdList.begin(), i = 0; it != stdList.end(); it++, i++) + { + Attachment[i] = *it; + } +} BOOLEAN ReadInAttachmentStats(STR fileName) { @@ -171,7 +191,6 @@ BOOLEAN ReadInAttachmentStats(STR fileName) XML_SetUserData(parser, &pData); - if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) { CHAR8 errorBuf[511]; @@ -183,13 +202,15 @@ BOOLEAN ReadInAttachmentStats(STR fileName) return FALSE; } + gMAXATTACHMENTS_READ = pData.curIndex + 1; + MapAttachments(); + MemFree(lpcBuffer); - - XML_ParserFree(parser); return( TRUE ); } + BOOLEAN WriteAttachmentStats() { HWFILE hFile; @@ -208,10 +229,10 @@ BOOLEAN WriteAttachmentStats() { FilePrintf(hFile,"\t\r\n"); - FilePrintf(hFile,"\t\t%d\r\n", Attachment[cnt][0]); - FilePrintf(hFile,"\t\t%d\r\n", Attachment[cnt][1]); - FilePrintf(hFile,"\t\t%d\r\n", Attachment[cnt][2]); - FilePrintf(hFile,"\t\t%d\r\n", Attachment[cnt][3]); + FilePrintf(hFile,"\t\t%d\r\n", Attachment[cnt].attachmentIndex); + FilePrintf(hFile,"\t\t%d\r\n", Attachment[cnt].itemIndex); + FilePrintf(hFile,"\t\t%d\r\n", Attachment[cnt].APCost); + FilePrintf(hFile,"\t\t%d\r\n", Attachment[cnt].NASOnly); FilePrintf(hFile,"\t\r\n"); } diff --git a/Tactical/XML_Launchable.cpp b/Tactical/XML_Launchable.cpp index bec05f33..26994d2d 100644 --- a/Tactical/XML_Launchable.cpp +++ b/Tactical/XML_Launchable.cpp @@ -19,6 +19,8 @@ struct } typedef launchableParseData; +UINT32 gMAXLAUNCHABLES_READ = 0; + static void XMLCALL launchableStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts) { @@ -168,9 +170,9 @@ BOOLEAN ReadInLaunchableStats(STR fileName) return FALSE; } + gMAXLAUNCHABLES_READ = pData.curIndex + 1; + MemFree(lpcBuffer); - - XML_ParserFree(parser); return( TRUE );