#include "../idlib/precompiled.h" #pragma hdrstop #include "Game_local.h" #define RECORDRATE 300 //record every X ms #define RECORD_LAUNCHAIM 1 #define RECORD_UNSPAWN 2 #define RECORD_SPAWN 3 const idEventDef EV_recordclear( "recordclear", "d" ); const idEventDef EV_recordstart( "recordstart", "dd" ); const idEventDef EV_recordstop( "recordstop" ); const idEventDef EV_recordplay( "recordplay", "d" ); const idEventDef EV_geteventcount( "geteventcount", "d", 'd' ); const idEventDef EV_getlasteventtimestamp( "getlasteventtimestamp", "d", 'd' ); const idEventDef EV_setrecordstarttime( "setrecordstarttime" ); const idEventDef EV_moveplayertoghost( "moveplayertoghost", "d" ); const idEventDef EV_setcomplete( "setcomplete", "dd" ); const idEventDef EV_getcomplete( "getcomplete", "d", 'd' ); const idEventDef EV_recordspawn( "recordspawn", "sdvf" ); const idEventDef EV_recordunspawn( "recordunspawn", "d" ); const idEventDef EV_recordlaunchaim( "recordlaunchaim", "dvf" ); const idEventDef EV_reset( "worldreset" ); CLASS_DECLARATION( idEntity, idWorldManager ) EVENT( EV_recordstart, idWorldManager::Event_recordstart) EVENT( EV_recordstop, idWorldManager::Event_recordstop) EVENT( EV_recordplay, idWorldManager::Event_recordplay) EVENT( EV_reset, idWorldManager::Event_reset) EVENT( EV_geteventcount, idWorldManager::Event_geteventcount) EVENT( EV_getlasteventtimestamp, idWorldManager::Event_getlasteventtimestamp) EVENT( EV_setrecordstarttime, idWorldManager::Event_setrecordstarttime) EVENT( EV_moveplayertoghost, idWorldManager::Event_moveplayertoghost) EVENT( EV_recordclear, idWorldManager::Event_recordclear) EVENT( EV_recordspawn, idWorldManager::RecordSpawn) EVENT( EV_recordunspawn, idWorldManager::RecordUnspawn) EVENT( EV_recordlaunchaim, idWorldManager::RecordLaunchAim) EVENT( EV_setcomplete, idWorldManager::Event_setcomplete) EVENT( EV_getcomplete, idWorldManager::Event_getcomplete) END_CLASS void idWorldManager::Save( idSaveGame *savefile ) const { savefile->WriteInt(index); savefile->WriteInt(state); savefile->WriteInt(recordStarttime); savefile->WriteInt(nextRecordtime); savefile->WriteVec3(lastPosition); savefile->WriteFloat(lastAngle); //RECORDS. for (int i = 0; i < 3; i++) { savefile->WriteObject(records[i].ghost); savefile->WriteInt(records[i].index); savefile->WriteBool(records[i].playing); savefile->WriteBool(records[i].completed); int totalEvents = records[i].events.Num(); savefile->WriteInt(totalEvents); //how many events. for (int k = 0; k < totalEvents; k++) { savefile->WriteInt(records[i].events[k].timestamp); savefile->WriteVec3(records[i].events[k].position); savefile->WriteFloat(records[i].events[k].yaw); savefile->WriteString(records[i].events[k].deckcommand.c_str()); savefile->WriteString(records[i].events[k].frobcommand.c_str()); savefile->WriteString(records[i].events[k].spawncommand.c_str()); savefile->WriteBool(records[i].events[k].done); savefile->WriteInt(records[i].events[k].entID); savefile->WriteInt(records[i].events[k].recordtype); } } //RECORDLINES for (int i = 0; i < 3; i++) { int totalLines = recordlines[i].lines.Num(); savefile->WriteInt(totalLines); for (int k = 0; k < totalLines; k++) { savefile->WriteVec3(recordlines[i].lines[k].position1); savefile->WriteVec3(recordlines[i].lines[k].position2); } } } void idWorldManager::Restore( idRestoreGame *savefile ) { savefile->ReadInt(index); savefile->ReadInt(state); savefile->ReadInt(recordStarttime); savefile->ReadInt(nextRecordtime); savefile->ReadVec3(lastPosition); savefile->ReadFloat(lastAngle); //RECORDS. for (int i = 0; i < 3; i++) { savefile->ReadObject(reinterpret_cast( records[i].ghost ) ); savefile->ReadInt(records[i].index); savefile->ReadBool(records[i].playing); savefile->ReadBool(records[i].completed); int totalEvents; savefile->ReadInt(totalEvents); for (int k = 0; k < totalEvents; k++) { record_t newEvent; savefile->ReadInt(newEvent.timestamp); savefile->ReadVec3(newEvent.position); savefile->ReadFloat(newEvent.yaw); savefile->ReadString(newEvent.deckcommand); savefile->ReadString(newEvent.frobcommand); savefile->ReadString(newEvent.spawncommand); savefile->ReadBool(newEvent.done); savefile->ReadInt(newEvent.entID); savefile->ReadInt(newEvent.recordtype); records[i].events.Append(newEvent); } } //RECORDLINES for (int i = 0; i < 3; i++) { recordlines[i].lines.Clear(); int totalLines; savefile->ReadInt(totalLines); for (int k = 0; k < totalLines; k++) { recordline_t newLine; savefile->ReadVec3(newLine.position1); savefile->ReadVec3(newLine.position2); recordlines[i].lines.Append(newLine); } } } void idWorldManager::Spawn( void ) { int i; for (i = 0; i < 3; i++) { idDict args; records[i].index = 0; records[i].playing = false; records[i].completed = false; args.Clear(); args.Set( "model", "models/monster_npc/tris.ase" ); args.SetInt( "solid", 0 ); args.Set("skin", "skins/npc/ghost2"); records[i].ghost = ( idMover * )gameLocal.SpawnEntityType( idMover::Type, &args ); records[i].ghost->Hide(); recordlines[i].lines.Clear(); } lastPosition = vec3_zero; lastAngle = 0; nextRecordtime = 0; recordStarttime = 0; index = 0; state = OFF; BecomeActive( TH_THINK ); } void idWorldManager::Event_getcomplete(int idx) { idThread::ReturnInt(records[idx].completed); } void idWorldManager::Event_setcomplete(int idx, bool value) { records[idx].completed = value; } void idWorldManager::Event_moveplayertoghost(int idx) { //common->Printf("ghost %f %f %f\n", records[idx].ghost->GetPhysics()->GetOrigin().x, records[idx].ghost->GetPhysics()->GetOrigin().y, records[idx].ghost->GetPhysics()->GetOrigin().z); gameLocal.GetLocalPlayer()->SetOrigin( records[idx].ghost->GetPhysics()->GetOrigin() + idVec3(0,0,0) ); } void idWorldManager::Event_recordclear(int idx) { if (idx < 0) { return; } //erase all events. records[idx].events.Clear(); } void idWorldManager::Event_setrecordstarttime() { recordStarttime = gameLocal.time; } void idWorldManager::Event_getlasteventtimestamp(int idx) { int indexOfLastEvent = records[idx].events.Num(); if (indexOfLastEvent <= 0) { idThread::ReturnInt(0); return; } int time = records[idx].events[indexOfLastEvent - 1].timestamp; time += gameLocal.time; idThread::ReturnFloat(time / 1000.0f); } void idWorldManager::Event_geteventcount(int idx) { idThread::ReturnInt( records[idx].events.Num() ); } void idWorldManager::Event_reset(void) { int i; for ( i = 0; i < gameLocal.num_entities; i++ ) { if ( !gameLocal.entities[ i ] ) continue; if (gameLocal.entities[i]->IsType(idMoveableItem::Type)) { gameLocal.entities[i]->GetPhysics()->PutToRest(); gameLocal.entities[i]->SetOrigin(static_cast( gameLocal.entities[i] )->originalPosition); gameLocal.entities[i]->SetAxis(static_cast( gameLocal.entities[i] )->originalAngle); if (static_cast( gameLocal.entities[i] )->frozen == 2) { static_cast( gameLocal.entities[i] )->frozen = 1; } } else if (gameLocal.entities[i]->IsType(idLever::Type)) { static_cast( gameLocal.entities[i] )->Reset(); } else if (gameLocal.entities[i]->IsType(idMoveable::Type)) { gameLocal.entities[i]->GetPhysics()->PutToRest(); gameLocal.entities[i]->SetOrigin(static_cast( gameLocal.entities[i] )->originalPosition); gameLocal.entities[i]->SetAxis(static_cast( gameLocal.entities[i] )->originalAngle); gameLocal.entities[i]->GetPhysics()->PutToRest(); } else if (gameLocal.entities[i]->IsType(idMover::Type)) { //gameLocal.entities[i]->GetPhysics()->PutToRest(); if (gameLocal.entities[i]->spawnArgs.GetInt("resetmover", "0") <= 0) { continue; } static_cast( gameLocal.entities[i] )->Event_StopMoving(); static_cast( gameLocal.entities[i] )->Event_StopRotating(); if ( gameLocal.entities[i]->GetBindMaster() == NULL ) { gameLocal.entities[i]->SetOrigin( static_cast(gameLocal.entities[i] )->originalPosition ); } else { if (gameLocal.entities[i]->GetBindMaster()->IsType(idMover::Type)) { idVec3 masterPos = GetLocalCoordinates( static_cast(gameLocal.entities[i]->GetBindMaster())->originalPosition ); gameLocal.entities[i]->SetOrigin( static_cast(gameLocal.entities[i] )->originalPosition - masterPos); } else { idVec3 orgPos = GetLocalCoordinates( static_cast(gameLocal.entities[i])->originalPosition ); gameLocal.entities[i]->SetOrigin( orgPos ); } } gameLocal.entities[i]->SetAxis(static_cast( gameLocal.entities[i] )->originalAngle); } else if (gameLocal.entities[i]->IsType(idTrembler::Type)) { static_cast( gameLocal.entities[i] )->Reset(); } } //popcorn reset. gameLocal.GetLocalPlayer()->popcornPosition = vec3_zero; } void idWorldManager::Event_recordstart( int idx, int continuation ) { lastPosition = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); if (!continuation) { recordStarttime = gameLocal.time; //reset the record lines. if (idx >= 0) { recordlines[idx].lines.Clear(); } } this->index = idx; if (idx >= 0) { state = RECORDING; records[index].ghost->Hide(); if (!continuation) { records[index].events.Clear(); } } //worldmanager_moveable int i; for ( i = 0; i < gameLocal.num_entities; i++ ) { if ( !gameLocal.entities[ i ] ) continue; if (!gameLocal.entities[i]->IsType(idWorldManager_Moveable::Type)) continue; int role = gameLocal.entities[i]->spawnArgs.GetInt("role", "-1"); if (role != index) continue; static_cast( gameLocal.entities[i])->Event_startrecord( continuation ); } } void idWorldManager::Event_recordstop( void ) { int i; state = OFF; this->index = 0; for ( i = 0; i < gameLocal.num_entities; i++ ) { if ( !gameLocal.entities[ i ] ) continue; if (!gameLocal.entities[i]->IsType(idWorldManager_Moveable::Type)) continue; static_cast( gameLocal.entities[i])->Event_stop(); } } void idWorldManager::Event_recordplay( int idx ) { int i; if (records[idx].events.Num() <= 0) { return; } records[idx].index = 0; records[idx].playing = true; records[idx].ghost->Show(); records[idx].ghost->SetSkin(declManager->FindSkin( this->spawnArgs.GetString(va("skin%d", idx), "skins/npc/ghost2") )); //flag every event as NOT DONE. for (i = 0; i < records[idx].events.Num(); i++) { records[idx].events[i].done = false; } //the colored line that the operative poops out. if (index == -1) { recordline_t newLine; newLine.position1 = records[idx].events[0].position; newLine.position2 = records[idx].events[0].position; recordlines[idx].lines.Append(newLine); } for ( i = 0; i < gameLocal.num_entities; i++ ) { if ( !gameLocal.entities[ i ] ) continue; if (!gameLocal.entities[i]->IsType(idWorldManager_Moveable::Type)) continue; int role = gameLocal.entities[i]->spawnArgs.GetInt("role", "-1"); if (role == index) continue; static_cast( gameLocal.entities[i])->Event_startplay(); } } void idWorldManager::RecordDeck( const char *deckString ) { if (state == OFF) { return; } record_t newEvent; newEvent.timestamp = gameLocal.time - recordStarttime; newEvent.deckcommand = deckString; records[index].events.Append( newEvent ); } void idWorldManager::RecordFrob( const char *entName ) { if (state == OFF) { return; } record_t newEvent; newEvent.timestamp = gameLocal.time - recordStarttime; newEvent.frobcommand = entName; records[index].events.Append( newEvent ); } void idWorldManager::RecordUnspawn( int id ) { if (state == OFF) { return; } record_t newEvent; newEvent.recordtype = RECORD_UNSPAWN; newEvent.timestamp = gameLocal.time - recordStarttime; newEvent.entID = id; records[index].events.Append( newEvent ); } void idWorldManager::RecordSpawn( const char *defName, int id, const idVec3 &pos, float yaw ) { if (state == OFF) { return; } record_t newEvent; newEvent.recordtype = RECORD_SPAWN; newEvent.timestamp = gameLocal.time - recordStarttime; newEvent.spawncommand = defName; newEvent.position = pos; newEvent.yaw = yaw; newEvent.entID = id; records[index].events.Append( newEvent ); if (developer.GetBool()) { common->Printf("worldmanager: recording entity spawn ID %d defname %s \n", id, defName); } } void idWorldManager::RecordLaunchAim( int id, const idVec3 &dir, float force ) { if (state == OFF) { return; } if (id < 0) return; record_t newEvent; newEvent.timestamp = gameLocal.time - recordStarttime; newEvent.recordtype = RECORD_LAUNCHAIM; newEvent.entID = id; //id # newEvent.position = dir; //direction. newEvent.yaw = force; //force. records[index].events.Append( newEvent ); if (developer.GetBool()) { common->Printf("worldmanager: recording launcher aim ID %d \n", id); } } void idWorldManager::UpdatePlay( int idx ) { int currentIndex = records[idx].index; if (currentIndex > records[idx].events.Num() - 1) { return; } if (records[idx].events[currentIndex].timestamp < gameLocal.time - recordStarttime) { if (!records[idx].events[currentIndex].frobcommand.IsEmpty() && !records[idx].events[currentIndex].done) { //FROB EVENT. idEntity *frobEnt; frobEnt = gameLocal.FindEntity( records[idx].events[currentIndex].frobcommand ); if (frobEnt) { if (frobEnt->IsType(idTrigger_Multi::Type)) { static_cast( frobEnt )->TriggerAction(NULL); } else { gameLocal.GetLocalPlayer()->DoFrob(frobEnt); //common->Printf("frobbing! %d\n", gameLocal.time); } } else { gameLocal.Warning("WorldManager couldn't find frob ent: %s\n", gameLocal.FindEntity( records[idx].events[currentIndex].frobcommand.c_str())); } records[idx].events[currentIndex].done = true; } else if (records[idx].events[currentIndex].recordtype == RECORD_LAUNCHAIM && !records[idx].events[currentIndex].done) { //event: change launcher aim. //find the launcher with this id. int i; int targetEnt = -1; for ( i = 0; i < gameLocal.num_entities; i++ ) { if ( !gameLocal.entities[ i ] ) continue; int kk = records[idx].events[currentIndex].entID; if (gameLocal.entities[i]->spawnArgs.GetInt("id") != records[idx].events[currentIndex].entID) { continue; } targetEnt = i; break; } if (developer.GetBool()) { common->Printf("worldmanager: aiming launcher entity ID %d \n", records[idx].events[currentIndex].entID); } if (targetEnt >= 0) { //legacy. gameLocal.entities[targetEnt]->spawnArgs.SetVector("new_launchdir", records[idx].events[currentIndex].position); gameLocal.entities[targetEnt]->spawnArgs.SetInt("new_force", records[idx].events[currentIndex].yaw); gameLocal.entities[targetEnt]->spawnArgs.SetInt("new_update", 1); static_cast( gameLocal.entities[i] )->SetLaunchDir(records[idx].events[currentIndex].position, records[idx].events[currentIndex].yaw); } records[idx].events[currentIndex].done = true; } else if (!records[idx].events[currentIndex].deckcommand.IsEmpty() && !records[idx].events[currentIndex].done) { //DECK EVENT. gameLocal.RunDeckCommand(records[idx].events[currentIndex].deckcommand.c_str()); records[idx].events[currentIndex].done = true; } else if (records[idx].events[currentIndex].recordtype == RECORD_UNSPAWN && !records[idx].events[currentIndex].done) { //UNSPAWN EVENT. //find the ent with this id. int i; int targetEnt = -1; for ( i = 0; i < gameLocal.num_entities; i++ ) { if ( !gameLocal.entities[ i ] ) continue; int kk = records[idx].events[currentIndex].entID; if (gameLocal.entities[i]->spawnArgs.GetInt("id") != records[idx].events[currentIndex].entID) { continue; } targetEnt = i; break; } if (targetEnt >= 0) { gameLocal.entities[targetEnt]->PostEventMS( &EV_Remove, 0 ); //gameLocal.entities[targetEnt]->Event_CallFunction("onPick"); } records[idx].events[currentIndex].done = true; } else if (records[idx].events[currentIndex].recordtype == RECORD_SPAWN && !records[idx].events[currentIndex].done) { //SPAWN EVENT. idEntity *ent; idDict args; idAngles ang; int id = gameLocal.time - recordStarttime; if (developer.GetBool()) { common->Printf("worldmanager: spawning entity ID %d classname %s \n", records[idx].events[currentIndex].entID, records[idx].events[currentIndex].spawncommand.c_str()); } args.Set( "classname", records[idx].events[currentIndex].spawncommand ); args.SetVector( "origin", records[idx].events[currentIndex].position ); args.SetInt( "id", records[idx].events[currentIndex].entID); bool bSpawn = gameLocal.SpawnEntityDef( args, &ent ); if (ent) { ent->spawnArgs.SetInt("id", records[idx].events[currentIndex].entID); //BC 7-21-2016 was crashing here. ang.pitch = 0; ang.yaw = records[idx].events[currentIndex].yaw; ang.roll = 0; ent->SetAngles( ang ); } if (developer.GetBool()) { common->Printf("worldmanager: spawning success:%d\n", bSpawn); } records[idx].events[currentIndex].done = true; } else { //MOVE EVENT. float moveTime = (float)RECORDRATE / 1000; records[idx].ghost->Event_SetMoveTime(moveTime); records[idx].ghost->Event_MoveToPos(records[idx].events[currentIndex].position); idAngles newAng = idAngles(0, records[idx].events[currentIndex].yaw, 0); records[idx].ghost->SetAxis(newAng.ToMat3()); if (index == -1) { int lastLineIndex = recordlines[idx].lines.Num() - 1; recordline_t newLine; newLine.position1 = recordlines[idx].lines[lastLineIndex].position2; newLine.position2 = records[idx].events[currentIndex].position; recordlines[idx].lines.Append(newLine); } records[idx].events[currentIndex].done = true; } records[idx].index++; } } void idWorldManager::UpdateRecord( void ) { if (state == OFF) { return; } if (gameLocal.time < nextRecordtime) { return; } nextRecordtime = gameLocal.time + RECORDRATE; idVec3 currentPosition = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); int currentAngle = (int)gameLocal.GetLocalPlayer()->viewAngles.yaw; if ((int)lastPosition.x == (int)currentPosition.x && (int)lastPosition.y == (int)currentPosition.y && (int)lastPosition.z == (int)currentPosition.z && (int)lastAngle == (int)currentAngle) { return; } lastPosition = currentPosition; lastAngle = gameLocal.GetLocalPlayer()->viewAngles.yaw; record_t newEvent; newEvent.timestamp = gameLocal.time - recordStarttime; newEvent.position = currentPosition; newEvent.yaw = currentAngle; newEvent.deckcommand = NULL; newEvent.frobcommand = NULL; records[index].events.Append( newEvent ); } void idWorldManager::Think( void ) { int i; UpdateRecord(); for (i = 0; i < 3; i++) { idVec4 markerColor; idVec3 markerPos; int markerRadius = 24; if (records[i].playing) { UpdatePlay(i); } else { continue; } if (state == OFF && index != -1 /*caser*/) { continue; } if (this->index == i) { continue; } if (!records[i].ghost->IsHidden()) { if ((int)(gameLocal.time * 0.004) % 3 == 0) { markerRadius = 12; } switch (i) { case 0: markerColor = idVec4(.25, .75, 1, 1); break; //blue. case 1: markerColor = idVec4(.74, .9, .26, 1); break; //green. default: markerColor = idVec4(1, .6, 0, 1); break; //orange. } //blue .25, .75, 1 //yellow 1, .8, 0, 1 //red .9, .25, .25 markerPos = records[i].ghost->GetPhysics()->GetOrigin() + idVec3(0,0,85); gameRenderWorld->DebugLine( markerColor, markerPos + idVec3(0,0,-markerRadius), markerPos + idVec3(0,0,markerRadius) ); gameRenderWorld->DebugLine( markerColor, markerPos + idVec3(0,-markerRadius,0), markerPos + idVec3(0,markerRadius,0) ); gameRenderWorld->DebugLine( markerColor, markerPos + idVec3(-markerRadius,0,0), markerPos + idVec3(markerRadius,0,0) ); if (index == -1) { //draw lines. int k; if (recordlines[i].lines.Num() >= 3) { for (k = 0; k < recordlines[i].lines.Num() - 1; k++) { if (k > 2) //BC hack. The initial lines are garbage data. So, just skip them. { gameRenderWorld->DebugLine( markerColor, recordlines[i].lines[k].position1, recordlines[i].lines[k].position2 ); } } //This is the dangly bit that connects the last recordline position to the character model's feet. if (records[i].index < recordlines[i].lines.Num() && i > 1) { gameRenderWorld->DebugLine( markerColor, recordlines[i].lines[records[i].index].position1, records[i].ghost->GetPhysics()->GetOrigin() ); } } } } } }