diff --git a/src/mods/cmseg.cpp b/src/mods/cmseg.cpp index 25744f3b..12e3fb5f 100644 --- a/src/mods/cmseg.cpp +++ b/src/mods/cmseg.cpp @@ -365,7 +365,7 @@ void disp() { color = draw::GOLD; else color = draw::WHITE; - timerdisp::draw_timer(static_cast(s_seg_time), "SEG:", 0, color, false); + timerdisp::draw_timer(380, 0, 44, "SEG:", static_cast(s_seg_time), false, draw::WHITE); } } diff --git a/src/mods/deathcounter.cpp b/src/mods/deathcounter.cpp new file mode 100644 index 00000000..7b968937 --- /dev/null +++ b/src/mods/deathcounter.cpp @@ -0,0 +1,63 @@ +#include "mods/deathcounter.h" + +#include "mkb/mkb.h" + +#include "mods/freecam.h" +#include "mods/storytimer.h" +#include "mods/validate.h" +#include "systems/assembly.h" +#include "systems/pad.h" +#include "systems/pref.h" +#include "utils/draw.h" +#include "utils/patch.h" +#include "utils/timerdisp.h" + +namespace deathcounter { + +static bool s_can_die; +static u32 s_death_count; + +void tick() { + // set the death count to 0 on the file select screen + if (mkb::scen_info.mode == 5) { + s_death_count = 0; + s_can_die = false; + } + + // Don't increment the death counter on stage 1 if the setting is ticked + if (mkb::sub_mode == mkb::SMD_GAME_PLAY_MAIN && !validate::has_entered_goal()) { + if (pref::get(pref::BoolPref::CountFirstStageDeaths)) { + s_can_die = true; + } else if (!pref::get(pref::BoolPref::CountFirstStageDeaths) && + storytimer::get_completed_stagecount() != 0) { + s_can_die = true; + } + } else if (validate::has_entered_goal()) { + s_can_die = false; + } + + if (s_can_die && + (mkb::sub_mode == mkb::SMD_GAME_READY_INIT || mkb::sub_mode == mkb::SMD_GAME_RINGOUT_INIT || + mkb::sub_mode == mkb::SMD_GAME_TIMEOVER_INIT || + mkb::sub_mode == mkb::SMD_GAME_SCENARIO_RETURN || + mkb::sub_mode == mkb::SMD_GAME_INTR_SEL_INIT)) { + // you can die either by retrying after dropping in, falling out, timing over, stage + // selecting after dropping in (but before breaking the tape), or exiting game after + // dropping in (but before breaking the tape) + s_death_count += 1; + s_can_die = false; // once the death counter is incremented, set this to false so we only + // increment it by 1 + } +} + +void disp() { + if ((mkb::main_game_mode != mkb::STORY_MODE && mkb::sub_mode != mkb::SMD_AUTHOR_PLAY_INIT && + mkb::sub_mode != mkb::SMD_AUTHOR_PLAY_MAIN) || + freecam::should_hide_hud() || !pref::get(pref::BoolPref::ShowDeathCounter)) { + return; + } + draw::debug_text(18, 56, draw::WHITE, "Deaths: "); + draw::debug_text(98, 56, draw::WHITE, "%d", s_death_count); +} + +} // namespace deathcounter diff --git a/src/mods/deathcounter.h b/src/mods/deathcounter.h new file mode 100644 index 00000000..68d11abb --- /dev/null +++ b/src/mods/deathcounter.h @@ -0,0 +1,8 @@ +#pragma once + +namespace deathcounter { + +void tick(); +void disp(); + +} // namespace deathcounter \ No newline at end of file diff --git a/src/mods/iw.cpp b/src/mods/iw.cpp index 83cd6149..47786957 100644 --- a/src/mods/iw.cpp +++ b/src/mods/iw.cpp @@ -123,7 +123,6 @@ void disp() { mkb::main_game_mode != mkb::STORY_MODE || !main::currently_playing_iw || freecam::should_hide_hud()) return; - timerdisp::draw_timer(static_cast(s_iw_time), "IW:", 0, draw::WHITE, false); + timerdisp::draw_timer(392, 0, 32, "IW:", static_cast(s_iw_time), false, draw::WHITE); } - } // namespace iw diff --git a/src/mods/storytimer.cpp b/src/mods/storytimer.cpp new file mode 100644 index 00000000..e82a08b8 --- /dev/null +++ b/src/mods/storytimer.cpp @@ -0,0 +1,320 @@ +#include "storytimer.h" + +#include "mkb/mkb.h" + +#include "mods/freecam.h" +#include "mods/validate.h" +#include "systems/assembly.h" +#include "systems/pad.h" +#include "systems/pref.h" +#include "utils/draw.h" +#include "utils/patch.h" +#include "utils/timerdisp.h" +#include "validate.h" + +namespace storytimer { + +enum class TimerOptions { + DontShow = 0, + AlwaysShow = 1, + BetweenWorlds = 2, + EndOfRun = 3, +}; + +static constexpr s32 FULLGAME_TIMER_LOCATION_X = 18 + 24; +static constexpr s32 FULLGAME_TIMER_TEXT_OFFSET = 56; +static constexpr s32 SEGMENT_TIMER_LOCATION_X = 30 + 24; +static constexpr s32 SEGMENT_TIMER_TEXT_OFFSET = 44; +static constexpr s32 IW_TIME_LOCATION_X = 42 + 24; +static constexpr s32 WORLD_COUNT = 10; +static constexpr s32 STAGES_PER_WORLD = 10; +static constexpr u32 SECOND_FRAMES = 60; +static constexpr u32 MINUTE_FRAMES = SECOND_FRAMES * 60; +static constexpr u32 HOUR_FRAMES = MINUTE_FRAMES * 60; +struct TimerGroup { + u32 segment; // the time taken to complete a world up until tape break on the last stage + u32 full_world; // the time taken to complete a world until the fade to white on the last stage +}; +static TimerGroup s_timer_group[WORLD_COUNT]; // each world has its own TimerGroup structure +static s32 s_completed_stages; // the completed stages for the whole run + +u32 get_completed_stagecount() { return s_completed_stages; } + +// before starting the run, there are several values we zero +static void reset_timer() { + s_completed_stages = 0; + for (s32 k = 0; k < WORLD_COUNT; k++) { + s_timer_group[k] = {}; + } +} + +void tick() { + // reset the timer on the file select screen (scen_info.mode == 5) and the name entry screen + // (scen_info.mode == 21) + if (mkb::scen_info.mode == 5 || mkb::scen_info.mode == 21) { + reset_timer(); + } + + u32 sum = 0; + for (s32 k = 0; k < WORLD_COUNT; k++) { + sum += mkb::get_world_unbeaten_stage_count(k); + } + s_completed_stages = sum; + + // for later, it's useful to record what submodes correspond to spin in, gameplay, etc. + + // submodes during spin in + bool is_on_spin_in = + (mkb::sub_mode == mkb::SMD_GAME_FIRST_INIT || mkb::sub_mode == mkb::SMD_GAME_READY_INIT || + mkb::sub_mode == mkb::SMD_GAME_READY_MAIN); + + // submodes during gameplay + bool is_on_gameplay = + (mkb::sub_mode == mkb::SMD_GAME_PLAY_INIT || mkb::sub_mode == mkb::SMD_GAME_PLAY_MAIN); + + // submodes entered when exiting game + bool is_on_exit_game = (mkb::sub_mode == mkb::SMD_GAME_INTR_SEL_INIT || + mkb::sub_mode == mkb::SMD_GAME_INTR_SEL_MAIN || + mkb::sub_mode == mkb::SMD_GAME_SUGG_SAVE_INIT || + mkb::sub_mode == mkb::SMD_GAME_SUGG_SAVE_MAIN); + + // submodes entered when breaking the goal tape + bool is_postgoal = + (mkb::sub_mode == mkb::SMD_GAME_GOAL_INIT || mkb::sub_mode == mkb::SMD_GAME_GOAL_MAIN || + mkb::sub_mode == mkb::SMD_GAME_GOAL_REPLAY_INIT || + mkb::sub_mode == mkb::SMD_GAME_GOAL_REPLAY_MAIN || + mkb::sub_mode == mkb::SMD_GAME_SCENARIO_RETURN); + + // submodes for the fallout and y/n screen + bool is_on_fallout = + (mkb::sub_mode == mkb::SMD_GAME_RINGOUT_INIT || + mkb::sub_mode == mkb::SMD_GAME_RINGOUT_MAIN || mkb::sub_mode == mkb::SMD_GAME_RETRY_INIT || + mkb::sub_mode == mkb::SMD_GAME_RETRY_MAIN); + + // submodes during timeover + bool is_timeover = (mkb::sub_mode == mkb::SMD_GAME_TIMEOVER_INIT || + mkb::sub_mode == mkb::SMD_GAME_TIMEOVER_MAIN); + + // stage select states on the 10 ball screen *not* including the transition after the a press + // input. this transition is not included in the timer because even ignoring completely white + // frames, the time spent on mkb::g_storymode_stageselect_state == mkb::STAGE_SELECTED can be + // highly variable (up to over 40 frames sometimes!), so for the purpose of a loadless + // timer, it makes sense to cut this out + bool is_pre_load_in_stage_select = + (mkb::g_storymode_stageselect_state == mkb::STAGE_SELECT_INTRO_SEQUENCE || + mkb::g_storymode_stageselect_state == 3 || + mkb::g_storymode_stageselect_state == mkb::STAGE_SELECT_IDLE); + + for (s32 k = 0; k < WORLD_COUNT; k++) { + if (mkb::scen_info.world == k) { + if (is_on_spin_in || is_on_exit_game || is_on_fallout || is_timeover || + is_on_gameplay || is_postgoal || is_pre_load_in_stage_select) { + // increment the timer during every frame on spin in, exit game, fallout, + // timeover, gameplay, and every frame after breaking the tape until (but not + // including) the load back to the 10 ball screen. In addition, increment the timer + // on every frame on the 10 ball screen until the a press input + s_timer_group[k].full_world += 1; + } + if (s_completed_stages % STAGES_PER_WORLD != 9 && + mkb::sub_mode == mkb::SMD_GAME_SCENARIO_RETURN) { + // need to add 1 frame to the timer when stage selecting to the 10 ball screen + // since the first non-completely white frame is not included in + // mkb::STAGE_SELECT_INTRO_SEQUENCE; don't include the last stage of a world + // since that missing frame is handled by the world start correction + s_timer_group[k].full_world += 1; + } + if (mkb::get_world_unbeaten_stage_count(k) < 9 || + (mkb::get_world_unbeaten_stage_count(k) == 9 && + (is_pre_load_in_stage_select || is_on_spin_in || !validate::has_entered_goal()))) { + // stop incrementing the segment timer once the tape is broken on the last stage of + // the world + s_timer_group[k].segment = s_timer_group[k].full_world; + } + } + } +} + +void disp() { + if ((mkb::main_game_mode != mkb::STORY_MODE && mkb::sub_mode != mkb::SMD_AUTHOR_PLAY_INIT && + mkb::sub_mode != mkb::SMD_AUTHOR_PLAY_MAIN) || + freecam::should_hide_hud()) { + return; + } + + bool is_postgoal = + (mkb::sub_mode == mkb::SMD_GAME_GOAL_INIT || mkb::sub_mode == mkb::SMD_GAME_GOAL_MAIN || + mkb::sub_mode == mkb::SMD_GAME_GOAL_REPLAY_INIT || + mkb::sub_mode == mkb::SMD_GAME_GOAL_REPLAY_MAIN || + mkb::sub_mode == mkb::SMD_GAME_SCENARIO_RETURN); + + bool is_between_worlds = false; + if ((s_completed_stages % STAGES_PER_WORLD == 9) && is_postgoal) { + is_between_worlds = true; + } else if ((s_completed_stages % STAGES_PER_WORLD == 0 && + mkb::g_storymode_stageselect_state == mkb::STAGE_SELECT_INTRO_SEQUENCE) || + s_completed_stages % STAGES_PER_WORLD != 0) { + // no longer "between worlds" if you enter the next world's 10 ball screen, or if you break + // the tape on the last stage of the current world, but retry + is_between_worlds = false; + } + + bool is_run_complete = (mkb::scen_info.world == 9 && + ((mkb::get_world_unbeaten_stage_count(9) == 9 && is_postgoal) || + mkb::get_world_unbeaten_stage_count(9) == 10)); + + // move the position of the fullgame timer if the death counter is on + u32 fullgame_timer_location_y = 2; + if (pref::get(pref::BoolPref::ShowDeathCounter)) { + fullgame_timer_location_y++; + } + + bool display_story_timer = false; + switch (TimerOptions(pref::get(pref::U8Pref::FullgameTimerOptions))) { + case TimerOptions::AlwaysShow: + display_story_timer = true; + break; + case TimerOptions::BetweenWorlds: + display_story_timer = is_between_worlds; + break; + case TimerOptions::EndOfRun: + display_story_timer = is_run_complete; + break; + case TimerOptions::DontShow: + display_story_timer = false; + break; + } + + // at the start of each world, we need to correct the timer by 1 frame + u32 full_world[10] = {}; + u32 segment[10] = {}; + + for (s32 k = 0; k < WORLD_COUNT; k++) { + if (s_timer_group[k].full_world > 0) { + // don't apply the correction until the world actually starts + full_world[k] = s_timer_group[k].full_world + 1; + segment[k] = s_timer_group[k].segment + 1; + } + } + + // split[k] is just the fullgame time at tape break on the last stage of world k; + // to calculate this, just add full_world[j] (j 0) { + if (segment_hours[k] > 0) { + mkb::sprintf(timer_str[k], "W%d:%d:%02d:%02d.%02d (%d:%02d:%02d.%02d)", k + 1, + split_hours[k], split_minutes[k], split_seconds[k], + split_centiseconds[k], segment_hours[k], segment_minutes[k], + segment_seconds[k], segment_centiseconds[k]); + draw::debug_text(X[k], Y[k], draw::WHITE, "%s", timer_str[k]); + } else { + mkb::sprintf(timer_str[k], "W%d:%d:%02d:%02d.%02d (%02d:%02d.%02d)", k + 1, + split_hours[k], split_minutes[k], split_seconds[k], + split_centiseconds[k], segment_minutes[k], segment_seconds[k], + segment_centiseconds[k]); + draw::debug_text(X[k], Y[k], draw::WHITE, "%s", timer_str[k]); + } + } else { + mkb::sprintf(timer_str[k], "W%d:%02d:%02d.%02d (%02d:%02d.%02d)", k + 1, + split_minutes[k], split_seconds[k], split_centiseconds[k], + segment_minutes[k], segment_seconds[k], segment_centiseconds[k]); + draw::debug_text(X[k], Y[k], draw::WHITE, "%s", timer_str[k]); + } + } + } +} + +} // namespace storytimer \ No newline at end of file diff --git a/src/mods/storytimer.h b/src/mods/storytimer.h new file mode 100644 index 00000000..fcc9a2c3 --- /dev/null +++ b/src/mods/storytimer.h @@ -0,0 +1,11 @@ +#pragma once + +#include "mkb/mkb.h" + +namespace storytimer { + +void tick(); +void disp(); +u32 get_completed_stagecount(); + +} // namespace storytimer \ No newline at end of file diff --git a/src/mods/timer.cpp b/src/mods/timer.cpp index 6bf23687..593d2eec 100644 --- a/src/mods/timer.cpp +++ b/src/mods/timer.cpp @@ -57,11 +57,11 @@ void disp() { u32 row = 1; if (pref::get(pref::BoolPref::TimerShowRTA) && !freecam::should_hide_hud()) { - timerdisp::draw_timer(s_rta_timer, "RTA:", row++, draw::WHITE, true); + timerdisp::draw_timer(380, row++, 44, "RTA:", s_rta_timer, true, draw::WHITE); } if (pref::get(pref::BoolPref::TimerShowPause) && !freecam::should_hide_hud()) { - timerdisp::draw_timer(s_pause_timer, "PAU:", row++, draw::WHITE, true); + timerdisp::draw_timer(380, row++, 44, "PAU:", s_pause_timer, true, draw::WHITE); } switch (mkb::sub_mode) { diff --git a/src/mods/validate.cpp b/src/mods/validate.cpp index d1719104..64d81231 100644 --- a/src/mods/validate.cpp +++ b/src/mods/validate.cpp @@ -194,6 +194,8 @@ void init() { }); } +bool has_entered_goal() { return s_entered_goal; } + void tick() { if (mkb::sub_mode == mkb::SMD_GAME_PLAY_INIT) { s_entered_goal = false; diff --git a/src/mods/validate.h b/src/mods/validate.h index 136065ea..11df5c1a 100644 --- a/src/mods/validate.h +++ b/src/mods/validate.h @@ -5,6 +5,7 @@ namespace validate { void validate_run(); +bool has_entered_goal(); bool was_run_valid(bool mods_allowed); void disable_invalidating_settings(); u32 get_framesave(); diff --git a/src/systems/main.cpp b/src/systems/main.cpp index 9c0d214d..f2f6cac9 100644 --- a/src/systems/main.cpp +++ b/src/systems/main.cpp @@ -20,6 +20,7 @@ #include "mods/banans.h" #include "mods/camera.h" #include "mods/cmseg.h" +#include "mods/deathcounter.h" #include "mods/dpad.h" #include "mods/fallout.h" #include "mods/freecam.h" @@ -36,6 +37,7 @@ #include "mods/scratch.h" #include "mods/sfx.h" #include "mods/stage_edits.h" +#include "mods/storytimer.h" #include "mods/tetris.h" #include "mods/timer.h" #include "mods/unlock.h" @@ -124,6 +126,8 @@ void init() { cardio::tick(); unlock::tick(); iw::tick(); + storytimer::tick(); + deathcounter::tick(); savest_ui::tick(); menu_impl::tick(); // anything checking for pref changes should run after menu_impl::tick() fallout::tick(); @@ -164,6 +168,8 @@ void init() { draw::predraw(); timer::disp(); iw::disp(); + storytimer::disp(); + deathcounter::disp(); Tetris::get_instance().disp(); ilbattle::disp(); cmseg::disp(); @@ -192,6 +198,8 @@ void init() { validate::validate_run(); ilmark::validate_attempt(); ilbattle::validate_attempt(); + // storytimer::on_goal_entry(); + // deathcounter::on_goal_entry(); }); jump::patch_minimap(); } diff --git a/src/systems/menu_defn.cpp b/src/systems/menu_defn.cpp index 7fae704e..5c6d0678 100644 --- a/src/systems/menu_defn.cpp +++ b/src/systems/menu_defn.cpp @@ -804,7 +804,57 @@ static Widget s_cm_seg_widgets[] = { }, }; +static const char* TIMER_OPTIONS[] = { + "Don't show", + "Always show", + "Between worlds", + "End of run", +}; + +static Widget s_loadless_timers_widgets[] = { + { + .type = WidgetType::Choose, + .choose = + { + .label = "Fullgame Timer", + .choices = TIMER_OPTIONS, + .num_choices = LEN(TIMER_OPTIONS), + .pref = pref::U8Pref::FullgameTimerOptions, + }, + }, + { + .type = WidgetType::Choose, + .choose = + { + .label = "Segment Timer", + .choices = TIMER_OPTIONS, + .num_choices = LEN(TIMER_OPTIONS), + .pref = pref::U8Pref::SegmentTimerOptions, + }, + }, +}; + +static Widget s_deathcounter_widgets[] = { + { + .type = WidgetType::Checkbox, + .checkbox = + { + .label = "Show Death Counter", + .pref = pref::BoolPref::ShowDeathCounter, + }, + }, + { + .type = WidgetType::Checkbox, + .checkbox = + { + .label = "Count Stage 1 Deaths", + .pref = pref::BoolPref::CountFirstStageDeaths, + }, + }, +}; + static Widget s_timers_widgets[] = { + // I might want to reorganize this with the addition of a loadless timer {.type = WidgetType::Header, .header = {"Realtime Timers"}}, { .type = WidgetType::Checkbox, @@ -849,7 +899,7 @@ static Widget s_timers_widgets[] = { }, }, {.type = WidgetType::Separator}, - {.type = WidgetType::Header, .header = {"Segment Timers"}}, + {.type = WidgetType::Header, .header = {"Segment & Loadless Timers"}}, { .type = WidgetType::Checkbox, .checkbox = @@ -866,6 +916,10 @@ static Widget s_timers_widgets[] = { .pref = pref::BoolPref::CmTimer, }, }, + { + .type = WidgetType::Menu, + .menu = {"Loadless Timers", s_loadless_timers_widgets, LEN(s_loadless_timers_widgets)}, + }, }; static Widget s_sound_widgets[] = { @@ -1329,6 +1383,10 @@ static Widget s_displays_widgets[] = { .pref = pref::BoolPref::BananaCounter9999, }, }, + { + .type = WidgetType::Menu, + .menu = {"Death Counter", s_deathcounter_widgets, LEN(s_deathcounter_widgets)}, + }, }; static Widget s_enabled_physics_widgets[] = { diff --git a/src/systems/pref.cpp b/src/systems/pref.cpp index 3efe58c8..9053f197 100644 --- a/src/systems/pref.cpp +++ b/src/systems/pref.cpp @@ -101,6 +101,10 @@ enum class PrefId : u16 { MonkeyType = 81, JumpProfile = 82, CustomPhysicsDisp = 83, + FullgameTimerOptions = 84, + SegmentTimerOptions = 85, + ShowDeathCounter = 86, + CountFirstStageDeaths = 87, }; // Verbatim list of preference IDs we iterate over when writing savefile back out @@ -185,6 +189,10 @@ static const PrefId s_pref_ids[] = { PrefId::MonkeyType, PrefId::JumpProfile, PrefId::CustomPhysicsDisp, + PrefId::FullgameTimerOptions, + PrefId::SegmentTimerOptions, + PrefId::ShowDeathCounter, + PrefId::CountFirstStageDeaths, }; static std::optional pref_id_to_bool_pref(PrefId id) { @@ -287,6 +295,10 @@ static std::optional pref_id_to_bool_pref(PrefId id) { return BoolPref::JumpAllowWalljumps; case PrefId::CustomPhysicsDisp: return BoolPref::CustomPhysicsDisp; + case PrefId::ShowDeathCounter: + return BoolPref::ShowDeathCounter; + case PrefId::CountFirstStageDeaths: + return BoolPref::CountFirstStageDeaths; default: return {}; } @@ -356,6 +368,10 @@ static std::optional pref_id_to_u8_pref(PrefId id) { return U8Pref::MonkeyType; case PrefId::JumpProfile: return U8Pref::JumpProfile; + case PrefId::FullgameTimerOptions: + return U8Pref::FullgameTimerOptions; + case PrefId::SegmentTimerOptions: + return U8Pref::SegmentTimerOptions; default: return {}; } @@ -376,6 +392,7 @@ static BoolPref s_default_on_bool_prefs[] = { BoolPref::JumpChangePhysics, BoolPref::JumpAllowWalljumps, BoolPref::CustomPhysicsDisp, + BoolPref::CountFirstStageDeaths, }; struct DefaultU8Pref { @@ -400,8 +417,8 @@ static DefaultU8Pref s_default_u8_prefs[] = { // struct PrefState { - u8 bool_prefs[8]; // up to 64 bool prefs - u8 u8_prefs[31]; // 31 u8 prefs + u8 bool_prefs[8]; + u8 u8_prefs[33]; }; static PrefState s_pref_state, s_prev_pref_state, s_default_pref_state; diff --git a/src/systems/pref.h b/src/systems/pref.h index 418a817b..59b5ebac 100644 --- a/src/systems/pref.h +++ b/src/systems/pref.h @@ -56,6 +56,8 @@ enum class BoolPref : u8 { JumpChangePhysics, JumpAllowWalljumps, CustomPhysicsDisp, + ShowDeathCounter, + CountFirstStageDeaths, }; enum class U8Pref : u8 { @@ -90,6 +92,8 @@ enum class U8Pref : u8 { PhysicsPreset, MonkeyType, JumpProfile, + FullgameTimerOptions, + SegmentTimerOptions, }; void init(); diff --git a/src/utils/timerdisp.cpp b/src/utils/timerdisp.cpp index c6b94ddf..b1522dd9 100644 --- a/src/utils/timerdisp.cpp +++ b/src/utils/timerdisp.cpp @@ -11,7 +11,8 @@ static constexpr u32 HOUR_FRAMES = MINUTE_FRAMES * 60; static constexpr s32 X = 378; static constexpr s32 Y = 24; -void draw_timer(s32 frames, const char* prefix, u32 row, mkb::GXColor color, bool show_seconds) { +void draw_timer(u32 pos_x, u32 pos_y, u32 text_offset, const char* prefix, s32 frames, + bool show_seconds_only, mkb::GXColor color) { bool positive = frames >= 0; if (!positive) frames = -frames; const char* sign = positive ? "" : "-"; @@ -21,21 +22,22 @@ void draw_timer(s32 frames, const char* prefix, u32 row, mkb::GXColor color, boo u32 seconds = frames % MINUTE_FRAMES / SECOND_FRAMES; u32 centiseconds = (frames % SECOND_FRAMES) * 100 / 60; - s32 y = Y + row * 16; + s32 A = pos_x; + s32 a = A + text_offset; + s32 b = Y + (pos_y)*16; - if (hours > 0 && !show_seconds) { - draw::debug_text(X, y, color, prefix); - draw::debug_text(X + 48, y, color, "%s%d:%02d:%02d.%02d", sign, hours, minutes, seconds, - centiseconds); - } else if (minutes > 0 && !show_seconds) { - draw::debug_text(X, y, color, prefix); - draw::debug_text(X + 48, y, color, "%s%02d:%02d.%02d", sign, minutes, seconds, + if (hours > 0 && !show_seconds_only) { + draw::debug_text(A, b, color, prefix); + draw::debug_text(a, b, color, "%s%d:%02d:%02d.%02d", sign, hours, minutes, seconds, centiseconds); + } else if (minutes > 0 && !show_seconds_only) { + draw::debug_text(A, b, color, prefix); + draw::debug_text(a, b, color, "%s%02d:%02d.%02d", sign, minutes, seconds, centiseconds); } else { u32 total_seconds = seconds + (minutes * MINUTE_FRAMES + hours * HOUR_FRAMES) / SECOND_FRAMES; - draw::debug_text(X, y, color, prefix); - draw::debug_text(X + 48, y, color, "%s%02d.%02d", sign, total_seconds, centiseconds); + draw::debug_text(A, b, color, prefix); + draw::debug_text(a, b, color, "%s%02d.%02d", sign, total_seconds, centiseconds); } } diff --git a/src/utils/timerdisp.h b/src/utils/timerdisp.h index 8cbeb293..503a5cfa 100644 --- a/src/utils/timerdisp.h +++ b/src/utils/timerdisp.h @@ -4,7 +4,8 @@ namespace timerdisp { -void draw_timer(s32 frames, const char* prefix, u32 row, mkb::GXColor color, bool show_minutes); +void draw_timer(u32 pos_x, u32 pos_y, u32 text_offset, const char* prefix, s32 frames_1, + bool show_seconds_only, mkb::GXColor color); void draw_subtick_timer(s32 frames, const char* prefix, u32 row, mkb::GXColor color, bool show_minutes, u32 framesave, bool extra_precision); void draw_percentage(s32 fsave, const char* prefix, u32 row, mkb::GXColor color);