diff --git a/game/graphics/effect.cpp b/game/graphics/effect.cpp index 046af2be4..558ee0ba9 100644 --- a/game/graphics/effect.cpp +++ b/game/graphics/effect.cpp @@ -306,18 +306,27 @@ void Effect::onCollide(World& world, const VisualFx* root, const Vec3& pos, Npc* if(npc!=nullptr) vfx = root->emFXCollDyn; + // Gothic 1 uses emFXCollDyn->sendAssessMagic as check for PERC_ASSESSMAGIC + // Gothic 2 instead introduced new vfx emFXCollDynPerc specific for this purpose + bool g2 = world.version().game==2; if(vfx!=nullptr) { Effect eff(*vfx,world,pos,SpellFxKey::Collide); eff.setSpellId(splId,world); eff.setOrigin(other); eff.setActive(true); - if(npc!=nullptr) - npc ->runEffect(std::move(eff)); else + if(npc!=nullptr) { + npc ->runEffect(std::move(eff)); + if(!g2 && vfx->sendAssessMagic) { + auto oth = other==nullptr ? npc : other; + npc->perceptionProcess(*oth,npc,0,PERC_ASSESSMAGIC); + } + } else { world.runEffect(std::move(eff)); + } } - if(npc!=nullptr && root->emFXCollDynPerc!=nullptr) { + if(g2 && npc!=nullptr && root->emFXCollDynPerc!=nullptr) { const VisualFx* vfx = root->emFXCollDynPerc; Effect eff(*vfx,world,pos,SpellFxKey::Collide); eff.setActive(true); diff --git a/game/world/objects/item.cpp b/game/world/objects/item.cpp index 46ba1547d..c65338daa 100644 --- a/game/world/objects/item.cpp +++ b/game/world/objects/item.cpp @@ -4,6 +4,7 @@ #include "game/serialize.h" #include "game/gamescript.h" +#include "utils/versioninfo.h" #include "world/objects/npc.h" #include "world/world.h" #include "utils/fileext.h" @@ -204,12 +205,20 @@ bool Item::isMulti() const { } bool Item::isSpellShoot() const { + // Whether a spell is a projectile is hardcoded in vanilla, see + // https://forum.worldofplayers.de/forum/threads/1460092-Stuck-in-a-charge-spell/page2?p=24786462&viewfull=1#post24786462 + // Only hardcode Stormfist for now since other spells work with the heuristic based on target_collect_algo if(!isSpellOrRune()) return false; + bool g1 = world.version().game==1; + const int32_t SPL_STORMFIST = 47; + if(g1 && spellId()==SPL_STORMFIST) + return true; auto& spl = world.script().spellDesc(spellId()); return spl.target_collect_algo!=TargetCollect::TARGET_COLLECT_NONE && spl.target_collect_algo!=TargetCollect::TARGET_COLLECT_CASTER && - spl.target_collect_algo!=TargetCollect::TARGET_COLLECT_FOCUS; + spl.target_collect_algo!=TargetCollect::TARGET_COLLECT_FOCUS && + spl.target_collect_algo!=TargetCollect::TARGET_COLLECT_ALL_FALLBACK_NONE; } bool Item::isSpellOrRune() const { diff --git a/game/world/world.cpp b/game/world/world.cpp index 92c607504..5ace4c423 100644 --- a/game/world/world.cpp +++ b/game/world/world.cpp @@ -417,18 +417,20 @@ Focus World::validateFocus(const Focus &def) { } Focus World::findFocus(const Npc &pl, const Focus& def) { - auto opt = WorldObjects::NoFlg; - auto coll = TARGET_COLLECT_FOCUS; - auto& policy = searchPolicy(pl,coll,opt); + auto opt = WorldObjects::NoFlg; + auto collAlgo = TARGET_COLLECT_FOCUS; + auto collType = TARGET_TYPE_ALL; + auto& policy = searchPolicy(pl,collAlgo,collType,opt); - WorldObjects::SearchOpt optNpc {policy.npc_range1, policy.npc_range2, policy.npc_azi, coll, opt}; - WorldObjects::SearchOpt optMob {policy.mob_range1, policy.mob_range2, policy.mob_azi, coll }; - WorldObjects::SearchOpt optItm {policy.item_range1, policy.item_range2, policy.item_azi, coll }; + WorldObjects::SearchOpt optNpc {policy.npc_range1, policy.npc_range2, policy.npc_azi, collAlgo, collType, opt}; + WorldObjects::SearchOpt optMob {policy.mob_range1, policy.mob_range2, policy.mob_azi, collAlgo}; + WorldObjects::SearchOpt optItm {policy.item_range1, policy.item_range2, policy.item_azi, collAlgo, collType}; auto n = policy.npc_prio <0 ? nullptr : wobj.findNpcNear (pl,def.npc, optNpc); auto it = policy.item_prio<0 ? nullptr : wobj.findItem (pl,def.item, optItm); auto inter = policy.mob_prio <0 ? nullptr : wobj.findInteractive(pl,def.interactive,optMob); - if(pl.weaponState()!=WeaponState::NoWeapon) { + auto ws = pl.weaponState(); + if(ws==WeaponState::Bow || ws==WeaponState::CBow) { optMob.flags = WorldObjects::SearchFlg(WorldObjects::FcOverride | WorldObjects::NoRay); inter = wobj.findInteractive(pl,def.interactive,optMob); } @@ -470,11 +472,12 @@ Focus World::findFocus(const Focus &def) { } bool World::testFocusNpc(Npc* def) { - auto opt = WorldObjects::NoFlg; - auto coll = TARGET_COLLECT_FOCUS; - auto& policy = searchPolicy(*npcPlayer,coll,opt); + auto opt = WorldObjects::NoFlg; + auto collAlgo = TARGET_COLLECT_FOCUS; + auto collType = TARGET_TYPE_ALL; + auto& policy = searchPolicy(*npcPlayer,collAlgo,collType,opt); - WorldObjects::SearchOpt optNpc{policy.npc_range1, policy.npc_range2, policy.npc_azi, coll, opt}; + WorldObjects::SearchOpt optNpc{policy.npc_range1, policy.npc_range2, policy.npc_azi, collAlgo, collType, opt}; return wobj.testFocusNpc(*npcPlayer,def,optNpc); } @@ -815,9 +818,10 @@ void World::invalidateVobIndex() { wobj.invalidateVobIndex(); } -const zenkit::IFocus& World::searchPolicy(const Npc& pl, TargetCollect& coll, WorldObjects::SearchFlg& opt) const { - opt = WorldObjects::NoFlg; - coll = TARGET_COLLECT_FOCUS; +const zenkit::IFocus& World::searchPolicy(const Npc& pl, TargetCollect& collAlgo, TargetType& collType, WorldObjects::SearchFlg& opt) const { + opt = WorldObjects::NoFlg; + collAlgo = TARGET_COLLECT_FOCUS; + collType = TARGET_TYPE_ALL; switch(pl.weaponState()) { case WeaponState::Fist: @@ -833,7 +837,8 @@ const zenkit::IFocus& World::searchPolicy(const Npc& pl, TargetCollect& coll, Wo if(auto weapon = pl.inventory().activeWeapon()) { int32_t id = weapon->spellId(); auto& spl = script().spellDesc(id); - coll = TargetCollect(spl.target_collect_algo); + collAlgo = TargetCollect(spl.target_collect_algo); + collType = TargetType(spl.target_collect_type); } opt = WorldObjects::SearchFlg(WorldObjects::NoDeath | WorldObjects::NoUnconscious); return game.script()->focusMage(); diff --git a/game/world/world.h b/game/world/world.h index c5627c029..3d0572a40 100644 --- a/game/world/world.h +++ b/game/world/world.h @@ -184,7 +184,7 @@ class World final { void invalidateVobIndex(); private: - const zenkit::IFocus& searchPolicy(const Npc& pl, TargetCollect& coll, WorldObjects::SearchFlg& opt) const; + const zenkit::IFocus& searchPolicy(const Npc& pl, TargetCollect& collAlgo, TargetType& collType, WorldObjects::SearchFlg& opt) const; std::string wname; GameSession& game; diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index cf294903f..acaaed320 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -54,8 +54,8 @@ void WorldObjects::MobStates::load(Serialize& fin) { } } -WorldObjects::SearchOpt::SearchOpt(float rangeMin, float rangeMax, float azi, TargetCollect collectAlgo, WorldObjects::SearchFlg flags) - :rangeMin(rangeMin),rangeMax(rangeMax),azi(azi),collectAlgo(collectAlgo),flags(flags) { +WorldObjects::SearchOpt::SearchOpt(float rangeMin, float rangeMax, float azi, TargetCollect collectAlgo, TargetType collectType, WorldObjects::SearchFlg flags) + :rangeMin(rangeMin),rangeMax(rangeMax),azi(azi),collectAlgo(collectAlgo),collectType(collectType),flags(flags) { } WorldObjects::WorldObjects(World& owner):owner(owner){ @@ -735,6 +735,8 @@ Interactive* WorldObjects::findInteractive(const Npc &pl, Interactive* def, cons return def; if(owner.view()==nullptr) return nullptr; + if(!bool(opt.collectType&TARGET_TYPE_ALL)) + return nullptr; Interactive* ret = nullptr; float rlen = opt.rangeMax*opt.rangeMax; @@ -770,6 +772,8 @@ Item *WorldObjects::findItem(const Npc &pl, Item *def, const SearchOpt& opt) { return def; if(owner.view()==nullptr) return nullptr; + if(!bool(opt.collectType&(TARGET_TYPE_ALL|TARGET_TYPE_ITEMS))) + return nullptr; Item* ret = nullptr; float rlen = opt.rangeMax*opt.rangeMax; @@ -968,16 +972,16 @@ void WorldObjects::setMobState(std::string_view scheme, int32_t st) { } template -T& deref(std::unique_ptr& x){ return *x; } +static T& deref(std::unique_ptr& x){ return *x; } template -T& deref(T* x){ return *x; } +static T& deref(T* x){ return *x; } template -T& deref(T& x){ return x; } +static T& deref(T& x){ return x; } template -bool checkFlag(T&,WorldObjects::SearchFlg){ return true; } +static bool checkFlag(T&,WorldObjects::SearchFlg){ return true; } static bool checkFlag(Npc& n,WorldObjects::SearchFlg f){ if(n.handle().no_focus) @@ -996,7 +1000,28 @@ static bool checkFlag(Interactive& i,WorldObjects::SearchFlg f){ } template -bool canSee(const Npc&,const T&){ return true; } +static bool checkTargetType(T&, TargetType) { return true; } + +static bool checkTargetType(Npc& n, TargetType t) { + if(bool(t&(TARGET_TYPE_ALL|TARGET_TYPE_NPCS))) + return true; + Guild gil = Guild(n.trueGuild()); + if(bool(t&TARGET_TYPE_HUMANS) && gilGIL_SEPERATOR_ORC) + return true; + if(bool(t&TARGET_TYPE_UNDEAD)) { + if(gil == GIL_GOBBO_SKELETON || gil == GIL_SUMMONED_GOBBO_SKELETON || + gil == GIL_SKELETON || gil == GIL_SUMMONED_SKELETON || + gil == GIL_SKELETON_MAGE || gil == GIL_SHADOWBEAST_SKELETON || + gil == GIL_ZOMBIE) + return true; + } + return false; + } + +template +static bool canSee(const Npc&,const T&){ return true; } static bool canSee(const Npc& pl, const Npc& n){ return pl.canSeeNpc(n,true); @@ -1049,6 +1074,8 @@ bool WorldObjects::testObj(T &src, const Npc &pl, const WorldObjects::SearchOpt if(!checkFlag(npc,opt.flags)) return false; + if(!checkTargetType(npc,opt.collectType)) + return false; float l = pl.qDistTo(npc); if(l>qmax || l