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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.)");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)));
|
||||
|
||||
Reference in New Issue
Block a user