undo/redo most tools that draw terrain

This commit is contained in:
2025-06-02 14:24:36 -05:00
parent 862fff6c2f
commit 03c53588e8
5 changed files with 104 additions and 14 deletions

View File

@@ -709,6 +709,18 @@ static bool handle_rb_action(location the_point, bool option_hit) {
return false;
}
stroke_ter_changes_t current_stroke_changes;
std::string current_stroke_type;
void commit_stroke() {
if(!current_stroke_changes.empty()){
undo_list.add(action_ptr(new aDrawTerrain("Draw Terrain (" + current_stroke_type + ")", current_stroke_changes)));
update_edit_menu();
current_stroke_changes.clear();
current_stroke_type = "";
}
}
static bool handle_terrain_action(location the_point, bool ctrl_hit) {
cArea* cur_area = get_current_area();
std::shared_ptr<cAction> undo_action = nullptr;
@@ -751,8 +763,16 @@ static bool handle_terrain_action(location the_point, bool ctrl_hit) {
erasing_mode = terrain_matches(spot_hit.x, spot_hit.y, current_terrain_type);
mouse_button_held = true;
}
if(erasing_mode) set_terrain(spot_hit,current_ground);
else set_terrain(spot_hit,current_terrain_type);
if(erasing_mode){
stroke_ter_changes_t changes;
set_terrain(spot_hit,current_ground,changes);
undo_action.reset(new aDrawTerrain("Erase Terrain", changes));
}
else{
// This could be an ongoing stroke
current_stroke_type = "Pencil";
set_terrain(spot_hit,current_terrain_type,current_stroke_changes);
}
break;
case MODE_ROOM_RECT: case MODE_SET_TOWN_RECT: case MODE_HOLLOW_RECT: case MODE_FILLED_RECT:
@@ -844,22 +864,27 @@ static bool handle_terrain_action(location the_point, bool ctrl_hit) {
case MODE_LARGE_PAINTBRUSH:
mouse_button_held = true;
current_stroke_type = "Lg. Paintbrush";
change_circle_terrain(spot_hit,4,current_terrain_type,20);
break;
case MODE_SMALL_PAINTBRUSH:
mouse_button_held = true;
current_stroke_type = "Sm. Paintbrush";
change_circle_terrain(spot_hit,1,current_terrain_type,20);
break;
case MODE_LARGE_SPRAYCAN:
mouse_button_held = true;
current_stroke_type = "Lg. Spraycan";
change_circle_terrain(spot_hit,4,current_terrain_type,1);
break;
case MODE_SMALL_SPRAYCAN:
mouse_button_held = true;
current_stroke_type = "Sm. Spraycan";
change_circle_terrain(spot_hit,2,current_terrain_type,1);
break;
case MODE_ERASER: // erase
change_circle_terrain(spot_hit,2,current_ground,20);
current_stroke_type = "Eraser";
mouse_button_held = true;
break;
case MODE_FLOOD_FILL:
@@ -2031,6 +2056,7 @@ void handle_scroll(const sf::Event& event) {
}
}
// This is used by various paintbrushes/the spraycans
void change_circle_terrain(location center,short radius,ter_num_t terrain_type,short probability) {
// prob is 0 - 20, 0 no, 20 always
location l;
@@ -2041,7 +2067,7 @@ void change_circle_terrain(location center,short radius,ter_num_t terrain_type,s
l.x = i;
l.y = j;
if((dist(center,l) <= radius) && (get_ran(1,1,20) <= probability))
set_terrain(l,terrain_type);
set_terrain(l,terrain_type,current_stroke_changes);
}
}
@@ -2049,6 +2075,7 @@ void change_rect_terrain(rectangle r,ter_num_t terrain_type,short probability,bo
// prob is 0 - 20, 0 no, 20 always
location l;
cArea* cur_area = get_current_area();
stroke_ter_changes_t changes;
for(short i = 0; i < cur_area->max_dim; i++)
for(short j = 0; j < cur_area->max_dim; j++) {
@@ -2057,8 +2084,11 @@ void change_rect_terrain(rectangle r,ter_num_t terrain_type,short probability,bo
if((i >= r.left) && (i <= r.right) && (j >= r.top) && (j <= r.bottom)
&& (!hollow || (i == r.left) || (i == r.right) || (j == r.top) || (j == r.bottom))
&& ((hollow) || (get_ran(1,1,20) <= probability)))
set_terrain(l,terrain_type);
set_terrain(l,terrain_type,changes);
}
undo_list.add(action_ptr(new aDrawTerrain("Change Rectangle Terrain", changes)));
update_edit_menu();
}
void flood_fill_terrain(location start, ter_num_t terrain_type) {
@@ -2084,6 +2114,7 @@ void flood_fill_terrain(location start, ter_num_t terrain_type) {
if(check == to_replace && !visited.count(adj_loc))
to_visit.push(adj_loc);
}
// TODO store it as a stroke
cur_area->terrain(this_loc.x, this_loc.y) = terrain_type;
}
}
@@ -2103,6 +2134,7 @@ void frill_up_terrain() {
terrain_type = k;
}
// TODO store it as a stroke
cur_area->terrain(i,j) = terrain_type;
}
draw_terrain();
@@ -2120,6 +2152,7 @@ void unfrill_terrain() {
if(ter.frill_for >= 0)
terrain_type = ter.frill_for;
// TODO store it as a stroke
cur_area->terrain(i,j) = terrain_type;
}
draw_terrain();
@@ -2161,14 +2194,20 @@ static const std::array<location,5> trim_diffs = {{
loc(0,0), loc(-1,0), loc(1,0), loc(0,-1), loc(0,1)
}};
void set_terrain(location l,ter_num_t terrain_type) {
void set_terrain(location l,ter_num_t terrain_type,stroke_ter_changes_t& stroke_changes) {
cArea* cur_area = get_current_area();
if(!cur_area->is_on_map(l)) return;
ter_num_t old = cur_area->terrain(l.x,l.y);
auto revert = [&cur_area, l, old]() {
if(stroke_changes.find(l) != stroke_changes.end()){
// Don't successively overwrite the original terrain value of a tile in one stroke
old = stroke_changes[l].old_num;
}
stroke_changes[l] = {old, terrain_type};
auto revert = [&cur_area, &stroke_changes, l, old]() {
cur_area->terrain(l.x,l.y) = old;
stroke_changes.erase(l);
};
const cTerrain& new_ter = scenario.ter_types[terrain_type];
@@ -2196,8 +2235,16 @@ void set_terrain(location l,ter_num_t terrain_type) {
while(obj_loc.y > 0) l2.y-- , obj_loc.y--;
for(short i = 0; i < obj_dim.x; i++)
for(short j = 0; j < obj_dim.y; j++){
if(!cur_area->is_on_map(loc(l2.x + i, l2.y + j))) continue;
cur_area->terrain(l2.x + i,l2.y + j) = find_object_part(q,i,j,terrain_type);
location temp = loc(l2.x + i, l2.y + j);
if(!cur_area->is_on_map(temp)) continue;
ter_num_t object_part = find_object_part(q,i,j,terrain_type);
ter_num_t part_old = cur_area->terrain(temp.x, temp.y);
if(stroke_changes.find(temp) != stroke_changes.end()){
part_old = stroke_changes[temp].old_num;
}
stroke_changes[temp] = {part_old, object_part};
cur_area->terrain(temp.x,temp.y) = object_part;
}
}
@@ -2221,6 +2268,7 @@ void set_terrain(location l,ter_num_t terrain_type) {
// Otherwise it might overwrite important things, like buildings or forests.
if(ter_there != scenario.get_ter_from_ground(ter_type.trim_ter))
continue;
stroke_changes[l3] = {ter_there, new_ter};
cur_area->terrain(l3.x,l3.y) = new_ter;
}
}
@@ -2230,7 +2278,7 @@ void set_terrain(location l,ter_num_t terrain_type) {
for(location d : trim_diffs) {
location l3(l.x+d.x, l.y+d.y);
if(!cur_area->is_on_map(l3)) continue;
adjust_space(l3);
adjust_space(l3, stroke_changes);
}
cTerrain& ter = scenario.ter_types[terrain_type];
@@ -2278,7 +2326,7 @@ void set_terrain(location l,ter_num_t terrain_type) {
}
}
void adjust_space(location l) {
void adjust_space(location l, stroke_ter_changes_t& stroke_changes) {
bool needed_change = false;
location l2;
cArea* cur_area = get_current_area();
@@ -2364,6 +2412,11 @@ void adjust_space(location l) {
ter_num_t replace = scenario.get_trim_terrain(main_ground, trim_ground, need_trim);
if(replace != 90) { // If we got 90 back, the required trim doesn't exist.
needed_change = true;
ter_change_t change = {cur_area->terrain(l.x, l.y), replace};
if(stroke_changes.find(l) != stroke_changes.end()){
change.old_num = stroke_changes[l].old_num;
}
stroke_changes[l] = change;
cur_area->terrain(l.x,l.y) = replace;
}
}
@@ -2372,10 +2425,9 @@ void adjust_space(location l) {
for(location d : trim_diffs) {
if(d.x == 0 && d.y == 0) continue;
location l2(l.x+d.x, l.y+d.y);
adjust_space(l2);
adjust_space(l2, stroke_changes);
}
}
}
bool place_item(location spot_hit,short which_item,bool property,bool always,short odds) {
@@ -2452,12 +2504,14 @@ void set_special(location spot_hit) {
}
auto& specials = get_current_area()->special_locs;
for(short x = 0; x < specials.size(); x++)
// Edit the node of the encounter already on the space
if(specials[x] == spot_hit && specials[x].spec >= 0) {
int spec = edit_special_num(editing_town ? 2 : 1,specials[x].spec);
if(spec >= 0) specials[x].spec = spec;
return;
}
for(short x = 0; x <= specials.size(); x++) {
// Find/create an empty encounter spot
if(x == specials.size())
specials.emplace_back(-1,-1,-1);
if(specials[x].spec < 0) {

View File

@@ -1,6 +1,7 @@
#include "scen.global.hpp"
#include "tools/undo.hpp"
#include "scen.undo.hpp"
void handle_close_terrain_view(eScenMode new_mode);
void restore_editor_state(bool first_time = false);
@@ -22,8 +23,9 @@ void change_rect_terrain(rectangle r,ter_num_t terrain_type,short probability,bo
void flood_fill_terrain(location start, ter_num_t terrain_type);
void frill_up_terrain();
void unfrill_terrain();
void set_terrain(location l,ter_num_t terrain_type);
void adjust_space(location l);
void set_terrain(location l,ter_num_t terrain_type,stroke_ter_changes_t& stroke_changes);
void adjust_space(location l,stroke_ter_changes_t& stroke_changes);
void commit_stroke();
bool is_lava(short x,short y);
ter_num_t coord_to_ter(short x,short y);
bool place_item(location spot_hit,short which_item,bool property,bool always,short odds);

View File

@@ -436,6 +436,7 @@ void handle_one_event(const sf::Event& event) {
break;
case sf::Event::MouseButtonReleased:
commit_stroke();
mouse_button_held = false;
break;

View File

@@ -91,3 +91,18 @@ bool aNewTown::redo_me() {
aNewTown::~aNewTown() {
if(!isDone()) delete theTown;
}
bool aDrawTerrain::undo_me() {
cArea* cur_area = get_current_area();
for(auto change : changes){
cur_area->terrain(change.first.x, change.first.y) = change.second.old_num;
}
return true;
}
bool aDrawTerrain::redo_me() {
cArea* cur_area = get_current_area();
for(auto change : changes){
cur_area->terrain(change.first.x, change.first.y) = change.second.new_num;
}
return true;
}

View File

@@ -12,6 +12,13 @@ struct area_ref_t {
location where;
};
struct ter_change_t {
ter_num_t old_num;
ter_num_t new_num;
};
typedef std::map<location,ter_change_t,loc_compare> stroke_ter_changes_t;
// Action that modified town or outdoor terrain, so we should show the modified area when undoing or redoing
class cTerrainAction : public cAction {
public:
@@ -48,4 +55,15 @@ private:
bool editing_town;
};
class aDrawTerrain : public cTerrainAction {
public:
aDrawTerrain(std::string name, stroke_ter_changes_t stroke_changes) :
cTerrainAction(name, stroke_changes.begin()->first), // Use arbitrary changed tile as site of change
changes(stroke_changes) {}
bool undo_me() override;
bool redo_me() override;
private:
const stroke_ter_changes_t changes;
};
#endif