2022-04-19 19:25:57 -04:00

224 lines
5.9 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ipairs, math, minetest, type
= ipairs, math, minetest, type
local math_atan2, math_cos, math_pi, math_random, math_sin, math_sqrt
= math.atan2, math.cos, math.pi, math.random, math.sin, math.sqrt
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
minetest.register_privilege(modname, {
description = "experimental cinematic camera",
give_to_singleplayer = false,
give_to_admin = false
})
local function lookat(player, target)
local pos = player:get_pos()
pos.y = pos.y + player:get_properties().eye_height
local delta = vector.normalize(vector.subtract(target, pos))
player:set_look_horizontal(math_atan2(delta.z, delta.x) - math_pi / 2)
return player:set_look_vertical(-math_atan2(delta.y, math_sqrt(
delta.x ^ 2 + delta.y ^ 2)))
end
local function fluidmedium(pos)
local node = minetest.get_node(pos)
local def = minetest.registered_items[node.name]
if not def then return node.name end
if def.sunlight_propagates then return "CLEAR" end
return def.liquid_alternative_source or node.name
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 vector.multiply(vector.add(pt.under,
vector.multiply(pt.above, 3)), 0.25)
end
end
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 camcheck(player, dtime)
local data = getdata(player)
local text = ""
if data.target and data.tipttl then
data.tipttl = data.tipttl - dtime
if data.tipttl > 0 then
text = "Watching: " .. data.target
end
end
if data.tip then
if text ~= data.tip.text then
player:hud_change(data.tip.id, "text", text)
data.tip.text = text
end
elseif text ~= "" then
data.tip = {
id = player:hud_add({
hud_elem_type = "text",
position = {x = 0.5, y = 1},
text = text,
number = 0xFFFFFF,
alignment = {x = 0, y = -1},
offset = {x = 0, y = -8},
}),
text = text
}
end
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
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 end
for i = #q, 2, -1 do
local j = math_random(1, i)
local x = q[i]
q[i] = q[j]
q[j] = x
end
data.queue = q
end
data.target = data.queue[#data.queue]
data.queue[#data.queue] = nil
data.tracktime = 15
target = minetest.get_player_by_name(data.target)
if not target then return end
data.tipttl = 2
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
if tidle >= 10 then
data.tracktime = data.tracktime - dtime * 4
end
local tlight = nodecore and nodecore.get_node_light(tpos)
or minetest.get_node_light(tpos)
if tlight and tlight < 8 then
data.tracktime = data.tracktime - dtime * 8 / tlight
end
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)
}, 8))
trypos = vector.add(trypos, vector.multiply(
target:get_velocity(), -2))
local dist = vector.distance(trypos, tpos)
if dist > 4 then
trypos = vector.add(tpos, vector.multiply(
vector.direction(tpos, trypos), 4))
end
local bpos = sightblocked(tpos, trypos)
if bpos and vector.distance(tpos, bpos) < 1 then
return
end
trypos = bpos or trypos
if sightblocked(trypos, tpos) then
return
end
trypos.y = trypos.y - player:get_properties().eye_height
player:set_pos(trypos)
lookat(player, tpos)
data.moved = 0
data.locked = 1
end
local dist = vector.distance(pos, tpos)
if dist < 1 or dist > 16 then
return newcam()
end
local look = player:get_look_dir()
local angle = vector.angle(vector.direction(pos, tpos), look)
if angle > math_pi / 4 then
return newcam()
end
if sightblocked(pos, tpos) then return newcam() end
data.moved = (data.moved or 0) + dtime
if data.moved > 15 then return newcam() end
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 minetest.check_player_privs(player, modname) then
camcheck(player, dtime)
end
end
end)