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

@@ -24,6 +24,7 @@
#include "dialogxml/dialogs/choicedlog.hpp"
#include "dialogxml/dialogs/strdlog.hpp"
#include "boe.menus.hpp"
#include "pattern.hpp"
#include "spell.hpp"
#include "tools/prefs.hpp"
#include "utility.hpp"
@@ -80,176 +81,6 @@ location diag_place[14] = {
loc(0,2),loc(-2,1),loc(-1,2),loc(-2,2),loc(-3,2),
loc(-2,3),loc(-3,3),loc(-4,3),loc(-3,4)};
effect_pat_type null_pat = {{
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0}}};
effect_pat_type single = {{
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0}}};
effect_pat_type t = {{
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,1,0,0,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0}}};
effect_pat_type small_square = {{
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0}}};
effect_pat_type square = {{
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0}}};
effect_pat_type open_square = {{
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,0,1,0,1,0,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0}}};
effect_pat_type radius2 = {{
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,1,1,1,1,1,0,0},
{0,0,1,1,1,1,1,0,0},
{0,0,1,1,1,1,1,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0}}};
effect_pat_type radius3 = {{
{0,0,0,0,0,0,0,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,1,1,1,1,1,0,0},
{0,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,0},
{0,0,1,1,1,1,1,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,0,0,0,0,0,0,0}}};
effect_pat_type field[8] = {
{{
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,1,1,0,0,0}}},
{{
{0,0,0,0,0,0,0,0,1},
{0,0,0,0,0,0,0,1,1},
{0,0,0,0,0,0,1,1,0},
{0,0,0,0,0,1,1,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,1,1,0,0,0,0,0},
{0,1,1,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0,0}}},
{{
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0}}},
{{
{1,0,0,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0,0},
{0,1,1,0,0,0,0,0,0},
{0,0,1,1,0,0,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,0,1,1,0,0},
{0,0,0,0,0,0,1,1,0},
{0,0,0,0,0,0,0,1,1}}},
{{
{0,0,0,1,1,0,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,0,1,1,0,0,0,0}}},
{{
{0,0,0,0,0,0,0,1,1},
{0,0,0,0,0,0,1,1,0},
{0,0,0,0,0,1,1,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,1,1,0,0,0,0,0},
{0,1,1,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0,0},
{1,0,0,0,0,0,0,0,0}}},
{{
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0}}},
{{
{1,1,0,0,0,0,0,0,0},
{0,1,1,0,0,0,0,0,0},
{0,0,1,1,0,0,0,0,0},
{0,0,0,1,1,0,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,0,1,1,0,0},
{0,0,0,0,0,0,1,1,0},
{0,0,0,0,0,0,0,1,1},
{0,0,0,0,0,0,0,0,1}}}};
bool center_on_monst;
void start_outdoor_combat(cOutdoors::cWandering encounter,location where,short num_walls) {
@@ -756,7 +587,7 @@ void pc_attack_weapon(short who_att,iLiving& target,short hit_adj,short dam_adj,
else pause(5);
play_sound(5);
start_missile_anim();
place_spell_pattern(radius2, target.get_loc(), weap.abil_data.damage, weap.abil_strength * 2, who_att);
place_spell_pattern(PAT_RAD2, target.get_loc(), weap.abil_data.damage, weap.abil_strength * 2, who_att);
do_explosion_anim(5,0);
end_missile_anim();
handle_marked_damage();
@@ -1183,7 +1014,7 @@ void do_combat_cast(location target) {
add_missile(target,9,1,0,0);
store_sound = 11;
r1 = min(18,(level * 7) / 10 + 2 * bonus);
place_spell_pattern(radius2,target,eDamageType::MAGIC,r1,univ.cur_pc);
place_spell_pattern(PAT_RAD2,target,eDamageType::MAGIC,r1,univ.cur_pc);
ashes_loc = target;
break;
@@ -1231,7 +1062,7 @@ void do_combat_cast(location target) {
r1 = (r1 * 14) / 10;
else if(r1 > 10) r1 = (r1 * 8) / 10;
if(r1 <= 0) r1 = 1;
place_spell_pattern(square,target,eDamageType::FIRE,r1,univ.cur_pc);
place_spell_pattern(PAT_SQ,target,eDamageType::FIRE,r1,univ.cur_pc);
ashes_loc = target;
break;
case eSpell::FIRESTORM: case eSpell::ICY_RAIN:
@@ -1244,9 +1075,9 @@ void do_combat_cast(location target) {
if(r1 > 20)
r1 = (r1 * 8) / 10;
if(spell_being_cast == eSpell::FIRESTORM){
place_spell_pattern(radius2,target,eDamageType::FIRE,r1,univ.cur_pc);
place_spell_pattern(PAT_RAD2,target,eDamageType::FIRE,r1,univ.cur_pc);
ashes_loc = target;
}else place_spell_pattern(radius2,target,eDamageType::COLD,r1,univ.cur_pc);
}else place_spell_pattern(PAT_RAD2,target,eDamageType::COLD,r1,univ.cur_pc);
break;
case eSpell::KILL:
add_missile(target,9,1,0,0);
@@ -1647,8 +1478,8 @@ void load_missile() {
current_spell_range += thrown->abil_strength;
handle_target_mode(MODE_THROWING, current_spell_range);
if(thrown->ability == eItemAbil::EXPLODING_WEAPON)
current_pat = radius2;
else current_pat = single;
current_pat = cPattern::get_builtin(PAT_RAD2).pattern;
else current_pat = cPattern::get_builtin(PAT_SINGLE).pattern;
} else if((bow && bolts) || (crossbow && arrow)) {
add_string_to_buf("Fire: Wrong ammunition.");
} else if(bow && !arrow) {
@@ -1665,8 +1496,8 @@ void load_missile() {
add_string_to_buf("Fire: Select a target.");
add_string_to_buf(" (Hit 's' to cancel.)");
if(arrow->ability == eItemAbil::EXPLODING_WEAPON)
current_pat = radius2;
else current_pat = single;
current_pat = cPattern::get_builtin(PAT_RAD2).pattern;
else current_pat = cPattern::get_builtin(PAT_SINGLE).pattern;
} else if(crossbow && bolts) {
missile_inv_slot = crossbow.slot;
ammo_inv_slot = bolts.slot;
@@ -1677,8 +1508,8 @@ void load_missile() {
add_string_to_buf("Fire: Select a target.");
add_string_to_buf(" (Hit 's' to cancel.)");
if(bolts->ability == eItemAbil::EXPLODING_WEAPON)
current_pat = radius2;
else current_pat = single;
current_pat = cPattern::get_builtin(PAT_RAD2).pattern;
else current_pat = cPattern::get_builtin(PAT_SINGLE).pattern;
} else if(no_ammo) {
missile_inv_slot = no_ammo.slot;
ammo_inv_slot = no_ammo.slot;
@@ -1689,8 +1520,8 @@ void load_missile() {
add_string_to_buf("Fire: Select a target.");
add_string_to_buf(" (Hit 's' to cancel.)");
if(no_ammo->ability == eItemAbil::EXPLODING_WEAPON)
current_pat = radius2;
else current_pat = single;
current_pat = cPattern::get_builtin(PAT_RAD2).pattern;
else current_pat = cPattern::get_builtin(PAT_SINGLE).pattern;
}
else add_string_to_buf("Fire: Equip a missile.");
}
@@ -1778,7 +1609,7 @@ void fire_missile(location target) {
pause(dist(missile_firer.combat_pos,target)*5);
run_a_missile(missile_firer.combat_pos,target,2,1,5,0,0,100);
start_missile_anim();
place_spell_pattern(radius2,target, ammo.abil_data.damage,ammo.abil_strength * 2, univ.cur_pc);
place_spell_pattern(PAT_RAD2,target, ammo.abil_data.damage,ammo.abil_strength * 2, univ.cur_pc);
do_explosion_anim(5,0);
end_missile_anim();
handle_marked_damage();
@@ -2606,33 +2437,7 @@ void do_monster_turn() {
// Place fields for monsters that create them. Only done when monst sees foe
if(target != 6 && can_see_light(cur_monst->cur_loc,targ_space,sight_obscurity) < 5) {
if(cur_monst->abil[eMonstAbil::RADIATE].active && get_ran(1,1,100) < cur_monst->abil[eMonstAbil::RADIATE].radiate.chance) {
switch(cur_monst->abil[eMonstAbil::RADIATE].radiate.pat) {
case PAT_SINGLE:
place_spell_pattern(single, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7);
break;
case PAT_SMSQ:
place_spell_pattern(small_square, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7);
break;
case PAT_SQ:
place_spell_pattern(square, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7);
break;
case PAT_OPENSQ:
place_spell_pattern(open_square, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7);
break;
case PAT_RAD2:
place_spell_pattern(radius2, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7);
break;
case PAT_RAD3:
place_spell_pattern(radius3, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7);
break;
case PAT_PLUS:
place_spell_pattern(t, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7);
break;
case PAT_WALL:
int dir = (cur_monst->direction + 6) % 8;
place_spell_pattern(field[dir], cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7);
break;
}
place_spell_pattern(cur_monst->abil[eMonstAbil::RADIATE].radiate.pat, cur_monst->direction + 6, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7);
}
if(cur_monst->abil[eMonstAbil::SUMMON].active && get_ran(1,1,100) < cur_monst->abil[eMonstAbil::SUMMON].summon.chance) {
uAbility abil = cur_monst->abil[eMonstAbil::SUMMON];
@@ -3316,20 +3121,7 @@ void monst_basic_abil(short m_num, std::pair<eMonstAbil,uAbility> abil, iLiving*
univ.party.gold = std::max(0, univ.party.gold - get_ran(1,0,abil.second.gen.strength) - abil.second.gen.strength);
break;
case eMonstAbil::FIELD:
effect_pat_type pat;
switch(eSpellPat(abil.second.gen.strength)) {
case PAT_SINGLE: pat = single; break;
case PAT_SQ: pat = small_square; break;
case PAT_OPENSQ: pat = open_square; break;
case PAT_SMSQ: pat = square; break;
case PAT_PLUS: pat = t; break;
case PAT_RAD2: pat = radius2; break;
case PAT_RAD3: pat = radius3; break;
default: // either PAT_WALL or not a valid eSpellPat
pat = field[abil.second.gen.strength / 10];
break;
}
place_spell_pattern(pat, targ_space, abil.second.gen.fld, 7);
place_spell_pattern(eSpellPat(abil.second.gen.strength), m_target->direction + 6, targ_space, abil.second.gen.fld, 7);
break;
// Non-basic abilities
case eMonstAbil::MISSILE: case eMonstAbil::MISSILE_WEB: case eMonstAbil::RAY_HEAT:
@@ -3531,7 +3323,7 @@ bool monst_cast_mage(cCreature *caster,short targ) {
break;
case eSpell::CLOUD_FLAME:
run_a_missile(l,vict_loc,2,1,11,0,0,80);
place_spell_pattern(single,vict_loc,WALL_FIRE,7);
place_spell_pattern(PAT_SINGLE,vict_loc,WALL_FIRE,7);
break;
case eSpell::FLAME:
run_a_missile(l,vict_loc,2,1,11,0,0,80);
@@ -3553,7 +3345,7 @@ bool monst_cast_mage(cCreature *caster,short targ) {
break;
case eSpell::CLOUD_STINK:
run_a_missile(l,target,0,0,25,0,0,80);
place_spell_pattern(square,target,CLOUD_STINK,7);
place_spell_pattern(PAT_SQ,target,CLOUD_STINK,7);
break;
case eSpell::SUMMON_BEAST:
r1 = get_summon_monster(1);
@@ -3566,14 +3358,14 @@ bool monst_cast_mage(cCreature *caster,short targ) {
break;
case eSpell::CONFLAGRATION:
run_a_missile(l,target,13,1,25,0,0,80);
place_spell_pattern(radius2,target,WALL_FIRE,7);
place_spell_pattern(PAT_RAD2,target,WALL_FIRE,7);
break;
case eSpell::FIREBALL:
r1 = 1 + (caster->level * 3) / 4;
if(r1 > 29) r1 = 29;
run_a_missile(l,target,2,1,11,0,0,80);
start_missile_anim();
place_spell_pattern(square,target,eDamageType::FIRE,r1,7);
place_spell_pattern(PAT_SQ,target,eDamageType::FIRE,r1,7);
ashes_loc = target;
break;
case eSpell::SUMMON_WEAK: case eSpell::SUMMON: case eSpell::SUMMON_MAJOR: {
@@ -3608,7 +3400,7 @@ bool monst_cast_mage(cCreature *caster,short targ) {
}
case eSpell::WEB:
play_sound(25);
place_spell_pattern(radius2,target,FIELD_WEB,7);
place_spell_pattern(PAT_RAD2,target,FIELD_WEB,7);
break;
case eSpell::POISON:
run_a_missile(l,vict_loc,11,0,25,0,0,80);
@@ -3648,12 +3440,12 @@ bool monst_cast_mage(cCreature *caster,short targ) {
r1 = 1 + (caster->level * 3) / 4 + 3;
if(r1 > 29) r1 = 29;
start_missile_anim();
place_spell_pattern(radius2,target,eDamageType::FIRE,r1,7);
place_spell_pattern(PAT_RAD2,target,eDamageType::FIRE,r1,7);
ashes_loc = target;
break;
case eSpell::SHOCKSTORM:
run_a_missile(l,target,6,1,11,0,0,80);
place_spell_pattern(radius2,target,WALL_FORCE,7);
place_spell_pattern(PAT_RAD2,target,WALL_FORCE,7);
break;
case eSpell::POISON_MAJOR:
run_a_missile(l,vict_loc,11,1,11,0,0,80);
@@ -3844,7 +3636,7 @@ bool monst_cast_priest(cCreature *caster,short targ) {
break;
case eSpell::GOO:
play_sound(24);
place_spell_pattern(single,vict_loc,FIELD_WEB,7);
place_spell_pattern(PAT_SINGLE,vict_loc,FIELD_WEB,7);
break;
case eSpell::BLESS_MINOR: case eSpell::BLESS:
play_sound(24);
@@ -3976,7 +3768,7 @@ bool monst_cast_priest(cCreature *caster,short targ) {
run_a_missile(l,target,2,0,11,0,0,80);
r1 = 2 + caster->level / 2 + 2;
start_missile_anim();
place_spell_pattern(square,target,eDamageType::FIRE,r1,7);
place_spell_pattern(PAT_SQ,target,eDamageType::FIRE,r1,7);
ashes_loc = target;
break;
case eSpell::UNHOLY_RAVAGING:
@@ -3997,7 +3789,7 @@ bool monst_cast_priest(cCreature *caster,short targ) {
r1 = (caster->level * 3) / 4 + 5;
if(r1 > 29) r1 = 29;
start_missile_anim();
place_spell_pattern(radius2,target,eDamageType::MAGIC,r1,7 );
place_spell_pattern(PAT_RAD2,target,eDamageType::MAGIC,r1,7 );
// Note: This used to place ashes on the target square, but the spell description
// doesn't describe fire or combustion, only force.
break;
@@ -4113,15 +3905,13 @@ bool monst_near(short m_num,location where,short radius,short active) {
}
void fireball_space(location loc,short dam) {
place_spell_pattern(square,loc,eDamageType::FIRE,dam,7);
place_spell_pattern(PAT_SQ,loc,eDamageType::FIRE,dam,7);
}
//type; // 0 - take codes in pattern, OW make all nonzero this type
// Types 0 - Null 1 - web 2 - fire barrier 3 - force barrier 4 - force wall 5 - fire wall
// 6 - anti-magic field 7 - stink cloud 8 - ice wall 9 - blade wall 10 - quickfire
// 11 - dispel 12 - sleep field
// 50 + i - 80 : id6 fire damage 90 + i - 120 : id6 cold damage 130 + i - 160 : id6 magic dam.
// if prep for anim is true, supporess look checks and go fast
// Type:
// 0 - take codes in pattern (otherwise make all nonzero this type)
// 1-33 - a field type (see eFieldType)
// 50-400 - a specific damage type - 50 + type * 40 + dice (see eDamageType)
static void place_spell_pattern(effect_pat_type pat,location center,unsigned short type,short who_hit) {
short r1 = 0;
unsigned short effect;
@@ -4133,7 +3923,7 @@ static void place_spell_pattern(effect_pat_type pat,location center,unsigned sho
if(type != 0)
modify_pattern(&pat,type);
modify_pattern(pat,type);
@@ -4145,14 +3935,14 @@ static void place_spell_pattern(effect_pat_type pat,location center,unsigned sho
j <= minmax(active.top + 1,active.bottom - 1,center.y + 4); j++) {
s_loc.x = i; s_loc.y = j;
if(can_see_light(center,s_loc,sight_obscurity) == 5)
pat.pattern[i - center.x + 4][j - center.y + 4] = 0;
pat[i - center.x + 4][j - center.y + 4] = 0;
}
// First actually make barriers, then draw them, then inflict damaging effects.
for(short i = minmax(0,univ.town->max_dim - 1,center.x - 4); i <= minmax(0,univ.town->max_dim - 1,center.x + 4); i++)
for(short j = minmax(0,univ.town->max_dim - 1,center.y - 4); j <= minmax(0,univ.town->max_dim - 1,center.y + 4); j++) {
effect = pat.pattern[i - center.x + 4][j - center.y + 4];
effect = pat[i - center.x + 4][j - center.y + 4];
if(effect == FIELD_SMASH || sight_obscurity(i,j) < 5) {
switch(eFieldType(effect)) {
case FIELD_WEB:
@@ -4248,38 +4038,12 @@ static void place_spell_pattern(effect_pat_type pat,location center,unsigned sho
if(sight_obscurity(i,j) < 5 && pc.main_status == eMainStatus::ALIVE
&& (((is_combat()) && (pc.combat_pos == spot_hit)) ||
((is_town()) && (univ.party.town_loc == spot_hit)))) {
effect = pat.pattern[i - center.x + 4][j - center.y + 4];
effect = pat[i - center.x + 4][j - center.y + 4];
if(effect >= 50) {
eDamageType type = eDamageType::MARKED;
unsigned short dice;
if(effect > 50 && effect <= 80) {
type = eDamageType::FIRE;
dice = effect - 50;
} else if(effect > 90 && effect <= 120) {
type = eDamageType::COLD;
dice = effect - 90;
} else if(effect > 130 && effect <= 160) {
type = eDamageType::MAGIC;
dice = effect - 130;
// The rest of these are new, currently unused.
} else if(effect > 170 && effect <= 200) {
type = eDamageType::WEAPON;
dice = effect - 170;
} else if(effect > 210 && effect <= 240) {
type = eDamageType::POISON;
dice = effect - 210;
} else if(effect > 250 && effect <= 280) {
type = eDamageType::UNBLOCKABLE;
dice = effect - 250;
} else if(effect > 290 && effect <= 320) {
type = eDamageType::UNDEAD;
dice = effect - 290;
} else if(effect > 330 && effect <= 360) {
type = eDamageType::DEMON;
dice = effect - 330;
} else if(effect > 370 && effect <= 400) {
type = eDamageType::SPECIAL;
dice = effect - 370;
unsigned short dice = (effect - 50) % 40;
if(dice <= 30 && effect <= 400) {
type = eDamageType((effect - 50) / 40);
}
if(type != eDamageType::MARKED) {
r1 = get_ran(dice,1,6);
@@ -4335,43 +4099,17 @@ static void place_spell_pattern(effect_pat_type pat,location center,unsigned sho
if(!monster_hit && sight_obscurity(i,j) < 5 && univ.town.monst[k].on_space(spot_hit)) {
if(pat.pattern[i - center.x + 4][j - center.y + 4] > 0)
if(pat[i - center.x + 4][j - center.y + 4] > 0)
monster_hit = true;
effect = pat.pattern[i - center.x + 4][j - center.y + 4];
effect = pat[i - center.x + 4][j - center.y + 4];
which_m = &univ.town.monst[k];
if(which_m->abil[eMonstAbil::RADIATE].active && effect == which_m->abil[eMonstAbil::RADIATE].radiate.type)
continue;
if(effect >= 50) {
eDamageType type = eDamageType::MARKED;
unsigned short dice;
if(effect > 50 && effect <= 80) {
type = eDamageType::FIRE;
dice = effect - 50;
} else if(effect > 90 && effect <= 120) {
type = eDamageType::COLD;
dice = effect - 90;
} else if(effect > 130 && effect <= 160) {
type = eDamageType::MAGIC;
dice = effect - 130;
// The rest of these are new, currently unused.
} else if(effect > 170 && effect <= 200) {
type = eDamageType::WEAPON;
dice = effect - 170;
} else if(effect > 210 && effect <= 240) {
type = eDamageType::POISON;
dice = effect - 210;
} else if(effect > 250 && effect <= 280) {
type = eDamageType::UNBLOCKABLE;
dice = effect - 250;
} else if(effect > 290 && effect <= 320) {
type = eDamageType::UNDEAD;
dice = effect - 290;
} else if(effect > 330 && effect <= 360) {
type = eDamageType::DEMON;
dice = effect - 330;
} else if(effect > 370 && effect <= 400) {
type = eDamageType::SPECIAL;
dice = effect - 370;
unsigned short dice = (effect - 50) % 40;
if(dice <= 30 && effect <= 400) {
type = eDamageType((effect - 50) / 40);
}
if(type != eDamageType::MARKED) {
r1 = get_ran(dice,1,6);
@@ -4434,33 +4172,49 @@ void place_spell_pattern(effect_pat_type pat,location center,eFieldType type,sho
place_spell_pattern(pat, center, code, who_hit);
}
void place_spell_pattern(eSpellPat pat,location center,short who_hit) {
place_spell_pattern(pat, 0, center, who_hit);
}
void place_spell_pattern(eSpellPat pat,location center,eFieldType type,short who_hit) {
place_spell_pattern(pat, 0, center, type, who_hit);
}
void place_spell_pattern(eSpellPat pat,short rot,location center,short who_hit) {
const cPattern& pat_info = cPattern::get_builtin(pat);
if(pat_info.rotatable) place_spell_pattern(pat_info.patterns[rot % pat_info.patterns.size()], center, 0, who_hit);
else place_spell_pattern(pat_info.pattern, center, 0, who_hit);
}
void place_spell_pattern(eSpellPat pat,short rot,location center,eFieldType type,short who_hit) {
const cPattern& pat_info = cPattern::get_builtin(pat);
if(pat_info.rotatable) place_spell_pattern(pat_info.patterns[rot % pat_info.patterns.size()], center, type, who_hit);
else place_spell_pattern(pat_info.pattern, center, type, who_hit);
}
// Copied from place_spell_pattern comment above:
// 50 + i - 80 : id6 fire damage 90 + i - 120 : id6 cold damage 130 + i - 160 : id6 magic dam.
void place_spell_pattern(effect_pat_type pat,location center,eDamageType type,short dice,short who_hit) {
unsigned short code;
if(type == eDamageType::MARKED) return; // Not valid; do nothing.
unsigned short code = 50 + int(type) * 40;
dice = minmax(1, 30, dice);
switch(type) {
case eDamageType::FIRE: code = 50; break;
case eDamageType::COLD: code = 90; break;
case eDamageType::MAGIC: code = 130; break;
case eDamageType::WEAPON: code = 170; break;
case eDamageType::POISON: code = 210; break;
case eDamageType::UNBLOCKABLE: code = 250; break;
case eDamageType::UNDEAD: code = 290; break;
case eDamageType::DEMON: code = 330; break;
case eDamageType::SPECIAL: code = 370; break;
case eDamageType::MARKED:
// Not valid; do nothing.
return;
}
place_spell_pattern(pat, center, code + dice, who_hit);
}
void modify_pattern(effect_pat_type *pat,unsigned short type) {
void place_spell_pattern(eSpellPat pat, location center, eDamageType type, short dice, short who_hit) {
place_spell_pattern(pat, 0, center, type, dice, who_hit);
}
void place_spell_pattern(eSpellPat pat, short rot, location center, eDamageType type, short dice, short who_hit) {
const cPattern& pat_info = cPattern::get_builtin(pat);
if(pat_info.rotatable) place_spell_pattern(pat_info.patterns[rot % pat_info.patterns.size()], center, type, dice, who_hit);
else place_spell_pattern(pat_info.pattern, center, type, dice, who_hit);
}
void modify_pattern(effect_pat_type& pat,unsigned short type) {
for(short i = 0; i < 9; i++)
for(short j = 0; j < 9; j++)
if(pat->pattern[i][j] > 0)
pat->pattern[i][j] = type;
if(pat[i][j] > 0)
pat[i][j] = type;
}
void do_shockwave(location target) {
@@ -4946,10 +4700,10 @@ void combat_immed_mage_cast(short current_pc, eSpell spell_num, bool freebie) {
break;
case eSpell::BLADE_AURA: // Pyhrrus effect
place_spell_pattern(radius2,caster.combat_pos,WALL_BLADES,6);
place_spell_pattern(PAT_RAD2,caster.combat_pos,WALL_BLADES,6);
break;
case eSpell::FLAME_AURA:
place_spell_pattern(open_square, caster.combat_pos, eDamageType::FIRE, 6, current_pc);
place_spell_pattern(PAT_OPENSQ, caster.combat_pos, eDamageType::FIRE, 6, current_pc);
break;
default:
add_string_to_buf(" Error: Mage spell " + (*spell_num).name() + " not implemented for combat mode.", 4);
@@ -5033,17 +4787,6 @@ void combat_immed_priest_cast(short current_pc, eSpell spell_num, bool freebie)
if(!freebie && caster.traits[eTrait::ANAMA])
level++;
cCreature *which_m;
effect_pat_type protect_pat = {{
{0,1,1,1,1,1,1,1,0},
{1,5,5,5,5,5,5,5,1},
{1,5,6,6,6,6,6,5,1},
{1,5,6,3,3,3,6,5,1},
{1,5,6,3,3,3,6,5,1},
{1,5,6,3,3,3,6,5,1},
{1,5,6,6,6,6,6,5,1},
{1,5,5,5,5,5,5,5,1},
{0,1,1,1,1,1,1,1,0}
}};
start_missile_anim();
switch(spell_num) {
case eSpell::BLESS_MINOR: case eSpell::BLESS:
@@ -5118,7 +4861,7 @@ void combat_immed_priest_cast(short current_pc, eSpell spell_num, bool freebie)
caster.cur_sp -= (*spell_num).cost;
play_sound(24);
add_string_to_buf(" Protective field created.");
place_spell_pattern(protect_pat,caster.combat_pos,6);
place_spell_pattern(PAT_PROT,caster.combat_pos,6);
break;
case eSpell::AUGMENTATION:
if(target < 6) {
@@ -5159,50 +4902,42 @@ void start_spell_targeting(eSpell num, bool freebie, int spell_range, eSpellPat
else add_string_to_buf(" (Hit 'p' to cancel.)");
current_spell_range = num == eSpell::NONE ? spell_range : (*num).range;
handle_target_mode(MODE_SPELL_TARGET, current_spell_range);
current_pat = single;
switch(num) { // Different spells have different messages and diff. target shapes
case eSpell::CLOUD_SLEEP:
current_pat = small_square;
pat = PAT_SMSQ;
break;
case eSpell::DISPEL_SQUARE: case eSpell::FIREBALL: case eSpell::CLOUD_STINK:
case eSpell::FLAMESTRIKE: case eSpell::FORCEFIELD:
current_pat = square;
pat = PAT_SQ;
break;
case eSpell::CONFLAGRATION: case eSpell::FIRESTORM: case eSpell::SHOCKSTORM: case eSpell::WEB:
case eSpell::ANTIMAGIC: case eSpell::WALL_ICE_BALL: case eSpell::CLOUD_SLEEP_LARGE:
case eSpell::DIVINE_THUD: case eSpell::DISPEL_SPHERE:
current_pat = radius2;
pat = PAT_RAD2;
break;
case eSpell::PESTILENCE: case eSpell::GOO_BOMB: case eSpell::FOUL_VAPOR:
current_pat = radius3;
pat = PAT_RAD3;
break;
case eSpell::WALL_FORCE: case eSpell::WALL_ICE: case eSpell::WALL_BLADES:
add_string_to_buf(" (Hit space to rotate.)");
force_wall_position = 0;
current_pat = field[0];
pat = PAT_WALL;
break;
case eSpell::NONE:
switch(pat) {
case PAT_SINGLE: current_pat = single; break;
case PAT_SQ: current_pat = square; break;
case PAT_SMSQ: current_pat = small_square; break;
case PAT_OPENSQ: current_pat = open_square; break;
case PAT_PLUS: current_pat = t; break;
case PAT_RAD2: current_pat = radius2; break;
case PAT_RAD3: current_pat = radius3; break;
case PAT_WALL:
add_string_to_buf(" (Hit space to rotate.)");
force_wall_position = 0;
current_pat = field[0];
break;
}
// Do nothing - use the pat passed in
break;
default:
pat = PAT_SINGLE;
if((*num).refer != REFER_TARGET)
add_string_to_buf(" Error: Entered targeting for non-targeted spell " + (*num).name(), 4);
break;
}
auto pattern = cPattern::get_builtin(pat);
if(pattern.rotatable) {
add_string_to_buf(" (Hit space to rotate.)");
force_wall_position = 0;
current_pat = pattern.patterns[0];
} else current_pat = pattern.pattern;
}
void start_fancy_spell_targeting(eSpell num, bool freebie, int spell_range, eSpellPat pat, int targets) {
@@ -5222,7 +4957,6 @@ void start_fancy_spell_targeting(eSpell num, bool freebie, int spell_range, eSpe
add_string_to_buf(" (Hit space to cast.)");
current_spell_range = num == eSpell::NONE ? spell_range : (*num).range;
handle_target_mode(MODE_FANCY_TARGET, current_spell_range);
current_pat = single;
short bonus = univ.current_pc().stat_adj(eSkill::INTELLIGENCE);
short level = freebie ? store_item_spell_level : univ.current_pc().level;
@@ -5247,7 +4981,7 @@ void start_fancy_spell_targeting(eSpell num, bool freebie, int spell_range, eSpe
break;
case eSpell::SPRAY_FIELDS:
num_targets_left = level / 5 + bonus / 2;
current_pat = t;
pat = PAT_PLUS;
break;
case eSpell::SUMMON_WEAK:
num_targets_left = minmax(1,7,level / 4 + bonus / 2);
@@ -5260,24 +4994,22 @@ void start_fancy_spell_targeting(eSpell num, bool freebie, int spell_range, eSpe
break;
case eSpell::NONE:
num_targets_left = minmax(1,8,targets);
switch(pat) {
case PAT_SINGLE: current_pat = single; break;
case PAT_SQ: current_pat = square; break;
case PAT_SMSQ: current_pat = small_square; break;
case PAT_OPENSQ: current_pat = open_square; break;
case PAT_PLUS: current_pat = t; break;
case PAT_RAD2: current_pat = radius2; break;
case PAT_RAD3: current_pat = radius3; break;
case PAT_WALL: current_pat = single; break; // Fancy targeting doesn't support the rotateable pattern
}
break;
default:
if((*num).refer == REFER_FANCY)
std::cout << " Warning: Spell " << (*num).name() << " didn't assign target shape and count." << std::endl;
else add_string_to_buf(" Error: Entered fancy targeting for non-fancy-targeted spell " + (*num).name(), 4);
pat = PAT_SINGLE;
break;
}
auto pattern = cPattern::get_builtin(pat);
if(pattern.rotatable) {
// Fancy targeting doesn't support rotatable walls
// TODO: Some sort of error message instead of silently substituting SINGLE
current_pat = cPattern::get_builtin(PAT_SINGLE).pattern;
} else current_pat = pattern.pattern;
num_targets_left = minmax(1,8,num_targets_left);
}
@@ -5287,8 +5019,10 @@ void spell_cast_hit_return() {
}
if(force_wall_position < 10) {
force_wall_position = (force_wall_position + 1) % 8;
current_pat = field[force_wall_position];
// TODO: Make this work with custom rotatable patterns. Need to store the cPattern somewhere probably?
auto& pat = cPattern::get_builtin(PAT_WALL);
force_wall_position = (force_wall_position + 1) % pat.patterns.size();
current_pat = pat.patterns[force_wall_position];
}
}

View File

@@ -45,7 +45,13 @@ void fireball_space(location loc,short dam);
void place_spell_pattern(effect_pat_type pat,location center,short who_hit);
void place_spell_pattern(effect_pat_type pat,location center,eFieldType type,short who_hit);
void place_spell_pattern(effect_pat_type pat,location center,eDamageType type, short dice,short who_hit);
void modify_pattern(effect_pat_type *pat,unsigned short type);
void place_spell_pattern(eSpellPat pat,location center,short who_hit);
void place_spell_pattern(eSpellPat pat,location center,eFieldType type,short who_hit);
void place_spell_pattern(eSpellPat pat,location center,eDamageType type, short dice,short who_hit);
void place_spell_pattern(eSpellPat pat, short rot,location center,short who_hit);
void place_spell_pattern(eSpellPat pat, short rot,location center,eFieldType type,short who_hit);
void place_spell_pattern(eSpellPat pat, short rot,location center,eDamageType type, short dice,short who_hit);
void modify_pattern(effect_pat_type& pat,unsigned short type);
void do_shockwave(location target);
void radius_damage(location target,short radius, short dam, eDamageType type);
void hit_pcs_in_space(location target,short dam,eDamageType type,short report,short hit_all);

View File

@@ -28,10 +28,6 @@ struct scen_header_type{
std::string name, teaser1, teaser2, file;
};
struct effect_pat_type {
unsigned short pattern[9][9];
};
extern std::map<std::string, int> startup_button_indices;
extern std::map<int, std::string> startup_button_names;
extern std::map<int, std::string> startup_button_names_v1;

View File

@@ -1623,7 +1623,7 @@ void draw_targeting_line(location where_curs) {
from_loc = univ.current_pc().combat_pos;
else from_loc = univ.party.town_loc;
if((overall_mode == MODE_SPELL_TARGET) || (overall_mode == MODE_FIRING) || (overall_mode == MODE_THROWING) || (overall_mode == MODE_FANCY_TARGET)
|| ((overall_mode == MODE_TOWN_TARGET) && (current_pat.pattern[4][4] != 0))) {
|| ((overall_mode == MODE_TOWN_TARGET) && (current_pat[4][4] != 0))) {
if(where_curs.in(on_screen_terrain_area)) {
// && (point_onscreen(center,univ.party[current_pc].combat_pos))){
@@ -1653,7 +1653,7 @@ void draw_targeting_line(location where_curs) {
store_loc.y = center.y + j - 4;
if((abs(store_loc.x - which_space.x) <= 4) &&
(abs(store_loc.y - which_space.y) <= 4) &&
(current_pat.pattern[store_loc.x - which_space.x + 4][store_loc.y - which_space.y + 4] != 0)) {
(current_pat[store_loc.x - which_space.x + 4][store_loc.y - which_space.y + 4] != 0)) {
target_rect.left = 13 + 28 * i + 19;
target_rect.right = target_rect.left + 28;
target_rect.top = 13 + 36 * j + 7;

View File

@@ -76,7 +76,6 @@ extern short store_mage_lev, store_priest_lev;
extern short store_spell_target,pc_casting;
extern short store_item_spell_level;
extern eStatMode stat_screen_mode;
extern effect_pat_type null_pat,single,t,square,radius2,radius3,small_square,open_square;
extern effect_pat_type current_pat;
extern short current_spell_range;
extern std::array<short, 51> hit_chance;
@@ -2109,16 +2108,12 @@ void start_town_targeting(eSpell s_num,short who_c,bool freebie,eSpellPat pat) {
town_spell = s_num;
who_cast = who_c;
spell_freebie = freebie;
switch(pat) {
case PAT_SINGLE: current_pat = single; break;
case PAT_SQ: current_pat = square; break;
case PAT_SMSQ: current_pat = small_square; break;
case PAT_OPENSQ: current_pat = open_square; break;
case PAT_PLUS: current_pat = t; break;
case PAT_RAD2: current_pat = radius2; break;
case PAT_RAD3: current_pat = radius3; break;
case PAT_WALL: current_pat = single; break; // Rotateable wall not supported by town targeting
}
auto pattern = cPattern::get_builtin(pat);
if(pattern.rotatable) {
// Town targeting doesn't support rotatable walls
// TODO: Some sort of error message instead of silently substituting SINGLE
current_pat = cPattern::get_builtin(PAT_SINGLE).pattern;
} else current_pat = pattern.pattern;
}
void do_alchemy() {

View File

@@ -40,7 +40,6 @@ extern eItemWinMode stat_window;
extern short which_combat_type;
extern location center;
extern bool processing_fields,monsters_going,boom_anim_active;
extern effect_pat_type single,t,square,radius2,radius3,small_square,open_square,field[8];
extern effect_pat_type current_pat;
extern cOutdoors::cWandering store_wandering_special;
extern eSpell spell_being_cast, town_spell;
@@ -4225,45 +4224,47 @@ void townmode_spec(const runtime_state& ctx) {
showError("Invalid spell pattern (-1 - 14).");
break;
}
if((spec.ex2a < 1 || spec.ex2a == 9 || spec.ex2a > 24) && spec.ex2a != 32 && spec.ex2a != 33) {
if(spec.ex2a == -1 && (spec.ex1c == PAT_PROT || (spec.ex1c == PAT_CURRENT && current_pat == cPattern::get_builtin(PAT_PROT).pattern)))
; // This means to use the default effect of Protective Circle
else if((spec.ex2a < 1 || spec.ex2a == 9 || spec.ex2a > 24) && spec.ex2a != 32 && spec.ex2a != 33) {
showError("Invalid field type (see docs).");
break;
}
switch(spec.ex1c) {
case -1: pat = current_pat; break;
case PAT_SINGLE: pat = single; break;
case PAT_SQ: pat = square; break;
case PAT_SMSQ: pat = small_square; break;
case PAT_OPENSQ: pat = open_square; break;
case PAT_PLUS: pat = t; break;
case PAT_RAD2: pat = radius2; break;
case PAT_RAD3: pat = radius3; break;
default: pat = field[spec.ex1c - 7];
if(spec.ex1c == -1) {
pat = current_pat;
} else if(spec.ex1c < PAT_WALL) {
pat = cPattern::get_builtin(eSpellPat(spec.ex1c)).pattern;
} else if(spec.ex1c < PAT_PROT) {
pat = cPattern::get_builtin(PAT_WALL).patterns[spec.ex1c - PAT_WALL];
} else if(spec.ex1c == PAT_PROT) {
pat = cPattern::get_builtin(PAT_PROT).pattern;
}
place_spell_pattern(pat, l, eFieldType(spec.ex2a), 6);
if(spec.ex2a == -1) place_spell_pattern(pat, l, 6);
else place_spell_pattern(pat, l, eFieldType(spec.ex2a), 6);
break;
case eSpecType::TOWN_SPELL_PAT_BOOM:
if(spec.ex1c < -1 || spec.ex1c > 14) {
showError("Invalid spell pattern (-1 - 14).");
break;
}
if(spec.ex2a < 0 || spec.ex2a > 7) {
if(spec.ex2a == -1 && (spec.ex1c == PAT_PROT || (spec.ex1c == PAT_CURRENT && current_pat == cPattern::get_builtin(PAT_PROT).pattern)))
; // This means to use the default effect of Protective Circle
else if(spec.ex2a < 0 || spec.ex2a > 7) {
showError("Invalid damage type (0 - 7).");
break;
}
switch(spec.ex1c) {
case -1: pat = current_pat; break;
case PAT_SINGLE: pat = single; break;
case PAT_SQ: pat = square; break;
case PAT_SMSQ: pat = small_square; break;
case PAT_OPENSQ: pat = open_square; break;
case PAT_PLUS: pat = t; break;
case PAT_RAD2: pat = radius2; break;
case PAT_RAD3: pat = radius3; break;
default: pat = field[spec.ex1c - 7];
if(spec.ex1c == -1) {
pat = current_pat;
} else if(spec.ex1c < PAT_WALL) {
pat = cPattern::get_builtin(eSpellPat(spec.ex1c)).pattern;
} else if(spec.ex1c < PAT_PROT) {
pat = cPattern::get_builtin(PAT_WALL).patterns[spec.ex1c - PAT_WALL];
} else if(spec.ex1c == PAT_PROT) {
pat = cPattern::get_builtin(PAT_PROT).pattern;
}
if(spec.ex2c) start_missile_anim();
place_spell_pattern(pat, l, eDamageType(spec.ex2a), spec.ex2b, 6);
if(spec.ex2a == -1) place_spell_pattern(pat, l, 6);
else place_spell_pattern(pat, l, eDamageType(spec.ex2a), spec.ex2b, 6);
if(spec.ex2c) {
do_explosion_anim(0, 0);
end_missile_anim();