autoplacer

This commit is contained in:
BuckarooBanzay 2022-11-14 15:05:17 +01:00
parent c9913e1df1
commit 38b0767048
11 changed files with 361 additions and 109 deletions

17
api.lua
View File

@ -57,3 +57,20 @@ end
function building_lib.get_condition(name) function building_lib.get_condition(name)
return conditions[name] return conditions[name]
end end
-- name -> autoplace_def
local autoplacers = {}
function building_lib.register_autoplacer(name, def)
assert(type(def.buildings) == "table")
def.name = name
autoplacers[name] = def
end
function building_lib.get_autoplacers()
return autoplacers
end
function building_lib.get_autoplacer(name)
return autoplacers[name]
end

67
autoplace.lua Normal file
View File

@ -0,0 +1,67 @@
function building_lib.can_autoplace(mapblock_pos, _, autoplacer_name)
local autoplacer = building_lib.get_autoplacer(autoplacer_name)
if not autoplacer then
return false, "autoplacer not found: '" .. autoplacer_name .. "'"
end
local fallback_building_name
for _, building_condition in ipairs(autoplacer.buildings) do
if building_condition.fallback then
fallback_building_name = building_condition.name
end
for _, rotation in ipairs(building_condition.rotations or {0}) do
local rotated_condition_group = building_lib.rotate_condition_group(building_condition.conditions, rotation)
local success = building_lib.check_condition_group(mapblock_pos, mapblock_pos, rotated_condition_group)
if success then
return true, building_condition.name, rotation
end
end
end
if fallback_building_name then
return true, fallback_building_name, 0
else
return false, "no conditions matched"
end
end
local default_propagation_dirs = {
{x=1, y=0, z=0},
{x=-1, y=0, z=0},
{x=0, y=0, z=1},
{x=0, y=0, z=-1},
{x=1, y=1, z=0},
{x=-1, y=1, z=0},
{x=0, y=1, z=1},
{x=0, y=1, z=-1},
}
function building_lib.autoplace(mapblock_pos, playername, autoplacer_name, enable_propagation)
local success, building_name, rotation = building_lib.can_autoplace(mapblock_pos, playername, autoplacer_name)
if not success then
return false, building_name
end
local err
success, err = building_lib.build(mapblock_pos, playername, building_name, rotation)
if not success then
return success, err
end
local autoplacer = building_lib.get_autoplacer(autoplacer_name)
if enable_propagation and autoplacer.propagate then
-- propagate changes
for _, dir in ipairs(autoplacer.propagation_dirs or default_propagation_dirs) do
local offset_mapblock_pos = vector.add(mapblock_pos, dir)
success = building_lib.check_condition_table(autoplacer.propagate, offset_mapblock_pos)
if success then
-- selector matches, propagate autobuild
building_lib.autoplace(offset_mapblock_pos, playername, autoplacer_name, false)
end
end
end
return true
end

131
autoplace_tool.lua Normal file
View File

