From 384a9a7eb01ec6af73a5348dfccac6bee285f26a Mon Sep 17 00:00:00 2001 From: Sophia Caspe Date: Thu, 16 Apr 2026 13:15:06 -0700 Subject: [PATCH 01/22] Initial start on AP transpiler --- transpilers/ap/src/generate_expression.cpp | 176 +++++++++++++++++++++ transpilers/ap/src/generate_expression.h | 11 ++ 2 files changed, 187 insertions(+) create mode 100644 transpilers/ap/src/generate_expression.cpp create mode 100644 transpilers/ap/src/generate_expression.h diff --git a/transpilers/ap/src/generate_expression.cpp b/transpilers/ap/src/generate_expression.cpp new file mode 100644 index 0000000..a34f24b --- /dev/null +++ b/transpilers/ap/src/generate_expression.cpp @@ -0,0 +1,176 @@ +namespace rls::transpilers::ap { + +#include "generate_expression.h" + +#include +#include + +namespace rls::transpilers::soh { + +std::string GenerateExpression(const rls::ast::Expr::Variant& node); + +static std::string GenerateExpression(const rls::ast::BoolLiteral& node) { + return node.value ? "True" : "False"; +} + +static std::string GenerateExpression(const rls::ast::IntLiteral& node) { + return std::to_string(node.value); +} + +static std::string GenerateExpression(const rls::ast::Identifier& node) { + return node.name; +} + +static std::string GenerateExpression(const rls::ast::KeywordExpr& node) { + switch (node.keyword) { + case rls::ast::Keyword::IsChild: + return "is_child(bundle)"; + case rls::ast::Keyword::IsAdult: + return "is_adult(bundle)"; + case rls::ast::Keyword::AtDay: + return "at_day(bundle)"; + case rls::ast::Keyword::AtNight: + return "at_night(bundle)"; + // TODO Handle IsVanilla and IsMq + case rls::ast::Keyword::IsVanilla: + return "NOT IMPLEMENTED"; + case rls::ast::Keyword::IsMq: + return "NOT IMPLEMENTED"; + default: + return ""; + } +} + +// Returns the Python operator precedence for an expression node. +// Lower values bind tighter. Non-compound nodes return 0 (tightest). +// Precedence is from https://docs.python.org/3/reference/expressions.html#operator-precedence +static int GetPythonPrecedence(const rls::ast::ExprPtr& expr) { + if (auto* bin = std::get_if(&expr->node)) { + switch (bin->op) { + case rls::ast::BinaryOp::Mul: + case rls::ast::BinaryOp::Div: + return 6; + case rls::ast::BinaryOp::Add: + case rls::ast::BinaryOp::Sub: + return 7; + case rls::ast::BinaryOp::Lt: + case rls::ast::BinaryOp::LtEq: + case rls::ast::BinaryOp::Gt: + case rls::ast::BinaryOp::GtEq: + case rls::ast::BinaryOp::Eq: + case rls::ast::BinaryOp::NotEq: + return 12; + case rls::ast::BinaryOp::And: + return 14; + case rls::ast::BinaryOp::Or: + return 15; + default: return 0; + } + } + if (std::holds_alternative(expr->node)) { + return 16; + } + return 0; +} + +// Generates an expression, wrapping in parentheses when the child's Python +// precedence is looser than the parent's (or equal on the right side of +// a left-associative operator). +static std::string GenerateChildExpression( + const rls::ast::ExprPtr& expr, int parentPrec, bool isRightChild = false) +{ + auto result = GenerateExpression(expr); + int childPrec = GetPythonPrecedence(expr); + if (childPrec > parentPrec || (isRightChild && childPrec == parentPrec)) { + return "(" + result + ")"; + } + return result; +} + +static std::string GenerateExpression(const rls::ast::UnaryExpr& node) { + switch (node.op) { + case rls::ast::UnaryOp::Not: + return "!" + GenerateChildExpression(node.operand, 3); + default: + return ""; + } +} + +static std::string GenerateExpression(const rls::ast::UnaryExpr& node) { + switch (node.op) { + case rls::ast::UnaryOp::Not: + return "not " + GenerateChildExpression(node.operand, 13); + default: + return ""; + } +} + +static std::string GenerateExpression(const rls::ast::BinaryExpr& node) { + switch (node.op) { + case rls::ast::BinaryOp::And: + return GenerateChildExpression(node.left, 14) + " and " + GenerateChildExpression(node.right, 14, true); + case rls::ast::BinaryOp::Or: + return GenerateChildExpression(node.left, 15) + " or " + GenerateChildExpression(node.right, 15, true); + case rls::ast::BinaryOp::Eq: + return GenerateChildExpression(node.left, 12) + " == " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::NotEq: + return GenerateChildExpression(node.left, 12) + " != " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::Lt: + return GenerateChildExpression(node.left, 12) + " < " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::LtEq: + return GenerateChildExpression(node.left, 12) + " <= " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::Gt: + return GenerateChildExpression(node.left, 12) + " > " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::GtEq: + return GenerateChildExpression(node.left, 12) + " >= " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::Add: + return GenerateChildExpression(node.left, 7) + " + " + GenerateChildExpression(node.right, 7, true); + case rls::ast::BinaryOp::Sub: + return GenerateChildExpression(node.left, 7) + " - " + GenerateChildExpression(node.right, 7, true); + case rls::ast::BinaryOp::Mul: + return GenerateChildExpression(node.left, 6) + " * " + GenerateChildExpression(node.right, 6, true); + case rls::ast::BinaryOp::Div: + return GenerateChildExpression(node.left, 6) + " / " + GenerateChildExpression(node.right, 6, true); + default: + return ""; + } +} + +// Python ternary syntax is "a if test else b" +static std::string GenerateExpression(const rls::ast::TernaryExpr& node) { + return GenerateExpression(node.thenBranch) + " if " + + GenertateChildExpresssion(node.condition, 15) + " else " + + GenerateExpression(node.elseBranch); +} + +// TODO Handle Host Functions +static std::string GenerateExpression(const rls::ast::CallExpr& node) { + return "NOT IMPLEMENTED"; +} + +// TODO Figure out Shared blocks +static std::string GenerateExpression(const rls::ast::SharedBlock& node) { + return "NOT IMPLEMENTED"; +} + +// TODO Figure out AnyAge Blocks +// The Python AP implementation doesn't currently have an AnyAge function +static std::string GenerateExpression(const rls::ast::AnyAgeBlock& node) { + return "NOT IMPLEMENTED"; + +// TODO Figure out Match Statements +static std::string GenerateExpression(const rls::ast::MatchExpr& node) { + return "NOT IMPLEMENTED"; +} + +static std::string GenerateExpression(const rls::ast::Expr::Variant& node) { + return std::visit([&](const auto& node) { + return GenerateExpression(node); + }, node); +} + +std::string GenerateExpression(const rls::ast::ExprPtr& expr) { + return GenerateExpression(expr->node); +} + +} // rls::transpilers::ap diff --git a/transpilers/ap/src/generate_expression.h b/transpilers/ap/src/generate_expression.h new file mode 100644 index 0000000..a69eae2 --- /dev/null +++ b/transpilers/ap/src/generate_expression.h @@ -0,0 +1,11 @@ +#pragma once + +#include "ast.h" +#include "output.h" + +namespace rls::transpilers::ap { + +std::string GenerateExpression(const rls::ast::ExprPtr& expr); + +} + From c935b9ddcbf90c3ba8d994b83c7b4b09cab577e3 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Mon, 4 May 2026 21:16:39 -0400 Subject: [PATCH 02/22] Update ap namespace to soh_ap Get Basic output generating for soh_ap --- console/main.cpp | 6 +- console/tests/acceptance_ap_tests.cpp | 6 +- console/tests/acceptance_helpers.h | 2 +- docs/BUILDING.md | 2 +- examples/ap/ap.py | 1 - examples/soh_ap/functions.gen.py | 78 +++ examples/soh_ap/regions.gen.py | 445 ++++++++++++++++++ transpilers/CMakeLists.txt | 2 +- transpilers/ap/include/ap.h | 10 - transpilers/ap/src/ap.cpp | 9 - transpilers/ap/src/generate_expression.h | 11 - transpilers/{ap => soh_ap}/CMakeLists.txt | 0 transpilers/soh_ap/include/soh_ap.h | 38 ++ .../src/generate_expression.cpp | 127 ++--- transpilers/soh_ap/src/generate_functions.cpp | 72 +++ transpilers/soh_ap/src/generate_regions.cpp | 114 +++++ transpilers/soh_ap/src/generate_regions.h | 10 + transpilers/soh_ap/src/rls_match.py | 46 ++ transpilers/soh_ap/src/soh_ap.cpp | 13 + transpilers/{ap => soh_ap}/tests/ap_tests.cpp | 4 +- 20 files changed, 898 insertions(+), 98 deletions(-) delete mode 100644 examples/ap/ap.py create mode 100644 examples/soh_ap/functions.gen.py create mode 100644 examples/soh_ap/regions.gen.py delete mode 100644 transpilers/ap/include/ap.h delete mode 100644 transpilers/ap/src/ap.cpp delete mode 100644 transpilers/ap/src/generate_expression.h rename transpilers/{ap => soh_ap}/CMakeLists.txt (100%) create mode 100644 transpilers/soh_ap/include/soh_ap.h rename transpilers/{ap => soh_ap}/src/generate_expression.cpp (57%) create mode 100644 transpilers/soh_ap/src/generate_functions.cpp create mode 100644 transpilers/soh_ap/src/generate_regions.cpp create mode 100644 transpilers/soh_ap/src/generate_regions.h create mode 100644 transpilers/soh_ap/src/rls_match.py create mode 100644 transpilers/soh_ap/src/soh_ap.cpp rename transpilers/{ap => soh_ap}/tests/ap_tests.cpp (89%) diff --git a/console/main.cpp b/console/main.cpp index decfd09..4e53349 100644 --- a/console/main.cpp +++ b/console/main.cpp @@ -9,7 +9,7 @@ #include "output.h" #include "parser.h" #include "sema.h" -#include "ap.h" +#include "soh_ap.h" #include "soh.h" namespace fs = std::filesystem; @@ -79,8 +79,8 @@ static bool runTranspiler(const TranspilerConfig& config, const rls::ast::Projec if (config.name == "soh") { rls::transpilers::soh::SohTranspiler(project).Transpile(writer); - } else if (config.name == "ap") { - rls::transpilers::ap::Transpile(project, writer); + } else if (config.name == "soh_ap") { + rls::transpilers::soh_ap::SohApTranspiler(project).Transpile(writer); } else { std::cerr << "error: unknown transpiler '" << config.name << "'\n"; return false; diff --git a/console/tests/acceptance_ap_tests.cpp b/console/tests/acceptance_ap_tests.cpp index 1da955b..066c5ab 100644 --- a/console/tests/acceptance_ap_tests.cpp +++ b/console/tests/acceptance_ap_tests.cpp @@ -10,11 +10,11 @@ TEST(AcceptanceAp, ExamplesRlsMatchesGolden) { TempDirectory outputDir("ap"); { DirectoryWriter writer(outputDir.path()); - rls::transpilers::ap::Transpile(project, writer); + rls::transpilers::soh_ap::SohApTranspiler(project).Transpile(writer); } expectDirectoryMatchesGolden( outputDir.path(), - repoPath("examples/ap"), - R"(.\build\console\RandoLogicScript.exe -t ap -o .\examples\ap .\examples\rls)"); + repoPath("examples/soh_ap"), + R"(.\build\console\RandoLogicScript.exe -t soh_ap -o .\examples\soh_ap .\examples\rls)"); } diff --git a/console/tests/acceptance_helpers.h b/console/tests/acceptance_helpers.h index df1923b..2de7199 100644 --- a/console/tests/acceptance_helpers.h +++ b/console/tests/acceptance_helpers.h @@ -12,7 +12,7 @@ #include #include -#include "ap.h" +#include "soh_ap.h" #include "ast.h" #include "output.h" #include "parser.h" diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 156a427..c0c6938 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -138,7 +138,7 @@ Acceptance tests are included in `console_acceptance_tests` and run the end-to-e pipeline over `examples/rls`. - SOH acceptance golden files: `examples/soh/*.gen.{h,cpp}` -- AP acceptance golden file: `examples/ap/ap.py` +- AP acceptance golden files: `examples/soh_ap/*.gen.py` To run only acceptance tests: diff --git a/examples/ap/ap.py b/examples/ap/ap.py deleted file mode 100644 index cb054a1..0000000 --- a/examples/ap/ap.py +++ /dev/null @@ -1 +0,0 @@ -# Generated by RLS ap transpiler diff --git a/examples/soh_ap/functions.gen.py b/examples/soh_ap/functions.gen.py new file mode 100644 index 0000000..ffb4c99 --- /dev/null +++ b/examples/soh_ap/functions.gen.py @@ -0,0 +1,78 @@ +# Generated by RLS soh_ap transpiler + +from .Enums import * + +def _can_get_drop_gold_skulltula(distance: EnemyDistance) -> bool: + return soh_match((lambda distance: distance == ED_CLOSE or distance == ED_SHORT_JUMPSLASH or distance == ED_MASTER_SWORD_JUMPSLASH or distance == ED_LONG_JUMPSLASH or distance == ED_BOMB_THROW or distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG)), True, (lambda distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT)), False) + +def _can_kill_gold_skulltula(distance: EnemyDistance, wall_or_floor: bool) -> bool: + return soh_match((lambda distance: distance == ED_CLOSE), (lambda: can_use(RG_MEGATON_HAMMER)), True, (lambda distance: distance == ED_SHORT_JUMPSLASH), (lambda: can_use(RG_KOKIRI_SWORD)), True, (lambda distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: can_use(RG_MASTER_SWORD)), True, (lambda distance: distance == ED_LONG_JUMPSLASH), (lambda: can_use(RG_BIGGORON_SWORD) or can_use(RG_STICKS)), True, (lambda distance: distance == ED_BOMB_THROW), (lambda: can_use(RG_BOMB_BAG)), True, (lambda distance: distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG) or can_use(RG_DINS_FIRE)), True, (lambda distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT) or wall_or_floor and can_use(RG_BOMBCHU_5)), True, (lambda distance: distance == ED_FAR), (lambda: can_use(RG_FAIRY_SLINGSHOT) or can_use(RG_FAIRY_BOW)), False) + +def call_gossip_fairy() -> bool: + return call_gossip_fairy_except_suns() or can_use(RG_SUNS_SONG) + +def call_gossip_fairy_except_suns() -> bool: + return can_use(RG_ZELDAS_LULLABY) or can_use(RG_EPONAS_SONG) or can_use(RG_SONG_OF_TIME) + +def can_avoid(e: RandomizerEnemy, grounded: bool, quantity: int) -> bool: + return can_kill(e, ED_CLOSE, True, quantity, False, False) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: True), False) + +def can_break_lower_beehives() -> bool: + return can_break_upper_beehives() or can_use(RG_BOMB_BAG) + +def can_break_upper_beehives() -> bool: + return hookshot_or_boomerang() or trick(RT_BOMBCHU_BEEHIVES) and can_use(RG_BOMBCHU_5) or setting(RSK_SLINGBOW_BREAK_BEEHIVES) and (can_use(RG_FAIRY_BOW) or can_use(RG_FAIRY_SLINGSHOT)) + +def can_climb_ladder() -> bool: + return has(RG_CLIMB) or trick(RT_HOOKSHOT_LADDERS) and can_use(RG_HOOKSHOT) + +def can_cut_shrubs() -> bool: + return can_use(RG_KOKIRI_SWORD) or can_use(RG_BOOMERANG) or has_explosives() or has(RG_GORONS_BRACELET) or can_use(RG_MASTER_SWORD) or can_use(RG_MEGATON_HAMMER) or can_use(RG_BIGGORON_SWORD) or can_use(RG_GIANTS_KNIFE) + +def can_get_deku_baba_nuts() -> bool: + return can_jumpslash() or can_use(RG_FAIRY_SLINGSHOT) or can_use(RG_FAIRY_BOW) or has_explosives() or can_use(RG_DINS_FIRE) + +def can_get_deku_baba_sticks() -> bool: + return can_use_sword() or can_use(RG_BOOMERANG) + +def can_get_drop(e: RandomizerEnemy, distance: EnemyDistance, above_link: bool) -> bool: + return can_kill(e, distance, True, 1, False, False) and (distance_to_int(distance) <= distance_to_int(ED_MASTER_SWORD_JUMPSLASH) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: _can_get_drop_gold_skulltula(distance)), False, (lambda e: e == RE_KEESE or e == RE_FIRE_KEESE or e == RE_GUAY), (lambda: True), False, (lambda: true), (lambda: above_link or distance_to_int(distance) <= distance_to_int(ED_BOOMERANG) and can_use(RG_BOOMERANG)), False)) + +def can_get_night_time_gs() -> bool: + return at_night() and (can_use(RG_SUNS_SONG) or !setting(RSK_SKULLS_SUNS_SONG)) + +def can_jumpslash() -> bool: + return can_jumpslash_except_hammer() or can_use(RG_MEGATON_HAMMER) + +def can_jumpslash_except_hammer() -> bool: + return can_use(RG_STICKS) or can_use_sword() + +def can_kill(e: RandomizerEnemy, distance: EnemyDistance, wall_or_floor: bool, quantity: int, timer: bool, in_water: bool) -> bool: + return soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: _can_kill_gold_skulltula(distance, wall_or_floor)), False) + +def can_open_storms_grotto() -> bool: + return can_use(RG_SONG_OF_STORMS) and (has(RG_STONE_OF_AGONY) or trick(RT_GROTTOS_WITHOUT_AGONY)) + +def can_pass(e: RandomizerEnemy, distance: EnemyDistance, wall_or_floor: bool) -> bool: + return can_kill(e, distance, wall_or_floor, 1, False, False) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: True), False) + +def can_spawn_soil_skull(bean: RandomizerGet) -> bool: + return is_child() and can_use(RG_BOTTLE_WITH_BUGS) and has(bean) + +def can_use_sword() -> bool: + return can_use(RG_KOKIRI_SWORD) or can_use(RG_MASTER_SWORD) or can_use(RG_BIGGORON_SWORD) + +def distance_to_int(distance: EnemyDistance) -> int: + return soh_match((lambda distance: distance == ED_CLOSE), (lambda: 0), False, (lambda distance: distance == ED_SHORT_JUMPSLASH), (lambda: 1), False, (lambda distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: 2), False, (lambda distance: distance == ED_LONG_JUMPSLASH), (lambda: 3), False, (lambda distance: distance == ED_BOMB_THROW), (lambda: 4), False, (lambda distance: distance == ED_BOOMERANG), (lambda: 5), False, (lambda distance: distance == ED_HOOKSHOT), (lambda: 6), False, (lambda distance: distance == ED_LONGSHOT), (lambda: 7), False, (lambda distance: distance == ED_FAR), (lambda: 8), False) + +def has_bottle() -> bool: + return bottle_count() >= 1 + +def has_explosives() -> bool: + return can_use(RG_BOMB_BAG) or can_use(RG_BOMBCHU_5) + +def hookshot_or_boomerang() -> bool: + return can_use(RG_HOOKSHOT) or can_use(RG_BOOMERANG) + +def wallet_capacity() -> int: + return 999 if has(RG_TYCOON_WALLET) else 500 if has(RG_GIANT_WALLET) else 200 if has(RG_ADULT_WALLET) else 99 if has(RG_CHILD_WALLET) else 0 diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py new file mode 100644 index 0000000..a14dbf1 --- /dev/null +++ b/examples/soh_ap/regions.gen.py @@ -0,0 +1,445 @@ +# Generated by RLS soh_ap transpiler + +from ...LogicHelpers import * + +if TYPE_CHECKING: + from ... import SohWorld + +def set_region_rules(world: "SohWorld") -> None: + # Adult Spawn + # Events + add_events(Regions.RR_ADULT_SPAWN, world, [ + ]) + # Locations + add_locations(Regions.RR_ADULT_SPAWN, world, [ + ]) + # Exits + connect_regions(Regions.RR_ADULT_SPAWN, world, [ + (Regions.RR_TEMPLE_OF_TIME, True), + ]) + + # Bolero of Fire Warp + # Events + add_events(Regions.RR_BOLERO_OF_FIRE_WARP, world, [ + ]) + # Locations + add_locations(Regions.RR_BOLERO_OF_FIRE_WARP, world, [ + ]) + # Exits + connect_regions(Regions.RR_BOLERO_OF_FIRE_WARP, world, [ + (Regions.RR_DMC_PAD_ENTRY, True), + ]) + + # Child Spawn + # Events + add_events(Regions.RR_CHILD_SPAWN, world, [ + ]) + # Locations + add_locations(Regions.RR_CHILD_SPAWN, world, [ + ]) + # Exits + connect_regions(Regions.RR_CHILD_SPAWN, world, [ + (Regions.RR_KF_LINKS_HOUSE, True), + ]) + + # KF Boulder Loop + # Events + add_events(Regions.RR_KF_BOULDER_LOOP, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_BOULDER_LOOP, world, [ + (Locations.RC_KF_KOKIRI_SWORD_CHEST, is_child() and has(RG_OPEN_CHEST)), + (Locations.RC_KF_CHILD_GRASS_MAZE_1, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_MAZE_2, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_MAZE_3, is_child() and can_cut_shrubs()), + (Locations.RC_KF_BOULDER_RUPEE_1, is_child()), + (Locations.RC_KF_BOULDER_RUPEE_2, is_child()), + ]) + # Exits + connect_regions(Regions.RR_KF_BOULDER_LOOP, world, [ + (Regions.RR_KOKIRI_FOREST, can_use(RG_CRAWL)), + ]) + + # KF House of Twins + # Events + add_events(Regions.RR_KF_HOUSE_OF_TWINS, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_HOUSE_OF_TWINS, world, [ + (Locations.RC_KF_TWINS_HOUSE_POT_1, has(RG_POWER_BRACELET)), + (Locations.RC_KF_TWINS_HOUSE_POT_2, has(RG_POWER_BRACELET)), + ]) + # Exits + connect_regions(Regions.RR_KF_HOUSE_OF_TWINS, world, [ + (Regions.RR_KOKIRI_FOREST, True), + ]) + + # KF Know It All House + # Events + add_events(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ + (Locations.RC_KF_BROTHERS_HOUSE_POT_1, has(RG_POWER_BRACELET)), + (Locations.RC_KF_BROTHERS_HOUSE_POT_2, has(RG_POWER_BRACELET)), + ]) + # Exits + connect_regions(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ + (Regions.RR_KOKIRI_FOREST, True), + ]) + + # KF Kokiri Shop + # Events + add_events(Regions.RR_KF_KOKIRI_SHOP, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_KOKIRI_SHOP, world, [ + (Locations.RC_KF_SHOP_ITEM_1, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_2, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_3, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_4, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_5, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_6, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_7, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_8, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + ]) + # Exits + connect_regions(Regions.RR_KF_KOKIRI_SHOP, world, [ + (Regions.RR_KOKIRI_FOREST, True), + ]) + + # KF Link's House + # Events + add_events(Regions.RR_KF_LINKS_HOUSE, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_LINKS_HOUSE, world, [ + (Locations.RC_KF_LINKS_HOUSE_POT, has(RG_POWER_BRACELET)), + (Locations.RC_KF_LINKS_HOUSE_COW, is_adult() and can_use(RG_EPONAS_SONG) and flag(LOGIC_LINKS_COW)), + ]) + # Exits + connect_regions(Regions.RR_KF_LINKS_HOUSE, world, [ + (Regions.RR_KF_LINKS_PORCH, True), + ]) + + # KF Link's Porch + # Events + add_events(Regions.RR_KF_LINKS_PORCH, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_LINKS_PORCH, world, [ + ]) + # Exits + connect_regions(Regions.RR_KF_LINKS_PORCH, world, [ + (Regions.RR_KF_LINKS_HOUSE, True), + (Regions.RR_KOKIRI_FOREST, True), + ]) + + # KF Mido's House + # Events + add_events(Regions.RR_KF_MIDOS_HOUSE, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_MIDOS_HOUSE, world, [ + (Locations.RC_KF_MIDOS_TOP_LEFT_CHEST, has(RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_TOP_RIGHT_CHEST, has(RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_BOTTOM_LEFT_CHEST, has(RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_BOTTOM_RIGHT_CHEST, has(RG_OPEN_CHEST)), + ]) + # Exits + connect_regions(Regions.RR_KF_MIDOS_HOUSE, world, [ + (Regions.RR_KOKIRI_FOREST, True), + ]) + + # KF Outside Deku Tree + # Events + add_events(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ + (EventLocations.LOGIC_STICK_ACCESS, can_get_deku_baba_sticks()), + (EventLocations.LOGIC_NUT_ACCESS, can_get_deku_baba_nuts()), + (EventLocations.LOGIC_FAIRY_ACCESS, call_gossip_fairy_except_suns()), + (EventLocations.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, is_child() and has(RG_SPEAK_KOKIRI) and can_use(RG_KOKIRI_SWORD) and can_use(RG_DEKU_SHIELD)), + ]) + # Locations + add_locations(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE, True), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, True), + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY, call_gossip_fairy_except_suns()), + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG, can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY, call_gossip_fairy_except_suns()), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG, can_use(RG_SONG_OF_STORMS)), + ]) + # Exits + connect_regions(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ + (Regions.RR_DEKU_TREE_ENTRYWAY, is_child() or setting(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF and (setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), + (Regions.RR_KOKIRI_FOREST, is_adult() and (can_pass(RE_BIG_SKULLTULA, ED_CLOSE, True) or flag(LOGIC_DEKU_TREE_CLEAR)) or setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), + ]) + + # KF Outside Lost Woods + # Events + add_events(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ + (Locations.RC_KF_GOSSIP_STONE, True), + (Locations.RC_KF_BEAN_RUPEE_1, is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_2, is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_3, is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_4, is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_5, is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_6, is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RED_RUPEE, is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_GOSSIP_STONE_FAIRY, call_gossip_fairy_except_suns()), + (Locations.RC_KF_GOSSIP_STONE_FAIRY_BIG, can_use(RG_SONG_OF_STORMS)), + ]) + # Exits + connect_regions(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ + (Regions.RR_KOKIRI_FOREST, True), + (Regions.RR_THE_LOST_WOODS, True), + (Regions.RR_KF_RUPEE_ALCOVE, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS))), + (Regions.RR_KF_STORMS_GROTTO, can_open_storms_grotto()), + ]) + + # KF Alcove + # Events + add_events(Regions.RR_KF_RUPEE_ALCOVE, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_RUPEE_ALCOVE, world, [ + (Locations.RC_KF_BEAN_RUPEE_1, is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_2, is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_3, is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_4, is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_5, is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_6, is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RED_RUPEE, is_adult() and can_use(RG_HOVER_BOOTS)), + ]) + # Exits + connect_regions(Regions.RR_KF_RUPEE_ALCOVE, world, [ + (Regions.RR_KOKIRI_FOREST, True), + ]) + + # KF Saria's House + # Events + add_events(Regions.RR_KF_SARIAS_HOUSE, world, [ + ]) + # Locations + add_locations(Regions.RR_KF_SARIAS_HOUSE, world, [ + (Locations.RC_KF_SARIAS_TOP_LEFT_HEART, True), + (Locations.RC_KF_SARIAS_TOP_RIGHT_HEART, True), + (Locations.RC_KF_SARIAS_BOTTOM_LEFT_HEART, True), + (Locations.RC_KF_SARIAS_BOTTOM_RIGHT_HEART, True), + ]) + # Exits + connect_regions(Regions.RR_KF_SARIAS_HOUSE, world, [ + (Regions.RR_KOKIRI_FOREST, True), + ]) + + # KF Storms Grotto + # Events + add_events(Regions.RR_KF_STORMS_GROTTO, world, [ + (EventLocations.LOGIC_FAIRY_ACCESS, call_gossip_fairy() or can_use(RG_STICKS)), + (EventLocations.LOGIC_BUG_ACCESS, can_cut_shrubs()), + (EventLocations.LOGIC_FISH_ACCESS, True), + ]) + # Locations + add_locations(Regions.RR_KF_STORMS_GROTTO, world, [ + (Locations.RC_KF_STORMS_GROTTO_CHEST, has(RG_OPEN_CHEST)), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE, True), + (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_LEFT, can_break_lower_beehives()), + (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT, can_break_lower_beehives()), + (Locations.RC_KF_STORMS_GROTTO_FISH, has_bottle()), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY, call_gossip_fairy()), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG, can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_STORMS_GROTTO_GRASS_1, can_cut_shrubs()), + (Locations.RC_KF_STORMS_GROTTO_GRASS_2, can_cut_shrubs()), + (Locations.RC_KF_STORMS_GROTTO_GRASS_3, can_cut_shrubs()), + (Locations.RC_KF_STORMS_GROTTO_GRASS_4, can_cut_shrubs()), + ]) + # Exits + connect_regions(Regions.RR_KF_STORMS_GROTTO, world, [ + (Regions.RR_KF_OUTSIDE_LOST_WOODS, True), + ]) + + # Kokiri Forest + # Events + add_events(Regions.RR_KOKIRI_FOREST, world, [ + (EventLocations.LOGIC_FAIRY_ACCESS, call_gossip_fairy_except_suns() or is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (EventLocations.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, is_child() and has(RG_SPEAK_KOKIRI) and can_use(RG_KOKIRI_SWORD) and can_use(RG_DEKU_SHIELD)), + ]) + # Locations + add_locations(Regions.RR_KOKIRI_FOREST, world, [ + (Locations.RC_KF_CHILD_GRASS_1, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_2, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_3, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_4, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_5, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_6, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_7, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_8, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_9, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_10, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_11, is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_12, is_child() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_1, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_2, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_3, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_4, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_5, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_6, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_7, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_8, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_9, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_10, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_11, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_12, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_13, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_14, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_15, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_16, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_17, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_18, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_19, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_20, is_adult() and can_cut_shrubs()), + (Locations.RC_KF_BRIDGE_RUPEE, is_child()), + (Locations.RC_KF_BEHIND_MIDOS_RUPEE, is_child()), + (Locations.RC_KF_SOUTH_GRASS_WEST_RUPEE, is_child()), + (Locations.RC_KF_SOUTH_GRASS_EAST_RUPEE, is_child()), + (Locations.RC_KF_NORTH_GRASS_WEST_RUPEE, is_child()), + (Locations.RC_KF_NORTH_GRASS_EAST_RUPEE, is_child()), + (Locations.RC_KF_SARIAS_ROOF_WEST_HEART, is_child()), + (Locations.RC_KF_SARIAS_ROOF_EAST_HEART, is_child()), + (Locations.RC_KF_SARIAS_ROOF_NORTH_HEART, is_child()), + (Locations.RC_KF_BEAN_RUPEE_1, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_2, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_3, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_4, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_5, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_6, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RED_RUPEE, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_GS_KNOW_IT_ALL_HOUSE, is_child() and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False) and can_get_night_time_gs()), + (Locations.RC_KF_GS_BEAN_PATCH, can_spawn_soil_skull(RG_KOKIRI_FOREST_BEAN_SOUL) and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False)), + (Locations.RC_KF_GS_HOUSE_OF_TWINS, is_adult() and can_get_night_time_gs() and (can_get_drop(RE_GOLD_SKULLTULA, ED_BOOMERANG, False) or trick(RT_KF_ADULT_GS) and can_use(RG_HOVER_BOOTS) and can_kill(RE_GOLD_SKULLTULA, ED_SHORT_JUMPSLASH, True, 1, False, False))), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_1, is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_2, is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_3, is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + ]) + # Exits + connect_regions(Regions.RR_KOKIRI_FOREST, world, [ + (Regions.RR_KF_BOULDER_LOOP, can_use(RG_CRAWL)), + (Regions.RR_KF_LINKS_PORCH, can_climb_ladder() if is_child() else has(RG_CLIMB) or can_use(RG_HOVER_BOOTS)), + (Regions.RR_KF_MIDOS_HOUSE, True), + (Regions.RR_KF_SARIAS_HOUSE, True), + (Regions.RR_KF_HOUSE_OF_TWINS, True), + (Regions.RR_KF_KNOW_IT_ALL_HOUSE, True), + (Regions.RR_KF_KOKIRI_SHOP, True), + (Regions.RR_KF_OUTSIDE_DEKU_TREE, flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD) or setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or is_adult() and (can_pass(RE_BIG_SKULLTULA, ED_CLOSE, True) or flag(LOGIC_FOREST_TEMPLE_CLEAR))), + (Regions.RR_KF_OUTSIDE_LOST_WOODS, has(RG_CLIMB) or can_use(RG_HOOKSHOT) or is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or trick(RT_UNINTUITIVE_JUMPS))), + (Regions.RR_KF_RUPEE_ALCOVE, is_adult() and can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL)), + (Regions.RR_LW_BRIDGE_FROM_FOREST, is_adult() or setting(RSK_FOREST) != RO_CLOSED_FOREST_ON or flag(LOGIC_DEKU_TREE_CLEAR)), + ]) + + # Minuet of Forest Warp + # Events + add_events(Regions.RR_MINUET_OF_FOREST_WARP, world, [ + ]) + # Locations + add_locations(Regions.RR_MINUET_OF_FOREST_WARP, world, [ + ]) + # Exits + connect_regions(Regions.RR_MINUET_OF_FOREST_WARP, world, [ + (Regions.RR_SACRED_FOREST_MEADOW, True), + ]) + + # Nocturne of Shadow Warp + # Events + add_events(Regions.RR_NOCTURNE_OF_SHADOW_WARP, world, [ + ]) + # Locations + add_locations(Regions.RR_NOCTURNE_OF_SHADOW_WARP, world, [ + ]) + # Exits + connect_regions(Regions.RR_NOCTURNE_OF_SHADOW_WARP, world, [ + (Regions.RR_GRAVEYARD_WARP_PAD_REGION, True), + ]) + + # Prelude of Light Warp + # Events + add_events(Regions.RR_PRELUDE_OF_LIGHT_WARP, world, [ + ]) + # Locations + add_locations(Regions.RR_PRELUDE_OF_LIGHT_WARP, world, [ + ]) + # Exits + connect_regions(Regions.RR_PRELUDE_OF_LIGHT_WARP, world, [ + (Regions.RR_TEMPLE_OF_TIME, True), + ]) + + # Requiem of Spirit Warp + # Events + add_events(Regions.RR_REQUIEM_OF_SPIRIT_WARP, world, [ + ]) + # Locations + add_locations(Regions.RR_REQUIEM_OF_SPIRIT_WARP, world, [ + ]) + # Exits + connect_regions(Regions.RR_REQUIEM_OF_SPIRIT_WARP, world, [ + (Regions.RR_DESERT_COLOSSUS, True), + ]) + + # Root + # Events + add_events(Regions.RR_ROOT, world, [ + (EventLocations.LOGIC_KAKARIKO_GATE_OPEN, setting(RSK_KAK_GATE) == RO_KAK_GATE_OPEN), + (EventLocations.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE), + (EventLocations.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), + (EventLocations.LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), + (EventLocations.LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), + (EventLocations.LOGIC_TH_RESCUED_ALL_CARPENTERS, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE), + (EventLocations.LOGIC_FREED_EPONA, setting(RSK_SKIP_EPONA_RACE)), + ]) + # Locations + add_locations(Regions.RR_ROOT, world, [ + (Locations.RC_LINKS_POCKET, True), + (Locations.RC_TRIFORCE_COMPLETED, collected_triforce_pieces() >= required_triforce_pieces()), + (Locations.RC_SARIA_SONG_HINT, can_use(RG_SARIAS_SONG)), + (Locations.RC_SONG_FROM_IMPA, setting(RSK_SKIP_CHILD_ZELDA)), + (Locations.RC_HC_MALON_EGG, setting(RSK_SKIP_CHILD_ZELDA)), + (Locations.RC_HC_ZELDAS_LETTER, setting(RSK_SKIP_CHILD_ZELDA)), + (Locations.RC_TOT_MASTER_SWORD, setting(RSK_SELECTED_STARTING_AGE) == RO_AGE_ADULT), + ]) + # Exits + connect_regions(Regions.RR_ROOT, world, [ + (Regions.RR_ROOT_EXITS, True), + ]) + + # Root Exits + # Events + add_events(Regions.RR_ROOT_EXITS, world, [ + ]) + # Locations + add_locations(Regions.RR_ROOT_EXITS, world, [ + ]) + # Exits + connect_regions(Regions.RR_ROOT_EXITS, world, [ + (Regions.RR_CHILD_SPAWN, is_child()), + (Regions.RR_ADULT_SPAWN, is_adult()), + (Regions.RR_MINUET_OF_FOREST_WARP, can_use(RG_MINUET_OF_FOREST)), + (Regions.RR_BOLERO_OF_FIRE_WARP, can_use(RG_BOLERO_OF_FIRE)), + (Regions.RR_SERENADE_OF_WATER_WARP, can_use(RG_SERENADE_OF_WATER)), + (Regions.RR_NOCTURNE_OF_SHADOW_WARP, can_use(RG_NOCTURNE_OF_SHADOW)), + (Regions.RR_REQUIEM_OF_SPIRIT_WARP, can_use(RG_REQUIEM_OF_SPIRIT)), + (Regions.RR_PRELUDE_OF_LIGHT_WARP, can_use(RG_PRELUDE_OF_LIGHT)), + ]) + + # Serenade of Water Warp + # Events + add_events(Regions.RR_SERENADE_OF_WATER_WARP, world, [ + ]) + # Locations + add_locations(Regions.RR_SERENADE_OF_WATER_WARP, world, [ + ]) + # Exits + connect_regions(Regions.RR_SERENADE_OF_WATER_WARP, world, [ + (Regions.RR_LAKE_HYLIA, True), + ]) + diff --git a/transpilers/CMakeLists.txt b/transpilers/CMakeLists.txt index 02bb10f..41080b8 100644 --- a/transpilers/CMakeLists.txt +++ b/transpilers/CMakeLists.txt @@ -1,2 +1,2 @@ add_subdirectory(soh) -add_subdirectory(ap) +add_subdirectory(soh_ap) diff --git a/transpilers/ap/include/ap.h b/transpilers/ap/include/ap.h deleted file mode 100644 index 6f0055c..0000000 --- a/transpilers/ap/include/ap.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "ast.h" -#include "output.h" - -namespace rls::transpilers::ap { - -void Transpile(const rls::ast::Project& project, rls::OutputWriter& out); - -} // namespace rls::transpilers::ap diff --git a/transpilers/ap/src/ap.cpp b/transpilers/ap/src/ap.cpp deleted file mode 100644 index 826b152..0000000 --- a/transpilers/ap/src/ap.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "ap.h" - -namespace rls::transpilers::ap { - -void Transpile(const rls::ast::Project& project, rls::OutputWriter& out) { - out.open("ap.py") << "# Generated by RLS ap transpiler\n"; -} - -} // namespace rls::transpilers::ap diff --git a/transpilers/ap/src/generate_expression.h b/transpilers/ap/src/generate_expression.h deleted file mode 100644 index a69eae2..0000000 --- a/transpilers/ap/src/generate_expression.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "ast.h" -#include "output.h" - -namespace rls::transpilers::ap { - -std::string GenerateExpression(const rls::ast::ExprPtr& expr); - -} - diff --git a/transpilers/ap/CMakeLists.txt b/transpilers/soh_ap/CMakeLists.txt similarity index 100% rename from transpilers/ap/CMakeLists.txt rename to transpilers/soh_ap/CMakeLists.txt diff --git a/transpilers/soh_ap/include/soh_ap.h b/transpilers/soh_ap/include/soh_ap.h new file mode 100644 index 0000000..1f89c96 --- /dev/null +++ b/transpilers/soh_ap/include/soh_ap.h @@ -0,0 +1,38 @@ +#pragma once + +#include "ast.h" +#include "output.h" + +namespace rls::transpilers::soh_ap { + +class SohApTranspiler { +public: + explicit SohApTranspiler(const rls::ast::Project& project); + + void Transpile(rls::OutputWriter& out) const; + + void GenerateFunctionDefinitionsSource(rls::OutputWriter& out) const; + void GenerateRegionsSource(rls::OutputWriter& out) const; + std::string GenerateExpression(const rls::ast::ExprPtr& expr) const; + +private: + int GetPythonPrecedence(const rls::ast::ExprPtr& expr) const; + std::string GenerateChildExpression(const rls::ast::ExprPtr& expr, int parentPrec, bool isRightChild = false) const; + std::string GenerateExpression(const rls::ast::BoolLiteral& node) const; + std::string GenerateExpression(const rls::ast::IntLiteral& node) const; + std::string GenerateExpression(const rls::ast::Identifier& node) const; + std::string GenerateExpression(const rls::ast::UnaryExpr& node) const; + std::string GenerateExpression(const rls::ast::BinaryExpr& node) const; + std::string GenerateExpression(const rls::ast::TernaryExpr& node) const; + std::string GenerateExpression(const rls::ast::CallExpr& node) const; + std::string GenerateExpression(const rls::ast::SharedBlock& node) const; + std::string GenerateExpression(const rls::ast::AnyAgeBlock& node) const; + std::string GenerateExpression(const rls::ast::MatchExpr& node) const; + std::string GenerateExpression(const rls::ast::Expr::Variant& node) const; + + const rls::ast::Project& project; +}; + +void Transpile(const rls::ast::Project& project, rls::OutputWriter& out); + +} // namespace rls::transpilers::soh_ap diff --git a/transpilers/ap/src/generate_expression.cpp b/transpilers/soh_ap/src/generate_expression.cpp similarity index 57% rename from transpilers/ap/src/generate_expression.cpp rename to transpilers/soh_ap/src/generate_expression.cpp index a34f24b..9f43e21 100644 --- a/transpilers/ap/src/generate_expression.cpp +++ b/transpilers/soh_ap/src/generate_expression.cpp @@ -1,50 +1,28 @@ -namespace rls::transpilers::ap { - -#include "generate_expression.h" +#include "soh_ap.h" #include #include +#include +#include -namespace rls::transpilers::soh { +namespace rls::transpilers::soh_ap { -std::string GenerateExpression(const rls::ast::Expr::Variant& node); - -static std::string GenerateExpression(const rls::ast::BoolLiteral& node) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::BoolLiteral& node) const { return node.value ? "True" : "False"; } -static std::string GenerateExpression(const rls::ast::IntLiteral& node) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::IntLiteral& node) const { return std::to_string(node.value); } -static std::string GenerateExpression(const rls::ast::Identifier& node) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::Identifier& node) const { return node.name; } -static std::string GenerateExpression(const rls::ast::KeywordExpr& node) { - switch (node.keyword) { - case rls::ast::Keyword::IsChild: - return "is_child(bundle)"; - case rls::ast::Keyword::IsAdult: - return "is_adult(bundle)"; - case rls::ast::Keyword::AtDay: - return "at_day(bundle)"; - case rls::ast::Keyword::AtNight: - return "at_night(bundle)"; - // TODO Handle IsVanilla and IsMq - case rls::ast::Keyword::IsVanilla: - return "NOT IMPLEMENTED"; - case rls::ast::Keyword::IsMq: - return "NOT IMPLEMENTED"; - default: - return ""; - } -} - // Returns the Python operator precedence for an expression node. // Lower values bind tighter. Non-compound nodes return 0 (tightest). // Precedence is from https://docs.python.org/3/reference/expressions.html#operator-precedence -static int GetPythonPrecedence(const rls::ast::ExprPtr& expr) { +int SohApTranspiler::GetPythonPrecedence(const rls::ast::ExprPtr& expr) const { if (auto* bin = std::get_if(&expr->node)) { switch (bin->op) { case rls::ast::BinaryOp::Mul: @@ -76,8 +54,9 @@ static int GetPythonPrecedence(const rls::ast::ExprPtr& expr) { // Generates an expression, wrapping in parentheses when the child's Python // precedence is looser than the parent's (or equal on the right side of // a left-associative operator). -static std::string GenerateChildExpression( - const rls::ast::ExprPtr& expr, int parentPrec, bool isRightChild = false) +std::string SohApTranspiler::GenerateChildExpression( + const rls::ast::ExprPtr& expr, int parentPrec, bool isRightChild) + const { auto result = GenerateExpression(expr); int childPrec = GetPythonPrecedence(expr); @@ -87,7 +66,7 @@ static std::string GenerateChildExpression( return result; } -static std::string GenerateExpression(const rls::ast::UnaryExpr& node) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::UnaryExpr& node) const { switch (node.op) { case rls::ast::UnaryOp::Not: return "!" + GenerateChildExpression(node.operand, 3); @@ -96,16 +75,7 @@ static std::string GenerateExpression(const rls::ast::UnaryExpr& node) { } } -static std::string GenerateExpression(const rls::ast::UnaryExpr& node) { - switch (node.op) { - case rls::ast::UnaryOp::Not: - return "not " + GenerateChildExpression(node.operand, 13); - default: - return ""; - } -} - -static std::string GenerateExpression(const rls::ast::BinaryExpr& node) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::BinaryExpr& node) const { switch (node.op) { case rls::ast::BinaryOp::And: return GenerateChildExpression(node.left, 14) + " and " + GenerateChildExpression(node.right, 14, true); @@ -137,40 +107,85 @@ static std::string GenerateExpression(const rls::ast::BinaryExpr& node) { } // Python ternary syntax is "a if test else b" -static std::string GenerateExpression(const rls::ast::TernaryExpr& node) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::TernaryExpr& node) const { return GenerateExpression(node.thenBranch) + " if " + - GenertateChildExpresssion(node.condition, 15) + " else " + + GenerateChildExpression(node.condition, 15) + " else " + GenerateExpression(node.elseBranch); } // TODO Handle Host Functions -static std::string GenerateExpression(const rls::ast::CallExpr& node) { - return "NOT IMPLEMENTED"; +std::string SohApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) const { + auto resolvedPtr = project.getResolvedCallArgs(&node); + if (resolvedPtr == nullptr) { + // Unknown calls or calls with semantic errors are blocked earlier in sema; + // emit empty as a defensive fallback so generation does not invent call forms. + return ""; + } + const auto& resolved = *resolvedPtr; + + std::ostringstream oss; + oss << node.function << "("; + for (size_t i = 0; i < resolved.size(); ++i) { + if (i > 0) { + oss << ", "; + } + oss << GenerateExpression(resolved[i]->node); + } + oss << ")"; + return oss.str(); } // TODO Figure out Shared blocks -static std::string GenerateExpression(const rls::ast::SharedBlock& node) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::SharedBlock& node) const { return "NOT IMPLEMENTED"; } // TODO Figure out AnyAge Blocks // The Python AP implementation doesn't currently have an AnyAge function -static std::string GenerateExpression(const rls::ast::AnyAgeBlock& node) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::AnyAgeBlock& node) const { return "NOT IMPLEMENTED"; +} + +std::string SohApTranspiler::GenerateExpression(const rls::ast::MatchExpr& node) const { + std::ostringstream oss; + oss << "soh_match("; + + for (size_t i = 0; i < node.arms.size(); i++) { + const auto& arm = node.arms[i]; + + if (i > 0) oss << ", "; + + // Condition - lambda discriminant: discriminant == P1 or discriminant == P2 + if (arm.isDefault) { + oss << "(lambda: true), "; + } else { + oss << "(lambda " << node.discriminant << ": "; + for (size_t j = 0; j < arm.patterns.size(); j++) { + if (j > 0) oss << " or "; + oss << node.discriminant << " == " << arm.patterns[j]; + } + oss << "), "; + } + + // Body - lambda: + oss << "(lambda: " << GenerateExpression(arm.body) << "), "; + + // Fallthrough flag + oss << (arm.fallthrough ? "True" : "False"); + } -// TODO Figure out Match Statements -static std::string GenerateExpression(const rls::ast::MatchExpr& node) { - return "NOT IMPLEMENTED"; + oss << ")"; + return oss.str(); } -static std::string GenerateExpression(const rls::ast::Expr::Variant& node) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::Expr::Variant& node) const { return std::visit([&](const auto& node) { - return GenerateExpression(node); + return SohApTranspiler::GenerateExpression(node); }, node); } -std::string GenerateExpression(const rls::ast::ExprPtr& expr) { +std::string SohApTranspiler::GenerateExpression(const rls::ast::ExprPtr& expr) const { return GenerateExpression(expr->node); } -} // rls::transpilers::ap +} // rls::transpilers::soh_ap diff --git a/transpilers/soh_ap/src/generate_functions.cpp b/transpilers/soh_ap/src/generate_functions.cpp new file mode 100644 index 0000000..206e59a --- /dev/null +++ b/transpilers/soh_ap/src/generate_functions.cpp @@ -0,0 +1,72 @@ +#include "soh_ap.h" + +#include + +using AT = rls::ast::Type; + +namespace rls::transpilers::soh_ap { + +template +std::string nodeType(const rls::ast::Project& p, const T* node) { + const auto type = p.getType(node); + if (!type.has_value()) { + return "missing_type"; + } + + switch (type.value()) { + case AT::Bool: return "bool"; + case AT::Int: return "int"; + case AT::Item: return "RandomizerGet"; + case AT::Enemy: return "RandomizerEnemy"; + case AT::Distance: return "EnemyDistance"; + case AT::Trick: return "RandomizerTrick"; + case AT::Setting: return "RandomizerSettingKey"; + case AT::Region: return "RandomizerRegion"; + case AT::Check: return "RandomizerCheck"; + case AT::Logic: return "LogicVal"; + case AT::Scene: return "SceneID"; + case AT::Dungeon: return "DungeonKey"; + case AT::Area: return "RandomizerArea"; + case AT::Trial: return "TrialKey"; + case AT::WaterLevel: return "RandoWaterLeve"; + default: return "unsupported_type"; + } +} + +std::string functionSignature( + const SohApTranspiler& transpiler, + const rls::ast::Project& p, + const rls::ast::DefineDecl* decl, + const bool includeDefaults) +{ + std::ostringstream sig; + sig << "def " << decl->name << "("; + for (int i = 0; i < decl->params.size(); i++) { + const auto& param = decl->params[i]; + sig << param.name << ": " << nodeType(p, ¶m); + if (includeDefaults && param.defaultValue != nullptr) { + sig << " = " + transpiler.GenerateExpression(param.defaultValue); + } + if (i < decl->params.size() - 1) { + sig << ", "; + } + } + sig << ") -> " << nodeType(p, decl->body.get()) ; + return sig.str(); +} + +void SohApTranspiler::GenerateFunctionDefinitionsSource(rls::OutputWriter& out) const { + auto& source = out.open("functions.gen.py"); + source << "# Generated by RLS soh_ap transpiler\n\n" + << "from .Enums import *\n"; + + + for (const auto& [name, decl] : project.DefineDecls) { + source << "\n"; + + source << functionSignature(*this, project, decl, false) << ":\n"; + source << " return " << GenerateExpression(decl->body) << "\n"; + } +} + +} \ No newline at end of file diff --git a/transpilers/soh_ap/src/generate_regions.cpp b/transpilers/soh_ap/src/generate_regions.cpp new file mode 100644 index 0000000..53643a0 --- /dev/null +++ b/transpilers/soh_ap/src/generate_regions.cpp @@ -0,0 +1,114 @@ +#include "soh_ap.h" + +#include +#include + +namespace rls::transpilers::soh_ap { + +using EntryWriter = std::function; +void WriteEntries(const std::vector& sections, + rls::ast::SectionKind sectionKind, EntryWriter writer) { + for (const auto& section : sections) { + if (section.kind == sectionKind) { + for (const auto& entry : section.entries) { + writer(entry); + } + } + } +} + +void WriteEvents( + const SohApTranspiler& transpiler, + std::ostream& source, + const std::vector& sections) +{ + WriteEntries(sections, rls::ast::SectionKind::Events, [&](const rls::ast::Entry& entry){ + source << " (EventLocations." << entry.name << ", " << transpiler.GenerateExpression(entry.condition) << "),\n"; + }); +} + +void WriteLocations( + const SohApTranspiler& transpiler, + std::ostream& source, + const std::vector& sections) +{ + WriteEntries(sections, rls::ast::SectionKind::Locations, [&](const rls::ast::Entry& entry){ + source << " (Locations." << entry.name << ", " << transpiler.GenerateExpression(entry.condition) << "),\n"; + }); +} + +void WriteExits( + const SohApTranspiler& transpiler, + std::ostream& source, + const std::vector& sections) +{ + WriteEntries(sections, rls::ast::SectionKind::Exits, [&](const rls::ast::Entry& entry){ + source << " (Regions." << entry.name << ", " << transpiler.GenerateExpression(entry.condition) << "),\n"; + }); +} + +void SohApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { + auto& source = out.open("regions.gen.py"); + source << "# Generated by RLS soh_ap transpiler\n" + << "\n" + << "from ...LogicHelpers import *\n" + << "\n" + << "if TYPE_CHECKING:\n" + << " from ... import SohWorld\n\n" + << "def set_region_rules(world: \"SohWorld\") -> None:\n"; + + // TODO figure out local events and event locations + + for (const auto& [regionName, region] : project.RegionDecls) { + const auto [extendRegionBegin, extendRegionEnd] = project.ExtendRegionDecls.equal_range(region->key); + + std::string creationString = "Regions." + region->key + ", world, [\n"; + + source << " # " << region->body.name << "\n" + << " # Events\n"; + + source << " add_events(" << creationString; + WriteEvents(*this, source, region->body.sections); + for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { + WriteEvents(*this, source, it->second->sections); + } + source << " ])\n # Locations\n" + << " add_locations(" << creationString; + WriteLocations(*this, source, region->body.sections); + for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { + WriteLocations(*this, source, it->second->sections); + } + source << " ])\n # Exits\n" + << " connect_regions(" << creationString; + WriteExits(*this, source, region->body.sections); + for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { + WriteExits(*this, source, it->second->sections); + } + source << " ])\n\n"; + + // source << "areaTable[" << region->key << "] = Region(" + // << "\"" << region->body.name << "\", " + // << region->body.scene.value() << ", "; + + // source << "{\n // Events\n"; + // WriteEvents(source, region->body.sections); + // for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { + // WriteEvents(source, it->second->sections); + // } + // source << "}, {\n // Locations\n"; + // WriteLocations(source, region->body.sections); + // for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { + // WriteLocations(source, it->second->sections); + // } + // source << "}, {\n // Exits\n"; + // WriteExits(source, region->body.sections); + // for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { + // WriteExits(source, it->second->sections); + // } + // source << "});\n\n"; + } + + // source << "}\n"; +} + +} // namespace rls::transpilers::soh_ap \ No newline at end of file diff --git a/transpilers/soh_ap/src/generate_regions.h b/transpilers/soh_ap/src/generate_regions.h new file mode 100644 index 0000000..b15407f --- /dev/null +++ b/transpilers/soh_ap/src/generate_regions.h @@ -0,0 +1,10 @@ +// #pragma once + +// #include "ast.h" +// #include "output.h" + +// namespace rls::transpilers::soh_ap { + +// void GenerateRegionsSource(const rls::ast::Project& project, rls::OutputWriter& out); + +// } // namespace rls::transpilers::soh_ap \ No newline at end of file diff --git a/transpilers/soh_ap/src/rls_match.py b/transpilers/soh_ap/src/rls_match.py new file mode 100644 index 0000000..669fb3a --- /dev/null +++ b/transpilers/soh_ap/src/rls_match.py @@ -0,0 +1,46 @@ +from typing import Any +from enum import IntEnum, auto + +class EnemyDistances(IntEnum): + ED_CLOSE = auto() + ED_SHORT_JUMPSLASH = auto() + ED_MASTER_SWORD_JUMPSLASH = auto() + ED_LONG_JUMPSLASH = auto() + ED_BOMB_THROW = auto() + ED_BOOMERANG = auto() + ED_HOOKSHOT = auto() + ED_LONGSHOT = auto() + ED_FAR = auto() + +class RandomizerGet(IntEnum): + RG_BOOMERANG = auto() + RG_HOOKSHOT = auto() + RG_LONGSHOT = auto() + +# Unpack members into current namespace +RG_BOOMERANG, RG_HOOKSHOT, RG_LONGSHOT = RandomizerGet +ED_CLOSE, ED_SHORT_JUMPSLASH, ED_MASTER_SWORD_JUMPSLASH, ED_LONG_JUMPSLASH, ED_BOMB_THROW, ED_BOOMERANG, ED_HOOKSHOT, ED_LONGSHOT, ED_FAR = EnemyDistances + +# not sure how this active variable is used or changed +active = False + +def rls_match(compare, condition, body, fallthrough: bool, *args) -> Any: + if len(args) == 0: + if active or condition(compare): return body() + # Choose default for type to return + if type(body())== bool: return False + if type(body()) == int: return 0 + else: + if active or condition(compare): + if fallthrough: + if bool(body()): return body() + return rls_match(compare, *args) + return body() + return rls_match(compare, *args) + +distance = EnemyDistances.ED_FAR + +def can_use(x): + return True + +print(rls_match(distance, (lambda distance: distance == ED_CLOSE or distance == ED_SHORT_JUMPSLASH or distance == ED_MASTER_SWORD_JUMPSLASH or distance == ED_LONG_JUMPSLASH or distance == ED_BOMB_THROW or distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG)), True, (lambda distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT)), False)) \ No newline at end of file diff --git a/transpilers/soh_ap/src/soh_ap.cpp b/transpilers/soh_ap/src/soh_ap.cpp new file mode 100644 index 0000000..d324252 --- /dev/null +++ b/transpilers/soh_ap/src/soh_ap.cpp @@ -0,0 +1,13 @@ +#include "soh_ap.h" + +namespace rls::transpilers::soh_ap { + +SohApTranspiler::SohApTranspiler(const rls::ast::Project& project) + : project(project) {} + +void SohApTranspiler::Transpile(rls::OutputWriter& out) const { + GenerateFunctionDefinitionsSource(out); + GenerateRegionsSource(out); +} + +} // namespace rls::transpilers::soh_ap diff --git a/transpilers/ap/tests/ap_tests.cpp b/transpilers/soh_ap/tests/ap_tests.cpp similarity index 89% rename from transpilers/ap/tests/ap_tests.cpp rename to transpilers/soh_ap/tests/ap_tests.cpp index a0b30b2..fe67d25 100644 --- a/transpilers/ap/tests/ap_tests.cpp +++ b/transpilers/soh_ap/tests/ap_tests.cpp @@ -4,7 +4,7 @@ #include #include -#include "ap.h" +#include "soh_ap.h" namespace { @@ -29,7 +29,7 @@ TEST(ApTests, GeneratesHeaderComment) { const rls::ast::Project project{}; MemoryWriter out; - rls::transpilers::ap::Transpile(project, out); + rls::transpilers::soh_ap::SohApTranspiler(project).Transpile(out); EXPECT_EQ(out.content("ap.py"), "# Generated by RLS ap transpiler\n"); From 1f635fcc1071e3faebb6c612faa129252131c331 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Mon, 4 May 2026 21:38:44 -0400 Subject: [PATCH 03/22] Fix region lambdas --- examples/soh_ap/regions.gen.py | 368 ++++++++++---------- transpilers/soh_ap/src/generate_regions.cpp | 6 +- 2 files changed, 187 insertions(+), 187 deletions(-) diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py index a14dbf1..ae1d09e 100644 --- a/examples/soh_ap/regions.gen.py +++ b/examples/soh_ap/regions.gen.py @@ -15,7 +15,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_ADULT_SPAWN, world, [ - (Regions.RR_TEMPLE_OF_TIME, True), + (Regions.RR_TEMPLE_OF_TIME, lambda bundle: True), ]) # Bolero of Fire Warp @@ -27,7 +27,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_BOLERO_OF_FIRE_WARP, world, [ - (Regions.RR_DMC_PAD_ENTRY, True), + (Regions.RR_DMC_PAD_ENTRY, lambda bundle: True), ]) # Child Spawn @@ -39,7 +39,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_CHILD_SPAWN, world, [ - (Regions.RR_KF_LINKS_HOUSE, True), + (Regions.RR_KF_LINKS_HOUSE, lambda bundle: True), ]) # KF Boulder Loop @@ -48,16 +48,16 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_BOULDER_LOOP, world, [ - (Locations.RC_KF_KOKIRI_SWORD_CHEST, is_child() and has(RG_OPEN_CHEST)), - (Locations.RC_KF_CHILD_GRASS_MAZE_1, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_MAZE_2, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_MAZE_3, is_child() and can_cut_shrubs()), - (Locations.RC_KF_BOULDER_RUPEE_1, is_child()), - (Locations.RC_KF_BOULDER_RUPEE_2, is_child()), + (Locations.RC_KF_KOKIRI_SWORD_CHEST, lambda bundle: is_child() and has(RG_OPEN_CHEST)), + (Locations.RC_KF_CHILD_GRASS_MAZE_1, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_MAZE_2, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_MAZE_3, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_BOULDER_RUPEE_1, lambda bundle: is_child()), + (Locations.RC_KF_BOULDER_RUPEE_2, lambda bundle: is_child()), ]) # Exits connect_regions(Regions.RR_KF_BOULDER_LOOP, world, [ - (Regions.RR_KOKIRI_FOREST, can_use(RG_CRAWL)), + (Regions.RR_KOKIRI_FOREST, lambda bundle: can_use(RG_CRAWL)), ]) # KF House of Twins @@ -66,12 +66,12 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_HOUSE_OF_TWINS, world, [ - (Locations.RC_KF_TWINS_HOUSE_POT_1, has(RG_POWER_BRACELET)), - (Locations.RC_KF_TWINS_HOUSE_POT_2, has(RG_POWER_BRACELET)), + (Locations.RC_KF_TWINS_HOUSE_POT_1, lambda bundle: has(RG_POWER_BRACELET)), + (Locations.RC_KF_TWINS_HOUSE_POT_2, lambda bundle: has(RG_POWER_BRACELET)), ]) # Exits connect_regions(Regions.RR_KF_HOUSE_OF_TWINS, world, [ - (Regions.RR_KOKIRI_FOREST, True), + (Regions.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Know It All House @@ -80,12 +80,12 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ - (Locations.RC_KF_BROTHERS_HOUSE_POT_1, has(RG_POWER_BRACELET)), - (Locations.RC_KF_BROTHERS_HOUSE_POT_2, has(RG_POWER_BRACELET)), + (Locations.RC_KF_BROTHERS_HOUSE_POT_1, lambda bundle: has(RG_POWER_BRACELET)), + (Locations.RC_KF_BROTHERS_HOUSE_POT_2, lambda bundle: has(RG_POWER_BRACELET)), ]) # Exits connect_regions(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ - (Regions.RR_KOKIRI_FOREST, True), + (Regions.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Kokiri Shop @@ -94,18 +94,18 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_KOKIRI_SHOP, world, [ - (Locations.RC_KF_SHOP_ITEM_1, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_2, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_3, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_4, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_5, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_6, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_7, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_8, has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_1, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_2, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_3, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_4, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_5, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_6, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_7, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (Locations.RC_KF_SHOP_ITEM_8, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), ]) # Exits connect_regions(Regions.RR_KF_KOKIRI_SHOP, world, [ - (Regions.RR_KOKIRI_FOREST, True), + (Regions.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Link's House @@ -114,12 +114,12 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_LINKS_HOUSE, world, [ - (Locations.RC_KF_LINKS_HOUSE_POT, has(RG_POWER_BRACELET)), - (Locations.RC_KF_LINKS_HOUSE_COW, is_adult() and can_use(RG_EPONAS_SONG) and flag(LOGIC_LINKS_COW)), + (Locations.RC_KF_LINKS_HOUSE_POT, lambda bundle: has(RG_POWER_BRACELET)), + (Locations.RC_KF_LINKS_HOUSE_COW, lambda bundle: is_adult() and can_use(RG_EPONAS_SONG) and flag(LOGIC_LINKS_COW)), ]) # Exits connect_regions(Regions.RR_KF_LINKS_HOUSE, world, [ - (Regions.RR_KF_LINKS_PORCH, True), + (Regions.RR_KF_LINKS_PORCH, lambda bundle: True), ]) # KF Link's Porch @@ -131,8 +131,8 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_KF_LINKS_PORCH, world, [ - (Regions.RR_KF_LINKS_HOUSE, True), - (Regions.RR_KOKIRI_FOREST, True), + (Regions.RR_KF_LINKS_HOUSE, lambda bundle: True), + (Regions.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Mido's House @@ -141,37 +141,37 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_MIDOS_HOUSE, world, [ - (Locations.RC_KF_MIDOS_TOP_LEFT_CHEST, has(RG_OPEN_CHEST)), - (Locations.RC_KF_MIDOS_TOP_RIGHT_CHEST, has(RG_OPEN_CHEST)), - (Locations.RC_KF_MIDOS_BOTTOM_LEFT_CHEST, has(RG_OPEN_CHEST)), - (Locations.RC_KF_MIDOS_BOTTOM_RIGHT_CHEST, has(RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_TOP_LEFT_CHEST, lambda bundle: has(RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_TOP_RIGHT_CHEST, lambda bundle: has(RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_BOTTOM_LEFT_CHEST, lambda bundle: has(RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_BOTTOM_RIGHT_CHEST, lambda bundle: has(RG_OPEN_CHEST)), ]) # Exits connect_regions(Regions.RR_KF_MIDOS_HOUSE, world, [ - (Regions.RR_KOKIRI_FOREST, True), + (Regions.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Outside Deku Tree # Events add_events(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (EventLocations.LOGIC_STICK_ACCESS, can_get_deku_baba_sticks()), - (EventLocations.LOGIC_NUT_ACCESS, can_get_deku_baba_nuts()), - (EventLocations.LOGIC_FAIRY_ACCESS, call_gossip_fairy_except_suns()), - (EventLocations.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, is_child() and has(RG_SPEAK_KOKIRI) and can_use(RG_KOKIRI_SWORD) and can_use(RG_DEKU_SHIELD)), + (EventLocations.LOGIC_STICK_ACCESS, lambda bundle: can_get_deku_baba_sticks()), + (EventLocations.LOGIC_NUT_ACCESS, lambda bundle: can_get_deku_baba_nuts()), + (EventLocations.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns()), + (EventLocations.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child() and has(RG_SPEAK_KOKIRI) and can_use(RG_KOKIRI_SWORD) and can_use(RG_DEKU_SHIELD)), ]) # Locations add_locations(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE, True), - (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, True), - (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY, call_gossip_fairy_except_suns()), - (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG, can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY, call_gossip_fairy_except_suns()), - (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG, can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE, lambda bundle: True), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, lambda bundle: True), + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), ]) # Exits connect_regions(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (Regions.RR_DEKU_TREE_ENTRYWAY, is_child() or setting(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF and (setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), - (Regions.RR_KOKIRI_FOREST, is_adult() and (can_pass(RE_BIG_SKULLTULA, ED_CLOSE, True) or flag(LOGIC_DEKU_TREE_CLEAR)) or setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), + (Regions.RR_DEKU_TREE_ENTRYWAY, lambda bundle: is_child() or setting(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF and (setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), + (Regions.RR_KOKIRI_FOREST, lambda bundle: is_adult() and (can_pass(RE_BIG_SKULLTULA, ED_CLOSE, True) or flag(LOGIC_DEKU_TREE_CLEAR)) or setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), ]) # KF Outside Lost Woods @@ -180,23 +180,23 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ - (Locations.RC_KF_GOSSIP_STONE, True), - (Locations.RC_KF_BEAN_RUPEE_1, is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_2, is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_3, is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_4, is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_5, is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_6, is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RED_RUPEE, is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_GOSSIP_STONE_FAIRY, call_gossip_fairy_except_suns()), - (Locations.RC_KF_GOSSIP_STONE_FAIRY_BIG, can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_GOSSIP_STONE, lambda bundle: True), + (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), + (Locations.RC_KF_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), + (Locations.RC_KF_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), ]) # Exits connect_regions(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ - (Regions.RR_KOKIRI_FOREST, True), - (Regions.RR_THE_LOST_WOODS, True), - (Regions.RR_KF_RUPEE_ALCOVE, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS))), - (Regions.RR_KF_STORMS_GROTTO, can_open_storms_grotto()), + (Regions.RR_KOKIRI_FOREST, lambda bundle: True), + (Regions.RR_THE_LOST_WOODS, lambda bundle: True), + (Regions.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS))), + (Regions.RR_KF_STORMS_GROTTO, lambda bundle: can_open_storms_grotto()), ]) # KF Alcove @@ -205,17 +205,17 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_RUPEE_ALCOVE, world, [ - (Locations.RC_KF_BEAN_RUPEE_1, is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_2, is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_3, is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_4, is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_5, is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_6, is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RED_RUPEE, is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), ]) # Exits connect_regions(Regions.RR_KF_RUPEE_ALCOVE, world, [ - (Regions.RR_KOKIRI_FOREST, True), + (Regions.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Saria's House @@ -224,118 +224,118 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_SARIAS_HOUSE, world, [ - (Locations.RC_KF_SARIAS_TOP_LEFT_HEART, True), - (Locations.RC_KF_SARIAS_TOP_RIGHT_HEART, True), - (Locations.RC_KF_SARIAS_BOTTOM_LEFT_HEART, True), - (Locations.RC_KF_SARIAS_BOTTOM_RIGHT_HEART, True), + (Locations.RC_KF_SARIAS_TOP_LEFT_HEART, lambda bundle: True), + (Locations.RC_KF_SARIAS_TOP_RIGHT_HEART, lambda bundle: True), + (Locations.RC_KF_SARIAS_BOTTOM_LEFT_HEART, lambda bundle: True), + (Locations.RC_KF_SARIAS_BOTTOM_RIGHT_HEART, lambda bundle: True), ]) # Exits connect_regions(Regions.RR_KF_SARIAS_HOUSE, world, [ - (Regions.RR_KOKIRI_FOREST, True), + (Regions.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Storms Grotto # Events add_events(Regions.RR_KF_STORMS_GROTTO, world, [ - (EventLocations.LOGIC_FAIRY_ACCESS, call_gossip_fairy() or can_use(RG_STICKS)), - (EventLocations.LOGIC_BUG_ACCESS, can_cut_shrubs()), - (EventLocations.LOGIC_FISH_ACCESS, True), + (EventLocations.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy() or can_use(RG_STICKS)), + (EventLocations.LOGIC_BUG_ACCESS, lambda bundle: can_cut_shrubs()), + (EventLocations.LOGIC_FISH_ACCESS, lambda bundle: True), ]) # Locations add_locations(Regions.RR_KF_STORMS_GROTTO, world, [ - (Locations.RC_KF_STORMS_GROTTO_CHEST, has(RG_OPEN_CHEST)), - (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE, True), - (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_LEFT, can_break_lower_beehives()), - (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT, can_break_lower_beehives()), - (Locations.RC_KF_STORMS_GROTTO_FISH, has_bottle()), - (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY, call_gossip_fairy()), - (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG, can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_STORMS_GROTTO_GRASS_1, can_cut_shrubs()), - (Locations.RC_KF_STORMS_GROTTO_GRASS_2, can_cut_shrubs()), - (Locations.RC_KF_STORMS_GROTTO_GRASS_3, can_cut_shrubs()), - (Locations.RC_KF_STORMS_GROTTO_GRASS_4, can_cut_shrubs()), + (Locations.RC_KF_STORMS_GROTTO_CHEST, lambda bundle: has(RG_OPEN_CHEST)), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE, lambda bundle: True), + (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_LEFT, lambda bundle: can_break_lower_beehives()), + (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT, lambda bundle: can_break_lower_beehives()), + (Locations.RC_KF_STORMS_GROTTO_FISH, lambda bundle: has_bottle()), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy()), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_STORMS_GROTTO_GRASS_1, lambda bundle: can_cut_shrubs()), + (Locations.RC_KF_STORMS_GROTTO_GRASS_2, lambda bundle: can_cut_shrubs()), + (Locations.RC_KF_STORMS_GROTTO_GRASS_3, lambda bundle: can_cut_shrubs()), + (Locations.RC_KF_STORMS_GROTTO_GRASS_4, lambda bundle: can_cut_shrubs()), ]) # Exits connect_regions(Regions.RR_KF_STORMS_GROTTO, world, [ - (Regions.RR_KF_OUTSIDE_LOST_WOODS, True), + (Regions.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: True), ]) # Kokiri Forest # Events add_events(Regions.RR_KOKIRI_FOREST, world, [ - (EventLocations.LOGIC_FAIRY_ACCESS, call_gossip_fairy_except_suns() or is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), - (EventLocations.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, is_child() and has(RG_SPEAK_KOKIRI) and can_use(RG_KOKIRI_SWORD) and can_use(RG_DEKU_SHIELD)), + (EventLocations.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns() or is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (EventLocations.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child() and has(RG_SPEAK_KOKIRI) and can_use(RG_KOKIRI_SWORD) and can_use(RG_DEKU_SHIELD)), ]) # Locations add_locations(Regions.RR_KOKIRI_FOREST, world, [ - (Locations.RC_KF_CHILD_GRASS_1, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_2, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_3, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_4, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_5, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_6, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_7, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_8, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_9, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_10, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_11, is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_12, is_child() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_1, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_2, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_3, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_4, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_5, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_6, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_7, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_8, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_9, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_10, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_11, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_12, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_13, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_14, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_15, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_16, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_17, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_18, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_19, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_20, is_adult() and can_cut_shrubs()), - (Locations.RC_KF_BRIDGE_RUPEE, is_child()), - (Locations.RC_KF_BEHIND_MIDOS_RUPEE, is_child()), - (Locations.RC_KF_SOUTH_GRASS_WEST_RUPEE, is_child()), - (Locations.RC_KF_SOUTH_GRASS_EAST_RUPEE, is_child()), - (Locations.RC_KF_NORTH_GRASS_WEST_RUPEE, is_child()), - (Locations.RC_KF_NORTH_GRASS_EAST_RUPEE, is_child()), - (Locations.RC_KF_SARIAS_ROOF_WEST_HEART, is_child()), - (Locations.RC_KF_SARIAS_ROOF_EAST_HEART, is_child()), - (Locations.RC_KF_SARIAS_ROOF_NORTH_HEART, is_child()), - (Locations.RC_KF_BEAN_RUPEE_1, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_2, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_3, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_4, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_5, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_6, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RED_RUPEE, is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_GS_KNOW_IT_ALL_HOUSE, is_child() and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False) and can_get_night_time_gs()), - (Locations.RC_KF_GS_BEAN_PATCH, can_spawn_soil_skull(RG_KOKIRI_FOREST_BEAN_SOUL) and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False)), - (Locations.RC_KF_GS_HOUSE_OF_TWINS, is_adult() and can_get_night_time_gs() and (can_get_drop(RE_GOLD_SKULLTULA, ED_BOOMERANG, False) or trick(RT_KF_ADULT_GS) and can_use(RG_HOVER_BOOTS) and can_kill(RE_GOLD_SKULLTULA, ED_SHORT_JUMPSLASH, True, 1, False, False))), - (Locations.RC_KF_BEAN_SPROUT_FAIRY_1, is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_BEAN_SPROUT_FAIRY_2, is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_BEAN_SPROUT_FAIRY_3, is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_CHILD_GRASS_1, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_2, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_3, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_4, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_5, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_6, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_7, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_8, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_9, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_10, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_11, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_CHILD_GRASS_12, lambda bundle: is_child() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_1, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_2, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_3, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_4, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_5, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_6, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_7, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_8, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_9, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_10, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_11, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_12, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_13, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_14, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_15, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_16, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_17, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_18, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_19, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_ADULT_GRASS_20, lambda bundle: is_adult() and can_cut_shrubs()), + (Locations.RC_KF_BRIDGE_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_BEHIND_MIDOS_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_SOUTH_GRASS_WEST_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_SOUTH_GRASS_EAST_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_NORTH_GRASS_WEST_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_NORTH_GRASS_EAST_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_SARIAS_ROOF_WEST_HEART, lambda bundle: is_child()), + (Locations.RC_KF_SARIAS_ROOF_EAST_HEART, lambda bundle: is_child()), + (Locations.RC_KF_SARIAS_ROOF_NORTH_HEART, lambda bundle: is_child()), + (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_GS_KNOW_IT_ALL_HOUSE, lambda bundle: is_child() and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False) and can_get_night_time_gs()), + (Locations.RC_KF_GS_BEAN_PATCH, lambda bundle: can_spawn_soil_skull(RG_KOKIRI_FOREST_BEAN_SOUL) and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False)), + (Locations.RC_KF_GS_HOUSE_OF_TWINS, lambda bundle: is_adult() and can_get_night_time_gs() and (can_get_drop(RE_GOLD_SKULLTULA, ED_BOOMERANG, False) or trick(RT_KF_ADULT_GS) and can_use(RG_HOVER_BOOTS) and can_kill(RE_GOLD_SKULLTULA, ED_SHORT_JUMPSLASH, True, 1, False, False))), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_1, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_2, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_3, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), ]) # Exits connect_regions(Regions.RR_KOKIRI_FOREST, world, [ - (Regions.RR_KF_BOULDER_LOOP, can_use(RG_CRAWL)), - (Regions.RR_KF_LINKS_PORCH, can_climb_ladder() if is_child() else has(RG_CLIMB) or can_use(RG_HOVER_BOOTS)), - (Regions.RR_KF_MIDOS_HOUSE, True), - (Regions.RR_KF_SARIAS_HOUSE, True), - (Regions.RR_KF_HOUSE_OF_TWINS, True), - (Regions.RR_KF_KNOW_IT_ALL_HOUSE, True), - (Regions.RR_KF_KOKIRI_SHOP, True), - (Regions.RR_KF_OUTSIDE_DEKU_TREE, flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD) or setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or is_adult() and (can_pass(RE_BIG_SKULLTULA, ED_CLOSE, True) or flag(LOGIC_FOREST_TEMPLE_CLEAR))), - (Regions.RR_KF_OUTSIDE_LOST_WOODS, has(RG_CLIMB) or can_use(RG_HOOKSHOT) or is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or trick(RT_UNINTUITIVE_JUMPS))), - (Regions.RR_KF_RUPEE_ALCOVE, is_adult() and can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL)), - (Regions.RR_LW_BRIDGE_FROM_FOREST, is_adult() or setting(RSK_FOREST) != RO_CLOSED_FOREST_ON or flag(LOGIC_DEKU_TREE_CLEAR)), + (Regions.RR_KF_BOULDER_LOOP, lambda bundle: can_use(RG_CRAWL)), + (Regions.RR_KF_LINKS_PORCH, lambda bundle: can_climb_ladder() if is_child() else has(RG_CLIMB) or can_use(RG_HOVER_BOOTS)), + (Regions.RR_KF_MIDOS_HOUSE, lambda bundle: True), + (Regions.RR_KF_SARIAS_HOUSE, lambda bundle: True), + (Regions.RR_KF_HOUSE_OF_TWINS, lambda bundle: True), + (Regions.RR_KF_KNOW_IT_ALL_HOUSE, lambda bundle: True), + (Regions.RR_KF_KOKIRI_SHOP, lambda bundle: True), + (Regions.RR_KF_OUTSIDE_DEKU_TREE, lambda bundle: flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD) or setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or is_adult() and (can_pass(RE_BIG_SKULLTULA, ED_CLOSE, True) or flag(LOGIC_FOREST_TEMPLE_CLEAR))), + (Regions.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: has(RG_CLIMB) or can_use(RG_HOOKSHOT) or is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or trick(RT_UNINTUITIVE_JUMPS))), + (Regions.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult() and can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL)), + (Regions.RR_LW_BRIDGE_FROM_FOREST, lambda bundle: is_adult() or setting(RSK_FOREST) != RO_CLOSED_FOREST_ON or flag(LOGIC_DEKU_TREE_CLEAR)), ]) # Minuet of Forest Warp @@ -347,7 +347,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_MINUET_OF_FOREST_WARP, world, [ - (Regions.RR_SACRED_FOREST_MEADOW, True), + (Regions.RR_SACRED_FOREST_MEADOW, lambda bundle: True), ]) # Nocturne of Shadow Warp @@ -359,7 +359,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_NOCTURNE_OF_SHADOW_WARP, world, [ - (Regions.RR_GRAVEYARD_WARP_PAD_REGION, True), + (Regions.RR_GRAVEYARD_WARP_PAD_REGION, lambda bundle: True), ]) # Prelude of Light Warp @@ -371,7 +371,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_PRELUDE_OF_LIGHT_WARP, world, [ - (Regions.RR_TEMPLE_OF_TIME, True), + (Regions.RR_TEMPLE_OF_TIME, lambda bundle: True), ]) # Requiem of Spirit Warp @@ -383,33 +383,33 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_REQUIEM_OF_SPIRIT_WARP, world, [ - (Regions.RR_DESERT_COLOSSUS, True), + (Regions.RR_DESERT_COLOSSUS, lambda bundle: True), ]) # Root # Events add_events(Regions.RR_ROOT, world, [ - (EventLocations.LOGIC_KAKARIKO_GATE_OPEN, setting(RSK_KAK_GATE) == RO_KAK_GATE_OPEN), - (EventLocations.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE), - (EventLocations.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), - (EventLocations.LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), - (EventLocations.LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), - (EventLocations.LOGIC_TH_RESCUED_ALL_CARPENTERS, setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE), - (EventLocations.LOGIC_FREED_EPONA, setting(RSK_SKIP_EPONA_RACE)), + (EventLocations.LOGIC_KAKARIKO_GATE_OPEN, lambda bundle: setting(RSK_KAK_GATE) == RO_KAK_GATE_OPEN), + (EventLocations.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE), + (EventLocations.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), + (EventLocations.LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), + (EventLocations.LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), + (EventLocations.LOGIC_TH_RESCUED_ALL_CARPENTERS, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE), + (EventLocations.LOGIC_FREED_EPONA, lambda bundle: setting(RSK_SKIP_EPONA_RACE)), ]) # Locations add_locations(Regions.RR_ROOT, world, [ - (Locations.RC_LINKS_POCKET, True), - (Locations.RC_TRIFORCE_COMPLETED, collected_triforce_pieces() >= required_triforce_pieces()), - (Locations.RC_SARIA_SONG_HINT, can_use(RG_SARIAS_SONG)), - (Locations.RC_SONG_FROM_IMPA, setting(RSK_SKIP_CHILD_ZELDA)), - (Locations.RC_HC_MALON_EGG, setting(RSK_SKIP_CHILD_ZELDA)), - (Locations.RC_HC_ZELDAS_LETTER, setting(RSK_SKIP_CHILD_ZELDA)), - (Locations.RC_TOT_MASTER_SWORD, setting(RSK_SELECTED_STARTING_AGE) == RO_AGE_ADULT), + (Locations.RC_LINKS_POCKET, lambda bundle: True), + (Locations.RC_TRIFORCE_COMPLETED, lambda bundle: collected_triforce_pieces() >= required_triforce_pieces()), + (Locations.RC_SARIA_SONG_HINT, lambda bundle: can_use(RG_SARIAS_SONG)), + (Locations.RC_SONG_FROM_IMPA, lambda bundle: setting(RSK_SKIP_CHILD_ZELDA)), + (Locations.RC_HC_MALON_EGG, lambda bundle: setting(RSK_SKIP_CHILD_ZELDA)), + (Locations.RC_HC_ZELDAS_LETTER, lambda bundle: setting(RSK_SKIP_CHILD_ZELDA)), + (Locations.RC_TOT_MASTER_SWORD, lambda bundle: setting(RSK_SELECTED_STARTING_AGE) == RO_AGE_ADULT), ]) # Exits connect_regions(Regions.RR_ROOT, world, [ - (Regions.RR_ROOT_EXITS, True), + (Regions.RR_ROOT_EXITS, lambda bundle: True), ]) # Root Exits @@ -421,14 +421,14 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_ROOT_EXITS, world, [ - (Regions.RR_CHILD_SPAWN, is_child()), - (Regions.RR_ADULT_SPAWN, is_adult()), - (Regions.RR_MINUET_OF_FOREST_WARP, can_use(RG_MINUET_OF_FOREST)), - (Regions.RR_BOLERO_OF_FIRE_WARP, can_use(RG_BOLERO_OF_FIRE)), - (Regions.RR_SERENADE_OF_WATER_WARP, can_use(RG_SERENADE_OF_WATER)), - (Regions.RR_NOCTURNE_OF_SHADOW_WARP, can_use(RG_NOCTURNE_OF_SHADOW)), - (Regions.RR_REQUIEM_OF_SPIRIT_WARP, can_use(RG_REQUIEM_OF_SPIRIT)), - (Regions.RR_PRELUDE_OF_LIGHT_WARP, can_use(RG_PRELUDE_OF_LIGHT)), + (Regions.RR_CHILD_SPAWN, lambda bundle: is_child()), + (Regions.RR_ADULT_SPAWN, lambda bundle: is_adult()), + (Regions.RR_MINUET_OF_FOREST_WARP, lambda bundle: can_use(RG_MINUET_OF_FOREST)), + (Regions.RR_BOLERO_OF_FIRE_WARP, lambda bundle: can_use(RG_BOLERO_OF_FIRE)), + (Regions.RR_SERENADE_OF_WATER_WARP, lambda bundle: can_use(RG_SERENADE_OF_WATER)), + (Regions.RR_NOCTURNE_OF_SHADOW_WARP, lambda bundle: can_use(RG_NOCTURNE_OF_SHADOW)), + (Regions.RR_REQUIEM_OF_SPIRIT_WARP, lambda bundle: can_use(RG_REQUIEM_OF_SPIRIT)), + (Regions.RR_PRELUDE_OF_LIGHT_WARP, lambda bundle: can_use(RG_PRELUDE_OF_LIGHT)), ]) # Serenade of Water Warp @@ -440,6 +440,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_SERENADE_OF_WATER_WARP, world, [ - (Regions.RR_LAKE_HYLIA, True), + (Regions.RR_LAKE_HYLIA, lambda bundle: True), ]) diff --git a/transpilers/soh_ap/src/generate_regions.cpp b/transpilers/soh_ap/src/generate_regions.cpp index 53643a0..6749332 100644 --- a/transpilers/soh_ap/src/generate_regions.cpp +++ b/transpilers/soh_ap/src/generate_regions.cpp @@ -23,7 +23,7 @@ void WriteEvents( const std::vector& sections) { WriteEntries(sections, rls::ast::SectionKind::Events, [&](const rls::ast::Entry& entry){ - source << " (EventLocations." << entry.name << ", " << transpiler.GenerateExpression(entry.condition) << "),\n"; + source << " (EventLocations." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; }); } @@ -33,7 +33,7 @@ void WriteLocations( const std::vector& sections) { WriteEntries(sections, rls::ast::SectionKind::Locations, [&](const rls::ast::Entry& entry){ - source << " (Locations." << entry.name << ", " << transpiler.GenerateExpression(entry.condition) << "),\n"; + source << " (Locations." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; }); } @@ -43,7 +43,7 @@ void WriteExits( const std::vector& sections) { WriteEntries(sections, rls::ast::SectionKind::Exits, [&](const rls::ast::Entry& entry){ - source << " (Regions." << entry.name << ", " << transpiler.GenerateExpression(entry.condition) << "),\n"; + source << " (Regions." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; }); } From 990409aa1a152f940f9332fdbb6391ca422fcdc9 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Mon, 4 May 2026 22:06:16 -0400 Subject: [PATCH 04/22] Add missing space and comment out code that was ued in the development of rls_match.py for now --- transpilers/soh_ap/src/rls_match.py | 52 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/transpilers/soh_ap/src/rls_match.py b/transpilers/soh_ap/src/rls_match.py index 669fb3a..196090b 100644 --- a/transpilers/soh_ap/src/rls_match.py +++ b/transpilers/soh_ap/src/rls_match.py @@ -1,25 +1,25 @@ from typing import Any -from enum import IntEnum, auto - -class EnemyDistances(IntEnum): - ED_CLOSE = auto() - ED_SHORT_JUMPSLASH = auto() - ED_MASTER_SWORD_JUMPSLASH = auto() - ED_LONG_JUMPSLASH = auto() - ED_BOMB_THROW = auto() - ED_BOOMERANG = auto() - ED_HOOKSHOT = auto() - ED_LONGSHOT = auto() - ED_FAR = auto() - -class RandomizerGet(IntEnum): - RG_BOOMERANG = auto() - RG_HOOKSHOT = auto() - RG_LONGSHOT = auto() - -# Unpack members into current namespace -RG_BOOMERANG, RG_HOOKSHOT, RG_LONGSHOT = RandomizerGet -ED_CLOSE, ED_SHORT_JUMPSLASH, ED_MASTER_SWORD_JUMPSLASH, ED_LONG_JUMPSLASH, ED_BOMB_THROW, ED_BOOMERANG, ED_HOOKSHOT, ED_LONGSHOT, ED_FAR = EnemyDistances +# from enum import IntEnum, auto + +# class EnemyDistances(IntEnum): +# ED_CLOSE = auto() +# ED_SHORT_JUMPSLASH = auto() +# ED_MASTER_SWORD_JUMPSLASH = auto() +# ED_LONG_JUMPSLASH = auto() +# ED_BOMB_THROW = auto() +# ED_BOOMERANG = auto() +# ED_HOOKSHOT = auto() +# ED_LONGSHOT = auto() +# ED_FAR = auto() + +# class RandomizerGet(IntEnum): +# RG_BOOMERANG = auto() +# RG_HOOKSHOT = auto() +# RG_LONGSHOT = auto() + +# # Unpack members into current namespace +# RG_BOOMERANG, RG_HOOKSHOT, RG_LONGSHOT = RandomizerGet +# ED_CLOSE, ED_SHORT_JUMPSLASH, ED_MASTER_SWORD_JUMPSLASH, ED_LONG_JUMPSLASH, ED_BOMB_THROW, ED_BOOMERANG, ED_HOOKSHOT, ED_LONGSHOT, ED_FAR = EnemyDistances # not sure how this active variable is used or changed active = False @@ -28,7 +28,7 @@ def rls_match(compare, condition, body, fallthrough: bool, *args) -> Any: if len(args) == 0: if active or condition(compare): return body() # Choose default for type to return - if type(body())== bool: return False + if type(body()) == bool: return False if type(body()) == int: return 0 else: if active or condition(compare): @@ -38,9 +38,9 @@ def rls_match(compare, condition, body, fallthrough: bool, *args) -> Any: return body() return rls_match(compare, *args) -distance = EnemyDistances.ED_FAR +# distance = EnemyDistances.ED_FAR -def can_use(x): - return True +# def can_use(x): +# return True -print(rls_match(distance, (lambda distance: distance == ED_CLOSE or distance == ED_SHORT_JUMPSLASH or distance == ED_MASTER_SWORD_JUMPSLASH or distance == ED_LONG_JUMPSLASH or distance == ED_BOMB_THROW or distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG)), True, (lambda distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT)), False)) \ No newline at end of file +# print(rls_match(distance, (lambda distance: distance == ED_CLOSE or distance == ED_SHORT_JUMPSLASH or distance == ED_MASTER_SWORD_JUMPSLASH or distance == ED_LONG_JUMPSLASH or distance == ED_BOMB_THROW or distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG)), True, (lambda distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT)), False)) \ No newline at end of file From 2b8da28b7cedd7a8462343d19d2c9e54324aa10e Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Tue, 5 May 2026 21:08:48 -0400 Subject: [PATCH 05/22] Fix not case Add SharedSpirit. May need special attention when creating the SharedSpirit functions. Fixed function definitions not having default values for parameters. --- examples/soh_ap/functions.gen.py | 10 +++---- .../soh_ap/src/generate_expression.cpp | 27 ++++++++++++++----- transpilers/soh_ap/src/generate_functions.cpp | 2 +- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/examples/soh_ap/functions.gen.py b/examples/soh_ap/functions.gen.py index ffb4c99..b1de344 100644 --- a/examples/soh_ap/functions.gen.py +++ b/examples/soh_ap/functions.gen.py @@ -14,7 +14,7 @@ def call_gossip_fairy() -> bool: def call_gossip_fairy_except_suns() -> bool: return can_use(RG_ZELDAS_LULLABY) or can_use(RG_EPONAS_SONG) or can_use(RG_SONG_OF_TIME) -def can_avoid(e: RandomizerEnemy, grounded: bool, quantity: int) -> bool: +def can_avoid(e: RandomizerEnemy, grounded: bool = False, quantity: int = 1) -> bool: return can_kill(e, ED_CLOSE, True, quantity, False, False) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: True), False) def can_break_lower_beehives() -> bool: @@ -35,11 +35,11 @@ def can_get_deku_baba_nuts() -> bool: def can_get_deku_baba_sticks() -> bool: return can_use_sword() or can_use(RG_BOOMERANG) -def can_get_drop(e: RandomizerEnemy, distance: EnemyDistance, above_link: bool) -> bool: +def can_get_drop(e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, above_link: bool = False) -> bool: return can_kill(e, distance, True, 1, False, False) and (distance_to_int(distance) <= distance_to_int(ED_MASTER_SWORD_JUMPSLASH) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: _can_get_drop_gold_skulltula(distance)), False, (lambda e: e == RE_KEESE or e == RE_FIRE_KEESE or e == RE_GUAY), (lambda: True), False, (lambda: true), (lambda: above_link or distance_to_int(distance) <= distance_to_int(ED_BOOMERANG) and can_use(RG_BOOMERANG)), False)) def can_get_night_time_gs() -> bool: - return at_night() and (can_use(RG_SUNS_SONG) or !setting(RSK_SKULLS_SUNS_SONG)) + return at_night() and (can_use(RG_SUNS_SONG) or not setting(RSK_SKULLS_SUNS_SONG)) def can_jumpslash() -> bool: return can_jumpslash_except_hammer() or can_use(RG_MEGATON_HAMMER) @@ -47,13 +47,13 @@ def can_jumpslash() -> bool: def can_jumpslash_except_hammer() -> bool: return can_use(RG_STICKS) or can_use_sword() -def can_kill(e: RandomizerEnemy, distance: EnemyDistance, wall_or_floor: bool, quantity: int, timer: bool, in_water: bool) -> bool: +def can_kill(e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True, quantity: int = 1, timer: bool = False, in_water: bool = False) -> bool: return soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: _can_kill_gold_skulltula(distance, wall_or_floor)), False) def can_open_storms_grotto() -> bool: return can_use(RG_SONG_OF_STORMS) and (has(RG_STONE_OF_AGONY) or trick(RT_GROTTOS_WITHOUT_AGONY)) -def can_pass(e: RandomizerEnemy, distance: EnemyDistance, wall_or_floor: bool) -> bool: +def can_pass(e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True) -> bool: return can_kill(e, distance, wall_or_floor, 1, False, False) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: True), False) def can_spawn_soil_skull(bean: RandomizerGet) -> bool: diff --git a/transpilers/soh_ap/src/generate_expression.cpp b/transpilers/soh_ap/src/generate_expression.cpp index 9f43e21..7fb7c9b 100644 --- a/transpilers/soh_ap/src/generate_expression.cpp +++ b/transpilers/soh_ap/src/generate_expression.cpp @@ -69,7 +69,7 @@ std::string SohApTranspiler::GenerateChildExpression( std::string SohApTranspiler::GenerateExpression(const rls::ast::UnaryExpr& node) const { switch (node.op) { case rls::ast::UnaryOp::Not: - return "!" + GenerateChildExpression(node.operand, 3); + return "not " + GenerateChildExpression(node.operand, 3); default: return ""; } @@ -135,15 +135,30 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) return oss.str(); } -// TODO Figure out Shared blocks +// Going to have to fiddle with this. +// I think as long as we define the SharedSpirit functions and the SharedSpiritData map this could be done std::string SohApTranspiler::GenerateExpression(const rls::ast::SharedBlock& node) const { - return "NOT IMPLEMENTED"; + std::ostringstream oss; + + const auto& firstBranch = node.branches[0]; + oss << "spirit_shared(" << firstBranch.region.value_or("") << ", " + << "(lambda: " << GenerateExpression(firstBranch.condition) << "), " + << (node.anyAge ? "true" : "false"); + + for (int i = 1; i < node.branches.size(); i++) { + oss << ", " << node.branches[i].region.value_or("") << ", " + << "(labmda:" << GenerateExpression(node.branches[i].condition) << ")"; + } + + oss << ")"; + + return oss.str(); } -// TODO Figure out AnyAge Blocks -// The Python AP implementation doesn't currently have an AnyAge function +// This one I'm not quite sure how it works. Seems like Ship calls this recursively for the current region std::string SohApTranspiler::GenerateExpression(const rls::ast::AnyAgeBlock& node) const { - return "NOT IMPLEMENTED"; + return ""; + // return "AnyAgeTime((lambda:" + GenerateExpression(node.body) + "))"; } std::string SohApTranspiler::GenerateExpression(const rls::ast::MatchExpr& node) const { diff --git a/transpilers/soh_ap/src/generate_functions.cpp b/transpilers/soh_ap/src/generate_functions.cpp index 206e59a..2e3a26e 100644 --- a/transpilers/soh_ap/src/generate_functions.cpp +++ b/transpilers/soh_ap/src/generate_functions.cpp @@ -64,7 +64,7 @@ void SohApTranspiler::GenerateFunctionDefinitionsSource(rls::OutputWriter& out) for (const auto& [name, decl] : project.DefineDecls) { source << "\n"; - source << functionSignature(*this, project, decl, false) << ":\n"; + source << functionSignature(*this, project, decl, true) << ":\n"; source << " return " << GenerateExpression(decl->body) << "\n"; } } From 51a935a984f228037152339e330498eefefcf4ab Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Sun, 10 May 2026 12:59:02 -0400 Subject: [PATCH 06/22] Update other transpiler name locations I missed --- README.md | 4 ++-- console/CMakeLists.txt | 4 ++-- console/main.cpp | 2 +- console/tests/acceptance_ap_tests.cpp | 2 +- docs/BUILDING.md | 2 +- transpilers/soh_ap/CMakeLists.txt | 8 ++++---- transpilers/soh_ap/tests/ap_tests.cpp | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 99c1a82..c0b43a6 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ RandoLogicScript -t soh -o out/soh/ src/ Multiple transpilers can be specified, each with their own output directory: ``` -RandoLogicScript -t soh -o out/soh/ -t ap -o out/ap/ src/ extra.rls +RandoLogicScript -t soh -o out/soh/ -t soh_ap -o out/ap/ src/ extra.rls ``` Input paths can be individual `.rls` files or directories (which are recursively scanned for `.rls` files). @@ -35,7 +35,7 @@ Input paths can be individual `.rls` files or directories (which are recursively | Name | Target | | ------------ | ---------------------- | | `soh` | C++ for Shipwright | -| `ap` | Python for Archipelago | +| `soh_ap` | Python for Archipelago | ## Docs diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index f844ae7..cc53293 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -2,14 +2,14 @@ add_executable(RandoLogicScript main.cpp ) -target_link_libraries(RandoLogicScript PRIVATE ast parser sema soh ap) +target_link_libraries(RandoLogicScript PRIVATE ast parser sema soh soh_ap) if(BUILD_TESTING) rls_add_gtest(console_acceptance_tests tests/acceptance_soh_tests.cpp tests/acceptance_ap_tests.cpp ) - target_link_libraries(console_acceptance_tests PRIVATE ast parser sema soh ap) + target_link_libraries(console_acceptance_tests PRIVATE ast parser sema soh soh_ap) target_compile_definitions(console_acceptance_tests PRIVATE RLS_REPO_ROOT="${CMAKE_SOURCE_DIR}" ) diff --git a/console/main.cpp b/console/main.cpp index 4e53349..0f8c65d 100644 --- a/console/main.cpp +++ b/console/main.cpp @@ -24,7 +24,7 @@ static void printUsage(const char* program) { << "Options:\n" << " -t, --transpiler -o, --output \n" << " Transpiler and output directory pair (may be repeated).\n" - << " Available transpilers: soh, ap\n" + << " Available transpilers: soh, soh_ap\n" << " -h, --help Show this help message.\n"; } diff --git a/console/tests/acceptance_ap_tests.cpp b/console/tests/acceptance_ap_tests.cpp index 066c5ab..2e204bf 100644 --- a/console/tests/acceptance_ap_tests.cpp +++ b/console/tests/acceptance_ap_tests.cpp @@ -7,7 +7,7 @@ TEST(AcceptanceAp, ExamplesRlsMatchesGolden) { const auto project = parseAndAnalyzeProject(repoPath("examples/rls"), errors); ASSERT_TRUE(errors.empty()) << joinLines(errors); - TempDirectory outputDir("ap"); + TempDirectory outputDir("soh_ap"); { DirectoryWriter writer(outputDir.path()); rls::transpilers::soh_ap::SohApTranspiler(project).Transpile(writer); diff --git a/docs/BUILDING.md b/docs/BUILDING.md index c0c6938..95da5f6 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -138,7 +138,7 @@ Acceptance tests are included in `console_acceptance_tests` and run the end-to-e pipeline over `examples/rls`. - SOH acceptance golden files: `examples/soh/*.gen.{h,cpp}` -- AP acceptance golden files: `examples/soh_ap/*.gen.py` +- SOH AP acceptance golden files: `examples/soh_ap/*.gen.py` To run only acceptance tests: diff --git a/transpilers/soh_ap/CMakeLists.txt b/transpilers/soh_ap/CMakeLists.txt index f649679..8b96c25 100644 --- a/transpilers/soh_ap/CMakeLists.txt +++ b/transpilers/soh_ap/CMakeLists.txt @@ -2,10 +2,10 @@ file(GLOB ap_sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" ) -add_library(ap STATIC ${ap_sources}) +add_library(soh_ap STATIC ${ap_sources}) -target_include_directories(ap PUBLIC include) -target_link_libraries(ap PUBLIC ast) +target_include_directories(soh_ap PUBLIC include) +target_link_libraries(soh_ap PUBLIC ast) if(BUILD_TESTING) file(GLOB ap_test_sources CONFIGURE_DEPENDS @@ -13,5 +13,5 @@ if(BUILD_TESTING) ) rls_add_gtest(ap_tests ${ap_test_sources}) - target_link_libraries(ap_tests PRIVATE ap) + target_link_libraries(ap_tests PRIVATE soh_ap) endif() diff --git a/transpilers/soh_ap/tests/ap_tests.cpp b/transpilers/soh_ap/tests/ap_tests.cpp index fe67d25..9f27491 100644 --- a/transpilers/soh_ap/tests/ap_tests.cpp +++ b/transpilers/soh_ap/tests/ap_tests.cpp @@ -32,5 +32,5 @@ TEST(ApTests, GeneratesHeaderComment) { rls::transpilers::soh_ap::SohApTranspiler(project).Transpile(out); EXPECT_EQ(out.content("ap.py"), - "# Generated by RLS ap transpiler\n"); + "# Generated by RLS soh_ap transpiler\n"); } From 92c859fcc5e85f1821ff4bd77a8e56cbaf008e70 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Sun, 10 May 2026 21:01:36 -0400 Subject: [PATCH 07/22] Implement active in rls_match correctly --- transpilers/soh_ap/src/rls_match.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/transpilers/soh_ap/src/rls_match.py b/transpilers/soh_ap/src/rls_match.py index 196090b..e0dc91b 100644 --- a/transpilers/soh_ap/src/rls_match.py +++ b/transpilers/soh_ap/src/rls_match.py @@ -21,10 +21,7 @@ # RG_BOOMERANG, RG_HOOKSHOT, RG_LONGSHOT = RandomizerGet # ED_CLOSE, ED_SHORT_JUMPSLASH, ED_MASTER_SWORD_JUMPSLASH, ED_LONG_JUMPSLASH, ED_BOMB_THROW, ED_BOOMERANG, ED_HOOKSHOT, ED_LONGSHOT, ED_FAR = EnemyDistances -# not sure how this active variable is used or changed -active = False - -def rls_match(compare, condition, body, fallthrough: bool, *args) -> Any: +def rls_match(compare, condition, body, fallthrough: bool, *args, active: bool = False) -> Any: if len(args) == 0: if active or condition(compare): return body() # Choose default for type to return @@ -34,7 +31,7 @@ def rls_match(compare, condition, body, fallthrough: bool, *args) -> Any: if active or condition(compare): if fallthrough: if bool(body()): return body() - return rls_match(compare, *args) + return rls_match(compare, *args, active=True) return body() return rls_match(compare, *args) From 988365c973ed987a79f0854f48e29df423bdc24b Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Sun, 10 May 2026 21:22:28 -0400 Subject: [PATCH 08/22] Add bundle and update to the new vector --- examples/soh_ap/functions.gen.py | 50 +++++++-------- examples/soh_ap/regions.gen.py | 64 +++++++++---------- transpilers/soh_ap/src/generate_functions.cpp | 3 +- transpilers/soh_ap/src/generate_regions.cpp | 19 ++++-- 4 files changed, 71 insertions(+), 65 deletions(-) diff --git a/examples/soh_ap/functions.gen.py b/examples/soh_ap/functions.gen.py index b1de344..2dc185f 100644 --- a/examples/soh_ap/functions.gen.py +++ b/examples/soh_ap/functions.gen.py @@ -2,77 +2,77 @@ from .Enums import * -def _can_get_drop_gold_skulltula(distance: EnemyDistance) -> bool: +def _can_get_drop_gold_skulltula(bundle: "SohWorld", distance: EnemyDistance) -> bool: return soh_match((lambda distance: distance == ED_CLOSE or distance == ED_SHORT_JUMPSLASH or distance == ED_MASTER_SWORD_JUMPSLASH or distance == ED_LONG_JUMPSLASH or distance == ED_BOMB_THROW or distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG)), True, (lambda distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT)), False) -def _can_kill_gold_skulltula(distance: EnemyDistance, wall_or_floor: bool) -> bool: +def _can_kill_gold_skulltula(bundle: "SohWorld", distance: EnemyDistance, wall_or_floor: bool) -> bool: return soh_match((lambda distance: distance == ED_CLOSE), (lambda: can_use(RG_MEGATON_HAMMER)), True, (lambda distance: distance == ED_SHORT_JUMPSLASH), (lambda: can_use(RG_KOKIRI_SWORD)), True, (lambda distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: can_use(RG_MASTER_SWORD)), True, (lambda distance: distance == ED_LONG_JUMPSLASH), (lambda: can_use(RG_BIGGORON_SWORD) or can_use(RG_STICKS)), True, (lambda distance: distance == ED_BOMB_THROW), (lambda: can_use(RG_BOMB_BAG)), True, (lambda distance: distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG) or can_use(RG_DINS_FIRE)), True, (lambda distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT) or wall_or_floor and can_use(RG_BOMBCHU_5)), True, (lambda distance: distance == ED_FAR), (lambda: can_use(RG_FAIRY_SLINGSHOT) or can_use(RG_FAIRY_BOW)), False) -def call_gossip_fairy() -> bool: +def call_gossip_fairy(bundle: "SohWorld") -> bool: return call_gossip_fairy_except_suns() or can_use(RG_SUNS_SONG) -def call_gossip_fairy_except_suns() -> bool: +def call_gossip_fairy_except_suns(bundle: "SohWorld") -> bool: return can_use(RG_ZELDAS_LULLABY) or can_use(RG_EPONAS_SONG) or can_use(RG_SONG_OF_TIME) -def can_avoid(e: RandomizerEnemy, grounded: bool = False, quantity: int = 1) -> bool: +def can_avoid(bundle: "SohWorld", e: RandomizerEnemy, grounded: bool = False, quantity: int = 1) -> bool: return can_kill(e, ED_CLOSE, True, quantity, False, False) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: True), False) -def can_break_lower_beehives() -> bool: +def can_break_lower_beehives(bundle: "SohWorld") -> bool: return can_break_upper_beehives() or can_use(RG_BOMB_BAG) -def can_break_upper_beehives() -> bool: +def can_break_upper_beehives(bundle: "SohWorld") -> bool: return hookshot_or_boomerang() or trick(RT_BOMBCHU_BEEHIVES) and can_use(RG_BOMBCHU_5) or setting(RSK_SLINGBOW_BREAK_BEEHIVES) and (can_use(RG_FAIRY_BOW) or can_use(RG_FAIRY_SLINGSHOT)) -def can_climb_ladder() -> bool: +def can_climb_ladder(bundle: "SohWorld") -> bool: return has(RG_CLIMB) or trick(RT_HOOKSHOT_LADDERS) and can_use(RG_HOOKSHOT) -def can_cut_shrubs() -> bool: +def can_cut_shrubs(bundle: "SohWorld") -> bool: return can_use(RG_KOKIRI_SWORD) or can_use(RG_BOOMERANG) or has_explosives() or has(RG_GORONS_BRACELET) or can_use(RG_MASTER_SWORD) or can_use(RG_MEGATON_HAMMER) or can_use(RG_BIGGORON_SWORD) or can_use(RG_GIANTS_KNIFE) -def can_get_deku_baba_nuts() -> bool: +def can_get_deku_baba_nuts(bundle: "SohWorld") -> bool: return can_jumpslash() or can_use(RG_FAIRY_SLINGSHOT) or can_use(RG_FAIRY_BOW) or has_explosives() or can_use(RG_DINS_FIRE) -def can_get_deku_baba_sticks() -> bool: +def can_get_deku_baba_sticks(bundle: "SohWorld") -> bool: return can_use_sword() or can_use(RG_BOOMERANG) -def can_get_drop(e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, above_link: bool = False) -> bool: +def can_get_drop(bundle: "SohWorld", e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, above_link: bool = False) -> bool: return can_kill(e, distance, True, 1, False, False) and (distance_to_int(distance) <= distance_to_int(ED_MASTER_SWORD_JUMPSLASH) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: _can_get_drop_gold_skulltula(distance)), False, (lambda e: e == RE_KEESE or e == RE_FIRE_KEESE or e == RE_GUAY), (lambda: True), False, (lambda: true), (lambda: above_link or distance_to_int(distance) <= distance_to_int(ED_BOOMERANG) and can_use(RG_BOOMERANG)), False)) -def can_get_night_time_gs() -> bool: +def can_get_night_time_gs(bundle: "SohWorld") -> bool: return at_night() and (can_use(RG_SUNS_SONG) or not setting(RSK_SKULLS_SUNS_SONG)) -def can_jumpslash() -> bool: +def can_jumpslash(bundle: "SohWorld") -> bool: return can_jumpslash_except_hammer() or can_use(RG_MEGATON_HAMMER) -def can_jumpslash_except_hammer() -> bool: +def can_jumpslash_except_hammer(bundle: "SohWorld") -> bool: return can_use(RG_STICKS) or can_use_sword() -def can_kill(e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True, quantity: int = 1, timer: bool = False, in_water: bool = False) -> bool: +def can_kill(bundle: "SohWorld", e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True, quantity: int = 1, timer: bool = False, in_water: bool = False) -> bool: return soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: _can_kill_gold_skulltula(distance, wall_or_floor)), False) -def can_open_storms_grotto() -> bool: +def can_open_storms_grotto(bundle: "SohWorld") -> bool: return can_use(RG_SONG_OF_STORMS) and (has(RG_STONE_OF_AGONY) or trick(RT_GROTTOS_WITHOUT_AGONY)) -def can_pass(e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True) -> bool: +def can_pass(bundle: "SohWorld", e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True) -> bool: return can_kill(e, distance, wall_or_floor, 1, False, False) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: True), False) -def can_spawn_soil_skull(bean: RandomizerGet) -> bool: +def can_spawn_soil_skull(bundle: "SohWorld", bean: RandomizerGet) -> bool: return is_child() and can_use(RG_BOTTLE_WITH_BUGS) and has(bean) -def can_use_sword() -> bool: +def can_use_sword(bundle: "SohWorld") -> bool: return can_use(RG_KOKIRI_SWORD) or can_use(RG_MASTER_SWORD) or can_use(RG_BIGGORON_SWORD) -def distance_to_int(distance: EnemyDistance) -> int: +def distance_to_int(bundle: "SohWorld", distance: EnemyDistance) -> int: return soh_match((lambda distance: distance == ED_CLOSE), (lambda: 0), False, (lambda distance: distance == ED_SHORT_JUMPSLASH), (lambda: 1), False, (lambda distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: 2), False, (lambda distance: distance == ED_LONG_JUMPSLASH), (lambda: 3), False, (lambda distance: distance == ED_BOMB_THROW), (lambda: 4), False, (lambda distance: distance == ED_BOOMERANG), (lambda: 5), False, (lambda distance: distance == ED_HOOKSHOT), (lambda: 6), False, (lambda distance: distance == ED_LONGSHOT), (lambda: 7), False, (lambda distance: distance == ED_FAR), (lambda: 8), False) -def has_bottle() -> bool: +def has_bottle(bundle: "SohWorld") -> bool: return bottle_count() >= 1 -def has_explosives() -> bool: +def has_explosives(bundle: "SohWorld") -> bool: return can_use(RG_BOMB_BAG) or can_use(RG_BOMBCHU_5) -def hookshot_or_boomerang() -> bool: +def hookshot_or_boomerang(bundle: "SohWorld") -> bool: return can_use(RG_HOOKSHOT) or can_use(RG_BOOMERANG) -def wallet_capacity() -> int: +def wallet_capacity(bundle: "SohWorld") -> int: return 999 if has(RG_TYCOON_WALLET) else 500 if has(RG_GIANT_WALLET) else 200 if has(RG_ADULT_WALLET) else 99 if has(RG_CHILD_WALLET) else 0 diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py index ae1d09e..6f8ac3a 100644 --- a/examples/soh_ap/regions.gen.py +++ b/examples/soh_ap/regions.gen.py @@ -49,11 +49,11 @@ def set_region_rules(world: "SohWorld") -> None: # Locations add_locations(Regions.RR_KF_BOULDER_LOOP, world, [ (Locations.RC_KF_KOKIRI_SWORD_CHEST, lambda bundle: is_child() and has(RG_OPEN_CHEST)), + (Locations.RC_KF_BOULDER_RUPEE_1, lambda bundle: is_child()), + (Locations.RC_KF_BOULDER_RUPEE_2, lambda bundle: is_child()), (Locations.RC_KF_CHILD_GRASS_MAZE_1, lambda bundle: is_child() and can_cut_shrubs()), (Locations.RC_KF_CHILD_GRASS_MAZE_2, lambda bundle: is_child() and can_cut_shrubs()), (Locations.RC_KF_CHILD_GRASS_MAZE_3, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_BOULDER_RUPEE_1, lambda bundle: is_child()), - (Locations.RC_KF_BOULDER_RUPEE_2, lambda bundle: is_child()), ]) # Exits connect_regions(Regions.RR_KF_BOULDER_LOOP, world, [ @@ -114,8 +114,8 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_LINKS_HOUSE, world, [ - (Locations.RC_KF_LINKS_HOUSE_POT, lambda bundle: has(RG_POWER_BRACELET)), (Locations.RC_KF_LINKS_HOUSE_COW, lambda bundle: is_adult() and can_use(RG_EPONAS_SONG) and flag(LOGIC_LINKS_COW)), + (Locations.RC_KF_LINKS_HOUSE_POT, lambda bundle: has(RG_POWER_BRACELET)), ]) # Exits connect_regions(Regions.RR_KF_LINKS_HOUSE, world, [ @@ -161,12 +161,12 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE, lambda bundle: True), - (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, lambda bundle: True), (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE, lambda bundle: True), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, lambda bundle: True), ]) # Exits connect_regions(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ @@ -180,7 +180,8 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ - (Locations.RC_KF_GOSSIP_STONE, lambda bundle: True), + (Locations.RC_KF_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), + (Locations.RC_KF_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), @@ -188,8 +189,7 @@ def set_region_rules(world: "SohWorld") -> None: (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), - (Locations.RC_KF_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_GOSSIP_STONE, lambda bundle: True), ]) # Exits connect_regions(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ @@ -244,16 +244,16 @@ def set_region_rules(world: "SohWorld") -> None: # Locations add_locations(Regions.RR_KF_STORMS_GROTTO, world, [ (Locations.RC_KF_STORMS_GROTTO_CHEST, lambda bundle: has(RG_OPEN_CHEST)), - (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE, lambda bundle: True), (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_LEFT, lambda bundle: can_break_lower_beehives()), (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT, lambda bundle: can_break_lower_beehives()), - (Locations.RC_KF_STORMS_GROTTO_FISH, lambda bundle: has_bottle()), (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy()), (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_STORMS_GROTTO_FISH, lambda bundle: has_bottle()), (Locations.RC_KF_STORMS_GROTTO_GRASS_1, lambda bundle: can_cut_shrubs()), (Locations.RC_KF_STORMS_GROTTO_GRASS_2, lambda bundle: can_cut_shrubs()), (Locations.RC_KF_STORMS_GROTTO_GRASS_3, lambda bundle: can_cut_shrubs()), (Locations.RC_KF_STORMS_GROTTO_GRASS_4, lambda bundle: can_cut_shrubs()), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE, lambda bundle: True), ]) # Exits connect_regions(Regions.RR_KF_STORMS_GROTTO, world, [ @@ -268,6 +268,28 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KOKIRI_FOREST, world, [ + (Locations.RC_KF_BEAN_SPROUT_FAIRY_1, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_2, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_3, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), + (Locations.RC_KF_BRIDGE_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_BEHIND_MIDOS_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_SOUTH_GRASS_WEST_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_SOUTH_GRASS_EAST_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_NORTH_GRASS_WEST_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_NORTH_GRASS_EAST_RUPEE, lambda bundle: is_child()), + (Locations.RC_KF_SARIAS_ROOF_WEST_HEART, lambda bundle: is_child()), + (Locations.RC_KF_SARIAS_ROOF_EAST_HEART, lambda bundle: is_child()), + (Locations.RC_KF_SARIAS_ROOF_NORTH_HEART, lambda bundle: is_child()), + (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), + (Locations.RC_KF_GS_KNOW_IT_ALL_HOUSE, lambda bundle: is_child() and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False) and can_get_night_time_gs()), + (Locations.RC_KF_GS_BEAN_PATCH, lambda bundle: can_spawn_soil_skull(RG_KOKIRI_FOREST_BEAN_SOUL) and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False)), + (Locations.RC_KF_GS_HOUSE_OF_TWINS, lambda bundle: is_adult() and can_get_night_time_gs() and (can_get_drop(RE_GOLD_SKULLTULA, ED_BOOMERANG, False) or trick(RT_KF_ADULT_GS) and can_use(RG_HOVER_BOOTS) and can_kill(RE_GOLD_SKULLTULA, ED_SHORT_JUMPSLASH, True, 1, False, False))), (Locations.RC_KF_CHILD_GRASS_1, lambda bundle: is_child() and can_cut_shrubs()), (Locations.RC_KF_CHILD_GRASS_2, lambda bundle: is_child() and can_cut_shrubs()), (Locations.RC_KF_CHILD_GRASS_3, lambda bundle: is_child() and can_cut_shrubs()), @@ -300,28 +322,6 @@ def set_region_rules(world: "SohWorld") -> None: (Locations.RC_KF_ADULT_GRASS_18, lambda bundle: is_adult() and can_cut_shrubs()), (Locations.RC_KF_ADULT_GRASS_19, lambda bundle: is_adult() and can_cut_shrubs()), (Locations.RC_KF_ADULT_GRASS_20, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_BRIDGE_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_BEHIND_MIDOS_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_SOUTH_GRASS_WEST_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_SOUTH_GRASS_EAST_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_NORTH_GRASS_WEST_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_NORTH_GRASS_EAST_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_SARIAS_ROOF_WEST_HEART, lambda bundle: is_child()), - (Locations.RC_KF_SARIAS_ROOF_EAST_HEART, lambda bundle: is_child()), - (Locations.RC_KF_SARIAS_ROOF_NORTH_HEART, lambda bundle: is_child()), - (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_GS_KNOW_IT_ALL_HOUSE, lambda bundle: is_child() and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False) and can_get_night_time_gs()), - (Locations.RC_KF_GS_BEAN_PATCH, lambda bundle: can_spawn_soil_skull(RG_KOKIRI_FOREST_BEAN_SOUL) and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False)), - (Locations.RC_KF_GS_HOUSE_OF_TWINS, lambda bundle: is_adult() and can_get_night_time_gs() and (can_get_drop(RE_GOLD_SKULLTULA, ED_BOOMERANG, False) or trick(RT_KF_ADULT_GS) and can_use(RG_HOVER_BOOTS) and can_kill(RE_GOLD_SKULLTULA, ED_SHORT_JUMPSLASH, True, 1, False, False))), - (Locations.RC_KF_BEAN_SPROUT_FAIRY_1, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_BEAN_SPROUT_FAIRY_2, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_BEAN_SPROUT_FAIRY_3, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), ]) # Exits connect_regions(Regions.RR_KOKIRI_FOREST, world, [ diff --git a/transpilers/soh_ap/src/generate_functions.cpp b/transpilers/soh_ap/src/generate_functions.cpp index 2e3a26e..d799736 100644 --- a/transpilers/soh_ap/src/generate_functions.cpp +++ b/transpilers/soh_ap/src/generate_functions.cpp @@ -40,7 +40,8 @@ std::string functionSignature( const bool includeDefaults) { std::ostringstream sig; - sig << "def " << decl->name << "("; + sig << "def " << decl->name << "(bundle: \"SohWorld\""; + if (decl->params.size() > 0) { sig << ", "; } for (int i = 0; i < decl->params.size(); i++) { const auto& param = decl->params[i]; sig << param.name << ": " << nodeType(p, ¶m); diff --git a/transpilers/soh_ap/src/generate_regions.cpp b/transpilers/soh_ap/src/generate_regions.cpp index 6749332..9f0a660 100644 --- a/transpilers/soh_ap/src/generate_regions.cpp +++ b/transpilers/soh_ap/src/generate_regions.cpp @@ -60,7 +60,12 @@ void SohApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { // TODO figure out local events and event locations for (const auto& [regionName, region] : project.RegionDecls) { - const auto [extendRegionBegin, extendRegionEnd] = project.ExtendRegionDecls.equal_range(region->key); + const auto extendRegionIt = project.ExtendRegionDecls.find(region->key); + + std::vector extendRegionDecls; + if (extendRegionIt != project.ExtendRegionDecls.end()) { + extendRegionDecls = extendRegionIt->second; + } std::string creationString = "Regions." + region->key + ", world, [\n"; @@ -69,20 +74,20 @@ void SohApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { source << " add_events(" << creationString; WriteEvents(*this, source, region->body.sections); - for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { - WriteEvents(*this, source, it->second->sections); + for (const auto* extendRegion : extendRegionDecls) { + WriteEvents(*this, source, extendRegion->sections); } source << " ])\n # Locations\n" << " add_locations(" << creationString; WriteLocations(*this, source, region->body.sections); - for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { - WriteLocations(*this, source, it->second->sections); + for (const auto* extendRegion : extendRegionDecls) { + WriteLocations(*this, source, extendRegion->sections); } source << " ])\n # Exits\n" << " connect_regions(" << creationString; WriteExits(*this, source, region->body.sections); - for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { - WriteExits(*this, source, it->second->sections); + for (const auto* extendRegion : extendRegionDecls) { + WriteExits(*this, source, extendRegion->sections); } source << " ])\n\n"; From e836a734532d94a43b3c0be14ab6b1274fa0a315 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Sun, 10 May 2026 22:14:01 -0400 Subject: [PATCH 09/22] Add import Add basic enum identifier. Still needs work. Fix lambdas --- examples/soh_ap/functions.gen.py | 66 +++++++++---------- .../soh_ap/src/generate_expression.cpp | 34 +++++++++- transpilers/soh_ap/src/generate_functions.cpp | 7 +- 3 files changed, 68 insertions(+), 39 deletions(-) diff --git a/examples/soh_ap/functions.gen.py b/examples/soh_ap/functions.gen.py index 2dc185f..78a1d94 100644 --- a/examples/soh_ap/functions.gen.py +++ b/examples/soh_ap/functions.gen.py @@ -1,78 +1,78 @@ # Generated by RLS soh_ap transpiler - from .Enums import * +from .rls_match import rls_match -def _can_get_drop_gold_skulltula(bundle: "SohWorld", distance: EnemyDistance) -> bool: - return soh_match((lambda distance: distance == ED_CLOSE or distance == ED_SHORT_JUMPSLASH or distance == ED_MASTER_SWORD_JUMPSLASH or distance == ED_LONG_JUMPSLASH or distance == ED_BOMB_THROW or distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG)), True, (lambda distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT)), False) +def _can_get_drop_gold_skulltula(bundle, distance: EnemyDistance) -> bool: + return rls_match((lambda distance=distance: distance == ED_CLOSE or distance == ED_SHORT_JUMPSLASH or distance == ED_MASTER_SWORD_JUMPSLASH or distance == ED_LONG_JUMPSLASH or distance == ED_BOMB_THROW or distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG)), True, (lambda distance=distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance=distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT)), False) -def _can_kill_gold_skulltula(bundle: "SohWorld", distance: EnemyDistance, wall_or_floor: bool) -> bool: - return soh_match((lambda distance: distance == ED_CLOSE), (lambda: can_use(RG_MEGATON_HAMMER)), True, (lambda distance: distance == ED_SHORT_JUMPSLASH), (lambda: can_use(RG_KOKIRI_SWORD)), True, (lambda distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: can_use(RG_MASTER_SWORD)), True, (lambda distance: distance == ED_LONG_JUMPSLASH), (lambda: can_use(RG_BIGGORON_SWORD) or can_use(RG_STICKS)), True, (lambda distance: distance == ED_BOMB_THROW), (lambda: can_use(RG_BOMB_BAG)), True, (lambda distance: distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG) or can_use(RG_DINS_FIRE)), True, (lambda distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT) or wall_or_floor and can_use(RG_BOMBCHU_5)), True, (lambda distance: distance == ED_FAR), (lambda: can_use(RG_FAIRY_SLINGSHOT) or can_use(RG_FAIRY_BOW)), False) +def _can_kill_gold_skulltula(bundle, distance: EnemyDistance, wall_or_floor: bool) -> bool: + return rls_match((lambda distance=distance: distance == ED_CLOSE), (lambda: can_use(RG_MEGATON_HAMMER)), True, (lambda distance=distance: distance == ED_SHORT_JUMPSLASH), (lambda: can_use(RG_KOKIRI_SWORD)), True, (lambda distance=distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: can_use(RG_MASTER_SWORD)), True, (lambda distance=distance: distance == ED_LONG_JUMPSLASH), (lambda: can_use(RG_BIGGORON_SWORD) or can_use(RG_STICKS)), True, (lambda distance=distance: distance == ED_BOMB_THROW), (lambda: can_use(RG_BOMB_BAG)), True, (lambda distance=distance: distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG) or can_use(RG_DINS_FIRE)), True, (lambda distance=distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance=distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT) or wall_or_floor and can_use(RG_BOMBCHU_5)), True, (lambda distance=distance: distance == ED_FAR), (lambda: can_use(RG_FAIRY_SLINGSHOT) or can_use(RG_FAIRY_BOW)), False) -def call_gossip_fairy(bundle: "SohWorld") -> bool: +def call_gossip_fairy(bundle) -> bool: return call_gossip_fairy_except_suns() or can_use(RG_SUNS_SONG) -def call_gossip_fairy_except_suns(bundle: "SohWorld") -> bool: +def call_gossip_fairy_except_suns(bundle) -> bool: return can_use(RG_ZELDAS_LULLABY) or can_use(RG_EPONAS_SONG) or can_use(RG_SONG_OF_TIME) -def can_avoid(bundle: "SohWorld", e: RandomizerEnemy, grounded: bool = False, quantity: int = 1) -> bool: - return can_kill(e, ED_CLOSE, True, quantity, False, False) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: True), False) +def can_avoid(bundle, e: RandomizerEnemy, grounded: bool = False, quantity: int = 1) -> bool: + return can_kill(e, ED_CLOSE, True, quantity, False, False) or rls_match((lambda e=e: e == RE_GOLD_SKULLTULA), (lambda: True), False) -def can_break_lower_beehives(bundle: "SohWorld") -> bool: +def can_break_lower_beehives(bundle) -> bool: return can_break_upper_beehives() or can_use(RG_BOMB_BAG) -def can_break_upper_beehives(bundle: "SohWorld") -> bool: +def can_break_upper_beehives(bundle) -> bool: return hookshot_or_boomerang() or trick(RT_BOMBCHU_BEEHIVES) and can_use(RG_BOMBCHU_5) or setting(RSK_SLINGBOW_BREAK_BEEHIVES) and (can_use(RG_FAIRY_BOW) or can_use(RG_FAIRY_SLINGSHOT)) -def can_climb_ladder(bundle: "SohWorld") -> bool: +def can_climb_ladder(bundle) -> bool: return has(RG_CLIMB) or trick(RT_HOOKSHOT_LADDERS) and can_use(RG_HOOKSHOT) -def can_cut_shrubs(bundle: "SohWorld") -> bool: +def can_cut_shrubs(bundle) -> bool: return can_use(RG_KOKIRI_SWORD) or can_use(RG_BOOMERANG) or has_explosives() or has(RG_GORONS_BRACELET) or can_use(RG_MASTER_SWORD) or can_use(RG_MEGATON_HAMMER) or can_use(RG_BIGGORON_SWORD) or can_use(RG_GIANTS_KNIFE) -def can_get_deku_baba_nuts(bundle: "SohWorld") -> bool: +def can_get_deku_baba_nuts(bundle) -> bool: return can_jumpslash() or can_use(RG_FAIRY_SLINGSHOT) or can_use(RG_FAIRY_BOW) or has_explosives() or can_use(RG_DINS_FIRE) -def can_get_deku_baba_sticks(bundle: "SohWorld") -> bool: +def can_get_deku_baba_sticks(bundle) -> bool: return can_use_sword() or can_use(RG_BOOMERANG) -def can_get_drop(bundle: "SohWorld", e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, above_link: bool = False) -> bool: - return can_kill(e, distance, True, 1, False, False) and (distance_to_int(distance) <= distance_to_int(ED_MASTER_SWORD_JUMPSLASH) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: _can_get_drop_gold_skulltula(distance)), False, (lambda e: e == RE_KEESE or e == RE_FIRE_KEESE or e == RE_GUAY), (lambda: True), False, (lambda: true), (lambda: above_link or distance_to_int(distance) <= distance_to_int(ED_BOOMERANG) and can_use(RG_BOOMERANG)), False)) +def can_get_drop(bundle, e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, above_link: bool = False) -> bool: + return can_kill(e, distance, True, 1, False, False) and (distance_to_int(distance) <= distance_to_int(ED_MASTER_SWORD_JUMPSLASH) or rls_match((lambda e=e: e == RE_GOLD_SKULLTULA), (lambda: _can_get_drop_gold_skulltula(distance)), False, (lambda e=e: e == RE_KEESE or e == RE_FIRE_KEESE or e == RE_GUAY), (lambda: True), False, (lambda: true), (lambda: above_link or distance_to_int(distance) <= distance_to_int(ED_BOOMERANG) and can_use(RG_BOOMERANG)), False)) -def can_get_night_time_gs(bundle: "SohWorld") -> bool: +def can_get_night_time_gs(bundle) -> bool: return at_night() and (can_use(RG_SUNS_SONG) or not setting(RSK_SKULLS_SUNS_SONG)) -def can_jumpslash(bundle: "SohWorld") -> bool: +def can_jumpslash(bundle) -> bool: return can_jumpslash_except_hammer() or can_use(RG_MEGATON_HAMMER) -def can_jumpslash_except_hammer(bundle: "SohWorld") -> bool: +def can_jumpslash_except_hammer(bundle) -> bool: return can_use(RG_STICKS) or can_use_sword() -def can_kill(bundle: "SohWorld", e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True, quantity: int = 1, timer: bool = False, in_water: bool = False) -> bool: - return soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: _can_kill_gold_skulltula(distance, wall_or_floor)), False) +def can_kill(bundle, e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True, quantity: int = 1, timer: bool = False, in_water: bool = False) -> bool: + return rls_match((lambda e=e: e == RE_GOLD_SKULLTULA), (lambda: _can_kill_gold_skulltula(distance, wall_or_floor)), False) -def can_open_storms_grotto(bundle: "SohWorld") -> bool: +def can_open_storms_grotto(bundle) -> bool: return can_use(RG_SONG_OF_STORMS) and (has(RG_STONE_OF_AGONY) or trick(RT_GROTTOS_WITHOUT_AGONY)) -def can_pass(bundle: "SohWorld", e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True) -> bool: - return can_kill(e, distance, wall_or_floor, 1, False, False) or soh_match((lambda e: e == RE_GOLD_SKULLTULA), (lambda: True), False) +def can_pass(bundle, e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True) -> bool: + return can_kill(e, distance, wall_or_floor, 1, False, False) or rls_match((lambda e=e: e == RE_GOLD_SKULLTULA), (lambda: True), False) -def can_spawn_soil_skull(bundle: "SohWorld", bean: RandomizerGet) -> bool: +def can_spawn_soil_skull(bundle, bean: RandomizerGet) -> bool: return is_child() and can_use(RG_BOTTLE_WITH_BUGS) and has(bean) -def can_use_sword(bundle: "SohWorld") -> bool: +def can_use_sword(bundle) -> bool: return can_use(RG_KOKIRI_SWORD) or can_use(RG_MASTER_SWORD) or can_use(RG_BIGGORON_SWORD) -def distance_to_int(bundle: "SohWorld", distance: EnemyDistance) -> int: - return soh_match((lambda distance: distance == ED_CLOSE), (lambda: 0), False, (lambda distance: distance == ED_SHORT_JUMPSLASH), (lambda: 1), False, (lambda distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: 2), False, (lambda distance: distance == ED_LONG_JUMPSLASH), (lambda: 3), False, (lambda distance: distance == ED_BOMB_THROW), (lambda: 4), False, (lambda distance: distance == ED_BOOMERANG), (lambda: 5), False, (lambda distance: distance == ED_HOOKSHOT), (lambda: 6), False, (lambda distance: distance == ED_LONGSHOT), (lambda: 7), False, (lambda distance: distance == ED_FAR), (lambda: 8), False) +def distance_to_int(bundle, distance: EnemyDistance) -> int: + return rls_match((lambda distance=distance: distance == ED_CLOSE), (lambda: 0), False, (lambda distance=distance: distance == ED_SHORT_JUMPSLASH), (lambda: 1), False, (lambda distance=distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: 2), False, (lambda distance=distance: distance == ED_LONG_JUMPSLASH), (lambda: 3), False, (lambda distance=distance: distance == ED_BOMB_THROW), (lambda: 4), False, (lambda distance=distance: distance == ED_BOOMERANG), (lambda: 5), False, (lambda distance=distance: distance == ED_HOOKSHOT), (lambda: 6), False, (lambda distance=distance: distance == ED_LONGSHOT), (lambda: 7), False, (lambda distance=distance: distance == ED_FAR), (lambda: 8), False) -def has_bottle(bundle: "SohWorld") -> bool: +def has_bottle(bundle) -> bool: return bottle_count() >= 1 -def has_explosives(bundle: "SohWorld") -> bool: +def has_explosives(bundle) -> bool: return can_use(RG_BOMB_BAG) or can_use(RG_BOMBCHU_5) -def hookshot_or_boomerang(bundle: "SohWorld") -> bool: +def hookshot_or_boomerang(bundle) -> bool: return can_use(RG_HOOKSHOT) or can_use(RG_BOOMERANG) -def wallet_capacity(bundle: "SohWorld") -> int: +def wallet_capacity(bundle) -> int: return 999 if has(RG_TYCOON_WALLET) else 500 if has(RG_GIANT_WALLET) else 200 if has(RG_ADULT_WALLET) else 99 if has(RG_CHILD_WALLET) else 0 diff --git a/transpilers/soh_ap/src/generate_expression.cpp b/transpilers/soh_ap/src/generate_expression.cpp index 7fb7c9b..76120ea 100644 --- a/transpilers/soh_ap/src/generate_expression.cpp +++ b/transpilers/soh_ap/src/generate_expression.cpp @@ -16,7 +16,35 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::IntLiteral& node } std::string SohApTranspiler::GenerateExpression(const rls::ast::Identifier& node) const { - return node.name; + return node.name; + // auto type = project.getType(&node); + // if (!type.has_value()) { + // return node.name; + // } + // switch (type.value()) { + // case rls::ast::Type::Item: + // return "RandomizerGet." + node.name; + // case rls::ast::Type::Enemy: + // return "RandomizerEnemy." + node.name; + // case rls::ast::Type::Distance: + // return "EnemyDistance." + node.name; + // case rls::ast::Type::Trick: + // return "RandomizerTrick." + node.name; + // case rls::ast::Type::Setting: + // return "world.options." + node.name; + // case rls::ast::Type::Region: + // return "RandomizerRegion." + node.name; + // case rls::ast::Type::Check: + // return "RandomizerCheck." + node.name; + // case rls::ast::Type::Dungeon: + // return "Dungeon." + node.name; + // case rls::ast::Type::Trial: + // return "Trial." + node.name; + // case rls::ast::Type::WaterLevel: + // return "WaterLevel." + node.name; + // default: + // return node.name; + // } } // Returns the Python operator precedence for an expression node. @@ -163,7 +191,7 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::AnyAgeBlock& nod std::string SohApTranspiler::GenerateExpression(const rls::ast::MatchExpr& node) const { std::ostringstream oss; - oss << "soh_match("; + oss << "rls_match("; for (size_t i = 0; i < node.arms.size(); i++) { const auto& arm = node.arms[i]; @@ -174,7 +202,7 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::MatchExpr& node) if (arm.isDefault) { oss << "(lambda: true), "; } else { - oss << "(lambda " << node.discriminant << ": "; + oss << "(lambda " << node.discriminant << "=" << node.discriminant << ": "; for (size_t j = 0; j < arm.patterns.size(); j++) { if (j > 0) oss << " or "; oss << node.discriminant << " == " << arm.patterns[j]; diff --git a/transpilers/soh_ap/src/generate_functions.cpp b/transpilers/soh_ap/src/generate_functions.cpp index d799736..2025fce 100644 --- a/transpilers/soh_ap/src/generate_functions.cpp +++ b/transpilers/soh_ap/src/generate_functions.cpp @@ -40,7 +40,7 @@ std::string functionSignature( const bool includeDefaults) { std::ostringstream sig; - sig << "def " << decl->name << "(bundle: \"SohWorld\""; + sig << "def " << decl->name << "(bundle"; if (decl->params.size() > 0) { sig << ", "; } for (int i = 0; i < decl->params.size(); i++) { const auto& param = decl->params[i]; @@ -58,8 +58,9 @@ std::string functionSignature( void SohApTranspiler::GenerateFunctionDefinitionsSource(rls::OutputWriter& out) const { auto& source = out.open("functions.gen.py"); - source << "# Generated by RLS soh_ap transpiler\n\n" - << "from .Enums import *\n"; + source << "# Generated by RLS soh_ap transpiler\n" + << "from .Enums import *\n" + << "from .rls_match import rls_match\n"; for (const auto& [name, decl] : project.DefineDecls) { From d9e6140637013f98201507e882647fee6628838a Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Thu, 14 May 2026 23:00:27 -0400 Subject: [PATCH 10/22] Auto generate some enums Add enum types to access rules Add event locations Brainstorm settings --- examples/soh_ap/enums.gen.py | 63 +++ examples/soh_ap/functions.gen.py | 58 +-- examples/soh_ap/regions.gen.py | 371 +++++++++--------- transpilers/soh_ap/include/soh_ap.h | 1 + transpilers/soh_ap/src/generate_enums.cpp | 84 ++++ .../soh_ap/src/generate_expression.cpp | 91 +++-- transpilers/soh_ap/src/generate_functions.cpp | 2 +- transpilers/soh_ap/src/generate_regions.cpp | 73 ++-- transpilers/soh_ap/src/soh_ap.cpp | 1 + 9 files changed, 441 insertions(+), 303 deletions(-) create mode 100644 examples/soh_ap/enums.gen.py create mode 100644 transpilers/soh_ap/src/generate_enums.cpp diff --git a/examples/soh_ap/enums.gen.py b/examples/soh_ap/enums.gen.py new file mode 100644 index 0000000..af374c6 --- /dev/null +++ b/examples/soh_ap/enums.gen.py @@ -0,0 +1,63 @@ +# Generated by RLS soh_ap transpiler +from enum import StrEnum, IntEnum, auto, Enum + +class EventLocations(StrEnum): + RR_KF_OUTSIDE_DEKU_TREE_LOGIC_STICK_ACCESS = auto() + RR_KF_OUTSIDE_DEKU_TREE_LOGIC_NUT_ACCESS = auto() + RR_KF_OUTSIDE_DEKU_TREE_LOGIC_FAIRY_ACCESS = auto() + RR_KF_OUTSIDE_DEKU_TREE_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD = auto() + RR_KF_STORMS_GROTTO_LOGIC_FAIRY_ACCESS = auto() + RR_KF_STORMS_GROTTO_LOGIC_BUG_ACCESS = auto() + RR_KF_STORMS_GROTTO_LOGIC_FISH_ACCESS = auto() + RR_KOKIRI_FOREST_LOGIC_FAIRY_ACCESS = auto() + RR_KOKIRI_FOREST_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD = auto() + RR_ROOT_LOGIC_KAKARIKO_GATE_OPEN = auto() + RR_ROOT_LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER = auto() + RR_ROOT_LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER = auto() + RR_ROOT_LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER = auto() + RR_ROOT_LOGIC_TH_COULD_FREE_SLOPE_CARPENTER = auto() + RR_ROOT_LOGIC_TH_RESCUED_ALL_CARPENTERS = auto() + RR_ROOT_LOGIC_FREED_EPONA = auto() + +class Events(StrEnum): + LOGIC_STICK_ACCESS = auto() + LOGIC_NUT_ACCESS = auto() + LOGIC_FAIRY_ACCESS = auto() + LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD = auto() + LOGIC_FAIRY_ACCESS = auto() + LOGIC_BUG_ACCESS = auto() + LOGIC_FISH_ACCESS = auto() + LOGIC_FAIRY_ACCESS = auto() + LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD = auto() + LOGIC_KAKARIKO_GATE_OPEN = auto() + LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER = auto() + LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER = auto() + LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER = auto() + LOGIC_TH_COULD_FREE_SLOPE_CARPENTER = auto() + LOGIC_TH_RESCUED_ALL_CARPENTERS = auto() + LOGIC_FREED_EPONA = auto() + +class RandomizerRegions(StrEnum): + RR_ADULT_SPAWN = "Adult Spawn" + RR_BOLERO_OF_FIRE_WARP = "Bolero of Fire Warp" + RR_CHILD_SPAWN = "Child Spawn" + RR_KF_BOULDER_LOOP = "KF Boulder Loop" + RR_KF_HOUSE_OF_TWINS = "KF House of Twins" + RR_KF_KNOW_IT_ALL_HOUSE = "KF Know It All House" + RR_KF_KOKIRI_SHOP = "KF Kokiri Shop" + RR_KF_LINKS_HOUSE = "KF Link's House" + RR_KF_LINKS_PORCH = "KF Link's Porch" + RR_KF_MIDOS_HOUSE = "KF Mido's House" + RR_KF_OUTSIDE_DEKU_TREE = "KF Outside Deku Tree" + RR_KF_OUTSIDE_LOST_WOODS = "KF Outside Lost Woods" + RR_KF_RUPEE_ALCOVE = "KF Alcove" + RR_KF_SARIAS_HOUSE = "KF Saria's House" + RR_KF_STORMS_GROTTO = "KF Storms Grotto" + RR_KOKIRI_FOREST = "Kokiri Forest" + RR_MINUET_OF_FOREST_WARP = "Minuet of Forest Warp" + RR_NOCTURNE_OF_SHADOW_WARP = "Nocturne of Shadow Warp" + RR_PRELUDE_OF_LIGHT_WARP = "Prelude of Light Warp" + RR_REQUIEM_OF_SPIRIT_WARP = "Requiem of Spirit Warp" + RR_ROOT = "Root" + RR_ROOT_EXITS = "Root Exits" + RR_SERENADE_OF_WATER_WARP = "Serenade of Water Warp" diff --git a/examples/soh_ap/functions.gen.py b/examples/soh_ap/functions.gen.py index 78a1d94..07bcdd0 100644 --- a/examples/soh_ap/functions.gen.py +++ b/examples/soh_ap/functions.gen.py @@ -1,78 +1,78 @@ # Generated by RLS soh_ap transpiler -from .Enums import * +from .enums.gen.py import * from .rls_match import rls_match def _can_get_drop_gold_skulltula(bundle, distance: EnemyDistance) -> bool: - return rls_match((lambda distance=distance: distance == ED_CLOSE or distance == ED_SHORT_JUMPSLASH or distance == ED_MASTER_SWORD_JUMPSLASH or distance == ED_LONG_JUMPSLASH or distance == ED_BOMB_THROW or distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG)), True, (lambda distance=distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance=distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT)), False) + return rls_match((lambda distance=distance: distance == EnemyDistance.ED_CLOSE or distance == EnemyDistance.ED_SHORT_JUMPSLASH or distance == EnemyDistance.ED_MASTER_SWORD_JUMPSLASH or distance == EnemyDistance.ED_LONG_JUMPSLASH or distance == EnemyDistance.ED_BOMB_THROW or distance == EnemyDistance.ED_BOOMERANG), (lambda: can_use(bundle, RandomizerGet.RG_BOOMERANG)), True, (lambda distance=distance: distance == EnemyDistance.ED_HOOKSHOT), (lambda: can_use(bundle, RandomizerGet.RG_HOOKSHOT)), True, (lambda distance=distance: distance == EnemyDistance.ED_LONGSHOT), (lambda: can_use(bundle, RandomizerGet.RG_LONGSHOT)), False) def _can_kill_gold_skulltula(bundle, distance: EnemyDistance, wall_or_floor: bool) -> bool: - return rls_match((lambda distance=distance: distance == ED_CLOSE), (lambda: can_use(RG_MEGATON_HAMMER)), True, (lambda distance=distance: distance == ED_SHORT_JUMPSLASH), (lambda: can_use(RG_KOKIRI_SWORD)), True, (lambda distance=distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: can_use(RG_MASTER_SWORD)), True, (lambda distance=distance: distance == ED_LONG_JUMPSLASH), (lambda: can_use(RG_BIGGORON_SWORD) or can_use(RG_STICKS)), True, (lambda distance=distance: distance == ED_BOMB_THROW), (lambda: can_use(RG_BOMB_BAG)), True, (lambda distance=distance: distance == ED_BOOMERANG), (lambda: can_use(RG_BOOMERANG) or can_use(RG_DINS_FIRE)), True, (lambda distance=distance: distance == ED_HOOKSHOT), (lambda: can_use(RG_HOOKSHOT)), True, (lambda distance=distance: distance == ED_LONGSHOT), (lambda: can_use(RG_LONGSHOT) or wall_or_floor and can_use(RG_BOMBCHU_5)), True, (lambda distance=distance: distance == ED_FAR), (lambda: can_use(RG_FAIRY_SLINGSHOT) or can_use(RG_FAIRY_BOW)), False) + return rls_match((lambda distance=distance: distance == EnemyDistance.ED_CLOSE), (lambda: can_use(bundle, RandomizerGet.RG_MEGATON_HAMMER)), True, (lambda distance=distance: distance == EnemyDistance.ED_SHORT_JUMPSLASH), (lambda: can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD)), True, (lambda distance=distance: distance == EnemyDistance.ED_MASTER_SWORD_JUMPSLASH), (lambda: can_use(bundle, RandomizerGet.RG_MASTER_SWORD)), True, (lambda distance=distance: distance == EnemyDistance.ED_LONG_JUMPSLASH), (lambda: can_use(bundle, RandomizerGet.RG_BIGGORON_SWORD) or can_use(bundle, RandomizerGet.RG_STICKS)), True, (lambda distance=distance: distance == EnemyDistance.ED_BOMB_THROW), (lambda: can_use(bundle, RandomizerGet.RG_BOMB_BAG)), True, (lambda distance=distance: distance == EnemyDistance.ED_BOOMERANG), (lambda: can_use(bundle, RandomizerGet.RG_BOOMERANG) or can_use(bundle, RandomizerGet.RG_DINS_FIRE)), True, (lambda distance=distance: distance == EnemyDistance.ED_HOOKSHOT), (lambda: can_use(bundle, RandomizerGet.RG_HOOKSHOT)), True, (lambda distance=distance: distance == EnemyDistance.ED_LONGSHOT), (lambda: can_use(bundle, RandomizerGet.RG_LONGSHOT) or wall_or_floor and can_use(bundle, RandomizerGet.RG_BOMBCHU_5)), True, (lambda distance=distance: distance == EnemyDistance.ED_FAR), (lambda: can_use(bundle, RandomizerGet.RG_FAIRY_SLINGSHOT) or can_use(bundle, RandomizerGet.RG_FAIRY_BOW)), False) def call_gossip_fairy(bundle) -> bool: - return call_gossip_fairy_except_suns() or can_use(RG_SUNS_SONG) + return call_gossip_fairy_except_suns(bundle) or can_use(bundle, RandomizerGet.RG_SUNS_SONG) def call_gossip_fairy_except_suns(bundle) -> bool: - return can_use(RG_ZELDAS_LULLABY) or can_use(RG_EPONAS_SONG) or can_use(RG_SONG_OF_TIME) + return can_use(bundle, RandomizerGet.RG_ZELDAS_LULLABY) or can_use(bundle, RandomizerGet.RG_EPONAS_SONG) or can_use(bundle, RandomizerGet.RG_SONG_OF_TIME) def can_avoid(bundle, e: RandomizerEnemy, grounded: bool = False, quantity: int = 1) -> bool: - return can_kill(e, ED_CLOSE, True, quantity, False, False) or rls_match((lambda e=e: e == RE_GOLD_SKULLTULA), (lambda: True), False) + return can_kill(bundle, e, EnemyDistance.ED_CLOSE, True, quantity, False, False) or rls_match((lambda e=e: e == RandomizerEnemy.RE_GOLD_SKULLTULA), (lambda: True), False) def can_break_lower_beehives(bundle) -> bool: - return can_break_upper_beehives() or can_use(RG_BOMB_BAG) + return can_break_upper_beehives(bundle) or can_use(bundle, RandomizerGet.RG_BOMB_BAG) def can_break_upper_beehives(bundle) -> bool: - return hookshot_or_boomerang() or trick(RT_BOMBCHU_BEEHIVES) and can_use(RG_BOMBCHU_5) or setting(RSK_SLINGBOW_BREAK_BEEHIVES) and (can_use(RG_FAIRY_BOW) or can_use(RG_FAIRY_SLINGSHOT)) + return hookshot_or_boomerang(bundle) or trick(bundle, RandomizerTrick.RT_BOMBCHU_BEEHIVES) and can_use(bundle, RandomizerGet.RG_BOMBCHU_5) or bundle[2].options.RSK_SLINGBOW_BREAK_BEEHIVES and (can_use(bundle, RandomizerGet.RG_FAIRY_BOW) or can_use(bundle, RandomizerGet.RG_FAIRY_SLINGSHOT)) def can_climb_ladder(bundle) -> bool: - return has(RG_CLIMB) or trick(RT_HOOKSHOT_LADDERS) and can_use(RG_HOOKSHOT) + return has(bundle, RandomizerGet.RG_CLIMB) or trick(bundle, RandomizerTrick.RT_HOOKSHOT_LADDERS) and can_use(bundle, RandomizerGet.RG_HOOKSHOT) def can_cut_shrubs(bundle) -> bool: - return can_use(RG_KOKIRI_SWORD) or can_use(RG_BOOMERANG) or has_explosives() or has(RG_GORONS_BRACELET) or can_use(RG_MASTER_SWORD) or can_use(RG_MEGATON_HAMMER) or can_use(RG_BIGGORON_SWORD) or can_use(RG_GIANTS_KNIFE) + return can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) or can_use(bundle, RandomizerGet.RG_BOOMERANG) or has_explosives(bundle) or has(bundle, RandomizerGet.RG_GORONS_BRACELET) or can_use(bundle, RandomizerGet.RG_MASTER_SWORD) or can_use(bundle, RandomizerGet.RG_MEGATON_HAMMER) or can_use(bundle, RandomizerGet.RG_BIGGORON_SWORD) or can_use(bundle, RandomizerGet.RG_GIANTS_KNIFE) def can_get_deku_baba_nuts(bundle) -> bool: - return can_jumpslash() or can_use(RG_FAIRY_SLINGSHOT) or can_use(RG_FAIRY_BOW) or has_explosives() or can_use(RG_DINS_FIRE) + return can_jumpslash(bundle) or can_use(bundle, RandomizerGet.RG_FAIRY_SLINGSHOT) or can_use(bundle, RandomizerGet.RG_FAIRY_BOW) or has_explosives(bundle) or can_use(bundle, RandomizerGet.RG_DINS_FIRE) def can_get_deku_baba_sticks(bundle) -> bool: - return can_use_sword() or can_use(RG_BOOMERANG) + return can_use_sword(bundle) or can_use(bundle, RandomizerGet.RG_BOOMERANG) -def can_get_drop(bundle, e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, above_link: bool = False) -> bool: - return can_kill(e, distance, True, 1, False, False) and (distance_to_int(distance) <= distance_to_int(ED_MASTER_SWORD_JUMPSLASH) or rls_match((lambda e=e: e == RE_GOLD_SKULLTULA), (lambda: _can_get_drop_gold_skulltula(distance)), False, (lambda e=e: e == RE_KEESE or e == RE_FIRE_KEESE or e == RE_GUAY), (lambda: True), False, (lambda: true), (lambda: above_link or distance_to_int(distance) <= distance_to_int(ED_BOOMERANG) and can_use(RG_BOOMERANG)), False)) +def can_get_drop(bundle, e: RandomizerEnemy, distance: EnemyDistance = EnemyDistance.ED_CLOSE, above_link: bool = False) -> bool: + return can_kill(bundle, e, distance, True, 1, False, False) and (distance_to_int(bundle, distance) <= distance_to_int(bundle, EnemyDistance.ED_MASTER_SWORD_JUMPSLASH) or rls_match((lambda e=e: e == RandomizerEnemy.RE_GOLD_SKULLTULA), (lambda: _can_get_drop_gold_skulltula(bundle, distance)), False, (lambda e=e: e == RandomizerEnemy.RE_KEESE or e == RandomizerEnemy.RE_FIRE_KEESE or e == RandomizerEnemy.RE_GUAY), (lambda: True), False, (lambda: true), (lambda: above_link or distance_to_int(bundle, distance) <= distance_to_int(bundle, EnemyDistance.ED_BOOMERANG) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), False)) def can_get_night_time_gs(bundle) -> bool: - return at_night() and (can_use(RG_SUNS_SONG) or not setting(RSK_SKULLS_SUNS_SONG)) + return at_night(bundle) and (can_use(bundle, RandomizerGet.RG_SUNS_SONG) or not bundle[2].options.RSK_SKULLS_SUNS_SONG) def can_jumpslash(bundle) -> bool: - return can_jumpslash_except_hammer() or can_use(RG_MEGATON_HAMMER) + return can_jumpslash_except_hammer(bundle) or can_use(bundle, RandomizerGet.RG_MEGATON_HAMMER) def can_jumpslash_except_hammer(bundle) -> bool: - return can_use(RG_STICKS) or can_use_sword() + return can_use(bundle, RandomizerGet.RG_STICKS) or can_use_sword(bundle) -def can_kill(bundle, e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True, quantity: int = 1, timer: bool = False, in_water: bool = False) -> bool: - return rls_match((lambda e=e: e == RE_GOLD_SKULLTULA), (lambda: _can_kill_gold_skulltula(distance, wall_or_floor)), False) +def can_kill(bundle, e: RandomizerEnemy, distance: EnemyDistance = EnemyDistance.ED_CLOSE, wall_or_floor: bool = True, quantity: int = 1, timer: bool = False, in_water: bool = False) -> bool: + return rls_match((lambda e=e: e == RandomizerEnemy.RE_GOLD_SKULLTULA), (lambda: _can_kill_gold_skulltula(bundle, distance, wall_or_floor)), False) def can_open_storms_grotto(bundle) -> bool: - return can_use(RG_SONG_OF_STORMS) and (has(RG_STONE_OF_AGONY) or trick(RT_GROTTOS_WITHOUT_AGONY)) + return can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS) and (has(bundle, RandomizerGet.RG_STONE_OF_AGONY) or trick(bundle, RandomizerTrick.RT_GROTTOS_WITHOUT_AGONY)) -def can_pass(bundle, e: RandomizerEnemy, distance: EnemyDistance = ED_CLOSE, wall_or_floor: bool = True) -> bool: - return can_kill(e, distance, wall_or_floor, 1, False, False) or rls_match((lambda e=e: e == RE_GOLD_SKULLTULA), (lambda: True), False) +def can_pass(bundle, e: RandomizerEnemy, distance: EnemyDistance = EnemyDistance.ED_CLOSE, wall_or_floor: bool = True) -> bool: + return can_kill(bundle, e, distance, wall_or_floor, 1, False, False) or rls_match((lambda e=e: e == RandomizerEnemy.RE_GOLD_SKULLTULA), (lambda: True), False) def can_spawn_soil_skull(bundle, bean: RandomizerGet) -> bool: - return is_child() and can_use(RG_BOTTLE_WITH_BUGS) and has(bean) + return is_child(bundle) and can_use(bundle, RandomizerGet.RG_BOTTLE_WITH_BUGS) and has(bundle, bean) def can_use_sword(bundle) -> bool: - return can_use(RG_KOKIRI_SWORD) or can_use(RG_MASTER_SWORD) or can_use(RG_BIGGORON_SWORD) + return can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) or can_use(bundle, RandomizerGet.RG_MASTER_SWORD) or can_use(bundle, RandomizerGet.RG_BIGGORON_SWORD) def distance_to_int(bundle, distance: EnemyDistance) -> int: - return rls_match((lambda distance=distance: distance == ED_CLOSE), (lambda: 0), False, (lambda distance=distance: distance == ED_SHORT_JUMPSLASH), (lambda: 1), False, (lambda distance=distance: distance == ED_MASTER_SWORD_JUMPSLASH), (lambda: 2), False, (lambda distance=distance: distance == ED_LONG_JUMPSLASH), (lambda: 3), False, (lambda distance=distance: distance == ED_BOMB_THROW), (lambda: 4), False, (lambda distance=distance: distance == ED_BOOMERANG), (lambda: 5), False, (lambda distance=distance: distance == ED_HOOKSHOT), (lambda: 6), False, (lambda distance=distance: distance == ED_LONGSHOT), (lambda: 7), False, (lambda distance=distance: distance == ED_FAR), (lambda: 8), False) + return rls_match((lambda distance=distance: distance == EnemyDistance.ED_CLOSE), (lambda: 0), False, (lambda distance=distance: distance == EnemyDistance.ED_SHORT_JUMPSLASH), (lambda: 1), False, (lambda distance=distance: distance == EnemyDistance.ED_MASTER_SWORD_JUMPSLASH), (lambda: 2), False, (lambda distance=distance: distance == EnemyDistance.ED_LONG_JUMPSLASH), (lambda: 3), False, (lambda distance=distance: distance == EnemyDistance.ED_BOMB_THROW), (lambda: 4), False, (lambda distance=distance: distance == EnemyDistance.ED_BOOMERANG), (lambda: 5), False, (lambda distance=distance: distance == EnemyDistance.ED_HOOKSHOT), (lambda: 6), False, (lambda distance=distance: distance == EnemyDistance.ED_LONGSHOT), (lambda: 7), False, (lambda distance=distance: distance == EnemyDistance.ED_FAR), (lambda: 8), False) def has_bottle(bundle) -> bool: - return bottle_count() >= 1 + return bottle_count(bundle) >= 1 def has_explosives(bundle) -> bool: - return can_use(RG_BOMB_BAG) or can_use(RG_BOMBCHU_5) + return can_use(bundle, RandomizerGet.RG_BOMB_BAG) or can_use(bundle, RandomizerGet.RG_BOMBCHU_5) def hookshot_or_boomerang(bundle) -> bool: - return can_use(RG_HOOKSHOT) or can_use(RG_BOOMERANG) + return can_use(bundle, RandomizerGet.RG_HOOKSHOT) or can_use(bundle, RandomizerGet.RG_BOOMERANG) def wallet_capacity(bundle) -> int: - return 999 if has(RG_TYCOON_WALLET) else 500 if has(RG_GIANT_WALLET) else 200 if has(RG_ADULT_WALLET) else 99 if has(RG_CHILD_WALLET) else 0 + return 999 if has(bundle, RandomizerGet.RG_TYCOON_WALLET) else 500 if has(bundle, RandomizerGet.RG_GIANT_WALLET) else 200 if has(bundle, RandomizerGet.RG_ADULT_WALLET) else 99 if has(bundle, RandomizerGet.RG_CHILD_WALLET) else 0 diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py index 6f8ac3a..3610636 100644 --- a/examples/soh_ap/regions.gen.py +++ b/examples/soh_ap/regions.gen.py @@ -1,6 +1,5 @@ # Generated by RLS soh_ap transpiler - -from ...LogicHelpers import * +from .functions.gen.py import * if TYPE_CHECKING: from ... import SohWorld @@ -15,7 +14,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_ADULT_SPAWN, world, [ - (Regions.RR_TEMPLE_OF_TIME, lambda bundle: True), + (RandomizerRegion.RR_TEMPLE_OF_TIME, lambda bundle: True), ]) # Bolero of Fire Warp @@ -27,7 +26,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_BOLERO_OF_FIRE_WARP, world, [ - (Regions.RR_DMC_PAD_ENTRY, lambda bundle: True), + (RandomizerRegion.RR_DMC_PAD_ENTRY, lambda bundle: True), ]) # Child Spawn @@ -39,7 +38,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_CHILD_SPAWN, world, [ - (Regions.RR_KF_LINKS_HOUSE, lambda bundle: True), + (RandomizerRegion.RR_KF_LINKS_HOUSE, lambda bundle: True), ]) # KF Boulder Loop @@ -48,16 +47,16 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_BOULDER_LOOP, world, [ - (Locations.RC_KF_KOKIRI_SWORD_CHEST, lambda bundle: is_child() and has(RG_OPEN_CHEST)), - (Locations.RC_KF_BOULDER_RUPEE_1, lambda bundle: is_child()), - (Locations.RC_KF_BOULDER_RUPEE_2, lambda bundle: is_child()), - (Locations.RC_KF_CHILD_GRASS_MAZE_1, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_MAZE_2, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_MAZE_3, lambda bundle: is_child() and can_cut_shrubs()), + (RandomizerCheck.RC_KF_KOKIRI_SWORD_CHEST, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_OPEN_CHEST)), + (RandomizerCheck.RC_KF_BOULDER_RUPEE_1, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_BOULDER_RUPEE_2, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_MAZE_1, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_MAZE_2, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_MAZE_3, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), ]) # Exits connect_regions(Regions.RR_KF_BOULDER_LOOP, world, [ - (Regions.RR_KOKIRI_FOREST, lambda bundle: can_use(RG_CRAWL)), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: can_use(bundle, RandomizerGet.RG_CRAWL)), ]) # KF House of Twins @@ -66,12 +65,12 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_HOUSE_OF_TWINS, world, [ - (Locations.RC_KF_TWINS_HOUSE_POT_1, lambda bundle: has(RG_POWER_BRACELET)), - (Locations.RC_KF_TWINS_HOUSE_POT_2, lambda bundle: has(RG_POWER_BRACELET)), + (RandomizerCheck.RC_KF_TWINS_HOUSE_POT_1, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), + (RandomizerCheck.RC_KF_TWINS_HOUSE_POT_2, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), ]) # Exits connect_regions(Regions.RR_KF_HOUSE_OF_TWINS, world, [ - (Regions.RR_KOKIRI_FOREST, lambda bundle: True), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Know It All House @@ -80,12 +79,12 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ - (Locations.RC_KF_BROTHERS_HOUSE_POT_1, lambda bundle: has(RG_POWER_BRACELET)), - (Locations.RC_KF_BROTHERS_HOUSE_POT_2, lambda bundle: has(RG_POWER_BRACELET)), + (RandomizerCheck.RC_KF_BROTHERS_HOUSE_POT_1, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), + (RandomizerCheck.RC_KF_BROTHERS_HOUSE_POT_2, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), ]) # Exits connect_regions(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ - (Regions.RR_KOKIRI_FOREST, lambda bundle: True), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Kokiri Shop @@ -94,18 +93,18 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_KOKIRI_SHOP, world, [ - (Locations.RC_KF_SHOP_ITEM_1, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_2, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_3, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_4, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_5, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_6, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_7, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), - (Locations.RC_KF_SHOP_ITEM_8, lambda bundle: has(RG_SPEAK_KOKIRI) and check_price(RC_UNKNOWN_CHECK) <= wallet_capacity()), + (RandomizerCheck.RC_KF_SHOP_ITEM_1, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), + (RandomizerCheck.RC_KF_SHOP_ITEM_2, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), + (RandomizerCheck.RC_KF_SHOP_ITEM_3, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), + (RandomizerCheck.RC_KF_SHOP_ITEM_4, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), + (RandomizerCheck.RC_KF_SHOP_ITEM_5, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), + (RandomizerCheck.RC_KF_SHOP_ITEM_6, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), + (RandomizerCheck.RC_KF_SHOP_ITEM_7, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), + (RandomizerCheck.RC_KF_SHOP_ITEM_8, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), ]) # Exits connect_regions(Regions.RR_KF_KOKIRI_SHOP, world, [ - (Regions.RR_KOKIRI_FOREST, lambda bundle: True), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Link's House @@ -114,12 +113,12 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_LINKS_HOUSE, world, [ - (Locations.RC_KF_LINKS_HOUSE_COW, lambda bundle: is_adult() and can_use(RG_EPONAS_SONG) and flag(LOGIC_LINKS_COW)), - (Locations.RC_KF_LINKS_HOUSE_POT, lambda bundle: has(RG_POWER_BRACELET)), + (RandomizerCheck.RC_KF_LINKS_HOUSE_COW, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_EPONAS_SONG) and flag(bundle, LOGIC_LINKS_COW)), + (RandomizerCheck.RC_KF_LINKS_HOUSE_POT, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), ]) # Exits connect_regions(Regions.RR_KF_LINKS_HOUSE, world, [ - (Regions.RR_KF_LINKS_PORCH, lambda bundle: True), + (RandomizerRegion.RR_KF_LINKS_PORCH, lambda bundle: True), ]) # KF Link's Porch @@ -131,8 +130,8 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_KF_LINKS_PORCH, world, [ - (Regions.RR_KF_LINKS_HOUSE, lambda bundle: True), - (Regions.RR_KOKIRI_FOREST, lambda bundle: True), + (RandomizerRegion.RR_KF_LINKS_HOUSE, lambda bundle: True), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Mido's House @@ -141,37 +140,37 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_MIDOS_HOUSE, world, [ - (Locations.RC_KF_MIDOS_TOP_LEFT_CHEST, lambda bundle: has(RG_OPEN_CHEST)), - (Locations.RC_KF_MIDOS_TOP_RIGHT_CHEST, lambda bundle: has(RG_OPEN_CHEST)), - (Locations.RC_KF_MIDOS_BOTTOM_LEFT_CHEST, lambda bundle: has(RG_OPEN_CHEST)), - (Locations.RC_KF_MIDOS_BOTTOM_RIGHT_CHEST, lambda bundle: has(RG_OPEN_CHEST)), + (RandomizerCheck.RC_KF_MIDOS_TOP_LEFT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), + (RandomizerCheck.RC_KF_MIDOS_TOP_RIGHT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), + (RandomizerCheck.RC_KF_MIDOS_BOTTOM_LEFT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), + (RandomizerCheck.RC_KF_MIDOS_BOTTOM_RIGHT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), ]) # Exits connect_regions(Regions.RR_KF_MIDOS_HOUSE, world, [ - (Regions.RR_KOKIRI_FOREST, lambda bundle: True), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Outside Deku Tree # Events add_events(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (EventLocations.LOGIC_STICK_ACCESS, lambda bundle: can_get_deku_baba_sticks()), - (EventLocations.LOGIC_NUT_ACCESS, lambda bundle: can_get_deku_baba_nuts()), - (EventLocations.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns()), - (EventLocations.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child() and has(RG_SPEAK_KOKIRI) and can_use(RG_KOKIRI_SWORD) and can_use(RG_DEKU_SHIELD)), + (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_STICK_ACCESS, Events.LOGIC_STICK_ACCESS, lambda bundle: can_get_deku_baba_sticks(bundle)), + (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_NUT_ACCESS, Events.LOGIC_NUT_ACCESS, lambda bundle: can_get_deku_baba_nuts(bundle)), + (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns(bundle)), + (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) and can_use(bundle, RandomizerGet.RG_DEKU_SHIELD)), ]) # Locations add_locations(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), - (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), - (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE, lambda bundle: True), - (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, lambda bundle: True), + (RandomizerCheck.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), + (RandomizerCheck.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), + (RandomizerCheck.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), + (RandomizerCheck.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), + (RandomizerCheck.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE, lambda bundle: True), + (RandomizerCheck.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, lambda bundle: True), ]) # Exits connect_regions(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (Regions.RR_DEKU_TREE_ENTRYWAY, lambda bundle: is_child() or setting(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF and (setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), - (Regions.RR_KOKIRI_FOREST, lambda bundle: is_adult() and (can_pass(RE_BIG_SKULLTULA, ED_CLOSE, True) or flag(LOGIC_DEKU_TREE_CLEAR)) or setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), + (RandomizerRegion.RR_DEKU_TREE_ENTRYWAY, lambda bundle: is_child(bundle) or bundle[2].options.RSK_SHUFFLE_DUNGEON_ENTRANCES != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF and (bundle[2].options.RSK_FOREST == RO_CLOSED_FOREST_OFF or flag(bundle, LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: is_adult(bundle) and (can_pass(bundle, RandomizerEnemy.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) or flag(bundle, LOGIC_DEKU_TREE_CLEAR)) or bundle[2].options.RSK_FOREST == RO_CLOSED_FOREST_OFF or flag(bundle, LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), ]) # KF Outside Lost Woods @@ -180,23 +179,23 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ - (Locations.RC_KF_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns()), - (Locations.RC_KF_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult() and can_use(RG_BOOMERANG)), - (Locations.RC_KF_GOSSIP_STONE, lambda bundle: True), + (RandomizerCheck.RC_KF_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), + (RandomizerCheck.RC_KF_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), + (RandomizerCheck.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), + (RandomizerCheck.RC_KF_GOSSIP_STONE, lambda bundle: True), ]) # Exits connect_regions(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ - (Regions.RR_KOKIRI_FOREST, lambda bundle: True), - (Regions.RR_THE_LOST_WOODS, lambda bundle: True), - (Regions.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS))), - (Regions.RR_KF_STORMS_GROTTO, lambda bundle: can_open_storms_grotto()), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), + (RandomizerRegion.RR_THE_LOST_WOODS, lambda bundle: True), + (RandomizerRegion.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS))), + (RandomizerRegion.RR_KF_STORMS_GROTTO, lambda bundle: can_open_storms_grotto(bundle)), ]) # KF Alcove @@ -205,17 +204,17 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_RUPEE_ALCOVE, world, [ - (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), - (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult() and can_use(RG_HOVER_BOOTS)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), + (RandomizerCheck.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), ]) # Exits connect_regions(Regions.RR_KF_RUPEE_ALCOVE, world, [ - (Regions.RR_KOKIRI_FOREST, lambda bundle: True), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Saria's House @@ -224,118 +223,118 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Locations add_locations(Regions.RR_KF_SARIAS_HOUSE, world, [ - (Locations.RC_KF_SARIAS_TOP_LEFT_HEART, lambda bundle: True), - (Locations.RC_KF_SARIAS_TOP_RIGHT_HEART, lambda bundle: True), - (Locations.RC_KF_SARIAS_BOTTOM_LEFT_HEART, lambda bundle: True), - (Locations.RC_KF_SARIAS_BOTTOM_RIGHT_HEART, lambda bundle: True), + (RandomizerCheck.RC_KF_SARIAS_TOP_LEFT_HEART, lambda bundle: True), + (RandomizerCheck.RC_KF_SARIAS_TOP_RIGHT_HEART, lambda bundle: True), + (RandomizerCheck.RC_KF_SARIAS_BOTTOM_LEFT_HEART, lambda bundle: True), + (RandomizerCheck.RC_KF_SARIAS_BOTTOM_RIGHT_HEART, lambda bundle: True), ]) # Exits connect_regions(Regions.RR_KF_SARIAS_HOUSE, world, [ - (Regions.RR_KOKIRI_FOREST, lambda bundle: True), + (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Storms Grotto # Events add_events(Regions.RR_KF_STORMS_GROTTO, world, [ - (EventLocations.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy() or can_use(RG_STICKS)), - (EventLocations.LOGIC_BUG_ACCESS, lambda bundle: can_cut_shrubs()), - (EventLocations.LOGIC_FISH_ACCESS, lambda bundle: True), + (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy(bundle) or can_use(bundle, RandomizerGet.RG_STICKS)), + (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_BUG_ACCESS, Events.LOGIC_BUG_ACCESS, lambda bundle: can_cut_shrubs(bundle)), + (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_FISH_ACCESS, Events.LOGIC_FISH_ACCESS, lambda bundle: True), ]) # Locations add_locations(Regions.RR_KF_STORMS_GROTTO, world, [ - (Locations.RC_KF_STORMS_GROTTO_CHEST, lambda bundle: has(RG_OPEN_CHEST)), - (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_LEFT, lambda bundle: can_break_lower_beehives()), - (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT, lambda bundle: can_break_lower_beehives()), - (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy()), - (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_STORMS_GROTTO_FISH, lambda bundle: has_bottle()), - (Locations.RC_KF_STORMS_GROTTO_GRASS_1, lambda bundle: can_cut_shrubs()), - (Locations.RC_KF_STORMS_GROTTO_GRASS_2, lambda bundle: can_cut_shrubs()), - (Locations.RC_KF_STORMS_GROTTO_GRASS_3, lambda bundle: can_cut_shrubs()), - (Locations.RC_KF_STORMS_GROTTO_GRASS_4, lambda bundle: can_cut_shrubs()), - (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE, lambda bundle: True), + (RandomizerCheck.RC_KF_STORMS_GROTTO_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_BEEHIVE_LEFT, lambda bundle: can_break_lower_beehives(bundle)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT, lambda bundle: can_break_lower_beehives(bundle)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy(bundle)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_FISH, lambda bundle: has_bottle(bundle)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_GRASS_1, lambda bundle: can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_GRASS_2, lambda bundle: can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_GRASS_3, lambda bundle: can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_GRASS_4, lambda bundle: can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_STORMS_GROTTO_GOSSIP_STONE, lambda bundle: True), ]) # Exits connect_regions(Regions.RR_KF_STORMS_GROTTO, world, [ - (Regions.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: True), + (RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: True), ]) # Kokiri Forest # Events add_events(Regions.RR_KOKIRI_FOREST, world, [ - (EventLocations.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns() or is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), - (EventLocations.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child() and has(RG_SPEAK_KOKIRI) and can_use(RG_KOKIRI_SWORD) and can_use(RG_DEKU_SHIELD)), + (EventLocations.RR_KOKIRI_FOREST_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns(bundle) or is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), + (EventLocations.RR_KOKIRI_FOREST_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) and can_use(bundle, RandomizerGet.RG_DEKU_SHIELD)), ]) # Locations add_locations(Regions.RR_KOKIRI_FOREST, world, [ - (Locations.RC_KF_BEAN_SPROUT_FAIRY_1, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_BEAN_SPROUT_FAIRY_2, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_BEAN_SPROUT_FAIRY_3, lambda bundle: is_child() and can_use(RG_MAGIC_BEAN) and has(RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(RG_SONG_OF_STORMS)), - (Locations.RC_KF_BRIDGE_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_BEHIND_MIDOS_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_SOUTH_GRASS_WEST_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_SOUTH_GRASS_EAST_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_NORTH_GRASS_WEST_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_NORTH_GRASS_EAST_RUPEE, lambda bundle: is_child()), - (Locations.RC_KF_SARIAS_ROOF_WEST_HEART, lambda bundle: is_child()), - (Locations.RC_KF_SARIAS_ROOF_EAST_HEART, lambda bundle: is_child()), - (Locations.RC_KF_SARIAS_ROOF_NORTH_HEART, lambda bundle: is_child()), - (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(RG_HOVER_BOOTS) or can_use(RG_BOOMERANG))), - (Locations.RC_KF_GS_KNOW_IT_ALL_HOUSE, lambda bundle: is_child() and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False) and can_get_night_time_gs()), - (Locations.RC_KF_GS_BEAN_PATCH, lambda bundle: can_spawn_soil_skull(RG_KOKIRI_FOREST_BEAN_SOUL) and can_kill(RE_GOLD_SKULLTULA, ED_CLOSE, True, 1, False, False)), - (Locations.RC_KF_GS_HOUSE_OF_TWINS, lambda bundle: is_adult() and can_get_night_time_gs() and (can_get_drop(RE_GOLD_SKULLTULA, ED_BOOMERANG, False) or trick(RT_KF_ADULT_GS) and can_use(RG_HOVER_BOOTS) and can_kill(RE_GOLD_SKULLTULA, ED_SHORT_JUMPSLASH, True, 1, False, False))), - (Locations.RC_KF_CHILD_GRASS_1, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_2, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_3, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_4, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_5, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_6, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_7, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_8, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_9, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_10, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_11, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_CHILD_GRASS_12, lambda bundle: is_child() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_1, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_2, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_3, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_4, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_5, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_6, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_7, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_8, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_9, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_10, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_11, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_12, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_13, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_14, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_15, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_16, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_17, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_18, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_19, lambda bundle: is_adult() and can_cut_shrubs()), - (Locations.RC_KF_ADULT_GRASS_20, lambda bundle: is_adult() and can_cut_shrubs()), + (RandomizerCheck.RC_KF_BEAN_SPROUT_FAIRY_1, lambda bundle: is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), + (RandomizerCheck.RC_KF_BEAN_SPROUT_FAIRY_2, lambda bundle: is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), + (RandomizerCheck.RC_KF_BEAN_SPROUT_FAIRY_3, lambda bundle: is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), + (RandomizerCheck.RC_KF_BRIDGE_RUPEE, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_BEHIND_MIDOS_RUPEE, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_SOUTH_GRASS_WEST_RUPEE, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_SOUTH_GRASS_EAST_RUPEE, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_NORTH_GRASS_WEST_RUPEE, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_NORTH_GRASS_EAST_RUPEE, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_SARIAS_ROOF_WEST_HEART, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_SARIAS_ROOF_EAST_HEART, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_SARIAS_ROOF_NORTH_HEART, lambda bundle: is_child(bundle)), + (RandomizerCheck.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), + (RandomizerCheck.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), + (RandomizerCheck.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), + (RandomizerCheck.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), + (RandomizerCheck.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), + (RandomizerCheck.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), + (RandomizerCheck.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), + (RandomizerCheck.RC_KF_GS_KNOW_IT_ALL_HOUSE, lambda bundle: is_child(bundle) and can_kill(bundle, RandomizerEnemy.RE_GOLD_SKULLTULA, EnemyDistance.ED_CLOSE, True, 1, False, False) and can_get_night_time_gs(bundle)), + (RandomizerCheck.RC_KF_GS_BEAN_PATCH, lambda bundle: can_spawn_soil_skull(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_kill(bundle, RandomizerEnemy.RE_GOLD_SKULLTULA, EnemyDistance.ED_CLOSE, True, 1, False, False)), + (RandomizerCheck.RC_KF_GS_HOUSE_OF_TWINS, lambda bundle: is_adult(bundle) and can_get_night_time_gs(bundle) and (can_get_drop(bundle, RandomizerEnemy.RE_GOLD_SKULLTULA, EnemyDistance.ED_BOOMERANG, False) or trick(bundle, RandomizerTrick.RT_KF_ADULT_GS) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) and can_kill(bundle, RandomizerEnemy.RE_GOLD_SKULLTULA, EnemyDistance.ED_SHORT_JUMPSLASH, True, 1, False, False))), + (RandomizerCheck.RC_KF_CHILD_GRASS_1, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_2, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_3, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_4, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_5, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_6, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_7, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_8, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_9, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_10, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_11, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_CHILD_GRASS_12, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_1, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_2, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_3, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_4, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_5, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_6, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_7, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_8, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_9, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_10, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_11, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_12, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_13, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_14, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_15, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_16, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_17, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_18, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_19, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + (RandomizerCheck.RC_KF_ADULT_GRASS_20, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), ]) # Exits connect_regions(Regions.RR_KOKIRI_FOREST, world, [ - (Regions.RR_KF_BOULDER_LOOP, lambda bundle: can_use(RG_CRAWL)), - (Regions.RR_KF_LINKS_PORCH, lambda bundle: can_climb_ladder() if is_child() else has(RG_CLIMB) or can_use(RG_HOVER_BOOTS)), - (Regions.RR_KF_MIDOS_HOUSE, lambda bundle: True), - (Regions.RR_KF_SARIAS_HOUSE, lambda bundle: True), - (Regions.RR_KF_HOUSE_OF_TWINS, lambda bundle: True), - (Regions.RR_KF_KNOW_IT_ALL_HOUSE, lambda bundle: True), - (Regions.RR_KF_KOKIRI_SHOP, lambda bundle: True), - (Regions.RR_KF_OUTSIDE_DEKU_TREE, lambda bundle: flag(LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD) or setting(RSK_FOREST) == RO_CLOSED_FOREST_OFF or is_adult() and (can_pass(RE_BIG_SKULLTULA, ED_CLOSE, True) or flag(LOGIC_FOREST_TEMPLE_CLEAR))), - (Regions.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: has(RG_CLIMB) or can_use(RG_HOOKSHOT) or is_adult() and (can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL) or trick(RT_UNINTUITIVE_JUMPS))), - (Regions.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult() and can_plant_bean(RR_KOKIRI_FOREST, RG_KOKIRI_FOREST_BEAN_SOUL)), - (Regions.RR_LW_BRIDGE_FROM_FOREST, lambda bundle: is_adult() or setting(RSK_FOREST) != RO_CLOSED_FOREST_ON or flag(LOGIC_DEKU_TREE_CLEAR)), + (RandomizerRegion.RR_KF_BOULDER_LOOP, lambda bundle: can_use(bundle, RandomizerGet.RG_CRAWL)), + (RandomizerRegion.RR_KF_LINKS_PORCH, lambda bundle: can_climb_ladder(bundle) if is_child(bundle) else has(bundle, RandomizerGet.RG_CLIMB) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), + (RandomizerRegion.RR_KF_MIDOS_HOUSE, lambda bundle: True), + (RandomizerRegion.RR_KF_SARIAS_HOUSE, lambda bundle: True), + (RandomizerRegion.RR_KF_HOUSE_OF_TWINS, lambda bundle: True), + (RandomizerRegion.RR_KF_KNOW_IT_ALL_HOUSE, lambda bundle: True), + (RandomizerRegion.RR_KF_KOKIRI_SHOP, lambda bundle: True), + (RandomizerRegion.RR_KF_OUTSIDE_DEKU_TREE, lambda bundle: flag(bundle, LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD) or bundle[2].options.RSK_FOREST == RO_CLOSED_FOREST_OFF or is_adult(bundle) and (can_pass(bundle, RandomizerEnemy.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) or flag(bundle, LOGIC_FOREST_TEMPLE_CLEAR))), + (RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: has(bundle, RandomizerGet.RG_CLIMB) or can_use(bundle, RandomizerGet.RG_HOOKSHOT) or is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or trick(bundle, RandomizerTrick.RT_UNINTUITIVE_JUMPS))), + (RandomizerRegion.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult(bundle) and can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL)), + (RandomizerRegion.RR_LW_BRIDGE_FROM_FOREST, lambda bundle: is_adult(bundle) or bundle[2].options.RSK_FOREST != RO_CLOSED_FOREST_ON or flag(bundle, LOGIC_DEKU_TREE_CLEAR)), ]) # Minuet of Forest Warp @@ -347,7 +346,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_MINUET_OF_FOREST_WARP, world, [ - (Regions.RR_SACRED_FOREST_MEADOW, lambda bundle: True), + (RandomizerRegion.RR_SACRED_FOREST_MEADOW, lambda bundle: True), ]) # Nocturne of Shadow Warp @@ -359,7 +358,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_NOCTURNE_OF_SHADOW_WARP, world, [ - (Regions.RR_GRAVEYARD_WARP_PAD_REGION, lambda bundle: True), + (RandomizerRegion.RR_GRAVEYARD_WARP_PAD_REGION, lambda bundle: True), ]) # Prelude of Light Warp @@ -371,7 +370,7 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_PRELUDE_OF_LIGHT_WARP, world, [ - (Regions.RR_TEMPLE_OF_TIME, lambda bundle: True), + (RandomizerRegion.RR_TEMPLE_OF_TIME, lambda bundle: True), ]) # Requiem of Spirit Warp @@ -383,33 +382,33 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_REQUIEM_OF_SPIRIT_WARP, world, [ - (Regions.RR_DESERT_COLOSSUS, lambda bundle: True), + (RandomizerRegion.RR_DESERT_COLOSSUS, lambda bundle: True), ]) # Root # Events add_events(Regions.RR_ROOT, world, [ - (EventLocations.LOGIC_KAKARIKO_GATE_OPEN, lambda bundle: setting(RSK_KAK_GATE) == RO_KAK_GATE_OPEN), - (EventLocations.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE), - (EventLocations.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), - (EventLocations.LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), - (EventLocations.LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE or setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FAST), - (EventLocations.LOGIC_TH_RESCUED_ALL_CARPENTERS, lambda bundle: setting(RSK_GERUDO_FORTRESS) == RO_GF_CARPENTERS_FREE), - (EventLocations.LOGIC_FREED_EPONA, lambda bundle: setting(RSK_SKIP_EPONA_RACE)), + (EventLocations.RR_ROOT_LOGIC_KAKARIKO_GATE_OPEN, Events.LOGIC_KAKARIKO_GATE_OPEN, lambda bundle: bundle[2].options.RSK_KAK_GATE == RO_KAK_GATE_OPEN), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, Events.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, Events.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE or bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FAST), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, Events.LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE or bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FAST), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, Events.LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE or bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FAST), + (EventLocations.RR_ROOT_LOGIC_TH_RESCUED_ALL_CARPENTERS, Events.LOGIC_TH_RESCUED_ALL_CARPENTERS, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE), + (EventLocations.RR_ROOT_LOGIC_FREED_EPONA, Events.LOGIC_FREED_EPONA, lambda bundle: bundle[2].options.RSK_SKIP_EPONA_RACE == RO_GENERIC_YES), ]) # Locations add_locations(Regions.RR_ROOT, world, [ - (Locations.RC_LINKS_POCKET, lambda bundle: True), - (Locations.RC_TRIFORCE_COMPLETED, lambda bundle: collected_triforce_pieces() >= required_triforce_pieces()), - (Locations.RC_SARIA_SONG_HINT, lambda bundle: can_use(RG_SARIAS_SONG)), - (Locations.RC_SONG_FROM_IMPA, lambda bundle: setting(RSK_SKIP_CHILD_ZELDA)), - (Locations.RC_HC_MALON_EGG, lambda bundle: setting(RSK_SKIP_CHILD_ZELDA)), - (Locations.RC_HC_ZELDAS_LETTER, lambda bundle: setting(RSK_SKIP_CHILD_ZELDA)), - (Locations.RC_TOT_MASTER_SWORD, lambda bundle: setting(RSK_SELECTED_STARTING_AGE) == RO_AGE_ADULT), + (RandomizerCheck.RC_LINKS_POCKET, lambda bundle: True), + (RandomizerCheck.RC_TRIFORCE_COMPLETED, lambda bundle: collected_triforce_pieces(bundle) >= required_triforce_pieces(bundle)), + (RandomizerCheck.RC_SARIA_SONG_HINT, lambda bundle: can_use(bundle, RandomizerGet.RG_SARIAS_SONG)), + (RandomizerCheck.RC_SONG_FROM_IMPA, lambda bundle: bundle[2].options.RSK_SKIP_CHILD_ZELDA == RO_GENERIC_YES), + (RandomizerCheck.RC_HC_MALON_EGG, lambda bundle: bundle[2].options.RSK_SKIP_CHILD_ZELDA == RO_GENERIC_YES), + (RandomizerCheck.RC_HC_ZELDAS_LETTER, lambda bundle: bundle[2].options.RSK_SKIP_CHILD_ZELDA == RO_GENERIC_YES), + (RandomizerCheck.RC_TOT_MASTER_SWORD, lambda bundle: bundle[2].options.RSK_SELECTED_STARTING_AGE == RO_AGE_ADULT), ]) # Exits connect_regions(Regions.RR_ROOT, world, [ - (Regions.RR_ROOT_EXITS, lambda bundle: True), + (RandomizerRegion.RR_ROOT_EXITS, lambda bundle: True), ]) # Root Exits @@ -421,14 +420,14 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_ROOT_EXITS, world, [ - (Regions.RR_CHILD_SPAWN, lambda bundle: is_child()), - (Regions.RR_ADULT_SPAWN, lambda bundle: is_adult()), - (Regions.RR_MINUET_OF_FOREST_WARP, lambda bundle: can_use(RG_MINUET_OF_FOREST)), - (Regions.RR_BOLERO_OF_FIRE_WARP, lambda bundle: can_use(RG_BOLERO_OF_FIRE)), - (Regions.RR_SERENADE_OF_WATER_WARP, lambda bundle: can_use(RG_SERENADE_OF_WATER)), - (Regions.RR_NOCTURNE_OF_SHADOW_WARP, lambda bundle: can_use(RG_NOCTURNE_OF_SHADOW)), - (Regions.RR_REQUIEM_OF_SPIRIT_WARP, lambda bundle: can_use(RG_REQUIEM_OF_SPIRIT)), - (Regions.RR_PRELUDE_OF_LIGHT_WARP, lambda bundle: can_use(RG_PRELUDE_OF_LIGHT)), + (RandomizerRegion.RR_CHILD_SPAWN, lambda bundle: is_child(bundle)), + (RandomizerRegion.RR_ADULT_SPAWN, lambda bundle: is_adult(bundle)), + (RandomizerRegion.RR_MINUET_OF_FOREST_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_MINUET_OF_FOREST)), + (RandomizerRegion.RR_BOLERO_OF_FIRE_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_BOLERO_OF_FIRE)), + (RandomizerRegion.RR_SERENADE_OF_WATER_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_SERENADE_OF_WATER)), + (RandomizerRegion.RR_NOCTURNE_OF_SHADOW_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_NOCTURNE_OF_SHADOW)), + (RandomizerRegion.RR_REQUIEM_OF_SPIRIT_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_REQUIEM_OF_SPIRIT)), + (RandomizerRegion.RR_PRELUDE_OF_LIGHT_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_PRELUDE_OF_LIGHT)), ]) # Serenade of Water Warp @@ -440,6 +439,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_SERENADE_OF_WATER_WARP, world, [ - (Regions.RR_LAKE_HYLIA, lambda bundle: True), + (RandomizerRegion.RR_LAKE_HYLIA, lambda bundle: True), ]) diff --git a/transpilers/soh_ap/include/soh_ap.h b/transpilers/soh_ap/include/soh_ap.h index 1f89c96..d00847e 100644 --- a/transpilers/soh_ap/include/soh_ap.h +++ b/transpilers/soh_ap/include/soh_ap.h @@ -13,6 +13,7 @@ class SohApTranspiler { void GenerateFunctionDefinitionsSource(rls::OutputWriter& out) const; void GenerateRegionsSource(rls::OutputWriter& out) const; + void GenerateEnumsSource(rls::OutputWriter& out) const; std::string GenerateExpression(const rls::ast::ExprPtr& expr) const; private: diff --git a/transpilers/soh_ap/src/generate_enums.cpp b/transpilers/soh_ap/src/generate_enums.cpp new file mode 100644 index 0000000..03407af --- /dev/null +++ b/transpilers/soh_ap/src/generate_enums.cpp @@ -0,0 +1,84 @@ +#include "soh_ap.h" +#include "generate_regions.cpp" + +#include +#include + +namespace rls::transpilers::soh_ap { + +void WriteEventLocations( + const SohApTranspiler& transpiler, + std::ostream& source, + std::string region, + const std::vector& sections) +{ + WriteEntries(sections, rls::ast::SectionKind::Events, [&](const rls::ast::Entry& entry){ + source << " " << region << "_" << entry.name << " = auto()\n"; + }); +} + +void WriteEvents( + const SohApTranspiler& transpiler, + std::ostream& source, + const std::vector& sections) +{ + WriteEntries(sections, rls::ast::SectionKind::Events, [&](const rls::ast::Entry& entry){ + source << " " << entry.name << " = auto()\n"; + }); +} + +void SohApTranspiler::GenerateEnumsSource(rls::OutputWriter& out) const { + auto& source = out.open("enums.gen.py"); + + std::stringstream sstream; + + sstream << "class RandomizerRegions(StrEnum):\n"; + + source + << "# Generated by RLS soh_ap transpiler\n" + << "from enum import StrEnum, IntEnum, auto, Enum\n" + << "\n" + << "class EventLocations(StrEnum):\n"; + + // Event Locations + for (const auto& [regionName, region] : project.RegionDecls) { + // Do Regions while here + sstream << " " << region->key.text << " = \"" << region->body.name << "\"\n"; + + std::vector extendRegionDecls; + const auto extendRegionIt = project.ExtendRegionDecls.find(region->key.text); + + if (extendRegionIt != project.ExtendRegionDecls.end()) { + extendRegionDecls = extendRegionIt->second; + } + + WriteEventLocations(*this, source, region->key.text, region->body.sections); + for (const auto* extendRegion : extendRegionDecls) { + WriteEventLocations(*this, source, region->key.text, extendRegion->sections); + } + } + + // Events + source + << "\n" + << "class Events(StrEnum):\n"; + for (const auto& [regionName, region] : project.RegionDecls) { + std::vector extendRegionDecls; + const auto extendRegionIt = project.ExtendRegionDecls.find(region->key.text); + + if (extendRegionIt != project.ExtendRegionDecls.end()) { + extendRegionDecls = extendRegionIt->second; + } + + WriteEvents(*this, source, region->body.sections); + for (const auto* extendRegion : extendRegionDecls) { + WriteEvents(*this, source, extendRegion->sections); + } + } + + // Regions + source << "\n" << sstream.str(); + +} + +} // namespace rls::transpilers::soh_ap \ No newline at end of file diff --git a/transpilers/soh_ap/src/generate_expression.cpp b/transpilers/soh_ap/src/generate_expression.cpp index 76120ea..df696a7 100644 --- a/transpilers/soh_ap/src/generate_expression.cpp +++ b/transpilers/soh_ap/src/generate_expression.cpp @@ -16,35 +16,39 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::IntLiteral& node } std::string SohApTranspiler::GenerateExpression(const rls::ast::Identifier& node) const { - return node.name; - // auto type = project.getType(&node); - // if (!type.has_value()) { - // return node.name; - // } - // switch (type.value()) { - // case rls::ast::Type::Item: - // return "RandomizerGet." + node.name; - // case rls::ast::Type::Enemy: - // return "RandomizerEnemy." + node.name; - // case rls::ast::Type::Distance: - // return "EnemyDistance." + node.name; - // case rls::ast::Type::Trick: - // return "RandomizerTrick." + node.name; - // case rls::ast::Type::Setting: - // return "world.options." + node.name; - // case rls::ast::Type::Region: - // return "RandomizerRegion." + node.name; - // case rls::ast::Type::Check: - // return "RandomizerCheck." + node.name; - // case rls::ast::Type::Dungeon: - // return "Dungeon." + node.name; - // case rls::ast::Type::Trial: - // return "Trial." + node.name; - // case rls::ast::Type::WaterLevel: - // return "WaterLevel." + node.name; - // default: - // return node.name; - // } + if (node.kind == rls::ast::IdentifierKind::EnumValue) { + auto type = project.getType(&node); + if (!type.has_value()) { + return node.name.text; + } + switch (type.value()) { + case rls::ast::Type::Item: return "RandomizerGet." + node.name.text; + case rls::ast::Type::Enemy: return "RandomizerEnemy." + node.name.text; + case rls::ast::Type::Distance: return "EnemyDistance." + node.name.text; + case rls::ast::Type::Trick: return "RandomizerTrick." + node.name.text; + /* + AP has a couple ways we could use for comparing options. Ideally we use OptionFilter as that is rule builder compatible + `OptionFilter(OptionClassName, value, "operator")` -> `OptionFilter(SkipChildZelda, True)` + + If we aren't using rule builder we could go the classic comparison + `world.options.settingsJsonName == value` -> `world.options.skip_child_zelda == True` // Could also just be used without the True comparison. `world.options.skip_child_zelda` converts to a bool. + + In AP since each option has its own class, this is going to be tricky to do right. We may have to build a mapping between what RLS Option and AP settings classes/settingsJsonNames. + + For the values, we could probabaly make an enum to house them and use the enums in the options classes + */ + //case rls::ast::Type::Setting: return "RandomizerSettingKey::" + node.name.text; + case rls::ast::Type::Region: return "RandomizerRegion." + node.name.text; + case rls::ast::Type::Check: return "RandomizerCheck." + node.name.text; + case rls::ast::Type::Trial: return "TrialKey." + node.name.text; + default: return node.name.text; + } + } else if (node.kind == rls::ast::IdentifierKind::Parameter) { + return node.name.text; + } else { + // Unresolved identifiers should have been blocked earlier in sema; emit empty as a defensive fallback. + return ""; + } } // Returns the Python operator precedence for an expression node. @@ -141,7 +145,6 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::TernaryExpr& nod GenerateExpression(node.elseBranch); } -// TODO Handle Host Functions std::string SohApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) const { auto resolvedPtr = project.getResolvedCallArgs(&node); if (resolvedPtr == nullptr) { @@ -152,14 +155,22 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) const auto& resolved = *resolvedPtr; std::ostringstream oss; - oss << node.function << "("; - for (size_t i = 0; i < resolved.size(); ++i) { - if (i > 0) { + + // Handle settings differently + if (node.callee.text == "setting") { + oss << "bundle[2].options." << GenerateExpression(resolved[0]->node); + } else { + oss << node.callee.text << "(bundle"; + for (size_t i = 0; i < resolved.size(); ++i) { + // if (i > 0) { + // oss << ", "; + // } oss << ", "; + + oss << GenerateExpression(resolved[i]->node); } - oss << GenerateExpression(resolved[i]->node); + oss << ")"; } - oss << ")"; return oss.str(); } @@ -169,13 +180,13 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::SharedBlock& nod std::ostringstream oss; const auto& firstBranch = node.branches[0]; - oss << "spirit_shared(" << firstBranch.region.value_or("") << ", " + oss << "spirit_shared(" << firstBranch.region->text << ", " << "(lambda: " << GenerateExpression(firstBranch.condition) << "), " << (node.anyAge ? "true" : "false"); for (int i = 1; i < node.branches.size(); i++) { - oss << ", " << node.branches[i].region.value_or("") << ", " - << "(labmda:" << GenerateExpression(node.branches[i].condition) << ")"; + oss << ", " << node.branches[i].region->text << ", " + << "(lambda:" << GenerateExpression(node.branches[i].condition) << ")"; } oss << ")"; @@ -202,10 +213,10 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::MatchExpr& node) if (arm.isDefault) { oss << "(lambda: true), "; } else { - oss << "(lambda " << node.discriminant << "=" << node.discriminant << ": "; + oss << "(lambda " << GenerateExpression(node.discriminant) << "=" << GenerateExpression(node.discriminant) << ": "; for (size_t j = 0; j < arm.patterns.size(); j++) { if (j > 0) oss << " or "; - oss << node.discriminant << " == " << arm.patterns[j]; + oss << GenerateExpression(node.discriminant) << " == " << GenerateExpression(arm.patterns[j]); } oss << "), "; } diff --git a/transpilers/soh_ap/src/generate_functions.cpp b/transpilers/soh_ap/src/generate_functions.cpp index 2025fce..d57d48f 100644 --- a/transpilers/soh_ap/src/generate_functions.cpp +++ b/transpilers/soh_ap/src/generate_functions.cpp @@ -59,7 +59,7 @@ std::string functionSignature( void SohApTranspiler::GenerateFunctionDefinitionsSource(rls::OutputWriter& out) const { auto& source = out.open("functions.gen.py"); source << "# Generated by RLS soh_ap transpiler\n" - << "from .Enums import *\n" + << "from .enums.gen.py import *\n" << "from .rls_match import rls_match\n"; diff --git a/transpilers/soh_ap/src/generate_regions.cpp b/transpilers/soh_ap/src/generate_regions.cpp index 9f0a660..c7814bd 100644 --- a/transpilers/soh_ap/src/generate_regions.cpp +++ b/transpilers/soh_ap/src/generate_regions.cpp @@ -1,7 +1,6 @@ #include "soh_ap.h" #include -#include namespace rls::transpilers::soh_ap { @@ -20,10 +19,11 @@ void WriteEntries(const std::vector& sections, void WriteEvents( const SohApTranspiler& transpiler, std::ostream& source, + std::string region, const std::vector& sections) { WriteEntries(sections, rls::ast::SectionKind::Events, [&](const rls::ast::Entry& entry){ - source << " (EventLocations." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; + source << " (EventLocations." << region << "_" << entry.name << ", Events." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; }); } @@ -33,7 +33,7 @@ void WriteLocations( const std::vector& sections) { WriteEntries(sections, rls::ast::SectionKind::Locations, [&](const rls::ast::Entry& entry){ - source << " (Locations." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; + source << " (RandomizerCheck." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; }); } @@ -43,77 +43,56 @@ void WriteExits( const std::vector& sections) { WriteEntries(sections, rls::ast::SectionKind::Exits, [&](const rls::ast::Entry& entry){ - source << " (Regions." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; + source << " (RandomizerRegion." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; }); } void SohApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { - auto& source = out.open("regions.gen.py"); - source << "# Generated by RLS soh_ap transpiler\n" - << "\n" - << "from ...LogicHelpers import *\n" - << "\n" - << "if TYPE_CHECKING:\n" - << " from ... import SohWorld\n\n" - << "def set_region_rules(world: \"SohWorld\") -> None:\n"; - - // TODO figure out local events and event locations + auto& source = out.open("regions.gen.py"); + source + << "# Generated by RLS soh_ap transpiler\n" + << "from .functions.gen.py import *\n" + << "\n" + << "if TYPE_CHECKING:\n" + << " from ... import SohWorld\n" + << "\n" + << "def set_region_rules(world: \"SohWorld\") -> None:\n"; for (const auto& [regionName, region] : project.RegionDecls) { - const auto extendRegionIt = project.ExtendRegionDecls.find(region->key); + const auto extendRegionIt = project.ExtendRegionDecls.find(region->key.text); std::vector extendRegionDecls; if (extendRegionIt != project.ExtendRegionDecls.end()) { extendRegionDecls = extendRegionIt->second; } - std::string creationString = "Regions." + region->key + ", world, [\n"; + std::string creationString = "Regions." + region->key.text + ", world, [\n"; - source << " # " << region->body.name << "\n" - << " # Events\n"; + source + << " # " << region->body.name << "\n" + << " # Events\n"; source << " add_events(" << creationString; - WriteEvents(*this, source, region->body.sections); + WriteEvents(*this, source, region->key.text, region->body.sections); for (const auto* extendRegion : extendRegionDecls) { - WriteEvents(*this, source, extendRegion->sections); + WriteEvents(*this, source, region->key.text, extendRegion->sections); } - source << " ])\n # Locations\n" - << " add_locations(" << creationString; + source + << " ])\n # Locations\n" + << " add_locations(" << creationString; WriteLocations(*this, source, region->body.sections); for (const auto* extendRegion : extendRegionDecls) { WriteLocations(*this, source, extendRegion->sections); } - source << " ])\n # Exits\n" - << " connect_regions(" << creationString; + source + << " ])\n # Exits\n" + << " connect_regions(" << creationString; WriteExits(*this, source, region->body.sections); for (const auto* extendRegion : extendRegionDecls) { WriteExits(*this, source, extendRegion->sections); } source << " ])\n\n"; - - // source << "areaTable[" << region->key << "] = Region(" - // << "\"" << region->body.name << "\", " - // << region->body.scene.value() << ", "; - - // source << "{\n // Events\n"; - // WriteEvents(source, region->body.sections); - // for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { - // WriteEvents(source, it->second->sections); - // } - // source << "}, {\n // Locations\n"; - // WriteLocations(source, region->body.sections); - // for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { - // WriteLocations(source, it->second->sections); - // } - // source << "}, {\n // Exits\n"; - // WriteExits(source, region->body.sections); - // for (auto it = extendRegionBegin; it != extendRegionEnd; it++) { - // WriteExits(source, it->second->sections); - // } - // source << "});\n\n"; } - - // source << "}\n"; } } // namespace rls::transpilers::soh_ap \ No newline at end of file diff --git a/transpilers/soh_ap/src/soh_ap.cpp b/transpilers/soh_ap/src/soh_ap.cpp index d324252..8d97722 100644 --- a/transpilers/soh_ap/src/soh_ap.cpp +++ b/transpilers/soh_ap/src/soh_ap.cpp @@ -8,6 +8,7 @@ SohApTranspiler::SohApTranspiler(const rls::ast::Project& project) void SohApTranspiler::Transpile(rls::OutputWriter& out) const { GenerateFunctionDefinitionsSource(out); GenerateRegionsSource(out); + GenerateEnumsSource(out); } } // namespace rls::transpilers::soh_ap From 9d69a83110fca9d681a7f7387ce34d6795e3adc5 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Fri, 15 May 2026 09:55:10 -0400 Subject: [PATCH 11/22] Make enum creation better add locations enum --- examples/soh_ap/enums.gen.py | 125 ++++++++++++++++++ examples/soh_ap/regions.gen.py | 138 ++++++++++---------- transpilers/soh_ap/src/generate_enums.cpp | 65 +++++---- transpilers/soh_ap/src/generate_regions.cpp | 2 +- transpilers/soh_ap/src/generate_regions.h | 10 -- 5 files changed, 233 insertions(+), 107 deletions(-) delete mode 100644 transpilers/soh_ap/src/generate_regions.h diff --git a/examples/soh_ap/enums.gen.py b/examples/soh_ap/enums.gen.py index af374c6..63df6c0 100644 --- a/examples/soh_ap/enums.gen.py +++ b/examples/soh_ap/enums.gen.py @@ -61,3 +61,128 @@ class RandomizerRegions(StrEnum): RR_ROOT = "Root" RR_ROOT_EXITS = "Root Exits" RR_SERENADE_OF_WATER_WARP = "Serenade of Water Warp" + +class RandomizerChecks(StrEnum): + RC_KF_KOKIRI_SWORD_CHEST = auto() + RC_KF_BOULDER_RUPEE_1 = auto() + RC_KF_BOULDER_RUPEE_2 = auto() + RC_KF_CHILD_GRASS_MAZE_1 = auto() + RC_KF_CHILD_GRASS_MAZE_2 = auto() + RC_KF_CHILD_GRASS_MAZE_3 = auto() + RC_KF_TWINS_HOUSE_POT_1 = auto() + RC_KF_TWINS_HOUSE_POT_2 = auto() + RC_KF_BROTHERS_HOUSE_POT_1 = auto() + RC_KF_BROTHERS_HOUSE_POT_2 = auto() + RC_KF_SHOP_ITEM_1 = auto() + RC_KF_SHOP_ITEM_2 = auto() + RC_KF_SHOP_ITEM_3 = auto() + RC_KF_SHOP_ITEM_4 = auto() + RC_KF_SHOP_ITEM_5 = auto() + RC_KF_SHOP_ITEM_6 = auto() + RC_KF_SHOP_ITEM_7 = auto() + RC_KF_SHOP_ITEM_8 = auto() + RC_KF_LINKS_HOUSE_COW = auto() + RC_KF_LINKS_HOUSE_POT = auto() + RC_KF_MIDOS_TOP_LEFT_CHEST = auto() + RC_KF_MIDOS_TOP_RIGHT_CHEST = auto() + RC_KF_MIDOS_BOTTOM_LEFT_CHEST = auto() + RC_KF_MIDOS_BOTTOM_RIGHT_CHEST = auto() + RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY = auto() + RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG = auto() + RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY = auto() + RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG = auto() + RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE = auto() + RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE = auto() + RC_KF_GOSSIP_STONE_FAIRY = auto() + RC_KF_GOSSIP_STONE_FAIRY_BIG = auto() + RC_KF_BEAN_RUPEE_1 = auto() + RC_KF_BEAN_RUPEE_2 = auto() + RC_KF_BEAN_RUPEE_3 = auto() + RC_KF_BEAN_RUPEE_4 = auto() + RC_KF_BEAN_RUPEE_5 = auto() + RC_KF_BEAN_RUPEE_6 = auto() + RC_KF_BEAN_RED_RUPEE = auto() + RC_KF_GOSSIP_STONE = auto() + RC_KF_BEAN_RUPEE_1 = auto() + RC_KF_BEAN_RUPEE_2 = auto() + RC_KF_BEAN_RUPEE_3 = auto() + RC_KF_BEAN_RUPEE_4 = auto() + RC_KF_BEAN_RUPEE_5 = auto() + RC_KF_BEAN_RUPEE_6 = auto() + RC_KF_BEAN_RED_RUPEE = auto() + RC_KF_SARIAS_TOP_LEFT_HEART = auto() + RC_KF_SARIAS_TOP_RIGHT_HEART = auto() + RC_KF_SARIAS_BOTTOM_LEFT_HEART = auto() + RC_KF_SARIAS_BOTTOM_RIGHT_HEART = auto() + RC_KF_STORMS_GROTTO_CHEST = auto() + RC_KF_STORMS_GROTTO_BEEHIVE_LEFT = auto() + RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT = auto() + RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY = auto() + RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG = auto() + RC_KF_STORMS_GROTTO_FISH = auto() + RC_KF_STORMS_GROTTO_GRASS_1 = auto() + RC_KF_STORMS_GROTTO_GRASS_2 = auto() + RC_KF_STORMS_GROTTO_GRASS_3 = auto() + RC_KF_STORMS_GROTTO_GRASS_4 = auto() + RC_KF_STORMS_GROTTO_GOSSIP_STONE = auto() + RC_KF_BEAN_SPROUT_FAIRY_1 = auto() + RC_KF_BEAN_SPROUT_FAIRY_2 = auto() + RC_KF_BEAN_SPROUT_FAIRY_3 = auto() + RC_KF_BRIDGE_RUPEE = auto() + RC_KF_BEHIND_MIDOS_RUPEE = auto() + RC_KF_SOUTH_GRASS_WEST_RUPEE = auto() + RC_KF_SOUTH_GRASS_EAST_RUPEE = auto() + RC_KF_NORTH_GRASS_WEST_RUPEE = auto() + RC_KF_NORTH_GRASS_EAST_RUPEE = auto() + RC_KF_SARIAS_ROOF_WEST_HEART = auto() + RC_KF_SARIAS_ROOF_EAST_HEART = auto() + RC_KF_SARIAS_ROOF_NORTH_HEART = auto() + RC_KF_BEAN_RUPEE_1 = auto() + RC_KF_BEAN_RUPEE_2 = auto() + RC_KF_BEAN_RUPEE_3 = auto() + RC_KF_BEAN_RUPEE_4 = auto() + RC_KF_BEAN_RUPEE_5 = auto() + RC_KF_BEAN_RUPEE_6 = auto() + RC_KF_BEAN_RED_RUPEE = auto() + RC_KF_GS_KNOW_IT_ALL_HOUSE = auto() + RC_KF_GS_BEAN_PATCH = auto() + RC_KF_GS_HOUSE_OF_TWINS = auto() + RC_KF_CHILD_GRASS_1 = auto() + RC_KF_CHILD_GRASS_2 = auto() + RC_KF_CHILD_GRASS_3 = auto() + RC_KF_CHILD_GRASS_4 = auto() + RC_KF_CHILD_GRASS_5 = auto() + RC_KF_CHILD_GRASS_6 = auto() + RC_KF_CHILD_GRASS_7 = auto() + RC_KF_CHILD_GRASS_8 = auto() + RC_KF_CHILD_GRASS_9 = auto() + RC_KF_CHILD_GRASS_10 = auto() + RC_KF_CHILD_GRASS_11 = auto() + RC_KF_CHILD_GRASS_12 = auto() + RC_KF_ADULT_GRASS_1 = auto() + RC_KF_ADULT_GRASS_2 = auto() + RC_KF_ADULT_GRASS_3 = auto() + RC_KF_ADULT_GRASS_4 = auto() + RC_KF_ADULT_GRASS_5 = auto() + RC_KF_ADULT_GRASS_6 = auto() + RC_KF_ADULT_GRASS_7 = auto() + RC_KF_ADULT_GRASS_8 = auto() + RC_KF_ADULT_GRASS_9 = auto() + RC_KF_ADULT_GRASS_10 = auto() + RC_KF_ADULT_GRASS_11 = auto() + RC_KF_ADULT_GRASS_12 = auto() + RC_KF_ADULT_GRASS_13 = auto() + RC_KF_ADULT_GRASS_14 = auto() + RC_KF_ADULT_GRASS_15 = auto() + RC_KF_ADULT_GRASS_16 = auto() + RC_KF_ADULT_GRASS_17 = auto() + RC_KF_ADULT_GRASS_18 = auto() + RC_KF_ADULT_GRASS_19 = auto() + RC_KF_ADULT_GRASS_20 = auto() + RC_LINKS_POCKET = auto() + RC_TRIFORCE_COMPLETED = auto() + RC_SARIA_SONG_HINT = auto() + RC_SONG_FROM_IMPA = auto() + RC_HC_MALON_EGG = auto() + RC_HC_ZELDAS_LETTER = auto() + RC_TOT_MASTER_SWORD = auto() diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py index 3610636..d160dcc 100644 --- a/examples/soh_ap/regions.gen.py +++ b/examples/soh_ap/regions.gen.py @@ -7,46 +7,46 @@ def set_region_rules(world: "SohWorld") -> None: # Adult Spawn # Events - add_events(Regions.RR_ADULT_SPAWN, world, [ + add_events(RandomizerRegion.RR_ADULT_SPAWN, world, [ ]) # Locations - add_locations(Regions.RR_ADULT_SPAWN, world, [ + add_locations(RandomizerRegion.RR_ADULT_SPAWN, world, [ ]) # Exits - connect_regions(Regions.RR_ADULT_SPAWN, world, [ + connect_regions(RandomizerRegion.RR_ADULT_SPAWN, world, [ (RandomizerRegion.RR_TEMPLE_OF_TIME, lambda bundle: True), ]) # Bolero of Fire Warp # Events - add_events(Regions.RR_BOLERO_OF_FIRE_WARP, world, [ + add_events(RandomizerRegion.RR_BOLERO_OF_FIRE_WARP, world, [ ]) # Locations - add_locations(Regions.RR_BOLERO_OF_FIRE_WARP, world, [ + add_locations(RandomizerRegion.RR_BOLERO_OF_FIRE_WARP, world, [ ]) # Exits - connect_regions(Regions.RR_BOLERO_OF_FIRE_WARP, world, [ + connect_regions(RandomizerRegion.RR_BOLERO_OF_FIRE_WARP, world, [ (RandomizerRegion.RR_DMC_PAD_ENTRY, lambda bundle: True), ]) # Child Spawn # Events - add_events(Regions.RR_CHILD_SPAWN, world, [ + add_events(RandomizerRegion.RR_CHILD_SPAWN, world, [ ]) # Locations - add_locations(Regions.RR_CHILD_SPAWN, world, [ + add_locations(RandomizerRegion.RR_CHILD_SPAWN, world, [ ]) # Exits - connect_regions(Regions.RR_CHILD_SPAWN, world, [ + connect_regions(RandomizerRegion.RR_CHILD_SPAWN, world, [ (RandomizerRegion.RR_KF_LINKS_HOUSE, lambda bundle: True), ]) # KF Boulder Loop # Events - add_events(Regions.RR_KF_BOULDER_LOOP, world, [ + add_events(RandomizerRegion.RR_KF_BOULDER_LOOP, world, [ ]) # Locations - add_locations(Regions.RR_KF_BOULDER_LOOP, world, [ + add_locations(RandomizerRegion.RR_KF_BOULDER_LOOP, world, [ (RandomizerCheck.RC_KF_KOKIRI_SWORD_CHEST, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_OPEN_CHEST)), (RandomizerCheck.RC_KF_BOULDER_RUPEE_1, lambda bundle: is_child(bundle)), (RandomizerCheck.RC_KF_BOULDER_RUPEE_2, lambda bundle: is_child(bundle)), @@ -55,44 +55,44 @@ def set_region_rules(world: "SohWorld") -> None: (RandomizerCheck.RC_KF_CHILD_GRASS_MAZE_3, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), ]) # Exits - connect_regions(Regions.RR_KF_BOULDER_LOOP, world, [ + connect_regions(RandomizerRegion.RR_KF_BOULDER_LOOP, world, [ (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: can_use(bundle, RandomizerGet.RG_CRAWL)), ]) # KF House of Twins # Events - add_events(Regions.RR_KF_HOUSE_OF_TWINS, world, [ + add_events(RandomizerRegion.RR_KF_HOUSE_OF_TWINS, world, [ ]) # Locations - add_locations(Regions.RR_KF_HOUSE_OF_TWINS, world, [ + add_locations(RandomizerRegion.RR_KF_HOUSE_OF_TWINS, world, [ (RandomizerCheck.RC_KF_TWINS_HOUSE_POT_1, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), (RandomizerCheck.RC_KF_TWINS_HOUSE_POT_2, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), ]) # Exits - connect_regions(Regions.RR_KF_HOUSE_OF_TWINS, world, [ + connect_regions(RandomizerRegion.RR_KF_HOUSE_OF_TWINS, world, [ (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Know It All House # Events - add_events(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ + add_events(RandomizerRegion.RR_KF_KNOW_IT_ALL_HOUSE, world, [ ]) # Locations - add_locations(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ + add_locations(RandomizerRegion.RR_KF_KNOW_IT_ALL_HOUSE, world, [ (RandomizerCheck.RC_KF_BROTHERS_HOUSE_POT_1, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), (RandomizerCheck.RC_KF_BROTHERS_HOUSE_POT_2, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), ]) # Exits - connect_regions(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ + connect_regions(RandomizerRegion.RR_KF_KNOW_IT_ALL_HOUSE, world, [ (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Kokiri Shop # Events - add_events(Regions.RR_KF_KOKIRI_SHOP, world, [ + add_events(RandomizerRegion.RR_KF_KOKIRI_SHOP, world, [ ]) # Locations - add_locations(Regions.RR_KF_KOKIRI_SHOP, world, [ + add_locations(RandomizerRegion.RR_KF_KOKIRI_SHOP, world, [ (RandomizerCheck.RC_KF_SHOP_ITEM_1, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), (RandomizerCheck.RC_KF_SHOP_ITEM_2, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), (RandomizerCheck.RC_KF_SHOP_ITEM_3, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), @@ -103,63 +103,63 @@ def set_region_rules(world: "SohWorld") -> None: (RandomizerCheck.RC_KF_SHOP_ITEM_8, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), ]) # Exits - connect_regions(Regions.RR_KF_KOKIRI_SHOP, world, [ + connect_regions(RandomizerRegion.RR_KF_KOKIRI_SHOP, world, [ (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Link's House # Events - add_events(Regions.RR_KF_LINKS_HOUSE, world, [ + add_events(RandomizerRegion.RR_KF_LINKS_HOUSE, world, [ ]) # Locations - add_locations(Regions.RR_KF_LINKS_HOUSE, world, [ + add_locations(RandomizerRegion.RR_KF_LINKS_HOUSE, world, [ (RandomizerCheck.RC_KF_LINKS_HOUSE_COW, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_EPONAS_SONG) and flag(bundle, LOGIC_LINKS_COW)), (RandomizerCheck.RC_KF_LINKS_HOUSE_POT, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), ]) # Exits - connect_regions(Regions.RR_KF_LINKS_HOUSE, world, [ + connect_regions(RandomizerRegion.RR_KF_LINKS_HOUSE, world, [ (RandomizerRegion.RR_KF_LINKS_PORCH, lambda bundle: True), ]) # KF Link's Porch # Events - add_events(Regions.RR_KF_LINKS_PORCH, world, [ + add_events(RandomizerRegion.RR_KF_LINKS_PORCH, world, [ ]) # Locations - add_locations(Regions.RR_KF_LINKS_PORCH, world, [ + add_locations(RandomizerRegion.RR_KF_LINKS_PORCH, world, [ ]) # Exits - connect_regions(Regions.RR_KF_LINKS_PORCH, world, [ + connect_regions(RandomizerRegion.RR_KF_LINKS_PORCH, world, [ (RandomizerRegion.RR_KF_LINKS_HOUSE, lambda bundle: True), (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Mido's House # Events - add_events(Regions.RR_KF_MIDOS_HOUSE, world, [ + add_events(RandomizerRegion.RR_KF_MIDOS_HOUSE, world, [ ]) # Locations - add_locations(Regions.RR_KF_MIDOS_HOUSE, world, [ + add_locations(RandomizerRegion.RR_KF_MIDOS_HOUSE, world, [ (RandomizerCheck.RC_KF_MIDOS_TOP_LEFT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), (RandomizerCheck.RC_KF_MIDOS_TOP_RIGHT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), (RandomizerCheck.RC_KF_MIDOS_BOTTOM_LEFT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), (RandomizerCheck.RC_KF_MIDOS_BOTTOM_RIGHT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), ]) # Exits - connect_regions(Regions.RR_KF_MIDOS_HOUSE, world, [ + connect_regions(RandomizerRegion.RR_KF_MIDOS_HOUSE, world, [ (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Outside Deku Tree # Events - add_events(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ + add_events(RandomizerRegion.RR_KF_OUTSIDE_DEKU_TREE, world, [ (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_STICK_ACCESS, Events.LOGIC_STICK_ACCESS, lambda bundle: can_get_deku_baba_sticks(bundle)), (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_NUT_ACCESS, Events.LOGIC_NUT_ACCESS, lambda bundle: can_get_deku_baba_nuts(bundle)), (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns(bundle)), (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) and can_use(bundle, RandomizerGet.RG_DEKU_SHIELD)), ]) # Locations - add_locations(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ + add_locations(RandomizerRegion.RR_KF_OUTSIDE_DEKU_TREE, world, [ (RandomizerCheck.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), (RandomizerCheck.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), (RandomizerCheck.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), @@ -168,17 +168,17 @@ def set_region_rules(world: "SohWorld") -> None: (RandomizerCheck.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, lambda bundle: True), ]) # Exits - connect_regions(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ + connect_regions(RandomizerRegion.RR_KF_OUTSIDE_DEKU_TREE, world, [ (RandomizerRegion.RR_DEKU_TREE_ENTRYWAY, lambda bundle: is_child(bundle) or bundle[2].options.RSK_SHUFFLE_DUNGEON_ENTRANCES != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF and (bundle[2].options.RSK_FOREST == RO_CLOSED_FOREST_OFF or flag(bundle, LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: is_adult(bundle) and (can_pass(bundle, RandomizerEnemy.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) or flag(bundle, LOGIC_DEKU_TREE_CLEAR)) or bundle[2].options.RSK_FOREST == RO_CLOSED_FOREST_OFF or flag(bundle, LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), ]) # KF Outside Lost Woods # Events - add_events(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ + add_events(RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, world, [ ]) # Locations - add_locations(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ + add_locations(RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, world, [ (RandomizerCheck.RC_KF_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), (RandomizerCheck.RC_KF_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), (RandomizerCheck.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), @@ -191,7 +191,7 @@ def set_region_rules(world: "SohWorld") -> None: (RandomizerCheck.RC_KF_GOSSIP_STONE, lambda bundle: True), ]) # Exits - connect_regions(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ + connect_regions(RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, world, [ (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), (RandomizerRegion.RR_THE_LOST_WOODS, lambda bundle: True), (RandomizerRegion.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS))), @@ -200,10 +200,10 @@ def set_region_rules(world: "SohWorld") -> None: # KF Alcove # Events - add_events(Regions.RR_KF_RUPEE_ALCOVE, world, [ + add_events(RandomizerRegion.RR_KF_RUPEE_ALCOVE, world, [ ]) # Locations - add_locations(Regions.RR_KF_RUPEE_ALCOVE, world, [ + add_locations(RandomizerRegion.RR_KF_RUPEE_ALCOVE, world, [ (RandomizerCheck.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), (RandomizerCheck.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), (RandomizerCheck.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), @@ -213,35 +213,35 @@ def set_region_rules(world: "SohWorld") -> None: (RandomizerCheck.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), ]) # Exits - connect_regions(Regions.RR_KF_RUPEE_ALCOVE, world, [ + connect_regions(RandomizerRegion.RR_KF_RUPEE_ALCOVE, world, [ (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Saria's House # Events - add_events(Regions.RR_KF_SARIAS_HOUSE, world, [ + add_events(RandomizerRegion.RR_KF_SARIAS_HOUSE, world, [ ]) # Locations - add_locations(Regions.RR_KF_SARIAS_HOUSE, world, [ + add_locations(RandomizerRegion.RR_KF_SARIAS_HOUSE, world, [ (RandomizerCheck.RC_KF_SARIAS_TOP_LEFT_HEART, lambda bundle: True), (RandomizerCheck.RC_KF_SARIAS_TOP_RIGHT_HEART, lambda bundle: True), (RandomizerCheck.RC_KF_SARIAS_BOTTOM_LEFT_HEART, lambda bundle: True), (RandomizerCheck.RC_KF_SARIAS_BOTTOM_RIGHT_HEART, lambda bundle: True), ]) # Exits - connect_regions(Regions.RR_KF_SARIAS_HOUSE, world, [ + connect_regions(RandomizerRegion.RR_KF_SARIAS_HOUSE, world, [ (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), ]) # KF Storms Grotto # Events - add_events(Regions.RR_KF_STORMS_GROTTO, world, [ + add_events(RandomizerRegion.RR_KF_STORMS_GROTTO, world, [ (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy(bundle) or can_use(bundle, RandomizerGet.RG_STICKS)), (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_BUG_ACCESS, Events.LOGIC_BUG_ACCESS, lambda bundle: can_cut_shrubs(bundle)), (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_FISH_ACCESS, Events.LOGIC_FISH_ACCESS, lambda bundle: True), ]) # Locations - add_locations(Regions.RR_KF_STORMS_GROTTO, world, [ + add_locations(RandomizerRegion.RR_KF_STORMS_GROTTO, world, [ (RandomizerCheck.RC_KF_STORMS_GROTTO_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), (RandomizerCheck.RC_KF_STORMS_GROTTO_BEEHIVE_LEFT, lambda bundle: can_break_lower_beehives(bundle)), (RandomizerCheck.RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT, lambda bundle: can_break_lower_beehives(bundle)), @@ -255,18 +255,18 @@ def set_region_rules(world: "SohWorld") -> None: (RandomizerCheck.RC_KF_STORMS_GROTTO_GOSSIP_STONE, lambda bundle: True), ]) # Exits - connect_regions(Regions.RR_KF_STORMS_GROTTO, world, [ + connect_regions(RandomizerRegion.RR_KF_STORMS_GROTTO, world, [ (RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: True), ]) # Kokiri Forest # Events - add_events(Regions.RR_KOKIRI_FOREST, world, [ + add_events(RandomizerRegion.RR_KOKIRI_FOREST, world, [ (EventLocations.RR_KOKIRI_FOREST_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns(bundle) or is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), (EventLocations.RR_KOKIRI_FOREST_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) and can_use(bundle, RandomizerGet.RG_DEKU_SHIELD)), ]) # Locations - add_locations(Regions.RR_KOKIRI_FOREST, world, [ + add_locations(RandomizerRegion.RR_KOKIRI_FOREST, world, [ (RandomizerCheck.RC_KF_BEAN_SPROUT_FAIRY_1, lambda bundle: is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), (RandomizerCheck.RC_KF_BEAN_SPROUT_FAIRY_2, lambda bundle: is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), (RandomizerCheck.RC_KF_BEAN_SPROUT_FAIRY_3, lambda bundle: is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), @@ -323,7 +323,7 @@ def set_region_rules(world: "SohWorld") -> None: (RandomizerCheck.RC_KF_ADULT_GRASS_20, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), ]) # Exits - connect_regions(Regions.RR_KOKIRI_FOREST, world, [ + connect_regions(RandomizerRegion.RR_KOKIRI_FOREST, world, [ (RandomizerRegion.RR_KF_BOULDER_LOOP, lambda bundle: can_use(bundle, RandomizerGet.RG_CRAWL)), (RandomizerRegion.RR_KF_LINKS_PORCH, lambda bundle: can_climb_ladder(bundle) if is_child(bundle) else has(bundle, RandomizerGet.RG_CLIMB) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), (RandomizerRegion.RR_KF_MIDOS_HOUSE, lambda bundle: True), @@ -339,55 +339,55 @@ def set_region_rules(world: "SohWorld") -> None: # Minuet of Forest Warp # Events - add_events(Regions.RR_MINUET_OF_FOREST_WARP, world, [ + add_events(RandomizerRegion.RR_MINUET_OF_FOREST_WARP, world, [ ]) # Locations - add_locations(Regions.RR_MINUET_OF_FOREST_WARP, world, [ + add_locations(RandomizerRegion.RR_MINUET_OF_FOREST_WARP, world, [ ]) # Exits - connect_regions(Regions.RR_MINUET_OF_FOREST_WARP, world, [ + connect_regions(RandomizerRegion.RR_MINUET_OF_FOREST_WARP, world, [ (RandomizerRegion.RR_SACRED_FOREST_MEADOW, lambda bundle: True), ]) # Nocturne of Shadow Warp # Events - add_events(Regions.RR_NOCTURNE_OF_SHADOW_WARP, world, [ + add_events(RandomizerRegion.RR_NOCTURNE_OF_SHADOW_WARP, world, [ ]) # Locations - add_locations(Regions.RR_NOCTURNE_OF_SHADOW_WARP, world, [ + add_locations(RandomizerRegion.RR_NOCTURNE_OF_SHADOW_WARP, world, [ ]) # Exits - connect_regions(Regions.RR_NOCTURNE_OF_SHADOW_WARP, world, [ + connect_regions(RandomizerRegion.RR_NOCTURNE_OF_SHADOW_WARP, world, [ (RandomizerRegion.RR_GRAVEYARD_WARP_PAD_REGION, lambda bundle: True), ]) # Prelude of Light Warp # Events - add_events(Regions.RR_PRELUDE_OF_LIGHT_WARP, world, [ + add_events(RandomizerRegion.RR_PRELUDE_OF_LIGHT_WARP, world, [ ]) # Locations - add_locations(Regions.RR_PRELUDE_OF_LIGHT_WARP, world, [ + add_locations(RandomizerRegion.RR_PRELUDE_OF_LIGHT_WARP, world, [ ]) # Exits - connect_regions(Regions.RR_PRELUDE_OF_LIGHT_WARP, world, [ + connect_regions(RandomizerRegion.RR_PRELUDE_OF_LIGHT_WARP, world, [ (RandomizerRegion.RR_TEMPLE_OF_TIME, lambda bundle: True), ]) # Requiem of Spirit Warp # Events - add_events(Regions.RR_REQUIEM_OF_SPIRIT_WARP, world, [ + add_events(RandomizerRegion.RR_REQUIEM_OF_SPIRIT_WARP, world, [ ]) # Locations - add_locations(Regions.RR_REQUIEM_OF_SPIRIT_WARP, world, [ + add_locations(RandomizerRegion.RR_REQUIEM_OF_SPIRIT_WARP, world, [ ]) # Exits - connect_regions(Regions.RR_REQUIEM_OF_SPIRIT_WARP, world, [ + connect_regions(RandomizerRegion.RR_REQUIEM_OF_SPIRIT_WARP, world, [ (RandomizerRegion.RR_DESERT_COLOSSUS, lambda bundle: True), ]) # Root # Events - add_events(Regions.RR_ROOT, world, [ + add_events(RandomizerRegion.RR_ROOT, world, [ (EventLocations.RR_ROOT_LOGIC_KAKARIKO_GATE_OPEN, Events.LOGIC_KAKARIKO_GATE_OPEN, lambda bundle: bundle[2].options.RSK_KAK_GATE == RO_KAK_GATE_OPEN), (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, Events.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE), (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, Events.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE or bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FAST), @@ -397,7 +397,7 @@ def set_region_rules(world: "SohWorld") -> None: (EventLocations.RR_ROOT_LOGIC_FREED_EPONA, Events.LOGIC_FREED_EPONA, lambda bundle: bundle[2].options.RSK_SKIP_EPONA_RACE == RO_GENERIC_YES), ]) # Locations - add_locations(Regions.RR_ROOT, world, [ + add_locations(RandomizerRegion.RR_ROOT, world, [ (RandomizerCheck.RC_LINKS_POCKET, lambda bundle: True), (RandomizerCheck.RC_TRIFORCE_COMPLETED, lambda bundle: collected_triforce_pieces(bundle) >= required_triforce_pieces(bundle)), (RandomizerCheck.RC_SARIA_SONG_HINT, lambda bundle: can_use(bundle, RandomizerGet.RG_SARIAS_SONG)), @@ -407,19 +407,19 @@ def set_region_rules(world: "SohWorld") -> None: (RandomizerCheck.RC_TOT_MASTER_SWORD, lambda bundle: bundle[2].options.RSK_SELECTED_STARTING_AGE == RO_AGE_ADULT), ]) # Exits - connect_regions(Regions.RR_ROOT, world, [ + connect_regions(RandomizerRegion.RR_ROOT, world, [ (RandomizerRegion.RR_ROOT_EXITS, lambda bundle: True), ]) # Root Exits # Events - add_events(Regions.RR_ROOT_EXITS, world, [ + add_events(RandomizerRegion.RR_ROOT_EXITS, world, [ ]) # Locations - add_locations(Regions.RR_ROOT_EXITS, world, [ + add_locations(RandomizerRegion.RR_ROOT_EXITS, world, [ ]) # Exits - connect_regions(Regions.RR_ROOT_EXITS, world, [ + connect_regions(RandomizerRegion.RR_ROOT_EXITS, world, [ (RandomizerRegion.RR_CHILD_SPAWN, lambda bundle: is_child(bundle)), (RandomizerRegion.RR_ADULT_SPAWN, lambda bundle: is_adult(bundle)), (RandomizerRegion.RR_MINUET_OF_FOREST_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_MINUET_OF_FOREST)), @@ -432,13 +432,13 @@ def set_region_rules(world: "SohWorld") -> None: # Serenade of Water Warp # Events - add_events(Regions.RR_SERENADE_OF_WATER_WARP, world, [ + add_events(RandomizerRegion.RR_SERENADE_OF_WATER_WARP, world, [ ]) # Locations - add_locations(Regions.RR_SERENADE_OF_WATER_WARP, world, [ + add_locations(RandomizerRegion.RR_SERENADE_OF_WATER_WARP, world, [ ]) # Exits - connect_regions(Regions.RR_SERENADE_OF_WATER_WARP, world, [ + connect_regions(RandomizerRegion.RR_SERENADE_OF_WATER_WARP, world, [ (RandomizerRegion.RR_LAKE_HYLIA, lambda bundle: True), ]) diff --git a/transpilers/soh_ap/src/generate_enums.cpp b/transpilers/soh_ap/src/generate_enums.cpp index 03407af..02bc8c7 100644 --- a/transpilers/soh_ap/src/generate_enums.cpp +++ b/transpilers/soh_ap/src/generate_enums.cpp @@ -27,23 +27,36 @@ void WriteEvents( }); } +void WriteLocationsEnum( + const SohApTranspiler& transpiler, + std::ostream& source, + const std::vector& sections) +{ + WriteEntries(sections, rls::ast::SectionKind::Locations, [&](const rls::ast::Entry& entry){ + source << " " << entry.name << " = auto()\n"; + }); +} + void SohApTranspiler::GenerateEnumsSource(rls::OutputWriter& out) const { auto& source = out.open("enums.gen.py"); - std::stringstream sstream; - - sstream << "class RandomizerRegions(StrEnum):\n"; - source << "# Generated by RLS soh_ap transpiler\n" - << "from enum import StrEnum, IntEnum, auto, Enum\n" - << "\n" - << "class EventLocations(StrEnum):\n"; + << "from enum import StrEnum, IntEnum, auto, Enum\n"; + + std::ostringstream eventLocations; + std::ostringstream events; + std::ostringstream regions; + std::ostringstream locations; + + eventLocations << "class EventLocations(StrEnum):\n"; + events << "class Events(StrEnum):\n"; + regions << "class RandomizerRegions(StrEnum):\n";; + locations << "class RandomizerChecks(StrEnum):\n";; - // Event Locations for (const auto& [regionName, region] : project.RegionDecls) { // Do Regions while here - sstream << " " << region->key.text << " = \"" << region->body.name << "\"\n"; + regions << " " << region->key.text << " = \"" << region->body.name << "\"\n"; std::vector extendRegionDecls; const auto extendRegionIt = project.ExtendRegionDecls.find(region->key.text); @@ -52,32 +65,30 @@ void SohApTranspiler::GenerateEnumsSource(rls::OutputWriter& out) const { extendRegionDecls = extendRegionIt->second; } - WriteEventLocations(*this, source, region->key.text, region->body.sections); + // Event Locations + WriteEventLocations(*this, eventLocations, region->key.text, region->body.sections); for (const auto* extendRegion : extendRegionDecls) { - WriteEventLocations(*this, source, region->key.text, extendRegion->sections); + WriteEventLocations(*this, eventLocations, region->key.text, extendRegion->sections); } - } - - // Events - source - << "\n" - << "class Events(StrEnum):\n"; - for (const auto& [regionName, region] : project.RegionDecls) { - std::vector extendRegionDecls; - const auto extendRegionIt = project.ExtendRegionDecls.find(region->key.text); - if (extendRegionIt != project.ExtendRegionDecls.end()) { - extendRegionDecls = extendRegionIt->second; + // Events + WriteEvents(*this, events, region->body.sections); + for (const auto* extendRegion : extendRegionDecls) { + WriteEvents(*this, events, extendRegion->sections); } - WriteEvents(*this, source, region->body.sections); + // Locations + WriteLocationsEnum(*this, locations, region->body.sections); for (const auto* extendRegion : extendRegionDecls) { - WriteEvents(*this, source, extendRegion->sections); + WriteLocationsEnum(*this, locations, extendRegion->sections); } } - - // Regions - source << "\n" << sstream.str(); + + // Output to source + source << "\n" << eventLocations.str(); + source << "\n" << events.str(); + source << "\n" << regions.str(); + source << "\n" << locations.str(); } diff --git a/transpilers/soh_ap/src/generate_regions.cpp b/transpilers/soh_ap/src/generate_regions.cpp index c7814bd..36d67a5 100644 --- a/transpilers/soh_ap/src/generate_regions.cpp +++ b/transpilers/soh_ap/src/generate_regions.cpp @@ -66,7 +66,7 @@ void SohApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { extendRegionDecls = extendRegionIt->second; } - std::string creationString = "Regions." + region->key.text + ", world, [\n"; + std::string creationString = "RandomizerRegion." + region->key.text + ", world, [\n"; source << " # " << region->body.name << "\n" diff --git a/transpilers/soh_ap/src/generate_regions.h b/transpilers/soh_ap/src/generate_regions.h deleted file mode 100644 index b15407f..0000000 --- a/transpilers/soh_ap/src/generate_regions.h +++ /dev/null @@ -1,10 +0,0 @@ -// #pragma once - -// #include "ast.h" -// #include "output.h" - -// namespace rls::transpilers::soh_ap { - -// void GenerateRegionsSource(const rls::ast::Project& project, rls::OutputWriter& out); - -// } // namespace rls::transpilers::soh_ap \ No newline at end of file From 043ac39505cdf9f319e0d744c83cbe27f7f33225 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Fri, 15 May 2026 20:06:20 -0400 Subject: [PATCH 12/22] Filter out unnecessary add_event, add_locations, and connect_region calls from output. --- examples/soh_ap/regions.gen.py | 87 --------------------- transpilers/soh_ap/src/generate_regions.cpp | 68 ++++++++++++---- 2 files changed, 53 insertions(+), 102 deletions(-) diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py index d160dcc..6fabd3b 100644 --- a/examples/soh_ap/regions.gen.py +++ b/examples/soh_ap/regions.gen.py @@ -6,45 +6,24 @@ def set_region_rules(world: "SohWorld") -> None: # Adult Spawn - # Events - add_events(RandomizerRegion.RR_ADULT_SPAWN, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_ADULT_SPAWN, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_ADULT_SPAWN, world, [ (RandomizerRegion.RR_TEMPLE_OF_TIME, lambda bundle: True), ]) # Bolero of Fire Warp - # Events - add_events(RandomizerRegion.RR_BOLERO_OF_FIRE_WARP, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_BOLERO_OF_FIRE_WARP, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_BOLERO_OF_FIRE_WARP, world, [ (RandomizerRegion.RR_DMC_PAD_ENTRY, lambda bundle: True), ]) # Child Spawn - # Events - add_events(RandomizerRegion.RR_CHILD_SPAWN, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_CHILD_SPAWN, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_CHILD_SPAWN, world, [ (RandomizerRegion.RR_KF_LINKS_HOUSE, lambda bundle: True), ]) # KF Boulder Loop - # Events - add_events(RandomizerRegion.RR_KF_BOULDER_LOOP, world, [ - ]) # Locations add_locations(RandomizerRegion.RR_KF_BOULDER_LOOP, world, [ (RandomizerCheck.RC_KF_KOKIRI_SWORD_CHEST, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_OPEN_CHEST)), @@ -60,9 +39,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # KF House of Twins - # Events - add_events(RandomizerRegion.RR_KF_HOUSE_OF_TWINS, world, [ - ]) # Locations add_locations(RandomizerRegion.RR_KF_HOUSE_OF_TWINS, world, [ (RandomizerCheck.RC_KF_TWINS_HOUSE_POT_1, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), @@ -74,9 +50,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # KF Know It All House - # Events - add_events(RandomizerRegion.RR_KF_KNOW_IT_ALL_HOUSE, world, [ - ]) # Locations add_locations(RandomizerRegion.RR_KF_KNOW_IT_ALL_HOUSE, world, [ (RandomizerCheck.RC_KF_BROTHERS_HOUSE_POT_1, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), @@ -88,9 +61,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # KF Kokiri Shop - # Events - add_events(RandomizerRegion.RR_KF_KOKIRI_SHOP, world, [ - ]) # Locations add_locations(RandomizerRegion.RR_KF_KOKIRI_SHOP, world, [ (RandomizerCheck.RC_KF_SHOP_ITEM_1, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), @@ -108,9 +78,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # KF Link's House - # Events - add_events(RandomizerRegion.RR_KF_LINKS_HOUSE, world, [ - ]) # Locations add_locations(RandomizerRegion.RR_KF_LINKS_HOUSE, world, [ (RandomizerCheck.RC_KF_LINKS_HOUSE_COW, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_EPONAS_SONG) and flag(bundle, LOGIC_LINKS_COW)), @@ -122,12 +89,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # KF Link's Porch - # Events - add_events(RandomizerRegion.RR_KF_LINKS_PORCH, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_KF_LINKS_PORCH, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_KF_LINKS_PORCH, world, [ (RandomizerRegion.RR_KF_LINKS_HOUSE, lambda bundle: True), @@ -135,9 +96,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # KF Mido's House - # Events - add_events(RandomizerRegion.RR_KF_MIDOS_HOUSE, world, [ - ]) # Locations add_locations(RandomizerRegion.RR_KF_MIDOS_HOUSE, world, [ (RandomizerCheck.RC_KF_MIDOS_TOP_LEFT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), @@ -174,9 +132,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # KF Outside Lost Woods - # Events - add_events(RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, world, [ - ]) # Locations add_locations(RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, world, [ (RandomizerCheck.RC_KF_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), @@ -199,9 +154,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # KF Alcove - # Events - add_events(RandomizerRegion.RR_KF_RUPEE_ALCOVE, world, [ - ]) # Locations add_locations(RandomizerRegion.RR_KF_RUPEE_ALCOVE, world, [ (RandomizerCheck.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), @@ -218,9 +170,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # KF Saria's House - # Events - add_events(RandomizerRegion.RR_KF_SARIAS_HOUSE, world, [ - ]) # Locations add_locations(RandomizerRegion.RR_KF_SARIAS_HOUSE, world, [ (RandomizerCheck.RC_KF_SARIAS_TOP_LEFT_HEART, lambda bundle: True), @@ -338,48 +287,24 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Minuet of Forest Warp - # Events - add_events(RandomizerRegion.RR_MINUET_OF_FOREST_WARP, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_MINUET_OF_FOREST_WARP, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_MINUET_OF_FOREST_WARP, world, [ (RandomizerRegion.RR_SACRED_FOREST_MEADOW, lambda bundle: True), ]) # Nocturne of Shadow Warp - # Events - add_events(RandomizerRegion.RR_NOCTURNE_OF_SHADOW_WARP, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_NOCTURNE_OF_SHADOW_WARP, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_NOCTURNE_OF_SHADOW_WARP, world, [ (RandomizerRegion.RR_GRAVEYARD_WARP_PAD_REGION, lambda bundle: True), ]) # Prelude of Light Warp - # Events - add_events(RandomizerRegion.RR_PRELUDE_OF_LIGHT_WARP, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_PRELUDE_OF_LIGHT_WARP, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_PRELUDE_OF_LIGHT_WARP, world, [ (RandomizerRegion.RR_TEMPLE_OF_TIME, lambda bundle: True), ]) # Requiem of Spirit Warp - # Events - add_events(RandomizerRegion.RR_REQUIEM_OF_SPIRIT_WARP, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_REQUIEM_OF_SPIRIT_WARP, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_REQUIEM_OF_SPIRIT_WARP, world, [ (RandomizerRegion.RR_DESERT_COLOSSUS, lambda bundle: True), @@ -412,12 +337,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Root Exits - # Events - add_events(RandomizerRegion.RR_ROOT_EXITS, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_ROOT_EXITS, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_ROOT_EXITS, world, [ (RandomizerRegion.RR_CHILD_SPAWN, lambda bundle: is_child(bundle)), @@ -431,12 +350,6 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Serenade of Water Warp - # Events - add_events(RandomizerRegion.RR_SERENADE_OF_WATER_WARP, world, [ - ]) - # Locations - add_locations(RandomizerRegion.RR_SERENADE_OF_WATER_WARP, world, [ - ]) # Exits connect_regions(RandomizerRegion.RR_SERENADE_OF_WATER_WARP, world, [ (RandomizerRegion.RR_LAKE_HYLIA, lambda bundle: True), diff --git a/transpilers/soh_ap/src/generate_regions.cpp b/transpilers/soh_ap/src/generate_regions.cpp index 36d67a5..78069f6 100644 --- a/transpilers/soh_ap/src/generate_regions.cpp +++ b/transpilers/soh_ap/src/generate_regions.cpp @@ -1,6 +1,7 @@ #include "soh_ap.h" #include +#include namespace rls::transpilers::soh_ap { @@ -58,6 +59,10 @@ void SohApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { << "\n" << "def set_region_rules(world: \"SohWorld\") -> None:\n"; + std::ostringstream events; + std::ostringstream locations; + std::ostringstream exits; + for (const auto& [regionName, region] : project.RegionDecls) { const auto extendRegionIt = project.ExtendRegionDecls.find(region->key.text); @@ -68,30 +73,63 @@ void SohApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { std::string creationString = "RandomizerRegion." + region->key.text + ", world, [\n"; - source - << " # " << region->body.name << "\n" - << " # Events\n"; + source << " # " << region->body.name << "\n"; + events + << " # Events\n" + << " add_events(" << creationString; - source << " add_events(" << creationString; - WriteEvents(*this, source, region->key.text, region->body.sections); + WriteEvents(*this, events, region->key.text, region->body.sections); for (const auto* extendRegion : extendRegionDecls) { - WriteEvents(*this, source, region->key.text, extendRegion->sections); + WriteEvents(*this, events, region->key.text, extendRegion->sections); + } + + events << " ])\n"; + + if (events.str() == " # Events\n add_events(" + creationString + " ])\n") { + events.str(""); } - source - << " ])\n # Locations\n" + + locations + << " # Locations\n" << " add_locations(" << creationString; - WriteLocations(*this, source, region->body.sections); + + WriteLocations(*this, locations, region->body.sections); for (const auto* extendRegion : extendRegionDecls) { - WriteLocations(*this, source, extendRegion->sections); + WriteLocations(*this, locations, extendRegion->sections); + } + + locations << " ])\n"; + + if (locations.str() == " # Locations\n add_locations(" + creationString + " ])\n") { + locations.str(""); } - source - << " ])\n # Exits\n" + + + exits + << " # Exits\n" << " connect_regions(" << creationString; - WriteExits(*this, source, region->body.sections); + + WriteExits(*this, exits, region->body.sections); for (const auto* extendRegion : extendRegionDecls) { - WriteExits(*this, source, extendRegion->sections); + WriteExits(*this, exits, extendRegion->sections); + } + + exits << " ])\n"; + + if (exits.str() == " # Exits\n connect_regions(" + creationString + " ])\n") { + exits.str(""); } - source << " ])\n\n"; + + // Add things to source + source << events.str(); + source << locations.str(); + source << exits.str(); + source << "\n"; + + // Clear ostringstreams + events.str(""); + locations.str(""); + exits.str(""); } } From a421810c959a910cea27a798d96f5cb81cf0a256 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Sun, 14 Jun 2026 14:44:20 -0400 Subject: [PATCH 13/22] Update to RuleBuilder rules - Disable function creation for now - Update enum names to match upstream Ship AP - Fix duplicated values in enum output - Add enum value method for fixing names in python --- examples/soh_ap/enums.gen.py | 213 +++++---- examples/soh_ap/functions.gen.py | 78 ---- examples/soh_ap/regions.gen.py | 442 +++++++++--------- transpilers/soh_ap/include/soh_ap.h | 4 + transpilers/soh_ap/src/generate_enums.cpp | 71 +-- .../soh_ap/src/generate_expression.cpp | 153 ++++-- transpilers/soh_ap/src/generate_regions.cpp | 8 +- transpilers/soh_ap/src/soh_ap.cpp | 2 +- 8 files changed, 500 insertions(+), 471 deletions(-) delete mode 100644 examples/soh_ap/functions.gen.py diff --git a/examples/soh_ap/enums.gen.py b/examples/soh_ap/enums.gen.py index 63df6c0..9493e8a 100644 --- a/examples/soh_ap/enums.gen.py +++ b/examples/soh_ap/enums.gen.py @@ -2,6 +2,10 @@ from enum import StrEnum, IntEnum, auto, Enum class EventLocations(StrEnum): + @staticmethod + def _generate_next_value_(name, start, count, last_values): + new_name = name.replace("RR_", "").replace("_", " ").title() + return new_name RR_KF_OUTSIDE_DEKU_TREE_LOGIC_STICK_ACCESS = auto() RR_KF_OUTSIDE_DEKU_TREE_LOGIC_NUT_ACCESS = auto() RR_KF_OUTSIDE_DEKU_TREE_LOGIC_FAIRY_ACCESS = auto() @@ -20,24 +24,25 @@ class EventLocations(StrEnum): RR_ROOT_LOGIC_FREED_EPONA = auto() class Events(StrEnum): - LOGIC_STICK_ACCESS = auto() - LOGIC_NUT_ACCESS = auto() - LOGIC_FAIRY_ACCESS = auto() - LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD = auto() - LOGIC_FAIRY_ACCESS = auto() + @staticmethod + def _generate_next_value_(name, start, count, last_values): + new_name = name.replace("LOGIC_", "").replace("_", " ").title() + return new_name LOGIC_BUG_ACCESS = auto() - LOGIC_FISH_ACCESS = auto() LOGIC_FAIRY_ACCESS = auto() - LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD = auto() + LOGIC_FISH_ACCESS = auto() + LOGIC_FREED_EPONA = auto() LOGIC_KAKARIKO_GATE_OPEN = auto() + LOGIC_NUT_ACCESS = auto() + LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD = auto() + LOGIC_STICK_ACCESS = auto() LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER = auto() - LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER = auto() LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER = auto() + LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER = auto() LOGIC_TH_COULD_FREE_SLOPE_CARPENTER = auto() LOGIC_TH_RESCUED_ALL_CARPENTERS = auto() - LOGIC_FREED_EPONA = auto() -class RandomizerRegions(StrEnum): +class Regions(StrEnum): RR_ADULT_SPAWN = "Adult Spawn" RR_BOLERO_OF_FIRE_WARP = "Bolero of Fire Warp" RR_CHILD_SPAWN = "Child Spawn" @@ -62,127 +67,117 @@ class RandomizerRegions(StrEnum): RR_ROOT_EXITS = "Root Exits" RR_SERENADE_OF_WATER_WARP = "Serenade of Water Warp" -class RandomizerChecks(StrEnum): - RC_KF_KOKIRI_SWORD_CHEST = auto() +class Locations(StrEnum): + @staticmethod + def _generate_next_value_(name, start, count, last_values): + new_name = name.replace("RC_", "").replace("_", " ").title() + return new_name + RC_HC_MALON_EGG = auto() + RC_HC_ZELDAS_LETTER = auto() + RC_KF_ADULT_GRASS_1 = auto() + RC_KF_ADULT_GRASS_10 = auto() + RC_KF_ADULT_GRASS_11 = auto() + RC_KF_ADULT_GRASS_12 = auto() + RC_KF_ADULT_GRASS_13 = auto() + RC_KF_ADULT_GRASS_14 = auto() + RC_KF_ADULT_GRASS_15 = auto() + RC_KF_ADULT_GRASS_16 = auto() + RC_KF_ADULT_GRASS_17 = auto() + RC_KF_ADULT_GRASS_18 = auto() + RC_KF_ADULT_GRASS_19 = auto() + RC_KF_ADULT_GRASS_2 = auto() + RC_KF_ADULT_GRASS_20 = auto() + RC_KF_ADULT_GRASS_3 = auto() + RC_KF_ADULT_GRASS_4 = auto() + RC_KF_ADULT_GRASS_5 = auto() + RC_KF_ADULT_GRASS_6 = auto() + RC_KF_ADULT_GRASS_7 = auto() + RC_KF_ADULT_GRASS_8 = auto() + RC_KF_ADULT_GRASS_9 = auto() + RC_KF_BEAN_RED_RUPEE = auto() + RC_KF_BEAN_RUPEE_1 = auto() + RC_KF_BEAN_RUPEE_2 = auto() + RC_KF_BEAN_RUPEE_3 = auto() + RC_KF_BEAN_RUPEE_4 = auto() + RC_KF_BEAN_RUPEE_5 = auto() + RC_KF_BEAN_RUPEE_6 = auto() + RC_KF_BEAN_SPROUT_FAIRY_1 = auto() + RC_KF_BEAN_SPROUT_FAIRY_2 = auto() + RC_KF_BEAN_SPROUT_FAIRY_3 = auto() + RC_KF_BEHIND_MIDOS_RUPEE = auto() RC_KF_BOULDER_RUPEE_1 = auto() RC_KF_BOULDER_RUPEE_2 = auto() + RC_KF_BRIDGE_RUPEE = auto() + RC_KF_BROTHERS_HOUSE_POT_1 = auto() + RC_KF_BROTHERS_HOUSE_POT_2 = auto() + RC_KF_CHILD_GRASS_1 = auto() + RC_KF_CHILD_GRASS_10 = auto() + RC_KF_CHILD_GRASS_11 = auto() + RC_KF_CHILD_GRASS_12 = auto() + RC_KF_CHILD_GRASS_2 = auto() + RC_KF_CHILD_GRASS_3 = auto() + RC_KF_CHILD_GRASS_4 = auto() + RC_KF_CHILD_GRASS_5 = auto() + RC_KF_CHILD_GRASS_6 = auto() + RC_KF_CHILD_GRASS_7 = auto() + RC_KF_CHILD_GRASS_8 = auto() + RC_KF_CHILD_GRASS_9 = auto() RC_KF_CHILD_GRASS_MAZE_1 = auto() RC_KF_CHILD_GRASS_MAZE_2 = auto() RC_KF_CHILD_GRASS_MAZE_3 = auto() - RC_KF_TWINS_HOUSE_POT_1 = auto() - RC_KF_TWINS_HOUSE_POT_2 = auto() - RC_KF_BROTHERS_HOUSE_POT_1 = auto() - RC_KF_BROTHERS_HOUSE_POT_2 = auto() - RC_KF_SHOP_ITEM_1 = auto() - RC_KF_SHOP_ITEM_2 = auto() - RC_KF_SHOP_ITEM_3 = auto() - RC_KF_SHOP_ITEM_4 = auto() - RC_KF_SHOP_ITEM_5 = auto() - RC_KF_SHOP_ITEM_6 = auto() - RC_KF_SHOP_ITEM_7 = auto() - RC_KF_SHOP_ITEM_8 = auto() - RC_KF_LINKS_HOUSE_COW = auto() - RC_KF_LINKS_HOUSE_POT = auto() - RC_KF_MIDOS_TOP_LEFT_CHEST = auto() - RC_KF_MIDOS_TOP_RIGHT_CHEST = auto() - RC_KF_MIDOS_BOTTOM_LEFT_CHEST = auto() - RC_KF_MIDOS_BOTTOM_RIGHT_CHEST = auto() + RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE = auto() RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY = auto() RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG = auto() + RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE = auto() RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY = auto() RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG = auto() - RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE = auto() - RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE = auto() + RC_KF_GOSSIP_STONE = auto() RC_KF_GOSSIP_STONE_FAIRY = auto() RC_KF_GOSSIP_STONE_FAIRY_BIG = auto() - RC_KF_BEAN_RUPEE_1 = auto() - RC_KF_BEAN_RUPEE_2 = auto() - RC_KF_BEAN_RUPEE_3 = auto() - RC_KF_BEAN_RUPEE_4 = auto() - RC_KF_BEAN_RUPEE_5 = auto() - RC_KF_BEAN_RUPEE_6 = auto() - RC_KF_BEAN_RED_RUPEE = auto() - RC_KF_GOSSIP_STONE = auto() - RC_KF_BEAN_RUPEE_1 = auto() - RC_KF_BEAN_RUPEE_2 = auto() - RC_KF_BEAN_RUPEE_3 = auto() - RC_KF_BEAN_RUPEE_4 = auto() - RC_KF_BEAN_RUPEE_5 = auto() - RC_KF_BEAN_RUPEE_6 = auto() - RC_KF_BEAN_RED_RUPEE = auto() - RC_KF_SARIAS_TOP_LEFT_HEART = auto() - RC_KF_SARIAS_TOP_RIGHT_HEART = auto() + RC_KF_GS_BEAN_PATCH = auto() + RC_KF_GS_HOUSE_OF_TWINS = auto() + RC_KF_GS_KNOW_IT_ALL_HOUSE = auto() + RC_KF_KOKIRI_SWORD_CHEST = auto() + RC_KF_LINKS_HOUSE_COW = auto() + RC_KF_LINKS_HOUSE_POT = auto() + RC_KF_MIDOS_BOTTOM_LEFT_CHEST = auto() + RC_KF_MIDOS_BOTTOM_RIGHT_CHEST = auto() + RC_KF_MIDOS_TOP_LEFT_CHEST = auto() + RC_KF_MIDOS_TOP_RIGHT_CHEST = auto() + RC_KF_NORTH_GRASS_EAST_RUPEE = auto() + RC_KF_NORTH_GRASS_WEST_RUPEE = auto() RC_KF_SARIAS_BOTTOM_LEFT_HEART = auto() RC_KF_SARIAS_BOTTOM_RIGHT_HEART = auto() - RC_KF_STORMS_GROTTO_CHEST = auto() + RC_KF_SARIAS_ROOF_EAST_HEART = auto() + RC_KF_SARIAS_ROOF_NORTH_HEART = auto() + RC_KF_SARIAS_ROOF_WEST_HEART = auto() + RC_KF_SARIAS_TOP_LEFT_HEART = auto() + RC_KF_SARIAS_TOP_RIGHT_HEART = auto() + RC_KF_SHOP_ITEM_1 = auto() + RC_KF_SHOP_ITEM_2 = auto() + RC_KF_SHOP_ITEM_3 = auto() + RC_KF_SHOP_ITEM_4 = auto() + RC_KF_SHOP_ITEM_5 = auto() + RC_KF_SHOP_ITEM_6 = auto() + RC_KF_SHOP_ITEM_7 = auto() + RC_KF_SHOP_ITEM_8 = auto() + RC_KF_SOUTH_GRASS_EAST_RUPEE = auto() + RC_KF_SOUTH_GRASS_WEST_RUPEE = auto() RC_KF_STORMS_GROTTO_BEEHIVE_LEFT = auto() RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT = auto() + RC_KF_STORMS_GROTTO_CHEST = auto() + RC_KF_STORMS_GROTTO_FISH = auto() + RC_KF_STORMS_GROTTO_GOSSIP_STONE = auto() RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY = auto() RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG = auto() - RC_KF_STORMS_GROTTO_FISH = auto() RC_KF_STORMS_GROTTO_GRASS_1 = auto() RC_KF_STORMS_GROTTO_GRASS_2 = auto() RC_KF_STORMS_GROTTO_GRASS_3 = auto() RC_KF_STORMS_GROTTO_GRASS_4 = auto() - RC_KF_STORMS_GROTTO_GOSSIP_STONE = auto() - RC_KF_BEAN_SPROUT_FAIRY_1 = auto() - RC_KF_BEAN_SPROUT_FAIRY_2 = auto() - RC_KF_BEAN_SPROUT_FAIRY_3 = auto() - RC_KF_BRIDGE_RUPEE = auto() - RC_KF_BEHIND_MIDOS_RUPEE = auto() - RC_KF_SOUTH_GRASS_WEST_RUPEE = auto() - RC_KF_SOUTH_GRASS_EAST_RUPEE = auto() - RC_KF_NORTH_GRASS_WEST_RUPEE = auto() - RC_KF_NORTH_GRASS_EAST_RUPEE = auto() - RC_KF_SARIAS_ROOF_WEST_HEART = auto() - RC_KF_SARIAS_ROOF_EAST_HEART = auto() - RC_KF_SARIAS_ROOF_NORTH_HEART = auto() - RC_KF_BEAN_RUPEE_1 = auto() - RC_KF_BEAN_RUPEE_2 = auto() - RC_KF_BEAN_RUPEE_3 = auto() - RC_KF_BEAN_RUPEE_4 = auto() - RC_KF_BEAN_RUPEE_5 = auto() - RC_KF_BEAN_RUPEE_6 = auto() - RC_KF_BEAN_RED_RUPEE = auto() - RC_KF_GS_KNOW_IT_ALL_HOUSE = auto() - RC_KF_GS_BEAN_PATCH = auto() - RC_KF_GS_HOUSE_OF_TWINS = auto() - RC_KF_CHILD_GRASS_1 = auto() - RC_KF_CHILD_GRASS_2 = auto() - RC_KF_CHILD_GRASS_3 = auto() - RC_KF_CHILD_GRASS_4 = auto() - RC_KF_CHILD_GRASS_5 = auto() - RC_KF_CHILD_GRASS_6 = auto() - RC_KF_CHILD_GRASS_7 = auto() - RC_KF_CHILD_GRASS_8 = auto() - RC_KF_CHILD_GRASS_9 = auto() - RC_KF_CHILD_GRASS_10 = auto() - RC_KF_CHILD_GRASS_11 = auto() - RC_KF_CHILD_GRASS_12 = auto() - RC_KF_ADULT_GRASS_1 = auto() - RC_KF_ADULT_GRASS_2 = auto() - RC_KF_ADULT_GRASS_3 = auto() - RC_KF_ADULT_GRASS_4 = auto() - RC_KF_ADULT_GRASS_5 = auto() - RC_KF_ADULT_GRASS_6 = auto() - RC_KF_ADULT_GRASS_7 = auto() - RC_KF_ADULT_GRASS_8 = auto() - RC_KF_ADULT_GRASS_9 = auto() - RC_KF_ADULT_GRASS_10 = auto() - RC_KF_ADULT_GRASS_11 = auto() - RC_KF_ADULT_GRASS_12 = auto() - RC_KF_ADULT_GRASS_13 = auto() - RC_KF_ADULT_GRASS_14 = auto() - RC_KF_ADULT_GRASS_15 = auto() - RC_KF_ADULT_GRASS_16 = auto() - RC_KF_ADULT_GRASS_17 = auto() - RC_KF_ADULT_GRASS_18 = auto() - RC_KF_ADULT_GRASS_19 = auto() - RC_KF_ADULT_GRASS_20 = auto() + RC_KF_TWINS_HOUSE_POT_1 = auto() + RC_KF_TWINS_HOUSE_POT_2 = auto() RC_LINKS_POCKET = auto() - RC_TRIFORCE_COMPLETED = auto() RC_SARIA_SONG_HINT = auto() RC_SONG_FROM_IMPA = auto() - RC_HC_MALON_EGG = auto() - RC_HC_ZELDAS_LETTER = auto() RC_TOT_MASTER_SWORD = auto() + RC_TRIFORCE_COMPLETED = auto() diff --git a/examples/soh_ap/functions.gen.py b/examples/soh_ap/functions.gen.py deleted file mode 100644 index 07bcdd0..0000000 --- a/examples/soh_ap/functions.gen.py +++ /dev/null @@ -1,78 +0,0 @@ -# Generated by RLS soh_ap transpiler -from .enums.gen.py import * -from .rls_match import rls_match - -def _can_get_drop_gold_skulltula(bundle, distance: EnemyDistance) -> bool: - return rls_match((lambda distance=distance: distance == EnemyDistance.ED_CLOSE or distance == EnemyDistance.ED_SHORT_JUMPSLASH or distance == EnemyDistance.ED_MASTER_SWORD_JUMPSLASH or distance == EnemyDistance.ED_LONG_JUMPSLASH or distance == EnemyDistance.ED_BOMB_THROW or distance == EnemyDistance.ED_BOOMERANG), (lambda: can_use(bundle, RandomizerGet.RG_BOOMERANG)), True, (lambda distance=distance: distance == EnemyDistance.ED_HOOKSHOT), (lambda: can_use(bundle, RandomizerGet.RG_HOOKSHOT)), True, (lambda distance=distance: distance == EnemyDistance.ED_LONGSHOT), (lambda: can_use(bundle, RandomizerGet.RG_LONGSHOT)), False) - -def _can_kill_gold_skulltula(bundle, distance: EnemyDistance, wall_or_floor: bool) -> bool: - return rls_match((lambda distance=distance: distance == EnemyDistance.ED_CLOSE), (lambda: can_use(bundle, RandomizerGet.RG_MEGATON_HAMMER)), True, (lambda distance=distance: distance == EnemyDistance.ED_SHORT_JUMPSLASH), (lambda: can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD)), True, (lambda distance=distance: distance == EnemyDistance.ED_MASTER_SWORD_JUMPSLASH), (lambda: can_use(bundle, RandomizerGet.RG_MASTER_SWORD)), True, (lambda distance=distance: distance == EnemyDistance.ED_LONG_JUMPSLASH), (lambda: can_use(bundle, RandomizerGet.RG_BIGGORON_SWORD) or can_use(bundle, RandomizerGet.RG_STICKS)), True, (lambda distance=distance: distance == EnemyDistance.ED_BOMB_THROW), (lambda: can_use(bundle, RandomizerGet.RG_BOMB_BAG)), True, (lambda distance=distance: distance == EnemyDistance.ED_BOOMERANG), (lambda: can_use(bundle, RandomizerGet.RG_BOOMERANG) or can_use(bundle, RandomizerGet.RG_DINS_FIRE)), True, (lambda distance=distance: distance == EnemyDistance.ED_HOOKSHOT), (lambda: can_use(bundle, RandomizerGet.RG_HOOKSHOT)), True, (lambda distance=distance: distance == EnemyDistance.ED_LONGSHOT), (lambda: can_use(bundle, RandomizerGet.RG_LONGSHOT) or wall_or_floor and can_use(bundle, RandomizerGet.RG_BOMBCHU_5)), True, (lambda distance=distance: distance == EnemyDistance.ED_FAR), (lambda: can_use(bundle, RandomizerGet.RG_FAIRY_SLINGSHOT) or can_use(bundle, RandomizerGet.RG_FAIRY_BOW)), False) - -def call_gossip_fairy(bundle) -> bool: - return call_gossip_fairy_except_suns(bundle) or can_use(bundle, RandomizerGet.RG_SUNS_SONG) - -def call_gossip_fairy_except_suns(bundle) -> bool: - return can_use(bundle, RandomizerGet.RG_ZELDAS_LULLABY) or can_use(bundle, RandomizerGet.RG_EPONAS_SONG) or can_use(bundle, RandomizerGet.RG_SONG_OF_TIME) - -def can_avoid(bundle, e: RandomizerEnemy, grounded: bool = False, quantity: int = 1) -> bool: - return can_kill(bundle, e, EnemyDistance.ED_CLOSE, True, quantity, False, False) or rls_match((lambda e=e: e == RandomizerEnemy.RE_GOLD_SKULLTULA), (lambda: True), False) - -def can_break_lower_beehives(bundle) -> bool: - return can_break_upper_beehives(bundle) or can_use(bundle, RandomizerGet.RG_BOMB_BAG) - -def can_break_upper_beehives(bundle) -> bool: - return hookshot_or_boomerang(bundle) or trick(bundle, RandomizerTrick.RT_BOMBCHU_BEEHIVES) and can_use(bundle, RandomizerGet.RG_BOMBCHU_5) or bundle[2].options.RSK_SLINGBOW_BREAK_BEEHIVES and (can_use(bundle, RandomizerGet.RG_FAIRY_BOW) or can_use(bundle, RandomizerGet.RG_FAIRY_SLINGSHOT)) - -def can_climb_ladder(bundle) -> bool: - return has(bundle, RandomizerGet.RG_CLIMB) or trick(bundle, RandomizerTrick.RT_HOOKSHOT_LADDERS) and can_use(bundle, RandomizerGet.RG_HOOKSHOT) - -def can_cut_shrubs(bundle) -> bool: - return can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) or can_use(bundle, RandomizerGet.RG_BOOMERANG) or has_explosives(bundle) or has(bundle, RandomizerGet.RG_GORONS_BRACELET) or can_use(bundle, RandomizerGet.RG_MASTER_SWORD) or can_use(bundle, RandomizerGet.RG_MEGATON_HAMMER) or can_use(bundle, RandomizerGet.RG_BIGGORON_SWORD) or can_use(bundle, RandomizerGet.RG_GIANTS_KNIFE) - -def can_get_deku_baba_nuts(bundle) -> bool: - return can_jumpslash(bundle) or can_use(bundle, RandomizerGet.RG_FAIRY_SLINGSHOT) or can_use(bundle, RandomizerGet.RG_FAIRY_BOW) or has_explosives(bundle) or can_use(bundle, RandomizerGet.RG_DINS_FIRE) - -def can_get_deku_baba_sticks(bundle) -> bool: - return can_use_sword(bundle) or can_use(bundle, RandomizerGet.RG_BOOMERANG) - -def can_get_drop(bundle, e: RandomizerEnemy, distance: EnemyDistance = EnemyDistance.ED_CLOSE, above_link: bool = False) -> bool: - return can_kill(bundle, e, distance, True, 1, False, False) and (distance_to_int(bundle, distance) <= distance_to_int(bundle, EnemyDistance.ED_MASTER_SWORD_JUMPSLASH) or rls_match((lambda e=e: e == RandomizerEnemy.RE_GOLD_SKULLTULA), (lambda: _can_get_drop_gold_skulltula(bundle, distance)), False, (lambda e=e: e == RandomizerEnemy.RE_KEESE or e == RandomizerEnemy.RE_FIRE_KEESE or e == RandomizerEnemy.RE_GUAY), (lambda: True), False, (lambda: true), (lambda: above_link or distance_to_int(bundle, distance) <= distance_to_int(bundle, EnemyDistance.ED_BOOMERANG) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), False)) - -def can_get_night_time_gs(bundle) -> bool: - return at_night(bundle) and (can_use(bundle, RandomizerGet.RG_SUNS_SONG) or not bundle[2].options.RSK_SKULLS_SUNS_SONG) - -def can_jumpslash(bundle) -> bool: - return can_jumpslash_except_hammer(bundle) or can_use(bundle, RandomizerGet.RG_MEGATON_HAMMER) - -def can_jumpslash_except_hammer(bundle) -> bool: - return can_use(bundle, RandomizerGet.RG_STICKS) or can_use_sword(bundle) - -def can_kill(bundle, e: RandomizerEnemy, distance: EnemyDistance = EnemyDistance.ED_CLOSE, wall_or_floor: bool = True, quantity: int = 1, timer: bool = False, in_water: bool = False) -> bool: - return rls_match((lambda e=e: e == RandomizerEnemy.RE_GOLD_SKULLTULA), (lambda: _can_kill_gold_skulltula(bundle, distance, wall_or_floor)), False) - -def can_open_storms_grotto(bundle) -> bool: - return can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS) and (has(bundle, RandomizerGet.RG_STONE_OF_AGONY) or trick(bundle, RandomizerTrick.RT_GROTTOS_WITHOUT_AGONY)) - -def can_pass(bundle, e: RandomizerEnemy, distance: EnemyDistance = EnemyDistance.ED_CLOSE, wall_or_floor: bool = True) -> bool: - return can_kill(bundle, e, distance, wall_or_floor, 1, False, False) or rls_match((lambda e=e: e == RandomizerEnemy.RE_GOLD_SKULLTULA), (lambda: True), False) - -def can_spawn_soil_skull(bundle, bean: RandomizerGet) -> bool: - return is_child(bundle) and can_use(bundle, RandomizerGet.RG_BOTTLE_WITH_BUGS) and has(bundle, bean) - -def can_use_sword(bundle) -> bool: - return can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) or can_use(bundle, RandomizerGet.RG_MASTER_SWORD) or can_use(bundle, RandomizerGet.RG_BIGGORON_SWORD) - -def distance_to_int(bundle, distance: EnemyDistance) -> int: - return rls_match((lambda distance=distance: distance == EnemyDistance.ED_CLOSE), (lambda: 0), False, (lambda distance=distance: distance == EnemyDistance.ED_SHORT_JUMPSLASH), (lambda: 1), False, (lambda distance=distance: distance == EnemyDistance.ED_MASTER_SWORD_JUMPSLASH), (lambda: 2), False, (lambda distance=distance: distance == EnemyDistance.ED_LONG_JUMPSLASH), (lambda: 3), False, (lambda distance=distance: distance == EnemyDistance.ED_BOMB_THROW), (lambda: 4), False, (lambda distance=distance: distance == EnemyDistance.ED_BOOMERANG), (lambda: 5), False, (lambda distance=distance: distance == EnemyDistance.ED_HOOKSHOT), (lambda: 6), False, (lambda distance=distance: distance == EnemyDistance.ED_LONGSHOT), (lambda: 7), False, (lambda distance=distance: distance == EnemyDistance.ED_FAR), (lambda: 8), False) - -def has_bottle(bundle) -> bool: - return bottle_count(bundle) >= 1 - -def has_explosives(bundle) -> bool: - return can_use(bundle, RandomizerGet.RG_BOMB_BAG) or can_use(bundle, RandomizerGet.RG_BOMBCHU_5) - -def hookshot_or_boomerang(bundle) -> bool: - return can_use(bundle, RandomizerGet.RG_HOOKSHOT) or can_use(bundle, RandomizerGet.RG_BOOMERANG) - -def wallet_capacity(bundle) -> int: - return 999 if has(bundle, RandomizerGet.RG_TYCOON_WALLET) else 500 if has(bundle, RandomizerGet.RG_GIANT_WALLET) else 200 if has(bundle, RandomizerGet.RG_ADULT_WALLET) else 99 if has(bundle, RandomizerGet.RG_CHILD_WALLET) else 0 diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py index 6fabd3b..f5f6523 100644 --- a/examples/soh_ap/regions.gen.py +++ b/examples/soh_ap/regions.gen.py @@ -1,5 +1,5 @@ # Generated by RLS soh_ap transpiler -from .functions.gen.py import * +from .LogicHelpers import * if TYPE_CHECKING: from ... import SohWorld @@ -7,351 +7,351 @@ def set_region_rules(world: "SohWorld") -> None: # Adult Spawn # Exits - connect_regions(RandomizerRegion.RR_ADULT_SPAWN, world, [ - (RandomizerRegion.RR_TEMPLE_OF_TIME, lambda bundle: True), + connect_regions(Regions.RR_ADULT_SPAWN, world, [ + (Regions.RR_TEMPLE_OF_TIME, lambda bundle: True_()), ]) # Bolero of Fire Warp # Exits - connect_regions(RandomizerRegion.RR_BOLERO_OF_FIRE_WARP, world, [ - (RandomizerRegion.RR_DMC_PAD_ENTRY, lambda bundle: True), + connect_regions(Regions.RR_BOLERO_OF_FIRE_WARP, world, [ + (Regions.RR_DMC_PAD_ENTRY, lambda bundle: True_()), ]) # Child Spawn # Exits - connect_regions(RandomizerRegion.RR_CHILD_SPAWN, world, [ - (RandomizerRegion.RR_KF_LINKS_HOUSE, lambda bundle: True), + connect_regions(Regions.RR_CHILD_SPAWN, world, [ + (Regions.RR_KF_LINKS_HOUSE, lambda bundle: True_()), ]) # KF Boulder Loop # Locations - add_locations(RandomizerRegion.RR_KF_BOULDER_LOOP, world, [ - (RandomizerCheck.RC_KF_KOKIRI_SWORD_CHEST, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_OPEN_CHEST)), - (RandomizerCheck.RC_KF_BOULDER_RUPEE_1, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_BOULDER_RUPEE_2, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_MAZE_1, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_MAZE_2, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_MAZE_3, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), + add_locations(Regions.RR_KF_BOULDER_LOOP, world, [ + (Locations.RC_KF_KOKIRI_SWORD_CHEST, lambda bundle: is_child(bundle) & has_item(bundle, Items.RG_OPEN_CHEST)), + (Locations.RC_KF_BOULDER_RUPEE_1, lambda bundle: is_child(bundle)), + (Locations.RC_KF_BOULDER_RUPEE_2, lambda bundle: is_child(bundle)), + (Locations.RC_KF_CHILD_GRASS_MAZE_1, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_MAZE_2, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_MAZE_3, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_BOULDER_LOOP, world, [ - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: can_use(bundle, RandomizerGet.RG_CRAWL)), + connect_regions(Regions.RR_KF_BOULDER_LOOP, world, [ + (Regions.RR_KOKIRI_FOREST, lambda bundle: can_use(bundle, Items.RG_CRAWL)), ]) # KF House of Twins # Locations - add_locations(RandomizerRegion.RR_KF_HOUSE_OF_TWINS, world, [ - (RandomizerCheck.RC_KF_TWINS_HOUSE_POT_1, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), - (RandomizerCheck.RC_KF_TWINS_HOUSE_POT_2, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), + add_locations(Regions.RR_KF_HOUSE_OF_TWINS, world, [ + (Locations.RC_KF_TWINS_HOUSE_POT_1, lambda bundle: has_item(bundle, Items.RG_POWER_BRACELET)), + (Locations.RC_KF_TWINS_HOUSE_POT_2, lambda bundle: has_item(bundle, Items.RG_POWER_BRACELET)), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_HOUSE_OF_TWINS, world, [ - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), + connect_regions(Regions.RR_KF_HOUSE_OF_TWINS, world, [ + (Regions.RR_KOKIRI_FOREST, lambda bundle: True_()), ]) # KF Know It All House # Locations - add_locations(RandomizerRegion.RR_KF_KNOW_IT_ALL_HOUSE, world, [ - (RandomizerCheck.RC_KF_BROTHERS_HOUSE_POT_1, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), - (RandomizerCheck.RC_KF_BROTHERS_HOUSE_POT_2, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), + add_locations(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ + (Locations.RC_KF_BROTHERS_HOUSE_POT_1, lambda bundle: has_item(bundle, Items.RG_POWER_BRACELET)), + (Locations.RC_KF_BROTHERS_HOUSE_POT_2, lambda bundle: has_item(bundle, Items.RG_POWER_BRACELET)), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_KNOW_IT_ALL_HOUSE, world, [ - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), + connect_regions(Regions.RR_KF_KNOW_IT_ALL_HOUSE, world, [ + (Regions.RR_KOKIRI_FOREST, lambda bundle: True_()), ]) # KF Kokiri Shop # Locations - add_locations(RandomizerRegion.RR_KF_KOKIRI_SHOP, world, [ - (RandomizerCheck.RC_KF_SHOP_ITEM_1, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), - (RandomizerCheck.RC_KF_SHOP_ITEM_2, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), - (RandomizerCheck.RC_KF_SHOP_ITEM_3, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), - (RandomizerCheck.RC_KF_SHOP_ITEM_4, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), - (RandomizerCheck.RC_KF_SHOP_ITEM_5, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), - (RandomizerCheck.RC_KF_SHOP_ITEM_6, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), - (RandomizerCheck.RC_KF_SHOP_ITEM_7, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), - (RandomizerCheck.RC_KF_SHOP_ITEM_8, lambda bundle: has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and check_price(bundle, RandomizerCheck.RC_UNKNOWN_CHECK) <= wallet_capacity(bundle)), + add_locations(Regions.RR_KF_KOKIRI_SHOP, world, [ + (Locations.RC_KF_SHOP_ITEM_1, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), + (Locations.RC_KF_SHOP_ITEM_2, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), + (Locations.RC_KF_SHOP_ITEM_3, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), + (Locations.RC_KF_SHOP_ITEM_4, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), + (Locations.RC_KF_SHOP_ITEM_5, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), + (Locations.RC_KF_SHOP_ITEM_6, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), + (Locations.RC_KF_SHOP_ITEM_7, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), + (Locations.RC_KF_SHOP_ITEM_8, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_KOKIRI_SHOP, world, [ - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), + connect_regions(Regions.RR_KF_KOKIRI_SHOP, world, [ + (Regions.RR_KOKIRI_FOREST, lambda bundle: True_()), ]) # KF Link's House # Locations - add_locations(RandomizerRegion.RR_KF_LINKS_HOUSE, world, [ - (RandomizerCheck.RC_KF_LINKS_HOUSE_COW, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_EPONAS_SONG) and flag(bundle, LOGIC_LINKS_COW)), - (RandomizerCheck.RC_KF_LINKS_HOUSE_POT, lambda bundle: has(bundle, RandomizerGet.RG_POWER_BRACELET)), + add_locations(Regions.RR_KF_LINKS_HOUSE, world, [ + (Locations.RC_KF_LINKS_HOUSE_COW, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_EPONAS_SONG) & has_item(bundle, Events.LOGIC_LINKS_COW)), + (Locations.RC_KF_LINKS_HOUSE_POT, lambda bundle: has_item(bundle, Items.RG_POWER_BRACELET)), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_LINKS_HOUSE, world, [ - (RandomizerRegion.RR_KF_LINKS_PORCH, lambda bundle: True), + connect_regions(Regions.RR_KF_LINKS_HOUSE, world, [ + (Regions.RR_KF_LINKS_PORCH, lambda bundle: True_()), ]) # KF Link's Porch # Exits - connect_regions(RandomizerRegion.RR_KF_LINKS_PORCH, world, [ - (RandomizerRegion.RR_KF_LINKS_HOUSE, lambda bundle: True), - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), + connect_regions(Regions.RR_KF_LINKS_PORCH, world, [ + (Regions.RR_KF_LINKS_HOUSE, lambda bundle: True_()), + (Regions.RR_KOKIRI_FOREST, lambda bundle: True_()), ]) # KF Mido's House # Locations - add_locations(RandomizerRegion.RR_KF_MIDOS_HOUSE, world, [ - (RandomizerCheck.RC_KF_MIDOS_TOP_LEFT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), - (RandomizerCheck.RC_KF_MIDOS_TOP_RIGHT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), - (RandomizerCheck.RC_KF_MIDOS_BOTTOM_LEFT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), - (RandomizerCheck.RC_KF_MIDOS_BOTTOM_RIGHT_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), + add_locations(Regions.RR_KF_MIDOS_HOUSE, world, [ + (Locations.RC_KF_MIDOS_TOP_LEFT_CHEST, lambda bundle: has_item(bundle, Items.RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_TOP_RIGHT_CHEST, lambda bundle: has_item(bundle, Items.RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_BOTTOM_LEFT_CHEST, lambda bundle: has_item(bundle, Items.RG_OPEN_CHEST)), + (Locations.RC_KF_MIDOS_BOTTOM_RIGHT_CHEST, lambda bundle: has_item(bundle, Items.RG_OPEN_CHEST)), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_MIDOS_HOUSE, world, [ - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), + connect_regions(Regions.RR_KF_MIDOS_HOUSE, world, [ + (Regions.RR_KOKIRI_FOREST, lambda bundle: True_()), ]) # KF Outside Deku Tree # Events - add_events(RandomizerRegion.RR_KF_OUTSIDE_DEKU_TREE, world, [ + add_events(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_STICK_ACCESS, Events.LOGIC_STICK_ACCESS, lambda bundle: can_get_deku_baba_sticks(bundle)), (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_NUT_ACCESS, Events.LOGIC_NUT_ACCESS, lambda bundle: can_get_deku_baba_nuts(bundle)), (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns(bundle)), - (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) and can_use(bundle, RandomizerGet.RG_DEKU_SHIELD)), + (EventLocations.RR_KF_OUTSIDE_DEKU_TREE_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child(bundle) & has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_use(bundle, Items.RG_KOKIRI_SWORD) & can_use(bundle, Items.RG_DEKU_SHIELD)), ]) # Locations - add_locations(RandomizerRegion.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (RandomizerCheck.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), - (RandomizerCheck.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), - (RandomizerCheck.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), - (RandomizerCheck.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), - (RandomizerCheck.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE, lambda bundle: True), - (RandomizerCheck.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, lambda bundle: True), + add_locations(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, Items.RG_SONG_OF_STORMS)), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, Items.RG_SONG_OF_STORMS)), + (Locations.RC_KF_DEKU_TREE_LEFT_GOSSIP_STONE, lambda bundle: True_()), + (Locations.RC_KF_DEKU_TREE_RIGHT_GOSSIP_STONE, lambda bundle: True_()), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (RandomizerRegion.RR_DEKU_TREE_ENTRYWAY, lambda bundle: is_child(bundle) or bundle[2].options.RSK_SHUFFLE_DUNGEON_ENTRANCES != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF and (bundle[2].options.RSK_FOREST == RO_CLOSED_FOREST_OFF or flag(bundle, LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: is_adult(bundle) and (can_pass(bundle, RandomizerEnemy.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) or flag(bundle, LOGIC_DEKU_TREE_CLEAR)) or bundle[2].options.RSK_FOREST == RO_CLOSED_FOREST_OFF or flag(bundle, LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), + connect_regions(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ + (Regions.RR_DEKU_TREE_ENTRYWAY, lambda bundle: is_child(bundle) | (OptionFilter(RSK_SHUFFLE_DUNGEON_ENTRANCES, RandomizerSettingKey.RO_DUNGEON_ENTRANCE_SHUFFLE_OFF, "ne")) & ((OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_OFF)) | has_item(bundle, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), + (Regions.RR_KOKIRI_FOREST, lambda bundle: is_adult(bundle) & (can_pass(bundle, Enemies.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) | has_item(bundle, Events.LOGIC_DEKU_TREE_CLEAR)) | (OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_OFF)) | has_item(bundle, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), ]) # KF Outside Lost Woods # Locations - add_locations(RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, world, [ - (RandomizerCheck.RC_KF_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), - (RandomizerCheck.RC_KF_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), - (RandomizerCheck.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_BOOMERANG)), - (RandomizerCheck.RC_KF_GOSSIP_STONE, lambda bundle: True), + add_locations(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ + (Locations.RC_KF_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy_except_suns(bundle)), + (Locations.RC_KF_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, Items.RG_SONG_OF_STORMS)), + (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_BOOMERANG)), + (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_BOOMERANG)), + (Locations.RC_KF_GOSSIP_STONE, lambda bundle: True_()), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, world, [ - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), - (RandomizerRegion.RR_THE_LOST_WOODS, lambda bundle: True), - (RandomizerRegion.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS))), - (RandomizerRegion.RR_KF_STORMS_GROTTO, lambda bundle: can_open_storms_grotto(bundle)), + connect_regions(Regions.RR_KF_OUTSIDE_LOST_WOODS, world, [ + (Regions.RR_KOKIRI_FOREST, lambda bundle: True_()), + (Regions.RR_THE_LOST_WOODS, lambda bundle: True_()), + (Regions.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_use(bundle, Items.RG_HOVER_BOOTS))), + (Regions.RR_KF_STORMS_GROTTO, lambda bundle: can_open_storms_grotto(bundle)), ]) # KF Alcove # Locations - add_locations(RandomizerRegion.RR_KF_RUPEE_ALCOVE, world, [ - (RandomizerCheck.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), - (RandomizerCheck.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), + add_locations(Regions.RR_KF_RUPEE_ALCOVE, world, [ + (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_HOVER_BOOTS)), + (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) & can_use(bundle, Items.RG_HOVER_BOOTS)), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_RUPEE_ALCOVE, world, [ - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), + connect_regions(Regions.RR_KF_RUPEE_ALCOVE, world, [ + (Regions.RR_KOKIRI_FOREST, lambda bundle: True_()), ]) # KF Saria's House # Locations - add_locations(RandomizerRegion.RR_KF_SARIAS_HOUSE, world, [ - (RandomizerCheck.RC_KF_SARIAS_TOP_LEFT_HEART, lambda bundle: True), - (RandomizerCheck.RC_KF_SARIAS_TOP_RIGHT_HEART, lambda bundle: True), - (RandomizerCheck.RC_KF_SARIAS_BOTTOM_LEFT_HEART, lambda bundle: True), - (RandomizerCheck.RC_KF_SARIAS_BOTTOM_RIGHT_HEART, lambda bundle: True), + add_locations(Regions.RR_KF_SARIAS_HOUSE, world, [ + (Locations.RC_KF_SARIAS_TOP_LEFT_HEART, lambda bundle: True_()), + (Locations.RC_KF_SARIAS_TOP_RIGHT_HEART, lambda bundle: True_()), + (Locations.RC_KF_SARIAS_BOTTOM_LEFT_HEART, lambda bundle: True_()), + (Locations.RC_KF_SARIAS_BOTTOM_RIGHT_HEART, lambda bundle: True_()), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_SARIAS_HOUSE, world, [ - (RandomizerRegion.RR_KOKIRI_FOREST, lambda bundle: True), + connect_regions(Regions.RR_KF_SARIAS_HOUSE, world, [ + (Regions.RR_KOKIRI_FOREST, lambda bundle: True_()), ]) # KF Storms Grotto # Events - add_events(RandomizerRegion.RR_KF_STORMS_GROTTO, world, [ - (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy(bundle) or can_use(bundle, RandomizerGet.RG_STICKS)), + add_events(Regions.RR_KF_STORMS_GROTTO, world, [ + (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy(bundle) | can_use(bundle, Items.RG_STICKS)), (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_BUG_ACCESS, Events.LOGIC_BUG_ACCESS, lambda bundle: can_cut_shrubs(bundle)), - (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_FISH_ACCESS, Events.LOGIC_FISH_ACCESS, lambda bundle: True), + (EventLocations.RR_KF_STORMS_GROTTO_LOGIC_FISH_ACCESS, Events.LOGIC_FISH_ACCESS, lambda bundle: True_()), ]) # Locations - add_locations(RandomizerRegion.RR_KF_STORMS_GROTTO, world, [ - (RandomizerCheck.RC_KF_STORMS_GROTTO_CHEST, lambda bundle: has(bundle, RandomizerGet.RG_OPEN_CHEST)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_BEEHIVE_LEFT, lambda bundle: can_break_lower_beehives(bundle)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT, lambda bundle: can_break_lower_beehives(bundle)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy(bundle)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_FISH, lambda bundle: has_bottle(bundle)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_GRASS_1, lambda bundle: can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_GRASS_2, lambda bundle: can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_GRASS_3, lambda bundle: can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_GRASS_4, lambda bundle: can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_STORMS_GROTTO_GOSSIP_STONE, lambda bundle: True), + add_locations(Regions.RR_KF_STORMS_GROTTO, world, [ + (Locations.RC_KF_STORMS_GROTTO_CHEST, lambda bundle: has_item(bundle, Items.RG_OPEN_CHEST)), + (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_LEFT, lambda bundle: can_break_lower_beehives(bundle)), + (Locations.RC_KF_STORMS_GROTTO_BEEHIVE_RIGHT, lambda bundle: can_break_lower_beehives(bundle)), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY, lambda bundle: call_gossip_fairy(bundle)), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE_FAIRY_BIG, lambda bundle: can_use(bundle, Items.RG_SONG_OF_STORMS)), + (Locations.RC_KF_STORMS_GROTTO_FISH, lambda bundle: has_bottle(bundle)), + (Locations.RC_KF_STORMS_GROTTO_GRASS_1, lambda bundle: can_cut_shrubs(bundle)), + (Locations.RC_KF_STORMS_GROTTO_GRASS_2, lambda bundle: can_cut_shrubs(bundle)), + (Locations.RC_KF_STORMS_GROTTO_GRASS_3, lambda bundle: can_cut_shrubs(bundle)), + (Locations.RC_KF_STORMS_GROTTO_GRASS_4, lambda bundle: can_cut_shrubs(bundle)), + (Locations.RC_KF_STORMS_GROTTO_GOSSIP_STONE, lambda bundle: True_()), ]) # Exits - connect_regions(RandomizerRegion.RR_KF_STORMS_GROTTO, world, [ - (RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: True), + connect_regions(Regions.RR_KF_STORMS_GROTTO, world, [ + (Regions.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: True_()), ]) # Kokiri Forest # Events - add_events(RandomizerRegion.RR_KOKIRI_FOREST, world, [ - (EventLocations.RR_KOKIRI_FOREST_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns(bundle) or is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), - (EventLocations.RR_KOKIRI_FOREST_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child(bundle) and has(bundle, RandomizerGet.RG_SPEAK_KOKIRI) and can_use(bundle, RandomizerGet.RG_KOKIRI_SWORD) and can_use(bundle, RandomizerGet.RG_DEKU_SHIELD)), + add_events(Regions.RR_KOKIRI_FOREST, world, [ + (EventLocations.RR_KOKIRI_FOREST_LOGIC_FAIRY_ACCESS, Events.LOGIC_FAIRY_ACCESS, lambda bundle: call_gossip_fairy_except_suns(bundle) | is_child(bundle) & can_use(bundle, Items.RG_MAGIC_BEAN) & has_item(bundle, Items.RG_KOKIRI_FOREST_BEAN_SOUL) & can_use(bundle, Items.RG_SONG_OF_STORMS)), + (EventLocations.RR_KOKIRI_FOREST_LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD, lambda bundle: is_child(bundle) & has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_use(bundle, Items.RG_KOKIRI_SWORD) & can_use(bundle, Items.RG_DEKU_SHIELD)), ]) # Locations - add_locations(RandomizerRegion.RR_KOKIRI_FOREST, world, [ - (RandomizerCheck.RC_KF_BEAN_SPROUT_FAIRY_1, lambda bundle: is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), - (RandomizerCheck.RC_KF_BEAN_SPROUT_FAIRY_2, lambda bundle: is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), - (RandomizerCheck.RC_KF_BEAN_SPROUT_FAIRY_3, lambda bundle: is_child(bundle) and can_use(bundle, RandomizerGet.RG_MAGIC_BEAN) and has(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_use(bundle, RandomizerGet.RG_SONG_OF_STORMS)), - (RandomizerCheck.RC_KF_BRIDGE_RUPEE, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_BEHIND_MIDOS_RUPEE, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_SOUTH_GRASS_WEST_RUPEE, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_SOUTH_GRASS_EAST_RUPEE, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_NORTH_GRASS_WEST_RUPEE, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_NORTH_GRASS_EAST_RUPEE, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_SARIAS_ROOF_WEST_HEART, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_SARIAS_ROOF_EAST_HEART, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_SARIAS_ROOF_NORTH_HEART, lambda bundle: is_child(bundle)), - (RandomizerCheck.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), - (RandomizerCheck.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), - (RandomizerCheck.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), - (RandomizerCheck.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), - (RandomizerCheck.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), - (RandomizerCheck.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), - (RandomizerCheck.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) or can_use(bundle, RandomizerGet.RG_BOOMERANG))), - (RandomizerCheck.RC_KF_GS_KNOW_IT_ALL_HOUSE, lambda bundle: is_child(bundle) and can_kill(bundle, RandomizerEnemy.RE_GOLD_SKULLTULA, EnemyDistance.ED_CLOSE, True, 1, False, False) and can_get_night_time_gs(bundle)), - (RandomizerCheck.RC_KF_GS_BEAN_PATCH, lambda bundle: can_spawn_soil_skull(bundle, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) and can_kill(bundle, RandomizerEnemy.RE_GOLD_SKULLTULA, EnemyDistance.ED_CLOSE, True, 1, False, False)), - (RandomizerCheck.RC_KF_GS_HOUSE_OF_TWINS, lambda bundle: is_adult(bundle) and can_get_night_time_gs(bundle) and (can_get_drop(bundle, RandomizerEnemy.RE_GOLD_SKULLTULA, EnemyDistance.ED_BOOMERANG, False) or trick(bundle, RandomizerTrick.RT_KF_ADULT_GS) and can_use(bundle, RandomizerGet.RG_HOVER_BOOTS) and can_kill(bundle, RandomizerEnemy.RE_GOLD_SKULLTULA, EnemyDistance.ED_SHORT_JUMPSLASH, True, 1, False, False))), - (RandomizerCheck.RC_KF_CHILD_GRASS_1, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_2, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_3, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_4, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_5, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_6, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_7, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_8, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_9, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_10, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_11, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_CHILD_GRASS_12, lambda bundle: is_child(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_1, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_2, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_3, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_4, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_5, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_6, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_7, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_8, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_9, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_10, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_11, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_12, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_13, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_14, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_15, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_16, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_17, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_18, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_19, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), - (RandomizerCheck.RC_KF_ADULT_GRASS_20, lambda bundle: is_adult(bundle) and can_cut_shrubs(bundle)), + add_locations(Regions.RR_KOKIRI_FOREST, world, [ + (Locations.RC_KF_BEAN_SPROUT_FAIRY_1, lambda bundle: is_child(bundle) & can_use(bundle, Items.RG_MAGIC_BEAN) & has_item(bundle, Items.RG_KOKIRI_FOREST_BEAN_SOUL) & can_use(bundle, Items.RG_SONG_OF_STORMS)), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_2, lambda bundle: is_child(bundle) & can_use(bundle, Items.RG_MAGIC_BEAN) & has_item(bundle, Items.RG_KOKIRI_FOREST_BEAN_SOUL) & can_use(bundle, Items.RG_SONG_OF_STORMS)), + (Locations.RC_KF_BEAN_SPROUT_FAIRY_3, lambda bundle: is_child(bundle) & can_use(bundle, Items.RG_MAGIC_BEAN) & has_item(bundle, Items.RG_KOKIRI_FOREST_BEAN_SOUL) & can_use(bundle, Items.RG_SONG_OF_STORMS)), + (Locations.RC_KF_BRIDGE_RUPEE, lambda bundle: is_child(bundle)), + (Locations.RC_KF_BEHIND_MIDOS_RUPEE, lambda bundle: is_child(bundle)), + (Locations.RC_KF_SOUTH_GRASS_WEST_RUPEE, lambda bundle: is_child(bundle)), + (Locations.RC_KF_SOUTH_GRASS_EAST_RUPEE, lambda bundle: is_child(bundle)), + (Locations.RC_KF_NORTH_GRASS_WEST_RUPEE, lambda bundle: is_child(bundle)), + (Locations.RC_KF_NORTH_GRASS_EAST_RUPEE, lambda bundle: is_child(bundle)), + (Locations.RC_KF_SARIAS_ROOF_WEST_HEART, lambda bundle: is_child(bundle)), + (Locations.RC_KF_SARIAS_ROOF_EAST_HEART, lambda bundle: is_child(bundle)), + (Locations.RC_KF_SARIAS_ROOF_NORTH_HEART, lambda bundle: is_child(bundle)), + (Locations.RC_KF_BEAN_RUPEE_1, lambda bundle: is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_use(bundle, Items.RG_HOVER_BOOTS) | can_use(bundle, Items.RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_2, lambda bundle: is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_use(bundle, Items.RG_HOVER_BOOTS) | can_use(bundle, Items.RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_3, lambda bundle: is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_use(bundle, Items.RG_HOVER_BOOTS) | can_use(bundle, Items.RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_4, lambda bundle: is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_use(bundle, Items.RG_HOVER_BOOTS) | can_use(bundle, Items.RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_5, lambda bundle: is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_use(bundle, Items.RG_HOVER_BOOTS) | can_use(bundle, Items.RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RUPEE_6, lambda bundle: is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_use(bundle, Items.RG_HOVER_BOOTS) | can_use(bundle, Items.RG_BOOMERANG))), + (Locations.RC_KF_BEAN_RED_RUPEE, lambda bundle: is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_use(bundle, Items.RG_HOVER_BOOTS) | can_use(bundle, Items.RG_BOOMERANG))), + (Locations.RC_KF_GS_KNOW_IT_ALL_HOUSE, lambda bundle: is_child(bundle) & can_kill(bundle, Enemies.RE_GOLD_SKULLTULA, EnemyDistance.ED_CLOSE, True, 1, False, False) & can_get_night_time_gs(bundle)), + (Locations.RC_KF_GS_BEAN_PATCH, lambda bundle: can_spawn_soil_skull(bundle, Items.RG_KOKIRI_FOREST_BEAN_SOUL) & can_kill(bundle, Enemies.RE_GOLD_SKULLTULA, EnemyDistance.ED_CLOSE, True, 1, False, False)), + (Locations.RC_KF_GS_HOUSE_OF_TWINS, lambda bundle: is_adult(bundle) & can_get_night_time_gs(bundle) & (can_get_drop(bundle, Enemies.RE_GOLD_SKULLTULA, EnemyDistance.ED_BOOMERANG, False) | can_do_trick(bundle, Tricks.RT_KF_ADULT_GS) & can_use(bundle, Items.RG_HOVER_BOOTS) & can_kill(bundle, Enemies.RE_GOLD_SKULLTULA, EnemyDistance.ED_SHORT_JUMPSLASH, True, 1, False, False))), + (Locations.RC_KF_CHILD_GRASS_1, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_2, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_3, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_4, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_5, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_6, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_7, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_8, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_9, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_10, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_11, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_CHILD_GRASS_12, lambda bundle: is_child(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_1, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_2, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_3, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_4, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_5, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_6, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_7, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_8, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_9, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_10, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_11, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_12, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_13, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_14, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_15, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_16, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_17, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_18, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_19, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), + (Locations.RC_KF_ADULT_GRASS_20, lambda bundle: is_adult(bundle) & can_cut_shrubs(bundle)), ]) # Exits - connect_regions(RandomizerRegion.RR_KOKIRI_FOREST, world, [ - (RandomizerRegion.RR_KF_BOULDER_LOOP, lambda bundle: can_use(bundle, RandomizerGet.RG_CRAWL)), - (RandomizerRegion.RR_KF_LINKS_PORCH, lambda bundle: can_climb_ladder(bundle) if is_child(bundle) else has(bundle, RandomizerGet.RG_CLIMB) or can_use(bundle, RandomizerGet.RG_HOVER_BOOTS)), - (RandomizerRegion.RR_KF_MIDOS_HOUSE, lambda bundle: True), - (RandomizerRegion.RR_KF_SARIAS_HOUSE, lambda bundle: True), - (RandomizerRegion.RR_KF_HOUSE_OF_TWINS, lambda bundle: True), - (RandomizerRegion.RR_KF_KNOW_IT_ALL_HOUSE, lambda bundle: True), - (RandomizerRegion.RR_KF_KOKIRI_SHOP, lambda bundle: True), - (RandomizerRegion.RR_KF_OUTSIDE_DEKU_TREE, lambda bundle: flag(bundle, LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD) or bundle[2].options.RSK_FOREST == RO_CLOSED_FOREST_OFF or is_adult(bundle) and (can_pass(bundle, RandomizerEnemy.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) or flag(bundle, LOGIC_FOREST_TEMPLE_CLEAR))), - (RandomizerRegion.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: has(bundle, RandomizerGet.RG_CLIMB) or can_use(bundle, RandomizerGet.RG_HOOKSHOT) or is_adult(bundle) and (can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL) or trick(bundle, RandomizerTrick.RT_UNINTUITIVE_JUMPS))), - (RandomizerRegion.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult(bundle) and can_plant_bean(bundle, RandomizerRegion.RR_KOKIRI_FOREST, RandomizerGet.RG_KOKIRI_FOREST_BEAN_SOUL)), - (RandomizerRegion.RR_LW_BRIDGE_FROM_FOREST, lambda bundle: is_adult(bundle) or bundle[2].options.RSK_FOREST != RO_CLOSED_FOREST_ON or flag(bundle, LOGIC_DEKU_TREE_CLEAR)), + connect_regions(Regions.RR_KOKIRI_FOREST, world, [ + (Regions.RR_KF_BOULDER_LOOP, lambda bundle: can_use(bundle, Items.RG_CRAWL)), + (Regions.RR_KF_LINKS_PORCH, lambda bundle: can_climb_ladder(bundle) if is_child(bundle) else has_item(bundle, Items.RG_CLIMB) | can_use(bundle, Items.RG_HOVER_BOOTS)), + (Regions.RR_KF_MIDOS_HOUSE, lambda bundle: True_()), + (Regions.RR_KF_SARIAS_HOUSE, lambda bundle: True_()), + (Regions.RR_KF_HOUSE_OF_TWINS, lambda bundle: True_()), + (Regions.RR_KF_KNOW_IT_ALL_HOUSE, lambda bundle: True_()), + (Regions.RR_KF_KOKIRI_SHOP, lambda bundle: True_()), + (Regions.RR_KF_OUTSIDE_DEKU_TREE, lambda bundle: has_item(bundle, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD) | (OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_OFF)) | is_adult(bundle) & (can_pass(bundle, Enemies.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) | has_item(bundle, Events.LOGIC_FOREST_TEMPLE_CLEAR))), + (Regions.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: has_item(bundle, Items.RG_CLIMB) | can_use(bundle, Items.RG_HOOKSHOT) | is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_do_trick(bundle, Tricks.RT_UNINTUITIVE_JUMPS))), + (Regions.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult(bundle) & can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL)), + (Regions.RR_LW_BRIDGE_FROM_FOREST, lambda bundle: is_adult(bundle) | (OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_ON, "ne")) | has_item(bundle, Events.LOGIC_DEKU_TREE_CLEAR)), ]) # Minuet of Forest Warp # Exits - connect_regions(RandomizerRegion.RR_MINUET_OF_FOREST_WARP, world, [ - (RandomizerRegion.RR_SACRED_FOREST_MEADOW, lambda bundle: True), + connect_regions(Regions.RR_MINUET_OF_FOREST_WARP, world, [ + (Regions.RR_SACRED_FOREST_MEADOW, lambda bundle: True_()), ]) # Nocturne of Shadow Warp # Exits - connect_regions(RandomizerRegion.RR_NOCTURNE_OF_SHADOW_WARP, world, [ - (RandomizerRegion.RR_GRAVEYARD_WARP_PAD_REGION, lambda bundle: True), + connect_regions(Regions.RR_NOCTURNE_OF_SHADOW_WARP, world, [ + (Regions.RR_GRAVEYARD_WARP_PAD_REGION, lambda bundle: True_()), ]) # Prelude of Light Warp # Exits - connect_regions(RandomizerRegion.RR_PRELUDE_OF_LIGHT_WARP, world, [ - (RandomizerRegion.RR_TEMPLE_OF_TIME, lambda bundle: True), + connect_regions(Regions.RR_PRELUDE_OF_LIGHT_WARP, world, [ + (Regions.RR_TEMPLE_OF_TIME, lambda bundle: True_()), ]) # Requiem of Spirit Warp # Exits - connect_regions(RandomizerRegion.RR_REQUIEM_OF_SPIRIT_WARP, world, [ - (RandomizerRegion.RR_DESERT_COLOSSUS, lambda bundle: True), + connect_regions(Regions.RR_REQUIEM_OF_SPIRIT_WARP, world, [ + (Regions.RR_DESERT_COLOSSUS, lambda bundle: True_()), ]) # Root # Events - add_events(RandomizerRegion.RR_ROOT, world, [ - (EventLocations.RR_ROOT_LOGIC_KAKARIKO_GATE_OPEN, Events.LOGIC_KAKARIKO_GATE_OPEN, lambda bundle: bundle[2].options.RSK_KAK_GATE == RO_KAK_GATE_OPEN), - (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, Events.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE), - (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, Events.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE or bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FAST), - (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, Events.LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE or bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FAST), - (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, Events.LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE or bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FAST), - (EventLocations.RR_ROOT_LOGIC_TH_RESCUED_ALL_CARPENTERS, Events.LOGIC_TH_RESCUED_ALL_CARPENTERS, lambda bundle: bundle[2].options.RSK_GERUDO_FORTRESS == RO_GF_CARPENTERS_FREE), - (EventLocations.RR_ROOT_LOGIC_FREED_EPONA, Events.LOGIC_FREED_EPONA, lambda bundle: bundle[2].options.RSK_SKIP_EPONA_RACE == RO_GENERIC_YES), + add_events(Regions.RR_ROOT, world, [ + (EventLocations.RR_ROOT_LOGIC_KAKARIKO_GATE_OPEN, Events.LOGIC_KAKARIKO_GATE_OPEN, lambda bundle: OptionFilter(RSK_KAK_GATE, RandomizerSettingKey.RO_KAK_GATE_OPEN)), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, Events.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, lambda bundle: OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, Events.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, lambda bundle: (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)) | (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FAST))), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, Events.LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, lambda bundle: (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)) | (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FAST))), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, Events.LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, lambda bundle: (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)) | (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FAST))), + (EventLocations.RR_ROOT_LOGIC_TH_RESCUED_ALL_CARPENTERS, Events.LOGIC_TH_RESCUED_ALL_CARPENTERS, lambda bundle: OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)), + (EventLocations.RR_ROOT_LOGIC_FREED_EPONA, Events.LOGIC_FREED_EPONA, lambda bundle: OptionFilter(RSK_SKIP_EPONA_RACE, RandomizerSettingKey.RO_GENERIC_YES)), ]) # Locations - add_locations(RandomizerRegion.RR_ROOT, world, [ - (RandomizerCheck.RC_LINKS_POCKET, lambda bundle: True), - (RandomizerCheck.RC_TRIFORCE_COMPLETED, lambda bundle: collected_triforce_pieces(bundle) >= required_triforce_pieces(bundle)), - (RandomizerCheck.RC_SARIA_SONG_HINT, lambda bundle: can_use(bundle, RandomizerGet.RG_SARIAS_SONG)), - (RandomizerCheck.RC_SONG_FROM_IMPA, lambda bundle: bundle[2].options.RSK_SKIP_CHILD_ZELDA == RO_GENERIC_YES), - (RandomizerCheck.RC_HC_MALON_EGG, lambda bundle: bundle[2].options.RSK_SKIP_CHILD_ZELDA == RO_GENERIC_YES), - (RandomizerCheck.RC_HC_ZELDAS_LETTER, lambda bundle: bundle[2].options.RSK_SKIP_CHILD_ZELDA == RO_GENERIC_YES), - (RandomizerCheck.RC_TOT_MASTER_SWORD, lambda bundle: bundle[2].options.RSK_SELECTED_STARTING_AGE == RO_AGE_ADULT), + add_locations(Regions.RR_ROOT, world, [ + (Locations.RC_LINKS_POCKET, lambda bundle: True_()), + (Locations.RC_TRIFORCE_COMPLETED, lambda bundle: CanWinTriforceHunt()), + (Locations.RC_SARIA_SONG_HINT, lambda bundle: can_use(bundle, Items.RG_SARIAS_SONG)), + (Locations.RC_SONG_FROM_IMPA, lambda bundle: OptionFilter(RSK_SKIP_CHILD_ZELDA, RandomizerSettingKey.RO_GENERIC_YES)), + (Locations.RC_HC_MALON_EGG, lambda bundle: OptionFilter(RSK_SKIP_CHILD_ZELDA, RandomizerSettingKey.RO_GENERIC_YES)), + (Locations.RC_HC_ZELDAS_LETTER, lambda bundle: OptionFilter(RSK_SKIP_CHILD_ZELDA, RandomizerSettingKey.RO_GENERIC_YES)), + (Locations.RC_TOT_MASTER_SWORD, lambda bundle: OptionFilter(RSK_SELECTED_STARTING_AGE, RandomizerSettingKey.RO_AGE_ADULT)), ]) # Exits - connect_regions(RandomizerRegion.RR_ROOT, world, [ - (RandomizerRegion.RR_ROOT_EXITS, lambda bundle: True), + connect_regions(Regions.RR_ROOT, world, [ + (Regions.RR_ROOT_EXITS, lambda bundle: True_()), ]) # Root Exits # Exits - connect_regions(RandomizerRegion.RR_ROOT_EXITS, world, [ - (RandomizerRegion.RR_CHILD_SPAWN, lambda bundle: is_child(bundle)), - (RandomizerRegion.RR_ADULT_SPAWN, lambda bundle: is_adult(bundle)), - (RandomizerRegion.RR_MINUET_OF_FOREST_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_MINUET_OF_FOREST)), - (RandomizerRegion.RR_BOLERO_OF_FIRE_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_BOLERO_OF_FIRE)), - (RandomizerRegion.RR_SERENADE_OF_WATER_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_SERENADE_OF_WATER)), - (RandomizerRegion.RR_NOCTURNE_OF_SHADOW_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_NOCTURNE_OF_SHADOW)), - (RandomizerRegion.RR_REQUIEM_OF_SPIRIT_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_REQUIEM_OF_SPIRIT)), - (RandomizerRegion.RR_PRELUDE_OF_LIGHT_WARP, lambda bundle: can_use(bundle, RandomizerGet.RG_PRELUDE_OF_LIGHT)), + connect_regions(Regions.RR_ROOT_EXITS, world, [ + (Regions.RR_CHILD_SPAWN, lambda bundle: is_child(bundle)), + (Regions.RR_ADULT_SPAWN, lambda bundle: is_adult(bundle)), + (Regions.RR_MINUET_OF_FOREST_WARP, lambda bundle: can_use(bundle, Items.RG_MINUET_OF_FOREST)), + (Regions.RR_BOLERO_OF_FIRE_WARP, lambda bundle: can_use(bundle, Items.RG_BOLERO_OF_FIRE)), + (Regions.RR_SERENADE_OF_WATER_WARP, lambda bundle: can_use(bundle, Items.RG_SERENADE_OF_WATER)), + (Regions.RR_NOCTURNE_OF_SHADOW_WARP, lambda bundle: can_use(bundle, Items.RG_NOCTURNE_OF_SHADOW)), + (Regions.RR_REQUIEM_OF_SPIRIT_WARP, lambda bundle: can_use(bundle, Items.RG_REQUIEM_OF_SPIRIT)), + (Regions.RR_PRELUDE_OF_LIGHT_WARP, lambda bundle: can_use(bundle, Items.RG_PRELUDE_OF_LIGHT)), ]) # Serenade of Water Warp # Exits - connect_regions(RandomizerRegion.RR_SERENADE_OF_WATER_WARP, world, [ - (RandomizerRegion.RR_LAKE_HYLIA, lambda bundle: True), + connect_regions(Regions.RR_SERENADE_OF_WATER_WARP, world, [ + (Regions.RR_LAKE_HYLIA, lambda bundle: True_()), ]) diff --git a/transpilers/soh_ap/include/soh_ap.h b/transpilers/soh_ap/include/soh_ap.h index d00847e..7a54b6e 100644 --- a/transpilers/soh_ap/include/soh_ap.h +++ b/transpilers/soh_ap/include/soh_ap.h @@ -30,6 +30,10 @@ class SohApTranspiler { std::string GenerateExpression(const rls::ast::AnyAgeBlock& node) const; std::string GenerateExpression(const rls::ast::MatchExpr& node) const; std::string GenerateExpression(const rls::ast::Expr::Variant& node) const; + + // Helper to convert setting(KEY) == VALUE expressions to OptionFilter(...) form for RuleBuilder. + // Returns empty string if not a setting comparison; caller should use fallback. + std::string TryGenerateOptionFilter(const rls::ast::BinaryExpr& node) const; const rls::ast::Project& project; }; diff --git a/transpilers/soh_ap/src/generate_enums.cpp b/transpilers/soh_ap/src/generate_enums.cpp index 02bc8c7..879022d 100644 --- a/transpilers/soh_ap/src/generate_enums.cpp +++ b/transpilers/soh_ap/src/generate_enums.cpp @@ -3,9 +3,20 @@ #include #include +# include namespace rls::transpilers::soh_ap { +void InsertToSet(const std::vector& sections, rls::ast::SectionKind sectionKind, std::set& emittedValues) { + for (const auto& section : sections) { + if (section.kind == sectionKind) { + for (const auto& entry : section.entries) { + emittedValues.insert(entry.name.text); + } + } + } +} + void WriteEventLocations( const SohApTranspiler& transpiler, std::ostream& source, @@ -17,26 +28,6 @@ void WriteEventLocations( }); } -void WriteEvents( - const SohApTranspiler& transpiler, - std::ostream& source, - const std::vector& sections) -{ - WriteEntries(sections, rls::ast::SectionKind::Events, [&](const rls::ast::Entry& entry){ - source << " " << entry.name << " = auto()\n"; - }); -} - -void WriteLocationsEnum( - const SohApTranspiler& transpiler, - std::ostream& source, - const std::vector& sections) -{ - WriteEntries(sections, rls::ast::SectionKind::Locations, [&](const rls::ast::Entry& entry){ - source << " " << entry.name << " = auto()\n"; - }); -} - void SohApTranspiler::GenerateEnumsSource(rls::OutputWriter& out) const { auto& source = out.open("enums.gen.py"); @@ -49,10 +40,26 @@ void SohApTranspiler::GenerateEnumsSource(rls::OutputWriter& out) const { std::ostringstream regions; std::ostringstream locations; - eventLocations << "class EventLocations(StrEnum):\n"; - events << "class Events(StrEnum):\n"; - regions << "class RandomizerRegions(StrEnum):\n";; - locations << "class RandomizerChecks(StrEnum):\n";; + // Sets to track which enum values have already been emitted, to avoid duplicates across regions and extended regions + std::set emittedEvents; + std::set emittedLocations; + + eventLocations << "class EventLocations(StrEnum):\n" + << " @staticmethod\n" + << " def _generate_next_value_(name, start, count, last_values):\n" + << " new_name = name.replace(\"RR_\", \"\").replace(\"_\", \" \").title()\n" + << " return new_name\n"; + events << "class Events(StrEnum):\n" + << " @staticmethod\n" + << " def _generate_next_value_(name, start, count, last_values):\n" + << " new_name = name.replace(\"LOGIC_\", \"\").replace(\"_\", \" \").title()\n" + << " return new_name\n"; + regions << "class Regions(StrEnum):\n" ; + locations << "class Locations(StrEnum):\n" + << " @staticmethod\n" + << " def _generate_next_value_(name, start, count, last_values):\n" + << " new_name = name.replace(\"RC_\", \"\").replace(\"_\", \" \").title()\n" + << " return new_name\n"; for (const auto& [regionName, region] : project.RegionDecls) { // Do Regions while here @@ -72,18 +79,26 @@ void SohApTranspiler::GenerateEnumsSource(rls::OutputWriter& out) const { } // Events - WriteEvents(*this, events, region->body.sections); + InsertToSet(region->body.sections, rls::ast::SectionKind::Events, emittedEvents); for (const auto* extendRegion : extendRegionDecls) { - WriteEvents(*this, events, extendRegion->sections); + InsertToSet(extendRegion->sections, rls::ast::SectionKind::Events, emittedEvents); } // Locations - WriteLocationsEnum(*this, locations, region->body.sections); + InsertToSet(region->body.sections, rls::ast::SectionKind::Locations, emittedLocations); for (const auto* extendRegion : extendRegionDecls) { - WriteLocationsEnum(*this, locations, extendRegion->sections); + InsertToSet(extendRegion->sections, rls::ast::SectionKind::Locations, emittedLocations); } } + // Add distinct events and locations from all regions to the main Events and Locations enums + for (const auto& event : emittedEvents) { + events << " " << event << " = auto()\n"; + } + for (const auto& location : emittedLocations) { + locations << " " << location << " = auto()\n"; + } + // Output to source source << "\n" << eventLocations.str(); source << "\n" << events.str(); diff --git a/transpilers/soh_ap/src/generate_expression.cpp b/transpilers/soh_ap/src/generate_expression.cpp index df696a7..cad0084 100644 --- a/transpilers/soh_ap/src/generate_expression.cpp +++ b/transpilers/soh_ap/src/generate_expression.cpp @@ -7,8 +7,45 @@ namespace rls::transpilers::soh_ap { +// Try to generate an OptionFilter expression for setting comparisons. +// Returns empty string if not a setting comparison; caller should use standard binary expression. +std::string SohApTranspiler::TryGenerateOptionFilter(const rls::ast::BinaryExpr& node) const { + // Only handle == and != comparisons + if (node.op != rls::ast::BinaryOp::Eq && node.op != rls::ast::BinaryOp::NotEq) { + return ""; + } + + // Check if left side is a setting() call and right is an identifier (the enum value) + auto* leftCall = std::get_if(&node.left->node); + auto* rightId = std::get_if(&node.right->node); + + if (!leftCall || !rightId || leftCall->callee.text != "setting") { + return ""; + } + + // Get the setting key argument (RSK_*) + auto resolvedPtr = project.getResolvedCallArgs(leftCall); + if (!resolvedPtr || resolvedPtr->empty()) { + return ""; + } + + auto* settingKeyId = std::get_if(&resolvedPtr->front()->node); + if (!settingKeyId) { + return ""; + } + + std::ostringstream result; + result << "OptionFilter(" << settingKeyId->name.text << ", " << GenerateExpression(*rightId); + if (node.op == rls::ast::BinaryOp::NotEq) { + result << ", \"ne\""; + } + result << ")"; + + return result.str(); +} + std::string SohApTranspiler::GenerateExpression(const rls::ast::BoolLiteral& node) const { - return node.value ? "True" : "False"; + return node.value ? "True_()" : "False_()"; } std::string SohApTranspiler::GenerateExpression(const rls::ast::IntLiteral& node) const { @@ -22,24 +59,14 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::Identifier& node return node.name.text; } switch (type.value()) { - case rls::ast::Type::Item: return "RandomizerGet." + node.name.text; - case rls::ast::Type::Enemy: return "RandomizerEnemy." + node.name.text; + case rls::ast::Type::Item: return "Items." + node.name.text; + case rls::ast::Type::Enemy: return "Enemies." + node.name.text; case rls::ast::Type::Distance: return "EnemyDistance." + node.name.text; - case rls::ast::Type::Trick: return "RandomizerTrick." + node.name.text; - /* - AP has a couple ways we could use for comparing options. Ideally we use OptionFilter as that is rule builder compatible - `OptionFilter(OptionClassName, value, "operator")` -> `OptionFilter(SkipChildZelda, True)` - - If we aren't using rule builder we could go the classic comparison - `world.options.settingsJsonName == value` -> `world.options.skip_child_zelda == True` // Could also just be used without the True comparison. `world.options.skip_child_zelda` converts to a bool. - - In AP since each option has its own class, this is going to be tricky to do right. We may have to build a mapping between what RLS Option and AP settings classes/settingsJsonNames. - - For the values, we could probabaly make an enum to house them and use the enums in the options classes - */ - //case rls::ast::Type::Setting: return "RandomizerSettingKey::" + node.name.text; - case rls::ast::Type::Region: return "RandomizerRegion." + node.name.text; - case rls::ast::Type::Check: return "RandomizerCheck." + node.name.text; + case rls::ast::Type::Trick: return "Tricks." + node.name.text; + case rls::ast::Type::Logic: return "Events." + node.name.text; + case rls::ast::Type::Setting: return "RandomizerSettingKey." + node.name.text; + case rls::ast::Type::Region: return "Regions." + node.name.text; + case rls::ast::Type::Check: return "Locations." + node.name.text; case rls::ast::Type::Trial: return "TrialKey." + node.name.text; default: return node.name.text; } @@ -51,9 +78,15 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::Identifier& node } } -// Returns the Python operator precedence for an expression node. -// Lower values bind tighter. Non-compound nodes return 0 (tightest). -// Precedence is from https://docs.python.org/3/reference/expressions.html#operator-precedence +// Returns the RuleBuilder operator precedence for an expression node. +// Precedence adjusted for RuleBuilder bitwise operators (&, |, ~) as used in Archipelago: +// - Arithmetic (*, /): 6 +// - Arithmetic (+, -): 7 +// - Bitwise AND (&): 9 +// - Bitwise OR (|): 11 +// - Comparisons (==, !=, <, etc.): 12 +// - Ternary: 16 +// Unary bitwise NOT (~) and function calls have precedence 3 (very tight). int SohApTranspiler::GetPythonPrecedence(const rls::ast::ExprPtr& expr) const { if (auto* bin = std::get_if(&expr->node)) { switch (bin->op) { @@ -63,6 +96,8 @@ int SohApTranspiler::GetPythonPrecedence(const rls::ast::ExprPtr& expr) const { case rls::ast::BinaryOp::Add: case rls::ast::BinaryOp::Sub: return 7; + case rls::ast::BinaryOp::And: + return 9; // Bitwise AND (&) case rls::ast::BinaryOp::Lt: case rls::ast::BinaryOp::LtEq: case rls::ast::BinaryOp::Gt: @@ -70,10 +105,8 @@ int SohApTranspiler::GetPythonPrecedence(const rls::ast::ExprPtr& expr) const { case rls::ast::BinaryOp::Eq: case rls::ast::BinaryOp::NotEq: return 12; - case rls::ast::BinaryOp::And: - return 14; case rls::ast::BinaryOp::Or: - return 15; + return 11; // Bitwise OR (|) default: return 0; } } @@ -100,19 +133,62 @@ std::string SohApTranspiler::GenerateChildExpression( std::string SohApTranspiler::GenerateExpression(const rls::ast::UnaryExpr& node) const { switch (node.op) { - case rls::ast::UnaryOp::Not: - return "not " + GenerateChildExpression(node.operand, 3); + case rls::ast::UnaryOp::Not: { + // RuleBuilder doesn't support negation of rules. Negation on settings is handled + // via OptionFilter with a false value for direct setting() calls. + if (auto* call = std::get_if(&node.operand->node); + call && call->callee.text == "setting") { + auto resolvedPtr = project.getResolvedCallArgs(call); + if (resolvedPtr && !resolvedPtr->empty()) { + auto* settingKeyId = std::get_if(&resolvedPtr->front()->node); + if (settingKeyId) { + return std::string("OptionFilter(") + settingKeyId->name.text + ", False)"; + } + } + } + return GenerateExpression(node.operand); + } default: return ""; } } std::string SohApTranspiler::GenerateExpression(const rls::ast::BinaryExpr& node) const { + // Try to generate OptionFilter for setting comparisons + std::string optionFilter = TryGenerateOptionFilter(node); + if (!optionFilter.empty()) { + return optionFilter; + } + + // Special case: check_price(...) <= wallet_capacity(...) should only output check_price(...) + if (node.op == rls::ast::BinaryOp::LtEq) { + auto* leftCall = std::get_if(&node.left->node); + auto* rightCall = std::get_if(&node.right->node); + + if (leftCall && rightCall && + leftCall->callee.text == "check_price" && + rightCall->callee.text == "wallet_capacity") { + return GenerateExpression(node.left); + } + } + + // Special case: collected_triforce_pieces(...) >= required_triforce_pieces(...) should output CanWinTriforceHunt() + if (node.op == rls::ast::BinaryOp::GtEq) { + auto* leftCall = std::get_if(&node.left->node); + auto* rightCall = std::get_if(&node.right->node); + + if (leftCall && rightCall && + leftCall->callee.text == "collected_triforce_pieces" && + rightCall->callee.text == "required_triforce_pieces") { + return "CanWinTriforceHunt()"; + } + } + switch (node.op) { case rls::ast::BinaryOp::And: - return GenerateChildExpression(node.left, 14) + " and " + GenerateChildExpression(node.right, 14, true); + return GenerateChildExpression(node.left, 9) + " & " + GenerateChildExpression(node.right, 9, true); case rls::ast::BinaryOp::Or: - return GenerateChildExpression(node.left, 15) + " or " + GenerateChildExpression(node.right, 15, true); + return GenerateChildExpression(node.left, 11) + " | " + GenerateChildExpression(node.right, 11, true); case rls::ast::BinaryOp::Eq: return GenerateChildExpression(node.left, 12) + " == " + GenerateChildExpression(node.right, 12, true); case rls::ast::BinaryOp::NotEq: @@ -158,7 +234,15 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) // Handle settings differently if (node.callee.text == "setting") { - oss << "bundle[2].options." << GenerateExpression(resolved[0]->node); + if (auto* id = std::get_if(&resolved[0]->node)) { + oss << "OptionFilter(" << id->name.text << ", True)"; + } + } else if (node.callee.text == "has" || node.callee.text == "flag") { + oss << "has_item(bundle, " << GenerateExpression(resolved[0]->node) << ")"; + } else if (node.callee.text == "trick") { + oss << "can_do_trick(bundle, " << GenerateExpression(resolved[0]->node) << ")"; + } else if (node.callee.text == "check_price") { + oss << "can_afford_slot(" << GenerateExpression(resolved[0]->node) << ")"; } else { oss << node.callee.text << "(bundle"; for (size_t i = 0; i < resolved.size(); ++i) { @@ -167,6 +251,15 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) // } oss << ", "; + // Special case functions parameters use Python's True/False instead of True_()/False_(). + if (auto* id = std::get_if(&resolved[i]->node)) { + if (id->value) { + oss << "True"; + } else { + oss << "False"; + } + continue; + } oss << GenerateExpression(resolved[i]->node); } oss << ")"; @@ -182,7 +275,7 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::SharedBlock& nod const auto& firstBranch = node.branches[0]; oss << "spirit_shared(" << firstBranch.region->text << ", " << "(lambda: " << GenerateExpression(firstBranch.condition) << "), " - << (node.anyAge ? "true" : "false"); + << (node.anyAge ? "True" : "False"); for (int i = 1; i < node.branches.size(); i++) { oss << ", " << node.branches[i].region->text << ", " diff --git a/transpilers/soh_ap/src/generate_regions.cpp b/transpilers/soh_ap/src/generate_regions.cpp index 78069f6..96fa22f 100644 --- a/transpilers/soh_ap/src/generate_regions.cpp +++ b/transpilers/soh_ap/src/generate_regions.cpp @@ -34,7 +34,7 @@ void WriteLocations( const std::vector& sections) { WriteEntries(sections, rls::ast::SectionKind::Locations, [&](const rls::ast::Entry& entry){ - source << " (RandomizerCheck." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; + source << " (Locations." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; }); } @@ -44,7 +44,7 @@ void WriteExits( const std::vector& sections) { WriteEntries(sections, rls::ast::SectionKind::Exits, [&](const rls::ast::Entry& entry){ - source << " (RandomizerRegion." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; + source << " (Regions." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; }); } @@ -52,7 +52,7 @@ void SohApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { auto& source = out.open("regions.gen.py"); source << "# Generated by RLS soh_ap transpiler\n" - << "from .functions.gen.py import *\n" + << "from .LogicHelpers import *\n" << "\n" << "if TYPE_CHECKING:\n" << " from ... import SohWorld\n" @@ -71,7 +71,7 @@ void SohApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { extendRegionDecls = extendRegionIt->second; } - std::string creationString = "RandomizerRegion." + region->key.text + ", world, [\n"; + std::string creationString = "Regions." + region->key.text + ", world, [\n"; source << " # " << region->body.name << "\n"; events diff --git a/transpilers/soh_ap/src/soh_ap.cpp b/transpilers/soh_ap/src/soh_ap.cpp index 8d97722..1883435 100644 --- a/transpilers/soh_ap/src/soh_ap.cpp +++ b/transpilers/soh_ap/src/soh_ap.cpp @@ -6,7 +6,7 @@ SohApTranspiler::SohApTranspiler(const rls::ast::Project& project) : project(project) {} void SohApTranspiler::Transpile(rls::OutputWriter& out) const { - GenerateFunctionDefinitionsSource(out); + //GenerateFunctionDefinitionsSource(out); GenerateRegionsSource(out); GenerateEnumsSource(out); } From 073b792510380da3784a73b46b2a1b9d84ba5536 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Sun, 14 Jun 2026 17:39:35 -0400 Subject: [PATCH 14/22] Add handling for can_afford_slot location --- examples/soh_ap/regions.gen.py | 16 ++++++++-------- transpilers/soh_ap/include/soh_ap.h | 2 ++ transpilers/soh_ap/src/generate_expression.cpp | 13 ++++++++++++- transpilers/soh_ap/src/generate_regions.cpp | 2 ++ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py index f5f6523..bfd02d0 100644 --- a/examples/soh_ap/regions.gen.py +++ b/examples/soh_ap/regions.gen.py @@ -63,14 +63,14 @@ def set_region_rules(world: "SohWorld") -> None: # KF Kokiri Shop # Locations add_locations(Regions.RR_KF_KOKIRI_SHOP, world, [ - (Locations.RC_KF_SHOP_ITEM_1, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), - (Locations.RC_KF_SHOP_ITEM_2, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), - (Locations.RC_KF_SHOP_ITEM_3, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), - (Locations.RC_KF_SHOP_ITEM_4, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), - (Locations.RC_KF_SHOP_ITEM_5, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), - (Locations.RC_KF_SHOP_ITEM_6, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), - (Locations.RC_KF_SHOP_ITEM_7, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), - (Locations.RC_KF_SHOP_ITEM_8, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_UNKNOWN_CHECK))), + (Locations.RC_KF_SHOP_ITEM_1, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_1))), + (Locations.RC_KF_SHOP_ITEM_2, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_2))), + (Locations.RC_KF_SHOP_ITEM_3, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_3))), + (Locations.RC_KF_SHOP_ITEM_4, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_4))), + (Locations.RC_KF_SHOP_ITEM_5, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_5))), + (Locations.RC_KF_SHOP_ITEM_6, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_6))), + (Locations.RC_KF_SHOP_ITEM_7, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_7))), + (Locations.RC_KF_SHOP_ITEM_8, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_8))), ]) # Exits connect_regions(Regions.RR_KF_KOKIRI_SHOP, world, [ diff --git a/transpilers/soh_ap/include/soh_ap.h b/transpilers/soh_ap/include/soh_ap.h index 7a54b6e..1a2b75f 100644 --- a/transpilers/soh_ap/include/soh_ap.h +++ b/transpilers/soh_ap/include/soh_ap.h @@ -15,6 +15,7 @@ class SohApTranspiler { void GenerateRegionsSource(rls::OutputWriter& out) const; void GenerateEnumsSource(rls::OutputWriter& out) const; std::string GenerateExpression(const rls::ast::ExprPtr& expr) const; + void SetCurrentLocation(std::optional location) const; private: int GetPythonPrecedence(const rls::ast::ExprPtr& expr) const; @@ -36,6 +37,7 @@ class SohApTranspiler { std::string TryGenerateOptionFilter(const rls::ast::BinaryExpr& node) const; const rls::ast::Project& project; + mutable std::optional currentLocationName; }; void Transpile(const rls::ast::Project& project, rls::OutputWriter& out); diff --git a/transpilers/soh_ap/src/generate_expression.cpp b/transpilers/soh_ap/src/generate_expression.cpp index cad0084..b910a90 100644 --- a/transpilers/soh_ap/src/generate_expression.cpp +++ b/transpilers/soh_ap/src/generate_expression.cpp @@ -242,7 +242,14 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) } else if (node.callee.text == "trick") { oss << "can_do_trick(bundle, " << GenerateExpression(resolved[0]->node) << ")"; } else if (node.callee.text == "check_price") { - oss << "can_afford_slot(" << GenerateExpression(resolved[0]->node) << ")"; + // Special case: check_price(...) should output can_afford_slot(...) + // If the argument is an RC_UNKNOWN_CHECK identifier, use the current location from context + if (auto* id = std::get_if(&resolved[0]->node); + id && id->name.text == "RC_UNKNOWN_CHECK" && currentLocationName.has_value()) { + oss << "can_afford_slot(Locations." << currentLocationName.value() << ")"; + } else { + oss << "can_afford_slot(" << GenerateExpression(resolved[0]->node) << ")"; + } } else { oss << node.callee.text << "(bundle"; for (size_t i = 0; i < resolved.size(); ++i) { @@ -335,4 +342,8 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::ExprPtr& expr) c return GenerateExpression(expr->node); } +void SohApTranspiler::SetCurrentLocation(std::optional location) const { + currentLocationName = std::move(location); +} + } // rls::transpilers::soh_ap diff --git a/transpilers/soh_ap/src/generate_regions.cpp b/transpilers/soh_ap/src/generate_regions.cpp index 96fa22f..ec72e55 100644 --- a/transpilers/soh_ap/src/generate_regions.cpp +++ b/transpilers/soh_ap/src/generate_regions.cpp @@ -34,7 +34,9 @@ void WriteLocations( const std::vector& sections) { WriteEntries(sections, rls::ast::SectionKind::Locations, [&](const rls::ast::Entry& entry){ + transpiler.SetCurrentLocation(std::optional(entry.name.text)); source << " (Locations." << entry.name << ", lambda bundle: " << transpiler.GenerateExpression(entry.condition) << "),\n"; + transpiler.SetCurrentLocation(std::nullopt); }); } From c8eed07477492ce3a01243200df622ccdae2f530 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Wed, 17 Jun 2026 22:14:42 -0400 Subject: [PATCH 15/22] Overhaul OptionFilter creation Add OptionFilter unit tests --- examples/soh_ap/regions.gen.py | 30 ++-- transpilers/soh_ap/CMakeLists.txt | 2 +- transpilers/soh_ap/include/soh_ap.h | 10 ++ .../soh_ap/src/generate_expression.cpp | 66 ++++++--- transpilers/soh_ap/tests/ap_tests.cpp | 36 ----- transpilers/soh_ap/tests/helpers.h | 78 +++++++++++ .../soh_ap/tests/option_filter_tests.cpp | 130 ++++++++++++++++++ 7 files changed, 278 insertions(+), 74 deletions(-) delete mode 100644 transpilers/soh_ap/tests/ap_tests.cpp create mode 100644 transpilers/soh_ap/tests/helpers.h create mode 100644 transpilers/soh_ap/tests/option_filter_tests.cpp diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py index bfd02d0..9c96ef8 100644 --- a/examples/soh_ap/regions.gen.py +++ b/examples/soh_ap/regions.gen.py @@ -127,8 +127,8 @@ def set_region_rules(world: "SohWorld") -> None: ]) # Exits connect_regions(Regions.RR_KF_OUTSIDE_DEKU_TREE, world, [ - (Regions.RR_DEKU_TREE_ENTRYWAY, lambda bundle: is_child(bundle) | (OptionFilter(RSK_SHUFFLE_DUNGEON_ENTRANCES, RandomizerSettingKey.RO_DUNGEON_ENTRANCE_SHUFFLE_OFF, "ne")) & ((OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_OFF)) | has_item(bundle, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), - (Regions.RR_KOKIRI_FOREST, lambda bundle: is_adult(bundle) & (can_pass(bundle, Enemies.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) | has_item(bundle, Events.LOGIC_DEKU_TREE_CLEAR)) | (OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_OFF)) | has_item(bundle, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), + (Regions.RR_DEKU_TREE_ENTRYWAY, lambda bundle: is_child(bundle) | True_(options=[OptionFilter(RSK_SHUFFLE_DUNGEON_ENTRANCES, RandomizerSettingKey.RO_DUNGEON_ENTRANCE_SHUFFLE_OFF, "ne")]) & (True_(options=[OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_OFF)]) | has_item(bundle, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD))), + (Regions.RR_KOKIRI_FOREST, lambda bundle: is_adult(bundle) & (can_pass(bundle, Enemies.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) | has_item(bundle, Events.LOGIC_DEKU_TREE_CLEAR)) | True_(options=[OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_OFF)]) | has_item(bundle, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD)), ]) # KF Outside Lost Woods @@ -280,10 +280,10 @@ def set_region_rules(world: "SohWorld") -> None: (Regions.RR_KF_HOUSE_OF_TWINS, lambda bundle: True_()), (Regions.RR_KF_KNOW_IT_ALL_HOUSE, lambda bundle: True_()), (Regions.RR_KF_KOKIRI_SHOP, lambda bundle: True_()), - (Regions.RR_KF_OUTSIDE_DEKU_TREE, lambda bundle: has_item(bundle, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD) | (OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_OFF)) | is_adult(bundle) & (can_pass(bundle, Enemies.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) | has_item(bundle, Events.LOGIC_FOREST_TEMPLE_CLEAR))), + (Regions.RR_KF_OUTSIDE_DEKU_TREE, lambda bundle: has_item(bundle, Events.LOGIC_SHOWED_MIDO_SWORD_AND_SHIELD) | True_(options=[OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_OFF)]) | is_adult(bundle) & (can_pass(bundle, Enemies.RE_BIG_SKULLTULA, EnemyDistance.ED_CLOSE, True) | has_item(bundle, Events.LOGIC_FOREST_TEMPLE_CLEAR))), (Regions.RR_KF_OUTSIDE_LOST_WOODS, lambda bundle: has_item(bundle, Items.RG_CLIMB) | can_use(bundle, Items.RG_HOOKSHOT) | is_adult(bundle) & (can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL) | can_do_trick(bundle, Tricks.RT_UNINTUITIVE_JUMPS))), (Regions.RR_KF_RUPEE_ALCOVE, lambda bundle: is_adult(bundle) & can_plant_bean(bundle, Regions.RR_KOKIRI_FOREST, Items.RG_KOKIRI_FOREST_BEAN_SOUL)), - (Regions.RR_LW_BRIDGE_FROM_FOREST, lambda bundle: is_adult(bundle) | (OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_ON, "ne")) | has_item(bundle, Events.LOGIC_DEKU_TREE_CLEAR)), + (Regions.RR_LW_BRIDGE_FROM_FOREST, lambda bundle: is_adult(bundle) | True_(options=[OptionFilter(RSK_FOREST, RandomizerSettingKey.RO_CLOSED_FOREST_ON, "ne")]) | has_item(bundle, Events.LOGIC_DEKU_TREE_CLEAR)), ]) # Minuet of Forest Warp @@ -313,23 +313,23 @@ def set_region_rules(world: "SohWorld") -> None: # Root # Events add_events(Regions.RR_ROOT, world, [ - (EventLocations.RR_ROOT_LOGIC_KAKARIKO_GATE_OPEN, Events.LOGIC_KAKARIKO_GATE_OPEN, lambda bundle: OptionFilter(RSK_KAK_GATE, RandomizerSettingKey.RO_KAK_GATE_OPEN)), - (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, Events.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, lambda bundle: OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)), - (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, Events.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, lambda bundle: (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)) | (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FAST))), - (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, Events.LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, lambda bundle: (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)) | (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FAST))), - (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, Events.LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, lambda bundle: (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)) | (OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FAST))), - (EventLocations.RR_ROOT_LOGIC_TH_RESCUED_ALL_CARPENTERS, Events.LOGIC_TH_RESCUED_ALL_CARPENTERS, lambda bundle: OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)), - (EventLocations.RR_ROOT_LOGIC_FREED_EPONA, Events.LOGIC_FREED_EPONA, lambda bundle: OptionFilter(RSK_SKIP_EPONA_RACE, RandomizerSettingKey.RO_GENERIC_YES)), + (EventLocations.RR_ROOT_LOGIC_KAKARIKO_GATE_OPEN, Events.LOGIC_KAKARIKO_GATE_OPEN, lambda bundle: True_(options=[OptionFilter(RSK_KAK_GATE, RandomizerSettingKey.RO_KAK_GATE_OPEN)])), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, Events.LOGIC_TH_COULD_FREE_1_TORCH_CARPENTER, lambda bundle: True_(options=[OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)])), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, Events.LOGIC_TH_COULD_FREE_DOUBLE_CELL_CARPENTER, lambda bundle: True_(options=[OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)]) | True_(options=[OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FAST)])), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, Events.LOGIC_TH_COULD_FREE_DEAD_END_CARPENTER, lambda bundle: True_(options=[OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)]) | True_(options=[OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FAST)])), + (EventLocations.RR_ROOT_LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, Events.LOGIC_TH_COULD_FREE_SLOPE_CARPENTER, lambda bundle: True_(options=[OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)]) | True_(options=[OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FAST)])), + (EventLocations.RR_ROOT_LOGIC_TH_RESCUED_ALL_CARPENTERS, Events.LOGIC_TH_RESCUED_ALL_CARPENTERS, lambda bundle: True_(options=[OptionFilter(RSK_GERUDO_FORTRESS, RandomizerSettingKey.RO_GF_CARPENTERS_FREE)])), + (EventLocations.RR_ROOT_LOGIC_FREED_EPONA, Events.LOGIC_FREED_EPONA, lambda bundle: True_(options=[OptionFilter(RSK_SKIP_EPONA_RACE, True)])), ]) # Locations add_locations(Regions.RR_ROOT, world, [ (Locations.RC_LINKS_POCKET, lambda bundle: True_()), (Locations.RC_TRIFORCE_COMPLETED, lambda bundle: CanWinTriforceHunt()), (Locations.RC_SARIA_SONG_HINT, lambda bundle: can_use(bundle, Items.RG_SARIAS_SONG)), - (Locations.RC_SONG_FROM_IMPA, lambda bundle: OptionFilter(RSK_SKIP_CHILD_ZELDA, RandomizerSettingKey.RO_GENERIC_YES)), - (Locations.RC_HC_MALON_EGG, lambda bundle: OptionFilter(RSK_SKIP_CHILD_ZELDA, RandomizerSettingKey.RO_GENERIC_YES)), - (Locations.RC_HC_ZELDAS_LETTER, lambda bundle: OptionFilter(RSK_SKIP_CHILD_ZELDA, RandomizerSettingKey.RO_GENERIC_YES)), - (Locations.RC_TOT_MASTER_SWORD, lambda bundle: OptionFilter(RSK_SELECTED_STARTING_AGE, RandomizerSettingKey.RO_AGE_ADULT)), + (Locations.RC_SONG_FROM_IMPA, lambda bundle: True_(options=[OptionFilter(RSK_SKIP_CHILD_ZELDA, True)])), + (Locations.RC_HC_MALON_EGG, lambda bundle: True_(options=[OptionFilter(RSK_SKIP_CHILD_ZELDA, True)])), + (Locations.RC_HC_ZELDAS_LETTER, lambda bundle: True_(options=[OptionFilter(RSK_SKIP_CHILD_ZELDA, True)])), + (Locations.RC_TOT_MASTER_SWORD, lambda bundle: True_(options=[OptionFilter(RSK_SELECTED_STARTING_AGE, RandomizerSettingKey.RO_AGE_ADULT)])), ]) # Exits connect_regions(Regions.RR_ROOT, world, [ diff --git a/transpilers/soh_ap/CMakeLists.txt b/transpilers/soh_ap/CMakeLists.txt index 8b96c25..339a1c8 100644 --- a/transpilers/soh_ap/CMakeLists.txt +++ b/transpilers/soh_ap/CMakeLists.txt @@ -13,5 +13,5 @@ if(BUILD_TESTING) ) rls_add_gtest(ap_tests ${ap_test_sources}) - target_link_libraries(ap_tests PRIVATE soh_ap) + target_link_libraries(ap_tests PRIVATE soh_ap parser sema) endif() diff --git a/transpilers/soh_ap/include/soh_ap.h b/transpilers/soh_ap/include/soh_ap.h index 1a2b75f..6211c9c 100644 --- a/transpilers/soh_ap/include/soh_ap.h +++ b/transpilers/soh_ap/include/soh_ap.h @@ -32,10 +32,20 @@ class SohApTranspiler { std::string GenerateExpression(const rls::ast::MatchExpr& node) const; std::string GenerateExpression(const rls::ast::Expr::Variant& node) const; + // True if this binary expression is a `setting(KEY) == VALUE` / `!= VALUE` comparison, + // which is emitted as an atomic OptionFilter rule rather than a Python comparison. + bool IsSettingComparison(const rls::ast::BinaryExpr& node) const; + // Helper to convert setting(KEY) == VALUE expressions to OptionFilter(...) form for RuleBuilder. // Returns empty string if not a setting comparison; caller should use fallback. std::string TryGenerateOptionFilter(const rls::ast::BinaryExpr& node) const; + // Wraps an OptionFilter argument list as a standalone RuleBuilder rule: + // `True_(options=[OptionFilter()])`. A bare OptionFilter is not a Rule and cannot + // combine with another OptionFilter via & / |, so every setting comparison is wrapped in + // its own rule. This keeps each comparison a valid standalone rule that composes normally. + std::string WrapOptionFilter(const std::string& optionFilterArgs) const; + const rls::ast::Project& project; mutable std::optional currentLocationName; }; diff --git a/transpilers/soh_ap/src/generate_expression.cpp b/transpilers/soh_ap/src/generate_expression.cpp index b910a90..806c6f1 100644 --- a/transpilers/soh_ap/src/generate_expression.cpp +++ b/transpilers/soh_ap/src/generate_expression.cpp @@ -7,41 +7,50 @@ namespace rls::transpilers::soh_ap { -// Try to generate an OptionFilter expression for setting comparisons. -// Returns empty string if not a setting comparison; caller should use standard binary expression. -std::string SohApTranspiler::TryGenerateOptionFilter(const rls::ast::BinaryExpr& node) const { - // Only handle == and != comparisons +std::string SohApTranspiler::WrapOptionFilter(const std::string& optionFilterArgs) const { + return "True_(options=[OptionFilter(" + optionFilterArgs + ")])"; +} + +// True if this binary expression is a `setting(KEY) == VALUE` / `!= VALUE` comparison. +bool SohApTranspiler::IsSettingComparison(const rls::ast::BinaryExpr& node) const { + // Only == and != comparisons if (node.op != rls::ast::BinaryOp::Eq && node.op != rls::ast::BinaryOp::NotEq) { - return ""; + return false; } - // Check if left side is a setting() call and right is an identifier (the enum value) + // Left side must be a setting() call and right must be an identifier (the enum value) auto* leftCall = std::get_if(&node.left->node); auto* rightId = std::get_if(&node.right->node); - if (!leftCall || !rightId || leftCall->callee.text != "setting") { - return ""; + return false; } - // Get the setting key argument (RSK_*) + // The setting key argument (RSK_*) must resolve to an identifier auto resolvedPtr = project.getResolvedCallArgs(leftCall); if (!resolvedPtr || resolvedPtr->empty()) { - return ""; + return false; } - - auto* settingKeyId = std::get_if(&resolvedPtr->front()->node); - if (!settingKeyId) { + return std::get_if(&resolvedPtr->front()->node) != nullptr; +} + +// Try to generate an OptionFilter expression for setting comparisons. +// Returns empty string if not a setting comparison; caller should use standard binary expression. +std::string SohApTranspiler::TryGenerateOptionFilter(const rls::ast::BinaryExpr& node) const { + if (!IsSettingComparison(node)) { return ""; } - std::ostringstream result; - result << "OptionFilter(" << settingKeyId->name.text << ", " << GenerateExpression(*rightId); + auto* rightId = std::get_if(&node.right->node); + auto* leftCall = std::get_if(&node.left->node); + auto* settingKeyId = std::get_if(&project.getResolvedCallArgs(leftCall)->front()->node); + + std::ostringstream args; + args << settingKeyId->name.text << ", " << GenerateExpression(*rightId); if (node.op == rls::ast::BinaryOp::NotEq) { - result << ", \"ne\""; + args << ", \"ne\""; } - result << ")"; - - return result.str(); + + return WrapOptionFilter(args.str()); } std::string SohApTranspiler::GenerateExpression(const rls::ast::BoolLiteral& node) const { @@ -64,7 +73,14 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::Identifier& node case rls::ast::Type::Distance: return "EnemyDistance." + node.name.text; case rls::ast::Type::Trick: return "Tricks." + node.name.text; case rls::ast::Type::Logic: return "Events." + node.name.text; - case rls::ast::Type::Setting: return "RandomizerSettingKey." + node.name.text; + case rls::ast::Type::Setting: + // Catch the case where it is generic on or off + if (node.name.text == "RO_GENERIC_YES") { + return "True"; + } else if (node.name.text == "RO_GENERIC_NO") { + return "False"; + } + return "RandomizerSettingKey." + node.name.text; case rls::ast::Type::Region: return "Regions." + node.name.text; case rls::ast::Type::Check: return "Locations." + node.name.text; case rls::ast::Type::Trial: return "TrialKey." + node.name.text; @@ -89,6 +105,11 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::Identifier& node // Unary bitwise NOT (~) and function calls have precedence 3 (very tight). int SohApTranspiler::GetPythonPrecedence(const rls::ast::ExprPtr& expr) const { if (auto* bin = std::get_if(&expr->node)) { + // Setting comparisons are emitted as an atomic OptionFilter rule call, not a + // Python comparison, so they bind as tightly as a call (no parentheses needed). + if (IsSettingComparison(*bin)) { + return 0; + } switch (bin->op) { case rls::ast::BinaryOp::Mul: case rls::ast::BinaryOp::Div: @@ -142,7 +163,7 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::UnaryExpr& node) if (resolvedPtr && !resolvedPtr->empty()) { auto* settingKeyId = std::get_if(&resolvedPtr->front()->node); if (settingKeyId) { - return std::string("OptionFilter(") + settingKeyId->name.text + ", False)"; + return WrapOptionFilter(settingKeyId->name.text + ", False"); } } } @@ -235,7 +256,7 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) // Handle settings differently if (node.callee.text == "setting") { if (auto* id = std::get_if(&resolved[0]->node)) { - oss << "OptionFilter(" << id->name.text << ", True)"; + oss << WrapOptionFilter(id->name.text + std::string(", True")); } } else if (node.callee.text == "has" || node.callee.text == "flag") { oss << "has_item(bundle, " << GenerateExpression(resolved[0]->node) << ")"; @@ -251,6 +272,7 @@ std::string SohApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) oss << "can_afford_slot(" << GenerateExpression(resolved[0]->node) << ")"; } } else { + // Regular function call oss << node.callee.text << "(bundle"; for (size_t i = 0; i < resolved.size(); ++i) { // if (i > 0) { diff --git a/transpilers/soh_ap/tests/ap_tests.cpp b/transpilers/soh_ap/tests/ap_tests.cpp deleted file mode 100644 index 9f27491..0000000 --- a/transpilers/soh_ap/tests/ap_tests.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include - -#include -#include -#include - -#include "soh_ap.h" - -namespace { - -/// In-memory OutputWriter for tests. Captures all output by filename. -class MemoryWriter : public rls::OutputWriter { -public: - std::ostream& open(const std::string& filename) override { - return buffers[filename]; - } - - std::string content(const std::string& filename) const { - auto it = buffers.find(filename); - return it != buffers.end() ? it->second.str() : ""; - } - - std::unordered_map buffers; -}; - -} // namespace - -TEST(ApTests, GeneratesHeaderComment) { - const rls::ast::Project project{}; - MemoryWriter out; - - rls::transpilers::soh_ap::SohApTranspiler(project).Transpile(out); - - EXPECT_EQ(out.content("ap.py"), - "# Generated by RLS soh_ap transpiler\n"); -} diff --git a/transpilers/soh_ap/tests/helpers.h b/transpilers/soh_ap/tests/helpers.h new file mode 100644 index 0000000..2eaac7b --- /dev/null +++ b/transpilers/soh_ap/tests/helpers.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +#include +#include +#include + +#include "ast.h" +#include "parser.h" +#include "sema.h" + +#include "soh_ap.h" + +namespace rls::transpilers::soh_ap_tests { + + /// In-memory OutputWriter for tests. Captures all output by filename. + class MemoryWriter : public rls::OutputWriter { + public: + std::ostream& open(const std::string& filename) override { + return buffers[filename]; + } + + std::string content(const std::string& filename) const { + auto it = buffers.find(filename); + return it != buffers.end() ? it->second.str() : ""; + } + + std::unordered_map buffers; + }; + +} // namespace rls::transpilers::soh_ap_tests + +inline void printDiagnostic(const rls::ast::Diagnostic& d) { + std::ostringstream msg; + if (!d.span.file.empty()) { + msg << d.span.file; + if (d.span.start.line != 0) + msg << ":" << d.span.start.line << ":" << d.span.start.column; + msg << ": "; + } + msg << levelToString(d.level) << ": " << d.message; + ADD_FAILURE() << msg.str(); +} + +// Prepend the host-provided extern defines the access-rule expressions rely on, +// so test sources only need to contain the expression under test. +inline std::string withHostExterns(const std::string& source) { + return + "extern define has(item: Item) -> Bool\n" + "extern define can_use(item: Item) -> Bool\n" + "extern define flag(key: Logic) -> Bool\n" + "extern define setting(key: Setting) -> Setting\n" + "extern define trick(key: Trick) -> Bool\n" + "extern define can_kill(e: Enemy) -> Bool\n" + + source; +} + +inline rls::ast::Project resolveFromSource(const std::string& source) { + auto file = rls::parser::ParseString(withHostExterns(source)); + + for (const auto& d : file.diagnostics) { + if (d.level == rls::ast::DiagnosticLevel::Error) + printDiagnostic(d); + } + + rls::ast::Project project; + project.files.push_back(std::move(file)); + + const auto& diags = rls::sema::analyze(project); + + for (const auto& d : diags) { + if (d.level == rls::ast::DiagnosticLevel::Error) + printDiagnostic(d); + } + + return project; +} diff --git a/transpilers/soh_ap/tests/option_filter_tests.cpp b/transpilers/soh_ap/tests/option_filter_tests.cpp new file mode 100644 index 0000000..1e4e819 --- /dev/null +++ b/transpilers/soh_ap/tests/option_filter_tests.cpp @@ -0,0 +1,130 @@ +// Tests for OptionFilter generation: how `setting()` expressions become RuleBuilder +// OptionFilters wrapped in standalone rules. Other transpiler behavior is covered by +// separate test files. +#include "helpers.h" + +using namespace rls::transpilers::soh_ap_tests; + +namespace { +struct ResolvedExpression { + rls::ast::Project project; + rls::ast::ExprPtr expr; +}; +} // namespace + +static std::string GenerateExpression(const ResolvedExpression& resolved) { + return rls::transpilers::soh_ap::SohApTranspiler(resolved.project).GenerateExpression(resolved.expr); +} + +// Resolve a define from inline RLS source and hand back its body expression. +static ResolvedExpression sourceToExpression(const std::string& source, const std::string& defineName) { + auto project = resolveFromSource(source); + auto defineDecl = project.DefineDecls.find(defineName); + if (defineDecl == project.DefineDecls.end()) { + return { std::move(project), nullptr }; + } + + return { + std::move(project), + std::move(const_cast(defineDecl->second)->body) + }; +} + +// A bare OptionFilter is not a RuleBuilder Rule, so a `setting(KEY) is VALUE` comparison +// is wrapped in its own True_(options=[...]) rule. +TEST(ApOptionFilters, SettingComparisonIsWrappedAsRule) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " setting(RSK_FOO) is RO_BAR\n", + "test")), + "True_(options=[OptionFilter(RSK_FOO, RandomizerSettingKey.RO_BAR)])"); +} + +// `is not` carries the "ne" operator through into the OptionFilter. +TEST(ApOptionFilters, NotEqualSettingComparisonUsesNeOperator) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " setting(RSK_FOO) is not RO_BAR\n", + "test")), + "True_(options=[OptionFilter(RSK_FOO, RandomizerSettingKey.RO_BAR, \"ne\")])"); +} + +// A bare setting() call is a truthiness check, emitted as OptionFilter(KEY, True). +TEST(ApOptionFilters, BareSettingCallChecksTrue) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " setting(RSK_FOO)\n", + "test")), + "True_(options=[OptionFilter(RSK_FOO, True)])"); +} + +// `is RO_GENERIC_YES`, OptionFilter(KEY, True). +TEST(ApOptionFilters, GenericYesSettingCallChecksTrue) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " setting(RSK_FOO) is RO_GENERIC_YES\n", + "test")), + "True_(options=[OptionFilter(RSK_FOO, True)])"); +} + +// `not setting()` is the negated truthiness check, OptionFilter(KEY, False). +TEST(ApOptionFilters, NegatedSettingCallChecksFalse) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " not setting(RSK_FOO)\n", + "test")), + "True_(options=[OptionFilter(RSK_FOO, False)])"); +} + +// `is RO_GENERIC_NO`, OptionFilter(KEY, False). +TEST(ApOptionFilters, GenericNoSettingCallChecksFalse) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " setting(RSK_FOO) is RO_GENERIC_NO\n", + "test")), + "True_(options=[OptionFilter(RSK_FOO, False)])"); +} + +// Two setting comparisons OR'd together cannot combine as bare OptionFilters in RuleBuilder. +// Each must be wrapped in its own rule and joined with `|` so it stays a valid Or of rules. +TEST(ApOptionFilters, AdjacentOrOfSettingsBecomesSeparateWrappedRules) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " setting(RSK_FOO) is RO_BAR or setting(RSK_FOO) is RO_BAZ\n", + "test")), + "True_(options=[OptionFilter(RSK_FOO, RandomizerSettingKey.RO_BAR)]) | " + "True_(options=[OptionFilter(RSK_FOO, RandomizerSettingKey.RO_BAZ)])"); +} + +// The same applies to AND: each wrapped rule joins with `&`. +TEST(ApOptionFilters, AdjacentAndOfSettingsBecomesSeparateWrappedRules) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " setting(RSK_FOO) is RO_BAR and setting(RSK_QUX) is RO_BAZ\n", + "test")), + "True_(options=[OptionFilter(RSK_FOO, RandomizerSettingKey.RO_BAR)]) & " + "True_(options=[OptionFilter(RSK_QUX, RandomizerSettingKey.RO_BAZ)])"); +} + +// A setting comparison combined with a real rule needs no extra parentheses around the +// wrapped OptionFilter rule: it is emitted as an atomic call, not a Python comparison. +TEST(ApOptionFilters, SettingComparisonMixedWithRuleHasNoExtraParens) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " has(RG_HOOKSHOT) or setting(RSK_FOO) is RO_BAR\n", + "test")), + "has_item(bundle, Items.RG_HOOKSHOT) | " + "True_(options=[OptionFilter(RSK_FOO, RandomizerSettingKey.RO_BAR)])"); +} + +// Complex rule to test parenthesis +TEST(ApOptionFilters, ComplexSettingParens) { + EXPECT_EQ(GenerateExpression(sourceToExpression( + "define test():\n" + " has(RG_HOOKSHOT) or setting(RSK_FOO) is RO_BAR and (setting(RSK_BAR) is RO_FOO or can_kill(RE_GOLD_SKULLTULA)) and (flag(LOGIC_BAZ) or setting(RSK_BAZ) is RO_GENERIC_YES)\n", + "test")), + "has_item(bundle, Items.RG_HOOKSHOT) | " + "True_(options=[OptionFilter(RSK_FOO, RandomizerSettingKey.RO_BAR)]) & " + "(True_(options=[OptionFilter(RSK_BAR, RandomizerSettingKey.RO_FOO)]) | can_kill(bundle, Enemies.RE_GOLD_SKULLTULA)) & " + "(has_item(bundle, Events.LOGIC_BAZ) | True_(options=[OptionFilter(RSK_BAZ, True)]))"); +} \ No newline at end of file From 86cb4ad74d6e91feaefa12406a221a47105e2652 Mon Sep 17 00:00:00 2001 From: mattman107 <65982675+mattman107@users.noreply.github.com> Date: Sun, 21 Jun 2026 13:52:40 -0400 Subject: [PATCH 16/22] Split SoH transpiler into generic ap base + derived SohAp Co-Authored-By: Claude Opus 4.8 --- examples/soh_ap/regions.gen.py | 16 +- transpilers/CMakeLists.txt | 1 + transpilers/ap/CMakeLists.txt | 17 + transpilers/ap/include/ap_transpiler.h | 123 ++++++ transpilers/ap/include/section_walk.h | 44 +++ transpilers/ap/src/ap_transpiler.cpp | 43 ++ transpilers/ap/src/generate_expression.cpp | 308 +++++++++++++++ transpilers/ap/src/generate_functions.cpp | 50 +++ transpilers/ap/src/generate_regions.cpp | 72 ++++ transpilers/ap/tests/helpers.h | 105 +++++ transpilers/ap/tests/option_filter_tests.cpp | 114 ++++++ transpilers/soh_ap/CMakeLists.txt | 2 +- transpilers/soh_ap/include/soh_ap.h | 74 ++-- transpilers/soh_ap/src/generate_enums.cpp | 110 ------ .../soh_ap/src/generate_expression.cpp | 371 ------------------ transpilers/soh_ap/src/generate_functions.cpp | 74 ---- transpilers/soh_ap/src/generate_regions.cpp | 138 ------- transpilers/soh_ap/src/soh_ap.cpp | 11 +- transpilers/soh_ap/src/soh_enums.cpp | 97 +++++ transpilers/soh_ap/src/soh_expression.cpp | 112 ++++++ transpilers/soh_ap/src/soh_functions.cpp | 34 ++ transpilers/soh_ap/src/soh_regions.cpp | 46 +++ .../soh_ap/tests/host_rewrite_tests.cpp | 90 +++++ .../soh_ap/tests/option_filter_tests.cpp | 87 +--- 24 files changed, 1323 insertions(+), 816 deletions(-) create mode 100644 transpilers/ap/CMakeLists.txt create mode 100644 transpilers/ap/include/ap_transpiler.h create mode 100644 transpilers/ap/include/section_walk.h create mode 100644 transpilers/ap/src/ap_transpiler.cpp create mode 100644 transpilers/ap/src/generate_expression.cpp create mode 100644 transpilers/ap/src/generate_functions.cpp create mode 100644 transpilers/ap/src/generate_regions.cpp create mode 100644 transpilers/ap/tests/helpers.h create mode 100644 transpilers/ap/tests/option_filter_tests.cpp delete mode 100644 transpilers/soh_ap/src/generate_enums.cpp delete mode 100644 transpilers/soh_ap/src/generate_expression.cpp delete mode 100644 transpilers/soh_ap/src/generate_functions.cpp delete mode 100644 transpilers/soh_ap/src/generate_regions.cpp create mode 100644 transpilers/soh_ap/src/soh_enums.cpp create mode 100644 transpilers/soh_ap/src/soh_expression.cpp create mode 100644 transpilers/soh_ap/src/soh_functions.cpp create mode 100644 transpilers/soh_ap/src/soh_regions.cpp create mode 100644 transpilers/soh_ap/tests/host_rewrite_tests.cpp diff --git a/examples/soh_ap/regions.gen.py b/examples/soh_ap/regions.gen.py index 9c96ef8..6ddaf4f 100644 --- a/examples/soh_ap/regions.gen.py +++ b/examples/soh_ap/regions.gen.py @@ -63,14 +63,14 @@ def set_region_rules(world: "SohWorld") -> None: # KF Kokiri Shop # Locations add_locations(Regions.RR_KF_KOKIRI_SHOP, world, [ - (Locations.RC_KF_SHOP_ITEM_1, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_1))), - (Locations.RC_KF_SHOP_ITEM_2, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_2))), - (Locations.RC_KF_SHOP_ITEM_3, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_3))), - (Locations.RC_KF_SHOP_ITEM_4, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_4))), - (Locations.RC_KF_SHOP_ITEM_5, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_5))), - (Locations.RC_KF_SHOP_ITEM_6, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_6))), - (Locations.RC_KF_SHOP_ITEM_7, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_7))), - (Locations.RC_KF_SHOP_ITEM_8, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & (can_afford_slot(Locations.RC_KF_SHOP_ITEM_8))), + (Locations.RC_KF_SHOP_ITEM_1, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_afford_slot(Locations.RC_KF_SHOP_ITEM_1)), + (Locations.RC_KF_SHOP_ITEM_2, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_afford_slot(Locations.RC_KF_SHOP_ITEM_2)), + (Locations.RC_KF_SHOP_ITEM_3, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_afford_slot(Locations.RC_KF_SHOP_ITEM_3)), + (Locations.RC_KF_SHOP_ITEM_4, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_afford_slot(Locations.RC_KF_SHOP_ITEM_4)), + (Locations.RC_KF_SHOP_ITEM_5, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_afford_slot(Locations.RC_KF_SHOP_ITEM_5)), + (Locations.RC_KF_SHOP_ITEM_6, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_afford_slot(Locations.RC_KF_SHOP_ITEM_6)), + (Locations.RC_KF_SHOP_ITEM_7, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_afford_slot(Locations.RC_KF_SHOP_ITEM_7)), + (Locations.RC_KF_SHOP_ITEM_8, lambda bundle: has_item(bundle, Items.RG_SPEAK_KOKIRI) & can_afford_slot(Locations.RC_KF_SHOP_ITEM_8)), ]) # Exits connect_regions(Regions.RR_KF_KOKIRI_SHOP, world, [ diff --git a/transpilers/CMakeLists.txt b/transpilers/CMakeLists.txt index 41080b8..ae061b2 100644 --- a/transpilers/CMakeLists.txt +++ b/transpilers/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(soh) +add_subdirectory(ap) add_subdirectory(soh_ap) diff --git a/transpilers/ap/CMakeLists.txt b/transpilers/ap/CMakeLists.txt new file mode 100644 index 0000000..8f52622 --- /dev/null +++ b/transpilers/ap/CMakeLists.txt @@ -0,0 +1,17 @@ +file(GLOB ap_transpiler_sources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" +) + +add_library(ap_transpiler STATIC ${ap_transpiler_sources}) + +target_include_directories(ap_transpiler PUBLIC include) +target_link_libraries(ap_transpiler PUBLIC ast) + +if(BUILD_TESTING) + file(GLOB ap_transpiler_test_sources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp" + ) + + rls_add_gtest(ap_transpiler_tests ${ap_transpiler_test_sources}) + target_link_libraries(ap_transpiler_tests PRIVATE ap_transpiler parser sema) +endif() diff --git a/transpilers/ap/include/ap_transpiler.h b/transpilers/ap/include/ap_transpiler.h new file mode 100644 index 0000000..377f4a0 --- /dev/null +++ b/transpilers/ap/include/ap_transpiler.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include + +#include "ast.h" +#include "output.h" + +namespace rls::transpilers::ap { + +// Generic RLS -> Archipelago RuleBuilder transpiler. +// +// This base class owns everything that is true of *any* AP world: the expression +// walk, Python operator precedence/parentheses, OptionFilter wrapping of setting +// comparisons, True_/False_ rule literals, lambda composition, and the region/enum/ +// function file structure. +// +// Every game-specific decision is a protected virtual hook. Hooks with a sensible +// generic default are implemented here; hooks that are pure scaffolding (region +// helper names, enum classes, Python type names) are pure virtual, which makes this +// class abstract. A concrete game transpiler derives from it and overrides the hooks +// it needs -- see SohApTranspiler. +class ApTranspiler { +public: + virtual ~ApTranspiler() = default; + + // Emit the source files for this transpiler. Derived classes choose which of + // the Generate*Source building blocks to call (e.g. SoH emits regions + enums). + virtual void Transpile(rls::OutputWriter& out) const = 0; + + std::string GenerateExpression(const rls::ast::ExprPtr& expr) const; + std::string GenerateExpression(const rls::ast::Expr::Variant& node) const; + void SetCurrentLocation(std::optional location) const; + +protected: + explicit ApTranspiler(const rls::ast::Project& project); + + // Building blocks a derived Transpile() can choose from. + void GenerateFunctionDefinitionsSource(rls::OutputWriter& out) const; + void GenerateRegionsSource(rls::OutputWriter& out) const; + void GenerateEnumsSource(rls::OutputWriter& out) const; + + // == Game-specific hooks ================================================== + // Hooks with a generic default are defined in the base; override to change. + + // Name of the implicit first parameter threaded through generated rule + // lambdas, calls and function signatures (SoH uses "bundle"). Default: empty, + // meaning calls/signatures take no implicit receiver. + virtual std::string ruleContextParam() const; + + // Render an enum-value identifier (e.g. RG_HOOKSHOT) to its Python form. + // Default: the bare value name. Override to add world enum-class prefixes + // and value overrides (e.g. RO_GENERIC_YES -> "True"). + virtual std::string renderEnumValue(rls::ast::Type type, const std::string& name) const; + + // Rewrite a host/builtin call (has, flag, trick, ...) to Python. Default: + // std::nullopt, so the core emits the default call form `callee(args...)` + // (with the ruleContextParam() receiver prepended when one is set). + virtual std::optional renderHostCall(const rls::ast::CallExpr& node) const; + + // Rewrite a world-specific binary special case. Default: std::nullopt + // (normal binary-operator handling). + virtual std::optional renderBinarySpecialCase(const rls::ast::BinaryExpr& node) const; + + // World-specific block-node renderings. Default: empty string. + virtual std::string renderSharedBlock(const rls::ast::SharedBlock& node) const; + virtual std::string renderAnyAgeBlock(const rls::ast::AnyAgeBlock& node) const; + + // == Game-specific scaffolding (pure virtual: no generic AP default) ====== + + // Preamble for the regions file, ending with the rule-setup def line. + virtual std::string regionsPreamble() const = 0; + // Leading args of a region's helper call: e.g. `Regions., world, [\n`. + virtual std::string regionCreationArgs(const std::string& regionKey) const = 0; + // Names of the per-region helper calls. + virtual std::string addEventsFn() const = 0; + virtual std::string addLocationsFn() const = 0; + virtual std::string connectRegionsFn() const = 0; + // Per-entry tuple lines emitted inside each helper call. + virtual std::string eventEntryLine( + const std::string& regionKey, const std::string& entryName, const std::string& rule) const = 0; + virtual std::string locationEntryLine(const std::string& entryName, const std::string& rule) const = 0; + virtual std::string exitEntryLine(const std::string& entryName, const std::string& rule) const = 0; + // Emit the entire enums file (world enum-class scaffolding). + virtual void writeEnums(rls::OutputWriter& out) const = 0; + // Preamble for the functions file (header comment + imports). + virtual std::string functionsPreamble() const = 0; + // Python type name for an RLS type, used in generated function signatures. + virtual std::string pythonTypeName(rls::ast::Type type) const = 0; + + const rls::ast::Project& project; + mutable std::optional currentLocationName; + +private: + int GetPythonPrecedence(const rls::ast::ExprPtr& expr) const; + std::string GenerateChildExpression(const rls::ast::ExprPtr& expr, int parentPrec, bool isRightChild = false) const; + std::string GenerateExpression(const rls::ast::BoolLiteral& node) const; + std::string GenerateExpression(const rls::ast::IntLiteral& node) const; + std::string GenerateExpression(const rls::ast::Identifier& node) const; + std::string GenerateExpression(const rls::ast::UnaryExpr& node) const; + std::string GenerateExpression(const rls::ast::BinaryExpr& node) const; + std::string GenerateExpression(const rls::ast::TernaryExpr& node) const; + std::string GenerateExpression(const rls::ast::CallExpr& node) const; + std::string GenerateExpression(const rls::ast::SharedBlock& node) const; + std::string GenerateExpression(const rls::ast::AnyAgeBlock& node) const; + std::string GenerateExpression(const rls::ast::MatchExpr& node) const; + + // True if this binary expression is a `setting(KEY) == VALUE` / `!= VALUE` comparison, + // which is emitted as an atomic OptionFilter rule rather than a Python comparison. + bool IsSettingComparison(const rls::ast::BinaryExpr& node) const; + + // Convert setting(KEY) == VALUE expressions to OptionFilter(...) form for RuleBuilder. + // Returns empty string if not a setting comparison; caller uses the fallback. + std::string TryGenerateOptionFilter(const rls::ast::BinaryExpr& node) const; + + // Wraps an OptionFilter argument list as a standalone RuleBuilder rule: + // `True_(options=[OptionFilter()])`. A bare OptionFilter is not a Rule and cannot + // combine with another OptionFilter via & / |, so every setting comparison is wrapped in + // its own rule. This keeps each comparison a valid standalone rule that composes normally. + std::string WrapOptionFilter(const std::string& optionFilterArgs) const; +}; + +} // namespace rls::transpilers::ap diff --git a/transpilers/ap/include/section_walk.h b/transpilers/ap/include/section_walk.h new file mode 100644 index 0000000..6c43eb3 --- /dev/null +++ b/transpilers/ap/include/section_walk.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include + +#include "ast.h" + +namespace rls::transpilers::ap { + +using EntryWriter = std::function; + +// Invoke `writer` for every entry in every section of the given kind. +inline void WriteEntries( + const std::vector& sections, + rls::ast::SectionKind sectionKind, + const EntryWriter& writer) +{ + for (const auto& section : sections) { + if (section.kind == sectionKind) { + for (const auto& entry : section.entries) { + writer(entry); + } + } + } +} + +// Collect the names of every entry in every section of the given kind. +inline void InsertToSet( + const std::vector& sections, + rls::ast::SectionKind sectionKind, + std::set& emittedValues) +{ + for (const auto& section : sections) { + if (section.kind == sectionKind) { + for (const auto& entry : section.entries) { + emittedValues.insert(entry.name.text); + } + } + } +} + +} // namespace rls::transpilers::ap diff --git a/transpilers/ap/src/ap_transpiler.cpp b/transpilers/ap/src/ap_transpiler.cpp new file mode 100644 index 0000000..8d0c9b0 --- /dev/null +++ b/transpilers/ap/src/ap_transpiler.cpp @@ -0,0 +1,43 @@ +#include "ap_transpiler.h" + +namespace rls::transpilers::ap { + +ApTranspiler::ApTranspiler(const rls::ast::Project& project) + : project(project) {} + +void ApTranspiler::GenerateEnumsSource(rls::OutputWriter& out) const { + writeEnums(out); +} + +void ApTranspiler::SetCurrentLocation(std::optional location) const { + currentLocationName = std::move(location); +} + +// == Default hook implementations ============================================= +// Generic AP behavior; SoH (and other games) override as needed. + +std::string ApTranspiler::ruleContextParam() const { + return ""; +} + +std::string ApTranspiler::renderEnumValue(rls::ast::Type, const std::string& name) const { + return name; +} + +std::optional ApTranspiler::renderHostCall(const rls::ast::CallExpr&) const { + return std::nullopt; +} + +std::optional ApTranspiler::renderBinarySpecialCase(const rls::ast::BinaryExpr&) const { + return std::nullopt; +} + +std::string ApTranspiler::renderSharedBlock(const rls::ast::SharedBlock&) const { + return ""; +} + +std::string ApTranspiler::renderAnyAgeBlock(const rls::ast::AnyAgeBlock&) const { + return ""; +} + +} // namespace rls::transpilers::ap diff --git a/transpilers/ap/src/generate_expression.cpp b/transpilers/ap/src/generate_expression.cpp new file mode 100644 index 0000000..7fbf76a --- /dev/null +++ b/transpilers/ap/src/generate_expression.cpp @@ -0,0 +1,308 @@ +#include "ap_transpiler.h" + +#include + +namespace rls::transpilers::ap { + +std::string ApTranspiler::WrapOptionFilter(const std::string& optionFilterArgs) const { + return "True_(options=[OptionFilter(" + optionFilterArgs + ")])"; +} + +// True if this binary expression is a `setting(KEY) == VALUE` / `!= VALUE` comparison. +bool ApTranspiler::IsSettingComparison(const rls::ast::BinaryExpr& node) const { + // Only == and != comparisons + if (node.op != rls::ast::BinaryOp::Eq && node.op != rls::ast::BinaryOp::NotEq) { + return false; + } + + // Left side must be a setting() call and right must be an identifier (the enum value) + auto* leftCall = std::get_if(&node.left->node); + auto* rightId = std::get_if(&node.right->node); + if (!leftCall || !rightId || leftCall->callee.text != "setting") { + return false; + } + + // The setting key argument (RSK_*) must resolve to an identifier + auto resolvedPtr = project.getResolvedCallArgs(leftCall); + if (!resolvedPtr || resolvedPtr->empty()) { + return false; + } + return std::get_if(&resolvedPtr->front()->node) != nullptr; +} + +// Try to generate an OptionFilter expression for setting comparisons. +// Returns empty string if not a setting comparison; caller should use standard binary expression. +std::string ApTranspiler::TryGenerateOptionFilter(const rls::ast::BinaryExpr& node) const { + if (!IsSettingComparison(node)) { + return ""; + } + + auto* rightId = std::get_if(&node.right->node); + auto* leftCall = std::get_if(&node.left->node); + auto* settingKeyId = std::get_if(&project.getResolvedCallArgs(leftCall)->front()->node); + + std::ostringstream args; + args << settingKeyId->name.text << ", " << GenerateExpression(*rightId); + if (node.op == rls::ast::BinaryOp::NotEq) { + args << ", \"ne\""; + } + + return WrapOptionFilter(args.str()); +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::BoolLiteral& node) const { + return node.value ? "True_()" : "False_()"; +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::IntLiteral& node) const { + return std::to_string(node.value); +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::Identifier& node) const { + if (node.kind == rls::ast::IdentifierKind::EnumValue) { + auto type = project.getType(&node); + if (!type.has_value()) { + return node.name.text; + } + return renderEnumValue(type.value(), node.name.text); + } else if (node.kind == rls::ast::IdentifierKind::Parameter) { + return node.name.text; + } else { + // Unresolved identifiers should have been blocked earlier in sema; emit empty as a defensive fallback. + return ""; + } +} + +// Returns the RuleBuilder operator precedence for an expression node. +// Precedence adjusted for RuleBuilder bitwise operators (&, |, ~) as used in Archipelago: +// - Arithmetic (*, /): 6 +// - Arithmetic (+, -): 7 +// - Bitwise AND (&): 9 +// - Bitwise OR (|): 11 +// - Comparisons (==, !=, <, etc.): 12 +// - Ternary: 16 +// Unary bitwise NOT (~) and function calls have precedence 3 (very tight). +int ApTranspiler::GetPythonPrecedence(const rls::ast::ExprPtr& expr) const { + if (auto* bin = std::get_if(&expr->node)) { + // Setting comparisons are emitted as an atomic OptionFilter rule call, not a + // Python comparison, so they bind as tightly as a call (no parentheses needed). + if (IsSettingComparison(*bin)) { + return 0; + } + // A game-specific rewrite (e.g. wallet capacity, triforce hunt) collapses the + // comparison to an atomic call, so it also binds as tightly as a call. + if (renderBinarySpecialCase(*bin)) { + return 0; + } + switch (bin->op) { + case rls::ast::BinaryOp::Mul: + case rls::ast::BinaryOp::Div: + return 6; + case rls::ast::BinaryOp::Add: + case rls::ast::BinaryOp::Sub: + return 7; + case rls::ast::BinaryOp::And: + return 9; // Bitwise AND (&) + case rls::ast::BinaryOp::Lt: + case rls::ast::BinaryOp::LtEq: + case rls::ast::BinaryOp::Gt: + case rls::ast::BinaryOp::GtEq: + case rls::ast::BinaryOp::Eq: + case rls::ast::BinaryOp::NotEq: + return 12; + case rls::ast::BinaryOp::Or: + return 11; // Bitwise OR (|) + default: return 0; + } + } + if (std::holds_alternative(expr->node)) { + return 16; + } + return 0; +} + +// Generates an expression, wrapping in parentheses when the child's Python +// precedence is looser than the parent's (or equal on the right side of +// a left-associative operator). +std::string ApTranspiler::GenerateChildExpression( + const rls::ast::ExprPtr& expr, int parentPrec, bool isRightChild) const +{ + auto result = GenerateExpression(expr); + int childPrec = GetPythonPrecedence(expr); + if (childPrec > parentPrec || (isRightChild && childPrec == parentPrec)) { + return "(" + result + ")"; + } + return result; +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::UnaryExpr& node) const { + switch (node.op) { + case rls::ast::UnaryOp::Not: { + // RuleBuilder doesn't support negation of rules. Negation on settings is handled + // via OptionFilter with a false value for direct setting() calls. + if (auto* call = std::get_if(&node.operand->node); + call && call->callee.text == "setting") { + auto resolvedPtr = project.getResolvedCallArgs(call); + if (resolvedPtr && !resolvedPtr->empty()) { + auto* settingKeyId = std::get_if(&resolvedPtr->front()->node); + if (settingKeyId) { + return WrapOptionFilter(settingKeyId->name.text + ", False"); + } + } + } + return GenerateExpression(node.operand); + } + default: + return ""; + } +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::BinaryExpr& node) const { + // Setting comparisons become atomic OptionFilter rules. + std::string optionFilter = TryGenerateOptionFilter(node); + if (!optionFilter.empty()) { + return optionFilter; + } + + // Game-specific binary rewrites (e.g. price <= wallet capacity, triforce hunt). + if (auto special = renderBinarySpecialCase(node)) { + return *special; + } + + switch (node.op) { + case rls::ast::BinaryOp::And: + return GenerateChildExpression(node.left, 9) + " & " + GenerateChildExpression(node.right, 9, true); + case rls::ast::BinaryOp::Or: + return GenerateChildExpression(node.left, 11) + " | " + GenerateChildExpression(node.right, 11, true); + case rls::ast::BinaryOp::Eq: + return GenerateChildExpression(node.left, 12) + " == " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::NotEq: + return GenerateChildExpression(node.left, 12) + " != " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::Lt: + return GenerateChildExpression(node.left, 12) + " < " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::LtEq: + return GenerateChildExpression(node.left, 12) + " <= " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::Gt: + return GenerateChildExpression(node.left, 12) + " > " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::GtEq: + return GenerateChildExpression(node.left, 12) + " >= " + GenerateChildExpression(node.right, 12, true); + case rls::ast::BinaryOp::Add: + return GenerateChildExpression(node.left, 7) + " + " + GenerateChildExpression(node.right, 7, true); + case rls::ast::BinaryOp::Sub: + return GenerateChildExpression(node.left, 7) + " - " + GenerateChildExpression(node.right, 7, true); + case rls::ast::BinaryOp::Mul: + return GenerateChildExpression(node.left, 6) + " * " + GenerateChildExpression(node.right, 6, true); + case rls::ast::BinaryOp::Div: + return GenerateChildExpression(node.left, 6) + " / " + GenerateChildExpression(node.right, 6, true); + default: + return ""; + } +} + +// Python ternary syntax is "a if test else b" +std::string ApTranspiler::GenerateExpression(const rls::ast::TernaryExpr& node) const { + return GenerateExpression(node.thenBranch) + " if " + + GenerateChildExpression(node.condition, 15) + " else " + + GenerateExpression(node.elseBranch); +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::CallExpr& node) const { + auto resolvedPtr = project.getResolvedCallArgs(&node); + if (resolvedPtr == nullptr) { + // Unknown calls or calls with semantic errors are blocked earlier in sema; + // emit empty as a defensive fallback so generation does not invent call forms. + return ""; + } + const auto& resolved = *resolvedPtr; + + // setting(KEY) is a truthiness check, emitted as an OptionFilter rule (AP-generic). + if (node.callee.text == "setting") { + if (auto* id = std::get_if(&resolved[0]->node)) { + return WrapOptionFilter(id->name.text + std::string(", True")); + } + return ""; + } + + // Game-specific host-call rewrites (has, flag, trick, ...). + if (auto hostCall = renderHostCall(node)) { + return *hostCall; + } + + // Default: a regular function call, optionally threading the rule-context + // receiver (e.g. SoH's `bundle`) as the implicit first argument. + std::ostringstream oss; + oss << node.callee.text << "("; + const std::string receiver = ruleContextParam(); + bool needComma = false; + if (!receiver.empty()) { + oss << receiver; + needComma = true; + } + for (size_t i = 0; i < resolved.size(); ++i) { + if (needComma) { + oss << ", "; + } + needComma = true; + + // Function parameters use Python's True/False, not the True_()/False_() rule literals. + if (auto* lit = std::get_if(&resolved[i]->node)) { + oss << (lit->value ? "True" : "False"); + continue; + } + oss << GenerateExpression(resolved[i]->node); + } + oss << ")"; + return oss.str(); +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::SharedBlock& node) const { + return renderSharedBlock(node); +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::AnyAgeBlock& node) const { + return renderAnyAgeBlock(node); +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::MatchExpr& node) const { + std::ostringstream oss; + oss << "rls_match("; + + for (size_t i = 0; i < node.arms.size(); i++) { + const auto& arm = node.arms[i]; + + if (i > 0) oss << ", "; + + // Condition - lambda discriminant: discriminant == P1 or discriminant == P2 + if (arm.isDefault) { + oss << "(lambda: True), "; + } else { + oss << "(lambda " << GenerateExpression(node.discriminant) << "=" << GenerateExpression(node.discriminant) << ": "; + for (size_t j = 0; j < arm.patterns.size(); j++) { + if (j > 0) oss << " or "; + oss << GenerateExpression(node.discriminant) << " == " << GenerateExpression(arm.patterns[j]); + } + oss << "), "; + } + + // Body - lambda: + oss << "(lambda: " << GenerateExpression(arm.body) << "), "; + + // Fallthrough flag + oss << (arm.fallthrough ? "True" : "False"); + } + + oss << ")"; + return oss.str(); +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::Expr::Variant& node) const { + return std::visit([&](const auto& node) { + return ApTranspiler::GenerateExpression(node); + }, node); +} + +std::string ApTranspiler::GenerateExpression(const rls::ast::ExprPtr& expr) const { + return GenerateExpression(expr->node); +} + +} // namespace rls::transpilers::ap diff --git a/transpilers/ap/src/generate_functions.cpp b/transpilers/ap/src/generate_functions.cpp new file mode 100644 index 0000000..ccb7143 --- /dev/null +++ b/transpilers/ap/src/generate_functions.cpp @@ -0,0 +1,50 @@ +#include "ap_transpiler.h" + +#include + +namespace rls::transpilers::ap { + +void ApTranspiler::GenerateFunctionDefinitionsSource(rls::OutputWriter& out) const { + auto& source = out.open("functions.gen.py"); + source << functionsPreamble(); + + // Resolve an AST node's RLS type to its Python type name, deferring the + // concrete mapping to the game hook. + auto typeName = [&](const auto* node) -> std::string { + auto type = project.getType(node); + if (!type.has_value()) { + return "missing_type"; + } + return pythonTypeName(type.value()); + }; + + for (const auto& [name, decl] : project.DefineDecls) { + source << "\n"; + + std::ostringstream sig; + sig << "def " << decl->name << "("; + const std::string receiver = ruleContextParam(); + bool needComma = false; + if (!receiver.empty()) { + sig << receiver; + needComma = true; + } + for (size_t i = 0; i < decl->params.size(); i++) { + const auto& param = decl->params[i]; + if (needComma) { + sig << ", "; + } + needComma = true; + sig << param.name << ": " << typeName(¶m); + if (param.defaultValue != nullptr) { + sig << " = " + GenerateExpression(param.defaultValue); + } + } + sig << ") -> " << typeName(decl->body.get()); + + source << sig.str() << ":\n"; + source << " return " << GenerateExpression(decl->body) << "\n"; + } +} + +} // namespace rls::transpilers::ap diff --git a/transpilers/ap/src/generate_regions.cpp b/transpilers/ap/src/generate_regions.cpp new file mode 100644 index 0000000..3dd46da --- /dev/null +++ b/transpilers/ap/src/generate_regions.cpp @@ -0,0 +1,72 @@ +#include "ap_transpiler.h" +#include "section_walk.h" + +#include +#include + +namespace rls::transpilers::ap { + +void ApTranspiler::GenerateRegionsSource(rls::OutputWriter& out) const { + auto& source = out.open("regions.gen.py"); + source << regionsPreamble(); + + // Helper-call names are the same for every region; resolve them once. + const std::string eventsFn = addEventsFn(); + const std::string locationsFn = addLocationsFn(); + const std::string exitsFn = connectRegionsFn(); + + for (const auto& [regionName, region] : project.RegionDecls) { + const auto extendRegionIt = project.ExtendRegionDecls.find(region->key.text); + + std::vector extendRegionDecls; + if (extendRegionIt != project.ExtendRegionDecls.end()) { + extendRegionDecls = extendRegionIt->second; + } + + const std::string creationArgs = regionCreationArgs(region->key.text); + + // Build one `