From 5d8686ad008c129d43166e1e870e3b0e4146b44a Mon Sep 17 00:00:00 2001 From: SFENCE Date: Wed, 31 Jan 2024 21:48:36 +0100 Subject: [PATCH 1/5] Fix #11081 and disallow jump if player is in collision. --- src/client/localplayer.cpp | 18 +- src/collision.cpp | 350 +++++++++++++++++++++++-------------- src/collision.h | 5 + 3 files changed, 236 insertions(+), 137 deletions(-) diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index e606e1edc..a8fb31ddf 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -442,10 +442,14 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, // Move player to the maximal height when falling or when // the ledge is climbed on the next step. - // Smoothen the movement (based on 'position.Y = bmax.Y') - position.Y += y_diff * dtime * 22.0f + BS * 0.01f; - position.Y = std::min(position.Y, bmax.Y); - m_speed.Y = 0.0f; + // Check if there is space for sneak glitch + v3f pos(position.X, position.Y + y_diff * dtime * 22.0f + BS * 0.01f, position.Z); + if (!collisionCheckIntersection(env, m_client, m_collisionbox, pos)) { + // Smoothen the movement (based on 'position.Y = bmax.Y') + position.Y = pos.Y; + position.Y = std::min(position.Y, bmax.Y); + m_speed.Y = 0.0f; + } } // Allow jumping on node edges while sneaking @@ -542,6 +546,12 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, // Prevent sliding on the ground when jump speed is 0 m_can_jump = m_can_jump && jumpspeed != 0.0f; + // Prevent jump if in node/object + if (m_can_jump && could_sneak) { + if (collisionCheckIntersection(env, m_client, m_collisionbox, initial_position)) + m_can_jump = false; + } + // Autojump handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d); } diff --git a/src/collision.cpp b/src/collision.cpp index 8e778b402..479122eac 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -66,6 +66,12 @@ struct NearbyCollisionInfo { aabb3f box; }; +static bool add_area_node_boxes(const v3s16 &min, const v3s16 &max, IGameDef *gamedef, + Map *map, std::vector &cinfo); +static void add_object_boxes(Environment *env, const aabb3f &box_0, f32 dtime, + const v3f &pos_f, const v3f &speed_f, ActiveObject *self, + std::vector &cinfo); + // Helper functions: // Truncate floating point numbers to specified number of decimal places // in order to move all the floating point error to one side of the correct value @@ -288,73 +294,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, v3s16 min = floatToInt(minpos_f + box_0.MinEdge, BS) - v3s16(1, 1, 1); v3s16 max = floatToInt(maxpos_f + box_0.MaxEdge, BS) + v3s16(1, 1, 1); - bool any_position_valid = false; - - v3s16 p; - for (p.X = min.X; p.X <= max.X; p.X++) - for (p.Y = min.Y; p.Y <= max.Y; p.Y++) - for (p.Z = min.Z; p.Z <= max.Z; p.Z++) { - bool is_position_valid; - MapNode n = map->getNode(p, &is_position_valid); - - if (is_position_valid && n.getContent() != CONTENT_IGNORE) { - // Object collides into walkable nodes - - any_position_valid = true; - const NodeDefManager *nodedef = gamedef->getNodeDefManager(); - const ContentFeatures &f = nodedef->get(n); - - if (!f.walkable) - continue; - - // Negative bouncy may have a meaning, but we need +value here. - int n_bouncy_value = abs(itemgroup_get(f.groups, "bouncy")); - - int neighbors = 0; - if (f.drawtype == NDT_NODEBOX && - f.node_box.type == NODEBOX_CONNECTED) { - v3s16 p2 = p; - - p2.Y++; - getNeighborConnectingFace(p2, nodedef, map, n, 1, &neighbors); - - p2 = p; - p2.Y--; - getNeighborConnectingFace(p2, nodedef, map, n, 2, &neighbors); - - p2 = p; - p2.Z--; - getNeighborConnectingFace(p2, nodedef, map, n, 4, &neighbors); - - p2 = p; - p2.X--; - getNeighborConnectingFace(p2, nodedef, map, n, 8, &neighbors); - - p2 = p; - p2.Z++; - getNeighborConnectingFace(p2, nodedef, map, n, 16, &neighbors); - - p2 = p; - p2.X++; - getNeighborConnectingFace(p2, nodedef, map, n, 32, &neighbors); - } - std::vector nodeboxes; - n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors); - - // Calculate float position only once - v3f posf = intToFloat(p, BS); - for (auto box : nodeboxes) { - box.MinEdge += posf; - box.MaxEdge += posf; - cinfo.emplace_back(false, n_bouncy_value, p, box); - } - } else { - // Collide with unloaded nodes (position invalid) and loaded - // CONTENT_IGNORE nodes (position valid) - aabb3f box = getNodeBox(p, BS); - cinfo.emplace_back(true, 0, p, box); - } - } + bool any_position_valid = add_area_node_boxes(min, max, gamedef, map, cinfo); // Do not move if world has not loaded yet, since custom node boxes // are not available for collision detection. @@ -370,72 +310,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, if(collideWithObjects) { /* add object boxes to cinfo */ - - std::vector objects; -#ifndef SERVER - ClientEnvironment *c_env = dynamic_cast(env); - if (c_env != 0) { - // Calculate distance by speed, add own extent and 1.5m of tolerance - f32 distance = speed_f->getLength() * dtime + - box_0.getExtent().getLength() + 1.5f * BS; - std::vector clientobjects; - c_env->getActiveObjects(*pos_f, distance, clientobjects); - - for (auto &clientobject : clientobjects) { - // Do collide with everything but itself and the parent CAO - if (!self || (self != clientobject.obj && - self != clientobject.obj->getParent())) { - objects.push_back((ActiveObject*) clientobject.obj); - } - } - } - else -#endif - { - if (s_env != NULL) { - // Calculate distance by speed, add own extent and 1.5m of tolerance - f32 distance = speed_f->getLength() * dtime + - box_0.getExtent().getLength() + 1.5f * BS; - - // search for objects which are not us, or we are not its parent - // we directly use the callback to populate the result to prevent - // a useless result loop here - auto include_obj_cb = [self, &objects] (ServerActiveObject *obj) { - if (!obj->isGone() && - (!self || (self != obj && self != obj->getParent()))) { - objects.push_back((ActiveObject *)obj); - } - return false; - }; - - std::vector s_objects; - s_env->getObjectsInsideRadius(s_objects, *pos_f, distance, include_obj_cb); - } - } - - for (std::vector::const_iterator iter = objects.begin(); - iter != objects.end(); ++iter) { - ActiveObject *object = *iter; - - if (object && object->collideWithObjects()) { - aabb3f object_collisionbox; - if (object->getCollisionBox(&object_collisionbox)) - cinfo.emplace_back(object, 0, object_collisionbox); - } - } -#ifndef SERVER - if (self && c_env) { - LocalPlayer *lplayer = c_env->getLocalPlayer(); - if (lplayer->getParent() == nullptr) { - aabb3f lplayer_collisionbox = lplayer->getCollisionbox(); - v3f lplayer_pos = lplayer->getPosition(); - lplayer_collisionbox.MinEdge += lplayer_pos; - lplayer_collisionbox.MaxEdge += lplayer_pos; - ActiveObject *obj = (ActiveObject*) lplayer->getCAO(); - cinfo.emplace_back(obj, 0, lplayer_collisionbox); - } - } -#endif + add_object_boxes(env, box_0, dtime, *pos_f, *speed_f, self, cinfo); } //tt3 /* @@ -614,3 +489,212 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, return result; } + +bool collisionCheckIntersection(Environment *env, IGameDef *gamedef, + const aabb3f &box_0, const v3f &pos_f, ActiveObject *self, + bool collideWithObjects) +{ + #define PROFILER_NAME(text) (s_env ? ("Server: " text) : ("Client: " text)) + Map *map = &env->getMap(); + ServerEnvironment *s_env = dynamic_cast(env); + + ScopeProfiler sp(g_profiler, PROFILER_NAME("collisionInCollision()"), SPT_AVG); + + /* + Collect node boxes + */ + std::vector cinfo; + { + //TimeTaker tt2("collisionMoveSimple collect boxes"); + ScopeProfiler sp2(g_profiler, PROFILER_NAME("collision collect boxes"), SPT_AVG); + + v3s16 min = floatToInt(pos_f + box_0.MinEdge, BS) - v3s16(1, 1, 1); + v3s16 max = floatToInt(pos_f + box_0.MaxEdge, BS) + v3s16(1, 1, 1); + + bool any_position_valid = add_area_node_boxes(min, max, gamedef, map, cinfo); + + // Do not move if world has not loaded yet, since custom node boxes + // are not available for collision detection. + // This also intentionally occurs in the case of the object being positioned + // solely on loaded CONTENT_IGNORE nodes, no matter where they come from. + if (!any_position_valid) { + return true; + } + } // tt2 + + if(collideWithObjects) + { + /* add object boxes to cinfo */ + v3f speed; + add_object_boxes(env, box_0, 0, pos_f, speed, self, cinfo); + } //tt3 + + /* + Collision detection + */ + + { + aabb3f checkbox = box_0; + checkbox.MinEdge += pos_f; + checkbox.MaxEdge += pos_f; + + /* + Go through every nodebox, find nearest collision + */ + for (u32 boxindex = 0; boxindex < cinfo.size(); boxindex++) { + const NearbyCollisionInfo &box_info = cinfo[boxindex]; + + if (box_info.box.intersectsWithBox(checkbox)) + return true; + } + } + + return false; +} + +static bool add_area_node_boxes(const v3s16 &min, const v3s16 &max, IGameDef *gamedef, + Map *map, std::vector &cinfo) +{ + bool any_position_valid = false; + v3s16 p; + for (p.X = min.X; p.X <= max.X; p.X++) + for (p.Y = min.Y; p.Y <= max.Y; p.Y++) + for (p.Z = min.Z; p.Z <= max.Z; p.Z++) { + bool is_position_valid; + MapNode n = map->getNode(p, &is_position_valid); + + if (is_position_valid && n.getContent() != CONTENT_IGNORE) { + // Object collides into walkable nodes + + any_position_valid = true; + const NodeDefManager *nodedef = gamedef->getNodeDefManager(); + const ContentFeatures &f = nodedef->get(n); + + if (!f.walkable) + continue; + + // Negative bouncy may have a meaning, but we need +value here. + int n_bouncy_value = abs(itemgroup_get(f.groups, "bouncy")); + + int neighbors = 0; + if (f.drawtype == NDT_NODEBOX && + f.node_box.type == NODEBOX_CONNECTED) { + v3s16 p2 = p; + + p2.Y++; + getNeighborConnectingFace(p2, nodedef, map, n, 1, &neighbors); + + p2 = p; + p2.Y--; + getNeighborConnectingFace(p2, nodedef, map, n, 2, &neighbors); + + p2 = p; + p2.Z--; + getNeighborConnectingFace(p2, nodedef, map, n, 4, &neighbors); + + p2 = p; + p2.X--; + getNeighborConnectingFace(p2, nodedef, map, n, 8, &neighbors); + + p2 = p; + p2.Z++; + getNeighborConnectingFace(p2, nodedef, map, n, 16, &neighbors); + + p2 = p; + p2.X++; + getNeighborConnectingFace(p2, nodedef, map, n, 32, &neighbors); + } + std::vector nodeboxes; + n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors); + + // Calculate float position only once + v3f posf = intToFloat(p, BS); + for (auto box : nodeboxes) { + box.MinEdge += posf; + box.MaxEdge += posf; + cinfo.emplace_back(false, n_bouncy_value, p, box); + } + } else { + // Collide with unloaded nodes (position invalid) and loaded + // CONTENT_IGNORE nodes (position valid) + aabb3f box = getNodeBox(p, BS); + cinfo.emplace_back(true, 0, p, box); + } + } + return any_position_valid; +} + +static void add_object_boxes(Environment *env, + const aabb3f &box_0, f32 dtime, + const v3f &pos_f, const v3f &speed_f, ActiveObject *self, + std::vector &cinfo) +{ + /* add object boxes to cinfo */ + + std::vector objects; +#ifndef SERVER + ClientEnvironment *c_env = dynamic_cast(env); + if (c_env != 0) { + // Calculate distance by speed, add own extent and 1.5m of tolerance + f32 distance = speed_f.getLength() * dtime + + box_0.getExtent().getLength() + 1.5f * BS; + std::vector clientobjects; + c_env->getActiveObjects(pos_f, distance, clientobjects); + + for (auto &clientobject : clientobjects) { + // Do collide with everything but itself and the parent CAO + if (!self || (self != clientobject.obj && + self != clientobject.obj->getParent())) { + objects.push_back((ActiveObject*) clientobject.obj); + } + } + } + else +#endif + { + ServerEnvironment *s_env = dynamic_cast(env); + if (s_env != NULL) { + // Calculate distance by speed, add own extent and 1.5m of tolerance + f32 distance = speed_f.getLength() * dtime + + box_0.getExtent().getLength() + 1.5f * BS; + + // search for objects which are not us, or we are not its parent + // we directly use the callback to populate the result to prevent + // a useless result loop here + auto include_obj_cb = [self, &objects] (ServerActiveObject *obj) { + if (!obj->isGone() && + (!self || (self != obj && self != obj->getParent()))) { + objects.push_back((ActiveObject *)obj); + } + return false; + }; + + std::vector s_objects; + s_env->getObjectsInsideRadius(s_objects, pos_f, distance, include_obj_cb); + } + } + + for (std::vector::const_iterator iter = objects.begin(); + iter != objects.end(); ++iter) { + ActiveObject *object = *iter; + + if (object && object->collideWithObjects()) { + aabb3f object_collisionbox; + if (object->getCollisionBox(&object_collisionbox)) + cinfo.emplace_back(object, 0, object_collisionbox); + } + } +#ifndef SERVER + if (self && c_env) { + LocalPlayer *lplayer = c_env->getLocalPlayer(); + if (lplayer->getParent() == nullptr) { + aabb3f lplayer_collisionbox = lplayer->getCollisionbox(); + v3f lplayer_pos = lplayer->getPosition(); + lplayer_collisionbox.MinEdge += lplayer_pos; + lplayer_collisionbox.MaxEdge += lplayer_pos; + ActiveObject *obj = (ActiveObject*) lplayer->getCAO(); + cinfo.emplace_back(obj, 0, lplayer_collisionbox); + } + } +#endif +} diff --git a/src/collision.h b/src/collision.h index b44af55df..11ffa94a0 100644 --- a/src/collision.h +++ b/src/collision.h @@ -72,6 +72,11 @@ collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef, v3f accel_f, ActiveObject *self=NULL, bool collideWithObjects=true); +// check if box is in collision on actual position +bool collisionCheckIntersection(Environment *env, IGameDef *gamedef, + const aabb3f &box_0, const v3f &pos_f, ActiveObject *self = nullptr, + bool collideWithObjects = true); + // Helper function: // Checks for collision of a moving aabbox with a static aabbox // Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision From 93742b8bd282329ec3a11af6e85d82bba730c671 Mon Sep 17 00:00:00 2001 From: SFENCE Date: Fri, 9 Feb 2024 22:31:09 +0100 Subject: [PATCH 2/5] Move comment. --- src/client/localplayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index a8fb31ddf..a912c5c38 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -442,10 +442,10 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, // Move player to the maximal height when falling or when // the ledge is climbed on the next step. + // Smoothen the movement (based on 'position.Y = bmax.Y') // Check if there is space for sneak glitch v3f pos(position.X, position.Y + y_diff * dtime * 22.0f + BS * 0.01f, position.Z); if (!collisionCheckIntersection(env, m_client, m_collisionbox, pos)) { - // Smoothen the movement (based on 'position.Y = bmax.Y') position.Y = pos.Y; position.Y = std::min(position.Y, bmax.Y); m_speed.Y = 0.0f; From edb811176782e0d5baeb8e137232ace90436c2ae Mon Sep 17 00:00:00 2001 From: SFENCE Date: Fri, 9 Feb 2024 22:43:08 +0100 Subject: [PATCH 3/5] Appli performance tip from review. --- src/collision.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collision.cpp b/src/collision.cpp index 479122eac..6962dec1c 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -66,7 +66,7 @@ struct NearbyCollisionInfo { aabb3f box; }; -static bool add_area_node_boxes(const v3s16 &min, const v3s16 &max, IGameDef *gamedef, +static bool add_area_node_boxes(const v3s16 min, const v3s16 max, IGameDef *gamedef, Map *map, std::vector &cinfo); static void add_object_boxes(Environment *env, const aabb3f &box_0, f32 dtime, const v3f &pos_f, const v3f &speed_f, ActiveObject *self, @@ -552,7 +552,7 @@ bool collisionCheckIntersection(Environment *env, IGameDef *gamedef, return false; } -static bool add_area_node_boxes(const v3s16 &min, const v3s16 &max, IGameDef *gamedef, +static bool add_area_node_boxes(const v3s16 min, const v3s16 max, IGameDef *gamedef, Map *map, std::vector &cinfo) { bool any_position_valid = false; From 4a731b5a10d87faf06a106c3d1843abe8f3b64ad Mon Sep 17 00:00:00 2001 From: SFENCE Date: Tue, 27 Feb 2024 20:28:58 +0100 Subject: [PATCH 4/5] Simplier code. --- src/client/localplayer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index a912c5c38..f5663e8cf 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -446,8 +446,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, // Check if there is space for sneak glitch v3f pos(position.X, position.Y + y_diff * dtime * 22.0f + BS * 0.01f, position.Z); if (!collisionCheckIntersection(env, m_client, m_collisionbox, pos)) { - position.Y = pos.Y; - position.Y = std::min(position.Y, bmax.Y); + position.Y = std::min(pos.Y, bmax.Y); m_speed.Y = 0.0f; } } From 74523c8bff44639395901126036ed55dce3bdf87 Mon Sep 17 00:00:00 2001 From: SFENCE Date: Tue, 9 Apr 2024 18:15:47 +0200 Subject: [PATCH 5/5] Fix bug. --- src/client/localplayer.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index f5663e8cf..c86716edb 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -545,12 +545,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, // Prevent sliding on the ground when jump speed is 0 m_can_jump = m_can_jump && jumpspeed != 0.0f; - // Prevent jump if in node/object - if (m_can_jump && could_sneak) { - if (collisionCheckIntersection(env, m_client, m_collisionbox, initial_position)) - m_can_jump = false; - } - // Autojump handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d); }