Download Lua/Tactical/ActionCamera.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 36.9 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/Tactical/ActionCamera.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/Lua/Tactical/ActionCamera.lua
-
curl -L -o ActionCamera.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/Tactical/ActionCamera.lua
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 |