#include "../idlib/precompiled.h" #pragma hdrstop #include "Game_local.h" #include "ai/AI_Manager.h" /* =============================================================================== idTrigger =============================================================================== */ const idEventDef EV_Enable( "enable", NULL ); const idEventDef EV_Disable( "disable", NULL ); CLASS_DECLARATION( idEntity, idTrigger ) EVENT( EV_Enable, idTrigger::Event_Enable ) EVENT( EV_Disable, idTrigger::Event_Disable ) END_CLASS /* ================ idTrigger::DrawDebugInfo ================ */ void idTrigger::DrawDebugInfo( void ) { idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); idVec3 up = axis[ 2 ] * 5.0f; idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); idEntity *ent; idEntity *target; int i; bool show; const function_t *func; viewTextBounds.ExpandSelf( 128.0f ); viewBounds.ExpandSelf( 512.0f ); for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { if ( ent->GetPhysics()->GetContents() & ( CONTENTS_TRIGGER | CONTENTS_FLASHLIGHT_TRIGGER ) ) { show = viewBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ); if ( !show ) { for( i = 0; i < ent->targets.Num(); i++ ) { target = ent->targets[ i ].GetEntity(); if ( target && viewBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { show = true; break; } } } if ( !show ) { continue; } gameRenderWorld->DebugBounds( colorOrange, ent->GetPhysics()->GetAbsBounds() ); if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { gameRenderWorld->DrawText( ent->name.c_str(), ent->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); gameRenderWorld->DrawText( ent->GetEntityDefName(), ent->GetPhysics()->GetAbsBounds().GetCenter() + up, 0.1f, colorWhite, axis, 1 ); if ( ent->IsType( idTrigger::Type ) ) { func = static_cast( ent )->GetScriptFunction(); } else { func = NULL; } if ( func ) { gameRenderWorld->DrawText( va( "call script '%s'", func->Name() ), ent->GetPhysics()->GetAbsBounds().GetCenter() - up, 0.1f, colorWhite, axis, 1 ); } } for( i = 0; i < ent->targets.Num(); i++ ) { target = ent->targets[ i ].GetEntity(); if ( target ) { gameRenderWorld->DebugArrow( colorYellow, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); gameRenderWorld->DebugBounds( colorGreen, box, target->GetPhysics()->GetOrigin() ); if ( viewTextBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { gameRenderWorld->DrawText( target->name.c_str(), target->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); } } } } } } /* ================ idTrigger::Enable ================ */ void idTrigger::Enable( void ) { GetPhysics()->SetContents( CONTENTS_TRIGGER ); GetPhysics()->EnableClip(); } /* ================ idTrigger::Disable ================ */ void idTrigger::Disable( void ) { // we may be relinked if we're bound to another object, so clear the contents as well GetPhysics()->SetContents( 0 ); GetPhysics()->DisableClip(); } /* ================ idTrigger::CallScript ================ */ void idTrigger::CallScript( idEntity* scriptEntity ) { // RAVEN BEGIN // abahr for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { scriptFunctions[ix].InsertEntity( scriptEntity, 0 );//We could pass both the activator and self if wanted scriptFunctions[ix].CallFunc( &spawnArgs ); scriptFunctions[ix].RemoveIndex( 0 ); } // RAVEN END } /* ================ idTrigger::GetScriptFunction ================ */ const function_t *idTrigger::GetScriptFunction( void ) const { // RAVEN BEGIN // abahr: return (scriptFunctions.Num()) ? scriptFunctions[0].GetFunc() : NULL; // RAVEN END } /* ================ idTrigger::Save ================ */ void idTrigger::Save( idSaveGame *savefile ) const { // RAVEN BEGIN // abahr savefile->WriteInt( scriptFunctions.Num() ); for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { scriptFunctions[ix].Save( savefile ); } // RAVEN END } /* ================ idTrigger::Restore ================ */ void idTrigger::Restore( idRestoreGame *savefile ) { // RAVEN BEGIN // abahr int numScripts = 0; savefile->ReadInt( numScripts ); scriptFunctions.SetNum( numScripts ); for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { scriptFunctions[ix].Restore( savefile ); } // RAVEN END } /* ================ idTrigger::Event_Enable ================ */ void idTrigger::Event_Enable( void ) { Enable(); } /* ================ idTrigger::Event_Disable ================ */ void idTrigger::Event_Disable( void ) { Disable(); } /* ================ idTrigger::idTrigger ================ */ idTrigger::idTrigger() { // RAVEN BEGIN // abahr: scriptFunction init's itself //scriptFunction = NULL; // RAVEN END } /* ================ idTrigger::Spawn ================ */ void idTrigger::Spawn( void ) { GetPhysics()->SetContents( CONTENTS_TRIGGER ); // RAVEN BEGIN // abahr: scriptFunctions.SetGranularity( 1 ); for( const idKeyValue* kv = spawnArgs.MatchPrefix("call"); kv; kv = spawnArgs.MatchPrefix("call", kv) ) { if( !kv->GetValue() ) { continue; } rvScriptFuncUtility& utility = scriptFunctions.Alloc(); if( !utility.Init(kv->GetValue()) ) { gameLocal.Warning( "Trigger '%s' at (%s) trying to call an unknown function.", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); } } // RAVEN END } /* =============================================================================== idTrigger_Multi =============================================================================== */ // RAVEN BEGIN // abahr: changed to 'E' to allow NULL entities const idEventDef EV_TriggerAction( "", "E" ); // RAVEN END CLASS_DECLARATION( idTrigger, idTrigger_Multi ) EVENT( EV_FindTargets, idTrigger_Multi::Event_FindTargets ) EVENT( EV_Touch, idTrigger_Multi::Event_Touch ) EVENT( EV_SpectatorTouch, idTrigger_Multi::Event_SpectatorTouch ) EVENT( EV_Activate, idTrigger_Multi::Event_Trigger ) EVENT( EV_TriggerAction, idTrigger_Multi::Event_TriggerAction ) // RAVEN BEGIN // kfuller: respond to earthquakes EVENT( EV_Earthquake, idTrigger_Multi::Event_EarthQuake ) // RAVEN END END_CLASS /* ================ idTrigger_Multi::idTrigger_Multi ================ */ idTrigger_Multi::idTrigger_Multi( void ) { wait = 0.0f; random = 0.0f; delay = 0.0f; random_delay = 0.0f; nextTriggerTime = 0; removeItem = 0; touchClient = false; touchOther = false; touchVehicle = false; touchSpec = false; triggerFirst = false; triggerWithSelf = false; buyZoneTrigger = 0; controlZoneTrigger = 0; prevZoneController = TEAM_NONE; } /* ================ idTrigger_Multi::Save ================ */ void idTrigger_Multi::Save( idSaveGame *savefile ) const { savefile->WriteFloat( wait ); savefile->WriteFloat( random ); savefile->WriteFloat( delay ); savefile->WriteFloat( random_delay ); savefile->WriteInt( nextTriggerTime ); savefile->WriteString( requires ); savefile->WriteInt( removeItem ); savefile->WriteBool( touchClient ); savefile->WriteBool( touchOther ); savefile->WriteBool( touchVehicle ); savefile->WriteBool( triggerFirst ); savefile->WriteBool( triggerWithSelf ); } /* ================ idTrigger_Multi::Restore ================ */ void idTrigger_Multi::Restore( idRestoreGame *savefile ) { savefile->ReadFloat( wait ); savefile->ReadFloat( random ); savefile->ReadFloat( delay ); savefile->ReadFloat( random_delay ); savefile->ReadInt( nextTriggerTime ); savefile->ReadString( requires ); savefile->ReadInt( removeItem ); savefile->ReadBool( touchClient ); savefile->ReadBool( touchOther ); savefile->ReadBool( touchVehicle ); savefile->ReadBool( triggerFirst ); savefile->ReadBool( triggerWithSelf ); } /* ================ idTrigger_Multi::IsTeleporter Returns true if there is a single target, which is a teleporter ================ */ bool idTrigger_Multi::IsTeleporter( void ) const { if ( targets.Num() != 1 ) { return false; } return targets[ 0 ].GetEntity()->IsType( idPlayerStart::GetClassType() ); } /* ================ idTrigger_Multi::Spawn "wait" : Seconds between triggerings, 0.5 default, -1 = one time only. "call" : Script function to call when triggered "random" wait variance, default is 0 Variable sized repeatable trigger. Must be targeted at one or more entities. so, the basic time between firing is a random time between (wait - random) and (wait + random) ================ */ void idTrigger_Multi::Spawn( void ) { spawnArgs.GetFloat( "wait", "0.5", wait ); spawnArgs.GetFloat( "random", "0", random ); spawnArgs.GetFloat( "delay", "0", delay ); spawnArgs.GetFloat( "random_delay", "0", random_delay ); if ( random && ( random >= wait ) && ( wait >= 0 ) ) { random = wait - 1; gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); } if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { random_delay = delay - 1; gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); } spawnArgs.GetString( "requires", "", requires ); spawnArgs.GetInt( "removeItem", "0", removeItem ); spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); spawnArgs.GetBool( "triggerWithSelf", "0", triggerWithSelf ); spawnArgs.GetInt( "buyZone", "0", buyZoneTrigger); spawnArgs.GetInt( "controlZone", "0", controlZoneTrigger); if ( buyZoneTrigger == -1 ) gameLocal.Warning( "trigger_buyzone '%s' at (%s) has no buyZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); if ( controlZoneTrigger == -1 ) gameLocal.Warning( "trigger_controlzone '%s' at (%s) has no controlZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); if ( spawnArgs.GetBool( "onlyVehicle" ) ) { touchVehicle = true; } else if ( spawnArgs.GetBool( "anyTouch" ) ) { touchClient = true; touchOther = true; } else if ( spawnArgs.GetBool( "noTouch" ) ) { touchClient = false; touchOther = false; } else if ( spawnArgs.GetBool( "noClient" ) ) { touchClient = false; touchOther = true; } else { touchClient = true; touchOther = false; } nextTriggerTime = 0; if ( spawnArgs.GetBool( "flashlight_trigger" ) ) { GetPhysics()->SetContents( CONTENTS_FLASHLIGHT_TRIGGER ); } else if ( spawnArgs.GetBool( "projectile_trigger" ) ) { GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_PROJECTILE ); } else { GetPhysics()->SetContents( CONTENTS_TRIGGER ); } BecomeActive( TH_THINK ); } /* ================ idTrigger_Multi::FindTargets ================ */ void idTrigger_Multi::Event_FindTargets( void ) { FindTargets(); touchSpec = !spawnArgs.GetBool( "noSpec", IsTeleporter() ? "0" : "1" ); } /* ================ idTrigger_Multi::CheckFacing ================ */ bool idTrigger_Multi::CheckFacing( idEntity *activator ) { if ( spawnArgs.GetBool( "facing" ) ) { if ( !activator->IsType( idPlayer::GetClassType() ) ) { return true; } idPlayer *player = static_cast< idPlayer* >( activator ); // Unfortunately, the angle key rotates the trigger entity also. So I've added // an angleFacing key which is used instead when present, otherwise the code defaults // to the behaviour present prior to this change idVec3 tFacing = GetPhysics()->GetAxis()[0]; if ( spawnArgs.FindKey( "angleFacing" )) { idAngles angs(0,spawnArgs.GetFloat( "angleFacing", "0" ),0); tFacing = angs.ToForward(); } float dot = player->viewAngles.ToForward() * tFacing; float angle = RAD2DEG( idMath::ACos( dot ) ); if ( angle > spawnArgs.GetFloat( "angleLimit", "30" ) ) { return false; } } return true; } /* ================ idTrigger_Multi::TriggerAction ================ */ void idTrigger_Multi::TriggerAction( idEntity *activator ) { // RAVEN BEGIN // jdischler: added for Aweldon. The trigger, when activated, will call the listed func with all attached targets, then return. if ( spawnArgs.GetBool( "_callWithTargets", "0" )) { idEntity *ent; for( int i = 0; i < targets.Num(); i++ ) { ent = targets[ i ].GetEntity(); if ( !ent ) { continue; } CallScript( ent ); } return; } // RAVEN END ActivateTargets( triggerWithSelf ? this : activator ); CallScript( triggerWithSelf ? this : activator ); if ( wait >= 0 ) { nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); } else { // we can't just remove (this) here, because this is a touch function // called while looping through area links... nextTriggerTime = gameLocal.time + 1; PostEventMS( &EV_Remove, 0 ); } } /* ================ idTrigger_Multi::Event_TriggerAction ================ */ void idTrigger_Multi::Event_TriggerAction( idEntity *activator ) { TriggerAction( activator ); } /* ================ idTrigger_Multi::Event_Trigger the trigger was just activated activated should be the entity that originated the activation sequence (ie. the original target) activator should be set to the activator so it can be held through a delay so wait for the delay time before firing ================ */ void idTrigger_Multi::Event_Trigger( idEntity *activator ) { // RAVEN BEGIN // bdube: moved trigger first if ( triggerFirst ) { triggerFirst = false; return; } 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 ( !CheckFacing( activator ) ) { return; } // RAVEN END // don't allow it to trigger twice in a single frame nextTriggerTime = gameLocal.time + 1; if ( delay > 0 ) { // don't allow it to trigger again until our delay has passed nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); PostEventSec( &EV_TriggerAction, delay, activator ); } else { TriggerAction( activator ); } } void idTrigger_Multi::HandleControlZoneTrigger() { // This only does something in multiplayer with gameType == DeadZone if ( !gameLocal.isMultiplayer || gameLocal.gameType != GAME_DEADZONE ) return; const int TEAM_DEADLOCK = 2; int pCount = 0; int count = 0, controllingTeam = TEAM_NONE; count = playersInTrigger.Num(); for ( int i = 0; iPowerUpActive( POWERUP_DEADZONE ) ) continue; if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) { pCount++; } int team = playersInTrigger[i]->team; if ( i == 0 ) controllingTeam = playersInTrigger[i]->team; // Assign the controlling team based on the first player // for zones that accept both. if ( team != controllingTeam ) { controllingTeam = TEAM_DEADLOCK; pCount = 0; } } if ( controllingTeam != controlZoneTrigger-1 && controlZoneTrigger != 3 ) { controllingTeam = TEAM_NONE; pCount = 0; } int situation = DZ_NONE; if ( controllingTeam != prevZoneController ) { if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_NONE ) situation = DZ_MARINES_TAKEN; else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_NONE ) situation = DZ_STROGG_TAKEN; else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_MARINE ) situation = DZ_MARINES_LOST; else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_STROGG ) situation = DZ_STROGG_LOST; else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_STROGG ) situation = DZ_STROGG_TO_MARINE; else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_MARINE ) situation = DZ_MARINE_TO_STROGG; // DEADLOCK else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_MARINE ) situation = DZ_MARINE_DEADLOCK; else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_STROGG ) situation = DZ_STROGG_DEADLOCK; else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_NONE ) situation = DZ_MARINE_DEADLOCK; // Unlikely case, just use this. else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_DEADLOCK ) situation = DZ_MARINE_REGAIN; else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_DEADLOCK ) situation = DZ_STROGG_REGAIN; else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_DEADLOCK ) situation = DZ_MARINES_LOST; // Unlikely case, just use this. } /// Report individual credits for( int i = 0; i < count; i++ ) { idPlayer* player = playersInTrigger[i]; // No token? Ignore em! if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") && !player->PowerUpActive( POWERUP_DEADZONE ) ) continue; int team = player->team; if( team == controllingTeam ) { gameLocal.mpGame.ReportZoneControllingPlayer( player ); } } /// Report zone control to multiplayer game manager gameLocal.mpGame.ReportZoneController(controllingTeam, pCount, situation, this); playersInTrigger.Clear(); prevZoneController = controllingTeam; } /* ================ idTrigger_Multi::Think ================ */ void idTrigger_Multi::Think() { // Control zone handling if ( controlZoneTrigger > 0 ) HandleControlZoneTrigger(); } /* ================ idTrigger_Multi::Event_Touch ================ */ void idTrigger_Multi::Event_Touch( idEntity *other, trace_t *trace ) { if( triggerFirst ) { return; } // RAVEN BEGIN // jdischler: vehicle only trigger if ( touchVehicle ) { if ( !other->IsType(rvVehicle::GetClassType()) ) { return; } } else { // RAVEN BEGIN // jnewquist: Use accessor for static class type bool player = other->IsType( idPlayer::GetClassType() ); // RAVEN END if ( player ) { if ( !touchClient ) { return; } if ( static_cast< idPlayer * >( other )->spectating ) { Event_SpectatorTouch( other, trace ); return; } // Buy zone handling if ( buyZoneTrigger /*&& gameLocal.mpGame.mpGameState.gameState.currentState != 1*/ ) { idPlayer *p = static_cast< idPlayer * >( other ); if ( buyZoneTrigger-1 == p->team || buyZoneTrigger == 3) { p->inBuyZone = true; p->inBuyZonePrev = true; } } // Control zone handling if ( controlZoneTrigger > 0 ) { idPlayer *p = static_cast< idPlayer * >( other ); if ( p->PowerUpActive(POWERUP_DEADZONE) || !spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) playersInTrigger.Append(p); } } else if ( !touchOther ) { return; } } if ( nextTriggerTime > gameLocal.time ) { // can't retrigger until the wait is over return; } // see if this trigger requires an item if ( !gameLocal.RequirementMet( other, requires, removeItem ) ) { return; } if ( !CheckFacing( other ) ) { return; } if ( spawnArgs.GetBool( "toggleTriggerFirst" ) ) { triggerFirst = true; } // RAVEN BEGIN // rjohnson: added block if ( developer.GetBool() && *spawnArgs.GetString ( "message" ) ) { gameLocal.DPrintf ( "Trigger: %s\n", spawnArgs.GetString ( "message" ) ); } // RAVEN END nextTriggerTime = gameLocal.time + 1; if ( delay > 0 ) { // don't allow it to trigger again until our delay has passed nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); PostEventSec( &EV_TriggerAction, delay, other ); } else { TriggerAction( other ); } } /* ================ idTrigger_Multi::Event_Touch ================ */ void idTrigger_Multi::Event_SpectatorTouch( idEntity *other, trace_t *trace ) { idPlayer *player; if ( !touchSpec ) { return; } if ( !other->IsType( idPlayer::GetClassType() ) ) { return; } player = static_cast< idPlayer* >( other ); if ( !player->spectating ) { return; } if ( player->lastSpectateTeleport > (gameLocal.time - 1000) ) { // can't retrigger until the wait is over return; } if ( !CheckFacing( other ) ) { return; } player->lastSpectateTeleport = gameLocal.time + 1; if ( delay > 0 ) { // don't allow it to trigger again until our delay has passed player->lastSpectateTeleport += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); PostEventSec( &EV_TriggerAction, delay, other ); } else { TriggerAction( other ); } } // RAVEN BEGIN // kfuller: void idTrigger_Multi::Event_EarthQuake(float requiresLOS) { // does this entity even care about earthquakes? float quakeChance = 0; if (!spawnArgs.GetFloat("quakeChance", "0", quakeChance)) { return; } if (rvRandom::flrand(0, 1.0f) > quakeChance) { // failed its activation roll return; } if (requiresLOS) { // if the player doesn't have line of sight to this fx, don't do anything trace_t trace; idPlayer *player = gameLocal.GetLocalPlayer(); idVec3 viewOrigin; idMat3 viewAxis; player->GetViewPos(viewOrigin, viewAxis); // RAVEN BEGIN // ddynerman: multiple clip worlds gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player ); // RAVEN END if (trace.fraction < 1.0f) { // something blocked LOS return; } } // activate this effect now TriggerAction(gameLocal.entities[ENTITYNUM_WORLD]); } // RAVEN END /* =============================================================================== idTrigger_EntityName =============================================================================== */ CLASS_DECLARATION( idTrigger, idTrigger_EntityName ) EVENT( EV_Touch, idTrigger_EntityName::Event_Touch ) EVENT( EV_Activate, idTrigger_EntityName::Event_Trigger ) EVENT( EV_TriggerAction, idTrigger_EntityName::Event_TriggerAction ) END_CLASS /* ================ idTrigger_EntityName::idTrigger_EntityName ================ */ idTrigger_EntityName::idTrigger_EntityName( void ) { wait = 0.0f; random = 0.0f; delay = 0.0f; random_delay = 0.0f; nextTriggerTime = 0; triggerFirst = false; } /* ================ idTrigger_EntityName::Save ================ */ void idTrigger_EntityName::Save( idSaveGame *savefile ) const { savefile->WriteFloat( wait ); savefile->WriteFloat( random ); savefile->WriteFloat( delay ); savefile->WriteFloat( random_delay ); savefile->WriteInt( nextTriggerTime ); savefile->WriteBool( triggerFirst ); savefile->WriteString( entityName ); } /* ================ idTrigger_EntityName::Restore ================ */ void idTrigger_EntityName::Restore( idRestoreGame *savefile ) { savefile->ReadFloat( wait ); savefile->ReadFloat( random ); savefile->ReadFloat( delay ); savefile->ReadFloat( random_delay ); savefile->ReadInt( nextTriggerTime ); savefile->ReadBool( triggerFirst ); savefile->ReadString( entityName ); } /* ================ idTrigger_EntityName::Spawn ================ */ void idTrigger_EntityName::Spawn( void ) { spawnArgs.GetFloat( "wait", "0.5", wait ); spawnArgs.GetFloat( "random", "0", random ); spawnArgs.GetFloat( "delay", "0", delay ); spawnArgs.GetFloat( "random_delay", "0", random_delay ); if ( random && ( random >= wait ) && ( wait >= 0 ) ) { random = wait - 1; gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); } if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { random_delay = delay - 1; gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); } spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); entityName = spawnArgs.GetString( "entityname" ); if ( !entityName.Length() ) { gameLocal.Error( "idTrigger_EntityName '%s' at (%s) doesn't have 'entityname' key specified", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); } nextTriggerTime = 0; if ( !spawnArgs.GetBool( "noTouch" ) ) { GetPhysics()->SetContents( CONTENTS_TRIGGER ); } } /* ================ idTrigger_EntityName::TriggerAction ================ */ void idTrigger_EntityName::TriggerAction( idEntity *activator ) { // RAVEN BEGIN // abahr: want same functionality as trigger_multi. Need to move this code into these two function calls idEntity* scriptEntity = spawnArgs.GetBool("triggerWithSelf") ? this : activator; ActivateTargets( scriptEntity ); CallScript( scriptEntity ); // RAVEN END if ( wait >= 0 ) { nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); } else { // we can't just remove (this) here, because this is a touch function // called while looping through area links... nextTriggerTime = gameLocal.time + 1; PostEventMS( &EV_Remove, 0 ); } } /* ================ idTrigger_EntityName::Event_TriggerAction ================ */ void idTrigger_EntityName::Event_TriggerAction( idEntity *activator ) { TriggerAction( activator ); } /* ================ idTrigger_EntityName::Event_Trigger the trigger was just activated activated should be the entity that originated the activation sequence (ie. the original target) activator should be set to the activator so it can be held through a delay so wait for the delay time before firing ================ */ void idTrigger_EntityName::Event_Trigger( idEntity *activator ) { if ( nextTriggerTime > gameLocal.time ) { // can't retrigger until the wait is over return; } // RAVEN BEGIN // abahr: so we can exclude an entity by name if( !activator ) { return; } if( spawnArgs.GetBool("excludeEntityName") && activator->name == entityName ) { return; } if( !spawnArgs.GetBool("excludeEntityName") && activator->name != entityName ) { return; } // RAVEN END if ( triggerFirst ) { triggerFirst = false; return; } // don't allow it to trigger twice in a single frame nextTriggerTime = gameLocal.time + 1; if ( delay > 0 ) { // don't allow it to trigger again until our delay has passed nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); PostEventSec( &EV_TriggerAction, delay, activator ); } else { TriggerAction( activator ); } } /* ================ idTrigger_EntityName::Event_Touch ================ */ void idTrigger_EntityName::Event_Touch( idEntity *other, trace_t *trace ) { if( triggerFirst ) { return; } if ( nextTriggerTime > gameLocal.time ) { // can't retrigger until the wait is over return; } // RAVEN BEGIN // abahr: so we can exclude an entity by name if( !other ) { return; } if( spawnArgs.GetBool("excludeEntityName") && other->name == entityName ) { return; } if( !spawnArgs.GetBool("excludeEntityName") && other->name != entityName ) { return; } // RAVEN END nextTriggerTime = gameLocal.time + 1; if ( delay > 0 ) { // don't allow it to trigger again until our delay has passed nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); PostEventSec( &EV_TriggerAction, delay, other ); } else { TriggerAction( other ); } } /* =============================================================================== idTrigger_Timer =============================================================================== */ const idEventDef EV_Timer( "", NULL ); CLASS_DECLARATION( idTrigger, idTrigger_Timer ) EVENT( EV_Timer, idTrigger_Timer::Event_Timer ) EVENT( EV_Activate, idTrigger_Timer::Event_Use ) END_CLASS /* ================ idTrigger_Timer::idTrigger_Timer ================ */ idTrigger_Timer::idTrigger_Timer( void ) { random = 0.0f; wait = 0.0f; on = false; delay = 0.0f; } /* ================ idTrigger_Timer::Save ================ */ void idTrigger_Timer::Save( idSaveGame *savefile ) const { savefile->WriteFloat( random ); savefile->WriteFloat( wait ); savefile->WriteBool( on ); savefile->WriteFloat( delay ); savefile->WriteString( onName ); savefile->WriteString( offName ); } /* ================ idTrigger_Timer::Restore ================ */ void idTrigger_Timer::Restore( idRestoreGame *savefile ) { savefile->ReadFloat( random ); savefile->ReadFloat( wait ); savefile->ReadBool( on ); savefile->ReadFloat( delay ); savefile->ReadString( onName ); savefile->ReadString( offName ); } /* ================ idTrigger_Timer::Spawn Repeatedly fires its targets. Can be turned on or off by using. ================ */ void idTrigger_Timer::Spawn( void ) { spawnArgs.GetFloat( "random", "1", random ); spawnArgs.GetFloat( "wait", "1", wait ); spawnArgs.GetBool( "start_on", "0", on ); spawnArgs.GetFloat( "delay", "0", delay ); onName = spawnArgs.GetString( "onName" ); offName = spawnArgs.GetString( "offName" ); if ( random >= wait && wait >= 0 ) { random = wait - 0.001; gameLocal.Warning( "idTrigger_Timer '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); } if ( on ) { PostEventSec( &EV_Timer, delay ); } } /* ================ idTrigger_Timer::Enable ================ */ void idTrigger_Timer::Enable( void ) { // if off, turn it on if ( !on ) { on = true; PostEventSec( &EV_Timer, delay ); } } /* ================ idTrigger_Timer::Disable ================ */ void idTrigger_Timer::Disable( void ) { // if on, turn it off if ( on ) { on = false; CancelEvents( &EV_Timer ); } } /* ================ idTrigger_Timer::Event_Timer ================ */ void idTrigger_Timer::Event_Timer( void ) { ActivateTargets( this ); // set time before next firing if ( wait >= 0.0f ) { PostEventSec( &EV_Timer, wait + gameLocal.random.CRandomFloat() * random ); } } /* ================ idTrigger_Timer::Event_Use ================ */ void idTrigger_Timer::Event_Use( idEntity *activator ) { // if on, turn it off if ( on ) { if ( offName.Length() && offName.Icmp( activator->GetName() ) ) { return; } on = false; CancelEvents( &EV_Timer ); } else { // turn it on if ( onName.Length() && onName.Icmp( activator->GetName() ) ) { return; } on = true; PostEventSec( &EV_Timer, delay ); } } /* =============================================================================== idTrigger_Count =============================================================================== */ CLASS_DECLARATION( idTrigger, idTrigger_Count ) EVENT( EV_Activate, idTrigger_Count::Event_Trigger ) EVENT( EV_TriggerAction, idTrigger_Count::Event_TriggerAction ) END_CLASS /* ================ idTrigger_Count::idTrigger_Count ================ */ idTrigger_Count::idTrigger_Count( void ) { goal = 0; count = 0; delay = 0.0f; } /* ================ idTrigger_Count::Save ================ */ void idTrigger_Count::Save( idSaveGame *savefile ) const { savefile->WriteInt( goal ); savefile->WriteInt( count ); savefile->WriteFloat( delay ); } /* ================ idTrigger_Count::Restore ================ */ void idTrigger_Count::Restore( idRestoreGame *savefile ) { savefile->ReadInt( goal ); savefile->ReadInt( count ); savefile->ReadFloat( delay ); } /* ================ idTrigger_Count::Spawn ================ */ void idTrigger_Count::Spawn( void ) { spawnArgs.GetInt( "count", "1", goal ); spawnArgs.GetFloat( "delay", "0", delay ); count = 0; } /* ================ idTrigger_Count::Event_Trigger ================ */ void idTrigger_Count::Event_Trigger( idEntity *activator ) { // goal of -1 means trigger has been exhausted if (goal >= 0) { count++; if ( count >= goal ) { if (spawnArgs.GetBool("repeat")) { count = 0; } else { goal = -1; } PostEventSec( &EV_TriggerAction, delay, activator ); } } } /* ================ idTrigger_Count::Event_TriggerAction ================ */ void idTrigger_Count::Event_TriggerAction( idEntity *activator ) { ActivateTargets( activator ); CallScript( activator ); if ( goal == -1 ) { PostEventMS( &EV_Remove, 0 ); } } /* =============================================================================== idTrigger_Hurt =============================================================================== */ CLASS_DECLARATION( idTrigger, idTrigger_Hurt ) EVENT( EV_Touch, idTrigger_Hurt::Event_Touch ) EVENT( EV_Activate, idTrigger_Hurt::Event_Toggle ) END_CLASS /* ================ idTrigger_Hurt::idTrigger_Hurt ================ */ idTrigger_Hurt::idTrigger_Hurt( void ) { on = false; delay = 0.0f; nextTime = 0; } /* ================ idTrigger_Hurt::Save ================ */ void idTrigger_Hurt::Save( idSaveGame *savefile ) const { savefile->WriteBool( on ); savefile->WriteFloat( delay ); savefile->WriteInt( nextTime ); // RAVEN BEGIN // bdube: playeronly flag savefile->WriteBool ( playerOnly ); // RAVEN END } /* ================ idTrigger_Hurt::Restore ================ */ void idTrigger_Hurt::Restore( idRestoreGame *savefile ) { savefile->ReadBool( on ); savefile->ReadFloat( delay ); savefile->ReadInt( nextTime ); // RAVEN BEGIN // bdube: playeronly flag savefile->ReadBool( playerOnly ); // RAVEN END } /* ================ idTrigger_Hurt::Spawn Damages activator Can be turned on or off by using. ================ */ void idTrigger_Hurt::Spawn( void ) { spawnArgs.GetBool( "on", "1", on ); spawnArgs.GetFloat( "delay", "1.0", delay ); // RAVEN BEGIN // kfuller: playeronly flag spawnArgs.GetBool( "playerOnly", "0", playerOnly ); // RAVEN END nextTime = gameLocal.time; Enable(); } /* ================ idTrigger_Hurt::Event_Touch ================ */ void idTrigger_Hurt::Event_Touch( idEntity *other, trace_t *trace ) { const char *damage; // RAVEN BEGIN // kfuller: playeronly flag // jnewquist: Use accessor for static class type if ( playerOnly && !other->IsType( idPlayer::GetClassType() ) ) { return; } // RAVEN END if ( on && other && gameLocal.time >= nextTime ) { damage = spawnArgs.GetString( "def_damage", "damage_painTrigger" ); other->Damage( this, NULL, vec3_origin, damage, 1.0f, INVALID_JOINT ); ActivateTargets( other ); CallScript( other ); nextTime = gameLocal.time + SEC2MS( delay ); } } /* ================ idTrigger_Hurt::Event_Toggle ================ */ void idTrigger_Hurt::Event_Toggle( idEntity *activator ) { on = !on; } /* =============================================================================== idTrigger_Fade =============================================================================== */ CLASS_DECLARATION( idTrigger, idTrigger_Fade ) EVENT( EV_Activate, idTrigger_Fade::Event_Trigger ) END_CLASS /* ================ idTrigger_Fade::Event_Trigger ================ */ void idTrigger_Fade::Event_Trigger( idEntity *activator ) { idVec4 fadeColor; int fadeTime; idPlayer *player; player = gameLocal.GetLocalPlayer(); if ( player ) { fadeColor = spawnArgs.GetVec4( "fadeColor", "0, 0, 0, 1" ); fadeTime = SEC2MS( spawnArgs.GetFloat( "fadeTime", "0.5" ) ); player->playerView.Fade( fadeColor, fadeTime ); PostEventMS( &EV_ActivateTargets, fadeTime, activator ); } } /* =============================================================================== idTrigger_Touch =============================================================================== */ CLASS_DECLARATION( idTrigger, idTrigger_Touch ) EVENT( EV_Activate, idTrigger_Touch::Event_Trigger ) END_CLASS /* ================ idTrigger_Touch::idTrigger_Touch ================ */ idTrigger_Touch::idTrigger_Touch( void ) { clipModel = NULL; } /* ================ idTrigger_Touch::idTrigger_Touch ================ */ idTrigger_Touch::~idTrigger_Touch( ) { if ( clipModel ) { delete clipModel; clipModel = 0; } } /* ================ idTrigger_Touch::Spawn ================ */ void idTrigger_Touch::Spawn( void ) { // get the clip model // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation RV_PUSH_HEAP_MEM(this); // RAVEN END clipModel = new idClipModel( GetPhysics()->GetClipModel() ); // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation RV_POP_HEAP(); // RAVEN END // remove the collision model from the physics object GetPhysics()->SetClipModel( NULL, 1.0f ); if ( spawnArgs.GetBool( "start_on" ) ) { BecomeActive( TH_THINK ); } filterTeam = -1; idStr filterTeamStr = spawnArgs.GetString( "filterTeam" ); if ( filterTeamStr.Size() ) { if ( !idStr::Icmp( "marine", filterTeamStr.c_str() ) ) { filterTeam = AITEAM_MARINE; } else if ( !idStr::Icmp( "strogg", filterTeamStr.c_str() ) ) { filterTeam = AITEAM_STROGG; } } } /* ================ idTrigger_Touch::Save ================ */ void idTrigger_Touch::Save( idSaveGame *savefile ) { savefile->WriteClipModel( clipModel ); savefile->WriteInt( filterTeam ); } /* ================ idTrigger_Touch::Restore ================ */ void idTrigger_Touch::Restore( idRestoreGame *savefile ) { savefile->ReadClipModel( clipModel ); savefile->ReadInt( filterTeam ); } /* ================ idTrigger_Touch::TouchEntities ================ */ void idTrigger_Touch::TouchEntities( void ) { int numClipModels, i; idBounds bounds; idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; // RAVEN BEGIN // abahr: now scriptFunction list if ( clipModel == NULL || !scriptFunctions.Num() ) { // RAVEN END return; } bounds.FromTransformedBounds( clipModel->GetBounds(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ); // RAVEN BEGIN // MCG: filterTeam if ( filterTeam != -1 ) { idActor* actor; // Iterate through the filter team for( actor = aiManager.GetAllyTeam ( (aiTeam_t)filterTeam ); actor; actor = actor->teamNode.Next() ) { // Skip hidden actors and actors that can't be targeted if( actor->fl.notarget || actor->fl.isDormant || ( actor->IsHidden ( ) && !actor->IsInVehicle() ) ) { continue; } if ( !bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { continue; } cm = actor->GetPhysics()->GetClipModel(); if ( !cm || !cm->IsTraceModel() ) { continue; } if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, clipModel->GetCollisionModel(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ) ) { continue; } ActivateTargets( (idEntity*)actor ); CallScript( (idEntity*)actor ); } return; } // ddynerman: multiple clip worlds numClipModels = gameLocal.ClipModelsTouchingBounds( this, bounds, -1, clipModelList, MAX_GENTITIES ); // RAVEN END for ( i = 0; i < numClipModels; i++ ) { cm = clipModelList[ i ]; if ( !cm->IsTraceModel() ) { continue; } idEntity *entity = cm->GetEntity(); if ( !entity ) { continue; } // RAVEN BEGIN // ddynerman: multiple clip worlds if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { // RAVEN END continue; } ActivateTargets( entity ); // RAVEN BEGIN // abahr: changed to be compatible with new script function utility CallScript( entity ); // RAVEN END } } /* ================ idTrigger_Touch::Think ================ */ void idTrigger_Touch::Think( void ) { if ( thinkFlags & TH_THINK ) { TouchEntities(); } idEntity::Think(); } /* ================ idTrigger_Touch::Event_Trigger ================ */ void idTrigger_Touch::Event_Trigger( idEntity *activator ) { if ( thinkFlags & TH_THINK ) { BecomeInactive( TH_THINK ); } else { BecomeActive( TH_THINK ); } } /* ================ idTrigger_Touch::Enable ================ */ void idTrigger_Touch::Enable( void ) { BecomeActive( TH_THINK ); } /* ================ idTrigger_Touch::Disable ================ */ void idTrigger_Touch::Disable( void ) { BecomeInactive( TH_THINK ); }