Merge pull request #543 from NQNStudios:editor-bounds
Scenedit Quality of life: be helpful when scrolling past boundaries I've implemented 2 features here: * When scrolling past the boundaries of the current outdoor section, you will get a yes/no prompt which can load the adjacent section for you at the corresponding center position. * When scrolling past the boundaries (literal, not the changeable rectangle) of the current town, the editor will ask if you want to jump to the town's entrance in the outdoors. If there are more than one, you can choose. I did this because I need to be able to find town entrances in the built-in scenarios so I can debug things.
This commit is contained in:
11
rsrc/dialogs/shift-outdoor-section.xml
Normal file
11
rsrc/dialogs/shift-outdoor-section.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
|
||||
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
|
||||
<dialog defbtn='no'>
|
||||
<button name='no' type='regular' def-key='n' top='39' left='244'>No</button>
|
||||
<button name='yes' type='regular' def-key='y' top='39' left='178'>Yes</button>
|
||||
<pict type='dlog' num='11' top='9' left='9'/>
|
||||
<text top='4' left='51' width='251' height='32'>
|
||||
Shift to this outdoor section?
|
||||
</text>
|
||||
<text name='out-sec' relative='pos-in pos' rel-anchor='prev' top='4' left='0'></text>
|
||||
</dialog>
|
11
rsrc/dialogs/shift-town-entrance.xml
Normal file
11
rsrc/dialogs/shift-town-entrance.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
|
||||
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
|
||||
<dialog defbtn='no'>
|
||||
<button name='no' type='regular' def-key='n' top='39' left='244'>No</button>
|
||||
<button name='yes' type='regular' def-key='y' top='39' left='178'>Yes</button>
|
||||
<pict type='dlog' num='11' top='9' left='9'/>
|
||||
<text top='4' left='51' width='251' height='32'>
|
||||
Shift to this town's entrance in this outdoor section?
|
||||
</text>
|
||||
<text name='out-sec' relative='pos-in pos' rel-anchor='prev' top='4' left='0'></text>
|
||||
</dialog>
|
@@ -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);
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -577,3 +577,17 @@ void cScenario::readFrom(const cTagFile& file){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<town_entrance_t> cScenario::find_town_entrances(int town_num) {
|
||||
std::vector<town_entrance_t> 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<int>(entrance.spec)});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return matching_entrances;
|
||||
}
|
@@ -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<eItemType>& 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<town_entrance_t> find_town_entrances(int town_num = -1);
|
||||
|
||||
void reset_version();
|
||||
explicit cScenario();
|
||||
~cScenario();
|
||||
|
@@ -5,6 +5,8 @@
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#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<std::string>(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_entrance_t> 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<std::string>(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<std::string> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user