New feature: food system requires mercs to eat and drink in order to survive

- food and water levels are lowered every hour, eat food to survive
- not doing so leads to various penalties and ultimately death
- added food items for that purpose. Food can degrade over time. Rotten food poisons.
- Mercs automatically consume food if hungry/thirsty and food is in their inventories
- for more info, see http://www.bears-pit.com/board/ubbthreads.php/topics/307396/Re_Mercs_need_food_and_water_t.html#Post307396
- this feature requires new STI files from the GameDir trunk.

- added a bunch of filler variable to the soldier type, so hopefully savegame compatibility can be maintained easier in the future.

WARNING: This will break savegame compatibility

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@5411 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Flugente
2012-07-21 16:41:04 +00:00
parent b95d001812
commit f6bf6bf4aa
54 changed files with 2660 additions and 144 deletions
+13 -1
View File
@@ -673,8 +673,20 @@ void ChangeStat( MERCPROFILESTRUCT *pProfile, SOLDIERTYPE *pSoldier, UINT8 ubSta
// SANDRO - reduce damaged stat if this stat was increased normally
if ( fChangeTypeIncrease && (bDamagedStatToRaise != -1) )
{
INT16 ptstolower = sPtsChanged;
UINT8 oldctrpts = pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ];
if (pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ] > 0)
pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ] = max( 0, (pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ] - sPtsChanged));
{
pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ] = max( 0, (pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ] - ptstolower));
ptstolower -= oldctrpts - pSoldier->ubCriticalStatDamage[ bDamagedStatToRaise ];
}
if ( bDamagedStatToRaise == DAMAGED_STAT_STRENGTH && pSoldier->usStarveDamageStrength > 0 )
pSoldier->usStarveDamageStrength = max(0, pSoldier->usStarveDamageStrength - ptstolower);
else if ( bDamagedStatToRaise == DAMAGED_STAT_HEALTH && pSoldier->usStarveDamageHealth > 0 )
pSoldier->usStarveDamageHealth = max(0, pSoldier->usStarveDamageHealth - ptstolower);
}
// if it's a level gain, or sometimes for other stats
+26 -11
View File
@@ -66,6 +66,14 @@ BOOLEAN ApplyDrugs( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObject )
gMercProfiles[ LARRY_DRUNK ].bNPCData = 0;
}
BOOLEAN consumeitem = TRUE;
// if item is also a food iem, don't use it up here, it will be consumed in ApplyFood, which will be called afterwards
UINT32 foodtype = Item[pObject->usItem].foodtype;
// if not a food item, nothing to see here
if ( foodtype > 0 )
consumeitem = FALSE;
// set flag: we are on drugs
pSoldier->bSoldierFlagMask |= SOLDIER_DRUGGED;
@@ -209,23 +217,30 @@ BOOLEAN ApplyDrugs( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObject )
pSoldier->bRegenBoostersUsedToday++;
}
// ATE: use kit points...
if ( usItem == ALCOHOL )
UseKitPoints( pObject, 10, pSoldier );
else if ( usItem == WINE )
UseKitPoints( pObject, 20, pSoldier );
else if ( usItem == BEER )
UseKitPoints( pObject, 100, pSoldier );
else
// increase drug counter if not alcoholic drug
if ( usItem != ALCOHOL && usItem != WINE && usItem != BEER )
{
// remove object
pObject->RemoveObjectsFromStack(1);
if ( gMercProfiles[ pSoldier->ubProfile ].ubNumTimesDrugUseInLifetime != 255 )
{
gMercProfiles[ pSoldier->ubProfile ].ubNumTimesDrugUseInLifetime++;
}
}
// ATE: use kit points...
if ( consumeitem )
{
if ( usItem == ALCOHOL )
UseKitPoints( pObject, 10, pSoldier );
else if ( usItem == WINE )
UseKitPoints( pObject, 20, pSoldier );
else if ( usItem == BEER )
UseKitPoints( pObject, 100, pSoldier );
else
{
// remove object
pObject->RemoveObjectsFromStack(1);
}
}
if ( (ubDrugType & DRUG_ALCOHOL) != 0 )
{
+16
View File
@@ -40,6 +40,7 @@
// HEADROCK HAM 3.2: Added two includes so that a function can read values of the Gun Range/hospital location
#include "Campaign Types.h"
#include "Strategic Event Handler.h"
#include "Food.h" // added by Flugente
#endif
// Defines
@@ -2286,6 +2287,21 @@ void HandleRenderFaceAdjustments( FACETYPE *pFace, BOOLEAN fDisplayBuffer, BOOLE
bNumRightIcons++;
}
// Flugente: food system - symbols used if hungry or thirsty
if ( gGameOptions.fFoodSystem )
{
if ( MercPtrs[ pFace->ubSoldierID ]->bDrinkLevel < FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold )
{
DoRightIcon( uiRenderBuffer, pFace, sFaceX, sFaceY, bNumRightIcons, 19 );
bNumRightIcons++;
}
if ( MercPtrs[ pFace->ubSoldierID ]->bFoodLevel < FoodMoraleMods[FOOD_MERC_START_CONSUME].bThreshold )
{
DoRightIcon( uiRenderBuffer, pFace, sFaceX, sFaceY, bNumRightIcons, 20 );
bNumRightIcons++;
}
}
switch( pSoldier->bAssignment )
{
+964
View File
@@ -0,0 +1,964 @@
#ifdef PRECOMPILEDHEADERS
#include "Tactical All.h"
#else
#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 )
{
// 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;
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 );
// food in bad condition is harmful!
if ( foodcondition < FOOD_BAD_THRESHOLD )
{
// 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);
}
// 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 )
{
ApplyFood( pSoldier, pObj );
// 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 )
{
ApplyFood( pSoldier, pObj );
// 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;
}*/
+106
View File
@@ -0,0 +1,106 @@
#ifndef __FOOD_H
#define __FOOD_H
#include "soldier control.h"
#define FOOD_BAD_THRESHOLD 0.5f // must be > 0 !!!!!
#define FOOD_BAD_THRESHOLD_INVERSE 1.0f/FOOD_BAD_THRESHOLD
#define FOOD_MIN - 20000
#define FOOD_MAX 10000
#define DRINK_MIN - 20000
#define DRINK_MAX 10000
#define FOOD_MORALE_DRINK_TO_FOOD_RATIO 1.0f
#define FOOD_FACILITY_WATER_FACTOR 3.0f
#define FOOD_POW_MULTIPLICATOR 1.2f // multiplicator to the water we get when POWs (so many times our water digestion rate, so we will barely survive)
typedef enum
{
FOOD_STUFFED,
FOOD_EXTREMELY_FULL,
FOOD_FULL,
FOOD_SLIGHTLY_FULL,
FOOD_NORMAL,
FOOD_LOW,
FOOD_EVEN_LOWER,
FOOD_VERY_LOW,
FOOD_DANGER,
FOOD_DESPERATE,
FOOD_STARVING,
NUM_FOOD_MORALE_TYPES
} FoodMoraleModType;
#define FOOD_MERC_START_CONSUME FOOD_EVEN_LOWER
#define FOOD_MERC_STOP_FACILITY FOOD_FULL
typedef struct
{
INT32 bThreshold;
INT8 bMoraleModifier; // absolute modifier to max morale
INT8 bSleepModifier; // absolute modifier
INT8 bBreathRegenModifier; // percentage modifier
INT8 bAssignmentEfficiencyModifier; // percentage modifier
UINT8 ubStatDamageChance; // percentual chance to receive damage to life and strength
} FoodMoraleMod;
extern FoodMoraleMod FoodMoraleMods[NUM_FOOD_MORALE_TYPES];
extern UINT8 gbPlayerNum;
typedef struct
{
UINT16 uiIndex;
CHAR16 szName[80]; // name of this food
INT32 bFoodPoints; // points that will be added to our drink level
INT32 bDrinkPoints; // points that will be added to our drink level
UINT16 ubPortionSize; // how much is 'eaten' as a portion (percentage)
INT8 bMoraleMod; // morale modificator for eating this
FLOAT usDecayRate; // rate at which food decays
} FOODTYPE;
//GLOBALS
extern FOODTYPE Food[FOOD_TYPE_MAX];
// for determining the type of water source a sector has
typedef enum
{
WATER_NONE, // no water at all
WATER_DRINKABLE, // drinkable water (rivers, water supply from houses)
WATER_SALTWATER, // salt water, can't drink that (until something special for this gets implemented)
WATER_POISONOUS // there is water, but it is poisoned (swamps and polluted sectors)
} FoodSectorWaterSupply;
BOOLEAN ApplyFood( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObject );
UINT8 GetFoodSituation( SOLDIERTYPE *pSoldier );
void FoodMaxMoraleModifiy( SOLDIERTYPE *pSoldier, UINT8* pubMaxMorale );
void FoodNeedForSleepModifiy( SOLDIERTYPE *pSoldier, UINT8* pubNeedForSleep );
void ReducePointsForHunger( SOLDIERTYPE *pSoldier, UINT32 *pusPoints );
void ReduceBPRegenForHunger( SOLDIERTYPE *pSoldier, INT32 *psPoints );
void HourlyFoodSituationUpdate( SOLDIERTYPE *pSoldier );
void HourlyFoodAutoDigestion( SOLDIERTYPE *pSoldier );
void HourlyFoodUpdate( void );
UINT8 GetWaterQuality(INT16 asMapX, INT16 asMapY, INT8 asMapZ);
// a function that tries to fill up all canteens in this sector
void SectorFillCanteens( void );
OBJECTTYPE* GetUsableWaterDrumInSector( void );
void HourlyFoodDecay( void );
// Decay the food in a sector, performed every hour
void SectorFoodDecay( WORLDITEM* pWorldItem, UINT32 size );
// checks wether there is any drinkable water source in this sector. Atm every water found is taken as 'drinkable'
//BOOLEAN DrinkableWaterInSector(INT16 asMapX, INT16 asMapY, INT8 asMapZ);
// determine wether there is a kitechen furniture here (we assume its a sink, so it'll have water)
//BOOLEAN IsKitchenFurnitureWaterSource( INT32 sGridNo );
#endif
+39
View File
@@ -73,6 +73,7 @@
#include "opplist.h"
#include "los.h"
#include "Map Screen Interface Map.h"
#include "Food.h" // added by Flugente
#endif
//forward declarations of common classes to eliminate includes
@@ -2462,6 +2463,27 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr
MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + cnt ] );
cnt++;
}
//////////////////// drinkable WATER
UINT32 foodtype = Item[gpItemDescObject->usItem].foodtype;
if ( foodtype > 0 )
{
if ( Food[foodtype].bDrinkPoints > 0 )
{
swprintf( pStr, L"%s%s", szUDBGenSecondaryStatsTooltipText[ 26 ], szUDBGenSecondaryStatsExplanationsTooltipText[ 26 ]);
SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + cnt ]), pStr );
MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + cnt ] );
cnt++;
}
if ( Food[foodtype].bFoodPoints > 0 )
{
swprintf( pStr, L"%s%s", szUDBGenSecondaryStatsTooltipText[ 27 ], szUDBGenSecondaryStatsExplanationsTooltipText[ 27 ]);
SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + cnt ]), pStr );
MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + cnt ] );
cnt++;
}
}
}
//////////////////////////////////////////////////////
@@ -5453,6 +5475,23 @@ void DrawSecondaryStats( OBJECTTYPE * gpItemDescObject )
BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoSecondaryIcon, 24, gItemDescGenSecondaryRegions[cnt].sLeft+sOffsetX, gItemDescGenSecondaryRegions[cnt].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL );
cnt++;
}
//////////////////// drinkable WATER
UINT32 foodtype = Item[gpItemDescObject->usItem].foodtype;
if ( foodtype > 0 )
{
if ( Food[foodtype].bDrinkPoints > 0 )
{
BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoSecondaryIcon, 26, gItemDescGenSecondaryRegions[cnt].sLeft+sOffsetX, gItemDescGenSecondaryRegions[cnt].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL );
cnt++;
}
if ( Food[foodtype].bFoodPoints > 0 )
{
BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoSecondaryIcon, 27, gItemDescGenSecondaryRegions[cnt].sLeft+sOffsetX, gItemDescGenSecondaryRegions[cnt].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL );
cnt++;
}
}
}
void DrawWeaponValues( OBJECTTYPE * gpItemDescObject )
+69
View File
@@ -84,6 +84,7 @@
#include "SkillCheck.h" // added by Flugente
#include "random.h" // added by Flugente
#include "Explosion Control.h" // added by Flugente
#include "Food.h" // added by Flugente
#endif
#ifdef JA2UB
@@ -3803,6 +3804,23 @@ void INVRenderItem( UINT32 uiBuffer, SOLDIERTYPE * pSoldier, OBJECTTYPE *pObjec
DrawItemUIBarEx( pObjShown, DRAW_ITEM_TEMPERATURE, sX, sY + sHeight-1, ITEMDESC_ITEM_STATUS_WIDTH, sHeight-1, colour, colour, TRUE, guiSAVEBUFFER );
}
// Flugente: display condition of food if it can decay
if ( gGameOptions.fFoodSystem == TRUE && Item[pObject->usItem].foodtype > 0 )
{
if ( OVERHEATING_MAX_TEMPERATURE > 0 )
{
FLOAT condition = (*pObject)[0]->data.bTemperature / OVERHEATING_MAX_TEMPERATURE;
UINT32 red = (UINT32) ( 127 );
UINT32 green = (UINT32) ( 54 + 201 * ( min(1.0f, condition ) ) );
UINT32 blue = 0;
UINT16 colour = Get16BPPColor( FROMRGB( red, green, blue ) );
DrawItemUIBarEx( pObject, DRAW_ITEM_TEMPERATURE, sX, sY + sHeight-1, ITEMDESC_ITEM_STATUS_WIDTH, sHeight-1, colour, colour, TRUE, guiSAVEBUFFER);
}
}
// display symbol if we are leaning our weapon on something
// display only if eapon resting is allowed, display is allowed, item is a gun/launcher, we are a person, we hold the gun in our hand, and we are resting the gun
if ( gGameExternalOptions.fWeaponResting && gGameExternalOptions.fDisplayWeaponRestingIndicator && pItem->usItemClass & (IC_GUN | IC_LAUNCHER) && pSoldier && &(pSoldier->inv[pSoldier->ubAttackingHand]) == pObject && pSoldier->IsWeaponMounted() )
@@ -6677,6 +6695,57 @@ void RenderItemDescriptionBox( )
mprintf( usX, usY, pStr );
}
// Flugente: display condition of food if it can decay
else if ( gGameOptions.fFoodSystem == TRUE && Item[gpItemDescObject->usItem].foodtype > 0 )
{
if ( OVERHEATING_MAX_TEMPERATURE > 0 )
{
FLOAT condition = (*gpItemDescObject)[0]->data.bTemperature / OVERHEATING_MAX_TEMPERATURE;
UINT32 red = (UINT32) ( 127 );
UINT32 green = (UINT32) ( 54 + 201 * ( min(1.0f, condition ) ) );
UINT32 blue = 0;
UINT8 FoodStringNum = 6;
if ( condition > 0.84f )
FoodStringNum = 1;
else if ( condition > 0.66f )
FoodStringNum = 2;
else if ( condition > 0.5f )
FoodStringNum = 3;
else if ( condition > 0.33f )
FoodStringNum = 4;
else if ( condition > 0.16f )
FoodStringNum = 5;
// UDB system displays a string with colored condition text.
int regionindex = 7;
SetFontForeground( ForegroundColor );
swprintf( pStr, L"%s", gFoodDesc[0] ); // "Temperature is "
mprintf( gItemDescTextRegions[regionindex].sLeft, gItemDescTextRegions[regionindex].sTop, pStr );
// Record length
INT16 indent = StringPixLength( gFoodDesc[0], ITEMDESC_FONT );
swprintf( pStr, L"%s", gFoodDesc[FoodStringNum] );
SetRGBFontForeground( red, green, blue );
mprintf( gItemDescTextRegions[regionindex].sLeft+indent+2, gItemDescTextRegions[regionindex].sTop, pStr );
// Record length
indent += StringPixLength( gFoodDesc[FoodStringNum], ITEMDESC_FONT );
SetFontForeground( ForegroundColor );
swprintf( pStr, L"%s", gFoodDesc[7] ); // "."
mprintf( gItemDescTextRegions[regionindex].sLeft + indent + 2, gItemDescTextRegions[regionindex].sTop, pStr );
// to get the text to the left side...
swprintf( pStr, L"");
FindFontRightCoordinates( gItemDescTextRegions[regionindex].sLeft, gItemDescTextRegions[regionindex].sTop, gItemDescTextRegions[regionindex].sRight - gItemDescTextRegions[regionindex].sLeft ,gItemDescTextRegions[regionindex].sBottom - gItemDescTextRegions[regionindex].sTop ,pStr, BLOCKFONT2, &usX, &usY);
mprintf( usX, usY, pStr );
}
}
// UDB system displays a string with colored condition text.
SetFontForeground( ForegroundColor );
+59 -12
View File
@@ -74,6 +74,7 @@
#include "Boxing.h"
// HEADROCK HAM 3.6: This is required for Stat Progress Bars
#include "Campaign.h"
#include "Food.h" // added by Flugente
#endif
//legion by Jazz
@@ -2767,7 +2768,7 @@ void RenderSMPanel( BOOLEAN *pfDirty )
FindFontRightCoordinates(SM_DEX_X, SM_DEX_Y ,SM_STATS_WIDTH ,SM_STATS_HEIGHT ,sString, BLOCKFONT2, &usX, &usY);
mprintf( usX, usY , sString );
UpdateStatColor( gpSMCurrentMerc->timeChanges.uiChangeStrengthTime, ( BOOLEAN )( gpSMCurrentMerc->usValueGoneUp & STRENGTH_INCREASE?TRUE: FALSE ), ( BOOLEAN ) ( ( gGameOptions.fNewTraitSystem && ( gpSMCurrentMerc->ubCriticalStatDamage[DAMAGED_STAT_STRENGTH] > 0 )) ? TRUE : FALSE), MercUnderTheInfluence(gpSMCurrentMerc, DRUG_TYPE_STRENGTH)); // SANDRO
UpdateStatColor( gpSMCurrentMerc->timeChanges.uiChangeStrengthTime, ( BOOLEAN )( gpSMCurrentMerc->usValueGoneUp & STRENGTH_INCREASE?TRUE: FALSE ), ( BOOLEAN ) ( (( gGameOptions.fNewTraitSystem && ( gpSMCurrentMerc->ubCriticalStatDamage[DAMAGED_STAT_STRENGTH] > 0 )) || (gGameOptions.fFoodSystem && gpSMCurrentMerc->usStarveDamageStrength > 0) ) ? TRUE : FALSE), MercUnderTheInfluence(gpSMCurrentMerc, DRUG_TYPE_STRENGTH)); // SANDRO
swprintf( sString, L"%2d", gpSMCurrentMerc->stats.bStrength + gpSMCurrentMerc->bExtraStrength );
FindFontRightCoordinates(SM_STR_X, SM_STR_Y ,SM_STATS_WIDTH ,SM_STATS_HEIGHT ,sString, BLOCKFONT2, &usX, &usY);
@@ -2928,14 +2929,30 @@ void RenderSMPanel( BOOLEAN *pfDirty )
else
{
GetMoraleString( gpSMCurrentMerc, pMoraleStr );
// Flugente: added a display for poison, only show text if actualy poisoned
if ( gpSMCurrentMerc->bPoisonSum > 0 )
// Flugente: food info if food system is active
if ( gGameOptions.fFoodSystem )
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_POISON_POPUPTEXT ], gpSMCurrentMerc->stats.bLife, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bPoisonSum, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bBreath, gpSMCurrentMerc->bBreathMax, pMoraleStr );
// Flugente: added a display for poison, only show text if actually poisoned
if ( gpSMCurrentMerc->bPoisonSum > 0 )
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_POISON_AND_FOOD_POPUPTEXT ], gpSMCurrentMerc->stats.bLife, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bPoisonSum, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bBreath, gpSMCurrentMerc->bBreathMax, pMoraleStr, (INT32)(100*gpSMCurrentMerc->bDrinkLevel/FOOD_MAX), L"%", (INT32)(100*gpSMCurrentMerc->bFoodLevel/FOOD_MAX), L"%" );
}
else
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_FOOD_POPUPTEXT ], gpSMCurrentMerc->stats.bLife, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bBreath, gpSMCurrentMerc->bBreathMax, pMoraleStr, (INT32)(100*gpSMCurrentMerc->bDrinkLevel/FOOD_MAX), L"%", (INT32)(100*gpSMCurrentMerc->bFoodLevel/FOOD_MAX), L"%" );
}
}
else
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_POPUPTEXT ], gpSMCurrentMerc->stats.bLife, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bBreath, gpSMCurrentMerc->bBreathMax, pMoraleStr );
// Flugente: added a display for poison, only show text if actually poisoned
if ( gpSMCurrentMerc->bPoisonSum > 0 )
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_POISON_POPUPTEXT ], gpSMCurrentMerc->stats.bLife, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bPoisonSum, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bBreath, gpSMCurrentMerc->bBreathMax, pMoraleStr );
}
else
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_POPUPTEXT ], gpSMCurrentMerc->stats.bLife, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bBreath, gpSMCurrentMerc->bBreathMax, pMoraleStr );
}
}
SetRegionFastHelpText( &(gSM_SELMERCBarsRegion), pStr );
@@ -3264,8 +3281,8 @@ void SMInvClickCamoCallback( MOUSE_REGION * pRegion, INT32 iReason )
// Say OK acknowledge....
gpSMCurrentMerc->DoMercBattleSound( BATTLE_SOUND_COOL1 );
}
}
else if ( ApplyCanteen( gpSMCurrentMerc, gpItemPointer, &fGoodAPs ) )
}
else if ( !gGameOptions.fFoodSystem && ApplyCanteen( gpSMCurrentMerc, gpItemPointer, &fGoodAPs ) )
{
// Dirty
if ( fGoodAPs )
@@ -3298,7 +3315,7 @@ void SMInvClickCamoCallback( MOUSE_REGION * pRegion, INT32 iReason )
gpSMCurrentMerc->DoMercBattleSound( BATTLE_SOUND_COOL1 );
}
}
else if ( ApplyDrugs( gpSMCurrentMerc, gpItemPointer ) )
else if ( gGameOptions.fFoodSystem && ApplyDrugs( gpSMCurrentMerc, gpItemPointer ) )
{
// Dirty
fInterfacePanelDirty = DIRTYLEVEL2;
@@ -3325,6 +3342,21 @@ void SMInvClickCamoCallback( MOUSE_REGION * pRegion, INT32 iReason )
gpSMCurrentMerc->DoMercBattleSound( BATTLE_SOUND_COOL1 );
}
else if ( ApplyFood( gpSMCurrentMerc, gpItemPointer ) )
{
// Dirty
fInterfacePanelDirty = DIRTYLEVEL2;
// Check if it's the same now!
if ( gpItemPointer->exists() == false )
{
gbCompatibleApplyItem = FALSE;
EndItemPointer( );
}
// Say OK acknowledge....
gpSMCurrentMerc->DoMercBattleSound( BATTLE_SOUND_COOL1 );
}
else
{
// Send message
@@ -5475,14 +5507,29 @@ void RenderTEAMPanel( BOOLEAN fDirty )
{
GetMoraleString( pSoldier, pMoraleStr );
// Flugente: added a display for poison, only show text if actually poisoned
if ( pSoldier->bPoisonSum > 0 )
if ( gGameOptions.fFoodSystem )
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_POISON_POPUPTEXT ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bPoisonSum, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pMoraleStr );
// Flugente: added a display for poison, only show text if actually poisoned
if ( pSoldier->bPoisonSum > 0 )
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_POISON_AND_FOOD_POPUPTEXT ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bPoisonSum, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pMoraleStr, (INT32)(100*pSoldier->bDrinkLevel/FOOD_MAX), L"%", (INT32)(100*pSoldier->bFoodLevel/FOOD_MAX), L"%" );
}
else
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_FOOD_POPUPTEXT ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pMoraleStr, (INT32)(100*pSoldier->bDrinkLevel/FOOD_MAX), L"%", (INT32)(100*pSoldier->bFoodLevel/FOOD_MAX), L"%" );
}
}
else
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_POPUPTEXT ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pMoraleStr );
// Flugente: added a display for poison, only show text if actually poisoned
if ( pSoldier->bPoisonSum > 0 )
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_POISON_POPUPTEXT ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bPoisonSum, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pMoraleStr );
}
else
{
swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_POPUPTEXT ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pMoraleStr );
}
}
SetRegionFastHelpText( &(gTEAM_BarsRegions[ cnt ]), pStr );
}
+14 -6
View File
@@ -496,14 +496,22 @@ void DrawItemUIBarEx( OBJECTTYPE *pObject, UINT8 ubStatus, INT16 sXPos, INT16 sY
// Flugente FTW 1.2
if ( ubStatus == DRAW_ITEM_TEMPERATURE )
{
sValue = (INT16) (100 * GetGunOverheatJamPercentage( pObject) );
// the food item bar always has full size
if ( gGameOptions.fFoodSystem == TRUE && Item[pObject->usItem].foodtype > 0 )
{
sValue = 100;
}
else
{
sValue = (INT16) (100 * GetGunOverheatJamPercentage( pObject) );
// if temperature is 0 or below, do not display anything
if ( sValue < 1)
return;
// if temperature is 0 or below, do not display anything
if ( sValue < 1)
return;
// cut off temperature at 100%, otherwise the bar will be out of its box
sValue = min(sValue, 100);
// cut off temperature at 100%, otherwise the bar will be out of its box
sValue = min(sValue, 100);
}
}
// ATE: Subtract 1 to exagerate bad status
+8 -5
View File
@@ -221,7 +221,7 @@ typedef enum
#define GS_WEAPON_BEING_RELOADED 0x02
// Flugente: define for maximum temperature
#define OVERHEATING_MAX_TEMPERATURE 60000.0
#define OVERHEATING_MAX_TEMPERATURE 60000.0f
//forward declaration
class OBJECTTYPE;
@@ -717,10 +717,10 @@ extern OBJECTTYPE gTempObject;
#define SHOVEL 0x00000004 //4
#define CONCERTINA 0x00000008 //8
/*#define WH40K_POWER_ARMOR 0x00000010 //16
#define WH40K_POWER_PACK 0x00000020 //32
#define WH40K_JUMPPACK 0x00000040 //64
#define WH40K_DISPLACER 0x00000080 //128
#define WATER_DRUM 0x00000010 //16 // water drums allow to refill canteens in the sector they are in
#define MEAT_BLOODCAT 0x00000020 //32 // retrieve this by gutting a bloodcat
#define COW_MEAT 0x00000040 //64 // retrieve this by gutting a cow
/*#define WH40K_DISPLACER 0x00000080 //128
#define WH40K_ROSARIUS 0x00000100 //256
#define WH40K_SEAL 0x00000200 //512
@@ -1009,6 +1009,9 @@ typedef struct
UINT32 usItemFlag; // bitflags to store various item properties (better than introducing 32 BOOLEAN values). If I only had thought of this earlier....
// Flugente: food type
UINT32 foodtype;
} INVTYPE;
// CHRISL: Added new structures to handle LBE gear and the two new XML files that will be needed to deal
+16
View File
@@ -60,6 +60,7 @@
#include "popup_definition.h"
#include "drugs and alcohol.h"
#include "Food.h"
#endif
#ifdef JA2UB
@@ -1255,6 +1256,8 @@ std::map<UINT8,popupDef> LBEPocketPopup;
DRUGTYPE Drug[DRUG_TYPE_MAX];
FOODTYPE Food[FOOD_TYPE_MAX];
BOOLEAN ItemIsLegal( UINT16 usItemIndex, BOOLEAN fIgnoreCoolness )
{
if ( Item[usItemIndex].ubCoolness == 0 && !fIgnoreCoolness )
@@ -7117,6 +7120,13 @@ UINT16 UseKitPoints( OBJECTTYPE * pObj, UINT16 usPoints, SOLDIERTYPE *pSoldier )
(*pObj)[bLoop]->data.objectStatus -= (INT8)(usPoints * (max( 0, (100 - Item[pObj->usItem].percentstatusdrainreduction) ) )/100);
return( usOriginalPoints );
}
// Flugente: we no longer destroy canteens upon emtptying them - as we can now refill them
else if ( Item[pObj->usItem].canteen == TRUE )
{
// consume this kit totally
usPoints -= (((*pObj)[bLoop]->data.objectStatus - 1) / (max( 0, (100 - Item[pObj->usItem].percentstatusdrainreduction))) /100);
(*pObj)[bLoop]->data.objectStatus = 1;
}
else
{
// consume this kit totally
@@ -7597,6 +7607,12 @@ BOOLEAN CreateItem( UINT16 usItem, INT16 bStatus, OBJECTTYPE * pObj )
(*pObj)[0]->data.objectStatus = bStatus;
}
// Flugente: the temperature variable determines the quality of the food, begin with being fresh
if ( Item[usItem].foodtype > 0 )
{
(*pObj)[0]->data.bTemperature = OVERHEATING_MAX_TEMPERATURE;
}
//ADB ubWeight has been removed, see comments in OBJECTTYPE
//pObj->ubWeight = CalculateObjectWeight( pObj );
fRet = TRUE;
+15 -4
View File
@@ -26,6 +26,7 @@
// addedd by SANDRO
#include "GameSettings.h"
#include "Isometric Utils.h"
#include "Food.h"
#endif
#include "connect.h"
@@ -88,8 +89,11 @@ MoraleEvent gbMoraleEvent[NUM_MORALE_EVENTS] =
{ 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 SANDRO
{ TACTICAL_MORALE_EVENT, -1}, //MORALE_KHORNATE_RANGED_COMBAT,
// 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,
};
BOOLEAN gfSomeoneSaidMoraleQuote = FALSE;
@@ -349,6 +353,10 @@ void RefreshSoldierMorale( SOLDIERTYPE * pSoldier )
}
}
// Flugente: ubMaxMorale can now be influenced by our food situation
if ( gGameOptions.fFoodSystem )
FoodMaxMoraleModifiy(pSoldier, &ubMaxMorale);
if (ubMaxMorale > 0 && iActualMorale > ubMaxMorale)
{
// Normalize to Max Morale
@@ -525,7 +533,7 @@ void UpdateSoldierMorale( SOLDIERTYPE * pSoldier, INT8 bMoraleEvent )
}
}
}
else if ( bMoraleMod != MORALE_PSYCHO_UNABLE_TO_PSYCHO && bMoraleMod != MORALE_KHORNATE_RANGED_COMBAT)
else if ( bMoraleMod != MORALE_PSYCHO_UNABLE_TO_PSYCHO )
{
switch( pProfile->bAttitude )
{
@@ -912,7 +920,10 @@ void HandleMoraleEvent( SOLDIERTYPE *pSoldier, INT8 bMoraleEvent, INT16 sMapX, I
break;
// added by Flugente
case MORALE_KHORNATE_RANGED_COMBAT:
case MORALE_FOOD:
case MORALE_GOOD_FOOD:
case MORALE_BAD_FOOD:
case MORALE_LOATHSOME_FOOD:
Assert( pSoldier );
HandleMoraleEventForSoldier( pSoldier, bMoraleEvent );
break;
+5 -1
View File
@@ -51,7 +51,11 @@ typedef enum
MORALE_MALICIOUS_HIT,
// added by Flugente
MORALE_KHORNATE_RANGED_COMBAT,
MORALE_FOOD,
MORALE_GOOD_FOOD,
MORALE_BAD_FOOD,
MORALE_LOATHSOME_FOOD,
NUM_MORALE_EVENTS
} MoraleEventNames;
+6
View File
@@ -36,6 +36,7 @@
#include "Soldier Control.h" // added by SANDRO
#include "opplist.h" // added by SANDRO
#include "lighting.h" // added by SANDRO
#include "Food.h" // added by Flugente
#endif
#include "connect.h"
//rain
@@ -795,6 +796,11 @@ void DeductPoints( SOLDIERTYPE *pSoldier, INT16 sAPCost, INT32 iBPCost, UINT8 ub
// NB: iBPCost > 0 - breath loss, iBPCost < 0 - breath gain
if (iBPCost)
{
// Flugente: if we GAIN breath points, adjust them - if we are hungry, we get fewer points back
// Flugente: ubMaxMorale can now be influenced by our food situation
if ( iBPCost < 0 && gGameOptions.fFoodSystem )
ReduceBPRegenForHunger(pSoldier, &iBPCost);
if (is_networked)
{
// Adjust breath changes due to spending or regaining of energy
+44 -25
View File
@@ -275,8 +275,8 @@ BOOLEAN gbCorpseValidForDecapitation[ NUM_CORPSES ] =
1,
1,
1,
0,
0,
1, // Bloodcat - changed to 1 to allow gutting
1, // Cow - changed to 1 to allow gutting
0,
0,
0,
@@ -1760,9 +1760,9 @@ INT32 FindNearestAvailableGridNoForCorpse( ROTTING_CORPSE_DEFINITION *pDef, INT8
BOOLEAN IsValidDecapitationCorpse( ROTTING_CORPSE *pCorpse )
{
if ( pCorpse->def.fHeadTaken )
if ( (pCorpse->def.usFlags & ROTTING_CORPSE_GUTTED) )
{
return( FALSE );
return( FALSE );
}
return( gbCorpseValidForDecapitation[ pCorpse->def.ubType ] );
@@ -1799,6 +1799,8 @@ void DecapitateCorpse( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel )
ROTTING_CORPSE *pCorpse;
ROTTING_CORPSE_DEFINITION CorpseDef;
UINT16 usHeadIndex = HEAD_1;
static UINT16 usBloodCatMeatIndex = 1566;
static UINT16 usCowMeatIndex = 1565;
pCorpse = GetCorpseAtGridNo( sGridNo, bLevel );
@@ -1814,10 +1816,27 @@ void DecapitateCorpse( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel )
// Copy corpse definition...
memcpy( &CorpseDef, &(pCorpse->def), sizeof( ROTTING_CORPSE_DEFINITION ) );
// Flugente: if the corpse is of a cow or a bloodcat, we don't take its head, we simply gut it and give meat instead of a head
if ( CorpseDef.ubType == BLOODCAT_DEAD || CorpseDef.ubType == COW_DEAD )
{
pCorpse->def.usFlags |= (ROTTING_CORPSE_HEAD_TAKEN|ROTTING_CORPSE_GUTTED);
if ( CorpseDef.ubType == BLOODCAT_DEAD )
{
if ( HasItemFlag(usBloodCatMeatIndex, MEAT_BLOODCAT) || GetFirstItemWithFlag(&usBloodCatMeatIndex, MEAT_BLOODCAT) )
usHeadIndex = usBloodCatMeatIndex;
}
else
{
if ( HasItemFlag(usCowMeatIndex, COW_MEAT) || GetFirstItemWithFlag(&usCowMeatIndex, COW_MEAT) )
usHeadIndex = usCowMeatIndex;
}
}
// Add new one...
CorpseDef.ubType = gDecapitatedCorpse[ CorpseDef.ubType ];
pCorpse->def.fHeadTaken = TRUE;
pCorpse->def.usFlags |= (ROTTING_CORPSE_HEAD_TAKEN|ROTTING_CORPSE_GUTTED);
if ( CorpseDef.ubType != 0 )
{
@@ -1858,11 +1877,14 @@ void DecapitateCorpse( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel )
}
CreateItem( usHeadIndex, 100, &gTempObject );
AddItemToPool( sGridNo, &gTempObject, INVISIBLE, 0, 0, 0 );
if ( usHeadIndex > 0 )
{
CreateItem( usHeadIndex, 100, &gTempObject );
AddItemToPool( sGridNo, &gTempObject, INVISIBLE, 0, 0, 0 );
// All teams lok for this...
NotifySoldiersToLookforItems( );
// All teams lok for this...
NotifySoldiersToLookforItems( );
}
}
}
@@ -2112,26 +2134,23 @@ UINT8 GetNearestRottingCorpseAIWarning( INT32 sGridNo )
// if zombies should spawn individually, roll for every corpse individually
if ( gGameExternalOptions.fZombieSpawnWaves || ( !gGameExternalOptions.fZombieSpawnWaves && (INT8) ( Random( 100 ) ) > 100 - gGameExternalOptions.sZombieRiseWaveFrequency ) )
{
if ( pCorpse->fActivated && pCorpse->def.fHeadTaken == FALSE ) // ... if corpse is active, and still has a head ...
if ( pCorpse->fActivated && !(pCorpse->def.usFlags & (ROTTING_CORPSE_HEAD_TAKEN|ROTTING_CORPSE_NEVER_RISE_AGAIN) ) ) // ... if corpse is active, and still has a head and can rise again...
{
if ( !(pCorpse->def.usFlags & ROTTING_CORPSE_NEVER_RISE_AGAIN) ) // ... if corpse is already that of a zombie, don't create zombie again ...
{
if ( !TileIsOutOfBounds(pCorpse->def.sGridNo) ) // ... if corpse is on existing coordinates ...
{
if ( WhoIsThere2( pCorpse->def.sGridNo, pCorpse->def.bLevel ) == NOBODY ) // ... if nobody else is on that position ...
{
UINT16 recanimstate = STANDING;
if ( !TileIsOutOfBounds(pCorpse->def.sGridNo) ) // ... if corpse is on existing coordinates ...
{
if ( WhoIsThere2( pCorpse->def.sGridNo, pCorpse->def.bLevel ) == NOBODY ) // ... if nobody else is on that position ...
{
UINT16 recanimstate = STANDING;
if ( CorpseOkToSpawnZombie( pCorpse, &recanimstate ) ) // ... a zombie can be created from this corpse, in the corresponding animstate ...
{
zombieshaverisen = TRUE;
CreateZombiefromCorpse( pCorpse, recanimstate );
if ( CorpseOkToSpawnZombie( pCorpse, &recanimstate ) ) // ... a zombie can be created from this corpse, in the corresponding animstate ...
{
zombieshaverisen = TRUE;
CreateZombiefromCorpse( pCorpse, recanimstate );
//++pSector->ubNumZombies;
//++pSector->ubZombiesInBattle;
//++pSector->ubNumZombies;
//++pSector->ubZombiesInBattle;
RemoveCorpse( cnt );
}
RemoveCorpse( cnt );
}
}
}
+17 -14
View File
@@ -73,22 +73,25 @@ enum RottingCorpseDefines
#define ROTTING_CORPSE_FIND_SWEETSPOT_FROM_GRIDNO 0x001 //Find the closest spot to the given gridno
#define ROTTING_CORPSE_USE_NORTH_ENTRY_POINT 0x002 //Find the spot closest to the north entry grid
#define ROTTING_CORPSE_USE_SOUTH_ENTRY_POINT 0x004 //Find the spot closest to the south entry grid
#define ROTTING_CORPSE_USE_EAST_ENTRY_POINT 0x008 //Find the spot closest to the east entry grid
#define ROTTING_CORPSE_USE_WEST_ENTRY_POINT 0x010 //Find the spot closest to the west entry grid
#define ROTTING_CORPSE_USE_CAMO_PALETTE 0x020 //We use cammo palette here....
#define ROTTING_CORPSE_VEHICLE 0x040 //Vehicle Corpse
#define ROTTING_CORPSE_USE_STEALTH_PALETTE 0x080 //We use stealth palette here....
#define ROTTING_CORPSE_USE_URBAN_CAMO_PALETTE 0x100 //We use urban palette here....
#define ROTTING_CORPSE_USE_DESERT_CAMO_PALETTE 0x200 //We use desert palette here....
#define ROTTING_CORPSE_USE_SNOW_CAMO_PALETTE 0x400 //We use snow palette here....
#define ROTTING_CORPSE_FIND_SWEETSPOT_FROM_GRIDNO 0x00000001 //Find the closest spot to the given gridno
#define ROTTING_CORPSE_USE_NORTH_ENTRY_POINT 0x00000002 //Find the spot closest to the north entry grid
#define ROTTING_CORPSE_USE_SOUTH_ENTRY_POINT 0x00000004 //Find the spot closest to the south entry grid
#define ROTTING_CORPSE_USE_EAST_ENTRY_POINT 0x00000008 //Find the spot closest to the east entry grid
#define ROTTING_CORPSE_USE_WEST_ENTRY_POINT 0x00000010 //Find the spot closest to the west entry grid
#define ROTTING_CORPSE_USE_CAMO_PALETTE 0x00000020 //We use cammo palette here....
#define ROTTING_CORPSE_VEHICLE 0x00000040 //Vehicle Corpse
#define ROTTING_CORPSE_USE_STEALTH_PALETTE 0x00000080 //We use stealth palette here....
#define ROTTING_CORPSE_USE_URBAN_CAMO_PALETTE 0x00000100 //We use urban palette here....
#define ROTTING_CORPSE_USE_DESERT_CAMO_PALETTE 0x00000200 //We use desert palette here....
#define ROTTING_CORPSE_USE_SNOW_CAMO_PALETTE 0x00000400 //We use snow palette here....
#ifdef ENABLE_ZOMBIES
#define ROTTING_CORPSE_NEVER_RISE_AGAIN 0x800 //a zombie cannot be created from this corpse (if not set, it'll eventually rise again )
#define ROTTING_CORPSE_NEVER_RISE_AGAIN 0x00000800 //a zombie cannot be created from this corpse (if not set, it'll eventually rise again )
#endif
// Flugente: corpses can now be gutted after they have been decapitated. Atm there is no corpse that can be both gutted and decapitated (to be done later)
#define ROTTING_CORPSE_HEAD_TAKEN 0x00001000 // head has been taken off
#define ROTTING_CORPSE_GUTTED 0x00001000 // corpse has been gutted
typedef struct
{
@@ -107,7 +110,7 @@ typedef struct
UINT8 ubDirection;
UINT32 uiTimeOfDeath;
UINT16 usFlags;
UINT32 usFlags;
INT8 bLevel;
@@ -117,7 +120,7 @@ typedef struct
BOOLEAN fHeadTaken;
UINT8 ubAIWarningValue;
UINT8 ubFiller[ 12 ];
UINT8 ubFiller[ 10 ]; // Flugente: 12 -> 10, because usFlags was cahnged from UINT16 to UINT32
#ifdef ENABLE_ZOMBIES
// Flugente: added name so we can display individual name if corpse gets resurrected...
+2
View File
@@ -18,6 +18,8 @@
#include "Soldier Control.h"
#endif
extern void ReducePointsForHunger( SOLDIERTYPE *pSoldier, UINT32 *pusPoints );
INT16 EffectiveStrength( SOLDIERTYPE *pSoldier, BOOLEAN fTrainer )
{
INT8 bBandaged;
+158 -3
View File
@@ -111,6 +111,7 @@
#include "Dialogue Control.h"
#include "IMP Skill Trait.h" // added by Flugente
#include "Food.h" // added by Flugente
//forward declarations of common classes to eliminate includes
class OBJECTTYPE;
@@ -1010,6 +1011,11 @@ SOLDIERTYPE& SOLDIERTYPE::operator=(const OLDSOLDIERTYPE_101& src)
this->bPoisonSum = 0;
this->bPoisonResistance = 0;
this->bPoisonAbsorption = 0;
this->bFoodLevel = 0;
this->bDrinkLevel = 0;
this->usStarveDamageHealth = 0;
this->usStarveDamageStrength = 0;
}
return *this;
}
@@ -13256,7 +13262,7 @@ void SOLDIERTYPE::SoldierInventoryCoolDown(void)
FLOAT cooldownfactor = GetItemCooldownFactor(pObj); // ... get cooldown factor ...
FLOAT newtemperature = max((FLOAT)0.0, temperature - cooldownfactor); // ... calculate new temperature ...
FLOAT newtemperature = max(0.0f, temperature - cooldownfactor); // ... calculate new temperature ...
(*pObj)[i]->data.bTemperature = newtemperature; // ... set new temperature
#if 0//def JA2TESTVERSION
@@ -13272,7 +13278,7 @@ void SOLDIERTYPE::SoldierInventoryCoolDown(void)
FLOAT cooldownfactor = GetItemCooldownFactor( &(*iter) ); // ... get cooldown factor ...
FLOAT newtemperature = max((FLOAT)0.0, temperature - cooldownfactor); // ... calculate new temperature ...
FLOAT newtemperature = max(0.0f, temperature - cooldownfactor); // ... calculate new temperature ...
(*iter)[i]->data.bTemperature = newtemperature; // ... set new temperature
#if 0//def JA2TESTVERSION
@@ -13289,7 +13295,7 @@ void SOLDIERTYPE::SoldierInventoryCoolDown(void)
}
}
// Flugente: determine if we can rest our weapon on something. This can only happen when STANDING/CROUCHED. As a result, we get superior handling modifiers (we apply the PRONE modfiers)
// Flugente: determine if we can rest our weapon on something. This ca6n only happen when STANDING/CROUCHED. As a result, we get superior handling modifiers (we apply the PRONE modfiers)
BOOLEAN SOLDIERTYPE::IsWeaponMounted( void )
{
BOOLEAN applybipod = FALSE;
@@ -13568,6 +13574,27 @@ INT16 SOLDIERTYPE::GetPoisonDamagePercentage( void )
return( val );
}
// add poison
void SOLDIERTYPE::AddPoison( INT8 sPoisonAmount )
{
if ( sPoisonAmount < 1 )
return;
INT8 oldpoisonsum = this->bPoisonSum;
this->bPoisonSum = min(this->bPoisonSum + sPoisonAmount, this->stats.bLifeMax);
// recalc really added poison
sPoisonAmount = max(0, this->bPoisonSum - oldpoisonsum);
INT8 oldpoisonlife = this->bPoisonLife;
this->bPoisonLife = min(this->bPoisonLife + sPoisonAmount, this->bPoisonSum);
INT8 poisontolife = max(0, this->bPoisonLife - oldpoisonlife);
INT8 oldpoisonbleed = this->bPoisonBleeding;
this->bPoisonBleeding = min(this->bPoisonBleeding + (sPoisonAmount - poisontolife), this->bPoisonSum);
}
// reset the extra stat variables
void SOLDIERTYPE::ResetExtraStats()
{
@@ -13642,6 +13669,33 @@ void SOLDIERTYPE::InventoryExplosion( void )
}
}
// Flugente: Food decay in inventory (once an hour)
void SOLDIERTYPE::SoldierInventoryFoodDecay(void)
{
if ( !gGameOptions.fFoodSystem )
return;
// one hour has 60 minutes, with 12 5-second-intervals (cooldown values are based on 5-second values)
FLOAT decaymod = 12*60*gGameExternalOptions.sFoodDecayModificator;
INT8 invsize = (INT8)this->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[this->inv[bLoop].usItem].foodtype > 0 ) // food decays
{
OBJECTTYPE * pObj = &(this->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 ...
{
(*pObj)[i]->data.bTemperature = max(0.0f, (*pObj)[i]->data.bTemperature - decaymod * Food[Item[pObj->usItem].foodtype].usDecayRate); // set new temperature
}
}
}
}
}
INT32 CheckBleeding( SOLDIERTYPE *pSoldier )
{
@@ -15742,6 +15796,11 @@ UINT16 NumberOfDamagedStats( SOLDIERTYPE * pSoldier )
if (pSoldier->ubCriticalStatDamage[cnt] > 0 )
ubTotalStatsDamaged += pSoldier->ubCriticalStatDamage[cnt];
}
// Flugente: stats can also be damaged
ubTotalStatsDamaged += pSoldier->usStarveDamageHealth;
ubTotalStatsDamaged += pSoldier->usStarveDamageStrength;
return( ubTotalStatsDamaged );
}
@@ -15874,6 +15933,102 @@ UINT8 RegainDamagedStats( SOLDIERTYPE * pSoldier, UINT16 usAmountRegainedHundred
}
}
// Flugente: Third, heal damage from starvation if possible
if ( !gGameOptions.fFoodSystem || ( gGameOptions.fFoodSystem && ubAmountRegained > 0 && pSoldier->bFoodLevel > FoodMoraleMods[FOOD_NORMAL].bThreshold && pSoldier->bDrinkLevel > FoodMoraleMods[FOOD_NORMAL].bThreshold ) )
{
// if we have a damaged stat here
if (pSoldier->usStarveDamageHealth > 0 )
{
if (ubAmountRegained >= pSoldier->usStarveDamageHealth)
{
// if the amount we can return is bigger than what we need, keep the rest, for other stats
usStatIncreasement = pSoldier->usStarveDamageHealth;
ubAmountRegained = max(0,(ubAmountRegained - usStatIncreasement));
pSoldier->usStarveDamageHealth = 0;
}
else
{
// if not having full amount, heal what we can
usStatIncreasement = ubAmountRegained;
ubAmountRegained = 0;
pSoldier->usStarveDamageHealth = max( 0, (pSoldier->usStarveDamageHealth - usStatIncreasement));
}
// so we can start regaining the stats
if ( usStatIncreasement > 0 )
{
bStatsReturned += usStatIncreasement; // keep value for feedback
sStat = sStatGainStrings[0]; // set string
pSoldier->stats.bLifeMax += usStatIncreasement;
pSoldier->stats.bLife += usStatIncreasement;
pSoldier->iHealableInjury -= (usStatIncreasement * 100); // don't forget the healable injury
if (pSoldier->iHealableInjury < 0)
pSoldier->iHealableInjury = 0;
if (pSoldier->stats.bLifeMax >= 100 || pSoldier->stats.bLife >= 100 ) // repair if going too far
{
pSoldier->stats.bLifeMax = 100;
pSoldier->stats.bLife = 100;
pSoldier->iHealableInjury = 0;
pSoldier->usStarveDamageHealth = 0;
}
gMercProfiles[ pSoldier->ubProfile ].bLifeMax = pSoldier->stats.bLifeMax; // update profile
// Throw a message if healed anything
if ( gSkillTraitValues.fDORepStShouldThrowMessage )
{
if ( usStatIncreasement == 1 )
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_REGAINED_ONE_POINTS_OF_STAT], pSoldier->name, sStat );
else
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_REGAINED_X_POINTS_OF_STATS], pSoldier->name, usStatIncreasement, sStat );
}
}
}
// if we have a damaged stat here
if (pSoldier->usStarveDamageStrength > 0 )
{
if (ubAmountRegained >= pSoldier->usStarveDamageStrength)
{
// if the amount we can return is bigger than what we need, keep the rest, for other stats
usStatIncreasement = pSoldier->usStarveDamageStrength;
ubAmountRegained = max(0,(ubAmountRegained - usStatIncreasement));
pSoldier->usStarveDamageStrength = 0;
}
else
{
// if not having full amount, heal what we can
usStatIncreasement = ubAmountRegained;
ubAmountRegained = 0;
pSoldier->usStarveDamageStrength = max( 0, (pSoldier->usStarveDamageStrength - usStatIncreasement));
}
// so we can start regaining the stats
if ( usStatIncreasement > 0 )
{
bStatsReturned += usStatIncreasement; // keep value for feedback
sStat = sStatGainStrings[9]; // set string
pSoldier->stats.bStrength += usStatIncreasement;
if (pSoldier->stats.bStrength >= 100 ) // repair if going too far
{
pSoldier->stats.bStrength = 100;
pSoldier->usStarveDamageStrength = 0;
}
gMercProfiles[ pSoldier->ubProfile ].bStrength = pSoldier->stats.bStrength; // update profile
// Throw a message if healed anything
if ( gSkillTraitValues.fDORepStShouldThrowMessage )
{
if ( usStatIncreasement == 1 )
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_REGAINED_ONE_POINTS_OF_STAT], pSoldier->name, sStat );
else
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_REGAINED_X_POINTS_OF_STATS], pSoldier->name, usStatIncreasement, sStat );
}
}
}
}
// Done, return what we healed
return( bStatsReturned );
}
+16
View File
@@ -21,6 +21,7 @@
#define PTR_PRONE (gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_PRONE)
#define DRUG_TYPE_MAX 32
#define FOOD_TYPE_MAX 128
// TEMP VALUES FOR NAMES
#define MAXCIVLASTNAMES 30
@@ -1213,6 +1214,15 @@ public:
INT32 bSoldierFlagMask; // for various soldier-related flags (Illusion, Kill streak, etc.). Easier than adding 32 bool variables
// Flugente: food system
INT32 bFoodLevel; // current level of food saturation
INT32 bDrinkLevel; // current level of drink saturation
UINT8 usStarveDamageHealth; // damage to health due to starvation. Can be cured by surgery, but only if nutrition level is high enough again
UINT8 usStarveDamageStrength; // damage to strength due to starvation. Can be cured by surgery, but only if nutrition level is high enough again
// Flugente: Decrease this filler by 1 for each new UINT8 / BOOLEAN variable, so we can maintain savegame compatibility!!
UINT8 ubFiller[20];
#ifdef JA2UB
//ja25
@@ -1415,11 +1425,17 @@ public:
// returns the poison percentage of the damage we will be doing with the weapon currently in our hand
INT16 GetPoisonDamagePercentage( void );
// add poison
void AddPoison( INT8 sPoisonAmount );
// reset the extra stat variables
void ResetExtraStats();
// Flugente: inventory bombs can ignite while in mapscreen. Workaround: Damage items and health
void InventoryExplosion( void );
// Flugente: Food decay in inventory
void SoldierInventoryFoodDecay( void );
//////////////////////////////////////////////////////////////////////////////
}; // SOLDIERTYPE;
+16 -1
View File
@@ -122,6 +122,7 @@
#include "IMP Skill Trait.h" // added by Flugente
#include "Food.h" // added by Flugente
//forward declarations of common classes to eliminate includes
class OBJECTTYPE;
@@ -4456,11 +4457,25 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
case '.':
if ( fCtrl )
{
SectorFillCanteens();
}
else if ( fAlt )
{
/*
// Flugente: spawn items while debugging
if ( gusSelectedSoldier != NOBODY )
{
static UINT16 usitem = 1560;
static INT16 status = 100;
static FLOAT temperature = 50000.0;
OBJECTTYPE newobj;
CreateItem( usitem, status, &newobj );
(newobj)[0]->data.bTemperature = temperature;
AddItemToPool( MercPtrs[ gusSelectedSoldier ]->sGridNo, &newobj, 1, 0, 0, -1 );
}*/
}
else
{
+3 -3
View File
@@ -11285,7 +11285,7 @@ void GunIncreaseHeat( OBJECTTYPE *pObj )
FLOAT singleshottemperature = GetSingleShotTemperature( pObj ); // ... get temperature rise ...
FLOAT newguntemperature = min(guntemperature + singleshottemperature, (FLOAT)(OVERHEATING_MAX_TEMPERATURE) ); // ... calculate new temperature ...
FLOAT newguntemperature = min(guntemperature + singleshottemperature, OVERHEATING_MAX_TEMPERATURE ); // ... calculate new temperature ...
(*pObj)[0]->data.bTemperature = newguntemperature; // ... apply new temperature
@@ -11353,7 +11353,7 @@ FLOAT GetGunOverheatJamPercentage( OBJECTTYPE * pObj )
FLOAT GetOverheatJamThreshold( OBJECTTYPE *pObj )
{
FLOAT jamthreshold = (FLOAT) (OVERHEATING_MAX_TEMPERATURE / 4.0);
FLOAT jamthreshold = OVERHEATING_MAX_TEMPERATURE / 4.0f;
if ( Item[pObj->usItem].usItemClass & (IC_GUN|IC_LAUNCHER) )
{
@@ -11379,7 +11379,7 @@ FLOAT GetOverheatJamThreshold( OBJECTTYPE *pObj )
FLOAT GetOverheatDamageThreshold( OBJECTTYPE *pObj )
{
FLOAT damagethreshold = (FLOAT) (OVERHEATING_MAX_TEMPERATURE / 4.0);
FLOAT damagethreshold = OVERHEATING_MAX_TEMPERATURE / 4.0f;
if ( Item[pObj->usItem].usItemClass & (IC_GUN|IC_LAUNCHER) )
{
+13
View File
@@ -28,6 +28,7 @@
#include "Quests.h"
#include "Soldier Profile.h"
#include "message.h"
#include "Food.h" // added by Flugente
#include "connect.h"
#endif
@@ -895,6 +896,18 @@ void CoolDownWorldItems( BOOLEAN fSetZero )
}
}
}
else if ( Item[gWorldItems[ uiCount ].object.usItem].foodtype > 0 ) // food decays
{
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 ...
{
(*pObj)[i]->data.bTemperature = max(0.0f, (*pObj)[i]->data.bTemperature - Food[Item[pObj->usItem].foodtype].usDecayRate); // set new temperature
}
}
}
}
}
}
+5
View File
@@ -66,6 +66,7 @@ typedef PARSE_STAGE;
#define ARMOURSFILENAME "Armours.xml"
#define EXPLOSIVESFILENAME "Explosives.xml"
#define DRUGSFILENAME "Drugs.xml"
#define FOODFILENAME "Food.xml"
#define AMMOFILENAME "AmmoStrings.xml"
#define AMMOTYPESFILENAME "AmmoTypes.xml"
#define INCOMPATIBLEATTACHMENTSFILENAME "IncompatibleAttachments.xml"
@@ -301,6 +302,10 @@ extern BOOLEAN WriteExplosiveStats();
extern BOOLEAN ReadInDrugsStats(STR fileName);
extern BOOLEAN WriteDrugsStats();
// Flugente: food
extern BOOLEAN ReadInFoodStats(STR fileName);
extern BOOLEAN WriteFoodStats();
extern BOOLEAN ReadInAmmoStats(STR fileName);
extern BOOLEAN WriteAmmoStats();
+245
View File
@@ -0,0 +1,245 @@
#ifdef PRECOMPILEDHEADERS
#include "Tactical All.h"
#else
#include "sgp.h"
#include "overhead.h"
#include "Food.h"
#include "Debug Control.h"
#include "expat.h"
#include "XML.h"
#endif
struct
{
PARSE_STAGE curElement;
CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1];
FOODTYPE curFood;
FOODTYPE * curArray;
UINT32 maxArraySize;
UINT32 currentDepth;
UINT32 maxReadDepth;
}
typedef foodParseData;
static void XMLCALL
foodStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts)
{
foodParseData * pData = (foodParseData *)userData;
if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element?
{
if(strcmp(name, "FOODSLIST") == 0 && pData->curElement == ELEMENT_NONE)
{
pData->curElement = ELEMENT_LIST;
memset(pData->curArray,0,sizeof(FOODTYPE)*pData->maxArraySize);
pData->maxReadDepth++; //we are not skipping this element
}
else if(strcmp(name, "FOOD") == 0 && pData->curElement == ELEMENT_LIST)
{
pData->curElement = ELEMENT;
memset(&pData->curFood,0,sizeof(FOODTYPE));
pData->maxReadDepth++; //we are not skipping this element
}
else if(pData->curElement == ELEMENT &&
(strcmp(name, "uiIndex") == 0 ||
strcmp(name, "szName") == 0 ||
strcmp(name, "bFoodPoints") == 0 ||
strcmp(name, "bDrinkPoints") == 0 ||
strcmp(name, "ubPortionSize") == 0 ||
strcmp(name, "bMoraleMod") == 0 ||
strcmp(name, "usDecayRate") == 0 ))
{
pData->curElement = ELEMENT_PROPERTY;
pData->maxReadDepth++; //we are not skipping this element
}
pData->szCharData[0] = '\0';
}
pData->currentDepth++;
}
static void XMLCALL
foodCharacterDataHandle(void *userData, const XML_Char *str, int len)
{
foodParseData * pData = (foodParseData *)userData;
if( (pData->currentDepth <= pData->maxReadDepth) &&
(strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH)
){
strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData)));
}
}
static void XMLCALL
foodEndElementHandle(void *userData, const XML_Char *name)
{
foodParseData * pData = (foodParseData *)userData;
if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading
{
if(strcmp(name, "FOODSLIST") == 0)
{
pData->curElement = ELEMENT_NONE;
}
else if(strcmp(name, "FOOD") == 0)
{
pData->curElement = ELEMENT_LIST;
// we do NOT want to read the first entry -> move stuff by 1
if(pData->curFood.uiIndex < pData->maxArraySize)
{
pData->curArray[pData->curFood.uiIndex] = pData->curFood; //write the food into the table
}
}
else if(strcmp(name, "uiIndex") == 0)
{
pData->curElement = ELEMENT;
pData->curFood.uiIndex = (UINT16) atol(pData->szCharData);
}
else if(strcmp(name, "szName") == 0)
{
pData->curElement = ELEMENT;
// not needed, but it's there for informational purposes
}
else if(strcmp(name, "bFoodPoints") == 0)
{
pData->curElement = ELEMENT;
pData->curFood.bFoodPoints = (INT32) atol(pData->szCharData);
}
else if(strcmp(name, "bDrinkPoints") == 0)
{
pData->curElement = ELEMENT;
pData->curFood.bDrinkPoints = (INT32) atol(pData->szCharData);
}
else if(strcmp(name, "ubPortionSize") == 0)
{
pData->curElement = ELEMENT;
pData->curFood.ubPortionSize = (UINT16) atol(pData->szCharData);
}
else if(strcmp(name, "bMoraleMod") == 0)
{
pData->curElement = ELEMENT;
pData->curFood.bMoraleMod = (UINT8) atol(pData->szCharData);
}
else if(strcmp(name, "usDecayRate") == 0)
{
pData->curElement = ELEMENT;
pData->curFood.usDecayRate = (FLOAT) atof(pData->szCharData);
}
pData->maxReadDepth--;
}
pData->currentDepth--;
}
BOOLEAN ReadInFoodStats(STR fileName)
{
HWFILE hFile;
UINT32 uiBytesRead;
UINT32 uiFSize;
CHAR8 * lpcBuffer;
XML_Parser parser = XML_ParserCreate(NULL);
foodParseData pData;
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading Food.xml" );
// Open foods file
hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE );
if ( !hFile )
return( FALSE );
uiFSize = FileGetSize(hFile);
lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1);
//Read in block
if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) )
{
MemFree(lpcBuffer);
return( FALSE );
}
lpcBuffer[uiFSize] = 0; //add a null terminator
FileClose( hFile );
XML_SetElementHandler(parser, foodStartElementHandle, foodEndElementHandle);
XML_SetCharacterDataHandler(parser, foodCharacterDataHandle);
memset(&pData,0,sizeof(pData));
pData.curArray = Food;
pData.maxArraySize = FOOD_TYPE_MAX;
XML_SetUserData(parser, &pData);
if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE))
{
CHAR8 errorBuf[511];
sprintf(errorBuf, "XML Parser Error in Food.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
LiveMessage(errorBuf);
MemFree(lpcBuffer);
return FALSE;
}
MemFree(lpcBuffer);
XML_ParserFree(parser);
return( TRUE );
}
BOOLEAN WriteFoodStats()
{
//DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"writefoodsstats");
HWFILE hFile;
//Debug code; make sure that what we got from the file is the same as what's there
// Open a new file
hFile = FileOpen( "TABLEDATA\\Food out.xml", FILE_ACCESS_WRITE | FILE_CREATE_ALWAYS, FALSE );
if ( !hFile )
return( FALSE );
{
UINT32 cnt;
FilePrintf(hFile,"<FOODSLIST>\r\n");
for(cnt = 0; cnt < MAXITEMS; ++cnt)
{
FilePrintf(hFile,"\t<FOOD>\r\n");
FilePrintf(hFile,"\t\t<uiIndex>%d</uiIndex>\r\n", cnt );
FilePrintf(hFile,"\t\t<bFoodPoints>%d</bFoodPoints>\r\n", Food[cnt].bFoodPoints );
FilePrintf(hFile,"\t\t<bDrinkPoints>%d</bDrinkPoints>\r\n", Food[cnt].bDrinkPoints );
FilePrintf(hFile,"\t\t<ubPortionSize>%d</ubPortionSize>\r\n", Food[cnt].ubPortionSize );
FilePrintf(hFile,"\t\t<bMoraleMod>%d</bMoraleMod>\r\n", Food[cnt].bMoraleMod );
FilePrintf(hFile,"\t\t<usDecayRate>%4.2f</usDecayRate>\r\n", Food[cnt].usDecayRate );
FilePrintf(hFile,"\t</FOOD>\r\n");
}
FilePrintf(hFile,"</FOODSLIST>\r\n");
}
FileClose( hFile );
return( TRUE );
}