diff options
| author | Erik Kundiman <erik@megapahit.org> | 2026-10-20 16:20:00 +0800 |
|---|---|---|
| committer | Erik Kundiman <erik@megapahit.org> | 2026-10-20 16:20:00 +0800 |
| commit | 61e65e2c95779d29496e5859a6edac82fc3b1ddb (patch) | |
| tree | e0d70b7af955610b2a414b914c50d15f7af21015 /indra/newview/llagent.cpp | |
| parent | bbe494697ab37919f10009af516c711a685ac98e (diff) | |
| parent | 923f645a923e7f061f7ff2790d8510aa4b5ced36 (diff) | |
Merge tag 'Second_Life_Release#923f645a-26.5' into 26.5
Diffstat (limited to 'indra/newview/llagent.cpp')
| -rw-r--r-- | indra/newview/llagent.cpp | 513 |
1 files changed, 489 insertions, 24 deletions
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index b29d1706c4..72ed9f8c25 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -84,7 +84,6 @@ #include "llurlentry.h" #include "llviewercontrol.h" #include "llviewerdisplay.h" -#include "llviewerjoystick.h" #include "llviewermediafocus.h" #include "llviewermenu.h" #include "llviewerobjectlist.h" @@ -114,6 +113,12 @@ const F32 AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND = 1.f; // meters const F32 AUTOPILOT_MAX_TIME_NO_PROGRESS_WALK = 1.5f; // seconds const F32 AUTOPILOT_MAX_TIME_NO_PROGRESS_FLY = 2.5f; // seconds. Flying is less presize, needs a bit more time +// Ground-sit requests that do not originate from a click on the ground (e.g. the +// game-control/keyboard "toggle sit" action) have no target position to check +// against, so refuse them outright when the ground below the agent is farther +// than this away, rather than snapping the agent down onto distant terrain. +const F32 MAX_SIT_ON_GROUND_DISTANCE = 30.f; // meters + const F32 MAX_VELOCITY_AUTO_LAND_SQUARED = 4.f * 4.f; const F64 CHAT_AGE_FAST_RATE = 3.0; @@ -458,7 +463,7 @@ LLAgent::LLAgent() : mCurrentFidget(0), mFirstLogin(false), mOutfitChosen(false), - + mCrouch(false), mVoiceConnected(false), mMouselookModeInSignal(nullptr), @@ -794,13 +799,17 @@ void LLAgent::moveUp(S32 direction) mLastJumpInputTime = LLTimer::getTotalSeconds(); } setControlFlags(AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP); + mCrouch = false; } else if (direction < 0) { setControlFlags(AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP); } - gAgentCamera.resetView(); + if (!mCrouch) + { + gAgentCamera.resetView(); + } } //----------------------------------------------------------------------------- @@ -851,6 +860,11 @@ void LLAgent::movePitch(F32 mag) } } +bool LLAgent::isCrouching() const +{ + return mCrouch && !getFlying(); +} + // Does this parcel allow you to fly? bool LLAgent::canFly() { @@ -918,6 +932,7 @@ void LLAgent::setFlying(bool fly, bool fail_sound) { add(LLStatViewer::FLY, 1); } + mCrouch = false; setControlFlags(AGENT_CONTROL_FLY); } else @@ -1346,9 +1361,32 @@ LLVector3d LLAgent::getPosGlobalFromAgent(const LLVector3 &pos_agent) const return pos_agent_d + mAgentOriginGlobal; } +static void finish_sit_on_ground(bool success, void*) +{ + if (success && mAllowedToSit) + { + gAgent.setFlying(false); + gAgent.clearControlFlags(AGENT_CONTROL_STAND_UP); // might have been set by autopilot + gAgent.setControlFlags(AGENT_CONTROL_SIT_ON_GROUND); + } +} + void LLAgent::sitDown() { - if (mAllowedToSit) setControlFlags(AGENT_CONTROL_SIT_ON_GROUND); + // Sit on the ground directly beneath (or above) the agent's current position. + // Refuse if that spot is too far away, and otherwise walk/fly there first, + // rather than snapping straight down onto it (e.g. terrain far below a flying + // agent) as AGENT_CONTROL_SIT_ON_GROUND would do if sent immediately. + LLVector3d pos_global = getPositionGlobal(); + LLVector3d sit_spot_global = pos_global; + sit_spot_global.mdV[VZ] = LLWorld::getInstance()->resolveLandHeightGlobal(pos_global); + + if (dist_vec(pos_global, sit_spot_global) > MAX_SIT_ON_GROUND_DISTANCE) + { + return; + } + + startAutoPilotGlobal(sit_spot_global, "Sit", NULL, finish_sit_on_ground, NULL, 0.7f); } //----------------------------------------------------------------------------- @@ -1447,7 +1485,7 @@ LLVector3 LLAgent::getReferenceUpVector() return up_vector; } -// Radians, positive is forward into ground +// Radians, positive is downward toward ground //----------------------------------------------------------------------------- // pitch() //----------------------------------------------------------------------------- @@ -1461,27 +1499,23 @@ void LLAgent::pitch(F32 angle) LLVector3 skyward = getReferenceUpVector(); + // SL-19286 Avatar is upside down when viewed from below + // after left-clicking the mouse on the avatar and dragging down + // + // The issue is observed on angle below 10 degrees + const F32 look_down_limit = 179.f * DEG_TO_RAD; + const F32 look_up_limit = 10.f * DEG_TO_RAD; + + F32 angle_from_skyward = acos(mFrameAgent.getAtAxis() * skyward); + // clamp pitch to limits - if (angle >= 0.f) + if ((angle >= 0.f) && (angle_from_skyward + angle > look_down_limit)) { - const F32 look_down_limit = 179.f * DEG_TO_RAD; - F32 angle_from_skyward = acos(mFrameAgent.getAtAxis() * skyward); - if (angle_from_skyward + angle > look_down_limit) - { - angle = look_down_limit - angle_from_skyward; - } + angle = look_down_limit - angle_from_skyward; } - else if (angle < 0.f) + else if ((angle < 0.f) && (angle_from_skyward + angle < look_up_limit)) { - const F32 look_up_limit = 5.f * DEG_TO_RAD; - const LLVector3& viewer_camera_pos = LLViewerCamera::getInstance()->getOrigin(); - LLVector3 agent_focus_pos = getPosAgentFromGlobal(gAgentCamera.calcFocusPositionTargetGlobal()); - LLVector3 look_dir = agent_focus_pos - viewer_camera_pos; - F32 angle_from_skyward = angle_between(look_dir, skyward); - if (angle_from_skyward + angle < look_up_limit) - { - angle = look_up_limit - angle_from_skyward; - } + angle = look_up_limit - angle_from_skyward; } if (fabs(angle) > 1e-4) @@ -2134,7 +2168,7 @@ bool LLAgent::needsRenderAvatar() return mShowAvatar && mOutfitChosen; } - if (gAgentCamera.cameraMouselook() && !LLVOAvatar::sVisibleInFirstPerson) + if (gAgentCamera.isHidingAvatarForFirstPerson() && !LLVOAvatar::sVisibleInFirstPerson) { return false; } @@ -2150,7 +2184,7 @@ bool LLAgent::needsRenderHead() { return mShowAvatar; } - return (LLVOAvatar::sVisibleInFirstPerson && LLPipeline::sReflectionRender) || (mShowAvatar && !gAgentCamera.cameraMouselook()); + return (LLVOAvatar::sVisibleInFirstPerson && LLPipeline::sReflectionRender) || (mShowAvatar && !gAgentCamera.isHidingAvatarForFirstPerson()); } //----------------------------------------------------------------------------- @@ -5017,6 +5051,437 @@ void LLAgent::renderAutoPilotTarget() } } +static U64 g_lastUpdateTime { 0 }; +static F32 g_deltaTime { 0.0f }; +static S32 g_lastUpdateFrame { 0 }; +static S32 g_deltaFrame { 0 }; + +void LLAgent::updateGameControlMode() +{ + // Auto-derive the active mode from avatar/camera state. Cursor mode takes + // precedence over Flycam: toggling the mouse cursor on while Flycam is engaged + // reports CONTROL_MODE_CURSOR (so the left stick drives the on-screen cursor) + // while isUsingFlycam() stays true underneath -- LLAgent::updateFlycam() keeps + // driving the camera every frame regardless of the reported mode (it is gated + // on LLAgentCamera's own flycam state directly, not on AgentControlMode), and + // LLGameControl::setFlycamEngaged() below tells the input-mapping lookup to + // let Flycam's own axis/button config (Pan/Tilt/Boom/...) keep driving + // whatever physical inputs Cursor mode's own mouse-cursor actions don't claim. + // This is how a single Cursor mode ends up with two flavors -- "other axes" + // control the avatar/camera depending on whether Flycam is concurrently + // engaged -- without needing a distinct mode of its own. + // Next: mouselook (the camera mode can be entered while sitting too, so it is + // checked ahead of Captive), then sitting/controls-taken maps to Captive, + // otherwise the normal Avatar mode. + LLGameControl::AgentControlMode mode; + if (mUsingMouseCursor) + { + mode = LLGameControl::CONTROL_MODE_CURSOR; + } + else if (isUsingFlycam()) + { + mode = LLGameControl::CONTROL_MODE_FLYCAM; + } + else if (gAgentCamera.cameraMouselook()) + { + mode = LLGameControl::CONTROL_MODE_MOUSELOOK; + } + else if (isSitting()) + { + mode = LLGameControl::CONTROL_MODE_CAPTIVE; + } + else + { + mode = LLGameControl::CONTROL_MODE_AVATAR; + } + LLGameControl::setFlycamEngaged(isUsingFlycam()); + LLGameControl::setAgentControlMode(mode); + gAgentCamera.updateFlycamCursorVisibility(mode); +} + +void LLAgent::applyExternalActions(const LLGameControl::AgentActions& actions) +{ + llassert(LLCoros::on_main_thread_main_coro()); + // Valid whenever game control is driving avatar or flycam: in FlyCam mode this + // only services the flycam on/off toggle (movement bits are ignored below). + assert(LLGameControl::isEnabled() + && (LLGameControl::willControlAvatar() || LLGameControl::willControlFlycam())); + + // Process the non-flag actions first (see AgentActions comment in + // llgamecontrol.h). Each bit in mMiscActionBits is already edge-triggered + // by LLGameControllerManager::computeAgentActions() (set only on the + // frame the bound button transitions from not-pressed to pressed), so no + // further debouncing is needed here -- just react to the bit directly. + const U32 misc_actions = actions.mMiscActionBits; + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_FLY) + { + setFlying(!getFlying()); + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_FLYCAM) + { + toggleFlycam(); + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_SIT) + { + if (isSitting()) + { + standUp(); + } + else if (LLViewerObject* seat = find_nearby_seat()) + { + handle_object_sit(seat, LLVector3::zero); + } + else + { + sitDown(); + } + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_SPEAK) + { + LLVoiceClient::getInstance()->toggleUserPTTState(); + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_MOUSELOOK) + { + // Mirrors LLViewMouselook's "View.Mouselook" toggle. + if (!gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToMouselook(); + } + else + { + gAgentCamera.changeCameraToDefault(); + } + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_MOUSE_CURSOR) + { + toggleMouseCursorMode(); + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_ESCAPE) + { + resetToAvatarMode(); + } + + // CONTROL_MODE_CURSOR: drive the actual on-screen cursor from + // actions.mMouseCursorDX/DY (the analog deflection of whatever's bound to "Mouse + // left/right"/"Mouse up/down") BEFORE the click dispatch below, so a synthesized + // click lands wherever the stick just moved the cursor to, in the same frame. + if (LLGameControl::getAgentControlMode() == LLGameControl::CONTROL_MODE_CURSOR) + { + constexpr F32 MOUSE_CURSOR_PIXELS_PER_SEC = 1200.f; + U64 mouse_cursor_now = LLFrameTimer::getTotalTime(); + F32 mouse_cursor_dt = (mLastMouseCursorUpdate == 0) + ? 0.f : F32(mouse_cursor_now - mLastMouseCursorUpdate) / (F32)(USEC_PER_SEC); + mLastMouseCursorUpdate = mouse_cursor_now; + + if (actions.mMouseCursorDX != 0.f || actions.mMouseCursorDY != 0.f) + { + LLCoordGL cursor_pos = gViewerWindow->getCurrentMouse(); + S32 new_x = cursor_pos.mX + ll_round(actions.mMouseCursorDX * MOUSE_CURSOR_PIXELS_PER_SEC * mouse_cursor_dt); + // LLCoordGL's Y increases upward, but mMouseCursorDY is in screen-frame + // (Y increases downward, same as CURSOR_DY/CURSOR_PY on the wire), so it + // subtracts here rather than adds. + S32 new_y = cursor_pos.mY - ll_round(actions.mMouseCursorDY * MOUSE_CURSOR_PIXELS_PER_SEC * mouse_cursor_dt); + gViewerWindow->moveCursorTo(new_x, new_y); + } + } + else + { + mLastMouseCursorUpdate = 0; + } + + // Simulated mouse buttons (see llgamecontrol.h's AvatarMouseButton) are + // level-triggered: actions.mMouseButtonBits reflects which buttons are + // currently held, so the press/release edges are found here by diffing + // against last frame's value. Each edge is dispatched through + // LLViewerWindow exactly like a real mouse click, at the current cursor + // position, so it drives hover/pick/pie-menu/tool-manager the same way a + // hardware click would (e.g. touching/grabbing objects, walk-to-clicked, + // pie menu on right click). + U32 mouse_pressed_edges = actions.mMouseButtonBits & ~mPrevMouseButtonBits; + U32 mouse_released_edges = ~actions.mMouseButtonBits & mPrevMouseButtonBits; + mPrevMouseButtonBits = actions.mMouseButtonBits; + + if (mouse_pressed_edges & LLGameControl::AVATAR_MOUSE_BUTTON_LEFT) + { + gViewerWindow->handleMouseDown(gViewerWindow->getWindow(), gViewerWindow->getCurrentMouse(), MASK_NONE); + } + if (mouse_released_edges & LLGameControl::AVATAR_MOUSE_BUTTON_LEFT) + { + gViewerWindow->handleMouseUp(gViewerWindow->getWindow(), gViewerWindow->getCurrentMouse(), MASK_NONE); + } + if (mouse_pressed_edges & LLGameControl::AVATAR_MOUSE_BUTTON_RIGHT) + { + gViewerWindow->handleRightMouseDown(gViewerWindow->getWindow(), gViewerWindow->getCurrentMouse(), MASK_NONE); + } + if (mouse_released_edges & LLGameControl::AVATAR_MOUSE_BUTTON_RIGHT) + { + gViewerWindow->handleRightMouseUp(gViewerWindow->getWindow(), gViewerWindow->getCurrentMouse(), MASK_NONE); + } + + // actions.mIsRunning is derived purely from how hard the controller's own + // movement axes are pushed (see computeAgentActions()'s analog hysteresis). + // Only let it drive the agent's running state while the controller is + // actually supplying movement this frame -- otherwise an idle-but-connected + // controller would fight the keyboard (e.g. clobbering double-tap-to-run) + // every frame even though the player is moving with the keyboard instead. + constexpr U32 CONTROLLER_MOVEMENT_FLAGS = AGENT_CONTROL_AT_POS | AGENT_CONTROL_AT_NEG + | AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_LEFT_NEG; + if ((actions.mControlFlags & CONTROLLER_MOVEMENT_FLAGS) + && actions.mIsRunning != getRunning()) + { + if (actions.mIsRunning) + { + setRunning(); + } + else + { + clearRunning(); + } + sendWalkRun(actions.mIsRunning); + } + + mExternalActionFlags = actions.mControlFlags; + + // measure delta time and frame + // Note: it is possible for the deltas to be very large + // and it is the duty of the code that uses them to clamp as necessary + U64 now = LLFrameTimer::getTotalTime(); + g_deltaTime = F32(now - g_lastUpdateTime) / (F32)(USEC_PER_SEC); + g_lastUpdateTime = now; + + S32 frame_count = LLFrameTimer::getFrameCount(); + g_deltaFrame = frame_count - g_lastUpdateFrame; + g_lastUpdateFrame = frame_count; + + if (isUsingFlycam()) + { + // Flycam will be updated later by exernal context + return; + } + + S32 direction = (S32)(mExternalActionFlags & AGENT_CONTROL_AT_POS) + - (S32)((mExternalActionFlags & AGENT_CONTROL_AT_NEG) >> 1); + if (direction != 0) + { + moveAt(direction); + } + + static U32 last_non_fly_frame = 0; + static U64 last_non_fly_time = 0; + direction = (S32)(mExternalActionFlags & AGENT_CONTROL_UP_POS) + - (S32)((mExternalActionFlags & AGENT_CONTROL_UP_NEG) >> 1); + if (direction != 0) + { + // HACK: this auto-fly logic based on original code still extant in llviewerinput.cpp::agent_jump() + // but has been cleaned up. + // TODO?: DRY this logic + if (direction > 0) + { + if (!getFlying() + && !upGrabbed() + && gSavedSettings.getBOOL("AutomaticFly")) + { + constexpr F32 FLY_TIME = 0.5f; + constexpr U32 FLY_FRAMES = 4; + F32 delta_time = (F32)(now - last_non_fly_time) / (F32)(USEC_PER_SEC); + U32 delta_frames = frame_count - last_non_fly_frame; + if( delta_time > FLY_TIME + && delta_frames > FLY_FRAMES) + { + setFlying(TRUE); + } + } + } + else + { + last_non_fly_frame = frame_count; + last_non_fly_time = now; + } + + moveUp(direction); + } + else if (!getFlying()) + { + last_non_fly_frame = frame_count; + last_non_fly_time = now; + } + + direction = (S32)(mExternalActionFlags & AGENT_CONTROL_LEFT_POS) + - (S32)((mExternalActionFlags & AGENT_CONTROL_LEFT_NEG) >> 1); + if (direction != 0) + { + moveLeft(direction); + } + + direction = (S32)(mExternalActionFlags & AGENT_CONTROL_YAW_POS) + - (S32)((mExternalActionFlags & AGENT_CONTROL_YAW_NEG) >> 1); + // The "Slow".."Fast" preferences slider scales Mouselook's turn/look speed + // and Avatar's turn speed. + F32 turn_speed_factor = 1.0f; + F32 look_speed_factor = 1.0f; + LLGameControl::AgentControlMode control_mode = LLGameControl::getAgentControlMode(); + if (control_mode == LLGameControl::CONTROL_MODE_MOUSELOOK) + { + turn_speed_factor = LLGameControl::getSpeedFactor(LLGameControl::getModeName(control_mode)); + look_speed_factor = turn_speed_factor; + } + else if (control_mode == LLGameControl::CONTROL_MODE_AVATAR) + { + turn_speed_factor = LLGameControl::getSpeedFactor(LLGameControl::getModeName(control_mode)); + } + + if (direction != 0) + { + F32 sign = (direction < 0 ? -1.0f : 1.0f); + // actions.mYawAmplitude is the analog stick deflection ([-1, 1]) that + // set this flag; use its magnitude to modulate turn speed instead of + // always snapping to full rate + moveYaw(sign * fabs(actions.mYawAmplitude) * turn_speed_factor); + } + + { + F32 pitch_sign = ((mExternalActionFlags & AGENT_CONTROL_PITCH_POS) > 0 ? 1.0f : 0.0f) + - ((mExternalActionFlags & AGENT_CONTROL_PITCH_NEG) > 0 ? 1.0f : 0.0f); + // Same analog modulation as yaw above, via actions.mPitchAmplitude. + movePitch(pitch_sign * fabs(actions.mPitchAmplitude) * look_speed_factor); + } + + // actions.mZoomAmplitude ([-1, 1], from "Zoom +/-"/"Zoom +"/"Zoom -") drives the + // camera's field-of-view zoom. Positive amplitude zooms in (narrows the FOV); + if (actions.mZoomAmplitude != 0.f) + { + constexpr F32 FOV_ZOOM_RATE = 1.2f; + F32 fov_scale = powf(FOV_ZOOM_RATE, -actions.mZoomAmplitude * g_deltaTime); + LLViewerCamera* camera = LLViewerCamera::getInstance(); + camera->setDefaultFOV(camera->getDefaultFOV() * fov_scale); + gSavedSettings.setF32("CameraAngle", camera->getView()); + } + + // actions.mScrollAmplitude ([-1, 1], from "Scroll +/-"/"Scroll +"/"Scroll -") + // moves the camera toward/away from the avatar the same way the mousewheel does, + // so the third-person/follow-cam distance persists once the avatar moves (unlike + // the keyboard's camera in/out keys, which unlock the view from the avatar). + // Positive amplitude scrolls in (camera moves closer); scrollCamera()'s clicks + // are positive-out like the mousewheel's. + if (actions.mScrollAmplitude != 0.f) + { + constexpr F32 SCROLL_CLICKS_PER_SECOND = 8.f; + gAgentCamera.scrollCamera(-actions.mScrollAmplitude * SCROLL_CLICKS_PER_SECOND * g_deltaTime); + } + + if (mExternalActionFlags & AGENT_CONTROL_STOP) + { + setControlFlags(AGENT_CONTROL_STOP); + } + + // HACK: AGENT_CONTROL_NUDGE_AT_POS is used to toggle running + if ((mExternalActionFlags & AGENT_CONTROL_NUDGE_AT_POS) > 0) + { + if (mToggleRun) + { + if (getRunning()) + { + clearRunning(); + sendWalkRun(false); + } + else + { + setRunning(); + sendWalkRun(true); + } + } + mToggleRun = false; + } + else + { + mToggleRun = true; + } + + if ((mExternalActionFlags & AGENT_CONTROL_SIT_ON_GROUND) > 0 + && isAgentAvatarValid() && !isSitting() && !getFlying() && !gAgentAvatarp->mInAir) + { + // "Sit": only sit if currently standing; a no-op otherwise + // (already sitting, flying, or airborne). + sitDown(); + } + else if ((mExternalActionFlags & AGENT_CONTROL_STAND_UP) > 0 && isSitting()) + { + // "Stand up": only stand if currently sitting. + standUp(); + } +} + +bool LLAgent::isUsingFlycam() const +{ + return gAgentCamera.isUsingFlycam(); +} + +void LLAgent::toggleFlycam() +{ + gAgentCamera.toggleFlycam(); +} + +void LLAgent::setFlycamKeyInput(U8 channel, F32 value) +{ + gAgentCamera.setFlycamKeyInput(channel, value); +} + +void LLAgent::setFlycamKeyUnroll(bool unroll) +{ + gAgentCamera.setFlycamKeyUnroll(unroll); +} + +bool LLAgent::isCameraExternallyDriven() const +{ + // Both flycam input sources now share one toggle state on LLAgentCamera -- + // LLViewerJoystick::getOverrideCamera() forwards to the same flag. + return gAgentCamera.isUsingFlycam(); +} + +void LLAgent::toggleMouseCursorMode() +{ + mUsingMouseCursor = !mUsingMouseCursor; + mLastMouseCursorUpdate = 0; // avoid a jump from a stale delta-time baseline +} + +void LLAgent::resetToAvatarMode() +{ + if (mUsingMouseCursor) + { + toggleMouseCursorMode(); + } + if (isUsingFlycam()) + { + toggleFlycam(); + } + if (gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToDefault(); + } +} + +void LLAgent::pressGameControlButton(U8 button_index) +{ + mGameControlButtonsFromKeys |= (1U << button_index); +} + +void LLAgent::releaseGameControlButton(U8 button_index) +{ + mGameControlButtonsFromKeys &= ~(1U << button_index); +} + +void LLAgent::updateFlycam() +{ + gAgentCamera.updateFlycam(g_deltaTime); +} + /********************************************************************************/ //----------------------------------------------------------------------------- |
