/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando * * * * $Archive:: /Commando/Code/Combat/damage.cpp $* * * * $Author:: Greg_h $* * * * $Modtime:: 6/21/02 2:42p $* * * * $Revision:: 114 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* ** Includes */ #include "damage.h" #include "assets.h" #include "debug.h" #include "smartgameobj.h" #include "combat.h" #include "slist.h" #include "wwpacket.h" #include "playerdata.h" #include "soldier.h" #include "weapons.h" #include "diaglog.h" #include "playertype.h" #include #include #include "wwmath.h" #include "encoderlist.h" #include "bitpackids.h" #include "gametype.h" #include "csdamageevent.h" #ifdef WWDEBUG bool DefenseObjectClass::OneShotKills = false; #endif // _WWDEBUG ArmorWarheadManager ArmorManagerObject; DynamicVectorClass ArmorNames; DynamicVectorClass WarheadNames; DynamicVectorClass ArmorSaveIDs; DynamicVectorClass WarheadSaveIDs; DynamicVectorClass SoftArmorTable; // For each Warhead DynamicVectorClass SpecialDamageTypes; DynamicVectorClass SpecialDamageProbability; DynamicVectorClass VisceroidProbability; // For each special damage type int SpecialDamageWarhead[ArmorWarheadManager::NUM_SPECIAL_DAMAGE_TYPES]; float SpecialDamageDuration[ArmorWarheadManager::NUM_SPECIAL_DAMAGE_TYPES]; float SpecialDamageScale[ArmorWarheadManager::NUM_SPECIAL_DAMAGE_TYPES]; StringClass SpecialDamageExplosion[ArmorWarheadManager::NUM_SPECIAL_DAMAGE_TYPES]; // Skins that are imprevious to special damage DynamicVectorClass ImperviousSkins[ArmorWarheadManager::NUM_SPECIAL_DAMAGE_TYPES]; safe_float * ArmorWarheadManager::Multipliers = NULL; safe_float * ArmorWarheadManager::Absorbsion = NULL; #define ARMOR_INI_FILENAME "armor.ini" #define SECTION_SOFT_ARMOR_TYPES "Soft_Armor_Types" #define SECTION_HARD_ARMOR_TYPES "Hard_Armor_Types" #define SECTION_ARMOR_TYPES "Armor_Types" #define SECTION_WARHEAD_TYPES "Warhead_Types" #define SECTION_SCALE "Scale_%s" #define SECTION_SHIELD "Shield_%s" #define SECTION_ARMOR_SAVE_IDS "Armor_Save_IDs" #define SECTION_WARHEAD_SAVE_IDS "Warhead_Save_IDs" #define SECTION_SOFT_ARMOR "Soft_Armor" #define SECTION_SPECIAL_DAMAGE_TYPE "Special_Damage_Type" #define SECTION_SPECIAL_DAMAGE_PROBABILITY "Special_Damage_Probability" #define SECTION_VISCEROID_PROBABILITY "Visceroid_Probability" #define ENTRY_WARHEAD "Warhead" #define ENTRY_DURATION "Duration" #define ENTRY_SCALE "Scale" #define ENTRY_EXPLOSION "Explosion" /* ** */ void ArmorWarheadManager::Init( void ) { Shutdown(); INIClass * armorINI = Get_INI( ARMOR_INI_FILENAME ); if (armorINI != NULL) { WWASSERT( armorINI && armorINI->Section_Count() > 0 ); int count, entry; // Load Armor Types count = armorINI->Entry_Count( SECTION_ARMOR_TYPES ); for ( entry = 0; entry < count; entry++ ) { char entry_name[80]; armorINI->Get_String( SECTION_ARMOR_TYPES, armorINI->Get_Entry( SECTION_ARMOR_TYPES, entry), "", entry_name, sizeof( entry_name ) ); ArmorNames.Add( entry_name ); ArmorSaveIDs.Add( entry ); SoftArmorTable.Add( false ); } // Load Warhead Types count = armorINI->Entry_Count( SECTION_WARHEAD_TYPES ); for ( entry = 0; entry < count; entry++ ) { char entry_name[80]; armorINI->Get_String( SECTION_WARHEAD_TYPES, armorINI->Get_Entry( SECTION_WARHEAD_TYPES, entry), "", entry_name, sizeof( entry_name ) ); WarheadNames.Add( entry_name ); WarheadSaveIDs.Add( entry ); } int armor_num; // Load Armor Save IDs for ( armor_num = 0; armor_num < ArmorNames.Count(); armor_num++ ) { int id = armorINI->Get_Int( SECTION_ARMOR_SAVE_IDS, ArmorNames[armor_num], -100 ); if ( id == -100 ) { Debug_Say(( "Missing Armor_Save_ID for %s\n", ArmorNames[armor_num] )); } ArmorSaveIDs[ armor_num ] = id; } // Load Warhead Save IDs int warhead_num; for ( warhead_num = 0; warhead_num < WarheadNames.Count(); warhead_num++ ) { int id = armorINI->Get_Int( SECTION_WARHEAD_SAVE_IDS, WarheadNames[warhead_num], -100 ); if ( id == -100 ) { Debug_Say(( "Missing Warhead_Save_ID for %s\n", WarheadNames[warhead_num] )); } WarheadSaveIDs[ warhead_num ] = id; } // Load Soft Armor Table count = armorINI->Entry_Count( SECTION_SOFT_ARMOR ); for ( entry = 0; entry < count; entry++ ) { char entry_name[80]; armorINI->Get_String( SECTION_SOFT_ARMOR, armorINI->Get_Entry( SECTION_SOFT_ARMOR, entry), "", entry_name, sizeof( entry_name ) ); ArmorType type = Get_Armor_Type( entry_name ); SoftArmorTable[ type ] = true; } // Load Multipliers Multipliers = new safe_float[ Get_Num_Armor_Types() * Get_Num_Warhead_Types() ]; for ( armor_num = 0; armor_num < ArmorNames.Count(); armor_num++ ) { char section_name[80]; sprintf( section_name, SECTION_SCALE, ArmorNames[armor_num] ); for ( int warhead_num = 0; warhead_num < WarheadNames.Count(); warhead_num++ ) { Multipliers[ armor_num * Get_Num_Warhead_Types() + warhead_num ] = armorINI->Get_Float( section_name, WarheadNames[warhead_num], 1.0f ); } } // Load Shield Absorbsion Absorbsion = new safe_float[ Get_Num_Armor_Types() * Get_Num_Warhead_Types() ]; for ( armor_num = 0; armor_num < ArmorNames.Count(); armor_num++ ) { char section_name[80]; sprintf( section_name, SECTION_SHIELD, ArmorNames[armor_num] ); for ( int warhead_num = 0; warhead_num < WarheadNames.Count(); warhead_num++ ) { Absorbsion[ armor_num * Get_Num_Warhead_Types() + warhead_num ] = armorINI->Get_Float( section_name, WarheadNames[warhead_num], 0.0f ); } } // Load Warhead Special Damage for ( warhead_num = 0; warhead_num < WarheadNames.Count(); warhead_num++ ) { SpecialDamageTypes.Add( SPECIAL_DAMAGE_TYPE_NONE ); SpecialDamageProbability.Add( 0 ); VisceroidProbability.Add( 0 ); StringClass special_damage_type(0,true); armorINI->Get_String(special_damage_type, SECTION_SPECIAL_DAMAGE_TYPE, WarheadNames[warhead_num] ); if ( !special_damage_type.Is_Empty() ) { if ( !stricmp( special_damage_type, "FIRE" ) ) { SpecialDamageTypes[warhead_num] = SPECIAL_DAMAGE_TYPE_FIRE; } if ( !stricmp( special_damage_type, "CHEM" ) ) { SpecialDamageTypes[warhead_num] = SPECIAL_DAMAGE_TYPE_CHEM; } if ( !stricmp( special_damage_type, "CNC_FIRE" ) ) { SpecialDamageTypes[warhead_num] = SPECIAL_DAMAGE_TYPE_CNC_FIRE; } if ( !stricmp( special_damage_type, "CNC_CHEM" ) ) { SpecialDamageTypes[warhead_num] = SPECIAL_DAMAGE_TYPE_CNC_CHEM; } if ( !stricmp( special_damage_type, "ELECTRIC" ) ) { SpecialDamageTypes[warhead_num] = SPECIAL_DAMAGE_TYPE_ELECTRIC; } } SpecialDamageProbability[warhead_num] = armorINI->Get_Float( SECTION_SPECIAL_DAMAGE_PROBABILITY, WarheadNames[warhead_num], 0.0f ); VisceroidProbability[warhead_num] = armorINI->Get_Float( SECTION_VISCEROID_PROBABILITY, WarheadNames[warhead_num], 0.0f ); // Debug_Say(( "Warhead %s %d %f\n", WarheadNames[warhead_num], // SpecialDamageTypes[warhead_num], SpecialDamageProbability[warhead_num] )); } int i; for ( i = 0; i < NUM_SPECIAL_DAMAGE_TYPES; i++ ) { SpecialDamageWarhead[i] = 0; SpecialDamageDuration[i] = 0; SpecialDamageScale[i] = 0; SpecialDamageExplosion[i] = ""; } StringClass temp_string(0,true); for ( i = SPECIAL_DAMAGE_TYPE_FIRE; i < NUM_SPECIAL_DAMAGE_TYPES; i++ ) { const char * SpecialDamageSectionNames[NUM_SPECIAL_DAMAGE_TYPES] = { "Special_Damage_None", "Special_Damage_Fire", "Special_Damage_Chem", "Special_Damage_Electric", "Special_Damage_CNC_Fire", "Special_Damage_CNC_Chem", }; StringClass section(SpecialDamageSectionNames[i],true); SpecialDamageWarhead[i] = Get_Warhead_Type( armorINI->Get_String( temp_string, section, ENTRY_WARHEAD ) ); SpecialDamageDuration[i] = armorINI->Get_Float( section, ENTRY_DURATION, 0 ); SpecialDamageScale[i] = armorINI->Get_Float( section, ENTRY_SCALE, 1 ); SpecialDamageExplosion[i] = armorINI->Get_String( temp_string, section, ENTRY_EXPLOSION ); } // // Copy fire into super fire // SpecialDamageWarhead[SPECIAL_DAMAGE_TYPE_SUPER_FIRE] = SpecialDamageWarhead[SPECIAL_DAMAGE_TYPE_FIRE]; SpecialDamageDuration[SPECIAL_DAMAGE_TYPE_SUPER_FIRE] = SpecialDamageDuration[SPECIAL_DAMAGE_TYPE_FIRE]; SpecialDamageScale[SPECIAL_DAMAGE_TYPE_SUPER_FIRE] = SpecialDamageScale[SPECIAL_DAMAGE_TYPE_FIRE]; SpecialDamageExplosion[SPECIAL_DAMAGE_TYPE_SUPER_FIRE] = SpecialDamageExplosion[SPECIAL_DAMAGE_TYPE_FIRE]; // Load Impervious skin for ( int damage = SPECIAL_DAMAGE_TYPE_FIRE; damage < NUM_SPECIAL_DAMAGE_TYPES; damage++ ) { const char * ImperviousSectionNames[NUM_SPECIAL_DAMAGE_TYPES] = { "Impervious_None", "Impervious_Fire", "Impervious_Chem", "Impervious_Electric", "Impervious_CNC_Fire", "Impervious_CNC_Chem", }; StringClass section = ImperviousSectionNames[damage]; int count = armorINI->Entry_Count( section ); for ( entry = 0; entry < count; entry++ ) { StringClass entry_name = armorINI->Get_String( temp_string, section, armorINI->Get_Entry( section, entry) ); if ( !entry_name.Is_Empty() ) { ImperviousSkins[damage].Add( Get_Armor_Type( entry_name ) ); } } // for ( int i = 0; i < ImperviousSkins[damage].Count(); i++ ) { // Debug_Say(( "Loaded %s %d\n", (const char *)section, ImperviousSkins[damage][i] )); // } } Release_INI( armorINI ); } else { Debug_Say(("ArmorWarheadManager::Init - Unable to load %s\n", ARMOR_INI_FILENAME)); } } void ArmorWarheadManager::Shutdown( void ) { if ( Multipliers != NULL ) { delete [] Multipliers; Multipliers = NULL; } if ( Absorbsion != NULL ) { delete [] Absorbsion; Absorbsion = NULL; } ArmorNames.Delete_All(); WarheadNames.Delete_All(); ArmorSaveIDs.Delete_All(); WarheadSaveIDs.Delete_All(); SoftArmorTable.Delete_All(); SpecialDamageTypes.Delete_All(); SpecialDamageProbability.Delete_All(); VisceroidProbability.Delete_All(); for (int i=0; i= 0 ); WWASSERT( (int)type < Get_Num_Armor_Types() ); return ArmorNames[type]; } const char * ArmorWarheadManager::Get_Warhead_Name( WarheadType type ) { WWASSERT( (int)type >= 0 ); WWASSERT( (int)type < Get_Num_Warhead_Types() ); return WarheadNames[type]; } float ArmorWarheadManager::Get_Damage_Multiplier( ArmorType armor, WarheadType warhead ) { WWASSERT( (int)armor >= 0 ); WWASSERT( (int)armor < Get_Num_Armor_Types() ); WWASSERT( (int)warhead >= 0 ); WWASSERT( (int)warhead < Get_Num_Warhead_Types() ); return( Multipliers[ (unsigned int)armor * Get_Num_Warhead_Types() + (unsigned int)warhead ] ); /* int index = armor * Get_Num_Warhead_Types() + warhead; float multiplier = Multipliers[index]; WWASSERT(WWMath::Is_Valid_Float(multiplier)); Debug_Say(("index = %d, armor = %d, warhead = %d, multiplier = %5.2f\n", index, armor, warhead, multiplier)); return multiplier; */ } float ArmorWarheadManager::Get_Shield_Absorbsion( ArmorType armor, WarheadType warhead ) { WWASSERT( (int)armor >= 0 ); WWASSERT( (int)armor < Get_Num_Armor_Types() ); WWASSERT( (int)warhead >= 0 ); WWASSERT( (int)warhead < Get_Num_Warhead_Types() ); return( Absorbsion[ (unsigned int)armor * Get_Num_Warhead_Types() + (unsigned int)warhead ] ); } bool ArmorWarheadManager::Is_Armor_Soft( ArmorType armor ) { return SoftArmorTable[armor]; } int ArmorWarheadManager::Get_Armor_Save_ID( ArmorType type ) { return ArmorSaveIDs[ type ]; } ArmorType ArmorWarheadManager::Find_Armor_Save_ID( int id ) { for ( int index = 0; index < ArmorSaveIDs.Count(); index++ ) { if ( ArmorSaveIDs[ index ] == id ) { return index; } } //Debug_Say(( "FAILED to find ARMOR_SAVE_ID for %d\n", id )); return 0; } int ArmorWarheadManager::Get_Warhead_Save_ID( WarheadType type ) { return WarheadSaveIDs[ type ]; } ArmorType ArmorWarheadManager::Find_Warhead_Save_ID( int id ) { for ( int index = 0; index < WarheadSaveIDs.Count(); index++ ) { if ( WarheadSaveIDs[ index ] == id ) { return index; } } Debug_Say(( "FAILED to find WARHEAD_SAVE_ID for %d\n", id )); return 0; } ArmorWarheadManager::SpecialDamageType ArmorWarheadManager::Get_Special_Damage_Type( WarheadType type ) { WWASSERT( (int)type >= 0 ); WWASSERT( (int)type < Get_Num_Warhead_Types() ); return (SpecialDamageType)SpecialDamageTypes[type]; } float ArmorWarheadManager::Get_Special_Damage_Probability( WarheadType type ) { WWASSERT( (int)type >= 0 ); WWASSERT( (int)type < Get_Num_Warhead_Types() ); return SpecialDamageProbability[type]; } WarheadType ArmorWarheadManager::Get_Special_Damage_Warhead( SpecialDamageType type ) { WWASSERT( (int)type >= 0 ); WWASSERT( (int)type < NUM_SPECIAL_DAMAGE_TYPES ); return SpecialDamageWarhead[type]; } float ArmorWarheadManager::Get_Special_Damage_Duration( SpecialDamageType type ) { WWASSERT( (int)type >= 0 ); WWASSERT( (int)type < NUM_SPECIAL_DAMAGE_TYPES ); return SpecialDamageDuration[type]; } float ArmorWarheadManager::Get_Special_Damage_Scale( SpecialDamageType type ) { WWASSERT( (int)type >= 0 ); WWASSERT( (int)type < NUM_SPECIAL_DAMAGE_TYPES ); return SpecialDamageScale[type]; } const char * ArmorWarheadManager::Get_Special_Damage_Explosion( SpecialDamageType type ) { WWASSERT( (int)type >= 0 ); WWASSERT( (int)type < NUM_SPECIAL_DAMAGE_TYPES ); return SpecialDamageExplosion[type]; } float ArmorWarheadManager::Get_Visceroid_Probability( WarheadType type ) { WWASSERT( (int)type >= 0 ); WWASSERT( (int)type < Get_Num_Warhead_Types() ); return VisceroidProbability[type]; } bool ArmorWarheadManager::Is_Skin_Impervious( SpecialDamageType type, ArmorType skin ) { WWASSERT( (int)type >= 0 ); WWASSERT( (int)type < NUM_SPECIAL_DAMAGE_TYPES ); for ( int i = 0; i < ImperviousSkins[type].Count(); i++ ) { if ( ImperviousSkins[type][i] == skin ) { return true; } } return false; } /* ** */ OffenseObjectClass::OffenseObjectClass( const OffenseObjectClass & base ) : Damage( base.Damage ), Warhead( base.Warhead ) { Set_Owner( base.Get_Owner() ); } /* ** Save and Load */ enum { CHUNKID_VARIABLES = 914991020, CHUNKID_OWNER, MICROCHUNKID_WARHEAD = 1, MICROCHUNKID_DAMAGE, MICROCHUNKID_HEALTH, MICROCHUNKID_HEALTH_MAX, MICROCHUNKID_SKIN, MICROCHUNKID_SHIELD_STRENGTH, MICROCHUNKID_SHIELD_STRENGTH_MAX, MICROCHUNKID_SHIELD_TYPE, XXXMICROCHUNKID_LAST_HEALTH, XXXMICROCHUNKID_LAST_SKIN, XXXMICROCHUNKID_LAST_SHIELD_STRENGTH, XXXMICROCHUNKID_LAST_SHIELD_TYPE, MICROCHUNKID_DAMAGE_POINTS, MICROCHUNKID_DEATH_POINTS, }; bool OffenseObjectClass::Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_VARIABLES ); int save_id = ArmorWarheadManager::Get_Warhead_Save_ID( Warhead ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WARHEAD, save_id ); WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_DAMAGE, Damage, float ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_OWNER ); Owner.Save( csave ); csave.End_Chunk(); return true; } bool OffenseObjectClass::Load( ChunkLoadClass &cload ) { int save_id = -2; while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_WARHEAD, save_id ); READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_DAMAGE, Damage, float); default: Debug_Say(( "Unrecognized Offense Variable chunkID\n" )); break; } cload.Close_Micro_Chunk(); } break; case CHUNKID_OWNER: Owner.Load( cload ); break; default: Debug_Say(( "Unrecognized Offense chunkID\n" )); break; } cload.Close_Chunk(); } Warhead = ArmorWarheadManager::Find_Warhead_Save_ID( save_id ); return true; } void DefenseObjectClass::Init( const DefenseObjectDefClass & def, DamageableGameObj * owner ) { Health = def.Health; HealthMax = def.HealthMax; Skin = def.Skin; ShieldStrength = def.ShieldStrength; ShieldStrengthMax = def.ShieldStrengthMax; ShieldType = def.ShieldType; DamagePoints = def.DamagePoints; DeathPoints = def.DeathPoints; Set_Owner( owner ); } bool DefenseObjectClass::Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_HEALTH, Health, float ); WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_HEALTH_MAX, HealthMax, float ); int skin_save_id = ArmorWarheadManager::Get_Armor_Save_ID( Skin ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_SKIN, skin_save_id ); WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_SHIELD_STRENGTH, ShieldStrength, float ); WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_SHIELD_STRENGTH_MAX, ShieldStrengthMax, float ); int shield_save_id = ArmorWarheadManager::Get_Armor_Save_ID( ShieldType ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_SHIELD_TYPE, shield_save_id ); WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_DAMAGE_POINTS, DamagePoints, float ); WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_DEATH_POINTS, DeathPoints, float ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_OWNER ); Owner.Save( csave ); csave.End_Chunk(); return true; } bool DefenseObjectClass::Load( ChunkLoadClass &cload ) { int skin_save_id = -2; int shield_save_id = -2; while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_HEALTH, Health, float ); READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_HEALTH_MAX, HealthMax, float ); READ_MICRO_CHUNK( cload, MICROCHUNKID_SKIN, skin_save_id ); READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_SHIELD_STRENGTH, ShieldStrength, float ); READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_SHIELD_STRENGTH_MAX, ShieldStrengthMax, float ); READ_MICRO_CHUNK( cload, MICROCHUNKID_SHIELD_TYPE, shield_save_id ); READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_DAMAGE_POINTS, DamagePoints, float ); READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_DEATH_POINTS, DeathPoints, float ); default: Debug_Say(( "Unrecognized Defense Variable chunkID\n" )); break; } cload.Close_Micro_Chunk(); } break; case CHUNKID_OWNER: Owner.Load( cload ); break; default: Debug_Say(( "Unrecognized Defense chunkID\n" )); break; } cload.Close_Chunk(); } Skin = ArmorWarheadManager::Find_Armor_Save_ID( skin_save_id ); ShieldType = ArmorWarheadManager::Find_Armor_Save_ID( shield_save_id ); return true; } bool DefenseObjectClass::Is_Soft( void ) { if ( !ArmorWarheadManager::Is_Armor_Soft( Skin ) ) { return false; } if ( ( (float)ShieldStrength > 0 ) && ( !ArmorWarheadManager::Is_Armor_Soft( ShieldType ) ) ) { return false; } return true; } /* ** */ float DefenseObjectClass::Apply_Damage( const OffenseObjectClass & offense, float scale, int alternate_skin ) { #if 1 // If singleplay or non trusted clients, just apply damage normally, if server bool normal = IS_SOLOPLAY || !cCsDamageEvent::Get_Are_Clients_Trusted(); DamageableGameObj * owner = Get_Owner(); // Buildings have no owner if ( owner == NULL || owner->As_BuildingGameObj() ) { normal = true; } if ( offense.ForceServerDamage || !offense.EnableClientDamage ) { normal = true; } if ( normal ) { if ( CombatManager::I_Am_Server() ) { return Do_Damage( offense, scale, alternate_skin ); } else { return Health; } } // Client Damage // If I am a client, and damager is my guy, tell the server that damage should apply // If I am a server, and the damager has a client, ignore the damage bool has_client = false; bool my_client = false; if ( offense.Get_Owner() != NULL && offense.Get_Owner()->As_SmartGameObj() ) { int control_owner = offense.Get_Owner()->As_SmartGameObj()->Get_Control_Owner(); has_client = control_owner > 0; my_client = control_owner == CombatManager::Get_My_Id(); } if ( CombatManager::I_Am_Server() ) { if ( my_client || !has_client ) { return Do_Damage( offense, scale, alternate_skin ); } else { return Health; // assume we will be notified by the owner Don't apply the damage } } // I am a client. If this is my object, request damage if ( my_client ) { Request_Damage( offense, scale ); } return Health; // Don't apply the damage #else // Client Authorative damage is no longer done here. if ( CombatManager::I_Am_Server() ) { return Do_Damage( offense, scale, alternate_skin ); } else { return Health; } #endif } /* ** */ void DefenseObjectClass::Request_Damage( const OffenseObjectClass & offense, float scale ) { WWASSERT(CombatManager::I_Am_Only_Client()); WWASSERT(cCsDamageEvent::Get_Are_Clients_Trusted() == true); cCsDamageEvent * event = new cCsDamageEvent(); if ( event ) { int damager_id = 0; if ( offense.Get_Owner() ) { damager_id = offense.Get_Owner()->Get_ID(); } int damagee_id = 0; if ( Get_Owner() ) { damagee_id = Get_Owner()->Get_ID(); } int warhead = offense.Get_Warhead(); float damage = offense.Get_Damage() * scale; event->Init( damager_id, damagee_id, damage, warhead ); // Debug_Say(( "Requesting damage of %f from %d to %d\n", damage, damager_id, damagee_id )); } } /* ** */ float DefenseObjectClass::Do_Damage( const OffenseObjectClass & offense, float scale, int alternate_skin ) { SmartGameObj * smart = NULL; if ( offense.Get_Owner() != NULL ) { smart = offense.Get_Owner()->As_SmartGameObj(); } //TSS102201 // // Keep track of who is attempting to damage who. This is used as a server // heuristic used in network priority calculations. It doesn't matter whether // or not damage is actually done. // int damager_id = -1; int damagee_id = -1; if (offense.Get_Owner() != NULL) { damager_id = offense.Get_Owner()->Get_Network_ID(); } if (Get_Owner() != NULL) { damagee_id = Get_Owner()->Get_Network_ID(); } if (offense.Get_Owner() != NULL && damagee_id != -1) { offense.Get_Owner()->Set_Last_Object_Id_I_Damaged(damagee_id); } if (Get_Owner() != NULL && damager_id != -1) { Get_Owner()->Set_Last_Object_Id_I_Got_Damaged_By(damager_id); } float damage = offense.Get_Damage() * scale; // Scale damage by difficulty if star is applying if ( IS_MISSION && offense.Get_Owner() == COMBAT_STAR ) { switch ( CombatManager::Get_Difficulty_Level() ) { case 0: damage *= 2.0f; break; case 1: damage *= 1.333f; break; case 2: damage *= 1.0f; break; }; } float shield_damage = 0; float damage_scale = 1; float shield_damage_scale = 1; if (alternate_skin != -1) { damage_scale = ArmorWarheadManager::Get_Damage_Multiplier( alternate_skin, offense.Get_Warhead() ); shield_damage_scale = 0; } else { damage_scale = ArmorWarheadManager::Get_Damage_Multiplier( Skin, offense.Get_Warhead() ); shield_damage_scale = ArmorWarheadManager::Get_Damage_Multiplier( ShieldType, offense.Get_Warhead() ); } // check for punish = no more damage if ( smart != NULL && smart->Get_Player_Data() ) { #define PUNISH_DELAY 60 if ( smart->Get_Player_Data()->Get_Punish_Timer() > PUNISH_DELAY ) { damage_scale = 0; shield_damage_scale = 0; smart->Get_Player_Data()->Set_Money( 0 ); smart->Get_Player_Data()->Set_Score( 0 ); } } bool is_repair = false; // check for repair on either health of shield // Note: humans don't repair health, but vehicles do. if ( (damage * damage_scale < 0) || (damage * shield_damage_scale < 0) ) { // We are repairing is_repair = true; Mark_Owner_Dirty(); // Apply first to health, then to shield if ( Health < HealthMax && (damage_scale != 0) ) { damage *= damage_scale; float min_damage = (float)Health - (float)HealthMax; damage = WWMath::Clamp( damage, min_damage, 0 ); //Debug_Say(( "Repairing %f health\n", damage )); Health = (float)Health - damage; } else { damage *= shield_damage_scale; shield_damage = damage; damage = 0; float min_shield_damage = (float)ShieldStrength - (float)ShieldStrengthMax; shield_damage = WWMath::Clamp( shield_damage, min_shield_damage, 0 ); //Debug_Say(( "Repairing %f shield\n", shield_damage )); ShieldStrength = (float)ShieldStrength - shield_damage; } } else { if ( smart && Get_Owner() && (smart != Get_Owner() ) && smart->Is_Teammate( Get_Owner() ) ) { // This is friendly fire!! if ( !CombatManager::Is_Friendly_Fire_Permitted() ) { // damage = 0; return Health; } } if ( damage != 0 ) { Mark_Owner_Dirty(); } // if we have a shield, redirect a fraction of our damage; // If alternate skin (MCT) ignore sheild damage; if ( (float)ShieldStrength > 0.0f && alternate_skin == -1 ) { shield_damage = damage * ArmorWarheadManager::Get_Shield_Absorbsion( ShieldType, offense.Get_Warhead() ); damage -= shield_damage; shield_damage *= shield_damage_scale; float shield_damage_to_apply = shield_damage; // how much scaled damage to apply if ( shield_damage > (float)ShieldStrength ) { shield_damage = ShieldStrength; } ShieldStrength = (float)ShieldStrength - shield_damage; if ( shield_damage_scale != 0 ) { shield_damage /= shield_damage_scale; // how much un scaled damage did we apply shield_damage_to_apply /= shield_damage_scale; // how much scaled damage did we try to apply } damage += shield_damage_to_apply - shield_damage; // Clamp Shield Strength ShieldStrength = WWMath::Clamp( (float)ShieldStrength, 0, (float)ShieldStrengthMax ); } // scale the (remaining) damage // (gth) added alternate_skin feature which is used by buildings when their MCT is being damaged. damage *= damage_scale; if ( (float)Health < damage ) { damage = Health; } #ifdef WWDEBUG // // TSS090601 // if (OneShotKills) { damage = Health; } #endif // _WWDEBUG Health = (float)Health - damage; // // Don't allow this object to die (if necessary) // if ( CanObjectDie == false ) { Health = max ( (float)Health, 1.0F ); } } if ( COMBAT_STAR != NULL && // dedicated server test Get_Owner() == COMBAT_STAR ) { Vector3 pos; Get_Owner()->Get_Position( &pos ); int hitter_id = 0; const char * weapon_name = ""; if ( offense.Get_Owner() ) { hitter_id = offense.Get_Owner()->Get_ID(); if ( offense.Get_Owner()->Get_Weapon() ) { weapon_name = offense.Get_Owner()->Get_Weapon()->Get_Definition()->Get_Name(); } } float points = damage; float armor = Get_Shield_Strength(); DIAG_LOG(( "DRCV", "%s; %d; %1.2f; %1.2f; %1.2f; %1.2f; %1.2f; %1.2f", weapon_name, hitter_id, points, armor, Health, pos.X, pos.Y, pos.Z )); } if (( smart == COMBAT_STAR ) && ( smart != NULL )) { Vector3 pos; smart->Get_Position( &pos ); const char * weapon_name = ""; int ammo = 0; if ( smart->Get_Weapon() ) { weapon_name = smart->Get_Weapon()->Get_Definition()->Get_Name(); ammo = smart->Get_Weapon()->Get_Total_Rounds(); } int hittee_id = 0; Vector3 victim_pos(0,0,0); const char * team_name = ""; if ( Get_Owner() ) { hittee_id = Get_Owner()->Get_ID(); Get_Owner()->Get_Position( &victim_pos ); team_name = Player_Type_Name( Get_Owner()->Get_Player_Type() ); } float points = damage; float armor = Get_Shield_Strength(); DIAG_LOG(( "DEFC", "%1.2f; %1.2f; %1.2f; %s; %d; %d; %1.2f; %1.2f; %1.2f; %1.2f; %1.2f; %1.2f; %s ", pos.X, pos.Y, pos.Z, weapon_name, ammo, hittee_id, points, armor, Health, victim_pos.X, victim_pos.Y, victim_pos.Z, team_name )); } // Clamp Health to Max Health = WWMath::Clamp( (float)Health, 0, (float)HealthMax ); if ( damage > 0 && (float)Health <= 0 ) { int victim_id = 0; Vector3 victim_pos(0,0,0); if ( Get_Owner() != NULL ) { victim_id = Get_Owner()->Get_ID(); Get_Owner()->Get_Position( &victim_pos ); } int killer_id = 0; Vector3 killer_pos(0,0,0); const char * weapon_name = ""; if ( smart != NULL ) { killer_id = smart->Get_ID(); smart->Get_Position( &killer_pos ); if ( smart->Get_Weapon() ) { weapon_name = smart->Get_Weapon()->Get_Definition()->Get_Name(); } } DIAG_LOG(( "OBDE", "%d; %1.2f; %1.2f; %1.2f; %d; %1.2f; %1.2f; %1.2f; %s", victim_id, victim_pos.X, victim_pos.Y, victim_pos.Z, killer_id, killer_pos.X, killer_pos.Y, killer_pos.Z, weapon_name )); if ( Get_Owner() == COMBAT_STAR ) { DIAG_LOG(( "STDE", "%1.2f; %1.2f; %1.2f; %d; %1.2f; %1.2f; %1.2f; %s", victim_pos.X, victim_pos.Y, victim_pos.Z, killer_id, killer_pos.X, killer_pos.Y, killer_pos.Z, weapon_name )); } } if (damage > 0 && (float) Health <= 0 && smart != NULL && smart->As_SoldierGameObj() != NULL && Get_Owner() != NULL && Get_Owner()->As_SoldierGameObj() != NULL) { CombatManager::On_Soldier_Kill(smart->As_SoldierGameObj(), Get_Owner()->As_SoldierGameObj()); } // Apply Points for damage/death if ( smart != NULL && smart->Get_Player_Data() ) { float points_dir = 1; // If same team, make the points negative if ( Get_Owner() ) { if ( smart->Is_Teammate( Get_Owner() ) ) { points_dir = -1; } else if ( !smart->Is_Enemy( Get_Owner() ) ) { // No points for neutrals points_dir = 0; } } if (offense.Get_Owner() != Get_Owner()) { float points = (float)DamagePoints; if ( is_repair ) { // half points for repair points *= 0.5f; } smart->Get_Player_Data()->Apply_Damage_Points( points_dir * (damage + shield_damage) * points, Get_Owner() ); } if ( damage > 0 && (float)Health <= 0 ) { if (offense.Get_Owner() != Get_Owner()) { smart->Get_Player_Data()->Apply_Death_Points( points_dir * (float)DeathPoints, Get_Owner() ); } } } return Health; } bool DefenseObjectClass::Is_Repair( const OffenseObjectClass & offense, float scale ) { float damage = offense.Get_Damage() * scale; float damage_scale = ArmorWarheadManager::Get_Damage_Multiplier( Skin, offense.Get_Warhead() ); float shield_damage_scale = ArmorWarheadManager::Get_Damage_Multiplier( ShieldType, offense.Get_Warhead() ); // check for repair on either health of shield return ( (damage * damage_scale < 0) || (damage * shield_damage_scale < 0) ); } bool DefenseObjectClass::Would_Damage( const OffenseObjectClass & offense, float scale ) { float damage = offense.Get_Damage() * scale; float damage_scale = ArmorWarheadManager::Get_Damage_Multiplier( Skin, offense.Get_Warhead() ); float shield_damage_scale = ArmorWarheadManager::Get_Damage_Multiplier( ShieldType, offense.Get_Warhead() ); SmartGameObj * smart = NULL; if ( offense.Get_Owner() != NULL ) { smart = offense.Get_Owner()->As_SmartGameObj(); } if ( smart && Get_Owner() && smart->Is_Teammate( Get_Owner() ) && (smart != Get_Owner() ) ) { // This is friendly fire!! if ( !CombatManager::Is_Friendly_Fire_Permitted() ) { return false; } } if ( damage * damage_scale > 0 && (float)Health > 0 ) { return true; } if ( damage * shield_damage_scale > 0 && (float)ShieldStrength > 0 ) { return true; } return false; } void DefenseObjectClass::Import(BitStreamClass & packet) { bool is_health_zero = packet.Get(is_health_zero); int health = packet.Get(health, BITPACK_HEALTH); int shield_strength = packet.Get(shield_strength, BITPACK_SHIELD_STRENGTH); unsigned int shield_type; packet.Get(shield_type, BITPACK_SHIELD_TYPE); ShieldType = shield_type; Health = (float)health; ShieldStrength = (float)shield_strength; if (is_health_zero) { Health = 0; } else { Health = WWMath::Max(Health, 0.01f); } //WWASSERT(WWMath::Is_Valid_Float(ShieldStrength)); //WWASSERT(packet.Is_Flushed()); } void DefenseObjectClass::Export(BitStreamClass & packet) { // // N.B. The funky syntax is to prevent datasafe asserts // int health = cMathUtil::Round((double) (float)Health); int shield_strength = cMathUtil::Round((double) (float)ShieldStrength); //TSS012202 //packet.Add((bool)(health == 0)); packet.Add((bool)(((float)Health) == 0)); packet.Add(health, BITPACK_HEALTH); packet.Add(shield_strength, BITPACK_SHIELD_STRENGTH); packet.Add((unsigned long)ShieldType, BITPACK_SHIELD_TYPE); //LastSentHealth = Health; //LastSentSkin = Skin; //LastSentShieldStrength = ShieldStrength; //LastSentShieldType = ShieldType; } /* // // This was only valid with 1 client. We would need dirty tests for every client. // bool DefenseObjectClass::Is_Defense_State_Dirty(void) { return ( fabs(Health - LastSentHealth) > MISCUTIL_EPSILON || Skin != LastSentSkin || fabs(ShieldStrength - LastSentShieldStrength) > MISCUTIL_EPSILON || ShieldType != LastSentShieldType); } */ /***************************************************************************************** ** ** DefenseObjectDefClass Implementation ** *****************************************************************************************/ enum { DEFENSEOBJECTDEF_CHUNK_VARIABLES = 7311607, DEFENSEOBJECTDEF_VARIABLE_HEALTH = 0x00, DEFENSEOBJECTDEF_VARIABLE_HEALTHMAX, DEFENSEOBJECTDEF_VARIABLE_SKIN, DEFENSEOBJECTDEF_VARIABLE_SHIELDSTRENGTH, DEFENSEOBJECTDEF_VARIABLE_SHIELDSTRENGTHMAX, DEFENSEOBJECTDEF_VARIABLE_SHIELDTYPE, DEFENSEOBJECTDEF_VARIABLE_DAMAGE_POINTS, DEFENSEOBJECTDEF_VARIABLE_DEATH_POINTS, }; DefenseObjectDefClass::DefenseObjectDefClass(void) : Health( 100.0f ), HealthMax( 100.0f ), Skin( 0 ), ShieldStrength( 0 ), ShieldStrengthMax( 0 ), ShieldType( 0 ), DamagePoints( 0 ), DeathPoints( 0 ) { } DefenseObjectDefClass::~DefenseObjectDefClass(void) { } bool DefenseObjectDefClass::Save(ChunkSaveClass &csave) { csave.Begin_Chunk(DEFENSEOBJECTDEF_CHUNK_VARIABLES); WRITE_SAFE_MICRO_CHUNK(csave,DEFENSEOBJECTDEF_VARIABLE_HEALTH, Health, float); WRITE_SAFE_MICRO_CHUNK(csave,DEFENSEOBJECTDEF_VARIABLE_HEALTHMAX,HealthMax, float); int skin_save_id = ArmorWarheadManager::Get_Armor_Save_ID( Skin ); WRITE_MICRO_CHUNK(csave,DEFENSEOBJECTDEF_VARIABLE_SKIN,skin_save_id); WRITE_SAFE_MICRO_CHUNK(csave,DEFENSEOBJECTDEF_VARIABLE_SHIELDSTRENGTH, ShieldStrength, float); WRITE_SAFE_MICRO_CHUNK(csave,DEFENSEOBJECTDEF_VARIABLE_SHIELDSTRENGTHMAX, ShieldStrengthMax, float); int shield_save_id = ArmorWarheadManager::Get_Armor_Save_ID( ShieldType ); WRITE_MICRO_CHUNK(csave,DEFENSEOBJECTDEF_VARIABLE_SHIELDTYPE,shield_save_id); WRITE_SAFE_MICRO_CHUNK(csave,DEFENSEOBJECTDEF_VARIABLE_DAMAGE_POINTS,DamagePoints, float); WRITE_SAFE_MICRO_CHUNK(csave,DEFENSEOBJECTDEF_VARIABLE_DEATH_POINTS,DeathPoints, float); csave.End_Chunk(); return true; } bool DefenseObjectDefClass::Load(ChunkLoadClass &cload) { int skin_save_id = -2; int shield_save_id = -2; while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case DEFENSEOBJECTDEF_CHUNK_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_SAFE_MICRO_CHUNK(cload,DEFENSEOBJECTDEF_VARIABLE_HEALTH,Health,float); READ_SAFE_MICRO_CHUNK(cload,DEFENSEOBJECTDEF_VARIABLE_HEALTHMAX,HealthMax,float); READ_MICRO_CHUNK(cload,DEFENSEOBJECTDEF_VARIABLE_SKIN,skin_save_id); READ_SAFE_MICRO_CHUNK(cload,DEFENSEOBJECTDEF_VARIABLE_SHIELDSTRENGTH,ShieldStrength,float); READ_SAFE_MICRO_CHUNK(cload,DEFENSEOBJECTDEF_VARIABLE_SHIELDSTRENGTHMAX,ShieldStrengthMax,float); READ_MICRO_CHUNK(cload,DEFENSEOBJECTDEF_VARIABLE_SHIELDTYPE,shield_save_id); READ_SAFE_MICRO_CHUNK(cload,DEFENSEOBJECTDEF_VARIABLE_DAMAGE_POINTS,DamagePoints,float); READ_SAFE_MICRO_CHUNK(cload,DEFENSEOBJECTDEF_VARIABLE_DEATH_POINTS,DeathPoints,float); } cload.Close_Micro_Chunk(); } break; default: WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; }; cload.Close_Chunk(); } Skin = ArmorWarheadManager::Find_Armor_Save_ID( skin_save_id ); ShieldType = ArmorWarheadManager::Find_Armor_Save_ID( shield_save_id ); return true; } void DefenseObjectClass::Set_Health(float health) { float old_health = Health; Health = WWMath::Clamp(health, 0, HealthMax); if ( old_health != (float)Health ) { Mark_Owner_Dirty(); } } void DefenseObjectClass::Add_Health(float add_health) { Set_Health( WWMath::Clamp((float)Health + add_health, 0, HealthMax) ); } float DefenseObjectClass::Get_Health(void) const { return Health; } void DefenseObjectClass::Set_Health_Max(float health) { HealthMax = WWMath::Clamp(health, 0, MAX_MAX_HEALTH); Mark_Owner_Dirty(); } float DefenseObjectClass::Get_Health_Max(void) const { return HealthMax; } void DefenseObjectClass::Set_Shield_Strength(float str) { float old = ShieldStrength; ShieldStrength = WWMath::Clamp(str, 0, ShieldStrengthMax); if ( old != (float)ShieldStrength ) { Mark_Owner_Dirty(); } } void DefenseObjectClass::Add_Shield_Strength(float str) { Set_Shield_Strength( WWMath::Clamp((float)ShieldStrength + str, 0, ShieldStrengthMax) ); } float DefenseObjectClass::Get_Shield_Strength(void) const { return ShieldStrength; } void DefenseObjectClass::Set_Shield_Strength_Max(float str) { ShieldStrengthMax = WWMath::Clamp(str, 0, MAX_MAX_SHIELD_STRENGTH); Mark_Owner_Dirty(); } float DefenseObjectClass::Get_Shield_Strength_Max(void) const { return ShieldStrengthMax; } void DefenseObjectClass::Set_Precision(void) { // // This static function needs to be called after ArmorWarheadManager::Init // has done its work. // cEncoderList::Set_Precision(BITPACK_HEALTH, 0, (int) MAX_MAX_HEALTH); cEncoderList::Set_Precision(BITPACK_SHIELD_STRENGTH, 0, (int) MAX_MAX_SHIELD_STRENGTH); cEncoderList::Set_Precision(BITPACK_SHIELD_TYPE, 0, //ArmorWarheadManager::Get_Num_Armor_Types(), 1); ArmorWarheadManager::Get_Num_Armor_Types()); } /* ** */ void DefenseObjectClass::Mark_Owner_Dirty( void ) { if ( Get_Owner() != NULL ) { Get_Owner()->Set_Object_Dirty_Bit( NetworkObjectClass::BIT_OCCASIONAL, true ); } } /* ** */ void DefenseObjectClass::Set_Shield_Type( ArmorType type ) { ShieldType = type; Mark_Owner_Dirty(); } // // Do extra stuff when somebody kills somebody // /* if ( Get_Owner() != NULL && Get_Owner()->As_SmartGameObj() && Get_Owner()->As_SmartGameObj()->Has_Player() ) { int victim_id = Get_Owner()->As_SmartGameObj()->Get_Control_Owner(); smart->Get_Player_Data()->On_Kill(victim_id); } */ /* if ( Get_Owner() != NULL && Get_Owner()->As_DamageableGameObj() != NULL && Get_Owner()->As_DamageableGameObj()->As_PhysicalGameObj() != NULL && Get_Owner()->As_DamageableGameObj()->As_PhysicalGameObj()->As_SoldierGameObj() != NULL && Get_Owner()->As_SmartGameObj() != smart) { int victim_id = Get_Owner()->As_SmartGameObj()->Get_Control_Owner(); int victim_team = Get_Owner()->As_SmartGameObj()->Get_Player_Type(); smart->Get_Player_Data()->On_Kill(victim_id, victim_team); } */ /* if ( Get_Owner() != NULL && Get_Owner()->As_SoldierGameObj != NULL && Get_Owner()->As_SmartGameObj() != smart) { WWASSERT(Get_Owner()->As_SmartGameObj() != NULL); int victim_id = Get_Owner()->As_SmartGameObj()->Get_Control_Owner(); int victim_team = Get_Owner()->As_SmartGameObj()->Get_Player_Type(); smart->Get_Player_Data()->On_Kill(victim_id, victim_team); } */