Add a new spell pattern picker.

The picker is used in the special node dialog and also in monster abilities.

Some changes were made to the game as well:
* If the rotatable wall is used for a field missile or touch ability, there's no longer an option for the designer to pick an orientation. Instead, it behaves like the rotatable wall in a radiate field ability, selecting an orientation based on the creature's facing direction.
* The magic values sent to place_spell_pattern for direct damage were rearranged to match the order of the eDamageType enum. This should have no effect, since the core place_spell_pattern function is only called by the various wrapper overloads. It also simplifies the code quite a bit.
* The Protective Circle spell pattern is now exposed to the place patten special nodes. It can be used as just a radius 4 circle, but the effect of different layers of fields can also be obtained by specifying a field type or damage type of -1.

There is also a change to the dialog engine:
* Calling setText() also implicitly calls recalcRect()
This commit is contained in:
2025-03-04 21:46:29 -05:00
committed by Celtic Minstrel
parent 4c4c70648c
commit 628b0ee677
25 changed files with 747 additions and 450 deletions

View File

@@ -1336,7 +1336,7 @@ static bool edit_monst_abil_detail(cDialog& me, std::string hit, cMonster& monst
abil_dlg["pick-strength"].attachClickHandler([&](cDialog& me,std::string,eKeyMod) -> bool {
save_monst_abil_detail(me, abil, abil_params);
int i = abil_params.gen.strength;
i = choose_text(STRT_SPELL_PAT, i, &me, "Which spell pattern?");
i = choose_pattern(i, &me, false);
abil_params.gen.strength = i;
fill_monst_abil_detail(me, monst, abil, abil_params);
return true;
@@ -1354,7 +1354,7 @@ static bool edit_monst_abil_detail(cDialog& me, std::string hit, cMonster& monst
abil_dlg["pick-pat"].attachClickHandler([&](cDialog& me,std::string,eKeyMod) -> bool {
save_monst_abil_detail(me, abil, abil_params);
int i = abil_params.radiate.pat;
i = choose_text(STRT_SPELL_PAT, i, &me, "Which spell pattern?");
i = choose_pattern(i, &me, false);
abil_params.radiate.pat = eSpellPat(i);
fill_monst_abil_detail(me, monst, abil, abil_params);
return true;

View File

@@ -21,6 +21,7 @@
#include "dialogxml/dialogs/3choice.hpp"
#include "dialogxml/dialogs/strchoice.hpp"
#include "dialogxml/dialogs/pictchoice.hpp"
#include "dialogxml/widgets/tilemap.hpp"
#include "fileio/resmgr/res_dialog.hpp"
#include "fileio/resmgr/res_strings.hpp"
#include "spell.hpp"
@@ -165,6 +166,129 @@ short choose_background(short cur_choice, cDialog* parent) {
return cur_choice;
}
static void set_pattern(cTilemap& map, const effect_pat_type& pat) {
std::string id;
map.forEach([&id](std::string ctrl_id, cControl&) {
if(ctrl_id.size() > 4)
id = ctrl_id.substr(0, ctrl_id.size() - 5);
});
for(int x = 0; x < 9; x++) {
for(int y = 0; y < 9; y++) {
auto& pic = map.getChild(id, x, y);
int effect = pat[y][x];
sf::Color clr = sf::Color::Black;
if(effect == 0xffff) {
clr = sf::Color::White;
} else if(effect >= 50) {
eDamageType type = eDamageType::MARKED;
unsigned short dice = (effect - 50) % 40;
if(dice <= 30 && effect <= 400) {
type = eDamageType((effect - 50) / 40);
}
switch(type) {
case eDamageType::WEAPON: clr = Colours::MAROON; break;
case eDamageType::FIRE: clr = Colours::RED; break;
case eDamageType::POISON: clr = Colours::GREEN; break;
case eDamageType::MAGIC: clr = Colours::PURPLE; break;
case eDamageType::UNBLOCKABLE: clr = Colours::LIGHT_BLUE; break;
case eDamageType::COLD: clr = Colours::BLUE; break;
case eDamageType::UNDEAD: clr = Colours::GREY; break;
case eDamageType::DEMON: clr = Colours::ORANGE; break;
case eDamageType::SPECIAL: clr = Colours::FUCHSIA; break;
case eDamageType::MARKED: break;
}
} else if(effect > 0) {
auto type = eFieldType(effect);
switch(type) {
case SPECIAL_EXPLORED: case SPECIAL_ROAD: case SPECIAL_SPOT:
case SFX_SMALL_BLOOD: case SFX_MEDIUM_BLOOD: case SFX_LARGE_BLOOD:
case SFX_SMALL_SLIME: case SFX_LARGE_SLIME: case SFX_ASH:
case SFX_BONES: case SFX_RUBBLE: break;
case WALL_FORCE: clr = Colours::BLUE; break;
case WALL_FIRE: clr = Colours::RED; break;
case FIELD_ANTIMAGIC: clr = Colours::GREEN; break;
case CLOUD_STINK: clr = Colours::OLIVE; break;
case WALL_ICE: clr = Colours::AQUA; break;
case WALL_BLADES: clr = Colours::GREY; break;
case CLOUD_SLEEP: clr = Colours::LIGHT_BLUE; break;
case OBJECT_BLOCK: clr = Colours::MAROON; break;
case FIELD_WEB: clr = Colours::WHITE; break;
case OBJECT_CRATE: clr = Colours::MAROON; break;
case OBJECT_BARREL: clr = Colours::MAROON; break;
case BARRIER_FIRE: clr = Colours::DARK_RED; break;
case BARRIER_FORCE: clr = Colours::DARK_BLUE; break;
case FIELD_QUICKFIRE: clr = Colours::ORANGE; break;
case BARRIER_CAGE: clr = Colours::LIGHT_GREEN; break;
case FIELD_DISPEL: clr = Colours::PINK; break;
case FIELD_SMASH: clr = Colours::YELLOW; break;
}
}
pic.setColour(clr);
}
}
}
short choose_pattern(short cur_choice, cDialog* parent, bool expandRotatable) {
cDialog pat_dlg(*ResMgr::dialogs.get("choose-pattern"), parent);
pat_dlg.attachClickHandlers([](cDialog& me, std::string item_hit, eKeyMod) {
me.toast(item_hit == "done");
return true;
}, {"done", "cancel"});
static std::vector<int> all_pats;
if(all_pats.empty()) {
all_pats.resize(PAT_WALL + 1);
std::iota(all_pats.begin(), all_pats.end(), 0);
all_pats.push_back(PAT_PROT);
}
std::vector<int> choices;
int i = 1;
for(auto pat_id : all_pats) {
std::string id = "pic" + std::to_string(i++);
auto& pat = cPattern::get_builtin(eSpellPat(pat_id));
std::string id2 = id;
id2.replace(0, 3, "name");
pat_dlg[id2].setText(pat.name);
if(pat.rotatable) {
if(!expandRotatable) {
choices.push_back(pat_id);
set_pattern(dynamic_cast<cTilemap&>(pat_dlg[id]), pat.patterns[0]);
} else for(int j = 0; j < pat.patterns.size(); j++) {
choices.push_back(pat_id + j);
if(j > 0) id = "pic" + std::to_string(i++);
set_pattern(dynamic_cast<cTilemap&>(pat_dlg[id]), pat.patterns[j]);
}
} else {
choices.push_back(pat_id);
set_pattern(dynamic_cast<cTilemap&>(pat_dlg[id]), pat.pattern);
}
}
while(i <= 18) {
std::string id = "pic" + std::to_string(i++);
pat_dlg[id].hide();
id.replace(0, 3, "led");
pat_dlg[id].hide();
id.replace(0, 3, "name");
pat_dlg[id].hide();
}
pat_dlg["left"].hide();
pat_dlg["right"].hide();
auto& group = dynamic_cast<cLedGroup&>(pat_dlg["group"]);
auto iter = std::lower_bound(choices.rbegin(), choices.rend(), cur_choice, std::greater<int>());
if(iter == choices.rend()) {
group.setSelected("led1");
} else {
int idx = std::distance(choices.rbegin(), iter);
std::string id = "led" + std::to_string(9 - idx);
group.setSelected(id);
}
pat_dlg.run();
if(pat_dlg.accepted()) {
auto selected = group.getSelected();
return choices[std::stoi(selected.substr(3)) - 1];
}
return cur_choice;
}
// TODO: I have two functions that do this. (The other one is pick_picture.)
extern std::string scenario_temp_dir_name;
pic_num_t choose_graphic(short cur_choice,ePicType g_type,cDialog* parent, bool static_only) {
@@ -415,9 +539,6 @@ short choose_text(eStrType list, unsigned short cur_choice, cDialog* parent, std
case STRT_STATUS:
strings = {"Alive", "Dead", "Dust", "Petrified", "Fled Outdoor Combat", "Absent", "Deleted"};
break;
case STRT_SPELL_PAT:
strings = {"Single Space", "3x3 Square", "2x2 Square", "3x3 Open Square", "Radius 2 Circle", "Radius 3 Circle", "Cross", "Rotateable 2x8 Wall"};
break;
case STRT_SUMMON:
strings = {"0 - no summon (weak)", "1 - weak summoning", "2 - summoning", "3 - major summoning", "4 - no summon (unique/powerful"};
break;
@@ -970,7 +1091,6 @@ static bool edit_spec_enc_value(cDialog& me, std::string item_hit, node_stack_t&
case eSpecPicker::STRING: {
std::string title;
switch(fcn.str_type) {
case STRT_SPELL_PAT: title = "Which spell pattern?"; break;
case STRT_ITEM: title = "Which item?"; break;
case STRT_SPEC_ITEM: title = "Which special item?"; break;
case STRT_TER: title = "Which terrain?"; break;
@@ -1140,6 +1260,7 @@ static bool edit_spec_enc_value(cDialog& me, std::string item_hit, node_stack_t&
store = sdf.y;
me[otherField].setTextToNum(sdf.x);
} break;
case eSpecPicker::SPELL_PATTERN: store = choose_pattern(val, &me, fcn.augmented); break;
case eSpecPicker::FIELD: store = choose_field_type(val, &me, fcn.augmented); break;
case eSpecPicker::DAMAGE_TYPE: store = choose_damage_type(val, &me, true); break;
case eSpecPicker::EXPLOSION: store = choose_boom_type(val, &me); break;

View File

@@ -13,6 +13,7 @@ short choose_background(short cur_choice, cDialog* parent);
short choose_text_res(std::string res_list,short first_t,short last_t,unsigned short cur_choice,cDialog* parent,const char *title);
short choose_text(eStrType list, unsigned short cur_choice, cDialog* parent,std::string title);
short choose_text_editable(std::vector<std::string>& 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 edit_spec_enc(short which_node,short mode,cDialog* parent);
short get_fresh_spec(short which_mode);