#include "precompiled.h" #pragma hdrstop #include "prey_local.h" /********************************************************************** hhFuncParmAccessor **********************************************************************/ CLASS_DECLARATION( idClass, hhFuncParmAccessor ) END_CLASS /* ================ hhFuncParmAccessor::hhFuncParmAccessor ================ */ hhFuncParmAccessor::hhFuncParmAccessor() { function = NULL; } /* ================ hhFuncParmAccessor::hhFuncParmAccessor ================ */ hhFuncParmAccessor::hhFuncParmAccessor( const hhFuncParmAccessor* accessor ) { assert( accessor ); SetInfo( accessor->GetReturnKey(), accessor->GetFunction(), accessor->GetParms() ); } /* ================ hhFuncParmAccessor::hhFuncParmAccessor ================ */ hhFuncParmAccessor::hhFuncParmAccessor( const hhFuncParmAccessor& accessor ) { SetInfo( accessor.GetReturnKey(), accessor.GetFunction(), accessor.GetParms() ); } /* ================ hhFuncParmAccessor::SetInfo ================ */ void hhFuncParmAccessor::SetInfo( const char* returnKey, const function_t* func, const idList& parms ) { SetFunction( func ); SetParms( parms ); SetReturnKey( returnKey ); Verify(); } void hhFuncParmAccessor::InsertParm_String( const char *text, int index ) { InsertParm( text, index ); } void hhFuncParmAccessor::InsertParm_Float( float value, int index ) { InsertParm( va("%.2f", value), index ); } void hhFuncParmAccessor::InsertParm_Int( int value, int index ) { InsertParm( va("%d", value), index ); } void hhFuncParmAccessor::InsertParm_Vector( const idVec3 &vec, int index ) { InsertParm( va("'%.2f %.2f %.2f'", vec[0], vec[1], vec[2]), index ); } void hhFuncParmAccessor::InsertParm_Entity( const idEntity *ent, int index ) { if ( ent ) { InsertParm( ent->GetName(), index ); } } void hhFuncParmAccessor::SetParm_Entity( const idEntity *ent, int index ) { if ( ent ) { parms.AssureSize(index+1); parms[index] = ent->GetName(); } } void hhFuncParmAccessor::SetParm_String( const char *str, int index ) { if ( str ) { parms.AssureSize(index+1); parms[index] = str; } } void hhFuncParmAccessor::InsertParm( const char *text, int index ) { parms.Insert( text, index ); } /* ================ hhFuncParmAccessor::ParseFunctionKeyValue ================ */ void hhFuncParmAccessor::ParseFunctionKeyValue( const char* value ) { if( !value || !value[0] ) { return; } hhUtils::SplitString( idStr(value), GetParms(), ' ' ); ParseFunctionKeyValue( GetParms() ); } /* ================ hhFuncParmAccessor::ParseFunctionKeyValue ================ */ void hhFuncParmAccessor::ParseFunctionKeyValue( idList& valueList ) { SetFunction( FindFunction(valueList[0]) ); if( GetFunction() ) { valueList.RemoveIndex( 0 );//Remove function name } else { gameLocal.Warning("hhFuncParmAccessor: Function %s did not exist", (const char*)valueList[0]); SetReturnKey( valueList[0] ); valueList.RemoveIndex( 0 );//Remove return key SetFunction( FindFunction(valueList[0]) ); valueList.RemoveIndex( 0 );//Remove function name } } /* ================ hhFuncParmAccessor::FindFunction ================ */ function_t* hhFuncParmAccessor::FindFunction( const char* funcname ) { function_t* func = NULL; if( funcname && *funcname ) { func = gameLocal.program.FindFunction( funcname ); } return func; } /* ================ hhFuncParmAccessor::CallFunction ================ */ void hhFuncParmAccessor::CallFunction( idDict& returnDict ) { if( !GetFunction() ) { return; } hhThread* scriptThread = new hhThread; if( !scriptThread ) { return; } scriptThread->ClearStack(); if( !scriptThread->ParseAndPushArgsOntoStack(GetParms(), GetFunction()) ) { SAFE_DELETE_PTR( scriptThread ); //This is needed because we won't be removed by Execute return; } scriptThread->CallFunction( GetFunction(), false ); scriptThread->Execute(); idTypeDef* returnType = GetReturnType(); if( !GetReturnKey() || !GetReturnKey()[0] ) { return; } if( returnType == &type_void ) { gameLocal.Warning( "Return key assigned for function (%s) that returns void\n", GetFunctionName() ); return; } returnDict.Set( GetReturnKey(), returnType->GetReturnValueAsString(gameLocal.program) ); } /* ================ hhFuncParmAccessor::Verify ================ */ void hhFuncParmAccessor::Verify() { if( !GetFunction() ) { return; } int numParms = GetFunction()->def->TypeDef()->NumParameters(); const char* parm = NULL; idTypeDef* parmType = NULL; if( numParms != GetParms().Num() ) { gameLocal.Warning( "Wrong # of parms passed for function %s", GetFunctionName() ); return; } if( GetReturnType() == &type_void && GetReturnKey() && GetReturnKey()[0] ) { gameLocal.Warning( "Used Return key %s for function %s that returns void", GetReturnKey(), GetFunctionName() ); return; } for( int ix = 0; ix < numParms; ++ix ) { parmType = GetParmType( ix ); parm = GetParm( ix ); if( !parmType->VerifyData(parm) ) { gameLocal.Warning( "%s is not Type %s", parm, parmType->Name() ); return; } } } /* ================ hhFuncParmAccessor::GetFunctionName ================ */ const char* hhFuncParmAccessor::GetFunctionName() const { return GetFunction()->Name(); } /* ================ hhFuncParmAccessor::GetFunction ================ */ const function_t* hhFuncParmAccessor::GetFunction() const { return function; } /* ================ hhFuncParmAccessor::GetReturnKey ================ */ const char* hhFuncParmAccessor::GetReturnKey() const { return returnKey.c_str(); } /* ================ hhFuncParmAccessor::GetReturnType ================ */ idTypeDef* hhFuncParmAccessor::GetReturnType() const { return GetFunction()->def->TypeDef()->ReturnType(); } /* ================ hhFuncParmAccessor::GetParm ================ */ const char* hhFuncParmAccessor::GetParm( int index ) const { return parms[ index ]; } /* ================ hhFuncParmAccessor::GetParmType ================ */ idTypeDef* hhFuncParmAccessor::GetParmType( int index ) const { return GetFunction()->def->TypeDef()->GetParmType( index ); } /* ================ hhFuncParmAccessor::GetParms ================ */ const idList& hhFuncParmAccessor::GetParms() const { return parms; } /* ================ hhFuncParmAccessor::GetParms ================ */ idList& hhFuncParmAccessor::GetParms() { return parms; } /* ================ hhFuncParmAccessor::SetFunction ================ */ void hhFuncParmAccessor::SetFunction( const function_t* func ) { function = func; } /* ================ hhFuncParmAccessor::SetParms ================ */ void hhFuncParmAccessor::SetParms( const idList& parms ) { this->parms = parms; } /* ================ hhFuncParmAccessor::SetReturnKey ================ */ void hhFuncParmAccessor::SetReturnKey( const char* key ) { returnKey = key; } /* ================ hhFuncParmAccessor::Save ================ */ void hhFuncParmAccessor::Save( idSaveGame *savefile ) const { int num = parms.Num(); savefile->WriteInt( num ); for( int i = 0; i < num; i++ ) { savefile->WriteString( parms[i] ); } savefile->WriteString( returnKey ); savefile->WriteString( function ? function->Name() : "" ); } /* ================ hhFuncParmAccessor::Restore ================ */ void hhFuncParmAccessor::Restore( idRestoreGame *savefile ) { int num; savefile->ReadInt( num ); idStr tmp; parms.Clear(); for( int i = 0; i < num; i++ ) { savefile->ReadString( tmp ); parms.Append( tmp ); } savefile->ReadString( returnKey ); savefile->ReadString( tmp ); if( tmp.Length() > 0 ) { function = FindFunction( tmp ); HH_ASSERT( function ); } else { function = NULL; } } /********************************************************************** hhTrigger **********************************************************************/ const idEventDef EV_Trigger("", "e"); const idEventDef EV_Retrigger("", "e"); const idEventDef EV_UnTrigger("", NULL); const idEventDef EV_PollForUntouch("", NULL); CLASS_DECLARATION( idEntity, hhTrigger ) EVENT( EV_Enable, hhTrigger::Event_Enable ) EVENT( EV_Disable, hhTrigger::Event_Disable ) EVENT( EV_Activate, hhTrigger::Event_Activate) EVENT( EV_Deactivate, hhTrigger::Event_Deactivate) EVENT( EV_Touch, hhTrigger::Event_Touch) EVENT( EV_Trigger, hhTrigger::Event_TriggerAction ) EVENT( EV_Retrigger, hhTrigger::Event_Retrigger ) EVENT( EV_UnTrigger, hhTrigger::Event_UnTriggerAction ) EVENT( EV_PollForUntouch, hhTrigger::Event_PollForUntouch ) EVENT( EV_PostSpawn, hhTrigger::Event_PostSpawn ) END_CLASS /* ================ hhTrigger::Activate the trigger was just activated by activator ================ */ void hhTrigger::Activate( idEntity *activator ) { int triggerDelay = 0; unTriggerActivator = activator; // if not enabled, return if( !bEnabled ) { return; } bActive = true; if ( nextTriggerTime > gameLocal.time ) { // can't retrigger until the wait is over return; } // see if this trigger requires an item if ( !gameLocal.RequirementMet( activator, requires, removeItem ) ) { return; } if ( delay > 0 ) { // don't allow it to trigger again until our delay has passed triggerDelay = hhMath::hhMax( 0, SEC2MS(delay + randomDelay * gameLocal.random.CRandomFloat()) ); nextTriggerTime = gameLocal.time + triggerDelay + 1; PostEventMS( &EV_Trigger, triggerDelay, activator ); } else { //Changed so we check to see if the activator is still with in the trigger volume ProcessEvent( &EV_Trigger, activator ); } } /* ================ hhTrigger::Spawn ================ */ void hhTrigger::Spawn(void) { unTriggerActivator=NULL; bActive=false; spawnArgs.GetFloat( "wait", "0.5", wait ); spawnArgs.GetFloat( "random", "0", random ); spawnArgs.GetFloat( "delay", "0", delay ); spawnArgs.GetFloat( "randomeDelay", "0", randomDelay ); spawnArgs.GetString( "requires", "", requires ); spawnArgs.GetInt( "removeItem", "0", removeItem ); triggerBehavior = (triggerBehavior_t)spawnArgs.GetInt( "triggerBehavior" ); spawnArgs.GetBool( "noTouch", "0", noTouch ); spawnArgs.GetFloat( "uncalldelay", "0.2", untouchGranularity ); spawnArgs.GetFloat( "refire", "-1", refire ); spawnArgs.GetBool( "enabled", "1", initiallyEnabled ); spawnArgs.GetBool( "untrigger", "0", bUntrigger ); spawnArgs.GetBool( "always_trigger", "0", alwaysTrigger ); spawnArgs.GetBool( "isSimpleBox", "1", isSimpleBox ); spawnArgs.GetBool( "noVehicles", "0", bNoVehicles ); GetTriggerClasses( spawnArgs ); nextTriggerTime = 0; PostEventMS( (initiallyEnabled) ? &EV_Enable : &EV_Disable, 0); PostEventMS( &EV_PostSpawn, 0 ); } /* ================ hhTrigger::Save ================ */ void hhTrigger::Save( idSaveGame *savefile ) const { savefile->WriteBool( bActive ); savefile->WriteBool( bEnabled ); savefile->WriteFloat( untouchGranularity ); savefile->WriteFloat( wait ); savefile->WriteFloat( random ); savefile->WriteFloat( delay ); savefile->WriteFloat( randomDelay ); savefile->WriteFloat( refire ); savefile->WriteInt( nextTriggerTime ); savefile->WriteBool( alwaysTrigger ); savefile->WriteBool( isSimpleBox ); savefile->WriteBool( bNoVehicles ); savefile->WriteStaticObject( funcInfo ); savefile->WriteStaticObject( unfuncInfo ); savefile->WriteStaticObject( funcRefInfo ); savefile->WriteStaticObject( unfuncRefInfo ); savefile->WriteStaticObject( funcRefActivatorInfo ); savefile->WriteStaticObject( unfuncRefActivatorInfo ); savefile->WriteString( requires ); savefile->WriteInt( removeItem ); savefile->WriteBool( noTouch ); savefile->WriteBool( initiallyEnabled ); savefile->WriteBool( bUntrigger ); unTriggerActivator.Save( savefile ); savefile->WriteStringList( TriggerClasses ); savefile->WriteInt( triggerBehavior ); } /* ================ hhTrigger::Restore ================ */ void hhTrigger::Restore( idRestoreGame *savefile ) { savefile->ReadBool( bActive ); savefile->ReadBool( bEnabled ); savefile->ReadFloat( untouchGranularity ); savefile->ReadFloat( wait ); savefile->ReadFloat( random ); savefile->ReadFloat( delay ); savefile->ReadFloat( randomDelay ); savefile->ReadFloat( refire ); savefile->ReadInt( nextTriggerTime ); savefile->ReadBool( alwaysTrigger ); savefile->ReadBool( isSimpleBox ); savefile->ReadBool( bNoVehicles ); savefile->ReadStaticObject( funcInfo ); savefile->ReadStaticObject( unfuncInfo ); savefile->ReadStaticObject( funcRefInfo ); savefile->ReadStaticObject( unfuncRefInfo ); savefile->ReadStaticObject( funcRefActivatorInfo ); savefile->ReadStaticObject( unfuncRefActivatorInfo ); savefile->ReadString( requires ); savefile->ReadInt( removeItem ); savefile->ReadBool( noTouch ); savefile->ReadBool( initiallyEnabled ); savefile->ReadBool( bUntrigger ); unTriggerActivator.Restore( savefile ); savefile->ReadStringList( TriggerClasses ); savefile->ReadInt( reinterpret_cast ( triggerBehavior ) ); } /* ================ hhTrigger::CallFunctions ================ */ void hhTrigger::CallFunctions( idEntity *activator ) { funcInfo.CallFunction( spawnArgs ); funcRefInfo.CallFunction( spawnArgs ); funcRefActivatorInfo.InsertParm_Entity( activator, 1 );//Needs to be second parm. After self funcRefActivatorInfo.CallFunction( spawnArgs ); } /* ================ hhTrigger::UncallFunctions ================ */ void hhTrigger::UncallFunctions( idEntity *activator ) { unfuncInfo.CallFunction( spawnArgs ); unfuncRefInfo.CallFunction( spawnArgs ); unfuncRefActivatorInfo.InsertParm_Entity( activator, 1 );//Needs to be second parm. After self unfuncRefActivatorInfo.CallFunction( spawnArgs ); } /* ================ hhTrigger::TriggerAction ================ */ void hhTrigger::TriggerAction( idEntity *activator ) { // Activate all targets ActivateTargets( activator ); CallFunctions( activator ); if ( wait >= 0 ) { nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); } else { nextTriggerTime = gameLocal.time + 1; PostEventMS( &EV_Disable, 0 ); } // Handle refire if (refire > 0) { PostEventSec( &EV_Retrigger, refire + random * gameLocal.random.CRandomFloat(), activator); } } /* ================ hhTrigger::UnTriggerAction ================ */ void hhTrigger::UnTriggerAction() { if( bUntrigger ) { ActivateTargets( unTriggerActivator.GetEntity() ); } UncallFunctions( unTriggerActivator.GetEntity() ); } /* =============== hhTrigger::IsEncroaching =============== */ bool hhTrigger::IsEncroaching( const idEntity* entity ) { // nla - Simplified checks to bounds when possible. // Error was due to contents of an idActivator not hitting anything here. (it has contents 0) // But since the collide logic has been done elsewhere, we can simplify this to just a is this in this check. if (isSimpleBox) { // num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), entity->GetPhysics()->GetContents(), touch, MAX_GENTITIES ); if ( GetPhysics()->GetAbsBounds().IntersectsBounds( entity->GetPhysics()->GetAbsBounds() ) ) { return( true ); } } else { //num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, entity->GetPhysics()->GetContents() ); if ( entity->GetPhysics()->ClipContents( GetPhysics()->GetClipModel() ) ) { return( true ); } } return false; } /* =============== hhTrigger::IsEncroached =============== */ bool hhTrigger::IsEncroached() { idEntity *touch[ MAX_GENTITIES ]; int num; if (isSimpleBox) { num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES ); } else { num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES ); } for (int i = 0; i < num; i++ ) { if( touch[ i ] == this ) { continue; } if( CheckUnTriggerClass(touch[ i ]) ) { return true; } } return false; } /* =============== hhTrigger::SetTriggerClasses =============== */ void hhTrigger::SetTriggerClasses( idList& list ) { TriggerClasses.Clear(); TriggerClasses.SetNum(list.Num()); for(int iIndex = 0; iIndex < list.Num(); ++iIndex) { TriggerClasses[iIndex] = list[iIndex]; } } /* =============== hhTrigger::CheckTriggerClass =============== */ bool hhTrigger::CheckTriggerClass( idEntity* activator ) { if( !activator ) { return false; } if(!TriggerClasses.Num() || triggerBehavior == TB_ANY) { return true; } for(int iIndex = 0; iIndex < TriggerClasses.Num(); ++iIndex) { //Look for exact match then try for prefix match if( !idStr(activator->spawnArgs.GetString("classname")).Icmp(TriggerClasses[iIndex].c_str()) || !idStr(activator->spawnArgs.GetString("classname")).IcmpPrefix(TriggerClasses[iIndex].c_str()) ) { if( activator->IsType(hhPlayer::Type) ) {//Player needs client hhPlayer* player = static_cast(activator); if ( player->noclip || !player->ShouldTouchTrigger(this) ) { return false; } if ( bNoVehicles && player->InVehicle() ) { return false; } } if( activator->IsType(idActor::Type) ) { // Dead monsters shouldn't keep refires going if (activator->health <= 0) { return false; } } return true; } } return false; } /* =============== hhTrigger::CheckUnTriggerClass Helper function used for overriding =============== */ bool hhTrigger::CheckUnTriggerClass(idEntity* activator) { return CheckTriggerClass(activator); } /* =============== hhTrigger::GetTriggerClasses =============== */ void hhTrigger::GetTriggerClasses(idDict& Args) { const idKeyValue* pKeyValue = NULL; int iNumKeyValues = Args.GetNumKeyVals(); TriggerClasses.Clear(); if(triggerBehavior == TB_PLAYER_ONLY) { TriggerClasses.AddUnique( "player" ); } else if(triggerBehavior == TB_FRIENDLIES_ONLY) { TriggerClasses.AddUnique( "player" ); TriggerClasses.AddUnique( "character" ); } else if(triggerBehavior == TB_MONSTERS_ONLY) { TriggerClasses.AddUnique( "monster" ); } else if(triggerBehavior == TB_PLAYER_MONSTERS_FRIENDLIES) { TriggerClasses.AddUnique( "player" ); TriggerClasses.AddUnique( "monster" ); TriggerClasses.AddUnique( "character" ); } else if(triggerBehavior == TB_SPECIFIC_ENTITIES){ for( int iIndex = 0; iIndex < iNumKeyValues; ++iIndex ) { pKeyValue = Args.GetKeyVal( iIndex ); if ( !pKeyValue->GetKey().Cmpn( "trigger_class", 13 ) ) { TriggerClasses.AddUnique( pKeyValue->GetValue() ); } } } } /* =============== hhTrigger::Enable =============== */ void hhTrigger::Enable() { GetPhysics()->SetContents( CONTENTS_TRIGGER ); GetPhysics()->EnableClip(); bEnabled = true; } /* =============== hhTrigger::Disable =============== */ void hhTrigger::Disable() { // we may be relinked if we're bound to another object, so clear the contents as well GetPhysics()->SetContents( 0 ); GetPhysics()->DisableClip(); CancelEvents( &EV_Retrigger ); bEnabled = false; } /* =============== hhTrigger::Event_PostSpawn =============== */ void hhTrigger::Event_PostSpawn() { funcInfo.ParseFunctionKeyValue( spawnArgs.GetString("call") ); funcInfo.Verify(); unfuncInfo.ParseFunctionKeyValue( spawnArgs.GetString("uncall") ); unfuncInfo.Verify(); funcRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("callRef") ); funcRefInfo.InsertParm_Entity( this, 0 ); funcRefInfo.Verify(); unfuncRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("uncallRef") ); unfuncRefInfo.InsertParm_Entity( this, 0 ); unfuncRefInfo.Verify(); funcRefActivatorInfo.ParseFunctionKeyValue( spawnArgs.GetString("callRefActivator") ); funcRefActivatorInfo.InsertParm_Entity( this, 0 ); // NOTE: Activator parm inserted at time of function call, therefore don't verify now unfuncRefActivatorInfo.ParseFunctionKeyValue( spawnArgs.GetString("uncallRefActivator") ); unfuncRefActivatorInfo.InsertParm_Entity( this, 0 ); // NOTE: Activator parm inserted at time of function call, therefore don't verify now } /* =============== hhTrigger::Event_PollForUntouch =============== */ void hhTrigger::Event_PollForUntouch() { if (!IsEncroached()) { CancelEvents(&EV_PollForUntouch); PostEventMS(&EV_Deactivate, 0); } else { PostEventSec(&EV_PollForUntouch, untouchGranularity); } } /* =============== hhTrigger::Event_Touch =============== */ void hhTrigger::Event_Touch( idEntity *other, trace_t *trace ) { if( noTouch || !CheckTriggerClass(other) ) { return; } if( !IsActive() ) { Activate( other ); // If this trigger uses any of the unTrigger mechanisms, start polling if (bUntrigger || refire || unfuncInfo.GetFunction() || unfuncRefInfo.GetFunction() || unfuncRefActivatorInfo.GetFunction()) { PostEventSec(&EV_PollForUntouch, untouchGranularity); } } // If we have already triggered the first time, but should always trigger else if ( alwaysTrigger ) { Activate( other ); } } /* ================ hhTrigger::Retrigger ================ */ void hhTrigger::Event_Retrigger( idEntity *activator ) { //AOB - needed to stop infinite retrigger when triggered from gui //rww - activator can be null if removed before event is serviced. actually, isn't this a bad idea? maybe it's better to use an index. if (!activator || (!noTouch && !IsEncroaching(activator)) ) { CancelEvents( &EV_Retrigger ); PostEventMS( &EV_Deactivate, 0 ); return; } // This is seperate event so we can cull them seperately from normal trigger events TriggerAction( activator ); } /* ================ hhTrigger::Event_UnTriggerAction ================ */ void hhTrigger::Event_UnTriggerAction() { UnTriggerAction(); } /* ================ hhTrigger::Event_TriggerAction ================ */ void hhTrigger::Event_TriggerAction( idEntity *activator ) { //HUMANHEAD rww - we are running into a null activator situation when the spirit proxy for the player runs into a trigger, //and then the player switches back to his body and removes the proxy before the trigger fires (due to a relay/delay/whatever), //as well as possibly any other situation where a delayed trigger is activated by an entity that gets removed before firing. //it has been decided that in these situations, the world should be used as the activator. if (!activator) { activator = gameLocal.world; } //HUMANHEAD END // Added noTouch && !IsEncroached to fix the issue with retriggered hurt constantly damaging the player, even when they weren't in it. // nla - Added check to allow 'delayed' triggers to function when you weren't in them. if (!noTouch && !IsEncroaching(activator) && !delay ) { CancelEvents( &EV_Retrigger ); PostEventMS( &EV_Deactivate, 0 ); return; } TriggerAction( activator ); } /* ================ hhTrigger::Event_Enable ================ */ void hhTrigger::Event_Enable( void ) { Enable(); } /* ================ hhTrigger::Event_Disable ================ */ void hhTrigger::Event_Disable( void ) { Disable(); } /* ================ hhTrigger::Event_Activate ================ */ void hhTrigger::Event_Activate( idEntity *activator ) { Activate( activator ); } /* ================ hhTrigger::Event_Deactivate ================ */ void hhTrigger::Event_Deactivate() { CancelEvents( &EV_Retrigger ); PostEventMS( &EV_UnTrigger, 0 ); bActive=false; } /*********************************************************************** hhDamageTrigger ***********************************************************************/ CLASS_DECLARATION( hhTrigger, hhDamageTrigger ) EVENT( EV_Enable, hhDamageTrigger::Event_Enable ) EVENT( EV_Disable, hhDamageTrigger::Event_Disable ) END_CLASS void hhDamageTrigger::Spawn(void) { funcRefInfoDamage.ParseFunctionKeyValue( spawnArgs.GetString("damageCallRef") ); funcRefInfoDamage.InsertParm_Entity( this, 0 ); } void hhDamageTrigger::Save(idSaveGame *savefile) const { savefile->WriteStaticObject( funcRefInfoDamage ); } void hhDamageTrigger::Restore( idRestoreGame *savefile ) { savefile->ReadStaticObject( funcRefInfoDamage ); } void hhDamageTrigger::Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location) { // Call script if requested with damageDef, used for spherebrain if (funcRefInfoDamage.GetFunction() != NULL) { funcRefInfoDamage.InsertParm_Entity( attacker, 1 ); spawnArgs.Set( "last_damage", damageDefName ); funcRefInfoDamage.CallFunction( spawnArgs ); } if ( spawnArgs.GetBool( "activate_targets" ) ) { ActivateTargets( attacker ); } hhTrigger::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); } void hhDamageTrigger::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if (!CheckTriggerClass(attacker)) { health += damage; return; } if ( gameLocal.time < nextTriggerTime ) { health += damage; return; } Activate( attacker ); } void hhDamageTrigger::Event_Enable() { bEnabled = true; fl.takedamage = true; GetPhysics()->SetContents( CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW ); } void hhDamageTrigger::Event_Disable() { GetPhysics()->SetContents( 0 ); fl.takedamage = false; bEnabled = false; } /*********************************************************************** hhTriggerPain ***********************************************************************/ const idEventDef EV_DamageBox("damageBox", "e"); CLASS_DECLARATION( hhTrigger, hhTriggerPain ) EVENT( EV_Activate, hhTriggerPain::Event_Activate ) EVENT( EV_DamageBox, hhTriggerPain::Event_DamageBox ) END_CLASS /* ================ hhTriggerPain Damages activator Can be turned on or off by enable/disable Fires events, triggers targets ================ */ void hhTriggerPain::Spawn(void) { spawnArgs.GetBool( "apply_impulse", "0", applyImpulse ); } void hhTriggerPain::Event_DamageBox( idEntity *activator ) { idEntity *touch[ MAX_GENTITIES ]; int num; if (isSimpleBox) { num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES ); } else { num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES ); } for (int i = 0; i < num; i++ ) { if( touch[ i ] == this ) { continue; } if( CheckTriggerClass( touch[i] ) ) { touch[i]->Damage(activator, activator, vec3_origin, spawnArgs.GetString("def_damage"), 1.0f, INVALID_JOINT ); } } } /* ================ hhTriggerPain::TriggerAction ================ */ void hhTriggerPain::TriggerAction(idEntity *activator) { if(activator) { // If a player in a vehicle, apply pain to vehicle if (activator->IsType(hhPlayer::Type)) { hhPlayer *player = static_cast(activator); if (player->InVehicle()) { activator = player->GetVehicleInterface()->GetVehicle(); } } idVec3 Velocity = activator->GetPhysics()->GetLinearVelocity(); activator->Damage(NULL, NULL, -Velocity, spawnArgs.GetString("def_damage"), 1.0f, INVALID_JOINT ); if ( applyImpulse ) { // activator->ApplyImpulse( this, 0, activator->GetPhysics()->GetOrigin(), -Velocity / 10.0 ); activator->ApplyImpulse( this, 0, activator->GetPhysics()->GetOrigin(), -Velocity * (activator->GetPhysics()->GetMass()*1.5f) ); } } // Handle default trigger behavior hhTrigger::TriggerAction(activator); } /* ================ hhTriggerPain::Event_Activate ================ */ void hhTriggerPain::Event_Activate( idEntity *activator ) { //AOB - allow toggling // if( IsEnabled() && (IsActive() || !IsEncroached()) ) { if( IsEnabled() ) { ProcessEvent( &EV_Disable ); ProcessEvent( &EV_Deactivate ); } else { if( !IsEnabled() ) { ProcessEvent( &EV_Enable ); } Activate( activator ); } } /* ================ hhTriggerPain::Save ================ */ void hhTriggerPain::Save( idSaveGame *savefile ) const { savefile->WriteBool( applyImpulse ); } /* ================ hhTriggerPain::Restore ================ */ void hhTriggerPain::Restore( idRestoreGame *savefile ) { savefile->ReadBool( applyImpulse ); } /*********************************************************************** hhTriggerEnabler ***********************************************************************/ CLASS_DECLARATION( hhTrigger, hhTriggerEnabler ) END_CLASS /* ================ hhTriggerEnabler When it is entered, target is enabled When it is exited, target is disabled. ================ */ void hhTriggerEnabler::Spawn(void) { } /* ================ hhTriggerEnabler::TriggerAction ================ */ void hhTriggerEnabler::TriggerAction(idEntity *activator) { // Enable all targets for (int t=0; tPostEventMS( &EV_Enable, 0 ); } } // Handle default trigger behavior hhTrigger::TriggerAction(activator); } /* ================ hhTriggerEnabler::UnTriggerAction ================ */ void hhTriggerEnabler::UnTriggerAction() { // Disable all targets for (int t=0; tPostEventMS( &EV_Disable, 0 ); } } // Handle default trigger behavior hhTrigger::UnTriggerAction(); } /*********************************************************************** hhTriggerSight Activates targets when seen ***********************************************************************/ CLASS_DECLARATION( hhTrigger, hhTriggerSight ) EVENT( EV_Enable, hhTriggerSight::Event_Enable ) EVENT( EV_Disable, hhTriggerSight::Event_Disable ) END_CLASS void hhTriggerSight::Spawn(void) { BecomeActive(TH_THINK); } void hhTriggerSight::Think(void) { if (thinkFlags & TH_THINK) { hhPlayer *player = NULL; for (int i = 0; i < gameLocal.numClients; i++ ) { player = static_cast(gameLocal.entities[ i ]); if ( !player ) { continue; } // if there is no way we can see this player if ( !gameLocal.InPlayerPVS(this) ) { continue; } if (player->CanSee(this, true)) { // activate PostEventMS(&EV_Activate, 0, player); // Automatically disables itself, re-enable for repeat PostEventMS(&EV_Disable, 0); } } } Present(); // Need this so it get's removed from active list } void hhTriggerSight::Event_Enable( void ) { bEnabled = true; BecomeActive(TH_THINK); } void hhTriggerSight::Event_Disable( void ) { bEnabled = false; BecomeInactive(TH_THINK); } /* ================ hhTriggerSight::Save ================ */ void hhTriggerSight::Save( idSaveGame *savefile ) const { savefile->WriteInt( pvsArea ); } /* ================ hhTriggerSight::Restore ================ */ void hhTriggerSight::Restore( idRestoreGame *savefile ) { savefile->ReadInt( pvsArea ); Spawn(); } /* =============================================================================== hhTrigger_Count =============================================================================== */ CLASS_DECLARATION( hhTrigger, hhTrigger_Count ) END_CLASS /* ================ hhTrigger_Count::Save ================ */ void hhTrigger_Count::Save( idSaveGame *savefile ) const { savefile->WriteInt( goal ); savefile->WriteInt( count ); savefile->WriteFloat( delay ); } /* ================ hhTrigger_Count::Restore ================ */ void hhTrigger_Count::Restore( idRestoreGame *savefile ) { savefile->ReadInt( goal ); savefile->ReadInt( count ); savefile->ReadFloat( delay ); } /* ================ hhTrigger_Count::Spawn ================ */ void hhTrigger_Count::Spawn( void ) { spawnArgs.GetInt( "count", "1", goal ); spawnArgs.GetFloat( "delay", "0", delay ); count = 0; } /* ================ hhTrigger_Count::Activate ================ */ void hhTrigger_Count::Activate( idEntity *activator ) { int triggerDelay = 0; unTriggerActivator = activator; // if not enabled, return if (!bEnabled) { return; } bActive=true; if ( nextTriggerTime > gameLocal.time ) { // can't retrigger until the wait is over return; } // see if this trigger requires an item if ( !gameLocal.RequirementMet( activator, requires, removeItem ) ) { return; } if ( delay >= 0 ) { // goal of -1 means trigger has been exhausted if (goal >= 0) { count++; if ( count >= goal ) { if (spawnArgs.GetBool("repeat")) { count = 0; } else { goal = -1; } // don't allow it to trigger again until our delay has passed triggerDelay = hhMath::hhMax( 0, SEC2MS(delay + randomDelay * gameLocal.random.CRandomFloat()) ); nextTriggerTime = gameLocal.time + triggerDelay; PostEventMS( &EV_Trigger, triggerDelay, activator ); } } } } /* ================ hhTrigger_Count::TriggerAction ================ */ void hhTrigger_Count::TriggerAction( idEntity *activator ) { hhTrigger::TriggerAction( activator ); if (goal == -1) { PostEventMS( &EV_Remove, 0 ); } } /* =============================================================================== hhTrigger_Event =============================================================================== */ CLASS_DECLARATION( hhTrigger, hhTrigger_Event ) END_CLASS /* ================ hhTrigger_Event::Spawn ================ */ void hhTrigger_Event::Spawn( void ) { eventDef = FindEventDef( spawnArgs.GetString("eventDef") ); } /* ================ hhTrigger_Event::FindEventDef ================ */ const idEventDef* hhTrigger_Event::FindEventDef( const char* eventDefName ) const { function_t *func = hhTrigger_Event::FindFunction( eventDefName ); if( !func || !func->eventdef ) { gameLocal.Error( "Cannot find event %s on trigger %s\n", eventDefName, name.c_str() ); } return func->eventdef; } /* ================ hhTrigger_Event::FindFunction ================ */ function_t* hhTrigger_Event::FindFunction( const char* funcname ) { function_t* func = NULL; func = gameLocal.program.FindFunction( funcname, gameLocal.program.FindType(funcname) ); if( !func ) { gameLocal.Error( "Cannot find function '%s' in hhTrigger", funcname ); } return func; } /* ================ hhTrigger_Event::ActivateTargets ================ */ void hhTrigger_Event::ActivateTargets( idEntity *activator ) const { idEntity *ent; int i; HH_ASSERT( eventDef ); for( i = 0; i < targets.Num(); i++ ) { ent = targets[ i ].GetEntity(); if ( !ent ) { continue; } if ( ent->RespondsTo( *eventDef ) ) { ent->ProcessEvent( eventDef, activator ); } for (int ix=0; ixGetRenderEntity()->gui[ix]->Trigger(gameLocal.time); } } } } /*********************************************************************** hhMineTrigger ***********************************************************************/ CLASS_DECLARATION( hhTrigger, hhMineTrigger ) END_CLASS bool hhMineTrigger::CheckTriggerClass( idEntity* activator ) { if( !activator ) { return false; } if(!TriggerClasses.Num() || triggerBehavior == TB_ANY) { return true; } for(int iIndex = 0; iIndex < TriggerClasses.Num(); ++iIndex) { //Look for exact match then try for prefix match if( !idStr(activator->spawnArgs.GetString("classname")).Icmp(TriggerClasses[iIndex].c_str()) || !idStr(activator->spawnArgs.GetString("classname")).IcmpPrefix(TriggerClasses[iIndex].c_str()) ) { if( activator->IsType(hhPlayer::Type) ) {//Player needs client hhPlayer* player = static_cast(activator); if ( player->noclip || !player->ShouldTouchTrigger(this) ) { return false; } if ( bNoVehicles && player->InVehicle() ) { return false; } } if( activator->IsType(idActor::Type) ) { // Dead monsters shouldn't keep refires going if (activator->health <= 0) { return false; } } return true; } } if ( activator->IsType(hhMountedGun::Type) ) { return true; } //HUMANHEAD jsh PCF 4/28/06 hardcode monsters to trigger mines if ( !gameLocal.isMultiplayer && activator->IsType( idAI::Type ) && activator->health > 0 ) { return true; } return false; }