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 Enter
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. +
Type 11: Prevent Action
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: +
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 Time
This 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)));