mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Improve WMO wall collision, unstuck, interior zoom, and chat focus
- Stronger wall collision push (0.35/0.15) and swept push (0.45/0.25) for interior/exterior WMOs to reduce clipping through tunnel walls - Use all triangles (not just pre-classified walls) for collision checks - Allow invisible collidable triangles (MOPY 0x01 without 0x20) to block - Pass insideWMO flag to all collision callers, match swim sweep to ground - Widen swept hit detection radius from 0.15 to 0.25 - Restrict camera zoom to 12 units inside WMO interiors - Fix /unstuck launching player above WMOs: remove +20 fallback, use gravity when no floor found - Slash and Enter keys always focus chat unless already typing
This commit is contained in:
parent
4cae4bfcdc
commit
8014dde29b
5 changed files with 36 additions and 24 deletions
|
|
@ -81,6 +81,7 @@ public:
|
|||
bool isSitting() const { return sitting; }
|
||||
bool isSwimming() const { return swimming; }
|
||||
bool isInsideWMO() const { return cachedInsideWMO; }
|
||||
void setGrounded(bool g) { grounded = g; }
|
||||
bool isOnTaxi() const { return externalFollow_; }
|
||||
const glm::vec3* getFollowTarget() const { return followTarget; }
|
||||
glm::vec3* getFollowTargetMutable() { return followTarget; }
|
||||
|
|
@ -141,6 +142,7 @@ private:
|
|||
static constexpr float MIN_DISTANCE = 0.5f; // Minimum zoom (first-person threshold)
|
||||
static constexpr float MAX_DISTANCE_NORMAL = 22.0f; // Default max zoom out
|
||||
static constexpr float MAX_DISTANCE_EXTENDED = 50.0f; // Extended max zoom out
|
||||
static constexpr float MAX_DISTANCE_INTERIOR = 12.0f; // Max zoom inside WMOs
|
||||
bool extendedZoom_ = false;
|
||||
static constexpr float ZOOM_SMOOTH_SPEED = 15.0f; // How fast zoom eases
|
||||
static constexpr float CAM_SMOOTH_SPEED = 20.0f; // How fast camera position smooths
|
||||
|
|
|
|||
|
|
@ -1667,13 +1667,17 @@ void Application::setupUICallbacks() {
|
|||
}
|
||||
|
||||
// Sample floor at the DESTINATION position (after nudge).
|
||||
// Pick the highest floor so we snap up to WMO floors when fallen below.
|
||||
bool foundFloor = false;
|
||||
if (auto floor = sampleBestFloorAt(pos.x, pos.y, pos.z + 60.0f)) {
|
||||
pos.z = *floor + 0.2f;
|
||||
} else {
|
||||
pos.z += 20.0f;
|
||||
foundFloor = true;
|
||||
}
|
||||
|
||||
cc->teleportTo(pos);
|
||||
if (!foundFloor) {
|
||||
cc->setGrounded(false); // Let gravity pull player down to a surface
|
||||
}
|
||||
syncTeleportedPositionToServer(pos);
|
||||
forceServerTeleportCommand(pos);
|
||||
clearStuckMovement();
|
||||
|
|
|
|||
|
|
@ -626,7 +626,8 @@ void CameraController::update(float deltaTime) {
|
|||
glm::vec3 stepPos = swimFrom;
|
||||
|
||||
if (swimMoveDist > 0.01f) {
|
||||
int swimSteps = std::max(1, std::min(3, static_cast<int>(std::ceil(swimMoveDist / 0.65f))));
|
||||
float swimStepSize = cachedInsideWMO ? 0.20f : 0.35f;
|
||||
int swimSteps = std::max(1, std::min(8, static_cast<int>(std::ceil(swimMoveDist / swimStepSize))));
|
||||
glm::vec3 stepDelta = (swimTo - swimFrom) / static_cast<float>(swimSteps);
|
||||
|
||||
for (int i = 0; i < swimSteps; i++) {
|
||||
|
|
@ -634,7 +635,7 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
if (wmoRenderer) {
|
||||
glm::vec3 adjusted;
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted, cachedInsideWMO)) {
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
candidate.z = std::max(candidate.z, adjusted.z);
|
||||
|
|
@ -1274,8 +1275,10 @@ void CameraController::update(float deltaTime) {
|
|||
lastInsideWMOCheckPos = targetPos;
|
||||
}
|
||||
|
||||
// Do not clamp zoom target by ceiling checks. First-person should always
|
||||
// be reachable; occlusion handling below will resolve camera placement safely.
|
||||
// Smoothly pull camera in when entering WMO interiors
|
||||
if (cachedInsideWMO && userTargetDistance > MAX_DISTANCE_INTERIOR) {
|
||||
userTargetDistance = MAX_DISTANCE_INTERIOR;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Camera collision (sphere sweep approximation) =====
|
||||
|
|
@ -1499,14 +1502,15 @@ void CameraController::update(float deltaTime) {
|
|||
float moveDist = glm::length(desiredFeet - startFeet);
|
||||
|
||||
if (moveDist > 0.01f) {
|
||||
int sweepSteps = std::max(1, std::min(3, static_cast<int>(std::ceil(moveDist / 0.65f))));
|
||||
float stepSize = cachedInsideWMO ? 0.20f : 0.35f;
|
||||
int sweepSteps = std::max(1, std::min(8, static_cast<int>(std::ceil(moveDist / stepSize))));
|
||||
glm::vec3 stepPos = startFeet;
|
||||
glm::vec3 stepDelta = (desiredFeet - startFeet) / static_cast<float>(sweepSteps);
|
||||
|
||||
for (int i = 0; i < sweepSteps; i++) {
|
||||
glm::vec3 candidate = stepPos + stepDelta;
|
||||
glm::vec3 adjusted;
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted, cachedInsideWMO)) {
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
candidate.z = std::max(candidate.z, adjusted.z);
|
||||
|
|
@ -1985,6 +1989,7 @@ void CameraController::processMouseWheel(float delta) {
|
|||
float zoomSpeed = glm::max(userTargetDistance * 0.15f, 0.3f);
|
||||
userTargetDistance -= delta * zoomSpeed;
|
||||
float maxDist = extendedZoom_ ? MAX_DISTANCE_EXTENDED : MAX_DISTANCE_NORMAL;
|
||||
if (cachedInsideWMO) maxDist = std::min(maxDist, MAX_DISTANCE_INTERIOR);
|
||||
userTargetDistance = glm::clamp(userTargetDistance, MIN_DISTANCE, maxDist);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3095,7 +3095,7 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
float rangeMinY = std::min(localFrom.y, localTo.y) - PLAYER_RADIUS - 1.5f;
|
||||
float rangeMaxX = std::max(localFrom.x, localTo.x) + PLAYER_RADIUS + 1.5f;
|
||||
float rangeMaxY = std::max(localFrom.y, localTo.y) + PLAYER_RADIUS + 1.5f;
|
||||
group.getWallTrianglesInRange(rangeMinX, rangeMinY, rangeMaxX, rangeMaxY, triScratch_);
|
||||
group.getTrianglesInRange(rangeMinX, rangeMinY, rangeMaxX, rangeMaxY, triScratch_);
|
||||
|
||||
for (uint32_t triStart : triScratch_) {
|
||||
// Use pre-computed Z bounds for fast vertical reject
|
||||
|
|
@ -3113,17 +3113,18 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
if (triHeight < 1.0f && tb.maxZ <= localFeetZ + 1.2f) continue;
|
||||
|
||||
// Use MOPY flags to filter wall collision.
|
||||
// Only RENDERED triangles (flag 0x20) with collision intent (0x01)
|
||||
// should block the player. Skip invisible collision hulls (0x08/0x48)
|
||||
// and non-collidable render-only geometry.
|
||||
// Collidable triangles (flag 0x01) block the player — including
|
||||
// invisible collision walls (0x01 without 0x20) used in tunnels.
|
||||
// Skip detail/decorative geometry (0x04) and render-only surfaces.
|
||||
uint32_t triIdx = triStart / 3;
|
||||
if (!group.triMopyFlags.empty() && triIdx < group.triMopyFlags.size()) {
|
||||
uint8_t mopy = group.triMopyFlags[triIdx];
|
||||
// Must be rendered (0x20) AND have base collision flag (0x01)
|
||||
bool rendered = (mopy & 0x20) != 0;
|
||||
bool collidable = (mopy & 0x01) != 0;
|
||||
if (mopy != 0 && !(rendered && collidable)) {
|
||||
continue;
|
||||
if (mopy != 0) {
|
||||
bool collidable = (mopy & 0x01) != 0;
|
||||
bool detail = (mopy & 0x04) != 0;
|
||||
if (!collidable || detail) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3149,13 +3150,13 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
glm::vec3 hitPoint = localFrom + (localTo - localFrom) * tHit;
|
||||
glm::vec3 hitClosest = closestPointOnTriangle(hitPoint, v0, v1, v2);
|
||||
float hitErrSq = glm::dot(hitClosest - hitPoint, hitClosest - hitPoint);
|
||||
if (hitErrSq <= 0.15f * 0.15f) {
|
||||
if (hitErrSq <= 0.25f * 0.25f) {
|
||||
float side = fromDist > 0.0f ? 1.0f : -1.0f;
|
||||
glm::vec3 safeLocal = hitPoint + normal * side * (PLAYER_RADIUS + 0.05f);
|
||||
glm::vec3 pushLocal(safeLocal.x - localTo.x, safeLocal.y - localTo.y, 0.0f);
|
||||
// Cap swept pushback so walls don't shove the player violently
|
||||
float pushLen = glm::length(glm::vec2(pushLocal.x, pushLocal.y));
|
||||
const float MAX_SWEPT_PUSH = 0.15f;
|
||||
const float MAX_SWEPT_PUSH = insideWMO ? 0.45f : 0.25f;
|
||||
if (pushLen > MAX_SWEPT_PUSH) {
|
||||
float scale = MAX_SWEPT_PUSH / pushLen;
|
||||
pushLocal.x *= scale;
|
||||
|
|
@ -3185,7 +3186,7 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
|
||||
const float SKIN = 0.005f; // small separation so we don't re-collide immediately
|
||||
// Stronger push when inside WMO for more responsive indoor collision
|
||||
const float MAX_PUSH = insideWMO ? 0.12f : 0.08f;
|
||||
const float MAX_PUSH = insideWMO ? 0.35f : 0.15f;
|
||||
float penetration = (PLAYER_RADIUS - horizDist);
|
||||
float pushDist = glm::clamp(penetration + SKIN, 0.0f, MAX_PUSH);
|
||||
glm::vec2 pushDir2;
|
||||
|
|
|
|||
|
|
@ -1367,16 +1367,16 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
|
||||
}
|
||||
|
||||
// Slash key: focus chat input
|
||||
if (!io.WantCaptureKeyboard && input.isKeyJustPressed(SDL_SCANCODE_SLASH)) {
|
||||
// Slash key: focus chat input — always works unless already typing in chat
|
||||
if (!chatInputActive && input.isKeyJustPressed(SDL_SCANCODE_SLASH)) {
|
||||
refocusChatInput = true;
|
||||
chatInputBuffer[0] = '/';
|
||||
chatInputBuffer[1] = '\0';
|
||||
chatInputMoveCursorToEnd = true;
|
||||
}
|
||||
|
||||
// Enter key: focus chat input (empty)
|
||||
if (!io.WantCaptureKeyboard && input.isKeyJustPressed(SDL_SCANCODE_RETURN)) {
|
||||
// Enter key: focus chat input (empty) — always works unless already typing
|
||||
if (!chatInputActive && input.isKeyJustPressed(SDL_SCANCODE_RETURN)) {
|
||||
refocusChatInput = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue