├── Doxyfile ├── LICENCE.md ├── Makefile ├── README.md ├── config.mk ├── default.ini ├── default.nix ├── media ├── screenshot-preview.png └── screenshot.png ├── res ├── backgrounds │ ├── clouds.png │ ├── far-grounds.png │ ├── sea.png │ └── sky.png ├── icons │ └── telescope.png ├── maps │ └── 01.tmx ├── music │ └── 01.ogg ├── sfx │ ├── dead.wav │ ├── impact.wav │ ├── jump.wav │ ├── pause.wav │ └── unpause.wav ├── sprites │ └── characters.png └── tilesets │ ├── tileset.png │ └── tileset.tsx └── src ├── Makefile ├── aabb.c ├── aabb.h ├── audio.c ├── audio.h ├── background.c ├── background.h ├── config.c ├── config.h ├── entity.c ├── entity.h ├── hud.c ├── hud.h ├── inih ├── LICENSE.txt ├── Makefile ├── ini.c └── ini.h ├── main.c ├── map.c ├── map.h ├── tmx ├── COPYING ├── Makefile ├── tmx.c ├── tmx.h ├── tmx_err.c ├── tmx_hash.c ├── tmx_mem.c ├── tmx_utils.c ├── tmx_utils.h ├── tmx_xml.c ├── tsx.c └── tsx.h ├── video.c └── video.h /LICENCE.md: -------------------------------------------------------------------------------- 1 | "THE BEER-WARE LICENCE" (Revision 42): 2 | 3 | wrote this files. As long as you retain 4 | this notice you can do whatever you want with this stuff. If we meet 5 | some day, and you think this stuff is worth it, you can buy me a beer 6 | in return. Michael Fitzmayer 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | include config.mk 4 | 5 | all: $(OBJS) 6 | $(CC) $(CFLAGS) $(OBJS) $(LIBS) -o $(PROJECT) 7 | 8 | %: %.c 9 | $(CC) -c $(CFLAGS) $(LIBS) -o $@ $< 10 | 11 | clean: 12 | rm $(OBJS) 13 | rm $(PROJECT) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## THIS REPOSITORY HAS DISCONTINUED 2 | This project is no longer maintained. 3 | 4 | # Rainbow Joe 5 | ## About 6 | Rainbow Joe is a classic 2D platformer game in the spirit of the 1990s written 7 | in C using SDL2 and the 8 | [TMX Map Format](http://doc.mapeditor.org/en/stable/reference/tmx-map-format/) 9 | (Tile Map XML). It is also a constantly evolving project I work on to exercise 10 | C programming and to learn about the basic concepts of game development. 11 | 12 | Follow-on project: 13 | [Boondock Sam](https://github.com/mupfelofen-de/boondock-sam). 14 | 15 | Last but not least: follow-on project of 16 | [Janitor Jamboree](https://github.com/mupfelofen-de/janitor-jamboree) which I 17 | have discarded. 18 | 19 | ### Trivia 20 | The quite odd game title was generated using 21 | [The Video Game Name Generator](https://www.videogamena.me/). 22 | 23 | ## Why C? 24 | 25 | A question I hear alot is why I'm using C for this project instead of a modern 26 | language like Rust, Haskell or `[insert your preferred language here]`, because 27 | writing games in C is neither a easy thing to do nor very common (these days). 28 | 29 | The main reason is obvious and I already mentioned it in the introduction: to 30 | get exercise in C programming. And writing a game in C seemed like a fun way 31 | of getting some. 32 | 33 | Nevertheless: I hope you find the code of this project useful. If so, feel free 34 | to use it in any way you want. Just consider buying me a beer in case we meet 35 | someday. 36 | 37 | ## Screenshot 38 | Click to open in full-resolution. 39 | 40 | [![Screenshot](media/screenshot-preview.png)](media/screenshot.png?raw=true "Screenshot") 41 | 42 | ## Dependencies and how to compile 43 | The program has been successfully compiled with the following libraries: 44 | ``` 45 | libxml2 2.9.8 46 | sdl2 2.0.8 47 | sdl2_image 2.0.3 48 | sdl2_mixer 2.0.2 49 | zlib 1.2.11 50 | ``` 51 | 52 | To compile _Rainbow Joe_ under Linux simply enter: 53 | ``` 54 | make 55 | ``` 56 | 57 | If you're on NixOS enter: 58 | ``` 59 | nix-shell 60 | make 61 | ``` 62 | 63 | To generate the documentation using doxygen enter: 64 | ``` 65 | doxygen 66 | ``` 67 | 68 | ## Releases 69 | There are also **Windows** builds that can be found here: 70 | [Releases](https://github.com/mupfelofen-de/rainbow-joe/releases). 71 | 72 | ## Controls 73 | 74 | ``` 75 | A: move left 76 | D: move right 77 | ESCAPE: pause 78 | LSHIFT: hold to run 79 | SPACE: jump / unpause 80 | 1: set default zoom level 81 | 2: zoom out 82 | 3: zoom in 83 | F: hold to move camera freely 84 | UP: move camera up 85 | DOWN: move camera down 86 | LEFT: move camera left 87 | RIGHT: move camera right 88 | Q: quit 89 | ``` 90 | 91 | ## License 92 | This project is licenced under the "THE BEER-WARE LICENCE". See the file 93 | [LICENCE.md](LICENCE.md) for details. 94 | 95 | [TMX C Loader](https://github.com/baylej/tmx/) by Bayle Jonathan is licenced 96 | under a BSD 2-Clause "Simplified" Licence. See the file 97 | [COPYING](src/tmx/COPYING) for details. 98 | 99 | [inih](https://github.com/benhoyt/inih) by Ben Hoyt is licensed the 100 | New BSD licence. See the file [LICENSE.txt](src/inih/LICENSE.txt) for 101 | details. 102 | 103 | [Magic Cliffs Environment](http://pixelgameart.org/web/portfolio/magic-cliffs-environment/) 104 | by [Luis Zun aka ansimuz](https://www.patreon.com/ansimuz). Licenced under 105 | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). 106 | 107 | All other graphic, sound & music resources are dedicated to 108 | [public domain](https://creativecommons.org/publicdomain/zero/1.0/) and have 109 | been found on [OpenGameArt.org](https://opengameart.org/). 110 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | TOOLCHAIN= 2 | #TOOLCHAIN=i686-w64-mingw32 3 | CC=$(TOOLCHAIN)-cc 4 | PROJECT=rainbow-joe 5 | #PROJECT=rainbow-joe.exe 6 | LIBS=\ 7 | -lSDL2 -lpthread\ 8 | -lSDL2_image\ 9 | -lSDL2_mixer\ 10 | -lxml2 -lz -llzma -licuuc -lm 11 | CFLAGS=\ 12 | -D_REENTRANT\ 13 | -DSDL_MAIN_HANDLED\ 14 | -DWANT_ZLIB\ 15 | -L/usr/$(TOOLCHAIN)/lib\ 16 | -I/usr/$(TOOLCHAIN)/include\ 17 | -I/usr/$(TOOLCHAIN)/include/libxml2\ 18 | -O2\ 19 | -pedantic-errors\ 20 | -std=c99\ 21 | -Wall\ 22 | -Werror\ 23 | -Wextra 24 | SRCS=\ 25 | $(wildcard src/*.c)\ 26 | $(wildcard src/tmx/*.c)\ 27 | $(wildcard src/inih/*.c) 28 | OBJS=$(patsubst %.c, %.o, $(SRCS)) 29 | -------------------------------------------------------------------------------- /default.ini: -------------------------------------------------------------------------------- 1 | [Audio] 2 | enabled = 1 3 | 4 | [Video] 5 | width = 800 ; Horizontal screen resolution 6 | height = 600 ; Vertical screen resolution 7 | fullscreen = 1 ; Fullscreen state (0, 1) 8 | limitFPS = 1 ; Enable/Disable FPS limiter 9 | fps = 60 ; FPS cap 10 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | 3 | with pkgs; 4 | with stdenv.lib; 5 | 6 | let 7 | makeSDLCFlags = lib.concatMap (p: [ "-I${getDev p}/include/SDL2" "-I${getDev p}/include" ]); 8 | makeSDLDFlags = lib.concatMap (p: [ "-L${getLib p}/lib" ]); 9 | SDLlibs = [ SDL2 SDL2_image SDL2_mixer ]; 10 | 11 | in pkgs.stdenv.mkDerivation { 12 | name = "rainbow-joe"; 13 | src = ./.; 14 | 15 | NIX_CFLAGS_COMPILE = (makeSDLCFlags SDLlibs) ++ [ "-I${getDev zlib}/include" "-I${getDev libxml2}/include/libxml2" ]; 16 | NIX_CFLAGS_LINK = (makeSDLDFlags SDLlibs) ++ [ "-L${getLib zlib}/lib" "-L${getLib libxml2}/lib" "-L${getLib lzma}/lib" "-L${getLib icu}/lib" ]; 17 | 18 | installPhase = '' 19 | install -Dsm 755 rainbow-joe $out/bin/rainbow-joe 20 | ''; 21 | } 22 | -------------------------------------------------------------------------------- /media/screenshot-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/media/screenshot-preview.png -------------------------------------------------------------------------------- /media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/media/screenshot.png -------------------------------------------------------------------------------- /res/backgrounds/clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/backgrounds/clouds.png -------------------------------------------------------------------------------- /res/backgrounds/far-grounds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/backgrounds/far-grounds.png -------------------------------------------------------------------------------- /res/backgrounds/sea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/backgrounds/sea.png -------------------------------------------------------------------------------- /res/backgrounds/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/backgrounds/sky.png -------------------------------------------------------------------------------- /res/icons/telescope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/icons/telescope.png -------------------------------------------------------------------------------- /res/maps/01.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJztmjdvFFEURse7rgg2BRgbsAtiZzIS0AAiNsAPIHZkkIAGKEymAUFD7BywMfwCExeQQNAQGzA2wQWxMzmIs9I8MR5tYNez+2ZW35GOZjxraVbvzn3v3nnrOMWjC7vxVpnj3MYl5X19w2c9Rfw+IliqiWkN/sY/KeJby7W6MtvfUvSHo8TvWIrYHufaCcU28twhhnc98U245/c43ld8I49Zg7X2liZmDdbaWxxaY47TFsv/2I4XY7nd05/Dyt3+00wMWlLExwb+HLaVu+Y5m1/e12dce46dKT5TnZCZmTxTs3B27F9853A+F+cV+Xkzz5k/hkO5NgyrUnymOj8z24nhDtzpie8uznfjHgvzienXvDFcyt/LcLnnesI911qSGW//650HbeWF6df8eZpO1YGZ8fa/3nnQ1rrmzV/z3jRdbDU3Z8dbO3vnQVvznje+5r1puviqtspOqv43ubaFrYZW/uaPGdMw1Czpamjz3cJQI0QN/5jarlky5bC3RlDt/P+YMQ3DmKXLYX+vZPs5jBJmTMMyZqbOeksP/g7f4wf8iJ9impvzoToksU1i+rbRcccZg2NxHI7HCXHVzlHH5O8KYrkSV+FqXINr48rfIGnAvbgP9+MBPIiH8DAe8f1/Uyz9/km2fa9L7jtRk78nieUpPI1n8CyeU/4GSgdexit4Fa/hdbyBCbxZwHs/IJYP8RE+xif4NB6OOrBU6MXP+AW/4jf8jj/wJ/4q4L0HUDMPxEE4GCuwMgQ9XClRzzhOxEk4GafgVJyG03FGAcc52RMtwIW4CBdrzyhw1hG/9bgBN+Im3IxbcCtuK3IeKXeDpZGxbMJmbMHz2IpteAHbNdaRppP4vcAu7MaX+ApfJ98RYo/iG2mq6FmGYzXW4AgciaOwFuss/W5MCCGEEEIIIYQIigp620ocoh7XKg3uMZd9WhEdOtyjrX1aUVh63aOtfVpRWOrdd/y29mlF4QnbPq0IjkY3dtqnLU063dhpn7Y0qXL703z2afP5PWuuv3cVQgghhBBCiFz5C6N+EOs= 7 | 8 | 9 | 10 | 11 | eJztnNlOU0Ech6c9Va9kiRo1GrcXcGvicuGSqHGJ+hxujQhR0AdQH8Hlzq0RIQj6AMKV3rrcAUJkCXG5UsGI+E04JwzHacEWSqG/L/ky09OTpmd+zJycMvkbI4QQQgghRHF8TxrzA3/iKI7hL/yN4ziBJjAmgQGm8EDCmGww6SpcjWuChb4S4WM7uezAnbgLd+NlrAtznMnTnHcGzyrfsuQcuZzHC3gRL+FTbPbke5Vj17ARm8L3b9LewtvKtyypI6MrWI8NNkNsDPvxfNvI8Dm2Y0cweV4TXscbiYW+EuGjmVyeYQu22gyxPey3hv22WN9tO/AFvlS+ZckAuQziEA7jCH4O+8NhfyTWd9sv+BW/KV8hhFjyVPHMXY01yYX+JkIIIYQQQoi54EjCmKN4TL/riDmkm2fGHuzFPuyfRfsJB3BQz5tlz9rAmHW4Hjfgxlm0m3AzbtH/+IQQQgghhBCiIhnHPzhhXySMSTqmcBkuxxX6jWpRsofc9uI+3O/Z/3uQY4fw8CLPt4rvX401WDuH17IyOX0vRm2Z/T4W3xsc3/+7VPb+Huf7n8CTeGqRX8v/4NsbHDnfe3/v8Jl38R7er6AxLyW+vcGR87339y2f+Q7f4wflK4QQQgghypDXPH+/wU7sKrNncVE8UX2JqPZE9NpXg0J1J0pHrhohvlxGcSyYfD9uKjFd3zmRqjtROnw1QtI5MsxnKlaPIB1MGdUciV6r7kTpeMAYP8RHTo2QjJOFzc5tfXlZU2HtiUg3+6jmSCb0FXZiV5H5FrKftdL2u7o1YB7jk7A2U5SFzcdtM8G/eWWC4tfnXPcJa677dSH7WSttv6s7fz9iPTYEUzW40mFrxzkb5Dbf+hzXtz7nuk9Ydb8unG7GrAd7w7FswVbsD82Ebdo55jO+PrtzO65vffbVkorOV76F446rHcshHHbGNhvL2ZeXPe7OXZtvvrnuy8tdR9z7RDbH34OYHe642rHcmjJmW2pqbKP5mXXauPZ4Y2z+5pvrvrzcdcTeJ/qc8zV/C8cd13yZzORfjRtW8w== 12 | 13 | 14 | 15 | 16 | eJzt29dPFFEYxuFZdtHEApoogoULC7bE3jW22E3UP0DFmtgFQaV4YdfEEvXCfmUX1FDV2LFhwAsLemFDBRWRoom9/yY7E0cUhIU4Oez3Jk/YDUtydl++nTmzWU2TSCQSiUQikUgkEolEUp4kIgnJSEEqjuOEnYuSVFleoQCFKEIxXuONnYuSVFlaOTQtBK3RBm3RDu0ddq9MUhUJpcdJmIwpmIppmC79SiTVIuucmrYeG7ARLodbGLfDsQmbscVp90olnuQKvV1FuqVbXRz345GBTFyXfpXMD70316957e9wW4QovWt+54saLrtXKqloetJfL/S2zKvebR/0M+gdRyMGsXJeplTC6WsBIhBpmVnzti4JyUhBqvSrVOLp6wiO4hgSjD6PWTotQKH0qmSe0dtzvEAe8o0+84xOi6RXiUQiqVbx89E0f9Tz8ezvv+E7fuh3jPN5nVPfo+MkfLlfAzXlGKJcrPs2vddF9LkYUYg29nADMBCDpF/lUnLflkCniUhCslP25KqntH1bguW27MnVTWn7tnzLbXP/Viz9SiQSifLZyXt5E/Zsu+U9vcz05jXqg74YiEEY7OFe938mi17v4K70W2Yi6DISCxGDWCxRoF9J2bHO7SVm4DKGuDRtEz83e9FMLMUyLMcKrMQqrMYarLVtZZWLdW6/0ed3o98MfmZ6Ub+ncBpncBbncB4XkIaLtq2sctlAhxuNTq28bX7f4h3e4wM+4hM+4wu+2re0SiWdDq/9pV9vm98OPNeO6ITO6IKu6Ibu6KHoayHz684MnutMzMJszMFczMN8hNn8Wnj6vbOS85vmpcffPTzXvdiH/TiAgziEw4iz+bWo6PfOrOfNJWfXk/n147H+qIf6Cv5fPGDND/EI2XiMJ3iKHOTa/Jwq+r0z63nzS+TjFQpQ6FPx+R3BY0diFEYr2G8Az7kRAhGExj7u62JN0QzBil0LMI+7zZ2a1gIt0QohaO30vuNvdYt53B1PlxMwEaGYhMlO7zv+VreY87uVLrdhO3ZgJ3YhT7Hr0JLfY87vDbq8iVu4jSzcMfqV69DqxpzfWpwr10Yd1IUf/F3ufk0yx+pFn19zrzsUwzDc9eszBr3XNIfMsarR5zdbc++PrJ8bmZ8xWOdXl8Njc+1etKTcCaTDIMefnxsNMd6b7/GY+3igue834/fBcj6tRP517UrvswFdNkSAQ2ZXtVivXZXW7xh6HYtxDpnd8uYnjdgAjQ== 17 | 18 | 19 | 20 | 21 | eJzt2slOU1EAh/HT23cQUEQ2DmiiOMBzOOsTgLM+ArMYJbpxoyaKOMeN4pDIKEaNbtioiSIgiG5wTJxl4UdctmiCTU5avy/55TZhc3r/TRPahmBmVvjVpEKoTcU+hf2t+iSEBjSiCc1oQSsO4CAOoQ2HcQRHcYpt23Fy+jHak9jPxLLVxS7d6EEv+tCPAdzBXdzDfTzAQwziGdsO4en0Ywy5rxVIl30tR6vjD/d+jL+NJ7O/TuAVXrtvtIa59yMYzbLPvHQIpenZX8uwAOXp2M/y/62Ie1+Mkiz7mJnZv3WM/7uO44SfnxRkj9j1MZ64r5mZmZmZmZmZ5bg61KMBjWhCM1qwH63RTma56Ba60I0e9KIP/biNgWgns1z0CZ/xBV/xDd/xAz8xFe9oloOWp0JYgUqsxCqsxhpUodrvDPK6bey3HTuwE7uwG3uwF/vcN687zX4dOIOzOIfzuICLuOS+ed1z9hvGCEbxAmMYx0tMuG9GV3AVnbiG67iBmzEPNUNzkhCKUIwSzE1+/xajlOt8lPnb0owm8QZv8Q7v8QEfYx5qhtay3zqsxwZsxCZsxhZsdd+MFvL6X4TFWIIKLMUy3+vMzMzMzMzMzMzMzMzMzMzMLI/6BSO8cwE= 22 | 23 | 24 | 25 | 26 | eJztmrtPFFEUh+9ih4bHP4GP3lIRKHj5R4AaLS0FHwWiRgQVIQQCEqGkxGcBqAlPAcMju2yE0lbB3savmcyEOCjLzNzZy+9LvmSymWRP7rm5N+fMMUYIIYQQQgiRJBfwIlbjJazBWqyzGJOr2FjrW9iG7Xgb7+BdvBfjfx5XtNZuM4mv8Q2+xXf4Hj/YDEpEyg/8ibu4h7/shhNKS4kxrSW2oyg+qjLGnMYzeBbPZWxH5NOFT7Abh8ntCPbx3G83rKKihXy24hW8itdSlN9ZnMN5zJLbHK7y/NVuWEIIIYQQQiTOOm7gpu1ARCyUUoedxFMpqsf+B/VZ0skw+2gEXxa4n7xei9dnGcQhfI69OICjjvdfspjDLcxbjmU/WfKaw60C8+v1Wrw+ywZu4hdcxjXMO95/KWPtyrECK4vs3BX/poGcNmITNiu/QgghRGLYqA81sxcPwe/vPfjM+PVhL88vTDL1oObI4iH4/X0Bl4xfHy7zvGLcrgfF3zlqLylp0ty3SSNH7SWJdFPG+V2OFerTCyFE6lEN6zaqYUWcqAYSLnCdmvRGSF3qzaI8xq7AbEo39uDTwG+vcAzHVQPFTgdrfB878QE+xEcheRkjt+MZf75oNPD+frzZlHlcwMXAvMo33MYd5Td2pljjaZzBj/gJP4fkZZvc7mT8+aJ84H0hDqKePdKAjdiEzXg54n3zW/vQGmH3xECE9/X5E9HEKg5P2D2xFuF9fVP5dRqdz26j89ltdD67zYTy6zTfld+C+QPPxKBa 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /res/music/01.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/music/01.ogg -------------------------------------------------------------------------------- /res/sfx/dead.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/sfx/dead.wav -------------------------------------------------------------------------------- /res/sfx/impact.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/sfx/impact.wav -------------------------------------------------------------------------------- /res/sfx/jump.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/sfx/jump.wav -------------------------------------------------------------------------------- /res/sfx/pause.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/sfx/pause.wav -------------------------------------------------------------------------------- /res/sfx/unpause.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/sfx/unpause.wav -------------------------------------------------------------------------------- /res/sprites/characters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/sprites/characters.png -------------------------------------------------------------------------------- /res/tilesets/tileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mupfdev/rainbow-joe/074571a6b5658d6201da4b091f6873a76e60b1d4/res/tilesets/tileset.png -------------------------------------------------------------------------------- /res/tilesets/tileset.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | all: 4 | make -C ../ 5 | 6 | clean: 7 | make -C ../ clean 8 | -------------------------------------------------------------------------------- /src/aabb.c: -------------------------------------------------------------------------------- 1 | /** @file aabb.c 2 | * @ingroup AABB 3 | * @defgroup AABB 4 | * @brief Simple axis-aligned bounding boxes manager. 5 | * @author Michael Fitzmayer 6 | * @copyright "THE BEER-WARE LICENCE" (Revision 42) 7 | */ 8 | 9 | #include "aabb.h" 10 | 11 | /** 12 | * @brief Check if two bounding boxes intersect. 13 | * @param boxA bounding box A. 14 | * @param boxB bounding box B. 15 | * @return 1 if boxes intersect, 0 if not. 16 | * @ingroup AABB 17 | */ 18 | uint8_t doIntersect(AABB boxA, AABB boxB) 19 | { 20 | double dAx = boxB.l - boxA.r; 21 | double dAy = boxB.t - boxA.b; 22 | double dBx = boxA.l - boxB.r; 23 | double dBy = boxA.t - boxB.b; 24 | 25 | if (dAx > 0.0 || dAy > 0.0) return 0; 26 | if (dBx > 0.0 || dBy > 0.0) return 0; 27 | 28 | return 1; 29 | } 30 | -------------------------------------------------------------------------------- /src/aabb.h: -------------------------------------------------------------------------------- 1 | /** @file aabb.h 2 | * @ingroup AABB 3 | */ 4 | 5 | #ifndef AABB_h 6 | #define AABB_h 7 | 8 | #include 9 | 10 | /** 11 | * @ingroup AABB 12 | */ 13 | typedef struct AABB_t { 14 | double b; 15 | double l; 16 | double r; 17 | double t; 18 | } AABB; 19 | 20 | uint8_t doIntersect(AABB boxA, AABB boxB); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/audio.c: -------------------------------------------------------------------------------- 1 | /** @file audio.c 2 | * @ingroup Audio 3 | * @defgroup Audio 4 | * @brief Handler for the playback of music, sound effects, etc.. 5 | * @author Michael Fitzmayer 6 | * @copyright "THE BEER-WARE LICENCE" (Revision 42) 7 | */ 8 | 9 | #include 10 | #include 11 | #include "audio.h" 12 | 13 | /** 14 | * @brief Free audio mixer. 15 | * @param mixer the mixer structure. See @ref struct Mixer. 16 | * @ingroup Audio 17 | */ 18 | void mixerFree(Mixer *mixer) 19 | { 20 | free(mixer); 21 | Mix_CloseAudio(); 22 | while(Mix_Init(0)) Mix_Quit(); 23 | } 24 | 25 | /** 26 | * @brief Initialise audio mixer. 27 | * @return Mixer on success, NULL on error. See @ref struct Mixer. 28 | * @ingroup Audio 29 | */ 30 | Mixer *mixerInit() 31 | { 32 | static Mixer *mixer; 33 | mixer = malloc(sizeof(struct mixer_t)); 34 | if (NULL == mixer) 35 | { 36 | fprintf(stderr, "mixerInit(): error allocating memory.\n"); 37 | return NULL; 38 | } 39 | 40 | if (-1 == SDL_Init(SDL_INIT_AUDIO)) 41 | { 42 | fprintf(stderr, "Couldn't initialise SDL: %s\n", SDL_GetError()); 43 | free(mixer); 44 | return NULL; 45 | } 46 | 47 | mixer->audioFormat = MIX_DEFAULT_FORMAT; 48 | mixer->chunkSize = 4096; 49 | mixer->numChannels = 2; 50 | mixer->samplingFrequency = 44100; 51 | 52 | if (-1 == Mix_OpenAudio( 53 | mixer->samplingFrequency, 54 | mixer->audioFormat, 55 | mixer->numChannels, 56 | mixer->chunkSize)) 57 | { 58 | fprintf(stderr, "%s\n", Mix_GetError()); 59 | free(mixer); 60 | return NULL; 61 | } 62 | Mix_AllocateChannels(16); 63 | 64 | return mixer; 65 | } 66 | 67 | /** 68 | * @brief Same as musicPlay but with fade-in effect. 69 | * @param music the music structure that should be played. 70 | * @param loops number of times to play through the music, -1 plays the music 71 | * forever. 72 | * @param ms time to fade-in music. 73 | * @return 0 on success, -1 on error. 74 | * @ingroup Audio 75 | */ 76 | int8_t musicFadeIn(Music *music, int8_t loops, uint16_t ms) 77 | { 78 | if (-1 == Mix_FadeInMusic(music->music, loops, ms)) 79 | { 80 | fprintf(stderr, "%s\n", Mix_GetError()); 81 | return -1; 82 | } 83 | 84 | return 0; 85 | } 86 | 87 | /** 88 | * @brief Initialise Music. 89 | * @return Music on success, NULL on error. See @ref struct Music. 90 | * @ingroup Audio 91 | */ 92 | Music *musicInit(const char *filename) 93 | { 94 | static Music *music; 95 | music = malloc(sizeof(struct music_t)); 96 | if (NULL == music) 97 | { 98 | fprintf(stderr, "musicInit(): error allocating memory.\n"); 99 | return NULL; 100 | } 101 | 102 | music->music = Mix_LoadMUS(filename); 103 | 104 | if (NULL == music->music) 105 | { 106 | fprintf(stderr, "%s\n", Mix_GetError()); 107 | free(music); 108 | return NULL; 109 | } 110 | 111 | return music; 112 | } 113 | 114 | /** 115 | * @brief Play music. 116 | * @param music the music structure that should be played. 117 | * @param loops number of times to play through the music, -1 plays the 118 | * music forever. 119 | * @return 0 on success, -1 on error. 120 | * @ingroup Audio 121 | */ 122 | int8_t musicPlay(Music *music, int8_t loops) 123 | { 124 | if (-1 == Mix_PlayMusic(music->music, loops)) 125 | { 126 | fprintf(stderr, "%s\n", Mix_GetError()); 127 | return -1; 128 | } 129 | 130 | return 0; 131 | } 132 | 133 | /** 134 | * @brief Toggle music playback. 135 | * @ingroup Audio 136 | */ 137 | void musicToggle() 138 | { 139 | if (Mix_PausedMusic()) 140 | { 141 | Mix_ResumeMusic(); 142 | } 143 | else 144 | { 145 | Mix_PauseMusic(); 146 | } 147 | } 148 | 149 | /** 150 | * @brief Initialise sound effect. 151 | * @param sfx the sound sfx structure. See @ref struct SFX. 152 | * @return SFX on success, NULL on error. 153 | * @ingroup Audio 154 | */ 155 | SFX *sfxInit(const char *filename) 156 | { 157 | static SFX *sfx; 158 | sfx = malloc(sizeof(struct sfx_t)); 159 | if (NULL == sfx) 160 | { 161 | fprintf(stderr, "sfxInit(): error allocating memory.\n"); 162 | return NULL; 163 | } 164 | 165 | sfx->sfx = Mix_LoadWAV(filename); 166 | 167 | if (NULL == sfx->sfx) 168 | { 169 | fprintf(stderr, "%s\n", Mix_GetError()); 170 | free(sfx); 171 | return NULL; 172 | } 173 | 174 | return sfx; 175 | } 176 | 177 | /** 178 | * @brief Play sound effect. 179 | * @param sfx the sfx structure. 180 | * @param channel the mixer channel to use. 181 | * @param loops number of times to play the sound effect, -1 plays the 182 | * effect forever. 183 | * @return 184 | * @ingroup Audio 185 | */ 186 | int8_t sfxPlay(SFX *sfx, int8_t channel, int8_t loops) 187 | { 188 | if (0 == Mix_Playing(channel)) 189 | { 190 | if (-1 == Mix_PlayChannel(channel, sfx->sfx, loops)) 191 | { 192 | fprintf(stderr, "%s\n", Mix_GetError()); 193 | return -1; 194 | } 195 | } 196 | 197 | return 0; 198 | } 199 | -------------------------------------------------------------------------------- /src/audio.h: -------------------------------------------------------------------------------- 1 | /** @file audio.h 2 | * @ingroup Audio 3 | */ 4 | 5 | #ifndef AUDIO_h 6 | #define AUDIO_h 7 | 8 | #include 9 | #include 10 | 11 | /** 12 | * @def musicFree() 13 | * Free music structure. 14 | * @ingroup Audio 15 | */ 16 | #define musicFree(music) free(music) 17 | 18 | /** 19 | * @def musicHalt() 20 | * Halt music playback. 21 | * @ingroup Audio 22 | */ 23 | #define musicHalt() Mix_HaltMusic() 24 | 25 | /** 26 | * @def musicPause() 27 | * Pause music playback. 28 | * @ingroup Audio 29 | */ 30 | #define musicPause() Mix_PauseMusic() 31 | 32 | /** 33 | * @def musicResume() 34 | * Resume music playback. 35 | * @ingroup Audio 36 | */ 37 | #define musicResume() Mix_ResumeMusic() 38 | 39 | /** 40 | * @def sfxFree() 41 | * Free sfx structure. 42 | * @ingroup Audio 43 | */ 44 | #define sfxFree(sfx) free(sfx) 45 | 46 | #define NUM_SFX 5 47 | #define SFX_DEAD 0 48 | #define SFX_IMPACT 1 49 | #define SFX_JUMP 2 50 | #define SFX_PAUSE 3 51 | #define SFX_UNPAUSE 4 52 | #define CH_DEAD 1 53 | #define CH_IMPACT 2 54 | #define CH_JUMP 3 55 | #define CH_PAUSE 4 56 | #define CH_UNPAUSE 5 57 | 58 | /** 59 | * @ingroup Audio 60 | */ 61 | typedef struct mixer_t { 62 | uint16_t audioFormat; 63 | uint16_t chunkSize; 64 | uint8_t numChannels; 65 | uint16_t samplingFrequency; 66 | } Mixer; 67 | 68 | /** 69 | * @ingroup Audio 70 | */ 71 | typedef struct music_t { 72 | Mix_Music *music; 73 | } Music; 74 | 75 | /** 76 | * @ingroup Audio 77 | */ 78 | typedef struct sfx_t { 79 | Mix_Chunk *sfx; 80 | } SFX; 81 | 82 | void mixerFree(Mixer *mixer); 83 | Mixer *mixerInit(); 84 | int8_t musicFadeIn(Music *music, int8_t loops, uint16_t ms); 85 | Music *musicInit(const char *filename); 86 | int8_t musicPlay(Music *music, int8_t loops); 87 | void musicToggle(); 88 | SFX *sfxInit(const char *filename); 89 | int8_t sfxPlay(SFX *sfx, int8_t channel, int8_t loops); 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /src/background.c: -------------------------------------------------------------------------------- 1 | /** @file background.c 2 | * @ingroup Background 3 | * @defgroup Background 4 | * @brief A background handler to render parallax scrolling backgrounds. 5 | * @author Michael Fitzmayer 6 | * @copyright "THE BEER-WARE LICENCE" (Revision 42) 7 | */ 8 | 9 | #include 10 | #include 11 | #include "background.h" 12 | 13 | /** 14 | * @brief Initialise background structure. See @ref struct Background. 15 | * @param filename the image file to load. 16 | * @param mapWidth the width of the map. See @ref struct Map. 17 | * @return Background on success, NULL on error. 18 | * @ingroup Background 19 | */ 20 | Background *backgroundInit(SDL_Renderer *renderer, const char *filename, int32_t mapWidth) 21 | { 22 | static Background *background; 23 | background = malloc(sizeof(struct background_t)); 24 | if (NULL == background) 25 | { 26 | fprintf(stderr, "backgroundInit(): error allocating memory.\n"); 27 | return NULL; 28 | } 29 | 30 | background->background = NULL; 31 | background->filename = filename; 32 | background->height = 0; 33 | background->width = 0; 34 | background->worldPosX = 0; 35 | background->worldPosY = 0; 36 | 37 | background->image = IMG_LoadTexture(renderer, background->filename); 38 | if (NULL == background->image) 39 | { 40 | fprintf(stderr, "%s\n", SDL_GetError()); 41 | free(background); 42 | return NULL; 43 | } 44 | 45 | if (0 != SDL_QueryTexture(background->image, NULL, NULL, &background->imageWidth, &background->imageHeight)) 46 | { 47 | fprintf(stderr, "%s\n", SDL_GetError()); 48 | free(background); 49 | return NULL; 50 | } 51 | 52 | background->wFactor = 0; 53 | if (background->wFactor > mapWidth) 54 | { 55 | background->wFactor = 1; 56 | } 57 | else 58 | { 59 | background->wFactor = ceil((double)mapWidth / (double)background->imageWidth); 60 | } 61 | 62 | background->height = background->imageHeight; 63 | background->width = background->imageWidth * background->wFactor; 64 | 65 | return background; 66 | } 67 | 68 | /** 69 | * @brief Render background. 70 | * @param renderer SDL's rendering context. See @ref struct Video. 71 | * @param background the background structure. See @ref struct Background. 72 | * @param cameraPosX camera position along the x-axis. 73 | * @param cameraPosY camera position along the y-axis. 74 | * @return 0 on success, -1 on error. 75 | * @ingroup Background 76 | */ 77 | int8_t backgroundRender( 78 | SDL_Renderer *renderer, 79 | Background *background, 80 | double cameraPosX, 81 | double cameraPosY) 82 | { 83 | // Render background if already generated. 84 | if (background->background) 85 | { 86 | double renderPosX = background->worldPosX - cameraPosX; 87 | double renderPosY = background->worldPosY - cameraPosY; 88 | 89 | SDL_Rect dst = 90 | { 91 | renderPosX, 92 | renderPosY, 93 | background->width, 94 | background->height 95 | }; 96 | if (-1 == SDL_RenderCopyEx(renderer, background->background, NULL, &dst, 0, NULL, SDL_FLIP_NONE)) 97 | { 98 | fprintf(stderr, "%s\n", SDL_GetError()); 99 | return -1; 100 | } 101 | 102 | dst.x = renderPosX + background->width; 103 | if (-1 == SDL_RenderCopyEx(renderer, background->background, NULL, &dst, 0, NULL, SDL_FLIP_NONE)) 104 | { 105 | fprintf(stderr, "%s\n", SDL_GetError()); 106 | return -1; 107 | } 108 | 109 | dst.x = renderPosX - background->width; 110 | if (-1 == SDL_RenderCopyEx(renderer, background->background, NULL, &dst, 0, NULL, SDL_FLIP_NONE)) 111 | { 112 | fprintf(stderr, "%s\n", SDL_GetError()); 113 | return -1; 114 | } 115 | 116 | return 0; 117 | } 118 | 119 | // Generate background. 120 | background->background = SDL_CreateTexture( 121 | renderer, 122 | SDL_PIXELFORMAT_ARGB8888, 123 | SDL_TEXTUREACCESS_TARGET, 124 | background->width, 125 | background->height); 126 | 127 | if (NULL == background->background) 128 | { 129 | fprintf(stderr, "%s\n", SDL_GetError()); 130 | return -1; 131 | } 132 | 133 | if (0 != SDL_SetRenderTarget(renderer, background->background)) 134 | { 135 | fprintf(stderr, "%s\n", SDL_GetError()); 136 | return -1; 137 | } 138 | 139 | SDL_Rect dst; 140 | dst.x = 0; 141 | for (uint8_t i = 0; i < background->wFactor; i++) 142 | { 143 | dst.y = 0; 144 | dst.w = background->imageWidth; 145 | dst.h = background->imageHeight; 146 | SDL_RenderCopy(renderer, background->image, NULL, &dst); 147 | dst.x += background->imageWidth; 148 | } 149 | 150 | // Switch back to default render target. 151 | if (0 != SDL_SetRenderTarget(renderer, NULL)) 152 | { 153 | fprintf(stderr, "%s\n", SDL_GetError()); 154 | return -1; 155 | } 156 | 157 | if (0 != SDL_SetTextureBlendMode(background->background, SDL_BLENDMODE_BLEND)) 158 | { 159 | fprintf(stderr, "%s\n", SDL_GetError()); 160 | return -1; 161 | } 162 | 163 | return 0; 164 | } 165 | -------------------------------------------------------------------------------- /src/background.h: -------------------------------------------------------------------------------- 1 | /** @file background.h 2 | * @ingroup Background 3 | */ 4 | 5 | #ifndef BACKGROUND_h 6 | #define BACKGROUND_h 7 | 8 | #include 9 | #include 10 | 11 | /** 12 | * @def backgroundFree() 13 | * Free background structure. 14 | * @ingroup Background 15 | */ 16 | #define backgroundFree(background) free(background) 17 | 18 | /** 19 | * @def NUM_BACKGROUNDS 20 | * The overall number of backgrounds used in the main program. 21 | * @ingroup Background 22 | */ 23 | #define NUM_BACKGROUNDS 4 24 | 25 | /** 26 | * @ingroup Background 27 | */ 28 | typedef struct background_t 29 | { 30 | SDL_Texture *background; 31 | const char *filename; 32 | SDL_Texture *image; 33 | int32_t imageWidth; 34 | int32_t imageHeight; 35 | uint32_t height; 36 | uint32_t width; 37 | uint8_t wFactor; 38 | double worldPosX; 39 | double worldPosY; 40 | } Background; 41 | 42 | Background *backgroundInit(SDL_Renderer *renderer, const char *filename, int32_t mapWidth); 43 | int8_t backgroundRender(SDL_Renderer *renderer, Background *background, double cameraPosX, double cameraPosY); 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /** @file config.c 2 | * @ingroup Config 3 | * @defgroup Config 4 | * @brief Configuration file manager. 5 | * @author Michael Fitzmayer 6 | * @copyright "THE BEER-WARE LICENCE" (Revision 42) 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include "config.h" 13 | #include "inih/ini.h" 14 | 15 | static int32_t handler(void* cfg, const char *section, const char *name, const char *value) 16 | { 17 | Config *config = (Config*)cfg; 18 | 19 | #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 20 | 21 | int32_t val = atoi(value); 22 | 23 | if (MATCH("Audio", "enabled")) config->audio.enabled = val; 24 | else if (MATCH("Video", "fullscreen")) config->video.fullscreen = val; 25 | else if (MATCH("Video", "height")) config->video.height = val; 26 | else if (MATCH("Video", "width")) config->video.width = val; 27 | else if (MATCH("Video", "limitFPS")) config->video.limitFPS = val; 28 | else if (MATCH("Video", "fps")) config->video.fps = val; 29 | else 30 | { 31 | return 0; 32 | } 33 | 34 | return 1; 35 | } 36 | 37 | /** 38 | * @brief Initialise configuration file. 39 | * @param filename the configuration file to load. 40 | * @return Always Config, error message on failure. It is important to define 41 | * a fallback in the main program. See @ref struct Config. 42 | * @ingroup Config 43 | */ 44 | Config configInit(const char *filename) 45 | { 46 | static Config config; 47 | 48 | config.video.fps = 60; 49 | config.video.fullscreen = 0; 50 | config.video.height = 600; 51 | config.video.limitFPS = 1; 52 | config.video.width = 800; 53 | 54 | if (0 > ini_parse(filename, handler, &config)) 55 | { 56 | fprintf(stderr, "Couldn't load configuration file: %s\n", filename); 57 | } 58 | 59 | if (0 > config.video.fps) config.video.fps = abs(config.video.fps); 60 | if (0 > config.video.height) config.video.height = abs(config.video.height); 61 | if (0 > config.video.width) config.video.width = abs(config.video.width); 62 | 63 | return config; 64 | } 65 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /** @file config.h 2 | * @ingroup Config 3 | */ 4 | 5 | #ifndef CONFIG_h 6 | #define CONFIG_h 7 | 8 | #include 9 | 10 | /** 11 | * @ingroup Config 12 | */ 13 | typedef struct audioConfig_t { 14 | int8_t enabled; 15 | } AudioConfig; 16 | 17 | /** 18 | * @ingroup Config 19 | */ 20 | typedef struct videoConfig_t { 21 | int32_t height; 22 | int32_t width; 23 | int8_t fullscreen; 24 | int8_t limitFPS; 25 | int8_t fps; 26 | } VideoConfig; 27 | 28 | /** 29 | * @ingroup Config 30 | */ 31 | typedef struct cfg_t 32 | { 33 | AudioConfig audio; 34 | VideoConfig video; 35 | } Config; 36 | 37 | Config configInit(const char *filename); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/entity.c: -------------------------------------------------------------------------------- 1 | /** @file entity.c 2 | * @ingroup Entity 3 | * @defgroup Entity 4 | * @brief Handler to take care of game entities such as the player, enemies, 5 | * etc.. 6 | * @author Michael Fitzmayer 7 | * @copyright "THE BEER-WARE LICENCE" (Revision 42) 8 | */ 9 | 10 | #include 11 | #include 12 | #include "entity.h" 13 | 14 | /** 15 | * @brief Update entity. Thie function has to be called every frame. 16 | * @param entity the entity to update. See @ref struct Entity. 17 | * @param dTime delta time; time passed since last frame in seconds. 18 | * @ingroup Entity 19 | */ 20 | void entityFrame(Entity *entity, double dTime) 21 | { 22 | // Update bounding box. 23 | entity->bb.b = entity->worldPosY + entity->height; 24 | entity->bb.l = entity->worldPosX; 25 | entity->bb.r = entity->worldPosX + entity->width; 26 | entity->bb.t = entity->worldPosY; 27 | 28 | // Increase/decrease vertical velocity if player is in motion. 29 | if ((entity->flags >> IN_MOTION) & 1) 30 | { 31 | entity->velocity += entity->acceleration * dTime; 32 | } 33 | else 34 | { 35 | entity->velocity -= entity->deceleration * dTime; 36 | } 37 | 38 | // Set vertical velocity limits. 39 | if (entity->velocity > entity->velocityMax) 40 | { 41 | entity->velocity = entity->velocityMax; 42 | } 43 | if (entity->velocity < 0) 44 | { 45 | entity->velocity = 0; 46 | // Reset frame animation when standing still. 47 | entity->frame = entity->frameStart; 48 | } 49 | 50 | // Update frame. 51 | if (((entity->flags >> IN_MOTION) & 1) || 52 | ((entity->flags >> IN_MID_AIR) & 1)) 53 | { 54 | entity->frameTime += dTime; 55 | 56 | if (entity->frameTime > 1 / entity->fps) 57 | { 58 | entity->frame++; 59 | entity->frameTime = 0; 60 | } 61 | } 62 | 63 | // Loop frame animation. 64 | if (entity->frameEnd <= entity->frame) 65 | { 66 | entity->frame = entity->frameStart; 67 | } 68 | 69 | // Set vertical player position. 70 | if (entity->velocity > 0) 71 | { 72 | if ((entity->flags >> DIRECTION) & 1) 73 | { 74 | entity->worldPosX -= (entity->velocity * dTime); 75 | } 76 | else 77 | { 78 | entity->worldPosX += (entity->velocity * dTime); 79 | } 80 | } 81 | 82 | // Set horizontal player position. 83 | if ((entity->flags >> IS_JUMPING) & 1) 84 | { 85 | entity->jumpTime += dTime; 86 | entity->flags |= 1 << IN_MID_AIR; 87 | } 88 | 89 | // Handle falling, jumping, gravity, etc. 90 | if ((entity->flags >> IN_MID_AIR) & 1) 91 | { 92 | double g = (entity->worldMeterInPixel * entity->worldGravitation); 93 | if ((entity->flags >> IS_JUMPING) & 1) 94 | { 95 | entity->frameStart = JUMP; 96 | entity->frameEnd = JUMP_MAX; 97 | g += entity->velocityJump; 98 | g = -g * entity->jumpGravityFactor; 99 | 100 | if (entity->jumpTime > entity->jumpTimeMax) 101 | { 102 | entity->jumpTime = 0; 103 | entity->flags &= ~(1 << IS_JUMPING); 104 | } 105 | } 106 | else 107 | { 108 | entity->frameStart = FALL; 109 | entity->frameEnd = FALL_MAX; 110 | } 111 | 112 | entity->distanceFall = g * dTime * dTime; 113 | entity->velocityFall += entity->distanceFall; 114 | entity->worldPosY += entity->velocityFall; 115 | } 116 | else 117 | { 118 | entity->frameStart = WALK; 119 | entity->frameEnd = WALK_MAX; 120 | entity->flags &= ~(1 << IS_JUMPING); 121 | entity->velocityFall = 0; 122 | } 123 | 124 | // Connect left and right border of the map and vice versa. 125 | if (entity->worldPosX < 0 - (entity->width / 2)) 126 | { 127 | entity->worldPosX = entity->worldWidth - (entity->width / 2); 128 | } 129 | 130 | if (entity->worldPosX > entity->worldWidth - (entity->width / 2)) 131 | { 132 | entity->worldPosX = 0 - (entity->width / 2); 133 | } 134 | 135 | // Kill player when he falls out of the map. 136 | if (entity->worldPosY >= entity->worldHeight + entity->height) 137 | { 138 | entity->flags |= 1 << IS_DEAD; 139 | } 140 | 141 | if (entity->worldPosY > entity->worldHeight + entity->height) 142 | { 143 | entity->worldPosY = entity->worldHeight + entity->height; 144 | } 145 | } 146 | 147 | /** 148 | * @brief Initialise entity. 149 | * @return Entity on success, NULL on error. See @ref struct Entity. 150 | * @ingroup Entity 151 | */ 152 | Entity *entityInit() 153 | { 154 | static Entity *entity; 155 | entity = malloc(sizeof(struct entity_t)); 156 | if (NULL == entity) 157 | { 158 | fprintf(stderr, "entityInit(): error allocating memory.\n"); 159 | return NULL; 160 | } 161 | 162 | // Default values. 163 | entity->height = 32; 164 | entity->width = 32; 165 | entity->bb.b = 0; 166 | entity->bb.l = entity->height; 167 | entity->bb.r = entity->width; 168 | entity->bb.t = 0; 169 | entity->acceleration = 400; 170 | entity->deceleration = 200; 171 | entity->distanceFall = 0; 172 | entity->flags = 0; 173 | entity->fps = 12; 174 | entity->frame = 0; 175 | entity->frameEnd = WALK_MAX; 176 | entity->frameStart = WALK; 177 | entity->frameTime = 0.0; 178 | entity->frameYoffset = 32; 179 | entity->jumpGravityFactor = 4.0; 180 | entity->jumpTime = 0.0; 181 | entity->jumpTimeMax = 0.12; 182 | entity->respawnPosX = 0.0; 183 | entity->respawnPosY = 0.0; 184 | entity->sprite = NULL; 185 | entity->velocity = 0.0; 186 | entity->velocityFall = 0.0; 187 | entity->velocityJump = 0.0; 188 | entity->velocityMax = 100.0; 189 | entity->worldHeight = 0; 190 | entity->worldGravitation = 9.81; 191 | entity->worldMeterInPixel = 32; 192 | entity->worldPosX = 0.0; 193 | entity->worldPosY = 0.0; 194 | entity->worldWidth = 0; 195 | 196 | return entity; 197 | } 198 | 199 | /** 200 | * @brief 201 | * @param entity 202 | * @param renderer 203 | * @param filename 204 | * @return 0 on success, -1 on error. 205 | * @ingroup Entity 206 | */ 207 | int8_t entityLoadSprite(Entity *entity, SDL_Renderer *renderer, const char *filename) 208 | { 209 | if (NULL != entity->sprite) 210 | { 211 | SDL_DestroyTexture(entity->sprite); 212 | } 213 | 214 | entity->sprite = IMG_LoadTexture(renderer, filename); 215 | if (NULL == entity->sprite) 216 | { 217 | fprintf(stderr, "%s\n", SDL_GetError()); 218 | return -1; 219 | } 220 | 221 | return 0; 222 | } 223 | 224 | /** 225 | * @brief Render entity on screen. 226 | * @param renderer SDL's rendering context. See @ref struct Video. 227 | * @param entity the entity to render. See @ref struct Entity. 228 | * @param cameraPosX camera position along the x-axis. 229 | * @param cameraPosY camera position along the y-axis. 230 | * @return 0 on success, -1 on error. 231 | * @ingroup Entity 232 | */ 233 | int8_t entityRender(SDL_Renderer *renderer, Entity *entity, double cameraPosX, double cameraPosY) 234 | { 235 | if (NULL == entity->sprite) 236 | { 237 | fprintf(stderr, "%s\n", SDL_GetError()); 238 | return -1; 239 | } 240 | 241 | double renderPosX = entity->worldPosX - cameraPosX; 242 | double renderPosY = entity->worldPosY - cameraPosY; 243 | 244 | SDL_Rect dst = 245 | { 246 | renderPosX, 247 | renderPosY, 248 | entity->width, 249 | entity->height 250 | }; 251 | SDL_Rect src = 252 | { 253 | entity->frame * entity->width, 254 | entity->frameYoffset, 255 | entity->width, 256 | entity->height 257 | }; 258 | 259 | SDL_RendererFlip flip; 260 | 261 | if ((entity->flags >> DIRECTION) & 1) 262 | { 263 | flip = SDL_FLIP_HORIZONTAL; 264 | } 265 | else 266 | { 267 | flip = SDL_FLIP_NONE; 268 | } 269 | 270 | if (-1 == SDL_RenderCopyEx(renderer, entity->sprite, &src, &dst, 0, NULL, flip)) 271 | { 272 | fprintf(stderr, "%s\n", SDL_GetError()); 273 | return -1; 274 | } 275 | 276 | return 0; 277 | } 278 | 279 | /** 280 | * @brief Respawn entity. 281 | * @param entity the entity to respawn. See @ref struct Entity. 282 | * @ingroup Entity 283 | */ 284 | void entityRespawn(Entity *entity) 285 | { 286 | entity->flags &= ~(1 << IS_DEAD); 287 | entity->flags &= ~(1 << IN_MOTION); 288 | entity->worldPosX = entity->respawnPosX; 289 | entity->worldPosY = entity->respawnPosY; 290 | } 291 | -------------------------------------------------------------------------------- /src/entity.h: -------------------------------------------------------------------------------- 1 | /** @file entity.h 2 | * @ingroup Entity 3 | */ 4 | 5 | #ifndef ENTITY_h 6 | #define ENTITY_h 7 | 8 | #include 9 | #include 10 | #include "aabb.h" 11 | 12 | /** 13 | * @def entityFree() 14 | * Free entity structure. 15 | * @ingroup Entity 16 | */ 17 | #define entityFree(entity) free(entity) 18 | 19 | /** 20 | * @def NUM_ENTITIES 21 | * The number of entities used in the main program. 22 | * @ingroup Entity 23 | */ 24 | #define NUM_ENTITIES 7 25 | #define PLAYER_ENTITY 0 26 | 27 | // Flags. 28 | #define DIRECTION 0 29 | #define IN_MID_AIR 1 30 | #define IN_MOTION 2 31 | #define IS_DEAD 3 32 | #define IS_JUMPING 4 33 | 34 | // Frames. 35 | #define FALL 6 36 | #define FALL_MAX 7 37 | #define JUMP 4 38 | #define JUMP_MAX 5 39 | #define RUN 14 40 | #define RUN_MAX 17 41 | #define WALK 0 42 | #define WALK_MAX 3 43 | 44 | /** 45 | * @ingroup Entity 46 | */ 47 | typedef struct entity_t 48 | { 49 | double acceleration; 50 | double deceleration; 51 | uint16_t flags; 52 | double fps; 53 | uint8_t frameEnd; 54 | uint8_t frameStart; 55 | uint16_t frameYoffset; 56 | uint8_t height; 57 | double jumpGravityFactor; 58 | double jumpTimeMax; 59 | double respawnPosX; 60 | double respawnPosY; 61 | double velocityMax; 62 | uint8_t width; 63 | uint32_t worldHeight; 64 | double worldGravitation; 65 | double worldMeterInPixel; 66 | double worldPosX; 67 | double worldPosY; 68 | uint32_t worldWidth; 69 | /* These variables are used internally to store volatile values and usually 70 | * do not have to be changed manually. */ 71 | AABB bb; 72 | double distanceFall; 73 | uint8_t frame; 74 | double frameTime; 75 | double jumpTime; 76 | double velocityJump; 77 | SDL_Texture *sprite; 78 | double velocity; 79 | double velocityFall; 80 | 81 | } Entity; 82 | 83 | void entityFrame(Entity *entity, double dTime); 84 | Entity *entityInit(); 85 | int8_t entityLoadSprite(Entity *entity, SDL_Renderer *renderer, const char *filename); 86 | int8_t entityRender(SDL_Renderer *renderer, Entity *entity, double cameraPosX, double cameraPosY); 87 | void entityRespawn(Entity *entity); 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /src/hud.c: -------------------------------------------------------------------------------- 1 | /** @file hud.c 2 | * @ingroup HUD 3 | * @defgroup HUD 4 | * @brief Handler to manage the head-up display. 5 | * @author Michael Fitzmayer 6 | * @copyright "THE BEER-WARE LICENCE" (Revision 42) 7 | */ 8 | 9 | #include 10 | #include 11 | #include "hud.h" 12 | 13 | /** 14 | * @brief Initialise icon. See @ref struct Icon. 15 | * @param filename the image file to load. 16 | * @return Icon on success, NULL on error. 17 | * @ingroup HUD 18 | */ 19 | Icon *iconInit(SDL_Renderer *renderer, const char *filename) 20 | { 21 | static Icon *icon; 22 | icon = malloc(sizeof(struct icon_t)); 23 | if (NULL == icon) 24 | { 25 | fprintf(stderr, "iconInit(): error allocating memory.\n"); 26 | return NULL; 27 | } 28 | 29 | icon->icon = NULL; 30 | icon->height = 32; 31 | icon->width = 32; 32 | 33 | icon->icon = IMG_LoadTexture(renderer, filename); 34 | if (NULL == icon->icon) 35 | { 36 | fprintf(stderr, "%s\n", SDL_GetError()); 37 | free(icon); 38 | return NULL; 39 | } 40 | 41 | return icon; 42 | } 43 | 44 | /** 45 | * @brief Render icon on screen. 46 | * @param renderer SDL's rendering context. See @ref struct Video. 47 | * @param icon the icon structure to render. See @ref struct Icon. 48 | * @param posX render position along the x-axis. 49 | * @param posY render position along the y-axis. 50 | * @return 0 on success, -1 on error. 51 | * @ingroup HUD 52 | */ 53 | int8_t iconRender(SDL_Renderer *renderer, Icon *icon, double posX, double posY) 54 | { 55 | if (NULL == icon->icon) 56 | { 57 | fprintf(stderr, "%s\n", SDL_GetError()); 58 | return -1; 59 | } 60 | 61 | SDL_Rect dst = 62 | { 63 | posX, 64 | posY, 65 | icon->width, 66 | icon->height 67 | }; 68 | SDL_Rect src = 69 | { 70 | 0, 71 | 0, 72 | icon->width, 73 | icon->height 74 | }; 75 | 76 | if (-1 == SDL_RenderCopy(renderer, icon->icon, &src, &dst)) 77 | { 78 | fprintf(stderr, "%s\n", SDL_GetError()); 79 | return -1; 80 | } 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /src/hud.h: -------------------------------------------------------------------------------- 1 | /** @file hud.h 2 | * @ingroup HUD 3 | */ 4 | 5 | #ifndef HUD_h 6 | #define HUD_h 7 | 8 | #include 9 | #include 10 | 11 | /** 12 | * @def iconFree() 13 | * Free icon structure. 14 | * @ingroup HUD 15 | */ 16 | #define iconFree(icon) free(icon) 17 | 18 | /** 19 | * @ingroup HUD 20 | */ 21 | typedef struct icon_t { 22 | uint8_t height; 23 | SDL_Texture *icon; 24 | double renderPosX; 25 | double renderPosY; 26 | uint8_t width; 27 | } Icon; 28 | 29 | Icon *iconInit(SDL_Renderer *renderer, const char *filename); 30 | int8_t iconRender(SDL_Renderer *renderer, Icon *icon, double cameraPosX, double cameraPosY); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/inih/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The "inih" library is distributed under the New BSD license: 3 | 4 | Copyright (c) 2009, Ben Hoyt 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of Ben Hoyt nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/inih/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | include ../../config.mk 4 | 5 | all: 6 | make -C ../../ 7 | 8 | clean: 9 | make -C ../../ clean 10 | -------------------------------------------------------------------------------- /src/inih/ini.c: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 4 | home page for more info: 5 | 6 | https://github.com/benhoyt/inih 7 | 8 | */ 9 | 10 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 11 | #define _CRT_SECURE_NO_WARNINGS 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "ini.h" 19 | 20 | #if !INI_USE_STACK 21 | #include 22 | #endif 23 | 24 | #define MAX_SECTION 50 25 | #define MAX_NAME 50 26 | 27 | /* Used by ini_parse_string() to keep track of string parsing state. */ 28 | typedef struct { 29 | const char* ptr; 30 | size_t num_left; 31 | } ini_parse_string_ctx; 32 | 33 | /* Strip whitespace chars off end of given string, in place. Return s. */ 34 | static char* rstrip(char* s) 35 | { 36 | char* p = s + strlen(s); 37 | while (p > s && isspace((unsigned char)(*--p))) 38 | *p = '\0'; 39 | return s; 40 | } 41 | 42 | /* Return pointer to first non-whitespace char in given string. */ 43 | static char* lskip(const char* s) 44 | { 45 | while (*s && isspace((unsigned char)(*s))) 46 | s++; 47 | return (char*)s; 48 | } 49 | 50 | /* Return pointer to first char (of chars) or inline comment in given string, 51 | or pointer to null at end of string if neither found. Inline comment must 52 | be prefixed by a whitespace character to register as a comment. */ 53 | static char* find_chars_or_comment(const char* s, const char* chars) 54 | { 55 | #if INI_ALLOW_INLINE_COMMENTS 56 | int was_space = 0; 57 | while (*s && (!chars || !strchr(chars, *s)) && 58 | !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { 59 | was_space = isspace((unsigned char)(*s)); 60 | s++; 61 | } 62 | #else 63 | while (*s && (!chars || !strchr(chars, *s))) { 64 | s++; 65 | } 66 | #endif 67 | return (char*)s; 68 | } 69 | 70 | /* Version of strncpy that ensures dest (size bytes) is null-terminated. */ 71 | static char* strncpy0(char* dest, const char* src, size_t size) 72 | { 73 | strncpy(dest, src, size - 1); 74 | dest[size - 1] = '\0'; 75 | return dest; 76 | } 77 | 78 | /* See documentation in header file. */ 79 | int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 80 | void* user) 81 | { 82 | /* Uses a fair bit of stack (use heap instead if you need to) */ 83 | #if INI_USE_STACK 84 | char line[INI_MAX_LINE]; 85 | int max_line = INI_MAX_LINE; 86 | #else 87 | char* line; 88 | int max_line = INI_INITIAL_ALLOC; 89 | #endif 90 | #if INI_ALLOW_REALLOC 91 | char* new_line; 92 | int offset; 93 | #endif 94 | char section[MAX_SECTION] = ""; 95 | char prev_name[MAX_NAME] = ""; 96 | 97 | char* start; 98 | char* end; 99 | char* name; 100 | char* value; 101 | int lineno = 0; 102 | int error = 0; 103 | 104 | #if !INI_USE_STACK 105 | line = (char*)malloc(INI_INITIAL_ALLOC); 106 | if (!line) { 107 | return -2; 108 | } 109 | #endif 110 | 111 | #if INI_HANDLER_LINENO 112 | #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) 113 | #else 114 | #define HANDLER(u, s, n, v) handler(u, s, n, v) 115 | #endif 116 | 117 | /* Scan through stream line by line */ 118 | while (reader(line, max_line, stream) != NULL) { 119 | #if INI_ALLOW_REALLOC 120 | offset = strlen(line); 121 | while (offset == max_line - 1 && line[offset - 1] != '\n') { 122 | max_line *= 2; 123 | if (max_line > INI_MAX_LINE) 124 | max_line = INI_MAX_LINE; 125 | new_line = realloc(line, max_line); 126 | if (!new_line) { 127 | free(line); 128 | return -2; 129 | } 130 | line = new_line; 131 | if (reader(line + offset, max_line - offset, stream) == NULL) 132 | break; 133 | if (max_line >= INI_MAX_LINE) 134 | break; 135 | offset += strlen(line + offset); 136 | } 137 | #endif 138 | 139 | lineno++; 140 | 141 | start = line; 142 | #if INI_ALLOW_BOM 143 | if (lineno == 1 && (unsigned char)start[0] == 0xEF && 144 | (unsigned char)start[1] == 0xBB && 145 | (unsigned char)start[2] == 0xBF) { 146 | start += 3; 147 | } 148 | #endif 149 | start = lskip(rstrip(start)); 150 | 151 | if (strchr(INI_START_COMMENT_PREFIXES, *start)) { 152 | /* Start-of-line comment */ 153 | } 154 | #if INI_ALLOW_MULTILINE 155 | else if (*prev_name && *start && start > line) { 156 | /* Non-blank line with leading whitespace, treat as continuation 157 | of previous name's value (as per Python configparser). */ 158 | if (!HANDLER(user, section, prev_name, start) && !error) 159 | error = lineno; 160 | } 161 | #endif 162 | else if (*start == '[') { 163 | /* A "[section]" line */ 164 | end = find_chars_or_comment(start + 1, "]"); 165 | if (*end == ']') { 166 | *end = '\0'; 167 | strncpy0(section, start + 1, sizeof(section)); 168 | *prev_name = '\0'; 169 | } 170 | else if (!error) { 171 | /* No ']' found on section line */ 172 | error = lineno; 173 | } 174 | } 175 | else if (*start) { 176 | /* Not a comment, must be a name[=:]value pair */ 177 | end = find_chars_or_comment(start, "=:"); 178 | if (*end == '=' || *end == ':') { 179 | *end = '\0'; 180 | name = rstrip(start); 181 | value = end + 1; 182 | #if INI_ALLOW_INLINE_COMMENTS 183 | end = find_chars_or_comment(value, NULL); 184 | if (*end) 185 | *end = '\0'; 186 | #endif 187 | value = lskip(value); 188 | rstrip(value); 189 | 190 | /* Valid name[=:]value pair found, call handler */ 191 | strncpy0(prev_name, name, sizeof(prev_name)); 192 | if (!HANDLER(user, section, name, value) && !error) 193 | error = lineno; 194 | } 195 | else if (!error) { 196 | /* No '=' or ':' found on name[=:]value line */ 197 | error = lineno; 198 | } 199 | } 200 | 201 | #if INI_STOP_ON_FIRST_ERROR 202 | if (error) 203 | break; 204 | #endif 205 | } 206 | 207 | #if !INI_USE_STACK 208 | free(line); 209 | #endif 210 | 211 | return error; 212 | } 213 | 214 | /* See documentation in header file. */ 215 | int ini_parse_file(FILE* file, ini_handler handler, void* user) 216 | { 217 | return ini_parse_stream((ini_reader)fgets, file, handler, user); 218 | } 219 | 220 | /* See documentation in header file. */ 221 | int ini_parse(const char* filename, ini_handler handler, void* user) 222 | { 223 | FILE* file; 224 | int error; 225 | 226 | file = fopen(filename, "r"); 227 | if (!file) 228 | return -1; 229 | error = ini_parse_file(file, handler, user); 230 | fclose(file); 231 | return error; 232 | } 233 | 234 | /* An ini_reader function to read the next line from a string buffer. This 235 | is the fgets() equivalent used by ini_parse_string(). */ 236 | static char* ini_reader_string(char* str, int num, void* stream) { 237 | ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; 238 | const char* ctx_ptr = ctx->ptr; 239 | size_t ctx_num_left = ctx->num_left; 240 | char* strp = str; 241 | char c; 242 | 243 | if (ctx_num_left == 0 || num < 2) 244 | return NULL; 245 | 246 | while (num > 1 && ctx_num_left != 0) { 247 | c = *ctx_ptr++; 248 | ctx_num_left--; 249 | *strp++ = c; 250 | if (c == '\n') 251 | break; 252 | num--; 253 | } 254 | 255 | *strp = '\0'; 256 | ctx->ptr = ctx_ptr; 257 | ctx->num_left = ctx_num_left; 258 | return str; 259 | } 260 | 261 | /* See documentation in header file. */ 262 | int ini_parse_string(const char* string, ini_handler handler, void* user) { 263 | ini_parse_string_ctx ctx; 264 | 265 | ctx.ptr = string; 266 | ctx.num_left = strlen(string); 267 | return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, 268 | user); 269 | } 270 | -------------------------------------------------------------------------------- /src/inih/ini.h: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 4 | home page for more info: 5 | 6 | https://github.com/benhoyt/inih 7 | 8 | */ 9 | 10 | #ifndef __INI_H__ 11 | #define __INI_H__ 12 | 13 | /* Make this header file easier to include in C++ code */ 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | #include 19 | 20 | /* Nonzero if ini_handler callback should accept lineno parameter. */ 21 | #ifndef INI_HANDLER_LINENO 22 | #define INI_HANDLER_LINENO 0 23 | #endif 24 | 25 | /* Typedef for prototype of handler function. */ 26 | #if INI_HANDLER_LINENO 27 | typedef int (*ini_handler)(void* user, const char* section, 28 | const char* name, const char* value, 29 | int lineno); 30 | #else 31 | typedef int (*ini_handler)(void* user, const char* section, 32 | const char* name, const char* value); 33 | #endif 34 | 35 | /* Typedef for prototype of fgets-style reader function. */ 36 | typedef char* (*ini_reader)(char* str, int num, void* stream); 37 | 38 | /* Parse given INI-style file. May have [section]s, name=value pairs 39 | (whitespace stripped), and comments starting with ';' (semicolon). Section 40 | is "" if name=value pair parsed before any section heading. name:value 41 | pairs are also supported as a concession to Python's configparser. 42 | 43 | For each name=value pair parsed, call handler function with given user 44 | pointer as well as section, name, and value (data only valid for duration 45 | of handler call). Handler should return nonzero on success, zero on error. 46 | 47 | Returns 0 on success, line number of first error on parse error (doesn't 48 | stop on first error), -1 on file open error, or -2 on memory allocation 49 | error (only when INI_USE_STACK is zero). 50 | */ 51 | int ini_parse(const char* filename, ini_handler handler, void* user); 52 | 53 | /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't 54 | close the file when it's finished -- the caller must do that. */ 55 | int ini_parse_file(FILE* file, ini_handler handler, void* user); 56 | 57 | /* Same as ini_parse(), but takes an ini_reader function pointer instead of 58 | filename. Used for implementing custom or string-based I/O (see also 59 | ini_parse_string). */ 60 | int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 61 | void* user); 62 | 63 | /* Same as ini_parse(), but takes a zero-terminated string with the INI data 64 | instead of a file. Useful for parsing INI data from a network socket or 65 | already in memory. */ 66 | int ini_parse_string(const char* string, ini_handler handler, void* user); 67 | 68 | /* Nonzero to allow multi-line value parsing, in the style of Python's 69 | configparser. If allowed, ini_parse() will call the handler with the same 70 | name for each subsequent line parsed. */ 71 | #ifndef INI_ALLOW_MULTILINE 72 | #define INI_ALLOW_MULTILINE 1 73 | #endif 74 | 75 | /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of 76 | the file. See http://code.google.com/p/inih/issues/detail?id=21 */ 77 | #ifndef INI_ALLOW_BOM 78 | #define INI_ALLOW_BOM 1 79 | #endif 80 | 81 | /* Chars that begin a start-of-line comment. Per Python configparser, allow 82 | both ; and # comments at the start of a line by default. */ 83 | #ifndef INI_START_COMMENT_PREFIXES 84 | #define INI_START_COMMENT_PREFIXES ";#" 85 | #endif 86 | 87 | /* Nonzero to allow inline comments (with valid inline comment characters 88 | specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match 89 | Python 3.2+ configparser behaviour. */ 90 | #ifndef INI_ALLOW_INLINE_COMMENTS 91 | #define INI_ALLOW_INLINE_COMMENTS 1 92 | #endif 93 | #ifndef INI_INLINE_COMMENT_PREFIXES 94 | #define INI_INLINE_COMMENT_PREFIXES ";" 95 | #endif 96 | 97 | /* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ 98 | #ifndef INI_USE_STACK 99 | #define INI_USE_STACK 1 100 | #endif 101 | 102 | /* Maximum line length for any line in INI file (stack or heap). Note that 103 | this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ 104 | #ifndef INI_MAX_LINE 105 | #define INI_MAX_LINE 200 106 | #endif 107 | 108 | /* Nonzero to allow heap line buffer to grow via realloc(), zero for a 109 | fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is 110 | zero. */ 111 | #ifndef INI_ALLOW_REALLOC 112 | #define INI_ALLOW_REALLOC 0 113 | #endif 114 | 115 | /* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK 116 | is zero. */ 117 | #ifndef INI_INITIAL_ALLOC 118 | #define INI_INITIAL_ALLOC 200 119 | #endif 120 | 121 | /* Stop parsing on first error (default is to keep parsing). */ 122 | #ifndef INI_STOP_ON_FIRST_ERROR 123 | #define INI_STOP_ON_FIRST_ERROR 0 124 | #endif 125 | 126 | #ifdef __cplusplus 127 | } 128 | #endif 129 | 130 | #endif /* __INI_H__ */ 131 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /** @file main.c 2 | * @author Michael Fitzmayer 3 | * @copyright "THE BEER-WARE LICENCE" (Revision 42) 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include "aabb.h" 10 | #include "audio.h" 11 | #include "background.h" 12 | #include "config.h" 13 | #include "entity.h" 14 | #include "hud.h" 15 | #include "map.h" 16 | #include "video.h" 17 | 18 | int32_t main(int32_t argc, char *argv[]) 19 | { 20 | int32_t execStatus = EXIT_SUCCESS; 21 | 22 | const char *configFilename; 23 | if (argc > 1) 24 | { 25 | configFilename = argv[1]; 26 | } 27 | else 28 | { 29 | configFilename = "default.ini"; 30 | } 31 | 32 | Config config = configInit(configFilename); 33 | Video *video = NULL; 34 | Map *map = NULL; 35 | Mixer *mixer = NULL; 36 | Music *music = NULL; 37 | Icon *iconFC = NULL; 38 | 39 | Background *bg[NUM_BACKGROUNDS]; 40 | for (uint32_t i = 0; i < NUM_BACKGROUNDS; i++) 41 | { 42 | bg[i] = NULL; 43 | } 44 | 45 | Entity *entity[NUM_ENTITIES]; 46 | for (uint32_t i = 0; i < NUM_ENTITIES; i++) 47 | { 48 | entity[i] = NULL; 49 | } 50 | 51 | SFX *sfx[NUM_SFX]; 52 | for (uint32_t i = 0; i < NUM_SFX; i++) 53 | { 54 | sfx[i] = NULL; 55 | } 56 | 57 | video = videoInit( 58 | "Rainbow Joe", 59 | config.video.width, 60 | config.video.height, 61 | config.video.fullscreen, 62 | 2); 63 | 64 | if (NULL == video) 65 | { 66 | execStatus = EXIT_FAILURE; 67 | goto quit; 68 | } 69 | atexit(SDL_Quit); 70 | 71 | map = mapInit("res/maps/01.tmx"); 72 | if (NULL == map) 73 | { 74 | execStatus = EXIT_FAILURE; 75 | goto quit; 76 | } 77 | 78 | // Audio mixer and music. 79 | /* Note: The error handling isn't missing here. There is simply no need to 80 | * quit the program if the music can't be played by some reason. */ 81 | mixer = mixerInit(); 82 | music = musicInit("res/music/01.ogg"); 83 | if (mixer && config.audio.enabled) 84 | { 85 | musicFadeIn(music, -1, 2000); 86 | } 87 | 88 | iconFC = iconInit(video->renderer, "res/icons/telescope.png"); 89 | if (NULL == iconFC) 90 | { 91 | execStatus = EXIT_FAILURE; 92 | goto quit; 93 | } 94 | 95 | bg[0] = backgroundInit(video->renderer, "res/backgrounds/sky.png", map->width); 96 | if (NULL == bg[0]) 97 | { 98 | execStatus = EXIT_FAILURE; 99 | goto quit; 100 | } 101 | bg[0]->worldPosY = map->height - bg[0]->height; 102 | 103 | bg[1] = backgroundInit(video->renderer, "res/backgrounds/clouds.png", map->width); 104 | if (NULL == bg[1]) 105 | { 106 | execStatus = EXIT_FAILURE; 107 | goto quit; 108 | } 109 | bg[1]->worldPosY = map->height - bg[1]->height; 110 | 111 | bg[2] = backgroundInit(video->renderer, "res/backgrounds/sea.png", map->width); 112 | if (NULL == bg[2]) 113 | { 114 | execStatus = EXIT_FAILURE; 115 | goto quit; 116 | } 117 | bg[2]->worldPosY = map->height - bg[2]->height; 118 | 119 | bg[3] = backgroundInit(video->renderer, "res/backgrounds/far-grounds.png", map->width); 120 | if (NULL == bg[3]) 121 | { 122 | execStatus = EXIT_FAILURE; 123 | goto quit; 124 | } 125 | bg[3]->worldPosY = map->height - bg[3]->height; 126 | 127 | for (uint32_t i = 0; i < NUM_ENTITIES; i++) 128 | { 129 | entity[i] = entityInit(); 130 | if (NULL == entity[i]) 131 | { 132 | execStatus = EXIT_FAILURE; 133 | goto quit; 134 | } 135 | if (-1 == entityLoadSprite(entity[i], video->renderer, "res/sprites/characters.png")) 136 | { 137 | execStatus = EXIT_FAILURE; 138 | goto quit; 139 | } 140 | entity[i]->worldWidth = map->width; 141 | entity[i]->worldHeight = map->height; 142 | } 143 | 144 | // Set up individual entities. 145 | // Player. 146 | entity[PLAYER_ENTITY]->frameYoffset = 64; 147 | entity[PLAYER_ENTITY]->respawnPosX = 32; 148 | entity[PLAYER_ENTITY]->respawnPosY = 608; 149 | entity[PLAYER_ENTITY]->worldPosX = 32; 150 | entity[PLAYER_ENTITY]->worldPosY = 608; 151 | // NPCs. 152 | entity[1]->frameYoffset = 64; 153 | entity[3]->frameYoffset = 64; 154 | entity[5]->frameYoffset = 0; 155 | entity[6]->frameYoffset = 0; 156 | entity[1]->respawnPosX = 144; 157 | entity[1]->respawnPosY = 432; 158 | entity[2]->respawnPosX = 256; 159 | entity[2]->respawnPosY = 80; 160 | entity[3]->respawnPosX = 496; 161 | entity[3]->respawnPosY = 160; 162 | entity[4]->respawnPosX = 1776; 163 | entity[4]->respawnPosY = 80; 164 | entity[5]->respawnPosX = 1200; 165 | entity[5]->respawnPosY = 32; 166 | entity[6]->respawnPosX = 672; 167 | entity[6]->respawnPosY = 656; 168 | entity[1]->worldPosX = entity[1]->respawnPosX; 169 | entity[1]->worldPosY = entity[1]->respawnPosY; 170 | entity[2]->worldPosX = entity[2]->respawnPosX; 171 | entity[2]->worldPosY = entity[2]->respawnPosY; 172 | entity[3]->worldPosX = entity[3]->respawnPosX; 173 | entity[3]->worldPosY = entity[3]->respawnPosY; 174 | entity[4]->worldPosX = entity[4]->respawnPosX; 175 | entity[4]->worldPosY = entity[4]->respawnPosY; 176 | entity[5]->worldPosX = entity[5]->respawnPosX; 177 | entity[5]->worldPosY = entity[5]->respawnPosY; 178 | entity[6]->worldPosX = entity[6]->respawnPosX; 179 | entity[6]->worldPosY = entity[6]->respawnPosY; 180 | 181 | sfx[SFX_DEAD] = sfxInit("res/sfx/dead.wav"); 182 | sfx[SFX_IMPACT] = sfxInit("res/sfx/impact.wav"); 183 | sfx[SFX_JUMP] = sfxInit("res/sfx/jump.wav"); 184 | sfx[SFX_PAUSE] = sfxInit("res/sfx/pause.wav"); 185 | sfx[SFX_UNPAUSE] = sfxInit("res/sfx/unpause.wav"); 186 | 187 | uint8_t pause = 0; 188 | double cameraPosX = 0; 189 | double cameraPosY = map->height - video->windowHeight; 190 | double timeA = SDL_GetTicks(); 191 | double delay = 0; 192 | while (1) 193 | { 194 | double timeB = SDL_GetTicks(); 195 | double dTime = (timeB - timeA) / 1000; 196 | timeA = timeB; 197 | 198 | // Handle keyboard input. 199 | const uint8_t *keyState; 200 | SDL_PumpEvents(); 201 | if (SDL_PeepEvents(0, 0, SDL_PEEKEVENT, SDL_QUIT, SDL_QUIT) > 0) 202 | { 203 | goto quit; 204 | } 205 | keyState = SDL_GetKeyboardState(NULL); 206 | 207 | if (keyState[SDL_SCANCODE_Q]) goto quit; 208 | 209 | if (keyState[SDL_SCANCODE_ESCAPE]) { 210 | if (0 == pause) 211 | { 212 | if (mixer && config.audio.enabled) sfxPlay(sfx[SFX_PAUSE], CH_PAUSE, 0); 213 | } 214 | pause = 1; 215 | musicPause(); 216 | } 217 | 218 | if (keyState[SDL_SCANCODE_SPACE]) 219 | { 220 | if (pause) 221 | { 222 | if (mixer && config.audio.enabled) 223 | { 224 | sfxPlay(sfx[SFX_UNPAUSE], CH_UNPAUSE, 0); 225 | } 226 | pause = 0; 227 | musicResume(); 228 | } 229 | } 230 | 231 | if (pause) continue; 232 | 233 | // Limit FPS. 234 | if (config.video.limitFPS) 235 | { 236 | SDL_Delay(1000 / config.video.fps - dTime); 237 | } 238 | 239 | for (uint32_t i = 0; i < NUM_ENTITIES; i++) 240 | { 241 | entityFrame(entity[i], dTime); 242 | if ((entity[i]->flags >> IS_DEAD) & 1) 243 | { 244 | if (PLAYER_ENTITY != i) 245 | { 246 | if (mixer && config.audio.enabled) sfxPlay(sfx[SFX_IMPACT], CH_IMPACT, 0); 247 | entityRespawn(entity[i]); 248 | } 249 | } 250 | } 251 | 252 | if ((entity[PLAYER_ENTITY]->flags >> IS_DEAD) & 1) 253 | { 254 | if (0 == delay) 255 | { 256 | if (mixer && config.audio.enabled) 257 | { 258 | sfxPlay(sfx[SFX_DEAD], CH_DEAD, 0); 259 | } 260 | } 261 | delay += dTime; 262 | 263 | if (delay > 2) 264 | { 265 | entity[PLAYER_ENTITY]->flags &= ~(1 << IS_DEAD); 266 | for (uint32_t i = 0; i < NUM_ENTITIES; i++) 267 | entityRespawn(entity[i]); 268 | delay = 0; 269 | } 270 | } 271 | 272 | // Process keyboard input. 273 | // Reset IN_MOTION flag (in case no key is pressed). 274 | entity[PLAYER_ENTITY]->flags &= ~(1 << IN_MOTION); 275 | 276 | if (keyState[SDL_SCANCODE_LSHIFT]) 277 | { 278 | // Allow running only when not in mid-air. 279 | if (0 == ((entity[PLAYER_ENTITY]->flags >> IN_MID_AIR) & 1)) 280 | { 281 | entity[PLAYER_ENTITY]->velocityMax = 250; 282 | entity[PLAYER_ENTITY]->frameStart = RUN; 283 | entity[PLAYER_ENTITY]->frameEnd = RUN_MAX; 284 | } 285 | } 286 | else 287 | { 288 | // Don't allow to slow down in mid-air. 289 | if (0 == ((entity[PLAYER_ENTITY]->flags >> IN_MID_AIR) & 1)) 290 | { 291 | entity[PLAYER_ENTITY]->velocityMax = 100; 292 | entity[PLAYER_ENTITY]->frameStart = WALK; 293 | entity[PLAYER_ENTITY]->frameEnd = WALK_MAX; 294 | } 295 | } 296 | 297 | if (keyState[SDL_SCANCODE_A]) 298 | { 299 | if (0 == ((entity[PLAYER_ENTITY]->flags >> DIRECTION) & 1)) 300 | { 301 | entity[PLAYER_ENTITY]->velocity = -entity[PLAYER_ENTITY]->velocity; 302 | } 303 | entity[PLAYER_ENTITY]->flags |= 1 << IN_MOTION; 304 | entity[PLAYER_ENTITY]->flags |= 1 << DIRECTION; 305 | } 306 | 307 | if (keyState[SDL_SCANCODE_D]) 308 | { 309 | if ((entity[PLAYER_ENTITY]->flags >> DIRECTION) & 1) 310 | { 311 | entity[PLAYER_ENTITY]->velocity = -entity[PLAYER_ENTITY]->velocity; 312 | } 313 | 314 | entity[PLAYER_ENTITY]->flags |= 1 << IN_MOTION; 315 | entity[PLAYER_ENTITY]->flags &= ~(1 << DIRECTION); 316 | } 317 | 318 | if (0 == ((entity[PLAYER_ENTITY]->flags >> IN_MID_AIR) & 1)) 319 | { 320 | if (keyState[SDL_SCANCODE_SPACE]) 321 | { 322 | if (mixer && config.audio.enabled) 323 | { 324 | sfxPlay(sfx[SFX_JUMP], CH_JUMP, 0); 325 | } 326 | entity[PLAYER_ENTITY]->flags |= 1 << IS_JUMPING; 327 | entity[PLAYER_ENTITY]->velocityJump = entity[PLAYER_ENTITY]->velocity; 328 | } 329 | } 330 | 331 | if (keyState[SDL_SCANCODE_1]) 332 | { 333 | videoSetZoomLevel(video, video->zoomLevelInital); 334 | } 335 | 336 | if (keyState[SDL_SCANCODE_2]) 337 | { 338 | videoSetZoomLevel(video, video->zoomLevel - dTime); 339 | } 340 | 341 | if (keyState[SDL_SCANCODE_3]) 342 | { 343 | videoSetZoomLevel(video, video->zoomLevel + dTime); 344 | } 345 | 346 | if (keyState[SDL_SCANCODE_F]) 347 | { 348 | if (keyState[SDL_SCANCODE_UP]) cameraPosY -= (250 * dTime); 349 | if (keyState[SDL_SCANCODE_DOWN]) cameraPosY += (250 * dTime); 350 | if (keyState[SDL_SCANCODE_LEFT]) cameraPosX -= (250 * dTime); 351 | if (keyState[SDL_SCANCODE_RIGHT]) cameraPosX += (250 * dTime); 352 | } 353 | else 354 | { 355 | cameraPosX = entity[PLAYER_ENTITY]->worldPosX - video->windowWidth / (video->zoomLevel * 2) + (entity[PLAYER_ENTITY]->width / 2); 356 | cameraPosY = entity[PLAYER_ENTITY]->worldPosY - video->windowHeight / (video->zoomLevel * 2) + (entity[PLAYER_ENTITY]->height / 2); 357 | } 358 | 359 | // Set up collision detection. 360 | for (uint32_t i = 0; i < NUM_ENTITIES; i++) 361 | { 362 | if (mapCoordIsType(map, "floor", entity[i]->worldPosX, entity[i]->worldPosY + entity[i]->height)) 363 | { 364 | entity[i]->flags &= ~(1 << IN_MID_AIR); 365 | } 366 | else 367 | { 368 | entity[i]->flags |= 1 << IN_MID_AIR; 369 | } 370 | } 371 | 372 | // Set NPC behavior. 373 | for (uint32_t i = 1; i < NUM_ENTITIES; i++) 374 | { 375 | if (doIntersect(entity[PLAYER_ENTITY]->bb, entity[i]->bb)) 376 | { 377 | if (entity[PLAYER_ENTITY]->worldPosX > entity[i]->worldPosX) 378 | { 379 | entity[i]->flags |= 1 << DIRECTION; 380 | } 381 | else 382 | { 383 | entity[i]->flags &= ~(1 << DIRECTION); 384 | } 385 | 386 | entity[i]->flags |= 1 << IN_MOTION; 387 | } 388 | } 389 | 390 | // Set camera boundaries to map size. 391 | int32_t cameraMaxX = (map->width) - (video->windowWidth / video->zoomLevel); 392 | int32_t cameraMaxY = (map->height) - (video->windowHeight / video->zoomLevel); 393 | if (cameraPosX < 0) cameraPosX = 0; 394 | if (cameraPosY < 0) cameraPosY = 0; 395 | if (cameraPosX > cameraMaxX) cameraPosX = cameraMaxX; 396 | if (cameraPosY > cameraMaxY) cameraPosY = cameraMaxY; 397 | 398 | // Render scene. 399 | if (-1 == backgroundRender(video->renderer, bg[0], cameraPosX, cameraPosY)) 400 | { 401 | execStatus = EXIT_FAILURE; 402 | goto quit; 403 | } 404 | 405 | if (-1 == backgroundRender(video->renderer, bg[1], cameraPosX * 0.05, cameraPosY)) 406 | { 407 | execStatus = EXIT_FAILURE; 408 | goto quit; 409 | } 410 | 411 | if (-1 == backgroundRender(video->renderer, bg[2], cameraPosX * 0.15, cameraPosY)) 412 | { 413 | execStatus = EXIT_FAILURE; 414 | goto quit; 415 | } 416 | 417 | if (-1 == backgroundRender(video->renderer, bg[3], cameraPosX * 0.1, cameraPosY)) 418 | { 419 | execStatus = EXIT_FAILURE; 420 | goto quit; 421 | } 422 | 423 | if (-1 == mapRender(video->renderer, map, "Background", 1, 0, cameraPosX, cameraPosY)) 424 | { 425 | execStatus = EXIT_FAILURE; 426 | goto quit; 427 | } 428 | 429 | if (-1 == mapRender(video->renderer, map, "World", 1, 1, cameraPosX, cameraPosY)) 430 | { 431 | execStatus = EXIT_FAILURE; 432 | goto quit; 433 | } 434 | 435 | for (uint32_t i = 0; i < NUM_ENTITIES; i++) 436 | if (-1 == entityRender(video->renderer, entity[i], cameraPosX, cameraPosY)) 437 | { 438 | execStatus = EXIT_FAILURE; 439 | goto quit; 440 | } 441 | 442 | if (-1 == mapRender(video->renderer, map, "Overlay", 1, 2, cameraPosX, cameraPosY)) 443 | { 444 | execStatus = EXIT_FAILURE; 445 | goto quit; 446 | } 447 | 448 | if (keyState[SDL_SCANCODE_F]) 449 | if (-1 == iconRender(video->renderer, iconFC, video->windowWidth / video->zoomLevel - iconFC->width, 0)) 450 | { 451 | execStatus = EXIT_FAILURE; 452 | goto quit; 453 | } 454 | 455 | SDL_RenderPresent(video->renderer); 456 | SDL_RenderClear(video->renderer); 457 | } 458 | 459 | // Free allocated memory and exit. 460 | quit: 461 | for (uint32_t i = 0; i < NUM_SFX; i++) 462 | { 463 | sfxFree(sfx[i]); 464 | } 465 | 466 | for (uint32_t i = 0; i < NUM_ENTITIES; i++) 467 | { 468 | entityFree(entity[i]); 469 | } 470 | 471 | for (uint32_t i = 0; i < NUM_BACKGROUNDS; i++) 472 | { 473 | backgroundFree(bg[i]); 474 | } 475 | 476 | iconFree(iconFC); 477 | musicFree(music); 478 | mixerFree(mixer); 479 | mapFree(map); 480 | videoTerminate(video); 481 | 482 | return execStatus; 483 | } 484 | -------------------------------------------------------------------------------- /src/map.c: -------------------------------------------------------------------------------- 1 | /** @file map.c 2 | * @ingroup Map 3 | * @defgroup Map 4 | * @brief Handles TMX (Tile Map XML) map files that can be edited using the 5 | * Tiled Map Editor. 6 | * @author Michael Fitzmayer 7 | * @copyright "THE BEER-WARE LICENCE" (Revision 42) 8 | */ 9 | 10 | #include 11 | #include 12 | #include "map.h" 13 | 14 | /** 15 | * @brief Check whether a tile is from a specific type or not. 16 | * @param map the map. 17 | * @param type name of the tile type to look for. 18 | * @param xPos coordinate along the x-axis. 19 | * @param yPos coordinate along the y-axis. 20 | * @return 1 if the tile is of the specific type, 0 if not. 21 | * @ingroup Map 22 | */ 23 | uint8_t mapCoordIsType(Map *map, const char *type, double xPos, double yPos) 24 | { 25 | xPos = xPos / map->map->tile_width + 1; 26 | yPos = yPos / map->map->tile_height; 27 | 28 | // Prevent segfaults by setting boundaries. 29 | if ((xPos < 0) || 30 | (yPos < 0) || 31 | (xPos > map->map->width) || 32 | (yPos > map->map->height)) 33 | { 34 | return 0; 35 | } 36 | 37 | tmx_layer *layers = map->map->ly_head; 38 | while(layers) 39 | { 40 | uint16_t gid = layers->content.gids[((int32_t)yPos * map->map->width) + (int32_t)xPos] & TMX_FLIP_BITS_REMOVAL; 41 | if (NULL != map->map->tiles[gid]) 42 | { 43 | if (NULL != map->map->tiles[gid]->type) 44 | { 45 | if (0 == strcmp(type, map->map->tiles[gid]->type)) 46 | { 47 | return 1; 48 | } 49 | } 50 | } 51 | layers = layers->next; 52 | } 53 | 54 | return 0; 55 | } 56 | 57 | /** 58 | * @brief Free map. See @ref struct Map. 59 | * @param map the map that should be freed. 60 | * @ingroup Map 61 | */ 62 | void mapFree(Map *map) 63 | { 64 | tmx_map_free(map->map); 65 | } 66 | 67 | /** 68 | * @brief Initialise map. See @ref struct Map. 69 | * @param filename the TMX map file to load. 70 | * @return Map on success, NULL on error. 71 | * @ingroup Map 72 | */ 73 | Map *mapInit(const char *filename) 74 | { 75 | static Map *map; 76 | map = malloc(sizeof(struct map_t)); 77 | if (NULL == map) 78 | { 79 | fprintf(stderr, "mapInit(): error allocating memory.\n"); 80 | return NULL; 81 | } 82 | 83 | map->map = tmx_load(filename); 84 | if (NULL == map->map) 85 | { 86 | fprintf(stderr, "%s\n", tmx_strerr()); 87 | return NULL; 88 | } 89 | 90 | map->height = map->map->height * map->map->tile_height; 91 | map->width = map->map->width * map->map->tile_width; 92 | map->worldPosX = 0; 93 | map->worldPosY = 0; 94 | 95 | for (uint8_t i = 0; i < MAX_TEXTURES_PER_MAP; i++) 96 | { 97 | map->texture[i] = NULL; 98 | } 99 | 100 | return map; 101 | } 102 | 103 | /** 104 | * @brief Render map on screen. 105 | * @param renderer SDL's rendering context. See @ref struct Video. 106 | * @param map the map that should be rendered. 107 | * @param name substring of the layer name(s) that should be rendered. 108 | * @param bg boolean value to determine if the map's background colour 109 | * should be rendered or not. If set to 0, the background 110 | * stays transparent. 111 | * @param index determine the texture index. The total amount of textures 112 | * per map is defined by MAP_TEXTURES_PER_MAP. 113 | * @param cameraPosX camera position along the x-axis. 114 | * @param cameraPosY camera position along the y-axis. 115 | * @return 0 on success, -1 on error. 116 | * @ingroup Map 117 | */ 118 | int8_t mapRender( 119 | SDL_Renderer *renderer, 120 | Map *map, 121 | const char *name, 122 | uint8_t bg, 123 | uint8_t index, 124 | double cameraPosX, 125 | double cameraPosY) 126 | { 127 | // Render texture if already generated. 128 | if (map->texture[index]) 129 | { 130 | double renderPosX = map->worldPosX - cameraPosX; 131 | double renderPosY = map->worldPosY - cameraPosY; 132 | 133 | SDL_Rect dst = 134 | { 135 | renderPosX, 136 | renderPosY, 137 | map->map->width * map->map->tile_width, 138 | map->map->height * map->map->tile_height 139 | }; 140 | if (-1 == SDL_RenderCopyEx(renderer, map->texture[index], NULL, &dst, 0, NULL, SDL_FLIP_NONE)) 141 | { 142 | fprintf(stderr, "%s\n", SDL_GetError()); 143 | return -1; 144 | } 145 | return 0; 146 | } 147 | 148 | // Generate texture. 149 | map->texture[index] = SDL_CreateTexture( 150 | renderer, 151 | SDL_PIXELFORMAT_ARGB8888, 152 | SDL_TEXTUREACCESS_TARGET, 153 | map->map->width * map->map->tile_width, 154 | map->map->height * map->map->tile_height); 155 | 156 | if (NULL == map->texture[index]) 157 | { 158 | fprintf(stderr, "%s\n", SDL_GetError()); 159 | return -1; 160 | } 161 | 162 | SDL_Texture *tileset = IMG_LoadTexture(renderer, "res/tilesets/tileset.png"); 163 | if (NULL == tileset) 164 | { 165 | fprintf(stderr, "%s\n", SDL_GetError()); 166 | return -1; 167 | } 168 | 169 | if (0 != SDL_SetRenderTarget(renderer, map->texture[index])) 170 | { 171 | fprintf(stderr, "%s\n", SDL_GetError()); 172 | return -1; 173 | } 174 | 175 | if (bg) 176 | { 177 | SDL_SetRenderDrawColor( 178 | renderer, 179 | (map->map->backgroundcolor >> 16) & 0xFF, 180 | (map->map->backgroundcolor >> 8) & 0xFF, 181 | (map->map->backgroundcolor) & 0xFF, 182 | 255); 183 | } 184 | 185 | tmx_layer *layers = map->map->ly_head; 186 | while(layers) 187 | { 188 | uint32_t gid; 189 | SDL_Rect dst; 190 | SDL_Rect src; 191 | tmx_tileset *ts; 192 | 193 | if ((layers->visible) && (NULL != strstr(layers->name, name))) 194 | { 195 | for (uint32_t ih = 0; ih < map->map->height; ih++) 196 | { 197 | for (uint32_t iw = 0; iw < map->map->width; iw++) 198 | { 199 | gid = layers->content.gids[(ih * map->map->width) + iw] & TMX_FLIP_BITS_REMOVAL; 200 | if (NULL != map->map->tiles[gid]) 201 | { 202 | ts = map->map->tiles[gid]->tileset; 203 | src.x = map->map->tiles[gid]->ul_x; 204 | src.y = map->map->tiles[gid]->ul_y; 205 | src.w = dst.w = ts->tile_width; 206 | src.h = dst.h = ts->tile_height; 207 | dst.x = iw * ts->tile_width; 208 | dst.y = ih * ts->tile_height; 209 | SDL_RenderCopy(renderer, tileset, &src, &dst); 210 | } 211 | } 212 | } 213 | } 214 | layers = layers->next; 215 | } 216 | 217 | // Switch back to default render target. 218 | if (0 != SDL_SetRenderTarget(renderer, NULL)) 219 | { 220 | fprintf(stderr, "%s\n", SDL_GetError()); 221 | return -1; 222 | } 223 | 224 | if (0 != SDL_SetTextureBlendMode(map->texture[index], SDL_BLENDMODE_BLEND)) 225 | { 226 | fprintf(stderr, "%s\n", SDL_GetError()); 227 | return -1; 228 | } 229 | 230 | return 0; 231 | } 232 | -------------------------------------------------------------------------------- /src/map.h: -------------------------------------------------------------------------------- 1 | /** @file main.h 2 | * @ingroup Map 3 | */ 4 | 5 | #ifndef MAP_h 6 | #define MAP_h 7 | 8 | #include 9 | #include 10 | #include "tmx/tmx.h" 11 | 12 | /** 13 | * @def MAX_TEXTURES_PER_MAP 14 | * The maximum number of textures (layers) per map. 15 | * @ingroup Map 16 | */ 17 | #define MAX_TEXTURES_PER_MAP 5 18 | 19 | /** 20 | * @ingroup Map 21 | */ 22 | typedef struct map_t 23 | { 24 | tmx_map *map; 25 | SDL_Texture *texture[MAX_TEXTURES_PER_MAP]; 26 | uint32_t height; 27 | uint32_t width; 28 | double worldPosX; 29 | double worldPosY; 30 | } Map; 31 | 32 | uint8_t mapCoordIsType(Map *map, const char *type, double xPos, double yPos); 33 | void mapFree(Map *map); 34 | Map *mapInit(const char *filename); 35 | int8_t mapRender(SDL_Renderer *renderer, Map *map, const char *name, uint8_t bg, uint8_t index, double cameraPosX, double cameraPosY); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/tmx/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Bayle Jonathan 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 15 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/tmx/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | include ../../config.mk 4 | 5 | all: 6 | make -C ../../ 7 | 8 | clean: 9 | make -C ../../ clean 10 | -------------------------------------------------------------------------------- /src/tmx/tmx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include /* int32_t */ 5 | #include 6 | 7 | #include "tmx.h" 8 | #include "tsx.h" 9 | #include "tmx_utils.h" 10 | 11 | /* 12 | Public globals 13 | */ 14 | 15 | void* (*tmx_alloc_func) (void *address, size_t len) = NULL; 16 | void (*tmx_free_func ) (void *address) = NULL; 17 | void* (*tmx_img_load_func) (const char *p) = NULL; 18 | void (*tmx_img_free_func) (void *address) = NULL; 19 | 20 | /* 21 | Public functions 22 | */ 23 | 24 | tmx_map* tmx_load(const char *path) { 25 | tmx_map *map = NULL; 26 | set_alloc_functions(); 27 | map = parse_xml(NULL, path); 28 | map_post_parsing(&map); 29 | return map; 30 | } 31 | 32 | tmx_map* tmx_load_buffer(const char *buffer, int len) { 33 | tmx_map *map = NULL; 34 | set_alloc_functions(); 35 | map = parse_xml_buffer(NULL, buffer, len); 36 | map_post_parsing(&map); 37 | return map; 38 | } 39 | 40 | tmx_map* tmx_load_fd(int fd) { 41 | tmx_map *map = NULL; 42 | set_alloc_functions(); 43 | map = parse_xml_fd(NULL, fd); 44 | map_post_parsing(&map); 45 | return map; 46 | } 47 | 48 | tmx_map* tmx_load_callback(tmx_read_functor callback, void *userdata) { 49 | tmx_map *map = NULL; 50 | set_alloc_functions(); 51 | map = parse_xml_callback(NULL, callback, userdata); 52 | map_post_parsing(&map); 53 | return map; 54 | } 55 | 56 | void tmx_map_free(tmx_map *map) { 57 | if (map) { 58 | free_ts_list(map->ts_head); 59 | free_props(map->properties); 60 | free_layers(map->ly_head); 61 | tmx_free_func(map->tiles); 62 | tmx_free_func(map); 63 | } 64 | } 65 | 66 | tmx_tile* tmx_get_tile(tmx_map *map, unsigned int gid) { 67 | if (!map) { 68 | tmx_err(E_INVAL, "tmx_get_tile: invalid argument: map is NULL"); 69 | return NULL; 70 | } 71 | 72 | gid &= TMX_FLIP_BITS_REMOVAL; 73 | 74 | if (gid < map->tilecount) return map->tiles[gid]; 75 | 76 | return NULL; 77 | } 78 | 79 | tmx_property* tmx_get_property(tmx_properties *hash, const char *key) { 80 | if (hash == NULL) { 81 | return NULL; 82 | } 83 | return (tmx_property*) hashtable_get((void*)hash, key); 84 | } 85 | 86 | struct property_foreach_data { 87 | tmx_property_functor callback; 88 | void *userdata; 89 | }; 90 | 91 | static void property_foreach(void *val, void *userdata, const char *key UNUSED) { 92 | struct property_foreach_data *holder = ((struct property_foreach_data*)userdata); 93 | holder->callback((tmx_property*)val, holder->userdata); 94 | } 95 | 96 | void tmx_property_foreach(tmx_properties *hash, tmx_property_functor callback, void *userdata) { 97 | struct property_foreach_data holder; 98 | holder.callback = callback; 99 | holder.userdata = userdata; 100 | hashtable_foreach((void*)hash, property_foreach, &holder); 101 | } 102 | -------------------------------------------------------------------------------- /src/tmx/tmx.h: -------------------------------------------------------------------------------- 1 | /* 2 | TMX.H - TMX C LOADER 3 | Copyright (c) 2013-2017, Bayle Jonathan 4 | 5 | Data Structures storing the map, and function prototypes 6 | 7 | See : (I'm using names from this documentation) 8 | http://doc.mapeditor.org/reference/tmx-map-format/ 9 | */ 10 | 11 | #pragma once 12 | 13 | #ifndef TMX_H 14 | #define TMX_H 15 | 16 | #include 17 | #include 18 | 19 | #ifndef TMXEXPORT 20 | #define TMXEXPORT 21 | #endif 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | #define TMX_FLIPPED_HORIZONTALLY 0x80000000 28 | #define TMX_FLIPPED_VERTICALLY 0x40000000 29 | #define TMX_FLIPPED_DIAGONALLY 0x20000000 30 | #define TMX_FLIP_BITS_REMOVAL 0x1FFFFFFF 31 | 32 | /* 33 | Configuration 34 | */ 35 | /* Custom realloc and free function, for memalloc debugging purposes 36 | Please modify these values once before you use tmx_load */ 37 | TMXEXPORT extern void* (*tmx_alloc_func) (void *address, size_t len); /* realloc */ 38 | TMXEXPORT extern void (*tmx_free_func ) (void *address); /* free */ 39 | 40 | /* load/free tmx_image->resource_image, you should set this if you want 41 | the library to load/free images */ 42 | TMXEXPORT extern void* (*tmx_img_load_func) (const char *path); 43 | TMXEXPORT extern void (*tmx_img_free_func) (void *address); 44 | 45 | /* 46 | Data Structures 47 | */ 48 | 49 | enum tmx_map_orient {O_NONE, O_ORT, O_ISO, O_STA, O_HEX}; 50 | enum tmx_map_renderorder {R_NONE, R_RIGHTDOWN, R_RIGHTUP, R_LEFTDOWN, R_LEFTUP}; 51 | enum tmx_stagger_index {SI_NONE, SI_EVEN, SI_ODD}; 52 | enum tmx_stagger_axis {SA_NONE, SA_X, SA_Y}; 53 | enum tmx_layer_type {L_NONE, L_LAYER, L_OBJGR, L_IMAGE, L_GROUP}; 54 | enum tmx_objgr_draworder {G_NONE, G_INDEX, G_TOPDOWN}; 55 | enum tmx_obj_type {OT_NONE, OT_SQUARE, OT_POLYGON, OT_POLYLINE, OT_ELLIPSE, OT_TILE, OT_TEXT}; 56 | enum tmx_property_type {PT_NONE, PT_INT, PT_FLOAT, PT_BOOL, PT_STRING, PT_COLOR, PT_FILE}; 57 | enum tmx_horizontal_align {HA_NONE, HA_LEFT, HA_CENTER, HA_RIGHT}; 58 | enum tmx_vertical_align {VA_NONE, VA_TOP, VA_CENTER, VA_BOTTOM}; 59 | 60 | /* Typedefs of the structures below */ 61 | typedef struct _tmx_prop tmx_property; 62 | typedef struct _tmx_img tmx_image; 63 | typedef struct _tmx_frame tmx_anim_frame; 64 | typedef struct _tmx_tile tmx_tile; 65 | typedef struct _tmx_ts tmx_tileset; 66 | typedef struct _tmx_ts_list tmx_tileset_list; 67 | typedef struct _tmx_shape tmx_shape; 68 | typedef struct _tmx_text tmx_text; 69 | typedef struct _tmx_obj tmx_object; 70 | typedef struct _tmx_objgr tmx_object_group; 71 | typedef struct _tmx_layer tmx_layer; 72 | typedef struct _tmx_map tmx_map; 73 | typedef void tmx_properties; /* hashtable, use function tmx_get_property(...) */ 74 | 75 | typedef union { 76 | int integer; 77 | float decimal; 78 | void *pointer; 79 | } tmx_user_data; 80 | 81 | typedef union { 82 | int integer, boolean; /* type = int or bool */ 83 | float decimal; /* type = float */ 84 | char *string, *file; /* default and type = string or file */ 85 | unsigned int color; /* type = color */ 86 | } tmx_property_value; 87 | 88 | struct _tmx_prop { /* and */ 89 | char *name; 90 | enum tmx_property_type type; 91 | tmx_property_value value; 92 | }; 93 | 94 | struct _tmx_img { /* */ 95 | char *source; 96 | unsigned int trans; /* bytes : RGB */ 97 | int uses_trans; 98 | unsigned long width, height; 99 | /*char *format; Not currently implemented in QtTiled 100 | char *data;*/ 101 | void *resource_image; 102 | }; 103 | 104 | struct _tmx_frame { /* */ 105 | unsigned int tile_id; 106 | unsigned int duration; 107 | }; 108 | 109 | struct _tmx_tile { /* */ 110 | unsigned int id; 111 | tmx_tileset *tileset; 112 | unsigned int ul_x, ul_y; /* upper-left coordinate of this tile */ 113 | 114 | tmx_image *image; 115 | tmx_object *collision; 116 | 117 | unsigned int animation_len; 118 | tmx_anim_frame *animation; 119 | 120 | char *type; 121 | tmx_properties *properties; 122 | 123 | tmx_user_data user_data; 124 | }; 125 | 126 | struct _tmx_ts { /* and */ 127 | int is_embedded; /* used internally to free this node */ 128 | char *name; 129 | 130 | unsigned int tile_width, tile_height; 131 | unsigned int spacing, margin; 132 | int x_offset, y_offset; /* tileoffset */ 133 | 134 | unsigned int tilecount; 135 | tmx_image *image; 136 | 137 | tmx_user_data user_data; 138 | tmx_properties *properties; 139 | tmx_tile *tiles; 140 | }; 141 | 142 | struct _tmx_ts_list { /* Linked list */ 143 | unsigned int firstgid; 144 | tmx_tileset *tileset; 145 | tmx_tileset_list *next; 146 | }; 147 | 148 | struct _tmx_shape { /* and */ 149 | double **points; /* point[i][x,y]; x=0 y=1 */ 150 | int points_len; 151 | }; 152 | 153 | struct _tmx_text { /* */ 154 | char *fontfamily; 155 | int pixelsize; 156 | unsigned int color; 157 | 158 | int wrap; /* 0 == false */ 159 | int bold; 160 | int italic; 161 | int underline; 162 | int strikeout; 163 | int kerning; 164 | 165 | enum tmx_horizontal_align halign; 166 | enum tmx_vertical_align valign; 167 | 168 | char *text; 169 | }; 170 | 171 | struct _tmx_obj { /* */ 172 | unsigned int id; 173 | enum tmx_obj_type obj_type; 174 | 175 | double x, y; 176 | double width, height; 177 | 178 | union { 179 | int gid; 180 | tmx_shape *shape; 181 | tmx_text *text; 182 | } content; 183 | 184 | int visible; /* 0 == false */ 185 | double rotation; 186 | 187 | char *name, *type; 188 | tmx_properties *properties; 189 | tmx_object *next; 190 | }; 191 | 192 | struct _tmx_objgr { /* */ 193 | unsigned int color; /* bytes : RGB */ 194 | enum tmx_objgr_draworder draworder; 195 | tmx_object *head; 196 | }; 197 | 198 | struct _tmx_layer { /* or or */ 199 | char *name; 200 | double opacity; 201 | int visible; /* 0 == false */ 202 | int offsetx, offsety; 203 | 204 | enum tmx_layer_type type; 205 | union layer_content { 206 | int32_t *gids; 207 | tmx_object_group *objgr; 208 | tmx_image *image; 209 | tmx_layer *group_head; 210 | } content; 211 | 212 | tmx_user_data user_data; 213 | tmx_properties *properties; 214 | tmx_layer *next; 215 | }; 216 | 217 | struct _tmx_map { /* (Head of the data structure) */ 218 | enum tmx_map_orient orient; 219 | 220 | unsigned int width, height; 221 | unsigned int tile_width, tile_height; 222 | 223 | enum tmx_stagger_index stagger_index; 224 | enum tmx_stagger_axis stagger_axis; 225 | int hexsidelength; 226 | 227 | unsigned int backgroundcolor; /* bytes : RGB */ 228 | enum tmx_map_renderorder renderorder; 229 | 230 | tmx_properties *properties; 231 | tmx_tileset_list *ts_head; 232 | tmx_layer *ly_head; 233 | 234 | unsigned int tilecount; /* length of map->tiles */ 235 | tmx_tile **tiles; /* GID indexed tile array (array of pointers to tmx_tile) */ 236 | 237 | tmx_user_data user_data; 238 | }; 239 | 240 | /* 241 | Functions 242 | */ 243 | 244 | /* Loads a map from file at `path` and returns the head of the data structure 245 | returns NULL if an error occurred and set tmx_errno */ 246 | TMXEXPORT tmx_map* tmx_load(const char *path); 247 | 248 | /* Loads a map from file at `path` and returns the head of the data structure 249 | returns NULL if an error occurred and set tmx_errno */ 250 | TMXEXPORT tmx_map* tmx_load_buffer(const char *buffer, int len); 251 | 252 | /* Loads a map from a file descriptor and returns the head of the data structure 253 | The file descriptor will not be closed 254 | returns NULL if an error occurred and set tmx_errno */ 255 | TMXEXPORT tmx_map* tmx_load_fd(int fd); 256 | 257 | /* Callback used by tmx_load to delegate reading to client code 258 | userdata(in): user data passed to tmx_load() 259 | buffer(in): to store read bytes 260 | len: how many bytes to read (length of buffer) */ 261 | typedef int (*tmx_read_functor)(void *userdata, char *buffer, int len); 262 | /* Loads a map using the given read callback and returns the head of the data structure 263 | returns NULL if an error occurred and set tmx_errno */ 264 | TMXEXPORT tmx_map* tmx_load_callback(tmx_read_functor callback, void *userdata); 265 | 266 | /* Frees the map data structure */ 267 | TMXEXPORT void tmx_map_free(tmx_map *map); 268 | 269 | /* Returns the tile associated with this gid, returns NULL if it fails */ 270 | TMXEXPORT tmx_tile* tmx_get_tile(tmx_map *map, unsigned int gid); 271 | 272 | /* Returns the tmx_property from given hashtable and key, returns NULL if not found */ 273 | TMXEXPORT tmx_property* tmx_get_property(tmx_properties *hash, const char *key); 274 | 275 | /* ForEach callback type to be used with function tmx_property_foreach(...) */ 276 | typedef void (*tmx_property_functor)(tmx_property *property, void *userdata); 277 | /* Calls `callback` for each entry in the property hashtable, order of entries is random */ 278 | TMXEXPORT void tmx_property_foreach(tmx_properties *hash, tmx_property_functor callback, void *userdata); 279 | 280 | /* 281 | Error handling 282 | each time a function fails, tmx_errno is set 283 | */ 284 | 285 | /* Possible values for `tmx_errno` */ 286 | typedef enum _tmx_error_codes { 287 | /* Syst */ 288 | E_NONE = 0, /* No error so far */ 289 | E_UNKN = 1, /* See the message for more details */ 290 | E_INVAL = 2, /* Invalid argument */ 291 | E_ALLOC = 8, /* Mem alloc */ 292 | /* I/O */ 293 | E_ACCESS = 10, /* privileges needed */ 294 | E_NOENT = 11, /* File not found */ 295 | E_FORMAT = 12, /* Unsupported/Unknown file format */ 296 | E_ENCCMP = 13, /* Unsupported/Unknown data encoding/compression */ 297 | E_FONCT = 16, /* Functionality not enabled */ 298 | E_BDATA = 20, /* B64 bad data */ 299 | E_ZDATA = 21, /* Zlib corrupted data */ 300 | E_XDATA = 22, /* XML corrupted data */ 301 | E_CDATA = 24, /* CSV corrupted data */ 302 | E_MISSEL = 30 /* Missing element, incomplete source */ 303 | } tmx_error_codes; 304 | 305 | extern tmx_error_codes tmx_errno; 306 | 307 | /* Prints the error message prefixed with the parameter */ 308 | TMXEXPORT void tmx_perror(const char*); 309 | /* Returns the error message for the current value of `tmx_errno` */ 310 | TMXEXPORT const char* tmx_strerr(void); /* FIXME errno parameter ? (as strerror) */ 311 | 312 | #ifdef __cplusplus 313 | } 314 | #endif 315 | 316 | #endif /* TMX_H */ 317 | -------------------------------------------------------------------------------- /src/tmx/tmx_err.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "tmx.h" 5 | #include "tsx.h" 6 | #include "tmx_utils.h" 7 | 8 | tmx_error_codes tmx_errno = E_NONE; 9 | 10 | static char *errmsgs[] = { 11 | "No error", 12 | "Memory alloc failed", 13 | "Missing privileges to access the file", 14 | "File not found", 15 | "Unsupproted/Unknown map file format" 16 | }; 17 | 18 | char custom_msg[256]; 19 | 20 | const char* tmx_strerr(void) { 21 | char *msg; 22 | switch(tmx_errno) { 23 | case E_NONE: msg = errmsgs[0]; break; 24 | case E_ALLOC: msg = errmsgs[1]; break; 25 | case E_ACCESS: msg = errmsgs[2]; break; 26 | case E_NOENT: msg = errmsgs[3]; break; 27 | case E_FORMAT: msg = errmsgs[4]; break; 28 | default: msg = custom_msg; 29 | } 30 | return msg; 31 | } 32 | 33 | void tmx_perror(const char *pos) { 34 | const char *msg = tmx_strerr(); 35 | fprintf(stderr, "%s: %s\n", pos, msg); 36 | } 37 | -------------------------------------------------------------------------------- /src/tmx/tmx_hash.c: -------------------------------------------------------------------------------- 1 | /* 2 | Hashtable 3 | 4 | This implementation is based on libxml/hash.h provided by libxml2. 5 | */ 6 | 7 | #include 8 | 9 | #include "tmx.h" 10 | #include "tsx.h" 11 | #include "tmx_utils.h" 12 | 13 | void* mk_hashtable(unsigned int initial_size) { 14 | // Auto-resize is supported 15 | setup_libxml_mem(); 16 | return (void*)xmlHashCreate(initial_size); 17 | } 18 | 19 | void hashtable_set(void *hashtable, const char *key, void *val, hashtable_entry_deallocator deallocator) { 20 | // Set or update value, key string is duplicated, deallocator may be NULL if values were not allocated 21 | xmlHashUpdateEntry((xmlHashTablePtr)hashtable, (const xmlChar*)key, val, (xmlHashDeallocator)deallocator); 22 | } 23 | 24 | void* hashtable_get(void *hashtable, const char *key) { 25 | return xmlHashLookup((xmlHashTablePtr)hashtable, (const xmlChar*)key); 26 | } 27 | 28 | void hashtable_rm(void *hashtable, const char *key, hashtable_entry_deallocator deallocator) { 29 | xmlHashRemoveEntry((xmlHashTablePtr)hashtable, (const xmlChar*)key, (xmlHashDeallocator)deallocator); 30 | } 31 | 32 | void free_hashtable(void *hashtable, hashtable_entry_deallocator deallocator) { 33 | xmlHashFree((xmlHashTablePtr)hashtable, (xmlHashDeallocator)deallocator); 34 | } 35 | 36 | void hashtable_foreach(void *hashtable, hashtable_foreach_functor functor, void *userdata) { 37 | xmlHashScan((xmlHashTablePtr)hashtable, (xmlHashScanner)functor, userdata); 38 | } 39 | 40 | void property_deallocator(void *val, const char *key UNUSED) { 41 | free_property((tmx_property*)val); 42 | } 43 | 44 | void tileset_deallocator(void *val, const char *key UNUSED) { 45 | free_ts((tmx_tileset*)val); 46 | } 47 | -------------------------------------------------------------------------------- /src/tmx/tmx_mem.c: -------------------------------------------------------------------------------- 1 | /* 2 | Node allocation 3 | */ 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "tmx.h" 10 | #include "tsx.h" 11 | #include "tmx_utils.h" 12 | 13 | void set_alloc_functions() { 14 | if (!tmx_alloc_func) tmx_alloc_func = realloc; 15 | if (!tmx_free_func) tmx_free_func = free; 16 | } 17 | 18 | static void* tmx_malloc(size_t len) { 19 | return tmx_alloc_func(NULL, len); 20 | } 21 | 22 | void setup_libxml_mem() { 23 | xmlMemSetup((xmlFreeFunc)tmx_free_func, (xmlMallocFunc)tmx_malloc, (xmlReallocFunc)tmx_alloc_func, (xmlStrdupFunc)tmx_strdup); 24 | } 25 | 26 | static void* node_alloc(size_t size) { 27 | void *res = tmx_alloc_func(NULL, size); 28 | if (res) { 29 | memset(res, 0, size); 30 | } else { 31 | tmx_errno = E_ALLOC; 32 | } 33 | return res; 34 | } 35 | 36 | tmx_property* alloc_prop(void) { 37 | return (tmx_property*)node_alloc(sizeof(tmx_property)); 38 | } 39 | 40 | tmx_image* alloc_image(void) { 41 | return (tmx_image*)node_alloc(sizeof(tmx_image)); 42 | } 43 | 44 | tmx_shape* alloc_shape(void) { 45 | return (tmx_shape*)node_alloc(sizeof(tmx_shape)); 46 | } 47 | 48 | tmx_text* alloc_text(void) { 49 | tmx_text *res = (tmx_text*)node_alloc(sizeof(tmx_text)); 50 | if (res) { 51 | res->pixelsize = 16; 52 | res->kerning = 1; 53 | res->valign = VA_TOP; 54 | res->halign = HA_LEFT; 55 | } 56 | return res; 57 | } 58 | 59 | tmx_object* alloc_object(void) { 60 | tmx_object *res = (tmx_object*)node_alloc(sizeof(tmx_object)); 61 | if (res) { 62 | res->visible = 1; 63 | } 64 | return res; 65 | } 66 | 67 | tmx_object_group* alloc_objgr(void) { 68 | return (tmx_object_group*)node_alloc(sizeof(tmx_object_group)); 69 | } 70 | 71 | tmx_layer* alloc_layer(void) { 72 | tmx_layer *res = (tmx_layer*)node_alloc(sizeof(tmx_layer)); 73 | if (res) { 74 | res->opacity = 1.0f; 75 | res->visible = 1; 76 | } 77 | return res; 78 | } 79 | 80 | tmx_tile* alloc_tiles(int count) { 81 | return (tmx_tile*)node_alloc(count * sizeof(tmx_tile)); 82 | } 83 | 84 | tmx_tileset* alloc_tileset(void) { 85 | return (tmx_tileset*)node_alloc(sizeof(tmx_tileset)); 86 | } 87 | 88 | tmx_tileset_list* alloc_tileset_list(void) { 89 | return (tmx_tileset_list*)node_alloc(sizeof(tmx_tileset_list)); 90 | } 91 | 92 | tmx_map* alloc_map(void) { 93 | return (tmx_map*)node_alloc(sizeof(tmx_map)); 94 | } 95 | 96 | /* 97 | Node free 98 | */ 99 | 100 | void free_property(tmx_property *p) { 101 | if (p) { 102 | tmx_free_func(p->name); 103 | if (p->type == PT_STRING || p->type == PT_FILE || p->type == PT_NONE) { 104 | tmx_free_func(p->value.string); 105 | } 106 | tmx_free_func(p); 107 | } 108 | } 109 | 110 | void free_props(tmx_properties *h) { 111 | free_hashtable((void*)h, property_deallocator); 112 | } 113 | 114 | void free_obj(tmx_object *o) { 115 | if (o) { 116 | free_obj(o->next); 117 | tmx_free_func(o->name); 118 | if (o->obj_type == OT_POLYGON || o->obj_type == OT_POLYLINE) { 119 | if (o->content.shape) { 120 | if (o->content.shape->points) { 121 | tmx_free_func(*(o->content.shape->points)); 122 | tmx_free_func(o->content.shape->points); 123 | } 124 | tmx_free_func(o->content.shape); 125 | } 126 | } 127 | else if (o->obj_type == OT_TEXT) { 128 | if (o->content.text) { 129 | if (o->content.text->fontfamily) tmx_free_func(o->content.text->fontfamily); 130 | if (o->content.text->text) tmx_free_func(o->content.text->text); 131 | tmx_free_func(o->content.text); 132 | } 133 | } 134 | tmx_free_func(o->type); 135 | free_props(o->properties); 136 | tmx_free_func(o); 137 | } 138 | } 139 | 140 | void free_objgr(tmx_object_group *o) { 141 | if (o) { 142 | free_obj(o->head); 143 | tmx_free_func(o); 144 | } 145 | } 146 | 147 | void free_image(tmx_image *i) { 148 | if (i) { 149 | tmx_free_func(i->source); 150 | if (tmx_img_free_func) { 151 | tmx_img_free_func(i->resource_image); 152 | } 153 | tmx_free_func(i); 154 | } 155 | } 156 | 157 | void free_layers(tmx_layer *l) { 158 | if (l) { 159 | free_layers(l->next); 160 | tmx_free_func(l->name); 161 | if (l->type == L_LAYER) { 162 | tmx_free_func(l->content.gids); 163 | } 164 | else if (l->type == L_OBJGR) { 165 | free_objgr(l->content.objgr); 166 | } 167 | else if (l->type == L_IMAGE) { 168 | free_image(l->content.image); 169 | } 170 | else if (l->type == L_GROUP) { 171 | free_layers(l->content.group_head); 172 | } 173 | free_props(l->properties); 174 | tmx_free_func(l); 175 | } 176 | } 177 | 178 | void free_tiles(tmx_tile *t, int tilecount) { 179 | int i; 180 | if (t) { 181 | for (i=0; iname); 194 | free_image(ts->image); 195 | free_props(ts->properties); 196 | free_tiles(ts->tiles, ts->tilecount); 197 | tmx_free_func(ts->tiles); 198 | tmx_free_func(ts); 199 | } 200 | } 201 | 202 | void free_ts_list(tmx_tileset_list *tsl) { 203 | if (tsl) { 204 | free_ts_list(tsl->next); 205 | if (tsl->tileset->is_embedded) { 206 | free_ts(tsl->tileset); 207 | } 208 | tmx_free_func(tsl); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/tmx/tmx_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | Utility functions 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include /* is */ 9 | 10 | #include "tmx.h" 11 | #include "tsx.h" 12 | #include "tmx_utils.h" 13 | 14 | /* 15 | BASE 64 16 | */ 17 | 18 | static const char b64enc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/"; 19 | 20 | char* b64_encode(const char *source, unsigned int length) { 21 | unsigned int i, mlen, r_pos; 22 | unsigned short dif, j; 23 | unsigned int frame = 0; 24 | char out[5]; 25 | char *res; 26 | 27 | mlen = 4 * length/3 + 1; /* +1 : returns a null-terminated string */ 28 | if (length%3) { 29 | mlen += 4; 30 | } 31 | 32 | res = (char*) tmx_alloc_func(NULL, mlen); 33 | if (!res) { 34 | tmx_errno = E_ALLOC; 35 | return NULL; 36 | } 37 | res[mlen-1] = '\0'; 38 | out[4] = '\0'; 39 | 40 | for (i=0; i>' inserts '0' */ 42 | dif = (length-i)/3 ? 3 : (length-i)%3; /* number of byte to read */ 43 | for (j=0; j 4chars 49 | . 2B red => 3chars + "=" 50 | . 1B red => 2chars + "==" 51 | */ 52 | for (j=0; j> 18); /* first 6 bits */ 54 | out[j] = b64enc[(int)out[j]]; 55 | frame = frame << 6; /* next 6b word */ 56 | } 57 | if (dif == 1) { 58 | out[2] = out [3] = '='; 59 | } else if (dif == 2) { 60 | out [3] = '='; 61 | } 62 | r_pos = (i/3)*4; 63 | strcpy(res+r_pos, out); 64 | } 65 | return res; 66 | } 67 | 68 | static char b64_value(char c) { 69 | if (c>='A' && c<='Z') { 70 | return c - 'A'; 71 | } else if (c>='a' && c<='z') { 72 | return c - 'a' + 26; 73 | } else if (c>='0' && c<='9') { 74 | return c - '0' + 52; 75 | } else if (c=='+') { 76 | return 62; 77 | } else if (c=='/') { 78 | return 63; 79 | } else if (c=='=') { 80 | return 0; 81 | } 82 | return -1; 83 | } 84 | 85 | char* b64_decode(const char *source, unsigned int *rlength) { /* NULL terminated string */ 86 | char *res, v; 87 | short j; 88 | unsigned int i; 89 | unsigned int in = 0; 90 | unsigned int src_len = (unsigned int)(strlen(source)); 91 | 92 | if (!source) { 93 | tmx_err(E_INVAL, "Base64: invalid argument: source is NULL"); 94 | return NULL; 95 | } 96 | 97 | if (src_len%4) { 98 | tmx_err(E_BDATA, "Base64: invalid source"); 99 | return NULL; /* invalid source */ 100 | } 101 | 102 | *rlength = (src_len/4)*3; 103 | res = (char*) tmx_alloc_func(NULL, *rlength); 104 | if (!res) { 105 | tmx_errno = E_ALLOC; 106 | return NULL; 107 | } 108 | 109 | for (i=0; i 146 | 147 | void* z_alloc(void *opaque UNUSED, unsigned int items, unsigned int size) { 148 | return tmx_alloc_func(NULL, items *size); 149 | } 150 | 151 | void z_free(void *opaque UNUSED, void *address) { 152 | tmx_free_func(address); 153 | } 154 | 155 | char* zlib_decompress(const char *source, unsigned int slength, unsigned int rlength) { 156 | int ret; 157 | char *res = NULL; 158 | z_stream strm; 159 | 160 | if (!source) { 161 | tmx_err(E_INVAL, "zlib_decompress: invalid argument: source is NULL"); 162 | return NULL; 163 | } 164 | 165 | strm.zalloc = z_alloc; 166 | strm.zfree = z_free; 167 | strm.opaque = Z_NULL; 168 | strm.next_in = (Bytef*)source; 169 | strm.avail_in = slength; 170 | 171 | res = (char*) tmx_alloc_func(NULL, rlength); 172 | if (!res) { 173 | tmx_errno = E_ALLOC; 174 | return NULL; 175 | } 176 | 177 | strm.next_out = (Bytef*)res; 178 | strm.avail_out = rlength; 179 | 180 | /* 15+32 to enable zlib and gzip decoding with automatic header detection */ 181 | if ((ret=inflateInit2(&strm, 15 + 32)) != Z_OK) { 182 | tmx_err(E_UNKN, "zlib_decompress: inflateInit2 returned %d\n", ret); 183 | goto cleanup; 184 | } 185 | 186 | ret = inflate(&strm, Z_FINISH); 187 | inflateEnd(&strm); 188 | 189 | if (ret != Z_OK && ret != Z_STREAM_END) { 190 | tmx_err(E_ZDATA, "zlib_decompress: inflate returned %d\n", ret); 191 | goto cleanup; 192 | } 193 | 194 | if (strm.avail_out != 0) { 195 | tmx_err(E_ZDATA, "layer contains not enough tiles"); 196 | goto cleanup; 197 | } 198 | if (strm.avail_in != 0) { 199 | /* FIXME There is remains in the source */ 200 | } 201 | 202 | return res; 203 | cleanup: 204 | tmx_free_func(res); 205 | return NULL; 206 | } 207 | 208 | #else 209 | 210 | char* zlib_decompress(const char *source, unsigned int slength, unsigned int rlength) { 211 | tmx_err(E_FONCT, "This library was not built with the zlib/gzip support"); 212 | return NULL; 213 | } 214 | 215 | #endif /* WANT_ZLIB */ 216 | 217 | /* 218 | Layer data decoders 219 | */ 220 | 221 | int data_decode(const char *source, enum enccmp_t type, size_t gids_count, int32_t **gids) { 222 | char *b64dec; 223 | unsigned int b64_len, i; 224 | 225 | if (type==CSV) { 226 | if (!(*gids = (int32_t*)tmx_alloc_func(NULL, gids_count * sizeof(int32_t)))) { 227 | tmx_errno = E_ALLOC; 228 | return 0; 229 | } 230 | for (i=0; itileset and tile->ul_x,y */ 266 | int set_tiles_runtime_props(tmx_tileset *ts) { 267 | unsigned int i, j; 268 | unsigned int tiles_x_count, ts_w, tx, ty; 269 | 270 | if (ts == NULL) { 271 | tmx_err(E_INVAL, "set_tiles_runtime_props: invalid argument: ts is NULL"); 272 | return 0; 273 | } 274 | 275 | /* reindex tiles (tiles are sorted, but not indexed correctly) */ 276 | for (i=ts->tilecount-1, j=0; jtilecount; j++, i--) { 277 | if (ts->tiles[i].id > 0L && ts->tiles[i].id != i) { 278 | memcpy(ts->tiles+(ts->tiles[i].id), ts->tiles+i, sizeof(tmx_tile)); 279 | memset(ts->tiles+i, 0, sizeof(tmx_tile)); 280 | } 281 | } 282 | 283 | for (i=0; itilecount; i++) { 284 | ts->tiles[i].id = i; 285 | ts->tiles[i].tileset = ts; 286 | 287 | /* set bitmap's region x and y coordinates */ 288 | ts_w = ts->image->width - 2 * (ts->margin) + ts->spacing; 289 | 290 | tiles_x_count = ts_w / (ts->tile_width + ts->spacing); 291 | 292 | tx = i % tiles_x_count; 293 | ty = i / tiles_x_count; 294 | 295 | ts->tiles[i].ul_x = ts->margin + (tx * ts->tile_width) + (tx * ts->spacing); 296 | ts->tiles[i].ul_y = ts->margin + (ty * ts->tile_height) + (ty * ts->spacing); 297 | } 298 | 299 | return 1; 300 | } 301 | 302 | /* Creates the array at map->tiles */ 303 | int mk_map_tile_array(tmx_map *map) { 304 | unsigned int i; 305 | tmx_tileset_list *ts, *max_ts; 306 | 307 | if (!map) { 308 | tmx_err(E_INVAL, "mk_map_tile_array: invalid argument: map is NULL"); 309 | return 0; 310 | } 311 | 312 | if (!(map->ts_head)) { 313 | /* no tileset => nothing to do */ 314 | return 1; 315 | } 316 | 317 | /* Counts total tile count */ 318 | ts = max_ts = map->ts_head; 319 | while (ts != NULL) { 320 | if (ts->firstgid > max_ts->firstgid) { 321 | max_ts = ts; 322 | } 323 | ts = ts->next; 324 | } 325 | if (max_ts->tileset->image) { 326 | map->tilecount = max_ts->firstgid + max_ts->tileset->tilecount; 327 | } 328 | else { 329 | /* Gets the last id, ts->tiles is sorted by id */ 330 | map->tilecount = max_ts->firstgid + max_ts->tileset->tiles[max_ts->tileset->tilecount - 1].id + 1; 331 | } 332 | 333 | /* Allocates the GID indexed tile array */ 334 | if (!(map->tiles = tmx_alloc_func(NULL, map->tilecount * sizeof(void*)))) { 335 | tmx_errno = E_ALLOC; 336 | return 0; 337 | } 338 | memset(map->tiles, 0, map->tilecount * sizeof(void*)); 339 | 340 | /* Populates the array */ 341 | map->tiles[0] = NULL; /* GIDs start from 1 */ 342 | ts = map->ts_head; 343 | while (ts != NULL) { 344 | for (i=0; itileset->tilecount; i++) { 345 | map->tiles[ts->firstgid + ts->tileset->tiles[i].id] = &(ts->tileset->tiles[i]); 346 | } 347 | ts = ts->next; 348 | } 349 | 350 | return 1; 351 | } 352 | 353 | /* "orthogonal" -> ORT */ 354 | enum tmx_map_orient parse_orient(const char *orient_str) { 355 | if (!strcmp(orient_str, "orthogonal")) { 356 | return O_ORT; 357 | } 358 | if (!strcmp(orient_str, "isometric")) { 359 | return O_ISO; 360 | } 361 | if (!strcmp(orient_str, "staggered")) { 362 | return O_STA; 363 | } 364 | if (!strcmp(orient_str, "hexagonal")) { 365 | return O_HEX; 366 | } 367 | return O_NONE; 368 | } 369 | 370 | /* "left-up" -> R_LEFTUP */ 371 | enum tmx_map_renderorder parse_renderorder(const char *renderorder) { 372 | if (renderorder == NULL || !strcmp(renderorder, "right-down")) { 373 | return R_RIGHTDOWN; 374 | } 375 | if (!strcmp(renderorder, "right-up")) { 376 | return R_RIGHTUP; 377 | } 378 | if (!strcmp(renderorder, "left-down")) { 379 | return R_LEFTDOWN; 380 | } 381 | if (!strcmp(renderorder, "left-up")) { 382 | return R_LEFTUP; 383 | } 384 | return R_NONE; 385 | } 386 | 387 | /* "index" -> G_INDEX */ 388 | enum tmx_objgr_draworder parse_objgr_draworder(const char *draworder) { 389 | if (draworder == NULL || !strcmp(draworder, "topdown")) { 390 | return G_TOPDOWN; 391 | } 392 | if (!strcmp(draworder, "index")) { 393 | return G_INDEX; 394 | } 395 | return G_NONE; 396 | } 397 | 398 | /* "even" -> SI_EVEN */ 399 | enum tmx_stagger_index parse_stagger_index(const char *staggerindex) { 400 | if (staggerindex == NULL || !strcmp(staggerindex, "odd")) { 401 | return SI_ODD; 402 | } 403 | if (!strcmp(staggerindex, "even")) { 404 | return SI_EVEN; 405 | } 406 | return SI_NONE; 407 | } 408 | 409 | /* "y" -> SA_Y */ 410 | enum tmx_stagger_axis parse_stagger_axis(const char *staggeraxis) { 411 | if (staggeraxis == NULL || !strcmp(staggeraxis, "y")) { 412 | return SA_Y; 413 | } 414 | if (!strcmp(staggeraxis, "columns")) { 415 | return SA_X; 416 | } 417 | return SA_NONE; 418 | } 419 | 420 | /* "integer" -> PT_INT */ 421 | enum tmx_property_type parse_property_type(const char *propertytype) { 422 | if (propertytype == NULL || !strcmp(propertytype, "string")) { 423 | return PT_STRING; 424 | } 425 | if (!strcmp(propertytype, "int")) { 426 | return PT_INT; 427 | } 428 | if (!strcmp(propertytype, "float")) { 429 | return PT_FLOAT; 430 | } 431 | if (!strcmp(propertytype, "bool")) { 432 | return PT_BOOL; 433 | } 434 | if (!strcmp(propertytype, "color")) { 435 | return PT_COLOR; 436 | } 437 | if (!strcmp(propertytype, "file")) { 438 | return PT_FILE; 439 | } 440 | return PT_NONE; 441 | } 442 | 443 | enum tmx_horizontal_align parse_horizontal_align(const char *horalign) { 444 | if (horalign == NULL) { 445 | return HA_NONE; 446 | } 447 | if (!strcmp(horalign, "left")) { 448 | return HA_LEFT; 449 | } 450 | if (!strcmp(horalign, "center")) { 451 | return HA_CENTER; 452 | } 453 | if (!strcmp(horalign, "right")) { 454 | return HA_RIGHT; 455 | } 456 | return HA_NONE; 457 | } 458 | 459 | enum tmx_vertical_align parse_vertical_align(const char *veralign) { 460 | if (veralign == NULL) { 461 | return VA_NONE; 462 | } 463 | if (!strcmp(veralign, "top")) { 464 | return VA_TOP; 465 | } 466 | if (!strcmp(veralign, "center")) { 467 | return VA_CENTER; 468 | } 469 | if (!strcmp(veralign, "bottom")) { 470 | return VA_BOTTOM; 471 | } 472 | return VA_NONE; 473 | } 474 | 475 | enum tmx_layer_type parse_layer_type(const char *nodename) { 476 | if (nodename == NULL) { 477 | return L_NONE; 478 | } 479 | if (!strcmp(nodename, "layer")) { 480 | return L_LAYER; 481 | } 482 | if (!strcmp(nodename, "objectgroup")) { 483 | return L_OBJGR; 484 | } 485 | if (!strcmp(nodename, "imagelayer")) { 486 | return L_IMAGE; 487 | } 488 | if (!strcmp(nodename, "group")) { 489 | return L_GROUP; 490 | } 491 | return L_NONE; 492 | } 493 | 494 | /* "false" -> 0 */ 495 | int parse_boolean(const char *boolean) { 496 | if (boolean != NULL && !strcmp(boolean, "true")) { 497 | return 1; 498 | } 499 | return 0; 500 | } 501 | 502 | /* "#337FA2" -> 0x337FA2 */ 503 | int get_color_rgb(const char *c) { 504 | if (*c == '#') c++; 505 | return (int)strtol(c, NULL, 16); 506 | } 507 | 508 | int count_char_occurences(const char *str, char c) { 509 | int res = 0; 510 | while(*str != '\0') { 511 | if (*str == c) res++; 512 | str++; 513 | } 514 | return res; 515 | } 516 | 517 | /* trim 'str' to avoid blank characters at its beginning and end */ 518 | char* str_trim(char *str) { 519 | int end = (int)(strlen(str)-1); 520 | while (end>=0 && isspace((unsigned char) str[end])) end--; 521 | str[end+1] = '\0'; 522 | 523 | while(isspace((unsigned char) str[0])) str++; 524 | return str; 525 | } 526 | 527 | /* duplicate a string */ 528 | char* tmx_strdup(const char *str) { 529 | char *res = (char*)tmx_alloc_func(NULL, strlen(str)+1); 530 | strcpy(res, str); 531 | return res; 532 | } 533 | 534 | size_t dirpath_len(const char *str) { 535 | const char *lastslash = strrchr(str, '/' ); 536 | const char *lastbslash = strrchr(str, '\\'); 537 | 538 | const char *last_path_sep = MAX(lastslash, lastbslash); 539 | if (last_path_sep == NULL) return 0; 540 | last_path_sep++; /* Keeps the trailing path separator */ 541 | 542 | return (size_t) (last_path_sep - str); 543 | } 544 | 545 | /* ("C:\Maps\map.tmx", "tilesets\ts1.tsx") => "C:\Maps\tilesets\ts1.tsx" */ 546 | char* mk_absolute_path(const char *base_path, const char *rel_path) { 547 | if (base_path == NULL) { 548 | return tmx_strdup(rel_path); 549 | } 550 | /* if base_path is a directory, it MUST have a trailing path separator */ 551 | size_t dp_len = dirpath_len(base_path); 552 | size_t rp_len = strlen(rel_path); 553 | size_t ap_len = dp_len + rp_len; 554 | 555 | char* res = (char*)tmx_alloc_func(NULL, ap_len+1); 556 | if (!res) { 557 | tmx_errno = E_ALLOC; 558 | return NULL; 559 | } 560 | 561 | memcpy( res, base_path, dp_len); 562 | memcpy(res+dp_len, rel_path, rp_len); 563 | *(res+ap_len) = '\0'; 564 | 565 | return res; 566 | } 567 | 568 | /* resolves the path to the image, and delegates to the client code */ 569 | void* load_image(void **ptr, const char *base_path, const char *rel_path) { 570 | char *ap_img; 571 | if (tmx_img_load_func) { 572 | ap_img = mk_absolute_path(base_path, rel_path); 573 | if (!ap_img) return 0; 574 | *ptr = tmx_img_load_func(ap_img); 575 | tmx_free_func(ap_img); 576 | return(*ptr); 577 | } 578 | return (void*)1; 579 | } 580 | -------------------------------------------------------------------------------- /src/tmx/tmx_utils.h: -------------------------------------------------------------------------------- 1 | /* Private Header */ 2 | 3 | #pragma once 4 | 5 | #include "tmx.h" 6 | 7 | 8 | #ifndef TMXUTILS_H 9 | #define TMXUTILS_H 10 | 11 | /* UNUSED macro to suppress `unused parameter` warnings with GCC and CLANG */ 12 | #ifdef __GNUC__ 13 | #define UNUSED __attribute__((__unused__)) 14 | #else 15 | #define UNUSED 16 | #endif 17 | 18 | /* 19 | XML Parser implementation - tmx_xml.c 20 | */ 21 | tmx_map* parse_xml(tmx_tileset_manager *ts_mgr, const char *filename); 22 | tmx_map* parse_xml_buffer(tmx_tileset_manager *ts_mgr, const char *buffer, int len); 23 | tmx_map* parse_xml_fd(tmx_tileset_manager *ts_mgr, int fd); 24 | tmx_map* parse_xml_callback(tmx_tileset_manager *ts_mgr, tmx_read_functor callback, void *userdata); 25 | 26 | tmx_tileset* parse_tsx_xml(const char *filename); 27 | tmx_tileset* parse_tsx_xml_buffer(const char *buffer, int len); 28 | tmx_tileset* parse_tsx_xml_fd(int fd); 29 | tmx_tileset* parse_tsx_xml_callback(tmx_read_functor callback, void *userdata); 30 | 31 | /* 32 | Memory management, node allocation and free - tmx_mem.c 33 | */ 34 | void set_alloc_functions(); 35 | void setup_libxml_mem(); 36 | 37 | tmx_property* alloc_prop(void); 38 | tmx_image* alloc_image(void); 39 | tmx_shape* alloc_shape(void); 40 | tmx_text* alloc_text(void); 41 | tmx_object* alloc_object(void); 42 | tmx_object_group* alloc_objgr(void); 43 | tmx_layer* alloc_layer(void); 44 | tmx_tile* alloc_tiles(int count); 45 | tmx_tileset* alloc_tileset(void); 46 | tmx_tileset_list* alloc_tileset_list(void); 47 | tmx_map* alloc_map(void); 48 | tmx_tile* alloc_tile(void); 49 | 50 | void free_property(tmx_property *p); 51 | void free_props(tmx_properties *h); 52 | void free_obj(tmx_object *o); 53 | void free_objgr(tmx_object_group *o); 54 | void free_image(tmx_image *i); 55 | void free_layers(tmx_layer *l); 56 | void free_tiles(tmx_tile *t, int tilecount); 57 | void free_ts(tmx_tileset *ts); 58 | void free_ts_list(tmx_tileset_list *tsl); 59 | 60 | /* 61 | Misc - tmx_utils.c 62 | */ 63 | #define MAX(a,b) (a 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "tmx.h" 14 | #include "tsx.h" 15 | #include "tmx_utils.h" 16 | 17 | /* 18 | - Parsers - 19 | Each function is called when the XML reader is on an element 20 | with the same name. 21 | Each function return 1 on succes and 0 on failure. 22 | This parser is strict, the entry file MUST respect the file format. 23 | On failure tmx_errno is set and and an error message is generated. 24 | */ 25 | 26 | static void error_handler(void *arg UNUSED, const char *msg, xmlParserSeverities severity, xmlTextReaderLocatorPtr locator) { 27 | if (severity == XML_PARSER_SEVERITY_ERROR) { 28 | tmx_err(E_XDATA, "xml parser: error at line %d: %s", xmlTextReaderLocatorLineNumber(locator), msg); 29 | } 30 | } 31 | 32 | static int check_reader(xmlTextReaderPtr reader) { 33 | xmlTextReaderSetErrorHandler(reader, error_handler, NULL); 34 | 35 | if (xmlTextReaderRead(reader) != 1) { 36 | return 0; 37 | } 38 | return 1; 39 | } 40 | 41 | static int parse_property(xmlTextReaderPtr reader, tmx_property *prop) { 42 | char *value; 43 | 44 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"name"))) { /* name */ 45 | prop->name = value; 46 | } else { 47 | tmx_err(E_MISSEL, "xml parser: missing 'name' attribute in the 'property' element"); 48 | return 0; 49 | } 50 | 51 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*) "type"))) { /* type */ 52 | prop->type = parse_property_type(value); 53 | tmx_free_func(value); 54 | } else { 55 | prop->type = PT_STRING; 56 | } 57 | 58 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*) "value"))) { /* source */ 59 | switch (prop->type) { 60 | case PT_INT: 61 | prop->value.integer = atoi(value); 62 | tmx_free_func(value); 63 | break; 64 | case PT_FLOAT: 65 | prop->value.decimal = atof(value); 66 | tmx_free_func(value); 67 | break; 68 | case PT_BOOL: 69 | prop->value.integer = parse_boolean(value); 70 | tmx_free_func(value); 71 | break; 72 | case PT_COLOR: 73 | prop->value.integer = get_color_rgb(value); 74 | tmx_free_func(value); 75 | break; 76 | case PT_NONE: 77 | case PT_STRING: 78 | case PT_FILE: 79 | default: 80 | prop->value.string = value; 81 | break; 82 | } 83 | } else if (prop->type == PT_NONE || prop->type == PT_STRING) { 84 | if (!(value = (char*)xmlTextReaderReadInnerXml(reader))) { 85 | tmx_err(E_MISSEL, "xml parser: missing 'value' attribute or inner XML for the 'property' element"); 86 | } 87 | prop->value.string = value; 88 | } else { 89 | tmx_err(E_MISSEL, "xml parser: missing 'value' attribute in the 'property' element"); 90 | return 0; 91 | } 92 | return 1; 93 | } 94 | 95 | static int parse_properties(xmlTextReaderPtr reader, tmx_properties **prop_hashptr) { 96 | tmx_property *res; 97 | int curr_depth; 98 | const char *name; 99 | 100 | curr_depth = xmlTextReaderDepth(reader); 101 | 102 | /* Create hashtable */ 103 | if (*prop_hashptr == NULL) 104 | { 105 | if (!(*prop_hashptr = (tmx_properties*)mk_hashtable(5))) return 0; 106 | } 107 | 108 | /* Parse each child */ 109 | do { 110 | if (xmlTextReaderRead(reader) != 1) return 0; /* error_handler has been called */ 111 | 112 | if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) { 113 | name = (char*)xmlTextReaderConstName(reader); 114 | if (!strcmp(name, "property")) { 115 | if (!(res = alloc_prop())) return 0; 116 | if (!parse_property(reader, res)) return 0; 117 | hashtable_set((void*)*prop_hashptr, res->name, (void*)res, NULL); 118 | } else { /* Unknow element, skip its tree */ 119 | if (xmlTextReaderNext(reader) != 1) return 0; 120 | } 121 | } 122 | } while (xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT || 123 | xmlTextReaderDepth(reader) != curr_depth); 124 | return 1; 125 | } 126 | 127 | static int parse_points(xmlTextReaderPtr reader, tmx_shape *shape) { 128 | char *value, *v; 129 | int i; 130 | 131 | if (!(value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"points"))) { /* points */ 132 | tmx_err(E_MISSEL, "xml parser: missing 'points' attribute in the 'object' element"); 133 | return 0; 134 | } 135 | 136 | shape->points_len = 1 + count_char_occurences(value, ' '); 137 | 138 | shape->points = (double**)tmx_alloc_func(NULL, shape->points_len * sizeof(double*)); /* points[i][x,y] */ 139 | if (!(shape->points)) { 140 | tmx_errno = E_ALLOC; 141 | return 0; 142 | } 143 | 144 | shape->points[0] = (double*)tmx_alloc_func(NULL, shape->points_len * 2 * sizeof(double)); 145 | if (!(shape->points[0])) { 146 | tmx_free_func(shape->points); 147 | tmx_errno = E_ALLOC; 148 | return 0; 149 | } 150 | 151 | for (i=1; ipoints_len; i++) { 152 | shape->points[i] = shape->points[0]+(i*2); 153 | } 154 | 155 | v = value; 156 | for (i=0; ipoints_len; i++) { 157 | if (sscanf(v, "%lf,%lf", shape->points[i], shape->points[i]+1) != 2) { 158 | tmx_err(E_XDATA, "xml parser: corrupted point list"); 159 | return 0; 160 | } 161 | v = 1 + strchr(v, ' '); 162 | } 163 | 164 | tmx_free_func(value); 165 | return 1; 166 | } 167 | 168 | static int parse_text(xmlTextReaderPtr reader, tmx_text *text) { 169 | char *value; 170 | 171 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"fontfamily"))) { /* fontfamily */ 172 | text->fontfamily = value; 173 | } else { 174 | text->fontfamily = tmx_strdup("sans-serif"); 175 | } 176 | 177 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"pixelsize"))) { /* pixelsize */ 178 | text->pixelsize = (int)atoi(value); 179 | tmx_free_func(value); 180 | } 181 | 182 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"color"))) { /* color */ 183 | text->color = get_color_rgb(value); 184 | tmx_free_func(value); 185 | } 186 | 187 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"wrap"))) { /* wrap */ 188 | text->color = (int)atoi(value); 189 | tmx_free_func(value); 190 | } 191 | 192 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"bold"))) { /* bold */ 193 | text->bold = (int)atoi(value); 194 | tmx_free_func(value); 195 | } 196 | 197 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"italic"))) { /* italic */ 198 | text->italic = (int)atoi(value); 199 | tmx_free_func(value); 200 | } 201 | 202 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"underline"))) { /* underline */ 203 | text->underline = (int)atoi(value); 204 | tmx_free_func(value); 205 | } 206 | 207 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"strikeout"))) { /* strikeout */ 208 | text->strikeout = (int)atoi(value); 209 | tmx_free_func(value); 210 | } 211 | 212 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"kerning"))) { /* kerning */ 213 | text->kerning = (int)atoi(value); 214 | tmx_free_func(value); 215 | } 216 | 217 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"halign"))) { /* halign */ 218 | text->halign = parse_horizontal_align(value); 219 | tmx_free_func(value); 220 | } 221 | 222 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"valign"))) { /* valign */ 223 | text->valign = parse_vertical_align(value); 224 | tmx_free_func(value); 225 | } 226 | 227 | if ((value = (char*)xmlTextReaderReadInnerXml(reader))) { 228 | text->text = value; 229 | } 230 | 231 | return 1; 232 | } 233 | 234 | static int parse_object(xmlTextReaderPtr reader, tmx_object *obj) { 235 | int curr_depth; 236 | const char *name; 237 | char *value; 238 | 239 | /* parses each attribute */ 240 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"id"))) { /* id */ 241 | obj->id = atoi(value); 242 | tmx_free_func(value); 243 | } else { 244 | tmx_err(E_MISSEL, "xml parser: missing 'id' attribute in the 'object' element"); 245 | return 0; 246 | } 247 | 248 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"x"))) { /* x */ 249 | obj->x = atof(value); 250 | tmx_free_func(value); 251 | } else { 252 | tmx_err(E_MISSEL, "xml parser: missing 'x' attribute in the 'object' element"); 253 | return 0; 254 | } 255 | 256 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"y"))) { /* y */ 257 | obj->y = atof(value); 258 | tmx_free_func(value); 259 | } else { 260 | tmx_err(E_MISSEL, "xml parser: missing 'y' attribute in the 'object' element"); 261 | return 0; 262 | } 263 | 264 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"name"))) { /* name */ 265 | obj->name = value; 266 | } 267 | 268 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"type"))) { /* type */ 269 | obj->type = value; 270 | } 271 | 272 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"visible"))) { /* visible */ 273 | obj->visible = (char)atoi(value); 274 | tmx_free_func(value); 275 | } 276 | 277 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"height"))) { /* height */ 278 | obj->obj_type = OT_SQUARE; 279 | obj->height = atof(value); 280 | tmx_free_func(value); 281 | } 282 | 283 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"width"))) { /* width */ 284 | obj->width = atof(value); 285 | tmx_free_func(value); 286 | } 287 | 288 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"gid"))) { /* gid */ 289 | obj->obj_type = OT_TILE; 290 | obj->content.gid = atoi(value); 291 | tmx_free_func(value); 292 | } 293 | 294 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"rotation"))) { /* rotation */ 295 | obj->rotation = atof(value); 296 | tmx_free_func(value); 297 | } 298 | 299 | /* If it has a child, then it's a polygon or a polyline or an ellipse */ 300 | curr_depth = xmlTextReaderDepth(reader); 301 | if (!xmlTextReaderIsEmptyElement(reader)) { 302 | do { 303 | if (xmlTextReaderRead(reader) != 1) return 0; /* error_handler has been called */ 304 | 305 | if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) { 306 | name = (char*)xmlTextReaderConstName(reader); 307 | if (!strcmp(name, "properties")) { 308 | if (!parse_properties(reader, &(obj->properties))) return 0; 309 | } else if (!strcmp(name, "ellipse")) { 310 | obj->obj_type = OT_ELLIPSE; 311 | } else { 312 | if (!strcmp(name, "polygon")) { 313 | obj->obj_type = OT_POLYGON; 314 | } else if (!strcmp(name, "polyline")) { 315 | obj->obj_type = OT_POLYLINE; 316 | } else if (!strcmp(name, "text")) { 317 | obj->obj_type = OT_TEXT; 318 | } 319 | /* Unknow element, skip its tree */ 320 | else if (xmlTextReaderNext(reader) != 1) return 0; 321 | if (obj->obj_type == OT_POLYGON || obj->obj_type == OT_POLYLINE) { 322 | if (obj->content.shape = alloc_shape(), !(obj->content.shape)) return 0; 323 | if (!parse_points(reader, obj->content.shape)) return 0; 324 | } 325 | else if (obj->obj_type == OT_TEXT) { 326 | if (obj->content.text = alloc_text(), !(obj->content.text)) return 0; 327 | if (!parse_text(reader, obj->content.text)) return 0; 328 | } 329 | } 330 | } 331 | } while (xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT || 332 | xmlTextReaderDepth(reader) != curr_depth); 333 | } 334 | return 1; 335 | } 336 | 337 | static int parse_data(xmlTextReaderPtr reader, int32_t **gidsadr, size_t gidscount) { 338 | char *value, *inner_xml; 339 | 340 | if (!(value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"encoding"))) { /* encoding */ 341 | tmx_err(E_MISSEL, "xml parser: missing 'encoding' attribute in the 'data' element"); 342 | return 0; 343 | } 344 | 345 | if (!(inner_xml = (char*)xmlTextReaderReadInnerXml(reader))) { 346 | tmx_err(E_XDATA, "xml parser: missing content in the 'data' element"); 347 | tmx_free_func(value); 348 | return 0; 349 | } 350 | 351 | if (!strcmp(value, "base64")) { 352 | tmx_free_func(value); 353 | if (!(value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"compression"))) { /* compression */ 354 | tmx_err(E_MISSEL, "xml parser: missing 'compression' attribute in the 'data' element"); 355 | goto cleanup; 356 | } 357 | if (strcmp(value, "zlib") && strcmp(value, "gzip")) { 358 | tmx_err(E_ENCCMP, "xml parser: unsupported data compression: '%s'", value); /* unsupported compression */ 359 | goto cleanup; 360 | } 361 | if (!data_decode(str_trim(inner_xml), B64Z, gidscount, gidsadr)) goto cleanup; 362 | 363 | } else if (!strcmp(value, "xml")) { 364 | tmx_err(E_ENCCMP, "xml parser: unimplemented data encoding: XML"); 365 | goto cleanup; 366 | } else if (!strcmp(value, "csv")) { 367 | if (!data_decode(str_trim(inner_xml), CSV, gidscount, gidsadr)) goto cleanup; 368 | } else { 369 | tmx_err(E_ENCCMP, "xml parser: unknown data encoding: %s", value); 370 | goto cleanup; 371 | } 372 | tmx_free_func(value); 373 | tmx_free_func(inner_xml); 374 | return 1; 375 | 376 | cleanup: 377 | tmx_free_func(value); 378 | tmx_free_func(inner_xml); 379 | return 0; 380 | } 381 | 382 | static int parse_image(xmlTextReaderPtr reader, tmx_image **img_adr, short strict, const char *filename) { 383 | tmx_image *res; 384 | char *value; 385 | 386 | if (!(res = alloc_image())) return 0; 387 | *img_adr = res; 388 | 389 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"source"))) { /* source */ 390 | res->source = value; 391 | if (!(load_image(&(res->resource_image), filename, value))) { 392 | tmx_err(E_UNKN, "xml parser: an error occured in the delegated image loading function"); 393 | return 0; 394 | } 395 | } else { 396 | tmx_err(E_MISSEL, "xml parser: missing 'source' attribute in the 'image' element"); 397 | return 0; 398 | } 399 | 400 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"height"))) { /* height */ 401 | res->height = atoi(value); 402 | tmx_free_func(value); 403 | } else if (strict) { 404 | tmx_err(E_MISSEL, "xml parser: missing 'height' attribute in the 'image' element"); 405 | return 0; 406 | } 407 | 408 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"width"))) { /* width */ 409 | res->width = atoi(value); 410 | tmx_free_func(value); 411 | } else if (strict) { 412 | tmx_err(E_MISSEL, "xml parser: missing 'width' attribute in the 'image' element"); 413 | return 0; 414 | } 415 | 416 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"trans"))) { /* trans */ 417 | res->trans = get_color_rgb(value); 418 | res->uses_trans = 1; 419 | tmx_free_func(value); 420 | } 421 | 422 | return 1; 423 | } 424 | 425 | /* parse layers and objectgroups */ 426 | static int parse_layer(xmlTextReaderPtr reader, tmx_layer **layer_headadr, int map_h, int map_w, enum tmx_layer_type type, const char *filename) { 427 | tmx_layer *res; 428 | tmx_object *obj; 429 | int curr_depth; 430 | const char *name; 431 | char *value; 432 | enum tmx_layer_type child_type; 433 | 434 | curr_depth = xmlTextReaderDepth(reader); 435 | 436 | if (!(res = alloc_layer())) return 0; 437 | res->type = type; 438 | while(*layer_headadr) { 439 | layer_headadr = &((*layer_headadr)->next); 440 | } 441 | *layer_headadr = res; 442 | 443 | /* parses each attribute */ 444 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"name"))) { /* name */ 445 | res->name = value; 446 | } else { 447 | tmx_err(E_MISSEL, "xml parser: missing 'name' attribute in the 'layer' element"); 448 | return 0; 449 | } 450 | 451 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"visible"))) { /* visible */ 452 | res->visible = (char)atoi(value); 453 | tmx_free_func(value); 454 | } 455 | 456 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"opacity"))) { /* opacity */ 457 | res->opacity = atof(value); 458 | tmx_free_func(value); 459 | } 460 | 461 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"offsetx"))) { /* offsetx */ 462 | res->offsetx = (int)atoi(value); 463 | tmx_free_func(value); 464 | } 465 | 466 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"offsety"))) { /* offsety */ 467 | res->offsety = (int)atoi(value); 468 | tmx_free_func(value); 469 | } 470 | 471 | /* objectgroups have more properties */ 472 | if (type == L_OBJGR) { 473 | tmx_object_group *objgr = alloc_objgr(); 474 | res->content.objgr = objgr; 475 | 476 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"color"))) { /* color */ 477 | objgr->color = get_color_rgb(value); 478 | tmx_free_func(value); 479 | } 480 | 481 | value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"draworder"); /* draworder */ 482 | objgr->draworder = parse_objgr_draworder(value); 483 | tmx_free_func(value); 484 | } 485 | 486 | if (type == L_OBJGR && xmlTextReaderIsEmptyElement(reader)) { 487 | return 1; 488 | } 489 | 490 | do { 491 | if (xmlTextReaderRead(reader) != 1) return 0; /* error_handler has been called */ 492 | 493 | if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) { 494 | name = (char*)xmlTextReaderConstName(reader); 495 | if (!strcmp(name, "properties")) { 496 | if (!parse_properties(reader, &(res->properties))) return 0; 497 | } else if (!strcmp(name, "data")) { 498 | if (!parse_data(reader, &(res->content.gids), map_h * map_w)) return 0; 499 | } else if (!strcmp(name, "image")) { 500 | if (!parse_image(reader, &(res->content.image), 0, filename)) return 0; 501 | } else if (!strcmp(name, "object")) { 502 | if (!(obj = alloc_object())) return 0; 503 | 504 | obj->next = res->content.objgr->head; 505 | res->content.objgr->head = obj; 506 | 507 | if (!parse_object(reader, obj)) return 0; 508 | } else if (type == L_GROUP && (child_type = parse_layer_type(name)) != L_NONE) { 509 | if (!parse_layer(reader, &(res->content.group_head), map_h, map_w, child_type, filename)) return 0; 510 | } else { 511 | /* Unknow element, skip its tree */ 512 | if (xmlTextReaderNext(reader) != 1) return 0; 513 | } 514 | } 515 | } while (xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT || 516 | xmlTextReaderDepth(reader) != curr_depth); 517 | 518 | return 1; 519 | } 520 | 521 | static int parse_tileoffset(xmlTextReaderPtr reader, int *x, int *y) { 522 | char *value; 523 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"x"))) { /* x offset */ 524 | *x = atoi(value); 525 | tmx_free_func(value); 526 | } else { 527 | tmx_err(E_MISSEL, "xml parser: missing 'x' attribute in the 'tileoffset' element"); 528 | return 0; 529 | } 530 | 531 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"y"))) { /* y offset */ 532 | *y = atoi(value); 533 | tmx_free_func(value); 534 | } else { 535 | tmx_err(E_MISSEL, "xml parser: missing 'y' attribute in the 'tileoffset' element"); 536 | return 0; 537 | } 538 | 539 | return 1; 540 | } 541 | 542 | /* recursive function that alloc tmx_anim_frames on the stack and then move them to the heap */ 543 | static tmx_anim_frame* parse_animation(xmlTextReaderPtr reader, int frame_count, unsigned int *length) { 544 | char *value; 545 | int curr_depth; 546 | tmx_anim_frame frame; 547 | tmx_anim_frame *res; 548 | 549 | curr_depth = xmlTextReaderDepth(reader); 550 | 551 | value = (char*)xmlTextReaderConstName(reader); 552 | if (strcmp(value, "frame")) { 553 | tmx_err(E_XDATA, "xml parser: invalid element '%s' within an 'animation'", value); 554 | return 0; 555 | } 556 | 557 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"tileid"))) { /* tileid */ 558 | frame.tile_id = atoi(value); 559 | tmx_free_func(value); 560 | } 561 | else { 562 | tmx_err(E_MISSEL, "xml parser: missing 'tileid' attribute in the 'frame' element"); 563 | return 0; 564 | } 565 | 566 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"duration"))) { /* duration */ 567 | frame.duration = atoi(value); 568 | tmx_free_func(value); 569 | } 570 | else { 571 | tmx_err(E_MISSEL, "xml parser: missing 'duration' attribute in the 'frame' element"); 572 | return 0; 573 | } 574 | 575 | if (xmlTextReaderNext(reader) != 1) return 0; 576 | 577 | /* skips unwanted nodes */ 578 | while (xmlTextReaderDepth(reader) > curr_depth || 579 | (xmlTextReaderDepth(reader) == curr_depth && xmlTextReaderNodeType(reader) != XML_READER_TYPE_ELEMENT)) { 580 | if (xmlTextReaderNext(reader) != 1) return 0; 581 | } 582 | 583 | /* no more frames, alloc on the heap and returns */ 584 | if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_END_ELEMENT && xmlTextReaderDepth(reader) < curr_depth) { 585 | res = (tmx_anim_frame*)tmx_alloc_func(NULL, (frame_count+1) * sizeof(tmx_anim_frame)); 586 | if (res == NULL) { 587 | tmx_err(E_ALLOC, "xml parser: failed to alloc %d animation frames", frame_count+1); 588 | return NULL; 589 | } 590 | res[frame_count] = frame; 591 | *length = frame_count+1; 592 | return res; 593 | } 594 | /* recurse */ 595 | else if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) { 596 | res = parse_animation(reader, frame_count+1, length); 597 | if (res != NULL) { 598 | res[frame_count] = frame; 599 | } 600 | return res; 601 | } 602 | 603 | tmx_err(E_XDATA, "xml parser: unexpected element '%s' within 'animation'", (char*)xmlTextReaderConstName(reader)); 604 | return NULL; 605 | } 606 | 607 | static int parse_tile(xmlTextReaderPtr reader, tmx_tileset *tileset, const char *filename) { 608 | tmx_tile *res = NULL; 609 | tmx_object *obj; 610 | unsigned int id; 611 | int curr_depth; 612 | int len, to_move; 613 | const char *name; 614 | char *value; 615 | 616 | curr_depth = xmlTextReaderDepth(reader); 617 | 618 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"id"))) { /* id */ 619 | id = atoi(value); 620 | /* Insertion sort */ 621 | len = tileset->user_data.integer; 622 | for (to_move=0; (len-1)-to_move >= 0; to_move++) { 623 | if (tileset->tiles[(len-1)-to_move].id < id) { 624 | break; 625 | } 626 | } 627 | if (to_move > 0) { 628 | memmove((tileset->tiles)+(len-to_move+1), (tileset->tiles)+(len-to_move), to_move * sizeof(tmx_tile)); 629 | } 630 | res = &(tileset->tiles[len-to_move]); 631 | 632 | if ((unsigned int)(tileset->user_data.integer) == tileset->tilecount) { 633 | tileset->user_data.integer = 0; 634 | } 635 | else { 636 | tileset->user_data.integer += 1; 637 | } 638 | /* --- */ 639 | res->id = id; 640 | res->tileset = tileset; 641 | tmx_free_func(value); 642 | } 643 | else { 644 | tmx_err(E_MISSEL, "xml parser: missing 'id' attribute in the 'tile' element"); 645 | return 0; 646 | } 647 | 648 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"type"))) { /* type */ 649 | res->type = value; 650 | } 651 | 652 | if (!xmlTextReaderIsEmptyElement(reader)) { 653 | do { 654 | if (xmlTextReaderRead(reader) != 1) return 0; /* error_handler has been called */ 655 | 656 | if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) { 657 | name = (char*)xmlTextReaderConstName(reader); 658 | if (!strcmp(name, "properties")) { 659 | if (!parse_properties(reader, &(res->properties))) return 0; 660 | } 661 | else if (!strcmp(name, "image")) { 662 | if (!parse_image(reader, &(res->image), 0, filename)) return 0; 663 | } 664 | else if (!strcmp(name, "objectgroup")) { /* tile collision */ 665 | if (xmlTextReaderIsEmptyElement(reader)) continue; 666 | do { 667 | if (xmlTextReaderRead(reader) != 1) return 0; /* error_handler has been called */ 668 | name = (char*)xmlTextReaderConstName(reader); 669 | if (!strcmp(name, "object")) { 670 | if (!(obj = alloc_object())) return 0; 671 | 672 | obj->next = res->collision; 673 | res->collision = obj; 674 | 675 | if (!parse_object(reader, obj)) return 0; 676 | } 677 | /* else: ignore */ 678 | } while (xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT || 679 | xmlTextReaderDepth(reader) != curr_depth+1); 680 | } 681 | else if (!strcmp(name, "animation")) { 682 | /* reads the first frame */ 683 | do { 684 | if (xmlTextReaderRead(reader) != 1) return 0; 685 | name = (char*)xmlTextReaderConstName(reader); 686 | if (!strcmp(name, "frame")) { 687 | res->animation = parse_animation(reader, 0, &(res->animation_len)); 688 | if (!(res->animation)) return 0; 689 | } 690 | /* else: ignore */ 691 | } while (xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT || 692 | xmlTextReaderDepth(reader) != curr_depth+1); 693 | } 694 | else { 695 | /* Unknow element, skip its tree */ 696 | if (xmlTextReaderNext(reader) != 1) return 0; 697 | } 698 | } 699 | } while (xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT || 700 | xmlTextReaderDepth(reader) != curr_depth); 701 | } 702 | 703 | return 1; 704 | } 705 | 706 | /* parses a tileset within the tmx file or in a dedicated tsx file */ 707 | static int parse_tileset(xmlTextReaderPtr reader, tmx_tileset *ts_addr, const char *filename) { 708 | int curr_depth; 709 | const char *name; 710 | char *value; 711 | 712 | curr_depth = xmlTextReaderDepth(reader); 713 | 714 | /* parses each attribute */ 715 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"name"))) { /* name */ 716 | ts_addr->name = value; 717 | } else { 718 | tmx_err(E_MISSEL, "xml parser: missing 'name' attribute in the 'tileset' element"); 719 | return 0; 720 | } 721 | 722 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"tilecount"))) { /* tilecount */ 723 | ts_addr->tilecount = atoi(value); 724 | tmx_free_func(value); 725 | } else { 726 | tmx_err(E_MISSEL, "xml parser: missing 'tilecount' attribute in the 'tileset' element"); 727 | return 0; 728 | } 729 | 730 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"tilewidth"))) { /* tile_width */ 731 | ts_addr->tile_width = atoi(value); 732 | tmx_free_func(value); 733 | } else { 734 | tmx_err(E_MISSEL, "xml parser: missing 'tilewidth' attribute in the 'tileset' element"); 735 | return 0; 736 | } 737 | 738 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"tileheight"))) { /* tile_height */ 739 | ts_addr->tile_height = atoi(value); 740 | tmx_free_func(value); 741 | } else { 742 | tmx_err(E_MISSEL, "xml parser: missing 'tileheight' attribute in the 'tileset' element"); 743 | return 0; 744 | } 745 | 746 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"spacing"))) { /* spacing */ 747 | ts_addr->spacing = atoi(value); 748 | tmx_free_func(value); 749 | } 750 | 751 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"margin"))) { /* margin */ 752 | ts_addr->margin = atoi(value); 753 | tmx_free_func(value); 754 | } 755 | 756 | if (!(ts_addr->tiles = alloc_tiles(ts_addr->tilecount))) return 0; 757 | 758 | /* Parse each child */ 759 | do { 760 | if (xmlTextReaderRead(reader) != 1) return 0; /* error_handler has been called */ 761 | 762 | if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) { 763 | name = (char*)xmlTextReaderConstName(reader); 764 | if (!strcmp(name, "image")) { 765 | if (!parse_image(reader, &(ts_addr->image), 1, filename)) return 0; 766 | } else if (!strcmp(name, "tileoffset")) { 767 | if (!parse_tileoffset(reader, &(ts_addr->x_offset), &(ts_addr->y_offset))) return 0; 768 | } else if (!strcmp(name, "properties")) { 769 | if (!parse_properties(reader, &(ts_addr->properties))) return 0; 770 | } else if (!strcmp(name, "tile")) { 771 | if (!parse_tile(reader, ts_addr, filename)) return 0; 772 | } else { 773 | /* Unknown element, skip its tree */ 774 | if (xmlTextReaderNext(reader) != 1) return 0; 775 | } 776 | } 777 | } while (xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT || 778 | xmlTextReaderDepth(reader) != curr_depth); 779 | 780 | if (ts_addr->image && !set_tiles_runtime_props(ts_addr)) return 0; 781 | 782 | return 1; 783 | } 784 | 785 | static int parse_tileset_list(xmlTextReaderPtr reader, tmx_tileset_list **ts_headadr, tmx_tileset_manager *ts_mgr, const char *filename) { 786 | tmx_tileset_list *res_list = NULL; 787 | tmx_tileset *res = NULL; 788 | int ret; 789 | char *value, *ab_path; 790 | xmlTextReaderPtr sub_reader; 791 | 792 | if (!(res_list = alloc_tileset_list())) return 0; 793 | res_list->next = *ts_headadr; 794 | *ts_headadr = res_list; 795 | 796 | /* parses each attribute */ 797 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"firstgid"))) { /* fisrtgid */ 798 | res_list->firstgid = atoi(value); 799 | tmx_free_func(value); 800 | } else { 801 | tmx_err(E_MISSEL, "xml parser: missing 'firstgid' attribute in the 'tileset' element"); 802 | return 0; 803 | } 804 | 805 | /* External Tileset */ 806 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"source"))) { /* source */ 807 | if (ts_mgr) { 808 | res = (tmx_tileset*) hashtable_get((void*)ts_mgr, value); 809 | if (res) { 810 | res_list->tileset = res; 811 | return 1; 812 | } 813 | } 814 | if (!(res = alloc_tileset())) { 815 | tmx_free_func(value); 816 | return 0; 817 | } 818 | res_list->tileset = res; 819 | if (ts_mgr) { 820 | hashtable_set((void*)ts_mgr, value, (void*)res, tileset_deallocator); 821 | } 822 | else { 823 | res->is_embedded = 1; 824 | } 825 | if (!(ab_path = mk_absolute_path(filename, value))) return 0; 826 | tmx_free_func(value); 827 | if (!(sub_reader = xmlReaderForFile(ab_path, NULL, 0)) || !check_reader(sub_reader)) { /* opens */ 828 | tmx_err(E_XDATA, "xml parser: cannot open extern tileset '%s'", ab_path); 829 | tmx_free_func(ab_path); 830 | return 0; 831 | } 832 | ret = parse_tileset(sub_reader, res, ab_path); /* and parses the tsx file */ 833 | xmlFreeTextReader(sub_reader); 834 | tmx_free_func(ab_path); 835 | return ret; 836 | } 837 | 838 | /* Embedded tileset */ 839 | if (!(res = alloc_tileset())) return 0; 840 | res->is_embedded = 1; 841 | res_list->tileset = res; 842 | 843 | return parse_tileset(reader, res, filename); 844 | } 845 | 846 | static tmx_map *parse_root_map(xmlTextReaderPtr reader, tmx_tileset_manager *ts_mgr, const char *filename) { 847 | tmx_map *res = NULL; 848 | int curr_depth; 849 | const char *name; 850 | char *value; 851 | enum tmx_layer_type type; 852 | 853 | /* DTD before root element */ 854 | if (xmlTextReaderNodeType(reader) == 10) 855 | { 856 | if (xmlTextReaderRead(reader) != 1) return NULL; 857 | } 858 | 859 | name = (char*) xmlTextReaderConstName(reader); 860 | curr_depth = xmlTextReaderDepth(reader); 861 | 862 | if (strcmp(name, "map")) { 863 | tmx_err(E_XDATA, "xml parser: root is not a 'map' element"); 864 | return NULL; 865 | } 866 | 867 | if (!(res = alloc_map())) return NULL; 868 | 869 | /* parses each attribute */ 870 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"orientation"))) { /* orientation */ 871 | if (res->orient = parse_orient(value), res->orient == O_NONE) { 872 | tmx_err(E_XDATA, "xml parser: unsupported 'orientation' '%s'", value); 873 | goto cleanup; 874 | } 875 | tmx_free_func(value); 876 | } else { 877 | tmx_err(E_MISSEL, "xml parser: missing 'orientation' attribute in the 'map' element"); 878 | goto cleanup; 879 | } 880 | 881 | value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"staggerindex"); /* staggerindex */ 882 | if (value != NULL && (res->stagger_index = parse_stagger_index(value), res->stagger_index == SI_NONE)) { 883 | tmx_err(E_XDATA, "xml parser: unsupported 'staggerindex' '%s'", value); 884 | goto cleanup; 885 | } 886 | tmx_free_func(value); 887 | 888 | value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"staggeraxis"); /* staggeraxis */ 889 | if (res->stagger_axis = parse_stagger_axis(value), res->stagger_axis == SA_NONE) { 890 | tmx_err(E_XDATA, "xml parser: unsupported 'staggeraxis' '%s'", value); 891 | goto cleanup; 892 | } 893 | tmx_free_func(value); 894 | 895 | value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"renderorder"); /* renderorder */ 896 | if (res->renderorder = parse_renderorder(value), res->renderorder == R_NONE) { 897 | tmx_err(E_XDATA, "xml parser: unsupported 'renderorder' '%s'", value); 898 | goto cleanup; 899 | } 900 | tmx_free_func(value); 901 | 902 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"height"))) { /* height */ 903 | res->height = atoi(value); 904 | tmx_free_func(value); 905 | } else { 906 | tmx_err(E_MISSEL, "xml parser: missing 'height' attribute in the 'map' element"); 907 | goto cleanup; 908 | } 909 | 910 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"width"))) { /* width */ 911 | res->width = atoi(value); 912 | tmx_free_func(value); 913 | } else { 914 | tmx_err(E_MISSEL, "xml parser: missing 'width' attribute in the 'map' element"); 915 | goto cleanup; 916 | } 917 | 918 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"tileheight"))) { /* tileheight */ 919 | res->tile_height = atoi(value); 920 | tmx_free_func(value); 921 | } else { 922 | tmx_err(E_MISSEL, "xml parser: missing 'tileheight' attribute in the 'map' element"); 923 | goto cleanup; 924 | } 925 | 926 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"tilewidth"))) { /* tilewidth */ 927 | res->tile_width = atoi(value); 928 | tmx_free_func(value); 929 | } else { 930 | tmx_err(E_MISSEL, "xml parser: missing 'tilewidth' attribute in the 'map' element"); 931 | goto cleanup; 932 | } 933 | 934 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"backgroundcolor"))) { /* backgroundcolor */ 935 | res->backgroundcolor = get_color_rgb(value); 936 | tmx_free_func(value); 937 | } 938 | 939 | if ((value = (char*)xmlTextReaderGetAttribute(reader, (xmlChar*)"hexsidelength"))) { /* hexsidelength */ 940 | res->hexsidelength = atoi(value); 941 | tmx_free_func(value); 942 | } 943 | 944 | /* Parse each child */ 945 | do { 946 | if (xmlTextReaderRead(reader) != 1) goto cleanup; /* error_handler has been called */ 947 | 948 | if (xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) { 949 | name = (char*)xmlTextReaderConstName(reader); 950 | if (!strcmp(name, "tileset")) { 951 | if (!parse_tileset_list(reader, &(res->ts_head), ts_mgr, filename)) goto cleanup; 952 | } else if (!strcmp(name, "properties")) { 953 | if (!parse_properties(reader, &(res->properties))) goto cleanup; 954 | } else if ((type = parse_layer_type(name)) != L_NONE) { 955 | if (!parse_layer(reader, &(res->ly_head), res->height, res->width, type, filename)) goto cleanup; 956 | } else { 957 | /* Unknow element, skip its tree */ 958 | if (xmlTextReaderNext(reader) != 1) return 0; 959 | } 960 | } 961 | } while (xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT || 962 | xmlTextReaderDepth(reader) != curr_depth); 963 | return res; 964 | cleanup: 965 | tmx_map_free(res); 966 | return NULL; 967 | } 968 | 969 | static tmx_tileset* parse_root_tileset(xmlTextReaderPtr reader, const char *filename) { 970 | tmx_tileset *res; 971 | 972 | if (!(res = alloc_tileset())) return NULL; 973 | 974 | parse_tileset(reader, res, filename); 975 | 976 | return res; 977 | } 978 | 979 | /* 980 | Public TMX load functions 981 | */ 982 | 983 | tmx_map *parse_xml(tmx_tileset_manager *ts_mgr, const char *filename) { 984 | xmlTextReaderPtr reader; 985 | tmx_map *res = NULL; 986 | 987 | setup_libxml_mem(); 988 | 989 | if ((reader = xmlReaderForFile(filename, NULL, 0))) { 990 | if (check_reader(reader)) { 991 | res = parse_root_map(reader, ts_mgr, filename); 992 | } 993 | xmlFreeTextReader(reader); 994 | } else { 995 | tmx_err(E_UNKN, "xml parser: unable to open %s", filename); 996 | } 997 | 998 | return res; 999 | } 1000 | 1001 | tmx_map* parse_xml_buffer(tmx_tileset_manager *ts_mgr, const char *buffer, int len) { 1002 | xmlTextReaderPtr reader; 1003 | tmx_map *res = NULL; 1004 | 1005 | setup_libxml_mem(); 1006 | 1007 | if ((reader = xmlReaderForMemory(buffer, len, NULL, NULL, 0))) { 1008 | if (check_reader(reader)) { 1009 | res = parse_root_map(reader, ts_mgr, NULL); 1010 | } 1011 | xmlFreeTextReader(reader); 1012 | } else { 1013 | tmx_err(E_UNKN, "xml parser: unable to create parser for buffer"); 1014 | } 1015 | 1016 | return res; 1017 | } 1018 | 1019 | tmx_map* parse_xml_fd(tmx_tileset_manager *ts_mgr, int fd) { 1020 | xmlTextReaderPtr reader; 1021 | tmx_map *res = NULL; 1022 | 1023 | setup_libxml_mem(); 1024 | 1025 | if ((reader = xmlReaderForFd(fd, NULL, NULL, 0))) { 1026 | if (check_reader(reader)) { 1027 | res = parse_root_map(reader, ts_mgr, NULL); 1028 | } 1029 | xmlFreeTextReader(reader); 1030 | } else { 1031 | tmx_err(E_UNKN, "xml parser: unable create parser for file descriptor"); 1032 | } 1033 | 1034 | return res; 1035 | } 1036 | 1037 | tmx_map* parse_xml_callback(tmx_tileset_manager *ts_mgr, tmx_read_functor callback, void *userdata) { 1038 | xmlTextReaderPtr reader; 1039 | tmx_map *res = NULL; 1040 | 1041 | setup_libxml_mem(); 1042 | 1043 | if ((reader = xmlReaderForIO((xmlInputReadCallback)callback, NULL, userdata, NULL, NULL, 0))) { 1044 | if (check_reader(reader)) { 1045 | res = parse_root_map(reader, ts_mgr, NULL); 1046 | } 1047 | xmlFreeTextReader(reader); 1048 | } else { 1049 | tmx_err(E_UNKN, "xml parser: unable to create parser for input callback"); 1050 | } 1051 | 1052 | return res; 1053 | } 1054 | 1055 | /* 1056 | Public TSX load functions 1057 | */ 1058 | 1059 | tmx_tileset* parse_tsx_xml(const char *filename) { 1060 | xmlTextReaderPtr reader; 1061 | tmx_tileset *res = NULL; 1062 | 1063 | setup_libxml_mem(); 1064 | 1065 | if ((reader = xmlReaderForFile(filename, NULL, 0))) { 1066 | if (check_reader(reader)) { 1067 | res = parse_root_tileset(reader, filename); 1068 | } 1069 | xmlFreeTextReader(reader); 1070 | } else { 1071 | tmx_err(E_UNKN, "xml parser: unable to open %s", filename); 1072 | } 1073 | 1074 | return res; 1075 | } 1076 | 1077 | tmx_tileset* parse_tsx_xml_buffer(const char *buffer, int len) { 1078 | xmlTextReaderPtr reader; 1079 | tmx_tileset *res = NULL; 1080 | 1081 | setup_libxml_mem(); 1082 | 1083 | if ((reader = xmlReaderForMemory(buffer, len, NULL, NULL, 0))) { 1084 | if (check_reader(reader)) { 1085 | res = parse_root_tileset(reader, NULL); 1086 | } 1087 | xmlFreeTextReader(reader); 1088 | } else { 1089 | tmx_err(E_UNKN, "xml parser: unable to create parser for buffer"); 1090 | } 1091 | 1092 | return res; 1093 | } 1094 | 1095 | tmx_tileset* parse_tsx_xml_fd(int fd) { 1096 | xmlTextReaderPtr reader; 1097 | tmx_tileset *res = NULL; 1098 | 1099 | setup_libxml_mem(); 1100 | 1101 | if ((reader = xmlReaderForFd(fd, NULL, NULL, 0))) { 1102 | if (check_reader(reader)) { 1103 | res = parse_root_tileset(reader, NULL); 1104 | } 1105 | xmlFreeTextReader(reader); 1106 | } else { 1107 | tmx_err(E_UNKN, "xml parser: unable create parser for file descriptor"); 1108 | } 1109 | 1110 | return res; 1111 | } 1112 | 1113 | tmx_tileset* parse_tsx_xml_callback(tmx_read_functor callback, void *userdata) { 1114 | xmlTextReaderPtr reader; 1115 | tmx_tileset *res = NULL; 1116 | 1117 | setup_libxml_mem(); 1118 | 1119 | if ((reader = xmlReaderForIO((xmlInputReadCallback)callback, NULL, userdata, NULL, NULL, 0))) { 1120 | if (check_reader(reader)) { 1121 | res = parse_root_tileset(reader, NULL); 1122 | } 1123 | xmlFreeTextReader(reader); 1124 | } else { 1125 | tmx_err(E_UNKN, "xml parser: unable to create parser for input callback"); 1126 | } 1127 | 1128 | return res; 1129 | } 1130 | -------------------------------------------------------------------------------- /src/tmx/tsx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include /* int32_t */ 5 | #include 6 | 7 | #include "tmx.h" 8 | #include "tsx.h" 9 | #include "tmx_utils.h" 10 | 11 | /* 12 | Public functions 13 | */ 14 | 15 | tmx_tileset_manager* tmx_make_tileset_manager() { 16 | return (tmx_tileset_manager*)mk_hashtable(5); 17 | } 18 | 19 | void tmx_free_tileset_manager(tmx_tileset_manager *h) { 20 | free_hashtable((void*)h, tileset_deallocator); 21 | } 22 | 23 | int tmx_load_tileset(tmx_tileset_manager *ts_mgr, const char *path) { 24 | tmx_tileset *ts; 25 | 26 | if (ts_mgr == NULL) return 0; 27 | 28 | ts = parse_tsx_xml(path); 29 | if (ts) { 30 | hashtable_set((void*)ts_mgr, path, (void*)ts, tileset_deallocator); 31 | return 1; 32 | } 33 | 34 | return 0; 35 | } 36 | 37 | int tmx_load_tileset_buffer(tmx_tileset_manager *ts_mgr, const char *buffer, int len, const char *key) { 38 | tmx_tileset *ts; 39 | 40 | if (ts_mgr == NULL) return 0; 41 | 42 | ts = parse_tsx_xml_buffer(buffer, len); 43 | if (ts) { 44 | hashtable_set((void*)ts_mgr, key, (void*)ts, tileset_deallocator); 45 | return 1; 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | int tmx_load_tileset_fd(tmx_tileset_manager *ts_mgr, int fd, const char *key) { 52 | tmx_tileset *ts; 53 | 54 | if (ts_mgr == NULL) return 0; 55 | 56 | ts = parse_tsx_xml_fd(fd); 57 | if (ts) { 58 | hashtable_set((void*)ts_mgr, key, (void*)ts, tileset_deallocator); 59 | return 1; 60 | } 61 | 62 | return 0; 63 | } 64 | 65 | int tmx_load_tileset_callback(tmx_tileset_manager *ts_mgr, tmx_read_functor callback, void *userdata, const char *key) { 66 | tmx_tileset *ts; 67 | 68 | if (ts_mgr == NULL) return 0; 69 | 70 | ts = parse_tsx_xml_callback(callback, userdata); 71 | if (ts) { 72 | hashtable_set((void*)ts_mgr, key, (void*)ts, tileset_deallocator); 73 | return 1; 74 | } 75 | 76 | return 0; 77 | } 78 | 79 | tmx_map* tmx_tsmgr_load(tmx_tileset_manager *ts_mgr, const char *path) { 80 | tmx_map *map = NULL; 81 | set_alloc_functions(); 82 | map = parse_xml(ts_mgr, path); 83 | map_post_parsing(&map); 84 | return map; 85 | } 86 | 87 | tmx_map* tmx_tsmgr_load_buffer(tmx_tileset_manager *ts_mgr, const char *buffer, int len) { 88 | tmx_map *map = NULL; 89 | set_alloc_functions(); 90 | map = parse_xml_buffer(ts_mgr, buffer, len); 91 | map_post_parsing(&map); 92 | return map; 93 | } 94 | 95 | tmx_map* tmx_tsmgr_load_fd(tmx_tileset_manager *ts_mgr, int fd) { 96 | tmx_map *map = NULL; 97 | set_alloc_functions(); 98 | map = parse_xml_fd(ts_mgr, fd); 99 | map_post_parsing(&map); 100 | return map; 101 | } 102 | 103 | tmx_map* tmx_tsmgr_load_callback(tmx_tileset_manager *ts_mgr, tmx_read_functor callback, void *userdata) { 104 | tmx_map *map = NULL; 105 | set_alloc_functions(); 106 | map = parse_xml_callback(ts_mgr, callback, userdata); 107 | map_post_parsing(&map); 108 | return map; 109 | } 110 | -------------------------------------------------------------------------------- /src/tmx/tsx.h: -------------------------------------------------------------------------------- 1 | /* 2 | TSX.H - TSX C LOADER 3 | Copyright (c) 2017, Bayle Jonathan 4 | 5 | Functions to load tileset (.tsx) files in a tileset manager 6 | 7 | Optional functionality 8 | Include first 9 | */ 10 | 11 | #pragma once 12 | 13 | #ifndef TSX_H 14 | #define TSX_H 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | /* 21 | Tileset Manager functions 22 | */ 23 | 24 | /* Tileset Manager type (private hashtable) */ 25 | typedef void tmx_tileset_manager; 26 | 27 | /* Creates a Tileset Manager that holds a hashtable of loaded tilesets 28 | Only external tilesets (in .TSX files) are indexed in a tileset manager 29 | This is particularly useful to only load once tilesets needed by many maps 30 | The key is the `source` attribute of a tileset element */ 31 | TMXEXPORT tmx_tileset_manager* tmx_make_tileset_manager(); 32 | 33 | /* Frees the tilesetManager and all its loaded Tilesets 34 | All maps holding a pointer to external tileset loaded by the given manager 35 | now hold a pointer to freed memory */ 36 | TMXEXPORT void tmx_free_tileset_manager(tmx_tileset_manager *ts_mgr); 37 | 38 | /* Loads a tileset from file at `path` and stores it into given tileset manager 39 | `path` will be used as the key 40 | Returns 1 on success */ 41 | TMXEXPORT int tmx_load_tileset(tmx_tileset_manager *ts_mgr, const char *path); 42 | 43 | /* Loads a tileset from a buffer and stores it into given tileset manager 44 | Returns 1 on success */ 45 | TMXEXPORT int tmx_load_tileset_buffer(tmx_tileset_manager *ts_mgr, const char *buffer, int len, const char *key); 46 | 47 | /* Loads a tileset from a file descriptor and stores it into given tileset manager 48 | The file descriptor will not be closed 49 | Returns 1 on success */ 50 | TMXEXPORT int tmx_load_tileset_fd(tmx_tileset_manager *ts_mgr, int fd, const char *key); 51 | 52 | /* Loads a tileset using the given read callback and stores it into given tileset manager 53 | Returns 1 on success */ 54 | TMXEXPORT int tmx_load_tileset_callback(tmx_tileset_manager *ts_mgr, tmx_read_functor callback, void *userdata, const char *key); 55 | 56 | /* 57 | Load map using a Tileset Manager 58 | */ 59 | 60 | /* Same as tmx_load (tmx.h) but with a Tileset Manager. */ 61 | TMXEXPORT tmx_map* tmx_tsmgr_load(tmx_tileset_manager *ts_mgr, const char *path); 62 | 63 | /* Same as tmx_load_buffer (tmx.h) but with a Tileset Manager. */ 64 | TMXEXPORT tmx_map* tmx_tsmgr_load_buffer(tmx_tileset_manager *ts_mgr, const char *buffer, int len); 65 | 66 | /* Same as tmx_load_fd (tmx.h) but with a Tileset Manager. */ 67 | TMXEXPORT tmx_map* tmx_tsmgr_load_fd(tmx_tileset_manager *ts_mgr, int fd); 68 | 69 | /* Same as tmx_load_callback (tmx.h) but with a Tileset Manager. */ 70 | TMXEXPORT tmx_map* tmx_tsmgr_load_callback(tmx_tileset_manager *ts_mgr, tmx_read_functor callback, void *userdata); 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif 75 | 76 | #endif /* TSX_H */ 77 | -------------------------------------------------------------------------------- /src/video.c: -------------------------------------------------------------------------------- 1 | /** @file video.c 2 | * @ingroup Video 3 | * @defgroup Video 4 | * @brief Used to initialise and terminate SDL's video subsystem. 5 | * @author Michael Fitzmayer 6 | * @copyright "THE BEER-WARE LICENCE" (Revision 42) 7 | */ 8 | 9 | #include 10 | #include "video.h" 11 | 12 | /** 13 | * @brief Initialise SDL's video subsystem. 14 | * @param title the title of the window, in UTF-8 encoding. 15 | * @param width the width of the window, in screen coordinates. 16 | * @param height the height of the window, in screen coordinates. 17 | * @param fullscreen the window's fullscreen state. 18 | * @param zoomLevel the zoom level used by the renderer. 19 | * @return A Video structure or NULL on failure. See @ref struct Video. 20 | * @ingroup Video 21 | */ 22 | Video *videoInit(const char *title, int32_t width, int32_t height, uint8_t fullscreen, double zoomLevel) 23 | { 24 | static Video *video; 25 | video = malloc(sizeof(struct video_t)); 26 | 27 | if (NULL == video) 28 | { 29 | fprintf(stderr, "videoInit(): error allocating memory.\n"); 30 | return NULL; 31 | } 32 | 33 | if (0 != SDL_Init(SDL_INIT_VIDEO)) 34 | { 35 | fprintf(stderr, "%s\n", SDL_GetError()); 36 | free(video); 37 | return NULL; 38 | } 39 | 40 | video->windowHeight = height; 41 | video->windowWidth = width; 42 | video->zoomLevel = zoomLevel; 43 | video->zoomLevelInital = zoomLevel; 44 | 45 | uint32_t flags; 46 | if (fullscreen) 47 | { 48 | flags = SDL_WINDOW_FULLSCREEN_DESKTOP; 49 | } 50 | else 51 | { 52 | flags = 0; 53 | } 54 | 55 | video->window = SDL_CreateWindow( 56 | title, 57 | SDL_WINDOWPOS_UNDEFINED, 58 | SDL_WINDOWPOS_UNDEFINED, 59 | video->windowWidth, 60 | video->windowHeight, 61 | flags); 62 | 63 | if (NULL == video->window) 64 | { 65 | fprintf(stderr, "%s\n", SDL_GetError()); 66 | free(video); 67 | return NULL; 68 | } 69 | 70 | if (fullscreen) 71 | { 72 | SDL_GetWindowSize(video->window, &video->windowWidth, &video->windowHeight); 73 | if (0 > SDL_ShowCursor(SDL_DISABLE)) 74 | { 75 | fprintf(stderr, "%s\n", SDL_GetError()); 76 | free(video); 77 | return NULL; 78 | } 79 | } 80 | 81 | video->renderer = SDL_CreateRenderer( 82 | video->window, 83 | -1, 84 | SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); 85 | 86 | if (NULL == video->renderer) 87 | { 88 | fprintf(stderr, "%s\n", SDL_GetError()); 89 | free(video); 90 | return NULL; 91 | } 92 | 93 | if (0 != SDL_RenderSetLogicalSize(video->renderer, video->windowWidth / zoomLevel, video->windowHeight / zoomLevel)) 94 | { 95 | fprintf(stderr, "%s\n", SDL_GetError()); 96 | free(video); 97 | return NULL; 98 | } 99 | 100 | return video; 101 | } 102 | 103 | /** 104 | * @brief Set the renderer's zoom level. 105 | * @param video A Video structure. See @ref struct Video. 106 | * @param zoomLevel the zoom level 107 | * @ingroup Video 108 | * @return 0 on success, -1 on failure. 109 | */ 110 | int8_t videoSetZoomLevel(Video *video, double zoomLevel) 111 | { 112 | if (zoomLevel < 1) zoomLevel = 1; 113 | 114 | if (0 != SDL_RenderSetLogicalSize(video->renderer, video->windowWidth / zoomLevel, video->windowHeight / zoomLevel)) 115 | { 116 | fprintf(stderr, "%s\n", SDL_GetError()); 117 | return -1; 118 | } 119 | 120 | video->zoomLevel = zoomLevel; 121 | 122 | return 0; 123 | } 124 | 125 | /** 126 | * @brief Terminate SDL's video subsystem. 127 | * @param video a Video structure. See @ref struct Video. 128 | * @ingroup Video 129 | */ 130 | void videoTerminate(Video *video) 131 | { 132 | if ((NULL == video->window)) 133 | { 134 | fprintf(stderr, "%s\n", SDL_GetError()); 135 | } 136 | 137 | SDL_DestroyRenderer(video->renderer); 138 | SDL_DestroyWindow(video->window); 139 | free(video); 140 | } 141 | -------------------------------------------------------------------------------- /src/video.h: -------------------------------------------------------------------------------- 1 | /** @file video.h 2 | * @ingroup Video 3 | */ 4 | 5 | #ifndef VIDEO_h 6 | #define VIDEO_h 7 | 8 | #include 9 | #include 10 | 11 | /** 12 | * @ingroup Video 13 | */ 14 | typedef struct video_t 15 | { 16 | SDL_Renderer *renderer; 17 | SDL_Window *window; 18 | int32_t windowHeight; 19 | int32_t windowWidth; 20 | double zoomLevel; 21 | double zoomLevelInital; 22 | } Video; 23 | 24 | Video *videoInit(const char *title, int32_t width, int32_t height, uint8_t fullscreen, double zoomLevel); 25 | int8_t videoSetZoomLevel(Video *video, double zoomLevel); 26 | void videoTerminate(Video *video); 27 | 28 | #endif 29 | --------------------------------------------------------------------------------