From b7197b9d13fdf657bad26723293d129623c2ba57 Mon Sep 17 00:00:00 2001 From: scarf Date: Wed, 25 Feb 2026 03:44:12 +0900 Subject: [PATCH] perf: add sleep time skip option with safe fallback Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> Assisted-by: openai/gpt-5.3-codex on opencode --- src/activity_handlers.cpp | 18 ++++++ src/character.cpp | 2 + src/game.cpp | 24 +++++++- src/options.cpp | 3 + tests/sleep_skip_time_test.cpp | 109 +++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 tests/sleep_skip_time_test.cpp diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index aceefd8d0a7a..c7ca0a7dd7fc 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -71,6 +71,7 @@ #include "mtype.h" #include "npc.h" #include "omdata.h" +#include "options.h" #include "output.h" #include "overmapbuffer.h" #include "player.h" @@ -3418,8 +3419,25 @@ void activity_handlers::try_sleep_do_turn( player_activity *act, player *p ) { if( !p->has_effect( effect_sleep ) ) { if( character_funcs::roll_can_sleep( *p ) ) { + constexpr std::string_view sleep_skip_disabled_key = "sleep_skip_time_disabled"; + const bool sleep_skip_time = get_option( "SLEEP_SKIP_TIME" ); + const bool hostile_in_reality_bubble = sleep_skip_time && p->is_avatar() && + !g->get_creatures_if( [&]( const Creature & critter ) { + return &critter != p && p->attitude_to( critter ) == Attitude::A_HOSTILE; + } ).empty(); + const bool disable_sleep_skip_for_this_sleep = hostile_in_reality_bubble; + if( hostile_in_reality_bubble ) { + if( !query_yn( + _( "you don't feel that safe. Go to sleep anyway? (do not skip time on sleeping)" ) ) ) { + act->set_to_null(); + return; + } + } act->set_to_null(); p->fall_asleep(); + if( disable_sleep_skip_for_this_sleep ) { + p->set_value( sleep_skip_disabled_key.data(), "true" ); + } p->remove_value( "sleep_query" ); } else if( one_in( 1000 ) ) { p->add_msg_if_player( _( "You toss and turn…" ) ); diff --git a/src/character.cpp b/src/character.cpp index 739597dcf340..ea5dac7df943 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -8403,6 +8403,7 @@ void Character::cough( bool harmful, int loudness ) void Character::wake_up() { + remove_value( "sleep_skip_time_disabled" ); remove_effect( effect_slept_through_alarm ); remove_effect( effect_lying_down ); remove_effect( effect_alarm_clock ); @@ -10264,6 +10265,7 @@ void Character::fall_asleep( const time_duration &duration ) cancel_activity(); } } + remove_value( "sleep_skip_time_disabled" ); add_effect( effect_sleep, duration ); } diff --git a/src/game.cpp b/src/game.cpp index 722803eac66c..062a2495f93f 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1522,11 +1522,33 @@ bool game::do_turn() get_option( "SLEEP_SKIP_VEH" ); const auto soundperf = asleep && get_option( "SLEEP_SKIP_SOUND" ); const auto monperf = asleep && get_option( "SLEEP_SKIP_MON" ); + const bool sleep_skip_time = asleep && get_option( "SLEEP_SKIP_TIME" ) && + u.get_value( "sleep_skip_time_disabled" ) != "true"; + const bool hostile_in_reality_bubble = sleep_skip_time && + !get_creatures_if( [&]( const Creature & critter ) { + return &critter != &u && u.attitude_to( critter ) == Attitude::A_HOSTILE; + } ).empty(); + if( sleep_skip_time && !hostile_in_reality_bubble ) { + const auto sleep_duration = u.get_effect_dur( effect_sleep ); + if( sleep_duration > 1_turns ) { + calendar::turn += sleep_duration - 1_turns; + m.process_items(); + m.process_fields(); + if( !vehperf ) { + for( auto &veh : m.get_vehicles() ) { + veh.v->update_time( calendar::turn ); + } + m.vehmove(); + } + } + } // Actual stuff if( new_game ) { new_game = false; } else { - gamemode->per_turn(); + if( gamemode != nullptr ) { + gamemode->per_turn(); + } calendar::turn += 1_turns; } diff --git a/src/options.cpp b/src/options.cpp index 54c45a588b6b..c71cb3de7e3d 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -2273,6 +2273,9 @@ void options_manager::add_options_debug() add( "SLEEP_SKIP_MON", page_id, translate_marker( "Sleep Boost: Skip Monster Movement" ), translate_marker( "Monsters do not move while sleeping" ), false ); + add( "SLEEP_SKIP_TIME", page_id, translate_marker( "Skip Time When Sleeping" ), + translate_marker( "when sleeping, completely skip time like that funny block game" ), + false ); #if defined(__ANDROID__) add( "LOAD_FROM_EXTERNAL", page_id, translate_marker( "External Storage Saving" ), translate_marker( "Save in data/catalcysm... instead of Documents/..." ), diff --git a/tests/sleep_skip_time_test.cpp b/tests/sleep_skip_time_test.cpp new file mode 100644 index 000000000000..4c2551b473cc --- /dev/null +++ b/tests/sleep_skip_time_test.cpp @@ -0,0 +1,109 @@ +#include "catch/catch.hpp" + +#include "avatar.h" +#include "calendar.h" +#include "flag.h" +#include "game.h" +#include "item.h" +#include "map.h" +#include "map_helpers.h" +#include "monster.h" +#include "mtype.h" +#include "options_helpers.h" +#include "point.h" +#include "state_helpers.h" +#include "type_id.h" +#include "vehicle.h" +#include "vehicle_part.h" + +static const efftype_id effect_sleep( "sleep" ); +static const itype_id fuel_type_battery( "battery" ); +static const mtype_id mon_zombie( "mon_zombie" ); + +namespace +{ + +auto run_sleep_turn( const time_duration &duration ) -> time_duration +{ + avatar &you = g->u; + const time_point before_sleep = calendar::turn; + you.fall_asleep( duration ); + REQUIRE( you.has_effect( effect_sleep ) ); + CHECK_FALSE( g->do_turn() ); + return calendar::turn - before_sleep; +} + +} + +TEST_CASE( "sleep skip time advances to wake up when safe", "[sleep][perf]" ) +{ + clear_all_state(); + build_test_map( ter_id( "t_pavement" ) ); + clear_creatures(); + override_option sleep_skip_time( "SLEEP_SKIP_TIME", "true" ); + g->u.remove_value( "sleep_skip_time_disabled" ); + calendar::turn = calendar::turn_zero + 1_days; + + const time_duration elapsed = run_sleep_turn( 2_hours ); + + CHECK( elapsed >= 2_hours - 1_turns ); + CHECK_FALSE( g->u.has_effect( effect_sleep ) ); +} + +TEST_CASE( "sleep skip time falls back when hostiles are nearby", "[sleep][perf]" ) +{ + clear_all_state(); + build_test_map( ter_id( "t_pavement" ) ); + clear_creatures(); + override_option sleep_skip_time( "SLEEP_SKIP_TIME", "true" ); + g->u.remove_value( "sleep_skip_time_disabled" ); + calendar::turn = calendar::turn_zero + 1_days; + + avatar &you = g->u; + REQUIRE( g->place_critter_at( mon_zombie, you.pos() + tripoint_east ) != nullptr ); + + const time_duration elapsed = run_sleep_turn( 2_hours ); + + CHECK( elapsed < 2_hours ); + CHECK( elapsed >= 1_turns ); +} + +TEST_CASE( "sleep skip time processes rotting and charging effects", "[sleep][perf]" ) +{ + clear_all_state(); + build_test_map( ter_id( "t_pavement" ) ); + clear_creatures(); + override_option sleep_skip_time( "SLEEP_SKIP_TIME", "true" ); + g->u.remove_value( "sleep_skip_time_disabled" ); + calendar::turn = calendar::turn_zero + 2_days; + + map &here = get_map(); + const tripoint item_pos = g->u.pos(); + detached_ptr rotting_item = item::spawn( "meat_cooked" ); + rotting_item->mod_rot( 7_days ); + here.add_item_or_charges( item_pos, std::move( rotting_item ), false ); + + const tripoint vehicle_origin = tripoint( 10, 10, 0 ); + vehicle *veh_ptr = here.add_vehicle( vproto_id( "recharge_test" ), vehicle_origin, 0_degrees, 100, + 0 ); + REQUIRE( veh_ptr != nullptr ); + auto cargo_part_index = veh_ptr->part_with_feature( point_zero, "CARGO", true ); + REQUIRE( cargo_part_index >= 0 ); + + auto chargers = veh_ptr->get_parts_at( vehicle_origin, "RECHARGE", part_status_flag::available ); + REQUIRE( chargers.size() == 1 ); + chargers.front()->enabled = true; + + detached_ptr battery_item = item::spawn( "light_battery_cell" ); + battery_item->ammo_unset(); + REQUIRE( battery_item->has_flag( flag_RECHARGE ) ); + veh_ptr->add_item( veh_ptr->part( cargo_part_index ), std::move( battery_item ) ); + + run_sleep_turn( 1_hours ); + + CHECK( here.i_at( item_pos ).empty() ); + auto cargo_items = veh_ptr->get_items( cargo_part_index ); + REQUIRE( cargo_items.size() == 1 ); + CHECK( cargo_items.only_item().ammo_remaining() > 0 ); + CHECK( veh_ptr->fuel_left( fuel_type_battery ) > 0 ); +}