Sneak: Stripped down version

Fix taking damage caused by sneaking over a nodebox gap.
Fix strange behaviour on stair nodeboxes.
Enable jumping from node edges while sneaking.
Enable movement around corners while sneaking on a 1-node-high groove in a wall.
This commit is contained in:
SmallJoker 2017-04-01 20:38:14 +02:00 committed by paramat
parent dc3ca09e0e
commit a5c37717ff
2 changed files with 177 additions and 232 deletions

@ -42,88 +42,134 @@ LocalPlayer::~LocalPlayer()
{ {
} }
static aabb3f getTopBoundingBox(const std::vector<aabb3f> &nodeboxes) static aabb3f getNodeBoundingBox(const std::vector<aabb3f> &nodeboxes)
{ {
if (nodeboxes.size() == 0)
return aabb3f(0, 0, 0, 0, 0, 0);
aabb3f b_max; aabb3f b_max;
b_max.reset(-BS, -BS, -BS);
for (std::vector<aabb3f>::const_iterator it = nodeboxes.begin(); std::vector<aabb3f>::const_iterator it = nodeboxes.begin();
it != nodeboxes.end(); ++it) { b_max = aabb3f(it->MinEdge, it->MaxEdge);
aabb3f box = *it;
if (box.MaxEdge.Y > b_max.MaxEdge.Y) ++it;
b_max = box; for (; it != nodeboxes.end(); ++it)
else if (box.MaxEdge.Y == b_max.MaxEdge.Y) b_max.addInternalBox(*it);
b_max.addInternalBox(box);
} return b_max;
return aabb3f(v3f(b_max.MinEdge.X, b_max.MaxEdge.Y, b_max.MinEdge.Z), b_max.MaxEdge);
} }
#define GETNODE(map, p3, v2, y, valid) \ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
(map)->getNodeNoEx((p3) + v3s16((v2).X, y, (v2).Y), valid) const v3f &sneak_max)
// pos is the node the player is standing inside(!)
static bool detectSneakLadder(Map *map, INodeDefManager *nodemgr, v3s16 pos)
{ {
// Detects a structure known as "sneak ladder" or "sneak elevator" static const v3s16 dir9_center[9] = {
// that relies on bugs to provide a fast means of vertical transportation, v3s16( 0, 0, 0),
// the bugs have since been fixed but this function remains to keep it working. v3s16( 1, 0, 0),
// NOTE: This is just entirely a huge hack and causes way too many problems. v3s16(-1, 0, 0),
bool is_valid_position; v3s16( 0, 0, 1),
v3s16( 0, 0, -1),
v3s16( 1, 0, 1),
v3s16(-1, 0, 1),
v3s16( 1, 0, -1),
v3s16(-1, 0, -1)
};
INodeDefManager *nodemgr = m_client->ndef();
MapNode node; MapNode node;
// X/Z vectors for 4 neighboring nodes bool is_valid_position;
static const v2s16 vecs[] = { v2s16(-1, 0), v2s16(1, 0), v2s16(0, -1), v2s16(0, 1) }; bool new_sneak_node_exists = m_sneak_node_exists;
for (u16 i = 0; i < ARRLEN(vecs); i++) { // We want the top of the sneak node to be below the players feet
const v2s16 vec = vecs[i]; f32 position_y_mod = 0.05 * BS;
if (m_sneak_node_exists)
position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - position_y_mod;
// walkability of bottom & top node should differ // Get position of current standing node
node = GETNODE(map, pos, vec, 0, &is_valid_position); const v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS);
if (!is_valid_position)
continue; if (current_node != m_sneak_node) {
bool w = nodemgr->get(node).walkable; new_sneak_node_exists = false;
node = GETNODE(map, pos, vec, 1, &is_valid_position); } else {
if (!is_valid_position || w == nodemgr->get(node).walkable) node = map->getNodeNoEx(current_node, &is_valid_position);
if (!is_valid_position || !nodemgr->get(node).walkable)
new_sneak_node_exists = false;
}
// Keep old sneak node
if (new_sneak_node_exists)
return true;
// Get new sneak node
m_sneak_ladder_detected = false;
f32 min_distance_f = 100000.0 * BS;
for (s16 d = 0; d < 9; d++) {
const v3s16 p = current_node + dir9_center[d];
const v3f pf = intToFloat(p, BS);
const v2f diff(position.X - pf.X, position.Z - pf.Z);
f32 distance_f = diff.getLength();
if (distance_f > min_distance_f ||
fabs(diff.X) > (.5 + .1) * BS + sneak_max.X ||
fabs(diff.Y) > (.5 + .1) * BS + sneak_max.Z)
continue; continue;
// check one more node above OR below with corresponding walkability
node = GETNODE(map, pos, vec, -1, &is_valid_position); // The node to be sneaked on has to be walkable
bool ok = is_valid_position && w != nodemgr->get(node).walkable; node = map->getNodeNoEx(p, &is_valid_position);
if (!ok) { if (!is_valid_position || !nodemgr->get(node).walkable)
node = GETNODE(map, pos, vec, 2, &is_valid_position); continue;
ok = is_valid_position && w == nodemgr->get(node).walkable; // And the node(s) above have to be nonwalkable
bool ok = true;
if (!physics_override_sneak_glitch) {
u16 height = ceilf(
(m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS
);
for (u16 y = 1; y <= height; y++) {
node = map->getNodeNoEx(p + v3s16(0, y, 0), &is_valid_position);
if (!is_valid_position || nodemgr->get(node).walkable) {
ok = false;
break;
}
}
} else {
// legacy behaviour: check just one node
node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position);
ok = is_valid_position && !nodemgr->get(node).walkable;
} }
if (!ok)
continue;
if (ok) min_distance_f = distance_f;
return true; m_sneak_node = p;
new_sneak_node_exists = true;
} }
return false; if (!new_sneak_node_exists)
} return false;
static bool detectLedge(Map *map, INodeDefManager *nodemgr, v3s16 pos) // Update saved top bounding box of sneak node
{ node = map->getNodeNoEx(m_sneak_node);
bool is_valid_position; std::vector<aabb3f> nodeboxes;
MapNode node; node.getCollisionBoxes(nodemgr, &nodeboxes);
// X/Z vectors for 4 neighboring nodes m_sneak_node_bb_top = getNodeBoundingBox(nodeboxes);
static const v2s16 vecs[] = {v2s16(-1, 0), v2s16(1, 0), v2s16(0, -1), v2s16(0, 1)};
for (u16 i = 0; i < ARRLEN(vecs); i++) { if (physics_override_sneak_glitch) {
const v2s16 vec = vecs[i]; // Detect sneak ladder:
// Node two meters above sneak node must be solid
node = GETNODE(map, pos, vec, 1, &is_valid_position); node = map->getNodeNoEx(m_sneak_node + v3s16(0, 2, 0),
&is_valid_position);
if (is_valid_position && nodemgr->get(node).walkable) { if (is_valid_position && nodemgr->get(node).walkable) {
// Ledge exists // Node three meters above: must be non-solid
node = GETNODE(map, pos, vec, 2, &is_valid_position); node = map->getNodeNoEx(m_sneak_node + v3s16(0, 3, 0),
if (is_valid_position && !nodemgr->get(node).walkable) &is_valid_position);
// Space above ledge exists m_sneak_ladder_detected = is_valid_position &&
return true; !nodemgr->get(node).walkable;
} }
} }
return true;
return false;
} }
#undef GETNODE
void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
std::vector<CollisionInfo> *collision_info) std::vector<CollisionInfo> *collision_info)
{ {
@ -139,10 +185,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
v3f position = getPosition(); v3f position = getPosition();
// Copy parent position if local player is attached // Copy parent position if local player is attached
if(isAttached) if (isAttached) {
{
setPosition(overridePosition); setPosition(overridePosition);
m_sneak_node_exists = false;
return; return;
} }
@ -154,7 +198,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
if (free_move) { if (free_move) {
position += m_speed * dtime; position += m_speed * dtime;
setPosition(position); setPosition(position);
m_sneak_node_exists = false;
return; return;
} }
@ -225,7 +268,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
|| nodemgr->get(node2.getContent()).climbable) && !free_move; || nodemgr->get(node2.getContent()).climbable) && !free_move;
} }
/* /*
Collision uncertainty radius Collision uncertainty radius
Make it a bit larger than the maximum distance of movement Make it a bit larger than the maximum distance of movement
@ -237,59 +279,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
// This should always apply, otherwise there are glitches // This should always apply, otherwise there are glitches
sanity_check(d > pos_max_d); sanity_check(d > pos_max_d);
// Max. distance (X, Z) over border for sneaking determined by collision box
// * 0.49 to keep the center just barely on the node
v3f sneak_max = m_collisionbox.getExtent() * 0.49;
if (m_sneak_ladder_detected) {
// restore legacy behaviour (this makes the m_speed.Y hack necessary)
sneak_max = v3f(0.4 * BS, 0, 0.4 * BS);
}
/*
If sneaking, keep in range from the last walked node and don't
fall off from it
*/
if (control.sneak && m_sneak_node_exists &&
!(fly_allowed && g_settings->getBool("free_move")) &&
!in_liquid && !is_climbing &&
physics_override_sneak) {
const v3f sn_f = intToFloat(m_sneak_node, BS);
const v3f bmin = sn_f + m_sneak_node_bb_top.MinEdge;
const v3f bmax = sn_f + m_sneak_node_bb_top.MaxEdge;
const v3f old_pos = position;
const v3f old_speed = m_speed;
position.X = rangelim(position.X,
bmin.X - sneak_max.X, bmax.X + sneak_max.X);
position.Z = rangelim(position.Z,
bmin.Z - sneak_max.Z, bmax.Z + sneak_max.Z);
if (position.X != old_pos.X)
m_speed.X = 0;
if (position.Z != old_pos.Z)
m_speed.Z = 0;
// Because we keep the player collision box on the node, limiting
// position.Y is not necessary but useful to prevent players from
// being inside a node if sneaking on e.g. the lower part of a stair
if (!m_sneak_ladder_detected) {
position.Y = MYMAX(position.Y, bmax.Y);
} else {
// legacy behaviour that sometimes causes some weird slow sinking
m_speed.Y = MYMAX(m_speed.Y, 0);
}
if (collision_info != NULL &&
m_speed.Y - old_speed.Y > BS) {
// Collide with sneak node, report fall damage
CollisionInfo sn_info;
sn_info.node_p = m_sneak_node;
sn_info.old_speed = old_speed;
sn_info.new_speed = m_speed;
collision_info->push_back(sn_info);
}
}
// TODO: this shouldn't be hardcoded but transmitted from server // TODO: this shouldn't be hardcoded but transmitted from server
float player_stepheight = (touching_ground) ? (BS * 0.6f) : (BS * 0.2f); float player_stepheight = (touching_ground) ? (BS * 0.6f) : (BS * 0.2f);
@ -303,6 +292,10 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
pos_max_d, m_collisionbox, player_stepheight, dtime, pos_max_d, m_collisionbox, player_stepheight, dtime,
&position, &m_speed, accel_f); &position, &m_speed, accel_f);
bool could_sneak = control.sneak &&
!(fly_allowed && g_settings->getBool("free_move")) &&
!in_liquid && !is_climbing &&
physics_override_sneak;
/* /*
If the player's feet touch the topside of any node, this is If the player's feet touch the topside of any node, this is
set to true. set to true.
@ -311,110 +304,78 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
*/ */
bool touching_ground_was = touching_ground; bool touching_ground_was = touching_ground;
touching_ground = result.touching_ground; touching_ground = result.touching_ground;
bool sneak_can_jump = false;
// We want the top of the sneak node to be below the players feet // Max. distance (X, Z) over border for sneaking determined by collision box
f32 position_y_mod; // * 0.49 to keep the center just barely on the node
if (m_sneak_node_exists) v3f sneak_max = m_collisionbox.getExtent() * 0.49;
position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - 0.05 * BS;
else if (m_sneak_ladder_detected) {
position_y_mod = (0.5 - 0.05) * BS; // restore legacy behaviour (this makes the m_speed.Y hack necessary)
v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); sneak_max = v3f(0.4 * BS, 0, 0.4 * BS);
/*
Check the nodes under the player to see from which node the
player is sneaking from, if any. If the node from under
the player has been removed, the player falls.
*/
if (m_sneak_node_exists &&
nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" &&
m_old_node_below_type != "air") {
// Old node appears to have been removed; that is,
// it wasn't air before but now it is
m_need_to_get_new_sneak_node = false;
m_sneak_node_exists = false;
} else if (nodemgr->get(map->getNodeNoEx(current_node)).name != "air") {
// We are on something, so make sure to recalculate the sneak
// node.
m_need_to_get_new_sneak_node = true;
} }
if (m_need_to_get_new_sneak_node && physics_override_sneak) { /*
v2f player_p2df(position.X, position.Z); If sneaking, keep on top of last walked node and don't fall off
f32 min_distance_f = 100000.0 * BS; */
v3s16 new_sneak_node = m_sneak_node; if (could_sneak && m_sneak_node_exists) {
for(s16 x=-1; x<=1; x++) const v3f sn_f = intToFloat(m_sneak_node, BS);
for(s16 z=-1; z<=1; z++) const v3f bmin = sn_f + m_sneak_node_bb_top.MinEdge;
{ const v3f bmax = sn_f + m_sneak_node_bb_top.MaxEdge;
v3s16 p = current_node + v3s16(x,0,z); const v3f old_pos = position;
v3f pf = intToFloat(p, BS); const v3f old_speed = m_speed;
v2f node_p2df(pf.X, pf.Z); f32 y_diff = bmax.Y - position.Y;
f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
if (distance_f > min_distance_f || // (BS * 0.6f) is the basic stepheight while standing on ground
fabs(player_p2df.X-node_p2df.X) > (.5+.1)*BS + sneak_max.X || if (y_diff < BS * 0.6f) {
fabs(player_p2df.Y-node_p2df.Y) > (.5+.1)*BS + sneak_max.Z) // Only center player when they're on the node
continue; position.X = rangelim(position.X,
bmin.X - sneak_max.X, bmax.X + sneak_max.X);
position.Z = rangelim(position.Z,
bmin.Z - sneak_max.Z, bmax.Z + sneak_max.Z);
if (position.X != old_pos.X)
// The node to be sneaked on has to be walkable m_speed.X = 0;
node = map->getNodeNoEx(p, &is_valid_position); if (position.Z != old_pos.Z)
if (!is_valid_position || !nodemgr->get(node).walkable) m_speed.Z = 0;
continue;
// And the node(s) above have to be nonwalkable
bool ok = true;
if (!physics_override_sneak_glitch) {
u16 height = ceilf(
(m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS
);
for (u16 y = 1; y <= height; y++) {
node = map->getNodeNoEx(p + v3s16(0,y,0), &is_valid_position);
if (!is_valid_position || nodemgr->get(node).walkable) {
ok = false;
break;
}
}
} else {
// legacy behaviour: check just one node
node = map->getNodeNoEx(p + v3s16(0,1,0), &is_valid_position);
ok = is_valid_position && !nodemgr->get(node).walkable;
}
if (!ok)
continue;
min_distance_f = distance_f;
new_sneak_node = p;
} }
bool sneak_node_found = (min_distance_f < 100000.0 * BS); if (y_diff > 0 && m_speed.Y < 0 &&
m_sneak_node = new_sneak_node; (physics_override_sneak_glitch || y_diff < BS * 0.6f)) {
m_sneak_node_exists = sneak_node_found; // Move player to the maximal height when falling or when
// the ledge is climbed on the next step.
position.Y = bmax.Y;
m_speed.Y = 0;
}
if (sneak_node_found) { // Allow jumping on node edges while sneaking
// Update saved top bounding box of sneak node if (m_speed.Y == 0 || m_sneak_ladder_detected)
MapNode n = map->getNodeNoEx(m_sneak_node); sneak_can_jump = true;
std::vector<aabb3f> nodeboxes;
n.getCollisionBoxes(nodemgr, &nodeboxes);
m_sneak_node_bb_top = getTopBoundingBox(nodeboxes);
m_sneak_ladder_detected = physics_override_sneak_glitch && if (collision_info != NULL &&
detectSneakLadder(map, nodemgr, floatToInt(position, BS)); m_speed.Y - old_speed.Y > BS) {
} else { // Collide with sneak node, report fall damage
m_sneak_ladder_detected = false; CollisionInfo sn_info;
sn_info.node_p = m_sneak_node;
sn_info.old_speed = old_speed;
sn_info.new_speed = m_speed;
collision_info->push_back(sn_info);
} }
} }
/* /*
If 'sneak glitch' enabled detect ledge for old sneak-jump Find the next sneak node if necessary
behaviour of climbing onto a ledge 2 nodes up.
*/ */
if (physics_override_sneak_glitch && control.sneak && control.jump) bool new_sneak_node_exists = false;
m_ledge_detected = detectLedge(map, nodemgr, floatToInt(position, BS));
if (could_sneak)
new_sneak_node_exists = updateSneakNode(map, position, sneak_max);
/* /*
Set new position but keep sneak node set Set new position but keep sneak node set
*/ */
bool sneak_node_exists = m_sneak_node_exists;
setPosition(position); setPosition(position);
m_sneak_node_exists = sneak_node_exists; m_sneak_node_exists = new_sneak_node_exists;
/* /*
Report collisions Report collisions
@ -447,22 +408,15 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
} }
} }
/*
Update the node last under the player
*/
m_old_node_below = floatToInt(position - v3f(0,BS/2,0), BS);
m_old_node_below_type = nodemgr->get(map->getNodeNoEx(m_old_node_below)).name;
/* /*
Check properties of the node on which the player is standing Check properties of the node on which the player is standing
*/ */
const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos())); const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos()));
// Determine if jumping is possible // Determine if jumping is possible
m_can_jump = touching_ground && !in_liquid; m_can_jump = (touching_ground && !in_liquid && !is_climbing)
|| sneak_can_jump;
if (itemgroup_get(f.groups, "disable_jump")) if (itemgroup_get(f.groups, "disable_jump"))
m_can_jump = false; m_can_jump = false;
else if (control.sneak && m_sneak_ladder_detected && !in_liquid && !is_climbing)
m_can_jump = true;
// Jump key pressed while jumping off from a bouncy block // Jump key pressed while jumping off from a bouncy block
if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") &&
@ -651,17 +605,8 @@ void LocalPlayer::applyControl(float dtime)
*/ */
v3f speedJ = getSpeed(); v3f speedJ = getSpeed();
if(speedJ.Y >= -0.5 * BS) { if(speedJ.Y >= -0.5 * BS) {
if (m_ledge_detected) { speedJ.Y = movement_speed_jump * physics_override_jump;
// Limit jump speed to a minimum that allows setSpeed(speedJ);
// jumping up onto a ledge 2 nodes up.
speedJ.Y = movement_speed_jump *
MYMAX(physics_override_jump, 1.3f);
setSpeed(speedJ);
m_ledge_detected = false;
} else {
speedJ.Y = movement_speed_jump * physics_override_jump;
setSpeed(speedJ);
}
MtEvent *e = new SimpleTriggerEvent("PlayerJump"); MtEvent *e = new SimpleTriggerEvent("PlayerJump");
m_client->event()->put(e); m_client->event()->put(e);

@ -143,30 +143,30 @@ public:
private: private:
void accelerateHorizontal(const v3f &target_speed, const f32 max_increase); void accelerateHorizontal(const v3f &target_speed, const f32 max_increase);
void accelerateVertical(const v3f &target_speed, const f32 max_increase); void accelerateVertical(const v3f &target_speed, const f32 max_increase);
bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max);
v3f m_position; v3f m_position;
v3s16 m_sneak_node = v3s16(32767, 32767, 32767); v3s16 m_sneak_node = v3s16(32767, 32767, 32767);
// Stores the max player uplift by m_sneak_node
// To support temporary option for old move code
f32 m_sneak_node_bb_ymax = 0.0f;
// Stores the top bounding box of m_sneak_node // Stores the top bounding box of m_sneak_node
aabb3f m_sneak_node_bb_top = aabb3f(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); aabb3f m_sneak_node_bb_top = aabb3f(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
// Whether the player is allowed to sneak // Whether the player is allowed to sneak
bool m_sneak_node_exists = false; bool m_sneak_node_exists = false;
// Whether recalculation of m_sneak_node and its top bbox is needed
bool m_need_to_get_new_sneak_node = true;
// Whether a "sneak ladder" structure is detected at the players pos // Whether a "sneak ladder" structure is detected at the players pos
// see detectSneakLadder() in the .cpp for more info (always false if disabled) // see detectSneakLadder() in the .cpp for more info (always false if disabled)
bool m_sneak_ladder_detected = false; bool m_sneak_ladder_detected = false;
// Whether a 2-node-up ledge is detected at the players pos,
// see detectLedge() in the .cpp for more info (always false if disabled).
bool m_ledge_detected = false;
// ***** Variables for temporary option of the old move code *****
// Stores the max player uplift by m_sneak_node
f32 m_sneak_node_bb_ymax = 0.0f;
// Whether recalculation of m_sneak_node and its top bbox is needed
bool m_need_to_get_new_sneak_node = true;
// Node below player, used to determine whether it has been removed, // Node below player, used to determine whether it has been removed,
// and its old type // and its old type
v3s16 m_old_node_below = v3s16(32767, 32767, 32767); v3s16 m_old_node_below = v3s16(32767, 32767, 32767);
std::string m_old_node_below_type = "air"; std::string m_old_node_below_type = "air";
// ***** End of variables for temporary option *****
bool m_can_jump = false; bool m_can_jump = false;
u16 m_breath = PLAYER_MAX_BREATH; u16 m_breath = PLAYER_MAX_BREATH;
f32 m_yaw = 0.0f; f32 m_yaw = 0.0f;