diff --git a/game/game/constants.h b/game/game/constants.h index ee0705727..58fec43bf 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 = 165, + MOBSI_SEARCH_DISTANCE = 100*10, }; enum { diff --git a/game/game/gamescript.cpp b/game/game/gamescript.cpp index 3cbdaec57..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) { @@ -3384,7 +3390,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/game/movealgo.cpp b/game/game/movealgo.cpp index 7a49feea2..74c92ecd3 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 (lenplayer()==nullptr) + return; + auto pl = w->player(); + auto focus = findFocus(¤tFocus); + if(focus.interactive!=nullptr) { + focus.interactive->drawVobRay(p, *pl); + } + if(focus.item!=nullptr) { + focus.item->drawVobRay(p, *pl); + } + if(focus.npc!=nullptr) { + pl->drawVobRay(p, *focus.npc); + } + } + void PlayerControl::tickFocus() { currentFocus = findFocus(¤tFocus); setTarget(currentFocus.npc); @@ -358,7 +375,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 +384,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) @@ -481,7 +498,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 3b6106263..6f0dbf1f2 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 drawVobRay(DbgPainter& p) const; + void changeZoom(int delta); void tickFocus(); void clearFocus(); @@ -161,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/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/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/graphics/mesh/protomesh.cpp b/game/graphics/mesh/protomesh.cpp index 2f42d7d69..486c1df7d 100644 --- a/game/graphics/mesh/protomesh.cpp +++ b/game/graphics/mesh/protomesh.cpp @@ -318,26 +318,18 @@ size_t ProtoMesh::skinedNodesCount() const { return ret; } -Tempest::Matrix4x4 ProtoMesh::mapToRoot(size_t n) const { - Tempest::Matrix4x4 m; - m.identity(); - - while(nfindNode(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..da684c1a7 100644 --- a/game/graphics/mesh/protomesh.h +++ b/game/graphics/mesh/protomesh.h @@ -89,8 +89,8 @@ class ProtoMesh { std::string scheme, fname; 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/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/mainwindow.cpp b/game/mainwindow.cpp index b3856ef8b..f58339f41 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -263,22 +263,25 @@ 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); } + + if(Gothic::inst().doVobRays() && c!=nullptr) { + DbgPainter dbg(p,c->viewProj(),w(),h()); + player.drawVobRay(dbg); + } } if(auto wx = Gothic::inst().worldView()) { 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/physics/dynamicworld.cpp b/game/physics/dynamicworld.cpp index cfda0b1b8..5ca191fe5 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); @@ -921,6 +922,48 @@ 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, + const float padd) { + 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, padd); + 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, + const float padd) { + 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 : std::max(0.f, tNear-padd); + } + std::string_view DynamicWorld::validateSectorName(std::string_view name) const { return landMesh->validateSectorName(name); } @@ -965,6 +1008,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 c135abab6..d236f8d57 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); @@ -258,6 +259,13 @@ 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, + 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 float padd = 0.f); + std::string_view validateSectorName(std::string_view name) const; private: diff --git a/game/utils/dbgpainter.cpp b/game/utils/dbgpainter.cpp index 45517ef9d..1298c7f3c 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,30 @@ 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)); + } + +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 18cb3d87f..58f04aabb 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,8 @@ 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); + void drawObb (const Tempest::Matrix4x4& m, const Tempest::Vec3 bbox[2]); Tempest::Painter& painter; const Tempest::Matrix4x4 mvp; 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;inodeTranform(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 0b8977d2d..dc73f6b1b 100644 --- a/game/world/objects/interactive.cpp +++ b/game/world/objects/interactive.cpp @@ -6,7 +6,6 @@ #include #include "game/serialize.h" -#include "graphics/mesh/skeleton.h" #include "utils/string_frm.h" #include "world/triggers/abstracttrigger.h" #include "world/objects/npc.h" @@ -178,6 +177,44 @@ 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()) { + p.drawObb(transform(), mesh->bboxCol()); + } + + for(auto& i:attPos) { + p.setBrush(Tempest::Color(0,1,0)); + p.drawPoint(nodePosition(nullptr, i)); + } + } + +void Interactive::drawVobRay(DbgPainter& p, const Npc& npc) const { + auto head = npc.mapHeadBone(); + + if(auto mesh = visual.protoMesh()) { + auto bbox = mesh->bboxCol(); + auto boxMin = bbox[0]; + auto boxMax = bbox[1]; + auto at = (boxMin+boxMax)*0.5f; + transform().project(at); + + 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(head+dir*tHit, true)) + accessable = false; + p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); + p.drawLine(head, head+dir*tHit); + + p.setPen(Tempest::Color(1,1,0)); + p.drawLine(head+dir*tHit, at); + } + } + void Interactive::resetPositionToTA(int32_t state) { for(auto& i:attPos) if(i.user!=nullptr && i.user->isPlayer()) @@ -199,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 @@ -539,24 +576,37 @@ uint32_t Interactive::stateMask() const { } bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const { + 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 - head).length(); + auto dir = (at - head)/tMax; + float tHit = DynamicWorld::rayBox(head, dir, tMax, transform(), bbox[0], bbox[1], 0.1f); + + if(!npc.canRayHitPoint(head+dir*tHit, true)) + return false; + } + for(auto& i:attPos){ - auto pos = nodePosition(npc,i); - if(npc.canSeeNpc(pos,freeLos)) + auto pos = nodePosition(&npc,i); + if(npc.canRayHitPoint(pos,freeLos)) return true; } // graves - if(attPos.size()==0){ - auto pos = displayPosition(); - if(npc.canSeeNpc(pos,freeLos)) - return true; + if(attPos.size()==0) { + // ray-box test should be engough + return true; } return false; } Tempest::Vec3 Interactive::nearestPoint(const Npc& to) const { if(auto p = findNearest(to)) - return worldPos(*p); + return nodePosition(&to, *p); return displayPosition(); } @@ -584,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); + return npc.qDistTo(p); + } + void Interactive::implAddItem(std::string_view name) { size_t sep = name.find(':'); if(sep!=std::string::npos) { @@ -672,20 +727,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) @@ -731,13 +772,12 @@ 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); - float x=0, y=0, z=0; - mat.project(x,y,z); + auto mat = nodeTranform(&npc,to); - const Tempest::Vec3 mv = {x,y-npc.translateY(),z}; + Tempest::Vec3 mv = {}; + mat.project(mv); - if((npc.position()-mv).quadLength()>MAX_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); @@ -855,32 +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 = worldPos(to); - return npc.qDistTo(p); +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 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::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 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(); - } + return transform(); + + const auto nodeId = to.nodeId; //mesh->findNode(p.name); + if(nodeId==size_t(-1)) + return transform(); + + if(to.isDistPos() && npc!=nullptr) { + auto pos = position(); + auto npos = visual.bone(nodeId); + 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(); @@ -897,30 +952,18 @@ Tempest::Matrix4x4 Interactive::nodeTranform(const Npc &npc, const Pos& p) const return npos; } - if(nodeId!=size_t(-1)) - return visual.bone(nodeId); - - return transform(); + return visual.bone(nodeId); } -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}; - } - -Tempest::Matrix4x4 Interactive::nodeTranform(std::string_view nodeName) const { +Tempest::Matrix4x4 Interactive::mapBone(std::string_view nodeName) 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); - auto ret = transform(); - if(id!=size_t(-1)) - ret = visual.bone(id); - return ret; + 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) { @@ -1024,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(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 8a344e880..46800588c 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); @@ -86,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, @@ -103,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; @@ -112,6 +109,15 @@ class Interactive : public Vob { bool isDistPos() 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; + float extendedSearchRadius() const override; + virtual void onStateChanged(){} + + private: void setVisual(const zenkit::VirtualObject& vob); void invokeStateFunc(Npc &npc); void implTick(Pos &p); @@ -135,10 +141,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; @@ -172,7 +175,5 @@ class Interactive : public Vob { bool animChanged = false; std::vector attPos; - PhysicMesh physic; - ObjVisual visual; }; diff --git a/game/world/objects/item.cpp b/game/world/objects/item.cpp index 515f5c6fa..fec37e64a 100644 --- a/game/world/objects/item.cpp +++ b/game/world/objects/item.cpp @@ -4,10 +4,11 @@ #include "game/serialize.h" #include "game/gamescript.h" -#include "utils/versioninfo.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 +21,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 +62,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 +82,38 @@ Item::Item(Item &&it) Item::~Item() { } +void Item::drawVobBox(DbgPainter& p) const { + p.setPen(Tempest::Color(1,0,0)); + if(auto mesh = visual.protoMesh()) { + p.drawObb(transform(), mesh->bboxCol()); + + auto v = midPosition(); + p.setPen(Tempest::Color(0,1,0)); + p.drawPoint(v); + } + } + +void Item::drawVobRay(DbgPainter& p, const Npc& npc) const { + 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 { auto& h = *hitem; fout.write(uint32_t(h.symbol_index())); @@ -99,7 +132,7 @@ void Item::save(Serialize &fout) const { } void Item::clearView() { - view = MeshObjects::Mesh(); + visual = MeshObjects::Mesh(); } bool Item::isTorchBurn() const { @@ -119,7 +152,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 +171,7 @@ void Item::setAsEquipped(bool e) { } void Item::setPhysicsEnable(World& world) { - setPhysicsEnable(view); + setPhysicsEnable(visual); world.invalidateVobIndex(); } @@ -181,11 +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 = view.bounds(); - auto v = (b.bbox[1]-b.bbox[0])*0.5; - // 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 { @@ -322,7 +364,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..0c656467f 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(); @@ -52,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; @@ -100,6 +104,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 b8dcf208e..4db9e8f06 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" @@ -42,7 +43,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; @@ -327,10 +328,6 @@ void Npc::postValidate() { currentInteract = nullptr; } -void Npc::drawVobBox(DbgPainter& p) const { - physic.debugDraw(p); - } - void Npc::saveAiState(Serialize& fout) const { fout.write(aniWaitTime,waitTime,faiWaitTime,outWaitTime); fout.write(uint8_t(aiPolicy)); @@ -681,17 +678,19 @@ 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(); + //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; } -Npc *Npc::lookAtTarget() const { +Vec3 Npc::collosionCenter() const { + return physic.center(); + } + +Npc* Npc::lookAtTarget() const { return currentLookAtNpc; } @@ -704,7 +703,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 +714,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 { @@ -1775,22 +1774,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(); @@ -2326,8 +2328,7 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { queue.pushFront(std::move(act)); break; } - currentFp = nullptr; - currentFpLock = FpLock(); + attachToPoint(nullptr); go2.set(act.target); wayPath.clear(); break; @@ -2338,8 +2339,7 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { } auto fp = owner.findNextFreePoint(*this,act.s0); if(fp!=nullptr) { - currentFp = fp; - currentFpLock = FpLock(*fp); + attachToPoint(fp); go2.set(fp,GoToHint::GT_NextFp); wayPath.clear(); } @@ -2484,9 +2484,7 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { if(inter!=nullptr) { auto pos = inter->nearestPoint(*this); - auto dp = pos-position(); - dp.y = 0; - if(currentInteract==nullptr && dp.quadLength()>MAX_AI_USE_DISTANCE*MAX_AI_USE_DISTANCE) { // too far + 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)); @@ -2948,7 +2946,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(); } @@ -3254,9 +3252,8 @@ Item* Npc::takeItem(Item& item) { return nullptr; } - auto dpos = item.position()-position(); - dpos.y-=translateY(); - 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; @@ -4115,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); @@ -4304,7 +4302,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; @@ -4404,12 +4402,44 @@ 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.bounds().midTr; - if(canSeeNpc(mid,freeLos)) - return true; - const auto ppos = oth.physic.position(); - 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; } @@ -4418,7 +4448,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; } @@ -4433,10 +4463,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) @@ -4489,37 +4515,41 @@ 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 = visual.mapHeadBone(); + 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; + 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 false; + + return true; } bool Npc::isAlignedToGround() const { diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index 5ac69471c..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); @@ -119,8 +120,8 @@ class Npc final { auto world() -> World&; - float translateY() const; auto centerPosition() const -> Tempest::Vec3; + auto collosionCenter() const -> Tempest::Vec3; Npc* lookAtTarget() const; auto portalName() -> std::string_view; auto formerPortalName() -> std::string_view; @@ -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; diff --git a/game/world/waymatrix.cpp b/game/world/waymatrix.cpp index 3526e6085..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 { @@ -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); } @@ -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..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 - // Keroloth requires less than 8.25 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 + // 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 = 800.f; std::vector edges; @@ -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/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 ef8b35d83..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); @@ -645,7 +650,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 +673,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; @@ -723,8 +728,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(); @@ -904,9 +909,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 +929,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 +946,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 +984,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 2770bc864..cf9f23b8e 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(); @@ -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); @@ -830,11 +830,28 @@ 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); + } + + 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) { - 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)) @@ -964,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;icheckPos(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; } }