diff --git a/rsrc/dialogs/shift-outdoor-section.xml b/rsrc/dialogs/shift-outdoor-section.xml new file mode 100644 index 00000000..9e9d82f5 --- /dev/null +++ b/rsrc/dialogs/shift-outdoor-section.xml @@ -0,0 +1,11 @@ + + + + + + + + Shift to this outdoor section? + + + diff --git a/rsrc/dialogs/shift-town-entrance.xml b/rsrc/dialogs/shift-town-entrance.xml new file mode 100644 index 00000000..5303905e --- /dev/null +++ b/rsrc/dialogs/shift-town-entrance.xml @@ -0,0 +1,11 @@ + + + + + + + + Shift to this town's entrance in this outdoor section? + + + diff --git a/src/dialogxml/dialogs/strchoice.hpp b/src/dialogxml/dialogs/strchoice.hpp index 53bcab64..f11e9163 100644 --- a/src/dialogxml/dialogs/strchoice.hpp +++ b/src/dialogxml/dialogs/strchoice.hpp @@ -52,7 +52,8 @@ public: cDialog* operator->(); /// Show the dialog. /// @param selectedIndex The index of the string that should be initially selected when the dialog is shown. - /// @return The index of the newly selected string; if the user cancelled, this will be equal to selectedIndex. + /// @return The index of the newly selected string; if the user cancelled, this will be equal to the initial + /// selectedIndex you provide. (So, pass -1 or something to signify that cancelling means the result is invalid.) /// If initialized from an iterator range, this will be relative to begin. size_t show(size_t selectedIndex); }; diff --git a/src/game/boe.main.cpp b/src/game/boe.main.cpp index ed370ee7..04a96748 100644 --- a/src/game/boe.main.cpp +++ b/src/game/boe.main.cpp @@ -440,33 +440,27 @@ static void handle_scenario_args() { } // Try to put the party in an outdoor section from which you can enter the town -- // so when you leave, you'll hopefully be in the right place. - bool found_entrance = false; - for(int x = 0; x < univ.scenario.outdoors.width(); ++x){ - for(int y = 0; y < univ.scenario.outdoors.height(); ++y){ - for(spec_loc_t& entrance : univ.scenario.outdoors[x][y]->city_locs){ - if(entrance.spec == *scen_arg_town){ - // Very janky but I don't know how else to make it properly load the right sections and set i_w_c - while(univ.party.outdoor_corner.x > x){ - shift_universe_left(); - } - while(univ.party.outdoor_corner.x < x){ - shift_universe_right(); - } - while(univ.party.outdoor_corner.y > y){ - shift_universe_up(); - } - while(univ.party.outdoor_corner.y < y){ - shift_universe_down(); - } - outd_move_party(local_to_global(entrance), true); - - found_entrance = true; - break; - } - } - if(found_entrance) break; + auto town_entrances = univ.scenario.find_town_entrances(*scen_arg_town); + if(!town_entrances.empty()){ + // When there are multiple entrances, this part of the code shouldn't matter, + // but also won't hurt. + town_entrance_t first_entrance_found = town_entrances[0]; + int x = first_entrance_found.out_sec.x; + int y = first_entrance_found.out_sec.y; + // Very janky but I don't know how else to make it properly load the right sections and set i_w_c + while(univ.party.outdoor_corner.x > x){ + shift_universe_left(); } - if(found_entrance) break; + while(univ.party.outdoor_corner.x < x){ + shift_universe_right(); + } + while(univ.party.outdoor_corner.y > y){ + shift_universe_up(); + } + while(univ.party.outdoor_corner.y < y){ + shift_universe_down(); + } + outd_move_party(local_to_global(first_entrance_found.loc), true); } short town_entrance = 0; diff --git a/src/scenario/scenario.cpp b/src/scenario/scenario.cpp index b32bf0be..47cf681e 100644 --- a/src/scenario/scenario.cpp +++ b/src/scenario/scenario.cpp @@ -577,3 +577,17 @@ void cScenario::readFrom(const cTagFile& file){ } } } + +std::vector cScenario::find_town_entrances(int town_num) { + std::vector matching_entrances; + for(int x = 0; x < outdoors.width(); ++x){ + for(int y = 0; y < outdoors.height(); ++y){ + for(spec_loc_t& entrance : outdoors[x][y]->city_locs){ + if(town_num == -1 || entrance.spec == town_num){ + matching_entrances.push_back({{x, y}, {entrance.x, entrance.y}, static_cast(entrance.spec)}); + } + } + } + } + return matching_entrances; +} \ No newline at end of file diff --git a/src/scenario/scenario.hpp b/src/scenario/scenario.hpp index 2e7d8029..88538ca2 100644 --- a/src/scenario/scenario.hpp +++ b/src/scenario/scenario.hpp @@ -39,6 +39,13 @@ struct scenario_header_flags { enum eContentRating {G, PG, R, NC17}; +// Used for finding town entrances in the outdoors +struct town_entrance_t { + location out_sec; + location loc; + int town; +}; + class cScenario { public: class cItemStorage { @@ -117,6 +124,10 @@ public: cItem return_treasure(int loot, bool allow_junk_treasure = false) const; cItem pull_item_of_type(unsigned int loot_max,short min_val,short max_val,const std::vector& types,bool allow_junk_treasure=false) const; + // Debugging/Editing helper: find town entrances in the outdoors. When town_num is specified, only return entrances + // to the town with that number + std::vector find_town_entrances(int town_num = -1); + void reset_version(); explicit cScenario(); ~cScenario(); diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp index 052093b3..38e3cd38 100644 --- a/src/scenedit/scen.actions.cpp +++ b/src/scenedit/scen.actions.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "scen.global.hpp" #include "scenario/scenario.hpp" #include "gfx/render_shapes.hpp" @@ -23,6 +25,7 @@ #include "tools/cursors.hpp" #include "dialogxml/widgets/scrollbar.hpp" #include "dialogxml/dialogs/strdlog.hpp" +#include "dialogxml/dialogs/strchoice.hpp" #include "dialogxml/dialogs/choicedlog.hpp" #ifndef MSBUILD_GITREV #include "tools/gitrev.hpp" @@ -236,10 +239,7 @@ static bool handle_lb_action(location the_point) { file_to_load = nav_get_scenario(); if(!file_to_load.empty() && load_scenario(file_to_load, scenario)) { set_current_town(scenario.last_town_edited); - cur_out = scenario.last_out_edited; - current_terrain = scenario.outdoors[cur_out.x][cur_out.y]; - overall_mode = MODE_MAIN_SCREEN; - set_up_main_screen(); + set_current_out(scenario.last_out_edited); } else if(!file_to_load.empty()) // If we tried to load but failed, the scenario record is messed up, so boot to start screen. set_up_start_screen(); @@ -277,9 +277,7 @@ static bool handle_lb_action(location the_point) { case LB_LOAD_OUT: spot_hit = pick_out(cur_out, scenario); if(spot_hit != cur_out) { - cur_out = spot_hit; - current_terrain = scenario.outdoors[cur_out.x][cur_out.y]; - set_up_main_screen(); + set_current_out(spot_hit); } break; case LB_EDIT_OUT: @@ -1153,34 +1151,38 @@ static bool handle_terrain_action(location the_point, bool ctrl_hit) { return true; } bool need_redraw = false; - if((the_point.in(border_rect[0])) & (cen_y > (editing_town ? 4 : 3))) { - cen_y--; + if((the_point.in(border_rect[0]))) { if(ctrl_hit) cen_y = ((editing_town) ? 4 : 3); + else + handle_editor_screen_shift(0, -1); need_redraw = true; mouse_button_held = true; } - if((the_point.in(border_rect[1])) & (cen_x > (editing_town ? 4 : 3))) { - cen_x--; + if((the_point.in(border_rect[1]))) { if(ctrl_hit) cen_x = ((editing_town) ? 4 : 3); + else + handle_editor_screen_shift(-1, 0); need_redraw = true; mouse_button_held = true; } auto max_dim = cur_area->max_dim - 5; // This allows you to see a strip of terrain from the adjacent sector when editing outdoors if(!editing_town) max_dim++; - if((the_point.in(border_rect[2])) && (cen_y < max_dim)) { - cen_y++; + if((the_point.in(border_rect[2]))) { if(ctrl_hit) cen_y = max_dim; + else + handle_editor_screen_shift(0, 1); need_redraw = true; mouse_button_held = true; } - if((the_point.in(border_rect[3])) && (cen_x < max_dim)) { - cen_x++; + if((the_point.in(border_rect[3]))) { if(ctrl_hit) cen_x = max_dim; + else + handle_editor_screen_shift(1, 0); need_redraw = true; mouse_button_held = true; } @@ -1781,13 +1783,119 @@ void handle_keystroke(sf::Event event) { mouse_button_held = false; } +bool handle_outdoor_sec_shift(int dx, int dy){ + if(editing_town) return false; + int new_x = cur_out.x + dx; + int new_y = cur_out.y + dy; + if(new_x < 0) return true; + if(new_x >= scenario.outdoors.width()) return true; + if(new_y < 0) return true; + if(new_y >= scenario.outdoors.height()) return true; + + cChoiceDlog shift_prompt("shift-outdoor-section", {"yes", "no"}); + location new_out_sec = { new_x, new_y }; + shift_prompt->getControl("out-sec").setText(boost::lexical_cast(new_out_sec)); + + if(shift_prompt.show() == "yes"){ + int last_cen_x = cen_x; + int last_cen_y = cen_y; + set_current_out(new_out_sec); + // match the terrain view to where we were + start_out_edit(); + if(dx < 0) { + cen_x = get_current_area()->max_dim - 4; + }else if(dx > 0){ + cen_x = 3; + }else{ + cen_x = last_cen_x; + } + if(dy < 0){ + cen_y = get_current_area()->max_dim - 4; + }else if(dy > 0){ + cen_y = 3; + }else{ + cen_y = last_cen_y; + } + redraw_screen(); + } + return true; +} + +void handle_editor_screen_shift(int dx, int dy) { + int min = (editing_town ? 4 : 3); + int max = get_current_area()->max_dim - 5; + if(!editing_town) max++; + bool out_of_bounds = false; + if(cen_x + dx < min){ + // In outdoors, prompt whether to swap to the next section west + if(handle_outdoor_sec_shift(-1, 0)) return; + out_of_bounds = true; + }else if(cen_x + dx > max){ + // In outdoors, prompt whether to swap to the next section east + if(handle_outdoor_sec_shift(1, 0)) return; + out_of_bounds = true; + }else if(cen_y + dy < min){ + // In outdoors, prompt whether to swap to the next section north + if(handle_outdoor_sec_shift(0, -1)) return; + out_of_bounds = true; + }else if(cen_y + dy > max){ + // In outdoors, prompt whether to swap to the next section south + if(handle_outdoor_sec_shift(0, 1)) return; + out_of_bounds = true; + } + + if(out_of_bounds){ + // In town, prompt whether to go back to outdoor entrance location + std::vector town_entrances = scenario.find_town_entrances(cur_town); + if(town_entrances.size() == 1){ + town_entrance_t only_entrance = town_entrances[0]; + cChoiceDlog shift_prompt("shift-town-entrance", {"yes", "no"}); + shift_prompt->getControl("out-sec").setText(boost::lexical_cast(only_entrance.out_sec)); + + if(shift_prompt.show() == "yes"){ + set_current_out(only_entrance.out_sec); + start_out_edit(); + cen_x = only_entrance.loc.x; + cen_y = only_entrance.loc.y; + redraw_screen(); + return; + } + }else if(town_entrances.size() > 1){ + std::vector entrance_strings; + for(town_entrance_t entrance : town_entrances){ + std::ostringstream sstr; + sstr << "Entrance in section " << entrance.out_sec << " at " << entrance.loc; + entrance_strings.push_back(sstr.str()); + + } + cStringChoice dlog(entrance_strings, "Shift to one of this town's entrances in the outdoors?"); + size_t choice = dlog.show(-1); + if(choice >= 0 && choice < town_entrances.size()){ + town_entrance_t entrance = town_entrances[choice]; + set_current_out(entrance.out_sec); + start_out_edit(); + cen_x = entrance.loc.x; + cen_y = entrance.loc.y; + redraw_screen(); + return; + } + } + } + + cen_x = minmax(min, max, cen_x + dx); + cen_y = minmax(min, max, cen_y + dy); +} + void handle_scroll(const sf::Event& event) { location pos { translate_mouse_coordinates({event.mouseMove.x,event.mouseMove.y}) }; int amount = event.mouseWheel.delta; if(overall_mode < MODE_MAIN_SCREEN && pos.in(terrain_rect)) { if(kb.isCtrlPressed()) - cen_x = minmax(4, town->max_dim - 5, cen_x - amount); - else cen_y = minmax(4, town->max_dim - 5, cen_y - amount); + handle_editor_screen_shift(-amount, 0); + else handle_editor_screen_shift(0, -amount); + + draw_terrain(); + place_location(); } } diff --git a/src/scenedit/scen.actions.hpp b/src/scenedit/scen.actions.hpp index 74f20fed..b5b115d6 100644 --- a/src/scenedit/scen.actions.hpp +++ b/src/scenedit/scen.actions.hpp @@ -8,6 +8,7 @@ void flash_rect(rectangle to_flash); void swap_terrain(); void set_new_terrain(ter_num_t selected_terrain); void handle_keystroke(sf::Event event); +void handle_editor_screen_shift(int dx, int dy); void handle_scroll(const sf::Event& event); void get_wandering_monst(); void get_town_info(); diff --git a/src/scenedit/scen.appleevents.mm b/src/scenedit/scen.appleevents.mm index 3e089065..b1423ed8 100644 --- a/src/scenedit/scen.appleevents.mm +++ b/src/scenedit/scen.appleevents.mm @@ -52,11 +52,9 @@ void set_up_apple_events() { if(load_scenario(fileName, scenario)) { set_current_town(scenario.last_town_edited); - cur_out = scenario.last_out_edited; - current_terrain = scenario.outdoors[cur_out.x][cur_out.y]; change_made = false; ae_loading = true; - set_up_main_screen(); + set_current_out(scenario.last_out_edited); } return TRUE; } diff --git a/src/scenedit/scen.main.cpp b/src/scenedit/scen.main.cpp index bcfe1774..c9d2de1a 100644 --- a/src/scenedit/scen.main.cpp +++ b/src/scenedit/scen.main.cpp @@ -304,11 +304,9 @@ static void process_args(int argc, char* argv[]) { if(!file.empty()) { if(load_scenario(file, scenario)) { set_current_town(scenario.last_town_edited); - cur_out = scenario.last_out_edited; - current_terrain = scenario.outdoors[cur_out.x][cur_out.y]; change_made = false; ae_loading = true; - set_up_main_screen(); + set_current_out(scenario.last_out_edited); } else { std::cout << "Failed to load scenario: " << file << std::endl; } @@ -449,11 +447,8 @@ void handle_menu_choice(eMenu item_hit) { if(!file_to_load.empty() && load_scenario(file_to_load, scenario)) { cur_town = scenario.last_town_edited; town = scenario.towns[cur_town]; - cur_out = scenario.last_out_edited; - current_terrain = scenario.outdoors[cur_out.x][cur_out.y]; - overall_mode = MODE_MAIN_SCREEN; change_made = false; - set_up_main_screen(); + set_current_out(scenario.last_out_edited); } else if(!file_to_load.empty()) set_up_start_screen(); // Failed to load file, dump to start undo_list.clear(); diff --git a/src/scenedit/scen.townout.cpp b/src/scenedit/scen.townout.cpp index 58cd5dce..44439bfc 100644 --- a/src/scenedit/scen.townout.cpp +++ b/src/scenedit/scen.townout.cpp @@ -1296,6 +1296,13 @@ void set_current_town(int to) { scenario.last_town_edited = cur_town; } +void set_current_out(location out_sec) { + cur_out = out_sec; + scenario.last_out_edited = cur_out; + current_terrain = scenario.outdoors[cur_out.x][cur_out.y]; + set_up_main_screen(); +} + aNewTown::aNewTown(cTown* t) : cAction("add town") , theTown(t) diff --git a/src/scenedit/scen.townout.hpp b/src/scenedit/scen.townout.hpp index 64b64886..e3f89fce 100644 --- a/src/scenedit/scen.townout.hpp +++ b/src/scenedit/scen.townout.hpp @@ -23,3 +23,4 @@ void edit_placed_item(short which_i); void delete_last_town(); void edit_town_wand(); void set_current_town(int to); +void set_current_out(location out_sec);