854 lines
25 KiB
Lua
854 lines
25 KiB
Lua
local S = eye_spy.S
|
|
|
|
eye_spy.enrichers = {
|
|
registry = {},
|
|
}
|
|
|
|
local node_tool_state_by_player = {}
|
|
|
|
-- Called by init.lua's on_leaveplayer handler to free per-player cached state.
|
|
function eye_spy.enrichers.on_player_leave(player_name)
|
|
node_tool_state_by_player[player_name] = nil
|
|
end
|
|
|
|
local function add_line(view_model, id, text, color)
|
|
view_model.lines[#view_model.lines + 1] = {
|
|
id = id,
|
|
text = text,
|
|
color = color,
|
|
}
|
|
end
|
|
|
|
local function meta_bool(meta, key, default_value)
|
|
local raw = meta:get_string(key)
|
|
|
|
if raw == "" then
|
|
return default_value == true
|
|
end
|
|
|
|
return raw ~= "false"
|
|
end
|
|
|
|
local function growth_bar(percent)
|
|
local slots = 10
|
|
local p = math.max(0, math.min(100, percent or 0))
|
|
local filled = math.floor((p / 100) * slots + 0.5)
|
|
|
|
return "[" .. string.rep("|", filled) .. string.rep(" ", slots - filled) .. "] " .. p .. "%"
|
|
end
|
|
|
|
local function liquid_name_from_node(name)
|
|
if not name or name == "" then
|
|
return S("Unknown")
|
|
end
|
|
|
|
if name:find("water", 1, true) then
|
|
return S("Water")
|
|
end
|
|
|
|
if name:find("lava", 1, true) then
|
|
return S("Lava")
|
|
end
|
|
|
|
return eye_spy.readable_name_from_id(name) or S("Unknown")
|
|
end
|
|
|
|
local ETHEREAL_SAPLING_SOILS = {
|
|
["ethereal:basandra_bush_sapling"] = "ethereal:fiery_dirt",
|
|
["ethereal:yellow_tree_sapling"] = "group:soil",
|
|
["ethereal:big_tree_sapling"] = "default:dirt_with_grass",
|
|
["ethereal:banana_tree_sapling"] = "ethereal:grove_dirt",
|
|
["ethereal:frost_tree_sapling"] = "ethereal:crystal_dirt",
|
|
["ethereal:mushroom_sapling"] = "ethereal:mushroom_dirt",
|
|
["ethereal:mushroom_brown_sapling"] = "ethereal:mushroom_dirt",
|
|
["ethereal:palm_sapling"] = "default:sand",
|
|
["ethereal:willow_sapling"] = "ethereal:gray_dirt",
|
|
["ethereal:redwood_sapling"] = "default:dirt_with_dry_grass",
|
|
["ethereal:giant_redwood_sapling"] = "default:dirt_with_dry_grass",
|
|
["ethereal:orange_tree_sapling"] = "ethereal:prairie_dirt",
|
|
["ethereal:birch_sapling"] = "default:dirt_with_grass",
|
|
["ethereal:sakura_sapling"] = "ethereal:bamboo_dirt",
|
|
["ethereal:olive_tree_sapling"] = "ethereal:grove_dirt",
|
|
["ethereal:lemon_tree_sapling"] = "ethereal:grove_dirt",
|
|
}
|
|
|
|
local function get_current_dig_result(player, nodename)
|
|
if not player or not nodename or nodename == "" then
|
|
return false, nil
|
|
end
|
|
|
|
local wielded = player:get_wielded_item()
|
|
local can_dig, dig_time = eye_spy.tool_can_dig_node(wielded, nodename)
|
|
|
|
if can_dig then
|
|
return true, dig_time
|
|
end
|
|
|
|
local hand_can_dig, hand_dig_time = eye_spy.tool_can_dig_node(ItemStack(""), nodename)
|
|
|
|
if hand_can_dig then
|
|
return true, hand_dig_time
|
|
end
|
|
|
|
return false, nil
|
|
end
|
|
|
|
local function get_below_node(pos)
|
|
if not pos then
|
|
return nil, nil
|
|
end
|
|
|
|
local below_pos = { x = pos.x, y = pos.y - 1, z = pos.z }
|
|
local below = minetest.get_node_or_nil(below_pos)
|
|
|
|
if not below then
|
|
return nil, nil
|
|
end
|
|
|
|
return below, minetest.registered_nodes[below.name]
|
|
end
|
|
|
|
local function is_water_node(node, def)
|
|
local name = node and node.name or ""
|
|
|
|
if name == "" then
|
|
return false
|
|
end
|
|
|
|
if name:find("water", 1, true) then
|
|
return true
|
|
end
|
|
|
|
local source_name = def and def.liquid_alternative_source or ""
|
|
local flowing_name = def and def.liquid_alternative_flowing or ""
|
|
|
|
return source_name:find("water", 1, true) ~= nil or flowing_name:find("water", 1, true) ~= nil
|
|
end
|
|
|
|
local function has_nearby_water(pos, radius)
|
|
if not pos then
|
|
return false
|
|
end
|
|
|
|
local scan_radius = math.max(1, tonumber(radius) or 3)
|
|
|
|
for dx = -scan_radius, scan_radius do
|
|
for dz = -scan_radius, scan_radius do
|
|
for dy = -1, 1 do
|
|
if dx ~= 0 or dy ~= 0 or dz ~= 0 then
|
|
local neighbor = minetest.get_node_or_nil({
|
|
x = pos.x + dx,
|
|
y = pos.y + dy,
|
|
z = pos.z + dz,
|
|
})
|
|
|
|
if neighbor then
|
|
local neighbor_def = minetest.registered_nodes[neighbor.name]
|
|
|
|
if is_water_node(neighbor, neighbor_def) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function is_soil_wet(soil_pos, soil_group)
|
|
if (tonumber(soil_group) or 0) >= 3 then
|
|
return true
|
|
end
|
|
|
|
return has_nearby_water(soil_pos, 3)
|
|
end
|
|
|
|
local function get_node_label(name)
|
|
if not name or name == "" then
|
|
return S("Unknown")
|
|
end
|
|
|
|
if name:sub(1, 6) == "group:" then
|
|
local group_name = name:sub(7)
|
|
|
|
if group_name == "soil" then
|
|
return S("Soil")
|
|
end
|
|
|
|
return group_name
|
|
end
|
|
|
|
local def = minetest.registered_nodes[name]
|
|
local desc = def and def.description
|
|
|
|
if desc and desc ~= "" then
|
|
return desc:match("^[^\n]*") or desc
|
|
end
|
|
|
|
return name
|
|
end
|
|
|
|
local function get_soil_state(pos)
|
|
local below, below_def = get_below_node(pos)
|
|
local soil_group = below and minetest.get_item_group(below.name, "soil") or 0
|
|
local below_pos = pos and { x = pos.x, y = pos.y - 1, z = pos.z } or nil
|
|
local is_wet = soil_group > 0 and is_soil_wet(below_pos, soil_group)
|
|
|
|
return {
|
|
node = below,
|
|
def = below_def,
|
|
soil_group = soil_group,
|
|
is_soil = soil_group > 0,
|
|
is_wet = is_wet,
|
|
}
|
|
end
|
|
|
|
local function get_target_soil_state(target)
|
|
local soil_group = minetest.get_item_group(target.name or "", "soil")
|
|
|
|
if soil_group <= 0 then
|
|
return nil
|
|
end
|
|
|
|
return {
|
|
node = {
|
|
name = target.name,
|
|
param1 = target.node and target.node.param1 or 0,
|
|
param2 = target.node and target.node.param2 or 0,
|
|
},
|
|
def = target.def,
|
|
soil_group = soil_group,
|
|
is_soil = true,
|
|
is_wet = is_soil_wet(target.pos, soil_group),
|
|
}
|
|
end
|
|
|
|
local function infer_max_stage(base_name, current_stage)
|
|
local max_stage = current_stage or 0
|
|
|
|
for stage = max_stage + 1, 16 do
|
|
if minetest.registered_nodes[base_name .. "_" .. stage] then
|
|
max_stage = stage
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
if max_stage > 0 then
|
|
return max_stage
|
|
end
|
|
end
|
|
|
|
local function get_registered_crop_info(base_name)
|
|
if farming and farming.registered_plants and farming.registered_plants[base_name] then
|
|
local plant = farming.registered_plants[base_name]
|
|
|
|
return {
|
|
system = "farming",
|
|
steps = plant.steps,
|
|
minlight = plant.minlight or (farming and farming.min_light) or 12,
|
|
maxlight = plant.maxlight or (farming and farming.max_light) or 15,
|
|
}
|
|
end
|
|
|
|
if x_farming and x_farming.registered_plants then
|
|
local short_name = base_name:match(":(.+)$")
|
|
local plant = short_name and x_farming.registered_plants[short_name]
|
|
|
|
if plant then
|
|
return {
|
|
system = "x_farming",
|
|
steps = plant.steps,
|
|
minlight = plant.minlight or 13,
|
|
maxlight = plant.maxlight or 14,
|
|
fertility = plant.fertility,
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
local function soil_matches_fertility(soil_state, fertility)
|
|
if not fertility or #fertility == 0 then
|
|
return true
|
|
end
|
|
|
|
local below_def = soil_state.def
|
|
local groups = below_def and below_def.groups or nil
|
|
|
|
if not groups then
|
|
return false
|
|
end
|
|
|
|
for _, group_name in ipairs(fertility) do
|
|
if groups[group_name] and groups[group_name] > 0 then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function get_growth_light(pos)
|
|
if not pos then
|
|
return nil
|
|
end
|
|
|
|
return minetest.get_node_light(pos)
|
|
end
|
|
|
|
local function get_growth_info(target)
|
|
local def = target.def
|
|
local groups = def and def.groups or {}
|
|
|
|
if groups.sapling then
|
|
local meta = minetest.get_meta(target.pos)
|
|
|
|
return {
|
|
kind = "sapling",
|
|
age = meta and meta:get_int("age") or 0,
|
|
groups = groups,
|
|
}
|
|
end
|
|
|
|
local stage_suffix = target.name:match("_(%d+)$")
|
|
local base_name = stage_suffix and target.name:gsub("_(%d+)$", "", 1) or nil
|
|
local plant = base_name and get_registered_crop_info(base_name) or nil
|
|
|
|
if not stage_suffix or not (groups.plant or groups.crop or groups.growing or plant) then
|
|
return nil
|
|
end
|
|
|
|
local stage = tonumber(stage_suffix) or 0
|
|
plant = plant or {}
|
|
local max_stage = plant.steps or infer_max_stage(base_name, stage)
|
|
|
|
if not max_stage or max_stage <= 0 then
|
|
return nil
|
|
end
|
|
|
|
return {
|
|
kind = "crop",
|
|
system = plant.system or "generic",
|
|
stage = stage,
|
|
max_stage = max_stage,
|
|
minlight = plant.minlight or def.minlight,
|
|
maxlight = plant.maxlight or def.maxlight,
|
|
fertility = plant.fertility,
|
|
}
|
|
end
|
|
|
|
local function get_sapling_soil_requirement(target)
|
|
local requirement = ETHEREAL_SAPLING_SOILS[target.name]
|
|
|
|
if requirement then
|
|
return requirement
|
|
end
|
|
|
|
return "group:soil"
|
|
end
|
|
|
|
local function soil_matches_requirement(soil_state, requirement)
|
|
if not requirement or requirement == "" then
|
|
return true
|
|
end
|
|
|
|
if requirement:sub(1, 6) == "group:" then
|
|
return minetest.get_item_group(soil_state.node and soil_state.node.name or "", requirement:sub(7)) > 0
|
|
end
|
|
|
|
return soil_state.node and soil_state.node.name == requirement or false
|
|
end
|
|
|
|
local function get_crop_conditions(target, growth_info)
|
|
local soil_state = get_soil_state(target.pos)
|
|
local light = target.light_level
|
|
|
|
if light == nil then
|
|
light = get_growth_light(target.pos)
|
|
end
|
|
|
|
local minlight = growth_info.minlight
|
|
local maxlight = growth_info.maxlight
|
|
local fertility_ok = soil_matches_fertility(soil_state, growth_info.fertility)
|
|
local soil_ok = soil_state.is_wet and fertility_ok
|
|
local light_ok = true
|
|
|
|
if minlight and light and light < minlight then
|
|
light_ok = false
|
|
end
|
|
|
|
if maxlight and light and light > maxlight then
|
|
light_ok = false
|
|
end
|
|
|
|
if light == nil and (minlight or maxlight) then
|
|
light_ok = false
|
|
end
|
|
|
|
return {
|
|
soil = soil_state,
|
|
light = light,
|
|
soil_ok = soil_ok,
|
|
light_ok = light_ok,
|
|
fertility_ok = fertility_ok,
|
|
}
|
|
end
|
|
|
|
local function get_soil_line(target, growth_info)
|
|
local soil_state = get_soil_state(target.pos)
|
|
local target_groups = target.def and target.def.groups or {}
|
|
|
|
if growth_info and growth_info.kind == "crop" then
|
|
local suitable = soil_state.is_soil and soil_matches_fertility(soil_state, growth_info.fertility)
|
|
local moisture_label
|
|
|
|
if soil_state.is_wet then
|
|
moisture_label = S("Wet")
|
|
elseif soil_state.is_soil then
|
|
moisture_label = S("Dry")
|
|
else
|
|
moisture_label = S("No Soil")
|
|
end
|
|
|
|
local color
|
|
|
|
if suitable and soil_state.is_wet then
|
|
color = 0x6BD66B
|
|
elseif suitable then
|
|
color = 0xFFAA55
|
|
else
|
|
color = 0xE56363
|
|
end
|
|
|
|
return {
|
|
text = S("Soil") .. ": " .. moisture_label .. " | " .. (suitable and S("Suitable") or S("Unsuitable")),
|
|
color = color,
|
|
}
|
|
end
|
|
|
|
if growth_info and growth_info.kind == "sapling" then
|
|
local requirement = get_sapling_soil_requirement(target)
|
|
local suitable = soil_matches_requirement(soil_state, requirement)
|
|
|
|
return {
|
|
text = S("Soil") ..
|
|
": " .. get_node_label(requirement) .. " | " .. (suitable and S("Suitable") or S("Unsuitable")),
|
|
color = suitable and 0x6BD66B or 0xE56363,
|
|
}
|
|
end
|
|
|
|
if target_groups.flora or target_groups.attached_node then
|
|
return nil
|
|
end
|
|
|
|
local target_soil_state = get_target_soil_state(target)
|
|
|
|
if target_soil_state then
|
|
return {
|
|
text = S("Soil") .. ": " .. (target_soil_state.is_wet and S("Wet") or S("Dry")),
|
|
color = target_soil_state.is_wet and 0x5555FF or 0xFFAA55,
|
|
}
|
|
end
|
|
end
|
|
|
|
function eye_spy.enrichers.register(id, spec)
|
|
eye_spy.enrichers.registry[#eye_spy.enrichers.registry + 1] = {
|
|
id = id,
|
|
enabled = spec.enabled,
|
|
apply = spec.apply,
|
|
}
|
|
end
|
|
|
|
local function ensure_shared_context(context, target)
|
|
local shared = context.shared
|
|
|
|
if shared and shared._target_key == (target.key or target.name) then
|
|
return shared
|
|
end
|
|
|
|
shared = {
|
|
_target_key = target.key or target.name,
|
|
dig_cached = false,
|
|
growth_cached = false,
|
|
conditions_cached = false,
|
|
soil_line_cached = false,
|
|
}
|
|
|
|
function shared:get_dig_result()
|
|
if not self.dig_cached then
|
|
self.dig_cached = true
|
|
self.can_dig, self.dig_time = get_current_dig_result(context.player, target.name)
|
|
end
|
|
|
|
return self.can_dig, self.dig_time
|
|
end
|
|
|
|
function shared:get_growth_info()
|
|
if not self.growth_cached then
|
|
self.growth_cached = true
|
|
self.growth_info = get_growth_info(target)
|
|
end
|
|
|
|
return self.growth_info
|
|
end
|
|
|
|
function shared:get_crop_conditions()
|
|
if not self.conditions_cached then
|
|
self.conditions_cached = true
|
|
local growth_info = self:get_growth_info()
|
|
|
|
if growth_info and growth_info.kind == "crop" then
|
|
self.crop_conditions = get_crop_conditions(target, growth_info)
|
|
else
|
|
self.crop_conditions = nil
|
|
end
|
|
end
|
|
|
|
return self.crop_conditions
|
|
end
|
|
|
|
function shared:get_soil_line()
|
|
if not self.soil_line_cached then
|
|
self.soil_line_cached = true
|
|
self.soil_line = get_soil_line(target, self:get_growth_info())
|
|
end
|
|
|
|
return self.soil_line
|
|
end
|
|
|
|
context.shared = shared
|
|
return shared
|
|
end
|
|
|
|
local function get_cached_node_tool(context, target)
|
|
local player = context and context.player
|
|
|
|
if not player then
|
|
return nil
|
|
end
|
|
|
|
local shared = ensure_shared_context(context, target)
|
|
local player_name = player:get_player_name()
|
|
local wielded = player:get_wielded_item()
|
|
local wield_name = wielded:get_name() or ""
|
|
local target_key = target.key or target.name or ""
|
|
local cached = node_tool_state_by_player[player_name]
|
|
|
|
if cached and cached.target_key == target_key and cached.wield_name == wield_name then
|
|
return cached.tool
|
|
end
|
|
|
|
local can_dig = shared:get_dig_result()
|
|
local tool_type_hint, matches_tool_type = nil, false
|
|
|
|
if eye_spy.get_node_tool_hint_and_match then
|
|
tool_type_hint, matches_tool_type = eye_spy.get_node_tool_hint_and_match(target.name, wielded)
|
|
end
|
|
|
|
local label = tool_type_hint or S("Unknown")
|
|
local color
|
|
|
|
if can_dig then
|
|
color = 0x6BD66B
|
|
elseif matches_tool_type then
|
|
color = 0xFFAA55
|
|
else
|
|
color = 0xE56363
|
|
end
|
|
|
|
local tool = {
|
|
description = label,
|
|
color = color,
|
|
}
|
|
|
|
node_tool_state_by_player[player_name] = {
|
|
target_key = target_key,
|
|
wield_name = wield_name,
|
|
tool = tool,
|
|
}
|
|
|
|
return tool
|
|
end
|
|
|
|
function eye_spy.enrichers.run(context, target, view_model)
|
|
ensure_shared_context(context, target)
|
|
|
|
for _, enricher in ipairs(eye_spy.enrichers.registry) do
|
|
if not enricher.enabled or enricher.enabled(context, target, view_model) then
|
|
local perf_on = eye_spy.perf and eye_spy.perf.enabled and minetest.get_us_time
|
|
local start_us = perf_on and minetest.get_us_time() or 0
|
|
|
|
local ok, err = pcall(enricher.apply, context, target, view_model)
|
|
if not ok then
|
|
minetest.log("error", "[eye_spy] enricher '" .. (enricher.id or "?") ..
|
|
"' error: " .. tostring(err))
|
|
end
|
|
|
|
if perf_on then
|
|
eye_spy.perf_record("enricher_" .. (enricher.id or "unknown"), minetest.get_us_time() - start_us)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
eye_spy.enrichers.register("node_tool", {
|
|
enabled = function(_, target)
|
|
return target.kind == "node"
|
|
end,
|
|
apply = function(context, target, view_model)
|
|
local tool = get_cached_node_tool(context, target) or {
|
|
description = S("Unknown"),
|
|
color = 0xEBB344,
|
|
}
|
|
|
|
view_model.subtitle = tool.description ~= "" and tool.description or S("Unknown")
|
|
view_model.subtitle_color = tool.color or 0xEBB344
|
|
end,
|
|
})
|
|
|
|
eye_spy.enrichers.register("entity_health", {
|
|
enabled = function(_, target)
|
|
return target.kind == "entity"
|
|
end,
|
|
apply = function(context, target, view_model)
|
|
if context.meta:get_string("es_hud_health_in") == "Hearts" then
|
|
view_model.subtitle = S("Health") ..
|
|
": " ..
|
|
(eye_spy.round1((target.hp or 0) / 2) or S("Unknown")) ..
|
|
"♥/" .. (eye_spy.round1((target.max_hp or 0) / 2) or S("Unknown")) .. "♥"
|
|
else
|
|
view_model.subtitle = S("Health") ..
|
|
": " .. (eye_spy.round1(target.hp) or S("Unknown")) .. "/" .. (eye_spy.round1(target.max_hp) or S("Unknown"))
|
|
end
|
|
|
|
view_model.subtitle_color = 0xFF4040
|
|
end,
|
|
})
|
|
|
|
eye_spy.enrichers.register("item_details", {
|
|
enabled = function(_, target)
|
|
return target.kind == "item"
|
|
end,
|
|
apply = function(_, target, view_model)
|
|
local count = tonumber(target.stack_count) or 1
|
|
|
|
if count > 1 then
|
|
add_line(view_model, "item_count", S("Count") .. ": " .. tostring(count), 0xE8D37A)
|
|
end
|
|
|
|
local wear = tonumber(target.stack_wear) or 0
|
|
|
|
if wear > 0 then
|
|
local remain_pct = math.floor(math.max(0, 1 - (wear / 65535)) * 100 + 0.5)
|
|
local wear_color = 0xE56363
|
|
|
|
if remain_pct >= 60 then
|
|
wear_color = 0x6BD66B
|
|
elseif remain_pct >= 30 then
|
|
wear_color = 0xF0B35E
|
|
end
|
|
|
|
add_line(view_model, "item_wear", S("Durability") .. ": " .. tostring(remain_pct) .. "%", wear_color)
|
|
end
|
|
|
|
if target.item_def_description and target.item_def_description ~= "" then
|
|
local lines = {}
|
|
for line in target.item_def_description:gmatch("[^\n]+") do
|
|
table.insert(lines, line)
|
|
end
|
|
|
|
for i = 2, #lines do
|
|
add_line(view_model, "item_def_line_" .. i, lines[i], 0xCCCCCC)
|
|
end
|
|
end
|
|
|
|
if target.stack_has_meta then
|
|
local detail = target.stack_meta_summary
|
|
|
|
if detail and detail ~= "" then
|
|
add_line(view_model, "item_meta", S("Metadata") .. ": " .. detail, 0x98C9FF)
|
|
else
|
|
add_line(view_model, "item_meta", S("Metadata"), 0x98C9FF)
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
eye_spy.enrichers.register("growth", {
|
|
enabled = function(context, target)
|
|
return target.kind == "node" and meta_bool(context.meta, "es_show_growth", eye_spy.config.show_growth)
|
|
end,
|
|
apply = function(context, target, view_model)
|
|
local shared = ensure_shared_context(context, target)
|
|
local growth_info = shared:get_growth_info()
|
|
|
|
if not growth_info then
|
|
return
|
|
end
|
|
|
|
if growth_info.kind == "sapling" then
|
|
local age = growth_info.age or 0
|
|
|
|
if age > 0 then
|
|
local percent = math.min(math.floor((age / 20) * 100), 100)
|
|
add_line(view_model, "growth", S("Maturity") .. ": " .. growth_bar(percent), 0xAAAAFF)
|
|
else
|
|
add_line(view_model, "growth", S("Status") .. ": " .. S("Rooting"), 0x888888)
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
if growth_info.kind == "crop" then
|
|
local percent = math.floor(math.min(growth_info.stage / math.max(growth_info.max_stage, 1), 1) * 100)
|
|
local conditions = shared:get_crop_conditions() or get_crop_conditions(target, growth_info)
|
|
local stalled = growth_info.stage < growth_info.max_stage and
|
|
(not conditions.soil_ok or not conditions.light_ok)
|
|
local status = ""
|
|
|
|
if stalled then
|
|
local reasons = {}
|
|
|
|
if not conditions.soil_ok then
|
|
if not conditions.soil.is_wet then
|
|
-- Soil is dry (and may also be wrong type)
|
|
reasons[#reasons + 1] = conditions.soil.is_soil and S("Dry Soil") or S("Bad Soil")
|
|
else
|
|
-- Soil is wet but wrong fertility type for this crop
|
|
reasons[#reasons + 1] = S("Bad Soil")
|
|
end
|
|
end
|
|
|
|
if not conditions.light_ok then
|
|
reasons[#reasons + 1] = S("Bad Light")
|
|
end
|
|
|
|
status = " (" .. S("Stalled")
|
|
|
|
if #reasons > 0 then
|
|
status = status .. ": " .. table.concat(reasons, ", ")
|
|
end
|
|
|
|
status = status .. ")"
|
|
end
|
|
|
|
add_line(
|
|
view_model,
|
|
"growth",
|
|
S("Growth") .. ": " .. growth_bar(percent) .. status,
|
|
stalled and 0xFFAA55 or 0x55FF55
|
|
)
|
|
end
|
|
end,
|
|
})
|
|
|
|
eye_spy.enrichers.register("dig_time", {
|
|
enabled = function(context, target)
|
|
return target.kind == "node" and meta_bool(context.meta, "es_show_dig_time", true)
|
|
end,
|
|
apply = function(context, target, view_model)
|
|
local shared = ensure_shared_context(context, target)
|
|
local diggable, time = shared:get_dig_result()
|
|
|
|
if not diggable then
|
|
add_line(view_model, "dig_time", S("Dig Time") .. ": ∞", 0xA9C5FF)
|
|
return
|
|
end
|
|
|
|
local t = tonumber(time) or 0
|
|
add_line(view_model, "dig_time", S("Dig Time") .. ": " .. string.format("%.2fs", t), 0xA9C5FF)
|
|
end,
|
|
})
|
|
|
|
eye_spy.enrichers.register("soil", {
|
|
enabled = function(context, target)
|
|
return target.kind == "node" and meta_bool(context.meta, "es_show_growth", eye_spy.config.show_growth)
|
|
end,
|
|
apply = function(context, target, view_model)
|
|
local shared = ensure_shared_context(context, target)
|
|
local soil_line = shared:get_soil_line()
|
|
|
|
if not soil_line then
|
|
return
|
|
end
|
|
|
|
add_line(view_model, "soil", soil_line.text, soil_line.color)
|
|
end,
|
|
})
|
|
|
|
eye_spy.enrichers.register("light_level", {
|
|
enabled = function(context, target)
|
|
return target.kind == "node" and meta_bool(context.meta, "es_show_light_level", true)
|
|
end,
|
|
apply = function(_, target, view_model)
|
|
if target.light_level == nil then
|
|
add_line(view_model, "light", S("Light") .. ": " .. S("Unknown"), 0xAAAAAA)
|
|
return
|
|
end
|
|
|
|
add_line(view_model, "light", S("Light") .. ": " .. tostring(target.light_level), 0xF2DD88)
|
|
end,
|
|
})
|
|
|
|
eye_spy.enrichers.register("spawn_risk", {
|
|
enabled = function(context, target)
|
|
return target.kind == "node" and meta_bool(context.meta, "es_show_spawn_hint", false)
|
|
end,
|
|
apply = function(context, target, view_model)
|
|
if target.light_level == nil then
|
|
add_line(view_model, "spawn", S("Spawn Risk") .. " (" .. S("Est") .. "): " .. S("Unknown"), 0xAAAAAA)
|
|
return
|
|
end
|
|
|
|
local threshold = context.meta:get_int("es_spawn_safe_light_threshold")
|
|
|
|
if threshold < 0 or threshold > 15 then
|
|
threshold = 8
|
|
end
|
|
|
|
if target.light_level <= threshold then
|
|
add_line(view_model, "spawn", S("Spawn Risk") .. " (" .. S("Est") .. "): " .. S("Dark Enough"), 0xF08A55)
|
|
else
|
|
add_line(view_model, "spawn", S("Spawn Risk") .. " (" .. S("Est") .. "): " .. S("Likely Safe"), 0x6BD66B)
|
|
end
|
|
end,
|
|
})
|
|
|
|
eye_spy.enrichers.register("liquid_info", {
|
|
enabled = function(context, target)
|
|
if not eye_spy.config.show_liquids then
|
|
return false
|
|
end
|
|
|
|
local show_liquid = meta_bool(context.meta, "es_show_liquid_info", true)
|
|
|
|
if not show_liquid then
|
|
return false
|
|
end
|
|
|
|
return target.is_liquid or target.liquid_obstacle ~= nil
|
|
end,
|
|
apply = function(_, target, view_model)
|
|
local liquid_target = target.is_liquid and target or target.liquid_obstacle
|
|
|
|
if not liquid_target then
|
|
return
|
|
end
|
|
|
|
local name = liquid_name_from_node(liquid_target.full_name or liquid_target.name)
|
|
local detail
|
|
|
|
if liquid_target.liquid_type == "source" then
|
|
detail = S("Source")
|
|
elseif liquid_target.liquid_level then
|
|
detail = S("Level") .. ": " .. tostring(liquid_target.liquid_level)
|
|
else
|
|
detail = S("Flowing")
|
|
end
|
|
|
|
add_line(view_model, "liquid", S("Liquid") .. ": " .. name .. " (" .. detail .. ")", 0x6FB8FF)
|
|
end,
|
|
})
|