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 @@
+
+
+
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 @@
+
+
+
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);