-- 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'} } })