2019-02-10 01:17:28 +01:00
minetest.register_privilege ( " camera " , { description = " Can use camera commands " } )
2016-01-30 08:56:04 +01:00
local recordings = { }
2017-02-07 23:07:18 +01:00
-- [function] Load recordings
2017-02-07 16:32:06 +01:00
local path = minetest.get_worldpath ( )
local function load ( )
2017-03-21 17:48:56 +01:00
local res = io.open ( path .. " /recordings.txt " , " r " )
if res then
res = minetest.deserialize ( res : read ( " *all " ) )
if type ( res ) == " table " then
recordings = res
end
end
2017-02-07 16:32:06 +01:00
end
2017-02-07 23:07:18 +01:00
-- Call load
2017-02-07 16:32:06 +01:00
load ( )
2017-02-07 23:07:18 +01:00
-- [function] Save recordings
2017-02-07 16:32:06 +01:00
function save ( )
2017-03-21 17:48:56 +01:00
io.open ( path .. " /recordings.txt " , " w " ) : write ( minetest.serialize ( recordings ) )
2017-02-07 16:32:06 +01:00
end
2017-02-07 23:07:18 +01:00
-- [function] Get recording list per-player for chat
2017-02-07 16:32:06 +01:00
function get_recordings ( name )
2017-03-21 17:48:56 +01:00
local recs = recordings [ name ]
local list = " "
if recs then
for name , path in pairs ( recs ) do
list = list .. name .. " , "
end
return list
else
return " You do not saved any recordings. "
end
2017-02-07 16:32:06 +01:00
end
2017-02-07 23:07:18 +01:00
-- [event] On shutdown save recordings
2017-02-07 16:32:06 +01:00
minetest.register_on_shutdown ( save )
-- Table for storing unsaved temporary recordings
local temp = { }
2018-02-17 19:22:59 +01:00
-- Table and functions for storing params per players
local player_params = { }
local function get_player_params ( playern )
if not player_params [ playern ] then
player_params [ playern ] = { }
end
return player_params [ playern ]
end
2017-02-07 23:07:18 +01:00
-- Camera definition
2016-01-30 08:56:04 +01:00
local camera = {
description = " Camera " ,
2017-01-20 02:55:19 +01:00
visual = " wielditem " ,
textures = { } ,
is_visible = false ,
collide_with_objects = false ,
collisionbox = { - 0.5 , - 0.5 , - 0.5 , 0.5 , 0.5 , 0.5 } ,
physical = false ,
visual = " cube " ,
2016-01-30 08:56:04 +01:00
driver = nil ,
mode = 0 ,
velocity = { x = 0 , y = 0 , z = 0 } ,
old_pos = nil ,
old_velocity = nil ,
pre_stop_dir = nil ,
MAX_V = 20 ,
init = function ( self , player , mode )
self.driver = player
self.mode = mode
self.path = { }
2018-02-17 19:22:59 +01:00
self.look_dir_init = player : get_look_dir ( )
2018-02-17 22:37:27 +01:00
self.speed = 0
2016-01-30 08:56:04 +01:00
end ,
}
2017-01-20 02:55:19 +01:00
2017-02-07 23:07:18 +01:00
-- [event] On step
2016-01-30 08:56:04 +01:00
function camera : on_step ( dtime )
2017-03-21 17:48:56 +01:00
-- if not driver, remove object
2016-01-30 08:56:04 +01:00
if not self.driver then
self.object : remove ( )
return
end
2017-02-07 23:07:18 +01:00
2016-01-30 08:56:04 +01:00
local pos = self.object : getpos ( )
local vel = self.object : getvelocity ( )
2018-02-18 02:37:51 +01:00
2017-03-21 17:48:56 +01:00
-- if record mode
2018-02-18 02:37:51 +01:00
if self.mode == 0 or self.mode > 1 then
2018-02-17 19:22:59 +01:00
-- Calculate pitch and yaw if look target of player is defined
local look_target = player_params [ self.driver : get_player_name ( ) ] . look_target
if look_target then
local vec_pos = vector.subtract ( look_target , pos )
2017-04-30 18:33:03 +02:00
-- Pitch
if math.abs ( vec_pos.z ) > math.abs ( vec_pos.x ) then
2018-02-17 19:22:59 +01:00
self.driver : set_look_vertical ( - math.atan2 ( vec_pos.y , math.abs ( vec_pos.z ) ) )
2018-02-15 19:55:10 +01:00
else
2018-02-17 19:22:59 +01:00
self.driver : set_look_vertical ( - math.atan2 ( vec_pos.y , math.abs ( vec_pos.x ) ) )
2017-07-18 03:02:15 +02:00
end
2018-02-17 19:22:59 +01:00
-- Yaw
self.driver : set_look_horizontal ( - math.atan2 ( vec_pos.x , vec_pos.z ) )
2017-04-30 18:33:03 +02:00
end
2017-02-07 23:07:18 +01:00
-- Update path
2016-01-30 08:56:04 +01:00
self.path [ # self.path + 1 ] = {
pos = pos ,
velocity = vel ,
2018-02-15 19:55:10 +01:00
pitch = self.driver : get_look_vertical ( ) ,
yaw = self.driver : get_look_horizontal ( )
2016-01-30 08:56:04 +01:00
}
2017-02-07 23:07:18 +01:00
-- Modify yaw and pitch to match driver (player)
2018-02-15 19:55:10 +01:00
self.object : set_look_vertical ( self.driver : get_look_vertical ( ) )
self.object : set_look_horizontal ( self.driver : get_look_horizontal ( ) )
2017-01-20 02:55:19 +01:00
2018-02-17 19:22:59 +01:00
local params = get_player_params ( self.driver : get_player_name ( ) )
2017-02-07 23:07:18 +01:00
-- Get controls
2016-01-30 08:56:04 +01:00
local ctrl = self.driver : get_player_control ( )
2017-02-07 23:07:18 +01:00
2017-03-21 17:48:56 +01:00
-- Initialize speed
2018-02-17 22:37:27 +01:00
--local speed = vector.distance(vector.new(), vel)
local speed = self.speed
2017-02-07 23:07:18 +01:00
2017-03-21 17:48:56 +01:00
-- if up, accelerate forward
2016-01-30 08:56:04 +01:00
if ctrl.up then
2018-02-17 19:22:59 +01:00
speed = math.min ( speed + ( params.speed_step or 0.1 ) , 20 )
2016-01-30 08:56:04 +01:00
end
2017-02-07 23:07:18 +01:00
2017-03-21 17:48:56 +01:00
-- if down, accelerate backward
2016-01-30 08:56:04 +01:00
if ctrl.down then
2018-02-17 19:22:59 +01:00
speed = math.max ( speed - ( params.speed_step or 0.1 ) , - 20 )
2018-02-15 19:55:10 +01:00
end
2018-02-17 19:22:59 +01:00
-- if jump, brake
if ctrl.jump then
speed = math.max ( speed * 0.9 , 0.0 )
params.rotate_speed = math.max ( ( params.rotate_speed or 0 ) * 0.9 , 0.0 )
2016-01-30 08:56:04 +01:00
end
2017-02-07 23:07:18 +01:00
2018-02-17 19:22:59 +01:00
-- if aux1 (aka key 'e'), stop recording
2017-07-18 03:02:15 +02:00
if ctrl.aux1 then
2018-02-15 19:55:10 +01:00
self.driver : set_detach ( )
minetest.chat_send_player ( self.driver : get_player_name ( ) , " Recorded stopped after " .. # self.path .. " points " )
temp [ self.driver : get_player_name ( ) ] = table.copy ( self.path )
self.object : remove ( )
return
2017-07-18 03:02:15 +02:00
end
2017-02-07 23:07:18 +01:00
2018-02-17 19:22:59 +01:00
-- if sneak, stop rotation
2016-01-30 08:56:04 +01:00
if ctrl.sneak then
2018-02-17 19:22:59 +01:00
params.rotate = false
params.rotate_speed = 0.0
2016-01-30 08:56:04 +01:00
end
2017-02-07 23:07:18 +01:00
2018-02-17 19:22:59 +01:00
-- if right, accelerate rotation to right
if ctrl.right and params.look_target then
params.rotate = true
params.rotate_speed = math.min ( ( params.rotate_speed or 0.0 ) + ( params.speed_step or 0.1 ) , 0.5 )
2018-02-17 22:37:27 +01:00
speed = 0
2018-02-17 19:22:59 +01:00
end
-- if left, accelerate rotation to left
if ctrl.left and params.look_target then
params.rotate = true
params.rotate_speed = math.max ( ( params.rotate_speed or 0.0 ) - ( params.speed_step or 0.1 ) , - 1 )
2018-02-17 22:37:27 +01:00
speed = 0
2018-02-17 19:22:59 +01:00
end
2017-03-21 17:48:56 +01:00
-- Set updated velocity
2018-02-18 02:37:51 +01:00
-- Normal Velocity mode
2017-05-05 09:04:35 +02:00
if self.mode == 0 then
self.object : setvelocity ( vector.multiply ( self.driver : get_look_dir ( ) , speed ) )
2018-02-18 02:37:51 +01:00
elseif self.mode > 1 then
-- Rotation Velocity mode
2018-02-17 19:22:59 +01:00
if params.rotate then
2018-02-18 02:37:51 +01:00
self.object : setvelocity (
vector.multiply (
{
x = self.object : get_velocity ( ) . x + math.cos ( self.driver : get_look_horizontal ( ) ) ,
y = speed ,
z = self.object : get_velocity ( ) . z + math.sin ( self.driver : get_look_horizontal ( ) )
} ,
{
x = params.rotate_speed ,
y = 1 ,
z = params.rotate_speed
}
) )
-- if mode 3 then look target up or down at the same time of the camera during rotation
if self.mode == 3 then
2018-02-18 03:12:48 +01:00
-- First step (old_pos = pos)
if not self.old_pos then self.old_pos = pos end
2018-02-18 02:37:51 +01:00
look_target.y = look_target.y + ( pos.y - self.old_pos . y )
player_params [ self.driver : get_player_name ( ) ] . look_target = look_target
-- memorize pos as old_pos for next steps
self.old_pos = pos
end
2018-02-17 19:22:59 +01:00
else
2018-02-18 02:37:51 +01:00
-- Looking target Velocity mode
2018-02-17 19:22:59 +01:00
self.object : setvelocity ( vector.multiply ( self.look_dir_init , speed ) )
end
2017-05-05 09:04:35 +02:00
end
2018-02-18 02:37:51 +01:00
-- memorize speed for next step
2018-02-17 22:37:27 +01:00
self.speed = speed
2017-02-07 23:07:18 +01:00
elseif self.mode == 1 then -- elseif playback mode
-- Get controls
2016-01-30 08:56:04 +01:00
local ctrl = self.driver : get_player_control ( )
2017-02-07 23:07:18 +01:00
2018-02-17 19:22:59 +01:00
-- if aux1 or no path, stop playback
if ctrl.aux1 or # self.path < 1 then
2016-01-30 08:56:04 +01:00
self.driver : set_detach ( )
minetest.chat_send_player ( self.driver : get_player_name ( ) , " Playback stopped " )
self.object : remove ( )
return
end
2017-02-07 23:07:18 +01:00
-- Update position
2016-01-30 08:56:04 +01:00
self.object : moveto ( self.path [ 1 ] . pos , true )
2017-03-21 17:48:56 +01:00
-- Update yaw/pitch
2018-02-15 19:55:10 +01:00
self.driver : set_look_horizontal ( self.path [ 1 ] . yaw )
self.driver : set_look_vertical ( self.path [ 1 ] . pitch )
2017-03-21 17:48:56 +01:00
-- Update velocity
2016-01-30 08:56:04 +01:00
self.object : setvelocity ( self.path [ 1 ] . velocity )
2017-03-21 17:48:56 +01:00
-- Remove path table
2016-01-30 08:56:04 +01:00
table.remove ( self.path , 1 )
end
end
2017-02-07 23:07:18 +01:00
-- Register entity
2016-01-30 08:56:04 +01:00
minetest.register_entity ( " camera:camera " , camera )
2017-02-07 23:07:18 +01:00
-- Register chatcommand
2017-01-20 02:55:19 +01:00
minetest.register_chatcommand ( " camera " , {
description = " Manipulate recording " ,
2017-02-07 23:07:18 +01:00
params = " <option> <value> " ,
2017-01-20 02:55:19 +01:00
func = function ( name , param )
2019-02-10 01:17:28 +01:00
if not minetest.check_player_privs ( name , { camera = true } ) then
return false , " You need camera privilege! "
end
2017-02-07 23:07:18 +01:00
local player = minetest.get_player_by_name ( name )
local param1 , param2 = param : split ( " " ) [ 1 ] , param : split ( " " ) [ 2 ]
2017-02-07 16:32:06 +01:00
2018-02-17 19:22:59 +01:00
-- if play, begin playback preperation
2017-02-07 16:32:06 +01:00
if param1 == " play " then
local function play ( path )
local object = minetest.add_entity ( player : getpos ( ) , " camera:camera " )
object : get_luaentity ( ) : init ( player , 1 )
2018-02-17 21:05:53 +01:00
object : setyaw ( player : get_look_horizontal ( ) )
2017-02-07 16:32:06 +01:00
player : set_attach ( object , " " , { x = 5 , y = 10 , z = 0 } , { x = 0 , y = 0 , z = 0 } )
object : get_luaentity ( ) . path = path
end
2017-01-20 02:55:19 +01:00
2017-02-07 16:32:06 +01:00
-- Check for param2 (recording name)
if param2 and param2 ~= " " then
2017-03-21 17:48:56 +01:00
-- if recording exists, start
2017-02-07 16:32:06 +01:00
if recordings [ name ] [ param2 ] then
play ( table.copy ( recordings [ name ] [ param2 ] ) )
2017-02-07 23:07:18 +01:00
else -- else, return error
2017-02-07 16:32:06 +01:00
return false , " Invalid recording " .. param2 .. " . Use /camera list to list recordings. "
end
2017-02-07 23:07:18 +01:00
else -- else, check temp for a recording path
2017-02-07 16:32:06 +01:00
if temp [ name ] then
play ( table.copy ( temp [ name ] ) )
else
return false , " No recordings could be found "
end
2017-01-20 02:55:19 +01:00
end
2017-02-07 16:32:06 +01:00
2017-01-20 02:55:19 +01:00
return true , " Playback started "
2017-02-07 23:07:18 +01:00
elseif param1 == " save " then -- elseif save, prepare to save path
2017-03-21 17:48:56 +01:00
-- if no table for player in recordings, initialize
2017-02-07 16:32:06 +01:00
if not recordings [ name ] then
recordings [ name ] = { }
end
2017-03-21 17:48:56 +01:00
-- if param2 is not blank, save
2017-02-07 16:32:06 +01:00
if param2 and param2 ~= " " then
recordings [ name ] [ param2 ] = temp [ name ]
2017-03-21 17:48:56 +01:00
return true , " Saved recording as " .. param2
2017-02-07 23:07:18 +01:00
else -- else, return error
2017-02-07 16:32:06 +01:00
return false , " Missing name to save recording under (/camera save <name>) "
end
2017-03-21 17:48:56 +01:00
elseif param1 == " list " then -- elseif list, list recordings
return true , " Recordings: " .. get_recordings ( name )
2017-05-05 09:04:35 +02:00
elseif param1 == " look " then
2017-04-30 18:33:03 +02:00
if param2 and param2 ~= " " then
if param2 == " nil " then
2018-02-17 19:22:59 +01:00
get_player_params ( name ) . look_target = nil
2017-05-05 09:04:35 +02:00
return true , " Looking target removed "
elseif param2 == " here " then
2018-02-17 19:22:59 +01:00
local player_params = get_player_params ( name )
2018-02-18 02:37:51 +01:00
if ( not player_params.mode ) then
player_params.mode = 2
end
2018-02-17 19:22:59 +01:00
player_params.look_target = player : getpos ( )
2017-05-05 09:04:35 +02:00
return true , " Looking target fixed "
2017-04-30 18:33:03 +02:00
else
2018-02-17 19:22:59 +01:00
local look_target = string.split ( param2 , " , " )
if # look_target == 3 then
local player_params = get_player_params ( name )
player_params.mode = 2
player_params.look_target =
{ x = tonumber ( look_target [ 1 ] ) ,
y = tonumber ( look_target [ 2 ] ) ,
z = tonumber ( look_target [ 3 ] )
}
2017-05-05 09:04:35 +02:00
return true , " Looking target fixed "
else
2018-02-17 19:22:59 +01:00
return false , " Looking target wrong format (/camera look <x,y,z>) "
2017-05-05 09:04:35 +02:00
end
2017-04-30 18:33:03 +02:00
end
else
2018-02-17 21:05:53 +01:00
return false , " Parameters of looking target are missing (/camera look <nil|here|x,y,z>) "
2017-04-30 18:33:03 +02:00
end
2017-05-03 22:27:25 +02:00
elseif param1 == " speed " then
2018-02-17 19:22:59 +01:00
if param2 and param ~= " " then
2017-05-03 22:27:25 +02:00
local speed = tonumber ( param2 )
if speed then
2018-02-17 19:22:59 +01:00
get_player_params ( name ) . speed_step = 1 / speed
return true , " Speed step fixed to " .. get_player_params ( name ) . speed_step
2017-05-03 22:27:25 +02:00
else
2018-02-17 19:22:59 +01:00
return false , " Invalid speed step (/camera speed <number>) "
2017-05-03 22:27:25 +02:00
end
2018-02-17 19:22:59 +01:00
else return false , " Missing speed step parameter (/camera speed <number>) "
2017-05-03 22:27:25 +02:00
end
2017-05-05 09:04:35 +02:00
elseif param1 == " mode " then
if param2 and param2 ~= " " then
local mode = tonumber ( param2 )
2018-02-18 03:12:48 +01:00
if mode == 0 or mode > 1 then
2018-02-17 19:22:59 +01:00
get_player_params ( name ) . mode = mode
2018-02-17 22:37:27 +01:00
if mode == 0 then
get_player_params ( name ) . look_target = nil
end
2017-05-05 09:04:35 +02:00
return true , " Record mode is set "
2018-02-18 03:12:48 +01:00
else return false , " Invalid mode (0: Velocity follow mouse (default), 2: Velocity locked to player first look direction, 3: Same as 2 but looking target can up/down when rotating) "
2017-05-05 09:04:35 +02:00
end
2018-02-18 03:12:48 +01:00
else return false , " Missing mod parameter (/camera mode <0|2|3>) "
2017-05-05 09:04:35 +02:00
end
2018-02-17 19:22:59 +01:00
elseif param1 == " help " then
local str = " Usage: /camera \n " ..
2018-02-18 02:37:51 +01:00
" Execute command to start recording. \n " ..
" While recording: \n " ..
2018-02-17 19:22:59 +01:00
" - use up/down to accelerate/decelerate \n " ..
2018-02-18 02:37:51 +01:00
" - when rotating (mode 2 or 3) the camera up or down on Y axis \n " ..
2018-02-17 19:22:59 +01:00
" - use jump to brake \n " ..
" - use aux1 to stop recording \n " ..
" - use left/right to rotate if looking target is set \n " ..
" - use crouch to stop rotating \n " ..
" Use /camera play to play back the last recording. While playing back: \n " ..
" - use aux1 to stop playing back \n " ..
" Use /camera play <name> to play a specific recording \n " ..
" Use /camera save <name> to save the last recording \n " ..
" - saved recordings exist through game restarts \n " ..
" Use /camera list to show all saved recording \n " ..
2018-02-18 02:37:51 +01:00
" Use /camera mode <0|2|3> to change the velocity behaviour \n " ..
2018-02-17 19:22:59 +01:00
" - 0: Velocity follow mouse (default), \n " ..
" - 2: Velocity locked to player's first look direction with released mouse \n " ..
2018-02-18 02:37:51 +01:00
" - 3: Same that 2 but if you up or down when rotating then looking target will up or down too \n " ..
2018-02-17 19:22:59 +01:00
" Use /camera look <nil|here|x,y,z> \n " ..
" - nil: remove looking target, \n " ..
" - here: set looking target to player position, \n " ..
" - x,y,z: Coords to look at \n " ..
" Use /camera speed <speed> \n " ..
" - 10 is default speed, \n " ..
" - > 10 decrease speed factor, \n " ..
" - < 10 increase speed factor "
return true , str
2017-02-07 23:07:18 +01:00
else -- else, begin recording
2017-01-20 02:55:19 +01:00
local object = minetest.add_entity ( player : getpos ( ) , " camera:camera " )
2018-02-17 19:22:59 +01:00
object : get_luaentity ( ) : init ( player , get_player_params ( name ) . mode )
2018-02-15 19:55:10 +01:00
object : setyaw ( player : get_look_horizontal ( ) )
2017-01-20 02:55:19 +01:00
player : set_attach ( object , " " , { x = 0 , y = 10 , z = 0 } , { x = 0 , y = 0 , z = 0 } )
return true , " Recording started "
2016-01-30 08:56:04 +01:00
end
end ,
} )
2018-12-24 03:33:15 +01:00
minetest.log ( " action " , " [camera] loaded. " )