mirror of
				https://github.com/luanti-org/minetestmapper.git
				synced 2025-11-04 09:55:30 +01:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			20241111
			...
			night_rend
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					55e407c130 | 
@@ -1,16 +0,0 @@
 | 
			
		||||
.git
 | 
			
		||||
.github
 | 
			
		||||
 | 
			
		||||
*~
 | 
			
		||||
 | 
			
		||||
minetestmapper
 | 
			
		||||
minetestmapper.exe
 | 
			
		||||
 | 
			
		||||
CMakeCache.txt
 | 
			
		||||
CMakeFiles/
 | 
			
		||||
CPack*.cmake
 | 
			
		||||
_CPack_Packages/
 | 
			
		||||
install_manifest.txt
 | 
			
		||||
Makefile
 | 
			
		||||
cmake_install.cmake
 | 
			
		||||
cmake_config.h
 | 
			
		||||
							
								
								
									
										63
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										63
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,63 +0,0 @@
 | 
			
		||||
name: build
 | 
			
		||||
 | 
			
		||||
# build on c/cpp changes or workflow changes
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths:
 | 
			
		||||
      - '**.[ch]'
 | 
			
		||||
      - '**.cpp'
 | 
			
		||||
      - '**/CMakeLists.txt'
 | 
			
		||||
      - 'util/ci/**'
 | 
			
		||||
      - '.github/workflows/**.yml'
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths:
 | 
			
		||||
      - '**.[ch]'
 | 
			
		||||
      - '**.cpp'
 | 
			
		||||
      - '**/CMakeLists.txt'
 | 
			
		||||
      - 'util/ci/**'
 | 
			
		||||
      - '.github/workflows/**.yml'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  gcc:
 | 
			
		||||
    runs-on: ubuntu-22.04
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - name: Install deps
 | 
			
		||||
        run: |
 | 
			
		||||
          source util/ci/script.sh
 | 
			
		||||
          install_linux_deps
 | 
			
		||||
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: |
 | 
			
		||||
          source util/ci/script.sh
 | 
			
		||||
          run_build
 | 
			
		||||
        env:
 | 
			
		||||
          CC: gcc
 | 
			
		||||
          CXX: g++
 | 
			
		||||
 | 
			
		||||
      - name: Test
 | 
			
		||||
        run: |
 | 
			
		||||
          source util/ci/script.sh
 | 
			
		||||
          do_functional_test
 | 
			
		||||
 | 
			
		||||
  clang:
 | 
			
		||||
    runs-on: ubuntu-22.04
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - name: Install deps
 | 
			
		||||
        run: |
 | 
			
		||||
          source util/ci/script.sh
 | 
			
		||||
          install_linux_deps
 | 
			
		||||
 | 
			
		||||
      - name: Build
 | 
			
		||||
        run: |
 | 
			
		||||
          source util/ci/script.sh
 | 
			
		||||
          run_build
 | 
			
		||||
        env:
 | 
			
		||||
          CC: clang
 | 
			
		||||
          CXX: clang++
 | 
			
		||||
 | 
			
		||||
      - name: Test
 | 
			
		||||
        run: |
 | 
			
		||||
          source util/ci/script.sh
 | 
			
		||||
          do_functional_test
 | 
			
		||||
							
								
								
									
										87
									
								
								.github/workflows/docker_image.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										87
									
								
								.github/workflows/docker_image.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,87 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: docker_image
 | 
			
		||||
 | 
			
		||||
# https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
 | 
			
		||||
# https://docs.docker.com/build/ci/github-actions/multi-platform
 | 
			
		||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [ "master" ]
 | 
			
		||||
    # Publish semver tags as releases.
 | 
			
		||||
    tags: [ "*" ]
 | 
			
		||||
  pull_request:
 | 
			
		||||
    # Build docker image on pull requests. (but do not publish)
 | 
			
		||||
    paths:
 | 
			
		||||
      - '**/**.[ch]'
 | 
			
		||||
      - '**/**.cpp'
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
    inputs:
 | 
			
		||||
      use_cache:
 | 
			
		||||
        description: "Use build cache"
 | 
			
		||||
        required: true
 | 
			
		||||
        type: boolean
 | 
			
		||||
        default: true
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  REGISTRY: ghcr.io
 | 
			
		||||
  # github.repository as <account>/<repo>
 | 
			
		||||
  IMAGE_NAME: ${{ github.repository }}
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  publish:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: read
 | 
			
		||||
      packages: write
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out repository
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
 | 
			
		||||
      - name: Setup Docker buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.0.0
 | 
			
		||||
 | 
			
		||||
      # Login against the Docker registry except on PR
 | 
			
		||||
      # https://github.com/docker/login-action
 | 
			
		||||
      - name: Log into registry ${{ env.REGISTRY }}
 | 
			
		||||
        if: github.event_name != 'pull_request'
 | 
			
		||||
        uses: docker/login-action@v3.0.0
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ${{ env.REGISTRY }}
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
      # Extract metadata (tags, labels) for Docker
 | 
			
		||||
      # https://github.com/docker/metadata-action
 | 
			
		||||
      - name: Extract Docker metadata
 | 
			
		||||
        id: meta
 | 
			
		||||
        uses: docker/metadata-action@v5.5.0
 | 
			
		||||
        with:
 | 
			
		||||
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
 | 
			
		||||
          labels: |
 | 
			
		||||
            org.opencontainers.image.title=Minetestmapper
 | 
			
		||||
            org.opencontainers.image.vendor=Luanti
 | 
			
		||||
            org.opencontainers.image.licenses=BSD 2-Clause
 | 
			
		||||
 | 
			
		||||
      # Build and push Docker image
 | 
			
		||||
      # https://github.com/docker/build-push-action
 | 
			
		||||
      # No arm support for now. Require cross-compilation support in Dockerfile to not use QEMU.
 | 
			
		||||
      - name: Build and push Docker image
 | 
			
		||||
        uses: docker/build-push-action@v5.1.0
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          platforms: linux/amd64
 | 
			
		||||
          push: ${{ github.event_name != 'pull_request' }}
 | 
			
		||||
          load: true
 | 
			
		||||
          tags: ${{ steps.meta.outputs.tags }}
 | 
			
		||||
          labels: ${{ steps.meta.outputs.labels }}
 | 
			
		||||
          cache-from: type=gha
 | 
			
		||||
          cache-to: type=gha,mode=max
 | 
			
		||||
          no-cache: ${{ (github.event_name == 'workflow_dispatch' && !inputs.use_cache) || startsWith(github.ref, 'refs/tags/') }}
 | 
			
		||||
 | 
			
		||||
      - name: Test Docker Image
 | 
			
		||||
        run: |
 | 
			
		||||
          docker run --rm $(cut -d, -f1 <<<"$DOCKER_METADATA_OUTPUT_TAGS") minetestmapper --help
 | 
			
		||||
        shell: bash
 | 
			
		||||
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,14 +1,10 @@
 | 
			
		||||
*~
 | 
			
		||||
 | 
			
		||||
colors.txt
 | 
			
		||||
minetestmapper
 | 
			
		||||
minetestmapper.exe
 | 
			
		||||
colors.txt
 | 
			
		||||
 | 
			
		||||
CMakeCache.txt
 | 
			
		||||
CMakeFiles/
 | 
			
		||||
CPack*.cmake
 | 
			
		||||
_CPack_Packages/
 | 
			
		||||
install_manifest.txt
 | 
			
		||||
CPack*
 | 
			
		||||
Makefile
 | 
			
		||||
cmake_install.cmake
 | 
			
		||||
cmake_config.h
 | 
			
		||||
*~
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
language: cpp
 | 
			
		||||
compiler:
 | 
			
		||||
  - gcc
 | 
			
		||||
  - clang
 | 
			
		||||
dist: bionic
 | 
			
		||||
addons:
 | 
			
		||||
  apt:
 | 
			
		||||
    packages:
 | 
			
		||||
      - cmake
 | 
			
		||||
      - libgd-dev
 | 
			
		||||
      - libsqlite3-dev
 | 
			
		||||
      - libleveldb-dev
 | 
			
		||||
      - libpq-dev
 | 
			
		||||
      - postgresql-server-dev-all
 | 
			
		||||
script: ./util/travis/script.sh
 | 
			
		||||
notifications:
 | 
			
		||||
  email: false
 | 
			
		||||
matrix:
 | 
			
		||||
  fast_finish: true
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
@@ -10,8 +11,7 @@ static inline uint16_t readU16(const unsigned char *data)
 | 
			
		||||
	return data[0] << 8 | data[1];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline uint16_t readBlockContent(const unsigned char *mapData,
 | 
			
		||||
	u8 contentWidth, unsigned int datapos)
 | 
			
		||||
