ccompass-cd2025/init.lua
2021-09-06 21:16:37 +02:00

412 lines
13 KiB
Lua

-- compass configuration interface - adjustable from other mods or minetest.conf settings
ccompass = {}
-- default target to static_spawnpoint or 0/0/0
ccompass.default_target = minetest.setting_get_pos("static_spawnpoint") or {x=0, y=0, z=0}
-- Re-calibration allowed
ccompass.recalibrate = minetest.settings:get_bool("ccompass_recalibrate")
if ccompass.recalibrate == nil then
ccompass.recalibrate = true
end
-- Target restriction
ccompass.restrict_target = minetest.settings:get_bool("ccompass_restrict_target")
ccompass.restrict_target_nodes = {}
local nodes_setting = minetest.settings:get("ccompass_restrict_target_nodes")
if nodes_setting then
nodes_setting:gsub("[^,]+", function(z)
ccompass.restrict_target_nodes[z] = true
end)
end
ccompass.allow_climbable_target = not minetest.settings:get_bool("ccompass_deny_climbable_target")
ccompass.allow_damaging_target = minetest.settings:get_bool("ccompass_allow_damage_target")
-- Teleport targets
ccompass.teleport_nodes = {}
local teleport_nodes_setting = minetest.settings:get("ccompass_teleport_nodes")
if teleport_nodes_setting then
teleport_nodes_setting:gsub("[^,]+", function(z)
ccompass.teleport_nodes[z] = true
end)
else
ccompass.teleport_nodes["default:mese"] = true
end
-- Permited nodes above target
ccompass.nodes_over_target_allow = {}
nodes_setting = minetest.settings:get("ccompass_nodes_over_target_allow")
if nodes_setting then
nodes_setting:gsub("[^,]+", function(z)
ccompass.nodes_over_target_allow[z] = true
end)
end
-- Not permited nodes above target
ccompass.nodes_over_target_deny = {}
nodes_setting = minetest.settings:get("ccompass_nodes_over_target_deny")
if nodes_setting then
nodes_setting:gsub("[^,]+", function(z)
ccompass.nodes_over_target_deny[z] = true
end)
end
-- Permited drawtype of nodes above target
ccompass.nodes_over_target_allow_drawtypes = {}
nodes_setting = minetest.settings:get("ccompass_nodes_over_target_allow_drawtypes")
if nodes_setting then
nodes_setting:gsub("[^,]+", function(z)
ccompass.nodes_over_target_allow_drawtypes[z] = true
end)
else
ccompass.nodes_over_target_allow_drawtypes = {
airlike = true,
flowingliquid = true,
liquid = true,
plantlike = true,
plantlike_rooted = true,
}
end
-- default to legacy behaviour
ccompass.stack_max = tonumber(minetest.settings:get("ccompass_stack_max") or 1) or 1
ccompass.allow_using_stacks = minetest.settings:get_bool("ccompass_allow_using_stacks")
ccompass.idle_interval = tonumber(minetest.settings:get("ccompass_idle_interval") or 1) or 1
if minetest.settings:get_bool("ccompass_aliasses") then
minetest.register_alias("compass:0", "ccompass:0")
minetest.register_alias("compass:1", "ccompass:1")
minetest.register_alias("compass:2", "ccompass:2")
minetest.register_alias("compass:3", "ccompass:3")
minetest.register_alias("compass:4", "ccompass:4")
minetest.register_alias("compass:5", "ccompass:5")
minetest.register_alias("compass:6", "ccompass:6")
minetest.register_alias("compass:7", "ccompass:7")
minetest.register_alias("compass:8", "ccompass:8")
minetest.register_alias("compass:9", "ccompass:9")
minetest.register_alias("compass:10", "ccompass:10")
minetest.register_alias("compass:11", "ccompass:11")
end
-- set a position to the compass stack
function ccompass.set_target(stack, param)
param = param or {}
-- param.target_pos_string
-- param.target_name
-- param.playername
local meta=stack:get_meta()
meta:set_string("target_pos", param.target_pos_string)
if param.target_name == "" then
meta:set_string("description", "Compass to "..param.target_pos_string)
else
meta:set_string("description", "Compass to "..param.target_name)
end
if param.playername then
local player = minetest.get_player_by_name(param.playername)
minetest.chat_send_player(param.playername, "Calibration done to "..param.target_name.." "..param.target_pos_string)
minetest.sound_play({ name = "ccompass_calibrate", gain = 1 }, { pos = player:getpos(), max_hear_distance = 3 })
end
end
-- Get compass target
local function get_destination(player, stack)
local posstring = stack:get_meta():get_string("target_pos")
if posstring ~= "" then
return minetest.string_to_pos(posstring)
else
return ccompass.default_target
end
end
function ccompass.is_safe_target(target, nodename)
local node_def = minetest.registered_nodes[nodename]
-- unknown node: not dangerous but probably best treated as one
if not node_def then return false end
-- white-list
if ccompass.nodes_over_target_allow[nodename] then return true end
-- black-list
if ccompass.nodes_over_target_deny[nodename] then return false end
-- damaging node
if not ccompass.allow_damaging_target then
if node_def.damage_per_second and 0 < node_def.damage_per_second then
return false
end
end
-- climbable nodes are ok depending on settings
if ccompass.allow_climbable_target and node_def.climbable then return true end
-- deeper checks
local is_good_draw_type = ccompass.nodes_over_target_allow_drawtypes
if is_good_draw_type[node_def.drawtype] then return true end
-- anything else is assumed dangerous
return false
end
function ccompass.is_safe_target_under(target, nodename)
local node_def = minetest.registered_nodes[nodename]
-- unknown node: not dangerous but probably best treated as one
if not node_def then return false end
-- damaging node
if not ccompass.allow_damaging_target then
if node_def.damage_per_second and 0 < node_def.damage_per_second then
return false
end
end
-- climbable nodes are ok depending on settings
if ccompass.allow_climbable_target and node_def.climbable then return true end
-- solid / walkable
if node_def.walkable then return true end
-- anything else is assumed unsafe
return false
end
local function check_target(cur_target, nodenames_cache)
--check target
local nodename = nodenames_cache[cur_target.y] or minetest.get_node(cur_target).name
if nodename == "ignore" then return false end
nodenames_cache[cur_target.y] = nodename
if ccompass.is_safe_target(cur_target, nodename) then
-- Check under
cur_target.y = cur_target.y - 1
nodename = nodenames_cache[cur_target.y] or minetest.get_node(cur_target).name
if nodename == "ignore" then return false end
nodenames_cache[cur_target.y] = nodename
if ccompass.is_safe_target_under(cur_target, nodename) then
-- Check head
cur_target.y = cur_target.y + 2
nodename = nodenames_cache[cur_target.y] or minetest.get_node(cur_target).name
if nodename == "ignore" then return false end
nodenames_cache[cur_target.y] = nodename
if ccompass.is_safe_target(cur_target, nodename) then
return true
end
end
end
end
local function teleport_above(playername, target, counter)
local player = minetest.get_player_by_name(playername)
if not player then
return
end
local found_place = false
local cur_target = { x = target.x, z = target.z } -- y is handled in loop
local nodenames_cache = {}
for i = (counter or 0), 80 do
-- Search above
cur_target.y = target.y + i
found_place = check_target(cur_target, nodenames_cache)
if found_place == false then
minetest.emerge_area(cur_target, cur_target)
minetest.after(0.1, teleport_above, playername, target, i)
return
elseif found_place == true then
cur_target.y = target.y + i -- reset after check_target
break
end
if i > 0 then
-- Search bellow
cur_target.y = target.y - i
found_place = check_target(cur_target, nodenames_cache)
if found_place == false then
minetest.emerge_area(cur_target, cur_target)
minetest.after(0.1, teleport_above, playername, target, i)
return
elseif found_place == true then
cur_target.y = target.y - i -- reset after check_target
break
end
end
end
if found_place then
player:set_pos(cur_target)
else
minetest.chat_send_player(playername, "Could not find suitable surrounding at target.")
end
end
-- get right image number for players compass
local function get_compass_stack(player, stack)
local target = get_destination(player, stack)
local pos = player:get_pos()
local dir = player:get_look_horizontal()
local angle_north = math.deg(math.atan2(target.x - pos.x, target.z - pos.z))
if angle_north < 0 then
angle_north = angle_north + 360
end
local angle_dir = math.deg(dir)
local angle_relative = (angle_north + angle_dir) % 360
local compass_image = math.floor((angle_relative/22.5) + 0.5)%16
-- create new stack with metadata copied
local metadata = stack:get_meta():to_table()
local newstack = ItemStack("ccompass:"..compass_image.." "..stack:get_count())
if metadata then
newstack:get_meta():from_table(metadata)
end
if ccompass.usage_hook then
newstack = ccompass.usage_hook(newstack, player) or newstack
end
return newstack
end
-- Calibrate compass on pointed_thing
local function on_use_function(itemstack, player, pointed_thing)
-- if using with a bunch together, need to check server preference
if 1 ~= itemstack:get_count() and not ccompass.allow_using_stacks then
minetest.chat_send_player(player:get_player_name(), "Use a single compass.")
return
end
-- possible only on nodes
if pointed_thing.type ~= "node" then --support nodes only for destination
minetest.chat_send_player(player:get_player_name(), "Calibration can be done on nodes only")
return
end
local nodepos = minetest.get_pointed_thing_position(pointed_thing)
local node = minetest.get_node(nodepos)
-- Do teleport to target
if ccompass.teleport_nodes[node.name] then
teleport_above(player:get_player_name(), get_destination(player, itemstack))
return
end
-- recalibration allowed?
if not ccompass.recalibrate then
local destination = itemstack:get_meta():get_string("target_pos")
if destination ~= "" then
minetest.chat_send_player(player:get_player_name(), "Compass already calibrated")
return
end
end
-- target nodes restricted?
if ccompass.restrict_target then
if not ccompass.restrict_target_nodes[node.name] then
minetest.chat_send_player(player:get_player_name(), "Calibration on this node not possible")
return
end
end
-- check if waypoint name set in target node
local nodepos_string = minetest.pos_to_string(nodepos)
local nodemeta = minetest.get_meta(nodepos)
local waypoint_name = nodemeta:get_string("waypoint_name")
local waypoint_pos = nodemeta:get_string("waypoint_pos")
local skip_namechange = nodemeta:get_string("waypoint_skip_namechange")
local itemmeta=itemstack:get_meta()
if waypoint_pos and waypoint_pos ~= "" then
nodepos_string = waypoint_pos
end
if skip_namechange ~= "" then
ccompass.set_target(itemstack, {
target_pos_string = nodepos_string,
target_name = waypoint_name,
playername = player:get_player_name()
})
else
-- show the formspec to player
itemmeta:set_string("tmp_target_pos", nodepos_string) --just save temporary
minetest.show_formspec(player:get_player_name(), "ccompass",
"size[10,2.5]" ..
"field[1,1;8,1;name;Destination name:;"..waypoint_name.."]"..
"button_exit[0.7,2;3,1;cancel;Cancel]"..
"button_exit[3.7,2;5,1;ok;Calibrate]" )
end
return itemstack
end
-- Process the calibration using entered data
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "ccompass" and fields.name and (fields.ok or fields.key_enter) then
local stack=player:get_wielded_item()
local meta=stack:get_meta()
ccompass.set_target(stack, {
target_pos_string = meta:get_string("tmp_target_pos"),
target_name = fields.name,
playername = player:get_player_name()
})
meta:set_string("tmp_target_pos", "")
player:set_wielded_item(stack)
end
end)
-- update inventory
local function do_update_loop()
local found_compasses = false
for i,player in ipairs(minetest.get_connected_players()) do
if player:get_inventory() then
for i,stack in ipairs(player:get_inventory():get_list("main")) do
if i > 8 then
break
end
if string.sub(stack:get_name(), 0, 9) == "ccompass:" then
found_compasses = true
player:get_inventory():set_stack("main", i, get_compass_stack(player, stack))
end
end
end
end
-- if no compasses were found, idle for a time before checking again
local interval = found_compasses and 0 or ccompass.idle_interval
minetest.after(interval, do_update_loop)
end
minetest.after(0, do_update_loop)
-- register items
for i = 0, 15 do
local image = "ccompass_16_"..i..".png"
local groups = {}
if i > 0 then
groups.not_in_creative_inventory = 1
end
local itemname = "ccompass:"..i
minetest.register_craftitem(itemname, {
description = "Compass",
inventory_image = image,
wield_image = image,
stack_max = ccompass.stack_max or 42,
groups = groups,
on_use = on_use_function,
})
-- reset recipe
minetest.register_craft({
type = "shapeless",
output = "ccompass:0",
recipe = { itemname }
})
end
minetest.register_craft({
output = 'ccompass:0',
recipe = {
{'', 'default:steel_ingot', ''},
{'default:steel_ingot', 'default:mese_crystal_fragment', 'default:steel_ingot'},
{'', 'default:steel_ingot', ''}
}
})
-- add an alternative recipe
minetest.register_craft({
output = 'ccompass:0',
recipe = {
{'default:steel_ingot', '', 'default:steel_ingot'},
{'', 'default:mese_crystal_fragment', ''},
{'default:steel_ingot', '', 'default:steel_ingot'}
}
})