Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f92f9e4
FRLG: type names on the in-game naming screen
dayfine Oct 30, 2026
a848603
Merge origin/main into frlg/naming-screen
dayfine Oct 31, 2026
c2090aa
Merge origin/main into frlg/naming-screen
dayfine Nov 1, 2026
4506cb7
Merge origin/main into frlg/naming-screen
dayfine Nov 2, 2026
47c3ffc
FRLG naming screen: type a name without pressing OK
dayfine Nov 2, 2026
7695156
Merge remote-tracking branch 'origin/frlg/naming-screen' into frlg/au…
dayfine Nov 2, 2026
651cad2
FRLG AutoStory: type the player's name in the new game's intro
dayfine Nov 2, 2026
68bdfc7
Merge remote-tracking branch 'origin/frlg/autostory-mvp' into HEAD
dayfine Nov 2, 2026
fd6ce8c
Merge remote-tracking branch 'origin/frlg/autostory-mvp' into HEAD
dayfine Nov 2, 2026
c98160d
Merge commit 'f4bbb71de' into HEAD
dayfine Nov 2, 2026
701564a
Merge frlg/autostory-mvp into frlg/autostory-player-name
dayfine Nov 2, 2026
4bef31d
Merge branch 'frlg/autostory-mvp' into frlg/autostory-player-name
dayfine Nov 2, 2026
1dbf3c7
Merge branch 'frlg/autostory-mvp' into frlg/autostory-player-name
dayfine Nov 3, 2026
32cc761
Merge remote-tracking branch 'origin/frlg/autostory-mvp' into HEAD
dayfine Nov 3, 2026
75ea90f
FRLG AutoStory: give StoryEnv's player_name a default
dayfine Nov 3, 2026
51fa636
Merge branch 'frlg/autostory-mvp' into frlg/autostory-player-name
dayfine Nov 3, 2026
4efc512
Merge branch 'frlg/autostory-mvp' into frlg/autostory-player-name
dayfine Nov 3, 2026
f387f0b
Merge branch 'frlg/autostory-mvp' into frlg/autostory-player-name
dayfine Nov 3, 2026
b8dceff
Merge branch 'frlg/autostory-mvp' into frlg/autostory-player-name
dayfine Nov 3, 2026
af95529
Merge remote-tracking branch 'origin/frlg/autostory-mvp' into HEAD
dayfine Nov 3, 2026
ff997e3
Merge frlg/autostory-mvp into frlg/autostory-player-name
dayfine Nov 3, 2026
34d71b7
Merge frlg/autostory-mvp into frlg/autostory-player-name
dayfine Nov 3, 2026
021ed57
Merge frlg/autostory-mvp (10a423292, #119 recovery) into frlg/autosto…
dayfine Nov 3, 2026
f1b5ad0
Merge frlg/autostory-mvp into frlg/autostory-player-name
dayfine Nov 3, 2026
ee98a84
Merge remote-tracking branch 'origin/frlg/autostory-mvp' into HEAD
dayfine Nov 3, 2026
694bb50
Merge remote-tracking branch 'origin/frlg/autostory-mvp' into HEAD
dayfine Nov 3, 2026
61b5bb0
Merge remote-tracking branch 'origin/frlg/autostory-mvp' into HEAD
dayfine Nov 3, 2026
1b6ef12
Merge frlg/autostory-mvp into frlg/autostory-player-name
dayfine Nov 3, 2026
215cf3f
Merge frlg/autostory-mvp (battle loop: end on the overworld after RUN…
dayfine Nov 3, 2026
fb70b32
Merge frlg/autostory-mvp (a disabled move, and a move list that never…
dayfine Nov 3, 2026
4c40482
Merge frlg/autostory-mvp (matchup attacks in every battle) into frlg/…
dayfine Nov 3, 2026
c1f07a3
Merge frlg/autostory-mvp (HP bar share, nurse for yellow HP, move pro…
dayfine Nov 3, 2026
fb56c02
Merge frlg/autostory-mvp (c3edf46ff: verify mode from #122) into frlg…
dayfine Nov 3, 2026
29ca9d3
Merge frlg/autostory-mvp (cb51ddf86: battle sim from #120) into frlg/…
dayfine Nov 3, 2026
8fb3048
Merge frlg/autostory-mvp into frlg/autostory-player-name
dayfine Nov 3, 2026
f092f93
Merge frlg/autostory-mvp (TM moves in the summary reader) into frlg/a…
dayfine Nov 3, 2026
0b6bf8a
Merge frlg/autostory-mvp (bag rows below an item the reader can't nam…
dayfine Nov 3, 2026
ca6321a
FRLG AutoStory: check the options in check_setup(), keeping program()…
dayfine Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
394 changes: 394 additions & 0 deletions SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "Pokemon/AutoStory/Pokemon_StorySegments.h"
#include "Pokemon/Pokemon_Strings.h"
#include "VideoGames/PokemonFRLG/PokemonFRLG_Navigation.h"
#include "VideoGames/PokemonFRLG/Programs/PokemonFRLG_NamingScreenKeyboard.h"
#include "PokemonFRLG_AutoStoryEvents.h"
#include "PokemonFRLG_AutoStoryTable.h"
#include "PokemonFRLG_AutoStory.h"
Expand Down Expand Up @@ -60,13 +61,21 @@
LockMode::LOCK_WHILE_RUNNING,
true
)
, PLAYER_NAME(
false,
"<b>Player name:</b><br>Typed on the new game's keyboard (00.01). Empty keeps the game's default name. "
"The rival always keeps the default name.",
LockMode::LOCK_WHILE_RUNNING,
"Kosmos", ""
)
, NOTIFICATIONS({
&NOTIFICATION_PROGRAM_FINISH,
&NOTIFICATION_ERROR_FATAL,
})
{
PA_ADD_OPTION(RUN);
PA_ADD_OPTION(NEW_GAME_FROM_TITLE);
PA_ADD_OPTION(PLAYER_NAME);
PA_ADD_OPTION(NOTIFICATIONS);
}

Expand All @@ -89,6 +98,17 @@

namespace{

// Before the new game starts: a name the keyboard can't type is the user's to fix.
void check_player_name(ConsoleHandle& console, const std::string& name){
if (name.empty()){
return;
}
const std::string error = naming_text_error(name, NamingScreenKind::PLAYER);
if (!error.empty()){
throw UserSetupError(console, "Player name: " + error);
}
}

// A failed attempt reloads the last save, with the supplies it had, read
// from its bag again; the engine's lines go to the console's log.
Pokemon::SegmentHooks story_hooks(SingleSwitchProgramEnvironment& env, StoryEnv& story){
Expand Down Expand Up @@ -176,7 +196,23 @@

}

// The options' problems, before anything is pressed.
static void check_setup(
ConsoleHandle& console, const std::vector<StoryCheckpoint>& checkpoints, size_t first, size_t last,
bool new_game_from_title, const std::string& player_name
){
if (first > last || last >= checkpoints.size()){
throw UserSetupError(console, "The start checkpoint must not come after the end checkpoint.");
}
if (first == 0 && !new_game_from_title){
throw UserSetupError(console, "Checkpoint 00.01 starts a new game: start on the title screen with no save.");
}
if (first == 0){
check_player_name(console, player_name);
}
}

void AutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){

Check warning on line 215 in SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::NintendoSwitch::PokemonFRLG::AutoStory::program( SingleSwitchProgramEnvironment & env , ProControllerContext & context): function_lines=46 (advisory above 25, limit 50)
if (const std::optional<Pokemon::VerifyPolicy> verify = RUN.VERIFY.policy()){
verify_story_checkpoint(env, context, RUN, *verify, NOTIFICATION_PROGRAM_FINISH);
return;
Expand All @@ -184,14 +220,11 @@
const std::vector<StoryCheckpoint>& checkpoints = story_checkpoints();
const size_t first = RUN.start_index();
const size_t last = RUN.end_index();
if (first > last || last >= checkpoints.size()){
throw UserSetupError(env.console, "The start checkpoint must not come after the end checkpoint.");
}
if (first == 0 && !NEW_GAME_FROM_TITLE){
throw UserSetupError(env.console, "Checkpoint 00.01 starts a new game: start on the title screen with no save.");
}
const std::string player_name = PLAYER_NAME;
check_setup(env.console, checkpoints, first, last, NEW_GAME_FROM_TITLE, player_name);

StoryEnv story{env.console, context, checkpoints[first].supplies, checkpoints[first].supplies, false, RUN.checks()};
story.player_name = player_name;
if (!(first == 0 && NEW_GAME_FROM_TITLE)){
env.console.log("Loading the save for " + checkpoints[first].id + ".");
soft_reset(env.console, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "Common/Cpp/Options/BooleanCheckBoxOption.h"
#include "Common/Cpp/Options/EnumDropdownOption.h"
#include "Common/Cpp/Options/StringOption.h"
#include "CommonFramework/Notifications/EventNotificationsTable.h"
#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h"
#include "Pokemon/Options/Pokemon_StoryRunOptions.h"
Expand Down Expand Up @@ -45,6 +46,7 @@ class AutoStory : public SingleSwitchProgramInstance{
// every game's AutoStory.
Pokemon::StoryRunOptions RUN;
BooleanCheckBoxOption NEW_GAME_FROM_TITLE;
StringOption PLAYER_NAME;
EventNotificationsOption NOTIFICATIONS;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* AutoStory Actions

Check warning on line 1 in SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryActions.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

<file>: file_lines=861 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -26,6 +26,7 @@
#include "VideoGames/PokemonFRLG/Inference/PokemonFRLG_BattleScreenDetector.h"
#include "VideoGames/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h"
#include "VideoGames/PokemonFRLG/PokemonFRLG_Settings.h"
#include "VideoGames/PokemonFRLG/Programs/PokemonFRLG_NamingScreen.h"
#include "VideoGames/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h"
#include "PokemonFRLG_AutoStoryActions.h"

Expand Down Expand Up @@ -168,18 +169,20 @@
// without an arrow, and the arrow at the end of Oak's lines may be missed,
// so a message box gets A every 1.5 s whatever it shows. The player's name
// is asked on the keyboard straight away: B deletes whatever a stray A
// typed and START jumps to OK, which confirms the empty name as the
// default. The rival's is asked on a list with NEW NAME first, where A
// would open the keyboard: the cursor goes to the first preset name (the
// game's default rival, GREEN in FireRed, RED in LeafGreen) and A picks
// it, then YES confirms it (pokefirered oak_speech.c). If the keyboard
// opens anyway, the list was missed, and the empty name is confirmed
// there instead, which also names the rival GREEN.
// typed, then the name is typed, or START jumps to OK, which confirms the
// empty name as the default. The rival's is asked on a list with NEW NAME
// first, where A would open the keyboard: the cursor goes to the first
// preset name (the game's default rival, GREEN in FireRed, RED in
// LeafGreen) and A picks it, then YES confirms it (pokefirered
// oak_speech.c). If the keyboard opens anyway, the list was missed, and
// the empty name is confirmed there instead, which also names the rival
// GREEN.
class NewGameIntro{
public:
NewGameIntro(ConsoleHandle& console, ProControllerContext& context)
NewGameIntro(ConsoleHandle& console, ProControllerContext& context, std::string player_name)
: m_console(console)
, m_context(context)
, m_player_name(std::move(player_name))
{}

void run(){
Expand Down Expand Up @@ -262,12 +265,12 @@
switch (intro_name_step(shown, m_keyboards_seen, m_presets_picked)){
case IntroNameStep::PLAYER_KEYBOARD:
m_keyboards_seen++;
enter_empty_name();
enter_name(true);
return true;
case IntroNameStep::RIVAL_KEYBOARD:
m_keyboards_seen++;
m_console.log("New game: the rival's keyboard opened, so the name list was missed.", COLOR_ORANGE);
enter_empty_name();
enter_name(false);
return true;
case IntroNameStep::RIVAL_PRESET:
pick_rival_preset(*list_row);
Expand Down Expand Up @@ -317,18 +320,30 @@
return false;
}

void enter_empty_name(){
m_console.log("New game: the keyboard, confirming an empty name.");
void enter_name(bool player){
for (int c = 0; c < 8; c++){
press(m_context, BUTTON_B, 340ms);
}
press(m_context, BUTTON_PLUS, 500ms);
press(m_context, BUTTON_A, 2000ms);
if (!player || m_player_name.empty()){
m_console.log("New game: the keyboard, confirming an empty name.");
press(m_context, BUTTON_PLUS, 500ms);
press(m_context, BUTTON_A, 2000ms);
m_context.wait_for_all_requests();
return;
}
if (!type_name(m_console, m_context, m_player_name, NamingScreenKind::PLAYER)){
OperationFailedExceptionWithScreenshot::fire(
ErrorReportMode::SEND_ERROR_REPORT, "play_new_game_intro(): the keyboard closed before the name was typed.", m_console
);
}
// Past the keyboard's fade, so it isn't taken for the rival's.
pbf_wait(m_context, 2000ms);
m_context.wait_for_all_requests();
}

ConsoleHandle& m_console;
ProControllerContext& m_context;
const std::string m_player_name;
WallClock m_last_dialog;
WallClock m_last_press;
bool m_dialog_seen = false;
Expand Down Expand Up @@ -834,9 +849,9 @@
mash_until_overworld(console, context, 20000ms);
}

void play_new_game_intro(ConsoleHandle& console, ProControllerContext& context){
void play_new_game_intro(ConsoleHandle& console, ProControllerContext& context, const std::string& player_name){
console.log("New game: from the title screen.");
NewGameIntro(console, context).run();
NewGameIntro(console, context, player_name).run();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,12 @@ void buy_items(
);

// From the title screen of a game with no save, to the bedroom: the
// controls pages, Oak's speech, BOY, the default name for the player (an
// empty name confirmed) and the rival's first preset name. Returns on the
// overworld.
void play_new_game_intro(ConsoleHandle& console, ProControllerContext& context);
// controls pages, Oak's speech, BOY, `player_name` typed on the player's
// keyboard, and the rival's first preset name. An empty `player_name`
// keeps the player's default name (an empty name confirmed). Returns on
// the overworld. Throws UserSetupError if the game can't take the name
// (naming_text_error()).
void play_new_game_intro(ConsoleHandle& console, ProControllerContext& context, const std::string& player_name);



Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* AutoStory Events

Check warning on line 1 in SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

<file>: file_lines=528 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -389,7 +389,7 @@
}

Outcome StoryExecutor::new_game(RunState&, const StoryStep&){
play_new_game_intro(m_env.console, m_env.context);
play_new_game_intro(m_env.console, m_env.context, m_env.player_name);
return Pokemon::outcome_ok();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ struct StoryEnv{
// The game's language, for reading item names. In one the readers have
// no names for, the checks fall back to positions and counts.
Language language = Language::English;
// The name the new game's intro types for the player; empty keeps the
// game's default name.
std::string player_name = {};
// The map the checkpoint under way starts on, where its save is: a
// reload checks the save card against it.
std::string start_map = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ StoryStep shop(std::string name, std::vector<Pokemon::StoryItem> items, std::str
StoryCheckpoint new_game(){
return row("00.01", "new game to the bedroom", std::nullopt, {}, StartMenu::BEFORE_POKEMON, {
// new_game.c: the bedroom at (6, 6), facing the TV.
new_game_intro(at(BEDROOM, 6, 6, NORTH), "Oak's speech, BOY, default names, rival GREEN"),
new_game_intro(at(BEDROOM, 6, 6, NORTH), "Oak's speech, BOY, the player's name (default if none is set), rival GREEN"),
make(StoryStepKind::OPTIONS, "options", "fast text; no switch prompt between a trainer's Pokemon"),
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* Naming Screen
*
* From: https://github.com/PokemonAutomation/
*
*/

#include "Common/Cpp/Color.h"
#include "Common/Cpp/Exceptions.h"
#include "Common/Cpp/Time.h"
#include "CommonTools/Async/InferenceRoutines.h"
#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
#include "VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_NamingScreenDetector.h"
#include "PokemonFRLG_NamingScreen.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{


namespace{

// The game reads new presses once per frame and starts repeating a held direction after
// about 40 frames, so a short hold with a release of several frames is one press each.
constexpr Milliseconds PRESS_HOLD = 80ms;
constexpr Milliseconds MOVE_RELEASE = 120ms;
constexpr Milliseconds KEY_RELEASE = 200ms;
// The page swap animation runs 32 frames (about 530 ms) with input disabled.
constexpr Milliseconds PAGE_SWAP_RELEASE = 900ms;
// After the last character fits, the cursor animates to OK with input disabled.
constexpr Milliseconds START_RELEASE = 500ms;

void press(ProControllerContext& context, NamingAction action){
switch (action){
case NamingAction::UP:
pbf_press_dpad(context, DPAD_UP, PRESS_HOLD, MOVE_RELEASE);
return;
case NamingAction::DOWN:
pbf_press_dpad(context, DPAD_DOWN, PRESS_HOLD, MOVE_RELEASE);
return;
case NamingAction::LEFT:
pbf_press_dpad(context, DPAD_LEFT, PRESS_HOLD, MOVE_RELEASE);
return;
case NamingAction::RIGHT:
pbf_press_dpad(context, DPAD_RIGHT, PRESS_HOLD, MOVE_RELEASE);
return;
case NamingAction::A:
pbf_press_button(context, BUTTON_A, PRESS_HOLD, KEY_RELEASE);
return;
case NamingAction::SELECT:
pbf_press_button(context, BUTTON_MINUS, PRESS_HOLD, PAGE_SWAP_RELEASE);
return;
case NamingAction::START:
pbf_press_button(context, BUTTON_PLUS, PRESS_HOLD, START_RELEASE);
return;
}
}

void check_name(ConsoleHandle& console, const std::string& text, NamingScreenKind kind){
const std::string error = naming_text_error(text, kind);
if (!error.empty()){
throw UserSetupError(console, error);
}
}

bool press_on_naming_screen(
ConsoleHandle& console, ProControllerContext& context,
const std::string& text, NamingScreenKind kind, const std::vector<NamingAction>& actions
){
NamingScreenWatcher naming_screen(COLOR_RED);
context.wait_for_all_requests();
if (wait_until(console, context, std::chrono::seconds(5), {naming_screen}) < 0){
console.log("Naming screen not found. Not typing \"" + text + "\".", COLOR_RED);
return false;
}

console.log("Typing " + std::string(naming_screen_kind_name(kind)) + " name \"" + text + "\": " + to_string(actions));
for (NamingAction action : actions){
press(context, action);
}
context.wait_for_all_requests();
return true;
}

}


bool type_name(
ConsoleHandle& console, ProControllerContext& context,
const std::string& text, NamingScreenKind kind
){
check_name(console, text, kind);
return press_on_naming_screen(console, context, text, kind, plan_naming_input(text, kind));
}

bool type_name_without_confirming(
ConsoleHandle& console, ProControllerContext& context,
const std::string& text, NamingScreenKind kind
){
check_name(console, text, kind);
return press_on_naming_screen(console, context, text, kind, plan_naming_keys(text, kind));
}


}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* Naming Screen
*
* From: https://github.com/PokemonAutomation/
*
* Types a name on the in-game naming screen (player, rival, box, Pokemon nickname).
*
*/

#ifndef PokemonAutomation_PokemonFRLG_NamingScreen_H
#define PokemonAutomation_PokemonFRLG_NamingScreen_H

#include <string>
#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h"
#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h"
#include "PokemonFRLG_NamingScreenKeyboard.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{


// Types `text` on a naming screen that was just opened (cursor on "A", nothing typed) and
// presses OK. Returns once OK is pressed; what follows (e.g. "So your name is ...?") is up to
// the caller.
// Returns false, without pressing anything, if the naming screen is not on screen.
// Throws UserSetupError if the game can't take `text` (see naming_text_error()).
bool type_name(
ConsoleHandle& console, ProControllerContext& context,
const std::string& text, NamingScreenKind kind
);

// Like type_name(), but leaves the name on screen with OK not pressed, for a program that
// confirms the name itself (SID Helper presses PLUS then A on its own timing).
bool type_name_without_confirming(
ConsoleHandle& console, ProControllerContext& context,
const std::string& text, NamingScreenKind kind
);


}
}
}
#endif
Loading
Loading