From 69272083fd773778d053bc4f29b9c4f88f87cdd3 Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 18:26:40 -0800 Subject: [PATCH 1/5] FRLG AutoStory: every story checkpoint as an authored JSON plan The authored checkpoint format gains what the FRLG table uses: sections (each event logged with its section), notes on checkpoints, sections and events, once_per_save, and FRLG execution fields for supplies, live start and handoff. Variants are checked against what FRLG's executor does. StoryData/frlg-.plan.json is each C++ row exported once (the export test), split into sections, with the table's design comments as notes. Tests compare every JSON row with its C++ row and the whole story's sim trace with the C++ table's and a golden copy of it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../Pokemon/AutoStory/Pokemon_StoryEvents.cpp | 12 +- .../Pokemon/AutoStory/Pokemon_StoryEvents.h | 9 +- .../AutoStory/Pokemon_StoryStepJson.cpp | 107 +- .../Pokemon/AutoStory/Pokemon_StorySteps.cpp | 34 +- .../Pokemon/AutoStory/Pokemon_StorySteps.h | 21 + .../AutoStory/Data/AuthoredStory_Tests.cpp | 222 +++ .../AutoStory/Data/AuthoredStory_Tests.h | 30 + .../Programs/AutoStory/Data/JsonParser.cpp | 190 ++- .../Programs/AutoStory/Data/JsonParser.h | 9 + .../Programs/AutoStory/Data/Json_Tests.cpp | 2 + .../AutoStory/PokemonFRLG_AutoStoryEvents.cpp | 4 + .../ReplayData/frlg-story-sim-trace.json | 1208 +++++++++++++++++ .../AutoStory/StoryData/frlg-00.01.plan.json | 31 + .../AutoStory/StoryData/frlg-00.01b.plan.json | 33 + .../AutoStory/StoryData/frlg-00.02.plan.json | 24 + .../AutoStory/StoryData/frlg-00.03.plan.json | 39 + .../AutoStory/StoryData/frlg-00.04.plan.json | 27 + .../AutoStory/StoryData/frlg-00.04b.plan.json | 68 + .../AutoStory/StoryData/frlg-00.06.plan.json | 35 + .../AutoStory/StoryData/frlg-00.07.plan.json | 101 ++ .../AutoStory/StoryData/frlg-00.08.plan.json | 78 ++ .../AutoStory/StoryData/frlg-00.09.plan.json | 74 + .../AutoStory/StoryData/frlg-00.10.plan.json | 51 + .../AutoStory/StoryData/frlg-00.11.plan.json | 128 ++ .../AutoStory/StoryData/frlg-00.12.plan.json | 43 + .../AutoStory/StoryData/frlg-00.12b.plan.json | 28 + .../AutoStory/StoryData/frlg-00.12c.plan.json | 34 + .../AutoStory/StoryData/frlg-00.13.plan.json | 189 +++ .../AutoStory/StoryData/frlg-00.14.plan.json | 96 ++ .../AutoStory/StoryData/frlg-00.14b.plan.json | 96 ++ .../AutoStory/StoryData/frlg-00.14c.plan.json | 71 + .../AutoStory/StoryData/frlg-00.15.plan.json | 93 ++ .../AutoStory/StoryData/frlg-00.15b.plan.json | 40 + .../AutoStory/StoryData/frlg-00.16.plan.json | 173 +++ .../AutoStory/StoryData/frlg-00.17.plan.json | 146 ++ .../AutoStory/StoryData/frlg-00.18.plan.json | 112 ++ .../AutoStory/StoryData/frlg-00.19.plan.json | 47 + .../AutoStory/StoryData/frlg-00.20.plan.json | 75 + .../AutoStory/StoryData/frlg-00.21.plan.json | 94 ++ .../AutoStory/StoryData/frlg-00.22.plan.json | 160 +++ .../AutoStory/StoryData/frlg-00.23.plan.json | 178 +++ .../AutoStory/StoryData/frlg-00.24.plan.json | 115 ++ .../AutoStory/StoryData/frlg-00.25.plan.json | 72 + .../AutoStory/StoryData/frlg-00.26.plan.json | 156 +++ .../AutoStory/StoryData/frlg-00.27.plan.json | 202 +++ .../AutoStory/StoryData/frlg-00.28.plan.json | 355 +++++ .../AutoStory/StoryData/frlg-00.29.plan.json | 70 + SerialPrograms/cmake/SourceFiles.cmake | 2 + 48 files changed, 5158 insertions(+), 26 deletions(-) create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.h create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/ReplayData/frlg-story-sim-trace.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.01.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.01b.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.02.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.03.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.04.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.04b.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.06.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.07.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.08.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.09.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.10.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.11.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12b.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12c.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.13.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14b.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14c.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15b.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.16.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.17.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.18.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.19.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.20.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.21.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.22.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.23.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.24.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.25.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.26.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.27.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.28.plan.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.29.plan.json diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.cpp b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.cpp index ad22d7a6a7..75914a491b 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.cpp +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.cpp @@ -111,8 +111,14 @@ const char* recovery_name(RecoveryKind kind){ } -std::string event_log_line(const std::string& checkpoint, size_t number, const std::string& event, const std::string& status){ - return std::format("[Event] {}/{} {}: {}", checkpoint, number, event, status); +std::string event_log_line( + const std::string& checkpoint, size_t number, const std::string& event, const std::string& status, + const std::string& section +){ + if (section.empty()){ + return std::format("[Event] {}/{} {}: {}", checkpoint, number, event, status); + } + return std::format("[Event] {}/{} [{}] {}: {}", checkpoint, number, section, event, status); } @@ -198,7 +204,7 @@ struct EventRun{ }; void log_status(EventRun& run, const std::string& status){ - run.log(event_log_line(run.checkpoint.id, run.number, run.event.name, status)); + run.log(event_log_line(run.checkpoint.id, run.number, run.event.name, status, run.event.section)); } diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.h b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.h index 388ce48b03..739b817f6f 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.h +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.h @@ -165,6 +165,8 @@ struct Recovery{ struct Event{ std::string name; std::string why; + // The checkpoint's section the event is in, for the log; may be empty. + std::string section; // Skipped when all of these hold (and there is at least one). std::vector skip_if; // Must hold before `act`; a failure is "require_failed". @@ -240,7 +242,12 @@ using EventLogger = MoveOnlyFunction; // One log line, in a format the lab watcher parses: // [Event] 00.04/3 talk_mom: start // [Event] 00.04/3 talk_mom: failed: expect_failed: has_parcel -std::string event_log_line(const std::string& checkpoint, size_t number, const std::string& event, const std::string& status); +// An event in a section names it before the event: +// [Event] 00.28/12 [b1f_east] winston_line: start +std::string event_log_line( + const std::string& checkpoint, size_t number, const std::string& event, const std::string& status, + const std::string& section = "" +); // Problems an author should fix before running: unnamed or empty events, // missing actions, recoveries out of escalation order. diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp index 3ee2f51f6d..f433f9ec90 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp @@ -275,12 +275,18 @@ JsonValue story_step_json(const StoryStep& step){ if (step.pose_after){ result["declared_after"] = story_pose_json(*step.pose_after); } + if (step.once_per_save){ + result["once_per_save"] = true; + } + if (!step.notes.empty()){ + result["notes"] = strings_json(step.notes); + } return result; } StoryStep read_story_step(const JsonValue& value, std::string_view source){ const JsonObject& root = fields(value, {"id", "kind", "why", "target", "direction", "legs", "fight_wild", "yes", - "may_lose", "battles", "items", "variant", "skip_if", "declared_after"}, source); + "may_lose", "battles", "items", "variant", "skip_if", "declared_after", "once_per_save", "notes"}, source); StoryStep result; result.name = root.get_string_throw("id"); if (result.name.empty()){ @@ -312,26 +318,80 @@ StoryStep read_story_step(const JsonValue& value, std::string_view source){ if (const JsonValue* field = root.get_value("declared_after")){ result.pose_after = read_story_pose(*field, source); } + if (const JsonValue* field = root.get_value("once_per_save")){ + result.once_per_save = field->to_boolean_throw(); + } + if (const JsonValue* field = root.get_value("notes")){ + result.notes = read_strings(*field, source); + } + return result; +} + +namespace{ + +JsonArray events_json(const std::vector& steps, const std::string& section){ + JsonArray result; + for (const StoryStep& step : steps){ + if (step.section == section){ + result.push_back(story_step_json(step)); + } + } + return result; +} + +JsonArray sections_json(const StoryCheckpoint& checkpoint){ + JsonArray result; + for (const StorySection& section : checkpoint.sections){ + JsonObject item; + item["id"] = section.id; + item["title"] = section.title; + if (!section.notes.empty()){ + item["notes"] = strings_json(section.notes); + } + item["events"] = events_json(checkpoint.steps, section.id); + result.push_back(std::move(item)); + } return result; } +// `events` read into `checkpoint`'s steps, in `section`, each id unique +// across the checkpoint (`names`). +void read_events( + StoryCheckpoint& checkpoint, std::set& names, + const JsonValue& events, const std::string& section, std::string_view source +){ + for (const JsonValue& event : events.to_array_throw(std::string(source))){ + StoryStep step = read_story_step(event, source); + if (step.name.empty() || !names.insert(step.name).second){ + throw ParseException("Empty or duplicate story event id."); + } + step.section = section; + checkpoint.steps.push_back(std::move(step)); + } +} + +} // namespace + JsonValue story_checkpoint_json(const StoryCheckpoint& checkpoint){ JsonObject result; result["id"] = checkpoint.id; result["title"] = checkpoint.title; + if (!checkpoint.notes.empty()){ + result["notes"] = strings_json(checkpoint.notes); + } if (checkpoint.start){ result["start"] = story_pose_json(*checkpoint.start); } - JsonArray events; - for (const StoryStep& step : checkpoint.steps){ - events.push_back(story_step_json(step)); + if (checkpoint.sections.empty()){ + result["events"] = events_json(checkpoint.steps, ""); + }else{ + result["sections"] = sections_json(checkpoint); } - result["events"] = std::move(events); return result; } StoryCheckpoint read_story_checkpoint(const JsonValue& value, std::string_view source){ - const JsonObject& root = fields(value, {"id", "title", "start", "events"}, source); + const JsonObject& root = fields(value, {"id", "title", "notes", "start", "events", "sections"}, source); StoryCheckpoint result; result.id = root.get_string_throw("id"); if (result.id.empty()){ @@ -341,13 +401,38 @@ StoryCheckpoint read_story_checkpoint(const JsonValue& value, std::string_view s if (const JsonValue* start = root.get_value("start")){ result.start = read_story_pose(*start, source); } + if (const JsonValue* notes = root.get_value("notes")){ + result.notes = read_strings(*notes, source); + } + const JsonValue* events = root.get_value("events"); + const JsonValue* sections = root.get_value("sections"); + if ((events == nullptr) == (sections == nullptr)){ + throw ParseException(std::format("{}: a checkpoint has events or sections, not both or neither.", source)); + } std::set names; - for (const JsonValue& event : root.get_array_throw("events")){ - StoryStep step = read_story_step(event, source); - if (step.name.empty() || !names.insert(step.name).second){ - throw ParseException("Empty or duplicate story event id."); + if (events){ + read_events(result, names, *events, "", source); + return result; + } + std::set section_ids; + for (const JsonValue& item : sections->to_array_throw(std::string(source))){ + const JsonObject& section = fields(item, {"id", "title", "notes", "events"}, source); + StorySection header{.id = section.get_string_throw("id"), .title = section.get_string_throw("title"), .notes = {}}; + if (header.id.empty() || !section_ids.insert(header.id).second){ + throw ParseException(std::format("{}: empty or duplicate section id.", source)); + } + if (const JsonValue* notes = section.get_value("notes")){ + header.notes = read_strings(*notes, source); } - result.steps.push_back(std::move(step)); + const size_t before = result.steps.size(); + read_events(result, names, section.get_value_throw("events"), header.id, source); + if (result.steps.size() == before){ + throw ParseException(std::format("{}: section {} has no events.", source, header.id)); + } + result.sections.push_back(std::move(header)); + } + if (result.sections.empty()){ + throw ParseException(std::format("{}: no sections.", source)); } return result; } diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StorySteps.cpp b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StorySteps.cpp index 3004ba6013..2e3d19b5d9 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StorySteps.cpp +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StorySteps.cpp @@ -163,6 +163,7 @@ Event story_event(const StoryStep& step, StoryStepExecutor& executor){ Event event; event.name = step.name; event.why = step.why; + event.section = step.section; for (const std::string& name : step.skip_if){ event.skip_if.push_back(condition(name, [&executor, name](RunState& state){ return executor.holds(name, state); @@ -267,8 +268,39 @@ std::string missing_field(const StoryStep& step){ } // namespace -std::vector lint_story_checkpoint(const StoryCheckpoint& data){ +namespace{ + +// With sections, each step's names one, in the sections' order, and every +// section has a step. +std::vector section_problems(const StoryCheckpoint& data){ std::vector problems; + if (data.sections.empty()){ + return problems; + } + size_t at = 0; + std::vector used(data.sections.size(), false); + for (const StoryStep& step : data.steps){ + while (at < data.sections.size() && data.sections[at].id != step.section){ + at++; + } + if (at == data.sections.size()){ + problems.push_back(std::format("{} {}: section {} is not one of the checkpoint's, in order", data.id, step.name, step.section)); + return problems; + } + used[at] = true; + } + for (size_t c = 0; c < used.size(); c++){ + if (!used[c]){ + problems.push_back(std::format("{}: section {} has no step", data.id, data.sections[c].id)); + } + } + return problems; +} + +} // namespace + +std::vector lint_story_checkpoint(const StoryCheckpoint& data){ + std::vector problems = section_problems(data); if (data.id.empty()){ problems.emplace_back("a checkpoint has no id"); } diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StorySteps.h b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StorySteps.h index 27aaabad8a..bdfd089636 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StorySteps.h +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StorySteps.h @@ -134,6 +134,21 @@ struct StoryStep{ // Where the step leaves the player, when its action does not say: the // end of a scene that moves the player. std::optional pose_after; + // The id of the checkpoint's section the step is in (StorySection); + // empty for a checkpoint without sections. Logged with the event. + std::string section; + // The author's notes on the step: why it is done this way, what was + // seen on the console. Not read by any executor. + std::vector notes; +}; + +// A part of a checkpoint, e.g. one floor of a cave: a run of steps that +// name it in their `section`. Steps are numbered across the checkpoint, +// not per section. +struct StorySection{ + std::string id; + std::string title; + std::vector notes; }; // Milestone row in the story table grouping related steps and starting pose. @@ -143,6 +158,12 @@ struct StoryCheckpoint{ // Where its starting save leaves the player; none for a new game. std::optional start; std::vector steps; + // The author's notes on the whole checkpoint: the maps, trainers and + // numbers its steps were planned from. + std::vector notes; + // In order; each step's `section` names one of them. Empty when the + // checkpoint is not split. + std::vector sections; }; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp new file mode 100644 index 0000000000..8392d8d7da --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp @@ -0,0 +1,222 @@ +/* FRLG Authored Story Tests + * From: https://github.com/PokemonAutomation/ + * + * The story table as loaded from the StoryData plans against the C++ + * rows it was exported from: the same events, and the same trace of the + * whole story on the story maps (StoryMapsSim). + * + */ + +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "Common/Cpp/Filesystem/FileIO.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "Common/Cpp/TestRunners/UnitTest.h" +#include "Pokemon/AutoStory/Pokemon_StorySegments.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_StoryReplay.h" +#include "JsonParser.h" +#include "JsonWriter.h" +#include "AuthoredStory_Tests.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace{ + +// "[Event] 00.28/12 [b1f_east] winston_line: start" without its section, +// as the table logged it before it had sections. +std::string without_section(const std::string& line){ + const size_t open = line.find(" [", 1); + const size_t close = open == std::string::npos ? open : line.find("] ", open); + if (!line.starts_with("[Event] ") || close == std::string::npos){ + return line; + } + return line.substr(0, open) + line.substr(close + 1); +} + +// The story on the story maps, each step's end pose traced. +class TraceGame : public StoryMapsSim{ +public: + std::vector trace; + + virtual void begin_attempt(const Pokemon::StoryCheckpoint& data) override{ + StoryMapsSim::begin_attempt(data); + m_row = data.id; + trace.push_back("begin " + data.id); + } + virtual Pokemon::Outcome run(Pokemon::RunState& state, const StoryStep& step) override{ + Pokemon::Outcome outcome = StoryMapsSim::run(state, step); + trace.push_back(std::format("{}/{}: {} at {}", m_row, step.name, outcome.succeeded() ? "ok" : "failed", + outcome.pose ? Pokemon::to_string(*outcome.pose) : "-")); + return outcome; + } + +private: + std::string m_row; +}; + +} // namespace + +std::vector story_sim_trace(const std::vector& table){ + const std::vector rows = plan_story_run(table, 0, table.size() - 1, StoryHandoffs{}).rows; + TraceGame game; + game.set_language(Language::Japanese); + Pokemon::Segment segment; + segment.id = "00"; + for (const StoryCheckpoint& data : rows){ + segment.checkpoints.push_back(Pokemon::CheckpointSpec{ + .id = data.id, .title = data.title, .saves = true, + .build = [&game, &data]{ + return Pokemon::build_story_checkpoint(data, game); + }, + }); + } + Pokemon::SegmentHooks hooks; + hooks.save = [&game](Pokemon::RunState& state){ + game.trace.push_back("save at " + Pokemon::to_string(state.pose)); + return Pokemon::outcome_ok(); + }; + hooks.reload = [&game](Pokemon::RunState&){ + game.trace.push_back("reload"); + return Pokemon::outcome_ok(); + }; + hooks.log = [&game](const std::string& line){ + game.trace.push_back(without_section(line)); + }; + Pokemon::RunState state; + const Pokemon::SegmentReport report = Pokemon::run_segment(state, segment, {}, Pokemon::SegmentPolicy{}, hooks); + game.trace.push_back(report.completed ? "completed" : "not completed"); + game.trace.push_back(stub_report(game.stubs().rows())); + return game.trace; +} + + +namespace{ + +// The row as the C++ table built it: no notes or sections. +JsonValue plain_export(StoryCheckpoint row){ + row.notes.clear(); + row.sections.clear(); + for (StoryStep& step : row.steps){ + step.notes.clear(); + step.section.clear(); + } + return story_checkpoint_export(row); +} + +std::string compare_rows(const StoryCheckpoint& authored, const StoryCheckpoint& table){ + const JsonValue a = plain_export(authored); + const JsonValue b = plain_export(table); + if (!(a == b)){ + return std::format("{}: authored {}\nC++ {}", table.id, a.dump(-1), b.dump(-1)); + } + if (authored.live_start != table.live_start || authored.handoff != table.handoff){ + return table.id + ": live start or handoff differ."; + } + return ""; +} + +UnitTestResult test_rows_match(){ + const std::vector authored = load_authored_story(); + const std::vector& table = story_checkpoints(); + if (authored.size() != table.size()){ + return std::format("Expected {} authored rows, read {}.", table.size(), authored.size()); + } + for (size_t c = 0; c < table.size(); c++){ + const std::string error = compare_rows(authored[c], table[c]); + if (!error.empty()){ + return error; + } + } + return true; +} + +UnitTestResult test_traces_match(){ + const std::vector authored = story_sim_trace(load_authored_story()); + const std::vector table = story_sim_trace(story_checkpoints()); + for (size_t c = 0; c < std::min(authored.size(), table.size()); c++){ + if (authored[c] != table[c]){ + return std::format("Trace line {}: authored \"{}\", C++ \"{}\".", c, authored[c], table[c]); + } + } + if (authored.size() != table.size()){ + return std::format("Expected {} trace lines, read {}.", table.size(), authored.size()); + } + return true; +} + +// The story table's trace against the golden one, taken from the C++ +// rows before they moved to StoryData (ReplayData/frlg-story-sim-trace.json). +UnitTestResult test_trace_golden(){ + const JsonValue fixture = parse_json(story_replay_fixture("frlg-story-sim-trace")); + const JsonArray& golden = fixture.to_object_throw().get_array_throw("trace"); + const std::vector trace = story_sim_trace(story_checkpoints()); + for (size_t c = 0; c < std::min(golden.size(), trace.size()); c++){ + if (golden[c].to_string_throw() != trace[c]){ + return std::format("Trace line {}: golden \"{}\", read \"{}\".", c, golden[c].to_string_throw(), trace[c]); + } + } + if (golden.size() != trace.size()){ + return std::format("Expected {} trace lines, read {}.", golden.size(), trace.size()); + } + return true; +} + +// Migration only: writes each C++ row as its authored plan, and the C++ +// table's trace, to frlg-story-export/ under the working directory. +UnitTestResult test_export(){ + const std::string folder = "frlg-story-export/"; + Filesystem::create_directories(Filesystem::Path(folder)); + for (const StoryCheckpoint& row : story_checkpoints()){ + string_to_file(folder + "frlg-" + row.id + ".plan.json", authored_checkpoint_json(row).dump(2)); + } + JsonArray lines; + for (const std::string& line : story_sim_trace(story_checkpoints())){ + lines.push_back(line); + } + string_to_file(folder + "frlg-story-sim-trace.json", JsonValue(std::move(lines)).dump(1)); + return true; +} + +struct AuthoredCase{ + std::string name; + UnitTestResult (*body)(); +}; + +class AuthoredTest : public UnitTest{ +public: + explicit AuthoredTest(AuthoredCase test) + : UnitTest("PokemonFRLG::AutoStory - Authored: " + test.name), m_test(std::move(test)){} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + try{ + return m_test.body(); + }catch (Exception& e){ + return std::string(e.name()) + ": " + e.message(); + } + } +private: + AuthoredCase m_test; +}; + +} // namespace + + +void add_tests_AuthoredStory(UnitTestDatabase& database){ + database.add(AuthoredCase{"every row as authored is the C++ row", test_rows_match}); + database.add(AuthoredCase{"the story sim's trace is the C++ table's", test_traces_match}); + database.add(AuthoredCase{"export the C++ rows", test_export}); + database.add(AuthoredCase{"the story sim's trace is the golden one", test_trace_golden}); +} + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.h new file mode 100644 index 0000000000..be7c0c092c --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.h @@ -0,0 +1,30 @@ +/* FRLG Authored Story Tests + * From: https://github.com/PokemonAutomation/ + */ + +#ifndef PokemonAutomation_PokemonFRLG_AuthoredStory_Tests_H +#define PokemonAutomation_PokemonFRLG_AuthoredStory_Tests_H + +#include +#include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +// The whole story of `table` on the story maps (StoryMapsSim), every +// handoff off: each step's end, each event's log line without its +// section, each save's pose, and the steps the sim stubbed. +std::vector story_sim_trace(const std::vector& table); + +// The authored story table against its golden trace. +void add_tests_AuthoredStory(UnitTestDatabase& database); + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp index 780b3a9a88..e84e3eaa07 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp @@ -2,6 +2,7 @@ * From: https://github.com/PokemonAutomation/ */ +#include #include #include #include @@ -11,6 +12,15 @@ #include "Common/Cpp/Json/JsonObject.h" #include "Pokemon/AutoStory/Pokemon_StoryStepJson.h" #include "Pokemon/AutoStory/Pokemon_AuthoredCheckpointJson.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMember.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryParty.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTrashCans.h" #include "LegacyJsonFormat.h" #include "JsonParser.h" @@ -175,20 +185,142 @@ void check_envelope(const JsonObject& root, std::string_view source){ } } +constexpr std::pair HANDOFFS[]{ + {StoryHandoff::NONE, "none"}, + {StoryHandoff::SID_HELPER, "sid_helper"}, + {StoryHandoff::STARTER_RNG, "starter_rng"}, + {StoryHandoff::GIFT_RNG, "gift_rng"}, +}; + +// The healing items a checkpoint starts with, and its lead: a new game's +// unless "lead" names the one after Brock (story_lead_after_brock()). +Supplies read_authored_supplies(const JsonValue& value, std::string_view source){ + const JsonObject& root = fields(value, {"potions", "antidotes", "parlyz_heals", "lead"}, source); + Supplies result; + if (const JsonValue* field = root.get_value("potions")){ + result.potions = read_int(*field, source); + } + if (const JsonValue* field = root.get_value("antidotes")){ + result.antidotes = read_int(*field, source); + } + if (const JsonValue* field = root.get_value("parlyz_heals")){ + result.parlyz_heals = read_int(*field, source); + } + if (const JsonValue* field = root.get_value("lead")){ + if (field->to_string_throw(std::string(source)) != "after_brock"){ + throw ParseException(std::format("{}: unsupported lead {}", source, field->to_string_throw())); + } + result.lead = story_lead_after_brock(); + } + return result; +} + +// Whether `variant` names a GAME step FRLG's executor does. +bool known_game_variant(const std::string& variant){ + for (const std::string& name : {CLOSE_TRAINER_CARD, LEAVE_GIFT_SUMMARY, CUT_TREE_VARIANT, TRASH_CANS_VARIANT}){ + if (variant == name){ + return true; + } + } + return parse_capture_variant(variant) || parse_pc_variant(variant) || parse_mart_variant(variant) + || parse_teach_variant(variant) || parse_grind_variant(variant) || parse_grind_check_variant(variant) + || parse_fossil_variant(variant) || parse_level_variant(variant); +} + +// Whether FRLG does `step`'s kind the way its variant names. +bool known_variant(const StoryStep& step){ + switch (step.kind){ + case StoryStepKind::ROUTE: + case StoryStepKind::LEGS: + return step.variant.empty() || step.variant == RUN_VARIANT; + case StoryStepKind::STEP: + return step.variant.empty() || step.variant == TRAINER_LINE_VARIANT || step.variant == TRAINER_WAIT_VARIANT; + case StoryStepKind::GAME: + return known_game_variant(step.variant); + default: + return step.variant.empty(); + } +} + +// The checkpoint's steps against what FRLG can do: each variant known, +// and nothing lint_story_checkpoint() finds. +void check_steps(const StoryCheckpoint& checkpoint, std::string_view source){ + for (const StoryStep& step : checkpoint.steps){ + if (!known_variant(step)){ + throw ParseException(std::format("{}: {} has an unsupported variant \"{}\"", source, step.name, step.variant)); + } + } + const std::vector problems = Pokemon::lint_story_checkpoint(checkpoint); + if (!problems.empty()){ + throw ParseException(std::format("{}: {}", source, problems.front())); + } +} + StoryCheckpoint apply_execution(Pokemon::AuthoredCheckpoint parsed, std::string_view source){ const JsonObject& execution = parsed.execution; - Pokemon::require_story_json_fields(execution, {"menu"}, source); + for (const auto& [key, unused] : execution){ + if (key != "menu" && key != "supplies" && key != "live_start" && key != "handoff"){ + throw ParseException(std::format("{}: unsupported execution field {}", source, key)); + } + } StoryCheckpoint result; static_cast(result) = std::move(parsed.checkpoint); - const std::string& menu = execution.get_string_throw("menu"); - if (menu == "before_pokemon"){ - result.menu = StartMenu::BEFORE_POKEMON; - }else if (menu == "before_pokedex"){ - result.menu = StartMenu::BEFORE_POKEDEX; - }else if (menu == "full"){ - result.menu = StartMenu::FULL; - }else{ - throw ParseException("Unsupported authored checkpoint menu."); + result.menu = read_named({StartMenu::BEFORE_POKEMON, StartMenu::BEFORE_POKEDEX, StartMenu::FULL}, + StoryJsonInternal::menu_name, execution.get_string_throw("menu", std::string(source)), source); + if (const JsonValue* field = execution.get_value("supplies")){ + result.supplies = read_authored_supplies(*field, source); + } + if (const JsonValue* field = execution.get_value("live_start")){ + result.live_start = field->to_boolean_throw(std::string(source)); + } + if (const JsonValue* field = execution.get_value("handoff")){ + const std::string& name = field->to_string_throw(std::string(source)); + bool found = false; + for (const auto& [handoff, label] : HANDOFFS){ + if (name == label){ + result.handoff = handoff; + found = true; + } + } + if (!found){ + throw ParseException(std::format("{}: unsupported handoff {}", source, name)); + } + } + check_steps(result, source); + return result; +} + +// Whether two leads are the same as far as a checkpoint's row sets them. +bool same_lead(const PartyMember& a, const PartyMember& b){ + if (a.species != b.species || a.level != b.level || a.moves_through_level != b.moves_through_level + || a.moves_read != b.moves_read || a.moves.size() != b.moves.size() + ){ + return false; + } + for (size_t c = 0; c < a.moves.size(); c++){ + if (a.moves[c].move != b.moves[c].move || a.moves[c].pp != b.moves[c].pp){ + return false; + } + } + return true; +} + +// The supplies as authored: what differs from a new game's. +JsonObject authored_supplies_json(const Supplies& supplies){ + JsonObject result; + if (supplies.potions){ + result["potions"] = supplies.potions; + } + if (supplies.antidotes){ + result["antidotes"] = supplies.antidotes; + } + if (supplies.parlyz_heals){ + result["parlyz_heals"] = supplies.parlyz_heals; + } + if (same_lead(supplies.lead, story_lead_after_brock())){ + result["lead"] = "after_brock"; + }else if (!same_lead(supplies.lead, Supplies{}.lead)){ + throw ParseException("Only a new game's lead or the lead after Brock can be authored."); } return result; } @@ -204,6 +336,44 @@ StoryCheckpoint load_authored_checkpoint(std::string_view file){ return apply_execution(Pokemon::load_authored_checkpoint(autostory_checkpoint_json(), file, "frlg"), file); } +std::vector load_authored_story(){ + std::vector result; + for (const auto& [name, text] : autostory_checkpoint_json()){ + if (name.ends_with(".plan")){ + result.push_back(read_authored_checkpoint(parse_json(text), name)); + } + } + std::sort(result.begin(), result.end(), [](const StoryCheckpoint& a, const StoryCheckpoint& b){ + return a.id < b.id; + }); + for (size_t c = 1; c < result.size(); c++){ + if (result[c].id == result[c - 1].id){ + throw ParseException("Two authored checkpoints are " + result[c].id + "."); + } + } + return result; +} + +JsonValue authored_checkpoint_json(const StoryCheckpoint& checkpoint){ + JsonObject execution; + execution["menu"] = StoryJsonInternal::menu_name(checkpoint.menu); + JsonObject supplies = authored_supplies_json(checkpoint.supplies); + if (supplies.size() != 0){ + execution["supplies"] = std::move(supplies); + } + if (checkpoint.live_start){ + execution["live_start"] = true; + } + if (checkpoint.handoff != StoryHandoff::NONE){ + for (const auto& [handoff, label] : HANDOFFS){ + if (handoff == checkpoint.handoff){ + execution["handoff"] = std::string(label); + } + } + } + return Pokemon::authored_checkpoint_json(checkpoint, "frlg", std::move(execution)); +} + StoryCheckpoint read_story_checkpoint_export(const JsonValue& value, std::string_view source){ const JsonObject& root = fields(value, {"format", "game", "status", "checkpoint", "native_fields", "state_basis", "unbound", "source_revision"}, source); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h index 9b20466141..b931b7d677 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h @@ -24,6 +24,15 @@ StoryCheckpoint read_authored_checkpoint(const JsonValue& value, std::string_vie // Load an embedded authored plan by filename without .json; no controller IO. StoryCheckpoint load_authored_checkpoint(std::string_view file); +// Every embedded authored plan (StoryData/*.plan.json), in order of id: +// the story table. A malformed plan or two with one id throw ParseException. +std::vector load_authored_story(); + +// `checkpoint` as an authored plan: the shared checkpoint, and its START +// menu, supplies, live start and handoff as `execution`. Supplies beyond +// the counted items and the lead after Brock aren't authored. +JsonValue authored_checkpoint_json(const StoryCheckpoint& checkpoint); + // Embedded StoryData/*.json records: (filename without .json, text), sorted by name. const std::vector>& autostory_checkpoint_json(); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp index 0e712607a2..47c937a9cd 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp @@ -10,6 +10,7 @@ #include "JsonWriter.h" #include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h" #include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_StoryReplay.h" +#include "AuthoredStory_Tests.h" #include "Json_Tests.h" namespace PokemonAutomation{ @@ -271,6 +272,7 @@ class LoadTest : public UnitTest{ void add_tests_AutoStoryJson(UnitTestDatabase& database){ add_tests_StoryReplay(database); + add_tests_AuthoredStory(database); for (const ExportCase& test : CASES){ database.add(test); } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp index 13a39e6cae..97e0addd98 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp @@ -826,6 +826,10 @@ StoryCheckpoint joined_rows(const StoryCheckpoint& first, const StoryCheckpoint& row.live_start = first.live_start; row.steps = first.steps; row.steps.insert(row.steps.end(), second.steps.begin(), second.steps.end()); + row.notes = first.notes; + row.notes.insert(row.notes.end(), second.notes.begin(), second.notes.end()); + row.sections = first.sections; + row.sections.insert(row.sections.end(), second.sections.begin(), second.sections.end()); return row; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/ReplayData/frlg-story-sim-trace.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/ReplayData/frlg-story-sim-trace.json new file mode 100644 index 0000000000..282d79e0f5 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/ReplayData/frlg-story-sim-trace.json @@ -0,0 +1,1208 @@ +{ + "about": "story_sim_trace() of the FRLG story table (Data/AuthoredStory_Tests.h), taken from the C++ rows before they moved to StoryData.", + "trace": [ + "[Segment] 00: start, 33 checkpoints", + "begin 00.01", + "[Checkpoint] 00.01: start, 2 events", + "[Event] 00.01/1 intro: start", + "00.01/intro: ok at -", + "[Event] 00.01/1 intro: done", + "[Event] 00.01/2 options: start", + "00.01/options: ok at -", + "[Event] 00.01/2 options: done", + "save at PalletTown_PlayersHouse_2F (6, 6) facing north (tile)", + "[Checkpoint] 00.01: done, 2 run, 0 skipped", + "[Checkpoint] 00.01: passed on attempt 1 (one-shot)", + "begin 00.02", + "[Checkpoint] 00.02: start, 2 events", + "[Event] 00.02/1 from_save: start", + "[Event] 00.02/1 from_save: done", + "[Event] 00.02/2 front_door: start", + "00.02/front_door: ok at PalletTown (6, 8) facing south (tile)", + "[Event] 00.02/2 front_door: done", + "save at PalletTown (6, 8) facing south (tile)", + "[Checkpoint] 00.02: done, 2 run, 0 skipped", + "[Checkpoint] 00.02: passed on attempt 1 (one-shot)", + "begin 00.03", + "[Checkpoint] 00.03: start, 4 events", + "[Event] 00.03/1 from_save: start", + "[Event] 00.03/1 from_save: done", + "[Event] 00.03/2 below_oaks_trigger: start", + "00.03/below_oaks_trigger: ok at PalletTown (12, 2) facing north (tile)", + "[Event] 00.03/2 below_oaks_trigger: done", + "[Event] 00.03/3 oaks_trigger: start", + "00.03/oaks_trigger: ok at PalletTown (12, 1) facing north (tile)", + "[Event] 00.03/3 oaks_trigger: done", + "[Event] 00.03/4 to_the_lab: start", + "00.03/to_the_lab: ok at -", + "[Event] 00.03/4 to_the_lab: done", + "save at PalletTown_ProfessorOaksLab (6, 4) facing north (tile)", + "[Checkpoint] 00.03: done, 4 run, 0 skipped", + "[Checkpoint] 00.03: passed on attempt 1 (one-shot)", + "begin 00.04", + "[Checkpoint] 00.04: start, 9 events", + "[Event] 00.04/1 from_save: start", + "[Event] 00.04/1 from_save: done", + "[Event] 00.04/2 below_squirtle: start", + "00.04/below_squirtle: ok at PalletTown_ProfessorOaksLab (9, 5) facing north (tile)", + "[Event] 00.04/2 below_squirtle: done", + "[Event] 00.04/3 squirtle_ball: start", + "00.04/squirtle_ball: ok at -", + "[Event] 00.04/3 squirtle_ball: done", + "[Event] 00.04/4 choose_squirtle: start", + "00.04/choose_squirtle: ok at -", + "[Event] 00.04/4 choose_squirtle: done", + "[Event] 00.04/5 no_nickname: start", + "00.04/no_nickname: ok at -", + "[Event] 00.04/5 no_nickname: done", + "[Event] 00.04/6 rival_takes_bulbasaur: start", + "00.04/rival_takes_bulbasaur: ok at -", + "[Event] 00.04/6 rival_takes_bulbasaur: done", + "[Event] 00.04/7 above_rival_trigger: start", + "00.04/above_rival_trigger: ok at PalletTown_ProfessorOaksLab (7, 7) facing south (tile)", + "[Event] 00.04/7 above_rival_trigger: done", + "[Event] 00.04/8 rival_trigger: start", + "00.04/rival_trigger: ok at PalletTown_ProfessorOaksLab (7, 8) facing south (tile)", + "[Event] 00.04/8 rival_trigger: done", + "[Event] 00.04/9 rival_battle: start", + "00.04/rival_battle: ok at -", + "[Event] 00.04/9 rival_battle: done", + "save at PalletTown_ProfessorOaksLab (7, 8) facing south (tile)", + "[Checkpoint] 00.04: done, 9 run, 0 skipped", + "[Checkpoint] 00.04: passed on attempt 1 (one-shot)", + "begin 00.05", + "[Checkpoint] 00.05: start, 4 events", + "[Event] 00.05/1 from_save: start", + "[Event] 00.05/1 from_save: done", + "[Event] 00.05/2 below_mart_door: start", + "00.05/below_mart_door: ok at ViridianCity (36, 20) facing north (tile)", + "[Event] 00.05/2 below_mart_door: done", + "[Event] 00.05/3 mart_door: start", + "00.05/mart_door: ok at ViridianCity (36, 19) facing north (tile)", + "[Event] 00.05/3 mart_door: done", + "[Event] 00.05/4 parcel_from_clerk: start", + "00.05/parcel_from_clerk: ok at -", + "[Event] 00.05/4 parcel_from_clerk: done", + "save at ViridianCity_Mart (4, 3) facing west (tile)", + "[Checkpoint] 00.05: done, 4 run, 0 skipped", + "[Checkpoint] 00.05: passed on attempt 1 (one-shot)", + "begin 00.06", + "[Checkpoint] 00.06: start, 4 events", + "[Event] 00.06/1 from_save: start", + "[Event] 00.06/1 from_save: done", + "[Event] 00.06/2 below_oak: start", + "00.06/below_oak: ok at PalletTown_ProfessorOaksLab (6, 4) facing north (tile)", + "[Event] 00.06/2 below_oak: done", + "[Event] 00.06/3 give_parcel: start", + "00.06/give_parcel: ok at -", + "[Event] 00.06/3 give_parcel: done", + "[Event] 00.06/4 pokedex_scene: start", + "00.06/pokedex_scene: ok at -", + "[Event] 00.06/4 pokedex_scene: done", + "save at PalletTown_ProfessorOaksLab (6, 4) facing south (tile)", + "[Checkpoint] 00.06: done, 4 run, 0 skipped", + "[Checkpoint] 00.06: passed on attempt 1 (one-shot)", + "begin 00.07", + "[Checkpoint] 00.07: start, 10 events", + "[Event] 00.07/1 from_save: start", + "[Event] 00.07/1 from_save: done", + "[Event] 00.07/2 mart_clerk: start", + "00.07/mart_clerk: ok at ViridianCity_Mart (4, 3) facing west (tile)", + "[Event] 00.07/2 mart_clerk: done", + "[Event] 00.07/3 potions_and_antidotes: start", + "00.07/potions_and_antidotes: ok at -", + "[Event] 00.07/3 potions_and_antidotes: done", + "[Event] 00.07/4 viridian_nurse: start", + "00.07/viridian_nurse: ok at ViridianCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.07/4 viridian_nurse: done", + "[Event] 00.07/5 heal_viridian: start", + "00.07/heal_viridian: ok at -", + "[Event] 00.07/5 heal_viridian: done", + "[Event] 00.07/6 below_old_man_trigger: start", + "00.07/below_old_man_trigger: ok at ViridianCity (20, 9) facing north (tile)", + "[Event] 00.07/6 below_old_man_trigger: done", + "[Event] 00.07/7 old_man_trigger: start", + "00.07/old_man_trigger: ok at ViridianCity (20, 8) facing north (tile)", + "[Event] 00.07/7 old_man_trigger: done", + "[Event] 00.07/8 old_man_tutorial: start", + "00.07/old_man_tutorial: ok at -", + "[Event] 00.07/8 old_man_tutorial: done", + "[Event] 00.07/9 past_old_man: start", + "00.07/past_old_man: ok at ViridianCity (20, 5) facing north (tile)", + "[Event] 00.07/9 past_old_man: done", + "[Event] 00.07/10 forest_entrance: start", + "00.07/forest_entrance: ok at ViridianForest (29, 62) facing north (tile)", + "[Event] 00.07/10 forest_entrance: done", + "save at ViridianForest (29, 62) facing north (tile)", + "[Checkpoint] 00.07: done, 10 run, 0 skipped", + "[Checkpoint] 00.07: passed on attempt 1 (one-shot)", + "begin 00.08", + "[Checkpoint] 00.08: start, 7 events", + "[Event] 00.08/1 from_save: start", + "[Event] 00.08/1 from_save: done", + "[Event] 00.08/2 forest_east: start", + "00.08/forest_east: ok at ViridianForest (44, 43) facing north (tile)", + "[Event] 00.08/2 forest_east: done", + "[Event] 00.08/3 forest_north: start", + "00.08/forest_north: ok at ViridianForest (39, 25) facing west (tile)", + "[Event] 00.08/3 forest_north: done", + "[Event] 00.08/4 forest_exit: start", + "00.08/forest_exit: ok at ViridianForest (4, 13) facing east (tile)", + "[Event] 00.08/4 forest_exit: done", + "[Event] 00.08/5 forest_gate: start", + "00.08/forest_gate: ok at Route2_ViridianForest_NorthEntrance (7, 10) facing north (tile)", + "[Event] 00.08/5 forest_gate: done", + "[Event] 00.08/6 pewter_nurse: start", + "00.08/pewter_nurse: ok at PewterCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.08/6 pewter_nurse: done", + "[Event] 00.08/7 heal_pewter: start", + "00.08/heal_pewter: ok at -", + "[Event] 00.08/7 heal_pewter: done", + "save at PewterCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.08: done, 7 run, 0 skipped", + "[Checkpoint] 00.08: passed on attempt 1 (one-shot)", + "begin 00.09", + "[Checkpoint] 00.09: start, 8 events", + "[Event] 00.09/1 from_save: start", + "[Event] 00.09/1 from_save: done", + "[Event] 00.09/2 below_liam_line: start", + "00.09/below_liam_line: ok at PewterCity_Gym (6, 9) facing north (tile)", + "[Event] 00.09/2 below_liam_line: done", + "[Event] 00.09/3 liam_line: start", + "00.09/liam_line: ok at PewterCity_Gym (6, 8) facing north (tile)", + "[Event] 00.09/3 liam_line: done", + "[Event] 00.09/4 camper_liam: start", + "00.09/camper_liam: ok at -", + "[Event] 00.09/4 camper_liam: done", + "[Event] 00.09/5 ready_for_brock: start", + "00.09/ready_for_brock: ok at -", + "[Event] 00.09/5 ready_for_brock: done", + "[Event] 00.09/6 before_brock: start", + "00.09/before_brock: ok at PewterCity_Gym (6, 6) facing north (tile)", + "[Event] 00.09/6 before_brock: done", + "[Event] 00.09/7 challenge_brock: start", + "00.09/challenge_brock: ok at -", + "[Event] 00.09/7 challenge_brock: done", + "[Event] 00.09/8 brock: start", + "00.09/brock: ok at -", + "[Event] 00.09/8 brock: done", + "save at PewterCity_Gym (6, 6) facing north (tile)", + "[Checkpoint] 00.09: done, 8 run, 0 skipped", + "[Checkpoint] 00.09: passed on attempt 1 (one-shot)", + "begin 00.10", + "[Checkpoint] 00.10: start, 5 events", + "[Event] 00.10/1 from_save: start", + "[Event] 00.10/1 from_save: done", + "[Event] 00.10/2 before_aide_trigger: start", + "00.10/before_aide_trigger: ok at PewterCity (45, 21) facing east (tile)", + "[Event] 00.10/2 before_aide_trigger: done", + "[Event] 00.10/3 aide_trigger: start", + "00.10/aide_trigger: ok at PewterCity (46, 21) facing east (tile)", + "[Event] 00.10/3 aide_trigger: done", + "[Event] 00.10/4 shoes_from_aide: start", + "00.10/shoes_from_aide: ok at -", + "[Event] 00.10/4 shoes_from_aide: done", + "[Event] 00.10/5 below_pewter_center: start", + "00.10/below_pewter_center: ok at PewterCity (17, 26) facing south (tile)", + "[Event] 00.10/5 below_pewter_center: done", + "save at PewterCity (17, 26) facing south (tile)", + "[Checkpoint] 00.10: done, 5 run, 0 skipped", + "[Checkpoint] 00.10: passed on attempt 1 (one-shot)", + "begin 00.11", + "[Checkpoint] 00.11: start, 14 events", + "[Event] 00.11/1 from_save: start", + "[Event] 00.11/1 from_save: done", + "[Event] 00.11/2 pewter_nurse: start", + "00.11/pewter_nurse: ok at PewterCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.11/2 pewter_nurse: done", + "[Event] 00.11/3 heal_pewter: start", + "00.11/heal_pewter: ok at -", + "[Event] 00.11/3 heal_pewter: done", + "[Event] 00.11/4 west_of_janice_line: start", + "00.11/west_of_janice_line: ok at Route3 (13, 8) facing east (tile)", + "[Event] 00.11/4 west_of_janice_line: done", + "[Event] 00.11/5 below_colton_line: start", + "00.11/below_colton_line: ok at Route3 (14, 7) facing north (tile)", + "[Event] 00.11/5 below_colton_line: done", + "[Event] 00.11/6 colton_line: start", + "00.11/colton_line: ok at Route3 (14, 6) facing north (tile)", + "[Event] 00.11/6 colton_line: done", + "[Event] 00.11/7 bug_catcher_colton: start", + "00.11/bug_catcher_colton: ok at -", + "[Event] 00.11/7 bug_catcher_colton: done", + "[Event] 00.11/8 past_ben: start", + "00.11/past_ben: ok at Route3 (24, 5) facing east (tile)", + "[Event] 00.11/8 past_ben: done", + "[Event] 00.11/9 greg_line: start", + "00.11/greg_line: ok at Route3 (25, 5) facing east (tile)", + "[Event] 00.11/9 greg_line: done", + "[Event] 00.11/10 bug_catcher_greg: start", + "00.11/bug_catcher_greg: ok at -", + "[Event] 00.11/10 bug_catcher_greg: done", + "[Event] 00.11/11 above_james_line: start", + "00.11/above_james_line: ok at Route3 (36, 5) facing east (tile)", + "[Event] 00.11/11 above_james_line: done", + "[Event] 00.11/12 james_line: start", + "00.11/james_line: ok at Route3 (36, 6) facing south (tile)", + "[Event] 00.11/12 james_line: done", + "[Event] 00.11/13 bug_catcher_james: start", + "00.11/bug_catcher_james: ok at -", + "[Event] 00.11/13 bug_catcher_james: done", + "[Event] 00.11/14 save_spot: start", + "00.11/save_spot: ok at Route3 (36, 8) facing south (tile)", + "[Event] 00.11/14 save_spot: done", + "save at Route3 (36, 8) facing south (tile)", + "[Checkpoint] 00.11: done, 14 run, 0 skipped", + "[Checkpoint] 00.11: passed on attempt 1 (one-shot)", + "begin 00.12", + "[Checkpoint] 00.12: start, 4 events", + "[Event] 00.12/1 from_save: start", + "[Event] 00.12/1 from_save: done", + "[Event] 00.12/2 route4_nurse: start", + "00.12/route4_nurse: ok at Route4_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.12/2 route4_nurse: done", + "[Event] 00.12/3 heal_route4: start", + "00.12/heal_route4: ok at -", + "[Event] 00.12/3 heal_route4: done", + "[Event] 00.12/4 before_salesman: start", + "00.12/before_salesman: ok at Route4_PokemonCenter_1F (2, 3) facing west (tile)", + "[Event] 00.12/4 before_salesman: done", + "save at Route4_PokemonCenter_1F (2, 3) facing west (tile)", + "[Checkpoint] 00.12: done, 4 run, 0 skipped", + "[Checkpoint] 00.12: passed on attempt 1 (one-shot)", + "begin 00.12c", + "[Checkpoint] 00.12c: start, 3 events", + "[Event] 00.12c/1 from_save: start", + "[Event] 00.12c/1 from_save: done", + "[Event] 00.12c/2 pc_front: start", + "00.12c/pc_front: ok at Route4_PokemonCenter_1F (11, 2) facing north (tile)", + "[Event] 00.12c/2 pc_front: done", + "[Event] 00.12c/3 magikarp_to_box: start", + "00.12c/magikarp_to_box: ok at Route4_PokemonCenter_1F (11, 2) facing north (tile)", + "[Event] 00.12c/3 magikarp_to_box: done", + "save at Route4_PokemonCenter_1F (11, 2) facing north (tile)", + "[Checkpoint] 00.12c: done, 3 run, 0 skipped", + "[Checkpoint] 00.12c: passed on attempt 1 (one-shot)", + "begin 00.13", + "[Checkpoint] 00.13: start, 20 events", + "[Event] 00.13/1 from_save: start", + "[Event] 00.13/1 from_save: done", + "[Event] 00.13/2 route4_nurse: start", + "00.13/route4_nurse: ok at Route4_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.13/2 route4_nurse: done", + "[Event] 00.13/3 heal_route4: start", + "00.13/heal_route4: ok at -", + "[Event] 00.13/3 heal_route4: done", + "[Event] 00.13/4 mt_moon_entrance: start", + "00.13/mt_moon_entrance: ok at MtMoon_1F (18, 37) facing north (tile)", + "[Event] 00.13/4 mt_moon_entrance: done", + "[Event] 00.13/5 mt_moon_1f: start", + "00.13/mt_moon_1f: ok at MtMoon_1F (3, 3) facing east (tile)", + "[Event] 00.13/5 mt_moon_1f: done", + "[Event] 00.13/6 below_moon_stone: start", + "00.13/below_moon_stone: ok at MtMoon_1F (3, 3) facing north (tile)", + "[Event] 00.13/6 below_moon_stone: done", + "[Event] 00.13/7 moon_stone_ball: start", + "00.13/moon_stone_ball: ok at -", + "[Event] 00.13/7 moon_stone_ball: done", + "[Event] 00.13/8 found_moon_stone: start", + "00.13/found_moon_stone: ok at -", + "[Event] 00.13/8 found_moon_stone: done", + "[Event] 00.13/9 b2f_ladder_foot: start", + "00.13/b2f_ladder_foot: ok at MtMoon_B2F (26, 21) facing east (tile)", + "[Event] 00.13/9 b2f_ladder_foot: done", + "[Event] 00.13/10 b2f_south: start", + "00.13/b2f_south: ok at MtMoon_B2F (11, 21) facing north (tile)", + "[Event] 00.13/10 b2f_south: done", + "[Event] 00.13/11 grunt_line: start", + "00.13/grunt_line: ok at MtMoon_B2F (12, 21) facing east (tile)", + "[Event] 00.13/11 grunt_line: done", + "[Event] 00.13/12 rocket_grunt: start", + "00.13/rocket_grunt: ok at -", + "[Event] 00.13/12 rocket_grunt: done", + "[Event] 00.13/13 ready_for_miguel: start", + "00.13/ready_for_miguel: ok at -", + "[Event] 00.13/13 ready_for_miguel: done", + "[Event] 00.13/14 below_miguel_trigger: start", + "00.13/below_miguel_trigger: ok at MtMoon_B2F (14, 12) facing east (tile)", + "[Event] 00.13/14 below_miguel_trigger: done", + "[Event] 00.13/15 miguel_trigger: start", + "00.13/miguel_trigger: ok at MtMoon_B2F (14, 11) facing north (tile)", + "[Event] 00.13/15 miguel_trigger: done", + "[Event] 00.13/16 super_nerd_miguel: start", + "00.13/super_nerd_miguel: ok at -", + "[Event] 00.13/16 super_nerd_miguel: done", + "[Event] 00.13/17 below_helix_fossil: start", + "00.13/below_helix_fossil: ok at MtMoon_B2F (14, 8) facing north (tile)", + "[Event] 00.13/17 below_helix_fossil: done", + "[Event] 00.13/18 helix_fossil: start", + "00.13/helix_fossil: ok at MtMoon_B2F (14, 8) facing north (tile)", + "[Event] 00.13/18 helix_fossil: done", + "[Event] 00.13/19 helix_gap: start", + "00.13/helix_gap: ok at MtMoon_B2F (14, 7) facing north (tile)", + "[Event] 00.13/19 helix_gap: done", + "[Event] 00.13/20 route4_east: start", + "00.13/route4_east: ok at Route4 (32, 6) facing south (tile)", + "[Event] 00.13/20 route4_east: done", + "save at Route4 (32, 6) facing south (tile)", + "[Checkpoint] 00.13: done, 20 run, 0 skipped", + "[Checkpoint] 00.13: passed on attempt 1 (one-shot)", + "begin 00.14", + "[Checkpoint] 00.14: start, 10 events", + "[Event] 00.14/1 from_save: start", + "[Event] 00.14/1 from_save: done", + "[Event] 00.14/2 mega_kick_tutor: start", + "00.14/mega_kick_tutor: ok at Route4 (50, 4) facing north (tile)", + "[Event] 00.14/2 mega_kick_tutor: done", + "[Event] 00.14/3 teach_mega_kick: start", + "00.14/teach_mega_kick: ok at Route4 (50, 4) facing north (tile)", + "[Event] 00.14/3 teach_mega_kick: done", + "[Event] 00.14/4 above_ledge: start", + "00.14/above_ledge: ok at Route4 (93, 8) facing south (tile)", + "[Event] 00.14/4 above_ledge: done", + "[Event] 00.14/5 ledge_hop: start", + "00.14/ledge_hop: ok at Route4 (93, 9) facing south (tile)", + "[Event] 00.14/5 ledge_hop: done", + "[Event] 00.14/6 cerulean_entry: start", + "00.14/cerulean_entry: ok at CeruleanCity (9, 20) facing east (tile)", + "[Event] 00.14/6 cerulean_entry: done", + "[Event] 00.14/7 above_city_ledge: start", + "00.14/above_city_ledge: ok at CeruleanCity (25, 17) facing west (tile)", + "[Event] 00.14/7 above_city_ledge: done", + "[Event] 00.14/8 city_ledge_hop: start", + "00.14/city_ledge_hop: ok at CeruleanCity (25, 18) facing south (tile)", + "[Event] 00.14/8 city_ledge_hop: done", + "[Event] 00.14/9 cerulean_nurse: start", + "00.14/cerulean_nurse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.14/9 cerulean_nurse: done", + "[Event] 00.14/10 heal_cerulean: start", + "00.14/heal_cerulean: ok at -", + "[Event] 00.14/10 heal_cerulean: done", + "save at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.14: done, 10 run, 0 skipped", + "[Checkpoint] 00.14: passed on attempt 1 (one-shot)", + "begin 00.14b", + "[Checkpoint] 00.14b: start, 10 events", + "[Event] 00.14b/1 from_save: start", + "[Event] 00.14b/1 from_save: done", + "[Event] 00.14b/2 mart_clerk: start", + "00.14b/mart_clerk: ok at CeruleanCity_Mart (4, 3) facing west (tile)", + "[Event] 00.14b/2 mart_clerk: done", + "[Event] 00.14b/3 buy_potions: start", + "00.14b/buy_potions: ok at CeruleanCity_Mart (4, 3) facing west (tile)", + "[Event] 00.14b/3 buy_potions: done", + "[Event] 00.14b/4 grind_patch: start", + "00.14b/grind_patch: ok at Route4 (86, 16) facing west (tile)", + "[Event] 00.14b/4 grind_patch: done", + "[Event] 00.14b/5 grind_to_lv23: start", + "00.14b/grind_to_lv23: ok at Route4 (86, 16) (85, 16) (set)", + "[Event] 00.14b/5 grind_to_lv23: done", + "[Event] 00.14b/6 cerulean_nurse: start", + "00.14b/cerulean_nurse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.14b/6 cerulean_nurse: done", + "[Event] 00.14b/7 heal_cerulean: start", + "00.14b/heal_cerulean: ok at -", + "[Event] 00.14b/7 heal_cerulean: done", + "[Event] 00.14b/8 gym_below_luis: start", + "00.14b/gym_below_luis: ok at CeruleanCity_Gym (9, 13) facing north (tile)", + "[Event] 00.14b/8 gym_below_luis: done", + "[Event] 00.14b/9 gym_west_walkway: start", + "00.14b/gym_west_walkway: ok at CeruleanCity_Gym (4, 13) facing west (tile)", + "[Event] 00.14b/9 gym_west_walkway: done", + "[Event] 00.14b/10 below_misty_platform: start", + "00.14b/below_misty_platform: ok at CeruleanCity_Gym (9, 7) facing west (tile)", + "[Event] 00.14b/10 below_misty_platform: done", + "save at CeruleanCity_Gym (9, 7) facing west (tile)", + "[Checkpoint] 00.14b: done, 10 run, 0 skipped", + "[Checkpoint] 00.14b: passed on attempt 1 (one-shot)", + "begin 00.14c", + "[Checkpoint] 00.14c: start, 8 events", + "[Event] 00.14c/1 from_save: start", + "[Event] 00.14c/1 from_save: done", + "[Event] 00.14c/2 beside_misty: start", + "00.14c/beside_misty: ok at CeruleanCity_Gym (9, 6) facing west (tile)", + "[Event] 00.14c/2 beside_misty: done", + "[Event] 00.14c/3 challenge_misty: start", + "00.14c/challenge_misty: ok at -", + "[Event] 00.14c/3 challenge_misty: done", + "[Event] 00.14c/4 misty: start", + "00.14c/misty: ok at -", + "[Event] 00.14c/4 misty: done", + "[Event] 00.14c/5 gym_way_out: start", + "00.14c/gym_way_out: ok at CeruleanCity_Gym (9, 13) facing east (tile)", + "[Event] 00.14c/5 gym_way_out: done", + "[Event] 00.14c/6 cerulean_nurse: start", + "00.14c/cerulean_nurse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.14c/6 cerulean_nurse: done", + "[Event] 00.14c/7 heal_cerulean: start", + "00.14c/heal_cerulean: ok at -", + "[Event] 00.14c/7 heal_cerulean: done", + "[Event] 00.14c/8 teach_water_pulse: start", + "00.14c/teach_water_pulse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.14c/8 teach_water_pulse: done", + "save at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.14c: done, 8 run, 0 skipped", + "[Checkpoint] 00.14c: passed on attempt 1 (one-shot)", + "begin 00.15", + "[Checkpoint] 00.15: start, 9 events", + "[Event] 00.15/1 from_save: start", + "[Event] 00.15/1 from_save: done", + "[Event] 00.15/2 teach_water_pulse: start", + "00.15/teach_water_pulse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.15/2 teach_water_pulse: done", + "[Event] 00.15/3 mart_clerk: start", + "00.15/mart_clerk: ok at CeruleanCity_Mart (4, 3) facing west (tile)", + "[Event] 00.15/3 mart_clerk: done", + "[Event] 00.15/4 buy_potions: start", + "00.15/buy_potions: ok at CeruleanCity_Mart (4, 3) facing west (tile)", + "[Event] 00.15/4 buy_potions: done", + "[Event] 00.15/5 grind_patch: start", + "00.15/grind_patch: ok at Route4 (86, 16) facing west (tile)", + "[Event] 00.15/5 grind_patch: done", + "[Event] 00.15/6 grind_for_rival: start", + "00.15/grind_for_rival: ok at Route4 (86, 16) (85, 16) (set)", + "[Event] 00.15/6 grind_for_rival: done", + "[Event] 00.15/7 cerulean_nurse: start", + "00.15/cerulean_nurse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.15/7 cerulean_nurse: done", + "[Event] 00.15/8 heal_cerulean: start", + "00.15/heal_cerulean: ok at -", + "[Event] 00.15/8 heal_cerulean: done", + "[Event] 00.15/9 below_rival_trigger: start", + "00.15/below_rival_trigger: ok at CeruleanCity (23, 7) facing north (tile)", + "[Event] 00.15/9 below_rival_trigger: done", + "save at CeruleanCity (23, 7) facing north (tile)", + "[Checkpoint] 00.15: done, 9 run, 0 skipped", + "[Checkpoint] 00.15: passed on attempt 1 (one-shot)", + "begin 00.15b", + "[Checkpoint] 00.15b: start, 4 events", + "[Event] 00.15b/1 from_save: start", + "[Event] 00.15b/1 from_save: done", + "[Event] 00.15b/2 teach_water_pulse: start", + "00.15b/teach_water_pulse: ok at CeruleanCity (23, 7) facing north (tile)", + "[Event] 00.15b/2 teach_water_pulse: done", + "[Event] 00.15b/3 rival_trigger: start", + "00.15b/rival_trigger: ok at CeruleanCity (23, 6) facing north (tile)", + "[Event] 00.15b/3 rival_trigger: done", + "[Event] 00.15b/4 rival_cerulean: start", + "00.15b/rival_cerulean: ok at -", + "[Event] 00.15b/4 rival_cerulean: done", + "save at CeruleanCity (23, 6) facing south (tile)", + "[Checkpoint] 00.15b: done, 4 run, 0 skipped", + "[Checkpoint] 00.15b: passed on attempt 1 (one-shot)", + "begin 00.16", + "[Checkpoint] 00.16: start, 22 events", + "[Event] 00.16/1 from_save: start", + "[Event] 00.16/1 from_save: done", + "[Event] 00.16/2 cerulean_nurse: start", + "00.16/cerulean_nurse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.16/2 cerulean_nurse: done", + "[Event] 00.16/3 heal_cerulean: start", + "00.16/heal_cerulean: ok at -", + "[Event] 00.16/3 heal_cerulean: done", + "[Event] 00.16/4 below_cale_line: start", + "00.16/below_cale_line: ok at Route24 (11, 32) facing north (tile)", + "[Event] 00.16/4 below_cale_line: done", + "[Event] 00.16/5 cale_line: start", + "00.16/cale_line: ok at Route24 (11, 31) facing north (tile)", + "[Event] 00.16/5 cale_line: done", + "[Event] 00.16/6 bug_catcher_cale: start", + "00.16/bug_catcher_cale: ok at -", + "[Event] 00.16/6 bug_catcher_cale: done", + "[Event] 00.16/7 below_ali_line: start", + "00.16/below_ali_line: ok at Route24 (11, 29) facing north (tile)", + "[Event] 00.16/7 below_ali_line: done", + "[Event] 00.16/8 ali_line: start", + "00.16/ali_line: ok at Route24 (11, 28) facing north (tile)", + "[Event] 00.16/8 ali_line: done", + "[Event] 00.16/9 lass_ali: start", + "00.16/lass_ali: ok at -", + "[Event] 00.16/9 lass_ali: done", + "[Event] 00.16/10 below_timmy_line: start", + "00.16/below_timmy_line: ok at Route24 (11, 26) facing north (tile)", + "[Event] 00.16/10 below_timmy_line: done", + "[Event] 00.16/11 timmy_line: start", + "00.16/timmy_line: ok at Route24 (11, 25) facing north (tile)", + "[Event] 00.16/11 timmy_line: done", + "[Event] 00.16/12 youngster_timmy: start", + "00.16/youngster_timmy: ok at -", + "[Event] 00.16/12 youngster_timmy: done", + "[Event] 00.16/13 below_reli_line: start", + "00.16/below_reli_line: ok at Route24 (11, 23) facing north (tile)", + "[Event] 00.16/13 below_reli_line: done", + "[Event] 00.16/14 reli_line: start", + "00.16/reli_line: ok at Route24 (11, 22) facing north (tile)", + "[Event] 00.16/14 reli_line: done", + "[Event] 00.16/15 lass_reli: start", + "00.16/lass_reli: ok at -", + "[Event] 00.16/15 lass_reli: done", + "[Event] 00.16/16 below_ethan_line: start", + "00.16/below_ethan_line: ok at Route24 (11, 20) facing north (tile)", + "[Event] 00.16/16 below_ethan_line: done", + "[Event] 00.16/17 ethan_line: start", + "00.16/ethan_line: ok at Route24 (11, 19) facing north (tile)", + "[Event] 00.16/17 ethan_line: done", + "[Event] 00.16/18 camper_ethan: start", + "00.16/camper_ethan: ok at -", + "[Event] 00.16/18 camper_ethan: done", + "[Event] 00.16/19 ready_for_rocket: start", + "00.16/ready_for_rocket: ok at -", + "[Event] 00.16/19 ready_for_rocket: done", + "[Event] 00.16/20 below_rocket_trigger: start", + "00.16/below_rocket_trigger: ok at Route24 (11, 16) facing north (tile)", + "[Event] 00.16/20 below_rocket_trigger: done", + "[Event] 00.16/21 rocket_trigger: start", + "00.16/rocket_trigger: ok at Route24 (11, 15) facing north (tile)", + "[Event] 00.16/21 rocket_trigger: done", + "[Event] 00.16/22 rocket_grunt: start", + "00.16/rocket_grunt: ok at -", + "[Event] 00.16/22 rocket_grunt: done", + "save at Route24 (11, 15) facing east (tile)", + "[Checkpoint] 00.16: done, 22 run, 0 skipped", + "[Checkpoint] 00.16: passed on attempt 1 (one-shot)", + "begin 00.17", + "[Checkpoint] 00.17: start, 15 events", + "[Event] 00.17/1 from_save: start", + "[Event] 00.17/1 from_save: done", + "[Event] 00.17/2 below_franklin_line: start", + "00.17/below_franklin_line: ok at Route25 (15, 5) facing north (tile)", + "[Event] 00.17/2 below_franklin_line: done", + "[Event] 00.17/3 franklin_line: start", + "00.17/franklin_line: ok at Route25 (15, 4) facing north (tile)", + "[Event] 00.17/3 franklin_line: done", + "[Event] 00.17/4 hiker_franklin: start", + "00.17/hiker_franklin: ok at -", + "[Event] 00.17/4 hiker_franklin: done", + "[Event] 00.17/5 west_of_dan: start", + "00.17/west_of_dan: ok at Route25 (19, 4) facing north (tile)", + "[Event] 00.17/5 west_of_dan: done", + "[Event] 00.17/6 round_dan: start", + "00.17/round_dan: ok at Route25 (24, 7) facing south (tile)", + "[Event] 00.17/6 round_dan: done", + "[Event] 00.17/7 kelsey_line: start", + "00.17/kelsey_line: ok at Route25 (24, 8) facing south (tile)", + "[Event] 00.17/7 kelsey_line: done", + "[Event] 00.17/8 picnicker_kelsey: start", + "00.17/picnicker_kelsey: ok at -", + "[Event] 00.17/8 picnicker_kelsey: done", + "[Event] 00.17/9 west_of_nob_line: start", + "00.17/west_of_nob_line: ok at Route25 (26, 8) facing east (tile)", + "[Event] 00.17/9 west_of_nob_line: done", + "[Event] 00.17/10 nob_line: start", + "00.17/nob_line: ok at Route25 (27, 8) facing east (tile)", + "[Event] 00.17/10 nob_line: done", + "[Event] 00.17/11 hiker_nob: start", + "00.17/hiker_nob: ok at -", + "[Event] 00.17/11 hiker_nob: done", + "[Event] 00.17/12 below_haley_line: start", + "00.17/below_haley_line: ok at Route25 (42, 9) facing east (tile)", + "[Event] 00.17/12 below_haley_line: done", + "[Event] 00.17/13 haley_line: start", + "00.17/haley_line: ok at Route25 (42, 8) facing north (tile)", + "[Event] 00.17/13 haley_line: done", + "[Event] 00.17/14 lass_haley: start", + "00.17/lass_haley: ok at -", + "[Event] 00.17/14 lass_haley: done", + "[Event] 00.17/15 sea_cottage: start", + "00.17/sea_cottage: ok at Route25_SeaCottage (7, 8) facing north (tile)", + "[Event] 00.17/15 sea_cottage: done", + "save at Route25_SeaCottage (7, 8) facing north (tile)", + "[Checkpoint] 00.17: done, 15 run, 0 skipped", + "[Checkpoint] 00.17: passed on attempt 1 (one-shot)", + "begin 00.18", + "[Checkpoint] 00.18: start, 13 events", + "[Event] 00.18/1 from_save: start", + "[Event] 00.18/1 from_save: done", + "[Event] 00.18/2 below_bill_clefairy: start", + "00.18/below_bill_clefairy: ok at Route25_SeaCottage (10, 7) facing north (tile)", + "[Event] 00.18/2 below_bill_clefairy: done", + "[Event] 00.18/3 bill_clefairy: start", + "00.18/bill_clefairy: ok at -", + "[Event] 00.18/3 bill_clefairy: done", + "[Event] 00.18/4 help_bill: start", + "00.18/help_bill: ok at -", + "[Event] 00.18/4 help_bill: done", + "[Event] 00.18/5 bill_into_teleporter: start", + "00.18/bill_into_teleporter: ok at -", + "[Event] 00.18/5 bill_into_teleporter: done", + "[Event] 00.18/6 pc_front: start", + "00.18/pc_front: ok at Route25_SeaCottage (4, 6) facing north (tile)", + "[Event] 00.18/6 pc_front: done", + "[Event] 00.18/7 cell_separator: start", + "00.18/cell_separator: ok at -", + "[Event] 00.18/7 cell_separator: done", + "[Event] 00.18/8 bill_comes_out: start", + "00.18/bill_comes_out: ok at -", + "[Event] 00.18/8 bill_comes_out: done", + "[Event] 00.18/9 beside_bill: start", + "00.18/beside_bill: ok at Route25_SeaCottage (6, 6) facing east (tile)", + "[Event] 00.18/9 beside_bill: done", + "[Event] 00.18/10 thank_bill: start", + "00.18/thank_bill: ok at -", + "[Event] 00.18/10 thank_bill: done", + "[Event] 00.18/11 ss_ticket: start", + "00.18/ss_ticket: ok at -", + "[Event] 00.18/11 ss_ticket: done", + "[Event] 00.18/12 around_bill: start", + "00.18/around_bill: ok at Route25_SeaCottage (7, 8) facing east (tile)", + "[Event] 00.18/12 around_bill: done", + "[Event] 00.18/13 out_of_cottage: start", + "00.18/out_of_cottage: ok at Route25 (51, 5) facing south (tile)", + "[Event] 00.18/13 out_of_cottage: done", + "save at Route25 (51, 5) facing south (tile)", + "[Checkpoint] 00.18: done, 13 run, 0 skipped", + "[Checkpoint] 00.18: passed on attempt 1 (one-shot)", + "begin 00.19", + "[Checkpoint] 00.19: start, 5 events", + "[Event] 00.19/1 from_save: start", + "[Event] 00.19/1 from_save: done", + "[Event] 00.19/2 above_cerulean_ledge: start", + "00.19/above_cerulean_ledge: ok at CeruleanCity (27, 0) facing south (tile)", + "[Event] 00.19/2 above_cerulean_ledge: done", + "[Event] 00.19/3 cerulean_ledge_hop: start", + "00.19/cerulean_ledge_hop: ok at CeruleanCity (27, 1) facing south (tile)", + "[Event] 00.19/3 cerulean_ledge_hop: done", + "[Event] 00.19/4 cerulean_nurse: start", + "00.19/cerulean_nurse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.19/4 cerulean_nurse: done", + "[Event] 00.19/5 heal_cerulean: start", + "00.19/heal_cerulean: ok at -", + "[Event] 00.19/5 heal_cerulean: done", + "save at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.19: done, 5 run, 0 skipped", + "[Checkpoint] 00.19: passed on attempt 1 (one-shot)", + "begin 00.20", + "[Checkpoint] 00.20: start, 7 events", + "[Event] 00.20/1 from_save: start", + "[Event] 00.20/1 from_save: done", + "[Event] 00.20/2 mart_clerk: start", + "00.20/mart_clerk: ok at CeruleanCity_Mart (4, 3) facing west (tile)", + "[Event] 00.20/2 mart_clerk: done", + "[Event] 00.20/3 buy_poke_balls: start", + "00.20/buy_poke_balls: ok at CeruleanCity_Mart (4, 3) facing west (tile)", + "[Event] 00.20/3 buy_poke_balls: done", + "[Event] 00.20/4 oddish_patch: start", + "00.20/oddish_patch: ok at Route24 (3, 19) facing south (tile)", + "[Event] 00.20/4 oddish_patch: done", + "[Event] 00.20/5 catch_oddish: start", + "00.20/catch_oddish: ok at Route24 (3, 19) (4, 19) (set)", + "[Event] 00.20/5 catch_oddish: done", + "[Event] 00.20/6 cerulean_nurse: start", + "00.20/cerulean_nurse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.20/6 cerulean_nurse: done", + "[Event] 00.20/7 heal_cerulean: start", + "00.20/heal_cerulean: ok at -", + "[Event] 00.20/7 heal_cerulean: done", + "save at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.20: done, 7 run, 0 skipped", + "[Checkpoint] 00.20: passed on attempt 1 (one-shot)", + "begin 00.21", + "[Checkpoint] 00.21: start, 10 events", + "[Event] 00.21/1 from_save: start", + "[Event] 00.21/1 from_save: done", + "[Event] 00.21/2 west_of_rocket: start", + "00.21/west_of_rocket: ok at CeruleanCity (32, 7) facing east (tile)", + "[Event] 00.21/2 west_of_rocket: done", + "[Event] 00.21/3 rocket_trigger: start", + "00.21/rocket_trigger: ok at CeruleanCity (33, 7) facing east (tile)", + "[Event] 00.21/3 rocket_trigger: done", + "[Event] 00.21/4 rocket_grunt: start", + "00.21/rocket_grunt: ok at -", + "[Event] 00.21/4 rocket_grunt: done", + "[Event] 00.21/5 route6_column15: start", + "00.21/route6_column15: ok at Route6 (15, 14) facing west (tile)", + "[Event] 00.21/5 route6_column15: done", + "[Event] 00.21/6 round_keigo_and_elijah: start", + "00.21/round_keigo_and_elijah: ok at Route6 (9, 33) facing east (tile)", + "[Event] 00.21/6 round_keigo_and_elijah: done", + "[Event] 00.21/7 jeff_line: start", + "00.21/jeff_line: ok at Route6 (10, 33) facing east (tile)", + "[Event] 00.21/7 jeff_line: done", + "[Event] 00.21/8 camper_jeff: start", + "00.21/camper_jeff: ok at -", + "[Event] 00.21/8 camper_jeff: done", + "[Event] 00.21/9 vermilion_nurse: start", + "00.21/vermilion_nurse: ok at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.21/9 vermilion_nurse: done", + "[Event] 00.21/10 heal_vermilion: start", + "00.21/heal_vermilion: ok at -", + "[Event] 00.21/10 heal_vermilion: done", + "save at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.21: done, 10 run, 0 skipped", + "[Checkpoint] 00.21: passed on attempt 1 (one-shot)", + "begin 00.22", + "[Checkpoint] 00.22: start, 19 events", + "[Event] 00.22/1 from_save: start", + "[Event] 00.22/1 from_save: done", + "[Event] 00.22/2 above_ticket_check: start", + "00.22/above_ticket_check: ok at VermilionCity (23, 32) facing south (tile)", + "[Event] 00.22/2 above_ticket_check: done", + "[Event] 00.22/3 ticket_check: start", + "00.22/ticket_check: ok at VermilionCity (23, 33) facing south (tile)", + "[Event] 00.22/3 ticket_check: done", + "[Event] 00.22/4 show_ss_ticket: start", + "00.22/show_ss_ticket: ok at -", + "[Event] 00.22/4 show_ss_ticket: done", + "[Event] 00.22/5 aboard: start", + "00.22/aboard: ok at SSAnne_1F_Corridor (19, 2) facing south (tile)", + "[Event] 00.22/5 aboard: done", + "[Event] 00.22/6 past_the_cabins: start", + "00.22/past_the_cabins: ok at SSAnne_1F_Corridor (13, 8) facing north (tile)", + "[Event] 00.22/6 past_the_cabins: done", + "[Event] 00.22/7 below_rival_trigger: start", + "00.22/below_rival_trigger: ok at SSAnne_2F_Corridor (30, 7) facing north (tile)", + "[Event] 00.22/7 below_rival_trigger: done", + "[Event] 00.22/8 rival_trigger: start", + "00.22/rival_trigger: ok at SSAnne_2F_Corridor (30, 6) facing north (tile)", + "[Event] 00.22/8 rival_trigger: done", + "[Event] 00.22/9 rival_ss_anne: start", + "00.22/rival_ss_anne: ok at -", + "[Event] 00.22/9 rival_ss_anne: done", + "[Event] 00.22/10 behind_captain: start", + "00.22/behind_captain: ok at SSAnne_CaptainsOffice (5, 5) facing north (tile)", + "[Event] 00.22/10 behind_captain: done", + "[Event] 00.22/11 captain: start", + "00.22/captain: ok at -", + "[Event] 00.22/11 captain: done", + "[Event] 00.22/12 hm01_cut: start", + "00.22/hm01_cut: ok at -", + "[Event] 00.22/12 hm01_cut: done", + "[Event] 00.22/13 off_2f: start", + "00.22/off_2f: ok at SSAnne_1F_Corridor (13, 8) facing east (tile)", + "[Event] 00.22/13 off_2f: done", + "[Event] 00.22/14 round_the_sailor: start", + "00.22/round_the_sailor: ok at SSAnne_1F_Corridor (19, 9) facing east (tile)", + "[Event] 00.22/14 round_the_sailor: done", + "[Event] 00.22/15 below_ship_door: start", + "00.22/below_ship_door: ok at SSAnne_1F_Corridor (19, 2) facing north (tile)", + "[Event] 00.22/15 below_ship_door: done", + "[Event] 00.22/16 off_the_ship: start", + "00.22/off_the_ship: ok at SSAnne_1F_Corridor (19, 1) facing north (tile)", + "[Event] 00.22/16 off_the_ship: done", + "[Event] 00.22/17 ss_anne_sails: start", + "00.22/ss_anne_sails: ok at -", + "[Event] 00.22/17 ss_anne_sails: done", + "[Event] 00.22/18 vermilion_nurse: start", + "00.22/vermilion_nurse: ok at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.22/18 vermilion_nurse: done", + "[Event] 00.22/19 heal_vermilion: start", + "00.22/heal_vermilion: ok at -", + "[Event] 00.22/19 heal_vermilion: done", + "save at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.22: done, 19 run, 0 skipped", + "[Checkpoint] 00.22: passed on attempt 1 (one-shot)", + "begin 00.23", + "[Checkpoint] 00.23: start, 20 events", + "[Event] 00.23/1 from_save: start", + "[Event] 00.23/1 from_save: done", + "[Event] 00.23/2 teach_cut: start", + "00.23/teach_cut: ok at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.23/2 teach_cut: done", + "[Event] 00.23/3 teach_dig: start", + "00.23/teach_dig: ok at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.23/3 teach_dig: done", + "[Event] 00.23/4 above_gym_tree: start", + "00.23/above_gym_tree: ok at VermilionCity (19, 23) facing south (tile)", + "[Event] 00.23/4 above_gym_tree: done", + "[Event] 00.23/5 cut_gym_tree: start", + "00.23/cut_gym_tree: ok at VermilionCity (19, 23) facing south (tile)", + "[Event] 00.23/5 cut_gym_tree: done", + "[Event] 00.23/6 gym_door: start", + "00.23/gym_door: ok at VermilionCity_Gym (5, 19) facing north (tile)", + "[Event] 00.23/6 gym_door: done", + "[Event] 00.23/7 west_of_dwayne_line: start", + "00.23/west_of_dwayne_line: ok at VermilionCity_Gym (7, 15) facing east (tile)", + "[Event] 00.23/7 west_of_dwayne_line: done", + "[Event] 00.23/8 dwayne_sight: start", + "00.23/dwayne_sight: ok at VermilionCity_Gym (8, 15) facing east (tile)", + "[Event] 00.23/8 dwayne_sight: done", + "[Event] 00.23/9 sailor_dwayne: start", + "00.23/sailor_dwayne: ok at -", + "[Event] 00.23/9 sailor_dwayne: done", + "[Event] 00.23/10 east_of_baily_line: start", + "00.23/east_of_baily_line: ok at VermilionCity_Gym (3, 13) facing west (tile)", + "[Event] 00.23/10 east_of_baily_line: done", + "[Event] 00.23/11 baily_sight: start", + "00.23/baily_sight: ok at VermilionCity_Gym (2, 13) facing west (tile)", + "[Event] 00.23/11 baily_sight: done", + "[Event] 00.23/12 engineer_baily: start", + "00.23/engineer_baily: ok at -", + "[Event] 00.23/12 engineer_baily: done", + "[Event] 00.23/13 below_tucker_line: start", + "00.23/below_tucker_line: ok at VermilionCity_Gym (5, 9) facing east (tile)", + "[Event] 00.23/13 below_tucker_line: done", + "[Event] 00.23/14 tucker_line: start", + "00.23/tucker_line: ok at VermilionCity_Gym (5, 8) facing north (tile)", + "[Event] 00.23/14 tucker_line: done", + "[Event] 00.23/15 gentleman_tucker: start", + "00.23/gentleman_tucker: ok at -", + "[Event] 00.23/15 gentleman_tucker: done", + "[Event] 00.23/16 trash_cans: start", + "00.23/trash_cans: ok at VermilionCity_Gym (5, 8) facing east (tile)", + "[Event] 00.23/16 trash_cans: done", + "[Event] 00.23/17 below_gym_tree: start", + "00.23/below_gym_tree: ok at VermilionCity (19, 25) facing north (tile)", + "[Event] 00.23/17 below_gym_tree: done", + "[Event] 00.23/18 cut_tree_out: start", + "00.23/cut_tree_out: ok at VermilionCity (19, 25) facing north (tile)", + "[Event] 00.23/18 cut_tree_out: done", + "[Event] 00.23/19 vermilion_nurse: start", + "00.23/vermilion_nurse: ok at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.23/19 vermilion_nurse: done", + "[Event] 00.23/20 heal_vermilion: start", + "00.23/heal_vermilion: ok at -", + "[Event] 00.23/20 heal_vermilion: done", + "save at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.23: done, 20 run, 0 skipped", + "[Checkpoint] 00.23: passed on attempt 1 (one-shot)", + "begin 00.24", + "[Checkpoint] 00.24: start, 12 events", + "[Event] 00.24/1 from_save: start", + "[Event] 00.24/1 from_save: done", + "[Event] 00.24/2 surge_winnable: start", + "00.24/surge_winnable: ok at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.24/2 surge_winnable: done", + "[Event] 00.24/3 route6_south_path: start", + "00.24/route6_south_path: ok at Route6 (11, 34) facing north (tile)", + "[Event] 00.24/3 route6_south_path: done", + "[Event] 00.24/4 round_isabelle_line: start", + "00.24/round_isabelle_line: ok at Route6 (13, 31) facing east (tile)", + "[Event] 00.24/4 round_isabelle_line: done", + "[Event] 00.24/5 grind_for_surge: start", + "00.24/grind_for_surge: ok at Route6 (13, 31) (14, 31) (set)", + "[Event] 00.24/5 grind_for_surge: done", + "[Event] 00.24/6 west_on_row_31: start", + "00.24/west_on_row_31: ok at Route6 (7, 31) facing west (tile)", + "[Event] 00.24/6 west_on_row_31: done", + "[Event] 00.24/7 down_to_jeff_row: start", + "00.24/down_to_jeff_row: ok at Route6 (11, 33) facing east (tile)", + "[Event] 00.24/7 down_to_jeff_row: done", + "[Event] 00.24/8 vermilion_nurse: start", + "00.24/vermilion_nurse: ok at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.24/8 vermilion_nurse: done", + "[Event] 00.24/9 heal_vermilion: start", + "00.24/heal_vermilion: ok at -", + "[Event] 00.24/9 heal_vermilion: done", + "[Event] 00.24/10 above_gym_tree: start", + "00.24/above_gym_tree: ok at VermilionCity (19, 23) facing south (tile)", + "[Event] 00.24/10 above_gym_tree: done", + "[Event] 00.24/11 cut_gym_tree: start", + "00.24/cut_gym_tree: ok at VermilionCity (19, 23) facing south (tile)", + "[Event] 00.24/11 cut_gym_tree: done", + "[Event] 00.24/12 below_lt_surge: start", + "00.24/below_lt_surge: ok at VermilionCity_Gym (5, 3) facing north (tile)", + "[Event] 00.24/12 below_lt_surge: done", + "save at VermilionCity_Gym (5, 3) facing north (tile)", + "[Checkpoint] 00.24: done, 12 run, 0 skipped", + "[Checkpoint] 00.24: passed on attempt 1 (one-shot)", + "begin 00.25", + "[Checkpoint] 00.25: start, 8 events", + "[Event] 00.25/1 from_save: start", + "[Event] 00.25/1 from_save: done", + "[Event] 00.25/2 challenge_lt_surge: start", + "00.25/challenge_lt_surge: ok at -", + "[Event] 00.25/2 challenge_lt_surge: done", + "[Event] 00.25/3 lt_surge: start", + "00.25/lt_surge: ok at -", + "[Event] 00.25/3 lt_surge: done", + "[Event] 00.25/4 below_gym_tree: start", + "00.25/below_gym_tree: ok at VermilionCity (19, 25) facing north (tile)", + "[Event] 00.25/4 below_gym_tree: done", + "[Event] 00.25/5 cut_tree_out: start", + "00.25/cut_tree_out: ok at VermilionCity (19, 25) facing north (tile)", + "[Event] 00.25/5 cut_tree_out: done", + "[Event] 00.25/6 below_oaks_aide: start", + "00.25/below_oaks_aide: ok at VermilionCity (14, 10) facing west (tile)", + "[Event] 00.25/6 below_oaks_aide: done", + "[Event] 00.25/7 vermilion_nurse: start", + "00.25/vermilion_nurse: ok at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.25/7 vermilion_nurse: done", + "[Event] 00.25/8 heal_vermilion: start", + "00.25/heal_vermilion: ok at -", + "[Event] 00.25/8 heal_vermilion: done", + "save at VermilionCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.25: done, 8 run, 0 skipped", + "[Checkpoint] 00.25: passed on attempt 1 (one-shot)", + "begin 00.26", + "[Checkpoint] 00.26: start, 18 events", + "[Event] 00.26/1 from_save: start", + "[Event] 00.26/1 from_save: done", + "[Event] 00.26/2 below_center: start", + "00.26/below_center: ok at VermilionCity (14, 10) facing west (tile)", + "[Event] 00.26/2 below_center: done", + "[Event] 00.26/3 beside_chairman: start", + "00.26/beside_chairman: ok at VermilionCity_PokemonFanClub (4, 4) facing east (tile)", + "[Event] 00.26/3 beside_chairman: done", + "[Event] 00.26/4 chairman: start", + "00.26/chairman: ok at -", + "[Event] 00.26/4 chairman: done", + "[Event] 00.26/5 hear_story: start", + "00.26/hear_story: ok at -", + "[Event] 00.26/5 hear_story: done", + "[Event] 00.26/6 bike_voucher: start", + "00.26/bike_voucher: ok at -", + "[Event] 00.26/6 bike_voucher: done", + "[Event] 00.26/7 west_of_oaks_aide: start", + "00.26/west_of_oaks_aide: ok at VermilionCity (22, 10) facing west (tile)", + "[Event] 00.26/7 west_of_oaks_aide: done", + "[Event] 00.26/8 route6_south_path: start", + "00.26/route6_south_path: ok at Route6 (11, 34) facing north (tile)", + "[Event] 00.26/8 route6_south_path: done", + "[Event] 00.26/9 round_isabelle_and_elijah: start", + "00.26/round_isabelle_and_elijah: ok at Route6 (16, 12) facing north (tile)", + "[Event] 00.26/9 round_isabelle_and_elijah: done", + "[Event] 00.26/10 below_cerulean_tree: start", + "00.26/below_cerulean_tree: ok at CeruleanCity (26, 33) facing north (tile)", + "[Event] 00.26/10 below_cerulean_tree: done", + "[Event] 00.26/11 cut_cerulean_tree: start", + "00.26/cut_cerulean_tree: ok at CeruleanCity (26, 33) facing north (tile)", + "[Event] 00.26/11 cut_cerulean_tree: done", + "[Event] 00.26/12 bike_clerk: start", + "00.26/bike_clerk: ok at CeruleanCity_BikeShop (7, 3) facing east (tile)", + "[Event] 00.26/12 bike_clerk: done", + "[Event] 00.26/13 talk_bike_clerk: start", + "00.26/talk_bike_clerk: ok at -", + "[Event] 00.26/13 talk_bike_clerk: done", + "[Event] 00.26/14 bicycle: start", + "00.26/bicycle: ok at -", + "[Event] 00.26/14 bicycle: done", + "[Event] 00.26/15 mart_clerk: start", + "00.26/mart_clerk: ok at CeruleanCity_Mart (4, 3) facing west (tile)", + "[Event] 00.26/15 mart_clerk: done", + "[Event] 00.26/16 buy_repels: start", + "00.26/buy_repels: ok at CeruleanCity_Mart (4, 3) facing west (tile)", + "[Event] 00.26/16 buy_repels: done", + "[Event] 00.26/17 cerulean_nurse: start", + "00.26/cerulean_nurse: ok at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.26/17 cerulean_nurse: done", + "[Event] 00.26/18 heal_cerulean: start", + "00.26/heal_cerulean: ok at -", + "[Event] 00.26/18 heal_cerulean: done", + "save at CeruleanCity_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.26: done, 18 run, 0 skipped", + "[Checkpoint] 00.26: passed on attempt 1 (one-shot)", + "begin 00.27", + "[Checkpoint] 00.27: start, 23 events", + "[Event] 00.27/1 from_save: start", + "[Event] 00.27/1 from_save: done", + "[Event] 00.27/2 above_cerulean_tree: start", + "00.27/above_cerulean_tree: ok at CeruleanCity (26, 31) facing south (tile)", + "[Event] 00.27/2 above_cerulean_tree: done", + "[Event] 00.27/3 cut_cerulean_tree: start", + "00.27/cut_cerulean_tree: ok at CeruleanCity (26, 31) facing south (tile)", + "[Event] 00.27/3 cut_cerulean_tree: done", + "[Event] 00.27/4 west_of_route9_tree: start", + "00.27/west_of_route9_tree: ok at Route9 (1, 8) facing east (tile)", + "[Event] 00.27/4 west_of_route9_tree: done", + "[Event] 00.27/5 cut_route9_tree: start", + "00.27/cut_route9_tree: ok at Route9 (1, 8) facing east (tile)", + "[Event] 00.27/5 cut_route9_tree: done", + "[Event] 00.27/6 above_alicia_line: start", + "00.27/above_alicia_line: ok at Route9 (11, 9) facing south (tile)", + "[Event] 00.27/6 above_alicia_line: done", + "[Event] 00.27/7 alicia_line: start", + "00.27/alicia_line: ok at Route9 (11, 10) facing south (tile)", + "[Event] 00.27/7 alicia_line: done", + "[Event] 00.27/8 picnicker_alicia: start", + "00.27/picnicker_alicia: ok at -", + "[Event] 00.27/8 picnicker_alicia: done", + "[Event] 00.27/9 alicia_ledge_hop: start", + "00.27/alicia_ledge_hop: ok at Route9 (11, 11) facing south (tile)", + "[Event] 00.27/9 alicia_ledge_hop: done", + "[Event] 00.27/10 west_of_jeremy_sight: start", + "00.27/west_of_jeremy_sight: ok at Route9 (19, 13) facing east (tile)", + "[Event] 00.27/10 west_of_jeremy_sight: done", + "[Event] 00.27/11 jeremy_sight: start", + "00.27/jeremy_sight: ok at Route9 (20, 13) facing east (tile)", + "[Event] 00.27/11 jeremy_sight: done", + "[Event] 00.27/12 hiker_jeremy: start", + "00.27/hiker_jeremy: ok at -", + "[Event] 00.27/12 hiker_jeremy: done", + "[Event] 00.27/13 below_conner_line: start", + "00.27/below_conner_line: ok at Route9 (52, 8) facing north (tile)", + "[Event] 00.27/13 below_conner_line: done", + "[Event] 00.27/14 conner_line: start", + "00.27/conner_line: ok at Route9 (52, 7) facing north (tile)", + "[Event] 00.27/14 conner_line: done", + "[Event] 00.27/15 bug_catcher_conner: start", + "00.27/bug_catcher_conner: ok at -", + "[Event] 00.27/15 bug_catcher_conner: done", + "[Event] 00.27/16 round_brice: start", + "00.27/round_brice: ok at Route9 (64, 2) facing south (tile)", + "[Event] 00.27/16 round_brice: done", + "[Event] 00.27/17 above_east_ledge: start", + "00.27/above_east_ledge: ok at Route9 (64, 3) facing south (tile)", + "[Event] 00.27/17 above_east_ledge: done", + "[Event] 00.27/18 east_ledge_hop: start", + "00.27/east_ledge_hop: ok at Route9 (64, 4) facing south (tile)", + "[Event] 00.27/18 east_ledge_hop: done", + "[Event] 00.27/19 above_caitlin_line: start", + "00.27/above_caitlin_line: ok at Route9 (70, 8) facing east (tile)", + "[Event] 00.27/19 above_caitlin_line: done", + "[Event] 00.27/20 route10_ledge_end: start", + "00.27/route10_ledge_end: ok at Route10 (17, 11) facing east (tile)", + "[Event] 00.27/20 route10_ledge_end: done", + "[Event] 00.27/21 below_center_door: start", + "00.27/below_center_door: ok at Route10 (13, 21) facing east (tile)", + "[Event] 00.27/21 below_center_door: done", + "[Event] 00.27/22 route10_nurse: start", + "00.27/route10_nurse: ok at Route10_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.27/22 route10_nurse: done", + "[Event] 00.27/23 heal_route10: start", + "00.27/heal_route10: ok at -", + "[Event] 00.27/23 heal_route10: done", + "save at Route10_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.27: done, 23 run, 0 skipped", + "[Checkpoint] 00.27: passed on attempt 1 (one-shot)", + "begin 00.28", + "[Checkpoint] 00.28: start, 39 events", + "[Event] 00.28/1 from_save: start", + "[Event] 00.28/1 from_save: done", + "[Event] 00.28/2 below_route10_tree: start", + "00.28/below_route10_tree: ok at Route10 (10, 21) facing north (tile)", + "[Event] 00.28/2 below_route10_tree: done", + "[Event] 00.28/3 cut_route10_tree: start", + "00.28/cut_route10_tree: ok at Route10 (10, 21) facing north (tile)", + "[Event] 00.28/3 cut_route10_tree: done", + "[Event] 00.28/4 west_of_tree: start", + "00.28/west_of_tree: ok at Route10 (9, 20) facing west (tile)", + "[Event] 00.28/4 west_of_tree: done", + "[Event] 00.28/5 below_rock_tunnel: start", + "00.28/below_rock_tunnel: ok at Route10 (8, 20) facing west (tile)", + "[Event] 00.28/5 below_rock_tunnel: done", + "[Event] 00.28/6 rock_tunnel_1f: start", + "00.28/rock_tunnel_1f: ok at RockTunnel_1F (17, 2) facing north (tile)", + "[Event] 00.28/6 rock_tunnel_1f: done", + "[Event] 00.28/7 above_ashton_line: start", + "00.28/above_ashton_line: ok at RockTunnel_1F (24, 9) facing west (tile)", + "[Event] 00.28/7 above_ashton_line: done", + "[Event] 00.28/8 ashton_line: start", + "00.28/ashton_line: ok at RockTunnel_1F (24, 10) facing south (tile)", + "[Event] 00.28/8 ashton_line: done", + "[Event] 00.28/9 pokemaniac_ashton: start", + "00.28/pokemaniac_ashton: ok at -", + "[Event] 00.28/9 pokemaniac_ashton: done", + "[Event] 00.28/10 b1f_south_ladder: start", + "00.28/b1f_south_ladder: ok at RockTunnel_B1F (38, 28) facing north (tile)", + "[Event] 00.28/10 b1f_south_ladder: done", + "[Event] 00.28/11 winston_line_end: start", + "00.28/winston_line_end: ok at RockTunnel_B1F (32, 37) facing west (tile)", + "[Event] 00.28/11 winston_line_end: done", + "[Event] 00.28/12 winston_line: start", + "00.28/winston_line: ok at RockTunnel_B1F (29, 37) facing west (tile)", + "[Event] 00.28/12 winston_line: done", + "[Event] 00.28/13 pokemaniac_winston: start", + "00.28/pokemaniac_winston: ok at -", + "[Event] 00.28/13 pokemaniac_winston: done", + "[Event] 00.28/14 below_martha_line: start", + "00.28/below_martha_line: ok at RockTunnel_B1F (16, 35) facing east (tile)", + "[Event] 00.28/14 below_martha_line: done", + "[Event] 00.28/15 martha_line: start", + "00.28/martha_line: ok at RockTunnel_B1F (16, 33) facing north (tile)", + "[Event] 00.28/15 martha_line: done", + "[Event] 00.28/16 picnicker_martha: start", + "00.28/picnicker_martha: ok at -", + "[Event] 00.28/16 picnicker_martha: done", + "[Event] 00.28/17 below_allen_line: start", + "00.28/below_allen_line: ok at RockTunnel_B1F (38, 12) facing north (tile)", + "[Event] 00.28/17 below_allen_line: done", + "[Event] 00.28/18 allen_line: start", + "00.28/allen_line: ok at RockTunnel_B1F (36, 12) facing west (tile)", + "[Event] 00.28/18 allen_line: done", + "[Event] 00.28/19 hiker_allen: start", + "00.28/hiker_allen: ok at -", + "[Event] 00.28/19 hiker_allen: done", + "[Event] 00.28/20 west_of_b1f_ladder: start", + "00.28/west_of_b1f_ladder: ok at RockTunnel_B1F (32, 2) facing north (tile)", + "[Event] 00.28/20 west_of_b1f_ladder: done", + "[Event] 00.28/21 1f_west_ladder: start", + "00.28/1f_west_ladder: ok at RockTunnel_1F (4, 2) facing north (tile)", + "[Event] 00.28/21 1f_west_ladder: done", + "[Event] 00.28/22 above_lenny_line: start", + "00.28/above_lenny_line: ok at RockTunnel_1F (6, 9) facing south (tile)", + "[Event] 00.28/22 above_lenny_line: done", + "[Event] 00.28/23 lenny_line: start", + "00.28/lenny_line: ok at RockTunnel_1F (7, 9) facing east (tile)", + "[Event] 00.28/23 lenny_line: done", + "[Event] 00.28/24 hiker_lenny: start", + "00.28/hiker_lenny: ok at -", + "[Event] 00.28/24 hiker_lenny: done", + "[Event] 00.28/25 west_of_lucas_line: start", + "00.28/west_of_lucas_line: ok at RockTunnel_1F (20, 16) facing east (tile)", + "[Event] 00.28/25 west_of_lucas_line: done", + "[Event] 00.28/26 b1f_middle_ladder: start", + "00.28/b1f_middle_ladder: ok at RockTunnel_B1F (27, 12) facing north (tile)", + "[Event] 00.28/26 b1f_middle_ladder: done", + "[Event] 00.28/27 east_of_sofia_line: start", + "00.28/east_of_sofia_line: ok at RockTunnel_B1F (13, 16) facing west (tile)", + "[Event] 00.28/27 east_of_sofia_line: done", + "[Event] 00.28/28 sofia_line: start", + "00.28/sofia_line: ok at RockTunnel_B1F (11, 16) facing west (tile)", + "[Event] 00.28/28 sofia_line: done", + "[Event] 00.28/29 picnicker_sofia: start", + "00.28/picnicker_sofia: ok at -", + "[Event] 00.28/29 picnicker_sofia: done", + "[Event] 00.28/30 east_of_dudley_line: start", + "00.28/east_of_dudley_line: ok at RockTunnel_B1F (8, 15) facing north (tile)", + "[Event] 00.28/30 east_of_dudley_line: done", + "[Event] 00.28/31 dudley_line: start", + "00.28/dudley_line: ok at RockTunnel_B1F (5, 15) facing west (tile)", + "[Event] 00.28/31 dudley_line: done", + "[Event] 00.28/32 hiker_dudley: start", + "00.28/hiker_dudley: ok at -", + "[Event] 00.28/32 hiker_dudley: done", + "[Event] 00.28/33 past_cooper: start", + "00.28/past_cooper: ok at RockTunnel_B1F (1, 3) facing south (tile)", + "[Event] 00.28/33 past_cooper: done", + "[Event] 00.28/34 1f_east_ladder: start", + "00.28/1f_east_ladder: ok at RockTunnel_1F (45, 21) facing north (tile)", + "[Event] 00.28/34 1f_east_ladder: done", + "[Event] 00.28/35 above_dana_line: start", + "00.28/above_dana_line: ok at RockTunnel_1F (28, 31) facing south (tile)", + "[Event] 00.28/35 above_dana_line: done", + "[Event] 00.28/36 dana_line: start", + "00.28/dana_line: ok at RockTunnel_1F (26, 31) facing west (tile)", + "[Event] 00.28/36 dana_line: done", + "[Event] 00.28/37 picnicker_dana: start", + "00.28/picnicker_dana: ok at -", + "[Event] 00.28/37 picnicker_dana: done", + "[Event] 00.28/38 above_south_mouth: start", + "00.28/above_south_mouth: ok at RockTunnel_1F (18, 36) facing west (tile)", + "[Event] 00.28/38 above_south_mouth: done", + "[Event] 00.28/39 route10_south: start", + "00.28/route10_south: ok at Route10 (8, 58) facing south (tile)", + "[Event] 00.28/39 route10_south: done", + "save at Route10 (8, 58) facing south (tile)", + "[Checkpoint] 00.28: done, 39 run, 0 skipped", + "[Checkpoint] 00.28: passed on attempt 1 (one-shot)", + "begin 00.29", + "[Checkpoint] 00.29: start, 7 events", + "[Event] 00.29/1 from_save: start", + "[Event] 00.29/1 from_save: done", + "[Event] 00.29/2 route10_east_side: start", + "00.29/route10_east_side: ok at Route10 (11, 73) facing west (tile)", + "[Event] 00.29/2 route10_east_side: done", + "[Event] 00.29/3 route10_ledge_hop: start", + "00.29/route10_ledge_hop: ok at Route10 (11, 74) facing south (tile)", + "[Event] 00.29/3 route10_ledge_hop: done", + "[Event] 00.29/4 lavender_north_wall: start", + "00.29/lavender_north_wall: ok at LavenderTown (11, 8) facing south (tile)", + "[Event] 00.29/4 lavender_north_wall: done", + "[Event] 00.29/5 below_center_door: start", + "00.29/below_center_door: ok at LavenderTown (6, 9) facing west (tile)", + "[Event] 00.29/5 below_center_door: done", + "[Event] 00.29/6 lavender_nurse: start", + "00.29/lavender_nurse: ok at LavenderTown_PokemonCenter_1F (7, 4) facing north (tile)", + "[Event] 00.29/6 lavender_nurse: done", + "[Event] 00.29/7 heal_lavender: start", + "00.29/heal_lavender: ok at -", + "[Event] 00.29/7 heal_lavender: done", + "save at LavenderTown_PokemonCenter_1F (7, 4) facing north (tile)", + "[Checkpoint] 00.29: done, 7 run, 0 skipped", + "[Checkpoint] 00.29: passed on attempt 1 (one-shot)", + "[Segment] 00: 33 of 33 checkpoints one-shot", + "[Segment] 00: done, 33 checkpoints", + "completed", + "00.01: 2 stubbed: intro, options\n00.02: 0 stubbed\n00.03: 1 stubbed: to_the_lab\n00.04: 5 stubbed: squirtle_ball, choose_squirtle, no_nickname, rival_takes_bulbasaur, rival_battle\n00.05: 1 stubbed: parcel_from_clerk\n00.06: 2 stubbed: give_parcel, pokedex_scene\n00.07: 2 stubbed: heal_viridian, old_man_tutorial\n00.08: 1 stubbed: heal_pewter\n00.09: 3 stubbed: camper_liam, challenge_brock, brock\n00.10: 1 stubbed: shoes_from_aide\n00.11: 4 stubbed: heal_pewter, bug_catcher_colton, bug_catcher_greg, bug_catcher_james\n00.12: 1 stubbed: heal_route4\n00.12c: 1 stubbed: magikarp_to_box\n00.13: 6 stubbed: heal_route4, moon_stone_ball, found_moon_stone, rocket_grunt, super_nerd_miguel, helix_fossil\n00.14: 2 stubbed: teach_mega_kick, heal_cerulean\n00.14b: 3 stubbed: buy_potions, grind_to_lv23, heal_cerulean\n00.14c: 4 stubbed: challenge_misty, misty, heal_cerulean, teach_water_pulse\n00.15: 4 stubbed: teach_water_pulse, buy_potions, grind_for_rival, heal_cerulean\n00.15b: 2 stubbed: teach_water_pulse, rival_cerulean\n00.16: 7 stubbed: heal_cerulean, bug_catcher_cale, lass_ali, youngster_timmy, lass_reli, camper_ethan, rocket_grunt\n00.17: 4 stubbed: hiker_franklin, picnicker_kelsey, hiker_nob, lass_haley\n00.18: 7 stubbed: bill_clefairy, help_bill, bill_into_teleporter, cell_separator, bill_comes_out, thank_bill, ss_ticket\n00.19: 1 stubbed: heal_cerulean\n00.20: 3 stubbed: buy_poke_balls, catch_oddish, heal_cerulean\n00.21: 3 stubbed: rocket_grunt, camper_jeff, heal_vermilion\n00.22: 6 stubbed: show_ss_ticket, rival_ss_anne, captain, hm01_cut, ss_anne_sails, heal_vermilion\n00.23: 9 stubbed: teach_cut, teach_dig, cut_gym_tree, sailor_dwayne, engineer_baily, gentleman_tucker, trash_cans, cut_tree_out, heal_vermilion\n00.24: 4 stubbed: surge_winnable, grind_for_surge, heal_vermilion, cut_gym_tree\n00.25: 4 stubbed: challenge_lt_surge, lt_surge, cut_tree_out, heal_vermilion\n00.26: 8 stubbed: chairman, hear_story, bike_voucher, cut_cerulean_tree, talk_bike_clerk, bicycle, buy_repels, heal_cerulean\n00.27: 6 stubbed: cut_cerulean_tree, cut_route9_tree, picnicker_alicia, hiker_jeremy, bug_catcher_conner, heal_route10\n00.28: 9 stubbed: cut_route10_tree, pokemaniac_ashton, pokemaniac_winston, picnicker_martha, hiker_allen, hiker_lenny, picnicker_sofia, hiker_dudley, picnicker_dana\n00.29: 1 stubbed: heal_lavender\n" + ] +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.01.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.01.plan.json new file mode 100644 index 0000000000..f265a765ba --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.01.plan.json @@ -0,0 +1,31 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.01", + "title": "new game to the bedroom", + "sections": [ + { + "id": "new_game", + "title": "Title screen to the bedroom", + "events": [ + { + "id": "intro", + "kind": "new_game", + "declared_after": {"certainty": "tile", "map": "PalletTown_PlayersHouse_2F", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 6]]}}, + "why": "Oak's speech, BOY, the player's name (default if none is set), rival GREEN", + "notes": [ + "new_game.c: the bedroom at (6, 6), facing the TV." + ] + }, + { + "id": "options", + "kind": "options", + "why": "fast text; no switch prompt between a trainer's Pokemon" + } + ] + } + ] + }, + "execution": {"handoff": "sid_helper", "menu": "before_pokemon"} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.01b.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.01b.plan.json new file mode 100644 index 0000000000..bbf99b42a9 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.01b.plan.json @@ -0,0 +1,33 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.01b", + "title": "after SID Helper: close the Trainer Card, save in the bedroom", + "notes": [ + "SID Helper confirms the name, finishes the intro and reads the Trainer Card from the START menu, without moving the player or saving." + ], + "start": {"certainty": "tile", "map": "PalletTown_PlayersHouse_2F", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 6]]}}, + "sections": [ + { + "id": "bedroom", + "title": "Back from SID Helper in the bedroom", + "events": [ + { + "id": "close_trainer_card", + "kind": "game_action", + "variant": "close_trainer_card", + "declared_after": {"certainty": "tile", "map": "PalletTown_PlayersHouse_2F", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 6]]}}, + "why": "the Trainer Card SID Helper left open, or HOME" + }, + { + "id": "options", + "kind": "options", + "why": "fast text; no switch prompt between a trainer's Pokemon" + } + ] + } + ] + }, + "execution": {"live_start": true, "menu": "before_pokemon"} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.02.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.02.plan.json new file mode 100644 index 0000000000..24d0869164 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.02.plan.json @@ -0,0 +1,24 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.02", + "title": "bedroom to Pallet Town", + "start": {"certainty": "tile", "map": "PalletTown_PlayersHouse_2F", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 6]]}}, + "sections": [ + { + "id": "players_house", + "title": "Down the stairs and out", + "events": [ + { + "id": "front_door", + "kind": "route", + "target": {"certainty": "tile", "map": "PalletTown", "where": {"kind": "tiles", "tiles": [[6, 8]]}}, + "why": "down the stairs and out of the house" + } + ] + } + ] + }, + "execution": {"menu": "before_pokemon"} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.03.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.03.plan.json new file mode 100644 index 0000000000..a357eec763 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.03.plan.json @@ -0,0 +1,39 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.03", + "title": "Oak stops the player and walks them to the lab", + "start": {"certainty": "tile", "map": "PalletTown", "where": {"facing": "south", "kind": "tiles", "tiles": [[6, 8]]}}, + "sections": [ + { + "id": "pallet_north", + "title": "Pallet Town's north exit to Oak's lab", + "events": [ + { + "id": "below_oaks_trigger", + "kind": "route", + "target": {"certainty": "tile", "map": "PalletTown", "where": {"facing": "north", "kind": "tiles", "tiles": [[12, 2]]}}, + "why": "below the north exit, short of Oak's trigger at (12, 1)" + }, + { + "id": "oaks_trigger", + "kind": "step", + "direction": "north", + "why": "the trigger: \"Don't go out!\"" + }, + { + "id": "to_the_lab", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 4]]}}, + "why": "Oak walks the player into the lab; the scene ends at the table", + "notes": [ + "PlayerEnter walks the player up 8 tiles from the lab's door." + ] + } + ] + } + ] + }, + "execution": {"menu": "before_pokemon"} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.04.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.04.plan.json new file mode 100644 index 0000000000..59ff4431fa --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.04.plan.json @@ -0,0 +1,27 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.04", + "title": "to Squirtle's ball, south of the table", + "notes": [ + "Starter RNG starts from a save south of the table, facing the ball." + ], + "start": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 4]]}}, + "sections": [ + { + "id": "lab_table", + "title": "Oak's lab, below Squirtle's ball", + "events": [ + { + "id": "below_squirtle", + "kind": "route", + "target": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "north", "kind": "tiles", "tiles": [[9, 5]]}}, + "why": "under Squirtle's ball (9, 4)" + } + ] + } + ] + }, + "execution": {"handoff": "starter_rng", "menu": "before_pokemon"} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.04b.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.04b.plan.json new file mode 100644 index 0000000000..84f39b5060 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.04b.plan.json @@ -0,0 +1,68 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.04b", + "title": "Squirtle, then the rival's battle", + "start": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "north", "kind": "tiles", "tiles": [[9, 5]]}}, + "sections": [ + { + "id": "starter", + "title": "Squirtle from the table", + "events": [ + { + "id": "squirtle_ball", + "kind": "talk", + "why": "\"So, you want the WATER POKEMON SQUIRTLE?\"" + }, + { + "id": "choose_squirtle", + "kind": "answer", + "why": "take Squirtle" + }, + { + "id": "no_nickname", + "kind": "answer", + "yes": false, + "why": "\"give a nickname?\" NO" + }, + { + "id": "rival_takes_bulbasaur", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "north", "kind": "tiles", "tiles": [[9, 5]]}}, + "why": "the rival picks his ball; the player still faces the table (console B, 2026-11-01)" + } + ] + }, + { + "id": "rival_lab", + "title": "The rival's first battle", + "events": [ + { + "id": "above_rival_trigger", + "kind": "route", + "target": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "south", "kind": "tiles", "tiles": [[7, 7]]}}, + "why": "short of the rival's trigger row 8" + }, + { + "id": "rival_trigger", + "kind": "step", + "direction": "south", + "why": "\"Wait, let's check out our POKEMON!\"" + }, + { + "id": "rival_battle", + "kind": "battle", + "may_lose": true, + "declared_after": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "south", "kind": "tiles", "tiles": [[7, 8]]}}, + "why": "the tutorial battle; losing goes on too", + "notes": [ + "The rival leaves; PlayerWatchRivalExitAfterBattleRight ends facing down." + ] + } + ] + } + ] + }, + "execution": {"menu": "before_pokedex"} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.06.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.06.plan.json new file mode 100644 index 0000000000..7eac3e37a5 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.06.plan.json @@ -0,0 +1,35 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.06", + "title": "Oak's Parcel to the Pokedex", + "start": {"certainty": "tile", "map": "ViridianCity_Mart", "where": {"facing": "west", "kind": "tiles", "tiles": [[4, 3]]}}, + "sections": [ + { + "id": "route1_to_oak", + "title": "Route 1 back to Oak", + "events": [ + { + "id": "below_oak", + "kind": "route", + "target": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 4]]}}, + "why": "back down Route 1 to Oak" + }, + { + "id": "give_parcel", + "kind": "talk", + "why": "Oak takes the parcel" + }, + { + "id": "pokedex_scene", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "south", "kind": "tiles", "tiles": [[6, 4]]}}, + "why": "the rival comes in, Pokedexes from the desk; the player ends facing south (console B, 2026-11-01)" + } + ] + } + ] + }, + "execution": {"menu": "full"} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.07.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.07.plan.json new file mode 100644 index 0000000000..dd1d5f4047 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.07.plan.json @@ -0,0 +1,101 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.07", + "title": "Viridian shopping, then into Viridian Forest", + "start": {"certainty": "tile", "map": "PalletTown_ProfessorOaksLab", "where": {"facing": "south", "kind": "tiles", "tiles": [[6, 4]]}}, + "sections": [ + { + "id": "viridian_mart", + "title": "Viridian Mart", + "events": [ + { + "id": "mart_clerk", + "kind": "route", + "target": {"certainty": "tile", "map": "ViridianCity_Mart", "where": {"facing": "west", "kind": "tiles", "tiles": [[4, 3]]}}, + "why": "Route 1 again" + }, + { + "id": "potions_and_antidotes", + "kind": "shop", + "items": [{"item": "potion", "quantity": 5}, {"item": "antidote", "quantity": 3}, {"item": "parlyz_heal", "quantity": 1}], + "why": "5 Potions, 3 Antidotes, 1 Parlyz Heal", + "notes": [ + "2000 of the 3000 a new game starts with, plus the rival's prize." + ] + } + ] + }, + { + "id": "viridian_center", + "title": "Viridian's Pokemon Center", + "events": [ + { + "id": "viridian_nurse", + "kind": "route", + "target": {"certainty": "tile", "map": "ViridianCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "heal before the forest" + }, + { + "id": "heal_viridian", + "kind": "heal", + "why": "full HP" + } + ] + }, + { + "id": "old_man", + "title": "The old man's catching tutorial", + "events": [ + { + "id": "below_old_man_trigger", + "kind": "route", + "target": {"certainty": "tile", "map": "ViridianCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[20, 9]]}}, + "why": "short of the tutorial trigger (20, 8)" + }, + { + "id": "old_man_trigger", + "kind": "step", + "direction": "north", + "why": "the old man's catching tutorial can't be walked around" + }, + { + "id": "old_man_tutorial", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "ViridianCity", "where": {"facing": "east", "kind": "tiles", "tiles": [[20, 8]]}}, + "why": "about 60 lines and his demo battle; TEACHY TV", + "notes": [ + "TutorialTriggerLeft turns the player right, to the old man." + ] + }, + { + "id": "past_old_man", + "kind": "route", + "target": {"certainty": "tile", "map": "ViridianCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[20, 5]]}}, + "why": "north along column 20, clear of the old man", + "notes": [ + "After his demo the old man stands east of the player, beside (20, 8), where the map doesn't have him: \"east 2\" from there didn't move, and the walk north ended under Route 2's ledge in column 8 (console B, 2026-11-01). Go north first." + ] + } + ] + }, + { + "id": "route2", + "title": "Route 2 to Viridian Forest", + "events": [ + { + "id": "forest_entrance", + "kind": "route", + "target": {"certainty": "tile", "map": "ViridianForest", "where": {"facing": "north", "kind": "tiles", "tiles": [[29, 62]]}}, + "why": "Route 2 and the south gate, running from wild battles", + "notes": [ + "The gate's north warp lands on the forest's exit mat (29, 62), facing north. The save there keeps a forest failure from going back to Pallet." + ] + } + ] + } + ] + }, + "execution": {"menu": "full"} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.08.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.08.plan.json new file mode 100644 index 0000000000..3f348baaf4 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.08.plan.json @@ -0,0 +1,78 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.08", + "title": "Viridian Forest to Pewter Center", + "notes": [ + "The supplies bought in 00.07." + ], + "start": {"certainty": "tile", "map": "ViridianForest", "where": {"facing": "north", "kind": "tiles", "tiles": [[29, 62]]}}, + "sections": [ + { + "id": "viridian_forest", + "title": "Viridian Forest, fighting wild battles", + "events": [ + { + "id": "forest_east", + "kind": "legs", + "legs": [{"direction": "east", "tiles": 1}, {"direction": "north", "tiles": 4}, {"direction": "east", "tiles": 14}, {"direction": "north", "tiles": 15}], + "fight_wild": true, + "why": "up the east side to (44, 43)", + "notes": [ + "Wild battles are fought for EXP. Straight up the east side from the entrance, each leg ending on a wall (the Boy at (45, 58) stops the east one). Rick (47, 45) sees (42..46, 45), so the last leg meets his battle and is held on to its wall after it. The by-hand run's detour west to (13, 46) and back is dropped." + ] + }, + { + "id": "forest_north", + "kind": "route", + "target": {"certainty": "tile", "map": "ViridianForest", "where": {"kind": "tiles", "tiles": [[39, 25]]}}, + "fight_wild": true, + "why": "north part" + }, + { + "id": "forest_exit", + "kind": "legs", + "legs": [ + {"direction": "north", "tiles": 10}, {"direction": "west", "tiles": 12}, {"direction": "south", "tiles": 10}, {"direction": "west", "tiles": 6}, {"direction": "north", "tiles": 20}, {"direction": "south", "tiles": 1}, + {"direction": "west", "tiles": 10}, {"direction": "south", "tiles": 27}, {"direction": "west", "tiles": 8}, {"direction": "north", "tiles": 20}, {"direction": "east", "tiles": 1} + ], + "fight_wild": true, + "why": "walls all the way to the gate's column", + "notes": [ + "To the gate by legs that end on walls, which the walker holds again after a battle instead of estimating where it stopped: planned routes with open legs lost the player in the north of the forest (console B, 2026-11-01). The two 1-tile legs are single steps." + ] + }, + { + "id": "forest_gate", + "kind": "route", + "target": {"certainty": "tile", "map": "Route2_ViridianForest_NorthEntrance", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 10]]}}, + "fight_wild": true, + "why": "north onto the exit, arriving on the gate's mat" + } + ] + }, + { + "id": "pewter_center", + "title": "Route 2 north to Pewter's Pokemon Center", + "events": [ + { + "id": "pewter_nurse", + "kind": "route", + "target": {"certainty": "tile", "map": "PewterCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "Route 2 north to Pewter's Center", + "notes": [ + "Out of the forest, wild battles are run from; trainers are still fought." + ] + }, + { + "id": "heal_pewter", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"antidotes": 3, "parlyz_heals": 1, "potions": 5}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.09.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.09.plan.json new file mode 100644 index 0000000000..bbd97680fe --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.09.plan.json @@ -0,0 +1,74 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.09", + "title": "Pewter Gym: Camper Liam, then Brock", + "notes": [ + "The by-hand save: 2 Potions and 1 Antidote left after the forest." + ], + "start": {"certainty": "tile", "map": "PewterCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "camper_liam", + "title": "Pewter Gym: Camper Liam", + "events": [ + { + "id": "below_liam_line", + "kind": "route", + "target": {"certainty": "tile", "map": "PewterCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 9]]}}, + "why": "in by the door, up column 6 to below Liam's row", + "notes": [ + "Camper Liam (3, 8) always faces east (MOVEMENT_TYPE_FACE_RIGHT, sight 4) and sees (4..7, 8): walking north up column 6 from the door, the step onto (6, 8) starts his battle. He walks up to the player, who stays put and keeps facing north (FRLG doesn't turn the player to a trainer). That step is taken on its own, so no leg is cut." + ] + }, + { + "id": "liam_line", + "kind": "step", + "direction": "north", + "why": "onto (6, 8), in Liam's line" + }, + { + "id": "camper_liam", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "PewterCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 8]]}}, + "why": "Geodude and Sandshrew; EXP before Brock" + } + ] + }, + { + "id": "brock", + "title": "Pewter Gym: Brock", + "events": [ + { + "id": "ready_for_brock", + "kind": "ready", + "target": {"certainty": "tile", "map": "PewterCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "items for a status or low HP; the nurse only if they can't", + "notes": [ + "On to Brock unless red HP or a status is left that no item cures." + ] + }, + { + "id": "before_brock", + "kind": "route", + "target": {"certainty": "tile", "map": "PewterCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 6]]}}, + "why": "below Brock (6, 5); Liam, beaten, stands at (5, 8)" + }, + { + "id": "challenge_brock", + "kind": "talk", + "why": "Brock's speech" + }, + { + "id": "brock", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "PewterCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 6]]}}, + "why": "Geodude and Onix, with Bubble; Boulder Badge and TM39" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"antidotes": 1, "potions": 2}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.10.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.10.plan.json new file mode 100644 index 0000000000..05fd3c2de9 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.10.plan.json @@ -0,0 +1,51 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.10", + "title": "Pewter: the Running Shoes from Oak's aide", + "start": {"certainty": "tile", "map": "PewterCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[6, 6]]}}, + "sections": [ + { + "id": "pewter_aide", + "title": "Pewter City: Oak's aide and the Running Shoes", + "events": [ + { + "id": "before_aide_trigger", + "kind": "route", + "target": {"certainty": "tile", "map": "PewterCity", "where": {"facing": "east", "kind": "tiles", "tiles": [[45, 21]]}}, + "why": "out of the Gym, east along row 21, short of the top trigger", + "notes": [ + "Brock's win sets VAR_MAP_SCENE_PEWTER_CITY to 1 and shows the aide at (46, 20); his triggers are (46, 21..23) (PewterCity map.json). The Gym guide's triggers at (42..43, 21..23) need the scene at 0." + ] + }, + { + "id": "aide_trigger", + "kind": "step", + "direction": "east", + "why": "onto (46, 21), below the aide" + }, + { + "id": "shoes_from_aide", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "PewterCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[46, 21]]}}, + "why": "\"Oh, PLAYER!\" ... the Running Shoes; ends facing north", + "notes": [ + "RunningShoesAideTriggerTop: the aide talks from (46, 20), no YES/NO; the player only turns up. He walks off and is removed; FLAG_SYS_B_DASH is set (console B, 2026-11-02: about 20 B presses to the overworld)." + ] + }, + { + "id": "below_pewter_center", + "kind": "route", + "target": {"certainty": "tile", "map": "PewterCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[17, 26]]}}, + "why": "west along row 21 and down to below the Pokemon Center's door (17, 25)", + "notes": [ + "Where the by-hand walk stopped to start 00.11 (console B, 2026-11-02)." + ] + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.11.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.11.plan.json new file mode 100644 index 0000000000..e1a2022efd --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.11.plan.json @@ -0,0 +1,128 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.11", + "title": "Pewter Center, then Route 3's trainers", + "notes": [ + "Route 3's trainers (Route3 map.json): the way east meets three of them. Bug Catcher Colton (12, 6) faces east, sight 3: the only way north past the row 7 ledges is the gap at x 14..15, below his line. Youngster Ben (17, 4) faces south, sight 2, so the way east keeps to row 3 past him. Bug Catcher Greg (25, 4) faces south, sight 2, over the way east along row 5. Bug Catcher James (32, 6) faces east, sight 4, over the way down at x 35..36. The others are kept out of: Lass Janice (19, 9) sees west along row 9 to x 14, Lass Sally (30, 3) west along row 3 to x 25, and Youngster Calvin (29, 10) west along row 10. Each line is stepped into on its own, so no leg is cut; the trainer walks up to the player, who stays put facing the way they stepped." + ], + "start": {"certainty": "tile", "map": "PewterCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[17, 26]]}}, + "sections": [ + { + "id": "pewter_center", + "title": "Pewter's Pokemon Center", + "events": [ + { + "id": "pewter_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "PewterCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "in by the door below the player" + }, + { + "id": "heal_pewter", + "kind": "heal", + "why": "full HP" + } + ] + }, + { + "id": "route3_colton", + "title": "Route 3 west: Bug Catcher Colton", + "events": [ + { + "id": "west_of_janice_line", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route3", "where": {"kind": "tiles", "tiles": [[13, 8]]}}, + "why": "out of Pewter by row 21, east into Route 3, short of Janice's line", + "notes": [ + "Up from row 9 west of Janice's line, which ends at x 14." + ] + }, + { + "id": "below_colton_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 1}, {"direction": "north", "tiles": 1}], + "why": "to (14, 7), in the ledges' gap" + }, + { + "id": "colton_line", + "kind": "step", + "direction": "north", + "why": "onto (14, 6), in Colton's line" + }, + { + "id": "bug_catcher_colton", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route3", "where": {"facing": "north", "kind": "tiles", "tiles": [[14, 6]]}}, + "why": "his Bug Pokemon; Colton stops at (13, 6)" + } + ] + }, + { + "id": "route3_greg", + "title": "Route 3 middle: past Ben to Bug Catcher Greg", + "events": [ + { + "id": "past_ben", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 3}, {"direction": "east", "tiles": 4}, {"direction": "south", "tiles": 2}, {"direction": "east", "tiles": 6}], + "why": "up to row 3 above Ben, down at x 18 east of his line, to (24, 5)" + }, + { + "id": "greg_line", + "kind": "step", + "direction": "east", + "why": "onto (25, 5), below Greg" + }, + { + "id": "bug_catcher_greg", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route3", "where": {"facing": "east", "kind": "tiles", "tiles": [[25, 5]]}}, + "why": "his Bug Pokemon; Greg is already next to the player" + } + ] + }, + { + "id": "route3_james", + "title": "Route 3 east: Bug Catcher James and the save spot", + "events": [ + { + "id": "above_james_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 11}], + "why": "along row 5 to the wall at (36, 5)" + }, + { + "id": "james_line", + "kind": "step", + "direction": "south", + "why": "onto (36, 6), at the end of James's line" + }, + { + "id": "bug_catcher_james", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route3", "where": {"facing": "south", "kind": "tiles", "tiles": [[36, 6]]}}, + "why": "his Bug Pokemon; James stops at (35, 6)" + }, + { + "id": "save_spot", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "south", "tiles": 2}], + "why": "down to (36, 8), beside the wall east of it", + "notes": [ + "Saved here, the trainers behind, so a failure on the way to Route 4 doesn't fight them again." + ] + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12.plan.json new file mode 100644 index 0000000000..48e1acbaaf --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12.plan.json @@ -0,0 +1,43 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.12", + "title": "Route 3 and 4 to the Magikarp salesman", + "notes": [ + "Route 3's north exit (x 68..75) joins Route 4 (offset 60) at x 8..15, up which column 12 leads to the Pokemon Center's door (12, 5). Lass Robin (40, 11) wanders up and down at x 40, looking north and south, sight 3. Rock above (y <= 8) and grass below (y >= 12) leave every way east across her column at y 9..11, so whether she sees the player depends on her facing. The walk watches for her after the leg along row 9, which crosses her sight on (40, 9) only; if she saw the player, her message goes to story_interrupts(), which fight her, and the route goes on from (40, 9) (run_route(); console B, 2026-11-02, she stopped the player there and spoke after the leg's short watch)." + ], + "start": {"certainty": "tile", "map": "Route3", "where": {"facing": "south", "kind": "tiles", "tiles": [[36, 8]]}}, + "sections": [ + { + "id": "route4_center", + "title": "Route 3 and 4 to the Magikarp salesman", + "events": [ + { + "id": "route4_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route4_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "east over Route 3, off the grass, north into Route 4 and its Pokemon Center" + }, + { + "id": "heal_route4", + "kind": "heal", + "why": "full HP" + }, + { + "id": "before_salesman", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route4_PokemonCenter_1F", "where": {"facing": "west", "kind": "tiles", "tiles": [[2, 3]]}}, + "why": "beside the Magikarp salesman, facing him", + "notes": [ + "Gift RNG starts from a save facing the salesman (1, 3)." + ] + } + ] + } + ] + }, + "execution": {"handoff": "gift_rng", "menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12b.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12b.plan.json new file mode 100644 index 0000000000..005365f269 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12b.plan.json @@ -0,0 +1,28 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.12b", + "title": "after Gift RNG: save with the Magikarp", + "notes": [ + "Gift RNG buys the Magikarp (500 of the money 00.12 brings) without moving the player and leaves its summary open, or HOME. This saves the shiny where 00.12 ended. A run started here with Gift RNG's handoff on loads the save beside the salesman and runs Gift RNG first." + ], + "start": {"certainty": "tile", "map": "Route4_PokemonCenter_1F", "where": {"facing": "west", "kind": "tiles", "tiles": [[2, 3]]}}, + "sections": [ + { + "id": "route4_center", + "title": "Back from Gift RNG beside the salesman", + "events": [ + { + "id": "leave_gift_summary", + "kind": "game_action", + "variant": "leave_gift_summary", + "declared_after": {"certainty": "tile", "map": "Route4_PokemonCenter_1F", "where": {"facing": "west", "kind": "tiles", "tiles": [[2, 3]]}}, + "why": "the Magikarp's summary Gift RNG left open, or HOME" + } + ] + } + ] + }, + "execution": {"live_start": true, "menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12c.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12c.plan.json new file mode 100644 index 0000000000..0c547fa4f5 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12c.plan.json @@ -0,0 +1,34 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.12c", + "title": "Route 4 Center: the Magikarp into BOX 1", + "notes": [ + "The Magikarp goes to BOX 1 at the Center's PC (11, 1), so Mt. Moon is fought with the starter alone (console B, 2026-11-02: done by hand and saved at (11, 2), facing the PC, where this row ends). The wandering boy (4..6, 3..5) is walked around. The Magikarp comes from Gift RNG: with Gift RNG off the party is the starter alone, and the deposit is skipped." + ], + "start": {"certainty": "tile", "map": "Route4_PokemonCenter_1F", "where": {"facing": "west", "kind": "tiles", "tiles": [[2, 3]]}}, + "sections": [ + { + "id": "route4_center_pc", + "title": "Route 4's Pokemon Center PC", + "events": [ + { + "id": "pc_front", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route4_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 2]]}}, + "why": "below the PC (11, 1), facing it" + }, + { + "id": "magikarp_to_box", + "kind": "game_action", + "variant": "pc deposit slot=2 box=1 if_present", + "why": "DEPOSIT the party's second slot into BOX 1, if it has one" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.13.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.13.plan.json new file mode 100644 index 0000000000..4f9d2e6bab --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.13.plan.json @@ -0,0 +1,189 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.13", + "title": "Mt. Moon: a Moon Stone, a Rocket, Miguel and the Helix Fossil", + "notes": [ + "Mt. Moon (pokefirered data/maps/MtMoon_1F, _B1F, _B2F). Every floor tile of the cave has wild encounters, which are run from, so its legs end on walls, ladders or someone standing still, which the walker holds again after a battle; the few open ones are short. The way through: up 1F's middle and west side, its north-west ladder, B1F's west ladder down to B2F's south-east, round B2F's south edge to its west, north past Miguel and the fossils, and the ladder up to B1F's east end, which leads out to Route 4 east of the mountain.", + "- 1F's trainers are walked around: Youngster Josh (13, 17) sees east along row 17 to x 17, so the way west into the west room is down column 18 and up column 11; Lass Miriam walks round (32..33, 4..5), sight 2, so the way east keeps to row 9 and columns 38 and 40; Hiker Marcos wanders (5..9, 8..12) and sees one tile, so the way north is column 11 and row 3.", + "- The Moon Stone ball (3, 2) on 1F (FLAG_HIDE_MT_MOON_1F_MOON_STONE) is a few tiles off the way to the ladder (5, 6).", + "- The Grunt at (37, 21) on B2F looks around (sight 3) over the only way east round B2F. His room is crossed on legs to walls, at one tile of his sight each, (37, 22) and (40, 21); if he looks the player's way, he walks up beside the leg, story_interrupts() fights him, and the leg is held on to its wall.", + "- The Grunt at (12, 20) faces south (sight 2) over (12, 21..22), across rows 21..22, the only way north to Miguel: stepped into from the west, he is next to the player and doesn't walk.", + "- Super Nerd Miguel (13, 11) battles on the step onto (14, 11) (MtMoon_B2F_EventScript_MiguelTrigger, VAR_MAP_SCENE_MT_MOON_B2F 0), then \"We'll each take one!\"", + "- The fossils: DOME (13, 7), HELIX (14, 7). After the HELIX, Miguel walks up to (13, 8) and takes the DOME, which opens the gap north. The save is on Route 4: the save card's places don't include Mt. Moon." + ], + "start": {"certainty": "tile", "map": "Route4_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 2]]}}, + "sections": [ + { + "id": "route4_center", + "title": "Route 4's Pokemon Center", + "events": [ + { + "id": "route4_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route4_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "the nurse, below the counter" + }, + { + "id": "heal_route4", + "kind": "heal", + "why": "full HP" + } + ] + }, + { + "id": "mt_moon_1f", + "title": "Mt. Moon 1F and the Moon Stone", + "events": [ + { + "id": "mt_moon_entrance", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "MtMoon_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[18, 37]]}}, + "why": "out of the Center, west into Mt. Moon" + }, + { + "id": "mt_moon_1f", + "kind": "legs", + "variant": "run", + "legs": [ + {"direction": "north", "tiles": 12}, {"direction": "east", "tiles": 8}, {"direction": "north", "tiles": 12}, {"direction": "east", "tiles": 3}, {"direction": "south", "tiles": 1}, {"direction": "east", "tiles": 9}, + {"direction": "north", "tiles": 1}, {"direction": "east", "tiles": 2}, {"direction": "north", "tiles": 4}, {"direction": "west", "tiles": 24}, {"direction": "east", "tiles": 2}, {"direction": "south", "tiles": 12}, + {"direction": "west", "tiles": 7}, {"direction": "north", "tiles": 18}, {"direction": "west", "tiles": 9}, {"direction": "east", "tiles": 1} + ], + "why": "up the middle, east of Miriam, west along row 9, down column 18 past Josh, up column 11 past Marcos, to (3, 3)" + }, + { + "id": "below_moon_stone", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "MtMoon_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[3, 3]]}}, + "why": "facing the Moon Stone (3, 2)" + }, + { + "id": "moon_stone_ball", + "kind": "talk", + "why": "A on the item ball" + }, + { + "id": "found_moon_stone", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "MtMoon_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[3, 3]]}}, + "why": "\" found a MOON STONE!\", then the pocket line; the ball is gone", + "notes": [ + "No reader for the text yet: the scene's end is the overworld." + ] + } + ] + }, + { + "id": "mt_moon_b2f_grunt", + "title": "Mt. Moon B2F: round the south edge to the Grunt", + "events": [ + { + "id": "b2f_ladder_foot", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "MtMoon_B2F", "where": {"facing": "east", "kind": "tiles", "tiles": [[26, 21]]}}, + "why": "1F's ladder (5, 6), down B1F's west side to its ladder (22, 18), onto B2F" + }, + { + "id": "b2f_south", + "kind": "legs", + "variant": "run", + "legs": [ + {"direction": "north", "tiles": 4}, {"direction": "east", "tiles": 8}, {"direction": "west", "tiles": 2}, {"direction": "south", "tiles": 5}, {"direction": "east", "tiles": 8}, {"direction": "north", "tiles": 3}, + {"direction": "west", "tiles": 1}, {"direction": "north", "tiles": 5}, {"direction": "east", "tiles": 7}, {"direction": "south", "tiles": 14}, {"direction": "west", "tiles": 9}, {"direction": "south", "tiles": 10}, + {"direction": "west", "tiles": 29}, {"direction": "east", "tiles": 3}, {"direction": "north", "tiles": 17} + ], + "why": "through the Grunt's room at (37, 21), round the south edge, up column 11 to (11, 21)" + }, + { + "id": "grunt_line", + "kind": "step", + "direction": "east", + "why": "onto (12, 21), below the Grunt at (12, 20)" + }, + { + "id": "rocket_grunt", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "MtMoon_B2F", "where": {"facing": "east", "kind": "tiles", "tiles": [[12, 21]]}}, + "why": "Rattata and Zubat, Lv13" + } + ] + }, + { + "id": "mt_moon_b2f_miguel", + "title": "Mt. Moon B2F: Miguel and the Helix Fossil", + "events": [ + { + "id": "ready_for_miguel", + "kind": "ready", + "target": {"certainty": "tile", "map": "Route4_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "items for a status or low HP; the nurse, back through the cave, only if they can't" + }, + { + "id": "below_miguel_trigger", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 1}, {"direction": "north", "tiles": 9}, {"direction": "east", "tiles": 1}], + "why": "to (14, 12), below Miguel's trigger (14, 11)", + "notes": [ + "Up column 13 to Miguel, who stops the leg on (13, 12), then east to the wall: the tile below his trigger." + ] + }, + { + "id": "miguel_trigger", + "kind": "step", + "direction": "north", + "why": "onto (14, 11), beside Miguel" + }, + { + "id": "super_nerd_miguel", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "MtMoon_B2F", "where": {"facing": "north", "kind": "tiles", "tiles": [[14, 11]]}}, + "why": "Grimer, Voltorb and Koffing, Lv12; \"We'll each take one!\"" + }, + { + "id": "below_helix_fossil", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 3}], + "why": "up column 14 to the HELIX FOSSIL (14, 7)" + }, + { + "id": "helix_fossil", + "kind": "game_action", + "variant": "fossil helix", + "why": "the HELIX FOSSIL; Miguel takes the DOME FOSSIL" + }, + { + "id": "helix_gap", + "kind": "step", + "direction": "north", + "why": "onto (14, 7), where the HELIX FOSSIL lay", + "notes": [ + "The maps keep the fossils, which the game removes after the choice: the step onto the HELIX's tile is taken on its own." + ] + } + ] + }, + { + "id": "mt_moon_exit", + "title": "B1F's east exit onto Route 4", + "events": [ + { + "id": "route4_east", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route4", "where": {"facing": "south", "kind": "tiles", "tiles": [[32, 6]]}}, + "why": "west to B2F's ladder (5, 10), up to B1F, east to its exit onto Route 4" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14.plan.json new file mode 100644 index 0000000000..a2d58040b7 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14.plan.json @@ -0,0 +1,96 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.14", + "title": "Route 4: Mega Kick from its tutor, then east to Cerulean's Pokemon Center", + "notes": [ + "Route 4 east of Mt. Moon (Route4 map.json) joins Cerulean City at its east end (x 107; Route 4's row 3 is the city's row 13). Its upper road (rows 2..8) reaches the city only by water: the way on by land is down the ledge row 9 at x 90..96 to the lower road (rows 10..12), which joins the city's rows 20..22. Lass Crissy (75, 3) faces east, sight 4, over row 3 from x 76 to 79; the way east keeps out of her line and off the grass south of the road (75..87, 11..17).", + "On the way, the Mega Kick tutor (Black Belt (50, 3), facing west, Route4_EventScript_MegaKickTutor) stands in a pen (46..51, 2..4) north of the road, beside the Mega Punch tutor (47, 3); the pen opens south only on (48, 5) and (49, 5), the rest of row 5 ledges out of it. From Mt. Moon's exit the walk goes east and up into the pen to (50, 4), below the tutor, and Mega Kick takes Withdraw's slot. Cerulean can't be walked back to Route 4's upper road without Surf, so it is now or much later.", + "The open leg east across the city's edge ended a tile short on the console (2026-11-03: on (6, 20), not (7, 20)), and no hold to a wall in the city's west end finds the player from there: the next hold south met the fence on (6, 23) and the walk went on a column off. So the entry ends on (9, 20), and only legs to walls follow: north up column 9 (or 8, a tile short) to row 12, west to (8, 12), east to (30, 12) beside the policeman (31, 12), south to (30, 17), west to (25, 17), and down the ledge row 18 to (25, 19), a few tiles from the door (22, 19). The way keeps out of the boy's (16, 18..24) and the balding man's (8..10, 22..24) wander boxes." + ], + "start": {"certainty": "tile", "map": "Route4", "where": {"facing": "south", "kind": "tiles", "tiles": [[32, 6]]}}, + "sections": [ + { + "id": "mega_kick_tutor", + "title": "Route 4: the Mega Kick tutor", + "events": [ + { + "id": "mega_kick_tutor", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route4", "where": {"facing": "north", "kind": "tiles", "tiles": [[50, 4]]}}, + "why": "east and up through (48, 5) into the tutors' pen, below the Mega Kick tutor (50, 3)" + }, + { + "id": "teach_mega_kick", + "kind": "game_action", + "variant": "teach tutor mega-kick slot=1 forget=withdraw", + "once_per_save": true, + "why": "the tutor's Mega Kick in place of Withdraw, unless the lead knows it" + } + ] + }, + { + "id": "route4_east", + "title": "Route 4's upper road and its ledge", + "events": [ + { + "id": "above_ledge", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route4", "where": {"facing": "south", "kind": "tiles", "tiles": [[93, 8]]}}, + "why": "east along Route 4's upper road to above the ledge row 9" + }, + { + "id": "ledge_hop", + "kind": "step", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "Route4", "where": {"facing": "south", "kind": "tiles", "tiles": [[93, 10]]}}, + "why": "down the ledge (93, 9) to the lower road" + } + ] + }, + { + "id": "cerulean", + "title": "Cerulean City to its Pokemon Center", + "events": [ + { + "id": "cerulean_entry", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "east", "kind": "tiles", "tiles": [[9, 20]]}}, + "why": "east into Cerulean City, to (9, 20) below column 9" + }, + { + "id": "above_city_ledge", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 8}, {"direction": "west", "tiles": 1}, {"direction": "east", "tiles": 22}, {"direction": "south", "tiles": 5}, {"direction": "west", "tiles": 5}], + "why": "to walls: (9, 12), (8, 12), (30, 12), (30, 17), (25, 17)" + }, + { + "id": "city_ledge_hop", + "kind": "step", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[25, 19]]}}, + "why": "down the ledge (25, 18)" + }, + { + "id": "cerulean_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "west along row 20 to the Pokemon Center's door (22, 19)" + }, + { + "id": "heal_cerulean", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14b.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14b.plan.json new file mode 100644 index 0000000000..fa88ff60fa --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14b.plan.json @@ -0,0 +1,96 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.14b", + "title": "Cerulean: Potions, a GRIND to Lv23, then into the Gym, saved below Misty's platform", + "notes": [ + "Cerulean Gym (pokefirered CeruleanCity_Gym map.json and scripts.inc): a pool with walkways. Misty (8, 6) stands on a platform (7..9, 6) above row 7. Picnicker Diana (4, 7) faces east, sight 4, over (5..8, 7), so misty_front (8, 7) is in her line; Swimmer Luis (10, 12) in the water faces west, sight 1, over (9, 12), on column 9 north of the door. No gym trainer has to be fought for Misty, so neither is: from the door up column 9 to row 13 (a route there, so the planner's walk to a wall doesn't run on past it through (9, 12)), west to (4, 13), and to walls round the pool's west and north sides, (4, 10), (12, 10), (12, 7), then west to (9, 7), the tile below the platform's east end, out of Diana's line. 00.14c steps up onto (9, 6) and talks to Misty from beside her.", + "Misty beat a Lv20 Wartortle (console B, run m51), so first a GRIND to Lv23 on 00.15's patch. Gen 3 numbers (pokefirered: her mons have IV 0; Wartortle taken at IV 15, no EVs): Starmie Lv21 has 56 HP, Atk 36, SpA 47, SpD 40. Bite is Dark, special in Gen 3, no STAB, x2 on Psychic: at Lv23 (SpA 39) it does 23..28, three hits (two on top rolls); at Lv20, 20..24. Her Swift (physical, 60) does 9..11 to a Lv23 Wartortle (about 67 HP, Def 45) and Water Pulse (STAB, resisted) 8..10: 6 or more hits, though m51 saw about 20 a turn. Her Recover (28) and Super Potion undo a Bite, so the margin is in Bite's damage.", + "The Mart comes before the GRIND: its wild battles are fought only with 2 or more Potions, and 00.14's save has none (m51 met Misty with only the 5 this row bought). 5 Potions (row 3, 300 each), then up to 3 Super Potions (row 2, 700), last so they take the Items pocket's last row; the battle doesn't use them yet. The money box can't be read, so the floor stands for it, and a floor above the money fails the step (the quantity box wraps). Prizes (pokefirered: 4 x last level x the class's factor) from a new game's 3000, less Viridian's 2000 and the Magikarp's 500: Liam 220, Brock 1400, Route 3's bug catchers 360, the Mt. Moon grunt 416 and Miguel 288 (the first rival's 80 only if won), at least 3184 here. Floor and budget 3000: 5 Potions and 2 Super Potions, 2900. Misty's 2100 (4 x 21 x 25) then leaves 00.15 its floor of 1500. Then the nurse, and the save before Misty, so her battle is its own checkpoint, 00.14c, verified and retried from it. 00.15's GRIND then fights until the sim beats the Cerulean rival often enough." + ], + "start": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "cerulean_mart", + "title": "Cerulean Mart", + "events": [ + { + "id": "mart_clerk", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_Mart", "where": {"facing": "west", "kind": "tiles", "tiles": [[4, 3]]}}, + "why": "out of the Center to the Mart's clerk" + }, + { + "id": "buy_potions", + "kind": "game_action", + "variant": "mart CeruleanCity_Mart budget=3000 floor=3000 potion=5 super-potion=3", + "once_per_save": true, + "why": "5 Potions for the GRIND's wild battles, then Super Potions as the money allows" + } + ] + }, + { + "id": "grind", + "title": "Route 4's grass: a GRIND to Lv23", + "events": [ + { + "id": "grind_patch", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route4", "where": {"facing": "west", "kind": "tiles", "tiles": [[86, 16]]}}, + "why": "west out of the city along Route 4's lower road onto its grass's east edge" + }, + { + "id": "grind_to_lv23", + "kind": "game_action", + "variant": "grind slot=1 level=23 encounters=100 pace=west heal_every=0", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "wild battles until the lead is Lv23, where Bite takes Starmie in three" + }, + { + "id": "cerulean_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "back to the Center: Misty's battle starts at full HP and PP" + }, + { + "id": "heal_cerulean", + "kind": "heal", + "why": "full HP" + } + ] + }, + { + "id": "cerulean_gym", + "title": "Cerulean Gym to below Misty's platform", + "events": [ + { + "id": "gym_below_luis", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_Gym", "where": {"kind": "tiles", "tiles": [[9, 13]]}}, + "why": "into the Gym, up column 9 to row 13, short of Luis's (9, 12)" + }, + { + "id": "gym_west_walkway", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "west", "tiles": 5}], + "why": "west to the wall (4, 13)" + }, + { + "id": "below_misty_platform", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 3}, {"direction": "east", "tiles": 8}, {"direction": "north", "tiles": 3}, {"direction": "west", "tiles": 3}], + "why": "walls (4, 10), (12, 10), (12, 7), then to (9, 7), out of Diana's line" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14c.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14c.plan.json new file mode 100644 index 0000000000..989e062d93 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14c.plan.json @@ -0,0 +1,71 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.14c", + "title": "Cerulean Gym: Misty, then Water Pulse", + "notes": [ + "Misty (TRAINER_LEADER_MISTY: Staryu Lv18, Starmie Lv21), talked to from (9, 6), beside her on the platform. Her win text runs on to the CASCADE BADGE, TM03 (Water Pulse) and its explanation, all read with the battle's. Bite is chosen against her party (Starmie is Water/Psychic). Back to the nurse round the pool's west side, out of Luis's line; then Water Pulse in place of Tail Whip, last, at the Center, from the bag's TM CASE. Ends where 00.15 starts." + ], + "start": {"certainty": "tile", "map": "CeruleanCity_Gym", "where": {"facing": "west", "kind": "tiles", "tiles": [[9, 7]]}}, + "sections": [ + { + "id": "misty", + "title": "Misty", + "events": [ + { + "id": "beside_misty", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_Gym", "where": {"facing": "west", "kind": "tiles", "tiles": [[9, 6]]}}, + "why": "up onto (9, 6), facing Misty (8, 6)" + }, + { + "id": "challenge_misty", + "kind": "talk", + "why": "Misty's speech" + }, + { + "id": "misty", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "CeruleanCity_Gym", "where": {"facing": "west", "kind": "tiles", "tiles": [[9, 6]]}}, + "why": "Staryu and Starmie, with Bite; CASCADE BADGE and TM03" + } + ] + }, + { + "id": "cerulean_center", + "title": "Out of the Gym to the Center, then Water Pulse", + "events": [ + { + "id": "gym_way_out", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "south", "tiles": 1}, {"direction": "east", "tiles": 3}, {"direction": "south", "tiles": 3}, {"direction": "west", "tiles": 8}, {"direction": "south", "tiles": 3}, {"direction": "east", "tiles": 5}], + "why": "walls (12, 7), (12, 10), (4, 10), (4, 13), (9, 13), round Luis's (9, 12)" + }, + { + "id": "cerulean_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "down column 9 out of the Gym, to the Center" + }, + { + "id": "heal_cerulean", + "kind": "heal", + "why": "full HP" + }, + { + "id": "teach_water_pulse", + "kind": "game_action", + "variant": "teach tm03 water-pulse slot=1 forget=tail-whip", + "once_per_save": true, + "why": "TM03 Water Pulse in place of Tail Whip" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15.plan.json new file mode 100644 index 0000000000..e5d41285fe --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15.plan.json @@ -0,0 +1,93 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.15", + "title": "Cerulean: Water Pulse, Potions, a GRIND until the rival is winnable, then saved below his trigger", + "notes": [ + "Before the rival north of Cerulean (cerulean_rival()), from the Center: first TM03 Water Pulse over Tail Whip, if the lead doesn't know it yet, so the GRIND's sim plays it (a run without TM03 from Misty skips the step). Then 3 Potions at the Mart (CeruleanCity_Mart_Items: POTION is row 3, 300 each; Mt. Moon's trainers leave well over the floor) and 2 Awakenings (row 6, 250 each; pokefirered items.json) for the rival's Bulbasaur's Sleep Powder (console B, m56: asleep at 34/65 HP, it fainted two turns later with 15 Potions), 1400 of the floor's 1500. Then a GRIND on Route 4's grass east of Mt. Moon (75..86, 11..17; Spearow, Rattata, Ekans and Mankey around Lv10, about 80 EXP each), healing at the Center, until the battle sim beats the rival 00.15b fights 50% or more: the win chance his battle refuses below. Console B's Wartortle (Lv22, 65 HP, Water Pulse) is about 42% there and 62% at Lv23; Lv28 (about 88%, some 120 battles from Lv22) is the cap. Then the nurse, and the save on (23, 7), the tile below his trigger: his battle is its own checkpoint, 00.15b, verified and retried from this save." + ], + "start": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "water_pulse", + "title": "Water Pulse in the Center", + "events": [ + { + "id": "teach_water_pulse", + "kind": "game_action", + "variant": "teach tm03 water-pulse slot=1 forget=tail-whip", + "once_per_save": true, + "why": "TM03 Water Pulse in place of Tail Whip, unless the lead knows it or there is no TM03" + } + ] + }, + { + "id": "cerulean_mart", + "title": "Cerulean Mart", + "events": [ + { + "id": "mart_clerk", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_Mart", "where": {"facing": "west", "kind": "tiles", "tiles": [[4, 3]]}}, + "why": "out of the Center to the Mart's clerk" + }, + { + "id": "buy_potions", + "kind": "game_action", + "variant": "mart CeruleanCity_Mart budget=1500 floor=1500 potion=3 awakening=2", + "once_per_save": true, + "why": "3 Potions: the GRIND fights wild battles only with 2 or more; 2 Awakenings for the rival's Sleep Powder" + } + ] + }, + { + "id": "grind", + "title": "Route 4's grass: a GRIND until the rival is winnable", + "events": [ + { + "id": "grind_patch", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route4", "where": {"facing": "west", "kind": "tiles", "tiles": [[86, 16]]}}, + "why": "west out of the city along Route 4's lower road onto its grass's east edge" + }, + { + "id": "grind_for_rival", + "kind": "game_action", + "variant": "grind slot=1 level=28 encounters=150 pace=west heal_every=0 beat=TRAINER_RIVAL_CERULEAN_BULBASAUR chance=50", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "wild battles until the sim beats the rival 50% or more, Lv28 at most; the nurse when hurt or low on PP" + }, + { + "id": "cerulean_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "back to the Center: the rival's battle starts at full HP and PP" + }, + { + "id": "heal_cerulean", + "kind": "heal", + "why": "full HP" + } + ] + }, + { + "id": "rival_trigger", + "title": "North to below the rival's trigger", + "events": [ + { + "id": "below_rival_trigger", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[23, 7]]}}, + "why": "out of the Center, north up to (23, 7), short of the trigger row 6" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15b.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15b.plan.json new file mode 100644 index 0000000000..e2fe43c68b --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15b.plan.json @@ -0,0 +1,40 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.15b", + "title": "Cerulean: Water Pulse if not known, then the rival north of the city", + "notes": [ + "The rival north of Cerulean (CeruleanCity scripts.inc): the only way north to Nugget Bridge is columns 22..24, and a step onto row 6 there (CeruleanCity_EventScript_RivalTrigger*, VAR_MAP_SCENE_CERULEAN_CITY_RIVAL 0) brings him down to the player. From the middle, (23, 6), he stops at (23, 5); after the battle he walks round the player and off south, and the player, turning to watch him, ends facing south. His FAME CHECKER's text is read to the overworld with the battle's. For a player who took Squirtle he has Pidgeotto Lv17, Abra Lv16, Rattata Lv15 and Bulbasaur Lv18 (sParty_RivalCeruleanBulbasaur), whose Vine Whip and Sleep Powder are the danger. The row starts from 00.15's save below the trigger, and saves after him. First TM03 Water Pulse over Tail Whip (from the bag, where the player stands), if the lead doesn't know it yet: console B's save (2026-11-03) is past Misty's TEACH, which failed there. Where she taught it, the step reads the moves and does nothing." + ], + "start": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[23, 7]]}}, + "sections": [ + { + "id": "rival_cerulean", + "title": "The rival north of Cerulean", + "events": [ + { + "id": "teach_water_pulse", + "kind": "game_action", + "variant": "teach tm03 water-pulse slot=1 forget=tail-whip", + "once_per_save": true, + "why": "TM03 Water Pulse in place of Tail Whip, unless the lead knows it" + }, + { + "id": "rival_trigger", + "kind": "step", + "direction": "north", + "why": "onto (23, 6), the middle trigger: \"Yo! !\"" + }, + { + "id": "rival_cerulean", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[23, 6]]}}, + "why": "Pidgeotto, Abra, Rattata and Bulbasaur; then the FAME CHECKER" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.16.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.16.plan.json new file mode 100644 index 0000000000..aa4749e3d6 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.16.plan.json @@ -0,0 +1,173 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.16", + "title": "Nugget Bridge: the nurse, five trainers and the Rocket", + "notes": [ + "Nugget Bridge (Route24 map.json) is x 10..12 from row 32 north to row 16, the city's columns 22..24 (offset 12). Its five trainers stand on it facing across, sight 2, so each line crosses the middle column 11 on the trainer's row: Bug Catcher Cale (12, 31), Lass Ali (10, 28), Youngster Timmy (12, 25), Lass Reli (10, 22) and Camper Ethan (12, 19). Each is fought on the step onto their row, beside them: they don't walk, and the player keeps facing north. Two tiles separate the rows, walked as one short leg; the step onto a row goes on a tile at a time until the trainer sees the player (into_line()), as the open leg onto the bridge ended a tile short twice on console B (2026-11-03), on (11, 33), and one step left the player out of Cale's sight. Then the Rocket (12, 15): the step onto (11, 15) (Route24_EventScript_RocketTriggerRight, VAR_MAP_SCENE_ROUTE24 0) gives the NUGGET, \"join Team Rocket\", and his battle; he only turns, and the player turns east to him. Camper Shane (5, 21) looks north from the west bank, off the way." + ], + "start": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[23, 6]]}}, + "sections": [ + { + "id": "cerulean_center", + "title": "Cerulean's Pokemon Center", + "events": [ + { + "id": "cerulean_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "back to the Center after the rival, before Nugget Bridge" + }, + { + "id": "heal_cerulean", + "kind": "heal", + "why": "full HP" + } + ] + }, + { + "id": "nugget_bridge", + "title": "Nugget Bridge's five trainers", + "events": [ + { + "id": "below_cale_line", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route24", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 32]]}}, + "why": "out of the Center, north past the rival's row onto the bridge, below Cale's row" + }, + { + "id": "cale_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "onto (11, 31), beside Cale" + }, + { + "id": "bug_catcher_cale", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route24", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 31]]}}, + "why": "his Bug Pokemon" + }, + { + "id": "below_ali_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 2}], + "why": "up to (11, 29)" + }, + { + "id": "ali_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "onto (11, 28), beside Ali" + }, + { + "id": "lass_ali", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route24", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 28]]}}, + "why": "her Pokemon" + }, + { + "id": "below_timmy_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 2}], + "why": "up to (11, 26)" + }, + { + "id": "timmy_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "onto (11, 25), beside Timmy" + }, + { + "id": "youngster_timmy", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route24", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 25]]}}, + "why": "his Pokemon" + }, + { + "id": "below_reli_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 2}], + "why": "up to (11, 23)" + }, + { + "id": "reli_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "onto (11, 22), beside Reli" + }, + { + "id": "lass_reli", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route24", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 22]]}}, + "why": "her Pokemon" + }, + { + "id": "below_ethan_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 2}], + "why": "up to (11, 20)" + }, + { + "id": "ethan_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "onto (11, 19), beside Ethan" + }, + { + "id": "camper_ethan", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route24", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 19]]}}, + "why": "his Pokemon" + } + ] + }, + { + "id": "rocket", + "title": "The Rocket at the bridge's north end", + "events": [ + { + "id": "ready_for_rocket", + "kind": "ready", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "items for a status or low HP; the nurse only if they can't", + "notes": [ + "A route, not legs: the nurse may have been needed." + ] + }, + { + "id": "below_rocket_trigger", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route24", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 16]]}}, + "why": "up the bridge to below the Rocket's trigger (11, 15)" + }, + { + "id": "rocket_trigger", + "kind": "step", + "direction": "north", + "why": "onto (11, 15), beside the Rocket" + }, + { + "id": "rocket_grunt", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route24", "where": {"facing": "east", "kind": "tiles", "tiles": [[11, 15]]}}, + "why": "the NUGGET, then Ekans and Zubat, Lv15" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.17.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.17.plan.json new file mode 100644 index 0000000000..d3e074547a --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.17.plan.json @@ -0,0 +1,146 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.17", + "title": "Route 24 and 25 to Bill's Sea Cottage: Franklin, Kelsey, Nob and Haley", + "notes": [ + "Route 24 north of Nugget Bridge (Route24 map.json) joins Route 25 by its rows 7..9; the ledges on Route 25's row 6 (x 0..9) leave the way north at x 12..13. Route 25 (Route25 map.json, scripts.inc) to Bill's Sea Cottage door (51, 4): of its nine trainers, four can't be walked around.", + "- Hiker Franklin (11, 4) faces east, sight 4, over (12..15, 4), and Hiker Wayne (17, 7) east over (18..19, 7): the only ways east past x 15. Franklin is fought, from (15, 5) on the step onto (15, 4), the end of his line: he walks up to (14, 4).", + "- Youngster Joey (18, 2) faces south over (18, 3..4): the way keeps off column 18, south round Wayne to (19, 6) and up to (19, 4).", + "- Youngster Dan (22, 4) looks around, sight 2, over the only way east, (20..21, 4). On (20, 4), a one-tile leg, he may see the player and walk up to (21, 4); so the way goes north from there to row 2, east, and down column 23 past him to (23, 5), off every tile he may walk up to. Where his sight is crossed, the leg ends on a wall or is one tile; if he looks the player's way, story_interrupts() fight him.", + "- Picnicker Kelsey (22, 8) faces east over (23..24, 8), across the only way on: fought on the step onto (24, 8); she walks up to (23, 8).", + "- Hiker Nob (27, 9) faces north over (27, 6..8), Camper Flint (28, 4) south over (28, 5..7): every way east crosses one. Nob is fought on the step onto (27, 8), beside him; the way on keeps off Flint's column, by one-tile legs and legs to walls.", + "- Youngster Chad (36, 4) walks up and down column 36 (rows 2..7), so he may see any tile of it down to row 9. The way crosses his column on row 9, a leg to the water at (42, 9), where he can only walk up beside it.", + "- Lass Haley (42, 5) faces south over (42, 6..8), the only way east of x 42: fought on the step onto (42, 8); she walks up to (42, 7). The row saves inside the Sea Cottage, which the save card names ROUTE 25." + ], + "start": {"certainty": "tile", "map": "Route24", "where": {"facing": "east", "kind": "tiles", "tiles": [[11, 15]]}}, + "sections": [ + { + "id": "route24_franklin", + "title": "Off the bridge to Hiker Franklin", + "events": [ + { + "id": "below_franklin_line", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route25", "where": {"facing": "north", "kind": "tiles", "tiles": [[15, 5]]}}, + "why": "north off Nugget Bridge, east into Route 25, round its ledges to (15, 5), below the end of Franklin's line" + }, + { + "id": "franklin_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "onto (15, 4), at the end of Franklin's line" + }, + { + "id": "hiker_franklin", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route25", "where": {"facing": "north", "kind": "tiles", "tiles": [[15, 4]]}}, + "why": "Machop and Geodude, Lv15; Franklin stops at (14, 4)" + } + ] + }, + { + "id": "route25_kelsey", + "title": "Round Dan to Picnicker Kelsey", + "events": [ + { + "id": "west_of_dan", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 2}, {"direction": "south", "tiles": 2}, {"direction": "east", "tiles": 2}, {"direction": "north", "tiles": 2}], + "why": "to (17, 4), short of Joey's (18, 4); round Wayne (17, 7) to (19, 6); up to (19, 4)" + }, + { + "id": "round_dan", + "kind": "legs", + "variant": "run", + "legs": [ + {"direction": "east", "tiles": 1}, {"direction": "north", "tiles": 2}, {"direction": "east", "tiles": 4}, {"direction": "west", "tiles": 1}, {"direction": "south", "tiles": 3}, {"direction": "east", "tiles": 1}, + {"direction": "south", "tiles": 2} + ], + "why": "onto (20, 4); up to (20, 2), east to (24, 2), down column 23 past Dan to (23, 5), to (24, 7) above Kelsey's line" + }, + { + "id": "kelsey_line", + "kind": "step", + "variant": "trainer_line", + "direction": "south", + "why": "onto (24, 8), at the end of Kelsey's line" + }, + { + "id": "picnicker_kelsey", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route25", "where": {"facing": "south", "kind": "tiles", "tiles": [[24, 8]]}}, + "why": "Nidoran M and Nidoran F, Lv15; Kelsey stops at (23, 8)" + } + ] + }, + { + "id": "route25_nob", + "title": "Hiker Nob", + "events": [ + { + "id": "west_of_nob_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 2}], + "why": "along row 8 to (26, 8)" + }, + { + "id": "nob_line", + "kind": "step", + "variant": "trainer_line", + "direction": "east", + "why": "onto (27, 8), above Nob" + }, + { + "id": "hiker_nob", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route25", "where": {"facing": "east", "kind": "tiles", "tiles": [[27, 8]]}}, + "why": "three Geodude and a Machop, Lv13" + } + ] + }, + { + "id": "route25_haley", + "title": "Past Chad to Lass Haley and the Sea Cottage", + "events": [ + { + "id": "below_haley_line", + "kind": "legs", + "variant": "run", + "legs": [ + {"direction": "east", "tiles": 1}, {"direction": "south", "tiles": 1}, {"direction": "east", "tiles": 1}, {"direction": "north", "tiles": 3}, {"direction": "east", "tiles": 5}, {"direction": "south", "tiles": 3}, + {"direction": "east", "tiles": 8} + ], + "why": "below Flint's column to (29, 9), up to (29, 6), east to (34, 6), down to (34, 9), east along row 9 past Chad's column to the water, (42, 9)" + }, + { + "id": "haley_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "onto (42, 8), at the end of Haley's line" + }, + { + "id": "lass_haley", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route25", "where": {"facing": "north", "kind": "tiles", "tiles": [[42, 8]]}}, + "why": "Oddish, Pidgey and Oddish, Lv13; Haley stops at (42, 7)" + }, + { + "id": "sea_cottage", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route25_SeaCottage", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 8]]}}, + "why": "east along row 8, up column 51 into the Sea Cottage (51, 4), a step in from its mat" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.18.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.18.plan.json new file mode 100644 index 0000000000..6f0cc4d022 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.18.plan.json @@ -0,0 +1,112 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.18", + "title": "Bill's Sea Cottage: the Cell Separator and the S.S. Ticket", + "notes": [ + "Bill's Sea Cottage (Route25_SeaCottage scripts.inc). Bill is the Clefairy at (10, 6) (FLAG_HELPED_BILL_IN_SEA_COTTAGE unset): talked to from (10, 7), below him, he asks \"Help me out!\" (YES/NO; NO goes on the same), says to run the Cell Separation on the PC, and walks up into the right teleporter (10, 3); with the player facing north he walks straight up. The PC (4, 5) answers only from the south, (4, 6). Its scene: the teleporters run, Bill comes out of the left one (3, 3) and walks to (7, 6) (BillWalkToMiddleOfRoom); the player isn't moved. Talked to from (6, 6), Bill gives the S.S. TICKET, no YES/NO, and goes on about the S.S. ANNE (FLAG_GOT_SS_TICKET, which also hides Nugget Bridge's Rocket). Out by the mat (7, 9); the door puts the player on (51, 5), facing south. The maps keep Bill at (7, 5), but after his scene he stands on (7, 6), in the way from (6, 6) to the mat (console B, 2026-11-03), so the walk out goes around him to (7, 8) first." + ], + "start": {"certainty": "tile", "map": "Route25_SeaCottage", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 8]]}}, + "sections": [ + { + "id": "clefairy", + "title": "Bill as a Clefairy", + "events": [ + { + "id": "below_bill_clefairy", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route25_SeaCottage", "where": {"facing": "north", "kind": "tiles", "tiles": [[10, 7]]}}, + "why": "below the Clefairy (10, 6), facing it" + }, + { + "id": "bill_clefairy", + "kind": "talk", + "why": "\"Hiya! I'm a POKEMON... ...No I'm not!\" ... \"Help me out!\"" + }, + { + "id": "help_bill", + "kind": "answer", + "why": "YES: \"run the Cell Separation on the PC\"" + }, + { + "id": "bill_into_teleporter", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "Route25_SeaCottage", "where": {"facing": "north", "kind": "tiles", "tiles": [[10, 7]]}}, + "why": "the Clefairy walks up into the right teleporter" + } + ] + }, + { + "id": "cell_separator", + "title": "The PC's Cell Separator", + "events": [ + { + "id": "pc_front", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route25_SeaCottage", "where": {"facing": "north", "kind": "tiles", "tiles": [[4, 6]]}}, + "why": "below the PC (4, 5), facing it" + }, + { + "id": "cell_separator", + "kind": "talk", + "why": "the PC: \" initiated the TELEPORTER's CELL SEPARATOR!\"" + }, + { + "id": "bill_comes_out", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "Route25_SeaCottage", "where": {"facing": "north", "kind": "tiles", "tiles": [[4, 6]]}}, + "why": "the teleporters run; Bill comes out of the left one and walks to (7, 6)" + } + ] + }, + { + "id": "ss_ticket", + "title": "Bill and the S.S. Ticket", + "events": [ + { + "id": "beside_bill", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 2}], + "why": "to (6, 6), west of Bill, facing him" + }, + { + "id": "thank_bill", + "kind": "talk", + "why": "\"Thanks, bud! ... take this\"" + }, + { + "id": "ss_ticket", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "Route25_SeaCottage", "where": {"facing": "east", "kind": "tiles", "tiles": [[6, 6]]}}, + "why": "the S.S. TICKET; \"... the S.S. ANNE ... go instead\"" + } + ] + }, + { + "id": "out", + "title": "Round Bill and out of the cottage", + "events": [ + { + "id": "around_bill", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "south", "tiles": 2}, {"direction": "east", "tiles": 1}], + "why": "to (7, 8), above the mat; Bill stands on (7, 6) after his scene" + }, + { + "id": "out_of_cottage", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route25", "where": {"facing": "south", "kind": "tiles", "tiles": [[51, 5]]}}, + "why": "down to the mat and out; the door puts the player below it" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.19.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.19.plan.json new file mode 100644 index 0000000000..0e74ab339c --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.19.plan.json @@ -0,0 +1,47 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.19", + "title": "Route 25's south path back to Cerulean's Pokemon Center", + "notes": [ + "Back to Cerulean by Route 25's south path (Route25 map.json): from below the cottage down column 51 to row 12, west along row 12 and row 11 into Route 24 (x 14..23, row 11), and down its column 14..16 east of Nugget Bridge to Cerulean's north edge (x 26..28, offset 12). No trainer stands on it. Cerulean's row 1 there is a ledge (26..28, 1), hopped south from (27, 0); then on into the city to the Pokemon Center." + ], + "start": {"certainty": "tile", "map": "Route25", "where": {"facing": "south", "kind": "tiles", "tiles": [[51, 5]]}}, + "sections": [ + { + "id": "route25_south_path", + "title": "Route 25's south path to Cerulean's Pokemon Center", + "events": [ + { + "id": "above_cerulean_ledge", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[27, 0]]}}, + "why": "Route 25's south path west, Route 24 east of the bridge south, to above Cerulean's ledge (27, 1)" + }, + { + "id": "cerulean_ledge_hop", + "kind": "step", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[27, 2]]}}, + "why": "down the ledge (27, 1)" + }, + { + "id": "cerulean_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "into the city to the Pokemon Center" + }, + { + "id": "heal_cerulean", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.20.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.20.plan.json new file mode 100644 index 0000000000..d8a251d0aa --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.20.plan.json @@ -0,0 +1,75 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.20", + "title": "Route 24's grass: an Oddish for Cut, then the Center", + "notes": [ + "Stopgap for console B's save, which caught nothing on Route 1. A new playthrough should instead catch a Rattata on Route 1 or 22 early, as its Cut user and spare member, and drop this row (and its walk over Nugget Bridge and back).", + "An Oddish for HM01 Cut, and a second member behind the lead (docs/design/AutoStory-FRLG-FutureEvents.md, \"Console B run choices\"). Route 24's grass (Route24 map.json: x 3..5, rows 19..31, west of Nugget Bridge) is the nearest to the Center with Oddish: FireRed's sRoute24_FireRed (pokefirered wild_encounters.json) has it in 25% of battles, Lv12..14, as Route 25's grass; Routes 5 and 6 aren't on the story's maps yet. Up the bridge, whose trainers 00.16 beat, west along rows 10..15 to the cliff (3, 15) and down column 3 to the grass's first row, (3, 19); the step paces east to (4, 19) and back. Camper Shane (5, 21) faces north, sight 5, over (5, 16..20): column 5 is kept off. The leg west ends on the cliff, not open: on console B (m63) an open leg west to (4, 15) ended a tile short, and the walk down column 5 came into Shane's sight.", + "First 7 Poke Balls at the Mart (row 1, 200 each; the bag isn't read, so the step buys them whatever the bag holds). The floor, from 00.15's 1500 less its 1400: the rival's 288 (4 x 18 x 4), the Rocket's 480 (4 x 15 x 8), Franklin's 540 and Nob's 468 (hikers, 9) alone make 1876 (pokefirered gTrainerMoneyTable). An Oddish at full HP (catch rate 255) stays in a Poke Ball about 1 time in 3 (Gen 3: a = 85, four shakes of 0.76), so 7 balls catch it about 94% of the time; the step throws 7 at most, in 30 battles (none an Oddish: 0.02%). Nothing weakens it: the HP bar isn't read on the console yet.", + "The Oddish joins the party second. Battles keep the lead in (PokemonFRLG_AutoStoryBattleDecisions.h): the Oddish is sent only when the lead faints in a trainer's battle, which counts as lost. Back to the nurse, and the save in the Center." + ], + "start": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "cerulean_mart", + "title": "Cerulean Mart: Poke Balls", + "events": [ + { + "id": "mart_clerk", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_Mart", "where": {"facing": "west", "kind": "tiles", "tiles": [[4, 3]]}}, + "why": "out of the Center to the Mart's clerk" + }, + { + "id": "buy_poke_balls", + "kind": "game_action", + "variant": "mart CeruleanCity_Mart budget=1400 floor=1500 poke-ball=7", + "once_per_save": true, + "why": "7 Poke Balls for the Oddish" + } + ] + }, + { + "id": "route24_grass", + "title": "Route 24's grass: an Oddish", + "events": [ + { + "id": "oddish_patch", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route24", "where": {"kind": "tiles", "tiles": [[3, 19]]}}, + "why": "north over Nugget Bridge, west to the cliff and down column 3 onto the grass's first row, out of Shane's column 5" + }, + { + "id": "catch_oddish", + "kind": "game_action", + "variant": "capture oddish catches=1 encounters=30 balls=7 pace=east", + "why": "wild battles until an Oddish, then Poke Balls; any other species is run from" + } + ] + }, + { + "id": "cerulean_center", + "title": "Back to Cerulean's Pokemon Center", + "events": [ + { + "id": "cerulean_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "back over the bridge to the Center" + }, + { + "id": "heal_cerulean", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.21.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.21.plan.json new file mode 100644 index 0000000000..464619c714 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.21.plan.json @@ -0,0 +1,94 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.21", + "title": "The Cerulean Rocket, Route 5, the Underground Path and Route 6 to Vermilion's Pokemon Center: Jeff", + "notes": [ + "Cerulean's only way south without Cut (CeruleanCity map.json, scripts.inc) is through the burgled house (30, 11), whose door the policeman leaves once the S.S. TICKET is had, out of the hole in its back wall into the yard (31, 8). The Rocket there (33, 6) stops the player on (33, 5) or (33, 7) (CeruleanCity_EventScript_GruntTrigger*): from (32, 7), the step east onto (33, 7) turns the player to him; Machop and Drowzee, Lv17, then TM28 Dig, and he is gone. Then east round the yard, down to the city's south edge by the path along its east side (39..40, 20..31), down Route 5's east side to the Underground Path's north house (31, 31), through its tunnel and out of the south house onto Route 6, below its door (19, 14). Route 6:", + "- Bug Catcher Keigo (3, 16) faces east, sight 5, over the middle path (4..8, 16); Bug Catcher Elijah (20, 25) faces west, sight 3, over the east path (17..19, 25). The way goes down column 15, through the grass to the wall (15, 33), out of both lines; the open leg west to it, a tile short, leaves the player on column 16, out of them too.", + "- Camper Ricky and Picnicker Nancy (12..13, 21) battle only when talked to.", + "- Picnicker Isabelle (13, 32) and Camper Jeff (13, 33) face west, sight 3, over (10..12, 32) and (10..12, 33); the only way south, (10..12, 34), is from Jeff's row. Up to (15, 31), west to the wall (7, 31), down to the wall (7, 33), east: Jeff is fought on the step onto (10, 33) and walks up to (11, 33). Isabelle's row is kept off. Then down into Vermilion City (offset 12) to its Pokemon Center." + ], + "start": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "cerulean_rocket", + "title": "The Rocket behind the burgled house", + "events": [ + { + "id": "west_of_rocket", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "east", "kind": "tiles", "tiles": [[32, 7]]}}, + "why": "through the burgled house and out of the hole in its back wall, west of the Rocket (33, 6)" + }, + { + "id": "rocket_trigger", + "kind": "step", + "direction": "east", + "why": "onto (33, 7), below the Rocket" + }, + { + "id": "rocket_grunt", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[33, 7]]}}, + "why": "Machop and Drowzee, Lv17; TM28 Dig" + } + ] + }, + { + "id": "route6", + "title": "Route 5, the Underground Path and Route 6: Camper Jeff", + "events": [ + { + "id": "route6_column15", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route6", "where": {"kind": "tiles", "tiles": [[15, 14]]}}, + "why": "round the yard and down Cerulean's east side, Route 5 into the Underground Path, through it onto Route 6, west to column 15" + }, + { + "id": "round_keigo_and_elijah", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "south", "tiles": 19}, {"direction": "north", "tiles": 1}, {"direction": "north", "tiles": 1}, {"direction": "west", "tiles": 8}, {"direction": "south", "tiles": 2}, {"direction": "east", "tiles": 2}], + "why": "down column 15 to the wall (15, 33); up to (15, 31), west to the wall (7, 31), down to the wall (7, 33), east to (9, 33)" + }, + { + "id": "jeff_line", + "kind": "step", + "variant": "trainer_line", + "direction": "east", + "why": "onto (10, 33), in Jeff's line" + }, + { + "id": "camper_jeff", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route6", "where": {"facing": "east", "kind": "tiles", "tiles": [[10, 33]]}}, + "why": "Spearow and Raticate, Lv16; Jeff stops at (11, 33)" + } + ] + }, + { + "id": "vermilion_center", + "title": "Vermilion's Pokemon Center", + "events": [ + { + "id": "vermilion_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "down Route 6's south path into Vermilion City, to the Pokemon Center" + }, + { + "id": "heal_vermilion", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.22.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.22.plan.json new file mode 100644 index 0000000000..b932951dda --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.22.plan.json @@ -0,0 +1,160 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.22", + "title": "The S.S. Anne: the rival, HM01 Cut from the Captain, back to Vermilion's Pokemon Center", + "notes": [ + "The S.S. Anne (VermilionCity, SSAnne_* scripts.inc). The step onto (23, 33) (VermilionCity_EventScript_CheckTicket) has the sailor (24, 33) ask for a ticket; the S.S. TICKET (00.18) is shown and the player turns east to him. Then onto the pier (23, 34), along the gangway, through 1F, by legs off its cabins' doormats on row 10, which planned routes cross, and up its stairs (3, 8) to 2F. The step onto (30, 6), below the Captain's door (30, 2) (SSAnne_2F_Corridor_EventScript_RivalTriggerLeft), brings the rival out and down to (30, 5); the player keeps facing north, and after the battle he walks round the player and off. For a player who took Squirtle: Pidgeotto Lv19, Raticate Lv16, Kadabra Lv18, Ivysaur Lv20. The ship's trainers are in its cabins and on its deck, off the way. The Captain (5, 4) faces up; talked to from (5, 5) he is rubbed on the back and gives HM01 (VAR_MAP_SCENE_VERMILION_CITY 1). Back out through 1F's door (19, 1): on the gangway the ship sails (SSAnne_Exterior_ExitSSAnne), the player is put on (23, 34) and walked up to (23, 32) (VermilionCity_EventScript_ExitSSAnne). Cut is taught later, to the Oddish." + ], + "start": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "boarding", + "title": "The ticket check and S.S. Anne 1F", + "events": [ + { + "id": "above_ticket_check", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[23, 32]]}}, + "why": "out of the Center down to the harbor, above the ticket check (23, 33)" + }, + { + "id": "ticket_check", + "kind": "step", + "direction": "south", + "why": "onto (23, 33): \"Do you have a ticket?\"" + }, + { + "id": "show_ss_ticket", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "east", "kind": "tiles", "tiles": [[23, 33]]}}, + "why": "the S.S. TICKET; the player turns to the sailor" + }, + { + "id": "aboard", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "SSAnne_1F_Corridor", "where": {"facing": "south", "kind": "tiles", "tiles": [[19, 2]]}}, + "why": "onto the pier, along the gangway and in by 1F's door, a step below it" + }, + { + "id": "past_the_cabins", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "south", "tiles": 8}, {"direction": "north", "tiles": 1}, {"direction": "west", "tiles": 6}, {"direction": "north", "tiles": 1}], + "why": "to the wall (19, 10), up a step, west to the worker (12, 9), up a step to (13, 8): off the cabins' mats on row 10" + } + ] + }, + { + "id": "rival_ss_anne", + "title": "2F: the rival", + "events": [ + { + "id": "below_rival_trigger", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "SSAnne_2F_Corridor", "where": {"facing": "north", "kind": "tiles", "tiles": [[30, 7]]}}, + "why": "west along 1F, up the stairs (3, 8) and along 2F below the Captain's door" + }, + { + "id": "rival_trigger", + "kind": "step", + "direction": "north", + "why": "onto (30, 6): the rival comes out of the Captain's door" + }, + { + "id": "rival_ss_anne", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "SSAnne_2F_Corridor", "where": {"facing": "north", "kind": "tiles", "tiles": [[30, 6]]}}, + "why": "Pidgeotto, Raticate, Kadabra and Ivysaur" + } + ] + }, + { + "id": "captain", + "title": "The Captain and HM01", + "events": [ + { + "id": "behind_captain", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "SSAnne_CaptainsOffice", "where": {"facing": "north", "kind": "tiles", "tiles": [[5, 5]]}}, + "why": "into the Captain's office, behind the Captain (5, 4)" + }, + { + "id": "captain", + "kind": "talk", + "why": "\"Ooargh... I feel hideous... Urrp! Seasick...\"" + }, + { + "id": "hm01_cut", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "SSAnne_CaptainsOffice", "where": {"facing": "north", "kind": "tiles", "tiles": [[5, 5]]}}, + "why": "the player rubs his back; HM01, and what Cut does" + } + ] + }, + { + "id": "off_the_ship", + "title": "Back out and off the ship", + "events": [ + { + "id": "off_2f", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "SSAnne_1F_Corridor", "where": {"kind": "tiles", "tiles": [[13, 8]]}}, + "why": "back along 2F, down the stairs and east along 1F's row 8" + }, + { + "id": "round_the_sailor", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 4}, {"direction": "south", "tiles": 1}, {"direction": "east", "tiles": 2}], + "why": "to the sailor (18, 8), down a step, east to (19, 9): off the cabins' mats on row 10" + }, + { + "id": "below_ship_door", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "SSAnne_1F_Corridor", "where": {"facing": "north", "kind": "tiles", "tiles": [[19, 2]]}}, + "why": "up below the door to the gangway" + }, + { + "id": "off_the_ship", + "kind": "step", + "direction": "north", + "why": "out onto the gangway" + }, + { + "id": "ss_anne_sails", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[23, 32]]}}, + "why": "the ship sails; the player is walked off the pier to (23, 32)" + } + ] + }, + { + "id": "vermilion_center", + "title": "Vermilion's Pokemon Center", + "events": [ + { + "id": "vermilion_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "back to the Pokemon Center" + }, + { + "id": "heal_vermilion", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.23.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.23.plan.json new file mode 100644 index 0000000000..7ddd9c356c --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.23.plan.json @@ -0,0 +1,178 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.23", + "title": "Vermilion Gym: Cut and Dig, the tree, Dwayne, Baily and Tucker, the trash cans", + "notes": [ + "Rows 00.23 to 00.25: HM01 Cut to the Oddish, the tree before Vermilion Gym, its three trainers and its trash cans, then Lt. Surge from a save in front of him.", + "The gym (VermilionCity_Gym map.json, scripts.inc), 11 x 20, entered on (5, 19): the cans on rows 10, 12 and 14 at x 1, 3, 5, 7, 9 (PokemonFRLG_AutoStoryTrashCans.h), the beams across rows 6 and 7 that the two switches turn off, Lt. Surge (5, 2) facing down above them. Its trainers, sight 3:", + "- Sailor Dwayne (8, 13) and Engineer Baily (2, 11) look around (MOVEMENT_TYPE_LOOK_AROUND): between them they see across rows 11 and 13 and down columns 2 and 8, so no way from the door to the cans keeps out of their sight. Each is fought from the second tile of his line, stepped onto from beside it, out of sight, and waited on there until he turns to the player (TRAINER_WAIT_VARIANT): Dwayne from (8, 15), stepped onto from (7, 15), Baily from (2, 13), from (3, 13). Each walks up a tile, Dwayne to (8, 14), Baily to (2, 12), and stays there while the player is in the gym.", + "- Gentleman Tucker (7, 8) faces west over (4..6, 8): fought on the step up from (5, 9) onto (5, 8); he walks up to (6, 8). The cans' stands, on rows 9, 11 and 13 above each, are clear of where the three stand after their battles. Leaving the gym puts them back where the map has them, beaten, and grows the tree back.", + "HM01 Cut, from the S.S. Anne (00.22), to the Oddish (00.20), second in the party: a wild Oddish of Lv12 to 14 knows 2 or 3 moves (Absorb, Sweet Scent, Poison Powder at 14), so it has room. TM28 Dig, from the Cerulean Rocket (00.21), to the lead in place of Withdraw: Ground, twice as strong on Lt. Surge's three, it takes the sim's chance against him for a Lv28 Wartortle with 14 Potions from 6% to 47%. Vermilion's tree (19, 24) (EventScript_CutTree) is the only way into the gym's yard, row 24 being walls but for it; the gym's door is (14, 25). In the gym, the trainers as above, then the trash cans until the door opens; out, the tree cut again from below, and back to the nurse." + ], + "start": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "teach", + "title": "Cut and Dig in the Center", + "events": [ + { + "id": "teach_cut", + "kind": "game_action", + "variant": "teach hm01 cut slot=2", + "once_per_save": true, + "why": "HM01 Cut to the Oddish, second in the party, unless it knows it" + }, + { + "id": "teach_dig", + "kind": "game_action", + "variant": "teach tm28 dig slot=1 forget=withdraw", + "once_per_save": true, + "why": "TM28 Dig in place of Withdraw, for Lt. Surge, unless the lead knows it or there is no TM28" + } + ] + }, + { + "id": "gym_tree", + "title": "The tree and the gym's door", + "events": [ + { + "id": "above_gym_tree", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[19, 23]]}}, + "why": "out of the Center, south through the city to above the tree (19, 24)" + }, + { + "id": "cut_gym_tree", + "kind": "game_action", + "variant": "cut_tree", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[19, 24]]}}, + "why": "the Oddish cuts the tree; a step onto its tile" + }, + { + "id": "gym_door", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[5, 19]]}}, + "why": "down into the yard and in by the gym's door (14, 25)" + } + ] + }, + { + "id": "gym_trainers", + "title": "Dwayne, Baily and Tucker", + "events": [ + { + "id": "west_of_dwayne_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 4}, {"direction": "east", "tiles": 2}], + "why": "up column 5 to the can (5, 14), east to (7, 15): out of Dwayne's and Baily's sight" + }, + { + "id": "dwayne_sight", + "kind": "step", + "variant": "trainer_wait", + "direction": "east", + "why": "onto (8, 15), two below Dwayne (8, 13): he turns to the player and walks up to (8, 14)" + }, + { + "id": "sailor_dwayne", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "VermilionCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[8, 15]]}}, + "why": "two Pikachu, Lv21" + }, + { + "id": "east_of_baily_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "west", "tiles": 4}, {"direction": "north", "tiles": 2}, {"direction": "west", "tiles": 1}], + "why": "west along row 15 to (4, 15), up to (4, 13), west to (3, 13): out of Baily's sight" + }, + { + "id": "baily_sight", + "kind": "step", + "variant": "trainer_wait", + "direction": "west", + "why": "onto (2, 13), two below Baily (2, 11): he turns to the player and walks up to (2, 12)" + }, + { + "id": "engineer_baily", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "VermilionCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[2, 13]]}}, + "why": "Voltorb and Magnemite, Lv21" + }, + { + "id": "below_tucker_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 2}, {"direction": "north", "tiles": 4}, {"direction": "east", "tiles": 1}], + "why": "east to (4, 13), up column 4 past Baily's row to (4, 9), east to (5, 9): below Tucker's line, not on it" + }, + { + "id": "tucker_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "onto (5, 8), in Tucker's line: he walks up to (6, 8)" + }, + { + "id": "gentleman_tucker", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "VermilionCity_Gym", "where": {"facing": "east", "kind": "tiles", "tiles": [[5, 8]]}}, + "why": "Pikachu, Lv23" + } + ] + }, + { + "id": "trash_cans", + "title": "The trash cans", + "events": [ + { + "id": "trash_cans", + "kind": "game_action", + "variant": "trash_cans", + "why": "the cans until both locks open; a wrong second can resets them" + } + ] + }, + { + "id": "vermilion_center", + "title": "Out, the tree, back to the Center", + "events": [ + { + "id": "below_gym_tree", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[19, 25]]}}, + "why": "out of the gym, east to below the tree, grown back" + }, + { + "id": "cut_tree_out", + "kind": "game_action", + "variant": "cut_tree", + "direction": "north", + "declared_after": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[19, 24]]}}, + "why": "the tree again; a step onto its tile" + }, + { + "id": "vermilion_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "north through the city to the Pokemon Center" + }, + { + "id": "heal_vermilion", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.24.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.24.plan.json new file mode 100644 index 0000000000..e7c920ab07 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.24.plan.json @@ -0,0 +1,115 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.24", + "title": "Route 6's grass until Lt. Surge is winnable, then through the open beams to him", + "notes": [ + "Wild battles on Route 6 until the sim beats Lt. Surge half the time: with Dig, 47% at Lv28 and 51% at Lv29, so a lead the gym's trainers have brought to Lv29 passes at once, and a Lv28 one after a level. The grass nearest the city is Route 6's south patch (13..16, 30..33); Picnicker Isabelle (13, 32) faces west over (10..12, 32), on the way north from the city, so the walk goes along Jeff's row 33 (he is beaten) to the wall (7, 33), up to row 31 and east along it onto the patch's west edge, (13, 31), above Isabelle, out of her line. Back to the nurse the same way. All that is skipped when the lead, read in the Center, passes already. Then the tree, the gym, whose trainers stand where the map has them, beaten, and whose beams stay off (FLAG_FOUND_BOTH_VERMILION_GYM_SWITCHES): up column 5 to below Lt. Surge, and saved there." + ], + "start": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "surge_check", + "title": "Whether Lt. Surge is winnable already", + "events": [ + { + "id": "surge_winnable", + "kind": "game_action", + "variant": "grind_check slot=1 level=32 encounters=150 pace=east heal_every=0 beat=TRAINER_LEADER_LT_SURGE chance=50", + "why": "the lead as it is: if the sim beats Lt. Surge 50% or more already, no trip to Route 6" + } + ] + }, + { + "id": "route6_grind", + "title": "Route 6's grass: a GRIND until he is", + "events": [ + { + "id": "route6_south_path", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route6", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 34]]}}, + "skip_if": ["surge_winnable"], + "why": "out of the Center, north out of the city onto Route 6's path, below Jeff's row" + }, + { + "id": "round_isabelle_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 1}, {"direction": "west", "tiles": 4}, {"direction": "north", "tiles": 2}, {"direction": "east", "tiles": 5}, {"direction": "east", "tiles": 1}], + "skip_if": ["surge_winnable"], + "why": "onto Jeff's row (11, 33), west to the wall (7, 33), up to (7, 31), east along row 31 to (12, 31) and a step onto the grass, (13, 31): out of Isabelle's line" + }, + { + "id": "grind_for_surge", + "kind": "game_action", + "variant": "grind slot=1 level=32 encounters=150 pace=east heal_every=0 beat=TRAINER_LEADER_LT_SURGE chance=50", + "target": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "skip_if": ["surge_winnable"], + "why": "wild battles until the sim beats Lt. Surge 50% or more, Lv32 at most; the nurse when hurt or low on PP" + }, + { + "id": "west_on_row_31", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route6", "where": {"kind": "tiles", "tiles": [[7, 31]]}}, + "skip_if": ["surge_winnable"], + "why": "west along row 31, out of the grass, to the wall (7, 31)" + }, + { + "id": "down_to_jeff_row", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "south", "tiles": 2}, {"direction": "east", "tiles": 4}], + "skip_if": ["surge_winnable"], + "why": "down to the wall (7, 33), east along Jeff's row to (11, 33): out of Isabelle's line" + }, + { + "id": "vermilion_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "skip_if": ["surge_winnable"], + "why": "south into the city to the Pokemon Center" + }, + { + "id": "heal_vermilion", + "kind": "heal", + "skip_if": ["surge_winnable"], + "why": "full HP" + } + ] + }, + { + "id": "to_lt_surge", + "title": "The tree and the open beams to below Lt. Surge", + "events": [ + { + "id": "above_gym_tree", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[19, 23]]}}, + "why": "out of the Center, south through the city to above the tree (19, 24)" + }, + { + "id": "cut_gym_tree", + "kind": "game_action", + "variant": "cut_tree", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[19, 24]]}}, + "why": "the tree; a step onto its tile" + }, + { + "id": "below_lt_surge", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[5, 3]]}}, + "why": "into the gym, up round the cans and through the beams, off, to below Surge (5, 2)" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.25.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.25.plan.json new file mode 100644 index 0000000000..179b116978 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.25.plan.json @@ -0,0 +1,72 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.25", + "title": "Vermilion Gym: Lt. Surge, the THUNDERBADGE, then the Center", + "notes": [ + "Lt. Surge (5, 2), talked to from below, from the save in front of him: Voltorb Lv21, Pikachu Lv18 and Raichu Lv24, Shock Wave and Thunder Wave, a Super Potion and a Full Heal (sParty_LeaderLtSurge); then the THUNDERBADGE and TM34, read to the overworld with the battle's text. Out, the tree, and the nurse. Surge's win also puts Oak's aide on Vermilion City's (25, 7) (VermilionCity_Gym_EventScript_ShowOaksAide), where he stays until talked to; the story's maps don't have him. The way to the nurse along row 7 runs into him (console B, 2026-11-03: the leg west stopped beside him and the step into the Center's door went into a house), so it goes along row 10 to the water instead, clear of him, and up column 14 from there." + ], + "start": {"certainty": "tile", "map": "VermilionCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[5, 3]]}}, + "sections": [ + { + "id": "lt_surge", + "title": "Lt. Surge", + "events": [ + { + "id": "challenge_lt_surge", + "kind": "talk", + "why": "\"Hey, kid! What do you think you're doing here?\"" + }, + { + "id": "lt_surge", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "VermilionCity_Gym", "where": {"facing": "north", "kind": "tiles", "tiles": [[5, 3]]}}, + "why": "Voltorb, Pikachu and Raichu; THUNDERBADGE and TM34" + } + ] + }, + { + "id": "vermilion_center", + "title": "Out, the tree, round Oak's aide to the Center", + "events": [ + { + "id": "below_gym_tree", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[19, 25]]}}, + "why": "down through the beams, out of the gym, east to below the tree, grown back" + }, + { + "id": "cut_tree_out", + "kind": "game_action", + "variant": "cut_tree", + "direction": "north", + "declared_after": {"certainty": "tile", "map": "VermilionCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[19, 24]]}}, + "why": "the tree; a step onto its tile" + }, + { + "id": "below_oaks_aide", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity", "where": {"kind": "tiles", "tiles": [[14, 10]]}}, + "why": "north through the city and west along row 10 to the water, (14, 10): off Oak's aide's row 7" + }, + { + "id": "vermilion_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "up to the Pokemon Center" + }, + { + "id": "heal_vermilion", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.26.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.26.plan.json new file mode 100644 index 0000000000..34d8de5dd6 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.26.plan.json @@ -0,0 +1,156 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.26", + "title": "The BIKE VOUCHER from the Fan Club, back north to Cerulean, the BICYCLE, Repels and Escape Ropes", + "notes": [ + "Rows from 00.26 on, after the THUNDERBADGE: the BICYCLE, and the way east and south to Celadon City.", + "The BIKE VOUCHER and the BICYCLE, from Vermilion's Center to Cerulean's (pokeguy 43:50 to 45:00).", + "- The Pokemon Fan Club (VermilionCity_PokemonFanClub map.json, scripts.inc), in by Vermilion's (12, 17): the chairman (5, 4) sits above a table (5..6, 5..6) with the worker (6, 4) east of him, so he is talked to from (4, 4), west of him. \"Did you come to hear about my POKEMON?\" YES: his story, then the BIKE VOUCHER (FLAG_GOT_BIKE_VOUCHER). Oak's aide stands on Vermilion's (25, 7) since Lt. Surge, where the story's maps don't have him: the way planned from the Center's door goes east along his row 7, so the walk goes down to the water (14, 10) first, as 00.25 came up from it. From the Fan Club, up to (22, 10) first, so that the way on to Route 6 goes up column 22, west of him, not round by column 27.", + "- Route 6 north, the trainers as 00.21 left them: Jeff (13, 33) beaten, Isabelle (13, 32) and Elijah (20, 25) not. As in 00.24, onto Jeff's row, west to the wall (7, 33) and up to row 31, above Isabelle's line; then east along row 31 to the wall (17, 31) and up column 16, west of Elijah's line (17..19, 25) and east of Keigo's (4..8, 16), to the ledge (16, 11). The first three legs are 00.24's, walked on console B; the two after end on walls, which the walker holds again after a wild battle in the grass.", + "- The Underground Path north, Route 5's sides, which have no trainer, and Cerulean's south edge, whose way into the city without the burgled house is the tree (26, 32) (pokeguy 44:42).", + "- The Bike Shop (CeruleanCity_BikeShop), in by (13, 28): the clerk (9, 3) is across the counter (8, 3), talked to from (7, 3). With the voucher he gives the BICYCLE (FLAG_GOT_BICYCLE) with no YES/NO; it isn't registered or ridden yet.", + "- The Mart: 5 Repels (row 9, 350 each) and 2 Escape Ropes (row 8, 550 each; pokefirered items.json), 2850 in all. Console B had about 9000 after Lt. Surge (2026-11-03); the floor of 6000 leaves 3000 or more after them.", + "- The nurse, and the save in Cerulean's Center." + ], + "start": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "fan_club", + "title": "The Fan Club's BIKE VOUCHER", + "events": [ + { + "id": "below_center", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity", "where": {"kind": "tiles", "tiles": [[14, 10]]}}, + "why": "out of the Center, down to the water (14, 10): off Oak's aide's row 7" + }, + { + "id": "beside_chairman", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity_PokemonFanClub", "where": {"facing": "east", "kind": "tiles", "tiles": [[4, 4]]}}, + "why": "east along row 10, down to the Fan Club's door (12, 17), in, and up its west side beside the chairman (5, 4)" + }, + { + "id": "chairman", + "kind": "talk", + "why": "\"I chair the POKEMON Fan Club!\" ... \"Did you come to hear about my POKEMON?\"" + }, + { + "id": "hear_story", + "kind": "answer", + "why": "YES: his story" + }, + { + "id": "bike_voucher", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "VermilionCity_PokemonFanClub", "where": {"facing": "east", "kind": "tiles", "tiles": [[4, 4]]}}, + "why": "the story of his Rapidash; the BIKE VOUCHER" + } + ] + }, + { + "id": "route6_north", + "title": "North over Route 6", + "events": [ + { + "id": "west_of_oaks_aide", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "VermilionCity", "where": {"kind": "tiles", "tiles": [[22, 10]]}}, + "why": "out of the Fan Club, east along row 18 and up column 22 to (22, 10)" + }, + { + "id": "route6_south_path", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route6", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 34]]}}, + "why": "up column 22 west of Oak's aide (25, 7), out of the city onto Route 6's path, below Jeff's row" + }, + { + "id": "round_isabelle_and_elijah", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "north", "tiles": 1}, {"direction": "west", "tiles": 4}, {"direction": "north", "tiles": 2}, {"direction": "east", "tiles": 9}, {"direction": "north", "tiles": 19}], + "why": "onto Jeff's row (11, 33), west to the wall (7, 33), up to (7, 31), east along row 31 to the wall (16, 31), up column 16 to the ledge (16, 12): out of Isabelle's and Elijah's lines" + } + ] + }, + { + "id": "bike_shop", + "title": "Cerulean's tree and the Bike Shop", + "events": [ + { + "id": "below_cerulean_tree", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[26, 33]]}}, + "why": "the Underground Path north, up Route 5's side into Cerulean, below the tree (26, 32)" + }, + { + "id": "cut_cerulean_tree", + "kind": "game_action", + "variant": "cut_tree", + "direction": "north", + "declared_after": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "north", "kind": "tiles", "tiles": [[26, 32]]}}, + "why": "the tree; a step onto its tile" + }, + { + "id": "bike_clerk", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_BikeShop", "where": {"facing": "east", "kind": "tiles", "tiles": [[7, 3]]}}, + "why": "west into the Bike Shop (13, 28), across the counter from the clerk (9, 3)" + }, + { + "id": "talk_bike_clerk", + "kind": "talk", + "why": "\"Oh, that's... A BIKE VOUCHER!\"" + }, + { + "id": "bicycle", + "kind": "scene", + "declared_after": {"certainty": "tile", "map": "CeruleanCity_BikeShop", "where": {"facing": "east", "kind": "tiles", "tiles": [[7, 3]]}}, + "why": "the voucher exchanged for a BICYCLE" + } + ] + }, + { + "id": "cerulean_mart", + "title": "Repels and Escape Ropes, then the Center", + "events": [ + { + "id": "mart_clerk", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_Mart", "where": {"facing": "west", "kind": "tiles", "tiles": [[4, 3]]}}, + "why": "out of the Bike Shop to the Mart's clerk" + }, + { + "id": "buy_repels", + "kind": "game_action", + "variant": "mart CeruleanCity_Mart budget=2850 floor=6000 repel=5 escape-rope=2", + "once_per_save": true, + "why": "5 Repels and 2 Escape Ropes, 2850 at most" + }, + { + "id": "cerulean_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "out of the Mart to the Center" + }, + { + "id": "heal_cerulean", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.27.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.27.plan.json new file mode 100644 index 0000000000..e95427ff8e --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.27.plan.json @@ -0,0 +1,202 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.27", + "title": "East over Route 9 to Route 10's Pokemon Center: the two trees, Alicia, Jeremy and Conner", + "notes": [ + "East out of Cerulean over Route 9 to Route 10's Pokemon Center (pokeguy 45:12 to 47:27), on foot: the Bicycle isn't registered yet.", + "- Cerulean (CeruleanCity map.json): the way east, column 39..40 up from row 33 to rows 18..19 and out of the city's east edge, is reached from the city only through the tree (26, 32), grown back since 00.26 (its flag is a FLAG_TEMP), or the burgled house. Cut from above. Route 9 joins the city's rows 18..19 by its rows 8..9 (offset -10); its tree (2, 8) (FLAG_TEMP_12) closes them, (2, 9) being rock: cut from (1, 8), west of it.", + "- Route 9 (Route9 map.json, scripts.inc), its ledges all facing south. Of its nine trainers, three can't be walked around, as in pokeguy's run, where Jeremy happened to look away:", + " - Picnicker Alicia (14, 10) faces west, sight 3, over (11..13, 10), the only tiles above the ledge row 11 the way west goes down by: fought on the step onto (11, 10); she walks up to (12, 10). Then down the ledge (11, 11).", + " - Hiker Jeremy (20, 17) looks north and west (FACE_UP_AND_LEFT), sight 4: north over (20, 13..16), every way east of column 19 below the rocks of row 12. Fought on (20, 13), waiting for him to look north (TRAINER_WAIT_VARIANT); he walks up to (20, 14).", + " - Bug Catcher Conner (49, 7) faces east, sight 3, over (50..52, 7): the way north from row 13 is column 52 alone (the ledges of row 11 face south), and row 7 is the way onto the ridge. Fought on the step onto (52, 7); he walks up to (51, 7).", + " - Hiker Brice wanders row 2 (50..56), looking west and east, sight 1, over (49..57, 2), and the way up from the ledges of row 4 is (50..51, 4) below him. The leg up column 51 ends on row 3 if he stands at (51, 2), else it goes on to (51, 1) past him, and he may see the player there; either way the legs to walls after it meet on (48, 1): west to the rock (48, 3) or (48, 1), up to (48, 1). Row 1 is clear of his sight; east along it to the rock (64, 1) and down column 64 past the hidden Rare Candy (63, 2), not picked up. A held leg down to (64, 3) would hop the ledge (64, 4) and go on, so the last tile is one step, then the hop.", + " - Picnicker Caitlin (60, 9) faces east over (61..64, 9): the way to Route 10 goes along row 8, above her line, by legs to (70, 8), a tile short of the edge. Camper Chris (26, 7), Bug Catcher Brent (26, 1), Camper Drew (36, 6) and Hiker Alan (61, 16) are off the way; TM40 (12, 17) is left.", + "- Route 10 (Route10 map.json) joins Route 9's rows 8..10 by its own; its north part, down to the Center's door (13, 20), has no trainer on the way and its grass is walked round. The trees west of the door (10, 20..26) stay. The walk there ends its open legs where a tile of error can't reach a blocker, and finds its place again on walls (console B, 2026-11-09, two runs \"no fade\" at the door): the step onto Route 10 is not always held up, so the player may stand a tile further east, and the leg along row 11 holds to the wall (17, 11) instead of ending on (16, 11) by count. The 12 tiles down column 16 may end a tile short or long: from rows 22..29 a tile west and up to the Center's wall (15, 20) all end on (15, 21). West along row 21 to the wall (9, 21) ends on (10, 21), three west of the door's column: the only count no wall after it checks.", + "- The nurse, and the save in Route 10's Center: the save card names ROUTE 10." + ], + "start": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "cerulean_trees", + "title": "Cerulean's tree and Route 9's", + "events": [ + { + "id": "above_cerulean_tree", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[26, 31]]}}, + "why": "out of the Center, south through the city to above the tree (26, 32), grown back" + }, + { + "id": "cut_cerulean_tree", + "kind": "game_action", + "variant": "cut_tree", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "CeruleanCity", "where": {"facing": "south", "kind": "tiles", "tiles": [[26, 32]]}}, + "why": "the tree; a step onto its tile" + }, + { + "id": "west_of_route9_tree", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route9", "where": {"facing": "east", "kind": "tiles", "tiles": [[1, 8]]}}, + "why": "down to row 33, east and up column 39 or 40 to row 18, east out of the city onto Route 9, west of its tree (2, 8)" + }, + { + "id": "cut_route9_tree", + "kind": "game_action", + "variant": "cut_tree", + "direction": "east", + "declared_after": {"certainty": "tile", "map": "Route9", "where": {"facing": "east", "kind": "tiles", "tiles": [[2, 8]]}}, + "why": "Route 9's tree; a step onto its tile" + } + ] + }, + { + "id": "route9_west", + "title": "Route 9 west: Alicia and Jeremy", + "events": [ + { + "id": "above_alicia_line", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route9", "where": {"facing": "south", "kind": "tiles", "tiles": [[11, 9]]}}, + "why": "east to (11, 9), above the west end of Alicia's line" + }, + { + "id": "alicia_line", + "kind": "step", + "variant": "trainer_line", + "direction": "south", + "why": "onto (11, 10), in Alicia's line: she walks up to (12, 10)" + }, + { + "id": "picnicker_alicia", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route9", "where": {"facing": "east", "kind": "tiles", "tiles": [[11, 10]]}}, + "why": "Oddish, Bellsprout, Oddish and Bellsprout, Lv18" + }, + { + "id": "alicia_ledge_hop", + "kind": "step", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "Route9", "where": {"facing": "south", "kind": "tiles", "tiles": [[11, 12]]}}, + "why": "down the ledge (11, 11)" + }, + { + "id": "west_of_jeremy_sight", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route9", "where": {"facing": "east", "kind": "tiles", "tiles": [[19, 13]]}}, + "why": "down to row 13 and east along it to (19, 13), west of Jeremy's north line" + }, + { + "id": "jeremy_sight", + "kind": "step", + "variant": "trainer_wait", + "direction": "east", + "why": "onto (20, 13), four above Jeremy (20, 17): when he looks north he walks up to (20, 14)" + }, + { + "id": "hiker_jeremy", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route9", "where": {"facing": "south", "kind": "tiles", "tiles": [[20, 13]]}}, + "why": "Machop and Onix, Lv20" + } + ] + }, + { + "id": "route9_east", + "title": "Route 9 east: Conner, round Brice, above Caitlin", + "events": [ + { + "id": "below_conner_line", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route9", "where": {"facing": "north", "kind": "tiles", "tiles": [[52, 8]]}}, + "why": "east along row 13 and up column 52 to (52, 8), below the east end of Conner's line" + }, + { + "id": "conner_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "onto (52, 7), the end of Conner's line: he walks up to (51, 7)" + }, + { + "id": "bug_catcher_conner", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "Route9", "where": {"facing": "west", "kind": "tiles", "tiles": [[52, 7]]}}, + "why": "Caterpie, Weedle and Venonat, Lv20" + }, + { + "id": "round_brice", + "kind": "legs", + "variant": "run", + "legs": [ + {"direction": "north", "tiles": 2}, {"direction": "west", "tiles": 1}, {"direction": "north", "tiles": 2}, {"direction": "west", "tiles": 3}, {"direction": "north", "tiles": 2}, {"direction": "east", "tiles": 16}, + {"direction": "south", "tiles": 1} + ], + "why": "up to the ledge (52, 4), up column 51 to Brice's row, west to the rock (48, 3) (or (48, 1)), up to (48, 1), east along row 1 to the rock (64, 1), down to (64, 2)" + }, + { + "id": "above_east_ledge", + "kind": "step", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "Route9", "where": {"facing": "south", "kind": "tiles", "tiles": [[64, 3]]}}, + "why": "onto (64, 3), above the ledge (64, 4)" + }, + { + "id": "east_ledge_hop", + "kind": "step", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "Route9", "where": {"facing": "south", "kind": "tiles", "tiles": [[64, 5]]}}, + "why": "down the ledge (64, 4)" + }, + { + "id": "above_caitlin_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "south", "tiles": 3}, {"direction": "east", "tiles": 6}], + "why": "down column 64 to row 8, east along it to (70, 8): above Caitlin's line (61..64, 9)" + } + ] + }, + { + "id": "route10_center", + "title": "Route 10 to its Pokemon Center", + "events": [ + { + "id": "route10_ledge_end", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route10", "where": {"facing": "east", "kind": "tiles", "tiles": [[17, 11]]}}, + "why": "east onto Route 10, down to row 11 west of the grass and east along it to the wall (17, 11), above the fence post (17, 12)" + }, + { + "id": "below_center_door", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "west", "tiles": 1}, {"direction": "south", "tiles": 12}, {"direction": "west", "tiles": 1}, {"direction": "north", "tiles": 2}, {"direction": "west", "tiles": 5}, {"direction": "east", "tiles": 3}], + "why": "into the ledge's gap (16, 11), down column 16 to row 23, a tile west, up to the Center's wall (15, 21), west along row 21 to the wall (10, 21), east to (13, 21), below the door" + }, + { + "id": "route10_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route10_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "up onto the Center's door (13, 20), to the nurse" + }, + { + "id": "heal_route10", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.28.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.28.plan.json new file mode 100644 index 0000000000..1ff6a8a7b6 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.28.plan.json @@ -0,0 +1,355 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.28", + "title": "Through Rock Tunnel: Ashton, Winston, Martha, Allen, Lenny, Sofia, Dudley, Cooper and Dana", + "notes": [ + "Rows from 00.28 on: through Rock Tunnel, without Flash, and down Route 10 to Lavender Town's Pokemon Center.", + "From Route 10's Pokemon Center through Rock Tunnel (pokefirered RockTunnel_1F, RockTunnel_B1F map.json and scripts.inc) to Route 10 south of it. The save is on Route 10 by the tunnel's south mouth: the save card's places don't include Rock Tunnel.", + "- Every floor tile of the tunnel has wild encounters, which are run from (the table has no step that uses a Repel), so the legs end on walls, ladders or someone standing still, which the walker holds again after a battle. An open leg (two tiles or more, not to a wall) is followed only by legs a tile of error in it can't send astray.", + "- Nine trainers stand where the way can't go round them. The others (1F Lucas, Oliver, Ariana, Leah; B1F Eric, Steve) are walked round.", + "- Unverified on the console: Rock Tunnel is dark without Flash, and the overworld, fade, dialog and battle detectors haven't been measured under its darkness (~/FRLGLab/notes/2026-11-09-0028-dark-measure.md). Every step from rock_tunnel_1f to route10_south depends on them." + ], + "start": {"certainty": "tile", "map": "Route10_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "sections": [ + { + "id": "route10_to_mouth", + "title": "Route 10: the tree and below the north mouth", + "notes": [ + "- The tunnel's north mouth (8, 19) is reached from the Center through the tree (10, 20), cut from below; Heidi (9, 27) sees rows 28..29 below the trees and is left. Two single steps west then put the player below the mouth on (8, 20)." + ], + "events": [ + { + "id": "below_route10_tree", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route10", "where": {"facing": "north", "kind": "tiles", "tiles": [[10, 21]]}}, + "why": "out of the Center (13, 20), west along row 21 to the wall (9, 21), below the tree (10, 20)" + }, + { + "id": "cut_route10_tree", + "kind": "game_action", + "variant": "cut_tree", + "direction": "north", + "declared_after": {"certainty": "tile", "map": "Route10", "where": {"facing": "north", "kind": "tiles", "tiles": [[10, 20]]}}, + "why": "the tree; a step onto its tile" + }, + { + "id": "west_of_tree", + "kind": "step", + "direction": "west", + "declared_after": {"certainty": "tile", "map": "Route10", "where": {"facing": "west", "kind": "tiles", "tiles": [[9, 20]]}}, + "why": "a step west along row 20" + }, + { + "id": "below_rock_tunnel", + "kind": "step", + "direction": "west", + "declared_after": {"certainty": "tile", "map": "Route10", "where": {"facing": "west", "kind": "tiles", "tiles": [[8, 20]]}}, + "why": "a step west, below the mouth (8, 19)" + } + ] + }, + { + "id": "1f_north", + "title": "1F north: Ashton, then east to the ladder (45, 2)", + "notes": [ + "- 1F Ashton (27, 10) looks west over (24..26, 10), the only way south from the mouth's room: stepped into from (24, 9)." + ], + "events": [ + { + "id": "rock_tunnel_1f", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "RockTunnel_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[17, 2]]}}, + "why": "north into Rock Tunnel" + }, + { + "id": "above_ashton_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "south", "tiles": 6}, {"direction": "east", "tiles": 8}, {"direction": "south", "tiles": 1}, {"direction": "west", "tiles": 1}], + "why": "down column 17 to the wall (17, 8), east along row 8 to (25, 8), onto row 9 and west to the wall (24, 9)" + }, + { + "id": "ashton_line", + "kind": "step", + "variant": "trainer_line", + "direction": "south", + "why": "onto (24, 10), in Ashton's line: he walks up to (25, 10)" + }, + { + "id": "pokemaniac_ashton", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "RockTunnel_1F", "where": {"facing": "east", "kind": "tiles", "tiles": [[24, 10]]}}, + "why": "Slowpoke, Slowpoke and Slowpoke" + }, + { + "id": "b1f_south_ladder", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "RockTunnel_B1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[38, 28]]}}, + "why": "east along row 12 and up column 45 to the ladder (45, 2), down to B1F" + } + ] + }, + { + "id": "b1f_east", + "title": "B1F south and east: Winston, Martha and Allen, to the ladder (33, 3)", + "notes": [ + "- B1F Winston (29, 35) looks south over the south passage (29, 36..38): stepped into from (32, 37), after the leg down column 33 to the wall and a tile back.", + "- B1F Martha (15, 33) looks east over (16..17, 33), the corridor north: stepped into from (17, 34), the wall east of row 34.", + "- B1F Allen (36, 11) looks south over (36, 12..14), the way west to the ladder (33, 3): stepped into from (38, 12), below the wall." + ], + "events": [ + { + "id": "winston_line_end", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "west", "tiles": 5}, {"direction": "south", "tiles": 10}, {"direction": "north", "tiles": 1}, {"direction": "west", "tiles": 1}], + "why": "west to the wall (33, 28), down column 33 to the wall (33, 38), up to (33, 37) and west to (32, 37)" + }, + { + "id": "winston_line", + "kind": "step", + "variant": "trainer_line", + "direction": "west", + "why": "along row 37 onto (29, 37), the end of Winston's line: he walks down to (29, 36)" + }, + { + "id": "pokemaniac_winston", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "RockTunnel_B1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[29, 37]]}}, + "why": "Slowpoke and Slowpoke" + }, + { + "id": "below_martha_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "west", "tiles": 16}, {"direction": "north", "tiles": 2}, {"direction": "west", "tiles": 2}, {"direction": "east", "tiles": 5}], + "why": "west along row 37 to (13, 37), up to the wall (13, 35), west to the corner (11, 35), east to (16, 35)", + "notes": [ + "The corner (11, 35), wall north and rock west, finds the player from any of (11..14, 35) after a battle; east to the wall (19, 35) did not (console B, 2026-11-09 13:35: found 5 tiles off)." + ] + }, + { + "id": "martha_line", + "kind": "step", + "variant": "trainer_line", + "direction": "north", + "why": "up column 16 onto (16, 33), beside Martha; from (17, 35), a tile short, up column 17 onto (17, 33), the end of her line" + }, + { + "id": "picnicker_martha", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "RockTunnel_B1F", "where": {"facing": "west", "kind": "tiles", "tiles": [[16, 33]]}}, + "why": "Oddish and Bulbasaur" + }, + { + "id": "below_allen_line", + "kind": "legs", + "variant": "run", + "legs": [ + {"direction": "east", "tiles": 1}, {"direction": "north", "tiles": 5}, {"direction": "east", "tiles": 12}, {"direction": "north", "tiles": 9}, {"direction": "east", "tiles": 11}, {"direction": "north", "tiles": 2}, + {"direction": "west", "tiles": 2}, {"direction": "north", "tiles": 5} + ], + "why": "east to the wall (17, 33), up column 17 to the wall (17, 28), east to the wall (29, 28), up column 29 past Steve's line to the wall (29, 19), east to (40, 19), up to row 17, west to the wall (38, 17) and up to the wall (38, 12)" + }, + { + "id": "allen_line", + "kind": "step", + "variant": "trainer_line", + "direction": "west", + "why": "along row 12 onto (36, 12), below Allen" + }, + { + "id": "hiker_allen", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "RockTunnel_B1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[36, 12]]}}, + "why": "Geodude, Geodude and Graveler" + }, + { + "id": "west_of_b1f_ladder", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "west", "tiles": 4}, {"direction": "north", "tiles": 10}], + "why": "west along row 12 to the wall (32, 12), up column 32 to the wall (32, 2)" + }, + { + "id": "1f_west_ladder", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "RockTunnel_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[4, 2]]}}, + "why": "onto the ladder (33, 3), up to 1F" + } + ] + }, + { + "id": "1f_west", + "title": "1F west: Lenny, round Lucas's line to the ladder (20, 13)", + "notes": [ + "- 1F Lenny (7, 5) looks south over (7, 6..9), the way east from the ladder (4, 2): stepped into from (6, 9), the end of column 6. The way on to the ladder (20, 13) keeps west of Lucas's line (16..19, 17): down column 11, up column 14 to row 16." + ], + "events": [ + { + "id": "above_lenny_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 2}, {"direction": "south", "tiles": 7}], + "why": "east to the wall (6, 2), down column 6 to the wall (6, 9)" + }, + { + "id": "lenny_line", + "kind": "step", + "variant": "trainer_line", + "direction": "east", + "why": "onto (7, 9), the end of Lenny's line: he walks down to (7, 8)" + }, + { + "id": "hiker_lenny", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "RockTunnel_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 9]]}}, + "why": "Machop and Onix" + }, + { + "id": "west_of_lucas_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "east", "tiles": 4}, {"direction": "south", "tiles": 15}, {"direction": "east", "tiles": 7}, {"direction": "west", "tiles": 4}, {"direction": "north", "tiles": 8}, {"direction": "east", "tiles": 6}], + "why": "east to the wall (11, 9), down column 11 to the wall (11, 24), east to the wall (18, 24), west to (14, 24), up column 14 to the wall (14, 16), east to the wall (20, 16)" + }, + { + "id": "b1f_middle_ladder", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "RockTunnel_B1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[27, 12]]}}, + "why": "up column 20 onto the ladder (20, 13), down to B1F" + } + ] + }, + { + "id": "b1f_west", + "title": "B1F west: Sofia, Dudley and past Cooper to the ladder (2, 3)", + "notes": [ + "- B1F Sofia (11, 15) looks south over (11, 16..19), the way west: stepped into along row 16 from (12..14, 16).", + "- B1F Dudley (5, 11) looks south over (5, 12..15): stepped into from (8, 15) along row 15.", + "- B1F Cooper (2, 6) looks around (sight 2) by the ladder (2, 3): the leg up column 1 to the wall (1, 2) passes him on (1, 6); if he looks west, story_interrupts() fights him and the leg is held on to its wall, as Mt. Moon's Grunt in 00.13." + ], + "events": [ + { + "id": "east_of_sofia_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "south", "tiles": 3}, {"direction": "west", "tiles": 12}, {"direction": "south", "tiles": 1}, {"direction": "west", "tiles": 2}], + "why": "down to the wall (27, 15), west along row 15 to the wall (15, 15), down to row 16 and west to (13, 16)" + }, + { + "id": "sofia_line", + "kind": "step", + "variant": "trainer_line", + "direction": "west", + "why": "along row 16 onto (11, 16), below Sofia" + }, + { + "id": "picnicker_sofia", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "RockTunnel_B1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[11, 16]]}}, + "why": "Pidgey, Pidgey and Pidgeotto" + }, + { + "id": "east_of_dudley_line", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "west", "tiles": 3}, {"direction": "north", "tiles": 1}], + "why": "west to the wall (8, 16), up to (8, 15)" + }, + { + "id": "dudley_line", + "kind": "step", + "variant": "trainer_line", + "direction": "west", + "why": "along row 15 onto (5, 15), the end of Dudley's line: he walks down to (5, 14)" + }, + { + "id": "hiker_dudley", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "RockTunnel_B1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[5, 15]]}}, + "why": "Geodude, Geodude and Graveler" + }, + { + "id": "past_cooper", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "west", "tiles": 4}, {"direction": "north", "tiles": 13}, {"direction": "south", "tiles": 1}], + "why": "west to the wall (1, 15), up column 1 past Cooper (2, 6) to the wall (1, 2), back to (1, 3)" + }, + { + "id": "1f_east_ladder", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "RockTunnel_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[45, 21]]}}, + "why": "east onto the ladder (2, 3), up to 1F" + } + ] + }, + { + "id": "1f_south", + "title": "1F south: Dana, to the south mouth", + "notes": [ + "- 1F Dana (26, 28) looks south over (26, 29..31), the only way west to the south mouth: stepped into along row 31 from (27..29, 31)." + ], + "events": [ + { + "id": "above_dana_line", + "kind": "legs", + "variant": "run", + "legs": [ + {"direction": "west", "tiles": 6}, {"direction": "south", "tiles": 5}, {"direction": "east", "tiles": 6}, {"direction": "west", "tiles": 1}, {"direction": "south", "tiles": 4}, {"direction": "west", "tiles": 5}, + {"direction": "south", "tiles": 5}, {"direction": "west", "tiles": 2}, {"direction": "north", "tiles": 3}, {"direction": "west", "tiles": 6}, {"direction": "north", "tiles": 3}, {"direction": "west", "tiles": 3}, + {"direction": "south", "tiles": 2} + ], + "why": "west to the wall (39, 21), down column 39 to Ariana (39, 27), east to the wall (45, 26), down column 44 to the wall (44, 30), west to the wall (39, 30), down to the wall (39, 35), west to (37, 35), up to the wall (37, 32), west to the wall (31, 32), up to (31, 29), west to (28, 29) and down to the wall (28, 31)" + }, + { + "id": "dana_line", + "kind": "step", + "variant": "trainer_line", + "direction": "west", + "why": "along row 31 onto (26, 31), the end of Dana's line: she walks down to (26, 30)" + }, + { + "id": "picnicker_dana", + "kind": "battle", + "declared_after": {"certainty": "tile", "map": "RockTunnel_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[26, 31]]}}, + "why": "Meowth, Oddish and Pidgey" + }, + { + "id": "above_south_mouth", + "kind": "legs", + "variant": "run", + "legs": [ + {"direction": "west", "tiles": 6}, {"direction": "south", "tiles": 1}, {"direction": "west", "tiles": 8}, {"direction": "south", "tiles": 5}, {"direction": "east", "tiles": 2}, {"direction": "north", "tiles": 1}, + {"direction": "east", "tiles": 5}, {"direction": "west", "tiles": 1} + ], + "why": "west along row 31 to (20, 31), onto row 32 and west to the wall (12, 32), down column 12 to the wall (12, 37), east to the wall (14, 37), up to (14, 36), east to the wall (19, 36), back to (18, 36)" + } + ] + }, + { + "id": "route10_south", + "title": "Out onto Route 10 south of the tunnel", + "events": [ + { + "id": "route10_south", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "Route10", "where": {"facing": "south", "kind": "tiles", "tiles": [[8, 58]]}}, + "why": "onto the mouth (18, 37), out onto Route 10" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.29.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.29.plan.json new file mode 100644 index 0000000000..afebebf900 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.29.plan.json @@ -0,0 +1,70 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.29", + "title": "Route 10 south to Lavender Town's Pokemon Center", + "notes": [ + "Route 10 south of Rock Tunnel (Route10 map.json) to Lavender Town's Pokemon Center. It has no grass. Carol (7, 60) and Clark (4, 62) look around, Trent (4, 68) looks west and Herman wanders row 70 (13..17), looking west and east (sight 2, to x 19): the way keeps to the east side, columns 17..21, and crosses row 70 on column 21. The ledges of row 62 leave a gap at (18, 62); row 66's leaves (21, 66). Row 74's ledge (11, 74) is hopped, and the hold south goes on over the connection into Lavender Town to the wall (11, 9). West of the Center's building, the wall (8, 9) sets the door's column." + ], + "start": {"certainty": "tile", "map": "Route10", "where": {"facing": "south", "kind": "tiles", "tiles": [[8, 58]]}}, + "sections": [ + { + "id": "route10_south", + "title": "Route 10's east side and its last ledge", + "events": [ + { + "id": "route10_east_side", + "kind": "legs", + "variant": "run", + "legs": [ + {"direction": "east", "tiles": 10}, {"direction": "north", "tiles": 2}, {"direction": "west", "tiles": 1}, {"direction": "east", "tiles": 1}, {"direction": "south", "tiles": 8}, {"direction": "east", "tiles": 3}, + {"direction": "south", "tiles": 9}, {"direction": "west", "tiles": 10} + ], + "why": "east along row 58 to (18, 58), up to the wall (18, 56), west to the wall (17, 56), back to (18, 56), down through the ledges' gap to (18, 64), east to the wall (21, 64), down column 21 to the wall (21, 73), west along row 73 to the wall (11, 73)" + }, + { + "id": "route10_ledge_hop", + "kind": "step", + "direction": "south", + "declared_after": {"certainty": "tile", "map": "Route10", "where": {"facing": "south", "kind": "tiles", "tiles": [[11, 75]]}}, + "why": "down the ledge (11, 74)" + } + ] + }, + { + "id": "lavender", + "title": "Lavender Town to its Pokemon Center", + "events": [ + { + "id": "lavender_north_wall", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "LavenderTown", "where": {"facing": "south", "kind": "tiles", "tiles": [[11, 8]]}}, + "why": "down column 11 over the connection into Lavender Town, to the wall (11, 9)" + }, + { + "id": "below_center_door", + "kind": "legs", + "variant": "run", + "legs": [{"direction": "west", "tiles": 6}, {"direction": "south", "tiles": 1}, {"direction": "east", "tiles": 2}, {"direction": "west", "tiles": 1}], + "why": "west along row 8 to (5, 8), onto row 9, east to the wall (7, 9), west to (6, 9)" + }, + { + "id": "lavender_nurse", + "kind": "route", + "variant": "run", + "target": {"certainty": "tile", "map": "LavenderTown_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, + "why": "up column 6 onto the Center's door (6, 5), to the nurse" + }, + { + "id": "heal_lavender", + "kind": "heal", + "why": "full HP" + } + ] + } + ] + }, + "execution": {"menu": "full", "supplies": {"lead": "after_brock"}} +} diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 976060be1f..697a9e8f60 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1969,6 +1969,8 @@ file(GLOB LIBRARY_SOURCES Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_StoryReplay_Executor.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_StoryReplay_Tests.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/LegacyJsonFormat.h + Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp + Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp From 2c370c9dd5fbd9d579dc241731f53667acf5295d Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 18:29:46 -0800 Subject: [PATCH 2/5] FRLG AutoStory: load the story table from its authored plans only story_checkpoints() is now load_authored_story(): every StoryData plan, in order of id. The C++ row definitions are gone (the table's rows and the VermilionGym, Celadon and Lavender row files); the step helpers and map names stay for tests. The migration's C++ comparison and export tests go with them; the golden sim trace stays, with tests that a plan with unknown fields, variants or sections is rejected and that Rock Tunnel's events are logged with their sections. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../AutoStory/Data/AuthoredStory_Tests.cpp | 165 ++-- .../AutoStory/PokemonFRLG_AutoStoryTable.cpp | 781 +----------------- .../AutoStory/PokemonFRLG_AutoStoryTable.h | 16 +- .../PokemonFRLG_AutoStoryTableCeladon.cpp | 165 ---- .../PokemonFRLG_AutoStoryTableLavender.cpp | 134 --- .../PokemonFRLG_AutoStoryTableSteps.h | 18 - ...PokemonFRLG_AutoStoryTableVermilionGym.cpp | 158 ---- .../Programs/AutoStory/StoryData/README.md | 13 +- SerialPrograms/cmake/SourceFiles.cmake | 3 - 9 files changed, 128 insertions(+), 1325 deletions(-) delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableLavender.cpp delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableVermilionGym.cpp diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp index 8392d8d7da..8bc8b352a9 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp @@ -1,9 +1,10 @@ /* FRLG Authored Story Tests * From: https://github.com/PokemonAutomation/ * - * The story table as loaded from the StoryData plans against the C++ - * rows it was exported from: the same events, and the same trace of the - * whole story on the story maps (StoryMapsSim). + * The story table as loaded from the StoryData plans: the trace of the + * whole story on the story maps (StoryMapsSim) against the golden one + * taken from the C++ rows they were exported from, what a plan may not + * say, and the sections in the log. * */ @@ -11,8 +12,6 @@ #include #include #include "Common/Cpp/Exceptions.h" -#include "Common/Cpp/Filesystem/Filesystem.h" -#include "Common/Cpp/Filesystem/FileIO.h" #include "Common/Cpp/Json/JsonArray.h" #include "Common/Cpp/Json/JsonObject.h" #include "Common/Cpp/TestRunners/UnitTest.h" @@ -21,7 +20,6 @@ #include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h" #include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_StoryReplay.h" #include "JsonParser.h" -#include "JsonWriter.h" #include "AuthoredStory_Tests.h" namespace PokemonAutomation{ @@ -101,54 +99,116 @@ std::vector story_sim_trace(const std::vector& tab namespace{ -// The row as the C++ table built it: no notes or sections. -JsonValue plain_export(StoryCheckpoint row){ - row.notes.clear(); - row.sections.clear(); - for (StoryStep& step : row.steps){ - step.notes.clear(); - step.section.clear(); +// 00.28's plan, changed by `mutate`, read as an authored plan. +StoryCheckpoint read_changed_rock_tunnel(void (*mutate)(JsonObject& root)){ + JsonValue plan; + for (const auto& [name, text] : autostory_checkpoint_json()){ + if (name == "frlg-00.28.plan"){ + plan = parse_json(text); + } } - return story_checkpoint_export(row); + mutate(plan.to_object_throw()); + return read_authored_checkpoint(plan, "frlg-00.28.plan (changed)"); } -std::string compare_rows(const StoryCheckpoint& authored, const StoryCheckpoint& table){ - const JsonValue a = plain_export(authored); - const JsonValue b = plain_export(table); - if (!(a == b)){ - return std::format("{}: authored {}\nC++ {}", table.id, a.dump(-1), b.dump(-1)); - } - if (authored.live_start != table.live_start || authored.handoff != table.handoff){ - return table.id + ": live start or handoff differ."; - } - return ""; +JsonObject& first_event(JsonObject& root){ + JsonArray& sections = root.get_object_throw("checkpoint").get_array_throw("sections"); + return sections[0].to_object_throw().get_array_throw("events")[0].to_object_throw(); +} + +void unknown_event_field(JsonObject& root){ + first_event(root)["speed"] = "fast"; +} +void unknown_variant(JsonObject& root){ + first_event(root)["variant"] = "bike"; +} +void unknown_game_action(JsonObject& root){ + JsonObject& event = first_event(root); + event["kind"] = "game_action"; + event["variant"] = "mart Nowhere"; +} +void events_beside_sections(JsonObject& root){ + root.get_object_throw("checkpoint")["events"] = JsonArray(); } +void unknown_execution_field(JsonObject& root){ + root.get_object_throw("execution")["repel"] = true; +} +void unknown_lead(JsonObject& root){ + JsonObject supplies; + supplies["lead"] = "after_misty"; + root.get_object_throw("execution")["supplies"] = std::move(supplies); +} +void empty_section(JsonObject& root){ + JsonObject section; + section["id"] = "nothing"; + section["title"] = "no events"; + section["events"] = JsonArray(); + root.get_object_throw("checkpoint").get_array_throw("sections").push_back(std::move(section)); +} +void repeated_section(JsonObject& root){ + JsonArray& sections = root.get_object_throw("checkpoint").get_array_throw("sections"); + sections[1].to_object_throw()["id"] = sections[0].to_object_throw().get_string_throw("id"); +} + +struct RejectCase{ + std::string_view name; + void (*mutate)(JsonObject& root); +}; +const RejectCase REJECT_CASES[] = { + {"an unknown event field", unknown_event_field}, + {"a variant FRLG doesn't do", unknown_variant}, + {"a GAME step FRLG doesn't do", unknown_game_action}, + {"events beside sections", events_beside_sections}, + {"an unknown execution field", unknown_execution_field}, + {"an unknown lead", unknown_lead}, + {"a section without events", empty_section}, + {"two sections with one id", repeated_section}, +}; -UnitTestResult test_rows_match(){ - const std::vector authored = load_authored_story(); - const std::vector& table = story_checkpoints(); - if (authored.size() != table.size()){ - return std::format("Expected {} authored rows, read {}.", table.size(), authored.size()); +void unchanged(JsonObject&){} + +UnitTestResult test_rejects(){ + // Unchanged, it reads: each rejection is the change's. + if (read_changed_rock_tunnel(unchanged).id != "00.28"){ + return "Expected the unchanged plan to read as 00.28."; } - for (size_t c = 0; c < table.size(); c++){ - const std::string error = compare_rows(authored[c], table[c]); - if (!error.empty()){ - return error; - } + for (const RejectCase& test : REJECT_CASES){ + try{ + read_changed_rock_tunnel(test.mutate); + return std::format("Expected {} to be rejected.", test.name); + }catch (const ParseException&){} } return true; } -UnitTestResult test_traces_match(){ - const std::vector authored = story_sim_trace(load_authored_story()); - const std::vector table = story_sim_trace(story_checkpoints()); - for (size_t c = 0; c < std::min(authored.size(), table.size()); c++){ - if (authored[c] != table[c]){ - return std::format("Trace line {}: authored \"{}\", C++ \"{}\".", c, authored[c], table[c]); +// 00.28 in its sections, numbered across them, each event's line naming +// its section: Winston is event 12 (after from_save), on B1F's east side. +UnitTestResult test_rock_tunnel_sections(){ + const StoryCheckpoint* rock_tunnel = nullptr; + for (const StoryCheckpoint& row : story_checkpoints()){ + if (row.id == "00.28"){ + rock_tunnel = &row; } } - if (authored.size() != table.size()){ - return std::format("Expected {} trace lines, read {}.", table.size(), authored.size()); + if (rock_tunnel == nullptr){ + return "Expected a checkpoint 00.28."; + } + std::vector ids; + for (const Pokemon::StorySection& section : rock_tunnel->sections){ + ids.push_back(section.id); + } + const std::vector expected{ + "route10_to_mouth", "1f_north", "b1f_east", "1f_west", "b1f_west", "1f_south", "route10_south", + }; + if (ids != expected){ + return "Expected 00.28's sections by area, read " + std::to_string(ids.size()) + " others."; + } + StoryMapsSim game; + Pokemon::Checkpoint checkpoint = Pokemon::build_story_checkpoint(*rock_tunnel, game); + const Pokemon::Event& winston = checkpoint.events[11]; + const std::string line = Pokemon::event_log_line(checkpoint.id, 12, winston.name, "start", winston.section); + if (line != "[Event] 00.28/12 [b1f_east] winston_line: start"){ + return "Expected Winston's line in b1f_east, read: " + line; } return true; } @@ -170,22 +230,6 @@ UnitTestResult test_trace_golden(){ return true; } -// Migration only: writes each C++ row as its authored plan, and the C++ -// table's trace, to frlg-story-export/ under the working directory. -UnitTestResult test_export(){ - const std::string folder = "frlg-story-export/"; - Filesystem::create_directories(Filesystem::Path(folder)); - for (const StoryCheckpoint& row : story_checkpoints()){ - string_to_file(folder + "frlg-" + row.id + ".plan.json", authored_checkpoint_json(row).dump(2)); - } - JsonArray lines; - for (const std::string& line : story_sim_trace(story_checkpoints())){ - lines.push_back(line); - } - string_to_file(folder + "frlg-story-sim-trace.json", JsonValue(std::move(lines)).dump(1)); - return true; -} - struct AuthoredCase{ std::string name; UnitTestResult (*body)(); @@ -210,10 +254,9 @@ class AuthoredTest : public UnitTest{ void add_tests_AuthoredStory(UnitTestDatabase& database){ - database.add(AuthoredCase{"every row as authored is the C++ row", test_rows_match}); - database.add(AuthoredCase{"the story sim's trace is the C++ table's", test_traces_match}); - database.add(AuthoredCase{"export the C++ rows", test_export}); database.add(AuthoredCase{"the story sim's trace is the golden one", test_trace_golden}); + database.add(AuthoredCase{"malformed plans are rejected", test_rejects}); + database.add(AuthoredCase{"Rock Tunnel's sections", test_rock_tunnel_sections}); } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp index 94437d8c5c..5ae93c0b89 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp @@ -4,795 +4,16 @@ * */ -#include "PokemonFRLG_AutoStoryCapture.h" #include "Data/JsonParser.h" -#include "PokemonFRLG_AutoStoryChoice.h" -#include "PokemonFRLG_AutoStoryGrind.h" -#include "PokemonFRLG_AutoStoryMart.h" -#include "PokemonFRLG_AutoStoryPcStorage.h" #include "PokemonFRLG_AutoStoryTable.h" -#include "PokemonFRLG_AutoStoryTableSteps.h" -#include "PokemonFRLG_AutoStoryTeach.h" namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -namespace{ - -using namespace StoryTable; - - -StoryCheckpoint new_game(){ - StoryCheckpoint checkpoint = 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, the player's name (default if none is set), rival GREEN"), - options_step(), - }); - checkpoint.handoff = StoryHandoff::SID_HELPER; - return checkpoint; -} - -// SID Helper confirms the name, finishes the intro and reads the Trainer -// Card from the START menu, without moving the player or saving. -StoryCheckpoint after_sid_helper(){ - StoryCheckpoint checkpoint = row("00.01b", "after SID Helper: close the Trainer Card, save in the bedroom", at(BEDROOM, 6, 6, NORTH), {}, StartMenu::BEFORE_POKEMON, { - game_step(CLOSE_TRAINER_CARD, at(BEDROOM, 6, 6, NORTH), "the Trainer Card SID Helper left open, or HOME"), - options_step(), - }); - checkpoint.live_start = true; - return checkpoint; -} - -StoryCheckpoint leave_house(){ - return row("00.02", "bedroom to Pallet Town", at(BEDROOM, 6, 6, NORTH), {}, StartMenu::BEFORE_POKEMON, { - route("front_door", at(PALLET, 6, 8), "down the stairs and out of the house"), - }); -} - -StoryCheckpoint oaks_trigger(){ - return row("00.03", "Oak stops the player and walks them to the lab", at(PALLET, 6, 8, SOUTH), {}, StartMenu::BEFORE_POKEMON, { - route("below_oaks_trigger", at(PALLET, 12, 2, NORTH), "below the north exit, short of Oak's trigger at (12, 1)"), - step_into("oaks_trigger", NORTH, "the trigger: \"Don't go out!\""), - // PlayerEnter walks the player up 8 tiles from the lab's door. - scene("to_the_lab", at(LAB, 6, 4, NORTH), "Oak walks the player into the lab; the scene ends at the table"), - }); -} - -// Starter RNG starts from a save south of the table, facing the ball. -StoryCheckpoint to_starter_ball(){ - StoryCheckpoint checkpoint = row("00.04", "to Squirtle's ball, south of the table", at(LAB, 6, 4, NORTH), {}, StartMenu::BEFORE_POKEMON, { - route("below_squirtle", at(LAB, 9, 5, NORTH), "under Squirtle's ball (9, 4)"), - }); - checkpoint.handoff = StoryHandoff::STARTER_RNG; - return checkpoint; -} - -StoryCheckpoint starter(){ - return row("00.04b", "Squirtle, then the rival's battle", at(LAB, 9, 5, NORTH), {}, StartMenu::BEFORE_POKEDEX, { - talk("squirtle_ball", "\"So, you want the WATER POKEMON SQUIRTLE?\""), - answer("choose_squirtle", true, "take Squirtle"), - answer("no_nickname", false, "\"give a nickname?\" NO"), - scene("rival_takes_bulbasaur", at(LAB, 9, 5, NORTH), "the rival picks his ball; the player still faces the table (console B, 2026-11-01)"), - route("above_rival_trigger", at(LAB, 7, 7, SOUTH), "short of the rival's trigger row 8"), - step_into("rival_trigger", SOUTH, "\"Wait, let's check out our POKEMON!\""), - // The rival leaves; PlayerWatchRivalExitAfterBattleRight ends facing down. - battle("rival_battle", at(LAB, 7, 8, SOUTH), true, "the tutorial battle; losing goes on too"), - }); -} - -// TODO: Load an ordered series of authored checkpoints together and construct -// a partial story table, instead of loading one file in each row factory. -StoryCheckpoint parcel(){ - return load_authored_checkpoint("frlg-parcel-00.05.plan"); -} - -StoryCheckpoint pokedex(){ - return row("00.06", "Oak's Parcel to the Pokedex", at(VIRIDIAN_MART, 4, 3, WEST), {}, StartMenu::FULL, { - route("below_oak", at(LAB, 6, 4, NORTH), "back down Route 1 to Oak"), - talk("give_parcel", "Oak takes the parcel"), - scene("pokedex_scene", at(LAB, 6, 4, SOUTH), "the rival comes in, Pokedexes from the desk; the player ends facing south (console B, 2026-11-01)"), - }); -} - -StoryCheckpoint shopping(){ - return row("00.07", "Viridian shopping, then into Viridian Forest", at(LAB, 6, 4, SOUTH), {}, StartMenu::FULL, { - route("mart_clerk", at(VIRIDIAN_MART, 4, 3, WEST), "Route 1 again"), - // 2000 of the 3000 a new game starts with, plus the rival's prize. - shop("potions_and_antidotes", {{"potion", 5}, {"antidote", 3}, {"parlyz_heal", 1}}, "5 Potions, 3 Antidotes, 1 Parlyz Heal"), - route("viridian_nurse", at(VIRIDIAN_CENTER, 7, 4, NORTH), "heal before the forest"), - heal("heal_viridian"), - route("below_old_man_trigger", at(VIRIDIAN, 20, 9, NORTH), "short of the tutorial trigger (20, 8)"), - step_into("old_man_trigger", NORTH, "the old man's catching tutorial can't be walked around"), - // TutorialTriggerLeft turns the player right, to the old man. - scene("old_man_tutorial", at(VIRIDIAN, 20, 8, EAST), "about 60 lines and his demo battle; TEACHY TV"), - // After his demo the old man stands east of the player, beside (20, 8), where - // the map doesn't have him: "east 2" from there didn't move, and the walk north - // ended under Route 2's ledge in column 8 (console B, 2026-11-01). Go north first. - route("past_old_man", at(VIRIDIAN, 20, 5, NORTH), "north along column 20, clear of the old man"), - // The gate's north warp lands on the forest's exit mat (29, 62), facing - // north. The save there keeps a forest failure from going back to Pallet. - route("forest_entrance", at(FOREST, 29, 62, NORTH), "Route 2 and the south gate, running from wild battles"), - }); -} - -StoryCheckpoint forest(){ - // The supplies bought in 00.07. - return row("00.08", "Viridian Forest to Pewter Center", at(FOREST, 29, 62, NORTH), {5, 3, 1}, StartMenu::FULL, { - // Wild battles are fought for EXP. Straight up the east side from the - // entrance, each leg ending on a wall (the Boy at (45, 58) stops the - // east one). Rick (47, 45) sees (42..46, 45), so the last leg meets - // his battle and is held on to its wall after it. The by-hand run's - // detour west to (13, 46) and back is dropped. - legs("forest_east", {{EAST, 1}, {NORTH, 4}, {EAST, 14}, {NORTH, 15}}, "up the east side to (44, 43)", WildBattles::FIGHT), - route("forest_north", at(FOREST, 39, 25), "north part", WildBattles::FIGHT), - // To the gate by legs that end on walls, which the walker holds again - // after a battle instead of estimating where it stopped: planned - // routes with open legs lost the player in the north of the forest - // (console B, 2026-11-01). The two 1-tile legs are single steps. - legs("forest_exit", { - {NORTH, 10}, {WEST, 12}, {SOUTH, 10}, {WEST, 6}, {NORTH, 20}, - {SOUTH, 1}, {WEST, 10}, {SOUTH, 27}, {WEST, 8}, {NORTH, 20}, {EAST, 1}, - }, "walls all the way to the gate's column", WildBattles::FIGHT), - route("forest_gate", at(FOREST_GATE, 7, 10, NORTH), "north onto the exit, arriving on the gate's mat", WildBattles::FIGHT), - // Out of the forest, wild battles are run from; trainers are still fought. - route("pewter_nurse", at(PEWTER_CENTER, 7, 4, NORTH), "Route 2 north to Pewter's Center"), - heal("heal_pewter"), - }); -} - -StoryCheckpoint brock(){ - // The by-hand save: 2 Potions and 1 Antidote left after the forest. - return row("00.09", "Pewter Gym: Camper Liam, then Brock", at(PEWTER_CENTER, 7, 4, NORTH), {2, 1}, StartMenu::FULL, { - // Camper Liam (3, 8) always faces east (MOVEMENT_TYPE_FACE_RIGHT, sight - // 4) and sees (4..7, 8): walking north up column 6 from the door, - // the step onto (6, 8) starts his battle. He walks up to the player, - // who stays put and keeps facing north (FRLG doesn't turn the player - // to a trainer). That step is taken on its own, so no leg is cut. - route("below_liam_line", at(GYM, 6, 9, NORTH), "in by the door, up column 6 to below Liam's row"), - step_into("liam_line", NORTH, "onto (6, 8), in Liam's line"), - battle("camper_liam", at(GYM, 6, 8, NORTH), false, "Geodude and Sandshrew; EXP before Brock"), - // On to Brock unless red HP or a status is left that no item cures. - ready("ready_for_brock", at(PEWTER_CENTER, 7, 4, NORTH), "items for a status or low HP; the nurse only if they can't"), - route("before_brock", at(GYM, 6, 6, NORTH), "below Brock (6, 5); Liam, beaten, stands at (5, 8)"), - talk("challenge_brock", "Brock's speech"), - battle("brock", at(GYM, 6, 6, NORTH), false, "Geodude and Onix, with Bubble; Boulder Badge and TM39"), - }); -} - -StoryCheckpoint running_shoes(){ - return row("00.10", "Pewter: the Running Shoes from Oak's aide", at(GYM, 6, 6, NORTH), after_brock(), StartMenu::FULL, { - // Brock's win sets VAR_MAP_SCENE_PEWTER_CITY to 1 and shows the aide at - // (46, 20); his triggers are (46, 21..23) (PewterCity map.json). The - // Gym guide's triggers at (42..43, 21..23) need the scene at 0. - route("before_aide_trigger", at(PEWTER, 45, 21, EAST), "out of the Gym, east along row 21, short of the top trigger"), - step_into("aide_trigger", EAST, "onto (46, 21), below the aide"), - // RunningShoesAideTriggerTop: the aide talks from (46, 20), no YES/NO; - // the player only turns up. He walks off and is removed; FLAG_SYS_B_DASH - // is set (console B, 2026-11-02: about 20 B presses to the overworld). - scene("shoes_from_aide", at(PEWTER, 46, 21, NORTH), "\"Oh, PLAYER!\" ... the Running Shoes; ends facing north"), - // Where the by-hand walk stopped to start 00.11 (console B, 2026-11-02). - route("below_pewter_center", at(PEWTER, 17, 26, SOUTH), "west along row 21 and down to below the Pokemon Center's door (17, 25)"), - }); -} - -// Route 3's trainers (Route3 map.json): the way east meets three of them. -// Bug Catcher Colton (12, 6) faces east, sight 3: the only way north past -// the row 7 ledges is the gap at x 14..15, below his line. Youngster Ben -// (17, 4) faces south, sight 2, so the way east keeps to row 3 past him. -// Bug Catcher Greg (25, 4) faces south, sight 2, over the way east along -// row 5. Bug Catcher James (32, 6) faces east, sight 4, over the way down -// at x 35..36. The others are kept out of: Lass Janice (19, 9) sees west -// along row 9 to x 14, Lass Sally (30, 3) west along row 3 to x 25, and -// Youngster Calvin (29, 10) west along row 10. Each line is stepped into on -// its own, so no leg is cut; the trainer walks up to the player, who stays -// put facing the way they stepped. -StoryCheckpoint route3(){ - return row("00.11", "Pewter Center, then Route 3's trainers", at(PEWTER, 17, 26, SOUTH), after_brock(), StartMenu::FULL, { - running(route("pewter_nurse", at(PEWTER_CENTER, 7, 4, NORTH), "in by the door below the player")), - heal("heal_pewter"), - // Up from row 9 west of Janice's line, which ends at x 14. - running(route("west_of_janice_line", at(ROUTE3, 13, 8), "out of Pewter by row 21, east into Route 3, short of Janice's line")), - running(legs("below_colton_line", {{EAST, 1}, {NORTH, 1}}, "to (14, 7), in the ledges' gap")), - step_into("colton_line", NORTH, "onto (14, 6), in Colton's line"), - battle("bug_catcher_colton", at(ROUTE3, 14, 6, NORTH), false, "his Bug Pokemon; Colton stops at (13, 6)"), - running(legs("past_ben", {{NORTH, 3}, {EAST, 4}, {SOUTH, 2}, {EAST, 6}}, "up to row 3 above Ben, down at x 18 east of his line, to (24, 5)")), - step_into("greg_line", EAST, "onto (25, 5), below Greg"), - battle("bug_catcher_greg", at(ROUTE3, 25, 5, EAST), false, "his Bug Pokemon; Greg is already next to the player"), - running(legs("above_james_line", {{EAST, 11}}, "along row 5 to the wall at (36, 5)")), - step_into("james_line", SOUTH, "onto (36, 6), at the end of James's line"), - battle("bug_catcher_james", at(ROUTE3, 36, 6, SOUTH), false, "his Bug Pokemon; James stops at (35, 6)"), - // Saved here, the trainers behind, so a failure on the way to Route 4 - // doesn't fight them again. - running(legs("save_spot", {{SOUTH, 2}}, "down to (36, 8), beside the wall east of it")), - }); -} - -// Route 3's north exit (x 68..75) joins Route 4 (offset 60) at x 8..15, up -// which column 12 leads to the Pokemon Center's door (12, 5). Lass Robin -// (40, 11) wanders up and down at x 40, looking north and south, sight 3. -// Rock above (y <= 8) and grass below (y >= 12) leave every way east -// across her column at y 9..11, so whether she sees the player depends on -// her facing. The walk watches for her after the leg along row 9, which -// crosses her sight on (40, 9) only; if she saw the player, her message -// goes to story_interrupts(), which fight her, and the route goes on from -// (40, 9) (run_route(); console B, 2026-11-02, she stopped the player -// there and spoke after the leg's short watch). -StoryCheckpoint magikarp_salesman(){ - StoryCheckpoint checkpoint = row("00.12", "Route 3 and 4 to the Magikarp salesman", at(ROUTE3, 36, 8, SOUTH), after_brock(), StartMenu::FULL, { - running(route("route4_nurse", at(ROUTE4_CENTER, 7, 4, NORTH), "east over Route 3, off the grass, north into Route 4 and its Pokemon Center")), - heal("heal_route4"), - // Gift RNG starts from a save facing the salesman (1, 3). - running(route("before_salesman", at(ROUTE4_CENTER, 2, 3, WEST), "beside the Magikarp salesman, facing him")), - }); - checkpoint.handoff = StoryHandoff::GIFT_RNG; - return checkpoint; -} - -// Gift RNG buys the Magikarp (500 of the money 00.12 brings) without -// moving the player and leaves its summary open, or HOME. This saves the -// shiny where 00.12 ended. A run started here with Gift RNG's handoff on -// loads the save beside the salesman and runs Gift RNG first. -StoryCheckpoint after_gift_rng(){ - StoryCheckpoint checkpoint = row("00.12b", "after Gift RNG: save with the Magikarp", at(ROUTE4_CENTER, 2, 3, WEST), after_brock(), StartMenu::FULL, { - game_step(LEAVE_GIFT_SUMMARY, at(ROUTE4_CENTER, 2, 3, WEST), "the Magikarp's summary Gift RNG left open, or HOME"), - }); - checkpoint.live_start = true; - return checkpoint; -} - -// The Magikarp goes to BOX 1 at the Center's PC (11, 1), so Mt. Moon is -// fought with the starter alone (console B, 2026-11-02: done by hand and -// saved at (11, 2), facing the PC, where this row ends). The wandering -// boy (4..6, 3..5) is walked around. The Magikarp comes from Gift RNG: -// with Gift RNG off the party is the starter alone, and the deposit is -// skipped. -StoryCheckpoint magikarp_to_box(){ - return row("00.12c", "Route 4 Center: the Magikarp into BOX 1", at(ROUTE4_CENTER, 2, 3, WEST), after_brock(), StartMenu::FULL, { - running(route("pc_front", at(ROUTE4_CENTER, 11, 2, NORTH), "below the PC (11, 1), facing it")), - pc_step("magikarp_to_box", "DEPOSIT the party's second slot into BOX 1, if it has one", PcTransfer{ - .action = PcAction::DEPOSIT, .party_slot = 1, .box = 0, .if_missing = PcMissing::SKIP, - }), - }); -} - -// Mt. Moon (pokefirered data/maps/MtMoon_1F, _B1F, _B2F). Every floor tile -// of the cave has wild encounters, which are run from, so its legs end on -// walls, ladders or someone standing still, which the walker holds again -// after a battle; the few open ones are short. The way through: up 1F's -// middle and west side, its north-west ladder, B1F's west ladder down to -// B2F's south-east, round B2F's south edge to its west, north past -// Miguel and the fossils, and the ladder up to B1F's east end, which -// leads out to Route 4 east of the mountain. -// - 1F's trainers are walked around: Youngster Josh (13, 17) sees east -// along row 17 to x 17, so the way west into the west room is down -// column 18 and up column 11; Lass Miriam walks round (32..33, 4..5), -// sight 2, so the way east keeps to row 9 and columns 38 and 40; Hiker -// Marcos wanders (5..9, 8..12) and sees one tile, so the way north is -// column 11 and row 3. -// - The Moon Stone ball (3, 2) on 1F (FLAG_HIDE_MT_MOON_1F_MOON_STONE) is -// a few tiles off the way to the ladder (5, 6). -// - The Grunt at (37, 21) on B2F looks around (sight 3) over the only way -// east round B2F. His room is crossed on legs to walls, at one tile of -// his sight each, (37, 22) and (40, 21); if he looks the player's way, -// he walks up beside the leg, story_interrupts() fights him, and the -// leg is held on to its wall. -// - The Grunt at (12, 20) faces south (sight 2) over (12, 21..22), across -// rows 21..22, the only way north to Miguel: stepped into from the -// west, he is next to the player and doesn't walk. -// - Super Nerd Miguel (13, 11) battles on the step onto (14, 11) -// (MtMoon_B2F_EventScript_MiguelTrigger, VAR_MAP_SCENE_MT_MOON_B2F 0), -// then "We'll each take one!" -// - The fossils: DOME (13, 7), HELIX (14, 7). After the HELIX, Miguel -// walks up to (13, 8) and takes the DOME, which opens the gap north. -// The save is on Route 4: the save card's places don't include Mt. Moon. -StoryCheckpoint mt_moon(){ - return row("00.13", "Mt. Moon: a Moon Stone, a Rocket, Miguel and the Helix Fossil", at(ROUTE4_CENTER, 11, 2, NORTH), after_brock(), StartMenu::FULL, { - running(route("route4_nurse", at(ROUTE4_CENTER, 7, 4, NORTH), "the nurse, below the counter")), - heal("heal_route4"), - running(route("mt_moon_entrance", at(MT_MOON_1F, 18, 37, NORTH), "out of the Center, west into Mt. Moon")), - running(legs("mt_moon_1f", { - {NORTH, 12}, {EAST, 8}, {NORTH, 12}, {EAST, 3}, {SOUTH, 1}, {EAST, 9}, - {NORTH, 1}, {EAST, 2}, {NORTH, 4}, {WEST, 24}, {EAST, 2}, {SOUTH, 12}, - {WEST, 7}, {NORTH, 18}, {WEST, 9}, {EAST, 1}, - }, "up the middle, east of Miriam, west along row 9, down column 18 past Josh, up column 11 past Marcos, to (3, 3)")), - running(route("below_moon_stone", at(MT_MOON_1F, 3, 3, NORTH), "facing the Moon Stone (3, 2)")), - talk("moon_stone_ball", "A on the item ball"), - // No reader for the text yet: the scene's end is the overworld. - scene("found_moon_stone", at(MT_MOON_1F, 3, 3, NORTH), "\" found a MOON STONE!\", then the pocket line; the ball is gone"), - running(route("b2f_ladder_foot", at(MT_MOON_B2F, 26, 21, EAST), "1F's ladder (5, 6), down B1F's west side to its ladder (22, 18), onto B2F")), - running(legs("b2f_south", { - {NORTH, 4}, {EAST, 8}, {WEST, 2}, {SOUTH, 5}, {EAST, 8}, {NORTH, 3}, {WEST, 1}, {NORTH, 5}, - {EAST, 7}, {SOUTH, 14}, {WEST, 9}, {SOUTH, 10}, {WEST, 29}, {EAST, 3}, {NORTH, 17}, - }, "through the Grunt's room at (37, 21), round the south edge, up column 11 to (11, 21)")), - step_into("grunt_line", EAST, "onto (12, 21), below the Grunt at (12, 20)"), - battle("rocket_grunt", at(MT_MOON_B2F, 12, 21, EAST), false, "Rattata and Zubat, Lv13"), - ready("ready_for_miguel", at(ROUTE4_CENTER, 7, 4, NORTH), "items for a status or low HP; the nurse, back through the cave, only if they can't"), - // Up column 13 to Miguel, who stops the leg on (13, 12), then east - // to the wall: the tile below his trigger. - running(legs("below_miguel_trigger", {{EAST, 1}, {NORTH, 9}, {EAST, 1}}, "to (14, 12), below Miguel's trigger (14, 11)")), - step_into("miguel_trigger", NORTH, "onto (14, 11), beside Miguel"), - battle("super_nerd_miguel", at(MT_MOON_B2F, 14, 11, NORTH), false, "Grimer, Voltorb and Koffing, Lv12; \"We'll each take one!\""), - running(legs("below_helix_fossil", {{NORTH, 3}}, "up column 14 to the HELIX FOSSIL (14, 7)")), - fossil_step("helix_fossil", "the HELIX FOSSIL; Miguel takes the DOME FOSSIL", Fossil::HELIX), - // The maps keep the fossils, which the game removes after the - // choice: the step onto the HELIX's tile is taken on its own. - step_into("helix_gap", NORTH, "onto (14, 7), where the HELIX FOSSIL lay"), - running(route("route4_east", at(ROUTE4, 32, 6, SOUTH), "west to B2F's ladder (5, 10), up to B1F, east to its exit onto Route 4")), - }); -} - -// Route 4 east of Mt. Moon (Route4 map.json) joins Cerulean City at its -// east end (x 107; Route 4's row 3 is the city's row 13). Its upper road -// (rows 2..8) reaches the city only by water: the way on by land is down -// the ledge row 9 at x 90..96 to the lower road (rows 10..12), which -// joins the city's rows 20..22. Lass Crissy (75, 3) faces east, sight 4, -// over row 3 from x 76 to 79; the way east keeps out of her line and off -// the grass south of the road (75..87, 11..17). -// -// On the way, the Mega Kick tutor (Black Belt (50, 3), facing west, -// Route4_EventScript_MegaKickTutor) stands in a pen (46..51, 2..4) north -// of the road, beside the Mega Punch tutor (47, 3); the pen opens south -// only on (48, 5) and (49, 5), the rest of row 5 ledges out of it. From -// Mt. Moon's exit the walk goes east and up into the pen to (50, 4), -// below the tutor, and Mega Kick takes Withdraw's slot. Cerulean can't -// be walked back to Route 4's upper road without Surf, so it is now or -// much later. -// -// The open leg east across the city's edge ended a tile short on the -// console (2026-11-03: on (6, 20), not (7, 20)), and no hold to a wall -// in the city's west end finds the player from there: the next hold -// south met the fence on (6, 23) and the walk went on a column off. So -// the entry ends on (9, 20), and only legs to walls follow: north up -// column 9 (or 8, a tile short) to row 12, west to (8, 12), east to -// (30, 12) beside the policeman (31, 12), south to (30, 17), west to -// (25, 17), and down the ledge row 18 to (25, 19), a few tiles from the -// door (22, 19). The way keeps out of the boy's (16, 18..24) and the -// balding man's (8..10, 22..24) wander boxes. -StoryCheckpoint route4_to_cerulean(){ - const TeachPlan mega_kick{.item = std::string(TEACH_TUTOR), .move = "mega-kick", .member = 0, .forget_move = "withdraw"}; - return row("00.14", "Route 4: Mega Kick from its tutor, then east to Cerulean's Pokemon Center", at(ROUTE4, 32, 6, SOUTH), after_brock(), StartMenu::FULL, { - running(route("mega_kick_tutor", at(ROUTE4, 50, 4, NORTH), "east and up through (48, 5) into the tutors' pen, below the Mega Kick tutor (50, 3)")), - teach_step("teach_mega_kick", "the tutor's Mega Kick in place of Withdraw, unless the lead knows it", mega_kick), - running(route("above_ledge", at(ROUTE4, 93, 8, SOUTH), "east along Route 4's upper road to above the ledge row 9")), - hop("ledge_hop", SOUTH, at(ROUTE4, 93, 10, SOUTH), "down the ledge (93, 9) to the lower road"), - running(route("cerulean_entry", at(CERULEAN, 9, 20, EAST), "east into Cerulean City, to (9, 20) below column 9")), - running(legs("above_city_ledge", {{NORTH, 8}, {WEST, 1}, {EAST, 22}, {SOUTH, 5}, {WEST, 5}}, "to walls: (9, 12), (8, 12), (30, 12), (30, 17), (25, 17)")), - hop("city_ledge_hop", SOUTH, at(CERULEAN, 25, 19, SOUTH), "down the ledge (25, 18)"), - running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "west along row 20 to the Pokemon Center's door (22, 19)")), - heal("heal_cerulean"), - }); -} - -// Cerulean Gym (pokefirered CeruleanCity_Gym map.json and scripts.inc): -// a pool with walkways. Misty (8, 6) stands on a platform (7..9, 6) above -// row 7. Picnicker Diana (4, 7) faces east, sight 4, over (5..8, 7), so -// misty_front (8, 7) is in her line; Swimmer Luis (10, 12) in the water -// faces west, sight 1, over (9, 12), on column 9 north of the door. No -// gym trainer has to be fought for Misty, so neither is: from the door up -// column 9 to row 13 (a route there, so the planner's walk to a wall -// doesn't run on past it through (9, 12)), west to (4, 13), and to walls -// round the pool's west and north sides, (4, 10), (12, 10), (12, 7), then -// west to (9, 7), the tile below the platform's east end, out of Diana's -// line. 00.14c steps up onto (9, 6) and talks to Misty from beside her. -// -// Misty beat a Lv20 Wartortle (console B, run m51), so first a GRIND to -// Lv23 on 00.15's patch. Gen 3 numbers (pokefirered: her mons have IV 0; -// Wartortle taken at IV 15, no EVs): Starmie Lv21 has 56 HP, Atk 36, SpA -// 47, SpD 40. Bite is Dark, special in Gen 3, no STAB, x2 on Psychic: at -// Lv23 (SpA 39) it does 23..28, three hits (two on top rolls); at Lv20, -// 20..24. Her Swift (physical, 60) does 9..11 to a Lv23 Wartortle (about -// 67 HP, Def 45) and Water Pulse (STAB, resisted) 8..10: 6 or more hits, -// though m51 saw about 20 a turn. Her Recover (28) and Super Potion undo -// a Bite, so the margin is in Bite's damage. -// -// The Mart comes before the GRIND: its wild battles are fought only with -// 2 or more Potions, and 00.14's save has none (m51 met Misty with only -// the 5 this row bought). 5 Potions (row 3, 300 each), then up to 3 Super -// Potions (row 2, 700), last so they take the Items pocket's last row; -// the battle doesn't use them yet. The money box can't be read, so the -// floor stands for it, and a floor above the money fails the step (the -// quantity box wraps). Prizes (pokefirered: 4 x last level x the class's -// factor) from a new game's 3000, less Viridian's 2000 and the Magikarp's -// 500: Liam 220, Brock 1400, Route 3's bug catchers 360, the Mt. Moon -// grunt 416 and Miguel 288 (the first rival's 80 only if won), at least -// 3184 here. Floor and budget 3000: 5 Potions and 2 Super Potions, 2900. -// Misty's 2100 (4 x 21 x 25) then leaves 00.15 its floor of 1500. Then the -// nurse, and the save before Misty, so her battle is its own checkpoint, -// 00.14c, verified and retried from it. 00.15's GRIND then fights -// until the sim beats the Cerulean rival often enough. -StoryCheckpoint cerulean_gym(){ - const MartPlan supplies{ - .map = CERULEAN_MART, - .lines = {{.item = "potion", .quantity = 5}, {.item = "super-potion", .quantity = 3}}, - .budget = 3000, - .money_floor = 3000, - }; - const GrindPlan to_lv23{.member = 0, .level = 23, .max_encounters = 100, .pace = WEST, .heal_every = 0}; - return row("00.14b", "Cerulean: Potions, a GRIND to Lv23, then into the Gym, saved below Misty's platform", at(CERULEAN_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - running(route("mart_clerk", at(CERULEAN_MART, 4, 3, WEST), "out of the Center to the Mart's clerk")), - mart_step("buy_potions", "5 Potions for the GRIND's wild battles, then Super Potions as the money allows", supplies), - running(route("grind_patch", at(ROUTE4, 86, 16, WEST), "west out of the city along Route 4's lower road onto its grass's east edge")), - grind_step("grind_to_lv23", "wild battles until the lead is Lv23, where Bite takes Starmie in three", to_lv23, at(CERULEAN_CENTER, 7, 4, NORTH)), - running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "back to the Center: Misty's battle starts at full HP and PP")), - heal("heal_cerulean"), - running(route("gym_below_luis", at(CERULEAN_GYM, 9, 13), "into the Gym, up column 9 to row 13, short of Luis's (9, 12)")), - running(legs("gym_west_walkway", {{WEST, 5}}, "west to the wall (4, 13)")), - running(legs("below_misty_platform", {{NORTH, 3}, {EAST, 8}, {NORTH, 3}, {WEST, 3}}, "walls (4, 10), (12, 10), (12, 7), then to (9, 7), out of Diana's line")), - }); -} - -// Misty (TRAINER_LEADER_MISTY: Staryu Lv18, Starmie Lv21), talked to from -// (9, 6), beside her on the platform. Her win text runs on to the CASCADE -// BADGE, TM03 (Water Pulse) and its explanation, all read with the -// battle's. Bite is chosen against her party (Starmie is Water/Psychic). -// Back to the nurse round the pool's west side, out of Luis's line; then -// Water Pulse in place of Tail Whip, last, at the Center, from the bag's -// TM CASE. Ends where 00.15 starts. -StoryCheckpoint misty(){ - const TeachPlan water_pulse{.item = "tm03", .move = "water-pulse", .member = 0, .forget_move = "tail-whip"}; - return row("00.14c", "Cerulean Gym: Misty, then Water Pulse", at(CERULEAN_GYM, 9, 7, WEST), after_brock(), StartMenu::FULL, { - running(route("beside_misty", at(CERULEAN_GYM, 9, 6, WEST), "up onto (9, 6), facing Misty (8, 6)")), - talk("challenge_misty", "Misty's speech"), - battle("misty", at(CERULEAN_GYM, 9, 6, WEST), false, "Staryu and Starmie, with Bite; CASCADE BADGE and TM03"), - running(legs("gym_way_out", {{SOUTH, 1}, {EAST, 3}, {SOUTH, 3}, {WEST, 8}, {SOUTH, 3}, {EAST, 5}}, "walls (12, 7), (12, 10), (4, 10), (4, 13), (9, 13), round Luis's (9, 12)")), - running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "down column 9 out of the Gym, to the Center")), - heal("heal_cerulean"), - teach_step("teach_water_pulse", "TM03 Water Pulse in place of Tail Whip", water_pulse), - }); -} - -// Before the rival north of Cerulean (cerulean_rival()), from the -// Center: first TM03 Water Pulse over Tail Whip, if the lead doesn't know -// it yet, so the GRIND's sim plays it (a run without TM03 from Misty -// skips the step). Then 3 Potions at the Mart -// (CeruleanCity_Mart_Items: POTION is row 3, 300 each; Mt. Moon's -// trainers leave well over the floor) and 2 Awakenings (row 6, 250 each; -// pokefirered items.json) for the rival's Bulbasaur's Sleep Powder -// (console B, m56: asleep at 34/65 HP, it fainted two turns later with -// 15 Potions), 1400 of the floor's 1500. Then a GRIND on Route 4's grass -// east of Mt. Moon (75..86, 11..17; Spearow, Rattata, Ekans and Mankey -// around Lv10, about 80 EXP each), healing at the Center, until the -// battle sim beats the rival 00.15b fights 50% or more: the win chance -// his battle refuses below. Console B's Wartortle (Lv22, 65 HP, Water -// Pulse) is about 42% there and 62% at Lv23; Lv28 (about 88%, some 120 -// battles from Lv22) is the cap. Then the nurse, and the save on (23, 7), -// the tile below his trigger: his battle is its own checkpoint, 00.15b, -// verified and retried from this save. -StoryCheckpoint cerulean_grind(){ - const TeachPlan water_pulse{.item = "tm03", .move = "water-pulse", .member = 0, .forget_move = "tail-whip"}; - const MartPlan potions{ - .map = CERULEAN_MART, - .lines = {{.item = "potion", .quantity = 3}, {.item = "awakening", .quantity = 2}}, - .budget = 1500, - .money_floor = 1500, - }; - const GrindPlan for_rival{ - .member = 0, .level = 28, .max_encounters = 150, .pace = WEST, .heal_every = 0, - .trainer = std::string(story_trainer(CERULEAN, "rival_cerulean").value_or("")), .min_chance = MIN_WIN_CHANCE, - }; - return row("00.15", "Cerulean: Water Pulse, Potions, a GRIND until the rival is winnable, then saved below his trigger", at(CERULEAN_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - teach_step("teach_water_pulse", "TM03 Water Pulse in place of Tail Whip, unless the lead knows it or there is no TM03", water_pulse), - running(route("mart_clerk", at(CERULEAN_MART, 4, 3, WEST), "out of the Center to the Mart's clerk")), - mart_step("buy_potions", "3 Potions: the GRIND fights wild battles only with 2 or more; 2 Awakenings for the rival's Sleep Powder", potions), - running(route("grind_patch", at(ROUTE4, 86, 16, WEST), "west out of the city along Route 4's lower road onto its grass's east edge")), - grind_step("grind_for_rival", "wild battles until the sim beats the rival 50% or more, Lv28 at most; the nurse when hurt or low on PP", for_rival, at(CERULEAN_CENTER, 7, 4, NORTH)), - running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "back to the Center: the rival's battle starts at full HP and PP")), - heal("heal_cerulean"), - running(route("below_rival_trigger", at(CERULEAN, 23, 7, NORTH), "out of the Center, north up to (23, 7), short of the trigger row 6")), - }); -} - -// The rival north of Cerulean (CeruleanCity scripts.inc): the only way -// north to Nugget Bridge is columns 22..24, and a step onto row 6 there -// (CeruleanCity_EventScript_RivalTrigger*, VAR_MAP_SCENE_CERULEAN_CITY_RIVAL -// 0) brings him down to the player. From the middle, (23, 6), he stops -// at (23, 5); after the battle he walks round the player and off south, -// and the player, turning to watch him, ends facing south. His FAME -// CHECKER's text is read to the overworld with the battle's. For a player -// who took Squirtle he has Pidgeotto Lv17, Abra Lv16, Rattata Lv15 and -// Bulbasaur Lv18 (sParty_RivalCeruleanBulbasaur), whose Vine Whip and -// Sleep Powder are the danger. The row starts from 00.15's save below the -// trigger, and saves after him. First TM03 Water Pulse over Tail Whip (from -// the bag, where the player stands), if the lead doesn't know it yet: -// console B's save (2026-11-03) is past Misty's TEACH, which failed there. -// Where she taught it, the step reads the moves and does nothing. -StoryCheckpoint cerulean_rival(){ - const TeachPlan water_pulse{.item = "tm03", .move = "water-pulse", .member = 0, .forget_move = "tail-whip"}; - return row("00.15b", "Cerulean: Water Pulse if not known, then the rival north of the city", at(CERULEAN, 23, 7, NORTH), after_brock(), StartMenu::FULL, { - teach_step("teach_water_pulse", "TM03 Water Pulse in place of Tail Whip, unless the lead knows it", water_pulse), - step_into("rival_trigger", NORTH, "onto (23, 6), the middle trigger: \"Yo! !\""), - battle("rival_cerulean", at(CERULEAN, 23, 6, SOUTH), false, "Pidgeotto, Abra, Rattata and Bulbasaur; then the FAME CHECKER"), - }); -} - -// Nugget Bridge (Route24 map.json) is x 10..12 from row 32 north to row -// 16, the city's columns 22..24 (offset 12). Its five trainers stand on -// it facing across, sight 2, so each line crosses the middle column 11 -// on the trainer's row: Bug Catcher Cale (12, 31), Lass Ali (10, 28), -// Youngster Timmy (12, 25), Lass Reli (10, 22) and Camper Ethan (12, 19). -// Each is fought on the step onto their row, beside them: they don't -// walk, and the player keeps facing north. Two tiles separate the rows, -// walked as one short leg; the step onto a row goes on a tile at a time -// until the trainer sees the player (into_line()), as the open leg onto -// the bridge ended a tile short twice on console B (2026-11-03), on -// (11, 33), and one step left the player out of Cale's sight. Then the -// Rocket (12, 15): the step onto (11, 15) -// (Route24_EventScript_RocketTriggerRight, VAR_MAP_SCENE_ROUTE24 0) gives -// the NUGGET, "join Team Rocket", and his battle; he only turns, and the -// player turns east to him. Camper Shane (5, 21) looks north from the -// west bank, off the way. -StoryCheckpoint nugget_bridge(){ - return row("00.16", "Nugget Bridge: the nurse, five trainers and the Rocket", at(CERULEAN, 23, 6, SOUTH), after_brock(), StartMenu::FULL, { - running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "back to the Center after the rival, before Nugget Bridge")), - heal("heal_cerulean"), - running(route("below_cale_line", at(ROUTE24, 11, 32, NORTH), "out of the Center, north past the rival's row onto the bridge, below Cale's row")), - into_line("cale_line", NORTH, "onto (11, 31), beside Cale"), - battle("bug_catcher_cale", at(ROUTE24, 11, 31, NORTH), false, "his Bug Pokemon"), - running(legs("below_ali_line", {{NORTH, 2}}, "up to (11, 29)")), - into_line("ali_line", NORTH, "onto (11, 28), beside Ali"), - battle("lass_ali", at(ROUTE24, 11, 28, NORTH), false, "her Pokemon"), - running(legs("below_timmy_line", {{NORTH, 2}}, "up to (11, 26)")), - into_line("timmy_line", NORTH, "onto (11, 25), beside Timmy"), - battle("youngster_timmy", at(ROUTE24, 11, 25, NORTH), false, "his Pokemon"), - running(legs("below_reli_line", {{NORTH, 2}}, "up to (11, 23)")), - into_line("reli_line", NORTH, "onto (11, 22), beside Reli"), - battle("lass_reli", at(ROUTE24, 11, 22, NORTH), false, "her Pokemon"), - running(legs("below_ethan_line", {{NORTH, 2}}, "up to (11, 20)")), - into_line("ethan_line", NORTH, "onto (11, 19), beside Ethan"), - battle("camper_ethan", at(ROUTE24, 11, 19, NORTH), false, "his Pokemon"), - // A route, not legs: the nurse may have been needed. - ready("ready_for_rocket", at(CERULEAN_CENTER, 7, 4, NORTH), "items for a status or low HP; the nurse only if they can't"), - running(route("below_rocket_trigger", at(ROUTE24, 11, 16, NORTH), "up the bridge to below the Rocket's trigger (11, 15)")), - step_into("rocket_trigger", NORTH, "onto (11, 15), beside the Rocket"), - battle("rocket_grunt", at(ROUTE24, 11, 15, EAST), false, "the NUGGET, then Ekans and Zubat, Lv15"), - }); -} - -// Route 24 north of Nugget Bridge (Route24 map.json) joins Route 25 by -// its rows 7..9; the ledges on Route 25's row 6 (x 0..9) leave the way -// north at x 12..13. Route 25 (Route25 map.json, scripts.inc) to Bill's -// Sea Cottage door (51, 4): of its nine trainers, four can't be walked -// around. -// - Hiker Franklin (11, 4) faces east, sight 4, over (12..15, 4), and -// Hiker Wayne (17, 7) east over (18..19, 7): the only ways east past -// x 15. Franklin is fought, from (15, 5) on the step onto (15, 4), the -// end of his line: he walks up to (14, 4). -// - Youngster Joey (18, 2) faces south over (18, 3..4): the way keeps -// off column 18, south round Wayne to (19, 6) and up to (19, 4). -// - Youngster Dan (22, 4) looks around, sight 2, over the only way east, -// (20..21, 4). On (20, 4), a one-tile leg, he may see the player and -// walk up to (21, 4); so the way goes north from there to row 2, east, -// and down column 23 past him to (23, 5), off every tile he may walk -// up to. Where his sight is crossed, the leg ends on a wall or is one -// tile; if he looks the player's way, story_interrupts() fight him. -// - Picnicker Kelsey (22, 8) faces east over (23..24, 8), across the -// only way on: fought on the step onto (24, 8); she walks up to (23, 8). -// - Hiker Nob (27, 9) faces north over (27, 6..8), Camper Flint (28, 4) -// south over (28, 5..7): every way east crosses one. Nob is fought on -// the step onto (27, 8), beside him; the way on keeps off Flint's -// column, by one-tile legs and legs to walls. -// - Youngster Chad (36, 4) walks up and down column 36 (rows 2..7), so -// he may see any tile of it down to row 9. The way crosses his column -// on row 9, a leg to the water at (42, 9), where he can only walk up -// beside it. -// - Lass Haley (42, 5) faces south over (42, 6..8), the only way east of -// x 42: fought on the step onto (42, 8); she walks up to (42, 7). -// The row saves inside the Sea Cottage, which the save card names -// ROUTE 25. -StoryCheckpoint route25(){ - return row("00.17", "Route 24 and 25 to Bill's Sea Cottage: Franklin, Kelsey, Nob and Haley", at(ROUTE24, 11, 15, EAST), after_brock(), StartMenu::FULL, { - running(route("below_franklin_line", at(ROUTE25, 15, 5, NORTH), "north off Nugget Bridge, east into Route 25, round its ledges to (15, 5), below the end of Franklin's line")), - into_line("franklin_line", NORTH, "onto (15, 4), at the end of Franklin's line"), - battle("hiker_franklin", at(ROUTE25, 15, 4, NORTH), false, "Machop and Geodude, Lv15; Franklin stops at (14, 4)"), - running(legs("west_of_dan", {{EAST, 2}, {SOUTH, 2}, {EAST, 2}, {NORTH, 2}}, "to (17, 4), short of Joey's (18, 4); round Wayne (17, 7) to (19, 6); up to (19, 4)")), - running(legs("round_dan", { - {EAST, 1}, {NORTH, 2}, {EAST, 4}, {WEST, 1}, {SOUTH, 3}, {EAST, 1}, {SOUTH, 2}, - }, "onto (20, 4); up to (20, 2), east to (24, 2), down column 23 past Dan to (23, 5), to (24, 7) above Kelsey's line")), - into_line("kelsey_line", SOUTH, "onto (24, 8), at the end of Kelsey's line"), - battle("picnicker_kelsey", at(ROUTE25, 24, 8, SOUTH), false, "Nidoran M and Nidoran F, Lv15; Kelsey stops at (23, 8)"), - running(legs("west_of_nob_line", {{EAST, 2}}, "along row 8 to (26, 8)")), - into_line("nob_line", EAST, "onto (27, 8), above Nob"), - battle("hiker_nob", at(ROUTE25, 27, 8, EAST), false, "three Geodude and a Machop, Lv13"), - running(legs("below_haley_line", { - {EAST, 1}, {SOUTH, 1}, {EAST, 1}, {NORTH, 3}, {EAST, 5}, {SOUTH, 3}, {EAST, 8}, - }, "below Flint's column to (29, 9), up to (29, 6), east to (34, 6), down to (34, 9), east along row 9 past Chad's column to the water, (42, 9)")), - into_line("haley_line", NORTH, "onto (42, 8), at the end of Haley's line"), - battle("lass_haley", at(ROUTE25, 42, 8, NORTH), false, "Oddish, Pidgey and Oddish, Lv13; Haley stops at (42, 7)"), - running(route("sea_cottage", at(SEA_COTTAGE, 7, 8, NORTH), "east along row 8, up column 51 into the Sea Cottage (51, 4), a step in from its mat")), - }); -} - -// Bill's Sea Cottage (Route25_SeaCottage scripts.inc). Bill is the -// Clefairy at (10, 6) (FLAG_HELPED_BILL_IN_SEA_COTTAGE unset): talked to -// from (10, 7), below him, he asks "Help me out!" (YES/NO; NO goes on the -// same), says to run the Cell Separation on the PC, and walks up into -// the right teleporter (10, 3); with the player facing north he walks -// straight up. The PC (4, 5) answers only from the south, (4, 6). Its -// scene: the teleporters run, Bill comes out of the left one (3, 3) and -// walks to (7, 6) (BillWalkToMiddleOfRoom); the player isn't moved. -// Talked to from (6, 6), Bill gives the S.S. TICKET, no YES/NO, and goes -// on about the S.S. ANNE (FLAG_GOT_SS_TICKET, which also hides Nugget -// Bridge's Rocket). Out by the mat (7, 9); the door puts the player on -// (51, 5), facing south. The maps keep Bill at (7, 5), but after his -// scene he stands on (7, 6), in the way from (6, 6) to the mat (console -// B, 2026-11-03), so the walk out goes around him to (7, 8) first. -StoryCheckpoint bill(){ - return row("00.18", "Bill's Sea Cottage: the Cell Separator and the S.S. Ticket", at(SEA_COTTAGE, 7, 8, NORTH), after_brock(), StartMenu::FULL, { - running(route("below_bill_clefairy", at(SEA_COTTAGE, 10, 7, NORTH), "below the Clefairy (10, 6), facing it")), - talk("bill_clefairy", "\"Hiya! I'm a POKEMON... ...No I'm not!\" ... \"Help me out!\""), - answer("help_bill", true, "YES: \"run the Cell Separation on the PC\""), - scene("bill_into_teleporter", at(SEA_COTTAGE, 10, 7, NORTH), "the Clefairy walks up into the right teleporter"), - running(route("pc_front", at(SEA_COTTAGE, 4, 6, NORTH), "below the PC (4, 5), facing it")), - talk("cell_separator", "the PC: \" initiated the TELEPORTER's CELL SEPARATOR!\""), - scene("bill_comes_out", at(SEA_COTTAGE, 4, 6, NORTH), "the teleporters run; Bill comes out of the left one and walks to (7, 6)"), - running(legs("beside_bill", {{EAST, 2}}, "to (6, 6), west of Bill, facing him")), - talk("thank_bill", "\"Thanks, bud! ... take this\""), - scene("ss_ticket", at(SEA_COTTAGE, 6, 6, EAST), "the S.S. TICKET; \"... the S.S. ANNE ... go instead\""), - running(legs("around_bill", {{SOUTH, 2}, {EAST, 1}}, "to (7, 8), above the mat; Bill stands on (7, 6) after his scene")), - running(route("out_of_cottage", at(ROUTE25, 51, 5, SOUTH), "down to the mat and out; the door puts the player below it")), - }); -} - -// Back to Cerulean by Route 25's south path (Route25 map.json): from -// below the cottage down column 51 to row 12, west along row 12 and row -// 11 into Route 24 (x 14..23, row 11), and down its column 14..16 east -// of Nugget Bridge to Cerulean's north edge (x 26..28, offset 12). No -// trainer stands on it. Cerulean's row 1 there is a ledge (26..28, 1), -// hopped south from (27, 0); then on into the city to the Pokemon Center. -StoryCheckpoint back_to_cerulean(){ - return row("00.19", "Route 25's south path back to Cerulean's Pokemon Center", at(ROUTE25, 51, 5, SOUTH), after_brock(), StartMenu::FULL, { - running(route("above_cerulean_ledge", at(CERULEAN, 27, 0, SOUTH), "Route 25's south path west, Route 24 east of the bridge south, to above Cerulean's ledge (27, 1)")), - hop("cerulean_ledge_hop", SOUTH, at(CERULEAN, 27, 2, SOUTH), "down the ledge (27, 1)"), - running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "into the city to the Pokemon Center")), - heal("heal_cerulean"), - }); -} - -// Stopgap for console B's save, which caught nothing on Route 1. A new -// playthrough should instead catch a Rattata on Route 1 or 22 early, as -// its Cut user and spare member, and drop this row (and its walk over -// Nugget Bridge and back). -// -// An Oddish for HM01 Cut, and a second member behind the lead -// (docs/design/AutoStory-FRLG-FutureEvents.md, "Console B run choices"). -// Route 24's grass (Route24 map.json: x 3..5, rows 19..31, west of Nugget -// Bridge) is the nearest to the Center with Oddish: FireRed's -// sRoute24_FireRed (pokefirered wild_encounters.json) has it in 25% of -// battles, Lv12..14, as Route 25's grass; Routes 5 and 6 aren't on the -// story's maps yet. Up the bridge, whose trainers 00.16 beat, west along -// rows 10..15 to the cliff (3, 15) and down column 3 to the grass's first -// row, (3, 19); the step paces east to (4, 19) and back. Camper Shane -// (5, 21) faces north, sight 5, over (5, 16..20): column 5 is kept off. -// The leg west ends on the cliff, not open: on console B (m63) an open -// leg west to (4, 15) ended a tile short, and the walk down column 5 -// came into Shane's sight. -// -// First 7 Poke Balls at the Mart (row 1, 200 each; the bag isn't read, so -// the step buys them whatever the bag holds). The floor, from 00.15's -// 1500 less its 1400: the rival's 288 (4 x 18 x 4), the Rocket's 480 (4 -// x 15 x 8), Franklin's 540 and Nob's 468 (hikers, 9) alone make 1876 -// (pokefirered gTrainerMoneyTable). An Oddish at full HP (catch rate 255) -// stays in a Poke Ball about 1 time in 3 (Gen 3: a = 85, four shakes of -// 0.76), so 7 balls catch it about 94% of the time; the step throws 7 at -// most, in 30 battles (none an Oddish: 0.02%). Nothing weakens it: the HP -// bar isn't read on the console yet. -// -// The Oddish joins the party second. Battles keep the lead in -// (PokemonFRLG_AutoStoryBattleDecisions.h): the Oddish is sent only when -// the lead faints in a trainer's battle, which counts as lost. Back to -// the nurse, and the save in the Center. -StoryCheckpoint oddish(){ - const MartPlan balls{ - .map = CERULEAN_MART, - .lines = {{.item = "poke-ball", .quantity = 7}}, - .budget = 1400, - .money_floor = 1500, - }; - const CapturePlan catch_oddish{.species = "oddish", .catches = 1, .max_encounters = 30, .max_balls = 7, .pace = EAST}; - return row("00.20", "Route 24's grass: an Oddish for Cut, then the Center", at(CERULEAN_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - running(route("mart_clerk", at(CERULEAN_MART, 4, 3, WEST), "out of the Center to the Mart's clerk")), - mart_step("buy_poke_balls", "7 Poke Balls for the Oddish", balls), - running(route("oddish_patch", at(ROUTE24, 3, 19), "north over Nugget Bridge, west to the cliff and down column 3 onto the grass's first row, out of Shane's column 5")), - capture_step("catch_oddish", "wild battles until an Oddish, then Poke Balls; any other species is run from", catch_oddish), - running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "back over the bridge to the Center")), - heal("heal_cerulean"), - }); -} - -// Cerulean's only way south without Cut (CeruleanCity map.json, -// scripts.inc) is through the burgled house (30, 11), whose door the -// policeman leaves once the S.S. TICKET is had, out of the hole in its -// back wall into the yard (31, 8). The Rocket there (33, 6) stops the -// player on (33, 5) or (33, 7) (CeruleanCity_EventScript_GruntTrigger*): -// from (32, 7), the step east onto (33, 7) turns the player to him; -// Machop and Drowzee, Lv17, then TM28 Dig, and he is gone. Then east -// round the yard, down to the city's south edge by the path along its -// east side (39..40, 20..31), down Route 5's east side to the -// Underground Path's north house (31, 31), through its tunnel and out of -// the south house onto Route 6, below its door (19, 14). Route 6: -// - Bug Catcher Keigo (3, 16) faces east, sight 5, over the middle path -// (4..8, 16); Bug Catcher Elijah (20, 25) faces west, sight 3, over -// the east path (17..19, 25). The way goes down column 15, through the -// grass to the wall (15, 33), out of both lines; the open leg west to -// it, a tile short, leaves the player on column 16, out of them too. -// - Camper Ricky and Picnicker Nancy (12..13, 21) battle only when -// talked to. -// - Picnicker Isabelle (13, 32) and Camper Jeff (13, 33) face west, sight -// 3, over (10..12, 32) and (10..12, 33); the only way south, (10..12, -// 34), is from Jeff's row. Up to (15, 31), west to the wall (7, 31), -// down to the wall (7, 33), east: Jeff is fought on the step onto -// (10, 33) and walks up to (11, 33). Isabelle's row is kept off. -// Then down into Vermilion City (offset 12) to its Pokemon Center. -StoryCheckpoint to_vermilion(){ - return row("00.21", "The Cerulean Rocket, Route 5, the Underground Path and Route 6 to Vermilion's Pokemon Center: Jeff", at(CERULEAN_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - running(route("west_of_rocket", at(CERULEAN, 32, 7, EAST), "through the burgled house and out of the hole in its back wall, west of the Rocket (33, 6)")), - step_into("rocket_trigger", EAST, "onto (33, 7), below the Rocket"), - battle("rocket_grunt", at(CERULEAN, 33, 7, NORTH), false, "Machop and Drowzee, Lv17; TM28 Dig"), - running(route("route6_column15", at(ROUTE6, 15, 14), "round the yard and down Cerulean's east side, Route 5 into the Underground Path, through it onto Route 6, west to column 15")), - running(legs("round_keigo_and_elijah", { - {SOUTH, 19}, {NORTH, 1}, {NORTH, 1}, {WEST, 8}, {SOUTH, 2}, {EAST, 2}, - }, "down column 15 to the wall (15, 33); up to (15, 31), west to the wall (7, 31), down to the wall (7, 33), east to (9, 33)")), - into_line("jeff_line", EAST, "onto (10, 33), in Jeff's line"), - battle("camper_jeff", at(ROUTE6, 10, 33, EAST), false, "Spearow and Raticate, Lv16; Jeff stops at (11, 33)"), - running(route("vermilion_nurse", at(VERMILION_CENTER, 7, 4, NORTH), "down Route 6's south path into Vermilion City, to the Pokemon Center")), - heal("heal_vermilion"), - }); -} - -// The S.S. Anne (VermilionCity, SSAnne_* scripts.inc). The step onto -// (23, 33) (VermilionCity_EventScript_CheckTicket) has the sailor (24, 33) -// ask for a ticket; the S.S. TICKET (00.18) is shown and the player turns -// east to him. Then onto the pier (23, 34), along the gangway, through -// 1F, by legs off its cabins' doormats on row 10, which planned routes -// cross, and up its stairs (3, 8) to 2F. The step onto (30, 6), below the -// Captain's door (30, 2) (SSAnne_2F_Corridor_EventScript_RivalTriggerLeft), -// brings the rival out and down to (30, 5); the player keeps facing -// north, and after the battle he walks round the player and off. For a -// player who took Squirtle: Pidgeotto Lv19, Raticate Lv16, Kadabra Lv18, -// Ivysaur Lv20. The ship's trainers are in its cabins and on its deck, -// off the way. The Captain (5, 4) faces up; talked to from (5, 5) he is -// rubbed on the back and gives HM01 (VAR_MAP_SCENE_VERMILION_CITY 1). -// Back out through 1F's door (19, 1): on the gangway the ship sails -// (SSAnne_Exterior_ExitSSAnne), the player is put on (23, 34) and walked -// up to (23, 32) (VermilionCity_EventScript_ExitSSAnne). Cut is taught -// later, to the Oddish. -StoryCheckpoint ss_anne(){ - return row("00.22", "The S.S. Anne: the rival, HM01 Cut from the Captain, back to Vermilion's Pokemon Center", at(VERMILION_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - running(route("above_ticket_check", at(VERMILION, 23, 32, SOUTH), "out of the Center down to the harbor, above the ticket check (23, 33)")), - step_into("ticket_check", SOUTH, "onto (23, 33): \"Do you have a ticket?\""), - scene("show_ss_ticket", at(VERMILION, 23, 33, EAST), "the S.S. TICKET; the player turns to the sailor"), - running(route("aboard", at(SS_ANNE_1F, 19, 2, SOUTH), "onto the pier, along the gangway and in by 1F's door, a step below it")), - running(legs("past_the_cabins", {{SOUTH, 8}, {NORTH, 1}, {WEST, 6}, {NORTH, 1}}, "to the wall (19, 10), up a step, west to the worker (12, 9), up a step to (13, 8): off the cabins' mats on row 10")), - running(route("below_rival_trigger", at(SS_ANNE_2F, 30, 7, NORTH), "west along 1F, up the stairs (3, 8) and along 2F below the Captain's door")), - step_into("rival_trigger", NORTH, "onto (30, 6): the rival comes out of the Captain's door"), - battle("rival_ss_anne", at(SS_ANNE_2F, 30, 6, NORTH), false, "Pidgeotto, Raticate, Kadabra and Ivysaur"), - running(route("behind_captain", at(CAPTAINS_OFFICE, 5, 5, NORTH), "into the Captain's office, behind the Captain (5, 4)")), - talk("captain", "\"Ooargh... I feel hideous... Urrp! Seasick...\""), - scene("hm01_cut", at(CAPTAINS_OFFICE, 5, 5, NORTH), "the player rubs his back; HM01, and what Cut does"), - running(route("off_2f", at(SS_ANNE_1F, 13, 8), "back along 2F, down the stairs and east along 1F's row 8")), - running(legs("round_the_sailor", {{EAST, 4}, {SOUTH, 1}, {EAST, 2}}, "to the sailor (18, 8), down a step, east to (19, 9): off the cabins' mats on row 10")), - running(route("below_ship_door", at(SS_ANNE_1F, 19, 2, NORTH), "up below the door to the gangway")), - step_into("off_the_ship", NORTH, "out onto the gangway"), - scene("ss_anne_sails", at(VERMILION, 23, 32, NORTH), "the ship sails; the player is walked off the pier to (23, 32)"), - running(route("vermilion_nurse", at(VERMILION_CENTER, 7, 4, NORTH), "back to the Pokemon Center")), - heal("heal_vermilion"), - }); -} - -} // namespace - - - const std::vector& story_checkpoints(){ - static const std::vector checkpoints{ - new_game(), after_sid_helper(), leave_house(), oaks_trigger(), to_starter_ball(), starter(), parcel(), pokedex(), shopping(), forest(), brock(), - running_shoes(), route3(), magikarp_salesman(), after_gift_rng(), magikarp_to_box(), mt_moon(), - route4_to_cerulean(), cerulean_gym(), misty(), cerulean_grind(), cerulean_rival(), nugget_bridge(), - route25(), bill(), back_to_cerulean(), oddish(), to_vermilion(), ss_anne(), - vermilion_gym(), to_lt_surge(), lt_surge(), bike_voucher(), route10_center(), rock_tunnel(), lavender_center(), - }; + static const std::vector checkpoints = load_authored_story(); return checkpoints; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h index 0e97ce995e..88dc3cea8c 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h @@ -6,9 +6,15 @@ * 3 to the Magikarp salesman in Route 4's Pokemon Center, through Mt. * Moon to Route 4 east of it, and on to Cerulean City, Misty, its rival, * Nugget Bridge and Bill, then Route 6 to Vermilion City, the S.S. - * Anne's HM01 and Lt. Surge, as story checkpoints 00.01 to 00.25, each - * saved at its end. After 00.10 every route and leg runs. Taken from a by-hand run on - * console B (2026-11-01) and the pokefirered map scripts. + * Anne's HM01 and Lt. Surge, on to Rock Tunnel and Lavender Town, as + * story checkpoints 00.01 to 00.29, each saved at its end. After 00.10 + * every route and leg runs. Taken from a by-hand run on console B + * (2026-11-01) and the pokefirered map scripts. + * + * Each checkpoint is an authored plan, StoryData/frlg-.plan.json + * (00.05: frlg-parcel-00.05.plan.json), built into the program and + * loaded by load_authored_story() (Data/JsonParser.h). Its sections, the + * notes on how each step was planned and why, live there too. * */ @@ -24,8 +30,8 @@ namespace NintendoSwitch{ namespace PokemonFRLG{ -// Checkpoints 00.01 to 00.25, in order, with 00.01b, the way back in after -// SID Helper, after 00.01, 00.04 split in two at Squirtle's ball for +// Checkpoints 00.01 to 00.29, in order of id, as authored in StoryData, +// with 00.01b, the way back in after SID Helper, after 00.01, 00.04 split in two at Squirtle's ball for // Starter RNG: 00.04 and 00.04b, 00.12b, the way back in after Gift RNG, // after 00.12, 00.12c, the Magikarp put in BOX 1, after it, 00.14b and // 00.14c, Cerulean Gym to a save below Misty's platform and Misty alone diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp deleted file mode 100644 index 6406c1e857..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* AutoStory Table: to Celadon - * - * From: https://github.com/PokemonAutomation/ - * - * Rows from 00.26 on, after the THUNDERBADGE: the BICYCLE, and the way - * east and south to Celadon City. - * - */ - -#include "PokemonFRLG_AutoStoryMart.h" -#include "PokemonFRLG_AutoStoryTableSteps.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonFRLG{ -namespace StoryTable{ - - -// The BIKE VOUCHER and the BICYCLE, from Vermilion's Center to -// Cerulean's (pokeguy 43:50 to 45:00). -// - The Pokemon Fan Club (VermilionCity_PokemonFanClub map.json, -// scripts.inc), in by Vermilion's (12, 17): the chairman (5, 4) sits -// above a table (5..6, 5..6) with the worker (6, 4) east of him, so he -// is talked to from (4, 4), west of him. "Did you come to hear about -// my POKEMON?" YES: his story, then the BIKE VOUCHER -// (FLAG_GOT_BIKE_VOUCHER). Oak's aide stands on Vermilion's (25, 7) -// since Lt. Surge, where the story's maps don't have him: the way -// planned from the Center's door goes east along his row 7, so the -// walk goes down to the water (14, 10) first, as 00.25 came up from -// it. From the Fan Club, up to (22, 10) first, so that the way on to -// Route 6 goes up column 22, west of him, not round by column 27. -// - Route 6 north, the trainers as 00.21 left them: Jeff (13, 33) -// beaten, Isabelle (13, 32) and Elijah (20, 25) not. As in 00.24, onto -// Jeff's row, west to the wall (7, 33) and up to row 31, above -// Isabelle's line; then east along row 31 to the wall (17, 31) and up -// column 16, west of Elijah's line (17..19, 25) and east of Keigo's -// (4..8, 16), to the ledge (16, 11). The first three legs are 00.24's, -// walked on console B; the two after end on walls, which the walker -// holds again after a wild battle in the grass. -// - The Underground Path north, Route 5's sides, which have no trainer, -// and Cerulean's south edge, whose way into the city without the -// burgled house is the tree (26, 32) (pokeguy 44:42). -// - The Bike Shop (CeruleanCity_BikeShop), in by (13, 28): the clerk -// (9, 3) is across the counter (8, 3), talked to from (7, 3). With the -// voucher he gives the BICYCLE (FLAG_GOT_BICYCLE) with no YES/NO; it -// isn't registered or ridden yet. -// - The Mart: 5 Repels (row 9, 350 each) and 2 Escape Ropes (row 8, -// 550 each; pokefirered items.json), 2850 in all. Console B had about -// 9000 after Lt. Surge (2026-11-03); the floor of 6000 leaves 3000 or -// more after them. -// - The nurse, and the save in Cerulean's Center. -StoryCheckpoint bike_voucher(){ - const MartPlan repels{ - .map = CERULEAN_MART, - .lines = {{.item = "repel", .quantity = 5}, {.item = "escape-rope", .quantity = 2}}, - .budget = 2850, - .money_floor = 6000, - }; - return row("00.26", "The BIKE VOUCHER from the Fan Club, back north to Cerulean, the BICYCLE, Repels and Escape Ropes", at(VERMILION_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - running(route("below_center", at(VERMILION, 14, 10), "out of the Center, down to the water (14, 10): off Oak's aide's row 7")), - running(route("beside_chairman", at(FAN_CLUB, 4, 4, EAST), "east along row 10, down to the Fan Club's door (12, 17), in, and up its west side beside the chairman (5, 4)")), - talk("chairman", "\"I chair the POKEMON Fan Club!\" ... \"Did you come to hear about my POKEMON?\""), - answer("hear_story", true, "YES: his story"), - scene("bike_voucher", at(FAN_CLUB, 4, 4, EAST), "the story of his Rapidash; the BIKE VOUCHER"), - running(route("west_of_oaks_aide", at(VERMILION, 22, 10), "out of the Fan Club, east along row 18 and up column 22 to (22, 10)")), - running(route("route6_south_path", at(ROUTE6, 11, 34, NORTH), "up column 22 west of Oak's aide (25, 7), out of the city onto Route 6's path, below Jeff's row")), - running(legs("round_isabelle_and_elijah", {{NORTH, 1}, {WEST, 4}, {NORTH, 2}, {EAST, 9}, {NORTH, 19}}, "onto Jeff's row (11, 33), west to the wall (7, 33), up to (7, 31), east along row 31 to the wall (16, 31), up column 16 to the ledge (16, 12): out of Isabelle's and Elijah's lines")), - running(route("below_cerulean_tree", at(CERULEAN, 26, 33, NORTH), "the Underground Path north, up Route 5's side into Cerulean, below the tree (26, 32)")), - cut_tree("cut_cerulean_tree", at(CERULEAN, 26, 32, NORTH), "the tree; a step onto its tile"), - running(route("bike_clerk", at(BIKE_SHOP, 7, 3, EAST), "west into the Bike Shop (13, 28), across the counter from the clerk (9, 3)")), - talk("talk_bike_clerk", "\"Oh, that's... A BIKE VOUCHER!\""), - scene("bicycle", at(BIKE_SHOP, 7, 3, EAST), "the voucher exchanged for a BICYCLE"), - running(route("mart_clerk", at(CERULEAN_MART, 4, 3, WEST), "out of the Bike Shop to the Mart's clerk")), - mart_step("buy_repels", "5 Repels and 2 Escape Ropes, 2850 at most", repels), - running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "out of the Mart to the Center")), - heal("heal_cerulean"), - }); -} - -// East out of Cerulean over Route 9 to Route 10's Pokemon Center -// (pokeguy 45:12 to 47:27), on foot: the Bicycle isn't registered yet. -// - Cerulean (CeruleanCity map.json): the way east, column 39..40 up -// from row 33 to rows 18..19 and out of the city's east edge, is -// reached from the city only through the tree (26, 32), grown back -// since 00.26 (its flag is a FLAG_TEMP), or the burgled house. Cut -// from above. Route 9 joins the city's rows 18..19 by its rows 8..9 -// (offset -10); its tree (2, 8) (FLAG_TEMP_12) closes them, (2, 9) -// being rock: cut from (1, 8), west of it. -// - Route 9 (Route9 map.json, scripts.inc), its ledges all facing south. -// Of its nine trainers, three can't be walked around, as in pokeguy's -// run, where Jeremy happened to look away: -// - Picnicker Alicia (14, 10) faces west, sight 3, over (11..13, 10), -// the only tiles above the ledge row 11 the way west goes down by: -// fought on the step onto (11, 10); she walks up to (12, 10). Then -// down the ledge (11, 11). -// - Hiker Jeremy (20, 17) looks north and west (FACE_UP_AND_LEFT), -// sight 4: north over (20, 13..16), every way east of column 19 -// below the rocks of row 12. Fought on (20, 13), waiting for him to -// look north (TRAINER_WAIT_VARIANT); he walks up to (20, 14). -// - Bug Catcher Conner (49, 7) faces east, sight 3, over (50..52, 7): -// the way north from row 13 is column 52 alone (the ledges of row -// 11 face south), and row 7 is the way onto the ridge. Fought on the -// step onto (52, 7); he walks up to (51, 7). -// - Hiker Brice wanders row 2 (50..56), looking west and east, sight -// 1, over (49..57, 2), and the way up from the ledges of row 4 is -// (50..51, 4) below him. The leg up column 51 ends on row 3 if he -// stands at (51, 2), else it goes on to (51, 1) past him, and he may -// see the player there; either way the legs to walls after it meet -// on (48, 1): west to the rock (48, 3) or (48, 1), up to (48, 1). -// Row 1 is clear of his sight; east along it to the rock (64, 1) -// and down column 64 past the hidden Rare Candy (63, 2), not picked -// up. A held leg down to (64, 3) would hop the ledge (64, 4) and go -// on, so the last tile is one step, then the hop. -// - Picnicker Caitlin (60, 9) faces east over (61..64, 9): the way to -// Route 10 goes along row 8, above her line, by legs to (70, 8), a -// tile short of the edge. Camper Chris (26, 7), -// Bug Catcher Brent (26, 1), Camper Drew (36, 6) and Hiker Alan (61, -// 16) are off the way; TM40 (12, 17) is left. -// - Route 10 (Route10 map.json) joins Route 9's rows 8..10 by its own; -// its north part, down to the Center's door (13, 20), has no trainer -// on the way and its grass is walked round. The trees west of the -// door (10, 20..26) stay. The walk there ends its open legs where a -// tile of error can't reach a blocker, and finds its place again on -// walls (console B, 2026-11-09, two runs "no fade" at the door): the -// step onto Route 10 is not always held up, so the player may stand a -// tile further east, and the leg along row 11 holds to the wall -// (17, 11) instead of ending on (16, 11) by count. The 12 tiles down -// column 16 may end a tile short or long: from rows 22..29 a tile west -// and up to the Center's wall (15, 20) all end on (15, 21). West along -// row 21 to the wall (9, 21) ends on (10, 21), three west of the -// door's column: the only count no wall after it checks. -// - The nurse, and the save in Route 10's Center: the save card names -// ROUTE 10. -StoryCheckpoint route10_center(){ - return row("00.27", "East over Route 9 to Route 10's Pokemon Center: the two trees, Alicia, Jeremy and Conner", at(CERULEAN_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - running(route("above_cerulean_tree", at(CERULEAN, 26, 31, SOUTH), "out of the Center, south through the city to above the tree (26, 32), grown back")), - cut_tree("cut_cerulean_tree", at(CERULEAN, 26, 32, SOUTH), "the tree; a step onto its tile"), - running(route("west_of_route9_tree", at(ROUTE9, 1, 8, EAST), "down to row 33, east and up column 39 or 40 to row 18, east out of the city onto Route 9, west of its tree (2, 8)")), - cut_tree("cut_route9_tree", at(ROUTE9, 2, 8, EAST), "Route 9's tree; a step onto its tile"), - running(route("above_alicia_line", at(ROUTE9, 11, 9, SOUTH), "east to (11, 9), above the west end of Alicia's line")), - into_line("alicia_line", SOUTH, "onto (11, 10), in Alicia's line: she walks up to (12, 10)"), - battle("picnicker_alicia", at(ROUTE9, 11, 10, EAST), false, "Oddish, Bellsprout, Oddish and Bellsprout, Lv18"), - hop("alicia_ledge_hop", SOUTH, at(ROUTE9, 11, 12, SOUTH), "down the ledge (11, 11)"), - running(route("west_of_jeremy_sight", at(ROUTE9, 19, 13, EAST), "down to row 13 and east along it to (19, 13), west of Jeremy's north line")), - into_sight("jeremy_sight", EAST, "onto (20, 13), four above Jeremy (20, 17): when he looks north he walks up to (20, 14)"), - battle("hiker_jeremy", at(ROUTE9, 20, 13, SOUTH), false, "Machop and Onix, Lv20"), - running(route("below_conner_line", at(ROUTE9, 52, 8, NORTH), "east along row 13 and up column 52 to (52, 8), below the east end of Conner's line")), - into_line("conner_line", NORTH, "onto (52, 7), the end of Conner's line: he walks up to (51, 7)"), - battle("bug_catcher_conner", at(ROUTE9, 52, 7, WEST), false, "Caterpie, Weedle and Venonat, Lv20"), - running(legs("round_brice", {{NORTH, 2}, {WEST, 1}, {NORTH, 2}, {WEST, 3}, {NORTH, 2}, {EAST, 16}, {SOUTH, 1}}, "up to the ledge (52, 4), up column 51 to Brice's row, west to the rock (48, 3) (or (48, 1)), up to (48, 1), east along row 1 to the rock (64, 1), down to (64, 2)")), - step_onto("above_east_ledge", SOUTH, at(ROUTE9, 64, 3, SOUTH), "onto (64, 3), above the ledge (64, 4)"), - hop("east_ledge_hop", SOUTH, at(ROUTE9, 64, 5, SOUTH), "down the ledge (64, 4)"), - running(legs("above_caitlin_line", {{SOUTH, 3}, {EAST, 6}}, "down column 64 to row 8, east along it to (70, 8): above Caitlin's line (61..64, 9)")), - running(route("route10_ledge_end", at(ROUTE10, 17, 11, EAST), "east onto Route 10, down to row 11 west of the grass and east along it to the wall (17, 11), above the fence post (17, 12)")), - running(legs("below_center_door", {{WEST, 1}, {SOUTH, 12}, {WEST, 1}, {NORTH, 2}, {WEST, 5}, {EAST, 3}}, "into the ledge's gap (16, 11), down column 16 to row 23, a tile west, up to the Center's wall (15, 21), west along row 21 to the wall (10, 21), east to (13, 21), below the door")), - running(route("route10_nurse", at(ROUTE10_CENTER, 7, 4, NORTH), "up onto the Center's door (13, 20), to the nurse")), - heal("heal_route10"), - }); -} - - -} // namespace StoryTable -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableLavender.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableLavender.cpp deleted file mode 100644 index 9ba3bf882d..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableLavender.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* AutoStory Table: to Lavender Town - * - * From: https://github.com/PokemonAutomation/ - * - * Rows from 00.28 on: through Rock Tunnel, without Flash, and down Route - * 10 to Lavender Town's Pokemon Center. - * - */ - -#include "PokemonFRLG_AutoStoryTableSteps.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonFRLG{ -namespace StoryTable{ - - -// From Route 10's Pokemon Center through Rock Tunnel (pokefirered -// RockTunnel_1F, RockTunnel_B1F map.json and scripts.inc) to Route 10 -// south of it. The save is on Route 10 by the tunnel's south mouth: the -// save card's places don't include Rock Tunnel. -// - The tunnel's north mouth (8, 19) is reached from the Center through -// the tree (10, 20), cut from below; Heidi (9, 27) sees rows 28..29 -// below the trees and is left. Two single steps west then put the -// player below the mouth on (8, 20). -// - Every floor tile of the tunnel has wild encounters, which are run -// from (the table has no step that uses a Repel), so the legs end on -// walls, ladders or someone standing still, which the walker holds -// again after a battle. An open leg (two tiles or more, not to a wall) -// is followed only by legs a tile of error in it can't send astray. -// - Nine trainers stand where the way can't go round them. The others -// (1F Lucas, Oliver, Ariana, Leah; B1F Eric, Steve) are walked round. -// - 1F Ashton (27, 10) looks west over (24..26, 10), the only way south -// from the mouth's room: stepped into from (24, 9). -// - B1F Winston (29, 35) looks south over the south passage (29, -// 36..38): stepped into from (32, 37), after the leg down column 33 -// to the wall and a tile back. -// - B1F Martha (15, 33) looks east over (16..17, 33), the corridor -// north: stepped into from (17, 34), the wall east of row 34. -// - B1F Allen (36, 11) looks south over (36, 12..14), the way west to -// the ladder (33, 3): stepped into from (38, 12), below the wall. -// - 1F Lenny (7, 5) looks south over (7, 6..9), the way east from the -// ladder (4, 2): stepped into from (6, 9), the end of column 6. -// The way on to the ladder (20, 13) keeps west of Lucas's line (16..19, -// 17): down column 11, up column 14 to row 16. -// - B1F Sofia (11, 15) looks south over (11, 16..19), the way west: -// stepped into along row 16 from (12..14, 16). -// - B1F Dudley (5, 11) looks south over (5, 12..15): stepped into from -// (8, 15) along row 15. -// - B1F Cooper (2, 6) looks around (sight 2) by the ladder (2, 3): the -// leg up column 1 to the wall (1, 2) passes him on (1, 6); if he -// looks west, story_interrupts() fights him and the leg is held on -// to its wall, as Mt. Moon's Grunt in 00.13. -// - 1F Dana (26, 28) looks south over (26, 29..31), the only way west -// to the south mouth: stepped into along row 31 from (27..29, 31). -// - Unverified on the console: Rock Tunnel is dark without Flash, and -// the overworld, fade, dialog and battle detectors haven't been -// measured under its darkness (~/FRLGLab/notes/2026-11-09-0028-dark- -// measure.md). Every step from rock_tunnel_1f to route10_south depends -// on them. -StoryCheckpoint rock_tunnel(){ - return row("00.28", "Through Rock Tunnel: Ashton, Winston, Martha, Allen, Lenny, Sofia, Dudley, Cooper and Dana", at(ROUTE10_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - running(route("below_route10_tree", at(ROUTE10, 10, 21, NORTH), "out of the Center (13, 20), west along row 21 to the wall (9, 21), below the tree (10, 20)")), - cut_tree("cut_route10_tree", at(ROUTE10, 10, 20, NORTH), "the tree; a step onto its tile"), - step_onto("west_of_tree", WEST, at(ROUTE10, 9, 20, WEST), "a step west along row 20"), - step_onto("below_rock_tunnel", WEST, at(ROUTE10, 8, 20, WEST), "a step west, below the mouth (8, 19)"), - running(route("rock_tunnel_1f", at(ROCK_TUNNEL_1F, 17, 2, NORTH), "north into Rock Tunnel")), - running(legs("above_ashton_line", {{SOUTH, 6}, {EAST, 8}, {SOUTH, 1}, {WEST, 1}}, "down column 17 to the wall (17, 8), east along row 8 to (25, 8), onto row 9 and west to the wall (24, 9)")), - into_line("ashton_line", SOUTH, "onto (24, 10), in Ashton's line: he walks up to (25, 10)"), - battle("pokemaniac_ashton", at(ROCK_TUNNEL_1F, 24, 10, EAST), false, "Slowpoke, Slowpoke and Slowpoke"), - running(route("b1f_south_ladder", at(ROCK_TUNNEL_B1F, 38, 28, NORTH), "east along row 12 and up column 45 to the ladder (45, 2), down to B1F")), - running(legs("winston_line_end", {{WEST, 5}, {SOUTH, 10}, {NORTH, 1}, {WEST, 1}}, "west to the wall (33, 28), down column 33 to the wall (33, 38), up to (33, 37) and west to (32, 37)")), - into_line("winston_line", WEST, "along row 37 onto (29, 37), the end of Winston's line: he walks down to (29, 36)"), - battle("pokemaniac_winston", at(ROCK_TUNNEL_B1F, 29, 37, NORTH), false, "Slowpoke and Slowpoke"), - // The corner (11, 35), wall north and rock west, finds the player - // from any of (11..14, 35) after a battle; east to the wall (19, - // 35) did not (console B, 2026-11-09 13:35: found 5 tiles off). - running(legs("below_martha_line", {{WEST, 16}, {NORTH, 2}, {WEST, 2}, {EAST, 5}}, "west along row 37 to (13, 37), up to the wall (13, 35), west to the corner (11, 35), east to (16, 35)")), - into_line("martha_line", NORTH, "up column 16 onto (16, 33), beside Martha; from (17, 35), a tile short, up column 17 onto (17, 33), the end of her line"), - battle("picnicker_martha", at(ROCK_TUNNEL_B1F, 16, 33, WEST), false, "Oddish and Bulbasaur"), - running(legs("below_allen_line", {{EAST, 1}, {NORTH, 5}, {EAST, 12}, {NORTH, 9}, {EAST, 11}, {NORTH, 2}, {WEST, 2}, {NORTH, 5}}, "east to the wall (17, 33), up column 17 to the wall (17, 28), east to the wall (29, 28), up column 29 past Steve's line to the wall (29, 19), east to (40, 19), up to row 17, west to the wall (38, 17) and up to the wall (38, 12)")), - into_line("allen_line", WEST, "along row 12 onto (36, 12), below Allen"), - battle("hiker_allen", at(ROCK_TUNNEL_B1F, 36, 12, NORTH), false, "Geodude, Geodude and Graveler"), - running(legs("west_of_b1f_ladder", {{WEST, 4}, {NORTH, 10}}, "west along row 12 to the wall (32, 12), up column 32 to the wall (32, 2)")), - running(route("1f_west_ladder", at(ROCK_TUNNEL_1F, 4, 2, NORTH), "onto the ladder (33, 3), up to 1F")), - running(legs("above_lenny_line", {{EAST, 2}, {SOUTH, 7}}, "east to the wall (6, 2), down column 6 to the wall (6, 9)")), - into_line("lenny_line", EAST, "onto (7, 9), the end of Lenny's line: he walks down to (7, 8)"), - battle("hiker_lenny", at(ROCK_TUNNEL_1F, 7, 9, NORTH), false, "Machop and Onix"), - running(legs("west_of_lucas_line", {{EAST, 4}, {SOUTH, 15}, {EAST, 7}, {WEST, 4}, {NORTH, 8}, {EAST, 6}}, "east to the wall (11, 9), down column 11 to the wall (11, 24), east to the wall (18, 24), west to (14, 24), up column 14 to the wall (14, 16), east to the wall (20, 16)")), - running(route("b1f_middle_ladder", at(ROCK_TUNNEL_B1F, 27, 12, NORTH), "up column 20 onto the ladder (20, 13), down to B1F")), - running(legs("east_of_sofia_line", {{SOUTH, 3}, {WEST, 12}, {SOUTH, 1}, {WEST, 2}}, "down to the wall (27, 15), west along row 15 to the wall (15, 15), down to row 16 and west to (13, 16)")), - into_line("sofia_line", WEST, "along row 16 onto (11, 16), below Sofia"), - battle("picnicker_sofia", at(ROCK_TUNNEL_B1F, 11, 16, NORTH), false, "Pidgey, Pidgey and Pidgeotto"), - running(legs("east_of_dudley_line", {{WEST, 3}, {NORTH, 1}}, "west to the wall (8, 16), up to (8, 15)")), - into_line("dudley_line", WEST, "along row 15 onto (5, 15), the end of Dudley's line: he walks down to (5, 14)"), - battle("hiker_dudley", at(ROCK_TUNNEL_B1F, 5, 15, NORTH), false, "Geodude, Geodude and Graveler"), - running(legs("past_cooper", {{WEST, 4}, {NORTH, 13}, {SOUTH, 1}}, "west to the wall (1, 15), up column 1 past Cooper (2, 6) to the wall (1, 2), back to (1, 3)")), - running(route("1f_east_ladder", at(ROCK_TUNNEL_1F, 45, 21, NORTH), "east onto the ladder (2, 3), up to 1F")), - running(legs("above_dana_line", { - {WEST, 6}, {SOUTH, 5}, {EAST, 6}, {WEST, 1}, {SOUTH, 4}, {WEST, 5}, {SOUTH, 5}, - {WEST, 2}, {NORTH, 3}, {WEST, 6}, {NORTH, 3}, {WEST, 3}, {SOUTH, 2}, - }, "west to the wall (39, 21), down column 39 to Ariana (39, 27), east to the wall (45, 26), down column 44 to the wall (44, 30), west to the wall (39, 30), down to the wall (39, 35), west to (37, 35), up to the wall (37, 32), west to the wall (31, 32), up to (31, 29), west to (28, 29) and down to the wall (28, 31)")), - into_line("dana_line", WEST, "along row 31 onto (26, 31), the end of Dana's line: she walks down to (26, 30)"), - battle("picnicker_dana", at(ROCK_TUNNEL_1F, 26, 31, NORTH), false, "Meowth, Oddish and Pidgey"), - running(legs("above_south_mouth", {{WEST, 6}, {SOUTH, 1}, {WEST, 8}, {SOUTH, 5}, {EAST, 2}, {NORTH, 1}, {EAST, 5}, {WEST, 1}}, "west along row 31 to (20, 31), onto row 32 and west to the wall (12, 32), down column 12 to the wall (12, 37), east to the wall (14, 37), up to (14, 36), east to the wall (19, 36), back to (18, 36)")), - running(route("route10_south", at(ROUTE10, 8, 58, SOUTH), "onto the mouth (18, 37), out onto Route 10")), - }); -} - -// Route 10 south of Rock Tunnel (Route10 map.json) to Lavender Town's -// Pokemon Center. It has no grass. Carol (7, 60) and Clark (4, 62) look -// around, Trent (4, 68) looks west and Herman wanders row 70 (13..17), -// looking west and east (sight 2, to x 19): the way keeps to the east -// side, columns 17..21, and crosses row 70 on column 21. The ledges of -// row 62 leave a gap at (18, 62); row 66's leaves (21, 66). Row 74's -// ledge (11, 74) is hopped, and the hold south goes on over the -// connection into Lavender Town to the wall (11, 9). West of the -// Center's building, the wall (8, 9) sets the door's column. -StoryCheckpoint lavender_center(){ - return row("00.29", "Route 10 south to Lavender Town's Pokemon Center", at(ROUTE10, 8, 58, SOUTH), after_brock(), StartMenu::FULL, { - running(legs("route10_east_side", {{EAST, 10}, {NORTH, 2}, {WEST, 1}, {EAST, 1}, {SOUTH, 8}, {EAST, 3}, {SOUTH, 9}, {WEST, 10}}, "east along row 58 to (18, 58), up to the wall (18, 56), west to the wall (17, 56), back to (18, 56), down through the ledges' gap to (18, 64), east to the wall (21, 64), down column 21 to the wall (21, 73), west along row 73 to the wall (11, 73)")), - hop("route10_ledge_hop", SOUTH, at(ROUTE10, 11, 75, SOUTH), "down the ledge (11, 74)"), - running(route("lavender_north_wall", at(LAVENDER, 11, 8, SOUTH), "down column 11 over the connection into Lavender Town, to the wall (11, 9)")), - running(legs("below_center_door", {{WEST, 6}, {SOUTH, 1}, {EAST, 2}, {WEST, 1}}, "west along row 8 to (5, 8), onto row 9, east to the wall (7, 9), west to (6, 9)")), - running(route("lavender_nurse", at(LAVENDER_CENTER, 7, 4, NORTH), "up column 6 onto the Center's door (6, 5), to the nurse")), - heal("heal_lavender"), - }); -} - - -} // namespace StoryTable -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h index d0a314a2ac..53ce8fcaf4 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h @@ -184,24 +184,6 @@ StoryStep options_step(); StoryStep cut_tree(std::string name, Pokemon::Pose tree, std::string why); -// Rows in files of their own (PokemonFRLG_AutoStoryTableVermilionGym.cpp, -// PokemonFRLG_AutoStoryTableCeladon.cpp). -StoryCheckpoint vermilion_gym(); -// Route navigation step towards Lt. Surge battle. -StoryCheckpoint to_lt_surge(); -// Lt. Surge gym leader battle checkpoint. -StoryCheckpoint lt_surge(); -// Bike Voucher retrieval checkpoint in Vermilion City Fan Club. -StoryCheckpoint bike_voucher(); -// Route 10 Pokemon Center navigation checkpoint. -StoryCheckpoint route10_center(); -// Through Rock Tunnel to Route 10 south of it (PokemonFRLG_AutoStoryTableLavender.cpp). -StoryCheckpoint rock_tunnel(); -// Route 10 south to Lavender Town's Pokemon Center. -StoryCheckpoint lavender_center(); - - - } // namespace StoryTable } // namespace PokemonFRLG } // namespace NintendoSwitch diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableVermilionGym.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableVermilionGym.cpp deleted file mode 100644 index 6c8f3d653f..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableVermilionGym.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* AutoStory Table: Vermilion Gym - * - * From: https://github.com/PokemonAutomation/ - * - * Rows 00.23 to 00.25: HM01 Cut to the Oddish, the tree before Vermilion - * Gym, its three trainers and its trash cans, then Lt. Surge from a save - * in front of him. - * - * The gym (VermilionCity_Gym map.json, scripts.inc), 11 x 20, entered on - * (5, 19): the cans on rows 10, 12 and 14 at x 1, 3, 5, 7, 9 - * (PokemonFRLG_AutoStoryTrashCans.h), the beams across rows 6 and 7 that - * the two switches turn off, Lt. Surge (5, 2) facing down above them. Its - * trainers, sight 3: - * - Sailor Dwayne (8, 13) and Engineer Baily (2, 11) look around - * (MOVEMENT_TYPE_LOOK_AROUND): between them they see across rows 11 and - * 13 and down columns 2 and 8, so no way from the door to the cans - * keeps out of their sight. Each is fought from the second tile of his - * line, stepped onto from beside it, out of sight, and waited on there - * until he turns to the player (TRAINER_WAIT_VARIANT): Dwayne from - * (8, 15), stepped onto from (7, 15), Baily from (2, 13), from (3, 13). - * Each walks up a tile, Dwayne to (8, 14), Baily to (2, 12), and stays - * there while the player is in the gym. - * - Gentleman Tucker (7, 8) faces west over (4..6, 8): fought on the step - * up from (5, 9) onto (5, 8); he walks up to (6, 8). - * The cans' stands, on rows 9, 11 and 13 above each, are clear of where - * the three stand after their battles. Leaving the gym puts them back - * where the map has them, beaten, and grows the tree back. - * - */ - -#include "PokemonFRLG_AutoStoryBattleData.h" -#include "PokemonFRLG_AutoStoryGrind.h" -#include "PokemonFRLG_AutoStoryTableSteps.h" -#include "PokemonFRLG_AutoStoryTeach.h" -#include "PokemonFRLG_AutoStoryTrashCans.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonFRLG{ -namespace StoryTable{ - - -namespace{ - -// The search for the switches (PokemonFRLG_AutoStoryTrashCans.h). It -// ends at the last can's stand, which the step's Outcome says. -StoryStep trash_cans(std::string name, std::string why){ - StoryStep step = make(StoryStepKind::GAME, std::move(name), std::move(why)); - step.variant = TRASH_CANS_VARIANT; - return step; -} - -// `step`, skipped when the run flag `flag` is set. -StoryStep unless(const char* flag, StoryStep step){ - step.skip_if.emplace_back(flag); - return step; -} - -} // namespace - - -// HM01 Cut, from the S.S. Anne (00.22), to the Oddish (00.20), second in -// the party: a wild Oddish of Lv12 to 14 knows 2 or 3 moves (Absorb, -// Sweet Scent, Poison Powder at 14), so it has room. TM28 Dig, from the -// Cerulean Rocket (00.21), to the lead in place of Withdraw: Ground, twice -// as strong on Lt. Surge's three, it takes the sim's chance against him -// for a Lv28 Wartortle with 14 Potions from 6% to 47%. Vermilion's tree -// (19, 24) (EventScript_CutTree) is the only way into the gym's yard, -// row 24 being walls but for it; the gym's door is (14, 25). In the gym, -// the trainers as above, then the trash cans until the door opens; out, -// the tree cut again from below, and back to the nurse. -StoryCheckpoint vermilion_gym(){ - const TeachPlan cut{.item = "hm01", .move = "cut", .member = 1}; - const TeachPlan dig{.item = "tm28", .move = "dig", .member = 0, .forget_move = "withdraw"}; - return row("00.23", "Vermilion Gym: Cut and Dig, the tree, Dwayne, Baily and Tucker, the trash cans", at(VERMILION_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - teach_step("teach_cut", "HM01 Cut to the Oddish, second in the party, unless it knows it", cut), - teach_step("teach_dig", "TM28 Dig in place of Withdraw, for Lt. Surge, unless the lead knows it or there is no TM28", dig), - running(route("above_gym_tree", at(VERMILION, 19, 23, SOUTH), "out of the Center, south through the city to above the tree (19, 24)")), - cut_tree("cut_gym_tree", at(VERMILION, 19, 24, SOUTH), "the Oddish cuts the tree; a step onto its tile"), - running(route("gym_door", at(VERMILION_GYM, 5, 19, NORTH), "down into the yard and in by the gym's door (14, 25)")), - running(legs("west_of_dwayne_line", {{NORTH, 4}, {EAST, 2}}, "up column 5 to the can (5, 14), east to (7, 15): out of Dwayne's and Baily's sight")), - into_sight("dwayne_sight", EAST, "onto (8, 15), two below Dwayne (8, 13): he turns to the player and walks up to (8, 14)"), - battle("sailor_dwayne", at(VERMILION_GYM, 8, 15, NORTH), false, "two Pikachu, Lv21"), - running(legs("east_of_baily_line", {{WEST, 4}, {NORTH, 2}, {WEST, 1}}, "west along row 15 to (4, 15), up to (4, 13), west to (3, 13): out of Baily's sight")), - into_sight("baily_sight", WEST, "onto (2, 13), two below Baily (2, 11): he turns to the player and walks up to (2, 12)"), - battle("engineer_baily", at(VERMILION_GYM, 2, 13, NORTH), false, "Voltorb and Magnemite, Lv21"), - running(legs("below_tucker_line", {{EAST, 2}, {NORTH, 4}, {EAST, 1}}, "east to (4, 13), up column 4 past Baily's row to (4, 9), east to (5, 9): below Tucker's line, not on it")), - into_line("tucker_line", NORTH, "onto (5, 8), in Tucker's line: he walks up to (6, 8)"), - battle("gentleman_tucker", at(VERMILION_GYM, 5, 8, EAST), false, "Pikachu, Lv23"), - trash_cans("trash_cans", "the cans until both locks open; a wrong second can resets them"), - running(route("below_gym_tree", at(VERMILION, 19, 25, NORTH), "out of the gym, east to below the tree, grown back")), - cut_tree("cut_tree_out", at(VERMILION, 19, 24, NORTH), "the tree again; a step onto its tile"), - running(route("vermilion_nurse", at(VERMILION_CENTER, 7, 4, NORTH), "north through the city to the Pokemon Center")), - heal("heal_vermilion"), - }); -} - -// Wild battles on Route 6 until the sim beats Lt. Surge half the time: -// with Dig, 47% at Lv28 and 51% at Lv29, so a lead the gym's trainers -// have brought to Lv29 passes at once, and a Lv28 one after a level. The grass nearest the city is Route 6's -// south patch (13..16, 30..33); Picnicker Isabelle (13, 32) faces west -// over (10..12, 32), on the way north from the city, so the walk goes -// along Jeff's row 33 (he is beaten) to the wall (7, 33), up to row 31 -// and east along it onto the patch's west edge, (13, 31), above -// Isabelle, out of her line. Back to the nurse the same way. -// All that is skipped when the lead, read in the Center, passes already. -// Then the tree, the gym, whose trainers stand where the map has them, -// beaten, and whose beams stay off (FLAG_FOUND_BOTH_VERMILION_GYM_SWITCHES): -// up column 5 to below Lt. Surge, and saved there. -StoryCheckpoint to_lt_surge(){ - constexpr const char* SURGE_WINNABLE = "surge_winnable"; - const GrindPlan for_surge{ - .member = 0, .level = 32, .max_encounters = 150, .pace = EAST, .heal_every = 0, - .trainer = std::string(story_trainer(VERMILION_GYM, "lt_surge").value_or("")), .min_chance = MIN_WIN_CHANCE, - }; - return row("00.24", "Route 6's grass until Lt. Surge is winnable, then through the open beams to him", at(VERMILION_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { - grind_check_step(SURGE_WINNABLE, "the lead as it is: if the sim beats Lt. Surge 50% or more already, no trip to Route 6", for_surge), - unless(SURGE_WINNABLE, running(route("route6_south_path", at(ROUTE6, 11, 34, NORTH), "out of the Center, north out of the city onto Route 6's path, below Jeff's row"))), - unless(SURGE_WINNABLE, running(legs("round_isabelle_line", {{NORTH, 1}, {WEST, 4}, {NORTH, 2}, {EAST, 5}, {EAST, 1}}, "onto Jeff's row (11, 33), west to the wall (7, 33), up to (7, 31), east along row 31 to (12, 31) and a step onto the grass, (13, 31): out of Isabelle's line"))), - unless(SURGE_WINNABLE, grind_step("grind_for_surge", "wild battles until the sim beats Lt. Surge 50% or more, Lv32 at most; the nurse when hurt or low on PP", for_surge, at(VERMILION_CENTER, 7, 4, NORTH))), - unless(SURGE_WINNABLE, running(route("west_on_row_31", at(ROUTE6, 7, 31), "west along row 31, out of the grass, to the wall (7, 31)"))), - unless(SURGE_WINNABLE, running(legs("down_to_jeff_row", {{SOUTH, 2}, {EAST, 4}}, "down to the wall (7, 33), east along Jeff's row to (11, 33): out of Isabelle's line"))), - unless(SURGE_WINNABLE, running(route("vermilion_nurse", at(VERMILION_CENTER, 7, 4, NORTH), "south into the city to the Pokemon Center"))), - unless(SURGE_WINNABLE, heal("heal_vermilion")), - running(route("above_gym_tree", at(VERMILION, 19, 23, SOUTH), "out of the Center, south through the city to above the tree (19, 24)")), - cut_tree("cut_gym_tree", at(VERMILION, 19, 24, SOUTH), "the tree; a step onto its tile"), - running(route("below_lt_surge", at(VERMILION_GYM, 5, 3, NORTH), "into the gym, up round the cans and through the beams, off, to below Surge (5, 2)")), - }); -} - -// Lt. Surge (5, 2), talked to from below, from the save in front of him: -// Voltorb Lv21, Pikachu Lv18 and Raichu Lv24, Shock Wave and Thunder -// Wave, a Super Potion and a Full Heal (sParty_LeaderLtSurge); then the -// THUNDERBADGE and TM34, read to the overworld with the battle's text. -// Out, the tree, and the nurse. Surge's win also puts Oak's aide on -// Vermilion City's (25, 7) (VermilionCity_Gym_EventScript_ShowOaksAide), -// where he stays until talked to; the story's maps don't have him. The -// way to the nurse along row 7 runs into him (console B, 2026-11-03: the -// leg west stopped beside him and the step into the Center's door went -// into a house), so it goes along row 10 to the water instead, clear of -// him, and up column 14 from there. -StoryCheckpoint lt_surge(){ - return row("00.25", "Vermilion Gym: Lt. Surge, the THUNDERBADGE, then the Center", at(VERMILION_GYM, 5, 3, NORTH), after_brock(), StartMenu::FULL, { - talk("challenge_lt_surge", "\"Hey, kid! What do you think you're doing here?\""), - battle("lt_surge", at(VERMILION_GYM, 5, 3, NORTH), false, "Voltorb, Pikachu and Raichu; THUNDERBADGE and TM34"), - running(route("below_gym_tree", at(VERMILION, 19, 25, NORTH), "down through the beams, out of the gym, east to below the tree, grown back")), - cut_tree("cut_tree_out", at(VERMILION, 19, 24, NORTH), "the tree; a step onto its tile"), - running(route("below_oaks_aide", at(VERMILION, 14, 10), "north through the city and west along row 10 to the water, (14, 10): off Oak's aide's row 7")), - running(route("vermilion_nurse", at(VERMILION_CENTER, 7, 4, NORTH), "up to the Pokemon Center")), - heal("heal_vermilion"), - }); -} - - -} // namespace StoryTable -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md index 55a5b23722..ae63e57e6c 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md @@ -1,4 +1,15 @@ -# FRLG authored checkpoint pilot +# FRLG authored checkpoints + +Every story checkpoint is an authored plan here, one file per checkpoint: `frlg-.plan.json`, and `frlg-parcel-00.05.plan.json` for 00.05. `story_checkpoints()` is `load_authored_story()`: every embedded `*.plan` file, in order of id. There are no C++ row definitions; the files were exported once from the C++ table, and `ReplayData/frlg-story-sim-trace.json` keeps the whole story's sim trace from before that (`PokemonFRLG::AutoStory - Authored` tests it). + +- A checkpoint has `sections: [{id, title, notes?, events}]`, or plain `events` (one unnamed section, as 00.05 keeps so its bytes stay those PokemonSimulation pins). Events are numbered across the sections, and an event's log line names its section: `[Event] 00.28/12 [b1f_east] winston_line: start`. +- `notes` (a list of paragraphs) on the checkpoint, a section or an event hold what the C++ table's comments said: the maps, trainers, numbers and console runs a step was planned from. No executor reads them. +- `variant` says how FRLG does a step: `run` on a route or legs, `trainer_line`/`trainer_wait` on a step, and on a `game_action` the step's plan as its parser reads it (`mart ...`, `teach ...`, `grind ...`, `grind_check ...`, `capture ...`, `pc ...`, `fossil ...`, `level ...`, or `cut_tree`, `trash_cans`, `close_trainer_card`, `leave_gift_summary`). An unknown variant is rejected at load. +- `once_per_save` marks a step done once per save (a TEACH, a Mart). +- `execution` holds FRLG's fields: `menu`, and when not a new game's, `supplies` (`potions`, `antidotes`, `parlyz_heals`, `lead: "after_brock"`), `live_start` and `handoff` (`sid_helper`, `starter_rng`, `gift_rng`). +- Unknown fields anywhere are rejected. + +## The Parcel pilot `frlg-parcel-00.05.plan.json` is the production definition of checkpoint `00.05`. The story table loads it with `load_authored_checkpoint()`: there is no second C++ Parcel event list. Its `pokesim-authored-checkpoint/1` contract contains the common checkpoint plus FRLG executor configuration (`execution.menu`). It has no export revision, bookkeeping snapshot, or claimed observation. diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 697a9e8f60..bb3b4cf855 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1975,11 +1975,8 @@ file(GLOB LIBRARY_SOURCES Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h - Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp - Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableLavender.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h - Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableVermilionGym.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach_Tests.cpp From 3dcf3e8a231fce9935de5c60729272fa5347f52e Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 18:30:04 -0800 Subject: [PATCH 3/5] FRLG AutoStory: code health for the authored story tests Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../AutoStory/Data/AuthoredStory_Tests.cpp | 34 ++++++++++++++----- .../AutoStory/PokemonFRLG_AutoStoryEvents.cpp | 2 -- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp index 8bc8b352a9..35cb1317c6 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/AuthoredStory_Tests.cpp @@ -230,33 +230,51 @@ UnitTestResult test_trace_golden(){ return true; } -struct AuthoredCase{ +struct AuthoredInput{ std::string name; UnitTestResult (*body)(); }; +struct AuthoredExpectation{ + std::string description; +}; + +struct AuthoredTestCase{ + AuthoredInput input; + AuthoredExpectation expectation; +}; + +const AuthoredTestCase AUTHORED_CASES[] = { + {.input = {.name = "the story sim's trace is the golden one", .body = test_trace_golden}, + .expectation = {.description = "the trace the C++ rows gave"}}, + {.input = {.name = "malformed plans are rejected", .body = test_rejects}, + .expectation = {.description = "a ParseException for each change"}}, + {.input = {.name = "Rock Tunnel's sections", .body = test_rock_tunnel_sections}, + .expectation = {.description = "00.28 split by area, sections in its log lines"}}, +}; + class AuthoredTest : public UnitTest{ public: - explicit AuthoredTest(AuthoredCase test) - : UnitTest("PokemonFRLG::AutoStory - Authored: " + test.name), m_test(std::move(test)){} + explicit AuthoredTest(AuthoredTestCase test) + : UnitTest("PokemonFRLG::AutoStory - Authored: " + test.input.name), m_test(std::move(test)){} virtual UnitTestResult run(Logger&, CancellableScope&) const override{ try{ - return m_test.body(); + return m_test.input.body(); }catch (Exception& e){ return std::string(e.name()) + ": " + e.message(); } } private: - AuthoredCase m_test; + AuthoredTestCase m_test; }; } // namespace void add_tests_AuthoredStory(UnitTestDatabase& database){ - database.add(AuthoredCase{"the story sim's trace is the golden one", test_trace_golden}); - database.add(AuthoredCase{"malformed plans are rejected", test_rejects}); - database.add(AuthoredCase{"Rock Tunnel's sections", test_rock_tunnel_sections}); + for (const AuthoredTestCase& test : AUTHORED_CASES){ + database.add(test); + } } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp index 97e0addd98..df858d3c49 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp @@ -826,8 +826,6 @@ StoryCheckpoint joined_rows(const StoryCheckpoint& first, const StoryCheckpoint& row.live_start = first.live_start; row.steps = first.steps; row.steps.insert(row.steps.end(), second.steps.begin(), second.steps.end()); - row.notes = first.notes; - row.notes.insert(row.notes.end(), second.notes.begin(), second.notes.end()); row.sections = first.sections; row.sections.insert(row.sections.end(), second.sections.begin(), second.sections.end()); return row; From 43d1e50eb36002e34a105716fd465ce0e320faef Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 21:12:45 -0800 Subject: [PATCH 4/5] Story JSON: keep story_step_json() and event_log_line() within code health Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../Pokemon/AutoStory/Pokemon_StoryLog.h | 5 +-- .../AutoStory/Pokemon_StoryStepJson.cpp | 34 ++++++++++++------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryLog.h b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryLog.h index 474603b51e..099626610a 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryLog.h +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryLog.h @@ -28,10 +28,7 @@ namespace Pokemon{ // [Event] 00.04/3 talk_mom: failed: expect_failed: has_parcel // An event in a section of the checkpoint names it before the event: // [Event] 00.28/12 [b1f_east] winston_line: start -std::string event_log_line( - std::string_view checkpoint, size_t number, std::string_view event, std::string_view status, - std::string_view section = {} -); +std::string event_log_line(std::string_view checkpoint, size_t number, std::string_view event, std::string_view status, std::string_view section = {}); // A checkpoint starts: // [Checkpoint] 00.04: start diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp index 9bb70fc43d..1f4bdfad48 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp @@ -284,6 +284,27 @@ Pose read_story_pose(const JsonValue& value, std::string_view source){ return result; } +namespace{ + +// What `step` says about running it, not doing it: when to skip it, where +// it ends, once per save, the author's notes. +void add_run_fields(JsonObject& result, const StoryStep& step){ + if (!step.skip_if.empty()){ + result["skip_if"] = strings_json(step.skip_if); + } + if (step.pose_after){ + result["declared_after"] = story_pose_json(*step.pose_after); + } + if (step.once_per_save){ + result["once_per_save"] = true; + } + if (!step.notes.empty()){ + result["notes"] = strings_json(step.notes); + } +} + +} // namespace + JsonValue story_step_json(const StoryStep& step){ JsonObject result; result["id"] = step.name; @@ -321,18 +342,7 @@ JsonValue story_step_json(const StoryStep& step){ if (!step.args.empty()){ result["args"] = args_json(step.args); } - if (!step.skip_if.empty()){ - result["skip_if"] = strings_json(step.skip_if); - } - if (step.pose_after){ - result["declared_after"] = story_pose_json(*step.pose_after); - } - if (step.once_per_save){ - result["once_per_save"] = true; - } - if (!step.notes.empty()){ - result["notes"] = strings_json(step.notes); - } + add_run_fields(result, step); return result; } From c35b6839624a3995ddf6c4eed394289c36ed682e Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 21:30:35 -0800 Subject: [PATCH 5/5] FRLG AutoStory: GAME steps carry their arguments in args A GAME step's variant is now only its action's name (mart, teach, pc, grind, grind_check, capture, fossil, level); what the action reads moves to StoryStep::args, and a Mart's purchases to the step's items. Each action declares its args in the action table, so lint_story_checkpoint() checks every step against them, and each step reads its plan with parse__step(). The story's plans are converted; the sim trace is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../Programs/AutoStory/Data/JsonParser.cpp | 17 +-- .../PokemonFRLG_AutoStoryAbilities.cpp | 20 +-- .../PokemonFRLG_AutoStoryAbilities_Tests.cpp | 39 ++++-- .../PokemonFRLG_AutoStoryCapture.cpp | 123 +++++++----------- .../AutoStory/PokemonFRLG_AutoStoryCapture.h | 32 +++-- .../PokemonFRLG_AutoStoryCapture_Tests.cpp | 23 ++-- .../PokemonFRLG_AutoStoryCeladon_Tests.cpp | 4 +- .../AutoStory/PokemonFRLG_AutoStoryChoice.cpp | 15 +-- .../AutoStory/PokemonFRLG_AutoStoryChoice.h | 15 ++- .../PokemonFRLG_AutoStoryChoice_Tests.cpp | 11 +- .../AutoStory/PokemonFRLG_AutoStoryEvents.cpp | 6 +- .../AutoStory/PokemonFRLG_AutoStoryGrind.cpp | 121 ++++++++--------- .../AutoStory/PokemonFRLG_AutoStoryGrind.h | 42 +++--- .../PokemonFRLG_AutoStoryGrind_Tests.cpp | 42 +++--- .../AutoStory/PokemonFRLG_AutoStoryMart.cpp | 65 ++++----- .../AutoStory/PokemonFRLG_AutoStoryMart.h | 25 ++-- .../PokemonFRLG_AutoStoryMart_Tests.cpp | 38 ++++-- .../AutoStory/PokemonFRLG_AutoStoryMember.cpp | 45 +++---- .../AutoStory/PokemonFRLG_AutoStoryMember.h | 15 ++- .../PokemonFRLG_AutoStoryPcStorage.cpp | 103 ++++++--------- .../PokemonFRLG_AutoStoryPcStorage.h | 31 +++-- .../PokemonFRLG_AutoStoryPcStorage_Tests.cpp | 20 +-- .../PokemonFRLG_AutoStoryRunPlan_Tests.cpp | 18 +-- .../PokemonFRLG_AutoStoryStepTable.cpp | 20 ++- .../PokemonFRLG_AutoStoryStepTable.h | 5 +- .../AutoStory/PokemonFRLG_AutoStoryTeach.cpp | 66 ++++------ .../AutoStory/PokemonFRLG_AutoStoryTeach.h | 26 ++-- .../PokemonFRLG_AutoStoryTeach_Tests.cpp | 22 ++-- .../PokemonFRLG_AutoStoryVariantWords.cpp | 29 +++++ .../PokemonFRLG_AutoStoryVariantWords.h | 27 +++- ...okemonFRLG_AutoStoryVermilionGym_Tests.cpp | 2 +- .../AutoStory/PokemonFRLG_AutoStory_Tests.cpp | 4 +- .../Programs/AutoStory/StoryData/README.md | 3 +- .../AutoStory/StoryData/frlg-00.12c.plan.json | 3 +- .../AutoStory/StoryData/frlg-00.13.plan.json | 3 +- .../AutoStory/StoryData/frlg-00.14.plan.json | 3 +- .../AutoStory/StoryData/frlg-00.14b.plan.json | 7 +- .../AutoStory/StoryData/frlg-00.14c.plan.json | 3 +- .../AutoStory/StoryData/frlg-00.15.plan.json | 10 +- .../AutoStory/StoryData/frlg-00.15b.plan.json | 3 +- .../AutoStory/StoryData/frlg-00.20.plan.json | 7 +- .../AutoStory/StoryData/frlg-00.23.plan.json | 6 +- .../AutoStory/StoryData/frlg-00.24.plan.json | 6 +- .../AutoStory/StoryData/frlg-00.26.plan.json | 4 +- 44 files changed, 619 insertions(+), 510 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp index e84e3eaa07..8f6c63ecaf 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp @@ -215,16 +215,17 @@ Supplies read_authored_supplies(const JsonValue& value, std::string_view source) return result; } -// Whether `variant` names a GAME step FRLG's executor does. -bool known_game_variant(const std::string& variant){ +// Whether `step` is a GAME step FRLG's executor does, with args its +// action reads. +bool known_game_step(const StoryStep& step){ for (const std::string& name : {CLOSE_TRAINER_CARD, LEAVE_GIFT_SUMMARY, CUT_TREE_VARIANT, TRASH_CANS_VARIANT}){ - if (variant == name){ - return true; + if (step.variant == name){ + return step.args.empty(); } } - return parse_capture_variant(variant) || parse_pc_variant(variant) || parse_mart_variant(variant) - || parse_teach_variant(variant) || parse_grind_variant(variant) || parse_grind_check_variant(variant) - || parse_fossil_variant(variant) || parse_level_variant(variant); + return parse_capture_step(step) || parse_pc_step(step) || parse_mart_step(step) + || parse_teach_step(step) || parse_grind_step(step) || parse_grind_check_step(step) + || parse_fossil_step(step) || parse_level_step(step); } // Whether FRLG does `step`'s kind the way its variant names. @@ -236,7 +237,7 @@ bool known_variant(const StoryStep& step){ case StoryStepKind::STEP: return step.variant.empty() || step.variant == TRAINER_LINE_VARIANT || step.variant == TRAINER_WAIT_VARIANT; case StoryStepKind::GAME: - return known_game_variant(step.variant); + return known_game_step(step); default: return step.variant.empty(); } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryAbilities.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryAbilities.cpp index c6463b2ae8..65cb4b8644 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryAbilities.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryAbilities.cpp @@ -83,27 +83,27 @@ std::optional run_ability_game_step( Logger& logger, VideoStream* stream, const Pokemon::StoryStep& step, StoryAbilities& abilities ){ const std::string_view variant = step.variant; - if (variant.starts_with(MART_VARIANT_PREFIX)){ - const std::optional plan = parse_mart_variant(variant); + if (variant == MART_VARIANT){ + const std::optional plan = parse_mart_step(step); return plan ? mart(logger, *plan, abilities) : Pokemon::outcome_failed("bad_variant", step.variant); } - if (variant.starts_with(TEACH_VARIANT_PREFIX)){ - const std::optional plan = parse_teach_variant(variant); + if (variant == TEACH_VARIANT){ + const std::optional plan = parse_teach_step(step); return plan ? teach(logger, *plan, abilities) : Pokemon::outcome_failed("bad_variant", step.variant); } - if (variant.starts_with(LEVEL_VARIANT_PREFIX)){ - const std::optional check = parse_level_variant(variant); + if (variant == LEVEL_VARIANT){ + const std::optional check = parse_level_step(step); return check ? level(logger, *check, abilities) : Pokemon::outcome_failed("bad_variant", step.variant); } - if (variant.starts_with(GRIND_VARIANT_PREFIX)){ - const std::optional plan = parse_grind_variant(variant); + if (variant == GRIND_VARIANT){ + const std::optional plan = parse_grind_step(step); if (!plan || !step.target){ return Pokemon::outcome_failed("bad_variant", step.variant + (step.target ? "" : " (no nurse)")); } return grind(logger, stream, *plan, abilities); } - if (variant.starts_with(FOSSIL_VARIANT_PREFIX)){ - const std::optional choice = parse_fossil_variant(variant); + if (variant == FOSSIL_VARIANT){ + const std::optional choice = parse_fossil_step(step); return choice ? fossil(logger, *choice, abilities) : Pokemon::outcome_failed("bad_variant", step.variant); } return std::nullopt; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryAbilities_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryAbilities_Tests.cpp index de19009225..2cf3cdb24b 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryAbilities_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryAbilities_Tests.cpp @@ -9,11 +9,15 @@ #include #include +#include +#include #include +#include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/TestRunners/UnitTest.h" #include "CommonFramework/Exceptions/ProgramFinishedException.h" #include "PokemonFRLG_AutoStoryAbilities.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -115,11 +119,27 @@ class Test_AutoStoryAbilities : public UnitTest{ }; -Pokemon::StoryStep game_step(std::string variant){ - Pokemon::StoryStep step; - step.kind = Pokemon::StoryStepKind::GAME; +// A GAME step written the old way ("mart PewterCity_Mart budget=2000 +// potion=5"), a Mart's purchases as its items. +Pokemon::StoryStep game_step(std::string_view text){ + static const std::map> POSITIONAL{ + {MART_VARIANT, {"map"}}, {TEACH_VARIANT, {"item", "move"}}, {"capture", {"species"}}, + }; + const auto found = POSITIONAL.find(text.substr(0, text.find(' '))); + Pokemon::StoryStep step = legacy_game_step(text, found == POSITIONAL.end() ? std::span() : found->second); step.name = "test"; - step.variant = std::move(variant); + if (step.variant != MART_VARIANT){ + return step; + } + for (auto it = step.args.begin(); it != step.args.end();){ + const std::optional quantity = Pokemon::story_arg_number(step.args, it->first); + if (!quantity || it->first == "budget" || it->first == "floor"){ + ++it; + continue; + } + step.items.push_back({it->first, static_cast(*quantity)}); + it = step.args.erase(it); + } return step; } @@ -218,13 +238,14 @@ UnitTestResult test_grind_shiny_stops(){ UnitTestResult test_level_variant(){ - const std::string variant = level_variant({.slot = 1, .level = 23}); - const std::optional back = parse_level_variant(variant); - if (variant != "level slot=2 min=23" || !back || back->slot != 1 || back->level != 23){ - return "Unexpected level variant \"" + variant + "\"."; + const Pokemon::StoryStep step = level_step("level", "", {.slot = 1, .level = 23}); + const std::optional back = parse_level_step(step); + const Pokemon::StoryArgs expected{{"slot", int64_t(2)}, {"min", int64_t(23)}}; + if (step.variant != LEVEL_VARIANT || step.args != expected || !back || back->slot != 1 || back->level != 23){ + return "Unexpected LEVEL step \"" + step.variant + "\"."; } for (std::string_view bad : {"level", "level slot=1", "level min=5", "level slot=0 min=5", "level slot=1 min=101"}){ - if (parse_level_variant(bad)){ + if (parse_level_step(legacy_game_step(bad))){ return "Expected \"" + std::string(bad) + "\" refused."; } } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.cpp index f292e12fd2..877265def1 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.cpp @@ -22,6 +22,7 @@ #include "VideoGames/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h" #include "PokemonFRLG_AutoStoryActions.h" #include "PokemonFRLG_AutoStoryCapture.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -36,35 +37,8 @@ namespace{ constexpr size_t PARTY_SIZE = 6; constexpr size_t MOVE_COUNT = 4; -std::vector words_of(std::string_view text){ - std::vector words; - while (!text.empty()){ - const size_t space = text.find(' '); - const std::string_view word = text.substr(0, space); - if (!word.empty()){ - words.push_back(word); - } - if (space == std::string_view::npos){ - break; - } - text.remove_prefix(space + 1); - } - return words; -} - -std::optional number_of(std::string_view text){ - if (text.empty() || text.size() > 4){ - return std::nullopt; - } - size_t value = 0; - for (char c : text){ - if (c < '0' || c > '9'){ - return std::nullopt; - } - value = value * 10 + size_t(c - '0'); - } - return value; -} +// The most a count names: four digits. +constexpr int64_t MAX_COUNT = 9999; std::optional direction_of(std::string_view text){ for (Direction direction : Pokemon::ALL_DIRECTIONS){ @@ -110,59 +84,50 @@ const char* capture_end_name(CaptureEnd end){ } -std::string capture_variant(const CapturePlan& plan){ - std::string variant = std::format( - "{}{} catches={} encounters={} balls={} pace={}", - CAPTURE_VARIANT_PREFIX, plan.species, plan.catches, plan.max_encounters, plan.max_balls, direction_name(plan.pace) - ); +Pokemon::StoryArgs capture_args(const CapturePlan& plan){ + Pokemon::StoryArgs args{ + {"species", plan.species}, + {"catches", static_cast(plan.catches)}, + {"encounters", static_cast(plan.max_encounters)}, + {"balls", static_cast(plan.max_balls)}, + {"pace", std::string(direction_name(plan.pace))}, + }; if (plan.weaken_move){ - variant += std::format(" move={} turns={}", *plan.weaken_move + 1, plan.max_weaken_turns); + args["move"] = static_cast(*plan.weaken_move + 1); + args["turns"] = static_cast(plan.max_weaken_turns); } - return variant; + return args; } -std::optional parse_capture_variant(std::string_view variant){ - if (!variant.starts_with(CAPTURE_VARIANT_PREFIX)){ +std::optional parse_capture_step(const Pokemon::StoryStep& step){ + const Pokemon::StoryArgs* args = game_step_args(step, CAPTURE_VARIANT, CAPTURE_ARGS); + if (args == nullptr + || !arg_number_within(*args, "catches", 1, MAX_COUNT) || !arg_number_within(*args, "encounters", 1, MAX_COUNT) + || !arg_number_within(*args, "balls", 1, MAX_COUNT) || !arg_number_within(*args, "move", 1, static_cast(MOVE_COUNT)) + || !arg_number_within(*args, "turns", 0, MAX_COUNT) + ){ return std::nullopt; } - const std::vector words = words_of(variant.substr(CAPTURE_VARIANT_PREFIX.size())); - if (words.empty() || words[0].find('=') != std::string_view::npos){ + CapturePlan plan; + plan.species = std::string(*Pokemon::story_arg_text(*args, "species")); + if (plan.species.empty()){ return std::nullopt; } - CapturePlan plan; - plan.species = std::string(words[0]); - for (size_t c = 1; c < words.size(); c++){ - const size_t equals = words[c].find('='); - if (equals == std::string_view::npos){ - return std::nullopt; - } - const std::string_view key = words[c].substr(0, equals); - const std::string_view value = words[c].substr(equals + 1); - if (key == "pace"){ - const std::optional pace = direction_of(value); - if (!pace){ - return std::nullopt; - } - plan.pace = *pace; - continue; - } - const std::optional number = number_of(value); - if (!number){ - return std::nullopt; - } - if (key == "catches" && *number >= 1){ - plan.catches = *number; - }else if (key == "encounters" && *number >= 1){ - plan.max_encounters = *number; - }else if (key == "balls" && *number >= 1){ - plan.max_balls = *number; - }else if (key == "move" && *number >= 1 && *number <= MOVE_COUNT){ - plan.weaken_move = *number - 1; - }else if (key == "turns"){ - plan.max_weaken_turns = *number; - }else{ + plan.catches = static_cast(*Pokemon::story_arg_number(*args, "catches")); + plan.max_encounters = static_cast(*Pokemon::story_arg_number(*args, "encounters")); + plan.max_balls = static_cast(*Pokemon::story_arg_number(*args, "balls")); + if (const std::optional pace = Pokemon::story_arg_text(*args, "pace")){ + const std::optional direction = direction_of(*pace); + if (!direction){ return std::nullopt; } + plan.pace = *direction; + } + if (const std::optional move = Pokemon::story_arg_number(*args, "move")){ + plan.weaken_move = static_cast(*move) - 1; + } + if (const std::optional turns = Pokemon::story_arg_number(*args, "turns")){ + plan.max_weaken_turns = static_cast(*turns); } return plan; } @@ -172,7 +137,8 @@ Pokemon::StoryStep capture_step(std::string name, std::string why, const Capture step.kind = Pokemon::StoryStepKind::GAME; step.name = std::move(name); step.why = std::move(why); - step.variant = capture_variant(plan); + step.variant = CAPTURE_VARIANT; + step.args = capture_args(plan); return step; } @@ -181,7 +147,8 @@ Pokemon::StoryStep pc_step(std::string name, std::string why, const PcTransfer& step.kind = Pokemon::StoryStepKind::GAME; step.name = std::move(name); step.why = std::move(why); - step.variant = pc_variant(transfer); + step.variant = PC_VARIANT; + step.args = pc_args(transfer); return step; } @@ -396,8 +363,8 @@ std::optional run_storage_game_step( Logger& logger, VideoStream* stream, const Pokemon::StoryStep& step, CaptureActions& capture, PcStorageActions& pc ){ - if (step.variant.starts_with(CAPTURE_VARIANT_PREFIX)){ - const std::optional plan = parse_capture_variant(step.variant); + if (step.variant == CAPTURE_VARIANT){ + const std::optional plan = parse_capture_step(step); if (!plan){ return Pokemon::outcome_failed("bad_variant", step.variant); } @@ -416,8 +383,8 @@ std::optional run_storage_game_step( } return Pokemon::outcome_failed(std::string("capture_") + capture_end_name(result.end), message); } - if (step.variant.starts_with(PC_VARIANT_PREFIX)){ - const std::optional transfer = parse_pc_variant(step.variant); + if (step.variant == PC_VARIANT){ + const std::optional transfer = parse_pc_step(step); if (!transfer){ return Pokemon::outcome_failed("bad_variant", step.variant); } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.h index 26b99c5df5..b273878c59 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.h @@ -65,17 +65,27 @@ struct CapturePlan{ size_t max_weaken_turns = 2; }; -// The prefix of a GAME step's variant that names a capture step. -inline constexpr std::string_view CAPTURE_VARIANT_PREFIX = "capture "; - -// `plan` as a GAME step's variant, e.g. "capture rattata catches=1 -// encounters=20 balls=5 pace=east", with " move=1 turns=2" when it -// weakens (the move counted from 1). -std::string capture_variant(const CapturePlan& plan); -// A variant from capture_variant(), whose keys after the species may come -// in any order or be left out (their defaults); nothing if `variant` isn't -// one or has a key or value it doesn't know. -std::optional parse_capture_variant(std::string_view variant); +// The variant of a GAME step that is a capture step. +inline constexpr std::string_view CAPTURE_VARIANT = "capture"; +// Its args: the species, how many, the encounters and balls to spend, +// the pace, and the move to weaken with for how many turns. +inline constexpr Pokemon::StoryArgSpec CAPTURE_ARGS[] = { + {"species", Pokemon::StoryArgType::TEXT, true}, + {"catches", Pokemon::StoryArgType::NUMBER, true}, + {"encounters", Pokemon::StoryArgType::NUMBER, true}, + {"balls", Pokemon::StoryArgType::NUMBER, true}, + {"pace", Pokemon::StoryArgType::TEXT, false}, + {"move", Pokemon::StoryArgType::NUMBER, false}, + {"turns", Pokemon::StoryArgType::NUMBER, false}, +}; + +// `plan` as a capture step's args, e.g. {"species": "rattata", "catches": +// 1, "encounters": 20, "balls": 5, "pace": "east"}, with "move": 1, +// "turns": 2 when it weakens (the move counted from 1). +Pokemon::StoryArgs capture_args(const CapturePlan& plan); +// The plan a capture step (capture_step()) runs, the pace east and no +// weakening unless it says; nothing if it isn't one. +std::optional parse_capture_step(const Pokemon::StoryStep& step); // The table's step that runs `plan`. Pokemon::StoryStep capture_step(std::string name, std::string why, const CapturePlan& plan); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture_Tests.cpp index 39310cd804..27ae84a690 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture_Tests.cpp @@ -22,6 +22,7 @@ #include "Pokemon/AutoStory/Pokemon_StorySegments.h" #include "Pokemon/AutoStory/Pokemon_StorySteps.h" #include "PokemonFRLG_AutoStoryCapture.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -349,30 +350,32 @@ UnitTestResult test_capture_variant(){ .weaken_move = 3, .max_weaken_turns = 1}, }; for (const CapturePlan& plan : plans){ - const std::string variant = capture_variant(plan); - const std::optional back = parse_capture_variant(variant); + const std::optional back = parse_capture_step(capture_step("catch", "", plan)); if (!back || back->species != plan.species || back->catches != plan.catches || back->max_encounters != plan.max_encounters || back->max_balls != plan.max_balls || back->pace != plan.pace || back->weaken_move != plan.weaken_move || (plan.weaken_move && back->max_weaken_turns != plan.max_weaken_turns) ){ - return "\"" + variant + "\" didn't read back as the plan it was made from."; + return "The capture step for " + plan.species + " didn't read back as the plan it was made from."; } } - const std::optional short_form = parse_capture_variant("capture rattata"); - if (!short_form || short_form->species != "rattata" || short_form->max_balls != 5 || short_form->weaken_move){ - return "Expected \"capture rattata\" to read with the defaults."; + const std::string_view positional[] = {"species"}; + const std::optional short_form = parse_capture_step(legacy_game_step("capture rattata catches=1 encounters=20 balls=5", positional)); + if (!short_form || short_form->species != "rattata" || short_form->pace != Direction::EAST || short_form->weaken_move){ + return "Expected a capture step without a pace or a move to pace east and not weaken."; } for (std::string_view bad : { - "capture", "capture ", "capture balls=3", "capture rattata balls=0", "capture rattata move=5", - "capture rattata pace=up", "capture rattata colour=red", "capture rattata balls", "pc deposit slot=2 box=1", + "capture", "capture rattata", "capture catches=1 encounters=20 balls=3", "capture rattata catches=1 encounters=20 balls=0", + "capture rattata catches=1 encounters=20 balls=5 move=5", "capture rattata catches=1 encounters=20 balls=5 pace=up", + "capture rattata catches=1 encounters=20 balls=5 colour=red", "capture rattata catches=1 encounters=20 balls", + "pc deposit slot=2 box=1", }){ - if (parse_capture_variant(bad)){ + if (parse_capture_step(legacy_game_step(bad, positional))){ return "Expected \"" + std::string(bad) + "\" refused."; } } const Pokemon::StoryStep step = capture_step("catch_rattata", "Cut user", RATTATA); - if (step.kind != Pokemon::StoryStepKind::GAME || !parse_capture_variant(step.variant)){ + if (step.kind != Pokemon::StoryStepKind::GAME || !parse_capture_step(step)){ return "Expected capture_step() to be a GAME step with a capture variant."; } return true; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp index 922b9c0e18..f1f8f71bf6 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp @@ -209,12 +209,12 @@ UnitTestResult test_bike_voucher_row(){ UnitTestResult test_repels_and_escape_ropes(){ const StoryCheckpoint& row = checkpoint("00.26"); const auto step = std::find_if(row.steps.begin(), row.steps.end(), [](const StoryStep& data){ - return parse_mart_variant(data.variant).has_value(); + return parse_mart_step(data).has_value(); }); if (step == row.steps.end()){ return "00.26: expected a Mart step."; } - const MartPlan plan = *parse_mart_variant(step->variant); + const MartPlan plan = *parse_mart_step(*step); if (plan.map != StoryTable::CERULEAN_MART || !plan.money_floor){ return "00.26: expected the Mart step at Cerulean's Mart, with a money floor."; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.cpp index 7bd64d3949..f147661dcd 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.cpp @@ -11,6 +11,7 @@ #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonFRLG_AutoStoryActions.h" #include "PokemonFRLG_AutoStoryChoice.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -50,15 +51,12 @@ const char* choice_end_name(ChoiceEnd end){ } -std::string fossil_variant(Fossil fossil){ - return std::string(FOSSIL_VARIANT_PREFIX) + fossil_name(fossil); -} - -std::optional parse_fossil_variant(std::string_view variant){ - if (!variant.starts_with(FOSSIL_VARIANT_PREFIX)){ +std::optional parse_fossil_step(const Pokemon::StoryStep& step){ + const Pokemon::StoryArgs* args = game_step_args(step, FOSSIL_VARIANT, FOSSIL_ARGS); + if (args == nullptr){ return std::nullopt; } - return fossil_from_name(variant.substr(FOSSIL_VARIANT_PREFIX.size())); + return fossil_from_name(*Pokemon::story_arg_text(*args, "fossil")); } Pokemon::StoryStep fossil_step(std::string name, std::string why, Fossil fossil){ @@ -66,7 +64,8 @@ Pokemon::StoryStep fossil_step(std::string name, std::string why, Fossil fossil) step.kind = Pokemon::StoryStepKind::GAME; step.name = std::move(name); step.why = std::move(why); - step.variant = fossil_variant(fossil); + step.variant = FOSSIL_VARIANT; + step.args = {{"fossil", std::string(fossil_name(fossil))}}; return step; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.h index 59443c6675..d748efe1f1 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.h @@ -54,13 +54,16 @@ std::optional fossil_from_name(std::string_view name); // Where `fossil` lies on MtMoon_B2F (map.json object_events). Pokemon::TilePos fossil_tile(Fossil fossil); -// The prefix of a GAME step's variant that names the fossil CHOICE. -inline constexpr std::string_view FOSSIL_VARIANT_PREFIX = "fossil "; +// The variant of a GAME step that is the fossil CHOICE. +inline constexpr std::string_view FOSSIL_VARIANT = "fossil"; +// Its args: the fossil, "helix" or "dome". +inline constexpr Pokemon::StoryArgSpec FOSSIL_ARGS[] = { + {"fossil", Pokemon::StoryArgType::TEXT, true}, +}; -// "fossil helix" or "fossil dome". -std::string fossil_variant(Fossil fossil); -// Parses a fossil CHOICE variant string. -std::optional parse_fossil_variant(std::string_view variant); +// The fossil a fossil CHOICE step (fossil_step()) takes; nothing if it +// isn't one. +std::optional parse_fossil_step(const Pokemon::StoryStep& step); // The table's CHOICE(fossil) step, the player facing the fossil. Pokemon::StoryStep fossil_step(std::string name, std::string why, Fossil fossil); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice_Tests.cpp index 432ff26b00..c144af3703 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice_Tests.cpp @@ -14,6 +14,7 @@ #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/TestRunners/UnitTest.h" #include "PokemonFRLG_AutoStoryChoice.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -179,11 +180,15 @@ UnitTestResult test_fossil_choice(){ UnitTestResult test_fossil_variant(){ - if (fossil_variant(Fossil::HELIX) != "fossil helix" || parse_fossil_variant("fossil dome") != Fossil::DOME){ - return "Expected \"fossil helix\" and \"fossil dome\"."; + const std::string_view positional[] = {"fossil"}; + const Pokemon::StoryStep helix = fossil_step("fossil", "", Fossil::HELIX); + if (helix.variant != FOSSIL_VARIANT || Pokemon::story_arg_text(helix.args, "fossil") != "helix" + || parse_fossil_step(helix) != Fossil::HELIX || parse_fossil_step(legacy_game_step("fossil dome", positional)) != Fossil::DOME + ){ + return "Expected the helix and dome fossil steps."; } for (std::string_view bad : {"fossil", "fossil old_amber", "fossil helix dome", "grind level=18"}){ - if (parse_fossil_variant(bad)){ + if (parse_fossil_step(legacy_game_step(bad, positional))){ return "Expected \"" + std::string(bad) + "\" refused."; } } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp index df858d3c49..2aad5b3368 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp @@ -479,9 +479,9 @@ std::optional> fixed_route( Pokemon::Pose paced_pose(const Pokemon::Pose& patch, const StoryStep& step){ std::optional pace; - if (const std::optional grind = parse_grind_variant(step.variant)){ + if (const std::optional grind = parse_grind_step(step)){ pace = grind->pace; - }else if (const std::optional capture = parse_capture_variant(step.variant)){ + }else if (const std::optional capture = parse_capture_step(step)){ pace = capture->pace; } const std::vector tiles = Pokemon::tiles_of(patch); @@ -600,7 +600,7 @@ std::optional run_abilities(StoryEnv& env, RunState& state, const Story ); ConsoleChoiceActions choice(env.console, env.context); StoryAbilities abilities{console_member_reader(env.console, env.context, env.language), mart, teach, grind, choice}; - if (const std::optional plan = parse_grind_check_variant(step.variant)){ + if (const std::optional plan = parse_grind_check_step(step)){ if (grind_reached_already(env.console.logger(), grind, abilities.members, *plan)){ state.flags.insert(step.name); } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind.cpp index 38f4afba33..670a4dbe47 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include "CommonFramework/Exceptions/OperationFailedException.h" #include "PokemonFRLG_AutoStoryActions.h" @@ -35,96 +36,85 @@ const char* grind_end_name(GrindEnd end){ } -std::string grind_variant(const GrindPlan& plan){ - std::string variant = std::format( - "{}slot={} level={} encounters={} pace={} heal_every={}", - GRIND_VARIANT_PREFIX, plan.member + 1, plan.level, plan.max_encounters, - direction_name(plan.pace), plan.heal_every - ); +Pokemon::StoryArgs grind_args(const GrindPlan& plan){ + Pokemon::StoryArgs args{ + {"slot", static_cast(plan.member + 1)}, + {"level", static_cast(plan.level)}, + {"encounters", static_cast(plan.max_encounters)}, + {"pace", std::string(direction_name(plan.pace))}, + {"heal_every", static_cast(plan.heal_every)}, + }; if (!plan.trainer.empty()){ - variant += std::format(" beat={} chance={:.0f}", plan.trainer, 100 * plan.min_chance); + args["beat"] = plan.trainer; + args["chance"] = static_cast(std::llround(100 * plan.min_chance)); } - return variant; + return args; } namespace{ -bool set_grind_word(GrindPlan& plan, std::string_view word){ - const std::optional pair = variant_pair(word); - if (!pair){ - return false; - } - if (pair->key == "beat"){ - plan.trainer = std::string(pair->value); - return !plan.trainer.empty(); - } - if (pair->key == "pace"){ - for (Direction direction : Pokemon::ALL_DIRECTIONS){ - if (pair->value == direction_name(direction)){ - plan.pace = direction; - return true; - } +// The most a count names: four digits. +constexpr int64_t MAX_COUNT = 9999; + +std::optional direction_from_name(std::string_view name){ + for (Direction direction : Pokemon::ALL_DIRECTIONS){ + if (name == direction_name(direction)){ + return direction; } - return false; } - const std::optional number = variant_number(pair->value, 4); - if (!number){ - return false; - } - if (pair->key == "slot" && *number >= 1 && *number <= PARTY_SLOTS){ - plan.member = *number - 1; - }else if (pair->key == "level" && *number >= 2 && *number <= 100){ - plan.level = (unsigned)*number; - }else if (pair->key == "encounters" && *number >= 1){ - plan.max_encounters = *number; - }else if (pair->key == "heal_every"){ - plan.heal_every = *number; - }else if (pair->key == "chance" && *number >= 1 && *number <= 100){ - plan.min_chance = static_cast(*number) / 100; - }else{ - return false; - } - return true; + return std::nullopt; } -} // namespace - -std::optional parse_grind_variant(std::string_view variant){ - if (!variant.starts_with(GRIND_VARIANT_PREFIX)){ +// `args` (checked against GRIND_ARGS) as a plan; nothing if a value is out +// of range, or a chance has no trainer to be against. +std::optional grind_plan(const Pokemon::StoryArgs& args){ + using Pokemon::story_arg_number; + if (!arg_number_within(args, "slot", 1, static_cast(PARTY_SLOTS)) || !arg_number_within(args, "level", 2, 100) + || !arg_number_within(args, "encounters", 1, MAX_COUNT) || !arg_number_within(args, "heal_every", 0, MAX_COUNT) + || !arg_number_within(args, "chance", 1, 100) + ){ return std::nullopt; } - GrindPlan plan; - bool level_seen = false; - bool chance_seen = false; - for (std::string_view word : variant_words(variant.substr(GRIND_VARIANT_PREFIX.size()))){ - if (!set_grind_word(plan, word)){ - return std::nullopt; - } - level_seen |= word.starts_with("level="); - chance_seen |= word.starts_with("chance="); - } - // A chance needs a trainer to be against. - if (!level_seen || (chance_seen && plan.trainer.empty())){ + const std::optional pace = direction_from_name(*Pokemon::story_arg_text(args, "pace")); + const std::optional trainer = Pokemon::story_arg_text(args, "beat"); + const std::optional chance = story_arg_number(args, "chance"); + if (!pace || (trainer && trainer->empty()) || (chance && !trainer)){ return std::nullopt; } + GrindPlan plan; + plan.member = static_cast(*story_arg_number(args, "slot")) - 1; + plan.level = static_cast(*story_arg_number(args, "level")); + plan.max_encounters = static_cast(*story_arg_number(args, "encounters")); + plan.pace = *pace; + plan.heal_every = static_cast(story_arg_number(args, "heal_every").value_or(0)); + plan.trainer = std::string(trainer.value_or("")); + if (chance){ + plan.min_chance = static_cast(*chance) / 100; + } return plan; } +} // namespace + +std::optional parse_grind_step(const Pokemon::StoryStep& step){ + const Pokemon::StoryArgs* args = game_step_args(step, GRIND_VARIANT, GRIND_ARGS); + return args == nullptr ? std::nullopt : grind_plan(*args); +} + Pokemon::StoryStep grind_step(std::string name, std::string why, const GrindPlan& plan, Pokemon::Pose nurse){ Pokemon::StoryStep step; step.kind = Pokemon::StoryStepKind::GAME; step.name = std::move(name); step.why = std::move(why); - step.variant = grind_variant(plan); + step.variant = GRIND_VARIANT; + step.args = grind_args(plan); step.target = std::move(nurse); return step; } -std::optional parse_grind_check_variant(std::string_view variant){ - if (!variant.starts_with(GRIND_CHECK_VARIANT_PREFIX)){ - return std::nullopt; - } - return parse_grind_variant(std::string(GRIND_VARIANT_PREFIX) + std::string(variant.substr(GRIND_CHECK_VARIANT_PREFIX.size()))); +std::optional parse_grind_check_step(const Pokemon::StoryStep& step){ + const Pokemon::StoryArgs* args = game_step_args(step, GRIND_CHECK_VARIANT, GRIND_ARGS); + return args == nullptr ? std::nullopt : grind_plan(*args); } Pokemon::StoryStep grind_check_step(std::string name, std::string why, const GrindPlan& plan){ @@ -132,7 +122,8 @@ Pokemon::StoryStep grind_check_step(std::string name, std::string why, const Gri step.kind = Pokemon::StoryStepKind::GAME; step.name = std::move(name); step.why = std::move(why); - step.variant = std::string(GRIND_CHECK_VARIANT_PREFIX) + grind_variant(plan).substr(GRIND_VARIANT_PREFIX.size()); + step.variant = GRIND_CHECK_VARIANT; + step.args = grind_args(plan); return step; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind.h index 34953a85f2..8c02694d9b 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind.h @@ -72,26 +72,38 @@ struct GrindPlan{ double min_chance = MIN_WIN_CHANCE; }; -// The prefix of a GAME step's variant that names a GRIND step. -inline constexpr std::string_view GRIND_VARIANT_PREFIX = "grind "; +// The variant of a GAME step that is a GRIND step. +inline constexpr std::string_view GRIND_VARIANT = "grind"; +// Its args, and a GRIND_CHECK step's: the lead's slot, the level, the +// encounters and the pace; the nurse's visits, and the trainer to beat +// with the chance to reach. +inline constexpr Pokemon::StoryArgSpec GRIND_ARGS[] = { + {"slot", Pokemon::StoryArgType::NUMBER, true}, + {"level", Pokemon::StoryArgType::NUMBER, true}, + {"encounters", Pokemon::StoryArgType::NUMBER, true}, + {"pace", Pokemon::StoryArgType::TEXT, true}, + {"heal_every", Pokemon::StoryArgType::NUMBER, false}, + {"beat", Pokemon::StoryArgType::TEXT, false}, + {"chance", Pokemon::StoryArgType::NUMBER, false}, +}; -// `plan` as a variant: "grind slot=1 level=18 encounters=60 pace=east -// heal_every=8", the slot counted from 1, and with a trainer -// " beat=TRAINER_RIVAL_CERULEAN_BULBASAUR chance=70", the chance in -// percent. The step's target is the nurse. -std::string grind_variant(const GrindPlan& plan); -// Parses a grinding plan from a variant string. -std::optional parse_grind_variant(std::string_view variant); +// `plan` as a GRIND step's args: {"slot": 1, "level": 18, "encounters": +// 60, "pace": "east", "heal_every": 8}, the slot counted from 1, and with +// a trainer "beat": "TRAINER_RIVAL_CERULEAN_BULBASAUR", "chance": 70, the +// chance in percent. The step's target is the nurse. +Pokemon::StoryArgs grind_args(const GrindPlan& plan); +// The plan a GRIND step (grind_step()) runs; nothing if it isn't one. +std::optional parse_grind_step(const Pokemon::StoryStep& step); // The table's GRIND step: `nurse` is where the player faces the nurse. Pokemon::StoryStep grind_step(std::string name, std::string why, const GrindPlan& plan, Pokemon::Pose nurse); -// The prefix of a GAME step's variant that checks a GRIND step's plan -// before the walk out to its patch, e.g. "grind_check slot=1 level=32 -// ...", the rest as grind_variant()'s. -inline constexpr std::string_view GRIND_CHECK_VARIANT_PREFIX = "grind_check "; -// Parses a grind check variant string into its GrindPlan. -std::optional parse_grind_check_variant(std::string_view variant); +// The variant of a GAME step that checks a GRIND step's plan before the +// walk out to its patch, with grind_args() as its args. +inline constexpr std::string_view GRIND_CHECK_VARIANT = "grind_check"; +// The plan a grind check step (grind_check_step()) checks; nothing if it +// isn't one. +std::optional parse_grind_check_step(const Pokemon::StoryStep& step); // The table's check before the walk out to a GRIND step's patch: when the // lead already has what `plan` grinds for, the step sets a run flag named diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind_Tests.cpp index 963f819dfd..e9337729e7 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryGrind_Tests.cpp @@ -18,6 +18,7 @@ #include "Common/Cpp/TestRunners/UnitTest.h" #include "PokemonFRLG_AutoStoryBattleSim.h" #include "PokemonFRLG_AutoStoryGrind.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -437,33 +438,42 @@ UnitTestResult test_grind_variant(){ plan.max_encounters = 60; plan.pace = Pokemon::Direction::NORTH; plan.heal_every = 8; - const std::string variant = grind_variant(plan); - if (variant != "grind slot=1 level=18 encounters=60 pace=north heal_every=8"){ - return "Unexpected variant \"" + variant + "\"."; + const Pokemon::StoryStep step = grind_step("grind", "", plan, Pokemon::Pose{}); + const Pokemon::StoryArgs expected{ + {"slot", int64_t(1)}, {"level", int64_t(18)}, {"encounters", int64_t(60)}, {"pace", "north"}, {"heal_every", int64_t(8)}, + }; + if (step.variant != GRIND_VARIANT || step.args != expected){ + return "Unexpected GRIND step \"" + step.variant + "\"."; } - const std::optional back = parse_grind_variant(variant); + const std::optional back = parse_grind_step(step); if (!back || back->level != 18 || back->max_encounters != 60 || back->pace != Pokemon::Direction::NORTH || back->heal_every != 8 || back->member != 0 ){ - return "\"" + variant + "\" didn't read back as its plan."; + return "The GRIND step didn't read back as its plan."; } GrindPlan rival = beat_rival(30); rival.max_encounters = 100; rival.pace = Pokemon::Direction::WEST; - const std::string rival_variant = grind_variant(rival); - if (rival_variant != "grind slot=1 level=30 encounters=100 pace=west heal_every=0 beat=TRAINER_RIVAL_CERULEAN_BULBASAUR chance=50"){ - return "Unexpected variant \"" + rival_variant + "\"."; + Pokemon::StoryStep rival_step = grind_step("grind", "", rival, Pokemon::Pose{}); + if (Pokemon::story_arg_text(rival_step.args, "beat") != "TRAINER_RIVAL_CERULEAN_BULBASAUR" + || Pokemon::story_arg_number(rival_step.args, "chance") != 50 + ){ + return "Expected the rival's GRIND step to name the rival and a 50% chance."; } - const std::optional rival_back = parse_grind_variant(rival_variant + " chance=75"); + rival_step.args["chance"] = int64_t(75); + const std::optional rival_back = parse_grind_step(rival_step); if (!rival_back || rival_back->trainer != RIVAL || rival_back->level != 30 || rival_back->min_chance != 0.75){ - return "\"" + rival_variant + " chance=75\" didn't read back as its plan."; + return "The rival's GRIND step with a 75% chance didn't read back as its plan."; } for (std::string_view bad : { - "grind", "grind slot=1", "grind slot=1 level=101", "grind slot=7 level=18", "grind level=18 pace=up", - "grind level=18 encounters=0", "capture rattata", "grind level=30 beat=", "grind level=30 chance=70", - "grind level=30 beat=TRAINER_X chance=0", "grind level=30 beat=TRAINER_X chance=101", + "grind", "grind slot=1", "grind slot=1 level=101 encounters=60 pace=east", "grind slot=7 level=18 encounters=60 pace=east", + "grind slot=1 level=18 encounters=60 pace=up", "grind slot=1 level=18 encounters=0 pace=east", + "capture rattata", "grind slot=1 level=30 encounters=60 pace=east beat=", + "grind slot=1 level=30 encounters=60 pace=east chance=70", + "grind slot=1 level=30 encounters=60 pace=east beat=TRAINER_X chance=0", + "grind slot=1 level=30 encounters=60 pace=east beat=TRAINER_X chance=101", }){ - if (parse_grind_variant(bad)){ + if (parse_grind_step(legacy_game_step(bad))){ return "Expected \"" + std::string(bad) + "\" refused."; } } @@ -487,8 +497,8 @@ UnitTestResult test_grind_reached_already(){ } } const Pokemon::StoryStep check = grind_check_step("rival_winnable", "", beat_rival(30)); - const std::optional back = parse_grind_check_variant(check.variant); - if (!back || back->trainer != RIVAL || back->level != 30 || parse_grind_variant(check.variant)){ + const std::optional back = parse_grind_check_step(check); + if (!back || back->trainer != RIVAL || back->level != 30 || parse_grind_step(check)){ return "\"" + check.variant + "\" didn't read back as its plan, or read as a GRIND step."; } return true; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart.cpp index 937ae6c217..e79a5241ce 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart.cpp @@ -79,24 +79,8 @@ std::optional stock_row(const std::vector& stock, std::st return std::nullopt; } -// Sets one "key=value" of a Mart variant into `plan`. False if it can't. -bool set_mart_word(MartPlan& plan, std::string_view word){ - const std::optional pair = variant_pair(word); - const std::optional number = pair ? variant_number(pair->value) : std::nullopt; - if (!number){ - return false; - } - if (pair->key == "budget"){ - plan.budget = *number; - }else if (pair->key == "floor"){ - plan.money_floor = *number; - }else if (*number >= 1 && *number <= MAX_QUANTITY && stock_row(mart_stock(plan.map), pair->key)){ - plan.lines.push_back({std::string(pair->key), *number}); - }else{ - return false; - } - return true; -} +// The most money a budget or floor names: six digits. +constexpr int64_t MAX_MONEY = 999999; } // namespace @@ -118,34 +102,37 @@ const char* mart_end_name(MartEnd end){ } -std::string mart_variant(const MartPlan& plan){ - std::string variant = std::format("{}{} budget={}", MART_VARIANT_PREFIX, plan.map, plan.budget); +Pokemon::StoryArgs mart_args(const MartPlan& plan){ + Pokemon::StoryArgs args{ + {"map", plan.map}, + {"budget", static_cast(plan.budget)}, + }; if (plan.money_floor){ - variant += std::format(" floor={}", *plan.money_floor); - } - for (const MartLine& line : plan.lines){ - variant += std::format(" {}={}", line.item, line.quantity); + args["floor"] = static_cast(*plan.money_floor); } - return variant; + return args; } -std::optional parse_mart_variant(std::string_view variant){ - if (!variant.starts_with(MART_VARIANT_PREFIX)){ +std::optional parse_mart_step(const Pokemon::StoryStep& step){ + const Pokemon::StoryArgs* args = game_step_args(step, MART_VARIANT, MART_ARGS); + if (args == nullptr || !arg_number_within(*args, "budget", 0, MAX_MONEY) || !arg_number_within(*args, "floor", 0, MAX_MONEY)){ return std::nullopt; } - const std::vector words = variant_words(variant.substr(MART_VARIANT_PREFIX.size())); - if (words.empty() || mart_stock(words[0]).empty()){ + MartPlan plan; + plan.map = std::string(*Pokemon::story_arg_text(*args, "map")); + const std::vector& stock = mart_stock(plan.map); + if (stock.empty() || step.items.empty()){ return std::nullopt; } - MartPlan plan; - plan.map = std::string(words[0]); - for (size_t c = 1; c < words.size(); c++){ - if (!set_mart_word(plan, words[c])){ + plan.budget = static_cast(*Pokemon::story_arg_number(*args, "budget")); + if (const std::optional floor = Pokemon::story_arg_number(*args, "floor")){ + plan.money_floor = static_cast(*floor); + } + for (const Pokemon::StoryItem& item : step.items){ + if (item.quantity < 1 || item.quantity > MAX_QUANTITY || !stock_row(stock, item.item)){ return std::nullopt; } - } - if (plan.lines.empty()){ - return std::nullopt; + plan.lines.push_back({item.item, item.quantity}); } return plan; } @@ -155,7 +142,11 @@ Pokemon::StoryStep mart_step(std::string name, std::string why, const MartPlan& step.kind = Pokemon::StoryStepKind::GAME; step.name = std::move(name); step.why = std::move(why); - step.variant = mart_variant(plan); + step.variant = MART_VARIANT; + step.args = mart_args(plan); + for (const MartLine& line : plan.lines){ + step.items.push_back({line.item, line.quantity}); + } step.once_per_save = true; return step; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart.h index 70b137b0ec..6d4761f9b6 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart.h @@ -84,15 +84,22 @@ struct MartPlan{ std::optional money_floor = std::nullopt; }; -// The prefix of a GAME step's variant that names a Mart step. -inline constexpr std::string_view MART_VARIANT_PREFIX = "mart "; - -// `plan` as a variant: "mart PewterCity_Mart budget=2000 floor=2400 -// potion=5 repel=3", the lines in order. -std::string mart_variant(const MartPlan& plan); -// A variant from mart_variant(); nothing if it isn't one, has no line, or -// names an item its Mart doesn't sell. -std::optional parse_mart_variant(std::string_view variant); +// The variant of a GAME step that is a Mart step. +inline constexpr std::string_view MART_VARIANT = "mart"; +// Its args: the Mart's map, the budget and the money floor. What it buys +// is the step's items, in priority order. +inline constexpr Pokemon::StoryArgSpec MART_ARGS[] = { + {"map", Pokemon::StoryArgType::TEXT, true}, + {"budget", Pokemon::StoryArgType::NUMBER, true}, + {"floor", Pokemon::StoryArgType::NUMBER, false}, +}; + +// `plan`'s map, budget and floor as a Mart step's args, e.g. {"map": +// "PewterCity_Mart", "budget": 2000, "floor": 2400}. +Pokemon::StoryArgs mart_args(const MartPlan& plan); +// The plan a Mart step (mart_step()) buys; nothing if it isn't one, has no +// item, or names an item its Mart doesn't sell. +std::optional parse_mart_step(const Pokemon::StoryStep& step); // The table's Mart step, once per save (StoryStep::once_per_save): a // retry from the same save skips it when it had nothing to buy. diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart_Tests.cpp index 53e24da076..45f5dab77d 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMart_Tests.cpp @@ -19,6 +19,7 @@ #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/TestRunners/UnitTest.h" #include "PokemonFRLG_AutoStoryMart.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -337,23 +338,36 @@ UnitTestResult test_quantity_presses(){ UnitTestResult test_mart_variant(){ const MartPlan plan = pewter_plan(2000, {{"potion", 5}, {"repel", 3}}, 2400); - const std::string variant = mart_variant(plan); - if (variant != "mart PewterCity_Mart budget=2000 floor=2400 potion=5 repel=3"){ - return "Unexpected variant \"" + variant + "\"."; + const Pokemon::StoryStep step = mart_step("buy", "", plan); + const Pokemon::StoryArgs expected{{"map", "PewterCity_Mart"}, {"budget", int64_t(2000)}, {"floor", int64_t(2400)}}; + if (step.variant != MART_VARIANT || step.args != expected || step.items.size() != 2 || step.items[0].item != "potion"){ + return "Unexpected Mart step \"" + step.variant + "\"."; } - const std::optional back = parse_mart_variant(variant); + const std::optional back = parse_mart_step(step); if (!back || back->map != plan.map || back->budget != 2000 || back->money_floor != plan.money_floor || back->lines.size() != 2 || back->lines[1].item != "repel" || back->lines[1].quantity != 3 ){ - return "\"" + variant + "\" didn't read back as its plan."; + return "The Mart step didn't read back as its plan."; } - for (std::string_view bad : { - "mart", "mart PewterCity_Mart budget=2000", "mart Nowhere_Mart potion=1", - "mart ViridianCity_Mart repel=1", "mart PewterCity_Mart potion=0", "mart PewterCity_Mart potion=100", - "mart PewterCity_Mart potion", "teach hm03 surf slot=1", - }){ - if (parse_mart_variant(bad)){ - return "Expected \"" + std::string(bad) + "\" refused."; + // Each with what makes it bad; items as {item, quantity}. + const std::vector>> bad_steps = { + {"mart", {{"potion", 1}}}, + {"mart PewterCity_Mart budget=2000", {}}, + {"mart PewterCity_Mart", {{"potion", 1}}}, + {"mart Nowhere_Mart budget=2000", {{"potion", 1}}}, + {"mart ViridianCity_Mart budget=2000", {{"repel", 1}}}, + {"mart PewterCity_Mart budget=2000", {{"potion", 0}}}, + {"mart PewterCity_Mart budget=2000", {{"potion", 100}}}, + {"mart PewterCity_Mart budget=2000 potion", {{"potion", 1}}}, + {"mart PewterCity_Mart budget=2000 colour=red", {{"potion", 1}}}, + {"teach hm03 surf budget=2000", {{"potion", 1}}}, + }; + const std::string_view positional[] = {"map"}; + for (const auto& [text, items] : bad_steps){ + Pokemon::StoryStep bad = legacy_game_step(text, positional); + bad.items = items; + if (parse_mart_step(bad)){ + return "Expected \"" + std::string(text) + "\" refused."; } } return true; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMember.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMember.cpp index 6d3f71dd1e..350950cc47 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMember.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMember.cpp @@ -30,37 +30,18 @@ MemberReader console_member_reader(ConsoleHandle& console, ProControllerContext& } -std::string level_variant(const LevelCheck& check){ - return std::format("{}slot={} min={}", LEVEL_VARIANT_PREFIX, check.slot + 1, check.level); -} - -std::optional parse_level_variant(std::string_view variant){ - if (!variant.starts_with(LEVEL_VARIANT_PREFIX)){ +std::optional parse_level_step(const Pokemon::StoryStep& step){ + const Pokemon::StoryArgs* args = game_step_args(step, LEVEL_VARIANT, LEVEL_ARGS); + if (args == nullptr + || !arg_number_within(*args, "slot", 1, static_cast(PARTY_SLOTS)) + || !arg_number_within(*args, "min", 1, 100) + ){ return std::nullopt; } - LevelCheck check; - bool slot_seen = false; - bool level_seen = false; - for (std::string_view word : variant_words(variant.substr(LEVEL_VARIANT_PREFIX.size()))){ - const std::optional pair = variant_pair(word); - const std::optional number = pair ? variant_number(pair->value, 3) : std::nullopt; - if (!number){ - return std::nullopt; - } - if (pair->key == "slot" && *number >= 1 && *number <= PARTY_SLOTS){ - check.slot = *number - 1; - slot_seen = true; - }else if (pair->key == "min" && *number >= 1 && *number <= 100){ - check.level = (unsigned)*number; - level_seen = true; - }else{ - return std::nullopt; - } - } - if (!slot_seen || !level_seen){ - return std::nullopt; - } - return check; + return LevelCheck{ + .slot = static_cast(*Pokemon::story_arg_number(*args, "slot")) - 1, + .level = static_cast(*Pokemon::story_arg_number(*args, "min")), + }; } Pokemon::StoryStep level_step(std::string name, std::string why, const LevelCheck& check){ @@ -68,7 +49,11 @@ Pokemon::StoryStep level_step(std::string name, std::string why, const LevelChec step.kind = Pokemon::StoryStepKind::GAME; step.name = std::move(name); step.why = std::move(why); - step.variant = level_variant(check); + step.variant = LEVEL_VARIANT; + step.args = { + {"slot", static_cast(check.slot + 1)}, + {"min", static_cast(check.level)}, + }; return step; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMember.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMember.h index 937c4962f7..c165ad66e0 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMember.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryMember.h @@ -72,13 +72,16 @@ struct LevelCheck{ unsigned level = 1; }; -// The prefix of a GAME step's variant that names a LEVEL step. -inline constexpr std::string_view LEVEL_VARIANT_PREFIX = "level "; +// The variant of a GAME step that is a LEVEL step. +inline constexpr std::string_view LEVEL_VARIANT = "level"; +// Its args: the party slot, counted from 1, and the least level. +inline constexpr Pokemon::StoryArgSpec LEVEL_ARGS[] = { + {"slot", Pokemon::StoryArgType::NUMBER, true}, + {"min", Pokemon::StoryArgType::NUMBER, true}, +}; -// `check` as a variant: "level slot=1 min=16", the slot counted from 1. -std::string level_variant(const LevelCheck& check); -// A variant from level_variant(); nothing if it isn't one. -std::optional parse_level_variant(std::string_view variant); +// The check a LEVEL step (level_step()) makes; nothing if it isn't one. +std::optional parse_level_step(const Pokemon::StoryStep& step); // The table's LEVEL step. Pokemon::StoryStep level_step(std::string name, std::string why, const LevelCheck& check); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage.cpp index 380cdaed9a..92ca4debe4 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage.cpp @@ -10,6 +10,7 @@ #include "VideoGames/PokemonFRLG/PokemonFRLG_Navigation.h" #include "PokemonFRLG_AutoStoryActions.h" #include "PokemonFRLG_AutoStoryPcStorage.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -30,85 +31,57 @@ constexpr size_t STORAGE_MENU_ROWS = 5; // A second look at a frame that reads as the overworld. constexpr Milliseconds OVERWORLD_CONFIRM = 800ms; -std::vector split_words(std::string_view text){ - std::vector words; - while (!text.empty()){ - const size_t space = text.find(' '); - const std::string_view word = text.substr(0, space); - if (!word.empty()){ - words.push_back(word); - } - if (space == std::string_view::npos){ - break; - } - text.remove_prefix(space + 1); - } - return words; -} - -// "key=N" with N from 1 to `max`, as N - 1. -std::optional counted_value(std::string_view word, std::string_view key, size_t max){ - if (!word.starts_with(key) || word.size() <= key.size() + 1 || word[key.size()] != '='){ - return std::nullopt; - } - size_t value = 0; - for (char c : word.substr(key.size() + 1)){ - if (c < '0' || c > '9' || value > max){ - return std::nullopt; - } - value = value * 10 + size_t(c - '0'); - } - if (value < 1 || value > max){ - return std::nullopt; - } - return value - 1; -} - } // namespace -std::string pc_variant(const PcTransfer& transfer){ +std::string pc_transfer_text(const PcTransfer& transfer){ if (transfer.action == PcAction::DEPOSIT){ return std::format( - "{}deposit slot={} box={}{}", PC_VARIANT_PREFIX, transfer.party_slot + 1, transfer.box + 1, + "pc deposit slot={} box={}{}", transfer.party_slot + 1, transfer.box + 1, transfer.if_missing == PcMissing::SKIP ? " if_present" : "" ); } - return std::format("{}withdraw box={} slot={}", PC_VARIANT_PREFIX, transfer.box + 1, transfer.box_slot + 1); + return std::format("pc withdraw box={} slot={}", transfer.box + 1, transfer.box_slot + 1); } -std::optional parse_pc_variant(std::string_view variant){ - if (!variant.starts_with(PC_VARIANT_PREFIX)){ - return std::nullopt; - } - std::vector words = split_words(variant.substr(PC_VARIANT_PREFIX.size())); - PcTransfer transfer; - if (words.size() == 4 && words[0] == "deposit" && words[3] == "if_present"){ - transfer.if_missing = PcMissing::SKIP; - words.pop_back(); - } - if (words.size() != 3){ +Pokemon::StoryArgs pc_args(const PcTransfer& transfer){ + const bool deposit = transfer.action == PcAction::DEPOSIT; + Pokemon::StoryArgs args{ + {"op", std::string(deposit ? "deposit" : "withdraw")}, + {"slot", static_cast((deposit ? transfer.party_slot : transfer.box_slot) + 1)}, + {"box", static_cast(transfer.box + 1)}, + }; + if (deposit && transfer.if_missing == PcMissing::SKIP){ + args["if_present"] = true; + } + return args; +} + +std::optional parse_pc_step(const Pokemon::StoryStep& step){ + const Pokemon::StoryArgs* args = game_step_args(step, PC_VARIANT, PC_ARGS); + if (args == nullptr){ return std::nullopt; } - std::optional slot; - std::optional box; - if (words[0] == "deposit"){ - transfer.action = PcAction::DEPOSIT; - slot = counted_value(words[1], "slot", PARTY_SIZE); - box = counted_value(words[2], "box", BOX_COUNT); - }else if (words[0] == "withdraw"){ - transfer.action = PcAction::WITHDRAW; - box = counted_value(words[1], "box", BOX_COUNT); - slot = counted_value(words[2], "slot", BOX_SLOTS); - } - if (!slot || !box){ + const std::string_view op = *Pokemon::story_arg_text(*args, "op"); + const bool deposit = op == "deposit"; + const bool if_present = Pokemon::story_arg_flag(*args, "if_present"); + if ((!deposit && op != "withdraw") || (!deposit && if_present) + || !arg_number_within(*args, "slot", 1, static_cast(deposit ? PARTY_SIZE : BOX_SLOTS)) + || !arg_number_within(*args, "box", 1, static_cast(BOX_COUNT)) + ){ return std::nullopt; } - transfer.box = *box; - if (transfer.action == PcAction::DEPOSIT){ - transfer.party_slot = *slot; + PcTransfer transfer; + transfer.action = deposit ? PcAction::DEPOSIT : PcAction::WITHDRAW; + transfer.box = static_cast(*Pokemon::story_arg_number(*args, "box")) - 1; + const size_t slot = static_cast(*Pokemon::story_arg_number(*args, "slot")) - 1; + if (deposit){ + transfer.party_slot = slot; }else{ - transfer.box_slot = *slot; + transfer.box_slot = slot; + } + if (if_present){ + transfer.if_missing = PcMissing::SKIP; } return transfer; } @@ -317,7 +290,7 @@ PcDecision PcStorageDialog::on_box_chooser(const PcView& view){ PcResult run_pc_transfer(Logger& logger, PcStorageActions& actions, const PcTransfer& transfer){ - const std::string name = pc_variant(transfer); + const std::string name = pc_transfer_text(transfer); const std::vector party = actions.read_party(); if (transfer.action == PcAction::DEPOSIT && transfer.if_missing == PcMissing::SKIP && transfer.party_slot >= party.size()){ logger.log(std::format("PC: skipped \"{}\": the party has {} Pokemon.", name, party.size())); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage.h index 453c78b1d8..003b50a122 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage.h @@ -51,6 +51,8 @@ #include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" #include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" +#include "Pokemon/AutoStory/Types/Pokemon_StoryArgs.h" +#include "Pokemon/AutoStory/Types/Pokemon_StoryStep.h" #include "VideoGames/PokemonFRLG/Inference/Box/PokemonFRLG_PcScreenReader.h" namespace PokemonAutomation{ @@ -84,16 +86,27 @@ struct PcTransfer{ PcMissing if_missing = PcMissing::REFUSE; }; -// The prefix of a GAME step's variant that names a PC step. -inline constexpr std::string_view PC_VARIANT_PREFIX = "pc "; +// The variant of a GAME step that is a PC step. +inline constexpr std::string_view PC_VARIANT = "pc"; +// Its args: "deposit" or "withdraw", the slot (the party's for a deposit, +// the box's for a withdrawal) and the box; a deposit with if_present skips +// a slot the party hasn't got. +inline constexpr Pokemon::StoryArgSpec PC_ARGS[] = { + {"op", Pokemon::StoryArgType::TEXT, true}, + {"slot", Pokemon::StoryArgType::NUMBER, true}, + {"box", Pokemon::StoryArgType::NUMBER, true}, + {"if_present", Pokemon::StoryArgType::FLAG, false}, +}; -// `transfer` as a GAME step's variant: "pc deposit slot=2 box=1" or -// "pc withdraw box=1 slot=7", slots and boxes counted from 1 as the game -// shows them; a deposit with PcMissing::SKIP ends in " if_present". -std::string pc_variant(const PcTransfer& transfer); -// A variant from pc_variant(); nothing if `variant` isn't one, or names a -// slot or box the game hasn't got. -std::optional parse_pc_variant(std::string_view variant); +// `transfer` for the log: "pc deposit slot=2 box=1" or "pc withdraw box=1 +// slot=7", slots and boxes counted from 1 as the game shows them; a +// deposit with PcMissing::SKIP ends in " if_present". +std::string pc_transfer_text(const PcTransfer& transfer); +// `transfer` as a PC step's args, counted from 1 as pc_transfer_text(). +Pokemon::StoryArgs pc_args(const PcTransfer& transfer); +// The transfer a PC step (pc_step()) makes; nothing if it isn't one, or +// names a slot or box the game hasn't got. +std::optional parse_pc_step(const Pokemon::StoryStep& step); // A party member as far as a PC step needs to know. diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage_Tests.cpp index c3af11e750..99b97f3594 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryPcStorage_Tests.cpp @@ -18,7 +18,9 @@ #include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/TestRunners/UnitTest.h" +#include "PokemonFRLG_AutoStoryCapture.h" #include "PokemonFRLG_AutoStoryPcStorage.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -524,23 +526,25 @@ UnitTestResult test_pc_variant(){ {.action = PcAction::DEPOSIT, .party_slot = 1, .box = 0, .if_missing = PcMissing::SKIP}, }; for (const PcTransfer& transfer : transfers){ - const std::string variant = pc_variant(transfer); - const std::optional back = parse_pc_variant(variant); - if (!back || back->action != transfer.action || back->box != transfer.box + const Pokemon::StoryStep step = pc_step("pc", "", transfer); + const std::optional back = parse_pc_step(step); + if (step.variant != PC_VARIANT || !back || back->action != transfer.action || back->box != transfer.box || back->party_slot != transfer.party_slot || back->box_slot != transfer.box_slot || back->if_missing != transfer.if_missing ){ - return "\"" + variant + "\" didn't read back as the transfer it was made from."; + return "\"" + pc_transfer_text(transfer) + "\" didn't read back as the transfer it was made from."; } } - if (pc_variant(transfers[0]) != "pc deposit slot=2 box=1"){ - return "Expected \"pc deposit slot=2 box=1\", got \"" + pc_variant(transfers[0]) + "\"."; + const Pokemon::StoryArgs expected{{"op", "deposit"}, {"slot", int64_t(2)}, {"box", int64_t(1)}}; + if (pc_args(transfers[0]) != expected || pc_transfer_text(transfers[0]) != "pc deposit slot=2 box=1"){ + return "Expected \"pc deposit slot=2 box=1\", got \"" + pc_transfer_text(transfers[0]) + "\"."; } + const std::string_view positional[] = {"op"}; for (std::string_view bad : { "pc", "pc deposit", "pc deposit slot=7 box=1", "pc deposit slot=0 box=1", "pc deposit slot=2 box=15", - "pc withdraw box=1 slot=31", "pc release slot=1 box=1", "pc deposit box=1 slot=2", "capture rattata", + "pc withdraw box=1 slot=31", "pc release slot=1 box=1", "pc withdraw box=1 slot=2 if_present", "capture rattata", }){ - if (parse_pc_variant(bad)){ + if (parse_pc_step(legacy_game_step(bad, positional))){ return "Expected \"" + std::string(bad) + "\" refused."; } } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp index e595c875e0..c6344b076a 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp @@ -371,24 +371,24 @@ UnitTestResult test_cerulean_mart_and_grind(){ if (!start || start->map != "CeruleanCity_PokemonCenter_1F"){ return "00.15: expected it to start in Cerulean's Center."; } - const std::optional teach = steps.empty() ? std::nullopt : parse_teach_variant(steps[0].variant); + const std::optional teach = steps.empty() ? std::nullopt : parse_teach_step(steps[0]); if (!teach || teach->item != "tm03" || teach->move != "water-pulse" || teach->member != 0 || teach->forget_move != "tail-whip"){ return "00.15: expected it to start with TM03 Water Pulse over the lead's Tail Whip."; } const size_t mart = find_step(steps, 0, [](const StoryStep& step){ - const std::optional plan = parse_mart_variant(step.variant); + const std::optional plan = parse_mart_step(step); return plan && plan->map == "CeruleanCity_Mart" && !plan->lines.empty() && plan->lines[0].item == "potion"; }); const std::optional rival = story_trainer("CeruleanCity", "rival_cerulean"); const size_t grind = find_step(steps, mart, [&](const StoryStep& step){ - const std::optional plan = parse_grind_variant(step.variant); + const std::optional plan = parse_grind_step(step); return plan && plan->member == 0 && rival && plan->trainer == *rival && plan->min_chance == MIN_WIN_CHANCE && plan->level > 25; }); if (mart == steps.size() || grind == steps.size()){ return "00.15: expected a Cerulean Mart step buying Potions, then a GRIND of the lead until the rival is 50%, capped above Lv25."; } - const MartPlan plan = *parse_mart_variant(steps[mart].variant); + const MartPlan plan = *parse_mart_step(steps[mart]); const std::optional> buys = plan.money_floor ? plan_mart_buys(mart_stock(plan.map), plan, *plan.money_floor) : std::nullopt; @@ -437,7 +437,7 @@ UnitTestResult test_cerulean_rival_row(){ if (battles != " rival_cerulean" || rival.steps.size() != 3 || rival.steps[1].name != "rival_trigger"){ return "00.15b: expected a TEACH, the step onto his trigger and his battle; its battles:" + battles; } - const std::optional teach = parse_teach_variant(rival.steps[0].variant); + const std::optional teach = parse_teach_step(rival.steps[0]); if (rival.steps[0].kind != StoryStepKind::GAME || !teach || teach->item != "tm03" || teach->move != "water-pulse" || teach->member != 0 || teach->forget_move != "tail-whip" ){ @@ -482,7 +482,7 @@ UnitTestResult test_misty_rows(){ } const std::vector& steps = gym.steps; const size_t mart = find_step(steps, 0, [](const StoryStep& step){ - const std::optional plan = parse_mart_variant(step.variant); + const std::optional plan = parse_mart_step(step); return plan && plan->map == "CeruleanCity_Mart" && !plan->lines.empty() && plan->lines[0].item == "potion"; }); if (mart == steps.size() || !battle_names(gym).empty()){ @@ -504,7 +504,7 @@ UnitTestResult test_misty_rows(){ return "00.14c: expected the talk from (9, 6), facing Misty (8, 6)."; } const size_t teach = find_step(misty.steps, 0, [](const StoryStep& step){ - const std::optional plan = parse_teach_variant(step.variant); + const std::optional plan = parse_teach_step(step); return plan && plan->item == "tm03" && plan->move == "water-pulse" && plan->member == 0 && plan->forget_move == "tail-whip"; }); if (teach == misty.steps.size()){ @@ -523,7 +523,7 @@ UnitTestResult test_misty_grind_order(){ const std::vector& steps = story_checkpoints()[row_index("00.14b")].steps; std::optional plan; const size_t mart = find_step(steps, 0, [&](const StoryStep& step){ - plan = parse_mart_variant(step.variant); + plan = parse_mart_step(step); return plan && plan->map == "CeruleanCity_Mart"; }); if (mart == steps.size() || plan->lines.size() < 2 || plan->lines[0].item != "potion" || plan->lines[1].item != "super-potion" @@ -536,7 +536,7 @@ UnitTestResult test_misty_grind_order(){ return "00.14b: expected the money floor to buy 2 or more Potions and a Super Potion."; } const size_t grind = find_step(steps, mart, [](const StoryStep& step){ - const std::optional grind_plan = parse_grind_variant(step.variant); + const std::optional grind_plan = parse_grind_step(step); return grind_plan && grind_plan->member == 0 && grind_plan->level >= 23; }); if (grind == steps.size() || !steps[grind - 1].target || !steps[grind].target){ diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryStepTable.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryStepTable.cpp index 3781630bd9..5981e1b290 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryStepTable.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryStepTable.cpp @@ -5,8 +5,15 @@ */ #include "Common/Cpp/Exceptions.h" +#include "PokemonFRLG_AutoStoryCapture.h" +#include "PokemonFRLG_AutoStoryChoice.h" #include "PokemonFRLG_AutoStoryEvents.h" +#include "PokemonFRLG_AutoStoryGrind.h" +#include "PokemonFRLG_AutoStoryMart.h" +#include "PokemonFRLG_AutoStoryMember.h" +#include "PokemonFRLG_AutoStoryPcStorage.h" #include "PokemonFRLG_AutoStoryStepTable.h" +#include "PokemonFRLG_AutoStoryTeach.h" #include "PokemonFRLG_AutoStoryTrashCans.h" namespace PokemonAutomation{ @@ -57,6 +64,15 @@ const std::vector& story_action_rows(){ {StoryStepKind::GAME, LEAVE_GIFT_SUMMARY, StoryActionId::LEAVE_GIFT_SUMMARY}, {StoryStepKind::GAME, CUT_TREE_VARIANT, StoryActionId::CUT_TREE}, {StoryStepKind::GAME, TRASH_CANS_VARIANT, StoryActionId::TRASH_CANS}, + // The GAME action's steps that carry args, each with its own. + {StoryStepKind::GAME, std::string(MART_VARIANT), StoryActionId::GAME, MART_ARGS}, + {StoryStepKind::GAME, std::string(TEACH_VARIANT), StoryActionId::GAME, TEACH_ARGS}, + {StoryStepKind::GAME, std::string(PC_VARIANT), StoryActionId::GAME, PC_ARGS}, + {StoryStepKind::GAME, std::string(GRIND_VARIANT), StoryActionId::GAME, GRIND_ARGS}, + {StoryStepKind::GAME, std::string(GRIND_CHECK_VARIANT), StoryActionId::GAME, GRIND_ARGS}, + {StoryStepKind::GAME, std::string(CAPTURE_VARIANT), StoryActionId::GAME, CAPTURE_ARGS}, + {StoryStepKind::GAME, std::string(FOSSIL_VARIANT), StoryActionId::GAME, FOSSIL_ARGS}, + {StoryStepKind::GAME, std::string(LEVEL_VARIANT), StoryActionId::GAME, LEVEL_ARGS}, {StoryStepKind::GAME, "", StoryActionId::GAME}, }; return ROWS; @@ -65,7 +81,9 @@ const std::vector& story_action_rows(){ Pokemon::StoryActionTable make_story_action_table(const StoryActionBinding& bind){ Pokemon::StoryActionTable table; for (const StoryActionRow& row : story_action_rows()){ - table.add(row.kind, row.variant, bind(row.id)); + Pokemon::StoryAction action = bind(row.id); + action.args = row.args; + table.add(row.kind, row.variant, std::move(action)); } return table; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryStepTable.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryStepTable.h index 515bc5c3ce..0356dbc361 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryStepTable.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryStepTable.h @@ -14,6 +14,7 @@ #define PokemonAutomation_PokemonFRLG_AutoStoryStepTable_H #include +#include #include #include #include "Common/Cpp/TestRunners/UnitTestDatabase.h" @@ -59,11 +60,13 @@ enum class StoryActionId{ const char* story_action_name(StoryActionId id); // A row of FRLG's table: the action for steps of `kind` whose variant is -// named `variant`, or for every other step of `kind` if it is empty. +// named `variant`, or for every other step of `kind` if it is empty, and +// the args its steps carry. struct StoryActionRow{ Pokemon::StoryStepKind kind; std::string variant; StoryActionId id; + std::span args = {}; }; // Every row. TRAIN and SAVE have none: FRLG's story doesn't use them. const std::vector& story_action_rows(); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.cpp index fb487e8f09..1e4095b3f9 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.cpp @@ -37,14 +37,18 @@ const char* teach_end_name(TeachEnd end){ } -std::string teach_variant(const TeachPlan& plan){ - std::string variant = std::format("{}{} {} slot={}", TEACH_VARIANT_PREFIX, plan.item, plan.move, plan.member + 1); +Pokemon::StoryArgs teach_args(const TeachPlan& plan){ + Pokemon::StoryArgs args{ + {"item", plan.item}, + {"move", plan.move}, + {"slot", static_cast(plan.member + 1)}, + }; if (!plan.forget_move.empty()){ - variant += " forget=" + plan.forget_move; + args["forget"] = plan.forget_move; }else if (plan.forget_slot){ - variant += std::format(" forget_slot={}", *plan.forget_slot + 1); + args["forget_slot"] = static_cast(*plan.forget_slot + 1); } - return variant; + return args; } namespace{ @@ -54,47 +58,32 @@ bool is_tm_or_hm(std::string_view item){ && variant_number(item.substr(2), 2).has_value(); } -bool set_teach_word(TeachPlan& plan, std::string_view word){ - const std::optional pair = variant_pair(word); - if (!pair){ - return false; - } - if (pair->key == "forget" && !pair->value.empty()){ - plan.forget_move = std::string(pair->value); - return true; - } - const std::optional number = variant_number(pair->value, 1); - if (pair->key == "slot" && number && *number >= 1 && *number <= PARTY_SLOTS){ - plan.member = *number - 1; - return true; - } - if (pair->key == "forget_slot" && number && *number >= 1 && *number <= MOVE_SLOTS){ - plan.forget_slot = *number - 1; - return true; - } - return false; -} - } // namespace -std::optional parse_teach_variant(std::string_view variant){ - if (!variant.starts_with(TEACH_VARIANT_PREFIX)){ +std::optional parse_teach_step(const Pokemon::StoryStep& step){ + const Pokemon::StoryArgs* args = game_step_args(step, TEACH_VARIANT, TEACH_ARGS); + if (args == nullptr + || !arg_number_within(*args, "slot", 1, static_cast(PARTY_SLOTS)) + || !arg_number_within(*args, "forget_slot", 1, static_cast(MOVE_SLOTS)) + ){ return std::nullopt; } - const std::vector words = variant_words(variant.substr(TEACH_VARIANT_PREFIX.size())); - if (words.size() < 3 || !(is_tm_or_hm(words[0]) || words[0] == TEACH_TUTOR) || variant_pair(words[1])){ + TeachPlan plan{ + .item = std::string(*Pokemon::story_arg_text(*args, "item")), + .move = std::string(*Pokemon::story_arg_text(*args, "move")), + .member = static_cast(*Pokemon::story_arg_number(*args, "slot")) - 1, + }; + if (!(is_tm_or_hm(plan.item) || plan.item == TEACH_TUTOR) || plan.move.empty()){ return std::nullopt; } - TeachPlan plan{.item = std::string(words[0]), .move = std::string(words[1])}; - bool slot_seen = false; - for (size_t c = 2; c < words.size(); c++){ - if (!set_teach_word(plan, words[c])){ + if (const std::optional forget = Pokemon::story_arg_text(*args, "forget")){ + if (forget->empty()){ return std::nullopt; } - slot_seen |= words[c].starts_with("slot="); + plan.forget_move = std::string(*forget); } - if (!slot_seen){ - return std::nullopt; + if (const std::optional forget_slot = Pokemon::story_arg_number(*args, "forget_slot")){ + plan.forget_slot = static_cast(*forget_slot) - 1; } return plan; } @@ -104,7 +93,8 @@ Pokemon::StoryStep teach_step(std::string name, std::string why, const TeachPlan step.kind = Pokemon::StoryStepKind::GAME; step.name = std::move(name); step.why = std::move(why); - step.variant = teach_variant(plan); + step.variant = TEACH_VARIANT; + step.args = teach_args(plan); step.once_per_save = true; return step; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.h index 614a3c725d..a0d1c9bf3e 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach.h @@ -84,14 +84,24 @@ inline constexpr std::string_view TEACH_TUTOR = "tutor"; // Whether `move` is an HM's, which the game won't let a Pokemon forget. bool is_hm_move(std::string_view move); -// The prefix of a GAME step's variant that names a TEACH step. -inline constexpr std::string_view TEACH_VARIANT_PREFIX = "teach "; - -// `plan` as a variant: "teach hm03 surf slot=1 forget=tail-whip" or -// "... forget_slot=2", slots counted from 1, no forget when it has room. -std::string teach_variant(const TeachPlan& plan); -// Parses a move teaching plan from a variant string. -std::optional parse_teach_variant(std::string_view variant); +// The variant of a GAME step that is a TEACH step. +inline constexpr std::string_view TEACH_VARIANT = "teach"; +// Its args: the TM, HM or tutor, the move, the party slot, and the move +// to forget by name or by slot. +inline constexpr Pokemon::StoryArgSpec TEACH_ARGS[] = { + {"item", Pokemon::StoryArgType::TEXT, true}, + {"move", Pokemon::StoryArgType::TEXT, true}, + {"slot", Pokemon::StoryArgType::NUMBER, true}, + {"forget", Pokemon::StoryArgType::TEXT, false}, + {"forget_slot", Pokemon::StoryArgType::NUMBER, false}, +}; + +// `plan` as a TEACH step's args: {"item": "hm03", "move": "surf", "slot": +// 1, "forget": "tail-whip"} or "forget_slot": 2, slots counted from 1, no +// forget when it has room. +Pokemon::StoryArgs teach_args(const TeachPlan& plan); +// The plan a TEACH step (teach_step()) runs; nothing if it isn't one. +std::optional parse_teach_step(const Pokemon::StoryStep& step); // The table's TEACH step, once per save (StoryStep::once_per_save): a // retry from the same save skips it when the lead knew the move or there diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach_Tests.cpp index 202e979a0b..d2eacb97a1 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTeach_Tests.cpp @@ -22,6 +22,7 @@ #include "Common/Cpp/TestRunners/UnitTest.h" #include "PokemonFRLG_AutoStoryTeach.h" #include "PokemonFRLG_AutoStoryTeachScreens.h" +#include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -377,27 +378,30 @@ UnitTestResult test_tm_used_up(){ UnitTestResult test_teach_variant(){ const TeachPlan plan{.item = "hm03", .move = "surf", .member = 0, .forget_move = "tail-whip"}; - const std::string variant = teach_variant(plan); - if (variant != "teach hm03 surf slot=1 forget=tail-whip"){ - return "Unexpected variant \"" + variant + "\"."; + const Pokemon::StoryStep step = teach_step("teach_surf", "", plan); + const Pokemon::StoryArgs expected{{"item", "hm03"}, {"move", "surf"}, {"slot", int64_t(1)}, {"forget", "tail-whip"}}; + if (step.variant != TEACH_VARIANT || step.args != expected){ + return "Unexpected TEACH step \"" + step.variant + "\"."; } - const std::optional back = parse_teach_variant(variant); + const std::optional back = parse_teach_step(step); if (!back || back->item != "hm03" || back->move != "surf" || back->member != 0 || back->forget_move != "tail-whip"){ - return "\"" + variant + "\" didn't read back as its plan."; + return "The TEACH step didn't read back as its plan."; } - const std::optional by_slot = parse_teach_variant("teach tm28 dig slot=2 forget_slot=4"); + const std::string_view positional[] = {"item", "move"}; + const std::optional by_slot = parse_teach_step(legacy_game_step("teach tm28 dig slot=2 forget_slot=4", positional)); if (!by_slot || by_slot->member != 1 || by_slot->forget_slot != 3u){ return "\"teach tm28 dig slot=2 forget_slot=4\" didn't read back."; } - const std::optional tutor = parse_teach_variant("teach tutor mega-kick slot=1 forget=withdraw"); + const std::optional tutor = parse_teach_step(legacy_game_step("teach tutor mega-kick slot=1 forget=withdraw", positional)); if (!tutor || tutor->item != TEACH_TUTOR || tutor->move != "mega-kick" || tutor->forget_move != "withdraw"){ return "\"teach tutor mega-kick slot=1 forget=withdraw\" didn't read back."; } for (std::string_view bad : { "teach", "teach hm03 surf", "teach potion surf slot=1", "teach hm03 surf slot=7", "teach tutors mega-kick slot=1", - "teach hm03 surf slot=1 forget_slot=5", "teach hm03 slot=1", "mart PewterCity_Mart potion=1", + "teach hm03 surf slot=1 forget_slot=5", "teach hm03 slot=1", "teach hm03 surf slot=1 forget=", + "mart PewterCity_Mart potion=1", }){ - if (parse_teach_variant(bad)){ + if (parse_teach_step(legacy_game_step(bad, positional))){ return "Expected \"" + std::string(bad) + "\" refused."; } } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVariantWords.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVariantWords.cpp index 975658f892..3c57e706d0 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVariantWords.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVariantWords.cpp @@ -4,6 +4,7 @@ * */ +#include "Pokemon/AutoStory/Pokemon_StoryVariant.h" #include "PokemonFRLG_AutoStoryVariantWords.h" namespace PokemonAutomation{ @@ -50,6 +51,34 @@ std::optional variant_number(std::string_view text, size_t max_digits){ } +const Pokemon::StoryArgs* game_step_args( + const Pokemon::StoryStep& step, std::string_view name, std::span specs +){ + if (step.variant != name || !Pokemon::story_args_error(step.args, specs).empty()){ + return nullptr; + } + return &step.args; +} + +bool arg_number_within(const Pokemon::StoryArgs& args, std::string_view key, int64_t min, int64_t max){ + const std::optional value = Pokemon::story_arg_number(args, key); + return !value || (*value >= min && *value <= max); +} + +Pokemon::StoryStep legacy_game_step(std::string_view text, std::span positional){ + Pokemon::StoryStep step; + step.kind = Pokemon::StoryStepKind::GAME; + Pokemon::LegacyVariant split = Pokemon::split_legacy_variant(text, positional); + if (!split.error.empty()){ + step.variant = std::string(text); + return step; + } + step.variant = std::move(split.name); + step.args = std::move(split.args); + return step; +} + + } // namespace PokemonFRLG } // namespace NintendoSwitch diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVariantWords.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVariantWords.h index 3618657a27..9433a37168 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVariantWords.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVariantWords.h @@ -2,9 +2,10 @@ * * From: https://github.com/PokemonAutomation/ * - * Reading a GAME step's variant: words split on spaces, "key=value" - * pairs, and small numbers. The table writes variants such as - * "mart PewterCity_Mart budget=2000 potion=5"; the steps read them back. + * Reading a GAME step: its variant names the action ("mart") and its + * args (StoryStep::args) hold what the action reads, checked against the + * action's specs. Also the old variant words ("key=value" pairs and small + * numbers), for the item slugs that carry digits ("tm28"). * */ @@ -12,9 +13,13 @@ #define PokemonAutomation_PokemonFRLG_AutoStoryVariantWords_H #include +#include #include +#include #include #include +#include "Pokemon/AutoStory/Types/Pokemon_StoryArgs.h" +#include "Pokemon/AutoStory/Types/Pokemon_StoryStep.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -37,6 +42,22 @@ std::optional variant_pair(std::string_view word); std::optional variant_number(std::string_view text, size_t max_digits = 6); +// `step`'s args when its variant is `name` and they fit `specs` +// (Pokemon::story_args_error()); nullptr otherwise. +const Pokemon::StoryArgs* game_step_args( + const Pokemon::StoryStep& step, std::string_view name, std::span specs +); + +// Whether `args` has no number `key`, or one from `min` to `max`. +bool arg_number_within(const Pokemon::StoryArgs& args, std::string_view key, int64_t min, int64_t max); + +// A GAME step written the old way, e.g. "teach hm03 surf slot=1": its +// first word the variant, the rest its args (Pokemon::split_legacy_variant()), +// the plain words after the name taking `positional`'s keys. One that +// can't be split keeps `text` as its variant, which no action reads. +Pokemon::StoryStep legacy_game_step(std::string_view text, std::span positional = {}); + + } // namespace PokemonFRLG } // namespace NintendoSwitch diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVermilionGym_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVermilionGym_Tests.cpp index b59faf6d75..af918f809d 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVermilionGym_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryVermilionGym_Tests.cpp @@ -496,7 +496,7 @@ UnitTestResult test_surge_dig_two_turns(){ UnitTestResult test_surge_winnable_skips_grind(){ const StoryCheckpoint& row = checkpoint("00.24"); const StoryStep& check = row.steps.front(); - if (!parse_grind_check_variant(check.variant)){ + if (!parse_grind_check_step(check)){ return "Expected 00.24 to start with the grind's check, not " + check.name + "."; } StoryPose at = *story_pose(*row.start); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp index da54867cd9..2eb4e20f90 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp @@ -567,7 +567,7 @@ UnitTestResult check_lines(const StoryCheckpoint& checkpoint, std::set{}){ const StoryPose end = to_patch ? one_tile(*step.target) : StoryPose{}; const bool patch = to_patch && tile.map == end.map && tile.point == end.tile; @@ -914,7 +914,7 @@ UnitTestResult test_magikarp_deposit_skippable(){ continue; } for (const StoryStep& step : checkpoint.steps){ - const std::optional transfer = parse_pc_variant(step.variant); + const std::optional transfer = parse_pc_step(step); if (transfer && transfer->party_slot == 1 && transfer->if_missing == PcMissing::SKIP){ return true; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md index ae63e57e6c..09c85ee01b 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md @@ -4,7 +4,8 @@ Every story checkpoint is an authored plan here, one file per checkpoint: `frlg- - A checkpoint has `sections: [{id, title, notes?, events}]`, or plain `events` (one unnamed section, as 00.05 keeps so its bytes stay those PokemonSimulation pins). Events are numbered across the sections, and an event's log line names its section: `[Event] 00.28/12 [b1f_east] winston_line: start`. - `notes` (a list of paragraphs) on the checkpoint, a section or an event hold what the C++ table's comments said: the maps, trainers, numbers and console runs a step was planned from. No executor reads them. -- `variant` says how FRLG does a step: `run` on a route or legs, `trainer_line`/`trainer_wait` on a step, and on a `game_action` the step's plan as its parser reads it (`mart ...`, `teach ...`, `grind ...`, `grind_check ...`, `capture ...`, `pc ...`, `fossil ...`, `level ...`, or `cut_tree`, `trash_cans`, `close_trainer_card`, `leave_gift_summary`). An unknown variant is rejected at load. +- `variant` says how FRLG does a step: `run` on a route or legs, `trainer_line`/`trainer_wait` on a step, and on a `game_action` the action's name (`mart`, `teach`, `grind`, `grind_check`, `capture`, `pc`, `fossil`, `level`, or `cut_tree`, `trash_cans`, `close_trainer_card`, `leave_gift_summary`). An unknown variant is rejected at load. +- `args` holds what the action reads, e.g. a Mart's `{"map": "CeruleanCity_Mart", "budget": 1500, "floor": 1500}`, with what it buys in `items`. Each action declares its args in the action table (`PokemonFRLG_AutoStoryStepTable.cpp`); a step with an unknown, mistyped or missing one is rejected at load. - `once_per_save` marks a step done once per save (a TEACH, a Mart). - `execution` holds FRLG's fields: `menu`, and when not a new game's, `supplies` (`potions`, `antidotes`, `parlyz_heals`, `lead: "after_brock"`), `live_start` and `handoff` (`sid_helper`, `starter_rng`, `gift_rng`). - Unknown fields anywhere are rejected. diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12c.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12c.plan.json index 0c547fa4f5..3dc4e28dcb 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12c.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.12c.plan.json @@ -23,7 +23,8 @@ { "id": "magikarp_to_box", "kind": "game_action", - "variant": "pc deposit slot=2 box=1 if_present", + "variant": "pc", + "args": {"op": "deposit", "slot": 2, "box": 1, "if_present": true}, "why": "DEPOSIT the party's second slot into BOX 1, if it has one" } ] diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.13.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.13.plan.json index 4f9d2e6bab..e4421f1376 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.13.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.13.plan.json @@ -156,7 +156,8 @@ { "id": "helix_fossil", "kind": "game_action", - "variant": "fossil helix", + "variant": "fossil", + "args": {"fossil": "helix"}, "why": "the HELIX FOSSIL; Miguel takes the DOME FOSSIL" }, { diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14.plan.json index a2d58040b7..efa26977d1 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14.plan.json @@ -25,7 +25,8 @@ { "id": "teach_mega_kick", "kind": "game_action", - "variant": "teach tutor mega-kick slot=1 forget=withdraw", + "variant": "teach", + "args": {"item": "tutor", "move": "mega-kick", "slot": 1, "forget": "withdraw"}, "once_per_save": true, "why": "the tutor's Mega Kick in place of Withdraw, unless the lead knows it" } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14b.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14b.plan.json index fa88ff60fa..886a21b4be 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14b.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14b.plan.json @@ -25,7 +25,9 @@ { "id": "buy_potions", "kind": "game_action", - "variant": "mart CeruleanCity_Mart budget=3000 floor=3000 potion=5 super-potion=3", + "variant": "mart", + "args": {"map": "CeruleanCity_Mart", "budget": 3000, "floor": 3000}, + "items": [{"item": "potion", "quantity": 5}, {"item": "super-potion", "quantity": 3}], "once_per_save": true, "why": "5 Potions for the GRIND's wild battles, then Super Potions as the money allows" } @@ -45,7 +47,8 @@ { "id": "grind_to_lv23", "kind": "game_action", - "variant": "grind slot=1 level=23 encounters=100 pace=west heal_every=0", + "variant": "grind", + "args": {"slot": 1, "level": 23, "encounters": 100, "pace": "west", "heal_every": 0}, "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, "why": "wild battles until the lead is Lv23, where Bite takes Starmie in three" }, diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14c.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14c.plan.json index 989e062d93..3e0b63c4bc 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14c.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.14c.plan.json @@ -59,7 +59,8 @@ { "id": "teach_water_pulse", "kind": "game_action", - "variant": "teach tm03 water-pulse slot=1 forget=tail-whip", + "variant": "teach", + "args": {"item": "tm03", "move": "water-pulse", "slot": 1, "forget": "tail-whip"}, "once_per_save": true, "why": "TM03 Water Pulse in place of Tail Whip" } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15.plan.json index e5d41285fe..d6f298d308 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15.plan.json @@ -16,7 +16,8 @@ { "id": "teach_water_pulse", "kind": "game_action", - "variant": "teach tm03 water-pulse slot=1 forget=tail-whip", + "variant": "teach", + "args": {"item": "tm03", "move": "water-pulse", "slot": 1, "forget": "tail-whip"}, "once_per_save": true, "why": "TM03 Water Pulse in place of Tail Whip, unless the lead knows it or there is no TM03" } @@ -36,7 +37,9 @@ { "id": "buy_potions", "kind": "game_action", - "variant": "mart CeruleanCity_Mart budget=1500 floor=1500 potion=3 awakening=2", + "variant": "mart", + "args": {"map": "CeruleanCity_Mart", "budget": 1500, "floor": 1500}, + "items": [{"item": "potion", "quantity": 3}, {"item": "awakening", "quantity": 2}], "once_per_save": true, "why": "3 Potions: the GRIND fights wild battles only with 2 or more; 2 Awakenings for the rival's Sleep Powder" } @@ -56,7 +59,8 @@ { "id": "grind_for_rival", "kind": "game_action", - "variant": "grind slot=1 level=28 encounters=150 pace=west heal_every=0 beat=TRAINER_RIVAL_CERULEAN_BULBASAUR chance=50", + "variant": "grind", + "args": {"slot": 1, "level": 28, "encounters": 150, "pace": "west", "heal_every": 0, "beat": "TRAINER_RIVAL_CERULEAN_BULBASAUR", "chance": 50}, "target": {"certainty": "tile", "map": "CeruleanCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, "why": "wild battles until the sim beats the rival 50% or more, Lv28 at most; the nurse when hurt or low on PP" }, diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15b.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15b.plan.json index e2fe43c68b..0fdff3f869 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15b.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.15b.plan.json @@ -16,7 +16,8 @@ { "id": "teach_water_pulse", "kind": "game_action", - "variant": "teach tm03 water-pulse slot=1 forget=tail-whip", + "variant": "teach", + "args": {"item": "tm03", "move": "water-pulse", "slot": 1, "forget": "tail-whip"}, "once_per_save": true, "why": "TM03 Water Pulse in place of Tail Whip, unless the lead knows it" }, diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.20.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.20.plan.json index d8a251d0aa..5cd12caf93 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.20.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.20.plan.json @@ -26,7 +26,9 @@ { "id": "buy_poke_balls", "kind": "game_action", - "variant": "mart CeruleanCity_Mart budget=1400 floor=1500 poke-ball=7", + "variant": "mart", + "args": {"map": "CeruleanCity_Mart", "budget": 1400, "floor": 1500}, + "items": [{"item": "poke-ball", "quantity": 7}], "once_per_save": true, "why": "7 Poke Balls for the Oddish" } @@ -46,7 +48,8 @@ { "id": "catch_oddish", "kind": "game_action", - "variant": "capture oddish catches=1 encounters=30 balls=7 pace=east", + "variant": "capture", + "args": {"species": "oddish", "catches": 1, "encounters": 30, "balls": 7, "pace": "east"}, "why": "wild battles until an Oddish, then Poke Balls; any other species is run from" } ] diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.23.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.23.plan.json index 7ddd9c356c..1406e832dd 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.23.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.23.plan.json @@ -20,14 +20,16 @@ { "id": "teach_cut", "kind": "game_action", - "variant": "teach hm01 cut slot=2", + "variant": "teach", + "args": {"item": "hm01", "move": "cut", "slot": 2}, "once_per_save": true, "why": "HM01 Cut to the Oddish, second in the party, unless it knows it" }, { "id": "teach_dig", "kind": "game_action", - "variant": "teach tm28 dig slot=1 forget=withdraw", + "variant": "teach", + "args": {"item": "tm28", "move": "dig", "slot": 1, "forget": "withdraw"}, "once_per_save": true, "why": "TM28 Dig in place of Withdraw, for Lt. Surge, unless the lead knows it or there is no TM28" } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.24.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.24.plan.json index e7c920ab07..9a72ee5c5e 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.24.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.24.plan.json @@ -16,7 +16,8 @@ { "id": "surge_winnable", "kind": "game_action", - "variant": "grind_check slot=1 level=32 encounters=150 pace=east heal_every=0 beat=TRAINER_LEADER_LT_SURGE chance=50", + "variant": "grind_check", + "args": {"slot": 1, "level": 32, "encounters": 150, "pace": "east", "heal_every": 0, "beat": "TRAINER_LEADER_LT_SURGE", "chance": 50}, "why": "the lead as it is: if the sim beats Lt. Surge 50% or more already, no trip to Route 6" } ] @@ -44,7 +45,8 @@ { "id": "grind_for_surge", "kind": "game_action", - "variant": "grind slot=1 level=32 encounters=150 pace=east heal_every=0 beat=TRAINER_LEADER_LT_SURGE chance=50", + "variant": "grind", + "args": {"slot": 1, "level": 32, "encounters": 150, "pace": "east", "heal_every": 0, "beat": "TRAINER_LEADER_LT_SURGE", "chance": 50}, "target": {"certainty": "tile", "map": "VermilionCity_PokemonCenter_1F", "where": {"facing": "north", "kind": "tiles", "tiles": [[7, 4]]}}, "skip_if": ["surge_winnable"], "why": "wild battles until the sim beats Lt. Surge 50% or more, Lv32 at most; the nurse when hurt or low on PP" diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.26.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.26.plan.json index 34d8de5dd6..5a49347c84 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.26.plan.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-00.26.plan.json @@ -132,7 +132,9 @@ { "id": "buy_repels", "kind": "game_action", - "variant": "mart CeruleanCity_Mart budget=2850 floor=6000 repel=5 escape-rope=2", + "variant": "mart", + "args": {"map": "CeruleanCity_Mart", "budget": 2850, "floor": 6000}, + "items": [{"item": "repel", "quantity": 5}, {"item": "escape-rope", "quantity": 2}], "once_per_save": true, "why": "5 Repels and 2 Escape Ropes, 2850 at most" },