Skip to content

Commit

Permalink
Fix some Gothic 1 magic bugs (#682)
Browse files Browse the repository at this point in the history
* restrict spell targets

* add a perc check for collision effects

* mark some spells as non-projectile

* fix type error

* address review
  • Loading branch information
thokkat authored Sep 9, 2024
1 parent 7d5810d commit 221ca57
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 28 deletions.
15 changes: 12 additions & 3 deletions game/graphics/effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 10 additions & 1 deletion game/world/objects/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
35 changes: 20 additions & 15 deletions game/world/world.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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:
Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion game/world/world.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
41 changes: 34 additions & 7 deletions game/world/worldobjects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -968,16 +972,16 @@ void WorldObjects::setMobState(std::string_view scheme, int32_t st) {
}

template<class T>
T& deref(std::unique_ptr<T>& x){ return *x; }
static T& deref(std::unique_ptr<T>& x){ return *x; }

template<class T>
T& deref(T* x){ return *x; }
static T& deref(T* x){ return *x; }

template<class T>
T& deref(T& x){ return x; }
static T& deref(T& x){ return x; }

template<class T>
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)
Expand All @@ -996,7 +1000,28 @@ static bool checkFlag(Interactive& i,WorldObjects::SearchFlg f){
}

template<class T>
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) && gil<GIL_SEPERATOR_HUM)
return true;
if(bool(t&TARGET_TYPE_ORCS) && gil>GIL_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<class T>
static bool canSee(const Npc&,const T&){ return true; }

static bool canSee(const Npc& pl, const Npc& n){
return pl.canSeeNpc(n,true);
Expand Down Expand Up @@ -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<qmin)
Expand Down
3 changes: 2 additions & 1 deletion game/world/worldobjects.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ class WorldObjects final {

struct SearchOpt final {
SearchOpt()=default;
SearchOpt(float rangeMin, float rangeMax, float azi, TargetCollect collectAlgo=TARGET_COLLECT_CASTER, SearchFlg flags=NoFlg);
SearchOpt(float rangeMin, float rangeMax, float azi, TargetCollect collectAlgo=TARGET_COLLECT_CASTER, TargetType collectType=TARGET_TYPE_ALL, SearchFlg flags=NoFlg);
float rangeMin = 0;
float rangeMax = 0;
float azi = 0;
TargetCollect collectAlgo = TARGET_COLLECT_CASTER;
TargetType collectType = TARGET_TYPE_ALL;
SearchFlg flags = NoFlg;
};

Expand Down

0 comments on commit 221ca57

Please sign in to comment.