|
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"} |
|
|
|
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 |
|
|
|
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) |
|
|
|
|
|
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] |
|
|
|
|
|
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()) |
|
|
|
|
|
|
|
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() } |
|
|
|
|
|
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) |
|
|
|
|
|
ActionCameraHiding(true, hidingMode) |
|
ShowActionCameraVignette(vignette) |
|
listener.DistanceFromCamera = "0.0" |
|
ActionCameraUpdateUIVisibility() |
|
|
|
|
|
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) |
|
|
|
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 |
|
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) |
|
|
|
|
|
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 |
|
|
|
|
|
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 = 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 = GetActionCameraTargetPos(target, spot, attacker) |
|
pushTest(cam_pos, target_pos, cam, target) |
|
|
|
::continue:: |
|
end |
|
end |
|
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) |
|
fillOutput(r_new_pos, r_new_lookat) |
|
end |
|
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) |
|
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 = {}, |
|
targets = {}, |
|
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" |
|
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] |
|
|
|
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 |
|
|
|
local j = cam.begin_idx + 1 |
|
while test_to_cam[j] == cam do |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
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) |
|
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 |
|
|
|
Sleep(100) |
|
total_t = total_t + 100 |
|
if total_t >= 10000 then |
|
return |
|
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) |
|
|
|
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 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.") |
|
|
|
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) |
|
|
|
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 |
|
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) |
|
|
|
|
|
local camera_pos = CurrentActionCamera[3] |
|
local targets = MapGet(camera_pos, CurrentActionCamera[4], hide_nearby_objs_radius, "Unit") |
|
|
|
|
|
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 |
|
|
|
|
|
local zoomAmount = guim * 15 |
|
local vecTargetCamera = target_pos - pos |
|
local length = vecTargetCamera:Len() |
|
if length < zoomAmount then |
|
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 |
|
|
|
|
|
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 |