/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../idlib/precompiled.h" #pragma hdrstop #include "Game_local.h" // a mover will update any gui entities in it's target list with // a key/val pair of "mover" "state" from below.. guis can represent // realtime info like this // binary only static const char *guiBinaryMoverStates[] = { "1", // pos 1 "2", // pos 2 "3", // moving 1 to 2 "4" // moving 2 to 1 }; /* =============================================================================== idMover =============================================================================== */ const idEventDef EV_FindGuiTargets( "", NULL ); const idEventDef EV_TeamBlocked( "", "ee" ); const idEventDef EV_PartBlocked( "", "e" ); const idEventDef EV_ReachedPos( "", NULL ); const idEventDef EV_ReachedAng( "", NULL ); const idEventDef EV_PostRestore( "", "ddddd" ); const idEventDef EV_StopMoving( "stopMoving", NULL ); const idEventDef EV_StopRotating( "stopRotating", NULL ); const idEventDef EV_Speed( "speed", "f" ); const idEventDef EV_Time( "time", "f" ); const idEventDef EV_AccelTime( "accelTime", "f" ); const idEventDef EV_DecelTime( "decelTime", "f" ); const idEventDef EV_MoveTo( "moveTo", "e" ); const idEventDef EV_MoveToPos( "moveToPos", "v" ); const idEventDef EV_Move( "move", "ff" ); const idEventDef EV_MoveAccelerateTo( "accelTo", "ff" ); const idEventDef EV_MoveDecelerateTo( "decelTo", "ff" ); const idEventDef EV_RotateDownTo( "rotateDownTo", "df" ); const idEventDef EV_RotateUpTo( "rotateUpTo", "df" ); const idEventDef EV_RotateTo( "rotateTo", "v" ); const idEventDef EV_Rotate( "rotate", "v" ); const idEventDef EV_RotateOnce( "rotateOnce", "v" ); const idEventDef EV_Bob( "bob", "ffv" ); const idEventDef EV_Sway( "sway", "ffv" ); const idEventDef EV_Mover_OpenPortal( "openPortal" ); const idEventDef EV_Mover_ClosePortal( "closePortal" ); const idEventDef EV_AccelSound( "accelSound", "s" ); const idEventDef EV_DecelSound( "decelSound", "s" ); const idEventDef EV_MoveSound( "moveSound", "s" ); const idEventDef EV_Mover_InitGuiTargets( "", NULL ); const idEventDef EV_EnableSplineAngles( "enableSplineAngles", NULL ); const idEventDef EV_DisableSplineAngles( "disableSplineAngles", NULL ); const idEventDef EV_RemoveInitialSplineAngles( "removeInitialSplineAngles", NULL ); const idEventDef EV_StartSpline( "startSpline", "e" ); const idEventDef EV_StopSpline( "stopSpline", NULL ); const idEventDef EV_IsMoving( "isMoving", NULL, 'd' ); const idEventDef EV_IsRotating( "isRotating", NULL, 'd' ); CLASS_DECLARATION( idEntity, idMover ) EVENT( EV_FindGuiTargets, idMover::Event_FindGuiTargets ) EVENT( EV_Thread_SetCallback, idMover::Event_SetCallback ) EVENT( EV_TeamBlocked, idMover::Event_TeamBlocked ) EVENT( EV_PartBlocked, idMover::Event_PartBlocked ) EVENT( EV_ReachedPos, idMover::Event_UpdateMove ) EVENT( EV_ReachedAng, idMover::Event_UpdateRotation ) EVENT( EV_PostRestore, idMover::Event_PostRestore ) EVENT( EV_StopMoving, idMover::Event_StopMoving ) EVENT( EV_StopRotating, idMover::Event_StopRotating ) EVENT( EV_Speed, idMover::Event_SetMoveSpeed ) EVENT( EV_Time, idMover::Event_SetMoveTime ) EVENT( EV_AccelTime, idMover::Event_SetAccellerationTime ) EVENT( EV_DecelTime, idMover::Event_SetDecelerationTime ) EVENT( EV_MoveTo, idMover::Event_MoveTo ) EVENT( EV_MoveToPos, idMover::Event_MoveToPos ) EVENT( EV_Move, idMover::Event_MoveDir ) EVENT( EV_MoveAccelerateTo, idMover::Event_MoveAccelerateTo ) EVENT( EV_MoveDecelerateTo, idMover::Event_MoveDecelerateTo ) EVENT( EV_RotateDownTo, idMover::Event_RotateDownTo ) EVENT( EV_RotateUpTo, idMover::Event_RotateUpTo ) EVENT( EV_RotateTo, idMover::Event_RotateTo ) EVENT( EV_Rotate, idMover::Event_Rotate ) EVENT( EV_RotateOnce, idMover::Event_RotateOnce ) EVENT( EV_Bob, idMover::Event_Bob ) EVENT( EV_Sway, idMover::Event_Sway ) EVENT( EV_Mover_OpenPortal, idMover::Event_OpenPortal ) EVENT( EV_Mover_ClosePortal, idMover::Event_ClosePortal ) EVENT( EV_AccelSound, idMover::Event_SetAccelSound ) EVENT( EV_DecelSound, idMover::Event_SetDecelSound ) EVENT( EV_MoveSound, idMover::Event_SetMoveSound ) EVENT( EV_Mover_InitGuiTargets, idMover::Event_InitGuiTargets ) EVENT( EV_EnableSplineAngles, idMover::Event_EnableSplineAngles ) EVENT( EV_DisableSplineAngles, idMover::Event_DisableSplineAngles ) EVENT( EV_RemoveInitialSplineAngles, idMover::Event_RemoveInitialSplineAngles ) EVENT( EV_StartSpline, idMover::Event_StartSpline ) EVENT( EV_StopSpline, idMover::Event_StopSpline ) EVENT( EV_Activate, idMover::Event_Activate ) EVENT( EV_IsMoving, idMover::Event_IsMoving ) EVENT( EV_IsRotating, idMover::Event_IsRotating ) END_CLASS /* ================ idMover::idMover ================ */ idMover::idMover( void ) { memset( &move, 0, sizeof( move ) ); memset( &rot, 0, sizeof( rot ) ); move_thread = 0; rotate_thread = 0; dest_angles.Zero(); angle_delta.Zero(); dest_position.Zero(); move_delta.Zero(); move_speed = 0.0f; move_time = 0; deceltime = 0; acceltime = 0; stopRotation = false; useSplineAngles = true; lastCommand = MOVER_NONE; damage = 0.0f; areaPortal = 0; fl.networkSync = true; } /* ================ idMover::Save ================ */ void idMover::Save( idSaveGame *savefile ) const { int i; savefile->WriteStaticObject( physicsObj ); savefile->WriteInt( move.stage ); savefile->WriteInt( move.acceleration ); savefile->WriteInt( move.movetime ); savefile->WriteInt( move.deceleration ); savefile->WriteVec3( move.dir ); savefile->WriteInt( rot.stage ); savefile->WriteInt( rot.acceleration ); savefile->WriteInt( rot.movetime ); savefile->WriteInt( rot.deceleration ); savefile->WriteFloat( rot.rot.pitch ); savefile->WriteFloat( rot.rot.yaw ); savefile->WriteFloat( rot.rot.roll ); savefile->WriteInt( move_thread ); savefile->WriteInt( rotate_thread ); savefile->WriteAngles( dest_angles ); savefile->WriteAngles( angle_delta ); savefile->WriteVec3( dest_position ); savefile->WriteVec3( move_delta ); savefile->WriteFloat( move_speed ); savefile->WriteInt( move_time ); savefile->WriteInt( deceltime ); savefile->WriteInt( acceltime ); savefile->WriteBool( stopRotation ); savefile->WriteBool( useSplineAngles ); savefile->WriteInt( lastCommand ); savefile->WriteFloat( damage ); savefile->WriteInt( areaPortal ); if ( areaPortal > 0 ) { savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); } savefile->WriteInt( guiTargets.Num() ); for( i = 0; i < guiTargets.Num(); i++ ) { guiTargets[ i ].Save( savefile ); } if ( splineEnt.GetEntity() && splineEnt.GetEntity()->GetSpline() ) { idCurve_Spline *spline = physicsObj.GetSpline(); savefile->WriteBool( true ); splineEnt.Save( savefile ); savefile->WriteInt( spline->GetTime( 0 ) ); savefile->WriteInt( spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) ); savefile->WriteInt( physicsObj.GetSplineAcceleration() ); savefile->WriteInt( physicsObj.GetSplineDeceleration() ); savefile->WriteInt( (int)physicsObj.UsingSplineAngles() ); } else { savefile->WriteBool( false ); } } /* ================ idMover::Restore ================ */ void idMover::Restore( idRestoreGame *savefile ) { int i, num; bool hasSpline = false; savefile->ReadStaticObject( physicsObj ); RestorePhysics( &physicsObj ); savefile->ReadInt( (int&)move.stage ); savefile->ReadInt( move.acceleration ); savefile->ReadInt( move.movetime ); savefile->ReadInt( move.deceleration ); savefile->ReadVec3( move.dir ); savefile->ReadInt( (int&)rot.stage ); savefile->ReadInt( rot.acceleration ); savefile->ReadInt( rot.movetime ); savefile->ReadInt( rot.deceleration ); savefile->ReadFloat( rot.rot.pitch ); savefile->ReadFloat( rot.rot.yaw ); savefile->ReadFloat( rot.rot.roll ); savefile->ReadInt( move_thread ); savefile->ReadInt( rotate_thread ); savefile->ReadAngles( dest_angles ); savefile->ReadAngles( angle_delta ); savefile->ReadVec3( dest_position ); savefile->ReadVec3( move_delta ); savefile->ReadFloat( move_speed ); savefile->ReadInt( move_time ); savefile->ReadInt( deceltime ); savefile->ReadInt( acceltime ); savefile->ReadBool( stopRotation ); savefile->ReadBool( useSplineAngles ); savefile->ReadInt( (int &)lastCommand ); savefile->ReadFloat( damage ); savefile->ReadInt( areaPortal ); if ( areaPortal > 0 ) { int portalState = 0; savefile->ReadInt( portalState ); gameLocal.SetPortalState( areaPortal, portalState ); } guiTargets.Clear(); savefile->ReadInt( num ); guiTargets.SetNum( num ); for( i = 0; i < num; i++ ) { guiTargets[ i ].Restore( savefile ); } savefile->ReadBool( hasSpline ); if ( hasSpline ) { int starttime; int totaltime; int accel; int decel; int useAngles; splineEnt.Restore( savefile ); savefile->ReadInt( starttime ); savefile->ReadInt( totaltime ); savefile->ReadInt( accel ); savefile->ReadInt( decel ); savefile->ReadInt( useAngles ); PostEventMS( &EV_PostRestore, 0, starttime, totaltime, accel, decel, useAngles ); } } /* ================ idMover::Event_PostRestore ================ */ void idMover::Event_PostRestore( int start, int total, int accel, int decel, int useSplineAng ) { idCurve_Spline *spline; idEntity *splineEntity = splineEnt.GetEntity(); if ( !splineEntity ) { // We should never get this event if splineEnt is invalid common->Warning( "Invalid spline entity during restore\n" ); return; } spline = splineEntity->GetSpline(); spline->MakeUniform( total ); spline->ShiftTime( start - spline->GetTime( 0 ) ); physicsObj.SetSpline( spline, accel, decel, ( useSplineAng != 0 ) ); physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); } /* ================ idMover::Spawn ================ */ void idMover::Spawn( void ) { move_thread = 0; rotate_thread = 0; stopRotation = false; lastCommand = MOVER_NONE; acceltime = 1000.0f * spawnArgs.GetFloat( "accel_time", "0" ); deceltime = 1000.0f * spawnArgs.GetFloat( "decel_time", "0" ); move_time = 1000.0f * spawnArgs.GetFloat( "move_time", "1" ); // safe default value move_speed = spawnArgs.GetFloat( "move_speed", "0" ); spawnArgs.GetFloat( "damage" , "0", damage ); dest_position = GetPhysics()->GetOrigin(); dest_angles = GetPhysics()->GetAxis().ToAngles(); physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); physicsObj.SetAxis( GetPhysics()->GetAxis() ); physicsObj.SetClipMask( MASK_SOLID ); if ( !spawnArgs.GetBool( "solid", "1" ) ) { physicsObj.SetContents( 0 ); } if ( !renderEntity.hModel || !spawnArgs.GetBool( "nopush" ) ) { physicsObj.SetPusher( 0 ); } physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); SetPhysics( &physicsObj ); // see if we are on an areaportal areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { PostEventMS( &EV_FindGuiTargets, 0 ); } else { // not during spawn, so it's ok to get the targets FindGuiTargets(); } } health = spawnArgs.GetInt( "health" ); if ( health ) { fl.takedamage = true; } } /* ================ idMover::Hide ================ */ void idMover::Hide( void ) { idEntity::Hide(); physicsObj.SetContents( 0 ); } /* ================ idMover::Show ================ */ void idMover::Show( void ) { idEntity::Show(); if ( spawnArgs.GetBool( "solid", "1" ) ) { physicsObj.SetContents( CONTENTS_SOLID ); } SetPhysics( &physicsObj ); } /* ============ idMover::Killed ============ */ void idMover::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { fl.takedamage = false; ActivateTargets( this ); } /* ================ idMover::Event_SetCallback ================ */ void idMover::Event_SetCallback( void ) { if ( ( lastCommand == MOVER_ROTATING ) && !rotate_thread ) { lastCommand = MOVER_NONE; rotate_thread = idThread::CurrentThreadNum(); idThread::ReturnInt( true ); } else if ( ( lastCommand == MOVER_MOVING || lastCommand == MOVER_SPLINE ) && !move_thread ) { lastCommand = MOVER_NONE; move_thread = idThread::CurrentThreadNum(); idThread::ReturnInt( true ); } else { idThread::ReturnInt( false ); } } /* ================ idMover::VectorForDir ================ */ void idMover::VectorForDir( float angle, idVec3 &vec ) { idAngles ang; switch( ( int )angle ) { case DIR_UP : vec.Set( 0, 0, 1 ); break; case DIR_DOWN : vec.Set( 0, 0, -1 ); break; case DIR_LEFT : physicsObj.GetLocalAngles( ang ); ang.pitch = 0; ang.roll = 0; ang.yaw += 90; vec = ang.ToForward(); break; case DIR_RIGHT : physicsObj.GetLocalAngles( ang ); ang.pitch = 0; ang.roll = 0; ang.yaw -= 90; vec = ang.ToForward(); break; case DIR_FORWARD : physicsObj.GetLocalAngles( ang ); ang.pitch = 0; ang.roll = 0; vec = ang.ToForward(); break; case DIR_BACK : physicsObj.GetLocalAngles( ang ); ang.pitch = 0; ang.roll = 0; ang.yaw += 180; vec = ang.ToForward(); break; case DIR_REL_UP : vec.Set( 0, 0, 1 ); break; case DIR_REL_DOWN : vec.Set( 0, 0, -1 ); break; case DIR_REL_LEFT : physicsObj.GetLocalAngles( ang ); ang.ToVectors( NULL, &vec ); vec *= -1; break; case DIR_REL_RIGHT : physicsObj.GetLocalAngles( ang ); ang.ToVectors( NULL, &vec ); break; case DIR_REL_FORWARD : physicsObj.GetLocalAngles( ang ); vec = ang.ToForward(); break; case DIR_REL_BACK : physicsObj.GetLocalAngles( ang ); vec = ang.ToForward() * -1; break; default: ang.Set( 0, angle, 0 ); vec = GetWorldVector( ang.ToForward() ); break; } } /* ================ idMover::FindGuiTargets ================ */ void idMover::FindGuiTargets( void ) { gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); } /* ============================== idMover::SetGuiState key/val will be set to any renderEntity->gui's on the list ============================== */ void idMover::SetGuiState( const char *key, const char *val ) const { gameLocal.Printf( "Setting %s to %s\n", key, val ); for( int i = 0; i < guiTargets.Num(); i++ ) { idEntity *ent = guiTargets[ i ].GetEntity(); if ( ent ) { for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); } } ent->UpdateVisuals(); } } } /* ================ idMover::Event_InitGuiTargets ================ */ void idMover::Event_FindGuiTargets( void ) { FindGuiTargets(); } /* ================ idMover::SetGuiStates ================ */ void idMover::SetGuiStates( const char *state ) { int i; if ( guiTargets.Num() ) { SetGuiState( "movestate", state ); } for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { if ( renderEntity.gui[ i ] ) { renderEntity.gui[ i ]->SetStateString( "movestate", state ); renderEntity.gui[ i ]->StateChanged( gameLocal.time, true ); } } } /* ================ idMover::Event_InitGuiTargets ================ */ void idMover::Event_InitGuiTargets( void ) { SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); } /*********************************************************************** Translation control functions ***********************************************************************/ /* ================ idMover::Event_StopMoving ================ */ void idMover::Event_StopMoving( void ) { physicsObj.GetLocalOrigin( dest_position ); DoneMoving(); } /* ================ idMover::DoneMoving ================ */ void idMover::DoneMoving( void ) { if ( lastCommand != MOVER_SPLINE ) { // set our final position so that we get rid of any numerical inaccuracy physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); } lastCommand = MOVER_NONE; idThread::ObjectMoveDone( move_thread, this ); move_thread = 0; StopSound( SND_CHANNEL_BODY, false ); } /* ================ idMover::UpdateMoveSound ================ */ void idMover::UpdateMoveSound( moveStage_t stage ) { switch( stage ) { case ACCELERATION_STAGE: { StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); break; } case LINEAR_STAGE: { StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); break; } case DECELERATION_STAGE: { StopSound( SND_CHANNEL_BODY, false ); StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); break; } case FINISHED_STAGE: { StopSound( SND_CHANNEL_BODY, false ); break; } } } /* ================ idMover::Event_UpdateMove ================ */ void idMover::Event_UpdateMove( void ) { idVec3 org; physicsObj.GetLocalOrigin( org ); UpdateMoveSound( move.stage ); switch( move.stage ) { case ACCELERATION_STAGE: { physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, move.dir, vec3_origin ); if ( move.movetime > 0 ) { move.stage = LINEAR_STAGE; } else if ( move.deceleration > 0 ) { move.stage = DECELERATION_STAGE; } else { move.stage = FINISHED_STAGE; } break; } case LINEAR_STAGE: { physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, move.movetime, org, move.dir, vec3_origin ); if ( move.deceleration ) { move.stage = DECELERATION_STAGE; } else { move.stage = FINISHED_STAGE; } break; } case DECELERATION_STAGE: { physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, move.dir, vec3_origin ); move.stage = FINISHED_STAGE; break; } case FINISHED_STAGE: { if ( g_debugMover.GetBool() ) { gameLocal.Printf( "%d: '%s' move done\n", gameLocal.time, name.c_str() ); } DoneMoving(); break; } } } /* ================ idMover::BeginMove ================ */ void idMover::BeginMove( idThread *thread ) { moveStage_t stage; idVec3 org; float dist; float acceldist; int totalacceltime; int at; int dt; lastCommand = MOVER_MOVING; move_thread = 0; physicsObj.GetLocalOrigin( org ); move_delta = dest_position - org; if ( move_delta.Compare( vec3_zero ) ) { DoneMoving(); return; } // scale times up to whole physics frames at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); move_time += at - acceltime; acceltime = at; dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); move_time += dt - deceltime; deceltime = dt; // if we're moving at a specific speed, we need to calculate the move time if ( move_speed ) { dist = move_delta.Length(); totalacceltime = acceltime + deceltime; // calculate the distance we'll move during acceleration and deceleration acceldist = totalacceltime * 0.5f * 0.001f * move_speed; if ( acceldist >= dist ) { // going too slow for this distance to move at a constant speed move_time = totalacceltime; } else { // calculate move time taking acceleration into account move_time = totalacceltime + 1000.0f * ( dist - acceldist ) / move_speed; } } // scale time up to a whole physics frames move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); if ( acceltime ) { stage = ACCELERATION_STAGE; } else if ( move_time <= deceltime ) { stage = DECELERATION_STAGE; } else { stage = LINEAR_STAGE; } at = acceltime; dt = deceltime; if ( at + dt > move_time ) { // there's no real correct way to handle this, so we just scale // the times to fit into the move time in the same proportions at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); dt = move_time - at; } move_delta = move_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); move.stage = stage; move.acceleration = at; move.movetime = move_time - at - dt; move.deceleration = dt; move.dir = move_delta; ProcessEvent( &EV_ReachedPos ); } /*********************************************************************** Rotation control functions ***********************************************************************/ /* ================ idMover::Event_StopRotating ================ */ void idMover::Event_StopRotating( void ) { physicsObj.GetLocalAngles( dest_angles ); physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); DoneRotating(); } /* ================ idMover::DoneRotating ================ */ void idMover::DoneRotating( void ) { lastCommand = MOVER_NONE; idThread::ObjectMoveDone( rotate_thread, this ); rotate_thread = 0; StopSound( SND_CHANNEL_BODY, false ); } /* ================ idMover::UpdateRotationSound ================ */ void idMover::UpdateRotationSound( moveStage_t stage ) { switch( stage ) { case ACCELERATION_STAGE: { StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); break; } case LINEAR_STAGE: { StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); break; } case DECELERATION_STAGE: { StopSound( SND_CHANNEL_BODY, false ); StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); break; } case FINISHED_STAGE: { StopSound( SND_CHANNEL_BODY, false ); break; } } } /* ================ idMover::Event_UpdateRotation ================ */ void idMover::Event_UpdateRotation( void ) { idAngles ang; physicsObj.GetLocalAngles( ang ); UpdateRotationSound( rot.stage ); switch( rot.stage ) { case ACCELERATION_STAGE: { physicsObj.SetAngularExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, rot.acceleration, ang, rot.rot, ang_zero ); if ( rot.movetime > 0 ) { rot.stage = LINEAR_STAGE; } else if ( rot.deceleration > 0 ) { rot.stage = DECELERATION_STAGE; } else { rot.stage = FINISHED_STAGE; } break; } case LINEAR_STAGE: { if ( !stopRotation && !rot.deceleration ) { physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); } else { physicsObj.SetAngularExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); } if ( rot.deceleration ) { rot.stage = DECELERATION_STAGE; } else { rot.stage = FINISHED_STAGE; } break; } case DECELERATION_STAGE: { physicsObj.SetAngularExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, rot.deceleration, ang, rot.rot, ang_zero ); rot.stage = FINISHED_STAGE; break; } case FINISHED_STAGE: { lastCommand = MOVER_NONE; if ( stopRotation ) { // set our final angles so that we get rid of any numerical inaccuracy dest_angles.Normalize360(); physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); stopRotation = false; } else if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_ACCELLINEAR ) { // keep our angular velocity constant physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, rot.rot, ang_zero ); } if ( g_debugMover.GetBool() ) { gameLocal.Printf( "%d: '%s' rotation done\n", gameLocal.time, name.c_str() ); } DoneRotating(); break; } } } /* ================ idMover::BeginRotation ================ */ void idMover::BeginRotation( idThread *thread, bool stopwhendone ) { moveStage_t stage; idAngles ang; int at; int dt; lastCommand = MOVER_ROTATING; rotate_thread = 0; // rotation always uses move_time so that if a move was started before the rotation, // the rotation will take the same amount of time as the move. If no move has been // started and no time is set, the rotation takes 1 second. if ( !move_time ) { move_time = 1; } physicsObj.GetLocalAngles( ang ); angle_delta = dest_angles - ang; if ( angle_delta == ang_zero ) { // set our final angles so that we get rid of any numerical inaccuracy dest_angles.Normalize360(); physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); stopRotation = false; DoneRotating(); return; } // scale times up to whole physics frames at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); move_time += at - acceltime; acceltime = at; dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); move_time += dt - deceltime; deceltime = dt; move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); if ( acceltime ) { stage = ACCELERATION_STAGE; } else if ( move_time <= deceltime ) { stage = DECELERATION_STAGE; } else { stage = LINEAR_STAGE; } at = acceltime; dt = deceltime; if ( at + dt > move_time ) { // there's no real correct way to handle this, so we just scale // the times to fit into the move time in the same proportions at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); dt = move_time - at; } angle_delta = angle_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); stopRotation = stopwhendone || ( dt != 0 ); rot.stage = stage; rot.acceleration = at; rot.movetime = move_time - at - dt; rot.deceleration = dt; rot.rot = angle_delta; ProcessEvent( &EV_ReachedAng ); } /*********************************************************************** Script callable routines ***********************************************************************/ /* =============== idMover::Event_TeamBlocked =============== */ void idMover::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { if ( g_debugMover.GetBool() ) { gameLocal.Printf( "%d: '%s' stopped due to team member '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockedEntity->name.c_str(), blockingEntity->name.c_str() ); } } /* =============== idMover::Event_PartBlocked =============== */ void idMover::Event_PartBlocked( idEntity *blockingEntity ) { if ( damage > 0.0f ) { blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); } if ( g_debugMover.GetBool() ) { gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockingEntity->name.c_str() ); } } /* ================ idMover::Event_SetMoveSpeed ================ */ void idMover::Event_SetMoveSpeed( float speed ) { if ( speed <= 0 ) { gameLocal.Error( "Cannot set speed less than or equal to 0." ); } move_speed = speed; move_time = 0; // move_time is calculated for each move when move_speed is non-0 } /* ================ idMover::Event_SetMoveTime ================ */ void idMover::Event_SetMoveTime( float time ) { if ( time <= 0 ) { gameLocal.Error( "Cannot set time less than or equal to 0." ); } move_speed = 0; move_time = SEC2MS( time ); } /* ================ idMover::Event_SetAccellerationTime ================ */ void idMover::Event_SetAccellerationTime( float time ) { if ( time < 0 ) { gameLocal.Error( "Cannot set acceleration time less than 0." ); } acceltime = SEC2MS( time ); } /* ================ idMover::Event_SetDecelerationTime ================ */ void idMover::Event_SetDecelerationTime( float time ) { if ( time < 0 ) { gameLocal.Error( "Cannot set deceleration time less than 0." ); } deceltime = SEC2MS( time ); } /* ================ idMover::Event_MoveTo ================ */ void idMover::Event_MoveTo( idEntity *ent ) { if ( !ent ) { gameLocal.Warning( "Entity not found" ); } dest_position = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() ); BeginMove( idThread::CurrentThread() ); } /* ================ idMover::MoveToPos ================ */ void idMover::MoveToPos( const idVec3 &pos ) { dest_position = GetLocalCoordinates( pos ); BeginMove( NULL ); } /* ================ idMover::Event_MoveToPos ================ */ void idMover::Event_MoveToPos( idVec3 &pos ) { MoveToPos( pos ); } /* ================ idMover::Event_MoveDir ================ */ void idMover::Event_MoveDir( float angle, float distance ) { idVec3 dir; idVec3 org; physicsObj.GetLocalOrigin( org ); VectorForDir( angle, dir ); dest_position = org + dir * distance; BeginMove( idThread::CurrentThread() ); } /* ================ idMover::Event_MoveAccelerateTo ================ */ void idMover::Event_MoveAccelerateTo( float speed, float time ) { float v; idVec3 org, dir; int at; if ( time < 0 ) { gameLocal.Error( "idMover::Event_MoveAccelerateTo: cannot set acceleration time less than 0." ); } dir = physicsObj.GetLinearVelocity(); v = dir.Normalize(); // if not moving already if ( v == 0.0f ) { gameLocal.Error( "idMover::Event_MoveAccelerateTo: not moving." ); } // if already moving faster than the desired speed if ( v >= speed ) { return; } at = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); lastCommand = MOVER_MOVING; physicsObj.GetLocalOrigin( org ); move.stage = ACCELERATION_STAGE; move.acceleration = at; move.movetime = 0; move.deceleration = 0; StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, dir * ( speed - v ), dir * v ); } /* ================ idMover::Event_MoveDecelerateTo ================ */ void idMover::Event_MoveDecelerateTo( float speed, float time ) { float v; idVec3 org, dir; int dt; if ( time < 0 ) { gameLocal.Error( "idMover::Event_MoveDecelerateTo: cannot set deceleration time less than 0." ); } dir = physicsObj.GetLinearVelocity(); v = dir.Normalize(); // if not moving already if ( v == 0.0f ) { gameLocal.Error( "idMover::Event_MoveDecelerateTo: not moving." ); } // if already moving slower than the desired speed if ( v <= speed ) { return; } dt = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); lastCommand = MOVER_MOVING; physicsObj.GetLocalOrigin( org ); move.stage = DECELERATION_STAGE; move.acceleration = 0; move.movetime = 0; move.deceleration = dt; StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, dir * ( v - speed ), dir * speed ); } /* ================ idMover::Event_RotateDownTo ================ */ void idMover::Event_RotateDownTo( int axis, float angle ) { idAngles ang; if ( ( axis < 0 ) || ( axis > 2 ) ) { gameLocal.Error( "Invalid axis" ); } physicsObj.GetLocalAngles( ang ); dest_angles[ axis ] = angle; if ( dest_angles[ axis ] > ang[ axis ] ) { dest_angles[ axis ] -= 360; } BeginRotation( idThread::CurrentThread(), true ); } /* ================ idMover::Event_RotateUpTo ================ */ void idMover::Event_RotateUpTo( int axis, float angle ) { idAngles ang; if ( ( axis < 0 ) || ( axis > 2 ) ) { gameLocal.Error( "Invalid axis" ); } physicsObj.GetLocalAngles( ang ); dest_angles[ axis ] = angle; if ( dest_angles[ axis ] < ang[ axis ] ) { dest_angles[ axis ] += 360; } BeginRotation( idThread::CurrentThread(), true ); } /* ================ idMover::Event_RotateTo ================ */ void idMover::Event_RotateTo( idAngles &angles ) { dest_angles = angles; BeginRotation( idThread::CurrentThread(), true ); } /* ================ idMover::Event_Rotate ================ */ void idMover::Event_Rotate( idAngles &angles ) { idAngles ang; if ( rotate_thread ) { DoneRotating(); } physicsObj.GetLocalAngles( ang ); dest_angles = ang + angles * ( move_time - ( acceltime + deceltime ) / 2 ) * 0.001f; BeginRotation( idThread::CurrentThread(), false ); } /* ================ idMover::Event_RotateOnce ================ */ void idMover::Event_RotateOnce( idAngles &angles ) { idAngles ang; if ( rotate_thread ) { DoneRotating(); } physicsObj.GetLocalAngles( ang ); dest_angles = ang + angles; BeginRotation( idThread::CurrentThread(), true ); } /* ================ idMover::Event_Bob ================ */ void idMover::Event_Bob( float speed, float phase, idVec3 &depth ) { idVec3 org; physicsObj.GetLocalOrigin( org ); physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), speed * 1000 * phase, speed * 500, org, depth * 2.0f, vec3_origin ); } /* ================ idMover::Event_Sway ================ */ void idMover::Event_Sway( float speed, float phase, idAngles &depth ) { idAngles ang, angSpeed; float duration; physicsObj.GetLocalAngles( ang ); assert ( speed > 0.0f ); duration = idMath::Sqrt( depth[0] * depth[0] + depth[1] * depth[1] + depth[2] * depth[2] ) / speed; angSpeed = depth / ( duration * idMath::SQRT_1OVER2 ); physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), duration * 1000.0f * phase, duration * 1000.0f, ang, angSpeed, ang_zero ); } /* ================ idMover::Event_OpenPortal Sets the portal associtated with this mover to be open ================ */ void idMover::Event_OpenPortal( void ) { if ( areaPortal ) { SetPortalState( true ); } } /* ================ idMover::Event_ClosePortal Sets the portal associtated with this mover to be closed ================ */ void idMover::Event_ClosePortal( void ) { if ( areaPortal ) { SetPortalState( false ); } } /* ================ idMover::Event_SetAccelSound ================ */ void idMover::Event_SetAccelSound( const char *sound ) { // refSound.SetSound( "accel", sound ); } /* ================ idMover::Event_SetDecelSound ================ */ void idMover::Event_SetDecelSound( const char *sound ) { // refSound.SetSound( "decel", sound ); } /* ================ idMover::Event_SetMoveSound ================ */ void idMover::Event_SetMoveSound( const char *sound ) { // refSound.SetSound( "move", sound ); } /* ================ idMover::Event_EnableSplineAngles ================ */ void idMover::Event_EnableSplineAngles( void ) { useSplineAngles = true; } /* ================ idMover::Event_DisableSplineAngles ================ */ void idMover::Event_DisableSplineAngles( void ) { useSplineAngles = false; } /* ================ idMover::Event_RemoveInitialSplineAngles ================ */ void idMover::Event_RemoveInitialSplineAngles( void ) { idCurve_Spline *spline; idAngles ang; spline = physicsObj.GetSpline(); if ( !spline ) { return; } ang = spline->GetCurrentFirstDerivative( 0 ).ToAngles(); physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, -ang, ang_zero, ang_zero ); } /* ================ idMover::Event_StartSpline ================ */ void idMover::Event_StartSpline( idEntity *splineEntity ) { idCurve_Spline *spline; if ( !splineEntity ) { return; } // Needed for savegames splineEnt = splineEntity; spline = splineEntity->GetSpline(); if ( !spline ) { return; } lastCommand = MOVER_SPLINE; move_thread = 0; if ( acceltime + deceltime > move_time ) { acceltime = move_time / 2; deceltime = move_time - acceltime; } move.stage = FINISHED_STAGE; move.acceleration = acceltime; move.movetime = move_time; move.deceleration = deceltime; spline->MakeUniform( move_time ); spline->ShiftTime( gameLocal.time - spline->GetTime( 0 ) ); physicsObj.SetSpline( spline, move.acceleration, move.deceleration, useSplineAngles ); physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); } /* ================ idMover::Event_StopSpline ================ */ void idMover::Event_StopSpline( void ) { physicsObj.SetSpline( NULL, 0, 0, useSplineAngles ); splineEnt = NULL; } /* ================ idMover::Event_Activate ================ */ void idMover::Event_Activate( idEntity *activator ) { Show(); Event_StartSpline( this ); } /* ================ idMover::Event_IsMoving ================ */ void idMover::Event_IsMoving( void ) { if ( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) { idThread::ReturnInt( false ); } else { idThread::ReturnInt( true ); } } /* ================ idMover::Event_IsRotating ================ */ void idMover::Event_IsRotating( void ) { if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_NONE ) { idThread::ReturnInt( false ); } else { idThread::ReturnInt( true ); } } /* ================ idMover::WriteToSnapshot ================ */ void idMover::WriteToSnapshot( idBitMsgDelta &msg ) const { physicsObj.WriteToSnapshot( msg ); msg.WriteBits( move.stage, 3 ); msg.WriteBits( rot.stage, 3 ); WriteBindToSnapshot( msg ); WriteGUIToSnapshot( msg ); } /* ================ idMover::ReadFromSnapshot ================ */ void idMover::ReadFromSnapshot( const idBitMsgDelta &msg ) { moveStage_t oldMoveStage = move.stage; moveStage_t oldRotStage = rot.stage; physicsObj.ReadFromSnapshot( msg ); move.stage = (moveStage_t) msg.ReadBits( 3 ); rot.stage = (moveStage_t) msg.ReadBits( 3 ); ReadBindFromSnapshot( msg ); ReadGUIFromSnapshot( msg ); if ( msg.HasChanged() ) { if ( move.stage != oldMoveStage ) { UpdateMoveSound( oldMoveStage ); } if ( rot.stage != oldRotStage ) { UpdateRotationSound( oldRotStage ); } UpdateVisuals(); } } /* ================ idMover::SetPortalState ================ */ void idMover::SetPortalState( bool open ) { assert( areaPortal ); gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); } /* =============================================================================== idSplinePath, holds a spline path to be used by an idMover =============================================================================== */ CLASS_DECLARATION( idEntity, idSplinePath ) END_CLASS /* ================ idSplinePath::idSplinePath ================ */ idSplinePath::idSplinePath() { } /* ================ idSplinePath::Spawn ================ */ void idSplinePath::Spawn( void ) { } /* =============================================================================== idElevator =============================================================================== */ const idEventDef EV_PostArrival( "postArrival", NULL ); const idEventDef EV_GotoFloor( "gotoFloor", "d" ); CLASS_DECLARATION( idMover, idElevator ) EVENT( EV_Activate, idElevator::Event_Activate ) EVENT( EV_TeamBlocked, idElevator::Event_TeamBlocked ) EVENT( EV_PartBlocked, idElevator::Event_PartBlocked ) EVENT( EV_PostArrival, idElevator::Event_PostFloorArrival ) EVENT( EV_GotoFloor, idElevator::Event_GotoFloor ) EVENT( EV_Touch, idElevator::Event_Touch ) END_CLASS /* ================ idElevator::idElevator ================ */ idElevator::idElevator( void ) { state = INIT; floorInfo.Clear(); currentFloor = 0; pendingFloor = 0; lastFloor = 0; controlsDisabled = false; lastTouchTime = 0; returnFloor = 0; returnTime = 0; } /* ================ idElevator::Save ================ */ void idElevator::Save( idSaveGame *savefile ) const { int i; savefile->WriteInt( (int)state ); savefile->WriteInt( floorInfo.Num() ); for ( i = 0; i < floorInfo.Num(); i++ ) { savefile->WriteVec3( floorInfo[ i ].pos ); savefile->WriteString( floorInfo[ i ].door ); savefile->WriteInt( floorInfo[ i ].floor ); } savefile->WriteInt( currentFloor ); savefile->WriteInt( pendingFloor ); savefile->WriteInt( lastFloor ); savefile->WriteBool( controlsDisabled ); savefile->WriteFloat( returnTime ); savefile->WriteInt( returnFloor ); savefile->WriteInt( lastTouchTime ); } /* ================ idElevator::Restore ================ */ void idElevator::Restore( idRestoreGame *savefile ) { int i, num; savefile->ReadInt( (int &)state ); savefile->ReadInt( num ); for ( i = 0; i < num; i++ ) { floorInfo_s floor; savefile->ReadVec3( floor.pos ); savefile->ReadString( floor.door ); savefile->ReadInt( floor.floor ); floorInfo.Append( floor ); } savefile->ReadInt( currentFloor ); savefile->ReadInt( pendingFloor ); savefile->ReadInt( lastFloor ); savefile->ReadBool( controlsDisabled ); savefile->ReadFloat( returnTime ); savefile->ReadInt( returnFloor ); savefile->ReadInt( lastTouchTime ); } /* ================ idElevator::Spawn ================ */ void idElevator::Spawn( void ) { idStr str; int len1; lastFloor = 0; currentFloor = 0; pendingFloor = spawnArgs.GetInt( "floor", "1" ); SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1]); returnTime = spawnArgs.GetFloat( "returnTime" ); returnFloor = spawnArgs.GetInt( "returnFloor" ); len1 = strlen( "floorPos_" ); const idKeyValue *kv = spawnArgs.MatchPrefix( "floorPos_", NULL ); while( kv ) { str = kv->GetKey().Right( kv->GetKey().Length() - len1 ); floorInfo_s fi; fi.floor = atoi( str ); fi.door = spawnArgs.GetString( va( "floorDoor_%i", fi.floor ) ); fi.pos = spawnArgs.GetVector( kv->GetKey() ); floorInfo.Append( fi ); kv = spawnArgs.MatchPrefix( "floorPos_", kv ); } lastTouchTime = 0; state = INIT; BecomeActive( TH_THINK | TH_PHYSICS ); PostEventMS( &EV_Mover_InitGuiTargets, 0 ); controlsDisabled = false; } /* ============== idElevator::Event_Touch =============== */ void idElevator::Event_Touch( idEntity *other, trace_t *trace ) { if ( gameLocal.time < lastTouchTime + 2000 ) { return; } if ( !other->IsType( idPlayer::Type ) ) { return; } lastTouchTime = gameLocal.time; if ( thinkFlags & TH_PHYSICS ) { return; } int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { PostEventSec( &EV_GotoFloor, 0.25f, triggerFloor ); } } /* ================ idElevator::Think ================ */ void idElevator::Think( void ) { idVec3 masterOrigin; idMat3 masterAxis; idDoor *doorent = GetDoor( spawnArgs.GetString( "innerdoor" ) ); if ( state == INIT ) { state = IDLE; if ( doorent ) { doorent->BindTeam( this ); doorent->spawnArgs.Set( "snd_open", "" ); doorent->spawnArgs.Set( "snd_close", "" ); doorent->spawnArgs.Set( "snd_opened", "" ); } for ( int i = 0; i < floorInfo.Num(); i++ ) { idDoor *door = GetDoor( floorInfo[i].door ); if ( door ) { door->SetCompanion( doorent ); } } Event_GotoFloor( pendingFloor ); DisableAllDoors(); SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); } else if ( state == WAITING_ON_DOORS ) { if ( doorent ) { state = doorent->IsOpen() ? WAITING_ON_DOORS : IDLE; } else { state = IDLE; } if ( state == IDLE ) { lastFloor = currentFloor; currentFloor = pendingFloor; floorInfo_s *fi = GetFloorInfo( currentFloor ); if ( fi ) { MoveToPos( fi->pos ); } } } RunPhysics(); Present(); } /* ================ idElevator::Event_Activate ================ */ void idElevator::Event_Activate( idEntity *activator ) { int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { Event_GotoFloor( triggerFloor ); } } /* ================ idElevator::Event_TeamBlocked ================ */ void idElevator::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { if ( blockedEntity == this ) { Event_GotoFloor( lastFloor ); } else if ( blockedEntity && blockedEntity->IsType( idDoor::Type ) ) { // open the inner doors if one is blocked idDoor *blocked = static_cast( blockedEntity ); idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); if ( door && blocked->GetMoveMaster() == door->GetMoveMaster() ) { door->SetBlocked(true); OpenInnerDoor(); OpenFloorDoor( currentFloor ); } } } /* =============== idElevator::HandleSingleGuiCommand =============== */ bool idElevator::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { idToken token; if ( controlsDisabled ) { return false; } if ( !src->ReadToken( &token ) ) { return false; } if ( token == ";" ) { return false; } if ( token.Icmp( "changefloor" ) == 0 ) { if ( src->ReadToken( &token ) ) { int newFloor = atoi( token ); if ( newFloor == currentFloor ) { // open currentFloor and interior doors OpenInnerDoor(); OpenFloorDoor( currentFloor ); } else { idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); if ( door && door->IsOpen() ) { PostEventSec( &EV_GotoFloor, 0.5f, newFloor ); } else { ProcessEvent( &EV_GotoFloor, newFloor ); } } return true; } } src->UnreadToken( &token ); return false; } /* ================ idElevator::OpenFloorDoor ================ */ void idElevator::OpenFloorDoor( int floor ) { floorInfo_s *fi = GetFloorInfo( floor ); if ( fi ) { idDoor *door = GetDoor( fi->door ); if ( door ) { door->Open(); } } } /* ================ idElevator::OpenInnerDoor ================ */ void idElevator::OpenInnerDoor( void ) { idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); if ( door ) { door->Open(); } } /* ================ idElevator::GetFloorInfo ================ */ floorInfo_s *idElevator::GetFloorInfo( int floor ) { for ( int i = 0; i < floorInfo.Num(); i++ ) { if ( floorInfo[i].floor == floor ) { return &floorInfo[i]; } } return NULL; } /* ================ idElevator::Event_GotoFloor ================ */ void idElevator::Event_GotoFloor( int floor ) { floorInfo_s *fi = GetFloorInfo( floor ); if ( fi ) { idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); if ( door ) { if ( door->IsBlocked() || door->IsOpen() ) { PostEventSec( &EV_GotoFloor, 0.5f, floor ); return; } } DisableAllDoors(); CloseAllDoors(); state = WAITING_ON_DOORS; pendingFloor = floor; } } /* ================ idElevator::BeginMove ================ */ void idElevator::BeginMove( idThread *thread ) { controlsDisabled = true; CloseAllDoors(); DisableAllDoors(); const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" ); while( kv ) { idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); if ( ent ) { for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { ent->GetRenderEntity()->gui[ j ]->SetStateString( "floor", "" ); ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); } } ent->UpdateVisuals(); } kv = spawnArgs.MatchPrefix( "statusGui", kv ); } SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[3] : guiBinaryMoverStates[2] ); idMover::BeginMove( thread ); } /* ================ idElevator::GetDoor ================ */ idDoor *idElevator::GetDoor( const char *name ) { idEntity *ent; idEntity *master; idDoor *doorEnt; doorEnt = NULL; if ( name && *name ) { ent = gameLocal.FindEntity( name ); if ( ent && ent->IsType( idDoor::Type ) ) { doorEnt = static_cast( ent ); master = doorEnt->GetMoveMaster(); if ( master != doorEnt ) { if ( master->IsType( idDoor::Type ) ) { doorEnt = static_cast( master ); } else { doorEnt = NULL; } } } } return doorEnt; } /* ================ idElevator::Event_PostFloorArrival ================ */ void idElevator::Event_PostFloorArrival() { OpenFloorDoor( currentFloor ); OpenInnerDoor(); SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); controlsDisabled = false; if ( returnTime > 0.0f && returnFloor != currentFloor ) { PostEventSec( &EV_GotoFloor, returnTime, returnFloor ); } } /* ================ idElevator::DoneMoving ================ */ void idElevator::DoneMoving( void ) { idMover::DoneMoving(); EnableProperDoors(); const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" ); while( kv ) { idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); if ( ent ) { for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { ent->GetRenderEntity()->gui[ j ]->SetStateString( "floor", va( "%i", currentFloor ) ); ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); } } ent->UpdateVisuals(); } kv = spawnArgs.MatchPrefix( "statusGui", kv ); } if ( spawnArgs.GetInt( "pauseOnFloor", "-1" ) == currentFloor ) { PostEventSec( &EV_PostArrival, spawnArgs.GetFloat( "pauseTime" ) ); } else { Event_PostFloorArrival(); } } /* ================ idElevator::CloseAllDoors ================ */ void idElevator::CloseAllDoors( void ) { idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); if ( door ) { door->Close(); } for ( int i = 0; i < floorInfo.Num(); i++ ) { door = GetDoor( floorInfo[i].door ); if ( door ) { door->Close(); } } } /* ================ idElevator::DisableAllDoors ================ */ void idElevator::DisableAllDoors( void ) { idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); if ( door ) { door->Enable( false ); } for ( int i = 0; i < floorInfo.Num(); i++ ) { door = GetDoor( floorInfo[i].door ); if ( door ) { door->Enable( false ); } } } /* ================ idElevator::EnableProperDoors ================ */ void idElevator::EnableProperDoors( void ) { idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); if ( door ) { door->Enable( true ); } for ( int i = 0; i < floorInfo.Num(); i++ ) { if ( floorInfo[i].floor == currentFloor ) { door = GetDoor( floorInfo[i].door ); if ( door ) { door->Enable( true ); break; } } } } /* =============================================================================== idMover_Binary Doors, plats, and buttons are all binary (two position) movers Pos1 is "at rest", pos2 is "activated" =============================================================================== */ const idEventDef EV_Mover_ReturnToPos1( "", NULL ); const idEventDef EV_Mover_MatchTeam( "", "dd" ); const idEventDef EV_Mover_Enable( "enable", NULL ); const idEventDef EV_Mover_Disable( "disable", NULL ); CLASS_DECLARATION( idEntity, idMover_Binary ) EVENT( EV_FindGuiTargets, idMover_Binary::Event_FindGuiTargets ) EVENT( EV_Thread_SetCallback, idMover_Binary::Event_SetCallback ) EVENT( EV_Mover_ReturnToPos1, idMover_Binary::Event_ReturnToPos1 ) EVENT( EV_Activate, idMover_Binary::Event_Use_BinaryMover ) EVENT( EV_ReachedPos, idMover_Binary::Event_Reached_BinaryMover ) EVENT( EV_Mover_MatchTeam, idMover_Binary::Event_MatchActivateTeam ) EVENT( EV_Mover_Enable, idMover_Binary::Event_Enable ) EVENT( EV_Mover_Disable, idMover_Binary::Event_Disable ) EVENT( EV_Mover_OpenPortal, idMover_Binary::Event_OpenPortal ) EVENT( EV_Mover_ClosePortal, idMover_Binary::Event_ClosePortal ) EVENT( EV_Mover_InitGuiTargets, idMover_Binary::Event_InitGuiTargets ) END_CLASS /* ================ idMover_Binary::idMover_Binary() ================ */ idMover_Binary::idMover_Binary() { pos1.Zero(); pos2.Zero(); moverState = MOVER_POS1; moveMaster = NULL; activateChain = NULL; soundPos1 = 0; sound1to2 = 0; sound2to1 = 0; soundPos2 = 0; soundLoop = 0; wait = 0.0f; damage = 0.0f; duration = 0; accelTime = 0; decelTime = 0; activatedBy = this; stateStartTime = 0; team.Clear(); enabled = false; move_thread = 0; updateStatus = 0; areaPortal = 0; blocked = false; fl.networkSync = true; } /* ================ idMover_Binary::~idMover_Binary ================ */ idMover_Binary::~idMover_Binary() { idMover_Binary *mover; // if this is the mover master if ( this == moveMaster ) { // make the next mover in the chain the move master for ( mover = moveMaster; mover; mover = mover->activateChain ) { mover->moveMaster = this->activateChain; } } else { // remove mover from the activate chain for ( mover = moveMaster; mover; mover = mover->activateChain ) { if ( mover->activateChain == this ) { mover->activateChain = this->activateChain; break; } } } } /* ================ idMover_Binary::Save ================ */ void idMover_Binary::Save( idSaveGame *savefile ) const { int i; savefile->WriteVec3( pos1 ); savefile->WriteVec3( pos2 ); savefile->WriteInt( (moverState_t)moverState ); savefile->WriteObject( moveMaster ); savefile->WriteObject( activateChain ); savefile->WriteInt( soundPos1 ); savefile->WriteInt( sound1to2 ); savefile->WriteInt( sound2to1 ); savefile->WriteInt( soundPos2 ); savefile->WriteInt( soundLoop ); savefile->WriteFloat( wait ); savefile->WriteFloat( damage ); savefile->WriteInt( duration ); savefile->WriteInt( accelTime ); savefile->WriteInt( decelTime ); activatedBy.Save( savefile ); savefile->WriteInt( stateStartTime ); savefile->WriteString( team ); savefile->WriteBool( enabled ); savefile->WriteInt( move_thread ); savefile->WriteInt( updateStatus ); savefile->WriteInt( buddies.Num() ); for ( i = 0; i < buddies.Num(); i++ ) { savefile->WriteString( buddies[ i ] ); } savefile->WriteStaticObject( physicsObj ); savefile->WriteInt( areaPortal ); if ( areaPortal ) { savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); } savefile->WriteBool( blocked ); savefile->WriteInt( guiTargets.Num() ); for( i = 0; i < guiTargets.Num(); i++ ) { guiTargets[ i ].Save( savefile ); } } /* ================ idMover_Binary::Restore ================ */ void idMover_Binary::Restore( idRestoreGame *savefile ) { int i, num, portalState; idStr temp; savefile->ReadVec3( pos1 ); savefile->ReadVec3( pos2 ); savefile->ReadInt( (int &)moverState ); savefile->ReadObject( reinterpret_cast( moveMaster ) ); savefile->ReadObject( reinterpret_cast( activateChain ) ); savefile->ReadInt( soundPos1 ); savefile->ReadInt( sound1to2 ); savefile->ReadInt( sound2to1 ); savefile->ReadInt( soundPos2 ); savefile->ReadInt( soundLoop ); savefile->ReadFloat( wait ); savefile->ReadFloat( damage ); savefile->ReadInt( duration ); savefile->ReadInt( accelTime ); savefile->ReadInt( decelTime ); activatedBy.Restore( savefile ); savefile->ReadInt( stateStartTime ); savefile->ReadString( team ); savefile->ReadBool( enabled ); savefile->ReadInt( move_thread ); savefile->ReadInt( updateStatus ); savefile->ReadInt( num ); for ( i = 0; i < num; i++ ) { savefile->ReadString( temp ); buddies.Append( temp ); } savefile->ReadStaticObject( physicsObj ); RestorePhysics( &physicsObj ); savefile->ReadInt( areaPortal ); if ( areaPortal ) { savefile->ReadInt( portalState ); gameLocal.SetPortalState( areaPortal, portalState ); } savefile->ReadBool( blocked ); guiTargets.Clear(); savefile->ReadInt( num ); guiTargets.SetNum( num ); for( i = 0; i < num; i++ ) { guiTargets[ i ].Restore( savefile ); } } /* ================ idMover_Binary::Spawn Base class for all movers. "wait" wait before returning (3 default, -1 = never return) "speed" movement speed ================ */ void idMover_Binary::Spawn( void ) { idEntity *ent; const char *temp; move_thread = 0; enabled = true; areaPortal = 0; activateChain = NULL; spawnArgs.GetFloat( "wait", "0", wait ); spawnArgs.GetInt( "updateStatus", "0", updateStatus ); const idKeyValue *kv = spawnArgs.MatchPrefix( "buddy", NULL ); while( kv ) { buddies.Append( kv->GetValue() ); kv = spawnArgs.MatchPrefix( "buddy", kv ); } spawnArgs.GetString( "team", "", &temp ); team = temp; if ( !team.Length() ) { ent = this; } else { // find the first entity spawned on this team (which could be us) for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { if ( ent->IsType( idMover_Binary::Type ) && !idStr::Icmp( static_cast(ent)->team.c_str(), temp ) ) { break; } } if ( !ent ) { ent = this; } } moveMaster = static_cast(ent); // create a physics team for the binary mover parts if ( ent != this ) { JoinTeam( ent ); } physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); physicsObj.SetAxis( GetPhysics()->GetAxis() ); physicsObj.SetClipMask( MASK_SOLID ); if ( !spawnArgs.GetBool( "solid", "1" ) ) { physicsObj.SetContents( 0 ); } if ( !spawnArgs.GetBool( "nopush" ) ) { physicsObj.SetPusher( 0 ); } physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); SetPhysics( &physicsObj ); if ( moveMaster != this ) { JoinActivateTeam( moveMaster ); } idBounds soundOrigin; idMover_Binary *slave; soundOrigin.Clear(); for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { soundOrigin += slave->GetPhysics()->GetAbsBounds(); } moveMaster->refSound.origin = soundOrigin.GetCenter(); if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { PostEventMS( &EV_FindGuiTargets, 0 ); } else { // not during spawn, so it's ok to get the targets FindGuiTargets(); } } } /* =============== idMover_Binary::GetMovedir The editor only specifies a single value for angles (yaw), but we have special constants to generate an up or down direction. Angles will be cleared, because it is being used to represent a direction instead of an orientation. =============== */ void idMover_Binary::GetMovedir( float angle, idVec3 &movedir ) { if ( angle == -1 ) { movedir.Set( 0, 0, 1 ); } else if ( angle == -2 ) { movedir.Set( 0, 0, -1 ); } else { movedir = idAngles( 0, angle, 0 ).ToForward(); } } /* ================ idMover_Binary::Event_SetCallback ================ */ void idMover_Binary::Event_SetCallback( void ) { if ( ( moverState == MOVER_1TO2 ) || ( moverState == MOVER_2TO1 ) ) { move_thread = idThread::CurrentThreadNum(); idThread::ReturnInt( true ); } else { idThread::ReturnInt( false ); } } /* =============== idMover_Binary::UpdateMoverSound =============== */ void idMover_Binary::UpdateMoverSound( moverState_t state ) { if ( moveMaster == this ) { switch( state ) { case MOVER_POS1: break; case MOVER_POS2: break; case MOVER_1TO2: StartSound( "snd_open", SND_CHANNEL_ANY, 0, false, NULL ); break; case MOVER_2TO1: StartSound( "snd_close", SND_CHANNEL_ANY, 0, false, NULL ); break; } } } /* =============== idMover_Binary::SetMoverState =============== */ void idMover_Binary::SetMoverState( moverState_t newstate, int time ) { idVec3 delta; moverState = newstate; move_thread = 0; UpdateMoverSound( newstate ); stateStartTime = time; switch( moverState ) { case MOVER_POS1: { Signal( SIG_MOVER_POS1 ); physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos1, vec3_origin, vec3_origin ); break; } case MOVER_POS2: { Signal( SIG_MOVER_POS2 ); physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos2, vec3_origin, vec3_origin ); break; } case MOVER_1TO2: { Signal( SIG_MOVER_1TO2 ); physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos1, ( pos2 - pos1 ) * 1000.0f / duration, vec3_origin ); if ( accelTime != 0 || decelTime != 0 ) { physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos1, pos2 ); } else { physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); } break; } case MOVER_2TO1: { Signal( SIG_MOVER_2TO1 ); physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos2, ( pos1 - pos2 ) * 1000.0f / duration, vec3_origin ); if ( accelTime != 0 || decelTime != 0 ) { physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos2, pos1 ); } else { physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); } break; } } } /* ================ idMover_Binary::MatchActivateTeam All entities in a mover team will move from pos1 to pos2 in the same amount of time ================ */ void idMover_Binary::MatchActivateTeam( moverState_t newstate, int time ) { idMover_Binary *slave; for ( slave = this; slave != NULL; slave = slave->activateChain ) { slave->SetMoverState( newstate, time ); } } /* ================ idMover_Binary::Enable ================ */ void idMover_Binary::Enable( bool b ) { enabled = b; } /* ================ idMover_Binary::Event_MatchActivateTeam ================ */ void idMover_Binary::Event_MatchActivateTeam( moverState_t newstate, int time ) { MatchActivateTeam( newstate, time ); } /* ================ idMover_Binary::BindTeam All entities in a mover team will be bound ================ */ void idMover_Binary::BindTeam( idEntity *bindTo ) { idMover_Binary *slave; for ( slave = this; slave != NULL; slave = slave->activateChain ) { slave->Bind( bindTo, true ); } } /* ================ idMover_Binary::JoinActivateTeam Set all entities in a mover team to be enabled ================ */ void idMover_Binary::JoinActivateTeam( idMover_Binary *master ) { this->activateChain = master->activateChain; master->activateChain = this; } /* ================ idMover_Binary::Event_Enable Set all entities in a mover team to be enabled ================ */ void idMover_Binary::Event_Enable( void ) { idMover_Binary *slave; for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { slave->Enable( false ); } } /* ================ idMover_Binary::Event_Disable Set all entities in a mover team to be disabled ================ */ void idMover_Binary::Event_Disable( void ) { idMover_Binary *slave; for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { slave->Enable( false ); } } /* ================ idMover_Binary::Event_OpenPortal Sets the portal associtated with this mover to be open ================ */ void idMover_Binary::Event_OpenPortal( void ) { idMover_Binary *slave; for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { if ( slave->areaPortal ) { slave->SetPortalState( true ); } } } /* ================ idMover_Binary::Event_ClosePortal Sets the portal associtated with this mover to be closed ================ */ void idMover_Binary::Event_ClosePortal( void ) { idMover_Binary *slave; for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { if ( !slave->IsHidden() ) { if ( slave->areaPortal ) { slave->SetPortalState( false ); } } } } /* ================ idMover_Binary::Event_ReturnToPos1 ================ */ void idMover_Binary::Event_ReturnToPos1( void ) { MatchActivateTeam( MOVER_2TO1, gameLocal.time ); } /* ================ idMover_Binary::Event_Reached_BinaryMover ================ */ void idMover_Binary::Event_Reached_BinaryMover( void ) { if ( moverState == MOVER_1TO2 ) { // reached pos2 idThread::ObjectMoveDone( move_thread, this ); move_thread = 0; if ( moveMaster == this ) { StartSound( "snd_opened", SND_CHANNEL_ANY, 0, false, NULL ); } SetMoverState( MOVER_POS2, gameLocal.time ); SetGuiStates( guiBinaryMoverStates[MOVER_POS2] ); UpdateBuddies( 1 ); if ( enabled && wait >= 0 && !spawnArgs.GetBool( "toggle" ) ) { // return to pos1 after a delay PostEventSec( &EV_Mover_ReturnToPos1, wait ); } // fire targets ActivateTargets( moveMaster->GetActivator() ); SetBlocked(false); } else if ( moverState == MOVER_2TO1 ) { // reached pos1 idThread::ObjectMoveDone( move_thread, this ); move_thread = 0; SetMoverState( MOVER_POS1, gameLocal.time ); SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); UpdateBuddies( 0 ); // close areaportals if ( moveMaster == this ) { ProcessEvent( &EV_Mover_ClosePortal ); } if ( enabled && wait >= 0 && spawnArgs.GetBool( "continuous" ) ) { PostEventSec( &EV_Activate, wait, this ); } SetBlocked(false); } else { gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" ); } } /* ================ idMover_Binary::GotoPosition1 ================ */ void idMover_Binary::GotoPosition1( void ) { idMover_Binary *slave; int partial; // only the master should control this if ( moveMaster != this ) { moveMaster->GotoPosition1(); return; } SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); if ( ( moverState == MOVER_POS1 ) || ( moverState == MOVER_2TO1 ) ) { // already there, or on the way return; } if ( moverState == MOVER_POS2 ) { for ( slave = this; slave != NULL; slave = slave->activateChain ) { slave->CancelEvents( &EV_Mover_ReturnToPos1 ); } if ( !spawnArgs.GetBool( "toggle" ) ) { ProcessEvent( &EV_Mover_ReturnToPos1 ); } return; } // only partway up before reversing if ( moverState == MOVER_1TO2 ) { // use the physics times because this might be executed during the physics simulation partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); assert( partial >= 0 ); if ( partial < 0 ) { partial = 0; } MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial ); // if already at at position 1 (partial == duration) execute the reached event if ( partial >= duration ) { Event_Reached_BinaryMover(); } } } /* ================ idMover_Binary::GotoPosition2 ================ */ void idMover_Binary::GotoPosition2( void ) { int partial; // only the master should control this if ( moveMaster != this ) { moveMaster->GotoPosition2(); return; } SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); if ( ( moverState == MOVER_POS2 ) || ( moverState == MOVER_1TO2 ) ) { // already there, or on the way return; } if ( moverState == MOVER_POS1 ) { MatchActivateTeam( MOVER_1TO2, gameLocal.time ); // open areaportal ProcessEvent( &EV_Mover_OpenPortal ); return; } // only partway up before reversing if ( moverState == MOVER_2TO1 ) { // use the physics times because this might be executed during the physics simulation partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); assert( partial >= 0 ); if ( partial < 0 ) { partial = 0; } MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial ); // if already at at position 2 (partial == duration) execute the reached event if ( partial >= duration ) { Event_Reached_BinaryMover(); } } } /* ================ idMover_Binary::UpdateBuddies ================ */ void idMover_Binary::UpdateBuddies( int val ) { int i, c; if ( updateStatus == 2 ) { c = buddies.Num(); for ( i = 0; i < c; i++ ) { idEntity *buddy = gameLocal.FindEntity( buddies[i] ); if ( buddy ) { buddy->SetShaderParm( SHADERPARM_MODE, val ); buddy->UpdateVisuals(); } } } } /* ================ idMover_Binary::SetGuiStates ================ */ void idMover_Binary::SetGuiStates( const char *state ) { if ( guiTargets.Num() ) { SetGuiState( "movestate", state ); } idMover_Binary *mb = activateChain; while( mb ) { if ( mb->guiTargets.Num() ) { mb->SetGuiState( "movestate", state ); } mb = mb->activateChain; } } /* ================ idMover_Binary::Use_BinaryMover ================ */ void idMover_Binary::Use_BinaryMover( idEntity *activator ) { // only the master should be used if ( moveMaster != this ) { moveMaster->Use_BinaryMover( activator ); return; } if ( !enabled ) { return; } activatedBy = activator; if ( moverState == MOVER_POS1 ) { // FIXME: start moving USERCMD_MSEC later, because if this was player // triggered, gameLocal.time hasn't been advanced yet MatchActivateTeam( MOVER_1TO2, gameLocal.time + USERCMD_MSEC ); SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); // open areaportal ProcessEvent( &EV_Mover_OpenPortal ); return; } // if all the way up, just delay before coming down if ( moverState == MOVER_POS2 ) { idMover_Binary *slave; if ( wait == -1 ) { return; } SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); for ( slave = this; slave != NULL; slave = slave->activateChain ) { slave->CancelEvents( &EV_Mover_ReturnToPos1 ); slave->PostEventSec( &EV_Mover_ReturnToPos1, spawnArgs.GetBool( "toggle" ) ? 0 : wait ); } return; } // only partway down before reversing if ( moverState == MOVER_2TO1 ) { GotoPosition2(); return; } // only partway up before reversing if ( moverState == MOVER_1TO2 ) { GotoPosition1(); return; } } /* ================ idMover_Binary::Event_Use_BinaryMover ================ */ void idMover_Binary::Event_Use_BinaryMover( idEntity *activator ) { Use_BinaryMover( activator ); } /* ================ idMover_Binary::PreBind ================ */ void idMover_Binary::PreBind( void ) { pos1 = GetWorldCoordinates( pos1 ); pos2 = GetWorldCoordinates( pos2 ); } /* ================ idMover_Binary::PostBind ================ */ void idMover_Binary::PostBind( void ) { pos1 = GetLocalCoordinates( pos1 ); pos2 = GetLocalCoordinates( pos2 ); } /* ================ idMover_Binary::FindGuiTargets ================ */ void idMover_Binary::FindGuiTargets( void ) { gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); } /* ============================== idMover_Binary::SetGuiState key/val will be set to any renderEntity->gui's on the list ============================== */ void idMover_Binary::SetGuiState( const char *key, const char *val ) const { int i; for( i = 0; i < guiTargets.Num(); i++ ) { idEntity *ent = guiTargets[ i ].GetEntity(); if ( ent ) { for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); } } ent->UpdateVisuals(); } } } /* ================ idMover_Binary::Event_InitGuiTargets ================ */ void idMover_Binary::Event_FindGuiTargets( void ) { FindGuiTargets(); } /* ================ idMover_Binary::Event_InitGuiTargets ================ */ void idMover_Binary::Event_InitGuiTargets( void ) { if ( guiTargets.Num() ) { SetGuiState( "movestate", guiBinaryMoverStates[MOVER_POS1] ); } } /* ================ idMover_Binary::InitSpeed pos1, pos2, and speed are passed in so the movement delta can be calculated ================ */ void idMover_Binary::InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ) { idVec3 move; float distance; float speed; pos1 = mpos1; pos2 = mpos2; accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); speed = mspeed ? mspeed : 100; // calculate time to reach second position from speed move = pos2 - pos1; distance = move.Length(); duration = idPhysics::SnapTimeToPhysicsFrame( distance * 1000 / speed ); if ( duration <= 0 ) { duration = 1; } moverState = MOVER_POS1; physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); SetOrigin( pos1 ); PostEventMS( &EV_Mover_InitGuiTargets, 0 ); } /* ================ idMover_Binary::InitTime pos1, pos2, and time are passed in so the movement delta can be calculated ================ */ void idMover_Binary::InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ) { pos1 = mpos1; pos2 = mpos2; accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mtime ) ); if ( duration <= 0 ) { duration = 1; } moverState = MOVER_POS1; physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); SetOrigin( pos1 ); PostEventMS( &EV_Mover_InitGuiTargets, 0 ); } /* ================ idMover_Binary::SetBlocked ================ */ void idMover_Binary::SetBlocked( bool b ) { for ( idMover_Binary *slave = moveMaster; slave != NULL; slave = slave->activateChain ) { slave->blocked = b; if ( b ) { const idKeyValue *kv = slave->spawnArgs.MatchPrefix( "triggerBlocked" ); while( kv ) { idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); if ( ent ) { ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); } kv = slave->spawnArgs.MatchPrefix( "triggerBlocked", kv ); } } } } /* ================ idMover_Binary::IsBlocked ================ */ bool idMover_Binary::IsBlocked( void ) { return blocked; } /* ================ idMover_Binary::GetActivator ================ */ idEntity *idMover_Binary::GetActivator( void ) const { return activatedBy.GetEntity(); } /* ================ idMover_Binary::WriteToSnapshot ================ */ void idMover_Binary::WriteToSnapshot( idBitMsgDelta &msg ) const { physicsObj.WriteToSnapshot( msg ); msg.WriteBits( moverState, 3 ); WriteBindToSnapshot( msg ); } /* ================ idMover_Binary::ReadFromSnapshot ================ */ void idMover_Binary::ReadFromSnapshot( const idBitMsgDelta &msg ) { moverState_t oldMoverState = moverState; physicsObj.ReadFromSnapshot( msg ); moverState = (moverState_t) msg.ReadBits( 3 ); ReadBindFromSnapshot( msg ); if ( msg.HasChanged() ) { if ( moverState != oldMoverState ) { UpdateMoverSound( moverState ); } UpdateVisuals(); } } /* ================ idMover_Binary::SetPortalState ================ */ void idMover_Binary::SetPortalState( bool open ) { assert( areaPortal ); gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); } /* =============================================================================== idDoor A use can be triggered either by a touch function, by being shot, or by being targeted by another entity. =============================================================================== */ const idEventDef EV_Door_StartOpen( "", NULL ); const idEventDef EV_Door_SpawnDoorTrigger( "", NULL ); const idEventDef EV_Door_SpawnSoundTrigger( "", NULL ); const idEventDef EV_Door_Open( "open", NULL ); const idEventDef EV_Door_Close( "close", NULL ); const idEventDef EV_Door_Lock( "lock", "d" ); const idEventDef EV_Door_IsOpen( "isOpen", NULL, 'f' ); const idEventDef EV_Door_IsLocked( "isLocked", NULL, 'f' ); CLASS_DECLARATION( idMover_Binary, idDoor ) EVENT( EV_TeamBlocked, idDoor::Event_TeamBlocked ) EVENT( EV_PartBlocked, idDoor::Event_PartBlocked ) EVENT( EV_Touch, idDoor::Event_Touch ) EVENT( EV_Activate, idDoor::Event_Activate ) EVENT( EV_Door_StartOpen, idDoor::Event_StartOpen ) EVENT( EV_Door_SpawnDoorTrigger, idDoor::Event_SpawnDoorTrigger ) EVENT( EV_Door_SpawnSoundTrigger, idDoor::Event_SpawnSoundTrigger ) EVENT( EV_Door_Open, idDoor::Event_Open ) EVENT( EV_Door_Close, idDoor::Event_Close ) EVENT( EV_Door_Lock, idDoor::Event_Lock ) EVENT( EV_Door_IsOpen, idDoor::Event_IsOpen ) EVENT( EV_Door_IsLocked, idDoor::Event_Locked ) EVENT( EV_ReachedPos, idDoor::Event_Reached_BinaryMover ) EVENT( EV_SpectatorTouch, idDoor::Event_SpectatorTouch ) EVENT( EV_Mover_OpenPortal, idDoor::Event_OpenPortal ) EVENT( EV_Mover_ClosePortal, idDoor::Event_ClosePortal ) END_CLASS /* ================ idDoor::idDoor ================ */ idDoor::idDoor( void ) { triggersize = 1.0f; crusher = false; noTouch = false; aas_area_closed = false; buddyStr.Clear(); trigger = NULL; sndTrigger = NULL; nextSndTriggerTime = 0; localTriggerOrigin.Zero(); localTriggerAxis.Identity(); requires.Clear(); removeItem = 0; syncLock.Clear(); companionDoor = NULL; normalAxisIndex = 0; } /* ================ idDoor::~idDoor ================ */ idDoor::~idDoor( void ) { if ( trigger ) { delete trigger; } if ( sndTrigger ) { delete sndTrigger; } } /* ================ idDoor::Save ================ */ void idDoor::Save( idSaveGame *savefile ) const { savefile->WriteFloat( triggersize ); savefile->WriteBool( crusher ); savefile->WriteBool( noTouch ); savefile->WriteBool( aas_area_closed ); savefile->WriteString( buddyStr ); savefile->WriteInt( nextSndTriggerTime ); savefile->WriteVec3( localTriggerOrigin ); savefile->WriteMat3( localTriggerAxis ); savefile->WriteString( requires ); savefile->WriteInt( removeItem ); savefile->WriteString( syncLock ); savefile->WriteInt( normalAxisIndex ); savefile->WriteClipModel( trigger ); savefile->WriteClipModel( sndTrigger ); savefile->WriteObject( companionDoor ); } /* ================ idDoor::Restore ================ */ void idDoor::Restore( idRestoreGame *savefile ) { savefile->ReadFloat( triggersize ); savefile->ReadBool( crusher ); savefile->ReadBool( noTouch ); savefile->ReadBool( aas_area_closed ); SetAASAreaState( aas_area_closed ); savefile->ReadString( buddyStr ); savefile->ReadInt( nextSndTriggerTime ); savefile->ReadVec3( localTriggerOrigin ); savefile->ReadMat3( localTriggerAxis ); savefile->ReadString( requires ); savefile->ReadInt( removeItem ); savefile->ReadString( syncLock ); savefile->ReadInt( normalAxisIndex ); savefile->ReadClipModel( trigger ); savefile->ReadClipModel( sndTrigger ); savefile->ReadObject( reinterpret_cast( companionDoor ) ); } /* ================ idDoor::Spawn ================ */ void idDoor::Spawn( void ) { idVec3 abs_movedir; float distance; idVec3 size; idVec3 movedir; float dir; float lip; bool start_open; float time; float speed; // get the direction to move if ( !spawnArgs.GetFloat( "movedir", "0", dir ) ) { // no movedir, so angle defines movement direction and not orientation, // a la oldschool Quake SetAngles( ang_zero ); spawnArgs.GetFloat( "angle", "0", dir ); } GetMovedir( dir, movedir ); // default speed of 400 spawnArgs.GetFloat( "speed", "400", speed ); // default wait of 2 seconds spawnArgs.GetFloat( "wait", "3", wait ); // default lip of 8 units spawnArgs.GetFloat( "lip", "8", lip ); // by default no damage spawnArgs.GetFloat( "damage", "0", damage ); // trigger size spawnArgs.GetFloat( "triggersize", "120", triggersize ); spawnArgs.GetBool( "crusher", "0", crusher ); spawnArgs.GetBool( "start_open", "0", start_open ); spawnArgs.GetBool( "no_touch", "0", noTouch ); // expects syncLock to be a door that must be closed before this door will open spawnArgs.GetString( "syncLock", "", syncLock ); spawnArgs.GetString( "buddy", "", buddyStr ); spawnArgs.GetString( "requires", "", requires ); spawnArgs.GetInt( "removeItem", "0", removeItem ); // ever separate piece of a door is considered solid when other team mates push entities fl.solidForTeam = true; // first position at start pos1 = GetPhysics()->GetOrigin(); // calculate second position abs_movedir[0] = idMath::Fabs( movedir[ 0 ] ); abs_movedir[1] = idMath::Fabs( movedir[ 1 ] ); abs_movedir[2] = idMath::Fabs( movedir[ 2 ] ); size = GetPhysics()->GetAbsBounds()[1] - GetPhysics()->GetAbsBounds()[0]; distance = ( abs_movedir * size ) - lip; pos2 = pos1 + distance * movedir; // if "start_open", reverse position 1 and 2 if ( start_open ) { // post it after EV_SpawnBind PostEventMS( &EV_Door_StartOpen, 1 ); } if ( spawnArgs.GetFloat( "time", "1", time ) ) { InitTime( pos1, pos2, time, 0, 0 ); } else { InitSpeed( pos1, pos2, speed, 0, 0 ); } if ( moveMaster == this ) { if ( health ) { fl.takedamage = true; } if ( noTouch || health ) { // non touch/shoot doors PostEventMS( &EV_Mover_MatchTeam, 0, moverState, gameLocal.time ); const char *sndtemp = spawnArgs.GetString( "snd_locked" ); if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); } } else { // spawn trigger PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); } } // see if we are on an areaportal areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); if ( !start_open ) { // start closed ProcessEvent( &EV_Mover_ClosePortal ); } int locked = spawnArgs.GetInt( "locked" ); if ( locked ) { // make sure all members of the team get locked PostEventMS( &EV_Door_Lock, 0, locked ); } if ( spawnArgs.GetBool( "continuous" ) ) { PostEventSec( &EV_Activate, spawnArgs.GetFloat( "delay" ), this ); } // sounds have a habit of stuttering when portals close, so make them unoccluded refSound.parms.soundShaderFlags |= SSF_NO_OCCLUSION; companionDoor = NULL; enabled = true; blocked = false; } /* ================ idDoor::Think ================ */ void idDoor::Think( void ) { idVec3 masterOrigin; idMat3 masterAxis; idMover_Binary::Think(); if ( thinkFlags & TH_PHYSICS ) { // update trigger position if ( GetMasterPosition( masterOrigin, masterAxis ) ) { if ( trigger ) { trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); } if ( sndTrigger ) { sndTrigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); } } } } /* ================ idDoor::PreBind ================ */ void idDoor::PreBind( void ) { idMover_Binary::PreBind(); } /* ================ idDoor::PostBind ================ */ void idDoor::PostBind( void ) { idMover_Binary::PostBind(); GetLocalTriggerPosition( trigger ? trigger : sndTrigger ); } /* ================ idDoor::SetAASAreaState ================ */ void idDoor::SetAASAreaState( bool closed ) { aas_area_closed = closed; gameLocal.SetAASAreaState( physicsObj.GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL|AREACONTENTS_OBSTACLE, closed ); } /* ================ idDoor::Hide ================ */ void idDoor::Hide( void ) { idMover_Binary *slave; idMover_Binary *master; idDoor *slaveDoor; idDoor *companion; master = GetMoveMaster(); if ( this != master ) { master->Hide(); } else { for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { if ( slave->IsType( idDoor::Type ) ) { slaveDoor = static_cast( slave ); companion = slaveDoor->companionDoor; if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { companion->Hide(); } if ( slaveDoor->trigger ) { slaveDoor->trigger->Disable(); } if ( slaveDoor->sndTrigger ) { slaveDoor->sndTrigger->Disable(); } if ( slaveDoor->areaPortal ) { slaveDoor->SetPortalState( true ); } slaveDoor->SetAASAreaState( false ); } slave->GetPhysics()->GetClipModel()->Disable(); slave->idMover_Binary::Hide(); } } } /* ================ idDoor::Show ================ */ void idDoor::Show( void ) { idMover_Binary *slave; idMover_Binary *master; idDoor *slaveDoor; idDoor *companion; master = GetMoveMaster(); if ( this != master ) { master->Show(); } else { for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { if ( slave->IsType( idDoor::Type ) ) { slaveDoor = static_cast( slave ); companion = slaveDoor->companionDoor; if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { companion->Show(); } if ( slaveDoor->trigger ) { slaveDoor->trigger->Enable(); } if ( slaveDoor->sndTrigger ) { slaveDoor->sndTrigger->Enable(); } if ( slaveDoor->areaPortal && ( slaveDoor->moverState == MOVER_POS1 ) ) { slaveDoor->SetPortalState( false ); } slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); } slave->GetPhysics()->GetClipModel()->Enable(); slave->idMover_Binary::Show(); } } } /* ================ idDoor::GetLocalTriggerPosition ================ */ void idDoor::GetLocalTriggerPosition( const idClipModel *trigger ) { idVec3 origin; idMat3 axis; if ( !trigger ) { return; } GetMasterPosition( origin, axis ); localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); localTriggerAxis = trigger->GetAxis() * axis.Transpose(); } /* ================ idDoor::Use ================ */ void idDoor::Use( idEntity *other, idEntity *activator ) { if ( gameLocal.RequirementMet( activator, requires, removeItem ) ) { if ( syncLock.Length() ) { idEntity *sync = gameLocal.FindEntity( syncLock ); if ( sync && sync->IsType( idDoor::Type ) ) { if ( static_cast( sync )->IsOpen() ) { return; } } } ActivateTargets( activator ); Use_BinaryMover( activator ); } } /* ================ idDoor::Open ================ */ void idDoor::Open( void ) { GotoPosition2(); } /* ================ idDoor::Close ================ */ void idDoor::Close( void ) { GotoPosition1(); } /* ================ idDoor::Lock ================ */ void idDoor::Lock( int f ) { idMover_Binary *other; // lock all the doors on the team for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { if ( other->IsType( idDoor::Type ) ) { idDoor *door = static_cast( other ); if ( other == moveMaster ) { if ( door->sndTrigger == NULL ) { // in this case the sound trigger never got spawned const char *sndtemp = door->spawnArgs.GetString( "snd_locked" ); if ( sndtemp && *sndtemp ) { door->PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); } } if ( !f && ( door->spawnArgs.GetInt( "locked" ) != 0 ) ) { door->StartSound( "snd_unlocked", SND_CHANNEL_ANY, 0, false, NULL ); } } door->spawnArgs.SetInt( "locked", f ); if ( ( f == 0 ) || ( !IsHidden() && ( door->moverState == MOVER_POS1 ) ) ) { door->SetAASAreaState( f != 0 ); } } } if ( f ) { Close(); } } /* ================ idDoor::IsLocked ================ */ int idDoor::IsLocked( void ) { return spawnArgs.GetInt( "locked" ); } /* ================ idDoor::IsOpen ================ */ bool idDoor::IsOpen( void ) { return ( moverState != MOVER_POS1 ); } /* ================ idDoor::IsNoTouch ================ */ bool idDoor::IsNoTouch( void ) { return noTouch; } /* ====================== idDoor::CalcTriggerBounds Calcs bounds for a trigger. ====================== */ void idDoor::CalcTriggerBounds( float size, idBounds &bounds ) { idMover_Binary *other; int i; int best; // find the bounds of everything on the team bounds = GetPhysics()->GetAbsBounds(); fl.takedamage = true; for( other = activateChain; other != NULL; other = other->GetActivateChain() ) { if ( other->IsType( idDoor::Type ) ) { // find the bounds of everything on the team bounds.AddBounds( other->GetPhysics()->GetAbsBounds() ); // set all of the slaves as shootable other->fl.takedamage = true; } } // find the thinnest axis, which will be the one we expand best = 0; for ( i = 1 ; i < 3 ; i++ ) { if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { best = i; } } normalAxisIndex = best; bounds[0][ best ] -= size; bounds[1][ best ] += size; bounds[0] -= GetPhysics()->GetOrigin(); bounds[1] -= GetPhysics()->GetOrigin(); } /* ====================== idDoor::Event_StartOpen if "start_open", reverse position 1 and 2 ====================== */ void idDoor::Event_StartOpen( void ) { float time; float speed; // if "start_open", reverse position 1 and 2 pos1 = pos2; pos2 = GetPhysics()->GetOrigin(); spawnArgs.GetFloat( "speed", "400", speed ); if ( spawnArgs.GetFloat( "time", "1", time ) ) { InitTime( pos1, pos2, time, 0, 0 ); } else { InitSpeed( pos1, pos2, speed, 0, 0 ); } } /* ====================== idDoor::Event_SpawnDoorTrigger All of the parts of a door have been spawned, so create a trigger that encloses all of them. ====================== */ void idDoor::Event_SpawnDoorTrigger( void ) { idBounds bounds; idMover_Binary *other; bool toggle; if ( trigger ) { // already have a trigger, so don't spawn a new one. return; } // check if any of the doors are marked as toggled toggle = false; for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { if ( other->IsType( idDoor::Type ) && other->spawnArgs.GetBool( "toggle" ) ) { toggle = true; break; } } if ( toggle ) { // mark them all as toggled for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { if ( other->IsType( idDoor::Type ) ) { other->spawnArgs.Set( "toggle", "1" ); } } // don't spawn trigger return; } const char *sndtemp = spawnArgs.GetString( "snd_locked" ); if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); } CalcTriggerBounds( triggersize, bounds ); // create a trigger clip model trigger = new idClipModel( idTraceModel( bounds ) ); trigger->Link( gameLocal.clip, this, 255, GetPhysics()->GetOrigin(), mat3_identity ); trigger->SetContents( CONTENTS_TRIGGER ); GetLocalTriggerPosition( trigger ); MatchActivateTeam( moverState, gameLocal.time ); } /* ====================== idDoor::Event_SpawnSoundTrigger Spawn a sound trigger to activate locked sound if it exists. ====================== */ void idDoor::Event_SpawnSoundTrigger( void ) { idBounds bounds; if ( sndTrigger ) { return; } CalcTriggerBounds( triggersize * 0.5f, bounds ); // create a trigger clip model sndTrigger = new idClipModel( idTraceModel( bounds ) ); sndTrigger->Link( gameLocal.clip, this, 254, GetPhysics()->GetOrigin(), mat3_identity ); sndTrigger->SetContents( CONTENTS_TRIGGER ); GetLocalTriggerPosition( sndTrigger ); } /* ================ idDoor::Event_Reached_BinaryMover ================ */ void idDoor::Event_Reached_BinaryMover( void ) { if ( moverState == MOVER_2TO1 ) { SetBlocked( false ); const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerClosed" ); while( kv ) { idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); if ( ent ) { ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); } kv = spawnArgs.MatchPrefix( "triggerClosed", kv ); } } else if ( moverState == MOVER_1TO2 ) { const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOpened" ); while( kv ) { idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); if ( ent ) { ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); } kv = spawnArgs.MatchPrefix( "triggerOpened", kv ); } } idMover_Binary::Event_Reached_BinaryMover(); } /* ================ idDoor::Blocked_Door ================ */ void idDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { SetBlocked( true ); if ( crusher ) { return; // crushers don't reverse } // reverse direction Use_BinaryMover( moveMaster->GetActivator() ); if ( companionDoor ) { companionDoor->ProcessEvent( &EV_TeamBlocked, blockedEntity, blockingEntity ); } } /* =============== idDoor::SetCompanion =============== */ void idDoor::SetCompanion( idDoor *door ) { companionDoor = door; } /* =============== idDoor::Event_PartBlocked =============== */ void idDoor::Event_PartBlocked( idEntity *blockingEntity ) { if ( damage > 0.0f ) { blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); } } /* ================ idDoor::Event_Touch ================ */ void idDoor::Event_Touch( idEntity *other, trace_t *trace ) { idVec3 contact, translate; idVec3 planeaxis1, planeaxis2, normal; idBounds bounds; if ( !enabled ) { return; } if ( trigger && trace->c.id == trigger->GetId() ) { if ( !IsNoTouch() && !IsLocked() && GetMoverState() != MOVER_1TO2 ) { Use( this, other ); } } else if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { if ( other && other->IsType( idPlayer::Type ) && IsLocked() && gameLocal.time > nextSndTriggerTime ) { StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); nextSndTriggerTime = gameLocal.time + 10000; } } } /* ================ idDoor::Event_SpectatorTouch ================ */ void idDoor::Event_SpectatorTouch( idEntity *other, trace_t *trace ) { idVec3 contact, translate, normal; idBounds bounds; idPlayer *p; assert( other && other->IsType( idPlayer::Type ) && static_cast< idPlayer * >( other )->spectating ); p = static_cast< idPlayer * >( other ); // avoid flicker when stopping right at clip box boundaries if ( p->lastSpectateTeleport > gameLocal.time - 1000 ) { return; } if ( trigger && !IsOpen() ) { // teleport to the other side, center to the middle of the trigger brush bounds = trigger->GetAbsBounds(); contact = trace->endpos - bounds.GetCenter(); translate = bounds.GetCenter(); normal.Zero(); normal[ normalAxisIndex ] = 1.0f; if ( normal * contact > 0 ) { translate[ normalAxisIndex ] += ( bounds[ 0 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; } else { translate[ normalAxisIndex ] += ( bounds[ 1 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; } p->SetOrigin( translate ); p->lastSpectateTeleport = gameLocal.time; } } /* ================ idDoor::Event_Activate ================ */ void idDoor::Event_Activate( idEntity *activator ) { int old_lock; if ( spawnArgs.GetInt( "locked" ) ) { if ( !trigger ) { PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); } if ( buddyStr.Length() ) { idEntity *buddy = gameLocal.FindEntity( buddyStr ); if ( buddy ) { buddy->SetShaderParm( SHADERPARM_MODE, 1 ); buddy->UpdateVisuals(); } } old_lock = spawnArgs.GetInt( "locked" ); Lock( 0 ); if ( old_lock == 2 ) { return; } } if ( syncLock.Length() ) { idEntity *sync = gameLocal.FindEntity( syncLock ); if ( sync && sync->IsType( idDoor::Type ) ) { if ( static_cast( sync )->IsOpen() ) { return; } } } ActivateTargets( activator ); renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; UpdateVisuals(); Use_BinaryMover( activator ); } /* ================ idDoor::Event_Open ================ */ void idDoor::Event_Open( void ) { Open(); } /* ================ idDoor::Event_Close ================ */ void idDoor::Event_Close( void ) { Close(); } /* ================ idDoor::Event_Lock ================ */ void idDoor::Event_Lock( int f ) { Lock( f ); } /* ================ idDoor::Event_IsOpen ================ */ void idDoor::Event_IsOpen( void ) { bool state; state = IsOpen(); idThread::ReturnFloat( state ); } /* ================ idDoor::Event_Locked ================ */ void idDoor::Event_Locked( void ) { idThread::ReturnFloat( spawnArgs.GetInt("locked") ); } /* ================ idDoor::Event_OpenPortal Sets the portal associtated with this door to be open ================ */ void idDoor::Event_OpenPortal( void ) { idMover_Binary *slave; idDoor *slaveDoor; for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { if ( slave->IsType( idDoor::Type ) ) { slaveDoor = static_cast( slave ); if ( slaveDoor->areaPortal ) { slaveDoor->SetPortalState( true ); } slaveDoor->SetAASAreaState( false ); } } } /* ================ idDoor::Event_ClosePortal Sets the portal associtated with this door to be closed ================ */ void idDoor::Event_ClosePortal( void ) { idMover_Binary *slave; idDoor *slaveDoor; for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { if ( !slave->IsHidden() ) { if ( slave->IsType( idDoor::Type ) ) { slaveDoor = static_cast( slave ); if ( slaveDoor->areaPortal ) { slaveDoor->SetPortalState( false ); } slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); } } } } /* =============================================================================== idPlat =============================================================================== */ CLASS_DECLARATION( idMover_Binary, idPlat ) EVENT( EV_Touch, idPlat::Event_Touch ) EVENT( EV_TeamBlocked, idPlat::Event_TeamBlocked ) EVENT( EV_PartBlocked, idPlat::Event_PartBlocked ) END_CLASS /* =============== idPlat::idPlat =============== */ idPlat::idPlat( void ) { trigger = NULL; localTriggerOrigin.Zero(); localTriggerAxis.Identity(); } /* =============== idPlat::~idPlat =============== */ idPlat::~idPlat( void ) { if ( trigger ) { delete trigger; } } /* =============== idPlat::Save =============== */ void idPlat::Save( idSaveGame *savefile ) const { savefile->WriteClipModel( trigger ); savefile->WriteVec3( localTriggerOrigin ); savefile->WriteMat3( localTriggerAxis ); } /* =============== idPlat::Restore =============== */ void idPlat::Restore( idRestoreGame *savefile ) { savefile->ReadClipModel( trigger ); savefile->ReadVec3( localTriggerOrigin ); savefile->ReadMat3( localTriggerAxis ); } /* =============== idPlat::Spawn =============== */ void idPlat::Spawn( void ) { float lip; float height; float time; float speed; float accel; float decel; bool noTouch; spawnArgs.GetFloat( "speed", "100", speed ); spawnArgs.GetFloat( "damage", "0", damage ); spawnArgs.GetFloat( "wait", "1", wait ); spawnArgs.GetFloat( "lip", "8", lip ); spawnArgs.GetFloat( "accel_time", "0.25", accel ); spawnArgs.GetFloat( "decel_time", "0.25", decel ); // create second position if ( !spawnArgs.GetFloat( "height", "0", height ) ) { height = ( GetPhysics()->GetBounds()[1][2] - GetPhysics()->GetBounds()[0][2] ) - lip; } spawnArgs.GetBool( "no_touch", "0", noTouch ); // pos1 is the rest (bottom) position, pos2 is the top pos2 = GetPhysics()->GetOrigin(); pos1 = pos2; pos1[2] -= height; if ( spawnArgs.GetFloat( "time", "1", time ) ) { InitTime( pos1, pos2, time, accel, decel ); } else { InitSpeed( pos1, pos2, speed, accel, decel ); } SetMoverState( MOVER_POS1, gameLocal.time ); UpdateVisuals(); // spawn the trigger if one hasn't been custom made if ( !noTouch ) { // spawn trigger SpawnPlatTrigger( pos1 ); } } /* ================ idPlat::Think ================ */ void idPlat::Think( void ) { idVec3 masterOrigin; idMat3 masterAxis; idMover_Binary::Think(); if ( thinkFlags & TH_PHYSICS ) { // update trigger position if ( GetMasterPosition( masterOrigin, masterAxis ) ) { if ( trigger ) { trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); } } } } /* ================ idPlat::PreBind ================ */ void idPlat::PreBind( void ) { idMover_Binary::PreBind(); } /* ================ idPlat::PostBind ================ */ void idPlat::PostBind( void ) { idMover_Binary::PostBind(); GetLocalTriggerPosition( trigger ); } /* ================ idPlat::GetLocalTriggerPosition ================ */ void idPlat::GetLocalTriggerPosition( const idClipModel *trigger ) { idVec3 origin; idMat3 axis; if ( !trigger ) { return; } GetMasterPosition( origin, axis ); localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); localTriggerAxis = trigger->GetAxis() * axis.Transpose(); } /* ============== idPlat::SpawnPlatTrigger =============== */ void idPlat::SpawnPlatTrigger( idVec3 &pos ) { idBounds bounds; idVec3 tmin; idVec3 tmax; // the middle trigger will be a thin trigger just // above the starting position bounds = GetPhysics()->GetBounds(); tmin[0] = bounds[0][0] + 33; tmin[1] = bounds[0][1] + 33; tmin[2] = bounds[0][2]; tmax[0] = bounds[1][0] - 33; tmax[1] = bounds[1][1] - 33; tmax[2] = bounds[1][2] + 8; if ( tmax[0] <= tmin[0] ) { tmin[0] = ( bounds[0][0] + bounds[1][0] ) * 0.5f; tmax[0] = tmin[0] + 1; } if ( tmax[1] <= tmin[1] ) { tmin[1] = ( bounds[0][1] + bounds[1][1] ) * 0.5f; tmax[1] = tmin[1] + 1; } trigger = new idClipModel( idTraceModel( idBounds( tmin, tmax ) ) ); trigger->Link( gameLocal.clip, this, 255, GetPhysics()->GetOrigin(), mat3_identity ); trigger->SetContents( CONTENTS_TRIGGER ); } /* ============== idPlat::Event_Touch =============== */ void idPlat::Event_Touch( idEntity *other, trace_t *trace ) { if ( !other->IsType( idPlayer::Type ) ) { return; } if ( ( GetMoverState() == MOVER_POS1 ) && trigger && ( trace->c.id == trigger->GetId() ) && ( other->health > 0 ) ) { Use_BinaryMover( other ); } } /* ================ idPlat::Event_TeamBlocked ================ */ void idPlat::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { // reverse direction Use_BinaryMover( activatedBy.GetEntity() ); } /* =============== idPlat::Event_PartBlocked =============== */ void idPlat::Event_PartBlocked( idEntity *blockingEntity ) { if ( damage > 0.0f ) { blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); } } /* =============================================================================== idMover_Periodic =============================================================================== */ CLASS_DECLARATION( idEntity, idMover_Periodic ) EVENT( EV_TeamBlocked, idMover_Periodic::Event_TeamBlocked ) EVENT( EV_PartBlocked, idMover_Periodic::Event_PartBlocked ) END_CLASS /* =============== idMover_Periodic::idMover_Periodic =============== */ idMover_Periodic::idMover_Periodic( void ) { damage = 0.0f; fl.neverDormant = false; } /* =============== idMover_Periodic::Spawn =============== */ void idMover_Periodic::Spawn( void ) { spawnArgs.GetFloat( "damage", "0", damage ); if ( !spawnArgs.GetBool( "solid", "1" ) ) { GetPhysics()->SetContents( 0 ); } } /* =============== idMover_Periodic::Save =============== */ void idMover_Periodic::Save( idSaveGame *savefile ) const { savefile->WriteFloat( damage ); savefile->WriteStaticObject( physicsObj ); } /* =============== idMover_Periodic::Restore =============== */ void idMover_Periodic::Restore( idRestoreGame *savefile ) { savefile->ReadFloat( damage ); savefile->ReadStaticObject( physicsObj ); RestorePhysics( &physicsObj ); } /* ================ idMover_Periodic::Think ================ */ void idMover_Periodic::Think( void ) { // if we are completely closed off from the player, don't do anything at all if ( CheckDormant() ) { return; } RunPhysics(); Present(); } /* =============== idMover_Periodic::Event_TeamBlocked =============== */ void idMover_Periodic::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { } /* =============== idMover_Periodic::Event_PartBlocked =============== */ void idMover_Periodic::Event_PartBlocked( idEntity *blockingEntity ) { if ( damage > 0.0f ) { blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); } } /* ================ idMover_Periodic::WriteToSnapshot ================ */ void idMover_Periodic::WriteToSnapshot( idBitMsgDelta &msg ) const { physicsObj.WriteToSnapshot( msg ); WriteBindToSnapshot( msg ); } /* ================ idMover_Periodic::ReadFromSnapshot ================ */ void idMover_Periodic::ReadFromSnapshot( const idBitMsgDelta &msg ) { physicsObj.ReadFromSnapshot( msg ); ReadBindFromSnapshot( msg ); if ( msg.HasChanged() ) { UpdateVisuals(); } } /* =============================================================================== idRotater =============================================================================== */ CLASS_DECLARATION( idMover_Periodic, idRotater ) EVENT( EV_Activate, idRotater::Event_Activate ) END_CLASS /* =============== idRotater::idRotater =============== */ idRotater::idRotater( void ) { activatedBy = this; } /* =============== idRotater::Spawn =============== */ void idRotater::Spawn( void ) { physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); physicsObj.SetAxis( GetPhysics()->GetAxis() ); physicsObj.SetClipMask( MASK_SOLID ); if ( !spawnArgs.GetBool( "nopush" ) ) { physicsObj.SetPusher( 0 ); } physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, gameLocal.time, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); SetPhysics( &physicsObj ); if ( spawnArgs.GetBool( "start_on" ) ) { ProcessEvent( &EV_Activate, this ); } } /* =============== idRotater::Save =============== */ void idRotater::Save( idSaveGame *savefile ) const { activatedBy.Save( savefile ); } /* =============== idRotater::Restore =============== */ void idRotater::Restore( idRestoreGame *savefile ) { activatedBy.Restore( savefile ); } /* =============== idRotater::Event_Activate =============== */ void idRotater::Event_Activate( idEntity *activator ) { float speed; bool x_axis; bool y_axis; idAngles delta; activatedBy = activator; delta.Zero(); if ( !spawnArgs.GetBool( "rotate" ) ) { spawnArgs.Set( "rotate", "1" ); spawnArgs.GetFloat( "speed", "100", speed ); spawnArgs.GetBool( "x_axis", "0", x_axis ); spawnArgs.GetBool( "y_axis", "0", y_axis ); // set the axis of rotation if ( x_axis ) { delta[2] = speed; } else if ( y_axis ) { delta[0] = speed; } else { delta[1] = speed; } } else { spawnArgs.Set( "rotate", "0" ); } physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero ); } /* =============================================================================== idBobber =============================================================================== */ CLASS_DECLARATION( idMover_Periodic, idBobber ) END_CLASS /* =============== idBobber::idBobber =============== */ idBobber::idBobber( void ) { } /* =============== idBobber::Spawn =============== */ void idBobber::Spawn( void ) { float speed; float height; float phase; bool x_axis; bool y_axis; idVec3 delta; spawnArgs.GetFloat( "speed", "4", speed ); spawnArgs.GetFloat( "height", "32", height ); spawnArgs.GetFloat( "phase", "0", phase ); spawnArgs.GetBool( "x_axis", "0", x_axis ); spawnArgs.GetBool( "y_axis", "0", y_axis ); // set the axis of bobbing delta = vec3_origin; if ( x_axis ) { delta[ 0 ] = height; } else if ( y_axis ) { delta[ 1 ] = height; } else { delta[ 2 ] = height; } physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); physicsObj.SetAxis( GetPhysics()->GetAxis() ); physicsObj.SetClipMask( MASK_SOLID ); if ( !spawnArgs.GetBool( "nopush" ) ) { physicsObj.SetPusher( 0 ); } physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, speed * 500, GetPhysics()->GetOrigin(), delta * 2.0f, vec3_origin ); SetPhysics( &physicsObj ); } /* =============================================================================== idPendulum =============================================================================== */ CLASS_DECLARATION( idMover_Periodic, idPendulum ) END_CLASS /* =============== idPendulum::idPendulum =============== */ idPendulum::idPendulum( void ) { } /* =============== idPendulum::Spawn =============== */ void idPendulum::Spawn( void ) { float speed; float freq; float length; float phase; spawnArgs.GetFloat( "speed", "30", speed ); spawnArgs.GetFloat( "phase", "0", phase ); if ( spawnArgs.GetFloat( "freq", "", freq ) ) { if ( freq <= 0.0f ) { gameLocal.Error( "Invalid frequency on entity '%s'", GetName() ); } } else { // find pendulum length length = idMath::Fabs( GetPhysics()->GetBounds()[0][2] ); if ( length < 8 ) { length = 8; } freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_gravity.GetFloat() / ( 3 * length ) ); } physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); physicsObj.SetAxis( GetPhysics()->GetAxis() ); physicsObj.SetClipMask( MASK_SOLID ); if ( !spawnArgs.GetBool( "nopush" ) ) { physicsObj.SetPusher( 0 ); } physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, 500/freq, GetPhysics()->GetAxis().ToAngles(), idAngles( 0, 0, speed * 2.0f ), ang_zero ); SetPhysics( &physicsObj ); } /* =============================================================================== idBobber =============================================================================== */ CLASS_DECLARATION( idMover_Periodic, idRiser ) EVENT( EV_Activate, idRiser::Event_Activate ) END_CLASS /* =============== idRiser::idRiser =============== */ idRiser::idRiser( void ) { } /* =============== idRiser::Spawn =============== */ void idRiser::Spawn( void ) { physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); physicsObj.SetAxis( GetPhysics()->GetAxis() ); physicsObj.SetClipMask( MASK_SOLID ); if ( !spawnArgs.GetBool( "solid", "1" ) ) { physicsObj.SetContents( 0 ); } if ( !spawnArgs.GetBool( "nopush" ) ) { physicsObj.SetPusher( 0 ); } physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); SetPhysics( &physicsObj ); } /* ================ idRiser::Event_Activate ================ */ void idRiser::Event_Activate( idEntity *activator ) { if ( !IsHidden() && spawnArgs.GetBool("hide") ) { Hide(); } else { Show(); float time; float height; idVec3 delta; spawnArgs.GetFloat( "time", "4", time ); spawnArgs.GetFloat( "height", "32", height ); delta = vec3_origin; delta[ 2 ] = height; physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, time * 1000, physicsObj.GetOrigin(), delta, vec3_origin ); } }