static int readBlockContent(const unsigned char *mapData, u8 contentWidth, unsigned int datapos)
 | 
			
		||||
{
 | 
			
		||||
	if (contentWidth == 2) {
 | 
			
		||||
		size_t index = datapos << 1;
 | 
			
		||||
@@ -21,7 +21,7 @@ static inline uint16_t readBlockContent(const unsigned char *mapData,
 | 
			
		||||
		if (param <= 0x7f)
 | 
			
		||||
			return param;
 | 
			
		||||
		else
 | 
			
		||||
			return (param << 4) | (mapData[datapos + 0x2000] >> 4);
 | 
			
		||||
			return (int(param) << 4) | (int(mapData[datapos + 0x2000]) >> 4);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -38,7 +38,7 @@ void BlockDecoder::reset()
 | 
			
		||||
 | 
			
		||||
	m_version = 0;
 | 
			
		||||
	m_contentWidth = 0;
 | 
			
		||||
	m_mapData.clear();
 | 
			
		||||
	m_mapData = ustring();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BlockDecoder::decode(const ustring &datastr)
 | 
			
		||||
@@ -48,6 +48,7 @@ void BlockDecoder::decode(const ustring &datastr)
 | 
			
		||||
	// TODO: bounds checks
 | 
			
		||||
 | 
			
		||||
	uint8_t version = data[0];
 | 
			
		||||
	//uint8_t flags = data[1];
 | 
			
		||||
	if (version < 22) {
 | 
			
		||||
		std::ostringstream oss;
 | 
			
		||||
		oss << "Unsupported map version " << (int)version;
 | 
			
		||||
@@ -55,46 +56,12 @@ void BlockDecoder::decode(const ustring &datastr)
 | 
			
		||||
	}
 | 
			
		||||
	m_version = version;
 | 
			
		||||
 | 
			
		||||
	ustring datastr2;
 | 
			
		||||
	if (version >= 29) {
 | 
			
		||||
		// decompress whole block at once
 | 
			
		||||
		m_zstd_decompressor.setData(data, length, 1);
 | 
			
		||||
		datastr2 = m_zstd_decompressor.decompress();
 | 
			
		||||
		data = datastr2.c_str();
 | 
			
		||||
		length = datastr2.size();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size_t dataOffset = 0;
 | 
			
		||||
	if (version >= 29)
 | 
			
		||||
		dataOffset = 7;
 | 
			
		||||
	else if (version >= 27)
 | 
			
		||||
	if (version >= 27)
 | 
			
		||||
		dataOffset = 4;
 | 
			
		||||
	else
 | 
			
		||||
		dataOffset = 2;
 | 
			
		||||
 | 
			
		||||
	auto decode_mapping = [&] () {
 | 
			
		||||
		dataOffset++; // mapping version
 | 
			
		||||
		uint16_t numMappings = readU16(data + dataOffset);
 | 
			
		||||
		dataOffset += 2;
 | 
			
		||||
		for (int i = 0; i < numMappings; ++i) {
 | 
			
		||||
			uint16_t nodeId = readU16(data + dataOffset);
 | 
			
		||||
			dataOffset += 2;
 | 
			
		||||
			uint16_t nameLen = readU16(data + dataOffset);
 | 
			
		||||
			dataOffset += 2;
 | 
			
		||||
			std::string name(reinterpret_cast<const char *>(data) + dataOffset, nameLen);
 | 
			
		||||
			if (name == "air")
 | 
			
		||||
				m_blockAirId = nodeId;
 | 
			
		||||
			else if (name == "ignore")
 | 
			
		||||
				m_blockIgnoreId = nodeId;
 | 
			
		||||
			else
 | 
			
		||||
				m_nameMap[nodeId] = name;
 | 
			
		||||
			dataOffset += nameLen;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (version >= 29)
 | 
			
		||||
		decode_mapping();
 | 
			
		||||
 | 
			
		||||
	uint8_t contentWidth = data[dataOffset];
 | 
			
		||||
	dataOffset++;
 | 
			
		||||
	uint8_t paramsWidth = data[dataOffset];
 | 
			
		||||
@@ -105,20 +72,14 @@ void BlockDecoder::decode(const ustring &datastr)
 | 
			
		||||
		throw std::runtime_error("unsupported map version (paramsWidth)");
 | 
			
		||||
	m_contentWidth = contentWidth;
 | 
			
		||||
 | 
			
		||||
	if (version >= 29) {
 | 
			
		||||
		m_mapData.resize((contentWidth + paramsWidth) * 4096);
 | 
			
		||||
		m_mapData.assign(data + dataOffset, m_mapData.size());
 | 
			
		||||
		return; // we have read everything we need and can return early
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// version < 29
 | 
			
		||||
	ZlibDecompressor decompressor(data, length);
 | 
			
		||||
	decompressor.setSeekPos(dataOffset);
 | 
			
		||||
	m_mapData = decompressor.decompress();
 | 
			
		||||
	decompressor.decompress(); // unused metadata
 | 
			
		||||
	dataOffset = decompressor.seekPos();
 | 
			
		||||
 | 
			
		||||
	// Skip unused node timers
 | 
			
		||||
	// Skip unused data
 | 
			
		||||
	if (version == 23)
 | 
			
		||||
		dataOffset += 1;
 | 
			
		||||
	if (version == 24) {
 | 
			
		||||
@@ -142,7 +103,33 @@ void BlockDecoder::decode(const ustring &datastr)
 | 
			
		||||
	dataOffset += 4; // Skip timestamp
 | 
			
		||||
 | 
			
		||||
	// Read mapping
 | 
			
		||||
	decode_mapping();
 | 
			
		||||
	{
 | 
			
		||||
		dataOffset++; // mapping version
 | 
			
		||||
		uint16_t numMappings = readU16(data + dataOffset);
 | 
			
		||||
		dataOffset += 2;
 | 
			
		||||
		for (int i = 0; i < numMappings; ++i) {
 | 
			
		||||
			uint16_t nodeId = readU16(data + dataOffset);
 | 
			
		||||
			dataOffset += 2;
 | 
			
		||||
			uint16_t nameLen = readU16(data + dataOffset);
 | 
			
		||||
			dataOffset += 2;
 | 
			
		||||
			std::string name(reinterpret_cast<const char *>(data) + dataOffset, nameLen);
 | 
			
		||||
			if (name == "air")
 | 
			
		||||
				m_blockAirId = nodeId;
 | 
			
		||||
			else if (name == "ignore")
 | 
			
		||||
				m_blockIgnoreId = nodeId;
 | 
			
		||||
			else
 | 
			
		||||
				m_nameMap[nodeId] = name;
 | 
			
		||||
			dataOffset += nameLen;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Node timers
 | 
			
		||||
	if (version >= 25) {
 | 
			
		||||
		dataOffset++;
 | 
			
		||||
		uint16_t numTimers = readU16(data + dataOffset);
 | 
			
		||||
		dataOffset += 2;
 | 
			
		||||
		dataOffset += numTimers * 10;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BlockDecoder::isEmpty() const
 | 
			
		||||
@@ -151,18 +138,23 @@ bool BlockDecoder::isEmpty() const
 | 
			
		||||
	return m_nameMap.empty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const static std::string empty;
 | 
			
		||||
 | 
			
		||||
const std::string &BlockDecoder::getNode(u8 x, u8 y, u8 z) const
 | 
			
		||||
std::string BlockDecoder::getNode(u8 x, u8 y, u8 z) const
 | 
			
		||||
{
 | 
			
		||||
	unsigned int position = x + (y << 4) + (z << 8);
 | 
			
		||||
	uint16_t content = readBlockContent(m_mapData.c_str(), m_contentWidth, position);
 | 
			
		||||
	int content = readBlockContent(m_mapData.c_str(), m_contentWidth, position);
 | 
			
		||||
	if (content == m_blockAirId || content == m_blockIgnoreId)
 | 
			
		||||
		return empty;
 | 
			
		||||
		return "";
 | 
			
		||||
	NameMap::const_iterator it = m_nameMap.find(content);
 | 
			
		||||
	if (it == m_nameMap.end()) {
 | 
			
		||||
		std::cerr << "Skipping node with invalid ID." << std::endl;
 | 
			
		||||
		return empty;
 | 
			
		||||
		return "";
 | 
			
		||||
	}
 | 
			
		||||
	return it->second;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u8 BlockDecoder::getParam1(u8 x, u8 y, u8 z) const
 | 
			
		||||
{
 | 
			
		||||
	unsigned int position = x + (y << 4) + (z << 8);
 | 
			
		||||
	unsigned int offset = (m_contentWidth == 2) ? 0x2000 : 0x1000;
 | 
			
		||||
	return m_mapData.c_str()[offset + position];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
cmake_minimum_required(VERSION 3.5)
 | 
			
		||||
project(minetestmapper CXX)
 | 
			
		||||
cmake_minimum_required(VERSION 2.6)
 | 
			
		||||
cmake_policy(SET CMP0003 NEW)
 | 
			
		||||
 | 
			
		||||
project(minetestmapper
 | 
			
		||||
	VERSION 1.0
 | 
			
		||||
	LANGUAGES CXX
 | 
			
		||||
)
 | 
			
		||||
set(VERSION_MAJOR 1)
 | 
			
		||||
set(VERSION_MINOR 0)
 | 
			
		||||
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}")
 | 
			
		||||
 | 
			
		||||
# Stuff & Paths
 | 
			
		||||
 | 
			
		||||
@@ -12,17 +13,18 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
set(CMAKE_CXX_STANDARD 11)
 | 
			
		||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
 | 
			
		||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -DNDEBUG")
 | 
			
		||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g2 -Wall")
 | 
			
		||||
 | 
			
		||||
if(WIN32)
 | 
			
		||||
	set(SHAREDIR ".")
 | 
			
		||||
	set(BINDIR ".")
 | 
			
		||||
	set(DOCDIR ".")
 | 
			
		||||
else()
 | 
			
		||||
	set(SHAREDIR "share/luanti") # reuse engine share dir
 | 
			
		||||
	set(BINDIR "bin")
 | 
			
		||||
	set(DOCDIR "share/doc/${PROJECT_NAME}")
 | 
			
		||||
	set(MANDIR "share/man")
 | 
			
		||||
	set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/minetest") # reuse Minetest share dir
 | 
			
		||||
	set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin")
 | 
			
		||||
	set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}")
 | 
			
		||||
	set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
set(CUSTOM_SHAREDIR "" CACHE STRING "Directory to install data files into")
 | 
			
		||||
@@ -43,7 +45,9 @@ if(NOT CUSTOM_DOCDIR STREQUAL "")
 | 
			
		||||
        message(STATUS "Using DOCDIR=${DOCDIR}")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
 | 
			
		||||
#set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
 | 
			
		||||
find_package(PkgConfig)
 | 
			
		||||
include(FindPackageHandleStandardArgs)
 | 
			
		||||
 | 
			
		||||
# Libraries: gd
 | 
			
		||||
 | 
			
		||||
@@ -57,16 +61,18 @@ endif(NOT LIBGD_LIBRARY OR NOT LIBGD_INCLUDE_DIR)
 | 
			
		||||
 | 
			
		||||
# Libraries: zlib
 | 
			
		||||
 | 
			
		||||
find_package(ZLIB REQUIRED)
 | 
			
		||||
 | 
			
		||||
# Libraries: zstd
 | 
			
		||||
 | 
			
		||||
find_package(Zstd REQUIRED)
 | 
			
		||||
find_library(ZLIB_LIBRARY z)
 | 
			
		||||
find_path(ZLIB_INCLUDE_DIR zlib.h)
 | 
			
		||||
message (STATUS "zlib library: ${ZLIB_LIBRARY}")
 | 
			
		||||
message (STATUS "zlib headers: ${ZLIB_INCLUDE_DIR}")
 | 
			
		||||
if(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR)
 | 
			
		||||
	message(FATAL_ERROR "zlib not found!")
 | 
			
		||||
endif(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR)
 | 
			
		||||
 | 
			
		||||
# Libraries: sqlite3
 | 
			
		||||
 | 
			
		||||
find_library(SQLITE3_LIBRARY sqlite3)
 | 
			
		||||
find_path(SQLITE3_INCLUDE_DIR sqlite3.h)
 | 
			
		||||
find_path(SQLITE3_INCLUDE_DIR zlib.h)
 | 
			
		||||
message (STATUS "sqlite3 library: ${SQLITE3_LIBRARY}")
 | 
			
		||||
message (STATUS "sqlite3 headers: ${SQLITE3_INCLUDE_DIR}")
 | 
			
		||||
if(NOT SQLITE3_LIBRARY OR NOT SQLITE3_INCLUDE_DIR)
 | 
			
		||||
@@ -79,18 +85,7 @@ option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE)
 | 
			
		||||
set(USE_POSTGRESQL FALSE)
 | 
			
		||||
 | 
			
		||||
if(ENABLE_POSTGRESQL)
 | 
			
		||||
	if(CMAKE_VERSION VERSION_LESS "3.20")
 | 
			
		||||
		find_package(PostgreSQL QUIET)
 | 
			
		||||
		# Before CMake 3.20 FindPostgreSQL.cmake always looked for server includes
 | 
			
		||||
		# but we don't need them, so continue anyway if only those are missing.
 | 
			
		||||
		if(PostgreSQL_INCLUDE_DIR AND PostgreSQL_LIBRARY)
 | 
			
		||||
			set(PostgreSQL_FOUND TRUE)
 | 
			
		||||
			set(PostgreSQL_INCLUDE_DIRS ${PostgreSQL_INCLUDE_DIR})
 | 
			
		||||
			set(PostgreSQL_LIBRARIES ${PostgreSQL_LIBRARY})
 | 
			
		||||
		endif()
 | 
			
		||||
	else()
 | 
			
		||||
		find_package(PostgreSQL)
 | 
			
		||||
	endif()
 | 
			
		||||
	find_package("PostgreSQL")
 | 
			
		||||
 | 
			
		||||
	if(PostgreSQL_FOUND)
 | 
			
		||||
		set(USE_POSTGRESQL TRUE)
 | 
			
		||||
@@ -153,38 +148,40 @@ include_directories(
 | 
			
		||||
	${SQLITE3_INCLUDE_DIR}
 | 
			
		||||
	${LIBGD_INCLUDE_DIR}
 | 
			
		||||
	${ZLIB_INCLUDE_DIR}
 | 
			
		||||
	${ZSTD_INCLUDE_DIR}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
configure_file(
 | 
			
		||||
	"${PROJECT_SOURCE_DIR}/include/cmake_config.h.in"
 | 
			
		||||
	"${PROJECT_BINARY_DIR}/cmake_config.h"
 | 
			
		||||
)
 | 
			
		||||
add_definitions(-DUSE_CMAKE_CONFIG_H)
 | 
			
		||||
add_definitions ( -DUSE_CMAKE_CONFIG_H )
 | 
			
		||||
 | 
			
		||||
if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
 | 
			
		||||
	set(CMAKE_CXX_FLAGS_RELEASE "-O2")
 | 
			
		||||
	set(CMAKE_CXX_FLAGS_DEBUG "-Og -g2")
 | 
			
		||||
	add_compile_options(-Wall -pipe)
 | 
			
		||||
endif()
 | 
			
		||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
 | 
			
		||||
	add_definitions(-DNDEBUG)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_executable(minetestmapper
 | 
			
		||||
set(mapper_SRCS
 | 
			
		||||
	BlockDecoder.cpp
 | 
			
		||||
	PixelAttributes.cpp
 | 
			
		||||
	PlayerAttributes.cpp
 | 
			
		||||
	TileGenerator.cpp
 | 
			
		||||
	ZlibDecompressor.cpp
 | 
			
		||||
	ZstdDecompressor.cpp
 | 
			
		||||
	Image.cpp
 | 
			
		||||
	mapper.cpp
 | 
			
		||||
	util.cpp
 | 
			
		||||
	db-sqlite3.cpp
 | 
			
		||||
	$<$<BOOL:${USE_POSTGRESQL}>:db-postgresql.cpp>
 | 
			
		||||
	$<$<BOOL:${USE_LEVELDB}>:db-leveldb.cpp>
 | 
			
		||||
	$<$<BOOL:${USE_REDIS}>:db-redis.cpp>
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if(USE_POSTGRESQL)
 | 
			
		||||
	set(mapper_SRCS ${mapper_SRCS} db-postgresql.cpp)
 | 
			
		||||
endif(USE_POSTGRESQL)
 | 
			
		||||
 | 
			
		||||
if(USE_LEVELDB)
 | 
			
		||||
	set(mapper_SRCS ${mapper_SRCS} db-leveldb.cpp)
 | 
			
		||||
endif(USE_LEVELDB)
 | 
			
		||||
 | 
			
		||||
if(USE_REDIS)
 | 
			
		||||
	set(mapper_SRCS ${mapper_SRCS} db-redis.cpp)
 | 
			
		||||
endif(USE_REDIS)
 | 
			
		||||
 | 
			
		||||
add_executable(minetestmapper
 | 
			
		||||
	${mapper_SRCS}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
@@ -195,7 +192,6 @@ target_link_libraries(
 | 
			
		||||
	${REDIS_LIBRARY}
 | 
			
		||||
	${LIBGD_LIBRARY}
 | 
			
		||||
	${ZLIB_LIBRARY}
 | 
			
		||||
	${ZSTD_LIBRARY}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Installing & Packaging
 | 
			
		||||
@@ -209,15 +205,17 @@ if(UNIX)
 | 
			
		||||
	install(FILES "minetestmapper.6" DESTINATION "${MANDIR}/man6")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Overview mapper for Luanti")
 | 
			
		||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Overview mapper for Minetest")
 | 
			
		||||
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
 | 
			
		||||
set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
 | 
			
		||||
set(CPACK_PACKAGE_VENDOR "celeron55")
 | 
			
		||||
set(CPACK_PACKAGE_CONTACT "Perttu Ahola <celeron55@gmail.com>")
 | 
			
		||||
 | 
			
		||||
if(WIN32)
 | 
			
		||||
	set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-win32")
 | 
			
		||||
	set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win32")
 | 
			
		||||
	set(CPACK_GENERATOR ZIP)
 | 
			
		||||
else()
 | 
			
		||||
	set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-linux")
 | 
			
		||||
	set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-linux")
 | 
			
		||||
	set(CPACK_GENERATOR TGZ)
 | 
			
		||||
	set(CPACK_SOURCE_GENERATOR TGZ)
 | 
			
		||||
endif()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,24 +0,0 @@
 | 
			
		||||
ARG DOCKER_IMAGE=alpine:3.20
 | 
			
		||||
FROM $DOCKER_IMAGE AS builder
 | 
			
		||||
 | 
			
		||||
RUN apk add --no-cache build-base cmake \
 | 
			
		||||
		gd-dev sqlite-dev postgresql-dev hiredis-dev leveldb-dev \
 | 
			
		||||
		ninja
 | 
			
		||||
 | 
			
		||||
COPY . /usr/src/minetestmapper
 | 
			
		||||
WORKDIR /usr/src/minetestmapper
 | 
			
		||||
 | 
			
		||||
RUN cmake -B build -G Ninja && \
 | 
			
		||||
    cmake --build build --parallel $(nproc) && \
 | 
			
		||||
    cmake --install build
 | 
			
		||||
 | 
			
		||||
FROM $DOCKER_IMAGE AS runtime
 | 
			
		||||
 | 
			
		||||
RUN apk add --no-cache libstdc++ libgcc libpq \
 | 
			
		||||
        gd sqlite-libs postgresql hiredis leveldb
 | 
			
		||||
 | 
			
		||||
COPY --from=builder /usr/local/share/luanti /usr/local/share/luanti
 | 
			
		||||
COPY --from=builder /usr/local/bin/minetestmapper /usr/local/bin/minetestmapper
 | 
			
		||||
COPY COPYING /usr/local/share/minetest/minetestmapper.COPYING
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["/usr/local/bin/minetestmapper"]
 | 
			
		||||
							
								
								
									
										10
									
								
								Image.cpp
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Image.cpp
									
									
									
									
									
								
							@@ -17,7 +17,7 @@
 | 
			
		||||
 | 
			
		||||
// ARGB but with inverted alpha
 | 
			
		||||
 | 
			
		||||
static inline int color2int(const Color &c)
 | 
			
		||||
static inline int color2int(Color c)
 | 
			
		||||
{
 | 
			
		||||
	u8 a = (255 - c.a) * gdAlphaMax / 255;
 | 
			
		||||
	return (a << 24) | (c.r << 16) | (c.g << 8) | c.b;
 | 
			
		||||
@@ -26,15 +26,15 @@ static inline int color2int(const Color &c)
 | 
			
		||||
static inline Color int2color(int c)
 | 
			
		||||
{
 | 
			
		||||
	Color c2;
 | 
			
		||||
	u8 a;
 | 
			
		||||
	c2.b = c & 0xff;
 | 
			
		||||
	c2.g = (c >> 8) & 0xff;
 | 
			
		||||
	c2.r = (c >> 16) & 0xff;
 | 
			
		||||
	u8 a = (c >> 24) & 0xff;
 | 
			
		||||
	a = (c >> 24) & 0xff;
 | 
			
		||||
	c2.a = 255 - (a*255 / gdAlphaMax);
 | 
			
		||||
	return c2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
static inline void check_bounds(int x, int y, int width, int height)
 | 
			
		||||
{
 | 
			
		||||
	if(x < 0 || x >= width) {
 | 
			
		||||
@@ -50,13 +50,11 @@ static inline void check_bounds(int x, int y, int width, int height)
 | 
			
		||||
		throw std::out_of_range(oss.str());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Image::Image(int width, int height) :
 | 
			
		||||
	m_width(width), m_height(height), m_image(nullptr)
 | 
			
		||||
	m_width(width), m_height(height), m_image(NULL)
 | 
			
		||||
{
 | 
			
		||||
	SIZECHECK(0, 0);
 | 
			
		||||
	m_image = gdImageCreateTrueColor(m_width, m_height);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,14 @@
 | 
			
		||||
#include <cstring>
 | 
			
		||||
/*
 | 
			
		||||
 * =====================================================================
 | 
			
		||||
 *        Version:  1.0
 | 
			
		||||
 *        Created:  25.08.2012 10:55:27
 | 
			
		||||
 *         Author:  Miroslav Bendík
 | 
			
		||||
 *        Company:  LinuxOS.sk
 | 
			
		||||
 * =====================================================================
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "PixelAttributes.h"
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
PixelAttributes::PixelAttributes():
 | 
			
		||||
	m_width(0)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,11 @@
 | 
			
		||||
#include "PlayerAttributes.h"
 | 
			
		||||
#include "util.h"
 | 
			
		||||
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
PlayerAttributes::PlayerAttributes(const std::string &worldDir)
 | 
			
		||||
{
 | 
			
		||||
	std::ifstream ifs(worldDir + "world.mt");
 | 
			
		||||
	std::ifstream ifs((worldDir + "world.mt").c_str());
 | 
			
		||||
	if (!ifs.good())
 | 
			
		||||
		throw std::runtime_error("Failed to read world.mt");
 | 
			
		||||
	std::string backend = read_setting_default("player_backend", ifs, "files");
 | 
			
		||||
@@ -22,14 +24,14 @@ PlayerAttributes::PlayerAttributes(const std::string &worldDir)
 | 
			
		||||
	else if (backend == "sqlite3")
 | 
			
		||||
		readSqlite(worldDir + "players.sqlite");
 | 
			
		||||
	else
 | 
			
		||||
		throw std::runtime_error(std::string("Unknown player backend: ") + backend);
 | 
			
		||||
		throw std::runtime_error(((std::string) "Unknown player backend: ") + backend);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlayerAttributes::readFiles(const std::string &playersPath)
 | 
			
		||||
{
 | 
			
		||||
	DIR *dir;
 | 
			
		||||
	dir = opendir (playersPath.c_str());
 | 
			
		||||
	if (!dir)
 | 
			
		||||
	if (dir == NULL)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	struct dirent *ent;
 | 
			
		||||
@@ -37,17 +39,18 @@ void PlayerAttributes::readFiles(const std::string &playersPath)
 | 
			
		||||
		if (ent->d_name[0] == '.')
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		std::ifstream in(playersPath + PATH_SEPARATOR + ent->d_name);
 | 
			
		||||
		if (!in.good())
 | 
			
		||||
		string path = playersPath + PATH_SEPARATOR + ent->d_name;
 | 
			
		||||
		ifstream in(path.c_str());
 | 
			
		||||
		if(!in.good())
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		std::string name, position;
 | 
			
		||||
		string name, position;
 | 
			
		||||
		name = read_setting("name", in);
 | 
			
		||||
		in.seekg(0);
 | 
			
		||||
		position = read_setting("position", in);
 | 
			
		||||
 | 
			
		||||
		Player player;
 | 
			
		||||
		std::istringstream iss(position);
 | 
			
		||||
		istringstream iss(position);
 | 
			
		||||
		char tmp;
 | 
			
		||||
		iss >> tmp; // '('
 | 
			
		||||
		iss >> player.x;
 | 
			
		||||
@@ -56,17 +59,16 @@ void PlayerAttributes::readFiles(const std::string &playersPath)
 | 
			
		||||
		iss >> tmp; // ','
 | 
			
		||||
		iss >> player.z;
 | 
			
		||||
		iss >> tmp; // ')'
 | 
			
		||||
		if (tmp != ')')
 | 
			
		||||
		if(tmp != ')')
 | 
			
		||||
			continue;
 | 
			
		||||
		player.name = name;
 | 
			
		||||
 | 
			
		||||
		player.x /= 10.0f;
 | 
			
		||||
		player.y /= 10.0f;
 | 
			
		||||
		player.z /= 10.0f;
 | 
			
		||||
		player.x /= 10.0;
 | 
			
		||||
		player.y /= 10.0;
 | 
			
		||||
		player.z /= 10.0;
 | 
			
		||||
 | 
			
		||||
		m_players.push_back(player);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	closedir(dir);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -101,14 +103,14 @@ void PlayerAttributes::readSqlite(const std::string &db_name)
 | 
			
		||||
 | 
			
		||||
		Player player;
 | 
			
		||||
		const unsigned char *name_ = sqlite3_column_text(stmt_get_player_pos, 0);
 | 
			
		||||
		player.name = reinterpret_cast<const char*>(name_);
 | 
			
		||||
		player.name = std::string(reinterpret_cast<const char*>(name_));
 | 
			
		||||
		player.x = sqlite3_column_double(stmt_get_player_pos, 1);
 | 
			
		||||
		player.y = sqlite3_column_double(stmt_get_player_pos, 2);
 | 
			
		||||
		player.z = sqlite3_column_double(stmt_get_player_pos, 3);
 | 
			
		||||
 | 
			
		||||
		player.x /= 10.0f;
 | 
			
		||||
		player.y /= 10.0f;
 | 
			
		||||
		player.z /= 10.0f;
 | 
			
		||||
		player.x /= 10.0;
 | 
			
		||||
		player.y /= 10.0;
 | 
			
		||||
		player.z /= 10.0;
 | 
			
		||||
 | 
			
		||||
		m_players.push_back(player);
 | 
			
		||||
	}
 | 
			
		||||
@@ -119,13 +121,13 @@ void PlayerAttributes::readSqlite(const std::string &db_name)
 | 
			
		||||
 | 
			
		||||
/**********/
 | 
			
		||||
 | 
			
		||||
PlayerAttributes::Players::const_iterator PlayerAttributes::begin() const
 | 
			
		||||
PlayerAttributes::Players::iterator PlayerAttributes::begin()
 | 
			
		||||
{
 | 
			
		||||
	return m_players.cbegin();
 | 
			
		||||
	return m_players.begin();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PlayerAttributes::Players::const_iterator PlayerAttributes::end() const
 | 
			
		||||
PlayerAttributes::Players::iterator PlayerAttributes::end()
 | 
			
		||||
{
 | 
			
		||||
	return m_players.cend();
 | 
			
		||||
	return m_players.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								README.rst
									
									
									
									
									
								
							@@ -1,46 +1,31 @@
 | 
			
		||||
Minetest Mapper C++
 | 
			
		||||
===================
 | 
			
		||||
 | 
			
		||||
.. image:: https://github.com/minetest/minetestmapper/workflows/build/badge.svg
 | 
			
		||||
    :target: https://github.com/minetest/minetestmapper/actions/workflows/build.yml
 | 
			
		||||
.. image:: https://travis-ci.org/minetest/minetestmapper.svg?branch=master
 | 
			
		||||
    :target: https://travis-ci.org/minetest/minetestmapper
 | 
			
		||||
 | 
			
		||||
Minetestmapper generates an overview image from a Luanti map.
 | 
			
		||||
 | 
			
		||||
A port of minetestmapper.py to C++ from `the obsolete Python script
 | 
			
		||||
<https://github.com/minetest/minetest/tree/0.4.17/util>`_.
 | 
			
		||||
This version is both faster and provides more features.
 | 
			
		||||
 | 
			
		||||
Minetestmapper ships with a colors.txt file for Minetest Game, if you use a different game or have
 | 
			
		||||
many mods installed you should generate a matching colors.txt for better results.
 | 
			
		||||
The `generate_colorstxt.py script 
 | 
			
		||||
<./util/generate_colorstxt.py>`_ in the util folder exists for this purpose, detailed instructions can be found within.
 | 
			
		||||
A port of minetestmapper.py to C++ from https://github.com/minetest/minetest/tree/master/util.
 | 
			
		||||
This version is both faster and provides more features than the now deprecated Python script.
 | 
			
		||||
 | 
			
		||||
Requirements
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
* C++ compiler, zlib, zstd
 | 
			
		||||
* libgd
 | 
			
		||||
* sqlite3
 | 
			
		||||
* LevelDB (optional)
 | 
			
		||||
* hiredis (optional)
 | 
			
		||||
* Postgres libraries (optional)
 | 
			
		||||
* LevelDB (optional, set ENABLE_LEVELDB=1 in CMake to enable)
 | 
			
		||||
* hiredis library (optional, set ENABLE_REDIS=1 in CMake to enable)
 | 
			
		||||
* Postgres libraries (optional, set ENABLE_POSTGRES=1 in CMake to enable)
 | 
			
		||||
 | 
			
		||||
on Debian/Ubuntu:
 | 
			
		||||
^^^^^^^^^^^^^^^^^
 | 
			
		||||
e.g. on Debian:
 | 
			
		||||
^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
``sudo apt install cmake libgd-dev libhiredis-dev libleveldb-dev libpq-dev libsqlite3-dev zlib1g-dev libzstd-dev``
 | 
			
		||||
	sudo apt-get install libgd-dev libsqlite3-dev libleveldb-dev libhiredis-dev libpq-dev
 | 
			
		||||
 | 
			
		||||
on openSUSE:
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
Windows
 | 
			
		||||
^^^^^^^
 | 
			
		||||
Minetestmapper for Windows can be downloaded here: https://github.com/minetest/minetestmapper/releases
 | 
			
		||||
 | 
			
		||||
``sudo zypper install gd-devel hiredis-devel leveldb-devel postgresql-devel sqlite3-devel zlib-devel libzstd-devel``
 | 
			
		||||
 | 
			
		||||
for Windows:
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
Minetestmapper for Windows can be downloaded `from the Releases section
 | 
			
		||||
<https://github.com/minetest/minetestmapper/releases>`_.
 | 
			
		||||
 | 
			
		||||
After extracting the archive, it can be invoked from cmd.exe or PowerShell:
 | 
			
		||||
After extracting the archive, minetestmapper can be invoked from cmd.exe:
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
	cd C:\Users\yourname\Desktop\example\path
 | 
			
		||||
@@ -52,7 +37,7 @@ Compilation
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    cmake . -DENABLE_LEVELDB=1
 | 
			
		||||
    make -j$(nproc)
 | 
			
		||||
    make -j2
 | 
			
		||||
 | 
			
		||||
Usage
 | 
			
		||||
-----
 | 
			
		||||
@@ -117,7 +102,7 @@ zoom:
 | 
			
		||||
    Apply zoom to drawn nodes by enlarging them to n*n squares, e.g. ``--zoom 4``
 | 
			
		||||
 | 
			
		||||
colors:
 | 
			
		||||
    Override auto-detected path to colors.txt, e.g. ``--colors ../world/mycolors.txt``
 | 
			
		||||
    Override auto-detected path to colors.txt, e.g. ``--colors ../minetest/mycolors.txt``
 | 
			
		||||
 | 
			
		||||
scales:
 | 
			
		||||
    Draw scales on specified image edges (letters *t b l r* meaning top, bottom, left and right), e.g. ``--scales tbr``
 | 
			
		||||
 
 | 
			
		||||
@@ -8,16 +8,13 @@
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
 | 
			
		||||
#include "TileGenerator.h"
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "PlayerAttributes.h"
 | 
			
		||||
#include "BlockDecoder.h"
 | 
			
		||||
#include "Image.h"
 | 
			
		||||
#include "util.h"
 | 
			
		||||
 | 
			
		||||
#include "db-sqlite3.h"
 | 
			
		||||
#if USE_POSTGRESQL
 | 
			
		||||
#include "db-postgresql.h"
 | 
			
		||||
@@ -29,45 +26,18 @@
 | 
			
		||||
#include "db-redis.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef __has_builtin
 | 
			
		||||
#define __has_builtin(x) 0
 | 
			
		||||
#endif
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
// saturating multiplication
 | 
			
		||||
template<typename T, class = typename std::enable_if<std::is_unsigned<T>::value>::type>
 | 
			
		||||
inline T sat_mul(T a, T b)
 | 
			
		||||
template<typename T>
 | 
			
		||||
static inline T mymax(T a, T b)
 | 
			
		||||
{
 | 
			
		||||
#if __has_builtin(__builtin_mul_overflow)
 | 
			
		||||
	T res;
 | 
			
		||||
	if (__builtin_mul_overflow(a, b, &res))
 | 
			
		||||
		return std::numeric_limits<T>::max();
 | 
			
		||||
	return res;
 | 
			
		||||
#else
 | 
			
		||||
	// WARNING: the fallback implementation is incorrect since we compute ceil(log(x)) not log(x)
 | 
			
		||||
	// but that's good enough for our usecase...
 | 
			
		||||
	const int bits = sizeof(T) * 8;
 | 
			
		||||
	int hb_a = 0, hb_b = 0;
 | 
			
		||||
	for (int i = bits - 1; i >= 0; i--) {
 | 
			
		||||
		if (a & (static_cast<T>(1) << i)) {
 | 
			
		||||
			hb_a = i; break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for (int i = bits - 1; i >= 0; i--) {
 | 
			
		||||
		if (b & (static_cast<T>(1) << i)) {
 | 
			
		||||
			hb_b = i; break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// log2(a) + log2(b) >= log2(MAX) <=> calculation will overflow
 | 
			
		||||
	if (hb_a + hb_b >= bits)
 | 
			
		||||
		return std::numeric_limits<T>::max();
 | 
			
		||||
	return a * b;
 | 
			
		||||
#endif
 | 
			
		||||
	return (a > b) ? a : b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T>
 | 
			
		||||
inline T sat_mul(T a, T b, T c)
 | 
			
		||||
static inline T mymin(T a, T b)
 | 
			
		||||
{
 | 
			
		||||
	return sat_mul(sat_mul(a, b), c);
 | 
			
		||||
	return (a > b) ? b : a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// rounds n (away from 0) to a multiple of f while preserving the sign of n
 | 
			
		||||
@@ -82,25 +52,11 @@ static int round_multiple_nosign(int n, int f)
 | 
			
		||||
		return sign * (abs_n + f - (abs_n % f));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline unsigned int colorSafeBounds(int channel)
 | 
			
		||||
static inline unsigned int colorSafeBounds (int channel)
 | 
			
		||||
{
 | 
			
		||||
	return mymin(mymax(channel, 0), 255);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static Color parseColor(const std::string &color)
 | 
			
		||||
{
 | 
			
		||||
	if (color.length() != 7)
 | 
			
		||||
		throw std::runtime_error("Color needs to be 7 characters long");
 | 
			
		||||
	if (color[0] != '#')
 | 
			
		||||
		throw std::runtime_error("Color needs to begin with #");
 | 
			
		||||
	unsigned long col = strtoul(color.c_str() + 1, NULL, 16);
 | 
			
		||||
	u8 b, g, r;
 | 
			
		||||
	b = col & 0xff;
 | 
			
		||||
	g = (col >> 8) & 0xff;
 | 
			
		||||
	r = (col >> 16) & 0xff;
 | 
			
		||||
	return Color(r, g, b);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static Color mixColors(Color a, Color b)
 | 
			
		||||
{
 | 
			
		||||
	Color result;
 | 
			
		||||
@@ -142,11 +98,8 @@ TileGenerator::TileGenerator():
 | 
			
		||||
	m_geomX2(2048),
 | 
			
		||||
	m_geomY2(2048),
 | 
			
		||||
	m_exhaustiveSearch(EXH_AUTO),
 | 
			
		||||
	m_renderedAny(false),
 | 
			
		||||
	m_zoom(1),
 | 
			
		||||
	m_scales(SCALE_LEFT | SCALE_TOP),
 | 
			
		||||
	m_progressMax(0),
 | 
			
		||||
	m_progressLast(-1)
 | 
			
		||||
	m_scales(SCALE_LEFT | SCALE_TOP)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -187,6 +140,21 @@ void TileGenerator::setScales(uint flags)
 | 
			
		||||
	m_scales = flags;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Color TileGenerator::parseColor(const std::string &color)
 | 
			
		||||
{
 | 
			
		||||
	Color parsed;
 | 
			
		||||
	if (color.length() != 7)
 | 
			
		||||
		throw std::runtime_error("Color needs to be 7 characters long");
 | 
			
		||||
	if (color[0] != '#')
 | 
			
		||||
		throw std::runtime_error("Color needs to begin with #");
 | 
			
		||||
	unsigned long col = strtoul(color.c_str() + 1, NULL, 16);
 | 
			
		||||
	parsed.b = col & 0xff;
 | 
			
		||||
	parsed.g = (col >> 8) & 0xff;
 | 
			
		||||
	parsed.r = (col >> 16) & 0xff;
 | 
			
		||||
	parsed.a = 255;
 | 
			
		||||
	return parsed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::setDrawOrigin(bool drawOrigin)
 | 
			
		||||
{
 | 
			
		||||
	m_drawOrigin = drawOrigin;
 | 
			
		||||
@@ -245,21 +213,22 @@ void TileGenerator::setExhaustiveSearch(int mode)
 | 
			
		||||
	m_exhaustiveSearch = mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::setDontWriteEmpty(bool f)
 | 
			
		||||
{
 | 
			
		||||
	m_dontWriteEmpty = f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::parseColorsFile(const std::string &fileName)
 | 
			
		||||
{
 | 
			
		||||
	std::ifstream in(fileName);
 | 
			
		||||
	if (!in.good())
 | 
			
		||||
	ifstream in;
 | 
			
		||||
	in.open(fileName.c_str(), ifstream::in);
 | 
			
		||||
	if (!in.is_open())
 | 
			
		||||
		throw std::runtime_error("Specified colors file could not be found");
 | 
			
		||||
	parseColorsStream(in);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::printGeometry(const std::string &input_path)
 | 
			
		||||
void TileGenerator::printGeometry(const std::string &input)
 | 
			
		||||
{
 | 
			
		||||
	string input_path = input;
 | 
			
		||||
	if (input_path[input.length() - 1] != PATH_SEPARATOR) {
 | 
			
		||||
		input_path += PATH_SEPARATOR;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setExhaustiveSearch(EXH_NEVER);
 | 
			
		||||
	openDb(input_path);
 | 
			
		||||
	loadBlocks();
 | 
			
		||||
@@ -271,28 +240,21 @@ void TileGenerator::printGeometry(const std::string &input_path)
 | 
			
		||||
		<< std::endl;
 | 
			
		||||
 | 
			
		||||
	closeDatabase();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::dumpBlock(const std::string &input_path, BlockPos pos)
 | 
			
		||||
void TileGenerator::setDontWriteEmpty(bool f)
 | 
			
		||||
{
 | 
			
		||||
	openDb(input_path);
 | 
			
		||||
	m_dontWriteEmpty = f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	BlockList list;
 | 
			
		||||
	std::vector<BlockPos> positions;
 | 
			
		||||
	positions.emplace_back(pos);
 | 
			
		||||
	m_db->getBlocksByPos(list, positions);
 | 
			
		||||
	if (!list.empty()) {
 | 
			
		||||
		const ustring &data = list.begin()->second;
 | 
			
		||||
		for (u8 c : data)
 | 
			
		||||
			printf("%02x", static_cast<int>(c));
 | 
			
		||||
		printf("\n");
 | 
			
		||||
void TileGenerator::generate(const std::string &input, const std::string &output)
 | 
			
		||||
{
 | 
			
		||||
	string input_path = input;
 | 
			
		||||
	if (input_path[input.length() - 1] != PATH_SEPARATOR) {
 | 
			
		||||
		input_path += PATH_SEPARATOR;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	closeDatabase();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::generate(const std::string &input_path, const std::string &output)
 | 
			
		||||
{
 | 
			
		||||
	if (m_dontWriteEmpty) // FIXME: possible too, just needs to be done differently
 | 
			
		||||
		setExhaustiveSearch(EXH_NEVER);
 | 
			
		||||
	openDb(input_path);
 | 
			
		||||
@@ -326,24 +288,27 @@ void TileGenerator::parseColorsStream(std::istream &in)
 | 
			
		||||
	while (in.good()) {
 | 
			
		||||
		in.getline(line, sizeof(line));
 | 
			
		||||
 | 
			
		||||
		for (char *p = line; *p; p++) {
 | 
			
		||||
			if (*p != '#')
 | 
			
		||||
		for(char *p = line; *p; p++) {
 | 
			
		||||
			if(*p != '#')
 | 
			
		||||
				continue;
 | 
			
		||||
			*p = '\0'; // Cut off at the first #
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		if(!line[0])
 | 
			
		||||
		if(strlen(line) == 0)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		char name[200 + 1] = {0};
 | 
			
		||||
		unsigned int r, g, b, a = 255, t = 0;
 | 
			
		||||
		int items = sscanf(line, "%200s %u %u %u %u %u", name, &r, &g, &b, &a, &t);
 | 
			
		||||
		if (items < 4) {
 | 
			
		||||
		char name[128 + 1] = {0};
 | 
			
		||||
		unsigned int r, g, b, a, t;
 | 
			
		||||
		a = 255;
 | 
			
		||||
		t = 0;
 | 
			
		||||
		int items = sscanf(line, "%128s %u %u %u %u %u", name, &r, &g, &b, &a, &t);
 | 
			
		||||
		if(items < 4) {
 | 
			
		||||
			std::cerr << "Failed to parse color entry '" << line << "'" << std::endl;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		m_colorMap[name] = ColorEntry(r, g, b, a, t);
 | 
			
		||||
	
 | 
			
		||||
		ColorEntry color(r, g, b, a, t);
 | 
			
		||||
		m_colorMap[name] = color;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -363,44 +328,41 @@ std::set<std::string> TileGenerator::getSupportedBackends()
 | 
			
		||||
	return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::openDb(const std::string &input_path)
 | 
			
		||||
void TileGenerator::openDb(const std::string &input)
 | 
			
		||||
{
 | 
			
		||||
	std::string input = input_path;
 | 
			
		||||
	if (input.back() != PATH_SEPARATOR)
 | 
			
		||||
		input += PATH_SEPARATOR;
 | 
			
		||||
 | 
			
		||||
	std::string backend = m_backend;
 | 
			
		||||
	if (backend.empty()) {
 | 
			
		||||
		std::ifstream ifs(input + "world.mt");
 | 
			
		||||
	if (backend == "") {
 | 
			
		||||
		std::ifstream ifs(input + "/world.mt");
 | 
			
		||||
		if(!ifs.good())
 | 
			
		||||
			throw std::runtime_error("Failed to open world.mt");
 | 
			
		||||
		backend = read_setting_default("backend", ifs, "sqlite3");
 | 
			
		||||
		ifs.close();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (backend == "sqlite3")
 | 
			
		||||
	if(backend == "sqlite3")
 | 
			
		||||
		m_db = new DBSQLite3(input);
 | 
			
		||||
#if USE_POSTGRESQL
 | 
			
		||||
	else if (backend == "postgresql")
 | 
			
		||||
	else if(backend == "postgresql")
 | 
			
		||||
		m_db = new DBPostgreSQL(input);
 | 
			
		||||
#endif
 | 
			
		||||
#if USE_LEVELDB
 | 
			
		||||
	else if (backend == "leveldb")
 | 
			
		||||
	else if(backend == "leveldb")
 | 
			
		||||
		m_db = new DBLevelDB(input);
 | 
			
		||||
#endif
 | 
			
		||||
#if USE_REDIS
 | 
			
		||||
	else if (backend == "redis")
 | 
			
		||||
	else if(backend == "redis")
 | 
			
		||||
		m_db = new DBRedis(input);
 | 
			
		||||
#endif
 | 
			
		||||
	else
 | 
			
		||||
		throw std::runtime_error(std::string("Unknown map backend: ") + backend);
 | 
			
		||||
		throw std::runtime_error(((std::string) "Unknown map backend: ") + backend);
 | 
			
		||||
 | 
			
		||||
	// Determine how we're going to traverse the database (heuristic)
 | 
			
		||||
	if (m_exhaustiveSearch == EXH_AUTO) {
 | 
			
		||||
		size_t y_range = (m_yMax / 16 + 1) - (m_yMin / 16);
 | 
			
		||||
		size_t blocks = sat_mul<size_t>(m_geomX2 - m_geomX, y_range, m_geomY2 - m_geomY);
 | 
			
		||||
		using u64 = uint64_t;
 | 
			
		||||
		u64 y_range = (m_yMax / 16 + 1) - (m_yMin / 16);
 | 
			
		||||
		u64 blocks = (u64)(m_geomX2 - m_geomX) * y_range * (u64)(m_geomY2 - m_geomY);
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
		std::cerr << "Heuristic parameters:"
 | 
			
		||||
		std::cout << "Heuristic parameters:"
 | 
			
		||||
			<< " preferRangeQueries()=" << m_db->preferRangeQueries()
 | 
			
		||||
			<< " y_range=" << y_range << " blocks=" << blocks << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -428,27 +390,19 @@ void TileGenerator::closeDatabase()
 | 
			
		||||
	m_db = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline int16_t mod16(int16_t y)
 | 
			
		||||
{
 | 
			
		||||
	if (y < 0)
 | 
			
		||||
		return (y - 15) / 16;
 | 
			
		||||
	return y / 16;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::loadBlocks()
 | 
			
		||||
{
 | 
			
		||||
	const int16_t yMax = mod16(m_yMax) + 1;
 | 
			
		||||
	const int16_t yMin = mod16(m_yMin);
 | 
			
		||||
	const int16_t yMax = m_yMax / 16 + 1;
 | 
			
		||||
 | 
			
		||||
	if (m_exhaustiveSearch == EXH_NEVER || m_exhaustiveSearch == EXH_Y) {
 | 
			
		||||
		std::vector<BlockPos> vec = m_db->getBlockPos(
 | 
			
		||||
			BlockPos(m_geomX, yMin, m_geomY),
 | 
			
		||||
			BlockPos(m_geomX, m_yMin / 16, m_geomY),
 | 
			
		||||
			BlockPos(m_geomX2, yMax, m_geomY2)
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		for (auto pos : vec) {
 | 
			
		||||
			assert(pos.x >= m_geomX && pos.x < m_geomX2);
 | 
			
		||||
			assert(pos.y >= yMin && pos.y < yMax);
 | 
			
		||||
			assert(pos.y >= m_yMin / 16 && pos.y < yMax);
 | 
			
		||||
			assert(pos.z >= m_geomY && pos.z < m_geomY2);
 | 
			
		||||
 | 
			
		||||
			// Adjust minimum and maximum positions to the nearest block
 | 
			
		||||
@@ -465,12 +419,11 @@ void TileGenerator::loadBlocks()
 | 
			
		||||
			m_positions[pos.z].emplace(pos.x);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		size_t count = 0;
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
		int count = 0;
 | 
			
		||||
		for (const auto &it : m_positions)
 | 
			
		||||
			count += it.second.size();
 | 
			
		||||
		m_progressMax = count;
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
		std::cerr << "Loaded " << count
 | 
			
		||||
		std::cout << "Loaded " << count
 | 
			
		||||
			<< " positions (across Z: " << m_positions.size() << ") for rendering" << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
	}
 | 
			
		||||
@@ -482,6 +435,7 @@ void TileGenerator::createImage()
 | 
			
		||||
	if(!m_drawScale)
 | 
			
		||||
		m_scales = 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// If a geometry is explicitly set, set the bounding box to the requested geometry
 | 
			
		||||
	// instead of cropping to the content. This way we will always output a full tile
 | 
			
		||||
	// of the correct size.
 | 
			
		||||
@@ -522,9 +476,7 @@ void TileGenerator::createImage()
 | 
			
		||||
void TileGenerator::renderMap()
 | 
			
		||||
{
 | 
			
		||||
	BlockDecoder blk;
 | 
			
		||||
	const int16_t yMax = mod16(m_yMax) + 1;
 | 
			
		||||
	const int16_t yMin = mod16(m_yMin);
 | 
			
		||||
	size_t count = 0;
 | 
			
		||||
	const int16_t yMax = m_yMax / 16 + 1;
 | 
			
		||||
 | 
			
		||||
	auto renderSingle = [&] (int16_t xPos, int16_t zPos, BlockList &blockStack) {
 | 
			
		||||
		m_readPixels.reset();
 | 
			
		||||
@@ -540,12 +492,10 @@ void TileGenerator::renderMap()
 | 
			
		||||
		for (const auto &it : blockStack) {
 | 
			
		||||
			const BlockPos pos = it.first;
 | 
			
		||||
			assert(pos.x == xPos && pos.z == zPos);
 | 
			
		||||
			assert(pos.y >= yMin && pos.y < yMax);
 | 
			
		||||
			assert(pos.y >= m_yMin / 16 && pos.y < yMax);
 | 
			
		||||
 | 
			
		||||
			blk.reset();
 | 
			
		||||
			blk.decode(it.second);
 | 
			
		||||
			if (blk.isEmpty())
 | 
			
		||||
				continue;
 | 
			
		||||
			renderMapBlock(blk, pos);
 | 
			
		||||
 | 
			
		||||
			// Exit out if all pixels for this MapBlock are covered
 | 
			
		||||
@@ -554,7 +504,6 @@ void TileGenerator::renderMap()
 | 
			
		||||
		}
 | 
			
		||||
		if (!m_readPixels.full())
 | 
			
		||||
			renderMapBlockBottom(blockStack.begin()->first);
 | 
			
		||||
		m_renderedAny |= m_readInfo.any();
 | 
			
		||||
	};
 | 
			
		||||
	auto postRenderRow = [&] (int16_t zPos) {
 | 
			
		||||
		if (m_shading)
 | 
			
		||||
@@ -568,28 +517,27 @@ void TileGenerator::renderMap()
 | 
			
		||||
				int16_t xPos = *it2;
 | 
			
		||||
 | 
			
		||||
				BlockList blockStack;
 | 
			
		||||
				m_db->getBlocksOnXZ(blockStack, xPos, zPos, yMin, yMax);
 | 
			
		||||
				m_db->getBlocksOnXZ(blockStack, xPos, zPos, m_yMin / 16, yMax);
 | 
			
		||||
				blockStack.sort();
 | 
			
		||||
 | 
			
		||||
				renderSingle(xPos, zPos, blockStack);
 | 
			
		||||
				reportProgress(count++);
 | 
			
		||||
			}
 | 
			
		||||
			postRenderRow(zPos);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (m_exhaustiveSearch == EXH_Y) {
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
		std::cerr << "Exhaustively searching height of "
 | 
			
		||||
			<< (yMax - yMin) << " blocks" << std::endl;
 | 
			
		||||
			<< (yMax - (m_yMin / 16)) << " blocks" << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
		std::vector<BlockPos> positions;
 | 
			
		||||
		positions.reserve(yMax - yMin);
 | 
			
		||||
		positions.reserve(yMax - (m_yMin / 16));
 | 
			
		||||
		for (auto it = m_positions.rbegin(); it != m_positions.rend(); ++it) {
 | 
			
		||||
			int16_t zPos = it->first;
 | 
			
		||||
			for (auto it2 = it->second.rbegin(); it2 != it->second.rend(); ++it2) {
 | 
			
		||||
				int16_t xPos = *it2;
 | 
			
		||||
 | 
			
		||||
				positions.clear();
 | 
			
		||||
				for (int16_t yPos = yMin; yPos < yMax; yPos++)
 | 
			
		||||
				for (int16_t yPos = m_yMin / 16; yPos < yMax; yPos++)
 | 
			
		||||
					positions.emplace_back(xPos, yPos, zPos);
 | 
			
		||||
 | 
			
		||||
				BlockList blockStack;
 | 
			
		||||
@@ -597,25 +545,21 @@ void TileGenerator::renderMap()
 | 
			
		||||
				blockStack.sort();
 | 
			
		||||
 | 
			
		||||
				renderSingle(xPos, zPos, blockStack);
 | 
			
		||||
				reportProgress(count++);
 | 
			
		||||
			}
 | 
			
		||||
			postRenderRow(zPos);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (m_exhaustiveSearch == EXH_FULL) {
 | 
			
		||||
		const size_t span_y = yMax - yMin;
 | 
			
		||||
		m_progressMax = (m_geomX2 - m_geomX) * span_y * (m_geomY2 - m_geomY);
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
		std::cerr << "Exhaustively searching "
 | 
			
		||||
			<< (m_geomX2 - m_geomX) << "x" << span_y << "x"
 | 
			
		||||
			<< (m_geomX2 - m_geomX) << "x" << (yMax - (m_yMin / 16)) << "x"
 | 
			
		||||
			<< (m_geomY2 - m_geomY) << " blocks" << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
		std::vector<BlockPos> positions;
 | 
			
		||||
		positions.reserve(span_y);
 | 
			
		||||
		positions.reserve(yMax - (m_yMin / 16));
 | 
			
		||||
		for (int16_t zPos = m_geomY2 - 1; zPos >= m_geomY; zPos--) {
 | 
			
		||||
			for (int16_t xPos = m_geomX2 - 1; xPos >= m_geomX; xPos--) {
 | 
			
		||||
				positions.clear();
 | 
			
		||||
				for (int16_t yPos = yMin; yPos < yMax; yPos++)
 | 
			
		||||
				for (int16_t yPos = m_yMin / 16; yPos < yMax; yPos++)
 | 
			
		||||
					positions.emplace_back(xPos, yPos, zPos);
 | 
			
		||||
 | 
			
		||||
				BlockList blockStack;
 | 
			
		||||
@@ -623,17 +567,36 @@ void TileGenerator::renderMap()
 | 
			
		||||
				blockStack.sort();
 | 
			
		||||
 | 
			
		||||
				renderSingle(xPos, zPos, blockStack);
 | 
			
		||||
				reportProgress(count++);
 | 
			
		||||
			}
 | 
			
		||||
			postRenderRow(zPos);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reportProgress(m_progressMax);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::renderMapBlock(const BlockDecoder &blk, const BlockPos &pos)
 | 
			
		||||
{
 | 
			
		||||
	/***/
 | 
			
		||||
	static bool light_curve_init = false;
 | 
			
		||||
	static float light_curve[16];
 | 
			
		||||
	if (!light_curve_init) {
 | 
			
		||||
		for (u8 i = 0; i < 16; i++)
 | 
			
		||||
			light_curve[i] = expf((i - 15) / 12.0f);
 | 
			
		||||
		light_curve_init = true;
 | 
			
		||||
	}
 | 
			
		||||
	/***/
 | 
			
		||||
	auto light_at = [blk] (u8 x, u8 y, u8 z) -> u8 {
 | 
			
		||||
		return blk.getParam1(x, y, z) >> 4; // night bank
 | 
			
		||||
	};
 | 
			
		||||
	static u8 m_light[16][16];
 | 
			
		||||
	if (blk.isEmpty()) {
 | 
			
		||||
		for (int z = 0; z < 16; ++z) {
 | 
			
		||||
			for (int x = 0; x < 16; ++x) {
 | 
			
		||||
				m_light[z][x] = light_at(x, 0, z);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int xBegin = (pos.x - m_xMin) * 16;
 | 
			
		||||
	int zBegin = (m_zMax - pos.z) * 16;
 | 
			
		||||
	int minY = (pos.y * 16 > m_yMin) ? 0 : m_yMin - pos.y * 16;
 | 
			
		||||
@@ -644,43 +607,50 @@ void TileGenerator::renderMapBlock(const BlockDecoder &blk, const BlockPos &pos)
 | 
			
		||||
			if (m_readPixels.get(x, z))
 | 
			
		||||
				continue;
 | 
			
		||||
			int imageX = xBegin + x;
 | 
			
		||||
			auto &attr = m_blockPixelAttributes.attribute(15 - z, xBegin + x);
 | 
			
		||||
 | 
			
		||||
			for (int y = maxY; y >= minY; --y) {
 | 
			
		||||
				const std::string &name = blk.getNode(x, y, z);
 | 
			
		||||
				if (name.empty())
 | 
			
		||||
				string name = blk.getNode(x, y, z);
 | 
			
		||||
				if (name == "") {
 | 
			
		||||
					if (y == 0) m_light[z][x] = light_at(x, 0, z);
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
				ColorMap::const_iterator it = m_colorMap.find(name);
 | 
			
		||||
				if (it == m_colorMap.end()) {
 | 
			
		||||
					m_unknownNodes.insert(name);
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Color c = it->second.toColor();
 | 
			
		||||
				if (c.a == 0)
 | 
			
		||||
					continue; // node is fully invisible
 | 
			
		||||
				Color c = it->second.to_color();
 | 
			
		||||
				u8 light = (y == 15) ? m_light[z][x] : light_at(x, y+1, z);
 | 
			
		||||
				if (light < 15) light = mymax(light, light_at(x, y, z));
 | 
			
		||||
				if (1) {
 | 
			
		||||
					float l2 = light_curve[light];
 | 
			
		||||
					c.r = colorSafeBounds(c.r * l2);
 | 
			
		||||
					c.g = colorSafeBounds(c.g * l2);
 | 
			
		||||
					c.b = colorSafeBounds(c.b * l2);
 | 
			
		||||
				} else
 | 
			
		||||
					c = Color(light * 17, light * 17, light * 17);
 | 
			
		||||
				if (m_drawAlpha) {
 | 
			
		||||
					if (m_color[z][x].a != 0)
 | 
			
		||||
						c = mixColors(m_color[z][x], c);
 | 
			
		||||
					if (c.a < 255) {
 | 
			
		||||
						// remember color and near thickness value
 | 
			
		||||
						m_color[z][x] = c;
 | 
			
		||||
						m_thickness[z][x] = (m_thickness[z][x] + it->second.t) / 2;
 | 
			
		||||
					if (m_color[z][x].a == 0)
 | 
			
		||||
						m_color[z][x] = c; // first visible time, no color mixing
 | 
			
		||||
					else
 | 
			
		||||
						m_color[z][x] = mixColors(m_color[z][x], c);
 | 
			
		||||
					if(m_color[z][x].a < 0xff) {
 | 
			
		||||
						// near thickness value to thickness of current node
 | 
			
		||||
						m_thickness[z][x] = (m_thickness[z][x] + it->second.t) / 2.0;
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
					// color became opaque, draw it
 | 
			
		||||
					setZoomed(imageX, imageY, c);
 | 
			
		||||
					attr.thickness = m_thickness[z][x];
 | 
			
		||||
					setZoomed(imageX, imageY, m_color[z][x]);
 | 
			
		||||
					m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x];
 | 
			
		||||
				} else {
 | 
			
		||||
					c.a = 255;
 | 
			
		||||
					setZoomed(imageX, imageY, c);
 | 
			
		||||
					setZoomed(imageX, imageY, c.noAlpha());
 | 
			
		||||
				}
 | 
			
		||||
				m_readPixels.set(x, z);
 | 
			
		||||
 | 
			
		||||
				// do this afterwards so we can record height values
 | 
			
		||||
				// inside transparent nodes (water) too
 | 
			
		||||
				if (!m_readInfo.get(x, z)) {
 | 
			
		||||
					attr.height = pos.y * 16 + y;
 | 
			
		||||
					m_blockPixelAttributes.attribute(15 - z, xBegin + x).height = pos.y * 16 + y;
 | 
			
		||||
					m_readInfo.set(x, z);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
@@ -702,19 +672,17 @@ void TileGenerator::renderMapBlockBottom(const BlockPos &pos)
 | 
			
		||||
			if (m_readPixels.get(x, z))
 | 
			
		||||
				continue;
 | 
			
		||||
			int imageX = xBegin + x;
 | 
			
		||||
			auto &attr = m_blockPixelAttributes.attribute(15 - z, xBegin + x);
 | 
			
		||||
 | 
			
		||||
			// set color since it wasn't done in renderMapBlock()
 | 
			
		||||
			setZoomed(imageX, imageY, m_color[z][x]);
 | 
			
		||||
			m_readPixels.set(x, z);
 | 
			
		||||
			attr.thickness = m_thickness[z][x];
 | 
			
		||||
			m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x];
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::renderShading(int zPos)
 | 
			
		||||
{
 | 
			
		||||
	auto &a = m_blockPixelAttributes;
 | 
			
		||||
	int zBegin = (m_zMax - zPos) * 16;
 | 
			
		||||
	for (int z = 0; z < 16; ++z) {
 | 
			
		||||
		int imageY = zBegin + z;
 | 
			
		||||
@@ -722,27 +690,23 @@ void TileGenerator::renderShading(int zPos)
 | 
			
		||||
			continue;
 | 
			
		||||
		for (int x = 0; x < m_mapWidth; ++x) {
 | 
			
		||||
			if(
 | 
			
		||||
				!a.attribute(z, x).valid_height() ||
 | 
			
		||||
				!a.attribute(z, x - 1).valid_height() ||
 | 
			
		||||
				!a.attribute(z - 1, x).valid_height()
 | 
			
		||||
				!m_blockPixelAttributes.attribute(z, x).valid_height() ||
 | 
			
		||||
				!m_blockPixelAttributes.attribute(z, x - 1).valid_height() ||
 | 
			
		||||
				!m_blockPixelAttributes.attribute(z - 1, x).valid_height()
 | 
			
		||||
			)
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			// calculate shadow to apply
 | 
			
		||||
			int y = a.attribute(z, x).height;
 | 
			
		||||
			int y1 = a.attribute(z, x - 1).height;
 | 
			
		||||
			int y2 = a.attribute(z - 1, x).height;
 | 
			
		||||
			int y = m_blockPixelAttributes.attribute(z, x).height;
 | 
			
		||||
			int y1 = m_blockPixelAttributes.attribute(z, x - 1).height;
 | 
			
		||||
			int y2 = m_blockPixelAttributes.attribute(z - 1, x).height;
 | 
			
		||||
			int d = ((y - y1) + (y - y2)) * 12;
 | 
			
		||||
 | 
			
		||||
			if (m_drawAlpha) { // less visible shadow with increasing "thickness"
 | 
			
		||||
				float t = a.attribute(z, x).thickness * 1.2f;
 | 
			
		||||
				t = mymin(t, 255.0f);
 | 
			
		||||
				d *= 1.0f - t / 255.0f;
 | 
			
		||||
				double t = m_blockPixelAttributes.attribute(z, x).thickness * 1.2;
 | 
			
		||||
				d *= 1.0 - mymin(t, 255.0) / 255.0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			d = mymin(d, 36);
 | 
			
		||||
 | 
			
		||||
			// apply shadow/light by just adding to it pixel values
 | 
			
		||||
			Color c = m_image->getPixel(getImageX(x), getImageY(imageY));
 | 
			
		||||
			c.r = colorSafeBounds(c.r + d);
 | 
			
		||||
			c.g = colorSafeBounds(c.g + d);
 | 
			
		||||
@@ -750,7 +714,7 @@ void TileGenerator::renderShading(int zPos)
 | 
			
		||||
			setZoomed(x, imageY, c);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	a.scroll();
 | 
			
		||||
	m_blockPixelAttributes.scroll();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::renderScale()
 | 
			
		||||
@@ -828,13 +792,9 @@ void TileGenerator::renderOrigin()
 | 
			
		||||
	m_image->drawCircle(getImageX(0, true), getImageY(0, true), 12, m_originColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::renderPlayers(const std::string &input_path)
 | 
			
		||||
void TileGenerator::renderPlayers(const std::string &inputPath)
 | 
			
		||||
{
 | 
			
		||||
	std::string input = input_path;
 | 
			
		||||
	if (input.back() != PATH_SEPARATOR)
 | 
			
		||||
		input += PATH_SEPARATOR;
 | 
			
		||||
 | 
			
		||||
	PlayerAttributes players(input);
 | 
			
		||||
	PlayerAttributes players(inputPath);
 | 
			
		||||
	for (auto &player : players) {
 | 
			
		||||
		if (player.x < m_xMin * 16 || player.x > m_xMax * 16 ||
 | 
			
		||||
			player.z < m_zMin * 16 || player.z > m_zMax * 16)
 | 
			
		||||
@@ -854,42 +814,16 @@ void TileGenerator::writeImage(const std::string &output)
 | 
			
		||||
{
 | 
			
		||||
	m_image->save(output);
 | 
			
		||||
	delete m_image;
 | 
			
		||||
	m_image = nullptr;
 | 
			
		||||
	m_image = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::printUnknown()
 | 
			
		||||
{
 | 
			
		||||
	if (m_unknownNodes.empty())
 | 
			
		||||
	if (m_unknownNodes.size() == 0)
 | 
			
		||||
		return;
 | 
			
		||||
	std::cerr << "Unknown nodes:" << std::endl;
 | 
			
		||||
	for (const auto &node : m_unknownNodes)
 | 
			
		||||
		std::cerr << "\t" << node << std::endl;
 | 
			
		||||
	if (!m_renderedAny) {
 | 
			
		||||
		std::cerr << "The map was read successfully and not empty, but none of the "
 | 
			
		||||
			"encountered nodes had a color associated.\nCheck that you're using "
 | 
			
		||||
			"the right colors.txt. It should match the game you have installed." << std::endl;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileGenerator::reportProgress(size_t count)
 | 
			
		||||
{
 | 
			
		||||
	if (!m_progressMax)
 | 
			
		||||
		return;
 | 
			
		||||
	int percent = count / static_cast<float>(m_progressMax) * 100;
 | 
			
		||||
	if (percent == m_progressLast)
 | 
			
		||||
		return;
 | 
			
		||||
	m_progressLast = percent;
 | 
			
		||||
 | 
			
		||||
	// Print a nice-looking ASCII progress bar
 | 
			
		||||
	char bar[51] = {0};
 | 
			
		||||
	memset(bar, ' ', 50);
 | 
			
		||||
	int i = 0, j = percent;
 | 
			
		||||
	for (; j >= 2; j -= 2)
 | 
			
		||||
		bar[i++] = '=';
 | 
			
		||||
	if (j)
 | 
			
		||||
		bar[i++] = '-';
 | 
			
		||||
	std::cout << "[" << bar << "] " << percent << "% " << (percent == 100 ? "\n" : "\r");
 | 
			
		||||
	std::cout.flush();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline int TileGenerator::getImageX(int val, bool absolute) const
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,17 @@
 | 
			
		||||
/*
 | 
			
		||||
 * =====================================================================
 | 
			
		||||
 *        Version:  1.0
 | 
			
		||||
 *        Created:  18.09.2012 10:20:47
 | 
			
		||||
 *         Author:  Miroslav Bendík
 | 
			
		||||
 *        Company:  LinuxOS.sk
 | 
			
		||||
 * =====================================================================
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <zlib.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include "ZlibDecompressor.h"
 | 
			
		||||
 | 
			
		||||
ZlibDecompressor::ZlibDecompressor(const u8 *data, size_t size):
 | 
			
		||||
ZlibDecompressor::ZlibDecompressor(const unsigned char *data, std::size_t size):
 | 
			
		||||
	m_data(data),
 | 
			
		||||
	m_seekPos(0),
 | 
			
		||||
	m_size(size)
 | 
			
		||||
@@ -13,12 +22,12 @@ ZlibDecompressor::~ZlibDecompressor()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ZlibDecompressor::setSeekPos(size_t seekPos)
 | 
			
		||||
void ZlibDecompressor::setSeekPos(std::size_t seekPos)
 | 
			
		||||
{
 | 
			
		||||
	m_seekPos = seekPos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t ZlibDecompressor::seekPos() const
 | 
			
		||||
std::size_t ZlibDecompressor::seekPos() const
 | 
			
		||||
{
 | 
			
		||||
	return m_seekPos;
 | 
			
		||||
}
 | 
			
		||||
@@ -26,43 +35,36 @@ size_t ZlibDecompressor::seekPos() const
 | 
			
		||||
ustring ZlibDecompressor::decompress()
 | 
			
		||||
{
 | 
			
		||||
	const unsigned char *data = m_data + m_seekPos;
 | 
			
		||||
	const size_t size = m_size - m_seekPos;
 | 
			
		||||
	const std::size_t size = m_size - m_seekPos;
 | 
			
		||||
 | 
			
		||||
	ustring buffer;
 | 
			
		||||
	constexpr size_t BUFSIZE = 32 * 1024;
 | 
			
		||||
	const size_t BUFSIZE = 128 * 1024;
 | 
			
		||||
	uint8_t temp_buffer[BUFSIZE];
 | 
			
		||||
 | 
			
		||||
	z_stream strm;
 | 
			
		||||
	strm.zalloc = Z_NULL;
 | 
			
		||||
	strm.zfree = Z_NULL;
 | 
			
		||||
	strm.opaque = Z_NULL;
 | 
			
		||||
	strm.next_in = Z_NULL;
 | 
			
		||||
	strm.avail_in = 0;
 | 
			
		||||
	strm.avail_in = size;
 | 
			
		||||
 | 
			
		||||
	if (inflateInit(&strm) != Z_OK)
 | 
			
		||||
	if (inflateInit(&strm) != Z_OK) {
 | 
			
		||||
		throw DecompressError();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	strm.next_in = const_cast<unsigned char *>(data);
 | 
			
		||||
	strm.avail_in = size;
 | 
			
		||||
	buffer.resize(BUFSIZE);
 | 
			
		||||
	strm.next_out = &buffer[0];
 | 
			
		||||
	strm.avail_out = BUFSIZE;
 | 
			
		||||
 | 
			
		||||
	int ret = 0;
 | 
			
		||||
	do {
 | 
			
		||||
		strm.avail_out = BUFSIZE;
 | 
			
		||||
		strm.next_out = temp_buffer;
 | 
			
		||||
		ret = inflate(&strm, Z_NO_FLUSH);
 | 
			
		||||
		if (strm.avail_out == 0) {
 | 
			
		||||
			const auto off = buffer.size();
 | 
			
		||||
			buffer.reserve(off + BUFSIZE);
 | 
			
		||||
			strm.next_out = &buffer[off];
 | 
			
		||||
			strm.avail_out = BUFSIZE;
 | 
			
		||||
		}
 | 
			
		||||
		buffer += ustring(reinterpret_cast<unsigned char *>(temp_buffer), BUFSIZE - strm.avail_out);
 | 
			
		||||
	} while (ret == Z_OK);
 | 
			
		||||
	if (ret != Z_STREAM_END)
 | 
			
		||||
	if (ret != Z_STREAM_END) {
 | 
			
		||||
		throw DecompressError();
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	m_seekPos += strm.next_in - data;
 | 
			
		||||
	buffer.resize(buffer.size() - strm.avail_out);
 | 
			
		||||
	(void) inflateEnd(&strm);
 | 
			
		||||
	(void)inflateEnd(&strm);
 | 
			
		||||
 | 
			
		||||
	return buffer;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,58 +0,0 @@
 | 
			
		||||
#include <zstd.h>
 | 
			
		||||
#include "ZstdDecompressor.h"
 | 
			
		||||
 | 
			
		||||
ZstdDecompressor::ZstdDecompressor():
 | 
			
		||||
	m_data(nullptr),
 | 
			
		||||
	m_seekPos(0),
 | 
			
		||||
	m_size(0)
 | 
			
		||||
{
 | 
			
		||||
	m_stream = ZSTD_createDStream();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ZstdDecompressor::~ZstdDecompressor()
 | 
			
		||||
{
 | 
			
		||||
	ZSTD_freeDStream(reinterpret_cast<ZSTD_DStream*>(m_stream));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ZstdDecompressor::setData(const u8 *data, size_t size, size_t seekPos)
 | 
			
		||||
{
 | 
			
		||||
	m_data = data;
 | 
			
		||||
	m_seekPos = seekPos;
 | 
			
		||||
	m_size = size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t ZstdDecompressor::seekPos() const
 | 
			
		||||
{
 | 
			
		||||
	return m_seekPos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ustring ZstdDecompressor::decompress()
 | 
			
		||||
{
 | 
			
		||||
	ZSTD_DStream *stream = reinterpret_cast<ZSTD_DStream*>(m_stream);
 | 
			
		||||
	ZSTD_inBuffer inbuf = { m_data, m_size, m_seekPos };
 | 
			
		||||
 | 
			
		||||
	ustring buffer;
 | 
			
		||||
	constexpr size_t BUFSIZE = 32 * 1024;
 | 
			
		||||
 | 
			
		||||
	buffer.resize(BUFSIZE);
 | 
			
		||||
	ZSTD_outBuffer outbuf = { &buffer[0], buffer.size(), 0 };
 | 
			
		||||
 | 
			
		||||
	ZSTD_initDStream(stream);
 | 
			
		||||
 | 
			
		||||
	size_t ret;
 | 
			
		||||
	do {
 | 
			
		||||
		ret = ZSTD_decompressStream(stream, &outbuf, &inbuf);
 | 
			
		||||
		if (outbuf.size == outbuf.pos) {
 | 
			
		||||
			outbuf.size += BUFSIZE;
 | 
			
		||||
			buffer.resize(outbuf.size);
 | 
			
		||||
			outbuf.dst = &buffer[0];
 | 
			
		||||
		}
 | 
			
		||||
		if (ret && ZSTD_isError(ret))
 | 
			
		||||
			throw DecompressError();
 | 
			
		||||
	} while (ret != 0);
 | 
			
		||||
 | 
			
		||||
	m_seekPos = inbuf.pos;
 | 
			
		||||
	buffer.resize(outbuf.pos);
 | 
			
		||||
 | 
			
		||||
	return buffer;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										132
									
								
								autogenerating-colors.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								autogenerating-colors.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
==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 <input>" % 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)
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
mark_as_advanced(ZSTD_LIBRARY ZSTD_INCLUDE_DIR)
 | 
			
		||||
 | 
			
		||||
find_path(ZSTD_INCLUDE_DIR NAMES zstd.h)
 | 
			
		||||
 | 
			
		||||
find_library(ZSTD_LIBRARY NAMES zstd)
 | 
			
		||||
 | 
			
		||||
if(ZSTD_INCLUDE_DIR AND ZSTD_LIBRARY)
 | 
			
		||||
	# Check that the API we use exists
 | 
			
		||||
	include(CheckSymbolExists)
 | 
			
		||||
	unset(HAVE_ZSTD_INITDSTREAM CACHE)
 | 
			
		||||
	set(CMAKE_REQUIRED_INCLUDES ${ZSTD_INCLUDE_DIR})
 | 
			
		||||
	set(CMAKE_REQUIRED_LIBRARIES ${ZSTD_LIBRARY})
 | 
			
		||||
	check_symbol_exists(ZSTD_initDStream zstd.h HAVE_ZSTD_INITDSTREAM)
 | 
			
		||||
	unset(CMAKE_REQUIRED_INCLUDES)
 | 
			
		||||
	unset(CMAKE_REQUIRED_LIBRARIES)
 | 
			
		||||
 | 
			
		||||
	if(NOT HAVE_ZSTD_INITDSTREAM)
 | 
			
		||||
		unset(ZSTD_INCLUDE_DIR CACHE)
 | 
			
		||||
		unset(ZSTD_LIBRARY CACHE)
 | 
			
		||||
	endif()
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
include(FindPackageHandleStandardArgs)
 | 
			
		||||
find_package_handle_standard_args(Zstd DEFAULT_MSG ZSTD_LIBRARY ZSTD_INCLUDE_DIR)
 | 
			
		||||
							
								
								
									
										19
									
								
								colors.txt
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								colors.txt
									
									
									
									
									
								
							@@ -10,7 +10,7 @@ bones:bones 117 117 117
 | 
			
		||||
# butterflies
 | 
			
		||||
 | 
			
		||||
# carts
 | 
			
		||||
carts:brakerail 150 121 102
 | 
			
		||||
carts:brakerail 138 121 102
 | 
			
		||||
carts:powerrail 160 145 102
 | 
			
		||||
carts:rail 146 128 108
 | 
			
		||||
 | 
			
		||||
@@ -117,11 +117,7 @@ default:marram_grass_1 113 139 96
 | 
			
		||||
default:marram_grass_2 102 131 90
 | 
			
		||||
default:marram_grass_3 99 130 88
 | 
			
		||||
default:mese 222 222 0
 | 
			
		||||
default:mese_post_light 132 103 57
 | 
			
		||||
default:mese_post_light_acacia_wood 151 62 39
 | 
			
		||||
default:mese_post_light_aspen_wood 210 199 170
 | 
			
		||||
default:mese_post_light_junglewood 57 39 14
 | 
			
		||||
default:mese_post_light_pine_wood 221 185 131
 | 
			
		||||
default:mese_post_light 134 105 59
 | 
			
		||||
default:meselamp 213 215 143
 | 
			
		||||
default:mossycobble 88 91 73
 | 
			
		||||
default:obsidian 21 24 29
 | 
			
		||||
@@ -178,20 +174,12 @@ default:wood 131 102 57
 | 
			
		||||
# doors
 | 
			
		||||
doors:door_glass_a 245 245 245 64 16
 | 
			
		||||
doors:door_glass_b 245 245 245 64 16
 | 
			
		||||
doors:door_glass_c 245 245 245 64 16
 | 
			
		||||
doors:door_glass_d 245 245 245 64 16
 | 
			
		||||
doors:door_obsidian_glass_a 48 49 50 64 16
 | 
			
		||||
doors:door_obsidian_glass_b 48 49 50 64 16
 | 
			
		||||
doors:door_obsidian_glass_c 48 49 50 64 16
 | 
			
		||||
doors:door_obsidian_glass_d 48 49 50 64 16
 | 
			
		||||
doors:door_steel_a 203 203 203
 | 
			
		||||
doors:door_steel_b 203 203 203
 | 
			
		||||
doors:door_steel_c 203 203 203
 | 
			
		||||
doors:door_steel_d 203 203 203
 | 
			
		||||
doors:door_wood_a 89 68 37
 | 
			
		||||
doors:door_wood_b 89 68 37
 | 
			
		||||
doors:door_wood_c 89 68 37
 | 
			
		||||
doors:door_wood_d 89 68 37
 | 
			
		||||
doors:gate_acacia_wood_closed 150 61 39
 | 
			
		||||
doors:gate_acacia_wood_open 150 61 39
 | 
			
		||||
doors:gate_aspen_wood_closed 210 199 170
 | 
			
		||||
@@ -216,7 +204,6 @@ farming:cotton_5 116 105 53
 | 
			
		||||
farming:cotton_6 121 95 59
 | 
			
		||||
farming:cotton_7 94 70 37
 | 
			
		||||
farming:cotton_8 122 108 93
 | 
			
		||||
farming:cotton_wild 111 111 101
 | 
			
		||||
farming:desert_sand_soil 161 132 72
 | 
			
		||||
farming:desert_sand_soil_wet 120 99 53
 | 
			
		||||
farming:dry_soil 178 136 90
 | 
			
		||||
@@ -445,8 +432,6 @@ xpanes:bar 114 114 114 64 16
 | 
			
		||||
xpanes:bar_flat 114 114 114 64 16
 | 
			
		||||
xpanes:door_steel_bar_a 133 133 133 64 16
 | 
			
		||||
xpanes:door_steel_bar_b 133 133 133 64 16
 | 
			
		||||
xpanes:door_steel_bar_c 133 133 133 64 16
 | 
			
		||||
xpanes:door_steel_bar_d 133 133 133 64 16
 | 
			
		||||
xpanes:obsidian_pane 16 17 18 64 16
 | 
			
		||||
xpanes:obsidian_pane_flat 16 17 18 64 16
 | 
			
		||||
xpanes:pane 249 249 249 64 16
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
 | 
			
		||||
static inline int64_t stoi64(const std::string &s)
 | 
			
		||||
{
 | 
			
		||||
	std::istringstream tmp(s);
 | 
			
		||||
	std::stringstream tmp(s);
 | 
			
		||||
	int64_t t;
 | 
			
		||||
	tmp >> t;
 | 
			
		||||
	return t;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,8 @@
 | 
			
		||||
 | 
			
		||||
DBPostgreSQL::DBPostgreSQL(const std::string &mapdir)
 | 
			
		||||
{
 | 
			
		||||
	std::ifstream ifs(mapdir + "world.mt");
 | 
			
		||||
	if (!ifs.good())
 | 
			
		||||
	std::ifstream ifs((mapdir + "/world.mt").c_str());
 | 
			
		||||
	if(!ifs.good())
 | 
			
		||||
		throw std::runtime_error("Failed to read world.mt");
 | 
			
		||||
	std::string connect_string = read_setting("pgsql_connection", ifs);
 | 
			
		||||
	ifs.close();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								db-redis.cpp
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								db-redis.cpp
									
									
									
									
									
								
							@@ -20,6 +20,7 @@ static inline int64_t stoi64(const std::string &s)
 | 
			
		||||
	return t;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static inline std::string i64tos(int64_t i)
 | 
			
		||||
{
 | 
			
		||||
	std::ostringstream os;
 | 
			
		||||
@@ -27,11 +28,10 @@ static inline std::string i64tos(int64_t i)
 | 
			
		||||
	return os.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DBRedis::DBRedis(const std::string &mapdir)
 | 
			
		||||
{
 | 
			
		||||
	std::ifstream ifs(mapdir + "world.mt");
 | 
			
		||||
	if (!ifs.good())
 | 
			
		||||
	std::ifstream ifs((mapdir + "/world.mt").c_str());
 | 
			
		||||
	if(!ifs.good())
 | 
			
		||||
		throw std::runtime_error("Failed to read world.mt");
 | 
			
		||||
	std::string tmp;
 | 
			
		||||
 | 
			
		||||
@@ -40,16 +40,12 @@ DBRedis::DBRedis(const std::string &mapdir)
 | 
			
		||||
	hash = read_setting("redis_hash", ifs);
 | 
			
		||||
	ifs.seekg(0);
 | 
			
		||||
 | 
			
		||||
	if (tmp.find('/') != std::string::npos) {
 | 
			
		||||
		ctx = redisConnectUnix(tmp.c_str());
 | 
			
		||||
	} else {
 | 
			
		||||
		int port = stoi64(read_setting_default("redis_port", ifs, "6379"));
 | 
			
		||||
		ctx = redisConnect(tmp.c_str(), port);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!ctx) {
 | 
			
		||||
	const char *addr = tmp.c_str();
 | 
			
		||||
	int port = stoi64(read_setting_default("redis_port", ifs, "6379"));
 | 
			
		||||
	ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) : redisConnect(addr, port);
 | 
			
		||||
	if(!ctx) {
 | 
			
		||||
		throw std::runtime_error("Cannot allocate redis context");
 | 
			
		||||
	} else if (ctx->err) {
 | 
			
		||||
	} else if(ctx->err) {
 | 
			
		||||
		std::string err = std::string("Connection error: ") + ctx->errstr;
 | 
			
		||||
		redisFree(ctx);
 | 
			
		||||
		throw std::runtime_error(err);
 | 
			
		||||
@@ -86,9 +82,8 @@ std::vector<BlockPos> DBRedis::getBlockPos(BlockPos min, BlockPos max)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const char *DBRedis::replyTypeStr(int type)
 | 
			
		||||
{
 | 
			
		||||
	switch (type) {
 | 
			
		||||
const char *DBRedis::replyTypeStr(int type) {
 | 
			
		||||
	switch(type) {
 | 
			
		||||
		case REDIS_REPLY_STATUS:
 | 
			
		||||
			return "REDIS_REPLY_STATUS";
 | 
			
		||||
		case REDIS_REPLY_ERROR:
 | 
			
		||||
@@ -102,7 +97,7 @@ const char *DBRedis::replyTypeStr(int type)
 | 
			
		||||
		case REDIS_REPLY_ARRAY:
 | 
			
		||||
			return "REDIS_REPLY_ARRAY";
 | 
			
		||||
		default:
 | 
			
		||||
			return "(unknown)";
 | 
			
		||||
			return "unknown";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -111,12 +106,12 @@ void DBRedis::loadPosCache()
 | 
			
		||||
{
 | 
			
		||||
	redisReply *reply;
 | 
			
		||||
	reply = (redisReply*) redisCommand(ctx, "HKEYS %s", hash.c_str());
 | 
			
		||||
	if (!reply)
 | 
			
		||||
	if(!reply)
 | 
			
		||||
		throw std::runtime_error("Redis command HKEYS failed");
 | 
			
		||||
	if (reply->type != REDIS_REPLY_ARRAY)
 | 
			
		||||
	if(reply->type != REDIS_REPLY_ARRAY)
 | 
			
		||||
		REPLY_TYPE_ERR(reply, "HKEYS reply");
 | 
			
		||||
	for (size_t i = 0; i < reply->elements; i++) {
 | 
			
		||||
		if (reply->element[i]->type != REDIS_REPLY_STRING)
 | 
			
		||||
	for(size_t i = 0; i < reply->elements; i++) {
 | 
			
		||||
		if(reply->element[i]->type != REDIS_REPLY_STRING)
 | 
			
		||||
			REPLY_TYPE_ERR(reply->element[i], "HKEYS subreply");
 | 
			
		||||
		BlockPos pos = decodeBlockPos(stoi64(reply->element[i]->str));
 | 
			
		||||
		posCache[pos.z].emplace_back(pos.x, pos.y);
 | 
			
		||||
@@ -133,24 +128,25 @@ void DBRedis::HMGET(const std::vector<BlockPos> &positions,
 | 
			
		||||
	argv[0] = "HMGET";
 | 
			
		||||
	argv[1] = hash.c_str();
 | 
			
		||||
 | 
			
		||||
	auto position = positions.begin();
 | 
			
		||||
	size_t remaining = positions.size();
 | 
			
		||||
	size_t abs_i = 0;
 | 
			
		||||
	std::vector<BlockPos>::const_iterator position = positions.begin();
 | 
			
		||||
	std::size_t remaining = positions.size();
 | 
			
		||||
	std::size_t abs_i = 0;
 | 
			
		||||
	while (remaining > 0) {
 | 
			
		||||
		const size_t batch_size = mymin<size_t>(DB_REDIS_HMGET_NUMFIELDS, remaining);
 | 
			
		||||
		const std::size_t batch_size =
 | 
			
		||||
			(remaining > DB_REDIS_HMGET_NUMFIELDS) ? DB_REDIS_HMGET_NUMFIELDS : remaining;
 | 
			
		||||
 | 
			
		||||
		redisReply *reply;
 | 
			
		||||
		{
 | 
			
		||||
			// storage to preserve validity of .c_str()
 | 
			
		||||
			std::string keys[batch_size];
 | 
			
		||||
			for (size_t i = 0; i < batch_size; ++i) {
 | 
			
		||||
			for (std::size_t i = 0; i < batch_size; ++i) {
 | 
			
		||||
				keys[i] = i64tos(encodeBlockPos(*position++));
 | 
			
		||||
				argv[i+2] = keys[i].c_str();
 | 
			
		||||
			}
 | 
			
		||||
			reply = (redisReply*) redisCommandArgv(ctx, batch_size + 2, argv, NULL);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!reply)
 | 
			
		||||
		if(!reply)
 | 
			
		||||
			throw std::runtime_error("Redis command HMGET failed");
 | 
			
		||||
		if (reply->type != REDIS_REPLY_ARRAY)
 | 
			
		||||
			REPLY_TYPE_ERR(reply, "HMGET reply");
 | 
			
		||||
@@ -158,7 +154,7 @@ void DBRedis::HMGET(const std::vector<BlockPos> &positions,
 | 
			
		||||
			freeReplyObject(reply);
 | 
			
		||||
			throw std::runtime_error("HMGET wrong number of elements");
 | 
			
		||||
		}
 | 
			
		||||
		for (size_t i = 0; i < reply->elements; ++i) {
 | 
			
		||||
		for (std::size_t i = 0; i < reply->elements; ++i) {
 | 
			
		||||
			redisReply *subreply = reply->element[i];
 | 
			
		||||
			if (subreply->type == REDIS_REPLY_NIL)
 | 
			
		||||
				continue;
 | 
			
		||||
@@ -166,14 +162,10 @@ void DBRedis::HMGET(const std::vector<BlockPos> &positions,
 | 
			
		||||
				REPLY_TYPE_ERR(subreply, "HMGET subreply");
 | 
			
		||||
			if (subreply->len == 0)
 | 
			
		||||
				throw std::runtime_error("HMGET empty string");
 | 
			
		||||
			result(abs_i + i, ustring(
 | 
			
		||||
				reinterpret_cast<const unsigned char*>(subreply->str),
 | 
			
		||||
				subreply->len
 | 
			
		||||
			));
 | 
			
		||||
			result(abs_i + i, ustring((const unsigned char *) subreply->str, subreply->len));
 | 
			
		||||
		}
 | 
			
		||||
		freeReplyObject(reply);
 | 
			
		||||
 | 
			
		||||
		abs_i += batch_size;
 | 
			
		||||
		abs_i += reply->elements;
 | 
			
		||||
		remaining -= batch_size;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -151,7 +151,7 @@ void DBSQLite3::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
 | 
			
		||||
		 * because it's bad for performance. But rather than silently breaking
 | 
			
		||||
		 * do the right thing and load the blocks again. */
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
		std::cerr << "Warning: suboptimal access pattern for sqlite3 backend" << std::endl;
 | 
			
		||||
		std::cout << "Warning: suboptimal access pattern for sqlite3 backend" << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
		loadBlockCache(z);
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef BLOCKDECODER_H
 | 
			
		||||
#define BLOCKDECODER_H
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
 | 
			
		||||
#include "types.h"
 | 
			
		||||
#include <ZstdDecompressor.h>
 | 
			
		||||
 | 
			
		||||
class BlockDecoder {
 | 
			
		||||
public:
 | 
			
		||||
@@ -12,17 +12,17 @@ public:
 | 
			
		||||
	void reset();
 | 
			
		||||
	void decode(const ustring &data);
 | 
			
		||||
	bool isEmpty() const;
 | 
			
		||||
	// returns "" for air, ignore and invalid nodes
 | 
			
		||||
	const std::string &getNode(u8 x, u8 y, u8 z) const;
 | 
			
		||||
	std::string getNode(u8 x, u8 y, u8 z) const; // returns "" for air, ignore and invalid nodes
 | 
			
		||||
	u8 getParam1(u8 x, u8 y, u8 z) const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	typedef std::unordered_map<uint16_t, std::string> NameMap;
 | 
			
		||||
	typedef std::unordered_map<int, std::string> NameMap;
 | 
			
		||||
	NameMap m_nameMap;
 | 
			
		||||
	uint16_t m_blockAirId, m_blockIgnoreId;
 | 
			
		||||
	int m_blockAirId;
 | 
			
		||||
	int m_blockIgnoreId;
 | 
			
		||||
 | 
			
		||||
	u8 m_version, m_contentWidth;
 | 
			
		||||
	ustring m_mapData;
 | 
			
		||||
 | 
			
		||||
	// one instance for performance
 | 
			
		||||
	ZstdDecompressor m_zstd_decompressor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // BLOCKDECODER_H
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef IMAGE_HEADER
 | 
			
		||||
#define IMAGE_HEADER
 | 
			
		||||
 | 
			
		||||
#include "types.h"
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -8,6 +9,7 @@ struct Color {
 | 
			
		||||
	Color() : r(0), g(0), b(0), a(0) {};
 | 
			
		||||
	Color(u8 r, u8 g, u8 b) : r(r), g(g), b(b), a(255) {};
 | 
			
		||||
	Color(u8 r, u8 g, u8 b, u8 a) : r(r), g(g), b(b), a(a) {};
 | 
			
		||||
	inline Color noAlpha() const { return Color(r, g, b); }
 | 
			
		||||
 | 
			
		||||
	u8 r, g, b, a;
 | 
			
		||||
};
 | 
			
		||||
@@ -17,9 +19,6 @@ public:
 | 
			
		||||
	Image(int width, int height);
 | 
			
		||||
	~Image();
 | 
			
		||||
 | 
			
		||||
	Image(const Image&) = delete;
 | 
			
		||||
	Image& operator=(const Image&) = delete;
 | 
			
		||||
 | 
			
		||||
	void setPixel(int x, int y, const Color &c);
 | 
			
		||||
	Color getPixel(int x, int y);
 | 
			
		||||
	void drawLine(int x1, int y1, int x2, int y2, const Color &c);
 | 
			
		||||
@@ -29,6 +28,10 @@ public:
 | 
			
		||||
	void save(const std::string &filename);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Image(const Image&);
 | 
			
		||||
 | 
			
		||||
	int m_width, m_height;
 | 
			
		||||
	gdImagePtr m_image;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // IMAGE_HEADER
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,25 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
/*
 | 
			
		||||
 * =====================================================================
 | 
			
		||||
 *        Version:  1.0
 | 
			
		||||
 *        Created:  25.08.2012 10:55:29
 | 
			
		||||
 *         Author:  Miroslav Bendík
 | 
			
		||||
 *        Company:  LinuxOS.sk
 | 
			
		||||
 * =====================================================================
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <climits>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#ifndef PIXELATTRIBUTES_H_ADZ35GYF
 | 
			
		||||
#define PIXELATTRIBUTES_H_ADZ35GYF
 | 
			
		||||
 | 
			
		||||
#define BLOCK_SIZE 16
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include "config.h"
 | 
			
		||||
 | 
			
		||||
struct PixelAttribute {
 | 
			
		||||
	PixelAttribute() : height(INT16_MIN), thickness(0) {};
 | 
			
		||||
 | 
			
		||||
	int16_t height;
 | 
			
		||||
	PixelAttribute(): height(std::numeric_limits<int>::min()), thickness(0) {};
 | 
			
		||||
	int height;
 | 
			
		||||
	uint8_t thickness;
 | 
			
		||||
 | 
			
		||||
	inline bool valid_height() const {
 | 
			
		||||
		return height != INT16_MIN;
 | 
			
		||||
	inline bool valid_height() {
 | 
			
		||||
		return height != std::numeric_limits<int>::min();
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -21,13 +28,9 @@ class PixelAttributes
 | 
			
		||||
public:
 | 
			
		||||
	PixelAttributes();
 | 
			
		||||
	virtual ~PixelAttributes();
 | 
			
		||||
 | 
			
		||||
	void setWidth(int width);
 | 
			
		||||
	void scroll();
 | 
			
		||||
 | 
			
		||||
	inline PixelAttribute &attribute(int z, int x) {
 | 
			
		||||
		return m_pixelAttributes[z + 1][x + 1];
 | 
			
		||||
	};
 | 
			
		||||
	inline PixelAttribute &attribute(int z, int x) { return m_pixelAttributes[z + 1][x + 1]; };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void freeAttributes();
 | 
			
		||||
@@ -42,3 +45,6 @@ private:
 | 
			
		||||
	PixelAttribute *m_pixelAttributes[BLOCK_SIZE + 2]; // 1px gradient + empty
 | 
			
		||||
	int m_width;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif /* end of include guard: PIXELATTRIBUTES_H_ADZ35GYF */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef PLAYERATTRIBUTES_H_D7THWFVV
 | 
			
		||||
#define PLAYERATTRIBUTES_H_D7THWFVV
 | 
			
		||||
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -6,7 +7,7 @@
 | 
			
		||||
struct Player
 | 
			
		||||
{
 | 
			
		||||
	std::string name;
 | 
			
		||||
	float x, y, z;
 | 
			
		||||
	double x, y, z;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PlayerAttributes
 | 
			
		||||
@@ -15,8 +16,8 @@ public:
 | 
			
		||||
	typedef std::list<Player> Players;
 | 
			
		||||
 | 
			
		||||
	PlayerAttributes(const std::string &worldDir);
 | 
			
		||||
	Players::const_iterator begin() const;
 | 
			
		||||
	Players::const_iterator end() const;
 | 
			
		||||
	Players::iterator begin();
 | 
			
		||||
	Players::iterator end();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void readFiles(const std::string &playersPath);
 | 
			
		||||
@@ -24,3 +25,6 @@ private:
 | 
			
		||||
 | 
			
		||||
	Players m_players;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif /* end of include guard: PLAYERATTRIBUTES_H_D7THWFVV */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,21 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef TILEGENERATOR_HEADER
 | 
			
		||||
#define TILEGENERATOR_HEADER
 | 
			
		||||
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <iosfwd>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <set>
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "PixelAttributes.h"
 | 
			
		||||
#include "BlockDecoder.h"
 | 
			
		||||
#include "Image.h"
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include "types.h"
 | 
			
		||||
 | 
			
		||||
class BlockDecoder;
 | 
			
		||||
class Image;
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	SCALE_TOP = (1 << 0),
 | 
			
		||||
	SCALE_BOTTOM = (1 << 1),
 | 
			
		||||
@@ -30,12 +31,10 @@ enum {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ColorEntry {
 | 
			
		||||
	ColorEntry() : r(0), g(0), b(0), a(0), t(0) {};
 | 
			
		||||
	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) {};
 | 
			
		||||
	inline Color toColor() const { return Color(r, g, b, a); }
 | 
			
		||||
	uint8_t r, g, b, a; // Red, Green, Blue, Alpha
 | 
			
		||||
	uint8_t t; // "thickness" value
 | 
			
		||||
	ColorEntry(): r(0), g(0), b(0), a(0), t(0) {};
 | 
			
		||||
	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) {};
 | 
			
		||||
	inline Color to_color() const { return Color(r, g, b, a); }
 | 
			
		||||
	uint8_t r, g, b, a, t;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct BitmapThing { // 16x16 bitmap
 | 
			
		||||
@@ -43,19 +42,17 @@ struct BitmapThing { // 16x16 bitmap
 | 
			
		||||
		for (int i = 0; i < 16; ++i)
 | 
			
		||||
			val[i] = 0;
 | 
			
		||||
	}
 | 
			
		||||
	inline bool any_neq(uint16_t v) const {
 | 
			
		||||
	inline bool full() const {
 | 
			
		||||
		for (int i = 0; i < 16; ++i) {
 | 
			
		||||
			if (val[i] != v)
 | 
			
		||||
				return true;
 | 
			
		||||
			if (val[i] != 0xffff)
 | 
			
		||||
				return false;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
	inline bool any() const { return any_neq(0); }
 | 
			
		||||
	inline bool full() const { return !any_neq(0xffff); }
 | 
			
		||||
	inline void set(unsigned int x, unsigned int z) {
 | 
			
		||||
		val[z] |= (1 << x);
 | 
			
		||||
	}
 | 
			
		||||
	inline bool get(unsigned int x, unsigned int z) const {
 | 
			
		||||
	inline bool get(unsigned int x, unsigned int z) {
 | 
			
		||||
		return !!(val[z] & (1 << x));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -67,6 +64,7 @@ class TileGenerator
 | 
			
		||||
{
 | 
			
		||||
private:
 | 
			
		||||
	typedef std::unordered_map<std::string, ColorEntry> ColorMap;
 | 
			
		||||
	typedef std::unordered_set<std::string> NameSet;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	TileGenerator();
 | 
			
		||||
@@ -75,6 +73,7 @@ public:
 | 
			
		||||
	void setScaleColor(const std::string &scaleColor);
 | 
			
		||||
	void setOriginColor(const std::string &originColor);
 | 
			
		||||
	void setPlayerColor(const std::string &playerColor);
 | 
			
		||||
	Color parseColor(const std::string &color);
 | 
			
		||||
	void setDrawOrigin(bool drawOrigin);
 | 
			
		||||
	void setDrawPlayers(bool drawPlayers);
 | 
			
		||||
	void setDrawScale(bool drawScale);
 | 
			
		||||
@@ -92,8 +91,6 @@ public:
 | 
			
		||||
 | 
			
		||||
	void generate(const std::string &input, const std::string &output);
 | 
			
		||||
	void printGeometry(const std::string &input);
 | 
			
		||||
	void dumpBlock(const std::string &input, BlockPos pos);
 | 
			
		||||
 | 
			
		||||
	static std::set<std::string> getSupportedBackends();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
@@ -111,7 +108,6 @@ private:
 | 
			
		||||
	void renderPlayers(const std::string &inputPath);
 | 
			
		||||
	void writeImage(const std::string &output);
 | 
			
		||||
	void printUnknown();
 | 
			
		||||
	void reportProgress(size_t count);
 | 
			
		||||
	int getImageX(int val, bool absolute=false) const;
 | 
			
		||||
	int getImageY(int val, bool absolute=false) const;
 | 
			
		||||
	void setZoomed(int x, int y, Color color);
 | 
			
		||||
@@ -146,22 +142,20 @@ private:
 | 
			
		||||
	int16_t m_geomY; /* Y in terms of rendered image, Z in the world */
 | 
			
		||||
	int16_t m_geomX2;
 | 
			
		||||
	int16_t m_geomY2;
 | 
			
		||||
 | 
			
		||||
	/* */
 | 
			
		||||
	int m_mapWidth;
 | 
			
		||||
	int m_mapHeight;
 | 
			
		||||
	int m_exhaustiveSearch;
 | 
			
		||||
	std::set<std::string> m_unknownNodes;
 | 
			
		||||
	bool m_renderedAny;
 | 
			
		||||
	std::map<int16_t, std::set<int16_t>> m_positions; /* indexed by Z, contains X coords */
 | 
			
		||||
	ColorMap m_colorMap;
 | 
			
		||||
	BitmapThing m_readPixels;
 | 
			
		||||
	BitmapThing m_readInfo;
 | 
			
		||||
	NameSet m_unknownNodes;
 | 
			
		||||
	Color m_color[16][16];
 | 
			
		||||
	uint8_t m_thickness[16][16];
 | 
			
		||||
 | 
			
		||||
	int m_zoom;
 | 
			
		||||
	uint m_scales;
 | 
			
		||||
 | 
			
		||||
	size_t m_progressMax;
 | 
			
		||||
	int m_progressLast; // percentage
 | 
			
		||||
}; // class TileGenerator
 | 
			
		||||
 | 
			
		||||
#endif // TILEGENERATOR_HEADER
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,37 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
/*
 | 
			
		||||
 * =====================================================================
 | 
			
		||||
 *        Version:  1.0
 | 
			
		||||
 *        Created:  18.09.2012 10:20:51
 | 
			
		||||
 *         Author:  Miroslav Bendík
 | 
			
		||||
 *        Company:  LinuxOS.sk
 | 
			
		||||
 * =====================================================================
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <exception>
 | 
			
		||||
#ifndef ZLIBDECOMPRESSOR_H_ZQL1PN8Q
 | 
			
		||||
#define ZLIBDECOMPRESSOR_H_ZQL1PN8Q
 | 
			
		||||
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include "types.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ZlibDecompressor
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	class DecompressError : std::exception {};
 | 
			
		||||
	class DecompressError {
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	ZlibDecompressor(const u8 *data, size_t size);
 | 
			
		||||
	ZlibDecompressor(const unsigned char *data, std::size_t size);
 | 
			
		||||
	~ZlibDecompressor();
 | 
			
		||||
	void setSeekPos(size_t seekPos);
 | 
			
		||||
	size_t seekPos() const;
 | 
			
		||||
	void setSeekPos(std::size_t seekPos);
 | 
			
		||||
	std::size_t seekPos() const;
 | 
			
		||||
	ustring decompress();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const u8 *m_data;
 | 
			
		||||
	size_t m_seekPos, m_size;
 | 
			
		||||
};
 | 
			
		||||
	const unsigned char *m_data;
 | 
			
		||||
	std::size_t m_seekPos;
 | 
			
		||||
	std::size_t m_size;
 | 
			
		||||
}; /* -----  end of class ZlibDecompressor  ----- */
 | 
			
		||||
 | 
			
		||||
#endif /* end of include guard: ZLIBDECOMPRESSOR_H_ZQL1PN8Q */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <exception>
 | 
			
		||||
#include "types.h"
 | 
			
		||||
 | 
			
		||||
class ZstdDecompressor
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	class DecompressError : std::exception {};
 | 
			
		||||
 | 
			
		||||
	ZstdDecompressor();
 | 
			
		||||
	~ZstdDecompressor();
 | 
			
		||||
	void setData(const u8 *data, size_t size, size_t seekPos);
 | 
			
		||||
	size_t seekPos() const;
 | 
			
		||||
	ustring decompress();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void *m_stream; // ZSTD_DStream
 | 
			
		||||
	const u8 *m_data;
 | 
			
		||||
	size_t m_seekPos, m_size;
 | 
			
		||||
};
 | 
			
		||||
@@ -4,8 +4,14 @@
 | 
			
		||||
#define PATH_SEPARATOR '/'
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define BLOCK_SIZE 16
 | 
			
		||||
 | 
			
		||||
#ifdef USE_CMAKE_CONFIG_H
 | 
			
		||||
#include "cmake_config.h"
 | 
			
		||||
#else
 | 
			
		||||
#error missing config
 | 
			
		||||
#define USE_POSTGRESQL 0
 | 
			
		||||
#define USE_LEVELDB 0
 | 
			
		||||
#define USE_REDIS 0
 | 
			
		||||
 | 
			
		||||
#define SHAREDIR "/usr/share/minetest"
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef DB_LEVELDB_HEADER
 | 
			
		||||
#define DB_LEVELDB_HEADER
 | 
			
		||||
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
@@ -26,3 +27,5 @@ private:
 | 
			
		||||
	std::unordered_map<int16_t, std::vector<pos2d>> posCache;
 | 
			
		||||
	leveldb::DB *db;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // DB_LEVELDB_HEADER
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef _DB_POSTGRESQL_H
 | 
			
		||||
#define _DB_POSTGRESQL_H
 | 
			
		||||
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include <libpq-fe.h>
 | 
			
		||||
@@ -21,7 +22,7 @@ protected:
 | 
			
		||||
	PGresult *execPrepared(
 | 
			
		||||
		const char *stmtName, const int paramsNumber,
 | 
			
		||||
		const void **params,
 | 
			
		||||
		const int *paramsLengths = nullptr, const int *paramsFormats = nullptr,
 | 
			
		||||
		const int *paramsLengths = NULL, const int *paramsFormats = NULL,
 | 
			
		||||
		bool clear = true
 | 
			
		||||
	);
 | 
			
		||||
	int pg_binary_to_int(PGresult *res, int row, int col);
 | 
			
		||||
@@ -30,3 +31,5 @@ protected:
 | 
			
		||||
private:
 | 
			
		||||
	PGconn *db;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // _DB_POSTGRESQL_H
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef DB_REDIS_HEADER
 | 
			
		||||
#define DB_REDIS_HEADER
 | 
			
		||||
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
@@ -32,3 +33,5 @@ private:
 | 
			
		||||
	redisContext *ctx;
 | 
			
		||||
	std::string hash;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // DB_REDIS_HEADER
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef _DB_SQLITE3_H
 | 
			
		||||
#define _DB_SQLITE3_H
 | 
			
		||||
 | 
			
		||||
#include "db.h"
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
@@ -31,3 +32,5 @@ private:
 | 
			
		||||
	int16_t blockCachedZ = -10000;
 | 
			
		||||
	std::unordered_map<int16_t, BlockList> blockCache; // indexed by X
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // _DB_SQLITE3_H
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef DB_HEADER
 | 
			
		||||
#define DB_HEADER
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <utility>
 | 
			
		||||
@@ -13,7 +15,6 @@ struct BlockPos {
 | 
			
		||||
	int16_t z;
 | 
			
		||||
 | 
			
		||||
	BlockPos() : x(0), y(0), z(0) {}
 | 
			
		||||
	explicit BlockPos(int16_t v) : x(v), y(v), z(v) {}
 | 
			
		||||
	BlockPos(int16_t x, int16_t y, int16_t z) : x(x), y(y), z(z) {}
 | 
			
		||||
 | 
			
		||||
	// Implements the inverse ordering so that (2,2,2) < (1,1,1)
 | 
			
		||||
@@ -121,3 +122,4 @@ inline BlockPos DB::decodeBlockPos(int64_t hash) const
 | 
			
		||||
 * End black magic *
 | 
			
		||||
 *******************/
 | 
			
		||||
 | 
			
		||||
#endif // DB_HEADER
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,18 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef UTIL_H
 | 
			
		||||
#define UTIL_H
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
template<typename T>
 | 
			
		||||
static inline T mymax(T a, T b)
 | 
			
		||||
{
 | 
			
		||||
	return (a > b) ? a : b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T>
 | 
			
		||||
static inline T mymin(T a, T b)
 | 
			
		||||
{
 | 
			
		||||
	return (a > b) ? b : a;
 | 
			
		||||
}
 | 
			
		||||
#include <fstream>
 | 
			
		||||
 | 
			
		||||
std::string read_setting(const std::string &name, std::istream &is);
 | 
			
		||||
 | 
			
		||||
std::string read_setting_default(const std::string &name, std::istream &is,
 | 
			
		||||
	const std::string &def);
 | 
			
		||||
inline std::string read_setting_default(const std::string &name, std::istream &is, const std::string &def)
 | 
			
		||||
{
 | 
			
		||||
	try {
 | 
			
		||||
		return read_setting(name, is);
 | 
			
		||||
	} catch(const std::runtime_error &e) {
 | 
			
		||||
		return def;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // UTIL_H
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										90
									
								
								mapper.cpp
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								mapper.cpp
									
									
									
									
									
								
							@@ -8,7 +8,7 @@
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "cmake_config.h"
 | 
			
		||||
#include "TileGenerator.h"
 | 
			
		||||
 | 
			
		||||
static void usage()
 | 
			
		||||
@@ -35,11 +35,10 @@ static void usage()
 | 
			
		||||
		{"--colors", "<colors.txt>"},
 | 
			
		||||
		{"--scales", "[t][b][l][r]"},
 | 
			
		||||
		{"--exhaustive", "never|y|full|auto"},
 | 
			
		||||
		{"--dumpblock", "x,y,z"},
 | 
			
		||||
	};
 | 
			
		||||
	const char *top_text =
 | 
			
		||||
		"minetestmapper -i <world_path> -o <output_image.png> [options]\n"
 | 
			
		||||
		"Generate an overview image of a Luanti map.\n"
 | 
			
		||||
		"Generate an overview image of a Minetest map.\n"
 | 
			
		||||
		"\n"
 | 
			
		||||
		"Options:\n";
 | 
			
		||||
	const char *bottom_text =
 | 
			
		||||
@@ -57,36 +56,28 @@ static void usage()
 | 
			
		||||
	printf("\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline bool file_exists(const std::string &path)
 | 
			
		||||
static bool file_exists(const std::string &path)
 | 
			
		||||
{
 | 
			
		||||
	std::ifstream ifs(path);
 | 
			
		||||
	std::ifstream ifs(path.c_str());
 | 
			
		||||
	return ifs.is_open();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline int stoi(const char *s)
 | 
			
		||||
{
 | 
			
		||||
	std::istringstream iss(s);
 | 
			
		||||
	int ret;
 | 
			
		||||
	iss >> ret;
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string search_colors(const std::string &worldpath)
 | 
			
		||||
{
 | 
			
		||||
	if (file_exists(worldpath + "/colors.txt"))
 | 
			
		||||
	if(file_exists(worldpath + "/colors.txt"))
 | 
			
		||||
		return worldpath + "/colors.txt";
 | 
			
		||||
 | 
			
		||||
#ifndef _WIN32
 | 
			
		||||
	char *home = std::getenv("HOME");
 | 
			
		||||
	if (home) {
 | 
			
		||||
		std::string check = std::string(home) + "/.minetest/colors.txt";
 | 
			
		||||
		if (file_exists(check))
 | 
			
		||||
	if(home) {
 | 
			
		||||
		std::string check = ((std::string) home) + "/.minetest/colors.txt";
 | 
			
		||||
		if(file_exists(check))
 | 
			
		||||
			return check;
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	constexpr bool sharedir_valid = !(SHAREDIR[0] == '.' || SHAREDIR[0] == '\0');
 | 
			
		||||
	if (sharedir_valid && file_exists(SHAREDIR "/colors.txt"))
 | 
			
		||||
	if(sharedir_valid && file_exists(SHAREDIR "/colors.txt"))
 | 
			
		||||
		return SHAREDIR "/colors.txt";
 | 
			
		||||
 | 
			
		||||
	std::cerr << "Warning: Falling back to using colors.txt from current directory." << std::endl;
 | 
			
		||||
@@ -119,17 +110,15 @@ int main(int argc, char *argv[])
 | 
			
		||||
		{"scales", required_argument, 0, 'f'},
 | 
			
		||||
		{"noemptyimage", no_argument, 0, 'n'},
 | 
			
		||||
		{"exhaustive", required_argument, 0, 'j'},
 | 
			
		||||
		{"dumpblock", required_argument, 0, 'k'},
 | 
			
		||||
		{0, 0, 0, 0}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::string input;
 | 
			
		||||
	std::string output;
 | 
			
		||||
	std::string colors;
 | 
			
		||||
	bool onlyPrintExtent = false;
 | 
			
		||||
	BlockPos dumpblock(INT16_MIN);
 | 
			
		||||
	std::string colors = "";
 | 
			
		||||
 | 
			
		||||
	TileGenerator generator;
 | 
			
		||||
	bool onlyPrintExtent = false;
 | 
			
		||||
	while (1) {
 | 
			
		||||
		int option_index;
 | 
			
		||||
		int c = getopt_long(argc, argv, "hi:o:", long_options, &option_index);
 | 
			
		||||
@@ -140,6 +129,7 @@ int main(int argc, char *argv[])
 | 
			
		||||
			case 'h':
 | 
			
		||||
				usage();
 | 
			
		||||
				return 0;
 | 
			
		||||
				break;
 | 
			
		||||
			case 'i':
 | 
			
		||||
				input = optarg;
 | 
			
		||||
				break;
 | 
			
		||||
@@ -179,11 +169,19 @@ int main(int argc, char *argv[])
 | 
			
		||||
			case 'd':
 | 
			
		||||
				generator.setBackend(optarg);
 | 
			
		||||
				break;
 | 
			
		||||
			case 'a':
 | 
			
		||||
				generator.setMinY(stoi(optarg));
 | 
			
		||||
			case 'a': {
 | 
			
		||||
					std::istringstream iss(optarg);
 | 
			
		||||
					int miny;
 | 
			
		||||
					iss >> miny;
 | 
			
		||||
					generator.setMinY(miny);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			case 'c':
 | 
			
		||||
				generator.setMaxY(stoi(optarg));
 | 
			
		||||
			case 'c': {
 | 
			
		||||
					std::istringstream iss(optarg);
 | 
			
		||||
					int maxy;
 | 
			
		||||
					iss >> maxy;
 | 
			
		||||
					generator.setMaxY(maxy);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			case 'g': {
 | 
			
		||||
					std::istringstream geometry(optarg);
 | 
			
		||||
@@ -199,19 +197,23 @@ int main(int argc, char *argv[])
 | 
			
		||||
				break;
 | 
			
		||||
			case 'f': {
 | 
			
		||||
					uint flags = 0;
 | 
			
		||||
					if (strchr(optarg, 't'))
 | 
			
		||||
					if(strchr(optarg, 't') != NULL)
 | 
			
		||||
						flags |= SCALE_TOP;
 | 
			
		||||
					if (strchr(optarg, 'b'))
 | 
			
		||||
					if(strchr(optarg, 'b') != NULL)
 | 
			
		||||
						flags |= SCALE_BOTTOM;
 | 
			
		||||
					if (strchr(optarg, 'l'))
 | 
			
		||||
					if(strchr(optarg, 'l') != NULL)
 | 
			
		||||
						flags |= SCALE_LEFT;
 | 
			
		||||
					if (strchr(optarg, 'r'))
 | 
			
		||||
					if(strchr(optarg, 'r') != NULL)
 | 
			
		||||
						flags |= SCALE_RIGHT;
 | 
			
		||||
					generator.setScales(flags);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			case 'z':
 | 
			
		||||
				generator.setZoom(stoi(optarg));
 | 
			
		||||
			case 'z': {
 | 
			
		||||
					std::istringstream iss(optarg);
 | 
			
		||||
					int zoom;
 | 
			
		||||
					iss >> zoom;
 | 
			
		||||
					generator.setZoom(zoom);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			case 'C':
 | 
			
		||||
				colors = optarg;
 | 
			
		||||
@@ -220,33 +222,24 @@ int main(int argc, char *argv[])
 | 
			
		||||
				generator.setDontWriteEmpty(true);
 | 
			
		||||
				break;
 | 
			
		||||
			case 'j': {
 | 
			
		||||
					int mode = EXH_AUTO;;
 | 
			
		||||
					int mode;
 | 
			
		||||
					if (!strcmp(optarg, "never"))
 | 
			
		||||
						mode = EXH_NEVER;
 | 
			
		||||
					else if (!strcmp(optarg, "y"))
 | 
			
		||||
						mode = EXH_Y;
 | 
			
		||||
					else if (!strcmp(optarg, "full"))
 | 
			
		||||
						mode = EXH_FULL;
 | 
			
		||||
					else
 | 
			
		||||
						mode = EXH_AUTO;
 | 
			
		||||
					generator.setExhaustiveSearch(mode);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			case 'k': {
 | 
			
		||||
				std::istringstream iss(optarg);
 | 
			
		||||
				char c, c2;
 | 
			
		||||
				iss >> dumpblock.x >> c >> dumpblock.y >> c2 >> dumpblock.z;
 | 
			
		||||
				if (iss.fail() || c != ',' || c2 != ',') {
 | 
			
		||||
					usage();
 | 
			
		||||
					exit(1);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			default:
 | 
			
		||||
				exit(1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const bool need_output = !onlyPrintExtent && dumpblock.x == INT16_MIN;
 | 
			
		||||
	if (input.empty() || (need_output && output.empty())) {
 | 
			
		||||
	if (input.empty() || (!onlyPrintExtent && output.empty())) {
 | 
			
		||||
		usage();
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
@@ -256,17 +249,14 @@ int main(int argc, char *argv[])
 | 
			
		||||
		if (onlyPrintExtent) {
 | 
			
		||||
			generator.printGeometry(input);
 | 
			
		||||
			return 0;
 | 
			
		||||
		} else if (dumpblock.x != INT16_MIN) {
 | 
			
		||||
			generator.dumpBlock(input, dumpblock);
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(colors.empty())
 | 
			
		||||
		if(colors == "")
 | 
			
		||||
			colors = search_colors(input);
 | 
			
		||||
		generator.parseColorsFile(colors);
 | 
			
		||||
		generator.generate(input, output);
 | 
			
		||||
 | 
			
		||||
	} catch (const std::exception &e) {
 | 
			
		||||
	} catch(std::runtime_error &e) {
 | 
			
		||||
		std::cerr << "Exception: " << e.what() << std::endl;
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
.TH MINETESTMAPPER 6
 | 
			
		||||
.SH NAME
 | 
			
		||||
minetestmapper \- generate an overview image of a Luanti map
 | 
			
		||||
minetestmapper \- generate an overview image of a Minetest map
 | 
			
		||||
.SH SYNOPSIS
 | 
			
		||||
.B minetestmapper
 | 
			
		||||
\fB\-i\fR \fIworld_path\fR
 | 
			
		||||
@@ -9,9 +9,9 @@ minetestmapper \- generate an overview image of a Luanti map
 | 
			
		||||
See additional optional parameters below.
 | 
			
		||||
.SH DESCRIPTION
 | 
			
		||||
.B minetestmapper
 | 
			
		||||
generates an overview image of a Luanti map. This is a port of
 | 
			
		||||
generates an overview image of a minetest map. This is a port of
 | 
			
		||||
the original minetestmapper.py to C++, that is both faster and
 | 
			
		||||
provides more functionality than the obsolete Python script.
 | 
			
		||||
provides more functionality than the deprecated Python script.
 | 
			
		||||
.SH MANDATORY PARAMETERS
 | 
			
		||||
.TP
 | 
			
		||||
.BR \-i " " \fIworld_path\fR
 | 
			
		||||
@@ -77,7 +77,7 @@ Use specific map backend; supported: \fIsqlite3\fP, \fIleveldb\fP, \fIredis\fP,
 | 
			
		||||
Limit area to specific geometry (\fIx:y+w+h\fP where x and y specify the lower left corner), e.g. "--geometry -800:-800+1600+1600"
 | 
			
		||||
 | 
			
		||||
.TP
 | 
			
		||||
.BR \-\-extent
 | 
			
		||||
.BR \-\-extent " " \fIextent\fR
 | 
			
		||||
Don't render the image, just print the extent of the map that would be generated, in the same format as the geometry above.
 | 
			
		||||
 | 
			
		||||
.TP
 | 
			
		||||
@@ -86,14 +86,14 @@ Zoom the image by using more than one pixel per node, e.g. "--zoom 4"
 | 
			
		||||
 | 
			
		||||
.TP
 | 
			
		||||
.BR \-\-colors " " \fIpath\fR
 | 
			
		||||
Forcefully set path to colors.txt file (autodetected otherwise), e.g. "--colors ../world/mycolors.txt"
 | 
			
		||||
Forcefully set path to colors.txt file (it's autodetected otherwise), e.g. "--colors ../minetest/mycolors.txt"
 | 
			
		||||
 | 
			
		||||
.TP
 | 
			
		||||
.BR \-\-scales " " \fIedges\fR
 | 
			
		||||
Draw scales on specified image edges (letters \fIt b l r\fP meaning top, bottom, left and right), e.g. "--scales tbr"
 | 
			
		||||
 | 
			
		||||
.TP
 | 
			
		||||
.BR \-\-exhaustive " " \fImode\fR
 | 
			
		||||
.BR \-\-exhaustive " \fImode\fR
 | 
			
		||||
Select if database should be traversed exhaustively or using range queries, available: \fInever\fP, \fIy\fP, \fIfull\fP, \fIauto\fP
 | 
			
		||||
 | 
			
		||||
Defaults to \fIauto\fP. You shouldn't need to change this, but doing so can improve rendering times on large maps.
 | 
			
		||||
@@ -103,10 +103,6 @@ and
 | 
			
		||||
.B max-y
 | 
			
		||||
when you don't care about the world below e.g. -60 and above 1000 nodes.
 | 
			
		||||
 | 
			
		||||
.TP
 | 
			
		||||
.BR \-\-dumpblock " " \fIpos\fR
 | 
			
		||||
Instead of rendering anything try to load the block at the given position (\fIx,y,z\fR) and print its raw data as hexadecimal.
 | 
			
		||||
 | 
			
		||||
.SH MORE INFORMATION
 | 
			
		||||
Website: https://github.com/minetest/minetestmapper
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								util.cpp
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								util.cpp
									
									
									
									
									
								
							@@ -3,15 +3,15 @@
 | 
			
		||||
 | 
			
		||||
#include "util.h"
 | 
			
		||||
 | 
			
		||||
static std::string trim(const std::string &s)
 | 
			
		||||
static inline std::string trim(const std::string &s)
 | 
			
		||||
{
 | 
			
		||||
	auto isspace = [] (char c) -> bool { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; };
 | 
			
		||||
 | 
			
		||||
	size_t front = 0;
 | 
			
		||||
	while (isspace(s[front]))
 | 
			
		||||
	while(isspace(s[front]))
 | 
			
		||||
		++front;
 | 
			
		||||
	size_t back = s.size() - 1;
 | 
			
		||||
	while (back > front && isspace(s[back]))
 | 
			
		||||
	while(back > front && isspace(s[back]))
 | 
			
		||||
		--back;
 | 
			
		||||
 | 
			
		||||
	return s.substr(front, back - front + 1);
 | 
			
		||||
@@ -23,7 +23,7 @@ std::string read_setting(const std::string &name, std::istream &is)
 | 
			
		||||
	while (is.good()) {
 | 
			
		||||
		is.getline(linebuf, sizeof(linebuf));
 | 
			
		||||
 | 
			
		||||
		for (char *p = linebuf; *p; p++) {
 | 
			
		||||
		for(char *p = linebuf; *p; p++) {
 | 
			
		||||
			if(*p != '#')
 | 
			
		||||
				continue;
 | 
			
		||||
			*p = '\0'; // Cut off at the first #
 | 
			
		||||
@@ -43,13 +43,3 @@ std::string read_setting(const std::string &name, std::istream &is)
 | 
			
		||||
	oss << "Setting '" << name << "' not found";
 | 
			
		||||
	throw std::runtime_error(oss.str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string read_setting_default(const std::string &name, std::istream &is,
 | 
			
		||||
	const std::string &def)
 | 
			
		||||
{
 | 
			
		||||
	try {
 | 
			
		||||
		return read_setting(name, is);
 | 
			
		||||
	} catch(const std::runtime_error &e) {
 | 
			
		||||
		return def;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
#!/bin/bash -e
 | 
			
		||||
 | 
			
		||||
[ -z "$CXX" ] && exit 255
 | 
			
		||||
export CC=false # don't need it actually
 | 
			
		||||
 | 
			
		||||
variant=win32
 | 
			
		||||
[[ "$(basename "$CXX")" == "x86_64-"* ]] && variant=win64
 | 
			
		||||
 | 
			
		||||
#######
 | 
			
		||||
# this expects unpacked libraries similar to what Luanti's buildbot uses
 | 
			
		||||
# $extradlls will typically point to the DLLs for libgcc, libstdc++ and libpng
 | 
			
		||||
libgd_dir=
 | 
			
		||||
zlib_dir=
 | 
			
		||||
zstd_dir=
 | 
			
		||||
sqlite_dir=
 | 
			
		||||
leveldb_dir=
 | 
			
		||||
extradlls=()
 | 
			
		||||
#######
 | 
			
		||||
 | 
			
		||||
[ -f ./CMakeLists.txt ] || exit 1
 | 
			
		||||
 | 
			
		||||
cmake -S . -B build \
 | 
			
		||||
	-DCMAKE_SYSTEM_NAME=Windows \
 | 
			
		||||
	-DCMAKE_EXE_LINKER_FLAGS="-s" \
 | 
			
		||||
	\
 | 
			
		||||
	-DENABLE_LEVELDB=1 \
 | 
			
		||||
	\
 | 
			
		||||
	-DLEVELDB_INCLUDE_DIR=$leveldb_dir/include \
 | 
			
		||||
	-DLEVELDB_LIBRARY=$leveldb_dir/lib/libleveldb.dll.a \
 | 
			
		||||
	-DLIBGD_INCLUDE_DIR=$libgd_dir/include \
 | 
			
		||||
	-DLIBGD_LIBRARY=$libgd_dir/lib/libgd.dll.a \
 | 
			
		||||
	-DSQLITE3_INCLUDE_DIR=$sqlite_dir/include \
 | 
			
		||||
	-DSQLITE3_LIBRARY=$sqlite_dir/lib/libsqlite3.dll.a \
 | 
			
		||||
	-DZLIB_INCLUDE_DIR=$zlib_dir/include \
 | 
			
		||||
	-DZLIB_LIBRARY=$zlib_dir/lib/libz.dll.a \
 | 
			
		||||
	-DZSTD_INCLUDE_DIR=$zstd_dir/include \
 | 
			
		||||
	-DZSTD_LIBRARY=$zstd_dir/lib/libzstd.dll.a \
 | 
			
		||||
 | 
			
		||||
make -C build -j4
 | 
			
		||||
 | 
			
		||||
mkdir pack
 | 
			
		||||
cp -p \
 | 
			
		||||
	AUTHORS colors.txt COPYING README.rst \
 | 
			
		||||
	build/minetestmapper.exe \
 | 
			
		||||
	$leveldb_dir/bin/libleveldb.dll \
 | 
			
		||||
	$libgd_dir/bin/libgd*.dll \
 | 
			
		||||
	$sqlite_dir/bin/libsqlite*.dll \
 | 
			
		||||
	$zlib_dir/bin/zlib1.dll \
 | 
			
		||||
	$zstd_dir/bin/libzstd.dll \
 | 
			
		||||
	"${extradlls[@]}" \
 | 
			
		||||
	pack/
 | 
			
		||||
zipfile=$PWD/minetestmapper-$variant.zip
 | 
			
		||||
(cd pack; zip -9r "$zipfile" *)
 | 
			
		||||
 | 
			
		||||
rm -rf build pack
 | 
			
		||||
echo "Done."
 | 
			
		||||
							
								
								
									
										73
									
								
								util/build_win.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										73
									
								
								util/build_win.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
#!/bin/bash -e
 | 
			
		||||
 | 
			
		||||
#######
 | 
			
		||||
# this expects an env similar to what minetest's buildbot uses
 | 
			
		||||
# extradll_path will typically contain libgcc, libstdc++ and libpng
 | 
			
		||||
toolchain_file=
 | 
			
		||||
toolchain_file64=
 | 
			
		||||
libgd_dir=
 | 
			
		||||
libgd_dir64=
 | 
			
		||||
zlib_dir=
 | 
			
		||||
zlib_dir64=
 | 
			
		||||
sqlite_dir=
 | 
			
		||||
sqlite_dir64=
 | 
			
		||||
leveldb_dir=
 | 
			
		||||
leveldb_dir64=
 | 
			
		||||
extradll_path=
 | 
			
		||||
extradll_path64=
 | 
			
		||||
#######
 | 
			
		||||
 | 
			
		||||
[ -f ./CMakeLists.txt ] || exit 1
 | 
			
		||||
 | 
			
		||||
if [ "$1" == "32" ]; then
 | 
			
		||||
	:
 | 
			
		||||
elif [ "$1" == "64" ]; then
 | 
			
		||||
	toolchain_file=$toolchain_file64
 | 
			
		||||
	libgd_dir=$libgd_dir64
 | 
			
		||||
	zlib_dir=$zlib_dir64
 | 
			
		||||
	sqlite_dir=$sqlite_dir64
 | 
			
		||||
	leveldb_dir=$leveldb_dir64
 | 
			
		||||
	extradll_path=$extradll_path64
 | 
			
		||||
else
 | 
			
		||||
	echo "Usage: $0 <32 / 64>"
 | 
			
		||||
	exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
cmake . \
 | 
			
		||||
	-DCMAKE_INSTALL_PREFIX=/tmp \
 | 
			
		||||
	-DCMAKE_TOOLCHAIN_FILE=$toolchain_file \
 | 
			
		||||
	-DCMAKE_EXE_LINKER_FLAGS="-s" \
 | 
			
		||||
	\
 | 
			
		||||
	-DENABLE_LEVELDB=1 \
 | 
			
		||||
	\
 | 
			
		||||
	-DLIBGD_INCLUDE_DIR=$libgd_dir/include \
 | 
			
		||||
	-DLIBGD_LIBRARY=$libgd_dir/lib/libgd.dll.a \
 | 
			
		||||
	\
 | 
			
		||||
	-DZLIB_INCLUDE_DIR=$zlib_dir/include \
 | 
			
		||||
	-DZLIB_LIBRARY=$zlib_dir/lib/libz.dll.a \
 | 
			
		||||
	\
 | 
			
		||||
	-DSQLITE3_INCLUDE_DIR=$sqlite_dir/include \
 | 
			
		||||
	-DSQLITE3_LIBRARY=$sqlite_dir/lib/libsqlite3.dll.a \
 | 
			
		||||
	\
 | 
			
		||||
	-DLEVELDB_INCLUDE_DIR=$leveldb_dir/include \
 | 
			
		||||
	-DLEVELDB_LIBRARY=$leveldb_dir/lib/libleveldb.dll.a
 | 
			
		||||
 | 
			
		||||
make -j4
 | 
			
		||||
 | 
			
		||||
mkdir pack
 | 
			
		||||
cp -p \
 | 
			
		||||
	AUTHORS colors.txt COPYING README.rst \
 | 
			
		||||
	minetestmapper.exe \
 | 
			
		||||
	$libgd_dir/bin/libgd-3.dll \
 | 
			
		||||
	$zlib_dir/bin/zlib1.dll \
 | 
			
		||||
	$sqlite_dir/bin/libsqlite3-0.dll \
 | 
			
		||||
	$leveldb_dir/bin/libleveldb.dll \
 | 
			
		||||
	$extradll_path/*.dll \
 | 
			
		||||
	pack/
 | 
			
		||||
zipfile=minetestmapper-win$1.zip
 | 
			
		||||
(cd pack; zip -9r ../$zipfile *)
 | 
			
		||||
 | 
			
		||||
make clean
 | 
			
		||||
rm -r pack CMakeCache.txt
 | 
			
		||||
 | 
			
		||||
echo "Done."
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
#!/bin/bash -e
 | 
			
		||||
 | 
			
		||||
install_linux_deps() {
 | 
			
		||||
	local pkgs=(cmake libgd-dev libsqlite3-dev libleveldb-dev libpq-dev libhiredis-dev libzstd-dev)
 | 
			
		||||
 | 
			
		||||
	sudo apt-get update
 | 
			
		||||
	sudo apt-get remove -y 'libgd3' nginx || : # ????
 | 
			
		||||
	sudo apt-get install -y --no-install-recommends "${pkgs[@]}" "$@"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
run_build() {
 | 
			
		||||
	local args=(
 | 
			
		||||
		-DCMAKE_BUILD_TYPE=Debug
 | 
			
		||||
		-DENABLE_LEVELDB=1 -DENABLE_POSTGRESQL=1 -DENABLE_REDIS=1
 | 
			
		||||
	)
 | 
			
		||||
	[[ "$CXX" == clang* ]] && args+=(-DCMAKE_CXX_FLAGS="-fsanitize=address")
 | 
			
		||||
	cmake . "${args[@]}"
 | 
			
		||||
 | 
			
		||||
	make -j2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
do_functional_test() {
 | 
			
		||||
	mkdir testmap
 | 
			
		||||
	echo "backend = sqlite3" >testmap/world.mt
 | 
			
		||||
	sqlite3 testmap/map.sqlite <<END
 | 
			
		||||
CREATE TABLE blocks(pos INT,data BLOB);
 | 
			
		||||
INSERT INTO blocks(pos, data) VALUES(0, x'$(cat util/ci/test_block)');
 | 
			
		||||
END
 | 
			
		||||
 | 
			
		||||
	./minetestmapper --noemptyimage -i ./testmap -o map.png
 | 
			
		||||
	file map.png
 | 
			
		||||
}
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
1b00ffff020278daedd4c1090000080331dd7f691710faf12589235cb12ae870fca6bffefaebafbffefaebafbffefaebbff7b708fdf1ffd11ffdd11ffdd11ffd01000000000000003836d59f010578da63000000010001000000ffffffff000002000000036169720001000d64656661756c743a73746f6e650a0000
 | 
			
		||||
@@ -1,61 +0,0 @@
 | 
			
		||||
local function get_tile(tiles, n)
 | 
			
		||||
	local tile = tiles[n]
 | 
			
		||||
	if type(tile) == 'table' then
 | 
			
		||||
		return tile.name or tile.image
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
core.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(core.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 = core.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,
 | 
			
		||||
})
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
name = dumpnodes
 | 
			
		||||
description = minetestmapper development mod (node dumper)
 | 
			
		||||
@@ -1,183 +0,0 @@
 | 
			
		||||
#!/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 Luanti 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/luanti/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 place
 | 
			
		||||
#    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 <folder>\t\tSet path to the game (for textures), required")
 | 
			
		||||
	print("  -m / --mods <folder>\t\tAdd search path for mod textures")
 | 
			
		||||
	print("  --replace <file>\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.")
 | 
			
		||||
							
								
								
									
										8
									
								
								util/travis/script.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										8
									
								
								util/travis/script.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
#!/bin/bash -e
 | 
			
		||||
mkdir -p travisbuild
 | 
			
		||||
cd travisbuild
 | 
			
		||||
 | 
			
		||||
cmake .. \
 | 
			
		||||
	-DENABLE_LEVELDB=1
 | 
			
		||||
 | 
			
		||||
make -j2
 | 
			
		||||
		Reference in New Issue
	
	Block a user