diff --git a/rsrc/dialogs/confirm-edit-terrain.xml b/rsrc/dialogs/confirm-edit-terrain.xml
new file mode 100644
index 00000000..3135c73a
--- /dev/null
+++ b/rsrc/dialogs/confirm-edit-terrain.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/src/global.hpp b/src/global.hpp
index ed36db29..015c9efc 100644
--- a/src/global.hpp
+++ b/src/global.hpp
@@ -63,6 +63,8 @@ inline void LOG(std::string line) {
#define LOG_VALUE(x) std::cout << #x << ": " << (x) << std::endl;
+#define CHECK_EQ(other, x) if(this->x != other.x) return false;
+
const std::string FINISH_FIRST = "Finish what you're doing first.";
#endif
diff --git a/src/scenario/terrain.cpp b/src/scenario/terrain.cpp
index b37fc329..6b07b1a3 100644
--- a/src/scenario/terrain.cpp
+++ b/src/scenario/terrain.cpp
@@ -413,3 +413,35 @@ bool cTerrain::blocksMove() const {
int code = (int) blockage;
return code > 2;
}
+
+bool cTerrain::operator==(const cTerrain& other) {
+ CHECK_EQ(other, name);
+ CHECK_EQ(other, picture);
+ CHECK_EQ(other, blockage);
+ CHECK_EQ(other, flag1);
+ CHECK_EQ(other, flag2);
+ CHECK_EQ(other, flag3);
+ CHECK_EQ(other, special);
+ CHECK_EQ(other, trans_to_what);
+ CHECK_EQ(other, fly_over);
+ CHECK_EQ(other, boat_over);
+ CHECK_EQ(other, block_horse);
+ CHECK_EQ(other, is_archetype);
+ CHECK_EQ(other, light_radius);
+ CHECK_EQ(other, step_sound);
+ CHECK_EQ(other, shortcut_key);
+ CHECK_EQ(other, obj_num);
+ CHECK_EQ(other, ground_type);
+ CHECK_EQ(other, trim_type);
+ CHECK_EQ(other, trim_ter);
+ CHECK_EQ(other, frill_for);
+ CHECK_EQ(other, frill_chance);
+ CHECK_EQ(other, combat_arena);
+ CHECK_EQ(other, obj_pos);
+ CHECK_EQ(other, obj_size);
+ CHECK_EQ(other, map_pic);
+ // This field is just temporary for porting. I'm guessing checking it for equality
+ // would actually be *incorrect.*
+ // CHECK_EQ(other, i);
+ return true;
+}
\ No newline at end of file
diff --git a/src/scenario/terrain.hpp b/src/scenario/terrain.hpp
index f51c363b..6c11b5ab 100644
--- a/src/scenario/terrain.hpp
+++ b/src/scenario/terrain.hpp
@@ -45,6 +45,10 @@ public:
pic_num_t map_pic = -1;
unsigned short i; // for temporary use in porting
+ // For detecting actual changes to types in the scenario editor
+ bool operator==(const cTerrain& other);
+ bool operator!=(const cTerrain& other) { return !(*this == other); }
+
bool blocksMove() const;
void import_legacy(legacy::terrain_type_type& old);
void writeTo(std::ostream& file) const;
diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp
index 319f835a..fcfa02af 100644
--- a/src/scenedit/scen.actions.cpp
+++ b/src/scenedit/scen.actions.cpp
@@ -1302,8 +1302,12 @@ static bool handle_terpal_action(location cur_point, bool option_hit) {
// option-click type that can't be deleted (because it would break other types' numbers, or is in use somewhere):
// reset type info
else{
- scenario.ter_types[i] = cTerrain();
- scenario.ter_types[i].name = "Unused Terrain";
+ cTerrain before = scenario.ter_types[i];
+ cTerrain after;
+ after.name = "Unused Terrain";
+ scenario.ter_types[i] = after;
+ undo_list.add(action_ptr(new aEditClearTerrain("Clear Terrain Type", i, before, after)));
+ update_edit_menu();
}
break;
case DRAW_MONST:
diff --git a/src/scenedit/scen.core.cpp b/src/scenedit/scen.core.cpp
index c573b46e..7fce9975 100644
--- a/src/scenedit/scen.core.cpp
+++ b/src/scenedit/scen.core.cpp
@@ -53,6 +53,7 @@ extern ter_num_t template_terrain[64][64];
extern cScenario scenario;
extern cCustomGraphics spec_scen_g;
extern location cur_out;
+extern cUndoList undo_list;
const std::set items_no_strength = {
eItemAbil::NONE, eItemAbil::HEALING_WEAPON, eItemAbil::RETURNING_MISSILE, eItemAbil::SEEKING_MISSILE, eItemAbil::DRAIN_MISSILES,
@@ -463,8 +464,31 @@ static void fill_ter_info(cDialog& me, short ter){
}
static bool finish_editing_ter(cDialog& me, std::string id, ter_num_t& which) {
- if(!save_ter_info(me, scenario.ter_types[which])) return true;
-
+ cTerrain after;
+ if(!save_ter_info(me, after)) return true;
+ cTerrain before = scenario.ter_types[which];
+
+ bool changed = (after != before);
+
+ if(changed){
+ if(id == "left" || id == "right"){
+ // Confirm keeping changes
+ cChoiceDlog dlog("confirm-edit-terrain", {"keep","revert","cancel"}, &me);
+ dlog->getControl("keep-msg").replaceText("{{ter}}", after.name);
+ std::string choice = dlog.show();
+ if(choice == "keep"){
+ scenario.ter_types[which] = after;
+ }else if(choice == "cancel"){
+ return true;
+ }
+ }else{
+ scenario.ter_types[which] = after;
+ }
+ // Store an undo action
+ undo_list.add(action_ptr(new aEditClearTerrain("Edit Terrain Type", which, before, after)));
+ update_edit_menu();
+ }
+
if(!me.toast(true)) return true;
if(id == "left") {
me.untoast();
diff --git a/src/scenedit/scen.undo.cpp b/src/scenedit/scen.undo.cpp
index 795fcc91..ea489841 100644
--- a/src/scenedit/scen.undo.cpp
+++ b/src/scenedit/scen.undo.cpp
@@ -110,6 +110,18 @@ bool aCreateDeleteTerrain::redo_me() {
return true;
}
+bool aEditClearTerrain::undo_me() {
+ // TODO show the type
+ scenario.ter_types[which] = before;
+ return true;
+}
+
+bool aEditClearTerrain::redo_me() {
+ // TODO show the type
+ scenario.ter_types[which] = after;
+ return true;
+}
+
bool aCreateDeleteMonster::undo_me() {
// TODO if not in MODE_EDIT_TYPES, show it
for(cMonster monst : monsters){
diff --git a/src/scenedit/scen.undo.hpp b/src/scenedit/scen.undo.hpp
index 1776db15..5c3190e3 100644
--- a/src/scenedit/scen.undo.hpp
+++ b/src/scenedit/scen.undo.hpp
@@ -144,4 +144,16 @@ public:
items(items) {}
};
+/// Action which edits or clears a terrain type
+class aEditClearTerrain : public cAction {
+ ter_num_t which;
+ cTerrain before;
+ cTerrain after;
+ bool undo_me() override;
+ bool redo_me() override;
+public:
+ aEditClearTerrain(std::string name, ter_num_t which, cTerrain before, cTerrain after) :
+ cAction(name), which(which), before(before), after(after) {}
+};
+
#endif
\ No newline at end of file