myspace / Lua /Tactical /ActionCamera.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
36.9 kB
ac_interpolation_time = 0
default_interpolation_time = 500
local ac_after_interpolation_idle = 700
local zoom_interpolation_time = 700
local hide_nearby_objs = {"Shrub", "SlabWallObject"}
local hide_nearby_objs_radius = 3*guim
local target_spots = {"Head", "Groin", "Hit"}
-- vignette
local ac_vignette_color = RGBA(22, 42, 22, 200)
MapVar("CameraBeforeActionCamera", false)
MapVar("ActionCameraInterpolatingTowards", false)
MapVar("VisibilityModesBeforeActionCamera", false)
MapVar("DOFBeforeActionCamera", false)
MapVar("ActionCameraFloatThread", false)
MapVar("ActionCameraInterpolationThread", false)
MapVar("ActionCameraTurnOffThread", false)
MapVar("CurrentActionCamera", false)
MapVar("ActionCameraPlaying", false)
MapVar("LocalACWillStartPlaying", false)
MapVar("ActionCameraHiddenObjs", {})
ActionCameraHidingModeCombo = {
{
id = "NoCMT",
name = Untranslated("No CMT (Default)")
},
{
id = "CMT",
name = Untranslated("CMT (Hide TreeTops/Walls)")
},
{
id = "ContourAll",
name = Untranslated("Contour Everything")
}
}
function SetActionCamera(attacker, target, disable_float, force_fp, ac_to_restore, interpolation_time, no_rotate)
local new_pos, new_lookat, preset
if not ac_to_restore then
new_pos, new_lookat, preset = CalcActionCamera(attacker, target, nil, force_fp, no_rotate)
end
SetActionCameraDirect(attacker, target, new_pos, new_lookat, preset, disable_float, interpolation_time or ac_interpolation_time, true, ac_to_restore)
end
function SetActionCameraNoFallback(attacker, target, disable_float, interpolation_time, no_rotate, preset_filter, dontActiveAC)
local new_pos, new_lookat, preset, fallback = CalcActionCamera(attacker, target, nil, nil, no_rotate)
if fallback then return false end
if preset_filter and table.find(preset_filter, preset.id) then return false end
if not dontActiveAC then
SetActionCameraDirect(attacker, target, new_pos, new_lookat, preset, disable_float, interpolation_time or ac_interpolation_time, true)
end
return true
end
function OnMsg.ActionCameraRemoved()
if netInGame then
if SelectedObj and SelectedObj:IsLocalPlayerControlled() then
ObjModified(SelectedObj)
end
end
end
function OnMsg.SelectedObjChange(obj)
Msg("ActionCameraWaitSignalEnd")
end
function OnMsg.WillStartSetpiece()
RemoveActionCamera(true, 0)
end
function NetSyncEvents.ActionCameraRemoved()
NetUpdateHash("ActionCameraPlaying", ActionCameraPlaying)
if not ActionCameraPlaying or ActionCameraPlaying == 0 then
return
end
ActionCameraPlaying = ActionCameraPlaying - 1
--print("ActionCameraPlaying dec", ActionCameraPlaying)
if ActionCameraPlaying <= 0 then
ActionCameraPlaying = false
Msg("ActionCameraRemoved")
if IsGameReplayRunning() then
_removeActionCamera(true)
end
end
NetUpdateHash("ActionCameraPlaying", ActionCameraPlaying)
end
function NetSyncEvents.SetActionCameraDirect(playerNetId, attacker, target, new_pos, new_lookat, presetId, disable_float, interpolation_time, vignette, ac_to_restore)
ActionCameraPlaying = (ActionCameraPlaying or 0) + 1
NetUpdateHash("ActionCameraPlaying", ActionCameraPlaying)
--print("ActionCameraPlaying inc", ActionCameraPlaying)
if playerNetId == netUniqueId then
LocalACWillStartPlaying = (LocalACWillStartPlaying or 0) - 1
if LocalACWillStartPlaying <= 0 then
LocalACWillStartPlaying = false
Msg("LocalACWillStartPlaying")
end
_SetActionCameraDirect(attacker, target, new_pos, new_lookat, table.get(Presets, "ActionCameraDef", "Default", presetId), disable_float, interpolation_time, vignette, ac_to_restore)
end
end
function SetActionCameraDirect(attacker, target, new_pos, new_lookat, preset, disable_float, interpolation_time, vignette, ac_to_restore)
assert(preset)
if not preset then return end
LocalACWillStartPlaying = (LocalACWillStartPlaying or 0) + 1
NetSyncEvent("SetActionCameraDirect", netUniqueId, attacker, target, new_pos, new_lookat, preset and preset.id, disable_float, interpolation_time, vignette, ac_to_restore)
end
function _SetActionCameraDirect(attacker, target, new_pos, new_lookat, preset, disable_float, interpolation_time, vignette, ac_to_restore)
if IsValidThread(ActionCameraInterpolationThread) then
DeleteThread(ActionCameraInterpolationThread)
if ActionCameraInterpolatingTowards then
_setCameraFromSavedState(attacker, ActionCameraInterpolatingTowards, true)
ActionCameraInterpolatingTowards = false
end
NetSyncEvent("ActionCameraRemoved")
elseif CurrentActionCamera then
NetSyncEvent("ActionCameraRemoved")
end
if not cameraTac.IsActive() then
return
end
Msg("SettingActionCamera")
CurrentActionCamera = ac_to_restore or {attacker, target, new_pos, new_lookat, preset}
attacker = CurrentActionCamera[1]
target = CurrentActionCamera[2]
new_pos = CurrentActionCamera[3]
new_lookat = CurrentActionCamera[4]
preset = CurrentActionCamera[5]
-- Record previous camera
LockCamera("ActionCamera")
local intTime = hr.CameraTacOverviewTime
hr.CameraTacOverviewTime = 0
cameraTac.SetOverview(false)
hr.CameraTacOverviewTime = intTime
local treeTopHidingRadius = const.CMT_HideTreeTopsCameraLookAt2DRadius
const.CMT_HideTreeTopsCameraLookAt2DRadius = 14000
local hidingMode = preset.HidingMode
if VisibilityModesBeforeActionCamera then
local enactedHidingMode = VisibilityModesBeforeActionCamera.hiding
if enactedHidingMode then
ActionCameraHiding(false, enactedHidingMode)
end
VisibilityModesBeforeActionCamera.hiding = hidingMode
end
if not CameraBeforeActionCamera then
CameraBeforeActionCamera = pack_params(GetCamera())
-- Restore camera to attacker.
-- If attacker is invalid the position will be the destination position of any interpolation.
local endPos, endLookAt, floor = GetCameraSnapToObjectParams(IsValid(attacker) and attacker:GetPos(), nil, "allow")
CameraBeforeActionCamera[1] = endPos
CameraBeforeActionCamera[2] = endLookAt
if floor and CameraBeforeActionCamera[5] then
CameraBeforeActionCamera[5].floor = floor
end
end
VisibilityModesBeforeActionCamera = VisibilityModesBeforeActionCamera or { hiding = hidingMode, hideRad = treeTopHidingRadius }
DOFBeforeActionCamera = DOFBeforeActionCamera or { GetDOFParams() }
-- Setup camera
table.change(hr, "ActionCamera", {
CameraTacClampToTerrain = false,
CameraTacUseVoxelBorder = false,
EnableObjectMarking = 0,
})
local targetFloor = IsValid(target) and GetFloorOfPos(target:GetPosXYZ()) or IsPoint(target) and GetFloorOfPos(target:xyz()) or 0
local attackerFloor = IsValid(attacker) and GetFloorOfPos(attacker:GetPosXYZ()) or IsPoint(attacker) and GetFloorOfPos(attacker:xyz()) or 0
local floor = Max(targetFloor, attackerFloor)
cameraTac.SetPosLookAtAndFloor(new_pos, new_lookat, floor, interpolation_time)
cameraTac.SetZoom(1000, interpolation_time/10)
camera.SetFovX(preset.FovX, interpolation_time)
preset:SetDOFParams(interpolation_time, attacker, target, new_pos)
-- Setup visuals
ActionCameraHiding(true, hidingMode)
ShowActionCameraVignette(vignette)
listener.DistanceFromCamera = "0.0"
ActionCameraUpdateUIVisibility()
-- Setup threads.
DeleteThread(ActionCameraFloatThread)
if ActionCameraTurnOffThread then
DeleteThread(ActionCameraTurnOffThread)
ActionCameraTurnOffThread = false
end
ActionCameraInterpolationThread = CreateRealTimeThread(function()
Sleep(interpolation_time)
WaitActionCameraInterpolation(new_pos)
Msg("ActionCameraInPosition")
end)
ActionCameraFloatThread = not disable_float and CreateRealTimeThread(function()
Sleep(interpolation_time)
WaitActionCameraInterpolation(new_pos)
while true do
local pt = GetRandomPosOnSphere(new_pos, preset.FloatSphereRadius)
cameraTac.SetCamera(pt, new_lookat, preset.FloatInterpolationTime, preset.FloatEasing)
Sleep(preset.FloatInterpolationTime)
WaitActionCameraInterpolation(pt)
Sleep(100 + AsyncRand(100))
end
end)
end
function _setCameraFromSavedState(attacker, state, force, interp_time)
local int_time = not force and (interp_time or ac_interpolation_time) or 0
cameraTac.SetZoom(state[4])
if IsValid(attacker) then
local pos = attacker:GetPos()
pos = not pos:IsValidZ() and pos:SetTerrainZ() or pos
local ptCamera, ptCameraLookAt = state[1], state[2]
local cameraVector = ptCameraLookAt - ptCamera
ptCamera = pos - cameraVector
ptCameraLookAt = pos
local extraParams = state[5]
if extraParams and extraParams.floor then
cameraTac.SetPosLookAtAndFloor(ptCamera, ptCameraLookAt, extraParams.floor, int_time)
else
cameraTac.SetCamera(ptCamera, ptCameraLookAt, int_time)
end
else
cameraTac.SetCamera(state[1], state[2], int_time)
end
if state.set_auto_fov_x then
SetAutoFovX(false, int_time)
else
camera.SetFovX(state[6], int_time)
end
return int_time
end
function _removeActionCamera(force, interp_time)
-- Stop threads
DeleteThread(ActionCameraFloatThread)
ActionCameraFloatThread = false
DeleteThread(ActionCameraInterpolationThread)
ActionCameraInterpolationThread = false
local attacker = CurrentActionCamera and CurrentActionCamera[1]
local preset = CurrentActionCamera and CurrentActionCamera[5]
listener.DistanceFromCamera = "1.0"
if CameraBeforeActionCamera then
if VisibilityModesBeforeActionCamera then
if VisibilityModesBeforeActionCamera.hideRad then
const.CMT_HideTreeTopsCameraLookAt2DRadius = VisibilityModesBeforeActionCamera.hideRad
end
end
local int_time = _setCameraFromSavedState(attacker, CameraBeforeActionCamera, force, interp_time)
ActionCameraInterpolatingTowards = CameraBeforeActionCamera
ActionCameraInterpolationThread = CreateRealTimeThread(function()
Sleep(int_time)
table.restore(hr, "ActionCamera")
CurrentActionCamera = false
ActionCameraUpdateUIVisibility()
UnlockCamera("ActionCamera")
NetSyncEvent("ActionCameraRemoved")
ActionCameraInterpolatingTowards = false
end)
if DOFBeforeActionCamera then
local params = table.copy(DOFBeforeActionCamera)
table.insert(params, int_time)
SetDOFParams(table.unpack(params))
end
local hidingMode = VisibilityModesBeforeActionCamera and VisibilityModesBeforeActionCamera.hiding or preset.HidingMode
if hidingMode and not IsChangingMap() then
ActionCameraHiding(false, hidingMode)
end
ShowActionCameraVignette(false, force)
if not force then
Sleep(interp_time or ac_interpolation_time)
end
CameraBeforeActionCamera = false
VisibilityModesBeforeActionCamera = false
DOFBeforeActionCamera = false
return
end
table.restore(hr, "ActionCamera", "ignore error")
CurrentActionCamera = false
ActionCameraUpdateUIVisibility()
UnlockCamera("ActionCamera")
NetSyncEvent("ActionCameraRemoved")
end
function RemoveActionCamera(force, interp_time)
if not force and ActionCameraAutoRemoveThread then
return false
end
if not CurrentActionCamera then
return false
end
if ActionCameraTurnOffThread then
DeleteThread(ActionCameraTurnOffThread)
ActionCameraTurnOffThread = false
end
if force then
_removeActionCamera(force)
else
ActionCameraTurnOffThread = CreateGameTimeThread(function()
if CurrentActionCamera and CurrentActionCamera.wait_signal then
WaitMsg("ActionCameraWaitSignalEnd", 5000)
end
_removeActionCamera(force, interp_time)
end)
end
end
function OnMsg.ActionCameraWaitSignalEnd()
if CurrentActionCamera then CurrentActionCamera.wait_signal = false end
end
function OnMsg.ChangeMap()
RemoveActionCamera("force")
end
local function lCalcCamPosLookat(a_pos, t_pos, ortog_a, ortog_t, target_z_offs, preset)
local pos = a_pos
local look_at = t_pos
if ortog_a then
pos = pos + ortog_a
end
if ortog_t then
look_at = look_at + ortog_t
end
pos = look_at + Lengthen(pos - look_at, preset.EyeBackOffset)
local cameraPos = pos:SetZ(pos:z() + preset.EyeZOffset)
local cameraLookAt = look_at:SetZ(look_at:z() + target_z_offs)
local terrainZCameraPos = terrain.GetHeight(cameraPos)
if terrainZCameraPos > cameraPos:z() then
cameraPos = cameraPos:SetZ(terrainZCameraPos + guim)
end
return cameraPos, cameraLookAt
end
local function lGetAttackAndTargetPos(attacker, target, preset, no_rotate)
local a_pos = IsPoint(attacker) and attacker or attacker:GetSpotLoc(attacker:GetSpotBeginIndex("Groin"))
local spotToCheck = target:GetSpotBeginIndex("Groin") == -1 and "Hit" or "Groin"
local t_pos = GetActionCameraTargetPos(target, spotToCheck, attacker)
if attacker == target then -- Jumping through windows, stuff that only focuses on one merc, etc
local temp = t_pos
t_pos = a_pos
a_pos = temp + Rotate(point(guim * 5, 0, 0), attacker:GetAngle())
end
local target_z_offs, ortog_t, ortog_a
if no_rotate then
local currentCamera = CameraBeforeActionCamera or pack_params(GetCamera())
local currentCamRot = Normalize(currentCamera[1] - currentCamera[2])
local dist = (a_pos - t_pos):SetZ(0):Len()
a_pos = t_pos + SetLen(currentCamRot, dist)
target_z_offs = 0
ortog_t = point30
ortog_a = point30
else
local vector = (a_pos - t_pos):SetZ(0)
local dist = vector:Len()
local target_lookat_offs = Max(guim, MulDivRound(dist, preset.LookAtTargetOffset, preset.AttackerTargetDistParam))
target_z_offs = MulDivRound(dist, preset.LookAtZOffset, preset.AttackerTargetDistParam)
ortog_t = SetLen(point(-vector:y(), vector:x(), 0), target_lookat_offs)
ortog_a = SetLen(point(vector:y(), - vector:x(), 0), preset.EyeAttackerOffset)
end
return a_pos, t_pos, target_z_offs, ortog_a, ortog_t
end
function AddActionCameraForPreset(attacker, target, preset, valid_cameras, all_cameras, cam_positioning, no_rotate)
local a_pos, t_pos, target_z_offs, ortog_a, ortog_t = lGetAttackAndTargetPos(attacker, target, preset, no_rotate)
-- camera behind right shoulder
local camera
if cam_positioning ~= "Left" then
local r_new_pos, r_new_lookat = lCalcCamPosLookat(a_pos, t_pos, ortog_a, ortog_t, target_z_offs, preset)
local r_obstacles = GetActionCameraObstacles(r_new_pos, target, attacker)
if r_obstacles then
camera = {r_new_pos, r_new_lookat, preset, r_obstacles}
if #r_obstacles == 0 and valid_cameras then
valid_cameras[#valid_cameras + 1] = camera
end
all_cameras[#all_cameras + 1] = camera
end
end
-- camera behind left shoulder
if cam_positioning ~= "Right" then
local l_new_pos, l_new_lookat = lCalcCamPosLookat(a_pos, t_pos, -ortog_a, -ortog_t, target_z_offs, preset)
local l_obstacles = GetActionCameraObstacles(l_new_pos, target, attacker)
if l_obstacles then
camera = {l_new_pos, l_new_lookat, preset, l_obstacles}
if #l_obstacles == 0 and valid_cameras then
valid_cameras[#valid_cameras + 1] = camera
end
all_cameras[#all_cameras + 1] = camera
end
end
end
function GetFPCameraFromPreset(attacker, target, preset, no_rotate)
local a_pos, t_pos, target_z_offs, ortog_a, ortog_t = lGetAttackAndTargetPos(attacker, target, preset, no_rotate)
local pos, lookat = lCalcCamPosLookat(a_pos, t_pos, false, false, target_z_offs, preset)
return {pos, lookat, preset}
end
VisionCollisionFilter = function(o)
return (not IsKindOf(o, "SlabWallObject") or not o:IsWindow()) and IsVisible(o) and not IsKindOf(o, "RoofPlaneSlab")
end
VisionCollisionFilterNoTerrain = function(o)
return VisionCollisionFilter(o) and not IsKindOf(o, "TerrainCollision")
end
local function lIsActionCameraHideableObject(cam_pos, o)
if IsKindOf(o, "SlabWallObject") then
if o:IsDoor() or not ActionCameraShouldHideWindow(cam_pos, o) then
return false
end
end
return IsKindOfClasses(o, hide_nearby_objs)
end
function GetACamsForPreset(attacker, target, preset, cam_positioning, no_rotate, output)
local ts = GetPreciseTicks()
local sources = output.sources
local dests = output.dests
local test_to_cam = output.test_to_cam
local targets = output.targets
local cameras = output.cameras
local a_pos, t_pos, target_z_offs, ortog_a, ortog_t = lGetAttackAndTargetPos(attacker, target, preset, no_rotate)
local function pushTest(src, dest, cam, target)
sources[#sources + 1] = src
dests[#dests + 1] = dest
test_to_cam[#test_to_cam + 1] = cam
targets[#targets + 1] = target
end
local function fillOutput(cam_pos, cam_look_at)
if cam_pos:z() - terrain.GetHeight(cam_pos) < guim then
return
end
local cam = {cam_pos, cam_look_at, preset, {target_visible = true, min_dist = max_int}, begin_idx = #sources + 1}
cameras[#cameras + 1] = cam
--local target_pos = C_GetActionCameraTargetPos(attacker, "Torso")
local target_pos = GetActionCameraTargetPos(attacker, "Torso")
pushTest(cam_pos, target_pos, cam, attacker)
for _, spot in ipairs(target_spots) do
local hasSpot = target:GetSpotBeginIndex(spot) ~= -1
if not hasSpot then goto continue end
--target_pos = C_GetActionCameraTargetPos(target, spot, attacker)
target_pos = GetActionCameraTargetPos(target, spot, attacker)
pushTest(cam_pos, target_pos, cam, target)
::continue::
end
end
if cam_positioning ~= "Left" then --behind right shldr, idk why its inversed
local r_new_pos, r_new_lookat = lCalcCamPosLookat(a_pos, t_pos, ortog_a, ortog_t, target_z_offs, preset)
fillOutput(r_new_pos, r_new_lookat)
end
if cam_positioning ~= "Right" then --behind lft shldr
local l_new_pos, l_new_lookat = lCalcCamPosLookat(a_pos, t_pos, -ortog_a, -ortog_t, target_z_offs, preset)
fillOutput(l_new_pos, l_new_lookat)
end
end
local buffer = point(guim, guim, guim)
function CalcActionCamera(attacker, target, cam_positioning, force_fp, no_rotate)
no_rotate = no_rotate or false
local fp_cam
if force_fp then
fp_cam = GetFPCameraFromPreset(attacker, target, Presets.ActionCameraDef.Default.FirstPerson_Cam)
return fp_cam[1], fp_cam[2], fp_cam[3]
end
local valid_cameras, all_cameras = {}, {}
local output = {
sources = {},
dests = {}, --target pt
targets = {}, --target obj
cameras = {},
test_to_cam = {},
}
local sources = output.sources
local dests = output.dests
local test_to_cam = output.test_to_cam
local targets = output.targets
local cameras = output.cameras
local stance = attacker.stance
if #(stance or "") == 0 then
stance = "Crouch" -- attacker.species ~= "Human"
end
for _, preset in ipairs(Presets.ActionCameraDef.Default) do
local isHigherCam = preset.id == "Z_HigherCamera"
assert(preset:HasMember(stance), "Invalid stance in ActionCameraDef properties")
if preset[stance] and not preset.SetPieceOnly and (preset.NoRotate == no_rotate or isHigherCam) then
if (not no_rotate and preset.id == "FirstPerson_Cam") or (no_rotate and isHigherCam) then
fp_cam = GetFPCameraFromPreset(attacker, target, preset, no_rotate)
else
GetACamsForPreset(attacker, target, preset, cam_positioning, no_rotate, output)
end
end
end
if #sources > 0 then
ACVisibilityBatchTest(sources, dests,
function(obj, idx, pos, dist)
local src = sources[idx]
if VisionCollisionFilter(obj) and not lIsActionCameraHideableObject(src, obj) and obj ~= target then
local cam = test_to_cam[idx]
local obstacles = cam[4]
--table.insert(obstacles, obj)
obstacles[#obstacles + 1] = obj
obstacles.min_dist = Min(obstacles.min_dist, dist)
if targets[idx] == target then
obstacles.target_visible = false
end
end
end)
for i, cam in ipairs(cameras) do
if cam[3].id ~= "Z_HigherCamera" then
local obstacles = cam[4]
if #obstacles <= 0 then
--test for units blocking view
local j = cam.begin_idx + 1
while test_to_cam[j] == cam do
--[[for _, unit in ipairs(g_Units) do
if unit ~= target and unit ~= attacker and ClipSegmentWithBox3D(sources[j], dests[j], unit) then
table.insert(obstacles, unit)
obstacles.target_visible = false
break
end
end]]
local s, d = sources[j], dests[j]
local min = point(Min(s:x(), d:x()), Min(s:y(), d:y()), Min(s:z(), d:z())) - buffer
local max = point(Max(s:x(), d:x()), Max(s:y(), d:y()), Max(s:z(), d:z())) + buffer
local b = box(min, max)
MapForEach(b, "Unit", function(unit)
if unit ~= target and unit ~= attacker and ClipSegmentWithBox3D(sources[j], dests[j], unit) then
obstacles[#obstacles + 1] = unit
obstacles.target_visible = false
return "break"
end
end)
if not obstacles.target_visible then
break
end
j = j + 1
end
if #obstacles <= 0 then
valid_cameras[#valid_cameras + 1] = cam
end
end
end
end
end
all_cameras = cameras
if next(valid_cameras) then
local seed = xxhash(IsPoint(attacker) and attacker or attacker:GetPos(), GetActionCameraTargetPos(target))
local rand = BraidRandom(seed, #valid_cameras)
local camera = valid_cameras[1 + rand]
return camera[1], camera[2], camera[3]
end
local tie
local best_match
for i = 1, #all_cameras do
local cam = all_cameras[i]
if cam[4].target_visible then
if not best_match then
best_match = cam
elseif #cam[4] < #best_match[4] then
best_match = cam
elseif #cam[4] == #best_match[4] then
tie = #cam[4]
end
end
end
if tie then
for i = 1, #all_cameras do
local cam = all_cameras[i]
local col_cam = cam[4]
if #col_cam == tie then
local col_best_match = best_match[4]
if col_cam.min_dist > col_best_match.min_dist then
best_match = cam
end
end
end
end
assert(fp_cam)
local fallback = not best_match
best_match = best_match or fp_cam
return best_match[1], best_match[2], best_match[3], fallback
end
function GetActionCameraTargetPos(target, spot_id, attacker)
if IsPoint(target) then
local valid_z = (target:IsValidZ() and target:z()) or spot_id and attacker and attacker:GetSpotLoc(attacker:GetSpotBeginIndex(spot_id)):z()
return valid_z and target:SetZ(valid_z) or target:SetTerrainZ()
elseif spot_id then
return target:GetSpotLoc(target:GetSpotBeginIndex(spot_id))
else
return target:GetPos()
end
end
GetActionCameraTargetPos = C_GetActionCameraTargetPos
function GetRandomPosOnSphere(center, radius)
local z = AsyncRand(2*radius + 1) - radius
local circle_radius = sqrt(radius*radius - z*z)
local rand_angle = AsyncRand(360)
local x = circle_radius * sin(rand_angle*60) / 4096
local y = circle_radius * cos(rand_angle*60) / 4096
return center + point(x, y, z)
end
function WaitActionCameraInterpolation(dest_pt)
local step = 10
local time = 0
while GetCamera() ~= dest_pt do
Sleep(step)
time = time + step
if time > 2000 then -- timeout after 2000 ms
if not IsEditorActive() then
assert(hr.CameraTacClampToTerrain == false)
assert(hr.CameraTacUseVoxelBorder == false)
assert(not CurrentActionCamera.wait_signal)
end
break
end
end
end
function OnMsg.UnitStanceChanged(unit)
CreateRealTimeThread(function()
Sleep(1) -- Wait for the change stance command to finish.
if not CurrentActionCamera or IsValidThread(ActionCameraTurnOffThread) then return end
local attacker, target, pos, lookat, preset = table.unpack(CurrentActionCamera)
if unit == attacker and preset[unit.stance] ~= true and unit:CanBeControlled() then
SetActionCamera(attacker, target)
end
end)
end
function KeepCurrentActionCamera(attacker, target)
return CurrentActionCamera and CurrentActionCamera[1] == attacker and CurrentActionCamera[2] == target
end
MapVar("ActionCameraAutoRemoveThread", false)
local function WaitAutoRemoveActionCamera(sleep, ac_to_restore, interp_time, interp_time_end)
if not interp_time_end then interp_time_end = interp_time end
if IsValidThread(ActionCameraAutoRemoveThread) then
ac_to_restore = false
end
DeleteThread(ActionCameraAutoRemoveThread)
ActionCameraAutoRemoveThread = CreateGameTimeThread(function()
local total_t = 0
assert(CurrentActionCamera)
while not CurrentActionCamera do
--wait for it to boot?
Sleep(100)
total_t = total_t + 100
if total_t >= 10000 then
return --no ac to remove
end
end
if CurrentActionCamera then
CurrentActionCamera.autoremove = true
if not sleep then
CurrentActionCamera.wait_signal = true
end
end
Sleep((sleep or 100) + (interp_time and interp_time or ac_interpolation_time))
if CurrentActionCamera and CurrentActionCamera.wait_signal then
WaitMsg("ActionCameraWaitSignalEnd", 5000)
else
Sleep(500)
end
ActionCameraAutoRemoveThread = false
if CurrentActionCamera then
if ac_to_restore then
SetActionCamera(nil, nil, nil, nil, ac_to_restore, interp_time_end)
else
RemoveActionCamera(false, interp_time_end)
end
end
end)
end
function SetActionCameraNoFallbackSync(attacker, target, disable_float, interpolation_time, no_rotate, preset_filter, dontActiveAC)
local new_pos, new_lookat, preset, fallback = CalcActionCamera(attacker, target, nil, nil, no_rotate)
--NetUpdateHash("SetActionCameraNoFallbackSync", new_pos, new_lookat, attacker, target, preset.id, fallback, dontActiveAC, no_rotate)
if fallback then return false end
if preset_filter and table.find(preset_filter, preset.id) then return false end
assert(preset)
if not preset then return end
if not dontActiveAC then
LocalACWillStartPlaying = (LocalACWillStartPlaying or 0) + 1
end
NetSyncEvents.SetActionCameraDirect(not dontActiveAC and netUniqueId, attacker, target, new_pos, new_lookat, preset and preset.id, disable_float, interpolation_time or ac_interpolation_time, true)
return true
end
--[[function SetActionCameraDirectSync(attacker, target, new_pos, new_lookat, preset, disable_float, interpolation_time, vignette, ac_to_restore, dontActiveAC)
assert(preset)
if not preset then return end
if not dontActiveAC then
LocalACWillStartPlaying = (LocalACWillStartPlaying or 0) + 1
end
NetSyncEvents.SetActionCameraDirect(not dontActiveAC and netUniqueId, attacker, target, new_pos, new_lookat, preset and preset.id, disable_float, interpolation_time, vignette, ac_to_restore)
end]]
function SetAutoRemoveActionCamera(attacker, target, sleep, restore_prev_ac, interp_time, interp_time_end, no_wait, dontActiveAC)
if GetInGameInterfaceModeDlg("IModeAIDebug") then
return
end
local ac_to_restore
if restore_prev_ac and CurrentActionCamera and not CurrentActionCamera.autoremove then
ac_to_restore = CurrentActionCamera
end
local no_rotate = not attacker:IsLocalPlayerTeam()
local foundCamera = SetActionCameraNoFallbackSync(attacker, target, "disable_float", interp_time, no_rotate, {"Z_HigherCamera"}, dontActiveAC)
if not foundCamera then
CinematicKillDebugPrint("Was going to play action camera, but found no suitable preset. Oh well.")
--return -- no return so both clients sleep the same amount of time!
end
if not dontActiveAC and foundCamera then
WaitAutoRemoveActionCamera(sleep, ac_to_restore, interp_time, interp_time_end)
end
if not no_wait then
Sleep(ac_interpolation_time + ac_after_interpolation_idle)
end
end
function ActionCameraShouldHideWindow(camera_pos, window)
local attacker = CurrentActionCamera[1]
local target = CurrentActionCamera[2]
local a_pos = IsPoint(attacker) and attacker or attacker:GetSpotLoc(attacker:GetSpotBeginIndex("Head"))
local window_bbox = window:GetObjectBBox()
if ClipSegmentWithBox3D(camera_pos, a_pos, window_bbox) then
return true
end
for _, spot in ipairs(target_spots) do
local hasSpot = target:GetSpotBeginIndex(spot) ~= -1
if not hasSpot then goto continue end
local t_pos = GetActionCameraTargetPos(target, spot, attacker)
if ClipSegmentWithBox3D(camera_pos, t_pos, window_bbox) then
return true
end
::continue::
end
return false
end
local function lHidingMode_NoCMT(bHide)
if bHide then
StopAllHiding("ActionCamera", 0, 0)
local camera_pos = CurrentActionCamera[3]
local lookat = CurrentActionCamera[4]
MapForEach(camera_pos, lookat, hide_nearby_objs_radius, hide_nearby_objs, const.efVisible, function(o)
local hide = lIsActionCameraHideableObject(camera_pos, o)
if hide then
o:SetShadowOnlyImmediate(true)
ActionCameraHiddenObjs[#ActionCameraHiddenObjs + 1] = o
end
end)
else
ResumeAllHiding("ActionCamera")
for _, o in ipairs(ActionCameraHiddenObjs) do
if IsValid(o) then
o:SetShadowOnlyImmediate(false)
end
end
ActionCameraHiddenObjs = {}
end
end
local function lHidingMode_CMT(bHide)
if bHide then
local camera_pos = CurrentActionCamera[3]
local lookat = CurrentActionCamera[4]
MapForEach(camera_pos, lookat, hide_nearby_objs_radius, hide_nearby_objs, const.efVisible, function(o)
local hide = lIsActionCameraHideableObject(camera_pos, o)
if hide then
o:SetShadowOnlyImmediate(true)
ActionCameraHiddenObjs[#ActionCameraHiddenObjs + 1] = o
end
end)
else
for _, o in ipairs(ActionCameraHiddenObjs) do
if IsValid(o) then
o:SetShadowOnlyImmediate(false)
end
end
ActionCameraHiddenObjs = {}
end
end
local function lIsActionCameraHideableContourMode(cam_pos, o)
-- Dont contour items that the CMT manages
if IsContourObject(o) and o:GetGameFlags(const.gofContourInner) ~= 0 then return end
if IsKindOf(o, "Unit") then return end
if IsKindOf(o, "Slab") and o.floor == 1 then -- Dont hide slabs on the first floor (ground floor)
return false
end
return true
end
local contour_hiding_parts_check = { "Torso", "Groin", "Head" }
local function lHidingMode_ContourAll(bHide)
if bHide then
StopAllHiding("ActionCamera", 0, 0)
-- Get possibly visible units.
local camera_pos = CurrentActionCamera[3]
local targets = MapGet(camera_pos, CurrentActionCamera[4], hide_nearby_objs_radius, "Unit")
-- Make sure all body parts of theirs are visible.
for i, t in ipairs(targets) do
for _, part in ipairs(contour_hiding_parts_check) do
local lookat = t and GetActionCameraTargetPos(t, part)
local parallels_src, parallels_tar = GetParallelSourceTargetPairs(camera_pos, lookat)
if not lookat then goto continue end
for i, src in ipairs(parallels_src) do
local tar = parallels_tar[i]
local objectsInWay = IntersectObjectsOnSegment(src, tar, const.efVisible)
for i, o in ipairs(objectsInWay) do
local hide = lIsActionCameraHideableContourMode(camera_pos, o)
if hide then
o:SetShadowOnlyImmediate(true)
o:SetHierarchyGameFlags(const.gofContourInner)
ActionCameraHiddenObjs[#ActionCameraHiddenObjs + 1] = o
end
end
end
::continue::
end
end
else
ResumeAllHiding("ActionCamera")
for _, o in ipairs(ActionCameraHiddenObjs) do
if IsValid(o) then
o:SetShadowOnlyImmediate(false)
o:ClearHierarchyGameFlags(const.gofContourInner)
end
end
ActionCameraHiddenObjs = {}
end
end
local hidingModeSwitch = {
["CMT"] = lHidingMode_CMT,
["NoCMT"] = lHidingMode_NoCMT,
["ContourAll"] = lHidingMode_ContourAll,
}
function ActionCameraHiding(bHide, hidingMode)
local func = hidingModeSwitch[hidingMode]
if func then
func(bHide)
end
end
local parallel_src_target_pairs_deltas = {50, 60, 70, 80, 90, 100}
function GetParallelSourceTargetPairs(src, tar)
local result_src = {src}
local result_tar = {tar}
local dir = tar - src
local dir_x = dir:x()
local dir_y = dir:y()
local dir_z = dir:z()
local orth_vector = point(-dir_y, dir_x, 0)
local dir_orth_vectors = {}
for i = 1, 6 do
dir_orth_vectors[i] = RotateAxis(orth_vector, dir, i * 60 * 60)
end
local s, t
for _, v in ipairs(dir_orth_vectors) do
for _, d in ipairs(parallel_src_target_pairs_deltas) do
v = SetLen(v, d)
s = src + v
t = tar + v
result_src[#result_src + 1] = s
result_tar[#result_tar + 1] = t
end
end
return result_src, result_tar
end
local query_flags = const.cqfSorted | const.cqfResultIfStartInside | const.cqfFrontAndBack
function CalcSrcTarObstacles(src, tar, obstacles, ignore_min_dist, is_tar)
local result, inter_pt
local dir = tar - src
collision.Collide(src, point(100, 100, 20), 100, dir, query_flags, 0, -1, function(obj, idx, param)
if VisionCollisionFilter(obj) and not lIsActionCameraHideableObject(src, obj) then
result = obj
inter_pt = src + SetLen(dir, MulDivRound(dir:Len(), param * 10000, 10000))
return true
end
end)
if result and obstacles then
if not ignore_min_dist then
local closest_dist = src:Dist(inter_pt)
obstacles.min_dist = (closest_dist < obstacles.min_dist) and closest_dist or obstacles.min_dist
end
obstacles[#obstacles + 1] = src
if is_tar and obstacles["target_visible"] then
obstacles["target_visible"] = false
end
end
end
function GetActionCameraObstacles(camera_pos, target, attacker)
if camera_pos:z() - terrain.GetHeight(camera_pos) < guim then
return false
end
local obstacles = {min_dist = max_int}
local t_pos
local s_pos = GetActionCameraTargetPos(attacker, "Torso")
CalcSrcTarObstacles(camera_pos, s_pos, obstacles)
obstacles["target_visible"] = true
for _, spot in ipairs(target_spots) do
local hasSpot = target:GetSpotBeginIndex(spot) ~= -1
if not hasSpot then goto continue end
t_pos = GetActionCameraTargetPos(target, spot, attacker)
CalcSrcTarObstacles(camera_pos, t_pos, obstacles, nil, true)
for _, unit in ipairs(g_Units) do
if unit ~= target and unit ~= attacker and ClipSegmentWithBox3D(camera_pos, t_pos, unit) then
obstacles[#obstacles + 1] = unit
obstacles["target_visible"] = false
end
end
::continue::
end
return obstacles
end
function ActionCameraUpdateUIVisibility()
local dlg = GetInGameInterfaceModeDlg()
local menu = dlg and dlg:ResolveId("idMenu")
if not menu then return end
menu:SetVisible(not CurrentActionCamera and not CheatEnabled("CombatUIHidden"))
end
function ShowActionCameraVignette(bShow, force)
local time = not force and ac_interpolation_time or 0
if bShow then
hr.EnablePostProcVignette = 1
SetSceneParamColor(1, "VignetteTintColor", ac_vignette_color, time, 0)
else
hr.EnablePostProcVignette = EngineOptions.Vignette == "On" and 1 or 0
if CurrentLightmodel and CurrentLightmodel[1] then
SetSceneParamColor(1, "VignetteTintColor", CurrentLightmodel[1].vignette_tint_color, time, 0)
end
end
end
function ZoomActionCamera()
local cam = CurrentActionCamera
assert(cam)
local pos, lookat, target_pos = CurrentActionCamera[3], CurrentActionCamera[4], CurrentActionCamera[2]:GetPos()
if not target_pos:IsValidZ() then
target_pos = target_pos:SetTerrainZ(500)
end
-- Zoom in along the camera-target line. Action camera logic should have
-- ensured that there is no blocking objects along it.
local zoomAmount = guim * 15
local vecTargetCamera = target_pos - pos
local length = vecTargetCamera:Len()
if length < zoomAmount then -- Camera is already close, change zoom to % of length.
zoomAmount = MulDivRound(length, 100, 1000)
end
if zoomAmount < guim * 2 then
return
end
if ActionCameraFloatThread then
DeleteThread(ActionCameraFloatThread)
ActionCameraFloatThread = false
end
if ActionCameraInterpolationThread then
DeleteThread(ActionCameraInterpolationThread)
ActionCameraInterpolationThread = false
end
local zoomed_pos = target_pos - SetLen(vecTargetCamera, zoomAmount)
cameraTac.SetCamera(zoomed_pos, target_pos, zoom_interpolation_time)
end
-- test action camera
DefineClass.ActionCameraTestDummy =
{
__parents = { "Object", "EditorVisibleObject", "EditorTextObject" },
flags = { gofWhiteColored = true },
editor_text_offset = point(0,0,2*guim),
}
function ActionCameraTestDummy:Init()
self:EditorTextUpdate(true)
local other_dummies = MapGet("map", self.class)
if #(other_dummies or "") > 1 then
for _, obj in ipairs(other_dummies) do
if obj ~= self then
DoneObject(obj)
end
end
print("only one object of this class should exist: ", self.class)
end
end
DefineClass.ActionCameraTestDummy_Player =
{
__parents = { "ActionCameraTestDummy" },
entity = "Female",
}
DefineClass.ActionCameraTestDummy_Enemy =
{
__parents = { "ActionCameraTestDummy" },
entity = "Male",
}
local l_cycle_right_left = 0
function ExecTestActionCamera(def)
local attacker = MapGetFirst("map", "ActionCameraTestDummy_Player")
local target = MapGetFirst("map", "ActionCameraTestDummy_Enemy")
if not attacker or not target then
CreateMessageBox(nil, T(634182240966, "Error"), T(626828916856, "ActionCameraTestDummy_Player or ActionCameraTestDummy_Target do not exist on the map"))
return
end
local valid_cameras, all_cameras = {}, {}
AddActionCameraForPreset(attacker, target, def, valid_cameras, all_cameras)
local new_pos, new_lookat = all_cameras[l_cycle_right_left+1][1], all_cameras[l_cycle_right_left+1][2]
SetCamera(new_pos, new_lookat, nil, 1000, nil, def.FovX)
l_cycle_right_left = (l_cycle_right_left + 1) % 2
if def.DOFStrengthFar > 0 or def.DOFStrengthNear > 0 then
def:SetDOFParams(0, attacker, target)
end
end
function WaitActionCamDonePlayingSync(timeout)
while ActionCameraPlaying do
WaitMsg("ActionCameraRemoved", timeout or 100)
end
end