mods that hopefully work for a TechAge-based pack
This commit is contained in:
@@ -0,0 +1,532 @@
|
||||
local S = eye_spy.S
|
||||
|
||||
eye_spy.target = {}
|
||||
|
||||
-- Returns the first line of a string, or nil if empty.
|
||||
local function first_line(text)
|
||||
if not text or text == "" then
|
||||
return nil
|
||||
end
|
||||
|
||||
return text:match("^[^\n]*")
|
||||
end
|
||||
|
||||
-- Returns a compact cache key string for a position.
|
||||
local function pos_key(pos)
|
||||
return string.format("%d,%d,%d", pos.x, pos.y, pos.z)
|
||||
end
|
||||
|
||||
-- Extracts the best available texture from a node definition.
|
||||
local function extract_node_texture(def)
|
||||
if not def or not def.tiles then
|
||||
return "eye_spy_default_texture.png"
|
||||
end
|
||||
|
||||
return def.tiles[6] or def.tiles[1] or "eye_spy_default_texture.png"
|
||||
end
|
||||
|
||||
-- Normalises a single tile entry (string or table) to a texture string.
|
||||
local function extract_tile_texture(tile)
|
||||
if type(tile) == "table" then
|
||||
return tile.name or tile.image or ""
|
||||
end
|
||||
|
||||
return type(tile) == "string" and tile or ""
|
||||
end
|
||||
|
||||
-- Extracts the best available texture from an item/node definition.
|
||||
local function extract_item_texture(item_name, def)
|
||||
local item_def = def or (item_name and minetest.registered_items[item_name]) or nil
|
||||
|
||||
if not item_def then
|
||||
return "eye_spy_default_texture.png"
|
||||
end
|
||||
|
||||
if item_def.inventory_image and item_def.inventory_image ~= "" then
|
||||
return item_def.inventory_image
|
||||
end
|
||||
|
||||
if item_def.wield_image and item_def.wield_image ~= "" then
|
||||
return item_def.wield_image
|
||||
end
|
||||
|
||||
if item_def.tiles then
|
||||
local tile = extract_tile_texture(item_def.tiles[1])
|
||||
|
||||
if tile ~= "" then
|
||||
return tile
|
||||
end
|
||||
end
|
||||
|
||||
return "eye_spy_default_texture.png"
|
||||
end
|
||||
|
||||
-- Extracts the first texture from a luaentity definition.
|
||||
local function extract_entity_texture(entity)
|
||||
if not entity then
|
||||
return "eye_spy_default_texture.png"
|
||||
end
|
||||
|
||||
if entity.initial_properties and entity.initial_properties.textures then
|
||||
return entity.initial_properties.textures[1] or "eye_spy_default_texture.png"
|
||||
end
|
||||
|
||||
if entity.textures then
|
||||
return entity.textures[1] or "eye_spy_default_texture.png"
|
||||
end
|
||||
|
||||
return "eye_spy_default_texture.png"
|
||||
end
|
||||
|
||||
-- Attempts to retrieve an ItemStack carried by a dropped-item entity.
|
||||
local function get_entity_itemstack(entity)
|
||||
if not entity then
|
||||
return nil
|
||||
end
|
||||
|
||||
local raw = entity.itemstring or entity._itemstring or entity.item or entity.stackstring
|
||||
local stack
|
||||
|
||||
if type(raw) == "string" and raw ~= "" then
|
||||
stack = ItemStack(raw)
|
||||
elseif raw and type(raw) == "userdata" and raw.get_name then
|
||||
stack = raw
|
||||
end
|
||||
|
||||
if stack and not stack:is_empty() then
|
||||
return stack
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Returns a brief summary of item-stack metadata for display purposes.
|
||||
-- Returns: summary_string, has_meta (bool), has_custom_description (bool)
|
||||
local function summarize_item_meta(stack)
|
||||
if not stack or stack:is_empty() then
|
||||
return nil, false, false
|
||||
end
|
||||
|
||||
local meta = stack:get_meta()
|
||||
|
||||
if not meta then
|
||||
return nil, false, false
|
||||
end
|
||||
|
||||
local custom_description = first_line(meta:get_string("description"))
|
||||
local table_data = meta:to_table() or {}
|
||||
local fields = table_data.fields or {}
|
||||
local field_count = 0
|
||||
|
||||
for key, value in pairs(fields) do
|
||||
if key ~= "description" and value ~= nil and tostring(value) ~= "" then
|
||||
field_count = field_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
local inv_count = 0
|
||||
local inventory = table_data.inventory or {}
|
||||
|
||||
for _, list in pairs(inventory) do
|
||||
if type(list) == "table" and #list > 0 then
|
||||
inv_count = inv_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
local parts = {}
|
||||
|
||||
if field_count > 0 then
|
||||
parts[#parts + 1] = tostring(field_count) .. "f"
|
||||
end
|
||||
|
||||
if inv_count > 0 then
|
||||
parts[#parts + 1] = tostring(inv_count) .. "inv"
|
||||
end
|
||||
|
||||
local has_meta = #parts > 0
|
||||
|
||||
return table.concat(parts, ", "), has_meta, custom_description ~= nil and custom_description ~= ""
|
||||
end
|
||||
|
||||
-- Builds a target table for a dropped-item entity, or returns nil if not applicable.
|
||||
local function get_dropped_item_target(ref, entity)
|
||||
local stack = get_entity_itemstack(entity)
|
||||
|
||||
if not stack then
|
||||
return nil
|
||||
end
|
||||
|
||||
local item_name = stack:get_name()
|
||||
|
||||
if item_name == "" then
|
||||
return nil
|
||||
end
|
||||
|
||||
local item_def = minetest.registered_items[item_name] or {}
|
||||
local entity_pos = ref and ref:get_pos() or { x = 0, y = 0, z = 0 }
|
||||
local count = stack:get_count() or 1
|
||||
local meta_summary, has_meta, has_custom_description = summarize_item_meta(stack)
|
||||
local custom_description = has_custom_description and first_line(stack:get_meta():get_string("description")) or nil
|
||||
local stack_description = nil
|
||||
|
||||
if stack.get_short_description then
|
||||
stack_description = first_line(stack:get_short_description())
|
||||
end
|
||||
|
||||
if not stack_description and stack.get_description then
|
||||
stack_description = first_line(stack:get_description())
|
||||
end
|
||||
|
||||
local base_description = custom_description
|
||||
or stack_description
|
||||
or first_line(item_def.short_description)
|
||||
or first_line(item_def.description)
|
||||
or eye_spy.readable_name_from_id(item_name)
|
||||
or S("Unknown")
|
||||
local description = count > 1 and (base_description .. " x" .. count) or base_description
|
||||
|
||||
return {
|
||||
kind = "item",
|
||||
key = "item:" .. item_name .. ":" .. count .. ":" .. tostring(ref),
|
||||
name = item_name,
|
||||
full_name = item_name,
|
||||
description = description,
|
||||
modname = eye_spy.get_modname_from_obj(item_name) or S("Unknown"),
|
||||
pos = vector.new(entity_pos),
|
||||
ref = ref,
|
||||
stack = stack,
|
||||
stack_count = count,
|
||||
stack_wear = stack:get_wear() or 0,
|
||||
stack_has_meta = has_meta,
|
||||
stack_meta_summary = meta_summary,
|
||||
stack_has_custom_description = has_custom_description,
|
||||
texture = extract_item_texture(item_name, item_def),
|
||||
mesh = nil,
|
||||
light_level = nil,
|
||||
item_def_description = item_def.short_description or item_def.description or "",
|
||||
}
|
||||
end
|
||||
|
||||
-- Returns positions to sample when determining a node's ambient light level.
|
||||
local function get_light_sample_positions(pos, def)
|
||||
local positions = {
|
||||
{ x = pos.x, y = pos.y, z = pos.z },
|
||||
{ x = pos.x, y = pos.y + 1, z = pos.z },
|
||||
{ x = pos.x + 1, y = pos.y, z = pos.z },
|
||||
{ x = pos.x - 1, y = pos.y, z = pos.z },
|
||||
{ x = pos.x, y = pos.y, z = pos.z + 1 },
|
||||
{ x = pos.x, y = pos.y, z = pos.z - 1 },
|
||||
}
|
||||
|
||||
-- For non-walkable nodes also sample below (e.g. torches, plants).
|
||||
if def and not def.walkable then
|
||||
positions[#positions + 1] = { x = pos.x, y = pos.y - 1, z = pos.z }
|
||||
end
|
||||
|
||||
return positions
|
||||
end
|
||||
|
||||
-- Microsecond TTL for the light-sample cache.
|
||||
local LIGHT_CACHE_TTL_US = 200000
|
||||
local light_sample_cache = {}
|
||||
local light_sample_cache_size = 0
|
||||
|
||||
-- Returns the highest light value among all sampled neighbour positions.
|
||||
local function get_best_node_light(pos, def)
|
||||
local best_light
|
||||
|
||||
for _, sample_pos in ipairs(get_light_sample_positions(pos, def)) do
|
||||
local light = minetest.get_node_light(sample_pos)
|
||||
|
||||
if light ~= nil and (best_light == nil or light > best_light) then
|
||||
best_light = light
|
||||
end
|
||||
end
|
||||
|
||||
return best_light
|
||||
end
|
||||
|
||||
-- Cached wrapper around get_best_node_light; evicts the cache when it grows
|
||||
-- beyond 1 024 entries.
|
||||
local function get_cached_node_light(pos, def)
|
||||
local now_us = minetest.get_us_time and minetest.get_us_time() or 0
|
||||
|
||||
if now_us <= 0 then
|
||||
return get_best_node_light(pos, def)
|
||||
end
|
||||
|
||||
local cache_key = pos_key(pos) .. ":" .. ((def and def.walkable) and "w" or "nw")
|
||||
local cached = light_sample_cache[cache_key]
|
||||
|
||||
if cached and (now_us - cached.time_us) <= LIGHT_CACHE_TTL_US then
|
||||
return cached.value
|
||||
end
|
||||
|
||||
local sampled = get_best_node_light(pos, def)
|
||||
|
||||
if light_sample_cache_size > 1024 then
|
||||
light_sample_cache = {}
|
||||
light_sample_cache_size = 0
|
||||
end
|
||||
|
||||
if not light_sample_cache[cache_key] then
|
||||
light_sample_cache_size = light_sample_cache_size + 1
|
||||
end
|
||||
|
||||
light_sample_cache[cache_key] = {
|
||||
value = sampled,
|
||||
time_us = now_us,
|
||||
}
|
||||
|
||||
return sampled
|
||||
end
|
||||
|
||||
-- Returns the current HP of an entity, preferring the luaentity field over the
|
||||
-- object method.
|
||||
local function get_entity_hp(ref, entity)
|
||||
local hp = entity and (entity.health or entity.hp)
|
||||
|
||||
if hp == nil and ref then
|
||||
hp = ref:get_hp()
|
||||
end
|
||||
|
||||
return hp
|
||||
end
|
||||
|
||||
-- Fix 3: Returns the maximum HP of an entity.
|
||||
-- Checks luaentity fields first, then the Minetest 5.8+ get_max_hp() API,
|
||||
-- and only falls back to current HP as a last resort.
|
||||
local function get_entity_max_hp(ref, entity)
|
||||
if entity then
|
||||
local m = entity.max_health or entity.max_hp or entity.hp_max
|
||||
if m then return m end
|
||||
end
|
||||
|
||||
-- Use get_max_hp() if available (Minetest 5.8+)
|
||||
if ref and ref.get_max_hp then
|
||||
local max_hp = ref:get_max_hp()
|
||||
if max_hp and max_hp > 0 then return max_hp end
|
||||
end
|
||||
|
||||
if ref then
|
||||
return ref:get_hp() -- last resort: current HP
|
||||
end
|
||||
end
|
||||
|
||||
-- Fix 4: Returns a synthetic target table used for UI preview rendering.
|
||||
-- When preview_type == "Node" a node target is returned; otherwise an entity
|
||||
-- target is returned (this was previously inverted).
|
||||
function eye_spy.target.get_preview(preview_type)
|
||||
if preview_type == "Node" then
|
||||
local def = minetest.registered_nodes["eye_spy:preview_node"]
|
||||
|
||||
return {
|
||||
kind = "node",
|
||||
key = "preview:node",
|
||||
name = "eye_spy:preview_node",
|
||||
full_name = "eye_spy:preview_node",
|
||||
description = def and def.description or S("Preview") .. " " .. S("Node"),
|
||||
modname = "eye_spy",
|
||||
pos = { x = 0, y = 0, z = 0 },
|
||||
def = def,
|
||||
texture = def and def.tiles and def.tiles[1] or "eye_spy_default_texture.png",
|
||||
mesh = def and def.mesh or "eye_spy_default_mesh.obj",
|
||||
color = def and def.color,
|
||||
palette = def and def.palette,
|
||||
light_level = 15,
|
||||
is_liquid = false,
|
||||
}
|
||||
end
|
||||
|
||||
-- Default (including "Entity"): return an entity preview target.
|
||||
local entity_def = minetest.registered_entities["eye_spy:preview_entity"]
|
||||
local props = entity_def and entity_def.initial_properties or {}
|
||||
|
||||
return {
|
||||
kind = "entity",
|
||||
key = "preview:entity",
|
||||
name = "eye_spy:preview_entity",
|
||||
full_name = "eye_spy:preview_entity",
|
||||
description = props.description or S("Preview") .. " " .. S("Entity"),
|
||||
modname = "eye_spy",
|
||||
pos = { x = 0, y = 0, z = 0 },
|
||||
texture = props.textures and props.textures[1] or "eye_spy_default_texture.png",
|
||||
mesh = props.mesh or "eye_spy_default_mesh.obj",
|
||||
hp = props.health or props.hp or 8,
|
||||
max_hp = props.max_health or props.max_hp or props.hp_max or 10,
|
||||
light_level = 15,
|
||||
}
|
||||
end
|
||||
|
||||
-- Casts a ray from the player's eye and returns a target table describing
|
||||
-- whatever the player is looking at (node, entity, dropped item, or air).
|
||||
function eye_spy.target.acquire(player, opts)
|
||||
local options = opts or {}
|
||||
|
||||
if options.preview then
|
||||
return eye_spy.target.get_preview(options.preview_type)
|
||||
end
|
||||
|
||||
local pos = player:get_pos()
|
||||
local meta = player and player:get_meta() or nil
|
||||
|
||||
-- Fix 1: (meta and X) or true is always true in Lua.
|
||||
-- Correct form: absent meta means show everything; present meta is checked.
|
||||
local show_light_level = (not meta) or (meta:get_string("es_show_light_level") ~= "false")
|
||||
local show_spawn_hint = (not meta) or (meta:get_string("es_show_spawn_hint") ~= "false")
|
||||
local show_liquid_info = (not meta) or (meta:get_string("es_show_liquid_info") ~= "false")
|
||||
|
||||
local need_node_light = show_light_level or show_spawn_hint
|
||||
|
||||
local properties = player:get_properties()
|
||||
local eye_height = properties.eye_height or 1.625
|
||||
|
||||
-- Fix 5: delegate range calculation to the shared helper in tools.lua.
|
||||
local range = eye_spy.get_player_range(player)
|
||||
|
||||
pos.y = pos.y + eye_height
|
||||
|
||||
local dir = player:get_look_dir()
|
||||
local start = vector.add(pos, vector.multiply(dir, 0.2))
|
||||
local finish = vector.add(start, vector.multiply(dir, range))
|
||||
local allow_liquids = eye_spy.config.show_liquids == true and show_liquid_info
|
||||
local ray = minetest.raycast(start, finish, eye_spy.config.show_entities, allow_liquids)
|
||||
local first_liquid = nil
|
||||
local primary_target = nil
|
||||
|
||||
-- Builds a complete node target table.
|
||||
local function make_node_target(node, under_pos, def, is_liquid, liquid_type, liquid_level)
|
||||
return {
|
||||
kind = "node",
|
||||
key = "node:" .. node.name .. "@" .. pos_key(under_pos),
|
||||
name = node.name,
|
||||
full_name = node.name,
|
||||
description = first_line(def and def.description)
|
||||
or eye_spy.readable_name_from_id(node.name)
|
||||
or S("Unknown"),
|
||||
modname = eye_spy.get_modname_from_obj(node.name) or S("Unknown"),
|
||||
pos = vector.new(under_pos),
|
||||
def = def,
|
||||
texture = extract_node_texture(def),
|
||||
mesh = def and def.mesh or "eye_spy_default_mesh.obj",
|
||||
color = def and def.color,
|
||||
palette = def and def.palette,
|
||||
light_level = need_node_light and get_cached_node_light(under_pos, def) or nil,
|
||||
is_liquid = is_liquid,
|
||||
liquid_type = liquid_type,
|
||||
liquid_level = liquid_level,
|
||||
behind_liquid = false,
|
||||
liquid_obstacle = nil,
|
||||
}
|
||||
end
|
||||
|
||||
for hit in ray do
|
||||
if hit.type == "node" then
|
||||
local node = minetest.get_node(hit.under)
|
||||
|
||||
if node.name ~= "air" then
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
local liquid_type = def and def.liquidtype or "none"
|
||||
local is_liquid = liquid_type ~= "none"
|
||||
local liquid_level
|
||||
|
||||
if is_liquid then
|
||||
if liquid_type == "source" then
|
||||
liquid_level = 8
|
||||
else
|
||||
liquid_level = ((node.param2 or 0) % 8) + 1
|
||||
end
|
||||
|
||||
if not first_liquid then
|
||||
first_liquid = {
|
||||
name = node.name,
|
||||
full_name = node.name,
|
||||
pos = vector.new(hit.under),
|
||||
liquid_type = liquid_type,
|
||||
liquid_level = liquid_level,
|
||||
}
|
||||
end
|
||||
else
|
||||
primary_target = make_node_target(node, hit.under, def, false, liquid_type, liquid_level)
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif hit.type == "object" and hit.ref and not hit.ref:is_player() then
|
||||
local entity = hit.ref:get_luaentity()
|
||||
local dropped_item = get_dropped_item_target(hit.ref, entity)
|
||||
|
||||
if dropped_item then
|
||||
dropped_item.behind_liquid = false
|
||||
dropped_item.liquid_obstacle = nil
|
||||
primary_target = dropped_item
|
||||
break
|
||||
end
|
||||
|
||||
local entity_name = entity and entity.name or "unknown"
|
||||
local entity_pos = hit.ref:get_pos() or { x = 0, y = 0, z = 0 }
|
||||
|
||||
primary_target = {
|
||||
kind = "entity",
|
||||
key = "entity:" .. entity_name .. ":" .. tostring(hit.ref),
|
||||
name = entity_name,
|
||||
full_name = entity_name,
|
||||
description = first_line(entity and entity.description)
|
||||
or eye_spy.readable_name_from_id(entity_name)
|
||||
or S("Unknown"),
|
||||
modname = eye_spy.get_modname_from_obj(entity_name) or S("Unknown"),
|
||||
pos = vector.new(entity_pos),
|
||||
ref = hit.ref,
|
||||
texture = extract_entity_texture(entity),
|
||||
mesh = entity and entity.mesh or "eye_spy_default_mesh.obj",
|
||||
hp = get_entity_hp(hit.ref, entity),
|
||||
max_hp = get_entity_max_hp(hit.ref, entity),
|
||||
light_level = nil,
|
||||
behind_liquid = false,
|
||||
liquid_obstacle = nil,
|
||||
}
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if primary_target then
|
||||
if first_liquid then
|
||||
primary_target.behind_liquid = true
|
||||
primary_target.liquid_obstacle = first_liquid
|
||||
end
|
||||
|
||||
return primary_target
|
||||
end
|
||||
|
||||
-- No solid target found — return the liquid itself if one was encountered.
|
||||
if first_liquid then
|
||||
local node_name = first_liquid.name
|
||||
local node_pos = first_liquid.pos
|
||||
local def = minetest.registered_nodes[node_name]
|
||||
|
||||
return {
|
||||
kind = "node",
|
||||
key = "node:" .. node_name .. "@" .. pos_key(node_pos),
|
||||
name = node_name,
|
||||
full_name = node_name,
|
||||
description = first_line(def and def.description)
|
||||
or eye_spy.readable_name_from_id(node_name)
|
||||
or S("Unknown"),
|
||||
modname = eye_spy.get_modname_from_obj(node_name) or S("Unknown"),
|
||||
pos = vector.new(node_pos),
|
||||
def = def,
|
||||
texture = extract_node_texture(def),
|
||||
mesh = def and def.mesh or "eye_spy_default_mesh.obj",
|
||||
color = def and def.color,
|
||||
palette = def and def.palette,
|
||||
light_level = need_node_light and get_cached_node_light(node_pos, def) or nil,
|
||||
is_liquid = true,
|
||||
liquid_type = first_liquid.liquid_type,
|
||||
liquid_level = first_liquid.liquid_level,
|
||||
behind_liquid = false,
|
||||
liquid_obstacle = nil,
|
||||
}
|
||||
end
|
||||
|
||||
return { kind = "air", key = "air", behind_liquid = false, liquid_obstacle = nil }
|
||||
end
|
||||
Reference in New Issue
Block a user