@ -0,0 +1,131 @@
local formname = "building_lib_autoplacer_configure"
local function get_autoplacer_list()
local autoplacer_list = {}
for name in pairs(building_lib.get_autoplacers()) do
table.insert(autoplacer_list, name)
end
table.sort(autoplacer_list, function(a,b) return a < b end)
return autoplacer_list
end
local function get_formspec(itemstack)
local meta = itemstack:get_meta()
local autoplacer_list = get_autoplacer_list()
local selected_autoplacer_name = meta:get_string("autoplacer_name")
if not selected_autoplacer_name or selected_autoplacer_name == "" then
selected_autoplacer_name = autoplacer_list[1]
end
local selected_autoplacer = 1
local textlist = ""
for i, autoplacer_name in ipairs(autoplacer_list) do
if selected_autoplacer_name == autoplacer_name then
selected_autoplacer = i
end
textlist = textlist .. autoplacer_name
if i < #autoplacer_list then
textlist = textlist .. ","
end
end
return "size[8,7;]" ..
"textlist[0,0.1;8,6;autoplacer_name;" .. textlist .. ";" .. selected_autoplacer .. "]" ..
"button_exit[0.1,6.5;8,1;back;Back]"
end
minetest.register_on_player_receive_fields(function(player, f, fields)
if not minetest.check_player_privs(player, { mapblock_lib = true }) then
return
end
if formname ~= f then
return
end
if fields.quit then
return
end
if fields.autoplacer_name then
local parts = fields.autoplacer_name:split(":")
if parts[1] == "CHG" then
local itemstack = player:get_wielded_item()
local meta = itemstack:get_meta()
local selected = tonumber(parts[2])
local autoplacer_list = get_autoplacer_list()
local autoplacer_name = autoplacer_list[selected]
if not autoplacer_name then
return
end
meta:set_string("autoplacer_name", autoplacer_name)
meta:set_string("description", "Selected autoplacer: '" .. autoplacer_name .. "'")
player:set_wielded_item(itemstack)
end
end
end)
minetest.register_tool("building_lib:autoplace", {
description = "building_lib autoplacer",
inventory_image = "building_lib_autoplace.png^[colorize:#00ff00",
stack_max = 1,
range = 0,
on_secondary_use = function(itemstack, player)
minetest.show_formspec(player:get_player_name(), formname, get_formspec(itemstack))
end,
on_use = function(itemstack, player)
local meta = itemstack:get_meta()
local playername = player:get_player_name()
local autoplacer_name = meta:get_string("autoplacer_name")
local pointed_mapblock_pos = building_lib.get_pointed_mapblock(player)
local success, err = building_lib.autoplace(pointed_mapblock_pos, playername, autoplacer_name, true)
if not success then
minetest.chat_send_player(playername, err)
end
end,
on_step = function(itemstack, player)
local playername = player:get_player_name()
local mapblock_pos1 = building_lib.get_pointed_mapblock(player)
local meta = itemstack:get_meta()
local autoplacer_name = meta:get_string("autoplacer_name")
local success, building_name, rotation = building_lib.can_autoplace(mapblock_pos1, playername, autoplacer_name)
if not success then
building_lib.clear_preview(playername)
return
end
local building_def = building_lib.get_building(building_name)
if not building_def then
building_lib.clear_preview(playername)
return
end
local size = building_lib.get_building_size(building_def, rotation)
local mapblock_pos2 = vector.add(mapblock_pos1, vector.subtract(size, 1))
local color = "#00ff00"
local can_build = building_lib.can_build(mapblock_pos1, playername, building_def.name, rotation)
if not can_build then
color = "#ffff00"
end
building_lib.show_preview(
playername,
"building_lib_autoplace.png",
color,
building_def,
mapblock_pos1,
mapblock_pos2,
rotation
)
end,
on_blur = function(player)
local playername = player:get_player_name()
building_lib.clear_preview(playername)
end
})

View File

@ -30,7 +30,7 @@ function building_lib.can_build(mapblock_pos, _, building_name, rotation)
local mapblock_pos2 = vector.add(mapblock_pos, vector.subtract(size, 1)) local mapblock_pos2 = vector.add(mapblock_pos, vector.subtract(size, 1))
local success local success
success, message = building_lib.check_conditions(mapblock_pos, mapblock_pos2, building_def) success, message = building_lib.check_condition_groups(mapblock_pos, mapblock_pos2, building_def.conditions)
if not success then if not success then
return false, message return false, message
end end

View File

@ -135,53 +135,3 @@ minetest.register_tool("building_lib:place", {
building_lib.clear_preview(playername) building_lib.clear_preview(playername)
end end
}) })
minetest.register_tool("building_lib:remove", {
description = "building_lib remover",
inventory_image = "building_lib_remove.png^[colorize:#ff0000",
stack_max = 1,
range = 0,
on_use = function(_, player)
local mapblock_pos = building_lib.get_pointed_mapblock(player)
local success, err = building_lib.remove(mapblock_pos)
if not success then
minetest.chat_send_player(player:get_player_name(), err)
end
end,
on_step = function(_, player)
local playername = player:get_player_name()
local pointed_mapblock_pos = building_lib.get_pointed_mapblock(player)
local building_info, origin = building_lib.get_placed_building_info(pointed_mapblock_pos)
if not building_info then
building_lib.clear_preview(playername)
return
end
local building_def = building_lib.get_building(building_info.name)
local size = building_lib.get_building_size(building_def, building_info.rotation or 0)
local mapblock_pos2 = vector.add(origin, vector.subtract(size, 1))
local color = "#ff0000"
local can_remove = building_lib.can_remove(origin)
if not can_remove then
color = "#ffff00"
end
building_lib.show_preview(
playername,
"building_lib_remove.png",
color,
building_def,
origin,
mapblock_pos2,
building_info.rotation
)
end,
on_blur = function(player)
local playername = player:get_player_name()
building_lib.clear_preview(playername)
end
})

View File

