diff --git a/src/scenario/special.hpp b/src/scenario/special.hpp index 99ab4d9b..34683ed1 100644 --- a/src/scenario/special.hpp +++ b/src/scenario/special.hpp @@ -88,6 +88,26 @@ public: cSpecial(); void import_legacy(legacy::special_node_type& old); void writeTo(std::ostream& file, int n) const; + + bool operator==(const cSpecial& other) const { + CHECK_EQ(other, type); + CHECK_EQ(other, sd1); + CHECK_EQ(other, sd2); + CHECK_EQ(other, pic); + CHECK_EQ(other, pictype); + CHECK_EQ(other, m1); + CHECK_EQ(other, m2); + CHECK_EQ(other, m3); + CHECK_EQ(other, ex1a); + CHECK_EQ(other, ex1b); + CHECK_EQ(other, ex1c); + CHECK_EQ(other, ex2a); + CHECK_EQ(other, ex2b); + CHECK_EQ(other, ex2c); + CHECK_EQ(other, jumpto); + return true; + } + bool operator!=(const cSpecial& other) const { return !(*this == other); } }; enum class eSpecCtxType { diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp index 1ed6d9e4..b48d05db 100644 --- a/src/scenedit/scen.actions.cpp +++ b/src/scenedit/scen.actions.cpp @@ -427,9 +427,12 @@ static bool handle_rb_action(location the_point, bool option_hit) { break; else scenario.scen_specials[j] = cSpecial(); } else { - if(j == size_before) + bool is_new = false; + if(j == size_before){ scenario.scen_specials.emplace_back(); - edit_spec_enc(j,0,nullptr); + is_new = true; + } + edit_spec_enc(j,0,nullptr,is_new); } start_special_editing(0); if(size_before > scenario.scen_specials.size()) @@ -448,9 +451,12 @@ static bool handle_rb_action(location the_point, bool option_hit) { break; else current_terrain->specials[j] = cSpecial(); } else { - if(j == size_before) + bool is_new = false; + if(j == size_before){ current_terrain->specials.emplace_back(); - edit_spec_enc(j,1,nullptr); + is_new = true; + } + edit_spec_enc(j,1,nullptr,is_new); } start_special_editing(1); if(size_before > current_terrain->specials.size()) @@ -469,9 +475,12 @@ static bool handle_rb_action(location the_point, bool option_hit) { break; else town->specials[j] = cSpecial(); } else { - if(j == size_before) + bool is_new = false; + if(j == size_before){ town->specials.emplace_back(); - edit_spec_enc(j,2,nullptr); + is_new = true; + } + edit_spec_enc(j,2,nullptr,is_new); } start_special_editing(2); if(size_before > town->specials.size()) @@ -2848,21 +2857,21 @@ void place_edit_special(location loc) { auto& specials = get_current_area()->special_locs; for(short i = 0; i < specials.size(); i++) if(specials[i] == loc && specials[i].spec >= 0) { - edit_spec_enc(specials[i].spec, editing_town ? 2 : 1, nullptr); - // TODO add the edit specials actions + edit_spec_enc(specials[i].spec, editing_town ? 2 : 1, nullptr, false); return; } // new special - short spec = get_fresh_spec(editing_town ? 2 : 1); for(short i = 0; i <= specials.size(); i++) { - if(i == specials.size()) + bool is_new = false; + if(i == specials.size()){ specials.emplace_back(-1,-1,-1); + is_new = true; + } if(specials[i].spec < 0) { - if(edit_spec_enc(spec, editing_town ? 2: 1, nullptr)) { + if(edit_spec_enc(i, editing_town ? 2: 1, nullptr, is_new)) { specials[i] = loc; - specials[i].spec = spec; + specials[i].spec = i; undo_list.add(action_ptr(new aPlaceEraseSpecial("Place Special Encounter", true, specials[i]))); - // TODO add the edit specials actions update_edit_menu(); } break; @@ -2882,7 +2891,6 @@ void set_special(location spot_hit) { int spec = edit_special_num(editing_town ? 2 : 1,specials[x].spec); if(spec >= 0 && spec != specials[x].spec){ undo_list.add(action_ptr(new aSetSpecial(spot_hit, specials[x].spec, spec))); - // TODO if create/edit was used, add those actions update_edit_menu(); specials[x].spec = spec; } @@ -2898,7 +2906,6 @@ void set_special(location spot_hit) { specials[x] = spot_hit; specials[x].spec = spec; undo_list.add(action_ptr(new aPlaceEraseSpecial("Place Special Encounter", true, specials[x]))); - // TODO if create/edit was used, add those actions update_edit_menu(); } break; diff --git a/src/scenedit/scen.core.cpp b/src/scenedit/scen.core.cpp index b5645440..36dc3184 100644 --- a/src/scenedit/scen.core.cpp +++ b/src/scenedit/scen.core.cpp @@ -273,9 +273,10 @@ static bool pick_ter_flag(cDialog& me, std::string id, eKeyMod) { else if(choice == "town") which_type = 2; } short spec = me["flag1"].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(which_type); - if(edit_spec_enc(spec,which_type,&me)) + spec = get_fresh_spec(which_type,is_new); + if(edit_spec_enc(spec,which_type,&me,is_new)) me["flag1"].setTextToNum(spec); return true; } else if(id == "picktrans") { @@ -1114,9 +1115,10 @@ static bool edit_monst_abil_event_filter(cDialog& me,std::string hit,cMonster& m else abils.setPage(abils.getPage() + 1); } else if(hit == "edit-see") { short spec = me["onsee"].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(0); - if(edit_spec_enc(spec,0,&me)) + spec = get_fresh_spec(0,is_new); + if(edit_spec_enc(spec,0,&me,is_new)) me["onsee"].setTextToNum(spec); } else if(hit == "pick-snd") { int i = me["snd"].getTextAsNum(); @@ -1314,11 +1316,12 @@ static bool edit_monst_abil_detail(cDialog& me, std::string hit, cMonster& monst break; case eMonstAbilTemplate::SPECIAL: case eMonstAbilTemplate::HIT_TRIGGERS: - case eMonstAbilTemplate::DEATH_TRIGGERS: - param = get_fresh_spec(0); - if(!edit_spec_enc(param,0,&me)) + case eMonstAbilTemplate::DEATH_TRIGGERS:{ + bool is_new = false; + param = get_fresh_spec(0,is_new); + if(!edit_spec_enc(param,0,&me,is_new)) return true; - break; + }break; case eMonstAbilTemplate::TOUCH_POISON: param = get_monst_abil_num("Poison strength:", 0, 8, me); break; @@ -1483,9 +1486,10 @@ static bool edit_monst_abil_detail(cDialog& me, std::string hit, cMonster& monst if(abil == eMonstAbil::SPECIAL || abil == eMonstAbil::HIT_TRIGGER || abil == eMonstAbil::DEATH_TRIGGER) abil_dlg["pick-extra1"].attachClickHandler([&](cDialog& me,std::string,eKeyMod) -> bool { short spec = me["extra1"].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(0); - if(edit_spec_enc(spec,0,&me)) + spec = get_fresh_spec(0,is_new); + if(edit_spec_enc(spec,0,&me,is_new)) me["extra1"].setTextToNum(spec); return true; }); @@ -2082,9 +2086,10 @@ static bool edit_item_abil_event_filter(cDialog& me, std::string hit, cItem& ite } else if(hit == "str1-choose") { save_item_abils(me, item); short spec = me["str1"].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(0); - if(edit_spec_enc(spec,0,&me)) { + spec = get_fresh_spec(0, is_new); + if(edit_spec_enc(spec,0,&me,is_new)) { item.abil_strength = spec; me["str1"].setTextToNum(spec); } @@ -2270,9 +2275,10 @@ static bool edit_spec_item_event_filter(cDialog& me, std::string hit, cSpecItem& } else if(hit == "edit-spec") { if(!save_spec_item(me, item, which, is_new)) return true; short spec = me["spec"].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(0); - if(edit_spec_enc(spec,0,&me)) + spec = get_fresh_spec(0,is_new); + if(edit_spec_enc(spec,0,&me,is_new)) me["spec"].setTextToNum(spec); save_spec_item(me, item, which, is_new); @@ -2645,8 +2651,9 @@ static void edit_shop_special(cDialog& parent, cItem& item, size_t& quantity) { spec_dlg["edit"].attachClickHandler([](cDialog& me, std::string, eKeyMod) -> bool { int spec = me["node"].getTextAsNum(); - if(spec < 0) spec = get_fresh_spec(0); - if(edit_spec_enc(spec, 0, &me)) + bool is_new = false; + if(spec < 0) spec = get_fresh_spec(0,is_new); + if(edit_spec_enc(spec, 0, &me,is_new)) me["node"].setTextToNum(spec); return true; }); @@ -3170,8 +3177,9 @@ static void put_scen_adv_details_in_dlog(cDialog& me) { static bool edit_scen_init_spec(cDialog& me, std::string, eKeyMod) { int spec = me["oninit"].getTextAsNum(); - if(spec < 0) spec = get_fresh_spec(0); - if(edit_spec_enc(spec, 0, &me)) + bool is_new = false; + if(spec < 0) spec = get_fresh_spec(0,is_new); + if(edit_spec_enc(spec, 0, &me, is_new)) me["oninit"].setTextToNum(spec); return true; } @@ -3502,9 +3510,10 @@ static bool edit_scenario_events_event_filter(cDialog& me, std::string item_hit, // item_hit is of the form editN; we need an ID of the form nodeN item_hit.replace(0, 4, "node"); short spec = me[item_hit].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(0); - if(edit_spec_enc(spec,0,&me)) + spec = get_fresh_spec(0,is_new); + if(edit_spec_enc(spec,0,&me,is_new)) me[item_hit].setTextToNum(spec); return true; } diff --git a/src/scenedit/scen.keydlgs.cpp b/src/scenedit/scen.keydlgs.cpp index 12ea99e2..2f73b641 100644 --- a/src/scenedit/scen.keydlgs.cpp +++ b/src/scenedit/scen.keydlgs.cpp @@ -754,6 +754,7 @@ bool edit_area_rect_str(info_rect_t& r) { struct editing_node_t { short which, mode; cSpecial node; + bool is_new; }; typedef std::vector node_stack_t; @@ -902,6 +903,19 @@ static bool preview_spec_enc_dlog_stack(cDialog& me, std::string item_hit, node_ return preview_spec_enc_dlog(me, item_hit, special, mode); } +static cSpecial& get_spec_ref(int mode, size_t which) { + switch(mode) { + case 0: + return scenario.scen_specials[which]; + case 1: + return current_terrain->specials[which]; + case 2: + return town->specials[which]; + default: + throw std::string {"Bad mode"}; + } +} + static bool commit_spec_enc(cDialog& me, std::string item_hit, node_stack_t& edit_stack) { // don't update the node from the dialog fields when unwinding if(item_hit != "unwind") @@ -909,18 +923,24 @@ static bool commit_spec_enc(cDialog& me, std::string item_hit, node_stack_t& edi // commit the node's changes int mode = edit_stack.back().mode, node = edit_stack.back().which; + bool is_new = edit_stack.back().is_new; cSpecial spec = edit_stack.back().node; - switch(mode) { - case 0: scenario.scen_specials[node] = spec; break; - case 1: current_terrain->specials[node] = spec; break; - case 2: town->specials[node] = spec; break; + + cSpecial& existing = get_spec_ref(mode, node); + if(is_new){ + undo_list.add(action_ptr(new aCreateDeleteSpecial(true, mode, spec))); } + else if(existing != spec){ + undo_list.add(action_ptr(new aEditSpecial(mode, node, existing, spec))); + } + existing = spec; edit_stack.pop_back(); // Update previous instances of the same node in the stack in case the designer looped auto last_instance = std::find_if(edit_stack.rbegin(), edit_stack.rend(), [node](editing_node_t e){ return e.which == node; }); if(last_instance != edit_stack.rend()){ last_instance->node = spec; + last_instance->is_new = false; } if(item_hit == "okay") { @@ -929,6 +949,7 @@ static bool commit_spec_enc(cDialog& me, std::string item_hit, node_stack_t& edi commit_spec_enc(me, "unwind", edit_stack); me.toast(true); me.setResult(true); + update_edit_menu(); } else if(item_hit == "back") { put_spec_enc_in_dlog(me, edit_stack); if(edit_stack.size() == 1) @@ -1204,7 +1225,8 @@ static bool edit_spec_enc_value(cDialog& me, std::string item_hit, node_stack_t& } break; case eSpecPicker::NODE: { short mode = fcn.force_global ? 0 : edit_stack.back().mode; - store = val < 0 ? get_fresh_spec(mode) : val; + bool is_new = false; + store = val < 0 ? get_fresh_spec(mode,is_new) : val; me[field].setTextToNum(store); save_spec_enc(me, edit_stack); cSpecial* node_to_change_to = nullptr; @@ -1217,7 +1239,7 @@ static bool edit_spec_enc_value(cDialog& me, std::string item_hit, node_stack_t& if (node_to_change_to) { if(node_to_change_to->pic < 0) node_to_change_to->pic = 0; - edit_stack.push_back({store,mode,*node_to_change_to}); + edit_stack.push_back({store,mode,*node_to_change_to,is_new}); } put_spec_enc_in_dlog(me, edit_stack); me["back"].show(); @@ -1439,8 +1461,7 @@ static bool edit_spec_enc_value(cDialog& me, std::string item_hit, node_stack_t& } // mode - 0 scen 1 - out 2 - town -// TODO collect a list of undo actions that can be committed to undo_list by the calling code -bool edit_spec_enc(short which_node,short mode,cDialog* parent) { +bool edit_spec_enc(short which_node,short mode,cDialog* parent,bool is_new) { using namespace std::placeholders; cSpecial the_node; node_stack_t edit_stack; @@ -1508,7 +1529,7 @@ bool edit_spec_enc(short which_node,short mode,cDialog* parent) { special["back"].hide(); - edit_stack.push_back({which_node,mode,the_node}); + edit_stack.push_back({which_node,mode,the_node,is_new}); special["preview-dialog"].attachClickHandler(std::bind(preview_spec_enc_dlog_stack, _1, _2, std::ref(edit_stack), mode)); put_spec_enc_in_dlog(special, edit_stack); @@ -1518,7 +1539,7 @@ bool edit_spec_enc(short which_node,short mode,cDialog* parent) { return special.getResult(); } -short get_fresh_spec(short which_mode) { +short get_fresh_spec(short which_mode,bool& is_new) { cSpecial store_node; size_t num_specs = 0; switch(which_mode) { @@ -1538,6 +1559,7 @@ short get_fresh_spec(short which_mode) { return i; } + is_new = true; switch(which_mode) { case 0: scenario.scen_specials.emplace_back(); break; case 1: current_terrain->specials.emplace_back(); break; @@ -1688,10 +1710,11 @@ static bool edit_special_num_event_filter(cDialog& me, std::string item_hit, sho if(i > num_specs){ showWarning("There is no special node with that number. " + range, how_to_create + ".", &me); }else{ + bool is_new = false; if(i < 0 || i == num_specs) - i = get_fresh_spec(spec_mode); + i = get_fresh_spec(spec_mode,is_new); me.setResult(i); - edit_spec_enc(i, spec_mode, &me); + edit_spec_enc(i, spec_mode, &me,is_new); me.toast(true); } }else if(item_hit == "okay") { diff --git a/src/scenedit/scen.keydlgs.hpp b/src/scenedit/scen.keydlgs.hpp index f4e187da..588dceb0 100644 --- a/src/scenedit/scen.keydlgs.hpp +++ b/src/scenedit/scen.keydlgs.hpp @@ -15,8 +15,8 @@ short choose_text(eStrType list, unsigned short cur_choice, cDialog* parent,std: short choose_text_editable(std::vector& list, short cur_choice, cDialog* parent, std::string title); short choose_pattern(short cur_choice, cDialog* parent, bool expandRotatable); bool edit_text_str(short which_str,eStrMode mode,bool& is_new,bool loop = true,short min_str = 0,short max_str = -1); -bool edit_spec_enc(short which_node,short mode,cDialog* parent); -short get_fresh_spec(short which_mode); +bool edit_spec_enc(short which_node,short mode,cDialog* parent,bool is_new); +short get_fresh_spec(short which_mode,bool& is_new); void edit_spec_text(eStrMode mode,short *str1,short *str2,cDialog* parent); void edit_dialog_text(eStrMode mode,short *str1,cDialog* parent); short edit_special_num(short mode,short what_start); diff --git a/src/scenedit/scen.townout.cpp b/src/scenedit/scen.townout.cpp index 3d5e8e19..7b9914fa 100644 --- a/src/scenedit/scen.townout.cpp +++ b/src/scenedit/scen.townout.cpp @@ -233,18 +233,20 @@ static bool edit_placed_monst_adv_time_flag(cDialog& me, std::string, bool losin static bool edit_placed_monst_adv_death(cDialog& me) { short spec = me["death"].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(2); - if(edit_spec_enc(spec,2,&me)) + spec = get_fresh_spec(2,is_new); + if(edit_spec_enc(spec,2,&me,is_new)) me["death"].setTextToNum(spec); return true; } static bool edit_placed_monst_adv_hail(cDialog& me) { short spec = me["hail"].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(2); - if(edit_spec_enc(spec,2,&me)) + spec = get_fresh_spec(2,is_new); + if(edit_spec_enc(spec,2,&me,is_new)) me["hail"].setTextToNum(spec); return true; } @@ -722,9 +724,10 @@ static bool edit_out_wand_spec(cDialog& me, std::string hit, short which, cOutdo save_out_wand(me, which, wand, 100); std::string fld = "on" + hit.substr(5); short spec = me[fld].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(1); - if(edit_spec_enc(spec,1,&me)) + spec = get_fresh_spec(1,is_new); + if(edit_spec_enc(spec,1,&me,is_new)) me[fld].setTextToNum(spec); return true; } @@ -891,9 +894,10 @@ static void put_town_events_in_dlog(cDialog& me) { static bool edit_town_events_event_filter(cDialog& me, std::string item_hit, eKeyMod) { std::string id = item_hit.substr(4); short spec = me["spec" + id].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(2); - if(edit_spec_enc(spec,2,&me)) + spec = get_fresh_spec(2,is_new); + if(edit_spec_enc(spec,2,&me,is_new)) me["spec" + id].setTextToNum(spec); return true; } @@ -963,9 +967,10 @@ static void put_advanced_town_in_dlog(cDialog& me) { static bool edit_advanced_town_special(cDialog& me, std::string hit, eKeyMod) { std::string fld = hit.substr(5); short spec = me[fld].getTextAsNum(); + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(2); - if(edit_spec_enc(spec,2,&me)) { + spec = get_fresh_spec(2,is_new); + if(edit_spec_enc(spec,2,&me,is_new)) { me[fld].setTextToNum(spec); } return true; @@ -1638,9 +1643,10 @@ static bool select_talk_node_value(cDialog& me, std::string item_hit, const std: } else if(fcn.button == eSpecPicker::NODE) { int spec = me[field_id].getTextAsNum(); int mode = fcn.force_global ? 0 : 2; + bool is_new = false; if(spec < 0) - spec = get_fresh_spec(mode); - if(edit_spec_enc(spec,mode,&me)) + spec = get_fresh_spec(mode,is_new); + if(edit_spec_enc(spec,mode,&me,is_new)) me[field_id].setTextToNum(spec); } return true; diff --git a/src/scenedit/scen.undo.cpp b/src/scenedit/scen.undo.cpp index 80e27daf..d6734894 100644 --- a/src/scenedit/scen.undo.cpp +++ b/src/scenedit/scen.undo.cpp @@ -1172,4 +1172,44 @@ bool aCreateDeleteSpecial::redo_me() { } start_special_editing(mode); return true; +} + +bool aEditSpecial::undo_me() { + switch(mode){ + case 0: + scenario.scen_specials[which] = old_spec; + break; + case 1: + set_current_out(which_out); + current_terrain->specials[which] = old_spec; + break; + case 2: + set_current_town(which_town); + town->specials[which] = old_spec; + break; + default: + break; + } + start_special_editing(mode); + return true; +} + +bool aEditSpecial::redo_me() { + switch(mode){ + case 0: + scenario.scen_specials[which] = new_spec; + break; + case 1: + set_current_out(which_out); + current_terrain->specials[which] = new_spec; + break; + case 2: + set_current_town(which_town); + town->specials[which] = new_spec; + break; + default: + break; + } + start_special_editing(mode); + return true; } \ No newline at end of file diff --git a/src/scenedit/scen.undo.hpp b/src/scenedit/scen.undo.hpp index 6b577228..2801cf09 100644 --- a/src/scenedit/scen.undo.hpp +++ b/src/scenedit/scen.undo.hpp @@ -796,4 +796,20 @@ public: cAction(create ? "Create Special Node" : "Delete Special Node", !create), mode(mode), spec(spec), which_town(cur_town), which_out(cur_out) {} }; +class aEditSpecial : public cAction { + short mode; + size_t which; + // undo/redo for town and outdoor nodes depends on global state of which town/outdoor section are active + size_t which_town; + location which_out; + cSpecial old_spec; + cSpecial new_spec; + bool undo_me() override; + bool redo_me() override; +public: + aEditSpecial(short mode, size_t which, cSpecial old_spec, cSpecial new_spec) : + cAction("Edit Special Node"), mode(mode), which(which), old_spec(old_spec), new_spec(new_spec), which_town(cur_town), which_out(cur_out) { + } +}; + #endif \ No newline at end of file