372 lines
10 KiB
Lua
Raw Normal View History

2022-04-18 23:46:36 -04:00
-- LUALOCALS < ---------------------------------------------------------
local ipairs, math, minetest, pairs, tonumber, type
= ipairs, math, minetest, pairs, tonumber, type
local math_atan2, math_ceil, math_cos, math_pi, math_random, math_sin,
math_sqrt
= math.atan2, math.ceil, math.cos, math.pi, math.random, math.sin,
math.sqrt
2022-04-18 23:46:36 -04:00
-- LUALOCALS > ---------------------------------------------------------
2022-04-25 22:36:45 -04:00
local modname = minetest.get_current_modname()
2022-04-25 22:36:45 -04:00
local defaults = {
cycletime = 20, -- time between cycles
hudtime = 4, -- duration to display HUD
hudreshow = 60, -- interval to re-display HUD
hudmargin = 0, -- bottom margin for HUD
dummyradius = 80, -- max radius for dummy cam
dummyheight = 32, -- max height for dummy cam
2022-04-25 22:36:45 -04:00
dummymin = 0.25, -- min radius (of max) for dummy cam
dummyclear = 16, -- min clear sight for dummy cam
vellead = -4, -- ratio of player vel for camera pos "leading/chasing"
facelead = -2, -- ratio of player facing for camera pos lead/chase
2022-04-25 22:36:45 -04:00
camdistset = 4, -- initial target distance from player to set camera
camdistmin = 1, -- min distance from player to allow camera
camdistmax = 16, -- max distance from player to allow camera
camfov = 72, -- assume camera fov for angle check
2022-04-20 07:33:31 -04:00
2022-04-25 22:36:45 -04:00
camlocktime = 1, -- min time to lock camera after moving
minlight = 4, -- min light level to not affect cam time
maxidle = 10, -- max idle time before penalty
idlepenalty = 6, -- extra countdown speed for being idle
actvig = 0.5, -- vignette when active
2022-04-25 22:36:45 -04:00
actdark = 0, -- darken when active
dummyvig = 0.75, -- vignette when dummycam
dummydark = 0.3, -- darken when dummycam
}
local config
local enabled_players
2022-04-25 22:36:45 -04:00
local function reconfigure()
config = {}
for k in pairs(defaults) do
config[k] = tonumber(minetest.settings:get(modname .. "_" .. k)) or defaults[k]
end
enabled_players = {}
for _, n in pairs((minetest.settings:get(modname .. "_players") or ""):split(',')) do
enabled_players[n] = true
2022-04-25 22:36:45 -04:00
end
return true, modname .. " config reloaded"
2022-04-25 22:36:45 -04:00
end
reconfigure()
2022-04-25 22:36:45 -04:00
minetest.register_chatcommand(modname, {
description = "Reload " .. modname .. " config from settings",
privs = {server = true},
func = reconfigure
2022-04-25 22:36:45 -04:00
})
local function setcamera(player, pos, target)
2022-04-18 23:46:36 -04:00
local delta = vector.normalize(vector.subtract(target, pos))
player:set_look_horizontal(math_atan2(delta.z, delta.x) - math_pi / 2)
player:set_look_vertical(-math_atan2(delta.y, math_sqrt(
2022-04-18 23:46:36 -04:00
delta.x ^ 2 + delta.y ^ 2)))
pos.y = pos.y - player:get_properties().eye_height
return player:set_pos(pos)
2022-04-18 23:46:36 -04:00
end
local allow
local fluidmedium
do
local allow_drawtypes = {
airlike = true,
torchlike = true,
signlike = true,
raillike = true,
plantlike = true,
firelike = true,
liquid = true,
flowingliquid = true,
glasslike = true,
glasslike_framed = true,
glasslike_framed_optional = true,
allfaces = true,
allfaces_optional = true,
}
2022-04-20 20:28:17 -04:00
local allow_nodes = {
ignore = true
}
local fluids = {}
minetest.after(0, function()
for k, v in pairs(minetest.registered_nodes) do
if allow_drawtypes[v.drawtype]
and v.paramtype == "light" then
allow_nodes[k] = true
end
if v.sunlight_propagates then
fluids[k] = "CLEAR"
else
fluids[k] = v.liquid_alternative_source or k
end
end
end)
allow = function(pos)
return allow_nodes[minetest.get_node(pos).name]
end
fluidmedium = function(pos)
return fluids[minetest.get_node(pos).name]
end
end
local function sightblocked(from, to)
local fsrc = fluidmedium(from)
for pt in minetest.raycast(from, to, false, true) do
if pt.type == "node" then
if fluidmedium(pt.under) ~= fsrc then
return pt.above
end
end
end
if fluidmedium(to) ~= fsrc then return to end
end
local getdata
do
local datastore = {}
function getdata(p)
local pname = type(p) == "string" and p or p:get_player_name()
local data = datastore[pname]
if not data then
data = {}
datastore[pname] = data
end
return data
end
minetest.register_on_leaveplayer(function(player)
datastore[player:get_player_name()] = nil
end)
end
local function hudset(player, data, key, def)
local hud = data[key]
if hud then
if hud.text == def.text then return end
player:hud_change(hud.id, "text", def.text)
hud.text = def.text
else
data[key] = {
id = player:hud_add(def),
text = def.text
}
end
end
local function dimhuds(player, data, darken, vignette)
hudset(player, data, "darken", {
hud_elem_type = "image",
position = {x = 0.5, y = 0.5},
text = darken == 0 and "" or (
"[combine:1x1^[noalpha^[opacity:"
.. math_ceil(255 * darken)),
direction = 0,
scale = {x = -100, y = -100},
offset = {x = 0, y = 0},
quick = true
})
hudset(player, data, "vignette", {
hud_elem_type = "image",
position = {x = 0.5, y = 0.5},
text = vignette == 0 and "" or (
"szutil_cinecam_vignette.png^[opacity:"
.. math_ceil(255 * vignette)),
direction = 0,
scale = {x = -100, y = -100},
offset = {x = 0, y = 0},
quick = true
})
end
local function camdummy(player, data, dtime)
data.dummycam = (data.dummycam or 0) - dtime
if data.dummycam > 0 then
local pos = player:get_pos()
pos.y = pos.y + player:get_properties().eye_height
if allow(pos) then return end
end
local theta = math_random() * math_pi * 2
2022-04-25 22:36:45 -04:00
local dummyradius = config.dummyradius
local dpos = {
x = math_cos(theta) * dummyradius,
2022-04-25 22:43:39 -04:00
y = math_random() * config.dummyheight,
z = math_sin(theta) * dummyradius
}
2022-04-25 22:36:45 -04:00
local dummymin = config.dummymin
dpos = vector.multiply(dpos, dummymin + math_random() * (1 - dummymin))
if not allow(dpos) then return end
local len = vector.length(dpos)
2022-04-25 22:36:45 -04:00
local tpos = vector.multiply(dpos, (len - config.dummyclear) / len)
if sightblocked(dpos, tpos) then return end
2022-04-25 22:36:45 -04:00
dimhuds(player, data, config.dummydark, config.dummyvig)
setcamera(player, dpos, tpos)
data.targethud = nil
2022-04-25 22:36:45 -04:00
data.dummycam = config.cycletime
end
2022-04-18 23:46:36 -04:00
local function camcheck(player, dtime)
local data = getdata(player)
2022-04-25 22:36:45 -04:00
if player:get_fov() ~= config.camfov then
player:set_fov(config.camfov)
2022-04-19 19:25:57 -04:00
end
2022-04-25 22:36:45 -04:00
local text = data.targethud or ""
if data.tiptext ~= text then data.tiptime = 0 end
data.tiptext = text
2022-04-25 22:36:45 -04:00
if data.tiptime > config.hudtime then text = "" end
data.tiptime = data.tiptime + dtime
if data.tiptime > config.hudreshow then
data.tiptime = data.tiptime - config.hudreshow
end
hudset(player, data, "tip", {
hud_elem_type = "text",
position = {x = 0.5, y = 1},
text = text,
number = 0xFFFFFF,
alignment = {x = 0, y = -1},
offset = {x = 0, y = -config.hudmargin},
})
2022-04-19 19:25:57 -04:00
2022-04-18 23:46:36 -04:00
if data.locked then
data.locked = data.locked - dtime
if data.locked > 0 then return end
data.locked = nil
end
if data.tracktime then
data.tracktime = data.tracktime - dtime
if data.tracktime <= 0 then data.tracktime = nil end
end
local target = data.tracktime and data.target
target = target and minetest.get_player_by_name(target)
if not target then
data.target = nil
2022-04-18 23:46:36 -04:00
if not (data.queue and #data.queue > 0) then
local q = {}
local pname = player:get_player_name()
for _, p in ipairs(minetest.get_connected_players()) do
local n = p:get_player_name()
if n ~= pname then
local props = p:get_properties()
local vs = props and props.visual_size
if vs and vs.x > 0 and vs.y > 0 then
q[#q + 1] = n
end
end
end
if #q < 1 then return camdummy(player, data, dtime) end
2022-04-18 23:46:36 -04:00
data.queue = q
end
data.target = data.queue[#data.queue]
data.queue[#data.queue] = nil
2022-04-25 22:36:45 -04:00
data.tracktime = config.cycletime
2022-04-18 23:46:36 -04:00
target = minetest.get_player_by_name(data.target)
if not target then return camdummy(player, data, dtime) end
2022-04-19 19:25:57 -04:00
data.dummycam = nil
2022-04-18 23:46:36 -04:00
end
local pos = player:get_pos()
pos.y = pos.y + player:get_properties().eye_height
local tpos = target:get_pos()
tpos.y = tpos.y + target:get_properties().eye_height
local tidle = getdata(target).idletime or 0
2022-04-25 22:36:45 -04:00
if tidle >= config.maxidle then
data.tracktime = data.tracktime - dtime * config.idlepenalty
2022-04-18 23:46:36 -04:00
end
2022-04-25 22:36:45 -04:00
local camdistset = config.camdistset
local camdistmax = config.camdistmax
2022-04-18 23:46:36 -04:00
local function newcam()
local theta = math_random() * math_pi * 2
local trypos = vector.add(tpos, vector.multiply({
x = math_cos(theta),
y = math_random() * 2 - 0.5,
z = math_sin(theta)
}, camdistmax))
2022-04-18 23:46:36 -04:00
trypos = vector.add(trypos, vector.multiply(
2022-04-25 22:36:45 -04:00
target:get_velocity(), config.vellead))
trypos = vector.add(trypos, vector.multiply(
target:get_look_dir(), config.facelead))
2022-04-18 23:46:36 -04:00
local dist = vector.distance(trypos, tpos)
if dist > camdistset then
2022-04-18 23:46:36 -04:00
trypos = vector.add(tpos, vector.multiply(
vector.direction(tpos, trypos), camdistset))
2022-04-18 23:46:36 -04:00
end
local bpos = sightblocked(tpos, trypos)
2022-04-25 22:36:45 -04:00
if bpos and vector.distance(tpos, bpos) < config.camdistmin then
2022-04-18 23:46:36 -04:00
return
end
trypos = bpos or trypos
if sightblocked(trypos, tpos) then
return
end
if not allow(trypos) then return end
2022-04-25 22:36:45 -04:00
dimhuds(player, data, config.actdark, config.actvig)
setcamera(player, trypos, tpos)
2022-04-18 23:46:36 -04:00
data.targethud = "Watching: " .. data.target
2022-04-18 23:46:36 -04:00
data.moved = 0
2022-04-25 22:36:45 -04:00
data.locked = config.camlocktime
2022-04-18 23:46:36 -04:00
end
if not allow(pos) then return newcam() end
local tlight = nodecore and nodecore.get_node_light(tpos)
or minetest.get_node_light(tpos)
2022-04-25 22:36:45 -04:00
local minlight = config.minlight
if tlight and tlight < minlight then
data.tracktime = data.tracktime - dtime * minlight / tlight
2022-04-18 23:46:36 -04:00
end
local dist = vector.distance(pos, tpos)
if dist < 1 or dist > camdistmax then return newcam() end
2022-04-18 23:46:36 -04:00
local look = player:get_look_dir()
local angle = vector.angle(vector.direction(pos, tpos), look)
2022-04-25 22:36:45 -04:00
if angle > config.camfov / 2/180 * math_pi then return newcam() end
2022-04-18 23:46:36 -04:00
if sightblocked(pos, tpos) then return newcam() end
data.moved = (data.moved or 0) + dtime
2022-04-25 22:36:45 -04:00
if data.moved > config.cycletime then return newcam() end
2022-04-18 23:46:36 -04:00
end
minetest.register_globalstep(function(dtime)
local players = minetest.get_connected_players()
for _, player in ipairs(players) do
local pos = player:get_pos()
local look = player:get_look_dir()
local ctl = player:get_player_control()
local data = getdata(player)
if not (ctl.up or ctl.down or ctl.left or ctl.right
or ctl.jump or ctl.LMB or ctl.RMB) and data.idlepos
and data.idlelook and vector.equals(pos, data.idlepos)
and vector.equals(look, data.idlelook) then
data.idletime = (data.idletime or 0) + dtime
else
data.idletime = 0
end
data.idlepos = pos
data.idlelook = look
end
for _, player in ipairs(players) do
if enabled_players[player:get_player_name()] then
2022-04-18 23:46:36 -04:00
camcheck(player, dtime)
end
end
end)