From c39c16f6bfadf2c23afc0f7cb0506ba1a20db86c Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Fri, 5 Sep 2025 11:16:04 -0500 Subject: [PATCH] implement if monsters alive node --- rsrc/strings/specials-opcodes.txt | 2 +- src/game/boe.items.cpp | 58 +++++++++++++++++-------------- src/game/boe.items.hpp | 1 + src/game/boe.specials.cpp | 35 +++++++++++++++++++ 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/rsrc/strings/specials-opcodes.txt b/rsrc/strings/specials-opcodes.txt index 2059d0e4..2ae6d6c9 100644 --- a/rsrc/strings/specials-opcodes.txt +++ b/rsrc/strings/specials-opcodes.txt @@ -158,7 +158,7 @@ if-num-response if-boat if-horse if-quest - +if-monsters-alive diff --git a/src/game/boe.items.cpp b/src/game/boe.items.cpp index a8c536be..adb68fa3 100644 --- a/src/game/boe.items.cpp +++ b/src/game/boe.items.cpp @@ -304,18 +304,7 @@ void make_town_hostile() { return; } -// Set Attitude node adapted from *i, meant to replace make_town_hostile node -void set_town_attitude(short lo,short hi,eAttitude att) { - short num; - - if(is_combat() && which_combat_type == 0) - return; - - give_help(53,0); - - static std::set hostile_attitudes = { eAttitude::HOSTILE_A, eAttitude::HOSTILE_B }; - univ.town.monst.hostile = hostile_attitudes.count(att); - +void foreach_townperson(short lo, short hi, std::function func) { long long num_monst = univ.town.monst.size(); // Nice smart indexing, like Python :D @@ -332,22 +321,39 @@ void set_town_attitude(short lo,short hi,eAttitude att) { for(short i = lo; i <= hi; i++) { if(univ.town.monst[i].is_alive() && univ.town.monst[i].summon_time == 0){ - univ.town.monst[i].attitude = att; - num = univ.town.monst[i].number; - // If made hostile, make mobile - if(!univ.town.monst[i].is_friendly()) { - univ.town.monst[i].mobility = 1; - // If a "guard", give a power boost - if(univ.scenario.scen_monsters[num].guard) { - univ.town.monst[i].active = eCreatureStatus::ALERTED; - univ.town.monst[i].health *= 3; - univ.town.monst[i].status[eStatus::HASTE_SLOW] = 8; - univ.town.monst[i].status[eStatus::BLESS_CURSE] = 8; - } - - } + func(univ.town.monst[i]); } } +} + +// Set Attitude node adapted from *i, meant to replace make_town_hostile node +void set_town_attitude(short lo,short hi,eAttitude att) { + + if(is_combat() && which_combat_type == 0) + return; + + // TODO this message would happen even if a node were making hostile monsters friendly or modifying a small range, + // or if it's a dungeon, not a friendly town + give_help(53,0); + + static std::set hostile_attitudes = { eAttitude::HOSTILE_A, eAttitude::HOSTILE_B }; + univ.town.monst.hostile = hostile_attitudes.count(att); + + foreach_townperson(lo, hi, [att](cCreature& creature) -> void { + creature.attitude = att; + short num = creature.number; + // If made hostile, make mobile + if(!creature.is_friendly()) { + creature.mobility = 1; + // If a "guard", give a power boost + if(univ.scenario.scen_monsters[num].guard) { + creature.active = eCreatureStatus::ALERTED; + creature.health *= 3; + creature.status[eStatus::HASTE_SLOW] = 8; + creature.status[eStatus::BLESS_CURSE] = 8; + } + } + }); // In some towns, doin' this'll getcha' killed. // (Or something else! Killing the party would be the responsibility of whatever special node is called.) diff --git a/src/game/boe.items.hpp b/src/game/boe.items.hpp index 9ae7fb26..58fcaec2 100644 --- a/src/game/boe.items.hpp +++ b/src/game/boe.items.hpp @@ -20,6 +20,7 @@ short dist_from_party(location where); void set_item_flag(cItem *item); short get_item(location place,short pc_num,bool check_container); +void foreach_townperson(short lo, short hi, std::function func); void make_town_hostile(); void set_town_attitude(short lo,short hi,eAttitude att); bool show_get_items(std::string titleText, std::vector& itemRefs, short pc_getting, bool overload = false); diff --git a/src/game/boe.specials.cpp b/src/game/boe.specials.cpp index 15af2d0f..a8395d26 100644 --- a/src/game/boe.specials.cpp +++ b/src/game/boe.specials.cpp @@ -3805,6 +3805,41 @@ void ifthen_spec(const runtime_state& ctx) { if(univ.party.in_horse >= 0 && (spec.ex1b < 0 || spec.ex1b == univ.party.in_horse)) ctx.next_spec = spec.ex1c; break; + case eSpecType::IF_MONSTERS_ALIVE:{ + if(univ.party.town_num == 200){ + showError("This node can only run in town!"); + ctx.next_spec = -1; + break; + } + if(spec.ex1c < -3 || spec.ex1c > 3){ + showError("Invalid attitude (0-Friendly Docile, 1-Hostile A, 2-Friendly Will Fight, 3-Hostile B)."); + break; + } + int min = spec.ex2a; + int count = 0; + foreach_townperson(spec.ex1a, spec.ex1b, [&count, spec](cCreature& creature) -> void { + std::map att_counts; + // I know there must be many better ways of doing this... + if(spec.ex1c == 0 || spec.ex1c == -2 || spec.ex1c == -3){ + att_counts[eAttitude::DOCILE] = 1; + } + if(spec.ex1c == 1 || spec.ex1c == -1 || spec.ex1c == -3){ + att_counts[eAttitude::HOSTILE_A] = 1; + } + if(spec.ex1c == 2 || spec.ex1c == -2 || spec.ex1c == -3){ + att_counts[eAttitude::FRIENDLY] = 1; + } + if(spec.ex1c == 3 || spec.ex1c == -1 || spec.ex1c == -3){ + att_counts[eAttitude::HOSTILE_B] = 1; + } + count += att_counts[creature.attitude]; + }); + if(count >= min){ + ctx.next_spec = spec.ex2b; + }else{ + ctx.next_spec = spec.jumpto; + } + }break; default: showError(fmt::format("Special node type \"{}\" is either miscategorized or unimplemented!", (*cur_node.type).name())); break;