--[[ Copyright 2016-2017 - Auke Kok Copyright 2017 - Elijah Duffy Copyright 2017 - sys4 License: - Code: MIT - Models and textures: CC-BY-SA-3.0 Usage: /camera Execute command to start recording. While recording: - use up/down to accelerate/decelerate - use jump to brake - use aux1 to stop recording - use left/right to rotate if looking target is set - use crouch to stop rotating Use /camera play to play back the last recording. While playing back: - use crouch to stop playing back Use /camera play to play a specific recording Use /camera save to save the last recording - saved recordings exist through game restarts Use /camera list to show all saved recording Use /camera mode <0|2> to change the velocity behaviour - 0: Velocity follow mouse (default), - 2: Velocity locked to player's first look direction with released mouse Use /camera look - nil: remove looking target, - here: set looking target to player position, - x,y,z: Coords to look at Use /camera speed - 10 is default speed, - > 10 decrease speed factor, - < 10 increase speed factor --]] local recordings = {} -- [function] Load recordings local path = minetest.get_worldpath() local function load() 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 end -- Call load load() -- [function] Save recordings function save() io.open(path.."/recordings.txt", "w"):write(minetest.serialize(recordings)) end -- [function] Get recording list per-player for chat function get_recordings(name) 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 end -- [event] On shutdown save recordings minetest.register_on_shutdown(save) -- Table for storing unsaved temporary recordings local temp = {} -- Camera definition local camera = { description = "Camera", visual = "wielditem", textures = {}, physical = false, is_visible = false, collide_with_objects = false, collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, physical = false, visual = "cube", 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 = {} end, } local player_look_dir = nil -- player look direction when starting record local target_look_position = nil -- looking target local speed_factor = 0.1 -- default speed factor local rec_mode = 0 -- record mode local rotate = false local rotate_speed = 0 -- [event] On step function camera:on_step(dtime) -- if not driver, remove object if not self.driver then self.object:remove() return end local pos = self.object:getpos() local vel = self.object:getvelocity() local dir = self.driver:get_look_dir() local cos_yaw, sin_yaw -- if record mode if self.mode == 0 or self.mode == 2 then -- Calculate pitch and yaw if target_look_position defined if target_look_position then local vec_pos = vector.subtract(target_look_position, pos) -- Pitch local opp = vec_pos.y local adj = vec_pos.x if math.abs(vec_pos.z) > math.abs(vec_pos.x) then adj = vec_pos.z end self.driver:set_look_vertical(-math.atan2(opp, math.abs(adj))) -- Yaw opp = vec_pos.x adj = vec_pos.z local yaw = -math.atan2(opp,adj) self.driver:set_look_horizontal(yaw) -- Make velocity to rotate camera if rotate then cos_yaw = math.cos(yaw) sin_yaw = math.sin(yaw) else cos_yaw, sin_yaw = 0, 0 end end -- Update path self.path[#self.path + 1] = { pos = pos, velocity = vel, pitch = self.driver:get_look_vertical(), yaw = self.driver:get_look_horizontal() } -- Modify yaw and pitch to match driver (player) self.object:set_look_vertical(self.driver:get_look_vertical()) self.object:set_look_horizontal(self.driver:get_look_horizontal()) -- Get controls local ctrl = self.driver:get_player_control() -- Initialize speed local speed = vector.distance(vector.new(), vel) -- if up, accelerate forward if ctrl.up then speed = math.min(speed + speed_factor, 20) end -- if down, accelerate backward if ctrl.down then speed = math.max(speed - speed_factor, -20) end if ctrl.right and target_look_position then rotate = true rotate_speed = math.min(rotate_speed + speed_factor, 0.5) end if ctrl.left and target_look_position then rotate = true rotate_speed = math.max(rotate_speed - speed_factor, -1) end -- if aux1 (aka key 'e') then stop recording if ctrl.aux1 then 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 end -- if jump, brake if ctrl.jump then speed = math.max(speed * 0.5, 0.0) rotate_speed = math.max(speed * 0.5, 0.0) end -- if sneak, stop rotating if ctrl.sneak then rotate = false rotate_speed = 0 end -- Set updated velocity if self.mode == 0 then self.object:setvelocity(vector.multiply(self.driver:get_look_dir(), speed)) elseif self.mode == 2 then if rotate then self.object:setvelocity( vector.multiply( { x = self.object:get_velocity().x+cos_yaw, y = 0, z = self.object:get_velocity().z+sin_yaw } , rotate_speed)) else self.object:setvelocity(vector.multiply(player_look_dir, speed)) end end elseif self.mode == 1 then -- elseif playback mode -- Get controls local ctrl = self.driver:get_player_control() -- if sneak or no path, stop playback if ctrl.sneak or #self.path < 1 then self.driver:set_detach() minetest.chat_send_player(self.driver:get_player_name(), "Playback stopped") self.object:remove() return end -- Update position self.object:moveto(self.path[1].pos, true) -- Update yaw/pitch self.driver:set_look_horizontal(self.path[1].yaw) self.driver:set_look_vertical(self.path[1].pitch) -- Update velocity self.object:setvelocity(self.path[1].velocity) -- Remove path table table.remove(self.path, 1) end end -- Register entity minetest.register_entity("camera:camera", camera) -- Register chatcommand minetest.register_chatcommand("camera", { description = "Manipulate recording", params = "