Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Story Events

Check warning on line 1 in SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

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

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));
}


Expand Down
2 changes: 2 additions & 0 deletions SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryEvents.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,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<Condition> skip_if;
// Must hold before `act`; a failure is "require_failed".
Expand Down
10 changes: 8 additions & 2 deletions SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryLog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,14 @@ std::string resets_text(size_t attempt){
} // namespace


std::string event_log_line(std::string_view checkpoint, size_t number, std::string_view event, std::string_view status){
return std::format("[Event] {}/{} {}: {}", checkpoint, number, event, status);
std::string event_log_line(
std::string_view checkpoint, size_t number, std::string_view event, std::string_view status,
std::string_view section
){
if (section.empty()){
return std::format("[Event] {}/{} {}: {}", checkpoint, number, event, status);
}
return std::format("[Event] {}/{} [{}] {}: {}", checkpoint, number, section, event, status);
}

std::string checkpoint_start_line(std::string_view checkpoint){
Expand Down
4 changes: 3 additions & 1 deletion SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryLog.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ namespace Pokemon{
// One event's line:
// [Event] 00.04/3 talk_mom: start
// [Event] 00.04/3 talk_mom: failed: expect_failed: has_parcel
std::string event_log_line(std::string_view checkpoint, size_t number, std::string_view event, std::string_view status);
// 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 = {});

// A checkpoint starts:
// [Checkpoint] 00.04: start
Expand Down
129 changes: 112 additions & 17 deletions SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Story Step JSON

Check warning on line 1 in SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

<file>: file_lines=508 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -284,7 +284,28 @@
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){

Check warning on line 308 in SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::Pokemon::story_step_json( const StoryStep & step): function_lines=40 (advisory above 25, limit 50)
JsonObject result;
result["id"] = step.name;
result["kind"] = enum_name(KINDS, step.kind);
Expand Down Expand Up @@ -321,18 +342,13 @@
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);
}
add_run_fields(result, step);
return result;
}

StoryStep read_story_step(const JsonValue& value, std::string_view source){

Check warning on line 349 in SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::Pokemon::read_story_step( const JsonValue & value , std :: string_view source): function_lines=45 (advisory above 25, limit 50)
const JsonObject& root = fields(value, {"id", "kind", "why", "target", "direction", "legs", "fight_wild", "yes",
"may_lose", "battles", "items", "variant", "args", "skip_if", "declared_after"}, source);
"may_lose", "battles", "items", "variant", "args", "skip_if", "declared_after", "once_per_save", "notes"}, source);
StoryStep result;
result.name = root.get_string_throw("id");
if (result.name.empty()){
Expand Down Expand Up @@ -367,26 +383,80 @@
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<StoryStep>& 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<std::string>& 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){

Check warning on line 458 in SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StoryStepJson.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::Pokemon::read_story_checkpoint( const JsonValue & value , std :: string_view source): function_lines=46 (advisory above 25, limit 50)
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()){
Expand All @@ -396,13 +466,38 @@
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<std::string> 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<std::string> 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));
}
result.steps.push_back(std::move(step));
if (const JsonValue* notes = section.get_value("notes")){
header.notes = read_strings(*notes, source);
}
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;
}
Expand Down
34 changes: 33 additions & 1 deletion SerialPrograms/Source/Pokemon/AutoStory/Pokemon_StorySteps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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);
Expand Down Expand Up @@ -135,8 +136,39 @@ std::string missing_field(const StoryStep& step){

} // namespace

std::vector<std::string> 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<std::string> section_problems(const StoryCheckpoint& data){
std::vector<std::string> problems;
if (data.sections.empty()){
return problems;
}
size_t at = 0;
std::vector<bool> 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<std::string> lint_story_checkpoint(const StoryCheckpoint& data){
std::vector<std::string> problems = section_problems(data);
if (data.id.empty()){
problems.emplace_back("a checkpoint has no id");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,28 @@ namespace PokemonAutomation{
namespace Pokemon{


// 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<std::string> notes;
};

// Milestone row in the story table grouping related steps and starting pose.
struct StoryCheckpoint{
std::string id;
std::string title;
// Where its starting save leaves the player; none for a new game.
std::optional<Pose> start;
std::vector<StoryStep> steps;
// The author's notes on the whole checkpoint: the maps, trainers and
// numbers its steps were planned from.
std::vector<std::string> notes = {};
// In order; each step's `section` names one of them. Empty when the
// checkpoint is not split.
std::vector<StorySection> sections = {};
};


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ 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> 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<std::string> notes = {};
};


Expand Down
Loading
Loading