Files

339 lines
9.9 KiB
Lua

-- Node Color Mapping System for Luanti
-- Dynamically generates colors based on node properties, keywords, and groups
local nodecolors = {}
-- ============================================================================
-- COLOR PALETTE - 16 base colors for all terrain types
-- ============================================================================
local PALETTE = {
GRASS_GREEN = {95, 159, 53},
DIRT_BROWN = {134, 96, 67},
STONE_GRAY = {128, 128, 128},
SAND_TAN = {194, 178, 128},
DESERT_SAND = {210, 180, 140},
WATER_BLUE = {38, 92, 255},
SNOW_WHITE = {240, 240, 240},
ICE_BLUE = {175, 210, 255},
TREE_BROWN = {101, 84, 50},
LEAVES_GREEN = {60, 100, 30},
LAVA_ORANGE = {255, 100, 0},
COAL_BLACK = {64, 64, 64},
IRON_GRAY = {140, 120, 110},
GOLD_YELLOW = {220, 180, 50},
GRAVEL_GRAY = {136, 126, 126},
DEFAULT_GRAY = {100, 100, 100}
}
-- ============================================================================
-- MANUAL OVERRIDES - Highest priority, exact node name matches
-- ============================================================================
local manual_overrides = {
-- Special cases that don't fit auto-detection well
["default:apple"] = PALETTE.GOLD_YELLOW,
["mcl_core:obsidian"] = {20, 10, 30},
["mcl_core:bedrock"] = {40, 40, 40},
["mcl_nether:netherrack"] = {100, 50, 50},
["mcl_nether:soul_sand"] = {80, 70, 60},
["mcl_end:end_stone"] = {220, 220, 180},
["nc_fire:ash"] = {80, 80, 80},
["nc_concrete:concrete"] = {160, 160, 160},
}
-- ============================================================================
-- KEYWORD DETECTION - Pattern matching in node names
-- ============================================================================
local keyword_colors = {
-- Grass and vegetation
{pattern = "grass", color = PALETTE.GRASS_GREEN},
{pattern = "turf", color = PALETTE.GRASS_GREEN},
{pattern = "tallgrass", color = PALETTE.GRASS_GREEN},
{pattern = "sedge", color = PALETTE.GRASS_GREEN},
-- Dirt and soil
{pattern = "dirt", color = PALETTE.DIRT_BROWN},
{pattern = "soil", color = PALETTE.DIRT_BROWN},
{pattern = "podzol", color = {90, 70, 50}},
{pattern = "coarse_dirt", color = {120, 85, 60}},
{pattern = "mycelium", color = {110, 90, 110}},
{pattern = "clay", color = {160, 160, 180}},
-- Stone types
{pattern = "stone", color = PALETTE.STONE_GRAY},
{pattern = "cobble", color = PALETTE.DEFAULT_GRAY},
{pattern = "mossycobble", color = {80, 120, 80}},
{pattern = "granite", color = {150, 100, 80}},
{pattern = "andesite", color = {130, 130, 130}},
{pattern = "diorite", color = {180, 180, 180}},
{pattern = "annealed", color = {150, 150, 150}},
{pattern = "pumice", color = {200, 190, 180}},
{pattern = "lode", color = {110, 110, 110}},
-- Sand and desert
{pattern = "sand", color = PALETTE.SAND_TAN},
{pattern = "redsand", color = PALETTE.DESERT_SAND},
{pattern = "desert", color = PALETTE.DESERT_SAND},
-- Water
{pattern = "water", color = PALETTE.WATER_BLUE},
{pattern = "river_water", color = {20, 100, 200}},
-- Snow and ice
{pattern = "snow", color = PALETTE.SNOW_WHITE},
{pattern = "ice", color = PALETTE.ICE_BLUE},
{pattern = "packed_ice", color = {160, 200, 255}},
-- Trees and wood
{pattern = "tree", color = PALETTE.TREE_BROWN},
{pattern = "trunk", color = PALETTE.TREE_BROWN},
{pattern = "bark", color = {90, 70, 45}},
{pattern = "root", color = {80, 60, 40}},
{pattern = "log", color = PALETTE.TREE_BROWN},
{pattern = "wood", color = PALETTE.TREE_BROWN},
-- Specific tree types
{pattern = "pine", color = {85, 70, 45}},
{pattern = "spruce", color = {85, 70, 45}},
{pattern = "jungle", color = {120, 90, 50}},
{pattern = "birch", color = {220, 220, 180}},
{pattern = "acacia", color = {160, 100, 60}},
{pattern = "dark", color = {60, 40, 20}},
-- Leaves
{pattern = "leaves", color = PALETTE.LEAVES_GREEN},
{pattern = "needles", color = {45, 80, 25}},
{pattern = "jungleleaves", color = {50, 90, 30}},
{pattern = "birchleaves", color = {80, 120, 50}},
{pattern = "acacialeaves", color = {90, 110, 40}},
{pattern = "darkleaves", color = {40, 70, 20}},
-- Ores
{pattern = "coal", color = PALETTE.COAL_BLACK},
{pattern = "iron", color = PALETTE.IRON_GRAY},
{pattern = "copper", color = {180, 120, 80}},
{pattern = "gold", color = PALETTE.GOLD_YELLOW},
{pattern = "diamond", color = {100, 200, 255}},
{pattern = "emerald", color = {50, 200, 100}},
{pattern = "lapis", color = {50, 100, 200}},
{pattern = "redstone", color = {200, 50, 50}},
-- Lava and gravel
{pattern = "lava", color = PALETTE.LAVA_ORANGE},
{pattern = "gravel", color = PALETTE.GRAVEL_GRAY},
-- Flora
{pattern = "flower", color = {200, 100, 150}},
{pattern = "junglegrass", color = {40, 80, 20}},
-- Farmland
{pattern = "farmland", color = {120, 80, 50}},
{pattern = "grass_path", color = {140, 120, 80}},
}
-- ============================================================================
-- GROUP-BASED DETECTION - Fallback based on node groups
-- ============================================================================
local group_colors = {
soil = PALETTE.DIRT_BROWN,
sand = PALETTE.SAND_TAN,
stone = PALETTE.STONE_GRAY,
tree = PALETTE.TREE_BROWN,
leaves = PALETTE.LEAVES_GREEN,
water = PALETTE.WATER_BLUE,
lava = PALETTE.LAVA_ORANGE,
ice = PALETTE.ICE_BLUE,
snow = PALETTE.SNOW_WHITE,
}
-- ============================================================================
-- COLOR CACHE - Store computed colors to avoid recalculation
-- ============================================================================
local color_cache = {}
-- ============================================================================
-- HELPER FUNCTIONS
-- ============================================================================
-- Check if a string contains a pattern (case-insensitive)
local function contains_pattern(str, pattern)
return str:lower():find(pattern:lower(), 1, true) ~= nil
end
-- Detect color based on keywords in node name
local function detect_color_from_keywords(nodename)
for _, entry in ipairs(keyword_colors) do
if contains_pattern(nodename, entry.pattern) then
return entry.color
end
end
return nil
end
-- Detect color based on node groups
local function detect_color_from_groups(nodename)
local node_def = minetest.registered_nodes[nodename]
if not node_def or not node_def.groups then
return nil
end
-- Check each group that has a color mapping
for group, color in pairs(group_colors) do
if node_def.groups[group] and node_def.groups[group] > 0 then
return color
end
end
return nil
end
-- ============================================================================
-- PUBLIC API
-- ============================================================================
-- Get color for a node (main function)
function nodecolors.get_color(nodename)
-- Return from cache if available
if color_cache[nodename] then
return color_cache[nodename]
end
local color = nil
-- Priority 1: Manual overrides
if manual_overrides[nodename] then
color = manual_overrides[nodename]
end
-- Priority 2: Keyword detection
if not color then
color = detect_color_from_keywords(nodename)
end
-- Priority 3: Group-based detection
if not color then
color = detect_color_from_groups(nodename)
end
-- Priority 4: Default color
if not color then
color = PALETTE.DEFAULT_GRAY
end
-- Cache the result
color_cache[nodename] = color
return color
end
-- Set a manual override color for a specific node
function nodecolors.set_color(nodename, color)
manual_overrides[nodename] = color
color_cache[nodename] = color
end
-- Check if a node has a defined color (not default)
function nodecolors.has_color(nodename)
local color = nodecolors.get_color(nodename)
return color ~= PALETTE.DEFAULT_GRAY
end
-- Add a new keyword pattern and color
function nodecolors.add_keyword(pattern, color)
table.insert(keyword_colors, {pattern = pattern, color = color})
end
-- Add a new group color mapping
function nodecolors.add_group_color(group, color)
group_colors[group] = color
end
-- Get the color palette (for external customization)
function nodecolors.get_palette()
return PALETTE
end
-- Clear the color cache (useful after bulk changes)
function nodecolors.clear_cache()
color_cache = {}
end
-- Get statistics about color detection
function nodecolors.get_stats()
local stats = {
manual_overrides = 0,
keyword_patterns = 0,
group_mappings = 0,
cached_colors = 0
}
for _ in pairs(manual_overrides) do
stats.manual_overrides = stats.manual_overrides + 1
end
stats.keyword_patterns = #keyword_colors
for _ in pairs(group_colors) do
stats.group_mappings = stats.group_mappings + 1
end
for _ in pairs(color_cache) do
stats.cached_colors = stats.cached_colors + 1
end
return stats
end
-- Debug function: show how a color was determined
function nodecolors.debug_color(nodename)
local info = {
nodename = nodename,
color = nil,
method = "none",
details = ""
}
-- Check manual override
if manual_overrides[nodename] then
info.color = manual_overrides[nodename]
info.method = "manual_override"
return info
end
-- Check keywords
local keyword_color = detect_color_from_keywords(nodename)
if keyword_color then
info.color = keyword_color
info.method = "keyword"
for _, entry in ipairs(keyword_colors) do
if contains_pattern(nodename, entry.pattern) then
info.details = "matched pattern: " .. entry.pattern
break
end
end
return info
end
-- Check groups
local group_color = detect_color_from_groups(nodename)
if group_color then
info.color = group_color
info.method = "group"
local node_def = minetest.registered_nodes[nodename]
if node_def and node_def.groups then
for group, color in pairs(group_colors) do
if node_def.groups[group] and node_def.groups[group] > 0 then
info.details = "matched group: " .. group
break
end
end
end
return info
end
-- Default
info.color = PALETTE.DEFAULT_GRAY
info.method = "default"
return info
end
return nodecolors