/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program 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. ** ** This program 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 this program. If not, see . */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando * * * * $Archive:: /Commando/Code/Combat/doors.cpp $* * * * $Author:: Steve_t $* * * * $Modtime:: 1/15/02 3:07p $* * * * $Revision:: 47 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "doors.h" #include "simpledefinitionfactory.h" #include "persistfactory.h" #include "wwphysids.h" #include "debug.h" #include "hanim.h" #include "wwhack.h" #include "combat.h" #include "smartgameobj.h" #include "gameobjmanager.h" #include "soldier.h" #include "wwaudio.h" #include "wwprofile.h" #include "bitpackids.h" #include "diaglog.h" #include "vehicle.h" DECLARE_FORCE_LINK( doorphys ); /* ** DoorPhysDefClass */ SimplePersistFactoryClass _DoorPhysDefPersistFactory; DECLARE_DEFINITION_FACTORY(DoorPhysDefClass, CLASSID_DOORPHYSDEF, "DoorPhys") _DoorPhysDefDefFactory; DoorPhysDefClass::DoorPhysDefClass( void ) : AccessiblePhysDefClass(), CloseDelay( 2 ), TriggerZone1( Vector3( 0,0,0), Vector3( 1,1,1 ) ), TriggerZone2( Vector3( 0,0,0), Vector3( 1,1,1 ) ), OpenSoundDefID( 0 ), CloseSoundDefID( 0 ), UnlockSoundDefID( 0 ), AccessDeniedSoundDefID( 0 ), DoorOpensForVehicles( false ) { ZONE_PARAM(DoorPhysDefClass, TriggerZone1, "TriggerZone1" ); ZONE_PARAM(DoorPhysDefClass, TriggerZone2, "TriggerZone2" ); EDITABLE_PARAM(DoorPhysDefClass, ParameterClass::TYPE_FLOAT, CloseDelay ); EDITABLE_PARAM(DoorPhysDefClass, ParameterClass::TYPE_SOUNDDEFINITIONID, OpenSoundDefID); EDITABLE_PARAM(DoorPhysDefClass, ParameterClass::TYPE_SOUNDDEFINITIONID, CloseSoundDefID); EDITABLE_PARAM(DoorPhysDefClass, ParameterClass::TYPE_SOUNDDEFINITIONID, UnlockSoundDefID); EDITABLE_PARAM(DoorPhysDefClass, ParameterClass::TYPE_SOUNDDEFINITIONID, AccessDeniedSoundDefID); EDITABLE_PARAM(DoorPhysDefClass, ParameterClass::TYPE_BOOL, DoorOpensForVehicles); } uint32 DoorPhysDefClass::Get_Class_ID (void) const { return CLASSID_DOORPHYSDEF; } bool DoorPhysDefClass::Is_Type(const char * type_name) { if (stricmp(type_name,DoorPhysDefClass::Get_Type_Name()) == 0) { return true; } else { return AccessiblePhysDefClass::Is_Type(type_name); } } PersistClass * DoorPhysDefClass::Create( void ) const { DoorPhysClass * door = new DoorPhysClass; door->Init( *this ); return door; } enum { CHUNKID_DEF_OLD_PARENT = 320001902, CHUNKID_DEF_VARIABLES, CHUNKID_DEF_PARENT, XXXMICROCHUNKID_DEF_TRIGGER_RADIUS = 1, MICROCHUNKID_DEF_CLOSE_DELAY, MICROCHUNKID_DEF_TRIGGER_ZONE1, MICROCHUNKID_DEF_OLD_LOCK_CODE, MICROCHUNKID_DEF_OPEN_SOUND_DEF_ID, MICROCHUNKID_DEF_CLOSE_SOUND_DEF_ID, MICROCHUNKID_DEF_UNLOCK_SOUND_DEF_ID, MICROCHUNKID_DEF_ACCESS_DENIED_SOUND_DEF_ID, MICROCHUNKID_DEF_TRIGGER_ZONE2, MICROCHUNKID_DEF_DOOR_OPENS_FOR_VEHICLES, }; bool DoorPhysDefClass::Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_DEF_PARENT ); AccessiblePhysDefClass::Save( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_DEF_VARIABLES ); // WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_TRIGGER_RADIUS, TriggerRadius ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_CLOSE_DELAY, CloseDelay ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_TRIGGER_ZONE1, TriggerZone1 ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_TRIGGER_ZONE2, TriggerZone2 ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_OPEN_SOUND_DEF_ID, OpenSoundDefID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_CLOSE_SOUND_DEF_ID, CloseSoundDefID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_UNLOCK_SOUND_DEF_ID, UnlockSoundDefID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_ACCESS_DENIED_SOUND_DEF_ID, AccessDeniedSoundDefID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_DOOR_OPENS_FOR_VEHICLES, DoorOpensForVehicles ); csave.End_Chunk(); return true; } bool DoorPhysDefClass::Load( ChunkLoadClass &cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_DEF_OLD_PARENT: StaticAnimPhysDefClass::Load( cload ); break; case CHUNKID_DEF_PARENT: AccessiblePhysDefClass::Load( cload ); break; case CHUNKID_DEF_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { // READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_TRIGGER_RADIUS, TriggerRadius ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_CLOSE_DELAY, CloseDelay ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_TRIGGER_ZONE1, TriggerZone1 ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_TRIGGER_ZONE2, TriggerZone2 ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_OLD_LOCK_CODE, LockCode ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_OPEN_SOUND_DEF_ID, OpenSoundDefID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_CLOSE_SOUND_DEF_ID, CloseSoundDefID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_UNLOCK_SOUND_DEF_ID, UnlockSoundDefID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_ACCESS_DENIED_SOUND_DEF_ID, AccessDeniedSoundDefID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_DOOR_OPENS_FOR_VEHICLES, DoorOpensForVehicles ); default: Debug_Say(( "Unrecognized DoorPhys Variable chunkID\n" )); break; } cload.Close_Micro_Chunk(); } break; default: Debug_Say(( "Unrecognized DoorPhys chunkID\n" )); break; } cload.Close_Chunk(); } return true; } const PersistFactoryClass & DoorPhysDefClass::Get_Factory (void) const { return _DoorPhysDefPersistFactory; } /* ** */ SimplePersistFactoryClass _DoorPhysPersistFactory; const PersistFactoryClass & DoorPhysClass::Get_Factory (void) const { return _DoorPhysPersistFactory; } /* ** */ DoorPhysClass::DoorPhysClass( void ) : Timer( 0 ), CheckTimer( 0 ), State( STATE_CLOSED_DOOR ), LockState( false ), OpenRequestPending( false ) { } DoorPhysClass::~DoorPhysClass( void ) { } void DoorPhysClass::Init( const DoorPhysDefClass & def ) { AccessiblePhysClass::Init(def); AnimManager.Set_Animation_Mode( AnimCollisionManagerClass::ANIMATE_TARGET ); AnimManager.Set_Target_Frame( 0 ); } /* ** Save and Load */ enum { CHUNKID_OLD_PARENT = 320002202, CHUNKID_VARIABLES, CHUNKID_PARENT, XXXMICROCHUNKID_ = 1, MICROCHUNKID_STATE = 2, MICROCHUNKID_TIMER = 3, MICROCHUNKID_CHECK_TIMER = 4, MICROCHUNKID_OPEN_REQUEST_PENDING = 5, }; bool DoorPhysClass::Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); AccessiblePhysClass::Save( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE, State ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TIMER, Timer ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_CHECK_TIMER, CheckTimer ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_OPEN_REQUEST_PENDING, OpenRequestPending ); csave.End_Chunk(); return true; } bool DoorPhysClass::Load( ChunkLoadClass &cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_OLD_PARENT: StaticAnimPhysClass::Load( cload ); break; case CHUNKID_PARENT: AccessiblePhysClass::Load( cload ); break; case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE, State ); READ_MICRO_CHUNK( cload, MICROCHUNKID_TIMER, Timer ); READ_MICRO_CHUNK( cload, MICROCHUNKID_CHECK_TIMER, CheckTimer ); READ_MICRO_CHUNK( cload, MICROCHUNKID_OPEN_REQUEST_PENDING, OpenRequestPending ); default: Debug_Say(( "Unrecognized DoorPhys Variable chunkID\n" )); break; } cload.Close_Micro_Chunk(); } break; default: Debug_Say(( "Unrecognized DoorPhys chunkID\n" )); break; } cload.Close_Chunk(); } AnimManager.Set_Animation_Mode( AnimCollisionManagerClass::ANIMATE_TARGET ); AnimManager.Set_Target_Frame( 0 ); return true; } void DoorPhysClass::Save_State( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); AccessiblePhysClass::Save_State( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE, State ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TIMER, Timer ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_CHECK_TIMER, CheckTimer ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_OPEN_REQUEST_PENDING, OpenRequestPending ); csave.End_Chunk(); } #define LEGACY_STATICANIMPHYS_CHUNK_ANIMMANAGER 0x01170012 void DoorPhysClass::Load_State( ChunkLoadClass &cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_OLD_PARENT: StaticAnimPhysClass::Load_State( cload ); break; case CHUNKID_PARENT: AccessiblePhysClass::Load_State( cload ); break; case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE, State ); READ_MICRO_CHUNK( cload, MICROCHUNKID_TIMER, Timer ); READ_MICRO_CHUNK( cload, MICROCHUNKID_CHECK_TIMER, CheckTimer ); READ_MICRO_CHUNK( cload, MICROCHUNKID_OPEN_REQUEST_PENDING, OpenRequestPending ); default: Debug_Say(( "Unrecognized DoorPhys Variable chunkID\n" )); break; } cload.Close_Micro_Chunk(); } break; case LEGACY_STATICANIMPHYS_CHUNK_ANIMMANAGER: AnimManager.Load(cload); break; default: Debug_Say(( "Unrecognized DoorPhys chunkID\n" )); break; } cload.Close_Chunk(); } } void DoorPhysClass::Timestep( float dt ) { AccessiblePhysClass::Timestep( dt ); { WWPROFILE("DoorPhys::Timestep"); // // If we are configured correctly with an animation, // then evaluate our state and determine if we need to // do anything. // if ( AnimManager.Peek_Animation() != NULL ) { Update_State( dt ); } // // Reset the open request flag // OpenRequestPending = false; } return ; } bool DoorPhysClass::Set_State( int new_state ) { bool retval = false; if ( State != new_state ) { switch ( new_state ) { case STATE_OPENING_DOOR: { // // Did someone just unlock the door? // if ( Get_DoorPhysDef()->LockCode != 0 ) { WWAudioClass::Get_Instance()->Create_Instant_Sound( Get_DoorPhysDef()->UnlockSoundDefID, Get_Transform() ); } // // Play the 'door-opening' sound // WWAudioClass::Get_Instance()->Create_Instant_Sound( Get_DoorPhysDef()->OpenSoundDefID, Get_Transform() ); // // Open the door // AnimManager.Set_Animation_Mode( AnimCollisionManagerClass::ANIMATE_TARGET ); AnimManager.Set_Target_Frame( AnimManager.Peek_Animation()->Get_Num_Frames() - 1 ); } break; case STATE_CLOSING_DOOR: { // // Play the 'door-closing' sound // WWAudioClass::Get_Instance()->Create_Instant_Sound( Get_DoorPhysDef()->CloseSoundDefID, Get_Transform() ); // // Close the door // AnimManager.Set_Animation_Mode( AnimCollisionManagerClass::ANIMATE_TARGET ); AnimManager.Set_Target_Frame( 0 ); } break; case STATE_OPENED_DOOR: { // // Set a timer that we can use to close the door // Timer = Get_DoorPhysDef()->CloseDelay; // // Make sure the door is open (net games, may not see the "opening" state) // // Clients *must* see the opening - it's a guaranteed packet. ST - 1/15/2002 12:59PM // AnimManager.Set_Animation_Mode( AnimCollisionManagerClass::ANIMATE_TARGET ); AnimManager.Set_Target_Frame( AnimManager.Peek_Animation()->Get_Num_Frames() - 1 ); } break; case STATE_CLOSED_DOOR: // // Make sure the door is closed (in net games, we may not get // the "closing" state) // AnimManager.Set_Animation_Mode( AnimCollisionManagerClass::ANIMATE_TARGET ); AnimManager.Set_Target_Frame( 0 ); break; case STATE_ACCESS_DENIED: { // // Play the 'access denied' sound // WWAudioClass::Get_Instance()->Create_Instant_Sound( Get_DoorPhysDef()->AccessDeniedSoundDefID, Get_Transform() ); } break; } State = new_state; retval = true; } return retval; } enum { DOOR_OPEN_OK = 0, DOOR_OPEN_LOCKED, DOOR_OPEN_NOONE }; void DoorPhysClass::Update_State( float dt ) { switch ( State ) { case STATE_OPENING_DOOR: { // // Has the door finished opening? // if (AnimManager.Get_Current_Frame() == AnimManager.Peek_Animation()->Get_Num_Frames() - 1) { Set_State( STATE_OPENED_DOOR ); } } break; case STATE_OPENED_DOOR: { // // Has the door been in 'opened' state long enough? // // Client shouldn't be able to close his own door unless he hears from the server. ST - 1/15/2002 12:58PM // if ( CombatManager::I_Am_Server() ) { Timer -= dt; if ( Timer < 0 && LockState == false && OpenRequestPending == false && Can_Open_Door() != DOOR_OPEN_OK) { // // Switch to closing state // Set_State( STATE_CLOSING_DOOR ); } } } break; case STATE_CLOSING_DOOR: { // // Has the door finished closing? // if ( AnimManager.Get_Current_Frame() == 0 ) { Set_State( STATE_CLOSED_DOOR ); } } case STATE_CLOSED_DOOR: case STATE_ACCESS_DENIED: { // // We only do this if we are the server // if ( CombatManager::I_Am_Server() ) { // // Should we check to see if someone wants to open the door? // CheckTimer -= dt; if ( CheckTimer <= 0 ) { CheckTimer = 0.3f; // Check every 1/3 second // // Check to see if we should open the door // if ( LockState == false ) { bool update_clients = false; int result = Can_Open_Door(); if ( OpenRequestPending || result == DOOR_OPEN_OK ) { update_clients = Set_State( STATE_OPENING_DOOR ); } else if ( result == DOOR_OPEN_LOCKED ) { update_clients = Set_State( STATE_ACCESS_DENIED ); } else if ( State == STATE_ACCESS_DENIED ) { update_clients = Set_State( STATE_CLOSED_DOOR ); } // // If the server has decided on a new state, then // notify the clients // if ( update_clients ) { Enable_Is_State_Dirty( true ); } } } } } break; } return ; } void DoorPhysClass::Lock_Door_Open( bool onoff ) { if ( CombatManager::I_Am_Server() ) { // // Force the door open... // if ( onoff && Set_State( STATE_OPENING_DOOR ) ) { Enable_Is_State_Dirty( true ); } // // Lock the state // LockState = onoff; } return ; } int DoorPhysClass::Can_Open_Door( void ) { // // Transform both trigger zones into world space // OBBoxClass trigger_zone1; OBBoxClass trigger_zone2; OBBoxClass::Transform( Get_Transform(), Get_DoorPhysDef()->TriggerZone1, &trigger_zone1 ); OBBoxClass::Transform( Get_Transform(), Get_DoorPhysDef()->TriggerZone2, &trigger_zone2 ); // // Check the zone's to see if we should open the door // int result1 = Check_Door_Trigger( trigger_zone1 ); int result2 = DOOR_OPEN_NOONE; if (result1 != DOOR_OPEN_OK) { result2 = Check_Door_Trigger( trigger_zone2 ); } // // Now determine what information to return about the trigger check // int result = DOOR_OPEN_NOONE; if ( result1 == DOOR_OPEN_OK || result2 == DOOR_OPEN_OK ) { result = DOOR_OPEN_OK; } else if ( result1 == DOOR_OPEN_LOCKED || result2 == DOOR_OPEN_LOCKED ) { result = DOOR_OPEN_LOCKED; } return result; } int DoorPhysClass::Check_Door_Trigger( const OBBoxClass &trigger_zone ) { int result = DOOR_OPEN_NOONE; #if 0 // // Get the list of all the players in the world // SList *player_list = GameObjManager::Get_Star_Game_Obj_List(); // // Loop over each player // SLNode *objnode; for ( objnode = player_list->Head(); objnode; objnode = objnode->Next()) { SoldierGameObj *soldier = objnode->Data(); if ( soldier != NULL ) { // // Is this player inside the trigger zone? // Vector3 pos; soldier->Get_Position( &pos ); if ( CollisionMath::Overlap_Test( trigger_zone, pos ) == CollisionMath::INSIDE ) { // // Is the door locked? // if ( Get_DoorPhysDef()->LockCode != 0 ) { if ( soldier == COMBAT_STAR ) { Vector3 pos; soldier->Get_Position( &pos ); const char * desc = soldier->Has_Key( Get_DoorPhysDef()->LockCode ) ? "KEUS" : "KEDE"; DIAG_LOG(( desc, "%1.2f; %1.2f; %1.2f; %d; %d", pos.X, pos.Y, pos.Z, Get_ID(), Get_DoorPhysDef()->LockCode )); } // // Can the soldier unlock this door? // if ( soldier->Has_Key( Get_DoorPhysDef()->LockCode ) ) { result = DOOR_OPEN_OK; break; } else { result = DOOR_OPEN_LOCKED; } } else { result = DOOR_OPEN_OK; break; } } } } #else NonRefPhysListClass obj_list; WWASSERT(PhysicsSceneClass::Get_Instance() != NULL); PhysicsSceneClass::Get_Instance()->Collect_Objects(trigger_zone,false,true,&obj_list); NonRefPhysListIterator it(&obj_list); while (!it.Is_Done() && result == DOOR_OPEN_NOONE) { CombatPhysObserverClass * observer = (CombatPhysObserverClass *)it.Peek_Obj()->Get_Observer(); //WWASSERT(observer != NULL); //if (observer->As_PhysicalGameObj()) { if (observer != NULL && observer->As_PhysicalGameObj()) { SoldierGameObj * soldier = observer->As_PhysicalGameObj()->As_SoldierGameObj(); VehicleGameObj * vehicle = observer->As_PhysicalGameObj()->As_VehicleGameObj(); // // Check if this is a soldier who can open the door // if ((soldier != NULL) && soldier->Is_Human_Controlled()) { WWASSERT(Get_DoorPhysDef() != NULL); if ( Get_DoorPhysDef()->LockCode == 0 ) { result = DOOR_OPEN_OK; } else { if (Can_Unlock_Me(soldier)) { result = DOOR_OPEN_OK; break; } else { result = DOOR_OPEN_LOCKED; } } } // // Check if this is a vehicle that is allowed to open the door // WWASSERT(Get_DoorPhysDef() != NULL); if ((vehicle != NULL) && (Get_DoorPhysDef()->DoorOpensForVehicles)) { if ( Get_DoorPhysDef()->LockCode == 0 ) { result = DOOR_OPEN_OK; } else { SoldierGameObj * driver = vehicle->Get_Driver(); if ((driver != NULL) && (Can_Unlock_Me(driver))) { result = DOOR_OPEN_OK; break; } else { result = DOOR_OPEN_LOCKED; } } } } it.Next(); } #endif return result; } bool DoorPhysClass::Can_Unlock_Me(SoldierGameObj * soldier) const { if ( soldier == COMBAT_STAR ) { Vector3 pos; soldier->Get_Position( &pos ); const char * desc = soldier->Has_Key( Get_DoorPhysDef()->LockCode ) ? "KEUS" : "KEDE"; DIAG_LOG(( desc, "%1.2f; %1.2f; %1.2f; %d; %d", pos.X, pos.Y, pos.Z, Get_ID(), Get_DoorPhysDef()->LockCode )); } // // Can the soldier unlock this door? // return (Get_DoorPhysDef()->LockCode == 0) || (soldier->Has_Key( Get_DoorPhysDef()->LockCode )); } void DoorPhysClass::Set_Precision(void) { cEncoderList::Set_Precision( BITPACK_DOOR_STATE, 0, (int)STATE_MAX ); return ; } bool DoorPhysClass::Is_Door_Open( void ) const { return (State == STATE_OPENED_DOOR); }