From 319d9caa7af20a9505c799075a9582d29a008f80 Mon Sep 17 00:00:00 2001 From: Try Date: Mon, 23 Feb 2026 19:02:58 +0100 Subject: [PATCH 01/19] Npc::position -> ::centerPosition in simple cases #605 --- game/game/gamescript.cpp | 2 +- game/world/world.cpp | 4 ++-- game/world/worldobjects.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/game/game/gamescript.cpp b/game/game/gamescript.cpp index 3cbdaec57..dbf75cf24 100644 --- a/game/game/gamescript.cpp +++ b/game/game/gamescript.cpp @@ -3384,7 +3384,7 @@ void GameScript::snd_play3d(std::shared_ptr npcRef, std::string_vi return; for(auto& c:file) c = char(std::toupper(c)); - auto sfx = ::Sound(*owner.world(),::Sound::T_3D,file,npc->position(),0.f,false); + auto sfx = ::Sound(*owner.world(),::Sound::T_3D,file,npc->centerPosition(),0.f,false); sfx.play(); } diff --git a/game/world/world.cpp b/game/world/world.cpp index ef8b35d83..470850b52 100644 --- a/game/world/world.cpp +++ b/game/world/world.cpp @@ -723,8 +723,8 @@ void World::sendImmediatePerc(Npc& self, Npc& other, Npc& victim, Item& item, in } Sound World::addWeaponHitEffect(Npc& src, const Bullet* srcArrow, Npc& reciver) { - auto p0 = src.position(); - auto p1 = reciver.position(); + auto p0 = src.centerPosition(); + auto p1 = reciver.centerPosition(); Tempest::Matrix4x4 pos; pos.identity(); diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index 2770bc864..1b4427a2c 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -373,7 +373,7 @@ void WorldObjects::removeNpc(Npc& npc) { void WorldObjects::tickNear(uint64_t /*dt*/) { for(Npc* i:npcNear) { - auto pos = i->position() + Vec3(0,i->translateY(),0); + auto pos = i->centerPosition(); for(CollisionZone* z:collisionZn) if(z->checkPos(pos)) z->onIntersect(*i); From 281563aeb1a8e3715925024353ecf39efb0a526d Mon Sep 17 00:00:00 2001 From: Try Date: Mon, 23 Feb 2026 20:30:05 +0100 Subject: [PATCH 02/19] more sound cases --- game/world/objects/npc.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index b8dcf208e..a6930b955 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -1775,22 +1775,25 @@ void Npc::implSetFightMode(const Animation::EvCount& ev) { if(ev.weaponCh==zenkit::MdsFightMode::NONE && (ws==WeaponState::W1H || ws==WeaponState::W2H)) { if(auto melee = invent.currentMeleeWeapon()) { + auto at = centerPosition(); if(melee->handle().material==ItemMaterial::MAT_METAL) - sfxWeapon = ::Sound(owner,::Sound::T_Regular,"UNDRAWSOUND_ME.WAV",{x,y+translateY(),z},2500,false); else - sfxWeapon = ::Sound(owner,::Sound::T_Regular,"UNDRAWSOUND_WO.WAV",{x,y+translateY(),z},2500,false); + sfxWeapon = ::Sound(owner,::Sound::T_Regular,"UNDRAWSOUND_ME.WAV",at,2500,false); else + sfxWeapon = ::Sound(owner,::Sound::T_Regular,"UNDRAWSOUND_WO.WAV",at,2500,false); sfxWeapon.play(); } } else if(ev.weaponCh==zenkit::MdsFightMode::SINGLE_HANDED || ev.weaponCh==zenkit::MdsFightMode::DUAL_HANDED) { if(auto melee = invent.currentMeleeWeapon()) { + auto at = centerPosition(); if(melee->handle().material==ItemMaterial::MAT_METAL) - sfxWeapon = ::Sound(owner,::Sound::T_Regular,"DRAWSOUND_ME.WAV",{x,y+translateY(),z},2500,false); else - sfxWeapon = ::Sound(owner,::Sound::T_Regular,"DRAWSOUND_WO.WAV",{x,y+translateY(),z},2500,false); + sfxWeapon = ::Sound(owner,::Sound::T_Regular,"DRAWSOUND_ME.WAV",at,2500,false); else + sfxWeapon = ::Sound(owner,::Sound::T_Regular,"DRAWSOUND_WO.WAV",at,2500,false); sfxWeapon.play(); } } else if(ev.weaponCh==zenkit::MdsFightMode::BOW || ev.weaponCh==zenkit::MdsFightMode::CROSSBOW) { - sfxWeapon = ::Sound(owner,::Sound::T_Regular,"DRAWSOUND_BOW",{x,y+translateY(),z},2500,false); + auto at = centerPosition(); + sfxWeapon = ::Sound(owner,::Sound::T_Regular,"DRAWSOUND_BOW",at,2500,false); sfxWeapon.play(); } dropTorch(); @@ -2948,7 +2951,7 @@ void Npc::setAiOutputBarrier(uint64_t dt, bool overlay) { } void Npc::emitSoundEffect(std::string_view sound, float range, bool freeSlot) { - auto sfx = ::Sound(owner,::Sound::T_Regular,sound,{x,y+translateY(),z},range,freeSlot); + auto sfx = ::Sound(owner,::Sound::T_Regular,sound,centerPosition(),range,freeSlot); sfx.play(); } From 185cb94987c16d07a3eb872d5a06d894e7da0c02 Mon Sep 17 00:00:00 2001 From: Try Date: Mon, 23 Feb 2026 22:27:39 +0100 Subject: [PATCH 03/19] Npc::position -> ::centerPosition --- game/game/playercontrol.cpp | 4 ++-- game/graphics/effect.cpp | 2 +- game/world/collisionzone.cpp | 4 ++-- game/world/objects/interactive.cpp | 4 ++-- game/world/objects/npc.cpp | 9 ++++----- game/world/waymatrix.cpp | 4 ++-- game/world/waypoint.cpp | 9 +++------ game/world/waypoint.h | 4 ++-- game/world/world.cpp | 13 ++++--------- game/world/worldobjects.cpp | 2 +- 10 files changed, 23 insertions(+), 32 deletions(-) diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 824b6002d..431b276ce 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -358,7 +358,7 @@ void PlayerControl::moveFocus(FocusAction act) { return; auto vp = c->viewProj(); - auto pos = currentFocus.npc->position()+Tempest::Vec3(0,currentFocus.npc->translateY(),0); + auto pos = currentFocus.npc->centerPosition(); vp.project(pos); Npc* next = nullptr; @@ -367,7 +367,7 @@ void PlayerControl::moveFocus(FocusAction act) { auto npc = w->npcById(i); if(npc->isPlayer()) continue; - auto p = npc->position()+Tempest::Vec3(0,npc->translateY(),0); + auto p = npc->centerPosition(); vp.project(p); if(std::abs(p.x)>1.f || std::abs(p.y)>1.f || p.z<0.f) diff --git a/game/graphics/effect.cpp b/game/graphics/effect.cpp index 455222699..e6f31a81e 100644 --- a/game/graphics/effect.cpp +++ b/game/graphics/effect.cpp @@ -16,7 +16,7 @@ Effect::Effect(PfxEmitter&& pfx, std::string_view node) } Effect::Effect(const VisualFx& vfx, World& owner, const Npc& src, SpellFxKey key) - :Effect(vfx, owner, src.position(), key) { + :Effect(vfx, owner, src.centerPosition(), key) { } Effect::Effect(const VisualFx& v, World& owner, const Vec3& inPos, SpellFxKey k) { diff --git a/game/world/collisionzone.cpp b/game/world/collisionzone.cpp index c04fa7387..787847d87 100644 --- a/game/world/collisionzone.cpp +++ b/game/world/collisionzone.cpp @@ -107,8 +107,8 @@ void CollisionZone::onIntersect(Npc& npc) { void CollisionZone::tick(uint64_t /*dt*/) { for(size_t i=0;iMAX_AI_USE_DISTANCE*MAX_AI_USE_DISTANCE) { + if((npc.centerPosition()-mv).quadLength()>MAX_AI_USE_DISTANCE*MAX_AI_USE_DISTANCE) { if(npc.isPlayer()) { auto& sc = npc.world().script(); sc.printMobTooFar(npc); diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index a6930b955..ba6455544 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -42,7 +42,7 @@ void Npc::GoTo::load(Serialize& fin) { Vec3 Npc::GoTo::target() const { if(npc!=nullptr) - return npc->position() + Vec3(0, npc->translateY(), 0); + return npc->centerPosition(); if(wp!=nullptr) return wp->position(); return pos; @@ -704,7 +704,7 @@ std::string_view Npc::formerPortalName() { } float Npc::qDistTo(const Vec3 pos) const { - auto dp = pos - Vec3(x,y+translateY(),z); + auto dp = pos - centerPosition(); return dp.quadLength(); } @@ -715,7 +715,7 @@ float Npc::qDistTo(const WayPoint *f) const { } float Npc::qDistTo(const Npc &p) const { - return qDistTo(Vec3(p.x,p.y+p.translateY(),p.z)); + return qDistTo(p.centerPosition()); } float Npc::qDistTo(const Interactive &p) const { @@ -3257,8 +3257,7 @@ Item* Npc::takeItem(Item& item) { return nullptr; } - auto dpos = item.position()-position(); - dpos.y-=translateY(); + auto dpos = item.position()-centerPosition(); const Animation::Sequence* sq = setAnimAngGet(Npc::Anim::ItmGet,Pose::calcAniCombVert(dpos)); if(sq==nullptr) return nullptr; diff --git a/game/world/waymatrix.cpp b/game/world/waymatrix.cpp index 3526e6085..c0d6173e2 100644 --- a/game/world/waymatrix.cpp +++ b/game/world/waymatrix.cpp @@ -196,8 +196,8 @@ void WayMatrix::adjustWaypoints(std::vector &wp) { for(auto& w:wp) { auto ray = world.physic()->landRay(w.position()); if(ray.hasCol) { - //NOTE: probably doesn't match original game and need to be removed - w.pos.y = ray.v.y; + //NOTE: what about water? + w.groundPos = ray.v; } indexPoints.push_back(&w); } diff --git a/game/world/waypoint.cpp b/game/world/waypoint.cpp index 42bd1abf0..2e36b023a 100644 --- a/game/world/waypoint.cpp +++ b/game/world/waypoint.cpp @@ -57,15 +57,12 @@ Vec3 WayPoint::direction() const { return dir; } -float WayPoint::qDistTo(float ix, float iy, float iz) const { - float dx = pos.x-ix; - float dy = pos.y-iy; - float dz = pos.z-iz; - return dx*dx+dy*dy+dz*dz; +float WayPoint::qDistTo(const Tempest::Vec3& to) const { + return (pos-to).quadLength(); } void WayPoint::connect(WayPoint &w) { - int32_t l = int32_t(std::sqrt(qDistTo(w.pos.x,w.pos.y,w.pos.z))); + int32_t l = int32_t(std::sqrt(qDistTo(w.pos))); if(l<=0) return; Conn c; diff --git a/game/world/waypoint.h b/game/world/waypoint.h index 7bba904d7..0f7625762 100644 --- a/game/world/waypoint.h +++ b/game/world/waypoint.h @@ -28,7 +28,7 @@ class WayPoint final { Tempest::Vec3 position () const; Tempest::Vec3 direction() const; - Tempest::Vec3 pos; + Tempest::Vec3 pos, groundPos; Tempest::Vec3 dir; bool underWater = false; bool freePoint = false; @@ -44,7 +44,7 @@ class WayPoint final { mutable int32_t pathLen = std::numeric_limits::max(); mutable uint16_t pathGen = 0; - float qDistTo(float x,float y,float z) const; + float qDistTo(const Tempest::Vec3& to) const; void connect(WayPoint& w); const std::vector& connections() const { return conn; } diff --git a/game/world/world.cpp b/game/world/world.cpp index 470850b52..fd0dad53e 100644 --- a/game/world/world.cpp +++ b/game/world/world.cpp @@ -904,9 +904,8 @@ const WayPoint *World::findFreePoint(const Npc &npc, std::string_view name) cons return p; } } - auto pos = npc.position(); - pos.y+=npc.translateY(); + const auto pos = npc.centerPosition(); return wmatrix->findFreePoint(pos,name,[&npc](const WayPoint& wp) -> bool { if(wp.isLocked()) return false; @@ -925,8 +924,7 @@ const WayPoint *World::findFreePoint(const Tempest::Vec3& pos, std::string_view } const WayPoint *World::findNextFreePoint(const Npc &npc, std::string_view name) const { - auto pos = npc.position(); - pos.y+=npc.translateY(); + auto pos = npc.centerPosition(); auto cur = npc.currentWayPoint(); if(cur!=nullptr && !cur->checkName(name)) { cur = nullptr; @@ -943,9 +941,7 @@ const WayPoint *World::findNextFreePoint(const Npc &npc, std::string_view name) } const WayPoint* World::findNextWayPoint(const Npc &npc) const { - auto pos = npc.position(); - pos.y+=npc.translateY(); - + auto pos = npc.centerPosition(); auto nearest = npc.currentWayPoint(); if(nearest==nullptr || nearest->isFreePoint()) { nearest = findWayPoint(pos); @@ -983,8 +979,7 @@ void World::detectItem(const Tempest::Vec3& p, const float r, const std::functio } WayPath World::wayTo(const Npc &npc, const WayPoint &end) const { - auto npcPos = npc.position(); - npcPos.y += npc.translateY(); + const auto npcPos = npc.centerPosition(); auto begin = npc.currentWayPoint(); if(begin==&end && MoveAlgo::isClose(npc,end)) { diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index 1b4427a2c..7a4abcc3d 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -340,7 +340,7 @@ Npc* WorldObjects::insertPlayer(std::unique_ptr &&npc, std::string_view at) if(p) pos=p; } - npc->setPosition (pos->position() ); + npc->setPosition (pos->groundPos ); npc->setDirection (pos->direction()); npc->attachToPoint(pos); npc->updateTransform(); From fa4cdd6f83ee631e33fe6762a5e5fef9f516e811 Mon Sep 17 00:00:00 2001 From: Try Date: Thu, 12 Mar 2026 21:52:26 +0100 Subject: [PATCH 04/19] more bbox draws and ray-mobsi intersection #182 --- game/game/playercontrol.cpp | 11 ++++ game/game/playercontrol.h | 3 + game/mainwindow.cpp | 1 + game/physics/dynamicworld.cpp | 40 ++++++++++++ game/physics/dynamicworld.h | 5 ++ game/utils/dbgpainter.cpp | 27 ++++++++- game/utils/dbgpainter.h | 3 +- game/world/objects/interactive.cpp | 97 ++++++++++++++++++++++++------ game/world/objects/interactive.h | 9 +-- game/world/objects/npc.cpp | 10 +++ game/world/worldobjects.cpp | 10 +++ 11 files changed, 192 insertions(+), 24 deletions(-) diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 431b276ce..19823fa8c 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -252,6 +252,17 @@ void PlayerControl::onRotateMouse(float dAngleX, float dAngleY) { rotMouseY += dAngleY; } +void PlayerControl::drawVobBox(DbgPainter& p) const { + auto w = Gothic::inst().world(); + if(w==nullptr || w->player()==nullptr) + return; + auto pl = w->player(); + auto focus = currentFocus; + if(focus.interactive!=nullptr) { + focus.interactive->drawVobRay(p, *pl); + } + } + void PlayerControl::tickFocus() { currentFocus = findFocus(¤tFocus); setTarget(currentFocus.npc); diff --git a/game/game/playercontrol.h b/game/game/playercontrol.h index 3b6106263..ac69515cb 100644 --- a/game/game/playercontrol.h +++ b/game/game/playercontrol.h @@ -8,6 +8,7 @@ class DialogMenu; class InventoryMenu; +class DbgPainter; class World; class Interactive; class Npc; @@ -25,6 +26,8 @@ class PlayerControl final { bool isPressed(KeyCodec::Action a) const; void onRotateMouse(float dAngleX, float dAngleY); + void drawVobBox(DbgPainter& p) const; + void changeZoom(int delta); void tickFocus(); void clearFocus(); diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index b3856ef8b..0406d15f7 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -278,6 +278,7 @@ void MainWindow::paintEvent(PaintEvent& event) { if(world!=nullptr && c!=nullptr) { DbgPainter dbg(p,c->viewProj(),w(),h()); world->drawVobBoxNpcNear(dbg); + player.drawVobBox(dbg); } } diff --git a/game/physics/dynamicworld.cpp b/game/physics/dynamicworld.cpp index cfda0b1b8..1657dd650 100644 --- a/game/physics/dynamicworld.cpp +++ b/game/physics/dynamicworld.cpp @@ -921,6 +921,46 @@ float DynamicWorld::materialDensity(zenkit::MaterialGroup mat) { return 2000.f; } +float DynamicWorld::rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, const float TMax, + const Tempest::Matrix4x4& obj, const Tempest::Vec3& min, const Tempest::Vec3& max) { + using namespace Tempest; + + auto tmp = obj; + tmp.inverse(); + + auto tOri = orig; + auto tDir = dir; + auto zero = 0.f; + tmp.project(tOri); + tmp.project(tDir.x, tDir.y, tDir.z, zero); + + float tHit = rayBox(tOri, Vec3(tDir.x, tDir.y, tDir.z), TMax, min, max); + if(tHit==TMax) + return TMax; + + //NOTE: worry about non-uniform scale matrix + tDir *= tHit; zero = 0; + tmp.project(tDir.x, tDir.y, tDir.z, zero); + return tDir.length(); + } + +float DynamicWorld::rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, const float TMax, + const Tempest::Vec3& boxMin, const Tempest::Vec3& boxMax) { + using namespace Tempest; + + Vec3 invDir = Vec3(1.f/dir.x, 1.f/dir.y, 1.f/dir.z); + + Vec3 tMin = (boxMin - orig)*invDir; + Vec3 tMax = (boxMax - orig)*invDir; + Vec3 t1 = Vec3{std::min(tMin.x, tMax.x), std::min(tMin.y, tMax.y), std::min(tMin.z, tMax.z)}; + Vec3 t2 = Vec3{std::max(tMin.x, tMax.x), std::max(tMin.y, tMax.y), std::max(tMin.z, tMax.z)}; + + float tNear = std::max(0.f, std::max(t1.x, std::max(t1.y, t1.z))); + float tFar = std::min(TMax, std::min(t2.x, std::min(t2.y, t2.z))); + + return tNear > tFar ? TMax : tNear; + } + std::string_view DynamicWorld::validateSectorName(std::string_view name) const { return landMesh->validateSectorName(name); } diff --git a/game/physics/dynamicworld.h b/game/physics/dynamicworld.h index c135abab6..e78dc4ece 100644 --- a/game/physics/dynamicworld.h +++ b/game/physics/dynamicworld.h @@ -258,6 +258,11 @@ class DynamicWorld final { static float materialFriction(zenkit::MaterialGroup mat); static float materialDensity (zenkit::MaterialGroup mat); + static float rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, const float TMax, + const Tempest::Matrix4x4& obj, const Tempest::Vec3& min, const Tempest::Vec3& max); + static float rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, const float TMax, + const Tempest::Vec3& min, const Tempest::Vec3& max); + std::string_view validateSectorName(std::string_view name) const; private: diff --git a/game/utils/dbgpainter.cpp b/game/utils/dbgpainter.cpp index 45517ef9d..f67a6e7d5 100644 --- a/game/utils/dbgpainter.cpp +++ b/game/utils/dbgpainter.cpp @@ -5,8 +5,8 @@ using namespace Tempest; -DbgPainter::DbgPainter(Painter& painter, const Tempest::Matrix4x4& mvp, int w, int h) - :painter(painter), mvp(mvp), w(w), h(h) { +DbgPainter::DbgPainter(Painter& painter, const Tempest::Matrix4x4& vp, int w, int h) + :painter(painter), mvp(vp), w(w), h(h) { } void DbgPainter::setBrush(const Brush& brush) { @@ -86,3 +86,26 @@ void DbgPainter::drawAabb(const Tempest::Vec3& min, const Tempest::Vec3& max) { drawLine(Tempest::Vec3(max.x, min.y, max.z), Tempest::Vec3(max.x, max.y, max.z)); drawLine(Tempest::Vec3(min.x, min.y, max.z), Tempest::Vec3(min.x, max.y, max.z)); } + +void DbgPainter::drawObb(const Tempest::Matrix4x4& m, const Tempest::Vec3& min, const Tempest::Vec3& max) { + auto line = [&](Vec3 a, Vec3 b) { + m.project(a); + m.project(b); + drawLine(a, b); + }; + + line(Tempest::Vec3(min.x, min.y, min.z), Tempest::Vec3(max.x, min.y, min.z)); + line(Tempest::Vec3(max.x, min.y, min.z), Tempest::Vec3(max.x, min.y, max.z)); + line(Tempest::Vec3(max.x, min.y, max.z), Tempest::Vec3(min.x, min.y, max.z)); + line(Tempest::Vec3(min.x, min.y, max.z), Tempest::Vec3(min.x, min.y, min.z)); + + line(Tempest::Vec3(min.x, max.y, min.z), Tempest::Vec3(max.x, max.y, min.z)); + line(Tempest::Vec3(max.x, max.y, min.z), Tempest::Vec3(max.x, max.y, max.z)); + line(Tempest::Vec3(max.x, max.y, max.z), Tempest::Vec3(min.x, max.y, max.z)); + line(Tempest::Vec3(min.x, max.y, max.z), Tempest::Vec3(min.x, max.y, min.z)); + + line(Tempest::Vec3(min.x, min.y, min.z), Tempest::Vec3(min.x, max.y, min.z)); + line(Tempest::Vec3(max.x, min.y, min.z), Tempest::Vec3(max.x, max.y, min.z)); + line(Tempest::Vec3(max.x, min.y, max.z), Tempest::Vec3(max.x, max.y, max.z)); + line(Tempest::Vec3(min.x, min.y, max.z), Tempest::Vec3(min.x, max.y, max.z)); + } diff --git a/game/utils/dbgpainter.h b/game/utils/dbgpainter.h index 18cb3d87f..5baca3ba9 100644 --- a/game/utils/dbgpainter.h +++ b/game/utils/dbgpainter.h @@ -5,7 +5,7 @@ class DbgPainter { public: - DbgPainter(Tempest::Painter& painter, const Tempest::Matrix4x4& mvp, int w, int h); + DbgPainter(Tempest::Painter& painter, const Tempest::Matrix4x4& vp, int w, int h); void setBrush(const Tempest::Brush& brush); void setPen (const Tempest::Pen& pen); @@ -16,6 +16,7 @@ class DbgPainter { void drawLine(const Tempest::Vec3& a, const Tempest::Vec3& b); void drawPoint(const Tempest::Vec3& a, int radiusPx = 5); void drawAabb(const Tempest::Vec3& min, const Tempest::Vec3& max); + void drawObb (const Tempest::Matrix4x4& m, const Tempest::Vec3& min, const Tempest::Vec3& max); Tempest::Painter& painter; const Tempest::Matrix4x4 mvp; diff --git a/game/world/objects/interactive.cpp b/game/world/objects/interactive.cpp index 987451be3..80a37c21a 100644 --- a/game/world/objects/interactive.cpp +++ b/game/world/objects/interactive.cpp @@ -178,6 +178,52 @@ void Interactive::postValidate() { animChanged = true; } +void Interactive::drawVobBox(DbgPainter& p) const { + p.setPen(Tempest::Color(1,0,0)); + //p.drawAabb(bbox[0], bbox[1]); + if(auto mesh = visual.protoMesh()) { + if(auto* skeleton = mesh->skeleton.get()) { + auto bbox = skeleton->bboxCol; + p.drawObb(transform(), bbox[0]*2, bbox[1]*2); + } + } + + p.setBrush(Tempest::Color(0,0,1)); + for(auto& i:attPos){ + auto mat = nodeTranform(i.name); + float x = mat.at(3,0); + float y = mat.at(3,1); + float z = mat.at(3,2); + p.drawPoint({x,y,z}); + } + } + +void Interactive::drawVobRay(DbgPainter& p, const Npc& npc) const { + auto cen = npc.centerPosition(); + + if(auto mesh = visual.protoMesh()) { + if(auto* skeleton = mesh->skeleton.get()) { + auto boxMin = skeleton->bboxCol[0] * 2.f; + auto boxMax = skeleton->bboxCol[1] * 2.f; + auto at = (boxMin+boxMax)*0.5f; + transform().project(at); + + auto tMax = (at - cen).length(); + auto dir = Tempest::Vec3::normalize(at - cen); + float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), boxMin, boxMax); + + bool accessable = true; + if(!npc.canRayHitPoint(cen+dir*tHit, true)) + accessable = false; + p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); + p.drawLine(cen, cen+dir*tHit); + + p.setPen(Tempest::Color(1,1,0)); + p.drawLine(cen+dir*tHit, at); + } + } + } + void Interactive::resetPositionToTA(int32_t state) { for(auto& i:attPos) if(i.user!=nullptr && i.user->isPlayer()) @@ -539,6 +585,24 @@ uint32_t Interactive::stateMask() const { } bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { + auto cen = npc.centerPosition(); + + if(auto mesh = visual.protoMesh()) { + if(auto* skeleton = mesh->skeleton.get()) { + auto boxMin = skeleton->bboxCol[0] * 2.f; + auto boxMax = skeleton->bboxCol[1] * 2.f; + auto at = (boxMin+boxMax)*0.5f; + transform().project(at); + + auto tMax = (at - cen).length(); + auto dir = (at - cen)/tMax; + float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), boxMin, boxMax); + + if(!npc.canRayHitPoint(cen+dir*tHit, true)) + return false; + } + } + for(auto& i:attPos){ auto pos = nodePosition(npc,i); if(npc.canSeeNpc(pos,freeLos)) @@ -732,10 +796,9 @@ bool Interactive::attach(Npc& npc, Interactive::Pos& to) { assert(to.user==nullptr); auto mat = nodeTranform(npc,to); - float x=0, y=0, z=0; - mat.project(x,y,z); - const Tempest::Vec3 mv = {x,y,z}; + Tempest::Vec3 mv = {}; + mat.project(mv); if((npc.centerPosition()-mv).quadLength()>MAX_AI_USE_DISTANCE*MAX_AI_USE_DISTANCE) { if(npc.isPlayer()) { @@ -860,7 +923,19 @@ float Interactive::qDistTo(const Npc &npc, const Interactive::Pos &to) const { return npc.qDistTo(p); } -Tempest::Matrix4x4 Interactive::nodeTranform(const Npc &npc, const Pos& p) const { +Tempest::Matrix4x4 Interactive::nodeTranform(std::string_view nodeName) const { + auto mesh = visual.protoMesh(); + if(mesh==nullptr || mesh->skeleton==nullptr) + return Tempest::Matrix4x4(); + + auto id = mesh->skeleton->findNode(nodeName); + auto ret = transform(); + if(id!=size_t(-1)) + ret = visual.bone(id); + return ret; + } + +Tempest::Matrix4x4 Interactive::nodeTranform(const Npc& npc, const Pos& p) const { auto mesh = visual.protoMesh(); if(mesh==nullptr) return Tempest::Matrix4x4(); @@ -903,7 +978,7 @@ Tempest::Matrix4x4 Interactive::nodeTranform(const Npc &npc, const Pos& p) const return transform(); } -Tempest::Vec3 Interactive::nodePosition(const Npc &npc, const Pos &p) const { +Tempest::Vec3 Interactive::nodePosition(const Npc& npc, const Pos &p) const { auto mat = nodeTranform(npc,p); float x = mat.at(3,0); float y = mat.at(3,1); @@ -911,18 +986,6 @@ Tempest::Vec3 Interactive::nodePosition(const Npc &npc, const Pos &p) const { return {x,y,z}; } -Tempest::Matrix4x4 Interactive::nodeTranform(std::string_view nodeName) const { - auto mesh = visual.protoMesh(); - if(mesh==nullptr || mesh->skeleton==nullptr) - return Tempest::Matrix4x4(); - - auto id = mesh->skeleton->findNode(nodeName); - auto ret = transform(); - if(id!=size_t(-1)) - ret = visual.bone(id); - return ret; - } - const Animation::Sequence* Interactive::setAnim(Interactive::Anim t) { int dir = (t==Anim::Out || t==Anim::ToStand) ? -1 : 1; int st[] = {state,state+dir}; diff --git a/game/world/objects/interactive.h b/game/world/objects/interactive.h index 8a344e880..96d69f0b0 100644 --- a/game/world/objects/interactive.h +++ b/game/world/objects/interactive.h @@ -32,6 +32,9 @@ class Interactive : public Vob { void save(Serialize& fout) const override; void postValidate(); + void drawVobBox(DbgPainter& p) const; + void drawVobRay(DbgPainter& p, const Npc& npc) const; + void resetPositionToTA(int32_t state); void updateAnimation(uint64_t dt); void tick(uint64_t dt); @@ -137,8 +140,8 @@ class Interactive : public Vob { Pos* findFreePos(); auto worldPos(const Pos &to) const -> Tempest::Vec3; float qDistTo(const Npc &npc, const Pos &to) const; - Tempest::Matrix4x4 nodeTranform(const Npc &npc, const Pos &p) const; - auto nodePosition(const Npc &npc, const Pos &p) const -> Tempest::Vec3; + Tempest::Matrix4x4 nodeTranform(const Npc& npc, const Pos &p) const; + auto nodePosition(const Npc& npc, const Pos &p) const -> Tempest::Vec3; std::string vobName; std::string focName; @@ -172,7 +175,5 @@ class Interactive : public Vob { bool animChanged = false; std::vector attPos; - PhysicMesh physic; - ObjVisual visual; }; diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index ba6455544..ac8739790 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -14,6 +14,7 @@ #include "world/world.h" #include "utils/versioninfo.h" #include "utils/fileext.h" +#include "utils/dbgpainter.h" #include "camera.h" #include "gothic.h" #include "resources.h" @@ -329,6 +330,15 @@ void Npc::postValidate() { void Npc::drawVobBox(DbgPainter& p) const { physic.debugDraw(p); + + if(auto sk = visual.visualSkeleton()) { + auto bbox = sk->bboxCol; + auto tr = transform(); + tr.translate(0,translateY(),0); + + p.setPen(Color(1,0,0)); + p.drawObb(tr, bbox[0]*2, bbox[1]*2); + } } void Npc::saveAiState(Serialize& fout) const { diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index 7a4abcc3d..463a91553 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -830,6 +830,16 @@ void WorldObjects::marchCsCameras(DbgPainter& p) const { void WorldObjects::drawVobBoxNpcNear(DbgPainter& p) const { for(auto& i:npcNear) i->drawVobBox(p); + + auto camera = Gothic::inst().camera(); + const float nearDist = 3000*3000; + for(auto& i:interactiveObj) { + auto bbox = i->bBox(); + auto pos = (bbox[0]+bbox[1])*0.5f; + if((pos-camera->originLwc()).quadLength() > nearDist) + continue; + i->drawVobBox(p); + } } Interactive *WorldObjects::availableMob(const Npc &pl, std::string_view dest) { From 039717c1a59082916bada670479d20acdcd71e5c Mon Sep 17 00:00:00 2001 From: Try Date: Fri, 13 Mar 2026 17:23:45 +0100 Subject: [PATCH 05/19] dbg bbox for items and new pick-item code --- game/game/playercontrol.cpp | 7 +++- game/game/playercontrol.h | 2 +- game/world/objects/item.cpp | 83 ++++++++++++++++++++++++++++++------- game/world/objects/item.h | 5 ++- game/world/objects/npc.cpp | 31 +++++--------- game/world/worldobjects.cpp | 7 ++++ 6 files changed, 97 insertions(+), 38 deletions(-) diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 19823fa8c..27842bb1b 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -257,10 +257,13 @@ void PlayerControl::drawVobBox(DbgPainter& p) const { if(w==nullptr || w->player()==nullptr) return; auto pl = w->player(); - auto focus = currentFocus; + auto focus = findFocus(¤tFocus); if(focus.interactive!=nullptr) { focus.interactive->drawVobRay(p, *pl); } + if(focus.item!=nullptr) { + focus.item->drawVobRay(p, *pl); + } } void PlayerControl::tickFocus() { @@ -492,7 +495,7 @@ void PlayerControl::marvinO() { w->setPlayer(target); } -Focus PlayerControl::findFocus(Focus* prev) { +Focus PlayerControl::findFocus(const Focus* prev) const { auto w = Gothic::inst().world(); auto c = Gothic::inst().camera(); if(w==nullptr) diff --git a/game/game/playercontrol.h b/game/game/playercontrol.h index ac69515cb..871aedd6a 100644 --- a/game/game/playercontrol.h +++ b/game/game/playercontrol.h @@ -164,7 +164,7 @@ class PlayerControl final { void toggleWalkMode(); void toggleSneakMode(); void moveFocus(FocusAction act); - Focus findFocus(Focus *prev); + Focus findFocus(const Focus* prev) const; void clrDraw(); void implMove(uint64_t dt); diff --git a/game/world/objects/item.cpp b/game/world/objects/item.cpp index 515f5c6fa..5fa40ce53 100644 --- a/game/world/objects/item.cpp +++ b/game/world/objects/item.cpp @@ -4,10 +4,12 @@ #include "game/serialize.h" #include "game/gamescript.h" -#include "utils/versioninfo.h" +#include "graphics/mesh/skeleton.h" #include "world/objects/npc.h" #include "world/world.h" +#include "utils/versioninfo.h" #include "utils/fileext.h" +#include "utils/dbgpainter.h" using namespace Tempest; @@ -20,9 +22,9 @@ Item::Item(World &owner, size_t itemInstance, Type type) setCount(1); if(type!=T_Inventory) { - view = world.addView(*hitem); + visual = world.addView(*hitem); if(type==T_WorldDyn) - setPhysicsEnable(view); + setPhysicsEnable(visual); } } @@ -61,19 +63,19 @@ Item::Item(World &owner, Serialize &fin, Type type) if(type!=T_Inventory) { if(!FileExt::hasExt(hitem->visual,"ZEN")) - view = world.addView(*hitem); + visual = world.addView(*hitem); if(type==T_WorldDyn) - setPhysicsEnable(view); + setPhysicsEnable(visual); } setLocalTransform(mat); - view .setObjMatrix(mat); + visual.setObjMatrix(mat); physic.setObjMatrix(mat); } Item::Item(Item &&it) : Vob(it.world), hitem(it.hitem), - pos(it.pos),equipped(it.equipped),itSlot(it.itSlot),view(std::move(it.view)) { + pos(it.pos),equipped(it.equipped),itSlot(it.itSlot),visual(std::move(it.visual)) { setLocalTransform(it.localTransform()); physic = std::move(it.physic); } @@ -81,6 +83,57 @@ Item::Item(Item &&it) Item::~Item() { } +void Item::drawVobBox(DbgPainter& p) const { + p.setPen(Tempest::Color(1,0,0)); + if(auto mesh = visual.protoMesh()) { + if(auto* skeleton = mesh->skeleton.get()) { + auto bbox = skeleton->bboxCol; + p.drawObb(transform(), bbox[0]*2, bbox[1]*2); + } else { + auto bbox = mesh->bbox; + p.drawObb(transform(), bbox[0], bbox[1]); + } + + auto v = (mesh->bbox[0]+mesh->bbox[1])*0.5; + p.setPen(Tempest::Color(0,1,0)); + p.drawPoint(pos+v); + } + } + +void Item::drawVobRay(DbgPainter& p, const Npc& npc) const { + auto cen = npc.centerPosition(); + auto at = midPosition(); + + bool accessable = true; + if(!npc.canRayHitPoint(at, true)) + accessable = false; + p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); + p.drawLine(cen, at); + /* + if(auto mesh = visual.protoMesh()) { + if(auto* skeleton = mesh->skeleton.get()) { + auto boxMin = skeleton->bboxCol[0] * 2.f; + auto boxMax = skeleton->bboxCol[1] * 2.f; + auto at = (boxMin+boxMax)*0.5f; + transform().project(at); + + auto tMax = (at - cen).length(); + auto dir = Tempest::Vec3::normalize(at - cen); + float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), boxMin, boxMax); + + bool accessable = true; + if(!npc.canRayHitPoint(cen+dir*tHit, true)) + accessable = false; + p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); + p.drawLine(cen, cen+dir*tHit); + + p.setPen(Tempest::Color(1,1,0)); + p.drawLine(cen+dir*tHit, at); + } + } + */ + } + void Item::save(Serialize &fout) const { auto& h = *hitem; fout.write(uint32_t(h.symbol_index())); @@ -99,7 +152,7 @@ void Item::save(Serialize &fout) const { } void Item::clearView() { - view = MeshObjects::Mesh(); + visual = MeshObjects::Mesh(); } bool Item::isTorchBurn() const { @@ -119,7 +172,7 @@ void Item::setObjMatrix(const Tempest::Matrix4x4 &m) { pos.y = m.at(3,1); pos.z = m.at(3,2); setLocalTransform(m); - view.setObjMatrix(m); + visual.setObjMatrix(m); } bool Item::isMission() const { @@ -138,7 +191,7 @@ void Item::setAsEquipped(bool e) { } void Item::setPhysicsEnable(World& world) { - setPhysicsEnable(view); + setPhysicsEnable(visual); world.invalidateVobIndex(); } @@ -182,10 +235,12 @@ Tempest::Vec3 Item::position() const { } Vec3 Item::midPosition() const { - auto b = view.bounds(); - auto v = (b.bbox[1]-b.bbox[0])*0.5; + auto b = visual.bounds(); + auto v = (b.bbox[0]+b.bbox[1])*0.5; + transform().project(v); + return v; // transform().project(v); // doesn't work for Karibik mod - return pos + v; + // return pos + v; } bool Item::isGold() const { @@ -322,7 +377,7 @@ void Item::updateMatrix() { } void Item::moveEvent() { - view .setObjMatrix(transform()); + visual.setObjMatrix(transform()); physic.setObjMatrix(transform()); if(!isDynamic()) world.invalidateVobIndex(); diff --git a/game/world/objects/item.h b/game/world/objects/item.h index 050419f6f..0dc5a0fb5 100644 --- a/game/world/objects/item.h +++ b/game/world/objects/item.h @@ -27,6 +27,9 @@ class Item : public Vob { ~Item(); Item& operator=(Item&&)=delete; + void drawVobBox(DbgPainter& p) const; + void drawVobRay(DbgPainter& p, const Npc& npc) const; + void save(Serialize& fout) const override; virtual void clearView(); @@ -100,6 +103,6 @@ class Item : public Vob { uint8_t equipped = 0; uint8_t itSlot = NSLOT; - MeshObjects::Mesh view; + MeshObjects::Mesh visual; DynamicWorld::Item physic; }; diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index ac8739790..99e2d9c05 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -3267,8 +3267,8 @@ Item* Npc::takeItem(Item& item) { return nullptr; } - auto dpos = item.position()-centerPosition(); - const Animation::Sequence* sq = setAnimAngGet(Npc::Anim::ItmGet,Pose::calcAniCombVert(dpos)); + const auto dpos = item.midPosition()-centerPosition(); + const auto* sq = setAnimAngGet(Npc::Anim::ItmGet, Pose::calcAniCombVert(dpos)); if(sq==nullptr) return nullptr; @@ -4501,37 +4501,28 @@ SensesBit Npc::canSenseNpc(const Tempest::Vec3 pos, bool freeLos, bool isNoisy, } bool Npc::canSeeItem(const Item& it, bool freeLos) const { - DynamicWorld* w = owner.physic(); static const double ref = std::cos(100*M_PI/180.0); // spec requires +-100 view angle range const auto itMid = it.midPosition(); + const auto cen = centerPosition(); + const auto dir = itMid - cen; const float range = float(hnpc->senses_range); - if(qDistTo(itMid)>range*range) + + if(dir.quadLength()>range*range) return false; if(!freeLos) { - float dx = x-itMid.x, dz=z-itMid.z; + float dx = dir.x, dz = dir.z; float dir = angleDir(dx,dz); float da = float(M_PI)*(visual.viewDirection()-dir)/180.f; if(double(std::cos(da))>ref) return false; } - // npc eyesight height - auto head = visual.mapHeadBone(); - auto r = w->ray(head,itMid); - auto err = (head-itMid)*(1.f-r.hitFraction); - if(!r.hasCol || err.length()<25.f) { - return true; - } - if(y<=itMid.y && itMid.y<=head.y) { - auto pl = Vec3(head.x,itMid.y,head.z); - r = w->ray(pl,itMid); - err = (pl-itMid)*(1.f-r.hitFraction); - if(!r.hasCol || err.length()<65.f) - return true; - } - return false; + const auto r = owner.physic()->ray(cen, itMid); + if(r.hasCol) + return false; + return true; } bool Npc::isAlignedToGround() const { diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index 463a91553..94947f35d 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -840,6 +840,13 @@ void WorldObjects::drawVobBoxNpcNear(DbgPainter& p) const { continue; i->drawVobBox(p); } + + for(auto& i:items) { + auto pos = i->midPosition(); + if((pos-camera->originLwc()).quadLength() > nearDist) + continue; + i->drawVobBox(p); + } } Interactive *WorldObjects::availableMob(const Npc &pl, std::string_view dest) { From 8a3e223cb8d9c46dfd3c05eaf2c017fa3bef3c27 Mon Sep 17 00:00:00 2001 From: Try Date: Sat, 14 Mar 2026 14:37:05 +0100 Subject: [PATCH 06/19] double bboxCol size - seem to be halfed in assets --- game/graphics/mesh/skeleton.cpp | 7 ++++++- game/physics/dynamicworld.cpp | 7 ++++--- game/utils/dbgpainter.cpp | 4 ++++ game/utils/dbgpainter.h | 1 + game/world/objects/interactive.cpp | 10 +++++----- game/world/objects/item.cpp | 6 ++---- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/game/graphics/mesh/skeleton.cpp b/game/graphics/mesh/skeleton.cpp index 7a54d0889..18f994943 100644 --- a/game/graphics/mesh/skeleton.cpp +++ b/game/graphics/mesh/skeleton.cpp @@ -11,6 +11,10 @@ Skeleton::Skeleton(const zenkit::ModelHierarchy& src, const Animation* anim, std bboxCol[0] = {src.collision_bbox.min.x, src.collision_bbox.min.y, src.collision_bbox.min.z}; bboxCol[1] = {src.collision_bbox.max.x, src.collision_bbox.max.y, src.collision_bbox.max.z}; + // bbox size apears to be halfed in source file + bboxCol[0] *= 2.f; + bboxCol[1] *= 2.f; + nodes.resize(src.nodes.size()); tr.resize(src.nodes.size()); @@ -83,7 +87,8 @@ std::string_view Skeleton::defaultMesh() const { } float Skeleton::colisionHeight() const { - return std::fabs(bboxCol[1].y-bboxCol[0].y); + // scale by 0.5, to be compatible with old behaviour for now + return std::fabs(bboxCol[1].y-bboxCol[0].y) * 0.5f; } void Skeleton::mkSkeleton() { diff --git a/game/physics/dynamicworld.cpp b/game/physics/dynamicworld.cpp index 1657dd650..f0c05a3e3 100644 --- a/game/physics/dynamicworld.cpp +++ b/game/physics/dynamicworld.cpp @@ -700,9 +700,10 @@ float DynamicWorld::soundOclusion(const Tempest::Vec3& from, const Tempest::Vec3 DynamicWorld::NpcItem DynamicWorld::ghostObj(std::string_view visual) { Tempest::Vec3 min={0,0,0}, max={0,0,0}; - if(auto sk=Resources::loadSkeleton(visual)) { - min = sk->bboxCol[0]; - max = sk->bboxCol[1]; + if(auto sk = Resources::loadSkeleton(visual)) { + // scale by 0.5, to be compatible with old behaviour for now + min = sk->bboxCol[0] * 0.5f; + max = sk->bboxCol[1] * 0.5f; } auto obj = npcList->create(min,max); float dim = std::max(obj->rX,obj->rZ); diff --git a/game/utils/dbgpainter.cpp b/game/utils/dbgpainter.cpp index f67a6e7d5..1298c7f3c 100644 --- a/game/utils/dbgpainter.cpp +++ b/game/utils/dbgpainter.cpp @@ -109,3 +109,7 @@ void DbgPainter::drawObb(const Tempest::Matrix4x4& m, const Tempest::Vec3& min, line(Tempest::Vec3(max.x, min.y, max.z), Tempest::Vec3(max.x, max.y, max.z)); line(Tempest::Vec3(min.x, min.y, max.z), Tempest::Vec3(min.x, max.y, max.z)); } + +void DbgPainter::drawObb(const Tempest::Matrix4x4& m, const Tempest::Vec3 bbox[]) { + drawObb(m, bbox[0], bbox[1]); + } diff --git a/game/utils/dbgpainter.h b/game/utils/dbgpainter.h index 5baca3ba9..58f04aabb 100644 --- a/game/utils/dbgpainter.h +++ b/game/utils/dbgpainter.h @@ -17,6 +17,7 @@ class DbgPainter { void drawPoint(const Tempest::Vec3& a, int radiusPx = 5); void drawAabb(const Tempest::Vec3& min, const Tempest::Vec3& max); void drawObb (const Tempest::Matrix4x4& m, const Tempest::Vec3& min, const Tempest::Vec3& max); + void drawObb (const Tempest::Matrix4x4& m, const Tempest::Vec3 bbox[2]); Tempest::Painter& painter; const Tempest::Matrix4x4 mvp; diff --git a/game/world/objects/interactive.cpp b/game/world/objects/interactive.cpp index 80a37c21a..2c17e0aaa 100644 --- a/game/world/objects/interactive.cpp +++ b/game/world/objects/interactive.cpp @@ -184,7 +184,7 @@ void Interactive::drawVobBox(DbgPainter& p) const { if(auto mesh = visual.protoMesh()) { if(auto* skeleton = mesh->skeleton.get()) { auto bbox = skeleton->bboxCol; - p.drawObb(transform(), bbox[0]*2, bbox[1]*2); + p.drawObb(transform(), bbox[0], bbox[1]); } } @@ -203,8 +203,8 @@ void Interactive::drawVobRay(DbgPainter& p, const Npc& npc) const { if(auto mesh = visual.protoMesh()) { if(auto* skeleton = mesh->skeleton.get()) { - auto boxMin = skeleton->bboxCol[0] * 2.f; - auto boxMax = skeleton->bboxCol[1] * 2.f; + auto boxMin = skeleton->bboxCol[0]; + auto boxMax = skeleton->bboxCol[1]; auto at = (boxMin+boxMax)*0.5f; transform().project(at); @@ -589,8 +589,8 @@ bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { if(auto mesh = visual.protoMesh()) { if(auto* skeleton = mesh->skeleton.get()) { - auto boxMin = skeleton->bboxCol[0] * 2.f; - auto boxMax = skeleton->bboxCol[1] * 2.f; + auto boxMin = skeleton->bboxCol[0]; + auto boxMax = skeleton->bboxCol[1]; auto at = (boxMin+boxMax)*0.5f; transform().project(at); diff --git a/game/world/objects/item.cpp b/game/world/objects/item.cpp index 5fa40ce53..6f47f8628 100644 --- a/game/world/objects/item.cpp +++ b/game/world/objects/item.cpp @@ -87,11 +87,9 @@ void Item::drawVobBox(DbgPainter& p) const { p.setPen(Tempest::Color(1,0,0)); if(auto mesh = visual.protoMesh()) { if(auto* skeleton = mesh->skeleton.get()) { - auto bbox = skeleton->bboxCol; - p.drawObb(transform(), bbox[0]*2, bbox[1]*2); + p.drawObb(transform(), skeleton->bboxCol); } else { - auto bbox = mesh->bbox; - p.drawObb(transform(), bbox[0], bbox[1]); + p.drawObb(transform(), mesh->bbox); } auto v = (mesh->bbox[0]+mesh->bbox[1])*0.5; From feaf747f7ae24e76adccb42b6cc9afb74ce61242 Mon Sep 17 00:00:00 2001 From: Try Date: Sat, 14 Mar 2026 19:43:01 +0100 Subject: [PATCH 07/19] ray-to-item bbox test --- game/graphics/mesh/protomesh.cpp | 6 +++ game/graphics/mesh/protomesh.h | 1 + game/world/objects/interactive.cpp | 61 +++++++++++------------ game/world/objects/item.cpp | 77 +++++++++++++----------------- game/world/objects/item.h | 1 + game/world/objects/npc.cpp | 26 +++++++--- 6 files changed, 87 insertions(+), 85 deletions(-) diff --git a/game/graphics/mesh/protomesh.cpp b/game/graphics/mesh/protomesh.cpp index 2f42d7d69..b4c184066 100644 --- a/game/graphics/mesh/protomesh.cpp +++ b/game/graphics/mesh/protomesh.cpp @@ -338,6 +338,12 @@ size_t ProtoMesh::findNode(std::string_view name, size_t def) const { return skeleton->findNode(name,def); } +const Vec3* ProtoMesh::bboxCol() const { + if(skeleton==nullptr) + return bbox; + return skeleton->bboxCol; + } + void ProtoMesh::setupScheme(std::string_view s) { auto sep = s.find("_"); if(sep!=std::string::npos) { diff --git a/game/graphics/mesh/protomesh.h b/game/graphics/mesh/protomesh.h index 8b8fa3b98..d27bbe069 100644 --- a/game/graphics/mesh/protomesh.h +++ b/game/graphics/mesh/protomesh.h @@ -91,6 +91,7 @@ class ProtoMesh { size_t skinedNodesCount() const; Tempest::Matrix4x4 mapToRoot(size_t node) const; size_t findNode(std::string_view name,size_t def=size_t(-1)) const; + const Tempest::Vec3* bboxCol() const; private: void setupScheme(std::string_view s); diff --git a/game/world/objects/interactive.cpp b/game/world/objects/interactive.cpp index 2c17e0aaa..9050298b1 100644 --- a/game/world/objects/interactive.cpp +++ b/game/world/objects/interactive.cpp @@ -182,10 +182,7 @@ void Interactive::drawVobBox(DbgPainter& p) const { p.setPen(Tempest::Color(1,0,0)); //p.drawAabb(bbox[0], bbox[1]); if(auto mesh = visual.protoMesh()) { - if(auto* skeleton = mesh->skeleton.get()) { - auto bbox = skeleton->bboxCol; - p.drawObb(transform(), bbox[0], bbox[1]); - } + p.drawObb(transform(), mesh->bboxCol()); } p.setBrush(Tempest::Color(0,0,1)); @@ -202,25 +199,24 @@ void Interactive::drawVobRay(DbgPainter& p, const Npc& npc) const { auto cen = npc.centerPosition(); if(auto mesh = visual.protoMesh()) { - if(auto* skeleton = mesh->skeleton.get()) { - auto boxMin = skeleton->bboxCol[0]; - auto boxMax = skeleton->bboxCol[1]; - auto at = (boxMin+boxMax)*0.5f; - transform().project(at); - - auto tMax = (at - cen).length(); - auto dir = Tempest::Vec3::normalize(at - cen); - float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), boxMin, boxMax); - - bool accessable = true; - if(!npc.canRayHitPoint(cen+dir*tHit, true)) - accessable = false; - p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); - p.drawLine(cen, cen+dir*tHit); - - p.setPen(Tempest::Color(1,1,0)); - p.drawLine(cen+dir*tHit, at); - } + auto bbox = mesh->bboxCol(); + auto boxMin = bbox[0]; + auto boxMax = bbox[1]; + auto at = (boxMin+boxMax)*0.5f; + transform().project(at); + + auto tMax = (at - cen).length(); + auto dir = Tempest::Vec3::normalize(at - cen); + float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), boxMin, boxMax); + + bool accessable = true; + if(!npc.canRayHitPoint(cen+dir*tHit, true)) + accessable = false; + p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); + p.drawLine(cen, cen+dir*tHit); + + p.setPen(Tempest::Color(1,1,0)); + p.drawLine(cen+dir*tHit, at); } } @@ -588,19 +584,16 @@ bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { auto cen = npc.centerPosition(); if(auto mesh = visual.protoMesh()) { - if(auto* skeleton = mesh->skeleton.get()) { - auto boxMin = skeleton->bboxCol[0]; - auto boxMax = skeleton->bboxCol[1]; - auto at = (boxMin+boxMax)*0.5f; - transform().project(at); + auto bbox = mesh->bboxCol(); + auto at = (bbox[0]+bbox[1])*0.5f; + transform().project(at); - auto tMax = (at - cen).length(); - auto dir = (at - cen)/tMax; - float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), boxMin, boxMax); + auto tMax = (at - cen).length(); + auto dir = (at - cen)/tMax; + float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), bbox[0], bbox[1]); - if(!npc.canRayHitPoint(cen+dir*tHit, true)) - return false; - } + if(!npc.canRayHitPoint(cen+dir*tHit, true)) + return false; } for(auto& i:attPos){ diff --git a/game/world/objects/item.cpp b/game/world/objects/item.cpp index 6f47f8628..fec37e64a 100644 --- a/game/world/objects/item.cpp +++ b/game/world/objects/item.cpp @@ -4,7 +4,6 @@ #include "game/serialize.h" #include "game/gamescript.h" -#include "graphics/mesh/skeleton.h" #include "world/objects/npc.h" #include "world/world.h" #include "utils/versioninfo.h" @@ -86,50 +85,33 @@ Item::~Item() { void Item::drawVobBox(DbgPainter& p) const { p.setPen(Tempest::Color(1,0,0)); if(auto mesh = visual.protoMesh()) { - if(auto* skeleton = mesh->skeleton.get()) { - p.drawObb(transform(), skeleton->bboxCol); - } else { - p.drawObb(transform(), mesh->bbox); - } + p.drawObb(transform(), mesh->bboxCol()); - auto v = (mesh->bbox[0]+mesh->bbox[1])*0.5; + auto v = midPosition(); p.setPen(Tempest::Color(0,1,0)); - p.drawPoint(pos+v); + p.drawPoint(v); } } void Item::drawVobRay(DbgPainter& p, const Npc& npc) const { - auto cen = npc.centerPosition(); - auto at = midPosition(); - - bool accessable = true; - if(!npc.canRayHitPoint(at, true)) - accessable = false; - p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); - p.drawLine(cen, at); - /* - if(auto mesh = visual.protoMesh()) { - if(auto* skeleton = mesh->skeleton.get()) { - auto boxMin = skeleton->bboxCol[0] * 2.f; - auto boxMax = skeleton->bboxCol[1] * 2.f; - auto at = (boxMin+boxMax)*0.5f; - transform().project(at); - - auto tMax = (at - cen).length(); - auto dir = Tempest::Vec3::normalize(at - cen); - float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), boxMin, boxMax); - - bool accessable = true; - if(!npc.canRayHitPoint(cen+dir*tHit, true)) - accessable = false; - p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); - p.drawLine(cen, cen+dir*tHit); - - p.setPen(Tempest::Color(1,1,0)); - p.drawLine(cen+dir*tHit, at); - } + if(auto bbox = this->bBox()) { + // npc eyesight height + auto cen = npc.mapHeadBone(); + auto at = this->midPosition(); + auto tMax = (at - cen).length(); + auto dir = (at - cen)/tMax; + float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), bbox[0], bbox[1]); + + bool accessable = true; + const auto r = world.physic()->ray(cen, cen+dir*tHit); + if(r.hasCol) + accessable = false; + p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); + p.drawLine(cen, cen+dir*tHit); + + p.setPen(Tempest::Color(1,1,0)); + p.drawLine(cen+dir*tHit, at); } - */ } void Item::save(Serialize &fout) const { @@ -232,13 +214,20 @@ Tempest::Vec3 Item::position() const { return pos; } +const Vec3* Item::bBox() const { + if(visual.protoMesh()==nullptr) + return nullptr; + return visual.protoMesh()->bboxCol(); + } + Vec3 Item::midPosition() const { - auto b = visual.bounds(); - auto v = (b.bbox[0]+b.bbox[1])*0.5; - transform().project(v); - return v; - // transform().project(v); // doesn't work for Karibik mod - // return pos + v; + if(auto mesh = visual.protoMesh()) { + auto bbox = mesh->bboxCol(); + auto v = (bbox[0] + bbox[1])*0.5; + transform().project(v); // doesn't use to work for Karibik mod + return v; + } + return pos; } bool Item::isGold() const { diff --git a/game/world/objects/item.h b/game/world/objects/item.h index 0dc5a0fb5..0c656467f 100644 --- a/game/world/objects/item.h +++ b/game/world/objects/item.h @@ -55,6 +55,7 @@ class Item : public Vob { std::string_view description() const; Tempest::Vec3 position() const; Tempest::Vec3 midPosition() const; + const Tempest::Vec3*bBox() const; bool isGold() const; ItmFlags mainFlag() const; int32_t itemFlag() const; diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 99e2d9c05..bf1c1aa53 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -332,12 +332,11 @@ void Npc::drawVobBox(DbgPainter& p) const { physic.debugDraw(p); if(auto sk = visual.visualSkeleton()) { - auto bbox = sk->bboxCol; - auto tr = transform(); + auto tr = transform(); tr.translate(0,translateY(),0); p.setPen(Color(1,0,0)); - p.drawObb(tr, bbox[0]*2, bbox[1]*2); + p.drawObb(tr, sk->bboxCol); } } @@ -4504,7 +4503,7 @@ bool Npc::canSeeItem(const Item& it, bool freeLos) const { static const double ref = std::cos(100*M_PI/180.0); // spec requires +-100 view angle range const auto itMid = it.midPosition(); - const auto cen = centerPosition(); + const auto cen = visual.mapHeadBone(); const auto dir = itMid - cen; const float range = float(hnpc->senses_range); @@ -4519,9 +4518,22 @@ bool Npc::canSeeItem(const Item& it, bool freeLos) const { return false; } - const auto r = owner.physic()->ray(cen, itMid); - if(r.hasCol) - return false; + if(auto bbox = it.bBox()) { + // npc eyesight height + auto at = it.midPosition(); + auto tMax = (at - cen).length(); + auto dir = (at - cen)/tMax; + float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), bbox[0], bbox[1]); + + const auto r = owner.physic()->ray(cen, cen+dir*tHit); + if(r.hasCol) + return false; + } else { + const auto r = owner.physic()->ray(cen, itMid); + if(r.hasCol) + return false; + } + return true; } From c472e7cb62881654b041ce978d51322e5a1e8152 Mon Sep 17 00:00:00 2001 From: Try Date: Sat, 14 Mar 2026 20:06:09 +0100 Subject: [PATCH 08/19] remove Npc::translateY --- game/game/movealgo.cpp | 6 ++---- game/world/objects/npc.cpp | 8 ++------ game/world/objects/npc.h | 1 - game/world/worldsound.cpp | 8 ++++---- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/game/game/movealgo.cpp b/game/game/movealgo.cpp index 7a49feea2..1a884342a 100644 --- a/game/game/movealgo.cpp +++ b/game/game/movealgo.cpp @@ -727,10 +727,8 @@ bool MoveAlgo::isClose(const Npc& npc, const WayPoint& p) { } bool MoveAlgo::isClose(const Npc& npc, const WayPoint& p, float dist) { - auto px = p.position(); - px.y += npc.translateY(); - - float len = npc.qDistTo(px); + auto dp = p.groundPos - npc.position(); + float len = dp.quadLength(); return (lenbboxCol); @@ -690,10 +690,6 @@ Bounds Npc::bounds() const { return b; } -float Npc::translateY() const { - return visual.pose().translateY(); - } - Vec3 Npc::centerPosition() const { auto p = position(); p.y = physic.centerY(); @@ -4315,7 +4311,7 @@ Npc::JumpStatus Npc::tryJump() { return ret; } - if(isInAir() && dY<=jumpLow + translateY()) { + if(isInAir() && dY<=jumpLow + visual.pose().translateY()) { // jumpup -> climb ret.anim = Anim::JumpHang; ret.height = jumpY; diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index 5ac69471c..56282533a 100644 --- a/game/world/objects/npc.h +++ b/game/world/objects/npc.h @@ -119,7 +119,6 @@ class Npc final { auto world() -> World&; - float translateY() const; auto centerPosition() const -> Tempest::Vec3; Npc* lookAtTarget() const; auto portalName() -> std::string_view; diff --git a/game/world/worldsound.cpp b/game/world/worldsound.cpp index 12bb314cb..c834458ac 100644 --- a/game/world/worldsound.cpp +++ b/game/world/worldsound.cpp @@ -40,7 +40,8 @@ struct WorldSound::WSound final { struct WorldSound::Zone final { Tempest::Vec3 bbox[2]={}; std::string name; - bool checkPos(float x,float y,float z) const { + bool checkPos(const Tempest::Vec3& v) const { return checkPos(v.x, v.y, v.z); } + bool checkPos(float x, float y, float z) const { return bbox[0].x <= x && xcheckPos(plPos.x,plPos.y+player.translateY(),plPos.z)){ + if(currentZone!=nullptr && currentZone->checkPos(plPos)){ zone = currentZone; } else { for(auto& z:zones) { - if(z.checkPos(plPos.x,plPos.y+player.translateY(),plPos.z)) { + if(z.checkPos(plPos)) { zone = &z; } } From 4dde326a822307ea8c44891c2fadf2abc3baf5a9 Mon Sep 17 00:00:00 2001 From: Try Date: Sat, 14 Mar 2026 23:01:30 +0100 Subject: [PATCH 09/19] introduce Npc::collosionCenter --- game/physics/dynamicworld.cpp | 9 +++++++++ game/physics/dynamicworld.h | 1 + game/world/objects/npc.cpp | 6 +++++- game/world/objects/npc.h | 1 + game/world/world.cpp | 4 ++-- 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/game/physics/dynamicworld.cpp b/game/physics/dynamicworld.cpp index f0c05a3e3..a50943de3 100644 --- a/game/physics/dynamicworld.cpp +++ b/game/physics/dynamicworld.cpp @@ -1006,6 +1006,15 @@ void DynamicWorld::NpcItem::setUserPointer(void *p) { obj->setUserPointer(p); } +auto DynamicWorld::NpcItem::center() const -> Tempest::Vec3 { + if(obj) { + const btTransform& tr = obj->getWorldTransform(); + const Tempest::Vec3 ret = {tr.getOrigin().x(), tr.getOrigin().y(), tr.getOrigin().z()}; + return ret*100.f; + } + return {}; + } + float DynamicWorld::NpcItem::centerY() const { if(obj) { const btTransform& tr = obj->getWorldTransform(); diff --git a/game/physics/dynamicworld.h b/game/physics/dynamicworld.h index e78dc4ece..4cd4ab9be 100644 --- a/game/physics/dynamicworld.h +++ b/game/physics/dynamicworld.h @@ -91,6 +91,7 @@ class DynamicWorld final { void setEnable(bool e); void setUserPointer(void* p); + auto center() const -> Tempest::Vec3; float centerY() const; bool testMove(const Tempest::Vec3& to, CollisionTest& out); diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index ddc2b3641..68ebb9d5b 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -696,6 +696,10 @@ Vec3 Npc::centerPosition() const { return p; } +auto Npc::collosionCenter() const -> Vec3 { + return physic.center(); + } + Npc *Npc::lookAtTarget() const { return currentLookAtNpc; } @@ -4415,7 +4419,7 @@ bool Npc::canSeeNpc(const Npc &oth, bool freeLos) const { const auto mid = oth.bounds().midTr; if(canSeeNpc(mid,freeLos)) return true; - const auto ppos = oth.physic.position(); + const auto ppos = oth.physic.center(); if(oth.isDown() && canSeeNpc(ppos,freeLos)) { // mid of dead npc may endedup inside a wall; extra check for physical center return true; diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index 56282533a..de3b2d482 100644 --- a/game/world/objects/npc.h +++ b/game/world/objects/npc.h @@ -120,6 +120,7 @@ class Npc final { auto world() -> World&; auto centerPosition() const -> Tempest::Vec3; + auto collosionCenter() const -> Tempest::Vec3; Npc* lookAtTarget() const; auto portalName() -> std::string_view; auto formerPortalName() -> std::string_view; diff --git a/game/world/world.cpp b/game/world/world.cpp index fd0dad53e..1e432337e 100644 --- a/game/world/world.cpp +++ b/game/world/world.cpp @@ -645,7 +645,7 @@ Bullet& World::shootSpell(const Item &itm, const Npc &npc, const Npc *target) { float tgRange = vfx==nullptr ? 0 : vfx->emTrjTargetRange; if(target!=nullptr) { - auto tgPos = target->centerPosition(); + auto tgPos = target->collosionCenter(); if(vfx!=nullptr) { pos = npc.mapBone(vfx->emTrjOriginNode); tgPos = target->mapBone(vfx->emTrjTargetNode); @@ -668,7 +668,7 @@ Bullet& World::shootBullet(const Item &itm, const Npc &npc, const Npc *target, c auto pos = npc.mapWeaponBone(); if(target!=nullptr) { - dir = target->centerPosition() - pos; + dir = target->collosionCenter() - pos; float lxz = std::sqrt(dir.x*dir.x+0*0+dir.z*dir.z); float speed = DynamicWorld::bulletSpeed; From 6d07d0bcd9e4cf3a783a79e0f2dbbe23b32ae165 Mon Sep 17 00:00:00 2001 From: Try Date: Sun, 15 Mar 2026 12:36:40 +0100 Subject: [PATCH 10/19] introduce C_Focus::npc_longrange --- game/world/objects/npc.cpp | 9 +++------ game/world/world.cpp | 5 +++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 68ebb9d5b..e7063e6ba 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -4416,11 +4416,8 @@ void Npc::stopWalking() { } bool Npc::canSeeNpc(const Npc &oth, bool freeLos) const { - const auto mid = oth.bounds().midTr; - if(canSeeNpc(mid,freeLos)) - return true; - const auto ppos = oth.physic.center(); - if(oth.isDown() && canSeeNpc(ppos,freeLos)) { + const auto mid = oth.physic.center(); + if(canRayHitPoint(mid,freeLos)) { // mid of dead npc may endedup inside a wall; extra check for physical center return true; } @@ -4429,7 +4426,7 @@ bool Npc::canSeeNpc(const Npc &oth, bool freeLos) const { if(oth.visual.visualSkeleton()->BIP01_HEAD==size_t(-1)) return false; auto head = oth.visual.mapHeadBone(); - if(canSeeNpc(head,freeLos)) + if(canRayHitPoint(head,freeLos)) return true; return false; } diff --git a/game/world/world.cpp b/game/world/world.cpp index 1e432337e..df859acf0 100644 --- a/game/world/world.cpp +++ b/game/world/world.cpp @@ -425,6 +425,11 @@ Focus World::findFocus(const Npc &pl, const Focus& def) { 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}; + if(pl.weaponState()==WeaponState::NoWeapon) { + // used only for dialogs it seems + optNpc.rangeMax = std::max(optNpc.rangeMax, policy.npc_longrange); + } + 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); From 5e1201bbcf8a0a37e46fe45b104db33781df8686 Mon Sep 17 00:00:00 2001 From: Try Date: Sun, 15 Mar 2026 13:36:17 +0100 Subject: [PATCH 11/19] update object matrix in wld_insertnpc --- game/game/gamescript.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/game/game/gamescript.cpp b/game/game/gamescript.cpp index dbf75cf24..87ea66d70 100644 --- a/game/game/gamescript.cpp +++ b/game/game/gamescript.cpp @@ -766,8 +766,8 @@ void GameScript::fixNpcPosition(Npc& npc, float angle0, float distBias) { auto pos0 = npc.position(); for(int r = 0; r<=800; r+=20) { - for(float ang = 0; ang<360; ang+=30.f) { - float a = float((ang+angle0)*M_PI/180.0); + for(int ang = 0; ang<360; ang+=30) { + float a = float((float(ang)+angle0)*M_PI/180.0); float d = float(r)+distBias; auto p = pos0+Vec3(std::cos(a)*d, 0, std::sin(a)*d); @@ -776,12 +776,18 @@ void GameScript::fixNpcPosition(Npc& npc, float angle0, float distBias) { continue; p.y = ray.v.y; npc.setPosition(p); - if(!npc.hasCollision()) + if(!npc.hasCollision()) { + npc.updateTransform(); return; + } + if(d==0) { + // no need to loop multiple angles, with R of zero + break; + } } } - npc.setPosition(pos0); + // npc.setPosition(pos0); } void GameScript::eventPlayAni(Npc& npc, std::string_view ani) { From db63992fadf12760f7d4cbd755cc34f5aeb951e8 Mon Sep 17 00:00:00 2001 From: Try Date: Sun, 15 Mar 2026 19:15:23 +0100 Subject: [PATCH 12/19] split show-box vs show-rays --- game/game/playercontrol.cpp | 5 ++- game/game/playercontrol.h | 2 +- game/gothic.h | 4 +++ game/mainwindow.cpp | 14 ++++---- game/marvin.cpp | 6 +++- game/marvin.h | 1 + game/world/objects/interactive.cpp | 4 +-- game/world/objects/npc.cpp | 51 ++++++++++++++++++++---------- game/world/objects/npc.h | 2 +- 9 files changed, 61 insertions(+), 28 deletions(-) diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 27842bb1b..5aa89b4c9 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -252,7 +252,7 @@ void PlayerControl::onRotateMouse(float dAngleX, float dAngleY) { rotMouseY += dAngleY; } -void PlayerControl::drawVobBox(DbgPainter& p) const { +void PlayerControl::drawVobRay(DbgPainter& p) const { auto w = Gothic::inst().world(); if(w==nullptr || w->player()==nullptr) return; @@ -264,6 +264,9 @@ void PlayerControl::drawVobBox(DbgPainter& p) const { if(focus.item!=nullptr) { focus.item->drawVobRay(p, *pl); } + if(focus.npc!=nullptr) { + pl->drawVobRay(p, *focus.npc); + } } void PlayerControl::tickFocus() { diff --git a/game/game/playercontrol.h b/game/game/playercontrol.h index 871aedd6a..6f0dbf1f2 100644 --- a/game/game/playercontrol.h +++ b/game/game/playercontrol.h @@ -26,7 +26,7 @@ class PlayerControl final { bool isPressed(KeyCodec::Action a) const; void onRotateMouse(float dAngleX, float dAngleY); - void drawVobBox(DbgPainter& p) const; + void drawVobRay(DbgPainter& p) const; void changeZoom(int delta); void tickFocus(); diff --git a/game/gothic.h b/game/gothic.h index c8911ae3e..f2703b302 100644 --- a/game/gothic.h +++ b/game/gothic.h @@ -139,6 +139,9 @@ class Gothic final { bool doVobBox() const { return vobBox; } void setVobBox(bool v) { vobBox = v; } + bool doVobRays() const { return vobRays; } + void setVobRays(bool v) { vobRays = v; } + bool isBenchmarkMode() const; bool isBenchmarkModeCi() const; void setBenchmarkMode(Benchmark b); @@ -221,6 +224,7 @@ class Gothic final { bool showFpsCounter = false; bool showTime = false; bool vobBox = false; + bool vobRays = false; Benchmark isBenchmark = Benchmark::None; std::string wrldDef, plDef, gameDatDef, ouDef; diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index 0406d15f7..f58339f41 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -263,22 +263,24 @@ void MainWindow::paintEvent(PaintEvent& event) { fnt.drawText(p,5,fnt.pixelSize()+5,fpsT); } - if(Gothic::inst().doClock() && world!=nullptr) { - if (!Gothic::inst().isDesktop()) { + if(!Gothic::inst().isDesktop() && world!=nullptr) { + if(Gothic::inst().doClock()) { auto hour = world->time().hour(); auto min = world->time().minute(); auto& fnt = Resources::font(scale); string_frm clockT(int(hour),":",int(min)); fnt.drawText(p,w()-fnt.textSize(clockT).w-5,fnt.pixelSize()+5,clockT); } - } - if(Gothic::inst().doVobBox() && !Gothic::inst().isDesktop()) { auto c = Gothic::inst().camera(); - if(world!=nullptr && c!=nullptr) { + if(Gothic::inst().doVobBox() && c!=nullptr) { DbgPainter dbg(p,c->viewProj(),w(),h()); world->drawVobBoxNpcNear(dbg); - player.drawVobBox(dbg); + } + + if(Gothic::inst().doVobRays() && c!=nullptr) { + DbgPainter dbg(p,c->viewProj(),w(),h()); + player.drawVobRay(dbg); } } diff --git a/game/marvin.cpp b/game/marvin.cpp index 4a91c537b..fd296c3d6 100644 --- a/game/marvin.cpp +++ b/game/marvin.cpp @@ -98,7 +98,7 @@ Marvin::Marvin() { {"ztoggle renderportals", C_Invalid}, {"ztoggle rendervob", C_Invalid}, {"ztoggle showportals", C_Invalid}, - {"ztoggle showtraceray", C_Invalid}, + {"ztoggle showtraceray", C_ToggleShowRay}, {"ztoggle tnl", C_Invalid}, {"ztoggle vobbox", C_ToggleVobBox}, {"zvideores %d %d %d", C_Invalid}, @@ -352,6 +352,10 @@ bool Marvin::exec(std::string_view v) { Gothic::inst().setFRate(!Gothic::inst().doFrate()); return true; } + case C_ToggleShowRay:{ + Gothic::inst().setVobRays(!Gothic::inst().doVobRays()); + return true; + } case C_ToggleVobBox:{ Gothic::inst().setVobBox(!Gothic::inst().doVobBox()); return true; diff --git a/game/marvin.h b/game/marvin.h index 3cfbe002c..964907bd3 100644 --- a/game/marvin.h +++ b/game/marvin.h @@ -28,6 +28,7 @@ class Marvin { // rendering C_ToggleFrame, + C_ToggleShowRay, C_ToggleVobBox, // game diff --git a/game/world/objects/interactive.cpp b/game/world/objects/interactive.cpp index 9050298b1..2b5a31428 100644 --- a/game/world/objects/interactive.cpp +++ b/game/world/objects/interactive.cpp @@ -598,14 +598,14 @@ bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { for(auto& i:attPos){ auto pos = nodePosition(npc,i); - if(npc.canSeeNpc(pos,freeLos)) + if(npc.canRayHitPoint(pos,freeLos)) return true; } // graves if(attPos.size()==0){ auto pos = displayPosition(); - if(npc.canSeeNpc(pos,freeLos)) + if(npc.canRayHitPoint(pos,freeLos)) return true; } return false; diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index e7063e6ba..eb2fd02b6 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -328,18 +328,6 @@ void Npc::postValidate() { currentInteract = nullptr; } -void Npc::drawVobBox(DbgPainter& p) const { - physic.debugDraw(p); - - if(auto sk = visual.visualSkeleton()) { - auto tr = transform(); - tr.translate(0,visual.pose().translateY(),0); - - p.setPen(Color(1,0,0)); - p.drawObb(tr, sk->bboxCol); - } - } - void Npc::saveAiState(Serialize& fout) const { fout.write(aniWaitTime,waitTime,faiWaitTime,outWaitTime); fout.write(uint8_t(aiPolicy)); @@ -4415,6 +4403,41 @@ void Npc::stopWalking() { stopWalkAnimation(); } +void Npc::drawVobBox(DbgPainter& p) const { + physic.debugDraw(p); + + if(auto sk = visual.visualSkeleton()) { + auto tr = transform(); + tr.translate(0,visual.pose().translateY(),0); + + p.setPen(Color(1,0,0)); + p.drawObb(tr, sk->bboxCol); + } + } + +void Npc::drawVobRay(DbgPainter& p, const Npc& oth) const { + const bool freeLos = true; + const auto mid = oth.physic.center(); + p.setPen(Color(0,1,0)); + + if(canRayHitPoint(mid,freeLos)) { + // mid of dead npc may endedup inside a wall; extra check for physical center + p.drawLine(mapHeadBone(), mid); + return; + } + if(oth.visual.visualSkeleton()==nullptr) + return; + if(oth.visual.visualSkeleton()->BIP01_HEAD==size_t(-1)) + return; + auto head = oth.visual.mapHeadBone(); + if(canRayHitPoint(head,freeLos)) { + p.drawLine(mapHeadBone(), head); + return; + } + p.setPen(Color(1,0,0)); + p.drawLine(mapHeadBone(), head); + } + bool Npc::canSeeNpc(const Npc &oth, bool freeLos) const { const auto mid = oth.physic.center(); if(canRayHitPoint(mid,freeLos)) { @@ -4441,10 +4464,6 @@ bool Npc::canSeeSource() const { return false; } -bool Npc::canSeeNpc(const Vec3 pos, bool freeLos) const { - return canRayHitPoint(pos, freeLos); - } - bool Npc::canRayHitPoint(const Tempest::Vec3 pos, bool freeLos, float extRange) const { const float range = float(hnpc->senses_range) + extRange; if(qDistTo(pos)>range*range) diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index de3b2d482..53945ac7b 100644 --- a/game/world/objects/npc.h +++ b/game/world/objects/npc.h @@ -83,6 +83,7 @@ class Npc final { void postValidate(); void drawVobBox(DbgPainter& p) const; + void drawVobRay(DbgPainter& p, const Npc& npc) const; bool setPosition (float x,float y,float z); bool setPosition (const Tempest::Vec3& pos); @@ -377,7 +378,6 @@ class Npc final { void stopWalking(); bool canSeeNpc(const Npc& oth, bool freeLos) const; - bool canSeeNpc(const Tempest::Vec3 pos, bool freeLos) const; bool canSeeItem(const Item& it, bool freeLos) const; bool canSeeSource() const; bool canRayHitPoint(const Tempest::Vec3 pos, bool freeLos = true, float extRange=0.f) const; From 372d2877461bae7569c084e2c21ff4b9759e34d9 Mon Sep 17 00:00:00 2001 From: Try Date: Sun, 15 Mar 2026 20:58:02 +0100 Subject: [PATCH 13/19] MOBSI: fixup billboards and graves --- game/physics/dynamicworld.cpp | 10 +++++---- game/physics/dynamicworld.h | 6 ++++-- game/world/objects/interactive.cpp | 33 +++++++++++++++++------------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/game/physics/dynamicworld.cpp b/game/physics/dynamicworld.cpp index a50943de3..5ca191fe5 100644 --- a/game/physics/dynamicworld.cpp +++ b/game/physics/dynamicworld.cpp @@ -923,7 +923,8 @@ float DynamicWorld::materialDensity(zenkit::MaterialGroup mat) { } float DynamicWorld::rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, const float TMax, - const Tempest::Matrix4x4& obj, const Tempest::Vec3& min, const Tempest::Vec3& max) { + const Tempest::Matrix4x4& obj, const Tempest::Vec3& min, const Tempest::Vec3& max, + const float padd) { using namespace Tempest; auto tmp = obj; @@ -935,7 +936,7 @@ float DynamicWorld::rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, tmp.project(tOri); tmp.project(tDir.x, tDir.y, tDir.z, zero); - float tHit = rayBox(tOri, Vec3(tDir.x, tDir.y, tDir.z), TMax, min, max); + float tHit = rayBox(tOri, Vec3(tDir.x, tDir.y, tDir.z), TMax, min, max, padd); if(tHit==TMax) return TMax; @@ -946,7 +947,8 @@ float DynamicWorld::rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, } float DynamicWorld::rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, const float TMax, - const Tempest::Vec3& boxMin, const Tempest::Vec3& boxMax) { + const Tempest::Vec3& boxMin, const Tempest::Vec3& boxMax, + const float padd) { using namespace Tempest; Vec3 invDir = Vec3(1.f/dir.x, 1.f/dir.y, 1.f/dir.z); @@ -959,7 +961,7 @@ float DynamicWorld::rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, float tNear = std::max(0.f, std::max(t1.x, std::max(t1.y, t1.z))); float tFar = std::min(TMax, std::min(t2.x, std::min(t2.y, t2.z))); - return tNear > tFar ? TMax : tNear; + return tNear > tFar ? TMax : std::max(0.f, tNear-padd); } std::string_view DynamicWorld::validateSectorName(std::string_view name) const { diff --git a/game/physics/dynamicworld.h b/game/physics/dynamicworld.h index 4cd4ab9be..d236f8d57 100644 --- a/game/physics/dynamicworld.h +++ b/game/physics/dynamicworld.h @@ -260,9 +260,11 @@ class DynamicWorld final { static float materialDensity (zenkit::MaterialGroup mat); static float rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, const float TMax, - const Tempest::Matrix4x4& obj, const Tempest::Vec3& min, const Tempest::Vec3& max); + const Tempest::Matrix4x4& obj, const Tempest::Vec3& min, const Tempest::Vec3& max, + const float padd = 0.f); static float rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir, const float TMax, - const Tempest::Vec3& min, const Tempest::Vec3& max); + const Tempest::Vec3& min, const Tempest::Vec3& max, + const float padd = 0.f); std::string_view validateSectorName(std::string_view name) const; diff --git a/game/world/objects/interactive.cpp b/game/world/objects/interactive.cpp index 2b5a31428..80e28ee61 100644 --- a/game/world/objects/interactive.cpp +++ b/game/world/objects/interactive.cpp @@ -196,7 +196,7 @@ void Interactive::drawVobBox(DbgPainter& p) const { } void Interactive::drawVobRay(DbgPainter& p, const Npc& npc) const { - auto cen = npc.centerPosition(); + auto head = npc.mapHeadBone(); if(auto mesh = visual.protoMesh()) { auto bbox = mesh->bboxCol(); @@ -205,18 +205,18 @@ void Interactive::drawVobRay(DbgPainter& p, const Npc& npc) const { auto at = (boxMin+boxMax)*0.5f; transform().project(at); - auto tMax = (at - cen).length(); - auto dir = Tempest::Vec3::normalize(at - cen); - float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), boxMin, boxMax); + auto tMax = (at - head).length(); + auto dir = Tempest::Vec3::normalize(at - head); + float tHit = DynamicWorld::rayBox(head, dir, tMax, transform(), boxMin, boxMax, 0.1f); bool accessable = true; - if(!npc.canRayHitPoint(cen+dir*tHit, true)) + if(!npc.canRayHitPoint(head+dir*tHit, true)) accessable = false; p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); - p.drawLine(cen, cen+dir*tHit); + p.drawLine(head, head+dir*tHit); p.setPen(Tempest::Color(1,1,0)); - p.drawLine(cen+dir*tHit, at); + p.drawLine(head+dir*tHit, at); } } @@ -581,18 +581,17 @@ uint32_t Interactive::stateMask() const { } bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { - auto cen = npc.centerPosition(); - + auto head = npc.mapHeadBone(); if(auto mesh = visual.protoMesh()) { auto bbox = mesh->bboxCol(); auto at = (bbox[0]+bbox[1])*0.5f; transform().project(at); - auto tMax = (at - cen).length(); - auto dir = (at - cen)/tMax; - float tHit = DynamicWorld::rayBox(cen, dir, tMax, transform(), bbox[0], bbox[1]); + auto tMax = (at - head).length(); + auto dir = (at - head)/tMax; + float tHit = DynamicWorld::rayBox(head, dir, tMax, transform(), bbox[0], bbox[1], 0.1f); - if(!npc.canRayHitPoint(cen+dir*tHit, true)) + if(!npc.canRayHitPoint(head+dir*tHit, true)) return false; } @@ -603,11 +602,17 @@ bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { } // graves - if(attPos.size()==0){ + if(attPos.size()==0) { + // ray-box test should be engough + return true; + } + /* + if(attPos.size()==0) { auto pos = displayPosition(); if(npc.canRayHitPoint(pos,freeLos)) return true; } + */ return false; } From 599e617ce0a89c93a08c36006ff53653af87b875 Mon Sep 17 00:00:00 2001 From: Try Date: Mon, 16 Mar 2026 21:19:23 +0100 Subject: [PATCH 14/19] rework npc-to-mobsi interation --- game/graphics/mesh/protomesh.cpp | 14 ------ game/graphics/mesh/protomesh.h | 1 - game/world/objects/interactive.cpp | 68 +++++++++++------------------- game/world/objects/interactive.h | 19 +++++---- game/world/objects/npc.cpp | 9 +++- 5 files changed, 42 insertions(+), 69 deletions(-) diff --git a/game/graphics/mesh/protomesh.cpp b/game/graphics/mesh/protomesh.cpp index b4c184066..486c1df7d 100644 --- a/game/graphics/mesh/protomesh.cpp +++ b/game/graphics/mesh/protomesh.cpp @@ -318,20 +318,6 @@ size_t ProtoMesh::skinedNodesCount() const { return ret; } -Tempest::Matrix4x4 ProtoMesh::mapToRoot(size_t n) const { - Tempest::Matrix4x4 m; - m.identity(); - - while(nbboxCol()); } - p.setBrush(Tempest::Color(0,0,1)); - for(auto& i:attPos){ - auto mat = nodeTranform(i.name); - float x = mat.at(3,0); - float y = mat.at(3,1); - float z = mat.at(3,2); - p.drawPoint({x,y,z}); + for(auto& i:attPos) { + p.setBrush(Tempest::Color(0,1,0)); + p.drawPoint(nodePosition(i)); } } @@ -606,19 +602,12 @@ bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { // ray-box test should be engough return true; } - /* - if(attPos.size()==0) { - auto pos = displayPosition(); - if(npc.canRayHitPoint(pos,freeLos)) - return true; - } - */ return false; } Tempest::Vec3 Interactive::nearestPoint(const Npc& to) const { if(auto p = findNearest(to)) - return worldPos(*p); + return nodePosition(*p); return displayPosition(); } @@ -734,20 +723,6 @@ Interactive::Pos *Interactive::findFreePos() { return nullptr; } -Tempest::Vec3 Interactive::worldPos(const Interactive::Pos &to) const { - auto mesh = visual.protoMesh(); - if(mesh==nullptr) - return Tempest::Vec3(); - - auto mat = transform(); - auto pos = mesh->mapToRoot(to.node); - mat.mul(pos); - - Tempest::Vec3 ret = {}; - mat.project(ret); - return ret; - } - bool Interactive::isAvailable() const { for(auto& i:attPos) if(i.user!=nullptr) @@ -917,20 +892,35 @@ void Interactive::setDir(Npc &npc, const Tempest::Matrix4x4 &mat) { } float Interactive::qDistTo(const Npc &npc, const Interactive::Pos &to) const { - auto p = worldPos(to); + auto p = nodePosition(to); return npc.qDistTo(p); } +Tempest::Vec3 Interactive::nodePosition(const Interactive::Pos &to) const { + auto p = nodeTranform(to.name); + float x = p.at(3,0); + float y = p.at(3,1); + float z = p.at(3,2); + return Tempest::Vec3(x,y,z); + } + +Tempest::Vec3 Interactive::nodePosition(const Npc& npc, const Interactive::Pos &to) const { + auto p = nodeTranform(npc,to); + float x = p.at(3,0); + float y = p.at(3,1); + float z = p.at(3,2); + return {x,y,z}; + } + Tempest::Matrix4x4 Interactive::nodeTranform(std::string_view nodeName) const { auto mesh = visual.protoMesh(); if(mesh==nullptr || mesh->skeleton==nullptr) return Tempest::Matrix4x4(); - auto id = mesh->skeleton->findNode(nodeName); - auto ret = transform(); + auto id = mesh->skeleton->findNode(nodeName); if(id!=size_t(-1)) - ret = visual.bone(id); - return ret; + return visual.bone(id); + return transform(); } Tempest::Matrix4x4 Interactive::nodeTranform(const Npc& npc, const Pos& p) const { @@ -976,14 +966,6 @@ Tempest::Matrix4x4 Interactive::nodeTranform(const Npc& npc, const Pos& p) const return transform(); } -Tempest::Vec3 Interactive::nodePosition(const Npc& npc, const Pos &p) const { - auto mat = nodeTranform(npc,p); - float x = mat.at(3,0); - float y = mat.at(3,1); - float z = mat.at(3,2); - return {x,y,z}; - } - const Animation::Sequence* Interactive::setAnim(Interactive::Anim t) { int dir = (t==Anim::Out || t==Anim::ToStand) ? -1 : 1; int st[] = {state,state+dir}; @@ -1085,7 +1067,7 @@ void Interactive::marchInteractives(DbgPainter &p) const { p.setBrush(Tempest::Color(1.0,0,0,1)); for(auto& m:attPos) { - auto pos = worldPos(m); + auto pos = nodePosition(m); float x = pos.x; float y = pos.y; diff --git a/game/world/objects/interactive.h b/game/world/objects/interactive.h index 96d69f0b0..dcae050d2 100644 --- a/game/world/objects/interactive.h +++ b/game/world/objects/interactive.h @@ -89,12 +89,6 @@ class Interactive : public Vob { void marchInteractives(DbgPainter& p) const; protected: - Tempest::Matrix4x4 nodeTranform(std::string_view nodeName) const; - void moveEvent() override; - float extendedSearchRadius() const override; - virtual void onStateChanged(){} - - private: enum Phase : uint8_t { NotStarted = 0, Started = 1, @@ -115,6 +109,16 @@ class Interactive : public Vob { bool isDistPos() const; }; + auto nodePosition(const Pos &to) const -> Tempest::Vec3; + auto nodePosition(const Npc& npc, const Pos &p) const -> Tempest::Vec3; + Tempest::Matrix4x4 nodeTranform(std::string_view nodeName) const; + Tempest::Matrix4x4 nodeTranform(const Npc& npc, const Pos &p) const; + + void moveEvent() override; + float extendedSearchRadius() const override; + virtual void onStateChanged(){} + + private: void setVisual(const zenkit::VirtualObject& vob); void invokeStateFunc(Npc &npc); void implTick(Pos &p); @@ -138,10 +142,7 @@ class Interactive : public Vob { Pos* findNearest(const Npc& to); const Pos* findFreePos() const; Pos* findFreePos(); - auto worldPos(const Pos &to) const -> Tempest::Vec3; float qDistTo(const Npc &npc, const Pos &to) const; - Tempest::Matrix4x4 nodeTranform(const Npc& npc, const Pos &p) const; - auto nodePosition(const Npc& npc, const Pos &p) const -> Tempest::Vec3; std::string vobName; std::string focName; diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index eb2fd02b6..39290587e 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -2484,8 +2484,13 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { if(inter!=nullptr) { auto pos = inter->nearestPoint(*this); - auto dp = pos-position(); - dp.y = 0; + auto ray = owner.physic()->ray(pos, pos+Vec3(0,-MAX_AI_USE_DISTANCE,0)); + if(ray.hasCol) { + // project position on landscape + pos = ray.v; + } + + auto dp = pos - position(); if(currentInteract==nullptr && dp.quadLength()>MAX_AI_USE_DISTANCE*MAX_AI_USE_DISTANCE) { // too far go2.set(pos); // go to MOBSI and then complete AI_UseMob From c6a8b9428c10fd2c81d4e1b53c0c907f3b262fcf Mon Sep 17 00:00:00 2001 From: Try Date: Tue, 17 Mar 2026 23:13:17 +0100 Subject: [PATCH 15/19] rework npc-to-mobsi interation P2 --- game/game/constants.h | 3 +- game/game/movealgo.cpp | 15 +---- game/world/objects/interactive.cpp | 98 ++++++++++++++---------------- game/world/objects/interactive.h | 9 ++- game/world/objects/npc.cpp | 17 ++---- game/world/waymatrix.cpp | 13 ++-- game/world/waymatrix.h | 2 +- game/world/worldobjects.cpp | 4 +- 8 files changed, 69 insertions(+), 92 deletions(-) diff --git a/game/game/constants.h b/game/game/constants.h index ee0705727..8efbd7afd 100644 --- a/game/game/constants.h +++ b/game/game/constants.h @@ -126,7 +126,8 @@ enum Guild: uint32_t { }; enum { - MAX_AI_USE_DISTANCE = 150 + MAX_AI_USE_DISTANCE = 150, + MOBSI_SEARCH_DISTANCE = 100*10, }; enum { diff --git a/game/game/movealgo.cpp b/game/game/movealgo.cpp index 1a884342a..e651ce468 100644 --- a/game/game/movealgo.cpp +++ b/game/game/movealgo.cpp @@ -709,14 +709,6 @@ int32_t MoveAlgo::diveTime() const { bool MoveAlgo::isClose(const Npc& npc, const Npc& p, float dist) { float len = npc.qDistTo(p); return (len #include "game/serialize.h" -#include "graphics/mesh/skeleton.h" #include "utils/string_frm.h" #include "world/triggers/abstracttrigger.h" #include "world/objects/npc.h" @@ -187,7 +186,7 @@ void Interactive::drawVobBox(DbgPainter& p) const { for(auto& i:attPos) { p.setBrush(Tempest::Color(0,1,0)); - p.drawPoint(nodePosition(i)); + p.drawPoint(nodePosition(nullptr, i, false)); } } @@ -237,9 +236,9 @@ void Interactive::setVisual(const zenkit::VirtualObject& vob) { if(auto mesh = visual.protoMesh()) { attPos.resize(mesh->pos.size()); for(size_t i=0;ipos[i].name; - attPos[i].pos = mesh->pos[i].transform; - attPos[i].node = mesh->pos[i].node; + attPos[i].name = mesh->pos[i].name; + attPos[i].pos = mesh->pos[i].transform; + attPos[i].nodeId = mesh->pos[i].node; } } setAnim(Interactive::Active); // setup default anim @@ -592,7 +591,7 @@ bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { } for(auto& i:attPos){ - auto pos = nodePosition(npc,i); + auto pos = nodePosition(&npc,i,false); if(npc.canRayHitPoint(pos,freeLos)) return true; } @@ -605,9 +604,9 @@ bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { return false; } -Tempest::Vec3 Interactive::nearestPoint(const Npc& to) const { +Tempest::Vec3 Interactive::nearestPoint(const Npc& to, bool groundPos) const { if(auto p = findNearest(to)) - return nodePosition(*p); + return nodePosition(&to, *p, groundPos); return displayPosition(); } @@ -635,6 +634,11 @@ Interactive::Pos* Interactive::findNearest(const Npc& to) { return findNearest(*this,to); } +float Interactive::qDistTo(const Npc &npc, const Interactive::Pos &to) const { + auto p = nodePosition(&npc, to, false); + return npc.qDistTo(p); + } + void Interactive::implAddItem(std::string_view name) { size_t sep = name.find(':'); if(sep!=std::string::npos) { @@ -768,7 +772,7 @@ bool Interactive::canQuitAtState(const Npc& npc, int32_t state) const { bool Interactive::attach(Npc& npc, Interactive::Pos& to) { assert(to.user==nullptr); - auto mat = nodeTranform(npc,to); + auto mat = nodeTranform(&npc,to); Tempest::Vec3 mv = {}; mat.project(mv); @@ -891,59 +895,47 @@ void Interactive::setDir(Npc &npc, const Tempest::Matrix4x4 &mat) { npc.setDirection(Tempest::Vec3(x1-x0, y1-y0, z1-z0)); } -float Interactive::qDistTo(const Npc &npc, const Interactive::Pos &to) const { - auto p = nodePosition(to); - return npc.qDistTo(p); - } - -Tempest::Vec3 Interactive::nodePosition(const Interactive::Pos &to) const { - auto p = nodeTranform(to.name); +Tempest::Vec3 Interactive::nodePosition(const Npc* npc, const Interactive::Pos &to, bool groundPos) const { + auto p = nodeTranform(npc, to); float x = p.at(3,0); float y = p.at(3,1); float z = p.at(3,2); return Tempest::Vec3(x,y,z); + //NOTE: no need in 'ground rays' - distance to point check allows extra distance on Y +#if 0 + if(!groundPos) + return Tempest::Vec3(x,y,z); + + auto pos = Tempest::Vec3(x,y,z); + auto ray = world.physic()->ray(pos, pos+Tempest::Vec3(0,MOBSI_SEARCH_DISTANCE,0)); + if(ray.hasCol) { + // project position on landscape + pos = ray.v; + } + return pos; +#endif } -Tempest::Vec3 Interactive::nodePosition(const Npc& npc, const Interactive::Pos &to) const { - auto p = nodeTranform(npc,to); - float x = p.at(3,0); - float y = p.at(3,1); - float z = p.at(3,2); - return {x,y,z}; - } - -Tempest::Matrix4x4 Interactive::nodeTranform(std::string_view nodeName) const { +Tempest::Matrix4x4 Interactive::nodeTranform(const Npc* npc, const Pos& p) const { auto mesh = visual.protoMesh(); - if(mesh==nullptr || mesh->skeleton==nullptr) - return Tempest::Matrix4x4(); + if(mesh==nullptr) + return transform(); - auto id = mesh->skeleton->findNode(nodeName); - if(id!=size_t(-1)) - return visual.bone(id); - return transform(); - } + const auto nodeId = p.nodeId; //mesh->findNode(p.name); + if(nodeId==size_t(-1)) + return transform(); + + if(p.isDistPos() && npc!=nullptr) { + auto pos = position(); + auto npos = visual.bone(nodeId); -Tempest::Matrix4x4 Interactive::nodeTranform(const Npc& npc, const Pos& p) const { - auto mesh = visual.protoMesh(); - if(mesh==nullptr) - return Tempest::Matrix4x4(); - - auto nodeId = mesh->findNode(p.name); - if(p.isDistPos()) { - auto pos = position(); - Tempest::Matrix4x4 npos; - if(nodeId!=size_t(-1)) { - npos = visual.bone(nodeId); - } else { - npos.identity(); - } float nodeX = npos.at(3,0) - pos.x; float nodeY = npos.at(3,1) - pos.y; float nodeZ = npos.at(3,2) - pos.z; float dist = std::sqrt(nodeX*nodeX + nodeZ*nodeZ); - float npcX = npc.position().x - pos.x; - float npcZ = npc.position().z - pos.z; + float npcX = npc->position().x - pos.x; + float npcZ = npc->position().z - pos.z; float npcA = 180.f*std::atan2(npcZ,npcX)/float(M_PI); npos.identity(); @@ -960,9 +952,13 @@ Tempest::Matrix4x4 Interactive::nodeTranform(const Npc& npc, const Pos& p) const return npos; } - if(nodeId!=size_t(-1)) - return visual.bone(nodeId); + return visual.bone(nodeId); + } +Tempest::Matrix4x4 Interactive::nodeTranform(std::string_view nodeName) const { + for(auto& i:attPos) + if(i.name==nodeName) + return nodeTranform(nullptr, i); return transform(); } @@ -1067,7 +1063,7 @@ void Interactive::marchInteractives(DbgPainter &p) const { p.setBrush(Tempest::Color(1.0,0,0,1)); for(auto& m:attPos) { - auto pos = nodePosition(m); + auto pos = nodePosition(nullptr, m, false); float x = pos.x; float y = pos.y; diff --git a/game/world/objects/interactive.h b/game/world/objects/interactive.h index dcae050d2..48e3d5b4b 100644 --- a/game/world/objects/interactive.h +++ b/game/world/objects/interactive.h @@ -75,7 +75,7 @@ class Interactive : public Vob { uint32_t stateMask() const; bool canSeeNpc(const Npc &npc, bool freeLos) const; - Tempest::Vec3 nearestPoint(const Npc& to) const; + Tempest::Vec3 nearestPoint(const Npc& to, bool groundPos) const; bool isAvailable() const; bool isStaticState() const; @@ -100,8 +100,8 @@ class Interactive : public Vob { Npc* user = nullptr; Phase started = NotStarted; bool attachMode = false; + size_t nodeId = 0; - size_t node=0; Tempest::Matrix4x4 pos; std::string_view posTag() const; @@ -109,10 +109,9 @@ class Interactive : public Vob { bool isDistPos() const; }; - auto nodePosition(const Pos &to) const -> Tempest::Vec3; - auto nodePosition(const Npc& npc, const Pos &p) const -> Tempest::Vec3; + auto nodePosition(const Npc* npc, const Pos &to, bool groundPos) const -> Tempest::Vec3; + Tempest::Matrix4x4 nodeTranform(const Npc* npc, const Pos &p) const; Tempest::Matrix4x4 nodeTranform(std::string_view nodeName) const; - Tempest::Matrix4x4 nodeTranform(const Npc& npc, const Pos &p) const; void moveEvent() override; float extendedSearchRadius() const override; diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 39290587e..95962dae0 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -688,7 +688,7 @@ auto Npc::collosionCenter() const -> Vec3 { return physic.center(); } -Npc *Npc::lookAtTarget() const { +Npc* Npc::lookAtTarget() const { return currentLookAtNpc; } @@ -716,7 +716,7 @@ float Npc::qDistTo(const Npc &p) const { } float Npc::qDistTo(const Interactive &p) const { - auto pos = p.nearestPoint(*this); + auto pos = p.nearestPoint(*this,false); return qDistTo(pos); } @@ -1848,7 +1848,7 @@ bool Npc::setGoToLadder() { if(go2.wp==nullptr || go2.wp!=wayPath.first()) return false; auto inter = go2.wp->ladder; - auto pos = inter->nearestPoint(*this); + auto pos = inter->nearestPoint(*this,true); if(MoveAlgo::isClose(*this,pos,MAX_AI_USE_DISTANCE)) { if(!inter->isAvailable()) setAnim(AnimationSolver::Idle); @@ -2483,15 +2483,8 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { } if(inter!=nullptr) { - auto pos = inter->nearestPoint(*this); - auto ray = owner.physic()->ray(pos, pos+Vec3(0,-MAX_AI_USE_DISTANCE,0)); - if(ray.hasCol) { - // project position on landscape - pos = ray.v; - } - - auto dp = pos - position(); - if(currentInteract==nullptr && dp.quadLength()>MAX_AI_USE_DISTANCE*MAX_AI_USE_DISTANCE) { // too far + auto pos = inter->nearestPoint(*this,true); + if(currentInteract==nullptr && !MoveAlgo::isClose(*this, pos, MAX_AI_USE_DISTANCE)) { // too far go2.set(pos); // go to MOBSI and then complete AI_UseMob queue.pushFront(std::move(act)); diff --git a/game/world/waymatrix.cpp b/game/world/waymatrix.cpp index c0d6173e2..1db4176ff 100644 --- a/game/world/waymatrix.cpp +++ b/game/world/waymatrix.cpp @@ -78,7 +78,7 @@ const WayPoint *WayMatrix::findWayPoint(const Vec3& at, const std::function& filter) const { auto& index = findFpIndex(name); - return findFreePoint(at.x,at.y,at.z,index,filter); + return findFreePoint(at,index,filter); } const WayPoint *WayMatrix::findNextPoint(const Vec3& at) const { @@ -264,13 +264,13 @@ const WayMatrix::FpIndex &WayMatrix::findFpIndex(std::string_view name) const { return *it; } -const WayPoint *WayMatrix::findFreePoint(float x, float y, float z, const FpIndex& ind, +const WayPoint *WayMatrix::findFreePoint(const Vec3& at, const FpIndex& ind, const std::function& filter) const { float R = distanceThreshold; - auto b = std::lower_bound(ind.index.begin(),ind.index.end(), x-R ,[](const WayPoint *a, float b){ + auto b = std::lower_bound(ind.index.begin(),ind.index.end(), at.x-R ,[](const WayPoint *a, float b){ return a->pos.x < b; }); - auto e = std::upper_bound(ind.index.begin(),ind.index.end(), x+R ,[](float a,const WayPoint *b){ + auto e = std::upper_bound(ind.index.begin(),ind.index.end(), at.x+R ,[](float a,const WayPoint *b){ return a < b->pos.x; }); @@ -281,10 +281,7 @@ const WayPoint *WayMatrix::findFreePoint(float x, float y, float z, const FpInde auto& w = **i; if(!w.isFreePoint()) continue; - float dx = w.pos.x-x; - float dy = w.pos.y-y; - float dz = w.pos.z-z; - float l = dx*dx+dy*dy+dz*dz; + float l = (w.pos - at).quadLength(); if(l>dist) continue; if(!filter(w)) diff --git a/game/world/waymatrix.h b/game/world/waymatrix.h index 83f041268..f850beb14 100644 --- a/game/world/waymatrix.h +++ b/game/world/waymatrix.h @@ -72,6 +72,6 @@ class WayMatrix final { void calculateLadderPoints(); const FpIndex& findFpIndex(std::string_view name) const; - const WayPoint* findFreePoint(float x, float y, float z, const FpIndex &ind, + const WayPoint* findFreePoint(const Tempest::Vec3& at, const FpIndex &ind, const std::function& filter) const; }; diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index 94947f35d..6a5224053 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -850,8 +850,8 @@ void WorldObjects::drawVobBoxNpcNear(DbgPainter& p) const { } Interactive *WorldObjects::availableMob(const Npc &pl, std::string_view dest) { - const float dist=100*10.f; - Interactive* ret =nullptr; + const float dist = MOBSI_SEARCH_DISTANCE; + Interactive* ret = nullptr; if(auto i = pl.interactive()){ if(i->checkMobName(dest)) From ea6e671f9bed0e497772e3da94d59cb778130fd4 Mon Sep 17 00:00:00 2001 From: Try Date: Wed, 18 Mar 2026 00:21:14 +0100 Subject: [PATCH 16/19] cleanups --- game/game/movealgo.cpp | 2 +- game/world/waymatrix.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/game/game/movealgo.cpp b/game/game/movealgo.cpp index e651ce468..74c92ecd3 100644 --- a/game/game/movealgo.cpp +++ b/game/game/movealgo.cpp @@ -725,7 +725,7 @@ bool MoveAlgo::isClose(const Npc& npc, const WayPoint& p, float dist) { } bool MoveAlgo::isClose(const Npc& npc, const Tempest::Vec3& p, float dist) { - auto dp = (p - npc.position()); + auto dp = (p - npc.centerPosition()); float len = dp.quadLength(); // NOTE: extra check fo vertical only distances: // some repair planks are too high (~5 meters) in the air diff --git a/game/world/waymatrix.h b/game/world/waymatrix.h index f850beb14..7562329c7 100644 --- a/game/world/waymatrix.h +++ b/game/world/waymatrix.h @@ -44,12 +44,12 @@ class WayMatrix final { World& world; // scripting doc says 20m, but number seems to be incorrect // Vatras requires at least 8 meters - // Keroloth requires less than 8.25 meters + // Keroloth requires less than 8.18 meters // Abuyin requires less than 10 meters - // Harry(CoM) requires less(!) than 8 meters + // Harry(CoM) requires less(!) than 8 meters (~7.96) // Gothic 1 range is identical // Vanilla is buggy here as Vatras can't reach his praying spot from teaching location - float distanceThreshold = 820.f; + float distanceThreshold = 805.f; std::vector edges; From e5d70f8e1093b00a8db9a94493a39b09f687c695 Mon Sep 17 00:00:00 2001 From: Try Date: Wed, 18 Mar 2026 22:44:41 +0100 Subject: [PATCH 17/19] cleanups --- game/game/constants.h | 2 +- game/world/objects/fireplace.cpp | 4 ++-- game/world/objects/interactive.cpp | 14 +++++++++----- game/world/objects/interactive.h | 2 +- game/world/objects/npc.cpp | 7 ++----- game/world/waymatrix.h | 4 ++-- game/world/worldobjects.cpp | 4 ++++ 7 files changed, 21 insertions(+), 16 deletions(-) diff --git a/game/game/constants.h b/game/game/constants.h index 8efbd7afd..58fec43bf 100644 --- a/game/game/constants.h +++ b/game/game/constants.h @@ -126,7 +126,7 @@ enum Guild: uint32_t { }; enum { - MAX_AI_USE_DISTANCE = 150, + MAX_AI_USE_DISTANCE = 165, MOBSI_SEARCH_DISTANCE = 100*10, }; diff --git a/game/world/objects/fireplace.cpp b/game/world/objects/fireplace.cpp index 15ca5a7bb..c1ee3a4fe 100644 --- a/game/world/objects/fireplace.cpp +++ b/game/world/objects/fireplace.cpp @@ -16,13 +16,13 @@ void FirePlace::load(Serialize& fin) { void FirePlace::moveEvent() { Interactive::moveEvent(); - auto at = this->nodeTranform(fireSlot); + auto at = this->mapBone(fireSlot); fireVobtree.setObjMatrix(at); } void FirePlace::onStateChanged() { if(stateId()>0) { - auto at = this->nodeTranform(fireSlot); + auto at = this->mapBone(fireSlot); fireVobtree = VobBundle(world,fireVobtreeName,Vob::Startup); fireVobtree.setObjMatrix(at); } else { diff --git a/game/world/objects/interactive.cpp b/game/world/objects/interactive.cpp index 82c04bb4c..225462b06 100644 --- a/game/world/objects/interactive.cpp +++ b/game/world/objects/interactive.cpp @@ -955,11 +955,15 @@ Tempest::Matrix4x4 Interactive::nodeTranform(const Npc* npc, const Pos& p) const return visual.bone(nodeId); } -Tempest::Matrix4x4 Interactive::nodeTranform(std::string_view nodeName) const { - for(auto& i:attPos) - if(i.name==nodeName) - return nodeTranform(nullptr, i); - return transform(); +Tempest::Matrix4x4 Interactive::mapBone(std::string_view nodeName) const { + auto mesh = visual.protoMesh(); + if(mesh==nullptr) + return transform(); + + const auto nodeId = mesh->findNode(nodeName); + if(nodeId==size_t(-1)) + return transform(); + return visual.bone(nodeId); } const Animation::Sequence* Interactive::setAnim(Interactive::Anim t) { diff --git a/game/world/objects/interactive.h b/game/world/objects/interactive.h index 48e3d5b4b..4b35abb21 100644 --- a/game/world/objects/interactive.h +++ b/game/world/objects/interactive.h @@ -111,7 +111,7 @@ class Interactive : public Vob { auto nodePosition(const Npc* npc, const Pos &to, bool groundPos) const -> Tempest::Vec3; Tempest::Matrix4x4 nodeTranform(const Npc* npc, const Pos &p) const; - Tempest::Matrix4x4 nodeTranform(std::string_view nodeName) const; + Tempest::Matrix4x4 mapBone(std::string_view nodeName) const; void moveEvent() override; float extendedSearchRadius() const override; diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 95962dae0..4b22ee30c 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -680,7 +680,8 @@ Bounds Npc::bounds() const { Vec3 Npc::centerPosition() const { auto p = position(); - p.y = physic.centerY(); + //p.y = physic.centerY(); + p.y += visual.pose().translateY(); return p; } @@ -2326,8 +2327,6 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { queue.pushFront(std::move(act)); break; } - currentFp = nullptr; - currentFpLock = FpLock(); go2.set(act.target); wayPath.clear(); break; @@ -2338,8 +2337,6 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { } auto fp = owner.findNextFreePoint(*this,act.s0); if(fp!=nullptr) { - currentFp = fp; - currentFpLock = FpLock(*fp); go2.set(fp,GoToHint::GT_NextFp); wayPath.clear(); } diff --git a/game/world/waymatrix.h b/game/world/waymatrix.h index 7562329c7..2441f3842 100644 --- a/game/world/waymatrix.h +++ b/game/world/waymatrix.h @@ -43,13 +43,13 @@ class WayMatrix final { World& world; // scripting doc says 20m, but number seems to be incorrect - // Vatras requires at least 8 meters + // Vatras requires at least 8.1 meters; Vatras is broken in vanilla // Keroloth requires less than 8.18 meters // Abuyin requires less than 10 meters // Harry(CoM) requires less(!) than 8 meters (~7.96) // Gothic 1 range is identical // Vanilla is buggy here as Vatras can't reach his praying spot from teaching location - float distanceThreshold = 805.f; + float distanceThreshold = 800.f; std::vector edges; diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index 6a5224053..cf9f23b8e 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -981,6 +981,10 @@ void WorldObjects::resetPositionToTA() { npcRemoved.push_back(std::move(i)); npcInvalid.clear(); + for(auto& i : npcArr) { + i->attachToPoint(nullptr); + } + for(size_t i=0;i Date: Wed, 18 Mar 2026 23:04:10 +0100 Subject: [PATCH 18/19] cleanups --- game/world/objects/npc.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 4b22ee30c..2f4723808 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -682,10 +682,11 @@ Vec3 Npc::centerPosition() const { auto p = position(); //p.y = physic.centerY(); p.y += visual.pose().translateY(); + p.y += 15; // seem to be off by ~15 centimeters, according to comparations vanilla testing return p; } -auto Npc::collosionCenter() const -> Vec3 { +Vec3 Npc::collosionCenter() const { return physic.center(); } @@ -2327,6 +2328,7 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { queue.pushFront(std::move(act)); break; } + attachToPoint(nullptr); go2.set(act.target); wayPath.clear(); break; @@ -2337,6 +2339,7 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { } auto fp = owner.findNextFreePoint(*this,act.s0); if(fp!=nullptr) { + attachToPoint(fp); go2.set(fp,GoToHint::GT_NextFp); wayPath.clear(); } @@ -4109,6 +4112,7 @@ bool Npc::setInteraction(Interactive *id, bool quick) { if(id->attach(*this)) { currentInteract = id; + attachToPoint(nullptr); //NOTE: Fajeth campfire if(!quick) { visual.stopAnim(*this,""); setAnimRotate(0); From 96c84030be4ffd36a8c77c910a55f8e47bf598ad Mon Sep 17 00:00:00 2001 From: Try Date: Wed, 18 Mar 2026 23:36:26 +0100 Subject: [PATCH 19/19] remove 'ground' rays for mobsi --- game/world/objects/interactive.cpp | 20 ++++++++++---------- game/world/objects/interactive.h | 6 +++--- game/world/objects/npc.cpp | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/game/world/objects/interactive.cpp b/game/world/objects/interactive.cpp index 225462b06..dc73f6b1b 100644 --- a/game/world/objects/interactive.cpp +++ b/game/world/objects/interactive.cpp @@ -186,7 +186,7 @@ void Interactive::drawVobBox(DbgPainter& p) const { for(auto& i:attPos) { p.setBrush(Tempest::Color(0,1,0)); - p.drawPoint(nodePosition(nullptr, i, false)); + p.drawPoint(nodePosition(nullptr, i)); } } @@ -591,7 +591,7 @@ bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { } for(auto& i:attPos){ - auto pos = nodePosition(&npc,i,false); + auto pos = nodePosition(&npc,i); if(npc.canRayHitPoint(pos,freeLos)) return true; } @@ -604,9 +604,9 @@ bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { return false; } -Tempest::Vec3 Interactive::nearestPoint(const Npc& to, bool groundPos) const { +Tempest::Vec3 Interactive::nearestPoint(const Npc& to) const { if(auto p = findNearest(to)) - return nodePosition(&to, *p, groundPos); + return nodePosition(&to, *p); return displayPosition(); } @@ -635,7 +635,7 @@ Interactive::Pos* Interactive::findNearest(const Npc& to) { } float Interactive::qDistTo(const Npc &npc, const Interactive::Pos &to) const { - auto p = nodePosition(&npc, to, false); + auto p = nodePosition(&npc, to); return npc.qDistTo(p); } @@ -895,7 +895,7 @@ void Interactive::setDir(Npc &npc, const Tempest::Matrix4x4 &mat) { npc.setDirection(Tempest::Vec3(x1-x0, y1-y0, z1-z0)); } -Tempest::Vec3 Interactive::nodePosition(const Npc* npc, const Interactive::Pos &to, bool groundPos) const { +Tempest::Vec3 Interactive::nodePosition(const Npc* npc, const Interactive::Pos &to) const { auto p = nodeTranform(npc, to); float x = p.at(3,0); float y = p.at(3,1); @@ -916,16 +916,16 @@ Tempest::Vec3 Interactive::nodePosition(const Npc* npc, const Interactive::Pos & #endif } -Tempest::Matrix4x4 Interactive::nodeTranform(const Npc* npc, const Pos& p) const { +Tempest::Matrix4x4 Interactive::nodeTranform(const Npc* npc, const Pos& to) const { auto mesh = visual.protoMesh(); if(mesh==nullptr) return transform(); - const auto nodeId = p.nodeId; //mesh->findNode(p.name); + const auto nodeId = to.nodeId; //mesh->findNode(p.name); if(nodeId==size_t(-1)) return transform(); - if(p.isDistPos() && npc!=nullptr) { + if(to.isDistPos() && npc!=nullptr) { auto pos = position(); auto npos = visual.bone(nodeId); @@ -1067,7 +1067,7 @@ void Interactive::marchInteractives(DbgPainter &p) const { p.setBrush(Tempest::Color(1.0,0,0,1)); for(auto& m:attPos) { - auto pos = nodePosition(nullptr, m, false); + auto pos = nodePosition(nullptr, m); float x = pos.x; float y = pos.y; diff --git a/game/world/objects/interactive.h b/game/world/objects/interactive.h index 4b35abb21..46800588c 100644 --- a/game/world/objects/interactive.h +++ b/game/world/objects/interactive.h @@ -75,7 +75,7 @@ class Interactive : public Vob { uint32_t stateMask() const; bool canSeeNpc(const Npc &npc, bool freeLos) const; - Tempest::Vec3 nearestPoint(const Npc& to, bool groundPos) const; + Tempest::Vec3 nearestPoint(const Npc& to) const; bool isAvailable() const; bool isStaticState() const; @@ -109,8 +109,8 @@ class Interactive : public Vob { bool isDistPos() const; }; - auto nodePosition(const Npc* npc, const Pos &to, bool groundPos) const -> Tempest::Vec3; - Tempest::Matrix4x4 nodeTranform(const Npc* npc, const Pos &p) const; + Tempest::Vec3 nodePosition(const Npc* npc, const Pos &to) const; + Tempest::Matrix4x4 nodeTranform(const Npc* npc, const Pos &to) const; Tempest::Matrix4x4 mapBone(std::string_view nodeName) const; void moveEvent() override; diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 2f4723808..4db9e8f06 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -718,7 +718,7 @@ float Npc::qDistTo(const Npc &p) const { } float Npc::qDistTo(const Interactive &p) const { - auto pos = p.nearestPoint(*this,false); + auto pos = p.nearestPoint(*this); return qDistTo(pos); } @@ -1850,7 +1850,7 @@ bool Npc::setGoToLadder() { if(go2.wp==nullptr || go2.wp!=wayPath.first()) return false; auto inter = go2.wp->ladder; - auto pos = inter->nearestPoint(*this,true); + auto pos = inter->nearestPoint(*this); if(MoveAlgo::isClose(*this,pos,MAX_AI_USE_DISTANCE)) { if(!inter->isAvailable()) setAnim(AnimationSolver::Idle); @@ -2483,7 +2483,7 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { } if(inter!=nullptr) { - auto pos = inter->nearestPoint(*this,true); + auto pos = inter->nearestPoint(*this); if(currentInteract==nullptr && !MoveAlgo::isClose(*this, pos, MAX_AI_USE_DISTANCE)) { // too far go2.set(pos); // go to MOBSI and then complete AI_UseMob