@ -1,18 +1,18 @@
-- checks a single condition -- checks a single condition
local function check_condition(key, value, mapblock_pos, building_def) local function check_condition(key, value, mapblock_pos)
local condition = building_lib.get_condition(key) local condition = building_lib.get_condition(key)
if condition and type(condition.can_build) == "function" then if condition and type(condition.can_build) == "function" then
return condition.can_build(mapblock_pos, building_def, value) return condition.can_build(mapblock_pos, value)
end end
return true return true
end end
-- checks a table of conditions with the given mapblock_pos -- checks a table of conditions with the given mapblock_pos
-- all entries have to match -- all entries have to match
local function check_table(map, mapblock_pos, building_def) function building_lib.check_condition_table(map, mapblock_pos)
for key, value in pairs(map) do for key, value in pairs(map) do
local success, msg = check_condition(key, value, mapblock_pos, building_def) local success, msg = check_condition(key, value, mapblock_pos)
if not success then if not success then
-- failure and in AND mode, return immediately -- failure and in AND mode, return immediately
return false, msg or "condition failed: '" .. key .. "'" return false, msg or "condition failed: '" .. key .. "'"
@ -21,69 +21,100 @@ local function check_table(map, mapblock_pos, building_def)
return true return true
end end
local default_conditions = { local default_condition_groups = {
{["*"] = { empty = true }} {["*"] = { empty = true }}
} }
function building_lib.check_conditions(mapblock_pos1, mapblock_pos2, building_def) -- rotates a condition group by given rotation
for _, condition_group in ipairs(building_def.conditions or default_conditions) do -- TODO: only supports sizes of 1
local group_match = true function building_lib.rotate_condition_group(condition_group, rotation)
if rotation == 0 then
-- no rotation
return condition_group
end
for selector, conditions in pairs(condition_group) do local new_condition_group = {}
local it for selector, condition in pairs(condition_group) do
if selector == "*" then local pos = minetest.string_to_pos(selector)
-- match all if pos then
it = mapblock_lib.pos_iterator(mapblock_pos1, mapblock_pos2) -- rotate position
elseif selector == "base" then local rotated_pos = mapblock_lib.rotate_pos(pos, {x=0, y=0, z=0}, rotation)
-- match only base positions new_condition_group[minetest.pos_to_string(rotated_pos)] = condition
it = mapblock_lib.pos_iterator(mapblock_pos1, {x=mapblock_pos2.x, y=mapblock_pos1.y, z=mapblock_pos2.z}) else
elseif selector == "underground" then -- keep selector name
-- match only underground positions new_condition_group[pos] = condition
it = mapblock_lib.pos_iterator({ end
x=mapblock_pos1.x, y=mapblock_pos1.y-1, z=mapblock_pos1.z end
},{ return new_condition_group
x=mapblock_pos2.x, y=mapblock_pos1.y-1, z=mapblock_pos2.z end
})
-- go through all condition groups and return true if any of them matches
function building_lib.check_condition_groups(mapblock_pos1, mapblock_pos2, condition_groups)
for _, condition_group in ipairs(condition_groups or default_condition_groups) do
local success = building_lib.check_condition_group(mapblock_pos1, mapblock_pos2, condition_group)
if success then
return true
end
end
return false, "no matching condition found"
end
function building_lib.check_condition_group(mapblock_pos1, mapblock_pos2, condition_group)
local group_match = true
for selector, conditions in pairs(condition_group) do
local it
if selector == "*" then
-- match all
it = mapblock_lib.pos_iterator(mapblock_pos1, mapblock_pos2)
elseif selector == "base" then
-- match only base positions
it = mapblock_lib.pos_iterator(mapblock_pos1, {x=mapblock_pos2.x, y=mapblock_pos1.y, z=mapblock_pos2.z})
elseif selector == "underground" then
-- match only underground positions
it = mapblock_lib.pos_iterator({
x=mapblock_pos1.x, y=mapblock_pos1.y-1, z=mapblock_pos1.z
},{
x=mapblock_pos2.x, y=mapblock_pos1.y-1, z=mapblock_pos2.z
})
else
-- try to parse a manual position
local rel_pos = minetest.string_to_pos(selector)
if rel_pos then
-- single position
local abs_pos = vector.add(mapblock_pos1, rel_pos)
it = mapblock_lib.pos_iterator(abs_pos, abs_pos)
else else
-- try to parse a manual position return false, "unknown selector: " .. selector
local pos = minetest.string_to_pos(selector) end
if pos then end
-- single position
it = mapblock_lib.pos_iterator(pos, pos) while true do
else local mapblock_pos = it()
return false, "unknown selector: " .. selector if not mapblock_pos then
end break
end end
while true do local success = building_lib.check_condition_table(conditions, mapblock_pos)
local mapblock_pos = it() if not success then
if not mapblock_pos then group_match = false
break
end
local success = check_table(conditions, mapblock_pos, building_def)
if not success then
group_match = false
break
end
end
if not group_match then
break break
end end
end end
if group_match then if not group_match then
return true break
end end
end end
return false, "no matching condition found" if group_match then
return true
end
end end
-- checks if a building with specified group is placed there already -- checks if a building with specified group is placed there already
building_lib.register_condition("group", { building_lib.register_condition("group", {
can_build = function(mapblock_pos, _, value) can_build = function(mapblock_pos, value)
local building_info = building_lib.get_placed_building_info(mapblock_pos) local building_info = building_lib.get_placed_building_info(mapblock_pos)
if building_info then if building_info then
local building_def = building_lib.get_building(building_info.name) local building_def = building_lib.get_building(building_info.name)
@ -95,6 +126,13 @@ building_lib.register_condition("group", {
end end
}) })
building_lib.register_condition("name", {
can_build = function(mapblock_pos, value)
local building_info = building_lib.get_placed_building_info(mapblock_pos)
return building_info and building_info.name == value
end
})
-- checks if the mapblock position is empty -- checks if the mapblock position is empty
building_lib.register_condition("empty", { building_lib.register_condition("empty", {
can_build = function(mapblock_pos) can_build = function(mapblock_pos)

View File

@ -13,19 +13,15 @@ local test_map = {
} }
building_lib.register_condition("test_map", { building_lib.register_condition("test_map", {
can_build = function(mapblock_pos, _, flag_value) can_build = function(mapblock_pos, flag_value)
return test_map[minetest.pos_to_string(mapblock_pos)] == flag_value return test_map[minetest.pos_to_string(mapblock_pos)] == flag_value
end end
}) })
local function run_conditions(conditions) local function run_conditions(condition_groups)
local mapblock_pos1 = {x = 0, y = 0, z = 0} local mapblock_pos1 = {x = 0, y = 0, z = 0}
local mapblock_pos2 = vector.add(mapblock_pos1, 2) local mapblock_pos2 = vector.add(mapblock_pos1, 2)
return building_lib.check_conditions(mapblock_pos1, mapblock_pos2, { return building_lib.check_condition_groups(mapblock_pos1, mapblock_pos2, condition_groups)
name = "something:test1",
placement = "dummy",
conditions = conditions
})
end end
mtt.register("check_conditions", function(callback) mtt.register("check_conditions", function(callback)

View File

@ -16,9 +16,12 @@ dofile(MP .. "/common.lua")
dofile(MP .. "/placements/mapblock_lib.lua") dofile(MP .. "/placements/mapblock_lib.lua")
dofile(MP .. "/conditions.lua") dofile(MP .. "/conditions.lua")
dofile(MP .. "/build.lua") dofile(MP .. "/build.lua")
dofile(MP .. "/build_tool.lua")
dofile(MP .. "/autoplace.lua")
dofile(MP .. "/autoplace_tool.lua")
dofile(MP .. "/remove.lua") dofile(MP .. "/remove.lua")
dofile(MP .. "/remove_tool.lua")
dofile(MP .. "/chat.lua") dofile(MP .. "/chat.lua")
dofile(MP .. "/tools.lua")
dofile(MP .. "/events.lua") dofile(MP .. "/events.lua")
dofile(MP .. "/hacks.lua") dofile(MP .. "/hacks.lua")
dofile(MP .. "/mapgen.lua") dofile(MP .. "/mapgen.lua")

View File

@ -66,7 +66,7 @@ building_lib.register_placement("simple", {
-- registers a condition that checks for certain world conditions -- registers a condition that checks for certain world conditions
building_lib.register_condition("on_flat_surface", { building_lib.register_condition("on_flat_surface", {
can_build = function(mapblock_pos, building_def, flag_value) can_build = function(mapblock_pos, flag_value)
return false, msg return false, msg
end end
}) })

50
remove_tool.lua Normal file
View File

@ -0,0 +1,50 @@
minetest.register_tool("building_lib:remove", {
description = "building_lib remover",
inventory_image = "building_lib_remove.png^[colorize:#ff0000",
stack_max = 1,
range = 0,
on_use = function(_, player)
local mapblock_pos = building_lib.get_pointed_mapblock(player)
local success, err = building_lib.remove(mapblock_pos)
if not success then
minetest.chat_send_player(player:get_player_name(), err)
end
end,
on_step = function(_, player)
local playername = player:get_player_name()
local pointed_mapblock_pos = building_lib.get_pointed_mapblock(player)
local building_info, origin = building_lib.get_placed_building_info(pointed_mapblock_pos)
if not building_info then
building_lib.clear_preview(playername)
return
end
local building_def = building_lib.get_building(building_info.name)
local size = building_lib.get_building_size(building_def, building_info.rotation or 0)
local mapblock_pos2 = vector.add(origin, vector.subtract(size, 1))
local color = "#ff0000"
local can_remove = building_lib.can_remove(origin)
if not can_remove then
color = "#ffff00"
end
building_lib.show_preview(
playername,
"building_lib_remove.png",
color,
building_def,
origin,
mapblock_pos2,
building_info.rotation
)
end,
on_blur = function(player)
local playername = player:get_player_name()
building_lib.clear_preview(playername)
end
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB