File: third_person.lua

package info (click to toggle)
openmw 0.49.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 33,992 kB
  • sloc: cpp: 372,479; xml: 2,149; sh: 1,403; python: 797; makefile: 26
file content (164 lines) | stat: -rw-r--r-- 6,107 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
local camera = require('openmw.camera')
local util = require('openmw.util')
local self = require('openmw.self')
local nearby = require('openmw.nearby')
local async = require('openmw.async')
local storage = require('openmw.storage')

local Actor = require('openmw.types').Actor

local settings = storage.playerSection('SettingsOMWCameraThirdPerson')

local MODE = camera.MODE
local STATE = { RightShoulder = 0, LeftShoulder = 1, Combat = 2, Swimming = 3 }

local M = {
    baseDistance = 192,
    preferredDistance = 0,
    standingPreview = false,
    noOffsetControl = {},
}

local viewOverShoulder, autoSwitchShoulder
local shoulderOffset
local zoomOutWhenMoveCoef

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)
    local to = from + util.transform.rotateZ(angle) * util.vector3(0, limit, 0)
    local res = nearby.castRay(from, to, { collisionType = camera.getCollisionType() })
    if res.hit then
        return (res.hitPos - from):length()
    else
        return limit
    end
end

local function trySwitchShoulder()
    local limitToSwitch = 120     -- switch to other shoulder if wall is closer than this limit
    local limitToSwitchBack = 300 -- switch back to default shoulder if there is no walls at this distance

    local pos = camera.getTrackedPosition()
    local rayRight = ray(pos, camera.getYaw() + math.rad(90), limitToSwitchBack + 1)
    local rayLeft = ray(pos, camera.getYaw() - math.rad(90), limitToSwitchBack + 1)
    local rayRightForward = ray(pos, camera.getYaw() + math.rad(30), limitToSwitchBack + 1)
    local rayLeftForward = ray(pos, camera.getYaw() - math.rad(30), limitToSwitchBack + 1)

    local distRight = math.min(rayRight, rayRightForward)
    local distLeft = math.min(rayLeft, rayLeftForward)

    if distLeft < limitToSwitch and distRight > limitToSwitchBack then
        state = STATE.RightShoulder
    elseif distRight < limitToSwitch and distLeft > limitToSwitchBack then
        state = STATE.LeftShoulder
    elseif distRight > limitToSwitchBack and distLeft > limitToSwitchBack then
        state = defaultShoulder
    end
end

local function calculateDistance(smoothedSpeed)
    local smoothedSpeedSqr = smoothedSpeed * smoothedSpeed
    return (M.baseDistance + math.max(camera.getPitch(), 0) * 50
        + smoothedSpeedSqr / (smoothedSpeedSqr + 300 * 300) * zoomOutWhenMoveCoef)
end

local function updateState()
    local mode = camera.getMode()
    local oldState = state
    if Actor.getStance(self) ~= Actor.STANCE.Nothing and mode == MODE.ThirdPerson then
        state = STATE.Combat
    elseif Actor.isSwimming(self) then
        state = STATE.Swimming
    elseif oldState == STATE.Combat or oldState == STATE.Swimming then
        state = defaultShoulder
    elseif not state then
        state = defaultShoulder
    end
    if (mode == MODE.ThirdPerson or Actor.getCurrentSpeed(self) > 0 or state ~= oldState or noThirdPersonLastFrame)
        and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then
        if autoSwitchShoulder then
            trySwitchShoulder()
        else
            state = defaultShoulder
        end
    end
    if oldState ~= state or noThirdPersonLastFrame then
        -- State was changed, start focal point transition.
        if mode == MODE.Vanity then
            -- Player doesn't touch controls for a long time. Transition should be very slow.
            camera.setFocalTransitionSpeed(0.2)
        elseif (oldState == STATE.Combat or state == STATE.Combat) and
            (mode ~= MODE.Preview or M.standingPreview) then
            -- Transition to/from combat mode and we are not in preview mode. Should be fast.
            camera.setFocalTransitionSpeed(5.0)
        else
            camera.setFocalTransitionSpeed(1.0) -- Default transition speed.
        end

        if state == STATE.RightShoulder then
            camera.setFocalPreferredOffset(rightShoulderOffset)
        elseif state == STATE.LeftShoulder then
            camera.setFocalPreferredOffset(leftShoulderOffset)
        else
            camera.setFocalPreferredOffset(combatOffset)
        end
    end
end

function M.update(dt, smoothedSpeed)
    local mode = camera.getMode()
    if mode == MODE.FirstPerson or mode == MODE.Static then
        noThirdPersonLastFrame = true
        return
    end
    if not viewOverShoulder then
        M.preferredDistance = M.baseDistance
        camera.setPreferredThirdPersonDistance(M.baseDistance)
        if noThirdPersonLastFrame then
            camera.setFocalPreferredOffset(util.vector2(0, 0))
            camera.instantTransition()
            noThirdPersonLastFrame = false
        end
        return
    end

    if not next(M.noOffsetControl) then
        updateState()
    else
        state = nil
    end

    M.preferredDistance = calculateDistance(smoothedSpeed)
    if noThirdPersonLastFrame then -- just switched to third person view
        camera.setPreferredThirdPersonDistance(M.preferredDistance)
        camera.instantTransition()
        noThirdPersonLastFrame = false
    else
        local maxIncrease = dt * (100 + M.baseDistance)
        camera.setPreferredThirdPersonDistance(math.min(
            M.preferredDistance, camera.getThirdPersonDistance() + maxIncrease))
    end
end

return M