From d396e459a3403d0218c223f8cda246c115d2f9ae Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Sun, 25 Jan 2015 18:24:47 -0500 Subject: [PATCH] Pass around iLiving references instead of target indices in most combat contexts (and some non-combat) - Fixes PC melee attacks using webs from the wrong PC - Support for PC-on-PC attacks is in place, though these code paths are currently not entered; could be used for charmed PCs, for example - In many cases, status effects that don't affect monsters are now supported for them (but still don't affect them... yet) - The "special" damage from assassination is now handled differently; support for it is no longer hard-coded into damage_monst(), and the message display for it is handled externally from damage_monst(). Also, it's no longer used for any kind of magic weapons. - Select target special node has changed its way of selecting a specific monster/PC as the target - Fix monster-on-monster attacks applying poison on all three attacks instead of just the first --- rsrc/strings/specials-text-affect.txt | 4 +- src/boe.actions.cpp | 14 +- src/boe.combat.cpp | 1143 ++++++++++++------------- src/boe.combat.h | 11 +- src/boe.graphics.cpp | 8 +- src/boe.infodlg.cpp | 4 +- src/boe.locutils.cpp | 25 +- src/boe.locutils.h | 2 - src/boe.monster.cpp | 28 +- src/boe.monster.h | 1 - src/boe.party.cpp | 98 +-- src/boe.party.h | 2 - src/boe.specials.cpp | 565 +++++------- src/boe.specials.h | 6 +- src/boe.text.cpp | 52 +- src/boe.text.h | 5 - src/boe.town.cpp | 6 +- src/boe.townspec.cpp | 20 +- src/classes/creature.cpp | 14 + src/classes/creature.hpp | 7 + src/classes/living.hpp | 2 + src/classes/party.cpp | 11 +- src/classes/party.h | 3 +- src/classes/pc.cpp | 7 + src/classes/pc.h | 5 +- src/classes/shop.cpp | 4 + src/classes/simpletypes.h | 1 + src/classes/universe.cpp | 38 + src/classes/universe.h | 4 + src/pcedit/pc.editors.cpp | 4 - 30 files changed, 986 insertions(+), 1108 deletions(-) diff --git a/rsrc/strings/specials-text-affect.txt b/rsrc/strings/specials-text-affect.txt index f3ad95e3..d0dcf193 100644 --- a/rsrc/strings/specials-text-affect.txt +++ b/rsrc/strings/specials-text-affect.txt @@ -9,8 +9,8 @@ Unused 0 - only living, 1 - any, 2 - all, 3 - dead, 4 - inv. space Special if Cancel button pressed Unused -0 - player choice, 1 - random, 11..16 - specific, 100+ monster -Unused +0 - player choice, 1 - random, 2 - specific +0..6 - PC, 100+ - monster (if above = 2) Unused Special to Jump To -------------------- diff --git a/src/boe.actions.cpp b/src/boe.actions.cpp index c23b98fb..038e564b 100644 --- a/src/boe.actions.cpp +++ b/src/boe.actions.cpp @@ -373,7 +373,7 @@ static void handle_pause(bool& did_something, bool& need_redraw) { move_to_zero(univ.party[current_pc].status[eStatus::WEBS]); put_pc_screen(); } - check_fields(univ.party[current_pc].combat_pos,eSpecCtx::COMBAT_MOVE,current_pc); + check_fields(univ.party[current_pc].combat_pos,eSpecCtx::COMBAT_MOVE,univ.party[current_pc]); } else { add_string_to_buf("Pause."); for(int k = 0; k < 6; k++) @@ -397,7 +397,7 @@ static void handle_pause(bool& did_something, bool& need_redraw) { } } put_pc_screen(); - check_fields(univ.town.p_loc,eSpecCtx::TOWN_MOVE,0); + check_fields(univ.town.p_loc,eSpecCtx::TOWN_MOVE,univ.party[0]); } did_something = true; @@ -519,7 +519,7 @@ static void handle_talk(location destination, bool& did_something, bool& need_re need_reprint = true; } else { for(int i = 0; i < univ.town.monst.size(); i++) { - if(monst_on_space(destination,i)) { + if(univ.town.monst[i].on_space(destination)) { did_something = true; need_redraw = true; if(univ.town.monst[i].attitude % 2 == 1) { @@ -1836,7 +1836,7 @@ bool handle_keystroke(sf::Event& event){ if((univ.town.monst[i].active > 0) && (univ.town.monst[i].attitude % 2 == 1) && (dist(univ.town.monst[i].cur_loc,univ.town.p_loc) <= 10) ) - damage_monst(i, 7,1000,0, eDamageType::UNBLOCKABLE,0); + damage_monst(i, 7,1000,eDamageType::UNBLOCKABLE,0); } // kill_monst(&univ.town.monst[i],6); draw_terrain(); @@ -2757,7 +2757,7 @@ bool outd_move_party(location destination,bool forced) { location store_corner,store_iwc; ter_num_t ter; - keep_going = check_special_terrain(destination,eSpecCtx::OUT_MOVE,0,&spec_num,&check_f); + keep_going = check_special_terrain(destination,eSpecCtx::OUT_MOVE,univ.party[0],&spec_num,&check_f); if(check_f) forced = true; if(in_scen_debug && ghost_mode) @@ -2974,8 +2974,8 @@ bool town_move_party(location destination,short forced) { } */ - if(monst_there(destination) >= univ.town.monst.size()) - keep_going = check_special_terrain(destination,eSpecCtx::TOWN_MOVE,0,&spec_num,&check_f); + if(univ.target_there(destination, TARG_MONST) != nullptr) + keep_going = check_special_terrain(destination,eSpecCtx::TOWN_MOVE,univ.party[0],&spec_num,&check_f); if(check_f) forced = true; diff --git a/src/boe.combat.cpp b/src/boe.combat.cpp index b7e12bf7..f7f591bc 100644 --- a/src/boe.combat.cpp +++ b/src/boe.combat.cpp @@ -330,7 +330,7 @@ void start_outdoor_combat(cOutdoors::cCreature encounter,ter_num_t in_which_terr num_tries = 0; while((!monst_can_be_there(univ.town.monst[i].cur_loc,i) || univ.town->terrain(univ.town.monst[i].cur_loc.x,univ.town.monst[i].cur_loc.y) == 180 || - (pc_there(univ.town.monst[i].cur_loc) < 6)) && + univ.target_there(univ.town.monst[i].cur_loc, TARG_PC)) && (num_tries++ < 50)) { univ.town.monst[i].cur_loc.x = get_ran(1,15,25); univ.town.monst[i].cur_loc.y = get_ran(1,14,18); @@ -351,7 +351,7 @@ void start_outdoor_combat(cOutdoors::cCreature encounter,ter_num_t in_which_terr for(i = 0; i < 6; i++) { univ.party[i].parry = 0; - univ.party[i].last_attacked = univ.town.monst.size() + 100; + univ.party[i].last_attacked = nullptr; } univ.town.items.clear(); @@ -374,21 +374,21 @@ void start_outdoor_combat(cOutdoors::cCreature encounter,ter_num_t in_which_terr bool pc_combat_move(location destination) { std::string create_line; - short monst_hit,s1,s2,i,monst_exist,switch_pc; + short s1,s2,i,monst_exist; bool keep_going = true,forced = false,check_f = false; location monst_loc,store_loc; short spec_num; eDirection dir; - monst_hit = monst_there(destination); + iLiving* monst_hit = univ.target_there(destination, TARG_MONST); - if(monst_hit >= univ.town.monst.size() && univ.party[current_pc].status[eStatus::FORCECAGE] > 0) { + if(monst_hit == nullptr && univ.party[current_pc].status[eStatus::FORCECAGE] > 0) { add_string_to_buf("Move: Can't escape."); return false; } - if(monst_hit >= univ.town.monst.size()) - keep_going = check_special_terrain(destination,eSpecCtx::COMBAT_MOVE,current_pc,&spec_num,&check_f); + if(monst_hit == nullptr) + keep_going = check_special_terrain(destination,eSpecCtx::COMBAT_MOVE,univ.party[current_pc],&spec_num,&check_f); if(check_f) forced = true; @@ -418,10 +418,10 @@ bool pc_combat_move(location destination) { add_string_to_buf(create_line); return true; } - else if(monst_hit < univ.town.monst.size()) { + else if(monst_hit != nullptr) { // s2 = 2 here appears to mean "go ahead and attack", while s2 = 1 means "cancel attack". // Then s1 % 2 == 1 means the monster is hostile to the party. - s1 = univ.town.monst[monst_hit].attitude; + s1 = dynamic_cast(monst_hit)->attitude; if(s1 % 2 == 1) s2 = 2; else { std::string result = cChoiceDlog("attack-friendly",{"cancel","attack"}).show(); @@ -431,29 +431,31 @@ bool pc_combat_move(location destination) { if((s2 == 2) && (s1 % 2 != 1)) make_town_hostile(); if(s2 == 2) { - univ.party[current_pc].last_attacked = 100 + monst_hit; - pc_attack(current_pc,100 + monst_hit); + univ.party[current_pc].last_attacked = monst_hit; + pc_attack(current_pc,monst_hit); return true; } } - else if((switch_pc = pc_there(destination)) < 6) { - if(univ.party[switch_pc].ap == 0) { + else if((monst_hit = univ.target_there(destination, TARG_PC))) { + if(monst_hit->ap == 0) { add_string_to_buf("Move: Can't switch places."); add_string_to_buf(" (other PC has no APs) "); return false; } - else univ.party[switch_pc].ap--; + else monst_hit->ap--; + cPlayer& switch_pc = *dynamic_cast(monst_hit); add_string_to_buf("Move: Switch places."); store_loc = univ.party[current_pc].combat_pos; univ.party[current_pc].combat_pos = destination; - univ.party[switch_pc].combat_pos = store_loc; + switch_pc.combat_pos = store_loc; univ.party[current_pc].direction = dir; take_ap(1); check_special_terrain(store_loc,eSpecCtx::COMBAT_MOVE,switch_pc,&spec_num,&check_f); move_sound(univ.town->terrain(destination.x,destination.y),univ.party[current_pc].ap); return true; } - else if(forced || (!impassable(univ.town->terrain(destination.x,destination.y)) && pc_there(destination) == 6)) { + else if(forced || (!impassable(univ.town->terrain(destination.x,destination.y)) && monst_hit == nullptr)) { + // Note: monst_hit == nullptr here means there is no PC on the space // monsters get back-shots for(i = 0; i < univ.town.monst.size(); i++) { @@ -467,7 +469,7 @@ bool pc_combat_move(location destination) { univ.town.monst[i].status[eStatus::ASLEEP] <= 0 && univ.town.monst[i].status[eStatus::PARALYZED] <= 0) { combat_posing_monster = current_working_monster = 100 + i; - monster_attack_pc(i,current_pc); + monster_attack(i,&univ.party[current_pc]); combat_posing_monster = current_working_monster = -1; draw_terrain(0); } @@ -500,9 +502,9 @@ bool pc_combat_move(location destination) { } void char_parry() { - univ.party[current_pc].parry = (univ.party[current_pc].ap / 4) * - (2 + stat_adj(current_pc,eSkill::DEXTERITY) + univ.party[current_pc].skill(eSkill::DEFENSE)); - univ.party[current_pc].ap = 0; + cPlayer& who = univ.party[current_pc]; + who.parry = (who.ap / 4) * (2 + who.stat_adj(eSkill::DEXTERITY) + who.skill(eSkill::DEFENSE)); + who.ap = 0; } void char_stand_ready() { @@ -510,51 +512,50 @@ void char_stand_ready() { univ.party[current_pc].ap = 0; } -void pc_attack(short who_att,short target) { - short r1,r2,weap1 = 24, weap2 = 24,i,store_hp,skill_item; +void pc_attack(short who_att,iLiving* target) { + short r1,r2,weap1 = 24, weap2 = 24,i,skill_item; short hit_adj, dam_adj; + cPlayer& attacker = univ.party[who_att]; // slice out bad attacks - if(univ.party[who_att].main_status != eMainStatus::ALIVE) - return; - if(univ.party[who_att].status[eStatus::ASLEEP] > 0 || univ.party[who_att].status[eStatus::PARALYZED] > 0) + if(attacker.is_alive()) return; + if(target == nullptr) return; + if(attacker.status[eStatus::ASLEEP] > 0 || attacker.status[eStatus::PARALYZED] > 0) return; - univ.party[who_att].last_attacked = target; - iLiving& which_m = univ.get_target(target); + attacker.last_attacked = target; for(i = 0; i < 24; i++) - if(((univ.party[who_att].items[i].variety == eItemType::ONE_HANDED) || (univ.party[who_att].items[i].variety == eItemType::TWO_HANDED)) && - univ.party[who_att].equip[i]) { + if((attacker.items[i].variety == eItemType::ONE_HANDED || attacker.items[i].variety == eItemType::TWO_HANDED) && attacker.equip[i]) { if(weap1 == 24) weap1 = i; else weap2 = i; } - hit_adj = (-5 * minmax(-8,8,univ.party[who_att].status[eStatus::BLESS_CURSE])) + 5 * minmax(-8,8,which_m.status[eStatus::BLESS_CURSE]) - - stat_adj(who_att,eSkill::DEXTERITY) * 5 + (get_encumberance(who_att)) * 5; + hit_adj = (-5 * minmax(-8,8,attacker.status[eStatus::BLESS_CURSE])) + 5 * minmax(-8,8,target->status[eStatus::BLESS_CURSE]) + - attacker.stat_adj(eSkill::DEXTERITY) * 5 + (get_encumberance(who_att)) * 5; - dam_adj = minmax(-8,8,univ.party[who_att].status[eStatus::BLESS_CURSE]) - minmax(-8,8,which_m.status[eStatus::BLESS_CURSE]) - + stat_adj(who_att,eSkill::STRENGTH); + dam_adj = minmax(-8,8,attacker.status[eStatus::BLESS_CURSE]) - minmax(-8,8,target->status[eStatus::BLESS_CURSE]) + + attacker.stat_adj(eSkill::STRENGTH); - if(which_m.status[eStatus::ASLEEP] > 0 || which_m.status[eStatus::PARALYZED] > 0) { + if(target->status[eStatus::ASLEEP] > 0 || target->status[eStatus::PARALYZED] > 0) { hit_adj -= 80; dam_adj += 10; } // TODO: These should check abil_data[0], not item_level - if((skill_item = univ.party[who_att].has_abil_equip(eItemAbil::SKILL)) < 24) { - hit_adj += 5 * (univ.party[who_att].items[skill_item].item_level / 2 + 1); - dam_adj += univ.party[who_att].items[skill_item].item_level / 2; + if((skill_item = attacker.has_abil_equip(eItemAbil::SKILL)) < 24) { + hit_adj += 5 * (attacker.items[skill_item].item_level / 2 + 1); + dam_adj += attacker.items[skill_item].item_level / 2; } - if((skill_item = univ.party[who_att].has_abil_equip(eItemAbil::GIANT_STRENGTH)) < 24) { - dam_adj += univ.party[who_att].items[skill_item].item_level; - hit_adj += univ.party[who_att].items[skill_item].item_level * 2; + if((skill_item = attacker.has_abil_equip(eItemAbil::GIANT_STRENGTH)) < 24) { + dam_adj += attacker.items[skill_item].item_level; + hit_adj += attacker.items[skill_item].item_level * 2; } - void_sanctuary(who_att); + attacker.void_sanctuary(); - store_hp = which_m.get_health(); + short store_hp = target->get_health(); combat_posing_monster = current_working_monster = who_att; @@ -566,29 +567,33 @@ void pc_attack(short who_att,short target) { r1 += 5 * (univ.party[current_pc].status[eStatus::WEBS] / 3); r2 = get_ran(1,1,4) + dam_adj; - if(r1 <= hit_chance[univ.party[who_att].skill(eSkill::DEXTERITY)]) { - damage_monst(target - 100, who_att, r2, 0,eDamageType::WEAPON,4); + if(r1 <= hit_chance[attacker.skill(eSkill::DEXTERITY)]) { + size_t i_monst = univ.get_target_i(*target); + // TODO: Change to damage_target() + if(i_monst >= 100) + damage_monst(i_monst - 100, who_att, r2, eDamageType::WEAPON,4); + else damage_pc(i_monst, r2, eDamageType::WEAPON, univ.party[who_att].race, 4); } else { draw_terrain(2); - create_line = univ.party[who_att].name + " misses."; + create_line = attacker.name + " misses."; add_string_to_buf(create_line); play_sound(2); } } - short weap_poisoned = univ.party[who_att].weap_poisoned; + short weap_poisoned = attacker.weap_poisoned; if(weap1 < 24) - pc_attack_weapon(who_att, target, hit_adj, dam_adj, univ.party[who_att].items[weap1],weap2 < 24 ? 2 : 1,weap_poisoned == weap1); - if(weap2 < 24 && which_m.is_alive()) - pc_attack_weapon(who_att, target, hit_adj, dam_adj, univ.party[who_att].items[weap2],0,weap_poisoned == weap2); - move_to_zero(univ.party[who_att].status[eStatus::POISONED_WEAPON]); + pc_attack_weapon(who_att, *target, hit_adj, dam_adj, attacker.items[weap1], weap2 < 24 ? 2 : 1, weap_poisoned == weap1); + if(weap2 < 24 && target->is_alive()) + pc_attack_weapon(who_att, *target, hit_adj, dam_adj, attacker.items[weap2], 0, weap_poisoned == weap2); + move_to_zero(attacker.status[eStatus::POISONED_WEAPON]); take_ap(4); - if(store_hp - which_m.get_health() > 0) { - if(which_m.is_shielded()) { - int how_much = which_m.get_shared_dmg(store_hp - which_m.get_health()); + if(store_hp - target->get_health() > 0) { + if(target->is_shielded()) { + int how_much = target->get_shared_dmg(store_hp - target->get_health()); add_string_to_buf(" Shares damage! "); damage_pc(who_att, how_much, eDamageType::MAGIC,eRace::UNKNOWN,0); } @@ -596,29 +601,24 @@ void pc_attack(short who_att,short target) { combat_posing_monster = current_working_monster = -1; } -static void apply_weapon_status(eStatus status, int how_much, int dmg, int who, std::string weap_type) { - iLiving& which_m = univ.get_target(who); +static void apply_weapon_status(eStatus status, int how_much, int dmg, iLiving& which_m, std::string weap_type) { switch(status) { // TODO: It should be possible to make monsters support magic resistance and invulnerability, at least. // Maybe also poisoned weapon and invisibility. case eStatus::MAIN: break; // Not a valid status case eStatus::INVISIBLE: - if(who >= 100) break; // Not supported by monsters add_string_to_buf(" " + weap_type + " leaks an odd-coloured aura."); which_m.apply_status(eStatus::INVISIBLE, how_much / -2); break; case eStatus::MAGIC_RESISTANCE: - if(who >= 100) break; // Not supported by monsters add_string_to_buf(" " + weap_type + " leaks an odd-coloured aura."); which_m.apply_status(eStatus::MAGIC_RESISTANCE, how_much / -2); break; case eStatus::INVULNERABLE: - if(who >= 100) break; // Not supported by monsters add_string_to_buf(" " + weap_type + " leaks an odd-coloured aura."); which_m.apply_status(eStatus::INVULNERABLE, how_much / -2); break; case eStatus::POISONED_WEAPON: - if(who >= 100) break; // Not supported by monsters add_string_to_buf(" " + weap_type + " leaks an odd-coloured aura."); which_m.apply_status(eStatus::POISONED_WEAPON, how_much / -2); break; @@ -674,28 +674,28 @@ static void apply_weapon_status(eStatus status, int how_much, int dmg, int who, } // primary: 0 - secondary weapon, 1 - primary (and only) weapon, 2 - primary of two weapons -void pc_attack_weapon(short who_att,short target,short hit_adj,short dam_adj,cItem& weap,short primary,bool do_poison) { +void pc_attack_weapon(short who_att,iLiving& target,short hit_adj,short dam_adj,cItem& weap,short primary,bool do_poison) { short r1, r2; - iLiving& which_m = univ.get_target(target); + cPlayer& attacker = univ.party[who_att]; eSkill what_skill = weap.weap_type; // safety valve if(what_skill == eSkill::INVALID) what_skill = eSkill::EDGED_WEAPONS; - std::string create_line = univ.party[who_att].name + " swings."; + std::string create_line = attacker.name + " swings."; add_string_to_buf(create_line); r1 = get_ran(1,1,100) + hit_adj - 5 * weap.bonus; - r1 += 5 * (univ.party[current_pc].status[eStatus::WEBS] / 3); + r1 += 5 * (attacker.status[eStatus::WEBS] / 3); if(primary) r1 -= 5; // Ambidextrous? - if(primary != 1 && !univ.party[who_att].traits[eTrait::AMBIDEXTROUS]) + if(primary != 1 && !attacker.traits[eTrait::AMBIDEXTROUS]) r1 += 25; if(primary) { // race adj. - if(univ.party[who_att].race == eRace::SLITH && weap.weap_type == eSkill::POLE_WEAPONS) + if(attacker.race == eRace::SLITH && weap.weap_type == eSkill::POLE_WEAPONS) r1 -= 10; } @@ -711,82 +711,113 @@ void pc_attack_weapon(short who_att,short target,short hit_adj,short dam_adj,cIt else pause(5); play_sound(5); start_missile_anim(); - place_spell_pattern(radius2, which_m.get_loc(), eDamageType(weap.abil_data[1]), weap.abil_data[0] * 2, who_att); + place_spell_pattern(radius2, target.get_loc(), eDamageType(weap.abil_data[1]), weap.abil_data[0] * 2, who_att); do_explosion_anim(5,0); end_missile_anim(); handle_marked_damage(); } else if(r1 <= hit_chance[univ.party[who_att].skill(what_skill)]) { - eDamageType dmg_tp = eDamageType::UNBLOCKABLE; - short spec_dam = calc_spec_dam(weap.ability,weap.abil_data[0],weap.abil_data[1],which_m,dmg_tp); + eDamageType dmg_tp = eDamageType::SPECIAL; + short spec_dam = calc_spec_dam(weap.ability,weap.abil_data[0],weap.abil_data[1],target,dmg_tp); short bonus_dam = 0; - if(dmg_tp != eDamageType::UNBLOCKABLE) + if(dmg_tp != eDamageType::SPECIAL) std::swap(spec_dam, bonus_dam); if(primary) { bool splits = false; - if(cCreature* who = dynamic_cast(&which_m)) + if(cCreature* who = dynamic_cast(&target)) splits = who->abil[eMonstAbil::SPLITS].active; // assassinate r1 = get_ran(1,1,100); int assassin = univ.party[who_att].skill(eSkill::ASSASSINATION); - if((univ.party[who_att].level >= which_m.get_level() - 1) && assassin >= which_m.get_level() / 2 + if((univ.party[who_att].level >= target.get_level() - 1) && assassin >= target.get_level() / 2 && !splits) // Can't assassinate splitters - if(r1 < hit_chance[max(assassin - which_m.get_level(),0)]) { + if(r1 < hit_chance[max(assassin - target.get_level(),0)]) { add_string_to_buf(" You assassinate."); spec_dam += r2; } } + snd_num_t dmg_snd = -1, no_dmg = -1; + size_t i_monst = univ.get_target_i(target); if(weap.ability == eItemAbil::HEALING_WEAPON) { ASB(" There is a flash of light."); - which_m.heal(r2); + target.heal(r2); } else switch(what_skill) { case eSkill::EDGED_WEAPONS: if(weap.item_level < 8) - damage_monst(target - 100, who_att, r2, spec_dam, eDamageType::WEAPON,1); - else damage_monst(target - 100, who_att, r2, spec_dam, eDamageType::WEAPON,2); + dmg_snd = 1; + else dmg_snd = 2; break; case eSkill::BASHING_WEAPONS: - damage_monst(target - 100, who_att, r2, spec_dam, eDamageType::WEAPON,4); + dmg_snd = 4; break; case eSkill::POLE_WEAPONS: - damage_monst(target - 100, who_att, r2, spec_dam, eDamageType::WEAPON,3); + dmg_snd = 3; break; default: // TODO: Not sure what sound to play for unconventional weapons, but let's just go with the generic "ouch" for now - damage_monst(target - 100, who_att, r2, spec_dam, eDamageType::WEAPON, 0); + dmg_snd = 0; break; } - if(bonus_dam) - damage_monst(target - 100, who_att, bonus_dam, 0, dmg_tp, 0); + // TODO: Change these to damage_target() + bool damaged = false; + if(cCreature* monst = dynamic_cast(&target)) { + if(dmg_snd != no_dmg) + damaged = damaged || damage_monst(i_monst - 100, who_att, r2, eDamageType::WEAPON, dmg_snd, false); + if(spec_dam) + damaged = damaged || damage_monst(i_monst - 100, who_att, spec_dam, eDamageType::SPECIAL, 5, false); + if(bonus_dam) + damaged = damaged || damage_monst(i_monst - 100, who_att, bonus_dam, dmg_tp, 0, false); + if(damaged) + monst->damaged_msg(r2, spec_dam + bonus_dam); + } else if(cPlayer* who = dynamic_cast(&target)) { + eRace race = univ.party[who_att].race; + if(dmg_snd != no_dmg) + damaged = damaged || damage_pc(i_monst, r2, eDamageType::WEAPON, race, dmg_snd, false); + if(spec_dam) + damaged = damaged || damage_pc(i_monst, spec_dam, eDamageType::SPECIAL, race, dmg_snd, false); + if(bonus_dam) + damaged = damaged || damage_pc(i_monst, bonus_dam, eDamageType::SPECIAL, race, dmg_snd, false); + if(damaged) { + std::string msg = " " + who->name + " takes " + std::to_string(r2); + if(spec_dam + bonus_dam) + msg += '+' + std::to_string(spec_dam + bonus_dam); + add_string_to_buf(msg + '.'); + } + } if(do_poison) { // poison if(univ.party[who_att].status[eStatus::POISONED_WEAPON] > 0) { short poison_amt = univ.party[who_att].status[eStatus::POISONED_WEAPON]; if(univ.party[who_att].has_abil_equip(eItemAbil::POISON_AUGMENT) < 24) poison_amt += 2; - which_m.poison(poison_amt); + target.poison(poison_amt); move_to_zero(univ.party[who_att].status[eStatus::POISONED_WEAPON]); } } if((weap.ability == eItemAbil::STATUS_WEAPON) && (get_ran(1,0,1) == 1)) { - apply_weapon_status(eStatus(weap.abil_data[1]), weap.abil_data[0], r2 + spec_dam, target + 100, "Blade"); + apply_weapon_status(eStatus(weap.abil_data[1]), weap.abil_data[0], r2 + spec_dam, target, "Blade"); } else if(weap.ability == eItemAbil::SOULSUCKER && get_ran(1,0,1) == 1) { add_string_to_buf(" Blade drains life."); univ.party[who_att].heal(weap.abil_data[0] / 2); } else if(weap.ability == eItemAbil::ANTIMAGIC_WEAPON) { - short before = which_m.get_magic(); - which_m.drain_sp(weap.abil_data[0]); - if(before > which_m.get_magic()) { + short before = target.get_magic(); + int mage = 0, cleric = 0; + if(cCreature* check = dynamic_cast(&target)) + mage = check->mu, cleric = check->cl; + else if(cPlayer* check = dynamic_cast(&target)) + mage = check->skill(eSkill::MAGE_SPELLS), cleric = check->skill(eSkill::PRIEST_SPELLS); + if(mage + cleric > 0 && get_ran(1,0,1) == 1) + target.drain_sp(weap.abil_data[0]); + if(before > target.get_magic()) { add_string_to_buf(" Blade drains energy."); - univ.party[who_att].restore_sp((before > which_m.get_magic()) / 3); + univ.party[who_att].restore_sp((before > target.get_magic()) / 3); } } else if(weap.ability == eItemAbil::WEAPON_CALL_SPECIAL) { short s1,s2,s3; univ.party.force_ptr(21, 301, 5); univ.party.force_ptr(22, 301, 6); univ.party.force_ptr(20, 301, 7); - PSD[SDF_SPEC_TARGLOC_X] = which_m.get_loc().x; - PSD[SDF_SPEC_TARGLOC_Y] = which_m.get_loc().y; - PSD[SDF_SPEC_TARGET] = target; // ready to be passed to SELECT_TARGET node - if(PSD[SDF_SPEC_TARGET] < 6) PSD[SDF_SPEC_TARGET] += 11; + PSD[SDF_SPEC_TARGLOC_X] = target.get_loc().x; + PSD[SDF_SPEC_TARGLOC_Y] = target.get_loc().y; + PSD[SDF_SPEC_TARGET] = i_monst; run_special(eSpecCtx::ATTACKING_MELEE, 0, weap.abil_data[0],univ.party[who_att].combat_pos, &s1, &s2, &s3); } } @@ -806,9 +837,7 @@ short calc_spec_dam(eItemAbil abil,short abil_str,short abil_dat,iLiving& monst, if(abil == eItemAbil::DAMAGING_WEAPON) { store += get_ran(abil_str,1,6); - if(abil == eItemAbil::DAMAGING_WEAPON) - dmg_type = eDamageType(abil_dat); - else dmg_type = eDamageType::FIRE; + dmg_type = eDamageType(abil_dat); } else if(abil == eItemAbil::SLAYER_WEAPON) { eRace race = eRace::UNKNOWN; if(cCreature* who = dynamic_cast(&monst)) @@ -899,7 +928,6 @@ void place_target(location target) { void do_combat_cast(location target) { short adjust,r1,r2,targ_num,level,bonus = 1,i,item; snd_num_t store_sound = 0; - cCreature *cur_monst; bool freebie = false,ap_taken = false,cost_taken = false; short num_targets = 1; miss_num_t store_m_type = 2; @@ -912,6 +940,8 @@ void do_combat_cast(location target) { WALL_ICE,WALL_ICE,WALL_BLADES, }; mon_num_t summon; + iLiving* victim; + cPlayer& caster = univ.party[current_pc]; location ashes_loc; @@ -926,8 +956,8 @@ void do_combat_cast(location target) { level = minmax(2,20,level); } else { - level = 1 + univ.party[current_pc].level / 2; - bonus = stat_adj(current_pc,eSkill::INTELLIGENCE); + level = 1 + caster.level / 2; + bonus = caster.stat_adj(eSkill::INTELLIGENCE); } force_wall_position = 10; @@ -936,7 +966,7 @@ void do_combat_cast(location target) { if(adjust <= 4 && !cast_spell_on_space(target, spell_being_cast)) return; // The special node intercepted and cancelled regular spell behaviour. - void_sanctuary(current_pc); + caster.void_sanctuary(); if(overall_mode == MODE_SPELL_TARGET) { spell_targets[0] = target; } @@ -966,21 +996,21 @@ void do_combat_cast(location target) { spell_targets[i].x = 120; // nullify target as it is used if(!cost_taken && !freebie && spell_being_cast != eSpell::MINDDUEL && spell_being_cast != eSpell::SIMULACRUM) { - univ.party[current_pc].cur_sp -= (*spell_being_cast).cost; + caster.cur_sp -= (*spell_being_cast).cost; cost_taken = true; } if(!cost_taken && !freebie && spell_being_cast == eSpell::SIMULACRUM) { - univ.party[current_pc].cur_sp -= store_sum_monst_cost; + caster.cur_sp -= store_sum_monst_cost; cost_taken = true; } - if((adjust = can_see_light(univ.party[current_pc].combat_pos,target,sight_obscurity)) > 4) { + if((adjust = can_see_light(caster.combat_pos,target,sight_obscurity)) > 4) { add_string_to_buf(" Can't see target. "); } else if(loc_off_act_area(target)) { add_string_to_buf(" Space not in town. "); } - else if(dist(univ.party[current_pc].combat_pos,target) > (*spell_being_cast).range) + else if(dist(caster.combat_pos,target) > (*spell_being_cast).range) add_string_to_buf(" Target out of range."); else if(sight_obscurity(target.x,target.y) == 5 && spell_being_cast != eSpell::DISPEL_BARRIER) add_string_to_buf(" Target space obstructed. "); @@ -1067,7 +1097,7 @@ void do_combat_cast(location target) { case eSpell::SPARK: case eSpell::ICE_BOLT: r1 = (spell_being_cast == eSpell::SPARK) ? get_ran(2,1,4) : get_ran(min(20,level + bonus),1,4); add_missile(target,6,1,0,0); - do_missile_anim(100,univ.party[current_pc].combat_pos,11); + do_missile_anim(100,caster.combat_pos,11); hit_space(target,r1,(spell_being_cast == eSpell::SPARK) ? eDamageType::MAGIC : eDamageType::COLD,1,0); break; case eSpell::ARROWS_FLAME: @@ -1090,20 +1120,20 @@ void do_combat_cast(location target) { r1 = get_ran(2 + bonus / 2,1,4); else r1 = get_ran(min(7,2 + bonus + level / 2),1,4); add_missile(target,14,1,0,0); - do_missile_anim(100,univ.party[current_pc].combat_pos,24); + do_missile_anim(100,caster.combat_pos,24); hit_space(target,r1,eDamageType::UNBLOCKABLE,1,0); break; case eSpell::FLAME: r1 = get_ran(min(10,1 + level / 3 + bonus),1,6); add_missile(target,2,1,0,0); - do_missile_anim(100,univ.party[current_pc].combat_pos,11); + do_missile_anim(100,caster.combat_pos,11); hit_space(target,r1,eDamageType::FIRE,1,0); break; case eSpell::FIREBALL: case eSpell::FLAMESTRIKE: r1 = min(9,1 + (level * 2) / 3 + bonus) + 1; add_missile(target,2,1,0,0); store_sound = 11; - //do_missile_anim(100,univ.party[current_pc].combat_pos,11); + //do_missile_anim(100,caster.combat_pos,11); if(spell_being_cast == eSpell::FLAMESTRIKE) r1 = (r1 * 14) / 10; else if(r1 > 10) r1 = (r1 * 8) / 10; @@ -1114,7 +1144,7 @@ void do_combat_cast(location target) { case eSpell::FIRESTORM: add_missile(target,2,1,0,0); store_sound = 11; - //do_missile_anim(100,univ.party[current_pc].combat_pos,11); + //do_missile_anim(100,caster.combat_pos,11); r1 = min(12,1 + (level * 2) / 3 + bonus) + 2; if(r1 > 20) r1 = (r1 * 8) / 10; @@ -1123,14 +1153,14 @@ void do_combat_cast(location target) { break; case eSpell::KILL: add_missile(target,9,1,0,0); - do_missile_anim(100,univ.party[current_pc].combat_pos,11); - r1 = get_ran(3,0,10) + univ.party[current_pc].level * 2; + do_missile_anim(100,caster.combat_pos,11); + r1 = get_ran(3,0,10) + caster.level * 2; hit_space(target,40 + r1,eDamageType::MAGIC,1,0); break; case eSpell::ARROWS_DEATH: add_missile(target,9,1,0,0); store_sound = 11; - r1 = get_ran(3,0,10) + univ.party[current_pc].level + 3 * bonus; + r1 = get_ran(3,0,10) + caster.level + 3 * bonus; boom_type[i] = eDamageType::MAGIC; boom_dam[i] = r1; //hit_space(target,40 + r1,3,1,0); @@ -1144,59 +1174,59 @@ void do_combat_cast(location target) { do_missile_anim(50,univ.party[current_pc].combat_pos,61); switch(spell_being_cast) { case eSpell::SIMULACRUM: - r2 = get_ran(3,1,4) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(3,1,4) + caster.stat_adj(eSkill::INTELLIGENCE); if(!summon_monster(store_sum_monst,target,r2,2)) add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON_BEAST: - r2 = get_ran(3,1,4) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(3,1,4) + caster.stat_adj(eSkill::INTELLIGENCE); if((summon == 0) || (!summon_monster(summon,target,r2,2))) add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON_WEAK: - r2 = get_ran(4,1,4) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(4,1,4) + caster.stat_adj(eSkill::INTELLIGENCE); if((summon == 0) || (!summon_monster(summon,target,r2,2))) add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON: - r2 = get_ran(5,1,4) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(5,1,4) + caster.stat_adj(eSkill::INTELLIGENCE); if((summon == 0) || (!summon_monster(summon,target,r2,2))) add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON_MAJOR: - r2 = get_ran(7,1,4) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(7,1,4) + caster.stat_adj(eSkill::INTELLIGENCE); if((summon == 0) || (!summon_monster(summon,target,r2,2))) add_string_to_buf(" Summon failed."); break; case eSpell::DEMON: - r2 = get_ran(5,1,4) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(5,1,4) + caster.stat_adj(eSkill::INTELLIGENCE); if(!summon_monster(85,target,r2,2)) add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON_RAT: - r1 = get_ran(3,1,4) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r1 = get_ran(3,1,4) + caster.stat_adj(eSkill::INTELLIGENCE); if(!summon_monster(80,target,r1,2)) add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON_SPIRIT: - r2 = get_ran(2,1,5) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(2,1,5) + caster.stat_adj(eSkill::INTELLIGENCE); if(!summon_monster(125,target,r2,2)) add_string_to_buf(" Summon failed."); break; case eSpell::STICKS_TO_SNAKES: r1 = get_ran(1,0,7); - r2 = get_ran(2,1,5) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(2,1,5) + caster.stat_adj(eSkill::INTELLIGENCE); if(!summon_monster((r1 == 1) ? 100 : 99,target,r2,2)) add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON_HOST: // host - r2 = get_ran(2,1,4) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(2,1,4) + caster.stat_adj(eSkill::INTELLIGENCE); if(!summon_monster((i == 0) ? 126 : 125,target,r2,2)) add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON_GUARDIAN: // guardian - r2 = get_ran(6,1,4) + stat_adj(current_pc,eSkill::INTELLIGENCE); + r2 = get_ran(6,1,4) + caster.stat_adj(eSkill::INTELLIGENCE); if(!summon_monster(122,target,r2,2)) add_string_to_buf(" Summon failed."); break; @@ -1205,12 +1235,10 @@ void do_combat_cast(location target) { default: - targ_num = monst_there(target); - if(targ_num >= univ.town.monst.size()) + if(!(victim = univ.target_there(target, TARG_MONST))) add_string_to_buf(" Nobody there "); else { - cur_monst = &univ.town.monst[targ_num]; - cCreature* cur_monst = &univ.town.monst[targ_num]; + cCreature* cur_monst = dynamic_cast(victim); if(cur_monst->attitude % 2 != 1 && spell_being_cast != eSpell::SCRY_MONSTER && spell_being_cast != eSpell::CAPTURE_SOUL) make_town_hostile(); switch(spell_being_cast) { @@ -1229,7 +1257,7 @@ void do_combat_cast(location target) { store_sound = 53; r1 = get_ran(4,1,8); r2 = get_ran(1,0,2); - damage_monst(targ_num, 7, r1, 0, eDamageType::MAGIC,0); + damage_monst(targ_num, 7, r1, eDamageType::MAGIC,0); cur_monst->slow(4 + r2); cur_monst->poison(5 + r2); break; @@ -1253,11 +1281,11 @@ void do_combat_cast(location target) { if((cur_monst->mu == 0) && (cur_monst->cl == 0)) add_string_to_buf(" Can't duel: no magic."); else { - item = univ.party[current_pc].has_abil(eItemAbil::SMOKY_CRYSTAL); + item = caster.has_abil(eItemAbil::SMOKY_CRYSTAL); if(item >= 24) add_string_to_buf(" You need a smoky crystal. "); else { - univ.party[current_pc].remove_charge(item); + caster.remove_charge(item); if(stat_window == current_pc) put_item_screen(stat_window,1); do_mindduel(current_pc,cur_monst); @@ -1268,7 +1296,7 @@ void do_combat_cast(location target) { case eSpell::CHARM_FOE: store_m_type = 14; - cur_monst->sleep(eStatus::CHARM,0,-1 * (bonus + univ.party[current_pc].level / 8)); + cur_monst->sleep(eStatus::CHARM,0,-1 * (bonus + caster.level / 8)); store_sound = 24; break; @@ -1298,7 +1326,7 @@ void do_combat_cast(location target) { break; case eSpell::FEAR: store_m_type = 11; - cur_monst->scare(get_ran(min(20,univ.party[current_pc].level / 2 + bonus),1,8)); + cur_monst->scare(get_ran(min(20,caster.level / 2 + bonus),1,8)); store_sound = 54; break; @@ -1344,7 +1372,7 @@ void do_combat_cast(location target) { case eSpell::HOLY_SCOURGE: store_m_type = 8; - cur_monst->curse(2 + univ.party[current_pc].level / 2); + cur_monst->curse(2 + caster.level / 2); store_sound = 24; break; @@ -1396,7 +1424,7 @@ void do_combat_cast(location target) { } } - do_missile_anim((num_targets > 1) ? 35 : 60,univ.party[current_pc].combat_pos,store_sound); + do_missile_anim((num_targets > 1) ? 35 : 60,caster.combat_pos,store_sound); // process mass damage for(i = 0; i < 8; i++) @@ -1427,7 +1455,7 @@ void handle_marked_damage() { } for(i = 0; i < univ.town.monst.size(); i++) if(univ.town.monst[i].marked_damage > 0) { - damage_monst(i, current_pc, univ.town.monst[i].marked_damage, 0, eDamageType::MARKED,0); + damage_monst(i, current_pc, univ.town.monst[i].marked_damage, eDamageType::MARKED,0); univ.town.monst[i].marked_damage = 0; } @@ -1522,34 +1550,39 @@ void load_missile() { } void fire_missile(location target) { - short r1, r2, skill, dam, dam_bonus, hit_bonus = 0, range, targ_monst, spec_dam = 0,poison_amt = 0; + missile_firer = current_pc; // This may be used by monsters to help pick a target + short r1, r2, skill, dam, dam_bonus, hit_bonus = 0, range, spec_dam = 0,poison_amt = 0; short skill_item; - cCreature *cur_monst; + iLiving* victim; bool exploding = false; - missile_firer = current_pc; + cPlayer& missile_firer = univ.party[current_pc]; + cItem& missile = missile_firer.items[missile_inv_slot]; + cItem& ammo = missile_firer.items[ammo_inv_slot]; - skill = univ.party[missile_firer].skill(univ.party[missile_firer].items[missile_inv_slot].weap_type); + skill = missile_firer.skill(missile.weap_type); range = (overall_mode == MODE_FIRING) ? 12 : 8; - dam = univ.party[missile_firer].items[ammo_inv_slot].item_level; - dam_bonus = univ.party[missile_firer].items[ammo_inv_slot].bonus + minmax(-8,8,univ.party[missile_firer].status[eStatus::BLESS_CURSE]); + dam = ammo.item_level; + dam_bonus = ammo.bonus + minmax(-8,8,missile_firer.status[eStatus::BLESS_CURSE]); if(overall_mode == MODE_FIRING) - hit_bonus = univ.party[missile_firer].items[missile_inv_slot].bonus; - hit_bonus += stat_adj(missile_firer,eSkill::DEXTERITY) - can_see_light(univ.party[missile_firer].combat_pos,target,sight_obscurity) - + minmax(-8,8,univ.party[missile_firer].status[eStatus::BLESS_CURSE]); - skill_item = univ.party[missile_firer].get_prot_level(eItemAbil::ACCURACY) / 2; + hit_bonus = missile.bonus; + hit_bonus += missile_firer.stat_adj(eSkill::DEXTERITY) - can_see_light(missile_firer.combat_pos,target,sight_obscurity) + + minmax(-8,8,missile_firer.status[eStatus::BLESS_CURSE]); + skill_item = missile_firer.get_prot_level(eItemAbil::ACCURACY) / 2; hit_bonus += skill_item; dam_bonus += skill_item; - if(univ.party[missile_firer].items[ammo_inv_slot].ability == eItemAbil::SEEKING_MISSILE) { - targ_monst = monst_there(target); - std::set targets; - if(targ_monst >= univ.town.monst.size() || univ.town.monst[targ_monst].attitude % 2 == 0) { + if(ammo.ability == eItemAbil::SEEKING_MISSILE) { + victim = univ.target_there(target); + std::set targets; + if(victim == nullptr || victim->is_friendly()) { for(int i = -1; i <= 1; i++) { for(int j = -1; j <= 1; j++) { if(i == 0 && j == 0) continue; - targ_monst = monst_there(loc(target.x + i, target.y + j)); - if(targ_monst < univ.town.monst.size()) { - bool invisible = univ.town.monst[targ_monst].invisible; - bool friendly = univ.town.monst[targ_monst].attitude % 2 == 0; + victim = univ.target_there(loc(target.x + i, target.y + j)); + if(victim) { + bool invisible = victim->status[eStatus::INVISIBLE] > 0; + if(cCreature* check = dynamic_cast(victim)) + invisible = invisible || check->invisible; + bool friendly = victim->is_friendly(); int seek_chance = 10; if(invisible && friendly) seek_chance -= 8; @@ -1558,185 +1591,163 @@ void fire_missile(location target) { else if(friendly) seek_chance -= 9; if(get_ran(1,1,10) <= seek_chance) - targets.insert(targ_monst); + targets.insert(victim); } } } if(!targets.empty()) { auto iter = targets.begin(); std::advance(iter, get_ran(1,1,targets.size()) - 1); - target = univ.town.monst[*iter].cur_loc; + target = (**iter).get_loc(); } } else hit_bonus += 10; } // race adj. - if(univ.party[missile_firer].race == eRace::NEPHIL) + if(missile_firer.race == eRace::NEPHIL) hit_bonus += 2; - if(univ.party[missile_firer].items[ammo_inv_slot].ability == eItemAbil::EXPLODING_WEAPON) + if(ammo.ability == eItemAbil::EXPLODING_WEAPON) exploding = true; - if(dist(univ.party[missile_firer].combat_pos,target) > range) + if(dist(missile_firer.combat_pos,target) > range) add_string_to_buf(" Out of range."); - else if(can_see_light(univ.party[missile_firer].combat_pos,target,sight_obscurity) >= 5) + else if(can_see_light(missile_firer.combat_pos,target,sight_obscurity) >= 5) add_string_to_buf(" Can't see target. "); else { // First, some missiles do special things if(exploding) { - cItem& missile = univ.party[missile_firer].items[ammo_inv_slot]; take_ap((overall_mode == MODE_FIRING) ? 3 : 2); - void_sanctuary(current_pc); // TODO: Is this right? - missile_firer = current_pc; + univ.party[current_pc].void_sanctuary(); // TODO: Is this right? add_string_to_buf(" The arrow explodes! "); if(PSD[SDF_GAME_SPEED] == 0) - pause(dist(univ.party[current_pc].combat_pos,target)); + pause(dist(missile_firer.combat_pos,target)); else - pause(dist(univ.party[current_pc].combat_pos,target)*5); - run_a_missile(univ.party[missile_firer].combat_pos,target,2,1,5,0,0,100); + pause(dist(missile_firer.combat_pos,target)*5); + run_a_missile(missile_firer.combat_pos,target,2,1,5,0,0,100); start_missile_anim(); - place_spell_pattern(radius2,target, eDamageType(missile.abil_data[1]),missile.abil_data[0] * 2,missile_firer); + place_spell_pattern(radius2,target, eDamageType(ammo.abil_data[1]),ammo.abil_data[0] * 2, current_pc); do_explosion_anim(5,0); end_missile_anim(); handle_marked_damage(); } else { - combat_posing_monster = current_working_monster = missile_firer; + combat_posing_monster = current_working_monster = current_pc; draw_terrain(2); - void_sanctuary(missile_firer); + missile_firer.void_sanctuary(); take_ap((overall_mode == MODE_FIRING) ? 3 : 2); - missile_firer = missile_firer; r1 = get_ran(1,1,100) - 5 * hit_bonus - 10; - r1 += 5 * (univ.party[missile_firer].status[eStatus::WEBS] / 3); + r1 += 5 * (missile_firer.status[eStatus::WEBS] / 3); r2 = get_ran(1,1,dam) + dam_bonus; - if(univ.party[missile_firer].items[ammo_inv_slot].ability == eItemAbil::WEAK_WEAPON) - r2 = (r2 * (10 - univ.party[missile_firer].items[ammo_inv_slot].abil_data[0])) / 10; - std::string create_line = univ.party[missile_firer].name + " fires."; + if(ammo.ability == eItemAbil::WEAK_WEAPON) + r2 = (r2 * (10 - ammo.abil_data[0])) / 10; + std::string create_line = missile_firer.name + " fires."; add_string_to_buf(create_line); - miss_num_t m_type = univ.party[missile_firer].items[ammo_inv_slot].missile; - run_a_missile(univ.party[missile_firer].combat_pos,target,m_type,1,(overall_mode == MODE_FIRING) ? 12 : 14, + miss_num_t m_type = ammo.missile; + run_a_missile(missile_firer.combat_pos,target,m_type,1,(overall_mode == MODE_FIRING) ? 12 : 14, 0,0,100); if(r1 > hit_chance[skill]) add_string_to_buf(" Missed."); - else if((targ_monst = monst_there(target)) < univ.town.monst.size()) { - cur_monst = &univ.town.monst[targ_monst]; - eDamageType dmg_tp = eDamageType::UNBLOCKABLE; - short bonus_dam = 0; - cItem& missile = univ.party[missile_firer].items[ammo_inv_slot]; - spec_dam = calc_spec_dam(missile.ability,missile.abil_data[0],missile.abil_data[1],*cur_monst,dmg_tp); - if(dmg_tp != eDamageType::UNBLOCKABLE) std::swap(bonus_dam, spec_dam); - if(univ.party[missile_firer].items[ammo_inv_slot].ability == eItemAbil::HEALING_WEAPON) { + else if((victim = univ.target_there(target))) { + size_t i_monst = univ.get_target_i(*victim); + eDamageType dmg_tp = eDamageType::SPECIAL; + spec_dam = calc_spec_dam(ammo.ability,ammo.abil_data[0],ammo.abil_data[1],*victim,dmg_tp); + if(ammo.ability == eItemAbil::HEALING_WEAPON) { ASB(" There is a flash of light."); - cur_monst->health += r2; - } - else damage_monst(targ_monst, missile_firer, r2, spec_dam, eDamageType::WEAPON,13); - if(bonus_dam) damage_monst(targ_monst, missile_firer, bonus_dam, 0, dmg_tp, 0); - // poison - if(univ.party[missile_firer].status[eStatus::POISONED_WEAPON] > 0 && univ.party[missile_firer].weap_poisoned == ammo_inv_slot) { - poison_amt = univ.party[missile_firer].status[eStatus::POISONED_WEAPON]; - if(univ.party[missile_firer].has_abil_equip(eItemAbil::POISON_AUGMENT) < 24) - poison_amt++; - cur_monst->poison(poison_amt); - } - if((missile.ability == eItemAbil::STATUS_WEAPON) && (get_ran(1,0,1) == 1)) { - apply_weapon_status(eStatus(missile.abil_data[1]), missile.abil_data[0], r2 + spec_dam, targ_monst + 100, "Missile"); - } else if(missile.ability == eItemAbil::SOULSUCKER && get_ran(1,0,1) == 1) { - add_string_to_buf(" Missile drains life."); - univ.party[missile_firer].heal(missile.abil_data[0] / 2); - } else if(missile.ability == eItemAbil::ANTIMAGIC_WEAPON) { - short before = cur_monst->get_magic(); - if(cur_monst->mu + cur_monst->cl > 0 && get_ran(1,0,1) == 1) - cur_monst->drain_sp(missile.abil_data[0]); - if(before > cur_monst->get_magic()) { - add_string_to_buf(" Missile drains energy."); - univ.party[missile_firer].restore_sp((before > cur_monst->get_magic()) / 3); + victim->heal(r2); + } else if(cCreature* monst = dynamic_cast(victim)) { + bool damaged = damage_monst(i_monst - 100, current_pc, r2, eDamageType::WEAPON,13,false); + if(spec_dam > 0) + damaged = damaged || damage_monst(i_monst - 100, current_pc, spec_dam, dmg_tp, 0,false); + if(damaged) monst->damaged_msg(r2, spec_dam); + } else if(cPlayer* who = dynamic_cast(victim)) { + eRace race = missile_firer.race; + bool damaged = damage_pc(i_monst, r2, eDamageType::WEAPON, race, 0, false); + if(spec_dam > 0) + damaged = damaged || damage_pc(i_monst, spec_dam, dmg_tp, race, 0, false); + if(damaged) { + std::string msg = " " + who->name + " takes " + std::to_string(r2); + if(spec_dam) msg += '+' + std::to_string(spec_dam); + add_string_to_buf(msg + '.'); } } - if(cur_monst->abil[eMonstAbil::HIT_TRIGGER].active) { - short s1,s2,s3; - univ.party.force_ptr(21, 301, 5); - univ.party.force_ptr(22, 301, 6); - univ.party.force_ptr(20, 301, 7); - PSD[SDF_SPEC_TARGLOC_X] = cur_monst->cur_loc.x; - PSD[SDF_SPEC_TARGLOC_Y] = cur_monst->cur_loc.y; - PSD[SDF_SPEC_TARGET] = 100 + targ_monst; // ready to be passed to SELECT_TARGET node - run_special(eSpecCtx::ATTACKED_RANGE, 0, cur_monst->abil[eMonstAbil::HIT_TRIGGER].special.extra1, univ.party[missile_firer].combat_pos, &s1, &s2, &s3); - } - } else if((targ_monst = pc_there(target)) < 6) { - eDamageType dmg_tp = eDamageType::UNBLOCKABLE; - cItem& missile = univ.party[missile_firer].items[ammo_inv_slot]; - spec_dam = calc_spec_dam(missile.ability,missile.abil_data[0],missile.abil_data[1],univ.party[targ_monst],dmg_tp); - eRace race = univ.party[missile_firer].race; - if(univ.party[current_pc].items[ammo_inv_slot].ability == eItemAbil::HEALING_WEAPON) { - ASB(" There is a flash of light."); - univ.party[targ_monst].heal(r2); - } - else damage_pc(targ_monst, r2, eDamageType::WEAPON, race, 0); - if(spec_dam > 0) damage_pc(targ_monst, spec_dam, dmg_tp, race, 0); // poison - if(univ.party[missile_firer].status[eStatus::POISONED_WEAPON] > 0 && univ.party[missile_firer].weap_poisoned == ammo_inv_slot) { - poison_amt = univ.party[missile_firer].status[eStatus::POISONED_WEAPON]; - if(univ.party[missile_firer].has_abil_equip(eItemAbil::POISON_AUGMENT) < 24) + if(missile_firer.status[eStatus::POISONED_WEAPON] > 0 && missile_firer.weap_poisoned == ammo_inv_slot) { + poison_amt = missile_firer.status[eStatus::POISONED_WEAPON]; + if(missile_firer.has_abil_equip(eItemAbil::POISON_AUGMENT) < 24) poison_amt++; - univ.party[targ_monst].poison(poison_amt); + victim->poison(poison_amt); } - if((missile.ability == eItemAbil::STATUS_WEAPON) && (get_ran(1,0,1) == 1)) { - apply_weapon_status(eStatus(missile.abil_data[1]), missile.abil_data[0], r2 + spec_dam, targ_monst, "Missile"); - } else if(missile.ability == eItemAbil::SOULSUCKER && get_ran(1,0,1) == 1) { + if((ammo.ability == eItemAbil::STATUS_WEAPON) && (get_ran(1,0,1) == 1)) { + apply_weapon_status(eStatus(ammo.abil_data[1]), ammo.abil_data[0], r2 + spec_dam, *victim, "Missile"); + } else if(ammo.ability == eItemAbil::SOULSUCKER && get_ran(1,0,1) == 1) { add_string_to_buf(" Missile drains life."); - univ.party[missile_firer].heal(missile.abil_data[0] / 2); - } else if(missile.ability == eItemAbil::ANTIMAGIC_WEAPON) { - cPlayer& which_m = univ.party[targ_monst]; - short before = which_m.get_magic(); - if(which_m.skill(eSkill::MAGE_SPELLS) + which_m.skill(eSkill::PRIEST_SPELLS) > 0 && - get_ran(1,0,1) == 1) - which_m.drain_sp(missile.abil_data[0]); - if(before > which_m.get_magic()) { + missile_firer.heal(ammo.abil_data[0] / 2); + } else if(ammo.ability == eItemAbil::ANTIMAGIC_WEAPON) { + short before = victim->get_magic(); + int mage = 0, cleric = 0; + if(cCreature* check = dynamic_cast(victim)) + mage = check->mu, cleric = check->cl; + else if(cPlayer* check = dynamic_cast(victim)) + mage = check->skill(eSkill::MAGE_SPELLS), cleric = check->skill(eSkill::PRIEST_SPELLS); + if(mage + cleric > 0 && get_ran(1,0,1) == 1) + victim->drain_sp(ammo.abil_data[0]); + if(before > victim->get_magic()) { add_string_to_buf(" Missile drains energy."); - univ.party[missile_firer].restore_sp((before > which_m.get_magic()) / 3); + missile_firer.restore_sp((before - victim->get_magic()) / 3); } - } else if(missile.ability == eItemAbil::WEAPON_CALL_SPECIAL) { + } else if(ammo.ability == eItemAbil::WEAPON_CALL_SPECIAL) { + // TODO: Should this be checked on the missile as well as on the ammo? (Provided they're different.) short s1,s2,s3; univ.party.force_ptr(21, 301, 5); univ.party.force_ptr(22, 301, 6); univ.party.force_ptr(20, 301, 7); - PSD[SDF_SPEC_TARGLOC_X] = univ.party[targ_monst].combat_pos.x; - PSD[SDF_SPEC_TARGLOC_Y] = univ.party[targ_monst].combat_pos.y; - PSD[SDF_SPEC_TARGET] = 11 + targ_monst; // ready to be passed to SELECT_TARGET node - run_special(eSpecCtx::ATTACKING_RANGE, 0, missile.abil_data[0], univ.party[missile_firer].combat_pos, &s1, &s2, &s3); + PSD[SDF_SPEC_TARGLOC_X] = victim->get_loc().x; + PSD[SDF_SPEC_TARGLOC_Y] = victim->get_loc().y; + PSD[SDF_SPEC_TARGET] = univ.get_target_i(*victim); + run_special(eSpecCtx::ATTACKING_RANGE, 0, missile.abil_data[0], missile_firer.combat_pos, &s1, &s2, &s3); } - int spec_item = univ.party[targ_monst].has_abil_equip(eItemAbil::HIT_CALL_SPECIAL); - if(spec_item < 24) { + cCreature* monst; cPlayer* pc; int spec_item; + if((monst = dynamic_cast(victim)) && monst->abil[eMonstAbil::HIT_TRIGGER].active) { short s1,s2,s3; univ.party.force_ptr(21, 301, 5); univ.party.force_ptr(22, 301, 6); univ.party.force_ptr(20, 301, 7); - PSD[SDF_SPEC_TARGLOC_X] = univ.party[targ_monst].combat_pos.x; - PSD[SDF_SPEC_TARGLOC_Y] = univ.party[targ_monst].combat_pos.y; - PSD[SDF_SPEC_TARGET] = 11 + targ_monst; // ready to be passed to SELECT_TARGET node - run_special(eSpecCtx::ATTACKED_RANGE, 0, univ.party[targ_monst].items[spec_item].abil_data[0], univ.party[missile_firer].combat_pos, &s1, &s2, &s3); + PSD[SDF_SPEC_TARGLOC_X] = monst->cur_loc.x; + PSD[SDF_SPEC_TARGLOC_Y] = monst->cur_loc.y; + PSD[SDF_SPEC_TARGET] = 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); + } 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, 301, 5); + univ.party.force_ptr(22, 301, 6); + univ.party.force_ptr(20, 301, 7); + PSD[SDF_SPEC_TARGLOC_X] = pc->combat_pos.x; + PSD[SDF_SPEC_TARGLOC_Y] = pc->combat_pos.y; + PSD[SDF_SPEC_TARGET] = 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(univ.party[missile_firer].items[ammo_inv_slot].variety != eItemType::MISSILE_NO_AMMO) { - if(univ.party[missile_firer].items[ammo_inv_slot].ability != eItemAbil::RETURNING_MISSILE) - univ.party[missile_firer].items[ammo_inv_slot].charges--; - else univ.party[missile_firer].items[ammo_inv_slot].charges = 1; - if(univ.party[missile_firer].has_abil_equip(eItemAbil::DRAIN_MISSILES) < 24 - && univ.party[missile_firer].items[ammo_inv_slot].ability != eItemAbil::RETURNING_MISSILE) - univ.party[missile_firer].items[ammo_inv_slot].charges--; - if(univ.party[missile_firer].items[ammo_inv_slot].charges <= 0) - univ.party[missile_firer].take_item(ammo_inv_slot); - if(missile_firer == stat_window) + if(ammo.variety != eItemType::MISSILE_NO_AMMO) { + if(ammo.ability != eItemAbil::RETURNING_MISSILE) + ammo.charges--; + else ammo.charges = 1; + if(missile_firer.has_abil_equip(eItemAbil::DRAIN_MISSILES) < 24 + && ammo.ability != eItemAbil::RETURNING_MISSILE) + ammo.charges--; + if(ammo.charges <= 0) + missile_firer.take_item(ammo_inv_slot); + if(current_pc == stat_window) put_item_screen(stat_window,1); } } if(!exploding){ combat_posing_monster = current_working_monster = -1; - move_to_zero(univ.party[missile_firer].status[eStatus::POISONED_WEAPON]); + move_to_zero(missile_firer.status[eStatus::POISONED_WEAPON]); } print_buf(); } @@ -2248,7 +2259,7 @@ void do_monster_turn() { if(pick_abil.second.active) { 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,target); + 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) @@ -2281,23 +2292,25 @@ void do_monster_turn() { } } // Special attacks - // Attack pc - if(univ.town.monst[i].target < 6 && univ.party[univ.town.monst[i].target].main_status == eMainStatus::ALIVE - && (monst_adjacent(targ_space,i)) && (cur_monst->attitude % 2 == 1) - && !acted_yet) { - monster_attack_pc(i,univ.town.monst[i].target); - take_m_ap(4,cur_monst); - acted_yet = true; - had_monst = true; - } - // Attack monst - if((univ.town.monst[i].target >= 100) && (univ.town.monst[univ.town.monst[i].target - 100].active > 0) - && (monst_adjacent(targ_space,i)) && (cur_monst->attitude > 0) - && !acted_yet) { - monster_attack_monster(i,univ.town.monst[i].target - 100); - take_m_ap(4,cur_monst); - acted_yet = true; - had_monst = true; + // Attack melee + iLiving& who = univ.get_target(target); + if(!acted_yet && who.is_alive() && monst_adjacent(targ_space,i)) { + bool do_attack = false; + if(dynamic_cast(&who)) { + // Attack a PC only if hostile + if(cur_monst->attitude % 2 == 1) + do_attack = true; + } else if(dynamic_cast(&who)) { + // Attack a monster only if not docile + if(cur_monst->attitude > 0) + do_attack = true; + } + if(do_attack) { + monster_attack(i, &who); + take_m_ap(4,cur_monst); + acted_yet = true; + had_monst = true; + } } if(acted_yet) { @@ -2320,7 +2333,7 @@ void do_monster_turn() { if(univ.party[k].parry > 99 && monst_adjacent(univ.party[k].combat_pos,i) && (cur_monst->active > 0)) { univ.party[k].parry = 0; - pc_attack(k,100 + i); + pc_attack(k,cur_monst); } } @@ -2331,7 +2344,7 @@ void do_monster_turn() { if(univ.party[k].parry > 99 && monst_adjacent(univ.party[k].combat_pos,i) && (cur_monst->active > 0) && (cur_monst->attitude % 2 == 1)) { univ.party[k].parry = 0; - pc_attack(k,100 + i); + pc_attack(k,cur_monst); } } @@ -2359,7 +2372,7 @@ void do_monster_turn() { && (pc_adj[k]) && (cur_monst->attitude % 2 == 1) && (cur_monst->active > 0) && univ.party[k].status[eStatus::INVISIBLE] == 0) { combat_posing_monster = current_working_monster = k; - pc_attack(k,100 + i); + pc_attack(k,cur_monst); combat_posing_monster = current_working_monster = 100 + i; pc_adj[k] = false; } @@ -2434,7 +2447,7 @@ void do_monster_turn() { printed_acid = true; } r1 = get_ran(cur_monst->status[eStatus::ACID],1,6); - damage_monst(i, 6,r1, 0, eDamageType::MAGIC,0); + damage_monst(i, 6,r1, eDamageType::MAGIC,0); cur_monst->status[eStatus::ACID]--; } @@ -2454,7 +2467,7 @@ void do_monster_turn() { printed_poison = true; } r1 = get_ran(cur_monst->status[eStatus::POISON],1,6); - damage_monst(i, 6, r1, 0, eDamageType::POISON,0); + damage_monst(i, 6, r1, eDamageType::POISON,0); cur_monst->status[eStatus::POISON]--; } if(cur_monst->status[eStatus::DISEASE] > 0) { // Disease @@ -2493,33 +2506,32 @@ void do_monster_turn() { monsters_going = false; } -void monster_attack_pc(short who_att,short target) { - cCreature *attacker; +void monster_attack(short who_att,iLiving* target) { short r1,r2,i,store_hp,sound_type = 0; eDamageType dam_type = eDamageType::WEAPON; + cCreature* attacker = &univ.town.monst[who_att]; - attacker = &univ.town.monst[who_att]; - - // A peaceful monster won't attack - if(attacker->attitude % 2 != 1) + // A peaceful monster won't attack PCs or friendly monsters + if(attacker->is_friendly() && target->is_friendly()) return; // Draw attacker frames if((is_combat()) - && ((center_on_monst) || !monsters_going)) { + && (center_on_monst || !monsters_going)) { if(!attacker->invisible) frame_space(attacker->cur_loc,0,attacker->x_width,attacker->y_width); - frame_space(univ.party[target].combat_pos,1,1,1); + frame_space(target->get_loc(),1,1,1); } - if((attacker->a[0].dice != 0) || (attacker->a[2].dice != 0)) - print_monst_attacks(attacker->number,target); + if(attacker->a[0].dice != 0 || attacker->a[1].dice != 0 || attacker->a[2].dice != 0) + attacker->print_attacks(target); // Check sanctuary - if(univ.party[target].status[eStatus::INVISIBLE] > 0) { + // TODO: What about monster permanent invisibility? + if(target->status[eStatus::INVISIBLE] > 0) { r1 = get_ran(1,1,100); if(r1 > hit_chance[attacker->level / 2]) { add_string_to_buf(" Can't find target! "); @@ -2528,27 +2540,42 @@ void monster_attack_pc(short who_att,short target) { } for(i = 0; i < 3; i++) { - if(attacker->a[i].dice > 0 && univ.party[target].main_status == eMainStatus::ALIVE) { + if(attacker->a[i].dice > 0 && target->is_alive()) { // sprintf ((char *) create_line, " Attacks %s.",(char *) univ.party[target].name); // add_string_to_buf((char *) create_line); + // Some things depend on whether it's a player or a monster. + cCreature* m_target = dynamic_cast(target); + cPlayer* pc_target = dynamic_cast(target); + + // if target monster friendly to party, make able to attack + if(m_target != nullptr && m_target->attitude == 0) + m_target->attitude = 2; + // Attack roll - r1 = get_ran(1,1,100) - 5 * min(8,attacker->status[eStatus::BLESS_CURSE]) + 5 * univ.party[target].status[eStatus::BLESS_CURSE] - + 5 * stat_adj(target,eSkill::DEXTERITY) - 15; + r1 = get_ran(1,1,100); + r1 -= 5 * min(8, attacker->status[eStatus::BLESS_CURSE]); + r1 += 5 * target->status[eStatus::BLESS_CURSE] - 15; r1 += 5 * (attacker->status[eStatus::WEBS] / 3); - r1 += univ.party[target].get_prot_level(eItemAbil::EVASION); - if(univ.party[target].parry < 100) - r1 += 5 * univ.party[target].parry; + if(pc_target != nullptr) { + r1 += 5 * pc_target->stat_adj(eSkill::DEXTERITY); + r1 += pc_target->get_prot_level(eItemAbil::EVASION); + if(pc_target->parry < 100) + r1 += 5 * pc_target->parry; + } // Damage roll - r2 = get_ran(attacker->a[i].dice,1,attacker->a[i].sides) - + min(8,attacker->status[eStatus::BLESS_CURSE]) - univ.party[target].status[eStatus::BLESS_CURSE] + 1; - if(univ.difficulty_adjust() > 2) - r2 = r2 * 2; - if(univ.difficulty_adjust() == 2) - r2 = (r2 * 3) / 2; + r2 = get_ran(attacker->a[i].dice, 1, attacker->a[i].sides) + 1; + r2 += min(8, attacker->status[eStatus::BLESS_CURSE]); + r2 -= target->status[eStatus::BLESS_CURSE]; + if(pc_target != nullptr) { + if(univ.difficulty_adjust() > 2) + r2 = r2 * 2; + if(univ.difficulty_adjust() == 2) + r2 = (r2 * 3) / 2; + } else r2 += 1; - if((univ.party[target].status[eStatus::ASLEEP] > 0) || (univ.party[target].status[eStatus::PARALYZED] > 0)) { + if((target->status[eStatus::ASLEEP] > 0) || (target->status[eStatus::PARALYZED] > 0)) { r1 -= 80; r2 = r2 * 2; } @@ -2561,18 +2588,26 @@ void monster_attack_pc(short who_att,short target) { if(attacker->m_type == eRace::DEMON) dam_type = eDamageType::DEMON; - store_hp = univ.party[target].cur_health; + store_hp = target->get_health(); sound_type = get_monst_sound(attacker,i); - if(damage_pc(target,r2,dam_type, - attacker->m_type,sound_type) && - (store_hp - univ.party[target].cur_health > 0)) { - damaged_message(store_hp - univ.party[target].cur_health, - attacker->a[i].type); + size_t i_monst = univ.get_target_i(*target); + bool damaged = false; + if(m_target != nullptr) { + // TODO: Maybe this damage should be printed? + damaged = damage_monst(i_monst - 100,7,r2,dam_type,sound_type,false); + } else if(pc_target != nullptr) { + damaged = damage_pc(i_monst,r2,dam_type,attacker->m_type,sound_type); + if(store_hp - target->get_health() <= 0) + damaged = false; + } + if(damaged) { + damaged_message(store_hp - target->get_health(), attacker->a[i].type); - if(univ.party[target].is_shielded()) { - int dmg = univ.party[target].get_shared_dmg(store_hp - univ.party[target].get_health()); + if(target->is_shielded()) { + int dmg = attacker->get_shared_dmg(store_hp - target->get_health()); add_string_to_buf(" Shares damage! "); - damage_monst(who_att, 6, dmg, 0, eDamageType::MAGIC,0); + int who_hit = pc_target != nullptr ? 6 : 7; + damage_monst(who_att, who_hit, dmg, eDamageType::MAGIC,0); } for(auto& abil : attacker->abil) { @@ -2588,11 +2623,18 @@ void monster_attack_pc(short who_att,short target) { switch(abil.first) { case eMonstAbil::STUN: add_string_to_buf(" Stuns!"); break; case eMonstAbil::PETRIFY: add_string_to_buf(" Petrifying touch!"); break; - case eMonstAbil::DRAIN_SP: add_string_to_buf(" Drains magic!"); break; + case eMonstAbil::DRAIN_SP: add_string_to_buf(" Drains magic!"); break; // TODO: This has no effect on monsters case eMonstAbil::DRAIN_XP: add_string_to_buf(" Drains life!"); break; case eMonstAbil::KILL: add_string_to_buf(" Killing touch!"); break; - case eMonstAbil::STEAL_FOOD: add_string_to_buf(" Steals food!"); snd = 26; break; - case eMonstAbil::STEAL_GOLD: add_string_to_buf(" Steals gold!"); break; // TODO: Pick a sound + case eMonstAbil::STEAL_FOOD: + if(pc_target != nullptr) continue; // Can't use this against other monsters. + add_string_to_buf(" Steals food!"); + snd = 26; + break; + case eMonstAbil::STEAL_GOLD: + if(pc_target != nullptr) continue; // Can't use this against other monsters. + add_string_to_buf(" Steals gold!"); + break; // TODO: Pick a sound case eMonstAbil::FIELD: break; // TODO: Invent messages? case eMonstAbil::DAMAGE: case eMonstAbil::DAMAGE2: switch(abil.second.gen.dmg) { @@ -2624,19 +2666,30 @@ void monster_attack_pc(short who_att,short target) { break; } if(snd > 0) play_sound(snd); + print_buf(); monst_basic_abil(who_att, abil, target); + put_pc_screen(); } - int spec_item = univ.party[target].has_abil_equip(eItemAbil::HIT_CALL_SPECIAL); - if(spec_item < 24) { + int spec_item; + if(pc_target != nullptr && (spec_item = pc_target->has_abil_equip(eItemAbil::HIT_CALL_SPECIAL)) < 24) { short s1,s2,s3; univ.party.force_ptr(21, 301, 5); univ.party.force_ptr(22, 301, 6); univ.party.force_ptr(20, 301, 7); - PSD[SDF_SPEC_TARGLOC_X] = univ.party[target].combat_pos.x; - PSD[SDF_SPEC_TARGLOC_Y] = univ.party[target].combat_pos.y; - PSD[SDF_SPEC_TARGET] = 11 + who_att; // ready to be passed to SELECT_TARGET node - run_special(eSpecCtx::ATTACKED_MELEE, 0, univ.party[target].items[spec_item].abil_data[0], attacker->cur_loc, &s1, &s2, &s3); + PSD[SDF_SPEC_TARGLOC_X] = target->get_loc().x; + PSD[SDF_SPEC_TARGLOC_Y] = target->get_loc().y; + PSD[SDF_SPEC_TARGET] = i_monst; + run_special(eSpecCtx::ATTACKED_MELEE, 0, pc_target->items[spec_item].abil_data[0], attacker->cur_loc, &s1, &s2, &s3); + } else if(m_target != nullptr && m_target->abil[eMonstAbil::HIT_TRIGGER].active) { + short s1,s2,s3; + univ.party.force_ptr(21, 301, 5); + univ.party.force_ptr(22, 301, 6); + univ.party.force_ptr(20, 301, 7); + PSD[SDF_SPEC_TARGLOC_X] = target->get_loc().x; + PSD[SDF_SPEC_TARGLOC_Y] = target->get_loc().y; + PSD[SDF_SPEC_TARGET] = i_monst; + run_special(eSpecCtx::ATTACKED_MELEE, 0, m_target->abil[eMonstAbil::HIT_TRIGGER].special.extra1, attacker->cur_loc, &s1, &s2, &s3); } } } @@ -2649,178 +2702,30 @@ void monster_attack_pc(short who_att,short target) { combat_posing_monster = -1; draw_terrain(2); combat_posing_monster = 100 + who_att; - - } - if(univ.party[target].main_status != eMainStatus::ALIVE) - i = 3; + if(!target->is_alive()) + break; } } -void monster_attack_monster(short who_att,short attackee) { - cCreature *attacker,*target; - short r1,r2,i,store_hp,sound_type = 0; - eDamageType dam_type = eDamageType::WEAPON; - - attacker = &univ.town.monst[who_att]; - target = &univ.town.monst[attackee]; - - // Draw attacker frames - if((is_combat()) - && (center_on_monst || !monsters_going)) { - if(!attacker->invisible) - frame_space(attacker->cur_loc,0,attacker->x_width,attacker->y_width); - frame_space(target->cur_loc,1,1,1); - } - - - if((attacker->a[1].dice != 0) || (attacker->a[0].dice != 0)) - print_monst_attacks(attacker->number,100 + attackee); - for(i = 0; i < 3; i++) { - if((attacker->a[i].dice > 0) && (target->active != 0)) { -// sprintf ((char *) create_line, " Attacks %s.",(char *) univ.party[target].name); -// add_string_to_buf((char *) create_line); - - // if friendly to party, make able to attack - if(target->attitude == 0) - target->attitude = 2; - - // Attack roll - r1 = get_ran(1,1,100) - 5 * min(10,attacker->status[eStatus::BLESS_CURSE]) - + 5 * target->status[eStatus::BLESS_CURSE] - 15; - r1 += 5 * (attacker->status[eStatus::WEBS] / 3); - - // Damage roll - r2 = get_ran(attacker->a[i].dice,1,attacker->a[i].sides) - + min(10,attacker->status[eStatus::BLESS_CURSE]) - target->status[eStatus::BLESS_CURSE] + 2; - - if(target->status[eStatus::ASLEEP] > 0 || target->status[eStatus::PARALYZED] > 0) { - r1 -= 80; - r2 = r2 * 2; - } - - draw_terrain(2); - // Check if hit, and do effects - if(r1 <= hit_chance[(attacker->skill + 4) / 2]) { - if(attacker->m_type == eRace::DEMON) - dam_type = eDamageType::DEMON; - if(attacker->m_type == eRace::UNDEAD) - dam_type = eDamageType::UNDEAD; - store_hp = target->health; - - sound_type = get_monst_sound(attacker,i); - if(damage_monst(attackee,7,r2,0,dam_type,sound_type,false)) { - damaged_message(store_hp - target->health, - attacker->a[i].type); - - if(target->is_shielded()) { - int how_much = target->get_shared_dmg(store_hp - target->get_health()); - add_string_to_buf(" Shares damage! "); - damage_monst(who_att, 7, how_much, 0, eDamageType::MAGIC, 0); - } - - for(auto& abil : attacker->abil) { - if(!abil.second.active) continue; - if(getMonstAbilCategory(abil.first) != eMonstAbilCat::GENERAL) - continue; - if(abil.second.gen.type != eMonstGen::TOUCH) - continue; - if(abil.second.gen.range > 0 && get_ran(1,1,1000) <= abil.second.gen.range) - continue; - // Print message and possibly choose sound - snd_num_t snd = 0; - switch(abil.first) { - case eMonstAbil::STUN: add_string_to_buf(" Stuns!"); break; - case eMonstAbil::PETRIFY: add_string_to_buf(" Petrifying touch!"); break; - case eMonstAbil::DRAIN_SP: add_string_to_buf(" Drains magic!"); break; - case eMonstAbil::KILL: add_string_to_buf(" Killing touch!"); break; - case eMonstAbil::FIELD: break; // TODO: Invent messages? - case eMonstAbil::DAMAGE: case eMonstAbil::DAMAGE2: - switch(abil.second.gen.dmg) { - case eDamageType::FIRE: add_string_to_buf(" Burning touch!"); break; - case eDamageType::COLD: add_string_to_buf(" Freezing touch!"); break; - case eDamageType::MAGIC: add_string_to_buf(" Shocking touch!"); break; - case eDamageType::UNBLOCKABLE: add_string_to_buf(" Eerie touch!"); break; - case eDamageType::POISON: add_string_to_buf(" Slimy touch!"); break; - case eDamageType::WEAPON: add_string_to_buf(" Drains stamina!"); break; - case eDamageType::UNDEAD: add_string_to_buf(" Chilling touch!"); break; - case eDamageType::DEMON: add_string_to_buf(" Unholy touch!"); break; - } - break; - case eMonstAbil::STATUS: case eMonstAbil::STATUS2: - switch(abil.second.gen.stat) { - case eStatus::POISON: add_string_to_buf(" Poisonous!"); break; - case eStatus::DISEASE: add_string_to_buf(" Causes disease!"); break; - case eStatus::DUMB: add_string_to_buf(" Dumbfounds!"); break; - case eStatus::WEBS: add_string_to_buf(" Webs!"); break; - case eStatus::ASLEEP: add_string_to_buf(" Sleeps!"); break; - case eStatus::PARALYZED: add_string_to_buf(" Paralysis touch!"); break; - case eStatus::ACID: add_string_to_buf(" Acid touch!"); break; - case eStatus::HASTE_SLOW: add_string_to_buf(" Slowing touch!"); break; - case eStatus::BLESS_CURSE: add_string_to_buf(" Cursing touch!"); break; - } - break; - } - if(snd > 0) play_sound(snd); - print_buf(); - monst_basic_abil(who_att, abil, attackee + 100); - put_pc_screen(); - } - - if(target->abil[eMonstAbil::HIT_TRIGGER].active) { - short s1,s2,s3; - univ.party.force_ptr(21, 301, 5); - univ.party.force_ptr(22, 301, 6); - univ.party.force_ptr(20, 301, 7); - PSD[SDF_SPEC_TARGLOC_X] = target->cur_loc.x; - PSD[SDF_SPEC_TARGLOC_Y] = target->cur_loc.y; - PSD[SDF_SPEC_TARGET] = 100 + attackee; // ready to be passed to SELECT_TARGET node - run_special(eSpecCtx::ATTACKED_MELEE, 0, target->abil[eMonstAbil::HIT_TRIGGER].special.extra1, attacker->cur_loc, &s1, &s2, &s3); - } - } - } - else { - add_string_to_buf(" Misses."); - play_sound(2); - } - combat_posing_monster = -1; - draw_terrain(2); - combat_posing_monster = 100 + who_att; - } - if(target->active == 0) - i = 3; - } - -} - - //short target; // 100 + - monster is target -void monst_fire_missile(short m_num,short bless,std::pair abil,location source,short target) { - cCreature *m_target; +void monst_fire_missile(short m_num,short bless,std::pair abil,location source,iLiving* target) { short r1,r2,dam[40] = { 0,1,2,3,4, 6,8,7,0,0, 0,0,0,0,0, 0,0,0,0,0, 8,0,0,0,0, 0,0,0,0,0, 0,0,0,0,6, 0,0,0,0,0},i,j; location targ_space; - if(target == 6) - return; - if(target >= 100) { - targ_space = univ.town.monst[target - 100].cur_loc; - if(univ.town.monst[target - 100].active == 0) - return; - } - else { - targ_space = (is_combat()) ? univ.party[target].combat_pos : univ.town.p_loc; - if(univ.party[target].main_status != eMainStatus::ALIVE) - return; - } + if(target == nullptr) return; + if(!target->is_alive()) return; + targ_space = target->get_loc(); + + cPlayer* pc_target = dynamic_cast(target); + cCreature* m_target = dynamic_cast(target); - if(target >= 100) - m_target = &univ.town.monst[target - 100]; if(((overall_mode >= MODE_COMBAT) && (overall_mode <= MODE_TALKING)) && (center_on_monst)) { frame_space(source,0,univ.town.monst[m_num].x_width,univ.town.monst[m_num].y_width); - if(target >= 100) + if(m_target != nullptr) frame_space(targ_space,1,m_target->x_width,m_target->y_width); else frame_space(targ_space,1,1,1); } @@ -2832,96 +2737,117 @@ void monst_fire_missile(short m_num,short bless,std::pair a case eMonstMissile::ARROW: case eMonstMissile::BOLT: snd = 12; - if(target < 100) add_string_to_buf(" Shoots at " + univ.party[target].name + '.'); - else m_target->spell_note(12); + if(pc_target != nullptr) + add_string_to_buf(" Shoots at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(12); break; case eMonstMissile::SPEAR: - if(target < 100) add_string_to_buf(" Throws spear at " + univ.party[target].name + '.'); - else m_target->spell_note(13); + if(pc_target != nullptr) + add_string_to_buf(" Throws spear at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(13); break; case eMonstMissile::RAZORDISK: - if(target < 100) add_string_to_buf(" Throws razordisk at " + univ.party[target].name + '.'); - else m_target->spell_note(15); + if(pc_target != nullptr) + add_string_to_buf(" Throws razordisk at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(15); break; case eMonstMissile::SPINE: - if(target < 100) add_string_to_buf(" Fires spines at " + univ.party[target].name + '.'); - else m_target->spell_note(32); + if(pc_target != nullptr) + add_string_to_buf(" Fires spines at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(32); break; case eMonstMissile::DART: - if(target < 100) add_string_to_buf(" Throws dart at " + univ.party[target].name + '.'); - else m_target->spell_note(53); + if(pc_target != nullptr) + add_string_to_buf(" Throws dart at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(53); break; case eMonstMissile::ROCK: - if(target < 100) add_string_to_buf(" Throws rock at " + univ.party[target].name + '.'); - else m_target->spell_note(14); + if(pc_target != nullptr) + add_string_to_buf(" Throws rock at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(14); break; case eMonstMissile::KNIFE: - if(target < 100) add_string_to_buf(" Throws knife at " + univ.party[target].name + '.'); - else m_target->spell_note(54); + if(pc_target != nullptr) + add_string_to_buf(" Throws knife at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(54); break; } if(abil.second.missile.type == eMonstMissile::ARROW || abil.second.missile.type == eMonstMissile::BOLT) snd = 12; if(abil.second.missile.pic < 0) play_sound(snd); else run_a_missile(source, targ_space, abil.second.missile.pic, 1, snd, 0, 0, 100); - if((target < 100 && univ.party[target].status[eStatus::INVISIBLE] > 0) || m_target->invisible) { + if(target->status[eStatus::INVISIBLE] > 0 || (m_target != nullptr && m_target->invisible)) { if(get_ran(1,1,100) > hit_chance[univ.town.monst[m_num].level]) { add_string_to_buf(" Can't find target!"); return; } } - int target_bless = target < 100 ? univ.party[target].status[eStatus::BLESS_CURSE] : m_target->status[eStatus::BLESS_CURSE]; - location target_pos = target < 100 ? univ.party[target].combat_pos : m_target->cur_loc; - int r1 = get_ran(1,1,100) - 5 * min(10,bless) + 5 * target_bless - 5 * can_see_light(source, target_pos, sight_obscurity); - r1 += univ.party[target].get_prot_level(eItemAbil::EVASION); - if(univ.party[target].parry < 100) - r1 += 5 * univ.party[target].parry; + int target_bless = target->status[eStatus::BLESS_CURSE]; + int r1 = get_ran(1,1,100) - 5 * min(8,bless) + 5 * target_bless - 5 * can_see_light(source, targ_space, sight_obscurity); + if(pc_target != nullptr) { + r1 += pc_target->get_prot_level(eItemAbil::EVASION); + if(pc_target->parry < 100) + r1 += 5 * pc_target->parry; + } + size_t i_monst = univ.get_target_i(*target); if(r1 <= hit_chance[abil.second.missile.skill]) { r2 = get_ran(abil.second.missile.dice,1,abil.second.missile.sides) + min(10,bless); - if(target < 100) { - add_string_to_buf(" Hits " + univ.party[target].name + '.'); + if(pc_target != nullptr) { + add_string_to_buf(" Hits " + pc_target->name + '.'); // TODO: Should we pass in the monster's actual race here? - damage_pc(target,r2,eDamageType::WEAPON,eRace::UNKNOWN,13); - } else { + damage_pc(i_monst,r2,eDamageType::WEAPON,eRace::UNKNOWN,13); + } else if(m_target != nullptr) { m_target->spell_note(16); - damage_monst(target - 100,7,r2,0,eDamageType::WEAPON,13); + damage_monst(i_monst - 100,7,r2,eDamageType::WEAPON,13); } } else { - if(target < 100) add_string_to_buf(" Misses " + univ.party[target].name + '.'); - else m_target->spell_note(18); + if(pc_target != nullptr) + add_string_to_buf(" Misses " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(18); } - if(target < 100) { - int spec_item = univ.party[target].has_abil_equip(eItemAbil::HIT_CALL_SPECIAL); + if(pc_target != nullptr) { + int spec_item = pc_target->has_abil_equip(eItemAbil::HIT_CALL_SPECIAL); if(spec_item < 24) { short s1,s2,s3; // TODO: This force_ptr...run_special code is almost duplicated in several places; maybe make a call_attack_spec subroutine? univ.party.force_ptr(21, 301, 5); univ.party.force_ptr(22, 301, 6); univ.party.force_ptr(20, 301, 7); - PSD[SDF_SPEC_TARGLOC_X] = univ.party[target].combat_pos.x; - PSD[SDF_SPEC_TARGLOC_Y] = univ.party[target].combat_pos.y; - PSD[SDF_SPEC_TARGET] = 11 + target; // ready to be passed to SELECT_TARGET node - run_special(eSpecCtx::ATTACKED_RANGE, 0, univ.party[target].items[spec_item].abil_data[0], univ.town.monst[m_num].cur_loc, &s1, &s2, &s3); + PSD[SDF_SPEC_TARGLOC_X] = target->get_loc().x; + PSD[SDF_SPEC_TARGLOC_Y] = target->get_loc().y; + PSD[SDF_SPEC_TARGET] = 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); } - } else if(m_target->abil[eMonstAbil::HIT_TRIGGER].active) { + } else if(m_target != nullptr && m_target->abil[eMonstAbil::HIT_TRIGGER].active) { short s1,s2,s3; univ.party.force_ptr(21, 301, 5); univ.party.force_ptr(22, 301, 6); univ.party.force_ptr(20, 301, 7); PSD[SDF_SPEC_TARGLOC_X] = m_target->cur_loc.x; PSD[SDF_SPEC_TARGLOC_Y] = m_target->cur_loc.y; - PSD[SDF_SPEC_TARGET] = target; // ready to be passed to SELECT_TARGET node + PSD[SDF_SPEC_TARGET] = 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); } } else if(abil.first == eMonstAbil::MISSILE_WEB) { - if(target < 100) add_string_to_buf(" Throws web at " + univ.party[target].name + '.'); - else m_target->spell_note(58); + if(pc_target != nullptr) + add_string_to_buf(" Throws web at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(58); run_a_missile(source, targ_space, 8, 0, 14, 0, 0, 100); web_space(targ_space.x, targ_space.y); } else if(abil.first == eMonstAbil::RAY_HEAT) { - if(target < 100) add_string_to_buf(" Hits " + univ.party[target].name + " with heat ray!"); - else m_target->spell_note(55); + if(pc_target != nullptr) add_string_to_buf(" Hits " + pc_target->name + " with heat ray!"); + else if(m_target != nullptr) + m_target->spell_note(55); run_a_missile(source, targ_space, 13, 0, 51, 0, 0, 100); uAbility proxy = {true}; proxy.gen.strength = abil.second.special.extra3; @@ -2929,15 +2855,16 @@ void monst_fire_missile(short m_num,short bless,std::pair a proxy.gen.dmg = eDamageType::FIRE; monst_basic_abil(m_num, {eMonstAbil::DAMAGE, proxy}, target); } else { - if(abil.first == eMonstAbil::DRAIN_SP && target < 100 && univ.party[target].cur_sp < 4) { + if(abil.first == eMonstAbil::DRAIN_SP && pc_target != nullptr && pc_target->cur_sp < 4) { // modify target if target has no sp + // TODO: What if it's a monster with no sp? for(i = 0; i < 8; i++) { j = get_ran(1,0,5); if(univ.party[j].main_status == eMainStatus::ALIVE && univ.party[j].cur_sp > 4 && (can_see_light(source,univ.party[j].combat_pos,sight_obscurity) < 5) && (dist(source,univ.party[j].combat_pos) <= 8)) { - target = j; + target = &univ.party[j]; i = 8; - targ_space = univ.party[target].combat_pos; + targ_space = univ.party[j].combat_pos; } } } @@ -2947,24 +2874,32 @@ void monst_fire_missile(short m_num,short bless,std::pair a case eMonstGen::TOUCH: return; // never reached case eMonstGen::RAY: snd = 51; - if(target < 100) add_string_to_buf(" Fires ray at " + univ.party[target].name + '.'); - else m_target->spell_note(55); + if(pc_target != nullptr) + add_string_to_buf(" Fires ray at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(55); break; case eMonstGen::GAZE: snd = 43; - if(target < 100) add_string_to_buf(" Gazes at " + univ.party[target].name + '.'); - else m_target->spell_note(56); + if(pc_target != nullptr) + add_string_to_buf(" Gazes at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(56); break; case eMonstGen::BREATH: snd = 44; - if(target < 100) add_string_to_buf(" Breathes on " + univ.party[target].name + '.'); - else m_target->spell_note(57); + if(pc_target != nullptr) + add_string_to_buf(" Breathes on " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(57); break; case eMonstGen::SPIT: path_type = 1; snd = 64; - if(target < 100) add_string_to_buf(" Spits at " + univ.party[target].name + '.'); - else m_target->spell_note(59); + if(pc_target != nullptr) + add_string_to_buf(" Spits at " + pc_target->name + '.'); + else if(m_target != nullptr) + m_target->spell_note(59); break; } if(abil.second.gen.pic < 0) play_sound(snd); @@ -2973,13 +2908,15 @@ void monst_fire_missile(short m_num,short bless,std::pair a } } -void monst_basic_abil(short m_num, std::pair abil, short target) { +void monst_basic_abil(short m_num, std::pair abil, iLiving* target) { int i, r1; - if(target == 6) - return; - iLiving& m_target = univ.get_target(target); - location targ_space = m_target.get_loc(); - if(!m_target.is_alive()) return; + if(target == nullptr) return; + if(!target->is_alive()) return; + location targ_space = target->get_loc(); + + cPlayer* pc_target = dynamic_cast(target); + cCreature* m_target = dynamic_cast(target); + switch(abil.first) { case eMonstAbil::DAMAGE: case eMonstAbil::DAMAGE2: // Determine die size @@ -2989,14 +2926,15 @@ void monst_basic_abil(short m_num, std::pair abil, short ta else if(abil.second.gen.type == eMonstGen::SPIT || abil.second.gen.type == eMonstGen::TOUCH) i = 10; r1 = get_ran(abil.second.gen.strength, 1, i); + i = univ.get_target_i(*target); start_missile_anim(); - damage_target(target, r1, abil.second.gen.dmg); + damage_target(i, r1, abil.second.gen.dmg); do_explosion_anim(5, 0); end_missile_anim(); handle_marked_damage(); break; case eMonstAbil::STUN: - if(target < 100 && univ.party[target].has_abil_equip(eItemAbil::LIFE_SAVING) < 24) + if(pc_target != nullptr && pc_target->has_abil_equip(eItemAbil::LIFE_SAVING) < 24) break; case eMonstAbil::STATUS: case eMonstAbil::STATUS2: switch(abil.second.gen.stat) { @@ -3005,39 +2943,39 @@ void monst_basic_abil(short m_num, std::pair abil, short ta if(abil.second.gen.type == eMonstGen::TOUCH) i = abil.second.gen.stat == eStatus::ASLEEP ? -15 : -5; else i = univ.town.monst[m_num].level / 2; - m_target.sleep(abil.second.gen.stat, abil.second.gen.strength, i); + target->sleep(abil.second.gen.stat, abil.second.gen.strength, i); break; case eStatus::ACID: - m_target.acid(abil.second.gen.strength); + target->acid(abil.second.gen.strength); break; case eStatus::POISON: - m_target.poison(abil.second.gen.strength); + target->poison(abil.second.gen.strength); break; case eStatus::BLESS_CURSE: - m_target.curse(abil.second.gen.strength); + target->curse(abil.second.gen.strength); break; case eStatus::HASTE_SLOW: - m_target.slow(abil.second.gen.strength); + target->slow(abil.second.gen.strength); break; case eStatus::WEBS: - m_target.web(abil.second.gen.strength); + target->web(abil.second.gen.strength); break; case eStatus::DISEASE: - m_target.disease(abil.second.gen.strength); + target->disease(abil.second.gen.strength); break; case eStatus::DUMB: - m_target.dumbfound(abil.second.gen.strength); + target->dumbfound(abil.second.gen.strength); break; // These only work on PCs case eStatus::INVULNERABLE: case eStatus::MAGIC_RESISTANCE: case eStatus::INVISIBLE: case eStatus::MARTYRS_SHIELD: // TODO: Wait what? This one works for monsters! - m_target.apply_status(abil.second.gen.stat, -abil.second.gen.strength); + target->apply_status(abil.second.gen.stat, -abil.second.gen.strength); break; // This only works on monsters case eStatus::CHARM: - m_target.sleep(abil.second.gen.stat, univ.town.monst[m_num].attitude, abil.second.gen.strength); + target->sleep(abil.second.gen.stat, univ.town.monst[m_num].attitude, abil.second.gen.strength); break; // These three don't make sense in this context case eStatus::MAIN: @@ -3048,37 +2986,38 @@ void monst_basic_abil(short m_num, std::pair abil, short ta break; case eMonstAbil::PETRIFY: i = univ.town.monst[m_num].level * abil.second.gen.strength / 100; - m_target.petrify(i); + target->petrify(i); break; case eMonstAbil::DRAIN_SP: - if(target < 100) { - add_string_to_buf(" Drains " + univ.party[target].name + '.'); - univ.party[target].cur_sp *= abil.second.gen.strength; - univ.party[target].cur_sp /= 100; + if(pc_target != nullptr) { + add_string_to_buf(" Drains " + pc_target->name + '.'); + pc_target->cur_sp *= abil.second.gen.strength; + pc_target->cur_sp /= 100; } else { - cCreature* who = dynamic_cast(&m_target); - who->spell_note(11); + m_target->spell_note(11); // TODO: If mp < 4 it used to set monster's skill to 1. Should that be restored? - who->mp *= abil.second.gen.strength; - who->mp /= 100; + m_target->mp *= abil.second.gen.strength; + m_target->mp /= 100; } break; case eMonstAbil::DRAIN_XP: - if(target < 100) { - if(univ.party[target].has_abil_equip(eItemAbil::LIFE_SAVING) < 24) break; - drain_pc(target, univ.town.monst[m_num].level * abil.second.gen.strength / 100); + if(pc_target != nullptr) { + i = univ.get_target_i(*target); + if(pc_target->has_abil_equip(eItemAbil::LIFE_SAVING) < 24) break; + drain_pc(i, univ.town.monst[m_num].level * abil.second.gen.strength / 100); } break; case eMonstAbil::KILL: + i = univ.get_target_i(*target); r1 = get_ran(10 * abil.second.gen.strength, 1, 10); - damage_target(target, r1, eDamageType::UNBLOCKABLE); + damage_target(i, r1, eDamageType::UNBLOCKABLE); break; case eMonstAbil::STEAL_FOOD: - if(target >= 100) break; + if(pc_target == nullptr) break; univ.party.food = std::max(0, univ.party.food - get_ran(1,0,abil.second.gen.strength) - abil.second.gen.strength); break; case eMonstAbil::STEAL_GOLD: - if(target >= 100) break; + if(pc_target == nullptr) break; univ.party.gold = std::max(0, univ.party.gold - get_ran(1,0,abil.second.gen.strength) - abil.second.gen.strength); break; case eMonstAbil::FIELD: @@ -3115,7 +3054,7 @@ bool monst_breathe(cCreature *caster,location targ_space,uAbility abil) { run_a_missile(l,targ_space,abil.gen.pic,0,44,0,0,100); else play_sound(44); - monst_breathe_note(caster->number); + caster->breathe_note(); short level = get_ran(abil.gen.strength,1,8); if(overall_mode < MODE_COMBAT) level = level / 3; @@ -3267,7 +3206,7 @@ bool monst_cast_mage(cCreature *caster,short targ) { cost = 4; if(caster->mp >= cost) { - monst_cast_spell_note(caster->number,spell); + caster->cast_spell_note(spell); acted = true; caster->mp -= cost; @@ -3583,7 +3522,7 @@ bool monst_cast_priest(cCreature *caster,short targ) { cost = 10; if(caster->mp >= cost) { - monst_cast_spell_note(caster->number,spell); + caster->cast_spell_note(spell); acted = true; caster->mp -= cost; draw_terrain(2); @@ -3774,7 +3713,7 @@ void damage_target(short target,short dam,eDamageType type) { if(target == 6) return; if(target < 6) damage_pc(target,dam,type,eRace::UNKNOWN,0); - else damage_monst(target - 100, 7, dam, 0, type,0); + else damage_monst(target - 100, 7, dam, type,0); } @@ -4061,7 +4000,7 @@ static void place_spell_pattern(effect_pat_type pat,location center,unsigned sho spot_hit.x = i; spot_hit.y = j; - if(!monster_hit && sight_obscurity(i,j) < 5 && monst_on_space(spot_hit,k)) { + if(!monster_hit && sight_obscurity(i,j) < 5 && univ.town.monst[k].on_space(spot_hit)) { if(pat.pattern[i - center.x + 4][j - center.y + 4] > 0) monster_hit = true; @@ -4075,29 +4014,29 @@ static void place_spell_pattern(effect_pat_type pat,location center,unsigned sho break; case WALL_FORCE: r1 = get_ran(3,1,6); - damage_monst(k, who_hit, r1,0, eDamageType::MAGIC,0); + damage_monst(k, who_hit, r1, eDamageType::MAGIC,0); break; case WALL_FIRE: r1 = get_ran(2,1,6); - damage_monst(k, who_hit, r1,0, eDamageType::FIRE,0); + damage_monst(k, who_hit, r1, eDamageType::FIRE,0); break; case CLOUD_STINK: which_m->curse(get_ran(1,1,2)); break; case WALL_ICE: r1 = get_ran(3,1,6); - damage_monst(k, who_hit, r1,0, eDamageType::COLD,0); + damage_monst(k, who_hit, r1, eDamageType::COLD,0); break; case WALL_BLADES: r1 = get_ran(6,1,8); - damage_monst(k, who_hit, r1,0, eDamageType::WEAPON,0); + damage_monst(k, who_hit, r1, eDamageType::WEAPON,0); break; case CLOUD_SLEEP: which_m->sleep(eStatus::ASLEEP,3,0); break; case OBJECT_BLOCK: r1 = get_ran(6,1,8); - damage_monst(k,who_hit,r1,0,eDamageType::WEAPON,0); + damage_monst(k,who_hit,r1,eDamageType::WEAPON,0); break; case BARRIER_CAGE: univ.town.monst[k].status[eStatus::FORCECAGE] = 8; @@ -4133,7 +4072,7 @@ static void place_spell_pattern(effect_pat_type pat,location center,unsigned sho } if(type == eDamageType::MARKED) break; r1 = get_ran(dice,1,6); - damage_monst(k,who_hit,r1,0,type,0); + damage_monst(k,who_hit,r1,type,0); break; } } @@ -4190,7 +4129,7 @@ void do_shockwave(location target) { if((univ.town.monst[i].active != 0) && (dist(target,univ.town.monst[i].cur_loc) > 0) && (dist(target,univ.town.monst[i].cur_loc) < 11) && (can_see_light(target,univ.town.monst[i].cur_loc,sight_obscurity) < 5)) - damage_monst(i, current_pc, get_ran(2 + dist(target,univ.town.monst[i].cur_loc) / 2 , 1, 6), 0, eDamageType::UNBLOCKABLE,0); + damage_monst(i, current_pc, get_ran(2 + dist(target,univ.town.monst[i].cur_loc) / 2 , 1, 6), eDamageType::UNBLOCKABLE,0); do_explosion_anim(5,0); end_missile_anim(); handle_marked_damage(); @@ -4208,7 +4147,7 @@ void radius_damage(location target,short radius, short dam, eDamageType type) { if((univ.town.monst[i].active != 0) && (dist(target,univ.town.monst[i].cur_loc) > 0) && (dist(target,univ.town.monst[i].cur_loc) <= radius) && (can_see_light(target,univ.town.monst[i].cur_loc,sight_obscurity) < 5)) - damage_monst(i, current_pc, dam, 0, type,0); + damage_monst(i, current_pc, dam, type,0); return; } @@ -4221,7 +4160,7 @@ void radius_damage(location target,short radius, short dam, eDamageType type) { if((univ.town.monst[i].active != 0) && (dist(target,univ.town.monst[i].cur_loc) > 0) && (dist(target,univ.town.monst[i].cur_loc) <= radius) && (can_see_light(target,univ.town.monst[i].cur_loc,sight_obscurity) < 5)) - damage_monst(i, current_pc, dam, 0, type,0); + damage_monst(i, current_pc, dam, type,0); do_explosion_anim(5,0); end_missile_anim(); handle_marked_damage(); @@ -4258,10 +4197,10 @@ void hit_space(location target,short dam,eDamageType type,short report,short hit for(i = 0; i < univ.town.monst.size(); i++) if((hit_monsters) && (univ.town.monst[i].active != 0) && !stop_hitting) - if(monst_on_space(target,i)) { + if(univ.town.monst[i].on_space(target)) { if(processing_fields) - damage_monst(i, 6, dam, 0, type,0); - else damage_monst(i, (monsters_going) ? 7 : current_pc, dam, 0, type,0); + damage_monst(i, 6, dam, type,0); + else damage_monst(i, (monsters_going) ? 7 : current_pc, dam, type,0); stop_hitting = (hit_all == 1) ? false : true; } @@ -4525,7 +4464,7 @@ void combat_immed_mage_cast(short current_pc, eSpell spell_num, bool freebie) { short target, i, num_opp = 0, r1; snd_num_t store_sound = 0; miss_num_t store_m_type = 0; - short bonus = freebie ? 1 : stat_adj(current_pc,eSkill::INTELLIGENCE); + short bonus = freebie ? 1 : univ.party[current_pc].stat_adj(eSkill::INTELLIGENCE); cCreature* which_m; start_missile_anim(); switch(spell_num) { @@ -4715,7 +4654,7 @@ void combat_immed_priest_cast(short current_pc, eSpell spell_num, bool freebie) short target,i,num_opp = 0; snd_num_t store_sound = 0; miss_num_t store_m_type = 0; - short bonus = freebie ? 1 : stat_adj(current_pc,eSkill::INTELLIGENCE); + short bonus = freebie ? 1 : univ.party[current_pc].stat_adj(eSkill::INTELLIGENCE); cCreature *which_m; effect_pat_type protect_pat = {{ {0,1,1,1,1,1,1,1,0}, @@ -4882,38 +4821,39 @@ void start_fancy_spell_targeting(eSpell num, bool freebie, int spell_range, eSpe overall_mode = MODE_FANCY_TARGET; current_pat = single; current_spell_range = num == eSpell::NONE ? spell_range : (*num).range; + short bonus = univ.party[current_pc].stat_adj(eSkill::INTELLIGENCE); switch(num) { // Assign special targeting shapes and number of targets case eSpell::SMITE: - num_targets_left = minmax(1,8,univ.party[current_pc].level / 4 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 2); + num_targets_left = minmax(1,8,univ.party[current_pc].level / 4 + bonus / 2); break; case eSpell::STICKS_TO_SNAKES: - num_targets_left = univ.party[current_pc].level / 5 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 2; + num_targets_left = univ.party[current_pc].level / 5 + bonus / 2; break; case eSpell::SUMMON_HOST: num_targets_left = 5; break; case eSpell::ARROWS_FLAME: - num_targets_left = univ.party[current_pc].level / 4 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 2; + num_targets_left = univ.party[current_pc].level / 4 + bonus / 2; break; case eSpell::ARROWS_VENOM: - num_targets_left = univ.party[current_pc].level / 5 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 2; + num_targets_left = univ.party[current_pc].level / 5 + bonus / 2; break; case eSpell::ARROWS_DEATH: case eSpell::PARALYZE: - num_targets_left = univ.party[current_pc].level / 8 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 3; + num_targets_left = univ.party[current_pc].level / 8 + bonus / 3; break; case eSpell::SPRAY_FIELDS: - num_targets_left = univ.party[current_pc].level / 5 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 2; + num_targets_left = univ.party[current_pc].level / 5 + bonus / 2; current_pat = t; break; case eSpell::SUMMON_WEAK: - num_targets_left = minmax(1,7,univ.party[current_pc].level / 4 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 2); + num_targets_left = minmax(1,7,univ.party[current_pc].level / 4 + bonus / 2); break; case eSpell::SUMMON: - num_targets_left = minmax(1,6,univ.party[current_pc].level / 6 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 2); + num_targets_left = minmax(1,6,univ.party[current_pc].level / 6 + bonus / 2); break; case eSpell::SUMMON_MAJOR: - num_targets_left = minmax(1,5,univ.party[current_pc].level / 8 + stat_adj(current_pc,eSkill::INTELLIGENCE) / 2); + num_targets_left = minmax(1,5,univ.party[current_pc].level / 8 + bonus / 2); break; case eSpell::NONE: num_targets_left = minmax(1,8,targets); @@ -5066,12 +5006,9 @@ void process_fields() { } if(univ.town.is_force_cage(i,j)) { loc.x = i; loc.y = j; - short m = monst_there(loc); - if(m == 90) { - short pc = pc_there(loc); - if(pc == 6) process_force_cage(loc, -1); - else process_force_cage(loc, pc); - } else process_force_cage(loc, 100 + m); + short who = univ.get_target_i(*univ.target_there(loc)); + if(who == 6) who = -1; + process_force_cage(loc, who); } } diff --git a/src/boe.combat.h b/src/boe.combat.h index cad7de0a..7df0dc90 100644 --- a/src/boe.combat.h +++ b/src/boe.combat.h @@ -12,8 +12,8 @@ void start_outdoor_combat(cOutdoors::cCreature encounter,ter_num_t in_which_terr bool pc_combat_move(location destination); void char_parry(); void char_stand_ready(); -void pc_attack(short who_att,short target); -void pc_attack_weapon(short who_att,short target,short hit_adj,short dam_adj,cItem& weap,short primary,bool do_poison); +void pc_attack(short who_att,iLiving* target); +void pc_attack_weapon(short who_att,iLiving& target,short hit_adj,short dam_adj,cItem& weap,short primary,bool do_poison); short calc_spec_dam(eItemAbil abil,short abil_str,short abil_dat,iLiving& monst,eDamageType& dmg_type); void place_target(location target); void do_combat_cast(location target); @@ -24,10 +24,9 @@ bool combat_next_step(); bool pick_next_pc(); void combat_run_monst(); void do_monster_turn(); -void monster_attack_pc(short who_att,short target); -void monster_attack_monster(short who_att,short attackee); -void monst_fire_missile(short m_num,short bless,std::pair abil,location source,short target); -void monst_basic_abil(short m_num, std::pair abil, short target); +void monster_attack(short who_att,iLiving* target); +void monst_fire_missile(short m_num,short bless,std::pair abil,location source,iLiving* target); +void monst_basic_abil(short m_num, std::pair abil, iLiving* target); bool monst_breathe(cCreature *caster,location targ_space,uAbility dam_type); bool monst_cast_mage(cCreature *caster,short targ); bool monst_cast_priest(cCreature *caster,short targ); diff --git a/src/boe.graphics.cpp b/src/boe.graphics.cpp index 3c659d98..f8d68b71 100644 --- a/src/boe.graphics.cpp +++ b/src/boe.graphics.cpp @@ -1489,10 +1489,10 @@ void boom_space(location where,short mode,short type,short damage,short sound) { // source_rect.right += 28 * type; // adjust for possible big monster - which_m = monst_there(where); - if(which_m < 90) { - x_adj += 14 * (univ.town.monst[which_m].x_width - 1); - y_adj += 18 * (univ.town.monst[which_m].y_width - 1); + if(iLiving* who = univ.target_there(where, TARG_MONST)) { + cCreature* monst = dynamic_cast(who); + x_adj += 14 * (monst->x_width - 1); + y_adj += 18 * (monst->y_width - 1); } dest_rect.offset(where_draw.x * 28,where_draw.y * 36); source_rect = store_rect = dest_rect; diff --git a/src/boe.infodlg.cpp b/src/boe.infodlg.cpp index b62ff292..b8d85e36 100644 --- a/src/boe.infodlg.cpp +++ b/src/boe.infodlg.cpp @@ -535,14 +535,14 @@ static void display_pc_info(cDialog& me, const short pc) { else weap2 = i; } - hit_adj = stat_adj(pc,eSkill::DEXTERITY) * 5 - (total_encumberance(pc)) * 5 + hit_adj = univ.party[pc].stat_adj(eSkill::DEXTERITY) * 5 - (total_encumberance(pc)) * 5 + 5 * minmax(-8,8,univ.party[pc].status[eStatus::BLESS_CURSE]); if(!univ.party[pc].traits[eTrait::AMBIDEXTROUS] && weap2 < 24) hit_adj -= 25; // TODO: These should check abil_data[0] instead of item_level // TODO: Perhaps dam_adj and hit_adj calculation should be moved into a function somewhere? - dam_adj = stat_adj(pc,eSkill::STRENGTH) + minmax(-8,8,univ.party[pc].status[eStatus::BLESS_CURSE]); + dam_adj = univ.party[pc].stat_adj(eSkill::STRENGTH) + minmax(-8,8,univ.party[pc].status[eStatus::BLESS_CURSE]); if((skill_item = univ.party[pc].has_abil_equip(eItemAbil::SKILL)) < 24) { hit_adj += 5 * (univ.party[pc].items[skill_item].item_level / 2 + 1); dam_adj += univ.party[pc].items[skill_item].item_level / 2; diff --git a/src/boe.locutils.cpp b/src/boe.locutils.cpp index a23b0ad2..a74e7ea3 100644 --- a/src/boe.locutils.cpp +++ b/src/boe.locutils.cpp @@ -296,7 +296,7 @@ bool is_blocked(location to_check) { return true; // Monster there? - if(monst_there(to_check) < univ.town.monst.size()) + if(univ.target_there(to_check, TARG_MONST)) return true; // Magic barrier? @@ -311,26 +311,6 @@ bool is_blocked(location to_check) { return true; } -bool monst_on_space(location loc,short m_num) { - - if(univ.town.monst[m_num].active == 0) - return false; - if((loc.x - univ.town.monst[m_num].cur_loc.x >= 0) && - (loc.x - univ.town.monst[m_num].cur_loc.x <= univ.town.monst[m_num].x_width - 1) && - (loc.y - univ.town.monst[m_num].cur_loc.y >= 0) && - (loc.y - univ.town.monst[m_num].cur_loc.y <= univ.town.monst[m_num].y_width - 1)) - return true; - return false; - -} -size_t monst_there(location where) { // returns 90 if no - short i; - - for(i = 0; i < univ.town.monst.size(); i++) - if((univ.town.monst[i].active != 0) && (monst_on_space(where,i))) - return i; - return univ.town.monst.size(); -} bool monst_can_be_there(location loc,short m_num) { short i,j; location destination; @@ -579,8 +559,7 @@ location push_loc(location from_where,location to_where) { if(sight_obscurity(loc_to_try.x,loc_to_try.y) > 0 || univ.scenario.ter_types[univ.town->terrain(loc_to_try.x,loc_to_try.y)].blockage != eTerObstruct::CLEAR || (loc_off_act_area(loc_to_try)) || - (monst_there(loc_to_try) < 90) || - (pc_there(loc_to_try) < 6)) + univ.target_there(loc_to_try)) return from_where; else return loc_to_try; } diff --git a/src/boe.locutils.h b/src/boe.locutils.h index eb9cd722..f57c82cb 100644 --- a/src/boe.locutils.h +++ b/src/boe.locutils.h @@ -21,8 +21,6 @@ ter_num_t coord_to_ter(short x,short y); bool is_container(location loc); void update_explored(location dest); bool is_blocked(location to_check); -bool monst_on_space(location loc,short m_num); -size_t monst_there(location where) ; bool monst_can_be_there(location loc,short m_num); bool monst_adjacent(location loc,short m_num); bool monst_can_see(short m_num,location l); diff --git a/src/boe.monster.cpp b/src/boe.monster.cpp index 2fd64580..885342ff 100644 --- a/src/boe.monster.cpp +++ b/src/boe.monster.cpp @@ -732,7 +732,7 @@ location find_clear_spot(location from_where,short mode) { loc.y = loc.y + r1; if(!loc_off_act_area(loc) && !is_blocked(loc) && can_see_light(from_where,loc,combat_obscurity) == 0 - && (!(is_combat()) || (pc_there(loc) == 6)) + && (!is_combat() || univ.target_there(loc,TARG_PC) == nullptr) && (!(is_town()) || (loc != univ.town.p_loc)) && (!(univ.town.fields[loc.x][loc.y] & blocking_fields))) { if((mode == 0) || ((mode == 1) && (adjacent(from_where,loc)))) @@ -743,15 +743,6 @@ location find_clear_spot(location from_where,short mode) { return store_loc; } -short pc_there(location where) { - short i; - - for(i = 0; i < 6; i++) - if(where == univ.party[i].combat_pos && univ.party[i].main_status == eMainStatus::ALIVE) - return i; - return 6; -} - location random_shift(location start) { location store; @@ -819,19 +810,19 @@ void monst_inflict_fields(short which_monst) { // TODO: If the goal is to damage the monster by any fields it's on, why all the break statements? if(univ.town.is_quickfire(where_check.x,where_check.y)) { r1 = get_ran(2,1,8); - damage_monst(which_monst,7,r1,0,eDamageType::FIRE,0); + damage_monst(which_monst,7,r1,eDamageType::FIRE,0); break; } if(univ.town.is_blade_wall(where_check.x,where_check.y)) { r1 = get_ran(6,1,8); if(have_radiate && which_radiate != eFieldType::WALL_BLADES) - damage_monst(which_monst,7,r1,0,eDamageType::WEAPON,0); + damage_monst(which_monst,7,r1,eDamageType::WEAPON,0); break; } if(univ.town.is_force_wall(where_check.x,where_check.y)) { r1 = get_ran(3,1,6); if(have_radiate && which_radiate != eFieldType::WALL_FORCE) - damage_monst(which_monst,7,r1,0,eDamageType::MAGIC,0); + damage_monst(which_monst,7,r1,eDamageType::MAGIC,0); break; } if(univ.town.is_sleep_cloud(where_check.x,where_check.y)) { @@ -842,7 +833,7 @@ void monst_inflict_fields(short which_monst) { if(univ.town.is_ice_wall(where_check.x,where_check.y)) { r1 = get_ran(3,1,6); if(have_radiate && which_radiate != eFieldType::WALL_ICE) - damage_monst(which_monst,7,r1,0,eDamageType::COLD,0); + damage_monst(which_monst,7,r1,eDamageType::COLD,0); break; } if(univ.town.is_scloud(where_check.x,where_check.y)) { @@ -861,7 +852,7 @@ void monst_inflict_fields(short which_monst) { if(univ.town.is_fire_wall(where_check.x,where_check.y)) { r1 = get_ran(2,1,6); if(have_radiate && which_radiate != eFieldType::WALL_FIRE) - damage_monst(which_monst,7,r1,0,eDamageType::FIRE,0); + damage_monst(which_monst,7,r1,eDamageType::FIRE,0); break; } if(univ.town.is_force_cage(where_check.x,where_check.y)) @@ -883,7 +874,7 @@ void monst_inflict_fields(short which_monst) { univ.town.set_barrel(where_check.x,where_check.y,false); if(univ.town.is_fire_barr(where_check.x,where_check.y)) { r1 = get_ran(2,1,10); - damage_monst(which_monst,7,r1,0,eDamageType::FIRE,0); + damage_monst(which_monst,7,r1,eDamageType::FIRE,0); } } @@ -1273,19 +1264,18 @@ short place_monster(mon_num_t which,location where) { // to put monster bool summon_monster(mon_num_t which,location where,short duration,short given_attitude) { location loc; - short which_m,spot; + short spot; if(which <= 0) return false; if((is_town()) || (monsters_going)) { - which_m = monst_there(where); loc = find_clear_spot(where,0); if(loc.x == 0) return false; } else { // pc may be summoning using item, in which case where will be pc's space, so fix - if(pc_there(where) < 6) { + if(univ.target_there(where, TARG_PC)) { where = find_clear_spot(where,0); if(where.x == 0) return false; diff --git a/src/boe.monster.h b/src/boe.monster.h index 11305a69..eb70108d 100644 --- a/src/boe.monster.h +++ b/src/boe.monster.h @@ -25,7 +25,6 @@ bool flee_party(short i,location l1,location l2); bool try_move(short i,location start,short x,short y); bool combat_move_monster(short which,location destination); location find_clear_spot(location from_where,short mode); -short pc_there(location where); location random_shift(location start); bool outdoor_move_monster(short num,location dest); bool town_move_monster(short num,location dest); diff --git a/src/boe.party.cpp b/src/boe.party.cpp index c6d0abb4..c9365d44 100644 --- a/src/boe.party.cpp +++ b/src/boe.party.cpp @@ -344,7 +344,8 @@ void cPlayer::heal(int amt) { cur_health += amt; if(cur_health > max_health) cur_health = max_health; - + if(cur_health < 0) + cur_health = 0; } void cPlayer::cure(int amt) { @@ -518,6 +519,8 @@ void cPlayer::restore_sp(int amt) { cur_sp += amt; if(cur_sp > max_sp) cur_sp = max_sp; + if(cur_sp < 0) + cur_sp = 0; } void award_party_xp(short amt) { @@ -822,7 +825,7 @@ void do_mage_spell(short pc_num,eSpell spell_num,bool freebie) { current_spell_range = 8; store_mage = spell_num; - adj = stat_adj(who_cast,eSkill::INTELLIGENCE); + adj = univ.party[pc_num].stat_adj(eSkill::INTELLIGENCE); switch(spell_num) { case eSpell::LIGHT: @@ -860,7 +863,7 @@ void do_mage_spell(short pc_num,eSpell spell_num,bool freebie) { add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON_WEAK: - store = univ.party[who_cast].level / 5 + stat_adj(who_cast,eSkill::INTELLIGENCE) / 3 + get_ran(1,0,2); + store = univ.party[pc_num].level / 5 + adj / 3 + get_ran(1,0,2); j = minmax(1,7,store); r1 = get_summon_monster(1); //// if(r1 < 0) break; @@ -872,7 +875,7 @@ void do_mage_spell(short pc_num,eSpell spell_num,bool freebie) { add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON: - store = univ.party[who_cast].level / 7 + stat_adj(who_cast,eSkill::INTELLIGENCE) / 3 + get_ran(1,0,1); + store = univ.party[pc_num].level / 7 + adj / 3 + get_ran(1,0,1); j = minmax(1,6,store); r1 = get_summon_monster(2); //// if(r1 < 0) break; @@ -884,19 +887,19 @@ void do_mage_spell(short pc_num,eSpell spell_num,bool freebie) { add_string_to_buf(" Summon failed."); break; case eSpell::SUMMON_MAJOR: - store = univ.party[who_cast].level / 10 + stat_adj(who_cast,eSkill::INTELLIGENCE) / 3 + get_ran(1,0,1); + store = univ.party[pc_num].level / 10 + adj / 3 + get_ran(1,0,1); j = minmax(1,5,store); r1 = get_summon_monster(3); //// if(r1 < 0) break; if(!freebie) univ.party[pc_num].cur_sp -= (*spell_num).cost; - store = get_ran(7,1,4) + stat_adj(who_cast,eSkill::INTELLIGENCE); + store = get_ran(7,1,4) + adj; for(i = 0; i < j; i++) if(!summon_monster(r1,where,store,2)) add_string_to_buf(" Summon failed."); break; case eSpell::DEMON: - store = get_ran(5,1,4) + 2 * stat_adj(who_cast,eSkill::INTELLIGENCE); + store = get_ran(5,1,4) + 2 * adj; if(!summon_monster(85,where,store,2)) add_string_to_buf(" Summon failed."); else if(!freebie) @@ -975,15 +978,15 @@ void do_mage_spell(short pc_num,eSpell spell_num,bool freebie) { if(target < 6 && !freebie) univ.party[pc_num].cur_sp -= (*spell_num).cost; if(spell_num == eSpell::PROTECTION && target < 6) { - univ.party[target].status[eStatus::INVULNERABLE] += 2 + stat_adj(pc_num,eSkill::INTELLIGENCE) + get_ran(2,1,2); + univ.party[target].status[eStatus::INVULNERABLE] += 2 + adj + get_ran(2,1,2); for(i = 0; i < 6; i++) if(univ.party[i].main_status == eMainStatus::ALIVE) { - univ.party[i].status[eStatus::MAGIC_RESISTANCE] += 4 + univ.party[pc_num].level / 3 + stat_adj(pc_num,eSkill::INTELLIGENCE); + univ.party[i].status[eStatus::MAGIC_RESISTANCE] += 4 + univ.party[pc_num].level / 3 + adj; } add_string_to_buf(" Party protected."); } if(spell_num == eSpell::RESIST_MAGIC && target < 6) { - univ.party[target].status[eStatus::MAGIC_RESISTANCE] += 2 + stat_adj(pc_num,eSkill::INTELLIGENCE) + get_ran(2,1,2); + univ.party[target].status[eStatus::MAGIC_RESISTANCE] += 2 + adj + get_ran(2,1,2); add_string_to_buf(" " + univ.party[target].name + " protected."); } break; @@ -999,7 +1002,7 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { where = univ.town.p_loc; - adj = stat_adj(pc_num,eSkill::INTELLIGENCE); + adj = univ.party[pc_num].stat_adj(eSkill::INTELLIGENCE); play_sound(24); current_spell_range = 8; @@ -1028,7 +1031,7 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { case eSpell::MANNA_MINOR: case eSpell::MANNA: if(!freebie) univ.party[pc_num].cur_sp -= (*spell_num).cost; - store = univ.party[pc_num].level / 3 + 2 * stat_adj(who_cast,eSkill::INTELLIGENCE) + get_ran(2,1,4); + store = univ.party[pc_num].level / 3 + 2 * adj + get_ran(2,1,4); r1 = max(0,store); if(spell_num == eSpell::MANNA_MINOR) r1 = r1 / 3; @@ -1048,8 +1051,7 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { break; case eSpell::SUMMON_SPIRIT: - store = stat_adj(who_cast,eSkill::INTELLIGENCE); - if(!summon_monster(125,where,get_ran(2,1,4) + store,2)) + if(!summon_monster(125,where,get_ran(2,1,4) + adj,2)) add_string_to_buf(" Summon failed."); else if(!freebie) univ.party[pc_num].cur_sp -= (*spell_num).cost; @@ -1057,10 +1059,10 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { case eSpell::STICKS_TO_SNAKES: if(!freebie) univ.party[pc_num].cur_sp -= (*spell_num).cost; - r1 = univ.party[who_cast].level / 6 + stat_adj(who_cast,eSkill::INTELLIGENCE) / 3 + get_ran(1,0,1); + r1 = univ.party[pc_num].level / 6 + adj / 3 + get_ran(1,0,1); for(i = 0; i < r1; i++) { r2 = get_ran(1,0,7); - store = get_ran(2,1,5) + stat_adj(who_cast,eSkill::INTELLIGENCE); + store = get_ran(2,1,5) + adj; if(!summon_monster((r2 == 1) ? 100 : 99,where,store,2 )) add_string_to_buf(" Summon failed."); } @@ -1068,17 +1070,17 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { case eSpell::SUMMON_HOST: if(!freebie) univ.party[pc_num].cur_sp -= (*spell_num).cost; - store = get_ran(2,1,4) + stat_adj(who_cast,eSkill::INTELLIGENCE); + store = get_ran(2,1,4) + adj; if(!summon_monster(126,where,store,2)) add_string_to_buf(" Summon failed."); for(i = 0; i < 4; i++) { - store = get_ran(2,1,4) + stat_adj(who_cast,eSkill::INTELLIGENCE); + store = get_ran(2,1,4) + adj; if(!summon_monster(125,where,store,2)) add_string_to_buf(" Summon failed."); } break; case eSpell::SUMMON_GUARDIAN: - store = get_ran(6,1,4) + stat_adj(who_cast,eSkill::INTELLIGENCE); + store = get_ran(6,1,4) + adj; if(!summon_monster(122,where,store,2)) add_string_to_buf(" Summon failed."); else if(!freebie) @@ -1167,7 +1169,7 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { case eSpell::POISON_WEAKEN: case eSpell::POISON_CURE: sout << " cured."; - r1 = ((spell_num == eSpell::POISON_WEAKEN) ? 1 : 3) + get_ran(1,0,2) + stat_adj(pc_num,eSkill::INTELLIGENCE) / 2; + r1 = ((spell_num == eSpell::POISON_WEAKEN) ? 1 : 3) + get_ran(1,0,2) + adj / 2; univ.party[target].cure(r1); break; @@ -1190,13 +1192,13 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { case eSpell::DISEASE_CURE: sout << " recovers."; - r1 = 2 + get_ran(1,0,2) + stat_adj(pc_num,eSkill::INTELLIGENCE) / 2; + r1 = 2 + get_ran(1,0,2) + adj / 2; univ.party[target].status[eStatus::DISEASE] = max(0,univ.party[target].status[eStatus::DISEASE] - r1); break; case eSpell::RESTORE_MIND: sout << " restored."; - r1 = 1 + get_ran(1,0,2) + stat_adj(pc_num,eSkill::INTELLIGENCE) / 2; + r1 = 1 + get_ran(1,0,2) + adj / 2; univ.party[target].status[eStatus::DUMB] = max(0,univ.party[target].status[eStatus::DUMB] - r1); break; @@ -1266,7 +1268,7 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { } else if(spell_num == eSpell::CURSE_REMOVE) { for(i = 0; i < 24; i++) if(univ.party[target].items[i].cursed){ - r1 = get_ran(1,0,200) - 10 * stat_adj(pc_num,eSkill::INTELLIGENCE); + r1 = get_ran(1,0,200) - 10 * adj; if(r1 < 60) { univ.party[target].items[i].cursed = univ.party[target].items[i].unsellable = false; } @@ -1346,7 +1348,7 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { if(!freebie) univ.party[pc_num].cur_sp -= (*spell_num).cost; add_string_to_buf(" Party cured."); - univ.party.cure(3 + stat_adj(pc_num,eSkill::INTELLIGENCE)); + univ.party.cure(3 + adj); break; case eSpell::SANCTUARY_MASS: case eSpell::CLEANSE_MAJOR: case eSpell::HYPERACTIVITY: @@ -1361,7 +1363,7 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { for(i = 0; i < 6; i++) if(univ.party[i].main_status == eMainStatus::ALIVE) { if(spell_num == eSpell::SANCTUARY_MASS) { - store = get_ran(0,1,3) + univ.party[pc_num].level / 6 + stat_adj(pc_num,eSkill::INTELLIGENCE); + store = get_ran(0,1,3) + univ.party[pc_num].level / 6 + adj; r1 = max(0,store); univ.party[i].status[eStatus::INVISIBLE] += r1; } @@ -1372,7 +1374,7 @@ void do_priest_spell(short pc_num,eSpell spell_num,bool freebie) { if(spell_num == eSpell::HYPERACTIVITY) { // Looks like this isn't clipped to a positive number. (That's probably intentional.) // TODO: So, should a status icon be added for negative levels of sleep? - univ.party[i].status[eStatus::ASLEEP] -= 6 + 2 * stat_adj(pc_num,eSkill::INTELLIGENCE); + univ.party[i].status[eStatus::ASLEEP] -= 6 + 2 * adj; univ.party[i].status[eStatus::HASTE_SLOW] = max(0,univ.party[i].status[eStatus::HASTE_SLOW]); } } @@ -1390,7 +1392,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; + short adjust,r1,store; location loc; ter_num_t ter; @@ -1406,6 +1408,7 @@ void cast_town_spell(location where) { if(!spell_freebie) univ.party[who_cast].cur_sp -= (*town_spell).cost; ter = univ.town->terrain(where.x,where.y); + short adj = univ.party[who_cast].stat_adj(eSkill::INTELLIGENCE); // 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.) @@ -1416,18 +1419,18 @@ void cast_town_spell(location where) { 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); + run_special(eSpecCtx::TARGET, 2, spell_caster, where, &r1, &adjust, &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.monst.size()) { + if(iLiving* targ = univ.target_there(where, TARG_MONST)) { + cCreature* monst = dynamic_cast(targ); if(town_spell == eSpell::SCRY_MONSTER) { - univ.party.m_noted[univ.town.monst[targ].number] = true; + univ.party.m_noted[monst->number] = true; adjust_monst_menu(); - display_monst(0,&univ.town.monst[targ],0); + display_monst(0,monst,0); } - else record_monst(&univ.town.monst[targ]); + else record_monst(monst); } else add_string_to_buf(" No monster there."); break; @@ -1443,7 +1446,7 @@ void cast_town_spell(location where) { update_explored(univ.town.p_loc); break; case eSpell::BARRIER_FIRE: - if(sight_obscurity(where.x,where.y) == 5 || monst_there(where) < 90) { + if(sight_obscurity(where.x,where.y) == 5 || univ.target_there(where, TARG_MONST)) { add_string_to_buf(" Target space obstructed."); break; } @@ -1453,7 +1456,7 @@ void cast_town_spell(location where) { else add_string_to_buf(" Failed."); break; case eSpell::BARRIER_FORCE: - if(sight_obscurity(where.x,where.y) == 5 || monst_there(where) < 90) { + if(sight_obscurity(where.x,where.y) == 5 || univ.target_there(where, TARG_MONST)) { add_string_to_buf(" Target space obstructed."); break; } @@ -1488,7 +1491,7 @@ void cast_town_spell(location where) { if(univ.scenario.ter_types[ter].flag2.u == 10) r1 = 10000; else{ - r1 = get_ran(1,1,100) - 5 * stat_adj(who_cast,eSkill::INTELLIGENCE) + 5 * univ.town.difficulty; + r1 = get_ran(1,1,100) - 5 * adj + 5 * univ.town.difficulty; r1 += univ.scenario.ter_types[ter].flag2.u * 7; } if(r1 < (135 - combat_percent[min(19,univ.party[who_cast].level)])) { @@ -1506,7 +1509,7 @@ void cast_town_spell(location where) { case eSpell::DISPEL_BARRIER: if((univ.town.is_fire_barr(where.x,where.y)) || (univ.town.is_force_barr(where.x,where.y))) { - r1 = get_ran(1,1,100) - 5 * stat_adj(who_cast,eSkill::INTELLIGENCE) + 5 * (univ.town.difficulty / 10); + r1 = get_ran(1,1,100) - 5 * adj + 5 * (univ.town.difficulty / 10); if(univ.town.is_fire_barr(where.x,where.y)) r1 -= 8; if(r1 < (120 - combat_percent[min(19,univ.party[who_cast].level)])) { @@ -2212,22 +2215,20 @@ void print_spell_cast(eSpell spell,eSkill which) { add_string_to_buf("Spell: " + name); } -short stat_adj(short pc_num,eSkill which) { - short tr; - +short cPlayer::stat_adj(eSkill which) const { // This is one place where we use the base skill level instead of the adjusted skill level // Using the adjusted skill level here would alter the original mechanics of stat-boosting items - tr = skill_bonus[univ.party[pc_num].skills[which]]; + short tr = skill_bonus[skills[which]]; if(which == eSkill::INTELLIGENCE) { - if(univ.party[pc_num].traits[eTrait::MAGICALLY_APT]) + if(traits[eTrait::MAGICALLY_APT]) tr++; } if(which == eSkill::STRENGTH) { - if(univ.party[pc_num].traits[eTrait::STRENGTH]) + if(traits[eTrait::STRENGTH]) tr++; } // TODO: Use ability strength? - if(univ.party[pc_num].has_abil_equip(eItemAbil::BOOST_STAT,int(which)) < 24) + if(has_abil_equip(eItemAbil::BOOST_STAT,int(which)) < 24) tr++; return tr; } @@ -2480,10 +2481,11 @@ void cPlayer::poison(int how_much) { put_pc_screen(); } -void void_sanctuary(short pc_num) { - if(univ.party[pc_num].status[eStatus::INVISIBLE] > 0) { - add_string_to_buf("You become visible!"); - univ.party[pc_num].status[eStatus::INVISIBLE] = 0; +void iLiving::void_sanctuary() { + if(status[eStatus::INVISIBLE] > 0) { + if(dynamic_cast(this)) + add_string_to_buf("You become visible!"); + status[eStatus::INVISIBLE] = 0; } } diff --git a/src/boe.party.h b/src/boe.party.h index 2b056ee3..bc154d0c 100644 --- a/src/boe.party.h +++ b/src/boe.party.h @@ -28,7 +28,6 @@ void dispel_fields(short i,short j,short mode); 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,eSpellPat pat = PAT_SINGLE); void do_alchemy(); eAlchemy alch_choice(short pc_num); @@ -36,7 +35,6 @@ bool pick_pc_graphic(short pc_num,short mode,cDialog* parent_num); bool pick_pc_name(short pc_num,cDialog* parent) ; mon_num_t pick_trapped_monst(); bool flying() ; -void void_sanctuary(short pc_num); void hit_party(short how_much,eDamageType damage_type,short snd_type = 0); void slay_party(eMainStatus mode); bool damage_pc(short which_pc,short how_much,eDamageType damage_type,eRace type_of_attacker, short sound_type,bool do_print = true); diff --git a/src/boe.specials.cpp b/src/boe.specials.cpp index 679928b4..33a91564 100644 --- a/src/boe.specials.cpp +++ b/src/boe.specials.cpp @@ -60,11 +60,12 @@ std::map boom_gr = { {eDamageType::COLD, 4}, {eDamageType::UNDEAD, 3}, {eDamageType::DEMON, 3}, + {eDamageType::SPECIAL, 1}, }; short store_item_spell_level = 10; // global values for when processing special encounters -short current_pc_picked_in_spec_enc = -1; // pc that's been selected, -1 if none +iLiving* current_pc_picked_in_spec_enc = nullptr; extern std::map skill_max; location store_special_loc; bool special_in_progress = false; @@ -126,7 +127,7 @@ bool handle_wandering_specials (short /*which*/,short mode) { // returns true if can enter this space // sets forced to true if definitely can enter -bool check_special_terrain(location where_check,eSpecCtx mode,short which_pc,short *spec_num, +bool check_special_terrain(location where_check,eSpecCtx mode,cPlayer& which_pc,short *spec_num, bool *forced) { ter_num_t ter; short r1,i,door_pc,pic_type = 0,ter_pic = 0; @@ -359,7 +360,7 @@ bool check_special_terrain(location where_check,eSpecCtx mode,short which_pc,sho hit_party(r1,dam_type); fast_bang = 1; if(mode == eSpecCtx::COMBAT_MOVE) - damage_pc(which_pc,r1,dam_type,eRace::UNKNOWN,0); + damage_pc(univ.get_target_i(which_pc),r1,dam_type,eRace::UNKNOWN,0); else boom_space(univ.party.p_loc,overall_mode,pic_type,r1,12); fast_bang = 0; @@ -373,7 +374,7 @@ bool check_special_terrain(location where_check,eSpecCtx mode,short which_pc,sho if(mode == eSpecCtx::OUT_MOVE && out_boat_there(where_check) < 30) break; //one_sound(17); - if(mode == eSpecCtx::COMBAT_MOVE) i = which_pc; else i = 0; + if(mode == eSpecCtx::COMBAT_MOVE) i = univ.get_target_i(which_pc); else i = 0; for( ; i < 6; i++) if(univ.party[i].main_status == eMainStatus::ALIVE) { if(get_ran(1,1,100) <= ter_flag2.u) { @@ -477,8 +478,9 @@ bool check_special_terrain(location where_check,eSpecCtx mode,short which_pc,sho // This procedure find the effects of fields that would affect a PC who moves into // a space or waited in that same space -void check_fields(location where_check,eSpecCtx mode,short which_pc) { +void check_fields(location where_check,eSpecCtx mode,cPlayer& which_pc) { short r1,i; + size_t i_pc = univ.get_target_i(which_pc); if(mode != eSpecCtx::COMBAT_MOVE && mode != eSpecCtx::TOWN_MOVE && mode != eSpecCtx::OUT_MOVE) { std::cout << "Note: Improper mode passed to check_special_terrain: " << int(mode) << std::endl; @@ -495,7 +497,7 @@ void check_fields(location where_check,eSpecCtx mode,short which_pc) { // if(mode < 2) // hit_party(r1,1); if(mode == eSpecCtx::COMBAT_MOVE) - damage_pc(which_pc,r1,eDamageType::FIRE,eRace::UNKNOWN,0); + damage_pc(i_pc,r1,eDamageType::FIRE,eRace::UNKNOWN,0); if(overall_mode < MODE_COMBAT) boom_space(univ.party.p_loc,overall_mode,0,r1,5); } @@ -505,7 +507,7 @@ void check_fields(location where_check,eSpecCtx mode,short which_pc) { // if(mode < 2) // hit_party(r1,3); if(mode == eSpecCtx::COMBAT_MOVE) - damage_pc(which_pc,r1,eDamageType::MAGIC,eRace::UNKNOWN,0); + damage_pc(i_pc,r1,eDamageType::MAGIC,eRace::UNKNOWN,0); if(overall_mode < MODE_COMBAT) boom_space(univ.party.p_loc,overall_mode,1,r1,12); } @@ -515,7 +517,7 @@ void check_fields(location where_check,eSpecCtx mode,short which_pc) { // if(mode < 2) // hit_party(r1,5); if(mode == eSpecCtx::COMBAT_MOVE) - damage_pc(which_pc,r1,eDamageType::COLD,eRace::UNKNOWN,0); + damage_pc(i_pc,r1,eDamageType::COLD,eRace::UNKNOWN,0); if(overall_mode < MODE_COMBAT) boom_space(univ.party.p_loc,overall_mode,4,r1,7); } @@ -525,7 +527,7 @@ void check_fields(location where_check,eSpecCtx mode,short which_pc) { // if(mode < 2) // hit_party(r1,0); if(mode == eSpecCtx::COMBAT_MOVE) - damage_pc(which_pc,r1,eDamageType::WEAPON,eRace::UNKNOWN,0); + damage_pc(i_pc,r1,eDamageType::WEAPON,eRace::UNKNOWN,0); if(overall_mode < MODE_COMBAT) boom_space(univ.party.p_loc,overall_mode,3,r1,2); } @@ -535,7 +537,7 @@ void check_fields(location where_check,eSpecCtx mode,short which_pc) { // if(mode < 2) // hit_party(r1,1); if(mode == eSpecCtx::COMBAT_MOVE) - damage_pc(which_pc,r1,eDamageType::FIRE,eRace::UNKNOWN,0); + damage_pc(i_pc,r1,eDamageType::FIRE,eRace::UNKNOWN,0); if(overall_mode < MODE_COMBAT) boom_space(univ.party.p_loc,overall_mode,0,r1,5); } @@ -564,15 +566,15 @@ void check_fields(location where_check,eSpecCtx mode,short which_pc) { if(mode != eSpecCtx::COMBAT_MOVE) hit_party(r1,eDamageType::MAGIC); if(mode == eSpecCtx::COMBAT_MOVE) - damage_pc(which_pc,r1,eDamageType::MAGIC,eRace::UNKNOWN,0); + damage_pc(i_pc,r1,eDamageType::MAGIC,eRace::UNKNOWN,0); if(overall_mode < MODE_COMBAT) boom_space(univ.party.p_loc,overall_mode,1,r1,12); } if(univ.town.is_force_cage(where_check.x,where_check.y)) { - if(univ.party[which_pc].status[eStatus::FORCECAGE] == 0) + if(which_pc.status[eStatus::FORCECAGE] == 0) add_string_to_buf(" Trapped in force cage!"); - univ.party[which_pc].status[eStatus::FORCECAGE] = 8; - } else univ.party[which_pc].status[eStatus::FORCECAGE] = 0; + which_pc.status[eStatus::FORCECAGE] = 8; + } else which_pc.status[eStatus::FORCECAGE] = 0; fast_bang = 0; } @@ -1386,7 +1388,7 @@ void change_level(short town_num,short x,short y) { // Damaging and killing monsters needs to be here because several have specials attached to them. -bool damage_monst(short which_m, short who_hit, short how_much, short how_much_spec, eDamageType dam_type, short sound_type, bool do_print) { +bool damage_monst(short which_m, short who_hit, short how_much, eDamageType dam_type, short sound_type, bool do_print) { cCreature *victim; short r1,which_spot; location where_put; @@ -1442,7 +1444,7 @@ bool damage_monst(short which_m, short who_hit, short how_much, short how_much_s how_much /= 2; // Invulnerable? - if(victim->invuln) + if(dam_type != eDamageType::SPECIAL && victim->invuln) how_much = how_much / 10; @@ -1480,8 +1482,8 @@ bool damage_monst(short which_m, short who_hit, short how_much, short how_much_s } if(do_print) - monst_damaged_mes(which_m,how_much,how_much_spec); - victim->health = victim->health - how_much - how_much_spec; + victim->damaged_msg(how_much,0); + victim->health = victim->health - how_much; if(in_scen_debug) victim->health = -1; @@ -1497,7 +1499,7 @@ bool damage_monst(short which_m, short who_hit, short how_much, short how_much_s } } if(who_hit < 7) - univ.party.total_dam_done += how_much + how_much_spec; + univ.party.total_dam_done += how_much; // Monster damages. Make it hostile. victim->active = 2; @@ -1507,18 +1509,14 @@ bool damage_monst(short which_m, short who_hit, short how_much, short how_much_s if(dam_type != eDamageType::MARKED) { // note special damage only gamed in hand-to-hand, not during animation if(party_can_see_monst(which_m)) { boom_space(victim->cur_loc,100,boom_gr[dam_type],how_much,sound_type); - if(how_much_spec > 0) - boom_space(victim->cur_loc,100,51,how_much_spec,5); } else { boom_space(victim->cur_loc,overall_mode, boom_gr[dam_type],how_much,sound_type); - if(how_much_spec > 0) - boom_space(victim->cur_loc,overall_mode,51,how_much_spec,5); } } if(victim->health < 0) { - monst_killed_mes(which_m); + victim->killed_msg(); kill_monst(victim,who_hit); } else { @@ -1891,7 +1889,7 @@ void run_special(eSpecCtx which_mode,short which_type,short start_spec,location special_in_progress = true; next_spec = start_spec; next_spec_type = which_type; - current_pc_picked_in_spec_enc = -1; + current_pc_picked_in_spec_enc = nullptr; switch(which_mode) { case eSpecCtx::OUT_MOVE: case eSpecCtx::TOWN_MOVE: case eSpecCtx::COMBAT_MOVE: case eSpecCtx::OUT_LOOK: case eSpecCtx::TOWN_LOOK: case eSpecCtx::ENTER_TOWN: case eSpecCtx::LEAVE_TOWN: @@ -1899,31 +1897,26 @@ void run_special(eSpecCtx which_mode,short which_type,short start_spec,location case eSpecCtx::TOWN_TIMER: case eSpecCtx::SCEN_TIMER: case eSpecCtx::PARTY_TIMER: case eSpecCtx::OUTDOOR_ENC: case eSpecCtx::FLEE_ENCOUNTER: case eSpecCtx::WIN_ENCOUNTER: // Default behaviour - select entire party, or active member if split or in combat - if(is_combat()) current_pc_picked_in_spec_enc = current_pc; + if(is_combat()) current_pc_picked_in_spec_enc = &univ.party[current_pc]; else { if(univ.party.is_split() && cur_node.type != eSpecType::AFFECT_DEADNESS) - current_pc_picked_in_spec_enc = univ.party.pc_present(); - if(current_pc_picked_in_spec_enc == 6 && univ.party.pc_present(current_pc_picked_in_spec_enc)) - current_pc_picked_in_spec_enc = current_pc_picked_in_spec_enc; - if(current_pc_picked_in_spec_enc == 6) - current_pc_picked_in_spec_enc = -1; + current_pc_picked_in_spec_enc = &univ.party.pc_present(); + else current_pc_picked_in_spec_enc = &univ.party; } break; case eSpecCtx::KILL_MONST: case eSpecCtx::SEE_MONST: case eSpecCtx::MONST_SPEC_ABIL: case eSpecCtx::ATTACKED_MELEE: case eSpecCtx::ATTACKING_MELEE: case eSpecCtx::ATTACKED_RANGE: case eSpecCtx::ATTACKING_RANGE: // The monster/PC on the trigger space is the target - current_pc_picked_in_spec_enc = 100 + monst_there(spec_loc); - if(current_pc_picked_in_spec_enc - 100 >= univ.town.monst.size()) - current_pc_picked_in_spec_enc = pc_there(spec_loc); - if(current_pc_picked_in_spec_enc == 6) - current_pc_picked_in_spec_enc = -1; + current_pc_picked_in_spec_enc = univ.target_there(spec_loc); + if(!current_pc_picked_in_spec_enc) + current_pc_picked_in_spec_enc = &univ.party; break; case eSpecCtx::TARGET: case eSpecCtx::USE_SPACE: // If there's a monster on the space, select that as the target - mon_num_t who = monst_there(spec_loc); - if(who < univ.town.monst.size()) - current_pc_picked_in_spec_enc = 100 + who; + current_pc_picked_in_spec_enc = univ.target_there(spec_loc, TARG_MONST); + if(!current_pc_picked_in_spec_enc) + current_pc_picked_in_spec_enc = &univ.party; break; } store_special_loc = spec_loc; @@ -2336,8 +2329,8 @@ void general_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, break; case eSpecType::APPEND_MONST: if(spec.ex1a == 0) { - int pc = current_pc_picked_in_spec_enc; - if(pc < 0) + int pc = univ.get_target_i(*current_pc_picked_in_spec_enc); + if(pc == 6) univ.scenario.get_buf() += "Your party"; else if(pc < 100) univ.scenario.get_buf() += univ.party[pc].name; @@ -2359,7 +2352,7 @@ void general_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, sf::sleep(sf::milliseconds(spec.ex1a)); break; case eSpecType::START_TALK: - i = current_pc_picked_in_spec_enc; + i = univ.get_target_i(*current_pc_picked_in_spec_enc); if(i >= 100) i -= 100; else i = -1; start_talk_mode(i, spec.ex1a, spec.ex1b, spec.pic); @@ -2563,7 +2556,9 @@ void oneshot_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, short *next_spec,short* /*next_spec_type*/,short *a,short *b,short *redraw) { bool check_mess = true; - short i,pc = current_pc_picked_in_spec_enc,r1; + short i,r1; + iLiving* pc = current_pc_picked_in_spec_enc; + short pc_num = univ.get_target_i(*current_pc_picked_in_spec_enc); std::string str; cSpecial spec; @@ -2579,57 +2574,55 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, if(spec.ex2a <= 0) { if(spec.ex1a == 2) - current_pc_picked_in_spec_enc = -1; + current_pc_picked_in_spec_enc = &univ.party; else if(spec.ex1a == 1) { i = select_pc(0); if(i != 6) - current_pc_picked_in_spec_enc = i; + current_pc_picked_in_spec_enc = &univ.party[i]; } else if(spec.ex1a == 0) { i = select_pc(1); if(i != 6) - current_pc_picked_in_spec_enc = i; + current_pc_picked_in_spec_enc = &univ.party[i]; } else if(spec.ex1a == 3) { i = select_pc(2); if(i != 6) - current_pc_picked_in_spec_enc = i; + current_pc_picked_in_spec_enc = &univ.party[i]; } else if(spec.ex1a == 4) { i = select_pc(3); if(i != 6) - current_pc_picked_in_spec_enc = i; + current_pc_picked_in_spec_enc = &univ.party[i]; } if(i == 6)// && (spec.ex1b >= 0)) *next_spec = spec.ex1b; } - else if(spec.ex2a > 10 || spec.ex2a <= 16) { + else if(spec.ex2a == 2) { // Select a specific PC - short pc = spec.ex2a - 11; - // Honour the request for alive PCs only. + short pc = spec.ex2b; bool can_pick = true; - if(univ.party[pc].main_status == eMainStatus::ABSENT) - can_pick = false; - else if(spec.ex1a % 4 == 0 && univ.party[pc].main_status != eMainStatus::ALIVE) - can_pick = false; - else if(spec.ex1a == 3 && univ.party[pc].main_status == eMainStatus::ALIVE) - can_pick = false; - else if(spec.ex1a == 4 && univ.party[pc].has_space() == 24) - can_pick = false; + if(pc >= 0 && pc < 6) { + // Honour the request for alive PCs only. + if(univ.party[pc].main_status == eMainStatus::ABSENT) + can_pick = false; + else if(spec.ex1a % 4 == 0 && univ.party[pc].main_status != eMainStatus::ALIVE) + can_pick = false; + else if(spec.ex1a == 3 && univ.party[pc].main_status == eMainStatus::ALIVE) + can_pick = false; + else if(spec.ex1a == 4 && univ.party[pc].has_space() == 24) + can_pick = false; + } else if(pc >= 100 && pc < univ.town.monst.size() + 100) { + short monst = pc - 100; + // Honour the request for alive only + if(spec.ex1a == 0 && univ.town.monst[monst].active == 0) + can_pick = false; + else if(spec.ex1a == 3 && univ.town.monst[monst].active > 0) + can_pick = false; + } else can_pick = false; // Because it's an invalid index if(can_pick) - current_pc_picked_in_spec_enc = pc; - else *next_spec = spec.ex1b; - } else if(spec.ex2a >= 100) { - short monst = spec.ex2a - 100; - // Honour the request for alive only - bool can_pick = true; - if(spec.ex1a == 0 && univ.town.monst[monst].active == 0) - can_pick = false; - else if(spec.ex1a == 3 && univ.town.monst[monst].active > 0) - can_pick = false; - if(can_pick) - current_pc_picked_in_spec_enc = spec.ex2a; + current_pc_picked_in_spec_enc = &univ.get_target(pc); else *next_spec = spec.ex1b; } else if(spec.ex2a == 1) { // Pick random PC (from *i) @@ -2651,12 +2644,12 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, tries++; } if(can_pick) - current_pc_picked_in_spec_enc = i; + current_pc_picked_in_spec_enc = &univ.party[i]; else *next_spec = spec.ex1b; } else { i = get_ran(1,0,5); - current_pc_picked_in_spec_enc = i; + current_pc_picked_in_spec_enc = &univ.party[i]; } } break; @@ -2664,59 +2657,49 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, r1 = get_ran(spec.ex1a,1,spec.ex1b) + spec.ex2a; eDamageType dam_type = (eDamageType) spec.ex2b; int snd_type = spec.ex2c < 0 ? 0 : -spec.ex2c; - if(pc < 0) hit_party(r1, dam_type, snd_type); - else if(pc >= 100) damage_monst(pc - 100, 7, r1, 0, dam_type, snd_type); - else damage_pc(pc,r1,dam_type,eRace::UNKNOWN, snd_type); + if(pc_num == 6) hit_party(r1, dam_type, snd_type); + else if(pc_num >= 100) damage_monst(pc_num - 100, 7, r1, dam_type, snd_type); + else damage_pc(pc_num,r1,dam_type,eRace::UNKNOWN, snd_type); break; } case eSpecType::AFFECT_HP: - if(pc < 100) { - for(i = 0; i < 6; i++) - if((pc < 0) || (pc == i)) - univ.party[i].cur_health = minmax(0,univ.party[i].max_health, - univ.party[i].cur_health + spec.ex1a * (spec.ex1b ? -1: 1)); - } - else { - cCreature& who = univ.town.monst[pc - 100]; - who.health = minmax(0, who.m_health, who.health + spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); + if(spec.ex1b == 0) + pc->heal(spec.ex1a); + else pc->heal(-spec.ex1a); + if(cCreature* who = dynamic_cast(pc)) { if(spec.ex1b == 0) - who.spell_note(41); - else who.spell_note(42); + who->spell_note(41); + else who->spell_note(42); } break; case eSpecType::AFFECT_SP: - if(pc < 100) { - for(i = 0; i < 6; i++) - if((pc < 0) || (pc == i)) - univ.party[i].cur_sp = minmax(0, univ.party[i].max_sp, - univ.party[i].cur_sp + spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); - } - else { - cCreature& who = univ.town.monst[pc - 100]; - who.mp = minmax(0, who.max_mp, who.mp + spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); + if(spec.ex1b == 0) + pc->restore_sp(spec.ex1a); + else pc->restore_sp(-spec.ex1a); + if(cCreature* who = dynamic_cast(pc)) { if(spec.ex1b == 0) - who.spell_note(43); - else who.spell_note(44); + who->spell_note(43); + else who->spell_note(44); } break; case eSpecType::AFFECT_XP: - if(pc >= 100) break; + if(pc_num >= 100) break; for(i = 0; i < 6; i++) - if((pc < 0) || (pc == i)) { + if(pc_num == 6 || pc_num == i) { if(spec.ex1b == 0) award_xp(i,spec.ex1a); else drain_pc(i,spec.ex1a); } break; case eSpecType::AFFECT_SKILL_PTS: - if(pc >= 100) break; + if(pc_num >= 100) break; for(i = 0; i < 6; i++) - if((pc < 0) || (pc == i)) + if(pc_num == 6 || pc_num == i) univ.party[i].skill_pts = minmax(0, 100, univ.party[i].skill_pts + spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); break; case eSpecType::AFFECT_DEADNESS: - if(pc < 100) { + if(pc_num < 100) { for(i = 0; i < 6; i++) - if((pc < 0) || (pc == i)) { + if(pc_num == 6 || pc_num == i) { if(spec.ex1b == 0) { if(spec.ex1a == 3 && is_combat() && which_combat_type == 0 && univ.party[i].main_status == eMainStatus::FLED) univ.party[i].main_status = eMainStatus::ALIVE; @@ -2748,7 +2731,7 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, univ.party[i].main_status = eMainStatus::FLED; break; case 4: - if(i != univ.party.pc_present()) + if(party_size(true) > 1) univ.party[i].main_status += eMainStatus::SPLIT; break; case 5: @@ -2759,7 +2742,7 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, *redraw = 1; } else { // Kill monster - cCreature& who = univ.town.monst[pc - 100]; + cCreature& who = univ.town.monst[pc_num - 100]; if(who.active > 0 && spec.ex1b > 0) { switch(spec.ex1a) { case 0: @@ -2791,163 +2774,96 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, } break; case eSpecType::AFFECT_STATUS: - if(pc < 100) { - for(i = 0; i < 6; i++) - if(pc < 0 || pc == i) { - switch(eStatus(spec.ex2a)) { - case eStatus::POISON: - if(spec.ex1b == 0) - univ.party[i].cure(spec.ex1a); - else univ.party[i].poison(spec.ex1a); - break; - case eStatus::HASTE_SLOW: - if(spec.ex1b == 0) - univ.party[i].slow(-spec.ex1a); - else univ.party[i].slow(spec.ex1a); - break; - case eStatus::INVULNERABLE: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::INVULNERABLE, spec.ex1a); - else univ.party[i].apply_status(eStatus::INVULNERABLE, -spec.ex1a); - break; - case eStatus::MAGIC_RESISTANCE: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::MAGIC_RESISTANCE, spec.ex1a); - else univ.party[i].apply_status(eStatus::MAGIC_RESISTANCE, -spec.ex1a); - break; - case eStatus::WEBS: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::WEBS, -spec.ex1a); - else univ.party[i].web(spec.ex1a); - break; - case eStatus::DISEASE: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::DISEASE, -spec.ex1a); - else univ.party[i].disease(spec.ex1a); - break; - case eStatus::INVISIBLE: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::INVISIBLE, spec.ex1a); - else univ.party[i].apply_status(eStatus::INVISIBLE, -spec.ex1a); - break; - case eStatus::BLESS_CURSE: - if(spec.ex1b == 0) - univ.party[i].curse(-spec.ex1a); - else univ.party[i].curse(spec.ex1a); - break; - case eStatus::DUMB: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::DUMB, -spec.ex1a); - else univ.party[i].dumbfound(spec.ex1a); - break; - case eStatus::ASLEEP: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::ASLEEP, -spec.ex1a); - else univ.party[i].sleep(eStatus::ASLEEP, spec.ex1a, 10); - break; - case eStatus::PARALYZED: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::PARALYZED, -spec.ex1a); - else univ.party[i].sleep(eStatus::PARALYZED, spec.ex1a, 10); - break; - case eStatus::POISONED_WEAPON: - if(spec.ex1b == 0) - poison_weapon(i, spec.ex1a, true); - else univ.party[i].apply_status(eStatus::POISONED_WEAPON, -spec.ex1a); - break; - case eStatus::MARTYRS_SHIELD: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::MARTYRS_SHIELD, spec.ex1a); - else univ.party[i].apply_status(eStatus::MARTYRS_SHIELD, -spec.ex1a); - break; - case eStatus::ACID: - if(spec.ex1b == 0) - univ.party[i].apply_status(eStatus::ACID, -spec.ex1a); - else univ.party[i].acid(spec.ex1a); - break; - // Invalid values - case eStatus::MAIN: - case eStatus::CHARM: - case eStatus::FORCECAGE: - break; + switch(eStatus(spec.ex2a)) { + case eStatus::POISON: + if(spec.ex1b == 0) + pc->cure(spec.ex1a); + else pc->poison(spec.ex1a); + break; + case eStatus::HASTE_SLOW: + if(spec.ex1b == 0) + pc->slow(-spec.ex1a); + else pc->slow(spec.ex1a); + break; + case eStatus::INVULNERABLE: + if(spec.ex1b == 0) + pc->apply_status(eStatus::INVULNERABLE, spec.ex1a); + else pc->apply_status(eStatus::INVULNERABLE, -spec.ex1a); + break; + case eStatus::MAGIC_RESISTANCE: + if(spec.ex1b == 0) + pc->apply_status(eStatus::MAGIC_RESISTANCE, spec.ex1a); + else pc->apply_status(eStatus::MAGIC_RESISTANCE, -spec.ex1a); + break; + case eStatus::WEBS: + if(spec.ex1b == 0) + pc->apply_status(eStatus::WEBS, -spec.ex1a); + else pc->web(spec.ex1a); + break; + case eStatus::DISEASE: + if(spec.ex1b == 0) + pc->apply_status(eStatus::DISEASE, -spec.ex1a); + else pc->disease(spec.ex1a); + break; + case eStatus::INVISIBLE: + if(spec.ex1b == 0) + pc->apply_status(eStatus::INVISIBLE, spec.ex1a); + else pc->apply_status(eStatus::INVISIBLE, -spec.ex1a); + break; + case eStatus::BLESS_CURSE: + if(spec.ex1b == 0) + pc->curse(-spec.ex1a); + else pc->curse(spec.ex1a); + break; + case eStatus::DUMB: + if(spec.ex1b == 0) + pc->apply_status(eStatus::DUMB, -spec.ex1a); + else pc->dumbfound(spec.ex1a); + break; + case eStatus::ASLEEP: + if(spec.ex1b == 0) + pc->apply_status(eStatus::ASLEEP, -spec.ex1a); + else pc->sleep(eStatus::ASLEEP, spec.ex1a, 10); + break; + case eStatus::PARALYZED: + if(spec.ex1b == 0) + pc->apply_status(eStatus::PARALYZED, -spec.ex1a); + else pc->sleep(eStatus::PARALYZED, spec.ex1a, 10); + break; + case eStatus::POISONED_WEAPON: + if(pc_num >= 100) break; + for(i = 0; i < 6; i++) + if(pc_num == 6 || pc_num == i) { + if(spec.ex1b == 0) + poison_weapon(i, spec.ex1a, true); + else univ.party[i].apply_status(eStatus::POISONED_WEAPON, -spec.ex1a); } - } - } - else { - cCreature& who = univ.town.monst[pc - 100]; - if(who.active > 0) { - switch(eStatus(spec.ex2a)) { - case eStatus::POISON: - if(spec.ex1b == 0) - who.poison(-spec.ex1a); - else who.poison(spec.ex1a); - break; - case eStatus::HASTE_SLOW: - if(spec.ex1b == 0) - who.slow(-spec.ex1a); - else who.slow(spec.ex1a); - break; - case eStatus::WEBS: - if(spec.ex1b == 0) - who.web(-spec.ex1a); - else who.web(spec.ex1a); - break; - case eStatus::DISEASE: - if(spec.ex1b == 0) - who.disease(-spec.ex1a); - else who.disease(spec.ex1a); - break; - case eStatus::BLESS_CURSE: - if(spec.ex1b == 0) - who.curse(-spec.ex1a); - else who.curse(spec.ex1a); - break; - case eStatus::DUMB: - if(spec.ex1b == 0) - who.dumbfound(-spec.ex1a); - else who.dumbfound(spec.ex1a); - break; - case eStatus::ASLEEP: - if(spec.ex1b == 0) - who.sleep(eStatus::ASLEEP, -spec.ex1a, 0); - else who.sleep(eStatus::ASLEEP, spec.ex1a, 0); - break; - case eStatus::PARALYZED: - if(spec.ex1b == 0) - who.sleep(eStatus::PARALYZED, -spec.ex1a, 0); - else who.sleep(eStatus::PARALYZED, spec.ex1a, 0); - break; - case eStatus::MARTYRS_SHIELD: - if(spec.ex1b == 0) - who.status[eStatus::MARTYRS_SHIELD] = min(10, who.status[eStatus::MARTYRS_SHIELD] + spec.ex1a); - else who.status[eStatus::MARTYRS_SHIELD] = max(0, who.status[eStatus::MARTYRS_SHIELD] - spec.ex1a); - break; - case eStatus::ACID: - if(spec.ex1b == 0) - who.acid(-spec.ex1a); - else who.acid(spec.ex1a); - break; - // Invalid values - case eStatus::MAIN: - case eStatus::FORCECAGE: - case eStatus::POISONED_WEAPON: - case eStatus::INVULNERABLE: - case eStatus::MAGIC_RESISTANCE: - case eStatus::INVISIBLE: - case eStatus::CHARM: - break; - } - } + break; + case eStatus::MARTYRS_SHIELD: + if(spec.ex1b == 0) + pc->apply_status(eStatus::MARTYRS_SHIELD, spec.ex1a); + else pc->apply_status(eStatus::MARTYRS_SHIELD, -spec.ex1a); + break; + case eStatus::ACID: + if(spec.ex1b == 0) + pc->apply_status(eStatus::ACID, -spec.ex1a); + else pc->acid(spec.ex1a); + break; + // Invalid values + case eStatus::MAIN: + case eStatus::CHARM: + case eStatus::FORCECAGE: + break; } break; case eSpecType::AFFECT_STAT: - if(pc >= 100) break; + if(pc_num >= 100) break; if(spec.ex2a != minmax(0,20,spec.ex2a)) { giveError("Skill is out of range."); break; } for(i = 0; i < 6; i++) - if((pc < 0 || pc == i) && get_ran(1,1,100) < spec.pic) { + if((pc_num == 6 || pc_num == i) && get_ran(1,1,100) < spec.pic) { eSkill skill = eSkill(spec.ex2a); int adj = spec.ex1a * (spec.ex1b != 0 ? -1: 1); if(skill == eSkill::MAX_HP) @@ -2958,23 +2874,23 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, } break; case eSpecType::AFFECT_MAGE_SPELL: - if(pc >= 100) break; + if(pc_num >= 100) break; if(spec.ex1a != minmax(0,61,spec.ex1a)) { giveError("Mage spell is out of range (0 - 61). See docs."); break; } for(i = 0; i < 6; i++) - if((pc < 0) || (pc == i)) + if(pc_num == 6 || pc_num == i) univ.party[i].mage_spells[spec.ex1a] = spec.ex1b; break; case eSpecType::AFFECT_PRIEST_SPELL: - if(pc >= 100) break; + if(pc_num >= 100) break; if(spec.ex1a != minmax(0,61,spec.ex1a)) { giveError("Priest spell is out of range (0 - 61). See docs."); break; } for(i = 0; i < 6; i++) - if((pc < 0) || (pc == i)) + if(pc_num == 6 || pc_num == i) univ.party[i].priest_spells[spec.ex1a] = spec.ex1b; break; case eSpecType::AFFECT_GOLD: @@ -3009,66 +2925,66 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, univ.party.status[ePartyStatus::STEALTH] = r1; break; case eSpecType::AFFECT_TRAITS: - if(pc >= 100) break; + if(pc_num >= 100) break; if(spec.ex1a < 0 || spec.ex1a > 15) { giveError("Trait is out of range (0 - 15)."); break; } for(i = 0; i < 6; i++) - if(pc < 0 || pc == i) + if(pc_num == 6 || pc_num == i) univ.party[i].traits[eTrait(spec.ex1a)] = !spec.ex1b; break; case eSpecType::AFFECT_AP: if(!is_combat()) break; - if(pc < 100) { - for(i = 0; i < 6; i++) - if(pc < 0 || pc == i) { - if(spec.ex1b) - univ.party[i].ap += spec.ex1a; - else univ.party[i].ap -= spec.ex1a; - if(univ.party[i].ap < 0) - univ.party[i].ap = 0; - } + if(pc_num == 6) { + for(i = 0; i < 6; i++) { + if(spec.ex1b) + univ.party[i].ap += spec.ex1a; + else univ.party[i].ap -= spec.ex1a; + if(univ.party[i].ap < 0) + univ.party[i].ap = 0; + } } else { - cCreature& who = univ.town.monst[pc - 100]; if(spec.ex1b) - who.ap += spec.ex1a; - else who.ap -= spec.ex1a; - if(who.ap < 0) - who.ap = 0; + pc->ap += spec.ex1a; + else pc->ap -= spec.ex1a; + if(pc->ap < 0) + pc->ap = 0; } break; case eSpecType::AFFECT_NAME: get_strs(str, str, 0, spec.m3, -1); - if(pc < 100) { + if(cPlayer* who = dynamic_cast(pc)) + who->name = str; + else if(cCreature* monst = dynamic_cast(pc)) + monst->m_name = str; + else if(dynamic_cast(pc)) for(i = 0; i < 6; i++) - if(pc < 0 || pc == i) - univ.party[i].name = str; - } else univ.town.monst[pc - 100].m_name = str; + univ.party[i].name = str; break; case eSpecType::CREATE_NEW_PC: if(spec.ex1c < 0 || spec.ex1c > 19) { giveError("Race out of range (0 - 19)."); break; } - pc = univ.party.free_space(); - if(pc == 6) { + pc_num = univ.party.free_space(); + if(pc_num == 6) { add_string_to_buf("No room for new PC."); *next_spec = spec.pictype; check_mess = false; break; } - current_pc_picked_in_spec_enc = pc; get_strs(str, str, 0, spec.m3, -1); - univ.party.new_pc(pc); - univ.party[pc].name = str; - univ.party[pc].which_graphic = spec.pic; - univ.party[pc].cur_health = univ.party[pc].max_health = spec.ex1a; - univ.party[pc].cur_sp = univ.party[pc].max_sp = spec.ex1b; - univ.party[pc].race = eRace(spec.ex1c); - univ.party[pc].skills[eSkill::STRENGTH] = spec.ex2a; - univ.party[pc].skills[eSkill::DEXTERITY] = spec.ex2b; - univ.party[pc].skills[eSkill::INTELLIGENCE] = spec.ex2c; + univ.party.new_pc(pc_num); + univ.party[pc_num].name = str; + univ.party[pc_num].which_graphic = spec.pic; + univ.party[pc_num].cur_health = univ.party[pc_num].max_health = spec.ex1a; + univ.party[pc_num].cur_sp = univ.party[pc_num].max_sp = spec.ex1b; + univ.party[pc_num].race = eRace(spec.ex1c); + univ.party[pc_num].skills[eSkill::STRENGTH] = spec.ex2a; + univ.party[pc_num].skills[eSkill::DEXTERITY] = spec.ex2b; + univ.party[pc_num].skills[eSkill::INTELLIGENCE] = spec.ex2c; + current_pc_picked_in_spec_enc = &univ.get_target(pc_num); break; } if(check_mess) { @@ -3291,12 +3207,10 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, if(spec.ex2b == -1) { // Check specific PC's stat (uses the active PC from Select PC node) - short pc = 6; - if(univ.party.is_split()) - pc = univ.party.pc_present(); - if(pc == 6 && univ.party.pc_present(current_pc_picked_in_spec_enc)) - pc = current_pc_picked_in_spec_enc; - if(pc != 6) { + short pc = univ.get_target_i(*current_pc_picked_in_spec_enc); + if(pc == 6 && univ.party.is_split()) + pc = univ.get_target_i(univ.party.pc_present()); + if(pc >= 0 && pc < 6) { if(check_party_stat(eSkill(spec.ex2a), 10 + pc) >= spec.ex1a) *next_spec = spec.ex1b; break; @@ -3387,13 +3301,12 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, break; case eSpecType::IF_ALIVE: i = 0; - if(current_pc_picked_in_spec_enc < 100) { - int pc = current_pc_picked_in_spec_enc; + if(spec.ex1a == -1) + i = current_pc_picked_in_spec_enc->is_alive(); + else if(cPlayer* who = dynamic_cast(current_pc_picked_in_spec_enc)) { + int pc = univ.get_target_i(*current_pc_picked_in_spec_enc); eMainStatus stat; switch(spec.ex1a) { - case -1: - stat = eMainStatus::ALIVE; - break; case 0: stat = eMainStatus::DEAD; break; @@ -3416,19 +3329,19 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, if(stat == eMainStatus::SPLIT) i = univ.party.is_split(); else for(j = 0; j < 6; j++) - if(pc < 0 || pc == j) + if(pc == 6 || pc == i) i += univ.party[j].main_status == stat; - } else i = univ.town.monst[current_pc_picked_in_spec_enc - 100].active; + } if(i > 0) *next_spec = spec.ex1b; break; case eSpecType::IF_MAGE_SPELL: i = 0; - if(current_pc_picked_in_spec_enc < 100) { - int pc = current_pc_picked_in_spec_enc; + if(cPlayer* who = dynamic_cast(current_pc_picked_in_spec_enc)) { + int pc = univ.get_target_i(*who); if(spec.ex1a < 0 || spec.ex1a >= 62) break; for(j = 0; j < 6; j++) - if(pc < 0 || pc == j) + if(pc == 6 || pc == i) i += univ.party[j].mage_spells[spec.ex1a]; } else { // TODO: Implement for monsters @@ -3438,12 +3351,12 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, break; case eSpecType::IF_PRIEST_SPELL: i = 0; - if(current_pc_picked_in_spec_enc < 100) { - int pc = current_pc_picked_in_spec_enc; + if(cPlayer* who = dynamic_cast(current_pc_picked_in_spec_enc)) { + int pc = univ.get_target_i(*who); if(spec.ex1a < 0 || spec.ex1a >= 62) break; for(j = 0; j < 6; j++) - if(pc < 0 || pc == j) + if(pc == 6 || pc == i) i += univ.party[j].priest_spells[spec.ex1a]; } else { // TODO: Implement for monsters @@ -3464,7 +3377,7 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, giveError("Invalid status effect (0...14)"); break; } - if(current_pc_picked_in_spec_enc < 0) { + if(dynamic_cast(current_pc_picked_in_spec_enc)) { k = spec.ex2b == 2 ? std::numeric_limits::max() : 0; j = 0; for(i = 0; i < 6; i++, j++) @@ -3479,15 +3392,9 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, } if(spec.ex2b == 1 && j > 0) k /= j; - } else if(current_pc_picked_in_spec_enc < 100) { - cPlayer& pc = univ.party[current_pc_picked_in_spec_enc]; - if(pc.main_status == eMainStatus::ALIVE) - k = pc.status[eStatus(spec.ex1a)]; - else k = 0; } else { - cCreature& monst = univ.town.monst[current_pc_picked_in_spec_enc - 100]; - if(monst.active > 0) - k = monst.status[eStatus(spec.ex1a)]; + if(current_pc_picked_in_spec_enc->is_alive()) + k = current_pc_picked_in_spec_enc->status[eStatus(spec.ex1a)]; else k = 0; } j = spec.ex2a; @@ -3743,8 +3650,11 @@ void townmode_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, *redraw = 1; break; case eSpecType::TOWN_DESTROY_MONST: - if(spec.ex1a >= 0 && spec.ex1b >= 0 && ((i = monst_there(l)) < univ.town.monst.size())) - univ.town.monst[i].active = 0; + if(spec.ex1a >= 0 && spec.ex1b >= 0) { + iLiving* monst = univ.target_there(l, TARG_MONST); + if(monst != nullptr) + dynamic_cast(monst)->active = 0; + } *redraw = 1; break; case eSpecType::TOWN_NUKE_MONSTS: @@ -4026,10 +3936,10 @@ void townmode_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, case eSpecType::TOWN_RUN_MISSILE: if(which_mode == eSpecCtx::TALK) break; - if((i = monst_there(loc(spec.ex2a, spec.ex2b))) < 90) { - cCreature& who = univ.town.monst[i]; - i = 14 * who.x_width - 1; - r1 = 18 * who.y_width - 1; + if(iLiving* targ = univ.target_there(loc(spec.ex2a, spec.ex2b), TARG_MONST)) { + cCreature* who = dynamic_cast(targ); + i = 14 * who->x_width - 1; + r1 = 18 * who->y_width - 1; } else i = r1 = 0; run_a_missile(l, loc(spec.ex2a, spec.ex2b), spec.pic, spec.ex1c, spec.ex2c, i, r1, 100); break; @@ -4045,10 +3955,9 @@ void townmode_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, break; i = combat_posing_monster; if(l.y >= 0) { - int monst = monst_there(l); - if(monst < univ.town.monst.size()) - combat_posing_monster = 100 + monst; - else combat_posing_monster = pc_there(l); + iLiving* who = univ.target_there(l); + if(who != nullptr) + combat_posing_monster = univ.get_target_i(*who); if(combat_posing_monster == 6) combat_posing_monster = -1; } else combat_posing_monster = spec.ex1a; diff --git a/src/boe.specials.h b/src/boe.specials.h index 5bb441b4..cdf4e69d 100644 --- a/src/boe.specials.h +++ b/src/boe.specials.h @@ -3,15 +3,15 @@ bool town_specials(short which,short t_num); bool handle_wandering_specials (short which,short mode); -bool check_special_terrain(location where_check,eSpecCtx mode,short which_pc,short *spec_num, +bool check_special_terrain(location where_check,eSpecCtx mode,cPlayer& which_pc,short *spec_num, bool *forced); -void check_fields(location where_check,eSpecCtx mode,short which_pc); +void check_fields(location where_check,eSpecCtx mode,cPlayer& which_pc); void use_spec_item(short item); void use_item(short pc,short item); bool use_space(location where); bool adj_town_look(location where); void PSOE(short which_special,unsigned char *stuff_done_val,short where_put); -bool damage_monst(short which_m, short who_hit, short how_much, short how_much_spec, eDamageType dam_type, short sound_type, bool do_print = true); +bool damage_monst(short which_m, short who_hit, short how_much, eDamageType dam_type, short sound_type, bool do_print = true); void kill_monst(cCreature *which_m,short who_killed,eMainStatus type = eMainStatus::DEAD); void special_increase_age(long length = 1, bool queue = false); void do_rest(long length, int hp_restore, int mp_restore); diff --git a/src/boe.text.cpp b/src/boe.text.cpp index 2194d28d..8a0f0162 100644 --- a/src/boe.text.cpp +++ b/src/boe.text.cpp @@ -12,6 +12,7 @@ #include "graphtool.hpp" #include "scrollbar.hpp" #include "restypes.hpp" +#include "spell.hpp" typedef struct { char line[50]; @@ -640,7 +641,7 @@ short do_look(location space) { if((overall_mode == MODE_LOOK_TOWN) || (overall_mode == MODE_LOOK_COMBAT)) { for(i = 0; i < univ.town.monst.size(); i++) if((univ.town.monst[i].active != 0) && (is_lit) - && (monst_on_space(space,i)) && + && univ.town.monst[i].on_space(space) && ((overall_mode == MODE_LOOK_TOWN) || (can_see_light(univ.party[current_pc].combat_pos,space,sight_obscurity) < 5)) && (univ.town.monst[i].picture_num != 0)) { @@ -848,15 +849,15 @@ void print_monst_name(mon_num_t m_type) { add_string_to_buf((char *) msg.c_str()); } -//short target; // < 100 - pc >= 100 monst -void print_monst_attacks(mon_num_t m_type,short target) { - std::string msg = get_m_name(m_type); +void cCreature::print_attacks(iLiving* target) { + std::string msg = m_name; msg += " attacks "; - if(target < 100) - msg += univ.party[target].name; - else - msg += get_m_name(univ.town.monst[target - 100].number); - add_string_to_buf((char *) msg.c_str()); + if(cPlayer* who = dynamic_cast(target)) + msg += who->name; + else if(cCreature* monst = dynamic_cast(target)) + msg += monst->m_name; + else msg += "you"; + add_string_to_buf(msg); } void damaged_message(short damage,eMonstMelee type) { @@ -1067,38 +1068,25 @@ void cCreature::spell_note(int which_mess) { add_string_to_buf((char *) msg.c_str()); } -//short type; // 0 - mage 1- priest -void monst_cast_spell_note(mon_num_t number,eSpell spell) { - short spell_num = short(spell); - std::string msg = get_m_name(number); - msg += " casts:"; - add_string_to_buf((char *) msg.c_str()); - msg = get_str("magic-names", spell_num); - msg = " " + msg; - add_string_to_buf((char *) msg.c_str()); +void cCreature::cast_spell_note(eSpell spell) { + add_string_to_buf(m_name + " casts:"); + add_string_to_buf(" " + (*spell).name()); } -void monst_breathe_note(mon_num_t number) { - std::string msg = get_m_name(number); - msg += " breathes."; - add_string_to_buf((char *) msg.c_str()); - +void cCreature::breathe_note() { + add_string_to_buf(m_name + " breathes."); } -void monst_damaged_mes(mon_num_t which_m,short how_much,short how_much_spec) { - std::string msg = get_m_name(univ.town.monst[which_m].number); +void cCreature::damaged_msg(int how_much,int how_much_spec) { std::ostringstream sout; - sout << " " << msg << " takes " << how_much; + sout << " " << m_name << " takes " << how_much; if(how_much_spec > 0) sout << '+' << how_much_spec; - msg = sout.str(); - add_string_to_buf((char *) msg.c_str()); + add_string_to_buf(sout.str()); } -void monst_killed_mes(mon_num_t which_m) { - std::string msg = get_m_name(univ.town.monst[which_m].number); - msg = " " + msg + " dies."; - add_string_to_buf((char *) msg.c_str()); +void cCreature::killed_msg() { + add_string_to_buf(" " + m_name + " dies."); } void print_nums(short a,short b,short c) { diff --git a/src/boe.text.h b/src/boe.text.h index b5b6d24a..f3750631 100644 --- a/src/boe.text.h +++ b/src/boe.text.h @@ -20,13 +20,8 @@ void notify_out_combat_began(cOutdoors::cWandering encounter,short *nums) ; std::string get_m_name(mon_num_t num); std::string get_ter_name(ter_num_t num); void print_monst_name(mon_num_t m_type); -void print_monst_attacks(mon_num_t m_type,short target); void damaged_message(short damage,eMonstMelee type); std::string print_monster_going(mon_num_t m_num,short ap); -void monst_cast_spell_note(mon_num_t number,eSpell spell); -void monst_breathe_note(mon_num_t number); -void monst_damaged_mes(mon_num_t which_m,short how_much,short how_much_spec); -void monst_killed_mes(mon_num_t which_m); void print_nums(short a,short b,short c); short print_terrain(location space); void add_string_to_buf(std::string str, unsigned short indent); // Set second paramater to nonzero to auto-split the line if it's too long diff --git a/src/boe.town.cpp b/src/boe.town.cpp index b13bba0e..2de7ba0d 100644 --- a/src/boe.town.cpp +++ b/src/boe.town.cpp @@ -688,7 +688,7 @@ void start_town_combat(eDirection direction) { univ.town.monst[i].target = 6; for(i = 0; i < 6; i++) { - univ.party[i].last_attacked = univ.town.monst.size() + 10; + univ.party[i].last_attacked = nullptr; univ.party[i].parry = 0; univ.party[i].direction = direction; univ.party[current_pc].direction = direction; @@ -1059,7 +1059,7 @@ void pick_lock(location where,short pc_num) { if(r1 < 75) will_break = true; - r1 = get_ran(1,1,100) - 5 * stat_adj(pc_num,eSkill::DEXTERITY) + univ.town.difficulty * 7 + r1 = get_ran(1,1,100) - 5 * univ.party[pc_num].stat_adj(eSkill::DEXTERITY) + univ.town.difficulty * 7 - 5 * univ.party[pc_num].skill(eSkill::LOCKPICKING) - univ.party[pc_num].items[which_item].abil_data[0] * 7; // Nimble? @@ -1096,7 +1096,7 @@ void bash_door(location where,short pc_num) { short r1,unlock_adjust; terrain = univ.town->terrain(where.x,where.y); - r1 = get_ran(1,1,100) - 15 * stat_adj(pc_num,eSkill::STRENGTH) + univ.town.difficulty * 4; + r1 = get_ran(1,1,100) - 15 * univ.party[pc_num].stat_adj(eSkill::STRENGTH) + univ.town.difficulty * 4; if(univ.scenario.ter_types[terrain].special != eTerSpec::UNLOCKABLE) { add_string_to_buf(" Wrong terrain type. "); diff --git a/src/boe.townspec.cpp b/src/boe.townspec.cpp index c23a6784..fc1cf20c 100644 --- a/src/boe.townspec.cpp +++ b/src/boe.townspec.cpp @@ -49,15 +49,17 @@ bool run_trap(short pc_num,eTrapType trap_type,short trap_level,short diff) { if(trap_type == TRAP_FALSE_ALARM) return true; + cPlayer& disarmer = univ.party[pc_num]; + if(pc_num < 6) { - i = stat_adj(pc_num,eSkill::DEXTERITY); - i += univ.party[pc_num].get_prot_level(eItemAbil::THIEVING) / 2; - skill = minmax(0,20,univ.party[pc_num].skill(eSkill::DISARM_TRAPS) + - + univ.party[pc_num].skill(eSkill::LUCK) / 2 + 1 - univ.town.difficulty + 2 * i); + i = disarmer.stat_adj(eSkill::DEXTERITY); + i += disarmer.get_prot_level(eItemAbil::THIEVING) / 2; + skill = minmax(0,20,disarmer.skill(eSkill::DISARM_TRAPS) + + + disarmer.skill(eSkill::LUCK) / 2 + 1 - univ.town.difficulty + 2 * i); r1 = get_ran(1,1,100) + diff; // Nimble? - if(univ.party[pc_num].traits[eTrait::NIMBLE]) + if(disarmer.traits[eTrait::NIMBLE]) r1 -= 6; @@ -81,7 +83,7 @@ bool run_trap(short pc_num,eTrapType trap_type,short trap_level,short diff) { add_string_to_buf(" A dart flies out. "); r1 = 3 + univ.town.difficulty / 14; r1 = r1 + trap_level * 2; - univ.party[pc_num].poison(r1); + disarmer.poison(r1); break; case TRAP_GAS: @@ -104,13 +106,13 @@ bool run_trap(short pc_num,eTrapType trap_type,short trap_level,short diff) { r1 = 200 + univ.town.difficulty * 100; r1 = r1 + trap_level * 400; // TODO: It says sleep ray but is actually paralysis ray? - univ.party[pc_num].sleep(eStatus::PARALYZED, r1, 50); + disarmer.sleep(eStatus::PARALYZED, r1, 50); break; case TRAP_DRAIN_XP: add_string_to_buf(" You feel weak. "); r1 = 40; r1 = r1 + trap_level * 30; - univ.party[pc_num].experience = max (0,univ.party[pc_num].experience - r1); + disarmer.experience = max(0,disarmer.experience - r1); break; case TRAP_ALERT: @@ -132,7 +134,7 @@ bool run_trap(short pc_num,eTrapType trap_type,short trap_level,short diff) { add_string_to_buf(" You prick your finger. "); r1 = 3 + univ.town.difficulty / 14; r1 = r1 + trap_level * 2; - univ.party[pc_num].disease(r1); + disarmer.disease(r1); break; case TRAP_DISEASE_ALL: diff --git a/src/classes/creature.cpp b/src/classes/creature.cpp index a03c7908..630812ee 100644 --- a/src/classes/creature.cpp +++ b/src/classes/creature.cpp @@ -91,6 +91,8 @@ void cCreature::heal(int amt) { health += amt; if(health > m_health) health = m_health; + if(health < 0) + health = 0; } void cCreature::cure(int amt) { @@ -105,6 +107,7 @@ void cCreature::restore_sp(int amt) { mp += amt; if(mp > max_mp) mp = max_mp; + if(mp < 0) mp = 0; } void cCreature::drain_sp(int drain) { @@ -123,6 +126,10 @@ bool cCreature::is_alive() const { return active > 0; } +bool cCreature::is_friendly() const { + return attitude % 2 == 0; +} + location cCreature::get_loc() const { return cur_loc; } @@ -170,6 +177,13 @@ int cCreature::get_level() const { return level; } +bool cCreature::on_space(location loc) const { + if(loc.x - cur_loc.x >= 0 && loc.x - cur_loc.x <= x_width - 1 && + loc.y - cur_loc.y >= 0 && loc.y - cur_loc.y <= y_width - 1) + return true; + return false; +} + void cCreature::writeTo(std::ostream& file) const { file << "MONSTER " << number << '\n'; file << "ATTITUDE " << attitude << '\n'; diff --git a/src/classes/creature.hpp b/src/classes/creature.hpp index f85f702c..2f1d50cc 100644 --- a/src/classes/creature.hpp +++ b/src/classes/creature.hpp @@ -53,6 +53,7 @@ public: int get_level() const; bool is_alive() const; + bool is_friendly() const; bool is_shielded() const; int get_shared_dmg(int base_dmg) const; location get_loc() const; @@ -60,6 +61,12 @@ public: int magic_adjust(int base); void spell_note(int which); + void cast_spell_note(eSpell spell); + void print_attacks(iLiving* target); + void breathe_note(); + void damaged_msg(int how_much, int extra); + void killed_msg(); + bool on_space(location loc) const; void append(legacy::creature_data_type old); void writeTo(std::ostream& file) const; diff --git a/src/classes/living.hpp b/src/classes/living.hpp index ccf602ad..45961d67 100644 --- a/src/classes/living.hpp +++ b/src/classes/living.hpp @@ -24,11 +24,13 @@ public: short marked_damage = 0; // for use during animations virtual bool is_alive() const = 0; + virtual bool is_friendly() const = 0; virtual bool is_shielded() const = 0; // Checks for martyr's shield in any form - status, monster abil, item abil virtual int get_shared_dmg(int base_dmg) const = 0; // And this goes with the above. virtual void apply_status(eStatus which, int how_much); virtual void clear_bad_status(); + virtual void void_sanctuary(); virtual void heal(int how_much) = 0; virtual void poison(int how_much) = 0; diff --git a/src/classes/party.cpp b/src/classes/party.cpp index 1d5618c3..094b3458 100644 --- a/src/classes/party.cpp +++ b/src/classes/party.cpp @@ -275,6 +275,10 @@ bool cParty::is_alive() const { return false; } +bool cParty::is_friendly() const { + return true; +} + bool cParty::is_shielded() const { return false; } @@ -965,7 +969,7 @@ bool cParty::end_split(snd_num_t noise) { return true; } -short cParty::pc_present() const { +iLiving& cParty::pc_present() const { short ret = 7; for(int i = 0; i < 6; i++){ if(pc_present(i) && ret == 7) @@ -973,8 +977,9 @@ short cParty::pc_present() const { else if(pc_present(i) && ret < 6) ret = 6; } - if(ret == 7) ret = 6; - return ret; + if(ret >= 6) + return *const_cast(this); + return *adven[ret]; } bool operator==(const cParty::cConvers& one, const cParty::cConvers& two) { diff --git a/src/classes/party.h b/src/classes/party.h index ca8c0759..9bcf3135 100644 --- a/src/classes/party.h +++ b/src/classes/party.h @@ -133,6 +133,7 @@ public: void append(legacy::pc_record_type(& old)[6]); bool is_alive() const; + bool is_friendly() const; bool is_shielded() const; int get_shared_dmg(int base_dmg) const; location get_loc() const; @@ -182,7 +183,7 @@ public: bool end_split(snd_num_t noise); bool is_split() const; bool pc_present(short n) const; - short pc_present() const; // If only one pc is present, returns the number of that pc. Otherwise returns 6. + iLiving& pc_present() const; // If only one pc is present, returns that pc. Otherwise returns party. void swap_pcs(size_t a, size_t b); typedef std::vector::iterator encIter; diff --git a/src/classes/pc.cpp b/src/classes/pc.cpp index c5ce0ee6..85c4389b 100644 --- a/src/classes/pc.cpp +++ b/src/classes/pc.cpp @@ -111,6 +111,13 @@ bool cPlayer::is_alive() const { return main_status == eMainStatus::ALIVE; } +bool cPlayer::is_friendly() const { + // Currently players can't be charmed, but that might change in the future + if(status[eStatus::CHARM] > 0) + return false; + return true; +} + bool cPlayer::is_shielded() const { if(status[eStatus::MARTYRS_SHIELD] > 0) return true; diff --git a/src/classes/pc.h b/src/classes/pc.h index 4d9728c7..fd10c26b 100644 --- a/src/classes/pc.h +++ b/src/classes/pc.h @@ -51,9 +51,11 @@ public: // transient stuff std::map last_cast; location combat_pos; - short parry, last_attacked; + short parry; + iLiving* last_attacked = nullptr; // Note: Currently this is assigned but never read bool is_alive() const; + bool is_friendly() const; bool is_shielded() const; int get_shared_dmg(int base_dmg) const; @@ -93,6 +95,7 @@ public: short has_abil_equip(eItemAbil abil, short dat = -1) const; short has_abil(eItemAbil abil, short dat = -1) const; short skill(eSkill skill) const; + short stat_adj(eSkill skill) const; eBuyStatus ok_to_buy(short cost,cItem item) const; void append(legacy::pc_record_type old); diff --git a/src/classes/shop.cpp b/src/classes/shop.cpp index 654f91f8..a82c8baf 100644 --- a/src/classes/shop.cpp +++ b/src/classes/shop.cpp @@ -38,6 +38,10 @@ std::map skill_g_cost = { {eSkill::LOCKPICKING,20}, {eSkill::ASSASSINATION,100}, {eSkill::POISON,80}, {eSkill::LUCK,0}, }; +// The index here is the skill's level, not the skill itself; thus 20 is the max index since no skill can go above 20. +short skill_bonus[21] = { + -3,-3,-2,-1,0,0,1,1,1,2, + 2,2,3,3,3,3,4,4,4,5,5}; static long cost_mult[7] = {5,7,10,13,16,20,25}; diff --git a/src/classes/simpletypes.h b/src/classes/simpletypes.h index 62eeaed7..ed1b399e 100644 --- a/src/classes/simpletypes.h +++ b/src/classes/simpletypes.h @@ -487,6 +487,7 @@ enum class eDamageType { COLD = 5, UNDEAD = 6, DEMON = 7, + SPECIAL = 9, // Completely unblockable damage from assassination skill MARKED = 10, }; diff --git a/src/classes/universe.cpp b/src/classes/universe.cpp index d61849f7..8cabd275 100644 --- a/src/classes/universe.cpp +++ b/src/classes/universe.cpp @@ -927,6 +927,30 @@ void cUniverse::check_item(cItem& item) { } } +// This attempts to find the index of a living entity in the party or town, using pointer arithmetic +// Assuming success, the two get_target() calls are a round-trip +// Returns maxint on failure (which could happen eg with a stored PC or a monster from a saved town) +size_t cUniverse::get_target_i(iLiving& who) { + if(dynamic_cast(&who)) + return 6; + else if(cPlayer* check = dynamic_cast(&who)) { + cPlayer* first = &party[0]; + cPlayer* last = &party[5]; + if(check >= first && check <= last) + return check - first; + } else if(cCreature* check = dynamic_cast(&who)) { + size_t num_monst = town.monst.size(); + cCreature* first = &town.monst[0]; + cCreature* last = &town.monst[num_monst-1]; + if(check >= first && check <= last) + return check - first + 100; + } + return -1; +} + +// TODO: Both of these have an issue that they'll return garbage if called outdoors +// It's less of a problem with target_there since that should never actually be called outdoors, +// but get_target would be called by "affect PC" special nodes which could be run outdoors. iLiving& cUniverse::get_target(size_t which) { size_t maxint = -1; if(which == maxint || which == 6) @@ -938,6 +962,20 @@ iLiving& cUniverse::get_target(size_t which) { else throw std::string("Tried to get nonexistent target!"); } +iLiving* cUniverse::target_there(location where, eTargetType type) { + if(type == TARG_ANY || type == TARG_PC) { + for(int i = 0; i < 6; i++) + if(party[i].is_alive() && where == party[i].get_loc()) + return &party[i]; + } + if(type == TARG_ANY || type == TARG_MONST) { + for(size_t i = 0; i < town.monst.size(); i++) + if(town.monst[i].is_alive() && town.monst[i].on_space(where)) + return &town.monst[i]; + } + return nullptr; +} + extern cCustomGraphics spec_scen_g; pic_num_t cUniverse::addGraphic(pic_num_t pic, ePicType type) { diff --git a/src/classes/universe.h b/src/classes/universe.h index 1165c9b2..6d5f88f1 100644 --- a/src/classes/universe.h +++ b/src/classes/universe.h @@ -145,6 +145,8 @@ public: explicit cCurOut(cUniverse& univ); }; +enum eTargetType {TARG_ANY, TARG_PC, TARG_MONST}; + class cUniverse{ template using update_info = std::set; std::map> update_items; @@ -160,6 +162,8 @@ public: void exportGraphics(); iLiving& get_target(size_t which); + iLiving* target_there(location pos, eTargetType type = TARG_ANY); + size_t get_target_i(iLiving& who); cScenario scenario; cParty party; diff --git a/src/pcedit/pc.editors.cpp b/src/pcedit/pc.editors.cpp index aa6328bd..1a3639e2 100644 --- a/src/pcedit/pc.editors.cpp +++ b/src/pcedit/pc.editors.cpp @@ -44,10 +44,6 @@ sf::Texture button_num_gworld; extern std::map skill_cost; extern std::map skill_max; extern std::map skill_g_cost; -// The index here is the skill's level, not the skill itself; thus 20 is the max index since no skill can go above 20. -short skill_bonus[21] = { - -3,-3,-2,-1,0,0,1,1,1,2, - 2,2,3,3,3,3,4,4,4,5,5}; // These are the IDs used to refer to the skills in the dialog const char* skill_ids[19] = {