undo/redo for drawing/erasing fields

This commit is contained in:
2025-06-14 19:13:03 -05:00
parent 1f9daccf2d
commit eab83c2112
6 changed files with 167 additions and 17 deletions

View File

@@ -353,15 +353,29 @@ cEnumLookup field_names = {
"explored", "wall-force", "wall-fire", "field-antimagic", "cloud-stink", "wall-ice", "wall-blades", "cloud-sleep",
"obj-block", "spec-spot", "field-web", "obj-crate", "obj-barrel", "barr-fire", "barr-force", "field-quickfire",
"sfx-sm-bld", "sfx-med-bld", "sfx-lg-bld", "sfx-sm-slm", "sfx-lg-slm", "sfx-ash", "sfx-bone", "sfx-rock",
"barr-cage", "", "", "", "", "", "", "",
"barr-cage", "spec-road", "", "", "", "", "", "",
"dispel", "smash",
};
// Field names that can be printed in the editor
cEnumLookup field_names_editor = {
"", "", "", "", "", "", "", "",
"Block", "Special Spot", "Web", "Crate", "Barrel", "Fire Barrier", "Force Barrier", "Quickfire",
"Small Blood", "Medium Blood", "Large Blood", "Small Slime", "Large Slime", "Ash", "Bones", "Rocks",
"", "Road", "", "", "", "", "", "",
"", "",
};
std::ostream& operator << (std::ostream& out, eFieldType e) {
writeEnum(out, e, field_names, "dispel");
return out;
}
std::string get_editor_field_name(eFieldType e){
std::ostringstream sstr;
writeEnum(sstr, e, field_names_editor);
return sstr.str();
}
std::istream& operator >> (std::istream& in, eFieldType& e) {
if(!readEnum(in, e, field_names, FIELD_DISPEL))
in.setstate(std::ios::failbit);

View File

@@ -702,6 +702,11 @@ std::string current_stroke_type;
item_changes_t current_items_placed;
creature_changes_t current_creatures_placed;
clear_field_stroke_t current_fields_cleared;
field_stroke_t current_fields_placed;
field_stroke_t current_fields_toggled;
eFieldType current_field_type;
void commit_stroke() {
if(!current_stroke_changes.empty()){
undo_list.add(action_ptr(new aDrawTerrain("Draw Terrain (" + current_stroke_type + ")", current_stroke_changes)));
@@ -716,6 +721,18 @@ void commit_stroke() {
undo_list.add(action_ptr(new aPlaceEraseCreature(current_creatures_placed.size() > 1 ? "Place Creatures" : "Place Creature", true, current_creatures_placed)));
update_edit_menu();
current_items_placed.clear();
}else if(!current_fields_cleared.empty()){
undo_list.add(action_ptr(new aClearFields(current_fields_cleared)));
update_edit_menu();
current_fields_cleared.clear();
}else if(!current_fields_placed.empty()){
undo_list.add(action_ptr(new aPlaceFields(current_field_type, current_fields_placed)));
update_edit_menu();
current_fields_placed.clear();
}else if(!current_fields_toggled.empty()){
undo_list.add(action_ptr(new aToggleOutFields(current_field_type == SPECIAL_ROAD, mode_count, current_fields_toggled)));
update_edit_menu();
current_fields_toggled.clear();
}
}
@@ -984,66 +1001,83 @@ static bool handle_terrain_action(location the_point, bool ctrl_hit) {
overall_mode = MODE_DRAWING;
break;
case MODE_PLACE_WEB:
make_field_type(spot_hit.x,spot_hit.y,FIELD_WEB);
current_field_type = FIELD_WEB;
make_field_type(spot_hit.x,spot_hit.y,FIELD_WEB,current_fields_placed);
mouse_button_held = true;
break;
case MODE_PLACE_CRATE:
make_field_type(spot_hit.x,spot_hit.y,OBJECT_CRATE);
current_field_type = OBJECT_CRATE;
make_field_type(spot_hit.x,spot_hit.y,OBJECT_CRATE,current_fields_placed);
mouse_button_held = true;
break;
case MODE_PLACE_BARREL:
make_field_type(spot_hit.x,spot_hit.y,OBJECT_BARREL);
current_field_type = OBJECT_BARREL;
make_field_type(spot_hit.x,spot_hit.y,OBJECT_BARREL,current_fields_placed);
mouse_button_held = true;
break;
case MODE_PLACE_FIRE_BARRIER:
make_field_type(spot_hit.x,spot_hit.y,BARRIER_FIRE);
current_field_type = BARRIER_FIRE;
make_field_type(spot_hit.x,spot_hit.y,BARRIER_FIRE,current_fields_placed);
mouse_button_held = true;
break;
case MODE_PLACE_FORCE_BARRIER:
make_field_type(spot_hit.x,spot_hit.y,BARRIER_FORCE);
current_field_type = BARRIER_FORCE;
make_field_type(spot_hit.x,spot_hit.y,BARRIER_FORCE,current_fields_placed);
mouse_button_held = true;
break;
case MODE_PLACE_QUICKFIRE:
make_field_type(spot_hit.x,spot_hit.y,FIELD_QUICKFIRE);
current_field_type = FIELD_QUICKFIRE;
make_field_type(spot_hit.x,spot_hit.y,FIELD_QUICKFIRE,current_fields_placed);
mouse_button_held = true;
break;
case MODE_PLACE_STONE_BLOCK:
make_field_type(spot_hit.x,spot_hit.y,OBJECT_BLOCK);
current_field_type = OBJECT_BLOCK;
make_field_type(spot_hit.x,spot_hit.y,OBJECT_BLOCK,current_fields_placed);
mouse_button_held = true;
break;
case MODE_PLACE_FORCECAGE:
make_field_type(spot_hit.x,spot_hit.y,BARRIER_CAGE);
current_field_type = BARRIER_CAGE;
make_field_type(spot_hit.x,spot_hit.y,BARRIER_CAGE,current_fields_placed);
mouse_button_held = true;
break;
case MODE_TOGGLE_SPECIAL_DOT:
current_field_type = SPECIAL_SPOT;
if(editing_town){
make_field_type(spot_hit.x, spot_hit.y, SPECIAL_SPOT);
make_field_type(spot_hit.x, spot_hit.y, SPECIAL_SPOT,current_fields_placed);
mouse_button_held = true;
} else {
if(!mouse_button_held)
mode_count = !current_terrain->special_spot[spot_hit.x][spot_hit.y];
current_fields_toggled.insert(spot_hit);
current_terrain->special_spot[spot_hit.x][spot_hit.y] = mode_count;
mouse_button_held = true;
}
break;
case MODE_TOGGLE_ROAD:
current_field_type = SPECIAL_ROAD;
if(editing_town){
make_field_type(spot_hit.x, spot_hit.y, SPECIAL_ROAD);
make_field_type(spot_hit.x, spot_hit.y, SPECIAL_ROAD, current_fields_placed);
mouse_button_held = true;
} else {
if(!mouse_button_held)
mode_count = !current_terrain->roads[spot_hit.x][spot_hit.y];
current_fields_toggled.insert(spot_hit);
current_terrain->roads[spot_hit.x][spot_hit.y] = mode_count;
mouse_button_held = true;
}
break;
case MODE_CLEAR_FIELDS:
for(int i = 8; i <= SPECIAL_ROAD; i++)
take_field_type(spot_hit.x,spot_hit.y, eFieldType(i));
take_field_type(spot_hit.x,spot_hit.y, eFieldType(i), current_fields_cleared);
mouse_button_held = true;
break;
case MODE_PLACE_SFX:
make_field_type(spot_hit.x,spot_hit.y,eFieldType(SFX_SMALL_BLOOD + mode_count));
current_field_type = eFieldType(SFX_SMALL_BLOOD + mode_count);
make_field_type(spot_hit.x,spot_hit.y, current_field_type, current_fields_placed);
mouse_button_held = true;
break;
case MODE_EYEDROPPER:

View File

@@ -1893,9 +1893,11 @@ bool is_field_type(short i,short j,eFieldType field_type) {
return false;
}
void make_field_type(short i,short j,eFieldType field_type) {
void make_field_type(short i,short j,eFieldType field_type,field_stroke_t& stroke) {
if(is_field_type(i,j,field_type))
return;
stroke.insert(loc(i,j));
for(short k = 0; k < town->preset_fields.size(); k++)
if(town->preset_fields[k].type == 0) {
town->preset_fields[k].loc.x = i;
@@ -1911,12 +1913,13 @@ void make_field_type(short i,short j,eFieldType field_type) {
}
void take_field_type(short i,short j,eFieldType field_type) {
void take_field_type(short i,short j,eFieldType field_type,clear_field_stroke_t& stroke) {
for(short k = 0; k < town->preset_fields.size(); k++)
if((town->preset_fields[k].type == field_type) &&
(town->preset_fields[k].loc.x == i) &&
(town->preset_fields[k].loc.y == j)) {
town->preset_fields[k].type = FIELD_DISPEL;
stroke[loc(i,j)].push_back(field_type);
return;
}
}

View File

@@ -2,6 +2,7 @@
#include <SFML/Graphics/RenderWindow.hpp>
#include "fields.hpp"
#include "location.hpp"
#include "scen.undo.hpp"
void Set_up_win ();
void run_startup_g();
@@ -29,8 +30,8 @@ void take_special(short i,short j);
void make_special(short i,short j);
void sort_specials();
bool is_field_type(short i,short j,eFieldType field_type);
void make_field_type(short i,short j,eFieldType field_type);
void take_field_type(short i,short j,eFieldType field_type);
void make_field_type(short i,short j,eFieldType field_type,field_stroke_t& stroke);
void take_field_type(short i,short j,eFieldType field_type,clear_field_stroke_t& stroke);
bool container_there(location l);
bool is_spot(short i,short j);
bool is_road(short i,short j);

View File

@@ -12,6 +12,7 @@ extern short cen_x;
extern short cen_y;
extern cScenario scenario;
extern cArea* get_current_area();
extern cOutdoors* current_terrain;
extern void start_town_edit();
extern void start_out_edit();
extern void redraw_screen();
@@ -22,6 +23,8 @@ extern eDrawMode draw_mode;
extern void apply_outdoor_shift(rectangle mod);
extern void clamp_current_section();
extern void clamp_view_center(cTown* town);
extern void make_field_type(short i,short j,eFieldType field_type,field_stroke_t& stroke);
extern void take_field_type(short i,short j,eFieldType field_type,clear_field_stroke_t& stroke);
cTerrainAction::cTerrainAction(std::string name, area_ref_t area, bool reversed) : cAction(name, reversed),
area(area) {}
@@ -638,4 +641,62 @@ bool aPlaceStartLocation::redo_me() {
scenario.out_start = area.where;
}
return true;
}
bool aClearFields::undo_me() {
field_stroke_t discard_stroke;
for(auto& space : stroke){
for(eFieldType field_type : space.second){
make_field_type(space.first.x, space.first.y, field_type, discard_stroke);
}
}
return true;
}
bool aClearFields::redo_me() {
clear_field_stroke_t discard_stroke;
for(auto& space : stroke){
for(eFieldType field_type : space.second){
take_field_type(space.first.x, space.first.y, field_type, discard_stroke);
}
}
return true;
}
bool aPlaceFields::undo_me() {
clear_field_stroke_t discard_stroke;
for(auto& space : stroke){
take_field_type(space.x, space.y, type, discard_stroke);
}
return true;
}
bool aPlaceFields::redo_me() {
field_stroke_t discard_stroke;
for(auto& space : stroke){
make_field_type(space.x, space.y, type, discard_stroke);
}
return true;
}
bool aToggleOutFields::undo_me() {
for(auto& space : stroke){
if(is_road)
current_terrain->roads[space.x][space.y] = !on;
else
current_terrain->special_spot[space.x][space.y] = !on;
}
return true;
}
bool aToggleOutFields::redo_me() {
for(auto& space : stroke){
if(is_road)
current_terrain->roads[space.x][space.y] = on;
else
current_terrain->special_spot[space.x][space.y] = on;
}
return true;
}

View File

@@ -33,6 +33,8 @@ typedef std::vector<cTerrain> terrain_type_changes_t;
typedef std::vector<cMonster> monst_type_changes_t;
typedef std::vector<class cItem> item_type_changes_t;
typedef std::map<location,cOutdoors*,loc_compare> outdoor_sections_t;
typedef std::map<location,std::vector<eFieldType>,loc_compare> clear_field_stroke_t;
typedef std::set<location,loc_compare> field_stroke_t;
// Action that modified something in town or outdoor terrain, so we should show the modified area when undoing or redoing
class cTerrainAction : public cAction {
@@ -403,4 +405,39 @@ public:
cTerrainAction(std::string { "Place Scenario Start Loc " } + (old_where.is_town ? "(Town)" : "(Outdoors)"), new_where), old_where(old_where) {}
};
/// Action that clears all fields from tiles within a stroke
class aClearFields : public cTerrainAction {
clear_field_stroke_t stroke;
bool undo_me() override;
bool redo_me() override;
public:
aClearFields(clear_field_stroke_t stroke) : cTerrainAction("Clear Fields", stroke.begin()->first),
stroke(stroke) {}
};
extern std::string get_editor_field_name(eFieldType e);
/// Action that draws a field on tiles within a stroke
class aPlaceFields : public cTerrainAction {
eFieldType type;
field_stroke_t stroke;
bool undo_me() override;
bool redo_me() override;
public:
aPlaceFields(eFieldType type, field_stroke_t stroke) : cTerrainAction(std::string{"Place "} + get_editor_field_name(type), *(stroke.begin())),
type(type), stroke(stroke) {}
};
/// Action that toggles a road or special spot outdoors within a stroke
class aToggleOutFields : public cTerrainAction {
bool is_road;
bool on;
field_stroke_t stroke;
bool undo_me() override;
bool redo_me() override;
public:
aToggleOutFields(bool is_road, bool on, field_stroke_t stroke): cTerrainAction(std::string{"Toggle "} + (is_road ? "Road" : "Special Spot"), *(stroke.begin())),
is_road(is_road), on(on), stroke(stroke) {}
};
#endif