275 lines
7.1 KiB
Lua
275 lines
7.1 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 = {}
|
|
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 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 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
|
|
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 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)
|