From 99e8b296c3de71e9f93957ca1b4b977265443ff9 Mon Sep 17 00:00:00 2001 From: Casey Doran Date: Fri, 15 Oct 2021 00:22:17 -0700 Subject: [PATCH] Cleaned up old work to add tiled map editor support, and got working on Linux. --- engine/compilers/Make-32bit/Makefile | 1 + engine/compilers/Make-32bit/TmxParser.mk | 64 + engine/compilers/Make-32bit/Torque2D.mk | 3 + engine/compilers/Make-64bit/Makefile | 1 + engine/compilers/Make-64bit/TmxParser.mk | 64 + engine/compilers/Make-64bit/Torque2D.mk | 3 + engine/lib/TmxParser/Tmx.h | 42 + engine/lib/TmxParser/TmxEllipse.cpp | 41 + engine/lib/TmxParser/TmxEllipse.h | 64 + engine/lib/TmxParser/TmxImage.cpp | 60 + engine/lib/TmxParser/TmxImage.h | 67 + engine/lib/TmxParser/TmxImageLayer.cpp | 96 + engine/lib/TmxParser/TmxImageLayer.h | 97 + engine/lib/TmxParser/TmxLayer.cpp | 278 +++ engine/lib/TmxParser/TmxLayer.h | 151 ++ engine/lib/TmxParser/TmxMap.cpp | 329 +++ engine/lib/TmxParser/TmxMap.h | 196 ++ engine/lib/TmxParser/TmxMapTile.h | 80 + engine/lib/TmxParser/TmxObject.cpp | 124 ++ engine/lib/TmxParser/TmxObject.h | 103 + engine/lib/TmxParser/TmxObjectGroup.cpp | 86 + engine/lib/TmxParser/TmxObjectGroup.h | 98 + engine/lib/TmxParser/TmxPoint.h | 40 + engine/lib/TmxParser/TmxPolygon.cpp | 56 + engine/lib/TmxParser/TmxPolygon.h | 58 + engine/lib/TmxParser/TmxPolyline.cpp | 56 + engine/lib/TmxParser/TmxPolyline.h | 58 + engine/lib/TmxParser/TmxPropertySet.cpp | 94 + engine/lib/TmxParser/TmxPropertySet.h | 73 + engine/lib/TmxParser/TmxTile.cpp | 63 + engine/lib/TmxParser/TmxTile.h | 66 + engine/lib/TmxParser/TmxTileset.cpp | 131 ++ engine/lib/TmxParser/TmxTileset.h | 103 + engine/lib/TmxParser/TmxUtil.cpp | 107 + engine/lib/TmxParser/TmxUtil.h | 43 + engine/lib/TmxParser/base64/base64.cpp | 123 ++ engine/lib/TmxParser/base64/base64.h | 35 + engine/lib/TmxParser/tinyxml/tinystr.h | 319 +++ engine/lib/TmxParser/tinyxml/tinyxml.h | 1802 +++++++++++++++++ engine/source/2d/assets/TmxMapAsset.cc | 243 +++ engine/source/2d/assets/TmxMapAsset.h | 77 + .../2d/assets/TmxMapAsset_ScriptBinding.h | 26 + engine/source/2d/sceneobject/SceneObject.h | 2 +- engine/source/2d/sceneobject/TmxMapSprite.cpp | 715 +++++++ engine/source/2d/sceneobject/TmxMapSprite.h | 93 + .../sceneobject/TmxMapSprite_ScriptBinding.h | 41 + .../1/assets/images/Woodland_x3.asset.taml | 8 + .../ToyAssets/1/assets/images/Woodland_x3.png | Bin 0 -> 90848 bytes .../ToyAssets/1/assets/images/town.asset.taml | 10 + toybox/ToyAssets/1/assets/images/town.png | Bin 0 -> 58659 bytes .../1/assets/images/town1.asset.taml | 8 + toybox/ToyAssets/1/assets/images/town1.png | Bin 0 -> 11083 bytes .../1/assets/maps/testtown.asset.taml | 31 + toybox/ToyAssets/1/assets/maps/testtown.tmx | 128 ++ toybox/tmxMapToy/1/main.cs | 52 + toybox/tmxMapToy/1/module.taml | 10 + 56 files changed, 6718 insertions(+), 1 deletion(-) create mode 100644 engine/compilers/Make-32bit/TmxParser.mk create mode 100644 engine/compilers/Make-64bit/TmxParser.mk create mode 100644 engine/lib/TmxParser/Tmx.h create mode 100644 engine/lib/TmxParser/TmxEllipse.cpp create mode 100644 engine/lib/TmxParser/TmxEllipse.h create mode 100644 engine/lib/TmxParser/TmxImage.cpp create mode 100644 engine/lib/TmxParser/TmxImage.h create mode 100644 engine/lib/TmxParser/TmxImageLayer.cpp create mode 100644 engine/lib/TmxParser/TmxImageLayer.h create mode 100644 engine/lib/TmxParser/TmxLayer.cpp create mode 100644 engine/lib/TmxParser/TmxLayer.h create mode 100644 engine/lib/TmxParser/TmxMap.cpp create mode 100644 engine/lib/TmxParser/TmxMap.h create mode 100644 engine/lib/TmxParser/TmxMapTile.h create mode 100644 engine/lib/TmxParser/TmxObject.cpp create mode 100644 engine/lib/TmxParser/TmxObject.h create mode 100644 engine/lib/TmxParser/TmxObjectGroup.cpp create mode 100644 engine/lib/TmxParser/TmxObjectGroup.h create mode 100644 engine/lib/TmxParser/TmxPoint.h create mode 100644 engine/lib/TmxParser/TmxPolygon.cpp create mode 100644 engine/lib/TmxParser/TmxPolygon.h create mode 100644 engine/lib/TmxParser/TmxPolyline.cpp create mode 100644 engine/lib/TmxParser/TmxPolyline.h create mode 100644 engine/lib/TmxParser/TmxPropertySet.cpp create mode 100644 engine/lib/TmxParser/TmxPropertySet.h create mode 100644 engine/lib/TmxParser/TmxTile.cpp create mode 100644 engine/lib/TmxParser/TmxTile.h create mode 100644 engine/lib/TmxParser/TmxTileset.cpp create mode 100644 engine/lib/TmxParser/TmxTileset.h create mode 100644 engine/lib/TmxParser/TmxUtil.cpp create mode 100644 engine/lib/TmxParser/TmxUtil.h create mode 100644 engine/lib/TmxParser/base64/base64.cpp create mode 100644 engine/lib/TmxParser/base64/base64.h create mode 100644 engine/lib/TmxParser/tinyxml/tinystr.h create mode 100644 engine/lib/TmxParser/tinyxml/tinyxml.h create mode 100644 engine/source/2d/assets/TmxMapAsset.cc create mode 100644 engine/source/2d/assets/TmxMapAsset.h create mode 100644 engine/source/2d/assets/TmxMapAsset_ScriptBinding.h create mode 100644 engine/source/2d/sceneobject/TmxMapSprite.cpp create mode 100644 engine/source/2d/sceneobject/TmxMapSprite.h create mode 100644 engine/source/2d/sceneobject/TmxMapSprite_ScriptBinding.h create mode 100644 toybox/ToyAssets/1/assets/images/Woodland_x3.asset.taml create mode 100644 toybox/ToyAssets/1/assets/images/Woodland_x3.png create mode 100644 toybox/ToyAssets/1/assets/images/town.asset.taml create mode 100644 toybox/ToyAssets/1/assets/images/town.png create mode 100644 toybox/ToyAssets/1/assets/images/town1.asset.taml create mode 100644 toybox/ToyAssets/1/assets/images/town1.png create mode 100644 toybox/ToyAssets/1/assets/maps/testtown.asset.taml create mode 100644 toybox/ToyAssets/1/assets/maps/testtown.tmx create mode 100644 toybox/tmxMapToy/1/main.cs create mode 100644 toybox/tmxMapToy/1/module.taml diff --git a/engine/compilers/Make-32bit/Makefile b/engine/compilers/Make-32bit/Makefile index 4154e3ba3..410364651 100644 --- a/engine/compilers/Make-32bit/Makefile +++ b/engine/compilers/Make-32bit/Makefile @@ -28,6 +28,7 @@ clean: .PHONY: all debug release clean -include x Torque2D.mk +-include x TmxParser.mk -include x zlib -include x lpng -include x ljpeg diff --git a/engine/compilers/Make-32bit/TmxParser.mk b/engine/compilers/Make-32bit/TmxParser.mk new file mode 100644 index 000000000..563fbcfc4 --- /dev/null +++ b/engine/compilers/Make-32bit/TmxParser.mk @@ -0,0 +1,64 @@ +# I release this sample under the MIT license: free for any use, provided +# you hold me harmless from any such use you make, and you retain my +# copyright on the actual sources. +# Copyright 2005 Jon Watte. + +LIBNAME := TmxParser +SOURCES := $(shell find ../../lib/TmxParser -name "*.cpp") + +LDFLAGS_TmxParser := -g -m32 + +CFLAGS_TmxParser := -MMD -I. -m32 -msse -mmmx -march=i686 + +CFLAGS_TmxParser += -I../../lib/TmxParser +CFLAGS_TmxParser += -I../../lib/TmxParser/base64 +CFLAGS_TmxParser += -I../../lib/TmxParser/tinyxml + +CFLAGS_TmxParser += -DUNICODE +CFLAGS_TmxParser += -DLINUX + +CFLAGS_DEBUG_TmxParser := $(CFLAGS_TmxParser) -ggdb +CFLAGS_DEBUG_TmxParser += -DTORQUE_DEBUG +CFLAGS_DEBUG_TmxParser += -DTORQUE_DEBUG_GUARD +CFLAGS_DEBUG_TmxParser += -DTORQUE_NET_STATS + +CFLAGS_TmxParser += -O3 + +CC := gcc +LD := gcc + +TARGET_TmxParser := lib/libTmxParser.a +TARGET_TmxParser_DEBUG := lib/libTmxParser_DEBUG.a + +LIB_TARGETS += $(TARGET_TmxParser) +LIB_TARGETS_DEBUG += $(TARGET_TmxParser_DEBUG) + +OBJS_TmxParser := $(patsubst ../../lib/TmxParser/%,Release/TmxParser/%.o,$(SOURCES)) +OBJS_TmxParser_DEBUG := $(patsubst ../../lib/TmxParser/%,Debug/TmxParser/%.o,$(SOURCES)) + +# Deriving the variable name from the target name is the secret sauce +# of the build system. +# +$(TARGET_TmxParser): $(OBJS_TmxParser) + @mkdir -p $(dir $@) + ar cr $@ $(OBJS_TmxParser) + +$(TARGET_TmxParser_DEBUG): $(OBJS_TmxParser_DEBUG) + @mkdir -p $(dir $@) + ar cr $@ $(OBJS_TmxParser_DEBUG) + +Release/TmxParser/%.o: ../../lib/TmxParser/% + @mkdir -p $(dir $@) + $(CC) -c $(CFLAGS_TmxParser) $< -o $@ + +Debug/TmxParser/%.o: ../../lib/TmxParser/% + @mkdir -p $(dir $@) + $(CC) -c $(CFLAGS_DEBUG_TmxParser) $< -o $@ + +release_TmxParser: $(TARGET_TmxParser) +debug_TmxParser: $(TARGET_TmxParser_DEBUG) + +.PHONY: debug_TmxParser release_TmxParser + +DEPS += $(patsubst %.o,%.d,$(OBJS_TmxParser)) +DEPS += $(patsubst %.o,%.d,$(OBJS_TmxParser_DEBUG)) diff --git a/engine/compilers/Make-32bit/Torque2D.mk b/engine/compilers/Make-32bit/Torque2D.mk index 9a2a40ce6..9ac5e8a86 100644 --- a/engine/compilers/Make-32bit/Torque2D.mk +++ b/engine/compilers/Make-32bit/Torque2D.mk @@ -78,10 +78,13 @@ CFLAGS += -I../../lib/freetype CFLAGS += -I../../lib/libvorbis/include CFLAGS += -I../../lib/libogg/include CFLAGS += -I../../lib/openal/LINUX/ +CFLAGS += -I../../lib/TmxParser/ CFLAGS += -DLINUX CFLAGS += -Di386 +CFLAGS += -Wno-invalid-offsetof + CFLAGS_DEBUG := $(CFLAGS) -ggdb CFLAGS_DEBUG += -DTORQUE_DEBUG diff --git a/engine/compilers/Make-64bit/Makefile b/engine/compilers/Make-64bit/Makefile index 7d5240e40..58ce4ce04 100644 --- a/engine/compilers/Make-64bit/Makefile +++ b/engine/compilers/Make-64bit/Makefile @@ -28,6 +28,7 @@ clean: .PHONY: all debug release clean -include x Torque2D.mk +-include x TmxParser.mk -include x zlib -include x lpng -include x ljpeg diff --git a/engine/compilers/Make-64bit/TmxParser.mk b/engine/compilers/Make-64bit/TmxParser.mk new file mode 100644 index 000000000..1eb064ef1 --- /dev/null +++ b/engine/compilers/Make-64bit/TmxParser.mk @@ -0,0 +1,64 @@ +# I release this sample under the MIT license: free for any use, provided +# you hold me harmless from any such use you make, and you retain my +# copyright on the actual sources. +# Copyright 2005 Jon Watte. + +LIBNAME := TmxParser +SOURCES := $(shell find ../../lib/TmxParser -name "*.cpp") + +LDFLAGS_TmxParser := -g -m64 + +CFLAGS_TmxParser := -MMD -I. -m64 -msse -mmmx -march=x86-64 + +CFLAGS_TmxParser += -I../../lib/TmxParser +CFLAGS_TmxParser += -I../../lib/TmxParser/base64 +CFLAGS_TmxParser += -I../../lib/TmxParser/tinyxml + +CFLAGS_TmxParser += -DUNICODE +CFLAGS_TmxParser += -DLINUX + +CFLAGS_DEBUG_TmxParser := $(CFLAGS_TmxParser) -ggdb +CFLAGS_DEBUG_TmxParser += -DTORQUE_DEBUG +CFLAGS_DEBUG_TmxParser += -DTORQUE_DEBUG_GUARD +CFLAGS_DEBUG_TmxParser += -DTORQUE_NET_STATS + +CFLAGS_TmxParser += -O3 + +CC := gcc +LD := gcc + +TARGET_TmxParser := lib/libTmxParser.a +TARGET_TmxParser_DEBUG := lib/libTmxParser_DEBUG.a + +LIB_TARGETS += $(TARGET_TmxParser) +LIB_TARGETS_DEBUG += $(TARGET_TmxParser_DEBUG) + +OBJS_TmxParser := $(patsubst ../../lib/TmxParser/%,Release/TmxParser/%.o,$(SOURCES)) +OBJS_TmxParser_DEBUG := $(patsubst ../../lib/TmxParser/%,Debug/TmxParser/%.o,$(SOURCES)) + +# Deriving the variable name from the target name is the secret sauce +# of the build system. +# +$(TARGET_TmxParser): $(OBJS_TmxParser) + @mkdir -p $(dir $@) + ar cr $@ $(OBJS_TmxParser) + +$(TARGET_TmxParser_DEBUG): $(OBJS_TmxParser_DEBUG) + @mkdir -p $(dir $@) + ar cr $@ $(OBJS_TmxParser_DEBUG) + +Release/TmxParser/%.o: ../../lib/TmxParser/% + @mkdir -p $(dir $@) + $(CC) -c $(CFLAGS_TmxParser) $< -o $@ + +Debug/TmxParser/%.o: ../../lib/TmxParser/% + @mkdir -p $(dir $@) + $(CC) -c $(CFLAGS_DEBUG_TmxParser) $< -o $@ + +release_TmxParser: $(TARGET_TmxParser) +debug_TmxParser: $(TARGET_TmxParser_DEBUG) + +.PHONY: debug_TmxParser release_TmxParser + +DEPS += $(patsubst %.o,%.d,$(OBJS_TmxParser)) +DEPS += $(patsubst %.o,%.d,$(OBJS_TmxParser_DEBUG)) diff --git a/engine/compilers/Make-64bit/Torque2D.mk b/engine/compilers/Make-64bit/Torque2D.mk index 5fd6bbc50..ca2b4754a 100644 --- a/engine/compilers/Make-64bit/Torque2D.mk +++ b/engine/compilers/Make-64bit/Torque2D.mk @@ -76,11 +76,14 @@ CFLAGS += -I../../lib/freetype CFLAGS += -I../../lib/libvorbis/include CFLAGS += -I../../lib/libogg/include CFLAGS += -I../../lib/openal/LINUX/ +CFLAGS += -I../../lib/TmxParser/ CFLAGS += -DLINUX CFLAGS += -D__amd64__ CFLAGS += -DTORQUE_64 +CFLAGS += -Wno-invalid-offsetof + CFLAGS_DEBUG := $(CFLAGS) -ggdb CFLAGS_DEBUG += -DTORQUE_DEBUG diff --git a/engine/lib/TmxParser/Tmx.h b/engine/lib/TmxParser/Tmx.h new file mode 100644 index 000000000..d70350110 --- /dev/null +++ b/engine/lib/TmxParser/Tmx.h @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// TmxImage.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include "TmxMap.h" +#include "TmxTileset.h" +#include "TmxTile.h" +#include "TmxImage.h" +#include "TmxLayer.h" +#include "TmxObject.h" +#include "TmxObjectGroup.h" +#include "TmxEllipse.h" +#include "TmxPolygon.h" +#include "TmxPolyline.h" +#include "TmxPropertySet.h" +#include "TmxUtil.h" +#include "TmxImageLayer.h" \ No newline at end of file diff --git a/engine/lib/TmxParser/TmxEllipse.cpp b/engine/lib/TmxParser/TmxEllipse.cpp new file mode 100644 index 000000000..4ce7f9d72 --- /dev/null +++ b/engine/lib/TmxParser/TmxEllipse.cpp @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// TmxPolygon.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxEllipse.h" + +namespace Tmx +{ + Ellipse::Ellipse( int _x, int _y, int width, int height ) + : x(_x+(width/2)) + , y(_y+(height/2)) + , radiusX(width/2) + , radiusY(height/2) + { + } +} diff --git a/engine/lib/TmxParser/TmxEllipse.h b/engine/lib/TmxParser/TmxEllipse.h new file mode 100644 index 000000000..242b3beac --- /dev/null +++ b/engine/lib/TmxParser/TmxEllipse.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// TmxPolygon.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include + +#include "TmxPoint.h" + +class TiXmlNode; + +namespace Tmx +{ + //------------------------------------------------------------------------- + // Class to store a Polygon of an Object. + //------------------------------------------------------------------------- + class Ellipse + { + public: + Ellipse( int x, int y, int width, int height ); + + // Get the center of the object, in pixels. + int GetCenterX() const { return x; } + + // Get the center of the object, in pixels. + int GetCenterY() const { return y; } + + // Get the RadiusX of the object, in pixels. + int GetRadiusX() const { return radiusX; } + + // Get the RadiusY of the object, in pixels. + int GetRadiusY() const { return radiusY; } + + private: + int x; + int y; + int radiusX; + int radiusY; + }; +}; \ No newline at end of file diff --git a/engine/lib/TmxParser/TmxImage.cpp b/engine/lib/TmxParser/TmxImage.cpp new file mode 100644 index 000000000..52659bd22 --- /dev/null +++ b/engine/lib/TmxParser/TmxImage.cpp @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// TmxImage.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxImage.h" + +namespace Tmx +{ + Image::Image() + : source() + , width() + , height() + , transparent_color() + {} + + Image::~Image() + {} + + void Image::Parse(const TiXmlNode *imageNode) + { + const TiXmlElement* imageElem = imageNode->ToElement(); + + // Read all the attribute into member variables. + source = imageElem->Attribute("source"); + + imageElem->Attribute("width", &width); + imageElem->Attribute("height", &height); + + const char *trans = imageElem->Attribute("trans"); + if (trans) + { + transparent_color = trans; + } + } +}; diff --git a/engine/lib/TmxParser/TmxImage.h b/engine/lib/TmxParser/TmxImage.h new file mode 100644 index 000000000..e5a8bdaed --- /dev/null +++ b/engine/lib/TmxParser/TmxImage.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// TmxImage.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include + +class TiXmlNode; + +namespace Tmx +{ + //------------------------------------------------------------------------- + // An image within a tileset. + //------------------------------------------------------------------------- + class Image + { + public: + Image(); + ~Image(); + + // Parses an image element. + void Parse(const TiXmlNode *imageNode); + + // Get the path to the file of the image (relative to the map) + const std::string &GetSource() const { return source; } + + // Get the width of the image. + int GetWidth() const { return width; } + + // Get the height of the image. + int GetHeight() const { return height; } + + // Get the transparent color used in the image. + const std::string &GetTransparentColor() const + { return transparent_color; } + + private: + std::string source; + int width; + int height; + std::string transparent_color; + }; +}; diff --git a/engine/lib/TmxParser/TmxImageLayer.cpp b/engine/lib/TmxParser/TmxImageLayer.cpp new file mode 100644 index 000000000..3b145015f --- /dev/null +++ b/engine/lib/TmxParser/TmxImageLayer.cpp @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// TmxTileset.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxImageLayer.h" +#include "TmxImage.h" + +using std::vector; +using std::string; + +namespace Tmx +{ + ImageLayer::ImageLayer(const Tmx::Map *_map) + : map(_map) + , name() + , width(0) + , height(0) + , image(NULL) + , opacity(0) + , visible(0) + , zOrder(0) + + { + } + + ImageLayer::~ImageLayer() + { + delete image; + } + + void ImageLayer::Parse(const TiXmlNode *imageLayerNode) + { + const TiXmlElement *imagenLayerElem = imageLayerNode->ToElement(); + + // Read all the attributes into local variables. + name = imagenLayerElem->Attribute("name"); + + imagenLayerElem->Attribute("width", &width); + imagenLayerElem->Attribute("height", &height); + + const char *opacityStr = imagenLayerElem->Attribute("opacity"); + if (opacityStr) + { + opacity = (float)atof(opacityStr); + } + + const char *visibleStr = imagenLayerElem->Attribute("visible"); + if (visibleStr) + { + visible = atoi(visibleStr) != 0; // to prevent visual c++ from complaining.. + } + + // Parse the image. + const TiXmlNode *imageNode = imagenLayerElem->FirstChild("image"); + + if (imageNode) + { + image = new Image(); + image->Parse(imageNode); + } + + // Parse the properties if any. + const TiXmlNode *propertiesNode = imagenLayerElem->FirstChild("properties"); + + if (propertiesNode) + { + properties.Parse(propertiesNode); + } + } + +}; diff --git a/engine/lib/TmxParser/TmxImageLayer.h b/engine/lib/TmxParser/TmxImageLayer.h new file mode 100644 index 000000000..cd251c43b --- /dev/null +++ b/engine/lib/TmxParser/TmxImageLayer.h @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// TmxTileset.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include +#include + +#include "TmxPropertySet.h" + +class TiXmlNode; + +namespace Tmx +{ + class Map; + class Image; + + //------------------------------------------------------------------------- + // A class used for storing information about each of the tilesets. + // A tileset is a collection of tiles, of whom each may contain properties. + // The tileset class itself does not have properties. + //------------------------------------------------------------------------- + class ImageLayer + { + public: + ImageLayer(const Tmx::Map *_map); + ~ImageLayer(); + + // Parse a ImageLayer element. + void Parse(const TiXmlNode *imageLayerNode); + + // Returns the name of the ImageLayer. + const std::string &GetName() const { return name; } + + // Get the width of the ImageLayer. + int GetWidth() const { return width; } + + // Get the height of the ImageLayer. + int GetHeight() const { return height; } + + // Get the visibility of the ImageLayer. + bool IsVisible() const { return visible; } + + // Returns a variable containing information + // about the image of the ImageLayer. + const Tmx::Image* GetImage() const { return image; } + + // Get a set of properties regarding the ImageLayer. + const Tmx::PropertySet &GetProperties() const { return properties; } + + // Get the zorder of the ImageLayer. + int GetZOrder() const { return zOrder; } + + // Set the zorder of the ImageLayer. + void SetZOrder( int z ) { zOrder = z; } + + private: + const Tmx::Map *map; + + std::string name; + + int width; + int height; + + float opacity; + bool visible; + int zOrder; + + Tmx::Image* image; + + Tmx::PropertySet properties; + }; +}; diff --git a/engine/lib/TmxParser/TmxLayer.cpp b/engine/lib/TmxParser/TmxLayer.cpp new file mode 100644 index 000000000..6a44b9f66 --- /dev/null +++ b/engine/lib/TmxParser/TmxLayer.cpp @@ -0,0 +1,278 @@ +//----------------------------------------------------------------------------- +// TmxLayer.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" +#include "../../lib/zlib/zlib.h" +#include +#include + +#include "TmxLayer.h" +#include "TmxUtil.h" +#include "TmxMap.h" +#include "TmxTileset.h" + +namespace Tmx +{ + Layer::Layer(const Map *_map) + : map(_map) + , name() + , width(0) + , height(0) + , opacity(1.0f) + , visible(true) + , zOrder(0) + , properties() + , encoding(TMX_ENCODING_XML) + , compression(TMX_COMPRESSION_NONE) + { + // Set the map to null to specify that it is not yet allocated. + tile_map = NULL; + } + + Layer::~Layer() + { + // If the tile map is allocated, delete it from the memory. + if (tile_map) + { + delete [] tile_map; + tile_map = NULL; + } + } + + void Layer::Parse(const TiXmlNode *layerNode) + { + const TiXmlElement *layerElem = layerNode->ToElement(); + + // Read the attributes. + name = layerElem->Attribute("name"); + + layerElem->Attribute("width", &width); + layerElem->Attribute("height", &height); + + const char *opacityStr = layerElem->Attribute("opacity"); + if (opacityStr) + { + opacity = (float)atof(opacityStr); + } + + const char *visibleStr = layerElem->Attribute("visible"); + if (visibleStr) + { + visible = atoi(visibleStr) != 0; // to prevent visual c++ from complaining.. + } + + // Read the properties. + const TiXmlNode *propertiesNode = layerNode->FirstChild("properties"); + if (propertiesNode) + { + properties.Parse(propertiesNode); + } + + // Allocate memory for reading the tiles. + tile_map = new MapTile[width * height]; + + const TiXmlNode *dataNode = layerNode->FirstChild("data"); + const TiXmlElement *dataElem = dataNode->ToElement(); + + const char *encodingStr = dataElem->Attribute("encoding"); + const char *compressionStr = dataElem->Attribute("compression"); + + // Check for encoding. + if (encodingStr) + { + if (!strcmp(encodingStr, "base64")) + { + encoding = TMX_ENCODING_BASE64; + } + else if (!strcmp(encodingStr, "csv")) + { + encoding = TMX_ENCODING_CSV; + } + } + + // Check for compression. + if (compressionStr) + { + if (!strcmp(compressionStr, "gzip")) + { + compression = TMX_COMPRESSION_GZIP; + } + else if (!strcmp(compressionStr, "zlib")) + { + compression = TMX_COMPRESSION_ZLIB; + } + } + + // Decode. + switch (encoding) + { + case TMX_ENCODING_XML: + ParseXML(dataNode); + break; + + case TMX_ENCODING_BASE64: + ParseBase64(dataElem->GetText()); + break; + + case TMX_ENCODING_CSV: + ParseCSV(dataElem->GetText()); + break; + } + } + + void Layer::ParseXML(const TiXmlNode *dataNode) + { + const TiXmlNode *tileNode = dataNode->FirstChild("tile"); + int tileCount = 0; + + while (tileNode) + { + const TiXmlElement *tileElem = tileNode->ToElement(); + + unsigned gid = 0; + + // Read the Global-ID of the tile. + const char* gidText = tileElem->Attribute("gid"); + + // Convert to an unsigned. + sscanf(gidText, "%u", &gid); + + // Find the tileset index. + const int tilesetIndex = map->FindTilesetIndex(gid); + if (tilesetIndex != -1) + { + // If valid, set up the map tile with the tileset. + const Tmx::Tileset* tileset = map->GetTileset(tilesetIndex); + tile_map[tileCount] = MapTile(gid, tileset->GetFirstGid(), tilesetIndex); + } + else + { + // Otherwise, make it null. + tile_map[tileCount] = MapTile(gid, 0, -1); + } + + tileNode = dataNode->IterateChildren("tile", tileNode); + tileCount++; + } + } + + void Layer::ParseBase64(const std::string &innerText) + { + const std::string &text = Util::DecodeBase64(innerText); + + // Temporary array of gids to be converted to map tiles. + unsigned *out = 0; + + if (compression == TMX_COMPRESSION_ZLIB) + { + // Use zlib to uncompress the layer into the temporary array of tiles. + uLongf outlen = width * height * 4; + out = (unsigned *)malloc(outlen); + uncompress( + (Bytef*)out, &outlen, + (const Bytef*)text.c_str(), text.size()); + + } + else if (compression == TMX_COMPRESSION_GZIP) + { + // Use the utility class for decompressing (which uses zlib) + out = (unsigned *)Util::DecompressGZIP( + text.c_str(), + text.size(), + width * height * 4); + } + else + { + out = (unsigned *)malloc(text.size()); + + // Copy every gid into the temporary array since + // the decoded string is an array of 32-bit integers. + memcpy(out, text.c_str(), text.size()); + } + + // Convert the gids to map tiles. + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + unsigned gid = out[y * width + x]; + + // Find the tileset index. + const int tilesetIndex = map->FindTilesetIndex(gid); + if (tilesetIndex != -1) + { + // If valid, set up the map tile with the tileset. + const Tmx::Tileset* tileset = map->GetTileset(tilesetIndex); + tile_map[y * width + x] = MapTile(gid, tileset->GetFirstGid(), tilesetIndex); + } + else + { + // Otherwise, make it null. + tile_map[y * width + x] = MapTile(gid, 0, -1); + } + } + } + + // Free the temporary array from memory. + free(out); + } + + void Layer::ParseCSV(const std::string &innerText) + { + // Duplicate the string for use with C stdio. + char *csv = strdup(innerText.c_str()); + + // Iterate through every token of ';' in the CSV string. + char *pch = strtok(csv, ","); + int tileCount = 0; + + while (pch) + { + unsigned gid; + sscanf(pch, "%u", &gid); + + // Find the tileset index. + const int tilesetIndex = map->FindTilesetIndex(gid); + if (tilesetIndex != -1) + { + // If valid, set up the map tile with the tileset. + const Tmx::Tileset* tileset = map->GetTileset(tilesetIndex); + tile_map[tileCount] = MapTile(gid, tileset->GetFirstGid(), tilesetIndex); + } + else + { + // Otherwise, make it null. + tile_map[tileCount] = MapTile(gid, 0, -1); + } + + pch = strtok(NULL, ","); + tileCount++; + } + + free(csv); + } +}; diff --git a/engine/lib/TmxParser/TmxLayer.h b/engine/lib/TmxParser/TmxLayer.h new file mode 100644 index 000000000..762d1e05a --- /dev/null +++ b/engine/lib/TmxParser/TmxLayer.h @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------------- +// TmxLayer.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include + +#include "TmxPropertySet.h" +#include "TmxMapTile.h" + +class TiXmlNode; + +namespace Tmx +{ + class Map; + + //------------------------------------------------------------------------- + // Type used for the encoding of the layer data. + //------------------------------------------------------------------------- + enum LayerEncodingType + { + TMX_ENCODING_XML, + TMX_ENCODING_BASE64, + TMX_ENCODING_CSV + }; + + //------------------------------------------------------------------------- + // Type used for the compression of the layer data. + //------------------------------------------------------------------------- + enum LayerCompressionType + { + TMX_COMPRESSION_NONE, + TMX_COMPRESSION_ZLIB, + TMX_COMPRESSION_GZIP + }; + + //------------------------------------------------------------------------- + // Used for storing information about the tile ids for every layer. + // This class also have a property set. + //------------------------------------------------------------------------- + class Layer + { + private: + // Prevent copy constructor. + Layer(const Layer &_layer); + + public: + Layer(const Tmx::Map *_map); + ~Layer(); + + // Parse a layer node. + void Parse(const TiXmlNode *layerNode); + + // Get the name of the layer. + const std::string &GetName() const { return name; } + + // Get the width of the layer, in tiles. + int GetWidth() const { return width; } + + // Get the height of the layer, in tiles. + int GetHeight() const { return height; } + + // Get the visibility of the layer + bool IsVisible() const { return visible; } + + // Get the property set. + const Tmx::PropertySet &GetProperties() const { return properties; } + + // Pick a specific tile from the list. + unsigned GetTileId(int x, int y) const { return tile_map[y * width + x].id; } + + // Get the tileset index for a tileset from the list. + int GetTileTilesetIndex(int x, int y) const { return tile_map[y * width + x].tilesetId; } + + // Get whether a tile is flipped horizontally. + bool IsTileFlippedHorizontally(int x, int y) const + { return tile_map[y * width + x].flippedHorizontally; } + + // Get whether a tile is flipped vertically. + bool IsTileFlippedVertically(int x, int y) const + { return tile_map[y * width + x].flippedVertically; } + + // Get whether a tile is flipped diagonally. + bool IsTileFlippedDiagonally(int x, int y) const + { return tile_map[y * width + x].flippedDiagonally; } + + // Get a tile specific to the map. + const Tmx::MapTile& GetTile(int x, int y) const { return tile_map[y * width + x]; } + + // Get the type of encoding that was used for parsing the layer data. + // See: LayerEncodingType + Tmx::LayerEncodingType GetEncoding() const { return encoding; } + + // Get the type of compression that was used for parsing the layer data. + // See: LayerCompressionType + Tmx::LayerCompressionType GetCompression() const { return compression; } + + // Get the zorder of the layer. + int GetZOrder() const { return zOrder; } + + // Set the zorder of the layer. + void SetZOrder( int z ) { zOrder = z; } + + private: + void ParseXML(const TiXmlNode *dataNode); + void ParseBase64(const std::string &innerText); + void ParseCSV(const std::string &innerText); + + const Tmx::Map *map; + + std::string name; + + int width; + int height; + + float opacity; + bool visible; + int zOrder; + + Tmx::PropertySet properties; + + Tmx::MapTile *tile_map; + + Tmx::LayerEncodingType encoding; + Tmx::LayerCompressionType compression; + }; +}; diff --git a/engine/lib/TmxParser/TmxMap.cpp b/engine/lib/TmxParser/TmxMap.cpp new file mode 100644 index 000000000..8d187abb8 --- /dev/null +++ b/engine/lib/TmxParser/TmxMap.cpp @@ -0,0 +1,329 @@ +//----------------------------------------------------------------------------- +// TmxMap.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" +#include + +#include "TmxMap.h" +#include "TmxTileset.h" +#include "TmxLayer.h" +#include "TmxObjectGroup.h" +#include "TmxImageLayer.h" + +#ifdef USE_SDL2_LOAD +#include +#endif + +using std::vector; +using std::string; + +namespace Tmx +{ + Map::Map() + : file_name() + , file_path() + , version(0.0) + , orientation(TMX_MO_ORTHOGONAL) + , width(0) + , height(0) + , tile_width(0) + , tile_height(0) + , layers() + , object_groups() + , tilesets() + , has_error(false) + , error_code(0) + , error_text() + {} + + Map::~Map() + { + // Iterate through all of the object groups and delete each of them. + vector< ObjectGroup* >::iterator ogIter; + for (ogIter = object_groups.begin(); ogIter != object_groups.end(); ++ogIter) + { + ObjectGroup *objectGroup = (*ogIter); + + if (objectGroup) + { + delete objectGroup; + objectGroup = NULL; + } + } + + // Iterate through all of the layers and delete each of them. + vector< Layer* >::iterator lIter; + for (lIter = layers.begin(); lIter != layers.end(); ++lIter) + { + Layer *layer = (*lIter); + + if (layer) + { + delete layer; + layer = NULL; + } + } + + // Iterate through all of the layers and delete each of them. + vector< ImageLayer* >::iterator ilIter; + for (ilIter = image_layers.begin(); ilIter != image_layers.end(); ++ilIter) + { + ImageLayer *layer = (*ilIter); + + if (layer) + { + delete layer; + layer = NULL; + } + } + + // Iterate through all of the tilesets and delete each of them. + vector< Tileset* >::iterator tsIter; + for (tsIter = tilesets.begin(); tsIter != tilesets.end(); ++tsIter) + { + Tileset *tileset = (*tsIter); + + if (tileset) + { + delete tileset; + tileset = NULL; + } + } + } + + void Map::ParseFile(const string &fileName) + { + file_name = fileName; + + int lastSlash = fileName.find_last_of("/"); + + // Get the directory of the file using substring. + if (lastSlash > 0) + { + file_path = fileName.substr(0, lastSlash + 1); + } + else + { + file_path = ""; + } + + char* fileText; + int fileSize; + + // Open the file for reading. +#ifdef USE_SDL2_LOAD + SDL_RWops * file = SDL_RWFromFile (fileName.c_str(), "rb"); +#else + FILE *file = fopen(fileName.c_str(), "rb"); +#endif + + // Check if the file could not be opened. + if (!file) + { + has_error = true; + error_code = TMX_COULDNT_OPEN; + error_text = "Could not open the file."; + return; + } + + // Find out the file size. +#ifdef USE_SDL2_LOAD + fileSize = file->size(file); +#else + fseek(file, 0, SEEK_END); + fileSize = ftell(file); + fseek(file, 0, SEEK_SET); +#endif + + // Check if the file size is valid. + if (fileSize <= 0) + { + has_error = true; + error_code = TMX_INVALID_FILE_SIZE; + error_text = "The size of the file is invalid."; + return; + } + + // Allocate memory for the file and read it into the memory. + fileText = new char[fileSize + 1]; + fileText[fileSize] = 0; +#ifdef USE_SDL2_LOAD + file->read(file, fileText, 1, fileSize); +#else + fread(fileText, 1, fileSize, file); +#endif + +#ifdef USE_SDL2_LOAD + file->close(file); +#else + fclose(file); +#endif + + // Copy the contents into a C++ string and delete it from memory. + std::string text(fileText, fileText+fileSize); + delete [] fileText; + + ParseText(text); + } + + void Map::ParseText(const string &text) + { + // Create a tiny xml document and use it to parse the text. + TiXmlDocument doc; + doc.Parse(text.c_str()); + + // Check for parsing errors. + if (doc.Error()) + { + has_error = true; + error_code = TMX_PARSING_ERROR; + error_text = doc.ErrorDesc(); + return; + } + + TiXmlNode *mapNode = doc.FirstChild("map"); + TiXmlElement* mapElem = mapNode->ToElement(); + + // Read the map attributes. + mapElem->Attribute("version", &version); + mapElem->Attribute("width", &width); + mapElem->Attribute("height", &height); + mapElem->Attribute("tilewidth", &tile_width); + mapElem->Attribute("tileheight", &tile_height); + + // Read the orientation + std::string orientationStr = mapElem->Attribute("orientation"); + + if (!orientationStr.compare("orthogonal")) + { + orientation = TMX_MO_ORTHOGONAL; + } + else if (!orientationStr.compare("isometric")) + { + orientation = TMX_MO_ISOMETRIC; + } + else if (!orientationStr.compare("staggered")) + { + orientation = TMX_MO_STAGGERED; + } + + + const TiXmlNode *node = mapElem->FirstChild(); + int zOrder = 0; + while( node ) + { + // Read the map properties. + if( strcmp( node->Value(), "properties" ) == 0 ) + { + properties.Parse(node); + } + + // Iterate through all of the tileset elements. + if( strcmp( node->Value(), "tileset" ) == 0 ) + { + // Allocate a new tileset and parse it. + Tileset *tileset = new Tileset(); + tileset->Parse(node->ToElement()); + + // Add the tileset to the list. + tilesets.push_back(tileset); + } + + // Iterate through all of the layer elements. + if( strcmp( node->Value(), "layer" ) == 0 ) + { + // Allocate a new layer and parse it. + Layer *layer = new Layer(this); + layer->Parse(node); + layer->SetZOrder( zOrder ); + ++zOrder; + + // Add the layer to the list. + layers.push_back(layer); + } + + // Iterate through all of the imagen layer elements. + if( strcmp( node->Value(), "imagelayer" ) == 0 ) + { + // Allocate a new layer and parse it. + ImageLayer *imageLayer = new ImageLayer(this); + imageLayer->Parse(node); + imageLayer->SetZOrder( zOrder ); + ++zOrder; + + // Add the layer to the list. + image_layers.push_back(imageLayer); + } + + // Iterate through all of the objectgroup elements. + if( strcmp( node->Value(), "objectgroup" ) == 0 ) + { + // Allocate a new object group and parse it. + ObjectGroup *objectGroup = new ObjectGroup(); + objectGroup->Parse(node); + objectGroup->SetZOrder( zOrder ); + ++zOrder; + + // Add the object group to the list. + object_groups.push_back(objectGroup); + } + + node = node->NextSibling(); + } + } + + int Map::FindTilesetIndex(int gid) const + { + // Clean up the flags from the gid (thanks marwes91). + gid &= ~(FlippedHorizontallyFlag | FlippedVerticallyFlag | FlippedDiagonallyFlag); + + for (int i = tilesets.size() - 1; i > -1; --i) + { + // If the gid beyond the tileset gid return its index. + if (gid >= tilesets[i]->GetFirstGid()) + { + return i; + } + } + + return -1; + } + + const Tileset *Map::FindTileset(int gid) const + { + for (int i = tilesets.size() - 1; i > -1; --i) + { + // If the gid beyond the tileset gid return it. + if (gid >= tilesets[i]->GetFirstGid()) + { + return tilesets[i]; + } + } + + return NULL; + } +}; diff --git a/engine/lib/TmxParser/TmxMap.h b/engine/lib/TmxParser/TmxMap.h new file mode 100644 index 000000000..61c1d6b86 --- /dev/null +++ b/engine/lib/TmxParser/TmxMap.h @@ -0,0 +1,196 @@ +//----------------------------------------------------------------------------- +// TmxMap.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include +#include + +#include "TmxPropertySet.h" + +namespace Tmx +{ + class Layer; + class ImageLayer; + class ObjectGroup; + class Tileset; + + //------------------------------------------------------------------------- + // Error in handling of the Map class. + //------------------------------------------------------------------------- + enum MapError + { + // A file could not be opened. (usually due to permission problems) + TMX_COULDNT_OPEN = 0x01, + + // There was an error in parsing the TMX file. + // This is being caused by TinyXML parsing problems. + TMX_PARSING_ERROR = 0x02, + + // The size of the file is invalid. + TMX_INVALID_FILE_SIZE = 0x04 + }; + + //------------------------------------------------------------------------- + // The way the map is viewed. + //------------------------------------------------------------------------- + enum MapOrientation + { + // This map is an orthogonal map. + TMX_MO_ORTHOGONAL = 0x01, + + // This map is an isometric map. + TMX_MO_ISOMETRIC = 0x02, + + // This map is an isometric staggered map. + TMX_MO_STAGGERED = 0x03 + }; + + //------------------------------------------------------------------------- + // This class is the root class of the parser. + // It has all of the information in regard to the TMX file. + // This class has a property set. + //------------------------------------------------------------------------- + class Map + { + private: + // Prevent copy constructor. + Map(const Map &_map); + + public: + Map(); + ~Map(); + + // Read a file and parse it. + // Note: use '/' instead of '\\' as it is using '/' to find the path. + void ParseFile(const std::string &fileName); + + // Parse text containing TMX formatted XML. + void ParseText(const std::string &text); + + // Get the filename used to read the map. + const std::string &GetFilename() { return file_name; } + + // Get a path to the directory of the map file if any. + const std::string &GetFilepath() const { return file_path; } + + // Get the version of the map. + double GetVersion() const { return version; } + + // Get the orientation of the map. + Tmx::MapOrientation GetOrientation() const { return orientation; } + + // Get the width of the map, in tiles. + int GetWidth() const { return width; } + + // Get the height of the map, in tiles. + int GetHeight() const { return height; } + + // Get the width of a tile, in pixels. + int GetTileWidth() const { return tile_width; } + + // Get the height of a tile, in pixels. + int GetTileHeight() const { return tile_height; } + + // Get the layer at a certain index. + const Tmx::Layer *GetLayer(int index) const { return layers.at(index); } + + // Get the amount of layers. + int GetNumLayers() const { return layers.size(); } + + // Get the whole layers collection. + const std::vector< Tmx::Layer* > &GetLayers() const { return layers; } + + // Get the object group at a certain index. + const Tmx::ObjectGroup *GetObjectGroup(int index) const { return object_groups.at(index); } + + // Get the amount of object groups. + int GetNumObjectGroups() const { return object_groups.size(); } + + // Get the whole object group collection. + const std::vector< Tmx::ObjectGroup* > &GetObjectGroups() const { return object_groups; } + + // Get the layer at a certain index. + const Tmx::ImageLayer *GetImageLayer(int index) const { return image_layers.at(index); } + + // Get the amount of layers. + int GetNumImageLayers() const { return image_layers.size(); } + + // Get the whole layers collection. + const std::vector< Tmx::ImageLayer* > &GetImageLayers() const { return image_layers; } + + // Find the tileset index for a tileset using a tile gid. + int FindTilesetIndex(int gid) const; + + // Find a tileset for a specific gid. + const Tmx::Tileset *FindTileset(int gid) const; + + // Get a tileset by an index. + const Tmx::Tileset *GetTileset(int index) const { return tilesets.at(index); } + + // Get the amount of tilesets. + int GetNumTilesets() const { return tilesets.size(); } + + // Get the collection of tilesets. + const std::vector< Tmx::Tileset* > &GetTilesets() const { return tilesets; } + + // Get whether there was an error or not. + bool HasError() const { return has_error; } + + // Get an error string containing the error in text format. + const std::string &GetErrorText() const { return error_text; } + + // Get a number that identifies the error. (TMX_ preceded constants) + unsigned char GetErrorCode() const { return error_code; } + + // Get the property set. + const Tmx::PropertySet &GetProperties() const { return properties; } + + private: + std::string file_name; + std::string file_path; + + double version; + Tmx::MapOrientation orientation; + + int width; + int height; + int tile_width; + int tile_height; + + std::vector< Tmx::Layer* > layers; + std::vector< Tmx::ImageLayer* > image_layers; + std::vector< Tmx::ObjectGroup* > object_groups; + std::vector< Tmx::Tileset* > tilesets; + + bool has_error; + unsigned char error_code; + std::string error_text; + + Tmx::PropertySet properties; + }; +}; diff --git a/engine/lib/TmxParser/TmxMapTile.h b/engine/lib/TmxParser/TmxMapTile.h new file mode 100644 index 000000000..96005d500 --- /dev/null +++ b/engine/lib/TmxParser/TmxMapTile.h @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// TmxMapTile.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +namespace Tmx +{ + //------------------------------------------------------------------------- + // Flags that may be in the first two bits of the gid. + //------------------------------------------------------------------------- + const unsigned FlippedHorizontallyFlag = 0x80000000; + const unsigned FlippedVerticallyFlag = 0x40000000; + const unsigned FlippedDiagonallyFlag = 0x20000000; + + //------------------------------------------------------------------------- + // Struct to store information about a specific tile in the map layer. + //------------------------------------------------------------------------- + struct MapTile + { + // Default constructor. + MapTile() + : tilesetId(0) + , id(0) + , flippedHorizontally(false) + , flippedVertically(false) + , flippedDiagonally(false) + {} + + // Will take a gid and read the attributes from the first + // two bits of it. + MapTile(unsigned _gid, int _tilesetFirstGid, unsigned _tilesetId) + : tilesetId(_tilesetId) + , id(_gid & ~(FlippedHorizontallyFlag | FlippedVerticallyFlag | FlippedDiagonallyFlag)) + , flippedHorizontally((_gid & FlippedHorizontallyFlag) != 0) + , flippedVertically((_gid & FlippedVerticallyFlag) != 0) + , flippedDiagonally((_gid & FlippedDiagonallyFlag) != 0) + { + id -= _tilesetFirstGid; + } + + // Tileset id. + int tilesetId; + + // Id. + unsigned id; + + // True when the tile should be drawn flipped horizontally. + bool flippedHorizontally; + + // True when the tile should be drawn flipped vertically. + bool flippedVertically; + + // True when the tile should be drawn flipped diagonally. + bool flippedDiagonally; + }; +}; diff --git a/engine/lib/TmxParser/TmxObject.cpp b/engine/lib/TmxParser/TmxObject.cpp new file mode 100644 index 000000000..d162abefa --- /dev/null +++ b/engine/lib/TmxParser/TmxObject.cpp @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// TmxObject.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxObject.h" +#include "TmxPolygon.h" +#include "TmxPolyline.h" +#include "TmxEllipse.h" + +namespace Tmx +{ + Object::Object() + : name() + , type() + , x(0) + , y(0) + , width(0) + , height(0) + , gid(0) + , ellipse(0) + , polygon(0) + , polyline(0) + , properties() + {} + + Object::~Object() + { + if (ellipse != 0) + { + delete ellipse; + ellipse = 0; + } + if (polygon != 0) + { + delete polygon; + polygon = 0; + } + if (polyline != 0) + { + delete polyline; + polyline = 0; + } + } + + void Object::Parse(const TiXmlNode *objectNode) + { + const TiXmlElement *objectElem = objectNode->ToElement(); + + // Read the attributes of the object. + const char *tempName = objectElem->Attribute("name"); + const char *tempType = objectElem->Attribute("type"); + + if (tempName) name = tempName; + if (tempType) type = tempType; + + objectElem->Attribute("x", &x); + objectElem->Attribute("y", &y); + objectElem->Attribute("width", &width); + objectElem->Attribute("height", &height); + objectElem->Attribute("gid", &gid); + + // Read the ellipse of the object if there are any. + const TiXmlNode *ellipseNode = objectNode->FirstChild("ellipse"); + if (ellipseNode) + { + if (ellipse != 0) + delete ellipse; + + ellipse = new Ellipse(x,y,width,height); + } + + // Read the Polygon and Polyline of the object if there are any. + const TiXmlNode *polygonNode = objectNode->FirstChild("polygon"); + if (polygonNode) + { + if (polygon != 0) + delete polygon; + + polygon = new Polygon(); + polygon->Parse(polygonNode); + } + const TiXmlNode *polylineNode = objectNode->FirstChild("polyline"); + if (polylineNode) + { + if (polyline != 0) + delete polyline; + + polyline = new Polyline(); + polyline->Parse(polylineNode); + } + + // Read the properties of the object. + const TiXmlNode *propertiesNode = objectNode->FirstChild("properties"); + if (propertiesNode) + { + properties.Parse(propertiesNode); + } + } +}; diff --git a/engine/lib/TmxParser/TmxObject.h b/engine/lib/TmxParser/TmxObject.h new file mode 100644 index 000000000..a9a91efca --- /dev/null +++ b/engine/lib/TmxParser/TmxObject.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// TmxObject.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include + +#include "TmxPropertySet.h" + +class TiXmlNode; + +namespace Tmx +{ + class Ellipse; + class Polygon; + class Polyline; + + //------------------------------------------------------------------------- + // Class used for representing a single object from the objectgroup. + //------------------------------------------------------------------------- + class Object + { + public: + Object(); + ~Object(); + + // Parse an object node. + void Parse(const TiXmlNode *objectNode); + + // Get the name of the object. + const std::string &GetName() const { return name; } + + // Get the type of the object. + const std::string &GetType() const { return type; } + + // Get the left side of the object, in pixels. + int GetX() const { return x; } + + // Get the top side of the object, in pixels. + int GetY() const { return y; } + + // Get the width of the object, in pixels. + int GetWidth() const { return width; } + + // Get the height of the object, in pixels. + int GetHeight() const { return height; } + + // Get the Global ID of the tile associated with this object. + int GetGid() const { return gid; } + + // Get the ellipse. + const Tmx::Ellipse *GetEllipse() const { return ellipse; } + + // Get the Polygon. + const Tmx::Polygon *GetPolygon() const { return polygon; } + + // Get the Polyline. + const Tmx::Polyline *GetPolyline() const { return polyline; } + + // Get the property set. + const Tmx::PropertySet &GetProperties() const { return properties; } + + private: + std::string name; + std::string type; + + int x; + int y; + int width; + int height; + int gid; + + Tmx::Ellipse *ellipse; + Tmx::Polygon *polygon; + Tmx::Polyline *polyline; + + Tmx::PropertySet properties; + }; +}; diff --git a/engine/lib/TmxParser/TmxObjectGroup.cpp b/engine/lib/TmxParser/TmxObjectGroup.cpp new file mode 100644 index 000000000..8c6e73fe3 --- /dev/null +++ b/engine/lib/TmxParser/TmxObjectGroup.cpp @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// TmxObjectGroup.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxObjectGroup.h" +#include "TmxObject.h" + +namespace Tmx +{ + ObjectGroup::ObjectGroup() + : name() + , width(0) + , height(0) + , zOrder(0) + {} + + ObjectGroup::~ObjectGroup() + { + for(std::size_t i = 0; i < objects.size(); i++) + { + Object *obj = objects.at(i); + delete obj; + } + } + + void ObjectGroup::Parse(const TiXmlNode *objectGroupNode) + { + const TiXmlElement *objectGroupElem = objectGroupNode->ToElement(); + if (objectGroupElem) { + // Read the object group attributes. + const char* nom = objectGroupElem->Attribute("name"); + if (nom) + name = nom; + + objectGroupElem->Attribute("width", &width); + objectGroupElem->Attribute("height", &height); + objectGroupElem->Attribute("visible", &visible); + } + // Read the properties. + const TiXmlNode *propertiesNode = objectGroupNode->FirstChild("properties"); + if (propertiesNode) + { + properties.Parse(propertiesNode); + } + + // Iterate through all of the object elements. + const TiXmlNode *objectNode = objectGroupNode->FirstChild("object"); + while (objectNode) + { + // Allocate a new object and parse it. + Object *object = new Object(); + object->Parse(objectNode); + + // Add the object to the list. + objects.push_back(object); + + objectNode = objectGroupNode->IterateChildren("object", objectNode); + } + } + +}; diff --git a/engine/lib/TmxParser/TmxObjectGroup.h b/engine/lib/TmxParser/TmxObjectGroup.h new file mode 100644 index 000000000..e4fd99435 --- /dev/null +++ b/engine/lib/TmxParser/TmxObjectGroup.h @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// TmxObjectGroup.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include +#include + +#include "TmxPropertySet.h" + +class TiXmlNode; + +namespace Tmx +{ + class Object; + + //------------------------------------------------------------------------- + // A class used for holding a list of objects. + // This class doesn't have a property set. + //------------------------------------------------------------------------- + class ObjectGroup + { + public: + ObjectGroup(); + ~ObjectGroup(); + + // Parse an objectgroup node. + void Parse(const TiXmlNode *objectGroupNode); + + // Get the name of the object group. + const std::string &GetName() const { return name; } + + // Get the width of the object group, in pixels. + // Note: do not rely on this due to temporary bug in tiled. + int GetWidth() const { return width; } + + // Get the height of the object group, in pixels. + // Note: do not rely on this due to temporary bug in tiled. + int GetHeight() const { return height; } + + // Get a single object. + const Tmx::Object *GetObject(int index) const { return objects.at(index); } + + // Get the number of objects in the list. + int GetNumObjects() const { return objects.size(); } + + // Get whether the object layer is visible. + int GetVisibility() const { return visible; } + + // Get the property set. + const Tmx::PropertySet &GetProperties() const { return properties; } + + // Get the whole list of objects. + const std::vector< Tmx::Object* > &GetObjects() const { return objects; } + + // Get the zorder of the object group. + int GetZOrder() const { return zOrder; } + + // Set the zorder of the object group. + void SetZOrder( int z ) { zOrder = z; } + + private: + std::string name; + + int width; + int height; + int visible; + int zOrder; + + Tmx::PropertySet properties; + + std::vector< Tmx::Object* > objects; + }; +}; diff --git a/engine/lib/TmxParser/TmxPoint.h b/engine/lib/TmxParser/TmxPoint.h new file mode 100644 index 000000000..e35f18803 --- /dev/null +++ b/engine/lib/TmxParser/TmxPoint.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// TmxPoint.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +namespace Tmx +{ + //------------------------------------------------------------------------- + // Used to store a vertex of a Polygon/Polyline. + //------------------------------------------------------------------------- + struct Point + { + double x; + double y; + }; +} diff --git a/engine/lib/TmxParser/TmxPolygon.cpp b/engine/lib/TmxParser/TmxPolygon.cpp new file mode 100644 index 000000000..9c4958dab --- /dev/null +++ b/engine/lib/TmxParser/TmxPolygon.cpp @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// TmxPolygon.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxPolygon.h" + +namespace Tmx +{ + Polygon::Polygon() + : points() + { + } + + void Polygon::Parse(const TiXmlNode *polygonNode) + { + char *pointsLine = strdup(polygonNode->ToElement()->Attribute("points")); + + char *token = strtok(pointsLine, " "); + while (token) + { + Point point; + sscanf(token, "%lf,%lf", &point.x, &point.y); + + points.push_back(point); + + token = strtok(0, " "); + } + + free(pointsLine); + } +} diff --git a/engine/lib/TmxParser/TmxPolygon.h b/engine/lib/TmxParser/TmxPolygon.h new file mode 100644 index 000000000..ccbac799f --- /dev/null +++ b/engine/lib/TmxParser/TmxPolygon.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// TmxPolygon.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include + +#include "TmxPoint.h" + +class TiXmlNode; + +namespace Tmx +{ + //------------------------------------------------------------------------- + // Class to store a Polygon of an Object. + //------------------------------------------------------------------------- + class Polygon + { + public: + Polygon(); + + // Parse the polygon node. + void Parse(const TiXmlNode *polygonNode); + + // Get one of the vertices. + const Tmx::Point &GetPoint(int index) const { return points[index]; } + + // Get the number of vertices. + int GetNumPoints() const { return points.size(); } + + private: + std::vector< Tmx::Point > points; + }; +}; diff --git a/engine/lib/TmxParser/TmxPolyline.cpp b/engine/lib/TmxParser/TmxPolyline.cpp new file mode 100644 index 000000000..4f6d166f2 --- /dev/null +++ b/engine/lib/TmxParser/TmxPolyline.cpp @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// TmxPolyline.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxPolyline.h" + +namespace Tmx +{ + Polyline::Polyline() + : points() + { + } + + void Polyline::Parse(const TiXmlNode *polylineNode) + { + char *pointsLine = strdup(polylineNode->ToElement()->Attribute("points")); + + char *token = strtok(pointsLine, " "); + while (token) + { + Point point; + sscanf(token, "%lf,%lf", &point.x, &point.y); + + points.push_back(point); + + token = strtok(0, " "); + } + + free(pointsLine); + } +} diff --git a/engine/lib/TmxParser/TmxPolyline.h b/engine/lib/TmxParser/TmxPolyline.h new file mode 100644 index 000000000..bfc3e1ebe --- /dev/null +++ b/engine/lib/TmxParser/TmxPolyline.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// TmxPolyline.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include + +#include "TmxPoint.h" + +class TiXmlNode; + +namespace Tmx +{ + //------------------------------------------------------------------------- + // Class to store a Polyline of an Object. + //------------------------------------------------------------------------- + class Polyline + { + public: + Polyline(); + + // Parse the polyline node. + void Parse(const TiXmlNode *polylineNode); + + // Get one of the vertices. + const Tmx::Point &GetPoint(int index) const { return points[index]; } + + // Get the number of vertices. + int GetNumPoints() const { return points.size(); } + + private: + std::vector< Tmx::Point > points; + }; +}; diff --git a/engine/lib/TmxParser/TmxPropertySet.cpp b/engine/lib/TmxParser/TmxPropertySet.cpp new file mode 100644 index 000000000..b2551cffe --- /dev/null +++ b/engine/lib/TmxParser/TmxPropertySet.cpp @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// TmxPropertySet.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxPropertySet.h" + +using std::string; +using std::map; + +namespace Tmx +{ + + PropertySet::PropertySet() : properties() + {} + + PropertySet::~PropertySet() + { + properties.clear(); + } + + void PropertySet::Parse(const TiXmlNode *propertiesNode) + { + // Iterate through all of the property nodes. + const TiXmlNode *propertyNode = propertiesNode->FirstChild("property"); + string propertyName; + string propertyValue; + + while (propertyNode) + { + const TiXmlElement* propertyElem = propertyNode->ToElement(); + + // Read the attributes of the property and add it to the map + propertyName = string(propertyElem->Attribute("name")); + propertyValue = string(propertyElem->Attribute("value")); + properties[propertyName] = propertyValue; + + propertyNode = propertiesNode->IterateChildren( + "property", propertyNode); + } + } + + string PropertySet::GetLiteralProperty(const string &name) const + { + // Find the property in the map. + map< string, string >::const_iterator iter = properties.find(name); + + if (iter == properties.end()) + return std::string(""); + + return iter->second; + } + + int PropertySet::GetNumericProperty(const string &name) const + { + return atoi(GetLiteralProperty(name).c_str()); + } + + float PropertySet::GetFloatProperty(const string &name) const + { + return float(atof(GetLiteralProperty(name).c_str())); + } + + bool PropertySet::HasProperty( const string& name ) const + { + if( properties.empty() ) return false; + return ( properties.find(name) != properties.end() ); + } + +}; diff --git a/engine/lib/TmxParser/TmxPropertySet.h b/engine/lib/TmxParser/TmxPropertySet.h new file mode 100644 index 000000000..34686ac51 --- /dev/null +++ b/engine/lib/TmxParser/TmxPropertySet.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// TmxPropertySet.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include +#include + +class TiXmlNode; + +namespace Tmx +{ + //----------------------------------------------------------------------------- + // This class contains a map of properties. + //----------------------------------------------------------------------------- + class PropertySet + { + public: + PropertySet(); + ~PropertySet(); + + // Parse a node containing all the property nodes. + void Parse(const TiXmlNode *propertiesNode); + + // Get a numeric property (integer). + int GetNumericProperty(const std::string &name) const; + // Get a numeric property (float). + float GetFloatProperty(const std::string &name) const; + + // Get a literal property (string). + std::string GetLiteralProperty(const std::string &name) const; + + // Returns the amount of properties. + int GetSize() const { return properties.size(); } + + bool HasProperty( const std::string& name ) const; + + // Returns the STL map of the properties. + std::map< std::string, std::string > GetList() const + { return properties; } + + // Returns whether there are no properties. + bool Empty() const { return properties.empty(); } + + private: + std::map< std::string, std::string > properties; + + }; +}; diff --git a/engine/lib/TmxParser/TmxTile.cpp b/engine/lib/TmxParser/TmxTile.cpp new file mode 100644 index 000000000..3c58bb380 --- /dev/null +++ b/engine/lib/TmxParser/TmxTile.cpp @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// TmxTile.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +// Major revisions: Casey Doran +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxTile.h" + +namespace Tmx +{ + Tile::Tile() : properties() + {} + + Tile::~Tile() + {} + + void Tile::Parse(const TiXmlNode *tileNode) + { + const TiXmlElement *tileElem = tileNode->ToElement(); + + // Parse the attributes. + tileElem->Attribute("id", &id); + + // Parse the properties if any. + const TiXmlNode *propertiesNode = tileNode->FirstChild("properties"); + + if (propertiesNode) + { + properties.Parse(propertiesNode); + } + + //Parse the objects, if any + const TiXmlNode *objectsNode = tileNode->FirstChild("objectgroup"); + + if (objectsNode){ + objects.Parse(objectsNode); + } + } +}; diff --git a/engine/lib/TmxParser/TmxTile.h b/engine/lib/TmxParser/TmxTile.h new file mode 100644 index 000000000..cff3f21bd --- /dev/null +++ b/engine/lib/TmxParser/TmxTile.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// TmxTile.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +// Major revisions: Casey Doran +//----------------------------------------------------------------------------- +#pragma once + +#include "TmxPropertySet.h" +#include "TmxObjectGroup.h" + +namespace Tmx +{ + //------------------------------------------------------------------------- + // Class to contain information about every tile in the tileset/tiles + // element. + // It may expand if there are more elements or attributes added into the + // the tile element. + // This class also contains a property set. + //------------------------------------------------------------------------- + class Tile + { + public: + Tile(); + ~Tile(); + + // Parse a tile node. + void Parse(const TiXmlNode *tileNode); + + // Get the Id. (relative to the tilset) + int GetId() const { return id; } + + // Get a set of properties regarding the tile. + const Tmx::PropertySet &GetProperties() const { return properties; } + const Tmx::ObjectGroup &GetObjectGroup() const{ return + objects;} + + private: + int id; + + Tmx::PropertySet properties; + Tmx::ObjectGroup objects; + }; +}; diff --git a/engine/lib/TmxParser/TmxTileset.cpp b/engine/lib/TmxParser/TmxTileset.cpp new file mode 100644 index 000000000..fecc11d93 --- /dev/null +++ b/engine/lib/TmxParser/TmxTileset.cpp @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// TmxTileset.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include "tinyxml/tinyxml.h" + +#include "TmxTileset.h" +#include "TmxImage.h" +#include "TmxTile.h" + +using std::vector; +using std::string; + +namespace Tmx +{ + Tileset::Tileset() + : first_gid(0) + , name() + , tile_width(0) + , tile_height(0) + , margin(0) + , spacing(0) + , image(NULL) + , tiles() + { + } + + Tileset::~Tileset() + { + // Delete the image from memory if allocated. + if (image) + { + delete image; + image = NULL; + } + + // Iterate through all of the tiles in the set and delete each of them. + vector< Tile* >::iterator tIter; + for (tIter = tiles.begin(); tIter != tiles.end(); ++tIter) + { + Tile *tile = (*tIter); + + if (tile) + { + delete tile; + tile = NULL; + } + } + } + + void Tileset::Parse(const TiXmlNode *tilesetNode) + { + const TiXmlElement *tilesetElem = tilesetNode->ToElement(); + + // Read all the attributes into local variables. + tilesetElem->Attribute("firstgid", &first_gid); + tilesetElem->Attribute("tilewidth", &tile_width); + tilesetElem->Attribute("tileheight", &tile_height); + tilesetElem->Attribute("margin", &margin); + tilesetElem->Attribute("spacing", &spacing); + + name = tilesetElem->Attribute("name"); + + // Parse the image. + const TiXmlNode *imageNode = tilesetNode->FirstChild("image"); + + if (imageNode) + { + image = new Image(); + image->Parse(imageNode); + } + + // Iterate through all of the tile elements and parse each. + const TiXmlNode *tileNode = tilesetNode->FirstChild("tile"); + while (tileNode) + { + // Allocate a new tile and parse it. + Tile *tile = new Tile(); + tile->Parse(tileNode); + + // Add the tile to the collection. + tiles.push_back(tile); + + tileNode = tilesetNode->IterateChildren("tile", tileNode); + } + + // Parse the properties if any. + const TiXmlNode *propertiesNode = tilesetNode->FirstChild("properties"); + + if (propertiesNode) + { + properties.Parse(propertiesNode); + } + } + + const Tile *Tileset::GetTile(int index) const + { + for (unsigned int i = 0; i < tiles.size(); ++i) + { + if (tiles.at(i)->GetId() == index) + { + return tiles.at(i); + } + } + + return NULL; + } +}; diff --git a/engine/lib/TmxParser/TmxTileset.h b/engine/lib/TmxParser/TmxTileset.h new file mode 100644 index 000000000..9424f9155 --- /dev/null +++ b/engine/lib/TmxParser/TmxTileset.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// TmxTileset.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include +#include + +#include "TmxPropertySet.h" + +class TiXmlNode; + +namespace Tmx +{ + class Image; + class Tile; + + //------------------------------------------------------------------------- + // A class used for storing information about each of the tilesets. + // A tileset is a collection of tiles, of whom each may contain properties. + // The tileset class itself does not have properties. + //------------------------------------------------------------------------- + class Tileset + { + public: + Tileset(); + ~Tileset(); + + // Parse a tileset element. + void Parse(const TiXmlNode *tilesetNode); + + // Returns the global id of the first tile. + int GetFirstGid() const { return first_gid; } + + // Returns the name of the tileset. + const std::string &GetName() const { return name; } + + // Get the width of a single tile. + int GetTileWidth() const { return tile_width; } + + // Get the height of a single tile. + int GetTileHeight() const { return tile_height; } + + // Get the margin of the tileset. + int GetMargin() const { return margin; } + + // Get the spacing of the tileset. + int GetSpacing() const { return spacing; } + + // Returns a variable containing information + // about the image of the tileset. + const Tmx::Image* GetImage() const { return image; } + + // Returns a a single tile of the set. + const Tmx::Tile *GetTile(int index) const; + + // Returns the whole tile collection. + const std::vector< Tmx::Tile *> &GetTiles() const { return tiles; } + + // Get a set of properties regarding the tile. + const Tmx::PropertySet &GetProperties() const { return properties; } + + private: + int first_gid; + + std::string name; + + int tile_width; + int tile_height; + int margin; + int spacing; + + Tmx::Image* image; + + std::vector< Tmx::Tile* > tiles; + + Tmx::PropertySet properties; + }; +}; diff --git a/engine/lib/TmxParser/TmxUtil.cpp b/engine/lib/TmxParser/TmxUtil.cpp new file mode 100644 index 000000000..01f520c92 --- /dev/null +++ b/engine/lib/TmxParser/TmxUtil.cpp @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// TmxUtil.cpp +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#include +#include "../../lib/zlib/zlib.h" + +#include "TmxUtil.h" +#include "base64/base64.h" + +namespace Tmx { + std::string Util::DecodeBase64(const std::string &str) + { + return base64_decode(str); + } + + char *Util::DecompressGZIP(const char *data, int dataSize, int expectedSize) + { + int bufferSize = expectedSize; + int ret; + z_stream strm; + char *out = (char*)malloc(bufferSize); + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_in = (Bytef*)data; + strm.avail_in = dataSize; + strm.next_out = (Bytef*)out; + strm.avail_out = bufferSize; + + ret = inflateInit2(&strm, 15 + 32); + + if (ret != Z_OK) + { + free(out); + return NULL; + } + + do + { + ret = inflate(&strm, Z_SYNC_FLUSH); + + switch (ret) + { + case Z_NEED_DICT: + case Z_STREAM_ERROR: + ret = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + free(out); + return NULL; + } + + if (ret != Z_STREAM_END) + { + out = (char *) realloc(out, bufferSize * 2); + + if (!out) + { + inflateEnd(&strm); + free(out); + return NULL; + } + + strm.next_out = (Bytef *)(out + bufferSize); + strm.avail_out = bufferSize; + bufferSize *= 2; + } + } + while (ret != Z_STREAM_END); + + if (strm.avail_in != 0) + { + free(out); + return NULL; + } + + inflateEnd(&strm); + + return out; + } +}; diff --git a/engine/lib/TmxParser/TmxUtil.h b/engine/lib/TmxParser/TmxUtil.h new file mode 100644 index 000000000..0bb18fec0 --- /dev/null +++ b/engine/lib/TmxParser/TmxUtil.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// TmxUtil.h +// +// Copyright (c) 2010-2013, Tamir Atias +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL TAMIR ATIAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Tamir Atias +//----------------------------------------------------------------------------- +#pragma once + +#include + +namespace Tmx +{ + class Util + { + public: + // Decode a base-64 encoded string. + static std::string DecodeBase64(const std::string &str); + + // Decompress a gzip encoded byte array. + static char* DecompressGZIP(const char *data, int dataSize, int expectedSize); + }; +}; diff --git a/engine/lib/TmxParser/base64/base64.cpp b/engine/lib/TmxParser/base64/base64.cpp new file mode 100644 index 000000000..50006d4f3 --- /dev/null +++ b/engine/lib/TmxParser/base64/base64.cpp @@ -0,0 +1,123 @@ +/* + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "base64.h" +#include + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + + } + + return ret; + +} + +std::string base64_decode(std::string const& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} \ No newline at end of file diff --git a/engine/lib/TmxParser/base64/base64.h b/engine/lib/TmxParser/base64/base64.h new file mode 100644 index 000000000..2af9b82f5 --- /dev/null +++ b/engine/lib/TmxParser/base64/base64.h @@ -0,0 +1,35 @@ +/* + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ +#ifndef TMXPARSER_BASE64_H_ +#define TMXPARSER_BASE64_H_ + +#include + +std::string base64_encode(unsigned char const* , unsigned int len); +std::string base64_decode(std::string const& s); + +#endif \ No newline at end of file diff --git a/engine/lib/TmxParser/tinyxml/tinystr.h b/engine/lib/TmxParser/tinyxml/tinystr.h new file mode 100644 index 000000000..3c2aa9d54 --- /dev/null +++ b/engine/lib/TmxParser/tinyxml/tinystr.h @@ -0,0 +1,319 @@ +/* +www.sourceforge.net/projects/tinyxml +Original file by Yves Berquin. + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +/* + * THIS FILE WAS ALTERED BY Tyge Lovset, 7. April 2005. + * + * - completely rewritten. compact, clean, and fast implementation. + * - sizeof(TiXmlString) = pointer size (4 bytes on 32-bit systems) + * - fixed reserve() to work as per specification. + * - fixed buggy compares operator==(), operator<(), and operator>() + * - fixed operator+=() to take a const ref argument, following spec. + * - added "copy" constructor with length, and most compare operators. + * - added swap(), clear(), size(), capacity(), operator+(). + */ + +#ifndef TIXML_USE_STL + +#ifndef TIXML_STRING_INCLUDED +#define TIXML_STRING_INCLUDED + +#include +#include + +/* The support for explicit isn't that universal, and it isn't really + required - it is used to check that the TiXmlString class isn't incorrectly + used. Be nice to old compilers and macro it here: +*/ +#if defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + #define TIXML_EXPLICIT explicit +#elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + #define TIXML_EXPLICIT explicit +#else + #define TIXML_EXPLICIT +#endif + + +/* + TiXmlString is an emulation of a subset of the std::string template. + Its purpose is to allow compiling TinyXML on compilers with no or poor STL support. + Only the member functions relevant to the TinyXML project have been implemented. + The buffer allocation is made by a simplistic power of 2 like mechanism : if we increase + a string and there's no more room, we allocate a buffer twice as big as we need. +*/ +class TiXmlString +{ + public : + // The size type used + typedef size_t size_type; + + // Error value for find primitive + static const size_type npos; // = -1; + + + // TiXmlString empty constructor + TiXmlString () : rep_(&nullrep_) + { + } + + // TiXmlString copy constructor + TiXmlString ( const TiXmlString & copy) : rep_(0) + { + init(copy.length()); + memcpy(start(), copy.data(), length()); + } + + // TiXmlString constructor, based on a string + TIXML_EXPLICIT TiXmlString ( const char * copy) : rep_(0) + { + init( static_cast( strlen(copy) )); + memcpy(start(), copy, length()); + } + + // TiXmlString constructor, based on a string + TIXML_EXPLICIT TiXmlString ( const char * str, size_type len) : rep_(0) + { + init(len); + memcpy(start(), str, len); + } + + // TiXmlString destructor + ~TiXmlString () + { + quit(); + } + + // = operator + TiXmlString& operator = (const char * copy) + { + return assign( copy, (size_type)strlen(copy)); + } + + // = operator + TiXmlString& operator = (const TiXmlString & copy) + { + return assign(copy.start(), copy.length()); + } + + + // += operator. Maps to append + TiXmlString& operator += (const char * suffix) + { + return append(suffix, static_cast( strlen(suffix) )); + } + + // += operator. Maps to append + TiXmlString& operator += (char single) + { + return append(&single, 1); + } + + // += operator. Maps to append + TiXmlString& operator += (const TiXmlString & suffix) + { + return append(suffix.data(), suffix.length()); + } + + + // Convert a TiXmlString into a null-terminated char * + const char * c_str () const { return rep_->str; } + + // Convert a TiXmlString into a char * (need not be null terminated). + const char * data () const { return rep_->str; } + + // Return the length of a TiXmlString + size_type length () const { return rep_->size; } + + // Alias for length() + size_type size () const { return rep_->size; } + + // Checks if a TiXmlString is empty + bool empty () const { return rep_->size == 0; } + + // Return capacity of string + size_type capacity () const { return rep_->capacity; } + + + // single char extraction + const char& at (size_type index) const + { + assert( index < length() ); + return rep_->str[ index ]; + } + + // [] operator + char& operator [] (size_type index) const + { + assert( index < length() ); + return rep_->str[ index ]; + } + + // find a char in a string. Return TiXmlString::npos if not found + size_type find (char lookup) const + { + return find(lookup, 0); + } + + // find a char in a string from an offset. Return TiXmlString::npos if not found + size_type find (char tofind, size_type offset) const + { + if (offset >= length()) return npos; + + for (const char* p = c_str() + offset; *p != '\0'; ++p) + { + if (*p == tofind) return static_cast< size_type >( p - c_str() ); + } + return npos; + } + + void clear () + { + //Lee: + //The original was just too strange, though correct: + // TiXmlString().swap(*this); + //Instead use the quit & re-init: + quit(); + init(0,0); + } + + /* Function to reserve a big amount of data when we know we'll need it. Be aware that this + function DOES NOT clear the content of the TiXmlString if any exists. + */ + void reserve (size_type cap); + + TiXmlString& assign (const char* str, size_type len); + + TiXmlString& append (const char* str, size_type len); + + void swap (TiXmlString& other) + { + Rep* r = rep_; + rep_ = other.rep_; + other.rep_ = r; + } + + private: + + void init(size_type sz) { init(sz, sz); } + void set_size(size_type sz) { rep_->str[ rep_->size = sz ] = '\0'; } + char* start() const { return rep_->str; } + char* finish() const { return rep_->str + rep_->size; } + + struct Rep + { + size_type size, capacity; + char str[1]; + }; + + void init(size_type sz, size_type cap) + { + if (cap) + { + // Lee: the original form: + // rep_ = static_cast(operator new(sizeof(Rep) + cap)); + // doesn't work in some cases of new being overloaded. Switching + // to the normal allocation, although use an 'int' for systems + // that are overly picky about structure alignment. + const size_type bytesNeeded = sizeof(Rep) + cap; + const size_type intsNeeded = ( bytesNeeded + sizeof(int) - 1 ) / sizeof( int ); + rep_ = reinterpret_cast( new int[ intsNeeded ] ); + + rep_->str[ rep_->size = sz ] = '\0'; + rep_->capacity = cap; + } + else + { + rep_ = &nullrep_; + } + } + + void quit() + { + if (rep_ != &nullrep_) + { + // The rep_ is really an array of ints. (see the allocator, above). + // Cast it back before delete, so the compiler won't incorrectly call destructors. + delete [] ( reinterpret_cast( rep_ ) ); + } + } + + Rep * rep_; + static Rep nullrep_; + +} ; + + +inline bool operator == (const TiXmlString & a, const TiXmlString & b) +{ + return ( a.length() == b.length() ) // optimization on some platforms + && ( strcmp(a.c_str(), b.c_str()) == 0 ); // actual compare +} +inline bool operator < (const TiXmlString & a, const TiXmlString & b) +{ + return strcmp(a.c_str(), b.c_str()) < 0; +} + +inline bool operator != (const TiXmlString & a, const TiXmlString & b) { return !(a == b); } +inline bool operator > (const TiXmlString & a, const TiXmlString & b) { return b < a; } +inline bool operator <= (const TiXmlString & a, const TiXmlString & b) { return !(b < a); } +inline bool operator >= (const TiXmlString & a, const TiXmlString & b) { return !(a < b); } + +inline bool operator == (const TiXmlString & a, const char* b) { return strcmp(a.c_str(), b) == 0; } +inline bool operator == (const char* a, const TiXmlString & b) { return b == a; } +inline bool operator != (const TiXmlString & a, const char* b) { return !(a == b); } +inline bool operator != (const char* a, const TiXmlString & b) { return !(b == a); } + +TiXmlString operator + (const TiXmlString & a, const TiXmlString & b); +TiXmlString operator + (const TiXmlString & a, const char* b); +TiXmlString operator + (const char* a, const TiXmlString & b); + + +/* + TiXmlOutStream is an emulation of std::ostream. It is based on TiXmlString. + Only the operators that we need for TinyXML have been developped. +*/ +class TiXmlOutStream : public TiXmlString +{ +public : + + // TiXmlOutStream << operator. + TiXmlOutStream & operator << (const TiXmlString & in) + { + *this += in; + return *this; + } + + // TiXmlOutStream << operator. + TiXmlOutStream & operator << (const char * in) + { + *this += in; + return *this; + } + +} ; + +#endif // TIXML_STRING_INCLUDED +#endif // TIXML_USE_STL diff --git a/engine/lib/TmxParser/tinyxml/tinyxml.h b/engine/lib/TmxParser/tinyxml/tinyxml.h new file mode 100644 index 000000000..afe8eb888 --- /dev/null +++ b/engine/lib/TmxParser/tinyxml/tinyxml.h @@ -0,0 +1,1802 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TINYXML_INCLUDED +#define TINYXML_INCLUDED + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4530 ) +#pragma warning( disable : 4786 ) +#endif + +#include +#include +#include +#include +#include + +// Help out windows: +#if defined( _DEBUG ) && !defined( DEBUG ) +#define DEBUG +#endif + +#ifdef TIXML_USE_STL + #include + #include + #include + #define TIXML_STRING std::string +#else + #include "tinystr.h" + #define TIXML_STRING TiXmlString +#endif + +// Deprecated library function hell. Compilers want to use the +// new safe versions. This probably doesn't fully address the problem, +// but it gets closer. There are too many compilers for me to fully +// test. If you get compilation troubles, undefine TIXML_SAFE +#define TIXML_SAFE + +#ifdef TIXML_SAFE + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + // Microsoft visual studio, version 2005 and higher. + #define TIXML_SNPRINTF _snprintf_s + #define TIXML_SSCANF sscanf_s + #elif defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + //#pragma message( "Using _sn* functions." ) + #define TIXML_SNPRINTF _snprintf + #define TIXML_SSCANF sscanf + #elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + //#warning( "Using sn* functions." ) + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #else + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #endif +#endif + +class TiXmlDocument; +class TiXmlElement; +class TiXmlComment; +class TiXmlUnknown; +class TiXmlAttribute; +class TiXmlText; +class TiXmlDeclaration; +class TiXmlParsingData; + +const int TIXML_MAJOR_VERSION = 2; +const int TIXML_MINOR_VERSION = 6; +const int TIXML_PATCH_VERSION = 1; + +/* Internal structure for tracking location of items + in the XML file. +*/ +struct TiXmlCursor +{ + TiXmlCursor() { Clear(); } + void Clear() { row = col = -1; } + + int row; // 0 based. + int col; // 0 based. +}; + + +/** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a TiXmlVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its sibilings will be Visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting. + + You should never change the document from a callback. + + @sa TiXmlNode::Accept() +*/ +class TiXmlVisitor +{ +public: + virtual ~TiXmlVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; } + /// Visit a document. + virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; } + + /// Visit an element. + virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; } + /// Visit an element. + virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; } + + /// Visit a declaration + virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; } + /// Visit a text node + virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } + /// Visit a comment node + virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } + /// Visit an unknow node + virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } +}; + +// Only used by Attribute::Query functions +enum +{ + TIXML_SUCCESS, + TIXML_NO_ATTRIBUTE, + TIXML_WRONG_TYPE +}; + + +// Used by the parsing routines. +enum TiXmlEncoding +{ + TIXML_ENCODING_UNKNOWN, + TIXML_ENCODING_UTF8, + TIXML_ENCODING_LEGACY +}; + +const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN; + +/** TiXmlBase is a base class for every class in TinyXml. + It does little except to establish that TinyXml classes + can be printed and provide some utility functions. + + In XML, the document and elements can contain + other elements and other types of nodes. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + A Decleration contains: Attributes (not on tree) + @endverbatim +*/ +class TiXmlBase +{ + friend class TiXmlNode; + friend class TiXmlElement; + friend class TiXmlDocument; + +public: + TiXmlBase() : userData(0) {} + virtual ~TiXmlBase() {} + + /** All TinyXml classes can print themselves to a filestream + or the string class (TiXmlString in non-STL mode, std::string + in STL mode.) Either or both cfile and str can be null. + + This is a formatted print, and will insert + tabs and newlines. + + (For an unformatted stream, use the << operator.) + */ + virtual void Print( FILE* cfile, int depth ) const = 0; + + /** The world does not agree on whether white space should be kept or + not. In order to make everyone happy, these global, static functions + are provided to set whether or not TinyXml will condense all white space + into a single space or not. The default is to condense. Note changing this + value is not thread safe. + */ + static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; } + + /// Return the current white space setting. + static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; } + + /** Return the position, in the original source file, of this node or attribute. + The row and column are 1-based. (That is the first row and first column is + 1,1). If the returns values are 0 or less, then the parser does not have + a row and column value. + + Generally, the row and column value will be set when the TiXmlDocument::Load(), + TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set + when the DOM was created from operator>>. + + The values reflect the initial load. Once the DOM is modified programmatically + (by adding or changing nodes and attributes) the new values will NOT update to + reflect changes in the document. + + There is a minor performance cost to computing the row and column. Computation + can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value. + + @sa TiXmlDocument::SetTabSize() + */ + int Row() const { return location.row + 1; } + int Column() const { return location.col + 1; } ///< See Row() + + void SetUserData( void* user ) { userData = user; } ///< Set a pointer to arbitrary user data. + void* GetUserData() { return userData; } ///< Get a pointer to arbitrary user data. + const void* GetUserData() const { return userData; } ///< Get a pointer to arbitrary user data. + + // Table that returs, for a given lead byte, the total number of bytes + // in the UTF-8 sequence. + static const int utf8ByteTable[256]; + + virtual const char* Parse( const char* p, + TiXmlParsingData* data, + TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0; + + /** Expands entities in a string. Note this should not contian the tag's '<', '>', etc, + or they will be transformed into entities! + */ + static void EncodeString( const TIXML_STRING& str, TIXML_STRING* out ); + + enum + { + TIXML_NO_ERROR = 0, + TIXML_ERROR, + TIXML_ERROR_OPENING_FILE, + TIXML_ERROR_PARSING_ELEMENT, + TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, + TIXML_ERROR_READING_ELEMENT_VALUE, + TIXML_ERROR_READING_ATTRIBUTES, + TIXML_ERROR_PARSING_EMPTY, + TIXML_ERROR_READING_END_TAG, + TIXML_ERROR_PARSING_UNKNOWN, + TIXML_ERROR_PARSING_COMMENT, + TIXML_ERROR_PARSING_DECLARATION, + TIXML_ERROR_DOCUMENT_EMPTY, + TIXML_ERROR_EMBEDDED_NULL, + TIXML_ERROR_PARSING_CDATA, + TIXML_ERROR_DOCUMENT_TOP_ONLY, + + TIXML_ERROR_STRING_COUNT + }; + +protected: + + static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); + + inline static bool IsWhiteSpace( char c ) + { + return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); + } + inline static bool IsWhiteSpace( int c ) + { + if ( c < 256 ) + return IsWhiteSpace( (char) c ); + return false; // Again, only truly correct for English/Latin...but usually works. + } + + #ifdef TIXML_USE_STL + static bool StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ); + static bool StreamTo( std::istream * in, int character, TIXML_STRING * tag ); + #endif + + /* Reads an XML name into the string provided. Returns + a pointer just past the last character of the name, + or 0 if the function has an error. + */ + static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding ); + + /* Reads text. Returns a pointer past the given end tag. + Wickedly complex options, but it keeps the (sensitive) code in one place. + */ + static const char* ReadText( const char* in, // where to start + TIXML_STRING* text, // the string read + bool ignoreWhiteSpace, // whether to keep the white space + const char* endTag, // what ends this text + bool ignoreCase, // whether to ignore case in the end tag + TiXmlEncoding encoding ); // the current encoding + + // If an entity has been found, transform it into a character. + static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding ); + + // Get a character, while interpreting entities. + // The length can be from 0 to 4 bytes. + inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding ) + { + assert( p ); + if ( encoding == TIXML_ENCODING_UTF8 ) + { + *length = utf8ByteTable[ *((const unsigned char*)p) ]; + assert( *length >= 0 && *length < 5 ); + } + else + { + *length = 1; + } + + if ( *length == 1 ) + { + if ( *p == '&' ) + return GetEntity( p, _value, length, encoding ); + *_value = *p; + return p+1; + } + else if ( *length ) + { + //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), + // and the null terminator isn't needed + for( int i=0; p[i] && i<*length; ++i ) { + _value[i] = p[i]; + } + return p + (*length); + } + else + { + // Not valid text. + return 0; + } + } + + // Return true if the next characters in the stream are any of the endTag sequences. + // Ignore case only works for english, and should only be relied on when comparing + // to English words: StringEqual( p, "version", true ) is fine. + static bool StringEqual( const char* p, + const char* endTag, + bool ignoreCase, + TiXmlEncoding encoding ); + + static const char* errorString[ TIXML_ERROR_STRING_COUNT ]; + + TiXmlCursor location; + + /// Field containing a generic user pointer + void* userData; + + // None of these methods are reliable for any language except English. + // Good for approximation, not great for accuracy. + static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding ); + static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding ); + inline static int ToLower( int v, TiXmlEncoding encoding ) + { + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( v < 128 ) return tolower( v ); + return v; + } + else + { + return tolower( v ); + } + } + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); + +private: + TiXmlBase( const TiXmlBase& ); // not implemented. + void operator=( const TiXmlBase& base ); // not allowed. + + struct Entity + { + const char* str; + unsigned int strLength; + char chr; + }; + enum + { + NUM_ENTITY = 5, + MAX_ENTITY_LENGTH = 6 + + }; + static Entity entity[ NUM_ENTITY ]; + static bool condenseWhiteSpace; +}; + + +/** The parent class for everything in the Document Object Model. + (Except for attributes). + Nodes have siblings, a parent, and children. A node can be + in a document, or stand on its own. The type of a TiXmlNode + can be queried, and it can be cast to its more defined type. +*/ +class TiXmlNode : public TiXmlBase +{ + friend class TiXmlDocument; + friend class TiXmlElement; + +public: + #ifdef TIXML_USE_STL + + /** An input stream operator, for every class. Tolerant of newlines and + formatting, but doesn't expect them. + */ + friend std::istream& operator >> (std::istream& in, TiXmlNode& base); + + /** An output stream operator, for every class. Note that this outputs + without any newlines or formatting, as opposed to Print(), which + includes tabs and new lines. + + The operator<< and operator>> are not completely symmetric. Writing + a node to a stream is very well defined. You'll get a nice stream + of output, without any extra whitespace or newlines. + + But reading is not as well defined. (As it always is.) If you create + a TiXmlElement (for example) and read that from an input stream, + the text needs to define an element or junk will result. This is + true of all input streams, but it's worth keeping in mind. + + A TiXmlDocument will read nodes until it reads a root element, and + all the children of that root element. + */ + friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base); + + /// Appends the XML node or attribute to a std::string. + friend std::string& operator<< (std::string& out, const TiXmlNode& base ); + + #endif + + /** The types of XML nodes supported by TinyXml. (All the + unsupported types are picked up by UNKNOWN.) + */ + enum NodeType + { + TINYXML_DOCUMENT, + TINYXML_ELEMENT, + TINYXML_COMMENT, + TINYXML_UNKNOWN, + TINYXML_TEXT, + TINYXML_DECLARATION, + TINYXML_TYPECOUNT + }; + + virtual ~TiXmlNode(); + + /** The meaning of 'value' changes for the specific type of + TiXmlNode. + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + + The subclasses will wrap this function. + */ + const char *Value() const { return value.c_str (); } + + #ifdef TIXML_USE_STL + /** Return Value() as a std::string. If you only use STL, + this is more efficient than calling Value(). + Only available in STL mode. + */ + const std::string& ValueStr() const { return value; } + #endif + + const TIXML_STRING& ValueTStr() const { return value; } + + /** Changes the value of the node. Defined as: + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + void SetValue(const char * _value) { value = _value;} + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Delete all the children of this node. Does not affect 'this'. + void Clear(); + + /// One step up the DOM. + TiXmlNode* Parent() { return parent; } + const TiXmlNode* Parent() const { return parent; } + + const TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children. + TiXmlNode* FirstChild() { return firstChild; } + const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found. + /// The first child of this node with the matching 'value'. Will be null if none found. + TiXmlNode* FirstChild( const char * _value ) { + // Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe) + // call the method, cast the return back to non-const. + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value )); + } + const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children. + TiXmlNode* LastChild() { return lastChild; } + + const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children. + TiXmlNode* LastChild( const char * _value ) { + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value )); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* FirstChild( const std::string& _value ) { return FirstChild (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* LastChild( const std::string& _value ) { return LastChild (_value.c_str ()); } ///< STL std::string form. + #endif + + /** An alternate way to walk the children of a node. + One way to iterate over nodes is: + @verbatim + for( child = parent->FirstChild(); child; child = child->NextSibling() ) + @endverbatim + + IterateChildren does the same thing with the syntax: + @verbatim + child = 0; + while( child = parent->IterateChildren( child ) ) + @endverbatim + + IterateChildren takes the previous child as input and finds + the next one. If the previous child is null, it returns the + first. IterateChildren will return null when done. + */ + const TiXmlNode* IterateChildren( const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) ); + } + + /// This flavor of IterateChildren searches for children with a particular 'value' + const TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + #endif + + /** Add a new node related to this. Adds a child past the LastChild. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertEndChild( const TiXmlNode& addThis ); + + + /** Add a new node related to this. Adds a child past the LastChild. + + NOTE: the node to be added is passed by pointer, and will be + henceforth owned (and deleted) by tinyXml. This method is efficient + and avoids an extra copy, but should be used with care as it + uses a different memory model than the other insert functions. + + @sa InsertEndChild + */ + TiXmlNode* LinkEndChild( TiXmlNode* addThis ); + + /** Add a new node related to this. Adds a child before the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ); + + /** Add a new node related to this. Adds a child after the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ); + + /** Replace a child of this node. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ); + + /// Delete a child of this node. + bool RemoveChild( TiXmlNode* removeThis ); + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling() const { return prev; } + TiXmlNode* PreviousSibling() { return prev; } + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling( const char * ) const; + TiXmlNode* PreviousSibling( const char *_prev ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* PreviousSibling( const std::string& _value ) { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Navigate to a sibling node. + const TiXmlNode* NextSibling() const { return next; } + TiXmlNode* NextSibling() { return next; } + + /// Navigate to a sibling node with the given 'value'. + const TiXmlNode* NextSibling( const char * ) const; + TiXmlNode* NextSibling( const char* _next ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement() const; + TiXmlElement* NextSiblingElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement( const char * ) const; + TiXmlElement* NextSiblingElement( const char *_next ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* NextSiblingElement( const std::string& _value) { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement() const; + TiXmlElement* FirstChildElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() ); + } + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement( const char * _value ) const; + TiXmlElement* FirstChildElement( const char * _value ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* FirstChildElement( const std::string& _value ) { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /** Query the type (as an enumerated value, above) of this node. + The possible types are: DOCUMENT, ELEMENT, COMMENT, + UNKNOWN, TEXT, and DECLARATION. + */ + int Type() const { return type; } + + /** Return a pointer to the Document this node lives in. + Returns null if not in a document. + */ + const TiXmlDocument* GetDocument() const; + TiXmlDocument* GetDocument() { + return const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() ); + } + + /// Returns true if this node has no children. + bool NoChildren() const { return !firstChild; } + + virtual const TiXmlDocument* ToDocument() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlElement* ToElement() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlComment* ToComment() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlUnknown* ToUnknown() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlText* ToText() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + virtual TiXmlDocument* ToDocument() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlElement* ToElement() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlComment* ToComment() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlText* ToText() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + /** Create an exact duplicate of this node and return it. The memory must be deleted + by the caller. + */ + virtual TiXmlNode* Clone() const = 0; + + /** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the TiXmlVisitor interface. + + This is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + TiXmlPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( TiXmlVisitor* visitor ) const = 0; + +protected: + TiXmlNode( NodeType _type ); + + // Copy to the allocated object. Shared functionality between Clone, Copy constructor, + // and the assignment operator. + void CopyTo( TiXmlNode* target ) const; + + #ifdef TIXML_USE_STL + // The real work of the input operator. + virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0; + #endif + + // Figure out what is at *p, and parse it. Returns null if it is not an xml node. + TiXmlNode* Identify( const char* start, TiXmlEncoding encoding ); + + TiXmlNode* parent; + NodeType type; + + TiXmlNode* firstChild; + TiXmlNode* lastChild; + + TIXML_STRING value; + + TiXmlNode* prev; + TiXmlNode* next; + +private: + TiXmlNode( const TiXmlNode& ); // not implemented. + void operator=( const TiXmlNode& base ); // not allowed. +}; + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not TiXmlNodes, since they are not + part of the tinyXML document object model. There are other + suggested ways to look at this problem. +*/ +class TiXmlAttribute : public TiXmlBase +{ + friend class TiXmlAttributeSet; + +public: + /// Construct an empty attribute. + TiXmlAttribute() : TiXmlBase() + { + document = 0; + prev = next = 0; + } + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlAttribute( const std::string& _name, const std::string& _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + #endif + + /// Construct an attribute with a name and value. + TiXmlAttribute( const char * _name, const char * _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + + const char* Name() const { return name.c_str(); } ///< Return the name of this attribute. + const char* Value() const { return value.c_str(); } ///< Return the value of this attribute. + #ifdef TIXML_USE_STL + const std::string& ValueStr() const { return value; } ///< Return the value of this attribute. + #endif + int IntValue() const; ///< Return the value of this attribute, converted to an integer. + double DoubleValue() const; ///< Return the value of this attribute, converted to a double. + + // Get the tinyxml string representation + const TIXML_STRING& NameTStr() const { return name; } + + /** QueryIntValue examines the value string. It is an alternative to the + IntValue() method with richer error checking. + If the value is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. + + A specialized but useful call. Note that for success it returns 0, + which is the opposite of almost all other TinyXml calls. + */ + int QueryIntValue( int* _value ) const; + /// QueryDoubleValue examines the value string. See QueryIntValue(). + int QueryDoubleValue( double* _value ) const; + + void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute. + void SetValue( const char* _value ) { value = _value; } ///< Set the value. + + void SetIntValue( int _value ); ///< Set the value from an integer. + void SetDoubleValue( double _value ); ///< Set the value from a double. + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetName( const std::string& _name ) { name = _name; } + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Get the next sibling attribute in the DOM. Returns null at end. + const TiXmlAttribute* Next() const; + TiXmlAttribute* Next() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() ); + } + + /// Get the previous sibling attribute in the DOM. Returns null at beginning. + const TiXmlAttribute* Previous() const; + TiXmlAttribute* Previous() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() ); + } + + bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; } + bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; } + bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; } + + /* Attribute parsing starts: first letter of the name + returns: the next char after the value end quote + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + // Prints this Attribute to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + + // [internal use] + // Set the document pointer so the attribute can report errors. + void SetDocument( TiXmlDocument* doc ) { document = doc; } + +private: + TiXmlAttribute( const TiXmlAttribute& ); // not implemented. + void operator=( const TiXmlAttribute& base ); // not allowed. + + TiXmlDocument* document; // A pointer back to a document, for error reporting. + TIXML_STRING name; + TIXML_STRING value; + TiXmlAttribute* prev; + TiXmlAttribute* next; +}; + + +/* A class used to manage a group of attributes. + It is only used internally, both by the ELEMENT and the DECLARATION. + + The set can be changed transparent to the Element and Declaration + classes that use it, but NOT transparent to the Attribute + which has to implement a next() and previous() method. Which makes + it a bit problematic and prevents the use of STL. + + This version is implemented with circular lists because: + - I like circular lists + - it demonstrates some independence from the (typical) doubly linked list. +*/ +class TiXmlAttributeSet +{ +public: + TiXmlAttributeSet(); + ~TiXmlAttributeSet(); + + void Add( TiXmlAttribute* attribute ); + void Remove( TiXmlAttribute* attribute ); + + const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + + TiXmlAttribute* Find( const char* _name ) const; + TiXmlAttribute* FindOrCreate( const char* _name ); + +# ifdef TIXML_USE_STL + TiXmlAttribute* Find( const std::string& _name ) const; + TiXmlAttribute* FindOrCreate( const std::string& _name ); +# endif + + +private: + //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), + //*ME: this class must be also use a hidden/disabled copy-constructor !!! + TiXmlAttributeSet( const TiXmlAttributeSet& ); // not allowed + void operator=( const TiXmlAttributeSet& ); // not allowed (as TiXmlAttribute) + + TiXmlAttribute sentinel; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TiXmlElement : public TiXmlNode +{ +public: + /// Construct an element. + TiXmlElement (const char * in_value); + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlElement( const std::string& _value ); + #endif + + TiXmlElement( const TiXmlElement& ); + + void operator=( const TiXmlElement& base ); + + virtual ~TiXmlElement(); + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + */ + const char* Attribute( const char* name ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an integer, + the integer value will be put in the return 'i', if 'i' + is non-null. + */ + const char* Attribute( const char* name, int* i ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an double, + the double value will be put in the return 'd', if 'd' + is non-null. + */ + const char* Attribute( const char* name, double* d ) const; + + /** QueryIntAttribute examines the attribute - it is an alternative to the + Attribute() method with richer error checking. + If the attribute is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. If the attribute + does not exist, then TIXML_NO_ATTRIBUTE is returned. + */ + int QueryIntAttribute( const char* name, int* _value ) const; + /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). + int QueryDoubleAttribute( const char* name, double* _value ) const; + /// QueryFloatAttribute examines the attribute - see QueryIntAttribute(). + int QueryFloatAttribute( const char* name, float* _value ) const { + double d; + int result = QueryDoubleAttribute( name, &d ); + if ( result == TIXML_SUCCESS ) { + *_value = (float)d; + } + return result; + } + + #ifdef TIXML_USE_STL + /// QueryStringAttribute examines the attribute - see QueryIntAttribute(). + int QueryStringAttribute( const char* name, std::string* _value ) const { + const char* cstr = Attribute( name ); + if ( cstr ) { + *_value = std::string( cstr ); + return TIXML_SUCCESS; + } + return TIXML_NO_ATTRIBUTE; + } + + /** Template form of the attribute query which will try to read the + attribute into the specified type. Very easy, very powerful, but + be careful to make sure to call this with the correct type. + + NOTE: This method doesn't work correctly for 'string' types that contain spaces. + + @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE + */ + template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + std::stringstream sstream( node->ValueStr() ); + sstream >> *outValue; + if ( !sstream.fail() ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; + } + + int QueryValueAttribute( const std::string& name, std::string* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + *outValue = node->ValueStr(); + return TIXML_SUCCESS; + } + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char* name, const char * _value ); + + #ifdef TIXML_USE_STL + const std::string* Attribute( const std::string& name ) const; + const std::string* Attribute( const std::string& name, int* i ) const; + const std::string* Attribute( const std::string& name, double* d ) const; + int QueryIntAttribute( const std::string& name, int* _value ) const; + int QueryDoubleAttribute( const std::string& name, double* _value ) const; + + /// STL std::string form. + void SetAttribute( const std::string& name, const std::string& _value ); + ///< STL std::string form. + void SetAttribute( const std::string& name, int _value ); + ///< STL std::string form. + void SetDoubleAttribute( const std::string& name, double value ); + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char * name, int value ); + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetDoubleAttribute( const char * name, double value ); + + /** Deletes an attribute with the given name. + */ + void RemoveAttribute( const char * name ); + #ifdef TIXML_USE_STL + void RemoveAttribute( const std::string& name ) { RemoveAttribute (name.c_str ()); } ///< STL std::string form. + #endif + + const TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element. + TiXmlAttribute* FirstAttribute() { return attributeSet.First(); } + const TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element. + TiXmlAttribute* LastAttribute() { return attributeSet.Last(); } + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the TiXmlText child + and accessing it directly. + + If the first child of 'this' is a TiXmlText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + + WARNING: GetText() accesses a child node - don't become confused with the + similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are + safe type casts on the referenced node. + */ + const char* GetText() const; + + /// Creates a new Element and returns it - the returned element is a copy. + virtual TiXmlNode* Clone() const; + // Print the Element to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: next char past '<' + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlElement* ToElement() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlElement* ToElement() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + + void CopyTo( TiXmlElement* target ) const; + void ClearThis(); // like clear, but initializes 'this' object as well + + // Used to be public [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + /* [internal use] + Reads the "value" of the element -- another element, or text. + This should terminate with the current end tag. + */ + const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + +private: + TiXmlAttributeSet attributeSet; +}; + + +/** An XML comment. +*/ +class TiXmlComment : public TiXmlNode +{ +public: + /// Constructs an empty comment. + TiXmlComment() : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {} + /// Construct a comment from text. + TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { + SetValue( _value ); + } + TiXmlComment( const TiXmlComment& ); + void operator=( const TiXmlComment& base ); + + virtual ~TiXmlComment() {} + + /// Returns a copy of this Comment. + virtual TiXmlNode* Clone() const; + // Write this Comment to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: at the ! of the !-- + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlComment* target ) const; + + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif +// virtual void StreamOut( TIXML_OSTREAM * out ) const; + +private: + +}; + + +/** XML text. A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCDATA() and query it with CDATA(). +*/ +class TiXmlText : public TiXmlNode +{ + friend class TiXmlElement; +public: + /** Constructor for text element. By default, it is treated as + normal, encoded text. If you want it be output as a CDATA text + element, set the parameter _cdata to 'true' + */ + TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + virtual ~TiXmlText() {} + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + #endif + + TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TINYXML_TEXT ) { copy.CopyTo( this ); } + void operator=( const TiXmlText& base ) { base.CopyTo( this ); } + + // Write this text object to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /// Queries whether this represents text using a CDATA section. + bool CDATA() const { return cdata; } + /// Turns on or off a CDATA representation of text. + void SetCDATA( bool _cdata ) { cdata = _cdata; } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlText* ToText() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + /// [internal use] Creates a new Element and returns it. + virtual TiXmlNode* Clone() const; + void CopyTo( TiXmlText* target ) const; + + bool Blank() const; // returns true if all white space and new lines + // [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + bool cdata; // true if this should be input and output as a CDATA style text element +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXml will happily read or write files without a declaration, + however. There are 3 possible attributes to the declaration: + version, encoding, and standalone. + + Note: In this version of the code, the attributes are + handled as special cases, not generic attributes, simply + because there can only be at most 3 and they are always the same. +*/ +class TiXmlDeclaration : public TiXmlNode +{ +public: + /// Construct an empty declaration. + TiXmlDeclaration() : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) {} + +#ifdef TIXML_USE_STL + /// Constructor. + TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ); +#endif + + /// Construct. + TiXmlDeclaration( const char* _version, + const char* _encoding, + const char* _standalone ); + + TiXmlDeclaration( const TiXmlDeclaration& copy ); + void operator=( const TiXmlDeclaration& copy ); + + virtual ~TiXmlDeclaration() {} + + /// Version. Will return an empty string if none was found. + const char *Version() const { return version.c_str (); } + /// Encoding. Will return an empty string if none was found. + const char *Encoding() const { return encoding.c_str (); } + /// Is this a standalone document? + const char *Standalone() const { return standalone.c_str (); } + + /// Creates a copy of this Declaration and returns it. + virtual TiXmlNode* Clone() const; + // Print this declaration to a FILE stream. + virtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlDeclaration* target ) const; + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + + TIXML_STRING version; + TIXML_STRING encoding; + TIXML_STRING standalone; +}; + + +/** Any tag that tinyXml doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into TiXmlUnknowns. +*/ +class TiXmlUnknown : public TiXmlNode +{ +public: + TiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) {} + virtual ~TiXmlUnknown() {} + + TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) { copy.CopyTo( this ); } + void operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); } + + /// Creates a copy of this Unknown and returns it. + virtual TiXmlNode* Clone() const; + // Print this Unknown to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected: + void CopyTo( TiXmlUnknown* target ) const; + + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + +}; + + +/** Always the top level node. A document binds together all the + XML pieces. It can be saved, loaded, and printed to the screen. + The 'value' of a document node is the xml file name. +*/ +class TiXmlDocument : public TiXmlNode +{ +public: + /// Create an empty document, that has no name. + TiXmlDocument(); + /// Create a document with a name. The name of the document is also the filename of the xml. + TiXmlDocument( const char * documentName ); + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlDocument( const std::string& documentName ); + #endif + + TiXmlDocument( const TiXmlDocument& copy ); + void operator=( const TiXmlDocument& copy ); + + virtual ~TiXmlDocument() {} + + /** Load a file using the current document value. + Returns true if successful. Will delete any existing + document data before loading. + */ + bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the current document value. Returns true if successful. + bool SaveFile() const; + /// Load a file using the given filename. Returns true if successful. + bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given filename. Returns true if successful. + bool SaveFile( const char * filename ) const; + /** Load a file using the given FILE*. Returns true if successful. Note that this method + doesn't stream - the entire object pointed at by the FILE* + will be interpreted as an XML file. TinyXML doesn't stream in XML from the current + file location. Streaming may be added in the future. + */ + bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given FILE*. Returns true if successful. + bool SaveFile( FILE* ) const; + + #ifdef TIXML_USE_STL + bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. + { + return LoadFile( filename.c_str(), encoding ); + } + bool SaveFile( const std::string& filename ) const ///< STL std::string version. + { + return SaveFile( filename.c_str() ); + } + #endif + + /** Parse the given null terminated block of xml data. Passing in an encoding to this + method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml + to use that encoding, regardless of what TinyXml might otherwise try to detect. + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + + /** Get the root element -- the only top level element -- of the document. + In well formed XML, there should only be one. TinyXml is tolerant of + multiple elements at the document level. + */ + const TiXmlElement* RootElement() const { return FirstChildElement(); } + TiXmlElement* RootElement() { return FirstChildElement(); } + + /** If an error occurs, Error will be set to true. Also, + - The ErrorId() will contain the integer identifier of the error (not generally useful) + - The ErrorDesc() method will return the name of the error. (very useful) + - The ErrorRow() and ErrorCol() will return the location of the error (if known) + */ + bool Error() const { return error; } + + /// Contains a textual (english) description of the error if one occurs. + const char * ErrorDesc() const { return errorDesc.c_str (); } + + /** Generally, you probably want the error string ( ErrorDesc() ). But if you + prefer the ErrorId, this function will fetch it. + */ + int ErrorId() const { return errorId; } + + /** Returns the location (if known) of the error. The first column is column 1, + and the first row is row 1. A value of 0 means the row and column wasn't applicable + (memory errors, for example, have no row/column) or the parser lost the error. (An + error in the error reporting, in that case.) + + @sa SetTabSize, Row, Column + */ + int ErrorRow() const { return errorLocation.row+1; } + int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow() + + /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol()) + to report the correct values for row and column. It does not change the output + or input in any way. + + By calling this method, with a tab size + greater than 0, the row and column of each node and attribute is stored + when the file is loaded. Very useful for tracking the DOM back in to + the source file. + + The tab size is required for calculating the location of nodes. If not + set, the default of 4 is used. The tabsize is set per document. Setting + the tabsize to 0 disables row/column tracking. + + Note that row and column tracking is not supported when using operator>>. + + The tab size needs to be enabled before the parse or load. Correct usage: + @verbatim + TiXmlDocument doc; + doc.SetTabSize( 8 ); + doc.Load( "myfile.xml" ); + @endverbatim + + @sa Row, Column + */ + void SetTabSize( int _tabsize ) { tabsize = _tabsize; } + + int TabSize() const { return tabsize; } + + /** If you have handled the error, it can be reset with this call. The error + state is automatically cleared if you Parse a new XML block. + */ + void ClearError() { error = false; + errorId = 0; + errorDesc = ""; + errorLocation.row = errorLocation.col = 0; + //errorLocation.last = 0; + } + + /** Write the document to standard out using formatted printing ("pretty print"). */ + void Print() const { } + + /* Write the document to a string using formatted printing ("pretty print"). This + will allocate a character array (new char[]) and return it as a pointer. The + calling code pust call delete[] on the return char* to avoid a memory leak. + */ + //char* PrintToMemory() const; + + /// Print this Document to a FILE stream. + virtual void Print( FILE* cfile, int depth = 0 ) const + { + + } + // [internal use] + void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + + virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + // [internal use] + virtual TiXmlNode* Clone() const; + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + void CopyTo( TiXmlDocument* target ) const; + + bool error; + int errorId; + TIXML_STRING errorDesc; + int tabsize; + TiXmlCursor errorLocation; + bool useMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write. +}; + + +/** + A TiXmlHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + TiXmlElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + TiXmlElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + TiXmlElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + TiXmlElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity + of such code. A TiXmlHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + TiXmlHandle docHandle( &document ); + TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + TiXmlHandle handleCopy = handle; + @endverbatim + + What they should not be used for is iteration: + + @verbatim + int i=0; + while ( true ) + { + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).ToElement(); + if ( !child ) + break; + // do something + ++i; + } + @endverbatim + + It seems reasonable, but it is in fact two embedded while loops. The Child method is + a linear walk to find the element, so this code would iterate much more than it needs + to. Instead, prefer: + + @verbatim + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).ToElement(); + + for( child; child; child=child->NextSiblingElement() ) + { + // do something + } + @endverbatim +*/ +class TiXmlHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + TiXmlHandle( TiXmlNode* _node ) { this->node = _node; } + /// Copy constructor + TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } + TiXmlHandle operator=( const TiXmlHandle& ref ) { this->node = ref.node; return *this; } + + /// Return a handle to the first child node. + TiXmlHandle FirstChild() const; + /// Return a handle to the first child node with the given name. + TiXmlHandle FirstChild( const char * value ) const; + /// Return a handle to the first child element. + TiXmlHandle FirstChildElement() const; + /// Return a handle to the first child element with the given name. + TiXmlHandle FirstChildElement( const char * value ) const; + + /** Return a handle to the "index" child with the given name. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( const char* value, int index ) const; + /** Return a handle to the "index" child. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( int index ) const; + /** Return a handle to the "index" child element with the given name. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( const char* value, int index ) const; + /** Return a handle to the "index" child element. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( int index ) const; + + #ifdef TIXML_USE_STL + TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); } + TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); } + + TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); } + TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); } + #endif + + /** Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* ToNode() const { return node; } + /** Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* ToElement() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); } + /** Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* ToText() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); } + /** Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* ToUnknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); } + + /** @deprecated use ToNode. + Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* Node() const { return ToNode(); } + /** @deprecated use ToElement. + Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* Element() const { return ToElement(); } + /** @deprecated use ToText() + Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* Text() const { return ToText(); } + /** @deprecated use ToUnknown() + Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* Unknown() const { return ToUnknown(); } + +private: + TiXmlNode* node; +}; + + +/** Print to memory functionality. The TiXmlPrinter is useful when you need to: + + -# Print to memory (especially in non-STL mode) + -# Control formatting (line endings, etc.) + + When constructed, the TiXmlPrinter is in its default "pretty printing" mode. + Before calling Accept() you can call methods to control the printing + of the XML document. After TiXmlNode::Accept() is called, the printed document can + be accessed via the CStr(), Str(), and Size() methods. + + TiXmlPrinter uses the Visitor API. + @verbatim + TiXmlPrinter printer; + printer.SetIndent( "\t" ); + + doc.Accept( &printer ); + fprintf( stdout, "%s", printer.CStr() ); + @endverbatim +*/ +class TiXmlPrinter : public TiXmlVisitor +{ +public: + TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ), + buffer(), indent( " " ), lineBreak( "\n" ) {} + + virtual bool VisitEnter( const TiXmlDocument& doc ); + virtual bool VisitExit( const TiXmlDocument& doc ); + + virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ); + virtual bool VisitExit( const TiXmlElement& element ); + + virtual bool Visit( const TiXmlDeclaration& declaration ); + virtual bool Visit( const TiXmlText& text ); + virtual bool Visit( const TiXmlComment& comment ); + virtual bool Visit( const TiXmlUnknown& unknown ); + + /** Set the indent characters for printing. By default 4 spaces + but tab (\t) is also useful, or null/empty string for no indentation. + */ + void SetIndent( const char* _indent ) { indent = _indent ? _indent : "" ; } + /// Query the indention string. + const char* Indent() { return indent.c_str(); } + /** Set the line breaking string. By default set to newline (\n). + Some operating systems prefer other characters, or can be + set to the null/empty string for no indenation. + */ + void SetLineBreak( const char* _lineBreak ) { lineBreak = _lineBreak ? _lineBreak : ""; } + /// Query the current line breaking string. + const char* LineBreak() { return lineBreak.c_str(); } + + /** Switch over to "stream printing" which is the most dense formatting without + linebreaks. Common when the XML is needed for network transmission. + */ + void SetStreamPrinting() { indent = ""; + lineBreak = ""; + } + /// Return the result. + const char* CStr() { return buffer.c_str(); } + /// Return the length of the result string. + size_t Size() { return buffer.size(); } + + #ifdef TIXML_USE_STL + /// Return the result. + const std::string& Str() { return buffer; } + #endif + +private: + void DoIndent() { + for( int i=0; i), ASSET_ID_FIELD_PREFIX ) + +//----------------------------------------------------------------------------- + +ConsoleGetType( TypeTmxMapAssetPtr ) +{ + // Fetch asset Id. + return (*((AssetPtr*)dptr)).getAssetId(); +} + +//----------------------------------------------------------------------------- + +ConsoleSetType( TypeTmxMapAssetPtr ) +{ + // Was a single argument specified? + if( argc == 1 ) + { + // Yes, so fetch field value. + const char* pFieldValue = argv[0]; + + // Fetch asset pointer. + AssetPtr* pAssetPtr = dynamic_cast*>((AssetPtrBase*)(dptr)); + + // Is the asset pointer the correct type? + if ( pAssetPtr == NULL ) + { + // No, so fail. + Con::warnf( "(TypeTmxMapAssetPtr) - Failed to set asset Id '%d'.", pFieldValue ); + return; + } + + // Set asset. + pAssetPtr->setAssetId( pFieldValue ); + + return; + } + + // Warn. + Con::warnf( "(TypeTmxMapAssetPtr) - Cannot set multiple args to a single asset." ); +} + +//------------------------------------------------------------------------------ + +IMPLEMENT_CONOBJECT(TmxMapAsset); + +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ + +TmxMapAsset::TmxMapAsset() : mMapFile(StringTable->EmptyString), + mParser(NULL) + +{ +} + +//------------------------------------------------------------------------------ + +TmxMapAsset::~TmxMapAsset() +{ + if (mParser) + { + delete mParser; + } +} + +//------------------------------------------------------------------------------ + +void TmxMapAsset::initPersistFields() +{ + // Call parent. + Parent::initPersistFields(); + + // Fields. + addProtectedField("MapFile", TypeAssetLooseFilePath, Offset(mMapFile, TmxMapAsset), &setMapFile, &getMapFile, &defaultProtectedWriteFn, ""); + +} + +//------------------------------------------------------------------------------ + +bool TmxMapAsset::onAdd() +{ + // Call Parent. + if (!Parent::onAdd()) + return false; + + return true; +} + +//------------------------------------------------------------------------------ + +void TmxMapAsset::onRemove() +{ + // Call Parent. + Parent::onRemove(); +} + + +//------------------------------------------------------------------------------ + +void TmxMapAsset::setMapFile( const char* pMapFile ) +{ + // Sanity! + AssertFatal( pMapFile != NULL, "Cannot use a NULL map file." ); + + // Fetch image file. + pMapFile = StringTable->insert( pMapFile ); + + // Ignore no change, + if ( pMapFile == mMapFile ) + return; + + // Update. + mMapFile = getOwned() ? expandAssetFilePath( pMapFile ) : StringTable->insert( pMapFile ); + + // Refresh the asset. + refreshAsset(); +} + +//------------------------------------------------------------------------------ + +void TmxMapAsset::initializeAsset( void ) +{ + // Call parent. + Parent::initializeAsset(); + + // Ensure the image-file is expanded. + mMapFile = expandAssetFilePath( mMapFile ); + + calculateMap(); +} + +//------------------------------------------------------------------------------ + +void TmxMapAsset::onAssetRefresh( void ) +{ + // Ignore if not yet added to the sim. + if ( !isProperlyAdded() ) + return; + + // Call parent. + Parent::onAssetRefresh(); + + calculateMap(); +} + +//----------------------------------------------------------------------------- + +void TmxMapAsset::onTamlPreWrite( void ) +{ + // Call parent. + Parent::onTamlPreWrite(); + + // Ensure the image-file is collapsed. + mMapFile = collapseAssetFilePath( mMapFile ); +} + +//----------------------------------------------------------------------------- + +void TmxMapAsset::onTamlPostWrite( void ) +{ + // Call parent. + Parent::onTamlPostWrite(); + + // Ensure the image-file is expanded. + mMapFile = expandAssetFilePath( mMapFile ); +} + +//---------------------------------------------------------------------------- + +void TmxMapAsset::calculateMap() +{ + if (mParser) + { + delete mParser; + mParser = NULL; + } + + mParser = new Tmx::Map(); + mParser->ParseFile( mMapFile ); + + if (mParser->HasError()) + { + // No, so warn. + Con::warnf( "Map '%' could not be parsed: error code (%d) - %s.", getAssetId(), mParser->GetErrorCode(), mParser->GetErrorText().c_str() ); + delete mParser; + mParser = NULL; + return; + } +} + +bool TmxMapAsset::isAssetValid() +{ + return (mParser != NULL); +} + +StringTableEntry TmxMapAsset::getOrientation() +{ + if (!isAssetValid()) return StringTable->EmptyString; + + switch(mParser->GetOrientation()) + { + case Tmx::TMX_MO_ORTHOGONAL: + { + return StringTable->insert("ortho"); + } + case Tmx::TMX_MO_ISOMETRIC: + { + return StringTable->insert("iso"); + } + case Tmx::TMX_MO_STAGGERED: + { + return StringTable->insert("stag"); + } + default: + { + return StringTable->EmptyString; + } + } +} + +int TmxMapAsset::getLayerCount() +{ + if (!isAssetValid()) return 0; + + return mParser->GetNumLayers(); +} + +Tmx::Map* TmxMapAsset::getParser() +{ + if (!isAssetValid()) return NULL; + + return mParser; +} \ No newline at end of file diff --git a/engine/source/2d/assets/TmxMapAsset.h b/engine/source/2d/assets/TmxMapAsset.h new file mode 100644 index 000000000..1158a447d --- /dev/null +++ b/engine/source/2d/assets/TmxMapAsset.h @@ -0,0 +1,77 @@ +#ifndef _TMXMAP_ASSET_H_ +#define _TMXMAP_ASSET_H_ + +#ifndef _ASSET_PTR_H_ +#include "assets/assetPtr.h" +#endif + +#include + +//----------------------------------------------------------------------------- + +DefineConsoleType( TypeTmxMapAssetPtr ) + +//----------------------------------------------------------------------------- + +class TmxMapAsset : public AssetBase +{ + +////T2D SIM/CONSOLE setup//////////////////// +private: + typedef AssetBase Parent; + +public: + + TmxMapAsset(); + virtual ~TmxMapAsset(); + + /// Core. + static void initPersistFields(); + virtual bool onAdd(); + virtual void onRemove(); + + /// Declare Console Object. + DECLARE_CONOBJECT(TmxMapAsset); +////////////////////////////////////////////// + + +private: + + /// Configuration. + StringTableEntry mMapFile; + +public: + + void setMapFile( const char* pMapFile ); + inline StringTableEntry getMapFile( void ) const { return mMapFile; }; + + + StringTableEntry getOrientation(); + int getLayerCount(); + + Tmx::Map* getParser(); + +private: + + Tmx::Map* mParser; + + void calculateMap( void ); + virtual bool isAssetValid(); + +protected: + virtual void initializeAsset( void ); + virtual void onAssetRefresh( void ); + + /// Taml callbacks. + virtual void onTamlPreWrite( void ); + virtual void onTamlPostWrite( void ); + + +protected: + + static bool setMapFile( void* obj, const char* data ) { static_cast(obj)->setMapFile(data); return false; } + static const char* getMapFile(void* obj, const char* data) { return static_cast(obj)->getMapFile(); } + +}; + +#endif //_TMXMAP_ASSET_H_ \ No newline at end of file diff --git a/engine/source/2d/assets/TmxMapAsset_ScriptBinding.h b/engine/source/2d/assets/TmxMapAsset_ScriptBinding.h new file mode 100644 index 000000000..f31e833a6 --- /dev/null +++ b/engine/source/2d/assets/TmxMapAsset_ScriptBinding.h @@ -0,0 +1,26 @@ + +ConsoleMethod(TmxMapAsset, setMapFile, void, 3, 3, "(MapFile) Sets the map file (tmx file).\n" + "@return No return value.") +{ + object->setMapFile( argv[2] ); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(TmxMapAsset, getMapFile, const char*, 2, 2, "() Gets the map file.\n" + "@return Returns the tmx map file.") +{ + return object->getMapFile(); +} + +ConsoleMethod(TmxMapAsset, getOrientation, const char*, 2, 2, "() Gets the map orientation.\n" + "@return Returns the tmx map orientation layout.") +{ + return object->getOrientation(); +} + +ConsoleMethod(TmxMapAsset, getLayerCount, S32, 2, 2, "() Gets the number of tile layers.\n" + "@return Returns the numer of tile layers in the map.") +{ + return object->getLayerCount(); +} \ No newline at end of file diff --git a/engine/source/2d/sceneobject/SceneObject.h b/engine/source/2d/sceneobject/SceneObject.h index 0d5c790cf..d924d07aa 100755 --- a/engine/source/2d/sceneobject/SceneObject.h +++ b/engine/source/2d/sceneobject/SceneObject.h @@ -373,7 +373,7 @@ class SceneObject : /// Body. virtual ePhysicsProxyType getPhysicsProxyType( void ) const { return PhysicsProxy::PHYSIC_PROXY_SCENEOBJECT; } inline b2Body* getBody( void ) const { return mpBody; } - void setBodyType( const b2BodyType type ); + virtual void setBodyType( const b2BodyType type ); inline b2BodyType getBodyType(void) const { if ( mpScene ) return mpBody->GetType(); else return mBodyDefinition.type; } inline void setActive( const bool active ) { if ( mpScene ) mpBody->SetActive( active ); else mBodyDefinition.active = active; } inline bool getActive(void) const { if ( mpScene ) return mpBody->IsActive(); else return mBodyDefinition.active; } diff --git a/engine/source/2d/sceneobject/TmxMapSprite.cpp b/engine/source/2d/sceneobject/TmxMapSprite.cpp new file mode 100644 index 000000000..1f0fa4265 --- /dev/null +++ b/engine/source/2d/sceneobject/TmxMapSprite.cpp @@ -0,0 +1,715 @@ +#include "TmxMapSprite.h" + +#include "assets/assetManager.h" +#include + +//script bindings +#include "TmxMapSprite_ScriptBinding.h" + + + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(TmxMapSprite); + +//------------------------------------------------------------------------------ + +TmxMapSprite::TmxMapSprite() : mMapPixelToMeterFactor(0.03f), + mLastTileAsset(StringTable->EmptyString), + mLastTileImage(StringTable->EmptyString) +{ + mAutoSizing = true; + setBodyType(b2_staticBody); +} + +//------------------------------------------------------------------------------ + +TmxMapSprite::~TmxMapSprite() +{ + ClearMap(); +} + +void TmxMapSprite::initPersistFields() +{ + // Call parent. + Parent::initPersistFields(); + + addProtectedField("Map", TypeTmxMapAssetPtr, Offset(mMapAsset, TmxMapSprite), &setMap, &getMap, &writeMap, ""); + addProtectedField("MapToMeterFactor", TypeF32, Offset(mMapPixelToMeterFactor, TmxMapSprite), &setMapToMeterFactor, &getMapToMeterFactor, &writeMapToMeterFactor, ""); +} + +bool TmxMapSprite::onAdd() +{ + auto layerIdx = mLayers.begin(); + for (layerIdx; layerIdx != mLayers.end(); ++layerIdx) + { + + (*layerIdx)->onAdd(); + } + auto objectsIdx = mObjects.begin(); + for (objectsIdx; objectsIdx != mObjects.end(); ++objectsIdx){ + (*objectsIdx)->onAdd(); + } + return (Parent::onAdd()); + +} + +void TmxMapSprite::onRemove() +{ + auto layerIdx = mLayers.begin(); + for (layerIdx; layerIdx != mLayers.end(); ++layerIdx) + { + + // (*layerIdx)->onRemove(); + } + auto objectsIdx = mObjects.begin(); + for (objectsIdx; objectsIdx != mObjects.end(); ++objectsIdx){ + // (*objectsIdx)->onRemove(); + } + Parent::onRemove(); +} + +void TmxMapSprite::OnRegisterScene(Scene* pScene) +{ + + Parent::OnRegisterScene(pScene); + auto layerIdx = mLayers.begin(); + for(layerIdx; layerIdx != mLayers.end(); ++layerIdx) + { + + pScene->addToScene(*layerIdx); + } + auto objectsIdx = mObjects.begin(); + for (objectsIdx; objectsIdx != mObjects.end(); ++objectsIdx){ + pScene->addToScene(*objectsIdx); + } + +} + +void TmxMapSprite::OnUnregisterScene( Scene* pScene ) +{ + Parent::OnUnregisterScene(pScene); + + auto layerIdx = mLayers.begin(); + for(layerIdx; layerIdx != mLayers.end(); ++layerIdx) + { + //(*layerIdx)->OnUnregisterScene(pScene); + } + auto objectsIdx = mObjects.begin(); + for (objectsIdx; objectsIdx != mObjects.end(); ++objectsIdx){ + //(*objectsIdx)->OnUnregisterScene(pScene); + } +} + +void TmxMapSprite::setPosition( const Vector2& position ) +{ + Parent::setPosition(position); + auto layerIdx = mLayers.begin(); + for(layerIdx; layerIdx != mLayers.end(); ++layerIdx) + { + (*layerIdx)->setPosition(position); + } + +} + +void TmxMapSprite::ClearMap() +{ + auto layerIdx = mLayers.begin(); + for(layerIdx; layerIdx != mLayers.end(); ++layerIdx) + { + //CompositeSprite* sprite = *layerIdx; + //delete sprite; + } + mLayers.clear(); + auto objectsIdx = mObjects.begin(); + for (objectsIdx; objectsIdx != mObjects.end(); ++objectsIdx){ + SceneObject* object = *objectsIdx; + delete object; + } + mObjects.clear(); +} +void TmxMapSprite::BuildMap() +{ + // Debug Profiling. + PROFILE_SCOPE(TmxMapSprite_BuildMap); + + + ClearMap(); + auto mapParser = mMapAsset->getParser(); + + F32 tileWidth = static_cast(mapParser->GetTileWidth()); + F32 tileHeight = static_cast(mapParser->GetTileHeight()); + F32 halfTileHeight = static_cast(tileHeight * 0.5); + + F32 width = (mapParser->GetWidth() * tileWidth); + F32 height = (mapParser->GetHeight() * tileHeight); + + F32 originY = height / 2 - halfTileHeight; + F32 originX = 0; + + Vector2 tileSize(tileWidth, tileHeight); + Vector2 originSize(originX, originY); + + Tmx::MapOrientation orient = mapParser->GetOrientation(); + + auto layerItr = mapParser->GetLayers().begin(); + for(layerItr; layerItr != mapParser->GetLayers().end(); ++layerItr) + { + Tmx::Layer* layer = *layerItr; + + //use default layer number, unless someone realy wants to override it. + S32 layerNumber = 31 - layer->GetZOrder(); + if (layer->GetProperties().HasProperty(TMX_MAP_LAYER_ID_PROP)) + layerNumber = layer->GetProperties().GetNumericProperty(TMX_MAP_LAYER_ID_PROP); + + auto compSprite = CreateLayer(layerNumber, orient == Tmx::TMX_MO_ISOMETRIC); + + U32 xTiles = mapParser->GetWidth(); + U32 yTiles = mapParser->GetHeight(); + for(U32 x=0; x < xTiles; ++x) + { + for (U32 y=0; y < yTiles; ++y) + { + const Tmx::MapTile& tile = layer->GetTile(x, y); + if (tile.tilesetId == TMX_MAP_NO_TILE) continue; //no tile at this location + + const Tmx::Tileset* tset = mapParser->GetTileset(tile.tilesetId); + + StringTableEntry assetName = GetTilesetAsset(tset); + if (assetName == StringTable->EmptyString) continue; + + U32 localFrame = tile.id; + F32 spriteHeight = static_cast( tset->GetTileHeight() ); + F32 spriteWidth = static_cast( tset->GetTileWidth() ); + + //F32 heightOffset = (spriteHeight - tileHeight) / 2; + //F32 widthOffset = (spriteWidth - tileWidth) / 2; + auto thisTilePosition = Vector2(static_cast(x), static_cast(yTiles - y)); + auto bId = compSprite->addSprite( SpriteBatchItem::LogicalPosition( thisTilePosition.scriptThis()) ); + compSprite->selectSpriteId(bId); + compSprite->setSpriteImage(assetName, localFrame); + auto thisTileSize = Vector2(spriteWidth * mMapPixelToMeterFactor, spriteHeight * mMapPixelToMeterFactor); + compSprite->setSpriteSize( thisTileSize); + + compSprite->setSpriteFlipX(tile.flippedHorizontally); + compSprite->setSpriteFlipY(tile.flippedVertically); + const Tmx::Tile* tileobj= tset->GetTile(tile.id); + if (tileobj) { + if (tileobj->GetObjectGroup().GetNumObjects() > 0) { + auto thisTileAbsolutePosition = Vector2(thisTilePosition.x * spriteWidth, (yTiles * spriteHeight) - (thisTilePosition.y * spriteHeight)); + addObjectGroup(&tset->GetTile(tile.id)->GetObjectGroup(), &thisTileAbsolutePosition); + } + } + + } + } + + } + + //now do object groups... + auto groupIdx = mapParser->GetObjectGroups().begin(); + + for(groupIdx; groupIdx != mapParser->GetObjectGroups().end(); ++groupIdx) + { + auto groupLayer = *groupIdx; + addObjectGroup(groupLayer, NULL); + + } +} + +void TmxMapSprite::addObjectGroup(const Tmx::ObjectGroup* objectGroup, const Vector2* offset ) { + auto mapParser = mMapAsset->getParser(); + Tmx::MapOrientation orient = mapParser->GetOrientation(); + //use default layer number, unless someone realy wants to override it. + S32 layerNumber = 31 - objectGroup->GetZOrder(); + if (objectGroup->GetProperties().HasProperty(TMX_MAP_LAYER_ID_PROP)) + layerNumber = objectGroup->GetProperties().GetNumericProperty(TMX_MAP_LAYER_ID_PROP); + + auto compSprite = CreateLayer(layerNumber, orient == Tmx::TMX_MO_ISOMETRIC); + + auto objectIdx = objectGroup->GetObjects().begin(); + for (objectIdx; objectIdx != objectGroup->GetObjects().end(); ++objectIdx) + { + auto object = *objectIdx; + //do a number of things. + //try it as a script reference + if (object->GetType() == TMX_MAP_SCRIPT_OBJECT || objectGroup->GetName() == TMX_MAP_SCRIPT_OBJECT) { + addScriptObject(object, layerNumber, offset); + continue; //don't allow script refs to have a sprite or physics presence... + } + //try it as a tile + auto gid = object->GetGid(); + auto tileSet = mapParser->FindTileset(gid); + if (tileSet != NULL) { + addObjectAsSprite(tileSet, object, mapParser, gid, compSprite, offset); + continue; + } + //try it as a physics object. + if (object->GetName() == TMX_MAP_COLLISION_OBJECT || object->GetType() == TMX_MAP_COLLISION_OBJECT || objectGroup->GetName() == TMX_MAP_COLLISION_OBJECT) { + //try to add some physics bodies... + + if (object->GetPolyline() != NULL) { + addPhysicsPolyLine(object, compSprite, offset); + } + else if (object->GetPolygon() != NULL) { + addPhysicsPolygon(object, compSprite, offset); + } + else if (object->GetEllipse() != NULL) { + addPhysicsEllipse(object, compSprite, offset); + } + else { + //must be a rectangle. + addPhysicsRectangle(object, compSprite, offset); + } + continue; + } + } +} + +void TmxMapSprite::addScriptObject(const Tmx::Object* object, S32 layerNumber, const Vector2* offset) { + std::string result; + if (object->GetProperties().HasProperty(TMX_MAP_SCRIPT_FUNCTION)) { + result = Con::evaluatef("%s();", object->GetProperties().GetLiteralProperty(TMX_MAP_SCRIPT_FUNCTION).c_str()); + } + else if (object->GetName() == TMX_MAP_SCRIPT_FUNCTION) { + result = Con::evaluatef("%s();", object->GetType().c_str()); + } + else if (object->GetType() == TMX_MAP_SCRIPT_FUNCTION) { + result = Con::evaluatef("%s();", object->GetName().c_str()); + } + //Con::printf("Executed a script and got %s back", result.c_str()); + + //set the object's layer... + char layer[128]; + dItoa(layerNumber, layer); + Con::evaluatef("%s.SceneLayer = %s;", result.c_str(), layer); + //set the object's position... + Vector2 pixLoc = Vector2( + static_cast(object->GetX()), + static_cast(object->GetY()) + ); + if (offset){ + pixLoc.x += offset->x; + pixLoc.y += offset->y; + } + const char* loc = PixelToCoord(pixLoc).scriptThis(); + Con::evaluatef("%s.position = \"%s\";", result.c_str(), loc); + + //assign the rest of the properties from TMX + std::map list = object->GetProperties().GetList(); + for (auto iter = list.begin(); + iter != list.end(); + iter++) { + if ( + iter->first != std::string(TMX_MAP_SCRIPT_FUNCTION) + && + iter->second != std::string(TMX_MAP_SCRIPT_FUNCTION) + ) { + //Con::printf("generating code to set property %s of %s to value %s", iter->first.c_str(), result.c_str(), iter->second.c_str()); + Con::evaluatef("%s.%s = %s;", result.c_str(), iter->first.c_str(), iter->second.c_str()); + } + } +} + +void TmxMapSprite::addObjectAsSprite(const Tmx::Tileset* tileSet, Tmx::Object* object, Tmx::Map * mapParser, U32 gid, CompositeSprite* compSprite, const Vector2* offset){ + + F32 objectWidth = static_cast(object->GetWidth()); + F32 objectHeight = static_cast(object->GetHeight()); + Vector2 vTile = + Vector2 + ( + static_cast(object->GetX()), + static_cast(object->GetY()) + ); + + Vector2 pos = PixelToCoord( vTile); + + if (offset) { + pos.x += offset->x; + pos.y += offset->y; + } + + S32 frameNumber = gid - tileSet->GetFirstGid(); + StringTableEntry assetName = GetTilesetAsset(tileSet); + + auto bId = compSprite->addSprite( SpriteBatchItem::LogicalPosition( pos.scriptThis()) ); + compSprite->selectSpriteId(bId); + compSprite->setSpriteImage(assetName, frameNumber); + compSprite->setSpriteSize(Vector2( objectWidth * mMapPixelToMeterFactor, objectHeight * mMapPixelToMeterFactor)); + compSprite->setSpriteFlipX(false); + compSprite->setSpriteFlipY(false); + + +} + +void TmxMapSprite::addPhysicsPolyLine(Tmx::Object* object, CompositeSprite* compSprite, const Vector2* offset){ + auto mapParser = mMapAsset->getParser(); + F32 tileWidth = static_cast(mapParser->GetTileWidth()); + F32 tileHeight = static_cast(mapParser->GetTileHeight()); + F32 mapHeight = (mapParser->GetHeight() * tileHeight); + Vector2 tileSize(tileWidth, tileHeight); + Tmx::MapOrientation orient = mapParser->GetOrientation(); + b2Vec2 origin = b2Vec2 + ( + static_cast(object->GetX()), + static_cast(object->GetY()) + ); + if (offset){ + origin.x += offset->x; + origin.y += offset->y; + } + + + //weird additions and subtractions in this area due to the fact that this engine uses bottom->left as origin, and TMX uses top->right. + //it's hacky, but it works. + Vector2 heightoffset = Vector2( origin.x, mapHeight-(origin.y)); + Vector2 tilecoord = CoordToTile(heightoffset, tileSize, orient == Tmx::TMX_MO_ISOMETRIC); + b2Vec2 nativePoint = tilecoord; + nativePoint += b2Vec2(-tileWidth/2,tileHeight/2); + // nativePoint += b2Vec2(object->GetWidth()/2.0f, -(object->GetHeight()/2.0f)); //adjust for tmx defining from bottom left point while t2d defines from center... + // nativePoint *= mMapPixelToMeterFactor; + const Tmx::Polyline* line = object->GetPolyline(); + + U32 points = line->GetNumPoints(); + if (points > 0){ + //find object height: + F32 min = line->GetPoint(0).y; + F32 max = line->GetPoint(0).y; + for (U32 i = 0; i < points; i++){ + Tmx::Point point = line->GetPoint(i); + if (point.y > max) + max = point.y; + if (point.y < min) + min = point.y; + } + + F32 polylineHeight = max - min; + for (U32 i = 0; i < points-1; i++){ + + Tmx::Point first = line->GetPoint(i); + Tmx::Point second = line->GetPoint(i+1); + + Vector2 firstvec = Vector2(nativePoint.x + first.x, nativePoint.y - first.y); + Vector2 secondvec = Vector2(nativePoint.x + second.x, nativePoint.y - second.y); + + compSprite->createEdgeCollisionShape(firstvec * mMapPixelToMeterFactor, secondvec * mMapPixelToMeterFactor, false, false, Vector2(), Vector2()); + + } + } + + + +} + +void TmxMapSprite::addPhysicsPolygon(Tmx::Object* object, CompositeSprite* compSprite, const Vector2* offset){ + auto mapParser = mMapAsset->getParser(); + F32 tileWidth = static_cast(mapParser->GetTileWidth()); + F32 tileHeight = static_cast(mapParser->GetTileHeight()); + F32 height = (mapParser->GetHeight() * tileHeight); + Vector2 tileSize(tileWidth, tileHeight); + Tmx::MapOrientation orient = mapParser->GetOrientation(); + const Tmx::Polygon* line = object->GetPolygon(); + int points = line->GetNumPoints(); + b2Vec2* pointsdata = new b2Vec2[points]; + b2Vec2 origin = b2Vec2 + ( + static_cast(object->GetX()), + static_cast(object->GetY()) + ); + if (offset){ + origin.x += offset->x; + origin.y += offset->y; + } + for (int i = 0; i < points; i++) + { + Tmx::Point tmxPoint = line->GetPoint(i); + + //weird additions and subtractions in this area due to the fact that this engine uses bottom->left as origin, and TMX uses top->right. + //it's hacky, but it works. + Vector2 isocoord = Vector2( tmxPoint.x + origin.x, height-(origin.y+tmxPoint.y)); + Vector2 tilecoord = CoordToTile(isocoord,tileSize,orient == Tmx::TMX_MO_ISOMETRIC); + b2Vec2 nativePoint = tilecoord; + nativePoint += b2Vec2(-tileWidth/2,tileHeight/2); + nativePoint *= mMapPixelToMeterFactor; + pointsdata[i] = nativePoint; + + } + compSprite->createPolygonCollisionShape(points, pointsdata); + delete[] pointsdata; +} + +void TmxMapSprite::addPhysicsEllipse(Tmx::Object* object, CompositeSprite* compSprite, const Vector2* offset){ + auto mapParser = mMapAsset->getParser(); + F32 tileWidth = static_cast(mapParser->GetTileWidth()); + F32 tileHeight = static_cast(mapParser->GetTileHeight()); + F32 mapHeight = (mapParser->GetHeight() * tileHeight); + Vector2 tileSize(tileWidth, tileHeight); + Tmx::MapOrientation orient = mapParser->GetOrientation(); + const Tmx::Ellipse* ellipse = object->GetEllipse(); + b2Vec2 origin = b2Vec2 + ( + static_cast(ellipse->GetCenterX()), + static_cast(ellipse->GetCenterY()) + ); + if (offset){ + origin.x += offset->x; + origin.y += offset->y; + } + + F32 ellipseHeight = static_cast(ellipse->GetRadiusY()); + F32 ellipseWidth = static_cast(ellipse->GetRadiusX()); + + //tmx allows arbitrary ellipses, t2d only lets us use circles. approximate ellipses with a height:width ratio of 1/2 - 2with a circle, otherwise fail. + F32 ratio = ellipseHeight/ellipseWidth; + if (ratio < 0.5f || ratio > 2.0f){ + Con::warnf("TMX map has an ellipse collision body with H:W ratio outside of our approximatable bounds. Use a different shape."); + return; + } + + + //weird additions and subtractions in this area due to the fact that this engine uses bottom->left as origin, and TMX uses top->right. + //it's hacky, but it works. + Vector2 originOffset = Vector2( origin.x, mapHeight-(origin.y)); + Vector2 tilecoord = CoordToTile(originOffset ,tileSize,orient == Tmx::TMX_MO_ISOMETRIC); + b2Vec2 nativePoint = tilecoord; + nativePoint += b2Vec2(-tileWidth/2,tileHeight/2); + nativePoint *= mMapPixelToMeterFactor; + compSprite->createCircleCollisionShape( (ellipseHeight > ellipseWidth ? ellipseHeight : ellipseWidth ) * mMapPixelToMeterFactor, nativePoint); + + +} + +void TmxMapSprite::addPhysicsRectangle(Tmx::Object* object, CompositeSprite* compSprite, const Vector2* offset){ + auto mapParser = mMapAsset->getParser(); + F32 tileWidth = static_cast(mapParser->GetTileWidth()); + F32 tileHeight = static_cast(mapParser->GetTileHeight()); + F32 mapHeight = (mapParser->GetHeight() * tileHeight); + Vector2 tileSize(tileWidth, tileHeight); + Tmx::MapOrientation orient = mapParser->GetOrientation(); + b2Vec2 origin = b2Vec2 + ( + static_cast(object->GetX()), + static_cast(object->GetY()) + ); + if (offset){ + origin.x += offset->x; + origin.y += offset->y; + } + + + //weird additions and subtractions in this area due to the fact that this engine uses bottom->left as origin, and TMX uses top->right. + //it's hacky, but it works. + Vector2 heightoffset = Vector2( origin.x, mapHeight-(origin.y)); + Vector2 tilecoord = CoordToTile(heightoffset, tileSize, orient == Tmx::TMX_MO_ISOMETRIC); + b2Vec2 nativePoint = tilecoord; + nativePoint += b2Vec2(-tileWidth/2,tileHeight/2); + nativePoint += b2Vec2(object->GetWidth()/2.0f, -(object->GetHeight()/2.0f)); //adjust for tmx defining from bottom left point while t2d defines from center... + nativePoint *= mMapPixelToMeterFactor; + compSprite->createPolygonBoxCollisionShape(object->GetWidth()*mMapPixelToMeterFactor, object->GetHeight()*mMapPixelToMeterFactor, nativePoint); + + +} + +Vector2 TmxMapSprite::CoordToTile(Vector2& pos, Vector2& tileSize, bool isIso) +{ + + if (isIso) + { + Vector2 newPos( + pos.x / tileSize.y, + pos.y / tileSize.y + ); + + return newPos; + } + else + { + return pos; + } +} + +Vector2 TmxMapSprite::PixelToCoord(Vector2& pixPos){ + auto mapParser = mMapAsset->getParser(); + F32 tileWidth = static_cast(mapParser->GetTileWidth()); + F32 tileHeight = static_cast(mapParser->GetTileHeight()); + F32 mapHeight = (mapParser->GetHeight() * tileHeight); + Vector2 tileSize(tileWidth, tileHeight); + Tmx::MapOrientation orient = mapParser->GetOrientation(); + Vector2 heightOffset = Vector2(pixPos.x, mapHeight - (pixPos.y)); + Vector2 tilecoord = CoordToTile(heightOffset, tileSize, orient == Tmx::TMX_MO_ISOMETRIC); + b2Vec2 nativePoint = tilecoord; + nativePoint += b2Vec2(-tileWidth / 2, tileHeight / 2); + nativePoint *= mMapPixelToMeterFactor; + return nativePoint; +} + +Vector2 TmxMapSprite::TileToCoord(Vector2& pos, Vector2& tileSize, Vector2& offset, bool isIso) +{ + if (isIso) + { + Vector2 newPos( + static_cast( (pos.x - pos.y) * tileSize.y), + static_cast( offset.y - (pos.x + pos.y) * tileSize.y * 0.5 ) + ); + + return newPos; + } + else + { + Vector2 newpos(pos.x*tileSize.x*mMapPixelToMeterFactor, pos.y*tileSize.y*mMapPixelToMeterFactor); + return newpos; + } +} + +CompositeSprite* TmxMapSprite::CreateLayer(U32 layerIndex, bool isIso) +{ + CompositeSprite* compSprite = new CompositeSprite(); + mLayers.push_back(compSprite); + + auto scene = this->getScene(); + if (scene) + scene->addToScene(compSprite); + + if (isIso) + compSprite->setBatchLayout( CompositeSprite::ISOMETRIC_LAYOUT ); + else + compSprite->setBatchLayout(CompositeSprite::RECTILINEAR_LAYOUT); + + auto mapParser = mMapAsset->getParser(); + F32 tileWidth = static_cast(mapParser->GetTileWidth()); + F32 tileHeight = static_cast(mapParser->GetTileHeight()); + compSprite->setDefaultSpriteSize(Vector2(tileWidth, tileHeight) * mMapPixelToMeterFactor); + compSprite->setDefaultSpriteStride(Vector2(tileWidth, tileHeight) * mMapPixelToMeterFactor); + + + compSprite->setPosition(getPosition()); + compSprite->setSceneLayer(layerIndex); + compSprite->setBatchSortMode(SceneRenderQueue::RENDER_SORT_ZAXIS); + compSprite->setBatchIsolated(true); + compSprite->setBodyType(b2_staticBody); + + return compSprite; +} + +const char* TmxMapSprite::getFileName(const char* path) +{ + auto pStr = dStrrchr(path, '\\'); + if (pStr == NULL) + pStr = dStrrchr(path, '/'); + if (pStr == NULL) + return NULL; + + pStr = pStr+1; + + auto pDot = dStrrchr(pStr, '.'); + U32 file_len = pDot - pStr; + if (file_len >= 1024) return NULL; + + char buffer[1024]; + dStrncpy(buffer, pStr, file_len); + buffer[file_len] = 0; + return StringTable->insert(buffer); +} + +StringTableEntry TmxMapSprite::GetTilesetAsset(const Tmx::Tileset* tileSet) +{ + StringTableEntry assetName = StringTable->insert( tileSet->GetProperties().GetLiteralProperty(TMX_MAP_TILESET_ASSETNAME_PROP).c_str() ); + if (assetName == StringTable->EmptyString) + { + //we fall back to using the image filename as an asset name query. + //if that comes back with any hits, we use the first one. + //If you don't want to name your asset the same as your art tile set name, + //you need to set a custom "AssetName" property on the tile set to override this. + + auto imageSource = tileSet->GetImage()->GetSource().c_str(); + StringTableEntry imageName = getFileName(imageSource); + + if (imageName == mLastTileImage) + { + //this is a quick memoize optimization to cut down on asset queries. + //if we are requesting the same assetName from what we already found, we just return that. + return mLastTileAsset; + } + + mLastTileImage = imageName; + + AssetQuery query; + S32 count = AssetDatabase.findAssetName(&query, imageName); + if (count > 0) + { + assetName = query.first(); + mLastTileAsset = StringTable->insert( assetName ); + } + } + + return assetName; +} + +const char* TmxMapSprite::getTileProperty(StringTableEntry lName, StringTableEntry pName, U32 x,U32 y){ + //we'll need a parser and to iterate over the layers + auto mapParser = mMapAsset->getParser(); + auto layerItr = mapParser->GetLayers().begin(); + for(layerItr; layerItr != mapParser->GetLayers().end(); ++layerItr) + { + Tmx::Layer* layer = *layerItr; + + //look for a layer with a name that matches lName + if (std::string(lName) != layer->GetName()) + continue; + //we found one, get the tile properties at the xy of that layer + Tmx::MapTile tile = layer->GetTile(x,y); + const Tmx::Tileset *tset = mapParser->GetTileset(tile.tilesetId); + //make sure they're asking for valid x and y + if (tset == NULL) + return ""; + + const Tmx::PropertySet pset = tset->GetTile(tile.id)->GetProperties(); + if (pset.HasProperty(pName)){ + + //we have the result, put it in the string table and return it + std::string presult = pset.GetLiteralProperty(pName); + StringTableEntry s = StringTable->insert(presult.c_str()); + + return s; + } + else + return ""; + } + + //no layer or property of that name. + //return empty + return ""; + +} + +Vector2 TmxMapSprite::getTileSize(){ + Tmx::Map* mapParser = mMapAsset->getParser(); + F32 tileWidth = static_cast(mapParser->GetTileWidth()); + F32 tileHeight = static_cast(mapParser->GetTileHeight()); + return Vector2(tileWidth, tileHeight); + +} + +bool TmxMapSprite::isIsoMap(){ + Tmx::Map* mapParser = mMapAsset->getParser(); + Tmx::MapOrientation orient = mapParser->GetOrientation(); + return orient == Tmx::TMX_MO_ISOMETRIC; +} + +void TmxMapSprite::setBodyType(const b2BodyType type){ + Parent::setBodyType(type); + + auto layerIdx = mLayers.begin(); + for(layerIdx; layerIdx != mLayers.end(); ++layerIdx) + { + + (*layerIdx)->setBodyType(type); + } + + auto objectsIdx = mObjects.begin(); + for (objectsIdx; objectsIdx != mObjects.end(); ++objectsIdx){ + (*objectsIdx)->setBodyType(type); + } + + +} \ No newline at end of file diff --git a/engine/source/2d/sceneobject/TmxMapSprite.h b/engine/source/2d/sceneobject/TmxMapSprite.h new file mode 100644 index 000000000..5bb104a8b --- /dev/null +++ b/engine/source/2d/sceneobject/TmxMapSprite.h @@ -0,0 +1,93 @@ +#ifndef _TMXMAP_SPRITE_H +#define _TMXMAP_SPRITE_H + +#ifndef _COMPOSITE_SPRITE_H_ +#include "2d/sceneobject/CompositeSprite.h" +#endif + +#ifndef _TMXMAP_ASSET_H_ +#include "2d/assets/TmxMapAsset.h" +#endif + + +#define TMX_MAP_TILESET_ASSETNAME_PROP "AssetName" +#define TMX_MAP_LAYER_ID_PROP "LayerId" +#define TMX_MAP_COLLISION_OBJECT "Collision" +#define TMX_MAP_SCRIPT_OBJECT "Script" +#define TMX_MAP_SCRIPT_FUNCTION "Function" +#define TMX_MAP_NO_TILE -1 + +class TmxMapSprite : public SceneObject +{ +protected: + typedef SceneObject Parent; + +////////CORE/////////////////////////////// +public: + TmxMapSprite(); + virtual ~TmxMapSprite(); + + static void initPersistFields(); + virtual bool onAdd(); + virtual void onRemove(); + virtual void OnRegisterScene(Scene* scene); + virtual void OnUnregisterScene( Scene* pScene ); + virtual void setPosition( const Vector2& position ); + + /// Declare Console Object. + DECLARE_CONOBJECT( TmxMapSprite ); + +////////////////////////////////////////// + +private: //config + + AssetPtr mMapAsset; + F32 mMapPixelToMeterFactor; //This translates TMX pixel/image coordinate space into T2D scene coordinate space. + //The default is to set every pixel equal to 0.03 meters (or about 33 pixels per meter) + //This should match up with the rest of your asset design resolution. + Vector mLayers; + Vector mObjects; + + StringTableEntry mLastTileAsset; + StringTableEntry mLastTileImage; + + void BuildMap(); + void ClearMap(); + CompositeSprite* CreateLayer(U32 layerIndex, bool isIso); + const char* getFileName(const char* path); + StringTableEntry GetTilesetAsset(const Tmx::Tileset* tileSet); + void addObjectGroup(const Tmx::ObjectGroup* objectGroup, const Vector2* offset); + void addScriptObject(const Tmx::Object* object, S32 layerNumber, const Vector2* offset); + void addObjectAsSprite(const Tmx::Tileset* tileSet, Tmx::Object* object, Tmx::Map * mapParser, U32 gid, CompositeSprite* compSprite, const Vector2* offset); + void addPhysicsPolyLine(Tmx::Object* object, CompositeSprite* compSprite, const Vector2* offset); + void addPhysicsPolygon(Tmx::Object* object, CompositeSprite* compSprite, const Vector2* offset); + void addPhysicsEllipse(Tmx::Object* object, CompositeSprite* compSprite, const Vector2* offset); + void addPhysicsRectangle(Tmx::Object* object, CompositeSprite* compSprite, const Vector2* offset); + +public: + inline bool setMap( const char* pMapAssetId ){ if (pMapAssetId == NULL) return false; mMapAsset = pMapAssetId; BuildMap(); return false;} + inline StringTableEntry getMap( void ) const { return mMapAsset.getAssetId(); } + inline bool setMapToMeterFactor( F32 factor ) {mMapPixelToMeterFactor = factor; BuildMap(); return false;} + inline F32 getMapToMeterFactor( void ) const {return mMapPixelToMeterFactor;} + const char* getTileProperty(StringTableEntry lName, StringTableEntry pName, U32 x,U32 y); + Vector2 CoordToTile(Vector2& pos, Vector2& tileSize, bool isIso); + Vector2 TileToCoord(Vector2& pos, Vector2& tileSize, Vector2& offset, bool isIso); + Vector2 PixelToCoord(Vector2& pixPos); + Vector2 getTileSize(); + bool isIsoMap(); + void setBodyType(const b2BodyType type); + +protected: + static bool setMap(void* obj, const char* data) { return static_cast(obj)->setMap(data);} + static const char* getMap(void* obj, const char* data) { return static_cast(obj)->getMap(); } + static bool writeMap( void* obj, StringTableEntry pFieldName ) { return static_cast(obj)->mMapAsset.notNull(); } + + static bool setMapToMeterFactor(void* obj, const char* data) {return static_cast(obj)->setMapToMeterFactor( dAtof(data) ); } + static StringTableEntry getMapToMeterFactor(void* obj, const char* data) {return Con::getFloatArg( static_cast(obj)->getMapToMeterFactor() );} + static bool writeMapToMeterFactor(void* obj, StringTableEntry pFieldName) {return static_cast(obj)->mMapPixelToMeterFactor != 0.1f;} + + +}; + + +#endif //_TMXMAP_SPRITE_H diff --git a/engine/source/2d/sceneobject/TmxMapSprite_ScriptBinding.h b/engine/source/2d/sceneobject/TmxMapSprite_ScriptBinding.h new file mode 100644 index 000000000..344614c17 --- /dev/null +++ b/engine/source/2d/sceneobject/TmxMapSprite_ScriptBinding.h @@ -0,0 +1,41 @@ +ConsoleMethodWithDocs(TmxMapSprite, getMapToMeterFactor, F32, 2, 2, "() Returns the factor used to translate map pixels into scene meters\n" + "") +{ + return object->getMapToMeterFactor(); +} + +ConsoleMethodWithDocs(TmxMapSprite, setMapToMeterFactor, void, 3, 3, "(factor) Sets the factor used to translate map pixels into scene meters\n" + "") +{ + F32 factor = dAtof(argv[2]); + object->setMapToMeterFactor(factor); +} + + +ConsoleMethodWithDocs(TmxMapSprite, getTileProperty, const char*, 6, 6, "(layerName, propertyName, x, y) Gets a property value for the specific x/y coord on the given layer.\n" + "The property value or blank if one can not be found.") +{ + StringTableEntry lName = StringTable->insert(argv[2]); + StringTableEntry pName = StringTable->insert(argv[3]); + S32 x = dAtoi(argv[4]); + S32 y = dAtoi(argv[5]); + + return object->getTileProperty(lName, pName, x, y); +} + + +ConsoleMethodWithDocs(TmxMapSprite, WorldCoordToTile, const char*, 3,3,"Convert a world coordinate into a local tile coordinate\n" + "@Return a tile coordinate as string (x y)") +{ + Vector2 worldPoint(argv[2]); + Vector2 localPoint = object->getLocalPoint(worldPoint); + Vector2 tileSize = object->getTileSize(); + auto tilePoint = object->CoordToTile(localPoint, tileSize, object->isIsoMap()); + return tilePoint.scriptThis(); +} + +ConsoleMethodWithDocs(TmxMapSprite, setMap, void, 3,3,"Set the map this MapSprite is to load.") +{ + object ->setMap(argv[2]); +} + diff --git a/toybox/ToyAssets/1/assets/images/Woodland_x3.asset.taml b/toybox/ToyAssets/1/assets/images/Woodland_x3.asset.taml new file mode 100644 index 000000000..bfa03cbce --- /dev/null +++ b/toybox/ToyAssets/1/assets/images/Woodland_x3.asset.taml @@ -0,0 +1,8 @@ + diff --git a/toybox/ToyAssets/1/assets/images/Woodland_x3.png b/toybox/ToyAssets/1/assets/images/Woodland_x3.png new file mode 100644 index 0000000000000000000000000000000000000000..0efb2d91e817e64c7e1078e2b53c41750c5ec343 GIT binary patch literal 90848 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 JHPWVm{}0Af&msT- literal 0 HcmV?d00001 diff --git a/toybox/ToyAssets/1/assets/images/town.asset.taml b/toybox/ToyAssets/1/assets/images/town.asset.taml new file mode 100644 index 000000000..7f1ed5dc3 --- /dev/null +++ b/toybox/ToyAssets/1/assets/images/town.asset.taml @@ -0,0 +1,10 @@ + diff --git a/toybox/ToyAssets/1/assets/images/town.png b/toybox/ToyAssets/1/assets/images/town.png new file mode 100644 index 0000000000000000000000000000000000000000..5ac1f6d4fb6c60880723737437c18ad11ab02103 GIT binary patch literal 58659 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 literal 0 HcmV?d00001 diff --git a/toybox/ToyAssets/1/assets/images/town1.asset.taml b/toybox/ToyAssets/1/assets/images/town1.asset.taml new file mode 100644 index 000000000..93ec0072c --- /dev/null +++ b/toybox/ToyAssets/1/assets/images/town1.asset.taml @@ -0,0 +1,8 @@ + diff --git a/toybox/ToyAssets/1/assets/images/town1.png b/toybox/ToyAssets/1/assets/images/town1.png new file mode 100644 index 0000000000000000000000000000000000000000..91af7dbe60adbf7af2936506370e8950af238868 GIT binary patch literal 11083 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 qM;I=uRFn39r~dMPPyl)t6zY^Lo_12 + + + + + + + + + + + + + + + -0.5 -0.5 + 0.5 -0.5 + -0.5 0.5 + 0.5 0.5 + + + + + + + + + diff --git a/toybox/ToyAssets/1/assets/maps/testtown.tmx b/toybox/ToyAssets/1/assets/maps/testtown.tmx new file mode 100644 index 000000000..5c2a6841a --- /dev/null +++ b/toybox/ToyAssets/1/assets/maps/testtown.tmx @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + H4sIAAAAAAAAA+3VYQ7CIAwFYK5qdgXjbTwCR4EzqclIXl7aDQwFdP3xQkRk3zrLUgghv5MWGLXMdrnPfSuMo3xPyNV9uWF/r5/7RvmOgr6ztfj/vpKv9vlqFulcsPQd1Y/XjPBhbhD2Sb/T5qx8XIMVfLh3pDnJt8GY9nGDecv6Rfp+hfqxT+sPrClfE/u7py+SgT9L54vUy1zfnr4SzcXnC9cP50b7atwcK1+xfGPC++ndH2ejdF3prIzCul6+lufGNZ/pk+o3y1feuzW+8r4Y5fvse9+DPaL5HntmP98Y6vrS2qeNLbE4/9znvn/x1cR9v+/LSl6AqcfwABkAAA== + + + + + H4sIAAAAAAAAA+1Xy04CMRQte597RfwE4y8YE7/AuCQkhBiXhA9g7YK48RdcE7Z+iz8iaEvmwOHOaafDwEY54WaG6W177qO3rXMHHPCP0PK/1n6nWHj5Uf/F3Fb3zLeft8rfG8HMe+LfT8V/zM1A27d/n3vp+PfrVnmMJrA2t/37FY2N/5ibgbYjL8eiTxM/oq+1WcUzOk+hc+HlUvRt4kfbFzareMbmgU6sb7uBD2MxUPG0MQdszFVfti2XK+uh/7N/f8kQRpXuxNhWyqEI2Cb0n2bYVRdj89/mQQwrm2gNgF/KjxMzTo7Pl31MDYvlSwD7ltcA+KX8OHNrn4wrdLlPnbrNvuU8nkbm7Ji5GKw7SvCL1jCB2Lpl/3W9fBXPwO89wQ+8eiTMLYit/Tl10drC/IYkt/Su+PUSAn52rlT+rWBqvIrvh5eH4gl/KH5twS18+yz65PpNrQ1bX+qujxDfm+LJgm8z8kXVvqzWho3vwK1rQ99867tNDIpv8PFbIfA7bLJnn5gfOe7gxfHdB3j91sk/8Gqyf/D+wH5/jejkrNuYDfv03zZ+W4L2nWBnzpmAUbmvuc36AuTU5wC1v22Dnnif0dOu29w6g7ydC35dI1X87P4xM0/nyjkfA/PH3WHJj+K9S3619g23eb7C3SHw43gHTnde7jO49khX8at7D4E9Nv/4PBP220cvT25zPx6K8Uaki/OCyr9cwJ5U/jWFyr/c+wfsCbrIP66vuxDshZx/qfMf7vgAdJF/6q6bqlt2PNuu8i91jond8QPUXXfh0nWLx1PtqfxT+Qg/KXuRh7EzrqpbPJ5tXzidf3WhOCgbq9afbQ9j7YKf4pC7/6Z06taTA/4mfgFE/BMNABkAAA== + + + + + + + + + + + + + + + + + + H4sIAAAAAAAAA+2Yy0oDMRSGzyiigpeNStG6cqEi6k6kFqmufAYpiHTv5SF8hD6dbryAongpqCDU6h/MkBgynSiTy0A++Mi0PUxOTmZ6OiWKRCKRcOnCz5yYHvxykIuOsYRoPOkfM4HPJ3NibFHFvPOe5rZN03cCfTiCx/DE8bxNZXQ9N7vUBuAgHOIOwxE4SqImrmsj78c0nIEV7iycg9Wcc7S4NlmES3AZrnBX4RpcV2LlWrcU5ZoXBdvfGtyCdbjNbcAduKvET5GoNeMVPvL8TGtuiu5+aytjuoZ0lGutklVzm6RrkNeS1vqAx+zzsUH6mvuinXHsE5PvQF89xKQ/FNlDytgrz+GF+3SMuIRX8BreeM5F5RbewUPuA4lezNT1BFu9rat57wk+wxfYgfckejGzqJ7wX97gu/T6g0QvZrrsCUnGb/dT6XgDbsIF+unHjFrG+c6KSqwk6K6/EOgZxvh6rv1FSZ8fg6gdx2S//0JIa9Nh6/+OPY0mcRH36PYqJL8BZBBzwgAZAAA= + + + + + H4sIAAAAAAAAA+3VOwrCUBRF0TgBKwekg3QMOhlHoI0f8ItfFEwhbgtBkRfBKOcazoJdpAjvdDfLzMysjIt6wBv1mnqBRdCngXpEwpBGNKbJD99pfPDPlGY0pwUtafXNUSWtaUNb2tGeDtJFz450evg+Uy7aYmbV49v26n6vblL3KsptS92rCLet6F5FuW1NagneNbP/0FYPKNChrnpEMD31gIq7Ah5xH08AGQAA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toybox/tmxMapToy/1/main.cs b/toybox/tmxMapToy/1/main.cs new file mode 100644 index 000000000..b83ae5707 --- /dev/null +++ b/toybox/tmxMapToy/1/main.cs @@ -0,0 +1,52 @@ +///////////////////////////////////////////////////// +// All map assets for this toy came from: https://github.com/themanaworld/tmwa-client-data +///////////////////////////////////////////////////// + +function TmxMapToy::create( %this ) +{ + // Set the sandbox drag mode availability. + Sandbox.allowManipulation( pan ); + + // Set the manipulation mode. + Sandbox.useManipulation( pan ); + + // Reset the toy. + TmxMapToy.reset(); +} + + +//----------------------------------------------------------------------------- + +function TmxMapToy::destroy( %this ) +{ +} + +//----------------------------------------------------------------------------- + +function TmxMapToy::reset( %this ) +{ + // Clear the scene. + SandboxScene.clear(); + + %mapSprite = new TmxMapSprite() + { + Map = "ToyAssets:testtown_map"; + }; + SandboxScene.add( %mapSprite ); + + + +} + +function Player(){ +//add a simple dynamic sprite to collide with the tiles + %db = new Sprite(TestAnimation) + { + }; + + %db.createCircleCollisionShape(0.2); + SandboxScene.add( %db ); + return %db; +} + +//----------------------------------------------------------------------------- diff --git a/toybox/tmxMapToy/1/module.taml b/toybox/tmxMapToy/1/module.taml new file mode 100644 index 000000000..9c07d265b --- /dev/null +++ b/toybox/tmxMapToy/1/module.taml @@ -0,0 +1,10 @@ +