diff --git a/src/fileio/estreams.cpp b/src/fileio/estreams.cpp index fbc87592..6db62293 100644 --- a/src/fileio/estreams.cpp +++ b/src/fileio/estreams.cpp @@ -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); diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp index 617155bc..6767d1f6 100644 --- a/src/scenedit/scen.actions.cpp +++ b/src/scenedit/scen.actions.cpp @@ -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: diff --git a/src/scenedit/scen.graphics.cpp b/src/scenedit/scen.graphics.cpp index 2f21f0ae..0c786774 100644 --- a/src/scenedit/scen.graphics.cpp +++ b/src/scenedit/scen.graphics.cpp @@ -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; } } diff --git a/src/scenedit/scen.graphics.hpp b/src/scenedit/scen.graphics.hpp index 3d740c28..8cd683e8 100644 --- a/src/scenedit/scen.graphics.hpp +++ b/src/scenedit/scen.graphics.hpp @@ -2,6 +2,7 @@ #include #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); diff --git a/src/scenedit/scen.undo.cpp b/src/scenedit/scen.undo.cpp index 015014b3..e42a1c73 100644 --- a/src/scenedit/scen.undo.cpp +++ b/src/scenedit/scen.undo.cpp @@ -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; } \ No newline at end of file diff --git a/src/scenedit/scen.undo.hpp b/src/scenedit/scen.undo.hpp index 53424aa0..0e1c46af 100644 --- a/src/scenedit/scen.undo.hpp +++ b/src/scenedit/scen.undo.hpp @@ -33,6 +33,8 @@ typedef std::vector terrain_type_changes_t; typedef std::vector monst_type_changes_t; typedef std::vector item_type_changes_t; typedef std::map outdoor_sections_t; +typedef std::map,loc_compare> clear_field_stroke_t; +typedef std::set 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 \ No newline at end of file