//----------------------------------------------------------------------------- // // $Logfile:: /EF2/Code/DLLs/game/armor.cpp $ // $Revision:: 63 $ // $Author:: Singlis $ // $Date:: 9/26/03 2:35p $ // // Copyright (C) 1997 by Ritual Entertainment, Inc. // All rights reserved. // // This source is may not be distributed and/or modified without // expressly written permission by Ritual Entertainment, Inc. // // // DESCRIPTION: // Standard armor that prevents a percentage of damage per hit. // #include "_pch_cpp.h" #include "armor.h" #include "player.h" #include "mp_manager.hpp" extern cvar_t *g_armoradaptionlimit; extern Event EV_SetOriginEveryFrame; //------------------------------------------------------------------------- // Events //------------------------------------------------------------------------- Event EV_Armor_SetAdaptionFX ( "setadaptionfx", EV_DEFAULT, "s", "adaption_fx", "set the adaption fx model" ); Event EV_Armor_SetFXLife ( "setFXlife", EV_DEFAULT, "ff", "Normal_Time Explosive_Time", "Sets the FX Time" ); Event EV_Armor_SetActiveStatus ( "setarmoractivestatus", EV_DEFAULT, "b", "active", "sets the active status" ); Event EV_Armor_SetMultiplier ( "setarmormultiplier", EV_DEFAULT, "f", "multiplier", "sets the damage multiplier for the armor" ); Event EV_Armor_AddToNoProtectionList ( "noprotection", EV_DEFAULT, "s", "MOD", "Adds a MOD to the No Protection List" ); Event EV_Armor_UseArmorDirection ( "usearmordirection", EV_DEFAULT, "ff", "directionAngleMin directionAngleMax", "Only allows armor to work if attack is within the specifed yaw range" ); Event EV_Armor_LoadAdaptionDataFromGameVars ( "loadadaptiondata", EV_DEFAULT, NULL, NULL, "Used to control timing on adaption information loads" ); //------------------------------------------------------------------------- // Armor Implementation //------------------------------------------------------------------------- CLASS_DECLARATION( Item, Armor, NULL ) { { NULL, NULL } }; // // Name: Armor() // Class: Armor // // Description: Constructor // // Parameters: None // // Returns: None // Armor::Armor() { // all data will be setup by the archive function if ( LoadingSavegame ) return; if ( multiplayerManager.checkFlag( MP_FLAG_NO_ARMOR ) ) { PostEvent( EV_Remove, EV_REMOVE ); return; } setName( "UnknownArmor" ); amount = 0; SetMax ( 200 ); _mpItemType = MP_ITEM_TYPE_ARMOR; } // // Name: ~Armor() // Class: Armor // // Description: Destructor // // Parameters: None // // Returns: None // Armor::~Armor() { } // // Name: ResolveDamage() // Class: Armor // // Description: Resolves the damage. // // Parameters: float damage -- The initial damage. // // Returns: float newDamage -- The new damage after armor factor is taken into account // float Armor::ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ) { gi.Error( ERR_FATAL, "Armor::ResolveDamage -- Do Not Use Armor Base Class -- You MUST use a subclassed" ); return 0.0f; } //-------------------------------------------------------------- // Name: CanBeDamagedBy() // Class: Armor // // Description: Checks if this armor will aborb 100% of the damage // for the specified Means Of Death // // Parameters: int meansOfDeath // // Returns: true or false //-------------------------------------------------------------- bool Armor::CanBeDamagedBy( int meansOfDeath ) { gi.Error( ERR_FATAL, "Armor::CanBeDamagedBy -- Do Not Use Armor Base Class -- You MUST use a subclassed" ); return true; } // // Name: ItemPickup() // Class: Armor // // Description: Handles Sentients Picking up armor... In this case, however, // Only Actors are allowed to actually pick up armor // // Parameters: Entity *other - The entity doing the picking up // qboolean add_to_inventory - Here to make parameters match // // Returns: NULL -- This function doesn't actually return the armor, instead // it has the entity picking it up process and armor event // Item *Armor::ItemPickup( Entity *other, qboolean add_to_inventory, qboolean ) { Sentient *sent; str realname; // For right now, we only want players to pick up armor if ( !other->isSubclassOf( Player ) ) return NULL; if ( !Pickupable( other ) ) return NULL; sent = (Sentient*)other; // Check if we NEED to pick up the armor, if the armor we are about to pick up is // less than the armor we have, we don't want to pick it up if ( !_needThisArmor(sent) ) return NULL; // Handle the actual "Picking Up" _pickupArmor(sent); // Give the Sentient the Armor Event *armorEvent; armorEvent = new Event(EV_Sentient_GiveArmor); armorEvent->AddString( item_name.c_str() ); armorEvent->AddFloat( amount ); armorEvent->AddInteger( true ); sent->ProcessEvent(armorEvent); return NULL; // This doesn't create any items } // // Name: _needThisArmor() // Class: Armor // // Description: Checks if the sentient needs to pick up the // armor or not // // Parameters: Sentient *sentient -- The sentient to check // // Returns: true or false // qboolean Armor::_needThisArmor( Sentient *sentient ) { int currentArmorValue = sentient->GetArmorValue(); if( currentArmorValue >= amount ) return false; else return true; } // // Name: _pickupArmor() // Class: Armor // // Description: Takes care of "Picking Up" the armor, by playing the // appropriate sounds, removing the model... // // Parameters: None // // Returns: None // void Armor::_pickupArmor( Sentient *sentient) { str realname; // Play pickup sound realname = GetRandomAlias( "snd_pickup" ); if ( realname.length() > 1 ) sentient->Sound( realname, CHAN_ITEM ); // Cancel some events CancelEventsOfType( EV_Item_DropToFloor ); CancelEventsOfType( EV_Item_Respawn ); CancelEventsOfType( EV_FadeOut ); // Hide the model setSolidType( SOLID_NOT ); if ( _missingSkin ) { ChangeSkin( _missingSkin, true ); } else { hideModel(); } // Respawn? if ( !Respawnable() ) PostEvent( EV_Remove, FRAMETIME ); else PostEvent( EV_Item_Respawn, RespawnTime() ); // fire off any pickup_thread's if ( pickup_thread.length() ) ExecuteThread( pickup_thread ); } //------------------------------------------------------------------------- // AdaptiveArmor Implementation //------------------------------------------------------------------------- CLASS_DECLARATION( Armor, AdaptiveArmor, NULL ) { { &EV_Armor_SetAdaptionFX, &AdaptiveArmor::SetAdaptionFX }, { &EV_Armor_LoadAdaptionDataFromGameVars, &AdaptiveArmor::LoadDataFromGameVars}, { &EV_Armor_SetFXLife, &AdaptiveArmor::SetFXLife }, { NULL, NULL } }; // // Adaptive Armor is Adaption And Cannot Adapt is Global // Container AdaptiveArmor::_AdaptionList; Container AdaptiveArmor::_CannotAdaptToList; Container AdaptiveArmor::_fxList; // // Name: AdaptiveArmor() // Class: AdaptiveArmor // // Description: Constructor // // Parameters: None // // Returns: None // AdaptiveArmor::AdaptiveArmor() { amount = 0; SetMax ( 200 ); // Add Adaptions to list _fxList.AddObject( "fx/fx-borg-sheild-01.tik" ); _fxList.AddObject( "fx/fx-borg-sheild-02.tik" ); _fxList.AddObject( "fx/fx-borg-sheild-03.tik" ); // Set our current Adaption _AdaptionFX = "fx/fx-borg-sheild-01.tik"; // Set our index _currentFXIndex = 1; _fxTimeNormal = 0.15; _fxTimeExplosive = 1.0; _AddMODToCannotAdaptList( MOD_IMOD_PRIMARY); _AddMODToCannotAdaptList( MOD_IMOD_SECONDARY); _AddMODToCannotAdaptList( MOD_SUICIDE ); if ( !LoadingSavegame ) { //Try and pull data from the gameVars Event *loadEvent = new Event ( EV_Armor_LoadAdaptionDataFromGameVars ); PostEvent( loadEvent, 3.0 ); //LoadDataFromGameVars(); } } // // Name: ~AdaptiveArmor() // Class: AdaptiveArmor // // Description: Destructor // // Parameters: None // // Returns: None // AdaptiveArmor::~AdaptiveArmor() { } // // Name: ResolveDamage() // Class: AdaptiveArmor // // Description: Resolves the damage // // Parameters: float damage -- The initial damage. // // Returns: float newDamage -- The new damage after armor factor is taken into account // float AdaptiveArmor::ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ) { int DamageTotal; if (!_CanAdaptTo( meansOfDeath ) ) return damage; if ( attacker->isSubclassOf( Player ) ) { if ( !_AdaptionInList( meansOfDeath ) ) _AddAdaption( meansOfDeath , damage ); else _UpdateAdaption( meansOfDeath , damage ); } DamageTotal = (int)_GetDamageTotal( meansOfDeath ); _adaptionLimit = g_armoradaptionlimit->value; if ( DamageTotal > _adaptionLimit ) { if ( meansOfDeath == MOD_EXPLOSION || meansOfDeath == MOD_VAPORIZE_COMP ) _fxTime = _fxTimeExplosive; else _fxTime = _fxTimeNormal; _PlayAdaptionFX( direction , position ); return 0.0f; } return damage; } void AdaptiveArmor::SetAdaptionFX( Event *ev ) { SetAdaptionFX(ev->GetString( 1 ) ); } void AdaptiveArmor::SetAdaptionFX( const str &fxName ) { _AdaptionFX = fxName; } // // Name: _AddAdaption() // Class: AdaptiveArmor // // Description: Adds and adaption to the adaption list // // Parameters: int MOD - The Means Of Death index // // Returns: None // void AdaptiveArmor::_AddAdaption( int MeansOfDeath , float damage) { MODHitCounter *adaption = NULL; adaption = new MODHitCounter; //We don't want to be able to adapt to all MOD if ( _AdaptionList.NumObjects() > 2 ) return; //Double Check we're not adding the same MOD if ( _AdaptionInList( MeansOfDeath ) ) return; if ( adaption ) { adaption->MODIndex = MeansOfDeath; adaption->damage = damage + .1; // Have to add just a bit incase damage = 0 adaption->adapted = false; str MODName = MOD_NumToName( MeansOfDeath ); AddGameVar ( MODName , adaption->damage ); _AdaptionList.AddObject( adaption ); } } // // Name: _AdaptionInList() // Class: AdaptiveArmor // // Description: Checks if an adaption for the MOD is in the list // // Parameters: int MOD -- the Means Of Death index // // Returns: true or false // qboolean AdaptiveArmor::_AdaptionInList( int MeansOfDeath ) { int i; MODHitCounter *adaption; for( i=_AdaptionList.NumObjects(); i>0; i-- ) { adaption = _AdaptionList.ObjectAt( i ); if ( adaption->MODIndex == MeansOfDeath ) return true; } return false; } // // Name: _UpdateAdaption() // Class: AdaptiveArmor // // Description: Updates a particular adaption in the adaption list // // Parameters: int MOD -- the Means Of Death index // int count -- the number to add // // Returns: None // void AdaptiveArmor::_UpdateAdaption( int MeansOfDeath , float damage ) { int i; MODHitCounter *adaption; for( i=_AdaptionList.NumObjects(); i>0; i-- ) { adaption = _AdaptionList.ObjectAt( i ); if ( adaption->MODIndex == MeansOfDeath ) { adaption->damage += damage; str MODName = MOD_NumToName( MeansOfDeath ); UpdateGameVar ( MODName , adaption->damage ); if ( (adaption->damage >= g_armoradaptionlimit->value) && !adaption->adapted ) { adaption->adapted = true; Actor *act; for ( int i=1; i<=ActiveList.NumObjects(); i++ ) { act = ActiveList.ObjectAt(i); if ( !act ) continue; act->InContext( "enemyadapted" , 0 ); } } return; } } } // // Name: _GetDamageTotal() // Class: AdaptiveArmor // // Description: Retrieves the number of times shot for a // particular Means Of Death // // Parameters: int MOD -- Means Of Death Index // // Returns: int count // float AdaptiveArmor::_GetDamageTotal( int MeansOfDeath ) { int i; MODHitCounter *adaption; for( i=_AdaptionList.NumObjects(); i>0; i-- ) { adaption = _AdaptionList.ObjectAt( i ); if ( adaption->MODIndex == MeansOfDeath ) { if ( adaption->damage > 0 ) return adaption->damage; } } return 0.0f; } void AdaptiveArmor::_PlayAdaptionFX(const Vector &direction , const Vector &position ) { Vector dir; Vector newDir; Vector fxOrigin; Vector PosDelta; Entity *theFX; dir = direction; dir *= -1; dir.normalize(); fxOrigin = dir * 15; fxOrigin += position; dir = dir.toAngles(); PosDelta = _currentFXPosition - fxOrigin; if ( PosDelta.length() > FX_CHANGE_DELTA ) { _changeFX(); _currentFXPosition = fxOrigin; } theFX = SpawnEffect(_AdaptionFX, fxOrigin , dir , _fxTime ); theFX->bind( owner ); theFX->PostEvent( EV_SetOriginEveryFrame , FRAMETIME ); } qboolean AdaptiveArmor::_CanAdaptTo( int MOD ) { int checkMOD; int i; for( i=_CannotAdaptToList.NumObjects(); i>0; i-- ) { checkMOD = _CannotAdaptToList.ObjectAt( i ); if ( checkMOD == MOD ) return false; } return true; } void AdaptiveArmor::_AddMODToCannotAdaptList( int MOD ) { _CannotAdaptToList.AddObject( MOD ); } void AdaptiveArmor::_changeFX() { int MaxNumber; MaxNumber = _fxList.NumObjects(); if ( MaxNumber == 0 ) return; _currentFXIndex++; if ( _currentFXIndex > MaxNumber ) _currentFXIndex = 1; _AdaptionFX = _fxList.ObjectAt( _currentFXIndex ); } void AdaptiveArmor::ClearAdaptionList() { int i; MODHitCounter *adaption; for( i=_AdaptionList.NumObjects(); i>0; i-- ) { adaption = _AdaptionList.ObjectAt( i ); delete adaption; adaption = NULL; } _AdaptionList.FreeObjectList(); } //-------------------------------------------------------------- // Name: CanBeDamagedBy() // Class: AdaptiveArmor // // Description: Checks if this armor will aborb 100% of the damage // for the specified Means Of Death // // Parameters: int meansOfDeath // // Returns: true or false //-------------------------------------------------------------- bool AdaptiveArmor::CanBeDamagedBy( int meansOfDeath ) { float DamageTotal; if ( !_AdaptionInList( meansOfDeath ) ) return true; DamageTotal = _GetDamageTotal( meansOfDeath ); if ( DamageTotal > ADAPTION_LIMIT ) return false; return true; } void AdaptiveArmor::AddGameVar( const str &name , float value ) { ScriptVariable *script_var; if ( gameVars.VariableExists( name.c_str() ) ) return; script_var = new ScriptVariable; script_var->setName( name.c_str() ); script_var->setFloatValue( value ); gameVars.AddVariable( script_var ); } void AdaptiveArmor::UpdateGameVar( const str &name , float value ) { ScriptVariable *script_var; float currentValue; if ( !gameVars.VariableExists( name.c_str() ) ) return; script_var = gameVars.GetVariable( name.c_str() ); currentValue = script_var->floatValue(); currentValue = currentValue + value; script_var->setFloatValue( currentValue ); } void AdaptiveArmor::LoadAdaptionData( const str &name ) { ScriptVariable *script_var; float lastValue; int i; unsigned int theMOD; MODHitCounter *adaption; theMOD = MOD_NameToNum( name ); if ( gameVars.VariableExists( name.c_str() ) ) { script_var = gameVars.GetVariable( name.c_str() ); lastValue = script_var->floatValue(); _AddAdaption( theMOD , lastValue ); if ( lastValue >= g_armoradaptionlimit->value ) { for( i=_AdaptionList.NumObjects(); i>0; i-- ) { adaption = _AdaptionList.ObjectAt( i ); if ( adaption->MODIndex == theMOD ) { adaption->adapted = true; } } } } } void AdaptiveArmor::LoadDataFromGameVars( Event *ev ) { LoadDataFromGameVars(); } void AdaptiveArmor::LoadDataFromGameVars() { //Sigh, I HATE having to do this, but since we evidently HAVE to have //the adaptions carry across levels, here we go... LoadAdaptionData( "phaser" ); LoadAdaptionData( "vaporize" ); LoadAdaptionData( "comp_rifle" ); LoadAdaptionData( "vaporize_comp" ); } void AdaptiveArmor::SetFXLife( Event *ev ) { _fxTimeNormal = ev->GetFloat( 1 ); _fxTimeExplosive = ev->GetFloat( 2 ); } //------------------------------------------------------------------------- // BasicArmor Implementation //------------------------------------------------------------------------- const float BasicArmor::_maxAmount = 200.0f; const float BasicArmor::_normalMaxAmount = 100.0f; const float BasicArmor::_lossRate = 1.0f; const float BasicArmor::_highRangeCutoffSinglePlayer = 66.0f; const float BasicArmor::_midRangeCutoffSinglePlayer = 33.0f; const float BasicArmor::_highRangeCutoffMultiplayer = 100.0f; const float BasicArmor::_midRangeCutoffMultiplayer = 50.0f; const float BasicArmor::_highRangePercent = 0.75f; const float BasicArmor::_midRangePercent = 0.50f; const float BasicArmor::_lowRangePercent = 0.25f; CLASS_DECLARATION( Armor, BasicArmor, NULL ) { { NULL, NULL } }; BasicArmor::BasicArmor() { maximum_amount = _maxAmount; setName( "BasicArmor" ); turnThinkOn(); _lastAddTime = 0.0f; } // // Name: ResolveDamage() // Class: BasicAmor // // Description: Resolves the damage. // // Parameters: float damage -- The initial damage. // // Returns: float newDamage -- The new damage after armor factor is taken into account // float BasicArmor::ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ) { float tempDamage; float damageAbsorbed; float percent; float highRangeCutoff; float midRangeCutoff; if ( ( meansOfDeath == MOD_ARMOR_PIERCING ) || ( meansOfDeath == MOD_IMOD_PRIMARY ) || ( meansOfDeath == MOD_IMOD_SECONDARY ) || ( meansOfDeath == MOD_DEATH_QUAD ) ) return damage; // Determine which cutoff values to use if ( multiplayerManager.inMultiplayer() ) { highRangeCutoff = _highRangeCutoffMultiplayer; midRangeCutoff = _midRangeCutoffMultiplayer; } else { highRangeCutoff = _highRangeCutoffSinglePlayer; midRangeCutoff = _midRangeCutoffSinglePlayer; } // Figure out amount to absorb if ( amount > highRangeCutoff ) percent = _highRangePercent; else if ( amount > midRangeCutoff ) percent = _midRangePercent; else percent = _lowRangePercent; tempDamage = damage * percent; if ( amount > tempDamage ) damageAbsorbed = tempDamage; else damageAbsorbed = amount; // Damage the armor amount -= damageAbsorbed; if ( amount < 0.0 ) amount = 0.0; // Return damage left return damage - damageAbsorbed; } void BasicArmor::Think( void ) { if ( ( level.time > _lastAddTime + 1.0f ) && ( amount > _normalMaxAmount ) ) { amount -= level.frametime * _lossRate; if ( amount < _normalMaxAmount ) { amount = _normalMaxAmount; } } } void BasicArmor::Add( int num ) { Armor::Add( num ); _lastAddTime = level.time; } //-------------------------------------------------------------- // Name: CanBeDamagedBy() // Class: BasicArmor // // Description: Checks if this armor will aborb 100% of the damage // for the specified Means Of Death // // Parameters: int meansOfDeath // // Returns: true or false //-------------------------------------------------------------- bool BasicArmor::CanBeDamagedBy( int meansOfDeath ) { return true; } //-------------------------------------------------------------- // Name: _needThisArmor // Class: BasicArmor // // Description: Checks to see if we need more armor // // Parameters: Sentient *sentient - sentient that wants to know if it can have more armor // // Returns: true or false //-------------------------------------------------------------- qboolean BasicArmor::_needThisArmor( Sentient *sentient ) { int currentArmorValue = sentient->GetArmorValue(); if( currentArmorValue < _maxAmount ) return true; else return false; } void BasicArmor::cacheStrings( void ) { G_FindConfigstringIndex( va( "$$PickedUp$$ %d $$Item-Armor$$", (int)amount ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); } //------------------------------------------------------------------------- // ShieldArmor Implementation //------------------------------------------------------------------------- CLASS_DECLARATION( Armor, ShieldArmor, NULL ) { { &EV_Armor_SetActiveStatus, &ShieldArmor::SetActiveStatus }, { &EV_Armor_SetMultiplier, &ShieldArmor::SetMultiplier }, { &EV_Armor_AddToNoProtectionList, &ShieldArmor::AddMODToNoProtectionList }, { &EV_Armor_UseArmorDirection, &ShieldArmor::SetShieldDirection }, { NULL, NULL } }; ShieldArmor::ShieldArmor() { maximum_amount = 100.0f; //amount = 100.0f; setName( "ShieldArmor" ); _active = true; _multiplier = 0.0f; _directionMin = -90.0; _directionMax = 90.0; _useDirection = false; } void ShieldArmor::SetActiveStatus( Event *ev ) { _active = ev->GetBoolean( 1 ); } void ShieldArmor::SetMultiplier( Event *ev ) { _multiplier = ev->GetFloat( 1 ); } bool ShieldArmor::CanBeUsed() { if ( amount > 0 ) return true; return false; } // // Name: ResolveDamage() // Class: BasicAmor // // Description: Resolves the damage. // // Parameters: float damage -- The initial damage. // // Returns: float newDamage -- The new damage after armor factor is taken into account // float ShieldArmor::ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ) { //Shield Armor basically acts as a "life meter" for a shield. If the armor is actor //and he have an armor value greater than 0, then we take 0 damage. The as soon as the //amount is less than 0, then we start taking full damage. if ( !_active ) return damage; int inNoProtectionList = 0; inNoProtectionList = _noProtectionList.IndexOfObject( meansOfDeath ); if ( inNoProtectionList ) return damage; if ( meansOfDeath == MOD_ARMOR_PIERCING ) return damage; if ( amount <= 0 ) { amount = 0.0f; return damage; } if ( _useDirection ) { Vector directionAngle = direction.toAngles(); float directionYaw = AngleNormalize180( directionAngle[YAW] ); if ( (directionYaw > _directionMax) || (directionYaw < _directionMin) ) { return damage; } } amount -= damage; if ( amount < 0 ) amount = 0.0f; if ( amount > 0 ) { // We blocked so spawn an effect str modelName; Vector effectAngles; effectAngles = direction; effectAngles.toAngles(); modelName = "ReflectionModel-"; modelName += MOD_NumToName( meansOfDeath ); modelName += ".gdb"; SpawnEffect( modelName, position, vec_zero, 1.0f ); return (damage * _multiplier); } return damage; } //-------------------------------------------------------------- // Name: CanBeDamagedBy() // Class: BasicArmor // // Description: Checks if this armor will aborb 100% of the damage // for the specified Means Of Death // // Parameters: int meansOfDeath // // Returns: true or false //-------------------------------------------------------------- bool ShieldArmor::CanBeDamagedBy( int meansOfDeath ) { return true; } void ShieldArmor::AddMODToNoProtectionList( Event *ev ) { str MODName = ev->GetString( 1 ); AddMODToNoProtectionList( MODName ); } void ShieldArmor::AddMODToNoProtectionList(const str &MODName ) { unsigned int theMOD = MOD_NameToNum( MODName.c_str() ); _noProtectionList.AddUniqueObject( theMOD ); } void ShieldArmor::SetShieldDirection( Event *ev ) { SetShieldDirection( ev->GetFloat(1) , ev->GetFloat(2) ); } void ShieldArmor::SetShieldDirection( float minAngle, float maxAngle ) { _useDirection = true; _directionMin = minAngle; _directionMax = maxAngle; }