Files
source/Tactical/Food.cpp
T
Flugente 36055d4e4f - new icon for ammo belt and ammo vest
- mercs can refuse to eat if they do not like the food (works only once)

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@5422 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2012-07-25 00:05:01 +00:00

997 lines
37 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "Tactical All.h"
#else
#include <math.h>
#include "sgp.h"
#include "soldier profile.h"
#include "Food.h"
#include "items.h"
#include "morale.h"
#include "points.h"
#include "message.h"
#include "GameSettings.h" // SANDRO - had to add this, dammit!
#include "Random.h"
#include "Text.h"
#include "Interface.h"
#include "Dialogue Control.h"
#include "Sound Control.h"
#include "Assignments.h"
#include "Overhead.h"
#include "worldman.h"
#include "Isometric Utils.h"
#include "Campaign Types.h"
#include "Drugs And Alcohol.h"
#include "environment.h"
//#include "Game Clock.h"
#include "WorldDat.h"
#include "Facilities.h"
#endif
//forward declarations of common classes to eliminate includes
class OBJECTTYPE;
class SOLDIERTYPE;
extern MoraleEvent gbMoraleEvent[NUM_MORALE_EVENTS];
UINT8 gSectorWaterType[256][4];
//extern BOOLEAN HandleSoldierDeath( SOLDIERTYPE *pSoldier , BOOLEAN *pfMadeCorpse );
//extern BOOLEAN TacticalRemoveSoldier( UINT16 usSoldierIndex );
extern BOOLEAN GetSectorFlagStatus( INT16 sMapX, INT16 sMapY, UINT8 bMapZ, UINT32 uiFlagToSet );
extern void BuildStashForSelectedSectorAndDecayFood( INT16 sMapX, INT16 sMapY, INT16 sMapZ );
FoodMoraleMod FoodMoraleMods[NUM_FOOD_MORALE_TYPES] =
{
{ 100000, -10, -3, - -75, -75, 2}, // FOOD_STUFFED
{ 5000, -5, -2, -5, -5, 0}, // FOOD_EXTREMELY_FULL
{ 2500, 2, -1, -1, 0, 0}, // FOOD_FULL
{ 1000, 5, 0, 0, 0, 0}, // FOOD_SLIGHTLY_FULL
{ 0, 0, 0, 0, 0, 0}, // FOOD_NORMAL
{ -1000, 0, 0, 0, 0, 0}, // FOOD_LOW
{ -2500, -2, 0, 0, 0, 0}, // FOOD_EVEN_LOWER
{ -5000, -10, -1, -10, -10, 5}, // FOOD_VERY_LOW
{ -7500, -20, -2, -25, -25, 25}, // FOOD_DANGER
{ -8750, -30, -2, -50, -50, 75}, // FOOD_DESPERATE
{ -10000, -50, -3, -80, -75, 100}, // FOOD_STARVING
};
BOOLEAN ApplyFood( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObject, BOOLEAN fForce )
{
// static variables to remember the last food someone was forced to eat
static UINT8 lasteater = 0;
static UINT16 lastitem = 0;
// how did this even happen?
if ( !pSoldier || !pObject || !(pObject->exists() ) || (*pObject)[0]->data.objectStatus < 1 )
return( FALSE);
UINT32 foodtype = Item[pObject->usItem].foodtype;
// if not a food item, nothing to see here
if ( foodtype == 0 || foodtype > FOOD_TYPE_MAX )
return( FALSE);
// workaround: canteens with 1% status are treated as 'empty'. They cannot be consumed, but refilled
if ( Item[pObject->usItem].canteen == TRUE && (*pObject)[0]->data.objectStatus == 1 )
return( FALSE);
// do we eat or drink this stuff?
UINT8 type = AP_EAT;
if ( Food[foodtype].bDrinkPoints > Food[foodtype].bFoodPoints )
type = AP_DRINK;
// return if we don't have enough APs
if (!EnoughPoints( pSoldier, APBPConstants[type], 0, TRUE ) )
{
return( FALSE );
}
// check if we are willing to eat this: if we're filled, the merc refuses
if ( ( ( Food[foodtype].bFoodPoints > 0 && pSoldier->bFoodLevel > FoodMoraleMods[FOOD_EXTREMELY_FULL].bThreshold ) || Food[foodtype].bFoodPoints <= 0 ) && ( ( Food[foodtype].bDrinkPoints > 0 && pSoldier->bDrinkLevel > FoodMoraleMods[FOOD_EXTREMELY_FULL].bThreshold ) || Food[foodtype].bDrinkPoints <= 0 ) )
{
// Say quote!
TacticalCharacterDialogue( pSoldier, 61 );
return( FALSE);
}
// we have to determine wether the food is rotten, that might influence its condition and poison us
FLOAT foodcondition = (*pObject)[0]->data.bTemperature / OVERHEATING_MAX_TEMPERATURE;
// food in bad condition is harmful!
if ( foodcondition < FOOD_BAD_THRESHOLD )
{
// if we can choose to reject a food and haven't yet done so with this type of bad food, do so. Works only once
if ( !fForce && ( lasteater != pSoldier->ubID || lastitem != pObject->usItem) )
{
lasteater = pSoldier->ubID;
lastitem = pObject->usItem;
// notification
if ( type == AP_EAT )
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s does not want to eat %s", pSoldier->name, Item[pObject->usItem].szItemName );
else
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s does not want to drink %s", pSoldier->name, Item[pObject->usItem].szItemName );
// Say quote!
TacticalCharacterDialogue( pSoldier, 61 );
return( FALSE);
}
// determine the max nutritional value
INT32 maxpts = max(Food[foodtype].bFoodPoints, Food[foodtype].bDrinkPoints);
// divide by 100 (poison sum is in [0, 99]
// multiply with FOOD_BAD_THRESHOLD for more reasonable values
INT8 poisonadd = (INT8)(0.01 * maxpts * (1.0 - foodcondition) * FOOD_BAD_THRESHOLD );
pSoldier->AddPoison(poisonadd);
}
FLOAT conditionmodifier = 0.5f * (1.0f + sqrt(foodcondition));
FLOAT percentualsize = min(Food[foodtype].ubPortionSize, (*pObject)[0]->data.objectStatus) / 100.0f;
INT32 foodpts = (INT32) (Food[foodtype].bFoodPoints * percentualsize * conditionmodifier );
INT32 drinkpts = (INT32) (Food[foodtype].bDrinkPoints * percentualsize * conditionmodifier );
// eat it!
pSoldier->bFoodLevel = min(pSoldier->bFoodLevel + foodpts, FOOD_MAX);
pSoldier->bFoodLevel = max(pSoldier->bFoodLevel, FOOD_MIN);
pSoldier->bDrinkLevel = min(pSoldier->bDrinkLevel + drinkpts, DRINK_MAX);
pSoldier->bDrinkLevel = max(pSoldier->bDrinkLevel, DRINK_MIN);
/////////////////// MORALE //////////////////////
INT8 moralemod = Food[foodtype].bMoraleMod;
if ( moralemod > 0 )
// morale is lower if food is rotten, can even become negative
moralemod = (INT8) ( moralemod * (FOOD_BAD_THRESHOLD_INVERSE*foodcondition - 1.0f) );
else
// if we hate the food anyway, give even lower morale if its rotten
moralemod = (INT8) ( moralemod * (2.0f - foodcondition) );
if ( moralemod > 0 )
{
while ( moralemod > 0 && moralemod >= gbMoraleEvent[MORALE_GOOD_FOOD].bChange )
{
HandleMoraleEvent( pSoldier, MORALE_GOOD_FOOD, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
moralemod -= gbMoraleEvent[MORALE_GOOD_FOOD].bChange;
}
while ( moralemod > 0 && moralemod >= gbMoraleEvent[MORALE_FOOD].bChange )
{
HandleMoraleEvent( pSoldier, MORALE_FOOD, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
moralemod -= gbMoraleEvent[MORALE_FOOD].bChange;
}
}
else if ( moralemod < 0 )
{
while ( moralemod < 0 && moralemod <= gbMoraleEvent[MORALE_LOATHSOME_FOOD].bChange )
{
HandleMoraleEvent( pSoldier, MORALE_LOATHSOME_FOOD, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
moralemod -= gbMoraleEvent[MORALE_LOATHSOME_FOOD].bChange;
}
while ( moralemod < 0 && moralemod <= gbMoraleEvent[MORALE_BAD_FOOD].bChange )
{
HandleMoraleEvent( pSoldier, MORALE_BAD_FOOD, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
moralemod -= gbMoraleEvent[MORALE_BAD_FOOD].bChange;
}
}
/////////////////// MORALE //////////////////////
// notification
if ( type == AP_EAT )
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s ate %s", pSoldier->name, Item[pObject->usItem].szItemName );
else
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s drank %s", pSoldier->name, Item[pObject->usItem].szItemName );
// now remove a portion of the food item (or the whole item altogether)
UINT16 ptsconsumed = UseKitPoints( pObject, Food[foodtype].ubPortionSize, pSoldier );
INT32 sBPAdjustment = 0;
// if the food is more of a drink, we also restore breath points
if ( Food[foodtype].bDrinkPoints > Food[foodtype].bFoodPoints )
{
//UINT16 ptsconsumed = Food[foodtype].ubPortionSize - ptsleft;
INT16 usTotalKitPoints = TotalPoints( pObject );
INT16 sPointsToUse = __min( ptsconsumed, usTotalKitPoints );
sBPAdjustment = 2 * sPointsToUse * -(100 - pSoldier->bBreath);
}
DeductPoints( pSoldier, APBPConstants[type], sBPAdjustment );
// let it be known that we are eating
if ( pSoldier->bTeam == gbPlayerNum )
{
if ( type == AP_EAT )
{
// Play sound
PlayJA2SampleFromFile( "Sounds\\eat1.wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
}
else
{
if ( gMercProfiles[ pSoldier->ubProfile ].bSex == MALE )
{
PlayJA2Sample( DRINK_CANTEEN_MALE, RATE_11025, MIDVOLUME, 1, MIDDLEPAN );
}
else
{
PlayJA2Sample( DRINK_CANTEEN_FEMALE, RATE_11025, MIDVOLUME, 1, MIDDLEPAN );
}
}
}
return( TRUE );
}
UINT8 GetFoodSituation( SOLDIERTYPE *pSoldier )
{
UINT8 foodsituation = FOOD_NORMAL;
if ( !pSoldier )
return( foodsituation );
// The lowest value determines how bad we feel
INT32 missamount = min( pSoldier->bFoodLevel, pSoldier->bDrinkLevel);
// old code
/*INT32 missamount = 0;
if ( pSoldier->bFoodLevel < 0 )
missamount += pSoldier->bFoodLevel;
if ( pSoldier->bDrinkLevel < 0 )
missamount += (INT32)(FOOD_MORALE_DRINK_TO_FOOD_RATIO * pSoldier->bDrinkLevel);*/
for ( UINT8 i = FOOD_STUFFED; i < NUM_FOOD_MORALE_TYPES; ++i )
{
if ( missamount < FoodMoraleMods[i].bThreshold )
foodsituation = i;
else
break;
}
return( foodsituation );
}
void FoodMaxMoraleModifiy( SOLDIERTYPE *pSoldier, UINT8* pubMaxMorale )
{
if ( !pSoldier )
return;
UINT8 foodsituation = GetFoodSituation( pSoldier );
INT8 mod = FoodMoraleMods[foodsituation].bMoraleModifier;
if ( (*pubMaxMorale) + mod > 0 )
(*pubMaxMorale) += mod;
else
(*pubMaxMorale) = 0;
}
void FoodNeedForSleepModifiy( SOLDIERTYPE *pSoldier, UINT8* pubNeedForSleep )
{
if ( !pSoldier )
return;
UINT8 foodsituation = GetFoodSituation( pSoldier );
(*pubNeedForSleep) += max(1, (INT16)((*pubNeedForSleep) - FoodMoraleMods[foodsituation].bSleepModifier));
}
void ReducePointsForHunger( SOLDIERTYPE *pSoldier, UINT32 *pusPoints )
{
UINT8 foodsituation = GetFoodSituation( pSoldier );
INT8 mod = FoodMoraleMods[foodsituation].bAssignmentEfficiencyModifier;
*pusPoints = (UINT32)((*pusPoints) * (100 - mod)/100);
}
void ReduceBPRegenForHunger( SOLDIERTYPE *pSoldier, INT32 *psPoints )
{
UINT8 foodsituation = GetFoodSituation( pSoldier );
INT8 mod = FoodMoraleMods[foodsituation].bBreathRegenModifier;
*psPoints = (INT32)((*psPoints) * (100 - mod)/100);
}
void HourlyFoodSituationUpdate( SOLDIERTYPE *pSoldier )
{
if ( !pSoldier )
return;
// determine our current activity level
FLOAT activitymodifier = gGameExternalOptions.sFoodDigestionOnDuty;
if ( pSoldier->flags.fMercAsleep == TRUE )
activitymodifier = gGameExternalOptions.sFoodDigestionSleep;
else if ( !pSoldier->bInSector )
{
if( pSoldier->bAssignment == VEHICLE )
activitymodifier = gGameExternalOptions.sFoodDigestionTravelVehicle;
else
activitymodifier = gGameExternalOptions.sFoodDigestionTravel;
}
else if ( pSoldier->bAssignment > DOCTOR )
activitymodifier = gGameExternalOptions.sFoodDigestionAssignment;
else if ( (gTacticalStatus.uiFlags & INCOMBAT) )
activitymodifier = gGameExternalOptions.sFoodDigestionCombat;
// for some odd reason, the time isn't even needed here, so we just use 0 :-)
INT8 sectortemperaturemod = SectorTemperature( 0, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
// if we are heat intolerant, increase modifier
if ( sectortemperaturemod > 0 && (gMercProfiles[ pSoldier->ubProfile ].bDisability == HEAT_INTOLERANT || MercUnderTheInfluence(pSoldier, DRUG_TYPE_HEATINTOLERANT) ) )
++sectortemperaturemod;
FLOAT temperaturemodifier = (FLOAT)(3 + sectortemperaturemod)/3;
// due to digestion, reduce our food and drink levels
pSoldier->bFoodLevel = max(pSoldier->bFoodLevel - (INT32) (activitymodifier * gGameExternalOptions.usFoodDigestionHourlyBaseFood), FOOD_MIN);
pSoldier->bDrinkLevel = max(pSoldier->bDrinkLevel - (INT32) (activitymodifier * temperaturemodifier * gGameExternalOptions.usFoodDigestionHourlyBaseDrink), DRINK_MIN);
// there is a chance that we take damage to our health and strength stats if we are starving (or insanely obese :-) )
UINT8 foodsituation = GetFoodSituation( pSoldier );
UINT8 statdamagechance = FoodMoraleMods[foodsituation].ubStatDamageChance;
if ( statdamagechance > 0 )
{
// these reductions can be healed, but only if we are in a sufficient food situation again
// damage strength
if ( Random(100) < statdamagechance )
{
UINT8 numberofreduces = 1;
// if starving, we lose stats a LOT faster
if ( foodsituation == FOOD_STARVING )
numberofreduces += Random(2);
INT8 oldval = pSoldier->stats.bStrength;
pSoldier->stats.bStrength = max(1, pSoldier->stats.bStrength - numberofreduces);
pSoldier->usStarveDamageStrength += oldval - pSoldier->stats.bStrength;
// Update Profile
gMercProfiles[ pSoldier->ubProfile ].bStrength = pSoldier->stats.bStrength;
gMercProfiles[ pSoldier->ubProfile ].records.usTimesStatDamaged++;
// make stat RED for a while...
pSoldier->timeChanges.uiChangeStrengthTime = GetJA2Clock();
pSoldier->usValueGoneUp &= ~( STRENGTH_INCREASE );
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s's strength was damaged due to lack of nutrition!", pSoldier->name );
}
// damage health
if ( Random(100) < statdamagechance )
{
UINT8 numberofreduces = 1;
// if starving, we lose stats a LOT faster
if ( foodsituation == FOOD_STARVING )
numberofreduces += 1 + 2 * Random(4);
INT8 oldlife = pSoldier->stats.bLife;
pSoldier->stats.bLifeMax = max(2, pSoldier->stats.bLifeMax - numberofreduces);
pSoldier->stats.bLife = min(pSoldier->stats.bLife, pSoldier->stats.bLifeMax);
pSoldier->bBleeding = min(pSoldier->bBleeding, pSoldier->stats.bLifeMax);
// adjust poison values
pSoldier->bPoisonSum = min(pSoldier->bPoisonSum, pSoldier->stats.bLifeMax);
pSoldier->bPoisonLife = min(pSoldier->bPoisonLife, pSoldier->bPoisonSum);
pSoldier->bPoisonBleeding = min(pSoldier->bPoisonBleeding, pSoldier->bBleeding);
pSoldier->usStarveDamageHealth += oldlife - pSoldier->stats.bLifeMax;
// Update Profile
gMercProfiles[ pSoldier->ubProfile ].bLifeMax = pSoldier->stats.bLifeMax;
gMercProfiles[ pSoldier->ubProfile ].records.usTimesStatDamaged++;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s's health was damaged due to lack of nutrition!", pSoldier->name );
// if we fall below OKLIFE, we start bleeding...
// Reason for this is that
if ( pSoldier->stats.bLife < OKLIFE )
{
// handling a soldiers death doesn't work somehow - let them bleed to death instead
/*BOOLEAN fMadeCorpse;
HandleSoldierDeath( pSoldier, &fMadeCorpse );
//remove the merc from the tactical
TacticalRemoveSoldier( pSoldier->ubID );*/
pSoldier->bBleeding = max(1, pSoldier->stats.bLife - 1);
pSoldier->stats.bLife = 1;
// Update Profile
gMercProfiles[ pSoldier->ubProfile ].bLifeMax = pSoldier->stats.bLifeMax;
pSoldier->usStarveDamageHealth += oldlife - pSoldier->stats.bLifeMax;
return;
}
// make stat RED for a while...
pSoldier->timeChanges.uiChangeStrengthTime = GetJA2Clock();
pSoldier->usValueGoneUp &= ~( HEALTH_INCREASE );
}
}
}
void HourlyFoodAutoDigestion( SOLDIERTYPE *pSoldier )
{
if ( !pSoldier )
return;
// don't eat if not necessary ( note that if the play decides to eat manually, he can achieve better results. This is intended to award micro-management)
if ( pSoldier->bFoodLevel > FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold && pSoldier->bDrinkLevel > FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold )
return;
// if were a prisoner, we can't feed ourself, and the player can't do that either. Instead the army provides food (not much and of bad quality)
if (pSoldier->bAssignment == ASSIGNMENT_POW)
{
INT16 powwater = gGameExternalOptions.usFoodDigestionHourlyBaseDrink * FOOD_POW_MULTIPLICATOR;
INT16 powfoodadd = powwater * gGameExternalOptions.usFoodDigestionHourlyBaseFood / gGameExternalOptions.usFoodDigestionHourlyBaseDrink;
// if we're thirsty or hungry, and this is nutritious, consume it
if ( pSoldier->bDrinkLevel < FoodMoraleMods[FOOD_VERY_LOW].bThreshold )
{
pSoldier->bDrinkLevel = min(pSoldier->bDrinkLevel + powwater, DRINK_MAX);
pSoldier->bDrinkLevel = max(pSoldier->bDrinkLevel, DRINK_MIN);
}
if ( pSoldier->bFoodLevel < FoodMoraleMods[FOOD_VERY_LOW].bThreshold )
{
pSoldier->bFoodLevel = min(pSoldier->bFoodLevel + powfoodadd, FOOD_MAX);
pSoldier->bFoodLevel = max(pSoldier->bFoodLevel, FOOD_MIN);
}
}
else
{
// no eating if not able to!
if ( pSoldier->flags.fMercAsleep == TRUE || pSoldier->stats.bLife < OKLIFE )
return;
// In certain facilities, we can also eat
for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; ++cnt)
{
// Is this facility here?
if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][cnt].fFacilityHere)
{
// Does it allow training militia?
if (gFacilityTypes[cnt].AssignmentData[FAC_FOOD].sCantinaFoodModifier > 0)
{
if (cnt == (UINT16)pSoldier->sFacilityTypeOperated && // Soldier is operating this facility
GetSoldierFacilityAssignmentIndex( pSoldier ) != -1)
{
INT16 cantinafoodadd = gFacilityTypes[cnt].AssignmentData[FAC_FOOD].sCantinaFoodModifier;
INT16 cantinawater = cantinafoodadd * FOOD_FACILITY_WATER_FACTOR;
// if we're thirsty or hungry, and this is nutritious, consume it. When in a cantina, we are willing to eat a bit more
if ( pSoldier->bDrinkLevel < FoodMoraleMods[FOOD_MERC_STOP_FACILITY].bThreshold )
{
pSoldier->bDrinkLevel = min(pSoldier->bDrinkLevel + cantinawater, DRINK_MAX);
pSoldier->bDrinkLevel = max(pSoldier->bDrinkLevel, DRINK_MIN);
}
if ( pSoldier->bFoodLevel < FoodMoraleMods[FOOD_MERC_STOP_FACILITY].bThreshold )
{
pSoldier->bFoodLevel = min(pSoldier->bFoodLevel + cantinafoodadd, FOOD_MAX);
pSoldier->bFoodLevel = max(pSoldier->bFoodLevel, FOOD_MIN);
}
}
}
}
}
// search for food in our inventory
INT8 invsize = (INT8)pSoldier->inv.size(); // remember inventorysize, so we don't call size() repeatedly
// on the first loop, we omit food in bad condition, and refillable canteens and canned food
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop) // ... for all items in our inventory ...
{
// ... if Item exists and is food ...
if (pSoldier->inv[bLoop].exists() == true && Item[pSoldier->inv[bLoop].usItem].foodtype > 0 )
{
OBJECTTYPE * pObj = &(pSoldier->inv[bLoop]); // ... get pointer for this item ...
if ( pObj != NULL ) // ... if pointer is not obviously useless ...
{
UINT32 foodtype = Item[pObj->usItem].foodtype;
// omit non-degrading food (save it for later!)
if ( Food[foodtype].usDecayRate <= 0.0f )
continue;
// omit bad food (we don't like that and will eat it only if we have to)
FLOAT foodcondition = (*pObj)[0]->data.bTemperature / OVERHEATING_MAX_TEMPERATURE;
if ( foodcondition < FOOD_BAD_THRESHOLD )
continue;
if ( Item[pObj->usItem].canteen == TRUE )
continue;
// if we're thirsty or hungry, and this is nutritious, consume it
if ( ( pSoldier->bDrinkLevel < FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold && Food[foodtype].bDrinkPoints > 0 ) || ( pSoldier->bFoodLevel < FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold && Food[foodtype].bFoodPoints > 0 ) )
{
while ( (*pObj)[0]->data.objectStatus > 1 )
{
// if food is also a drug, ApplyDrugs will also call ApplyFood
if ( Item[pObj->usItem].drugtype > 0 )
ApplyDrugs( pSoldier, pObj );
else
ApplyFood( pSoldier, pObj, TRUE ); // cannot reject to eat this, we chose to eat this ourself!
// if we're full, finish
if ( pSoldier->bFoodLevel > FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold && pSoldier->bDrinkLevel > FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold )
return;
}
}
}
}
}
// second loop: consume anything to feed
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop) // ... for all items in our inventory ...
{
// ... if Item exists and is food ...
if (pSoldier->inv[bLoop].exists() == true && Item[pSoldier->inv[bLoop].usItem].foodtype > 0 )
{
OBJECTTYPE * pObj = &(pSoldier->inv[bLoop]); // ... get pointer for this item ...
if ( pObj != NULL ) // ... if pointer is not obviously useless ...
{
UINT32 foodtype = Item[pObj->usItem].foodtype;
// if we're thirsty or hungry, and this is nutritious, consume it
if ( ( pSoldier->bDrinkLevel < FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold && Food[foodtype].bDrinkPoints > 0 ) || ( pSoldier->bFoodLevel < FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold && Food[foodtype].bFoodPoints > 0 ) )
{
while ( (*pObj)[0]->data.objectStatus > 1 )
{
// if food is also a drug, ApplyDrugs will also call ApplyFood
if ( Item[pObj->usItem].drugtype > 0 )
ApplyDrugs( pSoldier, pObj );
else
ApplyFood( pSoldier, pObj, TRUE ); // cannot reject to eat this, we chose to eat this ourself!
// if we're full, finish
if ( pSoldier->bFoodLevel > FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold && pSoldier->bDrinkLevel > FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold )
return;
}
}
}
}
}
}
}
void HourlyFoodUpdate( void )
{
INT8 bMercID, bLastTeamID;
SOLDIERTYPE * pSoldier = NULL;
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, and in Arulco
if ( pSoldier && pSoldier->bActive && pSoldier->ubProfile != NO_PROFILE && !(pSoldier->bAssignment == IN_TRANSIT || pSoldier->bAssignment == ASSIGNMENT_DEAD ) )
{
// digestion
HourlyFoodSituationUpdate( pSoldier );
// if hungry, eat something automatically from the inventory
HourlyFoodAutoDigestion( pSoldier );
}
}
// decay food everywhere
HourlyFoodDecay();
}
UINT8 GetWaterQuality(INT16 asMapX, INT16 asMapY, INT8 asMapZ)
{
UINT8 waterquality = WATER_NONE;
// for now, we assume that only sectors on the surface have access to water
UINT8 ubSectorId = SECTOR(asMapX, asMapY);
if (asMapZ == 0 && ubSectorId >= 0 && ubSectorId < 256 )
{
waterquality = gSectorWaterType[ubSectorId][asMapZ];
}
return waterquality;
}
// a function that tries to fill up all canteens in this sector
void SectorFillCanteens( void )
{
// no functionality if not in tactical or in combat, or nobody is here
if ( guiCurrentScreen != GAME_SCREEN || (gTacticalStatus.uiFlags & INCOMBAT) || gusSelectedSoldier == NOBODY )
return;
// determine if there are any patches of water in this sector.
// If so, fill up all refillable water containers (= canteens) (there is no way to check if this is actually fresh water, we just assume it is)
// If not, see if there is a water drum, and fill up the canteens from that one
UINT8 waterquality = GetWaterQuality(gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
if ( waterquality == WATER_DRINKABLE || waterquality == WATER_POISONOUS )
{
// the temperature of the water in this sector (temperature reflects the quality)
FLOAT addtemperature = OVERHEATING_MAX_TEMPERATURE;
// if the water in this sector is poisoned, we add a different temperature - resulting in worsening of the item's decay status
if ( waterquality == WATER_POISONOUS )
addtemperature = 0.0f;
// first step: fill all canteens in inventories
INT8 bMercID, bLastTeamID;
SOLDIERTYPE * pSoldier = NULL;
bMercID = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
bLastTeamID = gTacticalStatus.Team[ gbPlayerNum ].bLastID;
// loop through all mercs
for ( pSoldier = MercPtrs[ bMercID ]; bMercID <= bLastTeamID; bMercID++, pSoldier++)
{
//if the merc is in this sector
if ( pSoldier->bActive && pSoldier->ubProfile != NO_PROFILE && pSoldier->bInSector && ( pSoldier->sSectorX == gWorldSectorX ) && ( pSoldier->sSectorY == gWorldSectorY ) && ( pSoldier->bSectorZ == gbWorldSectorZ) )
{
INT8 invsize = (INT8)pSoldier->inv.size(); // remember inventorysize, so we don't call size() repeatedly
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop) // ... for all items in our inventory ...
{
// ... if Item exists and is canteen (that can have drink points) ...
if (pSoldier->inv[bLoop].exists() == true && Item[pSoldier->inv[bLoop].usItem].canteen && Food[Item[pSoldier->inv[bLoop].usItem].foodtype].bDrinkPoints > 0)
{
OBJECTTYPE* pObj = &(pSoldier->inv[bLoop]); // ... get pointer for this item ...
if ( pObj != NULL ) // ... if pointer is not obviously useless ...
{
for(INT16 i = 0; i < pObj->ubNumberOfObjects; ++i) // ... there might be multiple items here (item stack), so for each one ...
{
UINT16 status = (*pObj)[i]->data.objectStatus;
UINT16 statusmmissing = max(0, 100 - status);
FLOAT temperature = (*pObj)[i]->data.bTemperature;
(*pObj)[i]->data.objectStatus = 100; // refill canteen
(*pObj)[i]->data.bTemperature = (status * temperature + statusmmissing * addtemperature)/100;
}
}
}
}
}
}
// second step: fill canteens in sector
for( UINT32 uiCount = 0; uiCount < guiNumWorldItems; ++uiCount ) // ... for all items in the world ...
{
if( gWorldItems[ uiCount ].fExists ) // ... if item exists ...
{
// ... if Item exists and is a canteen (only those are refillable) ...
if ( Item[gWorldItems[ uiCount ].object.usItem].canteen && Food[Item[gWorldItems[ uiCount ].object.usItem].foodtype].bDrinkPoints > 0)
{
OBJECTTYPE* pObj = &(gWorldItems[ uiCount ].object); // ... get pointer for this item ...
if ( pObj != NULL ) // ... if pointer is not obviously useless ...
{
for(INT16 i = 0; i < pObj->ubNumberOfObjects; ++i) // ... there might be multiple items here (item stack), so for each one ...
{
UINT16 status = (*pObj)[i]->data.objectStatus;
UINT16 statusmmissing = max(0, 100 - status);
FLOAT temperature = (*pObj)[i]->data.bTemperature;
(*pObj)[i]->data.objectStatus = 100; // refill canteen
(*pObj)[i]->data.bTemperature = (status * temperature + statusmmissing * addtemperature)/100;
}
}
}
}
}
}
else
{
OBJECTTYPE* pWaterDrum = GetUsableWaterDrumInSector();
if ( !pWaterDrum || !(pWaterDrum->exists()) )
return;
INT32 drumsize = Food[Item[pWaterDrum->usItem].foodtype].bDrinkPoints;
// first step: fill all canteens in inventories
INT8 bMercID, bLastTeamID;
SOLDIERTYPE * pSoldier = NULL;
bMercID = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
bLastTeamID = gTacticalStatus.Team[ gbPlayerNum ].bLastID;
// loop through all mercs
for ( pSoldier = MercPtrs[ bMercID ]; bMercID <= bLastTeamID; bMercID++, pSoldier++)
{
//if the merc is in this sector
if ( pSoldier->bActive && pSoldier->ubProfile != NO_PROFILE && pSoldier->bInSector && ( pSoldier->sSectorX == gWorldSectorX ) && ( pSoldier->sSectorY == gWorldSectorY ) && ( pSoldier->bSectorZ == gbWorldSectorZ) )
{
INT8 invsize = (INT8)pSoldier->inv.size(); // remember inventorysize, so we don't call size() repeatedly
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop) // ... for all items in our inventory ...
{
// ... if Item exists and is canteen and is NOT a water drum...
if (pSoldier->inv[bLoop].exists() == true && Item[pSoldier->inv[bLoop].usItem].canteen && (Food[Item[pSoldier->inv[bLoop].usItem].foodtype].bDrinkPoints > 0) && !HasItemFlag(pSoldier->inv[bLoop].usItem, (WATER_DRUM)))
{
OBJECTTYPE* pObj = &(pSoldier->inv[bLoop]); // ... get pointer for this item ...
if ( pObj != NULL && pObj->exists() ) // ... if pointer is not obviously useless ...
{
INT32 canteensize = Food[Item[pObj->usItem].foodtype].bDrinkPoints;
for(INT16 i = 0; i < pObj->ubNumberOfObjects; ++i) // ... there might be multiple items here (item stack), so for each one ...
{
if ( (*pObj)[i]->data.objectStatus < 100 ) // ... and status is > 1 (1 means that it is empty, but still usable)
{
INT32 ptsneeded = (INT32)((100 - (*pObj)[i]->data.objectStatus) * canteensize / 100);
INT32 ptsinwaterdrum = (INT32)(((*pWaterDrum)[i]->data.objectStatus - 1) * drumsize / 100); // -1 because 1% is the status of an empty water drum
if ( ptsneeded < ptsinwaterdrum )
{
(*pObj)[i]->data.objectStatus = 100;
(*pWaterDrum)[i]->data.objectStatus = max(1, (INT16)((100 * ( ptsinwaterdrum - ptsneeded )) / drumsize) );
}
else
{
(*pObj)[i]->data.objectStatus += (INT16)((100 * ptsinwaterdrum) / canteensize);
(*pWaterDrum)[i]->data.objectStatus = 1;
}
(*pObj)[i]->data.bTemperature = ((*pObj)[i]->data.bTemperature + (*pWaterDrum)[0]->data.bTemperature) / 2; // water now has mixed freshness
if ( (*pWaterDrum)[0]->data.objectStatus == 1 )
{
// get a new water drum!
pWaterDrum = GetUsableWaterDrumInSector();
if ( !pWaterDrum || !(pWaterDrum->exists()) )
return;
drumsize = Food[Item[pWaterDrum->usItem].foodtype].bDrinkPoints;
}
}
}
}
}
}
}
}
// second step: fill canteens in sector
for( UINT32 uiCount = 0; uiCount < guiNumWorldItems; ++uiCount ) // ... for all items in the world ...
{
if( gWorldItems[ uiCount ].fExists ) // ... if item exists ...
{
// ... if Item exists and is a canteen (only those are refillable) ...
if ( Item[gWorldItems[ uiCount ].object.usItem].canteen )
{
OBJECTTYPE* pObj = &(gWorldItems[ uiCount ].object); // ... get pointer for this item ...
if ( pObj != NULL && pObj->exists() ) // ... if pointer is not obviously useless ...
{
if ( (Food[pObj->usItem].bDrinkPoints > 0) && !HasItemFlag(pObj->usItem, (WATER_DRUM)) ) // ... if item is NOT a water drum...
{
INT32 canteensize = Food[Item[pObj->usItem].foodtype].bDrinkPoints;
for(INT16 i = 0; i < pObj->ubNumberOfObjects; ++i) // ... there might be multiple items here (item stack), so for each one ...
{
if ( (*pObj)[i]->data.objectStatus < 100 ) // ... and status is > 1 (1 means that it is empty, but still usable)
{
INT32 ptsneeded = (INT32)((100 - (*pObj)[i]->data.objectStatus) * canteensize / 100);
INT32 ptsinwaterdrum = (INT32)(((*pWaterDrum)[i]->data.objectStatus - 1) * drumsize / 100); // -1 because 1% is the status of an empty water drum
if ( ptsneeded < ptsinwaterdrum )
{
(*pObj)[i]->data.objectStatus = 100;
(*pWaterDrum)[i]->data.objectStatus = max(1, (INT16)((100 * ( ptsinwaterdrum - ptsneeded )) / drumsize) );
}
else
{
(*pObj)[i]->data.objectStatus += (INT16)((100 * ptsinwaterdrum) / canteensize);
(*pWaterDrum)[i]->data.objectStatus = 1;
}
(*pObj)[0]->data.bTemperature = ((*pObj)[0]->data.bTemperature + (*pWaterDrum)[0]->data.bTemperature) / 2; // water now has mixed freshness
if ( (*pWaterDrum)[i]->data.objectStatus == 1 )
{
// get a new water drum!
pWaterDrum = GetUsableWaterDrumInSector();
if ( !pWaterDrum || !(pWaterDrum->exists()) )
return;
drumsize = Food[Item[pWaterDrum->usItem].foodtype].bDrinkPoints;
}
}
}
}
}
}
}
}
}
}
OBJECTTYPE* GetUsableWaterDrumInSector( void )
{
for( UINT32 uiCount = 0; uiCount < guiNumWorldItems; ++uiCount ) // ... for all items in the world ...
{
if( gWorldItems[ uiCount ].fExists ) // ... if item exists ...
{
// ... if Item exists and is a canteen (only those are refillable) ...
if ( Item[gWorldItems[ uiCount ].object.usItem].canteen )
{
OBJECTTYPE* pObj = &(gWorldItems[ uiCount ].object); // ... get pointer for this item ...
if ( pObj != NULL && pObj->exists() ) // ... if pointer is not obviously useless ...
{
if ( HasItemFlag(pObj->usItem, (WATER_DRUM)) ) // ... if item is a water drum...
{
for(INT16 i = 0; i < pObj->ubNumberOfObjects; ++i) // ... there might be multiple items here (item stack), so for each one ...
{
if ( (*pObj)[i]->data.objectStatus > 1 ) // ... and status is > 1 (1 means that it is empty, but still usable)
{
return( pObj );
}
}
}
}
}
}
}
return( NULL );
}
void HourlyFoodDecay( void )
{
// decay food in all inventories
UINT32 cnt = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
for ( SOLDIERTYPE* pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; cnt++, pSoldier++)
{
if ( pSoldier && pSoldier->bActive )
{
pSoldier->SoldierInventoryFoodDecay();
}
}
if ( gGameExternalOptions.fFoodDecayInSectors )
{
INT16 currentSectorX = gWorldSectorX;
INT16 currentSectorY = gWorldSectorY;
INT8 currentSectorZ = gbWorldSectorZ;
// decay food in all visited sectors
for ( INT16 sbMapX = 1; sbMapX < MAP_WORLD_X - 1; ++sbMapX )
{
for ( INT16 sbMapY = 1; sbMapY < MAP_WORLD_Y - 1; ++sbMapY )
{
for ( UINT8 ubMapZ = 0; ubMapZ < 4; ++ubMapZ )
{
// only if sector has already been visisted
if( GetSectorFlagStatus( sbMapX, sbMapY, ubMapZ, SF_ALREADY_VISITED ) == TRUE )
{
BuildStashForSelectedSectorAndDecayFood(sbMapX, sbMapY, (INT16)ubMapZ);
}
}
}
}
}
}
void SectorFoodDecay( WORLDITEM* pWorldItem, UINT32 size )
{
// one hour has 60 minutes, with 12 5-second-intervals (cooldown values are based on 5-second values)
FLOAT decaymod = 12*60*gGameExternalOptions.sFoodDecayModificator;
for( UINT32 uiCount = 0; uiCount < size; ++uiCount ) // ... for all items in the world ...
{
if( pWorldItem[ uiCount ].fExists ) // ... if item exists ...
{
if ( Item[pWorldItem[ uiCount ].object.usItem].foodtype > 0 ) // ... if is food...
{
OBJECTTYPE* pObj = &(pWorldItem[ uiCount ].object); // ... get pointer for this item ...
if ( pObj != NULL ) // ... if pointer is not obviously useless ...
{
if ( Food[Item[pObj->usItem].foodtype].usDecayRate > 0.0f ) // ... if the food can decay...
{
for(INT16 i = 0; i < pObj->ubNumberOfObjects; ++i) // ... there might be multiple items here (item stack), so for each one ...
{
(*pObj)[i]->data.bTemperature = max(0.0f, (*pObj)[i]->data.bTemperature - decaymod * Food[Item[pObj->usItem].foodtype].usDecayRate); // set new temperature
}
}
}
}
}
}
}
// commented out, as we now look for an xml value. Might be relevant in the future, though
/*BOOLEAN DrinkableWaterInSector(INT16 asMapX, INT16 asMapY, INT8 asMapZ)
{
// static variables allow us to remember the result of the last time this function was called - this way, only one call per sector is required
static INT16 sMapX = 0;
static INT16 sMapY = 0;
static INT8 sMapZ = 0;
static BOOLEAN drinkablewaterfound = FALSE;
if ( asMapX != sMapX || asMapY != sMapY || asMapZ != sMapZ )
{
drinkablewaterfound = FALSE;
for( INT32 sGridNo = 0; sGridNo < MAX_MAP_POS; ++sGridNo )
{
if ( Water(sGridNo) || IsKitchenFurnitureWaterSource(sGridNo) )
{
drinkablewaterfound = TRUE;
break;
}
}
UINT8 ubSectorId = SECTOR(asMapX, asMapY);
if (ubSectorId >= 0 && ubSectorId < 256 && gSectorWaterType[ubSectorId][asMapZ] == WATER_DRINKABLE )
{
drinkablewaterfound = TRUE;
}
sMapX = asMapX;
sMapY = asMapY;
sMapZ = asMapZ;
}
return drinkablewaterfound;
}*/
// commented out, as we now look for an xml value. Might be relevant in the future, though
/*// determine wether there is a kitechen furniture here (we assume its a sink, so it'll have water)
BOOLEAN IsKitchenFurnitureWaterSource( INT32 sGridNo )
{
STRUCTURE* pStruct = FindStructure(sGridNo, STRUCTURE_GENERIC);
if ( pStruct != NULL )
{
// Get LEVELNODE for struct and remove!
LEVELNODE* pNode = FindLevelNodeBasedOnStructure( pStruct->sGridNo, pStruct );
if ( pNode )
{
UINT32 uiTileType = 0;
if ( GetTileType( pNode->usIndex, &uiTileType ) )
{
UINT16 usIndex = pNode->usIndex;
// Check if we are a sandbag
if ( ( _strnicmp( gTilesets[ giCurrentTilesetID ].TileSurfaceFilenames[ uiTileType ], "furn_6.sti", 10) == 0 ) ||
( _strnicmp( gTilesets[ giCurrentTilesetID ].TileSurfaceFilenames[ uiTileType ], "old_furn.sti", 12) == 0 ) ||
( _strnicmp( gTilesets[ giCurrentTilesetID ].TileSurfaceFilenames[ uiTileType ], "furnmix.sti", 11) == 0 ) ||
( _strnicmp( gTilesets[ giCurrentTilesetID ].TileSurfaceFilenames[ uiTileType ], "furn_mix.sti", 12) == 0 ) )
{
return TRUE;
}
}
}
}
return FALSE;
}*/