undo/redo for editing/clearing monster types
This commit is contained in:
12
rsrc/dialogs/confirm-edit-monst.xml
Normal file
12
rsrc/dialogs/confirm-edit-monst.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
|
||||
<!-- NOTE: This file should be updated to use relative positioning the next time it changes. -->
|
||||
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
|
||||
<dialog defbtn='keep' escbtn='cancel'>
|
||||
<pict type='dlog' num='7' top='6' left='6'/>
|
||||
<text name='keep-msg' top='6' left='49' width='256' height='32'>
|
||||
Keep changes to {{monst}} before editing another monster?
|
||||
</text>
|
||||
<button name='cancel' type='regular' top='43' left='109'>Cancel</button>
|
||||
<button name='revert' type='regular' top='43' left='175'>Discard</button>
|
||||
<button name='keep' type='regular' top='43' left='240'>Keep</button>
|
||||
</dialog>
|
@@ -928,3 +928,110 @@ eMonstAbil uAbility::readFrom(const cTagFile_Page& page) {
|
||||
return key;
|
||||
}
|
||||
|
||||
bool cMonster::operator==(const cMonster& other) {
|
||||
CHECK_EQ(other, level);
|
||||
CHECK_EQ(other, m_name);
|
||||
CHECK_EQ(other, m_health);
|
||||
CHECK_EQ(other, armor);
|
||||
CHECK_EQ(other, skill);
|
||||
// attacks
|
||||
for(int i = 0; i < a.size(); ++i){
|
||||
CHECK_EQ(other, a[i].dice);
|
||||
CHECK_EQ(other, a[i].sides);
|
||||
CHECK_EQ(other, a[i].type);
|
||||
}
|
||||
CHECK_EQ(other, m_type);
|
||||
CHECK_EQ(other, speed);
|
||||
CHECK_EQ(other, mu);
|
||||
CHECK_EQ(other, cl);
|
||||
CHECK_EQ(other, treasure);
|
||||
|
||||
// abilities
|
||||
CHECK_EQ(other, abil.size()); // TODO will NO_ABIL ever be a key? If so, it should be ignored.
|
||||
for(auto ability : abil){
|
||||
if(other.abil.find(ability.first) == other.abil.end()) return false;
|
||||
uAbility mine = ability.second;
|
||||
uAbility theirs = other.abil[ability.first];
|
||||
|
||||
// compare all ability types
|
||||
switch(getMonstAbilCategory(ability.first)){
|
||||
case eMonstAbilCat::INVALID: break;
|
||||
case eMonstAbilCat::MISSILE:
|
||||
if(mine.missile.active != theirs.missile.active) return false;
|
||||
if(mine.missile.type != theirs.missile.type) return false;
|
||||
if(mine.missile.pic != theirs.missile.pic) return false;
|
||||
if(mine.missile.dice != theirs.missile.dice) return false;
|
||||
if(mine.missile.sides != theirs.missile.sides) return false;
|
||||
if(mine.missile.skill != theirs.missile.skill) return false;
|
||||
if(mine.missile.range != theirs.missile.range) return false;
|
||||
if(mine.missile.odds != theirs.missile.odds) return false;
|
||||
break;
|
||||
case eMonstAbilCat::GENERAL:
|
||||
if(mine.gen.active != theirs.gen.active) return false;
|
||||
if(mine.gen.type != theirs.gen.type) return false;
|
||||
if(mine.gen.pic != theirs.gen.pic) return false;
|
||||
if(mine.gen.strength != theirs.gen.strength) return false;
|
||||
if(mine.gen.range != theirs.gen.range) return false;
|
||||
if(mine.gen.odds != theirs.gen.odds) return false;
|
||||
switch(ability.first){
|
||||
case eMonstAbil::FIELD:
|
||||
if(mine.gen.fld != theirs.gen.fld) return false;
|
||||
break;
|
||||
case eMonstAbil::DAMAGE: case eMonstAbil::DAMAGE2:
|
||||
if(mine.gen.dmg != theirs.gen.dmg) return false;
|
||||
break;
|
||||
case eMonstAbil::STATUS: case eMonstAbil::STATUS2:
|
||||
if(mine.gen.stat != theirs.gen.stat) return false;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case eMonstAbilCat::SUMMON:
|
||||
if(mine.summon.active != theirs.summon.active) return false;
|
||||
if(mine.summon.type != theirs.summon.type) return false;
|
||||
if(mine.summon.what != theirs.summon.what) return false;
|
||||
if(mine.summon.min != theirs.summon.min) return false;
|
||||
if(mine.summon.max != theirs.summon.max) return false;
|
||||
if(mine.summon.len != theirs.summon.len) return false;
|
||||
if(mine.summon.chance != theirs.summon.chance) return false;
|
||||
break;
|
||||
case eMonstAbilCat::RADIATE:
|
||||
if(mine.radiate.active != theirs.radiate.active) return false;
|
||||
if(mine.radiate.type != theirs.radiate.type) return false;
|
||||
if(mine.radiate.chance != theirs.radiate.chance) return false;
|
||||
if(mine.radiate.pat != theirs.radiate.pat) return false;
|
||||
break;
|
||||
case eMonstAbilCat::SPECIAL:
|
||||
if(mine.special.active != theirs.special.active) return false;
|
||||
if(mine.special.extra1 != theirs.special.extra1) return false;
|
||||
if(mine.special.extra2 != theirs.special.extra2) return false;
|
||||
if(mine.special.extra3 != theirs.special.extra3) return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_EQ(other, corpse_item);
|
||||
CHECK_EQ(other, corpse_item_chance);
|
||||
|
||||
// resistances
|
||||
CHECK_EQ(other, resist.size());
|
||||
for(auto resistance : resist){
|
||||
if(other.resist.find(resistance.first) == other.resist.end()) return false;
|
||||
if(resistance.second != other.resist[resistance.first]) return false;
|
||||
}
|
||||
|
||||
CHECK_EQ(other, mindless);
|
||||
CHECK_EQ(other, invuln);
|
||||
CHECK_EQ(other, invisible);
|
||||
CHECK_EQ(other, guard);
|
||||
CHECK_EQ(other, amorphous);
|
||||
CHECK_EQ(other, x_width);
|
||||
CHECK_EQ(other, y_width);
|
||||
CHECK_EQ(other, default_attitude);
|
||||
CHECK_EQ(other, summon_type);
|
||||
CHECK_EQ(other, default_facial_pic);
|
||||
CHECK_EQ(other, picture_num);
|
||||
CHECK_EQ(other, ambient_sound);
|
||||
CHECK_EQ(other, see_spec);
|
||||
return true;
|
||||
}
|
@@ -61,7 +61,8 @@ public:
|
||||
mutable std::map<eMonstAbil, uAbility> abil;
|
||||
item_num_t corpse_item;
|
||||
short corpse_item_chance;
|
||||
std::map<eDamageType, int> resist;
|
||||
// HACK: This is only really marked mutable so that I can use operator[] from const methods
|
||||
mutable std::map<eDamageType, int> resist;
|
||||
bool mindless, invuln, invisible, guard, amorphous;
|
||||
unsigned int x_width,y_width;
|
||||
eAttitude default_attitude;
|
||||
@@ -71,6 +72,10 @@ public:
|
||||
snd_num_t ambient_sound; // has a chance of being played every move
|
||||
spec_num_t see_spec;
|
||||
|
||||
// For detecting actual changes to types in the scenario editor
|
||||
bool operator==(const cMonster& other);
|
||||
bool operator!=(const cMonster& other) { return !(*this == other); }
|
||||
|
||||
std::map<eMonstAbil,uAbility>::iterator addAbil(eMonstAbilTemplate what, int param = 0);
|
||||
int addAttack(unsigned short dice, unsigned short sides, eMonstMelee type = eMonstMelee::SWING);
|
||||
|
||||
|
@@ -1320,8 +1320,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.scen_monsters[i] = cMonster();
|
||||
scenario.scen_monsters[i].m_name = "Unused Monster";
|
||||
cMonster before = scenario.scen_monsters[i];
|
||||
cMonster after;
|
||||
after.m_name = "Unused Monster";
|
||||
scenario.scen_monsters[i] = after;
|
||||
undo_list.add(action_ptr(new aEditClearMonster("Clear Monster Type", i, before, after)));
|
||||
update_edit_menu();
|
||||
}
|
||||
break;
|
||||
case DRAW_ITEM:
|
||||
|
@@ -823,28 +823,63 @@ static bool save_monst_info(cDialog& me, cMonster& monst) {
|
||||
|
||||
static bool edit_monst_type_event_filter(cDialog& me,std::string hit,cMonster& monst,short& which) {
|
||||
short i;
|
||||
short which_before = which;
|
||||
cMonster temp_monst;
|
||||
|
||||
bool commit_changes = false;
|
||||
|
||||
if(hit == "okay") {
|
||||
if(save_monst_info(me,monst)) {
|
||||
scenario.scen_monsters[which] = monst;
|
||||
if(monst != scenario.scen_monsters[which]){
|
||||
commit_changes = true;
|
||||
}
|
||||
me.toast(true);
|
||||
}
|
||||
} else if(hit == "abils") {
|
||||
if(!save_monst_info(me,monst)) return false;
|
||||
temp_monst = edit_monst_abil(monst,which,me);
|
||||
if(temp_monst.level < 255)
|
||||
|
||||
bool abil_changed = (temp_monst != monst);
|
||||
|
||||
// Canceling the monster abilities editor sets the temp monster level to 255 as a flag.
|
||||
// This should be fine unless we ever increase the max monster level (40) by a LOT.
|
||||
if(abil_changed && temp_monst.level < 255){
|
||||
monst = temp_monst;
|
||||
put_monst_info_in_dlog(me,monst,which);
|
||||
put_monst_info_in_dlog(me,monst,which);
|
||||
// TODO should probably show in the monster editor that there are now unsaved ability changes.
|
||||
}
|
||||
} else if(hit == "left") {
|
||||
if(!save_monst_info(me,monst)) return false;
|
||||
scenario.scen_monsters[which] = monst;
|
||||
// TODO run focus handlers!
|
||||
if(monst != scenario.scen_monsters[which]){
|
||||
// Confirm keeping changes
|
||||
cChoiceDlog dlog("confirm-edit-monst", {"keep","revert","cancel"}, &me);
|
||||
dlog->getControl("keep-msg").replaceText("{{monst}}", monst.m_name);
|
||||
std::string choice = dlog.show();
|
||||
if(choice == "keep"){
|
||||
commit_changes = true;
|
||||
}else if(choice == "cancel"){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
which--;
|
||||
if(which < 1) which = scenario.scen_monsters.size() - 1;
|
||||
monst = scenario.scen_monsters[which];
|
||||
put_monst_info_in_dlog(me,monst,which);
|
||||
} else if(hit == "right") {
|
||||
if(!save_monst_info(me,monst)) return false;
|
||||
// TODO run focus handlers!
|
||||
if(monst != scenario.scen_monsters[which]){
|
||||
// Confirm keeping changes
|
||||
cChoiceDlog dlog("confirm-edit-monst", {"keep","revert","cancel"}, &me);
|
||||
dlog->getControl("keep-msg").replaceText("{{monst}}", monst.m_name);
|
||||
std::string choice = dlog.show();
|
||||
if(choice == "keep"){
|
||||
commit_changes = true;
|
||||
}else if(choice == "cancel"){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
scenario.scen_monsters[which] = monst;
|
||||
which++;
|
||||
if(which >= scenario.scen_monsters.size()) which = 1;
|
||||
@@ -891,6 +926,13 @@ static bool edit_monst_type_event_filter(cDialog& me,std::string hit,cMonster& m
|
||||
put_monst_info(monstInfo, monst, scenario);
|
||||
monstInfo.run();
|
||||
}
|
||||
|
||||
if(commit_changes){
|
||||
undo_list.add(action_ptr(new aEditClearMonster("Edit Monster Type", which_before, scenario.scen_monsters[which_before], monst)));
|
||||
update_edit_menu();
|
||||
scenario.scen_monsters[which_before] = monst;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -122,6 +122,18 @@ bool aEditClearTerrain::redo_me() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool aEditClearMonster::undo_me() {
|
||||
// TODO show the type
|
||||
scenario.scen_monsters[which] = before;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool aEditClearMonster::redo_me() {
|
||||
// TODO show the type
|
||||
scenario.scen_monsters[which] = after;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool aCreateDeleteMonster::undo_me() {
|
||||
// TODO if not in MODE_EDIT_TYPES, show it
|
||||
for(cMonster monst : monsters){
|
||||
|
@@ -157,4 +157,16 @@ public:
|
||||
cAction(name), which(which), before(before), after(after) {}
|
||||
};
|
||||
|
||||
/// Action which edits or clears a monster type
|
||||
class aEditClearMonster : public cAction {
|
||||
mon_num_t which;
|
||||
cMonster before;
|
||||
cMonster after;
|
||||
bool undo_me() override;
|
||||
bool redo_me() override;
|
||||
public:
|
||||
aEditClearMonster(std::string name, mon_num_t which, cMonster before, cMonster after) :
|
||||
cAction(name), which(which), before(before), after(after) {}
|
||||
};
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user