From 962393d17043db2b8d540417104b230468a995e7 Mon Sep 17 00:00:00 2001 From: Flugente Date: Wed, 3 Jun 2015 21:04:06 +0000 Subject: [PATCH] - New Feature: Tactical recruitment allows recruiting civilians as volunteers. See http://thepit.ja-galaxy-forum.com/index.php?t=msg&goto=341335&#msg_341335 This is fully savegame compatible. GameDir <= r2250 is recommended. - Fix: prisoners do not talk. git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@7886 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- Strategic/Campaign Types.h | 6 +- Strategic/Strategic Event Handler.cpp | 17 ++++++ Tactical/Overhead Types.h | 2 +- Tactical/Overhead.cpp | 4 ++ Tactical/Soldier Control.cpp | 81 ++++++++++++++++++++++++++- Tactical/Soldier Control.h | 4 ++ Tactical/Soldier Create.cpp | 15 +++++ 7 files changed, 125 insertions(+), 4 deletions(-) diff --git a/Strategic/Campaign Types.h b/Strategic/Campaign Types.h index e9b9b7a7..24d58779 100644 --- a/Strategic/Campaign Types.h +++ b/Strategic/Campaign Types.h @@ -438,6 +438,10 @@ typedef enum PRISONER_MAX, } PrisonerType; +// -------- added by Flugente: sector info flags -------- +// easier than adding 32 differently named variables. DO NOT CHANGE THEM, UNLESS YOU KNOW WHAT YOU ARE DOING!!! +#define SECTORINFO_VOLUNTEERS_RECENTLY_RECRUITED 0x01 //1 // we recruited volunteers here. Until this flag is removed, newly created civilians wont be potential volunteers anymore + typedef struct SECTORINFO { //information pertaining to this sector @@ -495,7 +499,7 @@ typedef struct SECTORINFO // HEADROCK HAM 3.6: Flag to determine whether enemy units in this sector can be seen, and whether their numbers // should be displayed. One filler was replaced to make room. UINT8 ubDetectionLevel; - UINT8 bFiller2; + UINT8 usSectorInfoFlag; // a flagmask UINT8 bFiller3; UINT32 uiNumberOfWorldItemsInTempFileThatCanBeSeenByPlayer; diff --git a/Strategic/Strategic Event Handler.cpp b/Strategic/Strategic Event Handler.cpp index 35ef80d1..927f24d8 100644 --- a/Strategic/Strategic Event Handler.cpp +++ b/Strategic/Strategic Event Handler.cpp @@ -824,6 +824,23 @@ void HandleNPCSystemEvent( UINT32 uiEvent ) void HandleEarlyMorningEvents( void ) { + // Flugente: no reason to put this into LUA + for ( UINT8 sX = 1; sX < MAP_WORLD_X - 1; ++sX ) + { + for ( UINT8 sY = 1; sY < MAP_WORLD_X - 1; ++sY ) + { + UINT8 sector = SECTOR( sX, sY ); + + SECTORINFO *pSectorInfo = &(SectorInfo[sector]); + + // A flag prevents us from exploiting the civilian recruitment mechanic. Every x hours, we reset it + if ( pSectorInfo ) + { + pSectorInfo->usSectorInfoFlag &= ~SECTORINFO_VOLUNTEERS_RECENTLY_RECRUITED; + } + } + } + #ifdef LUA_STRATEGY_EVENT_HANDLER LetLuaHandleEarlyMorningEvents(0); #else diff --git a/Tactical/Overhead Types.h b/Tactical/Overhead Types.h index 53c2d883..8cd5e2d8 100644 --- a/Tactical/Overhead Types.h +++ b/Tactical/Overhead Types.h @@ -361,7 +361,7 @@ enum UNNAMED_CIV_GROUP_19, ASSASSIN_CIV_GROUP, // Flugente: enemy assassins belong to this group POW_PRISON_CIV_GROUP, // Flugente: prisoners of war the player caught are in this group - UNNAMED_CIV_GROUP_22, + VOLUNTEER_CIV_GROUP, // Flugente: civilians that the player recruited UNNAMED_CIV_GROUP_23, UNNAMED_CIV_GROUP_24, UNNAMED_CIV_GROUP_25, diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index 7a50e34d..57e87c38 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -3839,6 +3839,10 @@ void HandleNPCTeamMemberDeath( SOLDIERTYPE *pSoldierOld ) // Flugente: if this was a prisoner of war, reduce their sector count by 1 if ( pSoldierOld->usSoldierFlagMask & SOLDIER_POW_PRISON ) KillOnePrisoner( &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ] ); + + // If this was a volunteer, then we definetly reduce our volunteer pool + if ( pSoldierOld->bTeam == CIV_TEAM && pSoldierOld->ubCivilianGroup == VOLUNTEER_CIV_GROUP ) + AddVolunteers( -1 ); } else if ( pSoldierOld->bTeam == MILITIA_TEAM ) { diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index 682b845f..5e230775 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -118,6 +118,7 @@ #include "Tactical Save.h" // added by Flugente for AddItemsToUnLoadedSector() #include "LightEffects.h" // added by Flugente for CreatePersonalLight() #include "DynamicDialogue.h" // added by Flugente for HandleDynamicOpinions() +#include "strategic town loyalty.h" // added by Flugente for gTownLoyalty //forward declarations of common classes to eliminate includes class OBJECTTYPE; @@ -20437,14 +20438,16 @@ BOOLEAN SOLDIERTYPE::PlayerSoldierStartTalking( UINT8 ubTargetID, BOOLEAN fValid apsDeducted = TRUE; // Flugente: if we are talking to an enemy, we have the option to offer them surrender... - if ( (pTSoldier->bTeam == ENEMY_TEAM || (pTSoldier->bTeam == CIV_TEAM && zCivGroupName[pTSoldier->ubCivilianGroup].fCanBeCaptured) ) - && (gGameExternalOptions.fEnemyCanSurrender || gGameExternalOptions.fPlayerCanAsktoSurrender) ) + if ( (gGameExternalOptions.fEnemyCanSurrender || gGameExternalOptions.fPlayerCanAsktoSurrender) && pTSoldier->CanBeCaptured( ) ) { HandleSurrenderOffer( pTSoldier ); return(FALSE); } else { + // Flugente: if this guy is a potential volunteer, we might be able to sway him + HandleVolunteerRecruitment( this, pTSoldier ); + StartCivQuote( pTSoldier ); return(FALSE); } @@ -22806,3 +22809,77 @@ UINT32 VirtualSoldierDressWound( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pVictim, OB return uiMedcost; } ////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Flugente: decide whether pRecruiter can successfully recruit pTarget to be a volunteer +void HandleVolunteerRecruitment( SOLDIERTYPE* pRecruiter, SOLDIERTYPE* pTarget ) +{ + if ( !pRecruiter || pRecruiter->bTeam != OUR_TEAM ) + return; + + // potential recruit must be a civilain NPC of no other affilation, no kids + if ( !pTarget || pTarget->bTeam != CIV_TEAM || pTarget->ubProfile != NO_PROFILE || pTarget->ubCivilianGroup != NON_CIV_GROUP || pTarget->ubBodyType > DRESSCIV ) + return; + + // target must unharmed + if ( pTarget->bCollapsed || pTarget->bBreathCollapsed || pTarget->stats.bLife < pTarget->stats.bLifeMax ) + return; + + // target must be friendly + if ( !pTarget->aiData.bNeutral ) + return; + + // Set a flag in this sector. This flag is removed every x hours. As long as it exists, newly created civilians won't be potential volunteers + // this is simply there to prevent the exploit of reloading the sector repeatedly and 'harvesting' volunteers. + // we do this even if we do not recruit this guy - otherwise the player could reload the sector over and over again until he find a volunteer + UINT8 sector = SECTOR( pTarget->sSectorX, pTarget->sSectorY ); + + SECTORINFO *pSectorInfo = &(SectorInfo[sector]); + + if ( pSectorInfo ) + { + pSectorInfo->usSectorInfoFlag |= SECTORINFO_VOLUNTEERS_RECENTLY_RECRUITED; + } + + // can we recruit him in the first place? + if ( pTarget->usSoldierFlagMask2 & SOLDIER_POTENTIAL_VOLUNTEER ) + { + // if sector not under our control, has enemies in it, or is currently in combat mode + if ( !SectorOursAndPeaceful( pTarget->sSectorX, pTarget->sSectorY, pTarget->bSectorZ ) ) + return; + + // if this a town sector, min loyalty is required + // other sectors do not have a loyalty rating. This is okay here, as then the player has an incentive to try his luck outside of towns + UINT8 ubTownID = StrategicMap[CALCULATE_STRATEGIC_INDEX( pTarget->sSectorX, pTarget->sSectorY )].bNameId; + if ( ubTownID != BLANK_SECTOR ) + { + if ( gTownLoyalty[ubTownID].ubRating < gGameExternalOptions.iMinLoyaltyToTrain ) + return; + } + + // recruiter ability + FLOAT leadershipfactor = EffectiveLeadership( pRecruiter ) / 100.0; + + // bonus for assertive characters + if ( gMercProfiles[pRecruiter->ubProfile].bCharacterTrait == CHAR_TRAIT_ASSERTIVE ) + leadershipfactor *= 1.05; + + FLOAT recruitmodifier = (100 + pRecruiter->GetBackgroundValue( BG_PERC_APPROACH_RECRUIT )) / 100.0; + + FLOAT rating = leadershipfactor * recruitmodifier * gMercProfiles[pRecruiter->ubProfile].usApproachFactor[3]; + + // hard check. We do not use Chance()-base functions, as then the player would have to repeat this over and over again to be sure that someone is not a volunteer + if ( rating > 70.0 ) + { + // success! remove the flag, put this guy in a new group, and add one volunteer + pTarget->usSoldierFlagMask2 &= ~SOLDIER_POTENTIAL_VOLUNTEER; + + pTarget->ubCivilianGroup = VOLUNTEER_CIV_GROUP; + + AddVolunteers( 1 ); + + // the recruiter gets a bit of experience + StatChange( pRecruiter, LDRAMT, 8, TRUE ); + StatChange( pRecruiter, EXPERAMT, 5, TRUE ); + } + } +} diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index 754b8b0b..3cc267cf 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -408,6 +408,7 @@ enum #define SOLDIER_INTERROGATE_GENERAL 0x00000080 //128 // interrogate generals #define SOLDIER_INTERROGATE_CIVILIAN 0x00000100 //256 // interrogate civilian +#define SOLDIER_POTENTIAL_VOLUNTEER 0x00000200 //512 // this civilian _might_ join us as a volunteer if conditions are right #define SOLDIER_INTERROGATE_ALL 0x000001F8 // all interrogation flags // ---------------------------------------------------------------- @@ -2724,6 +2725,9 @@ void SetDamageDisplayCounter(SOLDIERTYPE* pSoldier); // SANDRO - This whole procedure was merged with the surgery ability of the doctor trait UINT32 VirtualSoldierDressWound( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pVictim, OBJECTTYPE *pKit, INT16 sKitPts, INT16 sStatus, BOOLEAN fOnSurgery ); +// Flugente: decide whether pRecruiter can successfully recruit pTarget to be a volunteer +void HandleVolunteerRecruitment( SOLDIERTYPE* pRecruiter, SOLDIERTYPE* pTarget ); + #endif diff --git a/Tactical/Soldier Create.cpp b/Tactical/Soldier Create.cpp index 14ac82ba..3031eab7 100644 --- a/Tactical/Soldier Create.cpp +++ b/Tactical/Soldier Create.cpp @@ -1137,7 +1137,22 @@ SOLDIERTYPE* TacticalCreateSoldier( SOLDIERCREATE_STRUCT *pCreateStruct, UINT8 * default: Soldier.aiData.bNormalSmell = NORMAL_HUMAN_SMELL_STRENGTH; break; + } + // Flugente: under certain conditions, we can recruit civilians + if ( Soldier.bTeam == CIV_TEAM && Soldier.ubProfile == NO_PROFILE && Soldier.ubCivilianGroup == NON_CIV_GROUP && Soldier.ubBodyType <= DRESSCIV ) + { + // there has to officially be an outbreak in this sector - if we don't know of a disease, we cannot treat it! + UINT8 sector = SECTOR( Soldier.sSectorX, Soldier.sSectorY ); + + SECTORINFO *pSectorInfo = &(SectorInfo[sector]); + + // only allow us to be a volunteer if this isn't blocked + if ( pSectorInfo && !(pSectorInfo->usSectorInfoFlag & SECTORINFO_VOLUNTEERS_RECENTLY_RECRUITED) ) + { + if ( Chance(10) ) + Soldier.usSoldierFlagMask2 |= SOLDIER_POTENTIAL_VOLUNTEER; + } } if( guiCurrentScreen != AUTORESOLVE_SCREEN )