Aaron Suen fd41bd6a52 Fix reshowing caption on same player
If only one player is online and they are idling in
darkness, it makes the "Watching: " caption show
up too frequently.  Instead, only show the caption
when we change to a different player.
2022-04-22 00:09:27 -04:00

281 lines
7.3 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ipairs, math, minetest, pairs, type
= ipairs, math, minetest, pairs, 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 cycletime = 20
local modname = minetest.get_current_modname()
minetest.register_privilege(modname, {
description = "experimental cinematic camera",
give_to_singleplayer = false,
give_to_admin = false
})
local function setcamera(player, pos, target)
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(
delta.x ^ 2 + delta.y ^ 2)))
pos.y = pos.y - player:get_properties().eye_height
return player:set_pos(pos)
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 pt.above
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 allow
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,
}
local allow_nodes = {
ignore = true
}
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
end
end)
allow = function(pos)
return allow_nodes[minetest.get_node(pos).name]
end
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
local dpos = {
x = math_cos(theta) * 64,
y = math_random() * 64,
z = math_sin(theta) * 64
}
dpos = vector.multiply(dpos, 0.25 + math_random() * 0.75)
if not allow(dpos) then return end
local len = vector.length(dpos)
local tpos = vector.multiply(dpos, (len - 16) / len)
if sightblocked(dpos, tpos) then return end
setcamera(player, dpos, tpos)
data.dummycam = cycletime
end
local function camcheck(player, dtime)
local data = getdata(player)
local text = ""
if data.target then
text = "Watching: " .. data.target
end
if data.tip then
if text ~= data.tip.text then data.tip.time = 0 end
data.tip.text = text
local show = text
if data.tip.time >= 2 then show = "" end
if data.tip.show ~= show then
player:hud_change(data.tip.id, "text", show)
data.tip.show = text
end
data.tip.time = data.tip.time + dtime
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,
show = text,
time = 0
}
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
data.target = nil
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
data.queue = q
end
data.target = data.queue[#data.queue]
data.queue[#data.queue] = nil
data.tracktime = cycletime
target = minetest.get_player_by_name(data.target)
if not target then return camdummy(player, data, dtime) end
data.dummycam = nil
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 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
if not allow(trypos) then return end
setcamera(player, trypos, tpos)
data.moved = 0
data.locked = 1
end
if not allow(pos) then return newcam() 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 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)