Expand three special node types

Can't Enter node renamed to Prevent Action, as it's a more accurate description of what it does. It has been expanded to cover the following cases, all of which are documented:
- When the special node was called during an attack action (which involves a weapon whose ability is to call a special node when attacking, or an item or monster ability that calls a special node when hit), then Prevent Action reverses the action point cost.
- When called as the result of a purchase (a shop item that calls a special), then Prevent Action prevents gold from being deducted (which is also new in this commit, as before it never deducted gold)
- When called as the result of using a normal item (not a special item), then Prevent Action prevents a charge from being deducted
- When called as the result of a monster using its ability, then Prevent Action prevents the action points from being deducted
- Cases it already covered (cancelling initiation of talk mode, searching of containers, outdoor wandering encounters) have been documented
- The fact that it will break things during talk mode is also documented now

Start Spell Targeting node has been tweaked and gained some new options:
- You can allow the player to target opaque or antimagic spaces. In town mode, you can prohibit them from targeting antimagic spaces.
- You can specify a special node to be called if targeting fails because they selected an invalid space, or because an special node keyed to spellcasting context cancelled it
- You no longer get a "Hit 'p' to cancel" message. Even better, hitting 'p' does not cancel it. (Well, more precisely, it triggers the failure node, with the party's or pc's location as the target space.)

Misc:
- Fix crash outdoors due to trying to check for force barriers
This commit is contained in:
2015-06-10 22:51:53 -04:00
parent a5a784c4f1
commit 6ef885c3ef
12 changed files with 184 additions and 56 deletions

View File

@@ -133,8 +133,27 @@ one at a time.
<dt>Mess2, Mess3:</dt><dd>The number of the first and last string to show in the dialog.</dd>
<dt>Pict, Pictype:</dt><dd>Specifies the icon to show in the dialog.</dd></dd>
<dt>Type 11: Can't Enter</dt><dd>If the party is walking, they are not allowed to enter
the space. This can be accompanied by one or two messages, if you wish.
<dt>Type 11: Prevent Action</dt><dd>If 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:
<ul>
<li>Movement</li>
<li>Using an item - if it's a normal (not special) item, no charge will be deducted</li>
<li>Monster special actions - no AP will be deducted</li>
<li>Looking - you will not be allowed to search, but you can't prevent the party from
seeing what the space is</li>
<li>Talking - even if the creature has a personality, talk mode will not start</li>
<li>Attacking - the attack will occur normally, but no AP will be deducted from the
attacker</li>
<li>Shop Items - gold will not be deducted</li>
<!--<li>Dropping - TODO: currently unsupported, but maybe should be supported?</li> -->
<li>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.</li>
<li>Encountering wandering monsters - the encounter will be prevented altogether, similar
to if the monsters had fled.</li>
</ul>
<dl>
<dt>Mess1, Mess2:</dt><dd>Standard usage. If the party is not moving, no messages are
displayed.</dd>
@@ -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.</dd>
<dt>Note:</dt><dd>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.</dd></dd>
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.</dd>
<dt>Warning:</dt><dd>Don't call this while talking. It will mess up your dialogue strings.</dd></dd>
<dt>Type 12: Change Time</dt><dd>This special node sets the adventure time forward.
<dl>
@@ -1681,6 +1702,18 @@ correctly even for the rotateable wall.
7 - rotateable 2x8 wall</dd>
<dt>Extra 1b:</dt><dd>Maximum range (combat mode only)</dd>
<dt>Extra 1c:</dt><dd>Maximum number of targets (combat mode only)</dd>
<dt>Extra 2a:</dt><dd>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.</dd>
<dt>Extra 2b:</dt><dd>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.</dd>
<dt>Extra 2c:</dt><dd>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.)</dd>
<dt>JumpTo:</dt><dd>This node is called once for every selected target.</dd></dd>
<dt>Type 201: Place Pattern (Fields/Objects)</dt><dd>Places fields according to a preset

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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<cCreature*>(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<cPlayer*>(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<eMonstAbil,uAbility> 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<eMonstAbil,uAbility> 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.)");

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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)));