//----------------------------------------------------------------------------- // // $Logfile:: /EF2/Code/DLLs/game/sentient.cpp $ // $Revision:: 197 $ // $Author:: Singlis $ // $Date:: 9/26/03 2:36p $ // // 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: // Base class of entity that can carry other entities, and use weapons. // #include "_pch_cpp.h" #include "entity.h" #include "sentient.h" #include "weapon.h" #include "WeaponDualWield.h" #include "weaputils.h" #include "scriptmaster.h" #include "ammo.h" #include "armor.h" #include "misc.h" #include "inventoryitem.h" #include "player.h" #include "actor.h" #include "mp_manager.hpp" #include #include "decals.h" Event EV_Sentient_BeginAttack ( "beginattack", EV_DEFAULT, NULL, NULL, "Called before attack animation begins" ); Event EV_Sentient_EndAttack ( "endattack", EV_DEFAULT, NULL, NULL, "Called after attack animation ends" ); Event EV_Sentient_Attack ( "fire", EV_DEFAULT, "SS", "hand mode", "Fires the weapon in the specified hand. With the specified mode (primary, alternate)" ); Event EV_Sentient_StopFire ( "stopfire", EV_TIKIONLY, "s", "hand", "Stops the firing of the weapon in the specified hand." ); Event EV_Sentient_StartChargeFire ( "startcharge", EV_DEFAULT, "s", "firemode", "Draws back the bow string" ); Event EV_Sentient_ReleaseAttack ( "releasefire", EV_TIKIONLY, "f", "fireholdtime", "Releases the attack in the time specified." ); Event EV_Sentient_GiveWeapon ( "weapon", EV_DEFAULT, "s", "weapon_modelname", "Gives the sentient the weapon specified." ); Event EV_Sentient_Take ( "take", EV_DEFAULT, "s", "item_name", "Takes away the specified item from the sentient." ); Event EV_Sentient_GiveAmmo ( "ammo", EV_DEFAULT, "siI", "type amount max_amount", "Gives the sentient some ammo." ); Event EV_Sentient_GiveAmmoOverTime ( "giveAmmoOverTime", EV_DEFAULT, "sif", "type amount time", "Gives the sentient some ammo over the specified period of time." ); Event EV_Sentient_GiveArmor ( "armor", EV_DEFAULT, "sFB", "type amount pickedup", "Gives the sentient some armor." ); Event EV_Sentient_GiveItem ( "item", EV_DEFAULT, "si", "type amount", "Gives the sentient the specified amount of the specified item." ); Event EV_Sentient_GiveTargetname ( "give", EV_CHEAT | EV_TIKIONLY | EV_SCRIPTONLY, "s", "name", "Gives the sentient the targeted item." ); Event EV_Sentient_GiveHealth ( "health", EV_CHEAT | EV_TIKIONLY, "f", "health", "Gives the sentient the specified amount health." ); Event EV_Sentient_SetBloodModel ( "bloodmodel", EV_TIKIONLY, "s", "bloodModel", "set the model to be used when showing blood" ); Event EV_Sentient_TurnOffShadow ( "noshadow", EV_TIKIONLY, NULL, NULL, "Turns off the shadow for this sentient." ); Event EV_Sentient_TurnOnShadow ( "shadow", EV_TIKIONLY, NULL, NULL, "Turns on the shadow for this sentient." ); Event EV_Sentient_AddImmunity ( "immune", EV_DEFAULT, "sSSSSS", "immune_string1 immune_string2 immune_string3 immune_string4 immune_string5 immune_string6", "Adds to the immunity list for this sentient." ); Event EV_Sentient_AddResistance ( "resistance", EV_DEFAULT, "si", "resistance_string resistance_amount", "Adds to the resistance list for this sentient." ); Event EV_Sentient_RemoveImmunity ( "removeimmune", EV_DEFAULT, "sSSSSS", "immune_string1 immune_string2 immune_string3 immune_string4 immune_string5 immune_string6", "Removes from the immunity list for this sentient." ); Event EV_Sentient_RemoveResistance ( "removeresistance", EV_DEFAULT, "s", "resistance_string", "Removes from the resistance list for this sentient." ); Event EV_Sentient_UpdateOffsetColor ( "updateoffsetcolor", EV_CODEONLY, NULL, NULL, "Updates the offset color." ); Event EV_Sentient_JumpXY ( "jumpxy", EV_DEFAULT, "fff", "forwardmove sidemove speed", "Makes the sentient jump." ); Event EV_Sentient_MeleeAttackStart ( "meleeattackstart", EV_TIKIONLY, "S", "hand", "Is the start of the sentient's melee attack." ); Event EV_Sentient_MeleeAttackEnd ( "meleeattackend", EV_TIKIONLY, "S", "hand", "Is the end of the sentient's melee attack." ); Event EV_Sentient_RangedAttackStart ( "rangedattackstart", EV_TIKIONLY, NULL, NULL, "Is the start of the sentient's hitscan attack." ); Event EV_Sentient_RangedAttackEnd ( "rangedattackend", EV_TIKIONLY, NULL, NULL, "Is the end of the sentient's hitscan attack." ); Event EV_Sentient_BlockStart ( "blockstart", EV_TIKIONLY, NULL, NULL, "Is the start of the sentient's block." ); Event EV_Sentient_BlockEnd ( "blockend", EV_TIKIONLY, NULL, NULL, "Is the end of the sentient's block." ); Event EV_Sentient_StunStart ( "stunstart", EV_CODEONLY, NULL, NULL, "Is the start of the sentient's stun." ); Event EV_Sentient_StunEnd ( "stunend", EV_CODEONLY, NULL, NULL, "Is the end of the sentient's stun." ); Event EV_Sentient_SetMouthAngle ( "mouthangle", EV_DEFAULT, "f", "mouth_angle", "Sets the mouth angle of the sentient." ); Event EV_Sentient_SetMaxMouthAngle ( "maxmouthangle", EV_TIKIONLY, "f", "max_mouth_angle", "Sets the max mouth angle." ); Event EV_Sentient_OnFire ( "onfire", EV_CODEONLY, NULL, NULL, "Called every frame when the sentient is on fire." ); Event EV_Sentient_StopOnFire ( "stoponfire", EV_CODEONLY, NULL, NULL, "Stops the sentient from being on fire." ); Event EV_Sentient_SpawnBloodyGibs ( "spawnbloodygibs", EV_DEFAULT, "IF", "number_of_gibs scale", "Spawns some bloody generic gibs." ); Event EV_Sentient_SetMaxGibs ( "maxgibs", EV_TIKIONLY, "i", "max_number_of_gibs", "Sets the maximum amount of generic gibs this sentient will spawn when hit." ); Event EV_Sentient_CheckAnimations ( "checkanims", EV_CONSOLE, NULL, NULL, "Check the animations in the .tik file versus the statefile" ); Event EV_Sentient_SetStateFile ( "setstatefile", EV_DEFAULT, "s", "state_file", "Change the state file associated with this character" ); Event EV_Sentient_AddHealth ( "addhealth", EV_SCRIPTONLY, "fF", "health_to_add maxhealth", "Adds health to the sentient." ); Event EV_Sentient_SetViewMode ( "setviewmode", EV_SCRIPTONLY, "sB", "viewModeName override", "Puts this sentient into the specified view mode.\n" "Override defaults to true." ); Event EV_Sentient_GetActiveWeaponName ( "getActiveWeaponName", EV_SCRIPTONLY, "@sS", "weaponName hand", "Gets the name of the weapon in the specified hand (left, right, or dual).\n" "If no hand is specified it will return the first it finds" ); Event EV_Sentient_CatchOnFire ( "catchonfire", EV_SCRIPTONLY, NULL, NULL, "Catches the actor on fire." ); Event EV_Sentient_AddMeleeAttacker ( "addmeleeattacker", EV_CODEONLY, "e", "attack_ent", "Adds the entity to the melee attacker list." ); Event EV_Sentient_SwipeOn ( "swipeon", EV_TIKIONLY, "s", "hand", "Turn on the sword swiping for the weapon in the specified hand" ); Event EV_Sentient_SwipeOff ( "swipeoff", EV_TIKIONLY, "s", "hand", "Turn off the sword swiping for the weapon in the specified hand" ); Event EV_Sentient_WeaponAnim ( "weaponanimon", EV_TIKIONLY, "ss", "animname hand", "Put the weapon in hand in the animation specified" ); Event EV_Sentient_HealOverTime ( "healovertime", EV_DEFAULT, "ffff", "add_immediately add_at_interval interval max_percentage", "Will add specified amount of health immediatly, then add more at each specified time interval\n" "until health reaches the maxPercentage" ); Event EV_Sentient_HealAtInterval ( "healatinterval", EV_CODEONLY, "fff", "percentageToAdd interval maxPercentage", "Will add the specified amount to health, then, if necessary , will generate a new event of its\n" "own type to continue the regen process" ); Event EV_Sentient_GroupMemberInjured ( "groupmemberinjured", EV_DEFAULT, "b", "injured", "Informs us that a group member is injured or not" ); Event EV_Sentient_SetCriticalHealthPercentage ( "setcriticalhealthpercentage", EV_DEFAULT, "f", "percentage", "Sets the percentage of health that qualifies as critical -- Note, values must be entered as\n" "floating point numbers... .10 for example is 10 percent" ); Event EV_Sentient_HeadWatchAllowed ( "headwatchallowed", EV_DEFAULT, "B", "flag", "Sets whether to headwatch or not, default is true." ); Event EV_Sentient_SetDamageThreshold ( "setdamagethreshold", EV_DEFAULT, "ff", "maxDamage duration", "Sets up the damage threshold" ); Event EV_Sentient_DisplayFireEffect ( "displayfireeffect", EV_DEFAULT, "B", "flag", "Sets whether or not this sentient displays the fire effect when on fire." ); Event EV_Sentient_ClearDamageThreshold ( "cleardamagethreshold", EV_DEFAULT, NULL, NULL, "Clears out and resets the damage threshold" ); Event EV_Sentient_DropItem ( "dropitem", EV_DEFAULT, "s", "itemName", "Drops the item by the specified name." ); Event EV_Sentient_SetArmorActiveStatus ( "setmyarmorstatus", EV_DEFAULT, "b", "flag", "Sets The Active Status on Armor" ); Event EV_Sentient_SetArmorMultiplier ( "setmyarmormultiplier", EV_DEFAULT, "f", "multiplier", "Sets the mulitplier of the armor" ); Event EV_Sentient_AddToMyArmor ( "addtomyarmor", EV_DEFAULT, "f", "amountToAdd", "Adds the amount to the current Armor" ); Event EV_Sentient_SetMyArmorAmount ( "setmyarmoramount", EV_DEFAULT, "f", "amount", "Sets the amount of armor to the specifed number" ); Event EV_Sentient_FreeInventory ( "freeInventory", EV_DEFAULT, NULL, NULL, "Frees the sentient's inventory" ); Event EV_Sentient_ArmorCommand ( "armorcommand", EV_CODEONLY, "sSSSSSSS", "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8", "Pass the args to the active armor" ); Event EV_Sentient_SetHateModifier ( "sethatemodifier", EV_DEFAULT, "f", "hate_modifier", "Sets the hate modifier" ); Event EV_Sentient_CacheStateMachineAnims ( "cacheStateMachineAnims", EV_CACHE, "s", "stateMachineName", "Caches all of the anims needed by the statemachine" ); CLASS_DECLARATION( Entity, Sentient, NULL ) { { &EV_Sentient_BeginAttack, &Sentient::BeginAttack }, { &EV_Sentient_EndAttack, &Sentient::EndAttack }, { &EV_Sentient_Attack, &Sentient::FireWeapon }, { &EV_Sentient_StopFire, &Sentient::StopFireWeapon }, { &EV_Sentient_StartChargeFire, &Sentient::StartChargeFire }, { &EV_Sentient_ReleaseAttack, &Sentient::ReleaseFireWeapon }, { &EV_Sentient_GiveAmmo, &Sentient::EventGiveAmmo }, { &EV_Sentient_GiveAmmoOverTime, &Sentient::giveAmmoOverTime }, { &EV_Sentient_GiveWeapon, &Sentient::EventGiveItem }, { &EV_Sentient_GiveArmor, &Sentient::EventGiveArmor }, { &EV_Sentient_GiveItem, &Sentient::EventGiveItem }, { &EV_Sentient_GiveHealth, &Sentient::EventGiveHealth }, { &EV_Sentient_Take, &Sentient::EventTake }, { &EV_Sentient_SetBloodModel, &Sentient::SetBloodModel }, { &EV_Sentient_GiveTargetname, &Sentient::EventGiveTargetname }, { &EV_Damage, &Sentient::ArmorDamage }, { &EV_Sentient_TurnOffShadow, &Sentient::TurnOffShadow }, { &EV_Sentient_TurnOnShadow, &Sentient::TurnOnShadow }, { &EV_Sentient_AddImmunity, &Sentient::AddImmunity }, { &EV_Sentient_RemoveImmunity, &Sentient::RemoveImmunity }, { &EV_Sentient_AddResistance, &Sentient::AddResistance }, { &EV_Sentient_RemoveResistance, &Sentient::RemoveResistance }, { &EV_Sentient_UpdateOffsetColor, &Sentient::UpdateOffsetColor }, { &EV_Sentient_JumpXY, &Sentient::JumpXY }, { &EV_Sentient_MeleeAttackStart, &Sentient::MeleeAttackStart }, { &EV_Sentient_MeleeAttackEnd, &Sentient::MeleeAttackEnd }, { &EV_Sentient_RangedAttackStart, &Sentient::RangedAttackStart }, { &EV_Sentient_RangedAttackEnd, &Sentient::RangedAttackEnd }, { &EV_Sentient_BlockStart, &Sentient::BlockStart }, { &EV_Sentient_BlockEnd, &Sentient::BlockEnd }, { &EV_Sentient_StunStart, &Sentient::StunStart }, { &EV_Sentient_StunEnd, &Sentient::StunEnd }, { &EV_Sentient_SetMaxMouthAngle, &Sentient::SetMaxMouthAngle }, { &EV_Sentient_OnFire, &Sentient::OnFire }, { &EV_Sentient_StopOnFire, &Sentient::StopOnFire }, { &EV_Sentient_SpawnBloodyGibs, &Sentient::SpawnBloodyGibs }, { &EV_Sentient_SetMaxGibs, &Sentient::SetMaxGibs }, { &EV_Sentient_CheckAnimations, &Sentient::CheckAnimations }, { &EV_Sentient_SetStateFile, &Sentient::SetStateFile }, { &EV_Sentient_AddHealth, &Sentient::AddHealth }, { &EV_Sentient_SetViewMode, &Sentient::setViewMode }, { &EV_Sentient_GetActiveWeaponName, &Sentient::getActiveWeaponName }, { &EV_Sentient_CatchOnFire, &Sentient::CatchOnFire }, { &EV_Sentient_SwipeOn, &Sentient::SwipeOn }, { &EV_Sentient_SwipeOff, &Sentient::SwipeOff }, { &EV_Sentient_HealOverTime, &Sentient::AddHealthOverTime }, { &EV_Sentient_HealAtInterval, &Sentient::AddHealthAtInterval }, { &EV_Sentient_SetCriticalHealthPercentage, &Sentient::SetCriticalHealthPercentage }, { &EV_Sentient_HeadWatchAllowed, &Sentient::HeadWatchAllowed }, { &EV_Sentient_WeaponAnim, &Sentient::SetWeaponAnim }, { &EV_Sentient_SetDamageThreshold, &Sentient::SetDamageThreshold }, { &EV_Sentient_DisplayFireEffect, &Sentient::DisplayFireEffect }, { &EV_Sentient_ClearDamageThreshold, &Sentient::ClearDamageThreshold }, { &EV_Sentient_DropItem, &Sentient::DropItemEvent }, { &EV_Sentient_SetArmorActiveStatus, &Sentient::SetArmorActiveStatus }, { &EV_Sentient_SetArmorMultiplier, &Sentient::SetArmorMultiplier }, { &EV_Sentient_AddToMyArmor, &Sentient::AddToMyArmor }, { &EV_Sentient_SetMyArmorAmount, &Sentient::SetMyArmorAmount }, { &EV_Sentient_ArmorCommand, &Sentient::ArmorEvent }, { &EV_Sentient_SetHateModifier, &Sentient::SetHateModifier }, { &EV_Sentient_FreeInventory, &Sentient::FreeInventory }, { &EV_Sentient_CacheStateMachineAnims, &Sentient::cacheStateMachineAnims }, { NULL, NULL } }; Container SentientList; Sentient::Sentient() { animate = new Animate( this ); SentientList.AddObject( ( Sentient * )this ); setContents( CONTENTS_BODY ); inventory.ClearObjectList(); newWeapon = NULL; currentBaseArmor = NULL; eyeposition = Vector(0, 0, 64); shotsFiredThisVolley = 0; //firing_frame = -1; //firing_anim = -1; knock_start_time = 0; // do better lighting on all sentients edict->s.renderfx |= RF_EXTRALIGHT; edict->s.renderfx |= RF_SHADOW; // sentients have precise shadows edict->s.renderfx |= RF_SHADOW_PRECISE; in_melee_attack = false; in_ranged_attack = false; in_block = false; in_stun = false; attack_blocked = false; max_mouth_angle = 10; // touch triggers by default flags |= FL_TOUCH_TRIGGERS; on_fire = false; max_gibs = 0; next_bleed_time = 0; last_surface_hit = -1; last_bone_hit = -1; _canSendInjuredEvent = true; _headWatchAllowed = true; _hateModifier = 1.0; addAffectingViewModes( gi.GetViewModeClassMask( "sentient" ) ); SetCriticalHealthPercentage( .5 ); //Initial Max Value for _damageThreshold is set to -1 so that //we won't do a lot of processing unless we specifically set //our max value _damageThreshold.maxDamage = -1.0f; _displayFireEffect = true; on_fire_tagnums[ 0 ] = -1; on_fire_tagnums[ 1 ] = -1; on_fire_tagnums[ 2 ] = -1; } Sentient::~Sentient() { Sentient *sentient; for ( int i = 1; i <= resistances.NumObjects(); i++ ) { if (resistances.ObjectAt( i ) ) { delete resistances.ObjectAt( i ); resistances.ObjectAt( i ) = 0; } } sentient = this; SentientList.RemoveObject( sentient ); FreeInventory(); } // HACK HACK HACK void Sentient::UpdateOffsetColor( Event * ) { G_SetConstantLight( &edict->s.constantLight, &offset_color[ 0 ], &offset_color[ 1 ], &offset_color[ 2 ], NULL ); offset_color -= offset_delta; offset_time -= FRAMETIME; if ( offset_time > 0.0f ) { PostEvent( EV_Sentient_UpdateOffsetColor, FRAMETIME ); } else { CancelEventsOfType( EV_Sentient_UpdateOffsetColor ); edict->s.renderfx &= ~RF_LIGHTOFFSET; offset_color[ 0 ] = offset_color[ 1 ] = offset_color[ 2 ] = 0; G_SetConstantLight( &edict->s.constantLight, &offset_color[ 0 ], &offset_color[ 1 ], &offset_color[ 2 ], NULL ); } } void Sentient::SetOffsetColor( float r, float g, float b, float time ) { // kill all pending events CancelEventsOfType( EV_Sentient_UpdateOffsetColor ); offset_color[ 0 ] = r; offset_color[ 1 ] = g; offset_color[ 2 ] = b; G_SetConstantLight( &edict->s.constantLight, &offset_color[ 0 ], &offset_color[ 1 ], &offset_color[ 2 ], NULL ); // delta is a little less so we don't go below zero offset_delta = offset_color * ( FRAMETIME / ( time + ( 0.5f * FRAMETIME ) ) ); offset_time = time; edict->s.renderfx |= RF_LIGHTOFFSET; PostEvent( EV_Sentient_UpdateOffsetColor, FRAMETIME ); } Vector Sentient::EyePosition( void ) { int tagNum; tagNum = gi.Tag_NumForName( edict->s.modelindex, "tag_eyes" ); if ( tagNum >= 0 ) { Vector tag_pos; GetTag( tagNum, &tag_pos ); return tag_pos; } else { return origin + eyeposition; } } Vector Sentient::GunPosition( void ) { return origin; } void Sentient::EventGiveHealth( Event *ev ) { health = ev->GetFloat( 1 ); } void Sentient::AddHealth( Event *ev ) { float health_to_add; float maxhealth; health_to_add = ev->GetFloat( 1 ); if ( ev->NumArgs() > 1 ) maxhealth = ev->GetFloat( 2 ); else maxhealth = max_health; AddHealth( health_to_add, maxhealth ); } void Sentient::AddHealth( float healthToAdd, float maxHealth ) { float tempMaxHealth; float newHealth; if ( maxHealth ) tempMaxHealth = maxHealth; else tempMaxHealth = max_health; newHealth = health + healthToAdd; // See if the health is already above the max (don't do anything if it is) if ( health >= tempMaxHealth ) return; if ( newHealth > tempMaxHealth ) SetHealth(tempMaxHealth); else SetHealth(newHealth); } void Sentient::SetBloodModel( Event *ev ) { str name; str cache_name; str models_dir = "models/"; if ( ev->NumArgs() < 1 ) return; blood_model = ev->GetString( 1 ); cache_name = models_dir + blood_model; CacheResource( cache_name.c_str(), this ); name = GetBloodSpurtName(); if ( name.length() ) { cache_name = models_dir + name; CacheResource( cache_name.c_str(), this ); } name = GetBloodSplatName(); if ( name.length() ) CacheResource( name.c_str(), this ); name = GetGibName(); if ( name.length() ) { cache_name = models_dir + name; CacheResource( cache_name.c_str(), this ); } } void Sentient::StartChargeFire( Event *ev ) { Weapon * activeWeapon; firemode_t mode=FIRE_MODE1; weaponhand_t hand=WEAPON_RIGHT; hand = WeaponHandNameToNum( "dualhand" ); if ( ev->NumArgs() > 0 ) { mode = WeaponModeNameToNum( ev->GetString( 1 ) ); if ( mode < 0 ) return; } if ( hand > MAX_ACTIVE_WEAPONS ) { warning( "Sentient::StartChargeFire", "Weapon hand number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", hand, MAX_ACTIVE_WEAPONS ); return; } // start charging the active weapon activeWeapon = activeWeaponList[ (int)hand ]; // Save off firing animation and frame /*firing_anim = ev->GetAnimationNumber(); firing_frame = ev->GetAnimationFrame();*/ knock_start_time = level.time; /* if ( ( activeWeapon ) && activeWeapon->ReadyToFire( mode ) ) { knock_start_time = level.time; if ( mode == FIRE_MODE1 ) activeWeapon->SetAnim( "charge", EV_Weapon_DrawBowStrain); else if ( mode == FIRE_MODE2 ) activeWeapon->SetAnim( "alternatecharge", EV_Weapon_AltDrawBowStrain ); } */ } void Sentient::BeginAttack( Event * ) { if ( !inheritsFrom( "Player" ) ) { return; } Weapon *weapon = GetActiveWeapon( WEAPON_RIGHT ); if ( !weapon ) { weapon = GetActiveWeapon( WEAPON_LEFT ); } if ( !weapon ) { return; } GameplayManager *gpm = GameplayManager::getTheGameplayManager(); float animationRate = 1.0f; if (gpm->hasProperty( weapon->getArchetype(), "animationRate" ) ) { animationRate = gpm->getFloatValue( weapon->getArchetype(), "animationRate" ); } animate->SetAnimationRate( animationRate ); } void Sentient::EndAttack( Event * ) { animate->RestoreAnimationRate(); } void Sentient::FireWeapon( Event *ev ) { Weapon *activeWeapon; firemode_t mode=FIRE_MODE1; int number=0; str modestring, side; if ( ev->NumArgs() > 0 ) { side = ev->GetString( 1 ); if ( !stricmp( side, "righthand" ) ) number = WEAPON_RIGHT; else if ( !stricmp( side, "lefthand" ) ) number = WEAPON_LEFT; else if ( !stricmp( side, "dualhand" ) ) { number = WEAPON_DUAL; if ( ev->NumArgs() == 2 ) { modestring = ev->GetString( 2 ); mode = WeaponModeNameToNum(modestring); //if ( !modestring.icmp( "primary" ) ) // mode = FIRE_MODE1; //else if ( !modestring.icmp( "alternate" ) ) // mode = FIRE_MODE2; //else // warning( "Sentient::FireWeapon", "Invalid fire mode %s\n", modestring ); } } else number = atoi( side.c_str() ); } if ( ( number > MAX_ACTIVE_WEAPONS ) || ( number < 0 ) ) { warning( "Sentient::FireWeapon", "Weapon number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", number, MAX_ACTIVE_WEAPONS ); return; } // Save off firing animation and frame //firing_anim = ev->GetAnimationNumber(); //firing_frame = ev->GetAnimationFrame(); activeWeapon = activeWeaponList[ number ]; if ( ( activeWeapon ) && activeWeapon->ReadyToFire( mode ) ) { activeWeapon->Fire( mode ); } else { if ( !activeWeapon ) gi.WDPrintf( "No active weapon in slot #: \"%i\"\n", number ); } } void Sentient::StopFireWeapon( Event *ev ) { Weapon *activeWeapon; int number=0; str side; if ( ev->NumArgs() > 0 ) { side = ev->GetString( 1 ); if ( !stricmp( side, "righthand" ) ) number = WEAPON_RIGHT; else if ( !stricmp( side, "lefthand" ) ) number = WEAPON_LEFT; else if ( !stricmp( side, "dualhand" ) ) number = WEAPON_DUAL; else number = atoi( side.c_str() ); } if ( ( number > MAX_ACTIVE_WEAPONS ) || ( number < 0 ) ) { warning( "Sentient::StopFireWeapon", "Weapon number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", number, MAX_ACTIVE_WEAPONS ); return; } activeWeapon = activeWeaponList[ number ]; if ( activeWeapon ) { if ( activeWeapon->animate->HasAnim("fire_stop") ) activeWeapon->SetAnim("fire_stop"); else activeWeapon->ForceIdle(); } else { gi.WDPrintf( "No active weapon in slot #: \"%i\"\n", number ); } } void Sentient::ReleaseFireWeapon( Event *ev ) { Weapon *activeWeapon; float charge_time=0; firemode_t mode=FIRE_MODE1; int number=0; str modestring, side; charge_time = level.time - knock_start_time; // Reset the down timer knock_start_time = 0; if ( ev->NumArgs() > 0 ) { side = ev->GetString( 1 ); if ( !stricmp( side, "righthand" ) ) number = WEAPON_RIGHT; else if ( !stricmp( side, "lefthand" ) ) number = WEAPON_LEFT; else if ( !stricmp( side, "dualhand" ) ) { number = WEAPON_DUAL; if ( ev->NumArgs() == 2 ) { modestring = ev->GetString( 2 ); mode = WeaponModeNameToNum(modestring); //if ( !modestring.icmp( "primary" ) ) // mode = FIRE_MODE1; //else if ( !modestring.icmp( "alternate" ) ) // mode = FIRE_MODE2; //else // warning( "Sentient::FireWeapon", "Invalid fire mode %s\n", modestring ); } } else number = atoi( side.c_str() ); } if ( ( number > MAX_ACTIVE_WEAPONS ) || ( number < 0 ) ) { warning( "Sentient::FireWeapon", "Weapon number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", number, MAX_ACTIVE_WEAPONS ); return; } // Save off firing animation and frame //firing_anim = ev->GetAnimationNumber(); //firing_frame = ev->GetAnimationFrame(); activeWeapon = activeWeaponList[ number ]; if ( activeWeapon ) { activeWeapon->ReleaseFire( mode, charge_time ); } } void Sentient::AddItem( const Item *object ) { inventory.AddObject( object->entnum ); } void Sentient::RemoveItem( Item *object ) { int i; inventory.RemoveObject( object->entnum ); if ( object->isSubclassOf( Weapon ) ) DeactivateWeapon( (Weapon *)object ); for ( i=0; iisSubclassOf( Armor ) ) { return item; } } return NULL; } //-------------------------------------------------------------- // // Name: FindItemByExternalName // Class: Sentient // // Description: Finds the item by its name // // Parameters: const char *itemname -- Name to find // Item *current (default, 0) -- Item to start searching from // // Returns: Item * or NULL // //-------------------------------------------------------------- Item *Sentient::FindItemByExternalName( const char *itemname, Item *current ) { int num, i; Item *item; bool seeking; if ( current ) seeking = true; else seeking = false; num = inventory.NumObjects(); for( i = 1; i <= num; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( item ); if ( item == current ) { seeking = false; continue; // Start compares with the next item } if ( seeking ) continue; if ( !Q_stricmp( item->getName(), itemname ) ) return item; } return NULL; } //-------------------------------------------------------------- // // Name: FindItemByModelname // Class: Sentient // // Description: Finds the item by its name // // Parameters: const char *mdl -- Model name to find // Item *current (default, 0) -- Item to start searching from // // Returns: Item * or NULL // //-------------------------------------------------------------- Item *Sentient::FindItemByModelname( const char *mdl, Item *current ) { int i, num; bool seeking; Item *item; str tmpmdl; if ( current ) seeking = true; else seeking = false; if ( strnicmp( "models/", mdl, 7 ) ) tmpmdl = "models/"; tmpmdl += mdl; num = inventory.NumObjects(); for( i = 1; i <= num; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( item ); if ( item == current ) { seeking = false; continue; // Start compares with the next item } if ( seeking ) continue; if ( !Q_stricmp( item->model, tmpmdl ) ) return item; } return NULL; } //-------------------------------------------------------------- // // Name: FindItemByClassName // Class: Sentient // // Description: Finds the item by its name // // Parameters: const char *classname -- Classname to find // Item *current (default, 0) -- Item to start searching from // // Returns: Item * or NULL // //-------------------------------------------------------------- Item *Sentient::FindItemByClassName( const char *classname, Item *current ) { int num, i; Item *item; bool seeking; if ( current ) seeking = true; else seeking = false; num = inventory.NumObjects(); for( i = 1; i <= num; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( item ); if ( item == current ) { seeking = false; continue; // Start compares with the next item } if ( seeking ) continue; if ( !Q_stricmp( item->edict->entname, classname ) ) return item; } return NULL; } //-------------------------------------------------------------- // // Name: FindItem // Class: Sentient // // Description: Finds the item by its name // // Parameters: const char *classname -- Classname to find // Item *current (default, 0) -- Item to start searching from // // Returns: Item * or NULL // //-------------------------------------------------------------- Item *Sentient::FindItem( const char *itemname, Item *current ) { Item *item = NULL; if ( !itemname ) return NULL; item = FindItemByExternalName( itemname, current ); if ( !item ) { item = FindItemByModelname( itemname, current ); if ( !item ) { item = FindItemByClassName( itemname, current ); } } return item; } void Sentient::AttachAllActiveWeapons( void ) { int i; for ( i=0; iAttachToOwner( (weaponhand_t )i ); } } void Sentient::DetachAllActiveWeapons( void ) { int i; for ( i=0; iDetachFromOwner(); } } void Sentient::FreeInventory( Event * ) { FreeInventory(); } void Sentient::FreeInventory( void ) { int num; int i; Item *item; Ammo *ammo; // Detach all Weapons DetachAllActiveWeapons(); // Delete all inventory items ( this includes weapons ) num = inventory.NumObjects(); for( i = num; i > 0; i-- ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); delete item; } inventory.ClearObjectList(); // Remove all ammo num = ammo_inventory.NumObjects(); for( i = num; i > 0; i-- ) { ammo = ( Ammo * )ammo_inventory.ObjectAt( i ); delete ammo; } ammo_inventory.ClearObjectList(); for( i = 0 ; i < MAX_ACTIVE_WEAPONS ; i++ ) { activeWeaponList[i] = NULL; } } qboolean Sentient::HasItem( const char *itemname ) { return ( FindItem( itemname ) != NULL ); } int Sentient::NumWeapons( void ) { int num; int i; Item *item; int numweaps; numweaps = 0; num = inventory.NumObjects(); for( i = 1; i <= num; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( checkInheritance( &Weapon::ClassInfo, item->getClassname() ) ) { numweaps++; } } return numweaps; } bool Sentient::ChangeWeapon( Weapon *weapon, weaponhand_t hand ) { if ( ( hand > MAX_ACTIVE_WEAPONS ) ) { warning( "Sentient::ChangeWeapon", "Weapon hand number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", hand, MAX_ACTIVE_WEAPONS ); return false; } // Check if weapon is already active in the slot if ( weapon == activeWeaponList[hand] ) return false; ActivateWeapon( weapon, hand ); return true; } void Sentient::DeactivateWeapon( weaponhand_t hand ) { int i; if ( !activeWeaponList[hand] ) { warning( "Sentient::DeactivateWeapon", "Tried to deactivate a non-active weapon in hand %d\n", hand ); return; } activeWeaponList[hand]->AttachToHolster( hand ); activeWeaponList[hand]->SetPutAway( false ); activeWeaponList[hand]->animate->RandomAnimate( "putaway" ); // Check the player's inventory and detach any weapons that are already attached to that spot for ( i=1; i<=inventory.NumObjects(); i++ ) { Item *item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( item->isSubclassOf( Weapon ) ) { Weapon *weap = ( Weapon * )item; if ( ( weap != activeWeaponList[hand] ) && ( !str::cmp( weap->GetCurrentAttachToTag(), activeWeaponList[hand]->GetCurrentAttachToTag() ) ) ) { weap->DetachFromOwner(); } } } activeWeaponList[hand] = NULL; } void Sentient::DeactivateWeapon( Weapon *weapon ) { int i; for ( i=0; iDetachFromOwner(); activeWeaponList[i]->SetPutAway( false ); activeWeaponList[i] = NULL; } } } void Sentient::ActivateWeapon( Weapon *weapon, weaponhand_t hand ) { int i; if ( weapon->isSubclassOf(WeaponDualWield) ) { WeaponDualWield *dw = (WeaponDualWield*)weapon; activeWeaponList[WEAPON_LEFT] = dw->getLeftWeapon(); activeWeaponList[WEAPON_RIGHT] = dw->getRightWeapon(); } else activeWeaponList[hand] = weapon; str holsterTag; switch( hand ) { case WEAPON_LEFT: holsterTag = weapon->GetLeftHolsterTag(); break; case WEAPON_RIGHT: holsterTag = weapon->GetRightHolsterTag(); break; case WEAPON_DUAL: holsterTag = weapon->GetDualHolsterTag(); break; default: holsterTag = ""; break; } // Check the player's inventory and detach any weapons that are currently attached to that tag. for ( i=1; i<=inventory.NumObjects(); i++ ) { Item *item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( item->isSubclassOf( Weapon ) ) { Weapon *weap = ( Weapon * )item; if ( ( !str::cmp( holsterTag, weap->GetCurrentAttachToTag() ) ) ) { weap->DetachFromOwner(); } } } weapon->AttachToOwner( hand ); weapon->playAnim( "raise", true ); } Weapon *Sentient::BestWeapon( Weapon *ignore ) { Item *next; int n; int j; int bestrank; Weapon *bestweapon; n = inventory.NumObjects(); // Search forewards until we find a weapon bestweapon = NULL; bestrank = -999999; for( j = 1; j <= n; j++ ) { next = ( Item * )G_GetEntity( inventory.ObjectAt( j ) ); assert( next ); if ( ( next != ignore ) && next->isSubclassOf( Weapon ) && ( ( ( Weapon * )next )->GetRank() > bestrank ) && ( ( ( Weapon * )next )->HasAmmo( FIRE_MODE1 ) ) ) { bestweapon = ( Weapon * )next; bestrank = bestweapon->GetRank(); } } return bestweapon; } Weapon *Sentient::NextWeapon( Weapon *weapon ) { Item *item; int i; int n; int weaponorder; Weapon *choice; int choiceorder; Weapon *bestchoice; int bestorder; Weapon *worstchoice; int worstorder; if ( !inventory.ObjectInList( weapon->entnum ) ) { error( "NextWeapon", "Weapon not in list" ); } weaponorder = weapon->GetOrder(); bestchoice = weapon; bestorder = 65535; worstchoice = weapon; worstorder = weaponorder; n = inventory.NumObjects(); for( i = 1; i <= n; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( item ); if ( item->isSubclassOf( Weapon ) ) { choice = ( Weapon * )item; if ( !choice->HasAmmo( FIRE_MODE1 ) || !choice->AutoChange() ) { continue; } choiceorder = choice->GetOrder(); if ( ( choiceorder > weaponorder ) && ( choiceorder < bestorder ) ) { bestorder = choiceorder; bestchoice = choice; } if ( choiceorder < worstorder ) { worstorder = choiceorder; worstchoice = choice; } } } if ( bestchoice == weapon ) { return worstchoice; } return bestchoice; } Weapon *Sentient::PreviousWeapon( Weapon *weapon ) { Item *item; int i; int n; int weaponorder; Weapon *choice; int choiceorder; Weapon *bestchoice; int bestorder; Weapon *worstchoice; int worstorder; if ( !inventory.ObjectInList( weapon->entnum ) ) { error( "PreviousWeapon", "Weapon not in list" ); } weaponorder = weapon->GetOrder(); bestchoice = weapon; bestorder = -65535; worstchoice = weapon; worstorder = weaponorder; n = inventory.NumObjects(); for( i = 1; i <= n; i++ ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( item ); if ( item->isSubclassOf( Weapon ) ) { choice = ( Weapon * )item; if ( !choice->HasAmmo( FIRE_MODE1 ) || !choice->AutoChange() ) { continue; } choiceorder = choice->GetOrder(); if ( ( choiceorder < weaponorder ) && ( choiceorder > bestorder ) ) { bestorder = choiceorder; bestchoice = choice; } if ( choiceorder > worstorder ) { worstorder = choiceorder; worstchoice = choice; } } } if ( bestchoice == weapon ) { return worstchoice; } return bestchoice; } Weapon *Sentient::GetActiveWeapon( weaponhand_t hand ) { if ( ( hand > MAX_ACTIVE_WEAPONS ) || ( hand < 0 ) ) { warning( "Sentient::GetActiveWeapon", "Weapon hand number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", hand, MAX_ACTIVE_WEAPONS ); return NULL; } else if ( hand == WEAPON_ANY ) { if ( activeWeaponList[ WEAPON_LEFT ] ) return activeWeaponList[ WEAPON_LEFT ]; else if ( activeWeaponList[ WEAPON_RIGHT ] ) return activeWeaponList[ WEAPON_RIGHT ]; else if ( activeWeaponList[ WEAPON_DUAL ] ) return activeWeaponList[ WEAPON_DUAL ]; else return NULL; } else { return activeWeaponList[hand]; } } qboolean Sentient::IsActiveWeapon( const Weapon *weapon ) { int i; for( i=0; iGetString( 1 ); ptr = name.c_str(); // skip over the $ ptr++; found = false; tlist = world->GetTargetList( str( ptr ) ); for ( i = 1; i <= tlist->list.NumObjects(); i++ ) { Entity * ent; ent = tlist->list.ObjectAt( i ); assert( ent ); if ( ent->isSubclassOf( Item ) ) { Item *item; item = ( Item * )ent; item->SetOwner( this ); item->ProcessPendingEvents(); AddItem( item ); found = true; } } if ( !found ) { ev->Error( "Could not give item with targetname %s to this sentient.\n", name.c_str() ); } } Item *Sentient::giveItem( const str &itemname, int amount, bool pickedUp, float skillLevel ) { ClassDef *cls; Item *item; item = FindItem( itemname ); if ( item ) { item->Add( amount ); item->SetSkillLevel( skillLevel ); return item; } else { qboolean set_the_model = false; // we don't have it, so lets try to resolve the item name // first lets see if it is a registered class name cls = getClass( itemname ); if ( !cls ) { SpawnArgs args; // if that didn't work lets try to resolve it as a model args.setArg( "model", itemname ); cls = args.getClassDef(); if ( !cls ) { gi.WDPrintf( "No item called '%s'\n", itemname.c_str() ); return NULL; } set_the_model = true; } assert( cls ); item = ( Item * )cls->newInstance(); if ( !item ) { gi.WDPrintf( "Could not spawn an item called '%s'\n", itemname.c_str() ); return NULL; } if ( !item->isSubclassOf( Item ) ) { gi.WDPrintf( "Could not spawn an item called '%s'\n", itemname.c_str() ); delete item; return NULL; } if ( set_the_model ) { // Set the model item->setModel( itemname ); } item->SetOwner( this ); item->ProcessPendingEvents(); if ( amount ) item->setAmount( amount ); item->hideModel(); item->SetSkillLevel( skillLevel ); AddItem( item ); if ( item->isSubclassOf( Weapon ) ) { // Post an event to give the ammo to the sentient Event *ev1; // Remove the fullbright from the weapon item->edict->s.renderfx &= ~RF_FULLBRIGHT; ev1 = new Event( EV_Weapon_GiveStartingAmmo ); ev1->AddEntity( this ); item->ProcessEvent( ev1 ); if ( this->isSubclassOf( Player ) ) { Player *player = (Player *)this; Weapon *weapon = (Weapon *)item; Weapon *currentWeapon; if ( pickedUp && player->getAutoSwitchWeapons() ) { currentWeapon = GetActiveWeapon( WEAPON_DUAL ); if ( ( !currentWeapon ) || ( weapon->getWeaponPriority() < currentWeapon->getWeaponPriority() ) ) { ev1 = new Event( EV_Player_UseItem ); ev1->AddString( item->getName() ); PostEvent( ev1, FRAMETIME ); } } } } return item; } } void Sentient::takeItem( const char *name ) { Item * item; item = FindItem( name ); if ( item ) { gi.DPrintf( "Taking item %s away from player\n", item->getName().c_str() ); item->PostEvent( EV_Remove, 0.0f ); return; } Ammo *ammo; ammo = FindAmmoByName( name ); if ( ammo ) { gi.DPrintf( "Taking ammo %s away from player\n", name ); ammo->setAmount( 0 ); } } bool Sentient::useWeapon( const char *weaponname, weaponhand_t hand ) { Weapon *weapon; assert( weaponname ); if ( !weaponname ) { warning( "Sentient::useWeapon", "weaponname is NULL\n" ); return false; } if ( deadflag || ( multiplayerManager.inMultiplayer() && this->isSubclassOf( Player ) && multiplayerManager.isPlayerSpectator( (Player *)this ) ) ) return false; // Find the item in the sentient's inventory weapon = ( Weapon * )FindItem( weaponname ); // If it exists, then make the change to the slot number specified if ( weapon ) { return ChangeWeapon( weapon, hand ); } return false; } void Sentient::EventTake( Event *ev ) { takeItem( ev->GetString( 1 ) ); } void Sentient::EventGiveAmmo( Event *ev ) { int amount,maxamount=-1; const char *type; type = ev->GetString( 1 ); amount = ev->GetInteger( 2 ); if ( ev->NumArgs() == 3 ) maxamount = ev->GetInteger( 3 ); GiveAmmo( type, amount, false, maxamount ); } void Sentient::giveAmmoOverTime( Event *ev ) { Event *event; float ammoToAdd; float ammoToAddThisFrame; float timeLeft; int numFrames; const char *ammoType; ammoType = ev->GetString( 1 ); ammoToAdd = ev->GetInteger( 2 ); timeLeft = ev->GetFloat( 3 ); // Figure out how much ammo to add this frame if ( timeLeft < level.frametime ) numFrames = 1; else numFrames = timeLeft / level.frametime; ammoToAddThisFrame = ammoToAdd / numFrames; // Actually add the ammo to the entity GiveAmmo( ammoType, ammoToAddThisFrame, false, -1 ); // Post the event for the next frame ammoToAdd -= ammoToAddThisFrame; timeLeft -= level.frametime; //CancelEventsOfType( EV_Sentient_GiveAmmoOverTime ); if ( timeLeft > 0.0f ) { event = new Event( EV_Sentient_GiveAmmoOverTime ); event->AddString( ammoType ); event->AddInteger( ammoToAdd ); event->AddFloat( timeLeft ); PostEvent( event, level.frametime ); } } void Sentient::EventGiveItem( Event *ev ) { const char *type; float amount; type = ev->GetString( 1 ); if ( ev->NumArgs() > 1 ) amount = ev->GetInteger( 2 ); else amount = 1; giveItem( type, amount ); } void Sentient::EventGiveArmor ( Event *ev ) { const char *type; Item* currentArmor; float amount; bool pickedup; currentArmor = NULL; currentArmor = FindBaseArmor(); //We can only have 1 base armor //if ( currentArmor ) // RemoveItem( currentArmor ); // Do the giveItem to put the armor in inventory // and to get it instantiated type = ev->GetString( 1 ); if ( stricmp(type , "none" ) == 0 ) { currentArmor = NULL; currentBaseArmor = NULL; return; } if ( ev->NumArgs() > 1 ) amount = ev->GetFloat( 2 ); else amount = 0; if ( ev->NumArgs() > 2 ) pickedup = ev->GetBoolean( 3 ); else pickedup = false; giveItem( type , amount ); currentArmor = NULL; currentArmor = FindBaseArmor(); if ( pickedup && this->isSubclassOf( Player ) && currentArmor ) { int iconIndex; iconIndex = gi.imageindex( "sysimg/icons/items/armor" ); ((Player *)this)->setItemText( iconIndex, va( "$$PickedUp$$ %d $$Item-Armor$$", (int)amount ) ); //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$PickedUp$$ %d armor", (int)amount ); } // Cast is safe because FindBaseArmor will only return // items of type armor if ( currentArmor ) currentBaseArmor = (Armor*)currentArmor; } qboolean Sentient::DoGib( int meansofdeath ) { if ( !com_blood->integer ) { return false; } if ( ( meansofdeath == MOD_TELEFRAG ) || ( meansofdeath == MOD_LAVA ) ) { return true; } if ( health > -75.0f ) { return false; } // Impact and Crush < -75 health if ( ( meansofdeath == MOD_IMPACT ) || ( meansofdeath == MOD_CRUSH ) ) { return true; } return false; } //#define WATER_CONVERSION_FACTOR 1.0f void Sentient::ArmorDamage( ::Damage &damage ) { CheckDamageThreshold( damage.damage ); if ( damageModSystem ) damageModSystem->resolveDamage(damage); ArmorDamage( damage.damage, damage.inflictor, damage.attacker, damage.position, damage.direction, damage.normal, damage.knockback, damage.dflags, damage.meansofdeath, damage.surfaceNumber, damage.boneNumber, damage.weapon, damage.showPain ); } void Sentient::ArmorDamage( Event *ev ) { float damage; Entity *inflictor; Entity *attacker; Entity *weapon = 0; Vector position; Vector direction; Vector normal; int knockback; int dflags; int meansofdeath; int surfaceNumber = -1; int boneNumber = -1; damage = ev->GetFloat ( 1 ); inflictor = ev->GetEntity ( 2 ); attacker = ev->GetEntity ( 3 ); position = ev->GetVector ( 4 ); direction = ev->GetVector ( 5 ); normal = ev->GetVector ( 6 ); knockback = ev->GetInteger( 7 ); dflags = ev->GetInteger( 8 ); meansofdeath = ev->GetInteger( 9 ); if ( ev->NumArgs() > 9 ) surfaceNumber = ev->GetInteger( 10 ); if ( ev->NumArgs() > 10 ) boneNumber = ev->GetInteger( 11 ); if ( ev->NumArgs() > 11 ) weapon = ev->GetEntity( 12 ); ArmorDamage( damage, inflictor, attacker, position, direction, normal, knockback, dflags, meansofdeath, surfaceNumber, boneNumber, weapon , false ); } void Sentient::ArmorDamage( float damage, Entity *inflictor, Entity *attacker, const Vector &position, const Vector &direction, const Vector &normal, int knockback, int dflags, int meansofdeath, int surfaceNumber, int boneNumber, Entity *weapon , bool showPain ) { Vector momentum; Event *event; Event *injuredEvent; float damage_red; float damage_green; float damage_time; float resistance_modifier; qboolean blocked = false; qboolean set_means_of_death; Vector normalizedDirection; Q_UNUSED(normal); if ( ( takedamage == DAMAGE_NO ) || ( movetype == MOVETYPE_NOCLIP ) ) { return; } // See if we should set means of death set_means_of_death = true; if ( this->isSubclassOf( Actor ) ) { Actor *act = (Actor *)this; if ( ( act->state_flags & STATE_FLAG_SMALL_PAIN ) && ( meansofdeath == MOD_ON_FIRE || Immune( meansofdeath ) ) ) set_means_of_death = false; } // See if sentient is immune to this type of damage if ( Immune ( meansofdeath ) ) { if ( set_means_of_death ) means_of_death = meansofdeath; // Send pain event event = new Event( EV_Pain ); event->AddFloat( 0.0f ); event->AddEntity( attacker ); event->AddInteger( meansofdeath ); event->AddVector( position ); event->AddVector( direction ); ProcessEvent( event ); return; } // Apply any resistances if any resistance_modifier = GetResistanceModifier( meansofdeath ); if( resistance_modifier ) { resistance_modifier /= 100.0f; float damagemod = resistance_modifier * damage; damage -= damagemod; } // See if the damage is melee and high enough on actor if ( this->isSubclassOf( Actor ) ) { Actor *act = ( Actor * )this; // Check the attack if it is melee if ( ( meansofdeath == MOD_SWORD ) || ( meansofdeath == MOD_CHAINSWORD ) || ( meansofdeath == MOD_AXE ) || ( meansofdeath == MOD_FIRESWORD ) || ( meansofdeath == MOD_ELECTRICSWORD ) || ( meansofdeath == MOD_LIGHTSWORD ) ) { // Make sure attack is high enough if ( position.z - origin.z < act->minimum_melee_height ) { Entity::SpawnEffect("fx/fx-sparkblock.tik", position, vec_zero, 2.0f); return; } // Make sure attack is within the damage angles if ( act->damage_angles ) { Vector attack_angle; float yaw_diff; attack_angle = direction.toAngles(); yaw_diff = angles[YAW] - attack_angle[YAW] + 180.0f; yaw_diff = AngleNormalize180( yaw_diff ); if ( ( yaw_diff < -act->damage_angles ) || ( yaw_diff > act->damage_angles ) ) { Entity::SpawnEffect("fx_sparkblock.tik", position, vec_zero, 2.0f); return; } } } } if ( deadflag ) { // Spawn a blood spurt if this model has one if ( ShouldBleed( meansofdeath, true ) ) { AddBloodSpurt( position, direction, boneNumber ); if ( ShouldGib( meansofdeath, damage ) ) ProcessEvent( EV_Sentient_SpawnBloodyGibs ); } if ( set_means_of_death ) means_of_death = meansofdeath; if ( ( meansofdeath == MOD_FIRE ) || ( meansofdeath == MOD_FIRESWORD ) || ( meansofdeath == MOD_FIRE_BLOCKABLE ) ) { if ( GetResistanceModifier(meansofdeath) < 100 ) TryLightOnFire( meansofdeath, attacker ); } if ( damage > 0.0f ) { // Send pain event event = new Event( EV_Pain ); event->AddFloat( damage ); event->AddEntity( attacker ); event->AddInteger( meansofdeath ); event->AddVector( position ); event->AddVector( direction ); event->AddInteger( showPain ); ProcessEvent( event ); } return; } if ( flags & FL_GODMODE ) { return; } if ( currentBaseArmor ) { if ( this->isSubclassOf(Actor) ) { Actor *act; qboolean resolveDamage; act = (Actor*)this; if ( act->GetActorFlag( ACTOR_FLAG_DISABLED ) || act->GetActorFlag( ACTOR_FLAG_CRIPPLED ) ) resolveDamage = false; else resolveDamage = true; if ( resolveDamage ) damage = currentBaseArmor->ResolveDamage( damage , meansofdeath , direction , position, attacker ); } else damage = currentBaseArmor->ResolveDamage( damage , meansofdeath , direction , position , attacker ); } if ( this->isSubclassOf( Player ) ) { Vector attack_angle; Vector player_angle; float yaw_diff; Player *player; player = ( Player * )this; player_angle = player->GetTorsoAngles(); attack_angle = direction.toAngles(); yaw_diff = player_angle[YAW] - attack_angle[YAW] + 180.0f; yaw_diff = AngleNormalize180( yaw_diff ); // Get real knockback value knockback = player->GetKnockback( knockback, blocked ); //If we're not in multiplayer AND we don't have a ground entity //cut our knockback in half if ( !multiplayerManager.inMultiplayer() && !player->groundentity) knockback*=0.5; } // Do the kick if (!(dflags & DAMAGE_NO_KNOCKBACK)) { if ((knockback) && (movetype != MOVETYPE_NONE) && (movetype != MOVETYPE_STATIONARY) && (movetype != MOVETYPE_BOUNCE) && (movetype != MOVETYPE_PUSH) && (movetype != MOVETYPE_STOP)) { float m; Event *immunity_event; Event *resistance_event; if (mass < 50) m = 50; else m = mass; normalizedDirection = direction; normalizedDirection.normalize(); if ( isClient() && ( attacker == this ) && multiplayerManager.inMultiplayer() ) momentum = normalizedDirection * ( 1000.0f * ( float )knockback / m ); // the rocket jump hack... else momentum = normalizedDirection * ( 500.0f * ( float )knockback / m ); if ( dflags & DAMAGE_BULLET ) { // Clip the z velocity for bullet weapons if ( momentum.z > 75.0f ) momentum.z = 75.0f; } velocity += momentum; // Make this sentient vulnerable to falling damage now if ( Immune( MOD_FALLING ) ) { immunity_event = new Event( EV_Sentient_RemoveImmunity ); immunity_event->AddString( "falling" ); ProcessEvent( immunity_event ); } if ( Resistant( MOD_FALLING ) ) { resistance_event = new Event( EV_Sentient_RemoveResistance ); resistance_event->AddString( "falling" ); ProcessEvent( resistance_event ); } } } Vector attack_angle; float yaw_diff; attack_angle = attacker->angles; yaw_diff = angles[YAW] - attack_angle[YAW] + 180.0f; yaw_diff = AngleNormalize180( yaw_diff ); GameplayManager *gpm = GameplayManager::getTheGameplayManager(); float clashchance = 0.0f; if ( gpm->isDefined("clash_chance") ) clashchance = atof(gpm->getDefine("clash_chance")); // Consider attacker == inflictor, so we don't clash vs. projectiles. if ( in_melee_attack && G_Random() < clashchance && attacker == inflictor) { if ( ( yaw_diff > -45.0f ) && ( yaw_diff < 45.0f ) ) { if ( attacker->isSubclassOf( Sentient ) ) { Sentient *sent = ( Sentient * )attacker; sent->SetAttackBlocked( true ); } SetAttackBlocked( true ); WeaponEffectsAndSound( weapon, "Clash", position ); means_of_death = meansofdeath; return; } } // Blocking of the attack by an enemy. if ( in_block && ( meansofdeath != MOD_ON_FIRE ) && ( meansofdeath != MOD_EXPLOSION ) ) { if ( ( yaw_diff > -45.0f ) && ( yaw_diff < 45.0f ) ) { if ( ( meansofdeath != MOD_FIRE ) && ( meansofdeath != MOD_LIFEDRAIN ) ) { if ( attacker->isSubclassOf( Sentient ) ) { Sentient *sent = ( Sentient * )attacker; sent->SetAttackBlocked( true ); } if ( this->isSubclassOf( Actor ) ) { Actor *act = ( Actor * )this; act->AddStateFlag( STATE_FLAG_BLOCKED_HIT ); } WeaponEffectsAndSound( weapon, "Blocked", position ); } means_of_death = meansofdeath; return; } in_block = false; } if ( g_debugdamage->integer ) G_DebugDamage( damage, this, attacker, inflictor ); // Handle statis damage if ( meansofdeath == MOD_STASIS ) { if ( damage > 0.0f ) { //Stupid last minute hack to make sure we can't stasis people who will break if ( this->isSubclassOf(Actor) ) { Actor *me; me = (Actor*)this; if ( me->GetActorFlag(ACTOR_FLAG_CANNOT_FREEZE ) ) { return; } } startStasis(); PostEvent( EV_StopStasis, damage ); } return; } if ( meansofdeath != MOD_LIFEDRAIN ) { if ( damage < 0 ) { //Because a resistance of over 100 will Add to health, need to make //sure that the health doesn't go over the max if( health < max_health ) { health -= damage; if ( health > max_health ) health = max_health; } } else { health -= damage; } } if ( ( damage > 0.0f ) && ( surfaceNumber != -1 ) ) { if ( this->isSubclassOf( Actor ) ) { Actor* theActor = (Actor*)this; if( theActor->GetActorFlag(ACTOR_FLAG_USE_DAMAGESKINS) ) { if ( !edict->s.surfaces[ surfaceNumber ] ) edict->s.surfaces[ surfaceNumber ]++; } } } // Do all of the regional damage stuff SetRegionalDamage( surfaceNumber, boneNumber, direction ); if ( ( ( meansofdeath == MOD_FIRE ) || ( meansofdeath == MOD_FIRESWORD ) || ( meansofdeath == MOD_FIRE_BLOCKABLE ) ) && !blocked ) { if ( GetResistanceModifier(meansofdeath) < 100 ) TryLightOnFire( meansofdeath, attacker ); } // Set means of death if ( set_means_of_death ) means_of_death = meansofdeath; // Spawn a blood spurt if this model has one if ( ( damage > 0.0f ) && ShouldBleed( meansofdeath, false ) && !blocked ) { AddBloodSpurt( position, direction, boneNumber ); if ( ( this->isSubclassOf( Actor ) || ( damage > 10.0f ) ) && ShouldGib( meansofdeath, damage ) ) ProcessEvent( EV_Sentient_SpawnBloodyGibs ); } if ( health < 0.1f ) { // See if we can kill this actor or not if ( this->isSubclassOf( Actor ) ) { Actor *act = ( Actor * )this; if ( act->IsImmortal() ) health = 1; } } if ( health < 0.1f ) { // Recalculate damage so that it is only damage needed to kill damage += health; // Make sure health is now 0 health = 0; if ( ( meansofdeath == MOD_VAPORIZE ) || ( meansofdeath == MOD_VAPORIZE_COMP ) || ( meansofdeath == MOD_VAPORIZE_DISRUPTOR ) || ( meansofdeath == MOD_VAPORIZE_PHOTON ) || ( meansofdeath == MOD_SNIPER ) ) { bool canDisintegrate = true; //YAY!!! ANOTHER SPECIAL CASE!! WOOPEE!! YEE-HAW!!! MY EXCITEMENT KNOWS NO BOUNDS!!! Actor *stupidSpecialCaseActor; if ( isSubclassOf( Actor ) ) { //YAY!!!! I get to cast myself!!! YAY!!!! stupidSpecialCaseActor = ( Actor* )this; if(stupidSpecialCaseActor->GetActorFlag(ACTOR_FLAG_CANNOT_DISINTEGRATE) ) canDisintegrate = false; } if ( canDisintegrate ) { event = new Event( EV_DisplayEffect ); event->AddString( "FadeOut" ); if ( ( meansofdeath == MOD_VAPORIZE_DISRUPTOR ) ) event->AddString( "Disruptor" ); else if ( ( meansofdeath == MOD_VAPORIZE_PHOTON ) ) event->AddString( "Photon" ); else if ( ( meansofdeath == MOD_SNIPER ) ) event->AddString( "Sniper" ); else event->AddString( "Phaser" ); ProcessEvent( event ); edict->s.renderfx |= RF_CHILDREN_DONT_INHERIT_ALPHA; if ( !isSubclassOf( Player ) ) PostEvent( EV_Remove, 5.0f ); } } event = new Event( EV_Killed ); event->AddEntity( attacker ); event->AddFloat( damage ); event->AddEntity( inflictor ); event->AddInteger( meansofdeath ); event->AddInteger( false ); event->AddEntity( weapon ); event->AddVector( direction ); ProcessEvent( event ); } if ( ( meansofdeath == MOD_GAS ) || ( meansofdeath == MOD_GAS_BLOCKABLE ) || ( meansofdeath == MOD_SLIME ) || ( meansofdeath == MOD_POISON ) ) { damage_green = damage / 50.0f; if ( damage_green > 1.0f ) damage_green = 1.0f; if ( ( damage_green < 0.2f ) && ( damage_green > 0.0f ) ) damage_green = 0.2f; damage_red = 0.0f; } else { damage_red = damage / 50.0f; if ( damage_red > 0.5f ) damage_red = 0.5f; if ( ( damage_red < 0.2f ) && ( damage_red > 0.0f ) ) damage_red = 0.2f; damage_green = 0.0f; } damage_time = damage / 50.0f; if ( damage_time > 0.5f ) damage_time = 0.5f; if ( sv_showdamagecolors->integer && !this->isSubclassOf( Player ) ) { SetOffsetColor( damage_red, damage_green, 0.0f, damage_time ); } if ( health > 0.0f && damage > 0.0f ) { // Send pain event event = new Event( EV_Pain ); event->AddFloat( damage ); event->AddEntity( attacker ); event->AddInteger( meansofdeath ); event->AddVector( position ); event->AddVector( direction ); event->AddInteger( showPain ); ProcessEvent( event ); } // Send the injured event to our group if we are at or below our critical health and // we can send the event; if ( _canSendInjuredEvent && ( health / max_health ) < GetCriticalHealthPercentage() ) { injuredEvent = new Event (EV_Sentient_GroupMemberInjured); injuredEvent->AddInteger( 1 ); groupcoordinator->SendEventToGroup(injuredEvent , GetGroupID()); _canSendInjuredEvent = false; } return; } void Sentient::SetRegionalDamage( int surface_number, int bone_number, Vector direction ) { Vector attack_angle; float yaw_diff; // Save the last surface that was hit last_surface_hit = surface_number; // Save the last bone that was hit last_bone_hit = bone_number; // Save the last general region that was hit last_region_hit = 0; attack_angle = direction.toAngles(); yaw_diff = angles[YAW] - attack_angle[YAW] + 180.0f; yaw_diff = AngleNormalize180( yaw_diff ); if ( ( yaw_diff < -90.0f ) || ( yaw_diff > 90.0f ) ) last_region_hit |= REGIONAL_DAMAGE_BACK; else last_region_hit |= REGIONAL_DAMAGE_FRONT; } qboolean Sentient::CanBlock( int meansofdeath, qboolean full_block ) { // Check to see what a full block can't even block switch ( meansofdeath ) { case MOD_DROWN : case MOD_TELEFRAG : case MOD_SLIME : case MOD_LAVA : case MOD_FALLING : case MOD_RADIATION : case MOD_IMPALE : case MOD_ON_FIRE : case MOD_ELECTRICWATER : case MOD_EAT : return false; } // Full blocks block everything else if ( full_block ) return true; // Check to see what a small block can't block switch ( meansofdeath ) { case MOD_FIRE : case MOD_GAS : case MOD_CRUSH_EVERY_FRAME : return false; } // Everything else is blocked return true; } void Sentient::AddBloodSpurt( const Vector &position, const Vector &direction, int bone_number ) { Entity *blood; Vector dir; Event *event; str blood_splat_name; float blood_splat_size; float length; trace_t trace; float scale; if ( !com_blood->integer ) return; next_bleed_time = level.time + .5f; // Calculate a good scale for the blood if ( mass < 50 ) scale = .5f; else if ( mass > 300 ) scale = 1.5f; else if ( mass >= 200 ) scale = mass / 200.0; else scale = .5f + ( (float)mass - 50.0f ) / 300.0f; // Add blood spurt blood = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); blood->setModel( blood_model ); if ( !isSubclassOf( Player ) ) { // Network optimization dir[0] = -direction[0]; dir[1] = -direction[1]; dir[2] = -direction[2]; blood->angles = dir.toAngles(); blood->setAngles( blood->angles ); } // Make the blood come from the centroid of the bone if possible if ( bone_number >= 0 ) { int parent_bone_number; Vector bone_pos; Vector parent_bone_pos; Vector bone_centroid; parent_bone_number = gi.Bone_GetParentNum( edict->s.modelindex, bone_number ); if ( parent_bone_number >= 0 ) { GetTag( bone_number, &bone_pos ); GetTag( parent_bone_number, &parent_bone_pos ); bone_centroid = (bone_pos + parent_bone_pos ) * 0.5f; blood->setOrigin( bone_centroid ); } } else { // Default the blood spurt to the position where the trace stopped blood->setOrigin( position ); } //blood->origin.copyTo( blood->edict->s.origin2 ); blood->setSolidType( SOLID_NOT ); blood->setScale( scale ); event = new Event( EV_Remove ); blood->PostEvent( event, 1.0f ); // Add blood splats near feet blood_splat_name = GetBloodSplatName(); blood_splat_size = GetBloodSplatSize(); if ( blood_splat_name.length() && G_Random() < 0.5f ) { dir = origin - centroid; dir.z -= 50.0f; dir.x += G_CRandom( 20.0f ); dir.y += G_CRandom( 20.0f ); length = dir.length(); dir.normalize(); dir = dir * ( length + 10.0f ); trace = G_Trace( centroid, vec_zero, vec_zero, centroid + dir, NULL, MASK_DEADSOLID, false, "AddBloodSpurt" ); if ( trace.fraction < 1.0f ) { Decal *decal = new Decal; decal->setShader( blood_splat_name ); decal->setOrigin( Vector( trace.endpos ) + ( Vector( trace.plane.normal ) * 0.2f ) ); decal->setDirection( trace.plane.normal ); decal->setOrientation( "random" ); decal->setRadius( blood_splat_size + G_Random( blood_splat_size ) ); } } } qboolean Sentient::ShouldBleed( int meansofdeath, qboolean dead ) { // Make sure we have a blood model if ( !blood_model.length() ) return false; // Don't let the player bleed in single player if ( !multiplayerManager.inMultiplayer() && isSubclassOf( Player ) ) return false; // Make sure if we are dead we are allowed to bleed after death if ( dead && this->isSubclassOf( Actor ) ) { Actor *act = (Actor *)this; if ( !act->GetActorFlag( ACTOR_FLAG_BLEED_AFTER_DEATH ) ) return false; } // See if we can bleed now based on means of death switch ( meansofdeath ) { // Sometimes bleed (based on time) case MOD_BULLET : case MOD_STING : case MOD_STING2 : case MOD_VORTEX : case MOD_CHAINSWORD : case MOD_PLASMABEAM : case MOD_CRUSH_EVERY_FRAME : case MOD_POISON : case MOD_ELECTRICWATER : case MOD_EAT : case MOD_CIRCLEOFPROTECTION : if ( next_bleed_time > level.time ) return false; break; // Sometimes bleed (based on chance) case MOD_PLASMASHOTGUN : if ( next_bleed_time < level.time ) return true; if ( G_Random() > 0.1f ) return false; break; // Never bleed case MOD_LIFEDRAIN : case MOD_SLIME : case MOD_LAVA : case MOD_GAS : case MOD_GAS_BLOCKABLE : case MOD_FIRE : case MOD_FIRE_BLOCKABLE : case MOD_SLING : case MOD_DROWN : case MOD_FLASHBANG : case MOD_ON_FIRE : case MOD_FALLING : case MOD_RADIATION : case MOD_DEATH_QUAD : return false; } // Always bleed by default return true; } // ShouldGib assumes that ShouldBleed has already been called qboolean Sentient::ShouldGib( int meansofdeath, float damage ) { // See if we can gib based on means of death switch ( meansofdeath ) { // Always gib case MOD_CHAINSWORD : case MOD_CRUSH_EVERY_FRAME : case MOD_PLASMASHOTGUN : case MOD_EAT : case MOD_EXPLOSION : return true; break; // Sometimes gib case MOD_BULLET : case MOD_STING : case MOD_STING2 : if ( G_Random( 100.0f ) < damage * 10.0f ) return true; break; case MOD_PLASMABEAM : if ( G_Random( 100.0f ) < damage * 5.0f ) return true; break; // Never gib case MOD_LIFEDRAIN : case MOD_SLIME : case MOD_LAVA : case MOD_GAS : case MOD_GAS_BLOCKABLE : case MOD_POISON : case MOD_FIRE : case MOD_FIRE_BLOCKABLE : case MOD_SLING : case MOD_DROWN : case MOD_FLASHBANG : case MOD_ON_FIRE : case MOD_FALLING : case MOD_RADIATION : case MOD_VORTEX : case MOD_ELECTRICWATER : return false; } // Default is random based on how much damage done if ( G_Random( 100.0f ) < damage * 2.0f ) return true; return false; } str Sentient::GetBloodSpurtName( void ) { str blood_spurt_name; if ( blood_model == "fx/fx_bspurt.tik" ) blood_spurt_name = "fx/fx_bspurt2.tik"; else if ( blood_model == "fx/fx_gspurt.tik" ) blood_spurt_name = "fx/fx_gspurt2.tik"; else if ( blood_model == "fx/fx_bspurt_blue.tik" ) blood_spurt_name = "fx/fx_bspurt2_blue.tik"; return blood_spurt_name; } str Sentient::GetBloodSplatName( void ) { str blood_splat_name; if ( blood_model == "fx/fx_bspurt.tik" ) blood_splat_name = "bloodsplat.spr"; else if ( blood_model == "fx/fx_gspurt.tik" ) blood_splat_name = "greensplat.spr"; else if ( blood_model == "fx/fx_bspurt_blue.tik" ) blood_splat_name = "bluesplat.spr"; return blood_splat_name; } float Sentient::GetBloodSplatSize( void ) { float m; m = (int)mass; if ( m < 50.0f ) m = 50.0f; else if ( m > 250.0f ) m = 250.0f; return ( 10.0f + ( m - 50.0f ) / 200.0f * 6.0f ); } str Sentient::GetGibName( void ) { str gib_name; if ( blood_model == "fx/fx_bspurt.tik" ) gib_name = "fx/fx_rgib"; else if ( blood_model == "fx/fx_gspurt.tik" ) gib_name = "fx/fx_ggib"; else if ( blood_model == "fx/fx_bspurt_green.tik" ) gib_name = "fx/fx_ggib"; return gib_name; } int Sentient::NumInventoryItems( void ) { return inventory.NumObjects(); } Item *Sentient::NextItem( Item *item ) { Item *next_item; int i; int n; qboolean item_found = false; if ( !item ) { item_found = true; } else if ( !inventory.ObjectInList( item->entnum ) ) { error( "NextItem", "Item not in list" ); } n = inventory.NumObjects(); for( i = 1; i <= n; i++ ) { next_item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( next_item ); if ( ( next_item->isSubclassOf( InventoryItem ) || ( next_item->isSubclassOf( Weapon ) ) )&& item_found ) return next_item; if ( next_item == item ) item_found = true; } return NULL; } Item *Sentient::PrevItem( Item *item ) { Item *prev_item; int i; int n; qboolean item_found = false; if ( !item ) { item_found = true; } else if ( !inventory.ObjectInList( item->entnum ) ) { error( "NextItem", "Item not in list" ); } n = inventory.NumObjects(); for( i = n; i >= 1; i-- ) { prev_item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); assert( prev_item ); if ( prev_item->isSubclassOf( InventoryItem ) && item_found) return prev_item; if ( prev_item == item ) item_found = true; } return NULL; } void Sentient::DropInventoryItems( void ) { int num; int i; Item *item; // Drop any inventory items num = inventory.NumObjects(); for( i = num; i >= 1; i-- ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( item->isSubclassOf( InventoryItem ) ) { item->Drop(); } } } void Sentient::setModel( const char *mdl ) { // Rebind all active weapons DetachAllActiveWeapons(); Entity::setModel( mdl ); AttachAllActiveWeapons(); } void Sentient::TurnOffShadow( Event * ) { edict->s.renderfx &= ~RF_SHADOW; } void Sentient::TurnOnShadow( Event * ) { edict->s.renderfx |= RF_SHADOW; } void Sentient::Archive( Archiver &arc ) { int i; int num; Resistance *resistance; Entity::Archive( arc ); arc.ArchiveFloat( &_criticalHealthPercentage ); // Archive Damage Threshold; arc.ArchiveFloat( &_damageThreshold.duration ); arc.ArchiveFloat( &_damageThreshold.startTime ); arc.ArchiveFloat( &_damageThreshold.maxDamage ); arc.ArchiveFloat( &_damageThreshold.currentDamage ); arc.ArchiveFloat( &shotsFiredThisVolley ); arc.ArchiveFloat( &_hateModifier ); inventory.Archive( arc ); if ( arc.Saving() ) { num = ammo_inventory.NumObjects(); } else { ammo_inventory.ClearObjectList(); } arc.ArchiveInteger( &num ); for( i = 1; i <= num; i++ ) { Ammo * ptr; if ( arc.Loading() ) { ptr = new Ammo; ammo_inventory.AddObject( ptr ); } else { ptr = ammo_inventory.ObjectAt( i ); } arc.ArchiveObject( ptr ); } arc.ArchiveSafePointer( &newWeapon ); arc.ArchiveSafePointer( ¤tBaseArmor ); //arc.ArchiveInteger( &firing_frame ); //arc.ArchiveInteger( &firing_anim ); arc.ArchiveVector( &offset_color ); arc.ArchiveVector( &offset_delta ); arc.ArchiveFloat( &offset_time ); arc.ArchiveFloat( &knock_start_time ); arc.ArchiveString( &blood_model ); arc.ArchiveInteger( &last_surface_hit ); arc.ArchiveInteger( &last_bone_hit ); arc.ArchiveUnsigned( &last_region_hit ); immunities.Archive( arc ); if ( arc.Saving() ) { num = resistances.NumObjects(); arc.ArchiveInteger( &num ); for( i = 1 ; i <= num ; i++ ) { resistance = resistances.ObjectAt( i ); arc.ArchiveInteger( &resistance->MODIndex ); arc.ArchiveInteger( &resistance->ResistanceAmount ); } } else { arc.ArchiveInteger( &num ); for( i = 1 ; i <= num ; i++ ) { resistance = new Resistance; resistances.AddObject( resistance ); arc.ArchiveInteger( &resistance->MODIndex ); arc.ArchiveInteger( &resistance->ResistanceAmount ); } } for( i = 0; i < MAX_ACTIVE_WEAPONS; i++ ) { arc.ArchiveSafePointer( &activeWeaponList[ i ] ); } arc.ArchiveUnsigned( &_viewMode ); arc.ArchiveBool( &_canSendInjuredEvent ); arc.ArchiveBool( &_headWatchAllowed ); arc.ArchiveBool( &_displayFireEffect ); arc.ArchiveVector( &gunoffset ); arc.ArchiveVector( &eyeposition ); arc.ArchiveInteger( &viewheight ); arc.ArchiveInteger( &means_of_death ); arc.ArchiveBoolean( &in_melee_attack ); arc.ArchiveBoolean( &in_ranged_attack ); arc.ArchiveBoolean( &in_block ); arc.ArchiveBoolean( &in_stun ); arc.ArchiveBoolean( &on_fire ); arc.ArchiveFloat( &on_fire_stop_time ); arc.ArchiveFloat( &next_catch_on_fire_time ); arc.ArchiveInteger( &on_fire_tagnums[ 0 ] ); arc.ArchiveInteger( &on_fire_tagnums[ 1 ] ); arc.ArchiveInteger( &on_fire_tagnums[ 2 ] ); arc.ArchiveSafePointer( &fire_owner ); arc.ArchiveBoolean( &attack_blocked ); arc.ArchiveFloat( &attack_blocked_time ); arc.ArchiveFloat( &max_mouth_angle ); arc.ArchiveInteger( &max_gibs ); arc.ArchiveFloat( &next_bleed_time ); } void Sentient::ArchivePersistantData( Archiver &arc, qboolean sublevelTransition ) { int i; int num; Q_UNUSED(sublevelTransition); if ( arc.Loading() ) FreeInventory(); // archive the inventory if ( arc.Saving() ) { // count up the total number num = inventory.NumObjects(); } else { inventory.ClearObjectList(); } // archive the number arc.ArchiveInteger( &num ); // archive each item for( i = 1; i <= num; i++ ) { str name; int amount; Item * item = NULL; if ( arc.Saving() ) { Entity * ent; ent = G_GetEntity( inventory.ObjectAt( i ) ); if ( ent && ent->isSubclassOf( Armor ) ) { item = ( Item * )ent; name = item->getName(); amount = item->getAmount(); } else if ( ent && ent->isSubclassOf( Item ) ) { item = ( Item * )ent; name = item->model; amount = item->getAmount(); } else { error( "ArchivePersistantData", "Non Item in inventory\n" ); } } arc.ArchiveString( &name ); arc.ArchiveInteger( &amount ); if ( arc.Loading() ) { item = giveItem( name, amount ); item->CancelEventsOfType( EV_Weapon_GiveStartingAmmo ); item->ProcessPendingEvents(); } if ( item ) { item->ArchivePersistantData( arc ); } } // archive the ammo inventory if ( arc.Saving() ) { // count up the total number num = ammo_inventory.NumObjects(); } else { ammo_inventory.ClearObjectList(); } // archive the number arc.ArchiveInteger( &num ); // archive each item for( i = 1; i <= num; i++ ) { str name; int amount; int maxamount; Ammo * ptr; if ( arc.Saving() ) { ptr = ammo_inventory.ObjectAt( i ); name = ptr->getName(); amount = ptr->getAmount(); maxamount = ptr->getMaxAmount(); } arc.ArchiveString( &name ); arc.ArchiveInteger( &amount ); arc.ArchiveInteger( &maxamount ); if ( arc.Loading() ) { GiveAmmo( name, amount, false, maxamount ); } } for( i = 0; i < MAX_ACTIVE_WEAPONS; i++ ) { str name; if ( arc.Saving() ) { if ( activeWeaponList[ i ] ) { name = activeWeaponList[ i ]->getName(); } else { name = "none"; } } arc.ArchiveString( &name ); if ( arc.Loading() ) { if ( name != "none" ) { Weapon * weapon; weapon = ( Weapon * )FindItem( name ); if ( weapon ) { ChangeWeapon( weapon, ( weaponhand_t )i ); } } } } arc.ArchiveFloat( &health ); arc.ArchiveFloat( &max_health ); if ( arc.Loading() ) { currentBaseArmor = (Armor *)FindBaseArmor(); } } void Sentient::WeaponKnockedFromHands( void ) { str realname; realname = GetRandomAlias( "snd_lostweapon" ); if ( realname.length() > 1 ) { Sound( realname.c_str() , CHAN_VOICE ); } else { Sound( "snd_pain", CHAN_VOICE ); } } void Sentient::AddImmunity( Event *ev ) { str immune_string; int new_immunity; int number_of_immunities; int i; // No Longer using Immunities. Now using Resistances // A resistance of 100 is immune. However due to a large // amount of legacy code, the immunity functions will still // be inplace, however, they will instead be used as wrappers // for the resistance functions /* number_of_immunities = ev->NumArgs(); for ( i = 1 ; i <= number_of_immunities ; i++ ) { immune_string = ev->GetString( i ); new_immunity = MOD_NameToNum( immune_string ); if ( new_immunity != -1 ) immunities.AddUniqueObject( new_immunity ); } */ Resistance *new_resistance; number_of_immunities = ev->NumArgs(); for ( i = 1 ; i <= number_of_immunities ; i ++ ) { immune_string = ev->GetString( i ); new_immunity = MOD_NameToNum( immune_string ); if ( new_immunity != -1 ) { new_resistance = new Resistance; new_resistance->MODIndex = new_immunity; new_resistance->ResistanceAmount = 100; resistances.AddUniqueObject( new_resistance ); } } } void Sentient::AddResistance( Event *ev ) { Resistance *new_resistance = new Resistance; if(new_resistance) { new_resistance->MODIndex = MOD_NameToNum(ev->GetString(1)); new_resistance->ResistanceAmount = ev->GetInteger(2); resistances.AddUniqueObject( new_resistance ); } } void Sentient::RemoveImmunity( Event *ev ) { str immune_string; int old_immunity; int number_of_immunities; int i; Resistance *old_resistance; // No Longer using Immunities. Now using Resistances // A resistance of 100 is immune. However due to a large // amount of legacy code, the immunity functions will still // be inplace, however, they will instead be used as wrappers // for the resistance functions /* number_of_immunities = ev->NumArgs(); for ( i = 1 ; i <= number_of_immunities ; i++ ) { immune_string = ev->GetString( i ); old_immunity = MOD_NameToNum( immune_string ); if ( old_immunity != -1 && immunities.ObjectInList( old_immunity ) ) immunities.RemoveObject( old_immunity ); }*/ number_of_immunities = ev->NumArgs(); for ( i = 1 ; i <= number_of_immunities ; i++ ) { immune_string = ev->GetString( i ); old_immunity = MOD_NameToNum( immune_string ); for ( int x = resistances.NumObjects(); x > 0 ; x-- ) { old_resistance = resistances.ObjectAt(i); if( old_resistance->MODIndex == old_immunity ) { resistances.RemoveObjectAt( x ); delete old_resistance; old_resistance = 0; } } } } void Sentient::RemoveResistance( Event *ev ) { Resistance *old_resistance; int resistance_idx; resistance_idx = MOD_NameToNum( ev->GetString(1) ); for ( int i = resistances.NumObjects(); i > 0 ; i-- ) { old_resistance = resistances.ObjectAt(i); if( old_resistance->MODIndex == resistance_idx ) { resistances.RemoveObjectAt( i ); delete old_resistance; old_resistance = 0; } } } qboolean Sentient::Immune( int meansofdeath ) { int number_of_immunities, i; number_of_immunities = immunities.NumObjects(); for( i = 1 ; i <= number_of_immunities ; i++ ) { if ( meansofdeath == immunities.ObjectAt( i ) ) return true; } return false; } qboolean Sentient::Resistant( int meansofdeath ) { int number_of_resistances = resistances.NumObjects(); Resistance *checkResistance; for( int i = 1; i <= number_of_resistances; i++ ) { checkResistance = resistances.ObjectAt(i); if( meansofdeath == checkResistance->MODIndex ) return true; } return false; } int Sentient::GetResistanceModifier( int meansofdeath ) { int number_of_resistances = resistances.NumObjects(); Resistance *checkResistance; for( int i = 1; i <= number_of_resistances; i++ ) { checkResistance = resistances.ObjectAt(i); if( meansofdeath == checkResistance->MODIndex ) return checkResistance->ResistanceAmount; } return 0; } Ammo *Sentient::FindAmmoByName( const str &name ) { int count, i; count = ammo_inventory.NumObjects(); for ( i=1; i<=count; i++ ) { Ammo *ammo = ammo_inventory.ObjectAt( i ); if ( name == ammo->getName() ) { return ammo; } } return NULL; } int Sentient::AmmoIndex( const str &type ) { Ammo *ammo; ammo = FindAmmoByName( type ); if ( ammo ) return ammo->getIndex(); else return 0; } int Sentient::AmmoCount( const str &type ) { Ammo *ammo; if ( !type.length() ) return 0; ammo = FindAmmoByName( type ); if ( ammo ) return ammo->getAmount(); else return 0; } int Sentient::MaxAmmoCount( const str &type ) { Ammo *ammo; ammo = FindAmmoByName( type ); if ( ammo ) return ammo->getMaxAmount(); else return 0; } int Sentient::GiveAmmo( const str &type, int amount, bool pickedUp, int maxamount ) { Ammo *ammo; int startAmount = 0; int totalAmount = 0; int amountLeft; ammo = FindAmmoByName( type ); if ( ammo ) { // Add amount to current amount startAmount = ammo->getAmount(); if ( maxamount >= 0 ) ammo->setMaxAmount( maxamount ); ammo->setAmount( ammo->getAmount() + amount ); } else { // Create a new inventory entry with this name ammo = new Ammo; if ( maxamount >= 0 ) ammo->setMaxAmount( maxamount ); ammo->setAmount( amount ); ammo->setName( type ); ammo_inventory.AddObject( ammo ); } totalAmount = ammo->getAmount() - startAmount; amountLeft = amount - totalAmount; if ( amountLeft > 0 ) { int entnum; Item *item; Weapon *weapon; int roomLeftInClip; int amountToAdd; int i; // Try to give directly to weapons now for ( i = 1 ; i <= inventory.NumObjects() ; i++ ) { entnum = inventory.ObjectAt( i ); item = ( Item * )G_GetEntity( entnum ); if ( item->isSubclassOf( Weapon ) ) { weapon = (Weapon *)item; if ( weapon->HasFullClip() ) continue; if ( stricmp( weapon->GetAmmoType( FIRE_MODE1 ).c_str(), type.c_str() ) != 0 ) continue; roomLeftInClip = weapon->GetClipSize( FIRE_MODE1 ) - weapon->getAmmoInClip( FIRE_MODE1 ); if ( roomLeftInClip ) { if ( amountLeft > roomLeftInClip ) amountToAdd = roomLeftInClip; else amountToAdd = amountLeft; amountLeft -= amountToAdd; weapon->SetAmmoAmount( weapon->getAmmoInClip( FIRE_MODE1 ) + amountToAdd, FIRE_MODE1 ); } if ( amountLeft <= 0 ) { break; } } } } if ( pickedUp && this->isSubclassOf( Player ) && ( amount - amountLeft > 0 ) ) { int iconIndex; str imageName; imageName = "sysimg/icons/items/ammo_"; imageName += type; iconIndex = gi.imageindex( imageName ); ((Player *)this)->setItemText( iconIndex, va( "$$PickedUp$$ %d $$Ammo-%s$$\n", amount, type.c_str() ) ); //((Player *)this)->setItemText( iconIndex, va( "$$PickedUp$$ %d $$Ammo-%s$$\n", amount - amountLeft, type.c_str() ) ); //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$PickedUp$$ %d %s\n", totalAmount, type.c_str() ); } AmmoAmountChanged( ammo ); return amount - amountLeft; } int Sentient::UseAmmo( const str &type, int amount ) { int count, i; count = ammo_inventory.NumObjects(); for ( i=1; i<=count; i++ ) { Ammo *ammo = ammo_inventory.ObjectAt( i ); if ( type == ammo->getName() ) { int ammo_amount = ammo->getAmount(); // Less ammo than what we specified to use if ( ammo_amount < amount ) { ammo->setAmount( 0 ); AmmoAmountChanged( ammo ); return ammo_amount; } else { ammo->setAmount( ammo->getAmount() - amount ); AmmoAmountChanged( ammo ); return amount; } } } return 0; } void Sentient::AmmoAmountInClipChanged( const str &type, int amount_in_clip ) { int count, i; count = ammo_inventory.NumObjects(); for ( i=1; i<=count; i++ ) { Ammo *ammo = ammo_inventory.ObjectAt( i ); if ( type == ammo->getName() ) { AmmoAmountChanged( ammo, amount_in_clip ); } } } void Sentient::JumpXY( Event *ev ) { float forwardmove; float sidemove; float distance; float time; float speed; Vector yaw_forward; Vector yaw_left; forwardmove = ev->GetFloat( 1 ); sidemove = ev->GetFloat( 2 ); speed = ev->GetFloat( 3 ); Vector( 0.0f, angles.y, 0.0f ).AngleVectors( &yaw_forward, &yaw_left ); velocity = ( yaw_forward * forwardmove ) - ( yaw_left * sidemove ); distance = velocity.length(); velocity *= speed / distance; time = distance / speed; velocity[ 2 ] = sv_currentGravity->integer * time * 0.5f; } void Sentient::MeleeAttackStart( Event * ) { int i; Entity *victim; Vector mins, maxs, pos, end; Container potential_victimlist; mins = Vector( -8, -8, -8 ); maxs = Vector( 8, 8, 8 ); pos = centroid; end = centroid + Vector( orientation[0] ) * 96.0f; G_TraceEntities( pos, mins, maxs, end, &potential_victimlist, MASK_MELEE ); for( i = 1 ; i <= potential_victimlist.NumObjects() ; i++ ) { victim = potential_victimlist.ObjectAt( i ); Event *event = new Event(EV_Sentient_AddMeleeAttacker); event->AddEntity(this); victim->ProcessEvent(event); } SetMeleeAttack(true); } void Sentient::MeleeAttackEnd( Event * ) { SetMeleeAttack(false); } void Sentient::SetMeleeAttack(bool value) { in_melee_attack = value; } void Sentient::RangedAttackStart( Event * ) { in_ranged_attack = true; } void Sentient::RangedAttackEnd( Event * ) { in_ranged_attack = false; } void Sentient::BlockStart( Event * ) { in_block = true; } void Sentient::BlockEnd( Event * ) { in_block = false; } void Sentient::StunStart( Event * ) { in_stun = true; } void Sentient::StunEnd( Event * ) { in_stun = false; } void Sentient::ListInventory( void ) { int i,count; // Display normal inventory count = inventory.NumObjects(); gi.Printf( "'Name' : 'Amount'\n" ); for ( i=1; i<=count; i++ ) { int entnum = inventory.ObjectAt( i ); Item *item = ( Item * )G_GetEntity( entnum ); gi.Printf( "'%s' : '%d'\n", item->getName().c_str(), item->getAmount() ); } // Display ammo inventory count = ammo_inventory.NumObjects(); for ( i=1; i<=count; i++ ) { Ammo *ammo = ammo_inventory.ObjectAt( i ); gi.Printf( "'%s' : '%d'\n", ammo->getName().c_str(), ammo->getAmount() ); } } void Sentient::SetAttackBlocked( qboolean blocked ) { attack_blocked = blocked; attack_blocked_time = level.time; } void Sentient::SetMaxMouthAngle( Event *ev ) { max_mouth_angle = ev->GetFloat( 1 ); } //---------------------------------------------------------------- // Name: CatchOnFire // Class: Sentient // // Description: Sets the character on fire // // Parameters: Event *ev // // Returns: None //---------------------------------------------------------------- void Sentient::CatchOnFire( Event * ) { TryLightOnFire(MOD_FIRE, NULL); } void Sentient::TryLightOnFire( int meansofdeath, Entity *attacker ) { float chance; float min_time; float add_time; if ( !com_blood->integer ) return; // Get the base chance of catching on fire if ( meansofdeath == MOD_FIRESWORD ) chance = .5f; else chance = .05f; // Get some values based on whether or not the sentient is a player or an actor if ( this->isSubclassOf( Player ) ) { chance = chance / 2.0f; min_time = 4.0f; add_time = 2.0f; } else { min_time = 8.0f; add_time = 4.0f; // Going to use MOD_FIRE to force fire on Actors - not players if ( meansofdeath == MOD_FIRE ) chance = 1.0f; } // Make sure not immune to on_fire if ( Immune( MOD_ON_FIRE ) ) return; // See if we should catch the victim on fire if ( G_Random() < chance ) { Event *event; int number_of_tags; int i; const char *tag_name; int number_of_fires_to_add; float scale; float chanceToChange; if ( !on_fire ) { on_fire_tagnums[0] = -1; on_fire_tagnums[1] = -1; on_fire_tagnums[2] = -1; } if ( mass >= 200 ) number_of_fires_to_add = 3; else if ( mass >= 100 ) number_of_fires_to_add = 2; else number_of_fires_to_add = 1; if ( mass > 200 ) scale = 1.25f; else if ( mass >= 300 ) scale = 1.75f; else scale = 1.00f; chanceToChange = 0.05f; number_of_tags = gi.NumTags( edict->s.modelindex ); if ( number_of_tags ) { for ( i = 0 ; i < number_of_fires_to_add ; i++ ) { if ( !on_fire || ( G_Random() < chanceToChange ) ) { if ( on_fire && ( on_fire_tagnums[i] >= 0 ) ) { tag_name = gi.Tag_NameForNum( edict->s.modelindex, on_fire_tagnums[i] ); if ( _displayFireEffect ) { event = new Event( EV_RemoveAttachedModel ); event->AddString( tag_name ); event->AddFloat( 0.0f ); event->AddString( "models/fx_catchfire.tik" ); ProcessEvent( event ); } } on_fire_tagnums[i] = G_Random( number_of_tags ); tag_name = gi.Tag_NameForNum( edict->s.modelindex, on_fire_tagnums[i] ); if ( _displayFireEffect ) { event = new Event( EV_AttachModel ); event->AddString( "fx_catchfire.tik" ); event->AddString( tag_name ); event->AddFloat( scale ); ProcessEvent( event ); } } } if ( !on_fire ) { on_fire = true; fire_owner = attacker; PostEvent( EV_Sentient_OnFire, FRAMETIME ); next_catch_on_fire_time = level.time + .15f + G_Random( .2f ); on_fire_stop_time = level.time + min_time + G_Random( add_time ); } } } } void Sentient::OnFire( Event * ) { float damage; // See if we should stop being on fire if ( !on_fire || ( on_fire_stop_time <= level.time ) || ( gi.pointcontents( origin, 0 ) & MASK_WATER ) ) { // Stop the fire ProcessEvent( EV_Sentient_StopOnFire ); return; } // Cause a little bit of damage if ( this->isSubclassOf( Player ) ) damage = 0.05; else damage = 0.5; if ( fire_owner && fire_owner->isSubclassOf( Player ) ) { Player *player = (Player *)(Entity *)fire_owner; damage = player->getDamageDone( damage, MOD_ON_FIRE, false ); } Damage( fire_owner, fire_owner, damage, vec_zero, vec_zero, vec_zero, 0, 0, MOD_ON_FIRE ); // Try to set other sentients on fire if ( next_catch_on_fire_time <= level.time ) { MeleeAttack( centroid, centroid, 0.05f, fire_owner, MOD_FIRE, maxs[0] + 10, -maxs[2] / 2, maxs[2] / 2, 0.0f, false ); next_catch_on_fire_time = level.time + .15f + G_Random( .2f ); } // Call this event again next frame PostEvent( EV_Sentient_OnFire, FRAMETIME ); } void Sentient::StopOnFire( Event * ) { int i; const char *tag_name; Event *event; for( i = 0 ; i < 3 ; i++ ) { if ( ( on_fire_tagnums[i] >= 0 ) && ( on_fire_tagnums[i] < gi.NumTags( edict->s.modelindex ) ) ) { tag_name = gi.Tag_NameForNum( edict->s.modelindex, on_fire_tagnums[i] ); if ( _displayFireEffect ) { event = new Event( EV_RemoveAttachedModel ); event->AddString( tag_name ); event->AddFloat( 0.0f ); event->AddString( "models/fx_catchfire.tik" ); ProcessEvent( event ); } } } on_fire_tagnums[ 0 ] = -1; on_fire_tagnums[ 1 ] = -1; on_fire_tagnums[ 2 ] = -1; on_fire = false; CancelEventsOfType( EV_Sentient_OnFire ); } void Sentient::SpawnBloodyGibs( Event *ev ) { str gib_name; int number_of_gibs; float scale; Entity *ent; str real_gib_name; if ( !com_blood->integer ) return; //if ( GetActorFlag( ACTOR_FLAG_FADING_OUT ) ) // return; gib_name = GetGibName(); if ( !gib_name.length() ) return; // Determine the number of gibs to spawn if ( ev->NumArgs() > 0 ) { number_of_gibs = ev->GetInteger( 1 ); } else { if ( max_gibs == 0 ) return; if ( deadflag ) //number_of_gibs = G_Random( max_gibs / 2 ) + 1; number_of_gibs = max_gibs; else number_of_gibs = (int)G_Random( (float)max_gibs ) + 1; } // Make sure we don't have too few or too many gibs if ( ( number_of_gibs <= 0 ) || ( number_of_gibs > 9 ) ) return; if ( ev->NumArgs() > 1 ) { scale = ev->GetFloat( 2 ); } else { // Calculate a good scale value if ( mass <= 50 ) scale = 1.0; else if ( mass <= 100 ) scale = 1.1; else if ( mass <= 250 ) scale = 1.2; else scale = 1.3; } // Spawn the gibs real_gib_name = gib_name; real_gib_name += number_of_gibs; real_gib_name += ".tik"; ent = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); ent->setModel( real_gib_name.c_str() ); ent->setScale( scale ); ent->setOrigin( centroid ); ent->animate->RandomAnimate( "idle" ); ent->PostEvent( EV_Remove, 1.0f ); Sound( "snd_decap", CHAN_BODY, 1.0f, 300.0f ); } void Sentient::SetMaxGibs( Event *ev ) { max_gibs = ev->GetInteger( 1 ); } void Sentient::GetStateAnims( Container *c ) { Q_UNUSED(c); } void Sentient::CheckAnimations( Event * ) { int i,j; Containerco; const char *cs; GetStateAnims( &co ); gi.DPrintf( "Unused Animations in TIKI\n" ); gi.DPrintf( "-------------------------\n" ); for( i=0; iNumAnims(); i++ ) { const char *c; c = gi.Anim_NameForNum( edict->s.modelindex, i ); for ( j=1; j<=co.NumObjects(); j++ ) { cs = co.ObjectAt( j ); if ( !stricmp( c, cs ) ) { goto out; } else if ( !strnicmp( c, cs, strlen( cs ) ) ) // partial match { int state_len = strlen( cs ); // Animation in tik file is longer than the state machine's anim if ( (int)strlen( c ) > state_len ) { if ( c[state_len] != '_' ) // If next character is an '_' then no match { goto out; } } else { goto out; } } } // No match made gi.DPrintf( "%s used in TIK file but not statefile\n", c ); out: ; } gi.DPrintf( "Unknown Animations in Statefile\n" ); gi.DPrintf( "-------------------------------\n" ); for ( j=1; j<=co.NumObjects(); j++ ) { if ( !animate->HasAnim( co.ObjectAt( j ) ) ) { gi.DPrintf( "%s in statefile is not in TIKI\n", co.ObjectAt( j ) ); } } } void Sentient::SetStateFile( Event *ev ) { Event *temp=ev; if (ev != NULL) { temp=NULL; } // This function is not defined for a Sentient // If Sentient was an abstract class then this would be pure virtual } void Sentient::ReceivedItem( Item * ) { } void Sentient::RemovedItem( Item * ) { } void Sentient::AmmoAmountChanged( Ammo * ammo, int ammo_in_clip ) { Q_UNUSED(ammo); Q_UNUSED(ammo_in_clip); } // // Armor Stuff // void Sentient::SetArmorValue(int armorVal) { if ( currentBaseArmor ) currentBaseArmor->setAmount( armorVal ); } int Sentient::GetArmorValue() { if ( currentBaseArmor ) return currentBaseArmor->getAmount(); return 0; } //---------------------------------------------------------------- // Name: setViewMode // Class: Sentient // // Description: Sets the players current view mode // // Parameters: Event *ev - event (the name of the view mode to go to) // // Returns: None //---------------------------------------------------------------- void Sentient::setViewMode( Event *ev ) { str viewMode; bool override; viewMode = ev->GetString( 1 ); if ( ev->NumArgs() > 1 ) override = ev->GetBoolean( 2 ); else override = true; if ( _viewMode && !override ) return; setViewMode( ev->GetString( 1 ) ); } //---------------------------------------------------------------- // Name: setViewMode // Class: Sentient // // Description: Sets the players current view mode // // Parameters: const str &viewModeName - the name of the view mode to go to // // Returns: None //---------------------------------------------------------------- void Sentient::setViewMode( const str &viewModeName ) { unsigned int newViewMode; newViewMode = gi.GetViewModeMask( viewModeName.c_str() ); if ( _viewMode != newViewMode ) { _viewMode = newViewMode; if ( this->isSubclassOf( Player ) ) { gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$SwitchedTo$$ $$ViewMode-%s$$ $$Viewmode$$", viewModeName.c_str() ); } } } //---------------------------------------------------------------- // Name: getViewMode // Class: Sentient // // Description: Gets the current view mode of this sentient // // Parameters: None // // Returns: unsigned int - the current view mode bit(s) //---------------------------------------------------------------- unsigned int Sentient::getViewMode( void ) { return _viewMode; } //---------------------------------------------------------------- // Name: getActiveWeaponName // Class: Sentient // // Description: Gets the name of the active weapon // // Parameters: Event *ev - contains name of the hand (left, right, or dual) // // Returns: str (through the event) - name of the active weapon //---------------------------------------------------------------- void Sentient::getActiveWeaponName( Event *ev ) { weaponhand_t hand = WEAPON_ANY; str weaponName; // See which hand we are concerned about if ( ev->NumArgs() > 0 ) { hand = WeaponHandNameToNum( ev->GetString( 1 ) ); } else { hand = WEAPON_ANY; } getActiveWeaponName(hand, weaponName); ev->ReturnString(weaponName); } void Sentient::getActiveWeaponName( weaponhand_t hand, str& weaponName) { Weapon* weapon; weaponName = "None"; // Get the name of the weapon if ( hand == WEAPON_ANY ) { // Try the left hand weapon = GetActiveWeapon( WEAPON_LEFT ); if ( weapon ) weaponName = weapon->getName(); // Try the right hand if ( !weapon ) { weapon = GetActiveWeapon( WEAPON_RIGHT ); if ( weapon ) weaponName = weapon->getName(); } // Try the both hands if ( !weapon ) { weapon = GetActiveWeapon( WEAPON_DUAL ); if ( weapon ) weaponName = weapon->getName(); } } else if ( ( hand == WEAPON_LEFT ) || ( hand == WEAPON_RIGHT ) || ( hand == WEAPON_DUAL ) ) { weapon = GetActiveWeapon( hand ); if ( weapon ) weaponName = weapon->getName(); } } //-------------------------------------------------------------- // // Name: SwipeOn // Class: Sentient // // Description: Turn the weapon swipes on for this sentient's weapons // // Parameters: Event *ev // // Returns: None // //-------------------------------------------------------------- void Sentient::SwipeOn( Event *ev ) { Weapon *weapon; weaponhand_t hand; hand = WeaponHandNameToNum( ev->GetString( 1 ) ); if ( hand == WEAPON_ERROR ) return; weapon = GetActiveWeapon( hand ); if ( weapon ) weapon->SetAnim( "swipeon", NULL ); } //-------------------------------------------------------------- // // Name: SwipeOff // Class: Sentient // // Description: Turn the weapon swipes off for this sentient's weapons // // Parameters: Event *ev // // Returns: None // //-------------------------------------------------------------- void Sentient::SwipeOff( Event *ev ) { Weapon *weapon; weaponhand_t hand; hand = WeaponHandNameToNum( ev->GetString( 1 ) ); if ( hand == WEAPON_ERROR ) return; weapon = GetActiveWeapon( hand ); if ( weapon ) weapon->SetAnim( "swipeoff", NULL ); } //-------------------------------------------------------------- // Name: AddHealthOverTime() // Class: Sentient // // Description: Adds the specified percentage of health right away, // then posts an event to AddHealthAtInterval to // kick start a health regeneration event chain // // Parameters: Event *ev // // Returns: None //-------------------------------------------------------------- void Sentient::AddHealthOverTime( Event *ev ) { float percentageToAdd = ev->GetFloat( 1 ); float percentageToRegen = ev->GetFloat( 2 ); float interval = ev->GetFloat( 3 ); float maxPercentage = ev->GetFloat( 4 ); float addHealth; float maxHealth; Event *regenEvent; //Translate our percentages into real numbers addHealth = percentageToAdd * max_health; maxHealth = maxPercentage * max_health; //Check if adding the initial health will put us //over our maximum allowed if ( (addHealth + health) >= maxHealth ) { health = maxHealth; return; } else health+= addHealth; //Now Generate our AddHealthAtInterval Event //which will continue the regeneration process regenEvent = new Event(EV_Sentient_HealAtInterval); if ( !regenEvent) { assert(regenEvent); return; } regenEvent->AddFloat(percentageToRegen); regenEvent->AddFloat(interval); regenEvent->AddFloat(maxPercentage); PostEvent( regenEvent, interval ); } //-------------------------------------------------------------- // Name: AddHealthAtInterval() // Class: Sentient // // Description: Adds the regen percentage to the health // of the percentage... if the health is // still not the maxPercentage, then it // will generate a new event of its own // type // // Parameters: Event *ev // // Returns: None //-------------------------------------------------------------- void Sentient::AddHealthAtInterval( Event *ev ) { float percentageToRegen = ev->GetFloat( 1 ); float interval = ev->GetFloat( 2 ); float maxPercentage = ev->GetFloat( 3 ); float addHealth; float maxHealth; Event *regenEvent; //Translate our percentages into real numbers addHealth = percentageToRegen * max_health; maxHealth = maxPercentage * max_health; //Check if adding the initial health will put us //over our maximum allowed if ( (addHealth + health) >= maxHealth ) { SetHealth(maxHealth); return; } else SetHealth(health + addHealth); //Now Generate a new AddHealthAtInterval Event //which will continue the regeneration process regenEvent = new Event(EV_Sentient_HealAtInterval); if ( !regenEvent) { assert(regenEvent); return; } regenEvent->AddFloat(percentageToRegen); regenEvent->AddFloat(interval); regenEvent->AddFloat(maxPercentage); PostEvent( regenEvent, interval ); } void Sentient::SetHealth( float newHealth ) { health = newHealth; if ( ( health / max_health ) > GetCriticalHealthPercentage() ) _canSendInjuredEvent = true; } //---------------------------------------------------------------- // Name: HeadWatchAllowed // Class: Sentient // // Description: Sets whether to headwatch or not, defaults to true // // Parameters: Event *ev // // Returns: None //---------------------------------------------------------------- void Sentient::HeadWatchAllowed(Event *ev) { if ( ev->NumArgs() > 0 ) HeadWatchAllowed( ev->GetBoolean( 1 ) ); else HeadWatchAllowed( true ); } void Sentient::HeadWatchAllowed( bool allowed ) { _headWatchAllowed = allowed; } //-------------------------------------------------------------- // // Name: SetWeaponAnim // Class: Sentient // // Description: Put the weapon in the specified animation // // Parameters: Event *ev // // Returns: None // //-------------------------------------------------------------- void Sentient::SetWeaponAnim( Event *ev ) { Weapon *weapon; weaponhand_t hand; str animname; animname = ev->GetString( 1 ); hand = WeaponHandNameToNum( ev->GetString( 2 ) ); if ( hand == WEAPON_ERROR ) return; weapon = GetActiveWeapon( hand ); if ( weapon ) weapon->playAnim( animname ); } //-------------------------------------------------------------- // Name: SetDamageThreshold() // Class: Sentient // // Description: Sets Values on our damageThreshold structure // // Parameters: Event *ev // // Returns: None //-------------------------------------------------------------- void Sentient::SetDamageThreshold( Event *ev ) { _damageThreshold.maxDamage = ev->GetFloat( 1 ); _damageThreshold.duration = ev->GetFloat( 2 ); _damageThreshold.startTime = 0; } //-------------------------------------------------------------- // Name: CheckDamageThreshold() // Class: Sentient // // Description: Checks if we have hit our damageThreshold // meaning, we have taken at least X damage // in Y amount of time. // // Parameters: float damageValue // // Returns: None //-------------------------------------------------------------- void Sentient::CheckDamageThreshold( float damageValue ) { //First Check if we even need to bother doing further //checks float timeDiff; if ( _damageThreshold.maxDamage < 0 ) return; if( _damageThreshold.startTime == 0 ) _damageThreshold.startTime = level.time; timeDiff = level.time - _damageThreshold.startTime; //Now we check if our timeDiff is greater than our //damageThreshold's duration. If it is, then obviously //we didn't hit our threshold, so we need to update //our starttime and currentDamage; if ( _damageThreshold.duration >= 0 && timeDiff > _damageThreshold.duration ) { _damageThreshold.startTime = level.time; _damageThreshold.currentDamage = damageValue; return; } //Well, we're still within our timeframe to hit our //threshold, so we'll add the damageValue to our //damageThreshold's currentDamage and check to see //if we hit the wall. _damageThreshold.currentDamage += damageValue; if ( _damageThreshold.currentDamage >= _damageThreshold.maxDamage ) { //Yay, more casting... I'm so excited about this if ( this->isSubclassOf(Actor) ) { Actor *actor; actor = (Actor*)this; actor->AddStateFlag(STATE_FLAG_DAMAGE_THRESHOLD_EXCEEDED); } //Clear out our structure ClearDamageThreshold(); } } //-------------------------------------------------------------- // Name: DisplayFireEffect // Class: Sentient // // Description: Sets the flag to display the fire effect while this entity // is on fire. Note: this doesn't prevent the "on fire" state, // nor does it prevent the damage from the fire. It's just // the fire effect itself that's not displayed. // // Parameters: Event *ev // // Returns: None //-------------------------------------------------------------- void Sentient::DisplayFireEffect( Event *ev ) { if ( ev->NumArgs() > 0 ) _displayFireEffect = ev->GetBoolean( 1 ); else _displayFireEffect = true; } void Sentient::ClearDamageThreshold( Event * ) { ClearDamageThreshold(); } void Sentient::ClearDamageThreshold() { _damageThreshold.startTime = level.time; _damageThreshold.currentDamage = 0.0f; } //-------------------------------------------------------------- // // Name: DropItemEvent // Class: Sentient // // Description: Drops the specified item to the ground as a pickup // // Parameters: Event *ev // -- str itenName // // Returns: None // //-------------------------------------------------------------- void Sentient::DropItemEvent( Event *ev ) { int num, i; Item *item; str itemName = ev->GetString( 1 ); num = inventory.NumObjects(); for( i = num; i >= 1; i-- ) { item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); if ( item->getName() == itemName ) { DropItem(item); return; } } } //-------------------------------------------------------------- // // Name: DropItem // Class: Sentient // // Description: Drops the specified item. This function // may grow. // // Parameters: Item *itemToDrop -- Item pointer to drop // // Returns: None // //-------------------------------------------------------------- void Sentient::DropItem( Item *itemToDrop ) { if ( !itemToDrop ) return; itemToDrop->Drop(); } //-------------------------------------------------------------- // // Name: DropItemsOnDeath // Class: Sentient // // Description: Drops the item/weapon when the sentient dies // // Parameters: None // // Returns: None // //-------------------------------------------------------------- void Sentient::DropItemsOnDeath() { Weapon *weap = 0; weap = GetActiveWeapon(WEAPON_RIGHT); if ( !weap ) weap = GetActiveWeapon(WEAPON_LEFT); if ( weap ) { GameplayManager *gpm = GameplayManager::getTheGameplayManager(); if ( gpm->hasProperty(weap->getArchetype(), "dropchance") ) { float dropchance = gpm->getFloatValue(weap->getArchetype(), "dropchance"); if ( G_Random() < dropchance ) DropItem(weap); } if ( gpm->hasProperty(getArchetype(), "potionchance") ) { str model; float potionchance = gpm->getFloatValue(getArchetype(), "potionchance"); if ( G_Random() < potionchance ) { model = gpm->getStringValue( "HealthPotion", "model"); } else if ( G_Random() < potionchance * 2.0f ) { model = gpm->getStringValue( "RedPotion", "model" ); } if ( model.length() ) { // Spawn the potion SpawnArgs args; Entity *ent; Item *item; args.setArg( "model", model ); ent = args.Spawn(); if ( !ent || !ent->isSubclassOf( Item ) ) return; item = (Item *)ent; item->setOrigin( centroid ); item->ProcessPendingEvents(); item->PlaceItem(); item->setOrigin( centroid ); item->velocity = Vector( G_CRandom( 100.0f ), G_CRandom( 100.0f ), 200.0f + G_Random( 200.0f ) ); item->edict->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; item->targetname = targetname; item->targetname += "_item"; item->SetTargetName( item->targetname ); } } } } //-------------------------------------------------------------- // // Name: WeaponEffectsAndSound // Class: Sentient // // Description: Plays a weapon specified tiki effect at pos. // Also plays a sound. All this comes from the gameplay database // // Parameters: Entity *weapon -- The weapon to use (only need Entity portion) // const str& objname -- Object name to reference // Vector pos -- location for the tiki effect // // Returns: None // //-------------------------------------------------------------- void Sentient::WeaponEffectsAndSound( Entity *weapon, const str& objname, Vector pos ) { if ( !weapon ) return; GameplayManager *gpm = GameplayManager::getTheGameplayManager(); str scopestr = weapon->getArchetype() + "." + objname; str effect = gpm->getStringValue(scopestr, "tikifx"); if ( effect.length() ) Entity::SpawnEffect(effect, pos, vec_zero, 2.0f); str snd = gpm->getStringValue(scopestr, "wav"); if ( snd.length() ) { float minpitch = gpm->getFloatValue(scopestr, "minpitch"); float maxpitch = gpm->getFloatValue(scopestr, "maxpitch"); float pitch = G_Random(maxpitch - minpitch) + minpitch; int channel = CHAN_BODY; float volume = -1.0f; float mindist = -1.0f; if ( gpm->hasProperty(scopestr,"channel") ) channel = (int)gpm->getFloatValue(scopestr, "channel"); if ( gpm->hasProperty(scopestr,"volume") ) volume = (int)gpm->getFloatValue(scopestr, "volume"); if ( gpm->hasProperty(scopestr,"mindist") ) mindist = (int)gpm->getFloatValue(scopestr, "mindist"); weapon->Sound(snd, channel, volume, mindist, NULL, pitch); } } void Sentient::SetArmorActiveStatus( Event *ev ) { bool status = ev->GetBoolean( 1 ); SetArmorActiveStatus( status ); } void Sentient::SetArmorActiveStatus(bool status) { if ( currentBaseArmor ) { Event* statusEvent; statusEvent = new Event ( EV_Armor_SetActiveStatus ); statusEvent->AddInteger( status ); currentBaseArmor->ProcessEvent ( statusEvent ); } } void Sentient::SetArmorMultiplier( Event *ev ) { if ( currentBaseArmor ) { float multiplier = ev->GetFloat( 1 ); SetArmorMultiplier( multiplier ); } } void Sentient::SetArmorMultiplier( float multiplier ) { Event* multiplierEvent; multiplierEvent = new Event ( EV_Armor_SetMultiplier ); multiplierEvent->AddFloat( multiplier ); if ( currentBaseArmor ) currentBaseArmor->ProcessEvent ( multiplierEvent ); } void Sentient::AddToMyArmor( Event *ev ) { AddToMyArmor( ev->GetFloat( 1 ) ); } void Sentient::AddToMyArmor( float amountToAdd ) { if ( currentBaseArmor ) currentBaseArmor->setAmount( currentBaseArmor->getAmount() + amountToAdd ); } void Sentient::SetMyArmorAmount( Event *ev ) { SetMyArmorAmount( ev->GetFloat(1) ); } void Sentient::SetMyArmorAmount( float amount ) { if ( currentBaseArmor ) currentBaseArmor->setAmount( amount ); } void Sentient::ArmorEvent( Event *ev ) { if ( !currentBaseArmor ) return; Event *e; e = new Event( ev->GetToken( 1 ) ); for( int i=2; i<=ev->NumArgs(); i++ ) e->AddToken( ev->GetToken( i ) ); currentBaseArmor->ProcessEvent( e ); } void Sentient::SetHateModifier( Event *ev ) { float modifier; modifier = ev->GetFloat( 1 ); SetHateModifier ( modifier ); } void Sentient::SetHateModifier( float modifier ) { _hateModifier = modifier; } float Sentient::GetHateModifier() { return _hateModifier; } void Sentient::cacheStateMachineAnims( Event *ev ) { G_CacheStateMachineAnims( this, ev->GetString( 1 ) ); } void Sentient::freeConditionals( Container &conditionalsToDelete ) { int i; Conditional *conditional; for ( i = conditionalsToDelete.NumObjects() ; i > 0 ; i-- ) { conditional = conditionalsToDelete.ObjectAt( i ); if ( conditional ) { delete conditional; } } conditionalsToDelete.FreeObjectList(); }