mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
- If possible, use the intended macrso to determine sector coordinates git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8018 3b4a5df2-a311-0410-b5c6-a8a6f20db521
1705 lines
59 KiB
C++
1705 lines
59 KiB
C++
#ifdef PRECOMPILEDHEADERS
|
|
#include "Tactical All.h"
|
|
#else
|
|
#include <stdlib.h>
|
|
#include "Morale.h"
|
|
#include "Overhead.h"
|
|
#include "Soldier Profile.h"
|
|
#include "dialogue control.h"
|
|
#include "Map Screen Interface.h"
|
|
#include "message.h"
|
|
#include "assignments.h"
|
|
#include "Strategic Movement.h"
|
|
#include "Strategic Status.h"
|
|
#include "SkillCheck.h"
|
|
#include "drugs and alcohol.h"
|
|
#include "StrategicMap.h"
|
|
#include "Debug.h"
|
|
#include "Squads.h"
|
|
#include "ai.h"
|
|
#include "Campaign.h"
|
|
#include "mapscreen.h"
|
|
#include "Soldier macros.h"
|
|
#include "Event Pump.h"
|
|
// HEADROCK HAM 3.5: Added for facility effect on morale
|
|
#include "Facilities.h"
|
|
// addedd by SANDRO
|
|
#include "GameSettings.h"
|
|
#include "Isometric Utils.h"
|
|
#include "Food.h"
|
|
#include "Interface.h" // added by Flugente
|
|
#include "finances.h" // added by Flugente for EXTENDED_CONTRACT_BY_1_DAY
|
|
#include "Soldier Add.h" // added by Flugente for MERC_TYPE__AIM_MERC
|
|
#include "CampaignStats.h" // added by Flugente for gCurrentIncident
|
|
#include "DynamicDialogue.h" // added by Flugente
|
|
#endif
|
|
|
|
#include "connect.h"
|
|
#include "fresh_header.h"
|
|
|
|
#include "Random.h"
|
|
|
|
#include "Merc Contract.h"
|
|
|
|
//#define MORALE_MOD_MAX 50 // morale *mod* range is -50 to 50, if you change this, check the decay formulas!
|
|
|
|
//#define DRUG_EFFECT_MORALE_MOD 150
|
|
//#define ALCOHOL_EFFECT_MORALE_MOD 160
|
|
|
|
//#define HOURS_BETWEEN_STRATEGIC_DECAY 3
|
|
|
|
//#define PHOBIC_LIMIT -20
|
|
|
|
// macros
|
|
#define SOLDIER_IN_SECTOR( pSoldier, sX, sY, bZ ) ( !pSoldier->flags.fBetweenSectors && ( pSoldier->sSectorX == sX ) && ( pSoldier->sSectorY == sY ) && ( pSoldier->bSectorZ == bZ ) )
|
|
|
|
|
|
|
|
MoraleEvent gbMoraleEvent[NUM_MORALE_EVENTS] =
|
|
{
|
|
// TACTICAL = Short Term Effect, STRATEGIC = Long Term Effect
|
|
{ TACTICAL_MORALE_EVENT, +4}, // MORALE_KILLED_ENEMY
|
|
{ TACTICAL_MORALE_EVENT, -5}, // MORALE_SQUADMATE_DIED, // in same sector (not really squad)... IN ADDITION to strategic loss of morale
|
|
{ TACTICAL_MORALE_EVENT, -1}, // MORALE_SUPPRESSED, // up to 4 times per turn
|
|
{ TACTICAL_MORALE_EVENT, -2}, // MORALE_AIRSTRIKE,
|
|
{ TACTICAL_MORALE_EVENT, +2}, // MORALE_DID_LOTS_OF_DAMAGE,
|
|
{ TACTICAL_MORALE_EVENT, -3}, // MORALE_TOOK_LOTS_OF_DAMAGE,
|
|
{ STRATEGIC_MORALE_EVENT, -5}, // MORALE_KILLED_CIVILIAN,
|
|
{ STRATEGIC_MORALE_EVENT, +4}, // MORALE_BATTLE_WON,
|
|
{ STRATEGIC_MORALE_EVENT, -5}, // MORALE_RAN_AWAY,
|
|
{ STRATEGIC_MORALE_EVENT, +2}, // MORALE_HEARD_BATTLE_WON,
|
|
{ STRATEGIC_MORALE_EVENT, -2}, // MORALE_HEARD_BATTLE_LOST,
|
|
{ STRATEGIC_MORALE_EVENT, +5}, // MORALE_TOWN_LIBERATED,
|
|
{ STRATEGIC_MORALE_EVENT, -5}, // MORALE_TOWN_LOST,
|
|
{ STRATEGIC_MORALE_EVENT, +8}, // MORALE_MINE_LIBERATED,
|
|
{ STRATEGIC_MORALE_EVENT, -8}, // MORALE_MINE_LOST,
|
|
{ STRATEGIC_MORALE_EVENT, +3}, // MORALE_SAM_SITE_LIBERATED,
|
|
{ STRATEGIC_MORALE_EVENT, -3}, // MORALE_SAM_SITE_LOST,
|
|
{ STRATEGIC_MORALE_EVENT, -15}, // MORALE_BUDDY_DIED,
|
|
{ STRATEGIC_MORALE_EVENT, +5}, // MORALE_HATED_DIED,
|
|
{ STRATEGIC_MORALE_EVENT, -5}, // MORALE_TEAMMATE_DIED, // not in same sector
|
|
{ STRATEGIC_MORALE_EVENT, +5}, // MORALE_LOW_DEATHRATE,
|
|
{ STRATEGIC_MORALE_EVENT, -5}, // MORALE_HIGH_DEATHRATE,
|
|
{ STRATEGIC_MORALE_EVENT, +2}, // MORALE_GREAT_MORALE,
|
|
{ STRATEGIC_MORALE_EVENT, -2}, // MORALE_POOR_MORALE,
|
|
{ TACTICAL_MORALE_EVENT, -5}, // MORALE_DRUGS_CRASH // Flugente: lowered as drugs can have several components that have a backlash, causing the the morale to plummet drastical
|
|
{ TACTICAL_MORALE_EVENT, -10}, // MORALE_ALCOHOL_CRASH
|
|
{ STRATEGIC_MORALE_EVENT, +15}, // MORALE_MONSTER_QUEEN_KILLED
|
|
{ STRATEGIC_MORALE_EVENT, +25}, // MORALE_DEIDRANNA_KILLED
|
|
{ TACTICAL_MORALE_EVENT, -1}, // MORALE_CLAUSTROPHOBE_UNDERGROUND,
|
|
{ TACTICAL_MORALE_EVENT, -5}, // MORALE_INSECT_PHOBIC_SEES_CREATURE,
|
|
{ TACTICAL_MORALE_EVENT, -1}, // MORALE_NERVOUS_ALONE,
|
|
{ STRATEGIC_MORALE_EVENT, -5}, // MORALE_MERC_CAPTURED,
|
|
{ STRATEGIC_MORALE_EVENT, -5}, // MORALE_MERC_MARRIED,
|
|
{ STRATEGIC_MORALE_EVENT, +8}, // MORALE_QUEEN_BATTLE_WON,
|
|
{ STRATEGIC_MORALE_EVENT, +5}, // MORALE_SEX,
|
|
// added by SANDRO
|
|
{ STRATEGIC_MORALE_EVENT, -1}, // MORALE_HEAT_INTOLERANT_IN_DESERT,
|
|
{ TACTICAL_MORALE_EVENT, -1}, // MORALE_PSYCHO_UNABLE_TO_PSYCHO,
|
|
{ STRATEGIC_MORALE_EVENT, +1}, // MORALE_PACIFIST_GAIN_NONCOMBAT,
|
|
{ TACTICAL_MORALE_EVENT, +1}, // MORALE_MALICIOUS_HIT,
|
|
// added by Flugente
|
|
{ TACTICAL_MORALE_EVENT, 1}, //MORALE_FOOD,
|
|
{ TACTICAL_MORALE_EVENT, 5}, //MORALE_GOOD_FOOD,
|
|
{ TACTICAL_MORALE_EVENT, -1}, //MORALE_BAD_FOOD,
|
|
{ TACTICAL_MORALE_EVENT, -5}, //MORALE_LOATHSOME_FOOD,
|
|
// added by anv
|
|
{ STRATEGIC_MORALE_EVENT, -5}, //MORALE_BUDDY_FIRED,
|
|
{ STRATEGIC_MORALE_EVENT, -8}, //MORALE_BUDDY_FIRED_ON_BAD_TERMS,
|
|
{ STRATEGIC_MORALE_EVENT, -8}, //MORALE_BUDDY_FIRED_EARLY
|
|
{ STRATEGIC_MORALE_EVENT, -2}, //MORALE_BAD_EQUIPMENT,
|
|
{ STRATEGIC_MORALE_EVENT, -3}, //MORALE_OWED_MONEY,
|
|
{ STRATEGIC_MORALE_EVENT, -3}, //MORALE_PLAYER_INACTIVE,
|
|
{ STRATEGIC_MORALE_EVENT, -1}, //MORALE_PREVENTED_MISBEHAVIOUR,
|
|
};
|
|
|
|
// Flugente: dynamic opinions
|
|
DynamicOpinionEvent gDynamicOpinionEvent[OPINIONEVENT_MAX] =
|
|
{
|
|
// ubEventId sOpinionModifier fSameSector fSameAssignment fAwake fSquadsOnly
|
|
{ OPINIONEVENT_FRIENDLYFIRE, -10, TRUE, FALSE, FALSE, FALSE },
|
|
{ OPINIONEVENT_SNITCHSOLDMEOUT, -3, FALSE, FALSE, FALSE, FALSE },
|
|
{ OPINIONEVENT_SNITCHINTERFERENCE, -5, FALSE, FALSE, FALSE, FALSE },
|
|
{ OPINIONEVENT_FRIENDSWITHHATED, -4, FALSE, FALSE, FALSE, FALSE },
|
|
{ OPINIONEVENT_CONTRACTEXTENSION, -2, FALSE, FALSE, FALSE, FALSE },
|
|
{ OPINIONEVENT_ORDEREDRETREAT, -6, TRUE, FALSE, TRUE, TRUE },
|
|
{ OPINIONEVENT_CIVKILLER, -8, TRUE, FALSE, FALSE, FALSE },
|
|
{ OPINIONEVENT_SLOWSUSDOWN, -2, FALSE, TRUE, FALSE, TRUE },
|
|
{ OPINIONEVENT_NOSHARINGFOOD, -1, TRUE, FALSE, TRUE, FALSE },
|
|
{ OPINIONEVENT_ANNOYINGDISABILITY, -2, TRUE, FALSE, TRUE, FALSE },
|
|
{ OPINIONEVENT_ADDICT, -6, TRUE, FALSE, TRUE, FALSE },
|
|
{ OPINIONEVENT_THIEF, -5, TRUE, FALSE, TRUE, FALSE },
|
|
{ OPINIONEVENT_WORSTCOMMANDEREVER, -9, TRUE, FALSE, FALSE, TRUE },
|
|
{ OPINIONEVENT_RICHGUY, -1, FALSE, FALSE, FALSE, FALSE },
|
|
{ OPINIONEVENT_BETTERGEAR, -2, TRUE, FALSE, FALSE, TRUE },
|
|
{ OPINIONEVENT_YOUMOUNTEDAGUNONMYBREASTS, -3, TRUE, FALSE, FALSE, TRUE },
|
|
{ OPINIONEVENT_BANDAGED, 1, TRUE, FALSE, FALSE, FALSE },
|
|
{ OPINIONEVENT_DRINKBUDDIES_GOOD, 1, TRUE, FALSE, TRUE, FALSE },
|
|
{ OPINIONEVENT_DRINKBUDDIES_SUPER, 4, TRUE, FALSE, TRUE, FALSE },
|
|
{ OPINIONEVENT_DRINKBUDDIES_BAD, -1, TRUE, FALSE, TRUE, FALSE },
|
|
{ OPINIONEVENT_DRINKBUDDIES_WORSE, -4, TRUE, FALSE, TRUE, FALSE },
|
|
{OPINIONEVENT_AGAINST_US, -2, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_FOR_US, 2, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_AGAINST_ENEMY, 1, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_FOR_ENEMY, -1, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_SOLVECONFLICT_REASON_GOOD, 2, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_SOLVECONFLICT_REASON_BAD, -2, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_SOLVECONFLICT_AGGRESSIVE_GOOD, 3, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_SOLVECONFLICT_AGGRESSIVE_BAD, -3, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_DISEASE_DISGUSTING, -3, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_DISEASE_TREATMENT, 1, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_BRUTAL_GOOD, 1, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_BRUTAL_BAD, -2, TRUE, FALSE, TRUE, FALSE},
|
|
{OPINIONEVENT_TEACHER, 1, TRUE, FALSE, FALSE, FALSE},
|
|
{OPINIONEVENT_BESTCOMMANDEREVER, 8, TRUE, FALSE, FALSE, TRUE },
|
|
{OPINIONEVENT_BATTLE_SAVIOUR, 6, TRUE, FALSE, FALSE, TRUE},
|
|
{OPINIONEVENT_FRAGTHIEF, -2, TRUE, FALSE, FALSE, TRUE},
|
|
{OPINIONEVENT_BATTLE_ASSIST, -1, TRUE, FALSE, FALSE, TRUE},
|
|
{OPINIONEVENT_BATTLE_TOOK_PRISONER, 3, TRUE, FALSE, FALSE, TRUE},
|
|
{OPINIONEVENT_CIV_ATTACKER, -3, TRUE, FALSE, FALSE, FALSE}
|
|
};
|
|
|
|
BOOLEAN gfSomeoneSaidMoraleQuote = FALSE;
|
|
|
|
BOOLEAN IsShowOffNearBy( SOLDIERTYPE * pSoldier ); // Added by SANDRO
|
|
|
|
INT8 GetMoraleModifier( SOLDIERTYPE * pSoldier )
|
|
{
|
|
INT8 morale = 0;
|
|
|
|
if (pSoldier->flags.uiStatusFlags & SOLDIER_PC)
|
|
{
|
|
if (pSoldier->aiData.bMorale > 50)
|
|
{
|
|
// give +1 at 55, +3 at 65, up to +5 at 95 and above
|
|
morale = (pSoldier->aiData.bMorale - 45) / 10;
|
|
}
|
|
else
|
|
{
|
|
// give penalties down to -20 at 0 (-2 at 45, -4 by 40...)
|
|
morale = (pSoldier->aiData.bMorale - 50) * 2 / 5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// use AI morale
|
|
switch( pSoldier->aiData.bAIMorale )
|
|
{
|
|
case MORALE_HOPELESS:
|
|
morale = -15;
|
|
case MORALE_WORRIED:
|
|
morale = -7;
|
|
case MORALE_CONFIDENT:
|
|
morale = -2;
|
|
case MORALE_FEARLESS:
|
|
morale = -5;
|
|
default:
|
|
morale = 0;
|
|
}
|
|
}
|
|
|
|
// Flugente: morale modifiers
|
|
morale = max( morale, morale * pSoldier->GetMoraleModifier( ) );
|
|
|
|
return morale;
|
|
}
|
|
|
|
void DecayTacticalMorale( SOLDIERTYPE * pSoldier )
|
|
{
|
|
// decay the tactical morale modifier
|
|
if (pSoldier->aiData.bTacticalMoraleMod != 0)
|
|
{
|
|
// decay the modifier!
|
|
if (pSoldier->aiData.bTacticalMoraleMod > 0)
|
|
{
|
|
pSoldier->aiData.bTacticalMoraleMod = __max( 0, pSoldier->aiData.bTacticalMoraleMod - (8 - pSoldier->aiData.bTacticalMoraleMod / 10) );
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.bTacticalMoraleMod = __min( 0, pSoldier->aiData.bTacticalMoraleMod + (6 + pSoldier->aiData.bTacticalMoraleMod / 10) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void DecayStrategicMorale( SOLDIERTYPE * pSoldier )
|
|
{
|
|
// HEADROCK HAM 3.5: Strategic Morale Mod no longer normalizes to 0 by default. In fact, a local facility can
|
|
// cause normalization to another value (positive or negative!), based on the activity that the character is
|
|
// currently performing. It can literally reduce morale to ridiculously low amounts if you don't watch your
|
|
// assignments in some sectors.
|
|
|
|
// decay the modifier!
|
|
if (pSoldier->aiData.bStrategicMoraleMod > 0)
|
|
{
|
|
pSoldier->aiData.bStrategicMoraleMod = __max( 0, pSoldier->aiData.bStrategicMoraleMod - (8 - (pSoldier->aiData.bStrategicMoraleMod / 10)) );
|
|
}
|
|
else
|
|
{
|
|
pSoldier->aiData.bStrategicMoraleMod = __min( 0, pSoldier->aiData.bStrategicMoraleMod + (6 + (pSoldier->aiData.bStrategicMoraleMod / 10)) );
|
|
}
|
|
}
|
|
|
|
void DecayTacticalMoraleModifiers( void )
|
|
{
|
|
SOLDIERTYPE * pSoldier;
|
|
UINT8 ubLoop, ubLoop2;
|
|
BOOLEAN fHandleNervous;
|
|
|
|
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pSoldier++ )
|
|
{
|
|
//if the merc is active, in Arulco
|
|
// CJC: decay modifiers while asleep! or POW!
|
|
if ( pSoldier->bActive && pSoldier->ubProfile != NO_PROFILE &&
|
|
!(pSoldier->bAssignment == IN_TRANSIT ||
|
|
pSoldier->bAssignment == ASSIGNMENT_DEAD ) )
|
|
{
|
|
// only let morale mod decay if it is positive while merc is a POW
|
|
if ( pSoldier->bAssignment == ASSIGNMENT_POW && pSoldier->aiData.bTacticalMoraleMod < 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( DoesMercHaveDisability( pSoldier, CLAUSTROPHOBIC ) )
|
|
{
|
|
if ( pSoldier->bSectorZ > 0 )
|
|
{
|
|
// underground, no recovery... in fact, if tact morale is high, decay
|
|
if ( pSoldier->aiData.bTacticalMoraleMod > gMoraleSettings.bModifiers[PHOBIC_LIMIT] )
|
|
{
|
|
HandleMoraleEvent( pSoldier, MORALE_CLAUSTROPHOBE_UNDERGROUND, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( DoesMercHaveDisability( pSoldier, NERVOUS ) )
|
|
{
|
|
if ( pSoldier->aiData.bMorale < 50 )
|
|
{
|
|
if (pSoldier->ubGroupID != 0 && PlayerIDGroupInMotion( pSoldier->ubGroupID ))
|
|
{
|
|
if ( NumberOfPeopleInSquad( pSoldier->bAssignment ) == 1 )
|
|
{
|
|
fHandleNervous = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fHandleNervous = FALSE;
|
|
}
|
|
}
|
|
else if ( pSoldier->bActive && pSoldier->bInSector )
|
|
{
|
|
if ( DistanceToClosestFriend( pSoldier ) > NERVOUS_RADIUS )
|
|
{
|
|
fHandleNervous = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fHandleNervous = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// look for anyone else in same sector
|
|
fHandleNervous = TRUE;
|
|
for ( ubLoop2 = gTacticalStatus.Team[ gbPlayerNum ].bFirstID; ubLoop2 <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop2++ )
|
|
{
|
|
if ( MercPtrs[ ubLoop2 ] != pSoldier && MercPtrs[ ubLoop2 ]->bActive && MercPtrs[ ubLoop2 ]->sSectorX == pSoldier->sSectorX && MercPtrs[ ubLoop2 ]->sSectorY == pSoldier->sSectorY && MercPtrs[ ubLoop2 ]->bSectorZ == pSoldier->bSectorZ )
|
|
{
|
|
// found someone!
|
|
fHandleNervous = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( fHandleNervous )
|
|
{
|
|
if ( pSoldier->aiData.bTacticalMoraleMod == gMoraleSettings.bModifiers[PHOBIC_LIMIT] )
|
|
{
|
|
// don't change morale
|
|
//continue;
|
|
}
|
|
else
|
|
{
|
|
// alone, no recovery... in fact, if tact morale is high, decay
|
|
if ( !(pSoldier->usQuoteSaidFlags & SOLDIER_QUOTE_SAID_PERSONALITY) )
|
|
{
|
|
TacticalCharacterDialogue( pSoldier, QUOTE_PERSONALITY_TRAIT );
|
|
pSoldier->usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_PERSONALITY;
|
|
}
|
|
HandleMoraleEvent( pSoldier, MORALE_NERVOUS_ALONE, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
|
|
|
|
// Flugente: dynamic opinions
|
|
HandleDynamicOpinionChange( pSoldier, OPINIONEVENT_ANNOYINGDISABILITY, TRUE, TRUE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DecayTacticalMorale( pSoldier );
|
|
RefreshSoldierMorale( pSoldier );
|
|
}
|
|
}
|
|
}
|
|
|
|
void DecayStrategicMoraleModifiers( void )
|
|
{
|
|
SOLDIERTYPE * pSoldier;
|
|
UINT8 ubLoop;
|
|
|
|
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pSoldier++ )
|
|
{
|
|
//if the merc is active, in Arulco
|
|
// CJC: decay modifiers while asleep! or POW!
|
|
if ( pSoldier->bActive && pSoldier->ubProfile != NO_PROFILE &&
|
|
!(pSoldier->bAssignment == IN_TRANSIT ||
|
|
pSoldier->bAssignment == ASSIGNMENT_DEAD ) )
|
|
{
|
|
// only let morale mod decay if it is positive while merc is a POW
|
|
if ( pSoldier->bAssignment == ASSIGNMENT_POW && pSoldier->aiData.bStrategicMoraleMod < 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
DecayStrategicMorale( pSoldier );
|
|
RefreshSoldierMorale( pSoldier );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void RefreshSoldierMorale( SOLDIERTYPE * pSoldier )
|
|
{
|
|
INT32 iActualMorale;
|
|
|
|
if ( pSoldier->flags.fMercAsleep )
|
|
{
|
|
// delay this till later!
|
|
return;
|
|
}
|
|
|
|
// CJC, April 19, 1999: added up to 20% morale boost according to progress
|
|
//iActualMorale = DEFAULT_MORALE + (INT32) pSoldier->aiData.bTeamMoraleMod + (INT32) pSoldier->aiData.bTacticalMoraleMod + (INT32) pSoldier->aiData.bStrategicMoraleMod + (INT32) (CurrentPlayerProgressPercentage() / 5);
|
|
iActualMorale = gMoraleSettings.ubDefaultMorale + (INT32) pSoldier->aiData.bTeamMoraleMod + (INT32) pSoldier->aiData.bTacticalMoraleMod + (INT32) pSoldier->aiData.bStrategicMoraleMod + (INT32) (CurrentPlayerProgressPercentage() / 5);
|
|
|
|
// Flugente: drug system has been redone
|
|
iActualMorale += pSoldier->newdrugs.size[DRUG_EFFECT_MORALE];
|
|
|
|
iActualMorale = __min( 100, iActualMorale );
|
|
iActualMorale = __max( 0, iActualMorale );
|
|
|
|
UINT8 ubMaxMorale = 100;
|
|
// HEADROCK HAM 3.5: Local facilities may decrease the total morale allowed.
|
|
for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++)
|
|
{
|
|
if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][cnt].fFacilityHere)
|
|
{
|
|
if (cnt == (UINT16)pSoldier->sFacilityTypeOperated && // Soldier is operating this facility
|
|
GetSoldierFacilityAssignmentIndex( pSoldier ) != -1)
|
|
{
|
|
UINT8 ubFacilityType = (UINT8)cnt;
|
|
UINT8 ubAssignmentType = (UINT8)GetSoldierFacilityAssignmentIndex( pSoldier );
|
|
// Check this facility both for an assignment-specific AND ambient value. Use it if it's the lowest
|
|
// encountered yet.
|
|
ubMaxMorale = __min(ubMaxMorale, (UINT8)GetFacilityModifier(FACILITY_MAX_MORALE, ubFacilityType, ubAssignmentType ));
|
|
}
|
|
else // Soldier is not operating this facility
|
|
{
|
|
// Check this facility for an AMBIENT Maximum Morale limit. Use it if it's the lowest encountered yet.
|
|
ubMaxMorale = __min(ubMaxMorale, (UINT8)GetFacilityModifier(FACILITY_MAX_MORALE, (UINT8)cnt, FAC_AMBIENT));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flugente: morale modifiers
|
|
iActualMorale = iActualMorale * pSoldier->GetMoraleModifier();
|
|
|
|
// Flugente: ubMaxMorale can now be influenced by our food situation
|
|
if ( gGameOptions.fFoodSystem )
|
|
FoodMaxMoraleModifiy(pSoldier, &ubMaxMorale);
|
|
|
|
// Flugente: max morale can be lowered
|
|
iActualMorale = min(iActualMorale, pSoldier->GetMoraleThreshold() );
|
|
|
|
if (ubMaxMorale > 0 && iActualMorale > ubMaxMorale)
|
|
{
|
|
// Normalize to Max Morale
|
|
iActualMorale = (iActualMorale + ubMaxMorale) / 2;
|
|
}
|
|
|
|
pSoldier->aiData.bMorale = (INT8) iActualMorale;
|
|
|
|
// update mapscreen as needed
|
|
fCharacterInfoPanelDirty = TRUE;
|
|
}
|
|
|
|
// SANDRO - changed this a bit
|
|
void UpdateSoldierMorale( SOLDIERTYPE * pSoldier, INT8 bMoraleEvent )
|
|
{
|
|
MERCPROFILESTRUCT * pProfile;
|
|
INT32 iMoraleModTotal;
|
|
UINT8 ubType;
|
|
INT8 bMoraleMod = 0;
|
|
|
|
if ( !pSoldier->bActive || ( pSoldier->stats.bLife < CONSCIOUSNESS ) ||
|
|
( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) || AM_A_ROBOT( pSoldier ) || AM_AN_EPC( pSoldier ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( ( pSoldier->bAssignment == ASSIGNMENT_DEAD ) ||
|
|
( pSoldier->bAssignment == ASSIGNMENT_POW ) ||
|
|
( pSoldier->bAssignment == IN_TRANSIT ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if ( pSoldier->ubProfile == NO_PROFILE )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(cDisableMorale && is_networked)
|
|
{
|
|
return;
|
|
}//hayden
|
|
|
|
ubType = gbMoraleEvent[bMoraleEvent].ubType;
|
|
bMoraleMod = gMoraleSettings.bValues[bMoraleEvent];//gbMoraleEvent[bMoraleEvent].bChange;
|
|
|
|
pProfile = &(gMercProfiles[ pSoldier->ubProfile ]);
|
|
|
|
if (bMoraleMod > 0)
|
|
{
|
|
if ( gGameOptions.fNewTraitSystem )
|
|
{
|
|
// SANDRO - STOMP traits
|
|
// Squadleader's bonus to morale
|
|
if ( IS_MERC_BODY_TYPE( pSoldier ) && (pSoldier->bTeam == ENEMY_TEAM || pSoldier->bTeam == MILITIA_TEAM || pSoldier->bTeam == gbPlayerNum) )
|
|
{
|
|
bMoraleMod += (gSkillTraitValues.ubSLMoraleGainBonus * GetSquadleadersCountInVicinity( pSoldier, FALSE, FALSE ));
|
|
}
|
|
// Check for character traits
|
|
INT8 bNumMercs = 0;
|
|
switch( pProfile->bCharacterTrait )
|
|
{
|
|
case CHAR_TRAIT_SOCIABLE:
|
|
bNumMercs = CheckMercsNearForCharTraits( pSoldier->ubProfile, CHAR_TRAIT_SOCIABLE );
|
|
if ( bNumMercs == 0 )
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_SOCIABLE_NO_MERCS_NEARBY];// -= 5;
|
|
else if ( bNumMercs == 1 )
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_SOCIABLE_ONE_MERC_NEARBY];// -= 2;
|
|
break;
|
|
case CHAR_TRAIT_LONER:
|
|
bNumMercs = CheckMercsNearForCharTraits( pSoldier->ubProfile, CHAR_TRAIT_LONER );
|
|
if ( bNumMercs > 1 )
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_LONER_MORE_MERCS_NEARBY];// -= 5;
|
|
else if ( bNumMercs == 1 )
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_LONER_ONE_MERC_NEARBY];// -= 2;
|
|
break;
|
|
case CHAR_TRAIT_OPTIMIST:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_OPTIMIST];//1;
|
|
break;
|
|
case CHAR_TRAIT_AGGRESSIVE:
|
|
switch ( bMoraleEvent )
|
|
{
|
|
case MORALE_KILLED_ENEMY:
|
|
case MORALE_DID_LOTS_OF_DAMAGE:
|
|
case MORALE_MONSTER_QUEEN_KILLED:
|
|
case MORALE_DEIDRANNA_KILLED:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_AGRESSIVE_VIOLENT_ACTION];//1;
|
|
break;
|
|
}
|
|
break;
|
|
case CHAR_TRAIT_PACIFIST:
|
|
switch ( bMoraleEvent )
|
|
{
|
|
case MORALE_KILLED_ENEMY:
|
|
case MORALE_DID_LOTS_OF_DAMAGE:
|
|
case MORALE_BATTLE_WON:
|
|
case MORALE_HEARD_BATTLE_WON:
|
|
case MORALE_HATED_DIED:
|
|
case MORALE_MONSTER_QUEEN_KILLED:
|
|
case MORALE_DEIDRANNA_KILLED:
|
|
case MORALE_QUEEN_BATTLE_WON:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_PACIFIST_VIOLENT_ACTION];//-= 5;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if ( IsShowOffNearBy( pSoldier ) )
|
|
{
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_SHOWOFF_AROUND];//-= 2;
|
|
}
|
|
|
|
}
|
|
else if ( bMoraleMod != MORALE_PSYCHO_UNABLE_TO_PSYCHO && bMoraleMod != MORALE_MALICIOUS_HIT)
|
|
{
|
|
switch( pProfile->bAttitude )
|
|
{
|
|
case ATT_OPTIMIST:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_OT_OPTIMIST_GOOD_EVENT];//1;
|
|
break;
|
|
case ATT_AGGRESSIVE:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_OT_AGGRESSIVE_GOOD_EVENT];//1;
|
|
break;
|
|
case ATT_PESSIMIST:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_OT_PESSIMIST_GOOD_EVENT];//-= 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bMoraleMod < 0)
|
|
{
|
|
// can't change a positive event into a negative one!
|
|
bMoraleMod = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( gGameOptions.fNewTraitSystem )
|
|
{
|
|
// SANDRO - STOMP traits - squadleader's bonus to morale
|
|
if ( IS_MERC_BODY_TYPE( pSoldier ) && (pSoldier->bTeam == ENEMY_TEAM || pSoldier->bTeam == MILITIA_TEAM || pSoldier->bTeam == gbPlayerNum) )
|
|
{
|
|
switch ( bMoraleEvent ) // certain thing are not supported by our squadleader
|
|
{
|
|
case MORALE_HATED_DIED:
|
|
case MORALE_MERC_MARRIED:
|
|
case MORALE_SEX:
|
|
case MORALE_PSYCHO_UNABLE_TO_PSYCHO:
|
|
case MORALE_MALICIOUS_HIT:
|
|
break;
|
|
default:
|
|
bMoraleMod += (gSkillTraitValues.ubSLMoraleLossReduction * GetSquadleadersCountInVicinity( pSoldier, FALSE, FALSE ));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check for character traits
|
|
if ( DoesMercHavePersonality( pSoldier, CHAR_TRAIT_OPTIMIST ) )
|
|
{
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_OPTIMIST];//1;
|
|
}
|
|
// Fearless character does not suffer morale loss for these so much
|
|
else if ( DoesMercHavePersonality( pSoldier, CHAR_TRAIT_DAUNTLESS ) )
|
|
{
|
|
switch ( bMoraleEvent )
|
|
{
|
|
case MORALE_SQUADMATE_DIED:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_DAUNTLESS_SQUADMATE_DIED];//3;
|
|
break;
|
|
case MORALE_SUPPRESSED:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_DAUNTLESS_SUPPRESSED];//3;
|
|
break;
|
|
case MORALE_TOOK_LOTS_OF_DAMAGE:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_DAUNTLESS_TOOK_LOTS_OF_DAMAGE];//3;
|
|
break;
|
|
case MORALE_TEAMMATE_DIED:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_DAUNTLESS_TEAMMATE_DIED];//2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( bMoraleMod != MORALE_PSYCHO_UNABLE_TO_PSYCHO )
|
|
{
|
|
switch( pProfile->bAttitude )
|
|
{
|
|
case ATT_OPTIMIST:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_OT_OPTIMIST_BAD_EVENT];//1;
|
|
break;
|
|
case ATT_PESSIMIST:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_OT_PESSIMIST_BAD_EVENT];//-= 1;
|
|
break;
|
|
case ATT_COWARD:
|
|
bMoraleMod += gMoraleSettings.bModifiers[MORALE_MODIFIER_OT_COWARD_BAD_EVENT];//-= 2;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Flugente: what the heck? why is morale lowered or increased depending on our height? This doesn't make any sense. AT ALL.
|
|
/*if (pSoldier->pathing.bLevel == 1)
|
|
{
|
|
bMoraleMod--;
|
|
}
|
|
else if (pSoldier->pathing.bLevel > 5)
|
|
{
|
|
bMoraleMod++;
|
|
}*/
|
|
|
|
if (bMoraleMod > 0)
|
|
{
|
|
// can't change a negative event into a positive one!
|
|
bMoraleMod = 0;
|
|
}
|
|
}
|
|
// apply change!
|
|
if (ubType == TACTICAL_MORALE_EVENT)
|
|
{
|
|
iMoraleModTotal = (INT32) pSoldier->aiData.bTacticalMoraleMod + (INT32) bMoraleMod;
|
|
iMoraleModTotal = __min( iMoraleModTotal, gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
iMoraleModTotal = __max( iMoraleModTotal, -gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
pSoldier->aiData.bTacticalMoraleMod = (INT8) iMoraleModTotal;
|
|
}
|
|
else if ( gTacticalStatus.fEnemyInSector && !pSoldier->bInSector ) // delayed strategic
|
|
{
|
|
iMoraleModTotal = (INT32) pSoldier->bDelayedStrategicMoraleMod + (INT32) bMoraleMod;
|
|
iMoraleModTotal = __min( iMoraleModTotal, gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
iMoraleModTotal = __max( iMoraleModTotal, -gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
pSoldier->bDelayedStrategicMoraleMod = (INT8) iMoraleModTotal;
|
|
}
|
|
else // strategic
|
|
{
|
|
iMoraleModTotal = (INT32) pSoldier->aiData.bStrategicMoraleMod + (INT32) bMoraleMod;
|
|
iMoraleModTotal = __min( iMoraleModTotal, gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
iMoraleModTotal = __max( iMoraleModTotal, -gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
pSoldier->aiData.bStrategicMoraleMod = (INT8) iMoraleModTotal;
|
|
}
|
|
|
|
RefreshSoldierMorale( pSoldier );
|
|
|
|
if ( !pSoldier->flags.fMercAsleep )
|
|
{
|
|
if ( !gfSomeoneSaidMoraleQuote )
|
|
{
|
|
// Check if we're below a certain value and warn
|
|
if ( pSoldier->aiData.bMorale < 35 )
|
|
{
|
|
// Have we said this quote yet?
|
|
if ( !( pSoldier->usQuoteSaidFlags & SOLDIER_QUOTE_SAID_LOW_MORAL ) )
|
|
{
|
|
gfSomeoneSaidMoraleQuote = TRUE;
|
|
|
|
// ATE: Amde it a DELAYED QUOTE - will be delayed by the dialogue Q until it's our turn...
|
|
DelayedTacticalCharacterDialogue( pSoldier, QUOTE_STARTING_TO_WHINE );
|
|
pSoldier->usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_LOW_MORAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset flag!
|
|
if ( pSoldier->aiData.bMorale > 65 )
|
|
{
|
|
pSoldier->usQuoteSaidFlags &= (~SOLDIER_QUOTE_SAID_LOW_MORAL );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void HandleMoraleEventForSoldier( SOLDIERTYPE * pSoldier, INT8 bMoraleEvent )
|
|
{
|
|
// SANDRO - changed this to send the event forward
|
|
UpdateSoldierMorale( pSoldier, bMoraleEvent ); //, gbMoraleEvent[bMoraleEvent].ubType, gbMoraleEvent[bMoraleEvent].bChange );
|
|
}
|
|
|
|
|
|
void HandleMoraleEvent( SOLDIERTYPE *pSoldier, INT8 bMoraleEvent, INT16 sMapX, INT16 sMapY, INT8 bMapZ )
|
|
{
|
|
UINT8 ubLoop;
|
|
SOLDIERTYPE * pTeamSoldier;
|
|
MERCPROFILESTRUCT * pProfile;
|
|
|
|
gfSomeoneSaidMoraleQuote = FALSE;
|
|
|
|
// NOTE: Many morale events are NOT attached to a specific player soldier at all!
|
|
// Those that do need it have Asserts on a case by case basis below
|
|
if (pSoldier == NULL)
|
|
{
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "Handling morale event %d at X=%d, Y=%d,Z=%d", bMoraleEvent, sMapX, sMapY, bMapZ ) );
|
|
}
|
|
else
|
|
{
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "Handling morale event %d for %S at X=%d, Y=%d, Z=%d", bMoraleEvent, pSoldier->GetName(), sMapX, sMapY, bMapZ ) );
|
|
}
|
|
|
|
|
|
switch( bMoraleEvent )
|
|
{
|
|
case MORALE_KILLED_ENEMY:
|
|
case MORALE_DID_LOTS_OF_DAMAGE:
|
|
case MORALE_DRUGS_CRASH:
|
|
case MORALE_ALCOHOL_CRASH:
|
|
case MORALE_SUPPRESSED:
|
|
case MORALE_TOOK_LOTS_OF_DAMAGE:
|
|
case MORALE_HIGH_DEATHRATE:
|
|
case MORALE_SEX:
|
|
// needs specific soldier!
|
|
Assert( pSoldier );
|
|
// affects the soldier only
|
|
HandleMoraleEventForSoldier( pSoldier, bMoraleEvent );
|
|
break;
|
|
|
|
case MORALE_CLAUSTROPHOBE_UNDERGROUND:
|
|
case MORALE_INSECT_PHOBIC_SEES_CREATURE:
|
|
case MORALE_NERVOUS_ALONE:
|
|
case MORALE_HEAT_INTOLERANT_IN_DESERT: // SANDRO added this one
|
|
// needs specific soldier!
|
|
Assert( pSoldier );
|
|
// affects the soldier only, should be ignored if tactical morale mod is -20 or less
|
|
// SANDRO - really?? I think it also shouldn't go lower if strategic morale mod is less than -20
|
|
if ( pSoldier->aiData.bTacticalMoraleMod > gMoraleSettings.bModifiers[PHOBIC_LIMIT] && pSoldier->aiData.bStrategicMoraleMod > gMoraleSettings.bModifiers[PHOBIC_LIMIT] )
|
|
{
|
|
HandleMoraleEventForSoldier( pSoldier, bMoraleEvent );
|
|
}
|
|
break;
|
|
|
|
case MORALE_BATTLE_WON:
|
|
// affects everyone to varying degrees
|
|
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pTeamSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pTeamSoldier++ )
|
|
{
|
|
if ( pTeamSoldier->bActive )
|
|
{
|
|
if ( SOLDIER_IN_SECTOR( pTeamSoldier, sMapX, sMapY, bMapZ ) )
|
|
{
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_BATTLE_WON );
|
|
}
|
|
// SANDRO - Assertive people don't care about actions of others
|
|
else if ( !gGameOptions.fNewTraitSystem || !DoesMercHavePersonality( pTeamSoldier, CHAR_TRAIT_ASSERTIVE ) )
|
|
{
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_HEARD_BATTLE_WON );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MORALE_RAN_AWAY:
|
|
// affects everyone to varying degrees
|
|
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pTeamSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pTeamSoldier++ )
|
|
{
|
|
if ( pTeamSoldier->bActive )
|
|
{
|
|
// CJC: adding to SOLDIER_IN_SECTOR check special stuff because the old sector values might
|
|
// be appropriate (because in transit going out of that sector!)
|
|
|
|
if ( SOLDIER_IN_SECTOR( pTeamSoldier, sMapX, sMapY, bMapZ ) || (pTeamSoldier->flags.fBetweenSectors && SECTORX( pTeamSoldier->ubPrevSectorID ) == sMapX && SECTORY( pTeamSoldier->ubPrevSectorID ) == sMapY && (pTeamSoldier->bSectorZ == bMapZ)) )
|
|
{
|
|
if ( gGameOptions.fNewTraitSystem )
|
|
{
|
|
// Flugente: if we have the covert trait and are covert, we might simply be returning from a reconnaissance mission in enemy territory. No need for a morale drop in this case
|
|
if ( !HAS_SKILL_TRAIT( pTeamSoldier, COVERT_NT ) || ( (pTeamSoldier->usSoldierFlagMask & (SOLDIER_COVERT_CIV|SOLDIER_COVERT_SOLDIER)) == 0) )
|
|
{
|
|
// SANDRO - no penalty for pacifists to run away
|
|
if ( DoesMercHavePersonality( pTeamSoldier, CHAR_TRAIT_PACIFIST ) )
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_RAN_AWAY );
|
|
|
|
// Double morale drop for aggressive people
|
|
if ( DoesMercHavePersonality( pTeamSoldier, CHAR_TRAIT_AGGRESSIVE ) )
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_RAN_AWAY );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch ( gMercProfiles[ pTeamSoldier->ubProfile ].bAttitude )
|
|
{
|
|
case ATT_AGGRESSIVE:
|
|
// double the penalty - these guys REALLY hate running away
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_RAN_AWAY );
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_RAN_AWAY );
|
|
break;
|
|
case ATT_COWARD:
|
|
// no penalty - cowards are perfectly happy to avoid fights!
|
|
break;
|
|
default:
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_RAN_AWAY );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// SANDRO - Assertive people don't care about actions of others
|
|
else if ( !gGameOptions.fNewTraitSystem || !DoesMercHavePersonality( pTeamSoldier, CHAR_TRAIT_ASSERTIVE ) )
|
|
{
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_HEARD_BATTLE_LOST );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MORALE_TOWN_LIBERATED:
|
|
case MORALE_TOWN_LOST:
|
|
case MORALE_MINE_LIBERATED:
|
|
case MORALE_MINE_LOST:
|
|
case MORALE_SAM_SITE_LIBERATED:
|
|
case MORALE_SAM_SITE_LOST:
|
|
case MORALE_KILLED_CIVILIAN:
|
|
case MORALE_LOW_DEATHRATE:
|
|
case MORALE_HEARD_BATTLE_WON:
|
|
case MORALE_HEARD_BATTLE_LOST:
|
|
case MORALE_MONSTER_QUEEN_KILLED:
|
|
case MORALE_DEIDRANNA_KILLED:
|
|
// affects everyone, everywhere
|
|
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pTeamSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pTeamSoldier++ )
|
|
{
|
|
if ( pTeamSoldier->bActive )
|
|
{
|
|
if ( gGameOptions.fNewTraitSystem && bMoraleEvent != MORALE_DEIDRANNA_KILLED)
|
|
{
|
|
if ( !SOLDIER_IN_SECTOR( pTeamSoldier, sMapX, sMapY, bMapZ ) && ( gMoraleSettings.bValues[bMoraleEvent] > 0 ) && //( gbMoraleEvent[bMoraleEvent].bChange > 0 ) &&
|
|
DoesMercHavePersonality( pTeamSoldier, CHAR_TRAIT_ASSERTIVE ) )
|
|
{
|
|
// No morale gain for assertive people from actions of others
|
|
}
|
|
else
|
|
{
|
|
HandleMoraleEventForSoldier( pTeamSoldier, bMoraleEvent );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HandleMoraleEventForSoldier( pTeamSoldier, bMoraleEvent );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MORALE_POOR_MORALE:
|
|
case MORALE_GREAT_MORALE:
|
|
case MORALE_AIRSTRIKE:
|
|
// affects every in sector
|
|
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pTeamSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pTeamSoldier++ )
|
|
{
|
|
if ( pTeamSoldier->bActive && SOLDIER_IN_SECTOR( pTeamSoldier, sMapX, sMapY, bMapZ ) )
|
|
{
|
|
HandleMoraleEventForSoldier( pTeamSoldier, bMoraleEvent );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MORALE_MERC_CAPTURED:
|
|
// needs specific soldier! (for reputation, not here)
|
|
Assert( pSoldier );
|
|
|
|
// affects everyone
|
|
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pTeamSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pTeamSoldier++ )
|
|
{
|
|
if ( pTeamSoldier->bActive )
|
|
{
|
|
HandleMoraleEventForSoldier( pTeamSoldier, bMoraleEvent );
|
|
}
|
|
}
|
|
break;
|
|
case MORALE_TEAMMATE_DIED:
|
|
// needs specific soldier!
|
|
Assert( pSoldier );
|
|
|
|
// affects everyone, in sector differently than not, extra bonuses if it's a buddy or hated merc
|
|
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pTeamSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pTeamSoldier++ )
|
|
{
|
|
if ( pTeamSoldier->bActive && pTeamSoldier->ubProfile != NO_PROFILE)
|
|
{
|
|
pProfile = &(gMercProfiles[ pTeamSoldier->ubProfile ]);
|
|
|
|
if (HATED_MERC( pProfile, pSoldier->ubProfile ))
|
|
{
|
|
// yesss!
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_HATED_DIED );
|
|
}
|
|
else
|
|
{
|
|
if ( SOLDIER_IN_SECTOR( pTeamSoldier, sMapX, sMapY, bMapZ ) )
|
|
{
|
|
// mate died in my sector! tactical morale mod
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_SQUADMATE_DIED );
|
|
|
|
// SANDRO - Squadleaders death cost a lot of morale
|
|
if( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, SQUADLEADER_NT ))
|
|
{
|
|
for ( UINT8 i = 0; i < (gSkillTraitValues.ubSLDeathMoralelossMultiplier * NUM_SKILL_TRAITS( pSoldier, SQUADLEADER_NT )); i++ )
|
|
{
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_SQUADMATE_DIED );
|
|
}
|
|
}
|
|
}
|
|
|
|
// this is handled for everyone even if in sector, as it's a strategic morale mod
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_TEAMMATE_DIED );
|
|
|
|
// SANDRO - Squadleaders death cost a lot of morale
|
|
if( gGameOptions.fNewTraitSystem && (NUM_SKILL_TRAITS( pSoldier, SQUADLEADER_NT ) > 1))
|
|
{
|
|
for ( UINT8 i = 0; i < gSkillTraitValues.ubSLDeathMoralelossMultiplier; i++ )
|
|
{
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_SQUADMATE_DIED );
|
|
}
|
|
}
|
|
|
|
if (BUDDY_MERC( pProfile, pSoldier->ubProfile ))
|
|
{
|
|
// oh no! buddy died!
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_BUDDY_DIED );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MORALE_MERC_MARRIED:
|
|
// female mercs get unhappy based on how sexist they are (=hate men)
|
|
// gentlemen males get unhappy too
|
|
|
|
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pTeamSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pTeamSoldier++ )
|
|
{
|
|
if ( pTeamSoldier->bActive && pTeamSoldier->ubProfile != NO_PROFILE )
|
|
{
|
|
if ( WhichHated( pTeamSoldier->ubProfile, pSoldier->ubProfile ) != -1 )
|
|
{
|
|
// we hate 'em anyways
|
|
continue;
|
|
}
|
|
|
|
if ( gMercProfiles[ pTeamSoldier->ubProfile ].bSex == FEMALE )
|
|
{
|
|
switch( gMercProfiles[ pTeamSoldier->ubProfile ].bSexist )
|
|
{
|
|
case SOMEWHAT_SEXIST:
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_MERC_MARRIED );
|
|
break;
|
|
case VERY_SEXIST:
|
|
// handle TWICE!
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_MERC_MARRIED );
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_MERC_MARRIED );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch( gMercProfiles[ pTeamSoldier->ubProfile ].bSexist )
|
|
{
|
|
case GENTLEMAN:
|
|
HandleMoraleEventForSoldier( pTeamSoldier, MORALE_MERC_MARRIED );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
break;
|
|
// added by SANDRO
|
|
case MORALE_PSYCHO_UNABLE_TO_PSYCHO:
|
|
case MORALE_PACIFIST_GAIN_NONCOMBAT: // added by SANDRO
|
|
case MORALE_MALICIOUS_HIT: // added by SANDRO
|
|
Assert( pSoldier );
|
|
HandleMoraleEventForSoldier( pSoldier, bMoraleEvent );
|
|
break;
|
|
|
|
// added by Flugente
|
|
case MORALE_FOOD:
|
|
case MORALE_GOOD_FOOD:
|
|
case MORALE_BAD_FOOD:
|
|
case MORALE_LOATHSOME_FOOD:
|
|
Assert( pSoldier );
|
|
HandleMoraleEventForSoldier( pSoldier, bMoraleEvent );
|
|
break;
|
|
|
|
case MORALE_BUDDY_FIRED:
|
|
case MORALE_BUDDY_FIRED_EARLY:
|
|
case MORALE_BUDDY_FIRED_ON_BAD_TERMS:
|
|
case MORALE_BAD_EQUIPMENT:
|
|
case MORALE_OWED_MONEY:
|
|
case MORALE_PLAYER_INACTIVE_DAYS:
|
|
case MORALE_PREVENTED_MISBEHAVIOUR:
|
|
Assert( pSoldier );
|
|
HandleMoraleEventForSoldier( pSoldier, bMoraleEvent );
|
|
break;
|
|
|
|
default:
|
|
// debug message
|
|
ScreenMsg( MSG_FONT_RED, MSG_BETAVERSION, L"Invalid morale event type = %d. AM/CC-1", bMoraleEvent );
|
|
break;
|
|
}
|
|
|
|
// Flugente: if the situation is really, REALLY bad, we can get traumatized
|
|
if ( pSoldier && pSoldier->aiData.bMorale < 15 )
|
|
HandlePossibleInfection( pSoldier, NULL, INFECTION_TYPE_TRAUMATIC );
|
|
|
|
// some morale events also impact the player's reputation with the mercs back home
|
|
switch( bMoraleEvent )
|
|
{
|
|
case MORALE_HIGH_DEATHRATE:
|
|
ModifyPlayerReputation(REPUTATION_HIGH_DEATHRATE);
|
|
break;
|
|
case MORALE_LOW_DEATHRATE:
|
|
ModifyPlayerReputation(REPUTATION_LOW_DEATHRATE);
|
|
break;
|
|
case MORALE_POOR_MORALE:
|
|
ModifyPlayerReputation(REPUTATION_POOR_MORALE);
|
|
break;
|
|
case MORALE_GREAT_MORALE:
|
|
ModifyPlayerReputation(REPUTATION_GREAT_MORALE);
|
|
break;
|
|
case MORALE_BATTLE_WON:
|
|
ModifyPlayerReputation(REPUTATION_BATTLE_WON);
|
|
break;
|
|
case MORALE_RAN_AWAY:
|
|
case MORALE_HEARD_BATTLE_LOST:
|
|
ModifyPlayerReputation(REPUTATION_BATTLE_LOST);
|
|
break;
|
|
case MORALE_TOWN_LIBERATED:
|
|
ModifyPlayerReputation(REPUTATION_TOWN_WON);
|
|
break;
|
|
case MORALE_TOWN_LOST:
|
|
ModifyPlayerReputation(REPUTATION_TOWN_LOST);
|
|
break;
|
|
case MORALE_TEAMMATE_DIED:
|
|
// impact depends on that dude's level of experience
|
|
ModifyPlayerReputation((UINT8) (pSoldier->stats.bExpLevel * REPUTATION_SOLDIER_DIED));
|
|
break;
|
|
case MORALE_MERC_CAPTURED:
|
|
// impact depends on that dude's level of experience
|
|
ModifyPlayerReputation((UINT8) (pSoldier->stats.bExpLevel * REPUTATION_SOLDIER_CAPTURED));
|
|
break;
|
|
case MORALE_KILLED_CIVILIAN:
|
|
ModifyPlayerReputation(REPUTATION_KILLED_CIVILIAN);
|
|
break;
|
|
case MORALE_MONSTER_QUEEN_KILLED:
|
|
ModifyPlayerReputation(REPUTATION_KILLED_MONSTER_QUEEN);
|
|
break;
|
|
case MORALE_DEIDRANNA_KILLED:
|
|
ModifyPlayerReputation(REPUTATION_KILLED_DEIDRANNA);
|
|
break;
|
|
case MORALE_BAD_EQUIPMENT:
|
|
ModifyPlayerReputation(REPUTATION_MERC_COMPLAIN_EQUIPMENT);
|
|
break;
|
|
case MORALE_OWED_MONEY:
|
|
ModifyPlayerReputation(REPUTATION_MERC_OWED_MONEY);
|
|
break;
|
|
case MORALE_PLAYER_INACTIVE_DAYS:
|
|
ModifyPlayerReputation(REPUTATION_PLAYER_IS_INACTIVE);
|
|
break;
|
|
|
|
default:
|
|
// no reputation impact
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void HourlyMoraleUpdate( void )
|
|
{
|
|
INT8 bMercID, bOtherID;
|
|
INT8 bActualTeamOpinion;
|
|
INT8 bTeamMoraleModChange, bTeamMoraleModDiff;
|
|
INT8 bOpinion=-1;
|
|
INT32 iTotalOpinions;
|
|
INT8 bNumTeamMembers;
|
|
INT8 bHighestTeamLeadership = 0;
|
|
INT8 bLastTeamID;
|
|
SOLDIERTYPE * pSoldier;
|
|
SOLDIERTYPE * pOtherSoldier;
|
|
MERCPROFILESTRUCT * pProfile;
|
|
BOOLEAN fSameGroupOnly;
|
|
static INT8 bStrategicMoraleUpdateCounter = 0;
|
|
BOOLEAN fFoundHated = FALSE;
|
|
INT8 bHated;
|
|
|
|
bMercID = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
bLastTeamID = gTacticalStatus.Team[ gbPlayerNum ].bLastID;
|
|
|
|
// loop through all mercs to calculate their morale
|
|
for ( pSoldier = MercPtrs[ bMercID ]; bMercID <= bLastTeamID; ++bMercID,pSoldier++)
|
|
{
|
|
//if the merc is active, in Arulco, and conscious, not POW
|
|
if ( pSoldier->bActive && pSoldier->ubProfile != NO_PROFILE &&
|
|
!(pSoldier->bAssignment == IN_TRANSIT ||
|
|
pSoldier->flags.fMercAsleep == TRUE ||
|
|
pSoldier->bAssignment == ASSIGNMENT_DEAD ||
|
|
pSoldier->bAssignment == ASSIGNMENT_POW) )
|
|
{
|
|
// calculate the guy's opinion of the people he is with
|
|
pProfile = &(gMercProfiles[ pSoldier->ubProfile ]);
|
|
|
|
// if we're moving
|
|
if (pSoldier->ubGroupID != 0 && PlayerIDGroupInMotion( pSoldier->ubGroupID ))
|
|
{
|
|
// we only check our opinions of people in our squad
|
|
fSameGroupOnly = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fSameGroupOnly = FALSE;
|
|
}
|
|
fFoundHated = FALSE;
|
|
|
|
// reset counts to calculate average opinion
|
|
iTotalOpinions = 0;
|
|
bNumTeamMembers = 0;
|
|
|
|
// let people with high leadership affect their own morale
|
|
bHighestTeamLeadership = EffectiveLeadership( pSoldier );
|
|
|
|
// loop through all other mercs
|
|
bOtherID = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
for ( pOtherSoldier = MercPtrs[ bOtherID ]; bOtherID <= bLastTeamID; bOtherID++,pOtherSoldier++)
|
|
{
|
|
// skip past ourselves and all inactive mercs
|
|
if (bOtherID != bMercID && pOtherSoldier->bActive && pOtherSoldier->ubProfile != NO_PROFILE &&
|
|
!(pOtherSoldier->bAssignment == IN_TRANSIT ||
|
|
pOtherSoldier->flags.fMercAsleep == TRUE ||
|
|
pOtherSoldier->bAssignment == ASSIGNMENT_DEAD ||
|
|
pOtherSoldier->bAssignment == ASSIGNMENT_POW))
|
|
{
|
|
if (fSameGroupOnly)
|
|
{
|
|
// all we have to check is the group ID
|
|
if (pSoldier->ubGroupID != pOtherSoldier->ubGroupID)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// check to see if the location is the same
|
|
if (pOtherSoldier->sSectorX != pSoldier->sSectorX ||
|
|
pOtherSoldier->sSectorY != pSoldier->sSectorY ||
|
|
pOtherSoldier->bSectorZ != pSoldier->bSectorZ)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if the OTHER soldier is in motion then we don't do anything!
|
|
if (pOtherSoldier->ubGroupID != 0 && PlayerIDGroupInMotion( pOtherSoldier->ubGroupID ))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bOpinion = SoldierRelation( pSoldier, pOtherSoldier);
|
|
|
|
if (bOpinion == HATED_OPINION)
|
|
{
|
|
bHated = WhichHated( pSoldier->ubProfile, pOtherSoldier->ubProfile );
|
|
if ( bHated >= 5 )
|
|
{
|
|
// learn to hate which has become full-blown hatred, full strength
|
|
fFoundHated = TRUE;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// scale according to how close to we are to snapping
|
|
//KM : Divide by 0 error found. Wrapped into an if statement.
|
|
if( pProfile->bHatedTime[ bHated ] )
|
|
{
|
|
bOpinion = ((INT32) bOpinion) * (pProfile->bHatedTime[ bHated ] - pProfile->bHatedCount[ bHated ]) / pProfile->bHatedTime[ bHated ];
|
|
}
|
|
|
|
if ( pProfile->bHatedCount[ bHated ] <= pProfile->bHatedTime[ bHated ] / 2 )
|
|
{
|
|
// Augh, we're teamed with someone we hate! We HATE this!! Ignore everyone else!
|
|
fFoundHated = TRUE;
|
|
break;
|
|
}
|
|
// otherwise just mix this opinion in with everyone else...
|
|
}
|
|
}
|
|
iTotalOpinions += bOpinion;
|
|
++bNumTeamMembers;
|
|
if ( EffectiveLeadership( pOtherSoldier ) > bHighestTeamLeadership)
|
|
{
|
|
bHighestTeamLeadership = EffectiveLeadership( pOtherSoldier );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fFoundHated)
|
|
{
|
|
// If teamed with someone we hated, team opinion is automatically minimum
|
|
bActualTeamOpinion = HATED_OPINION;
|
|
}
|
|
else if (bNumTeamMembers > 0)
|
|
{
|
|
bActualTeamOpinion = (INT8) (iTotalOpinions / bNumTeamMembers);
|
|
// give bonus/penalty for highest leadership value on team
|
|
bActualTeamOpinion += (bHighestTeamLeadership - 50) / 10;
|
|
}
|
|
else // alone
|
|
{
|
|
bActualTeamOpinion = 0;
|
|
}
|
|
|
|
// reduce to a range of HATED through BUDDY
|
|
if (bActualTeamOpinion > BUDDY_OPINION)
|
|
{
|
|
bActualTeamOpinion = BUDDY_OPINION;
|
|
}
|
|
else if (bActualTeamOpinion < HATED_OPINION)
|
|
{
|
|
bActualTeamOpinion = HATED_OPINION;
|
|
}
|
|
|
|
// shift morale from team by ~10%
|
|
|
|
// this should range between -75 and +75
|
|
bTeamMoraleModDiff = bActualTeamOpinion - pSoldier->aiData.bTeamMoraleMod;
|
|
if (bTeamMoraleModDiff > 0)
|
|
{
|
|
bTeamMoraleModChange = 1 + bTeamMoraleModDiff / 10;
|
|
}
|
|
else if (bTeamMoraleModDiff < 0)
|
|
{
|
|
bTeamMoraleModChange = -1 + bTeamMoraleModDiff / 10;
|
|
}
|
|
else
|
|
{
|
|
bTeamMoraleModChange = 0;
|
|
}
|
|
|
|
// SANDRO - morale is going down faster if not fighting for malicious characters
|
|
if ( DoesMercHavePersonality( pSoldier, CHAR_TRAIT_MALICIOUS ) )
|
|
{
|
|
bTeamMoraleModChange += gMoraleSettings.bModifiers[MORALE_MODIFIER_MALICIOUS_HOURLY_DECAY];//-= 1;
|
|
}
|
|
|
|
pSoldier->aiData.bTeamMoraleMod += bTeamMoraleModChange;
|
|
pSoldier->aiData.bTeamMoraleMod = __min( pSoldier->aiData.bTeamMoraleMod, gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
pSoldier->aiData.bTeamMoraleMod = __max( pSoldier->aiData.bTeamMoraleMod, -gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
|
|
// New, December 3rd, 1998, by CJC --
|
|
// If delayed strategic modifier exists then incorporate it in strategic mod
|
|
if ( pSoldier->bDelayedStrategicMoraleMod )
|
|
{
|
|
pSoldier->aiData.bStrategicMoraleMod += pSoldier->bDelayedStrategicMoraleMod;
|
|
pSoldier->bDelayedStrategicMoraleMod = 0;
|
|
pSoldier->aiData.bStrategicMoraleMod = __min( pSoldier->aiData.bStrategicMoraleMod, gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
pSoldier->aiData.bStrategicMoraleMod = __max( pSoldier->aiData.bStrategicMoraleMod, -gMoraleSettings.bModifiers[MORALE_MOD_MAX] );
|
|
}
|
|
|
|
// refresh the morale value for the soldier based on the recalculated team modifier
|
|
RefreshSoldierMorale( pSoldier );
|
|
}
|
|
}
|
|
|
|
++bStrategicMoraleUpdateCounter;
|
|
|
|
if ( bStrategicMoraleUpdateCounter == gMoraleSettings.ubHoursBetweenStrategicDelay )
|
|
{
|
|
DecayStrategicMoraleModifiers();
|
|
bStrategicMoraleUpdateCounter = 0;
|
|
}
|
|
}
|
|
|
|
void HandleSnitchCheck( void )
|
|
{
|
|
UINT16 bMercID, bOtherID;
|
|
INT8 bOpinion = -1;
|
|
UINT16 bLastTeamID;
|
|
SOLDIERTYPE* pSoldier;
|
|
SOLDIERTYPE* pOtherSoldier;
|
|
MERCPROFILESTRUCT* pProfile;
|
|
BOOLEAN fSameGroupOnly;
|
|
// anv: save merc id and his negative morale event for snitches
|
|
std::vector<SnitchEvent> snitcheventvector;
|
|
|
|
bMercID = gTacticalStatus.Team[gbPlayerNum].bFirstID;
|
|
bLastTeamID = gTacticalStatus.Team[gbPlayerNum].bLastID;
|
|
|
|
// loop through all mercs to calculate their morale
|
|
for ( pSoldier = MercPtrs[bMercID]; bMercID <= bLastTeamID; ++bMercID, pSoldier++ )
|
|
{
|
|
//if the merc is active, in Arulco, not POW, not a vehicle
|
|
if ( pSoldier && pSoldier->bActive && pSoldier->ubProfile != NO_PROFILE &&
|
|
!(pSoldier->bAssignment == IN_TRANSIT ||
|
|
pSoldier->bAssignment == ASSIGNMENT_DEAD ||
|
|
pSoldier->bAssignment == ASSIGNMENT_POW ||
|
|
pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) )
|
|
{
|
|
// calculate the guy's opinion of the people he is with
|
|
pProfile = &(gMercProfiles[pSoldier->ubProfile]);
|
|
|
|
// if we're moving
|
|
if ( pSoldier->ubGroupID != 0 && PlayerIDGroupInMotion( pSoldier->ubGroupID ) )
|
|
{
|
|
// we only check our opinions of people in our squad
|
|
fSameGroupOnly = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fSameGroupOnly = FALSE;
|
|
}
|
|
// loop through all other mercs
|
|
bOtherID = gTacticalStatus.Team[gbPlayerNum].bFirstID;
|
|
for ( pOtherSoldier = MercPtrs[bOtherID]; bOtherID <= bLastTeamID; ++bOtherID, pOtherSoldier++ )
|
|
{
|
|
// skip past ourselves and all inactive mercs
|
|
if ( bOtherID != bMercID && pOtherSoldier && pOtherSoldier->bActive && pOtherSoldier->ubProfile != NO_PROFILE &&
|
|
!(pOtherSoldier->bAssignment == IN_TRANSIT ||
|
|
pOtherSoldier->bAssignment == ASSIGNMENT_DEAD ||
|
|
pOtherSoldier->bAssignment == ASSIGNMENT_POW ||
|
|
pOtherSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) )
|
|
{
|
|
if ( fSameGroupOnly )
|
|
{
|
|
// all we have to check is the group ID
|
|
if ( pSoldier->ubGroupID != pOtherSoldier->ubGroupID )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// check to see if the location is the same
|
|
if ( pOtherSoldier->sSectorX != pSoldier->sSectorX ||
|
|
pOtherSoldier->sSectorY != pSoldier->sSectorY ||
|
|
pOtherSoldier->bSectorZ != pSoldier->bSectorZ )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if the OTHER soldier is in motion then we don't do anything!
|
|
if ( pOtherSoldier->ubGroupID != 0 && PlayerIDGroupInMotion( pOtherSoldier->ubGroupID ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
bOpinion = SoldierRelation( pSoldier, pOtherSoldier );
|
|
if ( bOpinion <= gSkillTraitValues.bSNTMercOpinionAboutMercTreshold )
|
|
{
|
|
RememberSnitchableEvent( pSoldier->ubProfile, pOtherSoldier->ubProfile, fSameGroupOnly, SNITCH_HATED_PERSON, snitcheventvector );
|
|
}
|
|
}
|
|
}
|
|
|
|
// check death rate vs. merc's tolerance once/day (ignores buddies!)
|
|
if ( MercThinksDeathRateTooHigh( pSoldier->ubProfile ) )
|
|
{
|
|
// too high, inform
|
|
RememberSnitchableEvent( pSoldier->ubProfile, NO_PROFILE, fSameGroupOnly, SNITCH_DEATH_RATE, snitcheventvector );
|
|
}
|
|
|
|
// check his morale vs. his morale tolerance once/day (ignores buddies!)
|
|
if ( MercThinksHisMoraleIsTooLow( pSoldier ) )
|
|
{
|
|
// too low, inform
|
|
RememberSnitchableEvent( pSoldier->ubProfile, NO_PROFILE, fSameGroupOnly, SNITCH_LOW_MORALE, snitcheventvector );
|
|
}
|
|
|
|
// check his opinion about player's reputation
|
|
if ( MercThinksBadReputationTooHigh( pSoldier->ubProfile ) )
|
|
{
|
|
// too high, inform
|
|
RememberSnitchableEvent( pSoldier->ubProfile, NO_PROFILE, fSameGroupOnly, SNITCH_REPUTATION, snitcheventvector );
|
|
}
|
|
|
|
// check his opinion about player activity
|
|
if ( MercThinksPlayerIsInactiveTooLong( pSoldier->ubProfile ) )
|
|
{
|
|
// player inactive, inform
|
|
RememberSnitchableEvent( pSoldier->ubProfile, NO_PROFILE, fSameGroupOnly, SNITCH_PROGRESS, snitcheventvector );
|
|
}
|
|
|
|
// check if player owes him money
|
|
if ( MercIsOwedTooMuchMoney( pSoldier->ubProfile ) )
|
|
{
|
|
// player owes him money, inform
|
|
RememberSnitchableEvent( pSoldier->ubProfile, NO_PROFILE, fSameGroupOnly, SNITCH_OWED_MONEY, snitcheventvector );
|
|
}
|
|
|
|
// check if contract is running out, and will not renew
|
|
// but only if he hasn't another contract signed, so there's still a chance to renew if player reacts properly
|
|
if ( ContractIsGoingToExpireSoon( pSoldier ) )
|
|
{
|
|
if ( (pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__AIM_MERC) )
|
|
{
|
|
// Only do this if they don't want to renew.....
|
|
if ( !(pSoldier->flags.fSignedAnotherContract) && !WillMercRenew( pSoldier, FALSE ) )
|
|
{
|
|
RememberSnitchableEvent( pSoldier->ubProfile, NO_PROFILE, fSameGroupOnly, SNITCH_GONNA_QUIT, snitcheventvector );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// anv: handle snitches informing player about reasons for morale drops
|
|
HandleSnitchesReports( snitcheventvector );
|
|
}
|
|
|
|
void HandleSnitchesReports( std::vector<SnitchEvent>& aVec )
|
|
{
|
|
UINT8 bSnitchID;
|
|
SOLDIERTYPE *pSnitch;
|
|
BOOLEAN fSleepingSnitch = FALSE;
|
|
|
|
UINT16 size = aVec.size( );
|
|
for ( UINT16 bCounter = 0; bCounter < size; ++bCounter )
|
|
{
|
|
SnitchEvent& event = aVec[bCounter];
|
|
|
|
if ( event.ubEventType < NUM_SNITCH_EVENTS )
|
|
{
|
|
bSnitchID = event.ubSnitchID;
|
|
pSnitch = FindSoldierByProfileID( bSnitchID, TRUE );
|
|
|
|
if ( pSnitch == NULL )
|
|
continue;
|
|
|
|
if ( !(pSnitch->bActive) )
|
|
continue;
|
|
|
|
if ( pSnitch->flags.fMercAsleep )
|
|
fSleepingSnitch = TRUE;
|
|
|
|
// snitch introduction
|
|
if ( fSleepingSnitch )
|
|
TacticalCharacterDialogueWithSpecialEvent( pSnitch, 0, DIALOGUE_SPECIAL_EVENT_SLEEP, 0, 0 );
|
|
|
|
SnitchTacticalCharacterDialogue( pSnitch, 0, SNITCH_INTRODUCTION, NO_PROFILE, NO_PROFILE );
|
|
|
|
// process all reports by the same snitch in row
|
|
for ( UINT16 bCounter2 = bCounter; bCounter2 < size; ++bCounter2 )
|
|
{
|
|
SnitchEvent& event2 = aVec[bCounter2];
|
|
|
|
if ( event2.ubEventType < NUM_SNITCH_EVENTS && event2.ubSnitchID == bSnitchID )
|
|
{
|
|
// check if relevant mercs are well
|
|
SOLDIERTYPE *pSoldier, *pOtherSoldier;
|
|
pSoldier = FindSoldierByProfileID( event2.ubTargetProfile, TRUE );
|
|
if ( pSoldier == NULL || !(pSoldier->bActive) )
|
|
continue;
|
|
|
|
if ( event2.ubEventType == SNITCH_HATED_PERSON )
|
|
{
|
|
pOtherSoldier = FindSoldierByProfileID( event2.ubSecondaryTargetProfile, TRUE );
|
|
if ( pOtherSoldier == NULL || !(pOtherSoldier->bActive) )
|
|
continue;
|
|
}
|
|
|
|
// say this info
|
|
SnitchTacticalCharacterDialogue( pSnitch, event2.ubEventType, event2.ubEventType, event2.ubTargetProfile, event2.ubSecondaryTargetProfile );
|
|
|
|
// clear info
|
|
event2.ubEventType = NUM_SNITCH_EVENTS;
|
|
|
|
// Flugente: dynamic opinions
|
|
AddOpinionEvent( event2.ubTargetProfile, pSnitch->ubProfile, OPINIONEVENT_SNITCHSOLDMEOUT );
|
|
}
|
|
}
|
|
|
|
if ( fSleepingSnitch )
|
|
TacticalCharacterDialogueWithSpecialEvent( FindSoldierByProfileID( bSnitchID, TRUE ), 0, DIALOGUE_SPECIAL_EVENT_SLEEP, 1, 0 );
|
|
|
|
// clear info
|
|
event.ubEventType = NUM_SNITCH_EVENTS;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RememberSnitchableEvent( UINT8 ubTargetProfile, UINT8 ubSecondaryTargetProfile, BOOLEAN fSameGroupOnly, UINT8 ubEventType, std::vector<SnitchEvent>& aVec )
|
|
{
|
|
UINT16 bSnitchID;
|
|
INT16 sSnitchingChance = 0;
|
|
UINT8 ubSnitchProfile;
|
|
SOLDIERTYPE * pSnitch;
|
|
SOLDIERTYPE * pSoldier;
|
|
SOLDIERTYPE * pOtherSoldier;
|
|
|
|
pSoldier = FindSoldierByProfileID( ubTargetProfile, FALSE );
|
|
pOtherSoldier = FindSoldierByProfileID( ubSecondaryTargetProfile, FALSE );
|
|
|
|
// loop through all other mercs
|
|
bSnitchID = gTacticalStatus.Team[gbPlayerNum].bFirstID;
|
|
for ( pSnitch = MercPtrs[bSnitchID]; bSnitchID <= gTacticalStatus.Team[gbPlayerNum].bLastID; ++bSnitchID, pSnitch++ )
|
|
{
|
|
ubSnitchProfile = pSnitch->ubProfile;
|
|
// skip past ourselves and all inactive mercs
|
|
if ( ProfileHasSkillTrait( ubSnitchProfile, SNITCH_NT ) &&
|
|
ubSnitchProfile != ubTargetProfile && ubSnitchProfile != ubSecondaryTargetProfile
|
|
&& pSnitch->bActive && ubSnitchProfile != NO_PROFILE &&
|
|
!(pSnitch->bAssignment == IN_TRANSIT ||
|
|
pSnitch->bAssignment == ASSIGNMENT_DEAD ||
|
|
pSnitch->bAssignment == ASSIGNMENT_POW) )
|
|
{
|
|
if ( fSameGroupOnly )
|
|
{
|
|
// all we have to check is the group ID
|
|
if ( pSoldier->ubGroupID != pSnitch->ubGroupID )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// check to see if the location is the same
|
|
if ( pSnitch->sSectorX != pSoldier->sSectorX ||
|
|
pSnitch->sSectorY != pSoldier->sSectorY ||
|
|
pSnitch->bSectorZ != pSoldier->bSectorZ )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if snitch is in motion then we don't do anything!
|
|
if ( pSnitch->ubGroupID != 0 && PlayerIDGroupInMotion( pSnitch->ubGroupID ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
// calculate chances of snitching
|
|
|
|
// decreased if target doesn't like snitch, increased if snitch doesn't like him
|
|
sSnitchingChance = gSkillTraitValues.ubSNTBaseChance;
|
|
sSnitchingChance = (INT16)(sSnitchingChance +
|
|
SoldierRelation( pSoldier, pSnitch )*gSkillTraitValues.fSNTMercOpinionAboutSnitchBonusModifier +
|
|
SoldierRelation( pSnitch, pSoldier )*gSkillTraitValues.fSNTSnitchOpinionAboutMercBonusModifier +
|
|
gMercProfiles[ubSnitchProfile].bLeadership*gSkillTraitValues.fSNTSnitchLeadershipBonusModifer);
|
|
|
|
if ( pSoldier->bAssignment == pSnitch->bAssignment )
|
|
{
|
|
sSnitchingChance += gSkillTraitValues.bSNTSameAssignmentBonus;
|
|
}
|
|
|
|
if ( DoesMercHavePersonality( pSnitch, CHAR_TRAIT_SOCIABLE ) )
|
|
{
|
|
sSnitchingChance += gSkillTraitValues.bSNTSociableMercBonus;
|
|
}
|
|
|
|
if ( DoesMercHavePersonality( pSnitch, CHAR_TRAIT_LONER ) )
|
|
{
|
|
sSnitchingChance += gSkillTraitValues.bSNTLonerMercBonus;
|
|
}
|
|
|
|
if ( ubEventType == SNITCH_HATED_PERSON )
|
|
{
|
|
if ( gSkillTraitValues.bSNTMercOpinionAboutMercTreshold != HATED_OPINION )
|
|
{
|
|
sSnitchingChance = (INT16)(sSnitchingChance *
|
|
(1.0 - (FLOAT)(SoldierRelation( pSoldier, pOtherSoldier ) - HATED_OPINION) /
|
|
(FLOAT)(gSkillTraitValues.bSNTMercOpinionAboutMercTreshold - HATED_OPINION)));
|
|
}
|
|
}
|
|
if ( DoesMercHaveDisability( pSnitch, DEAF ) )
|
|
{
|
|
sSnitchingChance /= 2;
|
|
}
|
|
sSnitchingChance = max( 0, sSnitchingChance );
|
|
if ( Random( 100 ) < (UINT8)sSnitchingChance )
|
|
{
|
|
//information gathered succesfully, remember event to report it later
|
|
SnitchEvent event;
|
|
|
|
event.ubEventType = ubEventType;
|
|
event.ubSnitchID = ubSnitchProfile;
|
|
event.ubTargetProfile = ubTargetProfile;
|
|
event.ubSecondaryTargetProfile = ubSecondaryTargetProfile;
|
|
|
|
aVec.push_back( event );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DailyMoraleUpdate(SOLDIERTYPE *pSoldier)
|
|
{
|
|
if ( pSoldier->ubProfile == NO_PROFILE )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// CJC: made per hour now
|
|
/*
|
|
// decay the merc's strategic morale modifier
|
|
if (pSoldier->aiData.bStrategicMoraleMod != 0)
|
|
{
|
|
// decay the modifier!
|
|
DecayStrategicMorale( pSoldier );
|
|
|
|
// refresh the morale value for the soldier based on the recalculated modifier
|
|
RefreshSoldierMorale( pSoldier );
|
|
}
|
|
*/
|
|
|
|
// check death rate vs. merc's tolerance once/day (ignores buddies!)
|
|
if ( MercThinksDeathRateTooHigh( pSoldier->ubProfile ) )
|
|
{
|
|
// too high, morale takes a hit
|
|
HandleMoraleEvent( pSoldier, MORALE_HIGH_DEATHRATE, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
|
|
}
|
|
|
|
// check his morale vs. his morale tolerance once/day (ignores buddies!)
|
|
if ( MercThinksHisMoraleIsTooLow( pSoldier ) )
|
|
{
|
|
// too low, morale sinks further (merc's in a funk and things aren't getting better)
|
|
HandleMoraleEvent( pSoldier, MORALE_POOR_MORALE, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
|
|
}
|
|
else if ( pSoldier->aiData.bMorale >= 75 )
|
|
{
|
|
// very high morale, merc is cheerleading others
|
|
HandleMoraleEvent( pSoldier, MORALE_GREAT_MORALE, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
|
|
}
|
|
|
|
}
|
|
|
|
// Added by SANDRO
|
|
BOOLEAN IsShowOffNearBy( SOLDIERTYPE * pSoldier )
|
|
{
|
|
UINT32 uiLoop;
|
|
SOLDIERTYPE * pTeammate;
|
|
BOOLEAN fOneException = FALSE;
|
|
BOOLEAN fYesHeIs = FALSE;
|
|
|
|
if (!pSoldier)
|
|
{
|
|
return( FALSE );
|
|
}
|
|
if( !(pSoldier->bActive) || !(pSoldier->bInSector) )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
|
|
for ( uiLoop = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID; uiLoop < gTacticalStatus.Team[ pSoldier->bTeam ].bLastID; uiLoop++)
|
|
{
|
|
pTeammate = MercPtrs[ uiLoop ];
|
|
if ( pTeammate == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
if ( pTeammate == pSoldier )
|
|
{
|
|
continue;
|
|
}
|
|
// Are we actually here?
|
|
if ( !(pTeammate->bActive) || !(pTeammate->bInSector) || ( pTeammate->flags.uiStatusFlags & SOLDIER_VEHICLE ) || (pTeammate->bAssignment == VEHICLE ) )
|
|
{
|
|
// is nowhere around!
|
|
continue;
|
|
}
|
|
// Are we from our team an dalive?
|
|
if ( pTeammate->bTeam == pSoldier->bTeam && pTeammate->stats.bLife >= OKLIFE &&
|
|
DoesMercHavePersonality( pTeammate, CHAR_TRAIT_SHOWOFF ) && PythSpacesAway( pSoldier->sGridNo, pTeammate->sGridNo ) <= 15 )
|
|
{
|
|
if ( (pSoldier->ubBodyType <= STOCKYMALE && pTeammate->ubBodyType <= STOCKYMALE) ||
|
|
(pSoldier->ubBodyType == REGFEMALE && pTeammate->ubBodyType == REGFEMALE) )
|
|
{
|
|
// phlegmatic character can ignore one
|
|
if ( DoesMercHavePersonality( pSoldier, CHAR_TRAIT_PHLEGMATIC ) && !fOneException )
|
|
{
|
|
fOneException = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fYesHeIs = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return( fYesHeIs );
|
|
}
|