Codebase list frogatto / 44b54f4
first git commit Dmitry E. Oboukhov 13 years ago
269 changed file(s) with 50695 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 Prerequisites:
1
2 You'll need to have these libraries with equivalent development versions to build Frogatto:
3
4 boost_iostreams >= 1.35.0
5 boost_regex >= 1.35.0
6 boost_asio >= 1.35.0
7 libsdl >= 1.2.7
8 libsdl-image >= 1.2 (with png support)
9 libsdl-mixer >= 1.2 (with Vorbis support)
10 libsdl-ttf >= 2.0.8
11 ccache
12 libz
13
14 Building:
15
16 To build, type 'make'. The Makefile will probably work. :) If it doesn't you may have to tweak it for your platform. The executable 'game' will be created which you can run.
0 The Frogatto game and all content is available for download free of charge from http://www.frogatto.com. The game may be redistributed for non-commercial purposes so long as the entire package is kept in-tact and unmodified. This license must also be included and kept in-tact. It is forbidden to distribute the game, or any portion thereof for any commercial or revenue-generating purpose.
1
2 The source code to the game -- limited to the C++ source code files found in the "src" directory -- is also available for license under the GNU GPL version 3 or later. See the LICENSE file in that directory for the details of the GPL.
0 objects = IMG_savepng.o background.o blur.o border_widget.o button.o character_editor_dialog.o current_generator.o editor_dialogs.o editor_formula_functions.o editor_layers_dialog.o editor_stats_dialog.o editor_variable_info.o collision_utils.o color_utils.o controls.o custom_object.o custom_object_callable.o custom_object_functions.o custom_object_type.o debug_console.o dialog.o draw_number.o draw_scene.o draw_tile.o editor.o editor_level_properties_dialog.o entity.o filesystem.o font.o formula.o formula_callable_definition.o formula_constants.o formula_function.o formula_profiler.o formula_tokenizer.o formula_variable_storage.o frame.o framed_gui_element.o geometry.o graphical_font.o graphical_font_label.o grid_widget.o group_property_editor_dialog.o gui_formula_functions.o gui_section.o image_widget.o input.o inventory.o iphone_controls.o joystick.o key.o label.o level.o level_logic.o level_object.o level_runner.o level_solid_map.o load_level_nothread.o main.o message_dialog.o movement_script.o multi_tile_pattern.o multiplayer.o object_events.o options_dialog.o particle_system.o pause_game_dialog.o playable_custom_object.o player_info.o powerup.o preferences.o preprocessor.o preview_tileset_widget.o property_editor_dialog.o random.o raster.o raster_distortion.o rectangle_rotator.o settings_dialog.o solid_map.o sound.o speech_dialog.o stats.o string_utils.o surface_cache.o surface_formula.o surface_palette.o surface_scaling.o surface.o texture.o text_entry_widget.o thread.o tile_map.o tileset_editor_dialog.o tooltip.o translate.o utils.o variant.o water.o water_particle_system.o weather_particle_system.o widget.o wml_formula_adapter.o wml_formula_callable.o wml_modify.o wml_node.o wml_parser.o wml_schema.o wml_utils.o wml_writer.o unit_test.o formula_test.o wml_parser_test.o loading_screen.o utility_object_compiler.o utility_object_editor.o
1
2 server_objects = server.o simple_wml.o
3
4 formula_test_objects = filesystem.o formula_function.o formula_tokenizer.o string_utils.o variant.o wml_node.o wml_parser.o wml_utils.o wml_writer.o
5
6 wml_modify_test_objects = filesystem.o string_utils.o wml_node.o wml_parser.o wml_utils.o
7 wml_schema_test_objects = filesystem.o string_utils.o wml_node.o wml_parser.o wml_utils.o
8
9 OPT=-O2 -fno-inline-functions
10
11 %.o : src/%.cpp
12 ccache g++ -DIMPLEMENT_SAVE_PNG -fno-inline-functions -g $(OPT) `sdl-config --cflags` -I/usr/X11R6/include -D_GNU_SOURCE=1 -D_REENTRANT -Wnon-virtual-dtor -Wreturn-type -fthreadsafe-statics -c $<
13
14 game: $(objects)
15 g++ -g $(OPT) -L. -L/sw/lib -L/usr/X11R6/lib -L. -lX11 -D_GNU_SOURCE=1 -D_REENTRANT -Wnon-virtual-dtor -Wreturn-type -L/usr/lib `sdl-config --libs` -lSDLmain -lSDL -lGL -lGLU -lGLEW -lSDL_image -lSDL_ttf -lSDL_mixer -lpng -lboost_regex-mt -lboost_system-mt -fthreadsafe-statics $(objects) -o game
16
17 server: $(server_objects)
18 g++ -fno-inline-functions -g $(OPT) -L/sw/lib -L/usr/X11R6/lib -lX11 -D_GNU_SOURCE=1 -D_REENTRANT -Wnon-virtual-dtor -Wreturn-type -L/usr/lib `sdl-config --libs` -lSDLmain -lSDL -lGL -lGLU -lSDL_image -lSDL_ttf -lSDL_mixer -lboost_regex-mt -lboost_system-mt -lboost_thread-mt -lboost_iostreams-mt -fthreadsafe-statics $(server_objects) -o server
19
20 poolalloc.o: src/poolalloc.c
21 gcc -fno-inline-functions -g $(OPT) `sdl-config --cflags` -I/usr/X11R6/include -D_GNU_SOURCE=1 -D_REENTRANT -Wreturn-type -c src/poolalloc.c
22
23 malloc.o: src/malloc.c
24 gcc -fno-inline-functions -g $(OPT) `sdl-config --cflags` -I/usr/X11R6/include -D_GNU_SOURCE=1 -D_REENTRANT -DUSE_LOCKS=1 -Wreturn-type -c src/malloc.c
25
26 formula_test: $(formula_test_objects)
27 g++ -O2 -g -I/usr/include/SDL -D_GNU_SOURCE=1 -D_REENTRANT -DUNIT_TEST_FORMULA -Wnon-virtual-dtor -Wreturn-type -L/usr/lib -lSDL -lGL -lGLU -lSDL_image -lSDL_ttf -lSDL_mixer -lboost_regex src/formula.cpp $(formula_test_objects) -o test
28
29 wml_modify_test: $(wml_modify_test_objects)
30 g++ -O2 -g -framework Cocoa -I/usr/local/include/boost-1_34 -I/sw/include/SDL -I/usr/X11R6/include -Isrc/ -I/usr/include/SDL -D_GNU_SOURCE=1 -D_REENTRANT -DUNIT_TEST_WML_MODIFY -Wnon-virtual-dtor -Wreturn-type -L/usr/lib -lboost_regex src/wml_modify.cpp $(wml_modify_test_objects) -o test
31
32 wml_schema_test: $(wml_schema_test_objects)
33 g++ -O2 -g -framework Cocoa -I/usr/local/include/boost-1_34 -I/sw/include/SDL -I/usr/X11R6/include -Isrc/ -I/usr/include/SDL -D_GNU_SOURCE=1 -D_REENTRANT -DUNIT_TEST_WML_SCHEMA -Wnon-virtual-dtor -Wreturn-type -L/usr/lib -lboost_regex src/wml_schema.cpp $(wml_schema_test_objects) -o test
34
35
36 clean:
37 rm -f *.o game
0 frogatto (1.0+dfsg1-1) unstable; urgency=low
1
2 * Initial release. (Closes: #589258).
3
4 -- Dmitry E. Oboukhov <unera@debian.org> Fri, 16 Jul 2010 12:14:11 +0400
0 Source: frogatto
1 Section: contrib/games
2 Priority: extra
3 Maintainer: Dmitry E. Oboukhov <unera@debian.org>
4 Standards-Version: 3.9.0
5 Build-Depends: cdbs, debhelper (>= 7.0)
6 Homepage: http://www.frogatto.com/
7 Uploaders: Debian Games Team <pkg-games-devel@lists.alioth.debian.org>
8
9 Package: frogatto
10 Architecture: any
11 Description: 2D platformer game starring a quixotic frog
12 Frogatto is a platformer in the style of old arcade, Sega and
13 Nintendo games. The world is viewed as a cross-section seen from the
14 side, and your character walks and jumps between solid platforms
15 whilst fighting monsters.
16
0 #!/usr/bin/make -f
1
2 include /usr/share/cdbs/1/rules/debhelper.mk
3
4 clean::
5 find -name \*.o -delete
6 rm -f game
7
8 build/frogatto::
9 make
0 3.0 (quilt)
0 /*
1 Based on zlib license - see http://www.gzip.org/zlib/zlib_license.html
2
3 This software is provided 'as-is', without any express or implied
4 warranty. In no event will the authors be held liable for any damages
5 arising from the use of this software.
6
7 Permission is granted to anyone to use this software for any purpose,
8 including commercial applications, and to alter it and redistribute it
9 freely, subject to the following restrictions:
10
11 1. The origin of this software must not be misrepresented; you must not
12 claim that you wrote the original software. If you use this software
13 in a product, an acknowledgment in the product documentation would be
14 appreciated but is not required.
15 2. Altered source versions must be plainly marked as such, and must not be
16 misrepresented as being the original software.
17 3. This notice may not be removed or altered from any source distribution.
18
19 "Philip D. Bober" <wildfire1138@mchsi.com>
20 */
21
22 /**
23 * 4/17/04 - IMG_SavePNG & IMG_SavePNG_RW - Philip D. Bober
24 * 11/08/2004 - Compr fix, levels -1,1-7 now work - Tyler Montbriand
25 */
26
27 #ifdef IMPLEMENT_SAVE_PNG
28 #include <png.h>
29 #endif
30
31 #include <stdlib.h>
32 #include <SDL/SDL.h>
33 #include <GL/gl.h>
34
35 #include <string>
36
37 #include "filesystem.hpp"
38 #include "IMG_savepng.h"
39 #include "preferences.hpp"
40 #include "stats.hpp"
41 #include "surface.hpp"
42
43 int IMG_SaveFrameBuffer(const char* file, int compression)
44 {
45 const int w = preferences::actual_screen_width();
46 const int h = preferences::actual_screen_height();
47
48 graphics::surface s(SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 24, SURFACE_MASK_RGB));
49 glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, s->pixels);
50
51 unsigned char* pixels = (unsigned char*)s->pixels;
52
53 for(int n = 0; n != h/2; ++n) {
54 unsigned char* s1 = pixels + n*w*3;
55 unsigned char* s2 = pixels + (h-n-1)*w*3;
56 for(int m = 0; m != w*3; ++m) {
57 std::swap(s1[m], s2[m]);
58 }
59 }
60
61 const int result = IMG_SavePNG(file, s.get(), compression);
62 if(result == -1) {
63 fprintf(stderr, "FAILED TO SAVE SCREENSHOT\n");
64 return result;
65 }
66 fprintf(stderr, "SAVED SCREENSHOT TO %s, UPLOADING...\n", file);
67 http_upload(sys::read_file(file), "upload-screenshot");
68 fprintf(stderr, "UPLOADED SCREENSHOT\n");
69 return result;
70 }
71
72 int IMG_SavePNG(const char *file, SDL_Surface *surf,int compression){
73 #ifdef IMPLEMENT_SAVE_PNG
74 SDL_RWops *fp;
75 int ret;
76
77 fp=SDL_RWFromFile(file,"wb");
78
79 if( fp == NULL ) {
80 return (-1);
81 }
82
83 ret=IMG_SavePNG_RW(fp,surf,compression);
84 SDL_RWclose(fp);
85 return ret;
86 #else
87 return -1;
88 #endif
89 }
90
91 #ifdef IMPLEMENT_SAVE_PNG
92 static void png_write_data(png_structp png_ptr,png_bytep data, png_size_t length){
93 SDL_RWops *rp = (SDL_RWops*) png_get_io_ptr(png_ptr);
94 SDL_RWwrite(rp,data,1,length);
95 }
96 #endif
97
98 int IMG_SavePNG_RW(SDL_RWops *src, SDL_Surface *surf,int compression){
99 #ifdef IMPLEMENT_SAVE_PNG
100 png_structp png_ptr;
101 png_infop info_ptr;
102 SDL_PixelFormat *fmt=NULL;
103 SDL_Surface *tempsurf=NULL;
104 int ret,funky_format,used_alpha;
105 unsigned int i,temp_alpha;
106 png_colorp palette;
107 Uint8 *palette_alpha=NULL;
108 png_byte **row_pointers=NULL;
109 png_ptr=NULL;info_ptr=NULL;palette=NULL;ret=-1;
110 funky_format=0;
111
112 if( !src || !surf) {
113 goto savedone; /* Nothing to do. */
114 }
115
116 row_pointers=(png_byte **)malloc(surf->h * sizeof(png_byte*));
117 if (!row_pointers) {
118 SDL_SetError("Couldn't allocate memory for rowpointers");
119 goto savedone;
120 }
121
122 png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,NULL,NULL);
123 if (!png_ptr){
124 SDL_SetError("Couldn't allocate memory for PNG file");
125 goto savedone;
126 }
127 info_ptr= png_create_info_struct(png_ptr);
128 if (!info_ptr){
129 SDL_SetError("Couldn't allocate image information for PNG file");
130 goto savedone;
131 }
132 /* setup custom writer functions */
133 png_set_write_fn(png_ptr,(voidp)src,png_write_data,NULL);
134
135 if (setjmp(png_jmpbuf(png_ptr))){
136 SDL_SetError("Unknown error writing PNG");
137 goto savedone;
138 }
139
140 if(compression>Z_BEST_COMPRESSION)
141 compression=Z_BEST_COMPRESSION;
142
143 if(compression == Z_NO_COMPRESSION) // No compression
144 {
145 png_set_filter(png_ptr,0,PNG_FILTER_NONE);
146 png_set_compression_level(png_ptr,Z_NO_COMPRESSION);
147 }
148 else if(compression<0) // Default compression
149 png_set_compression_level(png_ptr,Z_DEFAULT_COMPRESSION);
150 else
151 png_set_compression_level(png_ptr,compression);
152
153 fmt=surf->format;
154 if(fmt->BitsPerPixel==8){ /* Paletted */
155 png_set_IHDR(png_ptr,info_ptr,
156 surf->w,surf->h,8,PNG_COLOR_TYPE_PALETTE,
157 PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,
158 PNG_FILTER_TYPE_DEFAULT);
159 palette=(png_colorp) malloc(fmt->palette->ncolors * sizeof(png_color));
160 if (!palette) {
161 SDL_SetError("Couldn't create memory for palette");
162 goto savedone;
163 }
164 for (i=0;i<fmt->palette->ncolors;i++) {
165 palette[i].red=fmt->palette->colors[i].r;
166 palette[i].green=fmt->palette->colors[i].g;
167 palette[i].blue=fmt->palette->colors[i].b;
168 }
169 png_set_PLTE(png_ptr,info_ptr,palette,fmt->palette->ncolors);
170 if (surf->flags&SDL_SRCCOLORKEY) {
171 palette_alpha=(Uint8 *)malloc((fmt->colorkey+1)*sizeof(Uint8));
172 if (!palette_alpha) {
173 SDL_SetError("Couldn't create memory for palette transparency");
174 goto savedone;
175 }
176 /* FIXME: memset? */
177 for (i=0;i<(fmt->colorkey+1);i++) {
178 palette_alpha[i]=255;
179 }
180 palette_alpha[fmt->colorkey]=0;
181 png_set_tRNS(png_ptr,info_ptr,palette_alpha,fmt->colorkey+1,NULL);
182 }
183 }else{ /* Truecolor */
184 if (fmt->Amask) {
185 png_set_IHDR(png_ptr,info_ptr,
186 surf->w,surf->h,8,PNG_COLOR_TYPE_RGB_ALPHA,
187 PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,
188 PNG_FILTER_TYPE_DEFAULT);
189 } else {
190 png_set_IHDR(png_ptr,info_ptr,
191 surf->w,surf->h,8,PNG_COLOR_TYPE_RGB,
192 PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,
193 PNG_FILTER_TYPE_DEFAULT);
194 }
195 }
196 png_write_info(png_ptr, info_ptr);
197
198 if (fmt->BitsPerPixel==8) { /* Paletted */
199 for(i=0;i<surf->h;i++){
200 row_pointers[i]= ((png_byte*)surf->pixels) + i*surf->pitch;
201 }
202 if(SDL_MUSTLOCK(surf)){
203 SDL_LockSurface(surf);
204 }
205 png_write_image(png_ptr, row_pointers);
206 if(SDL_MUSTLOCK(surf)){
207 SDL_UnlockSurface(surf);
208 }
209 }else{ /* Truecolor */
210 if(fmt->BytesPerPixel==3){
211 if(fmt->Amask){ /* check for 24 bit with alpha */
212 funky_format=1;
213 }else{
214 /* Check for RGB/BGR/GBR/RBG/etc surfaces.*/
215 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
216 if(fmt->Rmask!=0xFF0000
217 || fmt->Gmask!=0x00FF00
218 || fmt->Bmask!=0x0000FF){
219 #else
220 if(fmt->Rmask!=0x0000FF
221 || fmt->Gmask!=0x00FF00
222 || fmt->Bmask!=0xFF0000){
223 #endif
224 funky_format=1;
225 }
226 }
227 }else if (fmt->BytesPerPixel==4){
228 if (!fmt->Amask) { /* check for 32bit but no alpha */
229 funky_format=1;
230 }else{
231 /* Check for ARGB/ABGR/GBAR/RABG/etc surfaces.*/
232 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
233 if(fmt->Rmask!=0xFF000000
234 || fmt->Gmask!=0x00FF0000
235 || fmt->Bmask!=0x0000FF00
236 || fmt->Amask!=0x000000FF){
237 #else
238 if(fmt->Rmask!=0x000000FF
239 || fmt->Gmask!=0x0000FF00
240 || fmt->Bmask!=0x00FF0000
241 || fmt->Amask!=0xFF000000){
242 #endif
243 funky_format=1;
244 }
245 }
246 }else{ /* 555 or 565 16 bit color */
247 funky_format=1;
248 }
249 if (funky_format) {
250 /* Allocate non-funky format, and copy pixeldata in*/
251 if(fmt->Amask){
252 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
253 tempsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, surf->w, surf->h, 24,
254 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff);
255 #else
256 tempsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, surf->w, surf->h, 24,
257 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
258 #endif
259 }else{
260 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
261 tempsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, surf->w, surf->h, 24,
262 0xff0000, 0x00ff00, 0x0000ff, 0x00000000);
263 #else
264 tempsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, surf->w, surf->h, 24,
265 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000);
266 #endif
267 }
268 if(!tempsurf){
269 SDL_SetError("Couldn't allocate temp surface");
270 goto savedone;
271 }
272 if(surf->flags&SDL_SRCALPHA){
273 temp_alpha=fmt->alpha;
274 used_alpha=1;
275 SDL_SetAlpha(surf,0,255); /* Set for an opaque blit */
276 }else{
277 used_alpha=0;
278 }
279 if(SDL_BlitSurface(surf,NULL,tempsurf,NULL)!=0){
280 SDL_SetError("Couldn't blit surface to temp surface");
281 SDL_FreeSurface(tempsurf);
282 goto savedone;
283 }
284 if (used_alpha) {
285 SDL_SetAlpha(surf,SDL_SRCALPHA,(Uint8)temp_alpha); /* Restore alpha settings*/
286 }
287 for(i=0;i<tempsurf->h;i++){
288 row_pointers[i]= ((png_byte*)tempsurf->pixels) + i*tempsurf->pitch;
289 }
290 if(SDL_MUSTLOCK(tempsurf)){
291 SDL_LockSurface(tempsurf);
292 }
293 png_write_image(png_ptr, row_pointers);
294 if(SDL_MUSTLOCK(tempsurf)){
295 SDL_UnlockSurface(tempsurf);
296 }
297 SDL_FreeSurface(tempsurf);
298 } else {
299 for(i=0;i<surf->h;i++){
300 row_pointers[i]= ((png_byte*)surf->pixels) + i*surf->pitch;
301 }
302 if(SDL_MUSTLOCK(surf)){
303 SDL_LockSurface(surf);
304 }
305 png_write_image(png_ptr, row_pointers);
306 if(SDL_MUSTLOCK(surf)){
307 SDL_UnlockSurface(surf);
308 }
309 }
310 }
311
312 png_write_end(png_ptr, NULL);
313 ret=0; /* got here, so nothing went wrong. YAY! */
314
315 savedone: /* clean up and return */
316 png_destroy_write_struct(&png_ptr,&info_ptr);
317 if (palette) {
318 free(palette);
319 }
320 if (palette_alpha) {
321 free(palette_alpha);
322 }
323 if (row_pointers) {
324 free(row_pointers);
325 }
326 return ret;
327 #else
328 return -1;
329 #endif
330 }
0 /*
1 Based on zlib license - see http://www.gzip.org/zlib/zlib_license.html
2
3 This software is provided 'as-is', without any express or implied
4 warranty. In no event will the authors be held liable for any damages
5 arising from the use of this software.
6
7 Permission is granted to anyone to use this software for any purpose,
8 including commercial applications, and to alter it and redistribute it
9 freely, subject to the following restrictions:
10
11 1. The origin of this software must not be misrepresented; you must not
12 claim that you wrote the original software. If you use this software
13 in a product, an acknowledgment in the product documentation would be
14 appreciated but is not required.
15 2. Altered source versions must be plainly marked as such, and must not be
16 misrepresented as being the original software.
17 3. This notice may not be removed or altered from any source distribution.
18
19 "Philip D. Bober" <wildfire1138@mchsi.com>
20
21 Modified by David White <davewx7@gmail.com>
22 */
23 #ifndef __IMG_SAVETOPNG_H__
24 #define __IMG_SAVETOPNG_H__
25
26 #include <SDL.h>
27
28 #define IMG_COMPRESS_OFF 0
29 #define IMG_COMPRESS_MAX 9
30 #define IMG_COMPRESS_DEFAULT -1
31
32 int IMG_SaveFrameBuffer(const char* file, int compression=5);
33
34 /**
35 * Takes a filename, a surface to save, and a compression level. The
36 * compression level can be 0(min) through 9(max), or -1(default).
37 */
38 int IMG_SavePNG(const char* file, SDL_Surface* surf, int compression);
39 /**
40 * Takes a SDL_RWops pointer, a surface to save, and a compression level.
41 * compression can be 0(min) through 9(max), or -1(default).
42 */
43 int IMG_SavePNG_RW(SDL_RWops* src, SDL_Surface* surf, int compression);
44
45 #endif/*__IMG_SAVETOPNG_H__*/
0 GNU GENERAL PUBLIC LICENSE
1 Version 3, 29 June 2007
2
3 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
4 Everyone is permitted to copy and distribute verbatim copies
5 of this license document, but changing it is not allowed.
6
7 Preamble
8
9 The GNU General Public License is a free, copyleft license for
10 software and other kinds of works.
11
12 The licenses for most software and other practical works are designed
13 to take away your freedom to share and change the works. By contrast,
14 the GNU General Public License is intended to guarantee your freedom to
15 share and change all versions of a program--to make sure it remains free
16 software for all its users. We, the Free Software Foundation, use the
17 GNU General Public License for most of our software; it applies also to
18 any other work released this way by its authors. You can apply it to
19 your programs, too.
20
21 When we speak of free software, we are referring to freedom, not
22 price. Our General Public Licenses are designed to make sure that you
23 have the freedom to distribute copies of free software (and charge for
24 them if you wish), that you receive source code or can get it if you
25 want it, that you can change the software or use pieces of it in new
26 free programs, and that you know you can do these things.
27
28 To protect your rights, we need to prevent others from denying you
29 these rights or asking you to surrender the rights. Therefore, you have
30 certain responsibilities if you distribute copies of the software, or if
31 you modify it: responsibilities to respect the freedom of others.
32
33 For example, if you distribute copies of such a program, whether
34 gratis or for a fee, you must pass on to the recipients the same
35 freedoms that you received. You must make sure that they, too, receive
36 or can get the source code. And you must show them these terms so they
37 know their rights.
38
39 Developers that use the GNU GPL protect your rights with two steps:
40 (1) assert copyright on the software, and (2) offer you this License
41 giving you legal permission to copy, distribute and/or modify it.
42
43 For the developers' and authors' protection, the GPL clearly explains
44 that there is no warranty for this free software. For both users' and
45 authors' sake, the GPL requires that modified versions be marked as
46 changed, so that their problems will not be attributed erroneously to
47 authors of previous versions.
48
49 Some devices are designed to deny users access to install or run
50 modified versions of the software inside them, although the manufacturer
51 can do so. This is fundamentally incompatible with the aim of
52 protecting users' freedom to change the software. The systematic
53 pattern of such abuse occurs in the area of products for individuals to
54 use, which is precisely where it is most unacceptable. Therefore, we
55 have designed this version of the GPL to prohibit the practice for those
56 products. If such problems arise substantially in other domains, we
57 stand ready to extend this provision to those domains in future versions
58 of the GPL, as needed to protect the freedom of users.
59
60 Finally, every program is threatened constantly by software patents.
61 States should not allow patents to restrict development and use of
62 software on general-purpose computers, but in those that do, we wish to
63 avoid the special danger that patents applied to a free program could
64 make it effectively proprietary. To prevent this, the GPL assures that
65 patents cannot be used to render the program non-free.
66
67 The precise terms and conditions for copying, distribution and
68 modification follow.
69
70 TERMS AND CONDITIONS
71
72 0. Definitions.
73
74 "This License" refers to version 3 of the GNU General Public License.
75
76 "Copyright" also means copyright-like laws that apply to other kinds of
77 works, such as semiconductor masks.
78
79 "The Program" refers to any copyrightable work licensed under this
80 License. Each licensee is addressed as "you". "Licensees" and
81 "recipients" may be individuals or organizations.
82
83 To "modify" a work means to copy from or adapt all or part of the work
84 in a fashion requiring copyright permission, other than the making of an
85 exact copy. The resulting work is called a "modified version" of the
86 earlier work or a work "based on" the earlier work.
87
88 A "covered work" means either the unmodified Program or a work based
89 on the Program.
90
91 To "propagate" a work means to do anything with it that, without
92 permission, would make you directly or secondarily liable for
93 infringement under applicable copyright law, except executing it on a
94 computer or modifying a private copy. Propagation includes copying,
95 distribution (with or without modification), making available to the
96 public, and in some countries other activities as well.
97
98 To "convey" a work means any kind of propagation that enables other
99 parties to make or receive copies. Mere interaction with a user through
100 a computer network, with no transfer of a copy, is not conveying.
101
102 An interactive user interface displays "Appropriate Legal Notices"
103 to the extent that it includes a convenient and prominently visible
104 feature that (1) displays an appropriate copyright notice, and (2)
105 tells the user that there is no warranty for the work (except to the
106 extent that warranties are provided), that licensees may convey the
107 work under this License, and how to view a copy of this License. If
108 the interface presents a list of user commands or options, such as a
109 menu, a prominent item in the list meets this criterion.
110
111 1. Source Code.
112
113 The "source code" for a work means the preferred form of the work
114 for making modifications to it. "Object code" means any non-source
115 form of a work.
116
117 A "Standard Interface" means an interface that either is an official
118 standard defined by a recognized standards body, or, in the case of
119 interfaces specified for a particular programming language, one that
120 is widely used among developers working in that language.
121
122 The "System Libraries" of an executable work include anything, other
123 than the work as a whole, that (a) is included in the normal form of
124 packaging a Major Component, but which is not part of that Major
125 Component, and (b) serves only to enable use of the work with that
126 Major Component, or to implement a Standard Interface for which an
127 implementation is available to the public in source code form. A
128 "Major Component", in this context, means a major essential component
129 (kernel, window system, and so on) of the specific operating system
130 (if any) on which the executable work runs, or a compiler used to
131 produce the work, or an object code interpreter used to run it.
132
133 The "Corresponding Source" for a work in object code form means all
134 the source code needed to generate, install, and (for an executable
135 work) run the object code and to modify the work, including scripts to
136 control those activities. However, it does not include the work's
137 System Libraries, or general-purpose tools or generally available free
138 programs which are used unmodified in performing those activities but
139 which are not part of the work. For example, Corresponding Source
140 includes interface definition files associated with source files for
141 the work, and the source code for shared libraries and dynamically
142 linked subprograms that the work is specifically designed to require,
143 such as by intimate data communication or control flow between those
144 subprograms and other parts of the work.
145
146 The Corresponding Source need not include anything that users
147 can regenerate automatically from other parts of the Corresponding
148 Source.
149
150 The Corresponding Source for a work in source code form is that
151 same work.
152
153 2. Basic Permissions.
154
155 All rights granted under this License are granted for the term of
156 copyright on the Program, and are irrevocable provided the stated
157 conditions are met. This License explicitly affirms your unlimited
158 permission to run the unmodified Program. The output from running a
159 covered work is covered by this License only if the output, given its
160 content, constitutes a covered work. This License acknowledges your
161 rights of fair use or other equivalent, as provided by copyright law.
162
163 You may make, run and propagate covered works that you do not
164 convey, without conditions so long as your license otherwise remains
165 in force. You may convey covered works to others for the sole purpose
166 of having them make modifications exclusively for you, or provide you
167 with facilities for running those works, provided that you comply with
168 the terms of this License in conveying all material for which you do
169 not control copyright. Those thus making or running the covered works
170 for you must do so exclusively on your behalf, under your direction
171 and control, on terms that prohibit them from making any copies of
172 your copyrighted material outside their relationship with you.
173
174 Conveying under any other circumstances is permitted solely under
175 the conditions stated below. Sublicensing is not allowed; section 10
176 makes it unnecessary.
177
178 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
179
180 No covered work shall be deemed part of an effective technological
181 measure under any applicable law fulfilling obligations under article
182 11 of the WIPO copyright treaty adopted on 20 December 1996, or
183 similar laws prohibiting or restricting circumvention of such
184 measures.
185
186 When you convey a covered work, you waive any legal power to forbid
187 circumvention of technological measures to the extent such circumvention
188 is effected by exercising rights under this License with respect to
189 the covered work, and you disclaim any intention to limit operation or
190 modification of the work as a means of enforcing, against the work's
191 users, your or third parties' legal rights to forbid circumvention of
192 technological measures.
193
194 4. Conveying Verbatim Copies.
195
196 You may convey verbatim copies of the Program's source code as you
197 receive it, in any medium, provided that you conspicuously and
198 appropriately publish on each copy an appropriate copyright notice;
199 keep intact all notices stating that this License and any
200 non-permissive terms added in accord with section 7 apply to the code;
201 keep intact all notices of the absence of any warranty; and give all
202 recipients a copy of this License along with the Program.
203
204 You may charge any price or no price for each copy that you convey,
205 and you may offer support or warranty protection for a fee.
206
207 5. Conveying Modified Source Versions.
208
209 You may convey a work based on the Program, or the modifications to
210 produce it from the Program, in the form of source code under the
211 terms of section 4, provided that you also meet all of these conditions:
212
213 a) The work must carry prominent notices stating that you modified
214 it, and giving a relevant date.
215
216 b) The work must carry prominent notices stating that it is
217 released under this License and any conditions added under section
218 7. This requirement modifies the requirement in section 4 to
219 "keep intact all notices".
220
221 c) You must license the entire work, as a whole, under this
222 License to anyone who comes into possession of a copy. This
223 License will therefore apply, along with any applicable section 7
224 additional terms, to the whole of the work, and all its parts,
225 regardless of how they are packaged. This License gives no
226 permission to license the work in any other way, but it does not
227 invalidate such permission if you have separately received it.
228
229 d) If the work has interactive user interfaces, each must display
230 Appropriate Legal Notices; however, if the Program has interactive
231 interfaces that do not display Appropriate Legal Notices, your
232 work need not make them do so.
233
234 A compilation of a covered work with other separate and independent
235 works, which are not by their nature extensions of the covered work,
236 and which are not combined with it such as to form a larger program,
237 in or on a volume of a storage or distribution medium, is called an
238 "aggregate" if the compilation and its resulting copyright are not
239 used to limit the access or legal rights of the compilation's users
240 beyond what the individual works permit. Inclusion of a covered work
241 in an aggregate does not cause this License to apply to the other
242 parts of the aggregate.
243
244 6. Conveying Non-Source Forms.
245
246 You may convey a covered work in object code form under the terms
247 of sections 4 and 5, provided that you also convey the
248 machine-readable Corresponding Source under the terms of this License,
249 in one of these ways:
250
251 a) Convey the object code in, or embodied in, a physical product
252 (including a physical distribution medium), accompanied by the
253 Corresponding Source fixed on a durable physical medium
254 customarily used for software interchange.
255
256 b) Convey the object code in, or embodied in, a physical product
257 (including a physical distribution medium), accompanied by a
258 written offer, valid for at least three years and valid for as
259 long as you offer spare parts or customer support for that product
260 model, to give anyone who possesses the object code either (1) a
261 copy of the Corresponding Source for all the software in the
262 product that is covered by this License, on a durable physical
263 medium customarily used for software interchange, for a price no
264 more than your reasonable cost of physically performing this
265 conveying of source, or (2) access to copy the
266 Corresponding Source from a network server at no charge.
267
268 c) Convey individual copies of the object code with a copy of the
269 written offer to provide the Corresponding Source. This
270 alternative is allowed only occasionally and noncommercially, and
271 only if you received the object code with such an offer, in accord
272 with subsection 6b.
273
274 d) Convey the object code by offering access from a designated
275 place (gratis or for a charge), and offer equivalent access to the
276 Corresponding Source in the same way through the same place at no
277 further charge. You need not require recipients to copy the
278 Corresponding Source along with the object code. If the place to
279 copy the object code is a network server, the Corresponding Source
280 may be on a different server (operated by you or a third party)
281 that supports equivalent copying facilities, provided you maintain
282 clear directions next to the object code saying where to find the
283 Corresponding Source. Regardless of what server hosts the
284 Corresponding Source, you remain obligated to ensure that it is
285 available for as long as needed to satisfy these requirements.
286
287 e) Convey the object code using peer-to-peer transmission, provided
288 you inform other peers where the object code and Corresponding
289 Source of the work are being offered to the general public at no
290 charge under subsection 6d.
291
292 A separable portion of the object code, whose source code is excluded
293 from the Corresponding Source as a System Library, need not be
294 included in conveying the object code work.
295
296 A "User Product" is either (1) a "consumer product", which means any
297 tangible personal property which is normally used for personal, family,
298 or household purposes, or (2) anything designed or sold for incorporation
299 into a dwelling. In determining whether a product is a consumer product,
300 doubtful cases shall be resolved in favor of coverage. For a particular
301 product received by a particular user, "normally used" refers to a
302 typical or common use of that class of product, regardless of the status
303 of the particular user or of the way in which the particular user
304 actually uses, or expects or is expected to use, the product. A product
305 is a consumer product regardless of whether the product has substantial
306 commercial, industrial or non-consumer uses, unless such uses represent
307 the only significant mode of use of the product.
308
309 "Installation Information" for a User Product means any methods,
310 procedures, authorization keys, or other information required to install
311 and execute modified versions of a covered work in that User Product from
312 a modified version of its Corresponding Source. The information must
313 suffice to ensure that the continued functioning of the modified object
314 code is in no case prevented or interfered with solely because
315 modification has been made.
316
317 If you convey an object code work under this section in, or with, or
318 specifically for use in, a User Product, and the conveying occurs as
319 part of a transaction in which the right of possession and use of the
320 User Product is transferred to the recipient in perpetuity or for a
321 fixed term (regardless of how the transaction is characterized), the
322 Corresponding Source conveyed under this section must be accompanied
323 by the Installation Information. But this requirement does not apply
324 if neither you nor any third party retains the ability to install
325 modified object code on the User Product (for example, the work has
326 been installed in ROM).
327
328 The requirement to provide Installation Information does not include a
329 requirement to continue to provide support service, warranty, or updates
330 for a work that has been modified or installed by the recipient, or for
331 the User Product in which it has been modified or installed. Access to a
332 network may be denied when the modification itself materially and
333 adversely affects the operation of the network or violates the rules and
334 protocols for communication across the network.
335
336 Corresponding Source conveyed, and Installation Information provided,
337 in accord with this section must be in a format that is publicly
338 documented (and with an implementation available to the public in
339 source code form), and must require no special password or key for
340 unpacking, reading or copying.
341
342 7. Additional Terms.
343
344 "Additional permissions" are terms that supplement the terms of this
345 License by making exceptions from one or more of its conditions.
346 Additional permissions that are applicable to the entire Program shall
347 be treated as though they were included in this License, to the extent
348 that they are valid under applicable law. If additional permissions
349 apply only to part of the Program, that part may be used separately
350 under those permissions, but the entire Program remains governed by
351 this License without regard to the additional permissions.
352
353 When you convey a copy of a covered work, you may at your option
354 remove any additional permissions from that copy, or from any part of
355 it. (Additional permissions may be written to require their own
356 removal in certain cases when you modify the work.) You may place
357 additional permissions on material, added by you to a covered work,
358 for which you have or can give appropriate copyright permission.
359
360 Notwithstanding any other provision of this License, for material you
361 add to a covered work, you may (if authorized by the copyright holders of
362 that material) supplement the terms of this License with terms:
363
364 a) Disclaiming warranty or limiting liability differently from the
365 terms of sections 15 and 16 of this License; or
366
367 b) Requiring preservation of specified reasonable legal notices or
368 author attributions in that material or in the Appropriate Legal
369 Notices displayed by works containing it; or
370
371 c) Prohibiting misrepresentation of the origin of that material, or
372 requiring that modified versions of such material be marked in
373 reasonable ways as different from the original version; or
374
375 d) Limiting the use for publicity purposes of names of licensors or
376 authors of the material; or
377
378 e) Declining to grant rights under trademark law for use of some
379 trade names, trademarks, or service marks; or
380
381 f) Requiring indemnification of licensors and authors of that
382 material by anyone who conveys the material (or modified versions of
383 it) with contractual assumptions of liability to the recipient, for
384 any liability that these contractual assumptions directly impose on
385 those licensors and authors.
386
387 All other non-permissive additional terms are considered "further
388 restrictions" within the meaning of section 10. If the Program as you
389 received it, or any part of it, contains a notice stating that it is
390 governed by this License along with a term that is a further
391 restriction, you may remove that term. If a license document contains
392 a further restriction but permits relicensing or conveying under this
393 License, you may add to a covered work material governed by the terms
394 of that license document, provided that the further restriction does
395 not survive such relicensing or conveying.
396
397 If you add terms to a covered work in accord with this section, you
398 must place, in the relevant source files, a statement of the
399 additional terms that apply to those files, or a notice indicating
400 where to find the applicable terms.
401
402 Additional terms, permissive or non-permissive, may be stated in the
403 form of a separately written license, or stated as exceptions;
404 the above requirements apply either way.
405
406 8. Termination.
407
408 You may not propagate or modify a covered work except as expressly
409 provided under this License. Any attempt otherwise to propagate or
410 modify it is void, and will automatically terminate your rights under
411 this License (including any patent licenses granted under the third
412 paragraph of section 11).
413
414 However, if you cease all violation of this License, then your
415 license from a particular copyright holder is reinstated (a)
416 provisionally, unless and until the copyright holder explicitly and
417 finally terminates your license, and (b) permanently, if the copyright
418 holder fails to notify you of the violation by some reasonable means
419 prior to 60 days after the cessation.
420
421 Moreover, your license from a particular copyright holder is
422 reinstated permanently if the copyright holder notifies you of the
423 violation by some reasonable means, this is the first time you have
424 received notice of violation of this License (for any work) from that
425 copyright holder, and you cure the violation prior to 30 days after
426 your receipt of the notice.
427
428 Termination of your rights under this section does not terminate the
429 licenses of parties who have received copies or rights from you under
430 this License. If your rights have been terminated and not permanently
431 reinstated, you do not qualify to receive new licenses for the same
432 material under section 10.
433
434 9. Acceptance Not Required for Having Copies.
435
436 You are not required to accept this License in order to receive or
437 run a copy of the Program. Ancillary propagation of a covered work
438 occurring solely as a consequence of using peer-to-peer transmission
439 to receive a copy likewise does not require acceptance. However,
440 nothing other than this License grants you permission to propagate or
441 modify any covered work. These actions infringe copyright if you do
442 not accept this License. Therefore, by modifying or propagating a
443 covered work, you indicate your acceptance of this License to do so.
444
445 10. Automatic Licensing of Downstream Recipients.
446
447 Each time you convey a covered work, the recipient automatically
448 receives a license from the original licensors, to run, modify and
449 propagate that work, subject to this License. You are not responsible
450 for enforcing compliance by third parties with this License.
451
452 An "entity transaction" is a transaction transferring control of an
453 organization, or substantially all assets of one, or subdividing an
454 organization, or merging organizations. If propagation of a covered
455 work results from an entity transaction, each party to that
456 transaction who receives a copy of the work also receives whatever
457 licenses to the work the party's predecessor in interest had or could
458 give under the previous paragraph, plus a right to possession of the
459 Corresponding Source of the work from the predecessor in interest, if
460 the predecessor has it or can get it with reasonable efforts.
461
462 You may not impose any further restrictions on the exercise of the
463 rights granted or affirmed under this License. For example, you may
464 not impose a license fee, royalty, or other charge for exercise of
465 rights granted under this License, and you may not initiate litigation
466 (including a cross-claim or counterclaim in a lawsuit) alleging that
467 any patent claim is infringed by making, using, selling, offering for
468 sale, or importing the Program or any portion of it.
469
470 11. Patents.
471
472 A "contributor" is a copyright holder who authorizes use under this
473 License of the Program or a work on which the Program is based. The
474 work thus licensed is called the contributor's "contributor version".
475
476 A contributor's "essential patent claims" are all patent claims
477 owned or controlled by the contributor, whether already acquired or
478 hereafter acquired, that would be infringed by some manner, permitted
479 by this License, of making, using, or selling its contributor version,
480 but do not include claims that would be infringed only as a
481 consequence of further modification of the contributor version. For
482 purposes of this definition, "control" includes the right to grant
483 patent sublicenses in a manner consistent with the requirements of
484 this License.
485
486 Each contributor grants you a non-exclusive, worldwide, royalty-free
487 patent license under the contributor's essential patent claims, to
488 make, use, sell, offer for sale, import and otherwise run, modify and
489 propagate the contents of its contributor version.
490
491 In the following three paragraphs, a "patent license" is any express
492 agreement or commitment, however denominated, not to enforce a patent
493 (such as an express permission to practice a patent or covenant not to
494 sue for patent infringement). To "grant" such a patent license to a
495 party means to make such an agreement or commitment not to enforce a
496 patent against the party.
497
498 If you convey a covered work, knowingly relying on a patent license,
499 and the Corresponding Source of the work is not available for anyone
500 to copy, free of charge and under the terms of this License, through a
501 publicly available network server or other readily accessible means,
502 then you must either (1) cause the Corresponding Source to be so
503 available, or (2) arrange to deprive yourself of the benefit of the
504 patent license for this particular work, or (3) arrange, in a manner
505 consistent with the requirements of this License, to extend the patent
506 license to downstream recipients. "Knowingly relying" means you have
507 actual knowledge that, but for the patent license, your conveying the
508 covered work in a country, or your recipient's use of the covered work
509 in a country, would infringe one or more identifiable patents in that
510 country that you have reason to believe are valid.
511
512 If, pursuant to or in connection with a single transaction or
513 arrangement, you convey, or propagate by procuring conveyance of, a
514 covered work, and grant a patent license to some of the parties
515 receiving the covered work authorizing them to use, propagate, modify
516 or convey a specific copy of the covered work, then the patent license
517 you grant is automatically extended to all recipients of the covered
518 work and works based on it.
519
520 A patent license is "discriminatory" if it does not include within
521 the scope of its coverage, prohibits the exercise of, or is
522 conditioned on the non-exercise of one or more of the rights that are
523 specifically granted under this License. You may not convey a covered
524 work if you are a party to an arrangement with a third party that is
525 in the business of distributing software, under which you make payment
526 to the third party based on the extent of your activity of conveying
527 the work, and under which the third party grants, to any of the
528 parties who would receive the covered work from you, a discriminatory
529 patent license (a) in connection with copies of the covered work
530 conveyed by you (or copies made from those copies), or (b) primarily
531 for and in connection with specific products or compilations that
532 contain the covered work, unless you entered into that arrangement,
533 or that patent license was granted, prior to 28 March 2007.
534
535 Nothing in this License shall be construed as excluding or limiting
536 any implied license or other defenses to infringement that may
537 otherwise be available to you under applicable patent law.
538
539 12. No Surrender of Others' Freedom.
540
541 If conditions are imposed on you (whether by court order, agreement or
542 otherwise) that contradict the conditions of this License, they do not
543 excuse you from the conditions of this License. If you cannot convey a
544 covered work so as to satisfy simultaneously your obligations under this
545 License and any other pertinent obligations, then as a consequence you may
546 not convey it at all. For example, if you agree to terms that obligate you
547 to collect a royalty for further conveying from those to whom you convey
548 the Program, the only way you could satisfy both those terms and this
549 License would be to refrain entirely from conveying the Program.
550
551 13. Use with the GNU Affero General Public License.
552
553 Notwithstanding any other provision of this License, you have
554 permission to link or combine any covered work with a work licensed
555 under version 3 of the GNU Affero General Public License into a single
556 combined work, and to convey the resulting work. The terms of this
557 License will continue to apply to the part which is the covered work,
558 but the special requirements of the GNU Affero General Public License,
559 section 13, concerning interaction through a network will apply to the
560 combination as such.
561
562 14. Revised Versions of this License.
563
564 The Free Software Foundation may publish revised and/or new versions of
565 the GNU General Public License from time to time. Such new versions will
566 be similar in spirit to the present version, but may differ in detail to
567 address new problems or concerns.
568
569 Each version is given a distinguishing version number. If the
570 Program specifies that a certain numbered version of the GNU General
571 Public License "or any later version" applies to it, you have the
572 option of following the terms and conditions either of that numbered
573 version or of any later version published by the Free Software
574 Foundation. If the Program does not specify a version number of the
575 GNU General Public License, you may choose any version ever published
576 by the Free Software Foundation.
577
578 If the Program specifies that a proxy can decide which future
579 versions of the GNU General Public License can be used, that proxy's
580 public statement of acceptance of a version permanently authorizes you
581 to choose that version for the Program.
582
583 Later license versions may give you additional or different
584 permissions. However, no additional obligations are imposed on any
585 author or copyright holder as a result of your choosing to follow a
586 later version.
587
588 15. Disclaimer of Warranty.
589
590 THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
591 APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
592 HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
593 OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
594 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
595 PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
596 IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
597 ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
598
599 16. Limitation of Liability.
600
601 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
602 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
603 THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
604 GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
605 USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
606 DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
607 PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
608 EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
609 SUCH DAMAGES.
610
611 17. Interpretation of Sections 15 and 16.
612
613 If the disclaimer of warranty and limitation of liability provided
614 above cannot be given local legal effect according to their terms,
615 reviewing courts shall apply local law that most closely approximates
616 an absolute waiver of all civil liability in connection with the
617 Program, unless a warranty or assumption of liability accompanies a
618 copy of the Program in return for a fee.
619
620 END OF TERMS AND CONDITIONS
621
622 How to Apply These Terms to Your New Programs
623
624 If you develop a new program, and you want it to be of the greatest
625 possible use to the public, the best way to achieve this is to make it
626 free software which everyone can redistribute and change under these terms.
627
628 To do so, attach the following notices to the program. It is safest
629 to attach them to the start of each source file to most effectively
630 state the exclusion of warranty; and each file should have at least
631 the "copyright" line and a pointer to where the full notice is found.
632
633 <one line to give the program's name and a brief idea of what it does.>
634 Copyright (C) <year> <name of author>
635
636 This program is free software: you can redistribute it and/or modify
637 it under the terms of the GNU General Public License as published by
638 the Free Software Foundation, either version 3 of the License, or
639 (at your option) any later version.
640
641 This program is distributed in the hope that it will be useful,
642 but WITHOUT ANY WARRANTY; without even the implied warranty of
643 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
644 GNU General Public License for more details.
645
646 You should have received a copy of the GNU General Public License
647 along with this program. If not, see <http://www.gnu.org/licenses/>.
648
649 Also add information on how to contact you by electronic and paper mail.
650
651 If the program does terminal interaction, make it output a short
652 notice like this when it starts in an interactive mode:
653
654 <program> Copyright (C) <year> <name of author>
655 This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
656 This is free software, and you are welcome to redistribute it
657 under certain conditions; type `show c' for details.
658
659 The hypothetical commands `show w' and `show c' should show the appropriate
660 parts of the General Public License. Of course, your program's commands
661 might be different; for a GUI interface, you would use an "about box".
662
663 You should also get your employer (if you work as a programmer) or school,
664 if any, to sign a "copyright disclaimer" for the program, if necessary.
665 For more information on this, and how to apply and follow the GNU GPL, see
666 <http://www.gnu.org/licenses/>.
667
668 The GNU General Public License does not permit incorporating your program
669 into proprietary programs. If your program is a subroutine library, you
670 may consider it more useful to permit linking proprietary applications with
671 the library. If this is what you want to do, use the GNU Lesser General
672 Public License instead of this License. But first, please read
673 <http://www.gnu.org/philosophy/why-not-lgpl.html>.
0 #ifndef ASSERTS_HPP_INCLUDED
1 #define ASSERTS_HPP_INCLUDED
2
3 #include <iostream>
4 #include <stdlib.h>
5
6 //various asserts of standard "equality" tests, such as "equals", "not equals", "greater than", etc. Example usage:
7 //ASSERT_NE(x, y);
8 #define ASSERT_EQ(a,b) if((a) != (b)) { std::cerr << __FILE__ << ":" << __LINE__ << " ASSERT EQ FAILED: " << #a << " != " << #b << ": " << (a) << " != " << (b) << "\n"; abort(); }
9
10 #define ASSERT_NE(a,b) if((a) == (b)) { std::cerr << __FILE__ << ":" << __LINE__ << " ASSERT NE FAILED: " << #a << " == " << #b << ": " << (a) << " == " << (b) << "\n"; abort(); }
11
12 #define ASSERT_GE(a,b) if((a) < (b)) { std::cerr << __FILE__ << ":" << __LINE__ << " ASSERT GE FAILED: " << #a << " < " << #b << ": " << (a) << " < " << (b) << "\n"; abort(); }
13
14 #define ASSERT_LE(a,b) if((a) > (b)) { std::cerr << __FILE__ << ":" << __LINE__ << " ASSERT LE FAILED: " << #a << " > " << #b << ": " << (a) << " > " << (b) << "\n"; abort(); }
15
16 #define ASSERT_GT(a,b) if((a) <= (b)) { std::cerr << __FILE__ << ":" << __LINE__ << " ASSERT GT FAILED: " << #a << " <= " << #b << ": " << (a) << " <= " << (b) << "\n"; abort(); }
17
18 #define ASSERT_LT(a,b) if((a) >= (b)) { std::cerr << __FILE__ << ":" << __LINE__ << " ASSERT LT FAILED: " << #a << " >= " << #b << ": " << (a) << " >= " << (b) << "\n"; abort(); }
19
20 #define ASSERT_INDEX_INTO_VECTOR(a,b) if((a) < 0 || (a) >= (b).size()) { std::cerr << __FILE__ << ":" << __LINE__ << " ASSERT INDEX INTO VECTOR FAILED: " << #a << " indexes " << #b << ": " << (a) << " indexes " << (b).size(); abort(); }
21
22 //for custom logging. Example usage:
23 //ASSERT_LOG(x != y, "x not equal to y. Value of x: " << x << ", y: " << y);
24 #define ASSERT_LOG(a,b) if( !(a) ) { std::cerr << __FILE__ << ":" << __LINE__ << " ASSERTION FAILED: " << b << "\n"; abort(); }
25
26
27 #endif
0 #include <SDL.h>
1 #ifndef SDL_VIDEO_OPENGL_ES
2 #include <GL/glew.h>
3 #endif
4
5 #include <math.h>
6
7 #include <iostream>
8 #include <map>
9
10 #include "background.hpp"
11 #include "color_utils.hpp"
12 #include "filesystem.hpp"
13 #include "foreach.hpp"
14 #include "formatter.hpp"
15 #include "level.hpp"
16 #include "raster.hpp"
17 #include "surface_palette.hpp"
18 #include "wml_node.hpp"
19 #include "wml_parser.hpp"
20 #include "wml_utils.hpp"
21
22 namespace {
23 //a cache key with background name and palette ID.
24 typedef std::pair<std::string, int> cache_key;
25 std::map<cache_key, boost::shared_ptr<background> > cache;
26 }
27
28 boost::shared_ptr<background> background::get(const std::string& name, int palette_id)
29 {
30 const cache_key id(name, palette_id);
31
32 boost::shared_ptr<background>& obj = cache[id];
33 if(!obj) {
34 obj.reset(new background(wml::parse_wml_from_file("data/backgrounds/" + name + ".cfg"), palette_id));
35 obj->id_ = name;
36 }
37
38 return obj;
39 }
40
41 std::vector<std::string> background::get_available_backgrounds()
42 {
43 std::vector<std::string> files;
44 sys::get_files_in_dir("data/backgrounds/", &files);
45
46 std::vector<std::string> result;
47 foreach(const std::string& fname, files) {
48 if(fname.size() > 4 && std::equal(fname.end() - 4, fname.end(), ".cfg")) {
49 result.push_back(std::string(fname.begin(), fname.end() - 4));
50 }
51 }
52
53 return result;
54 }
55
56 background::background(const wml::const_node_ptr& node, int palette) : palette_(palette)
57 {
58 top_ = string_to_color(node->attr("top"));
59 bot_ = string_to_color(node->attr("bottom"));
60
61 if(palette_ != -1) {
62 top_ = graphics::map_palette(top_, palette);
63 bot_ = graphics::map_palette(bot_, palette);
64 }
65
66 width_ = wml::get_int(node, "width");
67 height_ = wml::get_int(node, "height");
68
69 FOREACH_WML_CHILD(layer_node, node, "layer") {
70 layer bg;
71 bg.image = (*layer_node)["image"];
72 bg.image_formula = layer_node->attr("image_formula");
73 bg.xscale = wml::get_int(layer_node, "xscale", 100);
74 bg.yscale = wml::get_int(layer_node, "yscale", 100);
75 bg.xspeed = wml::get_int(layer_node, "xspeed", 0);
76 bg.xpad = wml::get_int(layer_node, "xpad", 0);
77 bg.xoffset = wml::get_int(layer_node, "xoffset", 0);
78 bg.yoffset = wml::get_int(layer_node, "yoffset", 0);
79 bg.scale = wml::get_int(layer_node, "scale", 1);
80 bg.blend = wml::get_bool(layer_node, "blend", true);
81 if(bg.scale < 1) {
82 bg.scale = 1;
83 }
84
85 #ifndef SDL_VIDEO_OPENGL_ES
86 std::string blend_mode = layer_node->attr("mode");
87 if(GLEW_EXT_blend_minmax) {
88 if(blend_mode == "GL_MAX") {
89 bg.mode = GL_MAX;
90 } else if(blend_mode == "GL_MIN") {
91 bg.mode = GL_MIN;
92 } else {
93 bg.mode = GL_FUNC_ADD;
94 }
95 }
96 #endif
97
98 std::fill(bg.color, bg.color + 4, 0.0);
99 bg.color[0] = wml::get_attr<GLfloat>(layer_node, "red", 1.0);
100 bg.color[1] = wml::get_attr<GLfloat>(layer_node, "green", 1.0);
101 bg.color[2] = wml::get_attr<GLfloat>(layer_node, "blue", 1.0);
102 bg.color[3] = wml::get_attr<GLfloat>(layer_node, "alpha", 1.0);
103
104 if(layer_node->has_attr("color_above")) {
105 bg.color_above.reset(new SDL_Color);
106 *bg.color_above = string_to_color(layer_node->attr("color_above"));
107 if(palette_ != -1) {
108 *bg.color_above = graphics::map_palette(*bg.color_above, palette);
109 }
110 }
111
112 if(layer_node->has_attr("color_below")) {
113 bg.color_below.reset(new SDL_Color);
114 *bg.color_below = string_to_color(layer_node->attr("color_below"));
115 if(palette_ != -1) {
116 *bg.color_below = graphics::map_palette(*bg.color_below, palette);
117 }
118 }
119
120 bg.y1 = wml::get_attr<int>(layer_node, "y1");
121 bg.y2 = wml::get_attr<int>(layer_node, "y2");
122
123 bg.foreground = wml::get_bool(layer_node, "foreground", false);
124 layers_.push_back(bg);
125 }
126 }
127
128 wml::node_ptr background::write() const
129 {
130 wml::node_ptr res(new wml::node("background"));
131 char buf[128];
132 sprintf(buf, "%02x%02x%02x", top_.r, top_.g, top_.b);
133 res->set_attr("top", buf);
134 sprintf(buf, "%02x%02x%02x", bot_.r, bot_.g, bot_.b);
135 res->set_attr("bottom", buf);
136 res->set_attr("width", formatter() << width_);
137 res->set_attr("height", formatter() << height_);
138
139 foreach(const layer& bg, layers_) {
140 wml::node_ptr node(new wml::node("layer"));
141 node->set_attr("image", bg.image);
142 node->set_attr("xscale", formatter() << bg.xscale);
143 node->set_attr("yscale", formatter() << bg.yscale);
144 node->set_attr("xspeed", formatter() << bg.xspeed);
145 node->set_attr("xpad", formatter() << bg.xpad);
146 node->set_attr("xoffset", formatter() << bg.xoffset);
147 node->set_attr("yoffset", formatter() << bg.yoffset);
148 node->set_attr("y1", formatter() << bg.y1);
149 node->set_attr("y2", formatter() << bg.y2);
150 node->set_attr("scale", formatter() << bg.scale);
151 node->set_attr("red", formatter() << bg.color[0]);
152 node->set_attr("green", formatter() << bg.color[1]);
153 node->set_attr("blue", formatter() << bg.color[2]);
154 node->set_attr("alpha", formatter() << bg.color[3]);
155
156 if(bg.color_above) {
157 sprintf(buf, "%02x%02x%02x", bg.color_above->r, bg.color_above->g, bg.color_above->b);
158 node->set_attr("color_above", buf);
159 }
160
161 if(bg.color_below) {
162 sprintf(buf, "%02x%02x%02x", bg.color_below->r, bg.color_below->g, bg.color_below->b);
163 node->set_attr("color_below", buf);
164 }
165 if(bg.foreground) {
166 node->set_attr("foreground", "true");
167 }
168
169 res->add_child(node);
170 }
171 return res;
172 }
173
174 void background::draw(int x, int y, const rect& area, const std::vector<rect>& opaque_areas, int rotation, int cycle) const
175 {
176 const int height = height_ + offset_.y*2;
177
178 //set the background colors for the level. The area above 'height' is
179 //painted with the top color, and the area below height is painted with
180 //the bottom color. For efficiency we do this using color clearing, with
181 //scissors to divide the screen into top and bottom.
182 if(height < y) {
183 //the entire screen is full of the bottom color
184 glClearColor(bot_.r/255.0, bot_.g/255.0, bot_.b/255.0, 0.0);
185 glClear(GL_COLOR_BUFFER_BIT);
186 } else if(height > y + graphics::screen_height()) {
187 //the entire screen is full of the top color.
188 glClearColor(top_.r/255.0, top_.g/255.0, top_.b/255.0, 0.0);
189 glClear(GL_COLOR_BUFFER_BIT);
190 } else {
191 //both bottom and top colors are on the screen, so draw them both,
192 //using scissors to delinate their areas.
193 const int dist_from_bottom = y + graphics::screen_height() - height;
194
195 glEnable(GL_SCISSOR_TEST);
196
197 //the scissor test does not respect any rotations etc. We use a rotation
198 //to transform the iPhone's display, which is fine normally, but
199 //here we have to accomodate the iPhone being "on its side"
200 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
201 glScissor(dist_from_bottom/2, 0, (graphics::screen_height() - dist_from_bottom)/2, graphics::screen_width()/2);
202 #else
203 glScissor(0, dist_from_bottom, graphics::screen_width(), graphics::screen_height() - dist_from_bottom);
204 #endif
205 glClearColor(top_.r/255.0, top_.g/255.0, top_.b/255.0, 0.0);
206 glClear(GL_COLOR_BUFFER_BIT);
207
208 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
209 glScissor(0, 0, dist_from_bottom/2, graphics::screen_width()/2);
210 #else
211 glScissor(0, 0, graphics::screen_width(), dist_from_bottom);
212 #endif
213 glClearColor(bot_.r/255.0, bot_.g/255.0, bot_.b/255.0, 0.0);
214 glClear(GL_COLOR_BUFFER_BIT);
215
216 glDisable(GL_SCISSOR_TEST);
217 }
218
219 draw_layers(x, y, area, opaque_areas, rotation, cycle);
220 }
221
222 namespace {
223 graphics::blit_queue blit_queue;
224
225 void calculate_draw_areas(rect area, std::vector<rect>::const_iterator opaque1, std::vector<rect>::const_iterator opaque2, std::vector<rect>* areas) {
226 if(opaque1 == opaque2) {
227 areas->push_back(area);
228 return;
229 }
230
231 rect sub_areas[4];
232 for(; opaque1 != opaque2; ++opaque1) {
233 const int result = rect_difference(area, *opaque1, sub_areas);
234 if(result == -1) {
235 continue;
236 }
237
238 if(result != 1) {
239 for(int n = 0; n < result; ++n) {
240
241 calculate_draw_areas(sub_areas[n], opaque1+1, opaque2, areas);
242 }
243
244 return;
245 }
246
247 area = sub_areas[0];
248 }
249
250 areas->push_back(area);
251 }
252
253 }
254
255 void background::draw_layers(int x, int y, const rect& area_ref, const std::vector<rect>& opaque_areas, int rotation, int cycle) const
256 {
257 static std::vector<rect> areas;
258 areas.clear();
259 calculate_draw_areas(area_ref, opaque_areas.begin(), opaque_areas.end(), &areas);
260
261 for(std::vector<layer>::const_iterator i = layers_.begin(); i != layers_.end(); ++i) {
262 const layer& bg = *i;
263 if(bg.foreground == false) {
264
265 for(std::vector<rect>::const_iterator a = areas.begin(); a != areas.end(); ++a) {
266 draw_layer(x, y, *a, rotation, bg, cycle);
267 }
268
269 if(!blit_queue.empty() && (i+1 == layers_.end() || i->texture != (i+1)->texture || (i+1)->foreground || i->blend != (i+1)->blend)) {
270 if(bg.blend == false) {
271 glDisable(GL_BLEND);
272 }
273 blit_queue.set_texture(bg.texture.get_id());
274 blit_queue.do_blit();
275 blit_queue.clear();
276 if(bg.blend == false) {
277 glEnable(GL_BLEND);
278 }
279 }
280
281 }
282 }
283 }
284
285 void background::draw_foreground(double xpos, double ypos, int rotation, int cycle) const
286 {
287 foreach(const layer& bg, layers_) {
288 if(bg.foreground) {
289 draw_layer(xpos, ypos, rect(xpos, ypos, graphics::screen_width(), graphics::screen_height()), rotation, bg, cycle);
290 if(!blit_queue.empty()) {
291 blit_queue.set_texture(bg.texture.get_id());
292 blit_queue.do_blit();
293 blit_queue.clear();
294 }
295 }
296 }
297 }
298
299 void background::set_offset(const point& offset)
300 {
301 offset_ = offset;
302 }
303
304 void background::draw_layer(int x, int y, const rect& area, int rotation, const background::layer& bg, int cycle) const
305 {
306 const double ScaleImage = 2.0;
307 GLshort y1 = y + (bg.yoffset+offset_.y)*ScaleImage - (y*bg.yscale)/100;
308 GLshort y2 = y1 + (bg.y2 - bg.y1)*ScaleImage;
309
310 if(y2 <= y || y2 <= area.y()) {
311 return;
312 }
313
314 if(y1 > area.y2()) {
315 return;
316 }
317
318 if(!bg.texture.valid()) {
319 if(palette_ == -1) {
320 bg.texture = graphics::texture::get(bg.image, bg.image_formula);
321 } else {
322 bg.texture = graphics::texture::get_palette_mapped(bg.image, palette_);
323 }
324
325 if(bg.y2 == 0) {
326 bg.y2 = bg.texture.height();
327 }
328 }
329
330 if(!bg.texture.valid()) {
331 return;
332 }
333
334 ASSERT_GT(bg.texture.height(), 0);
335 ASSERT_GT(bg.texture.width(), 0);
336
337 GLfloat v1 = bg.texture.translate_coord_y(double(bg.y1)/double(bg.texture.height()));
338 GLfloat v2 = bg.texture.translate_coord_y(double(bg.y2)/double(bg.texture.height()));
339
340 if(y1 < area.y()) {
341 v1 += (GLfloat(area.y() - y1)/GLfloat(y2 - y1))*(v2 - v1);
342 y1 = area.y();
343 }
344
345 if(bg.color_above && y1 > area.y()) {
346 glEnable(GL_SCISSOR_TEST);
347
348 const int xpos = area.x() - x;
349 const int ypos = y1 - y;
350 const int width = area.w();
351 const int height = y1 - area.y();
352 #if TARGET_OS_IPHONE
353 glScissor((graphics::screen_height() - ypos)/2, (graphics::screen_width() - (xpos + width))/2, height/2, width/2);
354 #else
355 glScissor(xpos, graphics::screen_height() - ypos, width, height);
356 #endif
357 glClearColor(bg.color_above->r, bg.color_above->g, bg.color_above->b, 0.0);
358 glClear(GL_COLOR_BUFFER_BIT);
359 glDisable(GL_SCISSOR_TEST);
360 }
361
362 if(bg.color_below && y2 < area.y() + area.h()) {
363 glEnable(GL_SCISSOR_TEST);
364
365 const int xpos = area.x() - x;
366 const int ypos = area.y() + area.h() - y;
367 const int width = area.w();
368 const int height = area.y() + area.h() - y2;
369
370 #if TARGET_OS_IPHONE
371 glScissor((graphics::screen_height() - ypos)/2, (graphics::screen_width() - (xpos + width))/2, height/2, width/2);
372 #else
373 glScissor(xpos, graphics::screen_height() - ypos, width, height);
374 #endif
375
376 glClearColor(bg.color_below->r, bg.color_below->g, bg.color_below->b, 0.0);
377 glClear(GL_COLOR_BUFFER_BIT);
378 glDisable(GL_SCISSOR_TEST);
379 }
380
381 if(y2 > area.y() + area.h()) {
382 v2 -= (GLfloat(y2 - (area.y() + area.h()))/GLfloat(y2 - y1))*(v2 - v1);
383 y2 = area.y() + area.h();
384 }
385
386 if(v1 > v2) {
387 return;
388 }
389
390 int screen_width = area.w();
391
392 const double xscale = double(bg.xscale)/100.0;
393 GLfloat xpos = (-GLfloat(bg.xspeed)*GLfloat(cycle)/1000 + int(GLfloat(x + bg.xoffset)*xscale))/GLfloat((bg.texture.width()+bg.xpad)*ScaleImage) + GLfloat(area.x() - x)/GLfloat((bg.texture.width()+bg.xpad)*ScaleImage);
394
395 //clamp xpos into the [0.0, 1.0] range
396 if(xpos > 0) {
397 xpos -= floor(xpos);
398 } else {
399 while(xpos < 0) { xpos += 1.0; }
400 //xpos += ceil(-xpos);
401 }
402
403 if(bg.xpad > 0) {
404 xpos *= GLfloat(bg.texture.width() + bg.xpad)/GLfloat(bg.texture.width());
405 }
406
407 glColor4f(bg.color[0], bg.color[1], bg.color[2], bg.color[3]);
408
409 #ifndef SDL_VIDEO_OPENGL_ES
410 if (GLEW_EXT_blend_minmax && (GLEW_ARB_imaging || GLEW_VERSION_1_4)) {
411 glBlendEquation(bg.mode);
412 }
413 #endif
414
415 x = area.x();
416 y = area.y();
417
418 while(screen_width > 0) {
419 const int texture_blit_width = (1.0 - xpos)*bg.texture.width()*ScaleImage;
420
421 const int blit_width = std::min(texture_blit_width, screen_width);
422
423 if(blit_width > 0) {
424 const GLfloat xpos2 = xpos + GLfloat(blit_width)/(GLfloat(bg.texture.width())*2.0);
425
426 const GLshort x1 = x;
427 const GLshort x2 = x1 + blit_width;
428
429 const GLfloat u1 = bg.texture.translate_coord_x(xpos);
430 const GLfloat u2 = bg.texture.translate_coord_x(xpos2);
431
432 blit_queue.repeat_last();
433 blit_queue.add(x1, y1, u1, v1);
434 blit_queue.repeat_last();
435 blit_queue.add(x2, y1, u2, v1);
436 blit_queue.add(x1, y2, u1, v2);
437 blit_queue.add(x2, y2, u2, v2);
438 }
439
440 x += blit_width + bg.xpad*ScaleImage;
441
442 xpos = 0.0;
443 screen_width -= blit_width + bg.xpad*ScaleImage;
444 }
445
446 glColor4f(1.0,1.0,1.0,1.0);
447 #ifndef SDL_VIDEO_OPENGL_ES
448 if (GLEW_EXT_blend_minmax && (GLEW_ARB_imaging || GLEW_VERSION_1_4)) {
449 glBlendEquation(GL_FUNC_ADD);
450 }
451 #endif
452 }
453
0 #ifndef BACKGROUND_HPP_INCLUDED
1 #define BACKGROUND_HPP_INCLUDED
2
3 #include <string>
4 #include <vector>
5
6 #include "boost/shared_ptr.hpp"
7
8 #include "SDL.h"
9
10 #include "geometry.hpp"
11 #include "texture.hpp"
12 #include "wml_node_fwd.hpp"
13
14 class level;
15
16 //class which represents the background to a level.
17 class background
18 {
19 public:
20 //gets a background associated with a given ID.
21 static boost::shared_ptr<background> get(const std::string& id, int palette_id);
22
23 //all available backgrounds.
24 static std::vector<std::string> get_available_backgrounds();
25
26 background(const wml::const_node_ptr& node, int palette);
27 const std::string& id() const { return id_; }
28 wml::node_ptr write() const;
29 void draw(int x, int y, const rect& area, const std::vector<rect>& opaque_areas, int rotation, int cycle) const;
30 void draw_foreground(double x, double y, int rotation, int cycle) const;
31
32 void set_offset(const point& offset);
33 private:
34
35 void draw_layers(int x, int y, const rect& area, const std::vector<rect>& opaque_areas, int rotation, int cycle) const;
36 std::string id_;
37 SDL_Color top_, bot_;
38 int width_, height_;
39 point offset_;
40
41 struct layer {
42 std::string image;
43 std::string image_formula;
44 mutable graphics::texture texture;
45 int xscale, yscale; //scales are how quickly the background scrolls compared to normal ground movement when the player
46 //walks around. They give us the illusion of 'depth'. 100 is normal ground, less=distant, more=closer
47
48 int xspeed; //speed is how fast (in millipixels/cycle) the bg moves on its own. It's for drifting clounds/rivers.
49 int xpad; //amount of empty space padding we put between
50 int scale; //a multiplier on the dimensions of the image. Usually unused.
51 int xoffset;
52 int yoffset;
53 GLfloat color[4];
54
55 boost::shared_ptr<SDL_Color> color_above, color_below;
56
57 GLenum mode; //Do we use the regular 'GL_FUNC_ADD' blend mode, or do we do something special? Examples:
58 //GL_MAX -> Max(src,dest) pixels, displays whichever's brighter. Useful for clouds.
59 //GL_MIN -> vice-versa, useful for spooky mist.
60
61 // Top and bottom edges of the background.
62 mutable int y1, y2;
63
64 //if true, this layer is actually drawn in the foreground.
65 bool foreground;
66
67 //if false we can disable blending while this is drawn
68 bool blend;
69 };
70
71 void draw_layer(int x, int y, const rect& area, int rotation, const layer& bg, int cycle) const;
72
73 std::vector<layer> layers_;
74 int palette_;
75 };
76
77 #endif
0 #include <GL/gl.h>
1
2 #include "blur.hpp"
3 #include "foreach.hpp"
4 #include "frame.hpp"
5
6 blur_info::blur_info(double alpha, double fade, int granularity)
7 : alpha_(alpha), fade_(fade), granularity_(granularity)
8 {
9 }
10
11 void blur_info::copy_settings(const blur_info& o)
12 {
13 alpha_ = o.alpha_;
14 fade_ = o.fade_;
15 granularity_ = o.granularity_;
16 }
17
18 void blur_info::next_frame(int start_x, int start_y, int end_x, int end_y,
19 const frame* object_frame, int time_in_frame, bool facing,
20 bool upside_down, float rotate) {
21 foreach(blur_frame& f, frames_) {
22 f.fade -= fade_;
23 }
24
25 while(!frames_.empty() && frames_.front().fade <= 0.0) {
26 frames_.pop_front();
27 }
28
29 for(int n = 0; n < granularity_; ++n) {
30 blur_frame f;
31 f.object_frame = object_frame;
32 f.x = (start_x*n + end_x*(granularity_ - n))/granularity_;
33 f.y = (start_y*n + end_y*(granularity_ - n))/granularity_;
34 f.time_in_frame = time_in_frame;
35 f.facing = facing;
36 f.upside_down = upside_down;
37 f.rotate = rotate;
38 f.fade = alpha_ + (fade_*(granularity_ - n))/granularity_;
39 frames_.push_back(f);
40 }
41 }
42
43 void blur_info::draw() const
44 {
45 GLfloat color[4];
46 glGetFloatv(GL_CURRENT_COLOR, color);
47 foreach(const blur_frame& f, frames_) {
48 glColor4f(color[0], color[1], color[2], color[3]*f.fade);
49 f.object_frame->draw(f.x, f.y, f.facing, f.upside_down, f.time_in_frame, f.rotate);
50 }
51
52 glColor4f(color[0], color[1], color[2], color[3]);
53 }
54
55 bool blur_info::destroyed() const
56 {
57 return granularity_ == 0 && frames_.empty();
58 }
0 #ifndef BLUR_HPP_INCLUDED
1 #define BLUR_HPP_INCLUDED
2
3 #include <deque>
4
5 class frame;
6
7 //class which represents the blur information for a single object.
8 //a blur contains three parameters:
9 // - alpha: the initial alpha value of the blurred vision of the object.
10 // - fade: the rate at which the alpha fades each frame
11 // - granularity: the number of copies of the object that are made
12 // every cycle.
13 class blur_info
14 {
15 public:
16 blur_info(double alpha, double fade, int granularity);
17
18 //function to copy settings into another blur_info instance. This will
19 //keep our blur_frames as they are, but copy in the alpha/fade/granularity
20 //settings and so change our blur behavior from then on.
21 void copy_settings(const blur_info& info);
22
23 //function to progress to the next frame. We are given starting and
24 //ending position of the object, along with its drawing settings.
25 //
26 //'granularity' copies of the object's image will be made, linearly
27 //interpolated between start_x,start_y and end_x,end_y.
28 void next_frame(int start_x, int start_y, int end_x, int end_y,
29 const frame* f, int time_in_frame, bool facing,
30 bool upside_down, float rotate);
31
32 void draw() const;
33
34 //returns true iff our granularity is now 0 and we have no blur_frames.
35 bool destroyed() const;
36
37 private:
38 struct blur_frame {
39 const frame* object_frame;
40 int time_in_frame;
41 double x, y;
42 bool facing, upside_down;
43 float rotate;
44 double fade;
45 };
46
47 double alpha_;
48 double fade_;
49 int granularity_;
50 std::deque<blur_frame> frames_;
51 };
52
53 #endif
0 #include <GL/gl.h>
1
2 #include "border_widget.hpp"
3 #include "raster.hpp"
4
5 namespace gui {
6
7 border_widget::border_widget(widget_ptr child, graphics::color col, int border_size)
8 : child_(child), color_(col), border_size_(border_size)
9 {
10 set_dim(child->width() + border_size*2, child->height() + border_size*2);
11 child_->set_loc(border_size, border_size);
12 }
13
14 void border_widget::set_color(const graphics::color& col)
15 {
16 color_ = col;
17 }
18
19 void border_widget::handle_draw() const
20 {
21 glPushMatrix();
22 graphics::draw_rect(rect(x(), y(), width(), height()), color_);
23 glTranslatef(x(), y(), 0.0);
24 child_->draw();
25 glPopMatrix();
26 }
27
28 bool border_widget::handle_event(const SDL_Event& event, bool claimed)
29 {
30 SDL_Event ev = event;
31 normalize_event(&ev);
32 return child_->process_event(ev, claimed);
33 }
34
35 }
0 #ifndef BORDER_WIDGET_HPP_INCLUDED
1 #define BORDER_WIDGET_HPP_INCLUDED
2
3 #include "color_utils.hpp"
4 #include "widget.hpp"
5
6 namespace gui {
7
8 //a widget which draws a border around another widget it holds as its child.
9 class border_widget : public widget
10 {
11 public:
12 border_widget(widget_ptr child, graphics::color col, int border_size=2);
13 void set_color(const graphics::color& col);
14 private:
15 void handle_draw() const;
16 bool handle_event(const SDL_Event& event, bool claimed);
17
18 widget_ptr child_;
19 graphics::color color_;
20 int border_size_;
21 };
22
23 }
24
25 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include "button.hpp"
13 #include "iphone_controls.hpp"
14 #include "raster.hpp"
15 #include "surface_cache.hpp"
16 #include "framed_gui_element.hpp"
17
18 namespace gui {
19
20 namespace {
21
22 int vpadding = 4;
23 int hpadding = 10;
24
25 }
26
27 button::button(widget_ptr label, boost::function<void ()> onclick)
28 : label_(label), onclick_(onclick),
29 normal_button_image_set_(framed_gui_element::get("regular_button")),
30 depressed_button_image_set_(framed_gui_element::get("regular_button_pressed")),
31 focus_button_image_set_(framed_gui_element::get("regular_button_focus")),
32 current_button_image_set_(normal_button_image_set_)
33
34 {
35 set_dim(label_->width()+hpadding*2,label_->height()+vpadding*2);
36 }
37
38 bool button::in_button(int xloc, int yloc) const
39 {
40 translate_mouse_coords(&xloc, &yloc);
41 return xloc > x() && xloc < x() + width() &&
42 yloc > y() && yloc < y() + height();
43 }
44
45 void button::handle_draw() const
46 {
47 label_->set_loc(x()+width()/2 - label_->width()/2,y()+height()/2 - label_->height()/2);
48 current_button_image_set_->blit(x(),y(),width(),height());
49 label_->draw();
50 }
51
52 bool button::handle_event(const SDL_Event& event, bool claimed)
53 {
54 if(claimed) {
55 current_button_image_set_ = normal_button_image_set_;
56 }
57
58 if(event.type == SDL_MOUSEMOTION) {
59 const SDL_MouseMotionEvent& e = event.motion;
60 if(current_button_image_set_ == depressed_button_image_set_) {
61 //pass
62 } else if(in_button(e.x,e.y)) {
63 current_button_image_set_ = focus_button_image_set_;
64 } else {
65 current_button_image_set_ = normal_button_image_set_;
66 }
67 } else if(event.type == SDL_MOUSEBUTTONDOWN) {
68 const SDL_MouseButtonEvent& e = event.button;
69 if(in_button(e.x,e.y)) {
70 current_button_image_set_ = depressed_button_image_set_;
71 }
72 } else if(event.type == SDL_MOUSEBUTTONUP) {
73 const SDL_MouseButtonEvent& e = event.button;
74 if(current_button_image_set_ == depressed_button_image_set_) {
75 if(in_button(e.x,e.y)) {
76 current_button_image_set_ = focus_button_image_set_;
77 onclick_();
78 claimed = true;
79 } else {
80 current_button_image_set_ = normal_button_image_set_;
81 }
82 }
83 }
84 return claimed;
85 }
86
87 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef BUTTON_HPP_INCLUDED
13 #define BUTTON_HPP_INCLUDED
14
15 #include <boost/function.hpp>
16
17 #include "texture.hpp"
18 #include "widget.hpp"
19 #include "framed_gui_element.hpp"
20
21
22 namespace gui {
23
24 //a button widget. Forwards to a given function whenever it is clicked.
25 class button : public widget
26 {
27 public:
28 button(widget_ptr label, boost::function<void ()> onclick);
29
30 private:
31 bool in_button(int x, int y) const;
32 void handle_draw() const;
33 bool handle_event(const SDL_Event& event, bool claimed);
34
35 widget_ptr label_;
36 boost::function<void ()> onclick_;
37
38 const_framed_gui_element_ptr normal_button_image_set_,depressed_button_image_set_,focus_button_image_set_,current_button_image_set_;
39 };
40
41 typedef boost::shared_ptr<button> button_ptr;
42
43 }
44
45 #endif
0 #include <boost/bind.hpp>
1
2 #include "border_widget.hpp"
3 #include "button.hpp"
4 #include "character_editor_dialog.hpp"
5 #include "editor.hpp"
6 #include "foreach.hpp"
7 #include "frame.hpp"
8 #include "grid_widget.hpp"
9 #include "image_widget.hpp"
10 #include "label.hpp"
11 #include "raster.hpp"
12
13 namespace editor_dialogs
14 {
15
16 character_editor_dialog::character_editor_dialog(editor& e)
17 : gui::dialog(graphics::screen_width() - 160, 160, 160, 440), editor_(e), first_index_(-1)
18 {
19 if(editor_.all_characters().empty() == false) {
20 category_ = editor_.all_characters().front().category;
21 }
22
23 init();
24 }
25
26 void character_editor_dialog::init()
27 {
28 clear();
29 using namespace gui;
30 set_padding(20);
31
32 const frame& frame = *editor_.all_characters()[editor_.get_object()].preview_frame;
33
34 button* category_button = new button(widget_ptr(new label(category_, graphics::color_white())), boost::bind(&character_editor_dialog::show_category_menu, this));
35 add_widget(widget_ptr(category_button), 10, 10);
36
37 grid_ptr grid(new gui::grid(3));
38 int index = 0;
39 first_index_ = -1;
40 foreach(const editor::enemy_type& c, editor_.all_characters()) {
41 if(c.category == category_) {
42 if(first_index_ == -1) {
43 first_index_ = index;
44 }
45
46 image_widget* preview = new image_widget(c.preview_frame->img());
47 preview->set_dim(40, 40);
48 preview->set_area(c.preview_frame->area());
49 button_ptr char_button(new button(widget_ptr(preview), boost::bind(&character_editor_dialog::set_character, this, index)));
50
51 std::string tooltip_str = c.node->attr("type");
52 const_editor_entity_info_ptr editor_info = c.preview_object->editor_info();
53 if(editor_info && !editor_info->help().empty()) {
54 tooltip_str += "\n" + editor_info->help();
55 }
56 char_button->set_tooltip(tooltip_str);
57 char_button->set_dim(44, 44);
58 grid->add_col(gui::widget_ptr(new gui::border_widget(char_button, index == editor_.get_object() ? graphics::color(255,255,255,255) : graphics::color(0,0,0,0))));
59 }
60
61 ++index;
62 }
63
64 grid->finish_row();
65 add_widget(grid);
66
67
68 button* facing_button = new button(
69 widget_ptr(new label(editor_.face_right() ? "right" : "left", graphics::color_white())),
70 boost::bind(&editor::toggle_facing, &editor_));
71 facing_button->set_tooltip("f Change Facing");
72 add_widget(widget_ptr(facing_button), category_button->x() + category_button->width() + 10, 10);
73 }
74
75 void character_editor_dialog::show_category_menu()
76 {
77 using namespace gui;
78 gui::grid* grid = new gui::grid(2);
79 grid->set_show_background(true);
80 grid->set_hpad(10);
81 grid->allow_selection();
82 grid->register_selection_callback(boost::bind(&character_editor_dialog::close_context_menu, this, _1));
83
84 std::set<std::string> categories;
85 foreach(const editor::enemy_type& c, editor_.all_characters()) {
86 if(categories.count(c.category)) {
87 continue;
88 }
89
90 categories.insert(c.category);
91
92 image_widget* preview = new image_widget(c.preview_frame->img());
93 preview->set_dim(28, 28);
94 preview->set_area(c.preview_frame->area());
95 grid->add_col(widget_ptr(preview))
96 .add_col(widget_ptr(new label(c.category, graphics::color_white())));
97 grid->register_row_selection_callback(boost::bind(&character_editor_dialog::select_category, this, c.category));
98 }
99
100 int mousex, mousey;
101 SDL_GetMouseState(&mousex, &mousey);
102 if(mousex + grid->width() > graphics::screen_width()) {
103 mousex = graphics::screen_width() - grid->width();
104 }
105
106 if(mousey + grid->height() > graphics::screen_height()) {
107 mousey = graphics::screen_height() - grid->height();
108 }
109
110 mousex -= x();
111 mousey -= y();
112
113 remove_widget(context_menu_);
114 context_menu_.reset(grid);
115 add_widget(context_menu_, mousex, mousey);
116 }
117
118 void character_editor_dialog::set_character(int index)
119 {
120 category_ = editor_.all_characters()[index].category;
121 editor_.set_object(index);
122 init();
123 }
124
125 void character_editor_dialog::close_context_menu(int index)
126 {
127 remove_widget(context_menu_);
128 context_menu_.reset();
129 }
130
131 void character_editor_dialog::select_category(const std::string& category)
132 {
133 category_ = category;
134 init();
135 if(first_index_ >= 0) {
136 set_character(first_index_);
137 }
138 }
139
140 }
0 #ifndef CHARACTER_EDITOR_DIALOG_HPP_INCLUDED
1 #define CHARACTER_EDITOR_DIALOG_HPP_INCLUDED
2
3 #include <string>
4
5 #include "dialog.hpp"
6 #include "widget.hpp"
7
8 class editor;
9
10 namespace editor_dialogs
11 {
12
13 //editor dialog which displays the details of an object and allows editing it.
14 class character_editor_dialog : public gui::dialog
15 {
16 public:
17 explicit character_editor_dialog(editor& e);
18 void init();
19 void set_character(int index);
20 void select_category(const std::string& str);
21 private:
22 void show_category_menu();
23
24 void close_context_menu(int index);
25 editor& editor_;
26 std::string category_;
27 gui::widget_ptr context_menu_;
28
29 int first_index_;
30 };
31
32 }
33
34 #endif
0 #include "asserts.hpp"
1 #include "collision_utils.hpp"
2 #include "foreach.hpp"
3 #include "geometry.hpp"
4 #include "level.hpp"
5 #include "object_events.hpp"
6
7 namespace {
8 std::map<std::string, int> solid_dimensions;
9 std::vector<std::string> solid_dimension_ids;
10 }
11
12 int get_num_solid_dimensions()
13 {
14 return solid_dimensions.size();
15 }
16
17 const std::string& get_solid_dimension_key(int id)
18 {
19 ASSERT_INDEX_INTO_VECTOR(id, solid_dimension_ids);
20 return solid_dimension_ids[id];
21 }
22
23 int get_solid_dimension_id(const std::string& key)
24 {
25 std::map<std::string, int>::const_iterator itor = solid_dimensions.find(key);
26 if(itor != solid_dimensions.end()) {
27 return itor->second;
28 }
29
30 solid_dimensions[key] = solid_dimension_ids.size();
31 solid_dimension_ids.push_back(key);
32 return solid_dimensions.size()-1;
33 }
34
35 bool point_standable(const level& lvl, const entity& e, int x, int y, collision_info* info, ALLOW_PLATFORM allow_platform)
36 {
37 if(allow_platform == SOLID_AND_PLATFORMS && lvl.standable(x, y, info ? &info->friction : NULL, info ? &info->traction : NULL, info ? &info->damage : NULL) ||
38 allow_platform != SOLID_AND_PLATFORMS && lvl.solid(x, y, info ? &info->friction : NULL, info ? &info->traction : NULL, info ? &info->damage : NULL)) {
39 if(info && !lvl.solid(x, y)) {
40 info->platform = true;
41 }
42 return true;
43 }
44
45 const point pt(x, y);
46
47 const std::vector<entity_ptr>& chars = lvl.get_solid_chars();
48
49 for(std::vector<entity_ptr>::const_iterator i = chars.begin();
50 i != chars.end(); ++i) {
51 const entity_ptr& obj = *i;
52 if(&e == obj.get() ||
53 (e.weak_solid_dimensions()&obj->solid_dimensions()) == 0 &&
54 (e.solid_dimensions()&obj->weak_solid_dimensions()) == 0) {
55 continue;
56 }
57
58 if(allow_platform == SOLID_AND_PLATFORMS) {
59 const rect& platform_rect = obj->platform_rect();
60 if(point_in_rect(pt, platform_rect)) {
61 if(info) {
62 info->collide_with = obj;
63 info->friction = obj->surface_friction();
64 info->traction = obj->surface_traction();
65 info->adjust_y = y - platform_rect.y();
66 info->platform = true;
67 }
68
69 return true;
70 }
71 }
72
73 if(!point_in_rect(pt, obj->solid_rect())) {
74 continue;
75 }
76
77 const frame& f = obj->current_frame();
78 const int xpos = obj->face_right() ? x - obj->x() : obj->x() + f.width() - x - 1;
79
80 const solid_info* solid = obj->solid();
81
82 if(solid && solid->solid_at(x - obj->x(), y - obj->y(), info ? &info->collide_with_area_id : NULL)) {
83 if(info) {
84 info->collide_with = obj;
85 info->friction = obj->surface_friction();
86 info->traction = obj->surface_traction();
87 }
88
89 return true;
90 }
91 }
92
93 return false;
94 }
95
96 bool entity_collides(level& lvl, const entity& e, MOVE_DIRECTION dir, collision_info* info)
97 {
98 if(!e.solid()) {
99 return false;
100 }
101
102 if(!e.allow_level_collisions() && entity_collides_with_level(lvl, e, dir, info)) {
103 return true;
104 }
105
106 const std::vector<entity_ptr>& solid_chars = lvl.get_solid_chars();
107 for(std::vector<entity_ptr>::const_iterator obj = solid_chars.begin(); obj != solid_chars.end(); ++obj) {
108 if(obj->get() != &e && entity_collides_with_entity(e, **obj, info)) {
109 if(info) {
110 info->collide_with = *obj;
111 }
112 return true;
113 }
114 }
115
116 return false;
117 }
118
119 bool entity_collides_with_entity(const entity& e, const entity& other, collision_info* info)
120 {
121 if((e.solid_dimensions()&other.weak_solid_dimensions()) == 0 &&
122 (e.weak_solid_dimensions()&other.solid_dimensions()) == 0) {
123 return false;
124 }
125
126 const rect& our_rect = e.solid_rect();
127 const rect& other_rect = other.solid_rect();
128
129 if(!rects_intersect(our_rect, other_rect)) {
130 return false;
131 }
132
133 const rect area = intersection_rect(our_rect, other_rect);
134
135 const solid_info* our_solid = e.solid();
136 const solid_info* other_solid = other.solid();
137 assert(our_solid && other_solid);
138
139 const frame& our_frame = e.current_frame();
140 const frame& other_frame = other.current_frame();
141
142 for(int y = area.y(); y <= area.y2(); ++y) {
143 for(int x = area.x(); x < area.x2(); ++x) {
144 const int our_x = e.face_right() ? x - e.x() : (e.x() + our_frame.width()-1) - x;
145 const int our_y = y - e.y();
146 if(our_solid->solid_at(our_x, our_y, info ? &info->area_id : NULL)) {
147 const int other_x = other.face_right() ? x - other.x() : (other.x() + other_frame.width()-1) - x;
148 const int other_y = y - other.y();
149 if(other_solid->solid_at(other_x, other_y, info ? &info->collide_with_area_id : NULL)) {
150 return true;
151 }
152 }
153 }
154 }
155
156 return false;
157 }
158
159 bool entity_collides_with_level(const level& lvl, const entity& e, MOVE_DIRECTION dir, collision_info* info)
160 {
161 const solid_info* s = e.solid();
162 if(!s) {
163 return false;
164 }
165
166 if(e.face_right() == false) {
167 if(dir == MOVE_RIGHT) {
168 dir = MOVE_LEFT;
169 } else if(dir == MOVE_LEFT) {
170 dir = MOVE_RIGHT;
171 }
172 }
173
174 const frame& f = e.current_frame();
175
176 const rect& area = s->area();
177 if(e.face_right()) {
178 rect solid_area(e.x() + area.x(), e.y() + area.y(), area.w(), area.h());
179 if(!lvl.may_be_solid_in_rect(solid_area)) {
180 return false;
181 }
182 } else {
183 rect solid_area(e.x() + f.width() - area.x() - area.w(), e.y() + area.y(), area.w(), area.h());
184 if(!lvl.may_be_solid_in_rect(solid_area)) {
185 return false;
186 }
187 }
188
189 int* friction = info ? &info->friction : NULL;
190 int* traction = info ? &info->traction : NULL;
191 int* damage = info ? &info->damage : NULL;
192
193 foreach(const const_solid_map_ptr& m, s->solid()) {
194 if(lvl.solid(e, m->dir(dir), friction, traction, damage)) {
195 return true;
196 }
197 }
198
199 return false;
200 }
201
202 int entity_collides_with_level_count(const level& lvl, const entity& e, MOVE_DIRECTION dir)
203 {
204 const solid_info* s = e.solid();
205 if(!s) {
206 return 0;
207 }
208
209 const frame& f = e.current_frame();
210 int count = 0;
211 foreach(const const_solid_map_ptr& m, s->solid()) {
212 const std::vector<point>& points = m->dir(dir);
213 foreach(const point& p, points) {
214 const int xpos = e.face_right() ? e.x() + p.x : e.x() + f.width() - 1 - p.x;
215 if(lvl.solid(xpos, e.y() + p.y)) {
216 ++count;
217 }
218 }
219 }
220
221 return count;
222 }
223
224 bool non_solid_entity_collides_with_level(const level& lvl, const entity& e)
225 {
226 const frame& f = e.current_frame();
227 if(!lvl.may_be_solid_in_rect(rect(e.x(), e.y(), f.width(), f.height()))) {
228 return false;
229 }
230
231 for(int y = 0; y < f.height(); ++y) {
232 for(int x = 0; x < f.width(); ++x) {
233 if(!f.is_alpha(e.face_right() ? x : f.width() - x - 1, y, e.time_in_frame(), true)) {
234 if(lvl.solid(e.x() + x, e.y() + y)) {
235 return true;
236 }
237 }
238 }
239 }
240
241 return false;
242 }
243
244 bool place_entity_in_level(level& lvl, entity& e)
245 {
246 if(!entity_collides(lvl, e, MOVE_NONE)) {
247 return true;
248 }
249
250 if(!entity_collides(lvl, e, MOVE_UP)) {
251 while(entity_collides(lvl, e, MOVE_NONE)) {
252 e.set_pos(e.x(), e.y()-1);
253 if(entity_collides(lvl, e, MOVE_UP)) {
254 return false;
255 }
256 }
257
258 return true;
259 }
260
261 if(!entity_collides(lvl, e, MOVE_DOWN)) {
262 while(entity_collides(lvl, e, MOVE_NONE)) {
263 e.set_pos(e.x(), e.y()+1);
264 if(entity_collides(lvl, e, MOVE_DOWN)) {
265 return false;
266 }
267 }
268
269 return true;
270 }
271
272 if(!entity_collides(lvl, e, MOVE_LEFT)) {
273 while(entity_collides(lvl, e, MOVE_NONE)) {
274 e.set_pos(e.x()-1, e.y());
275 if(entity_collides(lvl, e, MOVE_LEFT)) {
276 return false;
277 }
278 }
279
280 return true;
281 }
282
283 if(!entity_collides(lvl, e, MOVE_RIGHT)) {
284 while(entity_collides(lvl, e, MOVE_NONE)) {
285 e.set_pos(e.x()+1, e.y());
286 if(entity_collides(lvl, e, MOVE_RIGHT)) {
287 return false;
288 }
289 }
290
291 return true;
292 }
293
294 return false;
295 }
296
297 int entity_user_collision(const entity& a, const entity& b, collision_pair* areas_colliding, int buf_size)
298 {
299 if(!rects_intersect(a.frame_rect(), b.frame_rect())) {
300 return 0;
301 }
302
303 const frame& fa = a.current_frame();
304 const frame& fb = b.current_frame();
305
306 if(fa.collision_areas().empty() || fb.collision_areas().empty()) {
307 return 0;
308 }
309
310 int result = 0;
311
312 foreach(const frame::collision_area& area_a, fa.collision_areas()) {
313 rect rect_a(a.face_right() ? a.x() + area_a.area.x() : a.x() + fa.width() - area_a.area.x() - area_a.area.w(),
314 a.y() + area_a.area.y(),
315 area_a.area.w(), area_a.area.h());
316 foreach(const frame::collision_area& area_b, fb.collision_areas()) {
317 rect rect_b(b.face_right() ? b.x() + area_b.area.x() : b.x() + fb.width() - area_b.area.x() - area_b.area.w(),
318 b.y() + area_b.area.y(),
319 area_b.area.w(), area_b.area.h());
320 if(rects_intersect(rect_a, rect_b)) {
321 const int time_a = a.time_in_frame();
322 const int time_b = b.time_in_frame();
323
324 //we only check every other pixel, since this gives us
325 //enough accuracy and is 4x faster.
326 const int Stride = 2;
327 bool found = false;
328 const rect intersection = intersection_rect(rect_a, rect_b);
329 for(int y = intersection.y(); y <= intersection.y2() && !found; y += Stride) {
330 for(int x = intersection.x(); x <= intersection.x2(); x += Stride) {
331 if((area_a.no_alpha_check || !fa.is_alpha(x - a.x(), y - a.y(), time_a, a.face_right())) &&
332 (area_b.no_alpha_check || !fb.is_alpha(x - b.x(), y - b.y(), time_b, b.face_right()))) {
333 found = true;
334 break;
335 }
336 }
337 }
338
339 if(found) {
340 ++result;
341 if(buf_size > 0) {
342 areas_colliding->first = &area_a.name;
343 areas_colliding->second = &area_b.name;
344 ++areas_colliding;
345 --buf_size;
346 }
347 }
348 }
349 }
350 }
351
352 return result;
353 }
354
355 bool entity_user_collision_specific_areas(const entity& a, const std::string& area_a_id, const entity& b, const std::string& area_b_id)
356 {
357 if(&a == &b) {
358 return false;
359 }
360
361 const frame& fa = a.current_frame();
362 const frame& fb = b.current_frame();
363
364 if(fa.collision_areas().empty() || fb.collision_areas().empty()) {
365 return false;
366 }
367
368 if(!rects_intersect(rect(a.x(), a.y(), fa.width(), fa.height()),
369 rect(b.x(), b.y(), fb.width(), fb.height()))) {
370 return false;
371 }
372
373 const frame::collision_area* area_a = NULL;
374 foreach(const frame::collision_area& area, fa.collision_areas()) {
375 if(area.name == area_a_id) {
376 area_a = &area;
377 break;
378 }
379 }
380
381 if(!area_a) {
382 return false;
383 }
384
385 const frame::collision_area* area_b = NULL;
386 foreach(const frame::collision_area& area, fb.collision_areas()) {
387 if(area.name == area_b_id) {
388 area_b = &area;
389 break;
390 }
391 }
392
393 if(!area_b) {
394 return false;
395 }
396
397 rect rect_a(a.face_right() ? a.x() + area_a->area.x() : a.x() + fa.width() - area_a->area.x() - area_a->area.w(),
398 a.y() + area_a->area.y(),
399 area_a->area.w(), area_a->area.h());
400 rect rect_b(b.face_right() ? b.x() + area_b->area.x() : b.x() + fb.width() - area_b->area.x() - area_b->area.w(),
401 b.y() + area_b->area.y(),
402 area_b->area.w(), area_b->area.h());
403 if(!rects_intersect(rect_a, rect_b)) {
404 return false;
405 }
406
407 const int time_a = a.time_in_frame();
408 const int time_b = b.time_in_frame();
409
410 const rect intersection = intersection_rect(rect_a, rect_b);
411 for(int y = intersection.y(); y <= intersection.y2(); ++y) {
412 for(int x = intersection.x(); x <= intersection.x2(); ++x) {
413 if(!fa.is_alpha(x - a.x(), y - a.y(), time_a, a.face_right()) &&
414 !fb.is_alpha(x - b.x(), y - b.y(), time_b, b.face_right())) {
415 return true;
416 }
417 }
418 }
419
420 return false;
421 }
422
423 namespace {
424 class user_collision_callable : public game_logic::formula_callable {
425 entity_ptr a_, b_;
426 const std::string* area_a_;
427 const std::string* area_b_;
428 public:
429 user_collision_callable(entity_ptr a, entity_ptr b, const std::string& area_a, const std::string& area_b) : a_(a), b_(b), area_a_(&area_a), area_b_(&area_b) {
430 }
431
432 variant get_value(const std::string& key) const {
433 if(key == "collide_with") {
434 return variant(b_.get());
435 } else if(key == "area") {
436 return variant(*area_a_);
437 } else if(key == "collide_with_area") {
438 return variant(*area_b_);
439 } else {
440 return variant();
441 }
442 }
443 };
444
445 int get_collision_event_id(const std::string& area)
446 {
447 static std::map<std::string, int> cache;
448 std::map<std::string, int>::const_iterator itor = cache.find(area);
449 if(itor != cache.end()) {
450 return itor->second;
451 }
452
453 cache[area] = get_object_event_id("collide_object_" + area);
454 return cache[area];
455 }
456
457 }
458
459 void detect_user_collisions(level& lvl)
460 {
461 std::vector<entity_ptr> chars;
462 chars.reserve(lvl.get_active_chars().size());
463 foreach(const entity_ptr& a, lvl.get_active_chars()) {
464 if(a->weak_collide_dimensions() != 0 && a->current_frame().collision_areas().empty() == false) {
465 chars.push_back(a);
466 }
467 }
468
469 static const int CollideObjectID = get_object_event_id("collide_object");
470
471 const int MaxCollisions = 16;
472 collision_pair collision_buf[MaxCollisions];
473 for(std::vector<entity_ptr>::const_iterator i = chars.begin(); i != chars.end(); ++i) {
474 for(std::vector<entity_ptr>::const_iterator j = i + 1; j != chars.end(); ++j) {
475 const entity_ptr& a = *i;
476 const entity_ptr& b = *j;
477 if(a == b ||
478 (a->weak_collide_dimensions()&b->collide_dimensions()) == 0 &&
479 (a->collide_dimensions()&b->weak_collide_dimensions()) == 0) {
480 //the objects do not share a dimension, and so can't collide.
481 continue;
482 }
483
484 int ncollisions = entity_user_collision(*a, *b, collision_buf, MaxCollisions);
485 if(ncollisions > MaxCollisions) {
486 ncollisions = MaxCollisions;
487 }
488
489 for(int n = 0; n != ncollisions; ++n) {
490 {
491 user_collision_callable* callable = new user_collision_callable(a, b, *collision_buf[n].first, *collision_buf[n].second);
492 game_logic::formula_callable_ptr ptr(callable);
493 a->handle_event(CollideObjectID, callable);
494 a->handle_event(get_collision_event_id(*collision_buf[n].first), callable);
495 }
496
497 {
498 user_collision_callable* callable = new user_collision_callable(b, a, *collision_buf[n].second, *collision_buf[n].first);
499 game_logic::formula_callable_ptr ptr(callable);
500 b->handle_event(CollideObjectID, callable);
501 b->handle_event(get_collision_event_id(*collision_buf[n].second), callable);
502 }
503 }
504 }
505 }
506 }
507
508 bool is_flightpath_clear(const level& lvl, const entity& e, const rect& area)
509 {
510 if(lvl.may_be_solid_in_rect(area)) {
511 return false;
512 }
513
514 const std::vector<entity_ptr>& v = lvl.get_solid_chars();
515 for(std::vector<entity_ptr>::const_iterator obj = v.begin();
516 obj != v.end(); ++obj) {
517 if(obj->get() == &e) {
518 continue;
519 }
520
521 if(rects_intersect(area, (*obj)->solid_rect())) {
522 return false;
523 }
524 }
525
526 return true;
527 }
0 #ifndef COLLISION_UTILS_HPP_INCLUDED
1 #define COLLISION_UTILS_HPP_INCLUDED
2
3 #include "entity.hpp"
4 #include "solid_map.hpp"
5
6 class level;
7
8 int get_num_solid_dimensions();
9 const std::string& get_solid_dimension_key(int id);
10 int get_solid_dimension_id(const std::string& key);
11
12 //struct which provides information about a surface we collide with.
13 struct collision_info {
14 collision_info() : friction(0), traction(0), damage(0), adjust_y(0), platform(false), area_id(0), collide_with_area_id(0)
15 {}
16 int friction;
17 int traction;
18 int damage;
19
20 //adjustment that should take place of the colliding object's position.
21 //the reason for this is if the object is moving downwards, and at the
22 //same time a platform is moving upwards. The platform will NOT check
23 //for the downwards-moving object standing on it during its cycle, so
24 //on the downwards-moving object's cycle it may already be below where
25 //the platform is. This adjusts it so it is on top of the platform again.
26 int adjust_y;
27
28 //true iff the collided with area is a platform, rather than solid.
29 bool platform;
30
31 //the ID of the area of our body which collided.
32 const std::string* area_id;
33
34 //the object, if any, that we collided with. NULL if we collided with
35 //a tile in the level.
36 entity_ptr collide_with;
37
38 //if collide_with is non-null this will contain the ID of the area
39 //that we collided with.
40 const std::string* collide_with_area_id;
41 };
42
43 //value of what kind of collision we are looking for. If we only want to
44 //know if there is a collision with solid space, or with platforms as well.
45 enum ALLOW_PLATFORM { SOLID_ONLY, SOLID_AND_PLATFORMS };
46
47 //function which finds it a given point can be stood on.
48 bool point_standable(const level& lvl, const entity& e, int x, int y, collision_info* info=NULL, ALLOW_PLATFORM allow_platform=SOLID_AND_PLATFORMS);
49
50 //function which finds if an entity's solid area collides with anything, when
51 //the object has just moved one pixel in the direction given by 'dir'. If
52 //'dir' is MOVE_NONE, then all pixels will be checked.
53 bool entity_collides(level& lvl, const entity& e, MOVE_DIRECTION dir, collision_info* info=NULL);
54
55 //function which finds if one entity collides with another given entity.
56 bool entity_collides_with_entity(const entity& e, const entity& other, collision_info* info=NULL);
57
58
59 //function which finds if an entity collides with a level tile.
60 bool entity_collides_with_level(const level& lvl, const entity& e, MOVE_DIRECTION dir, collision_info* info=NULL);
61
62 //function which finds how many pixels in an entity collide with the level.
63 //this is generally used for debug purposes.
64 int entity_collides_with_level_count(const level& lvl, const entity& e, MOVE_DIRECTION dir);
65
66 //function to try placing an entity in a level, without it colliding. The entity
67 //may be moved according to some heuristics to place it sensibly -- the object's
68 //location will be modified. Will return true iff it succeeds in placing it.
69 bool place_entity_in_level(level& lvl, entity& e);
70
71 //function which returns true iff an entity collides with the level in
72 //'non-solid' space. That is, if any of the entity's pixels collide with
73 //level solid space.
74 bool non_solid_entity_collides_with_level(const level& lvl, const entity& e);
75
76 //function which detects user collisions between two entities. All
77 //collision areas on the objects will be checked, and the results stored
78 //in areas_colliding. The function will return the number of collision
79 //combinations that were found.
80 typedef std::pair<const std::string*, const std::string*> collision_pair;
81 int entity_user_collision(const entity& a, const entity& b, collision_pair* areas_colliding, int buf_size);
82
83 //function which returns true iff area_a of 'a' collides with area_b of 'b'
84 bool entity_user_collision_specific_areas(const entity& a, const std::string& area_a, const entity& b, const std::string& area_b);
85
86 //function to detect all user collisions and fire appropriate events to
87 //the colliding objects.
88 void detect_user_collisions(level& lvl);
89
90 bool is_flightpath_clear(const level& lvl, const entity& e, const rect& area);
91
92 #endif
0 #include <sstream>
1 #include <string>
2 #include <vector>
3 #include <GL/gl.h>
4
5 #include "color_utils.hpp"
6 #include "string_utils.hpp"
7 #include "unit_test.hpp"
8 #include "utils.hpp"
9
10 SDL_Color string_to_color(const std::string& str)
11 {
12 SDL_Color res = {0,0,0,0};
13 if(str.size() == 6) {
14 unsigned int num = strtol(str.c_str(), NULL, 16);
15 res.r = (num >> 16)&0xFF;
16 res.g = (num >> 8)&0xFF;
17 res.b = num&0xFF;
18 }
19 return res;
20 }
21
22 namespace graphics {
23
24 color::color( int r, int g, int b, int a)
25 {
26 c_.rgba[0] = truncate_to_char(r);
27 c_.rgba[1] = truncate_to_char(g);
28 c_.rgba[2] = truncate_to_char(b);
29 c_.rgba[3] = truncate_to_char(a);
30 }
31
32 color::color( uint32_t rgba)
33 {
34 c_.value = rgba;
35 c_ = convert_pixel_byte_order(c_);
36 }
37
38 color::color( const std::string& str)
39 {
40 std::vector<std::string> components;
41 components = util::split(str);
42
43 if( components.size() == 4){
44 int i=0;
45 for( std::vector<std::string>::iterator itor = components.begin();
46 itor != components.end(); ++itor)
47 {
48 c_.rgba[i] = atoi(itor->c_str());
49 ++i;
50 }
51 } else if ( components.size() == 3){ //assume that the fourth value, alpha, is 255
52 int i=0;
53 for( std::vector<std::string>::iterator itor = components.begin();
54 itor != components.end(); ++itor)
55 {
56 c_.rgba[i] = atoi(itor->c_str());
57 ++i;
58 }
59 c_.rgba[3] = 255; //no need to read the string element, b/c there isn't one
60 } else {
61 c_.value = 0;
62 }
63 }
64
65
66 variant color::get_value(const std::string& key) const
67 {
68 if(key == "r"){
69 return variant(r());
70 } else if(key == "g"){
71 return variant(g());
72 } else if(key == "b"){
73 return variant(b());
74 } else if(key == "a"){
75 return variant(a());
76 } else {
77 return variant();
78 }
79 }
80
81 void color::set_as_current_color() const
82 {
83 glColor4ub(c_.rgba[0], c_.rgba[1], c_.rgba[2], c_.rgba[3]);
84 }
85
86 void color::add_to_vector(std::vector<GLfloat>* v) const
87 {
88 v->push_back(r()/255.0);
89 v->push_back(g()/255.0);
90 v->push_back(b()/255.0);
91 v->push_back(a()/255.0);
92 }
93
94 color_transform::color_transform(const color& c)
95 {
96 rgba_[0] = c.r();
97 rgba_[1] = c.g();
98 rgba_[2] = c.b();
99 rgba_[3] = c.a();
100 }
101
102 color_transform::color_transform(int16_t r, int16_t g, int16_t b, int16_t a)
103 {
104 rgba_[0] = r;
105 rgba_[1] = g;
106 rgba_[2] = b;
107 rgba_[3] = a;
108 }
109
110 color_transform::color_transform(const std::string& str)
111 {
112 std::fill(rgba_, rgba_ + 4, 255);
113 std::vector<std::string> components = util::split(str);
114 for(int n = 0; n != components.size(); ++n) {
115 if(n < 4) {
116 rgba_[n] = atoi(components[n].c_str());
117 }
118 }
119 }
120
121 std::string color_transform::to_string() const
122 {
123 std::ostringstream s;
124 for(int n = 0; n != 4; ++n) {
125 if(n) {
126 s << ",";
127 }
128
129 s << rgba_[n];
130 }
131
132 return s.str();
133 }
134
135 variant color_transform::get_value(const std::string& key) const
136 {
137 if(key == "r"){
138 return variant(r());
139 } else if(key == "g"){
140 return variant(g());
141 } else if(key == "b"){
142 return variant(b());
143 } else if(key == "a"){
144 return variant(a());
145 } else {
146 return variant();
147 }
148 }
149
150 namespace {
151 int16_t clip(int16_t val) {
152 return val < 0 ? 0 : (val > 255 ? 255 : val);
153 }
154 }
155
156 color color_transform::to_color() const
157 {
158 return color(clip(r()),clip(g()),clip(b()),clip(a()));
159 }
160
161 bool color_transform::fits_in_color() const
162 {
163 for(int n = 0; n != 4; ++n) {
164 if(rgba_[n] > 255) {
165 return false;
166 }
167 }
168
169 return true;
170 }
171
172 color_transform operator+(const color_transform& a, const color_transform& b)
173 {
174 return color_transform(a.r() + b.r(), a.g() + b.g(), a.b() + b.b(), std::max(a.a(), b.a()));
175 }
176
177 color_transform operator-(const color_transform& a, const color_transform& b)
178 {
179 color_transform result = a;
180 for(int n = 0; n != 3; ++n) {
181 if(result.buf()[n] > b.buf()[n]) {
182 result.buf()[n] -= b.buf()[n];
183 } else {
184 result.buf()[n] = 0;
185 }
186 }
187
188 if(result.buf()[3] > 255) {
189 result.buf()[3] = 255;
190 }
191
192 return result;
193 }
194
195 UNIT_TEST(color)
196 {
197 //check that a value plugged into the color is returned when we
198 //ask for it.
199 CHECK_EQ(color(758492).rgba(), 758492);
200
201 //check that an rgba value is equal to an expected int value.
202 CHECK_EQ(color(255,255,0,255).rgba(), color(0xFFFF00FF).rgba());
203
204 //check that parsing from a string works properly
205 CHECK_EQ(color("255,128,255,128").rgba(), color(255,128,255,128).rgba());
206
207 //it would be nice to be able to specify a string with three instead of
208 //four values, and have it just assume 255 for the alpha.
209 CHECK_EQ(color("255,128,255").rgba(), color(255,128,255,255).rgba());
210
211 //check that we can get a value out of a color
212 CHECK_EQ(color(255,128,64,0).get_value("g").as_int(), 128);
213 }
214
215 }
0 #ifndef COLOR_UTILS_HPP_INCLUDED
1 #define COLOR_UTILS_HPP_INCLUDED
2
3 #include "SDL.h"
4 #include "GL/gl.h"
5
6 #include <string>
7 #include <vector>
8 #include "formula_callable.hpp"
9
10 SDL_Color string_to_color(const std::string& str);
11
12 namespace graphics {
13 class color : public game_logic::formula_callable
14 {
15 public:
16 uint8_t r() const {return c_.rgba[0]; }
17 uint8_t g() const {return c_.rgba[1]; }
18 uint8_t b() const {return c_.rgba[2]; }
19 uint8_t a() const {return c_.rgba[3]; }
20 uint32_t rgba() const {return convert_pixel_byte_order(c_).value; }
21 //TODO: color to return string version
22
23 uint32_t value() const { return c_.value; }
24
25 color( int r, int g, int b, int a);
26 explicit color( uint32_t rgba = 0);
27 explicit color( const std::string& str);
28
29 variant get_value(const std::string& key) const;
30
31 void set_as_current_color() const;
32 void add_to_vector(std::vector<GLfloat>* v) const;
33
34 static uint32_t convert_pixel_byte_order(uint32_t p) {
35 PixelUnion pu;
36 pu.value = p;
37 return convert_pixel_byte_order(pu).value;
38 }
39
40 private:
41 union PixelUnion {
42 uint32_t value;
43 uint8_t rgba[4];
44 };
45
46 PixelUnion c_;
47
48 static PixelUnion convert_pixel_byte_order(PixelUnion p) {
49 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
50 std::reverse(p.rgba, p.rgba + sizeof(p.rgba));
51 #endif
52 return p;
53 }
54 };
55
56 class color_transform : public game_logic::formula_callable
57 {
58 public:
59 color_transform(const color& c);
60 color_transform(int16_t r, int16_t g, int16_t b, int16_t a);
61 explicit color_transform(const std::string& str);
62
63 std::string to_string() const;
64
65 int16_t r() const { return rgba_[0]; }
66 int16_t g() const { return rgba_[1]; }
67 int16_t b() const { return rgba_[2]; }
68 int16_t a() const { return rgba_[3]; }
69
70 int16_t* buf() { return rgba_; }
71 const int16_t* buf() const { return rgba_; }
72
73 variant get_value(const std::string& key) const;
74
75 color to_color() const;
76
77 //whether this fits in a color object.
78 bool fits_in_color() const;
79 private:
80 int16_t rgba_[4];
81 };
82
83 color_transform operator+(const color_transform& a, const color_transform& b);
84 color_transform operator-(const color_transform& a, const color_transform& b);
85
86 }
87 #endif
0 #include <algorithm>
1 #include <inttypes.h>
2
3 #include "asserts.hpp"
4 #include "colorshift_hash_table.hpp"
5
6 namespace {
7 const int ArraySizes[] = {7, 17, 37, 79, 163, 331, 673, 1361, 2729, 5471, 10949, 21911, 43853, 87719, 175447, 350899, 701819, 1403641, 2807303, 5614657, 11229331, 22458671, 44917381, 89834777, }; //normally a vector would increase in size by ^2 with each iteration. However, with our hashing function, this yields perversely bad collisions, because the last byte of each color value is the alpha, and generally is always opaque or transparent (e.g. 0x00 or 0xFF). Whenever this lines lines up with a power of 2, such as with an arraySize of 256, it would fail like this. Thus we're using primes, instead. Our primes are also precalculated because in practice we'll never have many colors.
8 }
9
10 colorshift_hash_table::colorshift_hash_table()
11 : length_(10),
12 elements_stored_(0),
13 empty_color_(0x6f6d5100),
14 num_predefined_sizes_(24)
15 {
16 array_ = new pixel_pair[length_];
17 ideal_size_ = length_ / 2;
18
19 std::fill(array_, array_ + length_, std::pair<uint32_t, uint32_t> (empty_color_,empty_color_));
20 }
21
22 colorshift_hash_table::~colorshift_hash_table(){
23 delete [] array_;
24 }
25
26 void colorshift_hash_table::insert(const pixel_pair& entry){
27
28 if( elements_stored_ >= ideal_size_){
29 colorshift_hash_table::grow_array();
30 }
31
32 int desired_element = entry.first%length_;
33
34 for(int i=0; i <= elements_stored_; ++i){
35 desired_element = (desired_element + 1) % length_; //modulo addition
36
37 if (array_[desired_element].first != empty_color_){
38 array_[desired_element] = entry;
39
40 ++elements_stored_;
41 return;
42 }
43 }
44 }
45
46 void colorshift_hash_table::grow_array(){
47 int new_length = 0;
48 pixel_pair* temp_array; //copy of the old array
49 pixel_pair* new_array; //new, bigger array
50
51
52 for(int i=0; i < num_predefined_sizes_; ++i){
53 if (length_ <= ArraySizes[i]){
54 new_length = ArraySizes[i+1];
55 break;
56 }
57 }
58 ASSERT_NE(new_length,0);
59
60 new_array = new pixel_pair[new_length];
61 std::fill(new_array, new_array + new_length, std::pair<uint32_t, uint32_t> (empty_color_,empty_color_));
62
63 //set aside address to old array
64 temp_array = &array_[0];
65 array_ = &new_array[0];
66
67 ideal_size_ = new_length / 2; //bad code smell: failure to set this could result in an infinite recursion between this and insert
68
69 for(int i=0; i<length_ ;++i){
70 colorshift_hash_table::insert(temp_array[i]);
71 }
72 delete temp_array;
73 }
74
75
76 uint32_t colorshift_hash_table::operator[](uint32_t key) const {
77
78 //If the first+n element isn't what we're looking for, then loop forward until we find an empty slot - because of how these are inserted, this is a faster way of indicating the array doesn't have the desired item than the alternative of looping over the entire array.
79 int desired_element = key%length_;
80
81 for(int i=0; i <= elements_stored_; ++i){
82 desired_element = (desired_element + 1) % length_; //modulo addition
83
84 if (array_[desired_element].first == key){ //then we don't have a collision, and can return the color
85 return array_[key%length_].second;
86 } else if (array_[desired_element].first == empty_color_) { //if we did have a collision, we normally step to the next element, unless the one we're on is empty, which means the color isn't in our array
87 return key; //identity operation
88 }
89 }
90 //if we manage to step through the entire array without reaching any empty elements, something is wrong and we should probably assert, however
91 return key; //identity operation
92 }
93
94
0 #ifndef COLORSHIFT_HASH_TABLE_HPP_INCLUDED
1 #define COLORSHIFT_HASH_TABLE_HPP_INCLUDED
2
3 //A quick data structure for doing color shifts of pixel art images.
4 #include <utility>
5 #include <inttypes.h>
6
7
8 class colorshift_hash_table {
9 public:
10 colorshift_hash_table();
11 ~colorshift_hash_table();
12 typedef std::pair<uint32_t, uint32_t> pixel_pair; //original and result colors
13 void insert(const pixel_pair& entry); //insert a new entry into the table
14 uint32_t operator[](uint32_t key) const; //look up an item in the table
15
16 private:
17 pixel_pair* array_;
18 int length_; //size of the array
19 int ideal_size_; // half the array size. We want half the array to be empty, to avoid having collisions.
20 int elements_stored_; //number of actual valid items in the array
21 const int empty_color_; //when this is the key, this given array slot is 'empty'
22
23
24 const int num_predefined_sizes_;
25
26 void grow_array();
27
28
29 };
30
31 #endif
0 #ifndef CONCURRENT_CACHE_HPP_INCLUDED
1 #define CONCURRENT_CACHE_HPP_INCLUDED
2
3 #include <map>
4
5 #include "thread.hpp"
6
7 template<typename Key, typename Value>
8 class concurrent_cache
9 {
10 public:
11 typedef std::map<Key, Value> map_type;
12 size_t size() const { threading::lock l(mutex_); return map_.size(); }
13 Value get(const Key& key) {
14 threading::lock l(mutex_);
15 typename map_type::const_iterator itor = map_.find(key);
16 if(itor != map_.end()) {
17 return itor->second;
18 } else {
19 return Value();
20 }
21 }
22
23 void put(const Key& key, const Value& value) {
24 threading::lock l(mutex_);
25 map_[key] = value;
26 }
27
28 int count(const Key& key) const {
29 threading::lock l(mutex_);
30 return map_.count(key);
31 }
32
33 void clear() {
34 threading::lock l(mutex_);
35 map_.clear();
36 }
37
38 struct lock : public threading::lock {
39 explicit lock(concurrent_cache& cache) : threading::lock(cache.mutex_), cache_(cache) {
40 }
41
42 map_type& map() const { return cache_.map_; }
43
44 private:
45 concurrent_cache& cache_;
46 };
47
48 private:
49 map_type map_;
50 mutable threading::mutex mutex_;
51 };
52
53 #endif
0 #ifdef _WIN32
1 #include <winsock2.h>
2 #else
3 #include <netinet/in.h>
4 #endif
5
6 #include <assert.h>
7 #include <inttypes.h>
8
9 #include <stdio.h>
10
11 #include <stack>
12 #include <vector>
13
14 #include "SDL.h"
15
16 #include "asserts.hpp"
17 #include "controls.hpp"
18 #include "foreach.hpp"
19 #include "joystick.hpp"
20 #include "key.hpp"
21 #include "iphone_controls.hpp"
22
23 namespace controls {
24 namespace {
25 int npackets_received;
26 int ngood_packets;
27 int last_packet_size_;
28
29 const int MAX_PLAYERS = 8;
30
31 std::vector<unsigned char> controls[MAX_PLAYERS];
32
33 //for each player, the highest confirmed cycle we have
34 int32_t highest_confirmed[MAX_PLAYERS];
35
36 //for each player, the highest confirmed cycle of ours that they have
37 int32_t remote_highest_confirmed[MAX_PLAYERS];
38
39 int starting_cycles;
40 int nplayers = 1;
41 int local_player;
42
43 int first_invalid_cycle_var = -1;
44
45 int32_t our_highest_confirmed() {
46 int32_t res = -1;
47 for(int n = 0; n != nplayers; ++n) {
48 if(res == -1 || highest_confirmed[n] < res) {
49 res = highest_confirmed[n];
50 }
51 }
52
53 return res;
54 }
55
56 CKey& keyboard() {
57 static CKey key;
58 return key;
59 }
60 }
61
62 int their_highest_confirmed() {
63 int32_t res = -1;
64 for(int n = 0; n != nplayers; ++n) {
65 if(n == local_player) {
66 continue;
67 }
68
69 if(res == -1 || remote_highest_confirmed[n] < res) {
70 res = remote_highest_confirmed[n];
71 }
72 }
73
74 return res;
75 }
76
77 void new_level(int level_starting_cycles, int level_nplayers, int level_local_player)
78 {
79 std::cerr << "SET STARTING CYCLES: " << level_starting_cycles << "\n";
80 starting_cycles = level_starting_cycles;
81 nplayers = level_nplayers;
82 local_player = level_local_player;
83 foreach(std::vector<unsigned char>& v, controls) {
84 v.clear();
85 }
86
87 foreach(int32_t& highest, highest_confirmed) {
88 highest = 0;
89 }
90
91 foreach(int32_t& highest, remote_highest_confirmed) {
92 highest = 0;
93 }
94 }
95
96 namespace {
97 std::stack<unsigned char> local_control_locks;
98 }
99
100 local_controls_lock::local_controls_lock(unsigned char state)
101 {
102 local_control_locks.push(state);
103 }
104
105 local_controls_lock::~local_controls_lock()
106 {
107 local_control_locks.pop();
108 }
109
110 void read_local_controls()
111 {
112 if(local_player < 0 || local_player >= nplayers) {
113 return;
114 }
115
116
117 unsigned char state = 0;
118 if(local_control_locks.empty()) {
119 if(keyboard()[SDLK_UP] || joystick::up() || iphone_controls::up()) { state |= (1 << CONTROL_UP); }
120 if(keyboard()[SDLK_DOWN] || joystick::down() || iphone_controls::down()) { state |= (1 << CONTROL_DOWN); }
121 if(keyboard()[SDLK_LEFT] || joystick::left() || iphone_controls::left()) { state |= (1 << CONTROL_LEFT); }
122 if(keyboard()[SDLK_RIGHT] || joystick::right() || iphone_controls::right()) { state |= (1 << CONTROL_RIGHT); }
123 if(keyboard()[SDLK_d] || joystick::button(0) || iphone_controls::attack()) { state |= (1 << CONTROL_ATTACK); }
124 if(keyboard()[SDLK_a] || joystick::button(1) || iphone_controls::jump()) { state |= (1 << CONTROL_JUMP); }
125 if(keyboard()[SDLK_s] || joystick::button(2) || iphone_controls::tongue()) { state |= (1 << CONTROL_TONGUE); }
126 } else {
127 //we have the controls locked into a specific state.
128 state = local_control_locks.top();
129 }
130
131 controls[local_player].push_back(state);
132 highest_confirmed[local_player]++;
133
134 //advance networked player's controls based on the assumption that they
135 //just did the same thing as last time; incoming packets will correct
136 //any assumptions.
137 for(int n = 0; n != nplayers; ++n) {
138 while(n != local_player && controls[n].size() < controls[local_player].size()) {
139 if(controls[n].empty()) {
140 controls[n].push_back(0);
141 } else {
142 controls[n].push_back(controls[n].back());
143 }
144 }
145 }
146 }
147
148 void get_control_status(int cycle, int player, bool* output)
149 {
150 --cycle;
151 cycle -= starting_cycles;
152
153 ASSERT_INDEX_INTO_VECTOR(cycle, controls[player]);
154
155 unsigned char state = controls[player][cycle];
156
157 for(int n = 0; n != NUM_CONTROLS; ++n) {
158 output[n] = (state&(1 << n)) ? true : false;
159 }
160 }
161
162 void read_control_packet(const char* buf, size_t len)
163 {
164 ++npackets_received;
165
166 if(len < 13) {
167 fprintf(stderr, "ERROR: CONTROL PACKET TOO SHORT: %d\n", (int)len);
168 return;
169 }
170
171 const char* end_buf = buf + len;
172
173 int slot = *buf++;
174
175 if(slot < 0 || slot >= nplayers) {
176 fprintf(stderr, "ERROR: BAD SLOT NUMBER: %d/%d\n", slot, nplayers);
177 return;
178 }
179
180 if(slot == local_player) {
181 fprintf(stderr, "ERROR: NETWORK PLAYER SAYS THEY HAVE THE SAME SLOT AS US!\n");
182 return;
183 }
184
185 int32_t current_cycle;
186 memcpy(&current_cycle, buf, 4);
187 current_cycle = ntohl(current_cycle);
188 buf += 4;
189
190 if(current_cycle < highest_confirmed[slot]) {
191 fprintf(stderr, "DISCARDING PACKET -- OUT OF ORDER: %d < %d\n", current_cycle, highest_confirmed[slot]);
192 return;
193 }
194
195 int32_t highest_cycle;
196 memcpy(&highest_cycle, buf, 4);
197 highest_cycle = ntohl(highest_cycle);
198 buf += 4;
199
200 if(highest_cycle > remote_highest_confirmed[slot]) {
201 remote_highest_confirmed[slot] = highest_cycle;
202 }
203
204 int32_t ncycles;
205 memcpy(&ncycles, buf, 4);
206 ncycles = ntohl(ncycles);
207 buf += 4;
208
209 if(end_buf - buf != ncycles) {
210 fprintf(stderr, "ERROR: BAD NUMBER OF CYCLES: %d vs %d\n", ncycles, (end_buf - buf));
211 return;
212 }
213
214 int start_cycle = 1 + current_cycle - ncycles;
215
216 //if we already have data up to this point, don't reprocess it.
217 if(start_cycle < highest_confirmed[slot]) {
218 int diff = highest_confirmed[slot] - start_cycle;
219 ncycles -= diff;
220 buf += diff;
221 start_cycle = highest_confirmed[slot];
222 }
223
224 for(int cycle = start_cycle; cycle <= current_cycle; ++cycle) {
225 if(cycle < controls[slot].size()) {
226 if(controls[slot][cycle] != *buf) {
227 fprintf(stderr, "RECEIVED CORRECTION\n");
228 controls[slot][cycle] = *buf;
229 if(first_invalid_cycle_var == -1 || first_invalid_cycle_var > cycle) {
230 //mark us as invalid back to this point, so game logic
231 //will be recalculated from here.
232 first_invalid_cycle_var = cycle;
233 }
234 }
235 } else {
236 fprintf(stderr, "RECEIVED FUTURE PACKET!\n");
237 while(controls[slot].size() <= cycle) {
238 controls[slot].push_back(*buf);
239 }
240 }
241
242 ++buf;
243 }
244
245 //extend the current control out to the end, to keep the assumption that
246 //controls don't change unless we get an explicit signal
247 if(current_cycle < static_cast<int>(controls[slot].size()) - 1) {
248 for(int n = current_cycle + 1; n < controls[slot].size(); ++n) {
249 controls[slot][n] = controls[slot][current_cycle];
250 }
251 }
252
253 //mark our highest confirmed cycle for this player
254 highest_confirmed[slot] = current_cycle;
255
256 assert(buf == end_buf);
257
258 fprintf(stderr, "PROCESSED REMOTE PACKET OKAY\n");
259 ++ngood_packets;
260 }
261
262 void write_control_packet(std::vector<char>& v)
263 {
264 if(local_player < 0 || local_player >= nplayers) {
265 fprintf(stderr, "NO VALID LOCAL PLAYER\n");
266 return;
267 }
268
269 //write our slot to the packet
270 v.push_back(local_player);
271
272 //write our current cycle
273 int32_t current_cycle = controls[local_player].size()-1;
274 int32_t current_cycle_net = htonl(current_cycle);
275 v.resize(v.size() + 4);
276 memcpy(&v[v.size()-4], &current_cycle_net, 4);
277
278 //write our highest confirmed cycle
279 int32_t highest_cycle = htonl(our_highest_confirmed());
280 v.resize(v.size() + 4);
281 memcpy(&v[v.size()-4], &highest_cycle, 4);
282
283 int32_t ncycles_to_write = 1+current_cycle - their_highest_confirmed();
284
285 last_packet_size_ = ncycles_to_write;
286 if(ncycles_to_write > controls[local_player].size()) {
287 ncycles_to_write = controls[local_player].size();
288 }
289
290 int32_t ncycles_to_write_net = htonl(ncycles_to_write);
291 v.resize(v.size() + 4);
292 memcpy(&v[v.size()-4], &ncycles_to_write_net, 4);
293
294 v.insert(v.end(), controls[local_player].end() - ncycles_to_write, controls[local_player].end());
295 }
296
297 int first_invalid_cycle()
298 {
299 return first_invalid_cycle_var;
300 }
301
302 void mark_valid()
303 {
304 first_invalid_cycle_var = -1;
305 }
306
307 int num_players()
308 {
309 return nplayers;
310 }
311
312 int num_errors()
313 {
314 return npackets_received - ngood_packets;
315 }
316
317 int packets_received()
318 {
319 return npackets_received;
320 }
321
322 int cycles_behind()
323 {
324 if(local_player < 0 || local_player >= nplayers) {
325 return 0;
326 }
327
328 return highest_confirmed[local_player] - our_highest_confirmed();
329 }
330
331 int last_packet_size()
332 {
333 return last_packet_size_;
334 }
335
336 void debug_dump_controls()
337 {
338 fprintf(stderr, "CONTROLS:");
339 for(int n = 0; n < nplayers; ++n) {
340 fprintf(stderr, " %d:", n);
341 for(int m = 0; m < controls[n].size() && m < highest_confirmed[n]; ++m) {
342 char c = controls[n][m];
343 fprintf(stderr, "%02x", (int)c);
344 }
345 }
346
347 fprintf(stderr, "\n");
348
349 for(int n = 0; n < nplayers; ++n) {
350 for(int m = 0; m < controls[n].size() && m < highest_confirmed[n]; ++m) {
351 fprintf(stderr, "CTRL PLAYER %d CYCLE %d: ", n, m);
352 for(int j = 0; j != NUM_CONTROLS; ++j) {
353 fprintf(stderr, (1 << j)&controls[n][m] ? "1" : "0");
354 }
355 fprintf(stderr, "\n");
356 }
357 }
358 }
359
360 }
0 #ifndef CONTROLS_HPP_INCLUDED
1 #define CONTROLS_HPP_INCLUDED
2
3 #include <vector>
4
5 namespace controls {
6
7 enum CONTROL_ITEM {
8 CONTROL_UP,
9 CONTROL_DOWN,
10 CONTROL_LEFT,
11 CONTROL_RIGHT,
12 CONTROL_ATTACK,
13 CONTROL_JUMP,
14 CONTROL_TONGUE,
15 NUM_CONTROLS,
16 };
17
18 void new_level(int starting_cycle, int nplayers, int local_player);
19
20 //an object which can lock controls into a specific state for the duration
21 //of its scope.
22 class local_controls_lock {
23 public:
24 explicit local_controls_lock(unsigned char state=0);
25 ~local_controls_lock();
26 };
27
28 void read_local_controls();
29
30 void get_control_status(int cycle, int player, bool* output);
31
32 void read_control_packet(const char* buf, size_t len);
33 void write_control_packet(std::vector<char>& v);
34
35 int first_invalid_cycle();
36 void mark_valid();
37
38 int num_players();
39 int num_errors();
40 int packets_received();
41 int cycles_behind();
42
43 int their_highest_confirmed();
44 int last_packet_size();
45
46 void debug_dump_controls();
47
48 }
49
50 #endif
0 #include <iostream>
1 #include <math.h>
2
3 #include "current_generator.hpp"
4 #include "formatter.hpp"
5 #include "wml_node.hpp"
6 #include "wml_utils.hpp"
7
8 current_generator_ptr current_generator::create(wml::const_node_ptr node)
9 {
10 const std::string& type = node->attr("type");
11 if(type == "radial") {
12 return current_generator_ptr(new radial_current_generator(node));
13 } else if(type == "rect") {
14 return current_generator_ptr(new rect_current_generator(node));
15 } else {
16 return NULL;
17 }
18 }
19
20 current_generator::~current_generator() {
21 }
22
23 variant current_generator::get_value(const std::string& key) const
24 {
25 return variant();
26 }
27
28 radial_current_generator::radial_current_generator(int intensity, int radius)
29 : intensity_(intensity), radius_(radius)
30 {}
31
32 radial_current_generator::radial_current_generator(wml::const_node_ptr node)
33 : intensity_(wml::get_int(node, "intensity")),
34 radius_(wml::get_int(node, "radius"))
35 {}
36
37 void radial_current_generator::generate(int center_x, int center_y, int target_x, int target_y, int target_mass, int* velocity_x, int* velocity_y) {
38 if(center_x == target_x && center_y == target_y) {
39 return;
40 }
41
42 const float xdiff = target_x - center_x;
43 const float ydiff = target_y - center_y;
44 if(abs(xdiff) >= radius_ || abs(ydiff) > radius_) {
45 return;
46 }
47
48 const float distance = sqrt(xdiff*xdiff + ydiff*ydiff);
49 if(distance >= radius_) {
50 return;
51 }
52
53 const float intensity = intensity_*(1.0 - distance/radius_);
54 const float xdiff_normalized = xdiff/(abs(xdiff) + abs(ydiff));
55 const float ydiff_normalized = ydiff/(abs(xdiff) + abs(ydiff));
56
57 std::cerr << "DO_CURRENT: " << center_x << "," << center_y << " ~ " << target_x << "," << target_y << ": "<< intensity << " x " << xdiff_normalized << "," << ydiff_normalized << "\n";
58 *velocity_x += xdiff_normalized*intensity;
59 *velocity_y += ydiff_normalized*intensity;
60 }
61
62 wml::node_ptr radial_current_generator::write() const
63 {
64 wml::node_ptr result(new wml::node("current_generator"));
65 result->set_attr("type", "radial");
66 result->set_attr("intensity", formatter() << intensity_);
67 result->set_attr("radius", formatter() << radius_);
68 return result;
69 }
70
71 rect_current_generator::rect_current_generator(const rect& r, int xvelocity, int yvelocity, int strength)
72 : rect_(r), xvelocity_(xvelocity), yvelocity_(yvelocity), strength_(strength)
73 {}
74
75 rect_current_generator::rect_current_generator(wml::const_node_ptr node)
76 : rect_(node->attr("rect")), xvelocity_(wml::get_int(node, "xvelocity")), yvelocity_(wml::get_int(node, "yvelocity")), strength_(wml::get_int(node, "strength"))
77 {}
78
79 void rect_current_generator::generate(int center_x, int center_y, int target_x, int target_y, int target_mass, int* velocity_x, int* velocity_y)
80 {
81 const int strength = strength_;
82 if(point_in_rect(point(target_x, target_y), rect_)) {
83 if(xvelocity_ > 0 && *velocity_x < xvelocity_) {
84 int amount = (xvelocity_ - std::max(0, *velocity_x))*strength/(target_mass*1000);
85 const int distance = rect_.x2() - target_x;
86 amount = (amount*distance*distance)/(rect_.h()*rect_.h());
87 *velocity_x += amount;
88 if(*velocity_x > xvelocity_) {
89 *velocity_x = xvelocity_;
90 }
91 } else if(xvelocity_ < 0 && *velocity_x > xvelocity_) {
92 int amount = (xvelocity_ - std::min(0, *velocity_x))*strength/(target_mass*1000);
93 const int distance = target_x - rect_.x();
94 amount = (amount*distance*distance)/(rect_.h()*rect_.h());
95 *velocity_x += amount;
96 if(*velocity_x < xvelocity_) {
97 *velocity_x = xvelocity_;
98 }
99 }
100
101 if(yvelocity_ > 0 && *velocity_y < yvelocity_) {
102 int amount = (yvelocity_ - std::max(0, *velocity_y))*strength/(target_mass*1000);
103 const int distance = rect_.y2() - target_y;
104 amount = (amount*distance*distance)/(rect_.h()*rect_.h());
105 *velocity_y += amount;
106 if(*velocity_y > yvelocity_) {
107 *velocity_y = yvelocity_;
108 }
109 } else if(yvelocity_ < 0 && *velocity_y > yvelocity_) {
110 int amount = yvelocity_*strength/(target_mass*1000);
111 const int distance = target_y - rect_.y();
112 // amount = (amount*distance*distance)/(rect_.h()*rect_.h());
113 std::cerr << "DIST: " << distance << "/" << rect_.h() << " " << *velocity_y << "\n";
114 if(distance < rect_.h()/2 && *velocity_y > 0) {
115 std::cerr << "CANCEL\n";
116 amount = 0;
117 }
118 *velocity_y += amount;
119 if(*velocity_y < yvelocity_) {
120 // *velocity_y = yvelocity_;
121 }
122 }
123 }
124 }
125
126 wml::node_ptr rect_current_generator::write() const
127 {
128 wml::node_ptr node(new wml::node("current_generator"));
129 node->set_attr("type", "rect");
130 node->set_attr("rect", rect_.to_string());
131 node->set_attr("xvelocity", formatter() << xvelocity_);
132 node->set_attr("yvelocity", formatter() << yvelocity_);
133 node->set_attr("strength", formatter() << strength_);
134 return node;
135 }
0 #ifndef CURRENT_GENERATOR_HPP_INCLUDED
1 #define CURRENT_GENERATOR_HPP_INCLUDED
2
3 #include <boost/intrusive_ptr.hpp>
4
5 #include "formula_callable.hpp"
6 #include "geometry.hpp"
7 #include "wml_node_fwd.hpp"
8
9 typedef boost::intrusive_ptr<class current_generator> current_generator_ptr;
10
11 class current_generator : public game_logic::formula_callable
12 {
13 public:
14 static current_generator_ptr create(wml::const_node_ptr node);
15
16 virtual ~current_generator();
17
18 virtual void generate(int center_x, int center_y, int target_x, int target_y, int target_mass, int* velocity_x, int* velocity_y) = 0;
19 virtual wml::node_ptr write() const = 0;
20 private:
21 virtual variant get_value(const std::string& key) const;
22 };
23
24 class radial_current_generator : public current_generator
25 {
26 public:
27 radial_current_generator(int intensity, int radius);
28 explicit radial_current_generator(wml::const_node_ptr node);
29 virtual ~radial_current_generator() {}
30
31 virtual void generate(int center_x, int center_y, int target_x, int target_y, int target_mass, int* velocity_x, int* velocity_y);
32 virtual wml::node_ptr write() const;
33 private:
34 int intensity_;
35 int radius_;
36 };
37
38 class rect_current_generator : public current_generator
39 {
40 public:
41 rect_current_generator(const rect& r, int xvelocity, int yvelocity, int strength);
42 explicit rect_current_generator(wml::const_node_ptr node);
43 virtual void generate(int center_x, int center_y, int target_x, int target_y, int target_mass, int* velocity_x, int* velocity_y);
44
45 virtual wml::node_ptr write() const;
46 private:
47 rect rect_;
48 int xvelocity_, yvelocity_;
49 int strength_;
50 };
51
52 #endif
0 #include <SDL.h>
1 #ifndef SDL_VIDEO_OPENGL_ES
2 #include <GL/glew.h>
3 #endif
4
5 #include <boost/bind.hpp>
6 #include <boost/function.hpp>
7
8 #include <stdio.h>
9
10 #include <cassert>
11 #include <iostream>
12
13 #include "asserts.hpp"
14 #include "collision_utils.hpp"
15 #include "custom_object.hpp"
16 #include "custom_object_callable.hpp"
17 #include "custom_object_functions.hpp"
18 #include "draw_scene.hpp"
19 #include "font.hpp"
20 #include "formatter.hpp"
21 #include "formula_callable.hpp"
22 #include "formula_profiler.hpp"
23 #include "graphical_font.hpp"
24 #include "level.hpp"
25 #include "level_logic.hpp"
26 #include "object_events.hpp"
27 #include "playable_custom_object.hpp"
28 #include "raster.hpp"
29 #include "string_utils.hpp"
30 #include "surface_formula.hpp"
31 #include "wml_node.hpp"
32 #include "wml_utils.hpp"
33 #include "unit_test.hpp"
34 #include "utils.hpp"
35 #include "sound.hpp"
36
37 struct custom_object_text {
38 std::string text;
39 const_graphical_font_ptr font;
40 int size;
41 rect dimensions;
42 int alpha;
43 };
44
45 custom_object::custom_object(wml::const_node_ptr node)
46 : entity(node),
47 previous_y_(y()),
48 custom_type_(node->get_child("type")),
49 type_(custom_type_ ?
50 const_custom_object_type_ptr(new custom_object_type(custom_type_)) :
51 custom_object_type::get(node->attr("type"))),
52 base_type_(type_),
53 frame_(&type_->default_frame()),
54 frame_name_(wml::get_str(node, "current_frame", "normal")),
55 time_in_frame_(wml::get_int(node, "time_in_frame")),
56 time_in_frame_delta_(wml::get_int(node, "time_in_frame_delta", 1)),
57 velocity_x_(wml::get_int(node, "velocity_x")),
58 velocity_y_(wml::get_int(node, "velocity_y")),
59 accel_x_(wml::get_int(node, "accel_x")),
60 accel_y_(wml::get_int(node, "accel_y")),
61 rotate_(0), zorder_(wml::get_int(node, "zorder", type_->zorder())),
62 hitpoints_(wml::get_int(node, "hitpoints", type_->hitpoints())),
63 max_hitpoints_(wml::get_int(node, "max_hitpoints", type_->hitpoints())),
64 was_underwater_(false),
65 has_feet_(wml::get_bool(node, "has_feet", type_->has_feet())),
66 invincible_(0),
67 sound_volume_(128),
68 vars_(new game_logic::formula_variable_storage(type_->variables())),
69 tmp_vars_(new game_logic::formula_variable_storage(type_->tmp_variables())),
70 last_hit_by_anim_(0),
71 current_animation_id_(0),
72 cycle_(wml::get_int(node, "cycle")),
73 loaded_(false),
74 standing_on_prev_x_(INT_MIN), standing_on_prev_y_(INT_MIN),
75 can_interact_with_(false), fall_through_platforms_(0),
76 fragment_shaders_(util::split(node->attr("fragment_shaders"))),
77 vertex_shaders_(util::split(node->attr("vertex_shaders"))),
78 shader_(0),
79 always_active_(wml::get_bool(node, "always_active", false)),
80 last_cycle_active_(0)
81 {
82 if(node->has_attr("x_schedule")) {
83 if(position_schedule_.get() == NULL) {
84 position_schedule_.reset(new position_schedule);
85 }
86
87 const std::string& s = node->attr("x_schedule").str();
88
89 int nints = std::count(s.begin(), s.end(), ',')+1;
90 position_schedule_->x_pos.resize(nints);
91 util::split_into_ints(s.c_str(), &position_schedule_->x_pos[0], &nints);
92 position_schedule_->x_pos.resize(nints);
93 }
94
95 if(node->has_attr("y_schedule")) {
96 if(position_schedule_.get() == NULL) {
97 position_schedule_.reset(new position_schedule);
98 }
99
100 const std::string& s = node->attr("y_schedule").str();
101
102 int nints = std::count(s.begin(), s.end(), ',')+1;
103 position_schedule_->y_pos.resize(nints);
104 util::split_into_ints(s.c_str(), &position_schedule_->y_pos[0], &nints);
105 position_schedule_->y_pos.resize(nints);
106 }
107
108 if(node->has_attr("rotation_schedule")) {
109 if(position_schedule_.get() == NULL) {
110 position_schedule_.reset(new position_schedule);
111 }
112
113 const std::string& s = node->attr("rotation_schedule").str();
114
115 int nints = std::count(s.begin(), s.end(), ',')+1;
116 position_schedule_->rotation.resize(nints);
117 util::split_into_ints(s.c_str(), &position_schedule_->rotation[0], &nints);
118 position_schedule_->rotation.resize(nints);
119 }
120
121 if(position_schedule_.get() != NULL && node->has_attr("schedule_speed")) {
122 position_schedule_->speed = wml::get_int(node, "schedule_speed");
123 }
124
125 if(node->has_attr("draw_area")) {
126 draw_area_.reset(new rect(node->attr("draw_area")));
127 }
128
129 if(node->has_attr("activation_area")) {
130 activation_area_.reset(new rect(node->attr("activation_area")));
131 }
132
133 if(node->has_attr("variations")) {
134 current_variation_ = util::split(node->attr("variations"));
135 type_ = base_type_->get_variation(current_variation_);
136 }
137
138 if(node->has_attr("position_scale_x")) {
139 position_scale_millis_.reset(new std::pair<int, int>(wml::get_int(node, "position_scale_x"), wml::get_int(node, "position_scale_y")));
140 }
141
142 vars_->read(node->get_child("vars"));
143
144 unsigned int solid_dim = type_->solid_dimensions();
145 unsigned int weak_solid_dim = type_->weak_solid_dimensions();
146 unsigned int collide_dim = type_->collide_dimensions();
147 unsigned int weak_collide_dim = type_->weak_collide_dimensions();
148
149 if(node->has_attr("solid_dim")) {
150 weak_solid_dim = solid_dim = 0;
151 std::vector<std::string> solid_dim_str = util::split(node->attr("solid_dim"));
152 foreach(const std::string& str, solid_dim_str) {
153 if(str.empty() || str == "level_only") {
154 continue;
155 }
156
157 if(str[0] == '~') {
158 const int id = get_solid_dimension_id(std::string(str.begin() + 1, str.end()));
159 weak_solid_dim |= 1 << id;
160 } else {
161 const int id = get_solid_dimension_id(str);
162 solid_dim |= 1 << id;
163 }
164 }
165 }
166
167 if(node->has_attr("collide_dim")) {
168 weak_collide_dim = collide_dim = 0;
169 std::vector<std::string> collide_dim_str = util::split(node->attr("collide_dim"));
170 foreach(const std::string& str, collide_dim_str) {
171 if(str.empty() || str == "level_only") {
172 continue;
173 }
174
175 if(str[0] == '~') {
176 const int id = get_solid_dimension_id(std::string(str.begin() + 1, str.end()));
177 weak_collide_dim |= 1 << id;
178 } else {
179 const int id = get_solid_dimension_id(str);
180 collide_dim |= 1 << id;
181 }
182 }
183 }
184
185 set_solid_dimensions(solid_dim, weak_solid_dim);
186 set_collide_dimensions(collide_dim, weak_collide_dim);
187
188 wml::const_node_ptr tags_node = node->get_child("tags");
189 if(tags_node) {
190 tags_ = new game_logic::map_formula_callable(node->get_child("tags"));
191 } else {
192 tags_ = new game_logic::map_formula_callable(type_->tags());
193 }
194
195 if(node->has_attr("draw_color")) {
196 draw_color_.reset(new graphics::color_transform(node->attr("draw_color")));
197 }
198
199 if(node->has_attr("label")) {
200 set_label(node->attr("label"));
201 } else {
202 set_distinct_label();
203 }
204
205 if(!type_->respawns()) {
206 set_respawn(false);
207 }
208
209 assert(type_.get());
210 set_frame_no_adjustments(frame_name_);
211
212 next_animation_formula_ = type_->next_animation_formula();
213
214 custom_object_type::init_event_handlers(node, event_handlers_);
215
216 can_interact_with_ = get_event_handler(OBJECT_EVENT_INTERACT).get() != NULL;
217
218 wml::const_node_ptr text_node = node->get_child("text");
219 if(text_node) {
220 set_text(text_node->attr("text"), text_node->attr("font"), wml::get_int(text_node, "size", 2));
221 }
222
223 if(node->has_attr("particles")) {
224 std::vector<std::string> particles = util::split(node->attr("particles"));
225 foreach(const std::string& p, particles) {
226 add_particle_system(p, p);
227 }
228 }
229 }
230
231 custom_object::custom_object(const std::string& type, int x, int y, bool face_right)
232 : entity(x, y, face_right),
233 previous_y_(y),
234 type_(custom_object_type::get(type)),
235 base_type_(type_),
236 frame_(&type_->default_frame()),
237 frame_name_("normal"),
238 time_in_frame_(0), time_in_frame_delta_(1),
239 velocity_x_(0), velocity_y_(0),
240 accel_x_(0), accel_y_(0),
241 rotate_(0), zorder_(type_->zorder()),
242 hitpoints_(type_->hitpoints()),
243 max_hitpoints_(type_->hitpoints()),
244 was_underwater_(false),
245 has_feet_(type_->has_feet()),
246 invincible_(0),
247 sound_volume_(128),
248 vars_(new game_logic::formula_variable_storage(type_->variables())),
249 tmp_vars_(new game_logic::formula_variable_storage(type_->tmp_variables())),
250 tags_(new game_logic::map_formula_callable(type_->tags())),
251 last_hit_by_anim_(0),
252 cycle_(0),
253 loaded_(false), fall_through_platforms_(0),
254 shader_(0),
255 always_active_(false),
256 last_cycle_active_(0)
257 {
258 set_solid_dimensions(type_->solid_dimensions(),
259 type_->weak_solid_dimensions());
260 set_collide_dimensions(type_->collide_dimensions(),
261 type_->weak_collide_dimensions());
262
263 {
264 //generate a random label for the object
265 char buf[64];
266 sprintf(buf, "_%x", rand());
267 set_label(buf);
268 }
269
270 assert(type_.get());
271 set_frame_no_adjustments(frame_name_);
272
273 next_animation_formula_ = type_->next_animation_formula();
274 }
275
276 custom_object::custom_object(const custom_object& o) :
277 entity(o),
278 previous_y_(o.previous_y_),
279 custom_type_(o.custom_type_),
280 type_(o.type_),
281 base_type_(o.base_type_),
282 current_variation_(o.current_variation_),
283 frame_(o.frame_),
284 frame_name_(o.frame_name_),
285 time_in_frame_(o.time_in_frame_),
286 time_in_frame_delta_(o.time_in_frame_delta_),
287 velocity_x_(o.velocity_x_), velocity_y_(o.velocity_y_),
288 accel_x_(o.accel_x_), accel_y_(o.accel_y_),
289 rotate_(o.rotate_),
290 zorder_(o.zorder_),
291 hitpoints_(o.hitpoints_),
292 max_hitpoints_(o.max_hitpoints_),
293 was_underwater_(o.was_underwater_),
294 has_feet_(o.has_feet_),
295 invincible_(o.invincible_),
296 sound_volume_(o.sound_volume_),
297 next_animation_formula_(o.next_animation_formula_),
298
299 vars_(new game_logic::formula_variable_storage(*o.vars_)),
300 tmp_vars_(new game_logic::formula_variable_storage(*o.tmp_vars_)),
301 tags_(new game_logic::map_formula_callable(*o.tags_)),
302
303 last_hit_by_(o.last_hit_by_),
304 last_hit_by_anim_(o.last_hit_by_anim_),
305 current_animation_id_(o.current_animation_id_),
306 cycle_(o.cycle_),
307 loaded_(o.loaded_),
308 event_handlers_(o.event_handlers_),
309 standing_on_(o.standing_on_),
310 standing_on_prev_x_(o.standing_on_prev_x_), standing_on_prev_y_(o.standing_on_prev_y_),
311 distortion_(o.distortion_),
312 draw_color_(o.draw_color_ ? new graphics::color_transform(*o.draw_color_) : NULL),
313 can_interact_with_(o.can_interact_with_),
314 particle_systems_(o.particle_systems_),
315 text_(o.text_),
316 driver_(o.driver_),
317 blur_(o.blur_),
318 fall_through_platforms_(o.fall_through_platforms_),
319 fragment_shaders_(o.fragment_shaders_),
320 vertex_shaders_(o.vertex_shaders_),
321 shader_(o.shader_),
322 always_active_(o.always_active_),
323 last_cycle_active_(0)
324 {
325 }
326
327 custom_object::~custom_object()
328 {
329 sound::stop_looped_sounds(this);
330 //std::cerr << "Object died:" << type_->id() << " " << this << " \n";
331 }
332
333 wml::node_ptr custom_object::write() const
334 {
335 wml::node_ptr res(new wml::node("character"));
336 if(position_scale_millis_.get() != NULL) {
337 res->set_attr("position_scale_x", formatter() << position_scale_millis_->first);
338 res->set_attr("position_scale_y", formatter() << position_scale_millis_->second);
339 }
340
341 if(position_schedule_.get() != NULL) {
342 res->set_attr("schedule_speed", formatter() << position_schedule_->speed);
343 if(position_schedule_->x_pos.empty() == false) {
344 res->set_attr("x_schedule", util::join_ints(&position_schedule_->x_pos[0], position_schedule_->x_pos.size()));
345 }
346
347 if(position_schedule_->y_pos.empty() == false) {
348 res->set_attr("y_schedule", util::join_ints(&position_schedule_->y_pos[0], position_schedule_->y_pos.size()));
349 }
350
351 if(position_schedule_->rotation.empty() == false) {
352 res->set_attr("rotation_schedule", util::join_ints(&position_schedule_->rotation[0], position_schedule_->rotation.size()));
353 }
354 }
355
356 if(!attached_objects().empty()) {
357 std::string s;
358
359 foreach(const entity_ptr& e, attached_objects()) {
360 char buf[256];
361 sprintf(buf, "%p", e.get());
362 s += buf;
363 }
364
365 res->set_attr("attached_objects", s);
366 }
367
368 if(!current_variation_.empty()) {
369 res->set_attr("variations", util::join(current_variation_));
370 }
371
372 if(draw_color_) {
373 res->set_attr("draw_color", draw_color_->to_string());
374 }
375
376 if(label().empty() == false) {
377 res->set_attr("label", label());
378 }
379
380 if(cycle_ > 1) {
381 res->set_attr("cycle", formatter() << cycle_);
382 }
383
384 if(frame_name_ != "default") {
385 res->set_attr("current_frame", frame_name_);
386 }
387
388 res->set_attr("custom", "yes");
389 res->set_attr("type", type_->id());
390 res->set_attr("x", formatter() << x());
391 res->set_attr("y", formatter() << y());
392 res->set_attr("velocity_x", formatter() << velocity_x_);
393 res->set_attr("velocity_y", formatter() << velocity_y_);
394
395 if(solid_dimensions() != type_->solid_dimensions() ||
396 weak_solid_dimensions() != type_->weak_solid_dimensions()) {
397 std::string solid_dim;
398 for(int n = 0; n != 32; ++n) {
399 if(solid_dimensions()&(1 << n)) {
400 if(!solid_dim.empty()) {
401 solid_dim += ",";
402 }
403
404 solid_dim += get_solid_dimension_key(n);
405 }
406
407 if(weak_solid_dimensions()&(1 << n)) {
408 if(!solid_dim.empty()) {
409 solid_dim += ",";
410 }
411
412 solid_dim += "~" + get_solid_dimension_key(n);
413 }
414 }
415
416 if(solid_dim.empty()) {
417 solid_dim = "level_only";
418 }
419
420 res->set_attr("solid_dim", solid_dim);
421 }
422
423 if(collide_dimensions() != type_->collide_dimensions() ||
424 weak_collide_dimensions() != type_->weak_collide_dimensions()) {
425 std::string collide_dim, weak_collide_dim;
426 for(int n = 0; n != 32; ++n) {
427 if(collide_dimensions()&(1 << n)) {
428 if(!collide_dim.empty()) {
429 collide_dim += ",";
430 }
431
432 collide_dim += get_solid_dimension_key(n);
433 }
434
435 if(weak_collide_dimensions()&(1 << n)) {
436 if(!collide_dim.empty()) {
437 collide_dim += ",";
438 }
439
440 collide_dim += "~" + get_solid_dimension_key(n);
441 }
442 }
443
444 if(collide_dim.empty()) {
445 collide_dim = "level_only";
446 }
447
448 res->set_attr("collide_dim", collide_dim);
449 }
450
451 if(hitpoints_ != type_->hitpoints() || max_hitpoints_ != type_->hitpoints()) {
452 res->set_attr("hitpoints", formatter() << hitpoints_);
453 res->set_attr("max_hitpoints", formatter() << max_hitpoints_);
454 }
455
456 if(!vertex_shaders_.empty()) {
457 res->set_attr("vertex_shaders", util::join(vertex_shaders_));
458 }
459
460 if(!fragment_shaders_.empty()) {
461 res->set_attr("fragment_shaders", util::join(fragment_shaders_));
462 }
463
464 if(zorder_ != type_->zorder()) {
465 res->set_attr("zorder", formatter() << zorder_);
466 }
467
468 res->set_attr("face_right", face_right() ? "yes" : "no");
469 if(upside_down()) {
470 res->set_attr("upside_down", "yes");
471 }
472
473 res->set_attr("time_in_frame", formatter() << time_in_frame_);
474
475 if(time_in_frame_delta_ != 1) {
476 res->set_attr("time_in_frame_delta", formatter() << time_in_frame_delta_);
477 }
478
479 if(has_feet_ != type_->has_feet()) {
480 res->set_attr("has_feet", formatter() << (has_feet_ ? "yes" : "no"));
481 }
482
483 if(group() >= 0) {
484 res->set_attr("group", formatter() << group());
485 }
486
487 for(int n = 0; n != event_handlers_.size(); ++n) {
488 if(!event_handlers_[n]) {
489 continue;
490 }
491
492 res->set_attr("on_" + get_object_event_str(n), event_handlers_[n]->str());
493 }
494
495 if(!vars_->equal_to(type_->variables())) {
496 wml::node_ptr vars(new wml::node("vars"));
497 vars_->write(vars);
498 res->add_child(vars);
499 }
500
501 if(tags_->values() != type_->tags()) {
502 wml::node_ptr tags(new wml::node("tags"));
503 tags_->write(tags);
504 res->add_child(tags);
505 }
506
507 if(custom_type_) {
508 res->add_child(wml::deep_copy(custom_type_));
509 }
510
511 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
512 if(editor_info()) {
513 res->add_child(editor_info()->write());
514 }
515 #endif
516
517 if(text_) {
518 wml::node_ptr node(new wml::node("text"));
519 node->set_attr("text", text_->text);
520 if(text_->font) {
521 node->set_attr("font", text_->font->id());
522 }
523
524 node->set_attr("size", formatter() << text_->size);
525
526 res->add_child(node);
527 }
528
529 if(draw_area_) {
530 res->set_attr("draw_area", draw_area_->to_string());
531 }
532
533 if(activation_area_) {
534 res->set_attr("activation_area", activation_area_->to_string());
535 }
536
537 if(!particle_systems_.empty()) {
538 std::string systems;
539 for(std::map<std::string, particle_system_ptr>::const_iterator i = particle_systems_.begin(); i != particle_systems_.end(); ++i) {
540 if(i->second->should_save() == false) {
541 continue;
542 }
543
544 if(!systems.empty()) {
545 systems += ",";
546 }
547
548 systems += i->first;
549 }
550
551 if(!systems.empty()) {
552 res->set_attr("particles", systems);
553 }
554 }
555
556 return res;
557 }
558
559 void custom_object::setup_drawing() const
560 {
561 if(distortion_) {
562 graphics::add_raster_distortion(distortion_.get());
563 }
564 }
565
566 void custom_object::draw() const
567 {
568 if(frame_ == NULL) {
569 return;
570 }
571
572 if(shader_ == 0 && !fragment_shaders_.empty() && !vertex_shaders_.empty()) {
573 shader_ = get_gl_shader(vertex_shaders_, fragment_shaders_);
574 }
575
576 #ifndef SDL_VIDEO_OPENGL_ES
577 if(shader_) {
578 glUseProgram(shader_);
579
580 if(shader_vars_) {
581 for(game_logic::map_formula_callable::const_iterator i = shader_vars_->begin(); i != shader_vars_->end(); ++i) {
582 GLuint id = glGetUniformLocation(shader_, i->first.c_str());
583 glUniform1f(id, i->second.as_int()/1000.0);
584 }
585 }
586 }
587 #endif
588
589 if(driver_) {
590 driver_->draw();
591 }
592
593 foreach(const entity_ptr& attached, attached_objects()) {
594 attached->draw();
595 }
596
597 if(draw_color_) {
598 draw_color_->to_color().set_as_current_color();
599 }
600
601 const int draw_x = x();
602 const int draw_y = y();
603
604 if(!draw_area_.get()) {
605 frame_->draw(draw_x, draw_y, face_right(), upside_down(), time_in_frame_, rotate_);
606 } else {
607 frame_->draw(draw_x, draw_y, *draw_area_, face_right(), upside_down(), time_in_frame_, rotate_);
608 }
609
610 if(blur_) {
611 blur_->draw();
612 }
613
614 if(draw_color_) {
615 if(!draw_color_->fits_in_color()) {
616 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
617 graphics::color_transform transform = *draw_color_;
618 while(!transform.fits_in_color()) {
619 transform = transform - transform.to_color();
620 transform.to_color().set_as_current_color();
621 frame_->draw(draw_x, draw_y, face_right(), upside_down(), time_in_frame_, rotate_);
622 }
623
624 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
625 }
626
627 glColor4ub(255, 255, 255, 255);
628 }
629
630 // if(draw_color_int_ != DefaultColor) {
631 // glColor4ub(255, 255, 255, 255);
632 // }
633
634 draw_debug_rects();
635
636 for(std::map<std::string, particle_system_ptr>::const_iterator i = particle_systems_.begin(); i != particle_systems_.end(); ++i) {
637 i->second->draw(rect(last_draw_position().x/100, last_draw_position().y/100, graphics::screen_width(), graphics::screen_height()), *this);
638 }
639
640 if(text_ && text_->font && text_->alpha) {
641 glColor4ub(255, 255, 255, text_->alpha);
642 text_->font->draw(draw_x, draw_y, text_->text, text_->size);
643 glColor4ub(255, 255, 255, 255);
644 }
645
646 #ifndef SDL_VIDEO_OPENGL_ES
647 if(shader_) {
648 glUseProgram(0);
649 }
650 #endif
651 }
652
653 void custom_object::draw_group() const
654 {
655 if(label().empty() == false && label()[0] != '_') {
656 blit_texture(font::render_text(label(), graphics::color_yellow(), 32), x(), y() + 26);
657 }
658
659 if(group() >= 0) {
660 blit_texture(font::render_text(formatter() << group(), graphics::color_yellow(), 24), x(), y());
661 }
662 }
663
664 void custom_object::process(level& lvl)
665 {
666 if(type_->use_image_for_collisions()) {
667 //anything that uses their image for collisions is a static,
668 //un-moving object that will stay immobile.
669 return;
670 }
671
672 if(last_cycle_active_ < lvl.cycle() - 5) {
673 handle_event(OBJECT_EVENT_BECOME_ACTIVE);
674 }
675
676 last_cycle_active_ = lvl.cycle();
677
678 entity::process(lvl);
679
680 //the object should never be colliding with the level at the start of processing.
681 // assert(!entity_collides_with_level(lvl, *this, MOVE_NONE));
682 // assert(!entity_collides(lvl, *this, MOVE_NONE));
683
684 collision_info stand_info;
685 const bool started_standing = is_standing(lvl, &stand_info);
686 if(!started_standing && standing_on_) {
687 //if we were standing on something the previous frame, but aren't
688 //standing any longer, we use the value of what we were previously
689 //standing on.
690 stand_info.traction = standing_on_->surface_traction();
691 stand_info.friction = standing_on_->surface_friction();
692 }
693
694 if(y() > lvl.boundaries().y2()) {
695 --hitpoints_;
696 }
697
698 previous_y_ = y();
699 if(started_standing && velocity_y_ > 0) {
700 velocity_y_ = 0;
701 }
702
703 const int start_x = x();
704 const int start_y = y();
705 ++cycle_;
706
707 if(invincible_) {
708 --invincible_;
709 }
710
711 if(!loaded_) {
712 handle_event(OBJECT_EVENT_LOAD);
713 loaded_ = true;
714 }
715
716 if(cycle_ == 1) {
717 handle_event(OBJECT_EVENT_CREATE);
718 handle_event(OBJECT_EVENT_DONE_CREATE);
719 }
720
721 variant scheduled_command = get_scheduled_command(lvl.cycle());
722 while(!scheduled_command.is_null()) {
723 execute_command(scheduled_command);
724 scheduled_command = get_scheduled_command(lvl.cycle());
725 }
726
727 if(position_schedule_.get() != NULL) {
728 const int pos = cycle_/position_schedule_->speed;
729 const int next_fraction = cycle_%position_schedule_->speed;
730 const int this_fraction = position_schedule_->speed - next_fraction;
731
732 int xpos = INT_MIN, ypos = INT_MIN;
733 if(position_schedule_->x_pos.empty() == false) {
734 xpos = position_schedule_->x_pos[pos%position_schedule_->x_pos.size()];
735 if(next_fraction) {
736 xpos = (xpos*this_fraction + next_fraction*position_schedule_->x_pos[(pos+1)%position_schedule_->x_pos.size()])/position_schedule_->speed;
737 }
738 }
739
740 if(position_schedule_->y_pos.empty() == false) {
741 ypos = position_schedule_->y_pos[pos%position_schedule_->y_pos.size()];
742 if(next_fraction) {
743 ypos = (ypos*this_fraction + next_fraction*position_schedule_->y_pos[(pos+1)%position_schedule_->y_pos.size()])/position_schedule_->speed;
744 }
745 }
746
747 if(xpos != INT_MIN && ypos != INT_MIN) {
748 set_pos(xpos, ypos);
749 } else if(xpos != INT_MIN) {
750 set_x(xpos);
751 } else if(ypos != INT_MIN) {
752 set_y(ypos);
753 }
754
755 if(position_schedule_->rotation.empty() == false) {
756 rotate_ = position_schedule_->rotation[pos%position_schedule_->rotation.size()];
757 if(next_fraction) {
758 rotate_ = (rotate_*this_fraction + next_fraction*position_schedule_->rotation[(pos+1)%position_schedule_->rotation.size()])/position_schedule_->speed;
759 }
760 }
761 }
762
763 if(stand_info.damage) {
764 handle_event(OBJECT_EVENT_SURFACE_DAMAGE);
765 }
766
767 time_in_frame_ += time_in_frame_delta_;
768 if(time_in_frame_ < 0) {
769 time_in_frame_ = 0;
770 }
771
772 if(time_in_frame_ > frame_->duration()) {
773 time_in_frame_ = frame_->duration();
774 }
775
776 if(time_in_frame_ == frame_->duration()) {
777 handle_event(frame_->end_event_id());
778 handle_event(OBJECT_EVENT_END_ANIM);
779 if(next_animation_formula_) {
780 variant var = next_animation_formula_->execute(*this);
781 set_frame(var.as_string());
782 }
783 }
784
785 const std::string* event = frame_->get_event(time_in_frame_);
786 if(event) {
787 handle_event(*event);
788 }
789
790 const bool is_underwater = solid() && lvl.is_underwater(solid_rect());
791
792 if( is_underwater && !was_underwater_){
793 //event on_enter_water
794 handle_event(OBJECT_EVENT_ENTER_WATER);
795 was_underwater_ = true;
796 }else if ( !is_underwater && was_underwater_ ){
797 //event on_exit_water
798 handle_event(OBJECT_EVENT_EXIT_WATER);
799 was_underwater_ = false;
800 }
801
802 const int traction_from_surface = (stand_info.traction*type_->traction())/1000;
803 velocity_x_ += (accel_x_ * (stand_info.traction ? traction_from_surface : (is_underwater?type_->traction_in_water() :type_->traction_in_air())) * (face_right() ? 1 : -1))/1000;
804 if(!standing_on_ && !started_standing || accel_y_ < 0) {
805 //do not accelerate downwards if standing on something.
806 velocity_y_ += accel_y_ * (is_underwater ? type_->traction_in_water() : 1000)/1000;
807 }
808
809 if(type_->friction()) {
810
811 const int air_resistance = is_underwater ? lvl.water_resistance() : lvl.air_resistance();
812
813 const int friction = ((stand_info.friction + air_resistance)*type_->friction())/1000;
814 int vertical_resistance = (air_resistance*type_->friction())/1000;
815 if(velocity_y_ > 0 && !is_underwater) {
816 //vertical air resistance is reduced when moving downwards.
817 //This works well for most objects, though consider making it
818 //configurable in future.
819 vertical_resistance /= 2;
820 }
821
822 velocity_x_ = (velocity_x_*(1000 - friction))/1000;
823 velocity_y_ = (velocity_y_*(1000 - vertical_resistance))/1000;
824 }
825
826 if(type_->affected_by_currents()) {
827 lvl.get_current(*this, &velocity_x_, &velocity_y_);
828 }
829
830 bool collide = false;
831
832 //calculate velocity which takes into account velocity of the object we're standing on.
833 int effective_velocity_x = velocity_x_;
834 int effective_velocity_y = velocity_y_;
835
836 if(effective_velocity_y > 0 && (standing_on_ || started_standing)) {
837 effective_velocity_y = 0;
838 }
839
840 if(standing_on_) {
841 effective_velocity_x += (standing_on_->feet_x() - standing_on_prev_x_)*100;
842 effective_velocity_y += (standing_on_->feet_y() - standing_on_prev_y_)*100;
843 }
844
845 if(stand_info.collide_with != standing_on_ && stand_info.adjust_y) {
846 //if we're landing on a new platform, we might have to adjust our
847 //y position to suit its last movement and put us on top of
848 //the platform.
849
850 effective_velocity_y -= stand_info.adjust_y*100;
851 }
852
853 if(effective_velocity_x || effective_velocity_y) {
854 if(!solid() && !type_->object_level_collisions()) {
855 move_centipixels(effective_velocity_x, effective_velocity_y);
856 effective_velocity_x = 0;
857 effective_velocity_y = 0;
858 } else if(!has_feet() && solid()) {
859 move_centipixels(effective_velocity_x, effective_velocity_y);
860 if(is_flightpath_clear(lvl, *this, solid_rect())) {
861 effective_velocity_x = 0;
862 effective_velocity_y = 0;
863 } else {
864 //we can't guarantee smooth movement to this location, so
865 //roll the move back and we'll do a pixel-by-pixel move
866 //until we collide.
867 move_centipixels(-effective_velocity_x, -effective_velocity_y);
868 }
869 }
870 }
871
872 collision_info collide_info;
873 collision_info jump_on_info;
874
875 bool is_stuck = false;
876
877 //std::cerr << "velocity_y: " << velocity_y_ << "\n";
878 collide = false;
879 int move_left;
880 for(move_left = std::abs(effective_velocity_y); move_left > 0 && !collide && !type_->ignore_collide(); move_left -= 100) {
881 const int dir = effective_velocity_y > 0 ? 1 : -1;
882 int damage = 0;
883
884 const int original_centi_y = centi_y();
885
886 const int move_amount = std::min(std::max(move_left, 0), 100);
887
888 const bool moved = move_centipixels(0, move_amount*dir);
889 if(!moved) {
890 //we didn't actually move any pixels, so just abort.
891 break;
892 }
893
894 if(type_->object_level_collisions() && non_solid_entity_collides_with_level(lvl, *this)) {
895 handle_event(OBJECT_EVENT_COLLIDE_LEVEL);
896 }
897
898 if(effective_velocity_y > 0) {
899 if(entity_collides(lvl, *this, MOVE_DOWN, &collide_info)) {
900 //our 'legs' but not our feet collide with the level. Try to
901 //move one pixel to the left or right and see if either
902 //direction makes us no longer colliding.
903 set_x(x() + 1);
904 if(entity_collides(lvl, *this, MOVE_DOWN) || entity_collides(lvl, *this, MOVE_RIGHT)) {
905 set_x(x() - 2);
906 if(entity_collides(lvl, *this, MOVE_DOWN) || entity_collides(lvl, *this, MOVE_LEFT)) {
907 //moving in either direction fails to resolve the collision.
908 //This effectively means the object is 'stuck' in a small
909 //pit.
910 set_x(x() + 1);
911 move_centipixels(0, -move_amount*dir);
912 collide = true;
913 is_stuck = true;
914 break;
915 }
916 }
917
918
919 }
920 } else {
921 //effective_velocity_y < 0 -- going up
922 if(entity_collides(lvl, *this, MOVE_UP, &collide_info)) {
923 collide = true;
924 move_centipixels(0, -move_amount*dir);
925 break;
926 }
927 }
928
929 if(!collide && !type_->ignore_collide() && effective_velocity_y > 0 && is_standing(lvl, &jump_on_info)) {
930 if(!jump_on_info.collide_with || jump_on_info.collide_with != standing_on_) {
931 collide = true;
932 collide_info = jump_on_info;
933 }
934
935 break;
936 }
937
938 if(collide) {
939 std::cerr << "collide y!\n";
940 break;
941 }
942 }
943
944 //this variable handled whether we already landed in our vertical movement
945 //in which case horizontal movement won't consider us to land.
946 bool vertical_landed = false;
947
948 if(is_stuck) {
949 handle_event(OBJECT_EVENT_STUCK);
950 }
951
952 if(collide) {
953 if(effective_velocity_y > 0) {
954 vertical_landed = true;
955 }
956
957 if(effective_velocity_y < 0 || !started_standing) {
958
959 game_logic::map_formula_callable* callable = new game_logic::map_formula_callable;
960 variant v(callable);
961
962 if(collide_info.area_id != NULL) {
963 callable->add("area", variant(*collide_info.area_id));
964 }
965
966 if(collide_info.collide_with) {
967 callable->add("collide_with", variant(collide_info.collide_with.get()));
968 if(collide_info.collide_with_area_id) {
969 callable->add("collide_with_area", variant(*collide_info.collide_with_area_id));
970 }
971
972 }
973
974 handle_event(effective_velocity_y < 0 ? OBJECT_EVENT_COLLIDE_HEAD : OBJECT_EVENT_COLLIDE_FEET, callable);
975 }
976
977 if(collide_info.damage || jump_on_info.damage) {
978 game_logic::map_formula_callable* callable = new game_logic::map_formula_callable;
979 callable->add("surface_damage", variant(std::max(collide_info.damage, jump_on_info.damage)));
980 variant v(callable);
981 handle_event(OBJECT_EVENT_COLLIDE_DAMAGE, callable);
982 }
983 }
984
985 collide = false;
986
987 bool horizontal_landed = false;
988
989 //we go through up to two passes of moving an object horizontally. On the
990 //first pass, we are 'optimistic' and move the object along, assuming there
991 //will be no collisions. Then at the end of the pass we see if the object is
992 //colliding. If it's not, all is good, but if it is, we'll re-do the movement,
993 //detecting for collisions at each step, until we work out where exactly
994 //the collision occurs, and stop the object there.
995 for(int detect_collisions = 0; detect_collisions <= 1 && effective_velocity_x; ++detect_collisions) {
996 const int backup_centi_x = centi_x();
997 const int backup_centi_y = centi_y();
998
999
1000 for(move_left = std::abs(effective_velocity_x); move_left > 0 && !collide && !type_->ignore_collide(); move_left -= 100) {
1001 if(type_->object_level_collisions() && non_solid_entity_collides_with_level(lvl, *this)) {
1002 handle_event(OBJECT_EVENT_COLLIDE_LEVEL);
1003 }
1004
1005 const bool previous_standing = is_standing(lvl);
1006
1007 const int dir = effective_velocity_x > 0 ? 1 : -1;
1008 const int original_centi_y = centi_y();
1009
1010 const int move_amount = std::min(std::max(move_left, 0), 100);
1011
1012 const bool moved = move_centipixels(move_amount*dir, 0);
1013 if(!moved) {
1014 //we didn't actually move any pixels, so just abort.
1015 break;
1016 }
1017
1018 //if we go up or down a slope, and we began the frame standing,
1019 //move the character up or down as appropriate to try to keep
1020 //them standing.
1021
1022 const bool standing = is_standing(lvl);
1023 if(previous_standing && !standing) {
1024
1025 //we were standing, but we're not now. We want to look for
1026 //slopes that will enable us to still be standing. We see
1027 //if the object is trying to walk down stairs, in which case
1028 //we look downwards first, otherwise we look upwards first,
1029 //then downwards.
1030 int dir = walk_up_or_down_stairs() > 0 ? 1 : -1;
1031
1032 for(int tries = 0; tries != 2; ++tries) {
1033 bool resolved = false;
1034 const int SearchRange = 2;
1035 for(int n = 0; n != SearchRange; ++n) {
1036 set_y(y()+dir);
1037 if(detect_collisions && entity_collides(lvl, *this, dir < 0 ? MOVE_UP : MOVE_DOWN)) {
1038 break;
1039 }
1040
1041 if(is_standing(lvl)) {
1042 resolved = true;
1043 break;
1044 }
1045 }
1046
1047 if(resolved) {
1048 break;
1049 }
1050
1051 dir *= -1;
1052 set_centi_y(original_centi_y);
1053 }
1054 } else if(standing) {
1055 if(!vertical_landed && !started_standing && !standing_on_) {
1056 horizontal_landed = true;
1057 }
1058
1059 collision_info slope_standing_info;
1060
1061 bool collide_head = false;
1062
1063 //we are standing, but we need to see if we should be standing
1064 //on a higher point. If there are solid points immediately above
1065 //where we are, we adjust our feet to be on them.
1066 //
1067 //However, if there is a platform immediately above us, we only
1068 //adjust our feet upward if the object is trying to walk up
1069 //stairs, normally by the player pressing up while walking.
1070 const int begin_y = feet_y();
1071 int max_slope = 5;
1072 while(--max_slope && is_standing(lvl, &slope_standing_info)) {
1073 if(slope_standing_info.platform && walk_up_or_down_stairs() >= 0) {
1074 if(max_slope == 4) {
1075 //we always move at least one pixel up, if there is
1076 //solid, otherwise we'll fall through.
1077 set_y(y()-1);
1078 if(detect_collisions && entity_collides(lvl, *this, MOVE_UP)) {
1079 collide_head = true;
1080 break;
1081 }
1082 }
1083 break;
1084 }
1085
1086 set_y(y()-1);
1087 if(detect_collisions && entity_collides(lvl, *this, MOVE_UP)) {
1088 collide_head = true;
1089 break;
1090 }
1091 }
1092
1093 if(!max_slope || collide_head) {
1094 set_centi_y(original_centi_y);
1095 } else {
1096 set_y(y()+1);
1097 }
1098
1099 if(walk_up_or_down_stairs() > 0) {
1100 //if we are trying to walk down stairs and we're on a platform
1101 //and one pixel below is walkable, then we move down by
1102 //one pixel.
1103 is_standing(lvl, &slope_standing_info);
1104 std::cerr << "TRYING TO MOVE DOWN\n";
1105 if(slope_standing_info.platform) {
1106 set_y(y()+1);
1107 if(!is_standing(lvl) || detect_collisions && entity_collides(lvl, *this, MOVE_DOWN)) {
1108 set_y(y()-1);
1109 }
1110 }
1111 }
1112 }
1113
1114 if(detect_collisions && entity_collides(lvl, *this, centi_y() != original_centi_y ? MOVE_NONE : (dir > 0 ? MOVE_RIGHT : MOVE_LEFT), &collide_info)) {
1115 collide = true;
1116 }
1117
1118 if(collide) {
1119 //undo the move to cancel out the collision
1120 move_centipixels(-dir*move_amount, 0);
1121 set_centi_y(original_centi_y);
1122 break;
1123 }
1124 }
1125
1126 if(!detect_collisions) {
1127 if(entity_collides(lvl, *this, MOVE_NONE)) {
1128 set_centi_x(backup_centi_x);
1129 set_centi_y(backup_centi_y);
1130 } else {
1131 break;
1132 }
1133 }
1134 }
1135
1136 if(collide || horizontal_landed) {
1137
1138 game_logic::map_formula_callable* callable = new game_logic::map_formula_callable;
1139 variant v(callable);
1140
1141 if(collide_info.area_id != NULL) {
1142 callable->add("area", variant(*collide_info.area_id));
1143 }
1144
1145 if(collide_info.collide_with) {
1146 callable->add("collide_with", variant(collide_info.collide_with.get()));
1147 if(collide_info.collide_with_area_id) {
1148 callable->add("collide_with_area", variant(*collide_info.collide_with_area_id));
1149 }
1150 }
1151
1152 handle_event(collide ? OBJECT_EVENT_COLLIDE : OBJECT_EVENT_COLLIDE_FEET, callable);
1153 if(collide_info.damage) {
1154 game_logic::map_formula_callable* callable = new game_logic::map_formula_callable;
1155 callable->add("surface_damage", variant(collide_info.damage));
1156 variant v(callable);
1157 handle_event(OBJECT_EVENT_COLLIDE_DAMAGE, callable);
1158 }
1159 }
1160
1161 stand_info = collision_info();
1162 if(velocity_y_ >= 0) {
1163 is_standing(lvl, &stand_info);
1164 }
1165
1166 if(stand_info.collide_with && standing_on_ != stand_info.collide_with &&
1167 effective_velocity_y < stand_info.collide_with->velocity_y()) {
1168 stand_info.collide_with = NULL;
1169 }
1170
1171 if(standing_on_ && standing_on_ != stand_info.collide_with) {
1172 //we were previously standing on an object and we're not anymore.
1173 //add the object we were standing on's velocity to ours
1174 velocity_x_ += standing_on_->last_move_x()*100;
1175 velocity_y_ += standing_on_->last_move_y()*100;
1176 }
1177
1178 if(stand_info.collide_with && standing_on_ != stand_info.collide_with) {
1179 //we are standing on a new object. Adjust our velocity relative to
1180 //the object we're standing on
1181 velocity_x_ -= stand_info.collide_with->last_move_x()*100;
1182 velocity_y_ = 0;
1183
1184 game_logic::map_formula_callable* callable(new game_logic::map_formula_callable(this));
1185 callable->add("jumped_on_by", variant(this));
1186 game_logic::formula_callable_ptr callable_ptr(callable);
1187
1188 stand_info.collide_with->handle_event(OBJECT_EVENT_JUMPED_ON, callable);
1189 }
1190
1191 standing_on_ = stand_info.collide_with;
1192 if(standing_on_) {
1193 standing_on_prev_x_ = standing_on_->feet_x();
1194 standing_on_prev_y_ = standing_on_->feet_y();
1195 }
1196
1197 if(lvl.players().empty() == false) {
1198 lvl.set_touched_player(lvl.players().front());
1199 }
1200
1201 if(fall_through_platforms_ > 0) {
1202 --fall_through_platforms_;
1203 }
1204
1205 handle_event(OBJECT_EVENT_PROCESS);
1206 handle_event(frame_->process_event_id());
1207
1208 if(type_->timer_frequency() > 0 && (cycle_%type_->timer_frequency()) == 0) {
1209 static const std::string TimerStr = "timer";
1210 handle_event(OBJECT_EVENT_TIMER);
1211 }
1212
1213 for(std::map<std::string, particle_system_ptr>::iterator i = particle_systems_.begin(); i != particle_systems_.end(); ) {
1214 i->second->process(lvl, *this);
1215 if(i->second->is_destroyed()) {
1216 particle_systems_.erase(i++);
1217 } else {
1218 ++i;
1219 }
1220 }
1221
1222 set_driver_position();
1223
1224 if(blur_) {
1225 blur_->next_frame(start_x, start_y, x(), y(), frame_, time_in_frame_, face_right(), upside_down(), rotate_);
1226 if(blur_->destroyed()) {
1227 blur_.reset();
1228 }
1229 }
1230 }
1231
1232 void custom_object::set_driver_position()
1233 {
1234 if(driver_) {
1235 const int pos_right = x() + type_->passenger_x();
1236 const int pos_left = x() + current_frame().width() - driver_->current_frame().width() - type_->passenger_x();
1237 driver_->set_face_right(face_right());
1238
1239 driver_->set_pos(face_right() ? pos_right : pos_left, y() + type_->passenger_y());
1240 }
1241 }
1242
1243 const_editor_entity_info_ptr custom_object::editor_info() const
1244 {
1245 return type_->editor_info();
1246 }
1247
1248 int custom_object::zorder() const
1249 {
1250 return zorder_;
1251 }
1252
1253 int custom_object::velocity_x() const
1254 {
1255 return velocity_x_;
1256 }
1257
1258 int custom_object::velocity_y() const
1259 {
1260 return velocity_y_;
1261 }
1262
1263 int custom_object::surface_friction() const
1264 {
1265 return type_->surface_friction();
1266 }
1267
1268 int custom_object::surface_traction() const
1269 {
1270 return type_->surface_traction();
1271 }
1272
1273 bool custom_object::has_feet() const
1274 {
1275 return has_feet_ && solid();
1276 }
1277
1278 bool custom_object::is_standable(int xpos, int ypos, int* friction, int* traction, int* adjust_y) const
1279 {
1280 if(!body_passthrough() && !body_harmful() && point_collides(xpos, ypos)) {
1281 if(friction) {
1282 *friction = type_->surface_friction();
1283 }
1284
1285 if(traction) {
1286 *traction = type_->surface_traction();
1287 }
1288
1289 if(adjust_y) {
1290 if(type_->use_image_for_collisions()) {
1291 for(*adjust_y = 0; point_collides(xpos, ypos - *adjust_y - 1); --(*adjust_y)) {
1292 }
1293 } else {
1294 *adjust_y = ypos - body_rect().y();
1295 }
1296 }
1297
1298 return true;
1299 }
1300
1301 if(frame_->has_platform()) {
1302 const frame& f = *frame_;
1303 int y1 = y() + f.platform_y();
1304 int y2 = previous_y_ + f.platform_y();
1305
1306 if(y1 > y2) {
1307 std::swap(y1, y2);
1308 }
1309
1310 if(ypos < y1 || ypos > y2) {
1311 return false;
1312 }
1313
1314 if(xpos < x() + f.platform_x() || xpos >= x() + f.platform_x() + f.platform_w()) {
1315 return false;
1316 }
1317
1318 if(friction) {
1319 *friction = type_->surface_friction();
1320 }
1321
1322 if(traction) {
1323 *traction = type_->surface_traction();
1324 }
1325
1326 if(adjust_y) {
1327 *adjust_y = y() + f.platform_y() - ypos;
1328 }
1329
1330 return true;
1331 }
1332
1333 return false;
1334 }
1335
1336 bool custom_object::destroyed() const
1337 {
1338 return hitpoints_ <= 0;
1339 }
1340
1341 bool custom_object::point_collides(int xpos, int ypos) const
1342 {
1343 if(type_->use_image_for_collisions()) {
1344 const bool result = !current_frame().is_alpha(xpos - x(), ypos - y(), time_in_frame_, face_right());
1345 return result;
1346 } else {
1347 return point_in_rect(point(xpos, ypos), body_rect());
1348 }
1349 }
1350
1351 bool custom_object::rect_collides(const rect& r) const
1352 {
1353 if(type_->use_image_for_collisions()) {
1354 rect myrect(x(), y(), current_frame().width(), current_frame().height());
1355 if(rects_intersect(myrect, r)) {
1356 rect intersection = intersection_rect(myrect, r);
1357 for(int y = intersection.y(); y < intersection.y2(); ++y) {
1358 for(int x = intersection.x(); x < intersection.x2(); ++x) {
1359 if(point_collides(x, y)) {
1360 return true;
1361 }
1362 }
1363 }
1364
1365 return false;
1366 } else {
1367 return false;
1368 }
1369 } else {
1370 return rects_intersect(r, body_rect());
1371 }
1372 }
1373
1374 const solid_info* custom_object::calculate_solid() const
1375 {
1376 if(!type_->has_solid()) {
1377 return NULL;
1378 }
1379
1380 const frame& f = current_frame();
1381 if(f.solid()) {
1382 return f.solid().get();
1383 }
1384
1385 return type_->solid().get();
1386 }
1387
1388 const solid_info* custom_object::calculate_platform() const
1389 {
1390 return type_->platform().get();
1391 }
1392
1393 void custom_object::control(const level& lvl)
1394 {
1395 }
1396
1397 bool custom_object::is_standing(const level& lvl, collision_info* info) const
1398 {
1399 const bool result = has_feet() &&
1400 point_standable(lvl, *this, feet_x(), feet_y(), info, fall_through_platforms_ ? SOLID_ONLY : SOLID_AND_PLATFORMS);
1401 return result;
1402 }
1403
1404 namespace {
1405
1406 #ifndef DISABLE_FORMULA_PROFILER
1407 using formula_profiler::event_call_stack;
1408 #endif
1409
1410 variant call_stack(const custom_object& obj) {
1411 std::vector<variant> result;
1412
1413 #ifndef DISABLE_FORMULA_PROFILER
1414 for(int n = 0; n != event_call_stack.size(); ++n) {
1415 result.push_back(variant(get_object_event_str(event_call_stack[n].event_id)));
1416 }
1417 #endif
1418
1419 return variant(&result);
1420 }
1421
1422 }
1423
1424 void custom_object::init()
1425 {
1426 }
1427
1428 variant custom_object::get_value_by_slot(int slot) const
1429 {
1430 switch(slot) {
1431 case CUSTOM_OBJECT_CONSTS: return variant(type_->consts().get());
1432 case CUSTOM_OBJECT_TYPE: return variant(type_->id());
1433 case CUSTOM_OBJECT_ACTIVE: return variant(last_cycle_active_ >= level::current().cycle() - 2);
1434 case CUSTOM_OBJECT_TIME_IN_ANIMATION: return variant(time_in_frame_);
1435 case CUSTOM_OBJECT_TIME_IN_ANIMATION_DELTA: return variant(time_in_frame_delta_);
1436 case CUSTOM_OBJECT_LEVEL: return variant(&level::current());
1437 case CUSTOM_OBJECT_ANIMATION: return frame_->variant_id();
1438 case CUSTOM_OBJECT_HITPOINTS: return variant(hitpoints_);
1439 case CUSTOM_OBJECT_MAX_HITPOINTS: return variant(max_hitpoints_);
1440 case CUSTOM_OBJECT_MASS: return variant(type_->mass());
1441 case CUSTOM_OBJECT_LABEL: return variant(label());
1442 case CUSTOM_OBJECT_X: return variant(x());
1443 case CUSTOM_OBJECT_Y: return variant(y());
1444 case CUSTOM_OBJECT_Z:
1445 case CUSTOM_OBJECT_ZORDER: return variant(zorder_);
1446 case CUSTOM_OBJECT_PREVIOUS_Y: return variant(previous_y_);
1447 case CUSTOM_OBJECT_X1: return variant(solid_rect().x());
1448 case CUSTOM_OBJECT_X2: return variant(solid_rect().x2());
1449 case CUSTOM_OBJECT_Y1: return variant(solid_rect().y());
1450 case CUSTOM_OBJECT_Y2: return variant(solid_rect().y2());
1451 case CUSTOM_OBJECT_W: return variant(solid_rect().w());
1452 case CUSTOM_OBJECT_H: return variant(solid_rect().h());
1453
1454 case CUSTOM_OBJECT_MIDPOINT_X: return variant(x() + current_frame().width()/2);
1455 case CUSTOM_OBJECT_MIDPOINT_Y: return variant(y() + current_frame().height()/2);
1456 case CUSTOM_OBJECT_SOLID_RECT: return variant(solid_rect().callable());
1457 case CUSTOM_OBJECT_IMG_W: return variant(current_frame().width());
1458 case CUSTOM_OBJECT_IMG_H: return variant(current_frame().height());
1459 case CUSTOM_OBJECT_FRONT: return variant(face_right() ? body_rect().x2() : body_rect().x());
1460 case CUSTOM_OBJECT_BACK: return variant(face_right() ? body_rect().x() : body_rect().x2());
1461 case CUSTOM_OBJECT_CYCLE: return variant(cycle_);
1462 case CUSTOM_OBJECT_FACING: return variant(face_right() ? 1 : -1);
1463 case CUSTOM_OBJECT_UPSIDE_DOWN: return variant(upside_down() ? 1 : -1);
1464 case CUSTOM_OBJECT_UP: return variant(upside_down() ? 1 : -1);
1465 case CUSTOM_OBJECT_DOWN: return variant(upside_down() ? -1 : 1);
1466 case CUSTOM_OBJECT_VELOCITY_X: return variant(velocity_x_);
1467 case CUSTOM_OBJECT_VELOCITY_Y: return variant(velocity_y_);
1468 case CUSTOM_OBJECT_ACCEL_X: return variant(accel_x_);
1469 case CUSTOM_OBJECT_ACCEL_Y: return variant(accel_y_);
1470 case CUSTOM_OBJECT_VARS: return variant(vars_.get());
1471 case CUSTOM_OBJECT_TMP: return variant(tmp_vars_.get());
1472 case CUSTOM_OBJECT_GROUP: return variant(group());
1473 case CUSTOM_OBJECT_ROTATE: return variant(rotate_);
1474 case CUSTOM_OBJECT_ME:
1475 case CUSTOM_OBJECT_SELF: return variant(this);
1476 case CUSTOM_OBJECT_RED: return variant(draw_color().r());
1477 case CUSTOM_OBJECT_GREEN: return variant(draw_color().g());
1478 case CUSTOM_OBJECT_BLUE: return variant(draw_color().b());
1479 case CUSTOM_OBJECT_ALPHA: return variant(draw_color().a());
1480 case CUSTOM_OBJECT_TEXT_ALPHA: return variant(text_ ? text_->alpha : 255);
1481 case CUSTOM_OBJECT_DAMAGE: return variant(current_frame().damage()); case CUSTOM_OBJECT_HIT_BY: return variant(last_hit_by_.get());
1482 case CUSTOM_OBJECT_DISTORTION: return variant(distortion_.get());
1483 case CUSTOM_OBJECT_IS_STANDING: return variant(standing_on_.get() || is_standing(level::current()));
1484 case CUSTOM_OBJECT_NEAR_CLIFF_EDGE: return variant(is_standing(level::current()) && cliff_edge_within(level::current(), feet_x(), feet_y(), face_dir()*15));
1485 case CUSTOM_OBJECT_DISTANCE_TO_CLIFF: return variant(::distance_to_cliff(level::current(), feet_x(), feet_y(), face_dir()));
1486 case CUSTOM_OBJECT_SLOPE_STANDING_ON: return variant(-slope_standing_on(6)*face_dir());
1487 case CUSTOM_OBJECT_UNDERWATER: return variant(level::current().is_underwater(solid() ? solid_rect() : rect(x(), y(), current_frame().width(), current_frame().height())));
1488 case CUSTOM_OBJECT_WATER_BOUNDS: {
1489 rect area;
1490 if(level::current().is_underwater(solid_rect(), &area)) {
1491 std::vector<variant> v;
1492 v.push_back(variant(area.x()));
1493 v.push_back(variant(area.y()));
1494 v.push_back(variant(area.x2()));
1495 v.push_back(variant(area.y2()));
1496 return variant(&v);
1497 } else {
1498 return variant();
1499 }
1500 }
1501 case CUSTOM_OBJECT_WATER_OBJECT: {
1502 variant v;
1503 level::current().is_underwater(solid_rect(), NULL, &v);
1504 return v;
1505 }
1506 case CUSTOM_OBJECT_DRIVER: return variant(driver_ ? driver_.get() : this);
1507 case CUSTOM_OBJECT_IS_HUMAN: return variant(is_human() ? 1 : 0);
1508 case CUSTOM_OBJECT_INVINCIBLE: return variant(invincible_);
1509 case CUSTOM_OBJECT_SOUND_VOLUME: return variant(sound_volume_);
1510 case CUSTOM_OBJECT_DESTROYED: return variant(destroyed());
1511
1512 case CUSTOM_OBJECT_IS_STANDING_ON_PLATFORM: {
1513 if(standing_on_ && standing_on_->platform()) {
1514 return variant(1);
1515 }
1516
1517 collision_info info;
1518 is_standing(level::current(), &info);
1519 return variant(info.platform);
1520 }
1521
1522 case CUSTOM_OBJECT_STANDING_ON: {
1523 if(standing_on_) {
1524 return variant(standing_on_.get());
1525 }
1526
1527 entity_ptr stand_on;
1528 collision_info info;
1529 is_standing(level::current(), &info);
1530 return variant(info.collide_with.get());
1531 }
1532
1533 case CUSTOM_OBJECT_FRAGMENT_SHADERS: {
1534 std::vector<variant> v;
1535 foreach(const std::string& shader, fragment_shaders_) {
1536 v.push_back(variant(shader));
1537 }
1538
1539 return variant(&v);
1540 }
1541
1542 case CUSTOM_OBJECT_VERTEX_SHADERS: {
1543 std::vector<variant> v;
1544 foreach(const std::string& shader, vertex_shaders_) {
1545 v.push_back(variant(shader));
1546 }
1547
1548 return variant(&v);
1549 }
1550
1551 case CUSTOM_OBJECT_SHADER: {
1552 if(shader_vars_.get() == NULL) {
1553 shader_vars_.reset(new game_logic::map_formula_callable);
1554 }
1555
1556 return variant(shader_vars_.get());
1557 }
1558
1559 case CUSTOM_OBJECT_ACTIVATION_AREA: {
1560 if(activation_area_.get() != NULL) {
1561 std::vector<variant> v(4);
1562 v[0] = variant(activation_area_->x());
1563 v[1] = variant(activation_area_->y());
1564 v[2] = variant(activation_area_->w());
1565 v[3] = variant(activation_area_->h());
1566 return variant(&v);
1567 } else {
1568 return variant();
1569 }
1570 }
1571
1572 case CUSTOM_OBJECT_VARIATIONS: {
1573 std::vector<variant> result;
1574 foreach(const std::string& s, current_variation_) {
1575 result.push_back(variant(s));
1576 }
1577
1578 return variant(&result);
1579 }
1580
1581 case CUSTOM_OBJECT_ATTACHED_OBJECTS: {
1582 std::vector<variant> result;
1583 foreach(const entity_ptr& e, attached_objects()) {
1584 result.push_back(variant(e.get()));
1585 }
1586
1587 return variant(&result);
1588 }
1589
1590 case CUSTOM_OBJECT_CALL_STACK: {
1591 return call_stack(*this);
1592 }
1593
1594 case CUSTOM_OBJECT_ALWAYS_ACTIVE: return variant(always_active_);
1595 case CUSTOM_OBJECT_TAGS: return variant(tags_.get());
1596 case CUSTOM_OBJECT_HAS_FEET: return variant(has_feet_);
1597
1598 case CUSTOM_OBJECT_CTRL_UP:
1599 case CUSTOM_OBJECT_CTRL_DOWN:
1600 case CUSTOM_OBJECT_CTRL_LEFT:
1601 case CUSTOM_OBJECT_CTRL_RIGHT:
1602 case CUSTOM_OBJECT_CTRL_ATTACK:
1603 case CUSTOM_OBJECT_CTRL_JUMP:
1604 case CUSTOM_OBJECT_CTRL_TONGUE:
1605 return variant(control_status(static_cast<controls::CONTROL_ITEM>(slot - CUSTOM_OBJECT_CTRL_UP)));
1606 }
1607
1608 ASSERT_LOG(false, "UNKNOWN SLOT QUERIED FROM OBJECT: " << slot);
1609 }
1610
1611 variant custom_object::get_value(const std::string& key) const
1612 {
1613 const int slot = type_->callable_definition().get_slot(key);
1614 if(slot >= 0 && slot < NUM_CUSTOM_OBJECT_PROPERTIES) {
1615 return get_value_by_slot(slot);
1616 }
1617
1618 std::map<std::string, game_logic::const_formula_ptr>::const_iterator property_itor = type_->properties().find(key);
1619 if(property_itor != type_->properties().end() && property_itor->second) {
1620 return property_itor->second->execute(*this);
1621 }
1622
1623 variant var_result = tmp_vars_->query_value(key);
1624 if(!var_result.is_null()) {
1625 return var_result;
1626 }
1627
1628 var_result = vars_->query_value(key);
1629 if(!var_result.is_null()) {
1630 return var_result;
1631 }
1632
1633 std::map<std::string, variant>::const_iterator i = type_->variables().find(key);
1634 if(i != type_->variables().end()) {
1635 return i->second;
1636 }
1637
1638 std::map<std::string, particle_system_ptr>::const_iterator particle_itor = particle_systems_.find(key);
1639 if(particle_itor != particle_systems_.end()) {
1640 return variant(particle_itor->second.get());
1641 }
1642
1643 if(backup_callable_stack_.empty() == false && backup_callable_stack_.top()) {
1644 return backup_callable_stack_.top()->query_value(key);
1645 }
1646
1647 return variant();
1648 }
1649
1650 void custom_object::get_inputs(std::vector<game_logic::formula_input>* inputs) const
1651 {
1652 inputs->push_back(game_logic::formula_input("time_in_animation", game_logic::FORMULA_READ_WRITE));
1653 inputs->push_back(game_logic::formula_input("level", game_logic::FORMULA_READ_ONLY));
1654 inputs->push_back(game_logic::formula_input("animation", game_logic::FORMULA_READ_ONLY));
1655 inputs->push_back(game_logic::formula_input("hitpoints", game_logic::FORMULA_READ_WRITE));
1656 }
1657
1658 void custom_object::set_value(const std::string& key, const variant& value)
1659 {
1660 const int slot = custom_object_callable::get_key_slot(key);
1661 if(slot != -1) {
1662 set_value_by_slot(slot, value);
1663 return;
1664 }
1665
1666 if(key == "animation") {
1667 set_frame(value.as_string());
1668 } else if(key == "time_in_animation") {
1669 time_in_frame_ = value.as_int()%frame_->duration();
1670 } else if(key == "time_in_animation_delta") {
1671 time_in_frame_delta_ = value.as_int();
1672 std::cerr << "SET TIME IN ANIMATION_DELTA: " << time_in_frame_delta_ << "\n";
1673 } else if(key == "x") {
1674 const int start_x = centi_x();
1675 set_x(value.as_int());
1676 if(entity_collides(level::current(), *this, MOVE_NONE) && entity_in_current_level(this)) {
1677 set_centi_x(start_x);
1678 }
1679 } else if(key == "y") {
1680 const int start_y = centi_y();
1681 set_y(value.as_int());
1682 if(entity_collides(level::current(), *this, MOVE_NONE) && entity_in_current_level(this)) {
1683 set_centi_y(start_y);
1684 }
1685 } else if(key == "z" || key == "zorder") {
1686 zorder_ = value.as_int();
1687 } else if(key == "midpoint_x") {
1688 const int current_x = x() + current_frame().width()/2;
1689 const int xdiff = current_x - x();
1690 set_pos(value.as_int() - xdiff, y());
1691 } else if(key == "midpoint_y") {
1692 const int current_y = y() + current_frame().height()/2;
1693 const int ydiff = current_y - y();
1694 set_pos(x(), value.as_int() - ydiff);
1695 } else if(key == "facing") {
1696 set_face_right(value.as_int() > 0);
1697 } else if(key == "upside_down") {
1698 set_upside_down(value.as_int());
1699 } else if(key == "hitpoints") {
1700 const int old_hitpoints = hitpoints_;
1701 hitpoints_ = value.as_int();
1702 if(old_hitpoints > 0 && hitpoints_ <= 0) {
1703 die();
1704 }
1705 } else if(key == "max_hitpoints") {
1706 max_hitpoints_ = value.as_int();
1707 if(hitpoints_ > max_hitpoints_) {
1708 hitpoints_ = max_hitpoints_;
1709 }
1710 } else if(key == "velocity_x") {
1711 velocity_x_ = value.as_int();
1712 } else if(key == "velocity_y") {
1713 velocity_y_ = value.as_int();
1714 } else if(key == "accel_x") {
1715 accel_x_ = value.as_int();
1716 } else if(key == "accel_y") {
1717 accel_y_ = value.as_int();
1718 } else if(key == "rotate") {
1719 rotate_ = value.as_int();
1720 } else if(key == "red") {
1721 make_draw_color();
1722 draw_color_->buf()[0] = truncate_to_char(value.as_int());
1723 } else if(key == "green") {
1724 make_draw_color();
1725 draw_color_->buf()[1] = truncate_to_char(value.as_int());
1726 } else if(key == "blue") {
1727 make_draw_color();
1728 draw_color_->buf()[2] = truncate_to_char(value.as_int());
1729 } else if(key == "alpha") {
1730 make_draw_color();
1731 draw_color_->buf()[3] = truncate_to_char(value.as_int());
1732 } else if(key == "brightness"){
1733 make_draw_color();
1734 draw_color_->buf()[0] = value.as_int();
1735 draw_color_->buf()[1] = value.as_int();
1736 draw_color_->buf()[2] = value.as_int();
1737 } else if(key == "distortion") {
1738 distortion_ = value.try_convert<graphics::raster_distortion>();
1739 } else if(key == "current_generator") {
1740 set_current_generator(value.try_convert<current_generator>());
1741 } else if(key == "invincible") {
1742 invincible_ = value.as_int();
1743 } else if(key == "fall_through_platforms") {
1744 fall_through_platforms_ = value.as_int();
1745 } else if(key == "tags") {
1746 if(value.is_list()) {
1747 tags_ = new game_logic::map_formula_callable;
1748 for(int n = 0; n != value.num_elements(); ++n) {
1749 tags_->add(value[n].as_string(), variant(1));
1750 }
1751 }
1752 } else if(key == "fragment_shaders") {
1753 fragment_shaders_.clear();
1754 for(int n = 0; n != value.num_elements(); ++n) {
1755 fragment_shaders_.push_back(value[n].as_string());
1756 }
1757 shader_ = 0;
1758 } else if(key == "vertex_shaders") {
1759 vertex_shaders_.clear();
1760 for(int n = 0; n != value.num_elements(); ++n) {
1761 vertex_shaders_.push_back(value[n].as_string());
1762 }
1763 shader_ = 0;
1764 } else if(key == "draw_area") {
1765 if(value.is_list() && value.num_elements() == 4) {
1766 draw_area_.reset(new rect(value[0].as_int(), value[1].as_int(), value[2].as_int(), value[3].as_int()));
1767 } else {
1768 draw_area_.reset();
1769 }
1770 } else if(key == "activation_area") {
1771 if(value.is_list() && value.num_elements() == 4) {
1772 activation_area_.reset(new rect(value[0].as_int(), value[1].as_int(), value[2].as_int(), value[3].as_int()));
1773 } else {
1774 ASSERT_LOG(value.is_null(), "BAD ACTIVATION AREA: " << value.to_debug_string());
1775 activation_area_.reset();
1776 }
1777 } else if(key == "variations") {
1778 handle_event("reset_variations");
1779 current_variation_.clear();
1780 if(value.is_list()) {
1781 for(int n = 0; n != value.num_elements(); ++n) {
1782 current_variation_.push_back(value[n].as_string());
1783 }
1784 } else if(value.is_string()) {
1785 current_variation_.push_back(value.as_string());
1786 }
1787
1788 if(current_variation_.empty()) {
1789 type_ = base_type_;
1790 } else {
1791 type_ = base_type_->get_variation(current_variation_);
1792 }
1793
1794 calculate_solid_rect();
1795
1796 handle_event("set_variations");
1797 } else if(key == "attached_objects") {
1798 std::vector<entity_ptr> v;
1799 for(int n = 0; n != value.num_elements(); ++n) {
1800 entity* e = value[n].try_convert<entity>();
1801 if(e) {
1802 v.push_back(entity_ptr(e));
1803 }
1804 }
1805
1806 set_attached_objects(v);
1807 } else if(key == "solid_dimensions_in" || key == "solid_dimensions_not_in") {
1808
1809 unsigned int solid = 0, weak = 0;
1810 for(int n = 0; n != value.num_elements(); ++n) {
1811 std::string str = value[n].as_string();
1812 if(!str.empty() && str[0] == '~') {
1813 str = std::string(str.begin() + 1, str.end());
1814 const int id = get_solid_dimension_id(str);
1815 weak |= 1 << id;
1816 } else {
1817 const int id = get_solid_dimension_id(value[n].as_string());
1818 solid |= 1 << id;
1819 }
1820 }
1821
1822 if(key == "solid_dimensions_not_in") {
1823 solid = ~solid;
1824 weak = ~weak;
1825 }
1826
1827 weak |= solid;
1828
1829 const unsigned int old_solid = solid_dimensions();
1830 const unsigned int old_weak = weak_solid_dimensions();
1831 set_solid_dimensions(solid, weak);
1832 collision_info collide_info;
1833 if(entity_in_current_level(this) && entity_collides(level::current(), *this, MOVE_NONE, &collide_info)) {
1834 set_solid_dimensions(old_solid, old_weak);
1835 ASSERT_EQ(entity_collides(level::current(), *this, MOVE_NONE), false);
1836
1837 game_logic::map_formula_callable* callable(new game_logic::map_formula_callable(this));
1838 callable->add("collide_with", variant(collide_info.collide_with.get()));
1839 game_logic::formula_callable_ptr callable_ptr(callable);
1840
1841 handle_event(OBJECT_EVENT_CHANGE_SOLID_DIMENSIONS_FAIL, callable);
1842 }
1843
1844 } else if(key == "xscale" || key == "yscale") {
1845 if(position_scale_millis_.get() == NULL) {
1846 position_scale_millis_.reset(new std::pair<int,int>(1000,1000));
1847 }
1848
1849 const int v = value.as_int();
1850
1851 if(key == "xscale") {
1852 const int current = (position_scale_millis_->first*x())/1000;
1853 const int new_value = (v*current)/1000;
1854 set_x(new_value);
1855 position_scale_millis_->first = v;
1856 } else {
1857 const int current = (position_scale_millis_->second*y())/1000;
1858 const int new_value = (v*current)/1000;
1859 set_y(new_value);
1860 position_scale_millis_->second = v;
1861 }
1862 } else if(key == "type") {
1863 const_custom_object_type_ptr p = custom_object_type::get(value.as_string());
1864 if(p) {
1865 game_logic::formula_variable_storage_ptr old_vars = vars_, old_tmp_vars_ = tmp_vars_;
1866
1867 type_ = p;
1868 vars_.reset(new game_logic::formula_variable_storage(type_->variables())),
1869 tmp_vars_.reset(new game_logic::formula_variable_storage(type_->tmp_variables())),
1870
1871 vars_->add(*old_vars);
1872 tmp_vars_->add(*old_tmp_vars_);
1873 }
1874 } else {
1875 vars_->add(key, value);
1876 }
1877 }
1878
1879 void custom_object::set_value_by_slot(int slot, const variant& value)
1880 {
1881 switch(slot) {
1882 case CUSTOM_OBJECT_TYPE: {
1883 const_custom_object_type_ptr p = custom_object_type::get(value.as_string());
1884 if(p) {
1885 game_logic::formula_variable_storage_ptr old_vars = vars_, old_tmp_vars_ = tmp_vars_;
1886
1887 type_ = p;
1888 vars_.reset(new game_logic::formula_variable_storage(type_->variables())),
1889 tmp_vars_.reset(new game_logic::formula_variable_storage(type_->tmp_variables())),
1890
1891 vars_->add(*old_vars);
1892 tmp_vars_->add(*old_tmp_vars_);
1893 }
1894 }
1895 break;
1896 case CUSTOM_OBJECT_TIME_IN_ANIMATION:
1897 time_in_frame_ = value.as_int()%frame_->duration();
1898 break;
1899 case CUSTOM_OBJECT_TIME_IN_ANIMATION_DELTA:
1900 time_in_frame_delta_ = value.as_int();
1901 std::cerr << "SET TIME IN ANIMATION_DELTA: " << time_in_frame_delta_ << "\n";
1902 break;
1903 case CUSTOM_OBJECT_ANIMATION:
1904 set_frame(value.as_string());
1905 break;
1906
1907 case CUSTOM_OBJECT_X: {
1908 const int start_x = centi_x();
1909 set_x(value.as_int());
1910 if(entity_collides(level::current(), *this, MOVE_NONE) && entity_in_current_level(this)) {
1911 set_centi_x(start_x);
1912 }
1913
1914 break;
1915 }
1916
1917 case CUSTOM_OBJECT_Y: {
1918 const int start_y = centi_y();
1919 set_y(value.as_int());
1920 if(entity_collides(level::current(), *this, MOVE_NONE) && entity_in_current_level(this)) {
1921 set_centi_y(start_y);
1922 }
1923
1924 break;
1925 }
1926
1927 case CUSTOM_OBJECT_Z:
1928 case CUSTOM_OBJECT_ZORDER:
1929 zorder_ = value.as_int();
1930 break;
1931
1932 case CUSTOM_OBJECT_MIDPOINT_X: {
1933 const int current_x = x() + current_frame().width()/2;
1934 const int xdiff = current_x - x();
1935 set_pos(value.as_int() - xdiff, y());
1936 break;
1937 }
1938
1939 case CUSTOM_OBJECT_MIDPOINT_Y: {
1940 const int current_y = y() + current_frame().height()/2;
1941 const int ydiff = current_y - y();
1942 set_pos(x(), value.as_int() - ydiff);
1943 break;
1944 }
1945
1946 case CUSTOM_OBJECT_FACING:
1947 set_face_right(value.as_int() > 0);
1948 break;
1949
1950 case CUSTOM_OBJECT_UPSIDE_DOWN:
1951 set_upside_down(value.as_int());
1952 break;
1953
1954 case CUSTOM_OBJECT_HITPOINTS: {
1955 const int old_hitpoints = hitpoints_;
1956 hitpoints_ = value.as_int();
1957 if(old_hitpoints > 0 && hitpoints_ <= 0) {
1958 die();
1959 }
1960 break;
1961 }
1962 case CUSTOM_OBJECT_MAX_HITPOINTS:
1963 max_hitpoints_ = value.as_int();
1964 if(hitpoints_ > max_hitpoints_) {
1965 hitpoints_ = max_hitpoints_;
1966 }
1967 break;
1968
1969 case CUSTOM_OBJECT_VELOCITY_X:
1970 velocity_x_ = value.as_int();
1971 break;
1972
1973 case CUSTOM_OBJECT_VELOCITY_Y:
1974 velocity_y_ = value.as_int();
1975 break;
1976
1977 case CUSTOM_OBJECT_ACCEL_X:
1978 accel_x_ = value.as_int();
1979 break;
1980
1981 case CUSTOM_OBJECT_ACCEL_Y:
1982 accel_y_ = value.as_int();
1983 break;
1984
1985 case CUSTOM_OBJECT_ROTATE:
1986 rotate_ = value.as_int();
1987 break;
1988
1989 case CUSTOM_OBJECT_RED:
1990 make_draw_color();
1991 draw_color_->buf()[0] = truncate_to_char(value.as_int());
1992 break;
1993
1994 case CUSTOM_OBJECT_GREEN:
1995 make_draw_color();
1996 draw_color_->buf()[1] = truncate_to_char(value.as_int());
1997 break;
1998
1999 case CUSTOM_OBJECT_BLUE:
2000 make_draw_color();
2001 draw_color_->buf()[2] = truncate_to_char(value.as_int());
2002 break;
2003
2004 case CUSTOM_OBJECT_ALPHA:
2005 make_draw_color();
2006 draw_color_->buf()[3] = truncate_to_char(value.as_int());
2007 break;
2008
2009 case CUSTOM_OBJECT_TEXT_ALPHA:
2010 if(!text_) {
2011 set_text("", "default", 10);
2012 }
2013
2014 text_->alpha = value.as_int();
2015 break;
2016
2017 case CUSTOM_OBJECT_BRIGHTNESS:
2018 make_draw_color();
2019 draw_color_->buf()[0] = value.as_int();
2020 draw_color_->buf()[1] = value.as_int();
2021 draw_color_->buf()[2] = value.as_int();
2022 break;
2023
2024 case CUSTOM_OBJECT_DISTORTION:
2025 distortion_ = value.try_convert<graphics::raster_distortion>();
2026 break;
2027
2028 case CUSTOM_OBJECT_CURRENT_GENERATOR:
2029 set_current_generator(value.try_convert<current_generator>());
2030 break;
2031
2032 case CUSTOM_OBJECT_INVINCIBLE:
2033 invincible_ = value.as_int();
2034 break;
2035
2036 case CUSTOM_OBJECT_FALL_THROUGH_PLATFORMS:
2037 fall_through_platforms_ = value.as_int();
2038 break;
2039
2040 case CUSTOM_OBJECT_HAS_FEET:
2041 has_feet_ = value.as_bool();
2042 break;
2043
2044 case CUSTOM_OBJECT_TAGS:
2045 if(value.is_list()) {
2046 tags_ = new game_logic::map_formula_callable;
2047 for(int n = 0; n != value.num_elements(); ++n) {
2048 tags_->add(value[n].as_string(), variant(1));
2049 }
2050 }
2051
2052 break;
2053
2054 case CUSTOM_OBJECT_FRAGMENT_SHADERS:
2055 fragment_shaders_.clear();
2056 for(int n = 0; n != value.num_elements(); ++n) {
2057 fragment_shaders_.push_back(value[n].as_string());
2058 }
2059 shader_ = 0;
2060 break;
2061
2062 case CUSTOM_OBJECT_VERTEX_SHADERS:
2063 vertex_shaders_.clear();
2064 for(int n = 0; n != value.num_elements(); ++n) {
2065 vertex_shaders_.push_back(value[n].as_string());
2066 }
2067 shader_ = 0;
2068 break;
2069
2070 case CUSTOM_OBJECT_DRAW_AREA:
2071 if(value.is_list() && value.num_elements() == 4) {
2072 draw_area_.reset(new rect(value[0].as_int(), value[1].as_int(), value[2].as_int(), value[3].as_int()));
2073 } else {
2074 draw_area_.reset();
2075 }
2076
2077 break;
2078
2079 case CUSTOM_OBJECT_ACTIVATION_AREA:
2080 if(value.is_list() && value.num_elements() == 4) {
2081 activation_area_.reset(new rect(value[0].as_int(), value[1].as_int(), value[2].as_int(), value[3].as_int()));
2082 } else {
2083 ASSERT_LOG(value.is_null(), "BAD ACTIVATION AREA: " << value.to_debug_string());
2084 activation_area_.reset();
2085 }
2086
2087 break;
2088
2089 case CUSTOM_OBJECT_ALWAYS_ACTIVE:
2090 always_active_ = value.as_bool();
2091 break;
2092
2093 case CUSTOM_OBJECT_VARIATIONS:
2094 handle_event("reset_variations");
2095 current_variation_.clear();
2096 if(value.is_list()) {
2097 for(int n = 0; n != value.num_elements(); ++n) {
2098 current_variation_.push_back(value[n].as_string());
2099 }
2100 } else if(value.is_string()) {
2101 current_variation_.push_back(value.as_string());
2102 }
2103
2104 if(current_variation_.empty()) {
2105 type_ = base_type_;
2106 } else {
2107 type_ = base_type_->get_variation(current_variation_);
2108 }
2109
2110 calculate_solid_rect();
2111 handle_event("set_variations");
2112 break;
2113
2114 case CUSTOM_OBJECT_ATTACHED_OBJECTS: {
2115 std::vector<entity_ptr> v;
2116 for(int n = 0; n != value.num_elements(); ++n) {
2117 entity* e = value[n].try_convert<entity>();
2118 if(e) {
2119 v.push_back(entity_ptr(e));
2120 }
2121 }
2122
2123 set_attached_objects(v);
2124 break;
2125 }
2126
2127 case CUSTOM_OBJECT_COLLIDE_DIMENSIONS_IN:
2128 case CUSTOM_OBJECT_COLLIDE_DIMENSIONS_NOT_IN: {
2129 unsigned int solid = 0, weak = 0;
2130 for(int n = 0; n != value.num_elements(); ++n) {
2131 std::string str = value[n].as_string();
2132 if(!str.empty() && str[0] == '~') {
2133 str = std::string(str.begin() + 1, str.end());
2134 const int id = get_solid_dimension_id(str);
2135 weak |= 1 << id;
2136 } else {
2137 const int id = get_solid_dimension_id(value[n].as_string());
2138 solid |= 1 << id;
2139 }
2140 }
2141
2142 if(slot == CUSTOM_OBJECT_COLLIDE_DIMENSIONS_NOT_IN) {
2143 solid = ~solid;
2144 weak = ~weak;
2145 }
2146
2147 weak |= solid;
2148
2149 set_collide_dimensions(solid, weak);
2150 break;
2151 }
2152
2153 case CUSTOM_OBJECT_SOLID_DIMENSIONS_IN:
2154 case CUSTOM_OBJECT_SOLID_DIMENSIONS_NOT_IN: {
2155 unsigned int solid = 0, weak = 0;
2156 for(int n = 0; n != value.num_elements(); ++n) {
2157 std::string str = value[n].as_string();
2158 if(!str.empty() && str[0] == '~') {
2159 str = std::string(str.begin() + 1, str.end());
2160 const int id = get_solid_dimension_id(str);
2161 weak |= 1 << id;
2162 } else {
2163 const int id = get_solid_dimension_id(value[n].as_string());
2164 solid |= 1 << id;
2165 }
2166 }
2167
2168 if(slot == CUSTOM_OBJECT_SOLID_DIMENSIONS_NOT_IN) {
2169 solid = ~solid;
2170 weak = ~weak;
2171 }
2172
2173 weak |= solid;
2174
2175 const unsigned int old_solid = solid_dimensions();
2176 const unsigned int old_weak = weak_solid_dimensions();
2177 set_solid_dimensions(solid, weak);
2178 collision_info collide_info;
2179 if(entity_in_current_level(this) && entity_collides(level::current(), *this, MOVE_NONE, &collide_info)) {
2180 set_solid_dimensions(old_solid, old_weak);
2181 ASSERT_EQ(entity_collides(level::current(), *this, MOVE_NONE), false);
2182
2183 game_logic::map_formula_callable* callable(new game_logic::map_formula_callable(this));
2184 callable->add("collide_with", variant(collide_info.collide_with.get()));
2185 game_logic::formula_callable_ptr callable_ptr(callable);
2186
2187 handle_event(OBJECT_EVENT_CHANGE_SOLID_DIMENSIONS_FAIL, callable);
2188 }
2189
2190 break;
2191 }
2192
2193 case CUSTOM_OBJECT_X_SCHEDULE: {
2194 if(position_schedule_.get() == NULL) {
2195 position_schedule_.reset(new position_schedule);
2196 }
2197
2198 position_schedule_->x_pos.clear();
2199 for(int n = 0; n != value.num_elements(); ++n) {
2200 position_schedule_->x_pos.push_back(value[n].as_int());
2201 }
2202 break;
2203 }
2204 case CUSTOM_OBJECT_Y_SCHEDULE: {
2205 if(position_schedule_.get() == NULL) {
2206 position_schedule_.reset(new position_schedule);
2207 }
2208
2209 position_schedule_->y_pos.clear();
2210 for(int n = 0; n != value.num_elements(); ++n) {
2211 position_schedule_->y_pos.push_back(value[n].as_int());
2212 }
2213 break;
2214 }
2215 case CUSTOM_OBJECT_ROTATION_SCHEDULE: {
2216 if(position_schedule_.get() == NULL) {
2217 position_schedule_.reset(new position_schedule);
2218 }
2219
2220 position_schedule_->rotation.clear();
2221 for(int n = 0; n != value.num_elements(); ++n) {
2222 position_schedule_->rotation.push_back(value[n].as_int());
2223 }
2224 break;
2225 }
2226
2227 case CUSTOM_OBJECT_SCHEDULE_SPEED: {
2228 if(position_schedule_.get() == NULL) {
2229 position_schedule_.reset(new position_schedule);
2230 }
2231
2232 position_schedule_->speed = value.as_int();
2233
2234 break;
2235 }
2236
2237 default:
2238 break;
2239
2240 }
2241 }
2242
2243 void custom_object::set_frame(const std::string& name)
2244 {
2245 const std::string previous_animation = frame_name_;
2246
2247 const bool changing_anim = name != frame_name_;
2248
2249 //fire an event to say that we're leaving the current frame.
2250 if(frame_ && changing_anim) {
2251 handle_event(frame_->leave_event_id());
2252 }
2253
2254 const int start_x = feet_x();
2255 const int start_y = feet_y();
2256
2257 frame_ = &type_->get_frame(name);
2258 calculate_solid_rect();
2259 ++current_animation_id_;
2260
2261 const int diff_x = feet_x() - start_x;
2262 const int diff_y = feet_y() - start_y;
2263
2264 if(type_->adjust_feet_on_animation_change()) {
2265 move_centipixels(-diff_x*100, -diff_y*100);
2266 }
2267
2268 set_frame_no_adjustments(name);
2269
2270 frame_->play_sound(this);
2271
2272 if(entity_collides(level::current(), *this, MOVE_NONE) && entity_in_current_level(this)) {
2273 game_logic::map_formula_callable* callable(new game_logic::map_formula_callable);
2274 callable->add("previous_animation", variant(previous_animation));
2275 game_logic::formula_callable_ptr callable_ptr(callable);
2276 static int change_animation_failure_recurse = 0;
2277 ASSERT_LOG(change_animation_failure_recurse < 5, "OBJECT " << type_->id() << " FAILS TO RESOLVE ANIMATION CHANGE FAILURES");
2278 ++change_animation_failure_recurse;
2279 handle_event(OBJECT_EVENT_CHANGE_ANIMATION_FAILURE, callable);
2280 handle_event("change_animation_failure_" + frame_name_, callable);
2281 --change_animation_failure_recurse;
2282 ASSERT_LOG(destroyed() || !entity_collides(level::current(), *this, MOVE_NONE),
2283 "Object '" << type_->id() << "' has different solid areas when changing from frame " << previous_animation << " to " << frame_name_ << " and doesn't handle it properly");
2284 }
2285
2286 handle_event(OBJECT_EVENT_ENTER_ANIM);
2287 handle_event(frame_->enter_event_id());
2288 }
2289
2290 rect custom_object::draw_rect() const
2291 {
2292 if(draw_area_) {
2293 return rect(x(), y(), draw_area_->w()*2, draw_area_->h()*2);
2294 } else {
2295 return rect(x(), y(), frame_->width(), frame_->height());
2296 }
2297 }
2298
2299 void custom_object::set_frame_no_adjustments(const std::string& name)
2300 {
2301 frame_ = &type_->get_frame(name);
2302 frame_name_ = name;
2303 time_in_frame_ = 0;
2304 if(frame_->velocity_x() != INT_MIN) {
2305 velocity_x_ = frame_->velocity_x() * (face_right() ? 1 : -1);
2306 }
2307
2308 if(frame_->velocity_y() != INT_MIN) {
2309 velocity_y_ = frame_->velocity_y();
2310 }
2311
2312 if(frame_->accel_x() != INT_MIN) {
2313 accel_x_ = frame_->accel_x();
2314 }
2315
2316 if(frame_->accel_y() != INT_MIN) {
2317 accel_y_ = frame_->accel_y();
2318 }
2319
2320 calculate_solid_rect();
2321 }
2322
2323 void custom_object::die()
2324 {
2325 hitpoints_ = 0;
2326 handle_event(OBJECT_EVENT_DIE);
2327 }
2328
2329 bool custom_object::is_active(const rect& screen_area) const
2330 {
2331 if(always_active()) {
2332 return true;
2333 }
2334
2335 if(type_->goes_inactive_only_when_standing() && !is_standing(level::current())) {
2336 return true;
2337 }
2338
2339 if(activation_area_) {
2340 return rects_intersect(*activation_area_, screen_area);
2341 }
2342
2343 if(text_) {
2344 const rect text_area(x(), y(), text_->dimensions.w(), text_->dimensions.h());
2345 if(rects_intersect(screen_area, text_area)) {
2346 return true;
2347 }
2348 }
2349
2350 if(position_scale_millis_.get() != NULL) {
2351 const int diffx = ((position_scale_millis_->first - 1000)*screen_area.x())/1000;
2352 const int diffy = ((position_scale_millis_->second - 1000)*screen_area.y())/1000;
2353 rect screen(screen_area.x() - diffx, screen_area.y() - diffy,
2354 screen_area.w(), screen_area.h());
2355 const rect& area = frame_rect();
2356 return rects_intersect(screen, area);
2357 }
2358
2359 const rect& area = frame_rect();
2360 if(area.x() < screen_area.x2() + 100 &&
2361 area.x2() > screen_area.x() - 100 &&
2362 area.y() < screen_area.y2() + 100 &&
2363 area.y2() > screen_area.y() - 100) {
2364 return true;
2365 }
2366
2367 if(draw_area_) {
2368 rect draw_area(area.x(), area.y(), draw_area_->w()*2, draw_area_->h()*2);
2369 return rects_intersect(draw_area, screen_area);
2370 }
2371
2372 return false;
2373 }
2374
2375 void custom_object::move_to_standing(level& lvl)
2376 {
2377 int start_y = y();
2378 //descend from the initial-position (what the player was at in the prev level) until we're standing
2379 for(int n = 0; n != 10000; ++n) {
2380 if(is_standing(lvl)) {
2381
2382 if(n == 0) { //if we've somehow managed to be standing on the very first frame, try to avoid the possibility that this is actually some open space underground on a cave level by scanning up till we reach the surface.
2383 for(int n = 0; n != 10000; ++n) {
2384 set_pos(x(), y() - 1);
2385 if(!is_standing(lvl)) {
2386 set_pos(x(), y() + 1);
2387
2388 if(y() < lvl.boundaries().y()) {
2389 //we are too high, out of the level. Move the
2390 //character down, under the solid, and then
2391 //call this function again to move them down
2392 //to standing on the solid below.
2393 for(int n = 0; n != 10000; ++n) {
2394 set_pos(x(), y() + 1);
2395 if(!is_standing(lvl)) {
2396 move_to_standing(lvl);
2397 return;
2398 }
2399 }
2400 }
2401
2402 return;
2403 }
2404 }
2405 return;
2406 }
2407 return;
2408 }
2409
2410 set_pos(x(), y() + 1);
2411 }
2412
2413 set_pos(x(), start_y);
2414 std::cerr << "MOVE_TO_STANDING FAILED\n";
2415 }
2416
2417
2418 bool custom_object::dies_on_inactive() const
2419 {
2420 return type_->dies_on_inactive();
2421 }
2422
2423 bool custom_object::always_active() const
2424 {
2425 return always_active_ || type_->always_active();
2426 }
2427
2428 bool custom_object::body_harmful() const
2429 {
2430 return type_->body_harmful();
2431 }
2432
2433 bool custom_object::body_passthrough() const
2434 {
2435 return type_->body_passthrough();
2436 }
2437
2438 const frame& custom_object::icon_frame() const
2439 {
2440 return type_->default_frame();
2441 }
2442
2443 entity_ptr custom_object::clone() const
2444 {
2445 entity_ptr res(new custom_object(*this));
2446 res->set_distinct_label();
2447 return res;
2448 }
2449
2450 entity_ptr custom_object::backup() const
2451 {
2452 entity_ptr res(new custom_object(*this));
2453 return res;
2454 }
2455
2456 void custom_object::handle_event(const std::string& event, const formula_callable* context)
2457 {
2458 handle_event(get_object_event_id(event), context);
2459 }
2460
2461 void custom_object::handle_event(int event, const formula_callable* context)
2462 {
2463 if(hitpoints_ <= 0 && event != OBJECT_EVENT_DIE) {
2464 return;
2465 }
2466
2467 const game_logic::formula* handlers[2];
2468 int nhandlers = 0;
2469
2470 if(event < event_handlers_.size() && event_handlers_[event]) {
2471 handlers[nhandlers++] = event_handlers_[event].get();
2472 }
2473
2474 const game_logic::formula* type_handler = type_->get_event_handler(event).get();
2475 if(type_handler != NULL) {
2476 handlers[nhandlers++] = type_handler;
2477 }
2478
2479 if(!nhandlers) {
2480 return;
2481 }
2482
2483 backup_callable_stack_.push(context);
2484
2485 for(int n = 0; n != nhandlers; ++n) {
2486 const game_logic::formula* handler = handlers[n];
2487
2488 #ifndef DISABLE_FORMULA_PROFILER
2489 formula_profiler::custom_object_event_frame event_frame = { type_.get(), event, false };
2490 event_call_stack.push_back(event_frame);
2491 #endif
2492
2493 ++events_handled_per_second;
2494
2495 variant var = handler->execute(*this);
2496
2497 #ifndef DISABLE_FORMULA_PROFILER
2498 event_call_stack.back().executing_commands = true;
2499 #endif
2500
2501 const bool result = execute_command(var);
2502 #ifndef DISABLE_FORMULA_PROFILER
2503 event_call_stack.pop_back();
2504 #endif
2505 if(!result) {
2506 break;
2507 }
2508 }
2509
2510 backup_callable_stack_.pop();
2511 }
2512
2513 bool custom_object::execute_command(const variant& var)
2514 {
2515 bool result = true;
2516 if(var.is_null()) { return result; }
2517 if(var.is_list()) {
2518 const int num_elements = var.num_elements();
2519 for(int n = 0; n != num_elements; ++n) {
2520 result = execute_command(var[n]) && result;
2521 }
2522 } else {
2523 custom_object_command_callable* cmd = var.try_convert<custom_object_command_callable>();
2524 if(cmd != NULL) {
2525 cmd->execute(level::current(), *this);
2526 } else {
2527 entity_command_callable* cmd = var.try_convert<entity_command_callable>();
2528 if(cmd != NULL) {
2529 cmd->execute(level::current(), *this);
2530 } else {
2531 if(var.try_convert<swallow_object_command_callable>()) {
2532 result = false;
2533 }
2534 }
2535 }
2536 }
2537
2538 return result;
2539 }
2540
2541 int custom_object::slope_standing_on(int range) const
2542 {
2543 if(!is_standing(level::current())) {
2544 return 0;
2545 }
2546
2547 const int forward = face_right() ? 1 : -1;
2548 const int xpos = feet_x();
2549 int ypos = feet_y();
2550
2551
2552 for(int n = 0; !level::current().standable(xpos, ypos) && n != 10; ++n) {
2553 ++ypos;
2554 }
2555
2556 if(range == 1) {
2557 if(level::current().standable(xpos + forward, ypos - 1) &&
2558 !level::current().standable(xpos - forward, ypos)) {
2559 return 45;
2560 }
2561
2562 if(!level::current().standable(xpos + forward, ypos) &&
2563 level::current().standable(xpos - forward, ypos - 1)) {
2564 return -45;
2565 }
2566
2567 return 0;
2568 } else {
2569 if(!is_standing(level::current())) {
2570 return 0;
2571 }
2572
2573 int y1 = find_ground_level(level::current(), xpos + forward*range, ypos, range+1);
2574 int y2 = find_ground_level(level::current(), xpos - forward*range, ypos, range+1);
2575 while((y1 == INT_MIN || y2 == INT_MIN) && range > 0) {
2576 y1 = find_ground_level(level::current(), xpos + forward*range, ypos, range+1);
2577 y2 = find_ground_level(level::current(), xpos - forward*range, ypos, range+1);
2578 --range;
2579 }
2580
2581 if(range == 0) {
2582 return 0;
2583 }
2584
2585 const int dy = y2 - y1;
2586 const int dx = range*2;
2587 return (dy*45)/dx;
2588 }
2589 }
2590
2591 void custom_object::make_draw_color()
2592 {
2593 if(!draw_color_.get()) {
2594 draw_color_.reset(new graphics::color_transform(draw_color()));
2595 }
2596 }
2597
2598 const graphics::color_transform& custom_object::draw_color() const
2599 {
2600 if(draw_color_.get()) {
2601 return *draw_color_;
2602 }
2603
2604 static const graphics::color_transform white(0xFF, 0xFF, 0xFF, 0xFF);
2605 return white;
2606 }
2607
2608 game_logic::const_formula_ptr custom_object::get_event_handler(int key) const
2609 {
2610 if(key < event_handlers_.size()) {
2611 return event_handlers_[key];
2612 } else {
2613 return game_logic::const_formula_ptr();
2614 }
2615 }
2616
2617 void custom_object::set_event_handler(int key, game_logic::const_formula_ptr f)
2618 {
2619 if(key >= event_handlers_.size()) {
2620 event_handlers_.resize(key+1);
2621 }
2622
2623 event_handlers_[key] = f;
2624 }
2625
2626 bool custom_object::can_interact_with() const
2627 {
2628 return can_interact_with_;
2629 }
2630
2631 std::string custom_object::debug_description() const
2632 {
2633 return type_->id();
2634 }
2635
2636 void custom_object::map_entities(const std::map<entity_ptr, entity_ptr>& m)
2637 {
2638 if(last_hit_by_) {
2639 std::map<entity_ptr, entity_ptr>::const_iterator i = m.find(last_hit_by_);
2640 if(i != m.end()) {
2641 last_hit_by_ = i->second;
2642 }
2643 }
2644 }
2645
2646 void custom_object::add_particle_system(const std::string& key, const std::string& type)
2647 {
2648 particle_systems_[key] = type_->get_particle_system_factory(type)->create(*this);
2649 }
2650
2651 void custom_object::remove_particle_system(const std::string& key)
2652 {
2653 particle_systems_.erase(key);
2654 }
2655
2656 void custom_object::set_text(const std::string& text, const std::string& font, int size)
2657 {
2658 text_.reset(new custom_object_text);
2659 text_->text = text;
2660 text_->font = graphical_font::get(font);
2661 text_->size = size;
2662 text_->alpha = 255;
2663 ASSERT_LOG(text_->font, "UNKNOWN FONT: " << font);
2664 text_->dimensions = text_->font->dimensions(text_->text);
2665 }
2666
2667 bool custom_object::boardable_vehicle() const
2668 {
2669 return type_->is_vehicle() && driver_.get() == NULL;
2670 }
2671
2672 void custom_object::boarded(level& lvl, const entity_ptr& player)
2673 {
2674 if(!player) {
2675 return;
2676 }
2677
2678 player->board_vehicle();
2679
2680 if(player->is_human()) {
2681 playable_custom_object* new_player(new playable_custom_object(*this));
2682 new_player->driver_ = player;
2683
2684 lvl.add_player(new_player);
2685
2686 new_player->get_player_info()->swap_player_state(*player->get_player_info());
2687 lvl.remove_character(this);
2688 } else {
2689 driver_ = player;
2690 lvl.remove_character(player);
2691 }
2692 }
2693
2694 void custom_object::unboarded(level& lvl)
2695 {
2696 if(velocity_x() > 100) {
2697 driver_->set_face_right(false);
2698 }
2699
2700 if(velocity_x() < -100) {
2701 driver_->set_face_right(true);
2702 }
2703
2704 if(is_human()) {
2705 custom_object* vehicle(new custom_object(*this));
2706 vehicle->driver_ = entity_ptr();
2707 lvl.add_character(vehicle);
2708
2709 lvl.add_player(driver_);
2710
2711 driver_->unboard_vehicle();
2712
2713 driver_->get_player_info()->swap_player_state(*get_player_info());
2714 } else {
2715 lvl.add_character(driver_);
2716 driver_->unboard_vehicle();
2717 driver_ = entity_ptr();
2718 }
2719 }
2720
2721 void custom_object::board_vehicle()
2722 {
2723 }
2724
2725 void custom_object::unboard_vehicle()
2726 {
2727 }
2728
2729 void custom_object::set_blur(const blur_info* blur)
2730 {
2731 if(blur) {
2732 if(blur_) {
2733 blur_->copy_settings(*blur);
2734 } else {
2735 blur_.reset(new blur_info(*blur));
2736 }
2737 } else {
2738 blur_.reset();
2739 }
2740 }
2741
2742 void custom_object::set_sound_volume(const int sound_volume)
2743 {
2744 sound::change_volume(this, sound_volume);
2745 sound_volume_ = sound_volume;
2746 }
2747
2748 bool custom_object::allow_level_collisions() const
2749 {
2750 return type_->use_image_for_collisions();
2751 }
2752
2753 BENCHMARK(custom_object_spike) {
2754 static level* lvl = NULL;
2755 if(!lvl) {
2756 lvl = new level("test.cfg");
2757 static variant v(lvl);
2758 lvl->finish_loading();
2759 lvl->set_as_current_level();
2760 }
2761 BENCHMARK_LOOP {
2762 custom_object* obj = new custom_object("chain_base", 0, 0, false);
2763 variant v(obj);
2764 obj->handle_event(OBJECT_EVENT_CREATE);
2765 }
2766 }
2767
2768 int custom_object::events_handled_per_second = 0;
2769
2770 BENCHMARK_ARG(custom_object_get_attr, const std::string& attr)
2771 {
2772 static custom_object* obj = new custom_object("ant_black", 0, 0, false);
2773 BENCHMARK_LOOP {
2774 obj->query_value(attr);
2775 }
2776 }
2777
2778 BENCHMARK_ARG_CALL(custom_object_get_attr, easy_lookup, "x");
2779 BENCHMARK_ARG_CALL(custom_object_get_attr, hard_lookup, "xxxx");
2780
2781 BENCHMARK_ARG(custom_object_formula, const std::string& f)
2782 {
2783 static custom_object* obj = new custom_object("ant_black", 0, 0, false);
2784 const game_logic::formula fml(f, NULL, &custom_object_type::get("ant_black")->callable_definition());
2785 BENCHMARK_LOOP {
2786 fml.execute(*obj);
2787 }
2788 }
2789
2790 BENCHMARK_ARG_CALL_COMMAND_LINE(custom_object_formula);
2791
2792 BENCHMARK_ARG(custom_object_handle_event, const std::string& object_event)
2793 {
2794 std::string::const_iterator i = std::find(object_event.begin(), object_event.end(), ':');
2795 ASSERT_LOG(i != object_event.end(), "custom_object_event_handle argument must have a colon seperator: " << object_event);
2796 std::string obj_type(object_event.begin(), i);
2797 std::string event_name(i+1, object_event.end());
2798 static level* lvl = new level("titlescreen.cfg");
2799 lvl->set_as_current_level();
2800 static custom_object* obj = new custom_object(obj_type, 0, 0, false);
2801 obj->set_level(*lvl);
2802 const int event_id = get_object_event_id(event_name);
2803 BENCHMARK_LOOP {
2804 obj->handle_event(event_id);
2805 }
2806 }
2807
2808 BENCHMARK_ARG_CALL(custom_object_handle_event, ant_non_exist, "ant_black:blahblah");
2809
2810 BENCHMARK_ARG_CALL_COMMAND_LINE(custom_object_handle_event);
0 #ifndef CUSTOM_OBJECT_HPP_INCLUDED
1 #define CUSTOM_OBJECT_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4 #include <inttypes.h>
5 #include <stack>
6
7 #include "blur.hpp"
8 #include "color_utils.hpp"
9 #include "custom_object_type.hpp"
10 #include "entity.hpp"
11 #include "formula.hpp"
12 #include "formula_callable.hpp"
13 #include "formula_variable_storage.hpp"
14 #include "particle_system.hpp"
15 #include "raster_distortion.hpp"
16 #include "variant.hpp"
17 #include "wml_node_fwd.hpp"
18
19 class collision_info;
20 class level;
21
22 struct custom_object_text;
23
24 class custom_object : public entity
25 {
26 public:
27 static void init();
28
29 explicit custom_object(wml::const_node_ptr node);
30 custom_object(const std::string& type, int x, int y, bool face_right);
31 custom_object(const custom_object& o);
32 virtual ~custom_object();
33 virtual wml::node_ptr write() const;
34 virtual void setup_drawing() const;
35 virtual void draw() const;
36 virtual void draw_group() const;
37 virtual void process(level& lvl);
38 void set_level(level& lvl) { }
39
40 virtual int zorder() const;
41
42 virtual int velocity_x() const;
43 virtual int velocity_y() const;
44 virtual int mass() const { return type_->mass(); }
45
46 int teleport_offset_x() const { return type_->teleport_offset_x(); }
47 int teleport_offset_y() const { return type_->teleport_offset_y(); }
48
49 bool has_feet() const;
50
51 virtual bool is_standable(int x, int y, int* friction=NULL, int* traction=NULL, int* adjust_y=NULL) const;
52
53 virtual bool destroyed() const;
54 virtual bool point_collides(int x, int y) const;
55 virtual bool rect_collides(const rect& r) const;
56
57 virtual const frame& current_frame() const { return *frame_; }
58
59 void set_frame(const std::string& name);
60
61 virtual rect draw_rect() const;
62
63 //bare setting of the frame without adjusting position/checking solidity
64 //etc etc.
65 void set_frame_no_adjustments(const std::string& name);
66 void die();
67 virtual bool is_active(const rect& screen_area) const;
68 bool dies_on_inactive() const;
69 bool always_active() const;
70 void move_to_standing(level& lvl);
71
72 bool body_harmful() const;
73 bool body_passthrough() const;
74
75 int time_in_frame() const { return time_in_frame_; }
76
77 formula_callable* vars() { return vars_.get(); }
78 const formula_callable* vars() const { return vars_.get(); }
79
80 int cycle() const { return cycle_; }
81
82 int surface_friction() const;
83 int surface_traction() const;
84
85 wml::const_node_ptr get_child(const std::string& key) const {
86 return type_->get_child(key);
87 }
88
89 const frame& icon_frame() const;
90
91 virtual entity_ptr clone() const;
92 virtual entity_ptr backup() const;
93
94 game_logic::const_formula_ptr get_event_handler(int key) const;
95 void set_event_handler(int, game_logic::const_formula_ptr f);
96
97 bool can_interact_with() const;
98
99 std::string debug_description() const;
100
101 void map_entities(const std::map<entity_ptr, entity_ptr>& m);
102
103 void add_particle_system(const std::string& key, const std::string& type);
104 void remove_particle_system(const std::string& key);
105
106 void set_text(const std::string& text, const std::string& font, int size);
107
108 virtual int hitpoints() const { return hitpoints_; }
109
110 virtual bool boardable_vehicle() const;
111
112 virtual void boarded(level& lvl, const entity_ptr& player);
113 virtual void unboarded(level& lvl);
114
115 virtual void board_vehicle();
116 virtual void unboard_vehicle();
117
118 void set_driver_position();
119
120 virtual int current_animation_id() const { return current_animation_id_; }
121
122 virtual const_editor_entity_info_ptr editor_info() const;
123
124 virtual void handle_event(const std::string& event, const formula_callable* context=NULL);
125 virtual void handle_event(int event, const formula_callable* context=NULL);
126
127 void set_blur(const blur_info* blur);
128 void set_sound_volume(const int volume);
129
130 bool execute_command(const variant& var);
131
132 bool allow_level_collisions() const;
133
134 //statistic on how many FFL events are handled every second.
135 static int events_handled_per_second;
136
137 protected:
138 virtual void control(const level& lvl);
139 variant get_value(const std::string& key) const;
140 variant get_value_by_slot(int slot) const;
141 void set_value(const std::string& key, const variant& value);
142 void set_value_by_slot(int slot, const variant& value);
143
144 //function which indicates if the object wants to walk up or down stairs.
145 //-1 = up stairs, 0 = no change, 1 = down stairs
146 virtual int walk_up_or_down_stairs() const { return 0; }
147
148 bool is_underwater() const {
149 return was_underwater_;
150 }
151
152 const std::pair<int,int>* position_scale_millis() const { return position_scale_millis_.get(); }
153
154 private:
155 custom_object& operator=(const custom_object& o);
156 struct Accessor;
157
158 void process_frame();
159
160 const solid_info* calculate_solid() const;
161 const solid_info* calculate_platform() const;
162
163 bool is_standing(const level& lvl, collision_info* info=NULL) const;
164
165 void get_inputs(std::vector<game_logic::formula_input>* inputs) const;
166
167 int slope_standing_on(int range) const;
168
169 int previous_y_;
170
171 wml::const_node_ptr custom_type_;
172 const_custom_object_type_ptr type_; //the type after variations are applied
173 const_custom_object_type_ptr base_type_; //the type without any variation
174 std::vector<std::string> current_variation_;
175 const frame* frame_;
176 std::string frame_name_;
177 int time_in_frame_;
178 int time_in_frame_delta_;
179
180 int velocity_x_, velocity_y_;
181 int accel_x_, accel_y_;
182 int rotate_;
183
184 boost::scoped_ptr<std::pair<int, int> > position_scale_millis_;
185
186 int zorder_;
187
188 int hitpoints_, max_hitpoints_;
189 bool was_underwater_;
190
191 bool has_feet_;
192
193 int invincible_;
194
195 int sound_volume_; //see sound.cpp; valid values are 0-128, note that this affects all sounds spawned by this object
196
197 game_logic::const_formula_ptr next_animation_formula_;
198
199 game_logic::formula_variable_storage_ptr vars_, tmp_vars_;
200 game_logic::map_formula_callable_ptr tags_;
201
202 entity_ptr last_hit_by_;
203 int last_hit_by_anim_;
204 int current_animation_id_;
205
206 int cycle_;
207
208 //variable which is always set to false on construction, and then the
209 //first time process is called will fire the on_load event and set to false
210 bool loaded_;
211
212 std::vector<game_logic::const_formula_ptr> event_handlers_;
213
214 entity_ptr standing_on_;
215
216 int standing_on_prev_x_, standing_on_prev_y_;
217
218 graphics::const_raster_distortion_ptr distortion_;
219
220 void make_draw_color();
221 const graphics::color_transform& draw_color() const;
222 boost::shared_ptr<graphics::color_transform> draw_color_;
223
224 boost::shared_ptr<rect> draw_area_, activation_area_;
225
226 bool can_interact_with_;
227
228 std::map<std::string, particle_system_ptr> particle_systems_;
229
230 typedef boost::shared_ptr<custom_object_text> custom_object_text_ptr;
231 custom_object_text_ptr text_;
232
233 entity_ptr driver_;
234
235 boost::shared_ptr<blur_info> blur_;
236
237 //set if we should fall through platforms. This is decremented automatically
238 //at the end of every cycle.
239 int fall_through_platforms_;
240
241 //current shader we're using to draw with.
242 std::vector<std::string> fragment_shaders_, vertex_shaders_;
243 mutable GLuint shader_;
244
245 mutable game_logic::map_formula_callable_ptr shader_vars_;
246
247 bool always_active_;
248
249 std::stack<const formula_callable*> backup_callable_stack_;
250
251 int last_cycle_active_;
252
253 struct position_schedule {
254 position_schedule() : speed(1) {}
255 int speed;
256 std::vector<int> x_pos;
257 std::vector<int> y_pos;
258 std::vector<int> rotation;
259 };
260
261 boost::scoped_ptr<position_schedule> position_schedule_;
262 };
263
264 #endif
0 #include "asserts.hpp"
1 #include "custom_object_callable.hpp"
2
3 namespace {
4 std::vector<custom_object_callable::entry>& entries() {
5 static std::vector<custom_object_callable::entry> instance;
6 return instance;
7 }
8
9 std::map<std::string, int>& keys_to_slots() {
10 static std::map<std::string, int> instance;
11 return instance;
12 }
13
14 }
15
16 const custom_object_callable& custom_object_callable::instance()
17 {
18 static const custom_object_callable obj;
19 return obj;
20 }
21
22 custom_object_callable::custom_object_callable()
23 {
24
25 static const std::string CustomObjectProperties[] = {
26 "consts", "type", "active",
27 "time_in_animation", "time_in_animation_delta", "level", "animation",
28 "hitpoints", "max_hitpoints", "mass", "label", "x", "y", "z", "zorder",
29 "previous_y", "x1", "x2", "y1", "y2", "w", "h", "midpoint_x", "midpoint_y",
30 "solid_rect", "img_w", "img_h", "front", "back", "cycle", "facing",
31 "upside_down", "up", "down", "velocity_x", "velocity_y",
32 "accel_x", "accel_y", "vars", "tmp", "group", "rotate", "me", "self",
33 "red", "green", "blue", "alpha", "text_alpha", "damage", "hit_by",
34 "distortion", "is_standing", "near_cliff_edge", "distance_to_cliff",
35 "slope_standing_on", "underwater", "water_bounds", "water_object",
36 "driver", "is_human", "invincible",
37 "sound_volume", "destroyed", "is_standing_on_platform", "standing_on",
38 "fragment_shaders", "vertex_shaders", "shader", "variations",
39 "attached_objects", "call_stack",
40 "solid_dimensions_in", "solid_dimensions_not_in",
41 "collide_dimensions_in", "collide_dimensions_not_in",
42 "brightness", "current_generator", "tags", "draw_area", "activation_area",
43 "always_active", "fall_through_platforms", "has_feet",
44 "x_schedule", "y_schedule", "rotation_schedule", "schedule_speed",
45 "ctrl_up", "ctrl_down", "ctrl_left", "ctrl_right",
46 "ctrl_attack", "ctrl_jump", "ctrl_tongue",
47 };
48 ASSERT_EQ(NUM_CUSTOM_OBJECT_PROPERTIES, sizeof(CustomObjectProperties)/sizeof(*CustomObjectProperties));
49
50 if(entries().empty()) {
51 for(int n = 0; n != sizeof(CustomObjectProperties)/sizeof(*CustomObjectProperties); ++n) {
52 entries().push_back(entry(CustomObjectProperties[n]));
53 }
54
55 for(int n = 0; n != entries().size(); ++n) {
56 keys_to_slots()[entries()[n].id] = n;
57 }
58 }
59 }
60
61 int custom_object_callable::get_key_slot(const std::string& key)
62 {
63 std::map<std::string, int>::const_iterator itor = keys_to_slots().find(key);
64 if(itor == keys_to_slots().end()) {
65 return -1;
66 }
67
68 return itor->second;
69 }
70
71 int custom_object_callable::get_slot(const std::string& key) const
72 {
73 return get_key_slot(key);
74 }
75
76 game_logic::formula_callable_definition::entry* custom_object_callable::get_entry(int slot)
77 {
78 if(slot < 0 || slot >= entries().size()) {
79 return NULL;
80 }
81
82 return &entries()[slot];
83 }
84
85 const game_logic::formula_callable_definition::entry* custom_object_callable::get_entry(int slot) const
86 {
87 if(slot < 0 || slot >= entries().size()) {
88 return NULL;
89 }
90
91 return &entries()[slot];
92 }
0 #ifndef CUSTOM_OBJECT_CALLABLE_HPP_INCLUDED
1 #define CUSTOM_OBJECT_CALLABLE_HPP_INCLUDED
2
3 #include <map>
4 #include <vector>
5
6 #include "formula_callable_definition.hpp"
7
8 enum CUSTOM_OBJECT_PROPERTY {
9 CUSTOM_OBJECT_CONSTS,
10 CUSTOM_OBJECT_TYPE,
11 CUSTOM_OBJECT_ACTIVE,
12 CUSTOM_OBJECT_TIME_IN_ANIMATION,
13 CUSTOM_OBJECT_TIME_IN_ANIMATION_DELTA,
14 CUSTOM_OBJECT_LEVEL,
15 CUSTOM_OBJECT_ANIMATION,
16 CUSTOM_OBJECT_HITPOINTS,
17 CUSTOM_OBJECT_MAX_HITPOINTS,
18 CUSTOM_OBJECT_MASS,
19 CUSTOM_OBJECT_LABEL,
20 CUSTOM_OBJECT_X,
21 CUSTOM_OBJECT_Y,
22 CUSTOM_OBJECT_Z,
23 CUSTOM_OBJECT_ZORDER,
24 CUSTOM_OBJECT_PREVIOUS_Y,
25 CUSTOM_OBJECT_X1,
26 CUSTOM_OBJECT_X2,
27 CUSTOM_OBJECT_Y1,
28 CUSTOM_OBJECT_Y2,
29 CUSTOM_OBJECT_W,
30 CUSTOM_OBJECT_H,
31 CUSTOM_OBJECT_MIDPOINT_X,
32 CUSTOM_OBJECT_MIDPOINT_Y,
33 CUSTOM_OBJECT_SOLID_RECT,
34 CUSTOM_OBJECT_IMG_W,
35 CUSTOM_OBJECT_IMG_H,
36 CUSTOM_OBJECT_FRONT,
37 CUSTOM_OBJECT_BACK,
38 CUSTOM_OBJECT_CYCLE,
39 CUSTOM_OBJECT_FACING,
40 CUSTOM_OBJECT_UPSIDE_DOWN,
41 CUSTOM_OBJECT_UP,
42 CUSTOM_OBJECT_DOWN,
43 CUSTOM_OBJECT_VELOCITY_X,
44 CUSTOM_OBJECT_VELOCITY_Y,
45 CUSTOM_OBJECT_ACCEL_X,
46 CUSTOM_OBJECT_ACCEL_Y,
47 CUSTOM_OBJECT_VARS,
48 CUSTOM_OBJECT_TMP,
49 CUSTOM_OBJECT_GROUP,
50 CUSTOM_OBJECT_ROTATE,
51 CUSTOM_OBJECT_ME,
52 CUSTOM_OBJECT_SELF,
53 CUSTOM_OBJECT_RED,
54 CUSTOM_OBJECT_GREEN,
55 CUSTOM_OBJECT_BLUE,
56 CUSTOM_OBJECT_ALPHA,
57 CUSTOM_OBJECT_TEXT_ALPHA,
58 CUSTOM_OBJECT_DAMAGE,
59 CUSTOM_OBJECT_HIT_BY,
60 CUSTOM_OBJECT_DISTORTION,
61 CUSTOM_OBJECT_IS_STANDING,
62 CUSTOM_OBJECT_NEAR_CLIFF_EDGE,
63 CUSTOM_OBJECT_DISTANCE_TO_CLIFF,
64 CUSTOM_OBJECT_SLOPE_STANDING_ON,
65 CUSTOM_OBJECT_UNDERWATER,
66 CUSTOM_OBJECT_WATER_BOUNDS,
67 CUSTOM_OBJECT_WATER_OBJECT,
68 CUSTOM_OBJECT_DRIVER,
69 CUSTOM_OBJECT_IS_HUMAN,
70 CUSTOM_OBJECT_INVINCIBLE,
71 CUSTOM_OBJECT_SOUND_VOLUME,
72 CUSTOM_OBJECT_DESTROYED,
73 CUSTOM_OBJECT_IS_STANDING_ON_PLATFORM,
74 CUSTOM_OBJECT_STANDING_ON,
75 CUSTOM_OBJECT_FRAGMENT_SHADERS,
76 CUSTOM_OBJECT_VERTEX_SHADERS,
77 CUSTOM_OBJECT_SHADER,
78 CUSTOM_OBJECT_VARIATIONS,
79 CUSTOM_OBJECT_ATTACHED_OBJECTS,
80 CUSTOM_OBJECT_CALL_STACK,
81 CUSTOM_OBJECT_SOLID_DIMENSIONS_IN,
82 CUSTOM_OBJECT_SOLID_DIMENSIONS_NOT_IN,
83 CUSTOM_OBJECT_COLLIDE_DIMENSIONS_IN,
84 CUSTOM_OBJECT_COLLIDE_DIMENSIONS_NOT_IN,
85 CUSTOM_OBJECT_BRIGHTNESS,
86 CUSTOM_OBJECT_CURRENT_GENERATOR,
87 CUSTOM_OBJECT_TAGS,
88 CUSTOM_OBJECT_DRAW_AREA,
89 CUSTOM_OBJECT_ACTIVATION_AREA,
90 CUSTOM_OBJECT_ALWAYS_ACTIVE,
91 CUSTOM_OBJECT_FALL_THROUGH_PLATFORMS,
92 CUSTOM_OBJECT_HAS_FEET,
93 CUSTOM_OBJECT_X_SCHEDULE,
94 CUSTOM_OBJECT_Y_SCHEDULE,
95 CUSTOM_OBJECT_ROTATION_SCHEDULE,
96 CUSTOM_OBJECT_SCHEDULE_SPEED,
97 CUSTOM_OBJECT_CTRL_UP,
98 CUSTOM_OBJECT_CTRL_DOWN,
99 CUSTOM_OBJECT_CTRL_LEFT,
100 CUSTOM_OBJECT_CTRL_RIGHT,
101 CUSTOM_OBJECT_CTRL_ATTACK,
102 CUSTOM_OBJECT_CTRL_JUMP,
103 CUSTOM_OBJECT_CTRL_TONGUE,
104 NUM_CUSTOM_OBJECT_PROPERTIES
105 };
106
107 class custom_object_callable : public game_logic::formula_callable_definition
108 {
109 public:
110 static const custom_object_callable& instance();
111
112 static int get_key_slot(const std::string& key);
113
114 custom_object_callable();
115
116 int get_slot(const std::string& key) const;
117 entry* get_entry(int slot);
118 const entry* get_entry(int slot) const;
119 int num_slots() const { return NUM_CUSTOM_OBJECT_PROPERTIES; }
120 };
121
122 #endif
0 #include <boost/bind.hpp>
1 #include <iostream>
2 #include <map>
3 #include <string>
4 #include <time.h>
5
6 #include "asserts.hpp"
7 #include "blur.hpp"
8 #include "collision_utils.hpp"
9 #include "controls.hpp"
10 #include "current_generator.hpp"
11 #include "custom_object_functions.hpp"
12 #include "custom_object.hpp"
13 #include "debug_console.hpp"
14 #include "draw_scene.hpp"
15 #include "entity.hpp"
16 #include "filesystem.hpp"
17 #include "formula_callable_definition.hpp"
18 #include "formula_profiler.hpp"
19 #include "level.hpp"
20 #include "level_runner.hpp"
21 #include "object_events.hpp"
22 #include "pause_game_dialog.hpp"
23 #include "player_info.hpp"
24 #include "powerup.hpp"
25 #include "raster.hpp"
26 #include "texture.hpp"
27 #include "message_dialog.hpp"
28 #include "options_dialog.hpp"
29 #include "playable_custom_object.hpp"
30 #include "preferences.hpp"
31 #include "random.hpp"
32 #include "raster_distortion.hpp"
33 #include "sound.hpp"
34 #include "speech_dialog.hpp"
35 #include "string_utils.hpp"
36 #include "text_entry_widget.hpp"
37 #include "thread.hpp"
38 #include "unit_test.hpp"
39 #include "wml_parser.hpp"
40 #include "wml_node.hpp"
41 #include "wml_utils.hpp"
42 #include "wml_writer.hpp"
43 #include "preferences.hpp"
44
45 using namespace game_logic;
46
47 namespace {
48
49 class function_creator {
50 public:
51 virtual ~function_creator() {}
52 virtual function_expression* create(const function_expression::args_list& args) const = 0;
53 };
54
55 template<typename T>
56 class specific_function_creator : public function_creator {
57 public:
58 virtual ~specific_function_creator() {}
59 virtual function_expression* create(const function_expression::args_list& args) const {
60 return new T(args);
61 }
62 };
63
64 std::map<std::string, function_creator*>& function_creators() {
65 static std::map<std::string, function_creator*> creators;
66 return creators;
67 }
68
69 int register_function_creator(const std::string& id, function_creator* creator) {
70 function_creators()[id] = creator;
71 return function_creators().size();
72 }
73
74 std::vector<std::string>& function_helpstrings() {
75 static std::vector<std::string> instance;
76 return instance;
77 }
78
79 int register_function_helpstring(const std::string& str) {
80 function_helpstrings().push_back(str);
81 return function_helpstrings().size();
82 }
83
84 #define FUNCTION_DEF(name, min_args, max_args, helpstring) \
85 const int name##_dummy_help_var = register_function_helpstring(helpstring); \
86 class name##_function : public function_expression { \
87 public: \
88 explicit name##_function(const args_list& args) \
89 : function_expression(#name, args, min_args, max_args) {} \
90 private: \
91 variant execute(const formula_callable& variables) const {
92
93 #define END_FUNCTION_DEF(name) } }; const int name##_dummy_var = register_function_creator(#name, new specific_function_creator<name##_function>());
94
95 FUNCTION_DEF(time, 0, 0, "time() -> timestamp: returns the current real time")
96 rng::generate(); //this is to make the engine optimisation leave this function alone.
97 time_t t1;
98 time(&t1);
99 return variant(t1);
100 END_FUNCTION_DEF(time)
101
102 class save_game_command : public entity_command_callable
103 {
104 bool persistent_;
105 public:
106 explicit save_game_command(bool persistent) : persistent_(persistent)
107 {}
108
109 virtual void execute(level& lvl, entity& ob) const {
110 lvl.player()->get_entity().save_game();
111 if(persistent_) {
112 wml::node_ptr node = lvl.write();
113 if(sound::current_music().empty() == false) {
114 node->set_attr("music", sound::current_music());
115 }
116
117 sys::write_file(preferences::save_file_path(), wml::output(node));
118 }
119 }
120 };
121
122 FUNCTION_DEF(checkpoint_game, 0, 0, "checkpoint_game(): saves a checkpoint of the game")
123 return variant(new save_game_command(false));
124 END_FUNCTION_DEF(checkpoint_game)
125
126 FUNCTION_DEF(save_game, 0, 0, "save_game(): saves the current game state")
127 return variant(new save_game_command(true));
128 END_FUNCTION_DEF(save_game)
129
130 class load_game_command : public entity_command_callable
131 {
132 public:
133 virtual void execute(level& lvl, entity& ob) const {
134 level::portal p;
135 p.level_dest = "save.cfg";
136 p.dest_starting_pos = true;
137 p.saved_game = true;
138 lvl.force_enter_portal(p);
139 }
140 };
141
142 FUNCTION_DEF(load_game, 0, 0, "load_game(): loads the saved game")
143 return variant(new load_game_command());
144 END_FUNCTION_DEF(load_game)
145
146 FUNCTION_DEF(can_load_game, 0, 0, "can_load_game(): returns true iff there is a saved game available to load")
147 return variant(sys::file_exists(preferences::save_file_path()));
148 END_FUNCTION_DEF(can_load_game)
149
150 class move_to_standing_command : public entity_command_callable
151 {
152 public:
153 virtual void execute(level& lvl, entity& ob) const {
154 ob.move_to_standing(lvl);
155 }
156 };
157
158 FUNCTION_DEF(move_to_standing, 0, 0, "move_to_standing(): tries to move the object downwards if it's in the air, or upwards if it's in solid space, until it's standing on solid ground.");
159 return variant(new move_to_standing_command());
160 END_FUNCTION_DEF(move_to_standing)
161
162 class music_command : public entity_command_callable
163 {
164 public:
165 explicit music_command(const std::string& name, const bool loops)
166 : name_(name), loops_(loops)
167 {}
168 virtual void execute(level& lvl, entity& ob) const {
169 if(loops_){
170 sound::play_music(name_);
171 }else{
172 sound::play_music_interrupt(name_);
173 }
174 }
175 private:
176 std::string name_;
177 bool loops_;
178 };
179
180 FUNCTION_DEF(music, 1, 1, "music(string id): plays the music file given by 'id' in a loop")
181 return variant(new music_command(
182 args()[0]->evaluate(variables).as_string(),
183 true));
184 END_FUNCTION_DEF(music)
185
186 FUNCTION_DEF(music_onetime, 1, 1, "music_onetime(string id): plays the music file given by 'id' once")
187 return variant(new music_command(
188 args()[0]->evaluate(variables).as_string(),
189 false));
190 END_FUNCTION_DEF(music_onetime)
191
192 class sound_command : public entity_command_callable
193 {
194 public:
195 explicit sound_command(const std::string& name, const bool loops)
196 : name_(name), loops_(loops)
197 {}
198 virtual void execute(level& lvl, entity& ob) const {
199 if(loops_){
200 sound::play_looped(name_, &ob);
201 }else{
202 std::cerr << "PLAY SOUND COMMAND: " << name_ << "\n";
203 sound::play(name_, &ob);
204 }
205 }
206 private:
207 std::string name_;
208 bool loops_;
209 };
210
211 FUNCTION_DEF(sound, 1, 1, "sound(string id): plays the sound file given by 'id'")
212 return variant(new sound_command(
213 args()[0]->evaluate(variables).as_string(),
214 false));
215 END_FUNCTION_DEF(sound)
216
217 FUNCTION_DEF(sound_loop, 1, 1, "sound_loop(string id): plays the sound file given by 'id' in a loop")
218 return variant(new sound_command(
219 args()[0]->evaluate(variables).as_string(),
220 true));
221 END_FUNCTION_DEF(sound_loop)
222
223
224 class sound_volume_command : public entity_command_callable {
225 public:
226 explicit sound_volume_command(const int volume)
227 : volume_(volume)
228 {}
229 virtual void execute(level& lvl,entity& ob) const {
230 //sound::change_volume(&ob, volume_);
231 ob.set_sound_volume(volume_);
232 }
233 private:
234 std::string name_;
235 int volume_;
236 };
237
238 FUNCTION_DEF(sound_volume, 1, 1, "sound_volume(int volume): sets the volume of sound effects")
239 return variant(new sound_volume_command(
240 args()[0]->evaluate(variables).as_int()));
241 END_FUNCTION_DEF(sound_volume)
242
243 class stop_sound_command : public entity_command_callable
244 {
245 public:
246 explicit stop_sound_command(const std::string& name)
247 : name_(name)
248 {}
249 virtual void execute(level& lvl, entity& ob) const {
250 sound::stop_sound(name_, &ob);
251 }
252 private:
253 std::string name_;
254 };
255
256 FUNCTION_DEF(stop_sound, 1, 1, "stop_sound(string id): stops the sound that the current object is playing with the given id")
257 return variant(new stop_sound_command(
258 args()[0]->evaluate(variables).as_string()));
259 END_FUNCTION_DEF(stop_sound)
260
261 class screen_flash_command : public entity_command_callable
262 {
263 public:
264 screen_flash_command(const graphics::color_transform& color,
265 const graphics::color_transform& delta, int duration)
266 : color_(color), delta_(delta), duration_(duration)
267 {}
268
269 virtual void execute(level& lvl, entity& ob) const {
270 screen_color_flash(color_, delta_, duration_);
271 }
272 private:
273 graphics::color_transform color_, delta_;
274 int duration_;
275 };
276
277 FUNCTION_DEF(screen_flash, 2, 3, "screen_flash(list int[4] color, (optional) list int[4] delta, int duration): flashes the screen the given color, and keeps the flash going for duration cycles. If delta is given, the color of the flash will be changed every cycle until the duration expires.")
278 const variant color = args()[0]->evaluate(variables);
279 const variant delta = args().size() > 2 ? args()[1]->evaluate(variables) : variant();
280 const variant duration = args()[args().size() - 1]->evaluate(variables);
281 ASSERT_LOG(color.is_list() && color.num_elements() == 4 &&
282 (delta.is_null() || delta.is_list() && delta.num_elements() == 4) &&
283 duration.is_int(),
284 "BAD ARGUMENT TO screen_flash() FUNCTION: ARGUMENT FORMAT "
285 "IS screen_flash([r,g,b,a], (optional)[dr,dg,db,da], duration)");
286 graphics::color_transform delta_color = graphics::color_transform(0,0,0,0);
287 if(delta.is_null() == false) {
288 delta_color = graphics::color_transform(
289 delta[0].as_int(), delta[1].as_int(),
290 delta[2].as_int(), delta[3].as_int());
291 }
292
293 return variant(new screen_flash_command(
294 graphics::color_transform(color[0].as_int(), color[1].as_int(),
295 color[2].as_int(), color[3].as_int()),
296 delta_color, duration.as_int()));
297 END_FUNCTION_DEF(screen_flash)
298
299 class title_command : public entity_command_callable
300 {
301 public:
302 title_command(const std::string& title, int duration)
303 : title_(title), duration_(duration)
304 {}
305
306 virtual void execute(level& lvl, entity& ob) const {
307 set_scene_title(title_, duration_);
308 }
309 private:
310 std::string title_;
311 int duration_;
312 };
313
314 FUNCTION_DEF(title, 1, 2, "title(string text, int duration=50): shows level title text on the screen for duration cycles")
315 return variant(new title_command(
316 args()[0]->evaluate(variables).as_string(),
317 args().size() >= 2 ? args()[1]->evaluate(variables).as_int() : 50));
318 END_FUNCTION_DEF(title)
319
320 class shake_screen_command : public entity_command_callable
321 {
322 public:
323 explicit shake_screen_command(int x_offset, int y_offset, int x_velocity, int y_velocity)
324 : x_offset_(x_offset), y_offset_(y_offset), x_velocity_(x_velocity), y_velocity_(y_velocity)
325 {}
326 virtual void execute(level& lvl, entity& ob) const {
327 screen_position& screen_pos = last_draw_position();
328
329 screen_pos.shake_x_offset = x_offset_;
330 screen_pos.shake_y_offset = y_offset_;
331 screen_pos.shake_x_vel = x_velocity_;
332 screen_pos.shake_y_vel = y_velocity_;
333 }
334 private:
335 int x_offset_,y_offset_,x_velocity_,y_velocity_;
336 };
337
338 FUNCTION_DEF(shake_screen, 4, 4, "shake_screen(int x_offset, int y_offset, int x_velocity, int y_velocity): makes the screen camera shake")
339 return variant(new shake_screen_command(
340 args()[0]->evaluate(variables).as_int(),
341 args()[1]->evaluate(variables).as_int(),
342 args()[2]->evaluate(variables).as_int(),
343 args()[3]->evaluate(variables).as_int() ));
344 END_FUNCTION_DEF(shake_screen)
345
346 FUNCTION_DEF(radial_current, 2, 2, "radial_current(int intensity, int radius) -> current object: creates a current generator with the given intensity and radius")
347 return variant(new radial_current_generator(args()[0]->evaluate(variables).as_int(), args()[1]->evaluate(variables).as_int()));
348 END_FUNCTION_DEF(radial_current)
349
350 FUNCTION_DEF(distortion, 3, 3, "distortion(int, int, int): (currently unsupported")
351 return variant(new graphics::radial_distortion(
352 args()[0]->evaluate(variables).as_int(),
353 args()[1]->evaluate(variables).as_int(),
354 args()[2]->evaluate(variables).as_int()));
355 END_FUNCTION_DEF(distortion)
356
357 class execute_on_command : public entity_command_callable
358 {
359 entity_ptr e_;
360 variant cmd_;
361 public:
362 execute_on_command(entity_ptr e, variant cmd) : e_(e), cmd_(cmd)
363 {}
364
365 virtual void execute(level& lvl, entity& ob) const {
366 e_->execute_command(cmd_);
367 }
368 };
369
370 FUNCTION_DEF(execute, 2, 2, "execute(object context, command cmd): this function will execute the command or list of commands given by cmd on the object given by context. For instance, animation('foo') will set the current object to animation 'foo'. execute(obj, animation('foo')) can be used to set the object given by obj to the animation 'foo'.")
371 entity_ptr e(args()[0]->evaluate(variables).convert_to<entity>());
372 variant cmd = args()[1]->evaluate(variables);
373 return variant(new execute_on_command(e, cmd));
374 END_FUNCTION_DEF(execute)
375
376 class spawn_command : public entity_command_callable
377 {
378 public:
379 spawn_command(const std::string& type, int x, int y, bool face_right, variant instantiation_commands, bool player)
380 : type_(type), x_(x), y_(y), face_right_(face_right), instantiation_commands_(instantiation_commands), player_(player)
381 {}
382 virtual void execute(level& lvl, entity& ob) const {
383 custom_object* obj = new custom_object(type_, x_, y_, face_right_);
384 if(player_) {
385 //make a playable version of this object
386 custom_object* player = new playable_custom_object(*obj);
387 delete obj;
388 obj = player;
389 }
390
391 obj->set_level(lvl);
392 entity_ptr e = obj;
393
394 //spawn with the spawned object's midpoint (rather than its upper-left corner) at x_, y_.
395 //This means objects are centered on the point they're spawned on, which is a lot more intuitive for scripting.
396 e->set_pos(e->x() - e->current_frame().width() / 2 , e->y() - e->current_frame().height() / 2);
397
398 if(!place_entity_in_level(lvl, *obj)) {
399 //the object can't immediately/easily be placed in the level
400 //due to a solid collision. Try to incrementally push it in
401 //different directions and try to place it until we find
402 //a direciton that works.
403 const int xpos = obj->x();
404 const int ypos = obj->y();
405
406 bool found = false;
407 for(int distance = 4; distance < 256 && !found; distance *= 2) {
408 const point points[] = { point(xpos-distance, ypos),
409 point(xpos+distance, ypos),
410 point(xpos, ypos-distance),
411 point(xpos, ypos+distance), };
412 foreach(const point& p, points) {
413 obj->set_pos(p);
414 if(place_entity_in_level(lvl, *obj)) {
415 found = true;
416 break;
417 }
418 }
419 }
420
421 if(!found) {
422 return;
423 }
424 }
425
426 lvl.add_character(e);
427
428 e->execute_command(instantiation_commands_);
429
430 //send an event to the parent to let them know they've spawned a child,
431 //and let them record the child's details.
432 game_logic::map_formula_callable* spawn_callable(new game_logic::map_formula_callable);
433 variant holder(spawn_callable);
434 spawn_callable->add("parent", variant(&ob));
435 spawn_callable->add("child", variant(obj));
436 ob.handle_event("child_spawned", spawn_callable);
437 obj->handle_event("spawned", spawn_callable);
438
439 if(entity_collides(lvl, *e, MOVE_NONE)) {
440 lvl.remove_character(e);
441 }
442 }
443 private:
444 std::string type_;
445 int x_, y_;
446 variant instantiation_commands_;
447 bool face_right_;
448 bool player_;
449 };
450
451 FUNCTION_DEF(spawn, 4, 5, "spawn(string type_id, int midpoint_x, int midpoint_y, int facing, (optional) list of commands cmd): will create a new object of type given by type_id with the given midpoint and facing. Immediately after creation the object will have any commands given by cmd executed on it. The child object will have the spawned event sent to it, and the parent object will have the child_spawned event sent to it.")
452 return variant(new spawn_command(
453 args()[0]->evaluate(variables).as_string(),
454 args()[1]->evaluate(variables).as_int(),
455 args()[2]->evaluate(variables).as_int(),
456 args()[3]->evaluate(variables).as_int() > 0,
457 args().size() > 4 ? args()[4]->evaluate(variables) : variant(),
458 false));
459 END_FUNCTION_DEF(spawn)
460
461 FUNCTION_DEF(spawn_player, 4, 5, "spawn_player(string type_id, int midpoint_x, int midpoint_y, int facing, (optional) list of commands cmd): identical to spawn except that the new object is playable.")
462 return variant(new spawn_command(
463 args()[0]->evaluate(variables).as_string(),
464 args()[1]->evaluate(variables).as_int(),
465 args()[2]->evaluate(variables).as_int(),
466 args()[3]->evaluate(variables).as_int() > 0,
467 args().size() > 4 ? args()[4]->evaluate(variables) : variant(),
468 true));
469 END_FUNCTION_DEF(spawn_player)
470
471 FUNCTION_DEF(object, 4, 5, "object(string type_id, int midpoint_x, int midpoint_y, int facing, (optional) map properties) -> object: constructs and returns a new object. Note that the difference between this and spawn is that spawn returns a command to actually place the object in the level. object only creates the object and returns it. It may be stored for later use.")
472 //generate a random number just so we mark this as being a
473 //function which shouldn't have its result cached.
474 rng::generate();
475
476 const std::string type = args()[0]->evaluate(variables).as_string();
477 const int x = args()[1]->evaluate(variables).as_int();
478 const int y = args()[2]->evaluate(variables).as_int();
479 const bool face_right = args()[3]->evaluate(variables).as_int() > 0;
480 custom_object* obj = new custom_object(type, x, y, face_right);
481
482 //adjust so the object's x/y is its midpoint.
483 obj->set_pos(obj->x() - obj->current_frame().width() / 2 , obj->y() - obj->current_frame().height() / 2);
484
485 if(args().size() > 4) {
486 variant properties = args()[4]->evaluate(variables);
487 variant keys = properties.get_keys();
488 for(int n = 0; n != keys.num_elements(); ++n) {
489 variant value = properties[keys[n]];
490 obj->mutate_value(keys[n].as_string(), value);
491 }
492 }
493
494 return variant(obj);
495 END_FUNCTION_DEF(object)
496
497 class animation_command : public custom_object_command_callable
498 {
499 public:
500 animation_command(const std::string& anim)
501 : anim_(anim)
502 {}
503
504 virtual void execute(level& lvl, custom_object& ob) const {
505 ob.set_frame(anim_);
506 }
507 private:
508 std::string anim_;
509 };
510
511 FUNCTION_DEF(animation, 1, 1, "animation(string id): changes the current object's animation to the given animation. time_in_animation is reset to 0.")
512 return variant(new animation_command(args()[0]->evaluate(variables).as_string()));
513 END_FUNCTION_DEF(animation)
514
515 class die_command : public custom_object_command_callable
516 {
517 public:
518 virtual void execute(level& lvl, custom_object& ob) const {
519 ob.die();
520 }
521 };
522
523 FUNCTION_DEF(die, 0, 0, "die(): causes the current object to die. The object will receive the on_die signal and may even use it to resurrect itself. Use remove_object() to remove an object from play without it receiving on_die.")
524 return variant(new die_command());
525 END_FUNCTION_DEF(die)
526
527 class facing_command : public custom_object_command_callable
528 {
529 public:
530 explicit facing_command(int facing) : facing_(facing)
531 {}
532 virtual void execute(level& lvl, custom_object& ob) const {
533 ob.set_face_right(facing_ > 0);
534 }
535 private:
536 int facing_;
537 };
538
539 FUNCTION_DEF(facing, 1, 1, "facing(int new_facing): changes the current object's facing according to the value of new_facing (1 for right, otherwise left).")
540 return variant(new facing_command(args()[0]->evaluate(variables).as_int()));
541 END_FUNCTION_DEF(facing)
542
543 class set_var_command : public custom_object_command_callable
544 {
545 public:
546 explicit set_var_command(const std::string& attr, variant val)
547 : attr_(attr), val_(val)
548 {}
549 virtual void execute(level& lvl, custom_object& ob) const {
550 ob.vars()->mutate_value(attr_, val_);
551 }
552 private:
553 std::string attr_;
554 variant val_;
555 };
556
557 FUNCTION_DEF(set_var, 2, 2, "set_var(string varname, variant value): sets the variable named varname within the current object. Note that you should generally use set(vars.blah, x) rather than set_var('blah', x). The only exception is if you want to create the command and save it for later execution on an object you don't yet have access to -- most useful with the spawn() function.")
558 return variant(new set_var_command(
559 args()[0]->evaluate(variables).as_string(),
560 args()[1]->evaluate(variables)));
561 END_FUNCTION_DEF(set_var)
562
563 class set_command : public entity_command_callable
564 {
565 public:
566 set_command(variant target, const std::string& attr, variant val)
567 : target_(target), attr_(attr), val_(val)
568 {}
569 virtual void execute(level& lvl, entity& ob) const {
570 if(target_.is_callable()) {
571 target_.mutable_callable()->mutate_value(attr_, val_);
572 } else {
573 ob.mutate_value(attr_, val_);
574 }
575 }
576 private:
577 variant target_;
578 std::string attr_;
579 variant val_;
580 };
581
582 class add_command : public entity_command_callable
583 {
584 public:
585 add_command(variant target, const std::string& attr, variant val)
586 : target_(target), attr_(attr), val_(val)
587 {}
588 virtual void execute(level& lvl, entity& ob) const {
589 if(target_.is_callable()) {
590 target_.mutable_callable()->mutate_value(attr_, target_.mutable_callable()->query_value(attr_) + val_);
591 } else {
592 ob.mutate_value(attr_, ob.query_value(attr_) + val_);
593 }
594 }
595 private:
596 variant target_;
597 std::string attr_;
598 variant val_;
599 };
600
601 class set_by_slot_command : public entity_command_callable
602 {
603 public:
604 set_by_slot_command(int slot, const variant& value)
605 : slot_(slot), value_(value)
606 {}
607
608 virtual void execute(level& lvl, entity& obj) const {
609 obj.mutate_value_by_slot(slot_, value_);
610 }
611
612 void set_value(const variant& value) { value_ = value; }
613
614 private:
615 int slot_;
616 variant value_;
617 };
618
619 class add_by_slot_command : public entity_command_callable
620 {
621 public:
622 add_by_slot_command(int slot, const variant& value)
623 : slot_(slot), value_(value)
624 {}
625
626 virtual void execute(level& lvl, entity& obj) const {
627 obj.mutate_value_by_slot(slot_, obj.query_value_by_slot(slot_) + value_);
628 }
629
630 void set_value(const variant& value) { value_ = value; }
631
632 private:
633 int slot_;
634 variant value_;
635 };
636
637 class set_function : public function_expression {
638 public:
639 set_function(const args_list& args, const formula_callable_definition* callable_def)
640 : function_expression("set", args, 2, 3), slot_(-1) {
641 if(args.size() == 2) {
642 variant literal = args[0]->is_literal();
643 if(literal.is_string()) {
644 key_ = literal.as_string();
645 } else {
646 args[0]->is_identifier(&key_);
647 }
648
649 if(!key_.empty() && callable_def) {
650 slot_ = callable_def->get_slot(key_);
651 if(slot_ != -1) {
652 cmd_ = boost::intrusive_ptr<set_by_slot_command>(new set_by_slot_command(slot_, variant()));
653 }
654 }
655 }
656 }
657 private:
658 variant execute(const formula_callable& variables) const {
659 if(slot_ != -1) {
660 if(cmd_->refcount() == 1) {
661 cmd_->set_value(args()[1]->evaluate(variables));
662 return variant(cmd_.get());
663 }
664
665 cmd_ = boost::intrusive_ptr<set_by_slot_command>(new set_by_slot_command(slot_, args()[1]->evaluate(variables)));
666 return variant(cmd_.get());
667 }
668
669 if(!key_.empty()) {
670 return variant(new set_command(variant(), key_, args()[1]->evaluate(variables)));
671 }
672
673 if(args().size() == 2) {
674 try {
675 std::string member;
676 variant target = args()[0]->evaluate_with_member(variables, member);
677 return variant(new set_command(
678 target, member, args()[1]->evaluate(variables)));
679 } catch(game_logic::formula_error&) {
680 }
681 }
682
683 variant target;
684 if(args().size() == 3) {
685 target = args()[0]->evaluate(variables);
686 }
687 const int begin_index = args().size() == 2 ? 0 : 1;
688 return variant(new set_command(
689 target,
690 args()[begin_index]->evaluate(variables).as_string(),
691 args()[begin_index + 1]->evaluate(variables)));
692 }
693
694 std::string key_;
695 int slot_;
696 mutable boost::intrusive_ptr<set_by_slot_command> cmd_;
697 };
698
699 class add_function : public function_expression {
700 public:
701 add_function(const args_list& args, const formula_callable_definition* callable_def)
702 : function_expression("add", args, 2, 3), slot_(-1) {
703 if(args.size() == 2) {
704 variant literal = args[0]->is_literal();
705 if(literal.is_string()) {
706 key_ = literal.as_string();
707 } else {
708 args[0]->is_identifier(&key_);
709 }
710
711 if(!key_.empty() && callable_def) {
712 slot_ = callable_def->get_slot(key_);
713 if(slot_ != -1) {
714 cmd_ = boost::intrusive_ptr<add_by_slot_command>(new add_by_slot_command(slot_, variant()));
715 }
716 }
717 }
718 }
719 private:
720 variant execute(const formula_callable& variables) const {
721 if(slot_ != -1) {
722 if(cmd_->refcount() == 1) {
723 cmd_->set_value(args()[1]->evaluate(variables));
724 return variant(cmd_.get());
725 }
726
727 cmd_ = boost::intrusive_ptr<add_by_slot_command>(new add_by_slot_command(slot_, args()[1]->evaluate(variables)));
728 return variant(cmd_.get());
729 }
730
731 if(!key_.empty()) {
732 return variant(new add_command(variant(), key_, args()[1]->evaluate(variables)));
733 }
734
735 if(args().size() == 2) {
736 try {
737 std::string member;
738 variant target = args()[0]->evaluate_with_member(variables, member);
739 return variant(new add_command(
740 target, member, args()[1]->evaluate(variables)));
741 } catch(game_logic::formula_error&) {
742 }
743 }
744
745 variant target;
746 if(args().size() == 3) {
747 target = args()[0]->evaluate(variables);
748 }
749 const int begin_index = args().size() == 2 ? 0 : 1;
750 return variant(new add_command(
751 target,
752 args()[begin_index]->evaluate(variables).as_string(),
753 args()[begin_index + 1]->evaluate(variables)));
754 }
755
756 std::string key_;
757 int slot_;
758 mutable boost::intrusive_ptr<add_by_slot_command> cmd_;
759 };
760
761 FUNCTION_DEF(solid, 3, 5, "solid(level, int x, int y, (optional)int w=1, (optional) int h=1) -> boolean: returns true iff the level contains solid space within the given (x,y,w,h) rectangle")
762 level* lvl = args()[0]->evaluate(variables).convert_to<level>();
763 const int x = args()[1]->evaluate(variables).as_int();
764 const int y = args()[2]->evaluate(variables).as_int();
765
766 int w = args().size() >= 4 ? args()[3]->evaluate(variables).as_int() : 1;
767 int h = args().size() >= 5 ? args()[4]->evaluate(variables).as_int() : 1;
768
769 rect r(x, y, w, h);
770
771 return variant(lvl->solid(r));
772 END_FUNCTION_DEF(solid)
773
774 class set_solid_command : public entity_command_callable {
775 rect r_;
776 bool is_solid_;
777 public:
778 set_solid_command(const rect& r, bool is_solid) : r_(r), is_solid_(is_solid)
779 {}
780
781 virtual void execute(level& lvl, entity& ob) const {
782 lvl.set_solid_area(r_, is_solid_);
783 }
784 };
785
786 FUNCTION_DEF(set_solid, 4, 5, "set_solid(x1, y1, x2, y2, boolean is_solid=false): modifies the solidity of the level such that the rectangle given by (x1, y1, x2, y2) will have its solidity set to the value of is_solid")
787 return variant(new set_solid_command(
788 rect::from_coordinates(
789 args()[0]->evaluate(variables).as_int(),
790 args()[1]->evaluate(variables).as_int(),
791 args()[2]->evaluate(variables).as_int(),
792 args()[3]->evaluate(variables).as_int()),
793 args().size() > 4 ? args()[4]->evaluate(variables).as_bool() : false));
794
795 END_FUNCTION_DEF(set_solid)
796
797 FUNCTION_DEF(group_size, 2, 2, "group_size(level, int group_id) -> int: gives the number of objects in the object group given by group_id")
798 level* lvl = args()[0]->evaluate(variables).convert_to<level>();
799 return variant(lvl->group_size(args()[1]->evaluate(variables).as_int()));
800 END_FUNCTION_DEF(group_size)
801
802 class set_group_command : public entity_command_callable {
803 public:
804 explicit set_group_command(int group=-1) : group_(group)
805 {}
806
807 virtual void execute(level& lvl, entity& ob) const {
808 int group = group_;
809 if(group < 0) {
810 if(ob.group() >= 0) {
811 return;
812 }
813 group = lvl.add_group();
814 }
815
816 lvl.set_character_group(&ob, group);
817 }
818
819 private:
820 int group_;
821 };
822
823 FUNCTION_DEF(set_group, 0, 1, "set_group((optional)int group_id): sets the current object to have the given group id, or to be in no group if group_id is not given")
824 int group = -1;
825 if(args().size() > 0) {
826 group = args()[0]->evaluate(variables).as_int();
827 }
828
829 return variant(new set_group_command(group));
830 END_FUNCTION_DEF(set_group)
831
832 class scroll_to_command : public custom_object_command_callable
833 {
834 public:
835 explicit scroll_to_command(entity_ptr focus) : focus_(focus)
836 {}
837 virtual void execute(level& lvl, custom_object& ob) const {
838 screen_position pos = last_draw_position();
839 for(int n = 0; n != 50; ++n) {
840 draw_scene(lvl, pos, focus_.get());
841 SDL_GL_SwapBuffers();
842 SDL_Delay(20);
843 }
844 }
845 private:
846 entity_ptr focus_;
847 };
848
849 FUNCTION_DEF(scroll_to, 1, 1, "scroll_to(object target): scrolls the screen to the target object")
850 return variant(new scroll_to_command(args()[0]->evaluate(variables).try_convert<entity>()));
851 END_FUNCTION_DEF(scroll_to)
852
853 namespace {
854 static int g_in_speech_dialog = 0;
855 class in_speech_dialog_tracker {
856 public:
857 in_speech_dialog_tracker() : cancelled_(false) {
858 g_in_speech_dialog++;
859 }
860
861 ~in_speech_dialog_tracker() {
862 cancel();
863 }
864
865 void cancel() {
866 if(!cancelled_) {
867 g_in_speech_dialog--;
868 cancelled_ = true;
869 }
870 }
871
872 private:
873 bool cancelled_;
874 };
875 }
876
877 class transient_speech_dialog_command : public custom_object_command_callable {
878 entity_ptr speaker_;
879 std::vector<std::string> text_;
880 int duration_;
881 public:
882 transient_speech_dialog_command(entity_ptr speaker, const std::vector<std::string>& text, int duration) : speaker_(speaker), text_(text), duration_(duration)
883 {}
884 virtual void execute(level& lvl, custom_object& ob) const {
885 boost::shared_ptr<speech_dialog> d(new speech_dialog);
886 if(speaker_) {
887 d->set_speaker(speaker_);
888 } else {
889 d->set_speaker(entity_ptr(&ob));
890 }
891
892 d->set_text(text_);
893 d->set_expiration(duration_);
894 lvl.add_speech_dialog(d);
895 }
896 };
897
898 FUNCTION_DEF(transient_speech_dialog, 1, -1, "transient_speech_dialog(...): schedules a sequence of speech dialogs to be shown. Arguments may include a list of strings, which contain text. An integer which sets the duration of the dialog. An object which sets the speaker.")
899 entity_ptr speaker;
900 int duration = 100;
901
902 std::vector<variant> result;
903
904 for(int n = 0; n != args().size(); ++n) {
905 variant v = args()[n]->evaluate(variables);
906 entity* e = v.try_convert<entity>();
907 if(e) {
908 speaker = entity_ptr(e);
909 } else if(v.is_int()) {
910 duration = v.as_int();
911 } else if(v.is_list()) {
912 std::vector<std::string> str;
913 for(int m = 0; m != v.num_elements(); ++m) {
914 str.push_back(v[m].as_string());
915 }
916 result.push_back(variant(new transient_speech_dialog_command(speaker, str, duration)));
917 } else {
918 ASSERT_LOG(false, "UNRECOGNIZED ARGUMENT to transient_speech_dialog: " << v.to_debug_string());
919 }
920 }
921
922 return variant(&result);
923 END_FUNCTION_DEF(transient_speech_dialog)
924
925 namespace {
926 struct in_dialog_setter {
927 level& lvl_;
928 in_dialog_setter(level& lvl) : lvl_(lvl) {
929 lvl_.set_in_dialog(true);
930 }
931 ~in_dialog_setter() {
932 lvl_.set_in_dialog(false);
933 }
934 };
935 }
936
937 class speech_dialog_command : public custom_object_command_callable {
938 public:
939 explicit speech_dialog_command(const std::vector<variant>& args, bool paused=false)
940 : args_(args), paused_(paused)
941 {}
942 virtual void execute(level& lvl, custom_object& ob) const {
943 // pause_scope pauser;
944
945 if(g_in_speech_dialog) {
946 return;
947 }
948
949 foreach(const entity_ptr& e, lvl.get_chars()) {
950 e->handle_event(OBJECT_EVENT_BEGIN_DIALOG);
951 }
952
953 formula_profiler::suspend_scope profiler_suspend;
954 in_dialog_setter dialog_setter(lvl);
955
956 //make it so the player's controls become locked for the duration of the dialog.
957 controls::local_controls_lock controller_lock;
958
959 execute_commands(lvl, ob, args_);
960 }
961
962 private:
963 void execute_commands(level& lvl, custom_object& ob, const std::vector<variant>& commands) const {
964 in_speech_dialog_tracker dialog_tracker;
965
966 foreach(variant var, commands) {
967 if(var.is_callable()) {
968 const_entity_ptr e = var.try_convert<entity>();
969 if(e) {
970 std::cerr << "set speaker...\n";
971 dialog_.set_speaker_and_flip_side(e);
972 }
973
974 const entity_command_callable* entity_cmd = var.try_convert<const entity_command_callable>();
975 if(entity_cmd) {
976 entity_cmd->execute(lvl, ob);
977 }
978
979 const custom_object_command_callable* obj_cmd = var.try_convert<const custom_object_command_callable>();
980 if(obj_cmd) {
981 obj_cmd->execute(lvl, ob);
982 }
983 }
984
985 if(var.is_list()) {
986 if(var.num_elements() > 0 && var[0].is_callable()) {
987 std::vector<variant> cmd;
988 for(int n = 0; n != var.num_elements(); ++n) {
989 cmd.push_back(var[n]);
990 }
991
992 execute_commands(lvl, ob, cmd);
993 continue;
994 }
995
996 std::vector<variant> option_commands;
997 std::vector<std::string> options;
998 std::vector<std::string> message;
999 for(int n = 0; n != var.num_elements(); ++n) {
1000 if(message.empty() == false && var[n].is_list()) {
1001 options.push_back(message.back());
1002 message.pop_back();
1003 option_commands.push_back(var[n]);
1004 } else {
1005 message.push_back(var[n].as_string());
1006 }
1007 }
1008
1009 dialog_.set_text(message);
1010 dialog_.set_options(options);
1011
1012 bool done = false;
1013 while(!done) {
1014 if(!paused_) {
1015 lvl.process();
1016 lvl.process_draw();
1017 }
1018
1019 SDL_Event event;
1020 while(SDL_PollEvent(&event)) {
1021 switch(event.type) {
1022 case SDL_QUIT:
1023 throw interrupt_game_exception();
1024 case SDL_KEYDOWN:
1025 if(event.key.keysym.sym == SDLK_ESCAPE) {
1026 PAUSE_GAME_RESULT result = show_pause_game_dialog();
1027 if(result != PAUSE_GAME_CONTINUE) {
1028 throw interrupt_game_exception(result);
1029 }
1030 break;
1031 }
1032
1033 case SDL_MOUSEBUTTONDOWN:
1034 case SDL_MOUSEBUTTONUP:
1035 case SDL_MOUSEMOTION:
1036 done = done || dialog_.key_press(event);
1037 break;
1038 }
1039 }
1040
1041 done = done || dialog_.process();
1042 draw(lvl);
1043 }
1044
1045 if(options.empty() == false) {
1046 const int index = dialog_.option_selected();
1047 if(index >= 0 && index < option_commands.size()) {
1048 dialog_tracker.cancel();
1049 ob.execute_command(option_commands[index]);
1050 }
1051 }
1052
1053 dialog_.set_options(std::vector<std::string>());
1054 }
1055 }
1056 }
1057
1058 void draw(const level& lvl) const {
1059 draw_scene(lvl, last_draw_position(), &lvl.player()->get_entity());
1060
1061 dialog_.draw();
1062
1063 SDL_GL_SwapBuffers();
1064 SDL_Delay(20);
1065 }
1066
1067 std::vector<variant> args_;
1068
1069 mutable speech_dialog dialog_;
1070 bool paused_;
1071 };
1072
1073 FUNCTION_DEF(speech_dialog, 1, -1, "speech_dialog(...): schedules a sequence of speech dialogs to be shown modally. Arguments may include a list of strings, which contain text. An integer which sets the duration of the dialog. An object which sets the speaker. A string by itself indicates an option that should be shown for the player to select from. A string should be followed by a list of commands that will be executed should the player choose that option.")
1074 std::vector<variant> v;
1075 for(int n = 0; n != args().size(); ++n) {
1076 v.push_back(args()[n]->evaluate(variables));
1077 }
1078
1079 return variant(new speech_dialog_command(v));
1080 END_FUNCTION_DEF(speech_dialog)
1081
1082 FUNCTION_DEF(paused_speech_dialog, 1, -1, "paused_speech_dialog(...): like speech_dialog(), except the game is paused while the dialog is displayed.")
1083 std::vector<variant> v;
1084 for(int n = 0; n != args().size(); ++n) {
1085 v.push_back(args()[n]->evaluate(variables));
1086 }
1087
1088 return variant(new speech_dialog_command(v, true));
1089 END_FUNCTION_DEF(paused_speech_dialog)
1090
1091 class end_game_command : public custom_object_command_callable
1092 {
1093 public:
1094 virtual void execute(level& lvl, custom_object& ob) const {
1095 lvl.set_end_game();
1096 }
1097 };
1098
1099 FUNCTION_DEF(end_game, 0, 0, "end_game(): exits the game")
1100 return variant(new end_game_command());
1101 END_FUNCTION_DEF(end_game)
1102
1103 class debug_command : public entity_command_callable
1104 {
1105 public:
1106 explicit debug_command(const std::string& str) : str_(str)
1107 {}
1108 virtual void execute(level& lvl, entity& ob) const {
1109 debug_console::add_message(str_);
1110 std::cerr << "CONSOLE: " << str_ << "\n";
1111 }
1112 private:
1113 std::string str_;
1114 };
1115
1116 FUNCTION_DEF(debug, 1, -1, "debug(...): outputs arguments to the console")
1117 if(!preferences::debug()) {
1118 return variant();
1119 }
1120
1121 std::string str;
1122 for(int n = 0; n != args().size(); ++n) {
1123 str += args()[n]->evaluate(variables).to_debug_string();
1124 }
1125
1126 fprintf(stderr, "DEBUG FUNCTION: %s\n", str.c_str());
1127
1128 return variant(new debug_command(str));
1129 END_FUNCTION_DEF(debug)
1130
1131 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
1132 class debug_console_command : public entity_command_callable
1133 {
1134 public:
1135 explicit debug_console_command(const formula_callable& callable)
1136 : callable_(callable), history_pos_(0) {
1137 entry_.set_loc(10, 300);
1138 entry_.set_dim(300, 20);
1139 }
1140
1141 virtual void execute(level& lvl, entity& ob) const {
1142 history_.clear();
1143 history_pos_ = 0;
1144
1145 bool done = false;
1146 while(!done) {
1147 SDL_Event event;
1148 while(SDL_PollEvent(&event)) {
1149 switch(event.type) {
1150 case SDL_KEYDOWN:
1151 done = event.key.keysym.sym == SDLK_ESCAPE;
1152
1153 if(event.key.keysym.sym == SDLK_RETURN) {
1154 try {
1155 const std::string text = entry_.text();
1156 history_.push_back(entry_.text());
1157 history_pos_ = history_.size();
1158 entry_.set_text("");
1159 game_logic::formula f(text, &get_custom_object_functions_symbol_table());
1160 variant v = f.execute(ob);
1161 ob.execute_command(v);
1162 debug_console::add_message(v.to_debug_string());
1163 } catch(game_logic::formula_error&) {
1164 debug_console::add_message("error parsing formula");
1165 } catch(...) {
1166 debug_console::add_message("unknown error parsing formula");
1167 }
1168 } else if(event.key.keysym.sym == SDLK_UP) {
1169 if(history_pos_ > 0) {
1170 --history_pos_;
1171 ASSERT_LT(history_pos_, history_.size());
1172 entry_.set_text(history_[history_pos_]);
1173 }
1174 } else if(event.key.keysym.sym == SDLK_DOWN) {
1175 if(!history_.empty() && history_pos_ < history_.size() - 1) {
1176 ++history_pos_;
1177 entry_.set_text(history_[history_pos_]);
1178 } else {
1179 history_pos_ = history_.size();
1180 entry_.set_text("");
1181 }
1182 }
1183 break;
1184 }
1185
1186 entry_.process_event(event, false);
1187 }
1188
1189 draw(lvl);
1190 SDL_Delay(20);
1191 }
1192 }
1193 private:
1194 void draw(const level& lvl) const {
1195 draw_scene(lvl, last_draw_position(), &lvl.player()->get_entity());
1196
1197 entry_.draw();
1198
1199 SDL_GL_SwapBuffers();
1200 }
1201
1202 const formula_callable& callable_;
1203 mutable gui::text_entry_widget entry_;
1204 mutable std::vector<std::string> history_;
1205 mutable int history_pos_;
1206 };
1207
1208 FUNCTION_DEF(debug_console, 0, 0, "debug_console(): provides an interactive debugging console")
1209 return variant(new debug_console_command(variables));
1210 END_FUNCTION_DEF(debug_console)
1211
1212 #endif
1213
1214 class fire_event_command : public entity_command_callable {
1215 const entity_ptr target_;
1216 const std::string event_;
1217 const const_formula_callable_ptr callable_;
1218 public:
1219 fire_event_command(entity_ptr target, const std::string& event, const_formula_callable_ptr callable)
1220 : target_(target), event_(event), callable_(callable)
1221 {}
1222
1223 virtual void execute(level& lvl, entity& ob) const {
1224 entity* e = target_ ? target_.get() : &ob;
1225 e->handle_event(event_, callable_.get());
1226 }
1227 };
1228
1229 FUNCTION_DEF(fire_event, 1, 3, "fire_event((optional) object target, string id, (optional)callable arg): fires the event with the given id. Targets the current object by default, or target if given. Sends arg as the event argument if given")
1230 entity_ptr target;
1231 std::string event;
1232 const_formula_callable_ptr callable;
1233
1234 if(args().size() == 3) {
1235 target = args()[0]->evaluate(variables).convert_to<entity>();
1236 event = args()[1]->evaluate(variables).as_string();
1237 callable = args()[2]->evaluate(variables).as_callable();
1238 } else if(args().size() == 2) {
1239 variant v1 = args()[0]->evaluate(variables);
1240 variant v2 = args()[1]->evaluate(variables);
1241 if(v1.is_string()) {
1242 event = v1.as_string();
1243 callable = v2.as_callable();
1244 } else {
1245 target = v1.convert_to<entity>();
1246 event = v2.as_string();
1247 }
1248 } else {
1249 event = args()[0]->evaluate(variables).as_string();
1250 }
1251
1252 return variant(new fire_event_command(target, event, callable));
1253 END_FUNCTION_DEF(fire_event)
1254
1255 FUNCTION_DEF(get_object, 2, 2, "get_object(level, string label) -> object: returns the object that is present in the given level that has the given label")
1256
1257 level* lvl = args()[0]->evaluate(variables).try_convert<level>();
1258 if(lvl) {
1259 return variant(lvl->get_entity_by_label(args()[1]->evaluate(variables).as_string()).get());
1260 }
1261
1262 return variant();
1263 END_FUNCTION_DEF(get_object)
1264
1265 //a command which moves an object in a given direction enough to resolve
1266 //any solid conflicts.
1267 class resolve_solid_command : public entity_command_callable {
1268 entity_ptr e_;
1269 int xdir_, ydir_, max_cycles_;
1270 public:
1271 resolve_solid_command(entity_ptr e, int xdir, int ydir, int max_cycles) : e_(e), xdir_(xdir), ydir_(ydir), max_cycles_(max_cycles)
1272 {}
1273
1274 virtual void execute(level& lvl, entity& ob) const {
1275 const int start_x = e_->x();
1276 const int start_y = e_->y();
1277
1278 int max_cycles = max_cycles_;
1279 while(entity_collides(lvl, *e_, MOVE_NONE) && max_cycles > 0) {
1280 e_->set_pos(e_->x() + xdir_, e_->y() + ydir_);
1281 --max_cycles;
1282 }
1283
1284 if(max_cycles == 0) {
1285 e_->set_pos(start_x, start_y);
1286 }
1287 }
1288 };
1289
1290 FUNCTION_DEF(resolve_solid, 3, 4, "resolve_solid(object, int xdir, int ydir, int max_cycles=100): will attempt to move the given object in the direction indicated by xdir/ydir until the object no longer has a solid overlap. Gives up after max_cycles")
1291 entity_ptr e(args()[0]->evaluate(variables).try_convert<entity>());
1292 const int xdir = args()[1]->evaluate(variables).as_int();
1293 const int ydir = args()[2]->evaluate(variables).as_int();
1294 const int max_cycles = args().size() > 3 ? args()[3]->evaluate(variables).as_int() : 100;
1295 if(e) {
1296 return variant(new resolve_solid_command(e, xdir, ydir, max_cycles));
1297 } else {
1298 return variant();
1299 }
1300 END_FUNCTION_DEF(resolve_solid)
1301
1302 class add_object_command : public entity_command_callable {
1303 entity_ptr e_;
1304 public:
1305 explicit add_object_command(entity_ptr e) : e_(e)
1306 {}
1307
1308 virtual void execute(level& lvl, entity& ob) const {
1309 if(place_entity_in_level(lvl, *e_)) {
1310 lvl.add_character(e_);
1311 } else {
1312 collision_info collide_info;
1313 entity_collides(level::current(), *e_, MOVE_NONE, &collide_info);
1314 game_logic::map_formula_callable* callable(new game_logic::map_formula_callable(this));
1315 callable->add("collide_with", variant(collide_info.collide_with.get()));
1316
1317 game_logic::formula_callable_ptr callable_ptr(callable);
1318 e_->handle_event(OBJECT_EVENT_ADD_OBJECT_FAIL, callable);
1319
1320 if(!e_->destroyed()) {
1321 callable->add("object", variant(e_.get()));
1322 ob.handle_event(OBJECT_EVENT_ADD_OBJECT_FAIL, callable);
1323 }
1324 }
1325 }
1326 };
1327
1328 FUNCTION_DEF(add_object, 1, 1, "add_object(object): inserts the given object into the level. The object should not currently be persent in the level. The position of the object is tweaked to make sure there are no solid overlaps, however if it is not possible to reasonably place the object without a solid overlap, then the object will not be placed and the object and caller will both receive the event add_object_fail.")
1329
1330 entity_ptr e(args()[0]->evaluate(variables).try_convert<entity>());
1331 if(e) {
1332 return variant(new add_object_command(e));
1333 } else {
1334 std::cerr << "OBJECT NOT VALID!\n";
1335 return variant();
1336 }
1337 END_FUNCTION_DEF(add_object)
1338
1339 class remove_object_command : public entity_command_callable {
1340 entity_ptr e_;
1341 public:
1342 explicit remove_object_command(entity_ptr e) : e_(e)
1343 {}
1344
1345 virtual void execute(level& lvl, entity& ob) const {
1346 lvl.remove_character(e_);
1347 }
1348 };
1349
1350 FUNCTION_DEF(remove_object, 1, 1, "remove_object(object): removes the given object from the level. If there are no references to the object stored, then the object will immediately be destroyed. However it is possible to keep a reference to the object and even insert it back into the level later using add_object()")
1351
1352 entity_ptr e(args()[0]->evaluate(variables).try_convert<entity>());
1353 if(e) {
1354 return variant(new remove_object_command(e));
1355 } else {
1356 return variant();
1357 }
1358 END_FUNCTION_DEF(remove_object)
1359
1360 class teleport_command : public entity_command_callable
1361 {
1362 public:
1363 teleport_command(const std::string& level, const std::string& label, const std::string& transition) : level_(level), label_(label), transition_(transition)
1364 {}
1365
1366 virtual void execute(level& lvl, entity& ob) const {
1367 level::portal p;
1368 p.level_dest = level_;
1369 if(p.level_dest.empty()) {
1370 p.level_dest = lvl.id();
1371 }
1372
1373 p.dest_starting_pos = true;
1374 p.dest_label = label_;
1375 p.automatic = true;
1376 p.transition = transition_;
1377 lvl.force_enter_portal(p);
1378 }
1379 private:
1380 std::string level_, label_, transition_;
1381 };
1382
1383 FUNCTION_DEF(teleport, 1, 3, "teleport(string dest_level, (optional)string dest_label, (optional)string transition): teleports the player to a new level. The level is given by dest_level, with null() for the current level. If dest_label is given then the player will be teleported to the object in the destination level with that label. If transition is given, it names are type of transition (such as 'flip' or 'fade') which indicates the kind of visual effect to use for the transition.")
1384 std::string label, transition;
1385 if(args().size() > 1) {
1386 label = args()[1]->evaluate(variables).as_string();
1387 if(args().size() > 2) {
1388 transition = args()[2]->evaluate(variables).as_string();
1389 }
1390 }
1391
1392 variant dst_level = args()[0]->evaluate(variables);
1393 const std::string dst_level_str = dst_level.is_null() ? "" : dst_level.as_string();
1394 return variant(new teleport_command(dst_level_str, label, transition));
1395 END_FUNCTION_DEF(teleport)
1396
1397 class schedule_command : public entity_command_callable {
1398 public:
1399 schedule_command(int cycles, variant cmd) : cycles_(cycles), cmd_(cmd)
1400 {}
1401
1402 virtual void execute(level& lvl, entity& ob) const {
1403 ob.add_scheduled_command(lvl.cycle() + cycles_, cmd_);
1404 }
1405 private:
1406 int cycles_;
1407 variant cmd_;
1408 };
1409
1410 FUNCTION_DEF(schedule, 2, 2, "schedule(int cycles_in_future, list of commands): schedules the given list of commands to be run on the current object the given number of cycles in the future. Note that the object must be valid (not destroyed) and still present in the level for the commands to be run.")
1411 return variant(new schedule_command(
1412 args()[0]->evaluate(variables).as_int(),
1413 args()[1]->evaluate(variables)));
1414 END_FUNCTION_DEF(schedule)
1415
1416 class add_water_command : public entity_command_callable
1417 {
1418 rect r_;
1419 boost::array<unsigned char, 4> color_;
1420 public:
1421 add_water_command(const rect& r, boost::array<unsigned char, 4> col) : r_(r), color_(col)
1422 {}
1423
1424 virtual void execute(level& lvl, entity& ob) const {
1425 lvl.get_or_create_water().add_rect(r_, color_.data(), variant(&ob));
1426 }
1427 };
1428
1429 FUNCTION_DEF(add_water, 4, 5, "add_water(int x1, int y1, int x2, int y2, (optional)[r,g,b,a]=[70,0,0,50]): adds water of the given color in the given rectangle.")
1430 static const unsigned char default_color[] = {70, 0, 0, 50};
1431 boost::array<unsigned char, 4> color;
1432 for(int n = 0; n != 4; ++n) {
1433 color[n] = default_color[n];
1434 }
1435
1436 if(args().size() > 4) {
1437 variant v = args()[4]->evaluate(variables);
1438 ASSERT_LOG(v.is_list(), "MUST PROVIDE COLOR LIST AS FOURTH ARGUMENT TO add_water(), found: " << v.to_debug_string());
1439 for(int n = 0; n < 4 && n < v.num_elements(); ++n) {
1440 color[n] = v[n].as_int();
1441 }
1442 }
1443
1444 return variant(new add_water_command(
1445 rect::from_coordinates(
1446 args()[0]->evaluate(variables).as_int(),
1447 args()[1]->evaluate(variables).as_int(),
1448 args()[2]->evaluate(variables).as_int(),
1449 args()[3]->evaluate(variables).as_int()), color));
1450 END_FUNCTION_DEF(add_water)
1451
1452 class remove_water_command : public entity_command_callable
1453 {
1454 rect r_;
1455 public:
1456 remove_water_command(const rect& r) : r_(r)
1457 {}
1458
1459 virtual void execute(level& lvl, entity& ob) const {
1460 lvl.get_or_create_water().delete_rect(r_);
1461 }
1462 };
1463
1464 FUNCTION_DEF(remove_water, 4, 4, "remove_water(int x1, int y1, int x2, int y2): removes water that has the given rectangular area.")
1465 return variant(new remove_water_command(
1466 rect::from_coordinates(
1467 args()[0]->evaluate(variables).as_int(),
1468 args()[1]->evaluate(variables).as_int(),
1469 args()[2]->evaluate(variables).as_int(),
1470 args()[3]->evaluate(variables).as_int())));
1471 END_FUNCTION_DEF(remove_water)
1472
1473 class add_wave_command : public entity_command_callable
1474 {
1475 int x_, y_, xvelocity_, height_, length_, delta_height_, delta_length_;
1476 public:
1477 add_wave_command(int x, int y, int xvelocity, int height, int length, int delta_height, int delta_length) : x_(x), y_(y), xvelocity_(xvelocity), height_(height), length_(length), delta_height_(delta_height), delta_length_(delta_length)
1478 {}
1479
1480 virtual void execute(level& lvl, entity& ob) const {
1481 water* w = lvl.get_water();
1482 if(!w) {
1483 return;
1484 }
1485
1486 std::cerr << "EXECUTE ADD WAVE...\n";
1487
1488 point p(x_, y_);
1489 w->add_wave(p, xvelocity_/1000.0, height_/1000.0, length_/1000.0, delta_height_/1000.0, delta_length_/1000.0);
1490 }
1491 };
1492
1493 FUNCTION_DEF(add_wave, 7, 7, "add_wave(int x, int y, int xvelocity, int height, int length, int delta_height, int delta_length): will add a wave with the given characteristics at the surface of the water above the (x,y) point. (x,y) must be within a body of water. Waves are a visual effect only and may not display at all on slower devices.")
1494 return variant(new add_wave_command(
1495 args()[0]->evaluate(variables).as_int(),
1496 args()[1]->evaluate(variables).as_int(),
1497 args()[2]->evaluate(variables).as_int(),
1498 args()[3]->evaluate(variables).as_int(),
1499 args()[4]->evaluate(variables).as_int(),
1500 args()[5]->evaluate(variables).as_int(),
1501 args()[6]->evaluate(variables).as_int()));
1502 END_FUNCTION_DEF(add_wave)
1503
1504 FUNCTION_DEF(rect_current, 7, 7, "rect_current(int x, int y, int w, int h, int xvelocity, int yvelocity, int strength) -> current generator object: creates a current generator object that has a current with the given parameters. Set the return value of this function to an object's rect_current to attach it to an object and thus place it in the level.")
1505 return variant(new rect_current_generator(rect(
1506 args()[0]->evaluate(variables).as_int(),
1507 args()[1]->evaluate(variables).as_int(),
1508 args()[2]->evaluate(variables).as_int(),
1509 args()[3]->evaluate(variables).as_int()),
1510 args()[4]->evaluate(variables).as_int(),
1511 args()[5]->evaluate(variables).as_int(),
1512 args()[6]->evaluate(variables).as_int()));
1513 END_FUNCTION_DEF(rect_current)
1514
1515 class begin_script_command : public entity_command_callable {
1516 public:
1517 explicit begin_script_command(const std::string& id) : id_(id) {
1518 }
1519
1520 virtual void execute(level& lvl, entity& ob) const {
1521 lvl.begin_movement_script(id_, ob);
1522 }
1523 private:
1524 std::string id_;
1525 };
1526
1527 FUNCTION_DEF(begin_script, 1, 1, "begin_script(string id): begins the script with the given ID.")
1528 return variant(new begin_script_command(args()[0]->evaluate(variables).as_string()));
1529 END_FUNCTION_DEF(begin_script)
1530
1531 class end_script_command : public entity_command_callable {
1532 public:
1533 virtual void execute(level& lvl, entity& ob) const {
1534 lvl.end_movement_script();
1535 }
1536 };
1537
1538 FUNCTION_DEF(end_script, 0, 0, "end_script(): ends the most recent script to have begun.")
1539 return variant(new end_script_command);
1540 END_FUNCTION_DEF(end_script)
1541
1542 class add_particles_command : public custom_object_command_callable {
1543 public:
1544 add_particles_command(const std::string& id, const std::string& type)
1545 : id_(id), type_(type) {
1546 }
1547
1548 virtual void execute(level& lvl, custom_object& ob) const {
1549 ob.add_particle_system(id_, type_);
1550 }
1551 private:
1552 std::string id_, type_;
1553 };
1554
1555 FUNCTION_DEF(add_particles, 1, 2, "add_particles(string id): adds the particle system with the given id to the object")
1556 return variant(new add_particles_command(
1557 args()[0]->evaluate(variables).as_string(),
1558 args()[args().size() < 2 ? 0 : 1]->evaluate(variables).as_string()));
1559 END_FUNCTION_DEF(add_particles)
1560
1561 FUNCTION_DEF(collides, 4, 4, "collides(object a, string area_a, object b, string area_b) -> boolean: returns true iff area_a within object a collides with area_b within object b.")
1562 return variant(entity_user_collision_specific_areas(
1563 *args()[0]->evaluate(variables).convert_to<entity>(),
1564 args()[1]->evaluate(variables).as_string(),
1565 *args()[2]->evaluate(variables).convert_to<entity>(),
1566 args()[3]->evaluate(variables).as_string()));
1567 END_FUNCTION_DEF(collides)
1568
1569 FUNCTION_DEF(collides_with_level, 1, 1, "collides_with_level(object) -> boolean: returns true iff the given object collides with the level.")
1570 return variant(non_solid_entity_collides_with_level(
1571 level::current(),
1572 *args()[0]->evaluate(variables).convert_to<entity>()));
1573 END_FUNCTION_DEF(collides_with_level)
1574
1575 class blur_command : public custom_object_command_callable {
1576 int alpha_, fade_, granularity_;
1577 public:
1578 blur_command(int alpha, int fade, int granularity)
1579 : alpha_(alpha), fade_(fade), granularity_(granularity)
1580 {}
1581
1582 virtual void execute(level& lvl, custom_object& ob) const {
1583 if(alpha_ == 0) {
1584 ob.set_blur(NULL);
1585 return;
1586 }
1587
1588 blur_info blur(double(alpha_)/1000.0, double(fade_)/1000.0, granularity_);
1589 ob.set_blur(&blur);
1590 }
1591 };
1592
1593 FUNCTION_DEF(blur, 0, 3, "blur(int alpha=0, int fade=10, int granularity=1): creates a motion blur for the current object.")
1594 return variant(new blur_command(
1595 args().size() > 0 ? args()[0]->evaluate(variables).as_int() : 0,
1596 args().size() > 1 ? args()[1]->evaluate(variables).as_int() : 10,
1597 args().size() > 2 ? args()[2]->evaluate(variables).as_int() : 1));
1598 END_FUNCTION_DEF(blur)
1599
1600 class text_command : public custom_object_command_callable {
1601 public:
1602 text_command(const std::string& text, const std::string& font, int size)
1603 : text_(text), font_(font), size_(size) {
1604 }
1605
1606 virtual void execute(level& lvl, custom_object& ob) const {
1607 ob.set_text(text_, font_, size_*2);
1608 }
1609 private:
1610 std::string text_, font_;
1611 int size_;
1612 };
1613
1614 FUNCTION_DEF(text, 1, 3, "text(string text, (optional)string font='default', (optional)int size=1): adds text for the current object")
1615 const std::string text = args()[0]->evaluate(variables).as_string();
1616 const std::string font = args().size() > 1 ? args()[1]->evaluate(variables).as_string() : "default";
1617 const int size = args().size() > 2 ? args()[2]->evaluate(variables).as_int() : 1;
1618 return variant(new text_command(text, font, size));
1619 END_FUNCTION_DEF(text)
1620
1621 FUNCTION_DEF(swallow_event, 0, 0, "swallow_event(): when used in an instance-specific event handler, this causes the event to be swallowed and not passed to the object's main event handler.")
1622 return variant(new swallow_object_command_callable);
1623 END_FUNCTION_DEF(swallow_event)
1624
1625 class custom_object_function_symbol_table : public function_symbol_table
1626 {
1627 public:
1628 expression_ptr create_function(
1629 const std::string& fn,
1630 const std::vector<expression_ptr>& args,
1631 const formula_callable_definition* callable_def) const;
1632 };
1633
1634 expression_ptr custom_object_function_symbol_table::create_function(
1635 const std::string& fn,
1636 const std::vector<expression_ptr>& args,
1637 const formula_callable_definition* callable_def) const
1638 {
1639 if(fn == "set") {
1640 return expression_ptr(new set_function(args, callable_def));
1641 } else if(fn == "add") {
1642 return expression_ptr(new add_function(args, callable_def));
1643 }
1644
1645 std::map<std::string, function_creator*>::const_iterator i = function_creators().find(fn);
1646 if(i != function_creators().end()) {
1647 return expression_ptr(i->second->create(args));
1648 }
1649
1650 return function_symbol_table::create_function(fn, args, callable_def);
1651 }
1652
1653 } //namespace
1654
1655 function_symbol_table& get_custom_object_functions_symbol_table()
1656 {
1657 static custom_object_function_symbol_table table;
1658 return table;
1659 }
1660
1661 void init_custom_object_functions(wml::const_node_ptr node)
1662 {
1663 wml::node::const_child_iterator i1 = node->begin_child("function");
1664 wml::node::const_child_iterator i2 = node->end_child("function");
1665 for(; i1 != i2; ++i1) {
1666 const std::string& name = i1->second->attr("name");
1667 std::vector<std::string> args = util::split(i1->second->attr("args"));
1668
1669 game_logic::formula_callable_definition_ptr args_definition = game_logic::create_formula_callable_definition(&args[0], &args[0] + args.size());
1670
1671 recursive_function_symbol_table recursive_symbols(name, args, &get_custom_object_functions_symbol_table());
1672 const_formula_ptr fml(new formula(i1->second->attr("formula"), &recursive_symbols, args_definition.get()));
1673 get_custom_object_functions_symbol_table().add_formula_function(
1674 name, fml, const_formula_ptr(), args);
1675 recursive_symbols.resolve_recursive_calls(fml);
1676 std::vector<std::string> names = get_custom_object_functions_symbol_table().get_function_names();
1677 assert(std::count(names.begin(), names.end(), i1->second->attr("name").val()));
1678 }
1679 }
1680
1681 UTILITY(document_object_functions) {
1682 std::sort(function_helpstrings().begin(), function_helpstrings().end());
1683 foreach(std::string s, function_helpstrings()) {
1684 std::string::iterator i = std::find(s.begin(), s.end(), ':');
1685 if(i != s.end()) {
1686 s = "<b>" + std::string(s.begin(), i) + "</b>" + std::string(i, s.end());
1687 }
1688 s = "<p>" + s + "</p>";
1689 std::cout << s << "\n";
1690 }
1691 }
1692
0 #ifndef CUSTOM_OBJECT_FUNCTIONS_HPP_INCLUDED
1 #define CUSTOM_OBJECT_FUNCTIONS_HPP_INCLUDED
2
3 #include <string>
4
5 #include "formula.hpp"
6 #include "formula_callable.hpp"
7 #include "formula_function.hpp"
8 #include "variant.hpp"
9 #include "wml_node_fwd.hpp"
10
11 class custom_object;
12 class entity;
13 class level;
14
15 using game_logic::function_symbol_table;
16 function_symbol_table& get_custom_object_functions_symbol_table();
17 void init_custom_object_functions(wml::const_node_ptr node);
18
19 class entity_command_callable : public game_logic::formula_callable {
20 public:
21 virtual void execute(level& lvl, entity& ob) const = 0;
22
23 private:
24 variant get_value(const std::string& key) const { return variant(); }
25 void get_inputs(std::vector<game_logic::formula_input>* inputs) const {}
26 };
27
28 class custom_object_command_callable : public game_logic::formula_callable {
29 public:
30 virtual void execute(level& lvl, custom_object& ob) const = 0;
31
32 private:
33 variant get_value(const std::string& key) const { return variant(); }
34 void get_inputs(std::vector<game_logic::formula_input>* inputs) const {}
35 };
36
37 class swallow_object_command_callable : public game_logic::formula_callable {
38 private:
39 variant get_value(const std::string& key) const { return variant(); }
40 void get_inputs(std::vector<game_logic::formula_input>* inputs) const {}
41 };
42
43 #endif
0 #include <cassert>
1 #include <iostream>
2
3 #include "asserts.hpp"
4 #include "collision_utils.hpp"
5 #include "custom_object_callable.hpp"
6 #include "custom_object_functions.hpp"
7 #include "custom_object_type.hpp"
8 #include "filesystem.hpp"
9 #include "formula_constants.hpp"
10 #include "object_events.hpp"
11 #include "preferences.hpp"
12 #include "solid_map.hpp"
13 #include "string_utils.hpp"
14 #include "wml_modify.hpp"
15 #include "wml_node.hpp"
16 #include "wml_parser.hpp"
17 #include "wml_schema.hpp"
18 #include "wml_utils.hpp"
19 #include "wml_writer.hpp"
20 #include "unit_test.hpp"
21
22 namespace {
23 std::map<std::string, std::string>& object_file_paths() {
24 static std::map<std::string, std::string> paths;
25 return paths;
26 }
27
28 std::map<std::string, std::string>& prototype_file_paths() {
29 static std::map<std::string, std::string> paths;
30 return paths;
31 }
32
33 const std::string& object_file_path() {
34 if(preferences::load_compiled()) {
35 static const std::string value = "data/compiled/objects/";
36 return value;
37 } else {
38 static const std::string value = "data/objects/";
39 return value;
40 }
41 }
42
43 typedef std::map<std::string, const_custom_object_type_ptr> object_map;
44
45 object_map& cache() {
46 static object_map instance;
47 return instance;
48 }
49
50 namespace {
51 const std::string BaseStr = "%PROTO%";
52 }
53
54 void merge_into_prototype(wml::node_ptr prototype_node, wml::node_ptr node)
55 {
56 for(std::map<std::string, wml::const_node_ptr>::const_iterator i = node->base_elements().begin(); i != node->base_elements().end(); ++i) {
57 prototype_node->set_base_element(i->first, i->second);
58 }
59
60 //we have a prototype node and an object node. We want to merge
61 //them together to give one object definition. We will save
62 //the final definition in the prototype_node, overwriting its
63 //values with changes from the object node.
64 //begin by setting attributes in the prototype node from
65 //the object node.
66 for(wml::node::const_attr_iterator i = node->begin_attr();
67 i != node->end_attr(); ++i) {
68 std::string::const_iterator base_itor = std::search(i->second.str().begin(), i->second.str().end(), BaseStr.begin(), BaseStr.end());
69 if(base_itor != i->second.str().end()) {
70 std::string base_attr = prototype_node->attr(i->first);
71 if(base_attr.empty()) {
72 base_attr = "null()";
73 }
74
75 std::string value = std::string(i->second.str().begin(), base_itor) + base_attr + std::string(base_itor + BaseStr.size(), i->second.str().end());
76
77 wml::value val(value, i->second.filename(), i->second.line());
78 prototype_node->set_attr(i->first, val);
79 } else {
80 prototype_node->set_attr(i->first, i->second);
81 }
82 }
83
84 //mapping of animation nodes is kinda complicated: in the
85 //prototype there can be one specification of each animation.
86 //in objects there can be multiple specifications. Each
87 //animation in the object inherits from the specification in
88 //the prototype.
89 //
90 //We are going to build a completely fresh/new set of animations
91 //in a vector, and then wipe out all current animations and
92 //replace with these from the vector.
93 std::vector<wml::node_ptr> animation_nodes;
94
95 //go over every animation in the object, and see if the animation
96 //is also defined in the prototype.
97 FOREACH_WML_CHILD(anim_node, node, "animation") {
98 wml::node_ptr target_anim = wml::find_child_by_attribute(prototype_node, "animation", "id", anim_node->attr("id"));
99 if(target_anim) {
100 //the animation is in the prototype, so we merge the
101 //object's definition of the animation with the
102 //prototype's.
103 wml::node_ptr new_node(wml::deep_copy(target_anim));
104 wml::merge_over(anim_node, new_node);
105 animation_nodes.push_back(new_node);
106 } else {
107 //the animation isn't in the prototype, so just add
108 //what is given in the object.
109 animation_nodes.push_back(wml::deep_copy(anim_node));
110 }
111 }
112
113 //now go over the prototype node and add any animations that don't
114 //appear in the child.
115 FOREACH_WML_CHILD(anim_node, prototype_node, "animation") {
116 if(wml::find_child_by_attribute(node, "animation", "id", anim_node->attr("id"))) {
117 //it's already in the child, so don't add it.
118 continue;
119 }
120
121 animation_nodes.push_back(wml::deep_copy(anim_node));
122 }
123
124 prototype_node->clear_children("animation");
125 foreach(wml::node_ptr node, animation_nodes) {
126 prototype_node->add_child(node);
127 }
128
129 //now go over every element and copy them in.
130 for(wml::node::all_child_iterator i = node->begin_children();
131 i != node->end_children(); ++i) {
132 const std::string& name = (*i)->name();
133 if(name == "animation") {
134 //we handled animations above, so ignore this here.
135 continue;
136 }
137
138 if(name == "tmp" || name == "vars" || name == "consts" || name == "editor_info") {
139 //we like to merge in vars nodes into one vars definition
140 //if both the object and prototype have vars definitions.
141 wml::node_ptr target = prototype_node->get_child(name);
142 if(target) {
143 wml::merge_over(*i, target);
144 continue;
145 }
146 }
147
148 prototype_node->add_child(*i);
149 }
150 }
151
152 }
153
154 //function which finds if a node has a prototype, and if so, applies the
155 //prototype to the node.
156 wml::node_ptr custom_object_type::merge_prototype(wml::node_ptr node)
157 {
158 if(!node->has_attr("prototype")) {
159 return node;
160 }
161
162 std::vector<std::string> protos = util::split(node->attr("prototype"));
163
164 foreach(const std::string& proto, protos) {
165 //look up the object's prototype and merge it in
166 std::map<std::string, std::string>::const_iterator path_itor = prototype_file_paths().find(proto + ".cfg");
167 ASSERT_LOG(path_itor != prototype_file_paths().end(), "Could not find file for prototype '" << node->attr("prototype") << "'");
168
169 wml::node_ptr prototype_node = wml::parse_wml_from_file(path_itor->second);
170 prototype_node = merge_prototype(prototype_node);
171 merge_into_prototype(prototype_node, node);
172 node = prototype_node;
173 }
174 return node;
175 }
176
177 const std::string* custom_object_type::get_object_path(const std::string& id)
178 {
179 if(object_file_paths().empty()) {
180 //find out the paths to all our files
181 sys::get_unique_filenames_under_dir(object_file_path(), &object_file_paths());
182 sys::get_unique_filenames_under_dir("data/object_prototypes", &prototype_file_paths());
183 }
184
185 std::map<std::string, std::string>::const_iterator itor = object_file_paths().find(id);
186 if(itor == object_file_paths().end()) {
187 return NULL;
188 }
189
190 return &itor->second;
191 }
192
193 const_custom_object_type_ptr custom_object_type::get(const std::string& id)
194 {
195 std::string::const_iterator dot_itor = std::find(id.begin(), id.end(), '.');
196 if(dot_itor != id.end()) {
197 const_custom_object_type_ptr parent = get(std::string(id.begin(), dot_itor));
198 if(!parent) {
199 return const_custom_object_type_ptr();
200 }
201
202 return parent->get_sub_object(std::string(dot_itor+1, id.end()));
203 }
204
205 object_map::const_iterator itor = cache().find(id);
206 if(itor != cache().end()) {
207 return itor->second;
208 }
209
210 const_custom_object_type_ptr result(create(id));
211 cache()[id] = result;
212
213 return result;
214 }
215
216 const_custom_object_type_ptr custom_object_type::get_sub_object(const std::string& id) const
217 {
218 std::map<std::string, const_custom_object_type_ptr>::const_iterator itor = sub_objects_.find(id);
219 if(itor != sub_objects_.end()) {
220 return itor->second;
221 } else {
222 return const_custom_object_type_ptr();
223 }
224 }
225
226 custom_object_type_ptr custom_object_type::create(const std::string& id)
227 {
228 if(object_file_paths().empty()) {
229 //find out the paths to all our files
230 sys::get_unique_filenames_under_dir(object_file_path(), &object_file_paths());
231 sys::get_unique_filenames_under_dir("data/object_prototypes", &prototype_file_paths());
232 }
233
234 //find the file for the object we are loading.
235 std::map<std::string, std::string>::const_iterator path_itor = object_file_paths().find(id + ".cfg");
236 ASSERT_LOG(path_itor != object_file_paths().end(), "Could not find file for object '" << id << "'");
237
238 try {
239 wml::node_ptr node = wml::parse_wml_from_file(path_itor->second);
240
241 node = merge_prototype(node);
242
243 ASSERT_LOG(node->attr("id").str() == id, "IN " << path_itor->second << " OBJECT ID DOES NOT MATCH FILENAME");
244
245 static const wml::schema* schema = wml::schema::get("custom_object");
246 if(schema) {
247 schema->validate_node(node);
248 }
249
250 //create the object and add it to our cache.
251 custom_object_type_ptr result(new custom_object_type(node));
252
253 //load the object's variations here to avoid pausing the game
254 //when an object starts its variation.
255 result->load_variations();
256 return result;
257 } catch(wml::parse_error& e) {
258 ASSERT_LOG(false, "Error parsing WML for custom object in " << path_itor->second << ": " << e.message);
259 } catch(wml::schema_error& e) {
260 ASSERT_LOG(false, "Error loading object '" << id << "': " << e.message);
261 } catch(...) {
262 ASSERT_LOG(false, "Unknown error loading custom object in " << path_itor->second);
263 }
264 }
265
266 void custom_object_type::invalidate_object(const std::string& id)
267 {
268 cache().erase(id);
269 }
270
271 void custom_object_type::invalidate_all_objects()
272 {
273 cache().clear();
274 object_file_paths().clear();
275 prototype_file_paths().clear();
276 }
277
278 std::vector<const_custom_object_type_ptr> custom_object_type::get_all()
279 {
280 std::vector<const_custom_object_type_ptr> res;
281 std::map<std::string, std::string> file_paths;
282 sys::get_unique_filenames_under_dir(object_file_path(), &file_paths);
283 for(std::map<std::string, std::string>::const_iterator i = file_paths.begin(); i != file_paths.end(); ++i) {
284 const std::string& fname = i->first;
285 if(fname.size() < 4 || std::string(fname.end()-4, fname.end()) != ".cfg") {
286 continue;
287 }
288
289 const std::string id(fname.begin(), fname.end() - 4);
290 res.push_back(get(id));
291 }
292
293 return res;
294 }
295
296
297 void custom_object_type::init_event_handlers(wml::const_node_ptr node,
298 event_handler_map& handlers,
299 game_logic::function_symbol_table* symbols,
300 const event_handler_map* base_handlers)
301 {
302 if(symbols == NULL) {
303 symbols = &get_custom_object_functions_symbol_table();
304 }
305
306 static custom_object_callable custom_object_definition;
307
308 for(wml::node::const_attr_iterator i = node->begin_attr(); i != node->end_attr(); ++i) {
309 if(i->first.size() > 3 && std::equal(i->first.begin(), i->first.begin() + 3, "on_")) {
310 const std::string event(i->first.begin() + 3, i->first.end());
311 const int event_id = get_object_event_id(event);
312 if(handlers.size() <= event_id) {
313 handlers.resize(event_id+1);
314 }
315
316 if(base_handlers && base_handlers->size() > event_id && (*base_handlers)[event_id] && (*base_handlers)[event_id]->str() == i->second.str()) {
317 handlers[event_id] = (*base_handlers)[event_id];
318 } else {
319 handlers[event_id] = game_logic::formula::create_optional_formula(i->second, symbols, &custom_object_definition);
320 }
321 }
322 }
323 }
324
325
326 custom_object_type::custom_object_type(wml::const_node_ptr node, const custom_object_type* base_type)
327 : id_(node->attr("id")),
328 hitpoints_(wml::get_int(node, "hitpoints", 1)),
329 timer_frequency_(wml::get_int(node, "timer_frequency", -1)),
330 zorder_(wml::get_int(node, "zorder")),
331 is_human_(wml::get_bool(node, "is_human", false)),
332 goes_inactive_only_when_standing_(wml::get_bool(node, "goes_inactive_only_when_standing", false)),
333 dies_on_inactive_(wml::get_bool(node, "dies_on_inactive", false)),
334 always_active_(wml::get_bool(node, "always_active", false)),
335 body_harmful_(wml::get_bool(node, "body_harmful", true)),
336 body_passthrough_(wml::get_bool(node, "body_passthrough", false)),
337 ignore_collide_(wml::get_bool(node, "ignore_collide", false)),
338 object_level_collisions_(wml::get_bool(node, "object_level_collisions", false)),
339 surface_friction_(wml::get_int(node, "surface_friction", 100)),
340 surface_traction_(wml::get_int(node, "surface_traction", 100)),
341 friction_(wml::get_int(node, "friction")),
342 traction_(wml::get_int(node, "traction", 1000)),
343 traction_in_air_(wml::get_int(node, "traction_in_air", 0)),
344 traction_in_water_(wml::get_int(node, "traction_in_water", 0)),
345 respawns_(wml::get_bool(node, "respawns", true)),
346 affected_by_currents_(wml::get_bool(node, "affected_by_currents", false)),
347 is_vehicle_(wml::get_bool(node, "vehicle", false)),
348 passenger_x_(wml::get_int(node, "passenger_x")),
349 passenger_y_(wml::get_int(node, "passenger_y")),
350 feet_width_(wml::get_int(node, "feet_width", 5)),
351 use_image_for_collisions_(wml::get_bool(node, "use_image_for_collisions", false)),
352 has_feet_(wml::get_bool(node, "has_feet", true) && use_image_for_collisions_ == false),
353 adjust_feet_on_animation_change_(wml::get_bool(node, "adjust_feet_on_animation_change", true)),
354 teleport_offset_x_(wml::get_int(node, "teleport_offset_x")),
355 teleport_offset_y_(wml::get_int(node, "teleport_offset_y")),
356 solid_(solid_info::create(node)),
357 platform_(solid_info::create_platform(node)),
358 has_solid_(solid_ || use_image_for_collisions_),
359 solid_dimensions_(has_solid_ || platform_ ? 0xFFFFFFFF : 0),
360 collide_dimensions_(0xFFFFFFFF),
361 weak_solid_dimensions_(has_solid_ || platform_ ? 0xFFFFFFFF : 0),
362 weak_collide_dimensions_(0xFFFFFFFF)
363 {
364 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
365 if(node->get_child("editor_info")) {
366 editor_info_.reset(new editor_entity_info(node->get_child("editor_info")));
367 }
368 #endif
369
370 const bool is_variation = base_type != NULL;
371
372 //make it so any formula has these constants defined.
373 const game_logic::constants_loader scope_consts(node->get_child("consts"));
374
375 //if some constants change from base to variation, then we have to
376 //re-parse all formulas.
377 if(scope_consts.same_as_base() == false) {
378 base_type = NULL;
379 }
380
381 if(node->has_attr("solid_dimensions")) {
382 weak_solid_dimensions_ = solid_dimensions_ = 0;
383 foreach(std::string key, util::split(node->attr("solid_dimensions"))) {
384 if(key != "level_only") {
385 if(key.empty() == false && key[0] == '~') {
386 key = std::string(key.begin()+1, key.end());
387 weak_solid_dimensions_ |= (1 << get_solid_dimension_id(key));
388 } else {
389 solid_dimensions_ |= (1 << get_solid_dimension_id(key));
390 }
391 }
392 }
393
394 weak_solid_dimensions_ |= solid_dimensions_;
395 }
396
397 if(node->has_attr("collide_dimensions")) {
398 weak_collide_dimensions_ = collide_dimensions_ = 0;
399 foreach(std::string key, util::split(node->attr("collide_dimensions"))) {
400 if(key != "level_only") {
401 if(key.empty() == false && key[0] == '~') {
402 key = std::string(key.begin()+1, key.end());
403 weak_collide_dimensions_ |= (1 << get_solid_dimension_id(key));
404 } else {
405 collide_dimensions_ |= (1 << get_solid_dimension_id(key));
406 }
407 }
408 }
409
410 weak_collide_dimensions_ |= collide_dimensions_;
411 }
412
413 wml::node::const_child_iterator a1 = node->begin_child("animation");
414 wml::node::const_child_iterator a2 = node->end_child("animation");
415 for(; a1 != a2; ++a1) {
416 boost::shared_ptr<frame> f;
417 try {
418 f.reset(new frame(a1->second));
419 } catch(frame::error&) {
420 ASSERT_LOG(false, "ERROR LOADING FRAME IN OBJECT '" << id_ << "'");
421 }
422
423 if(use_image_for_collisions_) {
424 f->set_image_as_solid();
425 }
426
427 if(f->solid()) {
428 has_solid_ = true;
429 }
430
431 frames_[a1->second->attr("id")].push_back(f);
432 const int duplicates = atoi(a1->second->attr("duplicates").c_str());
433 if(duplicates > 1) {
434 for(int n = 1; n != duplicates; ++n) {
435 frames_[a1->second->attr("id")].push_back(f);
436 }
437 }
438 if(!default_frame_) {
439 default_frame_ = f;
440 }
441 }
442
443 mass_ = wml::get_int(node, "mass", (default_frame_->collide_w() * default_frame_->collide_h() ) );
444
445 wml::node::const_child_iterator c1 = node->begin_child("child");
446 wml::node::const_child_iterator c2 = node->end_child("child");
447 for(; c1 != c2; ++c1) {
448 const std::string& child_id = c1->second->attr("child_id");
449 children_[child_id] = c1->second;
450 }
451
452 assert(default_frame_);
453
454 next_animation_formula_ = game_logic::formula::create_optional_formula(node->attr("next_animation"), function_symbols());
455
456 FOREACH_WML_CHILD(particle_node, node, "particle_system") {
457 particle_factories_[particle_node->attr("id")] = particle_system_factory::create_factory(particle_node);
458 }
459
460 if(!is_variation) {
461 FOREACH_WML_CHILD(object_node, node, "object_type") {
462 wml::node_ptr dup_object_node = wml::deep_copy(object_node);
463 custom_object_type* type = new custom_object_type(merge_prototype(dup_object_node));
464 type->id_ = id_ + "." + type->id_;
465 sub_objects_[object_node->attr("id")].reset(type);
466 }
467 }
468
469 wml::const_node_ptr vars = node->get_child("vars");
470 if(vars) {
471 std::vector<std::string> var_str;
472 for(wml::node::const_attr_iterator v = vars->begin_attr(); v != vars->end_attr(); ++v) {
473 variant var;
474 var.serialize_from_string(v->second);
475 variables_[v->first] = var;
476 var_str.push_back(v->first);
477 }
478
479 game_logic::formula_callable_definition::entry* entry = callable_definition_.get_entry(CUSTOM_OBJECT_VARS);
480 ASSERT_LOG(entry != NULL, "CANNOT FIND VARS ENTRY IN OBJECT");
481 entry->type_definition_holder = game_logic::create_formula_callable_definition(&var_str[0], &var_str[0] + var_str.size());
482 entry->type_definition = entry->type_definition_holder.get();
483 }
484
485 wml::const_node_ptr tmp_vars = node->get_child("tmp");
486 if(tmp_vars) {
487 std::vector<std::string> var_str;
488 for(wml::node::const_attr_iterator v = tmp_vars->begin_attr(); v != tmp_vars->end_attr(); ++v) {
489 variant var;
490 var.serialize_from_string(v->second);
491 tmp_variables_[v->first] = var;
492 var_str.push_back(v->first);
493 }
494
495 game_logic::formula_callable_definition::entry* entry = callable_definition_.get_entry(CUSTOM_OBJECT_TMP);
496 ASSERT_LOG(entry != NULL, "CANNOT FIND TMP ENTRY IN OBJECT");
497 entry->type_definition_holder = game_logic::create_formula_callable_definition(&var_str[0], &var_str[0] + var_str.size());
498 entry->type_definition = entry->type_definition_holder.get();
499 }
500
501 std::cerr << "TMP_VARIABLES: '" << id_ << "' -> " << tmp_variables_.size() << "\n";
502
503 consts_.reset(new game_logic::map_formula_callable);
504 wml::const_node_ptr consts = node->get_child("consts");
505 if(consts) {
506 for(wml::node::const_attr_iterator v = consts->begin_attr(); v != consts->end_attr(); ++v) {
507 variant var;
508 var.serialize_from_string(v->second);
509 consts_->add(v->first, var);
510 }
511 }
512
513 if(node->has_attr("tags")) {
514 const std::vector<std::string> tags = util::split(node->attr("tags"));
515 foreach(const std::string& tag, tags) {
516 tags_[tag] = variant(1);
517 }
518 }
519
520 FOREACH_WML_CHILD(properties_node, node, "properties") {
521 for(wml::node::const_attr_iterator i = properties_node->begin_attr(); i != properties_node->end_attr(); ++i) {
522 properties_[i->first] = game_logic::formula::create_optional_formula(i->second, function_symbols(), &callable_definition_);
523 }
524 }
525
526 FOREACH_WML_CHILD(variation_node, node, "object_variation") {
527 const std::string& id = variation_node->attr("id");
528 variations_[id].reset(new wml::modifier(variation_node));
529 node_ = node;
530 }
531
532 game_logic::register_formula_callable_definition("object_type", &callable_definition_);
533
534 if(base_type) {
535 //if we're a variation, just get the functions from our base type.
536 //variations can't define new functions.
537 object_functions_ = base_type->object_functions_;
538 } else if(node->has_attr("functions")) {
539 object_functions_.reset(new game_logic::function_symbol_table);
540 object_functions_->set_backup(&get_custom_object_functions_symbol_table());
541 game_logic::formula f(node->attr("functions"), object_functions_.get());
542 }
543 init_event_handlers(node, event_handlers_, function_symbols(), base_type ? &base_type->event_handlers_ : NULL);
544 }
545
546 custom_object_type::~custom_object_type()
547 {
548 }
549
550 const frame& custom_object_type::default_frame() const
551 {
552 return *default_frame_;
553 }
554
555 const frame& custom_object_type::get_frame(const std::string& key) const
556 {
557 frame_map::const_iterator itor = frames_.find(key);
558 if(itor == frames_.end() || itor->second.empty()) {
559 ASSERT_LOG(key == "normal", "UNKNOWN FRAME " << key << " IN " << id_);
560 return default_frame();
561 } else {
562 if(itor->second.size() == 1) {
563 return *itor->second.front().get();
564 } else {
565 return *itor->second[rand()%itor->second.size()].get();
566 }
567 }
568 }
569
570 game_logic::const_formula_ptr custom_object_type::get_event_handler(int event) const
571 {
572 if(event >= event_handlers_.size()) {
573 return game_logic::const_formula_ptr();
574 } else {
575 return event_handlers_[event];
576 }
577 }
578
579 const_particle_system_factory_ptr custom_object_type::get_particle_system_factory(const std::string& id) const
580 {
581 std::map<std::string, const_particle_system_factory_ptr>::const_iterator i = particle_factories_.find(id);
582 ASSERT_LOG(i != particle_factories_.end(), "Unknown particle system type in " << id_ << ": " << id)
583 return i->second;
584 }
585
586 game_logic::function_symbol_table* custom_object_type::function_symbols() const
587 {
588 if(object_functions_) {
589 return object_functions_.get();
590 } else {
591 return &get_custom_object_functions_symbol_table();
592 }
593 }
594
595 const_custom_object_type_ptr custom_object_type::get_variation(const std::vector<std::string>& variations) const
596 {
597 ASSERT_LOG(node_, "tried to set variation in object which has no variations");
598
599 const_custom_object_type_ptr& result = variations_cache_[variations];
600 if(!result) {
601 wml::node_ptr node = wml::deep_copy(node_);
602 foreach(const std::string& v, variations) {
603 std::map<std::string, wml::const_modifier_ptr>::const_iterator var_itor = variations_.find(v);
604 if(var_itor != variations_.end() && var_itor->second) {
605 var_itor->second->modify(node);
606 }
607 }
608
609 //set our constants so the variation can decide whether it needs
610 //to re-parse formulas or not.
611 const game_logic::constants_loader scope_consts(node_->get_child("consts"));
612
613 result.reset(new custom_object_type(node, this));
614 }
615
616 return result;
617 }
618
619 void custom_object_type::load_variations() const
620 {
621 if(!node_ || variations_.empty() || !node_->has_attr("load_variations")) {
622 return;
623 }
624
625 const std::vector<std::string> variations_to_load = util::split(node_->attr("load_variations"));
626 foreach(const std::string& v, variations_to_load) {
627 get_variation(std::vector<std::string>(1, v));
628 }
629 }
630
631 #include "texture.hpp"
632 #include "surface_cache.hpp"
633
634 BENCHMARK(custom_object_type_load)
635 {
636 static std::map<std::string,std::string> file_paths;
637 if(file_paths.empty()) {
638 sys::get_unique_filenames_under_dir("data/objects", &file_paths);
639 }
640
641 BENCHMARK_LOOP {
642 for(std::map<std::string,std::string>::const_iterator i = file_paths.begin(); i != file_paths.end(); ++i) {
643 if(i->first.size() > 4 && std::equal(i->first.end()-4, i->first.end(), ".cfg")) {
644 custom_object_type::create(std::string(i->first.begin(), i->first.end()-4));
645 }
646 }
647 graphics::surface_cache::clear();
648 graphics::texture::clear_textures();
649 }
650 }
651
652
653 BENCHMARK(custom_object_type_frogatto_load)
654 {
655 BENCHMARK_LOOP {
656 custom_object_type::create("frogatto_playable");
657 graphics::texture::clear_textures();
658 graphics::surface_cache::clear();
659 }
660 }
661
662 UTILITY(object_definition)
663 {
664 foreach(const std::string& arg, args) {
665 const_custom_object_type_ptr obj = custom_object_type::get(arg);
666 ASSERT_LOG(obj.get() != NULL, "NO OBJECT FOUND: " << arg);
667
668 const std::string* fname = custom_object_type::get_object_path(arg + ".cfg");
669 ASSERT_LOG(fname != NULL, "NO OBJECT FILE FOUND: " << arg);
670
671 wml::node_ptr obj_node = wml::parse_wml_from_file(*fname);
672 wml::node_ptr node = custom_object_type::merge_prototype(obj_node);
673
674 std::cout << "OBJECT " << arg << "\n---\n" << wml::output(node) << "\n---\n";
675 }
676 }
0 #ifndef CUSTOM_OBJECT_TYPE_HPP_INCLUDED
1 #define CUSTOM_OBJECT_TYPE_HPP_INCLUDED
2
3 #include <map>
4 #include <string>
5
6 #include "boost/scoped_ptr.hpp"
7 #include "boost/shared_ptr.hpp"
8
9 #include "custom_object_callable.hpp"
10 #include "editor_variable_info.hpp"
11 #include "formula.hpp"
12 #include "formula_callable.hpp"
13 #include "formula_function.hpp"
14 #include "frame.hpp"
15 #include "particle_system.hpp"
16 #include "solid_map_fwd.hpp"
17 #include "variant.hpp"
18 #include "wml_node.hpp"
19
20 class custom_object_type;
21
22 typedef boost::shared_ptr<custom_object_type> custom_object_type_ptr;
23 typedef boost::shared_ptr<const custom_object_type> const_custom_object_type_ptr;
24
25 namespace wml {
26 class modifier;
27 typedef boost::shared_ptr<const modifier> const_modifier_ptr;
28 }
29
30 class custom_object_type
31 {
32 public:
33 static wml::node_ptr merge_prototype(wml::node_ptr node);
34 static const std::string* get_object_path(const std::string& id);
35 static const_custom_object_type_ptr get(const std::string& id);
36 static custom_object_type_ptr create(const std::string& id);
37 static void invalidate_object(const std::string& id);
38 static void invalidate_all_objects();
39 static std::vector<const_custom_object_type_ptr> get_all();
40
41 typedef std::vector<game_logic::const_formula_ptr> event_handler_map;
42
43 static void init_event_handlers(wml::const_node_ptr node,
44 event_handler_map& handlers,
45 game_logic::function_symbol_table* symbols=0,
46 const event_handler_map* base_handlers=NULL);
47
48 explicit custom_object_type(wml::const_node_ptr node, const custom_object_type* base_type=NULL);
49 ~custom_object_type();
50
51 const_custom_object_type_ptr get_sub_object(const std::string& id) const;
52
53 const custom_object_callable& callable_definition() const { return callable_definition_; }
54
55 const std::string& id() const { return id_; }
56 int hitpoints() const { return hitpoints_; }
57
58 int timer_frequency() const { return timer_frequency_; }
59
60 const frame& default_frame() const;
61 const frame& get_frame(const std::string& key) const;
62
63 const game_logic::const_formula_ptr& next_animation_formula() const { return next_animation_formula_; }
64
65 game_logic::const_formula_ptr get_event_handler(int event) const;
66
67 int zorder() const { return zorder_; }
68 bool is_human() const { return is_human_;}
69 bool goes_inactive_only_when_standing() const { return goes_inactive_only_when_standing_; }
70 bool dies_on_inactive() const { return dies_on_inactive_;}
71 bool always_active() const { return always_active_;}
72 bool body_harmful() const { return body_harmful_; }
73 bool body_passthrough() const { return body_passthrough_; }
74 bool ignore_collide() const { return ignore_collide_; }
75
76 bool object_level_collisions() const { return object_level_collisions_; }
77
78 int surface_friction() const { return surface_friction_; }
79 int surface_traction() const { return surface_traction_; }
80 int mass() const { return mass_; }
81
82 //amount of friction we experience.
83 int friction() const { return friction_; }
84 int traction() const { return traction_; }
85 int traction_in_air() const { return traction_in_air_; }
86 int traction_in_water() const { return traction_in_water_; }
87
88 bool respawns() const { return respawns_; }
89
90 bool affected_by_currents() const { return affected_by_currents_; }
91
92 wml::const_node_ptr get_child(const std::string& key) const {
93 if(children_.count(key)) {
94 return children_.find(key)->second;
95 }
96
97 return wml::const_node_ptr();
98 }
99
100 const_particle_system_factory_ptr get_particle_system_factory(const std::string& id) const;
101
102 bool is_vehicle() const { return is_vehicle_; }
103
104 int passenger_x() const { return passenger_x_; }
105 int passenger_y() const { return passenger_y_; }
106
107 int feet_width() const { return feet_width_; }
108
109 int teleport_offset_x() const { return teleport_offset_x_; }
110 int teleport_offset_y() const { return teleport_offset_y_; }
111
112 bool use_image_for_collisions() const { return use_image_for_collisions_; }
113 bool has_feet() const { return has_feet_; }
114 bool adjust_feet_on_animation_change() const { return adjust_feet_on_animation_change_; }
115
116 const std::map<std::string, variant>& variables() const { return variables_; }
117 const std::map<std::string, variant>& tmp_variables() const { return tmp_variables_; }
118 game_logic::const_map_formula_callable_ptr consts() const { return consts_; }
119 const std::map<std::string, variant>& tags() const { return tags_; }
120
121 const std::map<std::string, game_logic::const_formula_ptr>& properties() const { return properties_; }
122
123 game_logic::function_symbol_table* function_symbols() const;
124
125 const const_solid_info_ptr& solid() const { return solid_; }
126 const const_solid_info_ptr& platform() const { return platform_; }
127
128 //true if the object can ever be solid or standable
129 bool has_solid() const { return has_solid_; }
130
131 unsigned int solid_dimensions() const { return solid_dimensions_; }
132 unsigned int collide_dimensions() const { return collide_dimensions_; }
133
134 unsigned int weak_solid_dimensions() const { return weak_solid_dimensions_; }
135 unsigned int weak_collide_dimensions() const { return weak_collide_dimensions_; }
136
137 const_custom_object_type_ptr get_variation(const std::vector<std::string>& variations) const;
138 void load_variations() const;
139
140 const_editor_entity_info_ptr editor_info() const { return editor_info_; }
141
142 wml::const_node_ptr node() const { return node_; }
143
144 private:
145 custom_object_callable callable_definition_;
146
147 std::string id_;
148 int hitpoints_;
149
150 int timer_frequency_;
151
152 typedef std::map<std::string, std::vector<boost::shared_ptr<frame> > > frame_map;
153 frame_map frames_;
154
155 boost::shared_ptr<frame> default_frame_;
156
157 game_logic::const_formula_ptr next_animation_formula_;
158
159 event_handler_map event_handlers_;
160 boost::shared_ptr<game_logic::function_symbol_table> object_functions_;
161
162 int zorder_;
163
164 bool is_human_;
165 bool goes_inactive_only_when_standing_;
166 bool dies_on_inactive_;
167 bool always_active_;
168 bool body_harmful_;
169 bool body_passthrough_;
170 bool ignore_collide_;
171 bool object_level_collisions_;
172
173 int surface_friction_;
174 int surface_traction_;
175 int friction_, traction_, traction_in_air_, traction_in_water_;
176 int mass_;
177
178 bool respawns_;
179
180 bool affected_by_currents_;
181
182 std::map<std::string, wml::const_node_ptr> children_;
183
184 wml::const_node_ptr node_;
185
186 std::map<std::string, const_particle_system_factory_ptr> particle_factories_;
187
188 bool is_vehicle_;
189 int passenger_x_, passenger_y_;
190 int feet_width_;
191
192 bool use_image_for_collisions_;
193
194 bool has_feet_;
195
196 bool adjust_feet_on_animation_change_;
197
198 std::map<std::string, variant> variables_, tmp_variables_;
199 game_logic::map_formula_callable_ptr consts_;
200 std::map<std::string, variant> tags_;
201
202 std::map<std::string, game_logic::const_formula_ptr> properties_;
203
204 int teleport_offset_x_, teleport_offset_y_;
205
206 const_solid_info_ptr solid_, platform_;
207
208 //variable which is true if the object is ever solid or standable
209 bool has_solid_;
210
211 unsigned int solid_dimensions_, collide_dimensions_;
212 unsigned int weak_solid_dimensions_, weak_collide_dimensions_;
213
214 std::map<std::string, wml::const_modifier_ptr> variations_;
215 mutable std::map<std::vector<std::string>, const_custom_object_type_ptr> variations_cache_;
216
217 const_editor_entity_info_ptr editor_info_;
218
219 std::map<std::string, const_custom_object_type_ptr> sub_objects_;
220 };
221
222 #endif
0 #include <list>
1
2 #include "font.hpp"
3 #include "debug_console.hpp"
4 #include "foreach.hpp"
5 #include "preferences.hpp"
6 #include "raster.hpp"
7
8 namespace debug_console
9 {
10
11 namespace {
12 std::list<graphics::texture>& messages() {
13 static std::list<graphics::texture> message_queue;
14 return message_queue;
15 }
16 }
17
18 void add_message(const std::string& msg)
19 {
20 if(!preferences::debug()) {
21 return;
22 }
23
24 const SDL_Color col = {255, 255, 255, 255};
25 messages().push_back(font::render_text(msg, col, 14));
26 if(messages().size() > 8) {
27 messages().pop_front();
28 }
29 }
30
31 void draw()
32 {
33 if(messages().empty()) {
34 return;
35 }
36
37 int ypos = 80;
38 foreach(const graphics::texture& t, messages()) {
39 const SDL_Rect area = {0, ypos-2, t.width() + 10, t.height() + 5};
40 graphics::draw_rect(area, graphics::color_black(), 128);
41 graphics::blit_texture(t, 5, ypos);
42 ypos += t.height() + 5;
43 }
44 }
45
46 }
0 #ifndef DEBUG_CONSOLE_HPP_INCLUDED
1 #define DEBUG_CONSOLE_HPP_INCLUDED
2
3 #include <string>
4
5 namespace debug_console
6 {
7
8 void add_message(const std::string& msg);
9 void draw();
10
11 }
12
13 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include "SDL.h"
13
14 #include <iostream>
15
16 #include "dialog.hpp"
17 #include "font.hpp"
18 #include "foreach.hpp"
19 #include "raster.hpp"
20 #include "surface_cache.hpp"
21 #include "texture.hpp"
22 #include "tooltip.hpp"
23
24 namespace gui {
25
26 dialog::dialog(int x, int y, int w, int h)
27 : opened_(false), cancelled_(false), clear_bg_(true), padding_(10),
28 add_x_(0), add_y_(0), bg_alpha_(1.0), last_draw_(-1)
29 {
30 set_loc(x,y);
31 set_dim(w,h);
32 }
33
34 dialog::~dialog()
35 {}
36
37 dialog& dialog::add_widget(widget_ptr w, dialog::MOVE_DIRECTION dir)
38 {
39 add_widget(w, add_x_, add_y_, dir);
40 return *this;
41 }
42
43 dialog& dialog::add_widget(widget_ptr w, int x, int y,
44 dialog::MOVE_DIRECTION dir)
45 {
46 w->set_loc(x,y);
47 widgets_.push_back(w);
48 register_listener(w);
49 switch(dir) {
50 case MOVE_DOWN:
51 add_x_ = x;
52 add_y_ = y + w->height() + padding_;
53 break;
54 case MOVE_RIGHT:
55 add_x_ = x + w->width() + padding_;
56 add_y_ = y;
57 break;
58 }
59 return *this;
60 }
61
62 void dialog::remove_widget(widget_ptr w)
63 {
64 deregister_listener(w);
65 widgets_.erase(std::remove(widgets_.begin(),widgets_.end(),w),
66 widgets_.end());
67 }
68
69 void dialog::clear() {
70 foreach(widget_ptr w, widgets_) {
71 deregister_listener(w);
72 }
73 widgets_.clear();
74 }
75
76 void dialog::replace_widget(widget_ptr w_old, widget_ptr w_new)
77 {
78 int x = w_old->x();
79 int y = w_old->y();
80 int w = w_old->width();
81 int h = w_old->height();
82
83 std::replace(widgets_.begin(), widgets_.end(), w_old, w_new);
84
85 w_new->set_loc(x,y);
86 w_new->set_dim(w,h);
87
88 register_listener(w_new);
89 deregister_listener(w_old);
90 }
91
92 void dialog::show() {
93 opened_ = true;
94 set_visible(true);
95 }
96
97 void dialog::show_modal()
98 {
99 input::pump pump;
100 opened_ = true;
101 cancelled_ = false;
102 pump.register_listener(this);
103
104 while(opened_ && pump.process()) {
105 prepare_draw();
106 draw();
107 gui::draw_tooltip();
108 complete_draw();
109 }
110 }
111
112 void dialog::prepare_draw()
113 {
114 graphics::prepare_raster();
115 if(clear_bg()) {
116 glClearColor(0.0,0.0,0.0,0.0);
117 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
118 }
119 }
120
121 void dialog::complete_draw()
122 {
123 SDL_GL_SwapBuffers();
124
125 const int end_draw = last_draw_ + 20;
126 const int delay_time = std::max<int>(1, end_draw - SDL_GetTicks());
127
128 SDL_Delay(delay_time);
129
130 last_draw_ = SDL_GetTicks();
131 }
132
133 void dialog::handle_draw_children() const {
134 glPushMatrix();
135 glTranslatef(x(),y(),0.0);
136 foreach(const widget_ptr& w, widgets_) {
137 w->draw();
138 }
139 glPopMatrix();
140 }
141
142 void dialog::handle_draw() const
143 {
144 if(clear_bg()) {
145 SDL_Rect rect = {x(),y(),width(),height()};
146 SDL_Color col = {0,0,0,0};
147 graphics::draw_rect(rect,col,196);
148
149 //fade effect for fullscreen dialogs
150 if(bg_.valid()) {
151 if(bg_alpha_ > 0.25) {
152 bg_alpha_ -= 0.05;
153 }
154 glColor4f(1.0, 1.0, 1.0, bg_alpha_);
155 graphics::blit_texture(bg_, x(), y(), width(), height(), 0.0, 0.0, 1.0, 1.0, 0.0);
156 glColor4f(1.0, 1.0, 1.0, 1.0);
157 }
158 }
159 handle_draw_children();
160 }
161
162 bool dialog::process_event(const SDL_Event& ev, bool claimed) {
163 return widget::process_event(ev, claimed);
164 }
165
166 bool dialog::handle_event_children(const SDL_Event &event, bool claimed) {
167 SDL_Event ev = event;
168 normalize_event(&ev);
169 return input::listener_container::process_event(ev, claimed);
170 }
171
172 bool dialog::handle_event(const SDL_Event& event, bool claimed)
173 {
174 if(!claimed && opened_) {
175 if(event.type == SDL_KEYDOWN &&
176 event.key.keysym.sym == SDLK_RETURN) {
177 close();
178 cancelled_ = false;
179 claimed = true;
180 } else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) {
181 close();
182 cancelled_ = true;
183 claimed = true;
184 }
185 }
186 claimed |= handle_event_children(event, claimed);
187
188 if(!claimed) {
189 switch(event.type) {
190 //if it's a mouse button up or down and it's within the dialog area,
191 //then we claim it because nobody else should get it.
192 case SDL_MOUSEBUTTONDOWN:
193 case SDL_MOUSEBUTTONUP: {
194 int mousex, mousey;
195 SDL_GetMouseState(&mousex, &mousey);
196 if(mousex >= x() && mousex < x() + width() &&
197 mousey >= y() && mousey < y() + height()) {
198 claimed = true;
199 }
200 break;
201 }
202 }
203 }
204 return claimed;
205 }
206
207
208 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef DIALOG_HPP_INCLUDED
13 #define DIALOG_HPP_INCLUDED
14
15 #include "texture.hpp"
16 #include "widget.hpp"
17
18 #include <string>
19 #include <vector>
20
21 namespace gui {
22
23 class dialog : public widget, public input::listener_container
24 {
25 public:
26 typedef std::vector<widget_ptr>::const_iterator child_iterator;
27
28 dialog(int x, int y, int w, int h);
29 virtual ~dialog();
30 virtual void show_modal();
31 void show();
32
33 enum MOVE_DIRECTION { MOVE_DOWN, MOVE_RIGHT };
34 dialog& add_widget(widget_ptr w, MOVE_DIRECTION dir=MOVE_DOWN);
35 dialog& add_widget(widget_ptr w, int x, int y,
36 MOVE_DIRECTION dir=MOVE_DOWN);
37 void remove_widget(widget_ptr w);
38 void replace_widget(widget_ptr w_old, widget_ptr w_new);
39 void clear();
40 void set_padding(int pad) { padding_ = pad; }
41 void close() { opened_ = false; }
42 void cancel() { cancelled_ = true; close(); }
43
44 bool closed() { return !opened_; }
45 bool cancelled() { return cancelled_; }
46 void set_cursor(int x, int y) { add_x_ = x; add_y_ = y; }
47 int cursor_x() const { return add_x_; }
48 int cursor_y() const { return add_y_; }
49 child_iterator begin_children() const { return widgets_.begin(); }
50 child_iterator end_children() const { return widgets_.end(); }
51 bool process_event(const SDL_Event& e, bool claimed);
52 protected:
53 virtual bool handle_event(const SDL_Event& event, bool claimed);
54 virtual bool handle_event_children(const SDL_Event& event, bool claimed);
55 virtual void handle_draw() const;
56 virtual void handle_draw_children() const;
57 void prepare_draw();
58 void complete_draw();
59 void set_clear_bg(bool clear) { clear_bg_ = clear; };
60 bool clear_bg() const { return clear_bg_; };
61 private:
62 std::vector<widget_ptr> widgets_;
63 bool opened_;
64 bool cancelled_;
65 bool clear_bg_;
66
67 //default padding between widgets
68 int padding_;
69
70 //where the next widget will be placed by default
71 int add_x_, add_y_;
72
73 graphics::texture bg_;
74 mutable GLfloat bg_alpha_;
75
76 int last_draw_;
77 };
78
79 typedef boost::shared_ptr<dialog> dialog_ptr;
80
81 }
82
83 #endif
0 #include <string>
1
2 #include "draw_number.hpp"
3 #include "raster.hpp"
4 #include "texture.hpp"
5
6 namespace {
7
8 const int NumberWidth = 18;
9
10 void queue_blit_digit(graphics::blit_queue& q, const graphics::texture& t, char digit, int xpos, int ypos,
11 double yoffset) {
12 const int yadd = yoffset > 0.0 ? int(yoffset*14) : 0;
13 const int ysub = yoffset < 0.0 ? int(yoffset*14) : 0;
14 const int offset = (digit - '0') * 9;
15
16 const int x = xpos;
17 const int y = ypos + yadd;
18
19 const int x2 = x + NumberWidth;
20 const int y2 = y + 14 + ysub - yadd;
21
22 const GLfloat u1 = t.translate_coord_x((234.0 + offset)/400.0);
23 const GLfloat v1 = t.translate_coord_y((63.0 - ysub/2.0)/104.0);
24 const GLfloat u2 = t.translate_coord_x((242.0 + offset)/400.0);
25 const GLfloat v2 = t.translate_coord_y((70.0 - yadd/2.0)/104.0);
26
27 q.add(x, y, u1, v1);
28 q.add(x, y2, u1, v2);
29 q.add(x2, y, u2, v1);
30 q.add(x2, y2, u2, v2);
31 }
32
33 void blit_digit(const graphics::texture& t, char digit, int xpos, int ypos,
34 double yoffset) {
35 const int yadd = yoffset > 0.0 ? int(yoffset*14) : 0;
36 const int ysub = yoffset < 0.0 ? int(yoffset*14) : 0;
37 const int offset = (digit - '0') * 9;
38 graphics::queue_blit_texture(t, xpos, ypos + yadd, 16, 14 + ysub - yadd,
39 (234.0 + offset)/400.0,
40 (63.0 - ysub/2.0)/104.0,
41 (242.0 + offset)/400.0,
42 (70.0 - yadd/2.0)/104.0);
43 graphics::flush_blit_texture();
44 }
45
46 }
47
48 void queue_draw_number(graphics::blit_queue& q, int number, int places, int xpos, int ypos)
49 {
50 static const std::string Texture = "statusbar.png";
51 static const graphics::texture t = graphics::texture::get(Texture);
52
53 q.set_texture(t.get_id());
54
55 const int number_low = number/100;
56 const int number_high = 1 + number/100;
57 const int percent_high = number%100;
58 const int percent_low = 100 - percent_high;
59
60 char format_string[128];
61 sprintf(format_string, "%%0%dd", places);
62 char buf_high[128], buf_low[128];
63 sprintf(buf_low, format_string, number_low);
64 sprintf(buf_high, format_string, number_high);
65
66 for(int n = 0; n != places; ++n, xpos += NumberWidth) {
67 if(buf_low[n] == buf_high[n]) {
68 queue_blit_digit(q, t, buf_low[n], xpos, ypos, 0.0);
69 continue;
70 }
71
72 if(percent_low > 0) {
73 queue_blit_digit(q, t, buf_low[n], xpos, ypos, (percent_low - 100)/100.0);
74 }
75
76 if(percent_high > 0) {
77 queue_blit_digit(q, t, buf_high[n], xpos, ypos, (100 - percent_high)/100.0);
78 }
79 }
80
81 }
82
83 void draw_number(int number, int places, int xpos, int ypos)
84 {
85
86 static const std::string Texture = "statusbar.png";
87 graphics::texture t = graphics::texture::get(Texture);
88
89 const int number_low = number/100;
90 const int number_high = 1 + number/100;
91 const int percent_high = number%100;
92 const int percent_low = 100 - percent_high;
93
94 char format_string[128];
95 sprintf(format_string, "%%0%dd", places);
96 char buf_high[128], buf_low[128];
97 sprintf(buf_low, format_string, number_low);
98 sprintf(buf_high, format_string, number_high);
99
100 for(int n = 0; n != places; ++n, xpos += 18) {
101 if(buf_low[n] == buf_high[n]) {
102 blit_digit(t, buf_low[n], xpos, ypos, 0.0);
103 continue;
104 }
105
106 if(percent_low > 0) {
107 blit_digit(t, buf_low[n], xpos, ypos, (percent_low - 100)/100.0);
108 }
109
110 if(percent_high > 0) {
111 blit_digit(t, buf_high[n], xpos, ypos, (100 - percent_high)/100.0);
112 }
113 }
114 }
0 #ifndef DRAW_NUMBER_HPP_INCLUDED
1 #define DRAW_NUMBER_HPP_INCLUDED
2
3 namespace graphics {
4 class blit_queue;
5 }
6
7 void queue_draw_number(graphics::blit_queue& q, int number, int places, int xpos, int ypos);
8 void draw_number(int number, int places, int xpos, int ypos);
9
10 #endif
0 #include <GL/gl.h>
1 #include <GL/glu.h>
2 #include <SDL.h>
3 #include <algorithm>
4 #include <cmath>
5 #include <iostream>
6
7 #include <boost/lexical_cast.hpp>
8 #include <boost/scoped_ptr.hpp>
9
10 #include "asserts.hpp"
11 #include "controls.hpp"
12 #include "debug_console.hpp"
13 #include "draw_number.hpp"
14 #include "draw_scene.hpp"
15 #include "font.hpp"
16 #include "foreach.hpp"
17 #include "graphical_font.hpp"
18 #include "level.hpp"
19 #include "message_dialog.hpp"
20 #include "player_info.hpp"
21 #include "preferences.hpp"
22 #include "raster.hpp"
23 #include "speech_dialog.hpp"
24 #include "texture.hpp"
25
26 namespace {
27
28 std::string& scene_title() {
29 static std::string title;
30 return title;
31 }
32
33 struct screen_flash {
34 graphics::color_transform color, delta;
35 int duration;
36 };
37
38 std::vector<screen_flash>& flashes() {
39 static std::vector<screen_flash> obj;
40 return obj;
41 }
42
43 int scene_title_duration_;
44
45 screen_position last_position;
46 }
47
48 screen_position& last_draw_position()
49 {
50 return last_position;
51 }
52
53 void screen_color_flash(const graphics::color_transform& color, const graphics::color_transform& color_delta, int duration)
54 {
55 screen_flash f = { color, color_delta, duration };
56 flashes().push_back(f);
57 }
58
59 void set_scene_title(const std::string& msg, int duration) {
60 scene_title() = msg;
61 scene_title_duration_ = duration;
62 }
63
64 GLfloat hp_ratio = -1.0;
65 void draw_scene(const level& lvl, screen_position& pos, const entity* focus) {
66 static int frame_num = 0;
67 ++frame_num;
68 if(focus == NULL && lvl.player()) {
69 focus = &lvl.player()->get_entity();
70 }
71
72 last_position = pos;
73
74 //flag which gets set to false if we abort drawing, due to the
75 //screen position being initialized now.
76 const bool draw_level = pos.init;
77
78 const int start_time = SDL_GetTicks();
79 graphics::prepare_raster();
80 glPushMatrix();
81
82 const int camera_rotation = lvl.camera_rotation();
83 if(camera_rotation) {
84 GLfloat rotate = GLfloat(camera_rotation)/1000.0;
85 glRotatef(rotate, 0.0, 0.0, 1.0);
86 }
87
88 if(pos.flip_rotate) {
89 glClearColor(0.0, 0.0, 0.0, 0.0);
90 glClear(GL_COLOR_BUFFER_BIT);
91
92 const SDL_Surface* fb = SDL_GetVideoSurface();
93 const double angle = sin(0.5*3.141592653589*GLfloat(pos.flip_rotate)/1000.0);
94 const int pixels = (preferences::actual_screen_width()/2)*angle;
95
96 //then squish all future drawing inwards
97 glViewport(pixels, 0, preferences::actual_screen_width() - pixels*2, preferences::actual_screen_height());
98 }
99
100 if(focus) {
101 // If the camera is automatically moved along by the level (e.g. a
102 // hurtling through the sky level) do that here.
103 pos.x += lvl.auto_move_camera_x()*100;
104 pos.y += lvl.auto_move_camera_y()*100;
105
106 //find how much padding will have to be on the edge of the screen due
107 //to the level being wider than the screen. This value will be 0
108 //if the level is larger than the screen (i.e. most cases)
109 const int x_screen_pad = std::max<int>(0, graphics::screen_width() - lvl.boundaries().w());
110
111 const int y_screen_pad = std::max<int>(0, graphics::screen_height() - lvl.boundaries().h());
112
113 //find the boundary values for the camera position based on the size
114 //of the level. These boundaries keep the camera from ever going out
115 //of the bounds of the level.
116 const int min_x = lvl.boundaries().x() + graphics::screen_width()/2 - x_screen_pad/2;
117 const int max_x = lvl.boundaries().x2() - graphics::screen_width()/2 + x_screen_pad/2;
118 const int min_y = lvl.boundaries().y() + graphics::screen_height()/2 - y_screen_pad/2;
119 const int max_y = lvl.boundaries().y2() - graphics::screen_height()/2 + y_screen_pad/2;
120
121 //we look a certain number of frames ahead -- assuming the focus
122 //keeps moving at the current velocity, we converge toward the point
123 //they will be at in x frames.
124 const int PredictiveFramesHorz = 20;
125 const int PredictiveFramesVert = 5;
126
127 int displacement_x = 0, displacement_y = 0;
128 if(pos.focus_x || pos.focus_y) {
129 displacement_x = focus->feet_x() - pos.focus_x;
130 displacement_y = focus->feet_y() - pos.focus_y;
131 }
132
133 pos.focus_x = focus->feet_x();
134 pos.focus_y = focus->feet_y();
135
136 //find the point we want the camera to converge toward. It will be the
137 //feet of the player, but inside the boundaries we calculated above.
138 int x = std::min(std::max(focus->feet_x() + displacement_x*PredictiveFramesHorz, min_x), max_x);
139
140 //calculate the adjustment to the camera's target position based on
141 //our vertical look. This is calculated as the square root of the
142 //vertical look, to make the movement slowly converge.
143 const int vertical_look = focus->vertical_look();
144
145 //find the y point for the camera to converge toward
146 int y = std::min(std::max(focus->feet_y() - graphics::screen_height()/(5*lvl.zoom_level()) + displacement_y*PredictiveFramesVert + vertical_look, min_y), max_y);
147
148 if(lvl.focus_override().empty() == false) {
149 std::vector<entity_ptr> v = lvl.focus_override();
150 int left = 0, right = 0, top = 0, bottom = 0;
151 while(v.empty() == false) {
152 left = v[0]->feet_x();
153 right = v[0]->feet_x();
154 top = v[0]->feet_y();
155 bottom = v[0]->feet_y();
156 foreach(const entity_ptr& e, v) {
157 left = std::min<int>(e->feet_x(), left);
158 right = std::max<int>(e->feet_x(), right);
159 top = std::min<int>(e->feet_y(), top);
160 bottom = std::min<int>(e->feet_y(), bottom);
161 }
162
163 const int BorderSize = 20;
164 if(v.size() == 1 || right - left < graphics::screen_width()/lvl.zoom_level() - BorderSize && bottom - top < graphics::screen_height()/lvl.zoom_level() - BorderSize) {
165 break;
166 }
167
168 break;
169
170 v.pop_back();
171 }
172
173 x = std::min(std::max((left + right)/2, min_x), max_x);
174 y = std::min(std::max((top + bottom)/2 - graphics::screen_height()/(5*lvl.zoom_level()), min_y), max_y);
175 }
176
177 if(lvl.lock_screen()) {
178 x = lvl.lock_screen()->x;
179 y = lvl.lock_screen()->y;
180 }
181
182 //find the target x,y position of the camera in centi-pixels. Note that
183 //(x,y) represents the position the camera should center on, while
184 //now we're calculating the top-left point.
185 //
186 //the actual camera position will converge toward this point
187 const int target_xpos = 100*(x - graphics::screen_width()/2);
188 const int target_ypos = 100*(y - graphics::screen_height()/2);
189
190 if(pos.init == false) {
191 pos.x = target_xpos;
192 pos.y = target_ypos;
193 pos.init = true;
194 } else {
195 //Make (pos.x, pos.y) converge toward (target_xpos,target_ypos).
196 //We do this by moving asymptotically toward the target, which
197 //makes the camera have a nice acceleration/decceleration effect
198 //as the target position moves.
199 const int horizontal_move_speed = 30/lvl.zoom_level();
200 const int vertical_move_speed = 10/lvl.zoom_level();
201 int xdiff = (target_xpos - pos.x)/horizontal_move_speed;
202 int ydiff = (target_ypos - pos.y)/vertical_move_speed;
203
204 pos.x += xdiff;
205 pos.y += ydiff;
206 }
207
208
209 //shake decay is handled automatically; just by giving it an offset and velocity,
210 //it will automatically return to equilibrium
211
212 //shake speed
213 pos.x += (pos.shake_x_offset);
214 pos.y += (pos.shake_y_offset);
215
216 //shake velocity
217 pos.shake_x_offset += pos.shake_x_vel;
218 pos.shake_y_offset += pos.shake_y_vel;
219
220 //shake acceleration
221 if ((std::abs(pos.shake_x_vel) < 50) && (std::abs(pos.shake_x_offset) < 50)){
222 //prematurely end the oscillation if it's in the asymptote
223 pos.shake_x_offset = 0;
224 pos.shake_x_vel = 0;
225 }else{
226 //extraneous signs kept for consistency with conventional spring physics, also
227 //the value that "offset" is divided by, is (the inverse of) 'k', aka "spring stiffness"
228 //the value that "velocity" is divided by, is (the inverse of) 'b', aka "oscillation damping",
229 //which causes the spring to come to rest.
230 //These values are very sensitive, and tweaking them wrongly will cause the spring to 'explode',
231 //and increase its motion out of game-bounds.
232 if (pos.shake_x_offset > 0){
233 pos.shake_x_vel -= (1 * pos.shake_x_offset/3 + pos.shake_x_vel/15);
234 }else if(pos.shake_x_offset < 0) {
235 pos.shake_x_vel += (-1 * pos.shake_x_offset/3 - pos.shake_x_vel/15);
236 }
237 }
238 if ((std::abs(pos.shake_y_vel) < 50) && (std::abs(pos.shake_y_offset) < 50)){
239 //prematurely end the oscillation if it's in the asymptote
240 pos.shake_y_offset = 0;
241 pos.shake_y_vel = 0;
242 }else{
243 if (pos.shake_y_offset > 0){
244 pos.shake_y_vel -= (1 * pos.shake_y_offset/3 + pos.shake_y_vel/15);
245 }else if(pos.shake_y_offset < 0) {
246 pos.shake_y_vel += (-1 * pos.shake_y_offset/3 - pos.shake_y_vel/15);
247 }
248 }
249
250 const int target_zoom = lvl.zoom_level();
251 const float ZoomSpeed = 0.03;
252 if(std::abs(target_zoom - pos.zoom) < ZoomSpeed) {
253 pos.zoom = target_zoom;
254 } else if(pos.zoom > target_zoom) {
255 pos.zoom -= ZoomSpeed;
256 } else {
257 pos.zoom += ZoomSpeed;
258 }
259 }
260
261 if(!draw_level) {
262 glPopMatrix();
263 return;
264 }
265
266 int xscroll = (pos.x/100)&preferences::xypos_draw_mask;
267 int yscroll = (pos.y/100)&preferences::xypos_draw_mask;
268
269 if(pos.zoom > 1.0) {
270 glScalef(pos.zoom, pos.zoom, 0);
271 xscroll += (graphics::screen_width()/2)*(-1.0/pos.zoom + 1.0);
272 yscroll += (graphics::screen_height()/2)*(-1.0/pos.zoom + 1.0);
273 }
274
275 glTranslatef(-xscroll, -yscroll, 0);
276 lvl.draw_background(xscroll, yscroll, camera_rotation);
277
278 lvl.draw(xscroll, yscroll, graphics::screen_width(), graphics::screen_height());
279 graphics::clear_raster_distortion();
280 glPopMatrix();
281
282 for(std::vector<screen_flash>::iterator i = flashes().begin();
283 i != flashes().end(); ) {
284 const graphics::color& tint = i->color.to_color();
285 if(tint.a() > 0) {
286 graphics::draw_rect(rect(0, 0, graphics::screen_width(), graphics::screen_height()), tint);
287 }
288
289 i->color = graphics::color_transform(i->color.r() + i->delta.r(),
290 i->color.g() + i->delta.g(),
291 i->color.b() + i->delta.b(),
292 i->color.a() + i->delta.a());
293
294 if(--i->duration <= 0) {
295 i = flashes().erase(i);
296 } else {
297 ++i;
298 }
299 }
300
301 debug_console::draw();
302
303 lvl.draw_status();
304
305 if(scene_title_duration_ > 0) {
306 --scene_title_duration_;
307 const const_graphical_font_ptr f = graphical_font::get("default");
308 ASSERT_LOG(f.get() != NULL, "COULD NOT LOAD DEFAULT FONT");
309 const rect r = f->dimensions(scene_title());
310 const GLfloat alpha = scene_title_duration_ > 10 ? 1.0 : scene_title_duration_/10.0;
311 {
312 glColor4ub(0, 0, 0, 128*alpha);
313 f->draw(graphics::screen_width()/2 - r.w()/2 + 2, graphics::screen_height()/2 - r.h()/2 + 2, scene_title());
314 glColor4ub(255, 255, 255, 255*alpha);
315 }
316
317 {
318 f->draw(graphics::screen_width()/2 - r.w()/2, graphics::screen_height()/2 - r.h()/2, scene_title());
319 glColor4ub(255, 255, 255, 255);
320 }
321 }
322
323
324 if(pos.flip_rotate) {
325 const SDL_Surface* fb = SDL_GetVideoSurface();
326 const double angle = sin(0.5*3.141592653589*GLfloat(pos.flip_rotate)/1000.0);
327 //const int pixels = (fb->w/2)*angle;
328 const int pixels = (preferences::actual_screen_width()/2)*angle;
329
330
331 //first draw black over the sections of the screen which aren't to be drawn to
332 //GLshort varray1[8] = {0,0, pixels,0, pixels,fb->h, 0,fb->h};
333 //GLshort varray2[8] = {fb->w - pixels,0, fb->w,0, fb->w,fb->h, fb->w - pixels,fb->h};
334 GLshort varray1[8] = {0,0, pixels,0, pixels,preferences::actual_screen_height(), 0,preferences::actual_screen_height()};
335 GLshort varray2[8] = {preferences::actual_screen_width() - pixels,0, preferences::actual_screen_width(),0, preferences::actual_screen_width(),preferences::actual_screen_height(), preferences::actual_screen_width() - pixels,preferences::actual_screen_height()};
336 glColor4ub(0, 0, 0, 255);
337 glDisable(GL_TEXTURE_2D);
338 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
339
340 //glViewport(0, 0, fb->w, fb->h);
341 glViewport(0, 0, preferences::actual_screen_width(), preferences::actual_screen_height());
342 glVertexPointer(2, GL_SHORT, 0, &varray1);
343 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
344 glVertexPointer(2, GL_SHORT, 0, &varray2);
345 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
346 glEnable(GL_TEXTURE_2D);
347 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
348 glColor4ub(255, 255, 255, 255);
349
350 }
351
352 }
353
354 void draw_fps(const level& lvl, const performance_data& data)
355 {
356 if(!preferences::debug()) {
357 return;
358 }
359
360 const_graphical_font_ptr font(graphical_font::get("door_label"));
361 if(!font) {
362 return;
363 }
364 std::ostringstream s;
365 s << data.fps << "/" << data.cycles_per_second << "fps; " << (data.draw/10) << "% draw; " << (data.flip/10) << "% flip; " << (data.process/10) << "% process; " << (data.delay/10) << "% idle; " << lvl.num_active_chars() << " objects; " << data.nevents << " events";
366
367 rect area = font->draw(10, 60, s.str());
368
369 if(controls::num_players() > 1) {
370 //draw networking stats
371 std::ostringstream s;
372 s << controls::packets_received() << " packets received; " << controls::num_errors() << " errors; " << controls::cycles_behind() << " behind; " << controls::their_highest_confirmed() << " remote cycles " << controls::last_packet_size() << " packet";
373
374 area = font->draw(10, area.y2() + 5, s.str());
375 }
376
377 if(!data.profiling_info.empty()) {
378 font->draw(10, area.y2() + 5, data.profiling_info);
379 }
380 }
0 #ifndef DRAW_SCENE_HPP_INCLUDED
1 #define DRAW_SCENE_HPP_INCLUDED
2
3 #include <string>
4
5 namespace graphics {
6 class color;
7 class color_transform;
8 }
9
10 class entity;
11 class level;
12
13 struct screen_position {
14 screen_position() : init(false), x(0), y(0), focus_x(0), focus_y(0),
15 flip_rotate(0), coins(-1),
16 shake_x_offset(0),shake_y_offset(0),shake_x_vel(0),shake_y_vel(0), zoom(1)
17 {}
18 bool init;
19 int x, y;
20 int focus_x, focus_y;
21 int shake_x_offset,shake_y_offset;
22 int shake_x_vel,shake_y_vel;
23 int flip_rotate;
24 int coins;
25 float zoom;
26 };
27
28 screen_position& last_draw_position();
29
30 void screen_color_flash(const graphics::color_transform& color, const graphics::color_transform& color_delta, int duration);
31 void set_scene_title(const std::string& msg, int duration=150);
32 void draw_scene(const level& lvl, screen_position& pos, const entity* focus=NULL);
33
34 struct performance_data {
35 int fps;
36 int cycles_per_second;
37 int delay;
38 int draw;
39 int process;
40 int flip;
41 int cycle;
42 int nevents;
43
44 std::string profiling_info;
45 };
46
47 void draw_fps(const level& lvl, const performance_data& data);
48
49 #endif
0 #include "asserts.hpp"
1 #include "draw_tile.hpp"
2 #include "level_object.hpp"
3 #include "raster.hpp"
4
5 #include <algorithm>
6 #include <iostream>
7
8 void queue_draw_tile(graphics::blit_queue& q, const level_tile& t)
9 {
10 level_object::queue_draw(q, t);
11 }
12
13 int get_tile_corners(tile_corner* result, const graphics::texture& t, const rect& area, int tile_num, int x, int y, bool reverse)
14 {
15 if(tile_num < 0 || area.w() <= 0 || area.h() <= 0 || area.x() < 0 || area.y() < 0) {
16 return 0;
17 }
18
19 const int width = std::max<int>(t.width(), t.height());
20 const int xpos = 16*(tile_num%(width/16)) + area.x();
21 const int ypos = 16*(tile_num/(width/16)) + area.y();
22
23 //a value we subtract from the width and height of tiles when calculating
24 //UV co-ordinates. This is to prevent floating point rounding errors
25 //from causing us to draw slightly outside the tile. This is pretty
26 //nasty stuff though, and I'm not sure of a better way to do it. :(
27 const GLfloat TileEpsilon = 0.1;
28 GLfloat x1 = t.translate_coord_x(GLfloat(xpos + TileEpsilon)/GLfloat(t.width()));
29 GLfloat x2 = t.translate_coord_x(GLfloat(xpos+area.w() - TileEpsilon)/GLfloat(t.width()));
30 const GLfloat y1 = t.translate_coord_y(GLfloat(ypos + TileEpsilon)/GLfloat(t.height()));
31 const GLfloat y2 = t.translate_coord_y(GLfloat(ypos+area.h() - TileEpsilon)/GLfloat(t.height()));
32
33 int area_x = area.x()*2;
34 if(reverse) {
35 std::swap(x1, x2);
36 area_x = 32 - area.x()*2 - area.w()*2;
37 }
38
39 x += area_x;
40 y += area.y()*2;
41
42 result->vertex[0] = x;
43 result->vertex[1] = y;
44 result->uv[0] = x1;
45 result->uv[1] = y1;
46 ++result;
47
48 result->vertex[0] = x;
49 result->vertex[1] = y + area.h()*2;
50 result->uv[0] = x1;
51 result->uv[1] = y2;
52 ++result;
53
54 result->vertex[0] = x + area.w()*2;
55 result->vertex[1] = y;
56 result->uv[0] = x2;
57 result->uv[1] = y1;
58 ++result;
59
60 result->vertex[0] = x + area.w()*2;
61 result->vertex[1] = y + area.h()*2;
62 result->uv[0] = x2;
63 result->uv[1] = y2;
64 ++result;
65
66 return 4;
67 }
68
69 void queue_draw_from_tilesheet(graphics::blit_queue& q, const graphics::texture& t, const rect& area, int tile_num, int x, int y, bool reverse)
70 {
71 if(tile_num < 0 || area.w() <= 0 || area.h() <= 0 || area.x() < 0 || area.y() < 0) {
72 return;
73 }
74
75 q.set_texture(t.get_id());
76
77 const int width = std::max<int>(t.width(), t.height());
78 const int xpos = 16*(tile_num%(width/16)) + area.x();
79 const int ypos = 16*(tile_num/(width/16)) + area.y();
80
81 //a value we subtract from the width and height of tiles when calculating
82 //UV co-ordinates. This is to prevent floating point rounding errors
83 //from causing us to draw slightly outside the tile. This is pretty
84 //nasty stuff though, and I'm not sure of a better way to do it. :(
85 const GLfloat TileEpsilon = 0.01;
86 GLfloat x1 = t.translate_coord_x(GLfloat(xpos)/GLfloat(t.width()));
87 GLfloat x2 = t.translate_coord_x(GLfloat(xpos+area.w() - TileEpsilon)/GLfloat(t.width()));
88 const GLfloat y1 = t.translate_coord_y(GLfloat(ypos)/GLfloat(t.height()));
89 const GLfloat y2 = t.translate_coord_y(GLfloat(ypos+area.h() - TileEpsilon)/GLfloat(t.height()));
90
91 int area_x = area.x()*2;
92 if(reverse) {
93 std::swap(x1, x2);
94 area_x = 32 - area.x()*2 - area.w()*2;
95 }
96
97 x += area_x;
98 y += area.y()*2;
99 q.add(x, y, x1, y1);
100 q.add(x, y + area.h()*2, x1, y2);
101 q.add(x + area.w()*2, y, x2, y1);
102 q.add(x + area.w()*2, y + area.h()*2, x2, y2);
103 }
104
105 bool is_tile_opaque(const graphics::texture& t, int tile_num)
106 {
107 const int width = std::max<int>(t.width(), t.height());
108 const int xpos = 16*(tile_num%(width/16));
109 const int ypos = 16*(tile_num/(width/16));
110 for(int y = 0; y != 16; ++y) {
111 const int v = ypos + y;
112 for(int x = 0; x != 16; ++x) {
113 const int u = xpos + x;
114 if(t.is_alpha(u, v)) {
115 return false;
116 }
117 }
118 }
119
120 return true;
121 }
122
123 bool is_tile_solid_color(const graphics::texture& t, int tile_num, graphics::color& col)
124 {
125 bool first = true;
126 const int width = std::max<int>(t.width(), t.height());
127 const int xpos = 16*(tile_num%(width/16));
128 const int ypos = 16*(tile_num/(width/16));
129 for(int y = 0; y != 16; ++y) {
130 const int v = ypos + y;
131 for(int x = 0; x != 16; ++x) {
132 const int u = xpos + x;
133 const unsigned char* color = t.color_at(u, v);
134 ASSERT_LOG(color != NULL, "COULD NOT FIND COLOR IN TEXTURE");
135 graphics::color new_color(color[0], color[1], color[2], color[3]);
136 if(new_color.a() != 0xFF) {
137 return false;
138 }
139
140 if(first || col.rgba() == new_color.rgba()) {
141 col = new_color;
142 first = false;
143 } else {
144 return false;
145 }
146 }
147 }
148
149 return true;
150 }
151
152 rect get_tile_non_alpha_area(const graphics::texture& t, int tile_num)
153 {
154 const int width = std::max<int>(t.width(), t.height());
155 const int xpos = 16*(tile_num%(width/16));
156 const int ypos = 16*(tile_num/(width/16));
157 int top = -1, bottom = -1, left = -1, right = -1;
158
159 for(int y = 0; y != 16 && top == -1; ++y) {
160 const int v = ypos + y;
161 for(int x = 0; x != 16; ++x) {
162 const int u = xpos + x;
163 if(!t.is_alpha(u, v)) {
164 top = y;
165 break;
166 }
167 }
168 }
169
170 for(int y = 15; y != -1 && bottom == -1; --y) {
171 const int v = ypos + y;
172 for(int x = 0; x != 16; ++x) {
173 const int u = xpos + x;
174 if(!t.is_alpha(u, v)) {
175 bottom = y + 1;
176 break;
177 }
178 }
179 }
180
181 for(int x = 0; x != 16 && left == -1; ++x) {
182 const int u = xpos + x;
183 for(int y = 0; y != 16; ++y) {
184 const int v = ypos + y;
185 if(!t.is_alpha(u, v)) {
186 left = x;
187 break;
188 }
189 }
190 }
191
192 for(int x = 15; x != -1 && right == -1; --x) {
193 const int u = xpos + x;
194 for(int y = 0; y != 16; ++y) {
195 const int v = ypos + y;
196 if(!t.is_alpha(u, v)) {
197 right = x + 1;
198 break;
199 }
200 }
201 }
202
203 if(right <= left || bottom <= top) {
204 return rect();
205 }
206
207 return rect(left, top, right - left, bottom - top);
208 }
0 #ifndef DRAW_TILE_HPP_INCLUDED
1 #define DRAW_TILE_HPP_INCLUDED
2
3 #include "color_utils.hpp"
4 #include "geometry.hpp"
5 #include "texture.hpp"
6
7 namespace graphics {
8 class blit_queue;
9 }
10
11 class level_tile;
12
13 struct tile_corner
14 {
15 GLshort vertex[2];
16 GLfloat uv[2];
17 };
18
19 void queue_draw_tile(graphics::blit_queue& q, const level_tile& t);
20 int get_tile_corners(tile_corner* result, const graphics::texture& t, const rect& area, int tile_num, int x, int y, bool reverse);
21 void queue_draw_from_tilesheet(graphics::blit_queue& q, const graphics::texture& t, const rect& area, int tile_num, int x, int y, bool reverse);
22
23 bool is_tile_opaque(const graphics::texture& t, int tile_num);
24 bool is_tile_solid_color(const graphics::texture& t, int tile_num, graphics::color& col);
25
26 rect get_tile_non_alpha_area(const graphics::texture& t, int tile_num);
27
28 #endif
0 #include <GL/gl.h>
1 #include <GL/glu.h>
2 #include <SDL.h>
3 #include <iostream>
4 #include <cmath>
5
6 #include <boost/bind.hpp>
7 #include <boost/shared_ptr.hpp>
8
9 #include "asserts.hpp"
10 #include "border_widget.hpp"
11 #include "button.hpp"
12 #include "character_editor_dialog.hpp"
13 #include "collision_utils.hpp"
14 #include "draw_tile.hpp"
15 #include "debug_console.hpp"
16 #include "editor.hpp"
17 #include "editor_dialogs.hpp"
18 #include "editor_formula_functions.hpp"
19 #include "editor_layers_dialog.hpp"
20 #include "editor_level_properties_dialog.hpp"
21 #include "editor_stats_dialog.hpp"
22 #include "entity.hpp"
23 #include "filesystem.hpp"
24 #include "font.hpp"
25 #include "foreach.hpp"
26 #include "formatter.hpp"
27 #include "formula.hpp"
28 #include "frame.hpp"
29 #include "grid_widget.hpp"
30 #include "group_property_editor_dialog.hpp"
31 #include "image_widget.hpp"
32 #include "key.hpp"
33 #include "label.hpp"
34 #include "level.hpp"
35 #include "level_object.hpp"
36 #include "load_level.hpp"
37 #include "object_events.hpp"
38 #include "player_info.hpp"
39 #include "preferences.hpp"
40 #include "property_editor_dialog.hpp"
41 #include "raster.hpp"
42 #include "stats.hpp"
43 #include "texture.hpp"
44 #include "text_entry_widget.hpp"
45 #include "tile_map.hpp"
46 #include "tileset_editor_dialog.hpp"
47 #include "tooltip.hpp"
48 #include "wml_node.hpp"
49 #include "wml_parser.hpp"
50 #include "wml_utils.hpp"
51 #include "wml_writer.hpp"
52
53 #include "IMG_savepng.h"
54
55 namespace {
56 //keep a map of editors so that when we edit a level and then later
57 //come back to it we'll save all the state we had previously
58 std::map<std::string, editor*> all_editors;
59
60 //the last level we edited
61 std::string& g_last_edited_level() {
62 static std::string str;
63 return str;
64 }
65
66 bool g_draw_stats = false;
67
68 void toggle_draw_stats() {
69 g_draw_stats = !g_draw_stats;
70 }
71 }
72
73 class editor_menu_dialog : public gui::dialog
74 {
75 struct menu_item {
76 std::string description;
77 std::string hotkey;
78 boost::function<void()> action;
79 };
80
81 void execute_menu_item(const std::vector<menu_item>& items, int n) {
82 if(n >= 0 && n < items.size()) {
83 items[n].action();
84 }
85
86 remove_widget(context_menu_);
87 context_menu_.reset();
88 }
89
90 void show_file_menu() {
91 menu_item items[] = {
92 "New...", "", boost::bind(&editor_menu_dialog::new_level, this),
93 "Open...", "ctrl+o", boost::bind(&editor_menu_dialog::open_level, this),
94 "Save", "ctrl+s", boost::bind(&editor::save_level, &editor_),
95 "Save As...", "", boost::bind(&editor_menu_dialog::save_level_as, this),
96 "Exit", "<esc>", boost::bind(&editor::quit, &editor_),
97 };
98
99 std::vector<menu_item> res;
100 foreach(const menu_item& m, items) {
101 res.push_back(m);
102 }
103 show_menu(res);
104 }
105
106 void show_edit_menu() {
107 menu_item items[] = {
108 "Level Properties", "", boost::bind(&editor::edit_level_properties, &editor_),
109 "Undo", "u", boost::bind(&editor::undo_command, &editor_),
110 "Redo", "r", boost::bind(&editor::redo_command, &editor_),
111 "Edit Object...", "", boost::bind(&editor::edit_object_type, &editor_),
112 "New Object...", "", boost::bind(&editor::new_object_type, &editor_),
113 };
114
115 std::vector<menu_item> res;
116 foreach(const menu_item& m, items) {
117 res.push_back(m);
118 }
119 show_menu(res);
120 }
121
122 void show_view_menu() {
123 menu_item items[] = {
124 "Zoom Out", "x", boost::bind(&editor::zoom_out, &editor_),
125 "Zoom In", "z", boost::bind(&editor::zoom_in, &editor_),
126 editor_.get_level().show_foreground() ? "Hide Foreground" : "Show Foreground", "f", boost::bind(&level::set_show_foreground, &editor_.get_level(), !editor_.get_level().show_foreground()),
127 editor_.get_level().show_background() ? "Hide Background" : "Show Background", "b", boost::bind(&level::set_show_background, &editor_.get_level(), !editor_.get_level().show_background()),
128 g_draw_stats ? "Hide Stats" : "Show Stats", "", toggle_draw_stats
129 };
130
131 std::vector<menu_item> res;
132 foreach(const menu_item& m, items) {
133 res.push_back(m);
134 }
135 show_menu(res);
136 }
137
138 void show_stats_menu() {
139 menu_item items[] = {
140 "Details...", "", boost::bind(&editor::show_stats, &editor_),
141 "Refresh stats", "", boost::bind(&editor::download_stats, &editor_),
142 };
143 std::vector<menu_item> res;
144 foreach(const menu_item& m, items) {
145 res.push_back(m);
146 }
147 show_menu(res);
148 }
149
150 void show_scripts_menu() {
151 std::vector<menu_item> res;
152 foreach(const editor_script::info& script, editor_script::all_scripts()) {
153 menu_item item = { script.name, "", boost::bind(&editor::run_script, &editor_, script.name) };
154 res.push_back(item);
155 }
156
157 show_menu(res);
158 }
159
160 void show_window_menu() {
161 std::vector<menu_item> res;
162 for(std::map<std::string, editor*>::const_iterator i = all_editors.begin(); i != all_editors.end(); ++i) {
163 std::string name = i->first;
164 if(name == g_last_edited_level()) {
165 name += " *";
166 }
167 menu_item item = { name, "", boost::bind(&editor_menu_dialog::open_level_in_editor, this, i->first) };
168 res.push_back(item);
169 }
170 show_menu(res);
171 }
172
173 void show_menu(const std::vector<menu_item>& items) {
174 using namespace gui;
175 gui::grid* grid = new gui::grid(2);
176 grid->set_hpad(40);
177 grid->set_show_background(true);
178 grid->allow_selection();
179 grid->swallow_clicks();
180 grid->register_selection_callback(boost::bind(&editor_menu_dialog::execute_menu_item, this, items, _1));
181 foreach(const menu_item& item, items) {
182 grid->add_col(widget_ptr(new label(item.description, graphics::color_white()))).
183 add_col(widget_ptr(new label(item.hotkey, graphics::color_white())));
184 }
185
186 int mousex, mousey;
187 SDL_GetMouseState(&mousex, &mousey);
188
189 mousex -= x();
190 mousey -= y();
191
192 remove_widget(context_menu_);
193 context_menu_.reset(grid);
194 add_widget(context_menu_, mousex, mousey);
195 }
196
197 editor& editor_;
198 gui::widget_ptr context_menu_;
199 public:
200 explicit editor_menu_dialog(editor& e)
201 : gui::dialog(0, 0, graphics::screen_width() - 160, 40), editor_(e)
202 {
203 init();
204 }
205
206 void init() {
207 clear();
208
209 using namespace gui;
210 gui::grid* grid = new gui::grid(6);
211 grid->add_col(widget_ptr(
212 new button(widget_ptr(new label("File", graphics::color_white())),
213 boost::bind(&editor_menu_dialog::show_file_menu, this))));
214 grid->add_col(widget_ptr(
215 new button(widget_ptr(new label("Edit", graphics::color_white())),
216 boost::bind(&editor_menu_dialog::show_edit_menu, this))));
217 grid->add_col(widget_ptr(
218 new button(widget_ptr(new label("View", graphics::color_white())),
219 boost::bind(&editor_menu_dialog::show_view_menu, this))));
220 grid->add_col(widget_ptr(
221 new button(widget_ptr(new label("Window", graphics::color_white())),
222 boost::bind(&editor_menu_dialog::show_window_menu, this))));
223 grid->add_col(widget_ptr(
224 new button(widget_ptr(new label("Statistics", graphics::color_white())),
225 boost::bind(&editor_menu_dialog::show_stats_menu, this))));
226 add_widget(widget_ptr(grid));
227
228 grid->add_col(widget_ptr(
229 new button(widget_ptr(new label("Scripts", graphics::color_white())),
230 boost::bind(&editor_menu_dialog::show_scripts_menu, this))));
231 add_widget(widget_ptr(grid));
232 }
233
234 void new_level() {
235 using namespace gui;
236 dialog d(0, 0, graphics::screen_width(), graphics::screen_height());
237 d.add_widget(widget_ptr(new label("New Level", graphics::color_white(), 48)));
238 text_entry_widget* entry = new text_entry_widget;
239 d.add_widget(widget_ptr(new label("Name:", graphics::color_white())))
240 .add_widget(widget_ptr(entry));
241 d.show_modal();
242
243 std::string name = entry->text();
244 if(name.empty() == false) {
245 sys::write_file(preferences::level_path() + name, "[level]\n[/level]\n");
246 editor_.close();
247 g_last_edited_level() = name;
248 }
249 }
250
251 void save_level_as() {
252 using namespace gui;
253 dialog d(0, 0, graphics::screen_width(), graphics::screen_height());
254 d.add_widget(widget_ptr(new label("Save As", graphics::color_white(), 48)));
255 text_entry_widget* entry = new text_entry_widget;
256 d.add_widget(widget_ptr(new label("Name:", graphics::color_white())))
257 .add_widget(widget_ptr(entry));
258 d.show_modal();
259
260 if(!d.cancelled() && entry->text().empty() == false) {
261 editor_.save_level_as(entry->text());
262 }
263 }
264
265 void open_level() {
266 open_level_in_editor(show_choose_level_dialog("Open Level"));
267 }
268
269 void open_level_in_editor(const std::string& lvl) {
270 if(lvl.empty() == false && lvl != g_last_edited_level()) {
271 remove_widget(context_menu_);
272 context_menu_.reset();
273 editor_.close();
274 g_last_edited_level() = lvl;
275 }
276 }
277 };
278
279 namespace {
280 const char* ModeStrings[] = {"Tiles", "Objects", "Properties",};
281
282 const char* ToolStrings[] = {
283 "Add tiles by drawing rectangles",
284 "Select Tiles",
285 "Select connected regions of tiles",
286 "Add tiles by drawing pencil strokes",
287 "Pick tiles or objects",
288 "Add Objects",
289 "Select Objects",
290 };
291
292 const char* ToolIcons[] = {"editor_draw_rect", "editor_rect_select", "editor_wand", "editor_pencil", "editor_eyedropper", "editor_add_object", "editor_select_object",};
293 }
294
295 class editor_mode_dialog : public gui::dialog
296 {
297 editor& editor_;
298 gui::widget_ptr context_menu_;
299
300 std::vector<gui::border_widget*> tool_borders_;
301
302 void select_tool(int tool)
303 {
304 if(tool >= 0 && tool < editor::NUM_TOOLS) {
305 editor_.change_tool(static_cast<editor::EDIT_TOOL>(tool));
306 }
307 }
308
309 bool handle_event(const SDL_Event& event, bool claimed)
310 {
311 if(!claimed) {
312 const bool ctrl_pressed = (SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL)) != 0;
313 if(ctrl_pressed) {
314 return false;
315 }
316
317 switch(event.type) {
318 case SDL_KEYDOWN: {
319 switch(event.key.keysym.sym) {
320 //TODO: add short cuts for tools here.
321
322 }
323
324 break;
325 }
326 }
327 }
328
329 return claimed || dialog::handle_event(event, claimed);
330 }
331
332 public:
333 explicit editor_mode_dialog(editor& e)
334 : gui::dialog(graphics::screen_width() - 160, 0, 160, 160), editor_(e)
335 {
336 init();
337 }
338
339 void init()
340 {
341 using namespace gui;
342 clear();
343
344 tool_borders_.clear();
345
346 grid_ptr grid(new gui::grid(3));
347 for(int n = 0; n != editor::NUM_TOOLS; ++n) {
348 button_ptr tool_button(
349 new button(widget_ptr(new gui_section_widget(ToolIcons[n], 26, 26)),
350 boost::bind(&editor_mode_dialog::select_tool, this, n)));
351 tool_button->set_tooltip(ToolStrings[n]);
352 tool_borders_.push_back(new border_widget(tool_button, graphics::color(0,0,0,0)));
353 grid->add_col(widget_ptr(tool_borders_.back()));
354 }
355
356 grid->finish_row();
357 add_widget(grid, 5, 5);
358
359 refresh_selection();
360 }
361
362 void refresh_selection() {
363 using namespace gui;
364 for(int n = 0; n != tool_borders_.size(); ++n) {
365 tool_borders_[n]->set_color(n == editor_.tool() ? graphics::color(255,255,255,255) : graphics::color(0,0,0,0));
366 }
367 }
368 };
369
370 namespace {
371
372 const int RectEdgeSelectThreshold = 6;
373
374 void execute_functions(const std::vector<boost::function<void()> >& v) {
375 foreach(const boost::function<void()>& f, v) {
376 f();
377 }
378 }
379
380 bool g_started_dragging_object = false;
381
382 //the current state of the rectangle we're dragging
383 rect g_rect_drawing;
384
385 //the tiles that we've drawn in the current action.
386 std::vector<point> g_current_draw_tiles;
387
388 const editor_variable_info* g_variable_editing = NULL;
389 int g_variable_editing_original_value = 0;
390 const editor_variable_info* variable_info_selected(const_entity_ptr e, int xpos, int ypos, int zoom)
391 {
392 if(!e || !e->editor_info()) {
393 return NULL;
394 }
395
396 foreach(const editor_variable_info& var, e->editor_info()->vars()) {
397 const variant value = e->query_value(var.variable_name());
398 switch(var.type()) {
399 case editor_variable_info::XPOSITION: {
400 if(!value.is_int()) {
401 break;
402 }
403
404 if(xpos >= value.as_int() - zoom*RectEdgeSelectThreshold && xpos <= value.as_int() + zoom*RectEdgeSelectThreshold) {
405 return &var;
406 }
407 break;
408 }
409 case editor_variable_info::YPOSITION: {
410 if(!value.is_int()) {
411 break;
412 }
413
414 if(ypos >= value.as_int() - zoom*RectEdgeSelectThreshold && ypos <= value.as_int() + zoom*RectEdgeSelectThreshold) {
415 return &var;
416 }
417 break;
418 }
419 default:
420 break;
421 }
422 }
423
424 return NULL;
425 }
426
427 int round_tile_size(int n)
428 {
429 if(n >= 0) {
430 return n - n%TileSize;
431 } else {
432 n = -n + 32;
433 return -(n - n%TileSize);
434 }
435 }
436
437 bool resizing_left_level_edge = false,
438 resizing_right_level_edge = false,
439 resizing_top_level_edge = false,
440 resizing_bottom_level_edge = false;
441
442 rect modify_selected_rect(rect boundaries, int xpos, int ypos) {
443
444 const int x = round_tile_size(xpos);
445 const int y = round_tile_size(ypos);
446
447 if(resizing_left_level_edge) {
448 boundaries = rect::from_coordinates(x, boundaries.y(), boundaries.x2(), boundaries.y2());
449 }
450
451 if(resizing_right_level_edge) {
452 boundaries = rect::from_coordinates(boundaries.x(), boundaries.y(), x, boundaries.y2());
453 }
454
455 if(resizing_top_level_edge) {
456 boundaries = rect::from_coordinates(boundaries.x(), y, boundaries.x2(), boundaries.y2());
457 }
458
459 if(resizing_bottom_level_edge) {
460 boundaries = rect::from_coordinates(boundaries.x(), boundaries.y(), boundaries.x2(), y);
461 }
462
463 return boundaries;
464 }
465
466 //find if an edge of a rectangle is selected
467 bool rect_left_edge_selected(const rect& r, int x, int y, int zoom) {
468 return y >= r.y() - RectEdgeSelectThreshold*zoom &&
469 y <= r.y2() + RectEdgeSelectThreshold*zoom &&
470 x >= r.x() - RectEdgeSelectThreshold*zoom &&
471 x <= r.x() + RectEdgeSelectThreshold*zoom;
472 }
473
474 bool rect_right_edge_selected(const rect& r, int x, int y, int zoom) {
475 return y >= r.y() - RectEdgeSelectThreshold*zoom &&
476 y <= r.y2() + RectEdgeSelectThreshold*zoom &&
477 x >= r.x2() - RectEdgeSelectThreshold*zoom &&
478 x <= r.x2() + RectEdgeSelectThreshold*zoom;
479 }
480
481 bool rect_top_edge_selected(const rect& r, int x, int y, int zoom) {
482 return x >= r.x() - RectEdgeSelectThreshold*zoom &&
483 x <= r.x2() + RectEdgeSelectThreshold*zoom &&
484 y >= r.y() - RectEdgeSelectThreshold*zoom &&
485 y <= r.y() + RectEdgeSelectThreshold*zoom;
486 }
487
488 bool rect_bottom_edge_selected(const rect& r, int x, int y, int zoom) {
489 return x >= r.x() - RectEdgeSelectThreshold*zoom &&
490 x <= r.x2() + RectEdgeSelectThreshold*zoom &&
491 y >= r.y2() - RectEdgeSelectThreshold*zoom &&
492 y <= r.y2() + RectEdgeSelectThreshold*zoom;
493 }
494
495 std::vector<editor::tileset> tilesets;
496
497 std::vector<editor::enemy_type> enemy_types;
498
499 int selected_property = 0;
500
501 }
502
503 void editor::enemy_type::init(wml::const_node_ptr node)
504 {
505 enemy_types.clear();
506 const std::vector<const_custom_object_type_ptr> types = custom_object_type::get_all();
507 foreach(const_custom_object_type_ptr t, types) {
508 if(t->editor_info()) {
509 enemy_types.push_back(editor::enemy_type(*t));
510 }
511 }
512 }
513
514 editor::enemy_type::enemy_type(const custom_object_type& type)
515 : category(type.editor_info()->category())
516 {
517 wml::node_ptr new_node(new wml::node("character"));
518 new_node->set_attr("type", type.id());
519 new_node->set_attr("custom", "yes");
520 new_node->set_attr("face_right", "false");
521 new_node->set_attr("x", "1500");
522 new_node->set_attr("y", "0");
523
524 if(type.is_human()) {
525 new_node->set_attr("is_human", "true");
526 }
527
528 node = new_node;
529 preview_object = entity::build(node);
530 preview_frame = &preview_object->current_frame();
531 }
532
533 void editor::tileset::init(wml::const_node_ptr node)
534 {
535 wml::node::const_child_iterator i1 = node->begin_child("tileset");
536 wml::node::const_child_iterator i2 = node->end_child("tileset");
537 for(; i1 != i2; ++i1) {
538 tilesets.push_back(editor::tileset(i1->second));
539 }
540 }
541
542 editor::tileset::tileset(wml::const_node_ptr node)
543 : category(node->attr("category")), type(node->attr("type")),
544 zorder(wml::get_int(node, "zorder")),
545 x_speed(wml::get_int(node, "x_speed", 100)),
546 y_speed(wml::get_int(node, "y_speed", 100)),
547 sloped(wml::get_bool(node, "sloped"))
548 {
549 if(node->get_child("preview")) {
550 preview.reset(new tile_map(node->get_child("preview")));
551 }
552 }
553
554 void editor::edit(const char* level_cfg, int xpos, int ypos)
555 {
556 preferences::editor_screen_size_scope screen_size_scope;
557
558 editor*& e = all_editors[level_cfg];
559 if(!e) {
560 e = new editor(level_cfg);
561 }
562
563 if(xpos != -1) {
564 e->xpos_ = xpos;
565 e->ypos_ = ypos;
566 }
567
568 e->edit_level();
569 if(g_last_edited_level() != level_cfg) {
570 //a new level was set, so start editing it now.
571 edit(g_last_edited_level().c_str());
572 }
573
574 clear_level_wml();
575 }
576
577 std::string editor::last_edited_level()
578 {
579 return g_last_edited_level();
580 }
581
582 editor::editor(const char* level_cfg)
583 : zoom_(1), xpos_(0), ypos_(0), anchorx_(0), anchory_(0),
584 selected_entity_startx_(0), selected_entity_starty_(0),
585 filename_(level_cfg), tool_(TOOL_ADD_RECT),
586 done_(false), face_right_(true),
587 cur_tileset_(0), cur_object_(0),
588 current_dialog_(NULL),
589 drawing_rect_(false), dragging_(false), level_changed_(0)
590 {
591 editor_menu_dialog_.reset(new editor_menu_dialog(*this));
592 editor_mode_dialog_.reset(new editor_mode_dialog(*this));
593
594 static bool first_time = true;
595 if(first_time) {
596 wml::const_node_ptr editor_cfg = wml::parse_wml(sys::read_file("data/editor.cfg"));
597 tile_map::load_all();
598 tileset::init(editor_cfg);
599 enemy_type::init(editor_cfg);
600 first_time = false;
601 }
602
603 assert(!tilesets.empty());
604 lvl_.reset(new level(level_cfg));
605 lvl_->finish_loading();
606 lvl_->set_editor();
607 lvl_->set_as_current_level();
608
609 group_property_dialog_.reset(new editor_dialogs::group_property_editor_dialog(*this));
610 property_dialog_.reset(new editor_dialogs::property_editor_dialog(*this));
611 }
612
613 editor::~editor()
614 {
615 }
616
617 void editor::group_selection()
618 {
619 const int group = lvl_->add_group();
620 std::vector<boost::function<void()> > undo, redo;
621
622 foreach(const entity_ptr& c, lvl_->editor_selection()) {
623 undo.push_back(boost::bind(&level::set_character_group, lvl_.get(), c, c->group()));
624 redo.push_back(boost::bind(&level::set_character_group, lvl_.get(), c, group));
625 }
626
627 execute_command(
628 boost::bind(execute_functions, redo),
629 boost::bind(execute_functions, undo));
630 }
631
632 void editor::toggle_facing()
633 {
634 face_right_ = !face_right_;
635 if(character_dialog_) {
636 character_dialog_->init();
637 }
638 }
639
640 void editor::process_ghost_objects()
641 {
642 lvl_->swap_chars(ghost_objects_);
643
644 const size_t num_chars_before = lvl_->get_chars().size();
645 const std::vector<entity_ptr> chars = lvl_->get_chars();
646 foreach(const entity_ptr& p, chars) {
647 p->process(*lvl_);
648 }
649
650 foreach(const entity_ptr& p, chars) {
651 p->handle_event(OBJECT_EVENT_DRAW);
652 }
653
654 lvl_->swap_chars(ghost_objects_);
655
656 foreach(entity_ptr& p, ghost_objects_) {
657 if(p && p->destroyed()) {
658 lvl_->remove_character(p);
659 p = entity_ptr();
660 }
661 }
662
663 ghost_objects_.erase(std::remove(ghost_objects_.begin(), ghost_objects_.end(), entity_ptr()), ghost_objects_.end());
664 }
665
666 void editor::remove_ghost_objects()
667 {
668 foreach(entity_ptr c, ghost_objects_) {
669 lvl_->remove_character(c);
670 }
671 }
672
673 namespace {
674 unsigned int get_mouse_state(int& mousex, int& mousey) {
675 unsigned int res = SDL_GetMouseState(&mousex, &mousey);
676 mousex = (mousex*graphics::screen_width())/preferences::virtual_screen_width();
677 mousey = (mousey*graphics::screen_height())/preferences::virtual_screen_height();
678
679 return res;
680 }
681 }
682
683 void editor::edit_level()
684 {
685 reset_dialog_positions();
686 stats::flush();
687 try {
688 load_stats();
689 } catch(...) {
690 debug_console::add_message("Error parsing stats");
691 std::cerr << "ERROR LOADING STATS\n";
692 }
693
694 lvl_->set_as_current_level();
695
696 foreach(entity_ptr c, lvl_->get_chars()) {
697 if(entity_collides_with_level(*lvl_, *c, MOVE_NONE)) {
698 if(place_entity_in_level(*lvl_, *c)) {
699 debug_console::add_message(formatter() << "Adjusted position of " << c->debug_description() << " to fit in the level");
700 } else {
701 debug_console::add_message(formatter() << c->debug_description() << " is in an illegal position and can't be auto-corrected");
702 }
703 }
704 }
705
706 g_last_edited_level() = filename_;
707
708 tileset_dialog_.reset(new editor_dialogs::tileset_editor_dialog(*this));
709 layers_dialog_.reset(new editor_dialogs::editor_layers_dialog(*this));
710 current_dialog_ = tileset_dialog_.get();
711
712 glEnable(GL_BLEND);
713 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
714 glEnable(GL_TEXTURE_2D);
715 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
716
717 int selected_tile = 0;
718
719 //reset the tool status.
720 change_tool(tool_);
721
722 done_ = false;
723 int prev_mousex = -1, prev_mousey = -1;
724 while(!done_) {
725 const int scheduled_frame_end_time = SDL_GetTicks() + 20;
726
727 if(editor_mode_dialog_) {
728 editor_mode_dialog_->refresh_selection();
729 }
730
731 process_ghost_objects();
732 handle_scrolling();
733
734 int mousex, mousey;
735 const unsigned int buttons = get_mouse_state(mousex, mousey);
736
737 if(buttons == 0) {
738 drawing_rect_ = false;
739 }
740
741 //make middle-clicking drag the screen around.
742 if(prev_mousex != -1 && prev_mousey != -1 && (buttons&SDL_BUTTON_MIDDLE)) {
743 const int diff_x = mousex - prev_mousex;
744 const int diff_y = mousey - prev_mousey;
745 xpos_ -= diff_x*zoom_;
746 ypos_ -= diff_y*zoom_;
747 }
748
749 prev_mousex = mousex;
750 prev_mousey = mousey;
751
752 const int selectx = round_tile_size(xpos_ + mousex*zoom_);
753 const int selecty = round_tile_size(ypos_ + mousey*zoom_);
754
755 const bool object_mode = (tool() == TOOL_ADD_OBJECT || tool() == TOOL_SELECT_OBJECT);
756 if(property_dialog_ && g_variable_editing) {
757 int diff = 0;
758 switch(g_variable_editing->type()) {
759 case editor_variable_info::XPOSITION:
760 diff = (xpos_ + mousex*zoom_) - anchorx_;
761 break;
762 case editor_variable_info::YPOSITION:
763 diff = (ypos_ + mousey*zoom_) - anchory_;
764 break;
765 default:
766 break;
767 }
768
769 if(property_dialog_ && property_dialog_->get_entity()) {
770 const bool ctrl_pressed = (SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL)) != 0;
771 int new_value = g_variable_editing_original_value + diff;
772 if(ctrl_pressed) {
773 new_value += TileSize/2;
774 new_value = new_value - new_value%TileSize;
775 }
776
777 mutate_object_value(property_dialog_->get_entity(), g_variable_editing->variable_name(), variant(new_value));
778 }
779 } else if(object_mode && !buttons) {
780 //remove ghost objects and re-add them. This guarantees ghost
781 //objects always remain at the end of the level ordering.
782 remove_ghost_objects();
783 entity_ptr c = lvl_->get_character_at_point(xpos_ + mousex*zoom_, ypos_ + mousey*zoom_);
784 foreach(const entity_ptr& ghost, ghost_objects_) {
785 lvl_->add_character(ghost);
786 }
787
788 lvl_->set_editor_highlight(c);
789 //See if we should add ghost objects. Human objects don't get
790 //ghost (it doesn't make much sense for them to do so)
791 if(ghost_objects_.empty() && c && !c->is_human()) {
792 //we have an object but no ghost for it, make the
793 //object's ghost and deploy it.
794 entity_ptr clone = c->clone();
795 if(clone && !entity_collides_with_level(*lvl_, *clone, MOVE_NONE)) {
796 ghost_objects_.push_back(clone);
797 lvl_->add_character(clone);
798
799 //fire the event to tell the ghost it's been added.
800 lvl_->swap_chars(ghost_objects_);
801 clone->handle_event(OBJECT_EVENT_START_LEVEL);
802 lvl_->swap_chars(ghost_objects_);
803 }
804 } else if(ghost_objects_.empty() == false && !c) {
805 //ghost objects are present but we are no longer moused-over
806 //an object, so remove the ghosts.
807 remove_ghost_objects();
808 ghost_objects_.clear();
809 }
810 } else if(object_mode && lvl_->editor_highlight()) {
811 //we're handling objects, and a button is down, and we have an
812 //object under the mouse. This means we are dragging something.
813 handle_object_dragging(mousex, mousey);
814 } else if(drawing_rect_) {
815 handle_drawing_rect(mousex, mousey);
816 }
817
818 if(!object_mode) {
819 //not in object mode, the picker still highlights objects,
820 //though it won't create ghosts, so remove all ghosts.
821 if(tool() == TOOL_PICKER) {
822 entity_ptr c = lvl_->get_character_at_point(xpos_ + mousex*zoom_, ypos_ + mousey*zoom_);
823 lvl_->set_editor_highlight(c);
824 } else {
825 lvl_->set_editor_highlight(entity_ptr());
826 }
827
828 remove_ghost_objects();
829 ghost_objects_.clear();
830 }
831
832 //if we're drawing with a pencil see if we add a new tile
833 if(tool() == TOOL_PENCIL && buttons && mousey > editor_menu_dialog_->height() && mousex < editor_mode_dialog_->x()) {
834 const int xpos = xpos_ + mousex*zoom_;
835 const int ypos = ypos_ + mousey*zoom_;
836 point p(xpos, ypos);
837 if(std::find(g_current_draw_tiles.begin(), g_current_draw_tiles.end(), p) == g_current_draw_tiles.end()) {
838 g_current_draw_tiles.push_back(p);
839
840 if(buttons&SDL_BUTTON_LEFT) {
841 add_tile_rect(p.x, p.y, p.x, p.y);
842 } else {
843 remove_tile_rect(p.x, p.y, p.x, p.y);
844 }
845 }
846 }
847
848 SDL_Event event;
849 while(SDL_PollEvent(&event)) {
850
851 if(editor_menu_dialog_->process_event(event, false)) {
852 continue;
853 }
854
855 if(editor_mode_dialog_->process_event(event, false)) {
856 continue;
857 }
858
859 if(current_dialog_ && current_dialog_->process_event(event, false)) {
860 continue;
861 }
862
863 if(layers_dialog_ && layers_dialog_->process_event(event, false)) {
864 continue;
865 }
866
867 switch(event.type) {
868 case SDL_QUIT:
869 done_ = true;
870 break;
871 case SDL_KEYDOWN:
872 if(event.key.keysym.sym == SDLK_ESCAPE) {
873 if(confirm_quit()) {
874 return;
875 }
876 }
877
878 handle_key_press(event.key);
879 break;
880
881 break;
882 case SDL_MOUSEBUTTONDOWN:
883 handle_mouse_button_down(event.button);
884 break;
885
886 case SDL_MOUSEBUTTONUP:
887 handle_mouse_button_up(event.button);
888 break;
889 case SDL_VIDEORESIZE: {
890 const SDL_ResizeEvent* const resize = reinterpret_cast<SDL_ResizeEvent*>(&event);
891
892 preferences::set_actual_screen_width(resize->w);
893 preferences::set_actual_screen_height(resize->h);
894 SDL_SetVideoMode(resize->w,resize->h,0,SDL_OPENGL|SDL_RESIZABLE|(preferences::fullscreen() ? SDL_FULLSCREEN : 0));
895
896 reset_dialog_positions();
897
898 continue;
899 }
900
901 default:
902 break;
903 }
904 }
905
906 lvl_->complete_rebuild_tiles_in_background();
907 draw();
908
909 SDL_Delay(std::max<int>(1, scheduled_frame_end_time - SDL_GetTicks()));
910 }
911 }
912
913 void editor::reset_dialog_positions()
914 {
915
916 #define SET_DIALOG_POS(d) if(d) { d->set_loc(graphics::screen_width() - d->width(), d->y()); }
917 SET_DIALOG_POS(editor_mode_dialog_);
918 SET_DIALOG_POS(character_dialog_);
919 SET_DIALOG_POS(layers_dialog_);
920 SET_DIALOG_POS(group_property_dialog_);
921 SET_DIALOG_POS(property_dialog_);
922 SET_DIALOG_POS(tileset_dialog_);
923 #undef SET_DIALOG_POS
924 }
925
926 void editor::handle_key_press(const SDL_KeyboardEvent& key)
927 {
928 if(key.keysym.sym == SDLK_s && (key.keysym.mod&KMOD_ALT)) {
929 IMG_SaveFrameBuffer((std::string(preferences::user_data_path()) + "screenshot.png").c_str(), 5);
930 }
931
932 if(key.keysym.sym == SDLK_u) {
933 undo_command();
934 }
935
936 if(key.keysym.sym == SDLK_r &&
937 !(key.keysym.mod&KMOD_CTRL)) {
938 redo_command();
939 }
940
941 if(key.keysym.sym == SDLK_z) {
942 zoom_in();
943 }
944
945 if(key.keysym.sym == SDLK_x) {
946 zoom_out();
947 }
948
949 if(key.keysym.sym == SDLK_f) {
950 lvl_->set_show_foreground(!lvl_->show_foreground());
951 }
952
953 if(key.keysym.sym == SDLK_b) {
954 lvl_->set_show_background(!lvl_->show_background());
955 }
956
957 if(editing_objects() && (key.keysym.sym == SDLK_DELETE || key.keysym.sym == SDLK_BACKSPACE) && lvl_->editor_selection().empty() == false) {
958 //deleting objects. We clear the selection as well as
959 //deleting. To undo, the previous selection will be cleared,
960 //and then the deleted objects re-selected.
961 std::vector<boost::function<void()> > redo, undo;
962 undo.push_back(boost::bind(&level::editor_clear_selection, lvl_.get()));
963
964 //if we undo, return the objects to the property dialog
965 undo.push_back(boost::bind(&editor_dialogs::group_property_editor_dialog::set_group, group_property_dialog_.get(), lvl_->editor_selection()));
966 redo.push_back(boost::bind(&level::editor_clear_selection, lvl_.get()));
967 //we want to clear the objects in the property dialog
968 redo.push_back(boost::bind(&editor_dialogs::group_property_editor_dialog::set_group, group_property_dialog_.get(), std::vector<entity_ptr>()));
969 foreach(const entity_ptr& e, lvl_->editor_selection()) {
970 redo.push_back(boost::bind(&editor::remove_object_from_level, this, e));
971 undo.push_back(boost::bind(&editor::add_object_to_level, this, e));
972 undo.push_back(boost::bind(&level::editor_select_object, lvl_.get(), e));
973 }
974 execute_command(
975 boost::bind(execute_functions, redo),
976 boost::bind(execute_functions, undo));
977 }
978
979 if(!tile_selection_.empty() && (key.keysym.sym == SDLK_DELETE || key.keysym.sym == SDLK_BACKSPACE)) {
980 int min_x = INT_MAX, min_y = INT_MAX, max_x = INT_MIN, max_y = INT_MIN;
981 std::vector<boost::function<void()> > redo, undo;
982 foreach(const point& p, tile_selection_.tiles) {
983 const int x = p.x*TileSize;
984 const int y = p.y*TileSize;
985
986 min_x = std::min(x, min_x);
987 max_x = std::max(x, max_x);
988 min_y = std::min(y, min_y);
989 max_y = std::max(y, max_y);
990
991 redo.push_back(boost::bind(&level::clear_tile_rect, lvl_.get(), x, y, x, y));
992 std::map<int, std::vector<std::string> > old_tiles;
993 lvl_->get_all_tiles_rect(x, y, x, y, old_tiles);
994 for(std::map<int, std::vector<std::string> >::const_iterator i = old_tiles.begin(); i != old_tiles.end(); ++i) {
995 undo.push_back(boost::bind(&level::add_tile_rect_vector, lvl_.get(), i->first, x, y, x, y, i->second));
996 }
997 }
998
999 if(!tile_selection_.tiles.empty()) {
1000 undo.push_back(boost::bind(&level::start_rebuild_tiles_in_background, lvl_.get(), std::vector<int>()));
1001 redo.push_back(boost::bind(&level::start_rebuild_tiles_in_background, lvl_.get(), std::vector<int>()));
1002 }
1003
1004 execute_command(
1005 boost::bind(execute_functions, redo),
1006 boost::bind(execute_functions, undo));
1007 }
1008
1009 if(key.keysym.sym == SDLK_o) {
1010 editor_menu_dialog_->open_level();
1011 }
1012
1013 if(key.keysym.sym == SDLK_s) {
1014 save_level();
1015 }
1016
1017 if(key.keysym.sym == SDLK_f) {
1018 toggle_facing();
1019 }
1020
1021 if(key.keysym.sym == SDLK_r &&
1022 (key.keysym.mod&KMOD_CTRL)) {
1023 lvl_->rebuild_tiles();
1024 }
1025
1026 if(key.keysym.sym == SDLK_c) {
1027 foreach(const entity_ptr& obj, lvl_->get_chars()) {
1028 if(entity_collides_with_level(*lvl_, *obj, MOVE_NONE)) {
1029 xpos_ = obj->x() - graphics::screen_width()/2;
1030 ypos_ = obj->y() - graphics::screen_height()/2;
1031 break;
1032 }
1033 }
1034 }
1035 }
1036
1037 void editor::handle_scrolling()
1038 {
1039 const int ScrollSpeed = 24*zoom_;
1040 const int FastScrollSpeed = 48*zoom_;
1041
1042 if(key_[SDLK_LEFT]) {
1043 xpos_ -= ScrollSpeed;
1044 if(key_[SDLK_KP0]) {
1045 xpos_ -= FastScrollSpeed;
1046 }
1047 }
1048
1049 if(key_[SDLK_RIGHT]) {
1050 xpos_ += ScrollSpeed;
1051 if(key_[SDLK_KP0]) {
1052 xpos_ += FastScrollSpeed;
1053 }
1054 }
1055
1056 if(key_[SDLK_UP]) {
1057 ypos_ -= ScrollSpeed;
1058 if(key_[SDLK_KP0]) {
1059 ypos_ -= FastScrollSpeed;
1060 }
1061 }
1062
1063 if(key_[SDLK_DOWN]) {
1064 ypos_ += ScrollSpeed;
1065 if(key_[SDLK_KP0]) {
1066 ypos_ += FastScrollSpeed;
1067 }
1068 }
1069 }
1070
1071 void editor::handle_object_dragging(int mousex, int mousey)
1072 {
1073 const bool ctrl_pressed = (SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL)) != 0;
1074 const int dx = xpos_ + mousex*zoom_ - anchorx_;
1075 const int dy = ypos_ + mousey*zoom_ - anchory_;
1076 const int xpos = selected_entity_startx_ + dx;
1077 const int ypos = selected_entity_starty_ + dy;
1078
1079 const int new_x = xpos - (ctrl_pressed ? 0 : (xpos%TileSize));
1080 const int new_y = ypos - (ctrl_pressed ? 0 : (ypos%TileSize));
1081
1082 const int delta_x = new_x - lvl_->editor_highlight()->x();
1083 const int delta_y = new_y - lvl_->editor_highlight()->y();
1084
1085 //don't move the object from its starting position until the
1086 //delta in movement is large enough.
1087 const bool in_starting_position =
1088 lvl_->editor_highlight()->x() == selected_entity_startx_ &&
1089 lvl_->editor_highlight()->y() == selected_entity_starty_;
1090 const bool too_small_to_move = in_starting_position &&
1091 abs(dx) < 5 && abs(dy) < 5;
1092
1093 if(!too_small_to_move && (new_x != lvl_->editor_highlight()->x() || new_y != lvl_->editor_highlight()->y())) {
1094 std::vector<boost::function<void()> > redo, undo;
1095
1096 foreach(const entity_ptr& e, lvl_->editor_selection()) {
1097 redo.push_back(boost::bind(&editor::move_object, this, e, e->x() + delta_x, e->y() + delta_y));
1098 undo.push_back(boost::bind(&editor::move_object, this, e, e->x(), e->y()));
1099
1100 }
1101
1102 //all dragging that is done should be treated as one operation
1103 //from an undo/redo perspective. So, we see if we're already dragging
1104 //and have performed existing drag operations, and if so we
1105 //roll the previous undo command into this.
1106 boost::function<void()> undo_fn = boost::bind(execute_functions, undo);
1107
1108 if(g_started_dragging_object && undo_.empty() == false && undo_.back().type == COMMAND_TYPE_DRAG_OBJECT) {
1109 undo_fn = undo_.back().undo_command;
1110 undo_command();
1111 }
1112
1113 execute_command(boost::bind(execute_functions, redo), undo_fn, COMMAND_TYPE_DRAG_OBJECT);
1114
1115 g_started_dragging_object = true;
1116
1117 remove_ghost_objects();
1118 ghost_objects_.clear();
1119 }
1120 }
1121
1122 void editor::handle_drawing_rect(int mousex, int mousey)
1123 {
1124 const unsigned int buttons = get_mouse_state(mousex, mousey);
1125
1126 const int xpos = xpos_ + mousex*zoom_;
1127 const int ypos = ypos_ + mousey*zoom_;
1128
1129 int x1 = xpos;
1130 int x2 = anchorx_;
1131 int y1 = ypos;
1132 int y2 = anchory_;
1133 if(x1 > x2) {
1134 std::swap(x1, x2);
1135 }
1136
1137 if(y1 > y2) {
1138 std::swap(y1, y2);
1139 }
1140
1141 x1 = round_tile_size(x1);
1142 x2 = round_tile_size(x2 + TileSize);
1143 y1 = round_tile_size(y1);
1144 y2 = round_tile_size(y2 + TileSize);
1145
1146 const rect new_rect = rect(x1, y1, x2 - x1, y2 - y1);
1147 if(g_rect_drawing == new_rect) {
1148 return;
1149 }
1150
1151 if(tool() == TOOL_ADD_RECT) {
1152 lvl_->freeze_rebuild_tiles_in_background();
1153 if(tmp_undo_.get()) {
1154 tmp_undo_->undo_command();
1155 }
1156
1157 if(buttons == SDL_BUTTON_LEFT) {
1158 add_tile_rect(anchorx_, anchory_, xpos, ypos);
1159 } else {
1160 remove_tile_rect(anchorx_, anchory_, xpos, ypos);
1161 }
1162
1163 tmp_undo_.reset(new executable_command(undo_.back()));
1164 undo_.pop_back();
1165 lvl_->unfreeze_rebuild_tiles_in_background();
1166 }
1167 g_rect_drawing = new_rect;
1168 }
1169
1170 void editor::handle_mouse_button_down(const SDL_MouseButtonEvent& event)
1171 {
1172 const bool ctrl_pressed = (SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL)) != 0;
1173 const bool shift_pressed = (SDL_GetModState()&(KMOD_LSHIFT|KMOD_RSHIFT)) != 0;
1174 const bool alt_pressed = (SDL_GetModState()&KMOD_ALT) != 0;
1175 int mousex, mousey;
1176 const unsigned int buttons = get_mouse_state(mousex, mousey);
1177
1178 anchorx_ = xpos_ + mousex*zoom_;
1179 anchory_ = ypos_ + mousey*zoom_;
1180 if(event.button == SDL_BUTTON_MIDDLE && !alt_pressed) {
1181 return;
1182 }
1183
1184 resizing_left_level_edge = rect_left_edge_selected(lvl_->boundaries(), anchorx_, anchory_, zoom_);
1185 resizing_right_level_edge = rect_right_edge_selected(lvl_->boundaries(), anchorx_, anchory_, zoom_);
1186 resizing_top_level_edge = rect_top_edge_selected(lvl_->boundaries(), anchorx_, anchory_, zoom_);
1187 resizing_bottom_level_edge = rect_bottom_edge_selected(lvl_->boundaries(), anchorx_, anchory_, zoom_);
1188
1189 if(resizing_left_level_edge || resizing_right_level_edge || resizing_top_level_edge || resizing_bottom_level_edge) {
1190 return;
1191 }
1192
1193 dragging_ = drawing_rect_ = false;
1194
1195 if(tool() == TOOL_PICKER) {
1196 if(lvl_->editor_highlight()) {
1197 change_tool(TOOL_ADD_OBJECT);
1198
1199 wml::const_node_ptr node = lvl_->editor_highlight()->write();
1200 const std::string type = node->attr("type");
1201 for(int n = 0; n != all_characters().size(); ++n) {
1202 const enemy_type& c = all_characters()[n];
1203 if(c.node->attr("type").str() == type) {
1204 character_dialog_->select_category(c.category);
1205 character_dialog_->set_character(n);
1206 return;
1207 }
1208 }
1209 return;
1210 } else {
1211 //pick the top most tile at this point.
1212 std::map<int, std::vector<std::string> > tiles;
1213 lvl_->get_all_tiles_rect(anchorx_, anchory_, anchorx_, anchory_, tiles);
1214 std::string tile;
1215 for(std::map<int, std::vector<std::string> >::reverse_iterator i = tiles.rbegin(); i != tiles.rend(); ++i) {
1216 if(i->second.empty() == false) {
1217 tile = i->second.back();
1218 std::cerr << "picking tile: '" << tile << "'\n";
1219 break;
1220 }
1221 }
1222
1223 if(!tile.empty()) {
1224 for(int n = 0; n != all_tilesets().size(); ++n) {
1225 if(all_tilesets()[n].type == tile) {
1226 tileset_dialog_->select_category(all_tilesets()[n].category);
1227 tileset_dialog_->set_tileset(n);
1228 std::cerr << "pick tile " << n << "\n";
1229 //if we're in adding objects mode then switch to adding tiles mode.
1230 if(tool_ == TOOL_ADD_OBJECT) {
1231 change_tool(TOOL_ADD_RECT);
1232 }
1233 return;
1234 }
1235 }
1236 }
1237 }
1238 } else if(editing_tiles() && !tile_selection_.empty() &&
1239 std::binary_search(tile_selection_.tiles.begin(), tile_selection_.tiles.end(), point(round_tile_size(anchorx_)/TileSize, round_tile_size(anchory_)/TileSize))) {
1240 //we are beginning to drag our selection
1241 dragging_ = true;
1242 } else if(tool() == TOOL_ADD_RECT || tool() == TOOL_SELECT_RECT) {
1243 tmp_undo_.reset();
1244 drawing_rect_ = true;
1245 g_rect_drawing = rect();
1246 } else if(tool() == TOOL_MAGIC_WAND) {
1247 drawing_rect_ = false;
1248 } else if(tool() == TOOL_PENCIL) {
1249 drawing_rect_ = false;
1250 point p(anchorx_, anchory_);
1251 if(buttons&SDL_BUTTON_LEFT) {
1252 add_tile_rect(p.x, p.y, p.x, p.y);
1253 } else {
1254 remove_tile_rect(p.x, p.y, p.x, p.y);
1255 }
1256 g_current_draw_tiles.clear();
1257 g_current_draw_tiles.push_back(p);
1258 } else if(property_dialog_ && variable_info_selected(property_dialog_->get_entity(), anchorx_, anchory_, zoom_)) {
1259 g_variable_editing = variable_info_selected(property_dialog_->get_entity(), anchorx_, anchory_, zoom_);
1260 g_variable_editing_original_value = property_dialog_->get_entity()->query_value(g_variable_editing->variable_name()).as_int();
1261
1262 } else if(tool() == TOOL_SELECT_OBJECT && !lvl_->editor_highlight()) {
1263 //dragging a rectangle to select objects
1264 drawing_rect_ = true;
1265 } else if(property_dialog_) {
1266 property_dialog_->set_entity(lvl_->editor_highlight());
1267 }
1268
1269 if(lvl_->editor_highlight()) {
1270 if(std::count(lvl_->editor_selection().begin(),
1271 lvl_->editor_selection().end(), lvl_->editor_highlight()) == 0) {
1272 //set the object as selected in the editor.
1273 if(!shift_pressed) {
1274 lvl_->editor_clear_selection();
1275 }
1276
1277 lvl_->editor_select_object(lvl_->editor_highlight());
1278 group_property_dialog_->set_group(lvl_->editor_selection());
1279
1280 if(!lvl_->editor_selection().empty() && tool() == TOOL_ADD_OBJECT) {
1281 //we are in add objects mode and we clicked on an object,
1282 //so change to select mode.
1283 change_tool(TOOL_SELECT_OBJECT);
1284 }
1285
1286 if(lvl_->editor_selection().size() > 1) {
1287 current_dialog_ = group_property_dialog_.get();
1288 } else if(lvl_->editor_selection().size() == 1) {
1289 current_dialog_ = property_dialog_.get();
1290 }
1291 }
1292
1293 //start dragging the object
1294 selected_entity_startx_ = lvl_->editor_highlight()->x();
1295 selected_entity_starty_ = lvl_->editor_highlight()->y();
1296
1297 g_started_dragging_object = false;
1298
1299 } else {
1300 //clear any selection in the editor
1301 lvl_->editor_clear_selection();
1302 }
1303
1304 if(tool() == TOOL_ADD_OBJECT && event.button == SDL_BUTTON_LEFT && !lvl_->editor_highlight()) {
1305 wml::node_ptr node(wml::deep_copy(enemy_types[cur_object_].node));
1306 node->set_attr("x", formatter() << (ctrl_pressed ? anchorx_ : round_tile_size(anchorx_)));
1307 node->set_attr("y", formatter() << (ctrl_pressed ? anchory_ : round_tile_size(anchory_)));
1308 node->set_attr("face_right", face_right_ ? "yes" : "no");
1309
1310 entity_ptr c(entity::build(node));
1311
1312 //any vars that require formula initialization are calculated here.
1313 std::map<std::string, variant> vars;
1314 foreach(const editor_variable_info& info, c->editor_info()->vars()) {
1315 if(info.formula()) {
1316 vars[info.variable_name()] = info.formula()->execute(*c);
1317 }
1318 }
1319
1320 //we only want to actually set the vars once we've calculated all of
1321 //them, to avoid any ordering issues etc. So set them all here.
1322 for(std::map<std::string, variant>::const_iterator i = vars.begin();
1323 i != vars.end(); ++i) {
1324 game_logic::formula_callable* obj_vars = c->query_value("vars").mutable_callable();
1325 obj_vars->mutate_value(i->first, i->second);
1326 }
1327
1328 if(!place_entity_in_level(*lvl_, *c)) {
1329 //could not place entity. Not really an error; the user just
1330 //clicked in an illegal position to place an object.
1331
1332 } else if(c->is_human() && lvl_->player()) {
1333 execute_command(
1334 boost::bind(&editor::add_object_to_level, this, c),
1335 boost::bind(&editor::add_object_to_level, this, &lvl_->player()->get_entity()));
1336
1337 } else {
1338 execute_command(
1339 boost::bind(&editor::add_object_to_level, this, c),
1340 boost::bind(&editor::remove_object_from_level, this, c));
1341 }
1342 }
1343 }
1344
1345 void editor::handle_mouse_button_up(const SDL_MouseButtonEvent& event)
1346 {
1347 const bool ctrl_pressed = (SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL)) != 0;
1348 const bool shift_pressed = (SDL_GetModState()&(KMOD_LSHIFT|KMOD_RSHIFT)) != 0;
1349 int mousex, mousey;
1350 const unsigned int buttons = get_mouse_state(mousex, mousey);
1351
1352 const int xpos = xpos_ + mousex*zoom_;
1353 const int ypos = ypos_ + mousey*zoom_;
1354
1355 if(g_variable_editing) {
1356 if(property_dialog_ && property_dialog_->get_entity()) {
1357 entity_ptr e = property_dialog_->get_entity();
1358 const std::string& var = g_variable_editing->variable_name();
1359 execute_command(
1360 boost::bind(&editor::mutate_object_value, this, e.get(), var, e->query_value(var)),
1361 boost::bind(&editor::mutate_object_value, this, e.get(), var, variant(g_variable_editing_original_value)));
1362 property_dialog_->init();
1363 }
1364 g_variable_editing = NULL;
1365 return;
1366 }
1367
1368 if(resizing_left_level_edge || resizing_right_level_edge ||resizing_top_level_edge || resizing_bottom_level_edge) {
1369 rect boundaries = modify_selected_rect(lvl_->boundaries(), xpos, ypos);
1370
1371 resizing_left_level_edge = resizing_right_level_edge = resizing_top_level_edge = resizing_bottom_level_edge = false;
1372
1373 if(boundaries != lvl_->boundaries()) {
1374 execute_command(
1375 boost::bind(&level::set_boundaries, lvl_.get(), boundaries),
1376 boost::bind(&level::set_boundaries, lvl_.get(), lvl_->boundaries()));
1377 }
1378 return;
1379 }
1380
1381
1382 if(editing_tiles()) {
1383 if(dragging_) {
1384 const int selectx = xpos_ + mousex*zoom_;
1385 const int selecty = ypos_ + mousey*zoom_;
1386
1387 //dragging selection
1388 int diffx = (selectx - anchorx_)/TileSize;
1389 int diffy = (selecty - anchory_)/TileSize;
1390
1391 std::cerr << "MAKE DIFF: " << diffx << "," << diffy << "\n";
1392 std::vector<boost::function<void()> > redo, undo;
1393
1394 foreach(const point& p, tile_selection_.tiles) {
1395 const int x = (p.x+diffx)*TileSize;
1396 const int y = (p.y+diffy)*TileSize;
1397 undo.push_back(boost::bind(&level::clear_tile_rect,lvl_.get(), x, y, x, y));
1398 }
1399
1400 int min_x = INT_MAX, min_y = INT_MAX, max_x = INT_MIN, max_y = INT_MIN;
1401
1402 //backup both the contents of the old and new regions, so we can restore them both
1403 foreach(const point& p, tile_selection_.tiles) {
1404 int x = p.x*TileSize;
1405 int y = p.y*TileSize;
1406
1407 min_x = std::min(x, min_x);
1408 max_x = std::max(x, max_x);
1409 min_y = std::min(y, min_y);
1410 max_y = std::max(y, max_y);
1411
1412 std::map<int, std::vector<std::string> > old_tiles;
1413 lvl_->get_all_tiles_rect(x, y, x, y, old_tiles);
1414 for(std::map<int, std::vector<std::string> >::const_iterator i = old_tiles.begin(); i != old_tiles.end(); ++i) {
1415 undo.push_back(boost::bind(&level::add_tile_rect_vector, lvl_.get(), i->first, x, y, x, y, i->second));
1416 redo.push_back(boost::bind(&level::add_tile_rect_vector, lvl_.get(), i->first, x, y, x, y, std::vector<std::string>(1,"")));
1417 }
1418
1419 old_tiles.clear();
1420
1421 x += diffx*TileSize;
1422 y += diffy*TileSize;
1423
1424 min_x = std::min(x, min_x);
1425 max_x = std::max(x, max_x);
1426 min_y = std::min(y, min_y);
1427 max_y = std::max(y, max_y);
1428
1429 lvl_->get_all_tiles_rect(x, y, x, y, old_tiles);
1430 for(std::map<int, std::vector<std::string> >::const_iterator i = old_tiles.begin(); i != old_tiles.end(); ++i) {
1431 undo.push_back(boost::bind(&level::add_tile_rect_vector, lvl_.get(), i->first, x, y, x, y, i->second));
1432 redo.push_back(boost::bind(&level::add_tile_rect_vector, lvl_.get(), i->first, x, y, x, y, std::vector<std::string>(1,"")));
1433 }
1434 }
1435
1436
1437 foreach(const point& p, tile_selection_.tiles) {
1438 const int x = p.x*TileSize;
1439 const int y = p.y*TileSize;
1440
1441 min_x = std::min(x + diffx*TileSize, min_x);
1442 max_x = std::max(x + diffx*TileSize, max_x);
1443 min_y = std::min(y + diffy*TileSize, min_y);
1444 max_y = std::max(y + diffy*TileSize, max_y);
1445
1446 std::map<int, std::vector<std::string> > old_tiles;
1447 lvl_->get_all_tiles_rect(x, y, x, y, old_tiles);
1448 for(std::map<int, std::vector<std::string> >::const_iterator i = old_tiles.begin(); i != old_tiles.end(); ++i) {
1449 redo.push_back(boost::bind(&level::add_tile_rect_vector, lvl_.get(), i->first, x + diffx*TileSize, y + diffy*TileSize, x + diffx*TileSize, y + diffy*TileSize, i->second));
1450 }
1451 }
1452
1453 if(!tile_selection_.tiles.empty()) {
1454 undo.push_back(boost::bind(&level::start_rebuild_tiles_in_background, lvl_.get(), std::vector<int>()));
1455 redo.push_back(boost::bind(&level::start_rebuild_tiles_in_background, lvl_.get(), std::vector<int>()));
1456 }
1457
1458 tile_selection new_selection = tile_selection_;
1459 foreach(point& p, new_selection.tiles) {
1460 p.x += diffx;
1461 p.y += diffy;
1462 }
1463
1464 redo.push_back(boost::bind(&editor::set_selection, this, new_selection));
1465 undo.push_back(boost::bind(&editor::set_selection, this, tile_selection_));
1466
1467 execute_command(
1468 boost::bind(execute_functions, redo),
1469 boost::bind(execute_functions, undo));
1470
1471 } else if(!drawing_rect_) {
1472 //wasn't drawing a rect.
1473 if(event.button == SDL_BUTTON_LEFT && tool() == TOOL_MAGIC_WAND) {
1474 select_magic_wand(anchorx_, anchory_);
1475 }
1476 } else if(event.button == SDL_BUTTON_LEFT) {
1477
1478 if(tool() == TOOL_ADD_RECT) {
1479
1480 lvl_->freeze_rebuild_tiles_in_background();
1481 if(tmp_undo_.get()) {
1482 //if we have a temporary change that was made while dragging
1483 //to preview the change, undo that now.
1484 tmp_undo_->undo_command();
1485 tmp_undo_.reset();
1486 }
1487
1488 add_tile_rect(anchorx_, anchory_, xpos, ypos);
1489 lvl_->unfreeze_rebuild_tiles_in_background();
1490 } else if(tool() == TOOL_SELECT_RECT) {
1491 select_tile_rect(anchorx_, anchory_, xpos, ypos);
1492 }
1493
1494 } else if(event.button == SDL_BUTTON_RIGHT) {
1495 lvl_->freeze_rebuild_tiles_in_background();
1496 if(tmp_undo_.get()) {
1497 //if we have a temporary change that was made while dragging
1498 //to preview the change, undo that now.
1499 tmp_undo_->undo_command();
1500 tmp_undo_.reset();
1501 }
1502 remove_tile_rect(anchorx_, anchory_, xpos, ypos);
1503 lvl_->unfreeze_rebuild_tiles_in_background();
1504 }
1505 } else {
1506 //some kind of object editing
1507 if(event.button == SDL_BUTTON_RIGHT) {
1508 std::vector<boost::function<void()> > undo, redo;
1509 std::vector<entity_ptr> chars = lvl_->get_characters_in_rect(rect::from_coordinates(anchorx_, anchory_, xpos, ypos));
1510
1511 foreach(const entity_ptr& c, chars) {
1512 redo.push_back(boost::bind(&editor::remove_object_from_level, this, c));
1513 undo.push_back(boost::bind(&editor::add_object_to_level, this, c));
1514 }
1515 execute_command(
1516 boost::bind(execute_functions, redo),
1517 boost::bind(execute_functions, undo));
1518 } else if(tool() == TOOL_SELECT_OBJECT && drawing_rect_) {
1519 std::vector<entity_ptr> chars = lvl_->get_characters_in_rect(rect::from_coordinates(anchorx_, anchory_, xpos, ypos));
1520 foreach(const entity_ptr& c, chars) {
1521 lvl_->editor_select_object(c);
1522 }
1523
1524 group_property_dialog_->set_group(lvl_->editor_selection());
1525
1526 if(lvl_->editor_selection().size() == 1) {
1527 current_dialog_ = property_dialog_.get();
1528 property_dialog_->set_entity(lvl_->editor_selection().front());
1529 } else {
1530 current_dialog_ = group_property_dialog_.get();
1531 }
1532
1533 if(lvl_->editor_selection().empty()) {
1534 //if we choose an empty selection we revert to add
1535 //objects mode.
1536 change_tool(TOOL_ADD_OBJECT);
1537 }
1538 }
1539 }
1540
1541 drawing_rect_ = dragging_ = false;
1542 }
1543
1544
1545 void editor::load_stats()
1546 {
1547 stats_.clear();
1548
1549 const std::string fname = formatter() << preferences::user_data_path() << "stats/" << lvl_->id();
1550 if(!sys::file_exists(fname)) {
1551 return;
1552 }
1553
1554 std::string doc = "[stats]\n" + sys::read_file(fname) + "[/stats]\n";
1555 wml::const_node_ptr node(wml::parse_wml(doc));
1556 for(wml::node::const_all_child_iterator i = node->begin_children(); i != node->end_children(); ++i) {
1557 stats_.push_back(stats::record::read(*i));
1558 if(!stats_.back()) {
1559 stats_.pop_back();
1560 }
1561 }
1562 }
1563
1564 void editor::show_stats()
1565 {
1566 editor_dialogs::editor_stats_dialog stats_dialog(*this);
1567 stats_dialog.show_modal();
1568 }
1569
1570 void editor::download_stats()
1571 {
1572 const bool result = stats::download(lvl_->id());
1573 if(result) {
1574 debug_console::add_message("Got latest stats from the server");
1575 try {
1576 load_stats();
1577 } catch(...) {
1578 debug_console::add_message("Error parsing stats");
1579 std::cerr << "ERROR LOADING STATS\n";
1580 }
1581 } else {
1582 debug_console::add_message("Download of stats failed");
1583 }
1584 }
1585
1586 int editor::get_tile_zorder(const std::string& tile_id) const
1587 {
1588 foreach(const editor::tileset& tile, tilesets) {
1589 if(tile.type == tile_id) {
1590 return tile.zorder;
1591 }
1592 }
1593
1594 return 0;
1595 }
1596
1597 void editor::add_tile_rect(int zorder, const std::string& tile_id, int x1, int y1, int x2, int y2)
1598 {
1599 if(x2 < x1) {
1600 std::swap(x1, x2);
1601 }
1602
1603 if(y2 < y1) {
1604 std::swap(y1, y2);
1605 }
1606
1607 std::vector<std::string> old_rect;
1608 lvl_->get_tile_rect(zorder, x1, y1, x2, y2, old_rect);
1609
1610 std::vector<boost::function<void()> > undo, redo;
1611
1612 redo.push_back(boost::bind(&level::add_tile_rect, lvl_.get(), zorder, x1, y1, x2, y2, tile_id));
1613 undo.push_back(boost::bind(&level::add_tile_rect_vector, lvl_.get(), zorder, x1, y1, x2, y2, old_rect));
1614
1615 std::vector<int> layers;
1616 layers.push_back(zorder);
1617 undo.push_back(boost::bind(&level::start_rebuild_tiles_in_background, lvl_.get(), layers));
1618 redo.push_back(boost::bind(&level::start_rebuild_tiles_in_background, lvl_.get(), layers));
1619
1620 execute_command(
1621 boost::bind(execute_functions, redo),
1622 boost::bind(execute_functions, undo));
1623
1624 if(layers_dialog_) {
1625 layers_dialog_->init();
1626 }
1627 }
1628
1629 void editor::add_tile_rect(int x1, int y1, int x2, int y2)
1630 {
1631 x1 += ((100 - tilesets[cur_tileset_].x_speed)*xpos_)/100;
1632 x2 += ((100 - tilesets[cur_tileset_].x_speed)*xpos_)/100;
1633 y1 += ((100 - tilesets[cur_tileset_].y_speed)*ypos_)/100;
1634 y2 += ((100 - tilesets[cur_tileset_].y_speed)*ypos_)/100;
1635
1636 add_tile_rect(tilesets[cur_tileset_].zorder, tilesets[cur_tileset_].type, x1, y1, x2, y2);
1637 lvl_->set_tile_layer_speed(tilesets[cur_tileset_].zorder,
1638 tilesets[cur_tileset_].x_speed,
1639 tilesets[cur_tileset_].y_speed);
1640 }
1641
1642 void editor::remove_tile_rect(int x1, int y1, int x2, int y2)
1643 {
1644 if(x2 < x1) {
1645 std::swap(x1, x2);
1646 }
1647
1648 if(y2 < y1) {
1649 std::swap(y1, y2);
1650 }
1651
1652 std::map<int, std::vector<std::string> > old_tiles;
1653 lvl_->get_all_tiles_rect(x1, y1, x2, y2, old_tiles);
1654 std::vector<boost::function<void()> > redo, undo;
1655 for(std::map<int, std::vector<std::string> >::const_iterator i = old_tiles.begin(); i != old_tiles.end(); ++i) {
1656 undo.push_back(boost::bind(&level::add_tile_rect_vector, lvl_.get(), i->first, x1, y1, x2, y2, i->second));
1657 }
1658
1659 redo.push_back(boost::bind(&level::clear_tile_rect, lvl_.get(), x1, y1, x2, y2));
1660 undo.push_back(boost::bind(&level::start_rebuild_tiles_in_background, lvl_.get(), std::vector<int>()));
1661 redo.push_back(boost::bind(&level::start_rebuild_tiles_in_background, lvl_.get(), std::vector<int>()));
1662
1663 execute_command(
1664 boost::bind(execute_functions, redo),
1665 boost::bind(execute_functions, undo));
1666 }
1667
1668 void editor::select_tile_rect(int x1, int y1, int x2, int y2)
1669 {
1670 tile_selection new_selection;
1671
1672 const bool shift_pressed = (SDL_GetModState()&KMOD_SHIFT) != 0;
1673 if(shift_pressed) {
1674 //adding to the selection
1675 new_selection = tile_selection_;
1676 }
1677
1678 if(x2 < x1) {
1679 std::swap(x1, x2);
1680 }
1681
1682 if(y2 < y1) {
1683 std::swap(y1, y2);
1684 }
1685
1686 if(x2 - x1 > TileSize/4 || y2 - y1 > TileSize/4) {
1687 x2 += TileSize;
1688 y2 += TileSize;
1689
1690 x1 = round_tile_size(x1)/TileSize;
1691 y1 = round_tile_size(y1)/TileSize;
1692 x2 = round_tile_size(x2)/TileSize;
1693 y2 = round_tile_size(y2)/TileSize;
1694
1695 for(int x = x1; x != x2; ++x) {
1696 for(int y = y1; y != y2; ++y) {
1697 const point p(x, y);
1698 new_selection.tiles.push_back(p);
1699 }
1700 }
1701
1702 std::sort(new_selection.tiles.begin(), new_selection.tiles.end());
1703
1704 const bool alt_pressed = (SDL_GetModState()&(KMOD_LALT|KMOD_RALT)) != 0;
1705 if(alt_pressed) {
1706 //diff from selection
1707 tile_selection diff;
1708 foreach(const point& p, tile_selection_.tiles) {
1709 if(std::binary_search(new_selection.tiles.begin(), new_selection.tiles.end(), p) == false) {
1710 diff.tiles.push_back(p);
1711 }
1712 }
1713
1714 new_selection.tiles.swap(diff.tiles);
1715 }
1716 }
1717
1718 execute_command(
1719 boost::bind(&editor::set_selection, this, new_selection),
1720 boost::bind(&editor::set_selection, this, tile_selection_));
1721 }
1722
1723 void editor::select_magic_wand(int xpos, int ypos)
1724 {
1725 tile_selection new_selection;
1726
1727 const bool ctrl_pressed = (SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL)) != 0;
1728 if(ctrl_pressed) {
1729 //adding to the selection
1730 new_selection = tile_selection_;
1731 }
1732
1733 std::vector<point> tiles = lvl_->get_solid_contiguous_region(xpos, ypos);
1734 new_selection.tiles.insert(new_selection.tiles.end(), tiles.begin(), tiles.end());
1735 execute_command(
1736 boost::bind(&editor::set_selection, this, new_selection),
1737 boost::bind(&editor::set_selection, this, tile_selection_));
1738 }
1739
1740 void editor::set_selection(const tile_selection& s)
1741 {
1742 tile_selection_ = s;
1743 }
1744
1745 void editor::move_object(entity_ptr e, int new_x, int new_y)
1746 {
1747 const int orig_x = e->x();
1748 const int orig_y = e->y();
1749
1750 e->set_pos(new_x, new_y);
1751
1752 if(!place_entity_in_level(*lvl_, *e)) {
1753 //if we can't place the object due to solidity, then cancel
1754 //the movement.
1755 e->set_pos(orig_x, orig_y);
1756 return;
1757 }
1758
1759 const int delta_x = e->x() - orig_x;
1760 const int delta_y = e->y() - orig_y;
1761
1762 //update any x/y co-ordinates to be the same relative to the object's
1763 //new position.
1764 if(e->editor_info()) {
1765 foreach(const editor_variable_info& var, e->editor_info()->vars()) {
1766 const variant value = e->query_value(var.variable_name());
1767 switch(var.type()) {
1768 case editor_variable_info::XPOSITION:
1769 if(value.is_int()) {
1770 mutate_object_value(e, var.variable_name(), variant(value.as_int() + delta_x));
1771 }
1772 break;
1773 case editor_variable_info::YPOSITION:
1774 if(value.is_int()) {
1775 mutate_object_value(e, var.variable_name(), variant(value.as_int() + delta_y));
1776 }
1777 break;
1778 default:
1779 break;
1780 }
1781 }
1782 }
1783 }
1784
1785 const std::vector<editor::tileset>& editor::all_tilesets() const
1786 {
1787 return tilesets;
1788 }
1789
1790 const std::vector<editor::enemy_type>& editor::all_characters() const
1791 {
1792 return enemy_types;
1793 }
1794
1795 void editor::set_tileset(int index)
1796 {
1797 cur_tileset_ = index;
1798 if(cur_tileset_ < 0) {
1799 cur_tileset_ = tilesets.size()-1;
1800 } else if(cur_tileset_ >= tilesets.size()) {
1801 cur_tileset_ = 0;
1802 }
1803
1804 lvl_->set_tile_layer_speed(tilesets[cur_tileset_].zorder,
1805 tilesets[cur_tileset_].x_speed,
1806 tilesets[cur_tileset_].y_speed);
1807 }
1808
1809 void editor::set_object(int index)
1810 {
1811 int max = enemy_types.size();
1812
1813 if(index < 0) {
1814 index = max - 1;
1815 } else if(index >= max) {
1816 index = 0;
1817 }
1818
1819 cur_object_ = index;
1820 }
1821
1822 editor::EDIT_TOOL editor::tool() const
1823 {
1824 const bool alt_pressed = (SDL_GetModState()&KMOD_ALT) != 0;
1825 if(alt_pressed) {
1826 switch(tool_) {
1827 case TOOL_ADD_OBJECT:
1828 case TOOL_ADD_RECT:
1829 case TOOL_SELECT_RECT:
1830 case TOOL_MAGIC_WAND:
1831 case TOOL_PENCIL:
1832 case TOOL_PICKER:
1833 return TOOL_PICKER;
1834 default:
1835 break;
1836 }
1837 }
1838
1839 return tool_;
1840 }
1841
1842 void editor::change_tool(EDIT_TOOL tool)
1843 {
1844 tool_ = tool;
1845
1846 std::cerr << "CHANGE TOOL: " << (int)tool << "\n";
1847
1848 switch(tool_) {
1849 case TOOL_ADD_RECT:
1850 case TOOL_SELECT_RECT:
1851 case TOOL_MAGIC_WAND:
1852 case TOOL_PENCIL:
1853 case TOOL_PICKER: {
1854 if(!tileset_dialog_) {
1855 tileset_dialog_.reset(new editor_dialogs::tileset_editor_dialog(*this));
1856 }
1857 current_dialog_ = tileset_dialog_.get();
1858 lvl_->editor_clear_selection();
1859 break;
1860 }
1861 case TOOL_ADD_OBJECT: {
1862 if(!character_dialog_) {
1863 character_dialog_.reset(new editor_dialogs::character_editor_dialog(*this));
1864 }
1865 current_dialog_ = character_dialog_.get();
1866 character_dialog_->set_character(cur_object_);
1867 break;
1868 }
1869 case TOOL_SELECT_OBJECT: {
1870 current_dialog_ = property_dialog_.get();
1871 break;
1872 }
1873 }
1874
1875 if(editor_mode_dialog_) {
1876 editor_mode_dialog_->init();
1877 }
1878 }
1879
1880 void editor::save_level_as(const std::string& fname)
1881 {
1882 all_editors.erase(filename_);
1883 all_editors[fname] = this;
1884 filename_ = fname;
1885 save_level();
1886 g_last_edited_level() = filename_;
1887 }
1888
1889 void editor::quit()
1890 {
1891 if(confirm_quit()) {
1892 done_ = true;
1893 }
1894 }
1895
1896 namespace {
1897 void quit_editor_result(gui::dialog* d, int* result_ptr, int result) {
1898 d->close();
1899 *result_ptr = result;
1900 }
1901 }
1902
1903 bool editor::confirm_quit()
1904 {
1905 if(!level_changed_) {
1906 return true;
1907 }
1908
1909 const int center_x = graphics::screen_width()/2;
1910 const int center_y = graphics::screen_height()/2;
1911 using namespace gui;
1912 dialog d(center_x - 140, center_y - 100, center_x + 140, center_y + 100);
1913
1914 d.add_widget(widget_ptr(new label("Do you want to save the level?", graphics::color_white())), dialog::MOVE_DOWN);
1915
1916 gui::grid* grid = new gui::grid(3);
1917
1918 int result = 0;
1919 grid->add_col(widget_ptr(
1920 new button(widget_ptr(new label("Yes", graphics::color_white())),
1921 boost::bind(quit_editor_result, &d, &result, 0))));
1922 grid->add_col(widget_ptr(
1923 new button(widget_ptr(new label("No", graphics::color_white())),
1924 boost::bind(quit_editor_result, &d, &result, 1))));
1925 grid->add_col(widget_ptr(
1926 new button(widget_ptr(new label("Cancel", graphics::color_white())),
1927 boost::bind(quit_editor_result, &d, &result, 2))));
1928 d.add_widget(widget_ptr(grid));
1929 d.show_modal();
1930
1931 if(result == 2) {
1932 return false;
1933 }
1934
1935 if(result == 0 && !d.cancelled()) {
1936 save_level();
1937 }
1938
1939 return true;
1940 }
1941
1942 void editor::save_level()
1943 {
1944 level_changed_ = 0;
1945
1946 remove_ghost_objects();
1947 ghost_objects_.clear();
1948
1949 const std::string path = preferences::level_path();
1950 std::string data;
1951 wml::node_ptr lvl_node = lvl_->write();
1952 lvl_node->erase_attr("cycle"); //levels saved in the editor should never
1953 //have a cycle attached to them so that
1954 //all levels start at cycle 0.
1955 wml::write(lvl_node, data);
1956 sys::write_file(path + filename_, data);
1957
1958 //see if we should write the next/previous levels also
1959 //based on them having changed.
1960 if(lvl_->previous_level().empty() == false) {
1961 try {
1962 level prev(lvl_->previous_level());
1963 prev.finish_loading();
1964 if(prev.next_level() != lvl_->id()) {
1965 prev.set_next_level(lvl_->id());
1966 std::string data;
1967 wml::write(prev.write(), data);
1968 sys::write_file(path + prev.id(), data);
1969 }
1970 } catch(...) {
1971 }
1972 }
1973
1974 if(lvl_->next_level().empty() == false) {
1975 try {
1976 level next(lvl_->next_level());
1977 next.finish_loading();
1978 if(next.previous_level() != lvl_->id()) {
1979 next.set_previous_level(lvl_->id());
1980 std::string data;
1981 wml::write(next.write(), data);
1982 sys::write_file(path + next.id(), data);
1983 }
1984 } catch(...) {
1985 }
1986 }
1987 }
1988
1989 void editor::zoom_in()
1990 {
1991 if(zoom_ > 1) {
1992 zoom_ /= 2;
1993 }
1994 }
1995
1996 void editor::zoom_out()
1997 {
1998 if(zoom_ < 8) {
1999 zoom_ *= 2;
2000 }
2001 }
2002
2003 void editor::draw() const
2004 {
2005 const bool ctrl_pressed = (SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL)) != 0;
2006
2007 int mousex, mousey;
2008 get_mouse_state(mousex, mousey);
2009
2010 graphics::prepare_raster();
2011 glClearColor(0.0, 0.0, 0.0, 0.0);
2012 glClear(GL_COLOR_BUFFER_BIT);
2013 glPushMatrix();
2014 glScalef(1.0/zoom_, 1.0/zoom_, 0);
2015 glTranslatef(-xpos_,-ypos_,0);
2016
2017 if(zoom_ == 1) {
2018 //backgrounds only draw nicely at the regular zoom level for now.
2019 lvl_->draw_background(xpos_, ypos_, 0);
2020 }
2021
2022 lvl_->draw(xpos_, ypos_, graphics::screen_width()*zoom_, graphics::screen_height()*zoom_);
2023
2024 const int selectx = xpos_ + mousex*zoom_;
2025 const int selecty = ypos_ + mousey*zoom_;
2026
2027 {
2028 std::string next_level = "To " + lvl_->next_level();
2029 std::string previous_level = "To " + lvl_->previous_level();
2030 if(lvl_->next_level().empty()) {
2031 next_level = "(no next level)";
2032 }
2033 if(lvl_->previous_level().empty()) {
2034 previous_level = "(no previous level)";
2035 }
2036 graphics::texture t = font::render_text(previous_level, graphics::color_black(), 24);
2037 int x = lvl_->boundaries().x() - t.width();
2038 int y = ypos_ + graphics::screen_height()/2;
2039
2040 graphics::blit_texture(t, x, y);
2041 t = font::render_text(next_level, graphics::color_black(), 24);
2042 x = lvl_->boundaries().x2();
2043 graphics::blit_texture(t, x, y);
2044 }
2045
2046 if(tool() == TOOL_ADD_OBJECT && !lvl_->editor_highlight()) {
2047 int x = round_tile_size(xpos_ + mousex*zoom_);
2048 int y = round_tile_size(ypos_ + mousey*zoom_);
2049 if(ctrl_pressed) {
2050 x = xpos_ + mousex*zoom_;
2051 y = ypos_ + mousey*zoom_;
2052 }
2053
2054 entity& e = *all_characters()[cur_object_].preview_object;
2055 e.set_pos(x, y);
2056 if(place_entity_in_level(*lvl_, e)) {
2057 glColor4f(1.0, 1.0, 1.0, 0.5);
2058 all_characters()[cur_object_].preview_frame->draw(e.x(), e.y(), face_right_);
2059 glColor4f(1.0, 1.0, 1.0, 1.0);
2060 }
2061 }
2062
2063 if(drawing_rect_) {
2064 int x1 = anchorx_;
2065 int x2 = xpos_ + mousex*zoom_;
2066 if(x1 > x2) {
2067 std::swap(x1,x2);
2068 }
2069
2070 int y1 = anchory_;
2071 int y2 = ypos_ + mousey*zoom_;
2072 if(y1 > y2) {
2073 std::swap(y1,y2);
2074 }
2075
2076 const SDL_Rect rect = {x1, y1, x2 - x1, y2 - y1};
2077 const SDL_Color color = {255,255,255,255};
2078 graphics::draw_hollow_rect(rect, color);
2079 }
2080
2081 std::vector<GLfloat>& varray = graphics::global_vertex_array();
2082 if(property_dialog_ && property_dialog_.get() == current_dialog_ && property_dialog_->get_entity() && property_dialog_->get_entity()->editor_info()) {
2083 glDisable(GL_TEXTURE_2D);
2084 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
2085
2086 const editor_variable_info* selected_var = variable_info_selected(property_dialog_->get_entity(), xpos_ + mousex*zoom_, ypos_ + mousey*zoom_, zoom_);
2087 foreach(const editor_variable_info& var, property_dialog_->get_entity()->editor_info()->vars()) {
2088 const std::string& name = var.variable_name();
2089 const editor_variable_info::VARIABLE_TYPE type = var.type();
2090 variant value = property_dialog_->get_entity()->query_value(name);
2091 if(&var == selected_var) {
2092 glColor4ub(255, 255, 0, 255);
2093 } else {
2094 glColor4ub(255, 0, 0, 255);
2095 }
2096
2097 varray.clear();
2098 switch(type) {
2099 case editor_variable_info::XPOSITION:
2100 if(value.is_int()) {
2101 varray.push_back(value.as_int()); varray.push_back(ypos_);
2102 varray.push_back(value.as_int()); varray.push_back(ypos_ + graphics::screen_height()*zoom_);
2103 }
2104 break;
2105 case editor_variable_info::YPOSITION:
2106 if(value.is_int()) {
2107 varray.push_back(xpos_); varray.push_back(value.as_int());
2108 varray.push_back(xpos_ + graphics::screen_width()*zoom_); varray.push_back(value.as_int());
2109 }
2110 break;
2111 default:
2112 break;
2113 }
2114
2115 if(!varray.empty()) {
2116 glVertexPointer(2, GL_FLOAT, 0, &varray.front());
2117 glDrawArrays(GL_LINES, 0, varray.size()/2);
2118 }
2119 }
2120 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
2121 glEnable(GL_TEXTURE_2D);
2122 }
2123
2124 if(g_draw_stats) {
2125 foreach(const stats::const_record_ptr& record, stats_) {
2126 record->draw();
2127 }
2128 }
2129
2130 glPopMatrix();
2131
2132 //draw grid
2133 glDisable(GL_TEXTURE_2D);
2134 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
2135 varray.clear();
2136 glColor4ub(255, 255, 255, 64);
2137 for(int x = TileSize - (xpos_/zoom_)%TileSize; x < graphics::screen_width(); x += 32/zoom_) {
2138 varray.push_back(x); varray.push_back(0);
2139 varray.push_back(x); varray.push_back(graphics::screen_height());
2140 }
2141
2142 for(int y = TileSize - (ypos_/zoom_)%TileSize; y < graphics::screen_height(); y += 32/zoom_) {
2143 varray.push_back(0); varray.push_back(y);
2144 varray.push_back(graphics::screen_width()); varray.push_back(y);
2145 }
2146 glVertexPointer(2, GL_FLOAT, 0, &varray.front());
2147 glDrawArrays(GL_LINES, 0, varray.size()/2);
2148
2149 // draw level boundaries in clear white
2150 {
2151 varray.clear();
2152 std::vector<GLfloat>& carray = graphics::global_texcoords_array(); //reusing texcoords array for colors
2153 carray.clear();
2154 rect boundaries = modify_selected_rect(lvl_->boundaries(), selectx, selecty);
2155 const int x1 = boundaries.x()/zoom_;
2156 const int x2 = boundaries.x2()/zoom_;
2157 const int y1 = boundaries.y()/zoom_;
2158 const int y2 = boundaries.y2()/zoom_;
2159
2160 graphics::color selected_color(255, 255, 0, 255);
2161 graphics::color normal_color(255, 255, 255, 255);
2162
2163 if(resizing_top_level_edge || rect_top_edge_selected(lvl_->boundaries(), selectx, selecty, zoom_)) {
2164 selected_color.add_to_vector(&carray);
2165 selected_color.add_to_vector(&carray);
2166 } else {
2167 normal_color.add_to_vector(&carray);
2168 normal_color.add_to_vector(&carray);
2169 }
2170
2171 varray.push_back(x1 - xpos_/zoom_); varray.push_back(y1 - ypos_/zoom_);
2172 varray.push_back(x2 - xpos_/zoom_); varray.push_back(y1 - ypos_/zoom_);
2173
2174 if(resizing_left_level_edge || rect_left_edge_selected(lvl_->boundaries(), selectx, selecty, zoom_)) {
2175 selected_color.add_to_vector(&carray);
2176 selected_color.add_to_vector(&carray);
2177 } else {
2178 normal_color.add_to_vector(&carray);
2179 normal_color.add_to_vector(&carray);
2180 }
2181
2182 varray.push_back(x1 - xpos_/zoom_); varray.push_back(y1 - ypos_/zoom_);
2183 varray.push_back(x1 - xpos_/zoom_); varray.push_back(y2 - ypos_/zoom_);
2184
2185 if(resizing_right_level_edge || rect_right_edge_selected(lvl_->boundaries(), selectx, selecty, zoom_)) {
2186 selected_color.add_to_vector(&carray);
2187 selected_color.add_to_vector(&carray);
2188 } else {
2189 normal_color.add_to_vector(&carray);
2190 normal_color.add_to_vector(&carray);
2191 }
2192
2193 varray.push_back(x2 - xpos_/zoom_); varray.push_back(y1 - ypos_/zoom_);
2194 varray.push_back(x2 - xpos_/zoom_); varray.push_back(y2 - ypos_/zoom_);
2195
2196 if(resizing_bottom_level_edge || rect_bottom_edge_selected(lvl_->boundaries(), selectx, selecty, zoom_)) {
2197 selected_color.add_to_vector(&carray);
2198 selected_color.add_to_vector(&carray);
2199 } else {
2200 normal_color.add_to_vector(&carray);
2201 normal_color.add_to_vector(&carray);
2202 }
2203
2204 varray.push_back(x1 - xpos_/zoom_); varray.push_back(y2 - ypos_/zoom_);
2205 varray.push_back(x2 - xpos_/zoom_); varray.push_back(y2 - ypos_/zoom_);
2206
2207 glEnableClientState(GL_COLOR_ARRAY);
2208 glVertexPointer(2, GL_FLOAT, 0, &varray.front());
2209 glColorPointer(4, GL_FLOAT, 0, &carray.front());
2210 glDrawArrays(GL_LINES, 0, varray.size()/2);
2211 glDisableClientState(GL_COLOR_ARRAY);
2212 }
2213
2214 draw_selection(0, 0);
2215
2216 if(dragging_) {
2217 int diffx = (selectx - anchorx_)/TileSize;
2218 int diffy = (selecty - anchory_)/TileSize;
2219
2220 if(diffx != 0 || diffy != 0) {
2221 std::cerr << "DRAW DIFF: " << diffx << "," << diffy << "\n";
2222 draw_selection(diffx*TileSize, diffy*TileSize);
2223 }
2224 }
2225
2226 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
2227 glEnable(GL_TEXTURE_2D);
2228
2229 ASSERT_INDEX_INTO_VECTOR(cur_object_, enemy_types);
2230
2231 //the location of the mouse cursor in the map
2232 char loc_buf[256];
2233 sprintf(loc_buf, "%d,%d", xpos_ + mousex*zoom_, ypos_ + mousey*zoom_);
2234 glColor4f(1.0, 1.0, 1.0, 1.0);
2235 graphics::blit_texture(font::render_text(loc_buf, graphics::color_white(), 14), 10, 60);
2236
2237 if(current_dialog_) {
2238 current_dialog_->draw();
2239 }
2240
2241 if(layers_dialog_) {
2242 layers_dialog_->draw();
2243 }
2244
2245 editor_menu_dialog_->draw();
2246 editor_mode_dialog_->draw();
2247 gui::draw_tooltip();
2248
2249 debug_console::draw();
2250
2251 SDL_GL_SwapBuffers();
2252 }
2253
2254 void editor::draw_selection(int xoffset, int yoffset) const
2255 {
2256 if(tile_selection_.empty()) {
2257 return;
2258 }
2259
2260 const int ticks = (SDL_GetTicks()/40)%16;
2261 uint32_t stipple_bits = 0xFF;
2262 stipple_bits <<= ticks;
2263 const uint16_t stipple_mask = (stipple_bits&0xFFFF) | ((stipple_bits&0xFFFF0000) >> 16);
2264
2265 glColor4ub(255, 255, 255, 255);
2266 glEnable(GL_LINE_STIPPLE);
2267 glLineStipple(1, stipple_mask);
2268 std::vector<GLfloat>& varray = graphics::global_vertex_array();
2269 varray.clear();
2270 foreach(const point& p, tile_selection_.tiles) {
2271 const int size = TileSize/zoom_;
2272 const int xpos = xoffset/zoom_ + p.x*size - xpos_/zoom_;
2273 const int ypos = yoffset/zoom_ + p.y*size - ypos_/zoom_;
2274
2275 if(std::binary_search(tile_selection_.tiles.begin(), tile_selection_.tiles.end(), point(p.x, p.y - 1)) == false) {
2276 varray.push_back(xpos); varray.push_back(ypos);
2277 varray.push_back(xpos + size); varray.push_back(ypos);
2278 }
2279
2280 if(std::binary_search(tile_selection_.tiles.begin(), tile_selection_.tiles.end(), point(p.x, p.y + 1)) == false) {
2281 varray.push_back(xpos + size); varray.push_back(ypos + size);
2282 varray.push_back(xpos); varray.push_back(ypos + size);
2283 }
2284
2285 if(std::binary_search(tile_selection_.tiles.begin(), tile_selection_.tiles.end(), point(p.x - 1, p.y)) == false) {
2286 varray.push_back(xpos); varray.push_back(ypos + size);
2287 varray.push_back(xpos); varray.push_back(ypos);
2288 }
2289
2290 if(std::binary_search(tile_selection_.tiles.begin(), tile_selection_.tiles.end(), point(p.x + 1, p.y)) == false) {
2291 varray.push_back(xpos + size); varray.push_back(ypos);
2292 varray.push_back(xpos + size); varray.push_back(ypos + size);
2293 }
2294 }
2295 glVertexPointer(2, GL_FLOAT, 0, &varray.front());
2296 glDrawArrays(GL_LINES, 0, varray.size()/2);
2297 glDisable(GL_LINE_STIPPLE);
2298 glLineStipple(1, 0xFFFF);
2299 }
2300
2301 void editor::run_script(const std::string& id)
2302 {
2303 editor_script::execute(id, *this);
2304 }
2305
2306 void editor::execute_command(boost::function<void()> command, boost::function<void()> undo, EXECUTABLE_COMMAND_TYPE type)
2307 {
2308 level_changed_++;
2309
2310 command();
2311
2312 executable_command cmd;
2313 cmd.redo_command = command;
2314 cmd.undo_command = undo;
2315 cmd.type = type;
2316 undo_.push_back(cmd);
2317 redo_.clear();
2318 }
2319
2320 void editor::begin_command_group()
2321 {
2322 undo_commands_groups_.push(undo_.size());
2323
2324 lvl_->editor_freeze_tile_updates(true);
2325 }
2326
2327 void editor::end_command_group()
2328 {
2329 lvl_->editor_freeze_tile_updates(false);
2330
2331 ASSERT_NE(undo_commands_groups_.empty(), true);
2332
2333 const int index = undo_commands_groups_.top();
2334 undo_commands_groups_.pop();
2335
2336 if(index >= undo_.size()) {
2337 return;
2338 }
2339
2340 //group all of the commands since beginning into one command
2341 std::vector<boost::function<void()> > undo, redo;
2342 for(int n = index; n != undo_.size(); ++n) {
2343 undo.push_back(undo_[n].undo_command);
2344 redo.push_back(undo_[n].redo_command);
2345 }
2346
2347 //reverse the undos, since we want them executed in reverse order.
2348 std::reverse(undo.begin(), undo.end());
2349
2350 //make it so undoing and redoing will freeze tile updates during the
2351 //group command, and then do a full refresh of tiles once we're done.
2352 undo.insert(undo.begin(), boost::bind(&level::editor_freeze_tile_updates, lvl_.get(), true));
2353 undo.push_back(boost::bind(&level::editor_freeze_tile_updates, lvl_.get(), false));
2354 redo.insert(redo.begin(), boost::bind(&level::editor_freeze_tile_updates, lvl_.get(), true));
2355 redo.push_back(boost::bind(&level::editor_freeze_tile_updates, lvl_.get(), false));
2356
2357 executable_command cmd;
2358 cmd.redo_command = boost::bind(execute_functions, redo);
2359 cmd.undo_command = boost::bind(execute_functions, undo);
2360
2361 //replace all the individual commands with the one group command.
2362 undo_.erase(undo_.begin() + index, undo_.end());
2363 undo_.push_back(cmd);
2364 }
2365
2366 void editor::undo_command()
2367 {
2368 if(undo_.empty()) {
2369 return;
2370 }
2371
2372 --level_changed_;
2373
2374 undo_.back().undo_command();
2375 redo_.push_back(undo_.back());
2376 undo_.pop_back();
2377
2378 if(layers_dialog_) {
2379 layers_dialog_->init();
2380 }
2381 }
2382
2383 void editor::redo_command()
2384 {
2385 if(redo_.empty()) {
2386 return;
2387 }
2388
2389 ++level_changed_;
2390
2391 redo_.back().redo_command();
2392 undo_.push_back(redo_.back());
2393 redo_.pop_back();
2394
2395 if(layers_dialog_) {
2396 layers_dialog_->init();
2397 }
2398 }
2399
2400 void show_object_editor_dialog(const std::string& obj_type);
2401
2402 void editor::edit_object_type()
2403 {
2404 std::string type = enemy_types[cur_object_].node->attr("type");
2405 show_object_editor_dialog(type);
2406 }
2407
2408 void launch_object_editor(const std::vector<std::string>& args);
2409
2410 void editor::new_object_type()
2411 {
2412 launch_object_editor(std::vector<std::string>());
2413 wml::const_node_ptr editor_cfg = wml::parse_wml(sys::read_file("data/editor.cfg"));
2414 enemy_type::init(editor_cfg);
2415 }
2416
2417 void editor::edit_level_properties()
2418 {
2419 editor_dialogs::editor_level_properties_dialog prop_dialog(*this);
2420 prop_dialog.show_modal();
2421 }
2422
2423 void editor::add_object_to_level(entity_ptr e)
2424 {
2425 lvl_->add_character(e);
2426 e->handle_event("editor_added");
2427 }
2428
2429 void editor::remove_object_from_level(entity_ptr e)
2430 {
2431 e->handle_event("editor_removed");
2432 lvl_->remove_character(e);
2433 }
2434
2435 void editor::mutate_object_value(entity_ptr e, const std::string& value, variant new_value)
2436 {
2437 e->handle_event("editor_changing_variable");
2438 e->mutate_value(value, new_value);
2439 e->handle_event("editor_changed_variable");
2440 }
2441
0 #ifndef EDITOR_HPP_INCLUDED
1 #define EDITOR_HPP_INCLUDED
2
3 #include <boost/function.hpp>
4 #include <boost/scoped_ptr.hpp>
5 #include <stack>
6 #include <vector>
7
8 #include "geometry.hpp"
9 #include "key.hpp"
10 #include "level.hpp"
11 #include "level_object.hpp"
12 #include "stats.hpp"
13
14 namespace gui {
15 class dialog;
16 }
17
18 namespace editor_dialogs {
19 class character_editor_dialog;
20 class editor_layers_dialog;
21 class group_property_editor_dialog;
22 class property_editor_dialog;
23 class tileset_editor_dialog;
24 }
25
26 class editor_menu_dialog;
27 class editor_mode_dialog;
28
29 class editor
30 {
31 public:
32 static void edit(const char* level_cfg, int xpos=-1, int ypos=-1);
33 static std::string last_edited_level();
34
35 editor(const char* level_cfg);
36 ~editor();
37 void edit_level();
38
39 void load_stats();
40 void show_stats();
41 void download_stats();
42 const std::vector<stats::record_ptr>& stats() const { return stats_; }
43
44 struct tileset {
45 static void init(wml::const_node_ptr node);
46 explicit tileset(wml::const_node_ptr node);
47 std::string category;
48 std::string type;
49 int zorder;
50 int x_speed;
51 int y_speed;
52 boost::shared_ptr<tile_map> preview;
53 bool sloped;
54 };
55
56 struct enemy_type {
57 static void init(wml::const_node_ptr node);
58 explicit enemy_type(const custom_object_type& type);
59 wml::const_node_ptr node;
60 std::string category;
61 entity_ptr preview_object;
62 const frame* preview_frame;
63 };
64
65 struct tile_selection {
66 bool empty() const { return tiles.empty(); }
67 std::vector<point> tiles;
68 };
69
70 const tile_selection& selection() const { return tile_selection_; }
71
72 const std::vector<tileset>& all_tilesets() const;
73 int get_tileset() const { return cur_tileset_; }
74 void set_tileset(int index);
75
76 const std::vector<enemy_type>& all_characters() const;
77
78 int get_object() const { return cur_object_; }
79 void set_object(int index);
80
81 enum EDIT_TOOL { TOOL_ADD_RECT, TOOL_SELECT_RECT, TOOL_MAGIC_WAND, TOOL_PENCIL, TOOL_PICKER, TOOL_ADD_OBJECT, TOOL_SELECT_OBJECT, NUM_TOOLS };
82 EDIT_TOOL tool() const;
83 void change_tool(EDIT_TOOL tool);
84
85 level& get_level() { return *lvl_; }
86
87 void save_level();
88 void save_level_as(const std::string& filename);
89 void quit();
90 bool confirm_quit();
91 void zoom_in();
92 void zoom_out();
93
94 void undo_command();
95 void redo_command();
96
97 void edit_object_type();
98 void new_object_type();
99
100 void close() { done_ = true; }
101
102 void edit_level_properties();
103
104 //make the selected objects part of a group
105 void group_selection();
106
107 bool face_right() const { return face_right_; }
108
109 //switch the current facing.
110 void toggle_facing();
111
112 void run_script(const std::string& id);
113
114 //function which gets the expected layer at which a certain tile id appears.
115 int get_tile_zorder(const std::string& tile_id) const;
116 void add_tile_rect(int zorder, const std::string& tile_id, int x1, int y1, int x2, int y2);
117
118 enum EXECUTABLE_COMMAND_TYPE { COMMAND_TYPE_DEFAULT, COMMAND_TYPE_DRAG_OBJECT };
119
120 //function to execute a command which will go into the undo/redo list.
121 //normally any time the editor mutates the level, it should be done
122 //through this function
123 void execute_command(boost::function<void()> command, boost::function<void()> undo, EXECUTABLE_COMMAND_TYPE type=COMMAND_TYPE_DEFAULT);
124
125 //functions to begin and end a group of commands. This is used when we
126 //are going to execute a bunch of commands, and from the point of view of
127 //undoing, they should be viewed as a single operation.
128 //When end_command_group() is called, all calls to execute_command since
129 //the corresponding call to begin_command_group() will be rolled up
130 //into a single command.
131 //
132 //These functions are re-entrant.
133 void begin_command_group();
134 void end_command_group();
135
136 private:
137 void reset_dialog_positions();
138
139 void handle_mouse_button_down(const SDL_MouseButtonEvent& event);
140 void handle_mouse_button_up(const SDL_MouseButtonEvent& event);
141 void handle_key_press(const SDL_KeyboardEvent& key);
142 void handle_scrolling();
143
144 void handle_object_dragging(int mousex, int mousey);
145 void handle_drawing_rect(int mousex, int mousey);
146
147 void process_ghost_objects();
148 void remove_ghost_objects();
149 void draw() const;
150 void draw_selection(int xoffset, int yoffset) const;
151
152 void add_tile_rect(int x1, int y1, int x2, int y2);
153 void remove_tile_rect(int x1, int y1, int x2, int y2);
154 void select_tile_rect(int x1, int y1, int x2, int y2);
155 void select_magic_wand(int xpos, int ypos);
156
157 void set_selection(const tile_selection& s);
158
159 void move_object(entity_ptr e, int delta_x, int delta_y);
160
161 bool editing_objects() const { return tool_ == TOOL_ADD_OBJECT || tool_ == TOOL_SELECT_OBJECT; }
162 bool editing_tiles() const { return !editing_objects(); }
163
164 //functions which add and remove an object from a level, as well as
165 //sending the object appropriate events.
166 void add_object_to_level(entity_ptr e);
167 void remove_object_from_level(entity_ptr e);
168
169 void mutate_object_value(entity_ptr e, const std::string& value, variant new_value);
170
171 CKey key_;
172
173 boost::intrusive_ptr<level> lvl_;
174 int zoom_;
175 int xpos_, ypos_;
176 int anchorx_, anchory_;
177
178 //if we are dragging an entity around, this marks the position from
179 //which the entity started the drag.
180 int selected_entity_startx_, selected_entity_starty_;
181 std::string filename_;
182
183 EDIT_TOOL tool_;
184 bool done_;
185 bool face_right_;
186 int cur_tileset_;
187
188 int cur_object_;
189
190 tile_selection tile_selection_;
191
192 boost::scoped_ptr<editor_menu_dialog> editor_menu_dialog_;
193 boost::scoped_ptr<editor_mode_dialog> editor_mode_dialog_;
194 boost::scoped_ptr<editor_dialogs::character_editor_dialog> character_dialog_;
195 boost::scoped_ptr<editor_dialogs::editor_layers_dialog> layers_dialog_;
196 boost::scoped_ptr<editor_dialogs::group_property_editor_dialog> group_property_dialog_;
197 boost::scoped_ptr<editor_dialogs::property_editor_dialog> property_dialog_;
198 boost::scoped_ptr<editor_dialogs::tileset_editor_dialog> tileset_dialog_;
199
200 gui::dialog* current_dialog_;
201
202 //if the mouse is currently down, drawing a rect.
203 bool drawing_rect_, dragging_;
204
205 struct executable_command {
206 boost::function<void()> redo_command;
207 boost::function<void()> undo_command;
208 EXECUTABLE_COMMAND_TYPE type;
209 };
210
211 std::vector<executable_command> undo_, redo_;
212
213 //a temporary undo which is used for when we execute commands on
214 //a temporary basis -- e.g. for a preview -- so we can later undo them.
215 boost::scoped_ptr<executable_command> tmp_undo_;
216
217 //indexes into undo_ which records the beginning of the current 'group'
218 //of commands. When begin_command_group() is called, a value is added
219 //set to the size of undo_. When end_command_group() is called, all
220 //commands with index > the top value are aggregated into a single command,
221 //and the top value is popped.
222 std::stack<int> undo_commands_groups_;
223
224 std::vector<entity_ptr> ghost_objects_;
225
226 std::vector<stats::record_ptr> stats_;
227
228 int level_changed_;
229 };
230
231 #endif
0 #include <boost/bind.hpp>
1
2 #include "dialog.hpp"
3 #include "editor_dialogs.hpp"
4 #include "grid_widget.hpp"
5 #include "label.hpp"
6 #include "load_level.hpp"
7 #include "raster.hpp"
8 #include "widget.hpp"
9
10 namespace {
11 void do_select_level(gui::dialog* d, const std::vector<std::string>& levels, int index, std::string* result) {
12 if(index >= 0 && index < levels.size()) {
13 d->close();
14 *result = levels[index];
15 }
16 }
17 }
18
19 std::string show_choose_level_dialog(const std::string& prompt)
20 {
21 using namespace gui;
22 dialog d(0, 0, graphics::screen_width(), graphics::screen_height());
23 d.add_widget(widget_ptr(new label(prompt, graphics::color_white(), 48)));
24
25 std::string result;
26 const int levels_per_col = 20;
27 std::vector<std::string> levels = get_known_levels();
28 gui::grid* parent_grid = new gui::grid(levels.size()/levels_per_col + (levels.size()%levels_per_col ? 1 : 0));
29 int index = 0;
30 while(index < levels.size()) {
31 const int end = std::min(index + levels_per_col, static_cast<int>(levels.size()));
32 gui::grid* grid = new gui::grid(1);
33 grid->set_show_background(true);
34 grid->allow_selection();
35
36 std::vector<std::string> levels_portion(levels.begin() + index, levels.end());
37 grid->register_selection_callback(boost::bind(&do_select_level, &d, levels_portion, _1, &result));
38 while(index != end) {
39 grid->add_col(widget_ptr(new label(levels[index], graphics::color_white())));
40 ++index;
41 }
42 parent_grid->add_col(widget_ptr(grid));
43 }
44
45 d.add_widget(widget_ptr(parent_grid));
46 d.show_modal();
47 return result;
48 }
0 #ifndef EDITOR_DIALOGS_HPP_INCLUDED
1 #define EDITOR_DIALOGS_HPP_INCLUDED
2
3 #include <string>
4
5 std::string show_choose_level_dialog(const std::string& prompt);
6
7
8 #endif
0 #include <map>
1
2 #include "custom_object.hpp"
3 #include "debug_console.hpp"
4 #include "editor.hpp"
5 #include "editor_formula_functions.hpp"
6 #include "foreach.hpp"
7 #include "formula.hpp"
8 #include "formula_callable.hpp"
9 #include "level.hpp"
10 #include "wml_node.hpp"
11 #include "wml_parser.hpp"
12 #include "wml_utils.hpp"
13
14 namespace editor_script {
15
16 using namespace game_logic;
17
18 namespace {
19
20 const int TileSize = 32;
21
22 class editor_command : public formula_callable {
23 public:
24 virtual ~editor_command() {}
25 virtual void execute(editor& e) = 0;
26 private:
27 variant get_value(const std::string& key) const {
28 return variant();
29 }
30 };
31
32 class add_object_command : public editor_command {
33 std::string id_;
34 int x_, y_;
35 bool facing_;
36 public:
37 add_object_command(const std::string& id, int x, int y, bool facing)
38 : id_(id), x_(x), y_(y), facing_(facing)
39 {}
40 private:
41 void execute(editor& e) {
42 custom_object* obj = new custom_object(id_, x_, y_, facing_);
43 obj->set_level(e.get_level());
44 e.get_level().add_character(obj);
45 }
46 };
47
48 class add_object_function : public function_expression {
49 public:
50 explicit add_object_function(const args_list& args)
51 : function_expression("add_object", args, 4, 4)
52 {}
53 private:
54 variant execute(const formula_callable& variables) const {
55 return variant(new add_object_command(
56 args()[0]->evaluate(variables).as_string(),
57 args()[1]->evaluate(variables).as_int(),
58 args()[2]->evaluate(variables).as_int(),
59 args()[3]->evaluate(variables).as_bool()));
60 }
61 };
62
63 class remove_tile_rect_command : public editor_command {
64 std::string tile_id_;
65 int x1_, y1_, x2_, y2_;
66 public:
67 remove_tile_rect_command(const std::string& tile_id, int x1, int y1, int x2, int y2)
68 : tile_id_(tile_id), x1_(x1), y1_(y1), x2_(x2), y2_(y2)
69 {}
70
71 void execute(editor& e) {
72 e.add_tile_rect(e.get_tile_zorder(tile_id_), "", x1_, y1_, x2_, y2_);
73 }
74 };
75
76 class remove_tiles_function : public function_expression {
77 public:
78 explicit remove_tiles_function(const args_list& args)
79 : function_expression("remove_tiles", args, 3, 5)
80 {}
81 private:
82 variant execute(const formula_callable& variables) const {
83 const std::string& tile_id = args()[0]->evaluate(variables).as_string();
84 const int x1 = args()[1]->evaluate(variables).as_int();
85 const int y1 = args()[2]->evaluate(variables).as_int();
86 const int x2 = args().size() > 3 ? args()[3]->evaluate(variables).as_int() : x1;
87 const int y2 = args().size() > 4 ? args()[4]->evaluate(variables).as_int() : y1;
88 return variant(new remove_tile_rect_command(tile_id, x1*TileSize, y1*TileSize, x2*TileSize, y2*TileSize));
89 }
90 };
91
92 class add_tile_rect_command : public editor_command {
93 std::string tile_id_;
94 int x1_, y1_, x2_, y2_;
95 public:
96 add_tile_rect_command(const std::string& tile_id, int x1, int y1, int x2, int y2)
97 : tile_id_(tile_id), x1_(x1), y1_(y1), x2_(x2), y2_(y2)
98 {}
99
100 void execute(editor& e) {
101 e.add_tile_rect(e.get_tile_zorder(tile_id_), tile_id_, x1_, y1_, x2_, y2_);
102 }
103 };
104
105 class add_tiles_function : public function_expression {
106 public:
107 explicit add_tiles_function(const args_list& args)
108 : function_expression("add_tiles", args, 3, 5)
109 {}
110 private:
111 variant execute(const formula_callable& variables) const {
112 const std::string& tile_id = args()[0]->evaluate(variables).as_string();
113 const int x1 = args()[1]->evaluate(variables).as_int();
114 const int y1 = args()[2]->evaluate(variables).as_int();
115 const int x2 = args().size() > 3 ? args()[3]->evaluate(variables).as_int() : x1;
116 const int y2 = args().size() > 4 ? args()[4]->evaluate(variables).as_int() : y1;
117 return variant(new add_tile_rect_command(tile_id, x1*TileSize, y1*TileSize, x2*TileSize, y2*TileSize));
118 }
119 };
120
121 class debug_command : public editor_command
122 {
123 public:
124 explicit debug_command(const std::string& str) : str_(str)
125 {}
126 virtual void execute(editor& e) {
127 debug_console::add_message(str_);
128 }
129 private:
130 std::string str_;
131 };
132
133 class debug_function : public function_expression {
134 public:
135 explicit debug_function(const args_list& args)
136 : function_expression("debug", args, 1, -1) {
137 }
138 private:
139 variant execute(const formula_callable& variables) const {
140 std::string str;
141 for(int n = 0; n != args().size(); ++n) {
142 if(n) str += " ";
143 str += args()[n]->evaluate(variables).to_debug_string();
144 }
145
146 fprintf(stderr, "DEBUG FUNCTION: %s\n", str.c_str());
147
148 return variant(new debug_command(str));
149 }
150 };
151
152 class editor_command_function_symbol_table : public function_symbol_table
153 {
154 public:
155 static editor_command_function_symbol_table& instance() {
156 static editor_command_function_symbol_table result;
157 return result;
158 }
159
160 expression_ptr create_function(
161 const std::string& fn,
162 const std::vector<expression_ptr>& args,
163 const formula_callable_definition* callable_def) const
164 {
165 if(fn == "remove_tiles") {
166 return expression_ptr(new remove_tiles_function(args));
167 } else if(fn == "add_tiles") {
168 return expression_ptr(new add_tiles_function(args));
169 } else if(fn == "add_object") {
170 return expression_ptr(new add_object_function(args));
171 } else if(fn == "debug") {
172 return expression_ptr(new debug_function(args));
173 } else {
174 return function_symbol_table::create_function(fn, args, callable_def);
175 }
176 }
177 };
178
179 void execute_command(variant cmd, editor& e) {
180 if(cmd.is_list()) {
181 for(int n = 0; n != cmd.num_elements(); ++n) {
182 execute_command(cmd[n], e);
183 }
184 } else if(cmd.is_callable()) {
185 editor_command* command = cmd.try_convert<editor_command>();
186 if(command) {
187 command->execute(e);
188 }
189 }
190 }
191
192 class tile_callable : public formula_callable {
193 public:
194 tile_callable(editor& e, int x, int y)
195 : editor_(e), x_(x), y_(y)
196 {}
197
198 private:
199 variant get_value(const std::string& key) const {
200 if(key == "x") {
201 return variant(x_);
202 } else if(key == "y") {
203 return variant(y_);
204 } else if(key == "tiles") {
205 return get_tiles(x_, y_);
206 } else if(key == "up") {
207 return variant(new tile_callable(editor_, x_, y_-1));
208 } else if(key == "down") {
209 return variant(new tile_callable(editor_, x_, y_+1));
210 } else if(key == "left") {
211 return variant(new tile_callable(editor_, x_-1, y_));
212 } else if(key == "right") {
213 return variant(new tile_callable(editor_, x_+1, y_));
214 } else {
215 return variant();
216 }
217 }
218
219 variant get_tiles(int x, int y) const {
220 std::vector<variant> result;
221
222 std::map<int, std::vector<std::string> > m;
223 editor_.get_level().get_all_tiles_rect(x*TileSize, y*TileSize, x*TileSize, y*TileSize, m);
224 for(std::map<int, std::vector<std::string> >::const_iterator i = m.begin(); i != m.end(); ++i) {
225 foreach(const std::string& s, i->second) {
226 result.push_back(variant(s));
227 }
228 }
229
230 return variant(&result);
231 }
232
233 editor& editor_;
234 int x_, y_;
235 };
236
237 class editor_command_callable : public formula_callable {
238 public:
239 explicit editor_command_callable(editor& e) : editor_(e)
240 {}
241 private:
242 variant get_value(const std::string& key) const {
243 if(key == "cells") {
244 std::vector<variant> result;
245
246 const editor::tile_selection& selection = editor_.selection();
247 if(selection.empty()) {
248 const rect& dim = editor_.get_level().boundaries();
249 for(int y = dim.y() - dim.y()%TileSize; y < dim.y2(); y += TileSize) {
250 for(int x = dim.x() - dim.x()%TileSize; x < dim.x2(); x += TileSize) {
251 result.push_back(variant(new tile_callable(editor_, x/TileSize, y/TileSize)));
252 }
253 }
254 } else {
255 foreach(const point& p, selection.tiles) {
256 result.push_back(variant(new tile_callable(editor_, p.x, p.y)));
257 }
258 }
259
260 return variant(&result);
261 } else {
262 return variant();
263 }
264 }
265 editor& editor_;
266 };
267
268
269 std::vector<info> scripts_info;
270 std::map<std::string, const_formula_ptr> scripts;
271
272 void load_scripts()
273 {
274 if(scripts_info.empty() == false) {
275 return;
276 }
277
278 wml::node_ptr node(wml::parse_wml_from_file("data/editor/scripts.cfg"));
279 if(!node) {
280 return;
281 }
282
283 //load any functions defined here.
284 FOREACH_WML_CHILD(function_node, node, "function") {
285 }
286
287 FOREACH_WML_CHILD(script_node, node, "script") {
288 const std::string& id = script_node->attr("id");
289 info script = { id };
290 scripts_info.push_back(script);
291 scripts[id].reset(new formula(script_node->attr("script"), &editor_command_function_symbol_table::instance()));
292 }
293 }
294
295 }
296
297 std::vector<info> all_scripts() {
298 load_scripts();
299 return scripts_info;
300 }
301
302 void execute(const std::string& id, editor& e)
303 {
304 load_scripts();
305
306 std::map<std::string, const_formula_ptr>::const_iterator itor = scripts.find(id);
307 if(itor == scripts.end() || !itor->second) {
308 return;
309 }
310
311 formula_callable_ptr callable(new editor_command_callable(e));
312 const variant cmd = itor->second->execute(*callable);
313
314 //execute the command, making sure the editor allows the user to undo the
315 //entire script in one go.
316 e.begin_command_group();
317 execute_command(cmd, e);
318 e.end_command_group();
319 }
320
321 }
0 #ifndef EDITOR_FORMULA_FUNCTIONS_HPP_INCLUDED
1 #define EDITOR_FORMULA_FUNCTIONS_HPP_INCLUDED
2
3 #include <string>
4 #include <vector>
5
6 class editor;
7
8 namespace editor_script {
9
10 struct info {
11 std::string name;
12 };
13
14 std::vector<info> all_scripts();
15
16 void execute(const std::string& id, editor& e);
17
18 }
19
20 #endif
0 #include <boost/bind.hpp>
1
2 #include "editor_layers_dialog.hpp"
3 #include "foreach.hpp"
4 #include "formatter.hpp"
5 #include "grid_widget.hpp"
6 #include "image_widget.hpp"
7 #include "label.hpp"
8 #include "level.hpp"
9 #include "raster.hpp"
10
11 namespace editor_dialogs
12 {
13
14 editor_layers_dialog::editor_layers_dialog(editor& e)
15 : dialog(graphics::screen_width() - 200, 40, 40, graphics::screen_height() - 40), editor_(e)
16 {
17 init();
18 }
19
20 void editor_layers_dialog::init()
21 {
22 clear();
23 rows_.clear();
24
25 using namespace gui;
26 grid_ptr g(new grid(2));
27
28 std::set<int> all_layers, visible_layers;
29 editor_.get_level().get_tile_layers(&all_layers, &visible_layers);
30
31 foreach(int layer, all_layers) {
32 const bool hidden = visible_layers.count(layer);
33 gui_section_widget* section = new gui_section_widget(hidden ? "checkbox-empty" : "checkbox-filled");
34
35 row_data row = { section, layer, hidden };
36 rows_.push_back(row);
37 g->add_col(widget_ptr(section));
38 g->add_col(widget_ptr(new label(formatter() << layer, graphics::color_white())));
39 }
40
41 g->allow_selection();
42 g->register_selection_callback(boost::bind(&editor_layers_dialog::row_selected, this, _1));
43 g->register_mouseover_callback(boost::bind(&editor_layers_dialog::row_mouseover, this, _1));
44
45 add_widget(g, 0, 0);
46 }
47
48 void editor_layers_dialog::row_selected(int nrow)
49 {
50 if(nrow < 0 || nrow >= rows_.size()) {
51 return;
52 }
53
54 editor_.execute_command(
55 boost::bind(&level::hide_tile_layer, &editor_.get_level(), rows_[nrow].layer, !rows_[nrow].hidden),
56 boost::bind(&level::hide_tile_layer, &editor_.get_level(), rows_[nrow].layer, rows_[nrow].hidden));
57
58 init();
59 }
60
61 void editor_layers_dialog::row_mouseover(int nrow)
62 {
63 if(nrow < 0 || nrow >= rows_.size()) {
64 editor_.get_level().highlight_tile_layer(INT_MIN);
65 return;
66 }
67
68 editor_.get_level().highlight_tile_layer(rows_[nrow].layer);
69 }
70
71 }
0 #ifndef EDITOR_LAYERS_DIALOG_HPP_INCLUDED
1 #define EDITOR_LAYERS_DIALOG_HPP_INCLUDED
2
3 #include <vector>
4
5 #include "dialog.hpp"
6 #include "editor.hpp"
7 #include "image_widget.hpp"
8
9 namespace editor_dialogs
10 {
11
12 class editor_layers_dialog : public gui::dialog
13 {
14 public:
15 explicit editor_layers_dialog(editor& e);
16 void init();
17 private:
18 void row_selected(int nrow);
19 void row_mouseover(int nrow);
20 editor& editor_;
21
22 struct row_data {
23 gui::gui_section_widget* checkbox;
24 int layer;
25 bool hidden;
26 };
27
28 std::vector<row_data> rows_;
29 };
30
31 }
32
33 #endif
0 #include "SDL.h"
1
2 #include <boost/bind.hpp>
3
4 #include <algorithm>
5 #include <iostream>
6
7 #include "background.hpp"
8 #include "button.hpp"
9 #include "editor.hpp"
10 #include "editor_dialogs.hpp"
11 #include "editor_level_properties_dialog.hpp"
12 #include "foreach.hpp"
13 #include "formatter.hpp"
14 #include "grid_widget.hpp"
15 #include "label.hpp"
16 #include "raster.hpp"
17 #include "stats.hpp"
18 #include "text_entry_widget.hpp"
19
20 namespace editor_dialogs
21 {
22
23 editor_level_properties_dialog::editor_level_properties_dialog(editor& e)
24 : dialog(0, 0, graphics::screen_width(), graphics::screen_height()), editor_(e)
25 {
26 init();
27 }
28
29 void editor_level_properties_dialog::init()
30 {
31 using namespace gui;
32 clear();
33
34 add_widget(widget_ptr(new label("Level Properties", graphics::color_white(), 48)), 10, 10);
35
36 grid_ptr g(new grid(2));
37 g->add_col(widget_ptr(new label(editor_.get_level().title(), graphics::color_white(), 36)))
38 .add_col(widget_ptr(new button(widget_ptr(new label("Change Title", graphics::color_white())), boost::bind(&editor_level_properties_dialog::change_title, this))));
39
40 add_widget(g);
41
42 std::string background_id = editor_.get_level().get_background_id();
43 if(background_id.empty()) {
44 background_id = "(no background)";
45 }
46 g.reset(new grid(2));
47 g->add_col(widget_ptr(new label("Background", graphics::color_white())))
48 .add_col(widget_ptr(new button(widget_ptr(new label(background_id, graphics::color_white())), boost::bind(&editor_level_properties_dialog::change_background, this))));
49 add_widget(g);
50
51 g.reset(new grid(3));
52 g->set_hpad(10);
53 g->add_col(widget_ptr(new label("Next Level", graphics::color_white())));
54 g->add_col(widget_ptr(new label(editor_.get_level().next_level(), graphics::color_white())));
55 g->add_col(widget_ptr(new button(widget_ptr(new label("Set", graphics::color_white())), boost::bind(&editor_level_properties_dialog::change_next_level, this))));
56
57 g->add_col(widget_ptr(new label("Previous Level", graphics::color_white())));
58 g->add_col(widget_ptr(new label(editor_.get_level().previous_level(), graphics::color_white())));
59 g->add_col(widget_ptr(new button(widget_ptr(new label("Set", graphics::color_white())), boost::bind(&editor_level_properties_dialog::change_previous_level, this))));
60 add_widget(g);
61 }
62
63 void editor_level_properties_dialog::change_title()
64 {
65 using namespace gui;
66 dialog d(0, 0, graphics::screen_width(), graphics::screen_height());
67 d.add_widget(widget_ptr(new label("Change Title", graphics::color_white(), 48)));
68 text_entry_widget* entry = new text_entry_widget;
69 d.add_widget(widget_ptr(new label("Name:", graphics::color_white())))
70 .add_widget(widget_ptr(entry));
71 d.show_modal();
72
73 if(d.cancelled()) {
74 return;
75 }
76
77 std::string title = entry->text();
78
79 editor_.get_level().set_title(title);
80
81 init();
82 }
83
84 void editor_level_properties_dialog::change_background()
85 {
86 using namespace gui;
87 std::vector<std::string> backgrounds = background::get_available_backgrounds();
88 if(backgrounds.empty()) {
89 return;
90 }
91
92 std::sort(backgrounds.begin(), backgrounds.end());
93
94 gui::grid* grid = new gui::grid(1);
95 grid->set_hpad(40);
96 grid->set_show_background(true);
97 grid->allow_selection();
98 grid->swallow_clicks();
99 grid->register_selection_callback(boost::bind(&editor_level_properties_dialog::execute_change_background, this, backgrounds, _1));
100 foreach(const std::string& bg, backgrounds) {
101 grid->add_col(widget_ptr(new label(bg, graphics::color_white())));
102 }
103
104 int mousex, mousey;
105 SDL_GetMouseState(&mousex, &mousey);
106
107 mousex -= x();
108 mousey -= y();
109
110 remove_widget(context_menu_);
111 context_menu_.reset(grid);
112 add_widget(context_menu_, mousex, mousey);
113 }
114
115 void editor_level_properties_dialog::execute_change_background(const std::vector<std::string>& choices, int index)
116 {
117 if(context_menu_) {
118 remove_widget(context_menu_);
119 context_menu_.reset();
120 }
121
122 if(index < 0 || index >= choices.size()) {
123 return;
124 }
125
126 editor_.get_level().set_background_by_id(choices[index]);
127 init();
128 }
129
130 void editor_level_properties_dialog::change_next_level()
131 {
132 std::string result = show_choose_level_dialog("Next Level");
133 if(result.empty() == false) {
134 editor_.get_level().set_next_level(result);
135 }
136
137 init();
138 }
139
140 void editor_level_properties_dialog::change_previous_level()
141 {
142 std::string result = show_choose_level_dialog("Previous Level");
143 if(result.empty() == false) {
144 editor_.get_level().set_previous_level(result);
145 }
146
147 init();
148 }
149
150 }
0 #ifndef EDITOR_LEVEL_PROPERTIES_DIALOG_HPP_INCLUDED
1 #define EDITOR_LEVEL_PROPERTIES_DIALOG_HPP_INCLUDED
2
3 #include <string>
4 #include <vector>
5
6 #include "dialog.hpp"
7
8 class editor;
9
10 namespace editor_dialogs
11 {
12
13 class editor_level_properties_dialog : public gui::dialog
14 {
15 public:
16 explicit editor_level_properties_dialog(editor& e);
17 void init();
18 private:
19 void change_title();
20 void change_background();
21 void execute_change_background(const std::vector<std::string>& choices, int index);
22
23 void change_next_level();
24 void change_previous_level();
25
26 editor& editor_;
27 gui::widget_ptr context_menu_;
28 };
29
30 }
31
32 #endif
0 #include "SDL.h"
1
2 #include <boost/bind.hpp>
3
4 #include <algorithm>
5 #include <iostream>
6
7 #include "background.hpp"
8 #include "button.hpp"
9 #include "editor.hpp"
10 #include "editor_stats_dialog.hpp"
11 #include "foreach.hpp"
12 #include "formatter.hpp"
13 #include "grid_widget.hpp"
14 #include "label.hpp"
15 #include "raster.hpp"
16 #include "stats.hpp"
17 #include "text_entry_widget.hpp"
18
19 namespace editor_dialogs
20 {
21
22 editor_stats_dialog::editor_stats_dialog(editor& e)
23 : dialog(0, 0, graphics::screen_width(), graphics::screen_height()), editor_(e)
24 {
25 init();
26 }
27
28 void editor_stats_dialog::init()
29 {
30 using namespace gui;
31 add_widget(widget_ptr(new label("Statistics (whole level)", graphics::color_white(), 36)));
32
33 std::vector<stats::record_ptr> stats = editor_.stats();
34 add_stats(stats);
35
36 if(!editor_.selection().empty()) {
37 foreach(stats::record_ptr& s, stats) {
38 const point loc = s->location();
39 bool in_selection = false;
40 const int TileSize = 32;
41 foreach(const point& tile, editor_.selection().tiles) {
42 if(loc.x >= tile.x*TileSize && loc.y >= tile.y*TileSize && loc.x < (tile.x+1)*TileSize && loc.y < (tile.y+1)*TileSize) {
43 in_selection = true;
44 break;
45 }
46 }
47
48 if(!in_selection) {
49 s = stats::record_ptr();
50 }
51 }
52
53 add_widget(widget_ptr(new label("Statistics (selection)", graphics::color_white(), 36)));
54 add_stats(stats);
55 }
56 }
57
58 void editor_stats_dialog::add_stats(const std::vector<stats::record_ptr>& stats)
59 {
60 using namespace gui;
61
62 std::map<std::string, int> stats_counts;
63 foreach(stats::record_ptr r, stats) {
64 if(r) {
65 stats_counts[r->id()]++;
66 }
67 }
68
69 const int seconds_play_time = stats_counts["move"]/5;
70 stats_counts["Time Played"] = seconds_play_time;
71 if(stats_counts["die"] > 0) {
72 stats_counts["Seconds per Death"] = seconds_play_time/stats_counts["die"];
73 }
74
75 grid_ptr g(new grid(2));
76 g->set_hpad(10);
77 for(std::map<std::string, int>::const_iterator i = stats_counts.begin();
78 i != stats_counts.end(); ++i) {
79 g->add_col(widget_ptr(new label(i->first, graphics::color_white())));
80 g->add_col(widget_ptr(new label(formatter() << i->second, graphics::color_white())));
81 }
82
83 add_widget(g);
84 }
85
86 }
0 #ifndef EDITOR_STATS_DIALOG_HPP_INCLUDED
1 #define EDITOR_STATS_DIALOG_HPP_INCLUDED
2
3 #include <string>
4 #include <vector>
5
6 #include "dialog.hpp"
7
8 class editor;
9
10 namespace editor_dialogs
11 {
12
13 class editor_stats_dialog : public gui::dialog
14 {
15 public:
16 explicit editor_stats_dialog(editor& e);
17 void init();
18 private:
19 void add_stats(const std::vector<stats::record_ptr>& stats);
20 editor& editor_;
21 };
22
23 }
24
25 #endif
0 #include <iostream>
1
2 #include "editor_variable_info.hpp"
3 #include "foreach.hpp"
4 #include "formula.hpp"
5 #include "wml_node.hpp"
6 #include "wml_utils.hpp"
7
8 editor_variable_info::editor_variable_info(wml::const_node_ptr node)
9 : name_(node->attr("name")), type_(TYPE_INTEGER), info_(node->attr("info")),
10 help_(node->attr("help")),
11 formula_(game_logic::formula::create_optional_formula(node->attr("value")))
12 {
13 const std::string& type = node->attr("type");
14 if(type == "x") {
15 type_ = XPOSITION;
16 std::cerr << "XPOS VARIABLE\n";
17 } else if(type == "y") {
18 type_ = YPOSITION;
19 } else if(type == "level") {
20 type_ = TYPE_LEVEL;
21 } else if(type == "label") {
22 type_ = TYPE_LABEL;
23 } else if(type == "text") {
24 type_ = TYPE_TEXT;
25 } else if(type == "boolean") {
26 type_ = TYPE_BOOLEAN;
27 }
28 }
29
30 wml::node_ptr editor_variable_info::write() const
31 {
32 wml::node_ptr node(new wml::node("var"));
33 node->set_attr("name", name_);
34 if(info_.empty() == false) {
35 node->set_attr("info", info_);
36 }
37
38 switch(type_) {
39 case XPOSITION:
40 node->set_attr("type", "x");
41 break;
42 case YPOSITION:
43 node->set_attr("type", "y");
44 break;
45 case TYPE_LEVEL:
46 node->set_attr("type", "level");
47 break;
48 case TYPE_LABEL:
49 node->set_attr("type", "label");
50 break;
51 case TYPE_TEXT:
52 node->set_attr("type", "text");
53 break;
54 case TYPE_BOOLEAN:
55 node->set_attr("type", "boolean");
56 break;
57 }
58 return node;
59 }
60
61 editor_entity_info::editor_entity_info(wml::const_node_ptr node)
62 : category_(node->attr("category")), help_(node->attr("help"))
63 {
64 FOREACH_WML_CHILD(var_node, node, "var") {
65 std::cerr << "CREATE VAR INFO...\n";
66 vars_.push_back(editor_variable_info(var_node));
67 }
68 }
69
70 wml::node_ptr editor_entity_info::write() const
71 {
72 wml::node_ptr node(new wml::node("editor_info"));
73 node->set_attr("category", category_);
74 foreach(const editor_variable_info& v, vars_) {
75 node->add_child(v.write());
76 }
77
78 return node;
79 }
80
81 const editor_variable_info* editor_entity_info::get_var_info(const std::string& var_name) const
82 {
83 foreach(const editor_variable_info& v, vars_) {
84 if(v.variable_name() == var_name) {
85 return &v;
86 }
87 }
88
89 return NULL;
90 }
0 #ifndef EDITOR_VARIABLE_INFO_HPP_INCLUDED
1 #define EDITOR_VARIABLE_INFO_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 #include <string>
6 #include <vector>
7
8 #include "formula_fwd.hpp"
9 #include "wml_node_fwd.hpp"
10
11 class editor_variable_info {
12 public:
13 enum VARIABLE_TYPE { TYPE_INTEGER, XPOSITION, YPOSITION, TYPE_LEVEL, TYPE_LABEL, TYPE_TEXT, TYPE_BOOLEAN };
14
15 explicit editor_variable_info(wml::const_node_ptr node);
16
17 wml::node_ptr write() const;
18
19 const std::string& variable_name() const { return name_; }
20 VARIABLE_TYPE type() const { return type_; }
21 const std::string& info() const { return info_; }
22 const std::string& help() const { return help_; }
23
24 const game_logic::const_formula_ptr& formula() const { return formula_; }
25
26 private:
27 std::string name_;
28 VARIABLE_TYPE type_;
29 std::string info_;
30 std::string help_;
31 game_logic::const_formula_ptr formula_;
32 };
33
34 class editor_entity_info {
35 public:
36 explicit editor_entity_info(wml::const_node_ptr node);
37
38 wml::node_ptr write() const;
39
40 const std::string& category() const { return category_; }
41 const std::vector<editor_variable_info>& vars() const { return vars_; }
42 const editor_variable_info* get_var_info(const std::string& var_name) const;
43 const std::string& help() const { return help_; }
44 private:
45 std::string category_;
46 std::vector<editor_variable_info> vars_;
47 std::string help_;
48 };
49
50 typedef boost::shared_ptr<editor_entity_info> editor_entity_info_ptr;
51 typedef boost::shared_ptr<const editor_entity_info> const_editor_entity_info_ptr;
52
53 #endif
0 #include <iostream>
1
2 #include "custom_object.hpp"
3 #include "entity.hpp"
4 #include "foreach.hpp"
5 #include "playable_custom_object.hpp"
6 #include "preferences.hpp"
7 #include "raster.hpp"
8 #include "solid_map.hpp"
9 #include "wml_node.hpp"
10 #include "wml_utils.hpp"
11
12 entity::entity(wml::const_node_ptr node)
13 : x_(wml::get_int(node, "x")*100),
14 y_(wml::get_int(node, "y")*100),
15 prev_feet_x_(INT_MIN), prev_feet_y_(INT_MIN),
16 face_right_(wml::get_bool(node, "face_right")),
17 upside_down_(wml::get_bool(node, "upside_down", false)),
18 group_(wml::get_int(node, "group", -1)),
19 id_(-1), respawn_(wml::get_bool(node, "respawn", true)),
20 solid_dimensions_(0), collide_dimensions_(0),
21 weak_solid_dimensions_(0), weak_collide_dimensions_(0)
22 {
23 foreach(bool& b, controls_) {
24 b = false;
25 }
26 }
27
28 entity::entity(int x, int y, bool face_right)
29 : x_(x*100), y_(y*100), prev_feet_x_(INT_MIN), prev_feet_y_(INT_MIN),
30 face_right_(face_right), upside_down_(false), group_(-1), id_(-1),
31 solid_dimensions_(0), collide_dimensions_(0),
32 weak_solid_dimensions_(0), weak_collide_dimensions_(0)
33 {
34 foreach(bool& b, controls_) {
35 b = false;
36 }
37 }
38
39 entity_ptr entity::build(wml::const_node_ptr node)
40 {
41 if(node->has_attr("is_human")) {
42 return entity_ptr(new playable_custom_object(node));
43 } else {
44 return entity_ptr(new custom_object(node));
45 }
46 }
47
48 bool entity::has_feet() const
49 {
50 return solid();
51 }
52
53 int entity::feet_x() const
54 {
55 if(solid_) {
56 const int diff = solid_->area().x() + solid_->area().w()/2;
57 return face_right() ? x() + diff : x() + current_frame().width() - diff;
58 }
59 return face_right() ? x() + current_frame().feet_x() : x() + current_frame().width() - current_frame().feet_x();
60 }
61
62 int entity::feet_y() const
63 {
64 if(solid_) {
65 return y() + solid_->area().y() + solid_->area().h();
66 }
67 return y() + current_frame().feet_y();
68 }
69
70 int entity::last_move_x() const
71 {
72 if(prev_feet_x_ == INT_MIN) {
73 return 0;
74 }
75
76 return feet_x() - prev_feet_x_;
77 }
78
79 int entity::last_move_y() const
80 {
81 if(prev_feet_y_ == INT_MIN) {
82 return 0;
83 }
84
85 return feet_y() - prev_feet_y_;
86 }
87
88 void entity::process(level& lvl)
89 {
90 prev_feet_x_ = feet_x();
91 prev_feet_y_ = feet_y();
92 }
93
94 void entity::set_face_right(bool facing)
95 {
96 if(facing == face_right_) {
97 return;
98 }
99 const int start_x = feet_x();
100 face_right_ = facing;
101 const int delta_x = feet_x() - start_x;
102 x_ -= delta_x*100;
103 assert(feet_x() == start_x);
104
105 calculate_solid_rect();
106 }
107
108 void entity::set_upside_down(bool facing)
109 {
110 upside_down_ = facing;
111 }
112
113 void entity::calculate_solid_rect()
114 {
115 const frame& f = current_frame();
116
117 frame_rect_ = rect(x(), y(), f.width(), f.height());
118
119 solid_ = calculate_solid();
120 if(solid_) {
121 const rect& area = solid_->area();
122
123 if(face_right()) {
124 solid_rect_ = rect(x() + area.x(), y() + area.y(), area.w(), area.h());
125 } else {
126 solid_rect_ = rect(x() + f.width() - area.x() - area.w(), y() + area.y(), area.w(), area.h());
127 }
128 } else {
129 solid_rect_ = rect();
130 }
131
132 platform_ = calculate_platform();
133 if(platform_) {
134 const int delta_y = last_move_y();
135 const rect& area = platform_->area();
136
137 if(area.empty()) {
138 platform_rect_ = rect();
139 } else {
140 if(delta_y < 0) {
141 platform_rect_ = rect(x() + area.x(), y() + area.y(), area.w(), area.h() - delta_y);
142 } else {
143 platform_rect_ = rect(x() + area.x(), y() + area.y(), area.w(), area.h());
144 }
145 }
146 } else {
147 platform_rect_ = rect();
148 }
149 }
150
151 rect entity::body_rect() const
152 {
153 const frame& f = current_frame();
154
155 const int ypos = y() + (upside_down() ? (f.height() - (f.collide_y() + f.collide_h())) : f.collide_y());
156 return rect(face_right() ? x() + f.collide_x() : x() + f.width() - f.collide_x() - f.collide_w(),
157 ypos, f.collide_w(), f.collide_h());
158 }
159
160 rect entity::hit_rect() const
161 {
162 const frame& f = current_frame();
163 const std::vector<frame::collision_area>& areas = f.collision_areas();
164 foreach(const frame::collision_area& a, areas) {
165 if(a.name == "attack") {
166 const rect& r = a.area;
167 return rect(face_right() ? x() + r.x() : x() + f.width() - r.x() - r.w(), y() + r.y(), r.w(), r.h());
168 }
169 }
170
171 return rect();
172 }
173
174 point entity::midpoint() const
175 {
176 if(solid()) {
177 const rect r = solid_rect();
178 return point(r.x() + r.w()/2, r.y() + r.h()/2);
179 }
180
181 const frame& f = current_frame();
182 return point(x() + f.width()/2, y() + f.height()/2);
183 }
184
185 bool entity::is_alpha(int xpos, int ypos) const
186 {
187 return current_frame().is_alpha(xpos - x(), ypos - y(), time_in_frame(), face_right());
188 }
189
190 void entity::draw_debug_rects() const
191 {
192 if(preferences::show_debug_hitboxes() == false) {
193 return;
194 }
195
196 const rect& body = solid_rect();
197 if(body.w() > 0 && body.h() > 0) {
198 const SDL_Rect rect = { body.x(), body.y(), body.w(), body.h() };
199 graphics::draw_rect(rect, graphics::color_black(), 0xAA);
200 }
201
202 const rect& hit = hit_rect();
203 if(hit.w() > 0 && hit.h() > 0) {
204 const SDL_Rect rect = { hit.x(), hit.y(), hit.w(), hit.h() };
205 graphics::draw_rect(rect, graphics::color_red(), 0xAA);
206 }
207
208 const SDL_Rect rect = { feet_x() - 1, feet_y() - 1, 3, 3 };
209 graphics::draw_rect(rect, graphics::color_white(), 0xFF);
210 }
211
212 void entity::generate_current(const entity& target, int* velocity_x, int* velocity_y) const
213 {
214 if(current_generator_) {
215 const rect& my_rect = body_rect();
216 const rect& target_rect = target.body_rect();
217 current_generator_->generate(my_rect.mid_x(), my_rect.mid_y(),
218 target_rect.mid_x(), target_rect.mid_y(), target.mass(),
219 velocity_x, velocity_y);
220 }
221 }
222
223 void entity::add_scheduled_command(int cycle, variant cmd)
224 {
225 scheduled_commands_.push_back(ScheduledCommand(cycle, cmd));
226 std::sort(scheduled_commands_.begin(), scheduled_commands_.end());
227 }
228
229 variant entity::get_scheduled_command(int cycle)
230 {
231 if(scheduled_commands_.empty() == false && cycle >= scheduled_commands_.front().first) {
232 variant result = scheduled_commands_.front().second;
233 scheduled_commands_.erase(scheduled_commands_.begin());
234 return result;
235 }
236
237 return variant();
238 }
239
240 namespace {
241 std::vector<const_powerup_ptr> empty_powerup_vector;
242 }
243
244 const std::vector<const_powerup_ptr>& entity::powerups() const
245 {
246 return empty_powerup_vector;
247 }
248
249 const std::vector<const_powerup_ptr>& entity::abilities() const
250 {
251 return empty_powerup_vector;
252 }
253
254 void entity::set_current_generator(current_generator* generator)
255 {
256 current_generator_ = current_generator_ptr(generator);
257 }
258
259 void entity::set_attached_objects(const std::vector<entity_ptr>& v)
260 {
261 if(v != attached_objects_) {
262 attached_objects_ = v;
263 }
264 }
265
266 bool entity::move_centipixels(int dx, int dy)
267 {
268 int start_x = x();
269 int start_y = y();
270 x_ += dx;
271 y_ += dy;
272 if(x() != start_x || y() != start_y) {
273 calculate_solid_rect();
274 return true;
275 } else {
276 return false;
277 }
278 }
279
280 void entity::set_distinct_label()
281 {
282 //generate a random label for the object
283 char buf[64];
284 sprintf(buf, "_%x", rand());
285 set_label(buf);
286 }
287
288 void entity::set_control_status(const std::string& key, bool value)
289 {
290 static const std::string keys[] = { "up", "down", "left", "right", "attack", "jump" };
291 const std::string* k = std::find(keys, keys + controls::NUM_CONTROLS, key);
292 if(k == keys + controls::NUM_CONTROLS) {
293 return;
294 }
295
296 const int index = k - keys;
297 controls_[index] = value;
298 }
299
300 void entity::read_controls(int cycle)
301 {
302 player_info* info = get_player_info();
303 if(info) {
304 info->read_controls(cycle);
305 }
306 }
0 #ifndef ENTITY_HPP_INCLUDED
1 #define ENTITY_HPP_INCLUDED
2
3 #include <string>
4
5 #include "boost/intrusive_ptr.hpp"
6
7 #include "controls.hpp"
8 #include "current_generator.hpp"
9 #include "editor_variable_info.hpp"
10 #include "entity_fwd.hpp"
11 #include "formula_callable.hpp"
12 #include "formula_fwd.hpp"
13 #include "geometry.hpp"
14 #include "key.hpp"
15 #include "powerup_fwd.hpp"
16 #include "solid_map_fwd.hpp"
17 #include "wml_formula_callable.hpp"
18 #include "wml_node_fwd.hpp"
19
20 class character;
21 class frame;
22 class level;
23 class pc_character;
24 class player_info;
25
26 typedef boost::intrusive_ptr<character> character_ptr;
27
28 class entity : public game_logic::wml_serializable_formula_callable
29 {
30 public:
31 static entity_ptr build(wml::const_node_ptr node);
32 explicit entity(wml::const_node_ptr node);
33 entity(int x, int y, bool face_right);
34 virtual ~entity() {}
35 virtual wml::node_ptr write() const = 0;
36 virtual void setup_drawing() const {}
37 virtual void draw() const = 0;
38 virtual void draw_group() const = 0;
39 player_info* get_player_info() { return is_human(); }
40 const player_info* get_player_info() const { return is_human(); }
41 virtual const player_info* is_human() const { return NULL; }
42 virtual player_info* is_human() { return NULL; }
43 virtual void process(level& lvl);
44 virtual bool execute_command(const variant& var) = 0;
45
46 const std::string& label() const { return label_; }
47 void set_label(const std::string& lb) { label_ = lb; }
48 void set_distinct_label();
49
50 void set_pos(const point& p) { x_ = p.x*100; y_ = p.y*100; calculate_solid_rect(); }
51 void set_pos(int x, int y) { x_ = x*100; y_ = y*100; calculate_solid_rect(); }
52 void set_x(int x) { x_ = x*100; calculate_solid_rect(); }
53 void set_y(int y) { y_ = y*100; calculate_solid_rect(); }
54
55 void set_centi_x(int x) { x_ = x; calculate_solid_rect(); }
56 void set_centi_y(int y) { y_ = y; calculate_solid_rect(); }
57
58 int x() const { return x_/100 - (x_ < 0 && x_%100 ? 1 : 0); }
59 int y() const { return y_/100 - (y_ < 0 && y_%100 ? 1 : 0); }
60 virtual int zorder() const { return 0; }
61
62 virtual const std::pair<int,int>* position_scale_millis() const { return 0; }
63
64 int centi_x() const { return x_; }
65 int centi_y() const { return y_; }
66
67 virtual int velocity_x() const { return 0; }
68 virtual int velocity_y() const { return 0; }
69
70 int group() const { return group_; }
71 void set_group(int group) { group_ = group; }
72
73 virtual bool is_standable(int x, int y, int* friction=NULL, int* traction=NULL, int* adjust_y=NULL) const { return false; }
74
75 virtual bool destroyed() const = 0;
76
77 virtual int surface_friction() const { return 0; }
78 virtual int surface_traction() const { return 0; }
79
80 virtual bool point_collides(int x, int y) const = 0;
81 virtual bool rect_collides(const rect& r) const = 0;
82 const solid_info* platform() const { return platform_; }
83 const solid_info* solid() const { return solid_; }
84 const rect& solid_rect() const { return solid_rect_; }
85 const rect& frame_rect() const { return frame_rect_; }
86 rect platform_rect() const { return platform_rect_; }
87 rect body_rect() const;
88 rect hit_rect() const;
89 point midpoint() const;
90
91 virtual const frame& icon_frame() const = 0;
92 virtual const frame& current_frame() const = 0;
93
94 virtual rect draw_rect() const = 0;
95
96 bool is_alpha(int xpos, int ypos) const;
97
98 virtual bool has_feet() const;
99 int feet_x() const;
100 int feet_y() const;
101
102 int last_move_x() const;
103 int last_move_y() const;
104
105 bool face_right() const { return face_right_; }
106 virtual void set_face_right(bool facing);
107
108 bool upside_down() const { return upside_down_; }
109 virtual void set_upside_down(bool facing);
110
111 int face_dir() const { return face_right() ? 1 : -1; }
112
113 virtual bool body_harmful() const { return true; }
114
115 virtual int time_in_frame() const = 0;
116
117 virtual int teleport_offset_x() const { return 0; }
118 virtual int teleport_offset_y() const { return 0; }
119
120 virtual bool is_active(const rect& screen_area) const = 0;
121 virtual bool dies_on_inactive() const { return false; }
122 virtual bool always_active() const { return false; }
123
124 virtual formula_callable* vars() { return NULL; }
125 virtual const formula_callable* vars() const { return NULL; }
126
127 virtual bool body_passthrough() const { return false; }
128
129 //the number of pixels up or down to adjust the scroll position if this
130 //object is focused.
131 virtual int vertical_look() const { return 0; }
132
133 void set_id(int id) { id_ = id; }
134 int get_id() const { return id_; }
135
136 bool respawn() const { return respawn_; }
137
138 virtual bool boardable_vehicle() const { return false; }
139 virtual void boarded(level& lvl, const entity_ptr& player) {}
140 virtual void unboarded(level& lvl) {}
141
142 virtual void board_vehicle() {}
143 virtual void unboard_vehicle() {}
144
145 virtual void set_sound_volume(const int volume) = 0;
146 virtual int weight() const { return 1; }
147
148 virtual int mass() const = 0;
149
150 void draw_debug_rects() const;
151
152 virtual const_editor_entity_info_ptr editor_info() const { return const_editor_entity_info_ptr(); }
153
154 virtual entity_ptr clone() const { return entity_ptr(); }
155 virtual entity_ptr backup() const = 0;
156
157 virtual void generate_current(const entity& target, int* velocity_x, int* velocity_y) const;
158
159 virtual game_logic::const_formula_ptr get_event_handler(int key) const { return game_logic::const_formula_ptr(); }
160 virtual void set_event_handler(int, game_logic::const_formula_ptr f) { return; }
161
162 virtual void handle_event(const std::string& id, const formula_callable* context=NULL) {}
163 virtual void handle_event(int id, const formula_callable* context=NULL) {}
164
165 //function which returns true if this object can be 'interacted' with.
166 //i.e. if the player ovelaps with the object and presses up if they will
167 //talk to or enter the object.
168 virtual bool can_interact_with() const { return false; }
169
170 virtual std::string debug_description() const = 0;
171
172 //a function call which tells us to get any references to other entities
173 //that we hold, and map them according to the mapping given. This is useful
174 //when we back up an entire level and want to make references match.
175 virtual void map_entities(const std::map<entity_ptr, entity_ptr>& m) {}
176
177 void add_scheduled_command(int cycle, variant cmd);
178 variant get_scheduled_command(int cycle);
179
180 virtual void save_game() {}
181
182 virtual entity_ptr driver() { return entity_ptr(); }
183 virtual const_entity_ptr driver() const { return const_entity_ptr(); }
184
185 virtual void move_to_standing(level& lvl) {}
186 virtual int hitpoints() const { return 1; }
187 virtual int max_hitpoints() const { return 1; }
188 virtual int num_powerups() const { return 0; }
189
190 virtual void get_powerup(const std::string& id) {}
191 virtual void get_powerup(const_powerup_ptr powerup) {}
192 virtual void remove_powerup() {}
193 virtual int remove_powerup(const_powerup_ptr powerup) { return 0; }
194 virtual const std::vector<const_powerup_ptr>& powerups() const;
195 virtual const std::vector<const_powerup_ptr>& abilities() const;
196
197 //function to perform preloading of a powerup so it'll be ready to apply
198 //to this type of object when needed
199 virtual void preload_powerup(const_powerup_ptr powerup) {}
200 virtual bool is_powerup_loaded(const_powerup_ptr powerup) const { return true; }
201
202 void set_control_status(const std::string& key, bool value);
203 void set_control_status(controls::CONTROL_ITEM ctrl, bool value) { controls_[ctrl] = value; }
204 void clear_control_status() { for(int n = 0; n != controls::NUM_CONTROLS; ++n) { controls_[n] = false; } }
205
206 virtual bool enter() const { return false; }
207
208 virtual void set_invisible(bool value) {}
209 virtual void record_stats_movement() {}
210
211 virtual entity_ptr save_condition() const { return entity_ptr(); }
212 virtual void respawn_player() {}
213
214 virtual int current_animation_id() const { return 0; }
215
216 virtual void set_level(level* lvl) {}
217
218 unsigned int solid_dimensions() const { return solid_dimensions_; }
219 unsigned int collide_dimensions() const { return collide_dimensions_; }
220
221 unsigned int weak_solid_dimensions() const { return weak_solid_dimensions_; }
222 unsigned int weak_collide_dimensions() const { return weak_collide_dimensions_; }
223
224 void set_attached_objects(const std::vector<entity_ptr>& v);
225
226 virtual bool allow_level_collisions() const { return false; }
227
228 protected:
229
230 virtual const solid_info* calculate_solid() const = 0;
231 virtual const solid_info* calculate_platform() const = 0;
232 void calculate_solid_rect();
233
234 bool control_status(controls::CONTROL_ITEM ctrl) const { return controls_[ctrl]; }
235 void read_controls(int cycle);
236
237 void set_current_generator(current_generator* generator);
238
239 void set_respawn(bool value) { respawn_ = value; }
240
241 //move the entity by a number of centi pixels. Returns true if its
242 //position is changed.
243 bool move_centipixels(int dx, int dy);
244
245 void set_solid_dimensions(unsigned int dim, unsigned int weak) { solid_dimensions_ = dim; weak_solid_dimensions_ = dim|weak; }
246 void set_collide_dimensions(unsigned int dim, unsigned int weak) { collide_dimensions_ = dim; weak_collide_dimensions_ = dim|weak; }
247
248 const std::vector<entity_ptr>& attached_objects() const { return attached_objects_; }
249
250 private:
251 virtual void control(const level& lvl) = 0;
252
253 wml::node_ptr serialize_to_wml() const { return write(); }
254
255 std::string label_;
256
257 int x_, y_;
258
259 int prev_feet_x_, prev_feet_y_;
260
261 bool face_right_;
262 bool upside_down_;
263
264 //the entity group the entity is in.
265 int group_;
266
267 int id_;
268
269 bool respawn_;
270
271 unsigned int solid_dimensions_, collide_dimensions_;
272 unsigned int weak_solid_dimensions_, weak_collide_dimensions_;
273
274 current_generator_ptr current_generator_;
275
276 typedef std::pair<int, variant> ScheduledCommand;
277 std::vector<ScheduledCommand> scheduled_commands_;
278
279 bool controls_[controls::NUM_CONTROLS];
280
281 //attached objects are objects which are also drawn with this object.
282 //attached objects should generally NOT be present in the level, and are
283 //NOT processed independently of this object.
284 std::vector<entity_ptr> attached_objects_;
285
286 //caches of commonly queried rects.
287 rect solid_rect_, frame_rect_, platform_rect_;
288 const solid_info* solid_;
289 const solid_info* platform_;
290 };
291
292 #endif
0 #ifndef ENTITY_FWD_HPP_INCLUDED
1 #define ENTITY_FWD_HPP_INCLUDED
2
3 #include <boost/intrusive_ptr.hpp>
4
5 class entity;
6
7 typedef boost::intrusive_ptr<entity> entity_ptr;
8 typedef boost::intrusive_ptr<const entity> const_entity_ptr;
9
10 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include "filesystem.hpp"
13
14 #include <fstream>
15 #include <sstream>
16
17 // Include files for opendir(3), readdir(3), etc.
18 // These files may vary from platform to platform,
19 // since these functions are NOT ANSI-conforming functions.
20 // They may have to be altered to port to new platforms
21 #include <sys/types.h>
22
23 //for mkdir
24 #include <sys/stat.h>
25 #include <sys/types.h>
26
27 #ifdef __APPLE__
28 #include <CoreFoundation/CoreFoundation.h>
29 #endif
30
31 #ifdef _WIN32
32
33 /* /////////////////////////////////////////////////////////////////////////
34 * This code swiped from dirent.c in the unixem library, version 1.7.3.
35 * See http://synesis.com.au/software/unixem.html for full sources.
36 * It's under BSD license.
37 */
38
39 #include <direct.h>
40 #include <io.h>
41 #include <errno.h>
42 #include <stdlib.h>
43 #include <windows.h>
44
45
46 /* /////////////////////////////////////////////////////////////////////////
47 * Compiler differences
48 */
49
50 #if defined(__BORLANDC__)
51 # define DIRENT_PROVIDED_BY_COMPILER
52 #elif defined(__DMC__)
53 # define DIRENT_PROVIDED_BY_COMPILER
54 #elif defined(__GNUC__)
55 # define DIRENT_PROVIDED_BY_COMPILER
56 #elif defined(__INTEL_COMPILER)
57 #elif defined(_MSC_VER)
58 #elif defined(__MWERKS__)
59 #elif defined(__WATCOMC__)
60 #else
61 # error Compiler not discriminated
62 #endif /* compiler */
63
64 #if defined(DIRENT_PROVIDED_BY_COMPILER)
65 #include <dirent.h>
66 #else
67
68 /* ////////////////////////////////////////////////////////////////////// */
69
70 #include <stddef.h>
71
72 #ifndef NAME_MAX
73 # define NAME_MAX (260)
74 #endif /* !NAME_MAX */
75
76 struct dirent
77 {
78 char d_name[NAME_MAX + 1]; /*!< file name (null-terminated) */
79 int d_mode;
80 };
81
82 struct DIR
83 {
84 char directory[_MAX_DIR+1]; /* . */
85 WIN32_FIND_DATAA find_data; /* The Win32 FindFile data. */
86 HANDLE hFind; /* The Win32 FindFile handle. */
87 struct dirent dirent; /* The handle's entry. */
88 };
89
90 #ifndef FILE_ATTRIBUTE_ERROR
91 # define FILE_ATTRIBUTE_ERROR (0xFFFFFFFF)
92 #endif /* FILE_ATTRIBUTE_ERROR */
93
94 /* /////////////////////////////////////////////////////////////////////////
95 * Helper functions
96 */
97
98 static HANDLE dirent__findfile_directory(char const *name, LPWIN32_FIND_DATAA data)
99 {
100 char search_spec[_MAX_PATH +1];
101
102 // Simply add the *.*, ensuring the path separator is included.
103 (void)lstrcpyA(search_spec, name);
104 if( '\\' != search_spec[lstrlenA(search_spec) - 1] &&
105 '/' != search_spec[lstrlenA(search_spec) - 1])
106 {
107 (void)lstrcatA(search_spec, "\\*.*");
108 }
109 else
110 {
111 (void)lstrcatA(search_spec, "*.*");
112 }
113
114 return FindFirstFileA(search_spec, data);
115 }
116
117 /* /////////////////////////////////////////////////////////////////////////
118 * API functions
119 */
120
121 DIR *opendir(char const *name)
122 {
123 DIR *result = NULL;
124 DWORD dwAttr;
125
126 // Must be a valid name
127 if( !name ||
128 !*name ||
129 (dwAttr = GetFileAttributes(name)) == 0xFFFFFFFF)
130 {
131 errno = ENOENT;
132 }
133 // Must be a directory
134 else if(!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
135 {
136 errno = ENOTDIR;
137 }
138 else
139 {
140 result = (DIR*)malloc(sizeof(DIR));
141
142 if(result == NULL)
143 {
144 errno = ENOMEM;
145 }
146 else
147 {
148 result->hFind=dirent__findfile_directory(name, &result->find_data);
149
150 if(result->hFind == INVALID_HANDLE_VALUE)
151 {
152 free(result);
153
154 result = NULL;
155 }
156 else
157 {
158 // Save the directory, in case of rewind.
159 (void)lstrcpyA(result->directory, name);
160 (void)lstrcpyA(result->dirent.d_name, result->find_data.cFileName);
161 result->dirent.d_mode = (int)result->find_data.dwFileAttributes;
162 }
163 }
164 }
165
166 return result;
167 }
168
169 int closedir(DIR *dir)
170 {
171 int ret;
172
173 if(dir == NULL)
174 {
175 errno = EBADF;
176
177 ret = -1;
178 }
179 else
180 {
181 // Close the search handle, if not already done.
182 if(dir->hFind != INVALID_HANDLE_VALUE)
183 {
184 (void)FindClose(dir->hFind);
185 }
186
187 free(dir);
188
189 ret = 0;
190 }
191
192 return ret;
193 }
194
195 struct dirent *readdir(DIR *dir)
196 {
197 // The last find exhausted the matches, so return NULL.
198 if(dir->hFind == INVALID_HANDLE_VALUE)
199 {
200 if(FILE_ATTRIBUTE_ERROR == dir->find_data.dwFileAttributes)
201 {
202 errno = EBADF;
203 }
204 else
205 {
206 dir->find_data.dwFileAttributes = FILE_ATTRIBUTE_ERROR;
207 }
208
209 return NULL;
210 }
211 else
212 {
213 // Copy the result of the last successful match to dirent.
214 (void)lstrcpyA(dir->dirent.d_name, dir->find_data.cFileName);
215
216 // Attempt the next match.
217 if(!FindNextFileA(dir->hFind, &dir->find_data))
218 {
219 // Exhausted all matches, so close and null the handle.
220 (void)FindClose(dir->hFind);
221 dir->hFind = INVALID_HANDLE_VALUE;
222 }
223
224 return &dir->dirent;
225 }
226 }
227
228 /*
229 * Microsoft C uses _stat instead of stat,
230 * for both the function name and the structure name.
231 * See <http://svn.ghostscript.com:8080/ghostscript/trunk/gs/src/stat_.h>
232 */
233 #ifdef _MSC_VER
234 # define stat _stat
235 namespace {
236 typedef int mode_t;
237 }
238 #endif
239
240 #ifndef S_IFMT
241 #define S_IFMT (S_IFDIR|S_IFREG)
242 #endif
243 #ifndef S_ISREG
244 #define S_ISREG(x) (((x) & S_IFMT) == S_IFREG)
245 #endif
246 #ifndef S_ISDIR
247 #define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
248 #endif
249
250 #endif /* !DIRENT_PROVIDED_BY_COMPILER */
251
252 #define mkdir(a,b) (_mkdir(a))
253
254 #else /* !_WIN32 */
255
256 #include <unistd.h>
257
258 #include <dirent.h>
259
260 #endif /* !_WIN32 */
261
262 #ifdef __BEOS__
263 #include <Directory.h>
264 #include <FindDirectory.h>
265 #include <Path.h>
266 BPath be_path;
267 #endif
268
269 // for getenv
270 #include <cstdlib>
271 #include <cerrno>
272 #include <cstring>
273 #include <algorithm>
274 #include <fstream>
275 #include <iostream>
276 #include <iomanip>
277 #include <sstream>
278 #include <set>
279
280 namespace sys
281 {
282
283 namespace {
284 #ifdef HAVE_CONFIG_H
285 const std::string data_dir=DATADIR ;
286 const bool have_datadir = true;
287 #else
288 const std::string data_dir="";
289 const bool have_datadir = false;
290 #endif
291
292 const mode_t AccessMode = 00770;
293 }
294
295 void get_files_in_dir(const std::string& directory,
296 std::vector<std::string>* files,
297 std::vector<std::string>* dirs,
298 FILE_NAME_MODE mode)
299 {
300 struct stat st;
301
302 DIR* dir = opendir(directory.c_str());
303
304 if(dir == NULL) {
305 return;
306 }
307
308 struct dirent* entry;
309 while((entry = readdir(dir)) != NULL) {
310 if(entry->d_name[0] == '.')
311 continue;
312 #ifdef __APPLE__
313 // HFS Mac OS X decomposes filenames using combining unicode characters.
314 // Try to get the precomposed form.
315 char basename[MAXNAMLEN+1];
316 CFStringRef cstr = CFStringCreateWithCString(NULL,
317 entry->d_name,
318 kCFStringEncodingUTF8);
319 CFMutableStringRef mut_str = CFStringCreateMutableCopy(NULL,
320 0, cstr);
321 CFStringNormalize(mut_str, kCFStringNormalizationFormC);
322 CFStringGetCString(mut_str,
323 basename,sizeof(basename)-1,
324 kCFStringEncodingUTF8);
325 CFRelease(cstr);
326 CFRelease(mut_str);
327 #else
328 // generic Unix
329 char *basename = entry->d_name;
330 #endif /* !APPLE */
331
332 std::string fullname;
333 if (directory.empty() || directory[directory.size()-1] == '/'
334 #ifdef __AMIGAOS4__
335 || (directory[directory.size()-1]==':')
336 #endif /* __AMIGAOS4__ */
337 )
338 fullname = directory + basename;
339 else
340 fullname = (directory + "/") + basename;
341
342 if (::stat(fullname.c_str(), &st) != -1) {
343 if (S_ISREG(st.st_mode)) {
344 if (files != NULL) {
345 if (mode == ENTIRE_FILE_PATH)
346 files->push_back(fullname);
347 else
348 files->push_back(basename);
349 }
350 } else if (S_ISDIR(st.st_mode)) {
351 if (dirs != NULL) {
352 if (mode == ENTIRE_FILE_PATH)
353 dirs->push_back(fullname);
354 else
355 dirs->push_back(basename);
356 }
357 }
358 }
359 }
360
361 closedir(dir);
362
363 if(files != NULL)
364 std::sort(files->begin(),files->end());
365
366 if (dirs != NULL)
367 std::sort(dirs->begin(),dirs->end());
368 }
369
370 void get_unique_filenames_under_dir(const std::string& dir,
371 std::map<std::string, std::string>* file_map)
372 {
373 if(dir.size() > 1024) {
374 return;
375 }
376
377 std::vector<std::string> files;
378 std::vector<std::string> dirs;
379 get_files_in_dir(dir, &files, &dirs);
380 for(std::vector<std::string>::const_iterator i = files.begin();
381 i != files.end(); ++i) {
382 (*file_map)[*i] = dir + "/" + *i;
383 }
384
385 for(std::vector<std::string>::const_iterator i = dirs.begin();
386 i != dirs.end(); ++i) {
387 get_unique_filenames_under_dir(dir + "/" + *i, file_map);
388 }
389 }
390
391 std::string get_dir(const std::string& dir_path)
392 {
393 DIR* dir = opendir(dir_path.c_str());
394 if(dir == NULL) {
395 const int res = mkdir(dir_path.c_str(),AccessMode);
396 if(res == 0) {
397 dir = opendir(dir_path.c_str());
398 } else {
399 std::cerr << "could not open or create directory: " << dir_path << '\n';
400 }
401 }
402
403 if(dir == NULL)
404 return "";
405
406 closedir(dir);
407
408 return dir_path;
409 }
410
411 std::string get_user_data_dir()
412 {
413 #ifdef _WIN32
414
415 static bool inited_dirs = false;
416
417 if(!inited_dirs) {
418 _mkdir("userdata");
419 _mkdir("userdata/saves");
420 inited_dirs = true;
421 }
422
423 char buf[256];
424 const char* const res = getcwd(buf,sizeof(buf));
425
426 if(res != NULL) {
427 std::string cur_path(res);
428 std::replace(cur_path.begin(),cur_path.end(),'\\','/');
429 return cur_path + "/userdata";
430 } else {
431 return "userdata";
432 }
433 #elif defined(__BEOS__)
434 if (be_path.InitCheck() != B_OK) {
435 BPath tpath;
436 if (find_directory(B_USER_SETTINGS_DIRECTORY, &be_path, true) == B_OK) {
437 be_path.Append("frogatto");
438 } else {
439 be_path.SetTo("/boot/home/config/settings/frogatto");
440 }
441 tpath = be_path;
442 create_directory(tpath.Path(), 0775);
443 }
444 return be_path.Path();
445 #else
446
447 #ifndef __AMIGAOS4__
448 static const char* const current_dir = ".";
449 const char* home_str = getenv("HOME");
450 #else
451 static const char* const current_dir = " ";
452 const char* home_str = "PROGDIR:";
453 #endif
454 if(home_str == NULL)
455 home_str = current_dir;
456
457 const std::string home(home_str);
458
459 #ifndef PREFERENCES_DIR
460 #define PREFERENCES_DIR ".silvertree"
461 #endif
462
463 #ifndef __AMIGAOS4__
464 const std::string dir_path = home + std::string("/") + PREFERENCES_DIR;
465 #else
466 const std::string dir_path = home + PREFERENCES_DIR;
467 #endif
468 DIR* dir = opendir(dir_path.c_str());
469 if(dir == NULL) {
470 const int res = mkdir(dir_path.c_str(),AccessMode);
471
472 // Also create the maps directory
473 mkdir((dir_path + "/editor").c_str(),AccessMode);
474 mkdir((dir_path + "/saves").c_str(),AccessMode);
475 if(res == 0) {
476 dir = opendir(dir_path.c_str());
477 } else {
478 std::cerr << "could not open or create directory: " << dir_path << '\n';
479 }
480 }
481
482 if(dir == NULL)
483 return "";
484
485 closedir(dir);
486
487 return dir_path;
488 #endif
489 }
490
491 std::string get_saves_dir()
492 {
493 const std::string dir_path = get_user_data_dir() + "/saves";
494 return get_dir(dir_path);
495 }
496
497 bool do_file_exists(const std::string& fname)
498 {
499 std::ifstream file(fname.c_str(), std::ios_base::binary);
500 if(file.rdstate() != 0) {
501 return false;
502 }
503
504 file.close();
505 return true;
506 }
507
508 std::string find_file(const std::string& fname)
509 {
510 if(do_file_exists(fname)) {
511 return fname;
512 }
513 if(have_datadir) {
514 std::string data_fname = data_dir + "/" + fname;
515 if(do_file_exists(data_fname)) {
516 return data_fname;
517 }
518 }
519 return fname;
520 }
521
522 bool file_exists(const std::string& name)
523 {
524 return do_file_exists(find_file(name));
525 }
526
527 std::string read_file(const std::string& name)
528 {
529 std::string fname = find_file(name);
530 std::ifstream file(fname.c_str(),std::ios_base::binary);
531 std::stringstream ss;
532 ss << file.rdbuf();
533 return ss.str();
534 }
535
536 void write_file(const std::string& fname, const std::string& data)
537 {
538 std::ofstream file(fname.c_str(),std::ios_base::binary);
539 file << data;
540 }
541
542 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef FILESYSTEM_HPP_INCLUDED
13 #define FILESYSTEM_HPP_INCLUDED
14
15 #include <map>
16 #include <string>
17 #include <vector>
18
19 namespace sys
20 {
21
22 enum FILE_NAME_MODE { ENTIRE_FILE_PATH, FILE_NAME_ONLY };
23
24 //! Populates 'files' with all the files and
25 //! 'dirs' with all the directories in dir.
26 //! If files or dirs are NULL they will not be used.
27 //!
28 //! Mode determines whether the entire path or just the filename is retrieved.
29 void get_files_in_dir(const std::string& dir,
30 std::vector<std::string>* files,
31 std::vector<std::string>* dirs=NULL,
32 FILE_NAME_MODE mode=FILE_NAME_ONLY);
33
34 //Function which given a directory, will recurse through all sub-directories,
35 //and find each distinct filename. It will fill the files map such that the
36 //keys are filenames and the values are the full path to the file.
37 void get_unique_filenames_under_dir(const std::string& dir,
38 std::map<std::string, std::string>* file_map);
39
40 //creates a dir if it doesn't exist and returns the path
41 std::string get_dir(const std::string& dir);
42 std::string get_user_data_dir();
43 std::string get_saves_dir();
44
45 std::string read_file(const std::string& fname);
46 void write_file(const std::string& fname, const std::string& data);
47
48 bool file_exists(const std::string& fname);
49 std::string find_file(const std::string& name);
50
51 }
52
53 #endif
0 #include <iostream>
1 #include <map>
2
3 #include "font.hpp"
4 #include "foreach.hpp"
5 #include "string_utils.hpp"
6 #include "surface.hpp"
7
8 /* This manages the TTF loading library, and allows you to use fonts.
9 The only thing one will normally need to use is render_text(), and possibly char_width(), char_height() if you need to know the size of the resulting text. */
10
11 namespace font {
12 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
13 class TTF_Font;
14 #endif
15
16 namespace {
17 const char* FontFile = "FreeMono.ttf";
18
19 std::map<int, TTF_Font*> font_table;
20
21 TTF_Font* get_font(int size)
22 {
23 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
24 TTF_Font*& font = font_table[size];
25 if(font == NULL) {
26 font = TTF_OpenFont(FontFile, size);
27 if(font == NULL) {
28 std::cerr << "Failed to open font: " << FontFile << "\n";
29 exit(0);
30 }
31 }
32
33 return font;
34 #else
35 return NULL;
36 #endif
37 }
38
39 struct cache_entry
40 {
41 std::string text;
42 SDL_Color color;
43 int size;
44 graphics::texture texture;
45 };
46
47 const int CacheSize = 4;
48
49 std::vector<cache_entry>& cache() {
50 static std::vector<cache_entry> instance(CacheSize);
51 return instance;
52 }
53
54 int cache_index = 0;
55
56 }
57
58 manager::manager()
59 {
60 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
61 const int res = TTF_Init();
62 if(res == -1) {
63 std::cerr << "Could not initialize ttf\n";
64 exit(0);
65 } else {
66 std::cerr << "initialized ttf\n";
67 }
68 #endif
69 }
70
71 manager::~manager()
72 {
73 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
74 TTF_Quit();
75 #endif
76 }
77
78 graphics::texture render_text(const std::string& text,
79 const SDL_Color& color, int size)
80 {
81 for(int n = 0; n != CacheSize; ++n) {
82 if(text == cache()[n].text && memcmp(&color, &cache()[n].color, sizeof(color)) == 0 && size == cache()[n].size) {
83 return cache()[n].texture;
84 }
85 }
86 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
87 TTF_Font* font = get_font(size);
88
89 graphics::surface s;
90 if(std::find(text.begin(), text.end(), '\n') == text.end()) {
91 s = graphics::surface(TTF_RenderUTF8_Blended(font, text.c_str(), color));
92 } else {
93 std::vector<graphics::surface> parts;
94 std::vector<std::string> lines = util::split(text, '\n');
95 int height = 0, width = 0;
96 foreach(const std::string& line, lines) {
97 parts.push_back(graphics::surface(TTF_RenderUTF8_Blended(font, line.c_str(), color)));
98 if(parts.back()->w > width) {
99 width = parts.back()->w;
100 }
101
102 height += parts.back()->h;
103 }
104
105 const SDL_PixelFormat* f = parts.front()->format;
106 s = graphics::surface(SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, f->BitsPerPixel, f->Rmask, f->Gmask, f->Bmask, f->Amask));
107 int ypos = 0;
108 foreach(graphics::surface part, parts) {
109 SDL_Rect rect = {0, ypos, part->w, part->h};
110 SDL_SetAlpha(part.get(), 0, SDL_ALPHA_OPAQUE);
111 SDL_BlitSurface(part.get(), NULL, s.get(), &rect);
112 ypos += part->h;
113 }
114 }
115 #else
116 graphics::surface s;
117 #endif
118 graphics::texture res = graphics::texture::get_no_cache(s);
119 cache_index = (cache_index+1)%CacheSize;
120 cache()[cache_index].text = text;
121 cache()[cache_index].color = color;
122 cache()[cache_index].size = size;
123 cache()[cache_index].texture = res;
124 return res;
125 }
126
127 int char_width(int size)
128 {
129 static std::map<int, int> size_cache;
130 int& width = size_cache[size];
131 if(width) {
132 return width;
133 }
134 SDL_Color color = {0, 0, 0, 0};
135 graphics::texture t(render_text("ABCDEFABCDEF", color, size));
136 width = t.width()/12;
137 return width;
138 }
139
140 int char_height(int size)
141 {
142 static std::map<int, int> size_cache;
143 int& height = size_cache[size];
144 if(height) {
145 return height;
146 }
147 SDL_Color color = {0, 0, 0, 0};
148 graphics::texture t(render_text("A", color, size));
149 height = t.height();
150 return height;
151 }
152
153 }
0 /*
1 Copyright (C) 2008 by David White <dave@whitevine.net>
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License version 2
5 or at your option any later version.
6 This program is distributed in the hope that it will be useful,
7 but WITHOUT ANY WARRANTY.
8
9 See the COPYING file for more details.
10 */
11 #ifndef FONT_HPP_INCLUDED
12 #define FONT_HPP_INCLUDED
13
14 #include <string>
15
16 #include "SDL.h"
17
18 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
19 #include "SDL_ttf.h"
20 #endif
21
22 #include "texture.hpp"
23
24 namespace font {
25
26 struct manager {
27 manager();
28 ~manager();
29 };
30
31 graphics::texture render_text(const std::string& text,
32 const SDL_Color& color, int size);
33
34 int char_width(int size);
35 int char_height(int size);
36
37 }
38
39 #endif
0 ///////////////////////////////////////////////////////////////////////////////
1 // foreach.hpp header file
2 //
3 // Copyright 2004 Eric Niebler.
4 // Distributed under the Boost Software License, Version 1.0. (See
5 // accompanying file LICENSE_1_0.txt or copy at
6 // http://www.boost.org/LICENSE_1_0.txt)
7 //
8
9 #ifndef BOOST_FOREACH
10
11 // MS compatible compilers support #pragma once
12 #if defined(_MSC_VER) && (_MSC_VER >= 1020)
13 # pragma once
14 #endif
15
16 #include <cstddef>
17 #include <utility> // for std::pair
18
19 #include <boost/config.hpp>
20 #include <boost/detail/workaround.hpp>
21
22 // Some compilers let us detect even const-qualified rvalues at compile-time
23 #if BOOST_WORKAROUND(BOOST_MSVC, >= 1310) \
24 || BOOST_WORKAROUND(__GNUC__, >= 4) \
25 || (BOOST_WORKAROUND(__GNUC__, == 3) && (__GNUC_MINOR__ >= 4))
26 # define BOOST_FOREACH_COMPILE_TIME_CONST_RVALUE_DETECTION
27 #else
28 // Some compilers allow temporaries to be bound to non-const references.
29 // These compilers make it impossible to for BOOST_FOREACH to detect
30 // temporaries and avoid reevaluation of the collection expression.
31 # if BOOST_WORKAROUND(BOOST_MSVC, <= 1300) \
32 || BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) \
33 || (BOOST_WORKAROUND(BOOST_INTEL_CXX_VERSION, <= 700) && defined(_MSC_VER)) \
34 || BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x570)) \
35 || BOOST_WORKAROUND(__DECCXX_VER, BOOST_TESTED_AT(60590042))
36 # define BOOST_FOREACH_NO_RVALUE_DETECTION
37 # endif
38 // Some compilers do not correctly implement the lvalue/rvalue conversion
39 // rules of the ternary conditional operator.
40 # if defined(BOOST_FOREACH_NO_RVALUE_DETECTION) \
41 || defined(BOOST_NO_SFINAE) \
42 || BOOST_WORKAROUND(BOOST_MSVC, BOOST_TESTED_AT(1400)) \
43 || BOOST_WORKAROUND(BOOST_INTEL_WIN, <= 810) \
44 || BOOST_WORKAROUND(__GNUC__, < 3) \
45 || (BOOST_WORKAROUND(__GNUC__, == 3) && (__GNUC_MINOR__ <= 2)) \
46 || (BOOST_WORKAROUND(__GNUC__, == 3) && (__GNUC_MINOR__ <= 3) && defined(__APPLE_CC__)) \
47 || BOOST_WORKAROUND(__IBMCPP__, BOOST_TESTED_AT(600)) \
48 || BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3206))
49 # define BOOST_FOREACH_NO_CONST_RVALUE_DETECTION
50 # else
51 # define BOOST_FOREACH_RUN_TIME_CONST_RVALUE_DETECTION
52 # endif
53 #endif
54
55 #include <boost/mpl/if.hpp>
56 #include <boost/mpl/logical.hpp>
57 #include <boost/mpl/eval_if.hpp>
58 #include <boost/noncopyable.hpp>
59 #include <boost/range/end.hpp>
60 #include <boost/range/begin.hpp>
61 #include <boost/range/result_iterator.hpp>
62 #include <boost/type_traits/is_array.hpp>
63 #include <boost/type_traits/is_const.hpp>
64 #include <boost/type_traits/is_base_and_derived.hpp>
65 #include <boost/iterator/iterator_traits.hpp>
66 #include <boost/utility/addressof.hpp>
67
68 #ifdef BOOST_FOREACH_RUN_TIME_CONST_RVALUE_DETECTION
69 # include <new>
70 # include <boost/aligned_storage.hpp>
71 # include <boost/utility/enable_if.hpp>
72 # include <boost/type_traits/remove_const.hpp>
73 #endif
74
75 // This must be at global scope, hence the uglified name
76 enum boost_foreach_argument_dependent_lookup_hack
77 {
78 boost_foreach_argument_dependent_lookup_hack_value
79 };
80
81 namespace boost
82 {
83
84 // forward declarations for iterator_range
85 template<typename T>
86 class iterator_range;
87
88 // forward declarations for sub_range
89 template<typename T>
90 class sub_range;
91
92 namespace foreach
93 {
94 ///////////////////////////////////////////////////////////////////////////////
95 // in_range
96 //
97 template<typename T>
98 inline std::pair<T, T> in_range(T begin, T end)
99 {
100 return std::make_pair(begin, end);
101 }
102
103 ///////////////////////////////////////////////////////////////////////////////
104 // boost::foreach::tag
105 //
106 typedef boost_foreach_argument_dependent_lookup_hack tag;
107
108 ///////////////////////////////////////////////////////////////////////////////
109 // boost::foreach::is_lightweight_proxy
110 // Specialize this for user-defined collection types if they are inexpensive to copy.
111 // This tells BOOST_FOREACH it can avoid the rvalue/lvalue detection stuff.
112 template<typename T>
113 struct is_lightweight_proxy
114 : boost::mpl::false_
115 {
116 };
117
118 ///////////////////////////////////////////////////////////////////////////////
119 // boost::foreach::is_noncopyable
120 // Specialize this for user-defined collection types if they cannot be copied.
121 // This also tells BOOST_FOREACH to avoid the rvalue/lvalue detection stuff.
122 template<typename T>
123 struct is_noncopyable
124 #ifndef BOOST_BROKEN_IS_BASE_AND_DERIVED
125 : boost::is_base_and_derived<boost::noncopyable, T>
126 #else
127 : boost::mpl::false_
128 #endif
129 {
130 };
131
132 } // namespace foreach
133
134 } // namespace boost
135
136 ///////////////////////////////////////////////////////////////////////////////
137 // boost_foreach_is_lightweight_proxy
138 // Another customization point for the is_lightweight_proxy optimization,
139 // this one works on legacy compilers. Overload boost_foreach_is_lightweight_proxy
140 // at the global namespace for your type.
141 template<typename T>
142 inline boost::foreach::is_lightweight_proxy<T> *
143 boost_foreach_is_lightweight_proxy(T *&, ...) { return 0; }
144
145 template<typename T>
146 inline boost::mpl::true_ *
147 boost_foreach_is_lightweight_proxy(std::pair<T, T> *&, boost::foreach::tag) { return 0; }
148
149 template<typename T>
150 inline boost::mpl::true_ *
151 boost_foreach_is_lightweight_proxy(boost::iterator_range<T> *&, boost::foreach::tag) { return 0; }
152
153 template<typename T>
154 inline boost::mpl::true_ *
155 boost_foreach_is_lightweight_proxy(boost::sub_range<T> *&, boost::foreach::tag) { return 0; }
156
157 template<typename T>
158 inline boost::mpl::true_ *
159 boost_foreach_is_lightweight_proxy(T **, boost::foreach::tag) { return 0; }
160
161 template<typename T, std::size_t N>
162 inline boost::mpl::false_ *
163 boost_foreach_is_lightweight_proxy(T (*)[N], boost::foreach::tag) { return 0; }
164
165 ///////////////////////////////////////////////////////////////////////////////
166 // boost_foreach_is_noncopyable
167 // Another customization point for the is_noncopyable trait,
168 // this one works on legacy compilers. Overload boost_foreach_is_noncopyable
169 // at the global namespace for your type.
170 template<typename T>
171 inline boost::foreach::is_noncopyable<T> *
172 boost_foreach_is_noncopyable(T *&, ...) { return 0; }
173
174 namespace boost
175 {
176
177 namespace foreach_detail_
178 {
179
180 ///////////////////////////////////////////////////////////////////////////////
181 // Define some utilities for assessing the properties of expressions
182 //
183 typedef char yes_type;
184 typedef char (&no_type)[2];
185 yes_type is_true(boost::mpl::true_ *);
186 no_type is_true(boost::mpl::false_ *);
187
188 // Extracts the desired property from the expression without evaluating it
189 #define BOOST_FOREACH_PROTECT(expr) \
190 (static_cast<boost::mpl::bool_<1 == sizeof(boost::foreach_detail_::is_true(expr))> *>(0))
191
192 template<typename Bool1, typename Bool2>
193 inline boost::mpl::and_<Bool1, Bool2> *and_(Bool1 *, Bool2 *) { return 0; }
194
195 template<typename Bool1, typename Bool2, typename Bool3>
196 inline boost::mpl::and_<Bool1, Bool2, Bool3> *and_(Bool1 *, Bool2 *, Bool3 *) { return 0; }
197
198 template<typename Bool1, typename Bool2>
199 inline boost::mpl::or_<Bool1, Bool2> *or_(Bool1 *, Bool2 *) { return 0; }
200
201 template<typename Bool1, typename Bool2, typename Bool3>
202 inline boost::mpl::or_<Bool1, Bool2, Bool3> *or_(Bool1 *, Bool2 *, Bool3 *) { return 0; }
203
204 template<typename Bool>
205 inline boost::mpl::not_<Bool> *not_(Bool *) { return 0; }
206
207 template<typename T>
208 inline boost::mpl::false_ *is_rvalue(T &, int) { return 0; }
209
210 template<typename T>
211 inline boost::mpl::true_ *is_rvalue(T const &, ...) { return 0; }
212
213 template<typename T>
214 inline boost::is_array<T> *is_array(T const &) { return 0; }
215
216 template<typename T>
217 inline boost::is_const<T> *is_const(T &) { return 0; }
218
219 #ifndef BOOST_FOREACH_NO_RVALUE_DETECTION
220 template<typename T>
221 inline boost::mpl::true_ *is_const(T const &) { return 0; }
222 #endif
223
224 ///////////////////////////////////////////////////////////////////////////////
225 // auto_any_t/auto_any
226 // General utility for putting an object of any type into automatic storage
227 struct auto_any_base
228 {
229 // auto_any_base must evaluate to false in boolean context so that
230 // they can be declared in if() statements.
231 operator bool() const
232 {
233 return false;
234 }
235 };
236
237 template<typename T>
238 struct auto_any : auto_any_base
239 {
240 auto_any(T const &t)
241 : item(t)
242 {
243 }
244
245 // temporaries of type auto_any will be bound to const auto_any_base
246 // references, but we still want to be able to mutate the stored
247 // data, so declare it as mutable.
248 mutable T item;
249 };
250
251 typedef auto_any_base const &auto_any_t;
252
253 template<typename T, typename C>
254 inline BOOST_DEDUCED_TYPENAME boost::mpl::if_<C, T const, T>::type &auto_any_cast(auto_any_t a)
255 {
256 return static_cast<auto_any<T> const &>(a).item;
257 }
258
259 typedef boost::mpl::true_ const_;
260
261 ///////////////////////////////////////////////////////////////////////////////
262 // type2type
263 //
264 template<typename T, typename C = boost::mpl::false_>
265 struct type2type
266 : boost::mpl::if_<C, T const, T>
267 {
268 };
269
270 template<typename T, typename C = boost::mpl::false_>
271 struct foreach_iterator
272 {
273 typedef BOOST_DEDUCED_TYPENAME boost::mpl::eval_if<
274 C
275 , range_const_iterator<T>
276 , range_iterator<T>
277 >::type type;
278 };
279
280 template<typename T, typename C = boost::mpl::false_>
281 struct foreach_reference
282 : iterator_reference<BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type>
283 {
284 };
285
286 ///////////////////////////////////////////////////////////////////////////////
287 // encode_type
288 //
289 template<typename T>
290 inline type2type<T> *encode_type(T &, boost::mpl::false_ *) { return 0; }
291
292 template<typename T>
293 inline type2type<T, const_> *encode_type(T const &, boost::mpl::true_ *) { return 0; }
294
295 ///////////////////////////////////////////////////////////////////////////////
296 // set_false
297 //
298 inline bool set_false(bool &b) { return b = false; }
299
300 ///////////////////////////////////////////////////////////////////////////////
301 // to_ptr
302 //
303 template<typename T>
304 inline T *&to_ptr(T const &)
305 {
306 static T *t = 0;
307 return t;
308 }
309
310 // Borland needs a little extra help with arrays
311 #if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564))
312 template<typename T,std::size_t N>
313 inline T (*to_ptr(T (&t)[N]))[N] { return 0; }
314 #endif
315
316 ///////////////////////////////////////////////////////////////////////////////
317 // derefof
318 //
319 template<typename T>
320 inline T &derefof(T *t)
321 {
322 // This is a work-around for a compiler bug in Borland. If T* is a pointer to array type U(*)[N],
323 // then dereferencing it results in a U* instead of U(&)[N]. The cast forces the issue.
324 return reinterpret_cast<T &>(
325 *const_cast<char *>(
326 reinterpret_cast<char const volatile *>(t)
327 )
328 );
329 }
330
331 #ifdef BOOST_FOREACH_COMPILE_TIME_CONST_RVALUE_DETECTION
332 ///////////////////////////////////////////////////////////////////////////////
333 // Detect at compile-time whether an expression yields an rvalue or
334 // an lvalue. This is rather non-standard, but some popular compilers
335 // accept it.
336 ///////////////////////////////////////////////////////////////////////////////
337
338 ///////////////////////////////////////////////////////////////////////////////
339 // rvalue_probe
340 //
341 template<typename T>
342 struct rvalue_probe
343 {
344 // can't ever return an array by value
345 typedef BOOST_DEDUCED_TYPENAME boost::mpl::if_<boost::is_array<T>, int, T>::type value_type;
346 operator value_type();
347 operator T &() const;
348 };
349
350 template<typename T>
351 rvalue_probe<T> const make_probe(T const &t);
352
353 # define BOOST_FOREACH_IS_RVALUE(COL) \
354 boost::foreach_detail_::and_( \
355 boost::foreach_detail_::not_(boost::foreach_detail_::is_array(COL)) \
356 , BOOST_FOREACH_PROTECT(boost::foreach_detail_::is_rvalue( \
357 (true ? boost::foreach_detail_::make_probe(COL) : (COL)), 0)))
358
359 #elif defined(BOOST_FOREACH_RUN_TIME_CONST_RVALUE_DETECTION)
360 ///////////////////////////////////////////////////////////////////////////////
361 // Detect at run-time whether an expression yields an rvalue
362 // or an lvalue. This is 100% standard C++, but not all compilers
363 // accept it. Also, it causes FOREACH to break when used with non-
364 // copyable collection types.
365 ///////////////////////////////////////////////////////////////////////////////
366
367 ///////////////////////////////////////////////////////////////////////////////
368 // rvalue_probe
369 //
370 template<typename T>
371 struct rvalue_probe
372 {
373 rvalue_probe(T &t, bool &b)
374 : value(t)
375 , is_rvalue(b)
376 {
377 }
378
379 // can't ever return an array by value
380 typedef BOOST_DEDUCED_TYPENAME boost::mpl::if_<boost::is_array<T>, int, T>::type value_type;
381 operator value_type()
382 {
383 this->is_rvalue = true;
384 return this->value;
385 }
386
387 operator T &() const
388 {
389 return this->value;
390 }
391
392 private:
393 T &value;
394 bool &is_rvalue;
395 };
396
397 template<typename T>
398 rvalue_probe<T> make_probe(T &t, bool &b) { return rvalue_probe<T>(t, b); }
399
400 template<typename T>
401 rvalue_probe<T const> make_probe(T const &t, bool &b) { return rvalue_probe<T const>(t, b); }
402
403 ///////////////////////////////////////////////////////////////////////////////
404 // simple_variant
405 // holds either a T or a T const*
406 template<typename T>
407 struct simple_variant
408 {
409 simple_variant(T const *t)
410 : is_rvalue(false)
411 {
412 *static_cast<T const **>(this->data.address()) = t;
413 }
414
415 simple_variant(T const &t)
416 : is_rvalue(true)
417 {
418 ::new(this->data.address()) T(t);
419 }
420
421 simple_variant(simple_variant const &that)
422 : is_rvalue(that.is_rvalue)
423 {
424 if(this->is_rvalue)
425 ::new(this->data.address()) T(*that.get());
426 else
427 *static_cast<T const **>(this->data.address()) = that.get();
428 }
429
430 ~simple_variant()
431 {
432 if(this->is_rvalue)
433 this->get()->~T();
434 }
435
436 T const *get() const
437 {
438 if(this->is_rvalue)
439 return static_cast<T const *>(this->data.address());
440 else
441 return *static_cast<T const * const *>(this->data.address());
442 }
443
444 private:
445 enum size_type { size = sizeof(T) > sizeof(T*) ? sizeof(T) : sizeof(T*) };
446 simple_variant &operator =(simple_variant const &);
447 bool const is_rvalue;
448 aligned_storage<size> data;
449 };
450
451 // If the collection is an array or is noncopyable, it must be an lvalue.
452 // If the collection is a lightweight proxy, treat it as an rvalue
453 // BUGBUG what about a noncopyable proxy?
454 template<typename LValue, typename IsProxy>
455 inline BOOST_DEDUCED_TYPENAME boost::enable_if<boost::mpl::or_<LValue, IsProxy>, IsProxy>::type *
456 should_copy_impl(LValue *, IsProxy *, bool *)
457 {
458 return 0;
459 }
460
461 // Otherwise, we must determine at runtime whether it's an lvalue or rvalue
462 inline bool *
463 should_copy_impl(boost::mpl::false_ *, boost::mpl::false_ *, bool *is_rvalue)
464 {
465 return is_rvalue;
466 }
467
468 #endif
469
470 ///////////////////////////////////////////////////////////////////////////////
471 // contain
472 //
473 template<typename T>
474 inline auto_any<T> contain(T const &t, boost::mpl::true_ *) // rvalue
475 {
476 return t;
477 }
478
479 template<typename T>
480 inline auto_any<T *> contain(T &t, boost::mpl::false_ *) // lvalue
481 {
482 // Cannot seem to get sunpro to handle addressof() with array types.
483 #if BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x570))
484 return &t;
485 #else
486 return boost::addressof(t);
487 #endif
488 }
489
490 #ifdef BOOST_FOREACH_RUN_TIME_CONST_RVALUE_DETECTION
491 template<typename T>
492 auto_any<simple_variant<T> >
493 contain(T const &t, bool *rvalue)
494 {
495 return *rvalue ? simple_variant<T>(t) : simple_variant<T>(&t);
496 }
497 #endif
498
499 /////////////////////////////////////////////////////////////////////////////
500 // begin
501 //
502 template<typename T, typename C>
503 inline auto_any<BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type>
504 begin(auto_any_t col, type2type<T, C> *, boost::mpl::true_ *) // rvalue
505 {
506 return boost::begin(auto_any_cast<T, C>(col));
507 }
508
509 template<typename T, typename C>
510 inline auto_any<BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type>
511 begin(auto_any_t col, type2type<T, C> *, boost::mpl::false_ *) // lvalue
512 {
513 typedef BOOST_DEDUCED_TYPENAME type2type<T, C>::type type;
514 typedef BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type iterator;
515 return iterator(boost::begin(derefof(auto_any_cast<type *, boost::mpl::false_>(col))));
516 }
517
518 #ifdef BOOST_FOREACH_RUN_TIME_CONST_RVALUE_DETECTION
519 template<typename T>
520 auto_any<BOOST_DEDUCED_TYPENAME foreach_iterator<T, const_>::type>
521 begin(auto_any_t col, type2type<T, const_> *, bool *)
522 {
523 return boost::begin(*auto_any_cast<simple_variant<T>, boost::mpl::false_>(col).get());
524 }
525 #endif
526
527 ///////////////////////////////////////////////////////////////////////////////
528 // end
529 //
530 template<typename T, typename C>
531 inline auto_any<BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type>
532 end(auto_any_t col, type2type<T, C> *, boost::mpl::true_ *) // rvalue
533 {
534 return boost::end(auto_any_cast<T, C>(col));
535 }
536
537 template<typename T, typename C>
538 inline auto_any<BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type>
539 end(auto_any_t col, type2type<T, C> *, boost::mpl::false_ *) // lvalue
540 {
541 typedef BOOST_DEDUCED_TYPENAME type2type<T, C>::type type;
542 typedef BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type iterator;
543 return iterator(boost::end(derefof(auto_any_cast<type *, boost::mpl::false_>(col))));
544 }
545
546 #ifdef BOOST_FOREACH_RUN_TIME_CONST_RVALUE_DETECTION
547 template<typename T>
548 auto_any<BOOST_DEDUCED_TYPENAME foreach_iterator<T, const_>::type>
549 end(auto_any_t col, type2type<T, const_> *, bool *)
550 {
551 return boost::end(*auto_any_cast<simple_variant<T>, boost::mpl::false_>(col).get());
552 }
553 #endif
554
555 #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
556 template<typename T, typename C>
557 inline auto_any<int>
558 end(auto_any_t col, type2type<T *, C> *, boost::mpl::true_ *) // null-terminated C-style strings
559 {
560 return 0; // not used
561 }
562 #endif
563
564 ///////////////////////////////////////////////////////////////////////////////
565 // done
566 //
567 template<typename T, typename C>
568 inline bool done(auto_any_t cur, auto_any_t end, type2type<T, C> *)
569 {
570 typedef BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type iter_t;
571 return auto_any_cast<iter_t, boost::mpl::false_>(cur) == auto_any_cast<iter_t, boost::mpl::false_>(end);
572 }
573
574 #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
575 template<typename T, typename C>
576 inline bool done(auto_any_t cur, auto_any_t, type2type<T *, C> *) // null-terminated C-style strings
577 {
578 return ! *auto_any_cast<T *, boost::mpl::false_>(cur);
579 }
580 #endif
581
582 ///////////////////////////////////////////////////////////////////////////////
583 // next
584 //
585 template<typename T, typename C>
586 inline void next(auto_any_t cur, type2type<T, C> *)
587 {
588 typedef BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type iter_t;
589 ++auto_any_cast<iter_t, boost::mpl::false_>(cur);
590 }
591
592 ///////////////////////////////////////////////////////////////////////////////
593 // deref
594 //
595 template<typename T, typename C>
596 inline BOOST_DEDUCED_TYPENAME foreach_reference<T, C>::type
597 deref(auto_any_t cur, type2type<T, C> *)
598 {
599 typedef BOOST_DEDUCED_TYPENAME foreach_iterator<T, C>::type iter_t;
600 return *auto_any_cast<iter_t, boost::mpl::false_>(cur);
601 }
602
603 } // namespace foreach_detail_
604 } // namespace boost
605
606 // A sneaky way to get the type of the collection without evaluating the expression
607 #define BOOST_FOREACH_TYPEOF(COL) \
608 (true ? 0 : boost::foreach_detail_::encode_type(COL, boost::foreach_detail_::is_const(COL)))
609
610 // returns true_* if the type is noncopyable
611 #define BOOST_FOREACH_IS_NONCOPYABLE(COL) \
612 boost_foreach_is_noncopyable( \
613 boost::foreach_detail_::to_ptr(COL) \
614 , boost_foreach_argument_dependent_lookup_hack_value)
615
616 // returns true_* if the type is a lightweight proxy (and is not noncopyable)
617 #define BOOST_FOREACH_IS_LIGHTWEIGHT_PROXY(COL) \
618 boost::foreach_detail_::and_( \
619 boost::foreach_detail_::not_(BOOST_FOREACH_IS_NONCOPYABLE(COL)) \
620 , boost_foreach_is_lightweight_proxy( \
621 boost::foreach_detail_::to_ptr(COL) \
622 , boost_foreach_argument_dependent_lookup_hack_value))
623
624 #ifdef BOOST_FOREACH_COMPILE_TIME_CONST_RVALUE_DETECTION
625 ///////////////////////////////////////////////////////////////////////////////
626 // R-values and const R-values supported here with zero runtime overhead
627 ///////////////////////////////////////////////////////////////////////////////
628
629 // No variable is needed to track the rvalue-ness of the collection expression
630 # define BOOST_FOREACH_PREAMBLE() \
631 /**/
632
633 // Evaluate the collection expression
634 # define BOOST_FOREACH_EVALUATE(COL) \
635 (COL)
636
637 # define BOOST_FOREACH_SHOULD_COPY(COL) \
638 (true ? 0 : boost::foreach_detail_::or_( \
639 BOOST_FOREACH_IS_RVALUE(COL) \
640 , BOOST_FOREACH_IS_LIGHTWEIGHT_PROXY(COL)))
641
642 #elif defined(BOOST_FOREACH_RUN_TIME_CONST_RVALUE_DETECTION)
643 ///////////////////////////////////////////////////////////////////////////////
644 // R-values and const R-values supported here
645 ///////////////////////////////////////////////////////////////////////////////
646
647 // Declare a variable to track the rvalue-ness of the collection expression
648 # define BOOST_FOREACH_PREAMBLE() \
649 if (bool _foreach_is_rvalue = false) {} else
650
651 // Evaluate the collection expression, and detect if it is an lvalue or and rvalue
652 # define BOOST_FOREACH_EVALUATE(COL) \
653 (true ? boost::foreach_detail_::make_probe((COL), _foreach_is_rvalue) : (COL))
654
655 // The rvalue/lvalue-ness of the collection expression is determined dynamically, unless
656 // type type is an array or is noncopyable or is non-const, in which case we know it's an lvalue.
657 // If the type happens to be a lightweight proxy, always make a copy.
658 # define BOOST_FOREACH_SHOULD_COPY(COL) \
659 (boost::foreach_detail_::should_copy_impl( \
660 true ? 0 : boost::foreach_detail_::or_( \
661 boost::foreach_detail_::is_array(COL) \
662 , BOOST_FOREACH_IS_NONCOPYABLE(COL) \
663 , boost::foreach_detail_::not_(boost::foreach_detail_::is_const(COL))) \
664 , true ? 0 : BOOST_FOREACH_IS_LIGHTWEIGHT_PROXY(COL) \
665 , &_foreach_is_rvalue))
666
667 #elif !defined(BOOST_FOREACH_NO_RVALUE_DETECTION)
668 ///////////////////////////////////////////////////////////////////////////////
669 // R-values supported here, const R-values NOT supported here
670 ///////////////////////////////////////////////////////////////////////////////
671
672 // No variable is needed to track the rvalue-ness of the collection expression
673 # define BOOST_FOREACH_PREAMBLE() \
674 /**/
675
676 // Evaluate the collection expression
677 # define BOOST_FOREACH_EVALUATE(COL) \
678 (COL)
679
680 // Determine whether the collection expression is an lvalue or an rvalue.
681 // NOTE: this gets the answer wrong for const rvalues.
682 # define BOOST_FOREACH_SHOULD_COPY(COL) \
683 (true ? 0 : boost::foreach_detail_::or_( \
684 boost::foreach_detail_::is_rvalue((COL), 0) \
685 , BOOST_FOREACH_IS_LIGHTWEIGHT_PROXY(COL)))
686
687 #else
688 ///////////////////////////////////////////////////////////////////////////////
689 // R-values NOT supported here
690 ///////////////////////////////////////////////////////////////////////////////
691
692 // No variable is needed to track the rvalue-ness of the collection expression
693 # define BOOST_FOREACH_PREAMBLE() \
694 /**/
695
696 // Evaluate the collection expression
697 # define BOOST_FOREACH_EVALUATE(COL) \
698 (COL)
699
700 // Can't use rvalues with BOOST_FOREACH (unless they are lightweight proxies)
701 # define BOOST_FOREACH_SHOULD_COPY(COL) \
702 (true ? 0 : BOOST_FOREACH_IS_LIGHTWEIGHT_PROXY(COL))
703
704 #endif
705
706 #define BOOST_FOREACH_CONTAIN(COL) \
707 boost::foreach_detail_::contain( \
708 BOOST_FOREACH_EVALUATE(COL) \
709 , BOOST_FOREACH_SHOULD_COPY(COL))
710
711 #define BOOST_FOREACH_BEGIN(COL) \
712 boost::foreach_detail_::begin( \
713 _foreach_col \
714 , BOOST_FOREACH_TYPEOF(COL) \
715 , BOOST_FOREACH_SHOULD_COPY(COL))
716
717 #define BOOST_FOREACH_END(COL) \
718 boost::foreach_detail_::end( \
719 _foreach_col \
720 , BOOST_FOREACH_TYPEOF(COL) \
721 , BOOST_FOREACH_SHOULD_COPY(COL))
722
723 #define BOOST_FOREACH_DONE(COL) \
724 boost::foreach_detail_::done( \
725 _foreach_cur \
726 , _foreach_end \
727 , BOOST_FOREACH_TYPEOF(COL))
728
729 #define BOOST_FOREACH_NEXT(COL) \
730 boost::foreach_detail_::next( \
731 _foreach_cur \
732 , BOOST_FOREACH_TYPEOF(COL))
733
734 #define BOOST_FOREACH_DEREF(COL) \
735 boost::foreach_detail_::deref( \
736 _foreach_cur \
737 , BOOST_FOREACH_TYPEOF(COL))
738
739 ///////////////////////////////////////////////////////////////////////////////
740 // BOOST_FOREACH
741 //
742 // For iterating over collections. Collections can be
743 // arrays, null-terminated strings, or STL containers.
744 // The loop variable can be a value or reference. For
745 // example:
746 //
747 // std::list<int> int_list(/*stuff*/);
748 // BOOST_FOREACH(int &i, int_list)
749 // {
750 // /*
751 // * loop body goes here.
752 // * i is a reference to the int in int_list.
753 // */
754 // }
755 //
756 // Alternately, you can declare the loop variable first,
757 // so you can access it after the loop finishes. Obviously,
758 // if you do it this way, then the loop variable cannot be
759 // a reference.
760 //
761 // int i;
762 // BOOST_FOREACH(i, int_list)
763 // { ... }
764 //
765 #define BOOST_FOREACH(VAR, COL) \
766 BOOST_FOREACH_PREAMBLE() \
767 if (boost::foreach_detail_::auto_any_t _foreach_col = BOOST_FOREACH_CONTAIN(COL)) {} else \
768 if (boost::foreach_detail_::auto_any_t _foreach_cur = BOOST_FOREACH_BEGIN(COL)) {} else \
769 if (boost::foreach_detail_::auto_any_t _foreach_end = BOOST_FOREACH_END(COL)) {} else \
770 for (bool _foreach_continue = true; \
771 _foreach_continue && !BOOST_FOREACH_DONE(COL); \
772 _foreach_continue ? BOOST_FOREACH_NEXT(COL) : (void)0) \
773 if (boost::foreach_detail_::set_false(_foreach_continue)) {} else \
774 for (VAR = BOOST_FOREACH_DEREF(COL); !_foreach_continue; _foreach_continue = true)
775
776 #ifdef foreach
777 #undef foreach
778 #endif
779 #define foreach BOOST_FOREACH
780
781 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef FORMATTER_HPP_INCLUDED
13 #define FORMATTER_HPP_INCLUDED
14
15 #include <sstream>
16
17 #include "wml_value.hpp"
18
19 class formatter
20 {
21 public:
22 template<typename T>
23 formatter& operator<<(const T& o) {
24 stream_ << o;
25 return *this;
26 }
27
28 const std::string str() {
29 return stream_.str();
30 }
31
32 const char* c_str() {
33 return str().c_str();
34 }
35
36 operator std::string() {
37 return stream_.str();
38 }
39
40 operator wml::value() {
41 return wml::value(stream_.str());
42 }
43 private:
44 std::ostringstream stream_;
45 };
46
47 #endif
0 /* $Id: formula.cpp 25895 2008-04-17 18:57:13Z mordante $ */
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include <algorithm>
13 #include <boost/lexical_cast.hpp>
14 #include <cmath>
15 #include <stack>
16 #include <stdio.h>
17 #include <iostream>
18 #include <vector>
19
20 //#include "foreach.hpp"
21 #include "asserts.hpp"
22 #include "foreach.hpp"
23 #include "formula.hpp"
24 #include "formula_callable.hpp"
25 #include "formula_callable_definition.hpp"
26 #include "formula_constants.hpp"
27 #include "formula_function.hpp"
28 #include "formula_tokenizer.hpp"
29 #include "map_utils.hpp"
30 #include "random.hpp"
31 #include "unit_test.hpp"
32 #include "wml_node.hpp"
33
34 namespace {
35 //the last formula that was executed; used for outputting debugging info.
36 const game_logic::formula* last_executed_formula;
37 }
38
39 void output_formula_error_info() {
40 if(last_executed_formula) {
41 last_executed_formula->output_debug_info();
42 }
43 }
44
45 namespace game_logic
46 {
47
48 formula_error::formula_error()
49 {
50 }
51
52 void formula_callable::set_value(const std::string& key, const variant& /*value*/)
53 {
54 std::cerr << "ERROR: cannot set key '" << key << "' on object\n";
55 }
56
57 void formula_callable::set_value_by_slot(int slot, const variant& /*value*/)
58 {
59 std::cerr << "ERROR: cannot set slot '" << slot << "' on object\n";
60 }
61
62 variant formula_callable::get_value_by_slot(int slot) const
63 {
64 ASSERT_LOG(false, "Could not get value by slot from formula callable " << slot);
65 return variant(0); //so VC++ doesn't complain
66 }
67
68 map_formula_callable::map_formula_callable(wml::const_node_ptr node)
69 : formula_callable(false), fallback_(NULL)
70 {
71 if(!node) {
72 return;
73 }
74
75 for(wml::node::const_attr_iterator i = node->begin_attr(); i != node->end_attr(); ++i) {
76 variant var;
77 var.serialize_from_string(i->second);
78 add(i->first, var);
79 }
80 }
81
82 void map_formula_callable::write(wml::node_ptr node) const
83 {
84 for(std::map<std::string,variant>::const_iterator i = values_.begin(); i != values_.end(); ++i) {
85 std::string val;
86 i->second.serialize_to_string(val);
87 node->set_attr(i->first, val);
88 }
89 }
90
91 map_formula_callable::map_formula_callable(
92 const formula_callable* fallback) : formula_callable(false), fallback_(fallback)
93 {}
94
95 map_formula_callable::map_formula_callable(
96 const std::map<std::string, variant>& values) : formula_callable(false), fallback_(NULL), values_(values)
97 {}
98
99 map_formula_callable& map_formula_callable::add(const std::string& key,
100 const variant& value)
101 {
102 values_[key] = value;
103 return *this;
104 }
105
106 variant map_formula_callable::get_value(const std::string& key) const
107 {
108 std::map<std::string, variant>::const_iterator itor = values_.find(key);
109 if(itor == values_.end()) {
110 if(fallback_) {
111 return fallback_->query_value(key);
112 } else {
113 return variant();
114 }
115 } else {
116 return itor->second;
117 }
118 }
119
120 void map_formula_callable::get_inputs(std::vector<formula_input>* inputs) const
121 {
122 if(fallback_) {
123 fallback_->get_inputs(inputs);
124 }
125 for(std::map<std::string,variant>::const_iterator i = values_.begin(); i != values_.end(); ++i) {
126 inputs->push_back(formula_input(i->first, FORMULA_READ_WRITE));
127 }
128 }
129
130 void map_formula_callable::set_value(const std::string& key, const variant& value)
131 {
132 values_[key] = value;
133 }
134
135 namespace {
136
137 class function_list_expression : public formula_expression {
138 public:
139 explicit function_list_expression(function_symbol_table *symbols)
140 : formula_expression("_function_list"), symbols_(symbols)
141 {}
142
143 private:
144 variant execute(const formula_callable& /*variables*/) const {
145 std::vector<variant> res;
146 std::vector<std::string> function_names = builtin_function_names();
147 std::vector<std::string> more_function_names = symbols_->get_function_names();
148 function_names.insert(function_names.end(), more_function_names.begin(), more_function_names.end());
149 for(size_t i = 0; i < function_names.size(); i++) {
150 res.push_back(variant(function_names[i]));
151 }
152 return variant(&res);
153 }
154
155 function_symbol_table* symbols_;
156 };
157
158 class list_expression : public formula_expression {
159 public:
160 explicit list_expression(const std::vector<expression_ptr>& items)
161 : formula_expression("_list"), items_(items)
162 {}
163
164 private:
165 //a special version of static evaluation that doesn't save a
166 //reference to the list, so that we can allow static evaluation
167 //not to be fooled.
168 variant static_evaluate(const formula_callable& variables) const {
169 variant result;
170 std::vector<variant>& res = result.initialize_list();
171 res.reserve(items_.size());
172 for(std::vector<expression_ptr>::const_iterator i = items_.begin(); i != items_.end(); ++i) {
173 res.push_back((*i)->evaluate(variables));
174 }
175
176 return result;
177 }
178
179 variant execute(const formula_callable& variables) const {
180 return static_evaluate(variables);
181 }
182
183 std::vector<expression_ptr> items_;
184 };
185
186 class map_expression : public formula_expression {
187 public:
188 explicit map_expression(const std::vector<expression_ptr>& items)
189 : formula_expression("_map"), items_(items)
190 {}
191
192 private:
193 variant execute(const formula_callable& variables) const {
194 std::map<variant,variant> res;
195 for(std::vector<expression_ptr>::const_iterator i = items_.begin(); ( i != items_.end() ) && ( i+1 != items_.end() ) ; i+=2) {
196 variant key = (*i)->evaluate(variables);
197 variant value = (*(i+1))->evaluate(variables);
198 res[ key ] = value;
199 }
200
201 return variant(&res);
202 }
203
204 std::vector<expression_ptr> items_;
205 };
206
207 class unary_operator_expression : public formula_expression {
208 public:
209 unary_operator_expression(const std::string& op, expression_ptr arg)
210 : formula_expression("_unary"), operand_(arg)
211 {
212 if(op == "not") {
213 op_ = NOT;
214 } else if(op == "-") {
215 op_ = OP_SUB;
216 } else {
217 std::cerr << "illegal unary operator: '" << op << "'\n";
218 throw formula_error();
219 }
220 }
221 private:
222 variant execute(const formula_callable& variables) const {
223 const variant res = operand_->evaluate(variables);
224 switch(op_) {
225 case NOT:
226 return res.as_bool() ? variant(0) : variant(1);
227 case OP_SUB:
228 default:
229 return -res;
230 }
231 }
232 enum OP { NOT, OP_SUB };
233 OP op_;
234 expression_ptr operand_;
235 };
236
237 class list_callable : public formula_callable {
238 variant list_;
239
240 list_callable(const list_callable&);
241 public:
242 explicit list_callable(const variant& list) : formula_callable(false), list_(list)
243 {}
244
245 void get_inputs(std::vector<formula_input>* inputs) const {
246 inputs->push_back(formula_input("size", FORMULA_READ_WRITE));
247 inputs->push_back(formula_input("empty", FORMULA_READ_WRITE));
248 inputs->push_back(formula_input("first", FORMULA_READ_WRITE));
249 inputs->push_back(formula_input("last", FORMULA_READ_WRITE));
250 }
251
252 variant get_value(const std::string& key) const {
253 if(key == "size") {
254 return variant(list_.num_elements());
255 } else if(key == "empty") {
256 return variant(list_.num_elements() == 0);
257 } else if(key == "first") {
258 if(list_.num_elements() > 0) {
259 return list_[0];
260 } else {
261 return variant();
262 }
263 } else if(key == "last") {
264 if(list_.num_elements() > 0) {
265 return list_[list_.num_elements()-1];
266 } else {
267 return variant();
268 }
269 } else {
270 return variant();
271 }
272 }
273 };
274
275 class const_identifier_expression : public formula_expression {
276 public:
277 explicit const_identifier_expression(const std::string& id)
278 : formula_expression("_const_id"), v_(get_constant(id))
279 {
280 }
281
282 private:
283 variant execute(const formula_callable& variables) const {
284 return v_;
285 }
286
287 variant v_;
288 };
289
290 class slot_identifier_expression : public formula_expression {
291 public:
292 slot_identifier_expression(const std::string& id, int slot, const formula_callable_definition* callable_def)
293 : formula_expression("_id"), slot_(slot), id_(id), callable_def_(callable_def)
294 {
295 }
296
297 const std::string& id() const { return id_; }
298
299 bool is_identifier(std::string* ident) const {
300 if(ident) {
301 *ident = id_;
302 }
303
304 return true;
305 }
306
307 const formula_callable_definition* get_type_definition() const {
308 return callable_def_->get_entry(slot_)->type_definition;
309 }
310 private:
311 variant execute_member(const formula_callable& variables, std::string& id) const {
312 id = id_;
313 return variables.query_value("self");
314 }
315
316 variant execute(const formula_callable& variables) const {
317 return variables.query_value_by_slot(slot_);
318 }
319
320 int slot_;
321 std::string id_;
322 const formula_callable_definition* callable_def_;
323 };
324
325
326 class identifier_expression : public formula_expression {
327 public:
328 identifier_expression(const std::string& id, const formula_callable_definition* callable_def)
329 : formula_expression("_id"), id_(id), callable_def_(callable_def)
330 {}
331
332 const std::string& id() const { return id_; }
333
334 bool is_identifier(std::string* ident) const {
335 if(ident) {
336 *ident = id_;
337 }
338
339 return true;
340 }
341
342 expression_ptr optimize() const {
343 if(callable_def_) {
344 const int index = callable_def_->get_slot(id_);
345 if(index != -1) {
346 return expression_ptr(new slot_identifier_expression(id_, index, callable_def_));
347 }
348 }
349
350 return expression_ptr();
351 }
352
353 const formula_callable_definition* get_type_definition() const {
354 if(callable_def_) {
355 const formula_callable_definition::entry* e = callable_def_->get_entry(callable_def_->get_slot(id_));
356 if(e) {
357 return e->type_definition;
358 }
359 }
360
361 return NULL;
362 }
363
364 private:
365 variant execute_member(const formula_callable& variables, std::string& id) const {
366 id = id_;
367 return variables.query_value("self");
368 }
369
370 variant execute(const formula_callable& variables) const {
371 return variables.query_value(id_);
372 }
373 std::string id_;
374 const formula_callable_definition* callable_def_;
375 };
376
377 class lambda_function_expression : public formula_expression {
378 public:
379 lambda_function_expression(const std::vector<std::string>& args, const_formula_ptr fml) : args_(args), fml_(fml)
380 {}
381
382 private:
383 variant execute(const formula_callable& variables) const {
384 return variant(fml_, args_, variables);
385 }
386
387 std::vector<std::string> args_;
388 game_logic::const_formula_ptr fml_;
389 };
390
391 class function_call_expression : public formula_expression {
392 public:
393 function_call_expression(expression_ptr left, const std::vector<expression_ptr>& args)
394 : formula_expression("_fn"), left_(left), args_(args)
395 {}
396 private:
397 variant execute(const formula_callable& variables) const {
398 const variant left = left_->evaluate(variables);
399 std::vector<variant> args;
400 args.reserve(args_.size());
401 foreach(const expression_ptr& e, args_) {
402 args.push_back(e->evaluate(variables));
403 }
404
405 if(!left.is_function()) {
406 std::cerr << "ERROR: " << left_->str() << " IS NOT A VALID FUNCTION\n";
407 }
408
409 return left(args);
410 }
411
412 expression_ptr left_;
413 std::vector<expression_ptr> args_;
414 };
415
416 class dot_expression : public formula_expression {
417 public:
418 dot_expression(expression_ptr left, expression_ptr right)
419 : formula_expression("_dot"), left_(left), right_(right)
420 {}
421 const formula_callable_definition* get_type_definition() const {
422 return right_->get_type_definition();
423 }
424 private:
425 variant execute(const formula_callable& variables) const {
426 const variant left = left_->evaluate(variables);
427 if(!left.is_callable()) {
428 if(left.is_list()) {
429 formula_callable_ptr lc(new list_callable(left));
430 return right_->evaluate(*lc);
431 } else if(left.is_map()) {
432 return left[variant(right_->str())];
433 }
434
435 return left;
436 }
437
438 return right_->evaluate(*left.as_callable());
439 }
440
441 variant execute_member(const formula_callable& variables, std::string& id) const {
442 const variant left = left_->evaluate(variables);
443
444 const identifier_expression* id_expr = dynamic_cast<identifier_expression*>(right_.get());
445 if(!id_expr) {
446 return right_->evaluate_with_member(*left.as_callable(), id);
447 }
448
449 id = id_expr->id();
450
451 return left;
452 }
453
454 expression_ptr left_, right_;
455 };
456
457 class square_bracket_expression : public formula_expression { //TODO
458 public:
459 square_bracket_expression(expression_ptr left, expression_ptr key)
460 : formula_expression("_sqbr"), left_(left), key_(key)
461 {}
462 private:
463 variant execute(const formula_callable& variables) const {
464 const variant left = left_->evaluate(variables);
465 const variant key = key_->evaluate(variables);
466 if(left.is_list() || left.is_map()) {
467 return left[ key ];
468 } else {
469 std::cerr << "illegal usage of operator []'\n";
470 throw formula_error();
471 }
472 }
473
474 expression_ptr left_, key_;
475 };
476
477 #define OPTIMIZED_INT_BINARY_OP(name, op) \
478 class name##_integer_operator_expression : public formula_expression { \
479 public: \
480 name##_integer_operator_expression(expression_ptr left, int value) \
481 : formula_expression("_" #name), left_(left), value_(value) \
482 {} \
483 private: \
484 variant execute(const formula_callable& variables) const { \
485 return variant(left_->evaluate(variables).as_int() op value_); \
486 } \
487 expression_ptr left_; \
488 int value_; \
489 }
490
491 OPTIMIZED_INT_BINARY_OP(add, +);
492 OPTIMIZED_INT_BINARY_OP(sub, -);
493 OPTIMIZED_INT_BINARY_OP(mul, *);
494 OPTIMIZED_INT_BINARY_OP(div, /);
495 OPTIMIZED_INT_BINARY_OP(eq, ==);
496 OPTIMIZED_INT_BINARY_OP(ne, !=);
497 OPTIMIZED_INT_BINARY_OP(lt, <);
498 OPTIMIZED_INT_BINARY_OP(gt, >);
499 OPTIMIZED_INT_BINARY_OP(le, <=);
500 OPTIMIZED_INT_BINARY_OP(ge, >=);
501
502 #undef OPTIMIZED_INT_BINARY_OP
503
504 class and_operator_expression : public formula_expression {
505 public:
506 and_operator_expression(expression_ptr left, expression_ptr right)
507 : formula_expression("_and"), left_(left), right_(right)
508 {
509 }
510
511 private:
512 variant execute(const formula_callable& variables) const {
513 variant v = left_->evaluate(variables);
514 if(!v.as_bool()) {
515 return v;
516 }
517
518 return right_->evaluate(variables);
519 }
520
521 expression_ptr left_, right_;
522 };
523
524 class or_operator_expression : public formula_expression {
525 public:
526 or_operator_expression(expression_ptr left, expression_ptr right)
527 : formula_expression("_or"), left_(left), right_(right)
528 {
529 }
530
531 private:
532 variant execute(const formula_callable& variables) const {
533 variant v = left_->evaluate(variables);
534 if(v.as_bool()) {
535 return v;
536 }
537
538 return right_->evaluate(variables);
539 }
540
541 expression_ptr left_, right_;
542 };
543
544 class operator_expression : public formula_expression {
545 public:
546 operator_expression(const std::string& op, expression_ptr left,
547 expression_ptr right)
548 : formula_expression("_op"), op_(OP(op[0])), left_(left), right_(right)
549 {
550 if(op == ">=") {
551 op_ = OP_GTE;
552 } else if(op == "<=") {
553 op_ = OP_LTE;
554 } else if(op == "!=") {
555 op_ = OP_NEQ;
556 } else if(op == "and") {
557 op_ = OP_AND;
558 } else if(op == "or") {
559 op_ = OP_OR;
560 } else if(op == "in") {
561 op_ = OP_IN;
562 }
563 }
564
565 expression_ptr optimize() const {
566 if(op_ == OP_AND) {
567 return expression_ptr(new and_operator_expression(left_, right_));
568 } else if(op_ == OP_OR) {
569 return expression_ptr(new or_operator_expression(left_, right_));
570 }
571
572 variant v;
573 if(right_->can_reduce_to_variant(v) && v.is_int()) {
574 switch(op_) {
575 case OP_ADD: return expression_ptr(new add_integer_operator_expression(left_, v.as_int()));
576 case OP_SUB: return expression_ptr(new sub_integer_operator_expression(left_, v.as_int()));
577 case OP_MUL: return expression_ptr(new mul_integer_operator_expression(left_, v.as_int()));
578 case OP_DIV: if(v.as_int() != 0) { return expression_ptr(new div_integer_operator_expression(left_, v.as_int())); }
579 case OP_EQ: return expression_ptr(new eq_integer_operator_expression(left_, v.as_int()));
580 case OP_NEQ: return expression_ptr(new ne_integer_operator_expression(left_, v.as_int()));
581 case OP_LT: return expression_ptr(new lt_integer_operator_expression(left_, v.as_int()));
582 case OP_GT: return expression_ptr(new gt_integer_operator_expression(left_, v.as_int()));
583 case OP_GTE: return expression_ptr(new ge_integer_operator_expression(left_, v.as_int()));
584 case OP_LTE: return expression_ptr(new le_integer_operator_expression(left_, v.as_int()));
585 default: return expression_ptr();
586 }
587 } else if(left_->can_reduce_to_variant(v) && v.is_int()) {
588 switch(op_) {
589 case OP_ADD: return expression_ptr(new add_integer_operator_expression(right_, v.as_int()));
590 case OP_MUL: return expression_ptr(new mul_integer_operator_expression(right_, v.as_int()));
591 default: return expression_ptr();
592 }
593 }
594
595 return expression_ptr();
596 }
597
598 private:
599 variant execute(const formula_callable& variables) const {
600 const variant left = left_->evaluate(variables);
601 const variant right = right_->evaluate(variables);
602 switch(op_) {
603 case OP_IN:
604 if(!right.is_list()) {
605 return variant();
606 } else {
607 for(int n = 0; n != right.num_elements(); ++n) {
608 if(left == right[n]) {
609 return variant(1);
610 }
611 }
612
613 return variant(0);
614 }
615 case OP_AND:
616 return left.as_bool() == false ? left : right;
617 case OP_OR:
618 return left.as_bool() ? left : right;
619 case OP_ADD:
620 return left + right;
621 case OP_SUB:
622 return left - right;
623 case OP_MUL:
624 return left * right;
625 case OP_DIV:
626 return left / right;
627 case OP_POW:
628 return left ^ right;
629 case OP_EQ:
630 return left == right ? variant(1) : variant(0);
631 case OP_NEQ:
632 return left != right ? variant(1) : variant(0);
633 case OP_LTE:
634 return left <= right ? variant(1) : variant(0);
635 case OP_GTE:
636 return left >= right ? variant(1) : variant(0);
637 case OP_LT:
638 return left < right ? variant(1) : variant(0);
639 case OP_GT:
640 return left > right ? variant(1) : variant(0);
641 case OP_MOD:
642 return left % right;
643 case OP_DICE:
644 default:
645 return variant(dice_roll(left.as_int(), right.as_int()));
646 }
647 }
648
649 static int dice_roll(int num_rolls, int faces) {
650 int res = 0;
651 while(faces > 0 && num_rolls-- > 0) {
652 res += (rng::generate()%faces)+1;
653 }
654 return res;
655 }
656
657 enum OP { OP_IN, OP_AND, OP_OR, OP_NEQ, OP_LTE, OP_GTE, OP_GT='>', OP_LT='<', OP_EQ='=',
658 OP_ADD='+', OP_SUB='-', OP_MUL='*', OP_DIV='/', OP_DICE='d', OP_POW='^', OP_MOD='%' };
659
660 OP op_;
661 expression_ptr left_, right_;
662 };
663
664 typedef std::map<std::string,expression_ptr> expr_table;
665 typedef boost::shared_ptr<expr_table> expr_table_ptr;
666
667 class where_variables: public formula_callable {
668 public:
669 where_variables(const formula_callable &base,
670 expr_table_ptr table )
671 : formula_callable(false), base_(base), table_(table) { }
672 private:
673 const formula_callable& base_;
674 expr_table_ptr table_;
675
676 mutable std::map<std::string, variant> results_cache_;
677
678 void get_inputs(std::vector<formula_input>* inputs) const {
679 for(expr_table::const_iterator i = table_->begin(); i != table_->end(); ++i) {
680 inputs->push_back(formula_input(i->first, FORMULA_READ_ONLY));
681 }
682 }
683
684 variant get_value_by_slot(int slot) const {
685 return base_.query_value_by_slot(slot);
686 }
687
688 variant get_value(const std::string& key) const {
689 expr_table::iterator i = table_->find(key);
690 if(i != table_->end()) {
691 std::map<std::string, variant>::const_iterator itor = results_cache_.find(key);
692 if(itor != results_cache_.end()) {
693 return itor->second;
694 }
695
696 variant result = i->second->evaluate(base_);
697 results_cache_[key] = result;
698 return result;
699 }
700 return base_.query_value(key);
701 }
702 };
703
704 class where_expression: public formula_expression {
705 public:
706 explicit where_expression(expression_ptr body,
707 expr_table_ptr clauses)
708 : formula_expression("_where"), body_(body), clauses_(clauses)
709 {}
710
711 private:
712 expression_ptr body_;
713 expr_table_ptr clauses_;
714
715 variant execute(const formula_callable& variables) const {
716 formula_callable_ptr wrapped_variables(new where_variables(variables, clauses_));
717 return body_->evaluate(*wrapped_variables);
718 }
719 };
720
721 class null_expression : public formula_expression {
722 public:
723 explicit null_expression() : formula_expression("_null") {}
724 private:
725 variant execute(const formula_callable& /*variables*/) const {
726 return variant();
727 }
728 };
729
730
731 class integer_expression : public formula_expression {
732 public:
733 explicit integer_expression(int i) : formula_expression("_int"), i_(i)
734 {}
735 private:
736 variant execute(const formula_callable& /*variables*/) const {
737 return i_;
738 }
739
740 variant i_;
741 };
742
743 class string_expression : public formula_expression {
744 public:
745 explicit string_expression(std::string str) : formula_expression("_string")
746 {
747 std::string::iterator i;
748 while((i = std::find(str.begin(), str.end(), '{')) != str.end()) {
749 std::string::iterator j = std::find(i, str.end(), '}');
750 if(j == str.end()) {
751 break;
752 }
753
754 const std::string formula_str(i+1, j);
755 const int pos = i - str.begin();
756 str.erase(i, j+1);
757
758 substitution sub;
759 sub.pos = pos;
760 sub.calculation.reset(new formula(formula_str));
761 subs_.push_back(sub);
762 }
763
764 std::reverse(subs_.begin(), subs_.end());
765
766 str_ = variant(str);
767 }
768
769 variant is_literal() const {
770 if(subs_.empty()) {
771 return variant(str_);
772 } else {
773 return variant();
774 }
775 }
776 private:
777 variant execute(const formula_callable& variables) const {
778 if(subs_.empty()) {
779 return str_;
780 } else {
781 std::string res = str_.as_string();
782 for(size_t i=0; i < subs_.size(); ++i) {
783 const substitution& sub = subs_[i];
784 const std::string str = sub.calculation->execute(variables).string_cast();
785 res.insert(sub.pos, str);
786 }
787
788 return variant(res);
789 }
790 }
791
792 struct substitution {
793 int pos;
794 const_formula_ptr calculation;
795 };
796
797 variant str_;
798 std::vector<substitution> subs_;
799 };
800
801 using namespace formula_tokenizer;
802 int operator_precedence(const token& t)
803 {
804 static std::map<std::string,int> precedence_map;
805 if(precedence_map.empty()) {
806 int n = 0;
807 precedence_map["not"] = ++n;
808 precedence_map["where"] = ++n;
809 precedence_map["or"] = ++n;
810 precedence_map["and"] = ++n;
811 precedence_map["in"] = ++n;
812 precedence_map["="] = ++n;
813 precedence_map["!="] = n;
814 precedence_map["<"] = n;
815 precedence_map[">"] = n;
816 precedence_map["<="] = n;
817 precedence_map[">="] = n;
818 precedence_map["+"] = ++n;
819 precedence_map["-"] = n;
820 precedence_map["*"] = ++n;
821 precedence_map["/"] = ++n;
822 precedence_map["%"] = ++n;
823 precedence_map["^"] = ++n;
824 precedence_map["d"] = ++n;
825
826 //these operators are equal precedence, and left
827 //associative. Thus, x.y[4].z = ((x.y)[4]).z
828 precedence_map["["] = ++n;
829 precedence_map["("] = n;
830 precedence_map["."] = n;
831 }
832
833 assert(precedence_map.count(std::string(t.begin,t.end)));
834 return precedence_map[std::string(t.begin,t.end)];
835 }
836
837 expression_ptr parse_expression(const token* i1, const token* i2, function_symbol_table* symbols, const formula_callable_definition* callable_def, bool* can_optimize=NULL);
838
839 void parse_function_args(const token* &i1, const token* i2,
840 std::vector<std::string>* res,
841 std::vector<std::string>* types)
842 {
843 if(i1->type == TOKEN_LPARENS) {
844 ++i1;
845 } else {
846 std::cerr << "Invalid function definition" << std::endl;
847 throw formula_error();
848 }
849
850 while((i1->type != TOKEN_RPARENS) && (i1 != i2)) {
851 if(i1->type == TOKEN_IDENTIFIER) {
852 if(i1+1 != i2 && std::string((i1+1)->begin, (i1+1)->end) == "*") {
853 types->push_back("");
854 res->push_back(std::string(i1->begin, i1->end) + std::string("*"));
855 ++i1;
856 } else if(i1+1 != i2 && (i1+1)->type == TOKEN_IDENTIFIER) {
857 types->push_back(std::string(i1->begin, i1->end));
858 res->push_back(std::string((i1+1)->begin, (i1+1)->end));
859 ++i1;
860 } else {
861 types->push_back("");
862 res->push_back(std::string(i1->begin, i1->end));
863 }
864 } else if (i1->type == TOKEN_COMMA) {
865 //do nothing
866 } else {
867 std::cerr << "Invalid function definition" << std::endl;
868 throw formula_error();
869 }
870 ++i1;
871 }
872
873 if(i1->type != TOKEN_RPARENS) {
874 std::cerr << "Invalid function definition" << std::endl;
875 throw formula_error();
876 }
877 ++i1;
878 }
879
880 void parse_args(const token* i1, const token* i2,
881 std::vector<expression_ptr>* res,
882 function_symbol_table* symbols,
883 const formula_callable_definition* callable_def,
884 bool* can_optimize)
885 {
886 ASSERT_LE(i1, i2);
887 int parens = 0;
888 const token* beg = i1;
889 while(i1 != i2) {
890 if(i1->type == TOKEN_LPARENS || i1->type == TOKEN_LSQUARE || i1->type == TOKEN_LBRACKET ) {
891 ++parens;
892 } else if(i1->type == TOKEN_RPARENS || i1->type == TOKEN_RSQUARE || i1->type == TOKEN_RBRACKET) {
893 --parens;
894 } else if(i1->type == TOKEN_COMMA && !parens) {
895 res->push_back(parse_expression(beg,i1, symbols, callable_def, can_optimize));
896 beg = i1+1;
897 }
898
899 ++i1;
900 }
901
902 if(beg != i1) {
903 res->push_back(parse_expression(beg,i1, symbols, callable_def, can_optimize));
904 }
905 }
906
907 void parse_set_args(const token* i1, const token* i2,
908 std::vector<expression_ptr>* res,
909 function_symbol_table* symbols)
910 {
911 int parens = 0;
912 bool check_pointer = false;
913 const token* beg = i1;
914 while(i1 != i2) {
915 if(i1->type == TOKEN_LPARENS || i1->type == TOKEN_LSQUARE) {
916 ++parens;
917 } else if(i1->type == TOKEN_RPARENS || i1->type == TOKEN_RSQUARE) {
918 --parens;
919 } else if( i1->type == TOKEN_POINTER && !parens ) {
920 if (!check_pointer) {
921 check_pointer = true;
922 res->push_back(parse_expression(beg,i1, symbols, NULL));
923 beg = i1+1;
924 } else {
925 std::cerr << "Too many '->' operators\n";
926 throw formula_error();
927 }
928 } else if( i1->type == TOKEN_COMMA && !parens ) {
929 check_pointer = false;
930 res->push_back(parse_expression(beg,i1, symbols, NULL));
931 beg = i1+1;
932 }
933
934 ++i1;
935 }
936
937 if(beg != i1) {
938 res->push_back(parse_expression(beg,i1, symbols, NULL));
939 }
940 }
941
942 void parse_where_clauses(const token* i1, const token * i2,
943 expr_table_ptr res, function_symbol_table* symbols,
944 const formula_callable_definition* callable_def) {
945 int parens = 0;
946 const token *original_i1_cached = i1;
947 const token *beg = i1;
948 std::string var_name;
949 while(i1 != i2) {
950 if(i1->type == TOKEN_LPARENS || i1->type == TOKEN_LBRACKET || i1->type == TOKEN_LSQUARE) {
951 ++parens;
952 } else if(i1->type == TOKEN_RPARENS || i1->type == TOKEN_RBRACKET || i1->type == TOKEN_RSQUARE) {
953 --parens;
954 } else if(!parens) {
955 if(i1->type == TOKEN_COMMA) {
956 if(var_name.empty()) {
957 std::cerr << "There is 'where <expression>,; "
958 << "'where name=<expression>,' was needed.\n";
959 throw formula_error();
960 }
961 (*res)[var_name] = parse_expression(beg,i1, symbols, callable_def);
962 beg = i1+1;
963 var_name = "";
964 } else if(i1->type == TOKEN_OPERATOR) {
965 std::string op_name(i1->begin, i1->end);
966 if(op_name == "=") {
967 if(beg->type != TOKEN_IDENTIFIER) {
968 if(i1 == original_i1_cached) {
969 std::cerr<< "There is 'where =<expression'; "
970 << "'where name=<expression>' was needed.\n";
971 } else {
972 std::cerr<< "There is 'where <expression>=<expression>'; "
973 << "'where name=<expression>' was needed.\n";
974 }
975 throw formula_error();
976 } else if(beg+1 != i1) {
977 std::cerr<<"There is 'where name <expression>=<expression>'; "
978 << "'where name=<expression>' was needed.\n";
979 throw formula_error();
980 } else if(!var_name.empty()) {
981 std::cerr<<"There is 'where name=name=<expression>'; "
982 <<"'where name=<expression>' was needed.\n";
983 throw formula_error();
984 }
985 var_name.insert(var_name.end(), beg->begin, beg->end);
986 beg = i1+1;
987 }
988 }
989 }
990 ++i1;
991 }
992 if(beg != i1) {
993 if(var_name.empty()) {
994 std::cerr << "There is 'where <expression>'; "
995 << "'where name=<expression> was needed.\n";
996 throw formula_error();
997 }
998 (*res)[var_name] = parse_expression(beg,i1, symbols, callable_def);
999 }
1000 }
1001
1002 expression_ptr parse_expression_internal(const token* i1, const token* i2, function_symbol_table* symbols, const formula_callable_definition* callable_def, bool* can_optimize=NULL);
1003
1004 namespace {
1005
1006 //only allow one static_formula_callable to be active at a time.
1007 bool static_formula_callable_active = false;
1008
1009 //a special callable which will throw an exception if it's actually called.
1010 //we use this to determine if an expression is static -- i.e. doesn't
1011 //depend on input, and can be reduced to its result.
1012 struct non_static_expression_exception {};
1013 class static_formula_callable : public formula_callable {
1014 static_formula_callable(const static_formula_callable&);
1015 public:
1016 static_formula_callable() : formula_callable(false) {
1017 if(static_formula_callable_active) {
1018 throw non_static_expression_exception();
1019 }
1020
1021 static_formula_callable_active = true;
1022 }
1023
1024 ~static_formula_callable() {
1025 static_formula_callable_active = false;
1026 }
1027
1028 variant get_value(const std::string& key) const {
1029 throw non_static_expression_exception();
1030 }
1031
1032 variant get_value_by_slot(int slot) const {
1033 throw non_static_expression_exception();
1034 }
1035 };
1036 }
1037
1038 expression_ptr optimize_expression(expression_ptr result, function_symbol_table* symbols, const formula_callable_definition* callable_def, bool reduce_to_static)
1039 {
1040 const std::string str = result->str();
1041
1042 if(reduce_to_static) {
1043 //we want to try to evaluate this expression, and see if it is static.
1044 //it is static if it never reads its input, if it doesn't call the rng,
1045 //and if a reference to the input itself is not stored.
1046 try {
1047 const unsigned int rng_seed = rng::get_seed();
1048 formula_callable_ptr static_callable(new static_formula_callable);
1049 variant res = result->static_evaluate(*static_callable);
1050 if(rng_seed == rng::get_seed() && static_callable->refcount() == 1) {
1051 //this expression is static. Reduce it to its result.
1052 result = expression_ptr(new variant_expression(res));
1053 }
1054
1055 //it's possible if there is a latent reference to it the
1056 //static callable won't get destroyed, so make sure we
1057 //mark it as inactive to allow others to be created.
1058 static_formula_callable_active = false;
1059 } catch(non_static_expression_exception& e) {
1060 //the expression isn't static. Not an error.
1061 }
1062 }
1063
1064 if(result) {
1065 expression_ptr optimized = result->optimize();
1066 if(optimized) {
1067 result = optimized;
1068 }
1069 }
1070
1071 if(result) {
1072 result->set_str(str);
1073 }
1074
1075 return result;
1076 }
1077
1078 expression_ptr parse_expression(const token* i1, const token* i2, function_symbol_table* symbols, const formula_callable_definition* callable_def, bool* can_optimize)
1079 {
1080 bool optimize = true;
1081 expression_ptr result(parse_expression_internal(i1, i2, symbols, callable_def, &optimize));
1082 result->set_str(std::string(i1->begin, (i2-1)->end));
1083
1084 result = optimize_expression(result, symbols, callable_def, optimize);
1085
1086 if(can_optimize && !optimize) {
1087 *can_optimize = false;
1088 }
1089
1090 return result;
1091 }
1092
1093 expression_ptr parse_expression_internal(const token* i1, const token* i2, function_symbol_table* symbols, const formula_callable_definition* callable_def, bool* can_optimize)
1094 {
1095 if(i1 == i2) {
1096 std::cerr << "empty expression\n";
1097 throw formula_error();
1098 }
1099
1100 if(i1->type == TOKEN_KEYWORD && std::string(i1->begin, i1->end) == "def" &&
1101 ((i1+1)->type == TOKEN_IDENTIFIER || (i1+1)->type == TOKEN_LPARENS)) {
1102 ++i1;
1103 std::string formula_name;
1104 if(i1->type == TOKEN_IDENTIFIER) {
1105 formula_name = std::string(i1->begin, i1->end);
1106 ++i1;
1107 }
1108
1109 std::vector<std::string> args, types;
1110 parse_function_args(i1, i2, &args, &types);
1111 const token* beg = i1;
1112 while((i1 != i2) && (i1->type != TOKEN_SEMICOLON)) {
1113 ++i1;
1114 }
1115 const std::string formula_str = std::string(beg->begin, (i1-1)->end);
1116
1117 recursive_function_symbol_table recursive_symbols(formula_name.empty() ? "recurse" : formula_name, args, symbols);
1118
1119 formula_callable_definition_ptr args_definition;
1120 if(formula_name.empty() == false) {
1121 //create a definition of the callable representing
1122 //function arguments.
1123 args_definition = create_formula_callable_definition(&args[0], &args[0] + args.size());
1124 for(int n = 0; n != types.size(); ++n) {
1125 if(types[n].empty()) {
1126 continue;
1127 }
1128
1129 ASSERT_LOG(args_definition->get_entry(n) != NULL, "FORMULA FUNCTION TYPE ARGS MIS-MATCH");
1130
1131 const formula_callable_definition* def = get_formula_callable_definition(types[n]);
1132 ASSERT_LOG(def != NULL, "TYPE NOT FOUND: " << types[n]);
1133 args_definition->get_entry(n)->type_definition = def;
1134 }
1135 }
1136
1137 const_formula_ptr fml(new formula(formula_str, &recursive_symbols, args_definition.get()));
1138 recursive_symbols.resolve_recursive_calls(fml);
1139
1140 if(formula_name.empty()) {
1141 return expression_ptr(new lambda_function_expression(args, fml));
1142 }
1143
1144 const std::string precond = "";
1145 symbols->add_formula_function(formula_name, fml,
1146 formula::create_optional_formula(precond, symbols), args);
1147 if((i1 == i2) || (i1 == (i2-1))) {
1148 return expression_ptr(new function_list_expression(symbols));
1149 }
1150 else {
1151 return parse_expression((i1+1), i2, symbols, callable_def, can_optimize);
1152 }
1153 }
1154
1155 int parens = 0;
1156 const token* op = NULL;
1157 const token* fn_call = NULL;
1158
1159 for(const token* i = i1; i != i2; ++i) {
1160 if(fn_call && i+1 == i2 && i->type != TOKEN_RPARENS) {
1161 fn_call = NULL;
1162 }
1163
1164 if(i->type == TOKEN_LPARENS || i->type == TOKEN_LSQUARE) {
1165 if(i->type == TOKEN_LPARENS && parens == 0 && i != i1) {
1166 fn_call = i;
1167 } else if(i->type == TOKEN_LSQUARE && parens == 0 && i != i1 && (i-1)->type != TOKEN_OPERATOR && (op == NULL || operator_precedence(*op) >= operator_precedence(*i))) {
1168 //the square bracket itself is an operator
1169 op = i;
1170 }
1171
1172 ++parens;
1173 } else if(i->type == TOKEN_RPARENS || i->type == TOKEN_RSQUARE) {
1174 --parens;
1175
1176 if(parens == 0 && i+1 != i2) {
1177 fn_call = NULL;
1178 }
1179 } else if(parens == 0 && i->type == TOKEN_OPERATOR) {
1180 if(op == NULL || operator_precedence(*op) >=
1181 operator_precedence(*i)) {
1182 op = i;
1183 }
1184 }
1185 }
1186
1187 if(op != NULL && op->type == TOKEN_LSQUARE) {
1188 //the square bracket operator is handled below, just set the op
1189 //to NULL and it'll be handled.
1190 op = NULL;
1191 }
1192
1193 if(op == NULL) {
1194 if(i1->type == TOKEN_LPARENS && (i2-1)->type == TOKEN_RPARENS) {
1195 return parse_expression(i1+1,i2-1,symbols, callable_def, can_optimize);
1196 } else if( (i2-1)->type == TOKEN_RSQUARE) { //check if there is [ ] : either a list definition, or a operator
1197 const token* tok = i2-2;
1198 int square_parens = 0;
1199 while ( (tok->type != TOKEN_LSQUARE || square_parens) && tok != i1) {
1200 if (tok->type == TOKEN_RSQUARE) {
1201 square_parens++;
1202 } else if(tok->type == TOKEN_LSQUARE) {
1203 square_parens--;
1204 }
1205 --tok;
1206 }
1207 if (tok->type == TOKEN_LSQUARE) {
1208 if (tok == i1) {
1209 //create a list
1210 std::vector<expression_ptr> args;
1211 parse_args(i1+1,i2-1,&args,symbols, callable_def, can_optimize);
1212 return expression_ptr(new list_expression(args));
1213 } else {
1214 //execute operator [ ]
1215 return expression_ptr(new square_bracket_expression(
1216 parse_expression(i1,tok,symbols, callable_def, can_optimize),
1217 parse_expression(tok+1,i2-1,symbols, callable_def, can_optimize)));
1218 }
1219 }
1220 } else if(i1->type == TOKEN_LBRACKET && (i2-1)->type == TOKEN_RBRACKET) {
1221 //create a map TODO: add support for a set
1222 std::vector<expression_ptr> args;
1223 parse_set_args(i1+1,i2-1,&args,symbols);
1224 return expression_ptr(new map_expression(args));
1225 } else if(i2 - i1 == 1) {
1226 if(i1->type == TOKEN_KEYWORD) {
1227 if(std::string(i1->begin,i1->end) == "functions") {
1228 return expression_ptr(new function_list_expression(symbols));
1229 }
1230 } else if(i1->type == TOKEN_CONST_IDENTIFIER) {
1231 return expression_ptr(new const_identifier_expression(
1232 std::string(i1->begin,i1->end)));
1233 } else if(i1->type == TOKEN_IDENTIFIER) {
1234 if(can_optimize) {
1235 *can_optimize = false;
1236 }
1237 return expression_ptr(new identifier_expression(
1238 std::string(i1->begin,i1->end), callable_def));
1239 } else if(i1->type == TOKEN_INTEGER) {
1240 int n = strtol(std::string(i1->begin,i1->end).c_str(), NULL, 0);
1241 return expression_ptr(new integer_expression(n));
1242 } else if(i1->type == TOKEN_STRING_LITERAL) {
1243 return expression_ptr(new string_expression(std::string(i1->begin+1,i1->end-1)));
1244 }
1245 } else if(i1->type == TOKEN_IDENTIFIER &&
1246 (i1+1)->type == TOKEN_LPARENS &&
1247 (i2-1)->type == TOKEN_RPARENS) {
1248 int nleft = 0, nright = 0;
1249 for(const token* i = i1; i != i2; ++i) {
1250 if(i->type == TOKEN_LPARENS) {
1251 ++nleft;
1252 } else if(i->type == TOKEN_RPARENS) {
1253 ++nright;
1254 }
1255 }
1256
1257 if(nleft == nright) {
1258 const std::string function_name(i1->begin, i1->end);
1259 std::vector<expression_ptr> args;
1260 const bool optimize = optimize_function_arguments(function_name, symbols);
1261 parse_args(i1+2,i2-1,&args,symbols, optimize ? callable_def : NULL, can_optimize);
1262 expression_ptr result(create_function(function_name, args, symbols, callable_def));
1263 if(result) {
1264 return result;
1265 }
1266 }
1267 }
1268
1269 if(!fn_call) {
1270 if(i1->type == TOKEN_IDENTIFIER && (i1+1)->type == TOKEN_LPARENS) {
1271 const token* match = i1+2;
1272 int depth = 0;
1273 while(match < i2) {
1274 if(match->type == TOKEN_LPARENS) {
1275 ++depth;
1276 } else if(match->type == TOKEN_RPARENS) {
1277 if(depth == 0) {
1278 break;
1279 }
1280 --depth;
1281 }
1282 ++match;
1283 }
1284
1285 if(match != i2) {
1286 ++match;
1287 ASSERT_LT(match, i2);
1288
1289 std::cerr << "unexpected tokens after function call: '" << std::string(match->begin, (i2-1)->end) << "'\n";
1290 } else {
1291 std::cerr << "no closing parenthesis to function call: '" << std::string(i1->begin, (i2-1)->end) << "'\n";
1292 }
1293 } else {
1294 std::cerr << "could not parse expression: '" << std::string(i1->begin, (i2-1)->end) << "'\n";
1295 }
1296
1297 throw formula_error();
1298 }
1299 }
1300
1301 if(fn_call && (op == NULL || operator_precedence(*op) >=
1302 operator_precedence(*fn_call))) {
1303 op = fn_call;
1304 }
1305
1306 if(op == i1) {
1307 return expression_ptr(new unary_operator_expression(
1308 std::string(op->begin,op->end),
1309 parse_expression(op+1,i2,symbols, callable_def, can_optimize)));
1310 }
1311
1312 const std::string op_name(op->begin,op->end);
1313
1314 if(op_name == "(") {
1315 if(i2 - op < 2) {
1316 std::cerr << "MISSING PARENS IN FORMULA\n";
1317 throw formula_error();
1318 }
1319
1320 std::vector<expression_ptr> args;
1321 parse_args(op+1, i2-1, &args, symbols, callable_def, can_optimize);
1322
1323 return expression_ptr(new function_call_expression(
1324 parse_expression(i1, op, symbols, callable_def, can_optimize), args));
1325 }
1326
1327 if(op_name == ".") {
1328 expression_ptr left(parse_expression(i1,op,symbols, callable_def, can_optimize));
1329 const formula_callable_definition* type_definition = left->get_type_definition();
1330 //TODO: right now we don't give the right side of the dot
1331 //a expression definition. We should work out if we can
1332 //statically derive information from the left half to
1333 //give the right half a definition.
1334 return expression_ptr(new dot_expression(left,
1335 parse_expression(op+1,i2,symbols, type_definition, can_optimize)));
1336 }
1337
1338 if(op_name == "where") {
1339 expr_table_ptr table(new expr_table());
1340 parse_where_clauses(op+1, i2, table, symbols, callable_def);
1341 return expression_ptr(new where_expression(parse_expression(i1, op, symbols, callable_def, can_optimize),
1342 table));
1343 }
1344
1345 const bool is_dot = op_name == ".";
1346 return expression_ptr(new operator_expression(
1347 op_name, parse_expression(i1,op,symbols, callable_def, can_optimize),
1348 parse_expression(op+1,i2,symbols, callable_def, can_optimize)));
1349 }
1350
1351 }
1352
1353 formula_ptr formula::create_string_formula(const std::string& str)
1354 {
1355 formula_ptr res(new formula());
1356 res->expr_.reset(new string_expression(str));
1357 return res;
1358 }
1359
1360 formula_ptr formula::create_optional_formula(const wml::value& val, function_symbol_table* symbols, const formula_callable_definition* callable_definition)
1361 {
1362 if(val.empty()) {
1363 return formula_ptr();
1364 }
1365
1366 try {
1367 return formula_ptr(new formula(val, symbols, callable_definition));
1368 } catch(...) {
1369 if(val.filename()) {
1370 std::cerr << *val.filename() << " " << val.line() << ": ";
1371 }
1372
1373 std::cerr << "ERROR parsing optional formula: '" << val << "'\n";
1374 //for now die a horrible death on such errors
1375 assert(false);
1376
1377 return formula_ptr();
1378 }
1379 }
1380
1381 formula::formula(const wml::value& val, function_symbol_table* symbols, const formula_callable_definition* callable_definition) : str_(val.str()), filename_(val.filename()), line_(val.line())
1382 {
1383 using namespace formula_tokenizer;
1384
1385 std::vector<token> tokens;
1386 std::string::const_iterator i1 = str_.begin(), i2 = str_.end();
1387 while(i1 != i2) {
1388 try {
1389 tokens.push_back(get_token(i1,i2));
1390 if((tokens.back().type == TOKEN_WHITESPACE) || (tokens.back().type == TOKEN_COMMENT)) {
1391 tokens.pop_back();
1392 }
1393 } catch(token_error& /*e*/) {
1394 throw formula_error();
1395 }
1396 }
1397
1398 //check that all kinds of brackets match up.
1399 {
1400 std::string error_msg;
1401 int error_loc = -1;
1402
1403 std::stack<formula_tokenizer::FFL_TOKEN_TYPE> brackets;
1404 std::stack<int> brackets_locs;
1405 for(int n = 0; n != tokens.size(); ++n) {
1406 switch(tokens[n].type) {
1407 case TOKEN_LPARENS:
1408 case TOKEN_LSQUARE:
1409 case TOKEN_LBRACKET:
1410 brackets.push(tokens[n].type);
1411 brackets_locs.push(n);
1412 break;
1413 case TOKEN_RPARENS:
1414 case TOKEN_RSQUARE:
1415 case TOKEN_RBRACKET:
1416 if(brackets.empty()) {
1417 error_msg = "UNEXPECTED TOKEN: " + std::string(tokens[n].begin, tokens[n].end);
1418 error_loc = n;
1419 break;
1420 } else if(brackets.top() != tokens[n].type-1) {
1421 const int m = brackets_locs.top();
1422 error_msg = "UNMATCHED BRACKET: " + std::string(tokens[m].begin, tokens[m].end);
1423 error_loc = m;
1424 break;
1425 }
1426
1427 brackets.pop();
1428 brackets_locs.pop();
1429 break;
1430 }
1431 }
1432
1433 if(brackets.empty() == false) {
1434 const int m = brackets_locs.top();
1435 error_msg = "UNMATCHED BRACKET: " + std::string(tokens[m].begin, tokens[m].end);
1436 error_loc = m;
1437 }
1438
1439 if(error_loc != -1) {
1440 const token& tok = tokens[error_loc];
1441 std::string::const_iterator begin_line = tokens.front().begin;
1442 std::string::const_iterator i = begin_line;
1443 int nline = 0;
1444 while(i < tok.begin) {
1445 if(i == tok.begin) {
1446 break;
1447 }
1448
1449 if(*i == '\n') {
1450 ++nline;
1451 begin_line = i+1;
1452 }
1453 ++i;
1454 }
1455
1456 const std::string::const_iterator end_line = std::find(begin_line, tokens.back().end, '\n');
1457 while(begin_line < end_line && isspace(*begin_line)) {
1458 ++begin_line;
1459 }
1460
1461 std::string whitespace;
1462 for(int n = 0; n < tok.begin - begin_line; ++n) {
1463 whitespace += " ";
1464 }
1465
1466 ASSERT_LOG(false, "ERROR WHILE PARSING FORMULA AT "
1467 << (filename_ ? *filename_ : "UNKNOWN") << ":"
1468 << (line_ + nline) << " " << error_msg << "\n"
1469 << std::string(begin_line, end_line) << "\n" << "^\n");
1470 }
1471 }
1472
1473 try {
1474 if(tokens.size() != 0) {
1475 expr_ = parse_expression(&tokens[0],&tokens[0] + tokens.size(), symbols, callable_definition);
1476 } else {
1477 expr_ = expression_ptr(new null_expression());
1478 }
1479 } catch(formula_error&) {
1480 std::cerr << "ERROR WHILE PARSING AT " << (filename_ ? *filename_ : "UNKNOWN") << ":" << line_ << "::\n" << str_ << "\n";
1481 throw;
1482 }
1483 }
1484
1485 formula::~formula() {
1486 if(last_executed_formula == this) {
1487 last_executed_formula = NULL;
1488 }
1489 }
1490
1491 void formula::output_debug_info() const
1492 {
1493 std::cerr << "FORMULA: ";
1494 if(filename_) {
1495 std::cerr << *filename_ << " " << line_ << ": ";
1496 }
1497
1498 std::cerr << str_ << "\n";
1499 }
1500
1501 variant formula::execute(const formula_callable& variables) const
1502 {
1503 last_executed_formula = this;
1504 try {
1505 return expr_->evaluate(variables);
1506 } catch(type_error& e) {
1507 if(filename_) {
1508 std::cerr << *filename_ << " " << line_ << ": ";
1509 }
1510 std::cerr << "formula type error: " << e.message << "\n";
1511
1512 //for now die a horrible death on a formula type error
1513 assert(false);
1514 return variant();
1515 }
1516 }
1517
1518 variant formula::execute() const
1519 {
1520 last_executed_formula = this;
1521
1522 map_formula_callable* null_callable = new map_formula_callable;
1523 variant ref(null_callable);
1524 return execute(*null_callable);
1525 }
1526
1527 UNIT_TEST(formula_in) {
1528 CHECK(formula("1 in [4,5,6]").execute() == variant(0), "test failed");
1529 CHECK(formula("5 in [4,5,6]").execute() == variant(1), "test failed");
1530 }
1531
1532 UNIT_TEST(formula_fn) {
1533 function_symbol_table symbols;
1534 CHECK(formula("def f(g) g(5) + 1; f(def(n) n*n)", &symbols).execute() == variant(26), "test failed");
1535 }
1536
1537 UNIT_TEST(array_index) {
1538 formula f("map(range(6), 'n', elements[n]) = elements "
1539 "where elements = [5, 6, 7, 8, 9, 10]");
1540 CHECK(f.execute() == variant(1), "test failed");
1541 }
1542
1543 UNIT_TEST(dot_precedence) {
1544 map_formula_callable* callable = new map_formula_callable;
1545 variant ref(callable);
1546 map_formula_callable* callable2 = new map_formula_callable;
1547 std::vector<variant> v;
1548 for(int n = 0; n != 10; ++n) {
1549 map_formula_callable* obj = new map_formula_callable;
1550 obj->add("value", variant(n));
1551 v.push_back(variant(obj));
1552 }
1553 callable2->add("item", variant(&v));
1554 callable->add("obj", variant(callable2));
1555 formula f("obj.item[n].value where n = 2");
1556 const variant result = f.execute(*callable);
1557 CHECK(result == variant(2), "test failed: " << result.to_debug_string());
1558 }
1559
1560 UNIT_TEST(short_circuit) {
1561 return;
1562 map_formula_callable* callable = new map_formula_callable;
1563 variant ref(callable);
1564 callable->add("x", variant(0));
1565 formula f("x and (5/x)");
1566 f.execute(*callable);
1567 }
1568
1569 BENCHMARK(formula_if) {
1570 static map_formula_callable* callable = new map_formula_callable;
1571 callable->add("x", variant(1));
1572 static formula f("if(x, 1, 0)");
1573 BENCHMARK_LOOP {
1574 f.execute(*callable);
1575 }
1576 }
1577
1578 BENCHMARK(formula_add) {
1579 static map_formula_callable* callable = new map_formula_callable;
1580 callable->add("x", variant(1));
1581 static formula f("x+1");
1582 BENCHMARK_LOOP {
1583 f.execute(*callable);
1584 }
1585 }
1586
1587 }
0 /* $Id: formula.hpp 24955 2008-03-21 23:11:31Z dragonking $ */
1 /*
2 Copyright (C) 2007 - 2008 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12
13 #ifndef FORMULA_HPP_INCLUDED
14 #define FORMULA_HPP_INCLUDED
15
16 #include <map>
17 #include <string>
18
19 #include "formula_callable_definition_fwd.hpp"
20 #include "formula_fwd.hpp"
21 #include "formula_function.hpp"
22 #include "variant.hpp"
23 #include "wml_value.hpp"
24
25 namespace game_logic
26 {
27
28 class formula_callable;
29 class formula_expression;
30 class function_symbol_table;
31 typedef boost::shared_ptr<formula_expression> expression_ptr;
32
33 class formula {
34 public:
35 static variant evaluate(const const_formula_ptr& f,
36 const formula_callable& variables,
37 variant default_res=variant(0)) {
38 if(f) {
39 return f->execute(variables);
40 } else {
41 return default_res;
42 }
43 }
44
45 // function which will create a formula that is a single string literal, 'str'.
46 // 'str' should not be enclosed in quotes.
47 static formula_ptr create_string_formula(const std::string& str);
48 static formula_ptr create_optional_formula(const wml::value& str, function_symbol_table* symbols=NULL, const formula_callable_definition* def=NULL);
49 explicit formula(const wml::value& val, function_symbol_table* symbols=NULL, const formula_callable_definition* def=NULL);
50 ~formula();
51 variant execute(const formula_callable& variables) const;
52 variant execute() const;
53 const std::string& str() const { return str_; }
54
55 void output_debug_info() const;
56
57 private:
58 formula() {}
59 expression_ptr expr_;
60 std::string str_;
61 const std::string* filename_;
62 int line_;
63 };
64
65 struct formula_error
66 {
67 formula_error();
68 };
69
70 }
71
72 #endif
0 /* $Id: formula_callable.hpp 25895 2008-04-17 18:57:13Z mordante $ */
1 /*
2 Copyright (C) 2008 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2
7 or at your option any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY.
10
11 See the COPYING file for more details.
12 */
13
14 #ifndef FORMULA_CALLABLE_HPP_INCLUDED
15 #define FORMULA_CALLABLE_HPP_INCLUDED
16
17 #include <iostream>
18 #include <map>
19 #include <string>
20
21 #include "reference_counted_object.hpp"
22 #include "variant.hpp"
23 #include "wml_node_fwd.hpp"
24
25 namespace game_logic
26 {
27
28 enum FORMULA_ACCESS_TYPE { FORMULA_READ_ONLY, FORMULA_WRITE_ONLY, FORMULA_READ_WRITE };
29 struct formula_input {
30 std::string name;
31 FORMULA_ACCESS_TYPE access;
32 explicit formula_input(const std::string& name, FORMULA_ACCESS_TYPE access=FORMULA_READ_WRITE)
33 : name(name), access(access)
34 {}
35 };
36
37 //interface for objects that can have formulae run on them
38 class formula_callable : public reference_counted_object {
39 public:
40 explicit formula_callable(bool has_self=true) : has_self_(has_self)
41 {}
42
43 variant query_value(const std::string& key) const {
44 if(has_self_ && key == "self") {
45 return variant(this);
46 }
47 return get_value(key);
48 }
49
50 variant query_value_by_slot(int slot) const {
51 return get_value_by_slot(slot);
52 }
53
54 void mutate_value(const std::string& key, const variant& value) {
55 set_value(key, value);
56 }
57
58 void mutate_value_by_slot(int slot, const variant& value) {
59 set_value_by_slot(slot, value);
60 }
61
62 std::vector<formula_input> inputs() const {
63 std::vector<formula_input> res;
64 get_inputs(&res);
65 return res;
66 }
67
68 bool equals(const formula_callable* other) const {
69 return do_compare(other) == 0;
70 }
71
72 bool less(const formula_callable* other) const {
73 return do_compare(other) < 0;
74 }
75
76 virtual void get_inputs(std::vector<formula_input>* /*inputs*/) const {};
77
78 void serialize(std::string& str) const {
79 serialize_to_string(str);
80 }
81
82 bool has_key(const std::string& key) const
83 { return !query_value(key).is_null(); }
84
85 protected:
86 virtual ~formula_callable() {}
87
88 virtual void set_value(const std::string& key, const variant& value);
89 virtual void set_value_by_slot(int slot, const variant& value);
90 virtual int do_compare(const formula_callable* callable) const {
91 return this < callable ? -1 : (this == callable ? 0 : 1);
92 }
93
94 virtual void serialize_to_string(std::string& str) const {
95 std::cerr << "CONTENTS {{{" << str << "}}}\n";
96 throw type_error("Tried to serialize type which cannot be serialized");
97 }
98 private:
99 virtual variant get_value(const std::string& key) const = 0;
100 virtual variant get_value_by_slot(int slot) const;
101 bool has_self_;
102 };
103
104 class formula_callable_no_ref_count : public formula_callable {
105 public:
106 formula_callable_no_ref_count() {
107 turn_reference_counting_off();
108 }
109 virtual ~formula_callable_no_ref_count() {}
110 };
111
112 class formula_callable_with_backup : public formula_callable {
113 const formula_callable& main_;
114 const formula_callable& backup_;
115 variant get_value(const std::string& key) const {
116 variant var = main_.query_value(key);
117 if(var.is_null()) {
118 return backup_.query_value(key);
119 }
120
121 return var;
122 }
123
124 void get_inputs(std::vector<formula_input>* inputs) const {
125 main_.get_inputs(inputs);
126 backup_.get_inputs(inputs);
127 }
128 public:
129 formula_callable_with_backup(const formula_callable& main, const formula_callable& backup) : formula_callable(false), main_(main), backup_(backup)
130 {}
131 };
132
133 class formula_variant_callable_with_backup : public formula_callable {
134 variant var_;
135 const formula_callable& backup_;
136 variant get_value(const std::string& key) const {
137 variant var = var_.get_member(key);
138 if(var.is_null()) {
139 return backup_.query_value(key);
140 }
141
142 return var;
143 }
144
145 void get_inputs(std::vector<formula_input>* inputs) const {
146 backup_.get_inputs(inputs);
147 }
148
149 public:
150 formula_variant_callable_with_backup(const variant& var, const formula_callable& backup) : formula_callable(false), var_(var), backup_(backup)
151 {}
152 };
153
154 class map_formula_callable : public formula_callable {
155 public:
156 explicit map_formula_callable(wml::const_node_ptr node);
157 explicit map_formula_callable(const formula_callable* fallback=NULL);
158 explicit map_formula_callable(const std::map<std::string, variant>& m);
159 void write(wml::node_ptr node) const;
160 map_formula_callable& add(const std::string& key, const variant& value);
161 void set_fallback(const formula_callable* fallback) { fallback_ = fallback; }
162 bool empty() const { return values_.empty(); }
163 void clear() { values_.clear(); }
164 bool contains(const std::string& key) const { return values_.count(key); }
165
166 const std::map<std::string, variant>& values() const { return values_; }
167
168 typedef std::map<std::string,variant>::const_iterator const_iterator;
169
170 const_iterator begin() const { return values_.begin(); }
171 const_iterator end() const { return values_.end(); }
172
173 variant& ref(const std::string& key) { return values_[key]; }
174
175 private:
176 //map_formula_callable(const map_formula_callable&);
177
178 variant get_value(const std::string& key) const;
179 void get_inputs(std::vector<formula_input>* inputs) const;
180 void set_value(const std::string& key, const variant& value);
181 std::map<std::string,variant> values_;
182 const formula_callable* fallback_;
183 };
184
185 typedef boost::intrusive_ptr<formula_callable> formula_callable_ptr;
186 typedef boost::intrusive_ptr<const formula_callable> const_formula_callable_ptr;
187
188 typedef boost::intrusive_ptr<map_formula_callable> map_formula_callable_ptr;
189 typedef boost::intrusive_ptr<const map_formula_callable> const_map_formula_callable_ptr;
190
191 }
192
193 #endif
0 #include <map>
1 #include <vector>
2
3 #include "foreach.hpp"
4 #include "formula_callable_definition.hpp"
5
6 namespace game_logic
7 {
8
9 namespace
10 {
11
12 class simple_definition : public formula_callable_definition
13 {
14 public:
15 int get_slot(const std::string& key) const {
16 int index = 0;
17 foreach(const entry& e, entries_) {
18 if(e.id == key) {
19 return index;
20 }
21
22 ++index;
23 }
24
25 return -1;
26 }
27
28 entry* get_entry(int slot) {
29 if(slot < 0 || slot >= entries_.size()) {
30 return NULL;
31 }
32
33 return &entries_[slot];
34 }
35
36 const entry* get_entry(int slot) const {
37 if(slot < 0 || slot >= entries_.size()) {
38 return NULL;
39 }
40
41 return &entries_[slot];
42 }
43
44 int num_slots() const { return entries_.size(); }
45
46 void add(const std::string& id) {
47 entries_.push_back(entry(id));
48 }
49
50 private:
51 std::vector<entry> entries_;
52 };
53
54 }
55
56 formula_callable_definition_ptr create_formula_callable_definition(const std::string* i1, const std::string* i2)
57 {
58 simple_definition* def = new simple_definition;
59 while(i1 != i2) {
60 def->add(*i1);
61 ++i1;
62 }
63
64 return formula_callable_definition_ptr(def);
65 }
66
67 namespace {
68 std::map<std::string, const formula_callable_definition*> registry;
69 int num_definitions = 0;
70 }
71
72 int register_formula_callable_definition(const std::string& id, const formula_callable_definition* def)
73 {
74 registry[id] = def;
75 return ++num_definitions;
76 }
77
78 const formula_callable_definition* get_formula_callable_definition(const std::string& id)
79 {
80 return registry[id];
81 }
82
83 }
0 #ifndef FORMULA_CALLABLE_DEFINITION_HPP_INCLUDED
1 #define FORMULA_CALLABLE_DEFINITION_HPP_INCLUDED
2
3 #include <string>
4
5 #include "formula_callable_definition_fwd.hpp"
6
7 namespace game_logic
8 {
9
10 class formula_callable_definition
11 {
12 public:
13 struct entry {
14 explicit entry(const std::string& id_) : id(id_), type_definition(0) {}
15 std::string id;
16 const formula_callable_definition* type_definition;
17
18 //optionally store the reference to the type definition.
19 const_formula_callable_definition_ptr type_definition_holder;
20 };
21
22 virtual ~formula_callable_definition() {}
23
24 virtual int get_slot(const std::string& key) const = 0;
25 virtual const entry* get_entry(int slot) const = 0;
26 virtual entry* get_entry(int slot) { return NULL; }
27 virtual int num_slots() const = 0;
28 };
29
30 formula_callable_definition_ptr create_formula_callable_definition(const std::string* beg, const std::string* end);
31
32 int register_formula_callable_definition(const std::string& id, const formula_callable_definition* def);
33 const formula_callable_definition* get_formula_callable_definition(const std::string& id);
34
35 }
36
37 #endif
0 #ifndef FORMULA_CALLABLE_DEFINITION_FWD_HPP_INCLUDED
1 #define FORMULA_CALLABLE_DEFINITION_FWD_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 namespace game_logic
6 {
7
8 class formula_callable_definition;
9
10 typedef boost::shared_ptr<formula_callable_definition> formula_callable_definition_ptr;
11 typedef boost::shared_ptr<const formula_callable_definition> const_formula_callable_definition_ptr;
12
13 }
14
15 #endif
16
0 #ifndef FORMULA_CALLABLE_UTILS_HPP_INCLUDED
1 #define FORMULA_CALLABLE_UTILS_HPP_INCLUDED
2
3 #include <boost/intrusive_ptr.hpp>
4
5 #include <vector>
6
7 #include "asserts.hpp"
8 #include "formula_callable.hpp"
9 #include "variant.hpp"
10
11 namespace game_logic
12 {
13
14 class slot_formula_callable : public formula_callable
15 {
16 public:
17 slot_formula_callable() : value_names_(NULL)
18 {}
19
20 void set_names(const std::vector<std::string>* names) {
21 value_names_ = names;
22 }
23 void set_fallback(const const_formula_callable_ptr& fallback) { fallback_ = fallback; }
24 void add(const variant& val) { values_.push_back(val); }
25
26 variant get_value(const std::string& key) const {
27 if(value_names_) {
28 for(int n = 0; n != value_names_->size(); ++n) {
29 if((*value_names_)[n] == key) {
30 return values_[n];
31 }
32 }
33 }
34
35 if(fallback_) {
36 return fallback_->query_value(key);
37 }
38 ASSERT_LOG(false, "GET VALUE " << key << " FROM SLOT CALLABLE");
39 return variant();
40 }
41
42 variant get_value_by_slot(int slot) const {
43 ASSERT_INDEX_INTO_VECTOR(slot, values_);
44 return values_[slot];
45 }
46
47 void clear() {
48 value_names_ = 0;
49 values_.clear();
50 fallback_ = const_formula_callable_ptr();
51 }
52
53 private:
54 const std::vector<std::string>* value_names_;
55 std::vector<variant> values_;
56 const_formula_callable_ptr fallback_;
57 };
58
59 }
60
61 #endif
0 #include <ctype.h>
1
2 #include "asserts.hpp"
3 #include "foreach.hpp"
4 #include "formula_constants.hpp"
5 #include "preferences.hpp"
6 #include "raster.hpp"
7 #include "wml_node.hpp"
8
9 namespace game_logic
10 {
11
12 namespace {
13 typedef std::map<std::string, variant> constants_map;
14 std::vector<constants_map> constants_stack;
15 }
16
17 variant get_constant(const std::string& id)
18 {
19 if(id == "DOUBLE_SCALE") {
20 return variant(preferences::double_scale());
21 } else if(id == "SCREEN_WIDTH") {
22 return variant(graphics::screen_width());
23 } else if(id == "SCREEN_HEIGHT") {
24 return variant(graphics::screen_height());
25 } else if(id == "LOW_END_SYSTEM") {
26 #if TARGET_OS_IPHONE
27 return variant(1);
28 #else
29 return variant(0);
30 #endif
31 } else if(id == "IPHONE_SYSTEM") {
32 #if TARGET_OS_IPHONE
33 return variant(1);
34 #else
35 return variant(preferences::sim_iphone() ? 1 : 0);
36 #endif
37 } else if(id == "HIGH_END_SYSTEM") {
38 return variant(!get_constant("LOW_END_SYSTEM").as_bool());
39 }
40
41 if(constants_stack.empty() == false) {
42 constants_map& m = constants_stack.back();
43 constants_map::const_iterator itor = m.find(id);
44 if(itor != m.end()) {
45 return itor->second;
46 }
47 }
48
49 return variant();
50 }
51
52 constants_loader::constants_loader(wml::const_node_ptr node) : same_as_base_(false)
53 {
54 constants_map m;
55 if(node) {
56 for(wml::node::const_attr_iterator i = node->begin_attr(); i != node->end_attr(); ++i) {
57 const std::string& attr = i->first;
58 if(std::find_if(attr.begin(), attr.end(), islower) != attr.end()) {
59 //only all upper case are loaded as consts
60 continue;
61 }
62
63 m[attr].serialize_from_string(i->second);
64 }
65 }
66
67 if(constants_stack.empty() == false && constants_stack.back() == m) {
68 same_as_base_ = true;
69 } else if(constants_stack.empty() == false) {
70 std::cerr << "CONSTANTS ARE DIFFERENT: ";
71 for(constants_map::const_iterator i = m.begin(); i != m.end(); ++i) {
72 if(constants_stack.back().count(i->first) == 0) {
73 std::cerr << "NOT FOUND " << i->first << " ";
74 } else if(i->second != constants_stack.back()[i->first]) {
75 std::cerr << "DIFF " << i->first << " ";
76 }
77 }
78
79 const constants_map& m2 = constants_stack.back();
80 for(constants_map::const_iterator i = m2.begin(); i != m2.end(); ++i) {
81 if(m.count(i->first) == 0) {
82 std::cerr << "INSERTED " << i->first << " ";
83 }
84 }
85
86 std::cerr << "\n";
87 }
88
89 std::cerr << "ADD CONSTANTS_STACK ";
90 for(constants_map::const_iterator i = m.begin(); i != m.end(); ++i) {
91 std::cerr << i->first << " ";
92 }
93
94 std::cerr << "\n";
95
96 constants_stack.push_back(m);
97 }
98
99 constants_loader::~constants_loader()
100 {
101 ASSERT_EQ(constants_stack.empty(), false);
102 constants_stack.pop_back();
103 std::cerr << "REMOVE CONSTANTS_STACK\n";
104 }
105
106 }
0 #ifndef FORMULA_CONSTANTS_HPP_INCLUDED
1 #define FORMULA_CONSTANTS_HPP_INCLUDED
2
3 #include "variant.hpp"
4 #include "wml_node_fwd.hpp"
5
6 #include <string>
7
8 namespace game_logic
9 {
10
11 variant get_constant(const std::string& id);
12
13 class constants_loader
14 {
15 public:
16 explicit constants_loader(wml::const_node_ptr node);
17 ~constants_loader();
18
19 bool same_as_base() const { return same_as_base_; }
20
21 private:
22 bool same_as_base_;
23 };
24
25 }
26
27 #endif
0 /* $Id: formula_function.cpp 25895 2008-04-17 18:57:13Z mordante $ */
1 /*
2 Copyright (C) 2008 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2
7 or at your option any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY.
10
11 See the COPYING file for more details.
12 */
13
14 #include <iostream>
15 #include <math.h>
16
17 #include "foreach.hpp"
18 #include "formula.hpp"
19 #include "formula_callable.hpp"
20 #include "formula_callable_utils.hpp"
21 #include "formula_function.hpp"
22
23 #include "SDL.h"
24
25 namespace game_logic {
26
27 variant formula_expression::execute_member(const formula_callable& variables, std::string& id) const
28 {
29 throw formula_error();
30 }
31
32 namespace {
33
34 class dir_function : public function_expression {
35 public:
36 explicit dir_function(const args_list& args)
37 : function_expression("dir", args, 1, 1)
38 {}
39
40 private:
41 variant execute(const formula_callable& variables) const {
42 variant var = args()[0]->evaluate(variables);
43 const formula_callable* callable = var.as_callable();
44 std::vector<formula_input> inputs = callable->inputs();
45 std::vector<variant> res;
46 for(size_t i=0; i<inputs.size(); ++i) {
47 const formula_input& input = inputs[i];
48 res.push_back(variant(input.name));
49 }
50
51 return variant(&res);
52 }
53 };
54
55 class if_function : public function_expression {
56 public:
57 explicit if_function(const args_list& args)
58 : function_expression("if", args, 2, 3)
59 {}
60
61 expression_ptr optimize() const {
62 variant v;
63 if(args()[0]->can_reduce_to_variant(v)) {
64 if(v.as_bool()) {
65 return args()[1];
66 } else {
67 if(args().size() == 3) {
68 return args()[2];
69 } else {
70 return expression_ptr(new variant_expression(variant()));
71 }
72 }
73 }
74
75 return expression_ptr();
76 }
77
78 private:
79 variant execute(const formula_callable& variables) const {
80 const int i = args()[0]->evaluate(variables).as_bool() ? 1 : 2;
81 if(i >= args().size()) {
82 return variant();
83 }
84 return args()[i]->evaluate(variables);
85 }
86 };
87
88 class switch_function : public function_expression {
89 public:
90 explicit switch_function(const args_list& args)
91 : function_expression("switch", args, 3, -1)
92 {}
93
94 private:
95 variant execute(const formula_callable& variables) const {
96 variant var = args()[0]->evaluate(variables);
97 for(size_t n = 1; n < args().size()-1; n += 2) {
98 variant val = args()[n]->evaluate(variables);
99 if(val == var) {
100 return args()[n+1]->evaluate(variables);
101 }
102 }
103
104 if((args().size()%2) == 0) {
105 return args().back()->evaluate(variables);
106 } else {
107 return variant();
108 }
109 }
110 };
111
112 class rgb_function : public function_expression {
113 public:
114 explicit rgb_function(const args_list& args)
115 : function_expression("rgb", args, 3, 3)
116 {}
117
118 private:
119 variant execute(const formula_callable& variables) const {
120 return variant(10000*
121 std::min<int>(99,std::max<int>(0,args()[0]->evaluate(variables).as_int())) +
122 std::min<int>(99,std::max<int>(0,args()[1]->evaluate(variables).as_int()))*100+
123 std::min<int>(99,std::max<int>(0,args()[2]->evaluate(variables).as_int())));
124 }
125 };
126
127 namespace {
128 int transition(int begin, int val1, int end, int val2, int value) {
129 if(value < begin || value > end) {
130 return 0;
131 }
132
133 if(value == begin) {
134 return val1;
135 } else if(value == end) {
136 return val2;
137 }
138
139 const int comp1 = val1*(end - value);
140 const int comp2 = val2*(value - begin);
141 return (comp1 + comp2)/(end - begin);
142 }
143 }
144
145 class transition_function : public function_expression {
146 public:
147 explicit transition_function(const args_list& args)
148 : function_expression("transition", args, 5, 5)
149 {}
150 private:
151 variant execute(const formula_callable& variables) const {
152 const int value = args()[0]->evaluate(variables).as_int();
153 const int begin = args()[1]->evaluate(variables).as_int();
154 const int end = args()[3]->evaluate(variables).as_int();
155 if(value < begin || value > end) {
156 return variant(0);
157 }
158 const int val1 = args()[2]->evaluate(variables).as_int();
159 const int val2 = args()[4]->evaluate(variables).as_int();
160 return variant(transition(begin, val1, end, val2, value));
161 }
162 };
163
164 class color_transition_function : public function_expression {
165 public:
166 explicit color_transition_function(const args_list& args)
167 : function_expression("color_transition", args, 5)
168 {}
169 private:
170 variant execute(const formula_callable& variables) const {
171 const int value = args()[0]->evaluate(variables).as_int();
172 int begin = args()[1]->evaluate(variables).as_int();
173 int end = -1;
174 size_t n = 3;
175 while(n < args().size()) {
176 end = args()[n]->evaluate(variables).as_int();
177 if(value >= begin && value <= end) {
178 break;
179 }
180
181 begin = end;
182 n += 2;
183 }
184
185 if(value < begin || value > end) {
186 return variant(0);
187 }
188 const int val1 = args()[n-1]->evaluate(variables).as_int();
189 const int val2 = args()[n+1 < args().size() ? n+1 : n]->
190 evaluate(variables).as_int();
191 const int r1 = (val1/10000)%100;
192 const int g1 = (val1/100)%100;
193 const int b1 = (val1)%100;
194 const int r2 = (val2/10000)%100;
195 const int g2 = (val2/100)%100;
196 const int b2 = (val2)%100;
197
198 const int r = transition(begin,r1,end,r2,value);
199 const int g = transition(begin,g1,end,g2,value);
200 const int b = transition(begin,b1,end,b2,value);
201 return variant(
202 std::min<int>(99,std::max<int>(0,r))*100*100 +
203 std::min<int>(99,std::max<int>(0,g))*100+
204 std::min<int>(99,std::max<int>(0,b)));
205 }
206 };
207
208
209 class abs_function : public function_expression {
210 public:
211 explicit abs_function(const args_list& args)
212 : function_expression("abs", args, 1, 1)
213 {}
214
215 private:
216 variant execute(const formula_callable& variables) const {
217 const int n = args()[0]->evaluate(variables).as_int();
218 return variant(n >= 0 ? n : -n);
219 }
220 };
221
222 class min_function : public function_expression {
223 public:
224 explicit min_function(const args_list& args)
225 : function_expression("min", args, 1, -1)
226 {}
227
228 private:
229 variant execute(const formula_callable& variables) const {
230 bool found = false;
231 int res = 0;
232 for(size_t n = 0; n != args().size(); ++n) {
233 const variant v = args()[n]->evaluate(variables);
234 if(v.is_list()) {
235 for(size_t m = 0; m != v.num_elements(); ++m) {
236 if(!found || v[m].as_int() < res) {
237 res = v[m].as_int();
238 found = true;
239 }
240 }
241 } else if(v.is_int()) {
242 if(!found || v.as_int() < res) {
243 res = v.as_int();
244 found = true;
245 }
246 }
247 }
248
249 return variant(res);
250 }
251 };
252
253 class max_function : public function_expression {
254 public:
255 explicit max_function(const args_list& args)
256 : function_expression("max", args, 1, -1)
257 {}
258
259 private:
260 variant execute(const formula_callable& variables) const {
261 bool found = false;
262 int res = 0;
263 for(size_t n = 0; n != args().size(); ++n) {
264 const variant v = args()[n]->evaluate(variables);
265 if(v.is_list()) {
266 for(size_t m = 0; m != v.num_elements(); ++m) {
267 if(!found || v[m].as_int() > res) {
268 res = v[m].as_int();
269 found = true;
270 }
271 }
272 } else if(v.is_int()) {
273 if(!found || v.as_int() > res) {
274 res = v.as_int();
275 found = true;
276 }
277 }
278 }
279
280 return variant(res);
281 }
282 };
283
284 class keys_function : public function_expression {
285 public:
286 explicit keys_function(const args_list& args)
287 : function_expression("keys", args, 1, 1)
288 {}
289
290 private:
291 variant execute(const formula_callable& variables) const {
292 const variant map = args()[0]->evaluate(variables);
293 return map.get_keys();
294 }
295 };
296
297 class values_function : public function_expression {
298 public:
299 explicit values_function(const args_list& args)
300 : function_expression("values", args, 1, 1)
301 {}
302
303 private:
304 variant execute(const formula_callable& variables) const {
305 const variant map = args()[0]->evaluate(variables);
306 return map.get_values();
307 }
308 };
309
310 class choose_function : public function_expression {
311 public:
312 explicit choose_function(const args_list& args)
313 : function_expression("choose", args, 1, 2)
314 {}
315
316 private:
317 variant execute(const formula_callable& variables) const {
318 const variant items = args()[0]->evaluate(variables);
319 int max_index = -1;
320 variant max_value;
321 for(size_t n = 0; n != items.num_elements(); ++n) {
322 variant val;
323
324 if(args().size() >= 2) {
325 formula_callable_ptr callable(new formula_variant_callable_with_backup(items[n], variables));
326 val = args()[1]->evaluate(*callable);
327 } else {
328 val = variant(rand());
329 }
330
331 if(max_index == -1 || val > max_value) {
332 max_index = n;
333 max_value = val;
334 }
335 }
336
337 if(max_index == -1) {
338 return variant();
339 } else {
340 return items[max_index];
341 }
342 }
343 };
344
345 class wave_function : public function_expression {
346 public:
347 explicit wave_function(const args_list& args)
348 : function_expression("wave", args, 1, 1)
349 {}
350
351 private:
352 variant execute(const formula_callable& variables) const {
353 const int value = args()[0]->evaluate(variables).as_int()%1000;
354 const double angle = 2.0*3.141592653589*(static_cast<double>(value)/1000.0);
355 return variant(static_cast<int>(sin(angle)*1000.0));
356 }
357 };
358
359 namespace {
360 class variant_comparator : public formula_callable {
361 expression_ptr expr_;
362 const formula_callable* fallback_;
363 mutable variant a_, b_;
364 variant get_value(const std::string& key) const {
365 if(key == "a") {
366 return a_;
367 } else if(key == "b") {
368 return b_;
369 } else {
370 return fallback_->query_value(key);
371 }
372 }
373
374 void get_inputs(std::vector<formula_input>* inputs) const {
375 fallback_->get_inputs(inputs);
376 }
377 public:
378 variant_comparator(const expression_ptr& expr, const formula_callable& fallback) : formula_callable(false), expr_(expr), fallback_(&fallback)
379 {}
380
381 bool operator()(const variant& a, const variant& b) const {
382 a_ = a;
383 b_ = b;
384 return expr_->evaluate(*this).as_bool();
385 }
386 };
387 }
388
389 class sort_function : public function_expression {
390 public:
391 explicit sort_function(const args_list& args)
392 : function_expression("sort", args, 1, 2)
393 {}
394
395 private:
396 variant execute(const formula_callable& variables) const {
397 variant list = args()[0]->evaluate(variables);
398 std::vector<variant> vars;
399 vars.reserve(list.num_elements());
400 for(size_t n = 0; n != list.num_elements(); ++n) {
401 vars.push_back(list[n]);
402 }
403
404 if(args().size() == 1) {
405 std::sort(vars.begin(), vars.end());
406 } else {
407 std::sort(vars.begin(), vars.end(), variant_comparator(args()[1], variables));
408 }
409
410 return variant(&vars);
411 }
412 };
413
414 class filter_function : public function_expression {
415 public:
416 explicit filter_function(const args_list& args)
417 : function_expression("filter", args, 2, 3)
418 {}
419 private:
420 variant execute(const formula_callable& variables) const {
421 std::vector<variant> vars;
422 const variant items = args()[0]->evaluate(variables);
423 if(args().size() == 2) {
424 for(size_t n = 0; n != items.num_elements(); ++n) {
425 formula_callable_ptr callable(new formula_variant_callable_with_backup(items[n], variables));
426 const variant val = args()[1]->evaluate(*callable);
427 if(val.as_bool()) {
428 vars.push_back(items[n]);
429 }
430 }
431 } else {
432 map_formula_callable* self_callable = new map_formula_callable;
433 formula_callable_ptr callable(self_callable);
434 self_callable->add("context", variant(&variables));
435 const std::string self = args()[1]->evaluate(variables).as_string();
436 for(size_t n = 0; n != items.num_elements(); ++n) {
437 self_callable->add(self, items[n]);
438 formula_callable_ptr callable_with_backup(new formula_variant_callable_with_backup(items[n], variables));
439 formula_callable_ptr callable_ptr(new formula_callable_with_backup(*self_callable, *callable_with_backup));
440 const variant val = args()[2]->evaluate(*callable_ptr);
441 if(val.as_bool()) {
442 vars.push_back(items[n]);
443 }
444 }
445 }
446
447 return variant(&vars);
448 }
449 };
450
451 class mapping_function : public function_expression {
452 public:
453 explicit mapping_function(const args_list& args)
454 : function_expression("mapping", args, -1, -1)
455 {}
456
457 private:
458 variant execute(const formula_callable& variables) const {
459 map_formula_callable* callable = new map_formula_callable;
460 for(size_t n = 0; n < args().size()-1; n += 2) {
461 callable->add(args()[n]->evaluate(variables).as_string(),
462 args()[n+1]->evaluate(variables));
463 }
464
465 return variant(callable);
466 }
467 };
468
469 class find_function : public function_expression {
470 public:
471 explicit find_function(const args_list& args)
472 : function_expression("find", args, 2, 3)
473 {}
474
475 private:
476 variant execute(const formula_callable& variables) const {
477 const variant items = args()[0]->evaluate(variables);
478
479 if(args().size() == 2) {
480 for(size_t n = 0; n != items.num_elements(); ++n) {
481 formula_callable_ptr callable(new formula_variant_callable_with_backup(items[n], variables));
482 const variant val = args()[1]->evaluate(*callable);
483 if(val.as_bool()) {
484 return items[n];
485 }
486 }
487 } else {
488 map_formula_callable* self_callable = new map_formula_callable;
489 formula_callable_ptr callable(self_callable);
490 const std::string self = args()[1]->evaluate(variables).as_string();
491 for(size_t n = 0; n != items.num_elements(); ++n) {
492 self_callable->add(self, items[n]);
493
494 boost::intrusive_ptr<formula_variant_callable_with_backup> callable_backup(new formula_variant_callable_with_backup(items[n], variables));
495
496 formula_callable_ptr callable(new formula_callable_with_backup(*self_callable, *callable_backup));
497 const variant val = args().back()->evaluate(*callable);
498 if(val.as_bool()) {
499 return items[n];
500 }
501 }
502 }
503
504 return variant();
505 }
506 };
507
508 class transform_callable : public formula_callable {
509 public:
510 explicit transform_callable(const formula_callable& backup)
511 : backup_(backup)
512 {}
513
514 void set(const variant& v, const variant& i)
515 {
516 value_ = v;
517 index_ = i;
518 }
519 private:
520 variant get_value(const std::string& key) const {
521 if(key == "v") {
522 return value_;
523 } else if(key == "i") {
524 return index_;
525 } else {
526 return backup_.query_value(key);
527 }
528 }
529
530 variant get_value_by_slot(int slot) const {
531 return backup_.query_value_by_slot(slot);
532 }
533
534 const formula_callable& backup_;
535 variant value_, index_;
536 };
537
538 class transform_function : public function_expression {
539 public:
540 explicit transform_function(const args_list& args)
541 : function_expression("transform", args, 2, 2)
542 {}
543 private:
544 variant execute(const formula_callable& variables) const {
545 std::vector<variant> vars;
546 const variant items = args()[0]->evaluate(variables);
547
548 vars.reserve(items.num_elements());
549
550 transform_callable* callable = new transform_callable(variables);
551 variant v(callable);
552
553 const int nitems = items.num_elements();
554 for(size_t n = 0; n != nitems; ++n) {
555 callable->set(items[n], variant(n));
556 const variant val = args().back()->evaluate(*callable);
557 vars.push_back(val);
558 }
559
560 return variant(&vars);
561 }
562 };
563
564 class map_function : public function_expression {
565 public:
566 explicit map_function(const args_list& args)
567 : function_expression("map", args, 2, 3)
568 {}
569 private:
570 variant execute(const formula_callable& variables) const {
571 std::vector<variant> vars;
572 const variant items = args()[0]->evaluate(variables);
573
574 vars.reserve(items.num_elements());
575
576 if(args().size() == 2) {
577 for(size_t n = 0; n != items.num_elements(); ++n) {
578 formula_callable_ptr callable(new formula_variant_callable_with_backup(items[n], variables));
579 const variant val = args().back()->evaluate(*callable);
580 vars.push_back(val);
581 }
582 } else {
583 static const std::string index_str = "index";
584 map_formula_callable* self_callable = new map_formula_callable;
585 formula_callable_ptr callable_ref(self_callable);
586 self_callable->add("context", variant(&variables));
587 const std::string self = args()[1]->evaluate(variables).as_string();
588 for(size_t n = 0; n != items.num_elements(); ++n) {
589 self_callable->add(self, items[n]);
590 self_callable->add(index_str, variant(n));
591
592 formula_callable_ptr callable_backup(new formula_callable_with_backup(*self_callable, variables));
593 const variant val = args().back()->evaluate(*callable_backup);
594 vars.push_back(val);
595 }
596 }
597
598 return variant(&vars);
599 }
600 };
601
602 class sum_function : public function_expression {
603 public:
604 explicit sum_function(const args_list& args)
605 : function_expression("sum", args, 1, 2)
606 {}
607 private:
608 variant execute(const formula_callable& variables) const {
609 variant res(0);
610 const variant items = args()[0]->evaluate(variables);
611 if(args().size() >= 2) {
612 res = args()[1]->evaluate(variables);
613 }
614 for(size_t n = 0; n != items.num_elements(); ++n) {
615 res = res + items[n];
616 }
617
618 return res;
619 }
620 };
621
622 class range_function : public function_expression {
623 public:
624 explicit range_function(const args_list& args)
625 : function_expression("range", args, 1, 1)
626 {}
627 private:
628 variant execute(const formula_callable& variables) const {
629 const int nelem = args()[0]->evaluate(variables).as_int();
630 std::vector<variant> v;
631
632 if(nelem > 0) {
633 v.reserve(nelem);
634
635 for(int n = 0; n < nelem; ++n) {
636 v.push_back(variant(n));
637 }
638 }
639
640 return variant(&v);
641 }
642 };
643
644 class head_function : public function_expression {
645 public:
646 explicit head_function(const args_list& args)
647 : function_expression("head", args, 1, 1)
648 {}
649 private:
650 variant execute(const formula_callable& variables) const {
651 const variant items = args()[0]->evaluate(variables);
652 return items[0];
653 }
654 };
655
656 class size_function : public function_expression {
657 public:
658 explicit size_function(const args_list& args)
659 : function_expression("size", args, 1, 1)
660 {}
661 private:
662 variant execute(const formula_callable& variables) const {
663 const variant items = args()[0]->evaluate(variables);
664 return variant(static_cast<int>(items.num_elements()));
665 }
666 };
667
668 class slice_function : public function_expression {
669 public:
670 explicit slice_function(const args_list& args)
671 : function_expression("slice", args, 3, 3)
672 {}
673 private:
674 variant execute(const formula_callable& variables) const {
675 const variant list = args()[0]->evaluate(variables);
676 if(list.num_elements() == 0) {
677 return variant();
678 }
679 int begin_index = args()[1]->evaluate(variables).as_int()%list.num_elements();
680 int end_index = args()[2]->evaluate(variables).as_int()%list.num_elements();
681 if(end_index >= begin_index) {
682 std::vector<variant> result;
683 result.reserve(end_index - begin_index);
684 while(begin_index != end_index) {
685 result.push_back(list[begin_index++]);
686 }
687
688 return variant(&result);
689 } else {
690 return variant();
691 }
692 }
693 };
694
695 class str_function : public function_expression {
696 public:
697 explicit str_function(const args_list& args)
698 : function_expression("str", args, 1, 1)
699 {}
700 private:
701 variant execute(const formula_callable& variables) const {
702 const variant item = args()[0]->evaluate(variables);
703 std::string str;
704 item.serialize_to_string(str);
705 return variant(str);
706 }
707 };
708
709 class strstr_function : public function_expression {
710 public:
711 explicit strstr_function(const args_list& args)
712 : function_expression("strstr", args, 2, 2)
713 {}
714 private:
715 variant execute(const formula_callable& variables) const {
716 const std::string& haystack = args()[0]->evaluate(variables).as_string();
717 const std::string& needle = args()[1]->evaluate(variables).as_string();
718 return variant(strstr(haystack.c_str(), needle.c_str()) != NULL);
719 }
720 };
721
722 class null_function : public function_expression {
723 public:
724 explicit null_function(const args_list& args)
725 : function_expression("null", args, 0, 0)
726 {}
727 private:
728 variant execute(const formula_callable& /*variables*/) const {
729 return variant();
730 }
731 };
732
733 class refcount_function : public function_expression {
734 public:
735 explicit refcount_function(const args_list& args)
736 : function_expression("refcount", args, 1, 1)
737 {}
738 private:
739 variant execute(const formula_callable& variables) const {
740 return variant(args()[0]->evaluate(variables).refcount());
741 }
742 };
743
744 class deserialize_function : public function_expression {
745 public:
746 explicit deserialize_function(const args_list& args)
747 : function_expression("deserialize", args, 1, 1)
748 {}
749
750 private:
751 variant execute(const formula_callable& variables) const {
752 const intptr_t id = strtoll(args()[0]->evaluate(variables).as_string().c_str(), NULL, 16);
753 return variant::create_variant_under_construction(id);
754 }
755 };
756
757 }
758
759 formula_function_expression::formula_function_expression(const std::string& name, const args_list& args, const_formula_ptr formula, const_formula_ptr precondition, const std::vector<std::string>& arg_names)
760 : function_expression(name, args, arg_names.size(), arg_names.size()),
761 formula_(formula), precondition_(precondition), arg_names_(arg_names), star_arg_(-1)
762 {
763 for(size_t n = 0; n != arg_names_.size(); ++n) {
764 if(arg_names_.empty() == false && arg_names_[n][arg_names_[n].size()-1] == '*') {
765 arg_names_[n].resize(arg_names_[n].size()-1);
766 star_arg_ = n;
767 break;
768 }
769 }
770 }
771
772 variant formula_function_expression::execute(const formula_callable& variables) const
773 {
774 if(!callable_ || callable_->refcount() != 1) {
775 callable_ = boost::intrusive_ptr<slot_formula_callable>(new slot_formula_callable);
776 }
777
778 callable_->set_names(&arg_names_);
779
780 //we reset callable_ to NULL during any calls so that recursive calls
781 //will work properly.
782 boost::intrusive_ptr<slot_formula_callable> tmp_callable(callable_);
783 callable_.reset(NULL);
784
785 for(size_t n = 0; n != arg_names_.size(); ++n) {
786 variant var = args()[n]->evaluate(variables);
787 tmp_callable->add(var);
788 if(static_cast<int>(n) == star_arg_) {
789 tmp_callable->set_fallback(var.as_callable());
790 }
791 }
792
793 if(precondition_) {
794 if(!precondition_->execute(*tmp_callable).as_bool()) {
795 std::cerr << "FAILED function precondition for function '" << formula_->str() << "' with arguments: ";
796 for(size_t n = 0; n != arg_names_.size(); ++n) {
797 std::cerr << " arg " << (n+1) << ": " << args()[n]->evaluate(variables).to_debug_string() << "\n";
798 }
799 }
800 }
801
802 variant res = formula_->execute(*tmp_callable);
803
804 callable_ = tmp_callable;
805 callable_->clear();
806
807 return res;
808 }
809
810 formula_function_expression_ptr formula_function::generate_function_expression(const std::vector<expression_ptr>& args) const
811 {
812 return formula_function_expression_ptr(new formula_function_expression(name_, args, formula_, precondition_, args_));
813 }
814
815 void function_symbol_table::add_formula_function(const std::string& name, const_formula_ptr formula, const_formula_ptr precondition, const std::vector<std::string>& args)
816 {
817 custom_formulas_[name] = formula_function(name, formula, precondition, args);
818 }
819
820 expression_ptr function_symbol_table::create_function(const std::string& fn, const std::vector<expression_ptr>& args, const formula_callable_definition* callable_def) const
821 {
822 const std::map<std::string, formula_function>::const_iterator i = custom_formulas_.find(fn);
823 if(i != custom_formulas_.end()) {
824 return i->second.generate_function_expression(args);
825 }
826
827 if(backup_) {
828 return backup_->create_function(fn, args, callable_def);
829 }
830
831 return expression_ptr();
832 }
833
834 std::vector<std::string> function_symbol_table::get_function_names() const
835 {
836 std::vector<std::string> res;
837 for(std::map<std::string, formula_function>::const_iterator iter = custom_formulas_.begin(); iter != custom_formulas_.end(); iter++ ) {
838 res.push_back((*iter).first);
839 }
840 return res;
841 }
842
843 recursive_function_symbol_table::recursive_function_symbol_table(const std::string& fn, const std::vector<std::string>& args, function_symbol_table* backup)
844 : name_(fn), stub_(fn, const_formula_ptr(), const_formula_ptr(), args), backup_(backup)
845 {
846 }
847
848 expression_ptr recursive_function_symbol_table::create_function(
849 const std::string& fn,
850 const std::vector<expression_ptr>& args,
851 const formula_callable_definition* callable_def) const
852 {
853 if(fn == name_) {
854 formula_function_expression_ptr expr = stub_.generate_function_expression(args);
855 expr_.push_back(expr);
856 return expr;
857 } else if(backup_) {
858 return backup_->create_function(fn, args, callable_def);
859 }
860
861 return expression_ptr();
862 }
863
864 void recursive_function_symbol_table::resolve_recursive_calls(const_formula_ptr f)
865 {
866 foreach(formula_function_expression_ptr& fn, expr_) {
867 fn->set_formula(f);
868 }
869 }
870
871 namespace {
872
873 class base_function_creator {
874 public:
875 virtual expression_ptr create_function(const std::vector<expression_ptr>& args) const = 0;
876 virtual ~base_function_creator() {}
877 };
878
879 template<typename T>
880 class function_creator : public base_function_creator {
881 public:
882 virtual expression_ptr create_function(const std::vector<expression_ptr>& args) const {
883 return expression_ptr(new T(args));
884 }
885 virtual ~function_creator() {}
886 };
887
888 typedef std::map<std::string, base_function_creator*> functions_map;
889
890 functions_map& get_functions_map() {
891
892 static functions_map functions_table;
893
894 if(functions_table.empty()) {
895 #define FUNCTION(name) functions_table[#name] = new function_creator<name##_function>();
896 FUNCTION(dir);
897 FUNCTION(if);
898 FUNCTION(switch);
899 FUNCTION(abs);
900 FUNCTION(min);
901 FUNCTION(max);
902 FUNCTION(choose);
903 FUNCTION(wave);
904 FUNCTION(sort);
905 FUNCTION(filter);
906 FUNCTION(mapping);
907 FUNCTION(find);
908 FUNCTION(transform);
909 FUNCTION(map);
910 FUNCTION(sum);
911 FUNCTION(range);
912 FUNCTION(head);
913 FUNCTION(rgb);
914 FUNCTION(transition);
915 FUNCTION(color_transition);
916 FUNCTION(size);
917 FUNCTION(slice);
918 FUNCTION(str);
919 FUNCTION(strstr);
920 FUNCTION(null);
921 FUNCTION(refcount);
922 FUNCTION(keys);
923 FUNCTION(values);
924 FUNCTION(deserialize);
925 #undef FUNCTION
926 }
927
928 return functions_table;
929 }
930
931 }
932
933 expression_ptr create_function(const std::string& fn,
934 const std::vector<expression_ptr>& args,
935 const function_symbol_table* symbols,
936 const formula_callable_definition* callable_def)
937 {
938 if(symbols) {
939 expression_ptr res(symbols->create_function(fn, args, callable_def));
940 if(res) {
941 return res;
942 }
943 }
944
945 functions_map::const_iterator i = get_functions_map().find(fn);
946 if(i == get_functions_map().end()) {
947 return expression_ptr();
948 }
949
950 return i->second->create_function(args);
951 }
952
953 bool optimize_function_arguments(const std::string& fn,
954 const function_symbol_table* symbols)
955 {
956 static const std::string disable_optimization_functions[] = {
957 "choose", "filter", "find", "map",
958 };
959
960 for(int n = 0; n < sizeof(disable_optimization_functions)/sizeof(*disable_optimization_functions); ++n) {
961 if(disable_optimization_functions[n] == fn) {
962 return false;
963 }
964 }
965
966 return true;
967 }
968
969 std::vector<std::string> builtin_function_names()
970 {
971 std::vector<std::string> res;
972 const functions_map& m = get_functions_map();
973 for(functions_map::const_iterator i = m.begin(); i != m.end(); ++i) {
974 res.push_back(i->first);
975 }
976
977 return res;
978 }
979
980 function_expression::function_expression(
981 const std::string& name,
982 const args_list& args,
983 int min_args, int max_args)
984 : name_(name), args_(args)
985 {
986 set_name(name.c_str());
987 if(min_args >= 0 && args_.size() < static_cast<size_t>(min_args)) {
988 std::cerr << "ERROR: incorrect number of arguments to function '" << name << "': expected [" << min_args << "," << max_args << "], found " << args_.size() << "\n";
989 throw formula_error();
990 }
991
992 if(max_args >= 0 && args_.size() > static_cast<size_t>(max_args)) {
993 std::cerr << "ERROR: incorrect number of arguments to function '" << name << "': expected [" << min_args << "," << max_args << "], found " << args_.size() << "\n";
994 throw formula_error();
995 }
996 }
997
998
999 }
0 /* $Id: formula_function.hpp 25643 2008-04-06 21:36:24Z ilor $ */
1 /*
2 Copyright (C) 2008 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2
7 or at your option any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY.
10
11 See the COPYING file for more details.
12 */
13
14 #ifndef FORMULA_FUNCTION_HPP_INCLUDED
15 #define FORMULA_FUNCTION_HPP_INCLUDED
16
17 #include <boost/intrusive_ptr.hpp>
18 #include <boost/shared_ptr.hpp>
19
20 #include <iostream>
21 #include <map>
22
23 #include "formula_callable_definition_fwd.hpp"
24 #include "formula_callable_utils.hpp"
25 #include "formula_fwd.hpp"
26 #include "variant.hpp"
27
28 namespace game_logic {
29
30 class formula_expression;
31 typedef boost::shared_ptr<formula_expression> expression_ptr;
32
33 class formula_expression {
34 public:
35 formula_expression() : name_(NULL) {}
36 explicit formula_expression(const char* name) : name_(name) {}
37 virtual ~formula_expression() {}
38 virtual variant static_evaluate(const formula_callable& variables) const {
39 return evaluate(variables);
40 }
41
42 virtual bool is_identifier(std::string* id) const {
43 return false;
44 }
45
46 virtual variant is_literal() const {
47 return variant();
48 }
49
50 variant evaluate(const formula_callable& variables) const {
51 #if !TARGET_OS_IPHONE
52 // call_stack_manager manager(str_.c_str());
53 #endif
54 return execute(variables);
55 }
56
57 variant evaluate_with_member(const formula_callable& variables, std::string& id) const {
58 #if !TARGET_OS_IPHONE
59 // call_stack_manager manager(str_.c_str());
60 #endif
61 return execute_member(variables, id);
62 }
63
64 virtual expression_ptr optimize() const {
65 return expression_ptr();
66 }
67
68 virtual bool can_reduce_to_variant(variant& v) const {
69 return false;
70 }
71
72 virtual const formula_callable_definition* get_type_definition() const {
73 return NULL;
74 }
75
76 void set_name(const char* name) { name_ = name; }
77 void set_str(const std::string& str) { str_ = str; }
78 const std::string& str() const { return str_; }
79 protected:
80 virtual variant execute_member(const formula_callable& variables, std::string& id) const;
81 private:
82 virtual variant execute(const formula_callable& variables) const = 0;
83 const char* name_;
84 std::string str_;
85 };
86
87 class function_expression : public formula_expression {
88 public:
89 typedef std::vector<expression_ptr> args_list;
90 explicit function_expression(
91 const std::string& name,
92 const args_list& args,
93 int min_args=-1, int max_args=-1);
94
95 protected:
96 const std::string& name() const { return name_; }
97 const args_list& args() const { return args_; }
98 private:
99 std::string name_;
100 args_list args_;
101 };
102
103 class formula_function_expression : public function_expression {
104 public:
105 explicit formula_function_expression(const std::string& name, const args_list& args, const_formula_ptr formula, const_formula_ptr precondition, const std::vector<std::string>& arg_names);
106 virtual ~formula_function_expression() {}
107
108 void set_formula(const_formula_ptr f) { formula_ = f; }
109 private:
110 variant execute(const formula_callable& variables) const;
111 const_formula_ptr formula_;
112 const_formula_ptr precondition_;
113 std::vector<std::string> arg_names_;
114 int star_arg_;
115
116 //this is the callable object that is populated with the arguments to the
117 //function. We try to reuse the same object every time the function is
118 //called rather than recreating it each time.
119 mutable boost::intrusive_ptr<slot_formula_callable> callable_;
120 };
121
122 typedef boost::shared_ptr<function_expression> function_expression_ptr;
123 typedef boost::shared_ptr<formula_function_expression> formula_function_expression_ptr;
124
125 class formula_function {
126 std::string name_;
127 const_formula_ptr formula_;
128 const_formula_ptr precondition_;
129 std::vector<std::string> args_;
130 public:
131 formula_function() {}
132 formula_function(const std::string& name, const_formula_ptr formula, const_formula_ptr precondition, const std::vector<std::string>& args) : name_(name), formula_(formula), precondition_(precondition), args_(args)
133 {}
134
135 formula_function_expression_ptr generate_function_expression(const std::vector<expression_ptr>& args) const;
136 };
137
138 class function_symbol_table {
139 std::map<std::string, formula_function> custom_formulas_;
140 const function_symbol_table* backup_;
141 public:
142 function_symbol_table() : backup_(0) {}
143 virtual ~function_symbol_table() {}
144 void set_backup(const function_symbol_table* backup) { backup_ = backup; }
145 virtual void add_formula_function(const std::string& name, const_formula_ptr formula, const_formula_ptr precondition, const std::vector<std::string>& args);
146 virtual expression_ptr create_function(const std::string& fn,
147 const std::vector<expression_ptr>& args,
148 const formula_callable_definition* callable_def) const;
149 std::vector<std::string> get_function_names() const;
150 };
151
152 //a special symbol table which is used to facilitate recursive functions.
153 //it is given to a formula function during parsing, and will give out
154 //function stubs for recursive calls. At the end of parsing it can fill
155 //in the real call.
156 class recursive_function_symbol_table : public function_symbol_table {
157 std::string name_;
158 formula_function stub_;
159 function_symbol_table* backup_;
160 mutable std::vector<formula_function_expression_ptr> expr_;
161 public:
162 recursive_function_symbol_table(const std::string& fn, const std::vector<std::string>& args, function_symbol_table* backup);
163 virtual expression_ptr create_function(const std::string& fn,
164 const std::vector<expression_ptr>& args,
165 const formula_callable_definition* callable_def) const;
166 void resolve_recursive_calls(const_formula_ptr f);
167 };
168
169 expression_ptr create_function(const std::string& fn,
170 const std::vector<expression_ptr>& args,
171 const function_symbol_table* symbols,
172 const formula_callable_definition* callable_def);
173 bool optimize_function_arguments(const std::string& fn,
174 const function_symbol_table* symbols);
175 std::vector<std::string> builtin_function_names();
176
177 class variant_expression : public formula_expression {
178 public:
179 explicit variant_expression(variant v) : formula_expression("_var"), v_(v)
180 {}
181
182 bool can_reduce_to_variant(variant& v) const {
183 v = v_;
184 return true;
185 }
186 private:
187 variant execute(const formula_callable& /*variables*/) const {
188 return v_;
189 }
190
191 variant v_;
192 };
193
194
195 }
196
197 #endif
0 #ifndef FORMULA_FWD_HPP_INCLUDED
1 #define FORMULA_FWD_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 namespace game_logic {
6
7 class formula;
8 typedef boost::shared_ptr<formula> formula_ptr;
9 typedef boost::shared_ptr<const formula> const_formula_ptr;
10
11 }
12
13 #endif
0 #ifndef DISABLE_FORMULA_PROFILER
1
2 #include <assert.h>
3 #include <iostream>
4 #include <map>
5 #include <string>
6 #include <sstream>
7
8 #include <pthread.h>
9 #include <signal.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <sys/time.h>
13
14 #include "custom_object_type.hpp"
15 #include "filesystem.hpp"
16 #include "foreach.hpp"
17 #include "formula_profiler.hpp"
18 #include "object_events.hpp"
19
20 namespace formula_profiler
21 {
22
23 event_call_stack_type event_call_stack;
24
25 namespace {
26 bool handler_disabled = false;
27 bool profiler_on = false;
28 std::string output_fname;
29 pthread_t main_thread;
30
31 int empty_samples = 0;
32
33 std::vector<custom_object_event_frame> event_call_stack_samples;
34 int num_samples = 0;
35 const size_t max_samples = 10000;
36
37 void sigprof_handler(int sig)
38 {
39 if(handler_disabled || !pthread_equal(main_thread, pthread_self())) {
40 return;
41 }
42
43 if(num_samples == max_samples) {
44 return;
45 }
46
47 if(event_call_stack.empty()) {
48 ++empty_samples;
49 } else {
50 event_call_stack_samples[num_samples++] = event_call_stack.back();
51 }
52 }
53
54 }
55
56 manager::manager(const char* output_file)
57 {
58 if(output_file) {
59 event_call_stack_samples.resize(max_samples);
60
61 main_thread = pthread_self();
62
63 fprintf(stderr, "SETTING UP PROFILING...\n");
64 profiler_on = true;
65 output_fname = output_file;
66
67 signal(SIGPROF, sigprof_handler);
68
69 struct itimerval timer;
70 timer.it_interval.tv_sec = 0;
71 timer.it_interval.tv_usec = 10000;
72 timer.it_value = timer.it_interval;
73 setitimer(ITIMER_PROF, &timer, 0);
74 }
75 }
76
77 manager::~manager()
78 {
79 if(profiler_on){
80 struct itimerval timer;
81 memset(&timer, 0, sizeof(timer));
82 setitimer(ITIMER_PROF, &timer, 0);
83
84 std::ostringstream s;
85 s << "SAMPLES EMPTY: " << empty_samples << "\n";
86 for(int n = 0; n != num_samples; ++n) {
87 const custom_object_event_frame& frame = event_call_stack_samples[n];
88 s << frame.type->id() << ":" << get_object_event_str(frame.event_id) << ":" << (frame.executing_commands ? "CMD" : "FFL") << "\n";
89
90 }
91
92 if(!output_fname.empty()) {
93 sys::write_file(output_fname, s.str());
94 } else {
95 std::cerr << "===\n=== PROFILE REPORT ===\n";
96 std::cerr << s.str();
97 std::cerr << "=== END PROFILE REPORT ===\n";
98 }
99 }
100 }
101
102 bool custom_object_event_frame::operator<(const custom_object_event_frame& f) const
103 {
104 return type < f.type || type == f.type && event_id < f.event_id ||
105 type == f.type && event_id == f.event_id && executing_commands < f.executing_commands;
106 }
107
108 std::string get_profile_summary()
109 {
110 if(!profiler_on) {
111 return "";
112 }
113
114 handler_disabled = true;
115
116 static int last_empty_samples = 0;
117 static int last_num_samples = 0;
118
119 const int nsamples = num_samples - last_num_samples;
120 const int nempty = empty_samples - last_empty_samples;
121
122 std::sort(event_call_stack_samples.end() - nsamples, event_call_stack_samples.end());
123
124 std::ostringstream s;
125
126 s << "PROFILE: " << (nsamples + nempty) << " CPU. " << nsamples << " IN FFL ";
127
128
129 std::vector<std::pair<int, std::string> > samples;
130 int count = 0;
131 for(int n = last_num_samples; n < num_samples; ++n) {
132 if(n+1 == num_samples || event_call_stack_samples[n].type != event_call_stack_samples[n+1].type) {
133 samples.push_back(std::pair<int, std::string>(count + 1, event_call_stack_samples[n].type->id()));
134 count = 0;
135 } else {
136 ++count;
137 }
138 }
139
140 std::sort(samples.begin(), samples.end());
141 std::reverse(samples.begin(), samples.end());
142 for(int n = 0; n != samples.size(); ++n) {
143 s << samples[n].second << " " << samples[n].first << " ";
144 }
145
146 last_empty_samples = empty_samples;
147 last_num_samples = num_samples;
148
149 handler_disabled = false;
150
151 return s.str();
152 }
153
154 }
155
156 #endif
0 #ifndef FORMULA_PROFILER_HPP_INCLUDED
1 #define FORMULA_PROFILER_HPP_INCLUDED
2
3 #include <string>
4
5 #ifdef DISABLE_FORMULA_PROFILER
6
7 namespace formula_profiler
8 {
9
10 class manager
11 {
12 public:
13 explicit manager(const char* output_file) {}
14 ~manager() {}
15 };
16
17 class suspend_scope
18 {
19 };
20
21 inline std::string get_profile_summary() { return ""; }
22
23 }
24
25 #else
26
27 #include <vector>
28
29 class custom_object_type;
30
31 namespace formula_profiler
32 {
33
34 struct custom_object_event_frame {
35 const custom_object_type* type;
36 int event_id;
37 bool executing_commands;
38
39 bool operator<(const custom_object_event_frame& f) const;
40 };
41
42 typedef std::vector<custom_object_event_frame> event_call_stack_type;
43 extern event_call_stack_type event_call_stack;
44
45 class manager
46 {
47 public:
48 explicit manager(const char* output_file);
49 ~manager();
50 };
51
52 class suspend_scope
53 {
54 public:
55 suspend_scope() { event_call_stack.swap(backup_); }
56 ~suspend_scope() { event_call_stack.swap(backup_); }
57 private:
58 event_call_stack_type backup_;
59 };
60
61 std::string get_profile_summary();
62
63 }
64
65 #endif
66
67 #endif
0 #include "formula.hpp"
1 #include "formula_callable.hpp"
2 #include "unit_test.hpp"
3
4 namespace {
5 using namespace game_logic;
6 class mock_char : public formula_callable {
7 variant get_value(const std::string& key) const {
8 if(key == "strength") {
9 return variant(15);
10 } else if(key == "agility") {
11 return variant(12);
12 }
13
14 return variant(10);
15 }
16 };
17 class mock_party : public formula_callable {
18 variant get_value(const std::string& key) const {
19 c_.add_ref();
20 i_[0].add_ref();
21 i_[1].add_ref();
22 i_[2].add_ref();
23 if(key == "members") {
24 i_[0].add("strength",variant(12));
25 i_[1].add("strength",variant(16));
26 i_[2].add("strength",variant(14));
27 std::vector<variant> members;
28 for(int n = 0; n != 3; ++n) {
29 members.push_back(variant(&i_[n]));
30 }
31
32 return variant(&members);
33 } else if(key == "char") {
34 return variant(&c_);
35 } else {
36 return variant(0);
37 }
38 }
39
40 mock_char c_;
41 mutable map_formula_callable i_[3];
42
43 };
44
45 }
46
47 UNIT_TEST(formula)
48 {
49 mock_char c;
50 mock_party p;
51 try {
52 CHECK_EQ(formula("strength").execute(c).as_int(), 15);
53 CHECK_EQ(formula("17").execute(c).as_int(), 17);
54 CHECK_EQ(formula("strength/2 + agility").execute(c).as_int(), 19);
55 CHECK_EQ(formula("(strength+agility)/2").execute(c).as_int(), 13);
56 CHECK_EQ(formula("strength > 12").execute(c).as_int(), 1);
57 CHECK_EQ(formula("strength > 18").execute(c).as_int(), 0);
58 CHECK_EQ(formula("if(strength > 12, 7, 2)").execute(c).as_int(), 7);
59 CHECK_EQ(formula("if(strength > 18, 7, 2)").execute(c).as_int(), 2);
60 CHECK_EQ(formula("2 and 1").execute(c).as_int(), 1);
61 CHECK_EQ(formula("2 and 0").execute(c).as_int(), 0);
62 CHECK_EQ(formula("2 or 0").execute(c).as_int(), 2);
63 CHECK_EQ(formula("-5").execute(c).as_int(),-5);
64 CHECK_EQ(formula("not 5").execute(c).as_int(), 0);
65 CHECK_EQ(formula("not 0").execute(c).as_int(), 1);
66 CHECK_EQ(formula("abs(5)").execute(c).as_int(), 5);
67 CHECK_EQ(formula("abs(-5)").execute(c).as_int(), 5);
68 CHECK_EQ(formula("min(3,5)").execute(c).as_int(), 3);
69 CHECK_EQ(formula("min(5,2)").execute(c).as_int(), 2);
70 CHECK_EQ(formula("max(3,5)").execute(c).as_int(), 5);
71 CHECK_EQ(formula("max(5,2)").execute(c).as_int(), 5);
72 CHECK_EQ(formula("max(4,5,[2,18,7])").execute(c).as_int(), 18);
73 CHECK_EQ(formula("char.strength").execute(p).as_int(), 15);
74 CHECK_EQ(formula("choose(members,strength).strength").execute(p).as_int(), 16);
75 CHECK_EQ(formula("4^2").execute().as_int(), 16);
76 CHECK_EQ(formula("2+3^3").execute().as_int(), 29);
77 CHECK_EQ(formula("2*3^3+2").execute().as_int(), 56);
78 CHECK_EQ(formula("9^3").execute().as_int(), 729);
79 CHECK_EQ(formula("x*5 where x=1").execute().as_int(), 5);
80 CHECK_EQ(formula("x*(a*b where a=2,b=1) where x=5").execute().as_int(), 10);
81 CHECK_EQ(formula("char.strength * ability where ability=3").execute(p).as_int(), 45);
82 CHECK_EQ(formula("'abcd' = 'abcd'").execute(p).as_bool(), true);
83 CHECK_EQ(formula("'abcd' = 'acd'").execute(p).as_bool(), false);
84 CHECK_EQ(formula("'strength, agility: {strength}, {agility}'").execute(c).as_string(),
85 "strength, agility: 15, 12");
86 for(int n = 0; n != 128; ++n) {
87 const int dice_roll = formula("3d6").execute().as_int();
88 CHECK_GE(dice_roll, 3);
89 CHECK_LE(dice_roll, 18);
90 }
91
92 CHECK_EQ(formula::create_string_formula("Your strength is {strength}")->execute(c).as_string(),
93 "Your strength is 15");
94 variant myarray = formula("[1,2,3]").execute();
95 CHECK_EQ(myarray.num_elements(), 3);
96 CHECK_EQ(myarray[0].as_int(), 1);
97 CHECK_EQ(myarray[1].as_int(), 2);
98 CHECK_EQ(myarray[2].as_int(), 3);
99
100 } catch(formula_error& e) {
101 std::cerr << "parse error\n";
102 }
103 }
104
105 BENCHMARK(construct_int_variant)
106 {
107 BENCHMARK_LOOP {
108 variant v(0);
109 }
110 }
111
112 BENCHMARK_ARG(formula, const std::string& fm)
113 {
114 static mock_party p;
115 formula f(fm);
116 BENCHMARK_LOOP {
117 f.execute(p);
118 }
119 }
120
121 BENCHMARK_ARG_CALL(formula, integer, "0");
122 BENCHMARK_ARG_CALL(formula, where, "x where x = 5");
123 BENCHMARK_ARG_CALL(formula, add, "5 + 4");
124 BENCHMARK_ARG_CALL(formula, arithmetic, "(5 + 4)*17 + 12*9 - 5/2");
125 BENCHMARK_ARG_CALL(formula, read_input, "char");
126 BENCHMARK_ARG_CALL(formula, read_input_sub, "char.strength");
127 BENCHMARK_ARG_CALL(formula, array, "[4, 5, 8, 12, 17, 0, 19]");
128 BENCHMARK_ARG_CALL(formula, array_str, "['stand', 'walk', 'run', 'jump']");
129 BENCHMARK_ARG_CALL(formula, string, "'blah'");
130 BENCHMARK_ARG_CALL(formula, null_function, "null()");
131 BENCHMARK_ARG_CALL(formula, if_function, "if(4 > 5, 7, 8)");
0 /* $Id: formula_tokenizer.cpp 25713 2008-04-09 18:36:16Z dragonking $ */
1 /*
2 Copyright (C) 2007 - 2008 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12
13 #include <iostream>
14
15 #include "foreach.hpp"
16 #include "formula_tokenizer.hpp"
17 #include "unit_test.hpp"
18
19 namespace formula_tokenizer
20 {
21
22 namespace {
23
24 const FFL_TOKEN_TYPE* create_single_char_tokens() {
25 static FFL_TOKEN_TYPE chars[256];
26 std::fill(chars, chars+256, TOKEN_INVALID);
27
28 chars['('] = TOKEN_LPARENS;
29 chars[')'] = TOKEN_RPARENS;
30 chars['['] = TOKEN_LSQUARE;
31 chars[']'] = TOKEN_RSQUARE;
32 chars['{'] = TOKEN_LBRACKET;
33 chars['}'] = TOKEN_RBRACKET;
34 chars[','] = TOKEN_COMMA;
35 chars[';'] = TOKEN_SEMICOLON;
36 chars['.'] = TOKEN_OPERATOR;
37 chars['+'] = TOKEN_OPERATOR;
38 chars['*'] = TOKEN_OPERATOR;
39 chars['/'] = TOKEN_OPERATOR;
40 chars['='] = TOKEN_OPERATOR;
41 chars['%'] = TOKEN_OPERATOR;
42 chars['^'] = TOKEN_OPERATOR;
43 return chars;
44 }
45
46 const FFL_TOKEN_TYPE* single_char_tokens = create_single_char_tokens();
47
48 }
49
50 token get_token(iterator& i1, iterator i2) {
51 token t;
52 t.begin = i1;
53 t.type = single_char_tokens[*i1];
54 if(t.type != TOKEN_INVALID) {
55 t.end = ++i1;
56 return t;
57 }
58
59 switch(*i1) {
60 case '\'':
61 case '~':
62 case '#':
63 t.type = *i1 == '#' ? TOKEN_COMMENT : TOKEN_STRING_LITERAL;
64 i1 = std::find(i1+1, i2, *i1);
65 if(i1 == i2) {
66 std::cerr << "Unterminated string or comment\n";
67 throw token_error();
68 }
69 t.end = ++i1;
70 return t;
71 case '>':
72 case '<':
73 case '!':
74 t.type = TOKEN_OPERATOR;
75 ++i1;
76 if(i1 != i2 && *i1 == '=') {
77 ++i1;
78 } else if(*(i1-1) == '!') {
79 std::cerr << "Unexpected character in formula: '!'\n";
80 throw token_error();
81 }
82
83 t.end = i1;
84 return t;
85 case '-':
86 ++i1;
87 if(i1 != i2 && *i1 == '>') {
88 t.type = TOKEN_POINTER;
89 ++i1;
90 } else {
91 t.type = TOKEN_OPERATOR;
92 }
93
94 t.end = i1;
95 return t;
96 case '0':
97 if(i1 + 1 != i2 && *(i1+1) == 'x') {
98 t.type = TOKEN_INTEGER;
99 i1 += 2;
100 while(i1 != i2 && isxdigit(*i1)) {
101 ++i1;
102 }
103
104 t.end = i1;
105
106 return t;
107 }
108
109 break;
110 case 'd':
111 if(i1 + 1 != i2 && !isalpha(*(i1+1))) {
112 //die operator as in 1d6.
113 t.type = TOKEN_OPERATOR;
114 t.end = ++i1;
115 return t;
116 }
117 break;
118 }
119
120 if(isspace(*i1)) {
121 t.type = TOKEN_WHITESPACE;
122 while(i1 != i2 && isspace(*i1)) {
123 ++i1;
124 }
125
126 t.end = i1;
127 return t;
128 }
129
130 if(isdigit(*i1)) {
131 t.type = TOKEN_INTEGER;
132 while(i1 != i2 && isdigit(*i1)) {
133 ++i1;
134 }
135
136 t.end = i1;
137 return t;
138 }
139
140 if(isalpha(*i1) || *i1 == '_') {
141 ++i1;
142 while(i1 != i2 && (isalnum(*i1) || *i1 == '_')) {
143 ++i1;
144 }
145
146 t.end = i1;
147
148 static const std::string Keywords[] = { "functions", "def" };
149 foreach(const std::string& str, Keywords) {
150 if(str.size() == (t.end - t.begin) && std::equal(str.begin(), str.end(), t.begin)) {
151 t.type = TOKEN_KEYWORD;
152 return t;
153 }
154 }
155
156 static const std::string Operators[] = { "not", "and", "or", "where", "in" };
157 foreach(const std::string& str, Operators) {
158 if(str.size() == (t.end - t.begin) && std::equal(str.begin(), str.end(), t.begin)) {
159 t.type = TOKEN_OPERATOR;
160 return t;
161 }
162 }
163
164 for(std::string::const_iterator i = t.begin; i != t.end; ++i) {
165 if(islower(*i)) {
166 t.type = TOKEN_IDENTIFIER;
167 return t;
168 }
169 }
170
171 t.type = TOKEN_CONST_IDENTIFIER;
172 return t;
173 }
174
175 std::cerr << "Unrecognized token: '" << std::string(i1,i2) << "'\n";
176 throw token_error();
177 }
178
179 }
180
181 UNIT_TEST(tokenizer_test)
182 {
183 using namespace formula_tokenizer;
184 std::string test = "(abc + 0x4 * (5+3))*2 in [4,5]";
185 std::string::const_iterator i1 = test.begin();
186 std::string::const_iterator i2 = test.end();
187 FFL_TOKEN_TYPE types[] = {TOKEN_LPARENS, TOKEN_IDENTIFIER,
188 TOKEN_WHITESPACE, TOKEN_OPERATOR,
189 TOKEN_WHITESPACE, TOKEN_INTEGER,
190 TOKEN_WHITESPACE, TOKEN_OPERATOR,
191 TOKEN_WHITESPACE, TOKEN_LPARENS,
192 TOKEN_INTEGER, TOKEN_OPERATOR,
193 TOKEN_INTEGER, TOKEN_RPARENS,
194 TOKEN_RPARENS, TOKEN_OPERATOR, TOKEN_INTEGER};
195 std::string tokens[] = {"(", "abc", " ", "+", " ", "0x4", " ",
196 "*", " ", "(", "5", "+", "3", ")", ")", "*", "2",
197 "in", "[", "4", ",", "5", "]"};
198 for(int n = 0; n != sizeof(types)/sizeof(*types); ++n) {
199 token t = get_token(i1,i2);
200 CHECK_EQ(std::string(t.begin,t.end), tokens[n]);
201 CHECK_EQ(t.type, types[n]);
202
203 }
204 }
205
206 BENCHMARK(tokenizer_bench)
207 {
208 const std::string input =
209 " #function which returns true if the object is in an animation that"
210 " requires frogatto be on the ground#"
211 " def animation_requires_standing(obj)"
212 " obj.animation in ['stand', 'stand_up_slope', 'stand_down_slope', 'run', 'walk', 'lookup', 'crouch', 'enter_crouch', 'leave_crouch', 'turn', 'roll','skid'];"
213 " def set_facing(obj, facing) if(obj.facing != facing and (not (obj.animation in ['interact', 'slide'])),"
214 " [facing(facing), if(obj.is_standing, animation('turn'))]);"
215
216 " def stand(obj)"
217 " if(abs(obj.velocity_x) > 240 and (not obj.animation in ['walk']), animation('skid'),"
218 " if(abs(obj.slope_standing_on) < 20, animation('stand'),"
219 " if(obj.slope_standing_on*obj.facing > 0, animation('stand_down_slope'),"
220 " animation('stand_up_slope'))));"
221
222
223 " #make Frogatto walk. anim can be either 'walk' or 'run'. Does checking"
224 " to make sure Frogatto is in a state where he can walk or run."
225 " Will make Frogatto 'glide' if in mid air.#"
226 " def walk(obj, dir, anim)"
227 " if(obj.is_standing and (not (obj.animation in ['walk', 'run', 'jump', 'turn', 'run', 'crouch', 'enter_crouch', 'roll', 'run_attack', 'energyshot', 'attack', 'up_attack', 'interact'])), [animation(anim), if(anim = 'run', [sound('run.ogg')])],"
228 " #Frogatto is in the air, so make him glide.#"
229 " if(((not obj.is_standing) and obj.animation != 'slide'), set(obj.velocity_x, obj.velocity_x + obj.jump_glide*dir)));"
230
231 " #Function to attempt to make Frogatto crouch; does checking to make"
232 " sure he's in a state that allows entering a crouch.#"
233 " def crouch(obj)"
234 " if(((not obj.animation in ['crouch', 'enter_crouch', 'roll', 'interact'] ) and obj.is_standing), animation('enter_crouch'));"
235 " def roll(obj)"
236 " if( obj.animation in ['crouch'] and obj.is_standing, animation('roll'));"
237 " def get_charge_cycles(obj)"
238 " if(obj.tmp.start_attack_cycle, obj.cycle - obj.tmp.start_attack_cycle, 0);"
239
240 " #Function to make Frogatto attack. Does checking and chooses the"
241 " appropriate type of attack animation, if any.#"
242 " def attack(obj, charge_cycles)"
243 " [if('fat' in obj.variations,"
244 " [animation('spit')],["
245 " if(obj.animation in ['stand', 'stand_up_slope', 'stand_down_slope', 'walk', 'lookup','skid'], animation(if(obj.ctrl_up, 'up_', '') + if(charge_cycles >= obj.vars.charge_time, 'energyshot', 'attack'))),"
246
247 " if(obj.animation in ['run'], animation('run_attack')),"
248
249 " if(obj.animation in ['jump', 'fall'], animation(if(charge_cycles >= obj.vars.charge_time,'energyshot' + if(obj.ctrl_down,'_down','_jump'), if(obj.ctrl_down, 'fall_spin_attack', 'jump_attack' )))),"
250
251 " if(obj.animation in ['crouch'] and (charge_cycles > obj.vars.charge_time), animation('energyshot_crouch'))]"
252
253 " )];";
254
255 BENCHMARK_LOOP {
256 std::string::const_iterator i1 = input.begin();
257 std::string::const_iterator i2 = input.end();
258 while(i1 != i2) {
259 formula_tokenizer::get_token(i1, i2);
260 }
261 }
262 }
0 /* $Id: formula_tokenizer.hpp 25713 2008-04-09 18:36:16Z dragonking $ */
1 /*
2 Copyright (C) 2007 - 2008 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12
13 #ifndef FORMULA_TOKENIZER_HPP_INCLUDED
14 #define FORMULA_TOKENIZER_HPP_INCLUDED
15
16 #include <string>
17
18 namespace formula_tokenizer
19 {
20
21 typedef std::string::const_iterator iterator;
22
23 enum FFL_TOKEN_TYPE { TOKEN_OPERATOR, TOKEN_STRING_LITERAL,
24 TOKEN_CONST_IDENTIFIER,
25 TOKEN_IDENTIFIER, TOKEN_INTEGER,
26 TOKEN_LPARENS, TOKEN_RPARENS,
27 TOKEN_LSQUARE, TOKEN_RSQUARE,
28 TOKEN_LBRACKET, TOKEN_RBRACKET,
29 TOKEN_COMMA, TOKEN_SEMICOLON,
30 TOKEN_WHITESPACE, TOKEN_KEYWORD,
31 TOKEN_COMMENT, TOKEN_POINTER,
32 TOKEN_INVALID };
33
34 struct token {
35 FFL_TOKEN_TYPE type;
36 iterator begin, end;
37 };
38
39 token get_token(iterator& i1, iterator i2);
40
41 struct token_error {};
42
43 }
44
45 #endif
0 #include "formula_variable_storage.hpp"
1
2 namespace game_logic
3 {
4
5 formula_variable_storage::formula_variable_storage()
6 {}
7
8 formula_variable_storage::formula_variable_storage(const std::map<std::string, variant>& m)
9 {
10 for(std::map<std::string, variant>::const_iterator i = m.begin(); i != m.end(); ++i) {
11 add(i->first, i->second);
12 }
13 }
14
15 bool formula_variable_storage::equal_to(const std::map<std::string, variant>& m) const
16 {
17 if(m.size() != strings_to_values_.size()) {
18 return false;
19 }
20
21 std::map<std::string, int>::const_iterator i = strings_to_values_.begin();
22 std::map<std::string, variant>::const_iterator j = m.begin();
23
24 while(i != strings_to_values_.end()) {
25 if(i->first != j->first || j->second != values_[i->second]) {
26 return false;
27 }
28
29 ++i;
30 ++j;
31 }
32
33 return true;
34 }
35
36 void formula_variable_storage::read(wml::const_node_ptr node)
37 {
38 if(!node) {
39 return;
40 }
41
42 for(wml::node::const_attr_iterator i = node->begin_attr(); i != node->end_attr(); ++i) {
43 variant var;
44 var.serialize_from_string(i->second);
45 add(i->first, var);
46 }
47 }
48
49 void formula_variable_storage::write(wml::node_ptr node) const
50 {
51 for(std::map<std::string,int>::const_iterator i = strings_to_values_.begin(); i != strings_to_values_.end(); ++i) {
52 std::string val;
53 values_[i->second].serialize_to_string(val);
54 node->set_attr(i->first, val);
55 }
56 }
57
58 void formula_variable_storage::add(const std::string& key, const variant& value)
59 {
60 std::map<std::string,int>::const_iterator i = strings_to_values_.find(key);
61 if(i != strings_to_values_.end()) {
62 values_[i->second] = value;
63 } else {
64 strings_to_values_[key] = values_.size();
65 values_.push_back(value);
66 }
67 }
68
69 void formula_variable_storage::add(const formula_variable_storage& value)
70 {
71 for(std::map<std::string, int>::const_iterator i = value.strings_to_values_.begin(); i != value.strings_to_values_.end(); ++i) {
72 add(i->first, value.values_[i->second]);
73 }
74 }
75
76 variant formula_variable_storage::get_value(const std::string& key) const
77 {
78 std::map<std::string,int>::const_iterator i = strings_to_values_.find(key);
79 if(i != strings_to_values_.end()) {
80 return values_[i->second];
81 } else {
82 return variant();
83 }
84 }
85
86 variant formula_variable_storage::get_value_by_slot(int slot) const
87 {
88 return values_[slot];
89 }
90
91 void formula_variable_storage::set_value(const std::string& key, const variant& value)
92 {
93 add(key, value);
94 }
95
96 void formula_variable_storage::set_value_by_slot(int slot, const variant& value)
97 {
98 values_[slot] = value;
99 }
100
101 void formula_variable_storage::get_inputs(std::vector<formula_input>* inputs) const
102 {
103 for(std::map<std::string,int>::const_iterator i = strings_to_values_.begin(); i != strings_to_values_.end(); ++i) {
104 inputs->push_back(formula_input(i->first, FORMULA_READ_WRITE));
105 }
106 }
107
108 }
0 #ifndef FORMULA_VARIABLE_STORAGE_HPP_INCLUDED
1 #define FORMULA_VARIABLE_STORAGE_HPP_INCLUDED
2
3 #include <boost/intrusive_ptr.hpp>
4
5 #include "formula_callable.hpp"
6 #include "wml_node.hpp"
7
8 namespace game_logic
9 {
10
11 class formula_variable_storage : public formula_callable
12 {
13 public:
14 formula_variable_storage();
15 explicit formula_variable_storage(const std::map<std::string, variant>& m);
16
17 bool equal_to(const std::map<std::string, variant>& m) const;
18
19 void read(wml::const_node_ptr node);
20 void write(wml::node_ptr node) const;
21 void add(const std::string& key, const variant& value);
22 void add(const formula_variable_storage& value);
23
24 private:
25 variant get_value(const std::string& key) const;
26 variant get_value_by_slot(int slot) const;
27 void set_value(const std::string& key, const variant& value);
28 void set_value_by_slot(int slot, const variant& value);
29
30 void get_inputs(std::vector<formula_input>* inputs) const;
31
32 std::vector<variant> values_;
33 std::map<std::string, int> strings_to_values_;
34 };
35
36 typedef boost::intrusive_ptr<formula_variable_storage> formula_variable_storage_ptr;
37 typedef boost::intrusive_ptr<const formula_variable_storage> const_formula_variable_storage_ptr;
38
39 }
40
41 #endif
0 #include <algorithm>
1 #include <iostream>
2
3 #include <boost/lexical_cast.hpp>
4
5 #include "asserts.hpp"
6 #include "foreach.hpp"
7 #include "frame.hpp"
8 #include "object_events.hpp"
9 #include "raster.hpp"
10 #include "solid_map.hpp"
11 #include "sound.hpp"
12 #include "string_utils.hpp"
13 #include "surface_formula.hpp"
14 #include "surface_palette.hpp"
15 #include "texture.hpp"
16 #include "wml_node.hpp"
17 #include "wml_utils.hpp"
18
19 namespace {
20 std::set<frame*>& palette_frames() {
21 static std::set<frame*>* instance = new std::set<frame*>;
22 return *instance;
23 }
24
25 unsigned int current_palette_mask = 0;
26 }
27
28 frame::frame(wml::const_node_ptr node)
29 : id_(node->attr("id")),
30 image_(node->attr("image")),
31 variant_id_(id_),
32 enter_event_id_(get_object_event_id("enter_" + id_ + "_anim")),
33 end_event_id_(get_object_event_id("end_" + id_ + "_anim")),
34 leave_event_id_(get_object_event_id("leave_" + id_ + "_anim")),
35 process_event_id_(get_object_event_id("process_" + id_)),
36 texture_(graphics::texture::get(image_, node->attr("image_formula"))),
37 solid_(solid_info::create(node)),
38 collide_rect_(node->has_attr("collide") ? rect(node->attr("collide")) :
39 rect(wml::get_int(node, "collide_x"),
40 wml::get_int(node, "collide_y"),
41 wml::get_int(node, "collide_w"),
42 wml::get_int(node, "collide_h"))),
43 hit_rect_(node->has_attr("hit") ? rect(node->attr("hit")) :
44 rect(wml::get_int(node, "hit_x"),
45 wml::get_int(node, "hit_y"),
46 wml::get_int(node, "hit_w"),
47 wml::get_int(node, "hit_h"))),
48 platform_rect_(node->has_attr("platform") ? rect(node->attr("platform")) :
49 rect(wml::get_int(node, "platform_x"),
50 wml::get_int(node, "platform_y"),
51 wml::get_int(node, "platform_w"), 1)),
52 img_rect_(node->has_attr("rect") ? rect(node->attr("rect")) :
53 rect(wml::get_int(node, "x"),
54 wml::get_int(node, "y"),
55 wml::get_int(node, "w"),
56 wml::get_int(node, "h"))),
57 feet_x_(wml::get_int(node, "feet_x")),
58 feet_y_(wml::get_int(node, "feet_y")),
59 accel_x_(wml::get_int(node, "accel_x", INT_MIN)),
60 accel_y_(wml::get_int(node, "accel_y", INT_MIN)),
61 velocity_x_(wml::get_int(node, "velocity_x", INT_MIN)),
62 velocity_y_(wml::get_int(node, "velocity_y", INT_MIN)),
63 nframes_(wml::get_int(node, "frames", 1)),
64 nframes_per_row_(wml::get_int(node, "frames_per_row", -1)),
65 frame_time_(wml::get_int(node, "duration", -1)),
66 reverse_frame_(wml::get_bool(node, "reverse")),
67 play_backwards_(wml::get_bool(node, "play_backwards")),
68 scale_(wml::get_int(node, "scale", 2)),
69 pad_(wml::get_int(node, "pad")),
70 rotate_(wml::get_int(node, "rotate")),
71 blur_(wml::get_int(node, "blur")),
72 rotate_on_slope_(wml::get_bool(node, "rotate_on_slope")),
73 damage_(wml::get_int(node, "damage")),
74 sounds_(util::split(node->attr("sound"))),
75 current_palette_(-1)
76 {
77 std::vector<std::string> hit_frames = util::split((*node)["hit_frames"]);
78 foreach(const std::string& f, hit_frames) {
79 hit_frames_.push_back(boost::lexical_cast<int>(f));
80 }
81
82 const std::string& events = node->attr("events");
83 if(!events.empty()) {
84 //events are in the format time0:time1:...:timen:event0,time0:time1:...:timen:event1,...
85 std::vector<std::string> event_vector = util::split(events);
86 std::map<int, std::string> event_map;
87 foreach(const std::string& e, event_vector) {
88 std::vector<std::string> time_event = util::split(e, ':');
89 if(time_event.size() < 2) {
90 continue;
91 }
92
93 const std::string& event = time_event.back();
94
95 for(unsigned int n = 0; n < time_event.size() - 1; ++n) {
96 const int time = atoi(time_event[n].c_str());
97 event_map[time] = event;
98 }
99 }
100
101 typedef std::pair<int,std::string> event_pair;
102 foreach(const event_pair& p, event_map) {
103 event_frames_.push_back(p.first);
104 event_names_.push_back(p.second);
105 }
106 }
107
108 static const std::string AreaPostfix = "_area";
109 for(wml::node::const_attr_iterator i = node->begin_attr(); i != node->end_attr(); ++i) {
110 const std::string& attr = i->first;
111 if(attr.size() <= AreaPostfix.size() || std::equal(AreaPostfix.begin(), AreaPostfix.end(), attr.end() - AreaPostfix.size()) == false || attr == "solid_area" ||attr == "platform_area") {
112 continue;
113 }
114
115 const std::string area_id = std::string(attr.begin(), attr.end() - AreaPostfix.size());
116
117 bool solid = false;
118 std::string value = i->second;
119
120 static const std::string SolidStr = "solid:";
121 if(value.size() > SolidStr.size() && std::equal(SolidStr.begin(), SolidStr.end(), value.begin())) {
122 solid = true;
123 value.erase(value.begin(), value.begin() + SolidStr.size());
124 }
125
126 rect r;
127 if(value == "none") {
128 continue;
129 } else if(value == "all") {
130 r = rect(0, 0, width(), height());
131 } else {
132 r = rect(value);
133 r = rect(r.x()*scale_, r.y()*scale_, r.w()*scale_, r.h()*scale_);
134 }
135 collision_area area = { area_id, r, solid };
136 collision_areas_.push_back(area);
137 }
138
139 if(node->has_attr("frame_info")) {
140 int values_buf[1024];
141 int num_values = 1024;
142 util::split_into_ints(node->attr("frame_info").c_str(), values_buf, &num_values);
143
144 ASSERT_EQ(num_values%8, 0);
145 ASSERT_LE(num_values, 1024);
146 const int* i = values_buf;
147 const int* i2 = values_buf + num_values;
148 while(i != i2) {
149 frame_info info;
150 info.x_adjust = *i++;
151 info.y_adjust = *i++;
152 info.x2_adjust = *i++;
153 info.y2_adjust = *i++;
154 const int x = *i++;
155 const int y = *i++;
156 const int w = *i++;
157 const int h = *i++;
158 info.area = rect(x, y, w, h);
159 frames_.push_back(info);
160 ASSERT_EQ(intersection_rect(info.area, rect(0, 0, texture_.width(), texture_.height())), info.area);
161 ASSERT_EQ(w + (info.x_adjust + info.x2_adjust), img_rect_.w());
162 ASSERT_EQ(h + (info.y_adjust + info.y2_adjust), img_rect_.h());
163
164 }
165
166 ASSERT_EQ(frames_.size(), nframes_);
167
168 build_alpha_from_frame_info();
169 } else {
170 build_alpha();
171 }
172
173 std::vector<std::string> palettes = util::split(node->attr("palettes"));
174 foreach(const std::string& p, palettes) {
175 palettes_recognized_.push_back(graphics::get_palette_id(p));
176 }
177
178 std::cerr << "PALETTES: " << node->attr("palettes").str() << " " << palettes_recognized_.size() << "\n";
179
180 if(palettes_recognized_.empty() == false) {
181 palette_frames().insert(this);
182 if(current_palette_mask) {
183 set_palettes(current_palette_mask);
184 }
185 }
186 }
187
188 frame::~frame()
189 {
190 if(palettes_recognized_.empty() == false) {
191 palette_frames().erase(this);
192 }
193 }
194
195 void frame::set_palettes(unsigned int palettes)
196 {
197 if(current_palette_ >= 0 && (1 << current_palette_) == palettes) {
198 return;
199 }
200
201 int npalette = 0;
202 while(palettes) {
203 if((palettes&1) && std::count(palettes_recognized_.begin(), palettes_recognized_.end(), npalette)) {
204 break;
205 }
206 ++npalette;
207 palettes >>= 1;
208 }
209
210 if(palettes == 0) {
211 if(current_palette_ != -1) {
212 texture_ = graphics::texture::get(image_);
213 current_palette_ = -1;
214 }
215 return;
216 }
217
218 texture_ = graphics::texture::get_palette_mapped(image_, npalette);
219 current_palette_ = npalette;
220 }
221
222 void frame::set_color_palette(unsigned int palettes)
223 {
224 current_palette_mask = palettes;
225 for(std::set<frame*>::iterator i = palette_frames().begin(); i != palette_frames().end(); ++i) {
226 (*i)->set_palettes(palettes);
227 }
228 }
229
230 void frame::set_image_as_solid()
231 {
232 solid_ = solid_info::create_from_texture(texture_, img_rect_);
233 }
234
235 void frame::play_sound(const void* object) const
236 {
237 if (sounds_.empty() == false){
238 int randomNum = rand()%sounds_.size(); //like a 1d-size die
239 if(sounds_[randomNum].empty() == false) {
240 sound::play(sounds_[randomNum], object);
241 }
242 }
243 }
244
245 void frame::build_alpha_from_frame_info()
246 {
247 if(!texture_.valid()) {
248 return;
249 }
250
251 alpha_.resize(nframes_*img_rect_.w()*img_rect_.h(), true);
252 for(int n = 0; n < nframes_; ++n) {
253 const rect& area = frames_[n].area;
254 int dst_index = frames_[n].y_adjust*img_rect_.w()*nframes_ + n*img_rect_.w() + frames_[n].x_adjust;
255 for(int y = 0; y != area.h(); ++y) {
256 ASSERT_INDEX_INTO_VECTOR(dst_index, alpha_);
257 std::vector<bool>::iterator dst = alpha_.begin() + dst_index;
258
259 ASSERT_LT(area.x(), texture_.width());
260 ASSERT_LE(area.x() + area.w(), texture_.width());
261 ASSERT_LT(area.y() + y, texture_.height());
262 std::vector<bool>::const_iterator src = texture_.get_alpha_row(area.x(), area.y() + y);
263
264 std::copy(src, src + area.w(), dst);
265
266 dst_index += img_rect_.w()*nframes_;
267 }
268 }
269 }
270
271 void frame::build_alpha()
272 {
273 frames_.resize(nframes_);
274 if(!texture_.valid()) {
275 return;
276 }
277
278 alpha_.resize(nframes_*img_rect_.w()*img_rect_.h(), true);
279
280 for(int n = 0; n < nframes_; ++n) {
281 const int current_col = (nframes_per_row_ > 0) ? (n% nframes_per_row_) : n;
282 const int current_row = (nframes_per_row_ > 0) ? (n/nframes_per_row_) : 0;
283 const int xbase = img_rect_.x() + current_col*(img_rect_.w()+pad_);
284 const int ybase = img_rect_.y() + current_row*(img_rect_.h()+pad_);
285
286 if(xbase < 0 || ybase < 0 || xbase + img_rect_.w() > texture_.width() ||
287 ybase + img_rect_.h() > texture_.height()) {
288 std::cerr << "IMAGE RECT FOR FRAME '" << id_ << "' #" << n << ": " << img_rect_.x() << " + " << current_col << " * (" << img_rect_.w() << "+" << pad_ << ") IS INVALID: " << xbase << ", " << ybase << ", " << (xbase + img_rect_.w()) << ", " << (ybase + img_rect_.h()) << " / " << texture_.width() << "," << texture_.height() << "\n";
289 throw error();
290 }
291
292 for(int y = 0; y != img_rect_.h(); ++y) {
293 const int dst_index = y*img_rect_.w()*nframes_ + n*img_rect_.w();
294 ASSERT_INDEX_INTO_VECTOR(dst_index, alpha_);
295
296 std::vector<bool>::iterator dst = alpha_.begin() + dst_index;
297
298 std::vector<bool>::const_iterator src = texture_.get_alpha_row(xbase, ybase + y);
299 std::copy(src, src + img_rect_.w(), dst);
300 }
301
302 //now calculate if the actual frame we should be using for drawing
303 //is smaller than the outer rectangle, so we can save on drawing space
304 frame_info& f = frames_[n];
305 f.area = rect(xbase, ybase, img_rect_.w(), img_rect_.h());
306 int top;
307 for(top = 0; top != img_rect_.h(); ++top) {
308 const std::vector<bool>::const_iterator a = texture_.get_alpha_row(xbase, ybase + top);
309 if(std::find(a, a + img_rect_.w(), false) != a + img_rect_.w()) {
310 break;
311 }
312 }
313
314 int bot;
315 for(bot = img_rect_.h(); bot > 0; --bot) {
316 const std::vector<bool>::const_iterator a = texture_.get_alpha_row(xbase, ybase + bot-1);
317 if(std::find(a, a + img_rect_.w(), false) != a + img_rect_.w()) {
318 break;
319 }
320 }
321
322 int left;
323 for(left = 0; left < img_rect_.w(); ++left) {
324 std::vector<bool>::const_iterator a = texture_.get_alpha_row(xbase + left, ybase);
325
326 bool has_opaque = false;
327 for(int n = 0; n != img_rect_.h(); ++n) {
328 if(!*a) {
329 has_opaque = true;
330 }
331 if(n+1 != img_rect_.h()) {
332 a += texture_.width();
333 }
334 }
335
336 if(has_opaque) {
337 break;
338 }
339 }
340
341 int right;
342 for(right = img_rect_.w(); right > 0; --right) {
343 std::vector<bool>::const_iterator a = texture_.get_alpha_row(xbase + right-1, ybase);
344
345 bool has_opaque = false;
346 for(int n = 0; n != img_rect_.h(); ++n) {
347 if(!*a) {
348 has_opaque = true;
349 }
350
351 if(n+1 != img_rect_.h()) {
352 a += texture_.width();
353 }
354 }
355
356 if(has_opaque) {
357 break;
358 }
359 }
360
361 if(right < left) {
362 right = left;
363 }
364
365 if(bot < top) {
366 bot = top;
367 }
368
369 f.x_adjust = left;
370 f.y_adjust = top;
371 f.x2_adjust = img_rect_.w() - right;
372 f.y2_adjust = img_rect_.h() - bot;
373 f.area = rect(xbase + left, ybase + top, right - left, bot - top);
374 ASSERT_EQ(f.area.w() + f.x_adjust + f.x2_adjust, img_rect_.w());
375 ASSERT_EQ(f.area.h() + f.y_adjust + f.y2_adjust, img_rect_.h());
376 }
377 }
378
379 bool frame::is_alpha(int x, int y, int time, bool face_right) const
380 {
381 if(alpha_.empty()) {
382 return true;
383 }
384
385 if(face_right == false) {
386 x = width() - x - 1;
387 }
388
389 if(x < 0 || y < 0 || x >= width() || y >= height()) {
390 return true;
391 }
392
393 x /= scale_;
394 y /= scale_;
395
396 const int nframe = frame_number(time);
397 x += nframe*img_rect_.w();
398
399 const int index = y*img_rect_.w()*nframes_ + x;
400 ASSERT_INDEX_INTO_VECTOR(index, alpha_);
401 return alpha_[index];
402 }
403
404 void frame::draw_into_blit_queue(graphics::blit_queue& blit, int x, int y, bool face_right, bool upside_down, int time) const
405 {
406 const frame_info* info = NULL;
407 GLfloat rect[4];
408 get_rect_in_texture(time, &rect[0], info);
409
410 x += (face_right ? info->x_adjust : info->x2_adjust)*scale_;
411 y += (info->y_adjust)*scale_;
412 const int w = info->area.w()*scale_*(face_right ? 1 : -1);
413 const int h = info->area.h()*scale_*(upside_down ? -1 : 1);
414
415 blit.set_texture(texture_.get_id());
416
417 blit.add(x, y, rect[0], rect[1]);
418 blit.add(x + w, y, rect[2], rect[1]);
419 blit.add(x, y + h, rect[0], rect[3]);
420 blit.add(x + w, y + h, rect[2], rect[3]);
421 }
422
423 void frame::draw(int x, int y, bool face_right, bool upside_down, int time, int rotate) const
424 {
425 const frame_info* info = NULL;
426 GLfloat rect[4];
427 get_rect_in_texture(time, &rect[0], info);
428
429 x += (face_right ? info->x_adjust : info->x2_adjust)*scale_;
430 y += info->y_adjust*scale_;
431 const int w = info->area.w()*scale_*(face_right ? 1 : -1);
432 const int h = info->area.h()*scale_*(upside_down ? -1 : 1);
433
434 if(rotate == 0) {
435 //if there is no rotation, then we can make a much simpler call
436 graphics::queue_blit_texture(texture_, x, y, w, h, rect[0], rect[1], rect[2], rect[3]);
437 graphics::flush_blit_texture();
438 return;
439 }
440
441 graphics::queue_blit_texture(texture_, x, y, w, h, rotate, rect[0], rect[1], rect[2], rect[3]);
442 graphics::flush_blit_texture();
443 }
444
445 void frame::draw(int x, int y, const rect& area, bool face_right, bool upside_down, int time, int rotate) const
446 {
447 const frame_info* info = NULL;
448 GLfloat rect[4];
449 get_rect_in_texture(time, &rect[0], info);
450
451 const int x_adjust = area.x();
452 const int y_adjust = area.y();
453 const int w_adjust = area.w() - img_rect_.w();
454 const int h_adjust = area.h() - img_rect_.h();
455
456 const int w = info->area.w()*scale_*(face_right ? 1 : -1);
457 const int h = info->area.h()*scale_*(upside_down ? -1 : 1);
458
459 rect[0] += GLfloat(x_adjust)/GLfloat(texture_.width());
460 rect[1] += GLfloat(y_adjust)/GLfloat(texture_.height());
461 rect[2] += GLfloat(x_adjust + w_adjust)/GLfloat(texture_.width());
462 rect[3] += GLfloat(y_adjust + h_adjust)/GLfloat(texture_.height());
463
464 //the last 4 params are the rectangle of the single, specific frame
465 graphics::blit_texture(texture_, x, y, (w + w_adjust*scale_)*(face_right ? 1 : -1), (h + h_adjust*scale_)*(upside_down ? -1 : 1), rotate + (face_right ? rotate_ : -rotate_),
466 rect[0], rect[1], rect[2], rect[3]);
467 }
468
469 void frame::get_rect_in_texture(int time, GLfloat* output_rect, const frame_info*& info) const
470 {
471 //picks out a single frame to draw from a whole animation, based on time
472 get_rect_in_frame_number(frame_number(time), output_rect, info);
473 }
474
475 void frame::get_rect_in_frame_number(int nframe, GLfloat* output_rect, const frame_info*& info_result) const
476 {
477 const frame_info& info = frames_[nframe];
478 info_result = &info;
479
480 const int current_col = (nframes_per_row_ > 0) ? (nframe % nframes_per_row_) : nframe ;
481 const int current_row = (nframes_per_row_ > 0) ? (nframe/nframes_per_row_) : 0 ;
482
483 //a tiny amount we subtract from the right/bottom side of the texture,
484 //to avoid rounding errors in floating point going over the edge.
485 //This seems like a kludge but I don't know of a better way to do it. :(
486 const GLfloat TextureEpsilon = 0.1;
487
488 output_rect[0] = GLfloat(info.area.x() + TextureEpsilon)/GLfloat(texture_.width());
489 output_rect[1] = GLfloat(info.area.y() + TextureEpsilon) / GLfloat(texture_.height());
490 output_rect[2] = GLfloat(info.area.x() + info.area.w() - TextureEpsilon)/GLfloat(texture_.width());
491 output_rect[3] = GLfloat(info.area.y() + info.area.h() - TextureEpsilon)/GLfloat(texture_.height());
492 }
493
494 int frame::duration() const
495 {
496 return (nframes_ + (reverse_frame_ ? nframes_ : 0))*frame_time_;
497 }
498
499 bool frame::hit(int time_in_frame) const
500 {
501 if(hit_frames_.empty()) {
502 return false;
503 }
504
505 return std::find(hit_frames_.begin(), hit_frames_.end(), frame_number(time_in_frame)) != hit_frames_.end();
506 }
507
508 int frame::frame_number(int time) const
509 {
510 if(play_backwards_){
511 int frame_num = nframes_-1;
512 if(frame_time_ > 0 && nframes_ >= 1) {
513 if(time >= duration()) {
514 if(reverse_frame_){
515 frame_num = nframes_-1;
516 }else{
517 frame_num = 0;
518 }
519 } else {
520 frame_num = nframes_-1 - time/frame_time_;
521 }
522
523 //if we are in reverse now
524 if(frame_num < 0) {
525 frame_num = -frame_num - 1;
526 }
527 }
528
529 return frame_num;
530 } else {
531 int frame_num = 0;
532 if(frame_time_ > 0 && nframes_ >= 1) {
533 if(time >= duration()) {
534 frame_num = nframes_-1;
535 } else {
536 frame_num = time/frame_time_;
537 }
538
539 //if we are in reverse now
540 if(frame_num >= nframes_) {
541 frame_num = nframes_ - 1 - (frame_num - nframes_);
542 }
543 }
544
545 return frame_num;
546 }
547 }
548
549 const std::string* frame::get_event(int time_in_frame) const
550 {
551 if(event_frames_.empty()) {
552 return NULL;
553 }
554
555 std::vector<int>::const_iterator i = std::find(event_frames_.begin(), event_frames_.end(), time_in_frame);
556 if(i == event_frames_.end()) {
557 return NULL;
558 }
559
560 return &event_names_[i - event_frames_.begin()];
561 }
0 #ifndef FRAME_HPP_INCLUDED
1 #define FRAME_HPP_INCLUDED
2
3 #include <boost/array.hpp>
4
5 #include <string>
6 #include <vector>
7
8 #include "geometry.hpp"
9 #include "solid_map_fwd.hpp"
10 #include "texture.hpp"
11 #include "variant.hpp"
12 #include "wml_node_fwd.hpp"
13
14 namespace graphics {
15 class blit_queue;
16 }
17
18 class frame
19 {
20 public:
21 //exception thrown when there's a loading error.
22 struct error {};
23
24 struct collision_area {
25 std::string name;
26 rect area;
27
28 //if this flag is set, then the entire area is considered to
29 //collide, rather than just the pixels that have non-zero alpha.
30 bool no_alpha_check;
31 };
32
33 static void set_color_palette(unsigned int palettes);
34
35 explicit frame(wml::const_node_ptr node);
36 ~frame();
37
38 //ID of the frame. Not unique, but is the name of the element the frame
39 //came from. Useful to tell what kind of frame it is.
40 const std::string& id() const { return id_; }
41 const variant& variant_id() const { return variant_id_; }
42
43 //play a sound. 'object' is just the address of the object playing the
44 //sound, useful if the sound is later cancelled.
45 void play_sound(const void* object=NULL) const;
46 std::vector<bool>::const_iterator alpha_row(int x, int y, int time, bool face_right) const;
47 bool is_alpha(int x, int y, int time, bool face_right) const;
48 void draw_into_blit_queue(graphics::blit_queue& blit, int x, int y, bool face_right=true, bool upside_down=false, int time=0) const;
49 void draw(int x, int y, bool face_right=true, bool upside_down=false, int time=0, int rotate=0) const;
50 void draw(int x, int y, const rect& area, bool face_right=true, bool upside_down=false, int time=0, int rotate=0) const;
51 void set_image_as_solid();
52 const_solid_info_ptr solid() const { return solid_; }
53 int collide_x() const { return collide_rect_.x()*scale_; }
54 int collide_y() const { return collide_rect_.y()*scale_; }
55 int collide_w() const { return collide_rect_.w()*scale_; }
56 int collide_h() const { return collide_rect_.h()*scale_; }
57 int hit_x() const { return hit_rect_.x()*scale_; }
58 int hit_y() const { return hit_rect_.y()*scale_; }
59 int hit_w() const { return hit_rect_.w()*scale_; }
60 int hit_h() const { return hit_rect_.h()*scale_; }
61 int platform_x() const { return platform_rect_.x()*scale_; }
62 int platform_y() const { return platform_rect_.y()*scale_; }
63 int platform_w() const { return platform_rect_.w()*scale_; }
64 bool has_platform() const { return platform_rect_.w() > 0; }
65 int feet_x() const { return feet_x_*scale_; }
66 int feet_y() const { return feet_y_*scale_; }
67 int accel_x() const { return accel_x_; }
68 int accel_y() const { return accel_y_; }
69 int velocity_x() const { return velocity_x_; }
70 int velocity_y() const { return velocity_y_; }
71 int width() const { return img_rect_.w()*scale_; }
72 int height() const { return img_rect_.h()*scale_; }
73 int duration() const;
74 bool hit(int time_in_frame) const;
75 const graphics::texture& img() const { return texture_; }
76 const rect& area() const { return img_rect_; }
77 int num_frames() const { return nframes_; }
78 int num_frames_per_row() const { return nframes_per_row_ > 0 && nframes_per_row_ < nframes_ ? nframes_per_row_ : nframes_; }
79 int pad() const { return pad_; }
80 int blur() const { return blur_; }
81 bool rotate_on_slope() const { return rotate_on_slope_; }
82 int damage() const { return damage_; }
83
84 const std::string* get_event(int time_in_frame) const;
85
86 const std::vector<collision_area>& collision_areas() const { return collision_areas_; }
87
88 int enter_event_id() const { return enter_event_id_; }
89 int end_event_id() const { return end_event_id_; }
90 int leave_event_id() const { return leave_event_id_; }
91 int process_event_id() const { return process_event_id_; }
92
93 struct frame_info {
94 frame_info() : x_adjust(0), y_adjust(0), x2_adjust(0), y2_adjust(0)
95 {}
96 int x_adjust, y_adjust, x2_adjust, y2_adjust;
97 rect area;
98 };
99
100 const std::vector<frame_info>& frame_layout() const { return frames_; }
101 private:
102 int frame_number(int time_in_frame) const;
103
104 void get_rect_in_texture(int time, GLfloat* output_rect, const frame_info*& info) const;
105 void get_rect_in_frame_number(int nframe, GLfloat* output_rect, const frame_info*& info) const;
106 std::string id_, image_;
107
108 //ID as a variant, useful to be able to get a variant of the ID
109 //very efficiently.
110 variant variant_id_;
111
112 //ID's used to signal events that occur on this animation.
113 int enter_event_id_, end_event_id_, leave_event_id_, process_event_id_;
114 graphics::texture texture_;
115 const_solid_info_ptr solid_;
116 rect collide_rect_;
117 rect hit_rect_;
118 rect img_rect_;
119
120 std::vector<frame_info> frames_;
121
122 rect platform_rect_;
123 std::vector<int> hit_frames_;
124 int platform_x_, platform_y_, platform_w_;
125 int feet_x_, feet_y_;
126 int accel_x_, accel_y_;
127 int velocity_x_, velocity_y_;
128 int nframes_;
129 int nframes_per_row_;
130 int frame_time_;
131 bool reverse_frame_;
132 bool play_backwards_;
133 int scale_;
134 int pad_;
135 int rotate_;
136 int blur_;
137 bool rotate_on_slope_;
138 int damage_;
139
140 std::vector<int> event_frames_;
141 std::vector<std::string> event_names_;
142 std::vector <std::string> sounds_;
143
144 std::vector<collision_area> collision_areas_;
145
146 void build_alpha_from_frame_info();
147 void build_alpha();
148 std::vector<bool> alpha_;
149
150 std::vector<int> palettes_recognized_;
151 int current_palette_;
152
153 void set_palettes(unsigned int palettes);
154 };
155
156 #endif
0 #include <iostream>
1
2 #include "framed_gui_element.hpp"
3 #include "geometry.hpp"
4 #include "raster.hpp"
5 #include "wml_node.hpp"
6 #include "wml_utils.hpp"
7
8 namespace {
9 typedef std::map<std::string, const_framed_gui_element_ptr> cache_map;
10 cache_map cache;
11 }
12
13 void framed_gui_element::init(wml::const_node_ptr node)
14 {
15 wml::node::const_child_iterator i1 = node->begin_child("framed_gui_element");
16 wml::node::const_child_iterator i2 = node->end_child("framed_gui_element");
17 for(; i1 != i2; ++i1) {
18 const std::string& id = i1->second->attr("id");
19 cache[id].reset(new framed_gui_element(i1->second));
20 }
21 }
22
23 const_framed_gui_element_ptr framed_gui_element::get(const std::string& key)
24 {
25 cache_map::const_iterator itor = cache.find(key);
26 if(itor == cache.end()) {
27 assert(false); //TODO: replace with an exception.
28 return const_framed_gui_element_ptr();
29 }
30
31 return itor->second;
32 }
33
34
35 framed_gui_element::framed_gui_element(wml::const_node_ptr node)
36 : area_(node->attr("rect")),
37 corner_height_(get_int(node,"corner_height")),
38 texture_(graphics::texture::get(node->attr("image")))
39 {
40
41
42 top_left_corner_ = rect(area_.x(),area_.y(),corner_height_,corner_height_);
43 top_right_corner_ = rect(area_.x2() - corner_height_,area_.y(),corner_height_,corner_height_);
44 bottom_left_corner_ = rect(area_.x(),area_.y2() - corner_height_,corner_height_,corner_height_);
45 bottom_right_corner_ = rect(area_.x2() - corner_height_,area_.y2() - corner_height_,corner_height_,corner_height_);
46
47 top_border_ = rect(area_.x() + corner_height_, area_.y(),area_.w() - corner_height_ * 2, corner_height_);
48 bottom_border_ = rect(area_.x() + corner_height_, area_.y2() - corner_height_,area_.w() - corner_height_ * 2, corner_height_);
49 left_border_ = rect(area_.x(), area_.y() + corner_height_,corner_height_, area_.h() - corner_height_ * 2);
50 right_border_ = rect(area_.x2() - corner_height_, area_.y() + corner_height_,corner_height_,area_.h() - corner_height_ * 2);
51
52 interior_fill_ = rect(area_.x() + corner_height_, area_.y() + corner_height_,area_.w() - corner_height_ * 2,area_.h() - corner_height_ * 2);
53 }
54
55 void framed_gui_element::blit(int x, int y, int w, int h, int scale) const
56 {
57 blit_subsection(interior_fill_,x,y,w,h,scale);
58
59 blit_subsection(top_border_,x,y,w,top_border_.h(),scale);
60 blit_subsection(bottom_border_,x,y + h*scale - bottom_border_.h(),w,bottom_border_.h(),scale);
61 blit_subsection(left_border_,x,y,left_border_.w(),h,scale);
62 blit_subsection(right_border_,x + w*scale - right_border_.w(), y,right_border_.w(),h,scale);
63
64 blit_subsection(top_left_corner_,x,y,top_left_corner_.w(),top_left_corner_.h(),scale);
65 blit_subsection(top_right_corner_,x + w*scale - top_right_corner_.w(),y, top_right_corner_.w(), top_right_corner_.h(),scale);
66 blit_subsection(bottom_left_corner_,x,y + h*scale - bottom_left_corner_.h(),bottom_left_corner_.w(), bottom_left_corner_.h(),scale);
67 blit_subsection(bottom_right_corner_,x + w*scale - bottom_right_corner_.w(),y + h*scale - bottom_right_corner_.h(),bottom_right_corner_.w(), bottom_right_corner_.h(),scale);
68
69
70
71
72 }
73
74 void framed_gui_element::blit_subsection(rect subsection, int x, int y, int w, int h, int scale) const
75 {
76 graphics::blit_texture(texture_, x, y, w*scale, h*scale, 0.0,
77 GLfloat(subsection.x())/GLfloat(texture_.width()),
78 GLfloat(subsection.y())/GLfloat(texture_.height()),
79 GLfloat(subsection.x2())/GLfloat(texture_.width()),
80 GLfloat(subsection.y2())/GLfloat(texture_.height()));
81
82 }
0 #ifndef FRAMED_GUI_ELEMENT_HPP_INCLUDED
1 #define FRAMED_GUI_ELEMENT_HPP_INCLUDED
2
3
4 #include <boost/shared_ptr.hpp>
5 #include "geometry.hpp"
6 #include "texture.hpp"
7 #include "wml_node_fwd.hpp"
8
9
10 class framed_gui_element;
11 typedef boost::shared_ptr<const framed_gui_element> const_framed_gui_element_ptr;
12
13
14 class framed_gui_element
15 {
16 public:
17 static void init(wml::const_node_ptr node);
18 static const_framed_gui_element_ptr get(const std::string& key);
19
20 void blit(int x, int y, int w, int h, int scale=1) const;
21 explicit framed_gui_element(wml::const_node_ptr node);
22
23
24 private:
25 void blit_subsection(rect subsection, int x, int y, int w, int h, int scale=1) const;
26
27 const rect area_;
28 const int corner_height_;
29 graphics::texture texture_;
30
31 rect top_right_corner_;
32 rect top_left_corner_;
33 rect bottom_right_corner_;
34 rect bottom_left_corner_;
35
36 rect top_border_;
37 rect bottom_border_;
38 rect left_border_;
39 rect right_border_;
40
41
42 rect interior_fill_; //later on, we might want to do this as a proper pattern. For now, we've imped this as a stretch-to-fill because it doesn't matter with our current graphics (since they're just a solid color). If we never get anything but solid colors, no need to waste the effort.
43
44 };
45
46 #endif
0 #ifndef FROGATTO_FUNCTIONAL_HPP_INCLUDED
1 #define FROGATTO_FUNCTIONAL_HPP_INCLUDED
2
3
4 #include <vector>
5
6 template<typename To, typename From, typename Fn>
7 std::vector<To> map_vector(const std::vector<From>& v, Fn fn) {
8 std::vector<To> result;
9 result.reserve(v.size());
10 for(typename std::vector<From>::const_iterator i = v.begin(); i != v.end(); ++i) {
11 result.push_back(fn(*i));
12 }
13
14 return result;
15 }
16
17 #endif
0 #include <vector>
1
2 #include <algorithm>
3 #include "formatter.hpp"
4 #include "formula_callable.hpp"
5 #include "geometry.hpp"
6 #include "string_utils.hpp"
7 #include "unit_test.hpp"
8
9 point::point(const std::string& str)
10 {
11 int buf_size = 2;
12 util::split_into_ints(str.c_str(), buf, &buf_size);
13 if(buf_size != 2) {
14 x = y = 0;
15 }
16 }
17
18 std::string point::to_string() const
19 {
20 return formatter() << x << "," << y;
21 }
22
23 bool operator==(const point& a, const point& b) {
24 return a.x == b.x && a.y == b.y;
25 }
26
27 bool operator!=(const point& a, const point& b) {
28 return !operator==(a, b);
29 }
30
31 bool operator<(const point& a, const point& b) {
32 return a.x < b.x || a.x == b.x && a.y < b.y;
33 }
34
35 std::string rect::to_string() const
36 {
37 return formatter() << x() << "," << y() << "," << (x2()-1) << "," << (y2()-1);
38 }
39
40 SDL_Rect rect::sdl_rect() const
41 {
42 SDL_Rect r = {x(), y(), w(), h()};
43 return r;
44 }
45
46 rect rect::from_coordinates(int x1, int y1, int x2, int y2)
47 {
48 if(x1 > x2+1) {
49 std::swap(x1, x2);
50 }
51
52 if(y1 > y2+1) {
53 std::swap(y1, y2);
54 }
55
56 return rect(x1, y1, (x2 - x1) + 1, (y2 - y1) + 1);
57 }
58
59 rect::rect(const std::string& str)
60 {
61 if(str.empty()) {
62 *this = rect();
63 return;
64 }
65
66 int items[4];
67 int num_items = 4;
68 util::split_into_ints(str.c_str(), items, &num_items);
69
70 switch(num_items) {
71 case 2:
72 *this = rect::from_coordinates(items[0], items[1], 1, 1);
73 break;
74 case 3:
75 *this = rect::from_coordinates(items[0], items[1], items[2], 1);
76 break;
77 case 4:
78 *this = rect::from_coordinates(items[0], items[1], items[2], items[3]);
79 break;
80 default:
81 *this = rect();
82 break;
83 }
84 }
85
86 rect::rect(int x, int y, int w, int h)
87 : top_left_(std::min(x, x+w), std::min(y, y+h)),
88 bottom_right_(std::max(x, x+w), std::max(y, y+h))
89 {
90 }
91
92 int rect::x() const
93 {
94 return top_left_.x;
95 }
96
97 int rect::y() const
98 {
99 return top_left_.y;
100 }
101
102 int rect::x2() const
103 {
104 return bottom_right_.x;
105 }
106
107 int rect::y2() const
108 {
109 return bottom_right_.y;
110 }
111
112 int rect::w() const
113 {
114 return bottom_right_.x - top_left_.x;
115 }
116
117 int rect::h() const
118 {
119 return bottom_right_.y - top_left_.y;
120 }
121
122 bool point_in_rect(const point& p, const rect& r)
123 {
124 return p.x >= r.x() && p.y >= r.y() && p.x < r.x2() && p.y < r.y2();
125 }
126
127 bool rects_intersect(const rect& a, const rect& b)
128 {
129 if(a.x2() <= b.x() || b.x2() <= a.x()) {
130 return false;
131 }
132
133 if(a.y2() <= b.y() || b.y2() <= a.y()) {
134 return false;
135 }
136
137 if(a.w() == 0 || a.h() == 0 || b.w() == 0 || b.h() == 0) {
138 return false;
139 }
140
141 return true;
142 }
143
144 rect intersection_rect(const rect& a, const rect& b)
145 {
146 const int x = std::max(a.x(), b.x());
147 const int y = std::max(a.y(), b.y());
148 const int w = std::min(a.x2(), b.x2()) - x;
149 const int h = std::min(a.y2(), b.y2()) - y;
150 return rect(x, y, w, h);
151 }
152
153 int rect_difference(const rect& a, const rect& b, rect* output)
154 {
155 if (rects_intersect(a,b) == false){ //return empty if there's no intersection
156 return -1;
157 }
158
159 /* returning 4 rectangles in this orientation:
160 _________
161 | |___| |
162 | | | |
163 | |___| |
164 |_|___|_| */
165
166 const rect* begin_output = output;
167
168 if(a.x() < b.x()) {
169 //get the left section of the source rectangle
170 *output++ = rect(a.x(), a.y(), b.x() - a.x(), a.h());
171 }
172
173 if(a.x() + a.w() > b.x() + b.w()) {
174 *output++ = rect(b.x() + b.w(), a.y(), (a.x() + a.w()) - (b.x() + b.w()), a.h());
175 }
176
177 if(a.y() < b.y()) {
178 const int x1 = std::max(a.x(), b.x());
179 const int x2 = std::min(a.x() + a.w(), b.x() + b.w());
180 *output++ = rect(x1, a.y(), x2 - x1, b.y() - a.y());
181 }
182
183 if(a.y() + a.h() > b.y() + b.h()) {
184 const int x1 = std::max(a.x(), b.x());
185 const int x2 = std::min(a.x() + a.w(), b.x() + b.w());
186 *output++ = rect(x1, b.y() + b.h(), x2 - x1, (a.y() + a.h()) - (b.y() + b.h()));
187 }
188
189 return output - begin_output;
190 }
191
192 rect rect_union(const rect& a, const rect& b)
193 {
194 if(a.w() == 0 || a.h() == 0) {
195 return b;
196 }
197
198 if(b.w() == 0 || b.h() == 0) {
199 return a;
200 }
201
202 const int x = std::min<int>(a.x(), b.x());
203 const int y = std::min<int>(a.y(), b.y());
204 const int x2 = std::max<int>(a.x2(), b.x2());
205 const int y2 = std::max<int>(a.y2(), b.y2());
206
207 return rect(x, y, x2 - x, y2 - y);
208 }
209
210 std::ostream& operator<<(std::ostream& s, const rect& r)
211 {
212 s << "rect(" << r.x() << ", " << r.y() << ", " << r.x2() << ", " << r.y2() << ")";
213 return s;
214 }
215
216 class rect_callable : public game_logic::formula_callable
217 {
218 rect rect_;
219 variant get_value(const std::string& key) const {
220 if(key == "x") {
221 return variant(rect_.x());
222 } else if(key == "y") {
223 return variant(rect_.y());
224 } else if(key == "x2") {
225 return variant(rect_.x2());
226 } else if(key == "y2") {
227 return variant(rect_.y2());
228 } else if(key == "w") {
229 return variant(rect_.w());
230 } else if(key == "h") {
231 return variant(rect_.h());
232 } else {
233 return variant();
234 }
235 }
236 public:
237 explicit rect_callable(const rect& r) : rect_(r)
238 {}
239 };
240
241 game_logic::formula_callable* rect::callable() const
242 {
243 return new rect_callable(*this);
244 }
245
246 UNIT_TEST(rect)
247 {
248 rect r(10, 10, 10, 10);
249 rect r2(r.to_string());
250 CHECK_EQ(r, r2);
251
252 r = rect(10, 10, 10, 0);
253 CHECK_NE(true, point_in_rect(point(15, 9), r));
254 CHECK_NE(true, point_in_rect(point(15, 10), r));
255 CHECK_NE(true, point_in_rect(point(15, 11), r));
256 CHECK_EQ(r.h(), 0);
257 }
258
259 UNIT_TEST(rect_difference)
260 {
261 rect r(100, 100, 200, 400);
262 rect buf[4];
263
264 CHECK_EQ(rect_difference(r, rect(0, 0, 100, 100), buf), -1);
265
266 CHECK_EQ(rect_difference(r, rect(0, 0, 200, 1000), buf), 1);
267 CHECK_EQ(buf[0], rect(200, 100, 100, 400));
268
269 CHECK_EQ(rect_difference(r, rect(0, 0, 1000, 1000), buf), 0);
270
271 CHECK_EQ(rect_difference(r, rect(150, 150, 50, 50), buf), 4);
272 CHECK_EQ(buf[0], rect(100, 100, 50, 400));
273 CHECK_EQ(buf[1], rect(200, 100, 100, 400));
274 CHECK_EQ(buf[2], rect(150, 100, 50, 50));
275
276 CHECK_EQ(rect_difference(rect(0, 891, 800, 1491), rect(-32, 1344, 1120, 2432), buf), 1);
277 CHECK_EQ(buf[0], rect(0, 891, 800, 453));
278 }
279
280 BENCHMARK(benchmark_rect_str)
281 {
282 static const std::string str = "45,89,100, 120";
283 BENCHMARK_LOOP {
284 const rect r(str);
285 }
286 }
0 #ifndef GEOMETRY_HPP_INCLUDED
1 #define GEOMETRY_HPP_INCLUDED
2
3 #include "SDL.h"
4
5 #include <iostream>
6 #include <string>
7 #include <vector>
8
9 struct point {
10 explicit point(const std::string& str);
11 explicit point(int x=0, int y=0) : x(x), y(y)
12 {}
13
14 std::string to_string() const;
15
16 union {
17 struct { int x, y; };
18 int buf[2];
19 };
20 };
21
22 bool operator==(const point& a, const point& b);
23 bool operator!=(const point& a, const point& b);
24 bool operator<(const point& a, const point& b);
25
26 namespace game_logic {
27 class formula_callable;
28 }
29
30 class rect {
31 public:
32 static rect from_coordinates(int x1, int y1, int x2, int y2);
33 explicit rect(const std::string& str);
34 explicit rect(int x=0, int y=0, int w=0, int h=0);
35 int x() const;
36 int y() const;
37 int x2() const;
38 int y2() const;
39 int w() const;
40 int h() const;
41
42 int mid_x() const { return (x() + x2())/2; }
43 int mid_y() const { return (y() + y2())/2; }
44
45 const point& top_left() const { return top_left_; }
46 const point& bottom_right() const { return bottom_right_; }
47
48 std::string to_string() const;
49
50 SDL_Rect sdl_rect() const;
51
52 bool empty() const { return w() == 0 || h() == 0; }
53
54 game_logic::formula_callable* callable() const;
55 private:
56 point top_left_, bottom_right_;
57 };
58
59 bool point_in_rect(const point& p, const rect& r);
60 bool rects_intersect(const rect& a, const rect& b);
61 rect intersection_rect(const rect& a, const rect& b);
62 int rect_difference(const rect& a, const rect& b, rect* output); //returns a vector containing the parts of A that don't intersect B
63
64 rect rect_union(const rect& a, const rect& b);
65
66 inline bool operator==(const rect& a, const rect& b) {
67 return a.x() == b.x() && a.y() == b.y() && a.w() == b.w() && a.h() == b.h();
68 }
69
70 inline bool operator!=(const rect& a, const rect& b) {
71 return !operator==(a, b);
72 }
73
74 std::ostream& operator<<(std::ostream& s, const rect& r);
75
76 #endif
0 #include <map>
1
2 #include "graphical_font.hpp"
3 #include "raster.hpp"
4 #include "wml_node.hpp"
5 #include "wml_utils.hpp"
6
7 namespace {
8 typedef std::map<std::string, graphical_font_ptr> cache_map;
9 cache_map cache;
10 }
11
12 void graphical_font::init(wml::const_node_ptr node)
13 {
14 wml::node::const_child_iterator i1 = node->begin_child("font");
15 wml::node::const_child_iterator i2 = node->end_child("font");
16 for(; i1 != i2; ++i1) {
17 graphical_font_ptr font(new graphical_font(i1->second));
18 cache[font->id()] = font;
19 }
20 }
21
22 const_graphical_font_ptr graphical_font::get(const std::string& id)
23 {
24 cache_map::const_iterator itor = cache.find(id);
25 if(itor == cache.end()) {
26 return const_graphical_font_ptr();
27 }
28
29 return itor->second;
30 }
31
32 graphical_font::graphical_font(wml::const_node_ptr node)
33 : id_(node->attr("id")), texture_(graphics::texture::get(node->attr("texture"))),
34 kerning_(wml::get_int(node, "kerning", 2))
35 {
36 int pad = 2;
37 if (node->has_attr("pad")){
38 pad = wml::get_int(node, "pad", 2);
39 }
40
41 wml::node::const_child_iterator i1 = node->begin_child("chars");
42 wml::node::const_child_iterator i2 = node->end_child("chars");
43 rect current_rect;
44 for(; i1 != i2; ++i1) {
45 if(i1->second->has_attr("pad")) {
46 pad = wml::get_int(i1->second, "pad");
47 }
48
49 const std::string& chars = i1->second->attr("chars");
50 if(i1->second->has_attr("width")) {
51 current_rect = rect(current_rect.x(), current_rect.y(),
52 wml::get_int(i1->second, "width"),
53 current_rect.h());
54 } else {
55 current_rect = rect(i1->second->attr("rect"));
56 }
57 for(std::string::const_iterator i = chars.begin(); i != chars.end(); ++i) {
58 const unsigned char c = *i;
59 if(char_rect_map_.size() <= c) {
60 char_rect_map_.resize(c + 1);
61 }
62
63 char_rect_map_[c] = current_rect;
64
65 current_rect = rect(current_rect.x() + current_rect.w() + pad,
66 current_rect.y(),
67 current_rect.w(), current_rect.h());
68 }
69 }
70 }
71
72 rect graphical_font::draw(int x, int y, const std::string& text, int size) const
73 {
74 return do_draw(x, y, text, true, size);
75 }
76
77 namespace {
78 std::vector<GLfloat> font_varray;
79 std::vector<GLfloat> font_tcarray;
80 }
81
82 rect graphical_font::do_draw(int x, int y, const std::string& text, bool draw_text, int size) const
83 {
84 if(text.empty()) {
85 return rect(x, y, 0, 0);
86 }
87
88 if(draw_text) {
89 texture_.set_as_current_texture();
90 }
91
92 font_varray.clear();
93 font_tcarray.clear();
94
95 int x2 = x, y2 = y;
96 int xpos = x, ypos = y, highest = 0;
97 for(std::string::const_iterator i = text.begin(); i != text.end(); ++i) {
98 if(*i == '\n') {
99 ypos = y + highest;
100 xpos = x;
101 highest = 0;
102 continue;
103 }
104
105 const unsigned char c = *i;
106 if(c >= char_rect_map_.size() || char_rect_map_[c].w() == 0) {
107 continue;
108 }
109
110 const rect& r = char_rect_map_[c];
111
112 if(draw_text) {
113 const GLfloat TextureEpsilon = 0.1;
114 const GLfloat u1 = graphics::texture::get_coord_x(GLfloat(r.x() + TextureEpsilon)/GLfloat(texture_.width()));
115 const GLfloat v1 = graphics::texture::get_coord_y(GLfloat(r.y() + TextureEpsilon)/GLfloat(texture_.height()));
116 const GLfloat u2 = graphics::texture::get_coord_x(GLfloat(r.x2() - TextureEpsilon)/GLfloat(texture_.width()));
117 const GLfloat v2 = graphics::texture::get_coord_y(GLfloat(r.y2() - TextureEpsilon)/GLfloat(texture_.height()));
118
119 const int x = xpos&preferences::xypos_draw_mask;
120 const int y = ypos&preferences::xypos_draw_mask;
121
122 font_varray.push_back(x);
123 font_varray.push_back(y);
124 font_varray.push_back(x);
125 font_varray.push_back(y);
126 font_varray.push_back(x);
127 font_varray.push_back(y + (r.h())*size);
128 font_tcarray.push_back(u1);
129 font_tcarray.push_back(v1);
130 font_tcarray.push_back(u1);
131 font_tcarray.push_back(v1);
132 font_tcarray.push_back(u1);
133 font_tcarray.push_back(v2);
134
135 font_varray.push_back(x + (r.w())*size);
136 font_varray.push_back(y);
137 font_varray.push_back(x + (r.w())*size);
138 font_varray.push_back(y + (r.h())*size);
139 font_varray.push_back(x + (r.w())*size);
140 font_varray.push_back(y + (r.h())*size);
141 font_tcarray.push_back(u2);
142 font_tcarray.push_back(v1);
143 font_tcarray.push_back(u2);
144 font_tcarray.push_back(v2);
145 font_tcarray.push_back(u2);
146 font_tcarray.push_back(v2);
147 }
148
149 if(xpos + r.w()*size > x2) {
150 x2 = xpos + r.w()*size;
151 }
152
153 if(ypos + r.h()*size > y2) {
154 y2 = ypos + r.h()*size;
155 }
156
157 xpos += r.w()*size + kerning_*size;
158 if(r.h() > highest) {
159 highest = r.h();
160 }
161 }
162
163 if(draw_text && !font_varray.empty()) {
164 texture_.set_as_current_texture();
165 glVertexPointer(2, GL_FLOAT, 0, &font_varray.front());
166 glTexCoordPointer(2, GL_FLOAT, 0, &font_tcarray.front());
167 glDrawArrays(GL_TRIANGLE_STRIP, 0, font_varray.size()/2);
168 }
169
170 return rect(x, y, x2 - x, y2 - y);
171 }
172
173 rect graphical_font::dimensions(const std::string& text, int size) const
174 {
175 return do_draw(0, 0, text, false, size);
176 }
177
0 #ifndef GRAPHICAL_FONT_HPP_INCLUDED
1 #define GRAPHICAL_FONT_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 #include <string>
6 #include <vector>
7
8 #include "geometry.hpp"
9 #include "texture.hpp"
10 #include "wml_node_fwd.hpp"
11
12 class graphical_font;
13 typedef boost::shared_ptr<graphical_font> graphical_font_ptr;
14 typedef boost::shared_ptr<const graphical_font> const_graphical_font_ptr;
15
16 class graphical_font
17 {
18 public:
19 static void init(wml::const_node_ptr node);
20 static const_graphical_font_ptr get(const std::string& id);
21 explicit graphical_font(wml::const_node_ptr node);
22 const std::string& id() const { return id_; }
23 rect draw(int x, int y, const std::string& text, int size=2) const;
24 rect dimensions(const std::string& text, int size=2) const;
25
26 private:
27 rect do_draw(int x, int y, const std::string& text, bool draw_text, int size) const;
28
29 std::string id_;
30
31 graphics::texture texture_;
32 std::vector<rect> char_rect_map_;
33 int kerning_;
34 };
35
36 #endif
0 #include "asserts.hpp"
1 #include "graphical_font_label.hpp"
2
3 namespace gui {
4
5 graphical_font_label::graphical_font_label(
6 const std::string& text, const std::string& font, int size)
7 : text_(text), font_(graphical_font::get(font)), size_(size)
8 {
9 ASSERT_LOG(font_.get(), "UNKNOWN FONT: " << font);
10 rect dim = font_->dimensions(text_, size_);
11 widget::set_dim(dim.w(), dim.h());
12 }
13
14 void graphical_font_label::handle_draw() const
15 {
16 font_->draw(x(), y(), text_, size_);
17 }
18
19 }
0 #ifndef GRAPHICAL_FONT_LABEL_HPP_INCLUDED
1 #define GRAPHICAL_FONT_LABEL_HPP_INCLUDED
2
3 #include "graphical_font.hpp"
4 #include "widget.hpp"
5
6 namespace gui {
7
8 class graphical_font_label : public widget
9 {
10 public:
11 graphical_font_label(const std::string& text, const std::string& font, int size=1);
12
13 private:
14 void handle_draw() const;
15
16 std::string text_;
17 const_graphical_font_ptr font_;
18 int size_;
19 };
20
21 }
22
23 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include <iostream>
13
14 #include "foreach.hpp"
15 #include "grid_widget.hpp"
16 #include "raster.hpp"
17
18 namespace gui {
19
20 grid::grid(int ncols)
21 : ncols_(ncols), col_widths_(ncols, 0),
22 col_aligns_(ncols, grid::ALIGN_LEFT), row_height_(0),
23 selected_row_(-1), allow_selection_(false), must_select_(false),
24 swallow_clicks_(false), hpad_(0), show_background_(false)
25 {
26 set_dim(0,0);
27 }
28
29 void grid::add_row(const std::vector<widget_ptr>& widgets)
30 {
31 assert(widgets.size() == ncols_);
32 int index = 0;
33 foreach(const widget_ptr& widget, widgets) {
34 cells_.push_back(widget);
35
36 if(widget && widget->width()+hpad_ > col_widths_[index]) {
37 col_widths_[index] = widget->width()+hpad_;
38 }
39
40 if(widget && widget->height() > row_height_) {
41 row_height_ = widget->height();
42 }
43
44 ++index;
45 }
46
47 recalculate_dimensions();
48 }
49
50 grid& grid::add_col(const widget_ptr& widget) {
51 new_row_.push_back(widget);
52 if(new_row_.size() == ncols_) {
53 add_row(new_row_);
54 new_row_.clear();
55 }
56 return *this;
57 }
58
59 grid& grid::finish_row()
60 {
61 while(!new_row_.empty()) {
62 add_col();
63 }
64
65 return *this;
66 }
67
68 grid& grid::set_col_width(int col, int width)
69 {
70 assert(col >= 0 && col < ncols_);
71 col_widths_[col] = width;
72 recalculate_dimensions();
73 return *this;
74 }
75
76 grid& grid::set_align(int col, grid::COLUMN_ALIGN align)
77 {
78 assert(col >= 0 && col < ncols_);
79 col_aligns_[col] = align;
80 recalculate_dimensions();
81 return *this;
82 }
83
84 grid& grid::set_hpad(int pad)
85 {
86 hpad_ = pad;
87 return *this;
88 }
89
90 void grid::register_mouseover_callback(grid::callback_type ptr)
91 {
92 on_mouseover_ = ptr;
93 }
94
95 void grid::register_selection_callback(grid::callback_type ptr)
96 {
97 on_select_ = ptr;
98 }
99
100 void grid::register_row_selection_callback(boost::function<void()> ptr)
101 {
102 row_callbacks_.push_back(ptr);
103 }
104
105 int grid::row_at(int xpos, int ypos) const
106 {
107 if(xpos > x() && xpos < x() + width() &&
108 ypos > y() && ypos < y() + height()) {
109 return (ypos - y()) / row_height_;
110 } else {
111 return -1;
112 }
113 }
114
115 void grid::recalculate_dimensions()
116 {
117 int w = 0;
118 foreach(int width, col_widths_) {
119 w += width;
120 }
121
122 set_dim(w, row_height_*nrows());
123
124 int y = 0;
125 for(int n = 0; n != nrows(); ++n) {
126 int x = 0;
127 for(int m = 0; m != ncols_; ++m) {
128 int align = 0;
129 widget_ptr widget = cells_[n*ncols_ + m];
130 if(widget) {
131 switch(col_aligns_[m]) {
132 case ALIGN_LEFT:
133 align = 0;
134 break;
135 case ALIGN_CENTER:
136 align = (col_widths_[m] - widget->width())/2;
137 break;
138 case ALIGN_RIGHT:
139 align = col_widths_[m] - widget->width();
140 break;
141 }
142
143 widget->set_loc(x+align,y+row_height_/2 - widget->height()/2);
144 }
145
146 x += col_widths_[m];
147 }
148
149 y += row_height_;
150 }
151 }
152
153 void grid::handle_draw() const
154 {
155 glPushMatrix();
156 glTranslatef(x(), y(), 0.0);
157 if(show_background_) {
158 const SDL_Color bg = {0,0,0};
159 SDL_Rect rect = {0,0,width(),height()};
160 graphics::draw_rect(rect,bg);
161 }
162
163 if(selected_row_ >= 0 && selected_row_ < nrows()) {
164 SDL_Rect rect = {0,row_height_*selected_row_,width(),row_height_};
165 const SDL_Color col = {0xFF,0x00,0x00,0x00};
166 graphics::draw_rect(rect,col,128);
167 }
168 foreach(const widget_ptr& widget, cells_) {
169 if(widget) {
170 widget->draw();
171 }
172 }
173 glPopMatrix();
174 }
175
176 bool grid::handle_event(const SDL_Event& event, bool claimed)
177 {
178 if(claimed) {
179 return claimed;
180 }
181 if(allow_selection_) {
182 if(event.type == SDL_MOUSEMOTION) {
183 const SDL_MouseMotionEvent& e = event.motion;
184 int new_row = row_at(e.x,e.y);
185 if(new_row != selected_row_) {
186 selected_row_ = new_row;
187 if(on_mouseover_) {
188 on_mouseover_(new_row);
189 }
190 }
191 } else if(event.type == SDL_MOUSEBUTTONDOWN) {
192 const SDL_MouseButtonEvent& e = event.button;
193 const int row_index = row_at(e.x, e.y);
194 std::cerr << "SELECT ROW: " << row_index << "\n";
195 if(row_index >= 0 && row_index < row_callbacks_.size() &&
196 row_callbacks_[row_index]) {
197 std::cerr << "ROW CB: " << row_index << "\n";
198 row_callbacks_[row_index]();
199 }
200
201 if(on_select_) {
202 on_select_(row_index);
203 }
204
205 if(swallow_clicks_) {
206 claimed = true;
207 }
208 }
209 }
210
211 if(must_select_) {
212 if(event.type == SDL_KEYDOWN) {
213 if(event.key.keysym.sym == SDLK_UP) {
214 if(selected_row_-- == 0) {
215 selected_row_ = nrows()-1;
216 }
217 claimed = true;
218 } else if(event.key.keysym.sym == SDLK_DOWN) {
219 if(++selected_row_ == nrows()) {
220 selected_row_ = 0;
221 }
222 claimed = true;
223 }
224 }
225 }
226
227 if(claimed) {
228 return claimed;
229 }
230
231 SDL_Event ev = event;
232 normalize_event(&ev);
233 foreach(const widget_ptr& widget, cells_) {
234 if(widget) {
235 claimed = widget->process_event(ev, claimed);
236 }
237 if(claimed) break;
238 }
239 return claimed;
240 }
241
242 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef GRID_WIDGET_HPP_INCLUDED
13 #define GRID_WIDGET_HPP_INCLUDED
14
15 #include <boost/function.hpp>
16 #include <boost/shared_ptr.hpp>
17 #include <vector>
18
19 #include "grid_widget_fwd.hpp"
20 #include "widget.hpp"
21
22 namespace gui {
23
24 class grid : public widget
25 {
26 public:
27 typedef boost::function<void (int)> callback_type;
28 enum COLUMN_ALIGN { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT };
29
30 explicit grid(int ncols);
31 virtual ~grid() {}
32 grid& set_show_background(bool val) {
33 show_background_ = val;
34 return *this;
35 }
36 void add_row(const std::vector<widget_ptr>& widgets);
37
38 grid& add_col(const widget_ptr& widget=widget_ptr());
39
40 grid& finish_row();
41
42 grid& set_col_width(int col, int width);
43 grid& set_align(int col, COLUMN_ALIGN align);
44 grid& set_hpad(int pad);
45
46 void allow_selection(bool val=true) { allow_selection_ = val; }
47 void must_select(bool val=true) { must_select_ = val; selected_row_ = 0; }
48 void swallow_clicks(bool val=true) { swallow_clicks_ = val; }
49 int selection() const { return selected_row_; }
50 void register_mouseover_callback(callback_type cb);
51 void register_selection_callback(callback_type cb);
52 void register_row_selection_callback(boost::function<void()> cb);
53 private:
54 int row_at(int x, int y) const;
55 void recalculate_dimensions();
56 void handle_draw() const;
57 bool handle_event(const SDL_Event& event, bool claimed);
58
59 int nrows() const { return cells_.size()/ncols_; }
60 int ncols_;
61 std::vector<widget_ptr> cells_;
62 std::vector<int> col_widths_;
63 std::vector<COLUMN_ALIGN> col_aligns_;
64 int row_height_;
65 int selected_row_;
66 bool allow_selection_;
67 bool must_select_;
68 bool swallow_clicks_;
69
70 std::vector<widget_ptr> new_row_;
71 std::vector<boost::function<void()> > row_callbacks_;
72 callback_type on_mouseover_;
73 callback_type on_select_;
74 int hpad_;
75 bool show_background_;
76 };
77
78 typedef boost::shared_ptr<grid> grid_ptr;
79 typedef boost::shared_ptr<const grid> const_grid_ptr;
80
81 }
82
83 #endif
0 #ifndef GRID_WIDGET_FWD_HPP_INCLUDED
1 #define GRID_WIDGET_FWD_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 namespace gui {
6
7 class grid;
8 typedef boost::shared_ptr<grid> grid_ptr;
9 typedef boost::shared_ptr<const grid> const_grid_ptr;
10
11 }
12
13 #endif
0 #include <boost/bind.hpp>
1 #include <boost/function.hpp>
2 #include <sstream>
3
4 #include "button.hpp"
5 #include "editor_dialogs.hpp"
6 #include "foreach.hpp"
7 #include "grid_widget.hpp"
8 #include "group_property_editor_dialog.hpp"
9 #include "image_widget.hpp"
10 #include "label.hpp"
11 #include "load_level.hpp"
12 #include "raster.hpp"
13 #include "text_entry_widget.hpp"
14
15 namespace editor_dialogs
16 {
17
18 group_property_editor_dialog::group_property_editor_dialog(editor& e)
19 : gui::dialog(graphics::screen_width() - 160, 160, 160, 440), editor_(e)
20 {
21 group_ = e.get_level().editor_selection();
22 init();
23 }
24
25 void group_property_editor_dialog::init()
26 {
27 clear();
28
29 using namespace gui;
30 set_padding(20);
31
32 if(group_.empty() == false) {
33 std::cerr << "ADD BUTTON\n";
34 add_widget(widget_ptr(new button(widget_ptr(new label("Group Objects", graphics::color_white())), boost::bind(&group_property_editor_dialog::group_objects, this))), 10, 10);
35 }
36 }
37
38 void group_property_editor_dialog::set_group(const std::vector<entity_ptr>& group)
39 {
40 group_ = group;
41 init();
42 }
43
44 void group_property_editor_dialog::group_objects()
45 {
46 editor_.group_selection();
47 }
48
49 }
0 #ifndef GROUP_PROPERTY_EDITOR_DIALOG_HPP_INCLUDED
1 #define GROUP_PROPERTY_EDITOR_DIALOG_HPP_INCLUDED
2
3 #include <string>
4 #include <vector>
5
6 #include "dialog.hpp"
7 #include "editor.hpp"
8 #include "entity.hpp"
9
10 namespace editor_dialogs
11 {
12
13 class group_property_editor_dialog : public gui::dialog
14 {
15 public:
16 explicit group_property_editor_dialog(editor& e);
17 void init();
18
19 void set_group(const std::vector<entity_ptr>& group);
20 private:
21 void group_objects();
22
23 editor& editor_;
24 std::vector<entity_ptr> group_;
25 };
26
27 }
28
29 #endif
0 #include <boost/bind.hpp>
1 #include <boost/function.hpp>
2 #include <boost/shared_ptr.hpp>
3
4 #include <assert.h>
5 #include <map>
6
7 #include "asserts.hpp"
8 #include "custom_object.hpp"
9 #include "custom_object_callable.hpp"
10 #include "custom_object_functions.hpp"
11 #include "custom_object_type.hpp"
12 #include "draw_number.hpp"
13 #include "formula.hpp"
14 #include "formula_callable_definition.hpp"
15 #include "frame.hpp"
16 #include "graphical_font.hpp"
17 #include "gui_formula_functions.hpp"
18 #include "iphone_controls.hpp"
19 #include "level.hpp"
20 #include "preferences.hpp"
21 #include "raster.hpp"
22 #include "unit_test.hpp"
23 #include "wml_node.hpp"
24 #include "wml_parser.hpp"
25 #include "wml_utils.hpp"
26
27 using namespace game_logic;
28
29 namespace {
30
31 typedef boost::function<void (const gui_algorithm* algo)> gui_command_function;
32
33 class gui_command : public formula_callable {
34 variant get_value(const std::string& key) const { return variant(); }
35 public:
36
37 virtual void execute(const gui_algorithm& algo) = 0;
38 };
39
40 class draw_text_command : public gui_command {
41 const_graphical_font_ptr font_;
42 std::string text_;
43 int x_, y_;
44 public:
45 draw_text_command(const_graphical_font_ptr font, const std::string& text, int x, int y)
46 : font_(font), text_(text), x_(x), y_(y)
47 {}
48
49 void execute(const gui_algorithm& algo) {
50 font_->draw(x_, y_, text_);
51 }
52 };
53
54 class draw_text_function : public function_expression {
55 public:
56 explicit draw_text_function(const args_list& args)
57 : function_expression("draw_text", args, 3, 4)
58 {}
59 private:
60 variant execute(const formula_callable& variables) const {
61 std::string font = "default";
62
63 int arg = 0;
64 if(args().size() == 4) {
65 font = args()[arg++]->evaluate(variables).as_string();
66 }
67
68 std::string text = args()[arg++]->evaluate(variables).as_string();
69 const int x = args()[arg++]->evaluate(variables).as_int();
70 const int y = args()[arg++]->evaluate(variables).as_int();
71 return variant(new draw_text_command(graphical_font::get(font), text, x, y));
72 }
73 };
74
75 class draw_number_command : public gui_command {
76 graphics::blit_queue blit_;
77 public:
78 draw_number_command(int number, int places, int x, int y)
79 {
80 queue_draw_number(blit_, number, places, x, y);
81 }
82
83 void execute(const gui_algorithm& algo) {
84 blit_.do_blit();
85 }
86 };
87
88 class draw_number_function : public function_expression {
89 public:
90 explicit draw_number_function(const args_list& args)
91 : function_expression("draw_number", args, 4, 4)
92 {}
93 private:
94 variant execute(const formula_callable& variables) const {
95 static cache_entry cache[4];
96 static int cache_loc;
97
98 const int number = args()[0]->evaluate(variables).as_int();
99 const int places = args()[1]->evaluate(variables).as_int();
100 const int x = args()[2]->evaluate(variables).as_int();
101 const int y = args()[3]->evaluate(variables).as_int();
102 for(int n = 0; n != sizeof(cache)/sizeof(*cache); ++n) {
103 if(x == cache[n].x && y == cache[n].y && number == cache[n].number && places == cache[n].places) {
104 return cache[n].result;
105 }
106 }
107 const int n = cache_loc++%(sizeof(cache)/sizeof(*cache));
108 cache[n].x = x;
109 cache[n].y = y;
110 cache[n].number = number;
111 cache[n].places = places;
112 cache[n].result = variant(new draw_number_command(number, places, x, y));
113 return cache[n].result;
114 }
115
116 struct cache_entry {
117 cache_entry() : x(-1), y(-1) {}
118 int number, places, x, y;
119 variant result;
120 };
121
122 };
123
124 class draw_animation_area_command : public gui_command {
125 frame_ptr frame_;
126 int x_, y_;
127 rect area_;
128
129 variant get_value(const std::string& key) const { return variant(); }
130 public:
131 draw_animation_area_command(const frame_ptr& f, int x, int y, const rect& area)
132 : frame_(f), x_(x), y_(y), area_(area)
133 {}
134
135 void execute(const gui_algorithm& algo) {
136 frame_->draw(x_, y_, area_);
137 }
138 };
139
140 class draw_animation_area_function : public function_expression {
141 public:
142 draw_animation_area_function(gui_algorithm* algo, const args_list& args)
143 : function_expression("draw_animation_area", args, 4, 7), algo_(algo)
144 {}
145
146 gui_algorithm* algo_;
147 private:
148 variant execute(const formula_callable& variables) const {
149 variant anim = args()[0]->evaluate(variables);
150 const frame_ptr f = algo_->get_frame(anim.as_string());
151 if(!f) {
152 return variant();
153 }
154
155 const int x = args()[1]->evaluate(variables).as_int();
156 const int y = args()[2]->evaluate(variables).as_int();
157
158 int vals[4];
159 for(int n = 0; n != args().size() - 3; ++n) {
160 vals[n] = args()[n+3]->evaluate(variables).as_int();
161 }
162
163 rect area;
164 if(args().size() == 4) {
165 area = rect(0, 0, vals[0], f->height()/2);
166 } else if(args().size() == 5) {
167 area = rect(0, 0, vals[0], vals[1]);
168
169 } else {
170 ASSERT_EQ(args().size(), 7);
171 area = rect(vals[0], vals[1], vals[2], vals[3]);
172 }
173
174 return variant(new draw_animation_area_command(f, x, y, area));
175 }
176 };
177
178 class draw_animation_command : public gui_command {
179 graphics::blit_queue blit_;
180 variant get_value(const std::string& key) const { return variant(); }
181 public:
182 draw_animation_command(const frame_ptr& f, int x, int y)
183 {
184 f->draw_into_blit_queue(blit_, x, y);
185 }
186
187 void execute(const gui_algorithm& algo) {
188 blit_.do_blit();
189 }
190 };
191
192 class draw_animation_function : public function_expression {
193 public:
194 draw_animation_function(gui_algorithm* algo, const args_list& args)
195 : function_expression("draw_animation", args, 3, 3), algo_(algo)
196 {}
197
198 gui_algorithm* algo_;
199 private:
200 variant execute(const formula_callable& variables) const {
201 variant anim = args()[0]->evaluate(variables);
202 const frame_ptr f = algo_->get_frame(anim.as_string());
203 if(!f) {
204 return variant();
205 }
206
207 const int x = args()[1]->evaluate(variables).as_int();
208 const int y = args()[2]->evaluate(variables).as_int();
209
210 return variant(new draw_animation_command(f, x, y));
211 }
212 };
213
214 class color_command : public gui_command {
215 unsigned char r_, g_, b_, a_;
216 public:
217 color_command(int r, int g, int b, int a) : r_(r), g_(g), b_(b), a_(a)
218 {}
219
220 void execute(const gui_algorithm& algo) {
221 glColor4ub(r_, g_, b_, a_);
222 }
223 };
224
225 class color_function : public function_expression {
226 public:
227 explicit color_function(const args_list& args)
228 : function_expression("color", args, 4, 4)
229 {}
230 private:
231 variant execute(const formula_callable& variables) const {
232 return variant(new color_command(
233 args()[0]->evaluate(variables).as_int(),
234 args()[1]->evaluate(variables).as_int(),
235 args()[2]->evaluate(variables).as_int(),
236 args()[3]->evaluate(variables).as_int()));
237 }
238 };
239
240 class gui_command_function_symbol_table : public function_symbol_table
241 {
242 gui_algorithm* algo_;
243 public:
244 gui_command_function_symbol_table(gui_algorithm* algo) : algo_(algo)
245 {}
246
247 expression_ptr create_function(
248 const std::string& fn,
249 const std::vector<expression_ptr>& args,
250 const formula_callable_definition* callable_def) const
251 {
252 if(fn == "draw_animation") {
253 return expression_ptr(new draw_animation_function(algo_, args));
254 } else if(fn == "draw_animation_area") {
255 return expression_ptr(new draw_animation_area_function(algo_, args));
256 } else if(fn == "draw_number") {
257 return expression_ptr(new draw_number_function(args));
258 } else if(fn == "draw_text") {
259 return expression_ptr(new draw_text_function(args));
260 } else if(fn == "color") {
261 return expression_ptr(new color_function(args));
262 }
263
264 return function_symbol_table::create_function(fn, args, callable_def);
265 }
266 };
267
268 const std::string AlgorithmProperties[] = {
269 "level", "object", "cycle", "screen_width", "screen_height",
270 };
271
272 enum ALGORITHM_PROPERTY_ID {
273 ALGO_LEVEL, ALGO_OBJECT, ALGO_CYCLE, ALGO_SCREEN_WIDTH, ALGO_SCREEN_HEIGHT,
274 };
275
276 class gui_algorithm_definition : public game_logic::formula_callable_definition
277 {
278 public:
279 static const gui_algorithm_definition& instance() {
280 static const gui_algorithm_definition def;
281 return def;
282 }
283
284 gui_algorithm_definition() {
285 for(int n = 0; n != sizeof(AlgorithmProperties)/sizeof(*AlgorithmProperties); ++n) {
286 entries_.push_back(entry(AlgorithmProperties[n]));
287 keys_to_slots_[AlgorithmProperties[n]] = n;
288 }
289
290 entries_[ALGO_OBJECT].type_definition = &custom_object_type::get("dummy_gui_object")->callable_definition();
291 entries_[ALGO_LEVEL].type_definition = &level::get_formula_definition();
292 }
293
294 int get_slot(const std::string& key) const {
295 std::map<std::string, int>::const_iterator i = keys_to_slots_.find(key);
296 if(i == keys_to_slots_.end()) {
297 return -1;
298 }
299
300 return i->second;
301 }
302
303 const entry* get_entry(int slot) const {
304 if(slot < 0 || slot >= entries_.size()) {
305 return NULL;
306 }
307
308 return &entries_[slot];
309 }
310
311 int num_slots() const {
312 return entries_.size();
313 }
314 private:
315 std::vector<entry> entries_;
316 std::map<std::string, int> keys_to_slots_;
317 };
318
319 } // namespace
320
321 gui_algorithm::gui_algorithm(wml::const_node_ptr node)
322 : lvl_(NULL),
323 process_formula_(formula::create_optional_formula(node->attr("on_process"), &get_custom_object_functions_symbol_table(), &gui_algorithm_definition::instance())),
324 cycle_(0), object_(new custom_object("dummy_gui_object", 0, 0, true))
325 {
326 gui_command_function_symbol_table symbols(this);
327
328 object_->add_ref();
329 FOREACH_WML_CHILD(frame_node, node, "animation") {
330 frame_ptr f(new frame(frame_node));
331 frames_[frame_node->attr("id")] = f;
332 }
333
334 draw_formula_ = formula::create_optional_formula(node->attr("on_draw"), &symbols, &gui_algorithm_definition::instance());
335 }
336
337 gui_algorithm::~gui_algorithm()
338 {
339 }
340
341 gui_algorithm_ptr gui_algorithm::get(const std::string& key) {
342 static std::map<std::string, gui_algorithm_ptr> algorithms;
343 gui_algorithm_ptr& ptr = algorithms[key];
344 if(!ptr) {
345 ptr = create(key);
346 }
347
348 return ptr;
349 }
350
351 void gui_algorithm::new_level() {
352 cycle_ = 0;
353 object_ = boost::intrusive_ptr<custom_object>(new custom_object("dummy_gui_object", 0, 0, true));
354 }
355
356 void gui_algorithm::process(level& lvl) {
357 lvl_ = &lvl;
358 ++cycle_;
359 if((cycle_%2) == 0 && process_formula_) {
360 object_->set_level(lvl);
361 variant result = process_formula_->execute(*this);
362 object_->execute_command(result);
363 }
364 }
365
366 void gui_algorithm::draw(const level& lvl) {
367 lvl_ = &lvl;
368
369 if((cycle_%2) == 0) {
370 cached_draw_commands_ = variant();
371 }
372
373 if(cached_draw_commands_.is_null() && draw_formula_) {
374 cached_draw_commands_ = draw_formula_->execute(*this);
375 }
376
377 execute_command(cached_draw_commands_);
378
379 glColor4ub(255, 255, 255, 255);
380
381 iphone_controls::draw();
382 }
383
384 void gui_algorithm::execute_command(variant v) {
385 if(v.is_list()) {
386 const int num_elements = v.num_elements();
387 for(int n = 0; n != num_elements; ++n) {
388 execute_command(v[n]);
389 }
390
391 return;
392 }
393
394 gui_command* cmd = v.try_convert<gui_command>();
395 if(cmd) {
396 cmd->execute(*this);
397 }
398 }
399
400 gui_algorithm_ptr gui_algorithm::create(const std::string& key) {
401 static const std::string path = preferences::load_compiled() ? "data/compiled/gui/" : "data/gui/";
402 return gui_algorithm_ptr(new gui_algorithm(wml::parse_wml_from_file(path + key + ".cfg")));
403 }
404
405 void gui_algorithm::draw_animation(const std::string& object_name, const std::string& anim, int x, int y, int cycle) const {
406 const frame* f = NULL;
407 if(object_name.empty() == false) {
408 const_custom_object_type_ptr obj = custom_object_type::get(object_name);
409 if(obj) {
410 f = &obj->get_frame(anim);
411 }
412 }
413
414 if(!f) {
415 const std::map<std::string, frame_ptr>::const_iterator itor = frames_.find(anim);
416 if(itor != frames_.end()) {
417 f = itor->second.get();
418 }
419 }
420
421 if(f) {
422 if(cycle == -1) {
423 cycle = cycle_%f->duration();
424 }
425
426 f->draw(x, y, true, false, cycle);
427 }
428 }
429
430 void gui_algorithm::color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) const
431 {
432 glColor4ub(r, g, b, a);
433 }
434
435 frame_ptr gui_algorithm::get_frame(const std::string& id) const
436 {
437 const std::map<std::string, frame_ptr>::const_iterator itor = frames_.find(id);
438 if(itor != frames_.end()) {
439 return itor->second;
440 } else {
441 return frame_ptr();
442 }
443 }
444
445 variant gui_algorithm::get_value(const std::string& key) const
446 {
447 if(key == "level") {
448 return variant(lvl_);
449 } else if(key == "object") {
450 return variant(object_.get());
451 } else if(key == "cycle") {
452 return variant(cycle_);
453 } else if(key == "screen_width") {
454 return variant(graphics::screen_width());
455 } else if(key == "screen_height") {
456 return variant(graphics::screen_height());
457 } else {
458 return variant();
459 }
460 }
461
462 variant gui_algorithm::get_value_by_slot(int slot) const
463 {
464 switch(slot) {
465 case ALGO_LEVEL:
466 return variant(lvl_);
467 case ALGO_OBJECT:
468 return variant(object_.get());
469 case ALGO_CYCLE:
470 return variant(cycle_);
471 case ALGO_SCREEN_WIDTH:
472 return variant(graphics::screen_width());
473 case ALGO_SCREEN_HEIGHT:
474 return variant(graphics::screen_height());
475 }
476
477 return variant();
478 }
479
480 #include "level.hpp"
481
482 BENCHMARK(gui_algorithm_bench)
483 {
484 static boost::intrusive_ptr<level> lvl;
485 if(!lvl) {
486 lvl = boost::intrusive_ptr<level>(new level("titlescreen.cfg"));
487 lvl->finish_loading();
488 lvl->set_as_current_level();
489 }
490
491 static gui_algorithm_ptr gui(gui_algorithm::get("default"));
492 BENCHMARK_LOOP {
493 gui->draw(*lvl);
494 }
495 }
0 #ifndef GUI_FORMULA_FUNCTIONS_HPP_INCLUDED
1 #define GUI_FORMULA_FUNCTIONS_HPP_INCLUDED
2
3 #include "custom_object.hpp"
4
5 #include <boost/intrusive_ptr.hpp>
6 #include <boost/shared_ptr.hpp>
7
8 #include <string>
9
10 class frame;
11 class level;
12
13 class gui_algorithm;
14 typedef boost::intrusive_ptr<gui_algorithm> gui_algorithm_ptr;
15 typedef boost::shared_ptr<frame> frame_ptr;
16
17 class gui_algorithm : public game_logic::formula_callable {
18 public:
19 gui_algorithm(wml::const_node_ptr node);
20 ~gui_algorithm();
21
22 static gui_algorithm_ptr get(const std::string& key);
23 static gui_algorithm_ptr create(const std::string& key);
24
25 void new_level();
26
27 void process(level& lvl);
28 void draw(const level& lvl);
29
30 void draw_animation(const std::string& object_name, const std::string& anim, int x, int y, int cycle) const;
31 void color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) const;
32
33 frame_ptr get_frame(const std::string& id) const;
34
35 const custom_object* get_object() const { return object_.get(); }
36
37 private:
38 gui_algorithm(const gui_algorithm&);
39 void operator=(const gui_algorithm&);
40
41 variant get_value(const std::string& key) const;
42 variant get_value_by_slot(int slot) const;
43
44 void execute_command(variant v);
45
46 const level* lvl_;
47 game_logic::formula_ptr draw_formula_, process_formula_;
48 int cycle_;
49
50 std::map<std::string, frame_ptr> frames_;
51
52 boost::intrusive_ptr<custom_object> object_;
53
54 variant cached_draw_commands_;
55 };
56
57 #endif
0 #include <map>
1
2 #include "gui_section.hpp"
3 #include "raster.hpp"
4 #include "wml_node.hpp"
5 #include "wml_utils.hpp"
6
7 namespace {
8 typedef std::map<std::string, const_gui_section_ptr> cache_map;
9 cache_map cache;
10 }
11
12 void gui_section::init(wml::const_node_ptr node)
13 {
14 wml::node::const_child_iterator i1 = node->begin_child("section");
15 wml::node::const_child_iterator i2 = node->end_child("section");
16 for(; i1 != i2; ++i1) {
17 const std::string& id = i1->second->attr("id");
18 cache[id].reset(new gui_section(i1->second));
19 }
20 }
21
22 const_gui_section_ptr gui_section::get(const std::string& key)
23 {
24 cache_map::const_iterator itor = cache.find(key);
25 if(itor == cache.end()) {
26 assert(false); //TODO: replace with an exception.
27 return const_gui_section_ptr();
28 }
29
30 return itor->second;
31 }
32
33 gui_section::gui_section(wml::const_node_ptr node)
34 : texture_(graphics::texture::get(node->attr("image"))),
35 area_(node->attr("rect"))
36 {
37 }
38
39 void gui_section::blit(int x, int y, int w, int h) const
40 {
41 const GLfloat TextureEpsilon = 0.1;
42 graphics::blit_texture(texture_, x, y, w, h, 0.0,
43 GLfloat(area_.x()+TextureEpsilon)/GLfloat(texture_.width()),
44 GLfloat(area_.y()+TextureEpsilon)/GLfloat(texture_.height()),
45 GLfloat(area_.x2()-TextureEpsilon)/GLfloat(texture_.width()),
46 GLfloat(area_.y2()-TextureEpsilon)/GLfloat(texture_.height()));
47 }
0 #ifndef GUI_SECTION_HPP_INCLUDED
1 #define GUI_SECTION_HPP_INCLUDED
2
3 #include <string>
4
5 #include <boost/shared_ptr.hpp>
6
7 #include "geometry.hpp"
8 #include "texture.hpp"
9 #include "wml_node_fwd.hpp"
10
11 class gui_section;
12 typedef boost::shared_ptr<const gui_section> const_gui_section_ptr;
13
14 class gui_section
15 {
16 public:
17 static void init(wml::const_node_ptr node);
18 static const_gui_section_ptr get(const std::string& key);
19
20 explicit gui_section(wml::const_node_ptr node);
21
22 void blit(int x, int y) const { blit(x, y, width(), height()); }
23 void blit(int x, int y, int w, int h) const;
24 int width() const { return area_.w()*2; }
25 int height() const { return area_.h()*2; }
26 private:
27 graphics::texture texture_;
28 rect area_;
29 };
30
31 #endif
0 #ifndef HI_RES_TIMER_HPP_INCLUDED
1 #define HI_RES_TIMER_HPP_INCLUDED
2
3 #ifdef linux
4 #include <stdio.h>
5 #include <sys/time.h>
6 #endif
7
8 struct hi_res_timer {
9 hi_res_timer(const char* str) {
10 #ifdef linux
11 str_ = str;
12 gettimeofday(&tv_, 0);
13 #endif
14 };
15
16 #ifdef linux
17 ~hi_res_timer() {
18 const int begin = (tv_.tv_sec%1000)*1000000 + tv_.tv_usec;
19 gettimeofday(&tv_, 0);
20 const int end = (tv_.tv_sec%1000)*1000000 + tv_.tv_usec;
21 fprintf(stderr, "TIMER: %s: %dus\n", str_, (end - begin));
22 }
23
24 const char* str_;
25 timeval tv_;
26 #endif
27 };
28
29 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include "image_widget.hpp"
13 #include "raster.hpp"
14
15 #include <iostream>
16
17 namespace gui {
18
19 image_widget::image_widget(const std::string& fname, int w, int h)
20 : texture_(graphics::texture::get(fname)), rotate_(0.0)
21 {
22 if(w < 0) {
23 w = texture_.width();
24 }
25
26 if(h < 0) {
27 h = texture_.height();
28 }
29
30 set_dim(w,h);
31 }
32
33 image_widget::image_widget(graphics::texture tex, int w, int h)
34 : texture_(tex), rotate_(0.0)
35 {
36 if(w < 0) {
37 w = texture_.width();
38 }
39
40 if(h < 0) {
41 h = texture_.height();
42 }
43
44 set_dim(w,h);
45 }
46
47 void image_widget::handle_draw() const
48 {
49 if(area_.w() == 0) {
50 graphics::blit_texture(texture_, x(), y(), width(), height(), rotate_);
51 } else {
52 graphics::blit_texture(texture_, x(), y(), width(), height(), rotate_,
53 GLfloat(area_.x())/texture_.width(),
54 GLfloat(area_.y())/texture_.height(),
55 GLfloat(area_.x2())/texture_.width(),
56 GLfloat(area_.y2())/texture_.height());
57 }
58 }
59
60 gui_section_widget::gui_section_widget(const std::string& id, int w, int h)
61 : section_(gui_section::get(id))
62 {
63 if(section_ && w == -1) {
64 set_dim(section_->width()/2, section_->height()/2);
65 } else {
66 set_dim(w,h);
67 }
68 }
69
70 void gui_section_widget::set_gui_section(const std::string& id)
71 {
72 section_ = gui_section::get(id);
73 }
74
75 void gui_section_widget::handle_draw() const
76 {
77 if(section_) {
78 //draw without stretching, in the middle of the widget.
79 const int w = section_->width()/2;
80 const int h = section_->height()/2;
81
82 section_->blit(x() + (width() - w)/2, y() + (height() - h)/2, w, h);
83 }
84 }
85
86 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef IMAGE_WIDGET_HPP_INCLUDED
13 #define IMAGE_WIDGET_HPP_INCLUDED
14
15 #include <string>
16
17 #include "geometry.hpp"
18 #include "gui_section.hpp"
19 #include "image_widget_fwd.hpp"
20 #include "texture.hpp"
21 #include "widget.hpp"
22
23 namespace gui {
24
25 class image_widget : public widget
26 {
27 public:
28 explicit image_widget(const std::string& fname, int w=-1, int h=-1);
29 explicit image_widget(graphics::texture tex, int w=-1, int h=-1);
30
31 void set_rotation(GLfloat rotate) { rotate_ = rotate; }
32 void set_area(const rect& area) { area_ = area; }
33
34 private:
35 void handle_draw() const;
36 graphics::texture texture_;
37 GLfloat rotate_;
38 rect area_;
39 };
40
41 class gui_section_widget : public widget
42 {
43 public:
44 explicit gui_section_widget(const std::string& id, int w=-1, int h=-1);
45
46 //sets the GUI section. The dimensions of the widget will not change;
47 //you should set a GUI section that is the same size.
48 void set_gui_section(const std::string& id);
49
50 private:
51 void handle_draw() const;
52 const_gui_section_ptr section_;
53 };
54
55 }
56
57 #endif
0 #ifndef IMAGE_WIDGET_FWD_HPP_INCLUDED
1 #define IMAGE_WIDGET_FWD_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 namespace gui {
6
7 class image_widget;
8 typedef boost::shared_ptr<image_widget> image_widget_ptr;
9 typedef boost::shared_ptr<const image_widget> const_image_widget_ptr;
10
11 }
12
13 #endif
0 #include <boost/shared_ptr.hpp>
1 #include <iostream>
2
3 #include "input.hpp"
4 #include "scoped_resource.hpp"
5
6 #include "userevents.h"
7
8 namespace input {
9 pump::~pump() {
10 SDL_Event e;
11
12 e.type = SDL_USEREVENT;
13 e.user.code = ST_EVENT_NESTED_DEATH;
14 e.user.data1 = NULL;
15 e.user.data2 = NULL;
16 SDL_PushEvent(&e);
17 }
18
19 bool pump::process() {
20 SDL_Event event;
21 while(!killed_ && SDL_PollEvent(&event)) {
22 bool claimed = false;
23
24 switch(event.type) {
25 case SDL_QUIT:
26 killed_ = true;
27 claimed = true;
28 SDL_PushEvent(&event);
29 break;
30 case SDL_USEREVENT:
31 if(event.user.code == ST_EVENT_NESTED_DEATH) {
32 reset();
33 continue;
34 }
35 break;
36 default:
37 break;
38 }
39 claimed = process_event(event, claimed);
40 }
41
42 return !killed_;
43 }
44
45 namespace {
46 struct exit_event { void operator()(int* process_stack) const { --(*process_stack); } };
47 typedef util::scoped_resource<int*, exit_event> scoped_int_stack;
48 }
49
50 bool listener_container::process_event(const SDL_Event& event, bool claimed) {
51 {
52 scoped_int_stack stack_fixer(&(++process_event_stack_));
53 std::vector<listener*>::iterator raw_itor;
54 for(raw_itor = raw_listeners_.begin(); raw_itor != raw_listeners_.end(); ++raw_itor) {
55 claimed |= (*raw_itor)->process_event(event, claimed);
56 }
57 }
58
59 post_process_fixup();
60
61 return claimed;
62 }
63
64 void listener_container::reset() {
65 std::vector<listener*>::iterator raw_itor;
66 for(raw_itor = raw_listeners_.begin(); raw_itor != raw_listeners_.end(); ++raw_itor) {
67 (*raw_itor)->reset();
68 }
69 }
70
71 void listener_container::register_listener(listener_ptr p) {
72 listeners_.push_back(p);
73 register_listener(p.get());
74 }
75 void listener_container::deregister_listener(listener_ptr p) {
76 deregister_listener(p.get());
77 if(!can_change_listeners()) {
78 reference_holder_.push_back(p);
79 }
80 std::vector<listener_ptr>::iterator itor = std::find(listeners_.begin(), listeners_.end(), p);
81 if(itor != listeners_.end()) {
82 listeners_.erase(itor);
83 }
84 }
85 void listener_container::register_listener(listener *p) {
86 if(can_change_listeners()) {
87 raw_listeners_.push_back(p);
88 } else {
89 std::vector<listener*>::iterator itor = std::find(pending_removal_listeners_.begin(),
90 pending_removal_listeners_.end(), p);
91 if(itor != pending_removal_listeners_.end()) {
92 pending_removal_listeners_.erase(itor);
93 } else {
94 pending_addition_listeners_.push_back(p);
95 }
96 }
97 }
98 void listener_container::deregister_listener(listener *p) {
99 if(can_change_listeners()) {
100 std::vector<listener*>::iterator itor = std::find(raw_listeners_.begin(), raw_listeners_.end(), p);
101 if(itor != raw_listeners_.end()) {
102 raw_listeners_.erase(itor);
103 }
104 } else {
105 std::vector<listener*>::iterator itor = std::find(pending_addition_listeners_.begin(),
106 pending_addition_listeners_.end(), p);
107 if(itor != pending_addition_listeners_.end()) {
108 pending_addition_listeners_.erase(itor);
109 } else {
110 pending_removal_listeners_.push_back(p);
111 }
112 }
113 }
114
115 void listener_container::post_process_fixup() {
116 if(!can_change_listeners()) {
117 return;
118 }
119 if(!pending_removal_listeners_.empty()) {
120 std::vector<listener*>::iterator itor, found_itor;
121 for(itor = pending_removal_listeners_.begin();
122 itor != pending_removal_listeners_.end();
123 ++itor) {
124 found_itor = std::find(raw_listeners_.begin(), raw_listeners_.end(), *itor);
125 if(found_itor != raw_listeners_.end()) {
126 raw_listeners_.erase(found_itor);
127 }
128 }
129 pending_removal_listeners_.clear();
130 }
131 if(!pending_addition_listeners_.empty()) {
132 raw_listeners_.insert(raw_listeners_.end(),
133 pending_addition_listeners_.begin(),
134 pending_addition_listeners_.end());
135 pending_addition_listeners_.clear();
136 }
137 reference_holder_.clear();
138 }
139
140 Uint8 get_button_change_state(Uint8 button) {
141 return (1 << (button-1));
142 }
143
144 SDLMod get_mod_change_state(const SDLKey& key) {
145 SDLMod ret;
146 #if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 3
147 #define SDLK_LMETA SDLK_LGUI
148 #define SDLK_RMETA SDLK_RGUI
149 #endif
150 switch(key) {
151 case SDLK_LSHIFT:
152 ret = KMOD_LSHIFT;
153 break;
154 case SDLK_RSHIFT:
155 ret = KMOD_RSHIFT;
156 break;
157 case SDLK_LCTRL:
158 ret = KMOD_LCTRL;
159 break;
160 case SDLK_RCTRL:
161 ret = KMOD_RCTRL;
162 break;
163 case SDLK_LALT:
164 ret = KMOD_LALT;
165 break;
166 case SDLK_RALT:
167 ret = KMOD_RALT;
168 break;
169 case SDLK_LMETA:
170 ret = KMOD_LMETA;
171 break;
172 case SDLK_RMETA:
173 ret = KMOD_RMETA;
174 break;
175 case SDLK_NUMLOCK:
176 ret = KMOD_NUM;
177 break;
178 case SDLK_CAPSLOCK:
179 ret = KMOD_CAPS;
180 break;
181 case SDLK_MODE:
182 ret = KMOD_MODE;
183 break;
184 default:
185 ret = KMOD_NONE;
186 break;
187 }
188
189 return ret;
190 }
191
192 void key_listener::expand_keys(int logical_key, const SDL_keysym& sym,
193 const SDLMod& processed_mask, int depth) {
194 SDL_keysym tmp;
195 tmp = sym;
196
197 static const int NUM_CHUNKS = 4;
198 static const SDLMod chunks[NUM_CHUNKS] = {
199 (SDLMod)KMOD_SHIFT, (SDLMod)KMOD_ALT,
200 (SDLMod)KMOD_CTRL, (SDLMod)KMOD_META
201 };
202 static const SDLMod ALL_CHUNKS = (SDLMod)(KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_META);
203
204 static const int NUM_BITS = sizeof(SDLMod)*8;
205 int bit_idx[NUM_BITS];
206
207 int remaining = ALL_CHUNKS & sym.mod & ~processed_mask;
208 if(remaining == 0) {
209 bind_key(logical_key, sym, false);
210 return;
211 }
212
213 for(int i =0;i<NUM_CHUNKS;++i) {
214 if(sym.mod & chunks[i] && !(processed_mask & chunks[i])) {
215 SDLMod newmask = (SDLMod)(processed_mask | chunks[i]);
216 SDLMod newmod = (SDLMod)(sym.mod & ~chunks[i]);
217
218 int bit_count = 0;
219 for(int j=0;j<NUM_BITS;++j) {
220 if(chunks[i] & (1 << j)) {
221 bit_idx[bit_count++] = j;
222 }
223 }
224 int k_max = 1 << bit_count;
225 for(int k=1;k < k_max;++k) {
226 tmp.mod = newmod;
227 for(int j=0;j<bit_count;++j) {
228 int bit = 1 << j;
229 if(k & bit) {
230 tmp.mod = (SDLMod)(tmp.mod | (1 << bit_idx[j]));
231 }
232 }
233 expand_keys(logical_key, tmp, newmask, depth+1);
234 }
235 return;
236 }
237 }
238 }
239
240 void key_listener::bind_key(int logical_key, const SDL_keysym& sym,
241 bool collapse_doubles) {
242 if(collapse_doubles) {
243 expand_keys(logical_key, sym, KMOD_NONE,0);
244 return;
245 }
246
247 unbind_key(sym);
248
249 binding_map::iterator binding_itor = bindings_.find(sym.sym);
250 if(binding_itor == bindings_.end()) {
251 mod_map m;
252 bindings_[sym.sym] = m;
253 binding_itor = bindings_.find(sym.sym);
254 }
255 assert(binding_itor != bindings_.end());
256 binding_itor->second[static_cast<SDLMod>(sym.mod)] = logical_key;
257 }
258
259 void key_listener::bind_key(int logical_key, const SDLKey& k,
260 const SDLMod& m, bool collapse_doubles) {
261 SDL_keysym sym;
262 sym.sym = k;
263 sym.mod = m;
264 bind_key(logical_key, sym, collapse_doubles);
265 }
266
267 bool key_listener::unbind_key(const SDL_keysym& k) {
268 bool had_key = false;
269
270 binding_map::iterator binding_itor = bindings_.find(k.sym);
271 if(binding_itor != bindings_.end()) {
272 mod_map& mm = binding_itor->second;
273 mod_map::iterator mod_itor = mm.find(static_cast<SDLMod>(k.mod));
274 if(mod_itor != mm.end()) {
275 mm.erase(mod_itor);
276 had_key = true;
277 }
278 }
279 clean_mod_maps();
280
281 return had_key;
282 }
283
284 /* find every instance of logical_key in the nested maps
285 and erase it, erasing any mod_maps that become empty*/
286 bool key_listener::unbind_key(int logical_key) {
287 bool had_key = false;
288 bool erased;
289 do {
290 erased = false;
291 for(binding_map::iterator binding_itor = bindings_.begin();
292 binding_itor != bindings_.end();
293 ++binding_itor) {
294 mod_map& mm = binding_itor->second;
295 for(mod_map::iterator mod_itor = mm.begin();
296 mod_itor != mm.end();
297 ++mod_itor) {
298
299 if(mod_itor->second == logical_key) {
300 mm.erase(mod_itor);
301 had_key = true;
302 erased = true;
303 break;
304 }
305 }
306 if(erased) {
307 break;
308 }
309 }
310 } while(erased);
311
312 clean_mod_maps();
313
314 return had_key;
315 }
316
317 void key_listener::clean_mod_maps() {
318 bool erased;
319 do {
320 erased = false;
321 for(binding_map::iterator binding_itor = bindings_.begin();
322 binding_itor != bindings_.end();
323 ++binding_itor) {
324 if(binding_itor->second.empty()) {
325 bindings_.erase(binding_itor);
326 erased = true;
327 break;
328 }
329 }
330 } while(erased);
331 }
332
333 int key_listener::bound_key(const SDL_keysym& sym) const {
334 binding_map::const_iterator binding_itor;
335 binding_itor = bindings_.find(sym.sym);
336 if(binding_itor == bindings_.end()) {
337 return -1;
338 }
339 mod_map::const_iterator mod_itor;
340 const mod_map& mm = binding_itor->second;
341 mod_itor = mm.find(static_cast<SDLMod>(sym.mod));
342 if(mod_itor == mm.end()) {
343 return -1;
344 }
345 return mod_itor->second;
346 }
347
348 bool key_listener::check_keys(const SDL_keysym& sym, Uint8 type) {
349 binding_map::iterator binding_itor;
350 binding_itor = bindings_.find(sym.sym);
351
352 bool changed = false;
353
354 switch(type) {
355 case SDL_KEYDOWN:
356 if(binding_itor != bindings_.end()) {
357 mod_map::iterator mod_itor = binding_itor->second.find(static_cast<SDLMod>(sym.mod));
358 if(mod_itor != binding_itor->second.end()) {
359 do_keydown(mod_itor->second);
360 changed = true;
361 }
362 }
363 break;
364 case SDL_KEYUP:
365 if(binding_itor != bindings_.end()) {
366 for(mod_map::iterator mod_itor = binding_itor->second.begin();
367 mod_itor != binding_itor->second.end();
368 ++mod_itor) {
369 do_keyup(mod_itor->second);
370 }
371 changed = true;
372 }
373 break;
374 default:
375 break;
376 }
377
378 return changed;
379 }
380
381 bool key_listener::process_event(const SDL_Event& event, bool claimed) {
382 switch(event.type) {
383 case SDL_KEYDOWN:
384 case SDL_KEYUP:
385 if(claimed) {
386 reset();
387 break;
388 }
389 if(check_keys(event.key.keysym, event.type)) {
390 claimed = true;
391 }
392 break;
393 default:
394 break;
395 }
396
397 return claimed;
398 }
399
400 void key_down_listener::bind_key(int logical_key, const SDL_keysym& sym,
401 bool collapse_doubles) {
402 key_listener::bind_key(logical_key, sym, collapse_doubles);
403 if(logical_key >= state_.size()) {
404 state_.resize(logical_key+1);
405 }
406 state_[logical_key] = false;
407 }
408
409 bool key_down_listener::unbind_key(const SDL_keysym& k) {
410 int key = bound_key(k);
411 if(key >= 0 && key < state_.size()) {
412 state_[key] = false;
413 }
414 return key_listener::unbind_key(k);
415 }
416
417 bool key_down_listener::unbind_key(int logical_key) {
418 if(logical_key < state_.size()) {
419 state_[logical_key] = false;
420 }
421 return key_listener::unbind_key(logical_key);
422 }
423
424 void key_down_listener::do_keydown(int key) {
425 state_[key] = true;
426 }
427 void key_down_listener::do_keyup(int key) {
428 state_[key] = false;
429 }
430
431 void key_down_listener::reset() {
432 std::vector<bool>::iterator itor;
433 for(itor = state_.begin(); itor != state_.end(); ++itor) {
434 *itor = false;
435 }
436 }
437
438 #ifndef NO_EDITOR
439 bool mouse_drag_listener::process_event(const SDL_Event& event, bool claimed) {
440 Sint32 pos[2];
441 Sint16 rel[2];
442 Uint8 state;
443 Uint8 button_change_state;
444 SDLMod mod;
445 SDLMod mod_change_state;
446
447 bool was_capturing = is_capturing_;
448
449 if(claimed) {
450 is_capturing_ = false;
451 return claimed;
452 }
453
454 switch(event.type) {
455 case SDL_MOUSEMOTION:
456 pos[0] = event.motion.x;
457 pos[1] = event.motion.y;
458 rel[0] = event.motion.xrel;
459 rel[1] = event.motion.yrel;
460 state = event.motion.state;
461 mod = SDL_GetModState();
462 button_change_state = 0;
463 mod_change_state = KMOD_NONE;
464 break;
465 case SDL_MOUSEBUTTONDOWN:
466 case SDL_MOUSEBUTTONUP:
467 pos[0] = event.button.x;
468 pos[1] = event.button.y;
469 rel[0] = 0;
470 rel[1] = 0;
471 state = SDL_GetMouseState(NULL,NULL);
472 mod = SDL_GetModState();
473 button_change_state =
474 get_button_change_state(event.button.button)
475 & target_state_mask();
476 mod_change_state = KMOD_NONE;
477 break;
478 case SDL_KEYDOWN:
479 case SDL_KEYUP:
480 state = SDL_GetMouseState(&(pos[0]), &(pos[1]));
481 rel[0] = 0;
482 rel[1] = 0;
483 mod = event.key.keysym.mod;
484 button_change_state = 0;
485 mod_change_state = (SDLMod)
486 (get_mod_change_state(event.key.keysym.sym)
487 & target_mod_mask());
488 break;
489 default:
490 return claimed;
491 }
492
493 if(!matches_target_state(state)) {
494 is_capturing_ = false;
495 return claimed;
496 } else if(!matches_target_mod(mod)) {
497 is_capturing_ = false;
498 return claimed;
499 }
500
501 if(!is_capturing_) {
502 /* to start a gesture, we must be in the target area (if one is defined)
503 AND this must be a mouse state change event if buttons are involved
504 OR this must be a key state change event if keys are involved
505 */
506 if(target_state() != 0) {
507 /* if we have a target state, this must have been an event
508 which indicated an unmasked button was pressed */
509 if(button_change_state == 0) {
510 return claimed;
511 }
512 } else if(target_mod() != 0) {
513 /* if we don't have a target state, but do have target modifiers,
514 this must have been an event that indicated an unmasked
515 modifier was pressed */
516 if(mod_change_state == 0) {
517 return claimed;
518 }
519 }
520 if(target_area_enabled() && !in_target_area(pos[0], pos[1])) {
521 return claimed;
522 }
523 }
524
525 is_capturing_ = true;
526 claimed = true;
527
528 pos_[0] = pos[0];
529 pos_[1] = pos[1];
530 state_ = state;
531 mod_ = mod;
532
533 rel_[0] = rel[0];
534 rel_[1] = rel[1];
535
536 if(was_capturing) {
537 total_rel_[0] += rel[0];
538 total_rel_[1] += rel[1];
539 } else {
540 total_rel_[0] = rel[0];
541 total_rel_[1] = rel[1];
542 start_pos_[0] = pos[0];
543 start_pos_[1] = pos[1];
544 }
545
546 do_drag();
547
548 return claimed;
549 }
550
551 bool mouse_click_listener_base::process_event(const SDL_Event& event, bool claimed) {
552 if(claimed) {
553 click_count_ = 0;
554 clicked_ = false;
555 return claimed;
556 }
557
558 switch(event.type) {
559 case SDL_MOUSEMOTION:
560 pos_[0] = event.motion.x;
561 pos_[1] = event.motion.y;
562 state_ = event.motion.state;
563 button_state_ = 0;
564 mod_ = SDL_GetModState();
565 break;
566 case SDL_MOUSEBUTTONDOWN:
567 case SDL_MOUSEBUTTONUP:
568 pos_[0] = event.button.x;
569 pos_[1] = event.button.y;
570 state_ = SDL_GetMouseState(NULL,NULL);
571 mod_ = SDL_GetModState();
572 button_state_ = get_button_change_state(event.button.button);
573 if(event.type == SDL_MOUSEBUTTONDOWN) {
574 state_ |= button_state_;
575 }
576 break;
577 default:
578 return claimed;
579 }
580
581 /* this handles exiting from a click */
582 if(!matches_target_state(state_)) {
583 if(clicked_) {
584 click_time_ = SDL_GetTicks();
585 clicked_ = false;
586 do_click(pos_[0], pos_[1], click_count_, state_, button_state_, mod_);
587 }
588 return claimed;
589 }
590
591 switch(event.type) {
592 case SDL_MOUSEMOTION:
593 /* next continuing in a click - claim motion events
594 whilst clicking */
595 if(clicked_) {
596 claimed = true;
597 }
598 break;
599 case SDL_MOUSEBUTTONDOWN:
600 /* finally beginning a click */
601 if(!clicked_ && matches_target_mod(mod_)) {
602 /* to start a click, we must be in the target area */
603 if(target_area_enabled() && !in_target_area(pos_[0], pos_[1])) {
604 break;
605 }
606 clicked_ = true;
607 Uint32 curtime = SDL_GetTicks();
608 if(click_count_ > 0 &&
609 curtime - click_time_ >= click_timeout_) {
610 click_count_ = 0;
611 }
612 click_count_++;
613 claimed = true;
614 }
615 break;
616 default:
617 break;
618 }
619
620 return claimed;
621 }
622 #endif // NO_EDITOR
623
624 }
625
0 #ifndef INPUT_HPP_INCLUDED
1 #define INPUT_HPP_INCLUDED
2
3 #include <SDL.h>
4 #include <boost/shared_ptr.hpp>
5 #include <vector>
6 #include <map>
7
8 namespace input {
9
10 class listener {
11 public:
12 virtual ~listener() {}
13 /** returns true if the event is now claimed */
14 virtual bool process_event(const SDL_Event &e, bool is_claimed) =0;
15 virtual void reset() {}
16 };
17
18 typedef boost::shared_ptr<listener> listener_ptr;
19
20 class listener_container: public virtual listener {
21 public:
22 listener_container() : process_event_stack_(0) {}
23 bool process_event(const SDL_Event &e, bool is_claimed);
24 void reset();
25 void register_listener(listener_ptr p);
26 void register_listener(listener *p);
27 void deregister_listener(listener_ptr p);
28 void deregister_listener(listener *p);
29 private:
30 bool can_change_listeners() { return process_event_stack_ == 0; }
31 void post_process_fixup();
32 int process_event_stack_;
33 std::vector<listener_ptr> listeners_;
34 std::vector<listener*> raw_listeners_;
35 std::vector<listener*> pending_addition_listeners_, pending_removal_listeners_;
36 std::vector<listener_ptr> reference_holder_;
37 };
38
39 typedef boost::shared_ptr<listener_container> listener_container_ptr;
40
41 class delegate_listener: public virtual listener {
42 public:
43 delegate_listener(listener *delegate) : delegate_(delegate) {}
44 bool process_event(const SDL_Event &e, bool claimed) {
45 return delegate_->process_event(e, claimed);
46 }
47 void reset() { delegate_->reset(); }
48 private:
49 listener* delegate_;
50 };
51
52 class pump: private listener_container {
53 public:
54 pump() : killed_(false) {}
55 virtual ~pump();
56
57 using listener_container::reset;
58 using listener_container::register_listener;
59 using listener_container::deregister_listener;
60
61 bool process();
62 void resurrect() {
63 killed_ = false;
64 }
65 bool killed() const {
66 return killed_;
67 }
68 private:
69 bool killed_;
70 };
71
72 SDLMod get_mod_change_state(const SDLKey& k);
73 Uint8 get_button_change_state(Uint8 button);
74
75 class key_listener: public listener {
76 public:
77 virtual void bind_key(int logical_key, const SDL_keysym& sym,
78 bool collapse_doubles = true);
79 virtual void bind_key(int logical_key, const SDLKey& k, const SDLMod& m,
80 bool collapse_doubles = true);
81 virtual bool unbind_key(int logical_key);
82 virtual bool unbind_key(const SDL_keysym& sym);
83 int bound_key(const SDL_keysym& sym) const;
84 bool process_event(const SDL_Event& event, bool claimed);
85 protected:
86 bool check_keys(const SDL_keysym& sym, Uint8 event_type);
87 virtual void do_keydown(int key) =0;
88 virtual void do_keyup(int key) =0;
89 private:
90 void clean_mod_maps();
91 void expand_keys(int logical_key, const SDL_keysym& sym,
92 const SDLMod &mask, int depth);
93
94 typedef std::map<SDLMod,int> mod_map;
95 typedef std::map<SDLKey,mod_map> binding_map;
96
97 binding_map bindings_;
98 };
99
100 class key_down_listener: public key_listener {
101 public:
102 bool key(int logical_key) const { return state_[logical_key]; }
103 void reset();
104 void bind_key(int logical_key, const SDL_keysym& sym,
105 bool collapse_doubles = true);
106 void bind_key(int logical_key, const SDLKey& k, const SDLMod& m,
107 bool collapse_doubles = true) {
108 key_listener::bind_key(logical_key, k, m, collapse_doubles);
109 }
110 bool unbind_key(int logical_key);
111 bool unbind_key(const SDL_keysym& sym);
112 protected:
113 void do_keydown(int key);
114 void do_keyup(int key);
115 private:
116 std::vector<bool> state_;
117 };
118
119 /* T provides T->keydown(key), T->keyup(key) */
120 template <class T> class key_press_listener: public key_listener {
121 public:
122 key_press_listener(T owner) : owner_(owner) {}
123 void reset() {}
124 protected:
125 void do_keydown(int key) {
126 owner_->keydown(key);
127 }
128 void do_keyup(int key) {
129 owner_->keyup(key);
130 }
131 private:
132 T owner_;
133 };
134
135 /* convenient abstract base */
136 class mouse_listener: public listener {
137 public:
138 static const SDLMod MOD_MASK_NONE = (SDLMod)-1;
139 static const SDLMod MOD_MASK_ALL = (SDLMod)0;
140 static const Uint8 STATE_MASK_NONE = 0xFF;
141 static const Uint8 STATE_MASK_ALL = 0;
142
143 mouse_listener() {
144 target_area_.x = 0;
145 target_area_.y = 0;
146 target_area_.w = 0;
147 target_area_.h = 0;
148 target_state_ = 0;
149 target_state_mask_ = STATE_MASK_ALL;
150 target_mod_ = KMOD_NONE;
151 target_mod_mask_ = MOD_MASK_ALL;
152 }
153
154 void set_target_area(const SDL_Rect& rect) {
155 target_area_ = rect;
156 }
157 SDL_Rect target_area() const {
158 return target_area_;
159 }
160 void disable_target_area() {
161 target_area_.w = 0;
162 target_area_.h = 0;
163 }
164 bool target_area_enabled() const {
165 return target_area_.w != 0 && target_area_.h != 0;
166 }
167
168 void set_target_mod(SDLMod mod, SDLMod mask) {
169 target_mod_ = mod;
170 target_mod_mask_ = (SDLMod)(mask | mod);
171 }
172 SDLMod target_mod() const {
173 return target_mod_;
174 }
175 SDLMod target_mod_mask() const {
176 return target_mod_mask_;
177 }
178
179 void set_target_state(Uint8 state, Uint8 mask) {
180 target_state_ = state;
181 target_state_mask_ = mask | state;
182 }
183 Uint8 target_state() const {
184 return target_state_;
185 }
186 Uint8 target_state_mask() const {
187 return target_state_mask_;
188 }
189 protected:
190 bool in_target_area(Sint32 x, Sint32 y) const {
191 return x >= target_area_.x &&
192 x < target_area_.x + target_area_.w &&
193 y >= target_area_.y &&
194 y < target_area_.y + target_area_.h;
195 }
196
197 bool matches_target_state(const Uint8& state) const {
198 return (state & target_state_mask_ )== target_state_;
199 }
200 bool matches_target_mod(const SDLMod& mod) const {
201 return (mod & target_mod_mask_) == target_mod_;
202 }
203 private:
204 SDL_Rect target_area_; //= {0,0,0,0};
205 Uint8 target_state_;
206 Uint8 target_state_mask_;// = STATE_MASK_ALL;
207 SDLMod target_mod_;// = 0;
208 SDLMod target_mod_mask_;// = MOD_MASK_ALL;
209 };
210
211 class mouse_drag_listener: public mouse_listener {
212 public:
213 mouse_drag_listener() {
214 is_capturing_ = false;
215 pos_[0] = 0;
216 pos_[1] = 0;
217 start_pos_[0] = 0;
218 start_pos_[1] = 0;
219 rel_[0] = 0;
220 rel_[1] = 0;
221 total_rel_[0] = 0;
222 total_rel_[1] = 0;
223 mod_ = KMOD_NONE;
224 state_ = 0;
225 }
226
227 bool process_event(const SDL_Event& event, bool claimed);
228 void reset() {
229 is_capturing_ = false;
230 }
231
232 /* whether the state variables refer to an active drag,
233 and are being updated, or if the drag is complete -
234 in which case they are the last values before the drag
235 completed */
236 bool active() const {
237 return is_capturing_;
238 }
239
240 /* pos is the last observed mouse drag position */
241 Sint32 pos_x() const {
242 return pos_[0];
243 }
244 Sint32 pos_y() const {
245 return pos_[1];
246 }
247 /* the position the drag started at */
248 Sint32 start_pos_x() const {
249 return start_pos_[0];
250 }
251 Sint32 start_pos_y() const {
252 return start_pos_[1];
253 }
254
255 /* rel is the accumulated motion of the mouse
256 over the duration of the drag; not guaranteed by
257 SDL to be equal to pos-start_pos */
258 Sint16 rel_x() const {
259 return rel_[0];
260 }
261 Sint16 rel_y() const {
262 return rel_[1];
263 }
264 Sint16 total_rel_x() const {
265 return total_rel_[0];
266 }
267 Sint16 total_rel_y() const {
268 return total_rel_[1];
269 }
270 /* the last modifiers observed */
271 SDLMod mod() const {
272 return mod_;
273 }
274 /* the last state observed */
275 Uint8 state() const {
276 return state_;
277 }
278
279 void reset_start_pos() {
280 start_pos_[0] = pos_[0];
281 start_pos_[1] = pos_[1];
282 }
283 protected:
284 virtual void do_drag() {}
285 private:
286 Sint32 pos_[2];
287 Sint32 start_pos_[2];
288 Sint16 rel_[2];
289 Sint16 total_rel_[2];
290 SDLMod mod_;
291 Uint8 state_;
292 bool is_capturing_;
293 };
294
295 template <class T> class active_mouse_drag_listener: public mouse_drag_listener {
296 public:
297 active_mouse_drag_listener(T owner) : owner_(owner) {}
298 void do_drag() {
299 owner_->drag();
300 }
301 private:
302 T owner_;
303 };
304
305 class mouse_click_listener_base: public mouse_listener {
306 public:
307 mouse_click_listener_base() {
308 click_time_ = 0;
309 click_timeout_ = 0;
310 click_count_ = 0;
311 clicked_ = false;
312 state_ = 0;
313 button_state_ = 0;
314 mod_ = KMOD_NONE;
315 pos_[0] = 0;
316 pos_[1] = 0;
317 }
318 bool process_event(const SDL_Event& event, bool claimed);
319 void reset() { clicked_ = false; }
320 void set_click_timeout(Uint32 amount) { click_timeout_ = amount; }
321 Uint32 click_timeout() const { return click_timeout_; }
322 virtual void do_click(Sint32 x, Sint32 y, int count, Uint8 state, Uint8 button_state, SDLMod mod)=0;
323 private:
324 bool clicked_;
325 int click_count_;
326 Uint32 click_time_;
327 Uint32 click_timeout_;
328 Uint8 state_, button_state_;
329 SDLMod mod_;
330 Sint32 pos_[2];
331 };
332
333 /* T provides T->click(x,y, clicks, buttons, buttons changed, key modifiers) */
334 template <class T> class mouse_click_listener: public mouse_click_listener_base {
335 public:
336 mouse_click_listener(T owner) : owner_(owner) {}
337
338 void do_click(Sint32 x, Sint32 y, int count,
339 Uint8 state, Uint8 bstate, SDLMod mod) {
340 owner_->click(x, y, count, state, bstate, mod);
341 }
342 private:
343 T owner_;
344 };
345 }
346
347 #endif
0 #include <algorithm>
1 #include <iostream>
2
3 #include "SDL.h"
4
5 #include "draw_scene.hpp"
6 #include "entity.hpp"
7 #include "gui_section.hpp"
8 #include "inventory.hpp"
9 #include "powerup.hpp"
10 #include "raster.hpp"
11 #include "texture.hpp"
12
13 void show_inventory(const level& lvl, entity& c)
14 {
15 const_gui_section_ptr selector = gui_section::get("powerup_selector");
16 if(!selector) {
17 std::cerr << "ERROR: selector frame not found\n";
18 return;
19 }
20
21 const_powerup_ptr empty_powerup = powerup::get("empty");
22 const int NumPowerupSlots = 6;
23 int selected_powerup = 0;
24 for(;;) {
25
26 std::vector<const_powerup_ptr> powerups = c.powerups();
27 powerups.erase(std::unique(powerups.begin(), powerups.end()), powerups.end());
28 std::reverse(powerups.begin(), powerups.end());
29 while(empty_powerup && powerups.size() < NumPowerupSlots) {
30 powerups.push_back(empty_powerup);
31 }
32
33 SDL_Event event;
34 while(SDL_PollEvent(&event)) {
35 switch(event.type) {
36 case SDL_QUIT:
37 return;
38 case SDL_KEYDOWN:
39 if(event.key.keysym.sym == SDLK_ESCAPE || event.key.keysym.sym == SDLK_SPACE) {
40 return;
41 }
42
43 switch(event.key.keysym.sym) {
44 case SDLK_UP:
45 selected_powerup = (selected_powerup + 9)%NumPowerupSlots;
46 break;
47 case SDLK_DOWN:
48 selected_powerup = (selected_powerup + 3)%NumPowerupSlots;
49 break;
50 case SDLK_LEFT:
51 selected_powerup--;
52 if(selected_powerup == 2) {
53 selected_powerup = 5;
54 } else if(selected_powerup == -1) {
55 selected_powerup = 2;
56 }
57 break;
58 case SDLK_RIGHT:
59 selected_powerup++;
60 if(selected_powerup == 3) {
61 selected_powerup = 0;
62 } else if(selected_powerup == 6) {
63 selected_powerup = 3;
64 }
65 break;
66 case SDLK_a: {
67 //move the selected powerup to the front.
68 assert(selected_powerup >= 0 && selected_powerup < powerups.size());
69 const_powerup_ptr powerup = powerups[selected_powerup];
70 if(powerup != empty_powerup) {
71 const int count = c.remove_powerup(powerup);
72 for(int n = 0; n != count; ++n) {
73 c.get_powerup(powerup);
74 }
75
76 selected_powerup = 0;
77 }
78 break;
79 }
80 }
81 break;
82 }
83 }
84
85 graphics::prepare_raster();
86
87 graphics::texture inventory(graphics::texture::get("gui/inventory.png"));
88
89 const int InventoryWidth = 400;
90 const int InventoryHeight = 240;
91 graphics::blit_texture(inventory, 0, 0,
92 InventoryWidth*2, InventoryHeight*2, 0, 0, 0,
93 GLfloat(InventoryWidth)/inventory.width(),
94 GLfloat(InventoryHeight)/inventory.height());
95
96 for(int n = 0; n != powerups.size(); ++n) {
97 const int xpos = 208 + (n%3)*46;
98 const int ypos = 36 + (n/3)*66;
99
100 if(selected_powerup == n) {
101 selector->blit(xpos - 2, ypos - 1);
102 }
103
104 powerups[n]->icon().draw(xpos, ypos);
105
106 //draw the number of items we have. This is probably temporary
107 //code that'll be superceded later when we have a nice text
108 //drawing system.
109 const int count = std::count(c.powerups().begin(), c.powerups().end(), powerups[n]);
110 if(!count) {
111 continue;
112 }
113
114 const int width = 9;
115 const int height = 9;
116 const int x1 = 173 + width*count;
117 const int x2 = x1 + width;
118 const int y1 = 313;
119 const int y2 = y1 + height;
120 graphics::blit_texture(inventory, xpos + 18, ypos + 38, width*2, height*2, 0.0,
121 GLfloat(x1)/inventory.width(),
122 GLfloat(y1)/inventory.height(),
123 GLfloat(x2)/inventory.width(),
124 GLfloat(y2)/inventory.height());
125 }
126
127 std::vector<const_powerup_ptr> abilities = c.abilities();
128 for(int n = 0; n != abilities.size(); ++n) {
129 const int xpos = 200 + n*46;
130 const int ypos = 183;
131 abilities[n]->icon().draw(xpos, ypos);
132 }
133
134 SDL_GL_SwapBuffers();
135 SDL_Delay(20);
136 }
137 }
0 #ifndef INVENTORY_HPP_INCLUDED
1 #define INVENTORY_HPP_INCLUDED
2
3 class entity;
4 class level;
5
6 void show_inventory(const level& lvl, entity& c);
7
8 #endif
0 #include "iphone_controls.hpp"
1
2 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
3
4 #include <SDL.h>
5
6 #include "geometry.hpp"
7 #include "preferences.hpp"
8 #include "raster.hpp"
9 #include "texture.hpp"
10
11 namespace
12 {
13 rect left_arrow, right_arrow, down_arrow, up_arrow, a_button, c_button, interact_button;
14
15 const int underwater_circle_rad = 120;
16 int underwater_circle_x = 150;
17 int underwater_circle_y = 150; //changed to be relative to the bottom by setup_rects()
18
19 bool is_underwater = false;
20 bool can_interact = false;
21
22 //This is to keep track of whether the rects above have been modified
23 //by modify_rects() yet, to make them work on different resolutions
24 bool done_setup_rects = false;
25 static void setup_rects ()
26 {
27 if (done_setup_rects)
28 {
29 return;
30 }
31 done_setup_rects = true;
32
33 int vw = preferences::virtual_screen_width();
34 int vh = preferences::virtual_screen_height();
35
36 left_arrow = rect(0, vh - 100, 10 + 55*2, 100);
37 right_arrow = rect(244, vh - 100, 10 + 55*2, 100);
38 down_arrow = rect(159, vh - 66, 34*2, 66);
39 up_arrow = rect(159, vh - 184, 34*2, 55*2);
40 a_button = rect(vw - 204, vh - 120, 50*2*2, 60*2);
41 // b_button = rect(vw - 102, vh - 300, 50*2, 60*2);
42 c_button = rect(vw - 104, vh - 240, 50*2, 60*2);
43
44 interact_button = rect(vw - 320, vh - 120, 50*2, 60*2);
45
46 underwater_circle_y = vh-underwater_circle_y;
47 }
48 }
49
50 void translate_mouse_coords (int *x, int *y)
51 {
52 if(preferences::screen_rotated()) {
53 *x = preferences::actual_screen_width() - *x;
54 std::swap(*x, *y);
55 }
56
57 if(preferences::virtual_screen_width() >
58 (preferences::screen_rotated() ? preferences::actual_screen_height() : preferences::actual_screen_width())) {
59 *x *= 2;
60 *y *= 2;
61 }
62 }
63
64 void iphone_controls::set_underwater(bool value)
65 {
66 is_underwater = value;
67 }
68
69 void iphone_controls::set_can_interact(bool value)
70 {
71 can_interact = value;
72 }
73
74 bool iphone_controls::water_dir(float* xvalue, float* yvalue)
75 {
76 setup_rects();
77 for(int i = 0; i < SDL_GetNumMice(); i++) {
78 int x, y;
79 SDL_SelectMouse(i);
80 Uint8 button_state = SDL_GetMouseState(&x, &y);
81 if (button_state & SDL_BUTTON(SDL_BUTTON_LEFT)) {
82 translate_mouse_coords(&x, &y);
83
84 const int dx = x - underwater_circle_x;
85 const int dy = y - underwater_circle_y;
86
87 const int distance = sqrt(dx*dx + dy*dy);
88
89 if(distance > 0 && distance < underwater_circle_rad) {
90 *xvalue = float(dx)/float(distance);
91 *yvalue = float(dy)/float(distance);
92 return true;
93 }
94 }
95 }
96
97 return false;
98 }
99
100 void iphone_controls::draw()
101 {
102 if(!is_underwater) {
103 return;
104 }
105
106 glColor4ub(128, 128, 128, 128);
107 graphics::draw_circle(underwater_circle_x, underwater_circle_y, underwater_circle_rad);
108
109 GLfloat x, y;
110 if(water_dir(&x, &y)) {
111 GLfloat varray[] = {
112 underwater_circle_x, underwater_circle_y,
113 underwater_circle_x + x*underwater_circle_rad, underwater_circle_y + y*underwater_circle_rad
114 };
115
116 glDisable(GL_TEXTURE_2D);
117 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
118 glColor4ub(255, 0, 0, 255);
119 glVertexPointer(2, GL_FLOAT, 0, varray);
120 glDrawArrays(GL_LINES, 0, (sizeof(varray)/sizeof(*varray))/2);
121 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
122 glEnable(GL_TEXTURE_2D);
123 }
124
125 glColor4ub(255, 255, 255, 255);
126 }
127
128 bool iphone_controls::hittest_button(const rect& area)
129 {
130 setup_rects();
131 static graphics::texture tex(graphics::texture::get("gui/iphone_controls.png"));
132 for(int i = 0; i < SDL_GetNumMice(); i++) {
133 int x, y;
134 SDL_SelectMouse(i);
135 Uint8 button_state = SDL_GetMouseState(&x, &y);
136
137 if(button_state & SDL_BUTTON(SDL_BUTTON_LEFT)) {
138 //rotate the coordinates
139 translate_mouse_coords(&x, &y);
140
141 const point mouse_pos(x, y);
142 if(point_in_rect(mouse_pos, area)) {
143 return true;
144 }
145 }
146 }
147 return false;
148 }
149
150 bool iphone_controls::up()
151 {
152 if(is_underwater) {
153 return false;
154 }
155
156 if(can_interact && hittest_button(interact_button)) {
157 return true;
158 }
159
160 return hittest_button(up_arrow);
161 }
162
163 bool iphone_controls::down()
164 {
165 if(is_underwater) {
166 return false;
167 }
168
169 return hittest_button(down_arrow);
170 }
171
172 bool iphone_controls::left()
173 {
174 if(is_underwater) {
175 return false;
176 }
177
178 return hittest_button(left_arrow);
179 }
180
181 bool iphone_controls::right()
182 {
183 if(is_underwater) {
184 return false;
185 }
186
187 return hittest_button(right_arrow);
188 }
189
190 bool iphone_controls::attack()
191 {
192 return false; //hittest_button(b_button);
193 }
194
195 bool iphone_controls::jump()
196 {
197 if(is_underwater) {
198 return false;
199 }
200
201 return hittest_button(a_button);
202 }
203
204 bool iphone_controls::tongue()
205 {
206 return hittest_button(c_button);
207 }
208
209 #else // dummy functions for non-iPhone
210
211 void translate_mouse_coords (int *x, int *y) {}
212
213 void iphone_controls::draw() {}
214
215 void iphone_controls::set_underwater(bool value) {}
216 void iphone_controls::set_can_interact(bool value) {}
217
218 bool iphone_controls::water_dir(float* xvalue, float* yvalue) { return false; }
219
220 bool iphone_controls::up() {return false;}
221
222 bool iphone_controls::down() {return false;}
223
224 bool iphone_controls::left() {return false;}
225
226 bool iphone_controls::right() {return false;}
227
228 bool iphone_controls::attack() {return false;}
229
230 bool iphone_controls::jump() {return false;}
231
232 bool iphone_controls::tongue() {return false;}
233
234 #endif
0 #ifndef IPHONE_CONTROLS_HPP_INCLUDED
1 #define IPHONE_CONTROLS_HPP_INCLUDED
2
3 #include <SDL.h>
4
5 class rect;
6
7 void translate_mouse_coords (int *x, int *y);
8
9 class iphone_controls
10 {
11 public:
12 static bool up();
13 static bool down();
14 static bool left();
15 static bool right();
16 static bool attack();
17 static bool jump();
18 static bool tongue();
19 static bool water_dir(float* x, float* y);
20
21 static void set_underwater(bool value);
22 static void set_can_interact(bool value);
23
24 static void draw();
25
26 private:
27 static bool hittest_button (const rect& r);
28 };
29
30 #endif
0 #ifndef IPHONE_DEVICE_INFO_H_INCLUDED
1 #define IPHONE_DEVICE_INFO_H_INCLUDED
2
3 #ifdef __cplusplus
4 extern "C" {
5 #endif
6
7 void iphone_screen_res (int *w, int *h);
8
9 #ifdef __cplusplus
10 } //extern "C"
11 #endif
12
13 #endif
0 #import <UIKit/UIKit.h>
1
2 #import "iphone_device_info.h"
3
4 void iphone_screen_res (int *w, int *h)
5 {
6 *w = [UIScreen mainScreen].bounds.size.width;
7 *h = [UIScreen mainScreen].bounds.size.height;
8 }
0 #ifndef IPHONE_SOUND_H_INCLUDED
1 #define IPHONE_SOUND_H_INCLUDED
2
3 #ifdef __cplusplus
4 extern "C" {
5 #endif
6
7 void iphone_init_music (void (*callback)());
8 void iphone_fade_in_music (int duration);
9 void iphone_fade_out_music (int duration);
10 void iphone_play_music (const char *file, int loops);
11 void iphone_pause_music ();
12 void iphone_resume_music();
13 void iphone_kill_music ();
14
15 #ifdef __cplusplus
16 } //extern "C"
17 #endif
18
19 #endif
0 #import "iphone_sound.h"
1 #include "SDL.h"
2
3 #import <AVFoundation/AVFoundation.h>
4 #import <AudioToolbox/AudioToolbox.h>
5
6 AVAudioPlayer *song = nil;
7 const float fade_interval = 0.05;
8 void (*song_finished_callback)() = NULL;
9
10 @interface AudioDelegate : NSObject <AVAudioPlayerDelegate>
11 {
12 float amount;
13 BOOL fading;
14 }
15
16 @property (readwrite) float amount;
17 @property (readonly) BOOL fading;
18
19 - (void) fadeInMusic:(id)obj;
20 - (void) fadeOutMusic:(id)obj;
21
22 - (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
23 @end
24
25 AudioDelegate *delegate;
26
27 void iphone_init_music (void (*callback)())
28 {
29 delegate = [[AudioDelegate alloc] init];
30 song_finished_callback = callback;
31
32 AudioSessionInitialize (NULL, NULL, NULL, NULL);
33 AudioSessionSetActive (true);
34 UInt32 sessionCategory = kAudioSessionCategory_SoloAmbientSound;
35 AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory);
36 }
37
38 void iphone_fade_in_music (int duration)
39 {
40 if (song == nil || delegate == nil || delegate.fading) return;
41 song.volume = 0.0;
42 delegate.amount = 1.0/((duration/1000.0) * (1.0/fade_interval));
43 //NSLog(@"Starting fade in, volume=%f | amount=%f", song.volume, delegate.amount);
44 [delegate fadeInMusic:nil];
45 }
46
47 void iphone_fade_out_music (int duration)
48 {
49 if (song == nil || delegate == nil || delegate.fading) return;
50 delegate.amount = 1.0/((duration/1000.0) * (1.0/fade_interval));
51 [delegate fadeOutMusic:nil];
52 }
53
54 void iphone_play_music (const char *file, int loops)
55 {
56 int timer = SDL_GetTicks();
57 if (song)
58 {
59 [song stop];
60 [song release];
61 }
62 song = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String: file]] error:NULL];
63 NSLog(@"Initializing the new song took %i ms", SDL_GetTicks()-timer);
64 song.delegate = delegate;
65 song.numberOfLoops = loops;
66
67 timer = SDL_GetTicks();
68 [song play];
69 NSLog(@"Playing the new song (%s) took %i ms", file, SDL_GetTicks()-timer);
70 }
71
72 void iphone_pause_music ()
73 {
74 [song pause];
75 }
76
77 void iphone_resume_music ()
78 {
79 [song play];
80 }
81
82 void iphone_kill_music ()
83 {
84 if (song)
85 {
86 [song stop];
87 [song release];
88 song = nil;
89 }
90 [delegate release];
91 delegate = nil;
92 }
93
94 @implementation AudioDelegate
95
96 @synthesize amount;
97 @synthesize fading;
98
99 - (void) fadeInMusic:(id)obj
100 {
101 if (song.volume + amount >= 1.0)
102 {
103 //NSLog(@"Done fading in.");
104 fading = NO;
105 song.volume = 1.0; //fading is done, and the volume should be exactly 1.0
106 } else {
107 fading = YES;
108 song.volume += amount;
109 //NSLog(@"Fading in, volume=%f", song.volume);
110 [self performSelector:@selector(fadeInMusic:) withObject:nil afterDelay:fade_interval];
111 }
112 }
113
114 - (void) fadeOutMusic:(id)obj
115 {
116 if (song.volume - amount <= 0.0)
117 {
118 fading = NO;
119 song.volume = 0.0; //fading is done, and the volume should be exactly 0.0
120 [self audioPlayerDidFinishPlaying:song successfully:YES];
121 } else {
122 fading = YES;
123 song.volume -= amount;
124 [self performSelector:@selector(fadeOutMusic:) withObject:nil afterDelay:fade_interval];
125 }
126 }
127
128 - (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
129 {
130 [player stop];
131 [player release];
132 song = nil;
133 (*song_finished_callback)();
134 NSLog(@"Finished playing music");
135 }
136
137 - (void) audioPlayerEndInterruption:(AVAudioPlayer *)player
138 {
139 [player play];
140 }
141
142 @end
0 #include "SDL.h"
1
2 #include <iostream>
3 #include <vector>
4
5 #include "foreach.hpp"
6 #include "joystick.hpp"
7
8 namespace joystick {
9
10 namespace {
11 std::vector<SDL_Joystick*> joysticks;
12
13 const int threshold = 32700;
14 }
15
16 manager::manager() {
17 for(int n = 0; n != SDL_NumJoysticks(); ++n) {
18 SDL_Joystick* j = SDL_JoystickOpen(n);
19 if(j) {
20 joysticks.push_back(j);
21 }
22 }
23
24 std::cerr << "initialized " << joysticks.size() << " joysticks\n";
25 }
26
27 manager::~manager() {
28 foreach(SDL_Joystick* j, joysticks) {
29 SDL_JoystickClose(j);
30 }
31 joysticks.clear();
32 }
33
34 void update() {
35 SDL_JoystickUpdate();
36 }
37
38 bool up() {
39 foreach(SDL_Joystick* j, joysticks) {
40 if(SDL_JoystickGetAxis(j, 1) < -threshold ||
41 SDL_JoystickGetAxis(j, 3) < -threshold ||
42 SDL_JoystickGetAxis(j, 5) < -threshold) {
43 return true;
44 }
45
46 const int nhats = SDL_JoystickNumHats(j);
47 for(int n = 0; n != nhats; ++n) {
48 const Uint8 state = SDL_JoystickGetHat(j, n);
49 switch(state) {
50 case SDL_HAT_UP:
51 case SDL_HAT_RIGHTUP:
52 case SDL_HAT_LEFTUP:
53 return true;
54 }
55 }
56
57 }
58
59 return false;
60 }
61
62 bool down() {
63 foreach(SDL_Joystick* j, joysticks) {
64 if(SDL_JoystickGetAxis(j, 1) > threshold ||
65 SDL_JoystickGetAxis(j, 3) > threshold ||
66 SDL_JoystickGetAxis(j, 5) > threshold) {
67 return true;
68 }
69
70 const int nhats = SDL_JoystickNumHats(j);
71 for(int n = 0; n != nhats; ++n) {
72 const Uint8 state = SDL_JoystickGetHat(j, n);
73 switch(state) {
74 case SDL_HAT_DOWN:
75 case SDL_HAT_RIGHTDOWN:
76 case SDL_HAT_LEFTDOWN:
77 return true;
78 }
79 }
80
81 }
82
83 return false;
84 }
85
86 bool left() {
87 foreach(SDL_Joystick* j, joysticks) {
88 if(SDL_JoystickGetAxis(j, 0) < -threshold ||
89 SDL_JoystickGetAxis(j, 2) < -threshold ||
90 SDL_JoystickGetAxis(j, 4) < -threshold) {
91 return true;
92 }
93
94 const int nhats = SDL_JoystickNumHats(j);
95 for(int n = 0; n != nhats; ++n) {
96 const Uint8 state = SDL_JoystickGetHat(j, n);
97 switch(state) {
98 case SDL_HAT_LEFT:
99 case SDL_HAT_LEFTDOWN:
100 case SDL_HAT_LEFTUP:
101 return true;
102 }
103 }
104
105 }
106
107 return false;
108 }
109
110 bool right() {
111 foreach(SDL_Joystick* j, joysticks) {
112 if(SDL_JoystickGetAxis(j, 0) > threshold ||
113 SDL_JoystickGetAxis(j, 2) > threshold ||
114 SDL_JoystickGetAxis(j, 4) > threshold) {
115 return true;
116 }
117
118 const int nhats = SDL_JoystickNumHats(j);
119 for(int n = 0; n != nhats; ++n) {
120 const Uint8 state = SDL_JoystickGetHat(j, n);
121 switch(state) {
122 case SDL_HAT_RIGHT:
123 case SDL_HAT_RIGHTDOWN:
124 case SDL_HAT_RIGHTUP:
125 return true;
126 }
127 }
128 }
129
130 return false;
131 }
132
133 bool button(int n) {
134 foreach(SDL_Joystick* j, joysticks) {
135 if(SDL_JoystickGetButton(j, n)) {
136 return true;
137 }
138 }
139
140 return false;
141 }
142
143 int iphone_tilt() {
144 //#if TARGET_OS_IPHONE
145 // return SDL_JoystickGetAxis(joysticks.front(), 1);
146 //#else
147 return 0;
148 //#endif
149 }
150
151 std::vector<int> get_info() {
152 std::vector<int> res;
153 res.push_back(joysticks.size());
154 foreach(SDL_Joystick* j, joysticks) {
155 res.push_back(SDL_JoystickGetAxis(j, 0));
156 res.push_back(SDL_JoystickGetAxis(j, 1));
157 }
158
159 return res;
160 }
161
162 }
0 #ifndef JOYSTICK_HPP_INCLUDED
1 #define JOYSTICK_HPP_INCLUDED
2
3 #include <vector>
4
5 namespace joystick {
6
7 struct manager {
8 manager();
9 ~manager();
10 };
11
12 void update();
13
14 bool up();
15 bool down();
16 bool left();
17 bool right();
18
19 bool button(int n);
20
21 int iphone_tilt();
22
23 std::vector<int> get_info();
24 }
25
26 #endif
0 /* $Id: key.cpp 19552 2007-08-15 13:41:56Z mordante $ */
1 /*
2 Copyright (C) 2003 - 2007 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2
7 or at your option any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY.
10
11 See the COPYING file for more details.
12 */
13
14 #include <algorithm>
15
16 #include "key.hpp"
17 #include "string_utils.hpp"
18
19 CKey::CKey() : is_enabled(true), require_key_release(false), num_keys(300)
20 {
21 #if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 3
22 key_list = SDL_GetKeyboardState( &num_keys );
23 #else
24 key_list = SDL_GetKeyState( &num_keys );
25 #endif
26 }
27
28 int CKey::operator[]( int code ) const
29 {
30 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
31 return 0;
32 #endif
33 if(require_key_release) {
34 if(std::count(key_list, key_list + num_keys, 0) == num_keys) {
35 require_key_release = false;
36 }
37
38 return 0;
39 }
40
41 return (code == SDLK_ESCAPE || is_enabled) && int(key_list[code]);
42 }
43
44 void CKey::SetEnabled( bool enable )
45 {
46 is_enabled = enable;
47 }
48
49 void CKey::RequireRelease()
50 {
51 require_key_release = true;
52 }
53
54 void CKey::Write(std::string* output)
55 {
56 char buf[128];
57 for(int n = 0; n != num_keys; ++n) {
58 if(key_list[n]) {
59 sprintf(buf, "%d", n);
60 if(output->empty() == false) {
61 *output += ",";
62 }
63 *output += buf;
64 }
65 }
66 }
67
68 void CKey::Read(const std::string& input)
69 {
70 key_list = custom_key_list;
71 if(num_keys > sizeof(custom_key_list)) {
72 num_keys = sizeof(custom_key_list);
73 }
74
75 memset(custom_key_list, 0, sizeof(custom_key_list));
76
77 std::vector<std::string> keys = util::split(input);
78 for(int n = 0; n != keys.size(); ++n) {
79 const int nkey = atoi(keys[n].c_str());
80 if(nkey >= 0 && nkey < num_keys) {
81 key_list[nkey] = 1;
82 }
83 }
84 }
0 /* $Id: key.hpp 19552 2007-08-15 13:41:56Z mordante $ */
1 /*
2 Copyright (C) 2003 - 2007 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2
7 or at your option any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY.
10
11 See the COPYING file for more details.
12 */
13
14 #ifndef KEY_HPP_INCLUDED
15 #define KEY_HPP_INCLUDED
16
17 #include "SDL.h"
18
19 #include <string>
20
21 //object which keeps track of all the keys on the keyboard, and
22 //whether any key is pressed or not can be found by using its
23 //operator[]. Note though that it is generally better to use
24 //key events to see when keys are pressed rather than poll using
25 //this object.
26 class CKey {
27 public:
28 CKey();
29
30 int operator[](int) const;
31 void SetEnabled(bool enable);
32 void RequireRelease();
33
34 void Write(std::string* output);
35 void Read(const std::string& input);
36 private:
37 Uint8 *key_list;
38 Uint8 custom_key_list[512];
39 bool is_enabled;
40 mutable bool require_key_release;
41 int num_keys;
42 };
43
44 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include <iostream>
13
14 #include "font.hpp"
15 #include "label.hpp"
16 #include "raster.hpp"
17 #include "translate.hpp"
18
19 namespace gui {
20
21 label::label(const std::string& text, const SDL_Color& color, int size)
22 : text_(i18n::translate(text)), color_(color), size_(size),
23 fixed_width_(false)
24 {
25 recalculate_texture();
26 }
27
28 void label::set_color(const SDL_Color& color)
29 {
30 color_ = color;
31 recalculate_texture();
32 }
33
34 void label::set_font_size(int size)
35 {
36 size_ = size;
37 recalculate_texture();
38 }
39
40 void label::set_text(const std::string& text)
41 {
42 text_ = i18n::translate(text);
43 reformat_text();
44 recalculate_texture();
45 }
46
47 std::string& label::current_text() {
48 if(fixed_width_) {
49 return formatted_;
50 }
51 return text_;
52 }
53
54 void label::set_fixed_width(bool fixed_width)
55 {
56 fixed_width_ = fixed_width;
57 reformat_text();
58 recalculate_texture();
59 }
60
61 void label::set_dim(int w, int h) {
62 if(w != width() || h != height()) {
63 inner_set_dim(w, h);
64 reformat_text();
65 recalculate_texture();
66 }
67 }
68
69 void label::inner_set_dim(int w, int h) {
70 widget::set_dim(w, h);
71 }
72
73 void label::reformat_text()
74 {
75 if(fixed_width_) {
76 formatted_ = text_;
77 }
78 }
79
80 void label::recalculate_texture()
81 {
82 texture_ = font::render_text(current_text(), color_, size_);
83 inner_set_dim(texture_.width(),texture_.height());
84 }
85
86 void label::handle_draw() const
87 {
88 graphics::blit_texture(texture_, x(), y());
89 }
90
91 void label::set_texture(graphics::texture t) {
92 texture_ = t;
93 }
94
95 dialog_label::dialog_label(const std::string& text, const SDL_Color& color, int size)
96 : label(text, color, size), progress_(0) {
97
98 recalculate_texture();
99 }
100
101 void dialog_label::set_progress(int progress)
102 {
103 progress_ = progress;
104 recalculate_texture();
105 }
106
107 void dialog_label::recalculate_texture()
108 {
109 label::recalculate_texture();
110 stages_ = current_text().size();
111 int prog = progress_;
112 if(prog < 0) prog = 0;
113 if(prog > stages_) prog = stages_;
114 std::string txt = current_text().substr(0, prog);
115
116 if(prog > 0) {
117 set_texture(font::render_text(txt, color(), size()));
118 } else {
119 set_texture(graphics::texture());
120 }
121 }
122
123 label_factory::label_factory(const SDL_Color& color, int size)
124 : color_(color), size_(size)
125 {}
126
127 label_ptr label_factory::create(const std::string& text) const
128 {
129 return label_ptr(new label(text,color_,size_));
130 }
131
132 label_ptr label_factory::create(const std::string& text,
133 const std::string& tip) const
134 {
135 const label_ptr res(create(text));
136 res->set_tooltip(tip);
137 return res;
138 }
139
140 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef LABEL_HPP_INCLUDED
13 #define LABEL_HPP_INCLUDED
14
15 #include <boost/shared_ptr.hpp>
16
17 #include <SDL.h>
18
19 #include "texture.hpp"
20 #include "widget.hpp"
21
22 namespace gui {
23
24 class label;
25 class dialog_label;
26
27 typedef boost::shared_ptr<label> label_ptr;
28 typedef boost::shared_ptr<const label> const_label_ptr;
29 typedef boost::shared_ptr<dialog_label> dialog_label_ptr;
30
31 class label : public widget
32 {
33 public:
34 static label_ptr create(const std::string& text,
35 const SDL_Color& color, int size=14) {
36 return label_ptr(new label(text, color, size));
37 }
38 label(const std::string& text, const SDL_Color& color, int size=14);
39
40 void set_font_size(int font_size);
41 void set_color(const SDL_Color& color);
42 void set_text(const std::string& text);
43 void set_fixed_width(bool fixed_width);
44 virtual void set_dim(int x, int y);
45 SDL_Color color() { return color_; }
46 int size() { return size_; }
47 std::string text() { return text_; }
48 protected:
49 std::string& current_text();
50 virtual void recalculate_texture();
51 void set_texture(graphics::texture t);
52 private:
53 void handle_draw() const;
54 void inner_set_dim(int x, int y);
55 void reformat_text();
56
57 std::string text_, formatted_;
58 graphics::texture texture_;
59 SDL_Color color_;
60 int size_;
61 bool fixed_width_;
62 };
63
64 class dialog_label : public label
65 {
66 public:
67 dialog_label(const std::string& text, const SDL_Color& color, int size=18);
68 void set_progress(int progress);
69 int get_max_progress() { return stages_; }
70
71 protected:
72 virtual void recalculate_texture();
73 private:
74
75 int progress_, stages_;
76 };
77
78 class label_factory
79 {
80 public:
81 label_factory(const SDL_Color& color, int size);
82 label_ptr create(const std::string& text) const;
83 label_ptr create(const std::string& text,
84 const std::string& tip) const;
85 private:
86 SDL_Color color_;
87 int size_;
88 };
89
90 }
91
92 #endif
0 #include <boost/bind.hpp>
1
2 #include <algorithm>
3 #include <iostream>
4 #include <math.h>
5
6 #include "asserts.hpp"
7 #include "collision_utils.hpp"
8 #include "controls.hpp"
9 #include "draw_scene.hpp"
10 #include "draw_tile.hpp"
11 #include "entity.hpp"
12 #include "filesystem.hpp"
13 #include "foreach.hpp"
14 #include "formatter.hpp"
15 #include "gui_formula_functions.hpp"
16 #include "level.hpp"
17 #include "level_object.hpp"
18 #include "load_level.hpp"
19 #include "multiplayer.hpp"
20 #include "object_events.hpp"
21 #include "player_info.hpp"
22 #include "preferences.hpp"
23 #include "preprocessor.hpp"
24 #include "random.hpp"
25 #include "raster.hpp"
26 #include "stats.hpp"
27 #include "string_utils.hpp"
28 #include "surface_palette.hpp"
29 #include "thread.hpp"
30 #include "tile_map.hpp"
31 #include "unit_test.hpp"
32 #include "wml_formula_callable.hpp"
33 #include "wml_node.hpp"
34 #include "wml_parser.hpp"
35 #include "wml_utils.hpp"
36 #include "wml_writer.hpp"
37 #include "color_utils.hpp"
38
39 namespace {
40 boost::intrusive_ptr<level> current_level;
41
42 std::map<std::string, level::summary> load_level_summaries() {
43 std::map<std::string, level::summary> result;
44 const wml::const_node_ptr node = wml::parse_wml_from_file("data/compiled/level_index.cfg");
45
46 FOREACH_WML_CHILD(level_node, node, "level") {
47 level::summary& s = result[level_node->attr("level")];
48 s.music = level_node->attr("music");
49 s.title = level_node->attr("title");
50 }
51
52 return result;
53 }
54
55 }
56
57 level::summary level::get_summary(const std::string& id)
58 {
59 static const std::map<std::string, summary> summaries = load_level_summaries();
60 std::map<std::string, summary>::const_iterator i = summaries.find(id);
61 if(i != summaries.end()) {
62 return i->second;
63 }
64
65 return summary();
66 }
67
68 level& level::current()
69 {
70 ASSERT_LOG(current_level, "Tried to query current level when there is none");
71 return *current_level;
72 }
73
74 void level::set_as_current_level()
75 {
76 current_level = this;
77 frame::set_color_palette(palettes_used_);
78 }
79
80 level::level(const std::string& level_cfg)
81 : id_(level_cfg), highlight_layer_(INT_MIN),
82 num_compiled_tiles_(0),
83 entered_portal_active_(false), save_point_x_(-1), save_point_y_(-1),
84 editor_(false), show_foreground_(true), show_background_(true), air_resistance_(0), water_resistance_(7), end_game_(false),
85 editor_tile_updates_frozen_(0), zoom_level_(1),
86 palettes_used_(0),
87 background_palette_(-1)
88 {
89 std::cerr << "in level constructor...\n";
90 const int start_time = SDL_GetTicks();
91
92 wml::const_node_ptr node = load_level_wml(level_cfg);
93 ASSERT_LOG(node.get() != NULL, "LOAD LEVEL WML FOR " << level_cfg << " FAILED");
94
95 music_ = node->attr("music");
96 replay_data_ = node->attr("replay_data");
97 cycle_ = wml::get_int(node, "cycle");
98 time_freeze_ = 0;
99 in_dialog_ = false;
100 title_ = node->attr("title");
101 if(node->has_attr("dimensions")) {
102 boundaries_ = rect(node->attr("dimensions"));
103 } else {
104 boundaries_ = rect(0, 0, wml::get_int(node, "width", 800), wml::get_int(node, "height", 600));
105 }
106
107 if(node->has_attr("lock_screen")) {
108 lock_screen_.reset(new point(node->attr("lock_screen")));
109 }
110
111 if(node->has_attr("opaque_rects")) {
112 const std::vector<std::string> opaque_rects_str = util::split(node->attr("opaque_rects"), ':');
113 foreach(const std::string& r, opaque_rects_str) {
114 opaque_rects_.push_back(rect(r));
115 std::cerr << "OPAQUE RECT: " << r << "\n";
116 }
117 }
118
119 xscale_ = wml::get_int(node, "xscale", 100);
120 yscale_ = wml::get_int(node, "yscale", 100);
121 auto_move_camera_ = point(node->attr("auto_move_camera"));
122 air_resistance_ = wml::get_int(node, "air_resistance", 20);
123 water_resistance_ = wml::get_int(node, "water_resistance", 100);
124
125 camera_rotation_ = game_logic::formula::create_optional_formula(node->attr("camera_rotation"));
126
127 preloads_ = util::split(node->attr("preloads"));
128
129 wml::node::const_child_iterator r1 = node->begin_child("solid_rect");
130 wml::node::const_child_iterator r2 = node->end_child("solid_rect");
131 for(; r1 != r2; ++r1) {
132 solid_rect r;
133 r.r = rect(r1->second->attr("rect"));
134 r.friction = wml::get_int(r1->second, "friction", 100);
135 r.traction = wml::get_int(r1->second, "traction", 100);
136 r.damage = wml::get_int(r1->second, "damage");
137 solid_rects_.push_back(r);
138 add_solid_rect(r.r.x(), r.r.y(), r.r.x2(), r.r.y2(), r.friction, r.traction, r.damage);
139 }
140
141 std::cerr << "building..." << SDL_GetTicks() << "\n";
142 wml::node::const_child_iterator t1 = node->begin_child("tile");
143 wml::node::const_child_iterator t2 = node->end_child("tile");
144 widest_tile_ = 0;
145 highest_tile_ = 0;
146 layers_.insert(0);
147 for(; t1 != t2; ++t1) {
148 const level_tile t = level_object::build_tile(t1->second);
149 tiles_.push_back(t);
150 layers_.insert(t.zorder);
151 add_tile_solid(t);
152 }
153 std::cerr << "done building..." << SDL_GetTicks() << "\n";
154
155 t1 = node->begin_child("tile_map");
156 t2 = node->end_child("tile_map");
157 int begin_tile_index = tiles_.size();
158 for(; t1 != t2; ++t1) {
159 tile_map m(t1->second);
160 tile_maps_[m.zorder()] = m;
161 const int before = tiles_.size();
162 tile_maps_[m.zorder()].build_tiles(&tiles_);
163 std::cerr << "LAYER " << m.zorder() << " BUILT " << (tiles_.size() - before) << " tiles\n";
164 }
165
166 std::cerr << "done building tile_map..." << SDL_GetTicks() << "\n";
167
168 num_compiled_tiles_ = wml::get_int(node, "num_compiled_tiles");
169
170 tiles_.resize(tiles_.size() + num_compiled_tiles_);
171 std::vector<level_tile>::iterator compiled_itor = tiles_.end() - num_compiled_tiles_;
172
173 t1 = node->begin_child("compiled_tiles");
174 t2 = node->end_child("compiled_tiles");
175 for(; t1 != t2; ++t1) {
176 read_compiled_tiles(t1->second, compiled_itor);
177 wml_compiled_tiles_.push_back(t1->second);
178 }
179
180 ASSERT_LOG(compiled_itor == tiles_.end(), "INCORRECT NUMBER OF COMPILED TILES");
181
182 for(int i = begin_tile_index; i != tiles_.size(); ++i) {
183 add_tile_solid(tiles_[i]);
184 layers_.insert(tiles_[i].zorder);
185 }
186
187 if(std::adjacent_find(tiles_.rbegin(), tiles_.rend(), level_tile_zorder_pos_comparer()) != tiles_.rend()) {
188 std::sort(tiles_.begin(), tiles_.end(), level_tile_zorder_pos_comparer());
189 }
190
191 if(node->has_attr("palettes")) {
192 std::vector<std::string> v = util::split(node->attr("palettes"));
193 foreach(const std::string& p, v) {
194 const int id = graphics::get_palette_id(p);
195 palettes_used_ |= (1 << id);
196 }
197 }
198
199 if(node->has_attr("background_palette")) {
200 background_palette_ = graphics::get_palette_id(node->attr("background_palette"));
201 }
202
203 prepare_tiles_for_drawing();
204
205 wml::node::const_child_iterator c1 = node->begin_child("character");
206 wml::node::const_child_iterator c2 = node->end_child("character");
207 for(; c1 != c2; ++c1) {
208 wml_chars_.push_back(c1->second);
209 continue;
210 }
211
212 wml::const_node_ptr serialized_objects = node->get_child("serialized_objects");
213 if(serialized_objects.get() != NULL) {
214 wml_chars_.push_back(serialized_objects);
215 }
216
217 wml::node::const_child_iterator p1 = node->begin_child("portal");
218 wml::node::const_child_iterator p2 = node->end_child("portal");
219 for(; p1 != p2; ++p1) {
220 portal p;
221 p.area = rect(p1->second->attr("rect"));
222 p.level_dest = p1->second->attr("level");
223 p.dest = point(p1->second->attr("dest"));
224 p.dest_starting_pos = p1->second->attr("dest_starting_pos").str() == "yes";
225 p.automatic = wml::get_bool(p1->second, "automatic", true);
226 p.transition = p1->second->attr("transition");
227 portals_.push_back(p);
228 }
229
230 if(node->has_attr("next_level")) {
231 right_portal_.level_dest = node->attr("next_level");
232 right_portal_.dest_str = "left";
233 right_portal_.dest_starting_pos = false;
234 right_portal_.automatic = true;
235 }
236
237 if(node->has_attr("previous_level")) {
238 left_portal_.level_dest = node->attr("previous_level");
239 left_portal_.dest_str = "right";
240 left_portal_.dest_starting_pos = false;
241 left_portal_.automatic = true;
242 }
243
244 wml::const_node_ptr bg = node->get_child("background");
245 if(bg) {
246 background_.reset(new background(bg, background_palette_));
247 } else if(node->has_attr("background")) {
248 background_ = background::get(node->attr("background"), background_palette_);
249 background_offset_ = point(node->attr("background_offset"));
250 background_->set_offset(background_offset_);
251 }
252
253 wml::const_node_ptr water_node = node->get_child("water");
254 if(water_node) {
255 water_.reset(new water(water_node));
256 }
257
258 FOREACH_WML_CHILD(script_node, node, "script") {
259 movement_script s(script_node);
260 movement_scripts_[s.id()] = s;
261 }
262
263 const int time_taken_ms = (SDL_GetTicks() - start_time);
264 stats::record_event(id(), stats::const_record_ptr(new stats::load_level_record(time_taken_ms)));
265 std::cerr << "done level constructor: " << time_taken_ms << "\n";
266
267 gui_algo_str_ = wml::get_str(node, "gui", "default");
268 gui_algorithm_ = gui_algorithm::get(gui_algo_str_);
269 gui_algorithm_->new_level();
270
271 }
272
273 level::~level()
274 {
275 }
276
277 void level::read_compiled_tiles(wml::const_node_ptr node, std::vector<level_tile>::iterator& out)
278 {
279 const int xbase = wml::get_int(node, "x");
280 const int ybase = wml::get_int(node, "y");
281 const int zorder = wml::get_int(node, "zorder");
282
283 int x = xbase;
284 int y = ybase;
285 const std::string& tiles = node->attr("tiles");
286 const char* i = tiles.c_str();
287 const char* end = tiles.c_str() + tiles.size();
288 while(i != end) {
289 if(*i == '|') {
290 ++i;
291 } else if(*i == ',') {
292 x += TileSize;
293 ++i;
294 } else if(*i == '\n') {
295 x = xbase;
296 y += TileSize;
297 ++i;
298 } else {
299 ASSERT_LOG(out != tiles_.end(), "NOT ENOUGH COMPILED TILES REPORTED");
300
301 out->x = x;
302 out->y = y;
303 out->zorder = zorder;
304 out->face_right = false;
305 out->draw_disabled = false;
306 if(*i == '~') {
307 out->face_right = true;
308 ++i;
309 }
310
311 ASSERT_LOG(end - i >= 3, "ILLEGAL TILE FOUND");
312
313 out->object = level_object::get_compiled(i);
314 ++out;
315 i += 3;
316 }
317 }
318 }
319
320 void level::load_character(wml::const_node_ptr c)
321 {
322 chars_.push_back(entity::build(c));
323 layers_.insert(chars_.back()->zorder());
324 if(!chars_.back()->is_human()) {
325 chars_.back()->set_id(chars_.size());
326 }
327 if(chars_.back()->is_human()) {
328 if(players_.size() == multiplayer::slot()) {
329 last_touched_player_ = player_ = chars_.back();
330 }
331
332 players_.push_back(chars_.back());
333 players_.back()->get_player_info()->set_player_slot(players_.size() - 1);
334 }
335
336 const int group = chars_.back()->group();
337 if(group >= 0) {
338 if(group >= groups_.size()) {
339 groups_.resize(group + 1);
340 }
341
342 groups_[group].push_back(chars_.back());
343 }
344
345 if(chars_.back()->label().empty() == false) {
346 chars_by_label_[chars_.back()->label()] = chars_.back();
347 }
348
349 solid_chars_.clear();
350 }
351
352 void level::finish_loading()
353 {
354 graphics::texture::build_textures_from_worker_threads();
355
356 game_logic::wml_formula_callable_read_scope read_scope;
357 foreach(wml::const_node_ptr node, wml_chars_) {
358 if(node->name() != "serialized_objects") {
359 continue;
360 }
361
362 FOREACH_WML_CHILD(obj_node, node, "character") {
363 const intptr_t addr_id = strtoll(obj_node->attr("_addr").c_str(), NULL, 16);
364 game_logic::wml_formula_callable_read_scope::register_serialized_object(addr_id, entity::build(obj_node));
365 fprintf(stderr, "DESERIALIZED: %x\n", addr_id);
366 }
367 }
368
369 foreach(wml::const_node_ptr node, wml_chars_) {
370 if(node->name() == "serialized_objects") {
371 continue;
372 }
373
374 load_character(node);
375
376 const intptr_t addr_id = strtoll(node->attr("_addr").c_str(), NULL, 16);
377 game_logic::wml_formula_callable_read_scope::register_serialized_object(addr_id, chars_.back());
378
379 if(node->has_attr("attached_objects")) {
380 std::cerr << "LOADING ATTACHED: " << node->attr("attached_objects") << "\n";
381 std::vector<entity_ptr> attached;
382 std::vector<std::string> v = util::split(node->attr("attached_objects"));
383 foreach(const std::string& s, v) {
384 std::cerr << "ATTACHED: " << s << "\n";
385 const intptr_t addr_id = strtoll(s.c_str(), NULL, 16);
386 game_logic::wml_serializable_formula_callable_ptr obj = game_logic::wml_formula_callable_read_scope::get_serialized_object(addr_id);
387 entity* e = dynamic_cast<entity*>(obj.get());
388 if(e) {
389 std::cerr << "GOT ATTACHED\n";
390 attached.push_back(entity_ptr(e));
391 }
392 }
393
394 chars_.back()->set_attached_objects(attached);
395 }
396 }
397
398 wml_chars_.clear();
399
400 controls::new_level(cycle_, players_.empty() ? 1 : players_.size(), multiplayer::slot());
401
402 //start loading FML for previous and next level
403 if(!previous_level().empty()) {
404 preload_level_wml(previous_level());
405 }
406
407 if(!next_level().empty()) {
408 preload_level_wml(next_level());
409 }
410 }
411
412 void level::set_multiplayer_slot(int slot)
413 {
414 ASSERT_INDEX_INTO_VECTOR(slot, players_);
415 last_touched_player_ = player_ = players_[slot];
416 controls::new_level(cycle_, players_.empty() ? 1 : players_.size(), slot);
417 }
418
419 void level::load_save_point(const level& lvl)
420 {
421 if(lvl.save_point_x_ < 0) {
422 return;
423 }
424
425 save_point_x_ = lvl.save_point_x_;
426 save_point_y_ = lvl.save_point_y_;
427 if(player_) {
428 player_->set_pos(save_point_x_, save_point_y_);
429 }
430 }
431
432 namespace {
433 //we allow rebuilding tiles in the background. We only rebuild the tiles
434 //one at a time, if more requests for rebuilds come in while we are
435 //rebuilding, then queue the requests up.
436
437 //the level we're currently building tiles for.
438 const level* level_building = NULL;
439
440 //record whether we are currently rebuilding tiles, and if we have had
441 //another request come in during the current building of tiles.
442 bool tile_rebuild_in_progress = false;
443 bool tile_rebuild_queued = false;
444
445 threading::thread* rebuild_tile_thread = NULL;
446
447 //an unsynchronized buffer only accessed by the main thread with layers
448 //that will be rebuilt.
449 std::vector<int> rebuild_tile_layers_buffer;
450
451 //buffer accessed by the worker thread which contains layers that will
452 //be rebuilt.
453 std::vector<int> rebuild_tile_layers_worker_buffer;
454
455 //a locked flag which is polled to see if tile rebuilding has been completed.
456 bool tile_rebuild_complete = false;
457 threading::mutex& tile_rebuild_complete_mutex() {
458 static threading::mutex m;
459 return m;
460 }
461
462 //the tiles where the thread will store the new tiles.
463 std::vector<level_tile> task_tiles;
464
465 void build_tiles_thread_function(std::map<int, tile_map> tile_maps) {
466 task_tiles.clear();
467
468 if(rebuild_tile_layers_worker_buffer.empty()) {
469 for(std::map<int, tile_map>::const_iterator i = tile_maps.begin();
470 i != tile_maps.end(); ++i) {
471 i->second.build_tiles(&task_tiles);
472 }
473 } else {
474 foreach(int layer, rebuild_tile_layers_worker_buffer) {
475 std::map<int, tile_map>::const_iterator itor = tile_maps.find(layer);
476 if(itor != tile_maps.end()) {
477 itor->second.build_tiles(&task_tiles);
478 }
479 }
480 }
481
482 threading::lock l(tile_rebuild_complete_mutex());
483 tile_rebuild_complete = true;
484 }
485
486 }
487
488 void level::start_rebuild_tiles_in_background(const std::vector<int>& layers)
489 {
490 //merge the new layers with any layers we already have queued up.
491 if(layers.empty() == false && (!tile_rebuild_queued || rebuild_tile_layers_buffer.empty() == false)) {
492 //add the layers we want to rebuild to those already requested.
493 rebuild_tile_layers_buffer.insert(rebuild_tile_layers_buffer.end(), layers.begin(), layers.end());
494 std::sort(rebuild_tile_layers_buffer.begin(), rebuild_tile_layers_buffer.end());
495 rebuild_tile_layers_buffer.erase(std::unique(rebuild_tile_layers_buffer.begin(), rebuild_tile_layers_buffer.end()), rebuild_tile_layers_buffer.end());
496 } else if(layers.empty()) {
497 rebuild_tile_layers_buffer.clear();
498 }
499
500 if(tile_rebuild_in_progress) {
501 tile_rebuild_queued = true;
502 return;
503 }
504
505 level_building = this;
506
507 tile_rebuild_in_progress = true;
508 tile_rebuild_complete = false;
509
510 rebuild_tile_layers_worker_buffer = rebuild_tile_layers_buffer;
511 rebuild_tile_layers_buffer.clear();
512
513 rebuild_tile_thread = new threading::thread(boost::bind(build_tiles_thread_function, tile_maps_));
514 }
515
516 void level::freeze_rebuild_tiles_in_background()
517 {
518 tile_rebuild_in_progress = true;
519 }
520
521 void level::unfreeze_rebuild_tiles_in_background()
522 {
523 if(rebuild_tile_thread != NULL) {
524 //a thread is actually in flight calculating tiles, so any requests
525 //would have been queued up anyway.
526 return;
527 }
528
529 tile_rebuild_in_progress = false;
530 start_rebuild_tiles_in_background(rebuild_tile_layers_buffer);
531 }
532
533 namespace {
534 bool level_tile_from_layer(const level_tile& t, int zorder) {
535 return t.layer_from == zorder;
536 }
537 }
538
539 void level::complete_rebuild_tiles_in_background()
540 {
541 if(!tile_rebuild_in_progress) {
542 return;
543 }
544
545 {
546 threading::lock l(tile_rebuild_complete_mutex());
547 if(!tile_rebuild_complete) {
548 return;
549 }
550 }
551
552 const int begin_time = SDL_GetTicks();
553
554 // ASSERT_LOG(rebuild_tile_thread, "REBUILD TILE THREAD IS NULL");
555 delete rebuild_tile_thread;
556 rebuild_tile_thread = NULL;
557
558 if(level_building == this) {
559 if(rebuild_tile_layers_worker_buffer.empty()) {
560 tiles_.clear();
561 } else {
562 foreach(int layer, rebuild_tile_layers_worker_buffer) {
563 tiles_.erase(std::remove_if(tiles_.begin(), tiles_.end(), boost::bind(level_tile_from_layer, _1, layer)), tiles_.end());
564 }
565 }
566
567 tiles_.insert(tiles_.end(), task_tiles.begin(), task_tiles.end());
568 task_tiles.clear();
569
570 complete_tiles_refresh();
571 }
572
573 std::cerr << "COMPLETE TILE REBUILD: " << (SDL_GetTicks() - begin_time) << "\n";
574
575 rebuild_tile_layers_worker_buffer.clear();
576
577 tile_rebuild_in_progress = false;
578 if(tile_rebuild_queued) {
579 tile_rebuild_queued = false;
580 start_rebuild_tiles_in_background(rebuild_tile_layers_buffer);
581 }
582 }
583
584 void level::rebuild_tiles()
585 {
586 if(editor_tile_updates_frozen_) {
587 return;
588 }
589
590 tiles_.clear();
591 for(std::map<int, tile_map>::const_iterator i = tile_maps_.begin(); i != tile_maps_.end(); ++i) {
592 i->second.build_tiles(&tiles_);
593 }
594
595 complete_tiles_refresh();
596 }
597
598 void level::complete_tiles_refresh()
599 {
600 const int start = SDL_GetTicks();
601 std::cerr << "adding solids..." << (SDL_GetTicks() - start) << "\n";
602 solid_.clear();
603 standable_.clear();
604
605 foreach(level_tile& t, tiles_) {
606 add_tile_solid(t);
607 layers_.insert(t.zorder);
608 }
609
610 std::cerr << "sorting..." << (SDL_GetTicks() - start) << "\n";
611
612 if(std::adjacent_find(tiles_.rbegin(), tiles_.rend(), level_tile_zorder_pos_comparer()) != tiles_.rend()) {
613 std::sort(tiles_.begin(), tiles_.end(), level_tile_zorder_pos_comparer());
614 }
615 prepare_tiles_for_drawing();
616 std::cerr << "done..." << (SDL_GetTicks() - start) << "\n";
617 }
618
619 int level::variations(int xtile, int ytile) const
620 {
621 for(std::map<int, tile_map>::const_iterator i = tile_maps_.begin();
622 i != tile_maps_.end(); ++i) {
623 const int var = i->second.get_variations(xtile, ytile);
624 if(var > 1) {
625 return var;
626 }
627 }
628
629 return 1;
630 }
631
632 void level::flip_variations(int xtile, int ytile, int delta)
633 {
634 for(std::map<int, tile_map>::iterator i = tile_maps_.begin();
635 i != tile_maps_.end(); ++i) {
636 std::cerr << "get_variations zorder: " << i->first << "\n";
637 if(i->second.get_variations(xtile, ytile) > 1) {
638 i->second.flip_variation(xtile, ytile, delta);
639 }
640 }
641
642 rebuild_tiles_rect(rect(xtile*TileSize, ytile*TileSize, TileSize, TileSize));
643 }
644
645 namespace {
646 struct TileInRect {
647 explicit TileInRect(const rect& r) : rect_(r)
648 {}
649
650 bool operator()(const level_tile& t) const {
651 return point_in_rect(point(t.x, t.y), rect_);
652 }
653
654 rect rect_;
655 };
656 }
657
658 void level::rebuild_tiles_rect(const rect& r)
659 {
660 if(editor_tile_updates_frozen_) {
661 return;
662 }
663
664 for(int x = r.x(); x < r.x2(); x += TileSize) {
665 for(int y = r.y(); y < r.y2(); y += TileSize) {
666 tile_pos pos(x/TileSize, y/TileSize);
667 solid_.erase(pos);
668 standable_.erase(pos);
669 }
670 }
671
672 tiles_.erase(std::remove_if(tiles_.begin(), tiles_.end(), TileInRect(r)), tiles_.end());
673
674 std::vector<level_tile> tiles;
675 for(std::map<int, tile_map>::const_iterator i = tile_maps_.begin(); i != tile_maps_.end(); ++i) {
676 i->second.build_tiles(&tiles, &r);
677 }
678
679 foreach(level_tile& t, tiles) {
680 add_tile_solid(t);
681 tiles_.push_back(t);
682 layers_.insert(t.zorder);
683 }
684
685 if(std::adjacent_find(tiles_.rbegin(), tiles_.rend(), level_tile_zorder_pos_comparer()) != tiles_.rend()) {
686 std::sort(tiles_.begin(), tiles_.end(), level_tile_zorder_pos_comparer());
687 }
688 prepare_tiles_for_drawing();
689 }
690
691 wml::node_ptr level::write() const
692 {
693 std::sort(tiles_.begin(), tiles_.end(), level_tile_zorder_pos_comparer());
694 game_logic::wml_formula_callable_serialization_scope serialization_scope;
695
696 wml::node_ptr res(new wml::node("level"));
697 res->set_attr("title", title_);
698 res->set_attr("music", music_);
699 if(gui_algo_str_ != "default") {
700 res->set_attr("gui", gui_algo_str_);
701 }
702
703 if(cycle_) {
704 res->set_attr("cycle", formatter() << cycle_);
705 }
706 res->set_attr("dimensions", boundaries().to_string());
707
708 res->set_attr("xscale", formatter() << xscale_);
709 res->set_attr("yscale", formatter() << yscale_);
710 res->set_attr("auto_move_camera", auto_move_camera_.to_string());
711 res->set_attr("air_resistance", formatter() << air_resistance_);
712 res->set_attr("water_resistance", formatter() << water_resistance_);
713
714 res->set_attr("preloads", util::join(preloads_));
715
716 if(lock_screen_) {
717 res->set_attr("lock_screen", lock_screen_->to_string());
718 }
719
720 if(water_) {
721 res->add_child(water_->write());
722 }
723
724 if(camera_rotation_) {
725 res->set_attr("camera_rotation", camera_rotation_->str());
726 }
727
728 foreach(const solid_rect& r, solid_rects_) {
729 wml::node_ptr node(new wml::node("solid_rect"));
730 node->set_attr("rect", r.r.to_string());
731 node->set_attr("friction", formatter() << r.friction);
732 node->set_attr("traction", formatter() << r.traction);
733 node->set_attr("damage", formatter() << r.damage);
734 res->add_child(node);
735 }
736
737 for(std::map<int, tile_map>::const_iterator i = tile_maps_.begin(); i != tile_maps_.end(); ++i) {
738 wml::node_ptr node(i->second.write());
739 if(preferences::compiling_tiles) {
740 node->set_attr("tiles", "");
741 node->set_attr("unique_tiles", "");
742 }
743 res->add_child(node);
744 }
745
746 if(preferences::compiling_tiles && !tiles_.empty()) {
747
748 level_object::set_current_palette(palettes_used_);
749
750 int num_tiles = 0;
751 int last_zorder = INT_MIN;
752 int basex = 0, basey = 0;
753 int last_x = 0, last_y = 0;
754 std::string tiles_str;
755 for(int n = 0; n <= tiles_.size(); ++n) {
756 if(n != tiles_.size() && tiles_[n].draw_disabled && tiles_[n].object->has_solid() == false) {
757 continue;
758 }
759
760 if(n == tiles_.size() || tiles_[n].zorder != last_zorder) {
761 if(!tiles_str.empty()) {
762 wml::node_ptr node(new wml::node("compiled_tiles"));
763 node->set_attr("zorder", formatter() << last_zorder);
764 node->set_attr("x", formatter() << basex);
765 node->set_attr("y", formatter() << basey);
766 node->set_attr("tiles", tiles_str);
767 res->add_child(node);
768 }
769
770 if(n == tiles_.size()) {
771 break;
772 }
773
774 tiles_str.clear();
775
776 last_zorder = tiles_[n].zorder;
777
778 basex = basey = INT_MAX;
779 for(int m = n; m != tiles_.size() && tiles_[m].zorder == tiles_[n].zorder; ++m) {
780 if(tiles_[m].x < basex) {
781 basex = tiles_[m].x;
782 }
783
784 if(tiles_[m].y < basey) {
785 basey = tiles_[m].y;
786 }
787 }
788
789 last_x = basex;
790 last_y = basey;
791 }
792
793 while(last_y < tiles_[n].y) {
794 tiles_str += "\n";
795 last_y += TileSize;
796 last_x = basex;
797 }
798
799 while(last_x < tiles_[n].x) {
800 tiles_str += ",";
801 last_x += TileSize;
802 }
803
804 ASSERT_EQ(last_x, tiles_[n].x);
805 ASSERT_EQ(last_y, tiles_[n].y);
806
807 if(tiles_[n].face_right) {
808 tiles_str += "~";
809 }
810
811 const int xpos = tiles_[n].x;
812 const int ypos = tiles_[n].y;
813 const int zpos = tiles_[n].zorder;
814 const int start_n = n;
815
816 while(n != tiles_.size() && tiles_[n].x == xpos && tiles_[n].y == ypos && tiles_[n].zorder == zpos) {
817 char buf[4];
818 tiles_[n].object->write_compiled_index(buf);
819 if(n != start_n) {
820 tiles_str += "|";
821 }
822 tiles_str += buf;
823 ++n;
824 ++num_tiles;
825 }
826
827 --n;
828
829 tiles_str += ",";
830
831 last_x += TileSize;
832 }
833
834 res->set_attr("num_compiled_tiles", formatter() << num_tiles);
835
836 //calculate rectangular opaque areas of tiles that allow us
837 //to avoid drawing the background. Start by calculating the set
838 //of tiles that are opaque.
839 typedef std::pair<int,int> OpaqueLoc;
840 std::set<OpaqueLoc> opaque;
841 foreach(const level_tile& t, tiles_) {
842 if(t.object->is_opaque() == false) {
843 continue;
844 }
845
846 std::map<int, tile_map>::const_iterator tile_itor = tile_maps_.find(t.zorder);
847 ASSERT_LOG(tile_itor != tile_maps_.end(), "COULD NOT FIND TILE LAYER IN MAP");
848 if(tile_itor->second.x_speed() != 100 || tile_itor->second.y_speed() != 100) {
849 //we only consider the layer that moves at 100% speed,
850 //since calculating obscured areas at other layers is too
851 //complicated.
852 continue;
853 }
854
855 opaque.insert(std::pair<int,int>(t.x,t.y));
856 }
857
858 std::cerr << "BUILDING RECTS...\n";
859
860 std::vector<rect> opaque_rects;
861
862 //keep iterating, finding the largest rectangle we can make of
863 //available opaque locations, then removing all those opaque
864 //locations from our set, until we have all areas covered.
865 while(!opaque.empty()) {
866 rect largest_rect;
867
868 //iterate over every opaque location, treating each one
869 //as a possible upper-left corner of our rectangle.
870 for(std::set<OpaqueLoc>::const_iterator loc_itor = opaque.begin();
871 loc_itor != opaque.end(); ++loc_itor) {
872 const OpaqueLoc& loc = *loc_itor;
873
874 std::vector<OpaqueLoc> v;
875 v.push_back(loc);
876
877 std::set<OpaqueLoc>::const_iterator find_itor = opaque.end();
878
879 int max_height = -1;
880
881 //try to build a top row of a rectangle. After adding each
882 //cell, we will try to expand the rectangle downwards, as
883 //far as it will go.
884 while((find_itor = opaque.find(OpaqueLoc(v.back().first + TileSize, v.back().second))) != opaque.end()) {
885 v.push_back(OpaqueLoc(v.back().first + TileSize, v.back().second));
886
887 int rows = 0;
888 while(max_height == -1 || rows < max_height) {
889 ++find_itor;
890 if(find_itor == opaque.end() || find_itor->first != v.back().first || find_itor->second != v.back().second + TileSize*(rows+1)) {
891 break;
892 }
893
894 ++rows;
895 }
896
897 if(rows < max_height || max_height == -1) {
898 max_height = rows;
899 }
900
901 rect r(v.front().first, v.front().second, v.size()*TileSize, rows*TileSize);
902 if(r.w()*r.h() > largest_rect.w()*largest_rect.h()) {
903 largest_rect = r;
904 }
905 } //end while expand rectangle to the right.
906 } //end for iterating over all possible rectangle upper-left positions
907
908 //have a minimum size for rectangles. If we fail to reach
909 //the minimum size then just stop. It's not worth bothering
910 //with lots of small little rectangles.
911 if(largest_rect.w()*largest_rect.h() < TileSize*TileSize*32) {
912 break;
913 }
914
915 opaque_rects.push_back(largest_rect);
916
917 for(std::set<OpaqueLoc>::iterator i = opaque.begin();
918 i != opaque.end(); ) {
919 if(i->first >= largest_rect.x() && i->second >= largest_rect.y() && i->first < largest_rect.x2() && i->second < largest_rect.y2()) {
920 opaque.erase(i++);
921 } else {
922 ++i;
923 }
924 }
925 } //end searching for rectangles to add.
926 std::cerr << "DONE BUILDING RECTS...\n";
927
928 if(!opaque_rects.empty()) {
929 std::ostringstream opaque_rects_str;
930 foreach(const rect& r, opaque_rects) {
931 opaque_rects_str << r.to_string() << ":";
932 }
933
934 res->set_attr("opaque_rects", opaque_rects_str.str());
935
936 std::cerr << "RECTS: " << id_ << ": " << opaque_rects.size() << "\n";
937 }
938 } //end if preferences::compiling
939
940 foreach(entity_ptr ch, chars_) {
941 wml::node_ptr node(ch->write());
942 res->add_child(node);
943 game_logic::wml_formula_callable_serialization_scope::register_serialized_object(ch, node);
944 }
945
946 foreach(const portal& p, portals_) {
947 wml::node_ptr node(new wml::node("portal"));
948 node->set_attr("rect", p.area.to_string());
949 node->set_attr("level", p.level_dest);
950 node->set_attr("dest_starting_pos", p.dest_starting_pos ? "yes" : "no");
951 node->set_attr("dest", p.dest.to_string());
952 node->set_attr("automatic", p.automatic ? "yes" : "no");
953 node->set_attr("transition", p.transition);
954 res->add_child(node);
955 }
956
957 if(right_portal_.level_dest.empty() == false) {
958 res->set_attr("next_level", right_portal_.level_dest);
959 }
960
961 std::cerr << "PREVIOUS LEVEL: " << left_portal_.level_dest << "\n";
962 if(left_portal_.level_dest.empty() == false) {
963 res->set_attr("previous_level", left_portal_.level_dest);
964 }
965
966 if(background_) {
967 if(background_->id().empty()) {
968 res->add_child(background_->write());
969 } else {
970 res->set_attr("background", background_->id());
971 res->set_attr("background_offset", background_offset_.to_string());
972 }
973 }
974
975 for(std::map<std::string, movement_script>::const_iterator i = movement_scripts_.begin(); i != movement_scripts_.end(); ++i) {
976 res->add_child(i->second.write());
977 }
978
979 res->add_child(serialization_scope.write_objects());
980
981 if(num_compiled_tiles_ > 0) {
982 res->set_attr("num_compiled_tiles", formatter() << num_compiled_tiles_);
983 foreach(wml::node_ptr compiled_node, wml_compiled_tiles_) {
984 res->add_child(compiled_node);
985 }
986 }
987
988 if(palettes_used_) {
989 std::vector<std::string> out;
990 unsigned int p = palettes_used_;
991 int id = 0;
992 while(p) {
993 if(p&1) {
994 out.push_back(graphics::get_palette_name(id));
995 }
996
997 p >>= 1;
998 ++id;
999 }
1000
1001 res->set_attr("palettes", util::join(out));
1002 }
1003
1004 if(background_palette_ != -1) {
1005 res->set_attr("background_palette", graphics::get_palette_name(background_palette_));
1006 }
1007
1008 return res;
1009 }
1010
1011 point level::get_dest_from_str(const std::string& key) const
1012 {
1013 int ypos = 0;
1014 if(player()) {
1015 ypos = player()->get_entity().y();
1016 }
1017 if(key == "left") {
1018 return point(boundaries().x() + 32, ypos);
1019 } else if(key == "right") {
1020 return point(boundaries().x2() - 128, ypos);
1021 } else {
1022 return point();
1023 }
1024 }
1025
1026 const std::string& level::previous_level() const
1027 {
1028 return left_portal_.level_dest;
1029 }
1030
1031 const std::string& level::next_level() const
1032 {
1033 return right_portal_.level_dest;
1034 }
1035
1036 void level::set_previous_level(const std::string& name)
1037 {
1038 left_portal_.level_dest = name;
1039 left_portal_.dest_str = "right";
1040 left_portal_.dest_starting_pos = false;
1041 left_portal_.automatic = true;
1042 }
1043
1044 void level::set_next_level(const std::string& name)
1045 {
1046 right_portal_.level_dest = name;
1047 right_portal_.dest_str = "left";
1048 right_portal_.dest_starting_pos = false;
1049 right_portal_.automatic = true;
1050 }
1051
1052 namespace {
1053 //counter incremented every time the level is drawn.
1054 int draw_count = 0;
1055 }
1056
1057 void level::draw_layer(int layer, int x, int y, int w, int h) const
1058 {
1059 if(layer >= 1000 && editor_ && show_foreground_ == false) {
1060 return;
1061 }
1062
1063 if(editor_ && layer == highlight_layer_) {
1064 const GLfloat alpha = 0.3 + (1.0+sin(draw_count/5.0))*0.35;
1065 glColor4f(1.0, 1.0, 1.0, alpha);
1066
1067 } else if(editor_ && hidden_layers_.count(layer)) {
1068 glColor4f(1.0, 1.0, 1.0, 0.3);
1069 }
1070
1071 glPushMatrix();
1072
1073 graphics::distortion_translation distort_translation;
1074
1075 // parallax scrolling for tiles.
1076 std::map<int, tile_map>::const_iterator tile_map_iterator = tile_maps_.find(layer);
1077 if(tile_map_iterator != tile_maps_.end()) {
1078 int scrollx = tile_map_iterator->second.x_speed();
1079 int scrolly = tile_map_iterator->second.y_speed();
1080
1081 const int diffx = ((scrollx - 100)*x)/100;
1082 const int diffy = ((scrolly - 100)*y)/100;
1083
1084 glTranslatef(diffx, diffy, 0.0);
1085 distort_translation.translate(diffx, diffy);
1086
1087 //here, we adjust the screen bounds (they're a first order optimization) to account for the parallax shift
1088 x -= diffx;
1089 y -= diffy;
1090 }
1091
1092 typedef std::vector<level_tile>::const_iterator itor;
1093 std::pair<itor,itor> range = std::equal_range(tiles_.begin(), tiles_.end(), layer, level_tile_zorder_comparer());
1094
1095 itor tile_itor = std::lower_bound(range.first, range.second, y,
1096 level_tile_y_pos_comparer());
1097
1098 if(tile_itor == range.second) {
1099 glPopMatrix();
1100 return;
1101 }
1102
1103 std::map<int, layer_blit_info>::iterator layer_itor = blit_cache_.find(layer);
1104 if(layer_itor == blit_cache_.end()) {
1105 glPopMatrix();
1106 return;
1107 }
1108
1109 const level_tile* t = &*tile_itor;
1110 const level_tile* end_tiles = &*tiles_.begin() + tiles_.size();
1111
1112 layer_blit_info& blit_info = layer_itor->second;
1113
1114 const rect tile_positions(x/32 - (x < 0 ? 1 : 0), y/32 - (y < 0 ? 1 : 0),
1115 (x + w)/32 - (x + w < 0 ? 1 : 0),
1116 (y + h)/32 - (y + h < 0 ? 1 : 0));
1117
1118 std::vector<layer_blit_info::IndexType>& opaque_indexes = blit_info.opaque_indexes;
1119 std::vector<layer_blit_info::IndexType>& translucent_indexes = blit_info.translucent_indexes;
1120
1121 if(blit_info.tile_positions != tile_positions || editor_) {
1122 blit_info.tile_positions = tile_positions;
1123
1124 opaque_indexes.clear();
1125 translucent_indexes.clear();
1126
1127 int ystart = std::max<int>(0, (y - blit_info.ybase)/TileSize);
1128 int yend = std::min<int>(blit_info.indexes.size(), (y + h - blit_info.ybase)/TileSize + 1);
1129
1130 for(; ystart < yend; ++ystart) {
1131 const std::vector<layer_blit_info::IndexType>& indexes = blit_info.indexes[ystart];
1132 int xstart = std::max<int>(0, (x - blit_info.xbase)/TileSize);
1133 int xend = std::min<int>(indexes.size(), (x + w - blit_info.xbase)/TileSize + 1);
1134 for(; xstart < xend; ++xstart) {
1135 if(indexes[xstart] != TILE_INDEX_TYPE_MAX) {
1136 if(indexes[xstart] > 0) {
1137 GLint index = indexes[xstart];
1138 opaque_indexes.push_back(index);
1139 opaque_indexes.push_back(index+1);
1140 opaque_indexes.push_back(index+2);
1141 opaque_indexes.push_back(index+1);
1142 opaque_indexes.push_back(index+2);
1143 opaque_indexes.push_back(index+3);
1144 ASSERT_INDEX_INTO_VECTOR(index, blit_info.blit_vertexes);
1145 ASSERT_INDEX_INTO_VECTOR(index+3, blit_info.blit_vertexes);
1146 } else {
1147 GLint index = -indexes[xstart];
1148 translucent_indexes.push_back(index);
1149 translucent_indexes.push_back(index+1);
1150 translucent_indexes.push_back(index+2);
1151 translucent_indexes.push_back(index+1);
1152 translucent_indexes.push_back(index+2);
1153 translucent_indexes.push_back(index+3);
1154 ASSERT_INDEX_INTO_VECTOR(index, blit_info.blit_vertexes);
1155 ASSERT_INDEX_INTO_VECTOR(index+3, blit_info.blit_vertexes);
1156 }
1157 }
1158 }
1159 }
1160 }
1161
1162 glDisable(GL_BLEND);
1163 draw_layer_solid(layer, x, y, w, h);
1164 if(blit_info.texture_id != GLuint(-1)) {
1165 graphics::texture::set_current_texture(blit_info.texture_id);
1166 }
1167
1168 if(!opaque_indexes.empty()) {
1169 glVertexPointer(2, GL_SHORT, sizeof(tile_corner), &blit_info.blit_vertexes[0].vertex[0]);
1170 glTexCoordPointer(2, GL_FLOAT, sizeof(tile_corner), &blit_info.blit_vertexes[0].uv[0]);
1171 glDrawElements(GL_TRIANGLES, opaque_indexes.size(), TILE_INDEX_TYPE, &opaque_indexes[0]);
1172 }
1173 glEnable(GL_BLEND);
1174
1175 if(!translucent_indexes.empty()) {
1176 glVertexPointer(2, GL_SHORT, sizeof(tile_corner), &blit_info.blit_vertexes[0].vertex[0]);
1177 glTexCoordPointer(2, GL_FLOAT, sizeof(tile_corner), &blit_info.blit_vertexes[0].uv[0]);
1178
1179 if(blit_info.texture_id == GLuint(-1)) {
1180 //we have multiple different texture ID's in this layer. This means
1181 //we will draw each tile seperately.
1182 for(int n = 0; n < translucent_indexes.size(); n += 6) {
1183 graphics::texture::set_current_texture(blit_info.vertex_texture_ids[translucent_indexes[n]/4]);
1184 glDrawElements(GL_TRIANGLES, 6, TILE_INDEX_TYPE, &translucent_indexes[n]);
1185 }
1186 } else {
1187 //we have just one texture ID and so can draw all tiles in one call.
1188 glDrawElements(GL_TRIANGLES, translucent_indexes.size(), TILE_INDEX_TYPE, &translucent_indexes[0]);
1189 }
1190 }
1191
1192 glPopMatrix();
1193
1194 glColor4f(1.0, 1.0, 1.0, 1.0);
1195 }
1196
1197 void level::draw_layer_solid(int layer, int x, int y, int w, int h) const
1198 {
1199 solid_color_rect arg;
1200 arg.layer = layer;
1201 typedef std::vector<solid_color_rect>::const_iterator SolidItor;
1202 std::pair<SolidItor, SolidItor> solid = std::equal_range(solid_color_rects_.begin(), solid_color_rects_.end(), arg, solid_color_rect_cmp());
1203 if(solid.first != solid.second) {
1204 const rect viewport(x, y, w, h);
1205
1206 glDisable(GL_TEXTURE_2D);
1207 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
1208 while(solid.first != solid.second) {
1209 rect area = solid.first->area;
1210 if(!rects_intersect(area, viewport)) {
1211 ++solid.first;
1212 continue;
1213 }
1214
1215 area = intersection_rect(area, viewport);
1216
1217 solid.first->color.set_as_current_color();
1218 GLshort varray[] = {
1219 area.x(), area.y(),
1220 area.x() + area.w(), area.y(),
1221 area.x(), area.y() + area.h(),
1222 area.x() + area.w(), area.y() + area.h(),
1223 };
1224
1225 glVertexPointer(2, GL_SHORT, 0, varray);
1226 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
1227
1228 ++solid.first;
1229 }
1230 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
1231 glEnable(GL_TEXTURE_2D);
1232 glColor4ub(255, 255, 255, 255);
1233 }
1234 }
1235
1236 void level::prepare_tiles_for_drawing()
1237 {
1238 level_object::set_current_palette(palettes_used_);
1239
1240 solid_color_rects_.clear();
1241 blit_cache_.clear();
1242
1243 for(int n = 0; n != tiles_.size(); ++n) {
1244 if(tiles_[n].object->solid_color()) {
1245 continue;
1246 }
1247
1248 layer_blit_info& blit_info = blit_cache_[tiles_[n].zorder];
1249 if(blit_info.xbase == -1) {
1250 blit_info.texture_id = tiles_[n].object->texture().get_id();
1251 blit_info.xbase = tiles_[n].x;
1252 blit_info.ybase = tiles_[n].y;
1253 }
1254
1255 if(tiles_[n].x < blit_info.xbase) {
1256 blit_info.xbase = tiles_[n].x;
1257 }
1258
1259 if(tiles_[n].y < blit_info.ybase) {
1260 blit_info.ybase = tiles_[n].y;
1261 }
1262 }
1263
1264 for(int n = 0; n != tiles_.size(); ++n) {
1265
1266 if(tiles_[n].object->solid_color()) {
1267 tiles_[n].draw_disabled = true;
1268 if(!solid_color_rects_.empty()) {
1269 solid_color_rect& r = solid_color_rects_.back();
1270 if(r.layer == tiles_[n].zorder && r.color.rgba() == tiles_[n].object->solid_color()->rgba() && r.area.y() == tiles_[n].y && r.area.x() + r.area.w() == tiles_[n].x) {
1271 r.area = rect(r.area.x(), r.area.y(), r.area.w() + TileSize, r.area.h());
1272 continue;
1273 }
1274 }
1275
1276 solid_color_rect r;
1277 r.color = *tiles_[n].object->solid_color();
1278 r.area = rect(tiles_[n].x, tiles_[n].y, TileSize, TileSize);
1279 r.layer = tiles_[n].zorder;
1280 solid_color_rects_.push_back(r);
1281 continue;
1282 }
1283
1284 layer_blit_info& blit_info = blit_cache_[tiles_[n].zorder];
1285
1286 tiles_[n].draw_disabled = false;
1287
1288 blit_info.blit_vertexes.resize(blit_info.blit_vertexes.size() + 4);
1289 const int npoints = level_object::calculate_tile_corners(&blit_info.blit_vertexes[blit_info.blit_vertexes.size() - 4], tiles_[n]);
1290 if(npoints == 0) {
1291 blit_info.blit_vertexes.resize(blit_info.blit_vertexes.size() - 4);
1292 } else {
1293 blit_info.vertex_texture_ids.push_back(tiles_[n].object->texture().get_id());
1294 if(blit_info.vertex_texture_ids.back() != blit_info.texture_id) {
1295 blit_info.texture_id = GLuint(-1);
1296 }
1297
1298 const int xtile = (tiles_[n].x - blit_info.xbase)/TileSize;
1299 const int ytile = (tiles_[n].y - blit_info.ybase)/TileSize;
1300 ASSERT_GE(xtile, 0);
1301 ASSERT_GE(ytile, 0);
1302 if(blit_info.indexes.size() <= ytile) {
1303 blit_info.indexes.resize(ytile+1);
1304 }
1305
1306 if(blit_info.indexes[ytile].size() <= xtile) {
1307 blit_info.indexes[ytile].resize(xtile+1, TILE_INDEX_TYPE_MAX);
1308 }
1309
1310 blit_info.indexes[ytile][xtile] = (blit_info.blit_vertexes.size() - 4) * (tiles_[n].object->is_opaque() ? 1 : -1);
1311 }
1312 }
1313
1314 for(int n = 1; n < solid_color_rects_.size(); ++n) {
1315 solid_color_rect& a = solid_color_rects_[n-1];
1316 solid_color_rect& b = solid_color_rects_[n];
1317 if(a.area.x() == b.area.x() && a.area.x2() == b.area.x2() && a.area.y() + a.area.h() == b.area.y() && a.layer == b.layer) {
1318 a.area = rect(a.area.x(), a.area.y(), a.area.w(), a.area.h() + b.area.h());
1319 b.area = rect(0,0,0,0);
1320 }
1321 }
1322
1323 solid_color_rects_.erase(std::remove_if(solid_color_rects_.begin(), solid_color_rects_.end(), solid_color_rect_empty()), solid_color_rects_.end());
1324
1325 //remove tiles that are obscured by other tiles.
1326 std::set<std::pair<int, int> > opaque;
1327 for(int n = tiles_.size(); n > 0; --n) {
1328 level_tile& t = tiles_[n-1];
1329 const tile_map& map = tile_maps_[t.zorder];
1330 if(map.x_speed() != 100 || map.y_speed() != 100) {
1331 while(n != 0 && tiles_[n-1].zorder == t.zorder) {
1332 --n;
1333 }
1334
1335 continue;
1336 }
1337
1338 if(!t.draw_disabled && opaque.count(std::pair<int,int>(t.x, t.y))) {
1339 t.draw_disabled = true;
1340 continue;
1341 }
1342
1343 if(t.object->is_opaque()) {
1344 opaque.insert(std::pair<int,int>(t.x, t.y));
1345 }
1346 }
1347
1348 }
1349
1350 namespace {
1351 bool sort_entity_drawing_pos(const entity_ptr& a, const entity_ptr& b) {
1352 return a->zorder() < b->zorder() ||
1353 a->zorder() == b->zorder() && a->y() < b->y() ||
1354 a->zorder() == b->zorder() && a->y() < b->y() ||
1355 a->zorder() == b->zorder() && a->y() == b->y() && a->x() < b->x();
1356 }
1357 }
1358
1359 void level::draw_status() const
1360 {
1361 if(gui_algorithm_) {
1362 gui_algorithm_->draw(*this);
1363 }
1364
1365 if(current_speech_dialog()) {
1366 current_speech_dialog()->draw();
1367 }
1368 }
1369
1370 extern std::vector<rect> background_rects_drawn;
1371
1372 void level::draw(int x, int y, int w, int h) const
1373 {
1374 ++draw_count;
1375
1376 const int start_x = x;
1377 const int start_y = y;
1378
1379 const int ticks = SDL_GetTicks();
1380 x -= widest_tile_;
1381 y -= highest_tile_;
1382 w += widest_tile_;
1383 h += highest_tile_;
1384
1385 const std::vector<entity_ptr>* chars_ptr = &active_chars_;
1386 std::vector<entity_ptr> editor_chars_buf;
1387 if(editor_) {
1388 //in the editor we draw all chars, not just active chars. We also
1389 //sort the chars by drawing order to make sure they are drawn in
1390 //the correct order.
1391 editor_chars_buf = chars_;
1392 std::sort(editor_chars_buf.begin(), editor_chars_buf.end(), sort_entity_drawing_pos);
1393 chars_ptr = &editor_chars_buf;
1394 }
1395
1396 const std::vector<entity_ptr>& chars = *chars_ptr;
1397
1398 std::vector<entity_ptr>::const_iterator entity_itor = chars.begin();
1399
1400 bool water_drawn = true;
1401 int water_zorder = 0;
1402 if(water_) {
1403 water_drawn = false;
1404 water_zorder = water_->zorder();
1405 }
1406
1407 std::set<int>::const_iterator layer = layers_.begin();
1408
1409 int player_zorder = 0;
1410 if(players_.empty() == false) {
1411 player_zorder = players_.front()->zorder();
1412 }
1413
1414 for(; layer != layers_.end() && *layer < player_zorder; ++layer) {
1415
1416 if(!water_drawn && *layer > water_zorder) {
1417 water_->draw(x, y, w, h);
1418 water_drawn = true;
1419 }
1420
1421 while(entity_itor != chars.end() && (*entity_itor)->zorder() <= *layer) {
1422 if(!(*entity_itor)->is_human()) {
1423 const std::pair<int,int>* scroll_speed = (*entity_itor)->position_scale_millis();
1424
1425 if(scroll_speed) {
1426 glPushMatrix();
1427 const int scrollx = scroll_speed->first;
1428 const int scrolly = scroll_speed->second;
1429
1430 const int diffx = ((scrollx - 1000)*x)/1000;
1431 const int diffy = ((scrolly - 1000)*y)/1000;
1432
1433 glTranslatef(diffx, diffy, 0.0);
1434 }
1435
1436 (*entity_itor)->draw();
1437 if(editor_) {
1438 (*entity_itor)->draw_group();
1439 }
1440
1441 if(scroll_speed) {
1442 glPopMatrix();
1443 }
1444 }
1445 ++entity_itor;
1446 }
1447
1448 draw_layer(*layer, x, y, w, h);
1449 }
1450
1451 foreach(const const_entity_ptr& p, players_) {
1452 p->draw();
1453 }
1454
1455 for(; layer != layers_.end(); ++layer) {
1456 if(!water_drawn && *layer > water_zorder) {
1457 water_->draw(x, y, w, h);
1458 water_drawn = true;
1459 }
1460
1461 while(entity_itor != chars.end() && (*entity_itor)->zorder() <= *layer) {
1462 if(!(*entity_itor)->is_human()) {
1463 (*entity_itor)->draw();
1464 if(editor_) {
1465 (*entity_itor)->draw_group();
1466 }
1467 }
1468 ++entity_itor;
1469 }
1470
1471 draw_layer(*layer, x, y, w, h);
1472 }
1473
1474 if(!water_drawn) {
1475 water_->draw(x, y, w, h);
1476 water_drawn = true;
1477 }
1478
1479 while(entity_itor != chars.end()) {
1480 if(!(*entity_itor)->is_human()) {
1481 (*entity_itor)->draw();
1482 if(editor_) {
1483
1484 (*entity_itor)->draw_group();
1485 }
1486 }
1487 ++entity_itor;
1488 }
1489
1490 if(editor_) {
1491 foreach(const entity_ptr& obj, chars_) {
1492 if(entity_collides_with_level(*this, *obj, MOVE_NONE)) {
1493 //if the entity is colliding with the level, then draw
1494 //it in red to mark as 'bad'.
1495 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1496 const GLfloat alpha = 0.5 + sin(draw_count/5.0)*0.5;
1497 glColor4f(1.0, 0.0, 0.0, alpha);
1498 obj->draw();
1499 glColor4f(1.0, 1.0, 1.0, 1.0);
1500 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1501 }
1502 }
1503 }
1504
1505 if(editor_highlight_ || !editor_selection_.empty()) {
1506 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1507 const GLfloat alpha = 0.5 + sin(draw_count/5.0)*0.5;
1508 glColor4f(1.0, 1.0, 1.0, alpha);
1509
1510 if(editor_highlight_) {
1511 editor_highlight_->draw();
1512 }
1513
1514 foreach(const entity_ptr& e, editor_selection_) {
1515 e->draw();
1516 }
1517
1518 glColor4f(1.0, 1.0, 1.0, 1.0);
1519 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1520 }
1521
1522 draw_debug_solid(x, y, w, h);
1523
1524 if(background_) {
1525 background_->draw_foreground(start_x, start_y, 0.0, cycle());
1526 }
1527 }
1528
1529 void level::draw_debug_solid(int x, int y, int w, int h) const
1530 {
1531 if(preferences::show_debug_hitboxes() == false) {
1532 return;
1533 }
1534
1535 const int tile_x = x/TileSize - 2;
1536 const int tile_y = y/TileSize - 2;
1537
1538 for(int xpos = 0; xpos < w/TileSize + 4; ++xpos) {
1539 for(int ypos = 0; ypos < h/TileSize + 4; ++ypos) {
1540 const tile_pos pos(tile_x + xpos, tile_y + ypos);
1541 const tile_solid_info* info = solid_.find(pos);
1542 if(info == NULL) {
1543 continue;
1544 }
1545
1546 const int xpixel = (tile_x + xpos)*TileSize;
1547 const int ypixel = (tile_y + ypos)*TileSize;
1548
1549 if(info->all_solid) {
1550 graphics::draw_rect(rect(xpixel, ypixel, TileSize, TileSize), info->damage ? graphics::color(255, 0, 0, 196) : graphics::color(255, 255, 255, 196));
1551 } else {
1552 std::vector<GLshort> v;
1553 glDisable(GL_TEXTURE_2D);
1554 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
1555 for(int suby = 0; suby != TileSize; ++suby) {
1556 for(int subx = 0; subx != TileSize; ++subx) {
1557 if(info->bitmap.test(suby*TileSize + subx)) {
1558 v.push_back(xpixel + subx + 1);
1559 v.push_back(ypixel + suby + 1);
1560 }
1561 }
1562 }
1563
1564 if(!v.empty()) {
1565 if(info->damage) {
1566 glColor4ub(255, 0, 0, 196);
1567 } else {
1568 glColor4ub(255, 255, 255, 196);
1569 }
1570
1571 glPointSize(1);
1572 glVertexPointer(2, GL_SHORT, 0, &v[0]);
1573 glDrawArrays(GL_POINTS, 0, v.size()/2);
1574 }
1575 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
1576 glEnable(GL_TEXTURE_2D);
1577 }
1578 }
1579 }
1580
1581 glColor4ub(255, 255, 255, 255);
1582 }
1583
1584 void level::draw_background(int x, int y, int rotation) const
1585 {
1586 if(show_background_ == false) {
1587 return;
1588 }
1589
1590 if(water_) {
1591 water_->begin_drawing();
1592 }
1593
1594 foreach(const entity_ptr& c, active_chars_) {
1595 c->setup_drawing();
1596 }
1597
1598 if(background_) {
1599 static std::vector<rect> opaque_areas;
1600 opaque_areas.clear();
1601 rect screen_area(x, y, graphics::screen_width(), graphics::screen_height());
1602 foreach(const rect& r, opaque_rects_) {
1603 if(rects_intersect(r, screen_area)) {
1604
1605 rect intersection = intersection_rect(r, screen_area);
1606
1607 if(intersection.w() == screen_area.w() || intersection.h() == screen_area.h()) {
1608 rect result[2];
1609 const int nrects = rect_difference(screen_area, intersection, result);
1610 ASSERT_LOG(nrects <= 2, "TOO MANY RESULTS " << nrects << " IN " << screen_area << " - " << intersection);
1611 if(nrects < 1) {
1612 //background is completely obscured, so return
1613 return;
1614 } else if(nrects == 1) {
1615 screen_area = result[0];
1616 } else {
1617 opaque_areas.push_back(intersection);
1618 }
1619 } else if(intersection.w()*intersection.h() >= TileSize*TileSize*8) {
1620 opaque_areas.push_back(intersection);
1621 }
1622 }
1623 }
1624
1625 background_->draw(x, y, screen_area, opaque_areas, rotation, cycle());
1626 } else {
1627 glClearColor(0.0, 0.0, 0.0, 0.0);
1628 glClear(GL_COLOR_BUFFER_BIT);
1629 }
1630 }
1631
1632 void level::process()
1633 {
1634 if(gui_algorithm_) {
1635 gui_algorithm_->process(*this);
1636 }
1637
1638 multiplayer::send_and_receive();
1639
1640 const int LevelPreloadFrequency = 500; //10 seconds
1641 //see if we have levels to pre-load. Load one periodically.
1642 if((cycle_%LevelPreloadFrequency) == 0) {
1643 const int index = cycle_/LevelPreloadFrequency;
1644 if(index < preloads_.size()) {
1645 preload_level(preloads_[index]);
1646 }
1647 }
1648
1649 controls::read_local_controls();
1650
1651 do_processing();
1652
1653 if(speech_dialogs_.empty() == false) {
1654 if(speech_dialogs_.front()->process()) {
1655 speech_dialogs_.pop();
1656 }
1657 }
1658 }
1659
1660 void level::process_draw()
1661 {
1662 foreach(const entity_ptr& e, active_chars_) {
1663 e->handle_event(OBJECT_EVENT_DRAW);
1664 }
1665 }
1666
1667 void level::do_processing()
1668 {
1669 if(cycle_ == 0) {
1670 const std::vector<entity_ptr> chars = chars_;
1671 foreach(const entity_ptr& e, chars) {
1672 e->handle_event(OBJECT_EVENT_START_LEVEL);
1673 }
1674 }
1675
1676 ++cycle_;
1677
1678 const int ticks = SDL_GetTicks();
1679 detect_user_collisions(*this);
1680 active_chars_.clear();
1681
1682 if(!player_) {
1683 return;
1684 }
1685
1686 const int screen_left = last_draw_position().x/100;
1687 const int screen_right = last_draw_position().x/100 + graphics::screen_width();
1688 const int screen_top = last_draw_position().y/100;
1689 const int screen_bottom = last_draw_position().y/100 + graphics::screen_height();
1690
1691 const rect screen_area(screen_left, screen_top, screen_right - screen_left, screen_bottom - screen_top);
1692
1693 foreach(entity_ptr& c, chars_) {
1694 const bool is_active = c->is_active(screen_area);
1695
1696 if(is_active) {
1697 if(c->group() >= 0) {
1698 assert(c->group() < groups_.size());
1699 const entity_group& group = groups_[c->group()];
1700 active_chars_.insert(active_chars_.end(), group.begin(), group.end());
1701 } else {
1702 active_chars_.push_back(c);
1703 }
1704 } else { //char is inactive
1705 if( c->dies_on_inactive() ){
1706 if(c->label().empty() == false) {
1707 chars_by_label_.erase(c->label());
1708 }
1709
1710 c = entity_ptr(); //can't delete it while iterating over the container, so we null it for later removal
1711 }
1712 }
1713 }
1714
1715 chars_.erase(std::remove(chars_.begin(), chars_.end(), entity_ptr()), chars_.end());
1716
1717 std::sort(active_chars_.begin(), active_chars_.end());
1718 active_chars_.erase(std::unique(active_chars_.begin(), active_chars_.end()), active_chars_.end());
1719 std::sort(active_chars_.begin(), active_chars_.end(), sort_entity_drawing_pos);
1720
1721 const int ActivationDistance = 700;
1722
1723 std::vector<entity_ptr> active_chars = active_chars_;
1724 if(time_freeze_ >= 1000) {
1725 time_freeze_ -= 1000;
1726 active_chars = chars_immune_from_time_freeze_;
1727 }
1728 foreach(const entity_ptr& c, active_chars) {
1729 if(!c->destroyed() || c->is_human()) {
1730 c->process(*this);
1731 }
1732
1733 if(c->destroyed() && !c->is_human()) {
1734 if(player_ && !c->respawn() && c->get_id() != -1) {
1735 player_->is_human()->object_destroyed(id(), c->get_id());
1736 }
1737
1738 erase_char(c);
1739 }
1740 }
1741
1742 if(water_) {
1743 water_->process(*this);
1744 }
1745
1746 solid_chars_.clear();
1747 }
1748
1749 void level::erase_char(entity_ptr c)
1750 {
1751
1752 if(c->label().empty() == false) {
1753 chars_by_label_.erase(c->label());
1754 }
1755 chars_.erase(std::remove(chars_.begin(), chars_.end(), c), chars_.end());
1756 if(c->group() >= 0) {
1757 assert(c->group() < groups_.size());
1758 entity_group& group = groups_[c->group()];
1759 group.erase(std::remove(group.begin(), group.end(), c), group.end());
1760 }
1761
1762 solid_chars_.clear();
1763 }
1764
1765 bool level::is_solid(const level_solid_map& map, const entity& e, const std::vector<point>& points, int* friction, int* traction, int* damage) const
1766 {
1767 const tile_solid_info* info = NULL;
1768 int prev_x = INT_MIN, prev_y = INT_MIN;
1769
1770 const frame& current_frame = e.current_frame();
1771
1772 for(std::vector<point>::const_iterator p = points.begin(); p != points.end(); ++p) {
1773 int x, y;
1774 if(prev_x != INT_MIN) {
1775 const int diff_x = (p->x - (p-1)->x) * (e.face_right() ? 1 : -1);
1776 const int diff_y = p->y - (p-1)->y;
1777
1778 x = prev_x + diff_x;
1779 y = prev_y + diff_y;
1780
1781 if(x < 0 || y < 0 || x >= TileSize || y >= TileSize) {
1782 //we need to recalculate the info, since we've stepped into
1783 //another tile.
1784 prev_x = INT_MIN;
1785 }
1786 }
1787
1788 if(prev_x == INT_MIN) {
1789 x = e.x() + (e.face_right() ? p->x : (current_frame.width() - 1 - p->x));
1790 y = e.y() + p->y;
1791
1792 tile_pos pos(x/TileSize, y/TileSize);
1793 x = x%TileSize;
1794 y = y%TileSize;
1795 if(x < 0) {
1796 pos.first--;
1797 x += 32;
1798 }
1799
1800 if(y < 0) {
1801 pos.second--;
1802 y += 32;
1803 }
1804
1805 info = map.find(pos);
1806 }
1807
1808 if(info != NULL) {
1809 if(info->all_solid) {
1810 if(friction) {
1811 *friction = info->friction;
1812 }
1813
1814 if(traction) {
1815 *traction = info->traction;
1816 }
1817
1818 if(damage) {
1819 *damage = info->damage;
1820 }
1821 return true;
1822 }
1823
1824 const int index = y*TileSize + x;
1825 if(info->bitmap.test(index)) {
1826 if(friction) {
1827 *friction = info->friction;
1828 }
1829
1830 if(traction) {
1831 *traction = info->traction;
1832 }
1833
1834 if(damage) {
1835 *damage = info->damage;
1836 }
1837 return true;
1838 }
1839 }
1840
1841 prev_x = x;
1842 prev_y = y;
1843 }
1844
1845 return false;
1846 }
1847
1848 bool level::is_solid(const level_solid_map& map, int x, int y, int* friction, int* traction, int* damage) const
1849 {
1850 tile_pos pos(x/TileSize, y/TileSize);
1851 x = x%TileSize;
1852 y = y%TileSize;
1853 if(x < 0) {
1854 pos.first--;
1855 x += 32;
1856 }
1857
1858 if(y < 0) {
1859 pos.second--;
1860 y += 32;
1861 }
1862
1863 const tile_solid_info* info = map.find(pos);
1864 if(info != NULL) {
1865 if(info->all_solid) {
1866 if(friction) {
1867 *friction = info->friction;
1868 }
1869
1870 if(traction) {
1871 *traction = info->traction;
1872 }
1873
1874 if(damage) {
1875 *damage = info->damage;
1876 }
1877 return true;
1878 }
1879
1880 const int index = y*TileSize + x;
1881 if(info->bitmap.test(index)) {
1882 if(friction) {
1883 *friction = info->friction;
1884 }
1885
1886 if(traction) {
1887 *traction = info->traction;
1888 }
1889
1890 if(damage) {
1891 *damage = info->damage;
1892 }
1893 return true;
1894 } else {
1895 return false;
1896 }
1897 }
1898
1899 return false;
1900 }
1901
1902 bool level::standable(int x, int y, int* friction, int* traction, int* damage) const
1903 {
1904 if(is_solid(solid_, x, y, friction, traction, damage) ||
1905 is_solid(standable_, x, y, friction, traction, damage)) {
1906 return true;
1907 }
1908
1909 return false;
1910 }
1911
1912 bool level::standable_tile(int x, int y, int* friction, int* traction, int* damage) const
1913 {
1914 if(is_solid(solid_, x, y, friction, traction, damage) ||
1915 is_solid(standable_, x, y, friction, traction, damage)) {
1916 return true;
1917 }
1918
1919 return false;
1920 }
1921
1922
1923 bool level::solid(int x, int y, int* friction, int* traction, int* damage) const
1924 {
1925 return is_solid(solid_, x, y, friction, traction, damage);
1926 }
1927
1928 bool level::solid(const entity& e, const std::vector<point>& points, int* friction, int* traction, int* damage) const
1929 {
1930 return is_solid(solid_, e, points, friction, traction, damage);
1931 }
1932
1933 bool level::solid(const rect& r, int* friction, int* traction, int* damage) const
1934 {
1935 //TODO: consider optimizing this function.
1936 const int ybegin = r.y();
1937 const int yend = r.y2();
1938 const int xbegin = r.x();
1939 const int xend = r.x2();
1940
1941 for(int y = ybegin; y != yend; ++y) {
1942 for(int x = xbegin; x != xend; ++x) {
1943 if(solid(x, y, friction, traction, damage)) {
1944 return true;
1945 }
1946 }
1947 }
1948
1949 return false;
1950 }
1951
1952 bool level::may_be_solid_in_rect(const rect& r) const
1953 {
1954 int x = r.x();
1955 int y = r.y();
1956 tile_pos pos(x/TileSize, y/TileSize);
1957 x = x%TileSize;
1958 y = y%TileSize;
1959 if(x < 0) {
1960 pos.first--;
1961 x += 32;
1962 }
1963
1964 if(y < 0) {
1965 pos.second--;
1966 y += 32;
1967 }
1968
1969 const int x2 = (x + r.w())/TileSize + ((x + r.w())%TileSize ? 1 : 0);
1970 const int y2 = (y + r.h())/TileSize + ((y + r.h())%TileSize ? 1 : 0);
1971
1972 for(int ypos = 0; ypos < y2; ++ypos) {
1973 for(int xpos = 0; xpos < x2; ++xpos) {
1974 if(solid_.find(tile_pos(pos.first + xpos, pos.second + ypos))) {
1975 return true;
1976 }
1977 }
1978 }
1979
1980 return false;
1981 }
1982
1983 void level::set_solid_area(const rect& r, bool solid)
1984 {
1985 for(int y = r.y(); y < r.y2(); ++y) {
1986 for(int x = r.x(); x < r.x2(); ++x) {
1987 set_solid(solid_, x, y, 0, 0, 0, solid);
1988 }
1989 }
1990 }
1991
1992 entity_ptr level::board(int x, int y) const
1993 {
1994 for(std::vector<entity_ptr>::const_iterator i = active_chars_.begin();
1995 i != active_chars_.end(); ++i) {
1996 const entity_ptr& c = *i;
1997 if(c->boardable_vehicle() && c->point_collides(x, y)) {
1998 return c;
1999 }
2000 }
2001
2002 return entity_ptr();
2003 }
2004
2005 void level::add_tile(const level_tile& t)
2006 {
2007 std::vector<level_tile>::iterator itor = std::lower_bound(tiles_.begin(), tiles_.end(), t, level_tile_zorder_comparer());
2008 tiles_.insert(itor, t);
2009 add_tile_solid(t);
2010 layers_.insert(t.zorder);
2011 prepare_tiles_for_drawing();
2012 }
2013
2014 void level::add_tile_rect(int zorder, int x1, int y1, int x2, int y2, const std::string& str)
2015 {
2016 add_tile_rect_vector(zorder, x1, y1, x2, y2, std::vector<std::string>(1, str));
2017 }
2018
2019 void level::add_tile_rect_vector(int zorder, int x1, int y1, int x2, int y2, const std::vector<std::string>& tiles)
2020 {
2021 if(x1 > x2) {
2022 std::swap(x1, x2);
2023 }
2024
2025 if(y1 > y2) {
2026 std::swap(y1, y2);
2027 }
2028 add_tile_rect_vector_internal(zorder, x1, y1, x2, y2, tiles);
2029 }
2030
2031 void level::set_tile_layer_speed(int zorder, int x_speed, int y_speed)
2032 {
2033 tile_map& m = tile_maps_[zorder];
2034 m.set_zorder(zorder);
2035 m.set_speed(x_speed, y_speed);
2036 }
2037
2038 void level::refresh_tile_rect(int x1, int y1, int x2, int y2)
2039 {
2040 rebuild_tiles_rect(rect(x1-128, y1-128, (x2 - x1) + 256, (y2 - y1) + 256));
2041 }
2042
2043 namespace {
2044 int round_tile_size(int n)
2045 {
2046 if(n >= 0) {
2047 return n - n%TileSize;
2048 } else {
2049 n = -n + 32;
2050 return -(n - n%TileSize);
2051 }
2052 }
2053
2054 }
2055
2056 bool level::add_tile_rect_vector_internal(int zorder, int x1, int y1, int x2, int y2, const std::vector<std::string>& tiles)
2057 {
2058 if(tiles.empty()) {
2059 return false;
2060 }
2061
2062 if(x1 > x2) {
2063 std::swap(x1, x2);
2064 }
2065
2066 if(y1 > y2) {
2067 std::swap(y1, y2);
2068 }
2069
2070 x1 = round_tile_size(x1);
2071 y1 = round_tile_size(y1);
2072 x2 = round_tile_size(x2 + TileSize);
2073 y2 = round_tile_size(y2 + TileSize);
2074
2075 tile_map& m = tile_maps_[zorder];
2076 m.set_zorder(zorder);
2077
2078 bool changed = false;
2079
2080 int index = 0;
2081 for(int x = x1; x < x2; x += 32) {
2082 for(int y = y1; y < y2; y += 32) {
2083 changed = m.set_tile(x, y, tiles[index]) || changed;
2084 if(index+1 < tiles.size()) {
2085 ++index;
2086 }
2087 }
2088 }
2089
2090 return changed;
2091 }
2092
2093 void level::get_tile_rect(int zorder, int x1, int y1, int x2, int y2, std::vector<std::string>& tiles) const
2094 {
2095 if(x1 > x2) {
2096 std::swap(x1, x2);
2097 }
2098
2099 if(y1 > y2) {
2100 std::swap(y1, y2);
2101 }
2102
2103 x1 = round_tile_size(x1);
2104 y1 = round_tile_size(y1);
2105 x2 = round_tile_size(x2 + TileSize);
2106 y2 = round_tile_size(y2 + TileSize);
2107
2108 std::map<int, tile_map>::const_iterator map_iterator = tile_maps_.find(zorder);
2109 if(map_iterator == tile_maps_.end()) {
2110 tiles.push_back("");
2111 return;
2112 }
2113 const tile_map& m = map_iterator->second;
2114
2115 for(int x = x1; x < x2; x += 32) {
2116 for(int y = y1; y < y2; y += 32) {
2117 tiles.push_back(m.get_tile_from_pixel_pos(x, y));
2118 }
2119 }
2120 }
2121
2122 void level::get_all_tiles_rect(int x1, int y1, int x2, int y2, std::map<int, std::vector<std::string> >& tiles) const
2123 {
2124 for(std::set<int>::const_iterator i = layers_.begin(); i != layers_.end(); ++i) {
2125 if(hidden_layers_.count(*i)) {
2126 continue;
2127 }
2128
2129 std::vector<std::string> cleared;
2130 get_tile_rect(*i, x1, y1, x2, y2, cleared);
2131 if(std::count(cleared.begin(), cleared.end(), "") != cleared.size()) {
2132 tiles[*i].swap(cleared);
2133 }
2134 }
2135 }
2136
2137 void level::clear_tile_rect(int x1, int y1, int x2, int y2)
2138 {
2139 if(x1 > x2) {
2140 std::swap(x1, x2);
2141 }
2142
2143 if(y1 > y2) {
2144 std::swap(y1, y2);
2145 }
2146
2147 bool changed = false;
2148 std::vector<std::string> v(1, "");
2149 for(std::set<int>::const_iterator i = layers_.begin(); i != layers_.end(); ++i) {
2150 if(hidden_layers_.count(*i)) {
2151 continue;
2152 }
2153
2154 if(add_tile_rect_vector_internal(*i, x1, y1, x2, y2, v)) {
2155 changed = true;
2156 }
2157 }
2158 }
2159
2160 void level::add_tile_solid(const level_tile& t)
2161 {
2162 //zorders greater than 1000 are considered in the foreground and so
2163 //have no solids.
2164 if(t.zorder >= 1000) {
2165 return;
2166 }
2167
2168 if(t.object->width() > widest_tile_) {
2169 widest_tile_ = t.object->width();
2170 }
2171
2172 if(t.object->height() > highest_tile_) {
2173 highest_tile_ = t.object->height();
2174 }
2175
2176 const const_level_object_ptr& obj = t.object;
2177 if(obj->all_solid()) {
2178 add_solid_rect(t.x, t.y, t.x + obj->width(), t.y + obj->height(), obj->friction(), obj->traction(), obj->damage());
2179 return;
2180 }
2181
2182 if(obj->has_solid()) {
2183 for(int y = 0; y != obj->height(); ++y) {
2184 for(int x = 0; x != obj->width(); ++x) {
2185 int xpos = x;
2186 if(!t.face_right) {
2187 xpos = obj->width() - x - 1;
2188 }
2189 if(obj->is_solid(xpos, y)) {
2190 if(obj->is_passthrough()) {
2191 add_standable(t.x + x, t.y + y, obj->friction(), obj->traction(), obj->damage());
2192 } else {
2193 add_solid(t.x + x, t.y + y, obj->friction(), obj->traction(), obj->damage());
2194 }
2195 }
2196 }
2197 }
2198 }
2199 }
2200
2201 struct tile_on_point {
2202 int x_, y_;
2203 tile_on_point(int x, int y) : x_(x), y_(y)
2204 {}
2205
2206 bool operator()(const level_tile& t) const {
2207 return x_ >= t.x && y_ >= t.y && x_ < t.x + t.object->width() && y_ < t.y + t.object->height();
2208 }
2209 };
2210
2211 void level::remove_tiles_at(int x, int y)
2212 {
2213 tiles_.erase(std::remove_if(tiles_.begin(), tiles_.end(), tile_on_point(x,y)), tiles_.end());
2214 prepare_tiles_for_drawing();
2215 }
2216
2217 std::vector<point> level::get_solid_contiguous_region(int xpos, int ypos) const
2218 {
2219 std::vector<point> result;
2220
2221 xpos = round_tile_size(xpos);
2222 ypos = round_tile_size(ypos);
2223
2224 tile_pos base(xpos/TileSize, ypos/TileSize);
2225 const tile_solid_info* info = solid_.find(base);
2226 if(info == NULL || info->all_solid == false && info->bitmap.any() == false) {
2227 return result;
2228 }
2229
2230 std::set<tile_pos> positions;
2231 positions.insert(base);
2232
2233 int last_count = -1;
2234 while(positions.size() != last_count) {
2235 last_count = positions.size();
2236
2237 std::vector<tile_pos> new_positions;
2238 foreach(const tile_pos& pos, positions) {
2239 new_positions.push_back(std::make_pair(pos.first-1, pos.second));
2240 new_positions.push_back(std::make_pair(pos.first+1, pos.second));
2241 new_positions.push_back(std::make_pair(pos.first, pos.second-1));
2242 new_positions.push_back(std::make_pair(pos.first, pos.second+1));
2243 }
2244
2245 foreach(const tile_pos& pos, new_positions) {
2246 if(positions.count(pos)) {
2247 continue;
2248 }
2249
2250 const tile_solid_info* info = solid_.find(pos);
2251 if(info == NULL || info->all_solid == false && info->bitmap.any() == false) {
2252 continue;
2253 }
2254
2255 positions.insert(pos);
2256 }
2257 }
2258
2259 foreach(const tile_pos& pos, positions) {
2260 result.push_back(point(pos.first, pos.second));
2261 }
2262
2263 return result;
2264 }
2265
2266 const level_tile* level::get_tile_at(int x, int y) const
2267 {
2268 std::vector<level_tile>::const_iterator i = std::find_if(tiles_.begin(), tiles_.end(), tile_on_point(x,y));
2269 if(i != tiles_.end()) {
2270 return &*i;
2271 } else {
2272 return NULL;
2273 }
2274 }
2275
2276 void level::remove_character(entity_ptr e)
2277 {
2278 std::cerr << "removing char: '" << e->label() << "'\n";
2279 if(e->label().empty() == false) {
2280 chars_by_label_.erase(e->label());
2281 }
2282 chars_.erase(std::remove(chars_.begin(), chars_.end(), e), chars_.end());
2283 std::cerr << "removed char: '" << e->label() << "'\n";
2284 }
2285
2286 std::vector<entity_ptr> level::get_characters_in_rect(const rect& r) const
2287 {
2288 std::vector<entity_ptr> res;
2289 foreach(entity_ptr c, chars_) {
2290 if(point_in_rect(point(c->x(), c->y()), r)) {
2291 res.push_back(c);
2292 }
2293 }
2294
2295 return res;
2296 }
2297
2298 entity_ptr level::get_character_at_point(int x, int y) const
2299 {
2300 foreach(entity_ptr c, chars_) {
2301 const int xpos = x - c->x();
2302 const int ypos = y - c->y();
2303 if(!c->is_alpha(x, y)) {
2304 return c;
2305 }
2306 }
2307
2308 return entity_ptr();
2309 }
2310
2311 void level::add_solid_rect(int x1, int y1, int x2, int y2, int friction, int traction, int damage)
2312 {
2313 if((x1%TileSize) != 0 || (y1%TileSize) != 0 ||
2314 (x2%TileSize) != 0 || (y2%TileSize) != 0) {
2315 for(int y = y1; y < y2; ++y) {
2316 for(int x = x1; x < x2; ++x) {
2317 add_solid(x, y, friction, traction, damage);
2318 }
2319 }
2320
2321 return;
2322 }
2323
2324 for(int y = y1; y < y2; y += TileSize) {
2325 for(int x = x1; x < x2; x += TileSize) {
2326 tile_pos pos(x/TileSize, y/TileSize);
2327 tile_solid_info& s = solid_.insert_or_find(pos);
2328 s.all_solid = true;
2329 s.friction = friction;
2330 s.traction = traction;
2331
2332 if(s.damage >= 0) {
2333 s.damage = std::min(s.damage, damage);
2334 } else {
2335 s.damage = damage;
2336 }
2337 }
2338 }
2339 }
2340
2341 void level::add_solid(int x, int y, int friction, int traction, int damage)
2342 {
2343 set_solid(solid_, x, y, friction, traction, damage);
2344 }
2345
2346 void level::add_standable(int x, int y, int friction, int traction, int damage)
2347 {
2348 set_solid(standable_, x, y, friction, traction, damage);
2349 }
2350
2351 void level::set_solid(level_solid_map& map, int x, int y, int friction, int traction, int damage, bool solid)
2352 {
2353 tile_pos pos(x/TileSize, y/TileSize);
2354 x = x%TileSize;
2355 y = y%TileSize;
2356 if(x < 0) {
2357 pos.first--;
2358 x += 32;
2359 }
2360
2361 if(y < 0) {
2362 pos.second--;
2363 y += 32;
2364 }
2365 const int index = y*TileSize + x;
2366 tile_solid_info& info = map.insert_or_find(pos);
2367
2368 if(info.damage >= 0) {
2369 info.damage = std::min(info.damage, damage);
2370 } else {
2371 info.damage = damage;
2372 }
2373
2374 if(solid) {
2375 info.friction = friction;
2376 info.traction = traction;
2377 info.bitmap.set(index);
2378 } else {
2379 if(info.all_solid) {
2380 info.all_solid = false;
2381 info.bitmap.set();
2382 }
2383
2384 info.bitmap.reset(index);
2385 }
2386 }
2387
2388 void level::add_player(entity_ptr p)
2389 {
2390 chars_.erase(std::remove(chars_.begin(), chars_.end(), player_), chars_.end());
2391 last_touched_player_ = player_ = p;
2392 if(players_.empty()) {
2393 player_->get_player_info()->set_player_slot(players_.size());
2394 players_.push_back(player_);
2395 } else {
2396 player_->get_player_info()->set_player_slot(0);
2397 players_[0] = player_;
2398 }
2399
2400 assert(player_);
2401 chars_.push_back(p);
2402
2403 //remove objects that have already been destroyed
2404 const std::vector<int>& destroyed_objects = player_->get_player_info()->get_objects_destroyed(id());
2405 for(int n = 0; n != chars_.size(); ++n) {
2406 if(chars_[n]->respawn() == false && std::binary_search(destroyed_objects.begin(), destroyed_objects.end(), chars_[n]->get_id())) {
2407 std::cerr << "removing character: " << n << ": " << chars_[n]->get_id() << "\n";
2408 if(chars_[n]->label().empty() == false) {
2409 chars_by_label_.erase(chars_[n]->label());
2410 }
2411 chars_[n] = entity_ptr();
2412 }
2413 }
2414
2415 chars_.erase(std::remove(chars_.begin(), chars_.end(), entity_ptr()), chars_.end());
2416 }
2417
2418 void level::add_character(entity_ptr p)
2419 {
2420 if(solid_chars_.empty() == false && p->solid()) {
2421 solid_chars_.push_back(p);
2422 }
2423
2424 if(p->label().empty() == false) {
2425 chars_by_label_[p->label()] = p;
2426 }
2427
2428 if(p->is_human()) {
2429 add_player(p);
2430 } else {
2431 chars_.push_back(p);
2432 }
2433
2434 layers_.insert(p->zorder());
2435 }
2436
2437 void level::force_enter_portal(const portal& p)
2438 {
2439 entered_portal_active_ = true;
2440 entered_portal_ = p;
2441 }
2442
2443 const level::portal* level::get_portal() const
2444 {
2445 if(entered_portal_active_) {
2446 entered_portal_active_ = false;
2447 return &entered_portal_;
2448 }
2449
2450 if(!player_) {
2451 return NULL;
2452 }
2453
2454 const rect& r = player_->body_rect();
2455 if(r.x() < boundaries().x() && left_portal_.level_dest.empty() == false) {
2456 return &left_portal_;
2457 }
2458
2459 if(r.x2() > boundaries().x2() && right_portal_.level_dest.empty() == false) {
2460 return &right_portal_;
2461 }
2462 foreach(const portal& p, portals_) {
2463 if(rects_intersect(r, p.area) && (p.automatic || player_->enter())) {
2464 return &p;
2465 }
2466 }
2467
2468 return NULL;
2469 }
2470
2471 int level::group_size(int group) const
2472 {
2473 int res = 0;
2474 foreach(const entity_ptr& c, active_chars_) {
2475 if(c->group() == group) {
2476 ++res;
2477 }
2478 }
2479
2480 return res;
2481 }
2482
2483 void level::set_character_group(entity_ptr c, int group_num)
2484 {
2485 assert(group_num < groups_.size());
2486
2487 //remove any current grouping
2488 if(c->group() >= 0) {
2489 assert(c->group() < groups_.size());
2490 entity_group& group = groups_[c->group()];
2491 group.erase(std::remove(group.begin(), group.end(), c), group.end());
2492 }
2493
2494 c->set_group(group_num);
2495
2496 if(group_num >= 0) {
2497 entity_group& group = groups_[group_num];
2498 group.push_back(c);
2499 }
2500 }
2501
2502 int level::add_group()
2503 {
2504 groups_.resize(groups_.size() + 1);
2505 return groups_.size() - 1;
2506 }
2507
2508 void level::editor_select_object(entity_ptr c)
2509 {
2510 if(!c) {
2511 return;
2512 }
2513 editor_selection_.push_back(c);
2514 }
2515
2516 void level::editor_clear_selection()
2517 {
2518 editor_selection_.clear();
2519 }
2520
2521 const std::string& level::get_background_id() const
2522 {
2523 if(background_) {
2524 return background_->id();
2525 } else {
2526 static const std::string empty_string;
2527 return empty_string;
2528 }
2529 }
2530
2531 void level::set_background_by_id(const std::string& id)
2532 {
2533 background_ = background::get(id, background_palette_);
2534 }
2535
2536 namespace {
2537
2538 const std::string LevelProperties[] = {
2539 "cycle", "player", "in_dialog",
2540 "local_player", "num_active", "active_chars", "chars",
2541 "in_editor", "zoom", "focus", "gui", "id",
2542 };
2543
2544 enum LEVEL_PROPERTY_ID {
2545 LEVEL_CYCLE, LEVEL_PLAYER, LEVEL_IN_DIALOG,
2546 LEVEL_LOCAL_PLAYER, LEVEL_NUM_ACTIVE,
2547 LEVEL_ACTIVE_CHARS, LEVEL_CHARS, LEVEL_IN_EDITOR, LEVEL_ZOOM,
2548 LEVEL_FOCUS, LEVEL_GUI, LEVEL_ID
2549 };
2550 }
2551
2552 const game_logic::formula_callable_definition& level::get_formula_definition()
2553 {
2554 static game_logic::formula_callable_definition_ptr result = game_logic::create_formula_callable_definition(LevelProperties, LevelProperties + sizeof(LevelProperties)/sizeof(*LevelProperties));
2555 return *result;
2556 }
2557
2558 variant level::get_value_by_slot(int slot) const
2559 {
2560 switch(slot) {
2561 case LEVEL_CYCLE:
2562 return variant(cycle_);
2563 case LEVEL_PLAYER:
2564 return variant(last_touched_player_.get());
2565 case LEVEL_IN_DIALOG:
2566 return variant(in_dialog_);
2567 case LEVEL_LOCAL_PLAYER:
2568 return variant(player_.get());
2569 case LEVEL_NUM_ACTIVE:
2570 return variant(active_chars_.size());
2571 case LEVEL_ACTIVE_CHARS: {
2572 std::vector<variant> v;
2573 foreach(const entity_ptr& e, active_chars_) {
2574 v.push_back(variant(e.get()));
2575 }
2576
2577 return variant(&v);
2578 }
2579 case LEVEL_CHARS: {
2580 std::vector<variant> v;
2581 foreach(const entity_ptr& e, active_chars_) {
2582 v.push_back(variant(e.get()));
2583 }
2584
2585 return variant(&v);
2586 }
2587 case LEVEL_IN_EDITOR:
2588 return variant(editor_);
2589 case LEVEL_ZOOM:
2590 return variant(zoom_level_);
2591 case LEVEL_FOCUS: {
2592 std::vector<variant> v;
2593 foreach(const entity_ptr& e, focus_override_) {
2594 v.push_back(variant(e.get()));
2595 }
2596
2597 return variant(&v);
2598 }
2599 case LEVEL_GUI: {
2600 if(gui_algorithm_) {
2601 return variant(gui_algorithm_->get_object());
2602 } else {
2603 return variant();
2604 }
2605 }
2606 case LEVEL_ID: {
2607 return variant(id_);
2608 }
2609 }
2610
2611 ASSERT_LOG(false, "BAD SLOT IN GET_VALUE FROM LEVEL " << slot);
2612 return variant();
2613 }
2614
2615 variant level::get_value(const std::string& key) const
2616 {
2617 if(key == "cycle") {
2618 return variant(cycle_);
2619 } else if(key == "player") {
2620 return variant(last_touched_player_.get());
2621 } else if(key == "in_dialog") {
2622 return variant(in_dialog_);
2623 } else if(key == "local_player") {
2624 return variant(player_.get());
2625 } else if(key == "num_active") {
2626 return variant(active_chars_.size());
2627 } else if(key == "active_chars") {
2628 std::vector<variant> v;
2629 foreach(const entity_ptr& e, active_chars_) {
2630 v.push_back(variant(e.get()));
2631 }
2632
2633 return variant(&v);
2634 } else if(key == "chars") {
2635 std::vector<variant> v;
2636 foreach(const entity_ptr& e, chars_) {
2637 v.push_back(variant(e.get()));
2638 }
2639
2640 return variant(&v);
2641 } else if(key == "in_editor") {
2642 return variant(editor_);
2643 } else if(key == "zoom") {
2644 return variant(zoom_level_);
2645 } else if(key == "focus") {
2646 std::vector<variant> v;
2647 foreach(const entity_ptr& e, focus_override_) {
2648 v.push_back(variant(e.get()));
2649 }
2650
2651 return variant(&v);
2652 } else if(key == "gui") {
2653 if(gui_algorithm_) {
2654 return variant(gui_algorithm_->get_object());
2655 } else {
2656 return variant();
2657 }
2658 } else if(key == "time_freeze") {
2659 return variant(time_freeze_);
2660 } else if(key == "chars_immune_from_freeze") {
2661 std::vector<variant> v;
2662 foreach(const entity_ptr& e, chars_immune_from_time_freeze_) {
2663 v.push_back(variant(e.get()));
2664 }
2665
2666 return variant(&v);
2667 } else if(key == "id") {
2668 return variant(id());
2669 } else {
2670 const_entity_ptr e = get_entity_by_label(key);
2671 if(e) {
2672 return variant(e.get());
2673 }
2674
2675 std::map<std::string, variant>::const_iterator i = vars_.find(key);
2676 if(i != vars_.end()) {
2677 return i->second;
2678 }
2679
2680 return variant();
2681 }
2682 }
2683
2684 void level::set_value(const std::string& key, const variant& value)
2685 {
2686 if(key == "lock_screen") {
2687 if(value.is_list()) {
2688 lock_screen_.reset(new point(value[0].as_int(), value[1].as_int()));
2689 } else {
2690 lock_screen_.reset();
2691 }
2692 } else if(key == "zoom") {
2693 zoom_level_ = value.as_int();
2694 } else if(key == "focus") {
2695 focus_override_.clear();
2696 for(int n = 0; n != value.num_elements(); ++n) {
2697 entity* e = value[n].try_convert<entity>();
2698 if(e) {
2699 focus_override_.push_back(entity_ptr(e));
2700 }
2701 }
2702
2703 return;
2704 } else if(key == "in_dialog") {
2705 in_dialog_ = value.as_bool();
2706 } else if(key == "time_freeze") {
2707 time_freeze_ = value.as_int();
2708 } else if(key == "chars_immune_from_freeze") {
2709 chars_immune_from_time_freeze_.clear();
2710 for(int n = 0; n != value.num_elements(); ++n) {
2711 entity_ptr e(value[n].try_convert<entity>());
2712 if(e) {
2713 chars_immune_from_time_freeze_.push_back(e);
2714 }
2715 }
2716 } else {
2717 vars_[key] = value;
2718 }
2719 }
2720
2721 int level::camera_rotation() const
2722 {
2723 if(!camera_rotation_) {
2724 return 0;
2725 }
2726
2727 return camera_rotation_->execute(*this).as_int();
2728 }
2729
2730 bool level::is_underwater(const rect& r, rect* res_water_area, variant* v) const
2731 {
2732 return water_ && water_->is_underwater(r, res_water_area, v);
2733 }
2734
2735 void level::get_current(const entity& e, int* velocity_x, int* velocity_y) const
2736 {
2737 if(e.mass() == 0) {
2738 return;
2739 }
2740
2741 int delta_x = 0, delta_y = 0;
2742 if(is_underwater(e.body_rect())) {
2743 delta_x += *velocity_x;
2744 delta_y += *velocity_y;
2745 water_->get_current(e, &delta_x, &delta_y);
2746 delta_x -= *velocity_x;
2747 delta_y -= *velocity_y;
2748 }
2749
2750 delta_x /= e.mass();
2751 delta_y /= e.mass();
2752
2753 foreach(const entity_ptr& c, active_chars_) {
2754 if(c.get() != &e) {
2755 delta_x += *velocity_x;
2756 delta_y += *velocity_y;
2757 c->generate_current(e, &delta_x, &delta_y);
2758 delta_x -= *velocity_x;
2759 delta_y -= *velocity_y;
2760 }
2761 }
2762
2763 *velocity_x += delta_x;
2764 *velocity_y += delta_y;
2765 }
2766
2767 water& level::get_or_create_water()
2768 {
2769 if(!water_) {
2770 water_.reset(new water);
2771 }
2772
2773 return *water_;
2774 }
2775
2776 entity_ptr level::get_entity_by_label(const std::string& label)
2777 {
2778 std::map<std::string, entity_ptr>::iterator itor = chars_by_label_.find(label);
2779 if(itor != chars_by_label_.end()) {
2780 return itor->second;
2781 }
2782
2783 return entity_ptr();
2784 }
2785
2786 const_entity_ptr level::get_entity_by_label(const std::string& label) const
2787 {
2788 std::map<std::string, entity_ptr>::const_iterator itor = chars_by_label_.find(label);
2789 if(itor != chars_by_label_.end()) {
2790 return itor->second;
2791 }
2792
2793 return const_entity_ptr();
2794 }
2795
2796 void level::get_all_labels(std::vector<std::string>& labels) const
2797 {
2798 for(std::map<std::string, entity_ptr>::const_iterator i = chars_by_label_.begin(); i != chars_by_label_.end(); ++i) {
2799 labels.push_back(i->first);
2800 }
2801 }
2802
2803 const std::vector<entity_ptr>& level::get_solid_chars() const
2804 {
2805 if(solid_chars_.empty()) {
2806 foreach(const entity_ptr& e, chars_) {
2807 if(e->solid() || e->platform()) {
2808 solid_chars_.push_back(e);
2809 }
2810 }
2811 }
2812
2813 return solid_chars_;
2814 }
2815
2816 void level::begin_movement_script(const std::string& key, entity& e)
2817 {
2818 std::map<std::string, movement_script>::const_iterator itor = movement_scripts_.find(key);
2819 if(itor == movement_scripts_.end()) {
2820 return;
2821 }
2822
2823 active_movement_scripts_.push_back(itor->second.begin_execution(e));
2824 }
2825
2826 void level::end_movement_script()
2827 {
2828 if(!active_movement_scripts_.empty()) {
2829 active_movement_scripts_.pop_back();
2830 }
2831 }
2832
2833 bool level::can_interact(const rect& body) const
2834 {
2835 foreach(const portal& p, portals_) {
2836 if(p.automatic == false && rects_intersect(body, p.area)) {
2837 return true;
2838 }
2839 }
2840
2841 foreach(const entity_ptr& c, active_chars_) {
2842 if(c->can_interact_with() && rects_intersect(body, c->body_rect()) &&
2843 intersection_rect(body, c->body_rect()).w() >= std::min(body.w(), c->body_rect().w())/2) {
2844 return true;
2845 }
2846 }
2847
2848 return false;
2849 }
2850
2851 void level::replay_from_cycle(int ncycle)
2852 {
2853 const int cycles_ago = cycle_ - ncycle;
2854 if(cycles_ago <= 0) {
2855 return;
2856 }
2857
2858 int index = static_cast<int>(backups_.size()) - cycles_ago;
2859 ASSERT_GE(index, 0);
2860
2861 const int cycle_to_play_until = cycle_;
2862 restore_from_backup(*backups_[index]);
2863 ASSERT_EQ(cycle_, ncycle);
2864 backups_.erase(backups_.begin() + index, backups_.end());
2865 while(cycle_ < cycle_to_play_until) {
2866 backup();
2867 do_processing();
2868 }
2869 }
2870
2871 void level::backup()
2872 {
2873 std::map<entity_ptr, entity_ptr> entity_map;
2874
2875 std::cerr << "BACKUP " << cycle_ << ": ";
2876 backup_snapshot_ptr snapshot(new backup_snapshot);
2877 snapshot->rng_seed = rng::get_seed();
2878 snapshot->cycle = cycle_;
2879 snapshot->chars.reserve(chars_.size());
2880 foreach(const entity_ptr& e, chars_) {
2881 std::cerr << e->debug_description() << "(" << (e->is_human() ? "HUMAN," : "") << e->centi_x() << "," << e->centi_y() << "):";
2882 snapshot->chars.push_back(e->backup());
2883 entity_map[e] = snapshot->chars.back();
2884
2885 if(snapshot->chars.back()->is_human()) {
2886 snapshot->players.push_back(snapshot->chars.back());
2887 if(e == player_) {
2888 snapshot->player = snapshot->players.back();
2889 }
2890 }
2891 }
2892
2893 foreach(const entity_ptr& e, snapshot->chars) {
2894 e->map_entities(entity_map);
2895 }
2896
2897 std::cerr << "\n";
2898
2899 snapshot->last_touched_player = last_touched_player_;
2900
2901 backups_.push_back(snapshot);
2902 if(backups_.size() > 300) {
2903 backups_.erase(backups_.begin(), backups_.begin() + 100);
2904 }
2905 }
2906
2907 void level::restore_from_backup(backup_snapshot& snapshot)
2908 {
2909 rng::set_seed(snapshot.rng_seed);
2910 cycle_ = snapshot.cycle;
2911 chars_ = snapshot.chars;
2912 players_ = snapshot.players;
2913 player_ = snapshot.player;
2914 last_touched_player_ = snapshot.last_touched_player;
2915
2916 solid_chars_.clear();
2917
2918 chars_by_label_.clear();
2919 foreach(const entity_ptr& e, chars_) {
2920 if(e->label().empty() == false) {
2921 chars_by_label_[e->label()] = e;
2922 }
2923 }
2924 }
2925
2926 void level::get_tile_layers(std::set<int>* all_layers, std::set<int>* hidden_layers)
2927 {
2928 if(all_layers) {
2929 foreach(const level_tile& t, tiles_) {
2930 all_layers->insert(t.zorder);
2931 }
2932 }
2933
2934 if(hidden_layers) {
2935 *hidden_layers = hidden_layers_;
2936 }
2937 }
2938
2939 void level::hide_tile_layer(int layer, bool is_hidden)
2940 {
2941 if(is_hidden) {
2942 hidden_layers_.insert(layer);
2943 } else {
2944 hidden_layers_.erase(layer);
2945 }
2946 }
2947
2948 void level::editor_freeze_tile_updates(bool value)
2949 {
2950 if(value) {
2951 ++editor_tile_updates_frozen_;
2952 } else {
2953 --editor_tile_updates_frozen_;
2954 if(editor_tile_updates_frozen_ == 0) {
2955 rebuild_tiles();
2956 }
2957 }
2958 }
2959
2960 void level::add_speech_dialog(boost::shared_ptr<speech_dialog> d)
2961 {
2962 speech_dialogs_.push(d);
2963 }
2964
2965 boost::shared_ptr<const speech_dialog> level::current_speech_dialog() const
2966 {
2967 if(speech_dialogs_.empty()) {
2968 return boost::shared_ptr<const speech_dialog>();
2969 }
2970
2971 return speech_dialogs_.front();
2972 }
2973
2974 bool entity_in_current_level(const entity* e)
2975 {
2976 const level& lvl = level::current();
2977 return std::find(lvl.get_chars().begin(), lvl.get_chars().end(), e) != lvl.get_chars().end();
2978 }
2979
2980 UTILITY(correct_solidity)
2981 {
2982 std::vector<std::string> files;
2983 sys::get_files_in_dir(preferences::level_path(), &files);
2984 foreach(const std::string& file, files) {
2985 boost::intrusive_ptr<level> lvl(new level(file));
2986 lvl->finish_loading();
2987 lvl->set_as_current_level();
2988
2989 foreach(entity_ptr c, lvl->get_chars()) {
2990 if(entity_collides_with_level(*lvl, *c, MOVE_NONE)) {
2991 if(place_entity_in_level(*lvl, *c)) {
2992 std::cerr << "LEVEL: " << lvl->id() << " CORRECTED " << c->debug_description() << "\n";
2993 } else {
2994 std::cerr << "LEVEL: " << lvl->id() << " FAILED TO CORRECT " << c->debug_description() << "\n";
2995 }
2996 }
2997
2998 c->handle_event("editor_removed");
2999 c->handle_event("editor_added");
3000 }
3001
3002 std::string data;
3003 wml::write(lvl->write(), data);
3004 sys::write_file(preferences::level_path() + file, data);
3005 }
3006 }
3007
3008 UTILITY(compile_levels)
3009 {
3010 std::cerr << "COMPILING LEVELS...\n";
3011 preferences::compiling_tiles = true;
3012
3013 std::vector<std::string> files;
3014 sys::get_files_in_dir(preferences::level_path(), &files);
3015
3016 wml::node_ptr index_node(new wml::node("level_index"));
3017
3018 foreach(const std::string& file, files) {
3019 std::cerr << "LOADING LEVEL '" << file << "'\n";
3020 boost::intrusive_ptr<level> lvl(new level(file));
3021 lvl->finish_loading();
3022 std::string data;
3023 wml::write(lvl->write(), data);
3024 sys::write_file("data/compiled/level/" + file, data);
3025
3026 wml::node_ptr node(new wml::node("level"));
3027 node->set_attr("level", lvl->id());
3028 node->set_attr("title", lvl->title());
3029 node->set_attr("music", lvl->music());
3030 index_node->add_child(node);
3031 }
3032
3033 std::string index_data;
3034 wml::write(index_node, index_data);
3035 sys::write_file("data/compiled/level_index.cfg", index_data);
3036
3037 level_object::write_compiled();
3038 }
3039
3040 BENCHMARK(level_solid)
3041 {
3042 //benchmark which tells us how long level::solid takes.
3043 static level* lvl = new level("stairway-to-heaven.cfg");
3044 BENCHMARK_LOOP {
3045 lvl->solid(rng::generate()%1000, rng::generate()%1000);
3046 }
3047 }
3048
3049 BENCHMARK(load_nene)
3050 {
3051 BENCHMARK_LOOP {
3052 level lvl("to-nenes-house.cfg");
3053 }
3054 }
3055
3056 BENCHMARK(load_all_levels)
3057 {
3058 std::vector<std::string> files;
3059 sys::get_files_in_dir(preferences::level_path(), &files);
3060 BENCHMARK_LOOP {
3061 foreach(const std::string& file, files) {
3062 boost::intrusive_ptr<level> lvl(new level(file));
3063 }
3064 }
3065 }
3066
3067 #include "wml_writer.hpp"
3068
3069 BENCHMARK(load_and_save_all_levels)
3070 {
3071 BENCHMARK_LOOP {
3072 std::vector<std::string> files;
3073 sys::get_files_in_dir(preferences::level_path(), &files);
3074 foreach(const std::string& file, files) {
3075 std::cerr << "LOAD_LEVEL '" << file << "'\n";
3076 boost::intrusive_ptr<level> lvl(new level(file));
3077 lvl->finish_loading();
3078
3079 std::string data;
3080 wml::write(lvl->write(), data);
3081 sys::write_file(preferences::level_path() + file, data);
3082 }
3083 }
3084 }
0 #ifndef LEVEL_HPP_INCLUDED
1 #define LEVEL_HPP_INCLUDED
2
3 #include <bitset>
4 #include <deque>
5 #include <map>
6 #include <queue>
7 #include <set>
8 #include <string>
9 #include <vector>
10
11 #include "boost/array.hpp"
12 #include "boost/scoped_ptr.hpp"
13
14 #include "background.hpp"
15 #include "entity.hpp"
16 #include "formula.hpp"
17 #include "formula_callable.hpp"
18 #include "formula_callable_definition_fwd.hpp"
19 #include "geometry.hpp"
20 #include "gui_formula_functions.hpp"
21 #include "level_object.hpp"
22 #include "level_solid_map.hpp"
23 #include "movement_script.hpp"
24 #include "raster.hpp"
25 #include "speech_dialog.hpp"
26 #include "tile_map.hpp"
27 #include "water.hpp"
28 #include "wml_node_fwd.hpp"
29 #include "color_utils.hpp"
30
31 class tile_corner;
32
33 class level : public game_logic::formula_callable
34 {
35 public:
36 struct summary {
37 std::string music, title;
38 };
39
40 static summary get_summary(const std::string& id);
41
42 static level& current();
43 void set_as_current_level();
44
45 explicit level(const std::string& level_cfg);
46 ~level();
47
48 //function to do anything which loads the level and must be done
49 //in the main thread.
50 void finish_loading();
51
52 //function which sets which player we're controlling on this machine.
53 void set_multiplayer_slot(int slot);
54
55 const std::string& replay_data() const { return replay_data_; }
56 void load_save_point(const level& lvl);
57 void set_save_point(int x, int y) { save_point_x_ = x; save_point_y_ = y; }
58
59 const std::string& id() const { return id_; }
60 const std::string& music() const { return music_; }
61
62 wml::node_ptr write() const;
63 void draw(int x, int y, int w, int h) const;
64 void draw_status() const;
65 void draw_debug_solid(int x, int y, int w, int h) const;
66 void draw_background(int x, int y, int rotation) const;
67 void process();
68 void process_draw();
69 bool standable(int x, int y, int* friction=NULL, int* traction=NULL, int* damage=NULL) const;
70 bool standable_tile(int x, int y, int* friction=NULL, int* traction=NULL, int* damage=NULL) const;
71 bool solid(int x, int y, int* friction=NULL, int* traction=NULL, int* damage=NULL) const;
72 bool solid(const entity& e, const std::vector<point>& points, int* friction=NULL, int* traction=NULL, int* damage=NULL) const;
73 bool solid(const rect& r, int* friction=NULL, int* traction=NULL, int* damage=NULL) const;
74 bool may_be_solid_in_rect(const rect& r) const;
75 void set_solid_area(const rect& r, bool solid);
76 entity_ptr board(int x, int y) const;
77 const rect& boundaries() const { return boundaries_; }
78 void set_boundaries(const rect& bounds) { boundaries_ = bounds; }
79 void add_tile(const level_tile& t);
80 void add_tile_rect(int zorder, int x1, int y1, int x2, int y2, const std::string& tile);
81 void add_tile_rect_vector(int zorder, int x1, int y1, int x2, int y2, const std::vector<std::string>& tiles);
82 void set_tile_layer_speed(int zorder, int x_speed, int y_speed);
83 void refresh_tile_rect(int x1, int y1, int x2, int y2);
84 void get_tile_rect(int zorder, int x1, int y1, int x2, int y2, std::vector<std::string>& tiles) const;
85 void get_all_tiles_rect(int x1, int y1, int x2, int y2, std::map<int, std::vector<std::string> >& tiles) const;
86 void clear_tile_rect(int x1, int y1, int x2, int y2);
87 void remove_tiles_at(int x, int y);
88
89 //function to do 'magic wand' selection -- given an x/y pixel position,
90 //will return all the solid tiles connected
91 std::vector<point> get_solid_contiguous_region(int xpos, int ypos) const;
92
93 const level_tile* get_tile_at(int x, int y) const;
94 void remove_character(entity_ptr e);
95 std::vector<entity_ptr> get_characters_in_rect(const rect& r) const;
96 entity_ptr get_character_at_point(int x, int y) const;
97 const player_info* player() const { return player_ ? player_->get_player_info() : NULL; }
98 player_info* player() { return player_ ? player_->get_player_info() : NULL; }
99 std::vector<entity_ptr>& players() { return players_; }
100 const std::vector<entity_ptr>& players() const { return players_; }
101 void add_player(entity_ptr p);
102 void add_character(entity_ptr p);
103
104 //schedule a character for removal at the end of the current cycle.
105 void schedule_character_removal(entity_ptr p);
106
107 //sets the last 'touched' player. This is the player found in the level when
108 //using WML, so it works reasonably well in multiplayer.
109 void set_touched_player(entity_ptr p) { last_touched_player_ = p; }
110
111 struct portal {
112 portal() : dest_starting_pos(false), automatic(false), saved_game(false)
113 {}
114 rect area;
115 std::string level_dest;
116 std::string dest_label;
117 std::string dest_str;
118 point dest;
119 bool dest_starting_pos;
120 bool automatic;
121 std::string transition;
122 bool saved_game;
123 };
124
125 //function which will make it so the next call to get_portal() will return
126 //a pointer to a copy of the given portal. i.e. this makes the character immediately
127 //enter a portal.
128 void force_enter_portal(const portal& p);
129
130 //the portal the character has entered (if any)
131 const portal* get_portal() const;
132
133 int xscale() const { return xscale_; }
134 int yscale() const { return yscale_; }
135
136 int group_size(int group) const;
137 void set_character_group(entity_ptr c, int group_num);
138 int add_group();
139
140 void set_editor() { editor_ = true; }
141 void set_editor_highlight(entity_ptr c) { editor_highlight_ = c; }
142 entity_ptr editor_highlight() const { return editor_highlight_; }
143
144 void editor_select_object(entity_ptr c);
145 void editor_clear_selection();
146
147 const std::vector<entity_ptr>& editor_selection() const { return editor_selection_; }
148
149 bool show_foreground() const { return show_foreground_; }
150 void set_show_foreground(bool value) { show_foreground_ = value; }
151
152 bool show_background() const { return show_background_; }
153 void set_show_background(bool value) { show_background_ = value; }
154
155 const std::string& get_background_id() const;
156 void set_background_by_id(const std::string& id);
157
158 //a function to start rebuilding tiles in a background thread.
159 void start_rebuild_tiles_in_background(const std::vector<int>& layers);
160
161 //a function which, if rebuilding tiles has been completed, will update
162 //with the new tiles.
163 void complete_rebuild_tiles_in_background();
164
165 //stop calls to start_rebuild_tiles_in_background from proceeding
166 //until unfreeze_rebuild_tiles_in_background() is called.
167 void freeze_rebuild_tiles_in_background();
168
169 void unfreeze_rebuild_tiles_in_background();
170
171 void rebuild_tiles();
172
173 const std::string& title() const { return title_; }
174 void set_title(const std::string& t) { title_ = t; }
175
176 int variations(int xtile, int ytile) const;
177 void flip_variations(int xtile, int ytile, int delta=1);
178
179 int auto_move_camera_x() const { return auto_move_camera_.x; }
180 int auto_move_camera_y() const { return auto_move_camera_.y; }
181
182 int air_resistance() const { return air_resistance_; }
183 int water_resistance() const { return water_resistance_; }
184
185 int camera_rotation() const;
186
187 void set_end_game() { end_game_ = true; }
188 bool end_game() const { return end_game_; }
189
190 //Function used when the player is entering the level at a certain point.
191 //Will take the name of a destination within a level and return the point
192 //at that location. For now only takes "left" and "right".
193 point get_dest_from_str(const std::string& key) const;
194
195 //levels ended up at by exiting this level to the left or right.
196 const std::string& previous_level() const;
197 const std::string& next_level() const;
198
199 void set_previous_level(const std::string& name);
200 void set_next_level(const std::string& name);
201
202 int cycle() const { return cycle_; }
203 bool in_dialog() const { return in_dialog_; }
204 void set_in_dialog(bool value) { in_dialog_ = value; }
205 bool is_underwater(const rect& r, rect* res_water_area=NULL, variant* v=NULL) const;
206
207 void get_current(const entity& e, int* velocity_x, int* velocity_y) const;
208
209 water* get_water() { return water_.get(); }
210 const water* get_water() const { return water_.get(); }
211
212 water& get_or_create_water();
213
214 entity_ptr get_entity_by_label(const std::string& label);
215 const_entity_ptr get_entity_by_label(const std::string& label) const;
216
217 void get_all_labels(std::vector<std::string>& labels) const;
218
219 const std::vector<entity_ptr>& get_active_chars() const { return active_chars_; }
220 const std::vector<entity_ptr>& get_chars() const { return chars_; }
221 const std::vector<entity_ptr>& get_solid_chars() const;
222 void swap_chars(std::vector<entity_ptr>& v) { chars_.swap(v); solid_chars_.clear(); }
223 int num_active_chars() const { return active_chars_.size(); }
224
225 void begin_movement_script(const std::string& name, entity& e);
226 void end_movement_script();
227
228 //function which, given the rect of the player's body will return true iff
229 //the player can currently "interact" with a portal or object. i.e. if
230 //pressing up will talk to someone or enter a door etc.
231 bool can_interact(const rect& body) const;
232
233 void replay_from_cycle(int ncycle);
234 void backup();
235
236 bool is_multiplayer() const { return players_.size() > 1; }
237
238 void get_tile_layers(std::set<int>* all_layers, std::set<int>* hidden_layers=NULL);
239 void hide_tile_layer(int layer, bool is_hidden);
240
241 void highlight_tile_layer(int layer) { highlight_layer_ = layer; }
242
243 const point* lock_screen() const { return lock_screen_.get(); }
244
245 void editor_freeze_tile_updates(bool value);
246
247 int zoom_level() const { return zoom_level_; }
248
249 void add_speech_dialog(boost::shared_ptr<speech_dialog> d);
250 boost::shared_ptr<const speech_dialog> current_speech_dialog() const;
251
252 const std::vector<entity_ptr>& focus_override() const { return focus_override_; }
253
254 static const game_logic::formula_callable_definition& get_formula_definition();
255
256 private:
257 level(const level&);
258 void operator=(const level&);
259
260 void read_compiled_tiles(wml::const_node_ptr node, std::vector<level_tile>::iterator& out);
261
262 void complete_tiles_refresh();
263 void prepare_tiles_for_drawing();
264
265 void do_processing();
266
267 bool add_tile_rect_vector_internal(int zorder, int x1, int y1, int x2, int y2, const std::vector<std::string>& tiles);
268
269 void draw_layer(int layer, int x, int y, int w, int h) const;
270 void draw_layer_solid(int layer, int x, int y, int w, int h) const;
271
272 void rebuild_tiles_rect(const rect& r);
273 void add_tile_solid(const level_tile& t);
274 void add_solid_rect(int x1, int y1, int x2, int y2, int friction, int traction, int damage);
275 void add_solid(int x, int y, int friction, int traction, int damage);
276 void add_standable(int x, int y, int friction, int traction, int damage);
277 typedef std::pair<int,int> tile_pos;
278 typedef std::bitset<TileSize*TileSize> tile_bitmap;
279
280 std::string id_;
281 std::string music_;
282 std::string replay_data_;
283 int cycle_;
284
285 int time_freeze_;
286
287 bool in_dialog_;
288
289 std::map<std::string, variant> vars_;
290
291 level_solid_map solid_;
292 level_solid_map standable_;
293
294 bool is_solid(const level_solid_map& map, int x, int y, int* friction, int* traction, int* damage) const;
295 bool is_solid(const level_solid_map& map, const entity& e, const std::vector<point>& points, int* friction, int* traction, int* damage) const;
296
297 void set_solid(level_solid_map& map, int x, int y, int friction, int traction, int damage, bool solid=true);
298
299 variant get_value_by_slot(int slot) const;
300 variant get_value(const std::string& key) const;
301 void set_value(const std::string& key, const variant& value);
302
303 std::string title_;
304
305 rect boundaries_;
306
307 struct solid_rect {
308 rect r;
309 int friction;
310 int traction;
311 int damage;
312 };
313 std::vector<solid_rect> solid_rects_;
314 mutable std::vector<level_tile> tiles_;
315 std::set<int> layers_;
316 std::set<int> hidden_layers_; //layers hidden in the editor.
317 int highlight_layer_;
318
319 struct layer_blit_info {
320 layer_blit_info() : texture_id(0), xbase(-1), ybase(-1)
321 {}
322
323 GLuint texture_id;
324
325 std::vector<tile_corner> blit_vertexes;
326
327 //texture ID's for each set of corners, used if texture_id == -1
328 //(i.e. if there are
329 std::vector<GLuint> vertex_texture_ids;
330
331 int xbase, ybase;
332
333 //the iPhone doesn't seem to support ints being used as indexes in glDrawElements
334 // -- or at least XCode is saying GL_UNSIGNED_INT is undefined. So use shorts
335 //on the iPhone. Since we compile tiles on the iPhone and solid colored tiles
336 //are not counted, this is probably fine.
337 #if TARGET_OS_IPHONE
338 typedef GLshort IndexType;
339 #define TILE_INDEX_TYPE GL_UNSIGNED_SHORT
340 #define TILE_INDEX_TYPE_MAX SHRT_MAX
341 #else
342 typedef GLint IndexType;
343 #define TILE_INDEX_TYPE GL_UNSIGNED_INT
344 #define TILE_INDEX_TYPE_MAX UINT_MAX
345 #endif
346
347 //a two dimensional array of indexes into vertex_texture_ids,
348 //representing the tiles in a layer.
349 std::vector<std::vector<IndexType> > indexes;
350
351 //we have two blit queues for a layer. One to draw tiles which have
352 //some alpha (GL_BLEND enabled) and others which are completely opaque
353 //and can be drawn more efficiently without alpha blending.
354 std::vector<IndexType> opaque_indexes, translucent_indexes;
355
356 rect tile_positions;
357 };
358
359 mutable std::map<int, layer_blit_info> blit_cache_;
360
361 struct solid_color_rect {
362 graphics::color color;
363 rect area;
364 int layer;
365 };
366
367 struct solid_color_rect_empty {
368 bool operator()(const solid_color_rect& r) const { return r.area.w() == 0; }
369 };
370
371 struct solid_color_rect_cmp {
372 bool operator()(const solid_color_rect& r, int zorder) const { return r.layer < zorder; }
373 bool operator()(int zorder, const solid_color_rect& r) const { return zorder < r.layer; }
374 bool operator()(const solid_color_rect& a, const solid_color_rect& b) const { return a.layer < b.layer; }
375 };
376
377 std::vector<solid_color_rect> solid_color_rects_;
378
379 std::vector<rect> opaque_rects_;
380
381 void erase_char(entity_ptr c);
382 std::vector<entity_ptr> chars_;
383 std::vector<entity_ptr> active_chars_;
384 mutable std::vector<entity_ptr> solid_chars_;
385
386 std::vector<entity_ptr> chars_immune_from_time_freeze_;
387
388 std::map<std::string, entity_ptr> chars_by_label_;
389 entity_ptr player_;
390 entity_ptr last_touched_player_;
391
392 std::vector<entity_ptr> players_;
393
394 //characters stored in wml format; they can't be loaded in a separate thread
395 //they will be loaded when complete_load_level() is called.
396 std::vector<wml::const_node_ptr> wml_chars_;
397
398 std::vector<wml::node_ptr> wml_compiled_tiles_;
399 int num_compiled_tiles_;
400
401 void load_character(wml::const_node_ptr c);
402
403 typedef std::vector<entity_ptr> entity_group;
404 std::vector<entity_group> groups_;
405
406 portal left_portal_, right_portal_;
407 std::vector<portal> portals_;
408
409 mutable bool entered_portal_active_;
410 portal entered_portal_;
411
412 boost::shared_ptr<background> background_;
413 point background_offset_;
414 int widest_tile_, highest_tile_;
415
416 std::map<int, tile_map> tile_maps_;
417 int xscale_, yscale_;
418
419 int save_point_x_, save_point_y_;
420 bool editor_;
421 entity_ptr editor_highlight_;
422
423 std::vector<entity_ptr> editor_selection_;
424
425 bool show_foreground_, show_background_;
426
427 point auto_move_camera_;
428 int air_resistance_;
429 int water_resistance_;
430
431 game_logic::const_formula_ptr camera_rotation_;
432 bool end_game_;
433
434 std::vector<std::string> preloads_; //future levels to preload
435
436 boost::scoped_ptr<water> water_;
437
438 std::map<std::string, movement_script> movement_scripts_;
439 std::vector<active_movement_script_ptr> active_movement_scripts_;
440
441 boost::scoped_ptr<point> lock_screen_;
442
443 struct backup_snapshot {
444 unsigned int rng_seed;
445 int cycle;
446 std::vector<entity_ptr> chars;
447 std::vector<entity_ptr> players;
448 entity_ptr player, last_touched_player;
449 };
450
451 void restore_from_backup(backup_snapshot& snapshot);
452
453 typedef boost::shared_ptr<backup_snapshot> backup_snapshot_ptr;
454
455 std::deque<backup_snapshot_ptr> backups_;
456
457 int editor_tile_updates_frozen_;
458
459 std::string gui_algo_str_;
460 gui_algorithm_ptr gui_algorithm_;
461
462 int zoom_level_;
463 std::vector<entity_ptr> focus_override_;
464
465 std::queue<boost::shared_ptr<speech_dialog> > speech_dialogs_;
466
467 //color palettes that the level has set.
468 unsigned int palettes_used_;
469
470 int background_palette_;
471 };
472
473 bool entity_in_current_level(const entity* e);
474
475 #endif
0 #include <cstdlib>
1 #include <limits.h>
2
3 #include "level.hpp"
4 #include "level_logic.hpp"
5
6 bool cliff_edge_within(const level& lvl, int xpos, int ypos, int deltax)
7 {
8 const int FeetWidth = 5;
9 return !lvl.standable(xpos + deltax, ypos) &&
10 !lvl.standable(xpos + deltax, ypos + std::abs(deltax) + FeetWidth);
11 }
12
13 int distance_to_cliff(const level& lvl, int xpos, int ypos, int facing)
14 {
15 const int max_search = 1000;
16 const int cliff_face = 5;
17 const int cliff_drop = 2;
18
19 bool found = false;
20
21 //search for up to three pixels below us to try to get a starting
22 //position which is standable.
23 for(int n = 0; n != 3; ++n) {
24 if(lvl.standable_tile(xpos, ypos)) {
25 found = true;
26 break;
27 }
28
29 ++ypos;
30 }
31
32 if(!found) {
33 return max_search;
34 }
35
36 //make sure we are at the surface.
37 while(lvl.standable_tile(xpos, ypos-1)) {
38 --ypos;
39 }
40
41 int result = 0;
42 for(; result < max_search; xpos += facing, ++result) {
43 if(lvl.standable_tile(xpos, ypos) || lvl.standable_tile(xpos, ypos-1)) {
44 int ydiff = 0;
45 while(lvl.standable_tile(xpos, ypos-1) && ydiff < cliff_face) {
46 --ypos;
47 ++ydiff;
48 }
49
50 if(ydiff == cliff_face) {
51 return max_search;
52 }
53 } else {
54 int ydiff = 0;
55 while(!lvl.standable_tile(xpos, ypos) && ydiff < cliff_drop) {
56 ++ypos;
57 ++ydiff;
58 }
59
60 if(ydiff == cliff_drop) {
61 return result;
62 }
63 }
64 }
65
66 return result;
67 }
68
69 int find_ground_level(const level& lvl, int xpos, int ypos, int max_search)
70 {
71 if(lvl.standable(xpos, ypos)) {
72 --ypos;
73 while(lvl.standable(xpos, ypos) && --max_search > 0) {
74 --ypos;
75 }
76
77 if(!max_search) {
78 return INT_MIN;
79 }
80
81 return ypos + 1;
82 } else {
83 //search both up and down, since in the case of a platform the
84 //ground may be above us.
85 for(int n = 1; n < max_search; ++n) {
86 if(lvl.standable(xpos, ypos + n)) {
87 return ypos + n - 1;
88 }
89
90 if(lvl.standable(xpos, ypos - n) && !lvl.standable(xpos, ypos - n - 1)) {
91 return ypos - n;
92 }
93 }
94
95 return INT_MIN;
96 }
97 }
0 #ifndef LEVEL_LOGIC_HPP_INCLUDED
1 #define LEVEL_LOGIC_HPP_INCLUDED
2
3 class level;
4
5 // find out if [xpos + deltax, ypos] is over a drop-off from [xpos, ypos].
6 // [xpos, ypos] must be on the ground. deltax must not be greater than
7 // the tile size.
8 bool cliff_edge_within(const level& lvl, int xpos, int ypos, int deltax);
9
10 // find out how far the nearest cliff is from [xpos, ypos]
11 int distance_to_cliff(const level& lvl, int xpos, int ypos, int facing);
12
13 // given a position, will return the xpos of the ground level closest to this
14 // position. Will search downwards if (xpos,ypos) is not solid, and upwards
15 // if (xpos,ypos) is solid. Will return INT_MIN on failure to find a result.
16 int find_ground_level(const level& lvl, int xpos, int ypos, int max_search=20);
17
18 #endif
0 #include <iostream>
1 #include <map>
2 #include <string>
3
4 #include "IMG_savepng.h"
5 #include "asserts.hpp"
6 #include "draw_tile.hpp"
7 #include "filesystem.hpp"
8 #include "level_object.hpp"
9 #include "preferences.hpp"
10 #include "raster.hpp"
11 #include "string_utils.hpp"
12 #include "surface.hpp"
13 #include "surface_cache.hpp"
14 #include "surface_palette.hpp"
15 #include "wml_parser.hpp"
16 #include "wml_utils.hpp"
17 #include "wml_writer.hpp"
18 namespace {
19 typedef std::map<std::string,const_level_object_ptr> tiles_map;
20 tiles_map tiles_cache;
21 }
22
23 bool level_tile::is_solid(int xpos, int ypos) const
24 {
25 return object->is_solid(face_right ? (xpos - x) : (x + object->width() - xpos - 1), ypos - y);
26 }
27
28 std::vector<const_level_object_ptr> level_object::all()
29 {
30 std::vector<const_level_object_ptr> res;
31 for(tiles_map::const_iterator i = tiles_cache.begin(); i != tiles_cache.end(); ++i) {
32 res.push_back(i->second);
33 }
34
35 return res;
36 }
37
38 level_tile level_object::build_tile(wml::const_node_ptr node)
39 {
40 level_tile res;
41 res.x = wml::get_int(node, "x");
42 res.y = wml::get_int(node, "y");
43 res.zorder = wml::get_int(node, "zorder");
44 if(tiles_cache.count(node->attr("tile"))) {
45 res.object = tiles_cache[node->attr("tile")];
46 }
47 res.face_right = wml::get_bool(node, "face_right");
48 return res;
49 }
50
51 namespace {
52 std::vector<wml::const_node_ptr> level_object_index;
53
54 typedef std::pair<std::string, int> filename_palette_pair;
55
56 //a tile identifier made up of a filename, palette and tile position.
57 typedef std::pair<filename_palette_pair, int> tile_id;
58 std::map<tile_id, int> compiled_tile_ids;
59 }
60
61 //defined in texture.cpp
62 namespace graphics {
63 void set_alpha_for_transparent_colors_in_rgba_surface(SDL_Surface* s);
64 }
65
66 void create_compiled_tiles_image()
67 {
68 graphics::surface s(SDL_CreateRGBSurface(SDL_SWSURFACE, 1024, (compiled_tile_ids.size()/64 + 1)*16, 32, SURFACE_MASK));
69 for(std::map<tile_id, int>::const_iterator itor = compiled_tile_ids.begin();
70 itor != compiled_tile_ids.end(); ++itor) {
71 const tile_id& tile_info = itor->first;
72 const std::string& filename = tile_info.first.first;
73 const int palette = tile_info.first.second;
74 const int tile_pos = tile_info.second;
75
76 std::cerr << "WRITING PALETTE: " << palette << "\n";
77
78 graphics::surface src = graphics::surface_cache::get(filename);
79 if(palette >= 0) {
80 src = graphics::map_palette(src, palette);
81 }
82
83 SDL_SetAlpha(src.get(), 0, SDL_ALPHA_OPAQUE);
84 const int width = std::max<int>(src->w, src->h)/16;
85
86 const int src_x = (tile_pos%width) * 16;
87 const int src_y = (tile_pos/width) * 16;
88
89 const int dst_x = (itor->second%64) * 16;
90 const int dst_y = (itor->second/64) * 16;
91
92 SDL_Rect src_rect = { src_x, src_y, 16, 16 };
93 SDL_Rect dst_rect = { dst_x, dst_y, 16, 16 };
94
95 SDL_BlitSurface(src.get(), &src_rect, s.get(), &dst_rect);
96 }
97
98 graphics::set_alpha_for_transparent_colors_in_rgba_surface(s.get());
99
100 IMG_SavePNG("images/tiles-compiled.png", s.get(), 5);
101 }
102
103 namespace {
104 unsigned int current_palette_set = 0;
105 std::set<level_object*>& palette_level_objects() {
106 //we never want this to be destroyed, since it's too hard to
107 //guarantee destruction order.
108 static std::set<level_object*>* instance = new std::set<level_object*>;
109 return *instance;
110 }
111 }
112
113 palette_scope::palette_scope(const std::string& str)
114 : original_value(current_palette_set)
115 {
116 current_palette_set = 0;
117 if(str.empty() == false) {
118 std::vector<std::string> p = util::split(str);
119 foreach(const std::string& pal, p) {
120 const int id = graphics::get_palette_id(pal);
121 current_palette_set |= 1 << id;
122 }
123 }
124 }
125
126 palette_scope::~palette_scope()
127 {
128 current_palette_set = original_value;
129 }
130
131 void level_object::set_current_palette(unsigned int palette)
132 {
133 foreach(level_object* obj, palette_level_objects()) {
134 obj->set_palette(palette);
135 }
136 }
137
138 level_object::level_object(wml::const_node_ptr node)
139 : id_(node->attr("id")), image_(node->attr("image")),
140 t_(graphics::texture::get(image_)),
141 all_solid_(node->attr("solid").str() == "yes"),
142 passthrough_(wml::get_bool(node, "passthrough")),
143 flip_(wml::get_bool(node, "flip", false)),
144 friction_(wml::get_int(node, "friction", 100)),
145 traction_(wml::get_int(node, "traction", 100)),
146 damage_(wml::get_int(node, "damage", 0)),
147 opaque_(wml::get_bool(node, "opaque", false)),
148 draw_area_(0, 0, 16, 16),
149 tile_index_(-1),
150 palettes_recognized_(current_palette_set),
151 current_palettes_(0)
152 {
153 if(node->has_attr("palettes")) {
154 palettes_recognized_ = 0;
155 std::vector<std::string> p = util::split(node->attr("palettes"));
156 foreach(const std::string& pal, p) {
157 const int id = graphics::get_palette_id(pal);
158 palettes_recognized_ |= 1 << id;
159 }
160 }
161
162 if(palettes_recognized_) {
163 palette_level_objects().insert(this);
164 }
165
166 if(node->has_attr("solid_color")) {
167 solid_color_ = boost::intrusive_ptr<graphics::color>(new graphics::color(node->attr("solid_color")));
168 if(preferences::use_16bpp_textures()) {
169 *solid_color_ = graphics::color(graphics::map_color_to_16bpp(solid_color_->rgba()));
170 }
171 }
172
173 if(node->has_attr("draw_area")) {
174 draw_area_ = rect(node->attr("draw_area"));
175 }
176
177 std::vector<std::string> tile_variations = util::split(node->attr("tiles"), '|');
178 foreach(const std::string& variation, tile_variations) {
179 if(!variation.empty() && variation[0] == '+') {
180 //a + symbol at the start of tiles means that it's just a base-10
181 //number. This is generally what is used for compiled tiles.
182 tiles_.push_back(strtol(variation.c_str()+1, NULL, 10));
183 } else {
184 const int width = std::max<int>(t_.width(), t_.height());
185 assert(width%16 == 0);
186 const int base = std::min<int>(32, width/16);
187 tiles_.push_back(strtol(variation.c_str(), NULL, base));
188 }
189 }
190
191 if(tiles_.empty()) {
192 tiles_.resize(1);
193 }
194
195 if(node->has_attr("solid_map")) {
196 solid_.resize(width()*height());
197 graphics::surface surf(graphics::surface_cache::get(node->attr("solid_map")).convert_opengl_format());
198 if(surf.get()) {
199 const uint32_t* p = reinterpret_cast<const uint32_t*>(surf->pixels);
200 for(int n = 0; n != surf->w*surf->h && n != solid_.size(); ++n) {
201 uint8_t r, g, b, alpha;
202 SDL_GetRGBA(p[n], surf->format, &r, &g, &b, &alpha);
203 if(alpha > 64) {
204 solid_[n] = true;
205 }
206 }
207 }
208 }
209
210 std::vector<std::string> solid_attr = util::split(node->attr("solid").str());
211
212 if(all_solid_ || std::find(solid_attr.begin(), solid_attr.end(), "flat") != solid_attr.end()) {
213 if(passthrough_){
214 solid_.resize(width()*height());
215 for(int x = 0; x < width(); ++x) {
216 for(int y = 0; y < height(); ++y) {
217 const int index = y*width() + x;
218 solid_[index] = (y == 0);
219 }
220 }
221 //set all_solid_ to false because it's not longer the case.
222 all_solid_ = false;
223 }else{
224 solid_ = std::vector<bool>(width()*height(), true);
225 }
226 }
227
228 if(std::find(solid_attr.begin(), solid_attr.end(), "diagonal") != solid_attr.end()) {
229 solid_.resize(width()*height());
230 for(int x = 0; x < width(); ++x) {
231 for(int y = 0; y < height(); ++y) {
232 const int index = y*width() + x;
233 solid_[index] = solid_[index] || (passthrough_? (y==x) : (y >= x));
234 }
235 }
236 }
237
238 if(std::find(solid_attr.begin(), solid_attr.end(), "reverse_diagonal") != solid_attr.end()) {
239 solid_.resize(width()*height());
240 for(int x = 0; x < width(); ++x) {
241 for(int y = 0; y < height(); ++y) {
242 const int index = y*width() + x;
243 solid_[index] = solid_[index] || (passthrough_? (y == (width() - (x+1))) : (y >= (width() - (x+1))));
244 }
245 }
246 }
247
248 if(std::find(solid_attr.begin(), solid_attr.end(), "quarter_diagonal_lower") != solid_attr.end()) {
249 solid_.resize(width()*height());
250 for(int x = 0; x < width(); ++x) {
251 for(int y = 0; y < height(); ++y) {
252 const int index = y*width() + x;
253 solid_[index] = solid_[index] || (passthrough_? (y == (x/2 + width()/2)) : (y >= (x/2 + width()/2)));
254 }
255 }
256 }
257
258 if(std::find(solid_attr.begin(), solid_attr.end(), "quarter_diagonal_upper") != solid_attr.end()) {
259 solid_.resize(width()*height());
260 for(int x = 0; x < width(); ++x) {
261 for(int y = 0; y < height(); ++y) {
262 const int index = y*width() + x;
263 solid_[index] = solid_[index] || (passthrough_? (y == x/2) : (y >= x/2));
264 }
265 }
266 }
267
268 if(std::find(solid_attr.begin(), solid_attr.end(), "reverse_quarter_diagonal_lower") != solid_attr.end()) {
269 solid_.resize(width()*height());
270 for(int x = 0; x < width(); ++x) {
271 for(int y = 0; y < height(); ++y) {
272 const int index = y*width() + x;
273 solid_[index] = solid_[index] || (passthrough_? (y == (width() - x/2) -1) : (y >= (width() - x/2)));
274 }
275 }
276 }
277
278 if(std::find(solid_attr.begin(), solid_attr.end(), "reverse_quarter_diagonal_upper") != solid_attr.end()) {
279 solid_.resize(width()*height());
280 for(int x = 0; x < width(); ++x) {
281 for(int y = 0; y < height(); ++y) {
282 const int index = y*width() + x;
283 solid_[index] = solid_[index] || (passthrough_? (y == (width()/2 - x/2) -1) : (y >= (width()/2 - x/2)));
284 }
285 }
286 }
287
288 if(node->has_attr("solid_heights")) {
289 //this is a csv list of heights which represent the solids
290 std::vector<std::string> heights = util::split(node->attr("solid_heights"));
291 if(!heights.empty()) {
292 solid_.resize(width()*height());
293 for(int x = 0; x < width(); ++x) {
294 const int heights_index = (heights.size()*x)/width();
295 assert(heights_index >= 0 && heights_index < heights.size());
296 const std::string& height_str = heights[heights_index];
297 const int h = atoi(height_str.c_str());
298 for(int y = height() - h; y < height(); ++y) {
299 const int index = y*width() + x;
300 solid_[index] = true;
301 }
302 }
303 }
304 }
305
306 wml::node::const_child_iterator r1 = node->begin_child("rect");
307 wml::node::const_child_iterator r2 = node->end_child("rect");
308 for(; r1 != r2; ++r1) {
309 const int x = wml::get_int(r1->second, "x");
310 const int y = wml::get_int(r1->second, "y");
311 const int w = wml::get_int(r1->second, "w");
312 const int h = wml::get_int(r1->second, "h");
313
314 if(solid_.empty()) {
315 solid_.resize(width()*height());
316 }
317 for(int xpos = x; xpos != x+w; ++xpos) {
318 for(int ypos = y; ypos != y+h; ++ypos) {
319 if(xpos >= 0 && xpos < width() && ypos >= 0 && ypos < height()) {
320 const int index = ypos*width() + xpos;
321 assert(index >= 0 && index < solid_.size());
322 solid_[index] = true;
323 }
324 }
325 }
326 }
327
328 if(preferences::compiling_tiles) {
329 tile_index_ = level_object_index.size();
330
331 //set solid colors to always false if we're compiling, since having
332 //solid colors will confuse the compilation.
333 solid_color_ = boost::intrusive_ptr<graphics::color>();
334
335 std::vector<int> palettes;
336 palettes.push_back(-1);
337 get_palettes_used(palettes);
338
339 foreach(int palette, palettes) {
340 wml::node_ptr node_copy(wml::deep_copy(node));
341 node_copy->erase_attr("palettes");
342 if(calculate_opaque()) {
343 node_copy->set_attr("opaque", "yes");
344 opaque_ = true;
345 }
346
347 graphics::color col;
348 if(calculate_is_solid_color(col)) {
349 if(palette >= 0) {
350 col = graphics::map_palette(col, palette);
351 }
352 node_copy->set_attr("solid_color", graphics::color_transform(col).to_string());
353 }
354
355 if(calculate_draw_area()) {
356 node_copy->set_attr("draw_area", draw_area_.to_string());
357 }
358
359 std::string tiles_str;
360
361 foreach(int tile, tiles_) {
362 tile_id id(filename_palette_pair(node_copy->attr("image"), palette), tile);
363 std::map<tile_id, int>::const_iterator itor = compiled_tile_ids.find(id);
364 if(itor == compiled_tile_ids.end()) {
365 compiled_tile_ids[id] = compiled_tile_ids.size();
366 itor = compiled_tile_ids.find(id);
367 }
368
369 const int index = itor->second;
370
371 char tile_pos[64];
372 sprintf(tile_pos, "+%d", index);
373 if(!tiles_str.empty()) {
374 tiles_str += "|";
375 }
376
377 tiles_str += tile_pos;
378 }
379
380 node_copy->set_attr("image", "tiles-compiled.png");
381 node_copy->set_attr("tiles", tiles_str);
382
383 level_object_index.push_back(node_copy);
384 }
385 }
386
387 //debug code to output the solidity of a tile in case we need to introspect at some point
388 /*
389 std::cerr << "LEVEL_OBJECT: " << wml::output(node) << ":::\nSOLID:::\n";
390 if(solid_.size() == height()*width()) {
391 for(int y = 0; y != height(); ++y) {
392 for(int x = 0; x != width(); ++x) {
393 std::cerr << (solid_[y*width() + x] ? "1" : "0");
394 }
395
396 std::cerr << "\n";
397 }
398 } else {
399 std::cerr << "SOLID SIZE: " << solid_.size() << "\n";
400 }
401 */
402 }
403
404 level_object::~level_object()
405 {
406 if(palettes_recognized_) {
407 palette_level_objects().erase(this);
408 }
409 }
410
411 namespace {
412 const char base64_chars[65] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.!";
413
414 std::vector<int> create_base64_char_to_int()
415 {
416 std::vector<int> result(256);
417 int index = 0;
418 foreach(char c, base64_chars) {
419 result[c] = index++;
420 }
421
422 return result;
423 }
424
425 std::vector<int> base64_char_to_int = create_base64_char_to_int();
426
427 void base64_encode(int num, char* buf, int nplaces)
428 {
429 buf[nplaces--] = 0;
430 while(num > 0 && nplaces >= 0) {
431 buf[nplaces--] = base64_chars[num%64];
432 num /= 64;
433 }
434
435 while(nplaces >= 0) {
436 buf[nplaces--] = '0';
437 }
438 }
439
440 int base64_unencode(const char* begin, const char* end)
441 {
442 int result = 0;
443 while(begin != end) {
444 result = result*64 + base64_char_to_int[*begin];
445 ++begin;
446 }
447
448 return result;
449 }
450
451 }
452
453 void level_object::write_compiled()
454 {
455 for(int n = 0; n <= level_object_index.size()/64; ++n) {
456 char buf[128];
457 sprintf(buf, "%d", n);
458 const std::string filename = std::string(buf) + ".cfg";
459 wml::node_ptr tiles_node(new wml::node("tiles"));
460 for(int m = n*64; m < level_object_index.size() && m < (n+1)*64; ++m) {
461 tiles_node->add_child(wml::deep_copy(level_object_index[m]));
462 }
463
464 std::string data;
465 wml::write(tiles_node, data);
466 sys::write_file("data/compiled/tiles/" + filename, data);
467 }
468
469 create_compiled_tiles_image();
470 }
471
472 namespace {
473 std::vector<const_level_object_ptr> compiled_tiles;
474
475 void load_compiled_tiles(int index)
476 {
477 int starting_index = index*64;
478 char buf[128];
479 sprintf(buf, "%d", index);
480 wml::const_node_ptr node(wml::parse_wml_from_file("data/compiled/tiles/" + std::string(buf) + ".cfg"));
481 int count = 0;
482 for(wml::node::const_all_child_iterator i = node->begin_children(); i != node->end_children(); ++i) {
483 if(starting_index >= compiled_tiles.size()) {
484 compiled_tiles.resize(starting_index+64);
485 }
486 compiled_tiles[starting_index++].reset(new level_object(*i));
487 ++count;
488 }
489 }
490
491 }
492
493 const_level_object_ptr level_object::get_compiled(const char* buf)
494 {
495 const int index = base64_unencode(buf, buf+3);
496 if(index >= compiled_tiles.size() || !compiled_tiles[index]) {
497 load_compiled_tiles(base64_unencode(buf, buf+2));
498 }
499
500 ASSERT_LOG(index >= compiled_tiles.size() || compiled_tiles[index], "COULD NOT LOAD COMPILED TILE: " << std::string(buf, buf+3) << " -> " << index);
501
502 return compiled_tiles[index];
503 }
504
505 void level_object::write_compiled_index(char* buf) const
506 {
507 if(current_palettes_ == 0) {
508 base64_encode(tile_index_, buf, 3);
509 } else {
510 unsigned int mask = current_palettes_;
511 int npalette = 0;
512 while(!(mask&1)) {
513 mask >>= 1;
514 ++npalette;
515 }
516
517 std::vector<int> palettes;
518 get_palettes_used(palettes);
519 std::vector<int>::const_iterator i = std::find(palettes.begin(), palettes.end(), npalette);
520 ASSERT_LOG(i != palettes.end(), "PALETTE NOT FOUND: " << npalette);
521 base64_encode(tile_index_ + 1 + (i - palettes.begin()), buf, 3);
522 }
523 }
524
525 int level_object::width() const
526 {
527 return 32;
528 }
529
530 int level_object::height() const
531 {
532 return 32;
533 }
534
535 bool level_object::is_solid(int x, int y) const
536 {
537 if(solid_.empty()) {
538 return false;
539 }
540
541 if(x < 0 || y < 0 || x >= width() || y >= height()) {
542 return false;
543 }
544
545 const int index = y*width() + x;
546 assert(index >= 0 && index < solid_.size());
547 return solid_[index];
548 }
549
550 namespace {
551 int hash_level_object(int x, int y) {
552 x /= 32;
553 y /= 32;
554 x = (x + 92872873) ^ 918273;
555 y = (y + 1672517) ^ 128123;
556 return (x*y + x + y)*1103515245 + 12345;
557 }
558 }
559
560 void level_object::queue_draw(graphics::blit_queue& q, const level_tile& t)
561 {
562 const int random_index = hash_level_object(t.x,t.y);
563 const int tile = t.object->tiles_[random_index%t.object->tiles_.size()];
564
565 queue_draw_from_tilesheet(q, t.object->t_, t.object->draw_area_, tile, t.x, t.y, t.face_right);
566 }
567
568 int level_object::calculate_tile_corners(tile_corner* result, const level_tile& t)
569 {
570 const int random_index = hash_level_object(t.x,t.y);
571 const int tile = t.object->tiles_[random_index%t.object->tiles_.size()];
572
573 return get_tile_corners(result, t.object->t_, t.object->draw_area_, tile, t.x, t.y, t.face_right);
574 }
575
576 bool level_object::calculate_opaque() const
577 {
578 foreach(int tile, tiles_) {
579 if(!is_tile_opaque(t_, tile)) {
580 return false;
581 }
582 }
583
584 return true;
585 }
586
587 bool level_object::calculate_is_solid_color(graphics::color& col) const
588 {
589 foreach(int tile, tiles_) {
590 if(!is_tile_solid_color(t_, tile, col)) {
591 return false;
592 }
593 }
594
595 return true;
596 }
597
598 bool level_object::calculate_draw_area()
599 {
600 draw_area_ = rect();
601 foreach(int tile, tiles_) {
602 draw_area_ = rect_union(draw_area_, get_tile_non_alpha_area(t_, tile));
603 }
604
605 return draw_area_ != rect(0, 0, 16, 16);
606 }
607
608 void level_object::set_palette(unsigned int palette)
609 {
610 palette &= palettes_recognized_;
611 if(palette == current_palettes_) {
612 return;
613 }
614
615 current_palettes_ = palette;
616
617 if(palette == 0) {
618 t_ = graphics::texture::get(image_);
619 } else {
620 int npalette = 0;
621 while((palette&1) == 0) {
622 palette >>= 1;
623 ++npalette;
624 }
625
626 t_ = graphics::texture::get_palette_mapped(image_, npalette);
627 }
628 }
629
630 void level_object::get_palettes_used(std::vector<int>& v) const
631 {
632 unsigned int p = palettes_recognized_;
633 int palette = 0;
634 while(p) {
635 if(p&1) {
636 v.push_back(palette);
637 }
638
639 p >>= 1;
640 ++palette;
641 }
642 }
0 #ifndef LEVEL_OBJECT_HPP_INCLUDED
1 #define LEVEL_OBJECT_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4 #include <vector>
5
6 #include "color_utils.hpp"
7 #include "draw_tile.hpp"
8 #include "raster.hpp"
9 #include "texture.hpp"
10 #include "wml_node_fwd.hpp"
11
12 class level_object;
13 typedef boost::shared_ptr<level_object> level_object_ptr;
14 typedef boost::shared_ptr<const level_object> const_level_object_ptr;
15
16 struct level_tile {
17 bool is_solid(int x, int y) const;
18 int x, y;
19 int layer_from; //which zorder layer causes this tile to be built?
20 int zorder;
21 const_level_object_ptr object;
22 bool face_right;
23 bool draw_disabled;
24 };
25
26 struct level_tile_zorder_comparer {
27 bool operator()(const level_tile& a, const level_tile& b) const {
28 return a.zorder < b.zorder;
29 }
30
31 bool operator()(const level_tile& a, int b) const {
32 return a.zorder < b;
33 }
34
35 bool operator()(int a, const level_tile& b) const {
36 return a < b.zorder;
37 }
38 };
39
40 struct level_tile_zorder_pos_comparer {
41 bool operator()(const level_tile& a, const level_tile& b) const {
42 return a.zorder < b.zorder || a.zorder == b.zorder && a.y < b.y || a.zorder == b.zorder && a.y == b.y && a.x < b.x;
43 }
44 };
45
46 struct level_tile_y_pos_comparer {
47 bool operator()(const level_tile& a, int b) const {
48 return a.y < b;
49 }
50
51 bool operator()(int a, const level_tile& b) const {
52 return a < b.y;
53 }
54
55 bool operator()(const level_tile& a, const level_tile& b) const {
56 return a.y < b.y;
57 }
58 };
59
60 //utility which sets the palette for objects loaded within a scope
61 struct palette_scope {
62 explicit palette_scope(const std::string& str);
63 ~palette_scope();
64
65 unsigned int original_value;
66 };
67
68 class level_object {
69 public:
70 static std::vector<const_level_object_ptr> all();
71 static level_tile build_tile(wml::const_node_ptr node);
72 static void write_compiled();
73
74 static void set_current_palette(unsigned int palette);
75 explicit level_object(wml::const_node_ptr node);
76 ~level_object();
77
78 int width() const;
79 int height() const;
80 bool is_passthrough() const { return passthrough_; }
81 bool is_solid(int x, int y) const;
82 bool flipped() const { return flip_; }
83 bool has_solid() const { return !solid_.empty(); }
84 bool all_solid() const { return all_solid_; }
85 const std::string& id() const { return id_; }
86 int friction() const { return friction_; }
87 int traction() const { return traction_; }
88 int damage() const { return damage_; }
89 const graphics::texture& texture() const { return t_; }
90 static void queue_draw(graphics::blit_queue& q, const level_tile& t);
91 static int calculate_tile_corners(tile_corner* result, const level_tile& t);
92
93 bool is_opaque() const { return opaque_; }
94 bool calculate_opaque() const;
95 bool calculate_is_solid_color(graphics::color& col) const;
96
97 bool calculate_draw_area();
98
99 const graphics::color* solid_color() const { return solid_color_.get(); }
100
101 //write the compiled index of this object. buf MUST point to a buf
102 //of at least 4 chars.
103 void write_compiled_index(char* buf) const;
104
105 //reads an object from its compiled index. buf MUST point to a buf of
106 //at least 3 chars.
107 static const_level_object_ptr get_compiled(const char* buf);
108
109 private:
110 std::string id_;
111 std::string image_;
112 graphics::texture t_;
113 std::vector<int> tiles_;
114 std::vector<bool> solid_;
115 bool all_solid_;
116 bool passthrough_;
117 bool flip_;
118 int damage_;
119 int friction_;
120 int traction_;
121
122 bool opaque_;
123
124 rect draw_area_;
125
126 boost::intrusive_ptr<graphics::color> solid_color_;
127
128 int tile_index_;
129
130 unsigned int palettes_recognized_;
131 unsigned int current_palettes_;
132
133 void set_palette(unsigned int palette);
134
135 void get_palettes_used(std::vector<int>& v) const;
136 };
137
138 #endif
0 #include <math.h>
1
2 #include <boost/bind.hpp>
3 #include <boost/function.hpp>
4
5 #include "SDL.h"
6
7 #include "collision_utils.hpp"
8 #include "controls.hpp"
9 #include "custom_object.hpp"
10 #include "custom_object_functions.hpp"
11 #include "draw_scene.hpp"
12 #ifndef NO_EDITOR
13 #include "editor.hpp"
14 #endif
15 #include "filesystem.hpp"
16 #include "font.hpp"
17 #include "foreach.hpp"
18 #include "formula_profiler.hpp"
19 #include "inventory.hpp"
20 #include "joystick.hpp"
21 #include "level_runner.hpp"
22 #include "load_level.hpp"
23 #include "message_dialog.hpp"
24 #include "object_events.hpp"
25 #include "pause_game_dialog.hpp"
26 #include "player_info.hpp"
27 #include "preferences.hpp"
28 #include "raster.hpp"
29 #include "settings_dialog.hpp"
30 #include "sound.hpp"
31 #include "stats.hpp"
32 #include "surface_cache.hpp"
33 #include "text_entry_widget.hpp"
34 #include "wml_node.hpp"
35 #include "wml_writer.hpp"
36 #include "wml_utils.hpp"
37 #include "IMG_savepng.h"
38
39 namespace {
40 const int FrameTimeInMillis = 20;
41
42 int global_pause_time;
43
44 typedef boost::function<void(const level&, screen_position&, float)> TransitionFn;
45
46 //prepare to call transition_scene by making sure that frame buffers are
47 //filled with the image of the screen.
48 void prepare_transition_scene(level& lvl, screen_position& screen_pos)
49 {
50 draw_scene(lvl, screen_pos);
51 SDL_GL_SwapBuffers();
52 draw_scene(lvl, screen_pos);
53 SDL_GL_SwapBuffers();
54 }
55
56 void transition_scene(level& lvl, screen_position& screen_pos, bool transition_out, TransitionFn draw_fn) {
57 if(lvl.player()) {
58 lvl.player()->get_entity().set_invisible(true);
59 }
60
61 for(int n = 0; n <= 20; ++n) {
62 // lvl.process();
63
64 draw_fn(lvl, screen_pos, transition_out ? (n/20.0) : (1 - n/20.0));
65
66 SDL_GL_SwapBuffers();
67 SDL_Delay(FrameTimeInMillis);
68 }
69
70 if(lvl.player()) {
71 lvl.player()->get_entity().set_invisible(false);
72 }
73 }
74
75 void fade_scene(const level& lvl, screen_position& screen_pos, float fade) {
76 const SDL_Rect r = {0, 0, graphics::screen_width(), graphics::screen_height()};
77 const SDL_Color c = {0,0,0,0};
78 graphics::draw_rect(r, c, 128*fade);
79 }
80
81 void flip_scene(const level& lvl, screen_position& screen_pos, float amount) {
82 screen_pos.flip_rotate = amount*1000;
83 draw_scene(lvl, screen_pos);
84 }
85
86 bool calculate_stencil_buffer_available() {
87 GLint stencil_buffer_bits = 0;
88 glGetIntegerv(GL_STENCIL_BITS, &stencil_buffer_bits);
89 std::cerr << "stencil buffer size: " << stencil_buffer_bits << "\n";
90 return stencil_buffer_bits > 0;
91 }
92
93 bool is_stencil_buffer_available() {
94 static const bool result = calculate_stencil_buffer_available();
95 return result;
96 }
97
98 void iris_scene(const level& lvl, screen_position& screen_pos, float amount) {
99 if(!is_stencil_buffer_available()) {
100 //there's no stencil buffer available, so resort to a fade
101 //effect instead.
102 fade_scene(lvl, screen_pos, amount);
103 return;
104 }
105
106 if(lvl.player() == NULL) {
107 return;
108 }
109
110 //Enable the stencil buffer.
111 glEnable(GL_STENCIL_TEST);
112 glClear(GL_STENCIL_BUFFER_BIT);
113
114 //Set things up so we will draw to the stencil buffer, not to the screen.
115 glStencilFunc(GL_NEVER, 1, 1);
116 glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
117
118 glPushMatrix();
119
120 glTranslatef(-screen_pos.x/100, -screen_pos.y/100, 0);
121
122 point pos = lvl.player()->get_entity().midpoint();
123
124 //we want to make the circle shrink quickly at first, but then slow
125 //as it gets smaller, so we use the square of the amount to achieve this.
126 amount = (1.0 - amount)*(1.0 - amount);
127
128 //Draw a circle.
129 const float radius = amount*500;
130 std::vector<GLfloat>& varray = graphics::global_vertex_array();
131 varray.clear();
132 varray.push_back(pos.x);
133 varray.push_back(pos.y);
134 for(double angle = 0; angle < 3.1459*2.0; angle += 0.01) {
135 const double x = pos.x + radius*cos(angle);
136 const double y = pos.y + radius*sin(angle);
137 varray.push_back(x);
138 varray.push_back(y);
139 }
140
141 glVertexPointer(2, GL_FLOAT, 0, &varray.front());
142 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
143 glDrawArrays(GL_TRIANGLE_FAN, 0, varray.size()/2);
144 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
145
146 glPopMatrix();
147
148 //Now we've set the stencil to a circle, set things up so that the stencil
149 //will be used to draw only within the circle.
150 glStencilFunc(GL_EQUAL, 1, 1);
151 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
152
153 draw_scene(lvl, screen_pos);
154
155 //Now, reverse the rule; use the stencil to draw everything outside the circle - in this case, jet black.
156 glStencilFunc(GL_NOTEQUAL, 1, 1);
157
158 GLfloat varray2[8];
159
160 //populate the four screen corners
161 varray2[0] = 0;
162 varray2[1] = 0;
163
164 varray2[2] = graphics::screen_width();
165 varray2[3] = 0;
166
167 varray2[4] = 0;
168 varray2[5] = graphics::screen_height();
169
170 varray2[6] = graphics::screen_width();
171 varray2[7] = graphics::screen_height();
172
173 glVertexPointer(2, GL_FLOAT, 0, &varray2);
174 glColor4ub(0, 0, 0, 255);
175 glDisable(GL_TEXTURE_2D);
176
177 glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(varray2)/sizeof(GLfloat)/2);
178
179 glEnable(GL_TEXTURE_2D);
180
181
182 glDisable(GL_STENCIL_TEST);
183 }
184
185 void show_end_game()
186 {
187 const std::string msg = "to be continued...";
188 graphics::texture t(font::render_text(msg, graphics::color_white(), 48));
189 const int xpos = graphics::screen_width()/2 - t.width()/2;
190 const int ypos = graphics::screen_height()/2 - t.height()/2;
191 for(int n = 0; n <= msg.size(); ++n) {
192 const GLfloat percent = GLfloat(n)/GLfloat(msg.size());
193 SDL_Rect rect = {0, 0, graphics::screen_width(), graphics::screen_height()};
194 graphics::draw_rect(rect, graphics::color_black());
195 graphics::blit_texture(t, xpos, ypos, t.width()*percent, t.height(), 0.0,
196 0.0, 0.0, percent, 1.0);
197 SDL_GL_SwapBuffers();
198 SDL_Delay(40);
199 }
200
201 bool done = false;
202 while(!done) {
203 SDL_Event event;
204 while(SDL_PollEvent(&event)) {
205 switch(event.type) {
206 case SDL_QUIT:
207 case SDL_KEYDOWN:
208 done = true;
209 break;
210 }
211 }
212 joystick::update();
213 for(int n = 0; n != 6; ++n) {
214 if(joystick::button(n)) {
215 done = true;
216 }
217 }
218 }
219 }
220
221 }
222
223 level_runner::level_runner(boost::intrusive_ptr<level>& lvl, std::string& level_cfg, std::string& original_level_cfg)
224 : lvl_(lvl), level_cfg_(level_cfg), original_level_cfg_(original_level_cfg)
225 {
226 quit_ = false;
227
228 current_second_ = time(NULL);
229 current_fps_ = 0;
230 next_fps_ = 0;
231 current_cycles_ = 0;
232 next_cycles_ = 0;
233 current_delay_ = 0;
234 next_delay_ = 0;
235 current_draw_ = 0;
236 next_draw_ = 0;
237 current_flip_ = 0;
238 next_flip_ = 0;
239 current_process_ = 0;
240 next_process_ = 0;
241 current_events_ = 0;
242
243 nskip_draw_ = 0;
244
245 cycle = 0;
246 die_at = -1;
247 paused = false;
248 done = false;
249 start_time_ = SDL_GetTicks();
250 pause_time_ = -global_pause_time;
251 }
252
253 bool level_runner::play_level()
254 {
255 sound::stop_looped_sounds(NULL);
256
257 lvl_->set_as_current_level();
258 while(!done && !quit_) {
259 bool res = play_cycle();
260 if(!res) {
261 return quit_;
262 }
263 }
264
265 return quit_;
266 }
267
268 namespace {
269 void load_level_thread(const std::string& lvl, level** res) {
270 try {
271 *res = load_level(lvl);
272 } catch(const graphics::texture::worker_thread_error&) {
273 std::cerr << "LOAD LEVEL FAILED: MUST DO IN MAIN THREAD\n";
274 }
275 }
276 }
277
278 bool level_runner::play_cycle()
279 {
280 static settings_dialog settings_dialog;
281 if(controls::first_invalid_cycle() >= 0) {
282 lvl_->replay_from_cycle(controls::first_invalid_cycle());
283 controls::mark_valid();
284 }
285
286 if(controls::num_players() > 1) {
287 lvl_->backup();
288 }
289
290 const bool is_multiplayer = controls::num_players() > 1;
291
292 int desired_end_time = start_time_ + pause_time_ + global_pause_time + cycle*FrameTimeInMillis + FrameTimeInMillis;
293
294 if(!is_multiplayer) {
295 const int ticks = SDL_GetTicks();
296 if(desired_end_time < ticks) {
297 const int new_desired_end_time = ticks + FrameTimeInMillis;
298 pause_time_ += new_desired_end_time - desired_end_time;
299 desired_end_time = new_desired_end_time;
300 }
301 }
302
303 //record player movement every 5 cycles.
304 #if !TARGET_OS_IPHONE
305 if(cycle%5 == 0 && lvl_->player()) {
306 point p = lvl_->player()->get_entity().midpoint();
307
308 if(last_stats_point_level_ == lvl_->id()) {
309 stats::record_event(lvl_->id(), stats::record_ptr(
310 new stats::player_move_record(last_stats_point_, p)));
311 }
312
313 last_stats_point_ = p;
314 last_stats_point_level_ = lvl_->id();
315 }
316 #endif
317
318 if(die_at <= 0 && lvl_->players().size() == 1 && lvl_->player() && lvl_->player()->get_entity().hitpoints() <= 0) {
319 die_at = cycle;
320 }
321
322 if(die_at > 0 && cycle >= die_at + 30) {
323 die_at = -1;
324
325 //record stats of the player's death
326 lvl_->player()->get_entity().record_stats_movement();
327 stats::record_event(lvl_->id(), stats::record_ptr(new stats::die_record(lvl_->player()->get_entity().midpoint())));
328 last_stats_point_level_ = "";
329
330 entity_ptr save = lvl_->player()->get_entity().save_condition();
331 if(!save) {
332 return false;
333 }
334
335 prepare_transition_scene(*lvl_, last_draw_position());
336
337 preload_level(save->get_player_info()->current_level());
338 transition_scene(*lvl_, last_draw_position(), true, fade_scene);
339 sound::stop_looped_sounds(NULL);
340 level* new_level = load_level(save->get_player_info()->current_level());
341
342 if(!new_level->music().empty()) {
343 sound::play_music(new_level->music());
344 }
345
346 set_scene_title(new_level->title());
347 new_level->add_player(save);
348 new_level->set_as_current_level();
349 save->save_game();
350 save->handle_event(OBJECT_EVENT_LOAD_CHECKPOINT);
351 place_entity_in_level(*new_level, *save);
352 lvl_.reset(new_level);
353 last_draw_position() = screen_position();
354 } else if(lvl_->players().size() > 1) {
355 foreach(const entity_ptr& c, lvl_->players()) {
356 if(c->hitpoints() <= 0) {
357 //in multiplayer we respawn on death
358 c->respawn_player();
359 }
360 }
361 }
362
363 const level::portal* portal = lvl_->get_portal();
364 if(portal) {
365 //we might want to change the portal, so copy it and make it mutable.
366 level::portal mutable_portal = *portal;
367 portal = &mutable_portal;
368
369 level_cfg_ = portal->level_dest;
370 if(level_cfg_.empty()) {
371 //the portal is within the same level
372
373 if(portal->dest_label.empty() == false) {
374 const_entity_ptr dest_door = lvl_->get_entity_by_label(portal->dest_label);
375 if(dest_door) {
376 mutable_portal.dest = point(dest_door->x() + dest_door->teleport_offset_x()*dest_door->face_dir(), dest_door->y() + dest_door->teleport_offset_y());
377 mutable_portal.dest_starting_pos = false;
378 }
379 }
380
381 last_draw_position() = screen_position();
382 player_info* player = lvl_->player();
383 if(player) {
384 player->get_entity().set_pos(portal->dest);
385 }
386 } else {
387 //the portal is to another level
388
389 if (preferences::load_compiled())
390 {
391 level::summary summary = level::get_summary(level_cfg_);
392 if(!summary.music.empty()) {
393 sound::play_music(summary.music);
394 }
395 }
396
397 prepare_transition_scene(*lvl_, last_draw_position());
398
399 const std::string transition = portal->transition;
400 if(transition == "flip") {
401 transition_scene(*lvl_, last_draw_position(), true, flip_scene);
402 } else if(transition == "instant") {
403 //do nothing
404 } else if(transition != "fade" && is_stencil_buffer_available()) {
405 transition_scene(*lvl_, last_draw_position(), true, iris_scene);
406 } else {
407 preload_level(level_cfg_);
408 transition_scene(*lvl_, last_draw_position(), true, fade_scene);
409 }
410
411 sound::stop_looped_sounds(NULL);
412
413 boost::intrusive_ptr<level> new_level(load_level(level_cfg_));
414 if (!preferences::load_compiled() && !new_level->music().empty())
415 sound::play_music(new_level->music());
416
417 if(portal->dest_label.empty() == false) {
418 //the label of an object was specified as an entry point,
419 //so set our position there.
420 const_entity_ptr dest_door = new_level->get_entity_by_label(portal->dest_label);
421 if(dest_door) {
422 mutable_portal.dest = point(dest_door->x() + dest_door->teleport_offset_x()*dest_door->face_dir(), dest_door->y() + dest_door->teleport_offset_y());
423 mutable_portal.dest_starting_pos = false;
424 }
425 }
426
427 new_level->set_as_current_level();
428
429 set_scene_title(new_level->title());
430 point dest = portal->dest;
431 if(portal->dest_str.empty() == false) {
432 dest = new_level->get_dest_from_str(portal->dest_str);
433 } else if(portal->dest_starting_pos) {
434 const player_info* new_player = new_level->player();
435 if(new_player) {
436 dest = point(new_player->get_entity().x(), new_player->get_entity().y());
437 }
438 }
439
440 player_info* player = lvl_->player();
441 if(player && portal->saved_game == false) {
442 player->get_entity().set_pos(dest);
443 new_level->add_player(&player->get_entity());
444 player->get_entity().move_to_standing(*new_level);
445 player->get_entity().handle_event("enter_level");
446 } else {
447 player = new_level->player();
448 }
449
450 //if we're in a multiplayer level then going through a portal
451 //will take us out of multiplayer.
452 if(lvl_->players().size() != new_level->players().size()) {
453 lvl_ = new_level;
454 done = true;
455 throw multiplayer_exception();
456 }
457
458 lvl_ = new_level;
459 last_draw_position() = screen_position();
460
461 if(transition == "flip") {
462 transition_scene(*lvl_, last_draw_position(), false, flip_scene);
463 }
464
465 //we always want to exit this function so that we don't
466 //draw the new level when it hasn't had a chance to process.
467 return !done;
468 }
469 }
470
471 joystick::update();
472 bool should_pause = false;
473
474 if(message_dialog::get() == NULL) {
475 SDL_Event event;
476 while(SDL_PollEvent(&event)) {
477 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
478 should_pause = settings_dialog.handle_event(event);
479 #endif
480 switch(event.type) {
481 case SDL_QUIT: {
482 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
483 wml::node_ptr node = lvl_->write();
484 if(sound::current_music().empty() == false) {
485 node->set_attr("music", sound::current_music());
486 }
487
488 sys::write_file(preferences::auto_save_file_path(), wml::output(node));
489 sys::write_file(std::string(preferences::auto_save_file_path()) + ".stat", "1");
490 #endif
491 done = true;
492 quit_ = true;
493 break;
494 }
495 case SDL_VIDEORESIZE: {
496 const SDL_ResizeEvent* const resize = reinterpret_cast<SDL_ResizeEvent*>(&event);
497
498 preferences::set_actual_screen_width(resize->w);
499 preferences::set_actual_screen_height(resize->h);
500 SDL_SetVideoMode(resize->w,resize->h,0,SDL_OPENGL|SDL_RESIZABLE|(preferences::fullscreen() ? SDL_FULLSCREEN : 0));
501 continue;
502 }
503 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
504 case SDL_WINDOWEVENT:
505 if (event.window.event == SDL_WINDOWEVENT_MINIMIZED)
506 {
507 paused = true;
508 } else if (event.window.event == SDL_WINDOWEVENT_RESTORED) {
509 paused = false;
510 }
511 break;
512 #endif
513 case SDL_KEYDOWN: {
514 const SDLMod mod = SDL_GetModState();
515 const SDLKey key = event.key.keysym.sym;
516 if(key == SDLK_ESCAPE) {
517 should_pause = true;
518 break;
519 } else if(key == SDLK_d && (mod&KMOD_CTRL)) {
520 show_debug_console();
521
522 } else if(key == SDLK_e && (mod&KMOD_CTRL)) {
523 #ifndef NO_EDITOR
524 pause_time_ -= SDL_GetTicks();
525 editor::edit(lvl_->id().c_str(), last_draw_position().x/100, last_draw_position().y/100);
526 lvl_.reset(load_level(editor::last_edited_level().c_str()));
527 lvl_->set_as_current_level();
528 pause_time_ += SDL_GetTicks();
529 #endif
530 } else if(key == SDLK_s && (mod&KMOD_CTRL)) {
531 std::cerr << "SAVING...\n";
532 std::string data;
533
534 wml::node_ptr lvl_node = wml::deep_copy(lvl_->write());
535 if(sound::current_music().empty() == false) {
536 lvl_node->set_attr("music", sound::current_music());
537 }
538 wml::write(lvl_node, data);
539 sys::write_file(preferences::save_file_path(), data);
540 } else if(key == SDLK_s && (mod&KMOD_ALT)) {
541 IMG_SaveFrameBuffer((std::string(preferences::user_data_path()) + "screenshot.png").c_str(), 5);
542 } else if(key == SDLK_w && (mod&KMOD_CTRL)) {
543 //warp to another level.
544 std::vector<std::string> levels = get_known_levels();
545 assert(!levels.empty());
546 int index = std::find(levels.begin(), levels.end(), lvl_->id()) - levels.begin();
547 index = (index+1)%levels.size();
548 level* new_level = load_level(levels[index]);
549 new_level->set_as_current_level();
550
551 if(!new_level->music().empty()) {
552 sound::play_music(new_level->music());
553 }
554
555 set_scene_title(new_level->title());
556 lvl_.reset(new_level);
557 } else if(key == SDLK_l && (mod&KMOD_CTRL)) {
558 preferences::set_use_pretty_scaling(!preferences::use_pretty_scaling());
559 graphics::surface_cache::clear();
560 graphics::texture::clear_cache();
561 } else if(key == SDLK_i && lvl_->player()) {
562 // INVENTORY CURRENTLY DISABLED
563 // pause_scope pauser;
564 // show_inventory(*lvl_, lvl_->player()->get_entity());
565 } else if(key == SDLK_m && mod & KMOD_CTRL) {
566 sound::mute(!sound::muted()); //toggle sound
567 } else if(key == SDLK_p && mod & KMOD_CTRL) {
568 paused = !paused;
569 } else if(key == SDLK_p && mod & KMOD_ALT) {
570 preferences::set_use_pretty_scaling(!preferences::use_pretty_scaling());
571 graphics::texture::clear_textures();
572 } else if(key == SDLK_f && mod & KMOD_CTRL) {
573 preferences::set_fullscreen(!preferences::fullscreen());
574 SDL_SetVideoMode(graphics::screen_width(),graphics::screen_height(),0,SDL_OPENGL|(preferences::fullscreen() ? SDL_FULLSCREEN : 0));
575 }
576 break;
577 }
578 default:
579 break;
580 }
581 }
582
583 if (should_pause)
584 {
585 settings_dialog.reset();
586 const PAUSE_GAME_RESULT result = show_pause_game_dialog();
587
588 handle_pause_game_result(result);
589 }
590 }
591
592 if(message_dialog::get()) {
593 message_dialog::get()->process();
594 pause_time_ += FrameTimeInMillis;
595 } else {
596 if (!paused) {
597 const int start_process = SDL_GetTicks();
598
599 try {
600 lvl_->process();
601 } catch(interrupt_game_exception& e) {
602 handle_pause_game_result(e.result);
603 }
604
605 next_process_ += (SDL_GetTicks() - start_process);
606 } else {
607 pause_time_ += FrameTimeInMillis;
608 }
609 }
610
611 if(lvl_->end_game()) {
612 transition_scene(*lvl_, last_draw_position(), false, fade_scene);
613 show_end_game();
614 done = true;
615 return true;
616 }
617
618 const int MaxSkips = 3;
619
620 const int start_draw = SDL_GetTicks();
621 if(start_draw < desired_end_time || nskip_draw_ >= MaxSkips) {
622 lvl_->process_draw();
623 draw_scene(*lvl_, last_draw_position());
624 performance_data perf = { current_fps_, current_cycles_, current_delay_, current_draw_, current_process_, current_flip_, cycle, current_events_, profiling_summary_ };
625 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
626 settings_dialog.draw();
627 #endif
628
629 if(preferences::show_fps()) {
630 draw_fps(*lvl_, perf);
631 }
632
633 next_draw_ += (SDL_GetTicks() - start_draw);
634
635 const int start_flip = SDL_GetTicks();
636 SDL_GL_SwapBuffers();
637 next_flip_ += (SDL_GetTicks() - start_flip);
638 ++next_fps_;
639 nskip_draw_ = 0;
640 } else {
641 ++nskip_draw_;
642 }
643
644 ++next_cycles_;
645
646 const time_t this_second = time(NULL);
647 if(this_second != current_second_) {
648 current_second_ = this_second;
649 current_fps_ = next_fps_;
650 current_cycles_ = next_cycles_;
651 current_delay_ = next_delay_;
652 current_draw_ = next_draw_;
653 current_flip_ = next_flip_;
654 current_process_ = next_process_;
655 current_events_ = custom_object::events_handled_per_second;
656 next_fps_ = 0;
657 next_cycles_ = 0;
658 next_delay_ = 0;
659 next_draw_ = 0;
660 next_process_ = 0;
661 next_flip_ = 0;
662 custom_object::events_handled_per_second = 0;
663
664 profiling_summary_ = formula_profiler::get_profile_summary();
665 }
666
667 const int raw_wait_time = desired_end_time - SDL_GetTicks();
668 const int wait_time = std::max<int>(1, desired_end_time - SDL_GetTicks());
669 next_delay_ += wait_time;
670 if (wait_time != 1) SDL_Delay(wait_time);
671
672 if (!paused) ++cycle;
673
674 return !quit_;
675 }
676
677 void level_runner::show_debug_console()
678 {
679 #ifndef NO_EDITOR
680 pause_time_ -= SDL_GetTicks();
681 gui::text_entry_widget entry;
682 entry.set_loc(10, 200);
683
684 bool done = false;
685 while(!done) {
686 SDL_Event event;
687 while(SDL_PollEvent(&event)) {
688 if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) {
689 done = true;
690 break;
691 } else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_RETURN) {
692 const std::string script = entry.text();
693 game_logic::formula f(script, &get_custom_object_functions_symbol_table());
694 variant v = f.execute(lvl_->player()->get_entity());
695 lvl_->player()->get_entity().execute_command(v);
696 entry.set_text("");
697 } else {
698 entry.process_event(event, false);
699 }
700 }
701
702 draw_scene(*lvl_, last_draw_position());
703 entry.draw();
704 SDL_GL_SwapBuffers();
705 SDL_Delay(20);
706 }
707
708 pause_time_ += SDL_GetTicks();
709 #endif
710 }
711
712 namespace {
713 //variable to mark if we are already pausing. If so, don't pause again
714 //based on a new pause scope.
715 bool pause_scope_active = false;
716 }
717
718 pause_scope::pause_scope() : ticks_(SDL_GetTicks()), active_(!pause_scope_active)
719 {
720 pause_scope_active = true;
721 }
722
723 pause_scope::~pause_scope()
724 {
725 if(active_) {
726 const int t = SDL_GetTicks() - ticks_;
727 global_pause_time += t;
728 pause_scope_active = false;
729 }
730 }
731
732 void level_runner::handle_pause_game_result(PAUSE_GAME_RESULT result)
733 {
734 if(result == PAUSE_GAME_QUIT) {
735 //record a quit event in stats
736 if(lvl_->player()) {
737 lvl_->player()->get_entity().record_stats_movement();
738 stats::record_event(lvl_->id(), stats::record_ptr(new stats::quit_record(lvl_->player()->get_entity().midpoint())));
739 }
740
741 done = true;
742 quit_ = true;
743 } else if(result == PAUSE_GAME_GO_TO_TITLESCREEN) {
744 done = true;
745 original_level_cfg_ = "titlescreen.cfg";
746 }
747 }
0 #ifndef LEVEL_RUNNER_HPP_INCLUDED
1 #define LEVEL_RUNNER_HPP_INCLUDED
2
3 #include <string>
4
5 #include <boost/scoped_ptr.hpp>
6
7 #include "geometry.hpp"
8 #include "key.hpp"
9 #include "level.hpp"
10 #include "pause_game_dialog.hpp"
11
12 //an exception which is thrown if we go through a portal which takes us
13 //to a level with a different number of players, which indicates we are going
14 //into or out of multiplayer.
15 struct multiplayer_exception {
16 };
17
18 class level_runner {
19 public:
20 level_runner(boost::intrusive_ptr<level>& lvl, std::string& level_cfg, std::string& original_level_cfg);
21
22 bool play_level();
23 bool play_cycle();
24 private:
25 void show_debug_console();
26 void handle_pause_game_result(PAUSE_GAME_RESULT result);
27 boost::intrusive_ptr<level>& lvl_;
28 std::string& level_cfg_;
29 std::string& original_level_cfg_;
30
31 bool quit_;
32 boost::intrusive_ptr<level> start_lvl_;
33 time_t current_second_;
34
35 int current_fps_, next_fps_, current_cycles_, next_cycles_, current_delay_, next_delay_,
36 current_draw_, next_draw_, current_process_, next_process_,
37 current_flip_, next_flip_, current_events_;
38 std::string profiling_summary_;
39 int nskip_draw_;
40
41 CKey key;
42
43 int cycle;
44 int die_at;
45 bool paused;
46 bool done;
47 int start_time_;
48 int pause_time_;
49
50 point last_stats_point_;
51 std::string last_stats_point_level_;
52 };
53
54 class pause_scope
55 {
56 int ticks_;
57 bool active_;
58 public:
59 pause_scope();
60 ~pause_scope();
61 };
62
63
64 #endif
0 #include "foreach.hpp"
1 #include "level_solid_map.hpp"
2
3 level_solid_map::level_solid_map()
4 {
5 }
6
7 level_solid_map::~level_solid_map()
8 {
9 clear();
10 }
11
12 tile_solid_info& level_solid_map::insert_or_find(const tile_pos& pos)
13 {
14 tile_solid_info** result = insert_raw(pos);
15 if(!*result) {
16 *result = new tile_solid_info;
17 }
18
19 return **result;
20 }
21
22 tile_solid_info** level_solid_map::insert_raw(const tile_pos& pos)
23 {
24 row* r = NULL;
25 if(pos.second >= 0) {
26 if(positive_rows_.size() <= pos.second) {
27 positive_rows_.resize(pos.second + 1);
28 }
29
30 r = &positive_rows_[pos.second];
31 } else {
32 const int index = -(pos.second+1);
33 if(negative_rows_.size() <= index) {
34 negative_rows_.resize(index + 1);
35 }
36
37 r = &negative_rows_[index];
38 }
39
40 if(pos.first >= 0) {
41 if(r->positive_cells.size() <= pos.first) {
42 r->positive_cells.resize(pos.first + 1);
43 }
44
45 return &r->positive_cells[pos.first];
46 } else {
47 const int index = -(pos.first+1);
48 if(r->negative_cells.size() <= index) {
49 r->negative_cells.resize(index + 1);
50 }
51
52 return &r->negative_cells[index];
53 }
54 }
55
56 const tile_solid_info* level_solid_map::find(const tile_pos& pos) const
57 {
58 const row* r = NULL;
59 if(pos.second >= 0) {
60 if(pos.second < positive_rows_.size()) {
61 r = &positive_rows_[pos.second];
62 } else {
63 return NULL;
64 }
65 } else {
66 const int index = -(pos.second+1);
67 if(index < negative_rows_.size()) {
68 r = &negative_rows_[index];
69 } else {
70 return NULL;
71 }
72 }
73
74 if(pos.first >= 0) {
75 if(pos.first < r->positive_cells.size()) {
76 return r->positive_cells[pos.first];
77 } else {
78 return NULL;
79 }
80 } else {
81 const int index = -(pos.first+1);
82 if(index < r->negative_cells.size()) {
83 return r->negative_cells[index];
84 } else {
85 return NULL;
86 }
87 }
88 }
89
90 void level_solid_map::erase(const tile_pos& pos)
91 {
92 tile_solid_info** info = insert_raw(pos);
93 delete *info;
94 *info = NULL;
95 }
96
97 void level_solid_map::clear()
98 {
99 foreach(row& r, positive_rows_) {
100 foreach(tile_solid_info* info, r.positive_cells) {
101 delete info;
102 }
103
104 foreach(tile_solid_info* info, r.negative_cells) {
105 delete info;
106 }
107 }
108
109 foreach(row& r, negative_rows_) {
110 foreach(tile_solid_info* info, r.positive_cells) {
111 delete info;
112 }
113
114 foreach(tile_solid_info* info, r.negative_cells) {
115 delete info;
116 }
117 }
118
119 positive_rows_.clear();
120 negative_rows_.clear();
121 }
0 #ifndef LEVEL_SOLID_MAP_HPP_INCLUDED
1 #define LEVEL_SOLID_MAP_HPP_INCLUDED
2
3 #include <bitset>
4 #include <map>
5 #include <vector>
6
7 static const int TileSize = 32;
8
9 typedef std::pair<int,int> tile_pos;
10 typedef std::bitset<TileSize*TileSize> tile_bitmap;
11
12 struct tile_solid_info {
13 tile_solid_info() : all_solid(false), friction(0), traction(0), damage(-1)
14 {}
15 tile_bitmap bitmap;
16 bool all_solid;
17 int friction;
18 int traction;
19 int damage;
20 };
21
22 class level_solid_map {
23 public:
24 level_solid_map();
25 ~level_solid_map();
26 tile_solid_info& insert_or_find(const tile_pos& pos);
27 const tile_solid_info* find(const tile_pos& pos) const;
28 void erase(const tile_pos& pos);
29 void clear();
30 private:
31 level_solid_map(const level_solid_map&);
32 void operator=(const level_solid_map&);
33
34 tile_solid_info** insert_raw(const tile_pos& pos);
35
36 struct row {
37 std::vector<tile_solid_info*> positive_cells, negative_cells;
38 };
39
40 std::vector<row> positive_rows_, negative_rows_;
41 };
42
43 #endif
0 #include <assert.h>
1
2 #include "asserts.hpp"
3 #include "concurrent_cache.hpp"
4 #include "filesystem.hpp"
5 #include "level.hpp"
6 #include "load_level.hpp"
7 #include "preferences.hpp"
8 #include "preprocessor.hpp"
9 #include "texture.hpp"
10 #include "thread.hpp"
11 #include "wml_parser.hpp"
12
13 namespace {
14 typedef concurrent_cache<std::string, wml::const_node_ptr> level_wml_map;
15 level_wml_map& wml_cache() {
16 static level_wml_map instance;
17 return instance;
18 }
19
20 std::map<std::string, threading::thread*>& wml_threads()
21 {
22 static std::map<std::string, threading::thread*> instance;
23 return instance;
24 }
25
26 class wml_loader {
27 std::string lvl_;
28 public:
29 wml_loader(const std::string& lvl) : lvl_(lvl)
30 {}
31 void operator()() {
32 static const std::string path = preferences::load_compiled() ? "data/compiled/level/" : preferences::level_path();
33 const std::string filename = path + lvl_;
34 try {
35 wml::const_node_ptr node(wml::parse_wml(preprocess(sys::read_file(filename))));
36 wml_cache().put(lvl_, node);
37 } catch(...) {
38 std::cerr << "FAILED TO LOAD " << filename << "\n";
39 ASSERT_LOG(false, "FAILED TO LOAD");
40 }
41 }
42 };
43 }
44
45 void clear_level_wml()
46 {
47 wml_cache().clear();
48 }
49
50 void preload_level_wml(const std::string& lvl)
51 {
52 if(lvl == "save.cfg" || lvl == "autosave.cfg") {
53 return;
54 }
55
56 if(wml_cache().count(lvl)) {
57 return;
58 }
59
60 wml_cache().put(lvl, wml::const_node_ptr());
61 wml_threads()[lvl] = new threading::thread(wml_loader(lvl));
62 }
63
64 wml::const_node_ptr load_level_wml(const std::string& lvl)
65 {
66 if(lvl == "save.cfg" || lvl == "autosave.cfg") {
67 std::string filename;
68 if(lvl == "save.cfg") {
69 filename = preferences::save_file_path();
70 } else {
71 filename = preferences::auto_save_file_path();
72 }
73
74 return wml::parse_wml(preprocess(sys::read_file(filename)));
75 }
76
77 if(wml_cache().count(lvl)) {
78 std::map<std::string, threading::thread*>::iterator t = wml_threads().find(lvl);
79 if(t != wml_threads().end()) {
80 delete t->second;
81 wml_threads().erase(t);
82 }
83
84 return wml_cache().get(lvl);
85 }
86
87 wml_loader loader(lvl);
88 loader();
89 return load_level_wml_nowait(lvl);
90 }
91
92 wml::const_node_ptr load_level_wml_nowait(const std::string& lvl)
93 {
94 return wml_cache().get(lvl);
95 }
96
97 namespace {
98 typedef std::map<std::string, std::pair<threading::thread*, level*> > level_map;
99 level_map levels_loading;
100
101 threading::mutex& levels_loading_mutex() {
102 static threading::mutex m;
103 return m;
104 }
105
106 class level_loader {
107 std::string lvl_;
108 public:
109 level_loader(const std::string& lvl) : lvl_(lvl)
110 {}
111 void operator()() {
112 level* lvl = NULL;
113 try {
114 lvl = new level(lvl_);
115 } catch(const graphics::texture::worker_thread_error&) {
116 //we can't load the level in here, we must do it in the main thread.
117 std::cerr << "LOAD LEVEL FAILURE: " << lvl << " MUST LOAD IN "
118 "MAIN THREAD\n";
119 }
120 threading::lock lck(levels_loading_mutex());
121 levels_loading[lvl_].second = lvl;
122 }
123 };
124
125 }
126
127 load_level_manager::load_level_manager()
128 {}
129
130 load_level_manager::~load_level_manager()
131 {
132 for(level_map::iterator i = levels_loading.begin(); i != levels_loading.end(); ++i) {
133 i->second.first->join();
134 delete i->second.first;
135 delete i->second.second;
136 }
137
138 levels_loading.clear();
139 }
140
141 void preload_level(const std::string& lvl)
142 {
143 //--TODO: Currently multi-threaded pre-loading causes weird crashes.
144 //-- need to fix this!!
145 assert(!lvl.empty());
146 threading::lock lck(levels_loading_mutex());
147 if(levels_loading.count(lvl) == 0) {
148 levels_loading[lvl].first = new threading::thread(level_loader(lvl));
149 }
150 }
151
152 level* load_level(const std::string& lvl)
153 {
154 std::cerr << "START LOAD LEVEL\n";
155 level_map::iterator itor;
156 {
157 threading::lock lck(levels_loading_mutex());
158 itor = levels_loading.find(lvl);
159 if(itor == levels_loading.end()) {
160 level* res = new level(lvl);
161 res->finish_loading();
162 fprintf(stderr, "LOADED LEVEL: %p\n", res);
163 return res;
164 }
165 }
166
167 itor->second.first->join();
168 delete itor->second.first;
169 level* res = itor->second.second;
170 if(res == NULL) {
171 res = new level(lvl);
172 }
173 res->finish_loading();
174 levels_loading.erase(itor);
175 std::cerr << "FINISH LOAD LEVEL\n";
176 return res;
177 }
178
179 namespace {
180 bool hidden_file(const std::string& filename) {
181 return !filename.empty() && filename[0] == '.';
182 }
183 }
184
185 std::vector<std::string> get_known_levels()
186 {
187 std::vector<std::string> files;
188 sys::get_files_in_dir(preferences::level_path(), &files);
189 files.erase(std::remove_if(files.begin(), files.end(), hidden_file), files.end());
190 return files;
191 }
0 #ifndef LOAD_LEVEL_HPP_INCLUDED
1 #define LOAD_LEVEL_HPP_INCLUDED
2
3 #include <string>
4 #include <vector>
5
6 #include "wml_node_fwd.hpp"
7
8 class level;
9
10 struct load_level_manager {
11 load_level_manager();
12 ~load_level_manager();
13 };
14
15 void clear_level_wml();
16 void preload_level_wml(const std::string& lvl);
17 wml::const_node_ptr load_level_wml(const std::string& lvl);
18 wml::const_node_ptr load_level_wml_nowait(const std::string& lvl);
19
20 void preload_level(const std::string& lvl);
21 level* load_level(const std::string& lvl);
22
23 std::vector<std::string> get_known_levels();
24
25 #endif
0 #include "asserts.hpp"
1 #include "concurrent_cache.hpp"
2 #include "filesystem.hpp"
3 #include "level.hpp"
4 #include "load_level.hpp"
5 #include "preferences.hpp"
6 #include "preprocessor.hpp"
7 #include "wml_parser.hpp"
8
9 namespace {
10 typedef concurrent_cache<std::string, wml::const_node_ptr> level_wml_map;
11 level_wml_map& wml_cache() {
12 static level_wml_map instance;
13 return instance;
14 }
15
16 class wml_loader {
17 std::string lvl_;
18 public:
19 wml_loader(const std::string& lvl) : lvl_(lvl)
20 {}
21 void operator()() {
22 static const std::string path = preferences::load_compiled() ? "data/compiled/level/" : preferences::level_path();
23 const std::string filename = path + lvl_;
24 wml::const_node_ptr node(wml::parse_wml(preprocess(sys::read_file(filename))));
25 wml_cache().put(lvl_, node);
26 }
27 };
28 }
29
30 void clear_level_wml()
31 {
32 wml_cache().clear();
33 }
34
35 void preload_level_wml(const std::string& lvl)
36 {
37 if(lvl == "save.cfg" || lvl == "autosave.cfg") {
38 return;
39 }
40
41 if(wml_cache().count(lvl)) {
42 return;
43 }
44
45 wml_loader loader(lvl);
46 loader();
47 }
48
49 wml::const_node_ptr load_level_wml(const std::string& lvl)
50 {
51 if(lvl == "save.cfg" || lvl == "autosave.cfg") {
52 std::string filename;
53 if(lvl == "save.cfg") {
54 filename = preferences::save_file_path();
55 } else {
56 filename = preferences::auto_save_file_path();
57 }
58
59 return wml::parse_wml(preprocess(sys::read_file(filename)));
60 }
61
62 if(wml_cache().count(lvl)) {
63 return wml_cache().get(lvl);
64 }
65
66 wml_loader loader(lvl);
67 loader();
68 return load_level_wml_nowait(lvl);
69 }
70
71 wml::const_node_ptr load_level_wml_nowait(const std::string& lvl)
72 {
73 return wml_cache().get(lvl);
74 }
75
76 load_level_manager::load_level_manager()
77 {
78 }
79
80 load_level_manager::~load_level_manager()
81 {
82 }
83
84 void preload_level(const std::string& lvl)
85 {
86 }
87
88 level* load_level(const std::string& lvl)
89 {
90 level* res = new level(lvl);
91 res->finish_loading();
92 return res;
93 }
94
95 namespace {
96 bool not_cfg_file(const std::string& filename) {
97 return filename.size() < 4 || !std::equal(filename.end() - 4, filename.end(), ".cfg");
98 }
99 }
100
101 std::vector<std::string> get_known_levels()
102 {
103 std::vector<std::string> files;
104 sys::get_files_in_dir(preferences::level_path(), &files);
105 files.erase(std::remove_if(files.begin(), files.end(), not_cfg_file), files.end());
106 return files;
107 }
0 #include <string>
1 #include <iostream>
2
3 #include "loading_screen.hpp"
4 #include "wml_node.hpp"
5 #include "custom_object_type.hpp"
6 #include "wml_utils.hpp"
7 #include "raster.hpp"
8 #include "graphical_font.hpp"
9 #include "geometry.hpp"
10 #include "texture.hpp"
11
12 loading_screen::loading_screen (int items) : items_(items), status_(0)
13 {
14 background_ = graphics::texture::get("backgrounds/loading_screen.png");
15 }
16
17 void loading_screen::load (wml::const_node_ptr node)
18 {
19 //custom_object_type::get("frogatto_playable");
20 FOREACH_WML_CHILD(preload_node, node, "preload")
21 {
22 draw_and_increment(wml::get_str(preload_node, "message"));
23 if (wml::get_str(preload_node, "type") == "object")
24 {
25 custom_object_type::get(wml::get_str(preload_node, "name"));
26 } else if (wml::get_str(preload_node, "type") == "texture") {
27 graphics::texture::get(wml::get_str(preload_node, "name"));
28 }
29 }
30 }
31
32 void loading_screen::draw (const std::string& message)
33 {
34 std::cerr << "*** Drawing loading screen with message: " << message << "\n";
35 const int bar_width = 100;
36 const int bar_height = 10;
37 int screen_w = graphics::screen_width();
38 int screen_h = graphics::screen_height();
39
40 graphics::prepare_raster();
41 glClearColor(0.0, 0.0, 0.0, 0.0);
42 glClear(GL_COLOR_BUFFER_BIT);
43
44 int bg_w = background_.width();
45 int bg_h = background_.height();
46 graphics::blit_texture(background_, screen_w/2-bg_w, screen_h/2-bg_h, bg_w*2, bg_h*2);
47
48 int bar_origin_x = graphics::screen_width()/2 - bar_width/2;
49 rect bg(screen_w/2 - bar_width/2, screen_h/2 - bar_height/2, bar_width, bar_height);
50 graphics::draw_rect(bg, graphics::color(96, 96, 96, 255));
51 float amount_done = (float)status_ / (float)items_;
52 rect bar(screen_w/2 - bar_width/2, screen_h/2 - bar_height/2, bar_width*amount_done, bar_height);
53 graphics::draw_rect(bar, graphics::color(255, 255, 255, 255));
54
55 const_graphical_font_ptr font = graphical_font::get("door_label");
56 rect text_size = font->dimensions(message);
57 font->draw(screen_w/2 - text_size.w()/2, screen_h/2 + bar_height/2 + 5, message);
58
59 SDL_GL_SwapBuffers();
60 //SDL_Delay(500); //make it possible to see on fast computers; for debugging
61 }
62
63 void loading_screen::increment_status ()
64 {
65 status_++;
66 }
67
68 void loading_screen::set_number_of_items (int items)
69 {
70 items_ = items;
71 }
0 #ifndef LOADING_SCREEN_HPP_INCLUDED
1 #define LOADING_SCREEN_HPP_INCLUDED
2
3 #include <string>
4
5 #include "wml_node_fwd.hpp"
6 #include "texture.hpp"
7 #include "graphical_font.hpp"
8
9 class loading_screen
10 {
11 public:
12 loading_screen (int items=0);
13 void load (wml::const_node_ptr node); // preload objects defined by preload children of node, blocking, and calling draw automatically
14 void draw (const std::string& message);
15 void increment_status ();
16 void draw_and_increment (const std::string& message) {draw(message); increment_status();}
17 void set_number_of_items (int items);
18
19 private:
20 int items_; // number of items we'll load
21 int status_; // how many items we've loaded so far
22 graphics::texture background_;
23 };
24
25 #endif
0 #include <SDL.h>
1 #ifndef SDL_VIDEO_OPENGL_ES
2 #include <GL/glew.h>
3 #endif
4 #include <GL/gl.h>
5 #include <GL/glu.h>
6 #if defined(__APPLE__) && !(TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE)
7 #include <OpenGL/OpenGL.h>
8 #endif
9 #include <algorithm>
10 #include <cmath>
11 #include <iostream>
12 #include <cstdio>
13
14 #include <boost/bind.hpp>
15 #include <boost/lexical_cast.hpp>
16 #include <boost/scoped_ptr.hpp>
17
18 #include "asserts.hpp"
19 #include "controls.hpp"
20 #include "custom_object.hpp"
21 #include "custom_object_functions.hpp"
22 #include "custom_object_type.hpp"
23 #include "draw_scene.hpp"
24 #ifndef NO_EDITOR
25 #include "editor.hpp"
26 #endif
27 #include "filesystem.hpp"
28 #include "font.hpp"
29 #include "foreach.hpp"
30 #include "formula_profiler.hpp"
31 #include "framed_gui_element.hpp"
32 #include "graphical_font.hpp"
33 #include "gui_section.hpp"
34 #include "inventory.hpp"
35 #include "iphone_device_info.h"
36 #include "joystick.hpp"
37 #include "key.hpp"
38 #include "level.hpp"
39 #include "level_object.hpp"
40 #include "level_runner.hpp"
41 #include "load_level.hpp"
42 #include "loading_screen.hpp"
43 #include "message_dialog.hpp"
44 #include "multiplayer.hpp"
45 #include "player_info.hpp"
46 #include "preferences.hpp"
47 #include "preprocessor.hpp"
48 #include "raster.hpp"
49 #include "sound.hpp"
50 #include "stats.hpp"
51 #include "string_utils.hpp"
52 #include "surface_cache.hpp"
53 #include "texture.hpp"
54 #include "tile_map.hpp"
55 #include "unit_test.hpp"
56 #include "wml_node.hpp"
57 #include "wml_parser.hpp"
58 #include "wml_schema.hpp"
59 #include "wml_utils.hpp"
60 #include "wml_writer.hpp"
61
62 namespace {
63
64 bool show_title_screen(std::string& level_cfg)
65 {
66 //currently the titlescreen is disabled.
67 return false;
68 }
69
70 }
71
72 extern "C" int main(int argc, char** argv)
73 {
74 if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_JOYSTICK) < 0) {
75 std::cerr << "could not init SDL\n";
76 return -1;
77 }
78
79
80 // SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
81
82 #ifdef NO_STDERR
83 std::freopen("/dev/null", "w", stderr);
84 std::cerr.sync_with_stdio(true);
85 #endif
86 std::string level_cfg = "titlescreen.cfg";
87 bool unit_tests_only = false, skip_tests = false;
88 bool run_benchmarks = false;
89 std::vector<std::string> benchmarks_list;
90 std::string utility_program;
91 std::vector<std::string> util_args;
92 std::string server = "wesnoth.org";
93
94 const char* profile_output = NULL;
95 std::string profile_output_buf;
96
97 std::string orig_level_cfg = level_cfg;
98 std::string override_level_cfg = "";
99
100 for(int n = 1; n < argc; ++n) {
101 const std::string arg(argv[n]);
102 std::string arg_name, arg_value;
103 std::string::const_iterator equal = std::find(arg.begin(), arg.end(), '=');
104 if(equal != arg.end()) {
105 arg_name = std::string(arg.begin(), equal);
106 arg_value = std::string(equal+1, arg.end());
107 }
108
109 if(arg_name == "--profile" || arg == "--profile") {
110 profile_output_buf = arg_value;
111 profile_output = profile_output_buf.c_str();
112 } else if(arg_name == "--utility") {
113 utility_program = arg_value;
114 for(++n; n < argc; ++n) {
115 const std::string arg(argv[n]);
116 util_args.push_back(arg);
117 }
118
119 break;
120 } else if(arg == "--benchmarks") {
121 run_benchmarks = true;
122 } else if(arg_name == "--benchmarks") {
123 run_benchmarks = true;
124 benchmarks_list = util::split(arg_value);
125 } else if(arg == "--tests") {
126 unit_tests_only = true;
127 } else if(arg == "--no-tests") {
128 skip_tests = true;
129 } else if(arg == "--width" && n+1 < argc) {
130 std::string w(argv[++n]);
131 preferences::set_actual_screen_width(boost::lexical_cast<int>(w));
132 } else if(arg == "--height" && n+1 < argc) {
133 std::string h(argv[++n]);
134 preferences::set_actual_screen_height(boost::lexical_cast<int>(h));
135 } else if(arg == "--level" && n+1 < argc) {
136 override_level_cfg = argv[++n];
137 } else if(arg == "--host" && n+1 < argc) {
138 server = argv[++n];
139 } else if(arg == "--compiled") {
140 preferences::set_load_compiled(true);
141 } else if(arg == "--no-compiled") {
142 preferences::set_load_compiled(false);
143 } else {
144 const bool res = preferences::parse_arg(argv[n]);
145 if(!res) {
146 std::cerr << "unrecognized arg: '" << arg << "'\n";
147 return 0;
148 }
149 }
150 }
151
152 preferences::expand_data_paths();
153
154 std::cerr << "Preferences dir: " << preferences::user_data_path() << '\n';
155
156 //make sure that the user data path exists.
157 if(!preferences::setup_preferences_dir()) {
158 std::cerr << "cannot create preferences dir!\n";
159 }
160
161 #ifdef TARGET_OS_IPHONE
162 //on the iPhone, try to restore the auto-save if it exists
163 if(sys::file_exists(preferences::auto_save_file_path()) && sys::read_file(std::string(preferences::auto_save_file_path()) + ".stat") == "1") {
164 level_cfg = "autosave.cfg";
165 sys::write_file(std::string(preferences::auto_save_file_path()) + ".stat", "0");
166
167 }
168 #endif
169
170 if(override_level_cfg.empty() != true) {
171 level_cfg = override_level_cfg;
172 orig_level_cfg = level_cfg;
173 }
174
175 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
176 int width, height;
177 iphone_screen_res(&width, &height);
178 preferences::set_actual_screen_width(width);
179 preferences::set_actual_screen_height(height);
180 int multiplier = 2;
181 if (width > 320)
182 {
183 //preferences::set_use_pretty_scaling(true);
184 multiplier = 1;
185 }
186 preferences::set_virtual_screen_width(height*multiplier);
187 preferences::set_virtual_screen_height(width*multiplier);
188
189 SDL_WindowID windowID = SDL_CreateWindow (NULL, 0, 0, preferences::actual_screen_width(), preferences::actual_screen_height(),
190 SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN |
191 SDL_WINDOW_BORDERLESS);
192 if (windowID == 0) {
193 std::cerr << "Could not create window: " << SDL_GetError() << "\n";
194 return -1;
195 }
196
197 // if (SDL_GL_CreateContext(windowID) == 0) {
198 // std::cerr << "Could not create GL context: " << SDL_GetError() << "\n";
199 // return -1;
200 // }
201 if (SDL_CreateRenderer(windowID, -1, 0) != 0) {
202 std::cerr << "Could not create renderer\n";
203 return -1;
204 }
205
206 #else
207 #ifndef __APPLE__
208 graphics::surface wm_icon = graphics::surface_cache::get("window-icon.png");
209 if(!wm_icon.null()) {
210 SDL_WM_SetIcon(wm_icon, NULL);
211 }
212 #endif
213
214 if (SDL_SetVideoMode(preferences::actual_screen_width(),preferences::actual_screen_height(),0,SDL_OPENGL|SDL_RESIZABLE|(preferences::fullscreen() ? SDL_FULLSCREEN : 0)) == NULL) {
215 std::cerr << "could not set video mode\n";
216 return -1;
217 }
218 #endif
219
220 // srand(time(NULL));
221
222 const stats::manager stats_manager;
223
224 glShadeModel(GL_SMOOTH);
225 glEnable(GL_BLEND);
226 glEnable(GL_TEXTURE_2D);
227 glEnableClientState(GL_VERTEX_ARRAY);
228 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
229
230 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
231
232 SDL_WM_SetCaption("Frogatto", "Frogatto");
233
234 std::cerr << "JOYSTICKS: " << SDL_NumJoysticks() << "\n";
235
236 const load_level_manager load_manager;
237
238 { //manager scope
239 const font::manager font_manager;
240 const sound::manager sound_manager;
241 const joystick::manager joystick_manager;
242
243 #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
244 const SDL_Surface* fb = SDL_GetVideoSurface();
245 if(fb == NULL) {
246 return 0;
247 }
248 #endif
249
250 graphics::texture::manager texture_manager;
251
252 wml::const_node_ptr preloads;
253 loading_screen loader;
254
255 try {
256 wml::schema::init(wml::parse_wml_from_file("data/schema.cfg"));
257 graphical_font::init(wml::parse_wml_from_file("data/fonts.cfg"));
258 preloads = wml::parse_wml_from_file("data/preload.cfg");
259 int preload_items = std::distance(preloads->begin_child("preload"), preloads->end_child("preload"));
260 loader.set_number_of_items(preload_items+7); // 7 is the number of items that will be loaded below
261 loader.load(preloads);
262 loader.draw_and_increment("Initializing custom object");
263 custom_object::init();
264 loader.draw_and_increment("Initializing custom object functions");
265 init_custom_object_functions(wml::parse_wml_from_file("data/functions.cfg"));
266 loader.draw_and_increment("Initializing tiles");
267 tile_map::init(wml::parse_wml_from_file("data/tiles.cfg",
268 wml::schema::get("tiles")));
269 loader.draw_and_increment("Initializing GUI");
270
271 wml::const_node_ptr gui_node = wml::parse_wml_from_file(preferences::load_compiled() ? "data/compiled/gui.cfg" : "data/gui.cfg");
272 gui_section::init(gui_node);
273 loader.draw_and_increment("Initializing GUI");
274 framed_gui_element::init(gui_node);
275 } catch(const wml::parse_error& e) {
276 return 0;
277 }
278
279 loader.draw("Loading level");
280
281 if(!skip_tests && !test::run_tests()) {
282 return -1;
283 }
284
285 if(unit_tests_only) {
286 return 0;
287 }
288
289 if(run_benchmarks) {
290 if(benchmarks_list.empty() == false) {
291 test::run_benchmarks(&benchmarks_list);
292 } else {
293 test::run_benchmarks();
294 }
295 return 0;
296 } else if(utility_program.empty() == false) {
297 test::run_utility(utility_program, util_args);
298 return 0;
299 }
300
301 #if defined(__APPLE__) && !(TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE)
302 long swapInterval = 1;
303 CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &swapInterval);
304 #endif
305
306 #ifndef SDL_VIDEO_OPENGL_ES
307 GLenum glew_status = glewInit();
308 ASSERT_EQ(glew_status, GLEW_OK);
309 #endif
310
311 //look to see if we got any quit events while loading.
312 {
313 SDL_Event event;
314 while(SDL_PollEvent(&event)) {
315 if(event.type == SDL_QUIT) {
316 return 0;
317 }
318 }
319 }
320
321 formula_profiler::manager profiler(profile_output);
322
323 bool quit = false;
324
325 while(!quit && !show_title_screen(level_cfg)) {
326 boost::intrusive_ptr<level> lvl(load_level(level_cfg));
327
328 //see if we're loading a multiplayer level, in which case we
329 //connect to the server.
330 multiplayer::manager mp_manager(lvl->is_multiplayer());
331 if(lvl->is_multiplayer()) {
332 multiplayer::setup_networked_game(server);
333 }
334
335 if(lvl->is_multiplayer()) {
336 last_draw_position() = screen_position();
337 std::string level_cfg = "waiting-room.cfg";
338 boost::intrusive_ptr<level> wait_lvl(load_level(level_cfg));
339 wait_lvl->set_multiplayer_slot(0);
340 if(wait_lvl->player()) {
341 wait_lvl->player()->set_current_level(level_cfg);
342 }
343 level_runner runner(wait_lvl, level_cfg, orig_level_cfg);
344
345 multiplayer::sync_start_time(*lvl, boost::bind(&level_runner::play_cycle, &runner));
346
347 lvl->set_multiplayer_slot(multiplayer::slot());
348 }
349
350 last_draw_position() = screen_position();
351
352 assert(lvl.get());
353 if(!lvl->music().empty()) {
354 sound::play_music(lvl->music());
355 }
356
357 if(lvl->player() && level_cfg != "autosave.cfg") {
358 lvl->player()->set_current_level(level_cfg);
359 lvl->player()->get_entity().save_game();
360 }
361
362 set_scene_title(lvl->title());
363
364 try {
365 quit = level_runner(lvl, level_cfg, orig_level_cfg).play_level();
366 level_cfg = orig_level_cfg;
367 } catch(multiplayer_exception&) {
368 }
369 }
370
371 } //end manager scope, make managers destruct before calling SDL_Quit
372
373 // controls::debug_dump_controls();
374 SDL_Quit();
375 std::cerr << SDL_GetError() << "\n";
376 std::cerr << gluErrorString(glGetError()) << "\n";
377 return 0;
378 }
0 #ifndef MAP_UTILS_HPP_INCLUDED
1 #define MAP_UTILS_HPP_INCLUDED
2
3 #include <map>
4
5 template<typename K, typename V>
6 const V& map_get_value_default(const std::map<K,V>& m, const K& key, const V& val) {
7 typename std::map<K,V>::const_iterator i = m.find(key);
8 if(i != m.end()) {
9 return i->second;
10 } else {
11 return val;
12 }
13 }
14
15 #endif
0 #include <ctype.h>
1 #include <iostream>
2
3 #include "font.hpp"
4 #include "foreach.hpp"
5 #include "message_dialog.hpp"
6 #include "raster.hpp"
7
8 namespace {
9 const int FontSize = 22;
10
11 std::string::const_iterator get_end_of_word(std::string::const_iterator i1,
12 std::string::const_iterator i2)
13 {
14 while(i1 != i2 && isspace(*i1)) {
15 ++i1;
16 }
17
18 while(i1 != i2 && !isspace(*i1)) {
19 ++i1;
20 }
21
22 return i1;
23 }
24
25
26 std::string::const_iterator get_line(std::string::const_iterator i1,
27 std::string::const_iterator i2,
28 int max_chars)
29 {
30 std::string::const_iterator begin = i1;
31 i1 = get_end_of_word(i1, i2);
32 if(i1 == i2) {
33 return i1;
34 }
35
36 while(i1 != i2 && *i1 != '\n' && get_end_of_word(i1, i2) - begin < max_chars) {
37 i1 = get_end_of_word(i1, i2);
38 }
39
40 return i1;
41 }
42
43 message_dialog* current_dialog = NULL;
44
45 }
46
47 void message_dialog::show_modal(const std::string& text, const std::vector<std::string>* options)
48 {
49 if(current_dialog) {
50 clear_modal();
51 }
52
53 const int Width = 650;
54 const int Height = font::char_height(FontSize)*3;
55 current_dialog = new message_dialog(text, rect(graphics::screen_width()/2 - Width/2, graphics::screen_height()/2 - Height/2, Width, Height), options);
56 }
57
58 void message_dialog::clear_modal()
59 {
60 delete current_dialog;
61 current_dialog = NULL;
62 }
63
64 message_dialog* message_dialog::get()
65 {
66 return current_dialog;
67 }
68
69 message_dialog::message_dialog(const std::string& text, const rect& pos,
70 const std::vector<std::string>* options)
71 : text_(text), pos_(pos), line_height_(0),
72 cur_row_(0), cur_char_(0), cur_wait_(0),
73 selected_option_(0)
74 {
75 line_height_ = font::char_height(FontSize);
76 viewable_lines_ = pos.h()/line_height_;
77 if(viewable_lines_ < 1) {
78 viewable_lines_ = 1;
79 }
80
81 const int max_chars_on_line = std::max<int>(1, pos.w()/font::char_width(FontSize));
82 std::string::const_iterator i1 = text.begin();
83 std::string::const_iterator i2 = i1;
84 while(i2 != text.end()) {
85 i2 = get_line(i2, text.end(), max_chars_on_line);
86 if(i2 == i1) {
87 break;
88 }
89
90 while(i1 != i2 && isspace(*i1)) {
91 ++i1;
92 }
93
94 lines_.push_back(font::render_text(std::string(i1, i2), graphics::color_black(), FontSize));
95 i1 = i2;
96 }
97
98 if(options != NULL) {
99 foreach(const std::string& option, *options) {
100 options_.push_back(font::render_text(option, graphics::color_black(), FontSize));
101 }
102 }
103 }
104
105 namespace {
106
107 void draw_frame(const rect& r)
108 {
109 const SDL_Color border = { 0xa2, 0x64, 0x76, 0xff };
110 const SDL_Color bg = { 0xbe, 0xa2, 0x8f, 0xff };
111
112 const int Border = 4;
113 const int Padding = 10;
114 rect border_rect(r.x() - Padding - Border, r.y() - Padding - Border, r.w() + + Padding*2 + Border*2, r.h() + Padding*2 + Border*2);
115 graphics::draw_rect(border_rect.sdl_rect(), border);
116 rect back_rect(r.x() - Padding, r.y() - Padding, r.w() + Padding*2, r.h() + Padding*2);
117 graphics::draw_rect(back_rect.sdl_rect(), bg);
118 }
119
120 }
121
122 void message_dialog::draw() const
123 {
124 draw_frame(pos_);
125
126 for(int n = 0; n <= cur_row_ && n < lines_.size(); ++n) {
127 if(n != cur_row_) {
128 graphics::blit_texture(lines_[n], pos_.x(), pos_.y() + n*line_height_);
129 } else {
130 const int width = cur_char_*font::char_width(FontSize);
131 const int height = lines_[n].height();
132
133 const double x2 = double(width)/double(lines_[n].width());
134 graphics::blit_texture(lines_[n], pos_.x(), pos_.y() + n*line_height_,
135 width, height, 0.0, 0.0, 0.0, x2, 1.0);
136 }
137 }
138
139 if(cur_row_ >= lines_.size() && !options_.empty()) {
140 const int CursorWidth = 8;
141
142 int width = 0;
143 int height = 0;
144 foreach(const graphics::texture& t, options_) {
145 if(t.width() > width) {
146 width = t.width();
147 }
148
149 height += t.height();
150 }
151
152 rect r(pos_.x2() - 100, pos_.y2(), width + CursorWidth, height);
153 draw_frame(r);
154
155 for(int n = 0; n != options_.size(); ++n) {
156 graphics::blit_texture(options_[n], r.x() + CursorWidth, r.y() + n*line_height_);
157
158 if(n == selected_option_) {
159 int xpos = r.x() + CursorWidth;
160 const int ypos = r.y() + n*line_height_ + line_height_/2;
161 int height = 1;
162 while(xpos > r.x()) {
163 graphics::draw_rect(rect(xpos, ypos - height, 1, height*2).sdl_rect(), graphics::color_black());
164 --xpos;
165 ++height;
166 }
167 }
168 }
169 }
170 }
171
172 void message_dialog::process()
173 {
174 SDL_Event event;
175 while(SDL_PollEvent(&event)) {
176 switch(event.type) {
177 case SDL_KEYDOWN:
178 if(options_.empty() == false) {
179 if(event.key.keysym.sym == SDLK_DOWN) {
180 selected_option_++;
181 if(selected_option_ == options_.size()) {
182 selected_option_ = 0;
183 }
184 break;
185 }
186 if(event.key.keysym.sym == SDLK_UP) {
187 selected_option_--;
188 if(selected_option_ == -1) {
189 selected_option_ = options_.size() - 1;
190 }
191 break;
192 }
193 }
194 if(cur_row_ >= lines_.size()) {
195 clear_modal();
196 return;
197 }
198
199 break;
200 }
201 }
202
203 if(cur_row_ < lines_.size()) {
204 int num_keys;
205 #if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 3
206 const Uint8* keys = SDL_GetKeyboardState(&num_keys);
207 #else
208 const Uint8* keys = SDL_GetKeyState(&num_keys);
209 #endif
210 const int WaitTime = std::count(keys, keys + num_keys, 0) == num_keys ? 3 : 1;
211
212 const int nchars = lines_[cur_row_].width()/font::char_width(FontSize);
213
214 ++cur_wait_;
215 if(cur_wait_ >= WaitTime) {
216 cur_wait_ = 0;
217 ++cur_char_;
218 if(cur_char_ >= nchars) {
219 cur_char_ = 0;
220 ++cur_row_;
221 }
222 }
223 }
224 }
0 #ifndef MESSAGE_DIALOG_HPP_INCLUDED
1 #define MESSAGE_DIALOG_HPP_INCLUDED
2
3 #include <string>
4 #include <vector>
5
6 #include "geometry.hpp"
7 #include "texture.hpp"
8
9 class message_dialog
10 {
11 public:
12 static void show_modal(const std::string& text, const std::vector<std::string>* options=NULL);
13 static void clear_modal();
14 static message_dialog* get();
15 void draw() const;
16 void process();
17
18 int selected_option() const { return selected_option_; }
19 private:
20 message_dialog(const std::string& text, const rect& pos,
21 const std::vector<std::string>* options=NULL);
22 std::string text_;
23 rect pos_;
24 int viewable_lines_;
25 int line_height_;
26
27 int cur_row_, cur_char_, cur_wait_;
28
29 std::vector<graphics::texture> lines_;
30 std::vector<graphics::texture> options_;
31 int selected_option_;
32 };
33
34 #endif
0 #include "custom_object_functions.hpp"
1 #include "foreach.hpp"
2 #include "movement_script.hpp"
3 #include "object_events.hpp"
4 #include "wml_node.hpp"
5 #include "wml_utils.hpp"
6
7 active_movement_script::~active_movement_script()
8 {
9 foreach(const entity_mod& mod, mods_) {
10 for(std::map<std::string, game_logic::const_formula_ptr>::const_iterator i = mod.handlers_backup.begin(); i != mod.handlers_backup.end(); ++i) {
11 mod.entity->set_event_handler(get_object_event_id(i->first), i->second);
12 }
13
14 }
15 }
16
17 void active_movement_script::modify(entity_ptr entity, const std::map<std::string, game_logic::const_formula_ptr>& handlers)
18 {
19 entity_mod mod;
20 mod.entity = entity;
21 for(std::map<std::string, game_logic::const_formula_ptr>::const_iterator i = handlers.begin(); i != handlers.end(); ++i) {
22 mod.handlers_backup[i->first] = entity->get_event_handler(get_object_event_id(i->first));
23 entity->set_event_handler(get_object_event_id(i->first), i->second);
24 }
25
26 mods_.push_back(mod);
27 }
28
29 movement_script::movement_script(wml::const_node_ptr node) : id_(node->attr("id")) {
30 FOREACH_WML_CHILD(modification_node, node, "modification") {
31 modification m;
32 m.target_formula = game_logic::formula::create_optional_formula(modification_node->attr("target"), &get_custom_object_functions_symbol_table());
33 for(wml::node::const_attr_iterator i = modification_node->begin_attr(); i != modification_node->end_attr(); ++i) {
34 const std::string& key = i->first;
35 if(key.size() > 3 && std::equal(key.begin(), key.begin() + 3, "on_")) {
36 std::string event(key.begin() + 3, key.end());
37 m.handlers[event] = game_logic::formula::create_optional_formula(i->second, &get_custom_object_functions_symbol_table());
38 }
39 }
40
41 modifications_.push_back(m);
42 }
43 }
44
45 active_movement_script_ptr movement_script::begin_execution(const game_logic::formula_callable& callable) const
46 {
47 active_movement_script_ptr result(new active_movement_script);
48 foreach(const modification& m, modifications_) {
49 if(!m.target_formula) {
50 continue;
51 }
52
53 variant v = m.target_formula->execute(callable);
54 if(v.is_null()) {
55 continue;
56 }
57
58 for(int n = 0; n != v.num_elements(); ++n) {
59 variant obj = v[n];
60 entity_ptr e(obj.try_convert<entity>());
61 if(e) {
62 result->modify(e, m.handlers);
63 }
64 }
65 }
66
67 return result;
68 }
69
70 wml::node_ptr movement_script::write() const
71 {
72 wml::node_ptr result(new wml::node("script"));
73 result->set_attr("id", id_);
74 foreach(const modification& m, modifications_) {
75 wml::node_ptr node(new wml::node("modification"));
76 node->set_attr("target", m.target_formula ? m.target_formula->str() : "");
77 for(std::map<std::string, game_logic::const_formula_ptr>::const_iterator i = m.handlers.begin(); i != m.handlers.end(); ++i) {
78 node->set_attr("on_" + i->first, i->second ? i->second->str() : "");
79 }
80
81 result->add_child(node);
82 }
83
84 return result;
85 }
0 #ifndef MOVEMENT_SCRIPT_HPP_INCLUDED
1 #define MOVEMENT_SCRIPT_HPP_INCLUDED
2
3 #include "entity.hpp"
4 #include "formula.hpp"
5 #include "wml_node_fwd.hpp"
6
7 #include <map>
8 #include <string>
9 #include <vector>
10
11 #include <boost/shared_ptr.hpp>
12
13 class active_movement_script {
14 public:
15 ~active_movement_script();
16
17 void modify(entity_ptr entity, const std::map<std::string, game_logic::const_formula_ptr>& handlers);
18 private:
19 struct entity_mod {
20 entity_ptr entity;
21 std::map<std::string, game_logic::const_formula_ptr> handlers_backup;
22 };
23
24 std::vector<entity_mod> mods_;
25 };
26
27 typedef boost::shared_ptr<active_movement_script> active_movement_script_ptr;
28 typedef boost::shared_ptr<const active_movement_script> const_active_movement_script_ptr;
29
30 class movement_script
31 {
32 public:
33 movement_script() {}
34 explicit movement_script(wml::const_node_ptr node);
35 active_movement_script_ptr begin_execution(const game_logic::formula_callable& callable) const;
36 const std::string& id() const { return id_; }
37
38 wml::node_ptr write() const;
39 private:
40 struct modification {
41 game_logic::const_formula_ptr target_formula;
42 std::map<std::string, game_logic::const_formula_ptr> handlers;
43 };
44
45 std::string id_;
46 std::vector<modification> modifications_;
47 };
48
49 #endif
0 #include <string.h>
1
2 #include "asserts.hpp"
3 #include "formatter.hpp"
4 #include "multi_tile_pattern.hpp"
5 #include "string_utils.hpp"
6 #include "wml_node.hpp"
7 #include "wml_utils.hpp"
8 #include "wml_writer.hpp"
9
10 namespace {
11 //a pool of regular expressions. This makes sure that two regexes that
12 //are identical will point to the same place, and so we can easily
13 //test equality of regexes.
14 std::map<std::string, const boost::regex*> regex_pool;
15
16 std::deque<multi_tile_pattern>& patterns() {
17 static std::deque<multi_tile_pattern> instance;
18 return instance;
19 }
20
21 }
22
23 const boost::regex& get_regex_from_pool(const std::string& key)
24 {
25 if(key.empty()) {
26 static boost::regex res("^$");
27 return res;
28 }
29
30 const boost::regex*& re = regex_pool[key];
31 if(!re) {
32 if(key.empty() == false && key[0] == '!') {
33 //if the re starts with a ! we treat that as 'not matches'.
34 //This is marked by setting the lowest bit in the pointer.
35 re = new boost::regex(std::string(key.begin() + 1, key.end()));
36 re = reinterpret_cast<const boost::regex*>(reinterpret_cast<intptr_t>(re)+1);
37 } else {
38 re = new boost::regex(key);
39 }
40 }
41
42 return *re;
43 }
44
45 const std::deque<multi_tile_pattern>& multi_tile_pattern::get_all()
46 {
47 return patterns();
48 }
49
50 void multi_tile_pattern::init(wml::const_node_ptr node)
51 {
52 patterns().clear();
53 }
54
55 void multi_tile_pattern::load(wml::const_node_ptr node)
56 {
57 wml::node::const_child_iterator p1 = node->begin_child("multi_tile_pattern");
58 wml::node::const_child_iterator p2 = node->end_child("multi_tile_pattern");
59 for(; p1 != p2; ++p1) {
60 const wml::const_node_ptr& p = p1->second;
61 patterns().push_back(multi_tile_pattern(p));
62 }
63 }
64
65 namespace {
66 bool compare_match_cell_by_run_length(const multi_tile_pattern::match_cell& a,
67 const multi_tile_pattern::match_cell& b) {
68 return a.run_length > b.run_length;
69 }
70
71 struct raw_cell {
72 std::string regex;
73 std::vector<std::string> map_to;
74 };
75
76 int parse_pattern(const std::string& pattern, std::vector<raw_cell>& out) {
77
78 std::vector<std::string> lines = util::split(pattern, '\n', 0);
79 const int height = lines.size();
80 int width = -1;
81
82 foreach(const std::string& line, lines) {
83 std::vector<std::string> items = util::split(line, ',', util::STRIP_SPACES);
84 if(width == -1) {
85 width = items.size();
86 }
87
88 ASSERT_LOG(width == items.size(), "Inconsistent multi_tile_pattern size in pattern " << pattern);
89
90 foreach(std::string item, items) {
91 std::vector<const char*> arrows;
92 const char* arrow = strstr(item.c_str(), "->");
93 while(arrow) {
94 arrows.push_back(arrow);
95 arrow = strstr(arrow+2, "->");
96 }
97
98 raw_cell cell;
99
100 if(arrows.empty() == false) {
101 arrows.push_back(item.c_str() + item.size());
102 for(int n = 0; n != arrows.size()-1; ++n) {
103 std::string m(arrows[n]+2, arrows[n+1]);
104 util::strip(m);
105 cell.map_to.push_back(m);
106 }
107
108 item = std::string(item.c_str(), arrows.front());
109 }
110
111 util::strip(item);
112
113 cell.regex = item;
114 out.push_back(cell);
115 }
116 }
117
118 return width;
119 }
120
121 }
122
123 multi_tile_pattern::multi_tile_pattern(wml::const_node_ptr const_node)
124 : id_(const_node->attr("id")), width_(-1), height_(-1), chance_(wml::get_int(const_node, "chance", 100))
125 {
126 wml::node_ptr node = wml::deep_copy(const_node);
127
128 std::cerr << "INIT MTP: " << id_ << "\n";
129 FOREACH_WML_CHILD(alternative_node, node, "alternative") {
130 wml::node_ptr merged(new wml::node("multi_tile_pattern"));
131 wml::merge_attr_over(node, merged);
132 wml::merge_over(alternative_node, merged);
133 alternatives_.push_back(boost::shared_ptr<multi_tile_pattern>(new multi_tile_pattern(merged)));
134 }
135
136 std::vector<raw_cell> cells;
137 std::vector<raw_cell> cells_different_zorder;
138 width_ = parse_pattern(node->attr("pattern"), cells);
139 height_ = cells.size()/width_;
140
141 cells_different_zorder.resize(cells.size());
142
143 std::map<std::string, wml::node_ptr> base_nodes;
144
145 FOREACH_WML_CHILD(range_node, const_node, "range") {
146 const std::string from = range_node->attr("from");
147 const std::string to = range_node->attr("to");
148
149 const bool different_zorder = range_node->has_attr("zorder");
150
151 ASSERT_LOG(from != "", "MTP " << id_ << " DOES NOT HAVE from SPECIFIED IN RANGE: " << wml::output(range_node));
152 ASSERT_LOG(to != "", "MTP " << id_ << " DOES NOT HAVE to SPECIFIED IN RANGE");
153
154 std::string tile_pos = range_node->attr("tiles");
155 ASSERT_LOG(tile_pos.size() == 2, "In range for MTP " << id_ << " the tiles attribute is not in the correct format");
156
157 int from_index = -1;
158 int to_index = -1;
159 for(int n = 0; n != cells.size(); ++n) {
160 foreach(const std::string& m, cells[n].map_to) {
161 if(m == from) {
162 ASSERT_LOG(from_index == -1, "In multi_tile_pattern range specification for " << id_ << " the cell " << m << " is ambiguous since it appears multiple times");
163 from_index = n;
164 }
165
166 if(m == to) {
167 ASSERT_LOG(to_index == -1, "In multi_tile_pattern range specification for " << id_ << " the cell " << m << " is ambiguous since it appears multiple times");
168 to_index = n;
169 }
170 }
171 }
172
173 ASSERT_LOG(from_index != -1, "In multi_tile_pattern range specification for " << id_ << " the cell '" << from << "' was not found");
174 ASSERT_LOG(to_index != -1, "In multi_tile_pattern range specification for " << id_ << " the cell " << to << " was not found");
175 ASSERT_LOG(to_index > from_index, "In multi_tile_pattern range specification for " << id_ << " the cell " << to << " comes before the cell " << from);
176
177 const int from_x = from_index%width_;
178 const int from_y = from_index/width_;
179 const int to_x = to_index%width_;
180 const int to_y = to_index/width_;
181
182 char row = tile_pos[0];
183 for(int y = from_y; y <= to_y; ++y) {
184 char col = tile_pos[1];
185 for(int x = from_x; x <= to_x; ++x) {
186 const int index = y*width_ + x;
187 ASSERT_LT(index, cells.size());
188 foreach(std::string m, cells[index].map_to) {
189 if(different_zorder) {
190 m += "_zorder";
191 cells_different_zorder[index].map_to.push_back(m);
192 }
193
194 wml::node_ptr base_node = wml::deep_copy(range_node);
195 base_node->set_name(m);
196 base_node->erase_attr("from");
197 base_node->erase_attr("to");
198 char buf[3] = {row, col, 0};
199 base_node->set_attr("tiles", buf);
200
201 if(node->get_child(m)) {
202 ASSERT_LOG(base_nodes.count(m) == 0, "IN CALCULATING RANGE FOR MTP " << id_ << " TILE " << m << " APPEARS MULTIPLE TIMES");
203 base_nodes[m] = base_node;
204 } else {
205 node->add_child(base_node);
206 }
207 }
208
209 if(col == '9') {
210 col = 'a';
211 } else {
212 ++col;
213 }
214 }
215
216 if(row == '9') {
217 row = 'a';
218 } else {
219 ++row;
220 }
221 }
222 }
223
224 if(const_node->get_child("range")) {
225 std::cerr << "PARSE MTP: " << wml::output(node) << "\n";
226 }
227
228 for(int n = 0; n != cells.size(); ++n) {
229 foreach(const std::string& m, cells_different_zorder[n].map_to) {
230 cells[n].map_to.push_back(m);
231 }
232 }
233
234 std::map<std::string, level_object_ptr> objects;
235 std::map<std::string, int> object_zorders;
236 for(wml::node::const_all_child_iterator i = node->begin_children();
237 i != node->end_children(); ++i) {
238 if((*i)->name() == "alternative" || (*i)->name() == "range") {
239 continue;
240 }
241
242 wml::const_node_ptr obj_node = *i;
243 if(base_nodes.count(obj_node->name())) {
244 wml::merge_over(obj_node, base_nodes[obj_node->name()]);
245 obj_node = base_nodes[obj_node->name()];
246 }
247
248 objects[obj_node->name()].reset(new level_object(obj_node));
249 if(obj_node->has_attr("zorder")) {
250 object_zorders[obj_node->name()] = wml::get_int(obj_node, "zorder");
251 }
252 }
253
254 foreach(const raw_cell& cell, cells) {
255
256 tile_info info;
257 info.re = &get_regex_from_pool(cell.regex);
258
259 foreach(const std::string& m, cell.map_to) {
260 tile_entry entry;
261 entry.zorder = INT_MIN;
262 std::map<std::string, int>::const_iterator zorder_itor = object_zorders.find(m);
263 if(zorder_itor != object_zorders.end()) {
264 entry.zorder = zorder_itor->second;
265 }
266
267 if(cell.map_to.empty() == false) {
268 entry.tile = objects[m];
269 }
270
271 info.tiles.push_back(entry);
272 }
273
274 tiles_.push_back(info);
275 }
276
277 ASSERT_EQ(tiles_.size(), width_*height_);
278
279 for(int y = 0; y != height_; ++y) {
280 int run_length = 0;
281 for(int x = 0; x != width_; ++x) {
282 if(x != 0) {
283 if(tiles_[y*width_ + x].re == tiles_[y*width_ + x-1].re) {
284 ++run_length;
285 } else {
286 run_length = 0;
287 }
288 }
289
290 match_cell cell;
291 cell.loc = point(x, y);
292 cell.run_length = run_length;
293 try_order_.push_back(cell);
294 }
295 }
296
297 //try_order_ dictates the order in which patterns will be tried to see
298 //if an MTP matches.
299 //
300 //we want to order try_order_ to allow maximum chance of patterns being
301 //excluded from matching quickly.
302 std::sort(try_order_.begin(), try_order_.end(),
303 compare_match_cell_by_run_length);
304 if(!try_order_.empty()) {
305 for(int n = 0; n != try_order_.size(); ++n) {
306 const match_cell& cell = try_order_[n];
307 if(tiles_[cell.loc.y*width_ + cell.loc.x].re != &get_regex_from_pool("")) {
308 if(n != 0) {
309 match_cell c = try_order_[n];
310 try_order_.erase(try_order_.begin() + n);
311 try_order_.insert(try_order_.begin(), c);
312 }
313 break;
314 }
315 }
316
317 if(try_order_.size() > 2 && tiles_[try_order_[0].loc.y*width_ + try_order_[0].loc.x].re == tiles_[try_order_[1].loc.y*width_ + try_order_[1].loc.x].re) {
318 const boost::regex* re = tiles_[try_order_[0].loc.y*width_ + try_order_[0].loc.x].re;
319
320 for(int n = 2; n != try_order_.size(); ++n) {
321 const match_cell& cell = try_order_[n];
322 if(tiles_[cell.loc.y*width_ + cell.loc.x].re != re) {
323 match_cell c = try_order_[n];
324 try_order_.erase(try_order_.begin() + n);
325 try_order_.insert(try_order_.begin() + 1, c);
326 }
327 }
328 }
329 }
330 }
331
332 const multi_tile_pattern::tile_info& multi_tile_pattern::tile_at(int x, int y) const
333 {
334 //asserts commented out for performance
335 // ASSERT_GE(x, 0);
336 // ASSERT_GE(y, 0);
337 // ASSERT_LT(x, width_);
338 // ASSERT_LT(y, height_);
339 // ASSERT_EQ(tiles_.size(), width_*height_);
340
341 return tiles_[y*width_ + x];
342 }
343
344 int multi_tile_pattern::width() const
345 {
346 return width_;
347 }
348
349 int multi_tile_pattern::height() const
350 {
351 return height_;
352 }
353
354 const multi_tile_pattern& multi_tile_pattern::choose_random_alternative(int seed) const
355 {
356 if(alternatives_.empty()) {
357 return *this;
358 }
359
360 const int index = seed%(alternatives_.size() + 1);
361 if(index == alternatives_.size()) {
362 return *this;
363 }
364
365 return *alternatives_[index];
366 }
0 #ifndef MULTI_TILE_PATTERN_HPP_INCLUDED
1 #define MULTI_TILE_PATTERN_HPP_INCLUDED
2
3 #include <boost/regex.hpp>
4 #include <boost/shared_ptr.hpp>
5
6 #include <deque>
7 #include <string>
8 #include <vector>
9
10 #include "geometry.hpp"
11 #include "level_object.hpp"
12 #include "wml_node_fwd.hpp"
13
14 const boost::regex& get_regex_from_pool(const std::string& key);
15
16 class multi_tile_pattern
17 {
18 public:
19 //all multi tile patterns loaded. This is a deque meaning callers can
20 //save pointers to members, knowing they will never be destroyed.
21 static const std::deque<multi_tile_pattern>& get_all();
22 static void init(wml::const_node_ptr node);
23 static void load(wml::const_node_ptr node);
24 explicit multi_tile_pattern(wml::const_node_ptr node);
25
26 struct tile_entry {
27 level_object_ptr tile;
28 int zorder;
29 };
30
31 struct tile_info {
32 const boost::regex* re;
33 std::vector<tile_entry> tiles;
34 };
35
36 const std::string& id() const { return id_; }
37
38 const tile_info& tile_at(int x, int y) const;
39
40 int width() const;
41 int height() const;
42
43 int chance() const { return chance_; }
44
45 const multi_tile_pattern& choose_random_alternative(int seed) const;
46
47 struct match_cell {
48 point loc;
49 int run_length;
50 };
51
52 //the order to try matches in, optimized to eliminate things as soon
53 //as we possibly can.
54 const std::vector<match_cell>& try_order() const { return try_order_; }
55 private:
56 std::string id_;
57 std::vector<tile_info> tiles_;
58 std::vector<boost::shared_ptr<multi_tile_pattern> > alternatives_;
59 std::vector<match_cell> try_order_;
60 int width_, height_;
61 int chance_;
62 };
63
64 #endif
0 #include <sstream>
1 #include <string>
2
3 #include <inttypes.h>
4 #include <stdio.h>
5
6 #include <boost/asio.hpp>
7 #include <boost/shared_ptr.hpp>
8
9 #include "controls.hpp"
10 #include "level.hpp"
11 #include "multiplayer.hpp"
12
13 using boost::asio::ip::tcp;
14 using boost::asio::ip::udp;
15
16 namespace multiplayer {
17
18 namespace {
19 boost::shared_ptr<boost::asio::io_service> asio_service;
20 boost::shared_ptr<tcp::socket> tcp_socket;
21 boost::shared_ptr<udp::socket> udp_socket;
22 boost::shared_ptr<udp::endpoint> udp_endpoint;
23
24 int32_t id;
25 int player_slot;
26
27 bool udp_packet_waiting()
28 {
29 if(!udp_socket) {
30 return false;
31 }
32
33 boost::asio::socket_base::bytes_readable command(true);
34 udp_socket->io_control(command);
35 return command.get() != 0;
36 }
37
38 bool tcp_packet_waiting()
39 {
40 if(!tcp_socket) {
41 return false;
42 }
43
44 boost::asio::socket_base::bytes_readable command(true);
45 tcp_socket->io_control(command);
46 return command.get() != 0;
47 }
48 }
49
50 int slot()
51 {
52 return player_slot;
53 }
54
55 manager::manager(bool activate)
56 {
57 if(activate) {
58 asio_service.reset(new boost::asio::io_service);
59 }
60 }
61
62 manager::~manager() {
63 udp_endpoint.reset();
64 tcp_socket.reset();
65 udp_socket.reset();
66 asio_service.reset();
67 player_slot = 0;
68 }
69
70 void setup_networked_game(const std::string& server)
71 {
72 boost::asio::io_service& io_service = *asio_service;
73 tcp::resolver resolver(io_service);
74
75 tcp::resolver::query query(server, "17000");
76
77 tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
78 tcp::resolver::iterator end;
79
80 tcp_socket.reset(new tcp::socket(io_service));
81 tcp::socket& socket = *tcp_socket;
82 boost::system::error_code error = boost::asio::error::host_not_found;
83 while(error && endpoint_iterator != end) {
84 socket.close();
85 socket.connect(*endpoint_iterator++, error);
86 }
87
88 if(error) {
89 fprintf(stderr, "NETWORK ERROR: Can't resolve host\n");
90 throw multiplayer::error();
91 }
92
93 boost::array<char, 5> initial_response;
94 size_t len = socket.read_some(boost::asio::buffer(initial_response), error);
95 if(error) {
96 fprintf(stderr, "ERROR READING INITIAL RESPONSE\n");
97 throw multiplayer::error();
98 }
99
100 if(len != 5) {
101 fprintf(stderr, "INITIAL RESPONSE HAS THE WRONG SIZE: %d\n", (int)len);
102 throw multiplayer::error();
103 }
104
105 memcpy(&id, &initial_response[0], 4);
106 player_slot = initial_response[4];
107
108 fprintf(stderr, "ID: %d; SLOT: %d\n", id, player_slot);
109
110 udp::resolver udp_resolver(io_service);
111 udp::resolver::query udp_query(udp::v4(), server, "17001");
112 udp_endpoint.reset(new udp::endpoint);
113 *udp_endpoint = *udp_resolver.resolve(udp_query);
114 udp::endpoint& receiver_endpoint = *udp_endpoint;
115
116 udp_socket.reset(new udp::socket(io_service));
117 udp_socket->open(udp::v4());
118
119 boost::array<char, 4> udp_msg;
120 memcpy(&udp_msg[0], &id, 4);
121
122 // udp_socket->send_to(boost::asio::buffer(udp_msg), receiver_endpoint);
123
124 fprintf(stderr, "SENT UDP PACKET\n");
125
126 udp::endpoint sender_endpoint;
127 // len = udp_socket->receive_from(boost::asio::buffer(udp_msg), sender_endpoint);
128 fprintf(stderr, "GOT UDP PACKET: %d\n", (int)len);
129
130 std::string msg = "greetings!";
131 socket.write_some(boost::asio::buffer(msg), error);
132 if(error) {
133 fprintf(stderr, "NETWORK ERROR: Could not send data\n");
134 throw multiplayer::error();
135 }
136 }
137
138 void sync_start_time(const level& lvl, boost::function<bool()> idle_fn)
139 {
140 if(!tcp_socket) {
141 return;
142 }
143
144 std::ostringstream s;
145 s << "READY/" << lvl.id() << "/" << lvl.players().size();
146 boost::system::error_code error = boost::asio::error::host_not_found;
147 tcp_socket->write_some(boost::asio::buffer(s.str()), error);
148 if(error) {
149 fprintf(stderr, "ERROR WRITING TO SOCKET\n");
150 throw multiplayer::error();
151 }
152
153 while(!tcp_packet_waiting()) {
154 if(idle_fn) {
155 const bool res = idle_fn();
156 if(!res) {
157 std::cerr << "quitting game...\n";
158 throw multiplayer::error();
159 }
160 }
161 }
162
163 boost::array<char, 1024> response;
164 size_t len = tcp_socket->read_some(boost::asio::buffer(response), error);
165 if(error) {
166 fprintf(stderr, "ERROR READING FROM SOCKET\n");
167 throw multiplayer::error();
168 }
169
170 std::string str(&response[0], &response[0] + len);
171 if(str != "START") {
172 fprintf(stderr, "UNEXPECTED RESPONSE: '%s'\n", str.c_str());
173 throw multiplayer::error();
174 }
175 }
176
177 void send_and_receive()
178 {
179 if(!udp_socket || controls::num_players() == 1) {
180 return;
181 }
182
183 //send our ID followed by the send packet.
184 std::vector<char> send_buf(4);
185 memcpy(&send_buf[0], &id, 4);
186 controls::write_control_packet(send_buf);
187 udp_socket->send_to(boost::asio::buffer(send_buf), *udp_endpoint);
188
189 while(udp_packet_waiting()) {
190 udp::endpoint sender_endpoint;
191 boost::array<char, 4096> udp_msg;
192 size_t len = udp_socket->receive_from(boost::asio::buffer(udp_msg), sender_endpoint);
193 if(len < 4) {
194 fprintf(stderr, "UDP PACKET TOO SHORT: %d\n", (int)len);
195 continue;
196 }
197
198 controls::read_control_packet(&udp_msg[4], len - 4);
199 }
200 }
201
202 }
0 #ifndef MULTIPLAYER_HPP_INCLUDED
1 #define MULTIPLAYER_HPP_INCLUDED
2
3 #include <boost/function.hpp>
4
5 class level;
6
7 namespace multiplayer {
8 struct error {};
9 int slot();
10 void setup_networked_game(const std::string& server);
11
12 void sync_start_time(const level& lvl, boost::function<bool()> idle_fn);
13
14 void send_and_receive();
15
16 struct manager {
17 manager(bool activate);
18 ~manager();
19 };
20 }
21
22 #endif
0 #include <map>
1 #include <string>
2 #include <vector>
3
4 #include "asserts.hpp"
5 #include "object_events.hpp"
6
7 namespace {
8 std::vector<std::string> create_object_event_names()
9 {
10 std::vector<std::string> res;
11 res.push_back("start_level");
12 res.push_back("load");
13 res.push_back("load_checkpoint");
14 res.push_back("create");
15 res.push_back("done_create");
16 res.push_back("become_active");
17 res.push_back("surface_damage");
18 res.push_back("enter_anim");
19 res.push_back("end_anim");
20 res.push_back("collide_level");
21 res.push_back("collide_head");
22 res.push_back("collide_feet");
23 res.push_back("collide_damage");
24 res.push_back("collide");
25 res.push_back("stuck");
26 res.push_back("jumped_on");
27 res.push_back("get_hit");
28 res.push_back("process");
29 res.push_back("timer");
30 res.push_back("enter_water");
31 res.push_back("exit_water");
32 res.push_back("change_solid_dimensions_fail");
33 res.push_back("add_object_fail");
34 res.push_back("change_animation_failure");
35 res.push_back("die");
36 res.push_back("interact");
37 res.push_back("child_spawned");
38 res.push_back("spawned");
39 res.push_back("draw");
40 res.push_back("begin_dialog");
41
42 ASSERT_EQ(res.size(), NUM_OBJECT_BUILTIN_EVENT_IDS);
43 return res;
44 }
45
46 std::vector<std::string>& object_event_names() {
47 static std::vector<std::string> event_names = create_object_event_names();
48 return event_names;
49 }
50
51 std::map<std::string, int> create_object_event_ids()
52 {
53 std::map<std::string, int> result;
54 for(int n = 0; n != object_event_names().size(); ++n) {
55 result[object_event_names()[n]] = n;
56 }
57
58 return result;
59 }
60
61 std::map<std::string, int>& object_event_ids() {
62 static std::map<std::string, int> event_ids = create_object_event_ids();
63 return event_ids;
64 }
65
66 }
67
68 const std::string& get_object_event_str(int id)
69 {
70 return object_event_names()[id];
71 }
72
73 int get_object_event_id(const std::string& str)
74 {
75 std::map<std::string, int>::iterator itor = object_event_ids().find(str);
76 if(itor != object_event_ids().end()) {
77 return itor->second;
78 }
79
80 //we have to add a new entry for this new string
81 object_event_ids()[str] = object_event_names().size();
82 object_event_names().push_back(str);
83
84 return object_event_names().size()-1;
85 }
0 #ifndef OBJECT_EVENTS_HPP_INCLUDED
1 #define OBJECT_EVENTS_HPP_INCLUDED
2
3 enum OBJECT_EVENT_ID {
4 OBJECT_EVENT_START_LEVEL,
5 OBJECT_EVENT_LOAD,
6 OBJECT_EVENT_LOAD_CHECKPOINT,
7 OBJECT_EVENT_CREATE,
8 OBJECT_EVENT_DONE_CREATE,
9 OBJECT_EVENT_BECOME_ACTIVE,
10 OBJECT_EVENT_SURFACE_DAMAGE,
11 OBJECT_EVENT_ENTER_ANIM,
12 OBJECT_EVENT_END_ANIM,
13 OBJECT_EVENT_COLLIDE_LEVEL,
14 OBJECT_EVENT_COLLIDE_HEAD,
15 OBJECT_EVENT_COLLIDE_FEET,
16 OBJECT_EVENT_COLLIDE_DAMAGE,
17 OBJECT_EVENT_COLLIDE,
18 OBJECT_EVENT_STUCK,
19 OBJECT_EVENT_JUMPED_ON,
20 OBJECT_EVENT_GET_HIT,
21 OBJECT_EVENT_PROCESS,
22 OBJECT_EVENT_TIMER,
23 OBJECT_EVENT_ENTER_WATER,
24 OBJECT_EVENT_EXIT_WATER,
25 OBJECT_EVENT_CHANGE_SOLID_DIMENSIONS_FAIL,
26 OBJECT_EVENT_ADD_OBJECT_FAIL,
27 OBJECT_EVENT_CHANGE_ANIMATION_FAILURE,
28 OBJECT_EVENT_DIE,
29 OBJECT_EVENT_INTERACT,
30 OBJECT_EVENT_CHILD_SPAWNED,
31 OBJECT_EVENT_SPAWNED,
32 OBJECT_EVENT_DRAW,
33 OBJECT_EVENT_BEGIN_DIALOG,
34 NUM_OBJECT_BUILTIN_EVENT_IDS,
35 };
36
37 const std::string& get_object_event_str(int id);
38 int get_object_event_id(const std::string& str);
39
40 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include <ctype.h>
13 #include <iostream>
14
15 #include "font.hpp"
16 #include "foreach.hpp"
17 #include "dialog.hpp"
18 #include "raster.hpp"
19 #include "options_dialog.hpp"
20
21 namespace {
22
23 void draw_frame(const rect& r)
24 {
25 const SDL_Color border = { 0xa2, 0x64, 0x76, 0xff };
26 const SDL_Color bg = { 0xbe, 0xa2, 0x8f, 0xff };
27
28 const int Border = 4;
29 const int Padding = 10;
30 rect border_rect(r.x() - Padding - Border, r.y() - Padding - Border, r.w() + + Padding*2 + Border*2, r.h() + Padding*2 + Border*2);
31 graphics::draw_rect(border_rect.sdl_rect(), border);
32 rect back_rect(r.x() - Padding, r.y() - Padding, r.w() + Padding*2, r.h() + Padding*2);
33 graphics::draw_rect(back_rect.sdl_rect(), bg);
34 }
35
36 }
37
38
39 void options_dialog::draw() const
40 {
41 draw_frame( rect(x(),y(),width(),height()) );
42 }
43
44
45 options_dialog::options_dialog(int x, int y, int w, int h)
46 : dialog(x,y,w,h)
47 {
48
49 }
50
51 void options_dialog::handle_draw() const
52 {
53 /*if(clear_bg()) {
54 SDL_Rect rect = {x(),y(),width(),height()};
55 SDL_Color col = {0,0,0,0};
56 graphics::draw_rect(rect,col,196);
57
58 //fade effect for fullscreen dialogs
59 if(bg_.valid()) {
60 if(bg_alpha_ > 0.25) {
61 bg_alpha_ -= 0.05;
62 }
63 glColor4f(1.0, 1.0, 1.0, bg_alpha_);
64 graphics::blit_texture(bg_, x(), y(), width(), height(), 0.0, 0.0, 1.0, 1.0, 0.0);
65 glColor4f(1.0, 1.0, 1.0, 1.0);
66 }
67 }*/
68 draw_frame( rect(x(),y(),width(),height()) );
69 //handle_draw_children();
70 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef OPTIONS_DIALOG_HPP_INCLUDED
13 #define OPTIONS_DIALOG_HPP_INCLUDED
14
15 #include <string>
16 #include <vector>
17
18 #include "texture.hpp"
19 #include "geometry.hpp"
20 #include "dialog.hpp"
21
22
23 class options_dialog : public gui::dialog
24 {
25 public:
26 void draw() const;
27 options_dialog(int x, int y, int w, int h);
28 protected:
29 virtual void handle_draw() const;
30
31
32 };
33
34
35 #endif
0 #include <iostream>
1 #include <deque>
2 #include <inttypes.h>
3 #include <math.h>
4
5 #include "asserts.hpp"
6 #include "color_utils.hpp"
7 #include "entity.hpp"
8 #include "foreach.hpp"
9 #include "frame.hpp"
10 #include "particle_system.hpp"
11 #include "preferences.hpp"
12 #include "string_utils.hpp"
13 #include "texture.hpp"
14 #include "wml_node.hpp"
15 #include "wml_utils.hpp"
16 #include "weather_particle_system.hpp"
17 #include "water_particle_system.hpp"
18 #include "raster.hpp"
19
20 namespace {
21
22 class particle_animation {
23 public:
24 explicit particle_animation(wml::const_node_ptr node) :
25 id_(node->attr("id")),
26 texture_(graphics::texture::get(node->attr("image"))),
27 duration_(wml::get_int(node, "duration"))
28 {
29 rect base_area(node->has_attr("rect") ? rect(node->attr("rect")) :
30 rect(wml::get_int(node, "x"),
31 wml::get_int(node, "y"),
32 wml::get_int(node, "w"),
33 wml::get_int(node, "h")));
34 width_ = base_area.w()*2;
35 height_ = base_area.h()*2;
36 int nframes = wml::get_int(node, "frames", 1);
37 if(nframes < 1) {
38 nframes = 1;
39 }
40
41 const int nframes_per_row = wml::get_int(node, "frames_per_row", -1);
42 const int pad = wml::get_int(node, "pad");
43
44 frame frame_obj(node);
45
46 int row = 0, col = 0;
47 for(int n = 0; n != nframes; ++n) {
48 const frame::frame_info& info = frame_obj.frame_layout()[n];
49 const rect& area = info.area;
50
51 frame_area a;
52 a.u1 = GLfloat(area.x())/GLfloat(texture_.width());
53 a.u2 = GLfloat(area.x2())/GLfloat(texture_.width());
54 a.v1 = GLfloat(area.y())/GLfloat(texture_.height());
55 a.v2 = GLfloat(area.y2())/GLfloat(texture_.height());
56
57 a.x_adjust = info.x_adjust*2;
58 a.y_adjust = info.y_adjust*2;
59 a.x2_adjust = info.x2_adjust*2;
60 a.y2_adjust = info.y2_adjust*2;
61
62 frames_.push_back(a);
63
64 ++col;
65 if(col == nframes_per_row) {
66 col = 0;
67 ++row;
68 }
69 }
70 }
71
72 struct frame_area {
73 GLfloat u1, v1, u2, v2;
74 int x_adjust, y_adjust, x2_adjust, y2_adjust;
75 };
76
77 const frame_area& get_frame(int t) const {
78 int index = t/duration_;
79 if(index < 0) {
80 index = 0;
81 } else if(index >= frames_.size()) {
82 index = frames_.size() - 1;
83 }
84
85 return frames_[index];
86 }
87
88 void set_texture() const {
89 texture_.set_as_current_texture();
90 }
91
92 int width() const { return width_; }
93 int height() const { return height_; }
94 private:
95 std::string id_;
96 graphics::texture texture_;
97
98 std::vector<frame_area> frames_;
99 int duration_;
100 int width_, height_;
101 };
102
103 struct simple_particle_system_info {
104 simple_particle_system_info(wml::const_node_ptr node)
105 : spawn_rate_(wml::get_int(node, "spawn_rate", 1)),
106 spawn_rate_random_(wml::get_int(node, "spawn_rate_random")),
107 system_time_to_live_(wml::get_int(node, "system_time_to_live", -1)),
108 time_to_live_(wml::get_int(node, "time_to_live", 50)),
109 min_x_(wml::get_int(node, "min_x", 0)),
110 max_x_(wml::get_int(node, "max_x", 0)),
111 min_y_(wml::get_int(node, "min_y", 0)),
112 max_y_(wml::get_int(node, "max_y", 0)),
113 velocity_x_(wml::get_int(node, "velocity_x", 0)),
114 velocity_y_(wml::get_int(node, "velocity_y", 0)),
115 velocity_x_rand_(wml::get_int(node, "velocity_x_random", 0)),
116 velocity_y_rand_(wml::get_int(node, "velocity_y_random", 0)),
117 velocity_magnitude_(wml::get_int(node, "velocity_magnitude", 0)),
118 velocity_magnitude_rand_(wml::get_int(node, "velocity_magnitude_random", 0)),
119 velocity_rotate_(wml::get_int(node, "velocity_rotate", 0)),
120 velocity_rotate_rand_(wml::get_int(node, "velocity_rotate_random", 0)),
121 accel_x_(wml::get_int(node, "accel_x", 0)),
122 accel_y_(wml::get_int(node, "accel_y", 0)),
123 delta_r_(wml::get_int(node, "delta_r", 0)),
124 delta_g_(wml::get_int(node, "delta_g", 0)),
125 delta_b_(wml::get_int(node, "delta_b", 0)),
126 delta_a_(wml::get_int(node, "delta_a", 0))
127 {
128 }
129 int spawn_rate_, spawn_rate_random_;
130 int system_time_to_live_;
131 int time_to_live_;
132 int min_x_, max_x_, min_y_, max_y_;
133 int velocity_x_, velocity_y_;
134 int velocity_x_rand_, velocity_y_rand_;
135 int velocity_magnitude_, velocity_magnitude_rand_;
136 int velocity_rotate_, velocity_rotate_rand_;
137 int accel_x_, accel_y_;
138
139 int delta_r_, delta_g_, delta_b_, delta_a_;
140 };
141
142 class simple_particle_system_factory : public particle_system_factory {
143 public:
144 explicit simple_particle_system_factory(wml::const_node_ptr node);
145 ~simple_particle_system_factory() {}
146
147 particle_system_ptr create(const entity& e) const;
148
149 std::vector<particle_animation> frames_;
150
151 simple_particle_system_info info_;
152 };
153
154 simple_particle_system_factory::simple_particle_system_factory(wml::const_node_ptr node)
155 : info_(node)
156 {
157 FOREACH_WML_CHILD(frame_node, node, "animation") {
158 frames_.push_back(particle_animation(frame_node));
159 }
160 }
161
162 class simple_particle_system : public particle_system
163 {
164 public:
165 simple_particle_system(const entity& e, const simple_particle_system_factory& factory);
166 ~simple_particle_system() {}
167
168 bool is_destroyed() const { return info_.system_time_to_live_ == 0 || info_.spawn_rate_ < 0 && particles_.empty(); }
169 bool should_save() const { return info_.spawn_rate_ >= 0; }
170 void process(const level& lvl, const entity& e);
171 void draw(const rect& area, const entity& e) const;
172
173 private:
174 variant get_value(const std::string& key) const {
175 if(key == "spawn_rate") {
176 return variant(info_.spawn_rate_);
177 } else {
178 return variant();
179 }
180 }
181
182 void set_value(const std::string& key, const variant& value) {
183 if(key == "spawn_rate") {
184 info_.spawn_rate_ = value.as_int();
185 } else if(key == "min_x") {
186 info_.min_x_ = value.as_int();
187 } else if(key == "max_x") {
188 info_.max_x_ = value.as_int();
189 } else if(key == "min_y") {
190 info_.min_y_ = value.as_int();
191 } else if(key == "max_y") {
192 info_.max_y_ = value.as_int();
193 } else if(key == "velocity_x") {
194 info_.velocity_x_ = value.as_int();
195 } else if(key == "velocity_y") {
196 info_.velocity_y_ = value.as_int();
197 }
198 }
199
200 const simple_particle_system_factory& factory_;
201 simple_particle_system_info info_;
202
203 int cycle_;
204
205 struct particle {
206 GLfloat pos[2];
207 const particle_animation* anim;
208 GLfloat velocity[2];
209 };
210
211 struct generation {
212 int members;
213 int created_at;
214 };
215
216 std::deque<particle> particles_;
217 std::deque<generation> generations_;
218
219 int spawn_buildup_;
220 };
221
222 simple_particle_system::simple_particle_system(const entity& e, const simple_particle_system_factory& factory)
223 : factory_(factory), info_(factory.info_), cycle_(0), spawn_buildup_(0)
224 {
225 }
226
227 void simple_particle_system::process(const level& lvl, const entity& e)
228 {
229 --info_.system_time_to_live_;
230 ++cycle_;
231
232 while(!generations_.empty() && cycle_ - generations_.front().created_at == info_.time_to_live_) {
233 particles_.erase(particles_.begin(), particles_.begin() + generations_.front().members);
234 generations_.pop_front();
235 }
236
237 std::deque<particle>::iterator p = particles_.begin();
238 foreach(generation& gen, generations_) {
239 for(int n = 0; n != gen.members; ++n) {
240 p->pos[0] += p->velocity[0];
241 p->pos[1] += p->velocity[1];
242 if(e.face_right()) {
243 p->velocity[0] += info_.accel_x_/1000.0;
244 } else {
245 p->velocity[0] -= info_.accel_x_/1000.0;
246 }
247 p->velocity[1] += info_.accel_y_/1000.0;
248 ++p;
249 }
250 }
251
252 int nspawn = info_.spawn_rate_;
253 if(info_.spawn_rate_random_ > 0) {
254 nspawn += rand()%info_.spawn_rate_random_;
255 }
256
257 if(nspawn > 0) {
258 nspawn += spawn_buildup_;
259 }
260
261 spawn_buildup_ = nspawn%1000;
262 nspawn /= 1000;
263
264 generation new_gen;
265 new_gen.members = nspawn;
266 new_gen.created_at = cycle_;
267
268 generations_.push_back(new_gen);
269
270 while(nspawn-- > 0) {
271 particle p;
272 p.pos[0] = e.face_right() ? (e.x() + info_.min_x_) : (e.x() + e.current_frame().width() - info_.max_x_);
273 p.pos[1] = e.y() + info_.min_y_;
274 p.velocity[0] = info_.velocity_x_/1000.0;
275 p.velocity[1] = info_.velocity_y_/1000.0;
276
277 if(info_.velocity_x_rand_ > 0) {
278 p.velocity[0] += (rand()%info_.velocity_x_rand_)/1000.0;
279 }
280
281 if(info_.velocity_y_rand_ > 0) {
282 p.velocity[1] += (rand()%info_.velocity_y_rand_)/1000.0;
283 }
284
285 int velocity_magnitude = info_.velocity_magnitude_;
286 if(info_.velocity_magnitude_rand_ > 0) {
287 velocity_magnitude += rand()%info_.velocity_magnitude_rand_;
288 }
289
290 if(velocity_magnitude) {
291 int rotate_velocity = info_.velocity_rotate_;
292 if(info_.velocity_rotate_rand_) {
293 rotate_velocity += rand()%info_.velocity_rotate_rand_;
294 }
295
296 const GLfloat rotate_radians = (GLfloat(rotate_velocity)/360.0)*3.14*2.0;
297 const GLfloat magnitude = velocity_magnitude/1000.0;
298 p.velocity[0] += sin(rotate_radians)*magnitude;
299 p.velocity[1] += cos(rotate_radians)*magnitude;
300 }
301
302 ASSERT_GT(factory_.frames_.size(), 0);
303 p.anim = &factory_.frames_[rand()%factory_.frames_.size()];
304
305 const int diff_x = info_.max_x_ - info_.min_x_;
306 if(diff_x > 0) {
307 p.pos[0] += (rand()%(diff_x*1000))/1000.0;
308 }
309
310 const int diff_y = info_.max_y_ - info_.min_y_;
311 if(diff_y > 0) {
312 p.pos[1] += (rand()%(diff_y*1000))/1000.0;
313 }
314
315 if(!e.face_right()) {
316 p.velocity[0] = -p.velocity[0];
317 }
318
319 particles_.push_back(p);
320 }
321 }
322
323 void simple_particle_system::draw(const rect& area, const entity& e) const
324 {
325 if(particles_.empty()) {
326 return;
327 }
328
329 std::deque<particle>::const_iterator p = particles_.begin();
330
331 //all particles must have the same texture, so just set it once.
332 p->anim->set_texture();
333 std::vector<GLfloat>& varray = graphics::global_vertex_array();
334 std::vector<GLfloat>& tcarray = graphics::global_texcoords_array();
335 std::vector<GLbyte>& carray = graphics::global_vertex_color_array();
336
337 const int facing = e.face_right() ? 1 : -1;
338
339
340 carray.clear();
341 varray.clear();
342 tcarray.clear();
343 foreach(const generation& gen, generations_) {
344 for(int n = 0; n != gen.members; ++n) {
345 const particle_animation* anim = p->anim;
346 const particle_animation::frame_area& f = anim->get_frame(cycle_ - gen.created_at);
347
348 if(info_.delta_a_){
349 //Spare the bandwidth if we're opaque
350 const int alpha_level = std::max(256 - info_.delta_a_*(cycle_ - gen.created_at), 0);
351 const int red = 255;
352 const int green = 255;
353 const int blue = 255;
354 for( int i = 0; i < 6; ++i){
355 carray.push_back(red); carray.push_back(green); carray.push_back(blue); carray.push_back(alpha_level);
356 }
357
358 }
359 //draw the first point twice, to allow drawing all particles
360 //in one drawing operation.
361
362 tcarray.push_back(graphics::texture::get_coord_x(f.u1));
363 tcarray.push_back(graphics::texture::get_coord_y(f.v1));
364 varray.push_back(p->pos[0] + f.x_adjust*facing);
365 varray.push_back(p->pos[1] + f.y_adjust);
366 tcarray.push_back(graphics::texture::get_coord_x(f.u1));
367 tcarray.push_back(graphics::texture::get_coord_y(f.v1));
368 varray.push_back(p->pos[0] + f.x_adjust*facing);
369 varray.push_back(p->pos[1] + f.y_adjust);
370
371 tcarray.push_back(graphics::texture::get_coord_x(f.u2));
372 tcarray.push_back(graphics::texture::get_coord_y(f.v1));
373 varray.push_back(p->pos[0] + (anim->width() - f.x2_adjust)*facing);
374 varray.push_back(p->pos[1] + f.y_adjust);
375 tcarray.push_back(graphics::texture::get_coord_x(f.u1));
376 tcarray.push_back(graphics::texture::get_coord_y(f.v2));
377 varray.push_back(p->pos[0] + f.x_adjust*facing);
378 varray.push_back(p->pos[1] + anim->height() - f.y2_adjust);
379
380 //draw the last point twice.
381 tcarray.push_back(graphics::texture::get_coord_x(f.u2));
382 tcarray.push_back(graphics::texture::get_coord_y(f.v2));
383 varray.push_back(p->pos[0] + (anim->width() - f.x2_adjust)*facing);
384 varray.push_back(p->pos[1] + anim->height() - f.y2_adjust);
385 tcarray.push_back(graphics::texture::get_coord_x(f.u2));
386 tcarray.push_back(graphics::texture::get_coord_y(f.v2));
387 varray.push_back(p->pos[0] + (anim->width() - f.x2_adjust)*facing);
388 varray.push_back(p->pos[1] + anim->height() - f.y2_adjust);
389 ++p;
390 }
391 }
392
393 if(info_.delta_a_){
394 glEnableClientState(GL_COLOR_ARRAY);
395 glColorPointer(4, GL_UNSIGNED_BYTE, 0, &carray.front());
396 }
397
398 glVertexPointer(2, GL_FLOAT, 0, &varray.front());
399 glTexCoordPointer(2, GL_FLOAT, 0, &tcarray.front());
400 glDrawArrays(GL_TRIANGLE_STRIP, 0, varray.size()/2);
401
402 if(info_.delta_a_){
403 glDisableClientState(GL_COLOR_ARRAY);
404 }
405
406 glColor4f(1.0, 1.0, 1.0, 1.0);
407 }
408
409 particle_system_ptr simple_particle_system_factory::create(const entity& e) const
410 {
411 return particle_system_ptr(new simple_particle_system(e, *this));
412 }
413
414 struct point_particle_info
415 {
416 explicit point_particle_info(wml::const_node_ptr node)
417 : generation_rate_millis(wml::get_int(node, "generation_rate_millis")),
418 pos_x(wml::get_int(node, "pos_x")*1024),
419 pos_y(wml::get_int(node, "pos_y")*1024),
420 pos_x_rand(wml::get_int(node, "pos_x_rand")*1024),
421 pos_y_rand(wml::get_int(node, "pos_y_rand")*1024),
422 velocity_x(wml::get_int(node, "velocity_x")),
423 velocity_y(wml::get_int(node, "velocity_y")),
424 velocity_x_rand(wml::get_int(node, "velocity_x_rand")),
425 velocity_y_rand(wml::get_int(node, "velocity_y_rand")),
426 dot_size(wml::get_int(node, "dot_size", 1)*(preferences::double_scale() ? 2 : 1)),
427 time_to_live(wml::get_int(node, "time_to_live")),
428 time_to_live_max(wml::get_int(node, "time_to_live_rand") + time_to_live) {
429 std::vector<std::string> colors_str;
430 util::split(node->attr("colors"), colors_str);
431 foreach(const std::string& col, colors_str) {
432 unsigned int val = strtoul(col.c_str(), NULL, 16);
433 unsigned char* c = reinterpret_cast<unsigned char*>(&val);
434 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
435 std::reverse(c, c+4);
436 #endif
437 colors.push_back(val);
438 }
439
440 std::reverse(colors.begin(), colors.end());
441
442 ttl_divisor = time_to_live_max/(colors.size()-1);
443
444 rgba[0] = wml::get_int(node, "red");
445 rgba[1] = wml::get_int(node, "green");
446 rgba[2] = wml::get_int(node, "blue");
447 rgba[3] = wml::get_int(node, "alpha", 255);
448 rgba_rand[0] = wml::get_int(node, "red_rand");
449 rgba_rand[1] = wml::get_int(node, "green_rand");
450 rgba_rand[2] = wml::get_int(node, "blue_rand");
451 rgba_rand[3] = wml::get_int(node, "alpha_rand");
452 rgba_delta[0] = wml::get_int(node, "red_delta");
453 rgba_delta[1] = wml::get_int(node, "green_delta");
454 rgba_delta[2] = wml::get_int(node, "blue_delta");
455 rgba_delta[3] = wml::get_int(node, "alpha_delta");
456 }
457
458 int generation_rate_millis;
459 int pos_x, pos_y, pos_x_rand, pos_y_rand;
460 int velocity_x, velocity_y, velocity_x_rand, velocity_y_rand;
461 int time_to_live, time_to_live_max;
462 unsigned char rgba[4];
463 unsigned char rgba_rand[4];
464 char rgba_delta[4];
465 int dot_size;
466
467 std::vector<unsigned int> colors;
468 int ttl_divisor;
469 };
470
471 class point_particle_system : public particle_system
472 {
473 public:
474 point_particle_system(const entity& obj, const point_particle_info& info) : obj_(obj), info_(info), particle_generation_(0), generation_rate_millis_(info.generation_rate_millis) {
475 }
476
477 void process(const level& lvl, const entity& e) {
478 particle_generation_ += generation_rate_millis_;
479
480 particles_.erase(std::remove_if(particles_.begin(), particles_.end(), particle_destroyed), particles_.end());
481
482 for(std::vector<particle>::iterator p = particles_.begin();
483 p != particles_.end(); ++p) {
484 p->pos_x += p->velocity_x;
485 p->pos_y += p->velocity_y;
486 p->rgba[0] += info_.rgba_delta[0];
487 p->rgba[1] += info_.rgba_delta[1];
488 p->rgba[2] += info_.rgba_delta[2];
489 p->rgba[3] += info_.rgba_delta[3];
490 p->ttl--;
491 }
492
493 while(particle_generation_ >= 1000) {
494 particles_.push_back(particle());
495 particle& p = particles_.back();
496 p.ttl = info_.time_to_live;
497 if(info_.time_to_live_max != info_.time_to_live) {
498 p.ttl += rand()%(info_.time_to_live_max - info_.time_to_live);
499 }
500
501 p.velocity_x = info_.velocity_x;
502 p.velocity_y = info_.velocity_y;
503
504 if(info_.velocity_x_rand) {
505 p.velocity_x += rand()%info_.velocity_x_rand;
506 }
507
508 if(info_.velocity_y_rand) {
509 p.velocity_y += rand()%info_.velocity_y_rand;
510 }
511
512 p.pos_x = e.x()*1024 + info_.pos_x;
513 p.pos_y = e.y()*1024 + info_.pos_y;
514
515 if(info_.pos_x_rand) {
516 p.pos_x += rand()%info_.pos_x_rand;
517 }
518
519 if(info_.pos_y_rand) {
520 p.pos_y += rand()%info_.pos_y_rand;
521 }
522
523 p.rgba[0] = info_.rgba[0];
524 p.rgba[1] = info_.rgba[1];
525 p.rgba[2] = info_.rgba[2];
526 p.rgba[3] = info_.rgba[3];
527
528 if(info_.rgba_rand[0]) {
529 p.rgba[0] += rand()%info_.rgba_rand[0];
530 }
531
532 if(info_.rgba_rand[1]) {
533 p.rgba[1] += rand()%info_.rgba_rand[1];
534 }
535
536 if(info_.rgba_rand[2]) {
537 p.rgba[2] += rand()%info_.rgba_rand[2];
538 }
539
540 if(info_.rgba_rand[3]) {
541 p.rgba[3] += rand()%info_.rgba_rand[3];
542 }
543
544 particle_generation_ -= 1000;
545 }
546 }
547
548 void draw(const rect& area, const entity& e) const {
549 if(particles_.empty()) {
550 return;
551 }
552
553 static std::vector<GLshort> vertex;
554 static std::vector<unsigned int> colors;
555 vertex.resize(particles_.size()*2);
556 colors.resize(particles_.size());
557
558 unsigned int* c = &colors[0];
559 GLshort* v = &vertex[0];
560 for(std::vector<particle>::const_iterator p = particles_.begin();
561 p != particles_.end(); ++p) {
562 *v++ = p->pos_x/1024;
563 *v++ = p->pos_y/1024;
564 if(info_.colors.size() >= 2) {
565 *c++ = info_.colors[p->ttl/info_.ttl_divisor];
566 } else {
567 *c++ = p->color;
568 }
569 }
570
571 glColor4f(1.0, 1.0, 1.0, 1.0);
572
573 glDisable(GL_TEXTURE_2D);
574 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
575 glEnableClientState(GL_COLOR_ARRAY);
576 glPointSize(info_.dot_size);
577
578 glVertexPointer(2, GL_SHORT, 0, &vertex[0]);
579 glColorPointer(4, GL_UNSIGNED_BYTE, 0, &colors[0]);
580 glDrawArrays(GL_POINTS, 0, particles_.size());
581
582 glDisableClientState(GL_COLOR_ARRAY);
583 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
584 glEnable(GL_TEXTURE_2D);
585
586 glColor4f(1.0, 1.0, 1.0, 1.0);
587 }
588 private:
589 const entity& obj_;
590 const point_particle_info& info_;
591
592 struct particle {
593 GLshort velocity_x, velocity_y;
594 int pos_x, pos_y;
595 union { unsigned int color; unsigned char rgba[4]; };
596 int ttl;
597 };
598
599 static bool particle_destroyed(const particle& p) { return p.ttl <= 0; }
600
601 int particle_generation_;
602 int generation_rate_millis_;
603 std::vector<particle> particles_;
604
605 variant get_value(const std::string& key) const {
606 return variant();
607 }
608
609 void set_value(const std::string& key, const variant& value) {
610 if(key == "generation_rate") {
611 generation_rate_millis_ = value.as_int();
612 }
613 }
614 };
615
616 class point_particle_system_factory : public particle_system_factory
617 {
618 public:
619 explicit point_particle_system_factory(wml::const_node_ptr node)
620 : info_(node)
621 {}
622
623 particle_system_ptr create(const entity& e) const {
624 return particle_system_ptr(new point_particle_system(e, info_));
625 }
626
627 private:
628 point_particle_info info_;
629 };
630
631 }
632
633 const_particle_system_factory_ptr particle_system_factory::create_factory(wml::const_node_ptr node)
634 {
635 const std::string& type = node->attr("type");
636 if(type == "simple") {
637 return const_particle_system_factory_ptr(new simple_particle_system_factory(node));
638 } else if (type == "weather") {
639 return const_particle_system_factory_ptr(new weather_particle_system_factory(node));
640 } else if (type == "water") {
641 return const_particle_system_factory_ptr(new water_particle_system_factory(node));
642 } else if(type == "point") {
643 return const_particle_system_factory_ptr(new point_particle_system_factory(node));
644 }
645
646 ASSERT_LOG(false, "Unrecognized particle system type: " << node->attr("type"));
647 }
648
649 particle_system_factory::~particle_system_factory()
650 {
651 }
652
653 particle_system::~particle_system()
654 {
655 }
0 #ifndef PARTICLE_SYSTEM_HPP_INCLUDED
1 #define PARTICLE_SYSTEM_HPP_INCLUDED
2
3 #include <boost/intrusive_ptr.hpp>
4 #include <boost/shared_ptr.hpp>
5
6 #include "formula_callable.hpp"
7 #include "geometry.hpp"
8 #include "wml_node_fwd.hpp"
9
10 class entity;
11 class level;
12 class particle_system;
13 typedef boost::intrusive_ptr<particle_system> particle_system_ptr;
14 typedef boost::intrusive_ptr<const particle_system> const_particle_system_ptr;
15
16 class particle_system_factory;
17 typedef boost::shared_ptr<const particle_system_factory> const_particle_system_factory_ptr;
18
19 class particle_system_factory
20 {
21 public:
22 static const_particle_system_factory_ptr create_factory(wml::const_node_ptr node);
23
24 virtual ~particle_system_factory();
25 virtual particle_system_ptr create(const entity& e) const = 0;
26 };
27
28 class particle_system : public game_logic::formula_callable
29 {
30 public:
31 virtual ~particle_system();
32 virtual bool is_destroyed() const { return false; }
33 virtual bool should_save() const { return true; }
34 virtual void process(const level& lvl, const entity& e) = 0;
35 virtual void draw(const rect& area, const entity& e) const = 0;
36 private:
37 };
38
39 #endif
0 #include <boost/bind.hpp>
1
2 #include "button.hpp"
3 #include "dialog.hpp"
4 #include "graphical_font_label.hpp"
5 #include "pause_game_dialog.hpp"
6 #include "preferences.hpp"
7
8 namespace {
9 void end_dialog(gui::dialog* d, PAUSE_GAME_RESULT* result, PAUSE_GAME_RESULT value)
10 {
11 *result = value;
12 d->close();
13 }
14
15 }
16
17 PAUSE_GAME_RESULT show_pause_game_dialog()
18 {
19 PAUSE_GAME_RESULT result = PAUSE_GAME_QUIT;
20
21 bool show_exit = true;
22
23 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
24 show_exit = false;
25 #endif
26
27 using namespace gui;
28 dialog d(0, 0, preferences::virtual_screen_width(), preferences::virtual_screen_height());
29 widget_ptr b1(new button(widget_ptr(new graphical_font_label("Resume", "default", 2)), boost::bind(end_dialog, &d, &result, PAUSE_GAME_CONTINUE)));
30 widget_ptr b2(new button(widget_ptr(new graphical_font_label("Return to Titlescreen", "default", 2)), boost::bind(end_dialog, &d, &result, PAUSE_GAME_GO_TO_TITLESCREEN)));
31 widget_ptr b3(new button(widget_ptr(new graphical_font_label("Exit Game", "default", 2)), boost::bind(end_dialog, &d, &result, PAUSE_GAME_QUIT)));
32
33 b1->set_dim(400, 100);
34 b2->set_dim(400, 100);
35 if (show_exit) b3->set_dim(400, 100);
36
37 d.add_widget(b1, preferences::virtual_screen_width()/2 - b1->width()/2, preferences::virtual_screen_height()/2 - b1->height()*(show_exit ? 1.5 : 1));
38 d.add_widget(b2);
39 if (show_exit) d.add_widget(b3);
40
41
42 d.show_modal();
43 if(d.cancelled() && result == PAUSE_GAME_QUIT) {
44 result = PAUSE_GAME_CONTINUE;
45 }
46
47 return result;
48 }
0 #ifndef PAUSE_GAME_DIALOG_INCLUDED
1 #define PAUSE_GAME_DIALOG_INCLUDED
2
3 enum PAUSE_GAME_RESULT { PAUSE_GAME_CONTINUE, PAUSE_GAME_QUIT, PAUSE_GAME_GO_TO_TITLESCREEN };
4
5 PAUSE_GAME_RESULT show_pause_game_dialog();
6
7 struct interrupt_game_exception {
8 PAUSE_GAME_RESULT result;
9 interrupt_game_exception(PAUSE_GAME_RESULT res=PAUSE_GAME_QUIT) : result(res)
10 {}
11 };
12
13 #endif
0 #include "iphone_controls.hpp"
1 #include "joystick.hpp"
2 #include "level.hpp"
3 #include "playable_custom_object.hpp"
4 #include "wml_node.hpp"
5 #include "wml_utils.hpp"
6
7 playable_custom_object::playable_custom_object(const custom_object& obj)
8 : custom_object(obj), player_info_(*this), vertical_look_(0),
9 underwater_ctrl_x_(0), underwater_ctrl_y_(0), underwater_controls_(false),
10 can_interact_(0)
11 {
12 }
13
14 playable_custom_object::playable_custom_object(const playable_custom_object& obj)
15 : custom_object(obj), player_info_(obj.player_info_),
16 save_condition_(obj.save_condition_), vertical_look_(0),
17 underwater_ctrl_x_(0), underwater_ctrl_y_(0), underwater_controls_(false),
18 can_interact_(0)
19 {
20 player_info_.set_entity(*this);
21 }
22
23 playable_custom_object::playable_custom_object(wml::const_node_ptr node)
24 : custom_object(node), player_info_(*this, node), vertical_look_(0),
25 underwater_ctrl_x_(0), underwater_ctrl_y_(0),
26 underwater_controls_(wml::get_bool(node, "underwater_controls", false)),
27 can_interact_(0)
28 {
29 }
30
31 wml::node_ptr playable_custom_object::write() const
32 {
33 wml::node_ptr node = custom_object::write();
34 node->set_attr("is_human", "true");
35 if(underwater_controls_) {
36 node->set_attr("underwater_controls", "true");
37 }
38 player_info_.write(node);
39 return node;
40 }
41
42 void playable_custom_object::save_game()
43 {
44 save_condition_ = clone();
45 }
46
47 entity_ptr playable_custom_object::backup() const
48 {
49 return entity_ptr(new playable_custom_object(*this));
50 }
51
52 entity_ptr playable_custom_object::clone() const
53 {
54 return entity_ptr(new playable_custom_object(*this));
55 }
56
57 bool playable_custom_object::is_active(const rect& screen_area) const
58 {
59 //player objects are always active.
60 return true;
61 }
62
63 int playable_custom_object::walk_up_or_down_stairs() const
64 {
65 return control_status(controls::CONTROL_DOWN) - control_status(controls::CONTROL_UP);
66 }
67
68 void playable_custom_object::process(level& lvl)
69 {
70 if(player_info_.current_level() != lvl.id()) {
71 player_info_.set_current_level(lvl.id());
72 }
73
74 if(can_interact_ > 0) {
75 --can_interact_;
76 }
77
78 iphone_controls::set_underwater(underwater_controls_);
79 iphone_controls::set_can_interact(can_interact_ != 0);
80
81 float underwater_x, underwater_y;
82 if(underwater_controls_ && iphone_controls::water_dir(&underwater_x, &underwater_y)) {
83 underwater_ctrl_x_ = underwater_x*1000;
84 underwater_ctrl_y_ = underwater_y*1000;
85 } else {
86 underwater_ctrl_x_ = 0;
87 underwater_ctrl_y_ = 0;
88 }
89
90 bool controls[controls::NUM_CONTROLS];
91 for(int n = 0; n != controls::NUM_CONTROLS; ++n) {
92 controls[n] = control_status(static_cast<controls::CONTROL_ITEM>(n));
93 }
94
95 clear_control_status();
96 read_controls(lvl.cycle());
97 static const std::string keys[] = { "up", "down", "left", "right", "attack", "jump", "tongue" };
98 for(int n = 0; n != controls::NUM_CONTROLS; ++n) {
99 if(controls[n] != control_status(static_cast<controls::CONTROL_ITEM>(n))) {
100 if(controls[n]) {
101 handle_event("end_ctrl_" + keys[n]);
102 } else {
103 handle_event("ctrl_" + keys[n]);
104 }
105 }
106 }
107
108 custom_object::process(lvl);
109
110 }
111
112 namespace {
113 static const char* ctrl[] = { "ctrl_up", "ctrl_down", "ctrl_left", "ctrl_right", "ctrl_attack", "ctrl_jump", "ctrl_tongue" };
114 }
115
116 variant playable_custom_object::get_value(const std::string& key) const
117 {
118 if(key == "can_interact") {
119 return variant(can_interact_);
120 } else if(key == "underwater_controls") {
121 return variant(underwater_controls_);
122 } else if(key == "ctrl_tilt") {
123 return variant(-joystick::iphone_tilt());
124 } else if(key == "ctrl_x") {
125 return variant(underwater_ctrl_x_);
126 } else if(key == "ctrl_y") {
127 return variant(underwater_ctrl_y_);
128 }
129
130 for(int n = 0; n < sizeof(ctrl)/sizeof(*ctrl); ++n) {
131 if(key == ctrl[n]) {
132 return variant(control_status(static_cast<controls::CONTROL_ITEM>(n)));
133 }
134 }
135
136 if(key == "player") {
137 return variant(1);
138 } else if(key == "vertical_look") {
139 return variant(vertical_look_);
140 }
141
142 return custom_object::get_value(key);
143 }
144
145 void playable_custom_object::set_value(const std::string& key, const variant& value)
146 {
147 if(key == "can_interact") {
148 can_interact_ = value.as_int();
149 } else if(key == "underwater_controls") {
150 underwater_controls_ = value.as_bool();
151 } else if(key == "vertical_look") {
152 vertical_look_ = value.as_int();
153 } else if(key == "control_lock") {
154 if(value.is_null()) {
155 control_lock_.reset();
156 } else if(value.is_list()) {
157 unsigned char state = 0;
158 for(int n = 0; n != value.num_elements(); ++n) {
159 ASSERT_LOG(value[n].is_string(), "MEMBER OF control_lock LIST NOT A STRING");
160 const std::string& str = value[n].as_string();
161 int control_key = -1;
162 for(int m = 0; m != sizeof(ctrl)/sizeof(*ctrl); ++m) {
163 if(ctrl[m] == str) {
164 control_key = m;
165 break;
166 }
167 }
168
169 ASSERT_LOG(control_key != -1, "ILLEGAL STRING SET FOR control_lock: '" << str << "' LEGAL KEYS ARE ctrl_(up|down|left|right|attack|jump)");
170 state |= 1 << control_key;
171 }
172
173 std::cerr << "SET CONTROL STATE: " << (int)state << "\n";
174
175 //destroy the old one before creating a new control_lock,
176 //since control_lock objects must be constructed and destroyed
177 //in FIFO order.
178 control_lock_.reset();
179 control_lock_.reset(new controls::local_controls_lock(state));
180 } else {
181 ASSERT_LOG(false, "BAD VALUE WHEN SETTING control_lock KEY. A LIST OR NULL IS REQUIRED: " << value.to_debug_string());
182 }
183 } else {
184 custom_object::set_value(key, value);
185 }
186 }
0 #ifndef PLAYABLE_CUSTOM_OBJECT_HPP_INCLUDED
1 #define PLAYABLE_CUSTOM_OBJECT_HPP_INCLUDED
2
3 #include <boost/scoped_ptr.hpp>
4
5 #include "controls.hpp"
6 #include "custom_object.hpp"
7 #include "key.hpp"
8 #include "player_info.hpp"
9 #include "wml_node_fwd.hpp"
10
11 class level;
12
13 class playable_custom_object : public custom_object
14 {
15 public:
16 playable_custom_object(const custom_object& obj);
17 playable_custom_object(const playable_custom_object& obj);
18 playable_custom_object(wml::const_node_ptr node);
19
20 virtual wml::node_ptr write() const;
21
22 virtual player_info* is_human() { return &player_info_; }
23 virtual const player_info* is_human() const { return &player_info_; }
24
25 void save_game();
26 entity_ptr save_condition() const { return save_condition_; }
27
28 virtual entity_ptr backup() const;
29 virtual entity_ptr clone() const;
30
31 virtual int vertical_look() const { return vertical_look_; }
32
33 virtual bool is_active(const rect& screen_area) const;
34
35 bool can_interact() const { return can_interact_ != 0; }
36
37 private:
38 int walk_up_or_down_stairs() const;
39
40 virtual void process(level& lvl);
41 variant get_value(const std::string& key) const;
42 void set_value(const std::string& key, const variant& value);
43
44 player_info player_info_;
45
46 entity_ptr save_condition_;
47
48 int vertical_look_;
49
50 int underwater_ctrl_x_, underwater_ctrl_y_;
51
52 bool underwater_controls_;
53
54 int can_interact_;
55
56 boost::scoped_ptr<controls::local_controls_lock> control_lock_;
57
58 void operator=(const playable_custom_object);
59 };
60
61 #endif
0 #include "controls.hpp"
1 #include "entity.hpp"
2 #include "foreach.hpp"
3 #include "formatter.hpp"
4 #include "joystick.hpp"
5 #include "player_info.hpp"
6 #include "string_utils.hpp"
7 #include "wml_node.hpp"
8 #include "wml_utils.hpp"
9
10 player_info::player_info(entity& e, wml::const_node_ptr node)
11 : entity_(&e),
12 slot_(0)
13 {
14 FOREACH_WML_CHILD(objects_node, node, "objects_destroyed") {
15 std::vector<int>& v = objects_destroyed_[objects_node->attr("level")];
16 v = vector_lexical_cast<int>(util::split(objects_node->attr("objects")));
17 }
18 }
19
20 void player_info::object_destroyed(const std::string& level_id, int object)
21 {
22 objects_destroyed_[level_id].push_back(object);
23 }
24
25 const std::vector<int>& player_info::get_objects_destroyed(const std::string& level_id) const
26 {
27 std::vector<int>& v = objects_destroyed_[level_id];
28 std::sort(v.begin(), v.end());
29 v.erase(std::unique(v.begin(), v.end()), v.end());
30 return v;
31 }
32
33 void player_info::write(wml::node_ptr result) const
34 {
35 for(std::map<std::string, std::vector<int> >::const_iterator i = objects_destroyed_.begin(); i != objects_destroyed_.end(); ++i) {
36 get_objects_destroyed(i->first); //remove duplicates.
37
38 wml::node_ptr objects(new wml::node("objects_destroyed"));
39 objects->set_attr("level", i->first);
40 std::ostringstream s;
41 foreach(int n, i->second) {
42 s << n << ",";
43 }
44
45 std::string str = s.str();
46 if(str.empty() == false) {
47 str.resize(str.size() - 1);
48 }
49
50 objects->set_attr("objects", str);
51 result->add_child(objects);
52 }
53 }
54
55 void player_info::read_controls(int cycle)
56 {
57 bool status[controls::NUM_CONTROLS];
58 controls::get_control_status(cycle, slot_, status);
59
60 const int tilt = joystick::iphone_tilt();
61 if(tilt <= -400) {
62 status[controls::CONTROL_RIGHT] = true;
63 } else if(tilt >= 400) {
64 status[controls::CONTROL_LEFT] = true;
65 }
66
67 if(status[controls::CONTROL_LEFT] && status[controls::CONTROL_RIGHT]) {
68 //if both left and right are held, treat it as if neither are.
69 status[controls::CONTROL_LEFT] = status[controls::CONTROL_RIGHT] = false;
70 }
71
72 for(int n = 0; n != controls::NUM_CONTROLS; ++n) {
73 entity_->set_control_status(static_cast<controls::CONTROL_ITEM>(n), status[n]);
74 }
75 }
0 #ifndef PLAYER_INFO_HPP_INCLUDED
1 #define PLAYER_INFO_HPP_INCLUDED
2
3 #include <map>
4 #include <string>
5 #include <vector>
6
7 #include "wml_node_fwd.hpp"
8
9 //class which contains information about the player.
10 class player_info
11 {
12 public:
13 explicit player_info(entity& e) : entity_(&e), slot_(0)
14 {}
15 player_info(entity& e, wml::const_node_ptr node);
16
17 void object_destroyed(const std::string& level_id, int item);
18 const std::vector<int>& get_objects_destroyed(const std::string& level_id) const;
19
20 void write(wml::node_ptr node) const;
21
22 void swap_player_state(player_info& player) {
23 items_destroyed_.swap(player.items_destroyed_);
24 objects_destroyed_.swap(player.objects_destroyed_);
25 }
26
27 const entity& get_entity() const { return *entity_; }
28 entity& get_entity() { return *entity_; }
29
30 void set_entity(entity& e) { entity_ = &e; }
31
32 const std::string& current_level() const { return current_level_; }
33 void set_current_level(const std::string& lvl) { current_level_ = lvl; }
34
35 void set_player_slot(int slot) { slot_ = slot; }
36
37 void read_controls(int cycle);
38
39 private:
40 entity* entity_;
41
42 mutable std::map<std::string, std::vector<int> > items_destroyed_;
43 mutable std::map<std::string, std::vector<int> > objects_destroyed_;
44
45 //the number of the player.
46 int slot_;
47
48 std::string current_level_;
49 };
50
51 #endif
0 #ifndef POINT_MAP_HPP_INCLUDED
1 #define POINT_MAP_HPP_INCLUDED
2
3 #include <vector>
4
5 #include "geometry.hpp"
6
7 //A point_map is a data structure which can map (x,y) points as keys to
8 //values.
9 template<typename ValueType>
10 class point_map
11 {
12 public:
13
14 const ValueType& get(const point& p) const {
15 const ValueType* value = lookup(p);
16 if(!value) {
17 static ValueType EmptyValue;
18 return EmptyValue;
19 } else {
20 return *value;
21 }
22 }
23
24 void insert(const point& p, ValueType value) {
25 Row* row;
26 if(p.y < 0) {
27 const int index = -p.y - 1;
28 if(index >= negative_rows_.size()) {
29 negative_rows_.resize((index + 1)*2);
30 }
31
32 row = &negative_rows_[index];
33 } else {
34 const int index = p.y;
35 if(index >= positive_rows_.size()) {
36 positive_rows_.resize((index + 1)*2);
37 }
38
39 row = &positive_rows_[index];
40 }
41
42 if(p.x < 0) {
43 const int index = -p.x - 1;
44 if(index >= row->negative_cells.size()) {
45 row->negative_cells.resize(index+1);
46 }
47
48 row->negative_cells[index] = value;
49 } else {
50 const int index = p.x;
51 if(index >= row->positive_cells.size()) {
52 row->positive_cells.resize(index+1);
53 }
54
55 row->positive_cells[index] = value;
56 }
57 }
58
59 private:
60
61 const ValueType* lookup(const point& p) const {
62
63 const Row* row;
64 if(p.y < 0) {
65 const int index = -p.y - 1;
66 if(index >= negative_rows_.size()) {
67 return NULL;
68 }
69
70 row = &negative_rows_[index];
71 } else {
72 const int index = p.y;
73 if(index >= positive_rows_.size()) {
74 return NULL;
75 }
76
77 row = &positive_rows_[index];
78 }
79
80 if(p.x < 0) {
81 const int index = -p.x - 1;
82 if(index >= row->negative_cells.size()) {
83 return NULL;
84 }
85
86 return &row->negative_cells[index];
87 } else {
88 const int index = p.x;
89 if(index >= row->positive_cells.size()) {
90 return NULL;
91 }
92
93 return &row->positive_cells[index];
94 }
95 }
96
97 struct Row {
98 std::vector<ValueType> negative_cells, positive_cells;
99 };
100
101 std::vector<Row> negative_rows_, positive_rows_;
102 };
103
104 #endif
0 #include <map>
1
2 #include "powerup.hpp"
3 #include "wml_node.hpp"
4 #include "wml_utils.hpp"
5
6 namespace {
7 typedef std::map<std::string, const_powerup_ptr> powerup_map;
8 powerup_map cache;
9 }
10
11 powerup::powerup(wml::const_node_ptr node)
12 : id_(node->attr("id")), modifier_(node),
13 icon_(new frame(node->get_child("icon"))),
14 duration_(wml::get_int(node, "duration", -1)),
15 permanent_(wml::get_bool(node, "permanent"))
16 {
17 }
18
19 void powerup::init(wml::const_node_ptr node)
20 {
21 wml::node::const_child_iterator p1 = node->begin_child("powerup");
22 wml::node::const_child_iterator p2 = node->end_child("powerup");
23 for(; p1 != p2; ++p1) {
24 cache[p1->second->attr("id")].reset(new powerup(p1->second));
25 }
26 }
27
28 const_powerup_ptr powerup::get(const std::string& id)
29 {
30 powerup_map::const_iterator itor = cache.find(id);
31 if(itor == cache.end()) {
32 return const_powerup_ptr();
33 }
34
35 return itor->second;
36 }
0 #ifndef POWERUP_HPP_INCLUDED
1 #define POWERUP_HPP_INCLUDED
2
3 #include <boost/scoped_ptr.hpp>
4
5 #include <string>
6
7 #include "frame.hpp"
8 #include "powerup_fwd.hpp"
9 #include "wml_modify.hpp"
10 #include "wml_node_fwd.hpp"
11
12 class frame;
13
14 class powerup
15 {
16 public:
17 explicit powerup(wml::const_node_ptr node);
18 static void init(wml::const_node_ptr node);
19 static const_powerup_ptr get(const std::string& id);
20
21 const std::string& id() const { return id_; }
22 const wml::modifier& modifier() const { return modifier_; }
23 const frame& icon() const { return *icon_; }
24 int duration() const { return duration_; }
25 bool is_permanent() const { return permanent_; }
26
27 private:
28 std::string id_;
29 wml::modifier modifier_;
30 boost::scoped_ptr<frame> icon_;
31 int duration_;
32 bool permanent_;
33 };
34
35 #endif
0 #ifndef POWERUP_FWD_HPP_INCLUDED
1 #define POWERUP_FWD_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 class powerup;
6 typedef boost::shared_ptr<powerup> powerup_ptr;
7 typedef boost::shared_ptr<const powerup> const_powerup_ptr;
8
9 #endif
0 #include <iostream>
1 #include <algorithm>
2 #include <string>
3 #include <SDL.h>
4
5 #include "preferences.hpp"
6 #include "filesystem.hpp"
7
8 namespace preferences {
9 namespace {
10 int screen_editor_mode = 0;
11
12 bool no_sound_ = false;
13 bool no_music_ = false;
14 bool show_debug_hitboxes_ = false;
15 bool use_pretty_scaling_ = false;
16 bool fullscreen_ = false;
17 bool debug_ = true;
18
19 std::string level_path_ = "data/level/";
20
21 bool send_stats_ = true;
22
23 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
24
25 #ifndef PREFERENCES_PATH
26 #define PREFERENCES_PATH "../Documents/"
27 #endif
28
29 bool sim_iphone_ = true;
30
31 int virtual_screen_width_ = 960;
32 int virtual_screen_height_ = 640;
33
34 int actual_screen_width_ = 320;
35 int actual_screen_height_ = 480;
36
37 bool screen_rotated_ = true;
38
39 bool show_fps_ = false;
40
41 bool load_compiled_ = true;
42
43 bool use_16bpp_textures_ = true;
44 #else
45 bool sim_iphone_ = false;
46
47 #ifndef PREFERENCES_PATH
48 #define PREFERENCES_PATH "~/.frogatto/"
49 #endif
50 int virtual_screen_width_ = 800;
51 int virtual_screen_height_ = 600;
52
53 int actual_screen_width_ = 800;
54 int actual_screen_height_ = 600;
55
56 bool screen_rotated_ = false;
57
58 bool show_fps_ = false;
59
60 bool load_compiled_ = false;
61
62 bool use_16bpp_textures_ = false;
63 #endif
64
65 #define SAVE_FILENAME "save.cfg"
66 #define AUTOSAVE_FILENAME "autosave.cfg"
67
68 std::string preferences_path_ = PREFERENCES_PATH;
69 std::string save_file_path_ = PREFERENCES_PATH SAVE_FILENAME;
70 std::string auto_save_file_path_ = PREFERENCES_PATH AUTOSAVE_FILENAME;
71
72 bool force_no_npot_textures_ = false;
73 }
74
75 int xypos_draw_mask = actual_screen_width_ < virtual_screen_width_ ? ~1 : ~0;
76 bool double_scale() {
77 return xypos_draw_mask&1 != 0;
78 }
79 bool compiling_tiles = false;
80
81 namespace {
82 void recalculate_draw_mask() {
83 xypos_draw_mask = actual_screen_width_ < virtual_screen_width_ ? ~1 : ~0;
84 }
85 }
86
87 bool no_sound() {
88 return no_sound_;
89 }
90
91 bool no_music() {
92 return no_music_;
93 }
94
95 bool setup_preferences_dir()
96 {
97 return !sys::get_dir(user_data_path()).empty();
98 }
99
100 void set_preferences_path(const std::string& path)
101 {
102 preferences_path_ = path;
103 if(preferences_path_[preferences_path_.length()-1] != '/') {
104 preferences_path_ += '/';
105 }
106
107 save_file_path_ = preferences_path_ + SAVE_FILENAME;
108 auto_save_file_path_ = preferences_path_ + AUTOSAVE_FILENAME;
109 }
110
111 const std::string& level_path() {
112 return level_path_;
113 }
114
115 const char *save_file_path() {
116 return save_file_path_.c_str();
117 }
118
119 const char *auto_save_file_path() {
120 return auto_save_file_path_.c_str();
121 }
122
123 const char *user_data_path() {
124 return preferences_path_.c_str();
125 }
126
127 namespace {
128 void expand_path(std::string& str) {
129 if(!str.empty() && str[0] == '~') {
130 str = std::string(getenv("HOME")) + std::string(str.begin()+1, str.end());
131 }
132 }
133 }
134
135 void expand_data_paths() {
136 expand_path(level_path_);
137 expand_path(save_file_path_);
138 expand_path(auto_save_file_path_);
139 expand_path(preferences_path_);
140 }
141
142 bool show_debug_hitboxes() {
143 return show_debug_hitboxes_;
144 }
145
146 bool use_pretty_scaling() {
147 return use_pretty_scaling_;
148 }
149
150 void set_use_pretty_scaling(bool value) {
151 use_pretty_scaling_ = value;
152 }
153
154 bool fullscreen() {
155 return fullscreen_;
156 }
157
158 void set_fullscreen(bool value) {
159 fullscreen_ = value;
160 }
161
162 void set_widescreen()
163 {
164 virtual_screen_width_ = (virtual_screen_height_*16)/9;
165 actual_screen_width_ = (actual_screen_height_*16)/9;
166 recalculate_draw_mask();
167 }
168
169 int virtual_screen_width()
170 {
171 return virtual_screen_width_;
172 }
173
174 int virtual_screen_height()
175 {
176 return virtual_screen_height_;
177 }
178
179 void set_virtual_screen_width (int width)
180 {
181 virtual_screen_width_ = width;
182 recalculate_draw_mask();
183 }
184
185 void set_virtual_screen_height (int height)
186 {
187 virtual_screen_height_ = height;
188 }
189
190 int actual_screen_width()
191 {
192 return actual_screen_width_;
193 }
194
195 int actual_screen_height()
196 {
197 return actual_screen_height_;
198 }
199
200 void set_actual_screen_width(int width)
201 {
202 actual_screen_width_ = width;
203 if(screen_editor_mode) {
204 virtual_screen_width_ = actual_screen_width_;
205 }
206 recalculate_draw_mask();
207 }
208
209 void set_actual_screen_height(int height)
210 {
211 actual_screen_height_ = height;
212 if(screen_editor_mode) {
213 virtual_screen_height_ = actual_screen_height_;
214 }
215 }
216
217 bool load_compiled()
218 {
219 return load_compiled_;
220 }
221
222 void set_load_compiled(bool value)
223 {
224 load_compiled_ = value;
225 }
226
227 bool force_no_npot_textures()
228 {
229 return force_no_npot_textures_;
230 }
231
232 bool screen_rotated()
233 {
234 return screen_rotated_;
235 }
236
237 bool debug()
238 {
239 return debug_;
240 }
241
242 bool show_fps()
243 {
244 return show_fps_;
245 }
246
247 editor_screen_size_scope::editor_screen_size_scope() : width_(virtual_screen_width_), height_(virtual_screen_height_) {
248 ++screen_editor_mode;
249 virtual_screen_width_ = actual_screen_width_;
250 virtual_screen_height_ = actual_screen_height_;
251 }
252
253 editor_screen_size_scope::~editor_screen_size_scope() {
254 virtual_screen_width_ = width_;
255 virtual_screen_height_ = height_;
256 --screen_editor_mode;
257 }
258
259 bool parse_arg(const char* arg) {
260 const std::string s(arg);
261
262 std::string arg_name, arg_value;
263 std::string::const_iterator equal = std::find(s.begin(), s.end(), '=');
264 if(equal != s.end()) {
265 arg_name = std::string(s.begin(), equal);
266 arg_value = std::string(equal+1, s.end());
267 }
268
269 if(arg_name == "--level-path") {
270 level_path_ = arg_value + "/";
271 } else if(s == "--show-hitboxes") {
272 show_debug_hitboxes_ = true;
273 } else if(s == "--scale") {
274 set_use_pretty_scaling(true);
275 } else if(s == "--no-sound") {
276 no_sound_ = true;
277 } else if(s == "--no-music") {
278 no_music_ = true;
279 } else if(s == "--fullscreen") {
280 fullscreen_ = true;
281 } else if(s == "--widescreen") {
282 set_widescreen();
283 } else if(s == "--bigscreen") {
284 virtual_screen_width_ = actual_screen_width_;
285 virtual_screen_height_ = actual_screen_height_;
286 } else if(s == "--potonly") {
287 force_no_npot_textures_ = true;
288 } else if(s == "--textures16") {
289 use_16bpp_textures_ = true;
290 } else if(s == "--textures32") {
291 use_16bpp_textures_ = false;
292 } else if(s == "--debug") {
293 debug_ = true;
294 } else if(s == "--no-debug") {
295 debug_ = false;
296 } else if(s == "--simiphone") {
297 sim_iphone_ = true;
298
299 virtual_screen_width_ = 960;
300 virtual_screen_height_ = 640;
301
302 actual_screen_width_ = 480;
303 actual_screen_height_ = 320;
304 use_16bpp_textures_ = true;
305
306 recalculate_draw_mask();
307 } else if(s == "--simipad") {
308 sim_iphone_ = true;
309
310 virtual_screen_width_ = 1024;
311 virtual_screen_height_ = 768;
312
313 actual_screen_width_ = 1024;
314 actual_screen_height_ = 768;
315
316 recalculate_draw_mask();
317 } else if(s == "--fps") {
318 show_fps_ = true;
319 } else if(s == "--no-fps") {
320 show_fps_ = false;
321 } else if(arg_name == "--config-path" && !arg_value.empty()) {
322 set_preferences_path(arg_value);
323 } else if(s == "--no-send-stats") {
324 send_stats_ = false;
325 } else {
326 return false;
327 }
328
329 return true;
330 }
331
332 bool use_16bpp_textures() {
333 return use_16bpp_textures_;
334 }
335
336 bool sim_iphone() {
337 return sim_iphone_;
338 }
339
340 bool send_stats() {
341 return send_stats_;
342 }
343 }
0 #ifndef PREFERENCES_HPP_INCLUDED
1 #define PREFERENCES_HPP_INCLUDED
2
3 #include <string>
4
5 namespace preferences {
6
7 bool parse_arg(const char* arg);
8 bool no_sound();
9 bool no_music();
10
11 void set_preferences_path(const std::string& path);
12
13 bool setup_preferences_dir();
14
15 const std::string& level_path();
16 const char* user_data_path();
17 const char* save_file_path();
18 const char* auto_save_file_path();
19 void expand_data_paths();
20 bool show_debug_hitboxes();
21 bool use_pretty_scaling();
22 void set_use_pretty_scaling(bool value);
23 bool fullscreen();
24 void set_fullscreen(bool value);
25
26 void set_widescreen();
27
28 int virtual_screen_width();
29 int virtual_screen_height();
30
31 int actual_screen_width();
32 int actual_screen_height();
33
34 //whether we are debugging
35 bool debug();
36
37 //whether to show the fps display at the top
38 bool show_fps();
39
40 //load compiled data from data/compiled/
41 bool load_compiled();
42
43 void set_load_compiled(bool value);
44
45 //this is the mask which we apply to all x,y values before drawing, to
46 //avoid drawing things at "half pixels" when the actual screen dimensions
47 //are lower than the virtual screen dimensions.
48 extern int xypos_draw_mask;
49
50 bool double_scale();
51
52 //this is a flag set to true iff we are in a mode where we write
53 //'compiled' tile output.
54 extern bool compiling_tiles;
55
56 void set_actual_screen_width(int width);
57 void set_actual_screen_height(int height);
58 void set_virtual_screen_width(int width);
59 void set_virtual_screen_height(int height);
60
61 bool screen_rotated();
62
63 bool force_no_npot_textures();
64
65 bool use_16bpp_textures();
66
67 bool sim_iphone();
68
69 bool send_stats();
70
71 class editor_screen_size_scope {
72 int width_, height_;
73 public:
74 editor_screen_size_scope();
75 ~editor_screen_size_scope();
76 };
77 }
78
79 #endif
0 #include <algorithm>
1 #include <iostream>
2 #include <fstream>
3 #include <sstream>
4 #include <string>
5
6 #include "preprocessor.hpp"
7 #include "filesystem.hpp"
8 std::string preprocess(const std::string& input){
9 std::string output_string;
10
11 std::string::const_iterator i = input.begin();
12
13 while(i != input.end()){
14 //std::cerr << "test";
15 if(*i == '@'){
16
17
18 // process pre-processing directive here. See what comes after the '@' and do something appropriate
19 static const std::string IncludeString = "@include";
20 if(input.end() - i > IncludeString.size() && std::equal(IncludeString.begin(), IncludeString.end(), i)) {
21 std::string filename_string;
22
23 i += IncludeString.size(); //skip past the directive - we've tested that it exists
24
25 //test for an argument to @include - e.g. "filename.cfg". First the open quote:
26 std::string::const_iterator quote = std::find(i, input.end(), '"');
27 if(quote == input.end()) {
28 std::cerr << "we didn't find a opening quote. Syntax error." << std::endl;
29 }
30 if(std::count_if(i, quote, isspace) != quote - i) {
31 // # of whitespaces != number of intervening chars => something else was present. Syntax Error.
32 std::cerr << "# of whitespaces != number of intervening chars." << std::endl;
33 }
34 i = quote + 1; //we've found a quote, advance past it
35 //now the closing quote, and use it to find what's inbetween:
36 std::string::const_iterator endQuote = std::find(i, input.end(), '"');
37 if(endQuote == input.end()) {
38 std::cerr << "we didn't find a closing quote. Syntax error." << std::endl;
39 }
40
41
42 filename_string = std::string(i, endQuote);
43
44 i = endQuote + 1;
45
46
47 output_string += preprocess(sys::read_file(filename_string));
48 }
49 } else {
50 //nothing special to process, just copy the chars across
51 output_string.push_back(*i);
52 }
53 ++i;
54 }
55
56 return output_string;
57 }
58
59
60
61
62 #ifdef BUILD_PREPROCESSOR_TOOL
63
64 extern "C" int main(int argc, char** argv)
65 {
66
67
68 for(int i = 1; i < argc; ++i) {
69 std::ifstream file(argv[i], std::ios_base::binary);
70 std::stringstream ss;
71 ss << file.rdbuf();
72 std::cout << preprocess(ss.str());
73 }
74
75 }
76 #endif
0 #ifndef PREPROCESSOR_HPP_INCLUDED
1 #define PREPROCESSOR_HPP_INCLUDED
2
3 std::string preprocess(const std::string& input);
4
5
6 #endif
0 #include <iostream>
1
2 #include "foreach.hpp"
3 #include "preview_tileset_widget.hpp"
4 #include "tile_map.hpp"
5
6 namespace gui {
7
8 preview_tileset_widget::preview_tileset_widget(const tile_map& tiles)
9 : width_(0), height_(0)
10 {
11 tiles.build_tiles(&tiles_);
12
13 foreach(const level_tile& t, tiles_) {
14 const int w = t.x + t.object->width();
15 const int h = t.y + t.object->height();
16
17 width_ = std::max(width_, w);
18 height_ = std::max(height_, h);
19 }
20
21 set_dim(width_, height_);
22 }
23
24 void preview_tileset_widget::handle_draw() const
25 {
26 if(width_ == 0 || height_ == 0) {
27 return;
28 }
29
30 const GLfloat scale = std::min(GLfloat(width())/width_, GLfloat(height())/height_);
31 glPushMatrix();
32 glTranslatef(x(), y(), 0);
33 glScalef(scale, scale, 0.0);
34 foreach(const level_tile& t, tiles_) {
35 graphics::blit_queue q;
36 level_object::queue_draw(q, t);
37 q.do_blit();
38 }
39 glPopMatrix();
40 }
41
42 }
0 #ifndef PREVIEW_TILESET_WIDGET_HPP_INCLUDED
1 #define PREVIEW_TILESET_WIDGET_HPP_INCLUDED
2
3 #include "level_object.hpp"
4 #include "widget.hpp"
5
6 class tile_map;
7
8 namespace gui {
9
10 class preview_tileset_widget : public widget
11 {
12 public:
13 explicit preview_tileset_widget(const tile_map& tiles);
14 private:
15 void handle_draw() const;
16 std::vector<level_tile> tiles_;
17 int width_, height_;
18 };
19
20 }
21
22 #endif
0 #include <boost/bind.hpp>
1 #include <boost/function.hpp>
2 #include <sstream>
3
4 #include "button.hpp"
5 #include "editor_dialogs.hpp"
6 #include "foreach.hpp"
7 #include "grid_widget.hpp"
8 #include "image_widget.hpp"
9 #include "label.hpp"
10 #include "load_level.hpp"
11 #include "property_editor_dialog.hpp"
12 #include "raster.hpp"
13 #include "text_entry_widget.hpp"
14
15 namespace editor_dialogs
16 {
17
18 property_editor_dialog::property_editor_dialog(editor& e)
19 : gui::dialog(graphics::screen_width() - 160, 160, 160, 440), editor_(e)
20 {
21 init();
22 }
23
24 void property_editor_dialog::init()
25 {
26 clear();
27 if(!entity_) {
28 return;
29 }
30
31 using namespace gui;
32
33 set_padding(5);
34
35 const frame& frame = entity_->current_frame();
36 image_widget* preview = new image_widget(frame.img());
37 preview->set_dim(frame.width(), frame.height());
38 preview->set_area(frame.area());
39 add_widget(widget_ptr(preview), 10, 10);
40
41 if(entity_->label().empty() == false) {
42 add_widget(widget_ptr(new label(entity_->label(), graphics::color_white())));
43 }
44
45 add_widget(widget_ptr(new button(widget_ptr(new label("Set Label", graphics::color_white())), boost::bind(&property_editor_dialog::set_label_dialog, this))));
46
47 game_logic::formula_callable* vars = entity_->vars();
48 if(entity_->editor_info()) {
49 foreach(const editor_variable_info& info, entity_->editor_info()->vars()) {
50 if(info.type() == editor_variable_info::XPOSITION ||
51 info.type() == editor_variable_info::YPOSITION) {
52 //don't show x/y position as they are directly editable on
53 //the map.
54 continue;
55 }
56
57 std::ostringstream s;
58 s << info.variable_name() << ": " << vars->query_value(info.variable_name()).to_debug_string();
59 label_ptr lb = label::create(s.str(), graphics::color_white());
60 add_widget(widget_ptr(lb));
61
62 if(info.type() == editor_variable_info::TYPE_TEXT) {
63 std::string current_value;
64 variant current_value_var = entity_->query_value(info.variable_name());
65 if(current_value_var.is_string()) {
66 current_value = current_value_var.as_string();
67 }
68
69 add_widget(widget_ptr(new button(
70 widget_ptr(new label(current_value.empty() ? "(set text)" : current_value, graphics::color_white())),
71 boost::bind(&property_editor_dialog::change_text_property, this, info.variable_name()))));
72 } else if(info.type() == editor_variable_info::TYPE_LEVEL) {
73 std::string current_value;
74 variant current_value_var = entity_->query_value(info.variable_name());
75 if(current_value_var.is_string()) {
76 current_value = current_value_var.as_string();
77 }
78
79 add_widget(widget_ptr(new button(
80 widget_ptr(new label(current_value.empty() ? "(set level)" : current_value, graphics::color_white())),
81 boost::bind(&property_editor_dialog::change_level_property, this, info.variable_name()))));
82 } else if(info.type() == editor_variable_info::TYPE_LABEL) {
83 std::string current_value;
84 variant current_value_var = entity_->query_value(info.variable_name());
85 if(current_value_var.is_string()) {
86 current_value = current_value_var.as_string();
87 }
88
89 add_widget(widget_ptr(new button(
90 widget_ptr(new label(current_value.empty() ? "(set label)" : current_value, graphics::color_white())),
91 boost::bind(&property_editor_dialog::change_label_property, this, info.variable_name()))));
92
93 } else if(info.type() == editor_variable_info::TYPE_BOOLEAN) {
94 variant current_value = entity_->query_value(info.variable_name());
95 std::cerr << "CURRENT VALUE: " << current_value.as_bool() << "\n";
96 add_widget(widget_ptr(new button(
97 widget_ptr(new label("toggle", graphics::color_white())),
98 boost::bind(&property_editor_dialog::toggle_property, this, info.variable_name()))));
99 } else {
100 grid_ptr buttons_grid(new grid(4));
101 buttons_grid->add_col(widget_ptr(new button(widget_ptr(new label("-10", graphics::color_white())), boost::bind(&property_editor_dialog::change_property, this, info.variable_name(), -10))));
102 buttons_grid->add_col(widget_ptr(new button(widget_ptr(new label("-1", graphics::color_white())), boost::bind(&property_editor_dialog::change_property, this, info.variable_name(), -1))));
103 buttons_grid->add_col(widget_ptr(new button(widget_ptr(new label("+1", graphics::color_white())), boost::bind(&property_editor_dialog::change_property, this, info.variable_name(), +1))));
104 buttons_grid->add_col(widget_ptr(new button(widget_ptr(new label("+10", graphics::color_white())), boost::bind(&property_editor_dialog::change_property, this, info.variable_name(), +10))));
105 add_widget(widget_ptr(buttons_grid));
106 }
107 }
108 }
109 }
110
111 void property_editor_dialog::set_entity(entity_ptr e)
112 {
113 entity_ = e;
114 init();
115 }
116
117 void property_editor_dialog::toggle_property(const std::string& id)
118 {
119 game_logic::formula_callable* vars = entity_->vars();
120 if(vars) {
121 vars->mutate_value(id, variant(!vars->query_value(id).as_bool()));
122 init();
123 }
124 }
125
126 void property_editor_dialog::change_property(const std::string& id, int change)
127 {
128 std::cerr << "CHANGE PROPERTY: " << change << "\n";
129 game_logic::formula_callable* vars = entity_->vars();
130 if(vars) {
131 vars->mutate_value(id, vars->query_value(id) + variant(change));
132 init();
133 }
134 }
135
136 void property_editor_dialog::change_level_property(const std::string& id)
137 {
138 std::string lvl = show_choose_level_dialog("Set " + id);
139 if(lvl.empty() == false) {
140 entity_->mutate_value(id, variant(lvl));
141 init();
142 }
143 }
144
145 void property_editor_dialog::set_label_dialog()
146 {
147 using namespace gui;
148 text_entry_widget* entry = new text_entry_widget;
149 dialog d(200, 200, 400, 200);
150 d.add_widget(widget_ptr(new label("Label:", graphics::color_white())))
151 .add_widget(widget_ptr(entry));
152 d.show_modal();
153
154 //we have to add and remove the character from the level to
155 //modify their label properly.
156 editor_.get_level().remove_character(entity_);
157 entity_->set_label(entry->text());
158 editor_.get_level().add_character(entity_);
159 init();
160 }
161
162 void property_editor_dialog::change_text_property(const std::string& id)
163 {
164 //show a text box to set the new text
165 using namespace gui;
166 text_entry_widget* entry = new text_entry_widget;
167 dialog d(200, 200, 400, 200);
168 d.add_widget(widget_ptr(new label("Label:", graphics::color_white())))
169 .add_widget(widget_ptr(entry));
170 d.show_modal();
171
172 //set the variable to the new value
173 game_logic::formula_callable* vars = entity_->vars();
174 if(vars) {
175 vars->mutate_value(id, variant(entry->text()));
176 init();
177 }
178 }
179
180 namespace {
181 bool hidden_label(const std::string& label) {
182 return label.empty() || label[0] == '_';
183 }
184 }
185
186 void property_editor_dialog::change_label_property(const std::string& id)
187 {
188 const editor_variable_info* var_info = entity_->editor_info() ? entity_->editor_info()->get_var_info(id) : NULL;
189 if(!var_info) {
190 return;
191 }
192
193 bool loaded_level = false;
194 std::vector<std::string> labels;
195 if(var_info->info().empty() == false && var_info->info() != editor_.get_level().id()) {
196 variant level_id = entity_->query_value(var_info->info());
197 if(level_id.is_string() && level_id.as_string().empty() == false && level_id.as_string() != editor_.get_level().id()) {
198 level lvl(level_id.as_string());
199 lvl.finish_loading();
200 lvl.get_all_labels(labels);
201 loaded_level = true;
202 }
203 }
204
205 if(!loaded_level) {
206 editor_.get_level().get_all_labels(labels);
207 }
208
209 labels.erase(std::remove_if(labels.begin(), labels.end(), hidden_label), labels.end());
210
211 if(labels.empty() == false) {
212 gui::grid* grid = new gui::grid(1);
213 grid->set_show_background(true);
214 grid->allow_selection();
215 grid->register_selection_callback(boost::bind(&property_editor_dialog::set_label_property, this, id, labels, _1));
216 foreach(const std::string& lb, labels) {
217 grid->add_col(gui::widget_ptr(new gui::label(lb, graphics::color_white())));
218 }
219
220 int mousex, mousey;
221 SDL_GetMouseState(&mousex, &mousey);
222 mousex -= x();
223 mousey -= y();
224
225 if(grid->height() > graphics::screen_height() - mousey) {
226 mousey = graphics::screen_height() - grid->height();
227 }
228
229 if(grid->width() > graphics::screen_width() - mousex) {
230 mousex = graphics::screen_width() - grid->width();
231 }
232
233 remove_widget(context_menu_);
234 context_menu_.reset(grid);
235 add_widget(context_menu_, mousex, mousey);
236 }
237 }
238
239 void property_editor_dialog::set_label_property(const std::string& id, const std::vector<std::string>& labels, int index)
240 {
241 remove_widget(context_menu_);
242 context_menu_.reset();
243 if(index < 0 || index >= labels.size()) {
244 init();
245 return;
246 }
247
248 entity_->mutate_value(id, variant(labels[index]));
249
250 init();
251 }
252
253 }
0 #ifndef PROPERTY_EDITOR_DIALOG_HPP_INCLUDED
1 #define PROPERTY_EDITOR_DIALOG_HPP_INCLUDED
2
3 #include <string>
4
5 #include "dialog.hpp"
6 #include "editor.hpp"
7 #include "entity.hpp"
8
9 namespace editor_dialogs
10 {
11
12 class property_editor_dialog : public gui::dialog
13 {
14 public:
15 explicit property_editor_dialog(editor& e);
16 void init();
17
18 entity_ptr get_entity() const { return entity_; }
19 void set_entity(entity_ptr e);
20 void set_label_dialog();
21 private:
22 void toggle_property(const std::string& id);
23 void change_property(const std::string& id, int change);
24 void change_level_property(const std::string& id);
25
26 void change_label_property(const std::string& id);
27 void change_text_property(const std::string& id);
28 void set_label_property(const std::string& id, const std::vector<std::string>& labels, int index);
29
30 editor& editor_;
31 entity_ptr entity_;
32 gui::widget_ptr context_menu_;
33 };
34
35 }
36
37 #endif
0 #include <iostream>
1
2 #include "random.hpp"
3
4 namespace rng {
5
6 static unsigned int next = 1;
7
8 int generate() {
9 next = next * 1103515245 + 12345;
10 const int result = ((unsigned int)(next/65536) % 32768);
11 return result;
12 }
13
14 void set_seed(unsigned int seed) {
15 std::cerr << "RANDOM SEED: " << seed << "\n";
16 next = seed;
17 }
18
19 unsigned int get_seed() {
20 return next;
21 }
22
23 }
0 #ifndef RANDOM_HPP_INCLUDED
1 #define RANDOM_HPP_INCLUDED
2
3 namespace rng {
4
5 int generate();
6 void set_seed(unsigned int seed);
7 unsigned int get_seed();
8 }
9
10 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include <GL/gl.h>
13 #include <GL/glu.h>
14 #include <SDL.h>
15
16 #include "asserts.hpp"
17 #include "foreach.hpp"
18 #include "preferences.hpp"
19 #include "raster.hpp"
20 #include "raster_distortion.hpp"
21 #include "rectangle_rotator.hpp"
22
23 #include <boost/shared_array.hpp>
24 #include <iostream>
25 #include <cmath>
26
27 namespace graphics
28 {
29
30 /* unavoidable global variable to store global clip
31 rectangle changes */
32 std::vector<boost::shared_array<GLint> > clip_rectangles;
33
34 std::vector<GLfloat>& global_vertex_array()
35 {
36 static std::vector<GLfloat> v;
37 return v;
38 }
39
40 std::vector<GLfloat>& global_texcoords_array()
41 {
42 static std::vector<GLfloat> v;
43 return v;
44 }
45
46 std::vector<GLbyte>& global_vertex_color_array()
47 {
48 static std::vector<GLbyte> v;
49 return v;
50 }
51
52 #ifdef SDL_VIDEO_OPENGL_ES
53 #define glOrtho glOrthof
54 #endif
55
56 void prepare_raster()
57 {
58 // int real_w, real_h;
59 // bool rotated;
60
61 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
62 // real_w = 320;
63 // real_h = 480;
64 // rotated = true;
65 #else
66 const SDL_Surface* fb = SDL_GetVideoSurface();
67 if(fb == NULL) {
68 std::cerr << "Framebuffer was null in prepare_raster\n";
69 return;
70 }
71 // real_w = fb->w;
72 // real_h = fb->h;
73 // rotated = false;
74 #endif
75
76 glViewport(0, 0, preferences::actual_screen_width(), preferences::actual_screen_height());
77 // glClearColor(0.0, 0.0, 0.0, 0.0);
78 // glClear(GL_COLOR_BUFFER_BIT);
79 glShadeModel(GL_FLAT);
80 glMatrixMode(GL_PROJECTION);
81 glLoadIdentity();
82
83 if(preferences::screen_rotated()) {
84 // glOrtho(0, 640, 960, 0, -1.0, 1.0);
85 glOrtho(0, screen_height(), screen_width(), 0, -1.0, 1.0);
86 } else {
87 glOrtho(0, screen_width(), screen_height(), 0, -1.0, 1.0);
88 }
89
90 //glOrtho(0, real_w, real_h, 0, -1.0, 1.0);
91 if(preferences::screen_rotated()) {
92 // Rotate 90 degrees ccw, then move real_h pixels down
93 // This has to be in opposite order since A(); B(); means A(B(x))
94 glTranslatef(screen_height(), 0.0f, 0.0f);
95 glRotatef(90.0f, 0.0f, 0.0f, 1.0f);
96 //glTranslatef(0.0f, 0.5f, 0.0f);
97 //glScalef(0.5f, 0.5f, 1.0f);
98 }
99
100 glMatrixMode(GL_MODELVIEW);
101 glLoadIdentity();
102
103 glDisable(GL_DEPTH_TEST);
104 glDisable(GL_LIGHTING);
105 glDisable(GL_LIGHT0);
106
107 glColor4f(1.0, 1.0, 1.0, 1.0);
108 }
109
110 /*void prepare_raster()
111 {
112 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
113 int w = 320;
114 int h = 480;
115 #else
116 const SDL_Surface* fb = SDL_GetVideoSurface();
117 if(fb == NULL) {
118 std::cerr << "Framebuffer was null in prepare_raster\n";
119 return;
120 }
121 int w = fb->w;
122 int h = fb->h;
123 #endif
124
125 glViewport(0,0,w,h);
126 glClearColor(0.0,0.0,0.0,0.0);
127 glClear(GL_COLOR_BUFFER_BIT);
128 glShadeModel(GL_FLAT);
129 glMatrixMode(GL_PROJECTION);
130 glLoadIdentity();
131 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
132 glScalef(0.25f, 0.25f, 1.0f);
133 glRotatef(-90.0f, 0.0f, 0.0f, 1.0f);
134 #endif
135 #ifdef SDL_VIDEO_OPENGL_ES
136 #define glOrtho glOrthof
137 #endif
138 glOrtho(0,screen_width(),screen_height(),0,-1.0,1.0);
139 glMatrixMode(GL_MODELVIEW);
140 glLoadIdentity();
141
142 glDisable(GL_DEPTH_TEST);
143 glDisable(GL_LIGHTING);
144 glDisable(GL_LIGHT0);
145
146 glColor4f(1.0, 1.0, 1.0, 1.0);
147 }*/
148
149 namespace {
150 struct draw_detection_rect {
151 rect area;
152 char* buf;
153 };
154
155 std::vector<draw_detection_rect> draw_detection_rects_;
156 rect draw_detection_rect_;
157 char* draw_detection_buf_;
158
159 std::vector<const raster_distortion*> distortions_;
160 }
161
162 void blit_texture(const texture& tex, int x, int y, GLfloat rotate)
163 {
164 if(!tex.valid()) {
165 return;
166 }
167
168 x &= preferences::xypos_draw_mask;
169 y &= preferences::xypos_draw_mask;
170
171 int h = tex.height();
172 int w = tex.width();
173 const int w_odd = w % 2;
174 const int h_odd = h % 2;
175 h /= 2;
176 w /= 2;
177
178 glPushMatrix();
179
180 glTranslatef(x+w,y+h,0.0);
181 glRotatef(rotate,0.0,0.0,1.0);
182
183 tex.set_as_current_texture();
184
185 GLfloat varray[] = {
186 -w, -h,
187 -w, h+h_odd,
188 w+w_odd, -h,
189 w+w_odd, h+h_odd
190 };
191 GLfloat tcarray[] = {
192 texture::get_coord_x(0.0), texture::get_coord_y(0.0),
193 texture::get_coord_x(0.0), texture::get_coord_y(1.0),
194 texture::get_coord_x(1.0), texture::get_coord_y(0.0),
195 texture::get_coord_x(1.0), texture::get_coord_y(1.0)
196 };
197 glVertexPointer(2, GL_FLOAT, 0, varray);
198 glTexCoordPointer(2, GL_FLOAT, 0, tcarray);
199 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
200
201 glPopMatrix();
202 }
203
204 namespace {
205
206 //function which marks the draw detection buffer with pixels drawn.
207 void detect_draw(const texture& tex, int x, int y, int orig_w, int orig_h, GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
208 {
209 if(draw_detection_rects_.empty()) {
210 return;
211 }
212
213 rect draw_rect(x, y, std::abs(orig_w), std::abs(orig_h));
214
215 foreach(const draw_detection_rect& detect, draw_detection_rects_) {
216 if(rects_intersect(draw_rect, detect.area)) {
217 rect r = intersection_rect(draw_rect, detect.area);
218 for(int ypos = r.y(); ypos != r.y2(); ++ypos) {
219 for(int xpos = r.x(); xpos != r.x2(); ++xpos) {
220 const GLfloat u = (GLfloat(draw_rect.x2() - xpos)*x1 + GLfloat(xpos - draw_rect.x())*x2)/GLfloat(draw_rect.w());
221 const GLfloat v = (GLfloat(draw_rect.y2() - ypos)*y1 + GLfloat(ypos - draw_rect.y())*y2)/GLfloat(draw_rect.h());
222 const int texture_x = u*tex.width();
223 const int texture_y = v*tex.height();
224 ASSERT_GE(texture_x, 0);
225 ASSERT_GE(texture_y, 0);
226 ASSERT_LOG(texture_x < tex.width(), texture_x << " < " << tex.width() << " " << r.x() << " " << r.x2() << " " << xpos << " x: " << x1 << " x2: " << x2 << " u: " << u << "\n");
227 ASSERT_LT(texture_x, tex.width());
228 ASSERT_LT(texture_y, tex.height());
229 const bool alpha = tex.is_alpha(texture_x, texture_y);
230 if(!alpha) {
231 const int buf_x = xpos - detect.area.x();
232 const int buf_y = ypos - detect.area.y();
233 const int buf_index = buf_y*detect.area.w() + buf_x;
234 ASSERT_LOG(buf_index >= 0, xpos << ", " << ypos << " -> " << buf_x << ", " << buf_y << " -> " << buf_index << " in " << detect.area << "\n");
235 ASSERT_GE(buf_index, 0);
236 ASSERT_LT(buf_index, detect.area.w()*detect.area.h());
237 detect.buf[buf_index] = true;
238 }
239 }
240 }
241 }
242 }
243 }
244
245 void blit_texture_internal(const texture& tex, int x, int y, int w, int h, GLfloat rotate, GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
246 {
247 if(!tex.valid()) {
248 return;
249 }
250
251 const int w_odd = w % 2;
252 const int h_odd = h % 2;
253
254 w /= 2;
255 h /= 2;
256 glPushMatrix();
257 tex.set_as_current_texture();
258 glTranslatef(x+abs(w),y+abs(h),0.0);
259 glRotatef(rotate,0.0,0.0,1.0);
260 GLfloat varray[] = {
261 -w, -h,
262 -w, h+h_odd,
263 w+w_odd, -h,
264 w+w_odd, h+h_odd
265 };
266 GLfloat tcarray[] = {
267 texture::get_coord_x(x1), texture::get_coord_y(y1),
268 texture::get_coord_x(x1), texture::get_coord_y(y2),
269 texture::get_coord_x(x2), texture::get_coord_y(y1),
270 texture::get_coord_x(x2), texture::get_coord_y(y2)
271 };
272 glVertexPointer(2, GL_FLOAT, 0, varray);
273 glTexCoordPointer(2, GL_FLOAT, 0, tcarray);
274 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
275 glPopMatrix();
276 }
277
278 void blit_texture_with_distortion(const texture& tex, int x, int y, int w, int h, GLfloat rotate, GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, const raster_distortion& distort)
279 {
280 const rect& area = distort.area();
281 if(x < area.x()) {
282 const int new_x = area.x();
283 const GLfloat new_x1 = (x1*(x + w - new_x) + x2*(new_x - x))/w;
284
285 blit_texture(tex, x, y, new_x - x, h, rotate, x1, y1, new_x1, y2);
286
287 x1 = new_x1;
288 w -= new_x - x;
289 x = new_x;
290 }
291
292 if(y < area.y()) {
293 const int new_y = area.y();
294 const GLfloat new_y1 = (y1*(y + h - new_y) + y2*(new_y - y))/h;
295
296 blit_texture(tex, x, y, w, new_y - y, rotate, x1, y1, x2, new_y1);
297
298 y1 = new_y1;
299 h -= new_y - y;
300 y = new_y;
301 }
302
303 if(x + w > area.x2()) {
304 const int new_w = area.x2() - x;
305 const int new_xpos = x + new_w;
306 const GLfloat new_x2 = (x1*(x + w - new_xpos) + x2*(new_xpos - x))/w;
307
308 blit_texture(tex, new_xpos, y, x + w - new_xpos, h, rotate, new_x2, y1, x2, y2);
309
310 x2 = new_x2;
311 w = new_w;
312 }
313
314 if(y + h > area.y2()) {
315 const int new_h = area.y2() - y;
316 const int new_ypos = y + new_h;
317 const GLfloat new_y2 = (y1*(y + h - new_ypos) + y2*(new_ypos - y))/h;
318
319 blit_texture(tex, x, new_ypos, w, y + h - new_ypos, rotate, x1, new_y2, x2, y2);
320
321 y2 = new_y2;
322 h = new_h;
323 }
324
325 tex.set_as_current_texture();
326
327 const int xdiff = distort.granularity_x();
328 const int ydiff = distort.granularity_y();
329 for(int xpos = 0; xpos < w; xpos += xdiff) {
330 const int xbegin = x + xpos;
331 const int xend = std::min<int>(x + w, xbegin + xdiff);
332
333 const GLfloat u1 = (x1*(x+w - xbegin) + x2*(xbegin - x))/w;
334 const GLfloat u2 = (x1*(x+w - xend) + x2*(xend - x))/w;
335 for(int ypos = 0; ypos < h; ypos += ydiff) {
336 const int ybegin = y + ypos;
337 const int yend = std::min<int>(y + h, ybegin + ydiff);
338
339 const GLfloat v1 = (y1*(y+h - ybegin) + y2*(ybegin - y))/h;
340 const GLfloat v2 = (y1*(y+h - yend) + y2*(yend - y))/h;
341
342 GLfloat points[8] = { xbegin, ybegin, xend, ybegin, xbegin, yend, xend, yend };
343 GLfloat uv[8] = { u1, v1, u2, v1, u1, v2, u2, v2 };
344
345 for(int n = 0; n != 4; ++n) {
346 distort.distort_point(&points[n*2], &points[n*2 + 1]);
347 }
348
349 glVertexPointer(2, GL_FLOAT, 0, points);
350 glTexCoordPointer(2, GL_FLOAT, 0, uv);
351 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
352 }
353 }
354 }
355
356 int blit_texture_translate_x = 0;
357 int blit_texture_translate_y = 0;
358
359 } // namespace
360
361 distortion_translation::distortion_translation()
362 : x_(0), y_(0)
363 {
364 }
365
366 distortion_translation::~distortion_translation()
367 {
368 if(x_ || y_) {
369 foreach(const raster_distortion* distort, distortions_) {
370 rect r = distort->area();
371 r = rect(r.x() + x_, r.y() + y_, r.w(), r.h());
372 const_cast<raster_distortion*>(distort)->set_area(r);
373 }
374 }
375 }
376
377 void distortion_translation::translate(int x, int y)
378 {
379 x_ += x;
380 y_ += y;
381
382 foreach(const raster_distortion* distort, distortions_) {
383 rect r = distort->area();
384 r = rect(r.x() - x, r.y() - y, r.w(), r.h());
385 const_cast<raster_distortion*>(distort)->set_area(r);
386 }
387 }
388
389 void blit_texture(const texture& tex, int x, int y, int w, int h, GLfloat rotate, GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
390 {
391 x &= preferences::xypos_draw_mask;
392 y &= preferences::xypos_draw_mask;
393
394 if(w < 0) {
395 std::swap(x1, x2);
396 w *= -1;
397 }
398
399 if(h < 0) {
400 std::swap(y1, y2);
401 h *= -1;
402 }
403
404 detect_draw(tex, x, y, w, h, x1, y1, x2, y2);
405
406 for(std::vector<const raster_distortion*>::const_iterator i = distortions_.begin(); i != distortions_.end() && rotate == 0.0; ++i) {
407 const raster_distortion& distort = **i;
408 if(rects_intersect(rect(x, y, w, h), distort.area())) {
409 blit_texture_with_distortion(tex, x, y, w, h, rotate, x1, y1, x2, y2, distort);
410 return;
411 }
412 }
413 blit_texture_internal(tex, x, y, w, h, rotate, x1, y1, x2, y2);
414 }
415
416 namespace {
417 const texture* blit_current_texture;
418 std::vector<GLfloat> blit_tcqueue;
419 std::vector<GLshort> blit_vqueue;
420 }
421
422 void queue_blit_texture(const texture& tex, int x, int y, int w, int h,
423 GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
424 {
425 x &= preferences::xypos_draw_mask;
426 y &= preferences::xypos_draw_mask;
427
428 if(&tex != blit_current_texture) {
429 flush_blit_texture();
430 blit_current_texture = &tex;
431 }
432
433 x1 = tex.translate_coord_x(x1);
434 y1 = tex.translate_coord_y(y1);
435 x2 = tex.translate_coord_x(x2);
436 y2 = tex.translate_coord_y(y2);
437
438 if(w < 0) {
439 std::swap(x1, x2);
440 w *= -1;
441 }
442
443 if(h < 0) {
444 std::swap(y1, y2);
445 h *= -1;
446 }
447
448 blit_tcqueue.push_back(x1);
449 blit_tcqueue.push_back(y1);
450 blit_tcqueue.push_back(x2);
451 blit_tcqueue.push_back(y1);
452 blit_tcqueue.push_back(x1);
453 blit_tcqueue.push_back(y2);
454 blit_tcqueue.push_back(x2);
455 blit_tcqueue.push_back(y2);
456
457 blit_vqueue.push_back(x);
458 blit_vqueue.push_back(y);
459 blit_vqueue.push_back(x + w);
460 blit_vqueue.push_back(y);
461 blit_vqueue.push_back(x);
462 blit_vqueue.push_back(y + h);
463 blit_vqueue.push_back(x + w);
464 blit_vqueue.push_back(y + h);
465 }
466
467 void queue_blit_texture(const texture& tex, int x, int y, int w, int h, int rotate,
468 GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
469 {
470 x &= preferences::xypos_draw_mask;
471 y &= preferences::xypos_draw_mask;
472
473 if(&tex != blit_current_texture) {
474 flush_blit_texture();
475 blit_current_texture = &tex;
476 }
477
478 x1 = tex.translate_coord_x(x1);
479 y1 = tex.translate_coord_y(y1);
480 x2 = tex.translate_coord_x(x2);
481 y2 = tex.translate_coord_y(y2);
482
483 if(w < 0) {
484 std::swap(x1, x2);
485 w *= -1;
486 }
487
488 if(h < 0) {
489 std::swap(y1, y2);
490 h *= -1;
491 }
492
493 blit_tcqueue.push_back(x1);
494 blit_tcqueue.push_back(y1);
495 blit_tcqueue.push_back(x2);
496 blit_tcqueue.push_back(y1);
497 blit_tcqueue.push_back(x1);
498 blit_tcqueue.push_back(y2);
499 blit_tcqueue.push_back(x2);
500 blit_tcqueue.push_back(y2);
501
502
503
504 blit_vqueue.push_back(x);
505 blit_vqueue.push_back(y);
506 blit_vqueue.push_back(x + w);
507 blit_vqueue.push_back(y);
508 blit_vqueue.push_back(x);
509 blit_vqueue.push_back(y + h);
510 blit_vqueue.push_back(x + w);
511 blit_vqueue.push_back(y + h);
512
513 rect r(x,y,w,h);
514 GLshort* varray = &blit_vqueue[blit_vqueue.size()-8];
515 rotate_rect(x+(w/2), y+(h/2), rotate, varray);
516
517 }
518
519 void flush_blit_texture()
520 {
521 if(!blit_current_texture) {
522 return;
523 }
524
525 blit_current_texture->set_as_current_texture();
526 glVertexPointer(2, GL_SHORT, 0, &blit_vqueue.front());
527 glTexCoordPointer(2, GL_FLOAT, 0, &blit_tcqueue.front());
528 glDrawArrays(GL_TRIANGLE_STRIP, 0, blit_tcqueue.size()/2);
529
530 blit_current_texture = NULL;
531 blit_tcqueue.clear();
532 blit_vqueue.clear();
533 }
534
535 void blit_queue::clear()
536 {
537 texture_ = 0;
538 vertex_.clear();
539 uv_.clear();
540 }
541
542 void blit_queue::do_blit() const
543 {
544 if(vertex_.empty()) {
545 return;
546 }
547
548 texture::set_current_texture(texture_);
549
550 glVertexPointer(2, GL_SHORT, 0, &vertex_.front());
551 glTexCoordPointer(2, GL_FLOAT, 0, &uv_.front());
552 glDrawArrays(GL_TRIANGLE_STRIP, 0, uv_.size()/2);
553 }
554
555 void blit_queue::do_blit_range(short begin, short end) const
556 {
557 if(vertex_.empty()) {
558 return;
559 }
560
561 texture::set_current_texture(texture_);
562
563 glVertexPointer(2, GL_SHORT, 0, &vertex_[begin]);
564 glTexCoordPointer(2, GL_FLOAT, 0, &uv_[begin]);
565 glDrawArrays(GL_TRIANGLE_STRIP, 0, (end - begin)/2);
566 }
567
568 bool blit_queue::merge(const blit_queue& q, short begin, short end)
569 {
570 if(vertex_.empty()) {
571 texture_ = q.texture_;
572 vertex_.insert(vertex_.end(), q.vertex_.begin()+begin, q.vertex_.begin()+end);
573 uv_.insert(uv_.end(), q.uv_.begin()+begin, q.uv_.begin()+end);
574 return true;
575 }
576
577 if(texture_ != q.texture_) {
578 return false;
579 }
580
581 repeat_last();
582 vertex_.push_back(q.vertex_[begin]);
583 vertex_.push_back(q.vertex_[begin+1]);
584 uv_.push_back(q.uv_[begin]);
585 uv_.push_back(q.uv_[begin+1]);
586
587 vertex_.insert(vertex_.end(), q.vertex_.begin()+begin, q.vertex_.begin()+end);
588 uv_.insert(uv_.end(), q.uv_.begin()+begin, q.uv_.begin()+end);
589
590 return true;
591 }
592
593 void set_draw_detection_rect(const rect& rect, char* buf)
594 {
595 draw_detection_rect new_rect = { rect, buf };
596 draw_detection_rects_.push_back(new_rect);
597 }
598
599 void clear_draw_detection_rect()
600 {
601 draw_detection_rects_.clear();
602 }
603
604 void add_raster_distortion(const raster_distortion* distortion)
605 {
606 //TODO: distortions currently disabled
607 // distortion->next_cycle();
608 // distortions_.push_back(distortion);
609 }
610
611 void remove_raster_distortion(const raster_distortion* distortion)
612 {
613 // distortions_.erase(std::remove(distortions_.begin(), distortions_.end(), distortion), distortions_.end());
614 }
615
616 void clear_raster_distortion()
617 {
618 distortions_.clear();
619 }
620
621 void draw_rect(const SDL_Rect& r, const SDL_Color& color,
622 unsigned char alpha)
623 {
624 glDisable(GL_TEXTURE_2D);
625 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
626 glColor4ub(color.r,color.g,color.b,alpha);
627 GLfloat varray[] = {
628 r.x, r.y,
629 r.x+r.w, r.y,
630 r.x, r.y+r.h,
631 r.x+r.w, r.y+r.h
632 };
633 glVertexPointer(2, GL_FLOAT, 0, varray);
634 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
635 //glRecti(r.x,r.y,r.x+r.w,r.y+r.h);
636 glColor4ub(255, 255, 255, 255);
637 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
638 glEnable(GL_TEXTURE_2D);
639 }
640
641 void draw_rect(const rect& r, const graphics::color& color)
642 {
643 glDisable(GL_TEXTURE_2D);
644 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
645 glColor4ub(color.r(),color.g(),color.b(),color.a());
646 GLfloat varray[] = {
647 r.x(), r.y(),
648 r.x()+r.w(), r.y(),
649 r.x(), r.y()+r.h(),
650 r.x()+r.w(), r.y()+r.h()
651 };
652 glVertexPointer(2, GL_FLOAT, 0, varray);
653 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
654 //glRecti(r.x(),r.y(),r.x()+r.w(),r.y()+r.h());
655 glColor4ub(255, 255, 255, 255);
656 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
657 glEnable(GL_TEXTURE_2D);
658 }
659
660
661 void draw_hollow_rect(const SDL_Rect& r, const SDL_Color& color,
662 unsigned char alpha) {
663
664 glDisable(GL_TEXTURE_2D);
665 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
666 glColor4ub(color.r, color.g, color.b, alpha);
667 GLfloat varray[] = {
668 r.x, r.y,
669 r.x + r.w, r.y,
670 r.x + r.w, r.y + r.h,
671 r.x, r.y + r.h
672 };
673 glVertexPointer(2, GL_FLOAT, 0, varray);
674 glDrawArrays(GL_LINE_LOOP, 0, sizeof(varray)/sizeof(GLfloat)/2);
675 glColor4ub(255, 255, 255, 255);
676 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
677 glEnable(GL_TEXTURE_2D);
678 }
679
680 void draw_circle(int x, int y, int radius)
681 {
682 glDisable(GL_TEXTURE_2D);
683 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
684 static std::vector<GLfloat> varray;
685 varray.clear();
686 varray.push_back(x);
687 varray.push_back(y);
688 for(double angle = 0; angle < 3.1459*2.0; angle += 0.1) {
689 const double xpos = x + radius*cos(angle);
690 const double ypos = y + radius*sin(angle);
691 varray.push_back(xpos);
692 varray.push_back(ypos);
693 }
694
695 //repeat the first coordinate to complete the circle.
696 varray.push_back(varray[2]);
697 varray.push_back(varray[3]);
698
699 glVertexPointer(2, GL_FLOAT, 0, &varray.front());
700 glDrawArrays(GL_TRIANGLE_FAN, 0, varray.size()/2);
701 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
702 glEnable(GL_TEXTURE_2D);
703 }
704
705 void coords_to_screen(GLdouble sx, GLdouble sy, GLdouble sz,
706 GLdouble* dx, GLdouble* dy, GLdouble* dz) {
707 GLdouble model[16], proj[16];
708 GLint view[4];
709
710 #ifndef SDL_VIDEO_OPENGL_ES
711 glGetDoublev(GL_MODELVIEW_MATRIX, model);
712 glGetDoublev(GL_PROJECTION_MATRIX, proj);
713 glGetIntegerv(GL_VIEWPORT, view);
714 #endif
715
716 gluProject(sx, sy, sz, model, proj, view, dx, dy, dz);
717 }
718
719 void push_clip(const SDL_Rect& r)
720 {
721 const bool was_enabled_clip = glIsEnabled(GL_SCISSOR_TEST);
722 bool should_enable_clip = true;
723
724 GLdouble x, y ,z;
725 coords_to_screen(0, graphics::screen_height(), 0, &x, &y, &z);
726
727 SDL_Rect s = r;
728 s.x += static_cast<Sint16>(x);
729 s.y -= static_cast<Sint16>(y);
730
731 if(s.x == 0 && s.y == 0 && s.w == screen_width() && s.h == screen_height()) {
732 should_enable_clip = false;
733 }
734
735
736 boost::shared_array<GLint> box(new GLint[4]);
737
738 if(was_enabled_clip) {
739 glGetIntegerv(GL_SCISSOR_BOX, box.get());
740 } else {
741 box[0] = 0;
742 box[1] = 0;
743 box[2] = screen_width();
744 box[3] = screen_height();
745 }
746 clip_rectangles.push_back(box);
747
748 if(should_enable_clip) {
749 if(!was_enabled_clip) {
750 glEnable(GL_SCISSOR_TEST);
751 }
752 glScissor(s.x, screen_height() - (s.y + s.h), s.w, s.h);
753 } else if(was_enabled_clip) {
754 glDisable(GL_SCISSOR_TEST);
755 }
756 }
757
758 void pop_clip() {
759 const bool was_enabled_clip = glIsEnabled(GL_SCISSOR_TEST);
760 bool should_enable_clip = false;
761 boost::shared_array<GLint> box;
762
763 if(!clip_rectangles.empty()) {
764 box = *clip_rectangles.rbegin();
765 clip_rectangles.pop_back();
766
767 if(box[0] != 0 || box[1] != 0 || box[2] != screen_width() || box[3] != screen_height()) {
768 should_enable_clip = true;
769 }
770 }
771
772 if(should_enable_clip) {
773 if(!was_enabled_clip) {
774 glEnable(GL_SCISSOR_TEST);
775 }
776 glScissor(box[0], box[1], box[2], box[3]);
777 } else if(was_enabled_clip) {
778 glDisable(GL_SCISSOR_TEST);
779 }
780 }
781
782 namespace {
783 int zoom_level = 1;
784 }
785
786 int screen_width()
787 {
788 return preferences::virtual_screen_width()*zoom_level;
789 /*
790 SDL_Surface* surf = SDL_GetVideoSurface();
791 if(surf) {
792 return SDL_GetVideoSurface()->w;
793 } else {
794 return 1024;
795 }*/
796 }
797
798 int screen_height()
799 {
800 return preferences::virtual_screen_height()*zoom_level;
801 /*
802 SDL_Surface* surf = SDL_GetVideoSurface();
803 if(surf) {
804 return SDL_GetVideoSurface()->h;
805 } else {
806 return 768;
807 }*/
808 }
809
810 void zoom_in()
811 {
812 --zoom_level;
813 if(zoom_level < 1) {
814 zoom_level = 1;
815 }
816 }
817
818 void zoom_out()
819 {
820 ++zoom_level;
821 if(zoom_level > 5) {
822 zoom_level = 5;
823 }
824 }
825
826 void zoom_default()
827 {
828 zoom_level = 1;
829 }
830
831 const SDL_Color& color_black()
832 {
833 static SDL_Color res = {0,0,0,0};
834 return res;
835 }
836
837 const SDL_Color& color_white()
838 {
839 static SDL_Color res = {0xFF,0xFF,0xFF,0};
840 return res;
841 }
842
843 const SDL_Color& color_red()
844 {
845 static SDL_Color res = {0xFF,0,0,0};
846 return res;
847 }
848
849 const SDL_Color& color_green()
850 {
851 static SDL_Color res = {0,0xFF,0,0};
852 return res;
853 }
854
855 const SDL_Color& color_blue()
856 {
857 static SDL_Color res = {0,0,0xFF,0};
858 return res;
859 }
860
861 const SDL_Color& color_yellow()
862 {
863 static SDL_Color res = {0xFF,0xFF,0,0};
864 return res;
865 }
866
867 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef RASTER_HPP_INCLUDED
13 #define RASTER_HPP_INCLUDED
14
15 #include <vector>
16
17 #include "SDL.h"
18
19 #include "color_utils.hpp"
20 #include "geometry.hpp"
21 #include "texture.hpp"
22
23 namespace preferences
24 {
25 extern int xypos_draw_mask;
26 }
27
28 namespace graphics
29 {
30
31 std::vector<GLfloat>& global_vertex_array();
32 std::vector<GLfloat>& global_texcoords_array();
33 std::vector<GLbyte>& global_vertex_color_array();
34
35 void prepare_raster();
36 void blit_texture(const texture& tex, int x=0, int y=0, GLfloat rotate=0.0);
37
38 //Function to blit a texture to the screen. Parameters:
39 //x, y: target on-screen location.
40 //w, h: dimensions of the on-screen area that will be filled by the
41 //blit.
42 //rotate: the number of degrees to rotate by when blitting
43 //x1, y1, x2, y2: the area of the texture to blit onto the screen. The
44 //defaults are to blit the entire texture. Note that these values can be
45 // < 0.0 or > 1.0 and the texture will wrap, but *only* if the texture's
46 //dimensions are powers of two. Otherwise they must be in the range [0,1]
47 void blit_texture(const texture& tex, int x, int y, int w, int h, GLfloat rotate=0.0, GLfloat x1=0.0, GLfloat y1=0.0, GLfloat x2=1.0, GLfloat y2=1.0);
48
49 void queue_blit_texture(const texture& tex, int x, int y, int w, int h,
50 GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2);
51 void queue_blit_texture(const texture& tex, int x, int y, int w, int h, int rotate,
52 GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2);
53 void flush_blit_texture();
54
55 class blit_queue
56 {
57 public:
58 blit_queue() : texture_(0)
59 {}
60 void set_texture(GLuint id) {
61 texture_ = id;
62 }
63 GLuint texture() const { return texture_; }
64 void clear();
65 bool empty() const { return vertex_.empty(); }
66 size_t size() const { return vertex_.size(); }
67 void do_blit() const;
68 void do_blit_range(short begin, short end) const;
69 void add(GLshort x, GLshort y, GLfloat u, GLfloat v) {
70 vertex_.push_back(x&preferences::xypos_draw_mask);
71 vertex_.push_back(y&preferences::xypos_draw_mask);
72 uv_.push_back(u);
73 uv_.push_back(v);
74 }
75
76 void repeat_last() {
77 if(!vertex_.empty()) {
78 vertex_.push_back(vertex_[vertex_.size()-2]);
79 vertex_.push_back(vertex_[vertex_.size()-2]);
80 uv_.push_back(uv_[uv_.size()-2]);
81 uv_.push_back(uv_[uv_.size()-2]);
82 }
83 }
84
85 short position() const { return vertex_.size(); }
86
87 bool merge(const blit_queue& q, short begin, short end);
88
89 void reserve(size_t n) {
90 vertex_.reserve(n);
91 uv_.reserve(n);
92 }
93 private:
94 GLuint texture_;
95 std::vector<GLshort> vertex_;
96 std::vector<GLfloat> uv_;
97 };
98
99 //function which sets a rectangle where we want to detect if pixels are written.
100 //buf must point to a buffer with a size of rect.w*rect.h. Whenever a pixel
101 //is blitted within rect, the corresponding pixel in buf will be set. buf
102 //must remain valid until another call to set_draw_detection_rect() or a
103 //call to clear_draw_detection_rect().
104 void set_draw_detection_rect(const rect& rect, char* buf);
105 void clear_draw_detection_rect();
106
107 class raster_distortion;
108 void add_raster_distortion(const raster_distortion* distortion);
109 void remove_raster_distortion(const raster_distortion* distortion);
110 void clear_raster_distortion();
111
112 //a class that translates distortions within its scope.
113 class distortion_translation {
114 int x_, y_;
115 public:
116 distortion_translation();
117 ~distortion_translation();
118 void translate(int x, int y);
119 };
120
121 void draw_rect(const SDL_Rect& rect, const SDL_Color& color,
122 unsigned char alpha=0xFF);
123 void draw_rect(const rect& rect, const graphics::color& color);
124 void draw_hollow_rect(const SDL_Rect& rect, const SDL_Color& color,
125 unsigned char alpha=0xFF);
126 void draw_circle(int x, int y, int radius);
127 int screen_width();
128 int screen_height();
129 void zoom_in();
130 void zoom_out();
131 void zoom_default();
132
133 void coords_to_screen(GLfloat sx, GLfloat sy, GLfloat sz,
134 GLfloat* dx, GLfloat* dy, GLfloat* dz);
135 void push_clip(const SDL_Rect& rect);
136 void pop_clip();
137
138 const SDL_Color& color_black();
139 const SDL_Color& color_white();
140 const SDL_Color& color_red();
141 const SDL_Color& color_green();
142 const SDL_Color& color_blue();
143 const SDL_Color& color_yellow();
144
145 }
146
147 #endif
0 #include <math.h>
1 #include <stdlib.h>
2
3 #include "GL/gl.h"
4
5 #include "raster_distortion.hpp"
6
7 namespace graphics {
8
9 raster_distortion::raster_distortion(const rect& r) : area_(r), cycle_(0)
10 {}
11
12 raster_distortion::~raster_distortion()
13 {}
14
15 water_distortion::water_distortion(int offset, const rect& r) : raster_distortion(r), offset_(offset)
16 {}
17
18 void water_distortion::distort_point(GLfloat* x, GLfloat* y) const
19 {
20 *x = *x + 8.0*sin((offset_ + *x)/GLfloat(20.0)) - 5.0*sin((offset_/4 + *x * 3)/GLfloat(20.0));
21 }
22
23 int water_distortion::granularity_x() const
24 {
25 return 20;
26 }
27
28 int water_distortion::granularity_y() const
29 {
30 return 10000;
31 }
32
33 variant water_distortion::get_value(const std::string& key) const
34 {
35 return variant();
36 }
37
38 radial_distortion::radial_distortion(int x, int y, int radius, int intensity)
39 : raster_distortion(rect(x - radius, y - radius, radius*2, radius*2)),
40 x_(x), y_(y), radius_(radius), intensity_(intensity)
41 {
42 }
43
44 void radial_distortion::distort_point(GLfloat* x, GLfloat* y) const
45 {
46 if(*x == x_ && *y == y_) {
47 return;
48 }
49
50 const GLfloat vector_x = *x - x_;
51 const GLfloat vector_y = *y - y_;
52 const GLfloat distance = sqrt(vector_x*vector_x + vector_y*vector_y);
53 if(distance > radius_) {
54 return;
55 }
56
57 const GLfloat unit_vector_x = vector_x/distance;
58 const GLfloat unit_vector_y = vector_y/distance;
59
60 const GLfloat distort = sin(distance + cycle()*0.2)*intensity_*((radius_ - distance)/radius_);
61 *x += unit_vector_x*distort;
62 *y += unit_vector_y*distort;
63 }
64
65 int radial_distortion::granularity_x() const
66 {
67 return 10;
68 }
69
70 int radial_distortion::granularity_y() const
71 {
72 return 10;
73 }
74
75 variant radial_distortion::get_value(const std::string& key) const
76 {
77 if(key == "radius") {
78 return variant(radius_*1000);
79 } else {
80 return variant();
81 }
82 }
83
84 void radial_distortion::set_value(const std::string& key, const variant& value)
85 {
86 if(key == "radius") {
87 radius_ = value.as_int()/1000.0;
88 std::cerr << "set radius: " << radius_ << "\n";
89 set_area(rect(x_ - radius_, y_ - radius_, radius_*2, radius_*2));
90 }
91 }
92
93 }
0 #ifndef RASTER_DISTORTION_HPP_INCLUDED
1 #define RASTER_DISTORTION_HPP_INCLUDED
2
3 #include "GL/gl.h"
4
5 #include "formula_callable.hpp"
6 #include "geometry.hpp"
7
8 namespace graphics {
9
10 //Class which represents distortions which affect blitting operations.
11 //This is useful to generate 'waves' such as for water, heat, etc.
12 class raster_distortion : public game_logic::formula_callable
13 {
14 public:
15 explicit raster_distortion(const rect& r);
16 virtual ~raster_distortion();
17
18 //function which map undistorted co-ordinates into their distorted equivalents.
19 virtual void distort_point(GLfloat* x, GLfloat* y) const = 0;
20
21 //functions which determine the granularity of the distortion on each axis.
22 //This represents the size of the edges of the rectangles that textures will
23 //be divided into. The lower the value, the finer the granularity, and the
24 //more expensive the operations.
25 virtual int granularity_x() const = 0;
26 virtual int granularity_y() const = 0;
27
28 //the area that the raster distortion takes effect in.
29 rect area() const { return area_; }
30 void set_area(const rect& area) { area_ = area; }
31
32 int cycle() const { return cycle_; }
33 void next_cycle() const { ++cycle_; }
34 void set_cycle(int n) { cycle_ = n; }
35 private:
36 virtual variant get_value(const std::string& key) const { return variant(); }
37 rect area_;
38 mutable int cycle_;
39 };
40
41 typedef boost::intrusive_ptr<raster_distortion> raster_distortion_ptr;
42 typedef boost::intrusive_ptr<const raster_distortion> const_raster_distortion_ptr;
43
44 class water_distortion : public raster_distortion
45 {
46 public:
47 water_distortion(int offset, const rect& r);
48
49 void distort_point(GLfloat* x, GLfloat* y) const;
50
51 int granularity_x() const;
52 int granularity_y() const;
53 private:
54 virtual variant get_value(const std::string& key) const;
55 int offset_;
56 };
57
58 class radial_distortion : public raster_distortion
59 {
60 public:
61 radial_distortion(int x, int y, int radius, int intensity=5);
62
63 void distort_point(GLfloat* x, GLfloat* y) const;
64
65 int granularity_x() const;
66 int granularity_y() const;
67 private:
68 int x_, y_;
69 GLfloat radius_;
70 GLfloat intensity_;
71
72 virtual variant get_value(const std::string& key) const;
73 virtual void set_value(const std::string& key, const variant& value);
74 };
75
76 }
77
78 #endif
0 #include <GL/gl.h>
1
2 #include "rectangle_rotator.hpp"
3 #include <math.h>
4 #ifndef M_PI
5 #define M_PI 3.14159265358979323846
6 #endif
7 #include "unit_test.hpp"
8
9 void rotate_rect(GLshort center_x, GLshort center_y, GLshort rotation, GLshort* rect_vertexes){
10
11 point p;
12
13 float rotate_radians = (rotation * M_PI)/180;
14
15 //rect r(rect_vertexes[0],rect_vertexes[1],rect_vertexes[4]-rect_vertexes[0],rect_vertexes[5]-rect_vertexes[1]);
16
17 p = rotate_point_around_origin_with_offset( rect_vertexes[0], rect_vertexes[1], rotate_radians, center_x, center_y );
18 rect_vertexes[0] = p.x;
19 rect_vertexes[1] = p.y;
20
21 p = rotate_point_around_origin_with_offset( rect_vertexes[2], rect_vertexes[3], rotate_radians, center_x, center_y );
22 rect_vertexes[2] = p.x;
23 rect_vertexes[3] = p.y;
24
25 p = rotate_point_around_origin_with_offset( rect_vertexes[4], rect_vertexes[5], rotate_radians, center_x, center_y );
26 rect_vertexes[4] = p.x;
27 rect_vertexes[5] = p.y;
28
29 p = rotate_point_around_origin_with_offset( rect_vertexes[6], rect_vertexes[7], rotate_radians, center_x, center_y );
30 rect_vertexes[6] = p.x;
31 rect_vertexes[7] = p.y;
32
33 }
34
35
36 void rotate_rect(const rect& r, GLfloat angle, GLshort* output){
37
38 point offset;
39 offset.x = r.x() + r.w()/2;
40 offset.y = r.y() + r.h()/2;
41
42 point p;
43
44 p = rotate_point_around_origin_with_offset( r.x(), r.y(), angle, offset.x, offset.y );
45 output[0] = p.x;
46 output[1] = p.y;
47
48 p = rotate_point_around_origin_with_offset( r.x2(), r.y(), angle, offset.x, offset.y );
49 output[2] = p.x;
50 output[3] = p.y;
51
52 p = rotate_point_around_origin_with_offset( r.x2(), r.y2(), angle, offset.x, offset.y );
53 output[4] = p.x;
54 output[5] = p.y;
55
56 p = rotate_point_around_origin_with_offset( r.x(), r.y2(), angle, offset.x, offset.y );
57 output[6] = p.x;
58 output[7] = p.y;
59
60 }
61
62 point rotate_point_around_origin_with_offset(int x1, int y1, float alpha, int u1, int v1){
63
64 point beta = rotate_point_around_origin(x1 - u1, y1 - v1, alpha);
65
66 beta.x += u1;
67 beta.y += v1;
68
69 return beta;
70 }
71
72 point rotate_point_around_origin(int x1, int y1, float alpha){
73
74 point beta;
75
76 /* //we actually don't need the initial theta and radius. This is why:
77 x2 = R * (cos(theta) * cos(alpha) + sin(theta) * sin(alpha))
78 y2 = R * (sin(theta) * cos(alpha) + cos(theta) * sin(alpha));
79 but
80 R * (cos(theta)) = x1
81 R * (sin(theta)) = x2
82 this collapses the above to: */
83
84 beta.x = x1 * cos(alpha) - y1 * sin(alpha);
85 beta.y = y1 * cos(alpha) + x1 * sin(alpha);
86
87 return beta;
88 }
89
90 /*UNIT_TEST(rotate_test) {
91 std::cerr << "rotating_a_point \n";
92 std::cerr << rotate_point_around_origin( 1000, 1000, (M_PI/2)).to_string() << "\n"; //Should be -1000,1000
93 std::cerr << rotate_point_around_origin_with_offset( 11000, 1000, (M_PI/2), 10000,0).to_string() << "\n"; //Should be 9000,1000
94
95 GLshort myOutputData[8];
96 rect r(10, 10, 20, 30);
97 rotate_rect(r, (M_PI*2), myOutputData);
98
99 std::cerr << "Outputting point list \n";
100 for(int i=0;i<8;++i){
101 std::cerr << myOutputData[i] << " ";
102 if(i%2){ std::cerr << "\n";}
103 }
104 }*/
105
106
107 BENCHMARK(rect_rotation) {
108 rect r(10, 10, 20, 30);
109 GLshort output[8];
110 BENCHMARK_LOOP {
111 rotate_rect(r, 75, output);
112 }
113 }
0 #ifndef RECTANGLE_ROTATOR_HPP_INCLUDED
1 #define RECTANGLE_ROTATOR_HPP_INCLUDED
2
3 #include "geometry.hpp"
4
5 void rotate_rect(GLshort center_x, GLshort center_y, GLshort rotation, GLshort* rect_vertexes);
6 void rotate_rect(const rect& r, GLfloat angle, GLshort* output);
7 point rotate_point_around_origin_with_offset(int x1, int y1, float alpha, int u1, int v1);
8 point rotate_point_around_origin(int x1, int y1, float alpha);
9
10 #endif
0 #ifndef REFERENCE_COUNTED_OBJECT_HPP_INCLUDED
1 #define REFERENCE_COUNTED_OBJECT_HPP_INCLUDED
2
3 #include <assert.h>
4
5 #include "boost/intrusive_ptr.hpp"
6
7 class reference_counted_object
8 {
9 public:
10 reference_counted_object() : count_(0) {}
11 reference_counted_object(const reference_counted_object& /*obj*/) : count_(0) {}
12 reference_counted_object& operator=(const reference_counted_object& /*obj*/) {
13 return *this;
14 }
15 virtual ~reference_counted_object() { }
16
17 void add_ref() const { ++count_; }
18 void dec_ref() const { if(--count_ == 0) { delete this; } }
19
20 int refcount() const { return count_; }
21
22 protected:
23 void turn_reference_counting_off() { count_ = 1000000; }
24 private:
25 mutable int count_;
26 };
27
28 inline void intrusive_ptr_add_ref(const reference_counted_object* obj) {
29 obj->add_ref();
30 }
31
32 inline void intrusive_ptr_release(const reference_counted_object* obj) {
33 obj->dec_ref();
34 }
35
36 typedef boost::intrusive_ptr<reference_counted_object> object_ptr;
37 typedef boost::intrusive_ptr<const reference_counted_object> const_object_ptr;
38
39 #endif
0 /* $Id: scoped_resource.hpp 7396 2005-07-02 21:37:20Z ott $ */
1 /*
2 Copyright (C) 2003 by David White <davidnwhite@verizon.net>
3 Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef SCOPED_RESOURCE_H_INCLUDED
13 #define SCOPED_RESOURCE_H_INCLUDED
14
15 #include <cstdio> //for FILE
16
17 namespace util
18 {
19 /**
20 * A class template, scoped_resource, designed to
21 * implement the Resource Acquisition Is Initialization (RAII) approach
22 * to resource management. scoped_resource is designed to be used when
23 * a resource is initialized at the beginning or middle of a scope,
24 * and released at the end of the scope. The template argument
25 * ReleasePolicy is a functor which takes an argument of the
26 * type of the resource, and releases it.
27 *
28 * Usage example, for working with files:
29 *
30 * @code
31 * struct close_file { void operator()(int fd) const {close(fd);} };
32 * ...
33 * {
34 * const scoped_resource<int,close_file> file(open("file.txt",O_RDONLY));
35 * read(file, buf, 1000);
36 * } // file is automatically closed here
37 * @endcode
38 *
39 * Note that scoped_resource has an explicit constructor, and prohibits
40 * copy-construction, and thus the initialization syntax, rather than
41 * the assignment syntax must be used when initializing.
42 *
43 * i.e. using scoped_resource<int,close_file> file = open("file.txt",O_RDONLY);
44 * in the above example is illegal.
45 *
46 */
47 template<typename T,typename ReleasePolicy>
48 class scoped_resource
49 {
50 T resource;
51
52 //prohibited operations
53 scoped_resource(const scoped_resource&);
54 scoped_resource& operator=(const scoped_resource&);
55 public:
56 typedef T resource_type;
57 typedef ReleasePolicy release_type;
58
59 /**
60 * Constructor
61 *
62 * @ param res This is the resource to be managed
63 */
64 scoped_resource(resource_type res = resource_type())
65 : resource(res) {}
66
67 /**
68 * The destructor is the main point in this class. It takes care of proper
69 * deletion of the resource, using the provided release policy.
70 */
71 ~scoped_resource()
72 {
73 release_type()(resource);
74 }
75
76 /**
77 * This operator makes sure you can access and use the scoped_resource
78 * just like you were using the resource itself.
79 *
80 * @ret the underlying resource
81 */
82 operator resource_type() const { return resource; }
83
84 /**
85 * This function provides explicit access to the resource. Its behaviour
86 * is identical to operator resource_type()
87 *
88 * @ret the underlying resource
89 */
90 resource_type get() const { return resource; }
91
92 /**
93 * This function provides convenient direct access to the -> operator
94 * if the underlying resource is a pointer. Only call this function
95 * if resource_type is a pointer type.
96 */
97 resource_type operator->() const { return resource; }
98
99 void assign(const resource_type& o) {
100 release_type()(resource);
101 resource = o;
102 }
103 };
104
105 /**
106 * A helper policy for scoped_ptr.
107 * It will call the delete operator on a pointer, and assign the pointer to 0
108 */
109 struct delete_item {
110 template<typename T>
111 void operator()(T*& p) const { delete p; p = 0; }
112 };
113 /**
114 * A helper policy for scoped_array.
115 * It will call the delete[] operator on a pointer, and assign the pointer to 0
116 */
117 struct delete_array {
118 template<typename T>
119 void operator()(T*& p) const { delete [] p; p = 0; }
120 };
121
122 /**
123 * A class which implements an approximation of
124 * template<typename T>
125 * typedef scoped_resource<T*,delete_item> scoped_ptr<T>;
126 *
127 * It is a convenient synonym for a common usage of @ref scoped_resource.
128 * See scoped_resource for more details on how this class behaves.
129 *
130 * Usage example:
131 * @code
132 * {
133 * const scoped_ptr<Object> ptr(new Object);
134 * ...use ptr as you would a normal Object*...
135 * } // ptr is automatically deleted here
136 * @endcode
137 *
138 * NOTE: use this class only to manage a single object, *never* an array.
139 * Use scoped_array to manage arrays. This distinction is because you
140 * may call delete only on objects allocated with new, delete[] only
141 * on objects allocated with new[].
142 */
143 template<typename T>
144 struct scoped_ptr : public scoped_resource<T*,delete_item>
145 {
146 explicit scoped_ptr(T* p) : scoped_resource<T*,delete_item>(p) {}
147 };
148
149 /**
150 * This class has identical behaviour to @ref scoped_ptr, except it manages
151 * heap-allocated arrays instead of heap-allocated single objects
152 *
153 * Usage example:
154 * @code
155 * {
156 * const scoped_array<char> ptr(new char[n]);
157 * ...use ptr as you would a normal char*...
158 * } // ptr is automatically deleted here
159 * @endcode
160 *
161 */
162 template<typename T>
163 struct scoped_array : public scoped_resource<T*,delete_array>
164 {
165 explicit scoped_array(T* p) : scoped_resource<T*,delete_array>(p) {}
166 };
167
168 /**
169 * This class specializes the scoped_resource to implement scoped FILEs. Not
170 * sure this is the best place to place such an utility, though.
171 */
172 struct close_FILE
173 {
174 void operator()(FILE* f) const { if(f != NULL) { fclose(f); } }
175 };
176 typedef scoped_resource<FILE*,close_FILE> scoped_FILE;
177
178 }
179
180 #endif
0 #include <assert.h>
1
2 #include <boost/array.hpp>
3 #include <boost/asio.hpp>
4 #include <boost/bind.hpp>
5 #include <boost/regex.hpp>
6 #include <boost/shared_ptr.hpp>
7 #include <boost/thread.hpp>
8
9 #include <string>
10 #include <vector>
11
12 #include <iostream>
13 #include <inttypes.h>
14
15 #include "foreach.hpp"
16
17 using boost::asio::ip::tcp;
18 using boost::asio::ip::udp;
19
20 typedef boost::shared_ptr<tcp::socket> socket_ptr;
21 typedef boost::shared_ptr<boost::array<char, 1024> > buffer_ptr;
22
23 class server
24 {
25 public:
26 explicit server(boost::asio::io_service& io_service)
27 : acceptor_(io_service, tcp::endpoint(tcp::v4(), 17000)),
28 next_id_(0), slots_(0),
29 udp_socket_(io_service, udp::endpoint(udp::v4(), 17001))
30 {
31 start_accept();
32 start_udp_receive();
33 }
34
35 private:
36 void start_accept()
37 {
38 socket_ptr socket(new tcp::socket(acceptor_.io_service()));
39 acceptor_.async_accept(*socket, boost::bind(&server::handle_accept, this, socket, boost::asio::placeholders::error));
40 }
41
42 void handle_accept(socket_ptr socket, const boost::system::error_code& error)
43 {
44 if(!error) {
45 SocketInfo info;
46 info.id = next_id_++;
47 info.slot = 0;
48 while(slots_&(1 << info.slot)) {
49 ++info.slot;
50 }
51
52 slots_ |= (1 << info.slot);
53
54 boost::array<char, 5> buf;
55 memcpy(&buf[0], &info.id, 4);
56 buf[4] = info.slot;
57
58 boost::asio::async_write(*socket, boost::asio::buffer(buf),
59 boost::bind(&server::handle_send, this, socket, _1, _2));
60
61 sockets_info_[socket] = info;
62 id_to_socket_[info.id] = socket;
63
64 sockets_.push_back(socket);
65 start_receive(socket);
66 start_accept();
67 } else {
68 std::cerr << "ERROR IN ACCEPT!\n";
69 }
70 }
71
72 void handle_send(socket_ptr socket, const boost::system::error_code& e, size_t nbytes)
73 {
74 if(e) {
75 disconnect(socket);
76 }
77 }
78
79 void start_receive(socket_ptr socket)
80 {
81 buffer_ptr buf(new boost::array<char, 1024>);
82 socket->async_read_some(boost::asio::buffer(*buf), boost::bind(&server::handle_receive, this, socket, buf, _1, _2));
83 }
84
85 void handle_receive(socket_ptr socket, buffer_ptr buf, const boost::system::error_code& e, size_t nbytes)
86 {
87 if(!e) {
88 std::string str(buf->data(), buf->data() + nbytes);
89 std::cerr << "RECEIVE {{{" << str << "}}}\n";
90
91 static boost::regex ready("READY/(.+)/(\\d+)");
92
93 boost::cmatch match;
94 if(boost::regex_match(str.c_str(), match, ready)) {
95 std::string level_id(match[1].first, match[1].second);
96
97 GameInfoPtr& game = games_[level_id];
98 if(!game) {
99 game.reset(new GameInfo);
100 }
101
102 SocketInfo& info = sockets_info_[socket];
103 if(info.game) {
104 //if the player is already in a game, remove them from it.
105 std::vector<socket_ptr>& v = info.game->players;
106 v.erase(std::remove(v.begin(), v.end(), socket), v.end());
107 }
108
109 info.game = game;
110
111 game->players.push_back(socket);
112
113 const int nplayers = atoi(match[2].first);
114 if(game->players.size() >= nplayers) {
115 std::string msg = "START";
116 foreach(socket_ptr socket, game->players) {
117 boost::asio::async_write(*socket, boost::asio::buffer(msg),
118 boost::bind(&server::handle_send, this, socket, _1, _2));
119 }
120
121 game.reset();
122 }
123 }
124
125 start_receive(socket);
126 } else {
127 disconnect(socket);
128 }
129 }
130
131 void disconnect(socket_ptr socket) {
132
133 SocketInfo& info = sockets_info_[socket];
134 if(info.game) {
135 std::vector<socket_ptr>& v = info.game->players;
136 v.erase(std::remove(v.begin(), v.end(), socket), v.end());
137 }
138
139 std::cerr << "CLOSING SOCKET: ";
140 socket->close();
141 id_to_socket_.erase(sockets_info_[socket].id);
142 sockets_.erase(std::remove(sockets_.begin(), sockets_.end(), socket), sockets_.end());
143
144 std::cerr << sockets_info_[socket].id << " " << slots_ << " -> ";
145 slots_ &= ~(1LL << sockets_info_[socket].slot);
146 std::cerr << slots_ << "\n";
147 sockets_info_.erase(socket);
148 }
149
150 tcp::acceptor acceptor_;
151
152 std::vector<socket_ptr> sockets_;
153
154 typedef boost::shared_ptr<udp::endpoint> udp_endpoint_ptr;
155
156 struct GameInfo;
157 typedef boost::shared_ptr<GameInfo> GameInfoPtr;
158
159 struct SocketInfo {
160 uint32_t id;
161 uint8_t slot;
162 udp_endpoint_ptr udp_endpoint;
163 GameInfoPtr game;
164 };
165
166 std::map<socket_ptr, SocketInfo> sockets_info_;
167 std::map<uint32_t, socket_ptr> id_to_socket_;
168
169 struct GameInfo {
170 GameInfo() {}
171 std::vector<socket_ptr> players;
172 };
173
174 std::map<std::string, GameInfoPtr> games_;
175
176 uint32_t next_id_;
177 uint64_t slots_;
178
179 void start_udp_receive() {
180 udp_endpoint_ptr endpoint(new udp::endpoint);
181 udp_socket_.async_receive_from(
182 boost::asio::buffer(udp_buf_), *endpoint,
183 boost::bind(&server::handle_udp_receive, this, endpoint, _1, _2));
184 }
185
186 void handle_udp_receive(udp_endpoint_ptr endpoint, const boost::system::error_code& error, size_t len)
187 {
188 fprintf(stderr, "RECEIVED UDP PACKET: %d\n", len);
189 if(len >= 4) {
190 uint32_t id;
191 memcpy(&id, &udp_buf_[0], 4);
192 std::map<uint32_t, socket_ptr>::iterator socket_it = id_to_socket_.find(id);
193 if(socket_it != id_to_socket_.end()) {
194 assert(sockets_info_.count(socket_it->second));
195 sockets_info_[socket_it->second].udp_endpoint = endpoint;
196 GameInfoPtr game = sockets_info_[socket_it->second].game;
197
198 for(std::map<socket_ptr, SocketInfo>::iterator i = sockets_info_.begin(); i != sockets_info_.end(); ++i) {
199 if(i->first != socket_it->second && i->second.game == game && i->second.udp_endpoint) {
200 udp_socket_.async_send_to(boost::asio::buffer(&udp_buf_[0], len), *i->second.udp_endpoint,
201 boost::bind(&server::handle_udp_send, this, i->second.udp_endpoint, _1, _2));
202 }
203 }
204 }
205 }
206 start_udp_receive();
207 }
208
209 void handle_udp_send(udp_endpoint_ptr endpoint, const boost::system::error_code& error, size_t len)
210 {
211 if(error) {
212 fprintf(stderr, "ERROR IN UDP SEND!\n");
213 } else {
214 fprintf(stderr, "UDP: SENT %d BYTES\n", (int)len);
215 }
216 }
217
218 udp::socket udp_socket_;
219 boost::array<char, 1024> udp_buf_;
220 };
221
222 int main(int argc, char** argv)
223 {
224 boost::asio::io_service io_service;
225
226 server srv(io_service);
227 io_service.run();
228 }
0 #include "settings_dialog.hpp"
1
2 #include "gui_section.hpp"
3 #include "iphone_controls.hpp"
4 #include "preferences.hpp"
5 #include "raster.hpp"
6
7 namespace
8 {
9 const int sw = graphics::screen_width();
10 const int sh = graphics::screen_height();
11 const int padding = 20;
12 }
13
14 void settings_dialog::draw () const
15 {
16 const const_gui_section_ptr menu_button = menu_button_state_ ? menu_button_down_ : menu_button_normal_;
17 menu_button->blit(sw - menu_button->width() - padding, padding);
18 }
19
20 bool settings_dialog::handle_event (const SDL_Event& event)
21 {
22 if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP)
23 {
24 const int menu_button_x = sw - menu_button_normal_->width() - padding;
25 const int menu_button_y = padding;
26 int x = event.type == SDL_MOUSEMOTION ? event.motion.x : event.button.x;
27 int y = event.type == SDL_MOUSEMOTION ? event.motion.y : event.button.y;
28 translate_mouse_coords(&x, &y);
29 bool hittest = (x > (menu_button_x-padding) && y >= 0
30 && x < menu_button_x+menu_button_normal_->width()*2 && y < menu_button_y+menu_button_normal_->height()+padding);
31 if (hittest && (event.type == SDL_MOUSEBUTTONDOWN || (event.type == SDL_MOUSEMOTION && event.motion.state)))
32 {
33 menu_button_state_ = true;
34 } else {
35 menu_button_state_ = false;
36 }
37 if (hittest && event.type == SDL_MOUSEBUTTONUP)
38 {
39 show_window_ = true;
40 }
41 }
42 return show_window_;
43 }
44
45 void settings_dialog::reset ()
46 {
47 show_window_ = false;
48 menu_button_state_ = false;
49 }
50
51 settings_dialog::settings_dialog () : show_window_(false), menu_button_state_(false),
52 menu_button_normal_(gui_section::get("menu_button_normal")),
53 menu_button_down_(gui_section::get("menu_button_down"))
54 {
55 }
56
57 settings_dialog::~settings_dialog () {}
0 #ifndef SETTINGS_DIALOG_HPP_INCLUDED
1 #define SETTINGS_DIALOG_HPP_INCLUDED
2
3 #include "gui_section.hpp"
4 #include <SDL.h>
5
6 class settings_dialog
7 {
8 public:
9 settings_dialog ();
10 ~settings_dialog ();
11
12 bool handle_event (const SDL_Event& event); //returns whether the game should be paused
13 void draw () const;
14 void reset ();
15
16 private:
17 bool show_window_;
18 bool menu_button_state_;
19 const const_gui_section_ptr menu_button_normal_;
20 const const_gui_section_ptr menu_button_down_;
21 };
22
23 #endif
0 #include <iostream>
1 #include <sstream>
2
3 #include <boost/iostreams/filtering_stream.hpp>
4 #include <boost/iostreams/filter/gzip.hpp>
5
6 #include "simple_wml.hpp"
7
8 namespace simple_wml {
9
10 namespace {
11
12 void debug_delete(node* n) {
13 if(n) {
14 delete n;
15 }
16 }
17
18 char* uncompress_buffer(const string_span& input, string_span* span)
19 {
20 std::istringstream stream(std::string(input.begin(), input.end()));
21 boost::iostreams::filtering_stream<boost::iostreams::input> filter;
22 filter.push(boost::iostreams::gzip_decompressor());
23 filter.push(stream);
24
25 const size_t chunk_size = input.size() * 10;
26 std::vector<char> buf(chunk_size);
27 size_t len = 0;
28 size_t pos = 0;
29 while(filter.good() && (len = filter.read(&buf[pos], chunk_size).gcount()) == chunk_size) {
30 if(pos + chunk_size > 40000000) {
31 throw error("WML document exceeds 40MB limit");
32 }
33
34 pos += len;
35 buf.resize(pos + chunk_size);
36 len = 0;
37 }
38
39 if(!filter.eof() && !filter.good()) {
40 throw error("failed to uncompress");
41 }
42
43 pos += len;
44 buf.resize(pos);
45
46 char* small_out = new char[pos+1];
47 memcpy(small_out, &buf[0], pos);
48 small_out[pos] = 0;
49
50 *span = string_span(small_out, pos);
51 return small_out;
52 }
53
54 char* compress_buffer(const char* input, string_span* span)
55 {
56 std::string in(input);
57 std::istringstream stream(in);
58 boost::iostreams::filtering_stream<boost::iostreams::input> filter;
59 filter.push(boost::iostreams::gzip_compressor());
60 filter.push(stream);
61
62 std::vector<char> buf(in.size()*2 + 80);
63 const size_t len = filter.read(&buf[0], buf.size()).gcount();
64 assert(len < 128*1024*1024);
65 if((!filter.eof() && !filter.good()) || len == static_cast<size_t>(buf.size())) {
66 throw error("failed to compress");
67 }
68
69 buf.resize(len);
70
71 char* small_out = new char[len];
72 memcpy(small_out, &buf[0], len);
73
74 *span = string_span(small_out, len);
75 assert(*small_out == 31);
76 return small_out;
77 }
78
79 } // namespace
80
81 bool string_span::to_bool(bool default_value) const
82 {
83 if(empty()) {
84 return default_value;
85 }
86
87 return operator==("yes") || operator==("on") || operator==("true") || operator==("1");
88 }
89
90 int string_span::to_int() const
91 {
92 const int buf_size = 64;
93 if(size() >= buf_size) {
94 return 0;
95 }
96 char buf[64];
97 memcpy(buf, begin(), size());
98 buf[size()] = 0;
99 return atoi(buf);
100 }
101
102 std::string string_span::to_string() const
103 {
104 return std::string(begin(), end());
105 }
106
107 char* string_span::duplicate() const
108 {
109 char* buf = new char[size() + 1];
110 memcpy(buf, begin(), size());
111 buf[size()] = 0;
112 return buf;
113 }
114
115 error::error(const char* msg)
116 : message(msg)
117 {
118 std::cerr << "ERROR: '" << msg << "'\n";
119 }
120
121 std::ostream& operator<<(std::ostream& o, const string_span& s)
122 {
123 o << std::string(s.begin(), s.end());
124 return o;
125 }
126
127 node::node(document& doc, node* parent) :
128 doc_(&doc),
129 attr_(),
130 parent_(parent),
131 children_(),
132 ordered_children_(),
133 output_cache_()
134 {
135 }
136
137 node::node(document& doc, node* parent, const char** str, int depth) :
138 doc_(&doc),
139 attr_(),
140 parent_(parent),
141 children_(),
142 ordered_children_(),
143 output_cache_()
144 {
145 if(depth >= 1000) {
146 throw error("elements nested too deep");
147 }
148
149 const char*& s = *str;
150
151 const char* const begin = s;
152 while(*s) {
153 switch(*s) {
154 case '[': {
155 if(s[1] == '/') {
156 output_cache_ = string_span(begin, s - begin);
157 s = strchr(s, ']');
158 if(s == NULL) {
159 throw error("end element unterminated");
160 }
161
162 ++s;
163 return;
164 }
165
166 ++s;
167 const char* end = strchr(s, ']');
168 if(end == NULL) {
169 throw error("unterminated element");
170 }
171
172 const int list_index = get_children(string_span(s, end - s));
173 check_ordered_children();
174
175 s = end + 1;
176
177 children_[list_index].second.push_back(new node(doc, this, str, depth+1));
178 ordered_children_.push_back(node_pos(list_index, children_[list_index].second.size() - 1));
179 check_ordered_children();
180
181 break;
182 }
183 case ' ':
184 case '\t':
185 case '\n':
186 ++s;
187 break;
188 case '#':
189 s = strchr(s, '\n');
190 if(s == NULL) {
191 throw error("could not find newline after #");
192 }
193 break;
194 default: {
195 const char* end = strchr(s, '=');
196 if(end == NULL) {
197 std::cerr << "attribute: " << s << "\n";
198 throw error("could not find '=' after attribute");
199 }
200
201 string_span name(s, end - s);
202 s = end + 1;
203 if(*s == '_') {
204 s = strchr(s, '"');
205 if(s == NULL) {
206 throw error("could not find '\"' after _");
207 }
208 }
209
210 if(*s != '"') {
211 std::cerr << "no quotes for attribute '" << name << "'\n";
212 throw error("did not find quotes around attribute");
213 }
214
215 end = s;
216
217 for(;;) {
218 while((end = strchr(end+1, '"')) && end[1] == '"') {
219 ++end;
220 }
221
222 if(end == NULL) {
223 std::cerr << "ATTR: '" << name << "' (((" << s << ")))\n";
224 throw error("did not find end of attribute");
225 }
226
227 const char* endline = end;
228 while(*endline && *endline != '\n' && *endline != '+') {
229 ++endline;
230 }
231
232 if(*endline != '+') {
233 break;
234 }
235
236 end = strchr(endline, '"');
237 if(end == NULL) {
238 throw error("did not find quotes after +");
239 }
240 }
241
242 ++s;
243
244 string_span value(s, end - s);
245 if(attr_.empty() == false && !(attr_.back().first < name)) {
246 std::cerr << "attributes: '" << attr_.back().first << "' < '" << name << "'\n";
247 throw error("attributes not in order");
248 }
249
250 s = end + 1;
251
252 attr_.push_back(attribute(name, value));
253 }
254 }
255 }
256
257 output_cache_ = string_span(begin, s - begin);
258 check_ordered_children();
259 }
260
261 node::~node()
262 {
263 for(child_map::iterator i = children_.begin(); i != children_.end(); ++i) {
264 for(child_list::iterator j = i->second.begin(); j != i->second.end(); ++j) {
265 debug_delete(*j);
266 }
267 }
268 }
269
270 namespace {
271 struct string_span_pair_comparer
272 {
273 bool operator()(const string_span& a, const node::attribute& b) const {
274 return a < b.first;
275 }
276
277 bool operator()(const node::attribute& a, const string_span& b) const {
278 return a.first < b;
279 }
280
281 bool operator()(const node::attribute& a,
282 const node::attribute& b) const {
283 return a.first < b.first;
284 }
285 };
286 }
287
288 const string_span& node::operator[](const char* key) const
289 {
290 static string_span empty("");
291 string_span span(key);
292 std::pair<attribute_list::const_iterator,
293 attribute_list::const_iterator> range = std::equal_range(attr_.begin(), attr_.end(), span, string_span_pair_comparer());
294 if(range.first != range.second) {
295 return range.first->second;
296 }
297
298 return empty;
299 }
300
301 bool node::has_attr(const char* key) const
302 {
303 string_span span(key);
304 std::pair<attribute_list::const_iterator,
305 attribute_list::const_iterator> range = std::equal_range(attr_.begin(), attr_.end(), span, string_span_pair_comparer());
306 return range.first != range.second;
307 }
308
309 node& node::set_attr(const char* key, const char* value)
310 {
311 set_dirty();
312
313 string_span span(key);
314 std::pair<attribute_list::iterator,
315 attribute_list::iterator> range = std::equal_range(attr_.begin(), attr_.end(), span, string_span_pair_comparer());
316 if(range.first != range.second) {
317 range.first->second = string_span(value);
318 } else {
319 attr_.insert(range.first, attribute(span, string_span(value)));
320 }
321
322 return *this;
323 }
324
325 node& node::set_attr_dup(const char* key, const char* value)
326 {
327 return set_attr(key, doc_->dup_string(value));
328 }
329
330 node& node::set_attr_dup(const char* key, const string_span& value)
331 {
332 char* buf = value.duplicate();
333 doc_->take_ownership_of_buffer(buf);
334 return set_attr(key, buf);
335 }
336
337 node& node::set_attr_dup_key_and_value(const char* key, const char* value)
338 {
339 return set_attr(doc_->dup_string(key), doc_->dup_string(value));
340 }
341
342 node& node::set_attr_int(const char* key, int value)
343 {
344 char buf[64];
345 sprintf(buf, "%d", value);
346 return set_attr_dup(key, buf);
347 }
348
349 node& node::add_child_at(const char* name, size_t index)
350 {
351 set_dirty();
352
353 const int list_index = get_children(name);
354 child_list& list = children_[list_index].second;
355 if(index > list.size()) {
356 index = list.size();
357 }
358
359 check_ordered_children();
360 list.insert(list.begin() + index, new node(*doc_, this));
361 insert_ordered_child(list_index, index);
362
363 check_ordered_children();
364 return *list[index];
365 }
366
367
368 node& node::add_child(const char* name)
369 {
370 set_dirty();
371
372 const int list_index = get_children(name);
373 check_ordered_children();
374 child_list& list = children_[list_index].second;
375 list.push_back(new node(*doc_, this));
376 ordered_children_.push_back(node_pos(list_index, list.size() - 1));
377 check_ordered_children();
378 return *list.back();
379 }
380
381 void node::remove_child(const string_span& name, size_t index)
382 {
383 set_dirty();
384
385 //if we don't already have a vector for this item we don't want to add one.
386 child_map::iterator itor = find_in_map(children_, name);
387 if(itor == children_.end()) {
388 return;
389 }
390
391 child_list& list = itor->second;
392 if(index >= list.size()) {
393 return;
394 }
395
396 remove_ordered_child(itor - children_.begin(), index);
397
398 debug_delete(list[index]);
399 list.erase(list.begin() + index);
400
401 if(list.empty()) {
402 remove_ordered_child_list(itor - children_.begin());
403 children_.erase(itor);
404 }
405 }
406
407 void node::insert_ordered_child(int child_map_index, int child_list_index)
408 {
409 bool inserted = false;
410 std::vector<node_pos>::iterator i = ordered_children_.begin();
411 while(i != ordered_children_.end()) {
412 if(i->child_map_index == child_map_index && i->child_list_index > child_list_index) {
413 i->child_list_index++;
414 } else if(i->child_map_index == child_map_index && i->child_list_index == child_list_index) {
415 inserted = true;
416 i->child_list_index++;
417 i = ordered_children_.insert(i, node_pos(child_map_index, child_list_index));
418 ++i;
419 }
420
421 ++i;
422 }
423
424 if(!inserted) {
425 ordered_children_.push_back(node_pos(child_map_index, child_list_index));
426 }
427 }
428
429 void node::remove_ordered_child(int child_map_index, int child_list_index)
430 {
431 int erase_count = 0;
432 std::vector<node_pos>::iterator i = ordered_children_.begin();
433 while(i != ordered_children_.end()) {
434 if(i->child_map_index == child_map_index && i->child_list_index == child_list_index) {
435 i = ordered_children_.erase(i);
436 ++erase_count;
437 } else {
438 if(i->child_map_index == child_map_index && i->child_list_index > child_list_index) {
439 i->child_list_index--;
440 }
441 ++i;
442 }
443 }
444
445 assert(erase_count == 1);
446 }
447
448 void node::insert_ordered_child_list(int child_map_index)
449 {
450 std::vector<node_pos>::iterator i = ordered_children_.begin();
451 while(i != ordered_children_.end()) {
452 if(i->child_map_index >= child_map_index) {
453 i->child_map_index++;
454 }
455 }
456 }
457
458 void node::remove_ordered_child_list(int child_map_index)
459 {
460 std::vector<node_pos>::iterator i = ordered_children_.begin();
461 while(i != ordered_children_.end()) {
462 if(i->child_map_index == child_map_index) {
463 assert(false);
464 i = ordered_children_.erase(i);
465 } else {
466 if(i->child_map_index > child_map_index) {
467 i->child_map_index--;
468 }
469
470 ++i;
471 }
472 }
473 }
474
475 void node::check_ordered_children() const
476 {
477 // only define this symbol in debug mode to work out child ordering.
478 #ifdef CHECK_ORDERED_CHILDREN
479 std::vector<node_pos>::const_iterator i = ordered_children_.begin();
480 while(i != ordered_children_.end()) {
481 assert(i->child_map_index < children_.size());
482 assert(i->child_list_index < children_[i->child_map_index].second.size());
483 ++i;
484 }
485
486 for(child_map::const_iterator j = children_.begin(); j != children_.end(); ++j) {
487 const unsigned short child_map_index = j - children_.begin();
488 for(child_list::const_iterator k = j->second.begin(); k != j->second.end(); ++k) {
489 const unsigned short child_list_index = k - j->second.begin();
490 bool found = false;
491 for(int n = 0; n != ordered_children_.size(); ++n) {
492 if(ordered_children_[n].child_map_index == child_map_index &&
493 ordered_children_[n].child_list_index == child_list_index) {
494 found = true;
495 break;
496 }
497 }
498
499 assert(found);
500 }
501 }
502 #endif // CHECK_ORDERED_CHILDREN
503 }
504
505 void node::remove_child(const char* name, size_t index)
506 {
507 remove_child(string_span(name), index);
508 }
509
510 node* node::child(const char* name)
511 {
512 for(child_map::iterator i = children_.begin(); i != children_.end(); ++i) {
513 if(i->first == name) {
514 assert(i->second.empty() == false);
515 return i->second.front();
516 }
517 }
518
519 return NULL;
520 }
521
522 const node* node::child(const char* name) const
523 {
524 for(child_map::const_iterator i = children_.begin(); i != children_.end(); ++i) {
525 if(i->first == name) {
526 if(i->second.empty()) {
527 return NULL;
528 } else {
529 return i->second.front();
530 }
531 }
532 }
533
534 return NULL;
535 }
536
537 const node::child_list& node::children(const char* name) const
538 {
539 for(child_map::const_iterator i = children_.begin(); i != children_.end(); ++i) {
540 if(i->first == name) {
541 return i->second;
542 }
543 }
544
545 static const node::child_list empty;
546 return empty;
547 }
548
549 int node::get_children(const char* name)
550 {
551 return get_children(string_span(name));
552 }
553
554 int node::get_children(const string_span& name)
555 {
556 for(child_map::iterator i = children_.begin(); i != children_.end(); ++i) {
557 if(i->first == name) {
558 return i - children_.begin();
559 }
560 }
561
562 children_.push_back(child_pair(string_span(name), child_list()));
563 return children_.size() - 1;
564 }
565
566 node::child_map::const_iterator node::find_in_map(const child_map& m, const string_span& attr)
567 {
568 child_map::const_iterator i = m.begin();
569 for(; i != m.end(); ++i) {
570 if(i->first == attr) {
571 break;
572 }
573 }
574
575 return i;
576 }
577
578 node::child_map::iterator node::find_in_map(child_map& m, const string_span& attr)
579 {
580 child_map::iterator i = m.begin();
581 for(; i != m.end(); ++i) {
582 if(i->first == attr) {
583 break;
584 }
585 }
586
587 return i;
588 }
589
590 const string_span& node::first_child() const
591 {
592 if(children_.empty()) {
593 static const string_span empty;
594 return empty;
595 }
596
597 return children_.begin()->first;
598 }
599
600 int node::output_size() const
601 {
602 check_ordered_children();
603 if(output_cache_.empty() == false) {
604 return output_cache_.size();
605 }
606
607 int res = 0;
608 for(attribute_list::const_iterator i = attr_.begin(); i != attr_.end(); ++i) {
609 res += i->first.size() + i->second.size() + 4;
610 }
611
612 size_t count_children = 0;
613 for(child_map::const_iterator i = children_.begin(); i != children_.end(); ++i) {
614 for(child_list::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
615 res += i->first.size()*2 + 7;
616 res += (*j)->output_size();
617 ++count_children;
618 }
619 }
620
621 assert(count_children == ordered_children_.size());
622
623 return res;
624 }
625
626 void node::shift_buffers(ptrdiff_t offset)
627 {
628 if(!output_cache_.empty()) {
629 output_cache_ = string_span(output_cache_.begin() + offset, output_cache_.size());
630 }
631
632 for(std::vector<attribute>::iterator i = attr_.begin(); i != attr_.end(); ++i) {
633 i->first = string_span(i->first.begin() + offset, i->first.size());
634 i->second = string_span(i->second.begin() + offset, i->second.size());
635 }
636
637 for(child_map::iterator i = children_.begin(); i != children_.end(); ++i) {
638 string_span& key = i->first;
639 key = string_span(key.begin() + offset, key.size());
640 for(child_list::iterator j = i->second.begin(); j != i->second.end(); ++j) {
641 (*j)->shift_buffers(offset);
642 }
643 }
644 }
645
646 void node::output(char*& buf)
647 {
648 if(output_cache_.empty() == false) {
649 memcpy(buf, output_cache_.begin(), output_cache_.size());
650 shift_buffers(buf - output_cache_.begin());
651 buf += output_cache_.size();
652 return;
653 }
654
655 char* begin = buf;
656
657 for(std::vector<attribute>::iterator i = attr_.begin(); i != attr_.end(); ++i) {
658 memcpy(buf, i->first.begin(), i->first.size());
659 i->first = string_span(buf, i->first.size());
660 buf += i->first.size();
661 *buf++ = '=';
662 *buf++ = '"';
663 memcpy(buf, i->second.begin(), i->second.size());
664 i->second = string_span(buf, i->second.size());
665 buf += i->second.size();
666 *buf++ = '"';
667 *buf++ = '\n';
668 }
669
670 for(std::vector<node_pos>::const_iterator i = ordered_children_.begin();
671 i != ordered_children_.end(); ++i) {
672 assert(i->child_map_index < children_.size());
673 assert(i->child_list_index < children_[i->child_map_index].second.size());
674 string_span& attr = children_[i->child_map_index].first;
675 *buf++ = '[';
676 memcpy(buf, attr.begin(), attr.size());
677 attr = string_span(buf, attr.size());
678 buf += attr.size();
679 *buf++ = ']';
680 *buf++ = '\n';
681 children_[i->child_map_index].second[i->child_list_index]->output(buf);
682 *buf++ = '[';
683 *buf++ = '/';
684 memcpy(buf, attr.begin(), attr.size());
685 buf += attr.size();
686 *buf++ = ']';
687 *buf++ = '\n';
688 }
689
690 output_cache_ = string_span(begin, buf - begin);
691 }
692 const char* node::output() {
693 const char* out;
694
695 const int buf_size = output_size() + 1;
696 char* buf = new char[buf_size];
697 out = buf;
698
699 output(buf);
700 *buf++ = 0;
701 assert(buf == out + buf_size);
702
703 return out;
704 }
705
706 void node::copy_into(node& n) const
707 {
708 n.set_dirty();
709 for(attribute_list::const_iterator i = attr_.begin(); i != attr_.end(); ++i) {
710 char* key = i->first.duplicate();
711 char* value = i->second.duplicate();
712 n.doc_->take_ownership_of_buffer(key);
713 n.doc_->take_ownership_of_buffer(value);
714 n.set_attr(key, value);
715 }
716
717 for(std::vector<node_pos>::const_iterator i = ordered_children_.begin();
718 i != ordered_children_.end(); ++i) {
719 assert(i->child_map_index < children_.size());
720 assert(i->child_list_index < children_[i->child_map_index].second.size());
721 char* buf = children_[i->child_map_index].first.duplicate();
722 n.doc_->take_ownership_of_buffer(buf);
723 children_[i->child_map_index].second[i->child_list_index]->copy_into(n.add_child(buf));
724 }
725 }
726
727 void node::apply_diff(const node& diff)
728 {
729 set_dirty();
730 const node* inserts = diff.child("insert");
731 if(inserts != NULL) {
732 for(attribute_list::const_iterator i = inserts->attr_.begin(); i != inserts->attr_.end(); ++i) {
733 char* name = i->first.duplicate();
734 char* value = i->second.duplicate();
735 set_attr(name, value);
736 doc_->take_ownership_of_buffer(name);
737 doc_->take_ownership_of_buffer(value);
738 }
739 }
740
741 const node* deletes = diff.child("delete");
742 if(deletes != NULL) {
743 for(attribute_list::const_iterator i = deletes->attr_.begin(); i != deletes->attr_.end(); ++i) {
744 std::pair<attribute_list::iterator,
745 attribute_list::iterator> range = std::equal_range(attr_.begin(), attr_.end(), i->first, string_span_pair_comparer());
746 if(range.first != range.second) {
747 attr_.erase(range.first);
748 }
749 }
750 }
751
752 const child_list& child_changes = diff.children("change_child");
753 for(child_list::const_iterator i = child_changes.begin(); i != child_changes.end(); ++i) {
754 const size_t index = (**i)["index"].to_int();
755 for(child_map::const_iterator j = (*i)->children_.begin(); j != (*i)->children_.end(); ++j) {
756 const string_span& name = j->first;
757 for(child_list::const_iterator k = j->second.begin(); k != j->second.end(); ++k) {
758 child_map::iterator itor = find_in_map(children_, name);
759 if(itor != children_.end()) {
760 if(index < itor->second.size()) {
761 itor->second[index]->apply_diff(**k);
762 }
763 }
764 }
765 }
766 }
767
768 const child_list& child_inserts = diff.children("insert_child");
769 for(child_list::const_iterator i = child_inserts.begin(); i != child_inserts.end(); ++i) {
770 const size_t index = (**i)["index"].to_int();
771 for(child_map::const_iterator j = (*i)->children_.begin(); j != (*i)->children_.end(); ++j) {
772 const string_span& name = j->first;
773 for(child_list::const_iterator k = j->second.begin(); k != j->second.end(); ++k) {
774 char* buf = name.duplicate();
775 doc_->take_ownership_of_buffer(buf);
776 (*k)->copy_into(add_child_at(buf, index));
777 }
778 }
779 }
780
781 const child_list& child_deletes = diff.children("delete_child");
782 for(child_list::const_iterator i = child_deletes.begin(); i != child_deletes.end(); ++i) {
783 const size_t index = (**i)["index"].to_int();
784 for(child_map::const_iterator j = (*i)->children_.begin(); j != (*i)->children_.end(); ++j) {
785 if(j->second.empty()) {
786 continue;
787 }
788
789 const string_span& name = j->first;
790 remove_child(name, index);
791 }
792 }
793 }
794
795 void node::set_doc(document* doc)
796 {
797 doc_ = doc;
798
799 for(child_map::iterator i = children_.begin(); i != children_.end(); ++i) {
800 for(child_list::iterator j = i->second.begin(); j != i->second.end(); ++j) {
801 (*j)->set_doc(doc);
802 }
803 }
804 }
805
806 int node::nchildren() const
807 {
808 int res = 0;
809 for(child_map::const_iterator i = children_.begin(); i != children_.end(); ++i) {
810 for(child_list::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
811 ++res;
812 res += (*j)->nchildren();
813 }
814 }
815
816 return res;
817 }
818
819 int node::nattributes_recursive() const
820 {
821 int res = attr_.capacity();
822 for(child_map::const_iterator i = children_.begin(); i != children_.end(); ++i) {
823 for(child_list::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
824 res += (*j)->nattributes_recursive();
825 }
826 }
827
828 return res;
829 }
830
831 void node::set_dirty()
832 {
833 for(node* n = this; n != NULL && n->output_cache_.is_null() == false; n = n->parent_) {
834 n->output_cache_ = string_span();
835 }
836 }
837
838 document::document() :
839 compressed_buf_(),
840 output_(NULL),
841 buffers_(),
842 root_(new node(*this, NULL)),
843 prev_(NULL),
844 next_(NULL)
845 {
846 attach_list();
847 }
848
849 document::document(char* buf, INIT_BUFFER_CONTROL control) :
850 compressed_buf_(),
851 output_(buf),
852 buffers_(),
853 root_(NULL),
854 prev_(NULL),
855 next_(NULL)
856 {
857 if(control == INIT_TAKE_OWNERSHIP) {
858 buffers_.push_back(buf);
859 }
860 const char* cbuf = buf;
861 root_ = new node(*this, NULL, &cbuf);
862
863 attach_list();
864 }
865
866 document::document(const char* buf, INIT_STATE state) :
867 compressed_buf_(),
868 output_(buf),
869 buffers_(),
870 root_(NULL),
871 prev_(NULL),
872 next_(NULL)
873 {
874 if(state == INIT_COMPRESSED) {
875 output_compressed();
876 output_ = NULL;
877 } else {
878 root_ = new node(*this, NULL, &buf);
879 }
880
881 attach_list();
882 }
883
884 document::document(string_span compressed_buf) :
885 compressed_buf_(compressed_buf),
886 output_(NULL),
887 buffers_(),
888 root_(NULL),
889 prev_(NULL),
890 next_(NULL)
891 {
892 string_span uncompressed_buf;
893 buffers_.push_back(uncompress_buffer(compressed_buf, &uncompressed_buf));
894 output_ = uncompressed_buf.begin();
895 const char* cbuf = output_;
896 try {
897 root_ = new node(*this, NULL, &cbuf);
898 } catch(...) {
899 delete [] buffers_.front();
900 buffers_.clear();
901 throw;
902 }
903
904 attach_list();
905 }
906
907 document::~document()
908 {
909 for(std::vector<char*>::iterator i = buffers_.begin(); i != buffers_.end(); ++i) {
910 delete [] *i;
911 }
912
913 buffers_.clear();
914 debug_delete(root_);
915
916 detach_list();
917 }
918
919 const char* document::dup_string(const char* str)
920 {
921 const int len = strlen(str);
922 char* res = new char[len+1];
923 memcpy(res, str, len + 1);
924 buffers_.push_back(res);
925 return res;
926 }
927
928 const char* document::output()
929 {
930 if(output_ && (!root_ || root_->is_dirty() == false)) {
931 return output_;
932 }
933 if(!root_) {
934 assert(compressed_buf_.empty() == false);
935 string_span uncompressed_buf;
936 buffers_.push_back(uncompress_buffer(compressed_buf_, &uncompressed_buf));
937 output_ = uncompressed_buf.begin();
938 return output_;
939 }
940
941 //we're dirty, so the compressed buf must also be dirty; clear it.
942 compressed_buf_ = string_span();
943
944 std::vector<char*> bufs;
945 bufs.swap(buffers_);
946
947 const int buf_size = root_->output_size() + 1;
948 char* buf = new char[buf_size];
949 buffers_.push_back(buf);
950 output_ = buf;
951
952 root_->output(buf);
953 *buf++ = 0;
954 assert(buf == output_ + buf_size);
955
956 for(std::vector<char*>::iterator i = bufs.begin(); i != bufs.end(); ++i) {
957 delete [] *i;
958 }
959
960 bufs.clear();
961
962 return output_;
963 }
964
965 string_span document::output_compressed()
966 {
967 if(compressed_buf_.empty() == false &&
968 (root_ == NULL || root_->is_dirty() == false)) {
969 assert(*compressed_buf_.begin() == 31);
970 return compressed_buf_;
971 }
972
973 buffers_.push_back(compress_buffer(output(), &compressed_buf_));
974 assert(*compressed_buf_.begin() == 31);
975
976 return compressed_buf_;
977 }
978
979 void document::compress()
980 {
981 output_compressed();
982 debug_delete(root_);
983 root_ = NULL;
984 output_ = NULL;
985 std::vector<char*> new_buffers;
986 for(std::vector<char*>::iterator i = buffers_.begin(); i != buffers_.end(); ++i) {
987 if(*i != compressed_buf_.begin()) {
988 delete [] *i;
989 } else {
990 new_buffers.push_back(*i);
991 }
992 }
993
994 buffers_.swap(new_buffers);
995 assert(buffers_.size() == 1);
996 }
997
998 void document::generate_root()
999 {
1000 if(output_ == NULL) {
1001 assert(compressed_buf_.empty() == false);
1002 string_span uncompressed_buf;
1003 buffers_.push_back(uncompress_buffer(compressed_buf_, &uncompressed_buf));
1004 output_ = uncompressed_buf.begin();
1005 }
1006
1007 assert(root_ == NULL);
1008 const char* cbuf = output_;
1009 root_ = new node(*this, NULL, &cbuf);
1010 }
1011
1012 document* document::clone()
1013 {
1014 char* buf = new char[strlen(output())+1];
1015 strcpy(buf, output());
1016 return new document(buf);
1017 }
1018
1019 void document::swap(document& o)
1020 {
1021 std::swap(compressed_buf_, o.compressed_buf_);
1022 std::swap(output_, o.output_);
1023 buffers_.swap(o.buffers_);
1024 std::swap(root_, o.root_);
1025
1026 root_->set_doc(this);
1027 o.root_->set_doc(&o);
1028 }
1029
1030 void document::clear()
1031 {
1032 compressed_buf_ = string_span();
1033 output_ = NULL;
1034 debug_delete(root_);
1035 root_ = new node(*this, NULL);
1036 for(std::vector<char*>::iterator i = buffers_.begin(); i != buffers_.end(); ++i) {
1037 delete [] *i;
1038 }
1039
1040 buffers_.clear();
1041 }
1042
1043 namespace {
1044 document* head_doc = NULL;
1045 }
1046
1047 void document::attach_list()
1048 {
1049 prev_ = NULL;
1050 next_ = head_doc;
1051
1052 if(next_) {
1053 next_->prev_ = this;
1054 }
1055 head_doc = this;
1056 }
1057
1058 void document::detach_list()
1059 {
1060 if(head_doc == this) {
1061 head_doc = next_;
1062 }
1063
1064 if(next_) {
1065 next_->prev_ = prev_;
1066 }
1067
1068 if(prev_) {
1069 prev_->next_ = next_;
1070 }
1071 next_ = prev_ = NULL;
1072 }
1073
1074 std::string document::stats()
1075 {
1076 std::ostringstream s;
1077 int ndocs = 0;
1078 int ncompressed = 0;
1079 int compressed_size = 0;
1080 int ntext = 0;
1081 int text_size = 0;
1082 int nbuffers = 0;
1083 int nnodes = 0;
1084 int nhas_nodes = 0;
1085 int ndirty = 0;
1086 int nattributes = 0;
1087 for(document* d = head_doc; d != NULL; d = d->next_) {
1088 ndocs++;
1089 nbuffers += d->buffers_.size();
1090
1091 if(d->compressed_buf_.is_null() == false) {
1092 ++ncompressed;
1093 compressed_size += d->compressed_buf_.size();
1094 }
1095
1096 if(d->output_) {
1097 ++ntext;
1098 text_size += strlen(d->output_);
1099 }
1100
1101 if(d->root_) {
1102 ++nhas_nodes;
1103 nnodes += 1 + d->root_->nchildren();
1104 nattributes += d->root_->nattributes_recursive();
1105 }
1106
1107 if(d->root_ && d->root_->is_dirty()) {
1108 ++ndirty;
1109 }
1110 }
1111
1112 const int nodes_alloc = nnodes*(sizeof(node) + 12);
1113 const int attr_alloc = nattributes*(sizeof(string_span)*2);
1114 const int total_alloc = compressed_size + text_size + nodes_alloc + attr_alloc;
1115
1116 s << "WML documents: " << ndocs << "\n"
1117 << "Dirty: " << ndirty << "\n"
1118 << "With compression: " << ncompressed << " (" << compressed_size
1119 << " bytes)\n"
1120 << "With text: " << ntext << " (" << text_size
1121 << " bytes)\n"
1122 << "Nodes: " << nnodes << " (" << nodes_alloc << " bytes)\n"
1123 << "Attr: " << nattributes << " (" << attr_alloc << " bytes)\n"
1124 << "Buffers: " << nbuffers << "\n"
1125 << "Total allocation: " << total_alloc << " bytes\n";
1126
1127 return s.str();
1128 }
1129
1130 }
1131
1132 #ifdef UNIT_TEST_SIMPLE_WML
1133
1134 int main(int argc, char** argv)
1135 {
1136 char* doctext = strdup(
1137 "[test]\n"
1138 "a=\"blah\"\n"
1139 "b=\"blah\"\n"
1140 "c=\"\\\\\"\n"
1141 "d=\"\\\"\"\n"
1142 "[/test]");
1143 std::cerr << doctext << "\n";
1144 simple_wml::document doc(doctext);
1145
1146 simple_wml::node& node = doc.root();
1147 simple_wml::node* test_node = node.child("test");
1148 assert(test_node);
1149 assert((*test_node)["a"] == "blah");
1150 assert((*test_node)["b"] == "blah");
1151 assert((*test_node)["c"] == "\\\\");
1152 assert((*test_node)["d"] == "\\\"");
1153
1154 node.set_attr("blah", "blah");
1155 test_node->set_attr("e", "f");
1156 std::cerr << doc.output();
1157 }
1158
1159 #endif
0 #ifndef SIMPLE_WML_HPP_INCLUDED
1 #define SIMPLE_WML_HPP_INCLUDED
2
3 #include <string.h>
4
5 #include <iosfwd>
6 #include <map>
7 #include <string>
8 #include <vector>
9
10 namespace simple_wml {
11
12 struct error {
13 error(const char* msg);
14 std::string message;
15 };
16
17 class string_span
18 {
19 public:
20 string_span() : str_(NULL), size_(0)
21 {}
22 string_span(const char* str, int size) : str_(str), size_(size)
23 {}
24 string_span(const char* str) : str_(str), size_(strlen(str))
25 {}
26
27 bool operator==(const char* o) const {
28 const char* i1 = str_;
29 const char* i2 = str_ + size_;
30 while(i1 != i2 && *o && *i1 == *o) {
31 ++i1;
32 ++o;
33 }
34
35 return i1 == i2 && *o == 0;
36 }
37 bool operator!=(const char* o) const {
38 return !operator==(o);
39 }
40 bool operator==(const std::string& o) const {
41 return size_ == o.size() && memcmp(str_, o.data(), size_) == 0;
42 }
43 bool operator!=(const std::string& o) const {
44 return !operator==(o);
45 }
46 bool operator==(const string_span& o) const {
47 return size_ == o.size_ && memcmp(str_, o.str_, size_) == 0;
48 }
49 bool operator!=(const string_span& o) const {
50 return !operator==(o);
51 }
52 bool operator<(const string_span& o) const {
53 const int len = size_ < o.size_ ? size_ : o.size_;
54 for(int n = 0; n < len; ++n) {
55 if(str_[n] != o.str_[n]) {
56 if(str_[n] < o.str_[n]) {
57 return true;
58 } else {
59 return false;
60 }
61 }
62 }
63
64 return size_ < o.size_;
65 }
66
67 const char* begin() const { return str_; }
68 const char* end() const { return str_ + size_; }
69
70 int size() const { return size_; }
71 bool empty() const { return size_ == 0; }
72 bool is_null() const { return str_ == NULL; }
73
74 bool to_bool(bool default_value=false) const;
75 int to_int() const;
76 std::string to_string() const;
77
78 //returns a duplicate of the string span in a new[] allocated buffer
79 char* duplicate() const;
80
81 private:
82 const char* str_;
83 unsigned int size_;
84 };
85
86 std::ostream& operator<<(std::ostream& o, const string_span& s);
87
88 class document;
89
90 class node
91 {
92 public:
93 node(document& doc, node* parent);
94 node(document& doc, node* parent, const char** str, int depth=0);
95 ~node();
96
97 typedef std::pair<string_span, string_span> attribute;
98 typedef std::vector<node*> child_list;
99
100 const string_span& operator[](const char* key) const;
101 const string_span& attr(const char* key) const {
102 return (*this)[key];
103 }
104
105 bool has_attr(const char* key) const;
106
107 //sets an attribute in the WML node. The node will keep a direct reference
108 //to key and value which it will maintain for its lifetime. The caller
109 //MUST guarantee that the key and value buffers remain valid for the
110 //lifetime of the node.
111 node& set_attr(const char* key, const char* value);
112
113 //functions which are identical to set_attr() except that the buffer
114 //referred to by 'value' will be duplicated and the new buffer managed by
115 //the node. The caller may destroy the value buffer as soon as the function
116 //returns. The key buffer must remain valid for the lifetime of the node.
117 node& set_attr_dup(const char* key, const char* value);
118 node& set_attr_dup(const char* key, const string_span& value);
119
120 //sets an attribute with identical behavior to set_attr_dup, except that
121 //the buffer referred to by 'key' will also be duplicated and managed by
122 //the node. The caller may destroy both key and value as soon as the
123 //call returns.
124 node& set_attr_dup_key_and_value(const char* key, const char* value);
125
126 node& set_attr_int(const char* key, int value);
127
128 node& add_child(const char* name);
129 node& add_child_at(const char* name, size_t index);
130 void remove_child(const char* name, size_t index);
131 void remove_child(const string_span& name, size_t index);
132
133 node* child(const char* name);
134 const node* child(const char* name) const;
135
136 const child_list& children(const char* name) const;
137
138 const string_span& first_child() const;
139
140 bool is_dirty() const { return output_cache_.is_null(); }
141
142 int output_size() const;
143 void output(char*& buf);
144 const char* output();
145
146 void copy_into(node& n) const;
147
148 bool no_children() const { return children_.empty(); }
149 bool one_child() const { return children_.size() == 1 && children_.begin()->second.size() == 1; }
150
151 void apply_diff(const node& diff);
152
153 void set_doc(document* doc);
154
155 int nchildren() const;
156 int nattributes_recursive() const;
157
158 private:
159 node(const node&);
160 void operator=(const node&);
161
162 int get_children(const string_span& name);
163 int get_children(const char* name);
164
165 void set_dirty();
166 void shift_buffers(ptrdiff_t offset);
167
168 document* doc_;
169
170 typedef std::vector<attribute> attribute_list;
171 attribute_list attr_;
172
173 node* parent_;
174
175 typedef std::pair<string_span, child_list> child_pair;
176 typedef std::vector<child_pair> child_map;
177
178 static child_map::const_iterator find_in_map(const child_map& m, const string_span& attr);
179 static child_map::iterator find_in_map(child_map& m, const string_span& attr);
180 child_map children_;
181
182 //a node position indicates the index into the child map where the node
183 //is, and then the index into the child list within where the node is.
184 struct node_pos {
185 node_pos(int child_map_index, int child_list_index)
186 : child_map_index(child_map_index), child_list_index(child_list_index)
187 {}
188 unsigned short child_map_index;
189 unsigned short child_list_index;
190 };
191
192 //a list of all the children in order.
193 std::vector<node_pos> ordered_children_;
194
195 void insert_ordered_child(int child_map_index, int child_list_index);
196 void remove_ordered_child(int child_map_index, int child_list_index);
197 void insert_ordered_child_list(int child_map_index);
198 void remove_ordered_child_list(int child_map_index);
199
200 void check_ordered_children() const;
201
202 string_span output_cache_;
203 };
204
205 enum INIT_BUFFER_CONTROL { INIT_TAKE_OWNERSHIP };
206
207 enum INIT_STATE { INIT_COMPRESSED, INIT_STATIC };
208
209 class document
210 {
211 public:
212 document();
213 explicit document(char* buf, INIT_BUFFER_CONTROL control=INIT_TAKE_OWNERSHIP);
214 document(const char* buf, INIT_STATE state);
215 explicit document(string_span compressed_buf);
216 ~document();
217 const char* dup_string(const char* str);
218 node& root() { if(!root_) { generate_root(); } return *root_; }
219 const node& root() const { if(!root_) { const_cast<document*>(this)->generate_root(); } return *root_; }
220
221 const char* output();
222 string_span output_compressed();
223
224 void compress();
225
226 document* clone();
227
228 const string_span& operator[](const char* key) const {
229 return root()[key];
230 }
231
232 const string_span& attr(const char* key) const {
233 return root()[key];
234 }
235
236 node* child(const char* name) {
237 return root().child(name);
238 }
239
240 const node* child(const char* name) const {
241 return root().child(name);
242 }
243
244 node& set_attr(const char* key, const char* value) {
245 return root().set_attr(key, value);
246 }
247
248 node& set_attr_dup(const char* key, const char* value) {
249 return root().set_attr_dup(key, value);
250 }
251
252 void take_ownership_of_buffer(char* buffer) {
253 buffers_.push_back(buffer);
254 }
255
256 void swap(document& o);
257 void clear();
258
259 static std::string stats();
260
261 private:
262 void generate_root();
263 document(const document&);
264 void operator=(const document&);
265
266 string_span compressed_buf_;
267 const char* output_;
268 std::vector<char*> buffers_;
269 node* root_;
270
271 //linked list of documents for accounting purposes
272 void attach_list();
273 void detach_list();
274 document* prev_;
275 document* next_;
276 };
277
278 }
279
280 #endif
0 #include "asserts.hpp"
1 #include "foreach.hpp"
2 #include "solid_map.hpp"
3 #include "texture.hpp"
4 #include "wml_node.hpp"
5 #include "wml_utils.hpp"
6
7 const_solid_map_ptr solid_map::create_object_solid_map_from_solid_node(wml::const_node_ptr node)
8 {
9 solid_map_ptr result(create_from_texture(graphics::texture::get(node->attr("image")), rect(node->attr("area"))));
10 result->id_ = node->attr("id");
11 return result;
12
13 }
14
15 void solid_map::create_object_solid_maps(wml::const_node_ptr node, std::vector<const_solid_map_ptr>& v)
16 {
17 FOREACH_WML_CHILD(solid_node, node, "solid") {
18 v.push_back(create_object_solid_map_from_solid_node(solid_node));
19 }
20
21 if(!node->has_attr("solid_area") || node->attr("solid_area").str() == "none") {
22 return;
23 }
24
25 rect area(node->attr("solid_area"));
26 area = rect(area.x()*2, area.y()*2, area.w()*2, area.h()*2);
27
28 int legs_height = area.w()/2 + 1;
29 if(wml::get_bool(node, "has_feet", true) == false || node->attr("solid_shape").str() == "rect") {
30 legs_height = 0;
31 }
32
33 if(legs_height < area.h()) {
34 rect body(area.x(), area.y(), area.w(), area.h() - legs_height);
35 solid_map_ptr body_map(new solid_map);
36 body_map->id_ = "body";
37 body_map->area_ = body;
38 body_map->solid_.resize(body.w()*body.h(), true);
39 body_map->calculate_side(0, -1, body_map->top_);
40 body_map->calculate_side(-1, 0, body_map->left_);
41 body_map->calculate_side(1, 0, body_map->right_);
42 body_map->calculate_side(-100000, 0, body_map->all_);
43
44 if(legs_height == 0) {
45 body_map->calculate_side(0, 1, body_map->bottom_);
46 }
47 v.push_back(body_map);
48 } else {
49 legs_height = area.h();
50 }
51
52 if(legs_height) {
53 rect legs(area.x(), area.y2() - legs_height, area.w(), legs_height);
54 solid_map_ptr legs_map(new solid_map);
55 legs_map->id_ = "legs";
56 legs_map->area_ = legs;
57 legs_map->solid_.resize(legs.w()*legs.h(), false);
58 for(int y = 0; y != legs.h(); ++y) {
59 for(int x = y; x < legs.w() - y; ++x) {
60 legs_map->set_solid(x, y);
61 }
62 }
63
64 legs_map->calculate_side(0, 1, legs_map->bottom_);
65 legs_map->calculate_side(-1, 0, legs_map->left_);
66 legs_map->calculate_side(1, 0, legs_map->right_);
67 legs_map->calculate_side(-10000, 0, legs_map->all_);
68 v.push_back(legs_map);
69 }
70 }
71
72 void solid_map::create_object_platform_maps(wml::const_node_ptr node, std::vector<const_solid_map_ptr>& v)
73 {
74 if(!node->has_attr("platform_area")) {
75 return;
76 }
77
78 rect area(node->attr("platform_area"));
79
80 //intentionally do NOT double the height of the area.
81 area = rect(area.x()*2, area.y()*2, area.w()*2, 1);
82
83 ASSERT_EQ(area.h(), 1);
84
85 solid_map_ptr platform(new solid_map);
86 platform->id_ = "platform";
87 platform->area_ = area;
88 platform->solid_.resize(area.w()*area.h(), true);
89 platform->calculate_side(0, -1, platform->top_);
90 platform->calculate_side(0, 1, platform->bottom_);
91 platform->calculate_side(-1, 0, platform->left_);
92 platform->calculate_side(1, 0, platform->right_);
93 platform->calculate_side(-100000, 0, platform->all_);
94 v.push_back(platform);
95 }
96 solid_map_ptr solid_map::create_from_texture(const graphics::texture& t, const rect& area_rect)
97 {
98 rect area = area_rect;
99
100 bool found_solid = false;
101 while(!found_solid && area.h() > 0) {
102 for(int x = 0; x < area.w(); ++x) {
103 if(!t.is_alpha(area.x() + x, area.y() + area.h() - 1)) {
104 found_solid = true;
105 break;
106 }
107 }
108
109 if(!found_solid) {
110 area = rect(area.x(), area.y(), area.w(), area.h()-1);
111 }
112 }
113
114 found_solid = false;
115 while(!found_solid && area.h() > 0) {
116 for(int x = 0; x < area.w(); ++x) {
117 if(!t.is_alpha(area.x() + x, area.y())) {
118 found_solid = true;
119 break;
120 }
121 }
122
123 if(!found_solid) {
124 area = rect(area.x(), area.y()+1, area.w(), area.h()-1);
125 }
126 }
127
128 found_solid = false;
129 while(!found_solid && area.w() > 0) {
130 for(int y = 0; y < area.h(); ++y) {
131 if(!t.is_alpha(area.x(), area.y() + y)) {
132 found_solid = true;
133 break;
134 }
135 }
136
137 if(!found_solid) {
138 area = rect(area.x()+1, area.y(), area.w()-1, area.h());
139 }
140 }
141
142 found_solid = false;
143 while(!found_solid && area.w() > 0) {
144 for(int y = 0; y < area.h(); ++y) {
145 if(!t.is_alpha(area.x() + area.w() - 1, area.y() + y)) {
146 found_solid = true;
147 break;
148 }
149 }
150
151 if(!found_solid) {
152 area = rect(area.x(), area.y(), area.w()-1, area.h());
153 }
154 }
155
156 solid_map_ptr solid(new solid_map);
157 solid->area_ = rect(area.x()*2, area.y()*2, area.w()*2, area.h()*2);
158 solid->solid_.resize(solid->area_.w()*solid->area_.h(), false);
159 for(int y = 0; y < solid->area_.h(); ++y) {
160 for(int x = 0; x < solid->area_.w(); ++x) {
161 bool is_solid = !t.is_alpha(area.x() + x/2, area.y() + y/2);
162 if(!is_solid && (y&1) && y < solid->area_.h() - 1 && !t.is_alpha(area.x() + x/2, area.y() + y/2 + 1)) {
163 //we are scaling things up by double, so we want to smooth
164 //things out. In the bottom half of an empty source pixel, we
165 //will set it to solid if the pixel below is solid, and the
166 //adjacent horizontal pixel is solid
167 if((x&1) && x < solid->area_.w() - 1 && !t.is_alpha(area.x() + x/2 + 1, area.y() + y/2)) {
168 is_solid = true;
169 } else if(!(x&1) && x > 0 && !t.is_alpha(area.x() + x/2 - 1, area.y() + y/2)) {
170 is_solid = true;
171 }
172 }
173
174 if(is_solid) {
175 solid->set_solid(x, y);
176 }
177 }
178 }
179 return solid;
180 }
181
182 bool solid_map::solid_at(int x, int y) const
183 {
184 if(x < 0 || y < 0 || x >= area_.w() || y >= area_.h()) {
185 return false;
186 }
187
188 return solid_[y*area_.w() + x];
189 }
190
191 const std::vector<point>& solid_map::dir(MOVE_DIRECTION d) const
192 {
193 switch(d) {
194 case MOVE_LEFT: return left();
195 case MOVE_RIGHT: return right();
196 case MOVE_UP: return top();
197 case MOVE_DOWN: return bottom();
198 case MOVE_NONE: return all();
199 default:
200 assert(false);
201 return all();
202 }
203 }
204
205 void solid_map::set_solid(int x, int y, bool value)
206 {
207 ASSERT_EQ(solid_.size(), area_.w()*area_.h());
208 if(x < 0 || y < 0 || x >= area_.w() || y >= area_.h()) {
209 return;
210 }
211
212 solid_[y*area_.w() + x] = value;
213 }
214
215 void solid_map::calculate_side(int xdir, int ydir, std::vector<point>& points) const
216 {
217 int index = 0;
218 const int height = area_.h();
219 const int width = area_.w();
220 for(int y = 0; y < height; ++y) {
221 for(int x = 0; x < width; ++x) {
222 //for performance reasons, check our current position directly
223 //rather than calling solid_at() so we don't do bounds checking.
224 if(solid_[index] && !solid_at(x + xdir, y + ydir)) {
225 points.push_back(point(area_.x() + x, area_.y() + y));
226 }
227
228 ++index;
229 }
230 }
231 }
232
233 const_solid_info_ptr solid_info::create_from_solid_maps(const std::vector<const_solid_map_ptr>& solid)
234 {
235 if(solid.empty()) {
236 return const_solid_info_ptr();
237 } else {
238 solid_info* result = new solid_info;
239 int x1 = solid.front()->area().x();
240 int y1 = solid.front()->area().y();
241 int x2 = solid.front()->area().x2();
242 int y2 = solid.front()->area().y2();
243 foreach(const_solid_map_ptr s, solid) {
244 if(s->area().x() < x1) {
245 x1 = s->area().x();
246 }
247 if(s->area().y() < y1) {
248 y1 = s->area().y();
249 }
250 if(s->area().x2() > x2) {
251 x2 = s->area().x2();
252 }
253 if(s->area().y2() > y2) {
254 y2 = s->area().y2();
255 }
256 }
257
258 result->area_ = rect::from_coordinates(x1, y1, x2-1, y2-1);
259 result->solid_= solid;
260 return const_solid_info_ptr(result);
261 }
262 }
263
264 const_solid_info_ptr solid_info::create(wml::const_node_ptr node)
265 {
266 std::vector<const_solid_map_ptr> solid;
267 solid_map::create_object_solid_maps(node, solid);
268 return create_from_solid_maps(solid);
269 }
270
271 const_solid_info_ptr solid_info::create_platform(wml::const_node_ptr node)
272 {
273 std::vector<const_solid_map_ptr> platform;
274 solid_map::create_object_platform_maps(node, platform);
275 return create_from_solid_maps(platform);
276 }
277
278 const_solid_info_ptr solid_info::create_from_texture(const graphics::texture& t, const rect& area)
279 {
280 std::vector<const_solid_map_ptr> solid;
281 solid.push_back(solid_map::create_from_texture(t, area));
282 return create_from_solid_maps(solid);
283 }
284
285 bool solid_info::solid_at(int x, int y, const std::string** area_id) const
286 {
287 foreach(const const_solid_map_ptr& s, solid_) {
288 if(s->solid_at(x - s->area().x(), y - s->area().y())) {
289 if(area_id) {
290 *area_id = &s->id();
291 }
292 return true;
293 }
294 }
295
296 return false;
297 }
0 #ifndef SOLID_MAP_HPP_INCLUDED
1 #define SOLID_MAP_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 #include <vector>
6
7 #include "geometry.hpp"
8 #include "solid_map_fwd.hpp"
9 #include "wml_node_fwd.hpp"
10
11 enum MOVE_DIRECTION { MOVE_LEFT, MOVE_RIGHT, MOVE_UP, MOVE_DOWN, MOVE_NONE };
12
13 namespace graphics {
14 class texture;
15 }
16
17 class solid_map
18 {
19 public:
20 static void create_object_solid_maps(wml::const_node_ptr node, std::vector<const_solid_map_ptr>& v);
21 static void create_object_platform_maps(wml::const_node_ptr node, std::vector<const_solid_map_ptr>& v);
22 static solid_map_ptr create_from_texture(const graphics::texture& t, const rect& area);
23
24 const std::string& id() const { return id_; }
25 const rect& area() const { return area_; }
26
27 bool solid_at(int x, int y) const;
28
29 const std::vector<point>& dir(MOVE_DIRECTION d) const;
30 const std::vector<point>& left() const { return left_; }
31 const std::vector<point>& right() const { return right_; }
32 const std::vector<point>& top() const { return top_; }
33 const std::vector<point>& bottom() const { return bottom_; }
34 const std::vector<point>& all() const { return all_; }
35 private:
36 static const_solid_map_ptr create_object_solid_map_from_solid_node(wml::const_node_ptr node);
37
38 solid_map() {}
39
40 void set_solid(int x, int y, bool value=true);
41
42 void calculate_side(int xdir, int ydir, std::vector<point>& points) const;
43
44 std::string id_;
45 rect area_;
46
47 std::vector<bool> solid_;
48
49 //all the solid points that are on the different sides of the solid area.
50 std::vector<point> left_, right_, top_, bottom_, all_;
51 };
52
53 class solid_info
54 {
55 public:
56 static const_solid_info_ptr create(wml::const_node_ptr node);
57 static const_solid_info_ptr create_platform(wml::const_node_ptr node);
58 static const_solid_info_ptr create_from_texture(const graphics::texture& t, const rect& area);
59 const std::vector<const_solid_map_ptr>& solid() const { return solid_; }
60 const rect& area() const { return area_; }
61 bool solid_at(int x, int y, const std::string** area_id=NULL) const;
62 private:
63 static const_solid_info_ptr create_from_solid_maps(const std::vector<const_solid_map_ptr>& v);
64
65 std::vector<const_solid_map_ptr> solid_;
66 rect area_;
67 };
68
69 #endif
0 #ifndef SOLID_MAP_FWD_HPP_INCLUDED
1 #define SOLID_MAP_FWD_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 class solid_map;
6
7 typedef boost::shared_ptr<solid_map> solid_map_ptr;
8 typedef boost::shared_ptr<const solid_map> const_solid_map_ptr;
9
10 class solid_info;
11
12 typedef boost::shared_ptr<const solid_info> const_solid_info_ptr;
13
14 #endif
0 #include <iostream>
1 #include <map>
2 #include <vector>
3
4 #include <pthread.h>
5
6 #include <boost/shared_ptr.hpp>
7
8 #include "preferences.hpp"
9 #include "filesystem.hpp"
10 #include "SDL.h"
11 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
12 #include "SDL_mixer.h"
13 #endif
14
15 #include "sound.hpp"
16
17 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
18 #include "iphone_sound.h"
19 #endif
20
21 namespace sound {
22
23 namespace {
24 const int SampleRate = 44100;
25 // number of allocated channels,
26 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
27 const size_t NumChannels = 16;
28 #else
29 const size_t NumChannels = 16;
30 #endif
31
32 #ifdef WIN32
33 const size_t BufferSize = 4096;
34 #else
35 const size_t BufferSize = 1024;
36 #endif
37
38 bool sound_ok = false;
39 bool mute_ = false;
40 std::string& current_music_name() {
41 static std::string name;
42 return name;
43 }
44
45 std::string& next_music() {
46 static std::string name;
47 return name;
48 }
49
50 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
51 Mix_Music* current_mix_music = NULL;
52 #else
53 bool playing_music = false;
54 #endif
55
56 //function which gets called when music finishes playing. It starts playing
57 //of the next scheduled track, if there is one.
58 void on_music_finished()
59 {
60 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
61 Mix_FreeMusic(current_mix_music);
62 current_mix_music = NULL;
63 #else
64 playing_music = false;
65 #endif
66 if(next_music().empty() == false) {
67 play_music(next_music());
68 }
69 next_music().clear();
70 }
71
72 //record which channels sounds are playing on, in case we
73 //want to cancel a sound.
74 struct sound_playing {
75 std::string file;
76 const void* object;
77 int loops; //not strictly boolean. -1=true, 0=false
78 };
79
80 std::vector<sound_playing> channels_to_sounds_playing;
81
82 void on_sound_finished(int channel)
83 {
84 if(channel >= 0 && channel < channels_to_sounds_playing.size()) {
85 channels_to_sounds_playing[channel].object = NULL;
86 }
87 }
88
89 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
90
91 class sound; //so mixer can make pointers to it
92
93 struct mixer
94 {
95 /* channel array holds information about currently playing sounds */
96 struct
97 {
98 Uint8 *position; /* what is the current position in the buffer of this sound ? */
99 Uint32 remaining; /* how many bytes remaining before we're done playing the sound ? */
100 Uint32 timestamp; /* when did this sound start playing ? */
101 int volume;
102 int loops;
103 sound *s;
104 } channels[NumChannels];
105 SDL_AudioSpec outputSpec; /* what audio format are we using for output? */
106 int numSoundsPlaying; /* how many sounds are currently playing */
107 } mixer;
108
109 class sound
110 {
111 public:
112 boost::shared_ptr<Uint8> buffer; /* audio buffer for sound file */
113 Uint32 length; /* length of the buffer (in bytes) */
114 sound (const std::string& file = "") : length(0)
115 {
116 if (file == "") return;
117 SDL_AudioSpec spec; /* the audio format of the .wav file */
118 SDL_AudioCVT cvt; /* used to convert .wav to output format when formats differ */
119 Uint8 *tmp_buffer;
120 if (SDL_LoadWAV(file.c_str(), &spec, &tmp_buffer, &length) == NULL)
121 {
122 std::cerr << "Could not load sound: " << file << "\n";
123 return; //should maybe die
124 }
125 buffer = boost::shared_ptr<Uint8>(tmp_buffer, SDL_free);
126 return; // don't convert the audio, assume it's already in the right format
127 /* build the audio converter */
128 int result = SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq,
129 mixer.outputSpec.format, mixer.outputSpec.channels, mixer.outputSpec.freq);
130 if (result == -1)
131 {
132 std::cerr << "Could not build audio CVT for: " << file << "\n";
133 return; //should maybe die
134 } else if (result != 0) {
135 /*
136 this happens when the .wav format differs from the output format.
137 we convert the .wav buffer here
138 */
139 cvt.buf = (Uint8 *) SDL_malloc(length * cvt.len_mult); /* allocate conversion buffer */
140 cvt.len = length; /* set conversion buffer length */
141 SDL_memcpy(cvt.buf, buffer.get(), length); /* copy sound to conversion buffer */
142 if (SDL_ConvertAudio(&cvt) == -1) /* convert the sound */
143 {
144 std::cerr << "Could not convert sound: " << file << "\n";
145 SDL_free(cvt.buf);
146 return; //should maybe die
147 }
148 buffer = boost::shared_ptr<Uint8>(cvt.buf, SDL_free); /* point sound buffer to converted buffer */
149 length = cvt.len_cvt; /* set sound buffer's new length */
150 }
151 }
152
153 bool operator==(void *p) {return buffer.get() == p;}
154 };
155
156 void sdl_stop_channel (int channel)
157 {
158 if (mixer.channels[channel].position == NULL) return; // if the sound was playing in the first place
159 mixer.channels[channel].position = NULL; /* indicates no sound playing on channel anymore */
160 mixer.numSoundsPlaying--;
161 if (mixer.numSoundsPlaying == 0)
162 {
163 /* if no sounds left playing, pause audio callback */
164 SDL_PauseAudio(1);
165 }
166 }
167
168 void sdl_audio_callback (void *userdata, Uint8 * stream, int len)
169 {
170 int i;
171 int copy_amt;
172 SDL_memset(stream, mixer.outputSpec.silence, len); /* initialize buffer to silence */
173 /* for each channel, mix in whatever is playing on that channel */
174 for (i = 0; i < NumChannels; i++)
175 {
176 if (mixer.channels[i].position == NULL)
177 {
178 /* if no sound is playing on this channel */
179 continue; /* nothing to do for this channel */
180 }
181
182 /* copy len bytes to the buffer, unless we have fewer than len bytes remaining */
183 copy_amt = mixer.channels[i].remaining < len ? mixer.channels[i].remaining : len;
184
185 /* mix this sound effect with the output */
186 SDL_MixAudioFormat(stream, mixer.channels[i].position, mixer.outputSpec.format, copy_amt, mixer.channels[i].volume);
187
188 /* update buffer position in sound effect and the number of bytes left */
189 mixer.channels[i].position += copy_amt;
190 mixer.channels[i].remaining -= copy_amt;
191
192 /* did we finish playing the sound effect ? */
193 if (mixer.channels[i].remaining == 0)
194 {
195 if (mixer.channels[i].loops != 0)
196 {
197 mixer.channels[i].position = mixer.channels[i].s->buffer.get();
198 mixer.channels[i].remaining = mixer.channels[i].s->length;
199 if (mixer.channels[i].loops != -1) mixer.channels[i].loops--;
200 } else {
201 sdl_stop_channel(i);
202 }
203 }
204 }
205 }
206
207 int sdl_play_sound (sound *s, int loops)
208 {
209 /*
210 find an empty channel to play on.
211 if no channel is available, use oldest channel
212 */
213 int i;
214 int selected_channel = -1;
215 int oldest_channel = 0;
216
217 if (mixer.numSoundsPlaying == 0) {
218 /* we're playing a sound now, so start audio callback back up */
219 SDL_PauseAudio(0);
220 }
221
222 /* find a sound channel to play the sound on */
223 for (i = 0; i < NumChannels; i++) {
224 if (mixer.channels[i].position == NULL) {
225 /* if no sound on this channel, select it */
226 selected_channel = i;
227 break;
228 }
229 /* if this channel's sound is older than the oldest so far, set it to oldest */
230 if (mixer.channels[i].timestamp < mixer.channels[oldest_channel].timestamp)
231 oldest_channel = i;
232 }
233
234 /* no empty channels, take the oldest one */
235 if (selected_channel == -1)
236 selected_channel = oldest_channel;
237 else
238 mixer.numSoundsPlaying++;
239
240 /* point channel data to wav data */
241 mixer.channels[selected_channel].position = s->buffer.get();
242 mixer.channels[selected_channel].remaining = s->length;
243 mixer.channels[selected_channel].timestamp = SDL_GetTicks();
244 mixer.channels[selected_channel].volume = SDL_MIX_MAXVOLUME;
245 mixer.channels[selected_channel].loops = loops;
246 mixer.channels[selected_channel].s = s;
247
248 return selected_channel;
249 }
250
251 #endif
252
253 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
254 typedef std::map<std::string, Mix_Chunk*> cache_map;
255 #else
256 typedef std::map<std::string, sound> cache_map;
257 #endif
258 cache_map cache;
259
260 }
261
262 manager::manager()
263 {
264 if(preferences::no_sound()) {
265 return;
266 }
267
268 if(SDL_WasInit(SDL_INIT_AUDIO) == 0) {
269 if(SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
270 sound_ok = false;
271 std::cerr << "failed to init sound!\n";
272 return;
273 }
274 }
275
276 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
277
278 if(Mix_OpenAudio(SampleRate, MIX_DEFAULT_FORMAT, 2, BufferSize) == -1) {
279 sound_ok = false;
280 std::cerr << "failed to open audio!\n";
281 return;
282 }
283
284 Mix_AllocateChannels(NumChannels);
285 sound_ok = true;
286
287 Mix_ChannelFinished(on_sound_finished);
288 Mix_HookMusicFinished(on_music_finished);
289 Mix_VolumeMusic(MIX_MAX_VOLUME);
290 #else
291 iphone_init_music(on_music_finished);
292 sound_ok = true;
293
294 /* initialize the mixer */
295 SDL_memset(&mixer, 0, sizeof(mixer));
296 /* setup output format */
297 mixer.outputSpec.freq = SampleRate;
298 mixer.outputSpec.format = AUDIO_S16LSB;
299 mixer.outputSpec.channels = 1;
300 mixer.outputSpec.samples = 256;
301 mixer.outputSpec.callback = sdl_audio_callback;
302 mixer.outputSpec.userdata = NULL;
303
304 /* open audio for output */
305 if (SDL_OpenAudio(&mixer.outputSpec, NULL) != 0)
306 {
307 std::cerr << "Opening audio failed\n";
308 sound_ok = false;
309 }
310 #endif
311 }
312
313 manager::~manager()
314 {
315 if(preferences::no_sound()) {
316 return;
317 }
318
319 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
320 Mix_HookMusicFinished(NULL);
321 next_music().clear();
322 Mix_CloseAudio();
323 #else
324 iphone_kill_music();
325 #endif
326 }
327
328 bool ok() { return sound_ok; }
329 bool muted() { return mute_; }
330
331 void mute (bool flag)
332 {
333 mute_ = flag;
334 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
335 Mix_VolumeMusic(MIX_MAX_VOLUME*(!flag));
336 #endif
337 }
338
339 namespace {
340
341 int play_internal(const std::string& file, int loops, const void* object)
342 {
343 if(!sound_ok) {
344 return -1;
345 }
346
347 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
348 Mix_Chunk*& chunk = cache[file];
349 if(chunk == NULL) {
350 chunk = Mix_LoadWAV(("sounds/" + file).c_str());
351 if(chunk == NULL) {
352 return -1;
353 }
354 }
355
356 int result = Mix_PlayChannel(-1, chunk, loops);
357
358 #else
359 sound& s = cache[file];
360 if (s == NULL)
361 {
362 std::string wav_file = file;
363 wav_file.replace(wav_file.length()-3, wav_file.length(), "wav");
364 s = sound("sounds_wav/" + wav_file);
365 if (s == NULL)
366 {
367 return -1;
368 }
369 }
370
371 int result = sdl_play_sound(&s, loops);
372 #endif
373
374 //record which channel the sound is playing on.
375 if(result >= 0) {
376 if(channels_to_sounds_playing.size() <= result) {
377 channels_to_sounds_playing.resize(result + 1);
378 }
379 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
380 Mix_Volume(result, MIX_MAX_VOLUME); //start sound at full volume
381 #endif
382
383 channels_to_sounds_playing[result].file = file;
384 channels_to_sounds_playing[result].object = object;
385 channels_to_sounds_playing[result].loops = loops;
386 }
387
388 return result;
389 }
390
391 }
392
393 void play(const std::string& file, const void* object)
394 {
395 if(preferences::no_sound() || mute_) {
396 return;
397 }
398
399 play_internal(file, 0, object);
400 }
401
402 void stop_sound(const std::string& file, const void* object)
403 {
404 for(int n = 0; n != channels_to_sounds_playing.size(); ++n) {
405 if(channels_to_sounds_playing[n].object == object &&
406 channels_to_sounds_playing[n].file == file) {
407 channels_to_sounds_playing[n].object = NULL;
408 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
409 Mix_HaltChannel(n);
410 #else
411 sdl_stop_channel(n);
412 #endif
413 }
414 }
415 }
416
417 void stop_looped_sounds(const void* object)
418 {
419 for(int n = 0; n != channels_to_sounds_playing.size(); ++n) {
420 if((object == NULL && channels_to_sounds_playing[n].object != NULL
421 || channels_to_sounds_playing[n].object == object) &&
422 (channels_to_sounds_playing[n].loops != 0)) {
423 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
424 Mix_HaltChannel(n);
425 #else
426 sdl_stop_channel(n);
427 #endif
428 channels_to_sounds_playing[n].object = NULL;
429 } else if(channels_to_sounds_playing[n].object == object) {
430 //this sound is a looped sound, but make sure it keeps going
431 //until it ends, since this function signals that the associated
432 //object is going away.
433 channels_to_sounds_playing[n].object = NULL;
434 }
435 }
436 }
437
438 int play_looped(const std::string& file, const void* object)
439 {
440 if(preferences::no_sound() || mute_) {
441 return -1;
442 }
443
444
445 const int result = play_internal(file, -1, object);
446 std::cerr << "PLAY: " << object << " " << file << " -> " << result << "\n";
447 return result;
448 }
449
450 void change_volume(const void* object, int volume)
451 {
452 //Note - range is 0-128 (MIX_MAX_VOLUME). Truncate:
453 if( volume > 128){
454 volume = 128;
455 } else if ( volume < 0 ){
456 volume = 0;
457 }
458
459 //find the channel associated with this object.
460 for(int n = 0; n != channels_to_sounds_playing.size(); ++n) {
461 if(channels_to_sounds_playing[n].object == object) {
462 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
463 Mix_Volume(n, volume);
464 #else
465 mixer.channels[n].volume = volume;
466 #endif
467 } //else, we just do nothing
468 }
469 }
470
471 void cancel_looped(int handle)
472 {
473 if(handle >= 0 && handle < channels_to_sounds_playing.size()) {
474 channels_to_sounds_playing[handle].object = NULL;
475 }
476
477 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
478 Mix_HaltChannel(handle);
479 #else
480 sdl_stop_channel(handle);
481 #endif
482 }
483
484 void play_music(const std::string& file)
485 {
486 if(preferences::no_sound() || preferences::no_music() || !sound_ok) {
487 return;
488 }
489
490 if(file == current_music_name()) {
491 return;
492 }
493
494 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
495 if(current_mix_music) {
496 next_music() = file;
497 Mix_FadeOutMusic(500);
498 return;
499 }
500
501 if(file.empty()) {
502 return;
503 }
504
505 current_music_name() = file;
506 current_mix_music = Mix_LoadMUS(("music/" + file).c_str());
507 if(!current_mix_music) {
508 std::cerr << "Mix_LoadMUS ERROR loading " << file << ": " << Mix_GetError() << "\n";
509 return;
510 }
511
512 Mix_FadeInMusic(current_mix_music, -1, 500);
513 #else
514 if (playing_music)
515 {
516 next_music() = file;
517 iphone_fade_out_music(350);
518 return;
519 }
520
521 if(file.empty()) {
522 return;
523 }
524
525 current_music_name() = file;
526 std::string aac_file = file;
527 aac_file.replace(aac_file.length()-3, aac_file.length(), "m4a");
528 if (!sys::file_exists("music_aac/" + aac_file)) return;
529 iphone_play_music(("music_aac/" + aac_file).c_str(), -1);
530 iphone_fade_in_music(350);
531 playing_music = true;
532 #endif
533 }
534
535 void play_music_interrupt(const std::string& file)
536 {
537 if(preferences::no_sound() || preferences::no_music()) {
538 return;
539 }
540
541 if(next_music().empty() == false) {
542 current_music_name() = next_music();
543 next_music().clear();
544 }
545
546 next_music() = current_music_name();
547
548 current_music_name() = file;
549
550 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
551
552 //note that calling HaltMusic will result in on_music_finished being
553 //called, which releases the current_music pointer.
554 Mix_HaltMusic();
555 if(file.empty()) {
556 return;
557 }
558
559 current_mix_music = Mix_LoadMUS(("music/" + file).c_str());
560 if(!current_mix_music) {
561 std::cerr << "Mix_LoadMUS ERROR loading " << file << ": " << Mix_GetError() << "\n";
562 return;
563 }
564
565 Mix_PlayMusic(current_mix_music, 1);
566 #else
567 std::string aac_file = file;
568 aac_file.replace(aac_file.length()-3, aac_file.length(), "m4a");
569 iphone_play_music(("music_aac/" + aac_file).c_str(), 0);
570 playing_music = true;
571 #endif
572 }
573
574 const std::string& current_music() {
575 return current_music_name();
576 }
577
578 }
0 #ifndef SOUND_HPP_INCLUDED
1 #define SOUND_HPP_INCLUDED
2
3 #include <string>
4
5 namespace sound {
6
7 struct manager {
8 manager();
9 ~manager();
10 };
11
12 bool ok();
13 bool muted();
14 void mute(bool flag);
15
16 //play a sound. 'object' is the object that is playing the sound. It can be
17 //used later in stop_sound to specify which object is stopping playing
18 //the sound.
19 void play(const std::string& file, const void* object=0);
20
21 //stop a sound. object refers to the object that started the sound, and is
22 //the same as the object in play().
23 void stop_sound(const std::string& file, const void* object=0);
24
25 //stop all looped sounds associated with an object; same object as in play()
26 //intended to be called in all object's destructors
27 void stop_looped_sounds(const void* object=0);
28 void change_volume(const void* object=0, int volume=-1);
29
30
31 // function to play a sound effect over and over in a loop. Will return
32 // a handle to the sound effect. Will keep playing until cancel_looped()
33 // is called with the handle.
34 int play_looped(const std::string& file, const void* object=0);
35 void cancel_looped(int handle);
36
37 void play_music(const std::string& file);
38 void play_music_interrupt(const std::string& file);
39
40 const std::string& current_music();
41
42 }
43
44 #endif
0 #include <iostream>
1 #include <stack>
2 #include <limits.h>
3
4 #include "color_utils.hpp"
5 #include "draw_scene.hpp"
6 #include "foreach.hpp"
7 #include "frame.hpp"
8 #include "framed_gui_element.hpp"
9 #include "graphical_font.hpp"
10 #include "gui_section.hpp"
11 #include "joystick.hpp"
12 #include "preferences.hpp"
13 #include "raster.hpp"
14 #include "speech_dialog.hpp"
15
16 namespace {
17 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
18 const int OptionHeight = 70;
19 const int OptionWidth = 200;
20 const int OptionXPad = 20;
21 #else
22 const int OptionHeight = 45;
23 const int OptionWidth = 150;
24 const int OptionXPad = 10;
25 #endif
26 const int OptionsBorder = 20; // size of the border around the options window
27 const int OptionsX = 135; // these denote the bottom right corner
28 const int OptionsY = 115;
29 }
30
31 speech_dialog::speech_dialog()
32 : cycle_(0), left_side_speaking_(false), horizontal_position_(0), text_char_(0), option_selected_(0),
33 joystick_button_pressed_(true),
34 joystick_up_pressed_(true),
35 joystick_down_pressed_(true),
36 expiration_(-1)
37 {
38 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
39 option_selected_ = -1;
40 #endif
41 }
42
43 speech_dialog::~speech_dialog()
44 {
45 }
46
47 bool speech_dialog::handle_mouse_move(int x, int y)
48 {
49 if(preferences::screen_rotated()) {
50 x = preferences::actual_screen_width() - x;
51 std::swap(x, y);
52 x *= 2;
53 y *= 2;
54 }
55 rect box(
56 preferences::virtual_screen_width() - OptionsX - OptionWidth - OptionsBorder,
57 preferences::virtual_screen_height() - OptionsY - OptionHeight*options_.size() - OptionsBorder,
58 OptionWidth + OptionsBorder*2, OptionHeight*options_.size() + OptionsBorder*2
59 );
60 //std::cerr << "Options box: " << box << " : " << x << " : " << y << "\n";
61 if (point_in_rect(point(x, y), box))
62 {
63 option_selected_ = (y-box.y())/OptionHeight;
64 return true;
65 } else {
66 option_selected_ = -1;
67 return false;
68 }
69 }
70
71 void speech_dialog::move_up()
72 {
73 --option_selected_;
74 if(option_selected_ < 0) {
75 option_selected_ = options_.size() - 1;
76 }
77 }
78
79 void speech_dialog::move_down()
80 {
81 ++option_selected_;
82 if(option_selected_ == options_.size()) {
83 option_selected_ = 0;
84 }
85 }
86
87 bool speech_dialog::key_press(const SDL_Event& event)
88 {
89 static int last_mouse = 0;
90 if(text_char_ == num_chars() && options_.empty() == false) {
91 if(event.type == SDL_KEYDOWN)
92 {
93 switch(event.key.keysym.sym) {
94 case SDLK_UP:
95 move_up();
96 break;
97 case SDLK_DOWN:
98 move_down();
99 break;
100 case SDLK_RETURN:
101 case SDLK_SPACE:
102 case SDLK_a:
103 case SDLK_s:
104 return true;
105 default:
106 break;
107 }
108 }
109
110 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
111 if(event.type == SDL_MOUSEBUTTONDOWN)
112 {
113 last_mouse = event.button.which;
114 handle_mouse_move(event.button.x, event.button.y);
115 }
116 if (event.type == SDL_MOUSEMOTION)
117 {
118 if (event.motion.which == last_mouse)
119 handle_mouse_move(event.motion.x, event.motion.y);
120 }
121 if (event.type == SDL_MOUSEBUTTONUP)
122 {
123 if (event.motion.which == last_mouse)
124 {
125 last_mouse = -1;
126 return handle_mouse_move(event.motion.x, event.motion.y);
127 }
128 }
129 #endif
130
131 return false;
132 } else if (event.type != SDL_KEYDOWN && event.type != SDL_MOUSEBUTTONDOWN) {
133 return false; // only keydown and mousebuttondown should be handled by the rest of the function
134 }
135
136 if(text_char_ < num_chars()) {
137 text_char_ = num_chars();
138 return false;
139 }
140
141 if(text_.size() > 2) {
142 text_.erase(text_.begin());
143 text_char_ = text_.front().size();
144 return false;
145 }
146
147 return true;
148 }
149
150 bool speech_dialog::process()
151 {
152 ++cycle_;
153
154 if(text_char_ < num_chars()) {
155 ++text_char_;
156 }
157
158 const int ScrollSpeed = 20;
159 if(left_side_speaking_) {
160 if(horizontal_position_ > 0) {
161 horizontal_position_ -= ScrollSpeed;
162 if(horizontal_position_ < 0) {
163 horizontal_position_ = 0;
164 }
165 }
166 } else {
167 const int width = gui_section::get("speech_portrait_pane")->width();
168 if(horizontal_position_ < width) {
169 horizontal_position_ += ScrollSpeed;
170 if(horizontal_position_ > width) {
171 horizontal_position_ = width;
172 }
173 }
174 }
175
176 if(expiration_ <= 0) {
177 joystick::update();
178
179 if(!joystick_up_pressed_ && joystick::up()) {
180 move_up();
181 }
182
183 if(!joystick_down_pressed_ && joystick::down()) {
184 move_down();
185 }
186
187 if(!joystick_button_pressed_ && (joystick::button(0) || joystick::button(10))) {
188 return true;
189 }
190 }
191
192 joystick_up_pressed_ = joystick::up();
193 joystick_down_pressed_ = joystick::down();
194 joystick_button_pressed_ = joystick::button(0) || joystick::button(1);
195
196 return cycle_ == expiration_;
197 }
198
199 void speech_dialog::draw() const
200 {
201 static const const_gui_section_ptr top_corner = gui_section::get("speech_dialog_top_corner");
202 static const const_gui_section_ptr bottom_corner = gui_section::get("speech_dialog_bottom_corner");
203 static const const_gui_section_ptr top_edge = gui_section::get("speech_dialog_top_edge");
204 static const const_gui_section_ptr bottom_edge = gui_section::get("speech_dialog_bottom_edge");
205 static const const_gui_section_ptr side_edge = gui_section::get("speech_dialog_side_edge");
206 static const const_gui_section_ptr arrow = gui_section::get("speech_dialog_arrow");
207
208 const_graphical_font_ptr font = graphical_font::get("default");
209
210 const int TextAreaHeight = 80;
211
212 const int TextBorder = 10;
213
214 int speaker_xpos = INT_MAX;
215 int speaker_ypos = INT_MAX;
216
217 const_entity_ptr speaker = left_side_speaking_ ? left_ : right_;
218 if(speaker) {
219 const screen_position& pos = last_draw_position();
220 const int screen_x = pos.x/100 + (graphics::screen_width()/2)*(-1.0/pos.zoom + 1.0);
221 const int screen_y = pos.y/100 + (graphics::screen_height()/2)*(-1.0/pos.zoom + 1.0);
222
223 speaker_xpos = (speaker->feet_x() - screen_x)*pos.zoom - 36;
224 speaker_ypos = (speaker->feet_y() - screen_y)*pos.zoom - 10;
225 }
226
227 if(pane_area_.w() == 0) {
228 pane_area_ = rect(
229 top_corner->width(),
230 preferences::virtual_screen_height() - TextAreaHeight + TextBorder,
231 preferences::virtual_screen_width() - top_corner->width()*2,
232 TextAreaHeight - bottom_corner->height());
233 if(speaker_ypos < 100) {
234 pane_area_ = rect(pane_area_.x(), top_corner->height() + 50, pane_area_.w(), pane_area_.h());
235 }
236 }
237
238 const rect text_area(pane_area_.x()-30, pane_area_.y()-30, pane_area_.w()+60, pane_area_.h()+60);
239
240 graphics::draw_rect(pane_area_, graphics::color(85, 53, 53, 255));
241 top_corner->blit(pane_area_.x() - top_corner->width(), pane_area_.y() - top_corner->height());
242 top_corner->blit(pane_area_.x2()-1, pane_area_.y() - top_corner->height(), -top_corner->width(), top_corner->height());
243
244 top_edge->blit(pane_area_.x(), pane_area_.y() - top_edge->height(), pane_area_.w(), top_edge->height());
245
246 bottom_corner->blit(pane_area_.x() - bottom_corner->width(), pane_area_.y2());
247 bottom_corner->blit(pane_area_.x2()-1, pane_area_.y2(), -bottom_corner->width(), bottom_corner->height());
248
249 bottom_edge->blit(pane_area_.x(), pane_area_.y2(), pane_area_.w(), bottom_edge->height());
250
251 side_edge->blit(pane_area_.x() - side_edge->width(), pane_area_.y(), side_edge->width(), pane_area_.h());
252 side_edge->blit(pane_area_.x2()-1, pane_area_.y(), -side_edge->width(), pane_area_.h());
253
254 if(speaker) {
255
256 //if the arrow to the speaker is within reasonable limits, then
257 //blit it.
258 if(speaker_xpos > top_corner->width() && speaker_xpos < graphics::screen_width() - top_corner->width() - arrow->width()) {
259 arrow->blit(speaker_xpos, pane_area_.y() - arrow->height() - 32);
260 }
261 }
262
263
264 //we center our text. Create a vector of the left edge of the text.
265 std::vector<int> text_left_align;
266
267 int total_height = 0;
268 for(int n = 0; n < text_.size(); ++n) {
269 rect area = font->dimensions(text_[n]);
270
271 if(n < 2) {
272 total_height += area.h();
273 }
274
275 const int width = area.w();
276 const int left = text_area.x() + text_area.w()/2 - width/2;
277 text_left_align.push_back(left);
278 }
279
280 int ypos = text_area.y() + (text_area.h() - total_height)/2;
281 int nchars = text_char_;
282 for(int n = 0; n < 2 && n < text_.size() && nchars > 0; ++n) {
283 std::string str(text_[n].begin(), text_[n].begin() +
284 std::min<int>(nchars, text_[n].size()));
285 //currently the color of speech is hard coded.
286 glColor4ub(255, 187, 10, 255);
287 rect area = font->draw(text_left_align[n], ypos, str);
288 glColor4f(1.0, 1.0, 1.0, 1.0);
289 ypos = area.y2();
290 nchars -= text_[n].size();
291 }
292
293 if(text_.size() > 2 && text_char_ == num_chars() && (cycle_&16)) {
294 const_gui_section_ptr down_arrow = gui_section::get("speech_text_down_arrow");
295 down_arrow->blit(text_area.x2() - down_arrow->width() - 10, text_area.y2() - down_arrow->height() - 10);
296
297 }
298
299 if(text_char_ == num_chars() && options_.empty() == false) {
300 //const_gui_section_ptr options_panel = gui_section::get("speech_portrait_pane");
301 const_framed_gui_element_ptr options_panel = framed_gui_element::get("regular_window");
302 int xpos = graphics::screen_width() - OptionsX - OptionWidth - OptionsBorder*2;
303 int ypos = graphics::screen_height() - OptionsY - OptionHeight*options_.size() - OptionsBorder*2;
304 // the division by 2 (and lack of multiplication of OptionsBorder) here is
305 // because the last options specifies that it will multiply everything by 2
306 options_panel->blit(xpos, ypos, OptionsBorder + OptionWidth/2, OptionsBorder + (OptionHeight * options_.size())/2, 2);
307
308 xpos += OptionsBorder + OptionXPad;
309 ypos += OptionsBorder;
310
311 glColor4ub(255, 187, 10, 255);
312 int index = 0;
313 foreach(const std::string& option, options_) {
314 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
315 if(index == option_selected_) {
316 graphics::draw_rect(rect(xpos-OptionXPad, ypos, OptionWidth, OptionHeight), graphics::color(0xC74545FF));
317 glColor4ub(255, 187, 10, 255); //reset color to what it was, since draw_rect changes it
318 }
319 #endif
320 rect area = font->dimensions(option);
321 area = font->draw(xpos, ypos+(OptionHeight/2-area.h()/4), option);
322
323 #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE
324 if(index == option_selected_) {
325 const_gui_section_ptr cursor = gui_section::get("cursor");
326 cursor->blit(area.x2(), area.y());
327 }
328 #endif
329
330 ypos += OptionHeight;
331 ++index;
332 }
333 glColor4f(1.0, 1.0, 1.0, 1.0);
334 }
335 }
336
337 void speech_dialog::set_speaker_and_flip_side(const_entity_ptr e)
338 {
339 std::cerr << "set speaker\n";
340 left_side_speaking_ = !left_side_speaking_;
341 set_speaker(e, left_side_speaking_);
342 }
343
344 void speech_dialog::set_speaker(const_entity_ptr e, bool left_side)
345 {
346 if(left_side) {
347 left_ = e;
348 } else {
349 right_ = e;
350 }
351
352 pane_area_ = rect();
353 }
354
355 void speech_dialog::set_side(bool left_side)
356 {
357 left_side_speaking_ = left_side;
358 }
359
360 void speech_dialog::set_text(const std::vector<std::string>& text)
361 {
362 text_ = text;
363 text_char_ = 0;
364 }
365
366 void speech_dialog::set_options(const std::vector<std::string>& options)
367 {
368 options_ = options;
369 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
370 option_selected_ = -1;
371 #else
372 option_selected_ = 0;
373 #endif
374 }
375
376 int speech_dialog::num_chars() const
377 {
378 int res = 0;
379 if(text_.size() >= 1) {
380 res += text_[0].size();
381 }
382
383 if(text_.size() >= 2) {
384 res += text_[1].size();
385 }
386
387 return res;
388 }
0 #ifndef SPEECH_DIALOG_HPP_INCLUDED
1 #define SPEECH_DIALOG_HPP_INCLUDED
2
3 #include "SDL.h"
4
5 #include <string>
6 #include <vector>
7
8 #include "entity.hpp"
9 #include "gui_section.hpp"
10
11 class speech_dialog
12 {
13 public:
14
15 speech_dialog();
16 ~speech_dialog();
17
18 bool key_press(const SDL_Event& e);
19 bool process();
20 void draw() const;
21 void set_speaker_and_flip_side(const_entity_ptr e);
22 void set_speaker(const_entity_ptr e, bool left_side=false);
23 void set_side(bool left_side);
24 void set_text(const std::vector<std::string>& text);
25 void set_options(const std::vector<std::string>& options);
26 void set_expiration(int time) { expiration_ = time; }
27
28 int option_selected() const { return option_selected_; }
29 private:
30 bool handle_mouse_move(int x, int y);
31 void move_up();
32 void move_down();
33
34 int cycle_;
35 const_entity_ptr left_, right_;
36 bool left_side_speaking_;
37 int horizontal_position_;
38
39 std::vector<std::string> text_;
40 int text_char_;
41
42 std::vector<std::string> options_;
43 int option_selected_;
44
45 bool joystick_button_pressed_, joystick_up_pressed_, joystick_down_pressed_;
46
47 int expiration_;
48
49 mutable rect pane_area_;
50
51 int num_chars() const;
52
53 speech_dialog(const speech_dialog&);
54 void operator=(const speech_dialog&);
55 };
56
57 #endif
0 #include <GL/gl.h>
1
2 #include <map>
3 #include <sstream>
4 #include <stdio.h>
5 #include <vector>
6
7 #include <boost/array.hpp>
8 #include <boost/asio.hpp>
9
10 #include "filesystem.hpp"
11 #include "formatter.hpp"
12 #include "preferences.hpp"
13 #include "stats.hpp"
14 #include "wml_node.hpp"
15 #include "wml_utils.hpp"
16 #include "wml_writer.hpp"
17
18 namespace {
19 std::string get_stats_dir() {
20 return sys::get_dir(std::string(preferences::user_data_path()) + "stats/") + "/";
21 }
22
23 }
24
25 void http_upload(const std::string& payload, const std::string& script) {
26 using boost::asio::ip::tcp;
27
28 std::ostringstream s;
29 std::string header =
30 "POST /cgi-bin/" + script + " HTTP/1.1\n"
31 "Host: www.wesnoth.org\n"
32 "User-Agent: Frogatto 0.1\n"
33 "Content-Type: text/plain\n";
34 s << header << "Content-length: " << payload.size() << "\n\n" << payload;
35 std::string msg = s.str();
36
37 boost::asio::io_service io_service;
38 tcp::resolver resolver(io_service);
39 tcp::resolver::query query("www.wesnoth.org", "80");
40
41 tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
42 tcp::resolver::iterator end;
43
44 tcp::socket socket(io_service);
45 boost::system::error_code error = boost::asio::error::host_not_found;
46 while(error && endpoint_iterator != end) {
47 socket.close();
48 socket.connect(*endpoint_iterator++, error);
49 }
50
51 if(error) {
52 fprintf(stderr, "STATS ERROR: Can't resolve stats upload\n");
53 return;
54 }
55
56 socket.write_some(boost::asio::buffer(msg), error);
57 if(error) {
58 fprintf(stderr, "STATS ERROR: Couldn't upload stats buffer\n");
59 return;
60 }
61 }
62
63 namespace stats {
64
65 namespace {
66 std::map<std::string, std::vector<const_record_ptr> > write_queue;
67
68 threading::mutex& write_queue_mutex() {
69 static threading::mutex m;
70 return m;
71 }
72
73 threading::condition& send_stats_signal() {
74 static threading::condition c;
75 return c;
76 }
77
78 bool send_stats_done = false;
79
80 void send_stats(const std::map<std::string, std::vector<const_record_ptr> >& queue) {
81 if(queue.empty()) {
82 return;
83 }
84
85 wml::node_ptr msg(new wml::node("stats"));
86 for(std::map<std::string, std::vector<const_record_ptr> >::const_iterator i = queue.begin(); i != queue.end(); ++i) {
87 std::string commands;
88 wml::node_ptr cmd(new wml::node("level"));
89 cmd->set_attr("id", i->first);
90 for(std::vector<const_record_ptr>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
91 wml::node_ptr node((*j)->write());
92 cmd->add_child(node);
93 wml::write(node, commands);
94 }
95
96 msg->add_child(cmd);
97
98 const std::string fname = get_stats_dir() + i->first;
99 if(sys::file_exists(fname)) {
100 commands = sys::read_file(fname) + commands;
101 }
102
103 sys::write_file(fname, commands);
104 }
105
106 std::string msg_str;
107 wml::write(msg, msg_str);
108 try {
109 http_upload(msg_str, "upload-frogatto");
110 } catch(...) {
111 fprintf(stderr, "STATS ERROR: ERROR PERFORMING HTTP UPLOAD!\n");
112 }
113 }
114
115 void send_stats_thread() {
116 if(preferences::send_stats() == false) {
117 return;
118 }
119
120 for(;;) {
121 std::map<std::string, std::vector<const_record_ptr> > queue;
122 {
123 threading::lock lck(write_queue_mutex());
124 if(!send_stats_done && write_queue.empty()) {
125 send_stats_signal().wait_timeout(write_queue_mutex(), 60000);
126 }
127
128 if(send_stats_done && write_queue.empty()) {
129 break;
130 }
131
132 queue.swap(write_queue);
133 }
134
135 send_stats(queue);
136 }
137 }
138
139 }
140
141 bool download(const std::string& lvl) {
142 try {
143 using boost::asio::ip::tcp;
144
145 boost::asio::io_service io_service;
146 tcp::resolver resolver(io_service);
147 tcp::resolver::query query("www.wesnoth.org", "80");
148
149 tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
150 tcp::resolver::iterator end;
151
152 tcp::socket socket(io_service);
153 boost::system::error_code error = boost::asio::error::host_not_found;
154 while(error && endpoint_iterator != end) {
155 socket.close();
156 socket.connect(*endpoint_iterator++, error);
157 }
158
159 if(error) {
160 fprintf(stderr, "STATS ERROR: Can't resolve stats download\n");
161 return false;
162 }
163
164 std::string query_str =
165 "GET /files/dave/frogatto-stats/" + lvl + " HTTP/1.1\n"
166 "Host: www.wesnoth.org\n"
167 "Connection: close\n\n";
168 socket.write_some(boost::asio::buffer(query_str), error);
169 if(error) {
170 fprintf(stderr, "STATS ERROR: Error sending HTTP request\n");
171 return false;
172 }
173
174 std::string payload;
175
176 size_t nbytes;
177 boost::array<char, 256> buf;
178 while(!error && (nbytes = socket.read_some(boost::asio::buffer(buf), error)) > 0) {
179 payload.insert(payload.end(), buf.begin(), buf.begin() + nbytes);
180 }
181
182 if(error != boost::asio::error::eof) {
183 fprintf(stderr, "STATS ERROR: ERROR READING HTTP\n");
184 return false;
185 }
186
187 fprintf(stderr, "REQUEST: {{{%s}}}\n\nRESPONSE: {{{%s}}}\n", query_str.c_str(), payload.c_str());
188
189 const std::string expected_response = "HTTP/1.1 200 OK";
190 if(payload.size() < expected_response.size() || std::equal(expected_response.begin(), expected_response.end(), payload.begin()) == false) {
191 fprintf(stderr, "STATS ERROR: BAD HTTP RESPONSE\n");
192 return false;
193 }
194
195 const std::string length_str = "Content-Length: ";
196 const char* length_ptr = strstr(payload.c_str(), length_str.c_str());
197 if(!length_ptr) {
198 fprintf(stderr, "STATS ERROR: LENGTH NOT FOUND IN HTTP RESPONSE\n");
199 return false;
200 }
201
202 length_ptr += length_str.size();
203
204 const int len = atoi(length_ptr);
205 if(len <= 0 || payload.size() <= len) {
206 fprintf(stderr, "STATS ERROR: BAD LENGTH IN HTTP RESPONSE\n");
207 return false;
208 }
209
210 std::string stats_wml = std::string(payload.end() - len, payload.end());
211
212 sys::write_file(get_stats_dir() + lvl, stats_wml);
213 return true;
214 } catch(...) {
215 fprintf(stderr, "STATS ERROR: ERROR PERFORMING STATS DOWNLOAD\n");
216 return false;
217 }
218 }
219
220 manager::manager()
221 #if !TARGET_OS_IPHONE
222 : background_thread_(send_stats_thread)
223 #endif
224 {}
225
226 manager::~manager() {
227 threading::lock lck(write_queue_mutex());
228 send_stats_done = true;
229 send_stats_signal().notify_one();
230 }
231
232 record_ptr record::read(wml::const_node_ptr node) {
233 if(node->name() == "die") {
234 return record_ptr(new die_record(point(node->attr("pos"))));
235 } else if(node->name() == "quit") {
236 return record_ptr(new quit_record(point(node->attr("pos"))));
237 } else if(node->name() == "move") {
238 return record_ptr(new player_move_record(point(node->attr("src")), point(node->attr("dst"))));
239 } else if(node->name() == "load") {
240 return record_ptr(new load_level_record(wml::get_int(node, "ms")));
241 } else {
242 fprintf(stderr, "UNRECOGNIZED STATS NODE: '%s'\n", node->name().c_str());
243 return record_ptr();
244 }
245 }
246
247 record::~record() {
248 }
249
250 die_record::die_record(const point& p) : p_(p)
251 {}
252
253 wml::node_ptr die_record::write() const
254 {
255 wml::node_ptr result(new wml::node("die"));
256 result->set_attr("pos", p_.to_string());
257 return result;
258 }
259
260 void die_record::draw() const
261 {
262 glPointSize(5);
263 glDisable(GL_TEXTURE_2D);
264 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
265 glColor4ub(255, 0, 0, 255);
266 GLfloat point[] = {p_.x, p_.y};
267 glVertexPointer(2, GL_FLOAT, 0, point);
268 glDrawArrays(GL_POINTS, 0, 1);
269 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
270 glEnable(GL_TEXTURE_2D);
271 glColor4ub(255, 255, 255, 255);
272 }
273
274 quit_record::quit_record(const point& p) : p_(p)
275 {}
276
277 wml::node_ptr quit_record::write() const
278 {
279 wml::node_ptr result(new wml::node("quit"));
280 result->set_attr("pos", p_.to_string());
281 return result;
282 }
283
284 void quit_record::draw() const
285 {
286 glPointSize(5);
287 glDisable(GL_TEXTURE_2D);
288 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
289 glColor4ub(255, 255, 0, 255);
290 GLfloat point[] = {p_.x, p_.y};
291 glVertexPointer(2, GL_FLOAT, 0, point);
292 glDrawArrays(GL_POINTS, 0, 1);
293 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
294 glEnable(GL_TEXTURE_2D);
295 glColor4ub(255, 255, 255, 255);
296 }
297
298 player_move_record::player_move_record(const point& src, const point& dst) : src_(src), dst_(dst)
299 {}
300
301 wml::node_ptr player_move_record::write() const
302 {
303 wml::node_ptr result(new wml::node("move"));
304 result->set_attr("src", src_.to_string());
305 result->set_attr("dst", dst_.to_string());
306 return result;
307 }
308
309 void player_move_record::draw() const
310 {
311 glDisable(GL_TEXTURE_2D);
312 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
313 glColor4ub(0, 0, 255, 128);
314 GLfloat varray[] = {src_.x, src_.y, dst_.x, dst_.y};
315 glVertexPointer(2, GL_FLOAT, 0, varray);
316 glDrawArrays(GL_LINES, 0, 4);
317 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
318 glEnable(GL_TEXTURE_2D);
319 glColor4ub(255, 255, 255, 255);
320 }
321
322 load_level_record::load_level_record(int ms) : ms_(ms)
323 {}
324
325 wml::node_ptr load_level_record::write() const
326 {
327 wml::node_ptr result(new wml::node("load"));
328 result->set_attr("ms", formatter() << ms_);
329 return result;
330 }
331
332 void record_event(const std::string& lvl, const_record_ptr r)
333 {
334 threading::lock lck(write_queue_mutex());
335 write_queue[lvl].push_back(r);
336 }
337
338 void flush()
339 {
340 send_stats_signal().notify_one();
341 }
342
343 }
0 #ifndef STATS_HPP_INCLUDED
1 #define STATS_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 #include "geometry.hpp"
6 #include "thread.hpp"
7 #include "wml_node_fwd.hpp"
8
9 #include <string>
10
11 void http_upload(const std::string& payload, const std::string& script);
12
13 namespace stats {
14
15 //download stats for a given level.
16 bool download(const std::string& lvl);
17
18 class manager {
19 public:
20 manager();
21 ~manager();
22 private:
23 //currently the stats thread (and thus stats) are disabled, due to
24 //causing crashes on the iPhone. Need to investigate.
25 #if !TARGET_OS_IPHONE
26 threading::thread background_thread_;
27 #endif
28 };
29
30 class record;
31
32 typedef boost::shared_ptr<record> record_ptr;
33 typedef boost::shared_ptr<const record> const_record_ptr;
34
35 class record {
36 public:
37 static record_ptr read(wml::const_node_ptr node);
38 virtual ~record();
39 virtual const char* id() const = 0;
40 virtual wml::node_ptr write() const = 0;
41 virtual void draw() const {}
42 virtual point location() const = 0;
43 };
44
45 class die_record : public record {
46 public:
47 explicit die_record(const point& p);
48 wml::node_ptr write() const;
49 void draw() const;
50 const char* id() const { return "die"; }
51 point location() const { return p_; }
52 private:
53 point p_;
54 };
55
56 class quit_record : public record {
57 public:
58 explicit quit_record(const point& p);
59 wml::node_ptr write() const;
60 void draw() const;
61 const char* id() const { return "quit"; }
62 point location() const { return p_; }
63 private:
64 point p_;
65 };
66
67 class load_level_record : public record {
68 public:
69 explicit load_level_record(int ms);
70 wml::node_ptr write() const;
71 const char* id() const { return "load"; }
72 point location() const { return point(0,0); }
73 private:
74 int ms_;
75 };
76
77 class player_move_record : public record {
78 public:
79 player_move_record(const point& src, const point& dst);
80 wml::node_ptr write() const;
81 void draw() const;
82 const char* id() const { return "move"; }
83 point location() const { return src_; }
84 private:
85 point src_, dst_;
86 };
87
88 void record_event(const std::string& lvl, const_record_ptr r);
89 void flush();
90
91 }
92
93 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include "string_utils.hpp"
13 #include "unit_test.hpp"
14
15 #include <algorithm>
16 #include <cctype>
17 #include <stdio.h>
18
19 namespace util
20 {
21
22 bool isnewline(char c)
23 {
24 return c == '\r' || c == '\n';
25 }
26
27 bool portable_isspace(char c)
28 {
29 return isnewline(c) || isspace(c);
30 }
31
32 bool notspace(char c)
33 {
34 return !portable_isspace(c);
35 }
36
37 std::string &strip(std::string &str)
38 {
39 std::string::iterator it = std::find_if(str.begin(), str.end(), notspace);
40 str.erase(str.begin(), it);
41 str.erase(std::find_if(str.rbegin(), str.rend(), notspace).base(), str.end());
42
43 return str;
44 }
45
46 std::vector<std::string> split(std::string const &val, const std::string& delim)
47 {
48 /* this might be slow but its very convenient so long as you
49 aren't calling it too often */
50
51 std::vector< std::string > res;
52 std::string::const_iterator i1 = val.begin();
53 std::string::const_iterator i2 = val.begin();
54
55 while (i2 != val.end()) {
56 if(delim.find(*i2) != std::string::npos) {
57 std::string new_val(i1, i2);
58 res.push_back(new_val);
59 while(delim.find(*(++i2)) != std::string::npos) {}
60 i1 = i2;
61 }
62 ++i2;
63 }
64 std::string new_val(i1,i2);
65 if(!new_val.empty()) {
66 res.push_back(new_val);
67 }
68 return res;
69 }
70
71 std::vector<std::string> split(std::string const &val, char c, int flags)
72 {
73 std::vector<std::string> res;
74 split(val, res, c, flags);
75 return res;
76 }
77
78 void split(std::string const &val, std::vector<std::string>& res, char c, int flags)
79 {
80 std::string::const_iterator i1 = val.begin();
81 std::string::const_iterator i2 = val.begin();
82
83 while (i2 != val.end()) {
84 if (*i2 == c) {
85 std::string new_val(i1, i2);
86 if (flags & STRIP_SPACES)
87 strip(new_val);
88 if (!(flags & REMOVE_EMPTY) || !new_val.empty())
89 res.push_back(new_val);
90 ++i2;
91 if (flags & STRIP_SPACES) {
92 while (i2 != val.end() && *i2 == ' ')
93 ++i2;
94 }
95
96 i1 = i2;
97 } else {
98 ++i2;
99 }
100 }
101
102 std::string new_val(i1, i2);
103 if (flags & STRIP_SPACES)
104 strip(new_val);
105 if (!(flags & REMOVE_EMPTY) || !new_val.empty())
106 res.push_back(new_val);
107 }
108
109 std::string join(const std::vector<std::string>& v, char j)
110 {
111 std::string res;
112 for(int n = 0; n != v.size(); ++n) {
113 if(n != 0) {
114 res.push_back(j);
115 }
116
117 res += v[n];
118 }
119
120 return res;
121 }
122
123 const char* split_into_ints(const char* s, int* output, int* output_size)
124 {
125 char* endptr = NULL;
126 int index = 0;
127 for(;;) {
128 int result = strtol(s, &endptr, 10);
129 if(endptr == s) {
130 break;
131 }
132
133 if(index < *output_size) {
134 output[index] = result;
135 }
136
137 ++index;
138
139 if(*endptr != ',') {
140 break;
141 }
142
143 s = endptr+1;
144 }
145
146 *output_size = index;
147 return endptr;
148 }
149
150 std::string join_ints(const int* ints, int size)
151 {
152 std::string result;
153 char buf[256];
154 for(int n = 0; n != size; ++n) {
155 if(n != 0) {
156 result += ",";
157 }
158
159 sprintf(buf, "%d", ints[n]);
160 result += buf;
161 }
162
163 return result;
164 }
165
166 bool string_starts_with(const std::string& target, const std::string& prefix) {
167 if(target.length() < prefix.length()) {
168 return false;
169 }
170 std::string target_pfx = target.substr(0,prefix.length());
171 return target_pfx == prefix;
172 }
173
174 std::string strip_string_prefix(const std::string& target, const std::string& prefix) {
175 if(target.length() < prefix.length()) {
176 return "";
177 }
178 return target.substr(prefix.length());
179 }
180
181 }
182
183 UNIT_TEST(test_split_into_ints)
184 {
185 int buf[6];
186 int buf_size = 6;
187 const char* str = "4,18,7,245";
188 const char* res = util::split_into_ints(str, buf, &buf_size);
189 CHECK_EQ(buf_size, 4);
190 CHECK_EQ(res, str + strlen(str));
191 CHECK_EQ(buf[0], 4);
192 CHECK_EQ(buf[1], 18);
193 CHECK_EQ(buf[2], 7);
194 CHECK_EQ(buf[3], 245);
195
196 buf[1] = 0;
197 buf_size = 1;
198 res = util::split_into_ints(str, buf, &buf_size);
199 CHECK_EQ(buf_size, 4);
200 CHECK_EQ(res, str + strlen(str));
201 CHECK_EQ(buf[0], 4);
202 CHECK_EQ(buf[1], 0);
203 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef STRING_UTILS_HPP_INCLUDED
13 #define STRING_UTILS_HPP_INCLUDED
14
15 #include <string>
16 #include <vector>
17
18 namespace util
19 {
20
21 bool isnewline(char c);
22 bool portable_isspace(char c);
23 bool notspace(char c);
24
25 std::string& strip(std::string& str);
26
27 enum { REMOVE_EMPTY = 0x01, STRIP_SPACES = 0x02 };
28 std::vector<std::string> split(std::string const &val, char c = ',', int flags = REMOVE_EMPTY | STRIP_SPACES);
29 void split(std::string const &val, std::vector<std::string>& res, char c = ',', int flags = REMOVE_EMPTY | STRIP_SPACES);
30 std::vector<std::string> split(std::string const &val, std::string const &delim);
31 std::string join(const std::vector<std::string>& v, char c=',');
32
33 //splits the string 's' into ints, storing the output in 'output'. s
34 //should point to a comma-separated list of integers. output_size should point
35 //to the size of 'output'. The number of ints found will be stored in
36 //output_size.
37 const char* split_into_ints(const char* s, int* output, int* output_size);
38
39 std::string join_ints(const int* buf, int size);
40
41 bool string_starts_with(const std::string& target, const std::string& prefix);
42 std::string strip_string_prefix(const std::string& target, const std::string& prefix);
43 }
44
45 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include "surface.hpp"
13
14 namespace graphics
15 {
16
17 namespace {
18
19 SDL_PixelFormat& get_neutral_pixel_format()
20 {
21 static bool first_time = true;
22 static SDL_PixelFormat format;
23
24 if(first_time) {
25 first_time = false;
26 surface surf(SDL_CreateRGBSurface(SDL_SWSURFACE,1,1,32,SURFACE_MASK));
27 format = *surf->format;
28 format.palette = NULL;
29 }
30
31 return format;
32 }
33
34 }
35
36 surface surface::convert_opengl_format() const
37 {
38 return clone();
39 }
40
41 surface surface::clone() const
42 {
43 return SDL_ConvertSurface(get(),&get_neutral_pixel_format(),
44 SDL_SWSURFACE);
45 }
46
47 surface surface::create(int w, int h)
48 {
49 return surface(SDL_CreateRGBSurface(SDL_SWSURFACE,w,h,32,0xFF0000,0xFF00,0xFF,0xFF000000));
50 }
51
52 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef SURFACE_HPP_INCLUDED
13 #define SURFACE_HPP_INCLUDED
14
15 #include <iostream>
16
17 #include "SDL.h"
18 #include "scoped_resource.hpp"
19
20 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
21 #define SURFACE_MASK 0xFF,0xFF00,0xFF0000,0xFF000000
22 #define SURFACE_MASK_RGB 0xFF,0xFF00,0xFF0000,0x0
23 #else
24 #define SURFACE_MASK 0xFF000000,0xFF0000,0xFF00,0xFF
25 #define SURFACE_MASK_RGB 0xFF0000,0xFF00,0xFF,0x0
26 #endif
27
28 namespace graphics
29 {
30
31 struct surface
32 {
33 private:
34 static void sdl_add_ref(SDL_Surface *surf)
35 {
36 if (surf != NULL)
37 ++surf->refcount;
38 }
39
40 struct free_sdl_surface {
41 void operator()(SDL_Surface *surf) const
42 {
43 if (surf != NULL) {
44 SDL_FreeSurface(surf);
45 }
46 }
47 };
48
49 typedef util::scoped_resource<SDL_Surface*,free_sdl_surface> scoped_sdl_surface;
50 public:
51 surface() : surface_(NULL)
52 {}
53
54 surface(SDL_Surface *surf) : surface_(surf)
55 {
56 }
57
58 surface(const surface& o) : surface_(o.surface_.get())
59 {
60 sdl_add_ref(surface_.get());
61 }
62
63 static surface create(int w, int h);
64
65 void assign(const surface& o)
66 {
67 SDL_Surface *surf = o.surface_.get();
68 sdl_add_ref(surf); // need to be done before assign to avoid corruption on "a=a;"
69 surface_.assign(surf);
70 }
71
72 surface& operator=(const surface& o)
73 {
74 assign(o);
75 return *this;
76 }
77
78 operator SDL_Surface*() const { return surface_.get(); }
79
80 SDL_Surface* get() const { return surface_.get(); }
81
82 SDL_Surface* operator->() const { return surface_.get(); }
83
84 void assign(SDL_Surface* surf) { surface_.assign(surf); }
85
86 bool null() const { return surface_.get() == NULL; }
87
88 surface convert_opengl_format() const;
89 surface clone() const;
90
91 private:
92 scoped_sdl_surface surface_;
93 };
94
95 inline bool operator==(const surface& a, const surface& b)
96 {
97 return a.get() == b.get();
98 }
99
100 inline bool operator<(const surface& a, const surface& b)
101 {
102 return a.get() < b.get();
103 }
104
105 }
106
107 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include "concurrent_cache.hpp"
13 #include "filesystem.hpp"
14 #include "surface_cache.hpp"
15 #include "SDL_image.h"
16
17 #include <assert.h>
18 #include <iostream>
19 #include <map>
20
21 namespace graphics
22 {
23
24 namespace surface_cache
25 {
26
27 namespace {
28
29 typedef concurrent_cache<std::string,surface> surface_map;
30 surface_map& cache() {
31 static surface_map c;
32 return c;
33 }
34
35 const std::string path = "./images/";
36 }
37
38 surface get(const std::string& key)
39 {
40 surface surf = cache().get(key);
41 if(surf.null()) {
42 surf = get_no_cache(key);
43 cache().put(key,surf);
44 }
45
46 return surf;
47 }
48
49 surface get_no_cache(const std::string& key)
50 {
51 const std::string fname = path + key;
52 surface surf = surface(IMG_Load(sys::find_file(fname).c_str()));
53 std::cerr << "loading image '" << fname << "'\n";
54 if(surf.get() == false) {
55 std::cerr << "failed to load image '" << key << "'\n";
56 return surface();
57 }
58
59 std::cerr << "IMAGE SIZE: " << (surf->w*surf->h) << "\n";
60 return surf;
61 }
62
63 void clear_unused()
64 {
65 surface_map::lock lck(cache());
66 std::map<std::string, surface>& map = lck.map();
67 std::map<std::string, surface>::iterator i = map.begin();
68 while(i != map.end()) {
69 std::cerr << "CACHE REF " << i->first << " -> " << i->second->refcount << "\n";
70 if(i->second->refcount == 1) {
71 std::cerr << "CACHE FREE " << i->first << "\n";
72 map.erase(i++);
73 } else {
74 ++i;
75 }
76 }
77
78 std::cerr << "CACHE ITEMS: " << map.size() << "\n";
79 }
80
81 void clear()
82 {
83 cache().clear();
84 }
85
86 }
87
88 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef SURFACE_CACHE_HPP_INCLUDED
13 #define SURFACE_CACHE_HPP_INCLUDED
14
15 #include <string>
16
17 #include "surface.hpp"
18
19 namespace graphics
20 {
21
22 namespace surface_cache
23 {
24
25 surface get(const std::string& key);
26 surface get_no_cache(const std::string& key);
27 void clear_unused();
28 void clear();
29
30 }
31
32 }
33
34 #endif
0 #include <iostream>
1 #include <map>
2 #include <SDL.h>
3 #ifndef SDL_VIDEO_OPENGL_ES
4 #include <GL/glew.h>
5 #endif
6
7 #include "asserts.hpp"
8 #include "concurrent_cache.hpp"
9 #include "filesystem.hpp"
10 #include "foreach.hpp"
11 #include "formula.hpp"
12 #include "formula_callable.hpp"
13 #include "formula_function.hpp"
14 #include "hi_res_timer.hpp"
15 #include "surface.hpp"
16 #include "surface_cache.hpp"
17 #include "surface_formula.hpp"
18 #include "unit_test.hpp"
19
20 using namespace graphics;
21 using namespace game_logic;
22
23 namespace {
24
25 typedef std::pair<surface, std::string> cache_key;
26 typedef concurrent_cache<cache_key, surface> cache_map;
27
28 cache_map& cache() {
29 static cache_map instance;
30 return instance;
31 }
32
33 class rgba_function : public function_expression {
34 public:
35 explicit rgba_function(surface surf, const args_list& args)
36 : function_expression("rgba", args, 4), surf_(surf) {
37 }
38
39 private:
40 variant execute(const formula_callable& variables) const {
41 return variant(SDL_MapRGBA(surf_->format,
42 Uint8(args()[0]->evaluate(variables).as_int()),
43 Uint8(args()[1]->evaluate(variables).as_int()),
44 Uint8(args()[2]->evaluate(variables).as_int()),
45 Uint8(args()[3]->evaluate(variables).as_int())));
46 }
47 surface surf_;
48 };
49
50 class surface_formula_symbol_table : public function_symbol_table
51 {
52 public:
53 explicit surface_formula_symbol_table(surface surf) : surf_(surf)
54 {}
55 expression_ptr create_function(
56 const std::string& fn,
57 const std::vector<expression_ptr>& args,
58 const formula_callable_definition* callable_def) const;
59 private:
60 surface surf_;
61 };
62
63 expression_ptr surface_formula_symbol_table::create_function(
64 const std::string& fn,
65 const std::vector<expression_ptr>& args,
66 const formula_callable_definition* callable_def) const
67 {
68 if(fn == "rgba") {
69 return expression_ptr(new rgba_function(surf_, args));
70 } else {
71 return function_symbol_table::create_function(fn, args, callable_def);
72 }
73 }
74
75 class pixel_callable : public game_logic::formula_callable {
76 public:
77 pixel_callable(const surface& surf, Uint32 pixel)
78 {
79 SDL_GetRGBA(pixel, surf->format, &r, &g, &b, &a);
80 static const unsigned char AlphaPixel[] = {0x6f, 0x6d, 0x51};
81 if(r == AlphaPixel[0] && g == AlphaPixel[1] && b == AlphaPixel[2]) {
82 a = 0;
83 }
84 }
85
86 bool is_alpha() const { return a == 0; }
87 private:
88 variant get_value(const std::string& key) const {
89 switch(*key.c_str()) {
90 case 'r': return variant(r);
91 case 'g': return variant(g);
92 case 'b': return variant(b);
93 case 'a': return variant(a);
94 default: return variant();
95 }
96 }
97
98 Uint8 r, g, b, a;
99 };
100
101 void run_formula(surface surf, const std::string& algo)
102 {
103 const hi_res_timer timer("run_formula");
104
105 const int ticks = SDL_GetTicks();
106 surface_formula_symbol_table table(surf);
107 game_logic::formula f(algo, &table);
108 bool locked = false;
109 if(SDL_MUSTLOCK(surf.get())) {
110 const int res = SDL_LockSurface(surf.get());
111 if(res == 0) {
112 locked = true;
113 }
114 }
115
116 std::map<Uint32, Uint32> pixel_map;
117
118 Uint32* pixels = reinterpret_cast<Uint32*>(surf->pixels);
119 Uint32* end_pixels = pixels + surf->w*surf->h;
120
121 Uint32 AlphaPixel = SDL_MapRGBA(surf->format, 0x6f, 0x6d, 0x51, 0x0);
122
123 int skip = 0;
124 while(pixels != end_pixels) {
125 if(((*pixels)&(~surf->format->Amask)) == AlphaPixel) {
126 ++pixels;
127 continue;
128 }
129 std::map<Uint32, Uint32>::const_iterator itor = pixel_map.find(*pixels);
130 if(itor == pixel_map.end()) {
131 pixel_callable p(surf, *pixels);
132 Uint32 result = f.execute(p).as_int();
133 pixel_map[*pixels] = result;
134 *pixels = result;
135 } else {
136 *pixels = itor->second;
137 }
138 ++pixels;
139 }
140
141 if(locked) {
142 SDL_UnlockSurface(surf.get());
143 }
144 }
145
146 }
147
148 surface get_surface_formula(surface input, const std::string& algo)
149 {
150 if(algo.empty()) {
151 return input;
152 }
153
154 cache_key key(input, algo);
155 surface surf = cache().get(key);
156 if(surf.get() == NULL) {
157 surf = input.clone();
158 run_formula(surf, algo);
159 cache().put(key, surf);
160 }
161
162 return surf;
163 }
164
165 namespace {
166 typedef std::map<std::pair<std::string, GLuint>, GLuint> shader_object_map;
167 shader_object_map shader_object_cache;
168
169 typedef std::map<std::pair<std::vector<std::string>,std::vector<std::string> >, GLuint> shader_map;
170 shader_map shader_cache;
171
172 void check_shader_errors(const std::string& fname, GLuint shader)
173 {
174 #ifndef SDL_VIDEO_OPENGL_ES
175 GLint value = 0;
176 glGetShaderiv(shader, GL_COMPILE_STATUS, &value);
177 if(value == GL_FALSE) {
178 char buf[1024*16];
179 GLint len;
180 glGetShaderInfoLog(shader, sizeof(buf), &len, buf);
181 std::string errors(buf, buf + len);
182 ASSERT_LOG(false, "COMPILE ERROR IN SHADER " << fname << ": " << errors);
183 }
184 #endif
185 }
186
187 GLuint compile_shader(const std::string& shader_file, GLuint type)
188 {
189 GLuint& id = shader_object_cache[std::make_pair(shader_file, type)];
190 if(id) {
191 return id;
192 }
193 #ifndef SDL_VIDEO_OPENGL_ES
194 id = glCreateShader(type);
195
196 const std::string file_data = sys::read_file("data/shaders/" + shader_file);
197
198 const char* file_str = file_data.c_str();
199 glShaderSource(id, 1, &file_str, NULL);
200
201 glCompileShader(id);
202 check_shader_errors(shader_file, id);
203
204 return id;
205 #else
206 return 0;
207 #endif
208 }
209
210 }
211
212 GLuint get_gl_shader(const std::vector<std::string>& vertex_shader_file,
213 const std::vector<std::string>& fragment_shader_file)
214 {
215 #ifndef SDL_VIDEO_OPENGL_ES
216 if(vertex_shader_file.empty() || fragment_shader_file.empty()) {
217 return 0;
218 }
219
220 shader_map::iterator itor = shader_cache.find(std::make_pair(vertex_shader_file, fragment_shader_file));
221 if(itor != shader_cache.end()) {
222 return itor->second;
223 }
224
225 std::vector<GLuint> shader_objects;
226 foreach(const std::string& shader_file, vertex_shader_file) {
227 shader_objects.push_back(compile_shader(shader_file, GL_VERTEX_SHADER));
228 }
229
230 foreach(const std::string& shader_file, fragment_shader_file) {
231 shader_objects.push_back(compile_shader(shader_file, GL_FRAGMENT_SHADER));
232 }
233
234 GLuint program_id = glCreateProgram();
235
236 foreach(GLuint shader_id, shader_objects) {
237 glAttachShader(program_id, shader_id);
238 }
239
240 glLinkProgram(program_id);
241
242 GLint link_status = 0;
243 glGetProgramiv(program_id, GL_LINK_STATUS, &link_status);
244 if(link_status != GL_TRUE) {
245 char buf[1024*16];
246 GLint len;
247 glGetProgramInfoLog(program_id, sizeof(buf), &len, buf);
248 std::string errors(buf, buf + len);
249 ASSERT_LOG(false, "LINK ERROR IN SHADER PROGRAM: " << errors);
250 }
251
252 shader_cache[std::make_pair(vertex_shader_file, fragment_shader_file)] = program_id;
253
254 glUseProgram(0);
255
256 return program_id;
257 #else
258 return 0;
259 #endif
260 }
261
262 BENCHMARK(surface_formula)
263 {
264 surface s(graphics::surface_cache::get("characters/frogatto-spritesheet1.png"));
265 assert(s.get());
266
267 surface target(SDL_CreateRGBSurface(SDL_SWSURFACE,s->w,s->h,32,SURFACE_MASK));
268 SDL_BlitSurface(s.get(), NULL, target.get(), NULL);
269
270 const std::string algo("rgba(b,r,g,a)");
271 BENCHMARK_LOOP {
272 run_formula(target, algo);
273 }
274 }
275
276 BENCHMARK(pixel_table)
277 {
278 //This is some hard coded test data. It gives the set of pixels in
279 //the input image, and the pixels we want to map to.
280 const Uint32 PixelsFrom[] = {0xFF00FFFF, 0xFFFFFFFF, 0x9772FF13, 0xFF002145, 0x00FFFFFF, 0x94FF28FF };
281 const Uint32 PixelsTo[] = {0x00FF0000, 0xFF00FFFF, 0xFFFFFFFF, 0x9772FF13, 0xFF002145, 0x00FFFFFF };
282 const int NumColors = sizeof(PixelsFrom)/sizeof(*PixelsFrom);
283
284 //Set up an image of a million pixels in size. Set all values to values
285 //in the 'pixels from' range.
286 std::vector<Uint32> image(1000000);
287 for(int n = 0; n != image.size(); ++n) {
288 image[n] = PixelsFrom[n%NumColors];
289 }
290
291 //set up our table mapping pixels from -> pixels to.
292 typedef std::map<Uint32, Uint32> PixelTable;
293 PixelTable table;
294 for(int n = 0; n != NumColors; ++n) {
295 table.insert(std::pair<Uint32, Uint32>(PixelsFrom[n], PixelsTo[n]));
296 }
297
298 //now go over the image and map all source pixels to their destinations.
299 //this is the part that we want to benchmark.
300 BENCHMARK_LOOP {
301 for(int n = 0; n != image.size(); ++n) {
302 image[n] = table[image[n]];
303 }
304 }
305 }
0 #ifndef SURFACE_FORMULA_HPP_INCLUDED
1 #define SURFACE_FORMULA_HPP_INCLUDED
2
3 #include <string>
4 #include <vector>
5
6 #include <GL/gl.h>
7
8 #include "surface.hpp"
9
10 graphics::surface get_surface_formula(graphics::surface input, const std::string& algo);
11
12 GLuint get_gl_shader(const std::vector<std::string>& vertex_shader,
13 const std::vector<std::string>& fragment_shader);
14
15 #endif
0 #include <map>
1 #include <vector>
2
3 #include "asserts.hpp"
4 #include "surface_cache.hpp"
5 #include "surface_palette.hpp"
6
7 namespace graphics
8 {
9
10 namespace {
11
12 struct palette_definition {
13 std::string name;
14 std::map<uint32_t, uint32_t> mapping;
15 };
16
17 std::vector<palette_definition> palettes;
18
19 void load_palette_def(const std::string& id)
20 {
21 palette_definition def;
22 def.name = id;
23 surface s = surface_cache::get_no_cache("palette/" + id + ".png");
24
25 surface converted(SDL_CreateRGBSurface(SDL_SWSURFACE, s->w, s->h, 32, SURFACE_MASK));
26 SDL_SetAlpha(s.get(), 0, SDL_ALPHA_OPAQUE);
27 SDL_BlitSurface(s.get(), NULL, converted.get(), NULL);
28 s = converted;
29
30 ASSERT_LOG(s.get(), "COULD NOT LOAD PALETTE IMAGE " << id);
31 ASSERT_LOG(s->format->BytesPerPixel == 4, "PALETTE " << id << " NOT IN 32bpp PIXEL FORMAT");
32
33 const uint32_t* pixels = reinterpret_cast<const uint32_t*>(s->pixels);
34 for(int n = 0; n < s->w*s->h - 1; n += 2) {
35 def.mapping.insert(std::pair<uint32_t,uint32_t>(pixels[0], pixels[1]));
36 pixels += 2;
37 }
38
39 palettes.push_back(def);
40 }
41
42 }
43
44 int get_palette_id(const std::string& name)
45 {
46 if(name.empty()) {
47 return -1;
48 }
49
50 static std::map<std::string, int> m;
51 std::map<std::string, int>::const_iterator i = m.find(name);
52 if(i != m.end()) {
53 return i->second;
54 }
55
56 const int id = m.size();
57 m[name] = id;
58 load_palette_def(name);
59 return get_palette_id(name);
60 }
61
62 const std::string& get_palette_name(int id)
63 {
64 if(id < 0 || id >= palettes.size()) {
65 static const std::string str;
66 return str;
67 } else {
68 return palettes[id].name;
69 }
70 }
71
72 surface map_palette(surface s, int palette)
73 {
74 if(palette < 0 || palette >= palettes.size() || palettes[palette].mapping.empty()) {
75 return s;
76 }
77
78 surface result(SDL_CreateRGBSurface(SDL_SWSURFACE, s->w, s->h, 32, SURFACE_MASK));
79
80 s = surface(SDL_ConvertSurface(s.get(), result->format, 0));
81
82 ASSERT_LOG(s->format->BytesPerPixel == 4, "SURFACE NOT IN 32bpp PIXEL FORMAT");
83
84 std::cerr << "mapping palette " << palette << "\n";
85
86
87 uint32_t* dst = reinterpret_cast<uint32_t*>(result->pixels);
88 const uint32_t* src = reinterpret_cast<const uint32_t*>(s->pixels);
89
90 const std::map<uint32_t,uint32_t>& mapping = palettes[palette].mapping;
91
92 for(int n = 0; n != s->w*s->h; ++n) {
93 std::map<uint32_t,uint32_t>::const_iterator i = mapping.find(*src);
94 if(i != mapping.end()) {
95 *dst = i->second;
96 } else {
97 *dst = *src;
98 }
99
100 ++src;
101 ++dst;
102 }
103 return result;
104 }
105
106 color map_palette(const color& c, int palette)
107 {
108 if(palette < 0 || palette >= palettes.size() || palettes[palette].mapping.empty()) {
109 return c;
110 }
111
112 const std::map<uint32_t,uint32_t>& mapping = palettes[palette].mapping;
113 std::map<uint32_t,uint32_t>::const_iterator i = mapping.find(c.value());
114 if(i != mapping.end()) {
115 return color(color::convert_pixel_byte_order(i->second));
116 } else {
117 return c;
118 }
119 }
120
121 SDL_Color map_palette(const SDL_Color& c, int palette)
122 {
123 color result = map_palette(color(c.r, c.g, c.b, 255), palette);
124 SDL_Color res = { result.r(), result.g(), result.b(), 255 };
125 return res;
126 }
127
128 }
0 #ifndef SURFACE_PALETTE_HPP_INCLUDED
1 #define SURFACE_PALETTE_HPP_INCLUDED
2
3 #include <string>
4
5 #include "color_utils.hpp"
6 #include "surface.hpp"
7
8 namespace graphics
9 {
10
11 int get_palette_id(const std::string& name);
12 const std::string& get_palette_name(int id);
13
14 surface map_palette(surface s, int palette);
15 color map_palette(const color& c, int palette);
16 SDL_Color map_palette(const SDL_Color& c, int palette);
17 }
18
19 #endif
0 #include <inttypes.h>
1 #include <iostream>
2 #include <cassert>
3
4 #include "surface_cache.hpp"
5 #include "surface.hpp"
6 #include "unit_test.hpp"
7
8 namespace graphics {
9
10 uint32_t interpolate_pixels (uint32_t sourcePixelOne, uint32_t sourcePixelTwo){
11 //Take the average of the colors in two pixels. Each channel needs to be mixed separately.
12
13 uint32_t resultPixel;
14 uint8_t* result_pixel_color_channel = reinterpret_cast<uint8_t*>(&resultPixel);
15 uint8_t* pixel_one_channel = reinterpret_cast<uint8_t*>(&sourcePixelOne);
16 uint8_t* pixel_two_channel = reinterpret_cast<uint8_t*>(&sourcePixelTwo);
17
18 for ( int color = 0; color < 4; ++color) {
19 result_pixel_color_channel[color] = (pixel_one_channel[color] + pixel_two_channel[color]) / 2;
20 }
21 return resultPixel;
22 }
23
24 uint32_t interpolate_pixels (uint32_t sourcePixelOne, uint32_t sourcePixelTwo, uint32_t sourcePixelThree, uint32_t sourcePixelFour){
25 //Same as above, but with four pixels instead of two. The implementation was unnecessarily made different, solely for the programmer's edification.
26
27 union PixelUnion {
28 uint32_t value;
29 uint8_t rgba[4];
30 };
31
32 PixelUnion resultPixel, pixel_one, pixel_two, pixel_three, pixel_four;
33 pixel_one.value = sourcePixelOne;
34 pixel_two.value = sourcePixelTwo;
35 pixel_three.value = sourcePixelThree;
36 pixel_four.value = sourcePixelFour;
37
38
39 for ( int color = 0; color < 4; ++color) {
40 resultPixel.rgba[color] = (pixel_one.rgba[color] + pixel_two.rgba[color] + pixel_three.rgba[color] + pixel_four.rgba[color]) / 4;
41 }
42 return resultPixel.value;
43 }
44
45 int calculate_difference(uint32_t sourcePixelOne, uint32_t sourcePixelTwo, uint32_t sourcePixelThree, uint32_t sourcePixelFour){
46 //determines which of pixels one and two, is more different from pixels three and four
47 return ((sourcePixelOne != sourcePixelThree || sourcePixelOne != sourcePixelFour) - (sourcePixelTwo != sourcePixelThree || sourcePixelTwo != sourcePixelFour));
48 }
49
50
51
52
53 surface scale_surface_eagle(surface input) {
54 surface result(surface::create(input->w*2, input->h*2));
55
56 const uint32_t* in = reinterpret_cast<const uint32_t*>(input->pixels);
57 uint32_t* out = reinterpret_cast<uint32_t*>(result->pixels);
58 for(int y = 0; y != input->h; ++y) {
59 for(int x = 0; x != input->w; ++x) {
60 //do nearest neighbor interpolation
61 out[(y*2)*result->w + x*2] = in[y*input->w + x];
62 out[(y*2 + 1)*result->w + x*2] = in[y*input->w + x];
63 out[(y*2 + 1)*result->w + x*2 + 1] = in[y*input->w + x];
64 out[(y*2)*result->w + x*2 + 1] = in[y*input->w + x];
65
66 if ((y > 0) && (y < input->h - 1) && (x > 0) && (x < input->w - 1)){
67 //do additional eagle interpolation
68
69 // Eagle is a pixel art scaling algorithm designed to smooth rough edges. It works as follows: First, it doubles the scale of the art, turning every source pixel into four destination pixels, just like nearest neighbor scaling would.
70
71 // Then, to smooth things out, it conditionally alters each of these four pixels. Each of the four destination pixels represents a quadrant of the source pixel, and likewise a direction pointing away from the center of the source pixel. To choose if it 'smoothes an edge" in a given direction, it checks additional, adjacent source pixels in the direction represented by the quadrant. If they're all the same color, it represents a diagonal slope of pixels, and the given quadrant pixel is filled in with the same color to smooth out the diagonal.
72
73 // The following diagram illustrates first the scaling of a single input pixel into 4 output pixels (left side of diagram), and then the choices made to choose which color the output pixels are given (right side of diagram)
74
75 // first: |Then
76 // . . . --\ CC |S T U --\ 1 2
77 // . C . --/ CC |V C W --/ 3 4
78 // . . . |X Y Z
79 // | IF V==S==T => 1=S
80 // | IF T==U==W => 2=U
81 // | IF V==X==Y => 3=X
82 // | IF W==Z==Y => 4=Z
83
84 const int index_up_left = y*input->w + x - input->w - 1;
85 const int index_up = y*input->w + x - input->w;
86 const int index_up_right = y*input->w + x - input->w + 1;
87 const int index_right = y*input->w + x + 1;
88 const int index_down_right = y*input->w + x + input->w + 1;
89 const int index_down = y*input->w + x + input->w;
90 const int index_down_left = y*input->w + x + input->w - 1;
91 const int index_left = y*input->w + x - 1;
92 const int max_index = input->w*input->h;
93
94 assert(index_down_left < max_index);
95 assert(index_down_right < max_index);
96 assert(index_down < max_index);
97
98 if ( (in[index_up_left] == in[index_up]) && (in[index_up_left] == in[index_left]) ) {
99 out[(y*2)*result->w + x*2] = in[index_up_left];
100 }
101 if ( (in[index_up_right] == in[index_up]) && (in[index_up_right] == in[index_right]) ) {
102 out[(y*2)*result->w + x*2 + 1] = in[index_up_right];
103 }
104 if ( (in[index_down_left] == in[index_down]) && (in[index_down_left] == in[index_left]) ) {
105 out[(y*2 + 1)*result->w + x*2] = in[index_down_left];
106 }
107 if ( (in[index_down_right] == in[index_down]) && (in[index_down_right] == in[index_right]) ) {
108 out[(y*2 + 1)*result->w + x*2 + 1] = in[index_down_right];
109 }
110 }
111 }
112 }
113
114 return result;
115 }
116
117
118
119 surface scale_surface(surface input) {
120 surface result(surface::create(input->w*2, input->h*2));
121
122 const uint32_t* in = reinterpret_cast<const uint32_t*>(input->pixels);
123 uint32_t* out = reinterpret_cast<uint32_t*>(result->pixels);
124 for(int y = 0; y != input->h; ++y) {
125 for(int x = 0; x != input->w; ++x) {
126 //do nearest neighbor interpolation
127 out[(y*2)*result->w + x*2] = in[y*input->w + x];
128 out[(y*2 + 1)*result->w + x*2] = in[y*input->w + x];
129 out[(y*2 + 1)*result->w + x*2 + 1] = in[y*input->w + x];
130 out[(y*2)*result->w + x*2 + 1] = in[y*input->w + x];
131
132 if ((y > 0) && (y < input->h - 2) && (x > 0) && (x < input->w - 2)){
133
134
135 //do additional 2xSaI interpolation
136
137 // 2xSai works on a square group of sixteen pixels, rather than the square group of nine that Eagle works on. In Eagle, the current pixel being upsized is the one in the middle of this square of nine; in 2xSai, the current pixel is the one in the upper-left of the middle four. The other pixels in this group are all input pixels that are being examined to determine if we have an edge to smooth out.
138
139 // X X X X | 0 1 2 3
140 // X * X X | 4 5 6 7
141 // X X X X | 8 9 10 11
142 // X X X X | 12 13 14 15
143 const int px[4][4] = {{ //[x][y]
144 y*input->w + x - input->w - 1,
145 y*input->w + x - input->w,
146 y*input->w + x - input->w + 1,
147 y*input->w + x - input->w + 2},
148 {y*input->w + x - 1,
149 y*input->w + x,
150 y*input->w + x + 1,
151 y*input->w + x + 2},
152 {y*input->w + x + input->w - 1,
153 y*input->w + x + input->w,
154 y*input->w + x + input->w + 1,
155 y*input->w + x + input->w + 2},
156 {y*input->w + x + 2*input->w - 1,
157 y*input->w + x + 2*input->w,
158 y*input->w + x + 2*input->w + 1,
159 y*input->w + x + 2*input->w + 2}};
160
161 //these are the four output pixels corresponding to the one input pixel
162 const int out_index_upper_left = (y*2)*result->w + x*2;
163 const int out_index_upper_right = (y*2)*result->w + x*2 + 1;
164 const int out_index_lower_left = (y*2 + 1)*result->w + x*2;
165 const int out_index_lower_right = (y*2 + 1)*result->w + x*2 + 1;
166
167 //make sure we're not going out-of-bounds
168 const int max_index = input->w*input->h;
169 assert(px[0][2] < max_index);
170 assert(px[0][3] < max_index);
171 assert(px[1][2] < max_index);
172 assert(px[1][3] < max_index);
173 assert(px[2][0] < max_index);
174 assert(px[2][1] < max_index);
175 assert(px[2][2] < max_index);
176 assert(px[2][3] < max_index);
177 assert(px[3][0] < max_index);
178 assert(px[3][1] < max_index);
179 assert(px[3][2] < max_index);
180 assert(px[3][3] < max_index);
181
182 if ( (in[px[1][1]] == in[px[2][2]]) && (in[px[1][2]] != in[px[2][1]]) ) {
183 if ( ((in[px[1][1]] == in[px[0][1]]) && (in[px[1][2]] == in[px[2][3]])) || ((in[px[1][1]] == in[px[2][1]]) && (in[px[1][1]] == in[px[0][2]]) && (in[px[1][2]] != in[px[0][1]]) && (in[px[1][2]] == in[px[0][3]]))){
184 out[out_index_upper_right] = in[px[1][1]];
185 }else{
186 if( ! ((in[px[1][1]] == in[px[0][1]])) ){
187 out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]]);
188 }
189 }
190
191 if ( ((in[px[1][1]] == in[px[1][0]]) && (in[px[2][1]] == in[px[3][2]])) || ((in[px[1][1]] == in[px[1][2]]) && (in[px[1][1]] == in[px[2][0]]) && (in[px[1][0]] != in[px[2][1]]) && (in[px[2][1]] == in[px[3][0]]))){
192 out[out_index_lower_left] = in[px[1][1]];
193 }else{
194 if( ! ((in[px[1][1]] == in[px[1][0]])) ){
195 out[out_index_lower_left] = interpolate_pixels(in[px[1][1]],in[px[2][1]]);
196 }
197 }
198 out[out_index_lower_right] = in[px[1][1]];
199 } else if ( (in[px[1][2]] == in[px[2][1]]) && (in[px[1][1]] != in[px[2][2]]) ) {
200 if ( ((in[px[1][2]] == in[px[0][2]]) && (in[px[1][1]] == in[px[2][0]])) || ((in[px[1][2]] == in[px[0][1]]) && (in[px[1][2]] == in[px[2][2]]) && (in[px[1][2]] != in[px[0][2]]) && (in[px[1][1]] == in[px[0][0]]))){
201 out[out_index_upper_right] = in[px[1][2]];
202 }else{
203 out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]]);
204 }
205
206 if ( ((in[px[2][1]] == in[px[2][0]]) && (in[px[1][1]] == in[px[0][2]])) || ((in[px[2][1]] == in[px[1][0]]) && (in[px[2][1]] == in[px[2][2]]) && (in[px[1][1]] != in[px[2][0]]) && (in[px[1][1]] == in[px[0][0]]))){
207 out[out_index_lower_left] = in[px[2][1]];
208 }else{
209 out[out_index_lower_left] = interpolate_pixels(in[px[1][1]],in[px[2][1]]);
210 }
211 out[out_index_lower_right] = in[px[1][2]];
212
213 }else if ( (in[px[1][1]] == in[px[2][2]]) && (in[px[1][2]] == in[px[2][1]]) ) {
214 // these are crossed diagonal pairs of pixels in the inner, center set of four.
215 // if they're the same, then we weight them against a surrounding ring of pixels, and see which
216 // pair is more different from the ring. The ring is this asterisked set of pixels:
217 // X * * X
218 // * X X *
219 // * X X *
220 // X * * X
221 if (in[px[1][1]] == in[px[1][2]]){
222 out[out_index_lower_right] = in[px[1][1]];
223 out[out_index_lower_left] = in[px[1][1]];
224 out[out_index_upper_right] = in[px[1][1]];
225 out[out_index_upper_left] = in[px[1][1]];
226
227 } else {
228
229 int difference_direction = 0;
230 out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]]);
231 out[out_index_lower_left] = interpolate_pixels(in[px[1][1]],in[px[2][1]]);
232
233 difference_direction += calculate_difference(in[px[1][2]],in[px[1][1]],in[px[0][1]],in[px[1][0]]);
234 difference_direction += calculate_difference(in[px[1][2]],in[px[1][1]],in[px[0][2]],in[px[1][3]]);
235 difference_direction += calculate_difference(in[px[1][2]],in[px[1][1]],in[px[3][2]],in[px[2][3]]);
236 difference_direction += calculate_difference(in[px[1][2]],in[px[1][1]],in[px[2][0]],in[px[3][1]]);
237
238 if (difference_direction > 0){
239 out[out_index_lower_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]],in[px[1][2]],in[px[1][2]]);
240 out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]],in[px[1][2]],in[px[1][2]]);
241 }else if(difference_direction < 0){
242 out[out_index_lower_right] = interpolate_pixels(in[px[1][1]],in[px[1][1]],in[px[1][1]],in[px[1][2]]);
243 out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][1]],in[px[1][1]],in[px[1][2]]);
244 }else{
245 out[out_index_lower_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]],in[px[2][1]],in[px[2][2]]);
246 out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]],in[px[2][1]],in[px[2][2]]);
247 }
248
249 }
250 } else {
251 if ( ( (in[px[1][1]] != in[px[2][1]]) || (in[px[1][1]] != in[px[0][1]]) ) && ( (in[px[0][2]] == in[px[1][2]]) && (in[px[1][2]] == in[px[2][2]]) ) ){
252 out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][1]],in[px[1][1]],in[px[1][2]]);
253 out[out_index_lower_right] = interpolate_pixels(in[px[1][1]],in[px[1][1]],in[px[1][1]],in[px[1][2]]);
254 }
255
256 if ( ( (in[px[0][2]] == in[px[1][1]]) || (in[px[1][1]] == in[px[2][0]]) ) && ( (in[px[1][1]] != in[px[2][1]]) && (in[px[1][1]] != in[px[1][2]])) ){
257 out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][1]],in[px[1][1]],in[px[1][2]]);
258 out[out_index_lower_left] = interpolate_pixels(in[px[1][1]],in[px[1][1]],in[px[1][1]],in[px[2][1]]);
259 out[out_index_lower_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]],in[px[1][1]],in[px[2][1]]);
260 }
261
262 if ( ((in[px[1][1]] == in[px[0][0]]) && (in[px[1][1]] == in[px[1][2]])) && ( (in[px[1][1]] != in[px[1][0]]) && (in[px[1][1]] != in[px[2][1]]) ) ){
263 out[out_index_upper_left] = interpolate_pixels(in[px[1][1]],in[px[1][1]],in[px[1][1]],in[px[1][0]]);
264 out[out_index_lower_right] = interpolate_pixels(in[px[1][1]],in[px[1][1]],in[px[1][1]],in[px[2][1]]);
265 out[out_index_lower_left] = interpolate_pixels(in[px[1][0]],in[px[2][1]]);
266 }
267
268
269 if ( (in[px[1][1]] == in[px[2][1]]) && (in[px[1][1]] == in[px[0][2]]) && (in[px[1][2]] != in[px[0][1]]) && (in[px[1][2]] == in[px[0][3]]) ){
270 out[out_index_upper_right] = in[px[1][1]];
271 } else if ( (in[px[1][2]] == in[px[0][1]]) && (in[px[1][2]] == in[px[2][2]]) && (in[px[1][1]] != in[px[0][2]]) && (in[px[1][1]] == in[px[0][0]]) ){
272 out[out_index_upper_right] = in[px[1][2]];
273 } else {
274 //out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]]);
275 //out[out_index_lower_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]]);
276 }
277
278 if ( (in[px[1][1]] == in[px[1][2]]) && (in[px[1][1]] == in[px[2][0]]) && (in[px[1][0]] != in[px[2][1]]) && (in[px[2][1]] == in[px[3][0]]) ){
279 out[out_index_lower_left] = in[px[1][1]];
280 } else if ( (in[px[2][1]] == in[px[1][0]]) && (in[px[2][1]] == in[px[2][2]]) && (in[px[1][1]] != in[px[2][0]]) && (in[px[1][1]] == in[px[0][0]]) ){
281 out[out_index_lower_left] = in[px[2][1]];
282 } else {
283 //out[out_index_lower_right] = interpolate_pixels(in[px[1][1]],in[px[2][1]]);
284 //out[out_index_lower_left] = interpolate_pixels(in[px[1][1]],in[px[2][1]]);
285 }
286 //out[out_index_lower_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]],in[px[2][1]],in[px[2][2]]);
287 //out[out_index_upper_right] = interpolate_pixels(in[px[1][1]],in[px[1][2]],in[px[2][1]],in[px[2][2]]);
288 }
289
290 }
291 }
292 }
293
294 return result;
295 }
296
297 BENCHMARK(surface_scaling)
298 {
299 surface s(graphics::surface_cache::get("characters/frogatto-spritesheet1.png"));
300 assert(s.get());
301
302 surface target(SDL_CreateRGBSurface(SDL_SWSURFACE,s->w,s->h,32,SURFACE_MASK));
303 SDL_BlitSurface(s.get(), NULL, target.get(), NULL);
304 BENCHMARK_LOOP {
305 scale_surface(target);
306 }
307 }
308
309 }
0 #include "SDL.h"
1
2 #include <ctype.h>
3
4 #include "raster.hpp"
5 #include "text_entry_widget.hpp"
6
7 namespace gui {
8
9 text_entry_widget::text_entry_widget() : scroll_(0), cursor_(0), font_(graphical_font::get("default"))
10 {
11 SDL_EnableUNICODE(1);
12 }
13
14 const std::string& text_entry_widget::text() const
15 {
16 return text_;
17 }
18
19 void text_entry_widget::set_text(const std::string& value)
20 {
21 text_ = value;
22 cursor_ = 0;
23 }
24
25 void text_entry_widget::handle_draw() const
26 {
27 rect r = font_->draw(x(), y(), std::string(text_.begin(), text_.begin() + cursor_));
28 if(cursor_ == 0) {
29 r = rect(r.x() - 4, r.y(), 0, 0);
30 }
31 const SDL_Rect cursor_rect = {r.x2() + 2, y(), 2, 28};
32 graphics::draw_rect(cursor_rect, graphics::color_white());
33 font_->draw(r.x2() + 4, y(), std::string(text_.begin() + cursor_, text_.end()));
34 }
35
36 bool text_entry_widget::handle_event(const SDL_Event& event, bool claimed)
37 {
38 if(claimed) {
39 return claimed;
40 }
41
42 switch(event.type) {
43 case SDL_KEYDOWN: {
44
45 switch(event.key.keysym.sym) {
46 case SDLK_LEFT:
47 if(cursor_ > 0) {
48 --cursor_;
49 }
50 break;
51 case SDLK_RIGHT:
52 if(cursor_ < text_.size()) {
53 ++cursor_;
54 }
55 break;
56 case SDLK_BACKSPACE:
57 if(cursor_ > 0) {
58 --cursor_;
59 text_.erase(text_.begin() + cursor_);
60 }
61 break;
62 case SDLK_DELETE:
63 if(cursor_ < text_.size()) {
64 text_.erase(text_.begin() + cursor_);
65 }
66 break;
67 }
68
69 char c = event.key.keysym.unicode;
70 if(isprint(c)) {
71 text_.insert(text_.begin() + cursor_, c);
72 ++cursor_;
73 claimed = true;
74 }
75 break;
76 }
77 }
78
79 return claimed;
80 }
81
82 }
0 #ifndef TEXT_ENTRY_WIDGET_HPP_INCLUDED
1 #define TEXT_ENTRY_WIDGET_HPP_INCLUDED
2
3 #include <string>
4
5 #include "graphical_font.hpp"
6 #include "widget.hpp"
7
8 namespace gui {
9
10 class text_entry_widget : public widget
11 {
12 public:
13 text_entry_widget();
14
15 const std::string& text() const;
16 void set_text(const std::string& value);
17 private:
18 void handle_draw() const;
19 bool handle_event(const SDL_Event& event, bool claimed);
20
21 std::string text_;
22 int scroll_;
23 int cursor_;
24
25 const_graphical_font_ptr font_;
26 };
27
28 }
29
30 #endif
0 /*
1 Copyright (C) 2007 by David White <dave@whitevine.net>
2 Part of the Silver Tree Project
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License version 2 or later.
6 This program is distributed in the hope that it will be useful,
7 but WITHOUT ANY WARRANTY.
8
9 See the COPYING file for more details.
10 */
11
12 #include <GL/gl.h>
13 #include <GL/glu.h>
14 #include <pthread.h>
15
16 #include "asserts.hpp"
17 #include "concurrent_cache.hpp"
18 #include "foreach.hpp"
19 #include "preferences.hpp"
20 #include "raster.hpp"
21 #include "surface_cache.hpp"
22 #include "surface_formula.hpp"
23 #include "surface_palette.hpp"
24 #include "texture.hpp"
25 #include "thread.hpp"
26 #include "unit_test.hpp"
27 #include <map>
28 #include <set>
29 #include <iostream>
30 #include <cstring>
31
32 namespace graphics
33 {
34
35 pthread_t graphics_thread_id;
36 surface scale_surface(surface input);
37
38 namespace {
39 std::set<texture*>& texture_registry() {
40 static std::set<texture*>* reg = new std::set<texture*>;
41 return *reg;
42 }
43
44 threading::mutex& texture_registry_mutex() {
45 static threading::mutex* m = new threading::mutex;
46 return *m;
47 }
48
49 void add_texture_to_registry(texture* t) {
50 // TODO: Currently the registry is disabled for performance reasons.
51 // threading::lock lk(texture_registry_mutex());
52 // texture_registry().insert(t);
53 }
54
55 void remove_texture_from_registry(texture* t) {
56 // threading::lock lk(texture_registry_mutex());
57 // texture_registry().erase(t);
58 }
59
60 typedef concurrent_cache<std::string,graphics::texture> texture_map;
61 texture_map& texture_cache() {
62 static texture_map cache;
63 return cache;
64 }
65 typedef concurrent_cache<std::pair<std::string,std::string>,graphics::texture> algorithm_texture_map;
66 algorithm_texture_map& algorithm_texture_cache() {
67 static algorithm_texture_map cache;
68 return cache;
69 }
70
71 typedef concurrent_cache<std::pair<std::string,int>,graphics::texture> palette_texture_map;
72 palette_texture_map& palette_texture_cache() {
73 static palette_texture_map cache;
74 return cache;
75 }
76
77 const size_t TextureBufSize = 128;
78 GLuint texture_buf[TextureBufSize];
79 size_t texture_buf_pos = TextureBufSize;
80 std::vector<GLuint> avail_textures;
81 bool graphics_initialized = false;
82
83 GLuint current_texture = 0;
84
85 GLuint get_texture_id() {
86 if(!avail_textures.empty()) {
87 const GLuint res = avail_textures.back();
88 avail_textures.pop_back();
89 return res;
90 }
91
92 if(texture_buf_pos == TextureBufSize) {
93 if(!pthread_equal(graphics_thread_id, pthread_self())) {
94 //we are in a worker thread so we can't make an OpenGL
95 //call. Throw an exception.
96 std::cerr << "CANNOT ALLOCATE TEXTURE ID's in WORKER THREAD\n";
97 throw texture::worker_thread_error();
98 }
99
100 glGenTextures(TextureBufSize, texture_buf);
101 texture_buf_pos = 0;
102 }
103
104 return texture_buf[texture_buf_pos++];
105 }
106
107 bool npot_allowed = true;
108 GLfloat width_multiplier = -1.0;
109 GLfloat height_multiplier = -1.0;
110
111 unsigned int next_power_of_2(unsigned int n)
112 {
113 --n;
114 n = n|(n >> 1);
115 n = n|(n >> 2);
116 n = n|(n >> 4);
117 n = n|(n >> 8);
118 n = n|(n >> 16);
119 ++n;
120 return n;
121 }
122
123 bool is_npot_allowed()
124 {
125 static bool once = false;
126 static bool npot = true;
127 if (once) return npot;
128 once = true;
129
130 if(preferences::force_no_npot_textures()) {
131 npot = false;
132 return false;
133 }
134
135 const char *supported = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
136 const char *version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
137 const char *vendor = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
138
139 std::cerr << "OpenGL version: " << version << "\n";
140 std::cerr << "OpenGL vendor: " << vendor << "\n";
141 std::cerr << "OpenGL extensions: " << supported << "\n";
142
143 // OpenGL >= 2.0 drivers must support NPOT textures
144 bool version_2 = (version[0] >= '2');
145 npot = version_2;
146 // directly test for NPOT extension
147 if (std::strstr(supported, "GL_ARB_texture_non_power_of_two")) npot = true;
148
149 if (npot) {
150 // Use some heuristic to make sure it is HW accelerated. Might need some
151 // more work.
152 if (std::strstr(vendor, "NVIDIA Corporation")) {
153 if (!std::strstr(supported, "NV_fragment_program2") ||
154 !std::strstr(supported, "NV_vertex_program3")) {
155 npot = false;
156 }
157 } else if (std::strstr(vendor, "ATI Technologies")) {
158 // TODO: Investigation note: my ATI card works fine for npot textures as long
159 // as mipmapping is enabled. otherwise it runs slowly. Work out why. --David
160 //if (!std::strstr(supported, "GL_ARB_texture_non_power_of_two"))
161 npot = false;
162 } else if(std::strstr(vendor, "Apple Computer") || std::strstr(vendor, "Imagination Technologies")) {
163 if (!std::strstr(supported, "GL_ARB_texture_non_power_of_two")) {
164 npot = false;
165 }
166 }
167 }
168 if(!npot) {
169 std::cerr << "Using only pot textures\n";
170 }
171 return npot;
172 }
173
174 std::string mipmap_type_to_string(GLenum type) {
175 switch(type) {
176 case GL_NEAREST:
177 return "N";
178 case GL_LINEAR:
179 return "L";
180 case GL_NEAREST_MIPMAP_NEAREST:
181 return "NN";
182 case GL_NEAREST_MIPMAP_LINEAR:
183 return "NL";
184 case GL_LINEAR_MIPMAP_NEAREST:
185 return "LN";
186 case GL_LINEAR_MIPMAP_LINEAR:
187 return "LL";
188 default:
189 return "??";
190 }
191 }
192 }
193
194 texture::manager::manager() {
195 assert(!graphics_initialized);
196
197 width_multiplier = 1.0;
198 height_multiplier = 1.0;
199
200 graphics_initialized = true;
201
202 graphics_thread_id = pthread_self();
203 }
204
205 texture::manager::~manager() {
206 graphics_initialized = false;
207 }
208
209 void texture::clear_textures()
210 {
211 std::cerr << "TEXTURES LOADING...\n";
212 texture_map::lock lck(texture_cache());
213 for(texture_map::map_type::const_iterator i = lck.map().begin(); i != lck.map().end(); ++i) {
214 if(!i->second.id_) {
215 continue;
216 }
217
218 std::cerr << "TEXTURE: '" << i->first << "': " << (i->second.id_->init() ? "INIT" : "UNINIT") << "\n";
219 }
220
221 std::cerr << "DONE TEXTURES LOADING\n";
222 /*
223 //go through all the textures and clear out the ID's. We only want to
224 //re-initialize each shared ID once.
225 threading::lock lk(texture_registry_mutex());
226 for(std::set<texture*>::iterator i = texture_registry().begin(); i != texture_registry().end(); ++i) {
227 texture& t = **i;
228 if(t.id_) {
229 t.id_->destroy();
230 }
231 }
232
233 //go through and initialize anyone's ID which hasn't been initialized
234 //already.
235 for(std::set<texture*>::iterator i = texture_registry().begin(); i != texture_registry().end(); ++i) {
236 texture& t = **i;
237 if(t.id_ && t.id_->s.get() == NULL) {
238 t.initialize();
239 }
240 }
241 */
242 }
243
244 texture::texture() : width_(0), height_(0)
245 {
246 add_texture_to_registry(this);
247 }
248
249 texture::texture(const key& surfs)
250 : width_(0), height_(0), ratio_w_(1.0), ratio_h_(1.0)
251 {
252 add_texture_to_registry(this);
253 initialize(surfs);
254 }
255
256 texture::texture(const texture& t)
257 : id_(t.id_), width_(t.width_), height_(t.height_),
258 ratio_w_(t.ratio_w_), ratio_h_(t.ratio_h_),
259 alpha_map_(t.alpha_map_)
260 {
261 add_texture_to_registry(this);
262 }
263
264 texture::~texture()
265 {
266 remove_texture_from_registry(this);
267 }
268
269 //this function is designed to convert a 24bpp surface to a 32bpp one, adding
270 //an alpha channel. The dest surface may be larger than the source surface,
271 //in which case it will put it in the upper-left corner. This is much faster
272 //than using SDL blitting functions.
273 void add_alpha_channel_to_surface(uint8_t* dst_ptr, const uint8_t* src_ptr, size_t dst_w, size_t src_w, size_t src_h, size_t src_pitch)
274 {
275 ASSERT_GE(dst_w, src_w);
276
277 for(size_t y = 0; y < src_h; ++y) {
278 uint8_t* dst = dst_ptr + y*dst_w*4;
279 const uint8_t* src = src_ptr + y*src_pitch;
280 for(size_t x = 0; x < src_w; ++x) {
281 *dst++ = *src++;
282 *dst++ = *src++;
283 *dst++ = *src++;
284 *dst++ = 0xFF;
285 }
286
287 dst += (dst_w - src_w)*4;
288 }
289 }
290
291 void set_alpha_for_transparent_colors_in_rgba_surface(SDL_Surface* s)
292 {
293 const int npixels = s->w*s->h;
294 for(int n = 0; n != npixels; ++n) {
295 //we use a color in our sprite sheets to indicate transparency, rather than an alpha channel
296 static const unsigned char AlphaPixel[] = {0x6f, 0x6d, 0x51}; //the background color, brown
297 static const unsigned char AlphaPixel2[] = {0xf9, 0x30, 0x3d}; //the border color, red
298 unsigned char* pixel = reinterpret_cast<unsigned char*>(s->pixels) + n*4;
299
300 if(pixel[0] == AlphaPixel[0] && pixel[1] == AlphaPixel[1] && pixel[2] == AlphaPixel[2] ||
301 pixel[0] == AlphaPixel2[0] && pixel[1] == AlphaPixel2[1] && pixel[2] == AlphaPixel2[2]) {
302 pixel[3] = 0;
303 }
304 }
305 }
306
307 void texture::initialize(const key& k)
308 {
309 assert(graphics_initialized);
310 if(k.empty() ||
311 std::find(k.begin(),k.end(),surface()) != k.end()) {
312 return;
313 }
314
315 npot_allowed = is_npot_allowed();
316
317 width_ = k.front()->w;
318 height_ = k.front()->h;
319 alpha_map_.reset(new std::vector<bool>(width_*height_));
320
321 unsigned int surf_width = width_;
322 unsigned int surf_height = height_;
323 if(!npot_allowed) {
324 surf_width = next_power_of_2(surf_width);
325 surf_height = next_power_of_2(surf_height);
326 // surf_width = surf_height =
327 // std::max(next_power_of_2(surf_width),
328 // next_power_of_2(surf_height));
329 ratio_w_ = GLfloat(width_)/GLfloat(surf_width);
330 ratio_h_ = GLfloat(height_)/GLfloat(surf_height);
331 }
332
333 surface s(SDL_CreateRGBSurface(SDL_SWSURFACE,surf_width,surf_height,32,SURFACE_MASK));
334 if(k.size() == 1 && k.front()->format->Rmask == 0xFF && k.front()->format->Gmask == 0xFF00 && k.front()->format->Bmask == 0xFF0000 && k.front()->format->Amask == 0) {
335 add_alpha_channel_to_surface((uint8_t*)s->pixels, (uint8_t*)k.front()->pixels, s->w, k.front()->w, k.front()->h, k.front()->pitch);
336 } else if(k.size() == 1 && k.front()->format->Rmask == 0xFF00 && k.front()->format->Gmask == 0xFF0000 && k.front()->format->Bmask == 0xFF000000 && k.front()->format->Amask == 0xFF) {
337 //alpha channel already exists, so no conversion necessary.
338 s = k.front();
339 } else {
340 for(key::const_iterator i = k.begin(); i != k.end(); ++i) {
341 if(i == k.begin()) {
342 SDL_SetAlpha(i->get(), 0, SDL_ALPHA_OPAQUE);
343 } else {
344 SDL_SetAlpha(i->get(), SDL_SRCALPHA, SDL_ALPHA_OPAQUE);
345 }
346
347 SDL_BlitSurface(i->get(),NULL,s.get(),NULL);
348 }
349 }
350
351 set_alpha_for_transparent_colors_in_rgba_surface(s.get());
352
353 const int npixels = s->w*s->h;
354 for(int n = 0; n != npixels; ++n) {
355 const unsigned char* pixel = reinterpret_cast<const unsigned char*>(s->pixels) + n*4;
356 if(pixel[3] == 0) {
357 const int x = n%s->w;
358 const int y = n/s->w;
359 if(x < width_ && y < height_) {
360 (*alpha_map_)[y*width_ + x] = true;
361 }
362 }
363 }
364
365 if(!id_) {
366 id_.reset(new ID);
367 }
368
369 id_->s = s;
370
371 current_texture = 0;
372 }
373
374 int next_pot (int n)
375 {
376 int num = 1;
377 while (num < n)
378 {
379 num *= 2;
380 }
381 return num;
382 }
383
384 namespace {
385 threading::mutex id_to_build_mutex;
386 }
387
388 GLuint texture::get_id() const
389 {
390 if(!valid()) {
391 return 0;
392 }
393
394 if(id_->init() == false) {
395 id_->id = get_texture_id();
396 if(preferences::use_pretty_scaling()) {
397 id_->s = scale_surface(id_->s);
398 }
399
400 if(!pthread_equal(graphics_thread_id, pthread_self())) {
401 threading::lock lck(id_to_build_mutex);
402 id_to_build_.push_back(id_);
403 } else {
404 id_->build_id();
405 }
406 }
407
408 return id_->id;
409 }
410
411 void texture::build_textures_from_worker_threads()
412 {
413 ASSERT_LOG(pthread_equal(pthread_self(), graphics_thread_id), "CALLED build_textures_from_worker_threads from thread other than the main one");
414 threading::lock lck(id_to_build_mutex);
415 foreach(boost::shared_ptr<ID> id, id_to_build_) {
416 id->build_id();
417 }
418
419 id_to_build_.clear();
420 }
421
422 void texture::set_current_texture(GLuint id)
423 {
424 if(!id || current_texture == id) {
425 return;
426 }
427
428 glBindTexture(GL_TEXTURE_2D,id);
429 current_texture = id;
430 }
431
432 void texture::set_as_current_texture() const
433 {
434 width_multiplier = ratio_w_;
435 height_multiplier = ratio_h_;
436
437 const GLuint id = get_id();
438 if(!id || current_texture == id) {
439 return;
440 }
441
442 current_texture = id;
443
444 glBindTexture(GL_TEXTURE_2D,id);
445 //std::cerr << gluErrorString(glGetError()) << "~set_as_current_texture~\n";
446 }
447
448 texture texture::get(const std::string& str)
449 {
450 texture result = texture_cache().get(str);
451 if(!result.valid()) {
452 key surfs;
453 surfs.push_back(surface_cache::get_no_cache(str));
454 result = texture(surfs);
455 texture_cache().put(str, result);
456 std::cerr << (next_power_of_2(result.width())*next_power_of_2(result.height())*2)/1024 << "KB TEXTURE " << str << ": " << result.width() << "x" << result.height() << "\n";
457 }
458
459 return result;
460 }
461
462 texture texture::get(const std::string& str, const std::string& algorithm)
463 {
464 if(algorithm.empty()) {
465 return get(str);
466 }
467
468 std::pair<std::string,std::string> k(str, algorithm);
469 texture result = algorithm_texture_cache().get(k);
470 if(!result.valid()) {
471 key surfs;
472 surfs.push_back(get_surface_formula(surface_cache::get_no_cache(str), algorithm));
473 result = texture(surfs);
474 algorithm_texture_cache().put(k, result);
475 }
476
477 return result;
478 }
479
480 texture texture::get_palette_mapped(const std::string& str, int palette)
481 {
482 std::cerr << "get palette mapped: " << str << "," << palette << "\n";
483 std::pair<std::string,int> k(str, palette);
484 texture result = palette_texture_cache().get(k);
485 if(!result.valid()) {
486 key surfs;
487 surface s = surface_cache::get_no_cache(str);
488 surfs.push_back(map_palette(s, palette));
489 result = texture(surfs);
490 palette_texture_cache().put(k, result);
491 }
492
493 return result;
494 }
495
496 texture texture::get_no_cache(const key& surfs)
497 {
498 return texture(surfs);
499 }
500
501 texture texture::get_no_cache(const surface& surf)
502 {
503 return texture(key(1,surf));
504 }
505
506 GLfloat texture::get_coord_x(GLfloat x)
507 {
508 return npot_allowed ? x : x*width_multiplier;
509 }
510
511 GLfloat texture::get_coord_y(GLfloat y)
512 {
513 return npot_allowed ? y : y*height_multiplier;
514 }
515
516 GLfloat texture::translate_coord_x(GLfloat x) const
517 {
518 return npot_allowed ? x : x*ratio_w_;
519 }
520
521 GLfloat texture::translate_coord_y(GLfloat y) const
522 {
523 return npot_allowed ? y : y*ratio_h_;
524 }
525
526 void texture::clear_cache()
527 {
528 texture_cache().clear();
529 }
530
531 const unsigned char* texture::color_at(int x, int y) const
532 {
533 if(!id_ || !id_->s) {
534 return NULL;
535 }
536
537 const unsigned char* pixels = reinterpret_cast<const unsigned char*>(id_->s->pixels);
538 return pixels + (y*id_->s->w + x)*id_->s->format->BytesPerPixel;
539 }
540
541 texture::ID::~ID()
542 {
543 destroy();
544 }
545
546 namespace {
547
548 //a table which maps an 8bit color channel to a 4bit one.
549 const unsigned char table_8bits_to_4bits[256] = {
550 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, };
551
552 }
553
554 unsigned int map_color_to_16bpp(unsigned int color)
555 {
556 return table_8bits_to_4bits[(color >> 24)&0xFF] << 28 |
557 table_8bits_to_4bits[(color >> 24)&0xFF] << 24 |
558 table_8bits_to_4bits[(color >> 16)&0xFF] << 20 |
559 table_8bits_to_4bits[(color >> 16)&0xFF] << 16 |
560 table_8bits_to_4bits[(color >> 8)&0xFF] << 12 |
561 table_8bits_to_4bits[(color >> 8)&0xFF] << 8 |
562 table_8bits_to_4bits[(color >> 0)&0xFF] << 4 |
563 table_8bits_to_4bits[(color >> 0)&0xFF] << 0;
564 }
565
566 void texture::ID::build_id()
567 {
568 glBindTexture(GL_TEXTURE_2D,id);
569 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
570 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
571
572
573 if(preferences::use_16bpp_textures()) {
574 std::vector<GLushort> buf(s->w*s->h);
575 const GLuint* src = reinterpret_cast<const GLuint*>(s->pixels);
576 GLushort* dst = &*buf.begin();
577 for(int n = 0; n != s->w*s->h; ++n) {
578 const GLuint p =
579 table_8bits_to_4bits[(*src >> 24)&0xFF] << 28 |
580 table_8bits_to_4bits[(*src >> 16)&0xFF] << 20 |
581 table_8bits_to_4bits[(*src >> 8)&0xFF] << 12 |
582 table_8bits_to_4bits[(*src >> 0)&0xFF] << 4;
583 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
584 *dst = (((p >> 28)&0xF) << 12) |
585 (((p >> 20)&0xF) << 8) |
586 (((p >> 12)&0xF) << 4) |
587 (((p >> 4)&0xF) << 0);
588 #else
589 *dst = (((p >> 28)&0xF) << 0) |
590 (((p >> 20)&0xF) << 4) |
591 (((p >> 12)&0xF) << 8) |
592 (((p >> 4)&0xF) << 12);
593 #endif
594 ++dst;
595 ++src;
596 }
597 #ifndef WIN32
598 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, s->w, s->h, 0, GL_RGBA,
599 GL_UNSIGNED_SHORT_4_4_4_4, &buf[0]);
600 #endif
601 } else {
602 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, s->w, s->h, 0, GL_RGBA,
603 GL_UNSIGNED_BYTE, s->pixels);
604 }
605
606 //free the surface.
607 if(!preferences::compiling_tiles) {
608 std::cerr << "RELEASING SURFACE... " << s.get()->refcount << "\n";
609 s = surface();
610 }
611 }
612
613 void texture::ID::destroy()
614 {
615 if(graphics_initialized && init()) {
616 avail_textures.push_back(id);
617 }
618
619 id = GLuint(-1);
620 s = surface();
621 }
622
623 std::vector<boost::shared_ptr<texture::ID> > texture::id_to_build_;
624
625 }
626
627 BENCHMARK(texture_copy_ctor)
628 {
629 graphics::texture t(graphics::texture::get("characters/frogatto-spritesheet1.png"));
630 BENCHMARK_LOOP {
631 graphics::texture t2(t);
632 }
633 }
634
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef TEXTURE_HPP_INCLUDED
13 #define TEXTURE_HPP_INCLUDED
14
15 #include <bitset>
16 #include <string>
17 #include <boost/shared_ptr.hpp>
18 #include <vector>
19
20 #include <GL/gl.h>
21
22 #include "surface.hpp"
23
24 namespace graphics
25 {
26
27 class texture
28 {
29 public:
30 //error thrown if an operation is done from a worker thread that
31 //must be completed by the main graphics thread.
32 struct worker_thread_error {};
33
34 //create an instance of this object before using the first texture,
35 //and destroy it before the program exits
36 struct manager {
37 manager();
38 ~manager();
39 };
40
41 static void clear_textures();
42
43 //complete construction of any textures that were accessed in worker threads
44 //but which need to be completed in the main thread. May only be called
45 //in the main thread.
46 static void build_textures_from_worker_threads();
47
48 texture();
49 texture(const texture& t);
50 ~texture();
51
52 typedef std::vector<surface> key;
53
54 GLuint get_id() const;
55 static void set_current_texture(GLuint id);
56 void set_as_current_texture() const;
57 bool valid() const { return id_; }
58
59 static texture get(const std::string& str);
60 static texture get(const std::string& str, const std::string& algorithm);
61 static texture get_palette_mapped(const std::string& str, int palette);
62 static texture get_no_cache(const surface& surf);
63 static GLfloat get_coord_x(GLfloat x);
64 static GLfloat get_coord_y(GLfloat y);
65 GLfloat translate_coord_x(GLfloat x) const;
66 GLfloat translate_coord_y(GLfloat y) const;
67 static void clear_cache();
68
69 unsigned int width() const { return width_; }
70 unsigned int height() const { return height_; }
71
72 bool is_alpha(int x, int y) const { return (*alpha_map_)[y*width_ + x]; }
73 std::vector<bool>::const_iterator get_alpha_row(int x, int y) const { return alpha_map_->begin() + y*width_ + x; }
74 std::vector<bool>::const_iterator end_alpha() const { return alpha_map_->end(); }
75
76 const unsigned char* color_at(int x, int y) const;
77
78 friend bool operator==(const texture&, const texture&);
79 friend bool operator<(const texture&, const texture&);
80
81 void initialize(const key&);
82 explicit texture(const key& surfs);
83
84 private:
85 static texture get_no_cache(const key& k);
86
87 struct ID {
88 ID() : id(GLuint(-1)) {
89 }
90
91 explicit ID(GLuint id) : id(id) {
92 }
93
94 ~ID();
95
96 void build_id();
97 void destroy();
98
99 bool init() const { return id != GLuint(-1); }
100
101 GLuint id;
102
103 //before we've constructed the ID, we can store the
104 //surface in here.
105 surface s;
106 };
107 mutable boost::shared_ptr<ID> id_;
108 unsigned int width_, height_;
109 GLfloat ratio_w_, ratio_h_;
110
111 boost::shared_ptr<std::vector<bool> > alpha_map_;
112
113 //a list of ID objects that we assigned GL ID's to in a worker thread,
114 //but which need binding to a texture in the main thread.
115 static std::vector<boost::shared_ptr<ID> > id_to_build_;
116 };
117
118 inline bool operator==(const texture& a, const texture& b)
119 {
120 return a.id_ == b.id_;
121 }
122
123 inline bool operator!=(const texture& a, const texture& b)
124 {
125 return !operator==(a, b);
126 }
127
128 inline bool operator<(const texture& a, const texture& b)
129 {
130 if(!a.id_) {
131 return false;
132 } else if(!b.id_) {
133 return true;
134 }
135
136 return a.id_->id < b.id_->id;
137 }
138
139 unsigned int map_color_to_16bpp(unsigned int color);
140
141 }
142
143 #endif
0 /* $Id: thread.cpp 31858 2009-01-01 10:27:41Z mordante $ */
1 /*
2 Copyright (C) 2003 - 2009 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2
7 or at your option any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY.
10
11 See the COPYING file for more details.
12 */
13
14 #include <boost/scoped_ptr.hpp>
15
16 #include <iostream>
17 #include <vector>
18
19 #include "thread.hpp"
20
21 namespace {
22
23 std::vector<SDL_Thread*> detached_threads;
24
25 }
26
27 namespace threading {
28
29 manager::~manager()
30 {
31 for(std::vector<SDL_Thread*>::iterator i = detached_threads.begin(); i != detached_threads.end(); ++i) {
32 SDL_WaitThread(*i,NULL);
33 }
34 }
35
36 namespace {
37
38 int call_boost_function(void* arg)
39 {
40 boost::scoped_ptr<boost::function<void()> > fn((boost::function<void()>*)arg);
41 (*fn)();
42 return 0;
43 }
44
45 }
46
47 thread::thread(boost::function<void()> fn) : fn_(fn), thread_(SDL_CreateThread(call_boost_function, new boost::function<void()>(fn_)))
48 {}
49
50 thread::~thread()
51 {
52 join();
53 }
54
55 void thread::kill()
56 {
57 if(thread_ != NULL) {
58 SDL_KillThread(thread_);
59 thread_ = NULL;
60 }
61 }
62
63 void thread::join()
64 {
65 if(thread_ != NULL) {
66 SDL_WaitThread(thread_,NULL);
67 thread_ = NULL;
68 }
69 }
70
71 void thread::detach()
72 {
73 detached_threads.push_back(thread_);
74 thread_ = NULL;
75 }
76
77 mutex::mutex() : m_(SDL_CreateMutex())
78 {}
79
80 mutex::~mutex()
81 {
82 SDL_DestroyMutex(m_);
83 }
84
85 lock::lock(mutex& m) : m_(m)
86 {
87 SDL_mutexP(m_.m_);
88 }
89
90 lock::~lock()
91 {
92 SDL_mutexV(m_.m_);
93 }
94
95 condition::condition() : cond_(SDL_CreateCond())
96 {}
97
98 condition::~condition()
99 {
100 SDL_DestroyCond(cond_);
101 }
102
103 bool condition::wait(const mutex& m)
104 {
105 return SDL_CondWait(cond_,m.m_) == 0;
106 }
107
108 condition::WAIT_TIMEOUT_RESULT condition::wait_timeout(const mutex& m, unsigned int timeout)
109 {
110 const int res = SDL_CondWaitTimeout(cond_,m.m_,timeout);
111 switch(res) {
112 case 0: return THREAD_WAIT_OK;
113 case SDL_MUTEX_TIMEDOUT: return THREAD_WAIT_TIMEOUT;
114 default:
115 std::cerr << "SDL_CondWaitTimeout: " << SDL_GetError() << "\n";
116 return THREAD_WAIT_ERROR;
117 }
118 }
119
120 bool condition::notify_one()
121 {
122 if(SDL_CondSignal(cond_) < 0) {
123 std::cerr << "SDL_CondSignal: " << SDL_GetError() << "\n";
124 return false;
125 }
126
127 return true;
128 }
129
130 bool condition::notify_all()
131 {
132 if(SDL_CondBroadcast(cond_) < 0) {
133 std::cerr << "SDL_CondBroadcast: " << SDL_GetError() << "\n";
134 return false;
135 }
136 return true;
137 }
138
139 }
0 /* $Id: thread.hpp 31859 2009-01-01 10:28:26Z mordante $ */
1 /*
2 Copyright (C) 2003 - 2009 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2
7 or at your option any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY.
10
11 See the COPYING file for more details.
12 */
13
14 #ifndef THREAD_HPP_INCLUDED
15 #define THREAD_HPP_INCLUDED
16
17 #include "SDL.h"
18 #include "SDL_thread.h"
19
20 #include <list>
21
22 #include <boost/function.hpp>
23 #include <boost/scoped_ptr.hpp>
24 #include <boost/smart_ptr.hpp>
25
26 // Threading primitives wrapper for SDL_Thread.
27 //
28 // This module defines primitives for wrapping C++ around SDL's threading
29 // interface
30 namespace threading
31 {
32
33 struct manager
34 {
35 ~manager();
36 };
37
38 // Threading object.
39 //
40 // This class defines threading objects. One such object represents a
41 // thread and admits killing and joining on threads. Intended to be
42 // used for manipulating threads instead of poking around with SDL_Thread
43 // calls.
44 class thread
45 {
46 public:
47 // Construct a new thread to start executing the function
48 // pointed to by f. The void* data will be passed to f, to
49 // facilitate passing of parameters to f.
50 //
51 // \param f the function at which the thread should start executing
52 // \param data passed to f
53 //
54 // \pre f != NULL
55 explicit thread(boost::function<void ()> f);
56
57 // Destroy the thread object. This is done by waiting on the
58 // thread with the join() operation, thus blocking until the
59 // thread object has finished its operation.
60 ~thread();
61
62 // Kill the thread. If the thread has already been killed, this
63 // is a no-op.
64 void kill();
65
66 // Join (wait) on the thread to finish. When the thread finishes,
67 // the function will return. calling wait() on an already killed
68 // thread is a no-op.
69 void join();
70
71 void detach();
72
73 Uint32 get_id() { return SDL_GetThreadID(thread_); }
74 private:
75 thread(const thread&);
76 void operator=(const thread&);
77
78 boost::function<void ()> fn_;
79 SDL_Thread* thread_;
80 };
81
82 inline Uint32 get_current_thread_id() { return SDL_ThreadID(); }
83 // Binary mutexes.
84 //
85 // Implements an interface to binary mutexes. This class only defines the
86 // mutex itself. Locking is handled through the friend class lock,
87 // and monitor interfacing through condition variables is handled through
88 // the friend class condition.
89 class mutex
90 {
91 public:
92 mutex();
93 ~mutex();
94
95 friend class lock;
96 friend class condition;
97
98 private:
99 mutex(const mutex&);
100 void operator=(const mutex&);
101
102 SDL_mutex* const m_;
103 };
104
105 // Binary mutex locking.
106 //
107 // Implements a locking object for mutexes. The creation of a lock
108 // object on a mutex will lock the mutex as long as this object is
109 // not deleted.
110 class lock
111 {
112 public:
113 // Create a lock object on the mutex given as a parameter to
114 // the constructor. The lock will be held for the duration
115 // of the object existence.
116 // If the mutex is already locked, the constructor will
117 // block until the mutex lock can be acquired.
118 //
119 // \param m the mutex on which we should try to lock.
120 explicit lock(mutex& m);
121 // Delete the lock object, thus releasing the lock aquired
122 // on the mutex which the lock object was created with.
123 ~lock();
124 private:
125 lock(const lock&);
126 void operator=(const lock&);
127
128 mutex& m_;
129 };
130
131 // Condition variable locking.
132 //
133 // Implements condition variables for mutexes. A condition variable
134 // allows you to free up a lock inside a critical section
135 // of the code and regain it later. Condition classes only make
136 // sense to do operations on, if one already aquired a mutex.
137 class condition
138 {
139 public:
140 condition();
141 ~condition();
142
143 // Wait on the condition. When the condition is met, you
144 // have a lock on the mutex and can do work in the critical
145 // section. When the condition is not met, wait blocks until
146 // the condition is met and atomically frees up the lock on
147 // the mutex. One will automatically regain the lock when the
148 // thread unblocks.
149 //
150 // If wait returns false we have an error. In this case one cannot
151 // assume that he has a lock on the mutex anymore.
152 //
153 // \param m the mutex you wish to free the lock for
154 // \returns true: the wait was successful, false: an error occurred
155 //
156 // \pre You have already aquired a lock on mutex m
157 //
158 bool wait(const mutex& m);
159
160 enum WAIT_TIMEOUT_RESULT { THREAD_WAIT_OK, THREAD_WAIT_TIMEOUT, THREAD_WAIT_ERROR };
161
162 // wait on the condition with a timeout. Basically the same as the
163 // wait() function, but if the lock is not aquired before the
164 // timeout, the function returns with an error.
165 //
166 // \param m the mutex you wish free the lock for.
167 // \param timeout the allowed timeout in milliseconds (ms)
168 // \returns result based on whether condition was met, it timed out,
169 // or there was an error
170 WAIT_TIMEOUT_RESULT wait_timeout(const mutex& m, unsigned int timeout);
171 // signal the condition and wake up one thread waiting on the
172 // condition. If no thread is waiting, notify_one() is a no-op.
173 // Does not unlock the mutex.
174 bool notify_one();
175
176 // signal all threads waiting on the condition and let them contend
177 // for the lock. This is often used when varying resource amounts are
178 // involved and you do not know how many processes might continue.
179 // The function should be used with care, especially if many threads are
180 // waiting on the condition variable.
181 bool notify_all();
182
183 private:
184 condition(const condition&);
185 void operator=(const condition&);
186
187 SDL_cond* const cond_;
188 };
189
190 //class which defines an interface for waiting on an asynchronous operation
191 class waiter {
192 public:
193 enum ACTION { WAIT, ABORT };
194
195 virtual ~waiter() {}
196 virtual ACTION process() = 0;
197 };
198
199 }
200
201 #endif
0 #include <boost/regex.hpp>
1 #include <iostream>
2 #include <math.h>
3 #include <sstream>
4 #include <set>
5
6 #include "asserts.hpp"
7 #include "foreach.hpp"
8 #include "formatter.hpp"
9 #include "formula.hpp"
10 #include "formula_callable.hpp"
11 #include "formula_function.hpp"
12 #include "multi_tile_pattern.hpp"
13 #include "point_map.hpp"
14 #include "random.hpp"
15 #include "string_utils.hpp"
16 #include "tile_map.hpp"
17 #include "wml_node.hpp"
18 #include "wml_parser.hpp"
19 #include "wml_utils.hpp"
20
21 namespace {
22
23 typedef std::map<const boost::regex*, bool> regex_match_map;
24 std::map<boost::array<char, 4>, regex_match_map> re_matches;
25
26 bool match_regex(boost::array<char, 4> str, const boost::regex* re) {
27 if(reinterpret_cast<intptr_t>(re)&1) {
28 //the low bit in the re pointer is set, meaning this is an inverted
29 //match.
30 return !match_regex(str, reinterpret_cast<const boost::regex*>(reinterpret_cast<intptr_t>(re)-1));
31 }
32
33 std::map<const boost::regex*, bool>& m = re_matches[str];
34 std::map<const boost::regex*, bool>::const_iterator i = m.find(re);
35 if(i != m.end()) {
36 return i->second;
37 }
38
39 const bool match = boost::regex_match(str.data(), str.data() + strlen(str.data()), *re);
40 m[re] = match;
41 return match;
42 }
43
44 static const int TileSize = 32;
45
46 struct is_whitespace {
47 bool operator()(char c) const { return isspace(c); }
48 };
49
50 }
51
52 struct tile_pattern {
53 explicit tile_pattern(wml::const_node_ptr node)
54 : tile(new level_object(node)), reverse(node->attr("reverse").str() != "no"),
55 empty(node->attr("empty").str() == "yes"),
56 filter_formula(game_logic::formula::create_optional_formula(node->attr("filter")))
57 {
58 variations.push_back(tile);
59
60 pattern_str = node->attr("pattern");
61 std::string pattern_str = node->attr("pattern");
62 pattern_str.erase(std::remove_if(pattern_str.begin(), pattern_str.end(), is_whitespace()), pattern_str.end());
63
64 static std::vector<std::string> patterns;
65 patterns.clear();
66 util::split(pattern_str, patterns, ',', 0);
67
68 assert(!patterns.empty());
69
70 //the main pattern is always the very middle one.
71 int main_tile = patterns.size()/2;
72
73 int width = wml::get_int(node, "pattern_width", sqrt(static_cast<float>(patterns.size())));
74 assert(width != 0);
75 int height = patterns.size()/width;
76
77 int top = -height/2;
78 int left = -width/2;
79
80 //pattern with size 12 is a special case
81 if(patterns.size() == 12 && !node->has_attr("pattern_width")) {
82 width = 3;
83 height = 4;
84 top = -1;
85 left = -1;
86 main_tile = 4;
87 }
88
89 current_tile_pattern = &get_regex_from_pool(patterns[main_tile].empty() ? "^$" : patterns[main_tile]);
90
91 for(int n = 0; n != patterns.size(); ++n) {
92 if(n == main_tile) {
93 continue;
94 }
95
96 const int x = left + n%width;
97 const int y = top + n/width;
98 surrounding_tiles.push_back(surrounding_tile(x, y, patterns[n]));
99 }
100
101 wml::node::const_child_iterator i1 = node->begin_child("variation");
102 wml::node::const_child_iterator i2 = node->end_child("variation");
103 while(i1 != i2) {
104 variations.push_back(level_object_ptr(new level_object(i1->second)));
105 ++i1;
106 }
107
108 i1 = node->begin_child("tile");
109 i2 = node->end_child("tile");
110 while(i1 != i2) {
111 added_tile t;
112 t.object = level_object_ptr(new level_object(i1->second));
113 t.zorder = wml::get_int(i1->second, "zorder");
114 added_tiles.push_back(t);
115 ++i1;
116 }
117 }
118
119 const boost::regex* current_tile_pattern;
120
121 struct surrounding_tile {
122 surrounding_tile(int x, int y, const std::string& s)
123 : xoffset(x), yoffset(y), pattern(&get_regex_from_pool(s))
124 {}
125 int xoffset;
126 int yoffset;
127 const boost::regex* pattern;
128 };
129
130 std::vector<surrounding_tile> surrounding_tiles;
131
132 std::string pattern_str;
133 level_object_ptr tile;
134 std::vector<level_object_ptr> variations;
135 bool reverse;
136 bool empty;
137
138 struct added_tile {
139 level_object_ptr object;
140 int zorder;
141 };
142
143 std::vector<added_tile> added_tiles;
144
145 game_logic::const_formula_ptr filter_formula;
146 };
147
148 namespace {
149
150 std::vector<tile_pattern> patterns;
151 int current_patterns_version = 0;
152
153 class filter_callable : public game_logic::formula_callable {
154 const tile_map& m_;
155 int x_, y_;
156 public:
157 filter_callable(const tile_map& m, int x, int y) : m_(m), x_(x), y_(y)
158 {}
159
160 variant get_value(const std::string& key) const {
161 if(key == "tiles") {
162 return variant(&m_);
163 } else if(key == "x") {
164 return variant(x_);
165 } else if(key == "y") {
166 return variant(y_);
167 } else {
168 return variant();
169 }
170 }
171 };
172
173 using namespace game_logic;
174
175 class tile_at_function : public function_expression {
176 public:
177 explicit tile_at_function(const args_list& args)
178 : function_expression("tile_at", args, 2)
179 {}
180
181 private:
182 variant execute(const formula_callable& variables) const {
183 variant v = args()[0]->evaluate(variables);
184 tile_map* m = v.convert_to<tile_map>();
185 return variant(m->get_tile(args()[1]->evaluate(variables).as_int(),
186 args()[2]->evaluate(variables).as_int()));
187 }
188 };
189
190 class tile_map_function_symbol_table : public function_symbol_table
191 {
192 public:
193 expression_ptr create_function(
194 const std::string& fn, const std::vector<expression_ptr>& args, const formula_callable_definition* callable_def) const {
195 if(fn == "tile_at") {
196 return expression_ptr(new tile_at_function(args));
197 } else {
198 return function_symbol_table::create_function(fn, args, callable_def);
199 }
200 }
201 };
202
203 std::map<std::string, std::vector<std::string> > files_index;
204 std::set<std::string> files_loaded;
205
206 }
207
208 void tile_map::load_all()
209 {
210 for(std::map<std::string, std::vector<std::string> >::const_iterator i = files_index.begin(); i != files_index.end(); ++i) {
211 foreach(const std::string& s, i->second) {
212 load(s);
213 }
214 }
215 }
216
217 void tile_map::load(const std::string& fname)
218 {
219 if(files_loaded.count(fname)) {
220 return;
221 }
222
223 files_loaded.insert(fname);
224
225 wml::const_node_ptr node = wml::parse_wml_from_file("data/tiles/" + fname);
226
227 palette_scope palette_setter(node->attr("palettes"));
228
229 wml::node::const_child_iterator p1 = node->begin_child("tile_pattern");
230 wml::node::const_child_iterator p2 = node->end_child("tile_pattern");
231 for(; p1 != p2; ++p1) {
232 const wml::const_node_ptr& p = p1->second;
233 patterns.push_back(tile_pattern(p));
234 }
235
236 const int start = multi_tile_pattern::get_all().size();
237 multi_tile_pattern::load(node);
238 std::cerr << "TILE_MAP_LOAD: " << fname << " -> " << (multi_tile_pattern::get_all().size() - start) << ": " << multi_tile_pattern::get_all().size() << "\n";
239
240 ++current_patterns_version;
241 }
242
243 void tile_map::init(wml::const_node_ptr node)
244 {
245 files_index.clear();
246
247 for(wml::node::const_attr_iterator i = node->begin_attr(); i != node->end_attr(); ++i) {
248 files_index[i->first] = util::split(i->second);
249 }
250
251 patterns.clear();
252 files_loaded.clear();
253
254 multi_tile_pattern::init(node);
255
256 ++current_patterns_version;
257 }
258
259 tile_map::tile_map() : xpos_(0), ypos_(0), x_speed_(100), y_speed_(100), zorder_(0), patterns_version_(-1)
260 {
261 //turn off reference counting
262 add_ref();
263
264 //make an entry for the empty string.
265 pattern_index_.push_back(pattern_index_entry());
266 pattern_index_.back().matching_patterns.push_back(&get_regex_from_pool(""));
267 }
268
269 tile_map::tile_map(wml::const_node_ptr node)
270 : xpos_(wml::get_int(node, "x")), ypos_(wml::get_int(node, "y")),
271 x_speed_(wml::get_int(node, "x_speed", 100)), y_speed_(wml::get_int(node, "y_speed", 100)),
272 zorder_(wml::get_int(node, "zorder"))
273 {
274 //turn off reference counting
275 add_ref();
276
277 const std::string& unique_tiles = node->attr("unique_tiles");
278 foreach(const std::string& tile, util::split(unique_tiles)) {
279 const std::vector<std::string>& files = files_index[tile];
280 foreach(const std::string& file, files) {
281 load(file);
282 }
283 }
284
285 //make an entry for the empty string.
286 pattern_index_.push_back(pattern_index_entry());
287 pattern_index_.back().matching_patterns.push_back(&get_regex_from_pool(""));
288
289 {
290 const std::string& tiles_str = node->attr("tiles");
291 std::vector<std::string> lines;
292 lines.reserve(std::count(tiles_str.begin(), tiles_str.end(), '\n')+1);
293
294 util::split(tiles_str, lines, '\n', 0);
295 foreach(const std::string& line, lines) {
296 map_.resize(map_.size()+1);
297
298 std::vector<tile_string> items;
299 const char* ptr = line.c_str();
300 bool found_end = line.empty();
301 while(!found_end) {
302 const char* end = strchr(ptr, ',');
303 if(end == NULL) {
304 end = line.c_str() + line.size();
305 found_end = true;
306 }
307
308 //We want to copy [ptr,end) to tile_string. First strip the spaces.
309 while(ptr != end && isspace(*ptr)) {
310 ++ptr;
311 }
312
313 while(end != ptr && isspace(*(end-1))) {
314 --end;
315 }
316
317 ASSERT_LOG(end - ptr <= 4, "TILE PATTERN IS TOO LONG: " << std::string(ptr, end));
318
319 tile_string tile;
320 std::fill(tile.begin(), tile.end(), '\0');
321 std::copy(ptr, end, tile.begin());
322 items.push_back(tile);
323
324 if(!found_end) {
325 ptr = end + 1;
326 }
327 }
328
329 foreach(const tile_string& str, items) {
330 int index_entry = 0;
331 foreach(const pattern_index_entry& e, pattern_index_) {
332 if(strcmp(e.str.data(), str.data()) == 0) {
333 break;
334 }
335
336 ++index_entry;
337 }
338
339 if(index_entry == pattern_index_.size()) {
340 pattern_index_.push_back(pattern_index_entry());
341 pattern_index_.back().str = str;
342 }
343
344 map_.back().push_back(index_entry);
345 }
346 }
347
348 build_patterns();
349
350 }
351 }
352
353 void tile_map::build_patterns()
354 {
355 std::vector<const boost::regex*> all_regexes;
356
357 patterns_version_ = current_patterns_version;
358 const int begin_time = SDL_GetTicks();
359 patterns_.clear();
360 foreach(const tile_pattern& p, patterns) {
361 std::vector<const boost::regex*> re;
362 std::vector<const boost::regex*> accepted_re;
363 if(!p.current_tile_pattern->empty()) {
364 re.push_back(p.current_tile_pattern);
365 }
366
367 foreach(const tile_pattern::surrounding_tile& t, p.surrounding_tiles) {
368 re.push_back(t.pattern);
369 }
370
371 int matches = 0;
372 foreach(pattern_index_entry& e, pattern_index_) {
373 foreach(const boost::regex*& regex, re) {
374 if(regex && match_regex(e.str, regex)) {
375 accepted_re.push_back(regex);
376 regex = NULL;
377 ++matches;
378 if(matches == re.size()) {
379 break;
380 }
381 }
382 }
383
384 if(matches == re.size()) {
385 break;
386 }
387 }
388
389 if(matches == re.size()) {
390 all_regexes.insert(all_regexes.end(), accepted_re.begin(), accepted_re.end());
391 patterns_.push_back(&p);
392 }
393 }
394
395 foreach(const multi_tile_pattern& p, multi_tile_pattern::get_all()) {
396 std::vector<const boost::regex*> re;
397 std::vector<const boost::regex*> accepted_re;
398
399 re.reserve(p.width()*p.height());
400 for(int x = 0; x < p.width(); ++x) {
401 for(int y = 0; y < p.height(); ++y) {
402 re.push_back(p.tile_at(x, y).re);
403 }
404 }
405
406 int matches = 0;
407 foreach(pattern_index_entry& e, pattern_index_) {
408 foreach(const boost::regex*& regex, re) {
409 if(regex && match_regex(e.str, regex)) {
410 accepted_re.push_back(regex);
411 regex = NULL;
412 ++matches;
413 if(matches == re.size()) {
414 break;
415 }
416 }
417 }
418
419 if(matches == re.size()) {
420 break;
421 }
422 }
423
424 if(matches == re.size()) {
425 all_regexes.insert(all_regexes.end(), accepted_re.begin(), accepted_re.end());
426 multi_patterns_.push_back(&p);
427 }
428 }
429
430 std::sort(all_regexes.begin(), all_regexes.end());
431 all_regexes.erase(std::unique(all_regexes.begin(), all_regexes.end()), all_regexes.end());
432
433 foreach(pattern_index_entry& e, pattern_index_) {
434 e.matching_patterns.clear();
435
436 foreach(const boost::regex* re, all_regexes) {
437 if(match_regex(e.str, re)) {
438 e.matching_patterns.push_back(re);
439 }
440 }
441 }
442
443 const int end_time = SDL_GetTicks();
444 static int total_time = 0;
445 total_time += (end_time - begin_time);
446 }
447
448 const std::vector<const tile_pattern*>& tile_map::get_patterns() const
449 {
450 if(patterns_version_ != current_patterns_version) {
451 const_cast<tile_map*>(this)->build_patterns();
452 }
453
454 return patterns_;
455 }
456
457 wml::node_ptr tile_map::write() const
458 {
459 wml::node_ptr res(new wml::node("tile_map"));
460 res->set_attr("x", formatter() << xpos_);
461 res->set_attr("y", formatter() << ypos_);
462 res->set_attr("x_speed", formatter() << x_speed_);
463 res->set_attr("y_speed", formatter() << y_speed_);
464 res->set_attr("zorder", formatter() << zorder_);
465
466 std::vector<boost::array<char, 4> > unique_tiles;
467 std::ostringstream tiles;
468 foreach(const std::vector<int>& row, map_) {
469
470 //cut off any empty cells at the end.
471 int size = row.size();
472 while(size > 2 && *pattern_index_[row[size-1]].str.data() == 0) {
473 --size;
474 }
475
476
477 tiles << "\n";
478 for(int i = 0; i != size; ++i) {
479 if(i) {
480 tiles << ",";
481 }
482 tiles << pattern_index_[row[i]].str.data();
483 unique_tiles.push_back(pattern_index_[row[i]].str);
484 }
485
486 if(row.empty()) {
487 tiles << ",";
488 }
489 }
490
491 std::sort(unique_tiles.begin(), unique_tiles.end());
492 unique_tiles.erase(std::unique(unique_tiles.begin(), unique_tiles.end()), unique_tiles.end());
493 std::ostringstream unique_str;
494 for(int n = 0; n != unique_tiles.size(); ++n) {
495 if(n != 0) {
496 unique_str << ",";
497 }
498
499 unique_str << unique_tiles[n].data();
500 }
501
502 res->set_attr("unique_tiles", unique_str.str());
503
504 std::ostringstream variations;
505 foreach(const std::vector<int>& row, variations_) {
506 variations << "\n";
507 for(int i = 0; i != row.size(); ++i) {
508 if(i) {
509 variations << ",";
510 }
511 variations << row[i];
512 }
513
514 if(row.empty()) {
515 variations << ",";
516 }
517 }
518
519 res->set_attr("tiles", tiles.str());
520 res->set_attr("variations", variations.str());
521 return res;
522 }
523
524 const char* tile_map::get_tile_from_pixel_pos(int xpos, int ypos) const
525 {
526 const int x = (xpos - xpos_)/TileSize;
527 const int y = (ypos - ypos_)/TileSize;
528 return get_tile(y, x);
529 }
530
531 const char* tile_map::get_tile(int y, int x) const
532 {
533 if(x < 0 || y < 0 || y >= map_.size() || x >= map_[y].size()) {
534 return "";
535 }
536
537 return pattern_index_[map_[y][x]].str.data();
538 }
539
540 const tile_map::pattern_index_entry& tile_map::get_tile_entry(int y, int x) const
541 {
542 if(x < 0 || y < 0 || y >= map_.size() || x >= map_[y].size()) {
543 return pattern_index_.front();
544 }
545
546 return pattern_index_[map_[y][x]];
547 }
548
549 namespace {
550 struct cstr_less {
551 bool operator()(const char* a, const char* b) const {
552 return strcmp(a, b) < 0;
553 }
554 };
555
556 typedef std::map<const char*, std::vector<const tile_pattern*>, cstr_less> tile_pattern_cache_map;
557 struct tile_pattern_cache {
558 tile_pattern_cache_map cache;
559 };
560
561 }
562
563 int tile_map::get_variations(int x, int y) const
564 {
565 x -= xpos_/TileSize;
566 y -= ypos_/TileSize;
567 tile_pattern_cache cache;
568 bool face_right = false;
569 const tile_pattern* p = get_matching_pattern(x, y, cache, &face_right);
570 if(p == NULL) {
571 return 0;
572 }
573
574 return p->variations.size();
575 }
576
577 int tile_map::variation(int x, int y) const
578 {
579 if(x < 0 || y < 0 || y >= variations_.size() || x >= variations_[y].size()) {
580 return 0;
581 }
582
583 return variations_[y][x];
584 }
585
586 void tile_map::flip_variation(int x, int y, int delta)
587 {
588 const int variations = get_variations(x, y);
589 if(variations <= 1) {
590 return;
591 }
592
593 x -= xpos_/TileSize;
594 y -= ypos_/TileSize;
595
596 if(y >= variations_.size()) {
597 variations_.resize(y + 1);
598 }
599
600 std::vector<int>& row = variations_[y];
601 if(x >= row.size()) {
602 row.resize(x + 1);
603 }
604
605 row[x] += delta;
606 while(row[x] < 0) {
607 row[x] += variations;
608 }
609
610 while(row[x] >= variations) {
611 row[x] = row[x]%variations;
612 }
613 }
614
615 namespace {
616
617 //This function is a random hash. It takes an (x,y) position as well as a
618 //zorder, and then a 'n' integer which is used to identify the number of the
619 //pattern. The idea is that for the same input it always gives the same output,
620 //but the output appears random, and there is a complete avalanche effect --
621 //i.e. any change in the input results in a completely different output.
622 //
623 //The function returns a number in the range [0,99].
624 int random_hash(int x, int y, int z, int n)
625 {
626 //The implementation is simply four arrays of hard coded random numbers.
627 //We index our input into each of the arrays, summing the results and
628 //returning. This does have the implication that
629 //random_hash(x,y,z,n) == random_hash(x+k,y,z,n) where k is the size
630 //of the array. However, this should be acceptable.
631 static const unsigned int x_rng[] = {31,29,62,59,14,2,64,50,17,74,72,47,69,92,89,79,5,21,36,83,81,35,58,44,88,5,51,4,23,54,87,39,44,52,86,6,95,23,72,77,48,97,38,20,45,58,86,8,80,7,65,0,17,85,84,11,68,19,63,30,32,57,62,70,50,47,41,0,39,24,14,6,18,45,56,54,77,61,2,68,92,20,93,68,66,24,5,29,61,48,5,64,39,91,20,69,39,59,96,33,81,63,49,98,48,28,80,96,34,20,65,84,19,87,43,4,54,21,35,54,66,28,42,22,62,13,59,42,17,66,67,67,55,65,20,68,75,62,58,69,95,50,34,46,56,57,71,79,80,47,56,31,35,55,95,60,12,76,53,52,94,90,72,37,8,58,9,70,5,89,61,27,28,51,38,58,60,46,25,86,46,0,73,7,66,91,13,92,78,58,28,2,56,3,70,81,19,98,50,50,4,0,57,49,36,4,51,78,10,7,26,44,28,43,53,56,53,13,6,71,95,36,87,49,62,63,30,45,75,41,59,51,77,0,72,28,24,25,35,4,4,56,87,23,25,21,4,58,57,19,4,97,78,31,38,80,};
632 static const unsigned int y_rng[] = {91,80,42,50,40,7,82,67,81,3,54,31,74,49,30,98,49,93,7,62,10,4,67,93,28,53,74,20,36,62,54,64,60,33,85,31,31,6,22,2,29,16,63,46,83,78,2,11,18,39,62,56,36,56,0,39,26,45,72,46,11,4,49,13,24,40,47,51,17,99,80,64,27,21,20,4,1,37,33,25,9,87,87,36,44,4,77,72,23,73,76,47,28,41,94,69,48,81,82,0,41,7,90,75,4,37,8,86,64,14,1,89,91,0,29,44,35,36,78,89,40,86,19,5,39,52,24,42,44,74,71,96,78,29,54,72,35,96,86,11,49,96,90,79,79,70,50,36,15,50,34,31,86,99,77,97,19,15,32,54,58,87,79,85,49,71,91,78,98,64,18,82,55,66,39,35,86,63,87,41,25,73,79,99,43,2,29,16,53,42,43,26,45,45,95,70,35,75,55,73,58,62,45,86,46,90,12,10,72,88,29,77,10,8,92,72,22,3,1,49,5,51,41,86,65,66,95,23,60,87,64,86,55,30,48,76,21,76,43,52,52,23,40,64,69,43,69,97,34,39,18,87,46,8,96,50,};
633 static const unsigned int z_rng[] = {91,80,42,50,40,7,82,67,81,3,54,31,74,49,30,98,49,93,7,62,10,4,67,93,28,53,74,20,36,62,54,64,60,33,85,31,31,6,22,2,29,16,63,46,83,78,2,11,18,39,62,56,36,56,0,39,26,45,72,46,11,4,49,13,24,40,47,51,17,99,80,64,27,21,20,4,1,37,33,25,9,87,87,36,44,4,77,72,23,73,76,47,28,41,94,69,48,81,82,0,41,7,90,75,4,37,8,86,64,14,1,89,91,0,29,44,35,36,78,89,40,86,19,5,39,52,24,42,44,74,71,96,78,29,54,72,35,96,86,11,49,96,90,79,79,70,50,36,15,50,34,31,86,99,77,97,19,15,32,54,58,87,79,85,49,71,91,78,98,64,18,82,55,66,39,35,86,63,87,41,25,73,79,99,43,2,29,16,53,42,43,26,45,45,95,70,35,75,55,73,58,62,45,86,46,90,12,10,72,88,29,77,10,8,92,72,22,3,1,49,5,51,41,86,65,66,95,23,60,87,64,86,55,30,48,76,21,76,43,52,52,23,40,64,69,43,69,97,34,39,18,87,46,8,96,50,};
634 static const unsigned int n_rng[] = {28,61,82,84,31,6,65,20,50,87,22,52,92,28,39,81,54,48,21,10,5,45,32,62,51,46,60,65,11,67,59,50,48,73,42,40,30,88,33,59,88,33,32,7,15,74,38,6,0,76,66,29,32,40,22,62,62,39,17,24,64,75,35,75,99,57,43,98,6,16,63,72,62,39,10,48,48,82,88,94,26,79,49,98,4,40,8,54,67,85,81,66,69,46,27,76,45,68,76,49,94,59,21,74,26,36,97,34,22,98,84,33,7,17,43,56,75,51,32,74,23,67,29,43,32,89,28,50,11,37,30,2,81,6,4,83,99,7,76,46,32,12,3,33,83,19,0,47,19,32,59,97,92,71,45,93,5,55,53,99,77,96,49,90,16,98,99,6,22,14,5,47,10,49,42,61,7,33,21,84,68,19,22,47,28,8,87,66,65,74,21,21,50,70,64,97,29,54,96,94,42,18,88,79,72,66,93,92,3,93,22,62,73,63,69,27,35,45,27,1,88,23,78,10,61,26,70,67,11,43,16,43,99,42,39,43,89,3,84,90,65,49,67,71,60,45,38,95,32,27,7,30,77,75,24,46,};
635
636 return (
637 x_rng[x%(sizeof(x_rng)/sizeof(*x_rng))] +
638 y_rng[y%(sizeof(y_rng)/sizeof(*y_rng))] +
639 z_rng[z%(sizeof(z_rng)/sizeof(*z_rng))] +
640 n_rng[n%(sizeof(n_rng)/sizeof(*n_rng))]);
641 }
642
643 }
644
645 void tile_map::apply_matching_multi_pattern(int& x, int y,
646 const multi_tile_pattern& pattern,
647 point_map<level_object_ptr>& mapping,
648 std::map<point_zorder, level_object_ptr>& different_zorder_mapping) const
649 {
650
651 if(pattern.chance() < 100 && random_hash(x, y, zorder_, 0)%100 > pattern.chance()) {
652 return;
653 }
654
655 bool match = true;
656 for(int n = 0; n != pattern.try_order().size() && match; ++n) {
657 const int xpos = pattern.try_order()[n].loc.x;
658 const int ypos = pattern.try_order()[n].loc.y;
659
660 const pattern_index_entry& entry = get_tile_entry(y + ypos, x + xpos);
661 if(std::find(entry.matching_patterns.begin(), entry.matching_patterns.end(), pattern.tile_at(xpos, ypos).re) == entry.matching_patterns.end()) {
662 //the regex doesn't match
663 match = false;
664
665 //because this didn't match, we can skip over all patterns that
666 //have the same pattern repeating.
667 x += pattern.try_order()[n].run_length;
668 break;
669 }
670
671 if(pattern.tile_at(xpos, ypos).tiles.empty() == false && mapping.get(point(x + xpos, y + ypos))) {
672 //there is already another pattern filling this tile.
673 match = false;
674 break;
675 }
676 }
677
678 if(match) {
679 const int hash = random_hash(x, y, zorder_, 0);
680 const multi_tile_pattern& chosen_pattern = pattern.choose_random_alternative(hash);
681 for(int xpos = 0; xpos < chosen_pattern.width() && match; ++xpos) {
682 for(int ypos = 0; ypos < chosen_pattern.height() && match; ++ypos) {
683 const multi_tile_pattern::tile_info& info = chosen_pattern.tile_at(xpos, ypos);
684 foreach(const multi_tile_pattern::tile_entry& entry, info.tiles) {
685 level_object_ptr ob = entry.tile;
686 if(ob) {
687 if(entry.zorder == INT_MIN || entry.zorder == zorder_) {
688 mapping.insert(point(x + xpos, y + ypos), ob);
689 } else {
690 different_zorder_mapping[point_zorder(point(x + xpos, y + ypos), entry.zorder)] = ob;
691 }
692 }
693 }
694 }
695 }
696 }
697 }
698
699 void tile_map::build_tiles(std::vector<level_tile>* tiles, const rect* r) const
700 {
701 const int begin_time = SDL_GetTicks();
702 std::cerr << "build tiles... " << patterns_.size() << "/" << patterns.size() << "\n";
703 int width = 0;
704 foreach(const std::vector<int>& row, map_) {
705 if(row.size() > width) {
706 width = row.size();
707 }
708 }
709
710 point_map<level_object_ptr> multi_pattern_matches;
711 std::map<point_zorder, level_object_ptr> different_zorder_multi_pattern_matches;
712
713 std::cerr << "MULTIPATTERNS: " << multi_patterns_.size() << "/" << multi_tile_pattern::get_all().size() << "\n";
714 foreach(const multi_tile_pattern* p, multi_patterns_) {
715 for(int y = -p->height(); y < static_cast<int>(map_.size()) + p->height(); ++y) {
716 const int ypos = ypos_ + y*TileSize;
717
718 if(r && ypos < r->y() || r && ypos > r->y2()) {
719 continue;
720 }
721
722 for(int x = -p->width(); x < width + p->width(); ++x) {
723 apply_matching_multi_pattern(x, y, *p, multi_pattern_matches, different_zorder_multi_pattern_matches);
724 }
725 }
726 }
727
728 //add all tiles in different zorders to our own.
729 for(std::map<point_zorder, level_object_ptr>::const_iterator i = different_zorder_multi_pattern_matches.begin(); i != different_zorder_multi_pattern_matches.end(); ++i) {
730 const level_object_ptr& obj = i->second;
731 const int x = i->first.first.x;
732 const int y = i->first.first.y;
733
734 const int xpos = xpos_ + x*TileSize;
735 const int ypos = ypos_ + y*TileSize;
736
737 level_tile t;
738 t.x = xpos;
739 t.y = ypos;
740 t.layer_from = zorder_;
741 t.zorder = i->first.second;
742 t.object = i->second;
743 t.face_right = false;
744 tiles->push_back(t);
745 }
746
747
748 tile_pattern_cache cache;
749
750 int ntiles = 0;
751 for(int y = -1; y <= static_cast<int>(map_.size()); ++y) {
752 const int ypos = ypos_ + y*TileSize;
753
754 if(r && ypos < r->y() || r && ypos > r->y2()) {
755 continue;
756 }
757
758 for(int x = -1; x <= width; ++x) {
759 const int xpos = xpos_ + x*TileSize;
760
761 const level_object_ptr& obj = multi_pattern_matches.get(point(x, y));
762 if(obj) {
763 level_tile t;
764 t.x = xpos;
765 t.y = ypos;
766 t.layer_from = zorder_;
767 t.zorder = zorder_;
768 t.object = obj;
769 t.face_right = false;
770 tiles->push_back(t);
771 continue;
772 }
773
774 bool face_right = true;
775 const tile_pattern* p = get_matching_pattern(x, y, cache, &face_right);
776 if(p == NULL) {
777 continue;
778 }
779
780
781 if(r && xpos < r->x() || r && xpos > r->x2()) {
782 continue;
783 }
784
785 ++ntiles;
786
787 level_tile t;
788 t.x = xpos;
789 t.y = ypos;
790 t.layer_from = zorder_;
791 t.zorder = zorder_;
792
793 int variation_num = variation(x, y);
794 if(variation_num >= p->variations.size()) {
795 variation_num = 0;
796 }
797 assert(p->variations[variation_num]);
798 t.object = p->variations[variation_num];
799 t.face_right = face_right;
800 if(t.object->flipped()) {
801 t.face_right = !t.face_right;
802 }
803 tiles->push_back(t);
804
805 foreach(const tile_pattern::added_tile& a, p->added_tiles) {
806 std::cerr << "added_tile\n";
807 level_tile t;
808 t.x = xpos;
809 t.y = ypos;
810 t.layer_from = zorder_;
811 t.zorder = zorder_;
812 if(a.zorder) {
813 t.zorder = a.zorder;
814 }
815 t.object = a.object;
816 t.face_right = face_right;
817 if(t.object->flipped()) {
818 t.face_right = !t.face_right;
819 }
820
821 tiles->push_back(t);
822 }
823 }
824 }
825 std::cerr << "done build tiles: " << ntiles << " " << (SDL_GetTicks() - begin_time) << "\n";
826 }
827
828 const tile_pattern* tile_map::get_matching_pattern(int x, int y, tile_pattern_cache& cache, bool* face_right) const
829 {
830
831 if (!*get_tile(y, x) &&
832 !*get_tile(y-1, x) &&
833 !*get_tile(y+1, x) &&
834 !*get_tile(y, x-1) &&
835 !*get_tile(y, x+1)) {
836 return NULL;
837 }
838
839 const int xpos = xpos_ + x*TileSize;
840
841 filter_callable callable(*this, x, y);
842
843 const char* current_tile = get_tile(y,x);
844
845 //we build a cache of all of the patterns which have some chance of
846 //matching the current tile.
847 tile_pattern_cache_map::iterator itor = cache.cache.find(current_tile);
848 if(itor == cache.cache.end()) {
849 itor = cache.cache.insert(std::pair<const char*,std::vector<const tile_pattern*> >(current_tile, std::vector<const tile_pattern*>())).first;
850 foreach(const tile_pattern* p, get_patterns()) {
851 if(!p->current_tile_pattern->empty() && !boost::regex_match(current_tile, current_tile + strlen(current_tile), *p->current_tile_pattern)) {
852 continue;
853 }
854
855 itor->second.push_back(&*p);
856 }
857 }
858
859 const std::vector<const tile_pattern*>& matching_patterns = itor->second;
860
861 foreach(const tile_pattern* ptr, matching_patterns) {
862 const tile_pattern& p = *ptr;
863 if(p.filter_formula && p.filter_formula->execute(callable).as_bool() == false) {
864 continue;
865 }
866
867 bool match = true;
868 foreach(const tile_pattern::surrounding_tile& t, p.surrounding_tiles) {
869 const pattern_index_entry& entry = get_tile_entry(y + t.yoffset, x + t.xoffset);
870 if(std::find(entry.matching_patterns.begin(), entry.matching_patterns.end(), t.pattern) == entry.matching_patterns.end()) {
871 match = false;
872 break;
873 }
874 }
875
876 if(match) {
877 if(p.empty) {
878 return NULL;
879 }
880
881 *face_right = false;
882 return &p;
883 }
884
885 if(p.reverse) {
886 match = true;
887
888 foreach(const tile_pattern::surrounding_tile& t, p.surrounding_tiles) {
889 const pattern_index_entry& entry = get_tile_entry(y + t.yoffset, x - t.xoffset);
890 if(std::find(entry.matching_patterns.begin(), entry.matching_patterns.end(), t.pattern) == entry.matching_patterns.end()) {
891 match = false;
892 break;
893 }
894 }
895 }
896
897 if(match) {
898 if(p.empty) {
899 return NULL;
900 }
901
902 *face_right = true;
903 return &p;
904 }
905 }
906
907 return NULL;
908 }
909
910 bool tile_map::set_tile(int xpos, int ypos, const std::string& str)
911 {
912 if(str.empty() && (xpos < xpos_ || ypos < ypos_)) {
913 return false;
914 }
915
916 tile_string empty_tile;
917 std::fill(empty_tile.begin(), empty_tile.end(), '\0');
918 if(xpos < xpos_) {
919 const int add_tiles = abs((xpos - xpos_)/TileSize);
920 std::vector<int> insert(add_tiles, get_pattern_index_entry(empty_tile));
921 foreach(std::vector<int>& row, map_) {
922 row.insert(row.begin(), insert.begin(), insert.end());
923 }
924
925 xpos_ = xpos;
926 }
927
928 while(ypos < ypos_) {
929 map_.insert(map_.begin(), std::vector<int>());
930 ypos_ -= TileSize;
931 }
932
933 const int x = (xpos - xpos_)/TileSize;
934 const int y = (ypos - ypos_)/TileSize;
935 assert(x >= 0);
936 assert(y >= 0);
937 if(map_.size() <= y) {
938 map_.resize(y + 1);
939 }
940
941 std::vector<int>& row = map_[y];
942
943 tile_string tstr;
944 memset(&tstr[0], 0, tstr.size());
945 std::string::const_iterator end = str.end();
946 if(str.size() > 3) {
947 end = str.begin() + 3;
948 }
949
950 std::copy(str.begin(), end, tstr.begin());
951
952 const int index = get_pattern_index_entry(tstr);
953 if(row.size() > x && row[x] == index) {
954 return false;
955 }
956
957 const int empty_index = get_pattern_index_entry(empty_tile);
958 while(row.size() <= x) {
959 row.push_back(empty_index);
960 }
961
962 row[x] = index;
963
964 // clear out variations info
965 if (y < variations_.size() && x < variations_[y].size()) {
966 variations_[y][x] = 0;
967 }
968 return true;
969 }
970
971 int tile_map::get_pattern_index_entry(const tile_string& str) {
972 int index = 0;
973 foreach(pattern_index_entry& e, pattern_index_) {
974 if(strcmp(e.str.data(), str.data()) == 0) {
975 return index;
976 }
977 ++index;
978 }
979
980 pattern_index_.push_back(pattern_index_entry());
981 pattern_index_.back().str = str;
982 build_patterns();
983 return index;
984 }
0 #ifndef TILE_MAP_HPP_INCLUDED
1 #define TILE_MAP_HPP_INCLUDED
2
3 #include <boost/array.hpp>
4 #include <boost/regex.hpp>
5
6 #include <map>
7 #include <string>
8
9 #include "formula_callable.hpp"
10 #include "geometry.hpp"
11 #include "level_object.hpp"
12 #include "point_map.hpp"
13 #include "wml_node_fwd.hpp"
14
15 struct tile_pattern;
16 struct multi_tile_pattern;
17
18 namespace {
19 struct tile_pattern_cache;
20 }
21
22 class tile_map : public game_logic::formula_callable {
23 public:
24 static void init(wml::const_node_ptr node);
25 static void load_all();
26 static void load(const std::string& fname);
27 tile_map();
28 explicit tile_map(wml::const_node_ptr node);
29 wml::node_ptr write() const;
30 void build_tiles(std::vector<level_tile>* tiles, const rect* r=NULL) const;
31 bool set_tile(int xpos, int ypos, const std::string& str);
32 int zorder() const { return zorder_; }
33 int x_speed() const { return x_speed_; }
34 int y_speed() const { return y_speed_; }
35 void set_zorder(int z) { zorder_ = z; }
36 void set_speed(int x_speed, int y_speed) { x_speed_ = x_speed; y_speed_ = y_speed; }
37 const char* get_tile_from_pixel_pos(int xpos, int ypos) const;
38 const char* get_tile(int y, int x) const;
39 int get_variations(int x, int y) const;
40 void flip_variation(int x, int y, int delta=0);
41 private:
42 void build_patterns();
43 const std::vector<const tile_pattern*>& get_patterns() const;
44
45 int variation(int x, int y) const;
46 const tile_pattern* get_matching_pattern(int x, int y, tile_pattern_cache& cache, bool* face_right) const;
47 variant get_value(const std::string& key) const { return variant(); }
48 int xpos_, ypos_;
49 int x_speed_, y_speed_;
50 int zorder_;
51
52 typedef boost::array<char, 4> tile_string;
53
54 //a map of all of our strings, which maps into pattern_index.
55 std::vector<std::vector<int> > map_;
56
57 //an entry which holds one of the strings found in this map, as well
58 //as the patterns it matches.
59 struct pattern_index_entry {
60 pattern_index_entry() { for(int n = 0; n != str.size(); ++n) { str[n] = 0; } }
61 tile_string str;
62 mutable std::vector<const boost::regex*> matching_patterns;
63 };
64
65 const pattern_index_entry& get_tile_entry(int y, int x) const;
66
67 std::vector<pattern_index_entry> pattern_index_;
68
69 int get_pattern_index_entry(const tile_string& str);
70
71 //the subset of all multi tile patterns which might be valid for this map.
72 std::vector<const multi_tile_pattern*> multi_patterns_;
73
74 typedef std::pair<point, int> point_zorder;
75 //function to apply the first found matching multi pattern.
76 //mapping represents all the tiles added in our zorder.
77 //different_zorder_mapping represents the mappings in different zorders
78 //to this tile_map.
79 void apply_matching_multi_pattern(int& x, int y,
80 const multi_tile_pattern& pattern,
81 point_map<level_object_ptr>& mapping,
82 std::map<point_zorder, level_object_ptr>& different_zorder_mapping) const;
83
84 //the subset of all global patterns which might be valid for this map.
85 std::vector<const tile_pattern*> patterns_;
86
87 //when we generate patterns_ we check the underlying vector's version.
88 //when it is updated it will get a new version and so we'll have to
89 //update our view into it.
90 int patterns_version_;
91
92 std::vector<std::vector<int> > variations_;
93 };
94
95 #endif
0 #include <boost/bind.hpp>
1 #include <boost/function.hpp>
2
3 #include <iostream>
4
5 #include "border_widget.hpp"
6 #include "button.hpp"
7 #include "editor.hpp"
8 #include "foreach.hpp"
9 #include "grid_widget.hpp"
10 #include "label.hpp"
11 #include "preview_tileset_widget.hpp"
12 #include "raster.hpp"
13 #include "tileset_editor_dialog.hpp"
14
15 namespace editor_dialogs
16 {
17
18 tileset_editor_dialog::tileset_editor_dialog(editor& e)
19 : dialog(graphics::screen_width() - 160, 160, 160, 440), editor_(e), first_index_(-1)
20 {
21 if(editor_.all_tilesets().empty() == false) {
22 category_ = editor_.all_tilesets().front().category;
23 }
24
25 init();
26 }
27
28 void tileset_editor_dialog::init()
29 {
30 clear();
31 using namespace gui;
32 set_padding(20);
33
34 assert(editor_.get_tileset() >= 0 && editor_.get_tileset() < editor_.all_tilesets().size());
35
36 button* category_button = new button(widget_ptr(new label(category_, graphics::color_white())), boost::bind(&tileset_editor_dialog::show_category_menu, this));
37 add_widget(widget_ptr(category_button), 10, 10);
38
39 grid_ptr grid(new gui::grid(3));
40 int index = 0, first_index = -1;
41 first_index_ = -1;
42 foreach(const editor::tileset& t, editor_.all_tilesets()) {
43 if(t.category == category_) {
44 if(first_index_ == -1) {
45 first_index_ = index;
46 }
47 preview_tileset_widget* preview = new preview_tileset_widget(*t.preview);
48 preview->set_dim(40, 40);
49 button_ptr tileset_button(new button(widget_ptr(preview), boost::bind(&tileset_editor_dialog::set_tileset, this, index)));
50 tileset_button->set_dim(44, 44);
51 grid->add_col(gui::widget_ptr(new gui::border_widget(tileset_button, index == editor_.get_tileset() ? graphics::color(255,255,255,255) : graphics::color(0,0,0,0))));
52 }
53
54 ++index;
55 }
56
57 grid->finish_row();
58 add_widget(grid);
59 }
60
61 void tileset_editor_dialog::select_category(const std::string& category)
62 {
63 category_ = category;
64 init();
65
66 if(first_index_ != -1) {
67 set_tileset(first_index_);
68 }
69 }
70
71 void tileset_editor_dialog::close_context_menu(int index)
72 {
73 remove_widget(context_menu_);
74 context_menu_.reset();
75 }
76
77 void tileset_editor_dialog::show_category_menu()
78 {
79 using namespace gui;
80 gui::grid* grid = new gui::grid(2);
81 grid->swallow_clicks();
82 grid->set_show_background(true);
83 grid->set_hpad(10);
84 grid->allow_selection();
85 grid->register_selection_callback(boost::bind(&tileset_editor_dialog::close_context_menu, this, _1));
86
87 std::set<std::string> categories;
88 foreach(const editor::tileset& t, editor_.all_tilesets()) {
89 if(categories.count(t.category)) {
90 continue;
91 }
92
93 categories.insert(t.category);
94
95 preview_tileset_widget* preview = new preview_tileset_widget(*t.preview);
96 preview->set_dim(48, 48);
97 grid->add_col(widget_ptr(preview))
98 .add_col(widget_ptr(new label(t.category, graphics::color_white())));
99 grid->register_row_selection_callback(boost::bind(&tileset_editor_dialog::select_category, this, t.category));
100 }
101
102 int mousex, mousey;
103 SDL_GetMouseState(&mousex, &mousey);
104 if(mousex + grid->width() > graphics::screen_width()) {
105 mousex = graphics::screen_width() - grid->width();
106 }
107
108 if(mousey + grid->height() > graphics::screen_height()) {
109 mousey = graphics::screen_height() - grid->height();
110 }
111
112 mousex -= x();
113 mousey -= y();
114
115 remove_widget(context_menu_);
116 context_menu_.reset(grid);
117 add_widget(context_menu_, mousex, mousey);
118 }
119
120 void tileset_editor_dialog::set_tileset(int index)
121 {
122 if(editor_.get_tileset() != index) {
123 editor_.set_tileset(index);
124 init();
125 }
126 }
127
128 bool tileset_editor_dialog::handle_event(const SDL_Event& event, bool claimed)
129 {
130 if(!claimed) {
131 if(context_menu_) {
132 gui::widget_ptr ptr = context_menu_;
133 SDL_Event ev = event;
134 normalize_event(&ev);
135 return ptr->process_event(ev, claimed);
136 }
137
138 switch(event.type) {
139 case SDL_KEYDOWN:
140 if(event.key.keysym.sym == SDLK_COMMA) {
141 editor_.set_tileset(editor_.get_tileset()-1);
142 while(editor_.all_tilesets()[editor_.get_tileset()].category != category_) {
143 editor_.set_tileset(editor_.get_tileset()-1);
144 }
145 set_tileset(editor_.get_tileset());
146 claimed = true;
147 } else if(event.key.keysym.sym == SDLK_PERIOD) {
148 editor_.set_tileset(editor_.get_tileset()+1);
149 while(editor_.all_tilesets()[editor_.get_tileset()].category != category_) {
150 editor_.set_tileset(editor_.get_tileset()+1);
151 }
152 set_tileset(editor_.get_tileset());
153 claimed = true;
154 }
155 break;
156 }
157 }
158
159 return dialog::handle_event(event, claimed);
160 }
161 }
0 #ifndef TILESET_EDITOR_DIALOG_HPP_INCLUDED
1 #define TILESET_EDITOR_DIALOG_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4
5 #include "dialog.hpp"
6 #include "tile_map.hpp"
7 #include "widget.hpp"
8
9 class editor;
10
11 namespace editor_dialogs
12 {
13
14 class tileset_editor_dialog : public gui::dialog
15 {
16 public:
17 explicit tileset_editor_dialog(editor& e);
18 void init();
19 void select_category(const std::string& category);
20 void set_tileset(int index);
21 private:
22 void close_context_menu(int index);
23 void show_category_menu();
24
25 bool handle_event(const SDL_Event& event, bool claimed);
26 editor& editor_;
27
28 gui::widget_ptr context_menu_;
29 std::string category_;
30
31 //index of the first item in the current category
32 int first_index_;
33 };
34
35 }
36
37 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include "SDL.h"
13
14 #include "font.hpp"
15 #include "raster.hpp"
16 #include "tooltip.hpp"
17
18 namespace gui {
19
20 namespace {
21 boost::shared_ptr<std::string> cur_tooltip;
22
23 graphics::texture& text() {
24 static graphics::texture t;
25 return t;
26 }
27 }
28
29 void set_tooltip(const boost::shared_ptr<std::string>& tip)
30 {
31 cur_tooltip = tip;
32 text() = font::render_text(*cur_tooltip, graphics::color_yellow(), 18);
33 }
34
35 void remove_tooltip(const boost::shared_ptr<std::string>& tip)
36 {
37 if(tip == cur_tooltip) {
38 cur_tooltip.reset();
39 text() = graphics::texture();
40 }
41 }
42
43 void draw_tooltip()
44 {
45 if(!cur_tooltip) {
46 return;
47 }
48
49 int mousex, mousey;
50 SDL_GetMouseState(&mousex,&mousey);
51
52 const int pad = 10;
53 const int width = text().width() + pad*2;
54 const int height = text().height() + pad*2;
55 int x = mousex - width/2;
56 int y = mousey - height;
57 if(x < 0) {
58 x = 0;
59 }
60
61 if(x > graphics::screen_width()-width) {
62 x = graphics::screen_width()-width;
63 }
64
65 if(y < 0) {
66 y = 0;
67 }
68
69 if(y > graphics::screen_height()-height) {
70 y = graphics::screen_height()-height;
71 }
72
73 SDL_Rect rect = {x,y,width,height};
74 graphics::draw_rect(rect, graphics::color_black(), 160);
75
76 graphics::blit_texture(text(),x+pad,y+pad);
77 }
78
79 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef TOOLTIP_HPP_INCLUDED
13 #define TOOLTIP_HPP_INCLUDED
14
15 #include <boost/shared_ptr.hpp>
16 #include <string>
17
18 namespace gui {
19
20 void set_tooltip(const boost::shared_ptr<std::string>& str);
21 void remove_tooltip(const boost::shared_ptr<std::string>& str);
22 void draw_tooltip();
23
24 }
25
26 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include <iostream>
13 #include <map>
14
15 #include "translate.hpp"
16
17 namespace i18n {
18
19 namespace {
20 typedef std::map<std::string,std::string> translation_map;
21 translation_map map;
22 }
23
24 void add_translation(const std::string& from, const std::string& to)
25 {
26 std::cerr << "add translation: " << from << " -> " << to << "\n";
27 map[from] = to;
28 }
29
30 const std::string& translate(const std::string& from)
31 {
32 const translation_map::const_iterator i = map.find(from);
33 if(i != map.end()) {
34 return i->second;
35 } else {
36 return from;
37 }
38 }
39
40 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef TRANSLATE_HPP_INCLUDED
13 #define TRANSLATE_HPP_INCLUDED
14
15 #include <string>
16
17 namespace i18n {
18
19 void add_translation(const std::string& from, const std::string& to);
20 const std::string& translate(const std::string& from);
21
22 }
23
24 #endif
0 #include <boost/bind.hpp>
1 #include <iostream>
2 #include <map>
3
4 #include "asserts.hpp"
5 #include "foreach.hpp"
6 #include "unit_test.hpp"
7
8 #include "SDL.h"
9
10 namespace test {
11
12 namespace {
13 typedef std::map<std::string, UnitTest> TestMap;
14 TestMap& get_test_map()
15 {
16 static TestMap map;
17 return map;
18 }
19
20 typedef std::map<std::string, BenchmarkTest> BenchmarkMap;
21 BenchmarkMap& get_benchmark_map()
22 {
23 static BenchmarkMap map;
24 return map;
25 }
26
27 typedef std::map<std::string, CommandLineBenchmarkTest> CommandLineBenchmarkMap;
28 CommandLineBenchmarkMap& get_cl_benchmark_map()
29 {
30 static CommandLineBenchmarkMap map;
31 return map;
32 }
33
34 typedef std::map<std::string, UtilityProgram> UtilityMap;
35 UtilityMap& get_utility_map()
36 {
37 static UtilityMap map;
38 return map;
39 }
40
41 }
42
43 int register_test(const std::string& name, UnitTest test)
44 {
45 get_test_map()[name] = test;
46 return 0;
47 }
48
49 int register_utility(const std::string& name, UtilityProgram utility)
50 {
51 get_utility_map()[name] = utility;
52 return 0;
53 }
54
55 bool run_tests(const std::vector<std::string>* tests)
56 {
57 std::vector<std::string> all_tests;
58 if(!tests) {
59 for(TestMap::const_iterator i = get_test_map().begin(); i != get_test_map().end(); ++i) {
60 all_tests.push_back(i->first);
61 }
62
63 tests = &all_tests;
64 }
65
66 int npass = 0, nfail = 0;
67 foreach(const std::string& test, *tests) {
68 try {
69 get_test_map()[test]();
70 std::cerr << "TEST " << test << " PASSED\n";
71 ++npass;
72 } catch(failure_exception&) {
73 std::cerr << "TEST " << test << " FAILED!!\n";
74 ++nfail;
75 }
76 }
77
78 if(nfail) {
79 std::cerr << npass << " TESTS PASSED, " << nfail << " TESTS FAILED\n";
80 return false;
81 } else {
82 std::cerr << "ALL " << npass << " TESTS PASSED\n";
83 return true;
84 }
85 }
86
87 int register_benchmark(const std::string& name, BenchmarkTest test)
88 {
89 get_benchmark_map()[name] = test;
90 return 0;
91 }
92
93 int register_benchmark_cl(const std::string& name, CommandLineBenchmarkTest test)
94 {
95 get_cl_benchmark_map()[name] = test;
96 return 0;
97 }
98
99 namespace {
100 void run_benchmark(const std::string& name, BenchmarkTest fn)
101 {
102 //run it once without counting it to let any initialization code be run.
103 fn(1);
104
105 std::cerr << "RUNNING BENCHMARK " << name << "...\n";
106 const int MinTicks = 5000;
107 for(int64_t nruns = 10; ; nruns *= 10) {
108 const int start_time = SDL_GetTicks();
109 fn(nruns);
110 const int64_t time_taken_ms = SDL_GetTicks() - start_time;
111 if(time_taken_ms >= MinTicks || nruns > 1000000000) {
112 int64_t time_taken = time_taken_ms*1000000LL;
113 int time_taken_units = 0;
114 int64_t time_taken_per_iter = time_taken/nruns;
115 int time_taken_per_iter_units = 0;
116 while(time_taken > 10000 && time_taken_units < 3) {
117 time_taken /= 1000;
118 time_taken_units++;
119 }
120
121 while(time_taken_per_iter > 10000 && time_taken_per_iter_units < 3) {
122 time_taken_per_iter /= 1000;
123 time_taken_per_iter_units++;
124 }
125
126 const char* units[] = {"ns", "us", "ms", "s"};
127 std::cerr << "BENCH " << name << ": " << nruns << " iterations, " << time_taken_per_iter << units[time_taken_per_iter_units] << "/iteration; total, " << time_taken << units[time_taken_units] << "\n";
128 return;
129 }
130 }
131 }
132 }
133
134 void run_benchmarks(const std::vector<std::string>* benchmarks)
135 {
136 std::vector<std::string> all_benchmarks;
137 if(!benchmarks) {
138 for(BenchmarkMap::const_iterator i = get_benchmark_map().begin(); i != get_benchmark_map().end(); ++i) {
139 all_benchmarks.push_back(i->first);
140 }
141
142 benchmarks = &all_benchmarks;
143 }
144
145 foreach(const std::string& benchmark, *benchmarks) {
146 std::string::const_iterator colon = std::find(benchmark.begin(), benchmark.end(), ':');
147 if(colon != benchmark.end()) {
148 //this benchmark has a user-supplied argument
149 const std::string bench_name(benchmark.begin(), colon);
150 const std::string arg(colon+1, benchmark.end());
151 run_command_line_benchmark(bench_name, arg);
152 } else {
153 run_benchmark(benchmark, get_benchmark_map()[benchmark]);
154 }
155 }
156 }
157
158 void run_command_line_benchmark(const std::string& benchmark_name, const std::string& arg)
159 {
160 run_benchmark(benchmark_name, boost::bind(get_cl_benchmark_map()[benchmark_name], _1, arg));
161 }
162
163 void run_utility(const std::string& utility_name, const std::vector<std::string>& arg)
164 {
165 UtilityProgram util = get_utility_map()[utility_name];
166 if(!util) {
167 std::string known;
168 for(UtilityMap::const_iterator i = get_utility_map().begin(); i != get_utility_map().end(); ++i) {
169 if(i->second) {
170 known += i->first + " ";
171 }
172 }
173 ASSERT_LOG(false, "Unknown utility: '" << utility_name << "'; known utilities: " << known);
174 }
175 util(arg);
176 }
177
178 }
0 #ifndef UNIT_TEST_HPP_INCLUDED
1 #define UNIT_TEST_HPP_INCLUDED
2
3 #include <boost/function.hpp>
4
5 #include <iostream>
6 #include <string>
7 #include <vector>
8
9 namespace test {
10
11 struct failure_exception {
12 };
13
14 typedef boost::function<void ()> UnitTest;
15 typedef boost::function<void (int)> BenchmarkTest;
16 typedef boost::function<void (int, const std::string&)> CommandLineBenchmarkTest;
17 typedef boost::function<void (const std::vector<std::string>&)> UtilityProgram;
18
19 int register_test(const std::string& name, UnitTest test);
20 int register_benchmark(const std::string& name, BenchmarkTest test);
21 int register_benchmark_cl(const std::string& name, CommandLineBenchmarkTest test);
22 int register_utility(const std::string& name, UtilityProgram utility);
23 bool run_tests(const std::vector<std::string>* tests=NULL);
24 void run_benchmarks(const std::vector<std::string>* benchmarks=NULL);
25 void run_command_line_benchmark(const std::string& benchmark_name, const std::string& arg);
26 void run_utility(const std::string& utility_name, const std::vector<std::string>& arg);
27
28 }
29
30 #define CHECK(cond, msg) if(!(cond)) { std::cerr << __FILE__ << ":" << __LINE__ << ": TEST CHECK FAILED: " << #cond << ": " << msg << "\n"; throw test::failure_exception(); }
31
32 #define CHECK_CMP(a, b, cmp) CHECK((a) cmp (b), #a << ": " << (a) << "; " << #b << ": " << (b))
33
34 #define CHECK_EQ(a, b) CHECK_CMP(a, b, ==)
35 #define CHECK_NE(a, b) CHECK_CMP(a, b, !=)
36 #define CHECK_LE(a, b) CHECK_CMP(a, b, <=)
37 #define CHECK_GE(a, b) CHECK_CMP(a, b, >=)
38 #define CHECK_LT(a, b) CHECK_CMP(a, b, <)
39 #define CHECK_GT(a, b) CHECK_CMP(a, b, >)
40
41 //on the iPhone we don't do unit tests or benchmarks.
42 #if TARGET_OS_IPHONE
43
44 #define UNIT_TEST(name) \
45 void TEST_##name()
46
47 #define BENCHMARK(name) \
48 void BENCHMARK_##name(int benchmark_iterations)
49
50 #define BENCHMARK_LOOP
51
52 #define BENCHMARK_ARG(name, arg) \
53 void BENCHMARK_ARG_##name(int benchmark_iterations, arg)
54
55 #define BENCHMARK_ARG_CALL(name, id, arg)
56
57 #define BENCHMARK_ARG_CALL_COMMAND_LINE(name)
58
59 #define UTILITY(name) void UTILITY_##name(const std::vector<std::string>& args)
60
61 #else
62
63 #define UNIT_TEST(name) \
64 void TEST_##name(); \
65 static int TEST_VAR_##name = test::register_test(#name, TEST_##name); \
66 void TEST_##name()
67
68 #define BENCHMARK(name) \
69 void BENCHMARK_##name(int benchmark_iterations); \
70 static int BENCHMARK_VAR_##name = test::register_benchmark(#name, BENCHMARK_##name); \
71 void BENCHMARK_##name(int benchmark_iterations)
72
73 #define BENCHMARK_LOOP while(benchmark_iterations--)
74
75 #define BENCHMARK_ARG(name, arg) \
76 void BENCHMARK_ARG_##name(int benchmark_iterations, arg)
77
78 #define BENCHMARK_ARG_CALL(name, id, arg) \
79 void BENCHMARK_ARG_CALL_##name_##id(int benchmark_iterations) { \
80 BENCHMARK_ARG_##name(benchmark_iterations, arg); \
81 } \
82 static int BENCHMARK_ARG_VAR_##name_##id = test::register_benchmark(#name " " #id, BENCHMARK_ARG_CALL_##name_##id);
83
84 #define BENCHMARK_ARG_CALL_COMMAND_LINE(name) \
85 void BENCHMARK_ARG_CALL_##name(int benchmark_iterations, const std::string& arg) { \
86 BENCHMARK_ARG_##name(benchmark_iterations, arg); \
87 } \
88 static int BENCHMARK_ARG_VAR_##name = test::register_benchmark_cl(#name, BENCHMARK_ARG_CALL_##name);
89
90 #define UTILITY(name) \
91 void UTILITY_##name(const std::vector<std::string>& args); \
92 static int UTILITY_VAR_##name = test::register_utility(#name, UTILITY_##name); \
93 void UTILITY_##name(const std::vector<std::string>& args)
94
95 #endif
96
97 #endif
0 #ifndef USEREVENTS_H_INCLUDED
1 #define USEREVENTS_H_INCLUDED
2
3 #define ST_EVENT_KEY_REPEAT 1
4 #define ST_EVENT_NESTED_DEATH 2
5
6 #endif
0 #include "SDL.h"
1
2 #include <boost/shared_ptr.hpp>
3
4 #include <map>
5 #include <vector>
6 #include <sstream>
7
8 #include "asserts.hpp"
9 #include "custom_object_type.hpp"
10 #include "filesystem.hpp"
11 #include "formatter.hpp"
12 #include "frame.hpp"
13 #include "geometry.hpp"
14 #include "string_utils.hpp"
15 #include "surface.hpp"
16 #include "surface_cache.hpp"
17 #include "unit_test.hpp"
18 #include "wml_node.hpp"
19 #include "wml_parser.hpp"
20 #include "wml_utils.hpp"
21 #include "wml_writer.hpp"
22 #include "IMG_savepng.h"
23
24 namespace {
25 const int TextureImageSize = 1024;
26
27 struct animation_area {
28 explicit animation_area(wml::node_ptr node) : anim(new frame(node)), is_particle(false)
29 {
30 width = 0;
31 height = 0;
32 foreach(const frame::frame_info& f, anim->frame_layout()) {
33 width += f.area.w();
34 if(f.area.h() > height) {
35 height = f.area.h();
36 }
37 }
38
39 src_image = node->attr("image");
40 dst_image = -1;
41 }
42
43 boost::shared_ptr<frame> anim;
44 int width, height;
45
46 std::string src_image;
47
48 int dst_image;
49 rect dst_area;
50 bool is_particle;
51 };
52
53 typedef boost::shared_ptr<animation_area> animation_area_ptr;
54
55 bool operator==(const animation_area& a, const animation_area& b)
56 {
57 return a.src_image == b.src_image && a.anim->area() == b.anim->area() && a.anim->pad() == b.anim->pad() && a.anim->num_frames() == b.anim->num_frames() && a.anim->num_frames_per_row() == b.anim->num_frames_per_row();
58 }
59
60 bool animation_area_height_compare(animation_area_ptr a, animation_area_ptr b)
61 {
62 if(a->is_particle != b->is_particle) {
63 return a->is_particle;
64 }
65 return a->height > b->height;
66 }
67
68 struct output_area {
69 explicit output_area(int n) : image_id(n)
70 {
71 area = rect(0, 0, TextureImageSize, TextureImageSize);
72 }
73 int image_id;
74 rect area;
75 };
76
77 rect use_output_area(const output_area& input, int width, int height, std::vector<output_area>& areas)
78 {
79 ASSERT_LE(width, input.area.w());
80 ASSERT_LE(height, input.area.h());
81 rect result(input.area.x(), input.area.y(), width, height);
82 if(input.area.h() > height) {
83 areas.push_back(output_area(input.image_id));
84 areas.back().area = rect(input.area.x(), input.area.y() + height, input.area.w(), input.area.h() - height);
85 }
86
87 if(input.area.w() > width) {
88 areas.push_back(output_area(input.image_id));
89 areas.back().area = rect(input.area.x() + width, input.area.y(), input.area.w() - width, height);
90 }
91
92 return result;
93 }
94
95 }
96
97 namespace graphics {
98 void set_alpha_for_transparent_colors_in_rgba_surface(SDL_Surface* s);
99 }
100
101 UTILITY(object_compiler)
102 {
103 using graphics::surface;
104
105 int num_output_images = 0;
106 std::vector<output_area> output_areas;
107 output_areas.push_back(output_area(num_output_images++));
108
109 std::map<wml::node_ptr, std::string> nodes_to_files;
110
111 std::vector<wml::node_ptr> objects;
112 std::vector<animation_area_ptr> animation_areas;
113 std::map<wml::node_ptr, animation_area_ptr> nodes_to_animation_areas;
114
115 std::vector<wml::node_ptr> animation_containing_nodes;
116 std::vector<std::string> no_compile_images;
117
118 wml::node_ptr gui_node = wml::parse_wml_from_file("data/gui.cfg");
119 animation_containing_nodes.push_back(gui_node);
120
121 std::map<std::string, wml::node_ptr> gui_nodes;
122 std::vector<std::string> gui_files;
123 sys::get_files_in_dir("data/gui", &gui_files);
124 foreach(const std::string& gui, gui_files) {
125 if(gui[0] == '.') {
126 continue;
127 }
128
129 gui_nodes[gui] = wml::parse_wml_from_file("data/gui/" + gui);
130 animation_containing_nodes.push_back(gui_nodes[gui]);
131 if(gui_nodes[gui]->has_attr("no_compile_image")) {
132 std::vector<std::string> images = util::split(gui_nodes[gui]->attr("no_compile_image"));
133 no_compile_images.insert(no_compile_images.end(), images.begin(), images.end());
134 }
135 }
136
137 std::vector<const_custom_object_type_ptr> types = custom_object_type::get_all();
138 foreach(const_custom_object_type_ptr type, types) {
139 const std::string* path = custom_object_type::get_object_path(type->id() + ".cfg");
140 std::cerr << "OBJECT: " << type->id() << " -> " << *path << "\n";
141 wml::node_ptr obj_node = wml::parse_wml_from_file(*path);
142 obj_node = custom_object_type::merge_prototype(obj_node);
143 obj_node->erase_attr("prototype");
144 obj_node->clear_children("editor_info");
145 objects.push_back(obj_node);
146 nodes_to_files[obj_node] = "data/compiled/objects/" + type->id() + ".cfg";
147
148
149 if(obj_node->has_attr("no_compile_image")) {
150 std::vector<std::string> images = util::split(obj_node->attr("no_compile_image"));
151 no_compile_images.insert(no_compile_images.end(), images.begin(), images.end());
152 }
153
154 animation_containing_nodes.push_back(obj_node);
155
156 for(wml::node::child_iterator i = obj_node->begin_child("particle_system"); i != obj_node->end_child("particle_system"); ++i) {
157 animation_containing_nodes.push_back(i->second);
158 }
159 }
160
161 foreach(wml::node_ptr& node, animation_containing_nodes) {
162 for(wml::node::all_child_iterator i = node->begin_children();
163 i != node->end_children(); ++i) {
164 if((*i)->name() != "animation" && (*i)->name() != "framed_gui_element" && (*i)->name() != "section") {
165 continue;
166 }
167
168 animation_area_ptr anim(new animation_area(*i));
169 if(anim->src_image.empty() || (*i)->has_attr("palettes") || std::find(no_compile_images.begin(), no_compile_images.end(), anim->src_image) != no_compile_images.end()) {
170 continue;
171 }
172
173 animation_areas.push_back(anim);
174
175 foreach(animation_area_ptr p, animation_areas) {
176 if(*p == *anim) {
177 anim = p;
178 break;
179 }
180 }
181
182 if(node->name() == "particle_system") {
183 anim->is_particle = true;
184 }
185
186 if(anim != animation_areas.back()) {
187 animation_areas.pop_back();
188 }
189
190 nodes_to_animation_areas[*i] = anim;
191 }
192 }
193
194 std::sort(animation_areas.begin(), animation_areas.end(), animation_area_height_compare);
195
196 foreach(animation_area_ptr anim, animation_areas) {
197 ASSERT_LE(anim->width, 1024);
198 ASSERT_LE(anim->height, 1024);
199 int match = -1;
200 int match_diff = -1;
201 for(int n = 0; n != output_areas.size(); ++n) {
202 if(anim->width <= output_areas[n].area.w() && anim->height <= output_areas[n].area.h()) {
203 const int diff = output_areas[n].area.w()*output_areas[n].area.h() - anim->width*anim->height;
204 if(match == -1 || diff < match_diff) {
205 match = n;
206 match_diff = diff;
207 }
208 }
209 }
210
211 if(match == -1) {
212 match = output_areas.size();
213 output_areas.push_back(output_area(num_output_images++));
214 }
215
216 output_area match_area = output_areas[match];
217 output_areas.erase(output_areas.begin() + match);
218 rect area = use_output_area(match_area, anim->width, anim->height, output_areas);
219 anim->dst_image = match_area.image_id;
220 anim->dst_area = area;
221 }
222
223 std::vector<surface> surfaces;
224 for(int n = 0; n != num_output_images; ++n) {
225 surfaces.push_back(surface(SDL_CreateRGBSurface(SDL_SWSURFACE,TextureImageSize,TextureImageSize,32,SURFACE_MASK)));
226 }
227
228 foreach(animation_area_ptr anim, animation_areas) {
229 foreach(animation_area_ptr other, animation_areas) {
230 if(anim == other || anim->dst_image != other->dst_image) {
231 continue;
232 }
233
234 ASSERT_LOG(rects_intersect(anim->dst_area, other->dst_area) == false, "RECTANGLES CLASH: " << anim->dst_image << " " << anim->dst_area << " vs " << other->dst_area);
235 }
236
237 ASSERT_INDEX_INTO_VECTOR(anim->dst_image, surfaces);
238 surface dst = surfaces[anim->dst_image];
239 surface src = graphics::surface_cache::get(anim->src_image);
240 ASSERT_LOG(src.get() != NULL, "COULD NOT LOAD IMAGE: '" << anim->src_image << "'");
241 int xdst = 0;
242 for(int f = 0; f != anim->anim->num_frames(); ++f) {
243 const frame::frame_info& info = anim->anim->frame_layout()[f];
244
245 const int x = f%anim->anim->num_frames_per_row();
246 const int y = f/anim->anim->num_frames_per_row();
247
248 const rect& base_area = anim->anim->area();
249 const int xpos = base_area.x() + (base_area.w()+anim->anim->pad())*x;
250 const int ypos = base_area.y() + (base_area.h()+anim->anim->pad())*y;
251 SDL_Rect blit_src = {xpos + info.x_adjust, ypos + info.y_adjust, info.area.w(), info.area.h()};
252 SDL_Rect blit_dst = {anim->dst_area.x() + xdst,
253 anim->dst_area.y(),
254 info.area.w(), info.area.h()};
255 xdst += info.area.w();
256 ASSERT_GE(blit_dst.x, anim->dst_area.x());
257 ASSERT_GE(blit_dst.y, anim->dst_area.y());
258 ASSERT_LE(blit_dst.x + blit_dst.w, anim->dst_area.x() + anim->dst_area.w());
259 ASSERT_LE(blit_dst.y + blit_dst.h, anim->dst_area.y() + anim->dst_area.h());
260 SDL_SetAlpha(src.get(), 0, SDL_ALPHA_OPAQUE);
261 SDL_BlitSurface(src.get(), &blit_src, dst.get(), &blit_dst);
262 }
263 }
264
265 for(int n = 0; n != num_output_images; ++n) {
266 std::ostringstream fname;
267 fname << "images/compiled-" << n << ".png";
268
269 graphics::set_alpha_for_transparent_colors_in_rgba_surface(surfaces[n].get());
270
271 IMG_SavePNG(fname.str().c_str(), surfaces[n].get(), -1);
272 }
273
274 typedef std::pair<wml::node_ptr, animation_area_ptr> anim_pair;
275 foreach(const anim_pair& a, nodes_to_animation_areas) {
276 wml::node_ptr node = a.first;
277 animation_area_ptr anim = a.second;
278 std::ostringstream fname;
279 fname << "compiled-" << anim->dst_image << ".png";
280 node->set_attr("image", fname.str());
281 node->erase_attr("x");
282 node->erase_attr("y");
283 node->erase_attr("w");
284 node->erase_attr("h");
285 node->erase_attr("pad");
286
287 const frame::frame_info& first_frame = anim->anim->frame_layout().front();
288
289 rect r(anim->dst_area.x() - first_frame.x_adjust, anim->dst_area.y() - first_frame.y_adjust, anim->anim->area().w(), anim->anim->area().h());
290 node->set_attr("rect", r.to_string());
291
292 int xpos = anim->dst_area.x();
293
294 std::vector<int> v;
295 foreach(const frame::frame_info& f, anim->anim->frame_layout()) {
296 ASSERT_EQ(f.area.w() + f.x_adjust + f.x2_adjust, anim->anim->area().w());
297 ASSERT_EQ(f.area.h() + f.y_adjust + f.y2_adjust, anim->anim->area().h());
298 v.push_back(f.x_adjust);
299 v.push_back(f.y_adjust);
300 v.push_back(f.x2_adjust);
301 v.push_back(f.y2_adjust);
302 v.push_back(xpos);
303 v.push_back(anim->dst_area.y());
304 v.push_back(f.area.w());
305 v.push_back(f.area.h());
306
307 xpos += f.area.w();
308 }
309
310 std::vector<std::string> vs;
311 foreach(int n, v) {
312 vs.push_back(formatter() << n);
313 }
314
315 node->set_attr("frame_info", util::join(vs));
316 }
317
318 for(std::map<wml::node_ptr, std::string>::iterator i = nodes_to_files.begin(); i != nodes_to_files.end(); ++i) {
319 wml::node_ptr node = i->first;
320 node->strip_prettiness();
321 sys::write_file(i->second, wml::output(node));
322 }
323
324 sys::write_file("data/compiled/gui.cfg", wml::output(gui_node));
325
326 for(std::map<std::string, wml::node_ptr>::iterator i = gui_nodes.begin();
327 i != gui_nodes.end(); ++i) {
328 sys::write_file("data/compiled/gui/" + i->first, wml::output(i->second));
329 }
330 }
0 #include <iostream>
1
2 #include <boost/bind.hpp>
3 #include <boost/function.hpp>
4
5 #include <math.h>
6
7 #include "asserts.hpp"
8 #include "button.hpp"
9 #include "custom_object_type.hpp"
10 #include "dialog.hpp"
11 #include "filesystem.hpp"
12 #include "foreach.hpp"
13 #include "formatter.hpp"
14 #include "frame.hpp"
15 #include "geometry.hpp"
16 #include "grid_widget.hpp"
17 #include "label.hpp"
18 #include "preferences.hpp"
19 #include "raster.hpp"
20 #include "surface.hpp"
21 #include "surface_cache.hpp"
22 #include "text_entry_widget.hpp"
23 #include "texture.hpp"
24 #include "unit_test.hpp"
25 #include "wml_parser.hpp"
26 #include "wml_utils.hpp"
27 #include "wml_writer.hpp"
28
29 namespace {
30 using namespace gui;
31 using graphics::surface;
32
33 const unsigned char RedBorder[] = {0xf9, 0x30, 0x3d};
34
35 bool is_pixel_border(const surface& s, int x, int y)
36 {
37 if(x < 0 || y < 0 || x >= s->w || y >= s->h) {
38 return false;
39 }
40
41 unsigned char* pixel = reinterpret_cast<unsigned char*>(s->pixels) + y*s->pitch + x*4;
42 for(int n = 0; n != 3; ++n) {
43 if(pixel[n] != RedBorder[n]) {
44 return false;
45 }
46 }
47
48 return true;
49 }
50
51 rect get_border_rect(const surface& s, int x, int y)
52 {
53 int w = 0, h = 0;
54 while(is_pixel_border(s, x + w + 1, y)) {
55 ++w;
56 }
57
58 while(is_pixel_border(s, x, y + h + 1) &&
59 is_pixel_border(s, x + w, y + h + 1)) {
60 ++h;
61 }
62
63 if(w == 0 || h == 0) {
64 return rect();
65 }
66
67 return rect(x+1, y+1, w-1, h-1);
68 }
69
70 typedef std::vector<std::vector<rect> > RectSelection;
71
72 class object_image_widget : public widget
73 {
74 boost::function<void(RectSelection)> selection_handler_;
75 public:
76 object_image_widget(const std::string& fname, boost::function<void(RectSelection)> handler) : selection_handler_(handler)
77 {
78 surface_ = graphics::surface_cache::get(fname);
79 surface s(SDL_CreateRGBSurface(SDL_SWSURFACE, surface_->w, surface_->h, 32, SURFACE_MASK));
80
81 SDL_SetAlpha(surface_.get(), 0, SDL_ALPHA_OPAQUE);
82 SDL_BlitSurface(surface_.get(), NULL, s.get(), NULL);
83 surface_ = s;
84
85 for(int y = 0; y != surface_->h; ++y) {
86 for(int x = 0; x != surface_->w; ++x) {
87 if(is_pixel_border(surface_, x, y) &&
88 !is_pixel_border(surface_, x-1, y) &&
89 !is_pixel_border(surface_, x, y-1)) {
90 rect r = get_border_rect(surface_, x, y);
91 if(r.w()*r.h() != 0) {
92 rects_.push_back(r);
93 }
94 }
95 }
96 }
97
98 std::vector<surface> surfs;
99 surfs.push_back(surface_);
100 texture_ = graphics::texture(surfs);
101
102 set_dim(texture_.width()*2, texture_.height()*2);
103 }
104
105 void handle_draw() const {
106 int mousex, mousey;
107 const unsigned int buttons = SDL_GetMouseState(&mousex, &mousey);
108 mousex -= x();
109 mousey -= y();
110 point mouse_pos(mousex/2, mousey/2);
111
112 const float wave = sin(SDL_GetTicks()/200.0);
113
114 graphics::blit_texture(texture_, x(), y(), width(), height());
115 foreach(const rect& r, rects_) {
116 graphics::draw_rect(rect(x() + r.x()*2, y() + r.y()*2, r.w()*2, r.h()*2), point_in_rect(mouse_pos, r) ? graphics::color(255, 255, 0, 64 + wave*64.0) : graphics::color(255, 255, 255, 64));
117 }
118
119 foreach(const std::vector<rect>& row, selected_rects_) {
120 foreach(const rect& r, row) {
121 graphics::draw_rect(rect(x() + r.x()*2, y() + r.y()*2, r.w()*2, r.h()*2), graphics::color(255, 255, 0, 64 + wave*64.0));
122 }
123 }
124 }
125
126 std::vector<std::vector<rect> > calculate_selected_rects() const {
127 int mousex, mousey;
128 const unsigned int buttons = SDL_GetMouseState(&mousex, &mousey);
129 mousex -= x();
130 mousey -= y();
131
132 point mouse_pos(mousex/2, mousey/2);
133 if(buttons) {
134 return get_selected_rects(rect::from_coordinates(mouse_pos.x, mouse_pos.y, anchor_.x, anchor_.y));
135 } else {
136 return std::vector<std::vector<rect> >();
137 }
138 }
139
140 bool handle_event(const SDL_Event& event, bool claimed)
141 {
142 if(event.type == SDL_MOUSEBUTTONDOWN) {
143 int mousex, mousey;
144 const unsigned int buttons = SDL_GetMouseState(&mousex, &mousey);
145 if(mousex >= x() && mousex <= x() + width() && mousey >= y() && mousey <= y() + height()) {
146 mousex -= x();
147 mousey -= y();
148 mousex /= 2;
149 mousey /= 2;
150
151 anchor_ = point(mousex, mousey);
152 }
153 }
154
155 if(event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEMOTION) {
156 int mousex, mousey;
157 const unsigned int buttons = SDL_GetMouseState(&mousex, &mousey);
158 if((buttons || event.type == SDL_MOUSEBUTTONDOWN) &&
159 mousex >= x() && mousex <= x() + width() &&
160 mousey >= y() && mousey <= y() + height()) {
161 std::vector<std::vector<rect> > selection = calculate_selected_rects();
162 if(selection != selected_rects_ && !selection.empty()) {
163 selected_rects_ = selection;
164 selection_handler_(selected_rects_);
165 }
166 }
167 }
168
169 return false;
170 }
171
172 const rect* rect_at_point(const point& p) const
173 {
174 foreach(const rect& r, rects_) {
175 if(point_in_rect(p, r)) {
176 return &r;
177 }
178 }
179
180 return NULL;
181 }
182
183 std::vector<std::vector<rect> > get_selected_rects(const rect& selection) const
184 {
185 std::vector<std::vector<rect> > result;
186 const rect* first_rect = rect_at_point(selection.top_left());
187 const rect* last_rect = rect_at_point(selection.bottom_right());
188 if(!first_rect || !last_rect) {
189 return result;
190 }
191
192 std::vector<rect> rects;
193 foreach(const rect& r, rects_) {
194 if(r.x() >= first_rect->x() && r.y() >= first_rect->y() &&
195 r.x2() <= last_rect->x2() && r.y2() <= last_rect->y2()) {
196 rects.push_back(r);
197 }
198 }
199
200 if(rects.empty()) {
201 return result;
202 }
203
204 const int rect_width = rects.front().w();
205 const int rect_height = rects.front().h();
206
207 foreach(const rect& r, rects) {
208 if(r.w() != rect_width && r.h() != rect_height) {
209 return result;
210 }
211 }
212
213 int pad = -1;
214 int n;
215 for(n = 1; n < rects.size(); ++n) {
216 const int new_pad = rects[n].x() - rects[n-1].x2();
217 if(pad != -1 && new_pad != pad) {
218 break;
219 }
220
221 if(rects[n].y() != rects[n-1].y() || rects[n].y2() != rects[n-1].y2()) {
222 break;
223 }
224
225 pad = new_pad;
226 }
227
228 const int row_length = n;
229
230 if(n < rects.size()) {
231 if((rects.size()%n) != 0) {
232 return result;
233 }
234
235 for(; n < rects.size(); ++n) {
236 const int new_pad = rects[n].y() - rects[n-row_length].y2();
237 if(pad != -1 && new_pad != pad) {
238 return result;
239 }
240
241 if(rects[n].x() != rects[n-row_length].x()) {
242 return result;
243 }
244
245 pad = new_pad;
246 }
247 }
248
249 for(n = 0; n < rects.size(); n += row_length) {
250 ASSERT_LE(n+row_length, rects.size());
251 result.push_back(std::vector<rect>(rects.begin() + n, rects.begin() + n + row_length));
252 }
253
254 return result;
255 }
256
257 const point& anchor() const { return anchor_; }
258
259 void set_selection(const RectSelection& selection) { selected_rects_ = selection; }
260
261 private:
262 graphics::texture texture_;
263 surface surface_;
264 std::vector<rect> rects_;
265
266 std::vector<std::vector<rect> > selected_rects_;
267
268 point anchor_;
269 };
270
271 class object_image_dialog : public gui::dialog
272 {
273 public:
274 object_image_dialog(const std::string& image_name, wml::node_ptr node)
275 : dialog(0, 0, graphics::screen_width(), graphics::screen_height()), image_name_(image_name), node_(node),
276 duration_(1), reverse_(false)
277 {
278 for(wml::node::child_iterator i = node->begin_child("animation"); i != node->end_child("animation"); ++i) {
279 wml::node_ptr anim_node = i->second;
280 anim_nodes_.insert(std::pair<std::string, wml::node_ptr>(anim_node->attr("id"), anim_node));
281 }
282
283 wml::node_ptr anim = node->get_child("animation");
284 ASSERT_LOG(anim.get() != NULL, "OBJECT HAS NO ANIMATIONS");
285 edit_animation(anim);
286 }
287
288 void set_selection(RectSelection selection) {
289 if(selection.empty() || selection.front().empty() || !animation_editing_) {
290 return;
291 }
292
293 const rect& first_rect = selection.front().front();
294
295 const rect rect_scaled(first_rect.x()/2, first_rect.y()/2, first_rect.w()/2, first_rect.h()/2);
296
297 int pad = 0;
298 if(selection.front().size() > 1) {
299 pad = selection.front()[1].x() - selection.front()[0].x2();
300 } else if(selection.size() > 1) {
301 pad = selection[1].front().y() - selection[0].front().y2();
302 }
303
304 animation_editing_->set_attr("rect", first_rect.to_string());
305 animation_editing_->set_attr("frames", formatter() << selection.size()*selection.front().size());
306 animation_editing_->set_attr("frames_per_row", formatter() << selection.front().size());
307 animation_editing_->set_attr("pad", formatter() << pad);
308
309 frame_.reset(new frame(animation_editing_));
310 }
311
312 void handle_draw() const {
313 dialog::handle_draw();
314
315 if(!frame_) {
316 return;
317 }
318
319 static int nframe = 0;
320
321 const int time_in_frame = nframe%frame_->duration();
322 frame_->draw(600, 460, true, false, time_in_frame);
323 ++nframe;
324 }
325
326 void init() {
327 clear();
328
329 grid_ptr g(new grid(2));
330 g->set_hpad(10);
331 g->add_col(widget_ptr(new button(widget_ptr(new label("Ok", graphics::color_white())), boost::bind(&dialog::close, this))));
332 g->add_col(widget_ptr(new button(widget_ptr(new label("Cancel", graphics::color_white())), boost::bind(&dialog::cancel, this))));
333
334 add_widget(g, graphics::screen_width() - 140, 560);
335
336 object_image_widget* image_widget = new object_image_widget(image_name_, boost::bind(&object_image_dialog::set_selection, this, _1));
337
338 if(frame_) {
339 std::vector<rect> rects;
340 rects.push_back(frame_->area());
341 for(int n = 1; n < frame_->num_frames() && n < frame_->num_frames_per_row(); ++n) {
342 rects.push_back(rect(rects.back().x2() + frame_->pad(), rects.back().y(), rects.back().w(), rects.back().h()));
343 }
344
345 for(int n = frame_->num_frames_per_row(); n < frame_->num_frames(); ++n) {
346 const rect& above = rects[n - frame_->num_frames_per_row()];
347 rects.push_back(rect(above.x(), above.y2() + frame_->pad(), above.w(), above.h()));
348 }
349
350 RectSelection selection;
351 for(int n = 0; n < frame_->num_frames(); n += frame_->num_frames_per_row()) {
352 ASSERT_LE(n, frame_->num_frames() - frame_->num_frames_per_row());
353 std::vector<rect> v(rects.begin() + n, rects.begin() + n + frame_->num_frames_per_row());
354 selection.push_back(v);
355 }
356
357 image_widget->set_selection(selection);
358 }
359
360 add_widget(widget_ptr(image_widget), 0, 0);
361
362 animation_id_label_.reset(new label(animation_editing_->attr("id"), graphics::color_white()));
363
364 add_widget(widget_ptr(new label(node_->attr("id"), graphics::color_white(), 16)), graphics::screen_width() - 220, 2);
365
366 g = grid_ptr(new grid(2));
367 g->set_hpad(10);
368 g->add_col(animation_id_label_);
369 g->add_col(widget_ptr(new button(widget_ptr(new label("Change Name", graphics::color_white())), boost::bind(&object_image_dialog::change_name, this))));
370
371 add_widget(g);
372
373 g = grid_ptr(new grid(4));
374 g->set_hpad(10);
375 g->add_col(widget_ptr(new label("Duration:", graphics::color_white())));
376 g->add_col(widget_ptr(new label(formatter() << duration_, graphics::color_white())));
377 g->add_col(widget_ptr(new button(widget_ptr(new label("+", graphics::color_white())), boost::bind(&object_image_dialog::change_duration, this, 1))));
378 g->add_col(widget_ptr(new button(widget_ptr(new label("-", graphics::color_white())), boost::bind(&object_image_dialog::change_duration, this, -1))));
379 add_widget(g);
380
381 g = grid_ptr(new grid(3));
382 g->set_hpad(10);
383 g->add_col(widget_ptr(new label("Reverse:", graphics::color_white())));
384 g->add_col(widget_ptr(new label(reverse_ ? "yes" : "no", graphics::color_white())));
385 g->add_col(widget_ptr(new button(widget_ptr(new label("Toggle", graphics::color_white())), boost::bind(&object_image_dialog::toggle_reverse, this))));
386 add_widget(g);
387
388 g = grid_ptr(new grid(3));
389 for(std::multimap<std::string, wml::node_ptr>::iterator i = anim_nodes_.begin(); i != anim_nodes_.end(); ++i) {
390 g->add_col(widget_ptr(new label(i->first, i->second == animation_editing_ ? graphics::color_yellow() : graphics::color_white())));
391 g->add_col(widget_ptr(new button(widget_ptr(new label("Edit", graphics::color_white())), boost::bind(&object_image_dialog::edit_animation, this, i->second))));
392 if(anim_nodes_.size() > 1) {
393 g->add_col(widget_ptr(new button(widget_ptr(new label("Delete", graphics::color_white())), boost::bind(&object_image_dialog::delete_animation, this, i->second))));
394 } else {
395 g->finish_row();
396 }
397 }
398
399 add_widget(g);
400
401 add_widget(widget_ptr(new button(widget_ptr(new label("New animation", graphics::color_white())), boost::bind(&object_image_dialog::new_animation, this))));
402 }
403
404 private:
405 void new_animation() {
406 create_new_animation();
407 init();
408 }
409
410 void edit_animation(wml::node_ptr node) {
411 animation_editing_ = node;
412 duration_ = wml::get_int(animation_editing_, "duration", 1);
413 reverse_ = wml::get_bool(animation_editing_, "reverse", false);
414 frame_.reset(new frame(animation_editing_));
415 init();
416 }
417
418 void delete_animation(wml::node_ptr node) {
419 node_->erase_child(node);
420 if(animation_editing_ == node) {
421 edit_animation(node_->get_child("animation"));
422 }
423
424 for(std::multimap<std::string, wml::node_ptr>::iterator i = anim_nodes_.begin(); i != anim_nodes_.end(); ++i) {
425 if(i->second == node) {
426 anim_nodes_.erase(i);
427 break;
428 }
429 }
430
431 init();
432 }
433
434 void create_new_animation() {
435 animation_editing_ = wml::deep_copy(node_->get_child("animation"));
436 duration_ = wml::get_int(animation_editing_, "duration", 1);
437 animation_editing_->set_attr("duration", formatter() << duration_);
438
439 reverse_ = wml::get_bool(animation_editing_, "reverse", false);
440
441 node_->add_child(animation_editing_);
442 anim_nodes_.insert(std::pair<std::string, wml::node_ptr>(animation_editing_->attr("id"), animation_editing_));
443 }
444
445 void change_duration(int delta) {
446 duration_ += delta;
447 if(duration_ < 1) {
448 duration_ = 1;
449 }
450
451 animation_editing_->set_attr("duration", formatter() << duration_);
452 frame_.reset(new frame(animation_editing_));
453 init();
454 }
455
456 void toggle_reverse() {
457 reverse_ = !reverse_;
458 animation_editing_->set_attr("reverse", reverse_ ? "yes" : "no");
459 frame_.reset(new frame(animation_editing_));
460 init();
461 }
462
463 void change_name() {
464 dialog d(0, 0, graphics::screen_width(), graphics::screen_height());
465 text_entry_widget* entry = new text_entry_widget;
466 d.add_widget(widget_ptr(new label("Name", graphics::color_white())))
467 .add_widget(widget_ptr(entry));
468 d.show_modal();
469 std::string name = entry->text();
470 if(name.empty() == false) {
471 animation_editing_->set_attr("id", name);
472 for(std::multimap<std::string, wml::node_ptr>::iterator itor = anim_nodes_.begin(); itor != anim_nodes_.end(); ++itor) {
473 if(itor->second == animation_editing_) {
474 anim_nodes_.insert(std::pair<std::string, wml::node_ptr>(name, animation_editing_));
475 anim_nodes_.erase(itor);
476 break;
477 }
478 }
479 init();
480 }
481 }
482
483 std::string image_name_;
484 wml::node_ptr node_;
485 std::multimap<std::string, wml::node_ptr> anim_nodes_;
486
487 wml::node_ptr animation_editing_;
488 boost::shared_ptr<label> animation_id_label_;
489
490 boost::shared_ptr<frame> frame_;
491 int duration_;
492 bool reverse_;
493 };
494
495 }
496
497 void show_object_editor_dialog(const std::string& obj_type)
498 {
499 const std::string* fname = custom_object_type::get_object_path(obj_type + ".cfg");
500 if(fname == NULL) {
501 std::cerr << "OBJECT NOT FOUND\n";
502 }
503
504 const_custom_object_type_ptr type = custom_object_type::get(obj_type);
505 ASSERT_LOG(type.get() != NULL, "COULD NOT LOAD OBJECT");
506
507 wml::node_ptr obj_node = wml::parse_wml_from_file(*fname);
508 std::cerr << "OBJECT NODE: " << obj_node->base_elements().size() << "\n";
509 wml::node_ptr node = custom_object_type::merge_prototype(obj_node);
510 std::cerr << "PROTO NODE: " << node->base_elements().size() << "\n";
511 wml::const_node_ptr anim = node->get_child("animation");
512 ASSERT_LOG(anim.get() != NULL, "COULD NOT FIND ANIMATION");
513
514 const std::string image = anim->attr("image");
515
516 object_image_dialog d(image, node);
517 d.show_modal();
518 if(d.cancelled()) {
519 return;
520 }
521
522 if(obj_node != node) {
523 while(obj_node->get_child("animation")) {
524 obj_node->erase_child(obj_node->get_child("animation"));
525 }
526
527 FOREACH_WML_CHILD(anim_node, node, "animation") {
528 obj_node->add_child(wml::deep_copy(anim_node));
529 }
530 }
531
532 std::cerr << "OBJECT NODE2: " << obj_node->base_elements().size() << "\n";
533 sys::write_file(*fname, wml::output(obj_node));
534 }
535
536 namespace {
537 void select_prototype(dialog* d, int* index, int num) {
538 *index = num;
539 d->close();
540 }
541
542 bool is_not_cfg_filename(const std::string& fname) {
543 return fname.size() <= 4 || std::string(fname.end()-4, fname.end()) != ".cfg";
544 }
545
546 }
547
548 void launch_object_editor(const std::vector<std::string>& args)
549 {
550 std::string name;
551 if(!args.empty()) {
552 name = args.front();
553 } else {
554 using namespace gui;
555
556 while(name.empty()) {
557 dialog d(0, 0, graphics::screen_width(), graphics::screen_height());
558 d.add_widget(widget_ptr(new label("Enter the name of the new object", graphics::color_white())));
559 text_entry_widget* entry = new text_entry_widget;
560 d.add_widget(widget_ptr(entry));
561
562 d.show_modal();
563 if(d.cancelled()) {
564 return;
565 }
566
567 name = entry->text();
568 }
569
570 std::string image;
571 while(image.empty()) {
572 dialog d(0, 0, graphics::screen_width(), graphics::screen_height());
573 d.add_widget(widget_ptr(new label("Enter image to use for the new object", graphics::color_white())));
574 text_entry_widget* entry = new text_entry_widget;
575 d.add_widget(widget_ptr(entry));
576
577 d.show_modal();
578 if(d.cancelled()) {
579 return;
580 }
581
582 std::map<std::string, std::string> files;
583 sys::get_unique_filenames_under_dir("images/", &files);
584 typedef std::pair<std::string, std::string> FilePair;
585 foreach(const FilePair& f, files) {
586 if(f.first.size() <= 4 || std::string(f.first.end()-4,f.first.end()) != ".png") {
587 continue;
588 }
589
590 if(f.first == entry->text()) {
591 image = f.second;
592
593 //erase the 'images/' part off the filename
594 image.erase(image.begin(), image.begin()+8);
595 break;
596 }
597 }
598 }
599
600 std::vector<std::string> prototypes;
601 sys::get_files_in_dir("data/object_prototypes", &prototypes);
602 prototypes.erase(std::remove_if(prototypes.begin(), prototypes.end(), is_not_cfg_filename), prototypes.end());
603
604 dialog d(0, 0, graphics::screen_width(), graphics::screen_height());
605 d.add_widget(widget_ptr(new label("Select prototype to base this object off", graphics::color_white())));
606 gui::grid* grid = new gui::grid(1);
607 foreach(std::string& proto, prototypes) {
608 proto.erase(proto.end()-4, proto.end());
609 grid->add_col(widget_ptr(new label(proto, graphics::color_white())));
610 }
611
612 grid->allow_selection();
613 grid->must_select();
614
615 int prototype_index = -1;
616 grid->register_selection_callback(boost::bind(select_prototype, &d, &prototype_index, _1));
617
618 d.add_widget(widget_ptr(grid));
619 d.show_modal();
620
621 if(d.cancelled() || prototype_index < 0) {
622 return;
623 }
624
625 std::ostringstream object_stream;
626 object_stream << "[object_type]\nid=\"" << name
627 << "\"\nprototype=\"" << prototypes[prototype_index] << "\"\n"
628 << "\t[base:animation]\n\timage=\"" << image << "\"\n\t[/animation]\n";
629
630 wml::const_node_ptr proto_node = wml::parse_wml_from_file("data/object_prototypes/" + prototypes[prototype_index] + ".cfg");
631 FOREACH_WML_CHILD(anim_node, proto_node, "animation") {
632 const std::string id = anim_node->attr("id");
633 object_stream << "\t[animation]\n\tid=\"" << id << "\"\n\t[/animation]\n";
634 }
635
636 object_stream << "[/object_type]\n";
637 sys::write_file("data/objects/" + name + ".cfg", object_stream.str());
638 custom_object_type::invalidate_all_objects();
639 }
640
641 preferences::editor_screen_size_scope screen_size_scope;
642 show_object_editor_dialog(name);
643 custom_object_type::invalidate_all_objects();
644 }
645
646 UTILITY(object_editor)
647 {
648 launch_object_editor(args);
649 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12
13 #include <algorithm>
14 #include "utils.hpp"
15
16
17 int truncate_to_char(int value) { return std::min(std::max(value, 0), 255); }
18
19
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef UTILS_HPP_INCLUDED
13 #define UTILS_HPP_INCLUDED
14
15 #include <algorithm>
16
17
18 int truncate_to_char(int value);
19
20
21 #endif
0 #include <cmath>
1 #include <set>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <iostream>
5 #include <string.h>
6
7 #include "boost/lexical_cast.hpp"
8
9 #include "asserts.hpp"
10 #include "formatter.hpp"
11 #include "formula.hpp"
12 #include "formula_callable.hpp"
13 #include "variant.hpp"
14 #include "wml_formula_callable.hpp"
15
16 namespace {
17 std::set<variant*> callable_variants_loading;
18
19 std::string variant_type_to_string(variant::TYPE type) {
20 switch(type) {
21 case variant::TYPE_NULL:
22 return "null";
23 case variant::TYPE_INT:
24 return "int";
25 case variant::TYPE_CALLABLE:
26 return "object";
27 case variant::TYPE_CALLABLE_LOADING:
28 return "object_loading";
29 case variant::TYPE_LIST:
30 return "list";
31 case variant::TYPE_STRING:
32 return "string";
33 case variant::TYPE_MAP:
34 return "map";
35 case variant::TYPE_FUNCTION:
36 return "function";
37 default:
38 assert(false);
39 return "invalid";
40 }
41 }
42
43 std::vector<const char*> call_stack;
44 }
45
46 void swap_variants_loading(std::set<variant*>& v)
47 {
48 callable_variants_loading.swap(v);
49 }
50
51 void push_call_stack(const char* str)
52 {
53 call_stack.push_back(str);
54 }
55
56 void pop_call_stack()
57 {
58 call_stack.pop_back();
59 }
60
61 std::string get_call_stack()
62 {
63 std::string res;
64 for(std::vector<const char*>::const_iterator i = call_stack.begin();
65 i != call_stack.end(); ++i) {
66 if(!*i) {
67 continue;
68 }
69 res += formatter() << " FRAME " << (i - call_stack.begin()) << ": " << *i << "\n";
70 }
71 return res;
72 }
73
74 void output_formula_error_info();
75
76 type_error::type_error(const std::string& str) : message(str) {
77 std::cerr << "ERROR: " << message << "\n" << get_call_stack();
78 output_formula_error_info();
79 assert(false);
80 }
81
82 struct variant_list {
83 variant_list() : refcount(0)
84 {}
85 std::vector<variant> elements;
86 int refcount;
87 };
88
89 struct variant_string {
90 variant_string() : refcount(0)
91 {}
92 std::string str;
93 int refcount;
94 };
95
96 struct variant_map {
97 variant_map() : refcount(0)
98 {}
99 std::map<variant,variant> elements;
100 int refcount;
101 };
102
103 struct variant_fn {
104 variant_fn() : refcount(0)
105 {}
106
107 const std::string* begin_args;
108 const std::string* end_args;
109
110 game_logic::const_formula_ptr fn;
111
112 game_logic::const_formula_callable_ptr callable;
113
114 int refcount;
115 };
116
117 void variant::increment_refcount()
118 {
119 switch(type_) {
120 case TYPE_LIST:
121 ++list_->refcount;
122 break;
123 case TYPE_STRING:
124 ++string_->refcount;
125 break;
126 case TYPE_MAP:
127 ++map_->refcount;
128 break;
129 case TYPE_CALLABLE:
130 intrusive_ptr_add_ref(callable_);
131 break;
132 case TYPE_CALLABLE_LOADING:
133 callable_variants_loading.insert(this);
134 break;
135 case TYPE_FUNCTION:
136 ++fn_->refcount;
137 break;
138
139 // These are not used here, add them to silence a compiler warning.
140 case TYPE_NULL:
141 case TYPE_INT :
142 break;
143 }
144 }
145
146 std::vector<variant>& variant::initialize_list()
147 {
148 if(type_ == TYPE_LIST) {
149 if(list_->refcount == 1) {
150 list_->elements.clear();
151 return list_->elements;
152 }
153 release();
154 } else {
155 release();
156 type_ = TYPE_LIST;
157 }
158
159 list_ = new variant_list;
160 increment_refcount();
161 return list_->elements;
162 }
163
164 void variant::release()
165 {
166 switch(type_) {
167 case TYPE_LIST:
168 if(--list_->refcount == 0) {
169 delete list_;
170 }
171 break;
172 case TYPE_STRING:
173 if(--string_->refcount == 0) {
174 delete string_;
175 }
176 break;
177 case TYPE_MAP:
178 if(--map_->refcount == 0) {
179 delete map_;
180 }
181 break;
182 case TYPE_CALLABLE:
183 intrusive_ptr_release(callable_);
184 break;
185 case TYPE_CALLABLE_LOADING:
186 callable_variants_loading.erase(this);
187 break;
188 case TYPE_FUNCTION:
189 if(--fn_->refcount == 0) {
190 delete fn_;
191 }
192 break;
193
194 // These are not used here, add them to silence a compiler warning.
195 case TYPE_NULL:
196 case TYPE_INT :
197 break;
198 }
199 }
200
201 variant::variant(const game_logic::formula_callable* callable)
202 : type_(TYPE_CALLABLE), callable_(callable)
203 {
204 if(callable == NULL) {
205 type_ = TYPE_NULL;
206 return;
207 }
208 increment_refcount();
209 }
210
211 variant::variant(std::vector<variant>* array)
212 : type_(TYPE_LIST)
213 {
214 assert(array);
215 list_ = new variant_list;
216 list_->elements.swap(*array);
217 increment_refcount();
218 }
219
220 variant::variant(const std::string& str)
221 : type_(TYPE_STRING)
222 {
223 string_ = new variant_string;
224 string_->str = str;
225 increment_refcount();
226 }
227
228 variant::variant(std::map<variant,variant>* map)
229 : type_(TYPE_MAP)
230 {
231 assert(map);
232 map_ = new variant_map;
233 map_->elements.swap(*map);
234 increment_refcount();
235 }
236
237 variant::variant(game_logic::const_formula_ptr fml, const std::vector<std::string>& args, const game_logic::formula_callable& callable)
238 : type_(TYPE_FUNCTION)
239 {
240 fn_ = new variant_fn;
241 if(args.empty()) {
242 fn_->begin_args = fn_->end_args = NULL;
243 } else {
244 fn_->begin_args = &args[0];
245 fn_->end_args = fn_->begin_args + args.size();
246 }
247 fn_->fn = fml;
248 fn_->callable = &callable;
249 increment_refcount();
250 }
251
252 const variant& variant::operator=(const variant& v)
253 {
254 if(&v != this) {
255 if(type_ > TYPE_INT) {
256 release();
257 }
258 memcpy(this, &v, sizeof(v));
259 if(type_ > TYPE_INT) {
260 increment_refcount();
261 }
262 }
263 return *this;
264 }
265
266 const variant& variant::operator[](size_t n) const
267 {
268 if(type_ == TYPE_CALLABLE) {
269 assert(n == 0);
270 return *this;
271 }
272
273 must_be(TYPE_LIST);
274 assert(list_);
275 if(n >= list_->elements.size()) {
276 throw type_error("invalid index");
277 }
278
279 return list_->elements[n];
280 }
281
282 const variant& variant::operator[](const variant v) const
283 {
284 if(type_ == TYPE_CALLABLE) {
285 assert(v.as_int() == 0);
286 return *this;
287 }
288
289 if(type_ == TYPE_MAP) {
290 assert(map_);
291 std::map<variant,variant>::const_iterator i = map_->elements.find(v);
292 if (i == map_->elements.end())
293 {
294 static variant null_variant;
295 return null_variant;
296 }
297 return i->second;
298 } else if(type_ == TYPE_LIST) {
299 return operator[](v.as_int());
300 } else {
301 throw type_error(formatter() << "type error: " << " expected a list or a map but found " << variant_type_to_string(type_) << " (" << to_debug_string() << ")");
302 }
303 }
304
305 variant variant::get_keys() const
306 {
307 must_be(TYPE_MAP);
308 assert(map_);
309 std::vector<variant> tmp;
310 for(std::map<variant,variant>::const_iterator i=map_->elements.begin(); i != map_->elements.end(); ++i) {
311 tmp.push_back(i->first);
312 }
313 return variant(&tmp);
314 }
315
316 variant variant::get_values() const
317 {
318 must_be(TYPE_MAP);
319 assert(map_);
320 std::vector<variant> tmp;
321 for(std::map<variant,variant>::const_iterator i=map_->elements.begin(); i != map_->elements.end(); ++i) {
322 tmp.push_back(i->second);
323 }
324 return variant(&tmp);
325 }
326
327 size_t variant::num_elements() const
328 {
329 if (type_ == TYPE_NULL){
330 return 0;
331 } else if(type_ == TYPE_CALLABLE) {
332 return 1;
333 } else if (type_ == TYPE_LIST) {
334 assert(list_);
335 return list_->elements.size();
336 } else if (type_ == TYPE_MAP) {
337 assert(map_);
338 return map_->elements.size();
339 } else {
340 throw type_error(formatter() << "type error: " << " expected a list or a map but found " << variant_type_to_string(type_) << " (" << to_debug_string() << ")");
341 }
342 }
343
344 variant variant::operator()(const std::vector<variant>& args) const
345 {
346 must_be(TYPE_FUNCTION);
347 game_logic::map_formula_callable* callable = new game_logic::map_formula_callable(fn_->callable.get());
348 variant v(callable);
349
350 for(size_t n = 0; n != args.size() && n != fn_->end_args - fn_->begin_args; ++n) {
351 callable->add(fn_->begin_args[n], args[n]);
352 }
353
354 return fn_->fn->execute(*callable);
355 }
356
357 variant variant::get_member(const std::string& str) const
358 {
359 if(is_callable()) {
360 return callable_->query_value(str);
361 }
362
363 if(str == "self") {
364 return *this;
365 } else {
366 return variant();
367 }
368 }
369
370 bool variant::as_bool() const
371 {
372 switch(type_) {
373 case TYPE_NULL:
374 return false;
375 case TYPE_INT:
376 return int_value_ != 0;
377 case TYPE_CALLABLE_LOADING:
378 return true;
379 case TYPE_CALLABLE:
380 return callable_ != NULL;
381 case TYPE_LIST:
382 return !list_->elements.empty();
383 case TYPE_MAP:
384 return !map_->elements.empty();
385 case TYPE_STRING:
386 return !string_->str.empty();
387 case TYPE_FUNCTION:
388 return true;
389 default:
390 assert(false);
391 return false;
392 }
393 }
394
395 const std::string& variant::as_string() const
396 {
397 must_be(TYPE_STRING);
398 assert(string_);
399 return string_->str;
400 }
401
402 variant variant::operator+(const variant& v) const
403 {
404 if(type_ == TYPE_INT) {
405 return variant(int_value_ + v.as_int());
406 }
407
408 if(type_ == TYPE_NULL) {
409 return v;
410 } else if(v.type_ == TYPE_NULL) {
411 return *this;
412 }
413
414 if(type_ == TYPE_LIST) {
415 if(v.type_ == TYPE_LIST) {
416 std::vector<variant> res;
417 res.reserve(list_->elements.size() + v.list_->elements.size());
418 for(size_t i = 0; i<list_->elements.size(); ++i) {
419 const variant& var = list_->elements[i];
420 res.push_back(var);
421 }
422
423 for(size_t j = 0; j<v.list_->elements.size(); ++j) {
424 const variant& var = v.list_->elements[j];
425 res.push_back(var);
426 }
427
428 return variant(&res);
429 }
430 }
431 if(type_ == TYPE_MAP) {
432 if(v.type_ == TYPE_MAP) {
433 std::map<variant,variant> res(map_->elements);
434
435 for(std::map<variant,variant>::const_iterator i = v.map_->elements.begin(); i != v.map_->elements.end(); ++i) {
436 res[i->first] = i->second;
437 }
438
439 return variant(&res);
440 }
441 }
442
443 if(type_ == TYPE_STRING) {
444 if(v.type_ == TYPE_MAP) {
445 return variant(as_string() + v.as_string());
446 } else if(v.type_ == TYPE_STRING) {
447 return variant(as_string() + v.as_string());
448 }
449
450 std::string s;
451 v.serialize_to_string(s);
452 return variant(as_string() + s);
453 }
454
455 if(v.type_ == TYPE_STRING) {
456 std::string s;
457 serialize_to_string(s);
458 return variant(s + v.as_string());
459 }
460
461 return variant(as_int() + v.as_int());
462 }
463
464 variant variant::operator-(const variant& v) const
465 {
466 return variant(as_int() - v.as_int());
467 }
468
469 variant variant::operator*(const variant& v) const
470 {
471 if(type_ == TYPE_LIST) {
472 int ncopies = v.as_int();
473 if(ncopies < 0) {
474 ncopies *= -1;
475 }
476 const std::vector<variant>& items = list_->elements;
477 std::vector<variant> res;
478 res.reserve(items.size()*ncopies);
479 for(int n = 0; n != ncopies; ++n) {
480 for(int m = 0; m != items.size(); ++m) {
481 res.push_back(items[m]);
482 }
483 }
484
485 return variant(&res);
486 }
487
488 return variant(as_int() * v.as_int());
489 }
490
491 variant variant::operator/(const variant& v) const
492 {
493 const int numerator = as_int();
494 const int denominator = v.as_int();
495 if(denominator == 0) {
496 throw type_error(formatter() << "divide by zero error");
497 }
498
499 return variant(numerator/denominator);
500 }
501
502 variant variant::operator%(const variant& v) const
503 {
504 const int numerator = as_int();
505 const int denominator = v.as_int();
506 if(denominator == 0) {
507 throw type_error(formatter() << "divide by zero error");
508 }
509
510 return variant(numerator%denominator);
511 }
512
513 variant variant::operator^(const variant& v) const
514 {
515 return variant(static_cast<int>(pow(static_cast<double>(as_int()), v.as_int())));
516 }
517
518 variant variant::operator-() const
519 {
520 return variant(-as_int());
521 }
522
523 bool variant::operator==(const variant& v) const
524 {
525 if(type_ != v.type_) {
526 return false;
527 }
528
529 switch(type_) {
530 case TYPE_NULL: {
531 return v.is_null();
532 }
533
534 case TYPE_STRING: {
535 return string_->str == v.string_->str;
536 }
537
538 case TYPE_INT: {
539 return int_value_ == v.int_value_;
540 }
541
542 case TYPE_LIST: {
543 if(num_elements() != v.num_elements()) {
544 return false;
545 }
546
547 for(size_t n = 0; n != num_elements(); ++n) {
548 if((*this)[n] != v[n]) {
549 return false;
550 }
551 }
552
553 return true;
554 }
555
556 case TYPE_MAP: {
557 return map_->elements == v.map_->elements;
558 }
559
560 case TYPE_CALLABLE_LOADING: {
561 return false;
562 }
563
564 case TYPE_CALLABLE: {
565 return callable_->equals(v.callable_);
566 }
567 }
568
569 assert(false);
570 return false;
571 }
572
573 bool variant::operator!=(const variant& v) const
574 {
575 return !operator==(v);
576 }
577
578 bool variant::operator<=(const variant& v) const
579 {
580 if(type_ != v.type_) {
581 return type_ < v.type_;
582 }
583
584 switch(type_) {
585 case TYPE_NULL: {
586 return true;
587 }
588
589 case TYPE_STRING: {
590 return string_->str <= v.string_->str;
591 }
592
593 case TYPE_INT: {
594 return int_value_ <= v.int_value_;
595 }
596
597 case TYPE_LIST: {
598 for(size_t n = 0; n != num_elements() && n != v.num_elements(); ++n) {
599 if((*this)[n] < v[n]) {
600 return true;
601 } else if((*this)[n] > v[n]) {
602 return false;
603 }
604 }
605
606 return num_elements() <= v.num_elements();
607 }
608
609 case TYPE_MAP: {
610 return map_->elements <= v.map_->elements;
611 }
612
613 case TYPE_CALLABLE_LOADING: {
614 return false;
615 }
616
617 case TYPE_CALLABLE: {
618 return !v.callable_->less(callable_);
619 }
620 }
621
622 assert(false);
623 return false;
624 }
625
626 bool variant::operator>=(const variant& v) const
627 {
628 return v <= *this;
629 }
630
631 bool variant::operator<(const variant& v) const
632 {
633 return !(*this >= v);
634 }
635
636 bool variant::operator>(const variant& v) const
637 {
638 return !(*this <= v);
639 }
640
641 void variant::throw_type_error(variant::TYPE t) const
642 {
643 throw type_error(formatter() << "type error: " << " expected " << variant_type_to_string(t) << " but found " << variant_type_to_string(type_) << " (" << to_debug_string() << ")");
644 }
645
646 void variant::serialize_to_string(std::string& str) const
647 {
648 switch(type_) {
649 case TYPE_NULL:
650 str += "null()";
651 break;
652 case TYPE_INT:
653 str += boost::lexical_cast<std::string>(int_value_);
654 break;
655 case TYPE_CALLABLE_LOADING: {
656 ASSERT_LOG(false, "TRIED TO SERIALIZE A VARIANT LOADING");
657 break;
658 }
659 case TYPE_CALLABLE: {
660 if(game_logic::wml_formula_callable_serialization_scope::is_active()) {
661 const game_logic::wml_serializable_formula_callable* obj = try_convert<game_logic::wml_serializable_formula_callable>();
662 if(obj) {
663 //we have an object that is to be serialized into WML. However,
664 //it might be present in the level or a reference to it held
665 //from multiple objects. So we record the address of it and
666 //register it to be recorded seperately.
667 str += "deserialize('" + game_logic::wml_formula_callable_serialization_scope::require_serialized_object(obj) + "')";
668 return;
669 }
670 }
671
672 callable_->serialize(str);
673 break;
674 }
675 case TYPE_LIST: {
676 str += "[";
677 bool first_time = true;
678 for(size_t i=0; i<list_->elements.size(); ++i) {
679 const variant& var = list_->elements[i];
680 if(!first_time) {
681 str += ",";
682 }
683 first_time = false;
684 var.serialize_to_string(str);
685 }
686 str += "]";
687 break;
688 }
689 case TYPE_MAP: {
690 str += "{";
691 bool first_time = true;
692 for(std::map<variant,variant>::const_iterator i=map_->elements.begin(); i != map_->elements.end(); ++i) {
693 if(!first_time) {
694 str += ",";
695 }
696 first_time = false;
697 i->first.serialize_to_string(str);
698 str += "->";
699 i->second.serialize_to_string(str);
700 }
701 str += "}";
702 break;
703 }
704 case TYPE_STRING: {
705 const char* delim = "'";
706 if(strchr(string_->str.c_str(), '\'')) {
707 delim = "~";
708 }
709
710 str += delim;
711 str += string_->str;
712 str += delim;
713 break;
714 }
715 default:
716 assert(false);
717 }
718 }
719
720 void variant::serialize_from_string(const std::string& str)
721 {
722 try {
723 *this = game_logic::formula(str).execute();
724 } catch(...) {
725 *this = variant(str);
726 }
727 }
728
729 variant variant::create_variant_under_construction(intptr_t id)
730 {
731 variant v;
732 v.type_ = TYPE_CALLABLE_LOADING;
733 v.callable_loading_ = id;
734 v.increment_refcount();
735 return v;
736 }
737
738 int variant::refcount() const
739 {
740 switch(type_) {
741 case TYPE_LIST:
742 return list_->refcount;
743 break;
744 case TYPE_STRING:
745 return string_->refcount;
746 break;
747 case TYPE_MAP:
748 return map_->refcount;
749 break;
750 case TYPE_CALLABLE:
751 return callable_->refcount();
752 break;
753 default:
754 return -1;
755 }
756 }
757
758 std::string variant::string_cast() const
759 {
760 switch(type_) {
761 case TYPE_NULL:
762 return "0";
763 case TYPE_INT:
764 return boost::lexical_cast<std::string>(int_value_);
765 case TYPE_CALLABLE_LOADING:
766 return "(object loading)";
767 case TYPE_CALLABLE:
768 return "(object)";
769 case TYPE_LIST: {
770 std::string res = "";
771 for(size_t i=0; i<list_->elements.size(); ++i) {
772 const variant& var = list_->elements[i];
773 if(!res.empty()) {
774 res += ", ";
775 }
776
777 res += var.string_cast();
778 }
779
780 return res;
781 }
782 case TYPE_MAP: {
783 std::string res = "";
784 for(std::map<variant,variant>::const_iterator i=map_->elements.begin(); i != map_->elements.end(); ++i) {
785 if(!res.empty()) {
786 res += ",";
787 }
788 res += i->first.string_cast();
789 res += "->";
790 res += i->second.string_cast();
791 }
792 return res;
793 }
794
795 case TYPE_STRING:
796 return string_->str;
797 default:
798 assert(false);
799 return "invalid";
800 }
801 }
802
803 std::string variant::to_debug_string(std::vector<const game_logic::formula_callable*>* seen) const
804 {
805 std::vector<const game_logic::formula_callable*> seen_stack;
806 if(!seen) {
807 seen = &seen_stack;
808 }
809
810 std::ostringstream s;
811 switch(type_) {
812 case TYPE_NULL:
813 s << "(null)";
814 case TYPE_INT:
815 s << int_value_;
816 break;
817 case TYPE_LIST: {
818 s << "[";
819 for(size_t n = 0; n != num_elements(); ++n) {
820 if(n != 0) {
821 s << ", ";
822 }
823
824 s << operator[](n).to_debug_string(seen);
825 }
826 s << "]";
827 break;
828 }
829 case TYPE_CALLABLE_LOADING: {
830 char buf[64];
831 sprintf(buf, "(loading %x)", callable_loading_);
832 s << buf;
833 }
834
835 case TYPE_CALLABLE: {
836 char buf[64];
837 sprintf(buf, "(%p)", callable_);
838 s << buf << "{";
839 if(std::find(seen->begin(), seen->end(), callable_) == seen->end()) {
840 seen->push_back(callable_);
841 std::vector<game_logic::formula_input> v = callable_->inputs();
842 bool first = true;
843 for(size_t i=0; i<v.size(); ++i) {
844 const game_logic::formula_input& input = v[i];
845 if(!first) {
846 s << ", ";
847 }
848 first = false;
849 s << input.name << " ";
850 if(input.access == game_logic::FORMULA_READ_WRITE) {
851 s << "(read-write) ";
852 } else if(input.access == game_logic::FORMULA_WRITE_ONLY) {
853 s << "(writeonly) ";
854 }
855
856 s << "-> " << callable_->query_value(input.name).to_debug_string(seen);
857 }
858 } else {
859 s << "...";
860 }
861 s << "}";
862 break;
863 }
864 case TYPE_MAP: {
865 s << "{";
866 bool first_time = true;
867 for(std::map<variant,variant>::const_iterator i=map_->elements.begin(); i != map_->elements.end(); ++i) {
868 if(!first_time) {
869 s << ",";
870 }
871 first_time = false;
872 s << i->first.to_debug_string(seen);
873 s << "->";
874 s << i->second.to_debug_string(seen);
875 }
876 s << "}";
877 break;
878 }
879 case TYPE_STRING: {
880 s << "'" << string_->str << "'";
881 break;
882 }
883 }
884
885 return s.str();
886 }
0 #ifndef VARIANT_HPP_INCLUDED
1 #define VARIANT_HPP_INCLUDED
2
3 #include <boost/shared_ptr.hpp>
4 #include <string>
5 #include <map>
6 #include <set>
7 #include <vector>
8
9 #include <string.h>
10
11 #include "formula_fwd.hpp"
12
13 namespace game_logic {
14 class formula_callable;
15 }
16
17 void push_call_stack(const char* str);
18 void pop_call_stack();
19 std::string get_call_stack();
20
21 struct call_stack_manager {
22 explicit call_stack_manager(const char* str) {
23 push_call_stack(str);
24 }
25
26 ~call_stack_manager() {
27 pop_call_stack();
28 }
29 };
30
31 class variant;
32 void swap_variants_loading(std::set<variant*>& v);
33
34 struct variant_list;
35 struct variant_string;
36 struct variant_map;
37 struct variant_fn;
38
39 struct type_error {
40 explicit type_error(const std::string& str);
41 std::string message;
42 };
43
44 class variant {
45 public:
46 variant() : type_(TYPE_NULL), int_value_(0) {}
47 explicit variant(int n) : type_(TYPE_INT), int_value_(n) {}
48 explicit variant(const game_logic::formula_callable* callable);
49 explicit variant(std::vector<variant>* array);
50 explicit variant(const std::string& str);
51 explicit variant(std::map<variant,variant>* map);
52 variant(game_logic::const_formula_ptr, const std::vector<std::string>& args, const game_logic::formula_callable& callable);
53
54 static variant create_variant_under_construction(intptr_t id);
55
56 //only call the non-inlined release() function if we have a type
57 //that needs releasing.
58 ~variant() { if(type_ > TYPE_INT) { release(); } }
59
60 variant(const variant& v) {
61 memcpy(this, &v, sizeof(v));
62 if(type_ > TYPE_INT) {
63 increment_refcount();
64 }
65 }
66
67 const variant& operator=(const variant& v);
68
69 const variant& operator[](size_t n) const;
70 const variant& operator[](const variant v) const;
71 size_t num_elements() const;
72
73 variant operator()(const std::vector<variant>& args) const;
74
75 variant get_member(const std::string& str) const;
76
77 bool is_string() const { return type_ == TYPE_STRING; }
78 bool is_null() const { return type_ == TYPE_NULL; }
79 bool is_int() const { return type_ == TYPE_INT; }
80 bool is_map() const { return type_ == TYPE_MAP; }
81 bool is_function() const { return type_ == TYPE_FUNCTION; }
82 int as_int() const { if(type_ == TYPE_NULL) { return 0; } must_be(TYPE_INT); return int_value_; }
83 bool as_bool() const;
84
85 bool is_list() const { return type_ == TYPE_LIST; }
86
87 const std::string& as_string() const;
88
89 bool is_callable() const { return type_ == TYPE_CALLABLE; }
90 const game_logic::formula_callable* as_callable() const {
91 must_be(TYPE_CALLABLE); return callable_; }
92 game_logic::formula_callable* mutable_callable() const {
93 must_be(TYPE_CALLABLE); return mutable_callable_; }
94
95 intptr_t as_callable_loading() const { return callable_loading_; }
96
97 template<typename T>
98 T* try_convert() const {
99 if(!is_callable()) {
100 return NULL;
101 }
102
103 return dynamic_cast<T*>(mutable_callable());
104 }
105
106 template<typename T>
107 T* convert_to() const {
108 T* res = dynamic_cast<T*>(mutable_callable());
109 if(!res) {
110 throw type_error("could not convert type");
111 }
112
113 return res;
114 }
115
116 variant operator+(const variant&) const;
117 variant operator-(const variant&) const;
118 variant operator*(const variant&) const;
119 variant operator/(const variant&) const;
120 variant operator^(const variant&) const;
121 variant operator%(const variant&) const;
122 variant operator-() const;
123
124 bool operator==(const variant&) const;
125 bool operator!=(const variant&) const;
126 bool operator<(const variant&) const;
127 bool operator>(const variant&) const;
128 bool operator<=(const variant&) const;
129 bool operator>=(const variant&) const;
130
131 variant get_keys() const;
132 variant get_values() const;
133
134 void serialize_to_string(std::string& str) const;
135 void serialize_from_string(const std::string& str);
136
137 int refcount() const;
138
139 std::string string_cast() const;
140
141 std::string to_debug_string(std::vector<const game_logic::formula_callable*>* seen=NULL) const;
142
143 std::vector<variant>& initialize_list();
144 enum TYPE { TYPE_NULL, TYPE_INT, TYPE_CALLABLE, TYPE_CALLABLE_LOADING, TYPE_LIST, TYPE_STRING, TYPE_MAP, TYPE_FUNCTION };
145 private:
146 void must_be(TYPE t) const {
147 #if !TARGET_OS_IPHONE
148 if(type_ != t) {
149 throw_type_error(t);
150 }
151 #endif
152 }
153
154 void throw_type_error(TYPE expected) const;
155
156 TYPE type_;
157 union {
158 int int_value_;
159 const game_logic::formula_callable* callable_;
160 game_logic::formula_callable* mutable_callable_;
161 intptr_t callable_loading_;
162 variant_list* list_;
163 variant_string* string_;
164 variant_map* map_;
165 variant_fn* fn_;
166 };
167
168 //function to initialize the variant as a list, returning the
169 //underlying list vector to be initialized. If the variant is already a list,
170 //and holds the only reference to that list, then that list object may
171 //be cleared and re-used as a performance optimization.
172
173 void increment_refcount();
174 void release();
175 };
176
177 template<typename T>
178 T* convert_variant(const variant& v) {
179 T* res = dynamic_cast<T*>(v.mutable_callable());
180 if(!res) {
181 throw type_error("could not convert type");
182 }
183
184 return res;
185 }
186
187 template<typename T>
188 T* try_convert_variant(const variant& v) {
189 if(!v.is_callable()) {
190 return NULL;
191 }
192
193 return dynamic_cast<T*>(v.mutable_callable());
194 }
195
196 #endif
0 #include <SDL.h>
1 #ifndef SDL_VIDEO_OPENGL_ES
2 #include <GL/glew.h>
3 #endif
4
5 #include <iostream>
6 #include <math.h>
7
8 #include "asserts.hpp"
9 #include "color_utils.hpp"
10 #include "foreach.hpp"
11 #include "formatter.hpp"
12 #include "formula.hpp"
13 #include "level.hpp"
14 #include "raster.hpp"
15 #include "string_utils.hpp"
16 #include "water.hpp"
17 #include "wml_node.hpp"
18 #include "wml_utils.hpp"
19 #include "color_utils.hpp"
20
21 namespace {
22 const int WaterZorder = 3;
23 }
24
25 water::water()
26 : zorder_(WaterZorder)
27 {}
28
29 water::water(wml::const_node_ptr water_node) :
30 zorder_(wml::get_int(water_node, "zorder", WaterZorder)),
31 current_x_formula_(game_logic::formula::create_optional_formula(water_node->attr("current_x_formula"))),
32 current_y_formula_(game_logic::formula::create_optional_formula(water_node->attr("current_y_formula")))
33 {
34 FOREACH_WML_CHILD(area_node, water_node, "area") {
35 const rect r(area_node->attr("rect"));
36 std::vector<std::string> str = util::split(area_node->attr("color"));
37 unsigned char color[4];
38 for(int n = 0; n != 4; ++n) {
39 if(n < str.size()) {
40 color[n] = atoi(str[n].c_str());
41 } else {
42 color[n] = 0;
43 }
44 }
45
46 variant obj;
47 obj.serialize_from_string(area_node->attr("object"));
48 areas_.push_back(area(r, color, obj));
49 }
50 }
51
52 wml::node_ptr water::write() const
53 {
54 wml::node_ptr result(new wml::node("water"));
55 result->set_attr("zorder", formatter() << zorder_);
56 foreach(const area& a, areas_) {
57 wml::node_ptr node(new wml::node("area"));
58 node->set_attr("rect", a.rect_.to_string());
59 node->set_attr("color", formatter()
60 << static_cast<int>(a.color_[0]) << ","
61 << static_cast<int>(a.color_[1]) << ","
62 << static_cast<int>(a.color_[2]) << ","
63 << static_cast<int>(a.color_[3]));
64
65 std::string obj;
66 a.obj_.serialize_to_string(obj);
67 node->set_attr("object", obj);
68
69 result->add_child(node);
70 }
71
72 return result;
73 }
74
75 void water::add_rect(const rect& r, const unsigned char* color, variant obj)
76 {
77 areas_.push_back(area(r, color, obj));
78 }
79
80 void water::delete_rect(const rect& r)
81 {
82 for(std::vector<area>::iterator i = areas_.begin(); i != areas_.end(); ) {
83 if(r == i->rect_) {
84 i = areas_.erase(i);
85 } else {
86 ++i;
87 }
88 }
89 }
90
91 void water::begin_drawing()
92 {
93 foreach(area& a, areas_) {
94 graphics::add_raster_distortion(&a.distortion_);
95 }
96 }
97
98 void water::end_drawing() const
99 {
100 foreach(const area& a, areas_) {
101 graphics::remove_raster_distortion(&a.distortion_);
102 }
103 }
104
105 /*
106 void water::set_surface_detection_rects(int zorder)
107 {
108 const int offset = get_offset(zorder);
109 foreach(area& a, areas_) {
110 //detect drawing at the surface of the water.
111 a.draw_detection_buf_.resize(a.rect_.w());
112 memset(&a.draw_detection_buf_[0], 0, a.draw_detection_buf_.size());
113 graphics::set_draw_detection_rect(rect(a.rect_.x(), a.rect_.y() + offset, a.rect_.w(), 1), &a.draw_detection_buf_[0]);
114 }
115 }
116 */
117
118 bool water::draw(int x, int y, int w, int h) const
119 {
120 glShadeModel(GL_SMOOTH);
121
122 bool result = false;
123 foreach(const area& a, areas_) {
124 if(draw_area(a, x, y, w, h)) {
125 result = true;
126 }
127 }
128
129 end_drawing();
130 glShadeModel(GL_FLAT);
131
132 return result;
133 }
134
135 void water::add_wave(const point& p, double xvelocity, double height, double length, double delta_height, double delta_length)
136 {
137 foreach(area& a, areas_) {
138 if(point_in_rect(p, a.rect_)) {
139 std::pair<int, int> bounds(a.rect_.x(), a.rect_.x2());
140 for(int n = 0; n != a.surface_segments_.size(); ++n) {
141 if(p.x >= a.surface_segments_[n].first && p.x <= a.surface_segments_[n].second) {
142 bounds = a.surface_segments_[n];
143 break;
144 }
145 }
146 wave wv = { p.x, xvelocity, height, length, delta_height, delta_length, bounds.first, bounds.second };
147 a.waves_.push_back(wv);
148 return;
149 }
150 }
151 }
152
153 bool water::draw_area(const water::area& a, int x, int y, int w, int h) const
154 {
155 const graphics::color waterline_color(250, 240, 205, 255);
156 const graphics::color shallowwater_color(0, 51, 61, 140);
157 const graphics::color deepwater_color(0, 51, 61, 153);
158 const SDL_Rect waterline_rect = {a.rect_.x(), a.rect_.y(), a.rect_.w(), 2};
159 const SDL_Rect underwater_rect = {a.rect_.x(), a.rect_.y(), a.rect_.w(), a.rect_.h()};
160
161 unsigned char water_color[] = {a.color_[0], a.color_[1], a.color_[2], a.color_[3]};
162
163 glBlendFunc(GL_ONE, GL_ONE);
164 #if GL_OES_blend_subtract
165 glBlendEquationOES(GL_FUNC_REVERSE_SUBTRACT_OES);
166 #else
167 if(GLEW_EXT_blend_equation_separate && GLEW_ARB_imaging) {
168 glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
169 } else {
170 const int max_color = std::max(water_color[0], std::max(water_color[1], water_color[2]));
171 water_color[0] = (max_color - water_color[0])/8;
172 water_color[1] = (max_color - water_color[1])/8;
173 water_color[2] = (max_color - water_color[2])/8;
174 }
175 #endif
176 glDisable(GL_TEXTURE_2D);
177 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
178
179 GLfloat vertices[] = {
180 waterline_rect.x, waterline_rect.y, //shallow water colored
181 waterline_rect.x + waterline_rect.w, waterline_rect.y,
182
183 waterline_rect.x, waterline_rect.y + 100, //deep water colored
184 waterline_rect.x + waterline_rect.w, waterline_rect.y + 100,
185 waterline_rect.x, underwater_rect.y + underwater_rect.h,
186 waterline_rect.x + waterline_rect.w, underwater_rect.y + underwater_rect.h
187 };
188
189 glColor4ub(water_color[0], water_color[1], water_color[2], water_color[3]);
190 glVertexPointer(2, GL_FLOAT, 0, vertices);
191 glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(vertices)/sizeof(GLfloat)/2);
192 #if GL_OES_blend_subtract
193 glBlendEquationOES(GL_FUNC_ADD_OES);
194 #else
195 if (GLEW_EXT_blend_equation_separate && GLEW_ARB_imaging)
196 glBlendEquation(GL_FUNC_ADD);
197 #endif
198 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
199
200 glLineWidth(2.0);
201
202 typedef std::pair<int, int> Segment;
203
204 glEnableClientState(GL_COLOR_ARRAY);
205
206 const int EndSegmentSize = 20;
207
208 foreach(const Segment& seg, a.surface_segments_) {
209 glColor4f(1.0, 1.0, 1.0, 1.0);
210 GLfloat varray[] = {
211 seg.first - EndSegmentSize, waterline_rect.y,
212 seg.first, waterline_rect.y,
213 seg.second, waterline_rect.y,
214 seg.second + EndSegmentSize, waterline_rect.y,
215 };
216 static const unsigned char vcolors[] = {
217 255, 255, 255, 0,
218 255, 255, 255, 255,
219 255, 255, 255, 255,
220 255, 255, 255, 0,
221 };
222 glVertexPointer(2, GL_FLOAT, 0, varray);
223 glColorPointer(4, GL_UNSIGNED_BYTE, 0, vcolors);
224 glDrawArrays(GL_LINE_STRIP, 0, 4);
225
226 //draw a second line, in a different color, just below the first
227 glColor4f(0.0, 0.9, 0.75, 0.5);
228 GLfloat varray2[] = {
229 seg.first - EndSegmentSize, waterline_rect.y+2,
230 seg.first, waterline_rect.y+2,
231 seg.second, waterline_rect.y+2,
232 seg.second + EndSegmentSize, waterline_rect.y+2,
233 };
234 static const unsigned char vcolors2[] = {
235 0, 230, 200, 0,
236 0, 230, 200, 128,
237 0, 230, 200, 128,
238 0, 230, 200, 0,
239 };
240 glVertexPointer(2, GL_FLOAT, 0, varray2);
241 glColorPointer(4, GL_UNSIGNED_BYTE, 0, vcolors2);
242 glDrawArrays(GL_LINE_STRIP, 0, 4);
243 }
244
245 glDisableClientState(GL_COLOR_ARRAY);
246
247 glColor4f(1.0, 1.0, 1.0, 1.0);
248 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
249 glEnable(GL_TEXTURE_2D);
250
251 return true;
252 }
253
254 namespace {
255 bool wave_dead(const water::wave& w) {
256 return w.height <= 0.5 || w.length <= 0;
257 }
258 }
259
260 void water::process(const level& lvl)
261 {
262 foreach(area& a, areas_) {
263 init_area_surface_segments(lvl, a);
264
265 a.distortion_ = graphics::water_distortion(lvl.cycle(), a.rect_);
266 foreach(wave& w, a.waves_) {
267 w.process();
268
269 //if the wave has hit the edge, then turn it around.
270 if(w.xpos < w.left_bound && w.xvelocity < 0) {
271 w.xvelocity *= -1.0;
272 }
273
274 if(w.xpos > w.right_bound && w.xvelocity > 0) {
275 w.xvelocity *= -1.0;
276 }
277 }
278
279 a.waves_.erase(std::remove_if(a.waves_.begin(), a.waves_.end(), wave_dead), a.waves_.end());
280 }
281 }
282
283 void water::wave::process() {
284 xpos += xvelocity;
285 height *= 0.996;
286 length += delta_length;
287 }
288
289 void water::get_current(const entity& e, int* velocity_x, int* velocity_y) const
290 {
291 if(velocity_x && current_x_formula_) {
292 *velocity_x += current_x_formula_->execute(e).as_int();
293 }
294
295 if(velocity_y && current_y_formula_) {
296 *velocity_y += current_y_formula_->execute(e).as_int();
297 }
298 }
299
300 bool water::is_underwater(const rect& r, rect* result_water_area, variant* e) const
301 {
302 const point p((r.x() + r.x2())/2, (r.y() + r.y2())/2);
303 foreach(const area& a, areas_) {
304 if(point_in_rect(p, a.rect_)) {
305 if(result_water_area) {
306 *result_water_area = a.rect_;
307 }
308
309 if(e) {
310 *e = a.obj_;
311 }
312 return true;
313 }
314 }
315
316 return false;
317 }
318
319 void water::init_area_surface_segments(const level& lvl, water::area& a)
320 {
321 if(a.surface_segments_init_) {
322 return;
323 }
324
325 a.surface_segments_init_ = true;
326
327 bool prev_solid = true;
328 int begin_segment = 0;
329 for(int x = a.rect_.x(); x != a.rect_.x2(); ++x) {
330 const bool solid = lvl.solid(x, a.rect_.y()) || x == a.rect_.x2()-1;
331 if(solid && !prev_solid) {
332 a.surface_segments_.push_back(std::make_pair(begin_segment, x));
333 } else if(!solid && prev_solid) {
334 begin_segment = x;
335 }
336
337 prev_solid = solid;
338 }
339 }
340
341 water::area::area(const rect& r, const unsigned char* pcolor, variant obj)
342 : rect_(r), distortion_(0, rect(0,0,0,0)), surface_segments_init_(false), obj_(obj)
343 {
344 for(int n = 0; n != 4; ++n) {
345 color_[n] = pcolor[n];
346 }
347 }
0 #ifndef WATER_HPP_INCLUDED
1 #define WATER_HPP_INCLUDED
2
3 #include <vector>
4
5 #include "entity_fwd.hpp"
6 #include "formula_fwd.hpp"
7 #include "geometry.hpp"
8 #include "raster_distortion.hpp"
9 #include "variant.hpp"
10 #include "wml_node_fwd.hpp"
11
12 class level;
13
14 class water
15 {
16 public:
17 water();
18 explicit water(wml::const_node_ptr node);
19
20 wml::node_ptr write() const;
21
22 //color must point to an array of 4 bytes.
23 void add_rect(const rect& r, const unsigned char* color, variant obj);
24 void delete_rect(const rect& r);
25
26 bool draw(int x, int y, int w, int h) const;
27 int zorder() const { return zorder_; }
28
29 void begin_drawing();
30 void end_drawing() const;
31 void process(const level& lvl);
32
33 void get_current(const entity& e, int* velocity_x, int* velocity_y) const;
34
35 bool is_underwater(const rect& r, rect* water_area=NULL, variant* obj=NULL) const;
36
37 void add_wave(const point& p, double xvelocity, double height, double length, double delta_height, double delta_length);
38
39 struct wave {
40 double xpos;
41 double xvelocity;
42 double height;
43 double length;
44 double delta_height;
45 double delta_length;
46
47 int left_bound, right_bound;
48
49 void process();
50 };
51
52 private:
53
54 struct area {
55 area(const rect& r, const unsigned char* color, variant obj);
56 rect rect_;
57 graphics::water_distortion distortion_;
58 std::vector<char> draw_detection_buf_;
59
60 std::vector<wave> waves_;
61
62 //segments of the surface without solid.
63 std::vector<std::pair<int, int> > surface_segments_;
64 bool surface_segments_init_;
65
66 unsigned char color_[4];
67 variant obj_;
68 };
69
70 std::vector<area> areas_;
71
72 static void init_area_surface_segments(const level& lvl, area& a);
73
74 bool draw_area(const area& a, int x, int y, int w, int h) const;
75
76 bool draw_area(const area& a, int begin_layer, int end_layer, int x, int y, int w, int h) const;
77
78 int zorder_;
79
80
81 /*
82 struct zorder_pos {
83 int zorder;
84 int offset;
85 SDL_Color color;
86 };
87
88 std::vector<zorder_pos> positions_;
89 */
90
91 enum { BadOffset = -100000 };
92
93 game_logic::const_formula_ptr current_x_formula_, current_y_formula_;
94 };
95
96 #endif
0 #include <cstdio>
1 #include <math.h>
2 #include <vector>
3
4 #include "preferences.hpp"
5 #include "water_particle_system.hpp"
6 #include "wml_utils.hpp"
7
8 water_particle_system_info::water_particle_system_info(wml::const_node_ptr node)
9 : number_of_particles(wml::get_int(node, "number_of_particles", 1500)),
10 repeat_period(wml::get_int(node, "repeat_period", 1000)),
11 velocity_x(wml::get_int(node, "velocity_x")),
12 velocity_y(wml::get_int(node, "velocity_y", -5)),
13 velocity_rand(wml::get_int(node, "velocity_rand", 3)),
14 dot_size(wml::get_int(node, "dot_size", 1))
15 {
16 irgba = graphics::color(node->attr("color")).value();
17
18 if(dot_size > 1 && preferences::xypos_draw_mask) {
19 //if we are clipping our drawing granularity, then we have a small
20 //enough screen that we want to shrink the particles.
21 dot_size /= 2;
22 }
23 }
24
25 water_particle_system_factory::water_particle_system_factory (wml::const_node_ptr node)
26 : info(node)
27 {
28
29 }
30
31 particle_system_ptr water_particle_system_factory::create(const entity& e) const
32 {
33 return particle_system_ptr(new water_particle_system(e, *this));
34 }
35
36
37 water_particle_system::water_particle_system(const entity& e, const water_particle_system_factory& factory)
38 : factory_(factory), info_(factory.info), cycle_(0)
39 {
40 area_ = rect("0,0,1,1");
41 base_velocity = sqrtf(info_.velocity_x*info_.velocity_x + info_.velocity_y*info_.velocity_y);
42 direction[0] = info_.velocity_x / base_velocity;
43 direction[1] = info_.velocity_y / base_velocity;
44 particles_.reserve(info_.number_of_particles);
45 for (int i = 0; i < info_.number_of_particles; i++)
46 {
47 particle new_p;
48 new_p.pos[0] = rand()%info_.repeat_period;
49 new_p.pos[1] = rand()%info_.repeat_period;
50 new_p.velocity = base_velocity + (info_.velocity_rand ? (rand() % info_.velocity_rand) : 0);
51 particles_.push_back(new_p);
52 }
53 }
54
55 void water_particle_system::process(const level& lvl, const entity& e)
56 {
57 ++cycle_;
58
59 foreach(particle& p, particles_)
60 {
61 p.pos[0] = static_cast<int>(p.pos[0]+direction[0] * p.velocity) % info_.repeat_period;
62 p.pos[1] = static_cast<int>(p.pos[1]+direction[1] * p.velocity) % info_.repeat_period;
63 }
64
65
66 //while (particles_.size() > 1500) particles_.pop_front();
67 }
68
69 void water_particle_system::draw(const rect& screen_area, const entity& e) const
70 {
71 const rect area = intersection_rect(screen_area, area_);
72 if(area.w() == 0 || area.h() == 0) {
73 return;
74 }
75
76 int offset_x = area.x() - area.x()%info_.repeat_period;
77 if (area.x() < 0) offset_x -= info_.repeat_period;
78 int offset_y = area.y() - area.y()%info_.repeat_period;
79 if (area.y() < 0) offset_y -= info_.repeat_period;
80 static std::vector<GLshort> vertices;
81 vertices.clear();
82 foreach(const particle& p, particles_)
83 {
84 GLshort my_y = p.pos[1]+offset_y;
85 GLshort xpos = p.pos[0]+offset_x;
86 while(my_y < area_.y()) {
87 my_y += info_.repeat_period;
88 }
89
90 while(xpos < area_.x()) {
91 xpos += info_.repeat_period;
92 }
93
94 if(my_y > area_.y2() || xpos > area_.x2()) {
95 continue;
96 }
97
98 while(my_y <= area.y2()) {
99 GLshort my_x = xpos;
100
101 while (my_x <= area.x2()) {
102 vertices.push_back(my_x);
103 vertices.push_back(my_y);
104 my_x += info_.repeat_period;
105 }
106 my_y += info_.repeat_period;
107 }
108 }
109
110 if(vertices.empty()) {
111 return;
112 }
113 glDisable(GL_TEXTURE_2D);
114 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
115 glPointSize(info_.dot_size);
116 glColor4f(info_.rgba[0]/255.0, info_.rgba[1]/255.0, info_.rgba[2]/255.0, info_.rgba[3]/255.0);
117
118 glVertexPointer(2, GL_SHORT, 0, &vertices.front());
119 glDrawArrays(GL_POINTS, 0, vertices.size()/2);
120
121 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
122 glEnable(GL_TEXTURE_2D);
123 glColor4f(1.0, 1.0, 1.0, 1.0);
124 }
125
126
127 void water_particle_system::set_value(const std::string& key, const variant& value)
128 {
129 if(key == "area") {
130 if(value.is_string()) {
131 area_ = rect(value.as_string());
132 } else if(value.is_list() && value.num_elements() == 4) {
133 area_ = rect::from_coordinates(value[0].as_int(), value[1].as_int(), value[2].as_int(), value[3].as_int());
134 }
135 }
136 }
0 #ifndef water_PARTICLE_SYSTEM_H
1 #define water_PARTICLE_SYSTEM_H
2
3 #include <GL/gl.h>
4
5 #include <deque>
6
7 #include "particle_system.hpp"
8 #include "foreach.hpp"
9 #include "entity.hpp"
10 #include "wml_utils.hpp"
11 #include "color_utils.hpp"
12 #include "geometry.hpp"
13
14 struct water_particle_system_info {
15 water_particle_system_info(wml::const_node_ptr node);
16
17 int number_of_particles;
18 int repeat_period;
19 int velocity_x, velocity_y;
20 int velocity_rand;
21 int dot_size;
22
23 union {
24 uint8_t rgba[4];
25 uint32_t irgba;
26 };
27 };
28
29 class water_particle_system_factory : public particle_system_factory {
30 public:
31 explicit water_particle_system_factory(wml::const_node_ptr node);
32 ~water_particle_system_factory() {}
33
34 particle_system_ptr create(const entity& e) const;
35 water_particle_system_info info;
36 };
37
38 class water_particle_system : public particle_system
39 {
40 public:
41 water_particle_system(const entity& e, const water_particle_system_factory& factory);
42
43 bool is_destroyed() const { return false; }
44 void process(const level& lvl, const entity& e);
45 void draw(const rect& area, const entity& e) const;
46
47 private:
48 variant get_value(const std::string& key) const { return variant(); }
49 void set_value(const std::string& key, const variant& value);
50
51 const water_particle_system_factory& factory_;
52 const water_particle_system_info& info_;
53
54 int cycle_;
55
56 rect area_;
57
58 struct particle {
59 GLfloat pos[2];
60 GLfloat velocity;
61 };
62
63 GLfloat direction[2];
64 GLfloat base_velocity;
65
66 std::vector<particle> particles_;
67 };
68
69 #endif
0 #include <cstdio>
1 #include <math.h>
2 #include <vector>
3
4 #include "weather_particle_system.hpp"
5 #include "wml_utils.hpp"
6
7 weather_particle_system_factory::weather_particle_system_factory (wml::const_node_ptr node)
8 : info(node)
9 {
10
11 }
12
13 particle_system_ptr weather_particle_system_factory::create(const entity& e) const
14 {
15 return particle_system_ptr(new weather_particle_system(e, *this));
16 }
17
18
19 weather_particle_system::weather_particle_system(const entity& e, const weather_particle_system_factory& factory)
20 : factory_(factory), info_(factory.info), cycle_(0)
21 {
22 base_velocity = sqrtf(info_.velocity_x*info_.velocity_x + info_.velocity_y*info_.velocity_y);
23 direction[0] = info_.velocity_x / base_velocity;
24 direction[1] = info_.velocity_y / base_velocity;
25 particles_.reserve(info_.number_of_particles);
26 for (int i = 0; i < info_.number_of_particles; i++)
27 {
28 particle new_p;
29 new_p.pos[0] = rand()%info_.repeat_period;
30 new_p.pos[1] = rand()%info_.repeat_period;
31 new_p.velocity = base_velocity + (info_.velocity_rand ? (rand() % info_.velocity_rand) : 0);
32 particles_.push_back(new_p);
33 }
34 }
35
36 void weather_particle_system::process(const level& lvl, const entity& e)
37 {
38 ++cycle_;
39
40 foreach(particle& p, particles_)
41 {
42 p.pos[0] = static_cast<int>(p.pos[0]+direction[0] * p.velocity) % info_.repeat_period;
43 p.pos[1] = static_cast<int>(p.pos[1]+direction[1] * p.velocity) % info_.repeat_period;
44 }
45
46 //while (particles_.size() > 1500) particles_.pop_front();
47 }
48
49 void weather_particle_system::draw(const rect& area, const entity& e) const
50 {
51 glDisable(GL_TEXTURE_2D);
52 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
53 glLineWidth(info_.line_width);
54 glColor4f(info_.rgba[0]/255.0, info_.rgba[1]/255.0, info_.rgba[2]/255.0, info_.rgba[3]/255.0);
55 int offset_x = area.x() - area.x()%info_.repeat_period;
56 if (area.x() < 0) offset_x -= info_.repeat_period;
57 int offset_y = area.y() - area.y()%info_.repeat_period;
58 if (area.y() < 0) offset_y -= info_.repeat_period;
59 static std::vector<GLfloat> vertices;
60 vertices.clear();
61 foreach(const particle& p, particles_)
62 {
63 float my_y = p.pos[1]+offset_y;
64 do
65 {
66 float my_x = p.pos[0]+offset_x;
67 do
68 {
69 vertices.push_back(my_x);
70 vertices.push_back(my_y);
71 vertices.push_back(my_x+direction[0]*info_.line_length);
72 vertices.push_back(my_y+direction[1]*info_.line_length);
73 my_x += info_.repeat_period;
74 //printf("my_x: %f, area.x: %i, area.w: %i\n", my_x, area.x(), area.w());
75 } while (my_x < area.x()+area.w());
76 my_y += info_.repeat_period;
77 } while (my_y < area.y()+area.h());
78 }
79 glVertexPointer(2, GL_FLOAT, 0, &vertices.front());
80 glDrawArrays(GL_LINES, 0, vertices.size()/2);
81 //glDisable(GL_SMOOTH);
82 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
83 glEnable(GL_TEXTURE_2D);
84 glColor4f(1.0, 1.0, 1.0, 1.0);
85 }
0 #ifndef WEATHER_PARTICLE_SYSTEM_HPP_INCLUDED
1 #define WEATHER_PARTICLE_SYSTEM_HPP_INCLUDED
2
3 #include <GL/gl.h>
4
5 #include <deque>
6
7 #include "particle_system.hpp"
8 #include "foreach.hpp"
9 #include "entity.hpp"
10 #include "wml_utils.hpp"
11 #include "color_utils.hpp"
12
13 struct weather_particle_system_info {
14 weather_particle_system_info(wml::const_node_ptr node)
15 : number_of_particles(wml::get_int(node, "number_of_particles", 1500)),
16 repeat_period(wml::get_int(node, "repeat_period", 1000)),
17 velocity_x(wml::get_int(node, "velocity_x")),
18 velocity_y(wml::get_int(node, "velocity_y", 5)),
19 velocity_rand(wml::get_int(node, "velocity_rand", 3)),
20 line_width(wml::get_int(node, "line_width", 1)),
21 line_length(wml::get_int(node, "line_length", 8))
22 {
23 irgba = graphics::color(node->attr("color")).value();
24 }
25 int number_of_particles;
26 int repeat_period;
27 int velocity_x, velocity_y;
28 int velocity_rand;
29 int line_width, line_length;
30
31 union {
32 uint8_t rgba[4];
33 uint32_t irgba;
34 };
35 };
36
37 class weather_particle_system_factory : public particle_system_factory {
38 public:
39 explicit weather_particle_system_factory(wml::const_node_ptr node);
40 ~weather_particle_system_factory() {}
41
42 particle_system_ptr create(const entity& e) const;
43 weather_particle_system_info info;
44 };
45
46 class weather_particle_system : public particle_system
47 {
48 public:
49 weather_particle_system(const entity& e, const weather_particle_system_factory& factory);
50
51 bool is_destroyed() const { return false; }
52 void process(const level& lvl, const entity& e);
53 void draw(const rect& area, const entity& e) const;
54
55 private:
56 variant get_value(const std::string& key) const { return variant(); }
57
58 const weather_particle_system_factory& factory_;
59 const weather_particle_system_info& info_;
60
61 int cycle_;
62
63 struct particle {
64 GLfloat pos[2];
65 GLfloat velocity;
66 };
67
68 GLfloat direction[2];
69 GLfloat base_velocity;
70
71 std::vector<particle> particles_;
72 };
73
74 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12
13 #include "preferences.hpp"
14 #include "raster.hpp"
15 #include "tooltip.hpp"
16 #include "translate.hpp"
17 #include "widget.hpp"
18
19 #include <iostream>
20
21 namespace gui {
22
23 widget::~widget()
24 {
25 if(tooltip_displayed_) {
26 gui::remove_tooltip(tooltip_);
27 }
28 }
29
30 void widget::normalize_event(SDL_Event* event)
31 {
32 switch(event->type) {
33 case SDL_MOUSEMOTION:
34 event->motion.x = (event->motion.x*graphics::screen_width())/preferences::virtual_screen_width();
35 event->motion.y = (event->motion.y*graphics::screen_height())/preferences::virtual_screen_height();
36 event->motion.x -= x();
37 event->motion.y -= y();
38 break;
39 case SDL_MOUSEBUTTONDOWN:
40 case SDL_MOUSEBUTTONUP:
41 event->button.x = (event->button.x*graphics::screen_width())/preferences::virtual_screen_width();
42 event->button.y = (event->button.y*graphics::screen_height())/preferences::virtual_screen_height();
43 event->button.x -= x();
44 event->button.y -= y();
45 break;
46 default:
47 break;
48 }
49 }
50
51 void widget::set_tooltip(const std::string& str)
52 {
53 if(tooltip_displayed_) {
54 gui::remove_tooltip(tooltip_);
55 tooltip_displayed_ = false;
56 }
57 tooltip_.reset(new std::string(i18n::translate(str)));
58 }
59
60 bool widget::process_event(const SDL_Event& event, bool claimed)
61 {
62 if(!claimed) {
63 if(tooltip_ && event.type == SDL_MOUSEMOTION) {
64 if(event.motion.x >= x() && event.motion.x <= x()+width() &&
65 event.motion.y >= y() && event.motion.y <= y()+height()) {
66 if(!tooltip_displayed_) {
67 gui::set_tooltip(tooltip_);
68 tooltip_displayed_ = true;
69 }
70 } else {
71 if(tooltip_displayed_) {
72 gui::remove_tooltip(tooltip_);
73 tooltip_displayed_ = false;
74 }
75 }
76 }
77 }
78
79 return handle_event(event, claimed);
80 }
81
82 void widget::draw() const
83 {
84 if(visible_) {
85 handle_draw();
86 }
87 }
88
89 int widget::x() const
90 {
91 return x_;
92 }
93
94 int widget::y() const
95 {
96 return y_;
97 }
98
99 int widget::width() const
100 {
101 return w_;
102 }
103
104 int widget::height() const
105 {
106 return h_;
107 }
108
109 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef WIDGET_HPP_INCLUDED
13 #define WIDGET_HPP_INCLUDED
14
15 #include <boost/shared_ptr.hpp>
16 #include <string>
17
18 #include <SDL.h>
19 #include "input.hpp"
20
21 namespace gui {
22
23 class widget: public virtual input::listener
24 {
25 public:
26 bool process_event(const SDL_Event& event, bool claimed);
27 void draw() const;
28
29 virtual void set_loc(int x, int y) { x_ = x; y_ = y; }
30 virtual void set_dim(int w, int h) { w_ = w; h_ = h; }
31
32 int x() const;
33 int y() const;
34 int width() const;
35 int height() const;
36 void set_tooltip(const std::string& str);
37 bool visible() { return visible_; }
38 void set_visible(bool visible) { visible_ = visible; }
39 protected:
40 widget() : x_(0), y_(0), w_(0), h_(0), tooltip_displayed_(false), visible_(true)
41 {}
42 virtual ~widget();
43
44 void normalize_event(SDL_Event* event);
45 private:
46 virtual void handle_draw() const = 0;
47 virtual bool handle_event(const SDL_Event& event, bool claimed) { return claimed; }
48 int x_, y_;
49 int w_, h_;
50 boost::shared_ptr<std::string> tooltip_;
51 bool tooltip_displayed_;
52 bool visible_;
53 };
54
55 typedef boost::shared_ptr<widget> widget_ptr;
56 typedef boost::shared_ptr<const widget> const_widget_ptr;
57
58 }
59
60 #endif
0 #include <iostream>
1
2 #include "wml_formula_adapter.hpp"
3 #include "wml_node.hpp"
4 #include "wml_schema.hpp"
5 #include "wml_writer.hpp"
6
7 namespace wml {
8 variant node_elements_callable::get_value(const std::string& key) const
9 {
10 if(key == "_all") {
11 std::vector<variant> items;
12 wml::node::const_all_child_iterator i1 = node_->begin_children();
13 wml::node::const_all_child_iterator i2 = node_->end_children();
14 for(; i1 != i2; ++i1) {
15 items.push_back(variant(new node_callable(*i1)));
16 }
17
18 return variant(&items);
19 }
20
21 const bool repeated = node_->get_schema() == NULL || node_->get_schema()->is_element_repeated(key);
22 if(repeated) {
23 std::vector<variant> items;
24 wml::node::const_child_iterator i1 = node_->begin_child(key);
25 wml::node::const_child_iterator i2 = node_->end_child(key);
26 while(i1 != i2) {
27 items.push_back(variant(new node_callable(i1->second)));
28 ++i1;
29 }
30
31 return variant(&items);
32 } else {
33 wml::const_node_ptr child = node_->get_child(key);
34 if(child) {
35 return variant(new node_callable(child));
36 } else {
37 return variant();
38 }
39 }
40 }
41
42 variant node_callable::get_value(const std::string& key) const
43 {
44 const wml::schema* const schema = node_->get_schema();
45
46 if(key == "_type") {
47 if(schema) {
48 return variant(schema->id());
49 } else {
50 return variant();
51 }
52 } else if(key == "elements") {
53 if(elements_.is_null()) {
54 elements_ = variant(new node_elements_callable(node_));
55 }
56
57 return elements_;
58 } else if(key == "node") {
59 return variant(this);
60 }
61
62 if(schema && schema->has_attribute(key) == false) {
63 return node_callable_.query_value(key);
64 }
65
66 const std::string& value = node_->attr(key);
67 if(schema) {
68 return schema->attribute_to_variant(key, value);
69 } else {
70 return variant(value);
71 }
72 }
73
74 void node_callable::set_value(const std::string& key, const variant& value)
75 {
76 assert(mutable_node_);
77 mutable_node_->set_attr(key, value.string_cast());
78 }
79
80 }
0 #ifndef WML_FORMULA_ADAPTER_HPP_INCLUDED
1 #define WML_FORMULA_ADAPTER_HPP_INCLUDED
2
3 #include "formula_callable.hpp"
4 #include "variant.hpp"
5 #include "wml_node_fwd.hpp"
6
7 namespace wml {
8 class node_elements_callable : public game_logic::formula_callable
9 {
10 public:
11 explicit node_elements_callable(const_node_ptr n) : node_(n)
12 {}
13 private:
14 variant get_value(const std::string& key) const;
15 const_node_ptr node_;
16 };
17
18 class node_callable : public game_logic::formula_callable
19 {
20 public:
21 explicit node_callable(node_ptr n) : mutable_node_(n.get()), node_(n), node_callable_(n)
22 {}
23 explicit node_callable(const_node_ptr n) : mutable_node_(const_cast<node*>(n.get())), node_(n), node_callable_(n)
24 {}
25
26 wml::node* raw_node() { return mutable_node_; }
27 private:
28 variant get_value(const std::string& key) const;
29 void set_value(const std::string& key, const variant& value);
30 node* mutable_node_;
31 const_node_ptr node_;
32 mutable variant elements_;
33 node_elements_callable node_callable_;
34 };
35
36 }
37
38 #endif
0 #include <set>
1 #include <stack>
2 #include <string>
3
4 #include <stdio.h>
5
6 #include "asserts.hpp"
7 #include "foreach.hpp"
8 #include "wml_formula_callable.hpp"
9 #include "wml_node.hpp"
10
11 namespace game_logic
12 {
13
14 namespace {
15 struct scope_info {
16 std::set<const_wml_serializable_formula_callable_ptr> objects_to_write, objects_written;
17 };
18
19 std::stack<scope_info, std::vector<scope_info> > scopes;
20
21 }
22
23 void wml_formula_callable_serialization_scope::register_serialized_object(const_wml_serializable_formula_callable_ptr ptr, wml::node_ptr node)
24 {
25 ASSERT_LOG(scopes.empty() == false, "register_serialized_object() called when there is no wml_formula_callable_serialization_scope");
26 scopes.top().objects_written.insert(ptr);
27
28 char addr_buf[256];
29 sprintf(addr_buf, "%p", ptr.get());
30 node->set_attr("_addr", addr_buf);
31 }
32
33 std::string wml_formula_callable_serialization_scope::require_serialized_object(const_wml_serializable_formula_callable_ptr ptr)
34 {
35 char addr_buf[256];
36 sprintf(addr_buf, "%p", ptr.get());
37
38 ASSERT_LOG(scopes.empty() == false, "require_serialized_object() called when there is no wml_formula_callable_serialization_scope");
39 scopes.top().objects_to_write.insert(ptr);
40
41 return addr_buf;
42 }
43
44 bool wml_formula_callable_serialization_scope::is_active()
45 {
46 return scopes.empty() == false;
47 }
48
49 wml_formula_callable_serialization_scope::wml_formula_callable_serialization_scope()
50 {
51 scopes.push(scope_info());
52 }
53
54 wml_formula_callable_serialization_scope::~wml_formula_callable_serialization_scope()
55 {
56 scopes.pop();
57 }
58
59 wml::node_ptr wml_formula_callable_serialization_scope::write_objects() const
60 {
61 wml::node_ptr result(new wml::node("serialized_objects"));
62 foreach(const_wml_serializable_formula_callable_ptr ptr, scopes.top().objects_to_write) {
63 if(scopes.top().objects_written.count(ptr) != 0) {
64 continue;
65 }
66
67 char addr_buf[256];
68 sprintf(addr_buf, "%p", ptr.get());
69
70 wml::node_ptr node(ptr->write_to_wml());
71 node->set_attr("_addr", addr_buf);
72 result->add_child(node);
73 }
74
75 return result;
76 }
77
78 namespace {
79 std::map<intptr_t, wml_serializable_formula_callable_ptr> registered_objects;
80 }
81
82 void wml_formula_callable_read_scope::register_serialized_object(intptr_t addr, wml_serializable_formula_callable_ptr ptr)
83 {
84 fprintf(stderr, "REGISTER SERIALIZED: 0x%x\n", (int)addr);
85 registered_objects[addr] = ptr;
86 }
87
88 wml_serializable_formula_callable_ptr wml_formula_callable_read_scope::get_serialized_object(intptr_t addr)
89 {
90 return registered_objects[addr];
91 }
92
93 wml_formula_callable_read_scope::wml_formula_callable_read_scope()
94 {
95 }
96
97 wml_formula_callable_read_scope::~wml_formula_callable_read_scope()
98 {
99 std::set<variant*> v;
100 swap_variants_loading(v);
101 for(std::set<variant*>::iterator i = v.begin(); i != v.end(); ++i) {
102 variant& var = **i;
103 fprintf(stderr, "LOAD SERIALIZED: 0x%x\n", (int)var.as_callable_loading());
104 var = variant(registered_objects[var.as_callable_loading()].get());
105 }
106
107 registered_objects.clear();
108 }
109
110 }
0 #ifndef WML_FORMULA_CALLABLE_HPP_INCLUDED
1 #define WML_FORMULA_CALLABLE_HPP_INCLUDED
2
3 #include <stdint.h>
4
5 #include <boost/intrusive_ptr.hpp>
6
7 #include "formula_callable.hpp"
8 #include "wml_node_fwd.hpp"
9
10 namespace game_logic
11 {
12
13 class wml_serializable_formula_callable : public formula_callable
14 {
15 public:
16 explicit wml_serializable_formula_callable(bool has_self=true) : formula_callable(has_self) {}
17
18 virtual ~wml_serializable_formula_callable() {}
19
20 wml::node_ptr write_to_wml() const {
21 return serialize_to_wml();
22 }
23 private:
24 virtual wml::node_ptr serialize_to_wml() const = 0;
25
26 };
27
28 typedef boost::intrusive_ptr<wml_serializable_formula_callable> wml_serializable_formula_callable_ptr;
29 typedef boost::intrusive_ptr<const wml_serializable_formula_callable> const_wml_serializable_formula_callable_ptr;
30
31 class wml_formula_callable_serialization_scope
32 {
33 public:
34 static void register_serialized_object(const_wml_serializable_formula_callable_ptr ptr, wml::node_ptr node);
35 static std::string require_serialized_object(const_wml_serializable_formula_callable_ptr ptr);
36 static bool is_active();
37
38 wml_formula_callable_serialization_scope();
39 ~wml_formula_callable_serialization_scope();
40
41 wml::node_ptr write_objects() const;
42
43 private:
44 };
45
46 class wml_formula_callable_read_scope
47 {
48 public:
49 static void register_serialized_object(intptr_t addr, wml_serializable_formula_callable_ptr ptr);
50 static wml_serializable_formula_callable_ptr get_serialized_object(intptr_t addr);
51 wml_formula_callable_read_scope();
52 ~wml_formula_callable_read_scope();
53 private:
54 };
55
56 }
57
58 #endif
0 #include <iostream>
1
2 #include "foreach.hpp"
3 #include "string_utils.hpp"
4 #include "wml_formula_adapter.hpp"
5 #include "wml_modify.hpp"
6 #include "wml_node.hpp"
7 #include "wml_parser.hpp"
8
9 namespace wml {
10 modifier::modifier(wml::const_node_ptr node)
11 {
12 wml::node::const_child_iterator i1 = node->begin_child("children");
13 wml::node::const_child_iterator i2 = node->end_child("children");
14
15 while(i1 != i2) {
16 modification mod;
17 mod.target.reset(new game_logic::formula(i1->second->attr("_target")));
18 mod.add_children = util::split(i1->second->attr("add"));
19 mod.add_if_not_present_children = util::split(i1->second->attr("add_if_not_present"));
20 mod.remove_children = util::split(i1->second->attr("remove"));
21 mods_.push_back(mod);
22 ++i1;
23 }
24
25 i1 = node->begin_child("set");
26 i2 = node->end_child("set");
27 while(i1 != i2) {
28 modification mod;
29 mod.target.reset(new game_logic::formula(i1->second->attr("_target")));
30 for(wml::node::const_attr_iterator i = i1->second->begin_attr();
31 i != i1->second->end_attr(); ++i) {
32 if(i->first != "_target") {
33 mod.attr[i->first].reset(new game_logic::formula(i->second));
34 }
35 }
36
37 mods_.push_back(mod);
38 ++i1;
39 }
40
41 i1 = node->begin_child("set_str");
42 i2 = node->end_child("set_str");
43 while(i1 != i2) {
44 modification mod;
45 mod.target.reset(new game_logic::formula(i1->second->attr("_target")));
46 for(wml::node::const_attr_iterator i = i1->second->begin_attr();
47 i != i1->second->end_attr(); ++i) {
48 if(i->first != "_target") {
49 mod.attr[i->first] = game_logic::formula::create_string_formula(i->second);
50 }
51 }
52
53 mods_.push_back(mod);
54 ++i1;
55 }
56
57 }
58
59 void modifier::modify_target(variant target, const modification& mod) {
60 const std::map<std::string, game_logic::const_formula_ptr>& mods = mod.attr;
61 if(target.is_list()) {
62 for(int n = 0; n != target.num_elements(); ++n) {
63 modify_target(target[n], mod);
64 }
65
66 return;
67 }
68
69 if(target.is_callable()) {
70 wml::node_callable* c = target.try_convert<wml::node_callable>();
71 if(c) {
72 foreach(const std::string& add, mod.add_children) {
73 c->raw_node()->add_child(wml::node_ptr(new wml::node(add)));
74 }
75
76 foreach(const std::string& add, mod.add_if_not_present_children) {
77 if(!c->raw_node()->get_child(add)) {
78 c->raw_node()->add_child(wml::node_ptr(new wml::node(add)));
79 }
80 }
81
82 foreach(const std::string& remove, mod.remove_children) {
83 while(c->raw_node()->get_child(remove)) {
84 c->raw_node()->erase_child(c->raw_node()->get_child(remove));
85 }
86 }
87 }
88
89 for(std::map<std::string, game_logic::const_formula_ptr>::const_iterator i = mods.begin(); i != mods.end(); ++i) {
90 variant value = i->second->execute(*target.as_callable());
91 target.mutable_callable()->mutate_value(i->first, value);
92 }
93 }
94 }
95
96 void modifier::modify(wml::node_ptr doc) const
97 {
98 game_logic::formula_callable_ptr callable(new wml::node_callable(doc));
99 foreach(const modification& mod, mods_) {
100 variant target = mod.target->execute(*callable);
101 modify_target(target, mod);
102 }
103 }
104
105 } //namespace wml
106
107 #ifdef UNIT_TEST_WML_MODIFY
108
109 int main(int argc, char** argv) {
110
111 }
112
113 #endif
0 #include <map>
1 #include <string>
2
3 #include "formula.hpp"
4 #include "wml_node_fwd.hpp"
5
6 namespace wml {
7
8 class modifier
9 {
10 public:
11 explicit modifier(wml::const_node_ptr node);
12 void modify(wml::node_ptr doc) const;
13 private:
14 struct modification {
15 game_logic::const_formula_ptr target;
16 std::vector<std::string> add_children, add_if_not_present_children;
17 std::vector<std::string> remove_children;
18 std::map<std::string, game_logic::const_formula_ptr> attr;
19 };
20
21 static void modify_target(variant target, const modification& mod);
22
23 std::vector<modification> mods_;
24 };
25 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include <algorithm>
13
14 #include "wml_node.hpp"
15
16 namespace wml
17 {
18
19 namespace {
20 const value empty_value("");
21 }
22
23 const value& node::operator[](const std::string& key) const
24 {
25 attr_map::const_iterator itor = attr_.find(key);
26 if(itor == attr_.end()) {
27 return empty_value;
28 }
29
30 return itor->second;
31 }
32
33 const value& node::attr(const std::string& key) const
34 {
35 return (*this)[key];
36 }
37
38 void node::set_attr(const std::string& key, const value& val)
39 {
40 attr_[key] = val;
41 }
42
43 void node::set_or_erase_attr(const std::string& key, const std::string& value)
44 {
45 if(value.empty() == false) {
46 set_attr(key,value);
47 } else {
48 attr_.erase(key);
49 }
50 }
51
52 void node::erase_attr(const std::string& key)
53 {
54 attr_.erase(key);
55 if(!attr_order_.empty()) {
56 attr_order_.erase(std::find(attr_order_.begin(), attr_order_.end(), key));
57 }
58 }
59
60 bool node::has_attr(const std::string& key) const
61 {
62 attr_map::const_iterator itor = attr_.find(key);
63 return itor != attr_.end() && itor->second.empty() == false;
64 }
65
66 node::const_attr_iterator node::begin_attr() const
67 {
68 return attr_.begin();
69 }
70
71 node::const_attr_iterator node::end_attr() const
72 {
73 return attr_.end();
74 }
75
76 node::child_iterator node::begin_child(const std::string& key)
77 {
78 return childmap_.lower_bound(key);
79 }
80
81 node::const_child_iterator node::begin_child(const std::string& key) const
82 {
83 return childmap_.lower_bound(key);
84 }
85
86 node::child_iterator node::end_child(const std::string& key)
87 {
88 return childmap_.upper_bound(key);
89 }
90
91 node::const_child_iterator node::end_child(const std::string& key) const
92 {
93 return childmap_.upper_bound(key);
94 }
95
96 node::child_range node::get_child_range(const std::string& key)
97 {
98 return childmap_.equal_range(key);
99 }
100
101 node::const_child_range
102 node::get_child_range(const std::string& key) const
103 {
104 return childmap_.equal_range(key);
105 }
106
107 node::all_child_iterator node::begin_children()
108 {
109 return children_.begin();
110 }
111
112 node::const_all_child_iterator node::begin_children() const
113 {
114 return children_.begin();
115 }
116
117 node::all_child_iterator node::end_children()
118 {
119 return children_.end();
120 }
121
122 node::const_all_child_iterator node::end_children() const
123 {
124 return children_.end();
125 }
126
127 void node::add_child(boost::shared_ptr<node> child)
128 {
129 childmap_.insert(std::pair<std::string,boost::shared_ptr<node> >(
130 child->name(),child));
131 children_.push_back(child);
132 }
133
134 const_node_ptr node::get_child(const std::string& key) const
135 {
136 const const_child_range range = get_child_range(key);
137 if(range.first == range.second) {
138 return const_node_ptr();
139 } else {
140 return range.first->second;
141 }
142 }
143 node_ptr node::get_child(const std::string& key)
144 {
145 const child_range range = get_child_range(key);
146 if(range.first == range.second) {
147 return node_ptr();
148 } else {
149 return range.first->second;
150 }
151 }
152
153 void node::clear_attr()
154 {
155 attr_.clear();
156 }
157
158 void node::clear_children()
159 {
160 childmap_.clear();
161 children_.clear();
162 }
163
164 namespace {
165 class node_name_equals {
166 std::string name_;
167 public:
168 node_name_equals(const std::string& name) : name_(name)
169 {}
170
171 bool operator()(const wml::const_node_ptr& node) const {
172 return node->name() == name_;
173 }
174 };
175 }
176
177 void node::clear_children(const std::string& name)
178 {
179 childmap_.erase(name);
180 children_.erase(std::remove_if(children_.begin(),children_.end(),node_name_equals(name)), children_.end());
181 }
182
183 void node::erase_child(const boost::shared_ptr<node>& child_node)
184 {
185 child_range range = get_child_range(child_node->name());
186 while(range.first != range.second) {
187 if(range.first->second == child_node) {
188 childmap_.erase(range.first);
189 break;
190 }
191 ++range.first;
192 }
193
194 std::vector<boost::shared_ptr<node> >::iterator i = std::find(children_.begin(),children_.end(),child_node);
195 if(i != children_.end()) {
196 children_.erase(i);
197 }
198 }
199
200 void node::set_comment(const std::string& comment)
201 {
202 comment_ = comment;
203 }
204
205 const std::string& node::get_comment() const
206 {
207 return comment_;
208 }
209
210 void node::set_attr_comment(const std::string& name, const std::string& comment)
211 {
212 attr_comments_[name] = comment;
213 }
214
215 const std::string& node::get_attr_comment(const std::string& name) const
216 {
217 attr_map::const_iterator i = attr_comments_.find(name);
218 if(i != attr_comments_.end()) {
219 return i->second;
220 } else {
221 return empty_value;
222 }
223 }
224
225 void node::add_attr_order(const std::string& attr)
226 {
227 attr_order_.push_back(attr);
228 }
229
230 void node::set_base_element(const std::string& key, wml::const_node_ptr node)
231 {
232 base_elements_[key] = node;
233 }
234
235 wml::const_node_ptr node::get_base_element(const std::string& key) const
236 {
237 std::map<std::string, wml::const_node_ptr>::const_iterator itor = base_elements_.find(key);
238 if(itor != base_elements_.end()) {
239 return itor->second;
240 } else {
241 return wml::const_node_ptr();
242 }
243 }
244
245 void node::strip_prettiness()
246 {
247 comment_.clear();
248 attr_comments_.clear();
249 attr_order_.clear();
250 base_elements_.clear();
251
252 for(std::vector<boost::shared_ptr<node> >::iterator i = children_.begin();
253 i != children_.end(); ++i) {
254 (*i)->strip_prettiness();
255 }
256 }
257
258
259 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef WML_NODE_HPP_INCLUDED
13 #define WML_NODE_HPP_INCLUDED
14
15 #include <boost/shared_ptr.hpp>
16 #include <map>
17 #include <string>
18 #include <vector>
19
20 #include "wml_node_fwd.hpp"
21 #include "wml_value.hpp"
22
23 namespace wml
24 {
25
26 class schema;
27
28 class node
29 {
30 public:
31 explicit node(const std::string& name) : name_(name), schema_(NULL)
32 {}
33
34 void set_prefix(const std::string& p) { prefix_ = p; }
35 const std::string& prefix() const { return prefix_; }
36 const std::string& name() const { return name_; }
37 void set_name(const std::string& new_name) { name_ = new_name; }
38
39 const value& operator[](const std::string& key) const;
40 const value& attr(const std::string& key) const;
41 void set_attr(const std::string& key, const value& val);
42 void set_or_erase_attr(const std::string& key, const std::string& value);
43 void erase_attr(const std::string& key);
44
45 bool has_attr(const std::string& key) const;
46
47 typedef std::map<std::string,value> attr_map;
48 typedef attr_map::const_iterator const_attr_iterator;
49 const_attr_iterator begin_attr() const;
50 const_attr_iterator end_attr() const;
51
52 typedef std::multimap<std::string,boost::shared_ptr<node> >::
53 iterator child_iterator;
54 typedef std::multimap<std::string,boost::shared_ptr<node> >::
55 const_iterator const_child_iterator;
56
57 typedef std::pair<child_iterator,child_iterator> child_range;
58 typedef std::pair<const_child_iterator,const_child_iterator>
59 const_child_range;
60
61 typedef std::vector<boost::shared_ptr<node> >::iterator
62 all_child_iterator;
63 typedef std::vector<boost::shared_ptr<node> >::const_iterator
64 const_all_child_iterator;
65
66 child_iterator begin_child(const std::string& key);
67 const_child_iterator begin_child(const std::string& key) const;
68
69 child_iterator end_child(const std::string& key);
70 const_child_iterator end_child(const std::string& key) const;
71
72 child_range get_child_range(const std::string& key);
73 const_child_range get_child_range(const std::string& key) const;
74
75 all_child_iterator begin_children();
76 const_all_child_iterator begin_children() const;
77
78 all_child_iterator end_children();
79 const_all_child_iterator end_children() const;
80
81 void add_child(boost::shared_ptr<node> child);
82
83 const_node_ptr get_child(const std::string& key) const;
84 node_ptr get_child(const std::string& key);
85
86 void clear_attr();
87 void clear_children();
88 void clear_children(const std::string& name);
89 void erase_child(const boost::shared_ptr<node>& child_node);
90
91 void set_comment(const std::string& comment);
92 const std::string& get_comment() const;
93 void set_attr_comment(const std::string& name, const std::string& comment);
94 const std::string& get_attr_comment(const std::string& name) const;
95
96 const schema* get_schema() const { return schema_; }
97 void set_schema(const schema* s) { schema_ = s; }
98
99 void add_attr_order(const std::string& attr);
100 const std::vector<std::string>& attr_order() const { return attr_order_; }
101
102 void set_base_element(const std::string& key, wml::const_node_ptr node);
103 wml::const_node_ptr get_base_element(const std::string& key) const;
104
105 const std::map<std::string, wml::const_node_ptr>& base_elements() const { return base_elements_; }
106
107 void strip_prettiness();
108
109 private:
110 std::string name_, prefix_;
111 attr_map attr_;
112 std::multimap<std::string,boost::shared_ptr<node> > childmap_;
113 std::vector<boost::shared_ptr<node> > children_;
114
115 const schema* schema_;
116
117 //comment data
118 std::string comment_;
119 attr_map attr_comments_;
120
121 std::vector<std::string> attr_order_;
122
123 std::map<std::string, wml::const_node_ptr> base_elements_;
124 };
125
126 }
127
128 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef WML_NODE_FWD_HPP_INCLUDED
13 #define WML_NODE_FWD_HPP_INCLUDED
14
15 #include <boost/shared_ptr.hpp>
16
17 namespace wml
18 {
19
20 class node;
21 typedef boost::shared_ptr<node> node_ptr;
22 typedef boost::shared_ptr<const node> const_node_ptr;
23
24 }
25
26 #endif
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #include <cassert>
13 #include <cctype>
14 #include <iostream>
15 #include <map>
16 #include <set>
17 #include <stack>
18 #include <string>
19 #include <vector>
20
21 #include <string.h>
22
23 #include <boost/shared_ptr.hpp>
24
25 #include "asserts.hpp"
26 #include "concurrent_cache.hpp"
27 #include "filesystem.hpp"
28 #include "foreach.hpp"
29 #include "formatter.hpp"
30 #include "preprocessor.hpp"
31 #include "string_utils.hpp"
32 #include "unit_test.hpp"
33 #include "wml_parser.hpp"
34 #include "wml_schema.hpp"
35 #include "wml_utils.hpp"
36
37 namespace wml
38 {
39
40 namespace {
41
42 //string to provide iterators for errors that don't have a location.
43 std::string NullErrorLocStr;
44
45 class wml_template
46 {
47 public:
48 explicit wml_template(const node_ptr& node) : node_(node) {
49 }
50
51 void add_argument(const std::string& arg) {
52 args_.push_back(arg);
53 }
54
55 void set_node(const node_ptr& node) {
56 node_ = node;
57 }
58
59 node_ptr call(const std::vector<std::string>& args) const;
60 private:
61 node_ptr node_;
62 std::vector<std::string> args_;
63 };
64
65 typedef boost::shared_ptr<wml_template> wml_template_ptr;
66 typedef concurrent_cache<std::string,wml_template_ptr> wml_template_map;
67 wml_template_map wml_templates;
68
69 bool substitute_single_attr(const std::string& arg_name,
70 const std::string& arg_value,
71 std::string* str)
72 {
73 if(const char* pos = strstr(str->c_str(),arg_name.c_str())) {
74 str->replace(pos-str->c_str(), arg_name.size(), arg_value);
75 return true;
76 }
77
78 return false;
79 }
80
81 bool substitute_attr(const std::string& attr,
82 const std::vector<std::string>& arg_names,
83 const std::vector<std::string>& arg_values,
84 std::string* result)
85 {
86 assert(arg_names.size() == arg_values.size());
87 *result = attr;
88 bool diff = false;
89 for(unsigned int n = 0; n != arg_names.size(); ++n) {
90 while(substitute_single_attr(arg_names[n],arg_values[n],result)) {
91 diff = true;
92 }
93 }
94
95 return diff;
96 }
97
98 node_ptr substitute(node_ptr node,
99 const std::vector<std::string>& arg_names,
100 const std::vector<std::string>& arg_values)
101 {
102 assert(arg_names.size() == arg_values.size());
103 wml::node::const_all_child_iterator i1 = node->begin_children();
104 wml::node::const_all_child_iterator i2 = node->end_children();
105 std::vector<node_ptr> children;
106 bool is_different = false;
107 while(i1 != i2) {
108 node_ptr child = *i1;
109 node_ptr child_copy = substitute(child,arg_names,arg_values);
110 children.push_back(child_copy);
111 if(child != child_copy) {
112 is_different = true;
113 }
114 ++i1;
115 }
116
117 std::map<std::string,std::string> attr;
118 wml::node::const_attr_iterator a1 = node->begin_attr();
119 wml::node::const_attr_iterator a2 = node->end_attr();
120 while(a1 != a2) {
121 std::string new_str;
122 if(substitute_attr(a1->second,arg_names,arg_values,&new_str)) {
123 attr[a1->first] = new_str;
124 }
125 ++a1;
126 }
127
128 if(is_different || attr.empty() == false) {
129 node_ptr res(new wml::node(node->name()));
130 a1 = node->begin_attr();
131 while(a1 != a2) {
132 std::map<std::string,std::string>::const_iterator i = attr.find(a1->first);
133 if(i != attr.end()) {
134 res->set_attr(i->first, i->second);
135 } else {
136 res->set_attr(a1->first, a1->second);
137 }
138 ++a1;
139 }
140
141 foreach(const wml::node_ptr& child, children) {
142 res->add_child(child);
143 }
144
145 return res;
146 } else {
147 return node;
148 }
149 }
150
151 node_ptr wml_template::call(const std::vector<std::string>& args) const
152 {
153 if(args.size() != args_.size()) {
154 throw parse_error(formatter() << "incorrect number of arguments when calling '" << node_->name() << "'\n");
155 }
156
157 return substitute(node_, args_, args);
158 }
159
160 std::string parse_element(std::string::const_iterator& i1,
161 std::string::const_iterator i2)
162 {
163 const std::string::const_iterator start_pos = i1;
164
165 ++i1;
166 const std::string::const_iterator beg = i1;
167 while(*i1 != ']' && i1 != i2) {
168 ++i1;
169 }
170
171 if(i1 == i2) {
172 throw parse_error("unexpected end of wml document while parsing element", start_pos);
173 }
174
175 std::string res(beg,i1);
176 util::strip(res);
177 ++i1;
178
179 if(res.empty()) {
180 throw parse_error("empty element", start_pos);
181 }
182
183 return res;
184 }
185
186 std::string parse_name(std::string::const_iterator& i1,
187 std::string::const_iterator i2)
188 {
189 const std::string::const_iterator beg = i1;
190 i1 = std::find(i1,i2,'=');
191 if(i1 == i2) {
192 throw parse_error("unexpected end of wml document while parsing name", beg);
193 }
194
195 std::string res(beg,i1);
196 util::strip(res);
197
198 #if !TARGET_OS_IPHONE
199 for(std::string::const_iterator i = res.begin(); i != res.end(); ++i) {
200 if(!isalnum(*i) && *i != '_') {
201 throw parse_error("illegal character in wml attribute name", beg + (i - res.begin()));
202 }
203 }
204 #endif
205
206 return res;
207 }
208
209 std::string parse_value(std::string::const_iterator& i1,
210 std::string::const_iterator i2,
211 int& line_number, int& line_starts_at)
212 {
213 line_starts_at = line_number;
214 std::string res;
215 const std::string::const_iterator beg = i1;
216 while(i1 != i2 && !util::isnewline(*i1) && *i1 != '#') {
217 if(*i1 == '"') {
218 ++i1;
219 while(i1 != i2 && *i1 != '"') {
220 if(*i1 == '\\' && i1+1 != i2) {
221 ++i1;
222 }
223 if(util::isnewline(*i1)) {
224 ++line_number;
225 }
226 res.push_back(*i1);
227 ++i1;
228 }
229
230 if(i1 == i2) {
231 break;
232 }
233
234 ++i1;
235 continue;
236 }
237
238 res.push_back(*i1);
239
240 ++i1;
241 }
242
243 if(i1 == i2) {
244 throw parse_error(formatter() << "unexpected end of wml document while parsing value '" << std::string(beg,i1) << "'", beg);
245 }
246
247 //strip the string, but leave it untouched if it's all whitespace.
248 {
249 int newlines = 0;
250 std::string::iterator i = res.begin();
251 while(i != res.end() && isspace(*i)) {
252 if(*i == '\n') {
253 ++newlines;
254 }
255 ++i;
256 }
257
258 if(i != res.end()) {
259 res.erase(res.begin(), i);
260 while(isspace(res[res.size()-1])) {
261 res.resize(res.size()-1);
262 }
263
264 line_starts_at += newlines;
265 }
266 }
267
268 return res;
269 }
270
271 void skip_comment(std::string::const_iterator& i1,
272 std::string::const_iterator i2)
273 {
274 while(i1 != i2 && !util::isnewline(*i1)) {
275 ++i1;
276 }
277 }
278
279 int get_line_number(const std::string& doc, std::string::const_iterator i)
280 {
281 return std::count(doc.begin(), i, '\n') + 1;
282 }
283
284 namespace {
285 concurrent_cache<std::string, std::string*> filename_pool;
286
287 struct node_frame {
288 node_frame() : derived_frame(false) {}
289
290 node_ptr node;
291
292 //all 'bases' defined for nodes that we parse in this element.
293 std::map<std::string, node_ptr> base_nodes;
294
295 //if we are derived, then we don't do checks for multiply defined
296 //attributes.
297 bool derived_frame;
298 };
299
300 }
301
302 node_ptr parse_wml_internal(const std::string& error_context, const std::string& doc, bool must_have_doc, const schema* current_schema)
303 {
304 #define PARSE_ERROR(msg, loc) throw parse_error(formatter() << error_context << " line " << line_number << ": " << msg, loc);
305 node_ptr res;
306 std::stack<node_frame> nodes;
307 std::stack<const schema*> schemas;
308 schemas.push(NULL);
309 std::string::const_iterator i = doc.begin();
310 std::string current_comment;
311 int line_number = 1;
312
313 const std::string* filename_ptr = filename_pool.get(error_context);
314 if(!filename_ptr) {
315 std::string* str = new std::string(error_context);
316 filename_ptr = str;
317 filename_pool.put(error_context, str);
318 }
319
320 try {
321 while(i != doc.end()) {
322 if(util::isnewline(*i)) {
323 ++i;
324 ++line_number;
325 } else if(isspace(*i)) {
326 ++i;
327 } else if(*i == '[') {
328 const std::string::const_iterator begin_element = i;
329 std::string element = parse_element(i,doc.end());
330 if(element[0] == '/') {
331 const std::string close_element(element.begin()+1,element.end());
332 if(nodes.empty()) {
333 PARSE_ERROR("close element found when there are no elements", begin_element);
334 } else if(nodes.top().node->name() != close_element) {
335 PARSE_ERROR("mismatch between open and close elements: '" << nodes.top().node->name() << "' vs '" << close_element << "'", begin_element);
336 }
337
338 if(schemas.top()) {
339 schemas.top()->validate_node(nodes.top().node);
340 }
341
342 schemas.pop();
343 nodes.pop();
344 } else {
345 if(nodes.empty() && res) {
346 PARSE_ERROR("multiple root elements found", begin_element);
347 }
348
349 if(nodes.empty()) {
350 schemas.push(current_schema);
351 } else {
352 const schema* new_schema = NULL;
353 if(schemas.top()) {
354 new_schema = schemas.top()->validate_element(element);
355 }
356
357 schemas.push(new_schema);
358 }
359
360 std::string prefix;
361 std::string::iterator colon =
362 std::find(element.begin(),element.end(),':');
363 if(colon != element.end()) {
364 prefix.assign(element.begin(),colon);
365 element.erase(element.begin(),colon+1);
366 }
367
368 std::vector<wml::node_ptr> parents;
369
370 std::string::iterator begin_args =
371 std::find(element.begin(),element.end(),'(');
372 if(prefix != "template" && begin_args != element.end()) {
373 std::string::iterator end_args =
374 std::find(begin_args+1,element.end(),')');
375 std::string args_str(begin_args+1,end_args);
376 std::vector<std::string> args =
377 util::split(args_str);
378
379 element.erase(begin_args,element.end());
380
381 const wml_template_ptr t = wml_templates.get(element);
382 if(t.get() == NULL) {
383 PARSE_ERROR("unrecognized template call: '" << element << "'\n", i);
384 }
385
386 const wml::node_ptr el = t->call(args);
387 assert(el);
388 if(!prefix.empty()) {
389 parents.push_back(el);
390 element = prefix;
391 prefix.clear();
392 } else if(nodes.empty()) {
393 res = el;
394 continue;
395 } else {
396 nodes.top().node->add_child(el);
397 continue;
398 }
399 }
400
401 bool derived_node = false;
402 node_ptr el(new node(element));
403 if(!nodes.empty()) {
404 //see if we have a base node to use recorded to base this
405 //node off, rather than starting fresh.
406 std::map<std::string, node_ptr>::const_iterator itor = nodes.top().base_nodes.find(element);
407 if(itor != nodes.top().base_nodes.end()) {
408 el = deep_copy(itor->second);
409 derived_node = true;
410 }
411 }
412
413 el->set_schema(schemas.top());
414 foreach(const wml::node_ptr& parent, parents) {
415 wml::merge_over(parent, el);
416 }
417 if(current_comment.empty() == false) {
418 el->set_comment(current_comment);
419 current_comment.clear();
420 }
421
422 if(prefix == "template") {
423 std::string::iterator template_name =
424 std::find(element.begin(),element.end(),' ');
425 if(template_name == element.end()) {
426 PARSE_ERROR("no template name found for template '" << element << "'", i);
427 }
428
429 el.reset(new node(std::string(element.begin(),template_name)));
430
431 while(template_name != element.end() && isspace(*template_name)) {
432 ++template_name;
433 }
434
435 std::string::iterator beg_args =
436 std::find(template_name,element.end(),'(');
437 std::string::iterator end_args =
438 std::find(template_name,element.end(),')');
439 if(beg_args == element.end() || end_args == element.end()) {
440 PARSE_ERROR("no arg list found for template '" << element << "'", i);
441 }
442
443 wml_template_ptr t(new wml_template(el));
444
445 std::string args_str(beg_args+1,end_args);
446 std::vector<std::string> args;
447 if(!args_str.empty()) {
448 args = util::split(args_str);
449 foreach(const std::string& arg, args) {
450 t->add_argument("{" + arg + "}");
451 }
452 }
453
454 std::string template_name_str(template_name,beg_args);
455 util::strip(template_name_str);
456 wml_templates.put(template_name_str, t);
457 } else if(prefix == "base") {
458 el->set_prefix("base");
459 nodes.top().base_nodes[element] = el;
460 nodes.top().node->set_base_element(element, el);
461 std::cerr << "SET BASE: " << nodes.top().node->name() << " -> " << element << "\n";
462 } else if(prefix != "") {
463 PARSE_ERROR("unrecognized element prefix", i);
464 } else if(nodes.empty()) {
465 res = el;
466 } else {
467 nodes.top().node->add_child(el);
468 }
469
470 node_frame frame;
471 frame.node = el;
472 frame.derived_frame = derived_node;
473 nodes.push(frame);
474 }
475 } else if(isalnum(*i) || *i == '_') {
476 if(nodes.empty()) {
477 PARSE_ERROR("attributes found at root", i);
478 }
479
480 std::string::const_iterator name_start = i;
481
482 const std::string name = parse_name(i,doc.end());
483 int attr_line = line_number;
484 ++i;
485 const std::string value = parse_value(i,doc.end(), line_number, attr_line);
486
487 if(schemas.top()) {
488 schemas.top()->validate_attribute(name, value);
489 }
490
491 if(!nodes.top().derived_frame && nodes.top().node->has_attr(name)) {
492 PARSE_ERROR("attribute appears multiple times", name_start);
493 }
494 nodes.top().node->set_attr(name, wml::value(value, filename_ptr, attr_line));
495 nodes.top().node->add_attr_order(name);
496
497 if(current_comment.empty() == false) {
498 nodes.top().node->set_attr_comment(name, current_comment);
499 current_comment.clear();
500 }
501 } else if(*i == '@') {
502 ++i;
503 std::string::const_iterator begin = i;
504 while(i != doc.end() && !isspace(*i) && !util::isnewline(*i)) {
505 ++i;
506 }
507
508 std::string name(begin,i);
509 if(name == "import" || name == "include") {
510 if(i == doc.end()) {
511 PARSE_ERROR("unexpected document end while importing", i);
512 }
513
514 ++i;
515 begin = i;
516 while(i != doc.end() && !isspace(*i) && !util::isnewline(*i)) {
517 ++i;
518 }
519
520 const std::string filename(begin,i);
521 if(nodes.empty() && name == "import") {
522 PARSE_ERROR("@import statement at top level", i);
523 }
524 wml::node_ptr node(parse_wml_from_file("data/" + filename, NULL, name == "import"));
525 if(node) {
526 nodes.top().node->add_child(node);
527 }
528
529 } else {
530 PARSE_ERROR("unrecognized @ instruction: '" << name << "'", i);
531 }
532 } else if(*i == '#') {
533 std::string::const_iterator begin_comment = i;
534 skip_comment(i,doc.end());
535 current_comment.insert(current_comment.end(), begin_comment, i);
536 } else {
537 std::cerr << "unexpected chars: {{{" << &*i << "}}}\n";
538 PARSE_ERROR("unexpected characters in wml document", i);
539 }
540 } // while(i != doc.end())
541 } catch(const schema_error& e) {
542 PARSE_ERROR(e.message, NullErrorLocStr.end());
543 }
544
545 if(must_have_doc && !res) {
546 PARSE_ERROR("empty wml document for filename '" << doc <<"'", NullErrorLocStr.end());
547 }
548
549 if(nodes.empty() == false) {
550 PARSE_ERROR("unexpected end of wml document", NullErrorLocStr.end());
551 }
552
553 return res;
554 #undef PARSE_ERROR
555 }
556
557 } // namespace
558
559 node_ptr parse_wml(const std::string& doc, bool must_have_doc, const schema* schema)
560 {
561 return parse_wml_internal("", doc, must_have_doc, schema);
562 }
563
564 node_ptr parse_wml_from_file(const std::string& fname, const schema* schema, bool must_have_doc)
565 {
566 const std::string data = preprocess(sys::read_file(fname));
567 if(data.empty()) {
568 ASSERT_LOG(false, "Could not read file: " << fname);
569 }
570 try{
571 return parse_wml_internal(fname, data, must_have_doc, schema);
572 } catch(wml::parse_error& e) {
573 if(e.error_loc >= data.begin() && e.error_loc < data.end()) {
574 std::string::const_iterator i1 = e.error_loc;
575 while(i1 != data.begin() && *i1 != '\n') {
576 --i1;
577 }
578
579 while(i1 != e.error_loc && isspace(*i1)) {
580 ++i1;
581 }
582
583 std::string::const_iterator i2 = std::find(e.error_loc, data.end(), '\n');
584 int spaces = e.error_loc - i1;
585 const int line_num = std::count(data.begin(), i1, '\n') + 1;
586 std::cerr << "ERROR PARSING WML IN " << fname << " on line " << line_num << ":\n";
587 std::cerr << std::string(i1, i2) << "\n";
588 while(spaces--) {
589 std::cerr << " ";
590 }
591
592 std::cerr << "^\n";
593 ASSERT_LOG(false, e.message);
594 } else {
595
596 ASSERT_LOG(false, "Error parsing WML in " << fname << ": " << e.message);
597 }
598 } catch(...) {
599 ASSERT_LOG(false, "Unknown error loading WML in " << fname);
600 }
601 }
602
603 parse_error::parse_error(const std::string& msg)
604 : message(msg), error_loc(NullErrorLocStr.end())
605 {}
606
607 parse_error::parse_error(const std::string& msg, std::string::const_iterator i)
608 : message(msg), error_loc(i)
609 {}
610
611 }
612
613 UNIT_TEST(wml_parser_test) {
614 const std::string doc =
615 "[doc]\n"
616 "attr=blah\n"
617 " [base:test]\n"
618 " x=y\n"
619 " [/test]\n"
620 " [test]\n"
621 " a=b\n"
622 " [/test]\n"
623 "[/doc]\n";
624
625 wml::const_node_ptr node(wml::parse_wml(doc));
626 CHECK_EQ(node->name(), "doc");
627 CHECK_EQ(node->attr("attr").str(), "blah");
628
629 wml::const_node_ptr test_node = node->get_child("test");
630 CHECK_NE(test_node, wml::const_node_ptr());
631
632 CHECK_EQ(test_node->attr("x").str(), "y");
633 CHECK_EQ(test_node->attr("a").str(), "b");
634 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef WML_PARSER_HPP_INCLUDED
13 #define WML_PARSER_HPP_INCLUDED
14
15 #include <string>
16
17 #include "wml_node.hpp"
18
19 namespace wml
20 {
21
22 class schema;
23
24 struct parse_error {
25 parse_error(const std::string& msg);
26 parse_error(const std::string& msg, std::string::const_iterator error_loc);
27 std::string message;
28
29 std::string::const_iterator error_loc;
30 };
31
32 node_ptr parse_wml(const std::string& doc, bool must_have_doc=true, const schema* schema=NULL);
33
34 node_ptr parse_wml_from_file(const std::string& fname, const schema* schema=NULL, bool must_have_doc=true);
35
36 }
37
38 #endif
0 #include "unit_test.hpp"
1 #include "wml_parser.hpp"
2
3 UNIT_TEST(wml_parser)
4 {
5 using namespace wml;
6
7 const std::string test =
8 "[template:unit goblin(x,y)]\n"
9 "x={x}\n"
10 "y={y}\n"
11 "[/unit]\n"
12 "[goblin(10,4)]\n";
13 wml::node_ptr node = wml::parse_wml(test);
14 CHECK_EQ((*node)["x"].str(), "10");
15 CHECK_EQ((*node)["y"].str(), "4");
16 }
0 #include "filesystem.hpp"
1
2 namespace wml {
3
4 void preprocess(const std::string& filename, std::string* output)
5 {
6 }
7
8 }
0 #ifndef WML_PREPROCESSOR_HPP_INCLUDED
1 #define WML_PREPROCESSOR_HPP_INCLUDED
2
3 #include <map>
4 #include <string>
5
6 namespace wml {
7 typedef std::map<std::string, std::string> symbols_map;
8 struct preprocessor_error {};
9 void preprocess(const std::string& filename, std::string* output,
10 symbols_map* symbols=NULL);
11 }
12
13 #endif
0 #include <assert.h>
1 #include <iostream>
2 #include <map>
3
4 #include "foreach.hpp"
5 #include "formatter.hpp"
6 #include "string_utils.hpp"
7 #include "wml_node.hpp"
8 #include "wml_schema.hpp"
9
10 namespace wml {
11
12 namespace {
13 std::map<std::string, schema> schemas;
14 std::map<std::string, std::string> data_types;
15 }
16
17 void schema::init(wml::const_node_ptr node)
18 {
19 for(wml::node::const_attr_iterator i = node->begin_attr(); i != node->end_attr(); ++i) {
20 data_types[i->first] = i->second;
21 }
22
23 wml::node::const_all_child_iterator i1 = node->begin_children();
24 wml::node::const_all_child_iterator i2 = node->end_children();
25 while(i1 != i2) {
26 schemas.insert(std::pair<std::string, schema>((*i1)->name(), schema(*i1)));
27 ++i1;
28 }
29 }
30
31 const schema* schema::get(const std::string& id)
32 {
33 std::map<std::string, schema>::const_iterator itor = schemas.find(id);
34 if(itor != schemas.end()) {
35 return &itor->second;
36 }
37
38 return NULL;
39 }
40
41 namespace {
42
43 void parse_type_info(const std::string& type, schema::attribute_info& info, int depth=0)
44 {
45 if(depth == 10) {
46 std::cerr << "SCHEMA ERROR: self-referent types recurse too deeply in schema!\n";
47 return;
48 }
49
50 static const boost::regex re_pattern("re +(.*)");
51 static const boost::regex list_sized_pattern("list *\\[([0-9]+)\\] *(.*)");
52 static const boost::regex list_pattern("list +(.*)");
53 boost::smatch match;
54 info.list_size = -1;
55 if(type == "integer") {
56 info.type = schema::ATTR_INT;
57 } else if(type == "boolean") {
58 info.type = schema::ATTR_BOOL;
59 } else if(type == "string") {
60 info.type = schema::ATTR_STRING;
61 } else if(type == "formula") {
62 info.type = schema::ATTR_FORMULA;
63 } else if(boost::regex_match(type, match, re_pattern)) {
64 info.type = schema::ATTR_REGEX;
65 info.re.reset(new boost::regex(std::string(match[1].first, match[1].second)));
66 } else if(boost::regex_match(type, match, list_sized_pattern)) {
67 info.type = schema::ATTR_LIST;
68 info.list_size = atoi(std::string(match[1].first, match[1].second).c_str());
69 info.elements.reset(new schema::attribute_info);
70 parse_type_info(std::string(match[2].first, match[2].second), *info.elements);
71 } else if(boost::regex_match(type, match, list_pattern)) {
72 info.type = schema::ATTR_LIST;
73 info.elements.reset(new schema::attribute_info);
74 parse_type_info(std::string(match[1].first, match[1].second), *info.elements);
75 } else {
76 std::map<std::string, std::string>::const_iterator itor = data_types.find(type);
77 if(itor != data_types.end()) {
78 parse_type_info(itor->second, info, depth+1);
79 } else {
80 std::cerr << "ILLEGAL SCHEMA TYPE: '" << type << "'\n";
81 }
82 }
83 }
84
85 schema::attribute_info parse_attribute_info(const std::string& str)
86 {
87 schema::attribute_info info;
88 static const boost::regex pattern("(required|optional) +(.*)");
89 boost::smatch match;
90 if(boost::regex_match(str, match, pattern)) {
91 info.optional = (std::string(match[1].first, match[1].second) == "optional");
92 std::string type(match[2].first, match[2].second);
93 parse_type_info(type, info);
94 } else {
95 std::cerr << "ILLEGAL SCHEMA ATTR: '" << str << "'\n";
96 assert(false);
97 }
98
99 return info;
100 }
101 }
102
103 schema::schema(wml::const_node_ptr node)
104 : id_(node->name()), default_prefix_(node->attr("default_prefix"))
105 {
106 for(wml::node::const_attr_iterator i = node->begin_attr();
107 i != node->end_attr(); ++i) {
108 assert(i->first.empty() == false);
109
110 if(i->first[0] == '_') {
111 std::string element(i->first.begin() + 1, i->first.end());
112 static const boost::regex pattern("(optional|required|repeated) ([a-z_]+)");
113 boost::smatch match;
114 if(!boost::regex_match(i->second.str(), match, pattern)) {
115 std::cerr << "ILLEGAL SCHEMA ELEMENT: '" << i->second << "'\n";
116 assert(false);
117 }
118
119 element_info info;
120 info.type = std::string(match[2].first, match[2].second);
121
122 std::string cardinality(match[1].first, match[1].second);
123 if(cardinality == "optional") {
124 info.cardinality = ELEMENT_OPTIONAL;
125 } else if(cardinality == "required") {
126 info.cardinality = ELEMENT_REQUIRED;
127 } else {
128 info.cardinality = ELEMENT_REPEATED;
129 }
130
131 elements_[element] = info;
132 } else {
133 if(i->first != "default_prefix") {
134 attributes_[i->first] = parse_attribute_info(i->second);
135 }
136 }
137 }
138 }
139
140 void schema::attribute_info::validate(const std::string& name, const std::string& value) const
141 {
142 switch(type) {
143 case ATTR_INT: {
144 static const boost::regex pattern("-?[0-9]+");
145 boost::smatch match;
146 if(!boost::regex_match(value, match, pattern)) {
147 generate_error(formatter() << "Value for attribute " << name << " is " << value << " which is not an integer");
148 }
149 break;
150 }
151
152 case ATTR_BOOL: {
153 static const boost::regex pattern("(true|false|yes|no)");
154 boost::smatch match;
155 if(!boost::regex_match(value, match, pattern)) {
156 generate_error(formatter() << "Value for attribute " << name << " is " << value << " which is not a boolean.");
157 }
158 break;
159 }
160
161 case ATTR_STRING: {
162 break;
163 }
164
165 case ATTR_FORMULA: {
166 // TODO: validate it's a valid formula.
167 break;
168 }
169
170 case ATTR_REGEX: {
171 if(re) {
172 boost::smatch match;
173 if(!boost::regex_match(value, match, *re)) {
174 generate_error(formatter() << "Value for attribute " << name << " is " << value << " which is not in the correct format");
175 }
176 }
177
178 break;
179 }
180
181 case ATTR_LIST: {
182 if(elements) {
183 std::vector<std::string> items = util::split(value);
184 if(list_size != -1 && list_size != items.size()) {
185 generate_error(formatter() << "Expected " << list_size << " items in list, but found " << items.size());
186 }
187 foreach(const std::string& item, items) {
188 elements->validate(name, item);
189 }
190 }
191 break;
192 }
193
194 default:
195 assert(false);
196 }
197 }
198
199 bool schema::has_attribute(const std::string& name) const
200 {
201 return attributes_.count(name) != 0;
202 }
203
204 void schema::validate_attribute(const std::string& name, const std::string& value) const
205 {
206 attribute_map::const_iterator itor = attributes_.find(name);
207 if(itor == attributes_.end() && name.size() <= default_prefix_.size() &&
208 std::equal(default_prefix_.begin(), default_prefix_.end(), name.begin())) {
209 static const std::string DefaultStr = "default";
210 itor = attributes_.find(DefaultStr);
211 }
212
213 if(itor == attributes_.end()) {
214 generate_error(formatter() << "Unknown attribute: " << name);
215 }
216
217 itor->second.validate(name, value);
218 }
219
220 const schema* schema::validate_element(const std::string& name) const
221 {
222 element_map::const_iterator itor = elements_.find(name);
223 if(itor == elements_.end()) {
224 generate_error(formatter() << "Unrecognized element: " << name);
225 }
226
227 return get(itor->second.type);
228 }
229
230 void schema::validate_node(wml::const_node_ptr node) const
231 {
232 for(attribute_map::const_iterator i = attributes_.begin(); i != attributes_.end(); ++i) {
233 if(i->second.optional == false && node->has_attr(i->first) == false) {
234 generate_error(formatter() << "Required attribute " << i->first << " not found");
235 }
236 }
237
238 for(wml::node::const_attr_iterator i = node->begin_attr(); i != node->end_attr(); ++i) {
239 validate_attribute(i->first, i->second);
240 }
241
242 for(element_map::const_iterator i = elements_.begin(); i != elements_.end(); ++i) {
243 switch(i->second.cardinality) {
244 case ELEMENT_OPTIONAL: {
245 wml::node::const_child_iterator i1 = node->begin_child(i->first);
246 wml::node::const_child_iterator i2 = node->end_child(i->first);
247 if(i1 != i2) {
248 ++i1;
249 }
250
251 if(i1 != i2) {
252 generate_error(formatter() << "Element " << i->first << " appears too many times; it should appear at most once.");
253 }
254 break;
255 }
256 case ELEMENT_REQUIRED:
257 if(!node->get_child(i->first)) {
258 generate_error(formatter() << "Required element " << i->first << " not found");
259 }
260 break;
261 case ELEMENT_REPEATED:
262 break;
263 default:
264 assert(false);
265 }
266 }
267 }
268
269 namespace {
270 variant convert_attribute_to_variant(const schema::attribute_info& info, const std::string& value) {
271 switch(info.type) {
272 case schema::ATTR_INT:
273 return variant(atoi(value.c_str()));
274 case schema::ATTR_BOOL:
275 return variant((value == "yes" || value == "true") ? 1 : 0);
276 case schema::ATTR_LIST: {
277 std::vector<variant> v;
278 std::vector<std::string> items = util::split(value);
279 foreach(const std::string& item, items) {
280 v.push_back(convert_attribute_to_variant(*info.elements, item));
281 }
282
283 return variant(&v);
284 }
285 case schema::ATTR_STRING:
286 case schema::ATTR_FORMULA:
287 case schema::ATTR_REGEX:
288 return variant(value);
289 default:
290 assert(false);
291 }
292 }
293 }
294
295 variant schema::attribute_to_variant(const std::string& name, const std::string& value) const
296 {
297 attribute_map::const_iterator itor = attributes_.find(name);
298 if(itor == attributes_.end()) {
299 return variant(value);
300 }
301
302 return convert_attribute_to_variant(itor->second, value);
303 }
304
305 bool schema::is_element_repeated(const std::string& name) const
306 {
307 element_map::const_iterator itor = elements_.find(name);
308 if(itor != elements_.end()) {
309 return itor->second.cardinality == ELEMENT_REPEATED;
310 } else {
311 return true;
312 }
313 }
314
315 void schema::generate_error(const std::string& msg)
316 {
317 throw schema_error(msg);
318 }
319
320 }
321
322 #ifdef UNIT_TEST_WML_SCHEMA
323
324 int main(int argc, char** argv)
325 {
326 }
327
328 #endif
0 #ifndef WML_SCHEMA_HPP_INCLUDED
1 #define WML_SCHEMA_HPP_INCLUDED
2
3 #include <boost/regex.hpp>
4 #include <boost/shared_ptr.hpp>
5
6 #include <map>
7 #include <string>
8
9 #include "variant.hpp"
10 #include "wml_node_fwd.hpp"
11
12 namespace wml {
13
14 struct schema_error {
15 explicit schema_error(const std::string& msg) : message(msg)
16 {}
17 std::string message;
18 };
19
20 class schema {
21 public:
22 static void init(wml::const_node_ptr node);
23 static const schema* get(const std::string& id);
24 explicit schema(wml::const_node_ptr node);
25
26 const std::string& id() const { return id_; }
27
28 bool has_attribute(const std::string& name) const;
29 void validate_attribute(const std::string& name, const std::string& value) const;
30 const schema* validate_element(const std::string& name) const;
31 void validate_node(wml::const_node_ptr node) const;
32
33 variant attribute_to_variant(const std::string& name, const std::string& value) const;
34 bool is_element_repeated(const std::string& name) const;
35
36 enum ATTRIBUTE_TYPE { ATTR_INT, ATTR_BOOL, ATTR_STRING, ATTR_LIST, ATTR_FORMULA, ATTR_REGEX };
37 struct attribute_info {
38 bool optional;
39 ATTRIBUTE_TYPE type;
40 boost::shared_ptr<boost::regex> re;
41 boost::shared_ptr<attribute_info> elements;
42 int list_size;
43 void validate(const std::string& name, const std::string& value) const;
44 };
45
46 enum ELEMENT_CARDINALITY { ELEMENT_OPTIONAL, ELEMENT_REQUIRED, ELEMENT_REPEATED };
47 struct element_info {
48 std::string type;
49 ELEMENT_CARDINALITY cardinality;
50 };
51
52 private:
53 static void generate_error(const std::string& msg);
54 std::string id_;
55 std::string default_prefix_;
56
57 typedef std::map<std::string, attribute_info> attribute_map;
58 attribute_map attributes_;
59
60 typedef std::map<std::string, element_info> element_map;
61 element_map elements_;
62 };
63
64 }
65
66 #endif
0 #include "string_utils.hpp"
1 #include "unit_test.hpp"
2 #include "wml_utils.hpp"
3
4 namespace wml {
5
6 node_ptr deep_copy(const_node_ptr ptr, const std::string& name)
7 {
8 node_ptr res(new node(name));
9 res->set_schema(ptr->get_schema());
10 res->set_comment(ptr->get_comment());
11 for(std::map<std::string, wml::const_node_ptr>::const_iterator i = ptr->base_elements().begin(); i != ptr->base_elements().end(); ++i) {
12 std::cerr << "COPY: " << ptr->name() << " -> " << i->first << "\n";
13 res->set_base_element(i->first, i->second);
14 }
15
16 for(std::vector<std::string>::const_iterator i = ptr->attr_order().begin();
17 i != ptr->attr_order().end(); ++i) {
18 res->add_attr_order(*i);
19 }
20
21 for(node::const_attr_iterator i = ptr->begin_attr();
22 i != ptr->end_attr(); ++i) {
23 res->set_attr(i->first,i->second);
24 const std::string& comment = ptr->get_attr_comment(i->first);
25 if(comment.empty() == false) {
26 res->set_attr_comment(i->first,comment);
27 }
28 }
29
30 for(node::const_all_child_iterator i = ptr->begin_children();
31 i != ptr->end_children(); ++i) {
32 res->add_child(deep_copy(*i));
33 }
34
35 return res;
36 }
37
38 node_ptr deep_copy(const_node_ptr ptr)
39 {
40 return deep_copy(ptr, ptr->name());
41 }
42
43 void merge_attr_over(const_node_ptr src, node_ptr dst)
44 {
45 for(node::const_attr_iterator i = src->begin_attr();
46 i != src->end_attr(); ++i) {
47 dst->set_attr(i->first,i->second);
48 const std::string& comment = src->get_attr_comment(i->first);
49 if(comment.empty() == false) {
50 dst->set_attr_comment(i->first,comment);
51 }
52 }
53 }
54
55 void merge_over(const_node_ptr src, node_ptr dst)
56 {
57 if(src->get_comment().empty() == false) {
58 dst->set_comment(src->get_comment());
59 }
60
61 merge_attr_over(src, dst);
62
63 for(std::map<std::string, wml::const_node_ptr>::const_iterator i = src->base_elements().begin(); i != src->base_elements().end(); ++i) {
64 dst->set_base_element(i->first, i->second);
65 }
66
67 for(node::const_all_child_iterator i = src->begin_children();
68 i != src->end_children(); ++i) {
69 dst->add_child(deep_copy(*i));
70 }
71 }
72
73 void copy_over(const_node_ptr src, node_ptr dst)
74 {
75 dst->clear_attr();
76 dst->clear_children();
77 merge_over(src, dst);
78 }
79
80 node_ptr find_child_by_attribute(node_ptr node, const std::string& element_name, const std::string& attr, const std::string& value)
81 {
82 node::child_iterator i1 = node->begin_child(element_name);
83 node::child_iterator i2 = node->end_child(element_name);
84 while(i1 != i2) {
85 if(i1->second->attr(attr).str() == value) {
86 return i1->second;
87 }
88
89 ++i1;
90 }
91
92 return node_ptr();
93 }
94
95 std::vector<const_node_ptr> child_nodes(const const_node_ptr& ptr,
96 const std::string& element)
97 {
98 std::vector<const_node_ptr> res;
99 if(!ptr) {
100 return res;
101 }
102
103 wml::node::const_child_range range = ptr->get_child_range(element);
104 while(range.first != range.second) {
105 res.push_back(range.first->second);
106 ++range.first;
107 }
108
109 return res;
110 }
111
112 std::vector<node_ptr> child_nodes(const node_ptr& ptr,
113 const std::string& element)
114 {
115 std::vector<node_ptr> res;
116 if(!ptr) {
117 return res;
118 }
119
120 wml::node::child_range range = ptr->get_child_range(element);
121 while(range.first != range.second) {
122 res.push_back(range.first->second);
123 ++range.first;
124 }
125
126 return res;
127 }
128
129 child_sequence_iterator::child_sequence_iterator(wml::const_node_ptr node, const std::string& child_name)
130 : i1_(node->begin_child(child_name)), i2_(node->end_child(child_name))
131 {}
132
133 void child_sequence_iterator::next()
134 {
135 ++i1_;
136 }
137
138 bool child_sequence_iterator::at_end() const
139 {
140 return i1_ == i2_;
141 }
142
143 std::vector<int> get_vector_int(const_node_ptr ptr, const std::string& key)
144 {
145 std::vector<std::string> v = util::split(ptr->attr(key));
146 std::vector<int> result(v.size());
147 for(int n = 0; n != v.size(); ++n) {
148 result[n] = atoi(v[n].c_str());
149 }
150
151 return result;
152 }
153
154 }
155
156 BENCHMARK(wml_get_int)
157 {
158 wml::node_ptr node(new wml::node("a"));
159 node->set_attr("abc", "47");
160 BENCHMARK_LOOP {
161 wml::get_int(node, "abc");
162 }
163 }
0
1 /*
2 Copyright (C) 2007 by David White <dave@whitevine.net>
3 Part of the Silver Tree Project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 or later.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12 #ifndef WML_UTILS_HPP_INCLUDED
13 #define WML_UTILS_HPP_INCLUDED
14
15 #include <stdlib.h>
16
17 #include "foreach.hpp"
18 #include "wml_node.hpp"
19
20 #include <boost/lexical_cast.hpp>
21
22 #define WML_READ_VECTOR(node, v, create_statement, element, ptr) \
23 { \
24 wml::node::const_child_range range = node->get_child_range(element); \
25 while(range.first != range.second) { \
26 v.push_back(ptr(create_statement(range.first->second))); \
27 ++range.first; \
28 } \
29 }
30
31 #define WML_WRITE_ATTR(node, var) \
32 { \
33 node->set_attr(#var, boost::lexical_cast<std::string>(var##_)); \
34 }
35
36 namespace wml {
37
38 struct error {};
39
40 typedef std::vector<const_node_ptr> const_node_vector;
41 typedef std::vector<node_ptr> node_vector;
42
43 node_ptr deep_copy(const_node_ptr node);
44 node_ptr deep_copy(const_node_ptr node, const std::string& name);
45 void merge_attr_over(const_node_ptr src, node_ptr dst);
46 void merge_over(const_node_ptr src, node_ptr dst);
47 void copy_over(const_node_ptr src, node_ptr dst);
48
49 //find a child which has a given attribute matching
50 node_ptr find_child_by_attribute(node_ptr node, const std::string& element_name, const std::string& attr, const std::string& value);
51
52 std::vector<const_node_ptr> child_nodes(const const_node_ptr& ptr,
53 const std::string& element);
54 std::vector<node_ptr> child_nodes(const node_ptr& ptr,
55 const std::string& element);
56
57 inline const std::string& get_str(const_node_ptr ptr,
58 const std::string& key)
59 {
60 return (*ptr)[key];
61 }
62
63 inline const std::string& get_str(const_node_ptr ptr,
64 const std::string& key,
65 const std::string& default_val)
66 {
67 const std::string& res = (*ptr)[key];
68 if(res.empty()) {
69 return default_val;
70 }
71
72 return res;
73 }
74
75 inline bool get_bool(const_node_ptr ptr, const std::string& key,
76 bool default_val=false)
77 {
78 const std::string& str = get_str(ptr,key);
79 if(str.empty()) {
80 return default_val;
81 }
82 return str == "yes" || str == "true";
83 }
84
85 template<typename T>
86 T get_attr(const_node_ptr ptr, const std::string& key,
87 T default_value=T())
88 {
89 if(!ptr) {
90 return default_value;
91 }
92
93 try {
94 return boost::lexical_cast<T>((*ptr)[key]);
95 } catch(boost::bad_lexical_cast&) {
96 return default_value;
97 }
98 }
99
100 inline int get_int(const_node_ptr ptr, const std::string& key,
101 int default_value=0)
102 {
103 if(!ptr) {
104 return default_value;
105 }
106
107 const std::string& str = (*ptr)[key];
108 if(str.empty()) {
109 return default_value;
110 }
111
112 return atoi(str.c_str());
113 }
114
115 inline bool require(bool cond)
116 {
117 if(!cond) {
118 throw error();
119 }
120 }
121
122 std::vector<int> get_vector_int(const_node_ptr ptr, const std::string& key);
123
124 template<typename Value>
125 node_ptr write_attribute_map(const std::string& name, const std::map<std::string,Value>& vals)
126 {
127 node_ptr res(new node(name));
128 for(typename std::map<std::string,Value>::const_iterator i = vals.begin(); i != vals.end(); ++i) {
129 res->set_attr(i->first, boost::lexical_cast<std::string>(i->second));
130 }
131
132 return res;
133 }
134
135 class child_sequence_iterator {
136 public:
137 child_sequence_iterator(wml::const_node_ptr node, const std::string& child_name);
138 void next();
139 bool at_end() const;
140 operator wml::const_node_ptr() const {
141 return i1_->second;
142 }
143
144 wml::const_node_ptr operator->() const {
145 return i1_->second;
146 }
147
148 const wml::node& operator*() const {
149 return *get();
150 }
151
152 wml::const_node_ptr get() const {
153 return i1_->second;
154 }
155 private:
156 wml::node::const_child_iterator i1_, i2_;
157 };
158
159 }
160
161 #define FOREACH_WML_CHILD(item, node, element_name) \
162 for(wml::child_sequence_iterator item(node, element_name); !item.at_end(); item.next())
163
164 template<typename To, typename From>
165 std::vector<To> vector_lexical_cast(const std::vector<From>& v) {
166 std::vector<To> result;
167 result.resize(v.size());
168 foreach(const From& from, v) {
169 result.push_back(boost::lexical_cast<To>(from));
170 }
171
172 return result;
173 }
174
175 #endif
0 #ifndef WML_VALUE_HPP_INCLUDED
1 #define WML_VALUE_HPP_INCLUDED
2
3 #include <sstream>
4 #include <string>
5
6 namespace wml
7 {
8
9 class value
10 {
11 public:
12 value() : line_(-1)
13 {}
14
15 value(const char* str, const std::string* fname=NULL, int line=-1)
16 : str_(str), fname_(fname), line_(line)
17 {
18 }
19
20 value(const std::string& str, const std::string* fname=NULL, int line=-1)
21 : str_(str), fname_(fname), line_(line)
22 {
23 }
24
25 operator const std::string&() const { return str_; }
26
27 const std::string& str() const { return str_; }
28 const std::string& val() const { return str_; }
29 const std::string* filename() const { return fname_; }
30 int line() const { return line_; }
31
32 bool empty() const { return str_.empty(); }
33
34 const char* c_str() const { return str_.c_str(); }
35 private:
36 std::string str_;
37 const std::string* fname_;
38 int line_;
39 };
40
41 inline std::ostream& operator<<(std::ostream& s, const value& v) {
42 s << v.val();
43 return s;
44 }
45 }
46
47 #endif
0 #include <iostream>
1 #include <set>
2
3 #include "foreach.hpp"
4 #include "string_utils.hpp"
5 #include "wml_node.hpp"
6 #include "wml_writer.hpp"
7
8 namespace wml
9 {
10
11 namespace {
12 void write_comment(const std::string& comment, const std::string& indent, std::string& res)
13 {
14 std::vector<std::string> lines = util::split(comment, '\n');
15 foreach(const std::string& line, lines) {
16 res += indent + line + "\n";
17 }
18 }
19 }
20
21 void write(const wml::const_node_ptr& node, std::string& res)
22 {
23 std::string indent;
24 write(node,res,indent);
25 }
26
27 void write(const wml::const_node_ptr& node, std::string& res,
28 std::string& indent, wml::const_node_ptr base)
29 {
30 if(node->get_comment().empty() == false) {
31 write_comment(node->get_comment(), indent, res);
32 }
33 res += indent + "[";
34 if(node->prefix().empty() == false) {
35 res += node->prefix() + ":";
36 }
37
38 res += node->name() + "]\n";
39
40 const std::vector<std::string>& attr_order = node->attr_order();
41 foreach(const std::string& attr, attr_order) {
42 if(base && base->attr(attr).str() == node->attr(attr).str()) {
43 continue;
44 }
45
46 const std::string& comment = node->get_attr_comment(attr);
47 if(comment.empty() == false) {
48 write_comment(comment, indent, res);
49 }
50 res += indent + attr + "=\"" + node->attr(attr).str() + "\"\n";
51 }
52
53 for(wml::node::const_attr_iterator i = node->begin_attr();
54 i != node->end_attr(); ++i) {
55 if(std::find(attr_order.begin(), attr_order.end(), i->first) != attr_order.end()) {
56 continue;
57 }
58
59 if(base && base->attr(i->first).str() == i->second.str()) {
60 continue;
61 }
62
63 const std::string& comment = node->get_attr_comment(i->first);
64 if(comment.empty() == false) {
65 write_comment(comment, indent, res);
66 }
67 res += indent + i->first + "=\"" + i->second.str() + "\"\n";
68 }
69 indent.push_back('\t');
70
71 std::set<std::string> base_written;
72 for(wml::node::const_all_child_iterator i = node->begin_children();
73 i != node->end_children(); ++i) {
74 wml::const_node_ptr base_node = node->get_base_element((*i)->name());
75 if(base_node && base_written.count((*i)->name()) == 0) {
76 base_written.insert((*i)->name());
77 if(base_node) {
78 write(base_node, res, indent);
79 }
80 }
81
82 write(*i, res, indent, base_node);
83 }
84 indent.resize(indent.size()-1);
85 res += indent + "[/" + node->name() + "]\n\n";
86 }
87
88 std::string output(const wml::const_node_ptr& node)
89 {
90 std::string res;
91 write(node, res);
92 return res;
93 }
94
95 }
0 #ifndef WML_WRITER_HPP_INCLUDED
1 #define WML_WRITER_HPP_INCLUDED
2
3 #include <string>
4
5 #include "wml_node_fwd.hpp"
6
7 namespace wml
8 {
9 void write(const wml::const_node_ptr& node, std::string& res);
10 void write(const wml::const_node_ptr& node, std::string& res,
11 std::string& indent, wml::const_node_ptr base=wml::const_node_ptr());
12 std::string output(const wml::const_node_ptr& node);
13 }
14
15 #endif