// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE ) #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #include "Door.h" #include "../Player.h" #include "../script/Script_Helper.h" #include "../script/Script_ScriptObject.h" #include "../ContentMask.h" idList< doorSpawnInfo_t* > idDoor::s_doorInfo; /* =============================================================================== sdDoorPhysicsNetworkData =============================================================================== */ /* ================ sdDoorPhysicsNetworkData::MakeDefault ================ */ void sdDoorPhysicsNetworkData::MakeDefault( void ) { currentPos = 0.f; closing = true; } /* ================ sdDoorPhysicsNetworkData::Write ================ */ void sdDoorPhysicsNetworkData::Write( idFile* file ) const { file->WriteFloat( currentPos ); file->WriteBool( closing ); } /* ================ sdDoorPhysicsNetworkData::Read ================ */ void sdDoorPhysicsNetworkData::Read( idFile* file ) { file->ReadFloat( currentPos ); file->ReadBool( closing ); } /* =============================================================================== sdPhysics_Door =============================================================================== */ /* ================ sdPhysics_Door::sdPhysics_Door ================ */ sdPhysics_Door::sdPhysics_Door( void ) { currentPos = 0.f; destPos = 0.f; speed = 0.25f; } /* ================ sdPhysics_Door::~sdPhysics_Door ================ */ sdPhysics_Door::~sdPhysics_Door( void ) { for ( int i = 0; i < bodies.Num(); i++ ) { gameLocal.clip.DeleteClipModel( bodies[ i ].clipModel ); } } /* ================ sdPhysics_Door::Open ================ */ bool sdPhysics_Door::Open( void ) { if ( destPos == 1.f ) { return false; } destPos = 1.f; self->BecomeActive( TH_PHYSICS ); self->StartSound( "snd_open", SND_DOOR, 0, NULL ); return true; } /* ================ sdPhysics_Door::Close ================ */ bool sdPhysics_Door::Close( void ) { if ( destPos == 0.f ) { return false; } destPos = 0.f; self->BecomeActive( TH_PHYSICS ); self->StartSound( "snd_close", SND_DOOR, 0, NULL ); return true; } /* ================ sdPhysics_Door::SetBodyPositions ================ */ void sdPhysics_Door::SetBodyProperties( int id, const idVec3& _pos1, const idVec3& _pos2, const idMat3& _axes, bool pusher ) { assert( id >= 0 && id < bodies.Num() ); if ( id < 0 || id >= bodies.Num() ) { return; } bodies[ id ].pos1 = _pos1; bodies[ id ].pos2 = _pos2; bodies[ id ].axes = _axes; bodies[ id ].pusher = pusher; UpdateBodyPosition( id ); } /* ================ sdPhysics_Door::GetNumClipModels ================ */ int sdPhysics_Door::GetNumClipModels( void ) const { return bodies.Num(); } /* ================ sdPhysics_Door::GetOrigin ================ */ const idVec3& sdPhysics_Door::GetOrigin( int id ) const { assert( id >= 0 && id < bodies.Num() ); if ( id < 0 || id >= bodies.Num() ) { return vec3_origin; } return bodies[ id ].currentOrigin; } /* ================ sdPhysics_Door::Evaluate ================ */ bool sdPhysics_Door::Evaluate( int timeStepMSec, int endTimeMSec ) { if ( currentPos == destPos ) { return false; } float oldPos = currentPos; float diff = MS2SEC( timeStepMSec ) * speed; float newPos; if ( currentPos < destPos ) { newPos = currentPos + diff; if ( newPos > destPos ) { newPos = destPos; } } else { newPos = currentPos - diff; if ( newPos < destPos ) { newPos = destPos; } } float distance = newPos - oldPos; bool collision = false; self->OnMoveStarted(); trace_t tr; int collideEntityNum = 0; for ( int i = 0; i < bodies.Num(); i++ ) { body_t& doorBody = bodies[ i ]; idClipModel* cm = bodies[ i ].clipModel; if ( !doorBody.pusher ) { // not a pusher, so just try moving in that direction and stop at the end position idVec3 end = Lerp( bodies[ i ].pos1, bodies[ i ].pos2, newPos ); if ( gameLocal.clip.TranslationEntities( CLIP_DEBUG_PARMS tr, cm->GetOrigin(), end, cm, cm->GetAxis(), bodies[ i ].clipMask, self ) ) { distance *= tr.fraction; newPos = oldPos + distance; collision = true; collideEntityNum = tr.c.entityNum; } } else { // pusher, so push through the move idVec3 oldOrigin = bodies[ i ].currentOrigin; idVec3 newOrigin = Lerp( bodies[ i ].pos1, bodies[ i ].pos2, newPos ); idMat3 axis = GetAxis( i ); int pushFlags = PUSHFL_CLIP|PUSHFL_NOGROUNDENTITIES; //|PUSHFL_APPLYIMPULSE; gameLocal.push.ClipPush( tr, self, pushFlags, oldOrigin, axis, newOrigin, axis, bodies[ i ].clipModel ); if ( tr.fraction < 1.0f ) { distance *= tr.fraction; newPos = oldPos + distance; collision = true; collideEntityNum = tr.c.entityNum; } else { newPos = ( newOrigin - bodies[ i ].pos1 ).Length(); float pathLength = ( bodies[ i ].pos2 - bodies[ i ].pos1 ).Length(); if ( pathLength > idMath::FLT_EPSILON ) { newPos /= pathLength; } } } // re-link all the clip models that have moved so far using the new position // otherwise they could potentially move into blocking entities currentPos = newPos; for ( int j = 0; j <= i; j++ ) { UpdateBodyPosition( j ); } } // Gordon: as currentPos may have been modified above, reset it back so SetCurrentPos works properly currentPos = oldPos; self->OnMoveFinished(); SetCurrentPos( newPos ); if ( collision ) { self->OnTeamBlocked( self, gameLocal.entities[ collideEntityNum ] ); } return currentPos != oldPos; } /* ================ sdPhysics_Door::SetCurrentPos ================ */ bool sdPhysics_Door::SetCurrentPos( float newPos ) { if ( currentPos == newPos ) { return false; } currentPos = newPos; for ( int i = 0; i < bodies.Num(); i++ ) { UpdateBodyPosition( i ); } if ( currentPos == destPos ) { self->BecomeInactive( TH_PHYSICS ); self->ReachedPosition(); } else { self->BecomeActive( TH_PHYSICS ); } return true; } /* ================ sdPhysics_Door::UpdateBodyPosition ================ */ void sdPhysics_Door::UpdateBodyPosition( int id ) { bodies[ id ].currentOrigin = Lerp( bodies[ id ].pos1, bodies[ id ].pos2, currentPos ); bodies[ id ].clipModel->Link( gameLocal.clip, self, id, bodies[ id ].currentOrigin, bodies[ id ].axes ); } /* ================ sdPhysics_Door::SetClipModel ================ */ void sdPhysics_Door::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { assert( self ); assert( model ); // we need a clip model assert( model->IsTraceModel() ); // and it should be a trace model assert( density > 0.0f ); // density should be valid assert( id >= 0 ); int max = Max( id + 1, bodies.Num() ); if ( max > bodies.Num() ) { bodies.AssureSize( max ); } body_t& body = bodies[ id ]; if ( freeOld ) { if ( ( body.clipModel != NULL ) && ( body.clipModel != model ) ) { gameLocal.clip.DeleteClipModel( body.clipModel ); } } body.axes = mat3_identity; body.clipMask = 0; body.clipModel = model; body.currentOrigin = vec3_origin; body.pos1 = vec3_origin; body.pos2 = vec3_origin; } /* ================ sdPhysics_Door::EvaluateContacts ================ */ bool sdPhysics_Door::EvaluateContacts( CLIP_DEBUG_PARMS_DECLARATION_ONLY ) { return false; } /* ================ sdPhysics_Door::IsAtRest ================ */ bool sdPhysics_Door::IsAtRest( void ) const { return currentPos == destPos; } /* ================ sdPhysics_Door::Pushable ================ */ bool sdPhysics_Door::IsPushable( void ) const { return false; } /* ================ sdPhysics_Door::GetBounds ================ */ const idBounds& sdPhysics_Door::GetBounds( int id ) const { static idBounds bounds; bounds.Clear(); if( id >= 0 && id < bodies.Num() ) { bounds = bodies[ id ].clipModel->GetBounds(); } else { for( int i = 0; i < bodies.Num(); i++ ) { bounds += bodies[ i ].clipModel->GetBounds(); } } return bounds; } /* ================ sdPhysics_Door::GetAbsBounds ================ */ const idBounds& sdPhysics_Door::GetAbsBounds( int id ) const { static idBounds bounds; bounds.Clear(); if( id >= 0 && id < bodies.Num() ) { bounds = bodies[ id ].clipModel->GetAbsBounds(); } else { for( int i = 0; i < bodies.Num(); i++ ) { bounds += bodies[ i ].clipModel->GetAbsBounds(); } } return bounds; } /* ================ sdPhysics_Door::GetClipModel ================ */ idClipModel* sdPhysics_Door::GetClipModel( int id ) const { assert( id >= 0 && id < bodies.Num() ); if ( id < 0 || id >= bodies.Num() ) { return NULL; } return bodies[ id ].clipModel; } /* ================ sdPhysics_Door::GetContents ================ */ int sdPhysics_Door::GetContents( int id ) const { if ( id >= 0 && id < bodies.Num() ) { return bodies[ id ].clipModel->GetContents(); } int contents = 0; for ( int i = 0; i < bodies.Num(); i++ ) { contents |= bodies[ i ].clipModel->GetContents(); } return contents; } /* ================ sdPhysics_Door::SetContents ================ */ void sdPhysics_Door::SetContents( int contents, int id ) { assert( id >= 0 && id < bodies.Num() ); if ( id < 0 || id >= bodies.Num() ) { return; } bodies[ id ].clipModel->SetContents( contents ); } /* ================ sdPhysics_Door::GetClipMask ================ */ int sdPhysics_Door::GetClipMask( int id ) const { int contents = 0; if( id >= 0 && id < bodies.Num() ) { contents = bodies[ id ].clipMask; } else { for( int i = 0; i < bodies.Num(); i++ ) { contents |= bodies[ i ].clipMask; } } return contents; } /* ================ sdPhysics_Door::SetClipMask ================ */ void sdPhysics_Door::SetClipMask( int mask, int id ) { assert( id >= 0 && id < bodies.Num() ); if ( id < 0 || id >= bodies.Num() ) { return; } bodies[ id ].clipMask = mask; } /* ================ sdPhysics_Door::GetAxis ================ */ const idMat3& sdPhysics_Door::GetAxis( int id ) const { assert( id >= 0 && id < bodies.Num() ); if ( id < 0 || id >= bodies.Num() ) { return mat3_identity; } return bodies[ id ].axes; } /* ================ sdPhysics_Door::EnableClip ================ */ void sdPhysics_Door::EnableClip( void ) { for ( int i = 0; i < bodies.Num(); i++ ) { bodies[ i ].clipModel->Enable(); } } /* ================ sdPhysics_Door::DisableClip ================ */ void sdPhysics_Door::DisableClip( bool activateContacting ) { for ( int i = 0; i < bodies.Num(); i++ ) { if ( activateContacting ) { WakeEntitiesContacting( self, bodies[ i ].clipModel ); } bodies[ i ].clipModel->Disable(); } } /* ================ sdPhysics_Door::CreateNetworkStructure ================ */ sdEntityStateNetworkData* sdPhysics_Door::CreateNetworkStructure( networkStateMode_t mode ) const { if ( mode == NSM_VISIBLE ) { return new sdDoorPhysicsNetworkData(); } return NULL; } /* ================ sdPhysics_Door::CheckNetworkStateChanges ================ */ bool sdPhysics_Door::CheckNetworkStateChanges( networkStateMode_t mode, const sdEntityStateNetworkData& baseState ) const { if ( mode == NSM_VISIBLE ) { NET_GET_BASE( sdDoorPhysicsNetworkData ); bool wantClose = destPos == 0.f; if ( wantClose != baseData.closing ) { return true; } NET_CHECK_FIELD( currentPos, currentPos ); } return false; } /* ================ sdPhysics_Door::ApplyNetworkState ================ */ void sdPhysics_Door::ApplyNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) { if ( mode == NSM_VISIBLE ) { NET_GET_NEW( sdDoorPhysicsNetworkData ); if ( newData.closing ) { Close(); } else { Open(); } if ( SetCurrentPos( newData.currentPos ) ) { self->UpdateVisuals(); } } } /* ================ sdPhysics_Door::ReadNetworkState ================ */ void sdPhysics_Door::ReadNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, const idBitMsg& msg ) const { if ( mode == NSM_VISIBLE ) { NET_GET_STATES( sdDoorPhysicsNetworkData ); newData.currentPos = msg.ReadDeltaFloat( baseData.currentPos ); newData.closing = msg.ReadBool(); } } /* ================ sdPhysics_Door::WriteNetworkState ================ */ void sdPhysics_Door::WriteNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, idBitMsg& msg ) const { if ( mode == NSM_VISIBLE ) { NET_GET_STATES( sdDoorPhysicsNetworkData ); newData.currentPos = currentPos; newData.closing = destPos == 0.f; msg.WriteDeltaFloat( baseData.currentPos, newData.currentPos ); msg.WriteBool( newData.closing ); } } /* =============================================================================== sdDoorNetworkData =============================================================================== */ /* ================ sdDoorNetworkData::MakeDefault ================ */ void sdDoorNetworkData::MakeDefault( void ) { closeTime = 0; sdScriptEntityNetworkData::MakeDefault(); } /* ================ sdDoorNetworkData::Write ================ */ void sdDoorNetworkData::Write( idFile* file ) const { file->WriteInt( closeTime ); sdScriptEntityNetworkData::Write( file ); } /* ================ sdDoorNetworkData::Read ================ */ void sdDoorNetworkData::Read( idFile* file ) { file->ReadInt( closeTime ); sdScriptEntityNetworkData::Read( file ); } /* =============================================================================== idDoor =============================================================================== */ const idEventDef EV_Door_Open( "open", '\0', DOC_TEXT( "Opens the door, the door will naturally close again, if not in toggle mode." ), 0, "This will ignore any script checks which would normally keep the door locked." ); const idEventDef EV_Door_Close( "close", '\0', DOC_TEXT( "Closes the door." ), 0, "This will ignore any script checks which would normally keep the door locked." ); const idEventDef EV_Door_IsOpen( "isOpen", 'b', DOC_TEXT( "Returns whether the door is fully open or not." ), 0, NULL ); const idEventDef EV_Door_IsClosed( "isClosed", 'b', DOC_TEXT( "Returns whether the door is fully closed or not." ), 0, NULL ); const idEventDef EV_Door_IsOpening( "isOpening", 'b', DOC_TEXT( "Return whether the door is currently opening or not." ), 0, NULL ); const idEventDef EV_Door_IsClosing( "isClosing", 'b', DOC_TEXT( "Return whether the door is currently closing or not." ), 0, NULL ); extern const idEventDef EV_SetSkin; extern const idEventDef EV_SetToggle; CLASS_DECLARATION( sdScriptEntity, idDoor ) EVENT( EV_Activate, idDoor::Event_Activate ) EVENT( EV_Door_Open, idDoor::Event_Open ) EVENT( EV_Door_Close, idDoor::Event_Close ) EVENT( EV_Door_IsOpen, idDoor::Event_IsOpen ) EVENT( EV_Door_IsClosed, idDoor::Event_IsClosed ) EVENT( EV_Door_IsOpening, idDoor::Event_IsOpening ) EVENT( EV_Door_IsClosing, idDoor::Event_IsClosing ) EVENT( EV_SetSkin, idDoor::Event_SetSkin ) EVENT( EV_SetToggle, idDoor::Event_SetToggle ) END_CLASS /* ================ idDoor::idDoor ================ */ idDoor::idDoor( void ) { trigger = NULL; sndTrigger = NULL; nextSndTriggerTime = 0; normalAxisIndex = 0; closeTime = 0; moving = false; toggle = false; reverseOnBlock = true; isSlave = false; } /* ================ idDoor::~idDoor ================ */ idDoor::~idDoor( void ) { parts.DeleteContents( true ); gameLocal.clip.DeleteClipModel( trigger ); gameLocal.clip.DeleteClipModel( sndTrigger ); } /* ================ idDoor::OnTeamBlocked ================ */ void idDoor::OnTeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { if ( crushOnBlock ) { blockingEntity->Damage( NULL, NULL, vec3_zero, DAMAGE_FOR_NAME( "damage_mover_crush" ), 1.f, NULL ); return; } if ( reverseOnBlock ) { if ( physicsObj.IsOpening() ) { Close(); return; } if ( physicsObj.IsClosing() ) { Open(); return; } } } /* ================ idDoor::VectorForDir ================ */ void idDoor::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 : ang = physicsObj.GetAxis().ToAngles(); ang.pitch = 0; ang.roll = 0; ang.yaw += 90; vec = ang.ToForward(); break; case DIR_RIGHT : ang = physicsObj.GetAxis().ToAngles(); ang.pitch = 0; ang.roll = 0; ang.yaw -= 90; vec = ang.ToForward(); break; case DIR_FORWARD : ang = physicsObj.GetAxis().ToAngles(); ang.pitch = 0; ang.roll = 0; vec = ang.ToForward(); break; case DIR_BACK : ang = physicsObj.GetAxis().ToAngles(); 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 : ang = physicsObj.GetAxis().ToAngles(); ang.ToVectors( NULL, &vec ); vec *= -1; break; case DIR_REL_RIGHT : ang = physicsObj.GetAxis().ToAngles(); ang.ToVectors( NULL, &vec ); break; case DIR_REL_FORWARD : ang = physicsObj.GetAxis().ToAngles(); vec = ang.ToForward(); break; case DIR_REL_BACK : ang = physicsObj.GetAxis().ToAngles(); vec = ang.ToForward() * -1; break; default: ang.Set( 0, angle, 0 ); vec = GetWorldVector( ang.ToForward() ); break; } } /* ================ idDoor::Spawn ================ */ void idDoor::Spawn( void ) { baseOrg = GetPhysics()->GetOrigin(); baseAxis = GetPhysics()->GetAxis(); isSlave = !spawnArgs.GetBool( "groupmaster" ); maxHealth = health = spawnArgs.GetInt( "health" ); // default wait of 3 seconds waitTime = SEC2MS( spawnArgs.GetFloat( "wait", "3" ) ); reverseOnBlock = spawnArgs.GetBool( "reverse_on_block", "1" ); crushOnBlock = spawnArgs.GetBool( "crush_on_block", "1" ); toggle = spawnArgs.GetBool( "toggle" ); 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; checkOpenFunc = scriptObject->GetFunction( "allowOpen" ); BecomeActive( TH_THINK ); int i; for ( i = 0; i < s_doorInfo.Num(); i++ ) { if ( !s_doorInfo[ i ]->name.Icmp( GetName() ) ) { break; } } if ( i == s_doorInfo.Num() ) { doorSpawnInfo_t* info = new doorSpawnInfo_t; info->renderEnt = renderEntity; CalcPositions( info->pos1, info->pos2 ); info->axes = GetPhysics()->GetAxis(); GetTraceModel( info->trm ); info->name = GetName(); info->group = spawnArgs.GetString( "group" ); info->pusher = spawnArgs.GetBool( "pusher" ); s_doorInfo.Alloc() = info; } } /* ================ idDoor::CalcPositions ================ */ void idDoor::CalcPositions( idVec3& pos1, idVec3& pos2 ) { pos1 = GetPhysics()->GetOrigin(); idVec3 moveDelta; if ( spawnArgs.GetVector( "move_delta", "0 0 0", moveDelta ) ) { bool absolute = spawnArgs.GetBool( "move_absolute", "0" ); if( !absolute ) { pos2 = pos1 + moveDelta * GetPhysics()->GetAxis(); } else { pos2 = pos1 + moveDelta; } } else { float dir; // 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 ); dir = spawnArgs.GetFloat( "angle" ); } idVec3 movedir; // jrad - support for all the other motion types VectorForDir( dir, movedir ); // default lip of 8 units float lip = spawnArgs.GetFloat( "lip", "8" ); // calculate second position idVec3 absMovedir; absMovedir[ 0 ] = idMath::Fabs( movedir[ 0 ] ); absMovedir[ 1 ] = idMath::Fabs( movedir[ 1 ] ); absMovedir[ 2 ] = idMath::Fabs( movedir[ 2 ] ); idVec3 size = GetPhysics()->GetAbsBounds().Size(); float distance = ( absMovedir * size ) - lip; pos2 = pos1 + distance * movedir; } } /* ================ idDoor::OnMasterReady ================ */ void idDoor::OnMasterReady( void ) { idBounds absBounds = physicsObj.GetAbsBounds( -1 ); portal.Init( absBounds ); refSound.origin = absBounds.GetCenter(); CalcTriggerBounds( 0.f, baseBounds ); ClosePortals( false ); if ( health ) { fl.takedamage = true; } const char* sndtemp = spawnArgs.GetString( "snd_locked" ); if ( *sndtemp ) { SpawnSoundTrigger(); } if ( !spawnArgs.GetBool( "no_touch" ) ) { // spawn trigger SpawnDoorTrigger(); } } /* ================ idDoor::EnableClip ================ */ void idDoor::EnableClip( void ) { if ( fl.forceDisableClip ) { return; } if ( trigger ) { LinkTrigger(); } if ( sndTrigger ) { LinkSoundTrigger(); } sdScriptEntity::EnableClip(); } /* ================ idDoor::DisableClip ================ */ void idDoor::DisableClip( bool activateContacting ) { if ( trigger != NULL ) { trigger->Unlink( gameLocal.clip ); } if ( sndTrigger != NULL ) { sndTrigger->Unlink( gameLocal.clip ); } sdScriptEntity::DisableClip( activateContacting ); } /* ================ idDoor::Hide ================ */ void idDoor::Hide( void ) { if ( IsHidden() ) { return; } fl.hidden = true; OpenPortals(); for ( int i = 0; i < parts.Num(); i++ ) { parts[ i ]->Hide(); } } /* ================ idDoor::Show ================ */ void idDoor::Show( void ) { if ( !IsHidden() ) { return; } fl.hidden = false; if ( physicsObj.IsClosed() ) { ClosePortals( true ); } for ( int i = 0; i < parts.Num(); i++ ) { parts[ i ]->Show(); } } /* ================ idDoor::Use ================ */ void idDoor::Use( idEntity *activator ) { activator = activator; if ( physicsObj.IsClosed() ) { Open(); return; } if ( physicsObj.IsOpen() ) { if ( toggle ) { Close(); return; } if ( waitTime < 0 ) { return; } // if all the way up, just delay before coming down SetCloseTime( gameLocal.time + waitTime ); return; } // only partway down before reversing if ( physicsObj.IsClosing() ) { Open(); return; } // only partway up before reversing if ( physicsObj.IsOpening() ) { Close(); return; } } /* ================ idDoor::Open ================ */ void idDoor::Open( void ) { if ( physicsObj.IsOpening() ) { // already there, or on the way return; } physicsObj.Open(); if ( physicsObj.IsClosed() ) { // open areaportal OpenPortals(); return; } } /* ================ idDoor::Close ================ */ void idDoor::Close( void ) { if ( physicsObj.IsClosing() ) { // already there, or on the way return; } physicsObj.Close(); } /* ================ idDoor::IsOpen ================ */ bool idDoor::IsOpen( void ) const { return physicsObj.IsOpen(); } /* ================ idDoor::IsPermanentlyOpen ================ */ bool idDoor::IsPermanentlyOpen( void ) const { bool open = physicsObj.IsOpen(); if ( open && waitTime < 0 ) { return true; } return false; } /* ====================== idDoor::CalcTriggerBounds Calcs bounds for a trigger. ====================== */ void idDoor::CalcTriggerBounds( float size, idBounds &bounds ) { // find the bounds of everything on the team bounds.Clear(); const idVec3& org = baseOrg; idMat3 transpose = baseAxis.Transpose(); if ( health ) { fl.takedamage = true; } for ( int i = 0; i < parts.Num(); i++ ) { const idVec3& otherOrg = physicsObj.GetOrigin( i ); const idMat3& otherAxis = physicsObj.GetAxis( i ); idMat3 axisDiff = otherAxis * transpose; idVec3 diff = otherOrg - org; diff = diff * transpose; idBounds otherBounds = physicsObj.GetBounds( i ); otherBounds.Translate( diff ); idBounds addBounds; addBounds.FromTransformedBounds( otherBounds, diff, axisDiff ); bounds.AddBounds( addBounds ); } // find the thinnest axis, which will be the one we expand int best = 0; for ( int 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; } /* ====================== idDoor::LinkTrigger ====================== */ void idDoor::LinkTrigger( void ) { trigger->Link( gameLocal.clip, this, TRIGGER_ID, baseOrg, baseAxis ); } /* ====================== idDoor::SpawnDoorTrigger ====================== */ void idDoor::SpawnDoorTrigger( void ) { if ( trigger ) { // already have a trigger, so don't spawn a new one. LinkTrigger(); return; } float triggerSize = spawnArgs.GetFloat( "triggersize", "120" ); idBounds bounds; CalcTriggerBounds( triggerSize, bounds ); // create a trigger clip model trigger = new idClipModel( idTraceModel( bounds ), true ); trigger->SetContents( CONTENTS_TRIGGER ); trigger->SetPosition( baseOrg, baseAxis, gameLocal.clip ); LinkTrigger(); } /* ====================== idDoor::LinkSoundTrigger ====================== */ void idDoor::LinkSoundTrigger( void ) { sndTrigger->Link( gameLocal.clip, this, SND_TRIGGER_ID, baseOrg, baseAxis ); } /* ====================== idDoor::SpawnSoundTrigger ====================== */ void idDoor::SpawnSoundTrigger( void ) { if ( sndTrigger ) { LinkSoundTrigger(); return; } float triggerSize = spawnArgs.GetFloat( "triggersize", "120" ); idBounds bounds; CalcTriggerBounds( triggerSize * 0.5f, bounds ); // create a trigger clip model sndTrigger = new idClipModel( idTraceModel( bounds ), true ); sndTrigger->SetContents( CONTENTS_TRIGGER ); sndTrigger->SetPosition( baseOrg, baseAxis, gameLocal.clip ); LinkSoundTrigger(); } /* ================ idDoor::Script_AllowOpen ================ */ bool idDoor::Script_AllowOpen( idEntity* other ) { sdScriptHelper helper; helper.Push( other->GetScriptObject() ); return CallFloatNonBlockingScriptEvent( checkOpenFunc, helper ) != 0.f; } /* ================ idDoor::OnTouch ================ */ void idDoor::OnTouch( idEntity *other, const trace_t& trace ) { if ( IsHidden() ) { return; } idPlayer* p = other->Cast< idPlayer >(); if ( p && p->IsSpectating() ) { SpectatorTouch( p, trace ); return; } if ( p != NULL && p->GetHealth() <= 0 ) { return; } bool hasRequirements = true; if ( checkOpenFunc && !Script_AllowOpen( other ) ) { hasRequirements = false; } if ( trigger && trace.c.id == trigger->GetId() ) { if ( hasRequirements && !physicsObj.IsOpening() ) { Use( other ); } return; } if ( sndTrigger && trace.c.id == sndTrigger->GetId() ) { if ( !hasRequirements && gameLocal.time > nextSndTriggerTime ) { StartSound( "snd_locked", SND_ANY, 0, NULL ); nextSndTriggerTime = gameLocal.time + SEC2MS( 10.f ); } return; } } /* ================ idDoor::SpectatorTouch ================ */ void idDoor::SpectatorTouch( idPlayer* p, const trace_t& trace ) { assert( p && p->IsSpectating() ); if ( IsPermanentlyOpen() ) { return; } // use sndTrigger as it should always be smaller than trigger if ( sndTrigger && trace.c.id == sndTrigger->GetId() ) { idVec3 relativeOrg = ( trace.endpos - baseOrg ) * baseAxis.Transpose(); const idBounds& bounds = sndTrigger->GetBounds(); idVec3 translate = bounds.GetCenter(); idVec3 playerSize = p->GetPhysics()->GetBounds().Size(); playerSize.z = 0.f; if ( relativeOrg[ normalAxisIndex ] > bounds.GetCenter()[ normalAxisIndex ] ) { translate[ normalAxisIndex ] = bounds.GetMins()[ normalAxisIndex ]; translate[ normalAxisIndex ] -= playerSize.Length(); } else { translate[ normalAxisIndex ] = bounds.GetMaxs()[ normalAxisIndex ]; translate[ normalAxisIndex ] += playerSize.Length(); } translate = baseOrg + ( translate * baseAxis ); p->SetOrigin( translate ); p->lastSpectateTeleport = gameLocal.time; } } /* ================ idDoor::Event_Activate ================ */ void idDoor::Event_Activate( idEntity *activator ) { Use( activator ); } /* ================ idDoor::Event_Open ================ */ void idDoor::Event_Open( void ) { Open(); } /* ================ idDoor::Event_Close ================ */ void idDoor::Event_Close( void ) { Close(); } /* ================ idDoor::Event_IsOpen ================ */ void idDoor::Event_IsOpen( void ) { sdProgram::ReturnBoolean( IsOpen() ); } /* ================ idDoor::Event_IsClosed ================ */ void idDoor::Event_IsClosed( void ) { sdProgram::ReturnBoolean( physicsObj.IsClosed() ); } /* ================ idDoor::Event_IsOpening ================ */ void idDoor::Event_IsOpening( void ) { sdProgram::ReturnBoolean( physicsObj.IsOpening() ); } /* ================ idDoor::Event_IsClosing ================ */ void idDoor::Event_IsClosing( void ) { sdProgram::ReturnBoolean( physicsObj.IsClosing() ); } /* ================ idDoor::OpenPortals ================ */ void idDoor::OpenPortals( void ) { if ( !portal.IsValid() ) { return; } portal.Open(); if ( gameLocal.isServer ) { sdEntityBroadcastEvent msg( this, EVENT_PORTALSTATE ); msg.WriteBool( true ); msg.Send( true, sdReliableMessageClientInfoAll() ); } } /* ================ idDoor::ClosePortals ================ */ void idDoor::ClosePortals( bool force ) { if ( !portal.IsValid() ) { return; } if ( !force && IsHidden() ) { return; } portal.Close(); if ( gameLocal.isServer ) { sdEntityBroadcastEvent msg( this, EVENT_PORTALSTATE ); msg.WriteBool( false ); msg.Send( true, sdReliableMessageClientInfoAll() ); } } /* ================ idDoor::Event_OpenPortal Sets the portal associtated with this door to be open ================ */ void idDoor::Event_OpenPortal( void ) { OpenPortals(); } /* ================ idDoor::Event_ForceClosePortal Sets the portal associtated with this door to be closed ================ */ void idDoor::Event_ForceClosePortal( void ) { ClosePortals( true ); } /* ================ idDoor::Event_ClosePortal Sets the portal associtated with this door to be closed ================ */ void idDoor::Event_ClosePortal( void ) { ClosePortals( false ); } /* ================ idDoor::GetTraceModel ================ */ void idDoor::GetTraceModel( idTraceModel& trm ) { const char* clipModelName = GetClipModelName(); if ( !gameLocal.clip.LoadTraceModel( clipModelName, trm ) ) { idClipModel *mdl = new idClipModel( clipModelName ); trm.SetupBox( mdl->GetBounds() ); gameLocal.clip.DeleteClipModel( mdl ); // gameLocal.Warning( "idDoor '%s': cannot load trace model %s", name.c_str(), clipModelName ); } } /* ================ idDoor::PostMapSpawn ================ */ void idDoor::PostMapSpawn( void ) { sdScriptEntity::PostMapSpawn(); if ( isSlave ) { if ( !gameLocal.isClient ) { PostEventMS( &EV_Remove, 0 ); } return; } SetPhysics( &physicsObj ); physicsObj.SetSelf( this ); const char* groupName = spawnArgs.GetString( "group" ); for ( int i = 0; i < s_doorInfo.Num(); i++ ) { doorSpawnInfo_t& info = *s_doorInfo[ i ]; if ( ( !*groupName || info.group.Icmp( groupName ) ) && info.name.Icmp( GetName() ) ) { continue; } sdRenderEntityBundle* bundle = new sdRenderEntityBundle(); bundle->Copy( info.renderEnt ); bundle->Show(); int newIndex = physicsObj.GetNumClipModels(); physicsObj.SetClipModel( new idClipModel( info.trm, false ), 1.f, newIndex ); physicsObj.SetBodyProperties( newIndex, info.pos1, info.pos2, info.axes, info.pusher ); physicsObj.SetClipMask( MASK_PLAYERSOLID | CONTENTS_MONSTER, newIndex ); physicsObj.SetContents( CONTENTS_SOLID, newIndex ); parts.Alloc() = bundle; } float time; if ( !spawnArgs.GetFloat( "time", "1", time ) ) { idVec3 pos1, pos2; CalcPositions( pos1, pos2 ); // default speed of 400 float dist = ( pos2 - pos1 ).Length(); float speed = spawnArgs.GetFloat( "speed", "400" ); time = dist / speed; gameLocal.Warning( "idDoor::PostMapSpawn No Time Set, Using Speed Instead" ); } physicsObj.SetSpeed( 1 / time ); OnMasterReady(); } /* ================ idDoor::ReachedPosition ================ */ void idDoor::ReachedPosition( void ) { if ( physicsObj.IsOpen() ) { StartSound( "snd_opened", SND_DOOR, 0, NULL ); if ( waitTime > 0 && !toggle ) { SetCloseTime( gameLocal.time + waitTime ); } return; } if ( physicsObj.IsClosed() ) { StartSound( "snd_closed", SND_DOOR, 0, NULL ); ClosePortals( false ); return; } } /* ================ idDoor::Think ================ */ void idDoor::Think( void ) { sdScriptEntity::Think(); if ( closeTime != 0 && gameLocal.time > closeTime ) { Close(); SetCloseTime( 0 ); } } /* ================ idDoor::UpdateModelTransform ================ */ void idDoor::UpdateModelTransform( void ) { for ( int i = 0; i < parts.Num(); i++ ) { renderEntity_t& rEnt = parts[ i ]->GetEntity(); rEnt.axis = physicsObj.GetAxis( i ); rEnt.origin = physicsObj.GetOrigin( i ); } } /* ================ idDoor::Present ================ */ void idDoor::Present( void ) { if ( ( thinkFlags & TH_UPDATEVISUALS ) == 0 ) { return; } BecomeInactive( TH_UPDATEVISUALS ); OnUpdateVisuals(); if ( IsHidden() ) { return; } for ( int i = 0; i < parts.Num(); i++ ) { parts[ i ]->Update(); } } /* ================ idDoor::CanCollide ================ */ bool idDoor::CanCollide( const idEntity* other, int traceId ) const { return !moving || other->GetPhysics()->IsPushable() || other->fl.forceDoorCollision; } /* ================ idDoor::OnNewMapLoad ================ */ void idDoor::OnNewMapLoad( void ) { s_doorInfo.DeleteContents( true ); } /* ================ idDoor::OnMapClear ================ */ void idDoor::OnMapClear( void ) { s_doorInfo.DeleteContents( true ); } /* ================ idDoor::SetCloseTime ================ */ void idDoor::SetCloseTime( int time ) { closeTime = time; if ( closeTime != 0 ) { BecomeActive( TH_THINK ); } } /* ================ idDoor::ApplyNetworkState ================ */ void idDoor::ApplyNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) { if ( mode == NSM_VISIBLE ) { NET_GET_NEW( sdDoorNetworkData ); SetCloseTime( newData.closeTime ); } sdScriptEntity::ApplyNetworkState( mode, newState ); } /* ================ idDoor::ReadNetworkState ================ */ void idDoor::ReadNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, const idBitMsg& msg ) const { if ( mode == NSM_VISIBLE ) { NET_GET_STATES( sdDoorNetworkData ); newData.closeTime = msg.ReadDeltaLong( baseData.closeTime ); } sdScriptEntity::ReadNetworkState( mode, baseState, newState, msg ); } /* ================ idDoor::WriteNetworkState ================ */ void idDoor::WriteNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, idBitMsg& msg ) const { if ( mode == NSM_VISIBLE ) { NET_GET_STATES( sdDoorNetworkData ); newData.closeTime = closeTime; msg.WriteDeltaLong( baseData.closeTime, newData.closeTime ); } sdScriptEntity::WriteNetworkState( mode, baseState, newState, msg ); } /* ================ idDoor::CheckNetworkStateChanges ================ */ bool idDoor::CheckNetworkStateChanges( networkStateMode_t mode, const sdEntityStateNetworkData& baseState ) const { if ( isSlave ) { return false; } if ( mode == NSM_VISIBLE ) { NET_GET_BASE( sdDoorNetworkData ); NET_CHECK_FIELD( closeTime, closeTime ); } return sdScriptEntity::CheckNetworkStateChanges( mode, baseState ); } /* ================ idDoor::CreateNetworkStructure ================ */ sdEntityStateNetworkData* idDoor::CreateNetworkStructure( networkStateMode_t mode ) const { if ( isSlave ) { return NULL; } if ( mode == NSM_VISIBLE ) { sdDoorNetworkData* newData = new sdDoorNetworkData(); newData->physicsData = physicsObj.CreateNetworkStructure( mode ); return newData; } if ( mode == NSM_BROADCAST ) { sdScriptEntityBroadcastData* newData = new sdScriptEntityBroadcastData(); newData->physicsData = physicsObj.CreateNetworkStructure( mode ); return newData; } return NULL; } /* ================ idDoor::WantsToThink ================ */ bool idDoor::WantsToThink( void ) const { return closeTime != 0 || sdScriptEntity::WantsToThink(); } /* ================ idDoor::Event_SetSkin ================ */ void idDoor::Event_SetSkin( const char* skinname ) { const idDeclSkin* skin = *skinname ? declHolder.declSkinType.LocalFind( skinname ) : NULL; for( int i = 0; i < parts.Num(); i++ ) { parts[ i ]->GetEntity().customSkin = skin; } UpdateVisuals(); } /* ================ idDoor::Event_SetToggle ================ */ void idDoor::Event_SetToggle( bool t ) { toggle = t; closeTime = 0; } /* ================ idDoor::ClientReceiveEvent ================ */ bool idDoor::ClientReceiveEvent( int event, int time, const idBitMsg& msg ) { switch ( event ) { case EVENT_PORTALSTATE: { bool state = msg.ReadBool(); if ( state ) { OpenPortals(); } else { ClosePortals( true ); } return true; } } return sdScriptEntity::ClientReceiveEvent( event, time, msg ); } /* =============== idDoor::WriteDemoBaseData ============== */ void idDoor::WriteDemoBaseData( idFile* file ) const { idEntity::WriteDemoBaseData( file ); file->WriteBool( portal.IsOpen() ); } /* =============== idDoor::ReadDemoBaseData ============== */ void idDoor::ReadDemoBaseData( idFile* file ) { idEntity::ReadDemoBaseData( file ); bool state; file->ReadBool( state ); if ( state ) { OpenPortals(); } else { ClosePortals( true ); } } /* =============== idDoor::GetModelDefHandle ============== */ int idDoor::GetModelDefHandle( int id ) { if ( id < 0 || id >= parts.Num() ) { return -1; } else { return parts[id]->GetHandle(); } }