#include "tracker_state.h" #include #include #include #include #include #include #include #include #include "ap_state.h" #include "game_data.h" #include "logger.h" namespace { struct Requirements { bool disabled = false; std::set doors; // non-grouped, handles progressive std::set panel_doors; // non-grouped, handles progressive std::set items; // all other items std::set rooms; // maybe bool mastery = false; // maybe bool panel_hunt = false; // maybe void Merge(const Requirements& rhs) { if (rhs.disabled) { return; } for (int id : rhs.doors) { doors.insert(id); } for (int id : rhs.panel_doors) { panel_doors.insert(id); } for (int id : rhs.items) { items.insert(id); } for (int id : rhs.rooms) { rooms.insert(id); } mastery = mastery || rhs.mastery; panel_hunt = panel_hunt || rhs.panel_hunt; } }; class RequirementCalculator { public: void Reset() { doors_.clear(); panels_.clear(); } const Requirements& GetDoor(int door_id) { if (!doors_.count(door_id)) { Requirements requirements; const Door& door_obj = GD_GetDoor(door_id); if (door_obj.type == DoorType::kSunPainting) { if (!AP_IsPilgrimageEnabled()) { requirements.items.insert(door_obj.ap_item_id); } else { requirements.disabled = true; } } else if (door_obj.type == DoorType::kSunwarp) { switch (AP_GetSunwarpAccess()) { case kSUNWARP_ACCESS_NORMAL: // Do nothing. break; case kSUNWARP_ACCESS_DISABLED: requirements.disabled = true; break; case kSUNWARP_ACCESS_UNLOCK: requirements.items.insert(door_obj.group_ap_item_id); break; case kSUNWARP_ACCESS_INDIVIDUAL: case kSUNWARP_ACCESS_PROGRESSIVE: requirements.doors.insert(door_obj.id); break; } } else if (AP_GetDoorShuffleMode() != kDOORS_MODE || door_obj.skip_item) { for (int panel_id : door_obj.panels) { const Requirements& panel_reqs = GetPanel(panel_id); requirements.Merge(panel_reqs); } } else if (AP_AreDoorsGrouped() && !door_obj.group_name.empty()) { requirements.items.insert(door_obj.group_ap_item_id); } else { requirements.doors.insert(door_obj.id); } doors_[door_id] = requirements; } return doors_[door_id]; } const Requirements& GetPanel(int panel_id) { if (!panels_.count(panel_id)) { Requirements requirements; const Panel& panel_obj = GD_GetPanel(panel_id); requirements.rooms.insert(panel_obj.room); if (panel_obj.name == "THE MASTER") { requirements.mastery = true; } if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && AP_GetLevel2Requirement() > 1) { requirements.panel_hunt = true; } for (int room_id : panel_obj.required_rooms) { requirements.rooms.insert(room_id); } for (int door_id : panel_obj.required_doors) { const Requirements& door_reqs = GetDoor(door_id); requirements.Merge(door_reqs); } for (int panel_id : panel_obj.required_panels) { const Requirements& panel_reqs = GetPanel(panel_id); requirements.Merge(panel_reqs); } if (AP_IsColorShuffle()) { for (LingoColor color : panel_obj.colors) { requirements.items.insert(GD_GetItemIdForColor(color)); } } if (panel_obj.panel_door != -1 && AP_GetDoorShuffleMode() == kPANELS_MODE) { const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_obj.panel_door); if (panel_door_obj.group_ap_item_id != -1 && AP_AreDoorsGrouped()) { requirements.items.insert(panel_door_obj.group_ap_item_id); } else { requirements.panel_doors.insert(panel_obj.panel_door); } } panels_[panel_id] = requirements; } return panels_[panel_id]; } private: std::map doors_; std::map panels_; }; struct TrackerState { std::map reachability; std::set reachable_doors; std::set reachable_paintings; std::mutex reachability_mutex; RequirementCalculator requirements; std::map> door_reports; bool pilgrimage_doable = false; }; enum Decision { kYes, kNo, kMaybe }; TrackerState& GetState() { static TrackerState* instance = new TrackerState(); return *instance; } class StateCalculator; struct StateCalculatorOptions { int start; bool pilgrimage = false; StateCalculator* parent = nullptr; }; class StateCalculator { public: StateCalculator() = default; explicit StateCalculator(StateCalculatorOptions options) : options_(options) {} void Calculate() { painting_mapping_ = AP_GetPaintingMapping(); checked_paintings_ = AP_GetCheckedPaintings(); sunwarp_mapping_ = AP_GetSunwarpMapping(); std::list panel_boundary; std::list painting_boundary; std::list flood_boundary; flood_boundary.push_back( {.source_room = -1, .destination_room = options_.start}); bool reachable_changed = true; while (reachable_changed) { reachable_changed = false; std::list new_boundary; std::list new_panel_boundary; for (int panel_id : panel_boundary) { if (solveable_panels_.count(panel_id)) { continue; } Decision panel_reachable = IsPanelReachable(panel_id); if (panel_reachable == kYes) { solveable_panels_.insert(panel_id); reachable_changed = true; } else if (panel_reachable == kMaybe) { new_panel_boundary.push_back(panel_id); } } std::list new_painting_boundary; for (int painting_id : painting_boundary) { if (reachable_paintings_.count(painting_id)) { continue; } Decision painting_reachable = IsPaintingReachable(painting_id); if (painting_reachable == kYes) { reachable_paintings_.insert(painting_id); reachable_changed = true; PaintingExit cur_painting = GD_GetPaintingExit(painting_id); if (painting_mapping_.count(cur_painting.internal_id) && checked_paintings_.count(cur_painting.internal_id)) { Exit painting_exit; PaintingExit target_painting = GD_GetPaintingExit(GD_GetPaintingByName( painting_mapping_.at(cur_painting.internal_id))); painting_exit.source_room = cur_painting.room; painting_exit.destination_room = target_painting.room; painting_exit.type = EntranceType::kPainting; new_boundary.push_back(painting_exit); } } else if (painting_reachable == kMaybe) { new_painting_boundary.push_back(painting_id); } } for (const Exit& room_exit : flood_boundary) { if (reachable_rooms_.count(room_exit.destination_room)) { continue; } bool valid_transition = false; Decision exit_usable = IsExitUsable(room_exit); if (exit_usable == kYes) { valid_transition = true; } else if (exit_usable == kMaybe) { new_boundary.push_back(room_exit); } if (valid_transition) { reachable_rooms_.insert(room_exit.destination_room); reachable_changed = true; #ifndef NDEBUG std::list room_path = paths_[room_exit.source_room]; room_path.push_back(room_exit.destination_room); paths_[room_exit.destination_room] = room_path; #endif const Room& room_obj = GD_GetRoom(room_exit.destination_room); for (const Exit& out_edge : room_obj.exits) { if (out_edge.type == EntranceType::kPainting && AP_IsPaintingShuffle()) { continue; } if (out_edge.type == EntranceType::kSunwarp && AP_IsSunwarpShuffle()) { continue; } new_boundary.push_back(out_edge); } if (AP_IsPaintingShuffle()) { for (int out_edge : room_obj.paintings) { new_painting_boundary.push_back(out_edge); } } if (AP_IsSunwarpShuffle()) { for (int index : room_obj.sunwarps) { if (sunwarp_mapping_.count(index)) { const SunwarpMapping& sm = sunwarp_mapping_.at(index); new_boundary.push_back( {.source_room = room_exit.destination_room, .destination_room = GD_GetRoomForSunwarp(sm.exit_index), .door = GD_GetSunwarpDoors().at(sm.dots - 1), .type = EntranceType::kSunwarp}); } } } if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { new_boundary.push_back( {.source_room = room_exit.destination_room, .destination_room = GD_GetRoomByName("Color Hallways"), .type = EntranceType::kPainting}); } if (AP_IsPilgrimageEnabled()) { int pilgrimage_start_id = GD_GetRoomByName("Hub Room"); if (AP_IsSunwarpShuffle()) { for (const auto& [start_index, mapping] : sunwarp_mapping_) { if (mapping.dots == 1) { pilgrimage_start_id = GD_GetRoomForSunwarp(start_index); } } } if (room_exit.destination_room == pilgrimage_start_id) { new_boundary.push_back( {.source_room = room_exit.destination_room, .destination_room = GD_GetRoomByName("Pilgrim Antechamber"), .type = EntranceType::kPilgrimage}); } } else { if (room_obj.name == "Starting Room") { new_boundary.push_back( {.source_room = room_exit.destination_room, .destination_room = GD_GetRoomByName("Pilgrim Antechamber"), .door = GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"), .type = EntranceType::kPainting}); } } for (int panel_id : room_obj.panels) { new_panel_boundary.push_back(panel_id); } } } flood_boundary = new_boundary; panel_boundary = new_panel_boundary; painting_boundary = new_painting_boundary; } // Now that we know the full reachable area, let's make sure all doors are // evaluated. for (const Door& door : GD_GetDoors()) { int discard = IsDoorReachable(door.id); door_report_[door.id] = {}; discard = AreRequirementsSatisfied( GetState().requirements.GetDoor(door.id), &door_report_[door.id]); } } const std::set& GetReachableRooms() const { return reachable_rooms_; } const std::map& GetDoorDecisions() const { return door_decisions_; } const std::set& GetSolveablePanels() const { return solveable_panels_; } const std::set& GetReachablePaintings() const { return reachable_paintings_; } const std::map>& GetDoorReports() const { return door_report_; } bool IsPilgrimageDoable() const { return pilgrimage_doable_; } std::string GetPathToRoom(int room_id) const { if (!paths_.count(room_id)) { return ""; } const std::list& path = paths_.at(room_id); std::vector room_names; for (int room_id : path) { room_names.push_back(GD_GetRoom(room_id).name); } return hatkirby::implode(room_names, " -> "); } private: template Decision IsNonGroupedDoorReachable(const T& door_obj) { bool has_item = AP_HasItem(door_obj.ap_item_id); if (!has_item) { for (const ProgressiveRequirement& prog_req : door_obj.progressives) { if (AP_HasItem(prog_req.ap_item_id, prog_req.quantity)) { has_item = true; break; } } } return has_item ? kYes : kNo; } Decision AreRequirementsSatisfied( const Requirements& reqs, std::map* report = nullptr) { if (reqs.disabled) { return kNo; } Decision final_decision = kYes; for (int door_id : reqs.doors) { const Door& door_obj = GD_GetDoor(door_id); Decision decision = IsNonGroupedDoorReachable(door_obj); if (report) { (*report)[door_obj.item_name] = (decision == kYes); } if (decision != kYes) { final_decision = decision; } } for (int panel_door_id : reqs.panel_doors) { const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_door_id); Decision decision = IsNonGroupedDoorReachable(panel_door_obj); if (report) { (*report)[AP_GetItemName(panel_door_obj.ap_item_id)] = (decision == kYes); } if (decision != kYes) { final_decision = decision; } } for (int item_id : reqs.items) { bool has_item = AP_HasItem(item_id); if (report) { (*report)[AP_GetItemName(item_id)] = has_item; } if (!has_item) { final_decision = kNo; } } for (int room_id : reqs.rooms) { bool reachable = reachable_rooms_.count(room_id); if (report) { std::string report_name = "Reach \"" + GD_GetRoom(room_id).name + "\""; (*report)[report_name] = reachable; } if (!reachable && final_decision != kNo) { final_decision = kMaybe; } } if (reqs.mastery) { int achievements_accessible = 0; for (int achieve_id : GD_GetAchievementPanels()) { if (solveable_panels_.count(achieve_id)) { achievements_accessible++; if (achievements_accessible >= AP_GetMasteryRequirement()) { break; } } } bool can_mastery = (achievements_accessible >= AP_GetMasteryRequirement()); if (report) { (*report)["Mastery"] = can_mastery; } if (!can_mastery && final_decision != kNo) { final_decision = kMaybe; } } if (reqs.panel_hunt) { int counting_panels_accessible = 0; for (int solved_panel_id : solveable_panels_) { const Panel& solved_panel = GD_GetPanel(solved_panel_id); if (!solved_panel.non_counting) { counting_panels_accessible++; } } bool can_level2 = (counting_panels_accessible >= AP_GetLevel2Requirement() - 1); if (report) { std::string report_name = std::to_string(AP_GetLevel2Requirement()) + " Panels"; (*report)[report_name] = can_level2; } if (!can_level2 && final_decision != kNo) { final_decision = kMaybe; } } return final_decision; } Decision IsDoorReachable_Helper(int door_id) { return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id)); } Decision IsDoorReachable(int door_id) { if (options_.parent) { return options_.parent->IsDoorReachable(door_id); } if (door_decisions_.count(door_id)) { return door_decisions_.at(door_id); } Decision result = IsDoorReachable_Helper(door_id); if (result != kMaybe) { door_decisions_[door_id] = re
doors {
  name: "White Door"
  type: EVENT
  #receivers: "Components/Doors/Door18"
  panels { room: "White Room" name: "WRITE" }
}
doors {
  name: "Black Door"
  type: EVENT
  #receivers: "Components/Doors/Door19"
  panels { room: "Black Room" name: "HERE" }
}
doors {
  name: "Red Door"
  type: EVENT
  #receivers: "Components/Doors/Door20"
  panels { room: "Red Room" name: "SYNONYM" }
}
doors {
  name: "Blue Door"
  type: EVENT
  #receivers: "Components/Doors/Door21"
  panels { room: "Blue Room" name: "DEPLETE" }
}
doors {
  name: "Green Door"
  type: EVENT
  #receivers: "Components/Doors/Door22"
  panels { room: "Green Room" name: "INERT" }
}
doors {
  name: "Yellow Door"
  type: EVENT
  #receivers: "Components/Doors/Door23"
  panels { room: "Yellow Room" name: "WHOLE" }
}
doors {
  name: "Purple Door"
  type: EVENT
  #receivers: "Components/Doors/Door24"
  panels { room: "Purple Room" name: "TIME" }
}
doors {
  name: "Orange Door"
  type: EVENT
  #receivers: "Components/Doors/Door25"
  panels { room: "Orange Room" name: "YOUNG" }
}
doors {
  name: "Tutorial Door"
  type: ITEM_ONLY
  receivers: "Components/Doors/Door"
  panels { room: "Tutorial" name: "<- (1)" }
  panels { room: "Tutorial" name: "<- (2)" }
  panels { room: "Tutorial" name: "<- (3)" }
}
doors {
  name: "Tutorial Panels"
  type: LOCATION_ONLY
  panels { room: "Tutorial" name: "SAY" }
  panels { room: "Tutorial" name: "HIGH" }
  panels { room: "Tutorial" name: "<- (1)" }
  panels { room: "Tutorial" name: "<- (2)" }
  panels { room: "Tutorial" name: "<- (3)" }
  panels { room: "Tutorial" name: "THIS" }
  panels { room: "Tutorial" name: "WRITE" }
  panels { room: "Tutorial" name: "TYPE" }
  panels { room: "Tutorial" name: "SAME" }
  location_room: "Tutorial"
}
doors {
  name: "Main Area Entrance"
  type: EVENT
  panels { room: "Red Blue Room" name: "RIGHTWARD" answer: "word" }
  panels { room: "Red Blue Room" name: "TYPEWRITING" answer: "writing" }
}
doors {
  name: "Whirred Room Entrance"
  type: EVENT
  panels { room: "Red Blue Room" name: "RIGHTWARD" answer: "whirred" }
}
doors {
  name: "Whirred Room Panels"
  type: LOCATION_ONLY
  panels { room: "Whirred Room" name: "TAIPEI" }
  panels { room: "Whirred Room" name: "NAYSAYER" }
  panels { room: "Whirred Room" name: "NAY" }
  panels { room: "Whirred Room" name: "INDEX (1)" }
  panels { room: "Whirred Room" name: "INDEX (2)" }
  location_room: "Whirred Room"
}
doors {
  name: "Poetry Room Entrance"
  type: EVENT
  panels { room: "Red Blue Room" name: "TYPEWRITING" answer: "poetry" }
}
doors {
  name: "Poetry Room Door 1"
  type: EVENT
  panels { room: "Poetry Room 1" name: "ABSORBED" answer: "bed" }
  panels { room: "Poetry Room 1" name: "PRIMORDIAL" answer: "prim" }
  # It has to be the middle two strips of the door.
}
doors {
  name: "Poetry Room Door 2"
  type: EVENT
  panels { room: "Poetry Room 2" name: "NOT THERE" }
}
doors {
  name: "Poetry Room Left"
  type: EVENT
  panels { room: "Poetry Room 3" name: "NOT PRETTY" answer: "ugly" }
}
doors {
  name: "Poetry Room Right"
  type: EVENT
  panels { room: "Poetry Room 3" name: "NOT PRETTY" answer: "prey" }
}
doors {
  name: "Poetry Room Left Left"
  type: EVENT
  panels { room: "Poetry Room Left" name: "NOT TRUE" answer: "false" }
}
doors {
  name: "Poetry Room Left Right"
  type: EVENT
  panels { room: "Poetry Room Left" name: "NOT TRUE" answer: "rue" }
}
doors {
  name: "Poetry Room Right Left"
  type: EVENT
  panels { room: "Poetry Room Right" name: "NOT BETTER" answer: "worse" }
}
doors {
  name: "Poetry Room Right Right"
  type: EVENT
  panels { room: "Poetry Room Right" name: "NOT BETTER" answer: "beer" }
}
doors {
  name: "Poetry Room Panels"
  type: LOCATION_ONLY
  panels { room: "Poetry Room 1" name: "ABSORBED" }
  panels { room: "Poetry Room 1" name: "PRIMORDIAL" }
  panels { room: "Poetry Room 2" name: "NOT" }
  panels { room: "Poetry Room 2" name: "THERE" }
  panels { room: "Poetry Room 2" name: "NOT THERE" }
  panels { room: "Poetry Room 3" name: "NOT" }
  panels { room: "Poetry Room 3" name: "PRETTY" }
  panels { room: "Poetry Room 3" name: "NOT PRETTY" }
  panels { room: "Poetry Room Left" name: "NOT" }
  panels { room: "Poetry Room Left" name: "TRUE" }
  panels { room: "Poetry Room Left" name: "NOT TRUE" }
  panels { room: "Poetry Room Left Left" name: "NOT (1)" }
  panels { room: "Poetry Room Left Left" name: "NOT (2)" }
  panels { room: "Poetry Room Left Left" name: "LEFT" }
  panels { room: "Poetry Room Left Left" name: "NOT NOT LEFT" }
  panels { room: "Poetry Room Left Right" name: "NOT (1)" }
  panels { room: "Poetry Room Left Right" name: "NOT (2)" }
  panels { room: "Poetry Room Left Right" name: "MISS" }
  panels { room: "Poetry Room Left Right" name: "NOT NOT MISS" }
  panels { room: "Poetry Room Right" name: "NOT" }
  panels { room: "Poetry Room Right" name: "BETTER" }
  panels { room: "Poetry Room Right" name: "NOT BETTER" }
  panels { room: "Poetry Room Right Left" name: "NOT (1)" }
  panels { room: "Poetry Room Right Left" name: "NOT (2)" }
  panels { room: "Poetry Room Right Left" name: "TABLET" }
  panels { room: "Poetry Room Right Left" name: "NOT NOT TABLET" }
  panels { room: "Poetry Room Right Right" name: "NOT (1)" }
  panels { room: "Poetry Room Right Right" name: "NOT (2)" }
  panels { room: "Poetry Room Right Right" name: "NOT (3)" }
  panels { room: "Poetry Room Right Right" name: "NOT NOT NOT" }
  location_room: "Poetry Room Right Right"
}
doors {
  name: "Main Area First Row"
  type: LOCATION_ONLY
  panels { room: "Main Area" name: "JUSTICE" }
  panels { room: "Main Area" name: "NOTICE (1)" }
  panels { room: "Main Area" name: "NOTICE (2)" }
  panels { room: "Main Area" name: "NOTICE (3)" }
  panels { room: "Main Area" name: "UNABLE (1)" }
  panels { room: "Main Area" name: "UNABLE (2)" }
  location_room: "Main Area"
}
doors {
  name: "Main Area Second Row"
  type: LOCATION_ONLY
  panels { room: "Main Area" name: "LINEARLY" }
  panels { room: "Main Area" name: "SADDLED" }
  panels { room: "Main Area" name: "PADDING" }
  panels { room: "Main Area" name: "BRINGING" }
  panels { room: "Main Area" name: "THOUSANDS" }
  panels { room: "Main Area" name: "REINDICT" }
  panels { room: "Main Area" name: "LINEAGE" }
  panels { room: "Main Area" name: "TINCTURE" }
  panels { room: "Main Area" name: "IMMATURE" }
  panels { room: "Main Area" name: "THING" }
  location_room: "Main Area"
}
doors {
  name: "Main Area Third Row"
  type: LOCATION_ONLY
  panels { room: "Main Area" name: "SOME" }
  panels { room: "Main Area" name: "HALFTIME (1)" }
  panels { room: "Main Area" name: "HALFTIME (2)" }
  panels { room: "Main Area" name: "QUARTERBACK" }
  panels { room: "Main Area" name: "NORTHERN" }
  panels { room: "Main Area" name: "INMATE" }
  panels { room: "Main Area" name: "NOTCHES" }
  panels { room: "Main Area" name: "VIOLET (1)" }
  panels { room: "Main Area" name: "VIOLET (2)" }
  panels { room: "Main Area" name: "NONSENSE" }
  panels { room: "Main Area" name: "DISTANT" }
  panels { room: "Main Area" name: "TIGHT (1)" }
  panels { room: "Main Area" name: "TIGHT (2)" }
  panels { room: "Main Area" name: "DISCARD" }
  panels { room: "Main Area" name: "PASSPORT" }
  panels { room: "Main Area" name: "PORT" }
  panels { room: "Main Area" name: "STORMS" }
  panels { room: "Main Area" name: "MS" }
  location_room: "Main Area"
}
doors {
  name: "Main Area Fourth Row"
  type: LOCATION_ONLY
  panels { room: "Main Area" name: "SOUNDBITE" }
  panels { room: "Main Area" name: "BORED" }
  panels { room: "Main Area" name: "VOCALIZE" }
  panels { room: "Main Area" name: "VOICEMAIL" }
  panels { room: "Main Area" name: "MIXTURE" }
  panels { room: "Main Area" name: "PEAT" }
  panels { room: "Main Area" name: "SHUFFLEBOARD" }
  panels { room: "Main Area" name: "BLENDING" }
  panels { room: "Main Area" name: "FLIPPER" }
  panels { room: "Main Area" name: "PANT" }
  panels { room: "Main Area" name: "BACKFIRES" }
  panels { room: "Main Area" name: "DRAW" }
  panels { room: "Main Area" name: "OLDTIMER" }
  panels { room: "Main Area" name: "EMULATE" }
  panels { room: "Main Area" name: "CHICKEN" }
  panels { room: "Main Area" name: "PLUMAGED" }
  panels { room: "Main Area" name: "BOY (1)" }
  panels { room: "Main Area" name: "BOY (2)" }
  location_room: "Main Area"
}
doors {
  name: "Main Area Fifth Row"
  type: LOCATION_ONLY
  panels { room: "Main Area" name: "SAGE" }
  panels { room: "Main Area" name: "LIKEABLE" }
  panels { room: "Main Area" name: "MEANINGFULLY" }
  panels { room: "Main Area" name: "MORE" }
  panels { room: "Main Area" name: "MOUTHPIECE" }
  panels { room: "Main Area" name: "RAMPART" }
  panels { room: "Main Area" name: "INJURY" }
  panels { room: "Main Area" name: "NUMERATOR" }
  panels { room: "Main Area" name: "TYPEWRITING" }
  panels { room: "Main Area" name: "WHIRRED" }
  panels { room: "Main Area" name: "BOOMBOX" }
  panels { room: "Main Area" name: "STEREO" }
  panels { room: "Main Area" name: "KINDRED" }
  panels { room: "Main Area" name: "GEM" }
  panels { room: "Main Area" name: "GEIGER" }
  panels { room: "Main Area" name: "COUNTER" }
  panels { room: "Main Area" name: "HORSEMAN" }
  panels { room: "Main Area" name: "RATHER" }
  panels { room: "Main Area" name: "DEAR" }
  panels { room: "Main Area" name: "COWBOY" }
  panels { room: "Main Area" name: "HEIFER" }
  panels { room: "Main Area" name: "ANYMORE" }
  panels { room: "Main Area" name: "LIKE" }
  panels { room: "Main Area" name: "NEEDLESS" }
  panels { room: "Main Area" name: "RESTLESS" }
  location_room: "Main Area"
}
doors {
  name: "Main Area Exit"
  type: EVENT
  panels { room: "Main Area" name: "JUSTICE" }
  panels { room: "Main Area" name: "NOTICE (1)" }
  panels { room: "Main Area" name: "NOTICE (2)" }
  panels { room: "Main Area" name: "NOTICE (3)" }
  panels { room: "Main Area" name: "UNABLE (1)" }
  panels { room: "Main Area" name: "UNABLE (2)" }
  panels { room: "Main Area" name: "LINEARLY" }
  panels { room: "Main Area" name: "SADDLED" }
  panels { room: "Main Area" name: "PADDING" }
  panels { room: "Main Area" name: "BRINGING" }
  panels { room: "Main Area" name: "THOUSANDS" }
  panels { room: "Main Area" name: "REINDICT" }
  panels { room: "Main Area" name: "LINEAGE" }
  panels { room: "Main Area" name: "TINCTURE" }
  panels { room: "Main Area" name: "IMMATURE" }
  panels { room: "Main Area" name: "THING" }
  panels { room: "Main Area" name: "SOME" }
  panels { room: "Main Area" name: "HALFTIME (1)" }
  panels { room: "Main Area" name: "HALFTIME (2)" }
  panels { room: "Main Area" name: "QUARTERBACK" }
  panels { room: "Main Area" name: "NORTHERN" }
  panels { room: "Main Area" name: "INMATE" }
  panels { room: "Main Area" name: "NOTCHES" }
  panels { room: "Main Area" name: "VIOLET (1)" }
  panels { room: "Main Area" name: "VIOLET (2)" }
  panels { room: "Main Area" name: "NONSENSE" }
  panels { room: "Main Area" name: "DISTANT" }
  panels { room: "Main Area" name: "TIGHT (1)" }
  panels { room: "Main Area" name: "TIGHT (2)" }
  panels { room: "Main Area" name: "DISCARD" }
  panels { room: "Main Area" name: "PASSPORT" }
  panels { room: "Main Area" name: "PORT" }
  panels { room: "Main Area" name: "STORMS" }
  panels { room: "Main Area" name: "MS" }
  panels { room: "Main Area" name: "SOUNDBITE" }
  panels { room: "Main Area" name: "BORED" }
  panels { room: "Main Area" name: "VOCALIZE" }
  panels { room: "Main Area" name: "VOICEMAIL" }
  panels { room: "Main Area" name: "MIXTURE" }
  panels { room: "Main Area" name: "PEAT" }
  panels { room: "Main Area" name: "SHUFFLEBOARD" }
  panels { room: "Main Area" name: "BLENDING" }
  panels { room: "Main Area" name: "FLIPPER" }
  panels { room: "Main Area" name: "PANT" }
  panels { room: "Main Area" name: "BACKFIRES" }
  panels { room: "Main Area" name: "DRAW" }
  panels { room: "Main Area" name: "OLDTIMER" }
  panels { room: "Main Area" name: "EMULATE" }
  panels { room: "Main Area" name: "CHICKEN" }
  panels { room: "Main Area" name: "PLUMAGED" }
  panels { room: "Main Area" name: "BOY (1)" }
  panels { room: "Main Area" name: "BOY (2)" }
  panels { room: "Main Area" name: "SAGE" }
  panels { room: "Main Area" name: "LIKEABLE" }
  panels { room: "Main Area" name: "MEANINGFULLY" }
  panels { room: "Main Area" name: "MORE" }
  panels { room: "Main Area" name: "MOUTHPIECE" }
  panels { room: "Main Area" name: "RAMPART" }
  panels { room: "Main Area" name: "INJURY" }
  panels { room: "Main Area" name: "NUMERATOR" }
  panels { room: "Main Area" name: "TYPEWRITING" }
  panels { room: "Main Area" name: "WHIRRED" }
  panels { room: "Main Area" name: "BOOMBOX" }
  panels { room: "Main Area" name: "STEREO" }
  panels { room: "Main Area" name: "KINDRED" }
  panels { room: "Main Area" name: "GEM" }
  panels { room: "Main Area" name: "GEIGER" }
  panels { room: "Main Area" name: "COUNTER" }
  panels { room: "Main Area" name: "HORSEMAN" }
  panels { room: "Main Area" name: "RATHER" }
  panels { room: "Main Area" name: "DEAR" }
  panels { room: "Main Area" name: "COWBOY" }
  panels { room: "Main Area" name: "HEIFER" }
  panels { room: "Main Area" name: "ANYMORE" }
  panels { room: "Main Area" name: "LIKE" }
  panels { room: "Main Area" name: "NEEDLESS" }
  panels { room: "Main Area" name: "RESTLESS" }
}
doors {
  name: "Mastery"
  type: EVENT
  panels { room: "Last Room" name: "BLEAT" }
  panels { room: "Last Room" name: "JARGON" }
  panels { room: "Last Room" name: "JARGON BLEAT" }
  panels { room: "Last Room" name: "BRAG" }
}