Skip to content

Commit a614cf3

Browse files
committed
feat: allow camera cinematic mode outside demos
1 parent 1f10651 commit a614cf3

File tree

3 files changed

+69
-51
lines changed

3 files changed

+69
-51
lines changed

src/Features/Camera.cpp

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ Variable sar_cam_path_interp("sar_cam_path_interp", "2", 0, 2,
4747

4848
Variable sar_cam_path_draw("sar_cam_path_draw", "0", 0, 1, "Draws a representation of the camera path in the world. Disabled in cinematic mode.\n");
4949

50+
Variable sar_cam_path_sync_to_demo("sar_cam_path_sync_to_demo", "1", 0, 1,
51+
"If enabled, path will be synchronized to demo in cinematic mode.\n");
52+
5053
Variable cl_skip_player_render_in_main_view;
5154
Variable ss_force_primary_fullscreen;
5255

@@ -74,6 +77,16 @@ ON_EVENT(SAR_UNLOAD) {
7477
ResetCameraRelatedCvars();
7578
}
7679

80+
ON_EVENT(DEMO_START) {
81+
if (sar_cam_path_sync_to_demo.GetBool()) {
82+
camera->ActivatePath();
83+
}
84+
}
85+
86+
float Camera::GetCurrentPathTime() {
87+
return pathActive ? engine->GetClientTime() - timeOffset : 0.0f;
88+
}
89+
7790
//if in drive mode, checks if player wants to control the camera
7891
//for now it requires LMB input (as in demo drive mode)
7992
bool Camera::IsDriving() {
@@ -279,7 +292,7 @@ void Camera::DrawInWorld() const {
279292

280293
if (camera->states.size() < 2) return;
281294

282-
if (!(sv_cheats.GetBool() || engine->demoplayer->IsPlaying()) || !sar_cam_path_draw.GetBool() || sar_cam_control.GetInt() == 2) return;
295+
if (!camera->CanUseNonDefaultMode() || !sar_cam_path_draw.GetBool() || sar_cam_control.GetInt() == 2) return;
283296

284297
MeshId mesh_path = OverlayRender::createMesh(RenderCallback::none, RenderCallback::constant({ 255, 255, 255 }, true));
285298
MeshId mesh_cams = OverlayRender::createMesh(RenderCallback::none, RenderCallback::constant({ 255, 0, 0 }, true));
@@ -312,9 +325,7 @@ void Camera::DrawInWorld() const {
312325

313326
// draw fov things at each keyframe and the current one
314327
// the way this is done is rather sacrilegious
315-
316-
float currentTime = engine->GetClientTime() - timeOffset;
317-
CameraState currentCameraState = camera->InterpolateStates(currentTime);
328+
CameraState currentCameraState = camera->InterpolateStates(camera->GetCurrentPathTime());
318329

319330
std::vector<int> keyframeTicks(camera->states.size());
320331
int i = 0;
@@ -388,40 +399,18 @@ void Camera::OverrideView(ViewSetup *m_View) {
388399
}
389400

390401
if (timeOffsetRefreshRequested) {
391-
timeOffset = engine->GetClientTime() - engine->demoplayer->GetTick() * engine->GetIPT();
402+
timeOffset = engine->GetClientTime();
403+
if (IsSyncingPathToDemo()) {
404+
timeOffset -= engine->demoplayer->GetTick() * engine->GetIPT();
405+
}
392406
timeOffsetRefreshRequested = false;
393407
}
394408

395409
auto newControlType = static_cast<CameraControlType>(sar_cam_control.GetInt());
396410

397-
//don't allow cinematic mode outside of demo player
398-
if (!engine->demoplayer->IsPlaying() && newControlType == Cinematic) {
399-
if (controlType != Cinematic) {
400-
console->Print("Cinematic mode cannot be used outside of demo player.\n");
401-
} else {
402-
controlType = Default;
403-
ResetCameraRelatedCvars();
404-
}
405-
newControlType = controlType;
406-
sar_cam_control.SetValue(controlType);
407-
}
408-
409-
//don't allow drive mode when not using sv_cheats
410-
if (newControlType == Drive && !sv_cheats.GetBool() && !engine->demoplayer->IsPlaying()) {
411-
if (controlType != Drive) {
412-
console->Print("Drive mode requires sv_cheats 1 or demo player.\n");
413-
} else {
414-
controlType = Default;
415-
ResetCameraRelatedCvars();
416-
}
417-
newControlType = controlType;
418-
sar_cam_control.SetValue(controlType);
419-
}
420-
421-
//don't allow follow mode when not using sv_cheats
422-
if (newControlType == Follow && !sv_cheats.GetBool() && !engine->demoplayer->IsPlaying()) {
423-
if (controlType != Follow) {
424-
console->Print("Follow mode requires sv_cheats 1 or demo player.\n");
411+
if (newControlType != Default && !camera->CanUseNonDefaultMode()) {
412+
if (controlType == Default) {
413+
console->Print("Different camera modes require sv_cheats 1 or demo player.\n");
425414
} else {
426415
controlType = Default;
427416
ResetCameraRelatedCvars();
@@ -441,6 +430,9 @@ void Camera::OverrideView(ViewSetup *m_View) {
441430

442431
//handling camera control type switching
443432
if (newControlType != controlType) {
433+
if (newControlType == Cinematic && IsSyncingPathToDemo()) {
434+
this->ActivatePath();
435+
}
444436
if (controlType == Default && newControlType != Default) {
445437
//enabling
446438
if (newControlType == Follow)
@@ -454,6 +446,11 @@ void Camera::OverrideView(ViewSetup *m_View) {
454446
controlType = newControlType;
455447
}
456448

449+
// reset path active state if outside of cinematic mode
450+
if (pathActive && controlType != Cinematic) {
451+
pathActive = false;
452+
}
453+
457454
//don't do anything if not in game or demo player
458455
if (engine->hoststate->m_activeGame || engine->demoplayer->IsPlaying()) {
459456
if (controlType == Default || cameraRefreshRequested) {
@@ -545,8 +542,7 @@ void Camera::OverrideView(ViewSetup *m_View) {
545542
if (controlType == Cinematic) {
546543
//don't do interpolation when there are no points
547544
if (states.size() > 0) {
548-
float currentTime = engine->GetClientTime() - timeOffset;
549-
currentState = InterpolateStates(currentTime);
545+
currentState = InterpolateStates(GetCurrentPathTime());
550546
}
551547
}
552548
//applying custom view
@@ -579,8 +575,9 @@ void Camera::OverrideView(ViewSetup *m_View) {
579575
}
580576
}
581577

582-
void Camera::RequestTimeOffsetRefresh() {
578+
void Camera::ActivatePath() {
583579
timeOffsetRefreshRequested = true;
580+
pathActive = true;
584581
}
585582

586583
void Camera::RequestCameraRefresh() {
@@ -652,8 +649,11 @@ CON_COMMAND_F_COMPLETION(
652649
"sar_cam_path_setkf [frame] [x] [y] [z] [pitch] [yaw] [roll] [fov] - sets the camera path keyframe\n",
653650
0,
654651
AUTOCOMPLETION_FUNCTION(sar_cam_path_setkf)) {
655-
if (!engine->demoplayer->IsPlaying())
656-
return console->Print("Cinematic mode cannot be used outside of demo player.\n");
652+
653+
if (args.ArgC() == 1 && !engine->demoplayer->IsPlaying()) {
654+
console->Print("Frame has to be explicitly defined outside of demo player.\n");
655+
return;
656+
}
657657

658658
if (args.ArgC() >= 1 && args.ArgC() <= 9) {
659659
CameraState campos = camera->currentState;
@@ -713,8 +713,6 @@ CON_COMMAND_F_COMPLETION(
713713
"sar_cam_path_showkf <frame> - display information about camera path keyframe at specified frame\n",
714714
0,
715715
AUTOCOMPLETION_FUNCTION(sar_cam_path_showkf)) {
716-
if (!engine->demoplayer->IsPlaying())
717-
return console->Print("Cinematic mode cannot be used outside of demo player.\n");
718716

719717
if (args.ArgC() == 2) {
720718
int i = std::atoi(args[1]);
@@ -730,9 +728,6 @@ CON_COMMAND_F_COMPLETION(
730728
}
731729

732730
CON_COMMAND(sar_cam_path_getkfs, "sar_cam_path_getkfs - exports commands for recreating currently made camera path\n") {
733-
if (!engine->demoplayer->IsPlaying())
734-
return console->Print("Cinematic mode cannot be used outside of demo player.\n");
735-
736731
if (args.ArgC() == 1) {
737732
for (auto const &state : camera->states) {
738733
CameraState cam = state.second;
@@ -774,8 +769,6 @@ CON_COMMAND_F_COMPLETION(
774769
"sar_cam_path_remkf <frame> - removes camera path keyframe at specified frame\n",
775770
0,
776771
AUTOCOMPLETION_FUNCTION(sar_cam_path_remkf)) {
777-
if (!engine->demoplayer->IsPlaying())
778-
return console->Print("Cinematic mode cannot be used outside of demo player.\n");
779772

780773
if (args.ArgC() == 2) {
781774
int i = std::atoi(args[1]);
@@ -791,9 +784,6 @@ CON_COMMAND_F_COMPLETION(
791784
}
792785

793786
CON_COMMAND(sar_cam_path_remkfs, "sar_cam_path_remkfs - removes all camera path keyframes\n") {
794-
if (!engine->demoplayer->IsPlaying())
795-
return console->Print("Cinematic mode cannot be used outside of demo player.\n");
796-
797787
if (args.ArgC() == 1) {
798788
camera->states.clear();
799789
console->Print("All camera path keyframes have been removed.\n");
@@ -987,3 +977,28 @@ CON_COMMAND(sar_cam_reset, "sar_cam_reset - resets camera to its default positio
987977
return console->Print(sar_cam_reset.ThisPtr()->m_pszHelpString);
988978
}
989979
}
980+
981+
CON_COMMAND(sar_cam_path_start, "sar_cam_path_start - starts playback of predefined camera path. (requires camera Cinematic Mode)") {
982+
if (args.ArgC() != 1) {
983+
return console->Print(sar_cam_path_start.ThisPtr()->m_pszHelpString);
984+
}
985+
986+
if (camera->states.size() == 0) {
987+
return console->Print("No camera path has been defined.\n");
988+
}
989+
990+
if (engine->demoplayer->IsPlaying() && sar_cam_path_sync_to_demo.GetBool()) {
991+
return console->Print("Cannot restart the path. Camera path is synchronized to demo time. Turn off sar_cam_path_sync_to_demo.\n");
992+
}
993+
994+
if (camera->controlType != Cinematic) {
995+
if (!camera->CanUseNonDefaultMode()) {
996+
return console->Print("Camera path cannot be started - switching to cinematic mode is not possible.\n");
997+
}
998+
999+
console->Print("Camera has been switched to cinematic mode. You can switch it back with 'sar_cam_control' cvar.\n");
1000+
sar_cam_control.SetValue(CameraControlType::Cinematic);
1001+
}
1002+
1003+
camera->ActivatePath();
1004+
}

src/Features/Camera.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ extern Variable sar_cam_ortho;
1919
extern Variable sar_cam_ortho_scale;
2020
extern Variable sar_cam_ortho_nearz;
2121
extern Variable sar_cam_path_interp;
22+
extern Variable sar_cam_path_sync_to_demo;
2223

2324
extern Variable cl_skip_player_render_in_main_view;
2425
extern Variable ss_force_primary_fullscreen;
@@ -59,6 +60,7 @@ class Camera : public Feature {
5960
bool manualActive = false;
6061
bool cameraRefreshRequested = false;
6162
bool timeOffsetRefreshRequested = true;
63+
bool pathActive = false;
6264
int mouseHoldPos[2] = {0, 0};
6365
float timeOffset = 0.0;
6466

@@ -69,11 +71,14 @@ class Camera : public Feature {
6971
std::map<int, CameraState> states;
7072
Camera();
7173
~Camera();
74+
float GetCurrentPathTime();
75+
bool IsSyncingPathToDemo() const { return sar_cam_path_sync_to_demo.GetBool() && engine->demoplayer->IsPlaying(); }
76+
bool CanUseNonDefaultMode() const { return sv_cheats.GetBool() || engine->demoplayer->IsPlaying(); }
7277
bool IsDriving();
7378
void OverrideView(ViewSetup *m_View);
7479
CameraState InterpolateStates(float time);
7580
void DrawInWorld() const;
76-
void RequestTimeOffsetRefresh();
81+
void ActivatePath();
7782
void RequestCameraRefresh();
7883
void OverrideMovement(CUserCmd *cmd);
7984

src/Modules/EngineDemoPlayer.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,6 @@ DETOUR(EngineDemoPlayer::StartPlayback, const char *filename, bool bAsTimeDemo)
260260
}
261261
}
262262

263-
camera->RequestTimeOffsetRefresh();
264-
265263
Renderer::isDemoLoading = true;
266264

267265
engine->demoplayer->replayName = newFilename;

0 commit comments

Comments
 (0)