mirror of https://github.com/OpenMW/openmw.git
Merge branch 'camera_settings' into 'master'
In-game camera settings Closes #6715 See merge request OpenMW/openmw!1924combined_windows_build
commit
bf8cc36645
@ -1,71 +0,0 @@
|
||||
#include "luabindings.hpp"
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/store.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
static sol::table initSettingsPackage(const Context& context, bool player)
|
||||
{
|
||||
LuaUtil::LuaState* lua = context.mLua;
|
||||
sol::table config(lua->sol(), sol::create);
|
||||
|
||||
// Access to settings.cfg. Temporary, will be removed at some point.
|
||||
auto checkRead = [player](std::string_view category)
|
||||
{
|
||||
if ((category == "Camera" || category == "GUI" || category == "Hud" ||
|
||||
category == "Windows" || category == "Input") && !player)
|
||||
throw std::runtime_error("This setting is only available in player scripts");
|
||||
};
|
||||
config["_getBoolFromSettingsCfg"] = [=](const std::string& category, const std::string& setting)
|
||||
{
|
||||
checkRead(category);
|
||||
return Settings::Manager::getBool(setting, category);
|
||||
};
|
||||
config["_getIntFromSettingsCfg"] = [=](const std::string& category, const std::string& setting)
|
||||
{
|
||||
checkRead(category);
|
||||
return Settings::Manager::getInt(setting, category);
|
||||
};
|
||||
config["_getFloatFromSettingsCfg"] = [=](const std::string& category, const std::string& setting)
|
||||
{
|
||||
checkRead(category);
|
||||
return Settings::Manager::getFloat(setting, category);
|
||||
};
|
||||
config["_getStringFromSettingsCfg"] = [=](const std::string& category, const std::string& setting)
|
||||
{
|
||||
checkRead(category);
|
||||
return Settings::Manager::getString(setting, category);
|
||||
};
|
||||
config["_getVector2FromSettingsCfg"] = [=](const std::string& category, const std::string& setting)
|
||||
{
|
||||
checkRead(category);
|
||||
return Settings::Manager::getVector2(setting, category);
|
||||
};
|
||||
config["_getVector3FromSettingsCfg"] = [=](const std::string& category, const std::string& setting)
|
||||
{
|
||||
checkRead(category);
|
||||
return Settings::Manager::getVector3(setting, category);
|
||||
};
|
||||
|
||||
const MWWorld::Store<ESM::GameSetting>* gmst = &MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
config["getGMST"] = [lua, gmst](const std::string setting) -> sol::object
|
||||
{
|
||||
const ESM::Variant& value = gmst->find(setting)->mValue;
|
||||
if (value.getType() == ESM::VT_String)
|
||||
return sol::make_object<std::string>(lua->sol(), value.getString());
|
||||
else if (value.getType() == ESM::VT_Int)
|
||||
return sol::make_object<int>(lua->sol(), value.getInteger());
|
||||
else
|
||||
return sol::make_object<float>(lua->sol(), value.getFloat());
|
||||
};
|
||||
return LuaUtil::makeReadOnly(config);
|
||||
}
|
||||
|
||||
sol::table initGlobalSettingsPackage(const Context& context) { return initSettingsPackage(context, false); }
|
||||
sol::table initPlayerSettingsPackage(const Context& context) { return initSettingsPackage(context, true); }
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
Camera: "OpenMW 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: while weapon sheathed the camera is positioned behind the character's shoulder, crosshair is always visible.
|
||||
|
||||
shoulderOffsetX: "Shoulder view horizontal offset"
|
||||
shoulderOffsetXDescription: >
|
||||
Horizontal offset of the over-the-shoulder view.
|
||||
For the left shoulder use a negative value.
|
||||
|
||||
shoulderOffsetY: "Shoulder view vertical offset"
|
||||
shoulderOffsetYDescription: >
|
||||
Vertical offset of the over-the-shoulder view.
|
||||
|
||||
autoSwitchShoulder: "Auto switch shoulder"
|
||||
autoSwitchShoulderDescription: >
|
||||
When there are obstacles that would push the camera close to the player character,
|
||||
this setting makes the camera automatically switch to the shoulder farther away from the obstacles.
|
||||
|
||||
zoomOutWhenMoveCoef: "Zoom out when move coef"
|
||||
zoomOutWhenMoveCoefDescription: >
|
||||
Moves the camera away (positive value) or towards (negative value) the player character while the character is moving.
|
||||
Works only if "view over the shoulder" is enabled. Set this to zero to disable (default: 20.0).
|
||||
|
||||
previewIfStandStill: "Preview if stand still"
|
||||
previewIfStandStillDescription: >
|
||||
Prevents the player character from turning towards the camera direction while they're idle and have their weapon sheathed.
|
||||
|
||||
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 the camera from clipping through objects that have NC (No Collision) flag turned on in the NIF model.
|
||||
|
||||
move360: "Move 360"
|
||||
move360Description: >
|
||||
Makes the movement direction independent from the camera direction while the player character's weapon
|
||||
is sheathed. For example, the player character will look at the camera while running backwards.
|
||||
|
||||
move360TurnSpeed: "Move 360 turning speed"
|
||||
move360TurnSpeedDescription: "Turning speed multiplier (default: 5.0)."
|
||||
|
||||
slowViewChange: "Smooth view change"
|
||||
slowViewChangeDescription: "Makes the transition from 1st person to 3rd person view non-instantaneous."
|
||||
|
||||
povAutoSwitch: "First person auto switch"
|
||||
povAutoSwitchDescription: "Auto switch to the first person view if there is an obstacle right behind the player."
|
||||
|
||||
|
||||
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)."
|
||||
|
@ -0,0 +1,52 @@
|
||||
local camera = require('openmw.camera')
|
||||
local util = require('openmw.util')
|
||||
local nearby = require('openmw.nearby')
|
||||
local self = require('openmw.self')
|
||||
|
||||
local forcedFirstPerson = false
|
||||
local limitSwitch = 40
|
||||
local limitReturn = 65
|
||||
|
||||
local rayOptions = {collisionType = nearby.COLLISION_TYPE.Default - nearby.COLLISION_TYPE.Actor}
|
||||
local function castRayBackward()
|
||||
local from = camera.getTrackedPosition()
|
||||
local orient = util.transform.rotateZ(camera.getYaw()) * util.transform.rotateX(camera.getPitch())
|
||||
local resLeft = nearby.castRay(from, from + orient * util.vector3(-30, -limitReturn, 0), rayOptions)
|
||||
local resRight = nearby.castRay(from, from + orient * util.vector3(30, -limitReturn, 0), rayOptions)
|
||||
local distLeft = limitReturn + 1
|
||||
local distRight = limitReturn + 1
|
||||
if resLeft.hit then distLeft = (resLeft.hitPos - from):length() end
|
||||
if resRight.hit then distRight = (resRight.hitPos - from):length() end
|
||||
return math.min(distLeft, distRight)
|
||||
end
|
||||
|
||||
local M = {
|
||||
enabled = false,
|
||||
}
|
||||
|
||||
function M.onUpdate(dt)
|
||||
if camera.getMode() ~= camera.MODE.FirstPerson then forcedFirstPerson = false end
|
||||
if not M.enabled then
|
||||
if forcedFirstPerson then
|
||||
camera.setMode(camera.MODE.ThirdPerson, false)
|
||||
forcedFirstPerson = false
|
||||
end
|
||||
return
|
||||
end
|
||||
if camera.getMode() == camera.MODE.ThirdPerson and camera.getThirdPersonDistance() < limitSwitch
|
||||
and math.abs(util.normalizeAngle(camera.getYaw() - self.rotation.z)) < math.rad(10) then
|
||||
if castRayBackward() <= limitSwitch then
|
||||
camera.setMode(camera.MODE.FirstPerson, true)
|
||||
forcedFirstPerson = true
|
||||
end
|
||||
return
|
||||
end
|
||||
if forcedFirstPerson then
|
||||
if castRayBackward() > limitReturn then
|
||||
camera.setMode(camera.MODE.ThirdPerson, false)
|
||||
forcedFirstPerson = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
@ -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
|
@ -0,0 +1,73 @@
|
||||
local core = require('openmw.core')
|
||||
local camera = require('openmw.camera')
|
||||
local input = require('openmw.input')
|
||||
local self = require('openmw.self')
|
||||
local util = require('openmw.util')
|
||||
local I = require('openmw.interfaces')
|
||||
|
||||
local Actor = require('openmw.types').Actor
|
||||
|
||||
local MODE = camera.MODE
|
||||
|
||||
local active = false
|
||||
|
||||
local M = {
|
||||
enabled = false,
|
||||
turnSpeed = 5,
|
||||
}
|
||||
|
||||
local function turnOn()
|
||||
I.Camera.disableStandingPreview()
|
||||
active = true
|
||||
end
|
||||
|
||||
local function turnOff()
|
||||
I.Camera.enableStandingPreview()
|
||||
active = false
|
||||
if camera.getMode() == MODE.Preview then
|
||||
camera.setMode(MODE.ThirdPerson)
|
||||
end
|
||||
end
|
||||
|
||||
function M.onFrame(dt)
|
||||
if core.isWorldPaused() then return end
|
||||
local newActive = M.enabled and Actor.stance(self) == Actor.STANCE.Nothing
|
||||
if newActive and not active then
|
||||
turnOn()
|
||||
elseif not newActive and active then
|
||||
turnOff()
|
||||
end
|
||||
if not active then return end
|
||||
if camera.getMode() == MODE.Static then return end
|
||||
if camera.getMode() == MODE.ThirdPerson then camera.setMode(MODE.Preview) end
|
||||
if camera.getMode() == MODE.Preview and not input.isActionPressed(input.ACTION.TogglePOV) then
|
||||
camera.showCrosshair(camera.getFocalPreferredOffset():length() > 5)
|
||||
local move = util.vector2(self.controls.sideMovement, self.controls.movement)
|
||||
local yawDelta = camera.getYaw() - self.rotation.z
|
||||
move = move:rotate(-yawDelta)
|
||||
self.controls.sideMovement = move.x
|
||||
self.controls.movement = move.y
|
||||
self.controls.pitchChange = camera.getPitch() * math.cos(yawDelta) - self.rotation.x
|
||||
if move:length() > 0.05 then
|
||||
local delta = math.atan2(move.x, move.y)
|
||||
local maxDelta = math.max(delta, 1) * M.turnSpeed * dt
|
||||
self.controls.yawChange = util.clamp(delta, -maxDelta, maxDelta)
|
||||
else
|
||||
self.controls.yawChange = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.onInputAction(action)
|
||||
if not active or core.isWorldPaused() then return end
|
||||
if action == input.ACTION.ZoomIn and camera.getMode() == MODE.Preview
|
||||
and I.Camera.getBaseThirdPersonDistance() == 30 then
|
||||
self.controls.yawChange = camera.getYaw() - self.rotation.z
|
||||
camera.setMode(MODE.FirstPerson)
|
||||
elseif action == input.ACTION.ZoomOut and camera.getMode() == MODE.FirstPerson then
|
||||
camera.setMode(MODE.Preview)
|
||||
I.Camera.setBaseThirdPersonDistance(30)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
@ -0,0 +1,102 @@
|
||||
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),
|
||||
boolSetting('', 'move360', false),
|
||||
floatSetting('', 'move360TurnSpeed', 5),
|
||||
boolSetting('', 'slowViewChange', false),
|
||||
boolSetting('', 'povAutoSwitch', false),
|
||||
},
|
||||
})
|
||||
|
||||
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 shoulderDisabled = not settings.thirdPerson:get('viewOverShoulder')
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetX', {disabled = shoulderDisabled})
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetY', {disabled = shoulderDisabled})
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'autoSwitchShoulder', {disabled = shoulderDisabled})
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'zoomOutWhenMoveCoef', {disabled = shoulderDisabled})
|
||||
|
||||
local move360Disabled = not settings.thirdPerson:get('move360')
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'move360TurnSpeed', {disabled = move360Disabled})
|
||||
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
|
Loading…
Reference in New Issue