diff --git a/doc/editor/appendix/Specials.html b/doc/editor/appendix/Specials.html
index 783e529f..1c618b24 100644
--- a/doc/editor/appendix/Specials.html
+++ b/doc/editor/appendix/Specials.html
@@ -133,8 +133,27 @@ one at a time.
Mess2, Mess3:The number of the first and last string to show in the dialog.
Pict, Pictype:Specifies the icon to show in the dialog.
-Type 11: Can't EnterIf the party is walking, they are not allowed to enter
-the space. This can be accompanied by one or two messages, if you wish.
+Type 11: Prevent ActionIf the party is walking, they are not allowed to enter
+the space. This can be accompanied by one or two messages, if you wish. If called in other
+circumstances, this node prevents the action that called the node. The following actions
+are preventable in this manner:
+
+- Movement
+- Using an item - if it's a normal (not special) item, no charge will be deducted
+- Monster special actions - no AP will be deducted
+- Looking - you will not be allowed to search, but you can't prevent the party from
+seeing what the space is
+- Talking - even if the creature has a personality, talk mode will not start
+- Attacking - the attack will occur normally, but no AP will be deducted from the
+attacker
+- Shop Items - gold will not be deducted
+
+- Targeting - if a spell is being targeted, the spell effect will be cancelled. However,
+they will still lose their spell points. If the targeting was initiated by a special node,
+that node's failure option will trigger, potentially calling another special node.
+- Encountering wandering monsters - the encounter will be prevented altogether, similar
+to if the monsters had fled.
+
- Mess1, Mess2:
- Standard usage. If the party is not moving, no messages are
displayed.
@@ -160,7 +179,9 @@ encounter on the pillar, and have a node of this type be the first special node
called. Set Extra 1a to 0 and Extra 2a to 1.
Note:This doesn't have to be the last node in a chain. If this is the first
special node called, the party is still kept from entering the space... unless another
-node of the same type undoes it.
+node of the same type undoes it. The only exception to this is forced passage, as the node
+usually won't even be called if the space is impassable.
+Warning:Don't call this while talking. It will mess up your dialogue strings.
Type 12: Change TimeThis special node sets the adventure time forward.
@@ -1681,6 +1702,18 @@ correctly even for the rotateable wall.
7 - rotateable 2x8 wall
Extra 1b:Maximum range (combat mode only)
Extra 1c:Maximum number of targets (combat mode only)
+Extra 2a:A special node to call if the targeting fails because the player
+selected an invalid space, or because a special node on the space cancelled it.
+Extra 2b:Set this to 1 if you'd like the user to be able to select spaces
+that are opaque (like the spell Dispel Barrier). This only has an effect in combat mode,
+as town targeting places no such restriction.
+Extra 2c:Set this to 1 if you'd like the user to be able to target spaces
+that are covered by an antimagic field. In order to preserve the meaning of antimagic
+fields, it's recommended that you never use this unless the associated action is not
+intended to be magical. Set it to 0 to prevent the user from targeting spaces in an
+antimagic field. If left at -1, the default is used, which is to allow it in town mode but
+not in combat. (This is probably a relic of the extremely limited selection of spells that
+can be targeted in town mode.)
JumpTo:This node is called once for every selected target.
Type 201: Place Pattern (Fields/Objects)Places fields according to a preset
diff --git a/rsrc/strings/specials-text-general.txt b/rsrc/strings/specials-text-general.txt
index 4ece2c61..90dbc918 100644
--- a/rsrc/strings/specials-text-general.txt
+++ b/rsrc/strings/specials-text-general.txt
@@ -174,7 +174,7 @@ Unused
Unused
Special to Jump To
--------------------
-Can't Enter
+Prevent Action
Unused
Unused
First part of message
@@ -182,7 +182,7 @@ Second part of message
Unused
Unused
Unused
-0 - can enter, 1 - no enter
+0 - allow, 1 - prevent
Unused
Unused
0 - don't force, 1 - force if blocked
diff --git a/rsrc/strings/specials-text-town.txt b/rsrc/strings/specials-text-town.txt
index 6a0409f0..f9223ddc 100644
--- a/rsrc/strings/specials-text-town.txt
+++ b/rsrc/strings/specials-text-town.txt
@@ -489,9 +489,9 @@ Unused
Which spell pattern? (0 - single space)
Max range? (ignored outside combat mode)
Max targets? (>1 only in combat)
-Unused
-Unused
-Unused
+Special Called if Targeting Fails
+if 1, allow obstructed spaces
+if 1, allow targeting in antimagic fields
Special to Call for Each Target
--------------------
Place Fields in Spell Pattern
diff --git a/src/boe.actions.cpp b/src/boe.actions.cpp
index 8f9293b0..31a3cee9 100644
--- a/src/boe.actions.cpp
+++ b/src/boe.actions.cpp
@@ -278,6 +278,7 @@ bool prime_time() {
static void handle_spellcast(eSkill which_type, bool& did_something, bool& need_redraw, bool& need_reprint) {
short store_sp[6];
+ extern short spec_target_type, spec_target_fail;
if(!someone_awake()) {
ASB("Everyone's asleep/paralyzed.");
need_reprint = true;
@@ -302,6 +303,9 @@ static void handle_spellcast(eSkill which_type, bool& did_something, bool& need_
overall_mode = MODE_TOWN;
need_redraw = true;
need_reprint = true;
+ extern eSpell town_spell;
+ if(town_spell == eSpell::NONE)
+ queue_special(eSpecCtx::TARGET, spec_target_type, spec_target_fail, univ.town.p_loc);
} else if(overall_mode == MODE_COMBAT) {
if(which_type == eSkill::MAGE_SPELLS) {
did_something = combat_cast_mage_spell();
@@ -319,6 +323,9 @@ static void handle_spellcast(eSkill which_type, bool& did_something, bool& need_
center = univ.party[current_pc].combat_pos;
pause(10);
need_redraw = true;
+ extern eSpell spell_being_cast;
+ if(spell_being_cast == eSpell::NONE)
+ queue_special(eSpecCtx::TARGET, spec_target_type, spec_target_fail, univ.party[current_pc].combat_pos);
}
put_pc_screen();
put_item_screen(stat_window,0);
diff --git a/src/boe.combat.cpp b/src/boe.combat.cpp
index 4a76c4bb..5ead0da8 100644
--- a/src/boe.combat.cpp
+++ b/src/boe.combat.cpp
@@ -43,10 +43,11 @@ extern bool in_scen_debug;
extern short fast_bang;
extern short store_current_pc;
extern short combat_posing_monster , current_working_monster ; // 0-5 PC 100 + x - monster x
-extern short spell_caster, missile_firer,current_monst_tactic;
+extern short missile_firer,current_monst_tactic;
eSpell spell_being_cast;
bool spell_freebie;
short missile_inv_slot, ammo_inv_slot;
+short spell_caster, spec_target_type, spec_target_fail, spec_target_options;
short force_wall_position = 10; // 10 -> no force wall
bool processing_fields = true;
short futzing;
@@ -860,6 +861,8 @@ void pc_attack_weapon(short who_att,iLiving& target,short hit_adj,short dam_adj,
univ.party.force_ptr(22, target.get_loc().y);
univ.party.force_ptr(20, i_monst);
run_special(eSpecCtx::ATTACKING_MELEE, 0, weap.abil_data[0],univ.party[who_att].combat_pos, &s1, &s2, &s3);
+ if(s1 > 0)
+ univ.party[who_att].ap += 4;
}
}
else {
@@ -946,6 +949,11 @@ short calc_spec_dam(eItemAbil abil,short abil_str,short abil_dat,iLiving& monst,
void place_target(location target) {
short i;
+ bool allow_obstructed = false, allow_antimagic = false;
+ if(spell_being_cast == eSpell::DISPEL_BARRIER || (spell_being_cast == eSpell::NONE && spec_target_options % 10 == 1))
+ allow_obstructed = true;
+ if(spell_being_cast == eSpell::NONE && spec_target_options / 10 == 2)
+ allow_antimagic = true;
if(num_targets_left > 0) {
if(loc_off_act_area(target)) {
@@ -960,11 +968,11 @@ void place_target(location target) {
add_string_to_buf(" Target out of range.");
return;
}
- if(sight_obscurity(target.x,target.y) == 5 && spell_being_cast != eSpell::DISPEL_BARRIER) {
+ if(!allow_obstructed && sight_obscurity(target.x,target.y) == 5) {
add_string_to_buf(" Target space obstructed.");
return;
}
- if(univ.town.is_antimagic(target.x,target.y)) {
+ if(univ.town.is_antimagic(target.x,target.y) && !allow_antimagic) {
add_string_to_buf(" Target in antimagic field.");
return;
}
@@ -1010,6 +1018,11 @@ void do_combat_cast(location target) {
mon_num_t summon;
iLiving* victim;
cPlayer& caster = univ.party[current_pc];
+ bool allow_obstructed = false, allow_antimagic = false;
+ if(spell_being_cast == eSpell::DISPEL_BARRIER || (spell_being_cast == eSpell::NONE && spec_target_options % 10 == 1))
+ allow_obstructed = true;
+ if(spell_being_cast == eSpell::NONE && spec_target_options / 10 == 2)
+ allow_antimagic = false;
location ashes_loc;
@@ -1071,8 +1084,12 @@ void do_combat_cast(location target) {
adjust = can_see_light(caster.combat_pos, target, sight_obscurity);
// TODO: Should we do this here? Or in the handling of targeting modes?
// (It really depends whether we want to be able to trigger it for targeting something other than a spell.)
- if(adjust <= 4 && !cast_spell_on_space(target, spell_being_cast))
+ if(adjust <= 4 && !cast_spell_on_space(target, spell_being_cast)) {
+ queue_special(eSpecCtx::TARGET, spec_target_type, spec_target_fail, target);
continue; // The special node intercepted and cancelled regular spell behaviour.
+ }
+
+ bool failed = spell_being_cast == eSpell::NONE;
if(adjust > 4) {
add_string_to_buf(" Can't see target.");
@@ -1080,13 +1097,14 @@ void do_combat_cast(location target) {
else if(loc_off_act_area(target)) {
add_string_to_buf(" Space not in town.");
}
- else if(dist(caster.combat_pos,target) > (*spell_being_cast).range)
+ else if(dist(caster.combat_pos,target) > current_spell_range)
add_string_to_buf(" Target out of range.");
- else if(sight_obscurity(target.x,target.y) == 5 && spell_being_cast != eSpell::DISPEL_BARRIER)
+ else if(sight_obscurity(target.x,target.y) == 5 && !allow_obstructed)
add_string_to_buf(" Target space obstructed.");
- else if(univ.town.is_antimagic(target.x,target.y))
+ else if(univ.town.is_antimagic(target.x,target.y) && !allow_antimagic)
add_string_to_buf(" Target in antimagic field.");
else {
+ failed = false;
if(!ap_taken) {
if(!freebie)
take_ap(5);
@@ -1096,7 +1114,9 @@ void do_combat_cast(location target) {
boom_targ[i] = target;
switch(spell_being_cast) {
case eSpell::NONE: // Not a spell but a special node targeting
- run_special(eSpecCtx::TARGET, spell_caster / 1000, spell_caster % 1000, target, &r1, &r2, &item);
+ r1 = item = 0;
+ run_special(eSpecCtx::TARGET, spec_target_type, spell_caster, target, &r1, &r2, &item);
+ failed = r1;
if(item > 0) redraw_screen(REFRESH_ALL);
break;
case eSpell::GOO: case eSpell::WEB: case eSpell::GOO_BOMB:
@@ -1552,6 +1572,8 @@ void do_combat_cast(location target) {
}
}
}
+ if(failed)
+ queue_special(eSpecCtx::TARGET, spec_target_type, spec_target_fail, target);
}
do_missile_anim((num_targets > 1) ? 35 : 60,caster.combat_pos,store_sound);
@@ -1840,6 +1862,8 @@ void fire_missile(location target) {
univ.party.force_ptr(22, victim->get_loc().y);
univ.party.force_ptr(20, univ.get_target_i(*victim));
run_special(eSpecCtx::ATTACKING_RANGE, 0, missile.abil_data[0], missile_firer.combat_pos, &s1, &s2, &s3);
+ if(s1 > 0)
+ missile_firer.ap += (overall_mode == MODE_FIRING) ? 3 : 2;
}
cCreature* monst; cPlayer* pc; int spec_item;
if((monst = dynamic_cast(victim)) && monst->abil[eMonstAbil::HIT_TRIGGER].active) {
@@ -1848,12 +1872,16 @@ void fire_missile(location target) {
univ.party.force_ptr(22, monst->cur_loc.y);
univ.party.force_ptr(20, univ.get_target_i(*monst));
run_special(eSpecCtx::ATTACKED_RANGE, 0, monst->abil[eMonstAbil::HIT_TRIGGER].special.extra1, missile_firer.combat_pos, &s1, &s2, &s3);
+ if(s1 > 0)
+ missile_firer.ap += (overall_mode == MODE_FIRING) ? 3 : 2;
} else if((pc = dynamic_cast(victim)) && (spec_item = pc->has_abil_equip(eItemAbil::HIT_CALL_SPECIAL)) < 24) {
short s1,s2,s3;
univ.party.force_ptr(21, pc->combat_pos.x);
univ.party.force_ptr(22, pc->combat_pos.y);
univ.party.force_ptr(20, univ.get_target_i(*pc));
run_special(eSpecCtx::ATTACKED_RANGE, 0, pc->items[spec_item].abil_data[0], missile_firer.combat_pos, &s1, &s2, &s3);
+ if(s1 > 0)
+ missile_firer.ap += (overall_mode == MODE_FIRING) ? 3 : 2;
}
}
}
@@ -2464,16 +2492,7 @@ void do_monster_turn() {
print_monst_name(cur_monst->number);
monst_fire_missile(i/*,cur_monst->skill*/,cur_monst->status[eStatus::BLESS_CURSE],
pick_abil,cur_monst->cur_loc,&univ.get_target(target));
- if(pick_abil.first == eMonstAbil::MISSILE) {
- if(pick_abil.second.missile.type == eMonstMissile::ARROW || pick_abil.second.missile.type == eMonstMissile::BOLT
- || pick_abil.second.missile.type == eMonstMissile::SPINE || pick_abil.second.missile.type == eMonstMissile::BOULDER)
- take_m_ap(3,cur_monst);
- else take_m_ap(2,cur_monst);
- } else if(pick_abil.first == eMonstAbil::RAY_HEAT)
- take_m_ap(1,cur_monst);
- else if(pick_abil.first == eMonstAbil::DAMAGE2)
- take_m_ap(4,cur_monst);
- else take_m_ap(3,cur_monst);
+ take_m_ap(pick_abil.second.get_ap_cost(pick_abil.first), cur_monst);
had_monst = true;
acted_yet = true;
}
@@ -2489,7 +2508,8 @@ void do_monster_turn() {
univ.party.force_ptr(20, 11 + target); // ready to be passed to SELECT_TARGET node
else univ.party.force_ptr(20, target); // ready to be passed to SELECT_TARGET node
run_special(eSpecCtx::MONST_SPEC_ABIL,0,abil.special.extra1,cur_monst->cur_loc,&s1,&s2,&s3);
- take_m_ap(abil.special.extra2,cur_monst);
+ if(s1 <= 0)
+ take_m_ap(abil.special.extra2,cur_monst);
}
} // Special attacks
@@ -2943,12 +2963,16 @@ void monster_attack(short who_att,iLiving* target) {
univ.party.force_ptr(22, target->get_loc().y);
univ.party.force_ptr(20, i_monst);
run_special(eSpecCtx::ATTACKED_MELEE, 0, pc_target->items[spec_item].abil_data[0], attacker->cur_loc, &s1, &s2, &s3);
+ if(s1 > 0)
+ attacker->ap += 4;
} else if(m_target != nullptr && m_target->abil[eMonstAbil::HIT_TRIGGER].active) {
short s1,s2,s3;
univ.party.force_ptr(21, target->get_loc().x);
univ.party.force_ptr(22, target->get_loc().y);
univ.party.force_ptr(20, i_monst);
run_special(eSpecCtx::ATTACKED_MELEE, 0, m_target->abil[eMonstAbil::HIT_TRIGGER].special.extra1, attacker->cur_loc, &s1, &s2, &s3);
+ if(s1 > 0)
+ attacker->ap += 4;
}
}
}
@@ -3081,6 +3105,8 @@ void monst_fire_missile(short m_num,short bless,std::pair a
univ.party.force_ptr(22, target->get_loc().y);
univ.party.force_ptr(20, i_monst);
run_special(eSpecCtx::ATTACKED_RANGE, 0, pc_target->items[spec_item].abil_data[0], univ.town.monst[m_num].cur_loc, &s1, &s2, &s3);
+ if(s1 > 0)
+ univ.town.monst[m_num].ap += abil.second.get_ap_cost(abil.first);
}
} else if(m_target != nullptr && m_target->abil[eMonstAbil::HIT_TRIGGER].active) {
short s1,s2,s3;
@@ -3088,6 +3114,8 @@ void monst_fire_missile(short m_num,short bless,std::pair a
univ.party.force_ptr(22, m_target->cur_loc.y);
univ.party.force_ptr(20, i_monst);
run_special(eSpecCtx::ATTACKED_RANGE, 0, m_target->abil[eMonstAbil::HIT_TRIGGER].special.extra1, univ.town.monst[m_num].cur_loc, &s1, &s2, &s3);
+ if(s1 > 0)
+ univ.town.monst[m_num].ap += abil.second.get_ap_cost(abil.first);
}
} else if(abil.first == eMonstAbil::MISSILE_WEB) {
if(pc_target != nullptr)
@@ -5100,7 +5128,8 @@ void start_spell_targeting(eSpell num, bool freebie, int spell_range, eSpellPat
spell_freebie = freebie;
add_string_to_buf(" Target spell.");
- if(isMage(num))
+ if(num == eSpell::NONE);
+ else if(isMage(num))
add_string_to_buf(" (Hit 'm' to cancel.)");
else add_string_to_buf(" (Hit 'p' to cancel.)");
overall_mode = MODE_SPELL_TARGET;
@@ -5163,7 +5192,8 @@ void start_fancy_spell_targeting(eSpell num, bool freebie, int spell_range, eSpe
for(i = 0; i < 8; i++)
spell_targets[i] = null_loc;
add_string_to_buf(" Target spell.");
- if(isMage(num))
+ if(num == eSpell::NONE);
+ else if(isMage(num))
add_string_to_buf(" (Hit 'm' to cancel.)");
else add_string_to_buf(" (Hit 'p' to cancel.)");
add_string_to_buf(" (Hit space to cast.)");
diff --git a/src/boe.dlgutil.cpp b/src/boe.dlgutil.cpp
index 5e164079..c6903b30 100644
--- a/src/boe.dlgutil.cpp
+++ b/src/boe.dlgutil.cpp
@@ -248,7 +248,6 @@ void handle_shop_event(location p) {
}
void handle_sale(cShopItem item, int i) {
- short s1, s2, s3; // Dummy variables to pass to run_special
cItem base_item = item.item;
short cost = item.getCost(active_shop.getCostAdjust());
rectangle dummy_rect = {0,0,0,0};
@@ -368,7 +367,13 @@ void handle_sale(cShopItem item, int i) {
}
break;
case eShopItemType::CALL_SPECIAL:
- run_special(eSpecCtx::SHOPPING, 0, base_item.item_level, {0,0}, &s1, &s2, &s3);
+ if(univ.party.gold < cost)
+ ASB("Not enough gold.");
+ else {
+ short s1, s2, s3;
+ run_special(eSpecCtx::SHOPPING, 0, base_item.item_level, {0,0}, &s1, &s2, &s3);
+ if(s1 <= 0) take_gold(cost,false);
+ }
break;
case eShopItemType::SKILL:
if(base_item.item_level < 0 || base_item.item_level > 18) {
diff --git a/src/boe.main.cpp b/src/boe.main.cpp
index b10d4f8d..b3ee75c4 100644
--- a/src/boe.main.cpp
+++ b/src/boe.main.cpp
@@ -98,7 +98,7 @@ location center;
short current_pc;
short combat_active_pc;
effect_pat_type current_pat;
-short spell_caster, missile_firer,current_monst_tactic;
+short missile_firer,current_monst_tactic;
short store_current_pc = 0;
sf::Clock animTimer;
diff --git a/src/boe.party.cpp b/src/boe.party.cpp
index bf2683ef..8957112c 100644
--- a/src/boe.party.cpp
+++ b/src/boe.party.cpp
@@ -50,6 +50,7 @@ short combat_percent[20] = {
short who_cast,which_pc_displayed;
eSpell town_spell;
extern bool spell_freebie;
+extern short spec_target_type, spec_target_fail, spec_target_options;
bool spell_button_active[90];
extern short fast_bang;
@@ -1304,6 +1305,8 @@ void cast_town_spell(location where) {
(where.y <= univ.town->in_town_rect.top) ||
(where.y >= univ.town->in_town_rect.bottom)) {
add_string_to_buf(" Can't target outside town.");
+ if(town_spell == eSpell::NONE)
+ run_special(eSpecCtx::TARGET, spec_target_type, spec_target_fail, where, &r1, &store, &adjust);
return;
}
@@ -1320,14 +1323,25 @@ void cast_town_spell(location where) {
// TODO: Should we do this here? Or in the handling of targeting modes?
// (It really depends whether we want to be able to trigger it for targeting something other than a spell.)
- if(adjust <= 4 && !cast_spell_on_space(where, town_spell))
- return; // The special node intercepted and cancelled regular spell behaviour.
+ if(adjust <= 4 && !cast_spell_on_space(where, town_spell)) {
+ // The special node intercepted and cancelled regular spell behaviour.
+ queue_special(eSpecCtx::TARGET, spec_target_type, spec_target_fail, where);
+ return;
+ }
+ if(spec_target_options / 10 == 1 && univ.town.is_antimagic(where.x,where.y)) {
+ add_string_to_buf(" Target in antimagic field.");
+ queue_special(eSpecCtx::TARGET, spec_target_type, spec_target_fail, where);
+ return;
+ }
+
+ bool failed = town_spell == eSpell::NONE && adjust > 4;
if(adjust > 4)
add_string_to_buf(" Can't see target.");
else switch(town_spell) {
case eSpell::NONE: // Not a spell but a special node targeting
- run_special(eSpecCtx::TARGET, spell_caster / 1000, spell_caster % 1000, where, &r1, &adjust, &store);
+ r1 = store = 0;
+ run_special(eSpecCtx::TARGET, spec_target_type, spell_caster, where, &r1, &adjust, &store);
if(store > 0) redraw_screen(REFRESH_ALL);
break;
case eSpell::SCRY_MONSTER: case eSpell::CAPTURE_SOUL:
@@ -1446,6 +1460,8 @@ void cast_town_spell(location where) {
break;
}
+ if(failed)
+ queue_special(eSpecCtx::TARGET, spec_target_type, spec_target_fail, where);
}
// TODO: Currently, the node is called before any spell-specific behaviour (eg missiles) occurs.
diff --git a/src/boe.specials.cpp b/src/boe.specials.cpp
index bbfa906e..ea915585 100644
--- a/src/boe.specials.cpp
+++ b/src/boe.specials.cpp
@@ -41,7 +41,7 @@ extern effect_pat_type single,t,square,radius2,radius3,small_square,open_square,
extern effect_pat_type current_pat;
extern cOutdoors::cWandering store_wandering_special;
extern eSpell spell_being_cast, town_spell;
-extern short spell_caster;
+extern short spell_caster, spec_target_fail, spec_target_type, spec_target_options;
extern sf::RenderWindow mini_map;
extern short fast_bang;
extern bool end_scenario;
@@ -192,11 +192,11 @@ bool check_special_terrain(location where_check,eSpecCtx mode,cPlayer& which_pc,
return false; // TODO: Maybe replace eTrimType::CITY check with a blockage == clear/special && is_special() check?
}
- if(univ.town.is_force_barr(where_check.x,where_check.y)) {
+ if(mode != eSpecCtx::OUT_MOVE && univ.town.is_force_barr(where_check.x,where_check.y)) {
add_string_to_buf(" Magic barrier!");
can_enter = false;
}
- if(univ.town.is_force_cage(where_check.x,where_check.y)) {
+ if(mode != eSpecCtx::OUT_MOVE && univ.town.is_force_cage(where_check.x,where_check.y)) {
add_string_to_buf(" Force cage!");
can_enter = false;
}
@@ -1076,6 +1076,8 @@ void use_item(short pc,short item) {
case eItemAbil::CALL_SPECIAL:
// TODO: Should this have its own separate eSpecCtx?
run_special(eSpecCtx::USE_SPEC_ITEM,0,str,user_loc,&sp[0],&sp[1],&sp[2]);
+ if(sp[0] > 0)
+ take_charge = false;
break;
case eItemAbil::CAST_SPELL:
@@ -4159,7 +4161,16 @@ void townmode_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
else if(spec.ex1c > 1)
start_fancy_spell_targeting(eSpell::NONE, true, spec.ex1b, eSpellPat(spec.ex1a), spec.ex1c);
else start_spell_targeting(eSpell::NONE, true, spec.ex1b, eSpellPat(spec.ex1a));
- spell_caster = spec.jumpto + cur_spec_type * 1000;
+ spell_caster = spec.jumpto;
+ spec_target_type = cur_spec_type;
+ spec_target_fail = spec.ex2a;
+ spec_target_options = 0;
+ if(spec.ex2b > 0)
+ spec_target_options += 1;
+ if(spec.ex2c > 0)
+ spec_target_options += 20;
+ else if(spec.ex2c == 0)
+ spec_target_options += 10;
break;
case eSpecType::TOWN_SPELL_PAT_FIELD:
if(spec.ex1c < -1 || spec.ex1c > 14) {
diff --git a/src/classes/monster.cpp b/src/classes/monster.cpp
index 8055ec61..23cda50a 100644
--- a/src/classes/monster.cpp
+++ b/src/classes/monster.cpp
@@ -989,6 +989,41 @@ std::string uAbility::to_string(eMonstAbil key) const {
return sout.str();
}
+// Returns the action point cost, or one of the following magic values:
+// 0 - passive ability
+// -1 - part of normal attack
+// -256 - unknown (error)
+int uAbility::get_ap_cost(eMonstAbil key) const {
+ switch(key) {
+ case eMonstAbil::MISSILE:
+ switch(missile.type) {
+ case eMonstMissile::ARROW: case eMonstMissile::BOLT: case eMonstMissile::SPINE: case eMonstMissile::BOULDER:
+ return 3;
+ default:
+ return 2;
+ }
+ case eMonstAbil::RAY_HEAT:
+ return 1;
+ case eMonstAbil::DAMAGE2:
+ return 4;
+ case eMonstAbil::DAMAGE: case eMonstAbil::STATUS: case eMonstAbil::STATUS2: case eMonstAbil::STUN:
+ case eMonstAbil::FIELD: case eMonstAbil::PETRIFY: case eMonstAbil::DRAIN_SP: case eMonstAbil::DRAIN_XP:
+ case eMonstAbil::KILL: case eMonstAbil::STEAL_FOOD: case eMonstAbil::STEAL_GOLD:
+ return gen.type == eMonstGen::TOUCH ? -1 : 3;
+ case eMonstAbil::MISSILE_WEB:
+ return 3;
+ case eMonstAbil::SPECIAL:
+ return special.extra2;
+ case eMonstAbil::ABSORB_SPELLS: case eMonstAbil::DEATH_TRIGGER: case eMonstAbil::HIT_TRIGGER:
+ case eMonstAbil::MARTYRS_SHIELD: case eMonstAbil::RADIATE: case eMonstAbil::SPLITS:
+ case eMonstAbil::SUMMON:
+ return 0;
+ case eMonstAbil::NO_ABIL:
+ return -256;
+ }
+// return -256;
+}
+
void cMonster::writeTo(std::ostream& file) const {
file << "MONSTER " << maybe_quote_string(m_name) << '\n';
file << "LEVEL " << int(level) << '\n';
diff --git a/src/classes/monster.hpp b/src/classes/monster.hpp
index fd89b4fd..e9b885b9 100644
--- a/src/classes/monster.hpp
+++ b/src/classes/monster.hpp
@@ -98,6 +98,7 @@ union uAbility {
int extra1, extra2, extra3;
} special;
std::string to_string(eMonstAbil myKey) const;
+ int get_ap_cost(eMonstAbil key) const;
};
class cMonster {
diff --git a/src/scenedit/scen.core.cpp b/src/scenedit/scen.core.cpp
index c4c8e040..b27721aa 100644
--- a/src/scenedit/scen.core.cpp
+++ b/src/scenedit/scen.core.cpp
@@ -983,24 +983,14 @@ static void fill_monst_abil_detail(cDialog& me, cMonster& monst, eMonstAbil abil
// These names start at line 80 in the strings file, but the first valid ability is ID 1, so add 79.
me["type"].setText(get_str("monster-abilities", 79 + int(abil)));
// Action points
- if(cat == eMonstAbilCat::MISSILE) {
- if(detail.missile.type == eMonstMissile::ARROW || detail.missile.type == eMonstMissile::BOLT || detail.missile.type == eMonstMissile::SPINE || detail.missile.type == eMonstMissile::BOULDER)
- me["ap"].setTextToNum(3);
- else me["ap"].setTextToNum(2);
- } else if(cat == eMonstAbilCat::GENERAL) {
- if(detail.gen.type == eMonstGen::TOUCH)
+ if(abil != eMonstAbil::RADIATE && abil != eMonstAbil::RADIATE) {
+ int ap = detail.get_ap_cost(abil);
+ if(ap == 0)
+ me["ap"].setText("0 (passive ability)");
+ else if(ap == -1)
me["ap"].setText("0 (part of standard attack)");
- else if(abil == eMonstAbil::DAMAGE2)
- me["ap"].setTextToNum(4);
- else me["ap"].setTextToNum(3);
- } else if(abil == eMonstAbil::RAY_HEAT)
- me["ap"].setTextToNum(1);
- else if(abil == eMonstAbil::MISSILE_WEB)
- me["ap"].setTextToNum(3);
- else if(abil == eMonstAbil::SPECIAL)
- me["ap"].setTextToNum(detail.special.extra2);
- else if(cat == eMonstAbilCat::SPECIAL)
- me["ap"].setText("0 (passive ability)");
+ else me["ap"].setTextToNum(ap);
+ }
// Subtype
if(cat == eMonstAbilCat::MISSILE)
me["subtype"].setText(get_str("monster-abilities", 110 + int(detail.missile.type)));