diff --git a/docs/source/generate_luadoc.sh b/docs/source/generate_luadoc.sh index f518c96202..f71e3110cb 100755 --- a/docs/source/generate_luadoc.sh +++ b/docs/source/generate_luadoc.sh @@ -66,6 +66,6 @@ $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw/*lua cd $FILES_DIR/data $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/ai.lua -$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera.lua +$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera/camera.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/mwui/init.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/settings/player.lua diff --git a/docs/source/reference/lua-scripting/interface_camera.rst b/docs/source/reference/lua-scripting/interface_camera.rst index c2db83b721..6a0f19d47b 100644 --- a/docs/source/reference/lua-scripting/interface_camera.rst +++ b/docs/source/reference/lua-scripting/interface_camera.rst @@ -2,5 +2,5 @@ Interface Camera ================ .. raw:: html - :file: generated_html/scripts_omw_camera.html + :file: generated_html/scripts_omw_camera_camera.html diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 4705355d30..a7d6cf320f 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -12,6 +12,7 @@ set(BUILTIN_DATA_FILES l10n/Calendar/en.yaml l10n/Interface/en.yaml + l10n/OMWCamera/en.yaml openmw_aux/util.lua openmw_aux/time.lua @@ -21,9 +22,10 @@ set(BUILTIN_DATA_FILES builtin.omwscripts scripts/omw/ai.lua - scripts/omw/camera.lua - scripts/omw/head_bobbing.lua - scripts/omw/third_person.lua + scripts/omw/camera/camera.lua + scripts/omw/camera/head_bobbing.lua + scripts/omw/camera/third_person.lua + scripts/omw/camera/settings.lua scripts/omw/console/player.lua scripts/omw/console/global.lua scripts/omw/console/local.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 989575ee1a..8c88045ad7 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -1,8 +1,8 @@ -PLAYER: scripts/omw/camera.lua +PLAYER: scripts/omw/mwui/init.lua +GLOBAL: scripts/omw/settings/global.lua +PLAYER: scripts/omw/settings/player.lua +PLAYER: scripts/omw/camera/camera.lua NPC,CREATURE: scripts/omw/ai.lua PLAYER: scripts/omw/console/player.lua GLOBAL: scripts/omw/console/global.lua CUSTOM: scripts/omw/console/local.lua -PLAYER: scripts/omw/mwui/init.lua -GLOBAL: scripts/omw/settings/global.lua -PLAYER: scripts/omw/settings/player.lua diff --git a/files/data/l10n/OMWCamera/en.yaml b/files/data/l10n/OMWCamera/en.yaml new file mode 100644 index 0000000000..9d5e7ac7ee --- /dev/null +++ b/files/data/l10n/OMWCamera/en.yaml @@ -0,0 +1,59 @@ +Camera: "Camera" +settingsPageDescription: "OpenMW camera settings" + +thirdPersonSettings: "Third person mode" + +viewOverShoulder: "View over the shoulder" +viewOverShoulderDescription: | + Controls third person view mode. + No: view is centered on the character's head. Crosshair is hidden. + Yes: in non-combat mode camera is positioned behind the character's shoulder, crosshair is always visible. + +shoulderOffsetX: "Shoulder view horizontal offset" +shoulderOffsetXDescription: > + Horizontal offset of the camera in the view-over-the-shoulder mode. + For the left shoulder use a negative value. + +shoulderOffsetY: "Shoulder view vertical offset" +shoulderOffsetYDescription: > + Vertical offset of the camera in the view-over-the-shoulder mode. + +autoSwitchShoulder: "Auto switch shoulder" +autoSwitchShoulderDescription: > + When player is close to an obstacle, automatically switches camera + to the shoulder that is farther away from the obstacle. + +zoomOutWhenMoveCoef: "Zoom out when move coef" +zoomOutWhenMoveCoefDescription: > + Slightly pulls camera away (or closer in case of a negative value) when the character moves. + Works only if "view over the shoulder" is enabled. To disable set it to zero (default: 20.0). + +previewIfStandStill: "Preview if stand still" +previewIfStandStillDescription: > + If enabled then the character rotation is not synchonized with the camera rotation + while the character doesn't move and not in combat mode. + +deferredPreviewRotation: "Deferred preview rotation" +deferredPreviewRotationDescription: | + If enabled then the character smoothly rotates to the view direction after exiting preview or vanity mode. + If disabled then the camera rotates rather than the character. + +ignoreNC: "Ignore 'No Collision' flag" +ignoreNCDescription: > + Prevents camera from clipping through the objects with the NC (No Collision) NIF flag. + + +headBobbingSettings: "Head bobbing in first person view" + +headBobbing_enabled: "Enabled" +headBobbing_enabledDescription: "" + +headBobbing_step: "Base step length" +headBobbing_stepDescription: "The length of each step (default: 90.0)." + +headBobbing_height: "Step height" +headBobbing_heightDescription: "The amplitude of the head bobbing (default: 3.0)." + +headBobbing_roll: "Max roll angle" +headBobbing_rollDescription: "The maximum roll angle in degrees (default: 0.2)." + diff --git a/files/data/scripts/omw/camera.lua b/files/data/scripts/omw/camera/camera.lua similarity index 89% rename from files/data/scripts/omw/camera.lua rename to files/data/scripts/omw/camera/camera.lua index 8a614c9ac3..26dcfc036c 100644 --- a/files/data/scripts/omw/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -1,20 +1,33 @@ local camera = require('openmw.camera') local core = require('openmw.core') local input = require('openmw.input') -local settings = require('openmw.settings') local util = require('openmw.util') local self = require('openmw.self') local nearby = require('openmw.nearby') +local async = require('openmw.async') local Actor = require('openmw.types').Actor -local head_bobbing = require('scripts.omw.head_bobbing') -local third_person = require('scripts.omw.third_person') +local settings = require('scripts.omw.camera.settings').thirdPerson +local head_bobbing = require('scripts.omw.camera.head_bobbing') +local third_person = require('scripts.omw.camera.third_person') local MODE = camera.MODE -local previewIfStandSill = settings._getBoolFromSettingsCfg('Camera', 'preview if stand still') -local showCrosshairInThirdPerson = settings._getBoolFromSettingsCfg('Camera', 'view over shoulder') +local previewIfStandStill = false +local showCrosshairInThirdPerson = false + +local function updateSettings() + previewIfStandStill = settings:get('previewIfStandStill') + showCrosshairInThirdPerson = settings:get('viewOverShoulder') + camera.allowCharacterDeferredRotation(settings:get('deferredPreviewRotation')) + local collisionType = util.bitAnd(nearby.COLLISION_TYPE.Default, util.bitNot(nearby.COLLISION_TYPE.Actor)) + collisionType = util.bitOr(collisionType, nearby.COLLISION_TYPE.Camera) + if settings:get('ignoreNC') then + collisionType = util.bitOr(collisionType, nearby.COLLISION_TYPE.VisualOnly) + end + camera.setCollisionType(collisionType) +end local primaryMode @@ -24,17 +37,18 @@ local noHeadBobbing = 0 local noZoom = 0 local function init() - camera.setCollisionType(util.bitOr(util.bitAnd(nearby.COLLISION_TYPE.Default, util.bitNot(nearby.COLLISION_TYPE.Actor)), nearby.COLLISION_TYPE.Camera)) camera.setFieldOfView(camera.getBaseFieldOfView()) - camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation')) if camera.getMode() == MODE.FirstPerson then primaryMode = MODE.FirstPerson else primaryMode = MODE.ThirdPerson camera.setMode(MODE.ThirdPerson) end + updateSettings() end +settings:subscribe(async:callback(updateSettings)) + local smoothedSpeed = 0 local previewTimer = 0 @@ -60,7 +74,7 @@ local function updatePOV(dt) end local idleTimer = 0 -local vanityDelay = settings.getGMST('fVanityDelay') +local vanityDelay = core.getGMST('fVanityDelay') local function updateVanity(dt) if input.isIdle() then @@ -126,7 +140,7 @@ end local function updateStandingPreview() local mode = camera.getMode() - if not previewIfStandSill or noStandingPreview > 0 + if not previewIfStandStill or noStandingPreview > 0 or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then third_person.standingPreview = false return @@ -204,7 +218,7 @@ return { enableModeControl = function() noModeControl = math.max(0, noModeControl - 1) end, --- @function [parent=#Camera] isStandingPreviewEnabled - isStandingPreviewEnabled = function() return previewIfStandSill and noStandingPreview == 0 end, + isStandingPreviewEnabled = function() return previewIfStandStill and noStandingPreview == 0 end, --- @function [parent=#Camera] disableStandingPreview disableStandingPreview = function() noStandingPreview = noStandingPreview + 1 end, --- @function [parent=#Camera] enableStandingPreview diff --git a/files/data/scripts/omw/head_bobbing.lua b/files/data/scripts/omw/camera/head_bobbing.lua similarity index 79% rename from files/data/scripts/omw/head_bobbing.lua rename to files/data/scripts/omw/camera/head_bobbing.lua index b3d96a7eba..5b48c94fed 100644 --- a/files/data/scripts/omw/head_bobbing.lua +++ b/files/data/scripts/omw/camera/head_bobbing.lua @@ -1,21 +1,29 @@ local camera = require('openmw.camera') local self = require('openmw.self') -local settings = require('openmw.settings') local util = require('openmw.util') +local async = require('openmw.async') local Actor = require('openmw.types').Actor -local doubleStepLength = settings._getFloatFromSettingsCfg('Camera', 'head bobbing step') * 2 -local stepHeight = settings._getFloatFromSettingsCfg('Camera', 'head bobbing height') -local maxRoll = math.rad(settings._getFloatFromSettingsCfg('Camera', 'head bobbing roll')) +local M = {} + +local settings = require('scripts.omw.camera.settings').headBobbing + +local doubleStepLength, stepHeight, maxRoll + +local function updateSettings() + M.enabled = settings:get('enabled') + doubleStepLength = settings:get('step') * 2 + stepHeight = settings:get('height') + maxRoll = math.rad(settings:get('roll')) +end + +updateSettings() +settings:subscribe(async:callback(updateSettings)) local effectWeight = 0 local totalMovement = 0 -local M = { - enabled = settings._getBoolFromSettingsCfg('Camera', 'head bobbing') -} - -- Trajectory of each step is a scaled arc of 60 degrees. local halfArc = math.rad(30) local sampleArc = function(x) return 1 - math.cos(x * halfArc) end diff --git a/files/data/scripts/omw/camera/settings.lua b/files/data/scripts/omw/camera/settings.lua new file mode 100644 index 0000000000..ec7f8a9c07 --- /dev/null +++ b/files/data/scripts/omw/camera/settings.lua @@ -0,0 +1,95 @@ +local storage = require('openmw.storage') +local async = require('openmw.async') +local I = require('openmw.interfaces') + +I.Settings.registerPage({ + key = 'OMWCamera', + l10n = 'OMWCamera', + name = 'Camera', + description = 'settingsPageDescription', +}) + +local thirdPersonGroup = 'SettingsOMWCameraThirdPerson' +local headBobbingGroup = 'SettingsOMWCameraHeadBobbing' + +local function boolSetting(prefix, key, default) + return { + key = key, + renderer = 'checkbox', + name = prefix..key, + description = prefix..key..'Description', + default = default, + } +end + +local function floatSetting(prefix, key, default) + return { + key = key, + renderer = 'number', + name = prefix..key, + description = prefix..key..'Description', + default = default, + } +end + +I.Settings.registerGroup({ + key = thirdPersonGroup, + page = 'OMWCamera', + l10n = 'OMWCamera', + name = 'thirdPersonSettings', + permanentStorage = true, + order = 0, + settings = { + boolSetting('', 'viewOverShoulder', true), + floatSetting('', 'shoulderOffsetX', 30), + floatSetting('', 'shoulderOffsetY', -10), + boolSetting('', 'autoSwitchShoulder', true), + floatSetting('', 'zoomOutWhenMoveCoef', 20), + boolSetting('', 'previewIfStandStill', true), + boolSetting('', 'deferredPreviewRotation', true), + boolSetting('', 'ignoreNC', true), + }, +}) + +I.Settings.registerGroup({ + key = headBobbingGroup, + page = 'OMWCamera', + l10n = 'OMWCamera', + name = 'headBobbingSettings', + permanentStorage = true, + order = 1, + settings = { + boolSetting('headBobbing_', 'enabled', true), + floatSetting('headBobbing_', 'step', 90), + floatSetting('headBobbing_', 'height', 3), + floatSetting('headBobbing_', 'roll', 0.2), + }, +}) + +local settings = { + thirdPerson = storage.playerSection(thirdPersonGroup), + headBobbing = storage.playerSection(headBobbingGroup), +} + +local function updateViewOverShoulderDisabled() + local disabled = not settings.thirdPerson:get('viewOverShoulder') + I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetX', {disabled = disabled}) + I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetY', {disabled = disabled}) + I.Settings.updateRendererArgument(thirdPersonGroup, 'autoSwitchShoulder', {disabled = disabled}) + I.Settings.updateRendererArgument(thirdPersonGroup, 'zoomOutWhenMoveCoef', {disabled = disabled}) +end + +local function updateHeadBobbingDisabled() + local disabled = not settings.headBobbing:get('enabled') + I.Settings.updateRendererArgument(headBobbingGroup, 'step', {disabled = disabled, min = 1}) + I.Settings.updateRendererArgument(headBobbingGroup, 'height', {disabled = disabled}) + I.Settings.updateRendererArgument(headBobbingGroup, 'roll', {disabled = disabled, min = 0, max = 90}) +end + +updateViewOverShoulderDisabled() +updateHeadBobbingDisabled() + +settings.thirdPerson:subscribe(async:callback(updateViewOverShoulderDisabled)) +settings.headBobbing:subscribe(async:callback(updateHeadBobbingDisabled)) + +return settings diff --git a/files/data/scripts/omw/third_person.lua b/files/data/scripts/omw/camera/third_person.lua similarity index 77% rename from files/data/scripts/omw/third_person.lua rename to files/data/scripts/omw/camera/third_person.lua index 311dbde48b..c28cbe2a4f 100644 --- a/files/data/scripts/omw/third_person.lua +++ b/files/data/scripts/omw/camera/third_person.lua @@ -1,31 +1,47 @@ local camera = require('openmw.camera') -local settings = require('openmw.settings') local util = require('openmw.util') local self = require('openmw.self') local nearby = require('openmw.nearby') +local async = require('openmw.async') local Actor = require('openmw.types').Actor +local settings = require('scripts.omw.camera.settings').thirdPerson + local MODE = camera.MODE local STATE = { RightShoulder = 0, LeftShoulder = 1, Combat = 2, Swimming = 3 } local M = { - baseDistance = settings._getFloatFromSettingsCfg('Camera', 'third person camera distance'), + baseDistance = 192, preferredDistance = 0, standingPreview = false, noOffsetControl = 0, } -local viewOverShoulder = settings._getBoolFromSettingsCfg('Camera', 'view over shoulder') -local autoSwitchShoulder = settings._getBoolFromSettingsCfg('Camera', 'auto switch shoulder') -local shoulderOffset = settings._getVector2FromSettingsCfg('Camera', 'view over shoulder offset') -local zoomOutWhenMoveCoef = settings._getFloatFromSettingsCfg('Camera', 'zoom out when move coef') +local viewOverShoulder, autoSwitchShoulder +local shoulderOffset +local zoomOutWhenMoveCoef -local defaultShoulder = (shoulderOffset.x > 0 and STATE.RightShoulder) or STATE.LeftShoulder -local rightShoulderOffset = util.vector2(math.abs(shoulderOffset.x), shoulderOffset.y) -local leftShoulderOffset = util.vector2(-math.abs(shoulderOffset.x), shoulderOffset.y) +local defaultShoulder, rightShoulderOffset, leftShoulderOffset local combatOffset = util.vector2(0, 15) +local noThirdPersonLastFrame = true + +local function updateSettings() + viewOverShoulder = settings:get('viewOverShoulder') + autoSwitchShoulder = settings:get('autoSwitchShoulder') + shoulderOffset = util.vector2(settings:get('shoulderOffsetX'), + settings:get('shoulderOffsetY')) + zoomOutWhenMoveCoef = settings:get('zoomOutWhenMoveCoef') + + defaultShoulder = (shoulderOffset.x > 0 and STATE.RightShoulder) or STATE.LeftShoulder + rightShoulderOffset = util.vector2(math.abs(shoulderOffset.x), shoulderOffset.y) + leftShoulderOffset = util.vector2(-math.abs(shoulderOffset.x), shoulderOffset.y) + noThirdPersonLastFrame = true +end +updateSettings() +settings:subscribe(async:callback(updateSettings)) + local state = defaultShoulder local function ray(from, angle, limit) @@ -66,8 +82,6 @@ local function calculateDistance(smoothedSpeed) + smoothedSpeedSqr / (smoothedSpeedSqr + 300*300) * zoomOutWhenMoveCoef) end -local noThirdPersonLastFrame = true - local function updateState() local mode = camera.getMode() local oldState = state @@ -80,9 +94,13 @@ local function updateState() elseif not state then state = defaultShoulder end - if autoSwitchShoulder and (mode == MODE.ThirdPerson or state ~= oldState or noThirdPersonLastFrame) + if (mode == MODE.ThirdPerson or Actor.currentSpeed(self) > 0 or state ~= oldState or noThirdPersonLastFrame) and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then - trySwitchShoulder() + if autoSwitchShoulder then + trySwitchShoulder() + else + state = defaultShoulder + end end if oldState ~= state or noThirdPersonLastFrame then -- State was changed, start focal point transition. @@ -116,7 +134,11 @@ function M.update(dt, smoothedSpeed) if not viewOverShoulder then M.preferredDistance = M.baseDistance camera.setPreferredThirdPersonDistance(M.baseDistance) - noThirdPersonLastFrame = false + if noThirdPersonLastFrame then + camera.setFocalPreferredOffset(util.vector2(0, 0)) + camera.instantTransition() + noThirdPersonLastFrame = false + end return end