diff --git a/autogenerating-colors.txt b/autogenerating-colors.txt
deleted file mode 100644
index 6fb60c0..0000000
--- a/autogenerating-colors.txt
+++ /dev/null
@@ -1,132 +0,0 @@
-==FILE== mods/dumpnodes/init.lua
-local function nd_get_tiles(nd)
- return nd.tiles or nd.tile_images
-end
-
-local function nd_get_tile(nd, n)
- local tile = nd_get_tiles(nd)[n]
- if type(tile) == 'table' then
- tile = tile.name
- end
- return tile
-end
-
-local function pairs_s(dict)
- local keys = {}
- for k in pairs(dict) do
- table.insert(keys, k)
- end
- table.sort(keys)
- return ipairs(keys)
-end
-
-minetest.register_chatcommand("dumpnodes", {
- params = "",
- description = "",
- func = function(player, param)
- local n = 0
- local ntbl = {}
- for _, nn in pairs_s(minetest.registered_nodes) do
- local nd = minetest.registered_nodes[nn]
- local prefix, name = nn:match('(.*):(.*)')
- if prefix == nil or name == nil then
- print("ignored(1): " .. nn)
- else
- if ntbl[prefix] == nil then
- ntbl[prefix] = {}
- end
- ntbl[prefix][name] = true
- end
- end
- local out, err = io.open('nodes.txt', 'wb')
- if not out then
- return true, "io.open(): " .. err
- end
- for _, prefix in pairs_s(ntbl) do
- out:write('# ' .. prefix .. '\n')
- for _, name in pairs_s(ntbl[prefix]) do
- local nn = prefix .. ":" .. name
- local nd = minetest.registered_nodes[nn]
- if nd.drawtype == 'airlike' or nd_get_tiles(nd) == nil then
- print("ignored(2): " .. nn)
- else
- local tl = nd_get_tile(nd, 1)
- tl = (tl .. '^'):match('(.-)^') -- strip modifiers
- out:write(nn .. ' ' .. tl .. '\n')
- n = n + 1
- end
- end
- out:write('\n')
- end
- out:close()
- return true, n .. " nodes dumped."
- end,
-})
-==FILE== avgcolor.py
-#!/usr/bin/env python
-import sys
-from math import sqrt
-from PIL import Image
-
-if len(sys.argv) < 2:
- print("Prints average color (RGB) of input image")
- print("Usage: %s " % sys.argv[0])
- exit(1)
-
-inp = Image.open(sys.argv[1]).convert('RGBA')
-ind = inp.load()
-
-cl = ([], [], [])
-for x in range(inp.size[0]):
- for y in range(inp.size[1]):
- px = ind[x, y]
- if px[3] < 128: continue # alpha
- cl[0].append(px[0]**2)
- cl[1].append(px[1]**2)
- cl[2].append(px[2]**2)
-
-if len(cl[0]) == 0:
- print("Didn't find average color for %s" % sys.argv[1], file=sys.stderr)
- print("0 0 0")
-else:
- cl = tuple(sqrt(sum(x)/len(x)) for x in cl)
- print("%d %d %d" % cl)
-==SCRIPT==
-#!/bin/bash -e
-AVGCOLOR_PATH=/path/to/avgcolor.py
-GAME_PATH=/path/to/minetest_game
-MODS_PATH= # path to "mods" folder, only set if you have loaded mods
-NODESTXT_PATH=./nodes.txt
-COLORSTXT_PATH=./colors.txt
-
-while read -r line; do
- set -- junk $line; shift
- if [[ -z "$1" || $1 == "#" ]]; then
- echo "$line"; continue
- fi
- tex=$(find $GAME_PATH -type f -name "$2")
- [[ -z "$tex" && -n "$MODS_PATH" ]] && tex=$(find $MODS_PATH -type f -name "$2")
- if [ -z "$tex" ]; then
- echo "skip $1: texture not found" >&2
- continue
- fi
- echo "$1" $(python $AVGCOLOR_PATH "$tex")
- echo "ok $1" >&2
-done < $NODESTXT_PATH > $COLORSTXT_PATH
-# Use nicer colors for water and lava:
-sed -re 's/^default:((river_)?water_(flowing|source)) [0-9 ]+$/default:\1 39 66 106 128 224/g' $COLORSTXT_PATH -i
-sed -re 's/^default:(lava_(flowing|source)) [0-9 ]+$/default:\1 255 100 0/g' $COLORSTXT_PATH -i
-# Add transparency to glass nodes and xpanes:
-sed -re 's/^default:(.*glass) ([0-9 ]+)$/default:\1 \2 64 16/g' $COLORSTXT_PATH -i
-sed -re 's/^doors:(.*glass[^ ]*) ([0-9 ]+)$/doors:\1 \2 64 16/g' $COLORSTXT_PATH -i
-sed -re 's/^xpanes:(.*(pane|bar)[^ ]*) ([0-9 ]+)$/xpanes:\1 \3 64 16/g' $COLORSTXT_PATH -i
-# Delete some usually hidden nodes:
-sed '/^doors:hidden /d' $COLORSTXT_PATH -i
-sed '/^fireflies:firefly /d' $COLORSTXT_PATH -i
-sed '/^butterflies:butterfly_/d' $COLORSTXT_PATH -i
-==INSTRUCTIONS==
-1) Make sure avgcolors.py works (outputs the usage instructions when run)
-2) Add the dumpnodes mod to Minetest
-3) Create a world and load dumpnodes & all mods you want to generate colors for
-4) Execute /dumpnodes ingame
-5) Run the script to generate colors.txt (make sure to adjust the PATH variables at the top)
diff --git a/util/dumpnodes/init.lua b/util/dumpnodes/init.lua
new file mode 100644
index 0000000..c494752
--- /dev/null
+++ b/util/dumpnodes/init.lua
@@ -0,0 +1,61 @@
+local function get_tile(tiles, n)
+ local tile = tiles[n]
+ if type(tile) == 'table' then
+ return tile.name
+ end
+ return tile
+end
+
+local function pairs_s(dict)
+ local keys = {}
+ for k in pairs(dict) do
+ keys[#keys+1] = k
+ end
+ table.sort(keys)
+ return ipairs(keys)
+end
+
+minetest.register_chatcommand("dumpnodes", {
+ description = "Dump node and texture list for use with minetestmapper",
+ func = function()
+ local ntbl = {}
+ for _, nn in pairs_s(minetest.registered_nodes) do
+ local prefix, name = nn:match('(.*):(.*)')
+ if prefix == nil or name == nil then
+ print("ignored(1): " .. nn)
+ else
+ if ntbl[prefix] == nil then
+ ntbl[prefix] = {}
+ end
+ ntbl[prefix][name] = true
+ end
+ end
+ local out, err = io.open(minetest.get_worldpath() .. "/nodes.txt", 'wb')
+ if not out then
+ return true, err
+ end
+ local n = 0
+ for _, prefix in pairs_s(ntbl) do
+ out:write('# ' .. prefix .. '\n')
+ for _, name in pairs_s(ntbl[prefix]) do
+ local nn = prefix .. ":" .. name
+ local nd = minetest.registered_nodes[nn]
+ local tiles = nd.tiles or nd.tile_images
+ if tiles == nil or nd.drawtype == 'airlike' then
+ print("ignored(2): " .. nn)
+ else
+ local tex = get_tile(tiles, 1)
+ tex = (tex .. '^'):match('%(*(.-)%)*^') -- strip modifiers
+ if tex:find("[combine", 1, true) then
+ tex = tex:match('.-=([^:]-)') -- extract first texture
+ end
+ out:write(nn .. ' ' .. tex .. '\n')
+ n = n + 1
+ end
+ end
+ out:write('\n')
+ end
+ out:close()
+ return true, n .. " nodes dumped."
+ end,
+})
diff --git a/util/dumpnodes/mod.conf b/util/dumpnodes/mod.conf
new file mode 100644
index 0000000..eaaee76
--- /dev/null
+++ b/util/dumpnodes/mod.conf
@@ -0,0 +1,2 @@
+name = dumpnodes
+description = minetestmapper development mod (node dumper)
diff --git a/util/generate_colorstxt.py b/util/generate_colorstxt.py
new file mode 100755
index 0000000..ee7bfcb
--- /dev/null
+++ b/util/generate_colorstxt.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python3
+import sys
+import os.path
+import getopt
+import re
+from math import sqrt
+try:
+ from PIL import Image
+except:
+ print("Could not load image routines, install PIL ('pillow' on pypi)!", file=sys.stderr)
+ exit(1)
+
+############
+############
+# Instructions for generating a colors.txt file for custom games and/or mods:
+# 1) Add the dumpnodes mod to a Minetest world with the chosen game and mods enabled.
+# 2) Join ingame and run the /dumpnodes chat command.
+# 3) Run this script and poin it to the installation path of the game using -g,
+# the path(s) where mods are stored using -m and the nodes.txt in your world folder.
+# Example command line:
+# ./util/generate_colorstxt.py --game /usr/share/minetest/games/minetest_game \
+# -m ~/.minetest/mods ~/.minetest/worlds/my_world/nodes.txt
+# 4) Copy the resulting colors.txt file to your world folder or to any other places
+# and use it with minetestmapper's --colors option.
+###########
+###########
+
+# minimal sed syntax, s|match|replace| and /match/d supported
+REPLACEMENTS = [
+ # Delete some nodes that are usually hidden
+ r'/^fireflies:firefly /d',
+ r'/^butterflies:butterfly_/d',
+ # Nicer colors for water and lava
+ r's/^(default:(river_)?water_(flowing|source)) [0-9 ]+$/\1 39 66 106 128 224/',
+ r's/^(default:lava_(flowing|source)) [0-9 ]+$/\1 255 100 0/',
+ # Transparency for glass nodes and panes
+ r's/^(default:.*glass) ([0-9 ]+)$/\1 \2 64 16/',
+ r's/^(doors:.*glass[^ ]*) ([0-9 ]+)$/\1 \2 64 16/',
+ r's/^(xpanes:.*(pane|bar)[^ ]*) ([0-9 ]+)$/\1 \3 64 16/',
+]
+
+def usage():
+ print("Usage: generate_colorstxt.py [options] [input file] [output file]")
+ print("If not specified the input file defaults to ./nodes.txt and the output file to ./colors.txt")
+ print(" -g / --game \t\tSet path to the game (for textures), required")
+ print(" -m / --mods \t\tAdd search path for mod textures")
+ print(" --replace \t\tLoad replacements from file (ADVANCED)")
+
+def collect_files(path):
+ dirs = []
+ with os.scandir(path) as it:
+ for entry in it:
+ if entry.name[0] == '.': continue
+ if entry.is_dir():
+ dirs.append(entry.path)
+ continue
+ if entry.is_file() and '.' in entry.name:
+ if entry.name not in textures.keys():
+ textures[entry.name] = entry.path
+ for path2 in dirs:
+ collect_files(path2)
+
+def average_color(filename):
+ inp = Image.open(filename).convert('RGBA')
+ data = inp.load()
+
+ c0, c1, c2 = [], [], []
+ for x in range(inp.size[0]):
+ for y in range(inp.size[1]):
+ px = data[x, y]
+ if px[3] < 128: continue # alpha
+ c0.append(px[0]**2)
+ c1.append(px[1]**2)
+ c2.append(px[2]**2)
+
+ if len(c0) == 0:
+ print(f"didn't find color for '{os.path.basename(filename)}'", file=sys.stderr)
+ return "0 0 0"
+ c0 = sqrt(sum(c0) / len(c0))
+ c1 = sqrt(sum(c1) / len(c1))
+ c2 = sqrt(sum(c2) / len(c2))
+ return "%d %d %d" % (c0, c1, c2)
+
+def apply_sed(line, exprs):
+ for expr in exprs:
+ if expr[0] == '/':
+ if not expr.endswith("/d"): raise ValueError()
+ if re.search(expr[1:-2], line):
+ return ''
+ elif expr[0] == 's':
+ expr = expr.split(expr[1])
+ if len(expr) != 4 or expr[3] != '': raise ValueError()
+ line = re.sub(expr[1], expr[2], line)
+ else:
+ raise ValueError()
+ return line
+#
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "hg:m:", ["help", "game=", "mods=", "replace="])
+except getopt.GetoptError as e:
+ print(str(e))
+ exit(1)
+if ('-h', '') in opts or ('--help', '') in opts:
+ usage()
+ exit(0)
+
+input_file = "./nodes.txt"
+output_file = "./colors.txt"
+texturepaths = []
+
+try:
+ gamepath = next(o[1] for o in opts if o[0] in ('-g', '--game'))
+ if not os.path.isdir(os.path.join(gamepath, "mods")):
+ print(f"'{gamepath}' doesn't exist or does not contain a game.", file=sys.stderr)
+ exit(1)
+ texturepaths.append(os.path.join(gamepath, "mods"))
+except StopIteration:
+ print("No game path set but one is required. (see --help)", file=sys.stderr)
+ exit(1)
+
+try:
+ tmp = next(o[1] for o in opts if o[0] == "--replace")
+ REPLACEMENTS.clear()
+ with open(tmp, 'r') as f:
+ for line in f:
+ if not line or line[0] == '#': continue
+ REPLACEMENTS.append(line.strip())
+except StopIteration:
+ pass
+
+for o in opts:
+ if o[0] not in ('-m', '--mods'): continue
+ if not os.path.isdir(o[1]):
+ print(f"Given path '{o[1]}' does not exist.'", file=sys.stderr)
+ exit(1)
+ texturepaths.append(o[1])
+
+if len(args) > 2:
+ print("Too many arguments.", file=sys.stderr)
+ exit(1)
+if len(args) > 1:
+ output_file = args[1]
+if len(args) > 0:
+ input_file = args[0]
+
+if not os.path.exists(input_file) or os.path.isdir(input_file):
+ print(f"Input file '{input_file}' does not exist.", file=sys.stderr)
+ exit(1)
+
+#
+
+print(f"Collecting textures from {len(texturepaths)} path(s)... ", end="", flush=True)
+textures = {}
+for path in texturepaths:
+ collect_files(path)
+print("done")
+
+print("Processing nodes...")
+fin = open(input_file, 'r')
+fout = open(output_file, 'w')
+n = 0
+for line in fin:
+ line = line.rstrip('\r\n')
+ if not line or line[0] == '#':
+ fout.write(line + '\n')
+ continue
+ node, tex = line.split(" ")
+ if not tex or tex == "blank.png":
+ continue
+ elif tex not in textures.keys():
+ print(f"skip {node} texture not found")
+ continue
+ color = average_color(textures[tex])
+ line = f"{node} {color}"
+ #print(f"ok {node}")
+ line = apply_sed(line, REPLACEMENTS)
+ if line:
+ fout.write(line + '\n')
+ n += 1
+fin.close()
+fout.close()
+print(f"Done, {n} entries written.")