1 /*
2 * background.c - level background/foreground
3 * Copyright (C) 2010 Alexandre Martins <alemartf(at)gmail(dot)com>
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 as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <stdlib.h>
21 #include <math.h>
22 #include "background.h"
23 #include "actor.h"
24 #include "../core/sprite.h"
25 #include "../core/video.h"
26 #include "../core/osspec.h"
27 #include "../core/util.h"
28 #include "../core/stringutil.h"
29 #include "../core/logfile.h"
30 #include "../core/timer.h"
31 #include "../core/nanoparser/nanoparser.h"
32
33 /* forward declarations */
34 typedef struct background_t background_t;
35 typedef struct bgstrategy_t bgstrategy_t;
36 typedef struct bgstrategy_default_t bgstrategy_default_t;
37 typedef struct bgstrategy_circular_t bgstrategy_circular_t;
38 typedef struct bgstrategy_linear_t bgstrategy_linear_t;
39
40 /* === bgtheme struct (an ordered set of backgrounds) === */
41 struct bgtheme_t {
42 background_t **data; /* array of background_t* */
43 int length; /* length of the data vector */
44 };
45
46 /* === <<abstract>> bgstrategy_t === */
47 struct bgstrategy_t {
48 background_t *background; /* the background instance we're linked to */
49 void (*update)(bgstrategy_t*); /* update function */
50 };
51 static bgstrategy_t *bgstrategy_delete(bgstrategy_t *strategy); /* class destructor */
52
53
54 /* === background struct === */
55 struct background_t {
56 actor_t *actor; /* actor */
57 spriteinfo_t *data; /* this is not stored in the main hash */
58 int repeat_x, repeat_y; /* repeat background? */
59 float zindex; /* 0.0 (far) <= zindex <= 1.0 (near) */
60 bgstrategy_t *strategy; /* Strategy design pattern */
61 };
62 static background_t *background_new(); /* constructor */
63 static background_t *background_delete(background_t *bg); /* destructor */
64
65
66 /* === concrete strategies (background behaviors) === */
67
68 /* default background strategy */
69 struct bgstrategy_default_t {
70 bgstrategy_t base; /* inherits from bgstrategy_t */
71 };
72 static bgstrategy_t *bgstrategy_default_new(background_t *background); /* class constructor */
73 static void bgstrategy_default_update(bgstrategy_t *strategy); /* private class method*/
74
75 /* circular background strategy (elliptical trajectory) */
76 struct bgstrategy_circular_t {
77 bgstrategy_t base; /* base class */
78 float timer;
79 float amplitude_x, amplitude_y;
80 float angularspeed_x, angularspeed_y;
81 float initialphase_x, initialphase_y;
82 };
83 static bgstrategy_t *bgstrategy_circular_new(background_t *background, float amplitude_x, float amplitude_y, float angularspeed_x, float angularspeed_y, float initialphase_x, float initialphase_y); /* class constructor */
84 static void bgstrategy_circular_update(bgstrategy_t *strategy); /* private class method */
85
86 /* linear background strategy */
87 struct bgstrategy_linear_t {
88 bgstrategy_t base; /* base class */
89 float speed_x, speed_y;
90 };
91 static bgstrategy_t *bgstrategy_linear_new(background_t *background, float speed_x, float speed_y); /* class constructor */
92 static void bgstrategy_linear_update(bgstrategy_t *strategy); /* private class method */
93
94
95 /* === internal methods === */
96 static void sort_backgrounds(bgtheme_t *bgtheme);
97 static int sort_cmp(const void *a, const void *b);
98 static void render(bgtheme_t *theme, v2d_t camera_position, int foreground);
99 static int traverse(const parsetree_statement_t *stmt, void *bgtheme);
100 static int traverse_background_attributes(const parsetree_statement_t *stmt, void *background);
101 static void validate_background(const background_t *bg);
102
103
104 /* public methods */
105
106 /*
107 * background_load()
108 * Loads a background theme from a .bg file
109 */
background_load(const char * file)110 bgtheme_t* background_load(const char *file)
111 {
112 bgtheme_t *bgtheme;
113 parsetree_program_t *tree;
114 char abs_path[1024];
115
116 logfile_message("background_load('%s')", file);
117 resource_filepath(abs_path, file, sizeof(abs_path), RESFP_READ);
118
119 bgtheme = mallocx(sizeof *bgtheme);
120 bgtheme->data = NULL;
121 bgtheme->length = 0;
122
123 tree = nanoparser_construct_tree(abs_path);
124 nanoparser_traverse_program_ex(tree, (void*)bgtheme, traverse);
125 tree = nanoparser_deconstruct_tree(tree);
126
127 sort_backgrounds(bgtheme);
128 return bgtheme;
129 }
130
131
132
133 /*
134 * background_unload()
135 * Unloads a background theme
136 */
background_unload(bgtheme_t * bgtheme)137 bgtheme_t* background_unload(bgtheme_t *bgtheme)
138 {
139 int i;
140
141 logfile_message("background_unload()");
142
143 for(i=0; i<bgtheme->length; i++)
144 bgtheme->data[i] = background_delete(bgtheme->data[i]);
145
146 free(bgtheme->data);
147 free(bgtheme);
148 return NULL;
149 }
150
151
152
153 /*
154 * background_update()
155 * Updates the background
156 */
background_update(bgtheme_t * bgtheme)157 void background_update(bgtheme_t *bgtheme)
158 {
159 int i;
160 background_t *bg;
161
162 for(i=0; i<bgtheme->length; i++) {
163 bg = bgtheme->data[i];
164 bg->strategy->update(bg->strategy);
165 }
166 }
167
168
169
170 /*
171 * background_render_bg()
172 * Renders the background
173 */
background_render_bg(bgtheme_t * bgtheme,v2d_t camera_position)174 void background_render_bg(bgtheme_t *bgtheme, v2d_t camera_position)
175 {
176 render(bgtheme, camera_position, FALSE);
177 }
178
179 /*
180 * background_render_fg()
181 * Renders the foreground
182 */
background_render_fg(bgtheme_t * bgtheme,v2d_t camera_position)183 void background_render_fg(bgtheme_t *bgtheme, v2d_t camera_position)
184 {
185 render(bgtheme, camera_position, TRUE);
186 }
187
188
189
190
191
192
193 /* private methods */
194
background_new()195 background_t *background_new()
196 {
197 background_t *bg = mallocx(sizeof *bg);
198
199 bg->actor = actor_create();
200 bg->data = NULL;
201 bg->strategy = NULL;
202 bg->repeat_x = FALSE;
203 bg->repeat_y = FALSE;
204 bg->zindex = 0.0f;
205
206 return bg;
207 }
208
background_delete(background_t * bg)209 background_t *background_delete(background_t *bg)
210 {
211 bg->strategy = bgstrategy_delete(bg->strategy);
212 spriteinfo_destroy(bg->data);
213 actor_destroy(bg->actor);
214 free(bg);
215
216 return NULL;
217 }
218
bgstrategy_delete(bgstrategy_t * strategy)219 bgstrategy_t *bgstrategy_delete(bgstrategy_t *strategy)
220 {
221 free(strategy);
222 return NULL;
223 }
224
bgstrategy_default_new(background_t * background)225 bgstrategy_t *bgstrategy_default_new(background_t *background)
226 {
227 bgstrategy_default_t *me = mallocx(sizeof *me);
228 bgstrategy_t *base = (bgstrategy_t*)me;
229
230 base->background = background;
231 base->update = bgstrategy_default_update;
232
233 return base;
234 }
235
bgstrategy_default_update(bgstrategy_t * strategy)236 void bgstrategy_default_update(bgstrategy_t *strategy)
237 {
238 ; /* empty */
239 }
240
241
bgstrategy_circular_new(background_t * background,float amplitude_x,float amplitude_y,float angularspeed_x,float angularspeed_y,float initialphase_x,float initialphase_y)242 bgstrategy_t *bgstrategy_circular_new(background_t *background, float amplitude_x, float amplitude_y, float angularspeed_x, float angularspeed_y, float initialphase_x, float initialphase_y)
243 {
244 bgstrategy_circular_t *me = mallocx(sizeof *me);
245 bgstrategy_t *base = (bgstrategy_t*)me;
246
247 base->background = background;
248 base->update = bgstrategy_circular_update;
249 me->timer = 0.0f;
250 me->amplitude_x = amplitude_x;
251 me->amplitude_y = amplitude_y;
252 me->angularspeed_x = (2.0f * PI) * angularspeed_x;
253 me->angularspeed_y = (2.0f * PI) * angularspeed_y;
254 me->initialphase_x = (initialphase_x * PI) / 180.0f;
255 me->initialphase_y = (initialphase_y * PI) / 180.0f;
256
257 return base;
258 }
259
260
bgstrategy_circular_update(bgstrategy_t * strategy)261 void bgstrategy_circular_update(bgstrategy_t *strategy)
262 {
263 bgstrategy_circular_t *me = (bgstrategy_circular_t*)strategy;
264 background_t *bg = strategy->background;
265 float dt = timer_get_delta();
266 float t, sx, cy;
267
268 t = (me->timer += dt);
269 sx = sin(me->angularspeed_x * t + me->initialphase_x);
270 cy = cos(me->angularspeed_y * t + me->initialphase_y);
271
272 /* elliptical trajectory */
273 bg->actor->position.x += (-me->angularspeed_x * me->amplitude_x * sx) * dt;
274 bg->actor->position.y += (me->angularspeed_y * me->amplitude_y * cy) * dt;
275 }
276
277
bgstrategy_linear_new(background_t * background,float speed_x,float speed_y)278 bgstrategy_t *bgstrategy_linear_new(background_t *background, float speed_x, float speed_y)
279 {
280 bgstrategy_linear_t *me = mallocx(sizeof *me);
281 bgstrategy_t *base = (bgstrategy_t*)me;
282
283 base->background = background;
284 base->update = bgstrategy_linear_update;
285 me->speed_x = speed_x;
286 me->speed_y = speed_y;
287
288 return base;
289 }
290
291
bgstrategy_linear_update(bgstrategy_t * strategy)292 void bgstrategy_linear_update(bgstrategy_t *strategy)
293 {
294 bgstrategy_linear_t *me = (bgstrategy_linear_t*)strategy;
295 background_t *bg = strategy->background;
296 float dt = timer_get_delta();
297
298 bg->actor->position.x += me->speed_x * dt;
299 bg->actor->position.y += me->speed_y * dt;
300 }
301
302
render(bgtheme_t * bgtheme,v2d_t camera_position,int foreground)303 void render(bgtheme_t *bgtheme, v2d_t camera_position, int foreground)
304 {
305 int i;
306 v2d_t halfscreen = v2d_new(VIDEO_SCREEN_W/2, VIDEO_SCREEN_H/2);
307 v2d_t topleft = v2d_subtract(camera_position, halfscreen);
308 background_t *bg;
309
310 for(i=0; i<bgtheme->length; i++) {
311 bg = bgtheme->data[i];
312 if((!foreground && bg->zindex <= 0.5f) || (foreground && bg->zindex > 0.5f)) {
313 bg->actor->position.x += topleft.x * bg->actor->speed.x;
314 bg->actor->position.y += topleft.y * bg->actor->speed.y;
315
316 actor_render_repeat_xy(bg->actor, halfscreen, bg->repeat_x, bg->repeat_y);
317
318 bg->actor->position.y -= topleft.y * bg->actor->speed.y;
319 bg->actor->position.x -= topleft.x * bg->actor->speed.x;
320 }
321 }
322 }
323
sort_backgrounds(bgtheme_t * bgtheme)324 void sort_backgrounds(bgtheme_t *bgtheme)
325 {
326 /* merge_sort is a stable sorting algorithm. stdlib's qsort may not be. */
327 merge_sort(bgtheme->data, bgtheme->length, sizeof *(bgtheme->data), sort_cmp);
328 }
329
sort_cmp(const void * a,const void * b)330 int sort_cmp(const void *a, const void *b)
331 {
332 const background_t *i = *((const background_t**)a);
333 const background_t *j = *((const background_t**)b);
334
335 if(fabs(i->zindex - j->zindex) < EPSILON)
336 return 0;
337 else if(i->zindex < j->zindex)
338 return -1;
339 else
340 return 1;
341 }
342
traverse(const parsetree_statement_t * stmt,void * bgtheme)343 int traverse(const parsetree_statement_t *stmt, void *bgtheme)
344 {
345 const char *identifier;
346 const parsetree_parameter_t *param_list;
347 const parsetree_parameter_t *p1;
348 background_t *bg;
349 bgtheme_t *theme = (bgtheme_t*)bgtheme;
350
351 identifier = nanoparser_get_identifier(stmt);
352 param_list = nanoparser_get_parameter_list(stmt);
353
354 if(str_icmp(identifier, "background") == 0) {
355 p1 = nanoparser_get_nth_parameter(param_list, 1);
356
357 nanoparser_expect_program(p1, "Can't read background. Missing background attributes");
358
359 bg = background_new();
360 theme->data = reallocx(theme->data, (++(theme->length)) * sizeof(*(theme->data)));
361 theme->data[theme->length-1] = bg;
362 nanoparser_traverse_program_ex(nanoparser_get_program(p1), (void*)bg, traverse_background_attributes);
363 validate_background(bg);
364 actor_change_animation(bg->actor, bg->data->animation_data[0]);
365 }
366 else
367 fatal_error("Can't read background. Unknown identifier: '%s'", identifier);
368
369 return 0;
370 }
371
traverse_background_attributes(const parsetree_statement_t * stmt,void * background)372 int traverse_background_attributes(const parsetree_statement_t *stmt, void *background)
373 {
374 const char *identifier;
375 const parsetree_parameter_t *param_list;
376 const parsetree_parameter_t *p1, *p2, *p3, *p4, *p5, *p6, *p7;
377 background_t *bg = (background_t*)background;
378
379 identifier = nanoparser_get_identifier(stmt);
380 param_list = nanoparser_get_parameter_list(stmt);
381
382 if(str_icmp(identifier, "initial_position") == 0) {
383 p1 = nanoparser_get_nth_parameter(param_list, 1);
384 p2 = nanoparser_get_nth_parameter(param_list, 2);
385
386 nanoparser_expect_string(p1, "initial_position must be a pair of numbers");
387 nanoparser_expect_string(p2, "initial_position must be a pair of numbers");
388
389 bg->actor->spawn_point.x = atof(nanoparser_get_string(p1));
390 bg->actor->spawn_point.y = atof(nanoparser_get_string(p2));
391 bg->actor->position = bg->actor->spawn_point;
392 }
393 else if(str_icmp(identifier, "scroll_speed") == 0) {
394 p1 = nanoparser_get_nth_parameter(param_list, 1);
395 p2 = nanoparser_get_nth_parameter(param_list, 2);
396
397 nanoparser_expect_string(p1, "scroll_speed must be a pair of numbers");
398 nanoparser_expect_string(p2, "scroll_speed must be a pair of numbers");
399
400 bg->actor->speed.x = atof(nanoparser_get_string(p1));
401 bg->actor->speed.y = atof(nanoparser_get_string(p2));
402 }
403 else if(str_icmp(identifier, "behavior") == 0) {
404 p1 = nanoparser_get_nth_parameter(param_list, 1);
405 p2 = nanoparser_get_nth_parameter(param_list, 2);
406 p3 = nanoparser_get_nth_parameter(param_list, 3);
407 p4 = nanoparser_get_nth_parameter(param_list, 4);
408 p5 = nanoparser_get_nth_parameter(param_list, 5);
409 p6 = nanoparser_get_nth_parameter(param_list, 6);
410 p7 = nanoparser_get_nth_parameter(param_list, 7);
411
412 nanoparser_expect_string(p1, "Background behavior must be a string");
413
414 if(str_icmp(nanoparser_get_string(p1), "DEFAULT") == 0) {
415 if(bg->strategy)
416 bg->strategy = bgstrategy_delete(bg->strategy);
417 bg->strategy = bgstrategy_default_new(bg);
418 }
419 else if(str_icmp(nanoparser_get_string(p1), "LINEAR") == 0) {
420 nanoparser_expect_string(p2, "Linear background behavior expects a pair of numbers");
421 nanoparser_expect_string(p3, "Linear background behavior expects a pair of numbers");
422
423 if(bg->strategy)
424 bg->strategy = bgstrategy_delete(bg->strategy);
425 bg->strategy = bgstrategy_linear_new(
426 bg,
427 atof(nanoparser_get_string(p2)),
428 atof(nanoparser_get_string(p3))
429 );
430 }
431 else if(str_icmp(nanoparser_get_string(p1), "CIRCULAR") == 0) {
432 nanoparser_expect_string(p2, "Circular background behavior expects at least four numbers");
433 nanoparser_expect_string(p3, "Circular background behavior expects at least four numbers");
434 nanoparser_expect_string(p4, "Circular background behavior expects at least four numbers");
435 nanoparser_expect_string(p5, "Circular background behavior expects at least four numbers");
436
437 if(bg->strategy)
438 bg->strategy = bgstrategy_delete(bg->strategy);
439 bg->strategy = bgstrategy_circular_new(
440 bg,
441 atof(nanoparser_get_string(p2)),
442 atof(nanoparser_get_string(p3)),
443 atof(nanoparser_get_string(p4)),
444 atof(nanoparser_get_string(p5)),
445 atof(nanoparser_get_string(p6)),
446 atof(nanoparser_get_string(p7))
447 );
448 }
449 else
450 fatal_error("Unknown background behavior: '%s'", nanoparser_get_string(p1));
451 }
452 else if(str_icmp(identifier, "repeat_x") == 0) {
453 p1 = nanoparser_get_nth_parameter(param_list, 1);
454 nanoparser_expect_string(p1, "repeat_x expects a boolean value");
455 bg->repeat_x = atob(nanoparser_get_string(p1));
456 }
457 else if(str_icmp(identifier, "repeat_y") == 0) {
458 p1 = nanoparser_get_nth_parameter(param_list, 1);
459 nanoparser_expect_string(p1, "repeat_y expects a boolean value");
460 bg->repeat_y = atob(nanoparser_get_string(p1));
461 }
462 else if(str_icmp(identifier, "zindex") == 0) {
463 p1 = nanoparser_get_nth_parameter(param_list, 1);
464 nanoparser_expect_string(p1, "Can't read background attributes: zindex expects a number between 0.0 (far) and 1.0 (near)");
465 bg->zindex = clip(atof(nanoparser_get_string(p1)), 0.0f, 1.0f);
466 }
467 else if(str_icmp(identifier, "sprite") == 0) {
468 p1 = nanoparser_get_nth_parameter(param_list, 1);
469 nanoparser_expect_program(p1, "Can't read background attributes: sprite block expected");
470 if(bg->data != NULL)
471 spriteinfo_destroy(bg->data);
472 bg->data = spriteinfo_create(nanoparser_get_program(p1));
473 }
474 else
475 fatal_error("Can't read background attributes. Unknown identifier: '%s'", identifier);
476
477 return 0;
478 }
479
validate_background(const background_t * bg)480 void validate_background(const background_t *bg)
481 {
482 if(bg->data == NULL)
483 fatal_error("Can't read background: no sprite data given");
484
485 if(bg->strategy == NULL)
486 fatal_error("Can't read background: no behavior given");
487 }
488
489