#include "editor.hpp" #include #include #include "bool_button.hpp" #include "button.hpp" #include "d/actor/d_a_player.h" #include "d/d_kankyo.h" #include "d/d_meter2_info.h" #include "dusk/map_loader_definitions.h" #include "dusk/randomizer/game/verify_item_functions.h" #include "number_button.hpp" #include "pane.hpp" #include "select_button.hpp" #include "string_button.hpp" #include #include #include #include #include #include #include #include #include #include namespace dusk::ui { Rml::String stage_option_label(const MapEntry& map, bool showInternalNames) { return showInternalNames ? fmt::format("{} ({})", map.mapName, map.mapFile) : map.mapName; } Rml::String stage_label_for_file(const Rml::String& stageFile, bool showInternalNames) { for (const auto& region : gameRegions) { for (const auto& map : region.maps) { if (stageFile == map.mapFile) { return stage_option_label(map, showInternalNames); } } } return stageFile; } void populate_stage_picker(Pane& pane, std::function getStageFile, std::function setStageFile, bool showInternalNames) { pane.clear(); for (const auto& region : gameRegions) { pane.add_section(region.regionName); for (const auto& map : region.maps) { pane.add_button({ .text = stage_option_label(map, showInternalNames), .isSelected = [getStageFile, stageFile = map.mapFile] { return getStageFile() == stageFile; }, }) .on_pressed([setStageFile, stageFile = map.mapFile] { mDoAud_seStartMenu(kSoundItemChange); setStageFile(stageFile); }); } } } namespace { bool has_save_data() { return dComIfGs_getSaveData() != nullptr; } dSv_player_status_a_c* get_player_status() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getPlayer().getPlayerStatusA(); } dSv_player_status_b_c* get_player_status_b() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getPlayer().getPlayerStatusB(); } dSv_player_return_place_c* get_player_return_place() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getPlayer().getPlayerReturnPlace(); } dSv_horse_place_c* get_horse_place() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getPlayer().getHorsePlace(); } dSv_player_item_c* get_player_item() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getPlayer().getItem(); } dSv_player_item_record_c* get_player_item_record() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getPlayer().getItemRecord(); } dSv_player_item_max_c* get_player_item_max() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getPlayer().getItemMax(); } dSv_fishing_info_c* get_player_fishing_info() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getPlayer().getFishingInfo(); } dSv_MiniGame_c* get_minigame() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getMiniGame(); } dSv_player_config_c* get_player_config() { if (!has_save_data()) { return nullptr; } return &dComIfGs_getSaveData()->getPlayer().getConfig(); } template Rml::String fixed_string(const char (&value)[Size]) { size_t length = 0; while (length < Size && value[length] != '\0') { ++length; } return Rml::String(value, length); } template void set_fixed_string(char (&dest)[Size], const Rml::String& value) { std::memset(dest, 0, Size); std::memcpy(dest, value.data(), std::min(value.size(), Size - 1)); } void skip_whitespace(const char*& cursor) { while (std::isspace(static_cast(*cursor))) { ++cursor; } } bool parse_float_token(const char*& cursor, float& parsedValue) { skip_whitespace(cursor); char* end = nullptr; parsedValue = std::strtof(cursor, &end); if (end == cursor) { return false; } cursor = end; skip_whitespace(cursor); return true; } bool consume_comma(const char*& cursor) { skip_whitespace(cursor); if (*cursor != ',') { return false; } ++cursor; return true; } bool parse_vec3(const Rml::String& value, float& x, float& y, float& z) { const char* cursor = value.c_str(); if (!parse_float_token(cursor, x) || !consume_comma(cursor) || !parse_float_token(cursor, y) || !consume_comma(cursor) || !parse_float_token(cursor, z)) { return false; } skip_whitespace(cursor); return *cursor == '\0'; } Rml::String get_player_name() { if (!has_save_data()) { return ""; } return dComIfGs_getPlayerName().buffer; } void set_player_name(Rml::String name) { dComIfGs_setPlayerName(name.c_str()); } Rml::String get_horse_name() { if (!has_save_data()) { return ""; } return dComIfGs_getHorseName().buffer; } void set_horse_name(Rml::String name) { dComIfGs_setHorseName(name.c_str()); } enum ItemType { ITEMTYPE_DEFAULT_e, ITEMTYPE_EQUIP_e, }; struct itemInfo { std::string m_name; u8 m_type = ITEMTYPE_DEFAULT_e; }; std::map itemMap = { {dItemNo_HEART_e, {"Heart"}}, {dItemNo_GREEN_RUPEE_e, {"Green Rupee"}}, {dItemNo_BLUE_RUPEE_e, {"Blue Rupee"}}, {dItemNo_YELLOW_RUPEE_e, {"Yellow Rupee"}}, {dItemNo_RED_RUPEE_e, {"Red Rupee"}}, {dItemNo_PURPLE_RUPEE_e, {"Purple Rupee"}}, {dItemNo_ORANGE_RUPEE_e, {"Orange Rupee"}}, {dItemNo_SILVER_RUPEE_e, {"Silver Rupee"}}, {dItemNo_S_MAGIC_e, {"Small Magic"}}, {dItemNo_L_MAGIC_e, {"Large Magic"}}, {dItemNo_BOMB_5_e, {"Bombs (5)"}}, {dItemNo_BOMB_10_e, {"Bombs (10)"}}, {dItemNo_BOMB_20_e, {"Bombs (20)"}}, {dItemNo_BOMB_30_e, {"Bombs (30)"}}, {dItemNo_ARROW_10_e, {"Arrows (10)"}}, {dItemNo_ARROW_20_e, {"Arrows (20)"}}, {dItemNo_ARROW_30_e, {"Arrows (30)"}}, {dItemNo_ARROW_1_e, {"Arrows (1)"}}, {dItemNo_PACHINKO_SHOT_e, {"Pumpkin Seeds"}}, {dItemNo_NOENTRY_19_e, {"Reserved"}}, {dItemNo_NOENTRY_20_e, {"Reserved"}}, {dItemNo_NOENTRY_21_e, {"Reserved"}}, {dItemNo_WATER_BOMB_5_e, {"Water Bombs (5)"}}, {dItemNo_WATER_BOMB_10_e, {"Water Bombs (10)"}}, {dItemNo_WATER_BOMB_20_e, {"Water Bombs (20)"}}, {dItemNo_WATER_BOMB_30_e, {"Water Bombs (30)"}}, {dItemNo_BOMB_INSECT_5_e, {"Bomblings (5)"}}, {dItemNo_BOMB_INSECT_10_e, {"Bomblings (10)"}}, {dItemNo_BOMB_INSECT_20_e, {"Bomblings (20)"}}, {dItemNo_BOMB_INSECT_30_e, {"Bomblings (30)"}}, {dItemNo_RECOVERY_FAILY_e, {"Fairy"}}, {dItemNo_TRIPLE_HEART_e, {"Triple Hearts"}}, {dItemNo_SMALL_KEY_e, {"Small Key"}}, {dItemNo_KAKERA_HEART_e, {"Piece of Heart"}}, {dItemNo_UTAWA_HEART_e, {"Heart Container"}}, {dItemNo_MAP_e, {"Dungeon Map"}}, {dItemNo_COMPUS_e, {"Compass"}}, {dItemNo_DUNGEON_EXIT_e, {"Ooccoo Sr. (First Time)", ITEMTYPE_EQUIP_e}}, {dItemNo_BOSS_KEY_e, {"Boss Key"}}, {dItemNo_DUNGEON_BACK_e, {"Ooccoo Jr.", ITEMTYPE_EQUIP_e}}, {dItemNo_SWORD_e, {"Ordon Sword"}}, {dItemNo_MASTER_SWORD_e, {"Master Sword"}}, {dItemNo_WOOD_SHIELD_e, {"Ordon Shield"}}, {dItemNo_SHIELD_e, {"Wooden Shield"}}, {dItemNo_HYLIA_SHIELD_e, {"Hylian Shield"}}, {dItemNo_TKS_LETTER_e, {"Ooccoo's Note", ITEMTYPE_EQUIP_e}}, {dItemNo_WEAR_CASUAL_e, {"Ordon Clothes"}}, {dItemNo_WEAR_KOKIRI_e, {"Hero's Clothes"}}, {dItemNo_ARMOR_e, {"Magic Armor"}}, {dItemNo_WEAR_ZORA_e, {"Zora Armor"}}, {dItemNo_MAGIC_LV1_e, {"Magic Level 1"}}, {dItemNo_DUNGEON_EXIT_2_e, {"Ooccoo Sr.", ITEMTYPE_EQUIP_e}}, {dItemNo_WALLET_LV1_e, {"Wallet"}}, {dItemNo_WALLET_LV2_e, {"Big Wallet"}}, {dItemNo_WALLET_LV3_e, {"Giant Wallet"}}, {dItemNo_NOENTRY_55_e, {"Reserved"}}, {dItemNo_NOENTRY_56_e, {"Reserved"}}, {dItemNo_NOENTRY_57_e, {"Reserved"}}, {dItemNo_NOENTRY_58_e, {"Reserved"}}, {dItemNo_NOENTRY_59_e, {"Reserved"}}, {dItemNo_NOENTRY_60_e, {"Reserved"}}, {dItemNo_ZORAS_JEWEL_e, {"Coral Earring", ITEMTYPE_EQUIP_e}}, {dItemNo_HAWK_EYE_e, {"Hawkeye", ITEMTYPE_EQUIP_e}}, {dItemNo_WOOD_STICK_e, {"Wooden Sword"}}, {dItemNo_BOOMERANG_e, {"Gale Boomerang", ITEMTYPE_EQUIP_e}}, {dItemNo_SPINNER_e, {"Spinner", ITEMTYPE_EQUIP_e}}, {dItemNo_IRONBALL_e, {"Ball and Chain", ITEMTYPE_EQUIP_e}}, {dItemNo_BOW_e, {"Hero's Bow", ITEMTYPE_EQUIP_e}}, {dItemNo_HOOKSHOT_e, {"Clawshot", ITEMTYPE_EQUIP_e}}, {dItemNo_HVY_BOOTS_e, {"Iron Boots", ITEMTYPE_EQUIP_e}}, {dItemNo_COPY_ROD_e, {"Dominion Rod", ITEMTYPE_EQUIP_e}}, {dItemNo_W_HOOKSHOT_e, {"Double Clawshots", ITEMTYPE_EQUIP_e}}, {dItemNo_KANTERA_e, {"Lantern", ITEMTYPE_EQUIP_e}}, {dItemNo_LIGHT_SWORD_e, {"Light Sword"}}, {dItemNo_FISHING_ROD_1_e, {"Fishing Rod", ITEMTYPE_EQUIP_e}}, {dItemNo_PACHINKO_e, {"Slingshot", ITEMTYPE_EQUIP_e}}, {dItemNo_COPY_ROD_2_e, {"Dominion Rod (Uncharged)"}}, {dItemNo_NOENTRY_77_e, {"Reserved"}}, {dItemNo_NOENTRY_78_e, {"Reserved"}}, {dItemNo_BOMB_BAG_LV2_e, {"Giant Bomb Bag"}}, {dItemNo_BOMB_BAG_LV1_e, {"Empty Bomb Bag", ITEMTYPE_EQUIP_e}}, {dItemNo_BOMB_IN_BAG_e, {"Bomb Bag"}}, {dItemNo_NOENTRY_82_e, {"Reserved"}}, {dItemNo_LIGHT_ARROW_e, {"Light Arrow"}}, {dItemNo_ARROW_LV1_e, {"Quiver"}}, {dItemNo_ARROW_LV2_e, {"Big Quiver"}}, {dItemNo_ARROW_LV3_e, {"Giant Quiver"}}, {dItemNo_NOENTRY_87_e, {"Reserved"}}, {dItemNo_LURE_ROD_e, {"Fishing Rod (Lure)"}}, {dItemNo_BOMB_ARROW_e, {"Bomb Arrow"}}, {dItemNo_HAWK_ARROW_e, {"Hawk Arrow"}}, {dItemNo_BEE_ROD_e, {"Fishing Rod (Bee Larva)", ITEMTYPE_EQUIP_e}}, {dItemNo_JEWEL_ROD_e, {"Fishing Rod (Earring)", ITEMTYPE_EQUIP_e}}, {dItemNo_WORM_ROD_e, {"Fishing Rod (Worm)", ITEMTYPE_EQUIP_e}}, {dItemNo_JEWEL_BEE_ROD_e, {"Fishing Rod (Earring + Bee Larva)", ITEMTYPE_EQUIP_e}}, {dItemNo_JEWEL_WORM_ROD_e, {"Fishing Rod (Earring + Worm)", ITEMTYPE_EQUIP_e}}, {dItemNo_EMPTY_BOTTLE_e, {"Empty Bottle", ITEMTYPE_EQUIP_e}}, {dItemNo_RED_BOTTLE_e, {"Red Potion", ITEMTYPE_EQUIP_e}}, {dItemNo_GREEN_BOTTLE_e, {"Green Potion", ITEMTYPE_EQUIP_e}}, {dItemNo_BLUE_BOTTLE_e, {"Blue Potion", ITEMTYPE_EQUIP_e}}, {dItemNo_MILK_BOTTLE_e, {"Milk Bottle", ITEMTYPE_EQUIP_e}}, {dItemNo_HALF_MILK_BOTTLE_e, {"Half Milk Bottle", ITEMTYPE_EQUIP_e}}, {dItemNo_OIL_BOTTLE_e, {"Lantern Oil", ITEMTYPE_EQUIP_e}}, {dItemNo_WATER_BOTTLE_e, {"Water Bottle", ITEMTYPE_EQUIP_e}}, {dItemNo_OIL_BOTTLE_2_e, {"Lantern Oil (Scooped)"}}, {dItemNo_RED_BOTTLE_2_e, {"Red Potion (Scooped)"}}, {dItemNo_UGLY_SOUP_e, {"Nasty Soup", ITEMTYPE_EQUIP_e}}, {dItemNo_HOT_SPRING_e, {"Hotspring Water", ITEMTYPE_EQUIP_e}}, {dItemNo_FAIRY_e, {"Fairy", ITEMTYPE_EQUIP_e}}, {dItemNo_HOT_SPRING_2_e, {"Hotspring Water (Shop)"}}, {dItemNo_OIL2_e, {"Lantern Refill (Scooped)"}}, {dItemNo_OIL_e, {"Lantern Refill (Shop)"}}, {dItemNo_NORMAL_BOMB_e, {"Bombs", ITEMTYPE_EQUIP_e}}, {dItemNo_WATER_BOMB_e, {"Water Bombs", ITEMTYPE_EQUIP_e}}, {dItemNo_POKE_BOMB_e, {"Bomblings", ITEMTYPE_EQUIP_e}}, {dItemNo_FAIRY_DROP_e, {"Great Fairy's Tears", ITEMTYPE_EQUIP_e}}, {dItemNo_WORM_e, {"Worm", ITEMTYPE_EQUIP_e}}, {dItemNo_DROP_BOTTLE_e, {"Great Fairy Tears (Jovani)"}}, {dItemNo_BEE_CHILD_e, {"Bee Larva", ITEMTYPE_EQUIP_e}}, {dItemNo_CHUCHU_RARE_e, {"Rare Chu Jelly", ITEMTYPE_EQUIP_e}}, {dItemNo_CHUCHU_RED_e, {"Red Chu Jelly", ITEMTYPE_EQUIP_e}}, {dItemNo_CHUCHU_BLUE_e, {"Blue Chu Jelly", ITEMTYPE_EQUIP_e}}, {dItemNo_CHUCHU_GREEN_e, {"Green Chu Jelly", ITEMTYPE_EQUIP_e}}, {dItemNo_CHUCHU_YELLOW_e, {"Yellow Chu Jelly", ITEMTYPE_EQUIP_e}}, {dItemNo_CHUCHU_PURPLE_e, {"Purple Chu Jelly", ITEMTYPE_EQUIP_e}}, {dItemNo_LV1_SOUP_e, {"Simple Soup", ITEMTYPE_EQUIP_e}}, {dItemNo_LV2_SOUP_e, {"Good Soup", ITEMTYPE_EQUIP_e}}, {dItemNo_LV3_SOUP_e, {"Superb Soup", ITEMTYPE_EQUIP_e}}, {dItemNo_LETTER_e, {"Renado's Letter", ITEMTYPE_EQUIP_e}}, {dItemNo_BILL_e, {"Invoice", ITEMTYPE_EQUIP_e}}, {dItemNo_WOOD_STATUE_e, {"Wooden Statue", ITEMTYPE_EQUIP_e}}, {dItemNo_IRIAS_PENDANT_e, {"Ilia's Charm", ITEMTYPE_EQUIP_e}}, {dItemNo_HORSE_FLUTE_e, {"Horse Call", ITEMTYPE_EQUIP_e}}, {dItemNo_NOENTRY_133_e, {"Reserved"}}, {dItemNo_NOENTRY_134_e, {"Reserved"}}, {dItemNo_NOENTRY_135_e, {"Reserved"}}, {dItemNo_NOENTRY_136_e, {"Reserved"}}, {dItemNo_NOENTRY_137_e, {"Reserved"}}, {dItemNo_NOENTRY_138_e, {"Reserved"}}, {dItemNo_NOENTRY_139_e, {"Reserved"}}, {dItemNo_NOENTRY_140_e, {"Reserved"}}, {dItemNo_NOENTRY_141_e, {"Reserved"}}, {dItemNo_NOENTRY_142_e, {"Reserved"}}, {dItemNo_NOENTRY_143_e, {"Reserved"}}, {dItemNo_RAFRELS_MEMO_e, {"Auru's Memo", ITEMTYPE_EQUIP_e}}, {dItemNo_ASHS_SCRIBBLING_e, {"Ashei's Sketch", ITEMTYPE_EQUIP_e}}, {dItemNo_NOENTRY_146_e, {"Reserved"}}, {dItemNo_NOENTRY_147_e, {"Reserved"}}, {dItemNo_NOENTRY_148_e, {"Reserved"}}, {dItemNo_NOENTRY_149_e, {"Reserved"}}, {dItemNo_NOENTRY_150_e, {"Reserved"}}, {dItemNo_NOENTRY_151_e, {"Reserved"}}, {dItemNo_NOENTRY_152_e, {"Reserved"}}, {dItemNo_NOENTRY_153_e, {"Reserved"}}, {dItemNo_NOENTRY_154_e, {"Reserved"}}, {dItemNo_NOENTRY_155_e, {"Reserved"}}, {dItemNo_CHUCHU_YELLOW2_e, {"Lantern Refill (Yellow Chu)"}}, {dItemNo_OIL_BOTTLE3_e, {"Lantern Oil (Coro)"}}, {dItemNo_SHOP_BEE_CHILD_e, {"Bee Larve (Shop)"}}, {dItemNo_CHUCHU_BLACK_e, {"Black Chu Jelly", ITEMTYPE_EQUIP_e}}, {dItemNo_LIGHT_DROP_e, {"Tear of Light"}}, {dItemNo_DROP_CONTAINER_e, {"Vessel of Light (Faron)"}}, {dItemNo_DROP_CONTAINER02_e, {"Vessel of Light (Eldin)"}}, {dItemNo_DROP_CONTAINER03_e, {"Vessel of Light (Lanayru)"}}, {dItemNo_FILLED_CONTAINER_e, {"Vessel of Light (Filled)"}}, {dItemNo_MIRROR_PIECE_2_e, {"Mirror Shard (Snowpeak Ruins)"}}, {dItemNo_MIRROR_PIECE_3_e, {"Mirror Shard (Temple of Time)"}}, {dItemNo_MIRROR_PIECE_4_e, {"Mirror Shard (City in the Sky)"}}, {dItemNo_NOENTRY_168_e, {"Reserved"}}, {dItemNo_NOENTRY_169_e, {"Reserved"}}, {dItemNo_NOENTRY_170_e, {"Reserved"}}, {dItemNo_NOENTRY_171_e, {"Reserved"}}, {dItemNo_NOENTRY_172_e, {"Reserved"}}, {dItemNo_NOENTRY_173_e, {"Reserved"}}, {dItemNo_NOENTRY_174_e, {"Reserved"}}, {dItemNo_NOENTRY_175_e, {"Reserved"}}, {dItemNo_SMELL_YELIA_POUCH_e, {"Scent of Ilia"}}, {dItemNo_SMELL_PUMPKIN_e, {"Pumpkin Scent"}}, {dItemNo_SMELL_POH_e, {"Poe Scent"}}, {dItemNo_SMELL_FISH_e, {"Reekfish Scent"}}, {dItemNo_SMELL_CHILDREN_e, {"Youth's Scent"}}, {dItemNo_SMELL_MEDICINE_e, {"Medicine Scent"}}, {dItemNo_NOENTRY_182_e, {"Reserved"}}, {dItemNo_NOENTRY_183_e, {"Reserved"}}, {dItemNo_NOENTRY_184_e, {"Reserved"}}, {dItemNo_NOENTRY_185_e, {"Reserved"}}, {dItemNo_NOENTRY_186_e, {"Reserved"}}, {dItemNo_NOENTRY_187_e, {"Reserved"}}, {dItemNo_NOENTRY_188_e, {"Reserved"}}, {dItemNo_NOENTRY_189_e, {"Reserved"}}, {dItemNo_NOENTRY_190_e, {"Reserved"}}, {dItemNo_NOENTRY_191_e, {"Reserved"}}, {dItemNo_M_BEETLE_e, {"Beetle (M)"}}, {dItemNo_F_BEETLE_e, {"Beetle (F)"}}, {dItemNo_M_BUTTERFLY_e, {"Butterfly (M)"}}, {dItemNo_F_BUTTERFLY_e, {"Butterfly (F)"}}, {dItemNo_M_STAG_BEETLE_e, {"Stag Beetle (M)"}}, {dItemNo_F_STAG_BEETLE_e, {"Stag Beetle (F)"}}, {dItemNo_M_GRASSHOPPER_e, {"Grasshopper (M)"}}, {dItemNo_F_GRASSHOPPER_e, {"Grasshopper (F)"}}, {dItemNo_M_NANAFUSHI_e, {"Phasmid (M)"}}, {dItemNo_F_NANAFUSHI_e, {"Phasmid (F)"}}, {dItemNo_M_DANGOMUSHI_e, {"Pill Bug (M)"}}, {dItemNo_F_DANGOMUSHI_e, {"Pill Bug (F)"}}, {dItemNo_M_MANTIS_e, {"Mantis (M)"}}, {dItemNo_F_MANTIS_e, {"Mantis (F)"}}, {dItemNo_M_LADYBUG_e, {"Ladybug (M)"}}, {dItemNo_F_LADYBUG_e, {"Ladybug (F)"}}, {dItemNo_M_SNAIL_e, {"Snail (M)"}}, {dItemNo_F_SNAIL_e, {"Snail (F)"}}, {dItemNo_M_DRAGONFLY_e, {"Dragonfly (M)"}}, {dItemNo_F_DRAGONFLY_e, {"Dragonfly (F)"}}, {dItemNo_M_ANT_e, {"Ant (M)"}}, {dItemNo_F_ANT_e, {"Ant (F)"}}, {dItemNo_M_MAYFLY_e, {"Mayfly (M)"}}, {dItemNo_F_MAYFLY_e, {"Mayfly (F)"}}, {dItemNo_NOENTRY_216_e, {"Reserved"}}, {dItemNo_NOENTRY_217_e, {"Reserved"}}, {dItemNo_NOENTRY_218_e, {"Reserved"}}, {dItemNo_NOENTRY_219_e, {"Reserved"}}, {dItemNo_NOENTRY_220_e, {"Reserved"}}, {dItemNo_NOENTRY_221_e, {"Reserved"}}, {dItemNo_NOENTRY_222_e, {"Reserved"}}, {dItemNo_NOENTRY_223_e, {"Reserved"}}, {dItemNo_POU_SPIRIT_e, {"Poe Soul"}}, {dItemNo_NOENTRY_225_e, {"Reserved"}}, {dItemNo_NOENTRY_226_e, {"Reserved"}}, {dItemNo_NOENTRY_227_e, {"Reserved"}}, {dItemNo_NOENTRY_228_e, {"Reserved"}}, {dItemNo_NOENTRY_229_e, {"Reserved"}}, {dItemNo_NOENTRY_230_e, {"Reserved"}}, {dItemNo_NOENTRY_231_e, {"Reserved"}}, {dItemNo_NOENTRY_232_e, {"Reserved"}}, {dItemNo_ANCIENT_DOCUMENT_e, {"Ancient Sky Book", ITEMTYPE_EQUIP_e}}, {dItemNo_AIR_LETTER_e, {"Ancient Sky Book (Partial)", ITEMTYPE_EQUIP_e}}, {dItemNo_ANCIENT_DOCUMENT2_e, {"Ancient Sky Book (Filled)", ITEMTYPE_EQUIP_e}}, {dItemNo_LV7_DUNGEON_EXIT_e, {"Ooccoo Sr. (City in the Sky)"}}, {dItemNo_LINKS_SAVINGS_e, {"Purple Rupee (Link's Savings)"}}, {dItemNo_SMALL_KEY2_e, {"Small Key (North Faron Gate)"}}, {dItemNo_POU_FIRE1_e, {"Poe Fire 1"}}, {dItemNo_POU_FIRE2_e, {"Poe Fire 2"}}, {dItemNo_POU_FIRE3_e, {"Poe Fire 3"}}, {dItemNo_POU_FIRE4_e, {"Poe Fire 4"}}, {dItemNo_BOSSRIDER_KEY_e, {"Hyrule Field Keys"}}, {dItemNo_TOMATO_PUREE_e, {"Ordon Pumpkin", ITEMTYPE_EQUIP_e}}, {dItemNo_TASTE_e, {"Ordon Goat Cheese", ITEMTYPE_EQUIP_e}}, {dItemNo_LV5_BOSS_KEY_e, {"Bedroom Key"}}, {dItemNo_SURFBOARD_e, {"Surf Leaf"}}, {dItemNo_KANTERA2_e, {"Lantern (Reclaimed)"}}, {dItemNo_L2_KEY_PIECES1_e, {"Key Shard (1)"}}, {dItemNo_L2_KEY_PIECES2_e, {"Key Shard (2)"}}, {dItemNo_L2_KEY_PIECES3_e, {"Key Shard (3)"}}, {dItemNo_KEY_OF_CARAVAN_e, {"Bulblin Camp Key"}}, {dItemNo_LV2_BOSS_KEY_e, {"Goron Mines Boss Key"}}, {dItemNo_KEY_OF_FILONE_e, {"South Faron Gate Key"}}, {dItemNo_NONE_e, {"None"}}, }; Rml::String get_item_name(u8 id) { const auto it = itemMap.find(id); if (it == itemMap.end()) { return fmt::format("Item {}", id); } return it->second.m_name; } Rml::String item_label_for_slot(u8 slot) { if (slot == 0xFF) { return "None"; } const auto id = dComIfGs_getSaveData()->getPlayer().getItem().mItems[slot]; return fmt::format("Slot {0} ({1})", slot, get_item_name(id)); } struct NamedIndexEntry { const char* name; u8 index; }; struct NamedFlagEntry { const char* name; u16 flag; }; struct BugSpeciesEntry { const char* name; u8 maleItem; u8 femaleItem; u16 maleTurnInFlag; u16 femaleTurnInFlag; }; struct FishSpeciesEntry { const char* name; u8 index; }; constexpr std::array swordEntries = { dItemNo_SWORD_e, dItemNo_MASTER_SWORD_e, dItemNo_WOOD_STICK_e, dItemNo_LIGHT_SWORD_e, }; constexpr std::array shieldEntries = { dItemNo_SHIELD_e, dItemNo_WOOD_SHIELD_e, dItemNo_HYLIA_SHIELD_e, }; constexpr std::array smellEntries = { dItemNo_SMELL_CHILDREN_e, dItemNo_SMELL_YELIA_POUCH_e, dItemNo_SMELL_POH_e, dItemNo_SMELL_FISH_e, dItemNo_SMELL_MEDICINE_e, }; constexpr std::array fusedShadowEntries = { NamedIndexEntry{"Forest Temple", 0}, NamedIndexEntry{"Goron Mines", 1}, NamedIndexEntry{"Lakebed Temple", 2}, }; constexpr std::array mirrorShardEntries = { NamedIndexEntry{"Snowpeak Ruins", 1}, NamedIndexEntry{"Temple of Time", 2}, NamedIndexEntry{"City in the Sky", 3}, }; constexpr std::array bugSpeciesEntries = { BugSpeciesEntry{"Ant", dItemNo_M_ANT_e, dItemNo_F_ANT_e, dSv_event_flag_c::F_0421, dSv_event_flag_c::F_0422}, BugSpeciesEntry{"Dayfly", dItemNo_M_MAYFLY_e, dItemNo_F_MAYFLY_e, dSv_event_flag_c::F_0423, dSv_event_flag_c::F_0424}, BugSpeciesEntry{"Beetle", dItemNo_M_BEETLE_e, dItemNo_F_BEETLE_e, dSv_event_flag_c::F_0401, dSv_event_flag_c::F_0402}, BugSpeciesEntry{"Mantis", dItemNo_M_MANTIS_e, dItemNo_F_MANTIS_e, dSv_event_flag_c::F_0413, dSv_event_flag_c::F_0414}, BugSpeciesEntry{"Stag Beetle", dItemNo_M_STAG_BEETLE_e, dItemNo_F_STAG_BEETLE_e, dSv_event_flag_c::F_0405, dSv_event_flag_c::F_0406}, BugSpeciesEntry{"Pill Bug", dItemNo_M_DANGOMUSHI_e, dItemNo_F_DANGOMUSHI_e, dSv_event_flag_c::F_0411, dSv_event_flag_c::F_0412}, BugSpeciesEntry{"Butterfly", dItemNo_M_BUTTERFLY_e, dItemNo_F_BUTTERFLY_e, dSv_event_flag_c::F_0403, dSv_event_flag_c::F_0404}, BugSpeciesEntry{"Ladybug", dItemNo_M_LADYBUG_e, dItemNo_F_LADYBUG_e, dSv_event_flag_c::F_0415, dSv_event_flag_c::F_0416}, BugSpeciesEntry{"Snail", dItemNo_M_SNAIL_e, dItemNo_F_SNAIL_e, dSv_event_flag_c::F_0417, dSv_event_flag_c::F_0418}, BugSpeciesEntry{"Phasmid", dItemNo_M_NANAFUSHI_e, dItemNo_F_NANAFUSHI_e, dSv_event_flag_c::F_0409, dSv_event_flag_c::F_0410}, BugSpeciesEntry{"Grasshopper", dItemNo_M_GRASSHOPPER_e, dItemNo_F_GRASSHOPPER_e, dSv_event_flag_c::F_0407, dSv_event_flag_c::F_0408}, BugSpeciesEntry{"Dragonfly", dItemNo_M_DRAGONFLY_e, dItemNo_F_DRAGONFLY_e, dSv_event_flag_c::F_0419, dSv_event_flag_c::F_0420}, }; constexpr std::array hiddenSkillEntries = { NamedFlagEntry{"Ending Blow", dSv_event_flag_c::F_0339}, NamedFlagEntry{"Shield Attack", dSv_event_flag_c::F_0338}, NamedFlagEntry{"Back Slice", dSv_event_flag_c::F_0340}, NamedFlagEntry{"Helm Splitter", dSv_event_flag_c::F_0341}, NamedFlagEntry{"Mortal Draw", dSv_event_flag_c::F_0342}, NamedFlagEntry{"Jump Strike", dSv_event_flag_c::F_0343}, NamedFlagEntry{"Great Spin", dSv_event_flag_c::F_0344}, }; constexpr std::array letterSenders = { "Renado", "Ooccoo 1", "Ooccoo 2", "The Postman", "Kakariko Goods", "Barnes 1", "Barnes 2", "Barnes Bombs", "Malo Mart", "Telma", "Purlo", "From Jr.", "Princess Agitha", "Lanayru Tourism", "Shad", "Yeta", }; constexpr std::array fishSpeciesEntries = { FishSpeciesEntry{"Ordon Catfish", 3}, FishSpeciesEntry{"Greengill", 5}, FishSpeciesEntry{"Reekfish", 4}, FishSpeciesEntry{"Hyrule Bass", 0}, FishSpeciesEntry{"Hylian Pike", 2}, FishSpeciesEntry{"Hylian Loach", 1}, }; constexpr std::array targetTypeNames = { "Hold", "Switch", }; constexpr std::array soundModeNames = { "Mono", "Stereo", "Surround", }; struct DefaultInventoryEntry { u8 slot; u8 item; }; constexpr std::array defaultInventory = { DefaultInventoryEntry{SLOT_0, dItemNo_BOOMERANG_e}, DefaultInventoryEntry{SLOT_1, dItemNo_KANTERA_e}, DefaultInventoryEntry{SLOT_2, dItemNo_SPINNER_e}, DefaultInventoryEntry{SLOT_3, dItemNo_HVY_BOOTS_e}, DefaultInventoryEntry{SLOT_4, dItemNo_BOW_e}, DefaultInventoryEntry{SLOT_5, dItemNo_HAWK_EYE_e}, DefaultInventoryEntry{SLOT_6, dItemNo_IRONBALL_e}, DefaultInventoryEntry{SLOT_8, dItemNo_COPY_ROD_e}, DefaultInventoryEntry{SLOT_9, dItemNo_HOOKSHOT_e}, DefaultInventoryEntry{SLOT_10, dItemNo_W_HOOKSHOT_e}, DefaultInventoryEntry{SLOT_11, dItemNo_EMPTY_BOTTLE_e}, DefaultInventoryEntry{SLOT_12, dItemNo_EMPTY_BOTTLE_e}, DefaultInventoryEntry{SLOT_13, dItemNo_EMPTY_BOTTLE_e}, DefaultInventoryEntry{SLOT_14, dItemNo_EMPTY_BOTTLE_e}, DefaultInventoryEntry{SLOT_15, dItemNo_NORMAL_BOMB_e}, DefaultInventoryEntry{SLOT_16, dItemNo_WATER_BOMB_e}, DefaultInventoryEntry{SLOT_17, dItemNo_POKE_BOMB_e}, DefaultInventoryEntry{SLOT_18, dItemNo_DUNGEON_EXIT_e}, DefaultInventoryEntry{SLOT_20, dItemNo_FISHING_ROD_1_e}, DefaultInventoryEntry{SLOT_21, dItemNo_HORSE_FLUTE_e}, DefaultInventoryEntry{SLOT_22, dItemNo_ANCIENT_DOCUMENT_e}, DefaultInventoryEntry{SLOT_23, dItemNo_PACHINKO_e}, }; u8 get_slot_default(int slot) { for (const auto& entry : defaultInventory) { if (entry.slot == slot) { return entry.item; } } return dItemNo_NONE_e; } void set_item_first_bit(u8 itemNo, bool owned) { if (owned) { dComIfGs_onItemFirstBit(itemNo); } else { dComIfGs_offItemFirstBit(itemNo); } } void toggle_item_first_bit(u8 itemNo) { set_item_first_bit(itemNo, !dComIfGs_isItemFirstBit(itemNo)); } void set_event_bit(u16 flag, bool enabled) { if (enabled) { dComIfGs_onEventBit(flag); } else { dComIfGs_offEventBit(flag); } } void set_letter_get_flag(int index, bool received) { if (received) { if (dComIfGs_isLetterGetFlag(index)) { return; } dComIfGs_onLetterGetFlag(index); const u8 slot = dMeter2Info_getRecieveLetterNum() - 1; if (slot < 64) { dComIfGs_setGetNumber(slot, static_cast(index + 1)); } } else { if (!dComIfGs_isLetterGetFlag(index)) { return; } auto& info = dComIfGs_getSaveData()->getPlayer().getLetterInfo(); info.mLetterGetFlags[index >> 5] &= ~(1u << (index & 0x1F)); for (int slot = 0; slot < 64; ++slot) { if (dComIfGs_getGetNumber(slot) != index + 1) { continue; } for (int nextSlot = slot; nextSlot < 63; ++nextSlot) { dComIfGs_setGetNumber(nextSlot, dComIfGs_getGetNumber(nextSlot + 1)); } dComIfGs_setGetNumber(63, 0); break; } } } void set_max_life(int maxLife) { maxLife = std::clamp(maxLife, 15, 100); dComIfGs_setMaxLife(static_cast(maxLife)); const u16 maxHealth = (dComIfGs_getMaxLife() / 5) * 4; if (dComIfGs_getLife() > maxHealth) { dComIfGs_setLife(maxHealth); } } Rml::String max_life_label() { const int maxLife = dComIfGs_getMaxLife(); return fmt::format("{} hearts + {} pieces", maxLife / 5, maxLife % 5); } struct ToggleEntry { Rml::String text; std::function isSelected; std::function setSelected; }; void populate_toggle_group(Pane& pane, const std::vector& entries) { pane.clear(); pane.add_section("Actions"); pane.add_button("Select All").on_pressed([entries] { mDoAud_seStartMenu(kSoundItemChange); for (const auto& entry : entries) { entry.setSelected(true); } }); pane.add_button("Select None").on_pressed([entries] { mDoAud_seStartMenu(kSoundItemChange); for (const auto& entry : entries) { entry.setSelected(false); } }); pane.add_section("Items"); for (const auto& entry : entries) { pane.add_button({ .text = entry.text, .isSelected = entry.isSelected, }) .on_pressed([isSelected = entry.isSelected, setSelected = entry.setSelected] { mDoAud_seStartMenu(kSoundItemChange); setSelected(!isSelected()); }); } } template int count_item_first_bits(const std::array& entries) { int count = 0; for (const auto item : entries) { if (dComIfGs_isItemFirstBit(item)) { ++count; } } return count; } template int count_event_bits(const std::array& entries) { int count = 0; for (const auto& entry : entries) { if (dComIfGs_isEventBit(entry.flag)) { ++count; } } return count; } template int count_collect_crystals(const std::array& entries) { int count = 0; for (const auto& entry : entries) { if (dComIfGs_isCollectCrystal(entry.index)) { ++count; } } return count; } template int count_collect_mirrors(const std::array& entries) { int count = 0; for (const auto& entry : entries) { if (dComIfGs_isCollectMirror(entry.index)) { ++count; } } return count; } Rml::String count_label(int count, int total) { return fmt::format("{} / {}", count, total); } int count_clothing() { int count = 0; if (dComIfGs_isItemFirstBit(dItemNo_WEAR_CASUAL_e)) { ++count; } if (dComIfGs_isCollectClothes(KOKIRI_CLOTHES_FLAG)) { ++count; } if (dComIfGs_isItemFirstBit(dItemNo_WEAR_ZORA_e)) { ++count; } if (dComIfGs_isItemFirstBit(dItemNo_ARMOR_e)) { ++count; } return count; } int count_letters() { int count = 0; for (int index = 0; index < letterSenders.size(); ++index) { if (dComIfGs_isLetterGetFlag(index)) { ++count; } } return count; } Rml::String bug_species_label(const BugSpeciesEntry& bug) { int owned = 0; int given = 0; if (dComIfGs_isItemFirstBit(bug.maleItem)) { ++owned; } if (dComIfGs_isItemFirstBit(bug.femaleItem)) { ++owned; } if (dComIfGs_isEventBit(bug.maleTurnInFlag)) { ++given; } if (dComIfGs_isEventBit(bug.femaleTurnInFlag)) { ++given; } return fmt::format("{} / 2 owned, {} / 2 given", owned, given); } Rml::String fish_species_label(const FishSpeciesEntry& fish) { return fmt::format( "{} caught, {} cm", dComIfGs_getFishNum(fish.index), dComIfGs_getFishSize(fish.index)); } bool can_edit_item_first_bit(int itemId, const itemInfo& item) { return itemId < 254 && item.m_name != "Reserved"; } void set_all_item_first_bits(bool owned) { for (const auto& [itemId, item] : itemMap) { if (!can_edit_item_first_bit(itemId, item)) { continue; } set_item_first_bit(static_cast(itemId), owned); } } void populate_item_slot_picker(Pane& pane, int slot) { pane.clear(); pane.add_section("Actions"); pane.add_button(fmt::format("Default ({})", get_item_name(get_slot_default(slot)))) .on_pressed([slot] { mDoAud_seStartMenu(kSoundItemChange); dComIfGs_setItem(slot, get_slot_default(slot)); }); pane.add_section("Items"); pane.add_button( { .text = "None", .isSelected = [slot] { return get_player_item()->mItems[slot] == dItemNo_NONE_e; }, }) .on_pressed([slot] { mDoAud_seStartMenu(kSoundItemChange); dComIfGs_setItem(slot, dItemNo_NONE_e); }); for (const auto& [itemId, item] : itemMap) { if (item.m_type != ITEMTYPE_EQUIP_e) { continue; } pane .add_button({ .text = item.m_name, .isSelected = [slot, itemId] { return get_player_item()->mItems[slot] == itemId; }, }) .on_pressed([slot, itemId] { mDoAud_seStartMenu(kSoundItemChange); dComIfGs_setItem(slot, static_cast(itemId)); }); } } void populate_item_flag_picker(Pane& pane) { pane.clear(); pane.add_section("Actions"); pane.add_button("Select All").on_pressed([] { mDoAud_seStartMenu(kSoundItemChange); set_all_item_first_bits(true); }); pane.add_button("Clear None").on_pressed([] { mDoAud_seStartMenu(kSoundItemChange); set_all_item_first_bits(false); }); pane.add_section("Items"); for (const auto& [itemId, item] : itemMap) { if (!can_edit_item_first_bit(itemId, item)) { continue; } pane .add_button({ .text = item.m_name, .isSelected = [itemId] { return dComIfGs_isItemFirstBit(static_cast(itemId)); }, }) .on_pressed([itemId] { mDoAud_seStartMenu(kSoundItemChange); toggle_item_first_bit(static_cast(itemId)); }); } } void populate_select_item_picker(Pane& pane, u8& selectItemData) { pane.clear(); pane.add_button( { .text = "None", .isSelected = [&selectItemData] { return selectItemData == dItemNo_NONE_e; }, }) .on_pressed([&selectItemData] { mDoAud_seStartMenu(kSoundItemChange); selectItemData = dItemNo_NONE_e; }); for (int i = 0; i < 24; i++) { pane.add_button({ .text = item_label_for_slot(i), .isSelected = [i, &selectItemData] { return selectItemData == i; }, }) .on_pressed([i, &selectItemData] { mDoAud_seStartMenu(kSoundItemChange); selectItemData = i; }); } } void populate_select_clothes_picker(Pane& pane) { pane.clear(); const auto addOption = [&pane](u8 id) { pane.add_button( { .text = get_item_name(id), .isSelected = [id] { return get_player_status()->mSelectEquip[0] == id; }, }) .on_pressed([id] { mDoAud_seStartMenu(kSoundItemChange); dMeter2Info_setCloth(id, false); daPy_getPlayerActorClass()->setClothesChange(0); }); }; addOption(dItemNo_WEAR_CASUAL_e); addOption(dItemNo_WEAR_KOKIRI_e); addOption(dItemNo_WEAR_ZORA_e); addOption(dItemNo_ARMOR_e); } template void populate_select_equip_picker(Pane& pane, u8& equip, const std::array& entries) { pane.clear(); const auto addOption = [&pane, &equip](u8 id) { pane.add_button({ .text = get_item_name(id), .isSelected = [id, &equip] { return equip == id; }, }) .on_pressed([id, &equip] { mDoAud_seStartMenu(kSoundItemChange); equip = id; }); }; addOption(dItemNo_NONE_e); for (const auto item : entries) { addOption(item); } } static const std::array walletSizeNames = { "Normal", "Big", "Giant", }; void populate_wallet_picker(Pane& pane) { pane.clear(); for (int i = 0; i < walletSizeNames.size(); ++i) { pane.add_button({ .text = walletSizeNames[i], .isSelected = [i] { return get_player_status()->getWalletSize() == i; }, }) .on_pressed([i] { mDoAud_seStartMenu(kSoundItemChange); get_player_status()->setWalletSize(i); }); } } static const std::array formNames = { "Human", "Wolf", }; void populate_form_picker(Pane& pane) { pane.clear(); for (int i = 0; i < formNames.size(); ++i) { pane.add_button( { .text = formNames[i], .isSelected = [i] { return get_player_status()->getTransformStatus() == i; }, }) .on_pressed([i] { mDoAud_seStartMenu(kSoundItemChange); get_player_status()->setTransformStatus(i); }); } } void add_toggle_button(Pane& pane, ToggleEntry entry) { auto isSelected = std::move(entry.isSelected); auto setSelected = std::move(entry.setSelected); pane.add_button({ .text = entry.text, .isSelected = isSelected, }) .on_pressed([isSelected, setSelected] { mDoAud_seStartMenu(kSoundItemChange); setSelected(!isSelected()); }); } template std::vector item_toggle_entries(const std::array& entries) { std::vector toggles; toggles.reserve(entries.size()); for (const auto item : entries) { toggles.push_back({ .text = get_item_name(item), .isSelected = [item] { return dComIfGs_isItemFirstBit(item); }, .setSelected = [item](bool selected) { set_item_first_bit(item, selected); }, }); } return toggles; } template std::vector event_toggle_entries(const std::array& entries) { std::vector toggles; toggles.reserve(entries.size()); for (const auto& [name, flag] : entries) { toggles.push_back({ .text = name, .isSelected = [flag] { return dComIfGs_isEventBit(flag); }, .setSelected = [flag](bool selected) { set_event_bit(flag, selected); }, }); } return toggles; } template std::vector collect_crystal_toggle_entries( const std::array& entries) { std::vector toggles; toggles.reserve(entries.size()); for (const auto& [name, index] : entries) { toggles.push_back({ .text = name, .isSelected = [index] { return dComIfGs_isCollectCrystal(index); }, .setSelected = [index](bool selected) { static constexpr u8 fusedShadowItemNos[] = { dItemNo_Randomizer_FUSED_SHADOW_1_e, dItemNo_Randomizer_FUSED_SHADOW_2_e, dItemNo_Randomizer_FUSED_SHADOW_3_e, }; if (selected) { dComIfGs_onCollectCrystal(index); if (randomizer_IsActive()) { dComIfGs_onItemFirstBit(fusedShadowItemNos[index]); } } else { dComIfGs_offCollectCrystal(index); if (randomizer_IsActive()) { dComIfGs_offItemFirstBit(fusedShadowItemNos[index]); } } }, }); } return toggles; } template std::vector collect_mirror_toggle_entries( const std::array& entries) { std::vector toggles; toggles.reserve(entries.size()); for (const auto& [name, index] : entries) { toggles.push_back({ .text = name, .isSelected = [index] { return dComIfGs_isCollectMirror(index); }, .setSelected = [index](bool selected) { static constexpr u8 mirrorShardItemNos[] = { dItemNo_Randomizer_MIRROR_PIECE_1_e, dItemNo_Randomizer_MIRROR_PIECE_2_e, dItemNo_Randomizer_MIRROR_PIECE_3_e, dItemNo_Randomizer_MIRROR_PIECE_4_e, }; if (selected) { if (randomizer_IsActive()) { dComIfGs_onItemFirstBit(mirrorShardItemNos[index]); } dComIfGs_onCollectMirror(index); } else { dComIfGs_offCollectMirror(index); if (randomizer_IsActive()) { dComIfGs_offItemFirstBit(mirrorShardItemNos[index]); } } }, }); } return toggles; } void populate_collect_clothes_picker(Pane& pane) { populate_toggle_group(pane, { ToggleEntry{ .text = "Ordon Clothes", .isSelected = [] { return dComIfGs_isItemFirstBit(dItemNo_WEAR_CASUAL_e); }, .setSelected = [](bool selected) { set_item_first_bit(dItemNo_WEAR_CASUAL_e, selected); }, }, ToggleEntry{ .text = "Hero's Clothes", .isSelected = [] { return dComIfGs_isCollectClothes(KOKIRI_CLOTHES_FLAG); }, .setSelected = [](bool selected) { if (selected) { dComIfGs_setCollectClothes(KOKIRI_CLOTHES_FLAG); } else { dComIfGs_offCollectClothes(KOKIRI_CLOTHES_FLAG); } }, }, ToggleEntry{ .text = "Zora Armor", .isSelected = [] { return dComIfGs_isItemFirstBit(dItemNo_WEAR_ZORA_e); }, .setSelected = [](bool selected) { set_item_first_bit(dItemNo_WEAR_ZORA_e, selected); }, }, ToggleEntry{ .text = "Magic Armor", .isSelected = [] { return dComIfGs_isItemFirstBit(dItemNo_ARMOR_e); }, .setSelected = [](bool selected) { set_item_first_bit(dItemNo_ARMOR_e, selected); }, }, }); } void populate_poe_souls_picker(Pane& pane) { pane.clear(); pane.add_section("Actions"); pane.add_button("All 60").on_pressed([] { mDoAud_seStartMenu(kSoundItemChange); dComIfGs_setPohSpiritNum(60); }); pane.add_button("Clear").on_pressed([] { mDoAud_seStartMenu(kSoundItemChange); dComIfGs_setPohSpiritNum(0); }); pane.add_section("Value"); pane.add_child(NumberButton::Props{ .key = "Collected", .getValue = [] { return dComIfGs_getPohSpiritNum(); }, .setValue = [](int value) { dComIfGs_setPohSpiritNum(static_cast(std::clamp(value, 0, 60))); }, .max = 60, }); } void populate_max_life_picker(Pane& pane) { pane.clear(); pane.add_section("Actions"); pane.add_button("3 Hearts").on_pressed([] { mDoAud_seStartMenu(kSoundItemChange); dComIfGs_setMaxLife(15); dComIfGs_setLife(12); }); pane.add_button("20 Hearts").on_pressed([] { mDoAud_seStartMenu(kSoundItemChange); dComIfGs_setMaxLife(100); dComIfGs_setLife(80); }); pane.add_section("Value"); pane.add_child(NumberButton::Props{ .key = "Max Life", .getValue = [] { return dComIfGs_getMaxLife(); }, .setValue = [](int value) { set_max_life(value); }, .min = 15, .max = 100, }); } void populate_bug_species_picker(Pane& pane, const BugSpeciesEntry& bug) { pane.clear(); pane.add_section("Owned"); add_toggle_button( pane, { .text = fmt::format("Male {}", bug.name), .isSelected = [item = bug.maleItem] { return dComIfGs_isItemFirstBit(item); }, .setSelected = [item = bug.maleItem]( bool selected) { set_item_first_bit(item, selected); }, }); add_toggle_button( pane, { .text = fmt::format("Female {}", bug.name), .isSelected = [item = bug.femaleItem] { return dComIfGs_isItemFirstBit(item); }, .setSelected = [item = bug.femaleItem]( bool selected) { set_item_first_bit(item, selected); }, }); pane.add_section("Given to Agitha"); add_toggle_button( pane, { .text = fmt::format("Male {}", bug.name), .isSelected = [flag = bug.maleTurnInFlag] { return dComIfGs_isEventBit(flag); }, .setSelected = [flag = bug.maleTurnInFlag]( bool selected) { set_event_bit(flag, selected); }, }); add_toggle_button( pane, { .text = fmt::format("Female {}", bug.name), .isSelected = [flag = bug.femaleTurnInFlag] { return dComIfGs_isEventBit(flag); }, .setSelected = [flag = bug.femaleTurnInFlag]( bool selected) { set_event_bit(flag, selected); }, }); } void populate_letters_picker(Pane& pane) { std::vector toggles; toggles.reserve(letterSenders.size()); for (int index = 0; index < letterSenders.size(); ++index) { toggles.push_back({ .text = letterSenders[index], .isSelected = [index] { return dComIfGs_isLetterGetFlag(index); }, .setSelected = [index](bool selected) { set_letter_get_flag(index, selected); }, }); } populate_toggle_group(pane, toggles); } void populate_fish_species_picker(Pane& pane, const FishSpeciesEntry& fish) { pane.clear(); pane.add_section(fish.name); pane.add_child(NumberButton::Props{ .key = "Caught", .getValue = [index = fish.index] { return dComIfGs_getFishNum(index); }, .setValue = [index = fish.index](int value) { get_player_fishing_info()->mFishCount[index] = static_cast(std::clamp(value, 0, 999)); }, .max = 999, }); pane.add_child(NumberButton::Props{ .key = "Biggest", .getValue = [index = fish.index] { return dComIfGs_getFishSize(index); }, .setValue = [index = fish.index](int value) { dComIfGs_setFishSize(index, static_cast(std::clamp(value, 0, 255))); }, .max = 255, }); } Rml::String target_type_label() { const auto type = get_player_config()->getAttentionType(); if (type >= targetTypeNames.size()) { return fmt::format("Unknown ({})", type); } return targetTypeNames[type]; } Rml::String sound_mode_label() { const auto mode = get_player_config()->getSound(); if (mode >= soundModeNames.size()) { return fmt::format("Unknown ({})", mode); } return soundModeNames[mode]; } void populate_target_type_picker(Pane& pane) { pane.clear(); for (u8 type = 0; type < targetTypeNames.size(); ++type) { pane .add_button({ .text = targetTypeNames[type], .isSelected = [type] { return get_player_config()->getAttentionType() == type; }, }) .on_pressed([type] { mDoAud_seStartMenu(kSoundItemChange); get_player_config()->setAttentionType(type); }); } } void populate_sound_mode_picker(Pane& pane) { pane.clear(); for (u8 mode = 0; mode < soundModeNames.size(); ++mode) { pane.add_button( { .text = soundModeNames[mode], .isSelected = [mode] { return get_player_config()->getSound() == mode; }, }) .on_pressed([mode] { mDoAud_seStartMenu(kSoundItemChange); get_player_config()->setSound(mode); }); } } constexpr float kDaytimeUnitsPerHour = 15.0f; float daytime_from_clock(int hour, int minute) { hour = std::clamp(hour, 0, 23); minute = std::clamp(minute, 0, 59); return (hour * kDaytimeUnitsPerHour) + (minute / 60.0f * kDaytimeUnitsPerHour); } void set_clock_time(int hour, int minute) { if (auto* statusB = get_player_status_b()) { statusB->setTime(daytime_from_clock(hour, minute)); } } } // namespace EditorWindow::EditorWindow() { add_tab("Player Status", [this](Rml::Element* content) { auto& leftPane = add_child(content, Pane::Type::Controlled); auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Player"); leftPane.register_control(leftPane.add_child(StringButton::Props{ .key = "Player Name", .getValue = get_player_name, .setValue = set_player_name, .maxLength = 16, }), rightPane, {}); leftPane.register_control(leftPane.add_child(StringButton::Props{ .key = "Horse Name", .getValue = get_horse_name, .setValue = set_horse_name, .maxLength = 16, }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Max Health", .getValue = [] { return get_player_status()->getMaxLife(); }, .setValue = [](int value) { return get_player_status()->setMaxLife(value); }, .max = UINT16_MAX, // TODO: actual max }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Health", .getValue = [] { return get_player_status()->getLife(); }, .setValue = [](int value) { return get_player_status()->setLife(value); }, .max = UINT16_MAX, // TODO: actual max }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Rupees", .getValue = [] { return get_player_status()->getRupee(); }, .setValue = [](int value) { return get_player_status()->setRupee(value); }, .max = get_player_status()->getRupeeMax(), }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Max Oil", .getValue = [] { return get_player_status()->getMaxOil(); }, .setValue = [](int value) { return get_player_status()->setMaxOil(value); }, .max = UINT16_MAX, // TODO: actual max }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Oil", .getValue = [] { return get_player_status()->getOil(); }, .setValue = [](int value) { return get_player_status()->setOil(value); }, .max = UINT16_MAX, // TODO: actual max }), rightPane, {}); leftPane.add_section("Equipment"); const auto genSelectItemComboBox = [&leftPane, &rightPane]( const Rml::String& label, u8& selectItemData) { leftPane.register_control( leftPane.add_select_button({ .key = label, .getValue = [&selectItemData] { return item_label_for_slot(selectItemData); }, }), rightPane, [&selectItemData](Pane& pane) { populate_select_item_picker(pane, selectItemData); }); }; genSelectItemComboBox("Equip X", get_player_status()->mSelectItem[0]); genSelectItemComboBox("Equip Y", get_player_status()->mSelectItem[1]); genSelectItemComboBox("Combo Equip X", get_player_status()->mMixItem[0]); genSelectItemComboBox("Combo Equip Y", get_player_status()->mMixItem[1]); leftPane.register_control( leftPane.add_select_button({ .key = "Clothes", .getValue = [] { return get_item_name(get_player_status()->mSelectEquip[0]); }, }), rightPane, [](Pane& pane) { populate_select_clothes_picker(pane); }); leftPane.register_control( leftPane.add_select_button({ .key = "Sword", .getValue = [] { return get_item_name(get_player_status()->mSelectEquip[1]); }, }), rightPane, [](Pane& pane) { populate_select_equip_picker( pane, get_player_status()->mSelectEquip[1], swordEntries); }); leftPane.register_control( leftPane.add_select_button({ .key = "Shield", .getValue = [] { return get_item_name(get_player_status()->mSelectEquip[2]); }, }), rightPane, [](Pane& pane) { populate_select_equip_picker( pane, get_player_status()->mSelectEquip[2], shieldEntries); }); leftPane.register_control( leftPane.add_select_button({ .key = "Scent", .getValue = [] { return get_item_name(get_player_status()->mSelectEquip[3]); }, }), rightPane, [](Pane& pane) { populate_select_equip_picker( pane, get_player_status()->mSelectEquip[3], smellEntries); }); leftPane.register_control( leftPane.add_select_button({ .key = "Wallet Size", .getValue = [] { return walletSizeNames[get_player_status()->getWalletSize()]; }, }), rightPane, [](Pane& pane) { populate_wallet_picker(pane); }); leftPane.register_control( leftPane.add_select_button({ .key = "Form", .getValue = [] { return formNames[get_player_status()->getTransformStatus()]; }, }), rightPane, [](Pane& pane) { populate_form_picker(pane); }); leftPane.add_section("World"); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Day", .getValue = [] { return get_player_status_b()->getDate(); }, .setValue = [](int value) { get_player_status_b()->setDate(static_cast(value)); }, .max = UINT16_MAX, }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Hour", .getValue = [] { return dKy_getdaytime_hour(); }, .setValue = [](int value) { set_clock_time(value, dKy_getdaytime_minute()); }, .max = 23, }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Minute", .getValue = [] { return dKy_getdaytime_minute(); }, .setValue = [](int value) { set_clock_time(dKy_getdaytime_hour(), value); }, .max = 59, }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Transform Level", .getValue = [] { return std::popcount(static_cast( get_player_status_b()->mTransformLevelFlag & 0xF)); }, .setValue = [](int value) { get_player_status_b()->mTransformLevelFlag = static_cast((1u << value) - 1u); }, .max = 4, }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Twilight Clear Level", .getValue = [] { return std::popcount(static_cast( get_player_status_b()->mDarkClearLevelFlag & 0x7)); }, .setValue = [](int value) { get_player_status_b()->mDarkClearLevelFlag = static_cast((1u << value) - 1u); }, .max = 3, }), rightPane, {}); }); add_tab("Location", [this](Rml::Element* content) { auto& leftPane = add_child(content, Pane::Type::Controlled); auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Save Location"); leftPane .register_control(leftPane.add_select_button({ .key = "Stage", .getValue = [] { return stage_label_for_file( fixed_string(get_player_return_place()->mName)); }, }), rightPane, [](Pane& pane) { populate_stage_picker( pane, [] { return fixed_string(get_player_return_place()->mName); }, [](const char* stageFile) { set_fixed_string( get_player_return_place()->mName, Rml::String(stageFile)); }); }) .set_disabled(true); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Room", .getValue = [] { return get_player_return_place()->mRoomNo; }, .setValue = [](int value) { get_player_return_place()->mRoomNo = static_cast(value); }, .min = std::numeric_limits::min(), .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Spawn ID", .getValue = [] { return get_player_return_place()->mPlayerStatus; }, .setValue = [](int value) { get_player_return_place()->mPlayerStatus = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.add_section("Horse Location"); leftPane.register_control(leftPane.add_child(StringButton::Props{ .key = "Horse Position", .getValue = [] { const auto* horsePlace = get_horse_place(); return fmt::format("{}, {}, {}", static_cast(horsePlace->mPos.x), static_cast(horsePlace->mPos.y), static_cast(horsePlace->mPos.z)); }, .setValue = [](Rml::String value) { float x = 0.0f; float y = 0.0f; float z = 0.0f; if (parse_vec3(value, x, y, z)) { auto* horsePlace = get_horse_place(); horsePlace->mPos.x = x; horsePlace->mPos.y = y; horsePlace->mPos.z = z; } }, }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Horse Angle", .getValue = [] { return get_horse_place()->mAngleY; }, .setValue = [](int value) { get_horse_place()->mAngleY = static_cast(value); }, .min = std::numeric_limits::min(), .max = std::numeric_limits::max(), }), rightPane, {}); leftPane .register_control( leftPane.add_select_button({ .key = "Horse Stage", .getValue = [] { return stage_label_for_file(fixed_string(get_horse_place()->mName)); }, }), rightPane, [](Pane& pane) { populate_stage_picker( pane, [] { return fixed_string(get_horse_place()->mName); }, [](const char* stageFile) { set_fixed_string(get_horse_place()->mName, Rml::String(stageFile)); }); }) .set_disabled(true); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Horse Room", .getValue = [] { return get_horse_place()->mRoomNo; }, .setValue = [](int value) { get_horse_place()->mRoomNo = static_cast(value); }, .min = std::numeric_limits::min(), .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Horse Spawn ID", .getValue = [] { return get_horse_place()->mSpawnId; }, .setValue = [](int value) { get_horse_place()->mSpawnId = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); }); add_tab("Inventory", [this](Rml::Element* content) { auto& leftPane = add_child(content, Pane::Type::Controlled); auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Item Wheel"); leftPane.register_control(leftPane.add_button("Default All").on_pressed([&rightPane] { mDoAud_seStartMenu(kSoundItemChange); for (int slot = 0; slot < 24; ++slot) { dComIfGs_setItem(slot, get_slot_default(slot)); } rightPane.clear(); }), rightPane, {}); leftPane.register_control(leftPane.add_button("Clear All").on_pressed([&rightPane] { mDoAud_seStartMenu(kSoundItemChange); for (int slot = 0; slot < 24; ++slot) { dComIfGs_setItem(slot, dItemNo_NONE_e); } rightPane.clear(); }), rightPane, {}); for (int slot = 0; slot < 24; ++slot) { leftPane.register_control( leftPane.add_select_button({ .key = fmt::format("Slot {0:02d}", slot), .getValue = [slot] { return get_item_name(get_player_item()->mItems[slot]); }, }), rightPane, [slot](Pane& pane) { populate_item_slot_picker(pane, slot); }); } leftPane.add_section("Amounts"); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Arrows Amount", .getValue = [] { return get_player_item_record()->mArrowNum; }, .setValue = [](int value) { get_player_item_record()->mArrowNum = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Slingshot Amount", .getValue = [] { return get_player_item_record()->mPachinkoNum; }, .setValue = [](int value) { get_player_item_record()->mPachinkoNum = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); for (int bag = 0; bag < 3; ++bag) { leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = fmt::format("Bomb Bag {} Amount", bag + 1), .getValue = [bag] { return get_player_item_record()->mBombNum[bag]; }, .setValue = [bag](int value) { get_player_item_record()->mBombNum[bag] = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); } for (int bottle = 0; bottle < 4; ++bottle) { leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = fmt::format("Bottle {} Amount", bottle + 1), .getValue = [bottle] { return get_player_item_record()->mBottleNum[bottle]; }, .setValue = [bottle](int value) { get_player_item_record()->mBottleNum[bottle] = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); } leftPane.add_section("Capacities"); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Arrows Max", .getValue = [] { return get_player_item_max()->mItemMax[0]; }, .setValue = [](int value) { get_player_item_max()->mItemMax[0] = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Normal Bombs Max", .getValue = [] { return get_player_item_max()->mItemMax[1]; }, .setValue = [](int value) { get_player_item_max()->mItemMax[1] = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Water Bombs Max", .getValue = [] { return get_player_item_max()->mItemMax[2]; }, .setValue = [](int value) { get_player_item_max()->mItemMax[2] = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Bomblings Max", .getValue = [] { return get_player_item_max()->mItemMax[3]; }, .setValue = [](int value) { get_player_item_max()->mItemMax[3] = static_cast(value); }, .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.add_section("Flags"); leftPane.register_control(leftPane.add_select_button({ .key = "Obtained Items", .getValue = [] { return "Edit"; }, }), rightPane, [](Pane& pane) { populate_item_flag_picker(pane); }); }); add_tab("Collection", [this](Rml::Element* content) { auto& leftPane = add_child(content, Pane::Type::Controlled); auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Equipment"); leftPane.register_control( leftPane.add_select_button({ .key = "Swords", .getValue = [] { return count_label( count_item_first_bits(swordEntries), swordEntries.size()); }, }), rightPane, [](Pane& pane) { populate_toggle_group(pane, item_toggle_entries(swordEntries)); }); leftPane.register_control( leftPane.add_select_button({ .key = "Shields", .getValue = [] { return count_label( count_item_first_bits(shieldEntries), shieldEntries.size()); }, }), rightPane, [](Pane& pane) { populate_toggle_group(pane, item_toggle_entries(shieldEntries)); }); leftPane.register_control(leftPane.add_select_button({ .key = "Clothing", .getValue = [] { return count_label(count_clothing(), 4); }, }), rightPane, [](Pane& pane) { populate_collect_clothes_picker(pane); }); leftPane.add_section("Key Items"); leftPane.register_control( leftPane.add_select_button({ .key = "Fused Shadows", .getValue = [] { return count_label( count_collect_crystals(fusedShadowEntries), fusedShadowEntries.size()); }, }), rightPane, [](Pane& pane) { populate_toggle_group(pane, collect_crystal_toggle_entries(fusedShadowEntries)); }); leftPane.register_control( leftPane.add_select_button({ .key = "Mirror Shards", .getValue = [] { return count_label( count_collect_mirrors(mirrorShardEntries), mirrorShardEntries.size()); }, }), rightPane, [](Pane& pane) { populate_toggle_group(pane, collect_mirror_toggle_entries(mirrorShardEntries)); }); leftPane.add_section("Health & Souls"); leftPane.register_control( leftPane.add_select_button({ .key = "Poe Souls", .getValue = [] { return fmt::format("{} / 60", dComIfGs_getPohSpiritNum()); }, }), rightPane, [](Pane& pane) { populate_poe_souls_picker(pane); }); leftPane.register_control(leftPane.add_select_button({ .key = "Max Life", .getValue = [] { return max_life_label(); }, }), rightPane, [](Pane& pane) { populate_max_life_picker(pane); }); leftPane.add_section("Golden Bugs"); for (const auto& bug : bugSpeciesEntries) { leftPane.register_control(leftPane.add_select_button({ .key = bug.name, .getValue = [bug] { return bug_species_label(bug); }, }), rightPane, [bug](Pane& pane) { populate_bug_species_picker(pane, bug); }); } leftPane.add_section("Skills"); leftPane.register_control( leftPane.add_select_button({ .key = "Hidden Skills", .getValue = [] { return count_label( count_event_bits(hiddenSkillEntries), hiddenSkillEntries.size()); }, }), rightPane, [](Pane& pane) { populate_toggle_group(pane, event_toggle_entries(hiddenSkillEntries)); }); leftPane.add_section("Logs"); leftPane.register_control( leftPane.add_select_button({ .key = "Postman Letters", .getValue = [] { return count_label(count_letters(), letterSenders.size()); }, }), rightPane, [](Pane& pane) { populate_letters_picker(pane); }); leftPane.add_section("Fishing Log"); for (const auto& fish : fishSpeciesEntries) { leftPane.register_control(leftPane.add_select_button({ .key = fish.name, .getValue = [fish] { return fish_species_label(fish); }, }), rightPane, [fish](Pane& pane) { populate_fish_species_picker(pane, fish); }); } }); //add_tab("Flags", [this](Rml::Element* content) { // // TODO //}); add_tab("Minigame", [this](Rml::Element* content) { auto& leftPane = add_child(content, Pane::Type::Controlled); auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Records"); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "STAR Game Time (ms)", .getValue = [] { return static_cast(std::min( get_minigame()->getHookGameTime(), std::numeric_limits::max())); }, .setValue = [](int value) { get_minigame()->setHookGameTime(static_cast(std::max(0, value))); }, .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Snowboard Race Time (ms)", .getValue = [] { return static_cast(std::min( get_minigame()->getRaceGameTime(), std::numeric_limits::max())); }, .setValue = [](int value) { get_minigame()->setRaceGameTime(static_cast(std::max(0, value))); }, .max = std::numeric_limits::max(), }), rightPane, {}); leftPane.register_control( leftPane.add_child(NumberButton::Props{ .key = "Fruit-Pop-Flight Score", .getValue = [] { return static_cast(std::min( get_minigame()->getBalloonScore(), std::numeric_limits::max())); }, .setValue = [](int value) { get_minigame()->setBalloonScore(static_cast(std::max(0, value))); }, .max = std::numeric_limits::max(), }), rightPane, {}); }); add_tab("Config", [this](Rml::Element* content) { auto& leftPane = add_child(content, Pane::Type::Controlled); auto& rightPane = add_child(content, Pane::Type::Uncontrolled); leftPane.add_section("Options"); leftPane.register_control( leftPane.add_child(BoolButton::Props{ .key = "Enable Vibration", .getValue = [] { return get_player_config()->getVibration() != 0; }, .setValue = [](bool value) { get_player_config()->setVibration(value); }, }), rightPane, {}); leftPane.register_control(leftPane.add_select_button({ .key = "Target Type", .getValue = [] { return target_type_label(); }, }), rightPane, [](Pane& pane) { populate_target_type_picker(pane); }); leftPane.register_control(leftPane.add_select_button({ .key = "Sound", .getValue = [] { return sound_mode_label(); }, }), rightPane, [](Pane& pane) { populate_sound_mode_picker(pane); }); }); } } // namespace dusk::ui