-- 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)