From 4573518ac54f0a1913965b24b5dc0538c8c5d149 Mon Sep 17 00:00:00 2001 From: thokkat Date: Fri, 6 Sep 2024 17:25:52 +0200 Subject: [PATCH 1/5] restrict spell targets --- game/world/world.cpp | 35 ++++++++++++++++++++--------------- game/world/world.h | 2 +- game/world/worldobjects.cpp | 31 +++++++++++++++++++++++++++++-- game/world/worldobjects.h | 3 ++- 4 files changed, 52 insertions(+), 19 deletions(-) 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..ffa0ae12f 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; @@ -995,6 +999,27 @@ static bool checkFlag(Interactive& i,WorldObjects::SearchFlg f){ return true; } +template +bool checkTargetType(T&, TargetType) { return true; } + +static bool checkTargetType(Npc& n, TargetType t) { + if(bool(t&(TARGET_TYPE_ALL|TARGET_TYPE_NPCS))) + return true; + int32_t gil = 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 bool canSee(const Npc&,const T&){ return 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 Date: Fri, 6 Sep 2024 17:25:52 +0200 Subject: [PATCH 2/5] add a perc check for collision effects --- game/graphics/effect.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/game/graphics/effect.cpp b/game/graphics/effect.cpp index 046af2be4..94ee50ae8 100644 --- a/game/graphics/effect.cpp +++ b/game/graphics/effect.cpp @@ -312,9 +312,15 @@ void Effect::onCollide(World& world, const VisualFx* root, const Vec3& pos, Npc* eff.setOrigin(other); eff.setActive(true); - if(npc!=nullptr) - npc ->runEffect(std::move(eff)); else + if(npc!=nullptr) { + npc ->runEffect(std::move(eff)); + if(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) { From 18dd6baf683d801642409e8cc845c39bf589131d Mon Sep 17 00:00:00 2001 From: thokkat Date: Fri, 6 Sep 2024 17:25:52 +0200 Subject: [PATCH 3/5] mark some spells as non-projectile --- game/world/objects/item.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/game/world/objects/item.cpp b/game/world/objects/item.cpp index 46ba1547d..a312d934e 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,19 @@ 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; + if(g1 && spellId()==47) + 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 { From 9c570c80c8e44bf1cea7f04cb572bec21c4011bc Mon Sep 17 00:00:00 2001 From: thokkat Date: Fri, 6 Sep 2024 20:19:38 +0200 Subject: [PATCH 4/5] fix type error --- game/world/worldobjects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index ffa0ae12f..7795cf27d 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -1005,7 +1005,7 @@ bool checkTargetType(T&, TargetType) { return true; } static bool checkTargetType(Npc& n, TargetType t) { if(bool(t&(TARGET_TYPE_ALL|TARGET_TYPE_NPCS))) return true; - int32_t gil = n.trueGuild(); + Guild gil = Guild(n.trueGuild()); if(bool(t&TARGET_TYPE_HUMANS) && gilGIL_SEPERATOR_ORC) From 1b62d5b5c28f0e888942e2ddf1f375130c173e29 Mon Sep 17 00:00:00 2001 From: thokkat Date: Sun, 8 Sep 2024 16:43:01 +0200 Subject: [PATCH 5/5] address review --- game/graphics/effect.cpp | 7 +++++-- game/world/objects/item.cpp | 5 +++-- game/world/worldobjects.cpp | 12 ++++++------ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/game/graphics/effect.cpp b/game/graphics/effect.cpp index 94ee50ae8..558ee0ba9 100644 --- a/game/graphics/effect.cpp +++ b/game/graphics/effect.cpp @@ -306,6 +306,9 @@ 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); @@ -314,7 +317,7 @@ void Effect::onCollide(World& world, const VisualFx* root, const Vec3& pos, Npc* if(npc!=nullptr) { npc ->runEffect(std::move(eff)); - if(vfx->sendAssessMagic) { + if(!g2 && vfx->sendAssessMagic) { auto oth = other==nullptr ? npc : other; npc->perceptionProcess(*oth,npc,0,PERC_ASSESSMAGIC); } @@ -323,7 +326,7 @@ void Effect::onCollide(World& world, const VisualFx* root, const Vec3& pos, Npc* } } - 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 a312d934e..c65338daa 100644 --- a/game/world/objects/item.cpp +++ b/game/world/objects/item.cpp @@ -210,8 +210,9 @@ bool Item::isSpellShoot() const { // 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; - if(g1 && spellId()==47) + 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 && diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index 7795cf27d..acaaed320 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -972,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) @@ -1000,7 +1000,7 @@ static bool checkFlag(Interactive& i,WorldObjects::SearchFlg f){ } template -bool checkTargetType(T&, TargetType) { 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))) @@ -1021,7 +1021,7 @@ static bool checkTargetType(Npc& n, TargetType t) { } template -bool canSee(const Npc&,const T&){ return true; } +static bool canSee(const Npc&,const T&){ return true; } static bool canSee(const Npc& pl, const Npc& n){ return pl.canSeeNpc(n,true);