diff --git a/src/assets/ba_data/python/bascenev1/__init__.py b/src/assets/ba_data/python/bascenev1/__init__.py index e96deb0b..5f3bbd17 100644 --- a/src/assets/ba_data/python/bascenev1/__init__.py +++ b/src/assets/ba_data/python/bascenev1/__init__.py @@ -103,6 +103,7 @@ from _bascenev1 import ( host_scan_cycle, InputDevice, is_in_replay, + is_replay_paused, ls_input_devices, ls_objects, Material, @@ -112,11 +113,13 @@ from _bascenev1 import ( newactivity, newnode, Node, + pause_replay, printnodes, protocol_version, release_gamepad_input, release_keyboard_input, reset_random_player_names, + resume_replay, broadcastmessage, SessionData, SessionPlayer, @@ -352,6 +355,7 @@ __all__ = [ 'IntSetting', 'is_in_replay', 'is_point_in_box', + 'is_replay_paused', 'JoinActivity', 'Level', 'Lobby', @@ -374,6 +378,7 @@ __all__ = [ 'normalized_color', 'NotFoundError', 'OutOfBoundsMessage', + 'pause_replay', 'PickedUpMessage', 'PickUpMessage', 'Player', @@ -394,6 +399,7 @@ __all__ = [ 'release_gamepad_input', 'release_keyboard_input', 'reset_random_player_names', + 'resume_replay', 'safecolor', 'screenmessage', 'SceneV1AppMode', @@ -463,4 +469,4 @@ if __debug__: ' should not happen.', __name__, _mdl, - ) + ) \ No newline at end of file diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py index c9d26f96..362f5b55 100644 --- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py +++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py @@ -70,6 +70,7 @@ class MainMenuWindow(bui.Window): self._how_to_play_button: bui.Widget | None = None self._credits_button: bui.Widget | None = None self._settings_button: bui.Widget | None = None + self._pause_and_resume_image: bui.Widget | None = None self._next_refresh_allow_time = 0.0 self._store_char_tex = self._get_store_char_tex() @@ -431,13 +432,15 @@ class MainMenuWindow(bui.Window): # media players but this works for now). if bs.is_in_replay(): b_size = 50.0 - b_buffer = 10.0 + b_buffer_1 = 50.0 + b_buffer_2 = 10.0 t_scale = 0.75 assert bui.app.classic is not None uiscale = bui.app.ui_v1.uiscale if uiscale is bui.UIScale.SMALL: b_size *= 0.6 - b_buffer *= 1.0 + b_buffer_1 *= 0.8 + b_buffer_2 *= 1.0 v_offs = -40 t_scale = 0.5 elif uiscale is bui.UIScale.MEDIUM: @@ -467,8 +470,8 @@ class MainMenuWindow(bui.Window): btn = bui.buttonwidget( parent=self._root_widget, position=( - h - b_size - b_buffer, - v - b_size - b_buffer + v_offs, + h - b_size - b_buffer_1, + v - b_size - b_buffer_2 + v_offs, ), button_type='square', size=(b_size, b_size), @@ -481,8 +484,8 @@ class MainMenuWindow(bui.Window): draw_controller=btn, text='-', position=( - h - b_size * 0.5 - b_buffer, - v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs, + h - b_size * 0.5 - b_buffer_1, + v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs, ), h_align='center', v_align='center', @@ -491,7 +494,10 @@ class MainMenuWindow(bui.Window): ) btn = bui.buttonwidget( parent=self._root_widget, - position=(h + b_buffer, v - b_size - b_buffer + v_offs), + position=( + h + b_buffer_1, + v - b_size - b_buffer_2 + v_offs + ), button_type='square', size=(b_size, b_size), label='', @@ -503,14 +509,38 @@ class MainMenuWindow(bui.Window): draw_controller=btn, text='+', position=( - h + b_size * 0.5 + b_buffer, - v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs, + h + b_size * 0.5 + b_buffer_1, + v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs, ), h_align='center', v_align='center', size=(0, 0), scale=3.0 * t_scale, ) + btn = bui.buttonwidget( + parent=self._root_widget, + position=( + h - b_size * 0.5, + v - b_size - b_buffer_2 + v_offs + ), + button_type='square', + size=(b_size, b_size), + label='', + autoselect=True, + on_activate_call=bui.Call(self._pause_or_resume_replay), + ) + self._pause_and_resume_image = bui.imagewidget( + parent=self._root_widget, + size=(b_size, b_size), + draw_controller=btn, + position=( + h - b_size * 0.47, + v - b_size - b_buffer_2 + v_offs + ), + texture=bui.gettexture( + 'pauseIcon' if bs.is_replay_paused() else 'resumeIcon' + ), + ) def _refresh_not_in_game( self, positions: list[tuple[float, float, float]] @@ -1034,6 +1064,20 @@ class MainMenuWindow(bui.Window): ), ) + def _pause_or_resume_replay(self) -> None: + if bs.is_replay_paused(): + bs.resume_replay() + bui.imagewidget( + edit=self._pause_and_resume_image, + texture=bui.gettexture('resumeIcon'), + ) + else: + bs.pause_replay() + bui.imagewidget( + edit=self._pause_and_resume_image, + texture=bui.gettexture('pauseIcon'), + ) + def _quit(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.confirm import QuitWindow @@ -1376,4 +1420,4 @@ class MainMenuWindow(bui.Window): # If there's callbacks waiting for this window to go away, call them. for call in bui.app.ui_v1.main_menu_resume_callbacks: call() - del bui.app.ui_v1.main_menu_resume_callbacks[:] + del bui.app.ui_v1.main_menu_resume_callbacks[:] \ No newline at end of file diff --git a/src/ballistica/scene_v1/python/methods/python_methods_scene.cc b/src/ballistica/scene_v1/python/methods/python_methods_scene.cc index 9cf3846e..2ccf9f2b 100644 --- a/src/ballistica/scene_v1/python/methods/python_methods_scene.cc +++ b/src/ballistica/scene_v1/python/methods/python_methods_scene.cc @@ -1500,6 +1500,77 @@ static PyMethodDef PySetReplaySpeedExponentDef = { "Set replay speed. Actual displayed speed is pow(2, speed).", }; +// -------------------------- is_replay_paused --------------------------------- + +static auto PyIsReplayPaused(PyObject* self, PyObject* args) + -> PyObject* { + BA_PYTHON_TRY; + auto* appmode = SceneV1AppMode::GetActiveOrThrow(); + if (appmode->is_replay_paused()) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } + BA_PYTHON_CATCH; +} + +static PyMethodDef PyIsReplayPausedDef = { + "is_replay_paused", // name + PyIsReplayPaused, // method + METH_VARARGS, // flags + + "is_replay_paused() -> bool\n" + "\n" + "(internal)\n" + "\n" + "Returns if Replay is paused or not.", +}; +// ------------------------ pause_replay --------------------------------------- + +static auto PyPauseReplay(PyObject* self, PyObject* args) + -> PyObject* { + BA_PYTHON_TRY; + auto* appmode = SceneV1AppMode::GetActiveOrThrow(); + appmode->PauseReplay(); + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyPauseReplayDef = { + "pause_replay", // name + PyPauseReplay, // method + METH_VARARGS, // flags + + "pause_replay() -> None\n" + "\n" + "(internal)\n" + "\n" + "Pauses replay.", +}; + +// ------------------------ resume_replay -------------------------------------- + +static auto PyResumeReplay(PyObject* self, PyObject* args) + -> PyObject* { + BA_PYTHON_TRY; + auto* appmode = SceneV1AppMode::GetActiveOrThrow(); + appmode->ResumeReplay(); + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyResumeReplayDef = { + "resume_replay", // name + PyResumeReplay, // method + METH_VARARGS, // flags + + "resume_replay() -> None\n" + "\n" + "(internal)\n" + "\n" + "Resumes replay.", +}; + // ----------------------- reset_random_player_names --------------------------- static auto PyResetRandomPlayerNames(PyObject* self, PyObject* args, @@ -1777,6 +1848,9 @@ auto PythonMethodsScene::GetMethods() -> std::vector { PyResetRandomPlayerNamesDef, PySetReplaySpeedExponentDef, PyGetReplaySpeedExponentDef, + PyIsReplayPausedDef, + PyPauseReplayDef, + PyResumeReplayDef, PySetDebugSpeedExponentDef, PyGetGameRosterDef, PyGetForegroundHostActivityDef, diff --git a/src/ballistica/scene_v1/support/client_session.cc b/src/ballistica/scene_v1/support/client_session.cc index f796c64c..ded89653 100644 --- a/src/ballistica/scene_v1/support/client_session.cc +++ b/src/ballistica/scene_v1/support/client_session.cc @@ -174,6 +174,11 @@ void ClientSession::Update(int time_advance_millisecs, double time_advance) { if (shutting_down_) { return; } + if (auto* appmode = SceneV1AppMode::GetActiveOrThrow()) { + if (appmode->is_replay_paused()) { + return; + } + } // Allow replays to modulate speed, etc. // Also plug in our more exact time-advance here instead of the old int one. diff --git a/src/ballistica/scene_v1/support/client_session_replay.cc b/src/ballistica/scene_v1/support/client_session_replay.cc index 298bb420..780d3bd5 100644 --- a/src/ballistica/scene_v1/support/client_session_replay.cc +++ b/src/ballistica/scene_v1/support/client_session_replay.cc @@ -36,7 +36,7 @@ ClientSessionReplay::~ClientSessionReplay() { // we no longer are responsible for feeding clients to this device.. appmode->connections()->UnregisterClientController(this); - + appmode->ResumeReplay(); if (file_) { fclose(file_); file_ = nullptr; diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc index d61238bc..7b95934d 100644 --- a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc +++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc @@ -1223,6 +1223,14 @@ void SceneV1AppMode::SetReplaySpeedExponent(int val) { replay_speed_mult_ = powf(2.0f, static_cast(replay_speed_exponent_)); } +void SceneV1AppMode::PauseReplay() { + replay_paused_ = true; +} + +void SceneV1AppMode::ResumeReplay() { + replay_paused_ = false; +} + void SceneV1AppMode::SetDebugSpeedExponent(int val) { debug_speed_exponent_ = val; debug_speed_mult_ = powf(2.0f, static_cast(debug_speed_exponent_)); diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.h b/src/ballistica/scene_v1/support/scene_v1_app_mode.h index a3310535..0e47e4fd 100644 --- a/src/ballistica/scene_v1/support/scene_v1_app_mode.h +++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.h @@ -96,11 +96,14 @@ class SceneV1AppMode : public base::AppMode { auto debug_speed_mult() const -> float { return debug_speed_mult_; } auto replay_speed_exponent() const -> int { return replay_speed_exponent_; } auto replay_speed_mult() const -> float { return replay_speed_mult_; } + auto is_replay_paused() const -> bool { return replay_paused_; } void OnScreenSizeChange() override; auto kick_idle_players() const -> bool { return kick_idle_players_; } void LanguageChanged() override; void SetDebugSpeedExponent(int val); void SetReplaySpeedExponent(int val); + void PauseReplay(); + void ResumeReplay(); void set_admin_public_ids(const std::set& ids) { admin_public_ids_ = ids; } @@ -223,6 +226,7 @@ class SceneV1AppMode : public base::AppMode { bool game_roster_dirty_{}; bool kick_vote_in_progress_{}; bool kick_voting_enabled_{true}; + bool replay_paused_{false}; cJSON* game_roster_{}; millisecs_t last_game_roster_send_time_{};