Import upstream version 20200328+git20210329.1.fd4c5dd
Debian Janitor
2 years ago
10 | 10 | return data[0] << 8 | data[1]; |
11 | 11 | } |
12 | 12 | |
13 | static int readBlockContent(const unsigned char *mapData, u8 version, unsigned int datapos) | |
13 | static inline uint16_t readBlockContent(const unsigned char *mapData, | |
14 | u8 contentWidth, unsigned int datapos) | |
14 | 15 | { |
15 | if (version >= 24) { | |
16 | if (contentWidth == 2) { | |
16 | 17 | size_t index = datapos << 1; |
17 | 18 | return (mapData[index] << 8) | mapData[index + 1]; |
18 | } else if (version >= 20) { | |
19 | if (mapData[datapos] <= 0x80) | |
20 | return mapData[datapos]; | |
19 | } else { | |
20 | u8 param = mapData[datapos]; | |
21 | if (param <= 0x7f) | |
22 | return param; | |
21 | 23 | else |
22 | return (int(mapData[datapos]) << 4) | (int(mapData[datapos + 0x2000]) >> 4); | |
24 | return (param << 4) | (mapData[datapos + 0x2000] >> 4); | |
23 | 25 | } |
24 | std::ostringstream oss; | |
25 | oss << "Unsupported map version " << version; | |
26 | throw std::runtime_error(oss.str()); | |
27 | 26 | } |
28 | 27 | |
29 | 28 | BlockDecoder::BlockDecoder() |
38 | 37 | m_nameMap.clear(); |
39 | 38 | |
40 | 39 | m_version = 0; |
40 | m_contentWidth = 0; | |
41 | 41 | m_mapData = ustring(); |
42 | 42 | } |
43 | 43 | |
49 | 49 | |
50 | 50 | uint8_t version = data[0]; |
51 | 51 | //uint8_t flags = data[1]; |
52 | if (version < 22) { | |
53 | std::ostringstream oss; | |
54 | oss << "Unsupported map version " << (int)version; | |
55 | throw std::runtime_error(oss.str()); | |
56 | } | |
52 | 57 | m_version = version; |
53 | 58 | |
54 | 59 | size_t dataOffset = 0; |
55 | 60 | if (version >= 27) |
56 | dataOffset = 6; | |
57 | else if (version >= 22) | |
58 | 61 | dataOffset = 4; |
59 | 62 | else |
60 | 63 | dataOffset = 2; |
64 | ||
65 | uint8_t contentWidth = data[dataOffset]; | |
66 | dataOffset++; | |
67 | uint8_t paramsWidth = data[dataOffset]; | |
68 | dataOffset++; | |
69 | if (contentWidth != 1 && contentWidth != 2) | |
70 | throw std::runtime_error("unsupported map version (contentWidth)"); | |
71 | if (paramsWidth != 2) | |
72 | throw std::runtime_error("unsupported map version (paramsWidth)"); | |
73 | m_contentWidth = contentWidth; | |
74 | ||
61 | 75 | |
62 | 76 | ZlibDecompressor decompressor(data, length); |
63 | 77 | decompressor.setSeekPos(dataOffset); |
66 | 80 | dataOffset = decompressor.seekPos(); |
67 | 81 | |
68 | 82 | // Skip unused data |
69 | if (version <= 21) | |
70 | dataOffset += 2; | |
71 | 83 | if (version == 23) |
72 | 84 | dataOffset += 1; |
73 | 85 | if (version == 24) { |
91 | 103 | dataOffset += 4; // Skip timestamp |
92 | 104 | |
93 | 105 | // Read mapping |
94 | if (version >= 22) { | |
106 | { | |
95 | 107 | dataOffset++; // mapping version |
96 | 108 | uint16_t numMappings = readU16(data + dataOffset); |
97 | 109 | dataOffset += 2; |
113 | 125 | |
114 | 126 | // Node timers |
115 | 127 | if (version >= 25) { |
116 | dataOffset++; | |
128 | uint8_t timerLength = data[dataOffset++]; | |
117 | 129 | uint16_t numTimers = readU16(data + dataOffset); |
118 | 130 | dataOffset += 2; |
119 | dataOffset += numTimers * 10; | |
131 | dataOffset += numTimers * timerLength; | |
120 | 132 | } |
121 | 133 | } |
122 | 134 | |
126 | 138 | return m_nameMap.empty(); |
127 | 139 | } |
128 | 140 | |
129 | std::string BlockDecoder::getNode(u8 x, u8 y, u8 z) const | |
141 | const static std::string empty; | |
142 | ||
143 | const std::string &BlockDecoder::getNode(u8 x, u8 y, u8 z) const | |
130 | 144 | { |
131 | 145 | unsigned int position = x + (y << 4) + (z << 8); |
132 | int content = readBlockContent(m_mapData.c_str(), m_version, position); | |
146 | uint16_t content = readBlockContent(m_mapData.c_str(), m_contentWidth, position); | |
133 | 147 | if (content == m_blockAirId || content == m_blockIgnoreId) |
134 | return ""; | |
148 | return empty; | |
135 | 149 | NameMap::const_iterator it = m_nameMap.find(content); |
136 | 150 | if (it == m_nameMap.end()) { |
137 | 151 | std::cerr << "Skipping node with invalid ID." << std::endl; |
138 | return ""; | |
152 | return empty; | |
139 | 153 | } |
140 | 154 | return it->second; |
141 | 155 | } |
0 | cmake_minimum_required(VERSION 3.5) | |
1 | ||
0 | 2 | project(minetestmapper CXX) |
1 | cmake_minimum_required(VERSION 2.6) | |
2 | cmake_policy(SET CMP0003 NEW) | |
3 | 3 | |
4 | 4 | set(VERSION_MAJOR 1) |
5 | 5 | set(VERSION_MINOR 0) |
45 | 45 | endif() |
46 | 46 | |
47 | 47 | #set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) |
48 | find_package(PkgConfig) | |
49 | include(FindPackageHandleStandardArgs) | |
50 | 48 | |
51 | 49 | # Libraries: gd |
52 | 50 | |
60 | 58 | |
61 | 59 | # Libraries: zlib |
62 | 60 | |
63 | find_library(ZLIB_LIBRARY z) | |
64 | find_path(ZLIB_INCLUDE_DIR zlib.h) | |
65 | message (STATUS "zlib library: ${ZLIB_LIBRARY}") | |
66 | message (STATUS "zlib headers: ${ZLIB_INCLUDE_DIR}") | |
67 | if(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR) | |
68 | message(FATAL_ERROR "zlib not found!") | |
69 | endif(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR) | |
61 | find_package(ZLIB REQUIRED) | |
70 | 62 | |
71 | 63 | # Libraries: sqlite3 |
72 | 64 | |
73 | 65 | find_library(SQLITE3_LIBRARY sqlite3) |
74 | find_path(SQLITE3_INCLUDE_DIR zlib.h) | |
66 | find_path(SQLITE3_INCLUDE_DIR sqlite3.h) | |
75 | 67 | message (STATUS "sqlite3 library: ${SQLITE3_LIBRARY}") |
76 | 68 | message (STATUS "sqlite3 headers: ${SQLITE3_INCLUDE_DIR}") |
77 | 69 | if(NOT SQLITE3_LIBRARY OR NOT SQLITE3_INCLUDE_DIR) |
16 | 16 | |
17 | 17 | // ARGB but with inverted alpha |
18 | 18 | |
19 | static inline int color2int(Color c) | |
19 | static inline int color2int(const Color &c) | |
20 | 20 | { |
21 | 21 | u8 a = (255 - c.a) * gdAlphaMax / 255; |
22 | 22 | return (a << 24) | (c.r << 16) | (c.g << 8) | c.b; |
34 | 34 | return c2; |
35 | 35 | } |
36 | 36 | |
37 | #ifndef NDEBUG | |
37 | 38 | static inline void check_bounds(int x, int y, int width, int height) |
38 | 39 | { |
39 | 40 | if(x < 0 || x >= width) { |
49 | 50 | throw std::out_of_range(oss.str()); |
50 | 51 | } |
51 | 52 | } |
53 | #endif | |
52 | 54 | |
53 | 55 | |
54 | 56 | Image::Image(int width, int height) : |
55 | 57 | m_width(width), m_height(height), m_image(NULL) |
56 | 58 | { |
59 | SIZECHECK(0, 0); | |
57 | 60 | m_image = gdImageCreateTrueColor(m_width, m_height); |
58 | 61 | } |
59 | 62 |
0 | /* | |
1 | * ===================================================================== | |
2 | * Version: 1.0 | |
3 | * Created: 25.08.2012 10:55:27 | |
4 | * Author: Miroslav BendÃk | |
5 | * Company: LinuxOS.sk | |
6 | * ===================================================================== | |
7 | */ | |
0 | #include <cstring> | |
8 | 1 | |
9 | 2 | #include "PixelAttributes.h" |
10 | #include <cstring> | |
11 | 3 | |
12 | 4 | PixelAttributes::PixelAttributes(): |
13 | 5 | m_width(0) |
8 | 8 | #include "PlayerAttributes.h" |
9 | 9 | #include "util.h" |
10 | 10 | |
11 | using namespace std; | |
12 | ||
13 | 11 | PlayerAttributes::PlayerAttributes(const std::string &worldDir) |
14 | 12 | { |
15 | std::ifstream ifs((worldDir + "world.mt").c_str()); | |
13 | std::ifstream ifs(worldDir + "world.mt"); | |
16 | 14 | if (!ifs.good()) |
17 | 15 | throw std::runtime_error("Failed to read world.mt"); |
18 | 16 | std::string backend = read_setting_default("player_backend", ifs, "files"); |
38 | 36 | if (ent->d_name[0] == '.') |
39 | 37 | continue; |
40 | 38 | |
41 | string path = playersPath + PATH_SEPARATOR + ent->d_name; | |
42 | ifstream in(path.c_str()); | |
39 | std::string path = playersPath + PATH_SEPARATOR + ent->d_name; | |
40 | std::ifstream in(path); | |
43 | 41 | if(!in.good()) |
44 | 42 | continue; |
45 | 43 | |
46 | string name, position; | |
44 | std::string name, position; | |
47 | 45 | name = read_setting("name", in); |
48 | 46 | in.seekg(0); |
49 | 47 | position = read_setting("position", in); |
50 | 48 | |
51 | 49 | Player player; |
52 | istringstream iss(position); | |
50 | std::istringstream iss(position); | |
53 | 51 | char tmp; |
54 | 52 | iss >> tmp; // '(' |
55 | 53 | iss >> player.x; |
120 | 118 | |
121 | 119 | /**********/ |
122 | 120 | |
123 | PlayerAttributes::Players::iterator PlayerAttributes::begin() | |
121 | PlayerAttributes::Players::const_iterator PlayerAttributes::begin() const | |
124 | 122 | { |
125 | return m_players.begin(); | |
123 | return m_players.cbegin(); | |
126 | 124 | } |
127 | 125 | |
128 | PlayerAttributes::Players::iterator PlayerAttributes::end() | |
126 | PlayerAttributes::Players::const_iterator PlayerAttributes::end() const | |
129 | 127 | { |
130 | return m_players.end(); | |
128 | return m_players.cend(); | |
131 | 129 | } |
132 | 130 |
2 | 2 | |
3 | 3 | .. image:: https://travis-ci.org/minetest/minetestmapper.svg?branch=master |
4 | 4 | :target: https://travis-ci.org/minetest/minetestmapper |
5 | ||
6 | Minetestmapper generates an overview image from a Minetest map. | |
5 | 7 | |
6 | 8 | A port of minetestmapper.py to C++ from https://github.com/minetest/minetest/tree/master/util. |
7 | 9 | This version is both faster and provides more features than the now deprecated Python script. |
11 | 13 | |
12 | 14 | * libgd |
13 | 15 | * sqlite3 |
14 | * LevelDB (optional, set ENABLE_LEVELDB=1 in CMake to enable) | |
15 | * hiredis library (optional, set ENABLE_REDIS=1 in CMake to enable) | |
16 | * Postgres libraries (optional, set ENABLE_POSTGRES=1 in CMake to enable) | |
16 | * LevelDB (optional) | |
17 | * hiredis (optional) | |
18 | * Postgres libraries (optional) | |
17 | 19 | |
18 | e.g. on Debian: | |
19 | ^^^^^^^^^^^^^^^ | |
20 | on Debian: | |
21 | ^^^^^^^^^^ | |
20 | 22 | |
21 | sudo apt-get install libgd-dev libsqlite3-dev libleveldb-dev libhiredis-dev libpq-dev | |
23 | sudo apt install libgd-dev libsqlite3-dev libleveldb-dev libhiredis-dev libpq-dev | |
24 | ||
25 | on openSUSE: | |
26 | ^^^^^^^^^^^^ | |
27 | ||
28 | sudo zypper install gd-devel sqlite3-devel leveldb-devel hiredis-devel postgresql-devel postgresql-server-devel | |
22 | 29 | |
23 | 30 | Windows |
24 | 31 | ^^^^^^^ |
36 | 43 | :: |
37 | 44 | |
38 | 45 | cmake . -DENABLE_LEVELDB=1 |
39 | make -j2 | |
46 | make -j$(nproc) | |
40 | 47 | |
41 | 48 | Usage |
42 | 49 | ----- |
12 | 12 | #include "config.h" |
13 | 13 | #include "PlayerAttributes.h" |
14 | 14 | #include "BlockDecoder.h" |
15 | #include "Image.h" | |
15 | 16 | #include "util.h" |
17 | ||
16 | 18 | #include "db-sqlite3.h" |
17 | 19 | #if USE_POSTGRESQL |
18 | 20 | #include "db-postgresql.h" |
23 | 25 | #if USE_REDIS |
24 | 26 | #include "db-redis.h" |
25 | 27 | #endif |
26 | ||
27 | using namespace std; | |
28 | 28 | |
29 | 29 | template<typename T> |
30 | 30 | static inline T mymax(T a, T b) |
53 | 53 | static inline unsigned int colorSafeBounds (int channel) |
54 | 54 | { |
55 | 55 | return mymin(mymax(channel, 0), 255); |
56 | } | |
57 | ||
58 | static Color parseColor(const std::string &color) | |
59 | { | |
60 | if (color.length() != 7) | |
61 | throw std::runtime_error("Color needs to be 7 characters long"); | |
62 | if (color[0] != '#') | |
63 | throw std::runtime_error("Color needs to begin with #"); | |
64 | unsigned long col = strtoul(color.c_str() + 1, NULL, 16); | |
65 | u8 b, g, r; | |
66 | b = col & 0xff; | |
67 | g = (col >> 8) & 0xff; | |
68 | r = (col >> 16) & 0xff; | |
69 | return Color(r, g, b); | |
56 | 70 | } |
57 | 71 | |
58 | 72 | static Color mixColors(Color a, Color b) |
138 | 152 | m_scales = flags; |
139 | 153 | } |
140 | 154 | |
141 | Color TileGenerator::parseColor(const std::string &color) | |
142 | { | |
143 | Color parsed; | |
144 | if (color.length() != 7) | |
145 | throw std::runtime_error("Color needs to be 7 characters long"); | |
146 | if (color[0] != '#') | |
147 | throw std::runtime_error("Color needs to begin with #"); | |
148 | unsigned long col = strtoul(color.c_str() + 1, NULL, 16); | |
149 | parsed.b = col & 0xff; | |
150 | parsed.g = (col >> 8) & 0xff; | |
151 | parsed.r = (col >> 16) & 0xff; | |
152 | parsed.a = 255; | |
153 | return parsed; | |
154 | } | |
155 | ||
156 | 155 | void TileGenerator::setDrawOrigin(bool drawOrigin) |
157 | 156 | { |
158 | 157 | m_drawOrigin = drawOrigin; |
213 | 212 | |
214 | 213 | void TileGenerator::parseColorsFile(const std::string &fileName) |
215 | 214 | { |
216 | ifstream in; | |
217 | in.open(fileName.c_str(), ifstream::in); | |
218 | if (!in.is_open()) | |
215 | std::ifstream in(fileName); | |
216 | if (!in.good()) | |
219 | 217 | throw std::runtime_error("Specified colors file could not be found"); |
220 | 218 | parseColorsStream(in); |
221 | 219 | } |
222 | 220 | |
223 | 221 | void TileGenerator::printGeometry(const std::string &input) |
224 | 222 | { |
225 | string input_path = input; | |
223 | std::string input_path = input; | |
226 | 224 | if (input_path[input.length() - 1] != PATH_SEPARATOR) { |
227 | 225 | input_path += PATH_SEPARATOR; |
228 | 226 | } |
248 | 246 | |
249 | 247 | void TileGenerator::generate(const std::string &input, const std::string &output) |
250 | 248 | { |
251 | string input_path = input; | |
249 | std::string input_path = input; | |
252 | 250 | if (input_path[input.length() - 1] != PATH_SEPARATOR) { |
253 | 251 | input_path += PATH_SEPARATOR; |
254 | 252 | } |
304 | 302 | std::cerr << "Failed to parse color entry '" << line << "'" << std::endl; |
305 | 303 | continue; |
306 | 304 | } |
307 | ||
308 | ColorEntry color(r, g, b, a, t); | |
309 | m_colorMap[name] = color; | |
305 | ||
306 | m_colorMap[name] = ColorEntry(r, g, b, a, t); | |
310 | 307 | } |
311 | 308 | } |
312 | 309 | |
329 | 326 | void TileGenerator::openDb(const std::string &input) |
330 | 327 | { |
331 | 328 | std::string backend = m_backend; |
332 | if(backend == "") { | |
333 | std::ifstream ifs((input + "/world.mt").c_str()); | |
329 | if (backend == "") { | |
330 | std::ifstream ifs(input + "/world.mt"); | |
334 | 331 | if(!ifs.good()) |
335 | throw std::runtime_error("Failed to read world.mt"); | |
336 | backend = read_setting("backend", ifs); | |
332 | throw std::runtime_error("Failed to open world.mt"); | |
333 | backend = read_setting_default("backend", ifs, "sqlite3"); | |
337 | 334 | ifs.close(); |
338 | 335 | } |
339 | 336 | |
585 | 582 | if (m_readPixels.get(x, z)) |
586 | 583 | continue; |
587 | 584 | int imageX = xBegin + x; |
585 | auto &attr = m_blockPixelAttributes.attribute(15 - z, xBegin + x); | |
588 | 586 | |
589 | 587 | for (int y = maxY; y >= minY; --y) { |
590 | string name = blk.getNode(x, y, z); | |
591 | if (name == "") | |
588 | const std::string &name = blk.getNode(x, y, z); | |
589 | if (name.empty()) | |
592 | 590 | continue; |
593 | 591 | ColorMap::const_iterator it = m_colorMap.find(name); |
594 | 592 | if (it == m_colorMap.end()) { |
595 | 593 | m_unknownNodes.insert(name); |
596 | 594 | continue; |
597 | 595 | } |
598 | const Color c = it->second.to_color(); | |
596 | ||
597 | Color c = it->second.toColor(); | |
598 | if (c.a == 0) | |
599 | continue; // node is fully invisible | |
599 | 600 | if (m_drawAlpha) { |
600 | if (m_color[z][x].a == 0) | |
601 | m_color[z][x] = c; // first visible time, no color mixing | |
602 | else | |
603 | m_color[z][x] = mixColors(m_color[z][x], c); | |
604 | if(m_color[z][x].a < 0xff) { | |
605 | // near thickness value to thickness of current node | |
606 | m_thickness[z][x] = (m_thickness[z][x] + it->second.t) / 2.0; | |
601 | if (m_color[z][x].a != 0) | |
602 | c = mixColors(m_color[z][x], c); | |
603 | if (c.a < 255) { | |
604 | // remember color and near thickness value | |
605 | m_color[z][x] = c; | |
606 | m_thickness[z][x] = (m_thickness[z][x] + it->second.t) / 2; | |
607 | 607 | continue; |
608 | 608 | } |
609 | 609 | // color became opaque, draw it |
610 | setZoomed(imageX, imageY, m_color[z][x]); | |
611 | m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x]; | |
610 | setZoomed(imageX, imageY, c); | |
611 | attr.thickness = m_thickness[z][x]; | |
612 | 612 | } else { |
613 | setZoomed(imageX, imageY, c.noAlpha()); | |
613 | c.a = 255; | |
614 | setZoomed(imageX, imageY, c); | |
614 | 615 | } |
615 | 616 | m_readPixels.set(x, z); |
616 | 617 | |
617 | 618 | // do this afterwards so we can record height values |
618 | 619 | // inside transparent nodes (water) too |
619 | 620 | if (!m_readInfo.get(x, z)) { |
620 | m_blockPixelAttributes.attribute(15 - z, xBegin + x).height = pos.y * 16 + y; | |
621 | attr.height = pos.y * 16 + y; | |
621 | 622 | m_readInfo.set(x, z); |
622 | 623 | } |
623 | 624 | break; |
639 | 640 | if (m_readPixels.get(x, z)) |
640 | 641 | continue; |
641 | 642 | int imageX = xBegin + x; |
643 | auto &attr = m_blockPixelAttributes.attribute(15 - z, xBegin + x); | |
642 | 644 | |
643 | 645 | // set color since it wasn't done in renderMapBlock() |
644 | 646 | setZoomed(imageX, imageY, m_color[z][x]); |
645 | 647 | m_readPixels.set(x, z); |
646 | m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x]; | |
648 | attr.thickness = m_thickness[z][x]; | |
647 | 649 | } |
648 | 650 | } |
649 | 651 | } |
650 | 652 | |
651 | 653 | void TileGenerator::renderShading(int zPos) |
652 | 654 | { |
655 | auto &a = m_blockPixelAttributes; | |
653 | 656 | int zBegin = (m_zMax - zPos) * 16; |
654 | 657 | for (int z = 0; z < 16; ++z) { |
655 | 658 | int imageY = zBegin + z; |
657 | 660 | continue; |
658 | 661 | for (int x = 0; x < m_mapWidth; ++x) { |
659 | 662 | if( |
660 | !m_blockPixelAttributes.attribute(z, x).valid_height() || | |
661 | !m_blockPixelAttributes.attribute(z, x - 1).valid_height() || | |
662 | !m_blockPixelAttributes.attribute(z - 1, x).valid_height() | |
663 | !a.attribute(z, x).valid_height() || | |
664 | !a.attribute(z, x - 1).valid_height() || | |
665 | !a.attribute(z - 1, x).valid_height() | |
663 | 666 | ) |
664 | 667 | continue; |
665 | 668 | |
666 | 669 | // calculate shadow to apply |
667 | int y = m_blockPixelAttributes.attribute(z, x).height; | |
668 | int y1 = m_blockPixelAttributes.attribute(z, x - 1).height; | |
669 | int y2 = m_blockPixelAttributes.attribute(z - 1, x).height; | |
670 | int y = a.attribute(z, x).height; | |
671 | int y1 = a.attribute(z, x - 1).height; | |
672 | int y2 = a.attribute(z - 1, x).height; | |
670 | 673 | int d = ((y - y1) + (y - y2)) * 12; |
674 | ||
671 | 675 | if (m_drawAlpha) { // less visible shadow with increasing "thickness" |
672 | double t = m_blockPixelAttributes.attribute(z, x).thickness * 1.2; | |
673 | d *= 1.0 - mymin(t, 255.0) / 255.0; | |
674 | } | |
676 | float t = a.attribute(z, x).thickness * 1.2f; | |
677 | t = mymin(t, 255.0f); | |
678 | d *= 1.0f - t / 255.0f; | |
679 | } | |
680 | ||
675 | 681 | d = mymin(d, 36); |
676 | 682 | |
683 | // apply shadow/light by just adding to it pixel values | |
677 | 684 | Color c = m_image->getPixel(getImageX(x), getImageY(imageY)); |
678 | 685 | c.r = colorSafeBounds(c.r + d); |
679 | 686 | c.g = colorSafeBounds(c.g + d); |
681 | 688 | setZoomed(x, imageY, c); |
682 | 689 | } |
683 | 690 | } |
684 | m_blockPixelAttributes.scroll(); | |
691 | a.scroll(); | |
685 | 692 | } |
686 | 693 | |
687 | 694 | void TileGenerator::renderScale() |
0 | /* | |
1 | * ===================================================================== | |
2 | * Version: 1.0 | |
3 | * Created: 18.09.2012 10:20:47 | |
4 | * Author: Miroslav BendÃk | |
5 | * Company: LinuxOS.sk | |
6 | * ===================================================================== | |
7 | */ | |
8 | ||
9 | 0 | #include <zlib.h> |
10 | 1 | #include <stdint.h> |
11 | 2 | #include "ZlibDecompressor.h" |
37 | 28 | const std::size_t size = m_size - m_seekPos; |
38 | 29 | |
39 | 30 | ustring buffer; |
40 | const size_t BUFSIZE = 128 * 1024; | |
41 | uint8_t temp_buffer[BUFSIZE]; | |
31 | constexpr size_t BUFSIZE = 128 * 1024; | |
32 | unsigned char temp_buffer[BUFSIZE]; | |
42 | 33 | |
43 | 34 | z_stream strm; |
44 | 35 | strm.zalloc = Z_NULL; |
47 | 38 | strm.next_in = Z_NULL; |
48 | 39 | strm.avail_in = size; |
49 | 40 | |
50 | if (inflateInit(&strm) != Z_OK) { | |
41 | if (inflateInit(&strm) != Z_OK) | |
51 | 42 | throw DecompressError(); |
52 | } | |
53 | 43 | |
54 | 44 | strm.next_in = const_cast<unsigned char *>(data); |
55 | 45 | int ret = 0; |
57 | 47 | strm.avail_out = BUFSIZE; |
58 | 48 | strm.next_out = temp_buffer; |
59 | 49 | ret = inflate(&strm, Z_NO_FLUSH); |
60 | buffer += ustring(reinterpret_cast<unsigned char *>(temp_buffer), BUFSIZE - strm.avail_out); | |
50 | buffer.append(temp_buffer, BUFSIZE - strm.avail_out); | |
61 | 51 | } while (ret == Z_OK); |
62 | if (ret != Z_STREAM_END) { | |
52 | if (ret != Z_STREAM_END) | |
63 | 53 | throw DecompressError(); |
64 | } | |
54 | ||
65 | 55 | m_seekPos += strm.next_in - data; |
66 | 56 | (void)inflateEnd(&strm); |
67 | 57 |
0 | ==FILE== mods/dumpnodes/init.lua | |
1 | local function nd_get_tiles(nd) | |
2 | return nd.tiles or nd.tile_images | |
3 | end | |
4 | ||
5 | local function nd_get_tile(nd, n) | |
6 | local tile = nd_get_tiles(nd)[n] | |
7 | if type(tile) == 'table' then | |
8 | tile = tile.name | |
9 | end | |
10 | return tile | |
11 | end | |
12 | ||
13 | local function pairs_s(dict) | |
14 | local keys = {} | |
15 | for k in pairs(dict) do | |
16 | table.insert(keys, k) | |
17 | end | |
18 | table.sort(keys) | |
19 | return ipairs(keys) | |
20 | end | |
21 | ||
22 | minetest.register_chatcommand("dumpnodes", { | |
23 | params = "", | |
24 | description = "", | |
25 | func = function(player, param) | |
26 | local n = 0 | |
27 | local ntbl = {} | |
28 | for _, nn in pairs_s(minetest.registered_nodes) do | |
29 | local nd = minetest.registered_nodes[nn] | |
30 | local prefix, name = nn:match('(.*):(.*)') | |
31 | if prefix == nil or name == nil then | |
32 | print("ignored(1): " .. nn) | |
33 | else | |
34 | if ntbl[prefix] == nil then | |
35 | ntbl[prefix] = {} | |
36 | end | |
37 | ntbl[prefix][name] = true | |
38 | end | |
39 | end | |
40 | local out, err = io.open('nodes.txt', 'wb') | |
41 | if not out then | |
42 | return true, "io.open(): " .. err | |
43 | end | |
44 | for _, prefix in pairs_s(ntbl) do | |
45 | out:write('# ' .. prefix .. '\n') | |
46 | for _, name in pairs_s(ntbl[prefix]) do | |
47 | local nn = prefix .. ":" .. name | |
48 | local nd = minetest.registered_nodes[nn] | |
49 | if nd.drawtype == 'airlike' or nd_get_tiles(nd) == nil then | |
50 | print("ignored(2): " .. nn) | |
51 | else | |
52 | local tl = nd_get_tile(nd, 1) | |
53 | tl = (tl .. '^'):match('(.-)^') -- strip modifiers | |
54 | out:write(nn .. ' ' .. tl .. '\n') | |
55 | n = n + 1 | |
56 | end | |
57 | end | |
58 | out:write('\n') | |
59 | end | |
60 | out:close() | |
61 | return true, n .. " nodes dumped." | |
62 | end, | |
63 | }) | |
64 | ==FILE== avgcolor.py | |
65 | #!/usr/bin/env python | |
66 | import sys | |
67 | from math import sqrt | |
68 | from PIL import Image | |
69 | ||
70 | if len(sys.argv) < 2: | |
71 | print("Prints average color (RGB) of input image") | |
72 | print("Usage: %s <input>" % sys.argv[0]) | |
73 | exit(1) | |
74 | ||
75 | inp = Image.open(sys.argv[1]).convert('RGBA') | |
76 | ind = inp.load() | |
77 | ||
78 | cl = ([], [], []) | |
79 | for x in range(inp.size[0]): | |
80 | for y in range(inp.size[1]): | |
81 | px = ind[x, y] | |
82 | if px[3] < 128: continue # alpha | |
83 | cl[0].append(px[0]**2) | |
84 | cl[1].append(px[1]**2) | |
85 | cl[2].append(px[2]**2) | |
86 | ||
87 | if len(cl[0]) == 0: | |
88 | print("Didn't find average color for %s" % sys.argv[1], file=sys.stderr) | |
89 | print("0 0 0") | |
90 | else: | |
91 | cl = tuple(sqrt(sum(x)/len(x)) for x in cl) | |
92 | print("%d %d %d" % cl) | |
93 | ==SCRIPT== | |
94 | #!/bin/bash -e | |
95 | AVGCOLOR_PATH=/path/to/avgcolor.py | |
96 | GAME_PATH=/path/to/minetest_game | |
97 | MODS_PATH= # path to "mods" folder, only set if you have loaded mods | |
98 | NODESTXT_PATH=./nodes.txt | |
99 | COLORSTXT_PATH=./colors.txt | |
100 | ||
101 | while read -r line; do | |
102 | set -- junk $line; shift | |
103 | if [[ -z "$1" || $1 == "#" ]]; then | |
104 | echo "$line"; continue | |
105 | fi | |
106 | tex=$(find $GAME_PATH -type f -name "$2") | |
107 | [[ -z "$tex" && -n "$MODS_PATH" ]] && tex=$(find $MODS_PATH -type f -name "$2") | |
108 | if [ -z "$tex" ]; then | |
109 | echo "skip $1: texture not found" >&2 | |
110 | continue | |
111 | fi | |
112 | echo "$1" $(python $AVGCOLOR_PATH "$tex") | |
113 | echo "ok $1" >&2 | |
114 | done < $NODESTXT_PATH > $COLORSTXT_PATH | |
115 | # Use nicer colors for water and lava: | |
116 | sed -re 's/^default:((river_)?water_(flowing|source)) [0-9 ]+$/default:\1 39 66 106 128 224/g' $COLORSTXT_PATH -i | |
117 | sed -re 's/^default:(lava_(flowing|source)) [0-9 ]+$/default:\1 255 100 0/g' $COLORSTXT_PATH -i | |
118 | # Add transparency to glass nodes and xpanes: | |
119 | sed -re 's/^default:(.*glass) ([0-9 ]+)$/default:\1 \2 64 16/g' $COLORSTXT_PATH -i | |
120 | sed -re 's/^doors:(.*glass[^ ]*) ([0-9 ]+)$/doors:\1 \2 64 16/g' $COLORSTXT_PATH -i | |
121 | sed -re 's/^xpanes:(.*(pane|bar)[^ ]*) ([0-9 ]+)$/xpanes:\1 \3 64 16/g' $COLORSTXT_PATH -i | |
122 | # Delete some usually hidden nodes: | |
123 | sed '/^doors:hidden /d' $COLORSTXT_PATH -i | |
124 | sed '/^fireflies:firefly /d' $COLORSTXT_PATH -i | |
125 | sed '/^butterflies:butterfly_/d' $COLORSTXT_PATH -i | |
126 | ==INSTRUCTIONS== | |
127 | 1) Make sure avgcolors.py works (outputs the usage instructions when run) | |
128 | 2) Add the dumpnodes mod to Minetest | |
129 | 3) Create a world and load dumpnodes & all mods you want to generate colors for | |
130 | 4) Execute /dumpnodes ingame | |
131 | 5) Run the script to generate colors.txt (make sure to adjust the PATH variables at the top) |
0 | #ifndef BLOCKDECODER_H | |
1 | #define BLOCKDECODER_H | |
0 | #pragma once | |
2 | 1 | |
3 | 2 | #include <unordered_map> |
4 | 3 | |
11 | 10 | void reset(); |
12 | 11 | void decode(const ustring &data); |
13 | 12 | bool isEmpty() const; |
14 | std::string getNode(u8 x, u8 y, u8 z) const; // returns "" for air, ignore and invalid nodes | |
13 | // returns "" for air, ignore and invalid nodes | |
14 | const std::string &getNode(u8 x, u8 y, u8 z) const; | |
15 | 15 | |
16 | 16 | private: |
17 | typedef std::unordered_map<int, std::string> NameMap; | |
17 | typedef std::unordered_map<uint16_t, std::string> NameMap; | |
18 | 18 | NameMap m_nameMap; |
19 | 19 | int m_blockAirId; |
20 | 20 | int m_blockIgnoreId; |
21 | 21 | |
22 | u8 m_version; | |
22 | u8 m_version, m_contentWidth; | |
23 | 23 | ustring m_mapData; |
24 | 24 | }; |
25 | ||
26 | #endif // BLOCKDECODER_H |
0 | #ifndef IMAGE_HEADER | |
1 | #define IMAGE_HEADER | |
0 | #pragma once | |
2 | 1 | |
3 | 2 | #include "types.h" |
4 | 3 | #include <string> |
8 | 7 | Color() : r(0), g(0), b(0), a(0) {}; |
9 | 8 | Color(u8 r, u8 g, u8 b) : r(r), g(g), b(b), a(255) {}; |
10 | 9 | Color(u8 r, u8 g, u8 b, u8 a) : r(r), g(g), b(b), a(a) {}; |
11 | inline Color noAlpha() const { return Color(r, g, b); } | |
12 | 10 | |
13 | 11 | u8 r, g, b, a; |
14 | 12 | }; |
17 | 15 | public: |
18 | 16 | Image(int width, int height); |
19 | 17 | ~Image(); |
18 | ||
19 | Image(const Image&) = delete; | |
20 | Image& operator=(const Image&) = delete; | |
20 | 21 | |
21 | 22 | void setPixel(int x, int y, const Color &c); |
22 | 23 | Color getPixel(int x, int y); |
27 | 28 | void save(const std::string &filename); |
28 | 29 | |
29 | 30 | private: |
30 | Image(const Image&); | |
31 | ||
32 | 31 | int m_width, m_height; |
33 | 32 | gdImagePtr m_image; |
34 | 33 | }; |
35 | ||
36 | #endif // IMAGE_HEADER |
0 | /* | |
1 | * ===================================================================== | |
2 | * Version: 1.0 | |
3 | * Created: 25.08.2012 10:55:29 | |
4 | * Author: Miroslav BendÃk | |
5 | * Company: LinuxOS.sk | |
6 | * ===================================================================== | |
7 | */ | |
0 | #pragma once | |
8 | 1 | |
9 | #ifndef PIXELATTRIBUTES_H_ADZ35GYF | |
10 | #define PIXELATTRIBUTES_H_ADZ35GYF | |
11 | ||
12 | #include <limits> | |
13 | #include <stdint.h> | |
2 | #include <climits> | |
3 | #include <cstdint> | |
14 | 4 | #include "config.h" |
15 | 5 | |
16 | 6 | struct PixelAttribute { |
17 | PixelAttribute(): height(std::numeric_limits<int>::min()), thickness(0) {}; | |
18 | int height; | |
7 | PixelAttribute(): height(INT16_MIN), thickness(0) {}; | |
8 | int16_t height; | |
19 | 9 | uint8_t thickness; |
20 | 10 | inline bool valid_height() { |
21 | return height != std::numeric_limits<int>::min(); | |
11 | return height != INT16_MIN; | |
22 | 12 | } |
23 | 13 | }; |
24 | 14 | |
29 | 19 | virtual ~PixelAttributes(); |
30 | 20 | void setWidth(int width); |
31 | 21 | void scroll(); |
32 | inline PixelAttribute &attribute(int z, int x) { return m_pixelAttributes[z + 1][x + 1]; }; | |
22 | inline PixelAttribute &attribute(int z, int x) { | |
23 | return m_pixelAttributes[z + 1][x + 1]; | |
24 | }; | |
33 | 25 | |
34 | 26 | private: |
35 | 27 | void freeAttributes(); |
44 | 36 | PixelAttribute *m_pixelAttributes[BLOCK_SIZE + 2]; // 1px gradient + empty |
45 | 37 | int m_width; |
46 | 38 | }; |
47 | ||
48 | #endif /* end of include guard: PIXELATTRIBUTES_H_ADZ35GYF */ | |
49 |
0 | #ifndef PLAYERATTRIBUTES_H_D7THWFVV | |
1 | #define PLAYERATTRIBUTES_H_D7THWFVV | |
0 | #pragma once | |
2 | 1 | |
3 | 2 | #include <list> |
4 | 3 | #include <string> |
15 | 14 | typedef std::list<Player> Players; |
16 | 15 | |
17 | 16 | PlayerAttributes(const std::string &worldDir); |
18 | Players::iterator begin(); | |
19 | Players::iterator end(); | |
17 | Players::const_iterator begin() const; | |
18 | Players::const_iterator end() const; | |
20 | 19 | |
21 | 20 | private: |
22 | 21 | void readFiles(const std::string &playersPath); |
24 | 23 | |
25 | 24 | Players m_players; |
26 | 25 | }; |
27 | ||
28 | #endif /* end of include guard: PLAYERATTRIBUTES_H_D7THWFVV */ | |
29 |
0 | 0 | #ifndef TILEGENERATOR_HEADER |
1 | 1 | #define TILEGENERATOR_HEADER |
2 | 2 | |
3 | #include <iosfwd> | |
3 | #include <iostream> | |
4 | 4 | #include <map> |
5 | 5 | #include <set> |
6 | #include <config.h> | |
7 | 6 | #include <unordered_map> |
8 | 7 | #include <unordered_set> |
9 | #include <stdint.h> | |
8 | #include <cstdint> | |
10 | 9 | #include <string> |
11 | 10 | |
12 | 11 | #include "PixelAttributes.h" |
13 | #include "BlockDecoder.h" | |
14 | 12 | #include "Image.h" |
15 | 13 | #include "db.h" |
16 | 14 | #include "types.h" |
15 | ||
16 | class BlockDecoder; | |
17 | class Image; | |
17 | 18 | |
18 | 19 | enum { |
19 | 20 | SCALE_TOP = (1 << 0), |
30 | 31 | }; |
31 | 32 | |
32 | 33 | struct ColorEntry { |
33 | ColorEntry(): r(0), g(0), b(0), a(0), t(0) {}; | |
34 | ColorEntry(uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t t): r(r), g(g), b(b), a(a), t(t) {}; | |
35 | inline Color to_color() const { return Color(r, g, b, a); } | |
36 | uint8_t r, g, b, a, t; | |
34 | ColorEntry() : r(0), g(0), b(0), a(0), t(0) {}; | |
35 | ColorEntry(uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t t) : | |
36 | r(r), g(g), b(b), a(a), t(t) {}; | |
37 | inline Color toColor() const { return Color(r, g, b, a); } | |
38 | uint8_t r, g, b, a; // Red, Green, Blue, Alpha | |
39 | uint8_t t; // "thickness" value | |
37 | 40 | }; |
38 | 41 | |
39 | 42 | struct BitmapThing { // 16x16 bitmap |
72 | 75 | void setScaleColor(const std::string &scaleColor); |
73 | 76 | void setOriginColor(const std::string &originColor); |
74 | 77 | void setPlayerColor(const std::string &playerColor); |
75 | Color parseColor(const std::string &color); | |
76 | 78 | void setDrawOrigin(bool drawOrigin); |
77 | 79 | void setDrawPlayers(bool drawPlayers); |
78 | 80 | void setDrawScale(bool drawScale); |
0 | /* | |
1 | * ===================================================================== | |
2 | * Version: 1.0 | |
3 | * Created: 18.09.2012 10:20:51 | |
4 | * Author: Miroslav BendÃk | |
5 | * Company: LinuxOS.sk | |
6 | * ===================================================================== | |
7 | */ | |
8 | ||
9 | #ifndef ZLIBDECOMPRESSOR_H_ZQL1PN8Q | |
10 | #define ZLIBDECOMPRESSOR_H_ZQL1PN8Q | |
0 | #pragma once | |
11 | 1 | |
12 | 2 | #include <cstdlib> |
13 | 3 | #include <string> |
30 | 20 | const unsigned char *m_data; |
31 | 21 | std::size_t m_seekPos; |
32 | 22 | std::size_t m_size; |
33 | }; /* ----- end of class ZlibDecompressor ----- */ | |
34 | ||
35 | #endif /* end of include guard: ZLIBDECOMPRESSOR_H_ZQL1PN8Q */ | |
36 | ||
23 | }; |
0 | #ifndef DB_LEVELDB_HEADER | |
1 | #define DB_LEVELDB_HEADER | |
0 | #pragma once | |
2 | 1 | |
3 | 2 | #include "db.h" |
4 | 3 | #include <unordered_map> |
26 | 25 | std::unordered_map<int16_t, std::vector<pos2d>> posCache; |
27 | 26 | leveldb::DB *db; |
28 | 27 | }; |
29 | ||
30 | #endif // DB_LEVELDB_HEADER |
0 | #ifndef _DB_POSTGRESQL_H | |
1 | #define _DB_POSTGRESQL_H | |
0 | #pragma once | |
2 | 1 | |
3 | 2 | #include "db.h" |
4 | 3 | #include <libpq-fe.h> |
30 | 29 | private: |
31 | 30 | PGconn *db; |
32 | 31 | }; |
33 | ||
34 | #endif // _DB_POSTGRESQL_H |
0 | #ifndef DB_REDIS_HEADER | |
1 | #define DB_REDIS_HEADER | |
0 | #pragma once | |
2 | 1 | |
3 | 2 | #include "db.h" |
4 | 3 | #include <unordered_map> |
32 | 31 | redisContext *ctx; |
33 | 32 | std::string hash; |
34 | 33 | }; |
35 | ||
36 | #endif // DB_REDIS_HEADER |
0 | #ifndef _DB_SQLITE3_H | |
1 | #define _DB_SQLITE3_H | |
0 | #pragma once | |
2 | 1 | |
3 | 2 | #include "db.h" |
4 | 3 | #include <unordered_map> |
31 | 30 | int16_t blockCachedZ = -10000; |
32 | 31 | std::unordered_map<int16_t, BlockList> blockCache; // indexed by X |
33 | 32 | }; |
34 | ||
35 | #endif // _DB_SQLITE3_H |
0 | #ifndef DB_HEADER | |
1 | #define DB_HEADER | |
0 | #pragma once | |
2 | 1 | |
3 | #include <stdint.h> | |
4 | #include <map> | |
2 | #include <cstdint> | |
5 | 3 | #include <list> |
6 | 4 | #include <vector> |
7 | 5 | #include <utility> |
121 | 119 | * End black magic * |
122 | 120 | *******************/ |
123 | 121 | |
124 | #endif // DB_HEADER |
0 | #ifndef UTIL_H | |
1 | #define UTIL_H | |
0 | #pragma once | |
2 | 1 | |
3 | 2 | #include <string> |
4 | 3 | #include <fstream> |
5 | 4 | |
6 | 5 | std::string read_setting(const std::string &name, std::istream &is); |
7 | 6 | |
8 | inline std::string read_setting_default(const std::string &name, std::istream &is, const std::string &def) | |
9 | { | |
10 | try { | |
11 | return read_setting(name, is); | |
12 | } catch(const std::runtime_error &e) { | |
13 | return def; | |
14 | } | |
15 | } | |
16 | ||
17 | #endif // UTIL_H | |
7 | std::string read_setting_default(const std::string &name, std::istream &is, | |
8 | const std::string &def); |
7 | 7 | #include <string> |
8 | 8 | #include <sstream> |
9 | 9 | #include <stdexcept> |
10 | #include "cmake_config.h" | |
10 | #include "config.h" | |
11 | 11 | #include "TileGenerator.h" |
12 | 12 | |
13 | 13 | static void usage() |
57 | 57 | |
58 | 58 | static bool file_exists(const std::string &path) |
59 | 59 | { |
60 | std::ifstream ifs(path.c_str()); | |
60 | std::ifstream ifs(path); | |
61 | 61 | return ifs.is_open(); |
62 | 62 | } |
63 | 63 | |
255 | 255 | generator.parseColorsFile(colors); |
256 | 256 | generator.generate(input, output); |
257 | 257 | |
258 | } catch(std::runtime_error &e) { | |
258 | } catch (const std::runtime_error &e) { | |
259 | 259 | std::cerr << "Exception: " << e.what() << std::endl; |
260 | 260 | return 1; |
261 | 261 | } |
0 | local function get_tile(tiles, n) | |
1 | local tile = tiles[n] | |
2 | if type(tile) == 'table' then | |
3 | return tile.name | |
4 | end | |
5 | return tile | |
6 | end | |
7 | ||
8 | local function pairs_s(dict) | |
9 | local keys = {} | |
10 | for k in pairs(dict) do | |
11 | keys[#keys+1] = k | |
12 | end | |
13 | table.sort(keys) | |
14 | return ipairs(keys) | |
15 | end | |
16 | ||
17 | minetest.register_chatcommand("dumpnodes", { | |
18 | description = "Dump node and texture list for use with minetestmapper", | |
19 | func = function() | |
20 | local ntbl = {} | |
21 | for _, nn in pairs_s(minetest.registered_nodes) do | |
22 | local prefix, name = nn:match('(.*):(.*)') | |
23 | if prefix == nil or name == nil then | |
24 | print("ignored(1): " .. nn) | |
25 | else | |
26 | if ntbl[prefix] == nil then | |
27 | ntbl[prefix] = {} | |
28 | end | |
29 | ntbl[prefix][name] = true | |
30 | end | |
31 | end | |
32 | local out, err = io.open(minetest.get_worldpath() .. "/nodes.txt", 'wb') | |
33 | if not out then | |
34 | return true, err | |
35 | end | |
36 | local n = 0 | |
37 | for _, prefix in pairs_s(ntbl) do | |
38 | out:write('# ' .. prefix .. '\n') | |
39 | for _, name in pairs_s(ntbl[prefix]) do | |
40 | local nn = prefix .. ":" .. name | |
41 | local nd = minetest.registered_nodes[nn] | |
42 | local tiles = nd.tiles or nd.tile_images | |
43 | if tiles == nil or nd.drawtype == 'airlike' then | |
44 | print("ignored(2): " .. nn) | |
45 | else | |
46 | local tex = get_tile(tiles, 1) | |
47 | tex = (tex .. '^'):match('%(*(.-)%)*^') -- strip modifiers | |
48 | if tex:find("[combine", 1, true) then | |
49 | tex = tex:match('.-=([^:]-)') -- extract first texture | |
50 | end | |
51 | out:write(nn .. ' ' .. tex .. '\n') | |
52 | n = n + 1 | |
53 | end | |
54 | end | |
55 | out:write('\n') | |
56 | end | |
57 | out:close() | |
58 | return true, n .. " nodes dumped." | |
59 | end, | |
60 | }) |
0 | #!/usr/bin/env python3 | |
1 | import sys | |
2 | import os.path | |
3 | import getopt | |
4 | import re | |
5 | from math import sqrt | |
6 | try: | |
7 | from PIL import Image | |
8 | except: | |
9 | print("Could not load image routines, install PIL ('pillow' on pypi)!", file=sys.stderr) | |
10 | exit(1) | |
11 | ||
12 | ############ | |
13 | ############ | |
14 | # Instructions for generating a colors.txt file for custom games and/or mods: | |
15 | # 1) Add the dumpnodes mod to a Minetest world with the chosen game and mods enabled. | |
16 | # 2) Join ingame and run the /dumpnodes chat command. | |
17 | # 3) Run this script and poin it to the installation path of the game using -g, | |
18 | # the path(s) where mods are stored using -m and the nodes.txt in your world folder. | |
19 | # Example command line: | |
20 | # ./util/generate_colorstxt.py --game /usr/share/minetest/games/minetest_game \ | |
21 | # -m ~/.minetest/mods ~/.minetest/worlds/my_world/nodes.txt | |
22 | # 4) Copy the resulting colors.txt file to your world folder or to any other places | |
23 | # and use it with minetestmapper's --colors option. | |
24 | ########### | |
25 | ########### | |
26 | ||
27 | # minimal sed syntax, s|match|replace| and /match/d supported | |
28 | REPLACEMENTS = [ | |
29 | # Delete some nodes that are usually hidden | |
30 | r'/^fireflies:firefly /d', | |
31 | r'/^butterflies:butterfly_/d', | |
32 | # Nicer colors for water and lava | |
33 | r's/^(default:(river_)?water_(flowing|source)) [0-9 ]+$/\1 39 66 106 128 224/', | |
34 | r's/^(default:lava_(flowing|source)) [0-9 ]+$/\1 255 100 0/', | |
35 | # Transparency for glass nodes and panes | |
36 | r's/^(default:.*glass) ([0-9 ]+)$/\1 \2 64 16/', | |
37 | r's/^(doors:.*glass[^ ]*) ([0-9 ]+)$/\1 \2 64 16/', | |
38 | r's/^(xpanes:.*(pane|bar)[^ ]*) ([0-9 ]+)$/\1 \3 64 16/', | |
39 | ] | |
40 | ||
41 | def usage(): | |
42 | print("Usage: generate_colorstxt.py [options] [input file] [output file]") | |
43 | print("If not specified the input file defaults to ./nodes.txt and the output file to ./colors.txt") | |
44 | print(" -g / --game <folder>\t\tSet path to the game (for textures), required") | |
45 | print(" -m / --mods <folder>\t\tAdd search path for mod textures") | |
46 | print(" --replace <file>\t\tLoad replacements from file (ADVANCED)") | |
47 | ||
48 | def collect_files(path): | |
49 | dirs = [] | |
50 | with os.scandir(path) as it: | |
51 | for entry in it: | |
52 | if entry.name[0] == '.': continue | |
53 | if entry.is_dir(): | |
54 | dirs.append(entry.path) | |
55 | continue | |
56 | if entry.is_file() and '.' in entry.name: | |
57 | if entry.name not in textures.keys(): | |
58 | textures[entry.name] = entry.path | |
59 | for path2 in dirs: | |
60 | collect_files(path2) | |
61 | ||
62 | def average_color(filename): | |
63 | inp = Image.open(filename).convert('RGBA') | |
64 | data = inp.load() | |
65 | ||
66 | c0, c1, c2 = [], [], [] | |
67 | for x in range(inp.size[0]): | |
68 | for y in range(inp.size[1]): | |
69 | px = data[x, y] | |
70 | if px[3] < 128: continue # alpha | |
71 | c0.append(px[0]**2) | |
72 | c1.append(px[1]**2) | |
73 | c2.append(px[2]**2) | |
74 | ||
75 | if len(c0) == 0: | |
76 | print(f"didn't find color for '{os.path.basename(filename)}'", file=sys.stderr) | |
77 | return "0 0 0" | |
78 | c0 = sqrt(sum(c0) / len(c0)) | |
79 | c1 = sqrt(sum(c1) / len(c1)) | |
80 | c2 = sqrt(sum(c2) / len(c2)) | |
81 | return "%d %d %d" % (c0, c1, c2) | |
82 | ||
83 | def apply_sed(line, exprs): | |
84 | for expr in exprs: | |
85 | if expr[0] == '/': | |
86 | if not expr.endswith("/d"): raise ValueError() | |
87 | if re.search(expr[1:-2], line): | |
88 | return '' | |
89 | elif expr[0] == 's': | |
90 | expr = expr.split(expr[1]) | |
91 | if len(expr) != 4 or expr[3] != '': raise ValueError() | |
92 | line = re.sub(expr[1], expr[2], line) | |
93 | else: | |
94 | raise ValueError() | |
95 | return line | |
96 | # | |
97 | ||
98 | try: | |
99 | opts, args = getopt.getopt(sys.argv[1:], "hg:m:", ["help", "game=", "mods=", "replace="]) | |
100 | except getopt.GetoptError as e: | |
101 | print(str(e)) | |
102 | exit(1) | |
103 | if ('-h', '') in opts or ('--help', '') in opts: | |
104 | usage() | |
105 | exit(0) | |
106 | ||
107 | input_file = "./nodes.txt" | |
108 | output_file = "./colors.txt" | |
109 | texturepaths = [] | |
110 | ||
111 | try: | |
112 | gamepath = next(o[1] for o in opts if o[0] in ('-g', '--game')) | |
113 | if not os.path.isdir(os.path.join(gamepath, "mods")): | |
114 | print(f"'{gamepath}' doesn't exist or does not contain a game.", file=sys.stderr) | |
115 | exit(1) | |
116 | texturepaths.append(os.path.join(gamepath, "mods")) | |
117 | except StopIteration: | |
118 | print("No game path set but one is required. (see --help)", file=sys.stderr) | |
119 | exit(1) | |
120 | ||
121 | try: | |
122 | tmp = next(o[1] for o in opts if o[0] == "--replace") | |
123 | REPLACEMENTS.clear() | |
124 | with open(tmp, 'r') as f: | |
125 | for line in f: | |
126 | if not line or line[0] == '#': continue | |
127 | REPLACEMENTS.append(line.strip()) | |
128 | except StopIteration: | |
129 | pass | |
130 | ||
131 | for o in opts: | |
132 | if o[0] not in ('-m', '--mods'): continue | |
133 | if not os.path.isdir(o[1]): | |
134 | print(f"Given path '{o[1]}' does not exist.'", file=sys.stderr) | |
135 | exit(1) | |
136 | texturepaths.append(o[1]) | |
137 | ||
138 | if len(args) > 2: | |
139 | print("Too many arguments.", file=sys.stderr) | |
140 | exit(1) | |
141 | if len(args) > 1: | |
142 | output_file = args[1] | |
143 | if len(args) > 0: | |
144 | input_file = args[0] | |
145 | ||
146 | if not os.path.exists(input_file) or os.path.isdir(input_file): | |
147 | print(f"Input file '{input_file}' does not exist.", file=sys.stderr) | |
148 | exit(1) | |
149 | ||
150 | # | |
151 | ||
152 | print(f"Collecting textures from {len(texturepaths)} path(s)... ", end="", flush=True) | |
153 | textures = {} | |
154 | for path in texturepaths: | |
155 | collect_files(path) | |
156 | print("done") | |
157 | ||
158 | print("Processing nodes...") | |
159 | fin = open(input_file, 'r') | |
160 | fout = open(output_file, 'w') | |
161 | n = 0 | |
162 | for line in fin: | |
163 | line = line.rstrip('\r\n') | |
164 | if not line or line[0] == '#': | |
165 | fout.write(line + '\n') | |
166 | continue | |
167 | node, tex = line.split(" ") | |
168 | if not tex or tex == "blank.png": | |
169 | continue | |
170 | elif tex not in textures.keys(): | |
171 | print(f"skip {node} texture not found") | |
172 | continue | |
173 | color = average_color(textures[tex]) | |
174 | line = f"{node} {color}" | |
175 | #print(f"ok {node}") | |
176 | line = apply_sed(line, REPLACEMENTS) | |
177 | if line: | |
178 | fout.write(line + '\n') | |
179 | n += 1 | |
180 | fin.close() | |
181 | fout.close() | |
182 | print(f"Done, {n} entries written.") |
42 | 42 | oss << "Setting '" << name << "' not found"; |
43 | 43 | throw std::runtime_error(oss.str()); |
44 | 44 | } |
45 | ||
46 | std::string read_setting_default(const std::string &name, std::istream &is, | |
47 | const std::string &def) | |
48 | { | |
49 | try { | |
50 | return read_setting(name, is); | |
51 | } catch(const std::runtime_error &e) { | |
52 | return def; | |
53 | } | |
54 | } |