From 070ab6654ab4d8ab4a15a99698666f27552d6937 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 1 Apr 2017 20:38:14 +0200 Subject: [PATCH] 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. --- src/localplayer.cpp | 398 +++++++++++++++++++------------------------- src/localplayer.h | 108 ++++++------ 2 files changed, 224 insertions(+), 282 deletions(-) diff --git a/src/localplayer.cpp b/src/localplayer.cpp index b587f7bbb..ace0b992f 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -67,13 +67,6 @@ LocalPlayer::LocalPlayer(Client *client, const char *name): hurt_tilt_timer(0.0f), hurt_tilt_strength(0.0f), m_position(0,0,0), - m_sneak_node(32767,32767,32767), - m_sneak_node_bb_ymax(0), // To support temporary option for old move code - m_sneak_node_bb_top(0,0,0,0,0,0), - m_sneak_node_exists(false), - m_need_to_get_new_sneak_node(true), - m_sneak_ladder_detected(false), - m_ledge_detected(false), m_old_node_below(32767,32767,32767), m_old_node_below_type("air"), m_can_jump(false), @@ -96,88 +89,134 @@ LocalPlayer::~LocalPlayer() { } -static aabb3f getTopBoundingBox(const std::vector &nodeboxes) +static aabb3f getNodeBoundingBox(const std::vector &nodeboxes) { + if (nodeboxes.size() == 0) + return aabb3f(0, 0, 0, 0, 0, 0); + aabb3f b_max; - b_max.reset(-BS, -BS, -BS); - for (std::vector::const_iterator it = nodeboxes.begin(); - it != nodeboxes.end(); ++it) { - aabb3f box = *it; - if (box.MaxEdge.Y > b_max.MaxEdge.Y) - b_max = box; - else if (box.MaxEdge.Y == b_max.MaxEdge.Y) - b_max.addInternalBox(box); - } - 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) \ - (map)->getNodeNoEx((p3) + v3s16((v2).X, y, (v2).Y), valid) + std::vector::const_iterator it = nodeboxes.begin(); + b_max = aabb3f(it->MinEdge, it->MaxEdge); + + ++it; + for (; it != nodeboxes.end(); ++it) + b_max.addInternalBox(*it); + + return b_max; +} -// pos is the node the player is standing inside(!) -static bool detectSneakLadder(Map *map, INodeDefManager *nodemgr, v3s16 pos) +bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, + const v3f &sneak_max) { - // Detects a structure known as "sneak ladder" or "sneak elevator" - // that relies on bugs to provide a fast means of vertical transportation, - // the bugs have since been fixed but this function remains to keep it working. - // NOTE: This is just entirely a huge hack and causes way too many problems. - bool is_valid_position; + static const v3s16 dir9_center[9] = { + v3s16( 0, 0, 0), + v3s16( 1, 0, 0), + v3s16(-1, 0, 0), + 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; - // X/Z vectors for 4 neighboring nodes - static const v2s16 vecs[] = { v2s16(-1, 0), v2s16(1, 0), v2s16(0, -1), v2s16(0, 1) }; + bool is_valid_position; + bool new_sneak_node_exists = m_sneak_node_exists; - for (u16 i = 0; i < ARRLEN(vecs); i++) { - const v2s16 vec = vecs[i]; + // We want the top of the sneak node to be below the players feet + 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 - node = GETNODE(map, pos, vec, 0, &is_valid_position); - if (!is_valid_position) - continue; - bool w = nodemgr->get(node).walkable; - node = GETNODE(map, pos, vec, 1, &is_valid_position); - if (!is_valid_position || w == nodemgr->get(node).walkable) + // Get position of current standing node + const v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); + + if (current_node != m_sneak_node) { + new_sneak_node_exists = false; + } else { + 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; - // check one more node above OR below with corresponding walkability - node = GETNODE(map, pos, vec, -1, &is_valid_position); - bool ok = is_valid_position && w != nodemgr->get(node).walkable; - if (!ok) { - node = GETNODE(map, pos, vec, 2, &is_valid_position); - ok = is_valid_position && w == nodemgr->get(node).walkable; + + // The node to be sneaked on has to be walkable + node = map->getNodeNoEx(p, &is_valid_position); + if (!is_valid_position || !nodemgr->get(node).walkable) + 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; - if (ok) - return true; + min_distance_f = distance_f; + m_sneak_node = p; + new_sneak_node_exists = true; } - return false; -} - -static bool detectLedge(Map *map, INodeDefManager *nodemgr, v3s16 pos) -{ - bool is_valid_position; - MapNode node; - // X/Z vectors for 4 neighboring nodes - static const v2s16 vecs[] = {v2s16(-1, 0), v2s16(1, 0), v2s16(0, -1), v2s16(0, 1)}; + if (!new_sneak_node_exists) + return false; - for (u16 i = 0; i < ARRLEN(vecs); i++) { - const v2s16 vec = vecs[i]; + // Update saved top bounding box of sneak node + node = map->getNodeNoEx(m_sneak_node); + std::vector nodeboxes; + node.getCollisionBoxes(nodemgr, &nodeboxes); + m_sneak_node_bb_top = getNodeBoundingBox(nodeboxes); - node = GETNODE(map, pos, vec, 1, &is_valid_position); + if (physics_override_sneak_glitch) { + // Detect sneak ladder: + // Node two meters above sneak node must be solid + node = map->getNodeNoEx(m_sneak_node + v3s16(0, 2, 0), + &is_valid_position); if (is_valid_position && nodemgr->get(node).walkable) { - // Ledge exists - node = GETNODE(map, pos, vec, 2, &is_valid_position); - if (is_valid_position && !nodemgr->get(node).walkable) - // Space above ledge exists - return true; + // Node three meters above: must be non-solid + node = map->getNodeNoEx(m_sneak_node + v3s16(0, 3, 0), + &is_valid_position); + m_sneak_ladder_detected = is_valid_position && + !nodemgr->get(node).walkable; } } - - return false; + return true; } -#undef GETNODE - void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, std::vector *collision_info) { @@ -193,10 +232,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, v3f position = getPosition(); // Copy parent position if local player is attached - if(isAttached) - { + if (isAttached) { setPosition(overridePosition); - m_sneak_node_exists = false; return; } @@ -208,7 +245,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, if (free_move) { position += m_speed * dtime; setPosition(position); - m_sneak_node_exists = false; return; } @@ -279,7 +315,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, || nodemgr->get(node2.getContent()).climbable) && !free_move; } - /* Collision uncertainty radius Make it a bit larger than the maximum distance of movement @@ -291,48 +326,79 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, // This should always apply, otherwise there are glitches sanity_check(d > pos_max_d); + // TODO: this shouldn't be hardcoded but transmitted from server + float player_stepheight = (touching_ground) ? (BS * 0.6f) : (BS * 0.2f); + +#ifdef __ANDROID__ + player_stepheight += (0.6f * BS); +#endif + + v3f accel_f = v3f(0,0,0); + + collisionMoveResult result = collisionMoveSimple(env, m_client, + pos_max_d, m_collisionbox, player_stepheight, dtime, + &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 + set to true. + + Player is allowed to jump when this is true. + */ + bool touching_ground_was = touching_ground; + touching_ground = result.touching_ground; + bool sneak_can_jump = false; + // 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 sneaking, keep on top of last walked node and don't fall off */ - if (control.sneak && m_sneak_node_exists && - !(fly_allowed && g_settings->getBool("free_move")) && - !in_liquid && !is_climbing && - physics_override_sneak) { + if (could_sneak && m_sneak_node_exists) { 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; + f32 y_diff = bmax.Y - position.Y; - position.X = rangelim(position.X, + // (BS * 0.6f) is the basic stepheight while standing on ground + if (y_diff < BS * 0.6f) { + // Only center player when they're on the node + position.X = rangelim(position.X, bmin.X - sneak_max.X, bmax.X + sneak_max.X); - position.Z = rangelim(position.Z, + 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; + 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 (y_diff > 0 && m_speed.Y < 0 && + (physics_override_sneak_glitch || y_diff < BS * 0.6f)) { + // 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; } + // Allow jumping on node edges while sneaking + if (m_speed.Y == 0 || m_sneak_ladder_detected) + sneak_can_jump = true; + if (collision_info != NULL && m_speed.Y - old_speed.Y > BS) { // Collide with sneak node, report fall damage @@ -344,131 +410,19 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, } } - // TODO: this shouldn't be hardcoded but transmitted from server - float player_stepheight = (touching_ground) ? (BS * 0.6f) : (BS * 0.2f); - -#ifdef __ANDROID__ - player_stepheight += (0.6f * BS); -#endif - - v3f accel_f = v3f(0,0,0); - - collisionMoveResult result = collisionMoveSimple(env, m_client, - pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f); - - /* - If the player's feet touch the topside of any node, this is - set to true. - - Player is allowed to jump when this is true. - */ - bool touching_ground_was = touching_ground; - touching_ground = result.touching_ground; - - // We want the top of the sneak node to be below the players feet - f32 position_y_mod; - if (m_sneak_node_exists) - position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - 0.05 * BS; - else - position_y_mod = (0.5 - 0.05) * BS; - v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), 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. + Find the next sneak node if necessary */ - 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); - f32 min_distance_f = 100000.0 * BS; - v3s16 new_sneak_node = m_sneak_node; - for(s16 x=-1; x<=1; x++) - for(s16 z=-1; z<=1; z++) - { - v3s16 p = current_node + v3s16(x,0,z); - v3f pf = intToFloat(p, BS); - v2f node_p2df(pf.X, pf.Z); - f32 distance_f = player_p2df.getDistanceFrom(node_p2df); + bool new_sneak_node_exists = false; - if (distance_f > min_distance_f || - fabs(player_p2df.X-node_p2df.X) > (.5+.1)*BS + sneak_max.X || - fabs(player_p2df.Y-node_p2df.Y) > (.5+.1)*BS + sneak_max.Z) - continue; - - - // The node to be sneaked on has to be walkable - node = map->getNodeNoEx(p, &is_valid_position); - if (!is_valid_position || !nodemgr->get(node).walkable) - 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); - m_sneak_node = new_sneak_node; - m_sneak_node_exists = sneak_node_found; - - if (sneak_node_found) { - // Update saved top bounding box of sneak node - MapNode n = map->getNodeNoEx(m_sneak_node); - std::vector nodeboxes; - n.getCollisionBoxes(nodemgr, &nodeboxes); - m_sneak_node_bb_top = getTopBoundingBox(nodeboxes); - - m_sneak_ladder_detected = physics_override_sneak_glitch && - detectSneakLadder(map, nodemgr, floatToInt(position, BS)); - } else { - m_sneak_ladder_detected = false; - } - } - - /* - If 'sneak glitch' enabled detect ledge for old sneak-jump - behaviour of climbing onto a ledge 2 nodes up. - */ - if (physics_override_sneak_glitch && control.sneak && control.jump) - 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 */ - bool sneak_node_exists = m_sneak_node_exists; setPosition(position); - m_sneak_node_exists = sneak_node_exists; + m_sneak_node_exists = new_sneak_node_exists; /* Report collisions @@ -501,22 +455,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 */ const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos())); // 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")) 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 if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && @@ -705,17 +652,8 @@ void LocalPlayer::applyControl(float dtime) */ v3f speedJ = getSpeed(); if(speedJ.Y >= -0.5 * BS) { - if (m_ledge_detected) { - // Limit jump speed to a minimum that allows - // 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); - } + speedJ.Y = movement_speed_jump * physics_override_jump; + setSpeed(speedJ); MtEvent *e = new SimpleTriggerEvent("PlayerJump"); m_client->event()->put(e); diff --git a/src/localplayer.h b/src/localplayer.h index 9cbefae23..32714ff32 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "environment.h" +#include "constants.h" #include class Client; @@ -44,27 +45,29 @@ public: LocalPlayer(Client *client, const char *name); virtual ~LocalPlayer(); - ClientActiveObject *parent; + ClientActiveObject *parent = nullptr; - u16 hp; - bool isAttached; - bool touching_ground; + // Initialize hp to 0, so that no hearts will be shown if server + // doesn't support health points + u16 hp = 0; + bool isAttached = false; + bool touching_ground = false; // This oscillates so that the player jumps a bit above the surface - bool in_liquid; + bool in_liquid = false; // This is more stable and defines the maximum speed of the player - bool in_liquid_stable; + bool in_liquid_stable = false; // Gets the viscosity of water to calculate friction - u8 liquid_viscosity; - bool is_climbing; - bool swimming_vertical; - - float physics_override_speed; - float physics_override_jump; - float physics_override_gravity; - bool physics_override_sneak; - bool physics_override_sneak_glitch; + u8 liquid_viscosity = 0; + bool is_climbing = false; + bool swimming_vertical = false; + + float physics_override_speed = 1.0f; + float physics_override_jump = 1.0f; + float physics_override_gravity = 1.0f; + bool physics_override_sneak = true; + bool physics_override_sneak_glitch = false; // Temporary option for old move code - bool physics_override_new_move; + bool physics_override_new_move = true; v3f overridePosition; @@ -83,32 +86,32 @@ public: // Used to check if anything changed and prevent sending packets if not v3f last_position; v3f last_speed; - float last_pitch; - float last_yaw; - unsigned int last_keyPressed; - u8 last_camera_fov; - u8 last_wanted_range; + float last_pitch = 0.0f; + float last_yaw = 0.0f; + unsigned int last_keyPressed = 0; + u8 last_camera_fov = 0; + u8 last_wanted_range = 0; - float camera_impact; + float camera_impact = 0.0f; - bool makes_footstep_sound; + bool makes_footstep_sound = true; - int last_animation; + int last_animation = NO_ANIM; float last_animation_speed; - std::string hotbar_image; - std::string hotbar_selected_image; + std::string hotbar_image = ""; + std::string hotbar_selected_image = ""; - video::SColor light_color; + video::SColor light_color = video::SColor(255, 255, 255, 255); - float hurt_tilt_timer; - float hurt_tilt_strength; + float hurt_tilt_timer = 0.0f; + float hurt_tilt_strength = 0.0f; GenericCAO *getCAO() const { return m_cao; } void setCAO(GenericCAO *toset) { - assert(m_cao == NULL); // Pre-condition + assert(!m_cao); // Pre-condition m_cao = toset; } @@ -140,38 +143,39 @@ public: private: void accelerateHorizontal(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; - v3s16 m_sneak_node; - // Stores the max player uplift by m_sneak_node - // To support temporary option for old move code - f32 m_sneak_node_bb_ymax; + v3s16 m_sneak_node = v3s16(32767, 32767, 32767); // Stores the top bounding box of m_sneak_node - aabb3f m_sneak_node_bb_top; + 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 - bool m_sneak_node_exists; - // Whether recalculation of m_sneak_node and its top bbox is needed - bool m_need_to_get_new_sneak_node; + bool m_sneak_node_exists = false; // Whether a "sneak ladder" structure is detected at the players pos // see detectSneakLadder() in the .cpp for more info (always false if disabled) - bool m_sneak_ladder_detected; - // 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; + bool m_sneak_ladder_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, // and its old type - v3s16 m_old_node_below; - std::string m_old_node_below_type; - bool m_can_jump; - u16 m_breath; - f32 m_yaw; - f32 m_pitch; - bool camera_barely_in_ceiling; - aabb3f m_collisionbox; - - GenericCAO *m_cao; + v3s16 m_old_node_below = v3s16(32767, 32767, 32767); + std::string m_old_node_below_type = "air"; + // ***** End of variables for temporary option ***** + + bool m_can_jump = false; + u16 m_breath = PLAYER_MAX_BREATH; + f32 m_yaw = 0.0f; + f32 m_pitch = 0.0f; + bool camera_barely_in_ceiling = false; + aabb3f m_collisionbox = aabb3f(-BS * 0.30f, 0.0f, -BS * 0.30f, BS * 0.30f, + BS * 1.75f, BS * 0.30f); + + GenericCAO *m_cao = nullptr; Client *m_client; }; -- 2.25.1