diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a4099f965..c46dbf3ce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -157,6 +157,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_dialogue.cpp k_tally.cpp k_bans.cpp + k_endcam.cpp music.cpp music_manager.cpp ) diff --git a/src/k_endcam.cpp b/src/k_endcam.cpp new file mode 100644 index 000000000..91f87d35b --- /dev/null +++ b/src/k_endcam.cpp @@ -0,0 +1,197 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include + +#include "archive_wrapper.hpp" + +#include "byteptr.h" +#include "doomdef.h" +#include "doomtype.h" +#include "g_game.h" +#include "k_endcam.h" +#include "m_easing.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_saveg.h" +#include "p_tick.h" +#include "r_fps.h" +#include "r_main.h" + +endcam_t g_endcam; + +namespace +{ + +fixed_t interval(tic_t t, tic_t d) +{ + return (std::min(t, d) * FRACUNIT) / std::max(d, 1u); +} + +fixed_t interval(tic_t t, tic_t s, tic_t d) +{ + return interval(std::max(t, s) - s, d); +} + +INT32 lerp(fixed_t f, INT32 a, INT32 b) +{ + return a + FixedMul(f, b - a); +} + +struct Camera : camera_t +{ + void pos(const vector3_t& p) + { + x = p.x; + y = p.y; + z = p.z; + subsector = R_PointInSubsector(x, y); + } +}; + +struct EndCam : endcam_t +{ + tic_t Time() const { return leveltime - begin; } + bool Freezing() const { return Time() <= swirlDuration; } + + void GC() + { + if (P_MobjWasRemoved(panMobj)) + { + P_SetTarget(&panMobj, nullptr); + } + } + + void Start() + { + active = true; + begin = leveltime; + + // Reset all viewpoints + for (int i = 0; i < MAXSPLITSCREENPLAYERS; ++i) + { + Move(static_cast(camera[i])); + } + + R_ResetViewInterpolation(0); + } + + void Move(Camera& cam) + { + tic_t t = Time(); + fixed_t pan = Easing_OutQuint(interval(t, swirlDuration, panDuration), FRACUNIT, 0); + + auto aim = [&](vector3_t& p, vector3_t& q) + { + cam.aiming = lerp(pan, cam.aiming, R_PointToAngle2(0, p.z, FixedHypot(q.x - p.x, q.y - p.y), q.z)); + cam.pos(p); + }; + + if (t <= swirlDuration) + { + fixed_t swirl = interval(t, swirlDuration); + + angle_t ang = FixedAngle(swirl < FRACUNIT/2 ? + Easing_InOutQuint(swirl, startAngle, endAngle) : + Easing_InOutQuad(swirl, startAngle, endAngle)); + + fixed_t hDist = Easing_OutQuad(swirl, startRadius.x, endRadius.x); + + vector3_t p = { + origin.x - FixedMul(FCOS(ang), hDist), + origin.y - FixedMul(FSIN(ang), hDist), + origin.z + Easing_OutQuad(swirl, startRadius.y, endRadius.y) + }; + + aim(p, origin); + cam.angle = ang; + } + else if (!P_MobjWasRemoved(panMobj)) + { + vector3_t q = {panMobj->x, panMobj->y, P_GetMobjHead(panMobj)}; + vector3_t p = {cam.x, cam.y, cam.z}; + Follow(FixedMul(pan, panSpeed), p, q); // modifies p + + aim(p, q); + cam.angle = lerp( + Easing_Linear(pan, FRACUNIT/4, FRACUNIT), + cam.angle, + R_PointToAngle2(p.x, p.y, q.x, q.y) + ); + } + + } + + template + void Archive(T&& ar) + { + static_assert(srb2::is_archive_wrapper_v); +#define X(T, var) SRB2_ARCHIVE_WRAPPER_CALL(ar, T, var) + X(vector3_t, origin); + X(vector2_t, startRadius); + X(vector2_t, endRadius); + X(tic_t, swirlDuration); + X(fixed_t, startAngle); + X(fixed_t, endAngle); + // panMobj is handled in p_saveg.c + X(tic_t, panDuration); + X(fixed_t, panSpeed); + X(bool, active); + X(tic_t, begin); +#undef X + } + +private: + void Follow(fixed_t f, vector3_t& p, vector3_t q) const + { + FV3_Sub(&q, &p); + q.x = FixedMul(q.x, f); + q.y = FixedMul(q.y, f); + q.z = FixedMul(q.z, f); + + FV3_Add(&p, &q); + } +}; + +EndCam& endcam_cast() +{ + return static_cast(g_endcam); +} + +}; // namespace + +void K_CommitEndCamera(void) +{ + endcam_cast().Start(); +} + +void K_MoveEndCamera(camera_t *thiscam) +{ + endcam_cast().Move(static_cast(*thiscam)); +} + +void K_EndCameraGC(void) +{ + endcam_cast().GC(); +} + +boolean K_EndCameraIsFreezing(void) +{ + return endcam_cast().Freezing(); +} + +void K_SaveEndCamera(savebuffer_t *save) +{ + endcam_cast().Archive(srb2::ArchiveWrapper(save)); +} + +void K_LoadEndCamera(savebuffer_t *save) +{ + endcam_cast().Archive(srb2::UnArchiveWrapper(save)); +} diff --git a/src/k_endcam.h b/src/k_endcam.h new file mode 100644 index 000000000..e9ea3ed7a --- /dev/null +++ b/src/k_endcam.h @@ -0,0 +1,74 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef k_endcam_h +#define k_endcam_h + +#include "typedef.h" + +#include "doomtype.h" +#include "m_fixed.h" +#include "tables.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct endcam_t +{ + // + // Configurable properties + // + + vector3_t origin; // center point + vector2_t startRadius; // X = horizontal, Y = vertical + vector2_t endRadius; + + tic_t swirlDuration; + fixed_t startAngle; // 180*FRACUNIT, NOT ANGLE_180 + fixed_t endAngle; + + // 1) Camera pans vertically to keep this object centered + // 2) After swirling ends, pan horizontally too + mobj_t *panMobj; + tic_t panDuration; // dropoff after swirling ends + fixed_t panSpeed; // 0-FRACUNIT + + /// ... + + // You should not set these yourself. + // Use K_CommitEndCamera. + boolean active; + tic_t begin; // leveltime +}; + +extern endcam_t g_endcam; + +// Sets endcam_t.active and endcam_t.begin. +// +// VERY IMPORTANT: +// +// Set the OTHER fields in endcam_t BEFORE calling this +// function, so the camera can cut away cleanly. +void K_CommitEndCamera(void); + +/// ... + +// Low-level functions +void K_MoveEndCamera(camera_t *thiscam); +void K_EndCameraGC(void); +boolean K_EndCameraIsFreezing(void); +void K_SaveEndCamera(savebuffer_t *save); +void K_LoadEndCamera(savebuffer_t *save); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif/*k_endcam_h*/ diff --git a/src/p_saveg.c b/src/p_saveg.c index 64659573a..2cf06d1e2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -47,6 +47,7 @@ #include "g_party.h" #include "k_vote.h" #include "k_zvote.h" +#include "k_endcam.h" #include @@ -5745,6 +5746,12 @@ static void P_RelinkPointers(void) P_LoadMobjPointers(RelinkMobjVoid); + if (g_endcam.panMobj) + { + if (!RelinkMobj(&g_endcam.panMobj)) + CONS_Debug(DBG_GAMELOGIC, "g_endcam.panMobj not found\n"); + } + // use info field (value = oldposition) to relink mobjs for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; currentthinker = currentthinker->next) @@ -6868,6 +6875,9 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) } } + K_SaveEndCamera(save); + WriteMobjPointer(g_endcam.panMobj); + P_NetArchivePlayers(save); P_NetArchiveParties(save); P_NetArchiveRoundQueue(save); @@ -6933,6 +6943,9 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) if (!P_NetUnArchiveMisc(save, reloading)) return false; + K_LoadEndCamera(save); + ReadMobjPointer(&g_endcam.panMobj); + P_NetUnArchivePlayers(save); P_NetUnArchiveParties(save); P_NetUnArchiveRoundQueue(save); diff --git a/src/p_setup.cpp b/src/p_setup.cpp index aaa9389c2..f70f552ca 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -115,6 +115,7 @@ #include "music.h" #include "k_dialogue.h" #include "k_hud.h" // K_ClearPersistentMessages +#include "k_endcam.h" // Replay names have time #if !defined (UNDER_CE) @@ -7696,6 +7697,8 @@ static void P_InitLevelSettings(void) K_ResetSpecialStage(); K_ResetBossInfo(); + + memset(&g_endcam, 0, sizeof g_endcam); } #if 0 diff --git a/src/p_tick.c b/src/p_tick.c index c8bb7c4ec..309979cab 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -48,6 +48,7 @@ #include "k_dialogue.h" #include "m_easing.h" #include "k_hud.h" // messagetimer +#include "k_endcam.h" #include "lua_profile.h" @@ -65,12 +66,12 @@ static boolean g_freezeLevel; boolean P_LevelIsFrozen(void) { - return (g_freezeLevel || g_freezeCheat); + return (g_freezeLevel || g_freezeCheat || K_EndCameraIsFreezing()); } boolean P_FreezeCheat(void) { - return (g_freezeLevel || g_freezeCheat); + return (g_freezeLevel || g_freezeCheat || K_EndCameraIsFreezing()); } void P_SetFreezeCheat(boolean value) @@ -101,7 +102,7 @@ boolean P_MobjIsFrozen(mobj_t *mobj) } } - if (g_freezeLevel == true) + if (g_freezeLevel == true || K_EndCameraIsFreezing()) { // level totally frozen return true; @@ -767,6 +768,8 @@ void P_RunChaseCameras(void) P_MoveChaseCamera(&players[displayplayers[i]], &camera[i], false); } } + + K_EndCameraGC(); } static fixed_t P_GetDarkness(tic_t start, tic_t end) diff --git a/src/p_user.c b/src/p_user.c index 350ded74c..b9c1911f4 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -67,6 +67,7 @@ #include "music.h" #include "k_tally.h" #include "k_objects.h" +#include "k_endcam.h" #ifdef HWRENDER #include "hardware/hw_light.h" @@ -3062,6 +3063,11 @@ void P_ToggleDemoCamera(UINT8 viewnum) void P_ResetCamera(player_t *player, camera_t *thiscam) { + if (g_endcam.active) + { + return; + } + tic_t tries = 0; fixed_t x, y, z; @@ -3155,7 +3161,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall num = 0; } - if (thiscam->freecam || player->spectator) + if ((thiscam->freecam || player->spectator) && !g_endcam.active) { P_DemoCameraMovement(thiscam, num); return true; @@ -3164,6 +3170,12 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (paused || P_AutoPause()) return true; + if (g_endcam.active) + { + K_MoveEndCamera(thiscam); + return true; + } + playerScale = FixedDiv(player->mo->scale, mapobjectscale); scaleDiff = playerScale - FRACUNIT; diff --git a/src/typedef.h b/src/typedef.h index 77625d4cd..605103166 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -190,6 +190,9 @@ TYPEDEF (botcontroller_t); // k_brightmap.h TYPEDEF (brightmapStorage_t); +// k_endcam.h +TYPEDEF (endcam_t); + // k_follower.h TYPEDEF (follower_t); TYPEDEF (followercategory_t);