diff --git a/rsrc/strings/specials-opcodes.txt b/rsrc/strings/specials-opcodes.txt index a80c1497..c7b0e0e8 100644 --- a/rsrc/strings/specials-opcodes.txt +++ b/rsrc/strings/specials-opcodes.txt @@ -197,9 +197,9 @@ set-lighting set-attitude set-center lift-fog - - - +start-target +spell-pat-field +spell-pat-boom diff --git a/rsrc/strings/specials-text-town.txt b/rsrc/strings/specials-text-town.txt index c9b40aa0..ef7d514a 100644 --- a/rsrc/strings/specials-text-town.txt +++ b/rsrc/strings/specials-text-town.txt @@ -478,3 +478,51 @@ Unused Unused Special to Jump To -------------------- +Start Targeting +Unused +Unused +First part of message +Second part of message +Unused +Unused +Unused +Which spell pattern? (0 - single space) +Max range? (ignored outside combat mode) +Max targets? (>1 only in combat) +Unused +Unused +Unused +Special to Call for Each Target +-------------------- +Place Fields in Spell Pattern +Unused +Unused +First part of message +Second part of message +Unused +Unused +Unused +X Coordinate of pattern center +Y Coordinate of pattern center +Which spell pattern? (-1 for targeted pattern) +Which field type? +Unused +Unused +Special to Jump To +-------------------- +Deal Damage in Spell Pattern +Unused +Unused +First part of message +Second part of message +Unused +Unused +Unused +X Coordinate of pattern center +Y Coordinate of pattern center +Which spell pattern? (-1 for targeted pattern) +Which damage type? +How many dice? (calculated in d6's) +0 - sequential simple booms, 1 - simultaneous animated booms +Special to Jump To +-------------------- diff --git a/src/boe.combat.cpp b/src/boe.combat.cpp index 86b6ff9e..1bd34ca0 100644 --- a/src/boe.combat.cpp +++ b/src/boe.combat.cpp @@ -904,7 +904,8 @@ void do_combat_cast(location target) { num_targets = 8; } - spell_caster = current_pc; + if(spell_being_cast != eSpell::NONE) + spell_caster = current_pc; // assign monster summoned, if summoning if(spell_being_cast == eSpell::SUMMON_BEAST) { @@ -954,7 +955,10 @@ 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, which_combat_type + 1, spell_caster, target, &r1, &r2, &item); + if(item > 0) redraw_screen(REFRESH_ALL); + break; case eSpell::GOO: case eSpell::WEB: case eSpell::GOO_BOMB: place_spell_pattern(current_pat,target,FIELD_WEB,current_pc); break; @@ -4614,7 +4618,7 @@ void combat_immed_priest_cast(short current_pc, eSpell spell_num, bool freebie) end_missile_anim(); } -void start_spell_targeting(eSpell num, bool freebie) { +void start_spell_targeting(eSpell num, bool freebie, int spell_range, eSpellPat pat) { // First, remember what spell was cast. spell_being_cast = num; @@ -4625,7 +4629,7 @@ void start_spell_targeting(eSpell num, bool freebie) { add_string_to_buf(" (Hit 'm' to cancel.)"); else add_string_to_buf(" (Hit 'p' to cancel.)"); overall_mode = MODE_SPELL_TARGET; - current_spell_range = (*num).range; + current_spell_range = num == eSpell::NONE ? spell_range : (*num).range; current_pat = single; switch(num) { // Different spells have different messages and diff. target shapes @@ -4649,10 +4653,26 @@ void start_spell_targeting(eSpell num, bool freebie) { force_wall_position = 0; current_pat = field[0]; break; + case eSpell::NONE: + switch(pat) { + case PAT_SINGLE: current_pat = single; break; + case PAT_SQ: current_pat = square; break; + case PAT_SMSQ: current_pat = small_square; break; + case PAT_OPENSQ: current_pat = open_square; break; + case PAT_PLUS: current_pat = t; break; + case PAT_RAD2: current_pat = radius2; break; + case PAT_RAD3: current_pat = radius3; break; + case PAT_WALL: + add_string_to_buf(" (Hit space to rotate.)"); + force_wall_position = 0; + current_pat = field[0]; + break; + } + break; } } -void start_fancy_spell_targeting(eSpell num, bool freebie) { +void start_fancy_spell_targeting(eSpell num, bool freebie, int spell_range, eSpellPat pat, int targets) { short i; location null_loc(120,0); @@ -4669,7 +4689,7 @@ void start_fancy_spell_targeting(eSpell num, bool freebie) { add_string_to_buf(" (Hit space to cast.)"); overall_mode = MODE_FANCY_TARGET; current_pat = single; - current_spell_range = (*num).range; + current_spell_range = num == eSpell::NONE ? spell_range : (*num).range; switch(num) { // Assign special targeting shapes and number of targets case eSpell::SMITE: @@ -4703,6 +4723,19 @@ void start_fancy_spell_targeting(eSpell num, bool freebie) { case eSpell::SUMMON_MAJOR: num_targets_left = minmax(1,5,univ.party[current_pc].level / 8 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 2); break; + case eSpell::NONE: + num_targets_left = minmax(1,8,targets); + switch(pat) { + case PAT_SINGLE: current_pat = single; break; + case PAT_SQ: current_pat = square; break; + case PAT_SMSQ: current_pat = small_square; break; + case PAT_OPENSQ: current_pat = open_square; break; + case PAT_PLUS: current_pat = t; break; + case PAT_RAD2: current_pat = radius2; break; + case PAT_RAD3: current_pat = radius3; break; + case PAT_WALL: current_pat = single; break; // Fancy targeting doesn't support the rotateable pattern + } + break; } num_targets_left = minmax(1,8,num_targets_left); diff --git a/src/boe.combat.h b/src/boe.combat.h index 849dc6ca..65d1366d 100644 --- a/src/boe.combat.h +++ b/src/boe.combat.h @@ -6,6 +6,7 @@ #include "monster.h" #include "outdoors.h" #include "boe.global.h" +#include "spell.hpp" void start_outdoor_combat(cOutdoors::cCreature encounter,ter_num_t in_which_terrain,short num_walls); bool pc_combat_move(location destination); @@ -56,8 +57,8 @@ bool combat_cast_mage_spell(); bool combat_cast_priest_spell(); void combat_immed_mage_cast(short current_pc, eSpell spell_num, bool freebie = false); void combat_immed_priest_cast(short current_pc, eSpell spell_num, bool freebie = false); -void start_spell_targeting(eSpell num, bool freebie = false); -void start_fancy_spell_targeting(eSpell num, bool freebie = false); +void start_spell_targeting(eSpell num, bool freebie = false, int spell_range = 4, eSpellPat pat = PAT_SINGLE); +void start_fancy_spell_targeting(eSpell num, bool freebie = false, int spell_range = 4, eSpellPat pat = PAT_SINGLE, int targets = 1); void spell_cast_hit_return(); void process_fields(); void scloud_space(short m,short n); diff --git a/src/boe.party.cpp b/src/boe.party.cpp index e46b301e..9f88e1af 100644 --- a/src/boe.party.cpp +++ b/src/boe.party.cpp @@ -61,7 +61,7 @@ extern bool spell_forced,save_maps,suppress_stat_screen,boom_anim_active; extern eSpell store_mage, store_priest; extern short store_mage_lev, store_priest_lev; extern short store_spell_target,pc_casting,stat_screen_mode; -extern effect_pat_type null_pat,single,t,square,radius2,radius3; +extern effect_pat_type null_pat,single,t,square,radius2,radius3,small_square,open_square; extern effect_pat_type current_pat; extern short current_spell_range; extern short hit_chance[21],combat_active_pc; @@ -962,8 +962,7 @@ void do_mage_spell(short pc_num,eSpell spell_num,bool freebie) { break; case eSpell::DISPEL_SQUARE: - current_pat = square; - start_town_targeting(spell_num,pc_num,freebie); + start_town_targeting(spell_num,pc_num,freebie,PAT_SQ); break; case eSpell::LIGHT_LONG: @@ -1005,13 +1004,11 @@ void do_mage_spell(short pc_num,eSpell spell_num,bool freebie) { case eSpell::SCRY_MONSTER: case eSpell::UNLOCK: case eSpell::CAPTURE_SOUL: case eSpell::DISPEL_BARRIER: case eSpell::BARRIER_FIRE: case eSpell::BARRIER_FORCE: case eSpell::QUICKFIRE: - current_pat = single; start_town_targeting(spell_num,pc_num,freebie); break; case eSpell::ANTIMAGIC: - current_pat = radius2; - start_town_targeting(spell_num,pc_num,freebie); + start_town_targeting(spell_num,pc_num,freebie,PAT_RAD2); break; case eSpell::FLIGHT: @@ -1099,7 +1096,6 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { case eSpell::RITUAL_SANCTIFY: add_string_to_buf(" Sanctify which space? "); - current_pat = single; start_town_targeting(spell_num,pc_num,freebie); break; @@ -1150,13 +1146,11 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { case eSpell::MOVE_MOUNTAINS: case eSpell::MOVE_MOUNTAINS_MASS: add_string_to_buf(" Destroy what? "); - current_pat = (spell_num == eSpell::MOVE_MOUNTAINS) ? single : square; - start_town_targeting(spell_num,pc_num,freebie); + start_town_targeting(spell_num,pc_num,freebie, spell_num == eSpell::MOVE_MOUNTAINS ? PAT_SINGLE : PAT_SQ); break; case eSpell::DISPEL_SPHERE: case eSpell::DISPEL_FIELD: - current_pat = (spell_num == eSpell::DISPEL_SPHERE) ? radius2 : single; - start_town_targeting(spell_num,pc_num,freebie); + start_town_targeting(spell_num,pc_num,freebie, spell_num == eSpell::DISPEL_SPHERE ? PAT_RAD2 : PAT_SINGLE); break; case eSpell::DETECT_LIFE: @@ -1456,6 +1450,7 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { } } +extern short spell_caster; void cast_town_spell(location where) { short adjust,r1,targ,store; location loc; @@ -1482,6 +1477,10 @@ void cast_town_spell(location where) { 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, 2, spell_caster, where, &r1, &targ, &store); + if(store > 0) redraw_screen(REFRESH_ALL); + break; case eSpell::SCRY_MONSTER: case eSpell::CAPTURE_SOUL: targ = monst_there(where); if(targ < univ.town->max_monst()) { @@ -2293,12 +2292,22 @@ short stat_adj(short pc_num,eSkill which) { return tr; } -void start_town_targeting(eSpell s_num,short who_c,bool freebie) { +void start_town_targeting(eSpell s_num,short who_c,bool freebie,eSpellPat pat) { add_string_to_buf(" Target spell."); overall_mode = MODE_TOWN_TARGET; town_spell = s_num; who_cast = who_c; spell_freebie = freebie; + switch(pat) { + case PAT_SINGLE: current_pat = single; break; + case PAT_SQ: current_pat = square; break; + case PAT_SMSQ: current_pat = small_square; break; + case PAT_OPENSQ: current_pat = open_square; break; + case PAT_PLUS: current_pat = t; break; + case PAT_RAD2: current_pat = radius2; break; + case PAT_RAD3: current_pat = radius3; break; + case PAT_WALL: current_pat = single; break; // Rotateable wall not supported by town targeting + } } extern short alch_difficulty[20]; diff --git a/src/boe.party.h b/src/boe.party.h index 0d31250d..ae99033b 100644 --- a/src/boe.party.h +++ b/src/boe.party.h @@ -2,6 +2,8 @@ #ifndef BOE_GAME_PARTY_H #define BOE_GAME_PARTY_H +#include "spell.hpp" + class cDialog; void make_boats(); bool create_pc(short spot,cDialog* parent_num); @@ -39,7 +41,7 @@ bool pc_can_cast_spell(short pc_num,eSpell spell_num); bool pc_can_cast_spell(short pc_num,eSkill spell_num); eSpell pick_spell(short pc_num,eSkill type); short stat_adj(short pc_num,eSkill which); -void start_town_targeting(eSpell s_num,short who_c,bool freebie); +void start_town_targeting(eSpell s_num,short who_c,bool freebie,eSpellPat pat = PAT_SINGLE); void do_alchemy(); short alch_choice(short pc_num); bool pick_pc_graphic(short pc_num,short mode,cDialog* parent_num); diff --git a/src/boe.specials.cpp b/src/boe.specials.cpp index 7049b90d..2dc15cf1 100644 --- a/src/boe.specials.cpp +++ b/src/boe.specials.cpp @@ -36,9 +36,11 @@ extern eGameMode overall_mode; extern short which_combat_type,current_pc,stat_window; extern location center; extern bool in_scen_debug,belt_present,processing_fields,monsters_going,suppress_stat_screen,boom_anim_active; +extern effect_pat_type single,t,square,radius2,radius3,small_square,open_square,field[8]; extern effect_pat_type current_pat; extern cOutdoors::cWandering store_wandering_special; extern eSpell spell_being_cast, town_spell; +extern short spell_caster; extern sf::RenderWindow mini_map; extern short fast_bang; extern bool end_scenario; @@ -3681,6 +3683,7 @@ void townmode_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, location l; ter_num_t ter; cItem store_i; + effect_pat_type pat; spec = cur_node; *next_spec = cur_node.jumpto; @@ -4076,6 +4079,72 @@ void townmode_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, fog_lifted = spec.ex1a; redraw_screen(REFRESH_TERRAIN); break; + case eSpecType::TOWN_START_TARGETING: + if(spec.ex1a < 0 || spec.ex1a > 7) { + giveError("Invalid spell pattern (0 - 7)."); + break; + } + if(spec.ex1c > 1 && !is_combat()) { + add_string_to_buf(" Target: Only in combat"); + break; + } + if(!is_combat()) + start_town_targeting(eSpell::NONE, spec.jumpto, true, eSpellPat(spec.ex1a)); + 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; + *next_spec = -1; + break; + case eSpecType::TOWN_SPELL_PAT_FIELD: + if(spec.ex1c < -1 || spec.ex1c > 14) { + giveError("Invalid spell pattern (-1 - 14)."); + break; + } + if((spec.ex2a < 1 || spec.ex2a == 9 || spec.ex2a > 24) && spec.ex2a != 32 && spec.ex2a != 33) { + giveError("Invalid field type (see docs)."); + break; + } + switch(spec.ex1c) { + case -1: pat = current_pat; break; + case PAT_SINGLE: pat = single; break; + case PAT_SQ: pat = square; break; + case PAT_SMSQ: pat = small_square; break; + case PAT_OPENSQ: pat = open_square; break; + case PAT_PLUS: pat = t; break; + case PAT_RAD2: pat = radius2; break; + case PAT_RAD3: pat = radius3; break; + default: pat = field[spec.ex1c - 7]; + } + place_spell_pattern(pat, l, eFieldType(spec.ex2a), 6); + break; + case eSpecType::TOWN_SPELL_PAT_BOOM: + if(spec.ex1c < -1 || spec.ex1c > 14) { + giveError("Invalid spell pattern (-1 - 14)."); + break; + } + if(spec.ex2a < 0 || spec.ex2a > 7) { + giveError("Invalid damage type (0 - 7)."); + break; + } + switch(spec.ex1c) { + case -1: pat = current_pat; break; + case PAT_SINGLE: pat = single; break; + case PAT_SQ: pat = square; break; + case PAT_SMSQ: pat = small_square; break; + case PAT_OPENSQ: pat = open_square; break; + case PAT_PLUS: pat = t; break; + case PAT_RAD2: pat = radius2; break; + case PAT_RAD3: pat = radius3; break; + default: pat = field[spec.ex1c - 7]; + } + if(spec.ex2c) start_missile_anim(); + place_spell_pattern(pat, l, eDamageType(spec.ex2a), spec.ex2b, 6); + if(spec.ex2c) { + do_explosion_anim(0, 0); + end_missile_anim(); + } + break; } if(check_mess) { handle_message(which_mode,cur_spec_type,cur_node.m1,cur_node.m2,a,b); diff --git a/src/classes/simpletypes.h b/src/classes/simpletypes.h index 01258a1a..2d51a58c 100644 --- a/src/classes/simpletypes.h +++ b/src/classes/simpletypes.h @@ -643,6 +643,9 @@ enum class eSpecType { TOWN_SET_ATTITUDE = 197, TOWN_SET_CENTER = 198, TOWN_LIFT_FOG = 199, + TOWN_START_TARGETING = 200, + TOWN_SPELL_PAT_FIELD = 201, + TOWN_SPELL_PAT_BOOM = 202, RECT_PLACE_FIELD = 210, RECT_SET_EXPLORED = 211, RECT_MOVE_ITEMS = 212, diff --git a/src/classes/special.cpp b/src/classes/special.cpp index 956c4ae9..87fb13e0 100644 --- a/src/classes/special.cpp +++ b/src/classes/special.cpp @@ -370,6 +370,8 @@ std::istream& operator >> (std::istream& in, eSpecType& e) { // L - Choose button to select a town lighting type // & - Choose button to select shop type // % - Choose button to select shop cost adjustment +// { - Choose button to select a spell pattern +// } - As above, but allows you to select which version of the rotateable field // e - Choose button to select a status effect // E - Choose button to select a party status effect // w - Choose button to select main party status effect @@ -425,17 +427,17 @@ static const char*const button_dict[7][11] = { "s ss s + s==+s =", // ex2b " = s", // ex2c }, { // town nodes - "mmmmmmmmmmmmmmm dddmmmmmmmmm", // msg1 - " ", // msg2 - " ", // msg3 - " 8 ppp ", // pic - " ??? ", // pictype - " c L ", // ex1a - " s s s s @ ", // ex1b - " ", // ex1c - "@ D ! c T T i ", // ex2a - " DD / ", // ex2b - " x x : : ", // ex2c + "mmmmmmmmmmmmmmm dddmmmmmmmmmmmm", // msg1 + " ", // msg2 + " ", // msg3 + " 8 ppp ", // pic + " ??? ", // pictype + " c L { ", // ex1a + " s s s s @ ", // ex1b + " }}", // ex1c + "@ D ! c T T i FD", // ex2a + " DD / ", // ex2b + " x x : : ", // ex2c }, { // rectangle nodes "mm mmmmmmm", // msg1 " ", // msg2 diff --git a/src/scenedit/scen.keydlgs.cpp b/src/scenedit/scen.keydlgs.cpp index ecf6f80f..1479077f 100644 --- a/src/scenedit/scen.keydlgs.cpp +++ b/src/scenedit/scen.keydlgs.cpp @@ -272,6 +272,9 @@ short choose_text(eStrType list, unsigned short cur_choice, cDialog* parent, con case STRT_STATUS: strings = {"Alive", "Dead", "Dust", "Petrified", "Fled Outdoor Combat", "Absent", "Deleted"}; break; + case STRT_SPELL_PAT: + strings = {"Single Space", "3x3 Square", "2x2 Square", "3x3 Open Square", "Radius 2 Circle", "Radius 3 Circle", "Cross", "Rotateable 2x8 Wall"}; + break; } if(cur_choice < 0 || cur_choice >= strings.size()) cur_choice = 0; @@ -722,6 +725,7 @@ static bool edit_spec_enc_value(cDialog& me, std::string item_hit, node_stack_t& case '!': choose_string = false; store = choose_boom_type(val, &me); break; case 'e': choose_string = false; store = choose_status_effect(val, false, &me); break; case 'E': choose_string = false; store = choose_status_effect(val, true, &me); break; + case '{': case '}': strt = STRT_SPELL_PAT; title = "Which spell pattern?"; break; case 'i': strt = STRT_ITEM; title = "Which item?"; break; case 'I': strt = STRT_SPEC_ITEM; title = "Which special item?"; break; case 't': strt = STRT_TER; title = "Which terrain?"; break; diff --git a/src/scenedit/scen.keydlgs.h b/src/scenedit/scen.keydlgs.h index 3de3d9b5..257b8de4 100644 --- a/src/scenedit/scen.keydlgs.h +++ b/src/scenedit/scen.keydlgs.h @@ -10,7 +10,7 @@ enum eStrType { STRT_PICT, STRT_SND, STRT_CMP, STRT_ACCUM, STRT_TRAP, STRT_ATTITUDE, STRT_STAIR, STRT_LIGHT, STRT_CONTEXT, STRT_SHOP, STRT_COST_ADJ, STRT_STAIR_MODE, STRT_TALK_NODE, - STRT_STATUS, + STRT_STATUS, STRT_SPELL_PAT, }; bool cre(short val,short min,short max,const char *text1,const char *text2,cDialog* parent) ;