2021-02-17 15:04:26 -08:00
|
|
|
-- Privilege
|
|
|
|
minetest.register_privilege("terraform", "Ability to use terraform tools")
|
|
|
|
|
|
|
|
local function privileged(player, f, verbose)
|
2021-04-25 02:07:08 -07:00
|
|
|
if player ~= nil and player:get_player_name() ~= "" then
|
2021-04-06 13:18:46 -07:00
|
|
|
if minetest.check_player_privs(player, "terraform") then
|
|
|
|
return f()
|
|
|
|
elseif verbose then
|
|
|
|
minetest.chat_send_player(player:get_player_name(), "You need terraform privilege to perform the action")
|
|
|
|
end
|
2021-02-17 15:04:26 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-04-25 02:07:08 -07:00
|
|
|
-- Settings
|
|
|
|
local settings = {
|
|
|
|
undo_history_depth = minetest.settings:get("terraform.undo_history_depth") and tonumber(minetest.settings:get("terraform.undo_history_depth")) or 100,
|
|
|
|
undo_for_dig_place = minetest.settings:get("terraform.undo_for_dig_place") == "true",
|
|
|
|
}
|
|
|
|
|
2021-01-24 16:09:18 -08:00
|
|
|
-- In-memory history/undo engine
|
|
|
|
local history = {
|
2021-02-14 08:50:19 -08:00
|
|
|
_lists = {},
|
|
|
|
|
|
|
|
-- get list of history entries
|
|
|
|
get_list = function(self, name)
|
|
|
|
self._lists[name] = self._lists[name] or {}
|
|
|
|
return self._lists[name]
|
|
|
|
end,
|
|
|
|
|
|
|
|
-- capture a cuboid in space using voxel manipulator
|
|
|
|
capture = function(self, player, data, va, minp, maxp)
|
|
|
|
local capture = {}
|
|
|
|
for i in va:iter(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z) do
|
|
|
|
table.insert(capture,data[i])
|
|
|
|
end
|
|
|
|
local op = {minp = minp, maxp = maxp, data = capture}
|
2021-04-25 02:07:08 -07:00
|
|
|
local history = self:get_list(player:get_player_name())
|
|
|
|
table.insert(history, op)
|
|
|
|
while #history > settings.undo_history_depth do
|
|
|
|
table.remove(history, 1)
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
end,
|
|
|
|
|
|
|
|
-- restore state of the world map from history
|
|
|
|
undo = function(self, player)
|
|
|
|
local op = table.remove(self:get_list(player:get_player_name()))
|
|
|
|
if not op then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local vm = minetest.get_voxel_manip()
|
|
|
|
local minv,maxv = vm:read_from_map(op.minp, op.maxp)
|
|
|
|
local va = VoxelArea:new({MinEdge = minv, MaxEdge = maxv})
|
|
|
|
local si = 1
|
|
|
|
local data = vm:get_data()
|
|
|
|
for i in va:iter(op.minp.x, op.minp.y, op.minp.z, op.maxp.x, op.maxp.y, op.maxp.z) do
|
|
|
|
data[i] = op.data[si]
|
|
|
|
si = si + 1
|
|
|
|
end
|
|
|
|
vm:set_data(data)
|
|
|
|
vm:write_to_map(false)
|
2021-02-14 13:16:17 -08:00
|
|
|
end,
|
|
|
|
forget = function(self, player)
|
|
|
|
self._lists[player:get_player_name()] = nil
|
|
|
|
end
|
2021-01-24 16:09:18 -08:00
|
|
|
}
|
|
|
|
|
2021-04-25 02:07:08 -07:00
|
|
|
if settings.undo_for_dig_place then
|
|
|
|
minetest.register_on_dignode(function(pos,oldnode,player)
|
|
|
|
privileged(player, function()
|
|
|
|
history:capture(player, {minetest.get_content_id(oldnode.name)}, VoxelArea:new({MinEdge=pos,MaxEdge=pos}), pos, pos)
|
|
|
|
end)
|
2021-02-17 15:04:26 -08:00
|
|
|
end)
|
2021-04-25 02:07:08 -07:00
|
|
|
minetest.register_on_placenode(function(pos,newnode,player)
|
|
|
|
privileged(player, function()
|
|
|
|
history:capture(player, {minetest.CONTENT_AIR}, VoxelArea:new({MinEdge=pos,MaxEdge=pos}), pos, pos)
|
|
|
|
end)
|
2021-02-17 15:04:26 -08:00
|
|
|
end)
|
2021-04-25 02:07:08 -07:00
|
|
|
end
|
|
|
|
|
2021-02-14 13:16:17 -08:00
|
|
|
minetest.register_on_leaveplayer(function(player)
|
|
|
|
history:forget(player)
|
|
|
|
end)
|
2021-02-09 00:51:00 -08:00
|
|
|
|
2021-04-07 02:38:33 -07:00
|
|
|
-- Quirks for backward compatibility
|
2022-05-09 12:44:32 -07:00
|
|
|
local quirks = {
|
2021-04-07 02:38:33 -07:00
|
|
|
is_53_plus = minetest.features.object_step_has_moveresult
|
|
|
|
}
|
|
|
|
|
2022-05-09 12:44:32 -07:00
|
|
|
-- Per-player behavior settings and flags
|
|
|
|
local player_settings = {
|
|
|
|
flags = {
|
|
|
|
undo_on_aux1 = { id = "uaux1", default = true }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function player_settings.get_flag(player, name)
|
|
|
|
return player:get_meta():get_int("terraform:"..player_settings.flags[name].id, player_settings.flags[name].default and 1 or 0 ) == 1
|
|
|
|
end
|
|
|
|
function player_settings.set_flag(player, name, value)
|
|
|
|
return player:get_meta():set_int("terraform:"..player_settings.flags[name].id, value and 1 or 0 )
|
|
|
|
end
|
|
|
|
function player_settings.toggle_flag(player, name)
|
|
|
|
local new_value = not player_settings.get_flag(player, name)
|
|
|
|
player_settings.set_flag(player, name, new_value)
|
|
|
|
return new_value
|
|
|
|
end
|
|
|
|
|
|
|
|
minetest.register_chatcommand("tf", {
|
|
|
|
params = "toggle <flag>",
|
|
|
|
description = "Toggle a behavior flag.\n"..
|
|
|
|
"Supported flags:\n"..
|
|
|
|
" undo_on_aux1 - enable undo when using a tool with aux1",
|
|
|
|
privs = { terraform = true },
|
|
|
|
func = function(name, param)
|
|
|
|
local args = {}
|
|
|
|
for arg in param:gmatch("[^ ]+") do
|
|
|
|
table.insert(args, arg)
|
|
|
|
end
|
|
|
|
|
|
|
|
if args[1] ~= "toggle" then return false end
|
|
|
|
if player_settings.flags[args[2]] == nil then return false, "Unrecognized flag "..args[2] end
|
|
|
|
|
|
|
|
local new_value = player_settings.toggle_flag(minetest.get_player_by_name(name), args[2])
|
|
|
|
return true, new_value and "Enabled" or "Disabled"
|
|
|
|
end
|
|
|
|
})
|
|
|
|
|
2021-01-24 16:09:18 -08:00
|
|
|
-- public module API
|
|
|
|
terraform = {
|
2021-02-14 08:50:19 -08:00
|
|
|
_tools = {},
|
|
|
|
_history = history,
|
|
|
|
|
2022-05-09 11:16:43 -07:00
|
|
|
-- Per-player state of the tool configuration form
|
|
|
|
_latest_form = {},
|
|
|
|
|
2021-04-07 03:15:41 -07:00
|
|
|
-- Per-player flags for skipping light updates
|
|
|
|
skip_light = {},
|
|
|
|
|
2021-02-14 08:50:19 -08:00
|
|
|
-- register a terraform tool
|
|
|
|
register_tool = function(self, name, spec)
|
|
|
|
spec.tool_name = name
|
|
|
|
self._tools[spec.tool_name] = spec
|
2021-02-14 13:47:51 -08:00
|
|
|
if spec.init then
|
|
|
|
spec:init()
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
minetest.register_tool("terraform:"..spec.tool_name, {
|
|
|
|
description = spec.description,
|
|
|
|
short_description = spec.short_description,
|
|
|
|
inventory_image = spec.inventory_image,
|
|
|
|
wield_scale = {x=1,y=1,z=1},
|
|
|
|
stack_max = 1,
|
|
|
|
range = spec.range or 128.0,
|
|
|
|
liquids_pointable = true,
|
|
|
|
node_dig_prediction = "",
|
|
|
|
on_use = function(itemstack, player, target)
|
2021-02-17 15:04:26 -08:00
|
|
|
privileged(player, function()
|
|
|
|
terraform:show_config(player, spec.tool_name, itemstack)
|
|
|
|
end, true)
|
2021-02-14 08:50:19 -08:00
|
|
|
end,
|
|
|
|
on_secondary_use = function(itemstack, player, target)
|
2021-02-17 15:04:26 -08:00
|
|
|
privileged(player, function()
|
|
|
|
terraform:show_config(player, spec.tool_name, itemstack)
|
|
|
|
end, true)
|
2021-02-14 08:50:19 -08:00
|
|
|
end,
|
|
|
|
on_place = function(itemstack, player, target)
|
2021-02-17 15:04:26 -08:00
|
|
|
return privileged(player, function()
|
2022-05-09 12:44:32 -07:00
|
|
|
if player:get_player_control().aux1 and player_settings.get_flag(player, "undo_on_aux1") then
|
2021-02-17 15:04:26 -08:00
|
|
|
history:undo(player)
|
|
|
|
else
|
|
|
|
spec:execute(player, target, itemstack:get_meta())
|
|
|
|
end
|
|
|
|
return itemstack
|
|
|
|
end, true)
|
2021-02-14 08:50:19 -08:00
|
|
|
end,
|
|
|
|
})
|
|
|
|
end,
|
|
|
|
|
|
|
|
-- show configuration form for the specific tool
|
|
|
|
show_config = function(self, player, tool_name)
|
2021-04-06 16:19:38 -07:00
|
|
|
if self.blocked or not self._tools[tool_name].render_config then
|
2021-02-14 08:50:19 -08:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local itemstack = player:get_wielded_item()
|
2022-05-09 11:16:43 -07:00
|
|
|
local form = { id = "terraform:props:"..tool_name..math.random(1,100000), tool_name = tool_name}
|
|
|
|
self._latest_form[player:get_player_name()] = form
|
2021-02-14 08:50:19 -08:00
|
|
|
local formspec = self._tools[tool_name]:render_config(player, itemstack:get_meta())
|
2021-04-07 02:38:33 -07:00
|
|
|
if quirks.is_53_plus then
|
2022-05-09 11:16:43 -07:00
|
|
|
minetest.show_formspec(player:get_player_name(), form.id, formspec)
|
2021-04-07 02:38:33 -07:00
|
|
|
else
|
|
|
|
self.blocked = true
|
|
|
|
minetest.after(0.25, function()
|
2022-05-09 11:16:43 -07:00
|
|
|
minetest.show_formspec(player:get_player_name(), form.id, formspec)
|
2021-04-07 02:38:33 -07:00
|
|
|
self.blocked = false
|
|
|
|
end)
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
end,
|
|
|
|
|
2022-05-09 11:16:43 -07:00
|
|
|
forget = function(self, player)
|
|
|
|
self._latest_form[player:get_player_name()] = nil
|
|
|
|
minetest.remove_detached_inventory("terraform."..player:get_player_name())
|
|
|
|
end,
|
|
|
|
|
2021-02-14 08:50:19 -08:00
|
|
|
get_inventory = function(player)
|
|
|
|
return minetest.get_inventory({type = "detached", name = "terraform."..player:get_player_name()})
|
|
|
|
end,
|
|
|
|
|
|
|
|
-- Helpers for storing inventory into settings
|
|
|
|
string_to_list = function(s,size)
|
|
|
|
-- Accept: a comma-separated list of content names and desired list size
|
|
|
|
-- Return: a table with item names, compatible with inventory lists
|
|
|
|
local result = {}
|
|
|
|
for part in s:gmatch("[^,]+") do
|
|
|
|
table.insert(result, part)
|
|
|
|
end
|
|
|
|
while #result < size do table.insert(result, "") end
|
|
|
|
return result
|
|
|
|
end,
|
|
|
|
list_to_string = function(list)
|
|
|
|
-- Accept: result of InvRef:get_list
|
|
|
|
-- Retrun: a comma-separated list of items
|
|
|
|
local result = ""
|
|
|
|
for k,v in pairs(list) do
|
2021-03-10 15:29:58 -08:00
|
|
|
if v.get_name ~= nil then v = v:to_string() end -- ItemStack to string
|
2021-02-14 08:50:19 -08:00
|
|
|
if v ~= "" then
|
|
|
|
if string.len(result) > 0 then
|
|
|
|
result = result..","
|
|
|
|
end
|
|
|
|
result = result..v
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
end
|
2021-01-24 16:09:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
-- Handle input from forms
|
|
|
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
2021-02-17 15:04:26 -08:00
|
|
|
privileged(player, function()
|
2022-05-09 11:16:43 -07:00
|
|
|
local form = terraform._latest_form[player:get_player_name()]
|
|
|
|
if form ~= nil and formname == form.id then
|
|
|
|
local tool_name = form.tool_name
|
2021-02-17 15:04:26 -08:00
|
|
|
local tool = terraform._tools[tool_name]
|
|
|
|
if not tool.config_input then
|
|
|
|
return
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
|
2021-02-17 15:04:26 -08:00
|
|
|
local itemstack = player:get_wielded_item()
|
|
|
|
local reload = tool:config_input(player, fields, itemstack:get_meta())
|
2021-02-14 08:50:19 -08:00
|
|
|
|
2021-02-17 15:04:26 -08:00
|
|
|
-- update tool description in the inventory
|
|
|
|
if tool.get_description then
|
|
|
|
itemstack:get_meta():set_string("description", tool:get_description(itemstack:get_meta()))
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
|
2021-02-17 15:04:26 -08:00
|
|
|
player:set_wielded_item(itemstack)
|
2021-02-14 08:50:19 -08:00
|
|
|
|
2021-02-17 15:04:26 -08:00
|
|
|
if fields.quit then
|
2022-05-09 11:16:43 -07:00
|
|
|
terraform._latest_form[player:get_player_name()] = nil
|
2021-02-17 15:04:26 -08:00
|
|
|
return
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
|
2021-02-17 15:04:26 -08:00
|
|
|
if reload then
|
|
|
|
terraform:show_config(player, tool_name, itemstack)
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
end
|
2021-02-17 15:04:26 -08:00
|
|
|
end)
|
2021-01-24 16:09:18 -08:00
|
|
|
end)
|
|
|
|
|
2021-01-27 16:02:36 -08:00
|
|
|
minetest.register_on_leaveplayer(function(player)
|
2022-05-09 11:16:43 -07:00
|
|
|
terraform:forget(player)
|
2021-01-27 16:02:36 -08:00
|
|
|
end)
|
|
|
|
|
|
|
|
-- Tools
|
|
|
|
|
|
|
|
-- Brush
|
2021-01-24 16:09:18 -08:00
|
|
|
--
|
2021-01-27 16:02:36 -08:00
|
|
|
terraform:register_tool("brush", {
|
2021-02-14 08:50:19 -08:00
|
|
|
description = "Brush\n\nPaints the world with broad strokes",
|
|
|
|
short_description = "Brush",
|
|
|
|
inventory_image = "terraform_tool_brush.png",
|
|
|
|
|
|
|
|
-- 16 logical tag colors
|
|
|
|
colors = { "red", "yellow", "lime", "aqua",
|
|
|
|
"darkred", "orange", "darkgreen", "mediumblue",
|
|
|
|
"violet", "wheat", "olive", "dodgerblue" },
|
|
|
|
|
2021-02-21 13:39:15 -08:00
|
|
|
-- Modifier names and labels
|
|
|
|
modifiers = {{n="surface",l="Surface"}, {n="scatter",l="Scatter"}, {n="decor",l="Decoration"}, {n="landslide",l="Landslide"}, {n="flat", l="Flat"}},
|
|
|
|
|
2021-02-14 08:50:19 -08:00
|
|
|
max_size = 15,
|
|
|
|
|
2021-02-14 13:47:51 -08:00
|
|
|
init = function(self)
|
|
|
|
for _,shape_fn in ipairs(self.shapes) do
|
|
|
|
self.shapes[shape_fn().name] = shape_fn
|
|
|
|
end
|
|
|
|
end,
|
2021-02-14 08:50:19 -08:00
|
|
|
render_config = function(self, player, settings)
|
|
|
|
local function selection(texture, selected)
|
|
|
|
if selected then return texture.."^terraform_selection.png" end
|
|
|
|
return texture
|
|
|
|
end
|
|
|
|
|
|
|
|
local inventory = minetest.create_detached_inventory("terraform."..player:get_player_name(), {
|
2021-03-10 15:29:58 -08:00
|
|
|
|
2021-02-14 08:50:19 -08:00
|
|
|
allow_move = function(inv,source,sindex,dest,dindex,count)
|
|
|
|
if source == "palette" and dest ~= "palette" then
|
2021-03-10 15:29:58 -08:00
|
|
|
local source_stack = inv:get_stack(source, sindex)
|
|
|
|
local dest_stack = inv:get_stack(dest,dindex)
|
|
|
|
|
|
|
|
source_stack:set_count(count)
|
|
|
|
|
|
|
|
if dest_stack:get_name() == source_stack:get_name() then
|
|
|
|
dest_stack:add_item(source_stack)
|
|
|
|
else
|
|
|
|
dest_stack = source_stack
|
|
|
|
end
|
|
|
|
inv:set_stack(dest,dindex,dest_stack)
|
|
|
|
inv:set_stack(source,sindex,source_stack:get_name().." "..(source_stack:get_definition().stack_max or minetest.settings["default_stack_max"]))
|
2021-02-14 08:50:19 -08:00
|
|
|
return 0
|
|
|
|
elseif dest == "palette" and source ~= "palette" then
|
2021-03-10 15:29:58 -08:00
|
|
|
local stack = inv:get_stack(source, sindex, "")
|
|
|
|
stack:take_item(count)
|
|
|
|
inv:set_stack(source, sindex, stack)
|
|
|
|
return 0
|
|
|
|
elseif source == "palette" and dest == "palette" then
|
2021-02-14 08:50:19 -08:00
|
|
|
return 0
|
|
|
|
end
|
|
|
|
return count
|
2021-03-10 15:29:58 -08:00
|
|
|
end,
|
|
|
|
allow_take = function(inv,list,index,stack)
|
|
|
|
if list == "palette" then
|
|
|
|
return -1
|
|
|
|
end
|
|
|
|
return 1
|
|
|
|
end,
|
2021-02-14 08:50:19 -08:00
|
|
|
})
|
|
|
|
|
|
|
|
local palette = {}
|
|
|
|
local count = 0
|
|
|
|
local pattern = settings:get_string("search_text")
|
|
|
|
local skip = 40 * (settings:get_int("search_page") or 0)
|
|
|
|
for k,v in pairs(minetest.registered_nodes) do
|
|
|
|
if not pattern or string.find(k, pattern) ~= nil then
|
|
|
|
if skip > 0 then
|
|
|
|
skip = skip - 1
|
|
|
|
else
|
2021-03-10 15:29:58 -08:00
|
|
|
table.insert(palette, k.." "..(ItemStack(k):get_definition().stack_max or minetest.settings["default_stack_max"]))
|
2021-02-14 08:50:19 -08:00
|
|
|
if #palette >= 40 then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
count = count + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
while #palette < 40 do table.insert(palette, "") end
|
|
|
|
|
|
|
|
local paint = terraform.string_to_list(settings:get_string("paint"), 10)
|
|
|
|
local mask = terraform.string_to_list(settings:get_string("mask"), 10)
|
|
|
|
|
|
|
|
inventory:set_list("palette", palette)
|
|
|
|
inventory:set_list("paint", paint)
|
|
|
|
inventory:set_list("mask", mask)
|
|
|
|
|
|
|
|
spec =
|
|
|
|
"size[17,12]"..
|
|
|
|
"position[0.5,0.45]"..
|
|
|
|
"anchor[0.5,0.5]"..
|
|
|
|
"no_prepend[]"..
|
2021-04-07 02:38:33 -07:00
|
|
|
"real_coordinates[true]"..
|
2021-02-14 08:50:19 -08:00
|
|
|
|
|
|
|
"button_exit[14.5,10.5;2,1;quit;Close]".. -- Close button !Remember to offset when form size changes
|
|
|
|
|
|
|
|
"container[0.5,0.5]".. -- shape
|
|
|
|
"label[0,0.5; Shape:]"
|
|
|
|
local pos = 0
|
2021-02-14 13:47:51 -08:00
|
|
|
for _,shape_fn in ipairs(self.shapes) do
|
|
|
|
local shape = shape_fn().name -- Construct shape and extract the name
|
2021-02-14 08:50:19 -08:00
|
|
|
local x = pos % 3
|
|
|
|
local y = math.floor(pos / 3) + 1
|
|
|
|
spec = spec.."image_button["..x..","..y..";1,1;"..selection("terraform_shape_"..shape..".png",settings:get_string("shape") == shape)..";shape_"..shape..";]"
|
2021-02-14 13:27:34 -08:00
|
|
|
spec = spec.."tooltip[shape_"..shape..";"..shape.."]"
|
2021-02-14 08:50:19 -08:00
|
|
|
pos = pos + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
spec = spec ..
|
|
|
|
"container_end[]"..
|
|
|
|
|
|
|
|
"container[0.5,4]".. -- size
|
|
|
|
"label[0,0.4; Size:]"..
|
|
|
|
"field[1,0;1,0.7;size;;"..(settings:get_int("size") or 3).."]"..
|
|
|
|
"field_close_on_enter[size;false]"..
|
|
|
|
"scrollbaroptions[min=0;max="..self.max_size..";smallstep=1;thumbsize=0;arrows=show]"..
|
|
|
|
"scrollbar[2,0;0.35,0.7;vertical;size_sb;"..(self.max_size - (settings:get_int("size") or 3)).."]"..
|
|
|
|
"container_end[]"..
|
|
|
|
|
2021-02-21 13:39:15 -08:00
|
|
|
"container[0.5, 5.5]" -- modifiers
|
|
|
|
pos = 0
|
|
|
|
for _,modifier in ipairs(self.modifiers) do
|
|
|
|
spec = spec ..
|
|
|
|
"checkbox[0,"..pos..";modifiers_"..modifier.n..";"..modifier.l..";"..(settings:get_int("modifiers_"..modifier.n) == 1 and "true" or "false").."]"
|
|
|
|
pos = pos + 0.5
|
|
|
|
end
|
|
|
|
spec = spec ..
|
2021-02-14 08:50:19 -08:00
|
|
|
"container_end[]"..
|
|
|
|
|
|
|
|
"container[4,0.5]".. -- creative
|
|
|
|
"label[0,0.5; Palette]"..
|
|
|
|
"label[4.75,0.5; Find nodes:]"..
|
|
|
|
"field[6.5,0.1;2,0.75;search_text;;"..minetest.formspec_escape(settings:get_string("search_text") or "").."]"..
|
|
|
|
"field_close_on_enter[search_text;false]"..
|
|
|
|
"button[8.5,0.1;2,0.75;search;Search]"..
|
|
|
|
"button[10.5,0.1;0.75,0.75;prev_page;<]"..
|
|
|
|
"button[11.25,0.1;0.75,0.75;next_page;>]"..
|
|
|
|
"list[detached:terraform."..player:get_player_name()..";palette;0,1;10,4]"..
|
|
|
|
"container_end[]"..
|
|
|
|
|
|
|
|
"container[4,6]".. -- paint
|
|
|
|
"label[0,0.5; Paint]"..
|
|
|
|
"list[detached:terraform."..player:get_player_name()..";paint;0,1;10,1]"..
|
|
|
|
"container_end[]"..
|
|
|
|
|
|
|
|
"container[4,8]".. -- Mask
|
|
|
|
"label[0,0.5; Mask]"..
|
|
|
|
"list[detached:terraform."..player:get_player_name()..";mask;0,1;10,1]"..
|
|
|
|
"container_end[]"
|
|
|
|
|
|
|
|
-- Color tags
|
|
|
|
spec = spec..
|
2021-02-14 13:33:51 -08:00
|
|
|
"container[0.5, 8]"..
|
2021-02-14 08:50:19 -08:00
|
|
|
"label[0,0.5; Color Tag]"
|
|
|
|
local count = 0
|
|
|
|
local size = 0.5
|
|
|
|
for _, color in ipairs(self.colors) do
|
|
|
|
local offset = size*(count % 4)
|
|
|
|
local line = 0.75 + size*math.floor(count / 4)
|
2021-04-07 02:38:33 -07:00
|
|
|
if quirks.is_53_plus then
|
|
|
|
local texture = "terraform_tool_brush.png^[multiply:"..color..""
|
|
|
|
spec = spec.."image_button["..offset..","..line..";"..size..","..size..";"..selection(texture,settings:get_string("color") == color)..";color_"..color..";]"
|
|
|
|
else
|
|
|
|
spec = spec.."item_image_button["..offset..","..line..";"..size..","..size..";"..minetest.formspec_escape(minetest.itemstring_with_color("terraform:brush", color))..";color_"..color..";]"
|
|
|
|
end
|
|
|
|
spec = spec.."tooltip[color_"..color..";"..color.."]"
|
2021-02-14 08:50:19 -08:00
|
|
|
count = count + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
spec = spec..
|
|
|
|
"container_end[]"..
|
|
|
|
""
|
|
|
|
|
|
|
|
return spec
|
|
|
|
end,
|
|
|
|
|
|
|
|
config_input = function(self, player, fields, settings)
|
|
|
|
local refresh = false
|
|
|
|
|
|
|
|
-- Shape
|
|
|
|
for shape,_ in pairs(self.shapes) do
|
|
|
|
if fields["shape_"..shape] ~= nil then
|
|
|
|
settings:set_string("shape", shape)
|
|
|
|
refresh = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Size
|
|
|
|
if fields.size_sb ~= nil and string.find(fields.size_sb, "CHG") then
|
|
|
|
local e = minetest.explode_scrollbar_event(fields.size_sb)
|
|
|
|
if e.type == "CHG" then
|
|
|
|
settings:set_int("size", math.min(math.max(self.max_size - tonumber(e.value), 0), self.max_size))
|
|
|
|
refresh = true
|
|
|
|
end
|
2021-02-14 13:52:08 -08:00
|
|
|
elseif fields.size ~= nil and tonumber(fields.size) ~= nil then
|
2021-02-14 08:50:19 -08:00
|
|
|
settings:set_int("size", math.min(math.max(tonumber(fields.size), 0), self.max_size))
|
|
|
|
end
|
|
|
|
|
2021-02-21 13:39:15 -08:00
|
|
|
-- Modifiers
|
|
|
|
for _,modifier in ipairs(self.modifiers) do
|
|
|
|
if fields["modifiers_"..modifier.n] ~= nil then
|
|
|
|
settings:set_int("modifiers_"..modifier.n, fields["modifiers_"..modifier.n] == "true" and 1 or 0)
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Search
|
|
|
|
if fields.search_text ~= nil then
|
|
|
|
settings:set_string("search_text", fields.search_text or "")
|
|
|
|
refresh = true
|
|
|
|
end
|
|
|
|
if fields.search ~= nil or (fields.search_text ~= nil and fields.key_enter_field == "search_text") then
|
|
|
|
settings:set_int("search_page", 0)
|
|
|
|
refresh = true
|
|
|
|
end
|
|
|
|
if fields.prev_page ~= nil then
|
|
|
|
settings:set_int("search_page", math.max(0, (settings:get_int("search_page") or 0) - 1))
|
|
|
|
refresh = true
|
|
|
|
end
|
|
|
|
if fields.next_page ~= nil then
|
|
|
|
settings:set_int("search_page", (settings:get_int("search_page") or 0) + 1)
|
|
|
|
refresh = true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Color Tags
|
|
|
|
for _,color in ipairs(self.colors) do
|
|
|
|
if fields["color_"..color] then
|
|
|
|
settings:set_string("color", color)
|
|
|
|
refresh = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local inv = terraform.get_inventory(player)
|
|
|
|
if inv ~= nil then
|
|
|
|
settings:set_string("paint", terraform.list_to_string(inv:get_list("paint")))
|
|
|
|
settings:set_string("mask", terraform.list_to_string(inv:get_list("mask")))
|
|
|
|
end
|
|
|
|
|
|
|
|
return refresh
|
|
|
|
end,
|
|
|
|
|
|
|
|
get_description = function(self, settings)
|
|
|
|
return "Terraform Brush ("..(settings:get_string("shape") or "sphere")..")\n"..
|
|
|
|
"size "..(settings:get_int("size") or 0).."\n"..
|
|
|
|
"paint "..(settings:get_string("paint")).."\n"..
|
|
|
|
"mask "..(settings:get_string("mask"))
|
|
|
|
end,
|
|
|
|
|
|
|
|
execute = function(self, player, target, settings)
|
|
|
|
|
|
|
|
-- Get position
|
|
|
|
local target_pos = minetest.get_pointed_thing_position(target)
|
|
|
|
if not target_pos then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Define size in 3d
|
|
|
|
local size = settings:get_int("size") or 3
|
|
|
|
local size_3d = vector.new(size, size, size)
|
2021-02-21 13:39:15 -08:00
|
|
|
if settings:get_int("modifiers_flat") == 1 then
|
|
|
|
size_3d = vector.new(size_3d.x, 0, size_3d.z)
|
|
|
|
end
|
|
|
|
|
2021-02-14 08:50:19 -08:00
|
|
|
|
|
|
|
-- Pick a shape
|
|
|
|
local shape_name = settings:get_string("shape") or "sphere"
|
|
|
|
if not self.shapes[shape_name] then shape_name = "sphere" end
|
|
|
|
local shape = self.shapes[shape_name]()
|
|
|
|
|
|
|
|
-- Define working area and load state
|
2021-02-21 13:39:15 -08:00
|
|
|
local minp, maxp = shape:get_bounds(player, target_pos, size_3d)
|
|
|
|
local minc, maxc = vector.new(minp), vector.new(maxp)
|
|
|
|
if settings:get_int("modifiers_landslide") == 1 then
|
|
|
|
minc.y = minc.y - 100
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
local v = minetest.get_voxel_manip()
|
2021-02-21 13:39:15 -08:00
|
|
|
local minv, maxv = v:read_from_map(minc, maxc)
|
2021-02-14 08:50:19 -08:00
|
|
|
local a = VoxelArea:new({MinEdge = minv, MaxEdge = maxv })
|
|
|
|
|
2021-02-21 13:39:15 -08:00
|
|
|
-- Get data
|
2021-02-14 08:50:19 -08:00
|
|
|
local data = v:get_data()
|
2021-02-21 13:39:15 -08:00
|
|
|
|
|
|
|
-- Capture history. If landslide enabled, find the lowest Y with air
|
|
|
|
minc.y = minp.y
|
|
|
|
if settings:get_int("modifiers_landslide") == 1 then
|
|
|
|
for x = minp.x, maxp.x do
|
|
|
|
for z = minp.z, maxp.z do
|
|
|
|
for y = target_pos.y, target_pos.y - 100, -1 do
|
|
|
|
if data[a:index(x, y, z)] ~= minetest.CONTENT_AIR then
|
|
|
|
if y + 1 < minc.y then
|
|
|
|
minc.y = y + 1
|
|
|
|
end
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
history:capture(player, data, a, minc, maxc)
|
2021-02-14 08:50:19 -08:00
|
|
|
|
|
|
|
-- Set up context
|
|
|
|
local ctx = {
|
|
|
|
size_3d = size_3d,
|
|
|
|
player = player
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Prepare Paint
|
|
|
|
local paint = {}
|
2021-03-10 15:29:58 -08:00
|
|
|
local boundary = 0
|
2021-02-14 08:50:19 -08:00
|
|
|
for i,v in ipairs(terraform.string_to_list(settings:get_string("paint"), 10)) do
|
|
|
|
if v ~= "" then
|
2021-03-10 15:29:58 -08:00
|
|
|
local stack = ItemStack(v)
|
|
|
|
boundary = boundary + stack:get_count()
|
|
|
|
table.insert(paint, { id = minetest.get_content_id(stack:get_name()), boundary = boundary})
|
2021-02-14 08:50:19 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
if #paint == 0 then
|
2021-03-10 15:29:58 -08:00
|
|
|
table.insert(paint, { id = minetest.CONTENT_AIR, boundary = 1 })
|
2021-02-14 08:50:19 -08:00
|
|
|
end
|
|
|
|
|
2021-02-14 13:04:03 -08:00
|
|
|
ctx.paint = paint
|
2021-02-14 08:50:19 -08:00
|
|
|
ctx.get_paint = function()
|
2021-03-10 15:29:58 -08:00
|
|
|
local sample = math.random(1, paint[#paint].boundary)
|
|
|
|
for _,v in ipairs(paint) do
|
|
|
|
if sample < v.boundary then
|
|
|
|
return v.id
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return paint[#paint].id
|
2021-02-14 08:50:19 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Prepare Mask
|
|
|
|
local mask = {}
|
|
|
|
for i,v in ipairs(terraform.string_to_list(settings:get_string("mask"), 10)) do
|
|
|
|
if v ~= "" then
|
2021-03-10 15:29:58 -08:00
|
|
|
table.insert(mask, minetest.get_content_id(ItemStack(v):get_name()))
|
2021-02-14 08:50:19 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-02-14 13:04:03 -08:00
|
|
|
ctx.mask = mask
|
2021-02-14 08:50:19 -08:00
|
|
|
ctx.in_mask = function(cid)
|
|
|
|
if #mask == 0 then return true end
|
|
|
|
for i,v in ipairs(mask) do if v == cid then return true end end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2021-02-14 13:07:45 -08:00
|
|
|
-- Prepare modifiers
|
|
|
|
local modifiers = {}
|
2021-02-21 13:39:15 -08:00
|
|
|
if settings:get_int("modifiers_landslide") == 1 then
|
|
|
|
table.insert(modifiers, function(i)
|
2021-03-01 01:14:07 -08:00
|
|
|
while data[i - a.ystride] == minetest.CONTENT_AIR and a:position(i).y > minc.y do
|
2021-02-21 13:39:15 -08:00
|
|
|
i = i - a.ystride
|
|
|
|
end
|
2021-03-01 01:14:07 -08:00
|
|
|
return i
|
2021-02-21 13:39:15 -08:00
|
|
|
end)
|
|
|
|
end
|
2021-02-14 08:50:19 -08:00
|
|
|
if settings:get_int("modifiers_surface") == 1 then
|
2021-02-14 13:07:45 -08:00
|
|
|
table.insert(modifiers, function(i)
|
2021-02-14 08:50:19 -08:00
|
|
|
if data[i] == minetest.CONTENT_AIR then return nil end
|
|
|
|
if a:position(i).y < maxp.y and data[i+a.ystride] == minetest.CONTENT_AIR then return i end
|
|
|
|
return nil
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
if settings:get_int("modifiers_decor") == 1 then
|
2021-02-14 13:07:45 -08:00
|
|
|
table.insert(modifiers, function(i)
|
2021-02-14 08:50:19 -08:00
|
|
|
if data[i] == minetest.CONTENT_AIR then return nil end
|
|
|
|
if a:position(i).y < maxp.y and data[i+a.ystride] == minetest.CONTENT_AIR then return i+a.ystride end
|
|
|
|
return nil
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
if settings:get_int("modifiers_scatter") == 1 then
|
2021-02-14 13:07:45 -08:00
|
|
|
table.insert(modifiers, function(i)
|
2021-02-14 08:50:19 -08:00
|
|
|
return math.random(1,1000) <= 50 and i or nil
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
ctx.draw = function(i, paint)
|
|
|
|
if not ctx.in_mask(data[i]) then return end -- if not in mask, skip painting
|
2021-02-14 13:07:45 -08:00
|
|
|
for _,f in ipairs(modifiers) do
|
2021-02-14 08:50:19 -08:00
|
|
|
i = f(i)
|
|
|
|
if not i then return end -- if i is nil, skip painting
|
|
|
|
end
|
|
|
|
data[i] = paint or ctx.get_paint()
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Paint
|
|
|
|
shape:paint(data, a, target_pos, minp, maxp, ctx)
|
|
|
|
|
|
|
|
-- Save back to map, no light information
|
|
|
|
v:set_data(data)
|
2021-04-07 03:15:41 -07:00
|
|
|
v:write_to_map(not terraform.skip_light[player:get_player_name()])
|
2021-02-14 08:50:19 -08:00
|
|
|
end,
|
|
|
|
|
|
|
|
|
|
|
|
-- Definition of shapes
|
|
|
|
shapes = {
|
2021-02-14 13:47:51 -08:00
|
|
|
function()
|
2021-02-14 08:50:19 -08:00
|
|
|
return {
|
2021-02-14 13:47:51 -08:00
|
|
|
name = "cube",
|
2021-02-14 08:50:19 -08:00
|
|
|
get_bounds = function(self, player, target_pos, size_3d)
|
2021-02-14 08:55:55 -08:00
|
|
|
return vector.subtract(target_pos, size_3d), vector.add(target_pos, size_3d)
|
2021-02-14 08:50:19 -08:00
|
|
|
end,
|
|
|
|
paint = function(self, data, a, target_pos, minp, maxp, ctx)
|
|
|
|
for i in a:iter(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z) do
|
|
|
|
ctx.draw(i)
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
end,
|
2021-02-14 13:47:51 -08:00
|
|
|
function()
|
2021-02-14 08:50:19 -08:00
|
|
|
return {
|
2021-02-14 13:47:51 -08:00
|
|
|
name = "sphere",
|
2021-02-14 08:50:19 -08:00
|
|
|
get_bounds = function(self, player, target_pos, size_3d)
|
|
|
|
return vector.subtract(target_pos, size_3d), vector.add(target_pos, size_3d)
|
|
|
|
end,
|
|
|
|
paint = function(self, data, a, target_pos, minp, maxp, ctx)
|
|
|
|
for i in a:iter(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z) do
|
|
|
|
local ip = a:position(i)
|
|
|
|
local epsilon = 0.3
|
|
|
|
local delta = { x = ip.x - target_pos.x, y = ip.y - target_pos.y, z = ip.z - target_pos.z }
|
|
|
|
delta = { x = delta.x / (ctx.size_3d.x + epsilon), y = delta.y / (ctx.size_3d.y + epsilon), z = delta.z / (ctx.size_3d.z + epsilon) }
|
|
|
|
delta = { x = delta.x^2, y = delta.y^2, z = delta.z^2 }
|
|
|
|
|
|
|
|
if 1 > delta.x + delta.y + delta.z then
|
|
|
|
ctx.draw(i)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
end,
|
2021-02-14 13:47:51 -08:00
|
|
|
function()
|
2021-02-14 08:50:19 -08:00
|
|
|
return {
|
2021-02-14 13:47:51 -08:00
|
|
|
name = "cylinder",
|
2021-02-14 08:50:19 -08:00
|
|
|
get_bounds = function(self, player, target_pos, size_3d)
|
2021-02-14 08:55:55 -08:00
|
|
|
return vector.subtract(target_pos, size_3d), vector.add(target_pos, size_3d)
|
2021-02-14 08:50:19 -08:00
|
|
|
end,
|
|
|
|
paint = function(self, data, a, target_pos, minp, maxp, ctx)
|
|
|
|
for i in a:iter(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z) do
|
|
|
|
local ip = a:position(i)
|
|
|
|
local epsilon = 0.3
|
|
|
|
local delta = { x = ip.x - target_pos.x, z = ip.z - target_pos.z }
|
|
|
|
delta = { x = delta.x / (ctx.size_3d.x + epsilon), z = delta.z / (ctx.size_3d.z + epsilon) }
|
|
|
|
delta = { x = delta.x^2, z = delta.z^2 }
|
|
|
|
|
|
|
|
if 1 > delta.x + delta.z then
|
|
|
|
ctx.draw(i)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
end,
|
2021-02-14 13:47:51 -08:00
|
|
|
function()
|
2021-02-14 08:50:19 -08:00
|
|
|
return {
|
2021-02-14 13:47:51 -08:00
|
|
|
name = "plateau",
|
2021-02-14 08:50:19 -08:00
|
|
|
get_bounds = function(self, player, target_pos, size_3d)
|
|
|
|
-- look up to 100 meters down
|
|
|
|
return vector.subtract(target_pos, vector.new(size_3d.x, 100, size_3d.z)), vector.add(target_pos, vector.new(size_3d.x, 0, size_3d.z))
|
|
|
|
end,
|
|
|
|
paint = function(self, data, a, target_pos, minp, maxp, ctx)
|
|
|
|
|
|
|
|
local origin = a:indexp(target_pos)
|
|
|
|
|
2021-02-14 13:04:03 -08:00
|
|
|
local function is_solid(id)
|
|
|
|
if #ctx.mask > 0 then
|
|
|
|
return not ctx.in_mask(id)
|
|
|
|
else
|
|
|
|
return id ~= minetest.CONTENT_AIR
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-02-14 08:50:19 -08:00
|
|
|
-- find deepest level (as negative)
|
|
|
|
local depth = 0
|
|
|
|
for x = -ctx.size_3d.x,ctx.size_3d.x do
|
|
|
|
for z = -ctx.size_3d.z,ctx.size_3d.z do
|
|
|
|
-- look in the circle around origin
|
|
|
|
local r = (x/(ctx.size_3d.x+0.3))^2 + (z/(ctx.size_3d.z+0.3))^2
|
|
|
|
if r < 1 then
|
|
|
|
-- scan 100 levels down
|
|
|
|
for y = 0,-100,-1 do
|
|
|
|
-- stop if the bottom is hit
|
|
|
|
local p = origin + x + y * a.ystride + z * a.zstride
|
2021-02-14 13:04:03 -08:00
|
|
|
if is_solid(data[p]) then
|
2021-02-14 08:50:19 -08:00
|
|
|
if y < depth then depth = y end
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- fill
|
|
|
|
for x = -ctx.size_3d.x,ctx.size_3d.x do
|
|
|
|
for z = -ctx.size_3d.z,ctx.size_3d.z do
|
|
|
|
-- look in the circle around origin
|
|
|
|
local r = (x/(ctx.size_3d.x+0.3))^2 + (z/(ctx.size_3d.z+0.3))^2
|
|
|
|
if r < 1 then
|
|
|
|
-- innermost 0.3 radius is fully filled, then sine descend
|
|
|
|
local cutoff = 0
|
|
|
|
if r > 0.6 then
|
|
|
|
cutoff = math.min(0, math.floor(depth * math.sin((r - 0.6) * math.pi / 4)))
|
|
|
|
end
|
|
|
|
-- fill with material down from cut off point to depth
|
|
|
|
for y = cutoff,depth,-1 do
|
|
|
|
i = origin + x + y * a.ystride + z * a.zstride
|
|
|
|
|
2021-02-14 13:04:03 -08:00
|
|
|
if is_solid(data[i]) then
|
2021-02-14 08:50:19 -08:00
|
|
|
break --stop at the first non-mask
|
2021-02-14 13:04:03 -08:00
|
|
|
else
|
|
|
|
ctx.draw(i)
|
2021-02-14 08:50:19 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
end,
|
2021-02-14 13:47:51 -08:00
|
|
|
function()
|
2021-02-14 08:50:19 -08:00
|
|
|
return {
|
2021-02-14 13:47:51 -08:00
|
|
|
name = "smooth",
|
2021-02-14 08:50:19 -08:00
|
|
|
get_bounds = function(self, player, target_pos, size_3d)
|
|
|
|
return vector.subtract(target_pos, size_3d), vector.add(target_pos, size_3d)
|
|
|
|
end,
|
|
|
|
paint = function(self, data, a, target_pos, minp, maxp, ctx)
|
|
|
|
local origin = a:indexp(target_pos)
|
|
|
|
local paint_flags = {}
|
|
|
|
|
|
|
|
local function get_weight(i,r)
|
|
|
|
local top, bottom = 0, 0
|
|
|
|
|
|
|
|
for lx = -r,r do
|
|
|
|
for ly = -r,r do
|
|
|
|
for lz = -r,r do
|
|
|
|
local weight = 1 -- all dots are equal, but this could be fancier
|
|
|
|
top = top + (data[i + lx + a.ystride*ly + a.zstride*lz] ~= minetest.CONTENT_AIR and weight or 0)
|
|
|
|
bottom = bottom + weight
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return top / bottom
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Spherical shape
|
|
|
|
-- Reduce all bounds by 1 to avoid edge glitches when looking for neighbours
|
|
|
|
for x = -ctx.size_3d.x+1,ctx.size_3d.x-1 do
|
|
|
|
for y = -ctx.size_3d.y+1,ctx.size_3d.y-1 do
|
|
|
|
for z = -ctx.size_3d.z+1,ctx.size_3d.z-1 do
|
|
|
|
local r = (x/ctx.size_3d.x)^2 + (y/ctx.size_3d.y)^2 + (z/ctx.size_3d.z)^2
|
|
|
|
if r <= 1 then
|
|
|
|
local i = origin + x + a.ystride*y + a.zstride*z
|
|
|
|
local rr = math.floor(math.max(1,math.min(ctx.size_3d.x/3,(1-r)*ctx.size_3d.x)))
|
|
|
|
paint_flags[i] = (get_weight(i, rr) < 0.5)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for pos,is_air in pairs(paint_flags) do
|
|
|
|
if is_air ~= (data[pos] == minetest.CONTENT_AIR) then
|
|
|
|
ctx.draw(pos, is_air and minetest.CONTENT_AIR or ctx.get_paint())
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
end,
|
2021-02-14 13:47:51 -08:00
|
|
|
function()
|
2021-02-14 08:50:19 -08:00
|
|
|
return {
|
2021-02-14 13:47:51 -08:00
|
|
|
name = "trowel",
|
2021-02-14 08:50:19 -08:00
|
|
|
get_bounds = function(self, player, target_pos, size_3d)
|
|
|
|
local pp = vector.floor(player:get_pos())
|
|
|
|
local minp,maxp = vector.subtract(target_pos, size_3d), vector.add(target_pos, size_3d)
|
|
|
|
return vector.new(math.min(minp.x, pp.x), math.min(minp.y, pp.y), math.min(minp.z, pp.z)),
|
|
|
|
vector.new(math.max(maxp.x, pp.x), math.max(maxp.y, pp.y), math.max(maxp.z, pp.z))
|
|
|
|
end,
|
|
|
|
paint = function(self, data, a, target_pos, minp, maxp, ctx)
|
|
|
|
local origin = a:indexp(target_pos)
|
|
|
|
local paint_flags = {}
|
|
|
|
|
|
|
|
local function get_weight(i,r)
|
|
|
|
local top, bottom = 0, 0
|
|
|
|
|
|
|
|
for lx = -r,r do
|
|
|
|
for ly = -r,r do
|
|
|
|
for lz = -r,r do
|
|
|
|
local weight = 1 -- all dots are equal, but this could be fancier
|
|
|
|
top = top + (data[i + lx + a.ystride*ly + a.zstride*lz] ~= minetest.CONTENT_AIR and weight or 0)
|
|
|
|
bottom = bottom + weight
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return top / bottom
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Spherical shape
|
|
|
|
-- Reduce all bounds by 1 to avoid edge glitches when looking for neighbours
|
|
|
|
for x = -ctx.size_3d.x+1,ctx.size_3d.x-1 do
|
|
|
|
for y = -ctx.size_3d.y+1,ctx.size_3d.y-1 do
|
|
|
|
for z = -ctx.size_3d.z+1,ctx.size_3d.z-1 do
|
|
|
|
local r = (x/ctx.size_3d.x)^2 + (y/ctx.size_3d.y)^2 + (z/ctx.size_3d.z)^2
|
|
|
|
if r <= 1 then
|
|
|
|
local i = origin + x + a.ystride*y + a.zstride*z
|
|
|
|
local rr = math.floor(math.max(1,math.min(ctx.size_3d.x/3,(1-r)*ctx.size_3d.x)))
|
|
|
|
local dotproduct = vector.dot(ctx.player:get_look_dir(), vector.normalize(vector.new(x,y,z)))
|
|
|
|
paint_flags[i] = (get_weight(i, rr) + dotproduct < 0.5)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for pos,is_air in pairs(paint_flags) do
|
|
|
|
if is_air ~= (data[pos] == minetest.CONTENT_AIR) then
|
|
|
|
ctx.draw(pos, is_air and minetest.CONTENT_AIR or ctx.get_paint())
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
end,
|
|
|
|
}
|
2021-01-24 16:09:18 -08:00
|
|
|
})
|
|
|
|
|
2021-01-31 16:21:39 -08:00
|
|
|
-- Colorize brush when putting to inventory
|
|
|
|
minetest.register_on_player_inventory_action(function(player,action,inventory,inventory_info)
|
2021-03-09 15:01:58 -08:00
|
|
|
if action ~= "put" or inventory_info.listname ~= "main" or inventory_info.stack:get_name() ~= "terraform:brush" then
|
2021-02-14 08:50:19 -08:00
|
|
|
return
|
|
|
|
end
|
|
|
|
local stack = inventory_info.stack
|
|
|
|
if (stack:get_meta():get_string("color") or "") == "" then
|
|
|
|
local colors = terraform._tools["brush"].colors
|
|
|
|
local color = colors[math.random(1,#colors)]
|
|
|
|
stack:get_meta():set_string("color", color)
|
|
|
|
inventory:set_stack(inventory_info.listname, inventory_info.index, stack)
|
|
|
|
end
|
2021-01-31 16:21:39 -08:00
|
|
|
end)
|
|
|
|
|
2021-02-08 01:14:29 -08:00
|
|
|
--
|
|
|
|
-- Undo changes to the world
|
|
|
|
--
|
2021-01-24 16:09:18 -08:00
|
|
|
terraform:register_tool("undo", {
|
2021-02-14 08:50:19 -08:00
|
|
|
description = "Terraform Undo\n\nUndoes changes to the world",
|
|
|
|
short_description = "Terraform Undo",
|
|
|
|
inventory_image = "terraform_tool_undo.png",
|
|
|
|
execute = function(itemstack, player, target)
|
|
|
|
history:undo(player)
|
|
|
|
end
|
2021-01-24 16:09:18 -08:00
|
|
|
})
|
|
|
|
|
2021-02-08 01:14:29 -08:00
|
|
|
--
|
|
|
|
-- A magic wand to fix light problems.
|
|
|
|
--
|
2021-02-06 12:21:21 -08:00
|
|
|
terraform:register_tool("fixlight", {
|
2021-02-14 08:50:19 -08:00
|
|
|
description = "Terraform Fix Light\n\nFix lighting problems",
|
|
|
|
short_description = "Terraform Fix Light",
|
|
|
|
inventory_image = "terraform_tool_fix_light.png",
|
|
|
|
execute = function(itemstack, player, target)
|
|
|
|
-- Get position
|
|
|
|
local target_pos = minetest.get_pointed_thing_position(target)
|
|
|
|
if not target_pos then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local s = 100
|
|
|
|
local origin = target_pos
|
|
|
|
local minp = vector.subtract(origin, vector.new(s,s,s))
|
|
|
|
local maxp = vector.add(origin, vector.new(s,s,s))
|
|
|
|
minetest.fix_light(minp, maxp)
|
|
|
|
end
|
2021-02-06 12:21:21 -08:00
|
|
|
})
|
|
|
|
|
|
|
|
local function box_diff(a, b)
|
2021-02-14 08:50:19 -08:00
|
|
|
-- a - b for boxes a and b, both a { min = vector, max = vector }
|
|
|
|
-- return 3 boxes
|
|
|
|
-- * split along X axis, full Y and Z
|
|
|
|
-- * split along Y axis, half X, full Z
|
|
|
|
-- * half X, Y, Z
|
|
|
|
|
|
|
|
local function diff_split(ax1, ax2, bx1, bx2)
|
|
|
|
-- given boundaries of two boxes a and b along an axis (x)
|
|
|
|
-- return x coordinates of athe diff a - b as two boxes
|
|
|
|
-- first box is having full height
|
|
|
|
-- second box is having trimmed height (w/o the intersection part)
|
|
|
|
|
|
|
|
if ax1 < bx1 then
|
|
|
|
return ax1, bx1, bx1, ax2
|
|
|
|
else
|
|
|
|
return bx2, ax2, ax1, bx2
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local fx1, fx2, hx1, hx2 = diff_split(a.min.x, a.max.x, b.min.x, b.max.x)
|
|
|
|
local fy1, fy2, hy1, hy2 = diff_split(a.min.y, a.max.y, b.min.y, b.max.y)
|
|
|
|
local fz1, fz2, hz1, hz2 = diff_split(a.min.z, a.max.z, b.min.z, b.max.z)
|
|
|
|
|
|
|
|
return { min = vector.new(fx1, a.min.y, a.min.z), max = vector.new(fx2, a.max.y, a.max.z)},
|
|
|
|
{ min = vector.new(hx1, fy1, a.min.z), max = vector.new(hx2, fy2, a.max.z)},
|
|
|
|
{ min = vector.new(hx1, hy1, fz1), max = vector.new(hx2, hy2, fz2)}
|
2021-02-06 12:21:21 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local light = {
|
2021-02-14 08:50:19 -08:00
|
|
|
size = 20,
|
|
|
|
level = minetest.LIGHT_MAX,
|
|
|
|
pitch_rate = 1/5,
|
|
|
|
queues = { light = {}, dark = {} },
|
|
|
|
players = {},
|
|
|
|
light_bounds = function(self, pos)
|
|
|
|
local s = self.size
|
|
|
|
return { min = vector.subtract(pos, vector.new(s,s,s)), max = vector.add(pos, vector.new(s,s,s)) }
|
|
|
|
end,
|
|
|
|
add_player = function(self, player)
|
|
|
|
self.players[player:get_player_name()] = { player = player }
|
2021-04-07 03:15:41 -07:00
|
|
|
terraform.skip_light[player:get_player_name()] = true
|
2021-02-14 08:50:19 -08:00
|
|
|
end,
|
|
|
|
remove_player = function(self, player)
|
|
|
|
local light = self.players[player:get_player_name()]
|
|
|
|
if light ~= nil then
|
|
|
|
table.insert(self.queues.dark, self:light_bounds(vector.floor(light.player:get_pos())))
|
|
|
|
if light.last_pos ~= nil then
|
|
|
|
table.insert(self.queues.dark, self:light_bounds(light.last_pos))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self.players[player:get_player_name()] = nil
|
2022-05-09 11:16:43 -07:00
|
|
|
terraform.skip_light[player:get_player_name()] = nil
|
2021-02-14 08:50:19 -08:00
|
|
|
self:tick()
|
|
|
|
end,
|
|
|
|
tick = function(self)
|
|
|
|
for name,pl in pairs(self.players) do
|
|
|
|
local origin = vector.floor(pl.player:get_pos())
|
|
|
|
local box = self:light_bounds(origin)
|
|
|
|
local minp = box.min
|
|
|
|
local maxp = box.max
|
|
|
|
pl.c = (pl.c or 0) + 1
|
|
|
|
|
|
|
|
if pl.last_pos ~= nil then
|
|
|
|
if vector.distance(origin, pl.last_pos) < self.size * self.pitch_rate then
|
|
|
|
break -- skip the player, not enough movement
|
|
|
|
end
|
|
|
|
local old_box = self:light_bounds(pl.last_pos)
|
|
|
|
local b1, b2, b3 = box_diff(old_box, box)
|
|
|
|
table.insert(self.queues.dark, b1)
|
|
|
|
table.insert(self.queues.dark, b2)
|
|
|
|
table.insert(self.queues.dark, b3)
|
|
|
|
end
|
|
|
|
table.insert(self.queues.light, box)
|
|
|
|
pl.last_pos = origin
|
|
|
|
end
|
|
|
|
|
|
|
|
-- process queues
|
|
|
|
while #self.queues.dark > 0 do
|
|
|
|
local box = table.remove(self.queues.dark, 1)
|
|
|
|
minetest.get_voxel_manip(box.min, box.max):write_to_map(true) -- fix the light
|
|
|
|
end
|
|
|
|
|
|
|
|
while #self.queues.light > 0 do
|
|
|
|
local box = table.remove(self.queues.light, 1)
|
|
|
|
|
|
|
|
-- Load manipulator
|
|
|
|
local vm = minetest.get_voxel_manip()
|
|
|
|
local mine,maxe = vm:read_from_map(box.min, box.max)
|
|
|
|
local va = VoxelArea:new({MinEdge = mine, MaxEdge = maxe})
|
|
|
|
|
|
|
|
-- Set light information in the area
|
|
|
|
local light = vm:get_light_data()
|
|
|
|
local level = self.level
|
|
|
|
for i in va:iter(box.min.x, box.min.y, box.min.z, box.max.x, box.max.y, box.max.z) do
|
|
|
|
if light[i] == nil then
|
|
|
|
light[i] = level*17
|
|
|
|
else
|
|
|
|
light[i] = math.max(math.floor(light[i] / 16), level) * 16 + math.max(light[i] % 16, level)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
vm:set_light_data(light)
|
|
|
|
vm:write_to_map(false)
|
|
|
|
end
|
|
|
|
end
|
2021-02-06 12:21:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
terraform:register_tool("light", {
|
2021-02-14 08:50:19 -08:00
|
|
|
description = "Terraform Light\n\nTurn on the lights",
|
|
|
|
short_description = "Terraform Light",
|
|
|
|
inventory_image = "terraform_tool_light.png",
|
|
|
|
execute = function(itemstack, player, target)
|
|
|
|
if player:get_day_night_ratio() ~= nil then
|
|
|
|
player:override_day_night_ratio(nil)
|
|
|
|
light:remove_player(player)
|
|
|
|
else
|
|
|
|
player:override_day_night_ratio(1)
|
|
|
|
light:add_player(player)
|
|
|
|
end
|
|
|
|
end
|
2021-02-06 12:21:21 -08:00
|
|
|
})
|
|
|
|
|
|
|
|
minetest.register_on_leaveplayer(function(player)
|
2021-02-14 08:50:19 -08:00
|
|
|
light:remove_player(player)
|
2021-02-06 12:21:21 -08:00
|
|
|
end)
|
|
|
|
|
|
|
|
local function place_lights()
|
2021-02-14 08:50:19 -08:00
|
|
|
light:tick()
|
|
|
|
minetest.after(0.5, place_lights)
|
2021-02-06 12:21:21 -08:00
|
|
|
end
|
2021-02-08 01:11:50 -08:00
|
|
|
minetest.after(0.5, place_lights)
|
2021-01-24 16:09:18 -08:00
|
|
|
|
2021-02-21 05:06:27 -08:00
|
|
|
|
|
|
|
terraform:register_tool("teleport", {
|
|
|
|
description = "Terraform Teleport\n\nTravel fast",
|
|
|
|
short_description = "Terraform Teleport",
|
|
|
|
inventory_image = "terraform_tool_teleport.png",
|
|
|
|
execute = function(itemstack, player, target)
|
2021-02-21 12:21:28 -08:00
|
|
|
-- Get position
|
|
|
|
local target_pos = minetest.get_pointed_thing_position(target)
|
|
|
|
if not target_pos then
|
|
|
|
return
|
|
|
|
end
|
2021-02-21 05:06:27 -08:00
|
|
|
|
2021-02-21 12:21:28 -08:00
|
|
|
local player_pos = vector.floor(player:get_pos())
|
|
|
|
local probe = { x = player_pos.x, y = player_pos.y, z = player_pos.z }
|
|
|
|
while minetest.get_node(probe).name == "air" do
|
|
|
|
probe.y = probe.y - 1
|
|
|
|
end
|
|
|
|
local vm = minetest.get_voxel_manip()
|
|
|
|
local mine,maxe = vm:read_from_map(vector.add(target_pos, vector.new(0, -128, 0)), vector.add(target_pos, vector.new(0, 128 + player_pos.y - probe.y, 0)))
|
|
|
|
local va = VoxelArea:new({MinEdge=mine, MaxEdge=maxe})
|
|
|
|
local data = vm:get_data()
|
|
|
|
local i = va:indexp(target_pos)
|
|
|
|
while data[i] ~= minetest.CONTENT_AIR do --Move to the topmost block
|
|
|
|
i = i + va.ystride
|
|
|
|
end
|
|
|
|
i = i + va.ystride * (player_pos.y - probe.y - 1)
|
|
|
|
|
|
|
|
for delta = 0,128 do -- Try up to 128 meters up or down
|
|
|
|
for sign = -1,1,2 do
|
|
|
|
if data[i + va.ystride * delta * sign] == minetest.CONTENT_AIR and data[i + va.ystride * (1 + delta * sign)] == minetest.CONTENT_AIR then
|
|
|
|
result = va:position(i + va.ystride * delta * sign)
|
|
|
|
player:set_pos(result)
|
|
|
|
return
|
2021-02-21 05:06:27 -08:00
|
|
|
end
|
|
|
|
end
|
2021-02-21 12:21:28 -08:00
|
|
|
end
|
2021-02-21 05:06:27 -08:00
|
|
|
end
|
|
|
|
})
|