Buckaroo Banzai 6020a94ccf
recording and playback support (#3)
* recording and playback support

* wip

* wip

---------

Co-authored-by: BuckarooBanzay <BuckarooBanzay@users.noreply.github.com>
2024-03-07 19:41:01 +01:00

189 lines
5.5 KiB
Lua

-- current state of recording
local state = false
-- current recording entry
local recording
-- current recording origin
local origin
local function reset_recording()
recording = {
entries = {}
}
end
reset_recording()
minetest.register_chatcommand("pnp_record_save", {
params = "[filename]",
description = "save the current recording to a file in the world-directory",
func = function(_, param)
local json = minetest.write_json(recording)
local filename = minetest.get_worldpath() .. "/" .. param .. ".json"
local f = io.open(filename, "w")
f:write(json)
f:close()
return true, "saved " .. #recording.entries .. " entries to '" .. filename .. "'"
end
})
minetest.register_chatcommand("pnp_record_load", {
params = "[filename]",
description = "loads a recording from a file in the world-directory",
func = function(_, param)
local filename = minetest.get_worldpath() .. "/" .. param .. ".json"
local f = io.open(filename, "r")
if not f then
return false, "file not found: '" .. filename .. "'"
end
recording = minetest.parse_json(f:read("*all"))
if not recording then
reset_recording()
return false, "could not parse file '" .. filename .. "'"
end
return true, "read " .. #recording.entries .. " entries from '" .. filename .. "'"
end
})
minetest.register_chatcommand("pnp_record", {
params = "[origin|start|info|stop|reset|play]",
description = "manages the recording state or plays the current recording",
func = function(name, param)
if param == "origin" then
if state then
return false, "origin can't be set while the recording is active"
end
-- set origin to current player pos
local player = minetest.get_player_by_name(name)
if not player then
return false, "player not found"
end
origin = vector.round(player:get_pos())
return true, "origin set to: " .. minetest.pos_to_string(origin)
end
if not origin then
return false, "origin not set, please use /pnp_record_origin first"
end
if param == "start" then
state = true
return true, "recording started"
elseif param == "pause" then
state = false
return true, "recording paused"
elseif param == "reset" then
reset_recording()
return true, "recording reset"
elseif param == "play" then
return pick_and_place.start_playback(name, origin, recording)
else
local msg = "recording state: "
if state then
msg = msg .. "running"
else
msg = msg .. "stopped/paused"
end
msg = msg .. ", entries: " .. #recording.entries
msg = msg .. ", origin: "
if origin then
msg = msg .. minetest.pos_to_string(origin)
else
msg = msg .. "<not set>"
end
if recording.min_pos then
msg = msg .. ", min_pos: " .. minetest.pos_to_string(recording.min_pos)
end
if recording.max_pos then
msg = msg .. ", max_pos: " .. minetest.pos_to_string(recording.max_pos)
end
return true, msg
end
end
})
local function track_min_max_pos(pos)
if not recording.min_pos then
recording.min_pos = vector.copy(pos)
elseif pos.x < recording.min_pos.x then
recording.min_pos.x = pos.x
elseif pos.y < recording.min_pos.y then
recording.min_pos.y = pos.y
elseif pos.z < recording.min_pos.z then
recording.min_pos.z = pos.z
end
if not recording.max_pos then
recording.max_pos = vector.copy(pos)
elseif pos.x > recording.max_pos.x then
recording.max_pos.x = pos.x
elseif pos.y > recording.max_pos.y then
recording.max_pos.y = pos.y
elseif pos.z > recording.max_pos.z then
recording.max_pos.z = pos.z
end
end
function pick_and_place.record_removal(pos1, pos2)
if not state or not origin then
return
end
if #recording.entries == 0 then
return
end
local rel_pos1 = vector.subtract(pos1, origin)
local rel_pos2 = vector.subtract(pos2, origin)
track_min_max_pos(rel_pos1)
track_min_max_pos(rel_pos2)
-- search and remove exact pos1/2 matches
local entry_removed = false
for i, entry in ipairs(recording.entries) do
if vector.equals(entry.pos1, rel_pos1) and vector.equals(entry.pos2, rel_pos2) then
-- remove matching entry
table.remove(recording.entries, i)
entry_removed = true
break
end
end
if not entry_removed then
-- non-aligned removal, just record
table.insert(recording.entries, {
type = "remove",
pos1 = rel_pos1,
pos2 = rel_pos2
})
end
end
function pick_and_place.record_placement(pos1, pos2, rotation, name)
if not state or not origin then
return
end
local rel_pos1 = vector.subtract(pos1, origin)
local rel_pos2 = vector.subtract(pos2, origin)
track_min_max_pos(rel_pos1)
track_min_max_pos(rel_pos2)
table.insert(recording.entries, {
type = "place",
pos1 = rel_pos1,
pos2 = rel_pos2,
rotation = rotation,
name = name
})
end