diff options
author | Andrei Karas <akaras@inbox.ru> | 2011-01-02 01:48:38 +0200 |
---|---|---|
committer | Andrei Karas <akaras@inbox.ru> | 2011-01-02 02:41:24 +0200 |
commit | 3eeae12c498d1a4dbe969462d2ba841f77ee3ccb (patch) | |
tree | ff8eab35e732bc0749fc11677c8873a7b3a58704 /tools | |
download | ManaVerse-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.tar.gz ManaVerse-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.tar.bz2 ManaVerse-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.tar.xz ManaVerse-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.zip |
Initial commit.
This code based on mana client http://www.gitorious.org/mana/mana
and my private repository.
Diffstat (limited to 'tools')
28 files changed, 3060 insertions, 0 deletions
diff --git a/tools/Reorganize.java b/tools/Reorganize.java new file mode 100644 index 000000000..0b9c86060 --- /dev/null +++ b/tools/Reorganize.java @@ -0,0 +1,198 @@ +/* Reorganize (c) 2006 Bjørn Lindeijer + * License: GPL, v2 or later + */ + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.Vector; +import java.io.IOException; +import java.io.File; +import javax.imageio.ImageIO; + +/** + * Tool to reorganize the headgears. + */ +public class Reorganize +{ + private static final int SPRITE_WIDTH = 60; + private static final int SPRITE_HEIGHT = 60; + private static final int FRAMES = 10; + private static final int DIRECTIONS = 4; + + private static final int HAIR_COLORS = 10; + private static final int HAIR_FRAMES = 9; + private static final int HAIR_SPRITE_WIDTH = 40; + private static final int HAIR_SPRITE_HEIGHT = 40; + + private static final int TRANSPARENT = new Color(255, 0, 255).getRGB(); + + public static void main(String[] arg) + { + if (arg.length != 2) { + System.out.println("Usage:\n java Reorganize [source] [target]"); + return; + } + + BufferedImage source = null; + try { + source = ImageIO.read(new File(arg[0])); + } catch (IOException e) { + System.out.println("Error while trying to read " + arg[0] + "."); + e.printStackTrace(); + System.exit(1); + } + + // Read the existing frames into a vector + Vector<BufferedImage> spriteSet = gridCut(source, + HAIR_SPRITE_WIDTH, HAIR_SPRITE_HEIGHT, + HAIR_FRAMES, 1); + + // Determine minimal rectangle that can still contain the contents of + // any frame + /* + Rectangle cropRect = minimumCropRect(spriteSet); + + if (cropRect == null) { + System.out.println( + "Error: no optimal crop rect could be determined."); + System.exit(1); + } + + System.out.println(arg[0] + ": width=\"" + + cropRect.width + "\" height=\"" + cropRect.height + "\""); + */ + + filterHeadgear(spriteSet); + + BufferedImage target = gridDraw( + spriteSet, + new Rectangle(0, 0, HAIR_SPRITE_WIDTH, HAIR_SPRITE_HEIGHT), + HAIR_FRAMES - 4, 1); + + // Save the target image + try { + ImageIO.write(target, "png", new File(arg[1])); + } catch (IOException e) { + System.out.println("Error while trying to write " + arg[1] + "."); + e.printStackTrace(); + System.exit(1); + } + } + + private static Vector<BufferedImage> gridCut( + BufferedImage source, + int width, int height, int xFrames, int yFrames) + { + Vector<BufferedImage> spriteSet = new Vector<BufferedImage>(); + + for (int y = 0; y < yFrames; y++) { + for (int x = 0; x < xFrames; x++) { + BufferedImage sprite = source.getSubimage( + x * width, + y * height, + width, + height); + + spriteSet.add(sprite); + } + } + + return spriteSet; + } + + private static BufferedImage gridDraw(Vector<BufferedImage> spriteSet, + Rectangle cropRect, int xFrames, int yFrames) + { + // Create a new image + BufferedImage target = new BufferedImage( + xFrames * cropRect.width, + yFrames * cropRect.height, + BufferedImage.TYPE_INT_ARGB); + + // Draw the frames onto the target image + Graphics g = target.getGraphics(); + for (int y = 0; y < yFrames; y++) { + for (int x = 0; x < xFrames; x++) { + g.drawImage( + spriteSet.get(x + xFrames * y).getSubimage( + cropRect.x, + cropRect.y, + cropRect.width, + cropRect.height), + x * cropRect.width, + y * cropRect.height, + null); + } + } + + return target; + } + + private static Rectangle minimumCropRect(Vector<BufferedImage> spriteSet) + { + Rectangle cropRect = null; + + for (BufferedImage sprite : spriteSet) { + Rectangle frameCropRect = determineCropRect(sprite); + + if (cropRect == null) { + cropRect = frameCropRect; + } else { + cropRect.add(frameCropRect); + } + } + + // Make crop rect one pixel larger (since we want an inclusive rect) + if (cropRect != null) { + cropRect.add( + cropRect.x + cropRect.width + 1, + cropRect.y + cropRect.height + 1); + } + + return cropRect; + } + + private static Rectangle determineCropRect(BufferedImage image) + { + // Loop through all the pixels, ignoring transparent ones. + Rectangle rect = null; + + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int color = image.getRGB(x, y); + + if (color != TRANSPARENT && (color & 0xFF000000) != 0) { + if (rect == null) { + rect = new Rectangle(x, y, 0, 0); + } else { + rect.add(x, y); + } + } + } + } + + return rect; + } + + private static void filterHairstyle(Vector<BufferedImage> spriteSet) + { + // Remove frame 1, 2, 6 and 7 from each color + for (int i = HAIR_COLORS - 1; i >= 0; i--) { + spriteSet.remove(i * HAIR_FRAMES + 7); + spriteSet.remove(i * HAIR_FRAMES + 6); + spriteSet.remove(i * HAIR_FRAMES + 2); + spriteSet.remove(i * HAIR_FRAMES + 1); + } + } + + private static void filterHeadgear(Vector<BufferedImage> spriteSet) + { + // Remove frame 1, 2, 6 and 7 + spriteSet.remove(7); + spriteSet.remove(6); + spriteSet.remove(2); + spriteSet.remove(1); + } +} diff --git a/tools/adler32.c b/tools/adler32.c new file mode 100644 index 000000000..5dd7e4c17 --- /dev/null +++ b/tools/adler32.c @@ -0,0 +1,68 @@ +/* + * adler32.c (c) 2006 Bjorn Lindeijer + * License: GPL, v2 or later + * + * Calculates Adler-32 checksums for all files passed as argument. + * + * Usage: adler32 [file]... + */ + +#include <stdlib.h> +#include <stdio.h> +#include <zlib.h> + +/** + * Calculates the Adler-32 checksum for the given file. + */ +unsigned long fadler32(FILE *file) +{ + // Obtain file size + fseek(file, 0, SEEK_END); + long fileSize = ftell(file); + rewind(file); + + // Calculate Adler-32 checksum + char *buffer = (char*) malloc(fileSize); + fread(buffer, 1, fileSize, file); + unsigned long adler = adler32(0L, Z_NULL, 0); + adler = adler32(adler, (Bytef*) buffer, fileSize); + free(buffer); + + return adler; +} + +/** + * Prints out usage and exists. + */ +void print_usage() +{ + printf("Usage: adler32 [file]...\n"); + exit(0); +} + +int main(int argc, char *argv[]) +{ + int i; /**< Loops through arguments. */ + + if (argc == 1) + { + print_usage(); + } + + for (i = 1; i < argc; ++i) + { + FILE *file = fopen(argv[i], "r"); + + if (!file) + { + printf("Error while opening '%s' for reading!\n", argv[i]); + exit(1); + } + + unsigned long adler = fadler32(file); + printf("%s %lx\n", argv[i], adler); + fclose(file); + } + + return 0; +} diff --git a/tools/dyecmd/CMakeLists.txt b/tools/dyecmd/CMakeLists.txt new file mode 100644 index 000000000..485666485 --- /dev/null +++ b/tools/dyecmd/CMakeLists.txt @@ -0,0 +1,28 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +PROJECT(MANA_DYECOMMAND) + +IF (NOT VERSION) + SET(VERSION 1.0.0) +ENDIF() + +STRING(REPLACE "." " " _VERSION ${VERSION}) +SEPARATE_ARGUMENTS(_VERSION) +LIST(LENGTH _VERSION _LEN) +IF(NOT (_LEN EQUAL 4 OR _LEN EQUAL 3)) + MESSAGE(FATAL_ERROR "Version needs to be in the form MAJOR.MINOR.RELEASE[.BUILD]") +ENDIF() + +LIST(GET _VERSION 0 VER_MAJOR) +LIST(GET _VERSION 1 VER_MINOR) +LIST(GET _VERSION 2 VER_RELEASE) +IF(_LEN EQUAL 4) + LIST(GET _VERSION 3 VER_BUILD) +ELSE() + SET(VER_BUILD 0) +ENDIF() + +# where to look for cmake modules +SET(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake/Modules) + +ADD_SUBDIRECTORY(src) diff --git a/tools/dyecmd/README b/tools/dyecmd/README new file mode 100644 index 000000000..050a76559 --- /dev/null +++ b/tools/dyecmd/README @@ -0,0 +1,12 @@ +DYECMD +======= + +This tool is used to dye item graphics used by the Mana client according to the +specification described here: http://wiki.themanaworld.org/index.php/Image_dyeing + +The tool expects 3 parameters: + +dyecmd <source_image> <target_image> <dye_string> +e.g.: +dyecmd "armor-legs-shorts.png" "armor-legs-shorts2.png" "W:#222255,6666ff" + diff --git a/tools/dyecmd/dyecmd.cbp b/tools/dyecmd/dyecmd.cbp new file mode 100644 index 000000000..b3d1bb558 --- /dev/null +++ b/tools/dyecmd/dyecmd.cbp @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<CodeBlocks_project_file> + <FileVersion major="1" minor="6" /> + <Project> + <Option title="dyecmd" /> + <Option pch_mode="2" /> + <Option compiler="gcc" /> + <Build> + <Target title="Debug"> + <Option output="bin\Debug\dyecmd" prefix_auto="1" extension_auto="1" /> + <Option object_output="obj\Debug\" /> + <Option type="1" /> + <Option compiler="gcc" /> + <Compiler> + <Add option="-g" /> + </Compiler> + </Target> + <Target title="Release"> + <Option output="bin\Release\dyecmd" prefix_auto="1" extension_auto="1" /> + <Option object_output="obj\Release\" /> + <Option type="1" /> + <Option compiler="gcc" /> + <Compiler> + <Add option="-O2" /> + </Compiler> + <Linker> + <Add option="-s" /> + </Linker> + </Target> + </Build> + <Compiler> + <Add option="-Wall" /> + <Add option="-fexceptions" /> + <Add directory="include" /> + </Compiler> + <Linker> + <Add library="mingw32" /> + <Add library="SDLmain" /> + <Add library="SDL" /> + <Add library="SDL_image" /> + <Add library="png12.dll" /> + <Add directory="lib" /> + </Linker> + <Unit filename="src\dye.cpp" /> + <Unit filename="src\dye.h" /> + <Unit filename="src\dyecmd.cpp" /> + <Unit filename="src\imagewriter.cpp" /> + <Unit filename="src\imagewriter.h" /> + <Extensions> + <code_completion /> + <envvars /> + <debugger /> + <lib_finder disable_auto="1" /> + </Extensions> + </Project> +</CodeBlocks_project_file> diff --git a/tools/dyecmd/run.cmd b/tools/dyecmd/run.cmd new file mode 100644 index 000000000..86726215f --- /dev/null +++ b/tools/dyecmd/run.cmd @@ -0,0 +1 @@ +bin\debug\dyecmd "armor-legs-shorts.png" "armor-legs-shorts2.png" "W:#222255,6666ff"
\ No newline at end of file diff --git a/tools/dyecmd/src/CMakeLists.txt b/tools/dyecmd/src/CMakeLists.txt new file mode 100644 index 000000000..f8e717b78 --- /dev/null +++ b/tools/dyecmd/src/CMakeLists.txt @@ -0,0 +1,59 @@ +FIND_PACKAGE(SDL REQUIRED) +FIND_PACKAGE(SDL_image REQUIRED) +FIND_PACKAGE(PNG REQUIRED) + +IF (CMAKE_COMPILER_IS_GNUCXX) + # Help getting compilation warnings + SET(CMAKE_CXX_FLAGS "-Wall") + IF (WIN32) + # This includes enough debug information to get something useful + # from Dr. Mingw while keeping binary size down. Almost useless + # with gdb, though. + SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -ggdb0 -gstabs2") + ENDIF() +ENDIF() + +SET(FLAGS "-DPACKAGE_VERSION=\\\"${VERSION}\\\"") +SET(FLAGS "${FLAGS} -DPKG_DATADIR=\\\"${PKG_DATADIR}/\\\"") +SET(FLAGS "${FLAGS} -DLOCALEDIR=\\\"${LOCALEDIR}/\\\"") + +IF (CMAKE_BUILD_TYPE) + STRING(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) + IF((CMAKE_BUILD_TYPE_TOLOWER MATCHES debug) OR + (CMAKE_BUILD_TYPE_TOLOWER MATCHES relwithdebinfo)) + SET(FLAGS "${FLAGS} -DDEBUG") + ENDIF() +ENDIF() + +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${SDL_INCLUDE_DIR} + ${SDLIMAGE_INCLUDE_DIR} + ${PNG_INCLUDE_DIR} + ) + +# Fix some stuff that gets not hidden by mainline modules +MARK_AS_ADVANCED(SDLIMAGE_INCLUDE_DIR) +MARK_AS_ADVANCED(SDLIMAGE_LIBRARY) +MARK_AS_ADVANCED(SDLMAIN_LIBRARY) +MARK_AS_ADVANCED(SDL_INCLUDE_DIR) +MARK_AS_ADVANCED(SDL_LIBRARY) + +SET(SRCS + dye.cpp + dye.h + dyecmd.cpp + imagewriter.cpp + imagewriter.h + ) + +SET (PROGRAMS dyecmd) + +ADD_EXECUTABLE(dyecmd WIN32 ${SRCS}) + +TARGET_LINK_LIBRARIES(dyecmd + ${SDL_LIBRARY} + ${SDLIMAGE_LIBRARY} + ${PNG_LIBRARIES}) + +SET_TARGET_PROPERTIES(dyecmd PROPERTIES COMPILE_FLAGS "${FLAGS}") diff --git a/tools/dyecmd/src/dye.cpp b/tools/dyecmd/src/dye.cpp new file mode 100755 index 000000000..77da2ebbb --- /dev/null +++ b/tools/dyecmd/src/dye.cpp @@ -0,0 +1,202 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <algorithm> +#include <sstream> +#include <iostream> + +#include "dye.h" + +Palette::Palette(const std::string &description) +{ + mLoaded = false; + int size = description.length(); + if (size == 0) return; + if (description[0] != '#') + { + std::cout << "Missing # in the palette description " + << "in the third parameter." << std::endl; + return; + } + + int pos = 1; + for (;;) + { + if (pos + 6 > size) break; + int v = 0; + for (int i = 0; i < 6; ++i) + { + char c = description[pos + i]; + int n; + if ('0' <= c && c <= '9') + n = c - '0'; + else if ('A' <= c && c <= 'F') + n = c - 'A' + 10; + else if ('a' <= c && c <= 'f') + n = c - 'a' + 10; + else + { + std::cout << "invalid Hexadecimal description: " + << description << std::endl; + return; + } + + v = (v << 4) | n; + } + Color c = { { v >> 16, v >> 8, v } }; + mColors.push_back(c); + pos += 6; + if (pos == size) + { + mLoaded = true; + return; + } + if (description[pos] != ',') + break; + + ++pos; + } + + mLoaded = true; +} + +void Palette::getColor(int intensity, int color[3]) const +{ + // Return implicit black + if (intensity == 0) + { + color[0] = 0; + color[1] = 0; + color[2] = 0; + return; + } + + int last = mColors.size(); + if (last == 0) return; + + int i = intensity * last / 255; + int t = intensity * last % 255; + + int j = t != 0 ? i : i - 1; + // Get the exact color if any, the next color otherwise. + int r2 = mColors[j].value[0], + g2 = mColors[j].value[1], + b2 = mColors[j].value[2]; + + if (t == 0) + { + // Exact color. + color[0] = r2; + color[1] = g2; + color[2] = b2; + return; + } + + // Get the previous color. First color is implicitly black. + int r1 = 0, g1 = 0, b1 = 0; + if (i > 0) + { + r1 = mColors[i - 1].value[0]; + g1 = mColors[i - 1].value[1]; + b1 = mColors[i - 1].value[2]; + } + + // Perform a linear interpolation. + color[0] = ((255 - t) * r1 + t * r2) / 255; + color[1] = ((255 - t) * g1 + t * g2) / 255; + color[2] = ((255 - t) * b1 + t * b2) / 255; +} + +Dye::Dye(const std::string &description) +{ + mLoaded = false; + for (int i = 0; i < 7; ++i) + mPalettes[i] = 0; + + if (description.empty()) return; + + std::string::size_type next_pos = 0, length = description.length(); + do + { + std::string::size_type pos = next_pos; + next_pos = description.find(';', pos); + if (next_pos == std::string::npos) + next_pos = length; + if (next_pos <= pos + 3 || description[pos + 1] != ':') + { + std::cout << "Dyeing: Missing ':' in channel description." + << std::endl; + return; + } + int i = 0; + switch (description[pos]) + { + case 'R': i = 0; break; + case 'G': i = 1; break; + case 'Y': i = 2; break; + case 'B': i = 3; break; + case 'M': i = 4; break; + case 'C': i = 5; break; + case 'W': i = 6; break; + default: + std::cout << "Dyeing: Invalid channel. Not in [R,G,Y,B,M,C,W]" + << std::endl; + return; + } + mPalettes[i] = new Palette( + description.substr(pos + 2, next_pos - pos - 2)); + + if (!mPalettes[i]->loaded()) + return; + + ++next_pos; + } + while (next_pos < length); + + mLoaded = true; +} + +Dye::~Dye() +{ + for (int i = 0; i < 7; ++i) + delete mPalettes[i]; +} + +void Dye::update(int color[3]) const +{ + int cmax = std::max(color[0], std::max(color[1], color[2])); + if (cmax == 0) return; + + int cmin = std::min(color[0], std::min(color[1], color[2])); + int intensity = color[0] + color[1] + color[2]; + + if (cmin != cmax && + (cmin != 0 || (intensity != cmax && intensity != 2 * cmax))) + { + // not pure + return; + } + + int i = (color[0] != 0) | ((color[1] != 0) << 1) | ((color[2] != 0) << 2); + + if (mPalettes[i - 1]) + mPalettes[i - 1]->getColor(cmax, color); +} diff --git a/tools/dyecmd/src/dye.h b/tools/dyecmd/src/dye.h new file mode 100755 index 000000000..922f3370e --- /dev/null +++ b/tools/dyecmd/src/dye.h @@ -0,0 +1,105 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DYE_H +#define DYE_H + +#include <vector> + +#include <string> + +/** + * Class for performing a linear interpolation between colors. + */ +class Palette +{ + public: + + /** + * Creates a palette based on the given string. + * The string is either a file name or a sequence of hexadecimal RGB + * values separated by ',' and starting with '#'. + */ + Palette(const std::string &); + + /** + * Gets a pixel color depending on its intensity. + */ + void getColor(int intensity, int color[3]) const; + + /** + * Tells if the palette was successfully loaded. + */ + bool loaded() const + { return mLoaded; } + + private: + + struct Color { unsigned char value[3]; }; + + std::vector< Color > mColors; + + bool mLoaded; +}; + +/** + * Class for dispatching pixel-recoloring amongst several palettes. + */ +class Dye +{ + public: + + /** + * Creates a set of palettes based on the given string. + * + * The parts of string are separated by semi-colons. Each part starts + * by an uppercase letter, followed by a colon and then a palette name. + */ + Dye(const std::string &); + + /** + * Destroys the associated palettes. + */ + ~Dye(); + + /** + * Tells if the dye description was successfully loaded. + */ + bool loaded() const + { return mLoaded; } + + /** + * Modifies a pixel color. + */ + void update(int color[3]) const; + + private: + + /** + * The order of the palettes, as well as their uppercase letter, is: + * + * Red, Green, Yellow, Blue, Magenta, White (or rather gray). + */ + Palette *mPalettes[7]; + bool mLoaded; +}; + +#endif diff --git a/tools/dyecmd/src/dyecmd.cpp b/tools/dyecmd/src/dyecmd.cpp new file mode 100755 index 000000000..5e06e500d --- /dev/null +++ b/tools/dyecmd/src/dyecmd.cpp @@ -0,0 +1,155 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <SDL/SDL.h> +#include <SDL/SDL_image.h> + +#include "dye.h" +#include "imagewriter.h" + +using namespace std; + +// return values +enum ReturnValues +{ + RETURN_OK = 0, + INVALID_PARAMETER_LIST = 100, + INVALID_INPUT_IMAGE = 101, + INVALID_OUTPUT_IMAGE = 102, + INVALID_DYE_PARAMETER = 105 +}; + +SDL_Surface* recolor(SDL_Surface* tmpImage, Dye* dye) +{ + SDL_PixelFormat rgba; + rgba.palette = NULL; + rgba.BitsPerPixel = 32; + rgba.BytesPerPixel = 4; + rgba.Rmask = 0xFF000000; rgba.Rloss = 0; rgba.Rshift = 24; + rgba.Gmask = 0x00FF0000; rgba.Gloss = 0; rgba.Gshift = 16; + rgba.Bmask = 0x0000FF00; rgba.Bloss = 0; rgba.Bshift = 8; + rgba.Amask = 0x000000FF; rgba.Aloss = 0; rgba.Ashift = 0; + rgba.colorkey = 0; + rgba.alpha = 255; + + SDL_Surface *surf = SDL_ConvertSurface(tmpImage, &rgba, SDL_SWSURFACE); + //SDL_FreeSurface(tmpImage); <-- We'll free the surface later. + + Uint32 *pixels = static_cast< Uint32 * >(surf->pixels); + for (Uint32 *p_end = pixels + surf->w * surf->h; pixels != p_end; ++pixels) + { + int alpha = (*pixels >> rgba.Ashift) & 255; + if (!alpha) continue; + int v[3]; + + v[0] = (*pixels >> rgba.Rshift) & 255; + v[1] = (*pixels >> rgba.Gshift) & 255; + v[2] = (*pixels >> rgba.Bshift) & 255; + dye->update(v); + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + *pixels = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | alpha; +#else + *pixels = v[0] | (v[1] << 8) | (v[2] << 16) | (alpha << 24); +#endif + } + + return surf; +} + +void printHelp() +{ + cout << endl + << "This tool is used to dye item graphics used by the Mana client " + << "according to the specification described here: " + << endl << "http://doc.manasource.org/image_dyeing_system" + << endl << endl << + "The tool expects 3 parameters:" << endl + << "dyecmd <source_image> <target_image> <dye_string>" << endl + << "e.g.:" << endl + << "dyecmd \"armor-legs-shorts.png\" " + <<"\"armor-legs-shorts2.png\" \"W:#222255,6666ff\"" << std::endl; +} + +int main(int argc, char* argv[]) +{ + Dye* dye = NULL; + SDL_Surface* source = NULL, *target = NULL; + ReturnValues returnValue = RETURN_OK; + + if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) + { + printHelp(); + } + // not enough or to many parameters + else if (argc != 4) + { + cout << INVALID_PARAMETER_LIST << " - INVALID_PARAMETER_LIST"; + printHelp(); + returnValue = INVALID_PARAMETER_LIST; + } + else + { + // Start dyeing process. + string inputFile = argv[1]; + string outputFile = argv[2]; + string dyeDescription = argv[3]; + + dye = new Dye(dyeDescription); + if (!dye->loaded()) + { + cout << INVALID_DYE_PARAMETER << " - INVALID_DYE_PARAMETER"; + printHelp(); + returnValue = INVALID_DYE_PARAMETER; + } + else + { + source = IMG_Load(inputFile.c_str()); + if (!source) + { + cout << INVALID_INPUT_IMAGE << " - INVALID_INPUT_IMAGE"; + printHelp(); + returnValue = INVALID_INPUT_IMAGE; + } + else + { + target = recolor(source, dye); + + if (!ImageWriter::writePNG(target, outputFile)) + { + cout << INVALID_OUTPUT_IMAGE << " - INVALID_OUTPUT_IMAGE"; + printHelp(); + returnValue = INVALID_OUTPUT_IMAGE; + } + } // Valid source image file + } // Valid dye parameter + } // Parameters ok + + if (source) + SDL_FreeSurface(source); + if (target) + SDL_FreeSurface(target); + if (dye) + delete dye; + + return returnValue; +} diff --git a/tools/dyecmd/src/imagewriter.cpp b/tools/dyecmd/src/imagewriter.cpp new file mode 100755 index 000000000..d237abbfb --- /dev/null +++ b/tools/dyecmd/src/imagewriter.cpp @@ -0,0 +1,113 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "imagewriter.h" + +#include <png.h> +#include <iostream> +#include <string> +#include <SDL/SDL.h> + +bool ImageWriter::writePNG(SDL_Surface *surface, + const std::string &filename) +{ + // TODO Maybe someone can make this look nice? + FILE *fp = fopen(filename.c_str(), "wb"); + if (!fp) + { + std::cout << "PNG writer: Could not open file for writing: " + << filename << std::endl; + return false; + } + + png_structp png_ptr; + png_infop info_ptr; + png_bytep *row_pointers; + int colortype; + + if (SDL_MUSTLOCK(surface)) { + SDL_LockSurface(surface); + } + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + if (!png_ptr) + { + std::cout << "PNG writer: Had trouble creating png_structp" + << std::endl; + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + std::cout << "PNG writer: Could not create png_info" << std::endl; + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + std::cout << "PNG writer: problem writing to : " + << filename << std::endl; + return false; + } + + png_init_io(png_ptr, fp); + + colortype = (surface->format->BitsPerPixel == 24) ? + PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + + png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, 8, colortype, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_write_info(png_ptr, info_ptr); + + png_set_packing(png_ptr); + + row_pointers = new png_bytep[surface->h]; + if (!row_pointers) + { + std::cout + << "PNG writer: Had trouble converting surface to row pointers" + << std::endl; + return false; + } + + for (int i = 0; i < surface->h; i++) + { + row_pointers[i] = (png_bytep)(Uint8 *)surface->pixels + i * surface->pitch; + } + + png_write_image(png_ptr, row_pointers); + png_write_end(png_ptr, info_ptr); + + fclose(fp); + + delete [] row_pointers; + + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + + if (SDL_MUSTLOCK(surface)) + SDL_UnlockSurface(surface); + + return true; +} diff --git a/tools/dyecmd/src/imagewriter.h b/tools/dyecmd/src/imagewriter.h new file mode 100755 index 000000000..a8bcdf57d --- /dev/null +++ b/tools/dyecmd/src/imagewriter.h @@ -0,0 +1,31 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iosfwd> + +struct SDL_Surface; + +class ImageWriter +{ + public: + static bool writePNG(SDL_Surface *surface, + const std::string &filename); +}; diff --git a/tools/tmxcopy/base64.cpp b/tools/tmxcopy/base64.cpp new file mode 100644 index 000000000..8cea60f90 --- /dev/null +++ b/tools/tmxcopy/base64.cpp @@ -0,0 +1,148 @@ +/* + +----------------------------------------------------------------------+ + | PHP HTML Embedded Scripting Language Version 3.0 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2000 PHP Development Team (See Credits file) | + +----------------------------------------------------------------------+ + | This program is free software; you can redistribute it and/or modify | + | it under the terms of one of the following licenses: | + | | + | A) the GNU General Public License as published by the Free Software | + | Foundation; either version 2 of the License, or (at your option) | + | any later version. | + | | + | B) the PHP License as published by the PHP Development Team and | + | included in the distribution in the file: LICENSE | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU General Public License for more details. | + | | + | You should have received a copy of both licenses referred to here. | + | If you did not, or have any questions about PHP licensing, please | + | contact core@php.net. | + +----------------------------------------------------------------------+ + | Author: Jim Winstead (jimw@php.net) | + +----------------------------------------------------------------------+ + */ + +#include <string.h> +#include <stdlib.h> + +#include "base64.h" + +static char base64_table[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '\0' +}; +static char base64_pad = '='; + +unsigned char *php3_base64_encode(const unsigned char *string, int length, int *ret_length) { + const unsigned char *current = string; + int i = 0; + unsigned char *result = (unsigned char *)malloc(((length + 3 - length % 3) * 4 / 3 + 1) * sizeof(char)); + + while (length > 2) { /* keep going until we have less than 24 bits */ + result[i++] = base64_table[current[0] >> 2]; + result[i++] = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)]; + result[i++] = base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)]; + result[i++] = base64_table[current[2] & 0x3f]; + + current += 3; + length -= 3; /* we just handle 3 octets of data */ + } + + /* now deal with the tail end of things */ + if (length != 0) { + result[i++] = base64_table[current[0] >> 2]; + if (length > 1) { + result[i++] = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)]; + result[i++] = base64_table[(current[1] & 0x0f) << 2]; + result[i++] = base64_pad; + } + else { + result[i++] = base64_table[(current[0] & 0x03) << 4]; + result[i++] = base64_pad; + result[i++] = base64_pad; + } + } + if(ret_length) { + *ret_length = i; + } + result[i] = '\0'; + return result; +} + +/* as above, but backwards. :) */ +unsigned char *php3_base64_decode(const unsigned char *string, int length, int *ret_length) { + const unsigned char *current = string; + int ch, i = 0, j = 0, k; + char *chp; + + unsigned char *result = (unsigned char *)malloc(length + 1); + + if (result == NULL) { + return NULL; + } + + /* run through the whole string, converting as we go */ + while ((ch = *current++) != '\0') { + if (ch == base64_pad) break; + + /* When Base64 gets POSTed, all pluses are interpreted as spaces. + This line changes them back. It's not exactly the Base64 spec, + but it is completely compatible with it (the spec says that + spaces are invalid). This will also save many people considerable + headache. - Turadg Aleahmad <turadg@wise.berkeley.edu> + */ + + if (ch == ' ') ch = '+'; + + chp = strchr(base64_table, ch); + if (chp == NULL) continue; + ch = chp - base64_table; + + switch(i % 4) { + case 0: + result[j] = ch << 2; + break; + case 1: + result[j++] |= ch >> 4; + result[j] = (ch & 0x0f) << 4; + break; + case 2: + result[j++] |= ch >>2; + result[j] = (ch & 0x03) << 6; + break; + case 3: + result[j++] |= ch; + break; + } + i++; + } + + k = j; + /* mop things up if we ended on a boundary */ + if (ch == base64_pad) { + switch(i % 4) { + case 0: + case 1: + free(result); + return NULL; + case 2: + k++; + case 3: + result[k++] = 0; + } + } + if(ret_length) { + *ret_length = j; + } + result[k] = '\0'; + return result; +} diff --git a/tools/tmxcopy/base64.h b/tools/tmxcopy/base64.h new file mode 100644 index 000000000..92c230169 --- /dev/null +++ b/tools/tmxcopy/base64.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | PHP HTML Embedded Scripting Language Version 3.0 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997,1998 PHP Development Team (See Credits file) | + +----------------------------------------------------------------------+ + | This program is free software; you can redistribute it and/or modify | + | it under the terms of one of the following licenses: | + | | + | A) the GNU General Public License as published by the Free Software | + | Foundation; either version 2 of the License, or (at your option) | + | any later version. | + | | + | B) the PHP License as published by the PHP Development Team and | + | included in the distribution in the file: LICENSE | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU General Public License for more details. | + | | + | You should have received a copy of both licenses referred to here. | + | If you did not, or have any questions about PHP licensing, please | + | contact core@php.net. | + +----------------------------------------------------------------------+ + | Author: Jim Winstead (jimw@php.net) | + +----------------------------------------------------------------------+ + */ + +#ifndef BASE64_H +#define BASE64_H + +extern unsigned char *php3_base64_encode(const unsigned char *, int, int *); +extern unsigned char *php3_base64_decode(const unsigned char *, int, int *); + +#endif /* BASE64_H */ diff --git a/tools/tmxcopy/map.cpp b/tools/tmxcopy/map.cpp new file mode 100644 index 000000000..3e1f9bdaf --- /dev/null +++ b/tools/tmxcopy/map.cpp @@ -0,0 +1,679 @@ +/* + * TMXCopy + * Copyright (C) 2007 Philipp Sehmisch + * Copyright (C) 2009 Steve Cotton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <cstring> +#include <iostream> +#include <list> + +#include <string.h> +#include <zlib.h> +#include <cassert> +#include <ctime> + +#include "xmlutils.h" +#include "zlibutils.h" +#include "base64.h" +#include "tostring.h" + +#include "map.hpp" + +Map::Map(std::string filename): + mMaxGid(0) +{ + std::cout<<"Loading map "<<filename<<std::endl; + //load XML tree + mXmlDoc = xmlReadFile(filename.c_str(), NULL, 0); + + if (!mXmlDoc) + { + std::cerr<<"Could not load "<<filename; + throw 1; + } + + xmlNodePtr rootNode = xmlDocGetRootElement(mXmlDoc); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "map")) { + std::cerr<<filename<<"is not a Tiled map file!"; + throw 1; + } + + mWidth = XML::getProperty(rootNode, "width", 0); + mHeight = XML::getProperty(rootNode, "height", 0); + int layerNum = 0; + for_each_xml_child_node(node, rootNode) + { + if (xmlStrEqual(node->name, BAD_CAST "tileset")) + { + //add tilesets to vector of used tilesets + Tileset* tileset = new Tileset; + tileset->name = XML::getProperty(node, "name", "Unnamed"); + tileset->tilewidth = XML::getProperty(node, "tilewidth", 0); + tileset->tileheight = XML::getProperty(node, "tileheight", 0); + tileset->firstgid = XML::getProperty(node, "firstgid", 0); + for_each_xml_child_node(imageNode, node) + { + if (xmlStrEqual(imageNode->name, BAD_CAST "image")) + tileset->imagefile = XML::getProperty(imageNode, "source", ""); + } + + //add tileset to tileset list + Map::mTilesets.push_back(tileset); + } + } + + for_each_xml_child_node(node, rootNode) + { + if (xmlStrEqual(node->name, BAD_CAST "layer")) + { + //build layer information + std::string name = XML::getProperty(node, "name", ""); + Layer* layer = new Layer(name, mWidth * mHeight); + if ( + (mWidth != XML::getProperty(node, "width" , 0)) || + (mHeight != XML::getProperty(node, "height", 0)) || + (0 != XML::getProperty(node, "x" , 0)) || + (0 != XML::getProperty(node, "y" , 0))) + { + std::cerr<<"Error: layer size does not match map size for layer \""<<name<<"\" in "<<filename<<std::endl; + throw 1; + } + + for_each_xml_child_node(dataNode, node) + { + if (!xmlStrEqual(dataNode->name, BAD_CAST "data")) continue; + + std::string encoding = XML::getProperty(dataNode, "encoding", ""); + std::string compression = XML::getProperty(dataNode, "compression", ""); + + if (encoding != "base64") + { + std::cerr<<"Layers in "<<filename<<" are not base64 encoded!"; + return; + } + + // Read base64 encoded map file + xmlNodePtr dataChild = dataNode->xmlChildrenNode; + if (!dataChild) + continue; + + int len = strlen((const char*)dataChild->content) + 1; + unsigned char *charData = new unsigned char[len + 1]; + const char *charStart = (const char*)dataChild->content; + unsigned char *charIndex = charData; + + while (*charStart) { + if (*charStart != ' ' && *charStart != '\t' && + *charStart != '\n') + { + *charIndex = *charStart; + charIndex++; + } + charStart++; + } + *charIndex = '\0'; + + int binLen; + unsigned char *binData = + php3_base64_decode(charData, strlen((char*)charData), &binLen); + + delete[] charData; + + if (binData) { + if (compression == "gzip") + { + unsigned char *inflated; + unsigned int inflatedSize = + inflateMemory(binData, binLen, inflated); + free(binData); + binData = inflated; + binLen = inflatedSize; + if (inflated == NULL) + { + std::cerr<<"Error: while decompressing layer "<<layerNum<<" in "<<filename; + throw 1; + } + } + + int c = 0; + for (int i = 0; i < binLen - 3; i += 4) { + int gid = binData[i] | + binData[i + 1] << 8 | + binData[i + 2] << 16 | + binData[i + 3] << 24; + + if (gid == 0) + { + layer->at(c).tileset = -1; + layer->at(c).index = 0; + } + else + { + for (int s = mTilesets.size()-1; s >= 0; s--) + { + if (mTilesets.at(s)->firstgid <= gid) + { + layer->at(c).tileset = s; + layer->at(c).index = gid - mTilesets.at(s)->firstgid; + if (mMaxGid < gid) mMaxGid = gid; + break; + } + } + } + + c++; + } + free(binData); + } else { + std::cerr<<"error processing layer data in "<<filename<<std::endl; + } + } + mLayers.push_back(layer); + layerNum++; + } + } + + std::cout<<"tilesets: "<<mTilesets.size()<<std::endl; + std::cout<<"layers:"<<mLayers.size()<<std::endl; + std::cout<<"largest GID:"<<mMaxGid<<std::endl<<std::endl; +} + +/** + * When copying tiles from another map, add new tilesets to this map, and return the translation table. + */ +std::map<int, int> Map::addAndTranslateTilesets(const Map* srcMap) +{ + std::map<int, int> translation; + translation[-1] = -1; + std::vector<Tileset*>* srcTilesets = const_cast<Map*>(srcMap)->getTilesets(); + + for (std::vector<Tileset*>::size_type a = 0; a < srcTilesets->size(); a++) + { + std::vector<Tileset*>::size_type b; + for (b = 0; b < mTilesets.size(); b++) + { + if (*srcTilesets->at(a) == *mTilesets.at(b)) + { + break; + } + } + if (b == mTilesets.size()) + { + mMaxGid += srcTilesets->at(a)->firstgid; + Tileset* destTileset = new Tileset(*srcTilesets->at(a)); + destTileset->firstgid = mMaxGid;//it is possible to get some holes in the gid index this way but who cares, we got 32bit. + mTilesets.push_back(destTileset); + } + translation[a] = b; + } + return translation; +} + +bool Map::overwrite( Map* srcMap, + int srcX, int srcY, int srcWidth, int srcHeight, + int destX, int destY, + const ConfigurationOptions& config) +{ + //plausibility check of coordinates + bool checkPassed = true; + if (srcX + srcWidth > srcMap->getWidth()) { + std::cerr<<"Error: Area exceeds right map border of source map!"<<std::endl; + checkPassed = false; + } + if (srcY + srcHeight > srcMap->getHeight()) { + std::cerr<<"Error: Area exceeds lower map border of source map!"<<std::endl; + checkPassed = false; + } + if (destX + srcWidth > mWidth) { + std::cerr<<"Error: Area exceeds right map border of target map!"<<std::endl; + checkPassed = false; + } + if (destY + srcHeight > mHeight) { + std::cerr<<"Error: Area exceeds lower map border of target map!"<<std::endl; + checkPassed = false; + } + if (!config.createMissingLayers) + { + if (config.copyLayersByOrdinal) + { + if (srcMap->getNumberOfLayers() > mLayers.size()) { + std::cerr<<"Error: Source has more layers than target map"<<std::endl + <<"(and the command-line \"create layers\" option was not used)"<<std::endl; + checkPassed = false; + } + } + else + { + for (size_t i = 0; i < srcMap->getNumberOfLayers(); i++) + { + Layer* srcLayer = srcMap->getLayer(i); + Layer* destLayer = getLayer(srcLayer->getName()); + if (!destLayer) + { + std::cerr<<"Error: target map has no layer named \""<<srcLayer->getName()<<"\""<<std::endl + <<"(and the command-line \"create layers\" option was not used)"<<std::endl; + checkPassed = false; + } + } + } + } + + if (!checkPassed) return false; + + std::map<int, int> translation = addAndTranslateTilesets(srcMap); + + + //combining layer information + for (size_t i = 0; i < srcMap->getNumberOfLayers(); i++) + { + Layer* srcLayer = srcMap->getLayer(i); + Layer* destLayer = NULL; + if (config.copyLayersByOrdinal) + { + if (i < mLayers.size()) + { + destLayer = mLayers.at(i); + } + } + else + { + destLayer = getLayer(srcLayer->getName()); + } + + if (!destLayer) + { + assert(config.createMissingLayers); /* Tested earlier, in the checkPassed section */ + /* Generate a name for the new layer, which must be + * unique in the target map, and should be unique in + * the source map (to avoid collisions later in the + * copying process). + * Start by trying the name of the source layer. + */ + std::string name = srcLayer->getName(); + if (getLayer(name)) + { + int k=0; + do + { + name = "Layer" + toString(k); + k++; + } while (getLayer(name) || srcMap->getLayer(name)); + } + + destLayer = new Layer(name, mWidth * mHeight); + mLayers.push_back(destLayer); + std::cout<<"Created new layer "<<name<<std::endl; + } + + for (int y=0; y<srcHeight; y++) + { + + for (int x=0; x<srcWidth; x++) + { + int srcIndex = srcMap->getWidth() * (y + srcY) + (x + srcX); + int tgtIndex = mWidth * (y + destY) + (x + destX); + Tile tmpTile = srcLayer->at(srcIndex); + tmpTile.tileset = translation[tmpTile.tileset]; + destLayer->at(tgtIndex) = tmpTile; + } + } + } + + std::clog<<"copying successful!"<<std::endl; + return true; +} + +bool Map::randomFill(Map* templateMap, const std::string& destLayerName, + int destX, int destY, int destWidth, int destHeight, + const ConfigurationOptions& config) +{ + //plausibility check of coordinates + bool checkPassed = true; + if (destX + destWidth > mWidth) + { + std::cerr<<"Error: Area exceeds right map border of target map!"<<std::endl; + checkPassed = false; + } + if (destY + destHeight > mHeight) + { + std::cerr<<"Error: Area exceeds lower map border of target map!"<<std::endl; + checkPassed = false; + } + if (destWidth < templateMap->getWidth()) + { + std::cerr<<"Error: Template is wider than target area"<<std::endl; + checkPassed = false; + } + if (destWidth < templateMap->getHeight()) + { + std::cerr<<"Error: Template is higher than target area"<<std::endl; + checkPassed = false; + } + if (templateMap->getNumberOfLayers() == 0) + { + std::cerr<<"Error: Template has no layers"<<std::endl; + checkPassed = false; + } + if (!config.createMissingLayers && !getLayer(destLayerName)) + { + std::cerr<<"Error: target map has no layer named \""<<destLayerName<<"\""<<std::endl + <<"(and the command-line \"create layers\" option was not used)"<<std::endl; + checkPassed = false; + } + if (!checkPassed) return false; + + std::map<int, int> translation = addAndTranslateTilesets(templateMap); + + + Layer* destLayer = getLayer(destLayerName); + if (!destLayer) + { + destLayer = new Layer(destLayerName, mWidth * mHeight); + mLayers.push_back(destLayer); + std::cout<<"Created new layer "<<destLayerName<<std::endl; + } + + /* Now generate extra tiles. + * + * After considering ways to specify the number of objects to add, I think + * the best user interface (without integrating it with Tiled) is to place + * a small number of objects each time, and have the user run the utility + * several times, reloading the map in Tiled each time until it looks + * right. Simpler than typing magic numbers in at a command prompt. + * + * This algorithm completes after a fixed number of attempts at placing an + * object; regardless of how many attempts are successful. + * For 2x1 trees, destWidth*destHeight/10 is very sparse, dW*dH/2 is dense. + */ + srand(time(NULL)); + int patternsGenerated = 0; + int occupiedAreas = 0; + for (int i = destWidth*destHeight / 10; i > 0; i--) + { + /* Pick a random location, with enough tiles left and down from it to + * fit the template in (the +1 is because it starts on tile (x,y)) + */ + int x = destX + (rand() % (destWidth - templateMap->getWidth () + 1)); + int y = destY + (rand() % (destHeight - templateMap->getHeight() + 1)); + + bool areaIsClear = true; + + for (int loop_y=0; loop_y<templateMap->getHeight(); loop_y++) + { + for (int loop_x=0; loop_x<templateMap->getWidth(); loop_x++) + { + if (! destLayer->getTile(x+loop_x, y+loop_y, mWidth).empty()) + { + areaIsClear = false; + } + } + } + + if (areaIsClear) + { + int p = rand() % templateMap->getNumberOfLayers(); + + Layer* srcLayer = templateMap->getLayer(p); + for (int loop_y=0; loop_y<templateMap->getHeight(); loop_y++) + { + for (int loop_x=0; loop_x<templateMap->getWidth(); loop_x++) + { + Tile& srcTile = srcLayer->getTile(loop_x, loop_y, templateMap->getWidth()); + Tile& destTile = destLayer->getTile(x+loop_x, y+loop_y, mWidth); + destTile.tileset = translation[srcTile.tileset]; + destTile.index = srcTile.index; + } + } + patternsGenerated++; + } + else + { + occupiedAreas++; + } + } + + std::clog<<"Generated " << patternsGenerated << " new objects" <<std::endl; + (void) occupiedAreas; // Unused at the moment, but keep it without a compiler warning about unused variables + return true; +} + +bool Map::translateAllLayers(Map* templateMap, const std::string& destLayerName, + const ConfigurationOptions& config) +{ + bool checkPassed = true; + if (templateMap->getNumberOfLayers() != 2) + { + std::cerr<<"Error: template should have exactly 2 layers"<<std::endl; + checkPassed = false; + } + if (!config.createMissingLayers && !getLayer(destLayerName)) + { + std::cerr<<"Error: target map has no layer named \""<<destLayerName<<"\""<<std::endl + <<"(and the command-line \"create layers\" option was not used)"<<std::endl; + checkPassed = false; + } + if (!checkPassed) return false; + + //TODO This will unnecessarily add tilesets that are in the template but + //not used in the main map + std::map<int, int> tilesetTranslation = addAndTranslateTilesets(templateMap); + + //Lacking a better name, we'll say this is translating visible layers to collision + std::map<Tile, Tile> collisionTranslation; + + Layer* fromLayer = templateMap->getLayer(0); + Layer* toLayer = templateMap->getLayer(1); + for (int xy = (templateMap->getWidth() * templateMap->getHeight() -1); + xy >= 0; xy--) + { + Tile fromTile = fromLayer->at(xy); + Tile toTile = toLayer->at(xy); + if (!fromTile.empty()) + { + fromTile.tileset = tilesetTranslation[fromTile.tileset]; + toTile.tileset = tilesetTranslation[toTile.tileset]; + + collisionTranslation[fromTile] = toTile; + } + } + + /* Now apply that template to the game map */ + Layer* destLayer = getLayer(destLayerName); + if (!destLayer) + { + destLayer = new Layer(destLayerName, mWidth * mHeight); + mLayers.push_back(destLayer); + std::cout<<"Created new layer "<<destLayerName<<std::endl; + } + + for (std::vector<Layer*>::iterator layer = mLayers.begin(); + layer != mLayers.end(); + layer++) + { + if ((*layer)->getName() == destLayerName) + continue; + + for (int xy = mWidth * mHeight -1; xy >= 0; xy--) + { + Tile& fromTile = (*layer)->at(xy); + Tile& toTile = destLayer->at(xy); + std::map<Tile,Tile>::iterator iteratedTile = collisionTranslation.find(fromTile); + + if (iteratedTile != collisionTranslation.end()) + { + toTile = (*iteratedTile).second; + } + } + + } + + return true; +} + + + +int Map::save(std::string filename) +{ + //remove old tileset and layer information in XML tree + xmlNodePtr rootNode = xmlDocGetRootElement(mXmlDoc); + std::list<xmlNodePtr> toRemove; + for_each_xml_child_node(node, rootNode) + { + if ( xmlStrEqual(node->name, BAD_CAST "tileset") + || xmlStrEqual(node->name, BAD_CAST "layer")) + { + toRemove.push_back(node); + } + } + + while (!toRemove.empty()) + { + xmlUnlinkNode(toRemove.back()); + xmlFreeNode(toRemove.back()); + toRemove.pop_back(); + } + + //TODO: reorganize GIDs + + //add new tileset information to XML tree + for (size_t i = 0; i< mTilesets.size(); i++) + { + xmlNodePtr newNode; + + xmlAddChild(rootNode, xmlNewDocText(mXmlDoc, BAD_CAST " ")); + newNode = xmlNewNode(NULL, BAD_CAST "tileset"); + xmlNewProp(newNode, BAD_CAST "name", BAD_CAST mTilesets.at(i)->name.c_str()); + xmlNewProp(newNode, BAD_CAST "firstgid", BAD_CAST toString(mTilesets.at(i)->firstgid).c_str()); + xmlNewProp(newNode, BAD_CAST "tilewidth", BAD_CAST toString(mTilesets.at(i)->tilewidth).c_str()); + xmlNewProp(newNode, BAD_CAST "tileheight", BAD_CAST toString(mTilesets.at(i)->tileheight).c_str()); + xmlAddChild(newNode, xmlNewDocText(mXmlDoc, BAD_CAST "\n ")); + xmlNodePtr imageNode = xmlNewNode(NULL, BAD_CAST "image"); + xmlNewProp(imageNode, BAD_CAST "source", BAD_CAST mTilesets.at(i)->imagefile.c_str()); + xmlAddChild(newNode, imageNode); + xmlAddChild(newNode, xmlNewDocText(mXmlDoc, BAD_CAST "\n ")); + xmlAddChild(rootNode, newNode); + xmlAddChild(rootNode, xmlNewDocText(mXmlDoc, BAD_CAST "\n")); + } + + //add new layer information to XML tree + for (size_t i = 0; i < mLayers.size(); i++) + { + //lay out layer information in binary + unsigned char* binData = (unsigned char*)malloc(mWidth * mHeight * 4); + + for (int a=0; a < mWidth * mHeight; a++) + { + Tile tile = mLayers.at(i)->at(a); + int ldata; + if (tile.tileset != -1) + { + ldata = tile.index + mTilesets.at(tile.tileset)->firstgid; + } else { + ldata = 0; + } + + binData[a * 4 + 0] = (ldata & 0x000000ff); + binData[a * 4 + 1] = (ldata & 0x0000ff00) >> 8; + binData[a * 4 + 2] = (ldata & 0x00ff0000) >> 16; + binData[a * 4 + 3] = (ldata & 0xff000000) >> 24; + } + + + //GZIP layer information + /* + unsigned char* gzipData = (unsigned char*)malloc((mWidth * mHeight * 4) + 128); + unsigned int gzipLen; + compressMemory (binData, (mWidth * mHeight * 4) + 128, gzipData, gzipLen); + free (binData); + std::cout<<"GZIP length: "<<gzipLen<<std::endl; + */ + + //encode layer information in base64 + unsigned char* base64Data; + int base64len; + //base64Data = php3_base64_encode(gzipData, gzipLen, &base64len); + base64Data = php3_base64_encode(binData, (mWidth * mHeight * 4), &base64len); + //free(gzipData); + + xmlNodePtr newNode; + xmlAddChild(rootNode, xmlNewDocText(mXmlDoc, BAD_CAST " ")); + newNode = xmlNewNode(NULL, BAD_CAST "layer"); + xmlNewProp(newNode, BAD_CAST "name", BAD_CAST (mLayers.at(i)->getName()).c_str()); + xmlNewProp(newNode, BAD_CAST "width", BAD_CAST toString(mWidth).c_str()); + xmlNewProp(newNode, BAD_CAST "height", BAD_CAST toString(mHeight).c_str()); + xmlAddChild(newNode, xmlNewDocText(mXmlDoc, BAD_CAST "\n ")); + xmlNodePtr dataNode = xmlNewNode(NULL, BAD_CAST "data"); + xmlNewProp(dataNode, BAD_CAST "encoding", BAD_CAST "base64"); + //xmlNewProp(dataNode, BAD_CAST "compression", BAD_CAST "gzip"); + xmlAddChild(dataNode, xmlNewDocText(mXmlDoc, BAD_CAST "\n ")); + xmlAddChild(dataNode, xmlNewDocText(mXmlDoc, BAD_CAST base64Data)); + xmlAddChild(dataNode, xmlNewDocText(mXmlDoc, BAD_CAST "\n ")); + xmlAddChild(newNode, dataNode); + xmlAddChild(newNode, xmlNewDocText(mXmlDoc, BAD_CAST "\n ")); + xmlAddChild(rootNode, newNode); + xmlAddChild(rootNode, xmlNewDocText(mXmlDoc, BAD_CAST "\n")); + + free(base64Data); + free(binData); + } + + //save XML tree + int retval = xmlSaveFile(filename.c_str(), mXmlDoc); + + if (retval == -1) + { + std::cerr<<"Could not write outfile "<<filename<<std::endl; + return false; + } + else + { + std::cout<<"File saved successfully to "<<filename<<std::endl; + return true; + } +} + +Layer* Map::getLayer(std::string name) +{ + for (std::vector<Layer*>::iterator layer = mLayers.begin(); + layer != mLayers.end(); + layer++) + { + if ((*layer)->getName() == name) + return *layer; + } + return NULL; +} + +Map::~Map() +{ + for (std::vector<Layer*>::iterator layer = mLayers.begin(); + layer != mLayers.end(); + layer++) + { + delete *layer; + } + + for (std::vector<Tileset*>::iterator tileset = mTilesets.begin(); + tileset != mTilesets.end(); + tileset++) + { + delete *tileset; + } + + xmlFreeDoc(mXmlDoc); +} diff --git a/tools/tmxcopy/map.hpp b/tools/tmxcopy/map.hpp new file mode 100644 index 000000000..ff878811a --- /dev/null +++ b/tools/tmxcopy/map.hpp @@ -0,0 +1,183 @@ +/* + * TMXCopy + * Copyright (C) 2007 Philipp Sehmisch + * Copyright (C) 2009 Steve Cotton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <string> +#include <vector> +#include <set> +#include <map> +#include <libxml/parser.h> + +struct ConfigurationOptions +{ + /* When copying map layers, how to match source layer to + * destination layer. + * + * True: Pair the first layer to the first layer, the second + * to the second, etc. + * + * False: Pair up layers with matching names. + */ + bool copyLayersByOrdinal; + + /* Create extra layers in the target as necessary. */ + bool createMissingLayers; +}; + +struct Tileset +{ + std::string imagefile; + int firstgid; + std::string name; + int tilewidth; + int tileheight; + + bool operator== (Tileset const& a) + { + return (imagefile == a.imagefile && + tilewidth == a.tilewidth && + tileheight == a.tileheight + ); + } + + Tileset() + { + } + + Tileset(const Tileset& src) : + imagefile(src.imagefile), firstgid(src.firstgid), + name(src.name), + tilewidth(src.tilewidth), tileheight(src.tileheight) + { + } + +}; + +/** + * A tile in a layer of a map. + * + * With the exception of the emptyTile and empty() function, + * interpreting what this tile represents depends on the map it + * belongs to (specifically the ordering of that map's tilesets). + */ +struct Tile +{ + int tileset; // number of tileset + size_t index; // index in said tileset + + bool empty() + { + return (tileset == -1); + } + + /* This is to allow std::map<Tile,Tile> */ + bool operator< (const Tile& b) const + { + return ((tileset < b.tileset) || + ((tileset == b.tileset) && (index < b.index))); + } + + bool operator!= (const Tile& b) const + { + return ((tileset != b.tileset) || (index != b.index)); + } +}; + +typedef std::vector<Tile> LayerTiles; + +/* This represents an empty tile in the layer. + * Note that {0,0} would be the first tile in the first tileset. + */ +const Tile emptyTile = {-1, 0}; + +class Layer +{ + public: + /* name - the name of the layer, as shown in Tiled + * tileCount - total number of tiles (width*height) + */ + Layer(std::string name, LayerTiles::size_type tileCount) + : mTiles(tileCount, emptyTile), + mName (name) + { + } + + std::string getName() { return mName; } + Tile& at(LayerTiles::size_type c) { return mTiles.at(c); } + Tile& getTile(int x, int y, int mapWidth) { return mTiles.at(x + y*mapWidth); } + + private: + LayerTiles mTiles; + std::string mName; +}; + +class Map +{ + public: + Map(std::string filename); + ~Map(); + + /** + * Copies an area from srcMap, replacing its current contents. + */ + bool overwrite(Map* srcMap, + int srcX, int srcY, int srcWidth, int srcHeight, + int destX, int destY, + const ConfigurationOptions& config); + + /** + * Fills an area of this map with random parts of the template. + * Currently, each layer of the template is treated as an object that + * should be copied in its entirity. + */ + bool randomFill(Map* templateMap, const std::string& destLayerName, + int destX, int destY, int destWidth, int destHeight, + const ConfigurationOptions& config); + + /** + * Translates a layer - using the template, generates collision from visible layers (for example). + * TODO - avoid confusion with the geometry term "translate" + */ + bool translateAllLayers(Map* templateMap, const std::string& destLayerName, + const ConfigurationOptions& config); + + int save(std::string filename); + + size_t getNumberOfLayers() { return mLayers.size(); } + + Layer* getLayer(size_t num) { return mLayers.at(num); } + Layer* getLayer(std::string name); + + std::vector<Tileset*>* getTilesets() { return &mTilesets; } + + int getWidth() { return mWidth; } + int getHeight() { return mHeight; } + + private: + std::map<int, int> addAndTranslateTilesets(const Map* srcMap); + + std::vector<Layer*> mLayers; + + int mWidth; + int mHeight; + int mMaxGid; + + std::vector<Tileset*> mTilesets; + + xmlDocPtr mXmlDoc; +}; diff --git a/tools/tmxcopy/readme.txt b/tools/tmxcopy/readme.txt new file mode 100644 index 000000000..d130edd60 --- /dev/null +++ b/tools/tmxcopy/readme.txt @@ -0,0 +1,104 @@ +=== TMX Map Tools === + +A set of tools for manipulating TMX map files. + +After using any of these tools, load the map in Tiled and save it again; until this is done the game may not be able to load the file (see Bugs for an explanation). + + +=== TMXCopy === + +Tmxcopy is a little tool that allows to copy parts of one TMX map to another map. This will make it much easier to match the border areas of maps. The program is command line based. The usage is: + + tmxcopy [-c] [-n] srcFile x y width height tgtFile x y [outfile] + +Here an example: +When you want to copy the lower right corner (20x20 tiles) of mapA.tmx to the upper left corner of mapB.txt you would open map A with tiled and check at which coordinates the area you want to copy begins. Let's say mapA is 120x130 tiles. Then the area you want to copy would begin at 100:110 and would be 20x20 tiles large. So the first part of the command is: + + tmxcopy mapA.tmx 100 110 20 20 + +Then you open the target map to check the coordinates where you want to put the copied map part. We want the upper left corner, so the coordinates are 0:0. That means the next part of the command would be: + + mapB.tmx 0 0 + +The command is now complete: + + tmxcopy mapA.tmx 100 110 20 20 mapB.tmx 0 0 + +But when you enter this command the mapB will be overwritten. This could be a problem when you made an error in the command. So it is saver to write the output to a new map file so we can look at the result in Tiled before we replace the original map: + + tmxcopy mapA.tmx 100 110 20 20 mapB.tmx 0 0 temp.tmx + +Now we can check temp.tmx to see if the copying worked correctly. + + +Which layer gets copied to which: +By default layers are copied to layers of the same name. The -n option will make it copy by layer number instead. + + mapA: Ground, Fringe, Over, Collision, Object + mapB: Ground, Fencing, Fringe, Over, Collision, Object + The default copies Ground->Ground, Fringe->Fringe, Over->Over, Collision->Collision (the object layer is not affected) + -n copies Ground->Ground, Fringe->Fencing, Over->Fringe, Collision->Over (mapB's collision and object layers are not affected) + + mapA: Ground, Fringe, Over, Collision, Object + mapC: Ground, Fringe, Overhead, Collision, Object + The default quits with an error + -n copies Over->Overhead + +The -c option creates layers as needed. Using it to copy mapB to mapA will add a Fencing layer to mapA. + + +=== TMX Random Fill === + +This is for generating big areas of woodland (or other things that want lots of randomly-placed patterns). + +Usage: tmx_random_fill mapFile destLayer x y width height templateFile [-o outfile] + -c create layers, if they don't already exist in the target + -o save results to outfile, instead of overwriting the original + +Fill a rectangular area of mapFile's layer 'destLayer' with a random selection from the templateFile. + +The template is a map where each layer is a pattern. For example, to make a woodland: + Create a new 2x1 tile map (yes, this is tiny, and only the base of the tree will be visible). + Add the Woodland_x3 tileset, using the correct height (96 pixels). + Make a layer, add a tree. + Make a layer, add the second tree. + Make a layer, add the third tree. + Save this as template_trees.tmx + Run tmx_random_fill with the appropriate options (destLayer will be "Fringe") + +It will then randomly place trees, but only in places where they won't overlap with other things on that layer. The size of the template map is the size of the area which must be empty in the destination layer. + +Running it several times (without specifying an output file) will add more objects. After considering ways to specify the number of objects to add, I think the easiest is to just reload the map in Tiled each time until it looks right (you don't need to quit Tiled while running tmx_random_fill). + + +=== TMX Collide / Translate === + +A big woodland with lots of randomly-placed trees needs a complex collision layer, most of which can be generated from the visible layers. +This tool does that automatic generation. + +It's not limited to adding collision tiles; it can be used for any task where tiles are added to one layer based on the tiles present in other layers. + +Usage: tmxcollide [-c] mapFile destLayer templateFile [-o outfile] + -c create layers, if they don't already exist in the target + -o save results to outfile, instead of overwriting the original + +To fill the collision layer, "destLayer" should be "Collision". + +As with TMX Random Fill, this tool takes a template map; for this tool it should have exactly two layers. + Upper layer: tiles to translate to (collision tiles) + Lower layer: tiles to translate from (visible tiles) +Blank tiles in the lower layer will be ignored (put a blank in the upper layer too). + + +=== Bugs (for all these programs) === + +The programs work so far but there are still some minor problems: + +-Only tested for Mana-compilant maps. I don't guarantee that it works with Tiled maps that are made for other games and thus use different features. +-Compressed maps (tmx.gz) can not be handled yet (but compressed or uncompressed layers work properly) +-When the target map has an object layer it is moved to the bottom of the layer list of the map (no problem for the game but inconvenient for editing). Objects on the source map are ignored. +-All tilesets included in the srcFile (TMXCopy) or template (TMXRandomFill and TMXCollide) will be added to the output file, even if they aren't needed for the tiles that are added. +-Layer data of output file isn't gzip-compressed yet +-Created TMX file is a bit malformated (but working properly) + +The last 2 problems can be solved easily by opening and saving the map in Tiled. diff --git a/tools/tmxcopy/tmx_random_fill.cpp b/tools/tmxcopy/tmx_random_fill.cpp new file mode 100644 index 000000000..0039ff8b2 --- /dev/null +++ b/tools/tmxcopy/tmx_random_fill.cpp @@ -0,0 +1,115 @@ +/* + * TMXRandomFill + * Copyright (C) 2007 Philipp Sehmisch + * Copyright (C) 2009 Steve Cotton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <string> +#include <unistd.h> + +#include "map.hpp" + +void printUsage() +{ + std::cerr<<"Usage: tmx_random_fill [-c] mapFile destLayer x y width height templateFile [-o outfile]"<<std::endl + <<" -c create layers, if they don't already exist in the target"<<std::endl + <<" -o save results to outfile, instead of overwriting the original"<<std::endl + <<std::endl + <<"Fill a rectangular area of mapFile's layer 'destLayer' with a random selection from the templateFile"<<std::endl + <<"See readme.txt for full documentation"<<std::endl; +} + +int main(int argc, char * argv[] ) +{ + ConfigurationOptions config = {0}; + std::string outFile; + + int opt; + while ((opt = getopt(argc, argv, "co:")) != -1) + { + switch (opt) + { + case 'c': + config.createMissingLayers = true; + break; + case 'o': + if (optarg) + { + outFile = optarg; + } else { + printUsage(); + return -1; + } + break; + case '?': + std::cerr<<"Unrecognized option"<<std::endl; + printUsage(); + return -1; + } + } + + if ((argc-optind) < 7) + { + std::cerr<<"Too few args"<<std::endl; + printUsage(); + return -1; + } + if ((argc-optind) > 7) + { + std::cerr<<"Too many args"<<std::endl; + printUsage(); + return -1; + } + + std::string mapFile = argv[optind+0]; + std::string destLayer = argv[optind+1]; + int destX= atoi(argv[optind+2]); + int destY= atoi(argv[optind+3]); + int width= atoi(argv[optind+4]); + int height=atoi(argv[optind+5]); + std::string templateFile = argv[optind+6]; + if (outFile.empty()) + { + outFile = mapFile; + } + + // plausibility check of command line options + if (height < 1 || width < 1 || destX < 0 || destY < 0) + { + std::cerr<<"Illegal coordinates!"<<std::endl; + printUsage(); + return -1; + } + + try + { + Map* mainMap = new Map(mapFile); + Map* templateMap = new Map(templateFile); + if (mainMap->randomFill(templateMap, destLayer, destX, destY, width, height, config)) + { + mainMap->save(outFile); + } else { + return -1; + } + delete mainMap; + delete templateMap; + } + catch (int) + { + return -1; + } +} diff --git a/tools/tmxcopy/tmxcollide.cpp b/tools/tmxcopy/tmxcollide.cpp new file mode 100644 index 000000000..75b0a6a27 --- /dev/null +++ b/tools/tmxcopy/tmxcollide.cpp @@ -0,0 +1,103 @@ +/* + * TMXTranslate / TMXCollide + * Copyright (C) 2007 Philipp Sehmisch + * Copyright (C) 2009 Steve Cotton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <string> +#include <unistd.h> + +#include "map.hpp" + +void printUsage() +{ + std::cerr<<"Usage: tmxcollide [-c] mapFile destLayer templateFile [-o outfile]"<<std::endl + <<" -c create layers, if they don't already exist in the target"<<std::endl + <<" -o save results to outfile, instead of overwriting the original"<<std::endl + <<std::endl + <<"Using the template, translate visible layers to the collision layer"<<std::endl + <<"See readme.txt for full documentation"<<std::endl; +} + +int main(int argc, char * argv[] ) +{ + ConfigurationOptions config = {0}; + std::string outFile; + + int opt; + while ((opt = getopt(argc, argv, "co:")) != -1) + { + switch (opt) + { + case 'c': + config.createMissingLayers = true; + break; + case 'o': + if (optarg) + { + outFile = optarg; + } else { + printUsage(); + return -1; + } + break; + case '?': + std::cerr<<"Unrecognized option"<<std::endl; + printUsage(); + return -1; + } + } + + if ((argc-optind) < 3) + { + std::cerr<<"Too few args"<<std::endl; + printUsage(); + return -1; + } + if ((argc-optind) > 3) + { + std::cerr<<"Too many args"<<std::endl; + printUsage(); + return -1; + } + + std::string mapFile = argv[optind+0]; + std::string destLayer = argv[optind+1]; + std::string templateFile = argv[optind+2]; + if (outFile.empty()) + { + outFile = mapFile; + } + + try + { + Map* mainMap = new Map(mapFile); + Map* templateMap = new Map(templateFile); + if (mainMap->translateAllLayers(templateMap, destLayer, config)) + { + mainMap->save(outFile); + } else { + return -1; + } + delete mainMap; + delete templateMap; + } + catch (int) + { + return -1; + } +} diff --git a/tools/tmxcopy/tmxcopy.cbp b/tools/tmxcopy/tmxcopy.cbp new file mode 100644 index 000000000..237f6fdee --- /dev/null +++ b/tools/tmxcopy/tmxcopy.cbp @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<CodeBlocks_project_file> + <FileVersion major="1" minor="6" /> + <Project> + <Option title="untitled" /> + <Option pch_mode="0" /> + <Option compiler="gcc" /> + <Build> + <Target title="default"> + <Option output="TMXCopy.exe" prefix_auto="0" extension_auto="0" /> + <Option type="1" /> + <Option compiler="gcc" /> + <Option projectResourceIncludeDirsRelation="1" /> + <Linker> + <Add library="libxml2" /> + <Add library="z" /> + </Linker> + </Target> + </Build> + <VirtualTargets> + <Add alias="All" targets="default;" /> + </VirtualTargets> + <Unit filename="base64.cpp" /> + <Unit filename="base64.h" /> + <Unit filename="main.cpp" /> + <Unit filename="map.cpp" /> + <Unit filename="map.hpp" /> + <Unit filename="tostring.h" /> + <Unit filename="xmlutils.cpp" /> + <Unit filename="xmlutils.h" /> + <Unit filename="zlibutils.cpp" /> + <Unit filename="zlibutils.h" /> + <Extensions> + <code_completion /> + <envvars /> + <debugger /> + </Extensions> + </Project> +</CodeBlocks_project_file> diff --git a/tools/tmxcopy/tmxcopy.cpp b/tools/tmxcopy/tmxcopy.cpp new file mode 100644 index 000000000..9c9655d71 --- /dev/null +++ b/tools/tmxcopy/tmxcopy.cpp @@ -0,0 +1,106 @@ +/* + * TMXCopy + * Copyright (C) 2007 Philipp Sehmisch + * Copyright (C) 2009 Steve Cotton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <string> +#include <unistd.h> + +#include "map.hpp" + +void printUsage() +{ + std::cerr<<"Usage: tmxcopy [-c] [-n] srcFile x y width height tgtFile x y [outfile]"<<std::endl + <<" -c create layers, if they don't already exist in the target"<<std::endl + <<" -n copy layers by number, not name"<<std::endl; +} + +int main(int argc, char * argv[] ) +{ + ConfigurationOptions config = {0}; + int opt; + while ((opt = getopt(argc, argv, "cn")) != -1) + { + switch (opt) + { + case 'c': + config.createMissingLayers = true; + break; + case 'n': + config.copyLayersByOrdinal = true; + break; + case '?': + std::cerr<<"Unrecognized option"<<std::endl; + printUsage(); + return -1; + } + } + + if ((argc-optind) < 8) + { + std::cerr<<"Too few args"<<std::endl; + printUsage(); + return -1; + } + if ((argc-optind) > 9) + { + std::cerr<<"Too many args"<<std::endl; + printUsage(); + return -1; + } + + std::string srcFile = argv[optind+0]; + int srcX= atoi(argv[optind+1]); + int srcY= atoi(argv[optind+2]); + int width= atoi(argv[optind+3]); + int height=atoi(argv[optind+4]); + std::string tgtFile = argv[optind+5]; + int destX=atoi(argv[optind+6]); + int destY=atoi(argv[optind+7]); + std::string outFile = tgtFile; + if (argc == optind+9) + { + outFile = argv[optind+8]; + } + + // plausibility check of command line options + if (height < 1 || width < 1 || srcX < 0 || srcY < 0 || destX < 0 || destY < 0) + { + std::cerr<<"Illegal coordinates!"<<std::endl; + printUsage(); + return -1; + } + + try + { + Map* srcMap = new Map(srcFile); + Map* tgtMap = new Map(tgtFile); + if (tgtMap->overwrite(srcMap, srcX, srcY, width, height, destX, destY, config)) + { + tgtMap->save(outFile); + } else { + return -1; + } + delete srcMap; + delete tgtMap; + } + catch (int) + { + return -1; + } +} diff --git a/tools/tmxcopy/tostring.h b/tools/tmxcopy/tostring.h new file mode 100644 index 000000000..63c0a1a8c --- /dev/null +++ b/tools/tmxcopy/tostring.h @@ -0,0 +1,35 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef UTILS_TOSTRING_H +#define UTILS_TOSTRING_H + +#include <sstream> + +template<typename T> +std::string toString(const T &arg) +{ + std::stringstream ss; + ss << arg; + return ss.str(); +} + +#endif diff --git a/tools/tmxcopy/xmlutils.cpp b/tools/tmxcopy/xmlutils.cpp new file mode 100644 index 000000000..a4984a6a2 --- /dev/null +++ b/tools/tmxcopy/xmlutils.cpp @@ -0,0 +1,75 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "xmlutils.h" + +namespace XML +{ + int + getProperty(xmlNodePtr node, const char* name, int def) + { + int &ret = def; + + xmlChar *prop = xmlGetProp(node, BAD_CAST name); + if (prop) { + ret = atoi((char*)prop); + xmlFree(prop); + } + + return ret; + } + + double + getFloatProperty(xmlNodePtr node, const char* name, double def) + { + double &ret = def; + + xmlChar *prop = xmlGetProp(node, BAD_CAST name); + if (prop) { + ret = atof((char*)prop); + xmlFree(prop); + } + + return ret; + } + + std::string + getProperty(xmlNodePtr node, const char *name, const std::string &def) + { + xmlChar *prop = xmlGetProp(node, BAD_CAST name); + if (prop) { + std::string val = (char*)prop; + xmlFree(prop); + return val; + } + + return def; + } + + xmlNodePtr findFirstChildByName(xmlNodePtr parent, const char *name) + { + for_each_xml_child_node(child, parent) + if (xmlStrEqual(child->name, BAD_CAST name)) + return child; + + return NULL; + } +} diff --git a/tools/tmxcopy/xmlutils.h b/tools/tmxcopy/xmlutils.h new file mode 100644 index 000000000..3d9c5d7a7 --- /dev/null +++ b/tools/tmxcopy/xmlutils.h @@ -0,0 +1,61 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _XMLUTILS_H +#define _XMLUTILS_H + +#include <libxml/tree.h> + +#include <string> + +/** + * XML helper functions. + */ +namespace XML +{ + /** + * Gets an integer property from an xmlNodePtr. + */ + int + getProperty(xmlNodePtr node, const char *name, int def); + + /** + * Gets an floating point property from an xmlNodePtr. + */ + double + getFloatProperty(xmlNodePtr node, const char *name, double def); + + /** + * Gets a string property from an xmlNodePtr. + */ + std::string + getProperty(xmlNodePtr node, const char *name, const std::string &def); + + /** + * Finds the first child node with the given name + */ + xmlNodePtr findFirstChildByName(xmlNodePtr parent, const char *name); +} + +#define for_each_xml_child_node(var, parent) \ + for (xmlNodePtr var = parent->xmlChildrenNode; var; var = var->next) + +#endif diff --git a/tools/tmxcopy/zlibutils.cpp b/tools/tmxcopy/zlibutils.cpp new file mode 100644 index 000000000..39c19aeef --- /dev/null +++ b/tools/tmxcopy/zlibutils.cpp @@ -0,0 +1,122 @@ + +#include <stdlib.h> +#include <iostream> +#include <cassert> +#include <zlib.h> + +/** + * Inflates either zlib or gzip deflated memory. The inflated memory is + * expected to be freed by the caller. + */ +int +inflateMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out, unsigned int &outLength) +{ + int bufferSize = 256 * 1024; + int ret; + z_stream strm; + + out = (unsigned char*) malloc(bufferSize); + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_in = in; + strm.avail_in = inLength; + strm.next_out = out; + strm.avail_out = bufferSize; + + ret = inflateInit2(&strm, 15 + 32); + + if (ret != Z_OK) + return ret; + + do + { + if (strm.next_out == NULL) + { + inflateEnd(&strm); + return Z_MEM_ERROR; + } + + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); + + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void) inflateEnd(&strm); + return ret; + } + + if (ret != Z_STREAM_END) + { + out = (unsigned char*) realloc(out, bufferSize * 2); + + if (out == NULL) + { + inflateEnd(&strm); + return Z_MEM_ERROR; + } + + strm.next_out = out + bufferSize; + strm.avail_out = bufferSize; + bufferSize *= 2; + } + } + while (ret != Z_STREAM_END); + assert(strm.avail_in == 0); + + outLength = bufferSize - strm.avail_out; + (void) inflateEnd(&strm); + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; +} + +int +inflateMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out) +{ + unsigned int outLength = 0; + int ret = inflateMemory(in, inLength, out, outLength); + + if (ret != Z_OK || out == NULL) + { + if (ret == Z_MEM_ERROR) + { + std::cerr<<"Error: Out of memory while decompressing map data!"; + } + else if (ret == Z_VERSION_ERROR) + { + std::cerr<<"Error: Incompatible zlib version!"; + } + else if (ret == Z_DATA_ERROR) + { + std::cerr<<"Error: Incorrect zlib compressed data!"; + } + else + { + std::cerr<<"Error: Unknown error while decompressing map data!"; + } + + free(out); + out = NULL; + outLength = 0; + } + + return outLength; +} + +/* +int +compressMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out, unsigned int &outLength) +{ + uLongf fOutLen = outLength; + int ret = compress((Bytef*)out, &fOutLen, (Bytef*)in, inLength); + outLength = fOutLen; + + assert (ret == Z_OK); +} +*/ diff --git a/tools/tmxcopy/zlibutils.h b/tools/tmxcopy/zlibutils.h new file mode 100644 index 000000000..300c72ad9 --- /dev/null +++ b/tools/tmxcopy/zlibutils.h @@ -0,0 +1,11 @@ +int +inflateMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out, unsigned int &outLength); + +int +inflateMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out); + +int +compressMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out, unsigned int &outLength); diff --git a/tools/upalyzer/analyze.php b/tools/upalyzer/analyze.php new file mode 100644 index 000000000..bdb56d41d --- /dev/null +++ b/tools/upalyzer/analyze.php @@ -0,0 +1,191 @@ +<?php +/* analyze.php + * Copyright 2007 Bjørn Lindeijer + * + * This file is part of upalyzer. + * + * upalyzer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * upalyzer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with upalyzer. If not, see <http://www.gnu.org/licenses/>. + */ +header("Content-type: text/html"); +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +?> +<html> +<head><title>Update analysis</title></head> +<body> +<pre> +List of current updates: + +<?php +$download_url_base = 'http://updates.themanaworld.org/tmwdata/'; +$checkout_path = '/home/eathena/public_html/updates/tmwdata/'; +$update_file = array_filter(array_reverse(file('resources2.txt'))); +$updates = array(); +$update_file_maxlen = 0; +$data_size = 0; +$data_uncompressed_size = 0; +$data_used_size = 0; +$data_overhead_size = 0; +$update_entries = array(); +$update_entry_maxlen = 0; + +foreach ($update_file as $update_line) +{ + list($file, $hash) = explode(' ', trim($update_line), 2); + $update = array( + 'file' => $file, + 'adler32' => $hash, + 'filesize' => filesize($file), + 'size' => 0, + 'used_entry_count' => 0, + 'used_size' => 0, + 'uncompressed_size' => 0); + $update_file_maxlen = max($update_file_maxlen, strlen($file)); + + $entries = array(); + $zip = zip_open(realpath($file)); + + if ($zip && !is_int($zip)) { + while ($zip_entry = zip_read($zip)) { + $update['uncompressed_size'] += zip_entry_filesize($zip_entry); + $entry_name = zip_entry_name($zip_entry); + $entry_size = zip_entry_compressedsize($zip_entry); + $entry_exists = file_exists($checkout_path . $entry_name); + $entry_used = !array_key_exists($entry_name, $update_entries); + $entries[$entry_name] = array( + 'name' => $entry_name, + 'size' => $entry_size, + 'used' => $entry_used, + 'obsolete' => !$entry_exists); + $update['size'] += $entry_size; + + if ($entry_used) { + if ($entry_exists) { + $update['used_entry_count']++; + $update['used_size'] += $entry_size; + } + $update_entries[$entry_name] = $update; + } + + $update_entry_maxlen = max($update_entry_maxlen, strlen($entry_name)); + } + zip_close($zip); + } + else { + $update['zip_error'] = $zip or true; + } + + ksort($entries); + $update['entries'] = $entries; + $update['used_percentage'] = $update['used_size'] / $update['size']; + $updates[] = $update; + + $data_used_size += $update['used_size']; + $data_size += $update['size']; + $data_uncompressed_size += $update['uncompressed_size']; + $data_overhead_size += $update['filesize'] - $update['size']; +} + +function print_update_name($update, $pad = true) +{ + global $update_file_maxlen; + printf("<a href=\"#%s\">%s</a>", $update['file'], $update['file']); + if ($pad) + echo str_repeat(' ', $update_file_maxlen - strlen($update['file'])); +} + +// Print overall statistics + +foreach (array_reverse($updates) as $update) +{ + print_update_name($update); + printf(" %8s", $update['adler32']); + printf(" %4d kb", $update['filesize'] / 1024); + if (!$update['zip_error']) { + printf(" %4d kb", $update['uncompressed_size'] / 1024); + printf(" %3d%% used (%d/%d files)", $update['used_percentage'] * 100, + $update['used_entry_count'], + count($update['entries'])); + } else { + printf(" Error! "); + if (is_int($update['zip_error'])) + echo $update['zip_error']; + } + echo "\n"; +} + +printf("\n"); +printf("Amount of data: %4d kb (+%d kb zip file overhead)\n", + $data_size / 1024, + $data_overhead_size / 1024); +printf("Uncompressed: %4d kb\n", $data_uncompressed_size / 1024); +printf("Obsoleted data: %4d kb (%d%%)\n", + ($data_size - $data_used_size) / 1024, + 100 - ($data_used_size / $data_size) * 100); + +printf("\n"); + + +// Print list of update entries and the update they are loaded from + +ksort($update_entries); + +foreach ($update_entries as $entry => $update) +{ + $exists = file_exists($checkout_path . $entry); + printf("<span style=\"color: %s;\">%-{$update_entry_maxlen}s</span> ", + $exists ? "black" : "rgb(100,100,100)", + $entry); + print_update_name($update, true); + + if ($exists && substr($entry, strlen($entry) - 1) != '/') + printf(' <a href="%s%s">download</a>', $download_url_base, $entry); + echo "\n"; +} + + +// For each update, print its list of files and indicate whether they are used + +foreach (array_reverse($updates) as $update) +{ + print "\n<a name=\"".$update['file']."\"/><b>".$update['file']."</b>\n"; + + foreach ($update['entries'] as $entry_name => $entry) { + printf("%s%-{$update_entry_maxlen}s%s", + $entry['obsolete'] ? "<span style=\"color: rgb(100,100,100);\">" : "", + $entry_name, + $entry['obsolete'] ? "</span>" : ""); + if ($entry['used'] && !$entry['obsolete']) { + echo ' *'; + } elseif (!$entry['obsolete']) { + echo ' '; + print_update_name($update_entries[$entry_name], false); + } + echo "\n"; + } +} + +/* +if ($dh = opendir('.')) { + while (($file = readdir($dh)) !== false) { + } + closedir($dh); +} +*/ +?> +</pre> +</body> +</html> diff --git a/tools/update-copyright.sh b/tools/update-copyright.sh new file mode 100755 index 000000000..8f924f53c --- /dev/null +++ b/tools/update-copyright.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copyright (C) 2001-2010 Wormux Team. +# Copyright (C) 2010 The ManaWorld Development Team. + +new_year="$1" +[[ -z $new_year ]] && echo "Missing parameter: year" && exit 1 + +[[ ! -e src ]] && echo "This script should be ran from the top mana/manaserv dir" && exit 2 + +tmp_file="w$RANDOM$RANDOM$RANDOM$RANDOM" +[[ -e $tmp_file ]] && tmp_file="w$RANDOM$RANDOM$RANDOM$RANDOM" + +# update the dates, creating the interval if it doesn't exist yet +find -iname "*.cpp" -or -iname "*.h" -or -iname "*.hpp" | + xargs sed -i "/Copyright.*The Mana World Development Team/ s,\(20[0-9]*\) \|\(20[0-9]*\)-20[0-9]* ,\1\2-$new_year ," + +# do a semi-automated commit check +git diff > $tmp_file +echo "The next +/- counts mentioning copyrights should match:" +grep "^[-+][^-+]" $tmp_file | sort | uniq -c +echo "If they don't, try finding the offending files with grep -rl <\$bad_line>" + +# Remove temp file +[[ -e $tmp_file ]] && rm $tmp_file |