Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1806 | yogev_ezra | 1 | /* |
2 | ------------------------------------------------------------ |
||
3 | Fixed Rate Pig - a fixed logic frame rate demo |
||
4 | ------------------------------------------------------------ |
||
5 | * Copyright (C) 2004 David Olofson |
||
6 | * |
||
7 | * This software is released under the terms of the GPL. |
||
8 | * |
||
9 | * Contact author for permission if you want to use this |
||
10 | * software, or work derived from it, under other terms. |
||
11 | */ |
||
12 | |||
13 | #include |
||
14 | #include |
||
15 | #include "engine.h" |
||
16 | #include "SDL_image.h" |
||
17 | |||
18 | /* Size of sprite frame table */ |
||
19 | #define PIG_MAX_SPRITES 1024 |
||
20 | |||
21 | |||
22 | /* |
||
23 | * Actually remove an objects. Used internally, |
||
24 | * to remove objects that have been marked for |
||
25 | * destruction. |
||
26 | */ |
||
27 | static void close_object(PIG_object *po); |
||
28 | |||
29 | |||
30 | /*---------------------------------------------------------- |
||
31 | Engine |
||
32 | ----------------------------------------------------------*/ |
||
33 | PIG_engine *pig_open(SDL_Surface *screen) |
||
34 | { |
||
35 | PIG_engine *pe = (PIG_engine *)calloc(1, sizeof(PIG_engine)); |
||
36 | if(!pe) |
||
37 | return NULL; |
||
38 | |||
39 | pe->screen = screen; |
||
40 | if(!pe->screen) |
||
41 | { |
||
42 | pig_close(pe); |
||
43 | return NULL; |
||
44 | } |
||
45 | if((pe->screen->flags & SDL_HWSURFACE) == SDL_HWSURFACE) |
||
46 | { |
||
47 | pe->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, |
||
48 | screen->w, screen->h, |
||
49 | screen->format->BitsPerPixel, |
||
50 | screen->format->Rmask, |
||
51 | screen->format->Gmask, |
||
52 | screen->format->Bmask, |
||
53 | screen->format->Amask); |
||
54 | if(!pe->buffer) |
||
55 | { |
||
56 | pig_close(pe); |
||
57 | return NULL; |
||
58 | } |
||
59 | pe->surface = pe->buffer; |
||
60 | } |
||
61 | else |
||
62 | pe->surface = screen; |
||
63 | |||
64 | pe->pages = 1 + ((screen->flags & SDL_DOUBLEBUF) == SDL_DOUBLEBUF); |
||
65 | |||
66 | pe->interpolation = 1; |
||
67 | pe->time = 0.0; |
||
68 | pe->view.w = pe->surface->w; |
||
69 | pe->view.h = pe->surface->h; |
||
70 | |||
71 | pe->sprites = (PIG_sprite **)calloc(PIG_MAX_SPRITES, |
||
72 | sizeof(PIG_sprite *)); |
||
73 | if(!pe->sprites) |
||
74 | { |
||
75 | pig_close(pe); |
||
76 | return NULL; |
||
77 | } |
||
78 | |||
79 | pe->pagedirty[0] = pig_dirty_open(128); |
||
80 | pe->workdirty = pig_dirty_open(256); |
||
81 | if(!pe->pagedirty[0] || !pe->workdirty) |
||
82 | { |
||
83 | pig_close(pe); |
||
84 | return NULL; |
||
85 | } |
||
86 | if(pe->pages > 1) |
||
87 | { |
||
88 | pe->pagedirty[1] = pig_dirty_open(128); |
||
89 | if(!pe->pagedirty[1]) |
||
90 | { |
||
91 | pig_close(pe); |
||
92 | return NULL; |
||
93 | } |
||
94 | } |
||
95 | |||
96 | return pe; |
||
97 | } |
||
98 | |||
99 | |||
100 | void pig_close(PIG_engine *pe) |
||
101 | { |
||
102 | if(pe->sprites) |
||
103 | { |
||
104 | int i; |
||
105 | for(i = 0; i < pe->nsprites; ++i) |
||
106 | if(pe->sprites[i]) |
||
107 | { |
||
108 | if(pe->sprites[i]->surface) |
||
109 | SDL_FreeSurface(pe->sprites[i]->surface); |
||
110 | free(pe->sprites[i]); |
||
111 | } |
||
112 | free(pe->sprites); |
||
113 | } |
||
114 | while(pe->objects) |
||
115 | close_object(pe->objects); |
||
116 | if(pe->map) |
||
117 | pig_map_close(pe->map); |
||
118 | if(pe->buffer) |
||
119 | SDL_FreeSurface(pe->buffer); |
||
120 | if(pe->pagedirty[0]) |
||
121 | pig_dirty_close(pe->pagedirty[0]); |
||
122 | if(pe->pagedirty[1]) |
||
123 | pig_dirty_close(pe->pagedirty[1]); |
||
124 | if(pe->workdirty) |
||
125 | pig_dirty_close(pe->workdirty); |
||
126 | free(pe); |
||
127 | } |
||
128 | |||
129 | |||
130 | void pig_viewport(PIG_engine *pe, int x, int y, int w, int h) |
||
131 | { |
||
132 | pe->view.x = x; |
||
133 | pe->view.y = y; |
||
134 | pe->view.w = w; |
||
135 | pe->view.h = h; |
||
136 | } |
||
137 | |||
138 | |||
139 | int pig_sprites(PIG_engine *pe, const char *filename, int sw, int sh) |
||
140 | { |
||
141 | int x, y, count, handle; |
||
142 | SDL_Surface *tmp = IMG_Load(filename); |
||
143 | if(!tmp) |
||
144 | { |
||
145 | fprintf(stderr, "Could not load '%s'!\n", filename); |
||
146 | return -1; |
||
147 | } |
||
148 | |||
149 | handle = pe->nsprites; |
||
150 | |||
151 | if(!sw) |
||
152 | sw = tmp->w; |
||
153 | if(!sh) |
||
154 | sh = tmp->h; |
||
155 | |||
156 | /* Disable blending, so we get the alpha channel COPIED! */ |
||
157 | SDL_SetAlpha(tmp, 0, 0); |
||
158 | |||
159 | count = 0; |
||
160 | for(y = 0; y <= tmp->h - sh; y += sh) |
||
161 | for(x = 0; x <= tmp->w - sw; x += sw) |
||
162 | { |
||
163 | SDL_Rect r; |
||
164 | SDL_Surface *tmp2; |
||
165 | PIG_sprite *s; |
||
166 | if(pe->nsprites >= PIG_MAX_SPRITES) |
||
167 | { |
||
168 | fprintf(stderr, "Sprite bank full!\n"); |
||
169 | return -1; |
||
170 | } |
||
171 | s = (PIG_sprite *)calloc(1, sizeof(PIG_sprite)); |
||
172 | if(!s) |
||
173 | return -1; |
||
174 | s->w = sw; |
||
175 | s->h = sh; |
||
176 | s->hotx = sw / 2; |
||
177 | s->hoty = sh / 2; |
||
178 | s->radius = (sw + sh) / 5; |
||
179 | tmp2 = SDL_CreateRGBSurface(SDL_SWSURFACE, |
||
180 | sw, sh, 32, |
||
181 | 0xff000000, 0x00ff0000, |
||
182 | 0x0000ff00, 0x000000ff); |
||
183 | SDL_SetAlpha(tmp2, 0, 0); |
||
184 | r.x = x; |
||
185 | r.y = y; |
||
186 | r.w = sw; |
||
187 | r.h = sh; |
||
188 | SDL_BlitSurface(tmp, &r, tmp2, NULL); |
||
189 | SDL_SetAlpha(tmp2, SDL_SRCALPHA | SDL_RLEACCEL, |
||
190 | SDL_ALPHA_OPAQUE); |
||
191 | s->surface = SDL_DisplayFormatAlpha(tmp2); |
||
192 | if(!s->surface) |
||
193 | { |
||
194 | fprintf(stderr, "Could not convert sprite %d" |
||
195 | " of '%s'!\n", |
||
196 | count, filename); |
||
197 | return -1; |
||
198 | } |
||
199 | SDL_FreeSurface(tmp2); |
||
200 | pe->sprites[pe->nsprites] = s; |
||
201 | ++pe->nsprites; |
||
202 | ++count; |
||
203 | } |
||
204 | |||
205 | SDL_FreeSurface(tmp); |
||
206 | return handle; |
||
207 | } |
||
208 | |||
209 | |||
210 | int pig_hotspot(PIG_engine *pe, int frame, int hotx, int hoty) |
||
211 | { |
||
212 | if((frame < 0 ) || (frame >= pe->nsprites)) |
||
213 | return -1; |
||
214 | |||
215 | switch(hotx) |
||
216 | { |
||
217 | case PIG_UNCHANGED: |
||
218 | break; |
||
219 | case PIG_MIN: |
||
220 | pe->sprites[frame]->hotx = 0; |
||
221 | break; |
||
222 | case PIG_CENTER: |
||
223 | pe->sprites[frame]->hotx = pe->sprites[frame]->w / 2; |
||
224 | break; |
||
225 | case PIG_MAX: |
||
226 | pe->sprites[frame]->hotx = pe->sprites[frame]->w; |
||
227 | break; |
||
228 | default: |
||
229 | pe->sprites[frame]->hotx = hotx; |
||
230 | break; |
||
231 | } |
||
232 | switch(hoty) |
||
233 | { |
||
234 | case PIG_UNCHANGED: |
||
235 | break; |
||
236 | case PIG_MIN: |
||
237 | pe->sprites[frame]->hoty = 0; |
||
238 | break; |
||
239 | case PIG_CENTER: |
||
240 | pe->sprites[frame]->hoty = pe->sprites[frame]->h / 2; |
||
241 | break; |
||
242 | case PIG_MAX: |
||
243 | pe->sprites[frame]->hoty = pe->sprites[frame]->h; |
||
244 | break; |
||
245 | default: |
||
246 | pe->sprites[frame]->hoty = hoty; |
||
247 | break; |
||
248 | } |
||
249 | return 0; |
||
250 | } |
||
251 | |||
252 | |||
253 | int pig_radius(PIG_engine *pe, int frame, int radius) |
||
254 | { |
||
255 | if((frame < 0 ) || (frame >= pe->nsprites)) |
||
256 | return -1; |
||
257 | |||
258 | pe->sprites[frame]->radius = radius; |
||
259 | return 0; |
||
260 | } |
||
261 | |||
262 | |||
263 | void pig_start(PIG_engine *pe, int frame) |
||
264 | { |
||
265 | PIG_object *po = pe->objects; |
||
266 | pe->time = (double)frame; |
||
267 | pe->frame = frame; |
||
268 | while(po) |
||
269 | { |
||
270 | po->ip.gx = po->ip.ox = po->x; |
||
271 | po->ip.gy = po->ip.oy = po->y; |
||
272 | po->ip.gimage = po->ibase + po->image; |
||
273 | po = po->next; |
||
274 | } |
||
275 | } |
||
276 | |||
277 | |||
278 | static void run_timers(PIG_engine *pe, PIG_object *po) |
||
279 | { |
||
280 | int i; |
||
281 | for(i = 0; i < PIG_TIMERS; ++i) |
||
282 | if(po->timer[i]) |
||
283 | { |
||
284 | --po->timer[i]; |
||
285 | if(!po->timer[i]) |
||
286 | { |
||
287 | PIG_event ev; |
||
288 | ev.type = PIG_TIMER0 + i; |
||
289 | po->handler(po, &ev); |
||
290 | if(!po->id) |
||
291 | return; |
||
292 | } |
||
293 | } |
||
294 | } |
||
295 | |||
296 | |||
297 | static void test_offscreen(PIG_engine *pe, PIG_object *po, PIG_sprite *s) |
||
298 | { |
||
299 | PIG_event ev; |
||
300 | int hx, hy, w, h; |
||
301 | if(s) |
||
302 | { |
||
303 | hx = s->hotx; |
||
304 | hy = s->hoty; |
||
305 | w = s->w; |
||
306 | h = s->h; |
||
307 | } |
||
308 | else |
||
309 | hx = hy = w = h = 0; |
||
310 | ev.cinfo.sides = (po->y - hy < -h) << PIG_TOP_B; |
||
311 | ev.cinfo.sides |= (po->y - hy >= pe->view.h) << PIG_BOTTOM_B; |
||
312 | ev.cinfo.sides |= (po->x - hx < -w) << PIG_LEFT_B; |
||
313 | ev.cinfo.sides |= (po->x - hx >= pe->view.w) << PIG_RIGHT_B; |
||
314 | if(ev.cinfo.sides) |
||
315 | { |
||
316 | float dx = po->x - po->ip.ox; |
||
317 | float dy = po->y - po->ip.oy; |
||
318 | if(ev.cinfo.sides & PIG_TOP) |
||
319 | { |
||
320 | ev.cinfo.y = 0; |
||
321 | if(dy) |
||
322 | ev.cinfo.x = po->ip.ox - dx * po->ip.oy / dy; |
||
323 | } |
||
324 | else if(ev.cinfo.sides & PIG_BOTTOM) |
||
325 | { |
||
326 | ev.cinfo.y = pe->view.h - 1; |
||
327 | if(dy) |
||
328 | ev.cinfo.x = po->ip.ox + dx * |
||
329 | (ev.cinfo.y - po->ip.oy) / dy; |
||
330 | } |
||
331 | if(ev.cinfo.sides & PIG_LEFT) |
||
332 | { |
||
333 | ev.cinfo.x = 0; |
||
334 | if(dx) |
||
335 | ev.cinfo.y = po->ip.oy - dy * po->ip.ox / dx; |
||
336 | } |
||
337 | else if(ev.cinfo.sides & PIG_RIGHT) |
||
338 | { |
||
339 | ev.cinfo.x = pe->view.w - 1; |
||
340 | if(dx) |
||
341 | ev.cinfo.y = po->ip.oy + dy * |
||
342 | (ev.cinfo.x - po->ip.ox) / dx; |
||
343 | } |
||
344 | ev.type = PIG_OFFSCREEN; |
||
345 | po->handler(po, &ev); |
||
346 | } |
||
347 | } |
||
348 | |||
349 | |||
350 | /* Test for stationary sprite/sprite collision */ |
||
351 | static void sprite_sprite_one(PIG_object *po, PIG_object *po2, float t, float hitdist) |
||
352 | { |
||
353 | float dx, dy, dsquare; |
||
354 | PIG_event ev; |
||
355 | int sides; |
||
356 | float ix = po->ip.ox * (1 - t) + po->x * t; |
||
357 | float iy = po->ip.oy * (1 - t) + po->y * t; |
||
358 | float ix2 = po2->ip.ox * (1 - t) + po2->x * t; |
||
359 | float iy2 = po2->ip.oy * (1 - t) + po2->y * t; |
||
360 | dx = ix - ix2; |
||
361 | dy = iy - iy2; |
||
362 | dsquare = dx*dx + dy*dy; |
||
363 | if(dsquare >= hitdist*hitdist) |
||
364 | return; /* Nothing... --> */ |
||
365 | |||
366 | if(fabs(dsquare) < 1) |
||
367 | sides = PIG_ALL; |
||
368 | else |
||
369 | { |
||
370 | float d = sqrt(dsquare); |
||
371 | dx /= d; |
||
372 | dy /= d; |
||
373 | if(dx < -0.707) |
||
374 | sides = PIG_LEFT; |
||
375 | else if((dx > 0.707)) |
||
376 | sides = PIG_RIGHT; |
||
377 | else |
||
378 | sides = 0; |
||
379 | if(dy < -0.707) |
||
380 | sides |= PIG_TOP; |
||
381 | else if((dy > 0.707)) |
||
382 | sides |= PIG_BOTTOM; |
||
383 | } |
||
384 | ev.type = PIG_HIT_OBJECT; |
||
385 | ev.cinfo.ff = 0.0; |
||
386 | |||
387 | ev.cinfo.x = ix; |
||
388 | ev.cinfo.y = iy; |
||
389 | ev.cinfo.sides = sides; |
||
390 | if(po->hitmask & po2->hitgroup) |
||
391 | { |
||
392 | ev.obj = po2; |
||
393 | po->handler(po, &ev); |
||
394 | } |
||
395 | |||
396 | if(po2->id && (po2->hitmask & po->hitgroup)) |
||
397 | { |
||
398 | int s; |
||
399 | ev.cinfo.x = ix2; |
||
400 | ev.cinfo.y = iy2; |
||
401 | s = ((sides >> PIG_LEFT_B) & 1) << PIG_RIGHT_B; |
||
402 | s |= ((sides >> PIG_RIGHT_B) & 1) << PIG_LEFT_B; |
||
403 | s |= ((sides >> PIG_TOP_B) & 1) << PIG_BOTTOM_B; |
||
404 | s |= ((sides >> PIG_BOTTOM_B) & 1) << PIG_TOP_B; |
||
405 | ev.cinfo.sides = s; |
||
406 | ev.obj = po; |
||
407 | po2->handler(po2, &ev); |
||
408 | } |
||
409 | } |
||
410 | |||
411 | |||
412 | /* |
||
413 | * Check 'po' against all subsequent objects in the list. |
||
414 | * The testing is step size limited so that neither object |
||
415 | * moves more than 25% of the collision distance between tests. |
||
416 | * (25% should be sufficient for correct direction flags.) |
||
417 | */ |
||
418 | static void test_sprite_sprite(PIG_engine *pe, PIG_object *po, PIG_sprite *s) |
||
419 | { |
||
420 | int image; |
||
421 | PIG_object *po2, *next2; |
||
422 | for(po2 = po->next; po2; po2 = next2) |
||
423 | { |
||
424 | float hitdist, d, dmax, t, dt; |
||
425 | next2 = po2->next; |
||
426 | if(!po->id || !po2->id) |
||
427 | break; |
||
428 | |||
429 | /* Check collision groups and masks */ |
||
430 | if(!(po->hitmask & po2->hitgroup) && |
||
431 | !(po2->hitmask & po->hitgroup)) |
||
432 | continue; |
||
433 | |||
434 | /* Calculate minimum distance */ |
||
435 | hitdist = s ? s->radius : 0; |
||
436 | image = po2->ibase + po2->image; |
||
437 | if((image >= 0) && (image < pe->nsprites)) |
||
438 | hitdist += pe->sprites[image]->radius; |
||
439 | if(hitdist < 1) |
||
440 | hitdist = 1; |
||
441 | |||
442 | /* Calculate number of testing steps */ |
||
443 | dmax = fabs(po->ip.ox - po->x); |
||
444 | d = fabs(po->ip.oy - po->y); |
||
445 | dmax = d > dmax ? d : dmax; |
||
446 | d = fabs(po2->ip.ox - po2->x); |
||
447 | dmax = d > dmax ? d : dmax; |
||
448 | d = fabs(po2->ip.oy - po2->y); |
||
449 | dmax = d > dmax ? d : dmax; |
||
450 | if(dmax > 1) |
||
451 | dt = hitdist / (dmax * 4); |
||
452 | else |
||
453 | dt = 1; |
||
454 | |||
455 | /* Sweep test! */ |
||
456 | for(t = 0; t < 1; t += dt) |
||
457 | sprite_sprite_one(po, po2, t, hitdist); |
||
458 | } |
||
459 | } |
||
460 | |||
461 | |||
462 | /* |
||
463 | * Returns a non-zero value if the tile at (x, y) is marked for |
||
464 | * collisions on the side indicated by 'mask'. |
||
465 | */ |
||
466 | static __inline__ int check_tile(PIG_map *m, int x, int y, int mask) |
||
467 | { |
||
468 | int mx, my; |
||
469 | /* |
||
470 | * Must check < 0 first! (Division rounds |
||
471 | * towards zero - not downwards.) |
||
472 | */ |
||
473 | if(x < 0 || y < 0) |
||
474 | return PIG_NONE; |
||
475 | |||
476 | mx = x / m->tw; |
||
477 | my = y / m->th; |
||
478 | if(mx >= m->w || my >= m->h) |
||
479 | return PIG_NONE; |
||
480 | |||
481 | return m->hit[my * m->w + mx] & mask; |
||
482 | } |
||
483 | |||
484 | |||
485 | int pig_test_map(PIG_engine *pe, int x, int y) |
||
486 | { |
||
487 | int mx, my; |
||
488 | if(x < 0 || y < 0) |
||
489 | return PIG_NONE; |
||
490 | |||
491 | mx = x / pe->map->tw; |
||
492 | my = y / pe->map->th; |
||
493 | if(mx >= pe->map->w || my >= pe->map->h) |
||
494 | return PIG_NONE; |
||
495 | |||
496 | return pe->map->hit[my * pe->map->w + mx]; |
||
497 | } |
||
498 | |||
499 | |||
500 | /* |
||
501 | * Simple implementation that checks only for top edge collisions. |
||
502 | * (Full top/bottom/left/right checks with proper handling of |
||
503 | * corners and rows of tiles is a lot more complicated, so I'll |
||
504 | * leave that out for now, rather than hacking something simple |
||
505 | * but incorrect.) |
||
506 | */ |
||
507 | int pig_test_map_vector(PIG_engine *pe, int x1, int y1, int x2, int y2, |
||
508 | int mask, PIG_cinfo *ci) |
||
509 | { |
||
510 | PIG_cinfo lci; |
||
511 | PIG_map *m = pe->map; |
||
512 | int x, y; |
||
513 | int dist = 2000000000L; |
||
514 | if(!ci) |
||
515 | ci = &lci; |
||
516 | ci->sides = 0; |
||
517 | if((mask & PIG_TOP) && (y1 < y2)) |
||
518 | { |
||
519 | /* Test for tiles that can be hit from the top */ |
||
520 | for(y = y1 + m->th - y1 % m->th; y <= y2; y += m->th) |
||
521 | { |
||
522 | x = x1 + (x2 - x1) * (y - y1) / (y2 - y1); |
||
523 | if(check_tile(m, x, y + 1, PIG_TOP)) |
||
524 | { |
||
525 | dist = (x-x1) * (x-x1) + (y-y1) * (y-y1); |
||
526 | ci->x = x; |
||
527 | ci->y = y - 1; |
||
528 | ci->sides |= PIG_TOP; |
||
529 | break; |
||
530 | } |
||
531 | } |
||
532 | } |
||
533 | if(ci->sides) |
||
534 | ci->ff = sqrt((x2 - x1) * (x2 - x1) + |
||
535 | (y2 - y1) * (y2 - y1) / dist); |
||
536 | return ci->sides; |
||
537 | } |
||
538 | |||
539 | |||
540 | static void test_sprite_map(PIG_engine *pe, PIG_object *po, PIG_sprite *s) |
||
541 | { |
||
542 | PIG_event ev; |
||
543 | if(pig_test_map_vector(pe, po->ip.ox, po->ip.oy, po->x, po->y, |
||
544 | po->tilemask, &ev.cinfo)) |
||
545 | { |
||
546 | ev.type = PIG_HIT_TILE; |
||
547 | po->handler(po, &ev); |
||
548 | } |
||
549 | } |
||
550 | |||
551 | |||
552 | static void run_logic(PIG_engine *pe) |
||
553 | { |
||
554 | PIG_object *po, *next; |
||
555 | int image; |
||
556 | |||
557 | /* Shift logic coordinates */ |
||
558 | for(po = pe->objects; po; po = po->next) |
||
559 | { |
||
560 | po->ip.ox = po->x; |
||
561 | po->ip.oy = po->y; |
||
562 | } |
||
563 | |||
564 | if(pe->before_objects) |
||
565 | pe->before_objects(pe); |
||
566 | |||
567 | for(po = pe->objects; po; po = next) |
||
568 | { |
||
569 | PIG_event ev; |
||
570 | /* |
||
571 | * We must grab the next pointer before |
||
572 | * we call any event handlers, as they |
||
573 | * may cause objects to remove themselves! |
||
574 | */ |
||
575 | next = po->next; |
||
576 | ev.type = PIG_PREFRAME; |
||
577 | po->handler(po, &ev); |
||
578 | } |
||
579 | |||
580 | for(po = pe->objects; po; po = next) |
||
581 | { |
||
582 | PIG_sprite *s; |
||
583 | next = po->next; |
||
584 | image = po->ibase + po->image; |
||
585 | if((image >= 0) && (image < pe->nsprites)) |
||
586 | s = pe->sprites[image]; |
||
587 | else |
||
588 | s = NULL; |
||
589 | |||
590 | /* Move! */ |
||
591 | po->vx += po->ax; |
||
592 | po->vy += po->ay; |
||
593 | po->x += po->vx; |
||
594 | po->y += po->vy; |
||
595 | |||
596 | /* Check and handle events */ |
||
597 | if(po->handler) |
||
598 | { |
||
599 | run_timers(pe, po); |
||
600 | if(po->id) |
||
601 | test_offscreen(pe, po, s); |
||
602 | if(po->id && (po->hitmask || po->hitgroup)) |
||
603 | test_sprite_sprite(pe, po, s); |
||
604 | if(po->id && po->tilemask) |
||
605 | test_sprite_map(pe, po, s); |
||
606 | |||
607 | } |
||
608 | } |
||
609 | |||
610 | for(po = pe->objects; po; po = next) |
||
611 | { |
||
612 | next = po->next; |
||
613 | if(po->id) |
||
614 | { |
||
615 | PIG_event ev; |
||
616 | ev.type = PIG_POSTFRAME; |
||
617 | po->handler(po, &ev); |
||
618 | ++po->age; |
||
619 | } |
||
620 | } |
||
621 | |||
622 | if(pe->after_objects) |
||
623 | pe->after_objects(pe); |
||
624 | } |
||
625 | |||
626 | |||
627 | void pig_animate(PIG_engine *pe, float frames) |
||
628 | { |
||
629 | /* Advance logic time */ |
||
630 | int i = floor(pe->time + frames) - floor(pe->time); |
||
631 | while(i--) |
||
632 | { |
||
633 | run_logic(pe); |
||
634 | ++pe->frame; |
||
635 | } |
||
636 | pe->time += frames; |
||
637 | } |
||
638 | |||
639 | |||
640 | void pig_dirty(PIG_engine *pe, SDL_Rect *dr) |
||
641 | { |
||
642 | SDL_Rect r; |
||
643 | r.x = 0; |
||
644 | r.y = 0; |
||
645 | r.w = pe->surface->w; |
||
646 | r.h = pe->surface->h; |
||
647 | if(dr) |
||
648 | pig_intersectrect(dr, &r); |
||
649 | if(r.w && r.h) |
||
650 | pig_dirty_add(pe->pagedirty[pe->page], &r); |
||
651 | } |
||
652 | |||
653 | |||
654 | static void tile_area(PIG_engine *pe, SDL_Rect *r) |
||
655 | { |
||
656 | SDL_Rect cr; |
||
657 | int x, y, startx, starty, maxx, maxy, tilesperrow; |
||
658 | cr = *r; |
||
659 | cr.x += pe->view.x; |
||
660 | cr.y += pe->view.y; |
||
661 | SDL_SetClipRect(pe->surface, &cr); |
||
662 | |||
663 | startx = r->x / pe->map->tw; |
||
664 | starty = r->y / pe->map->th; |
||
665 | maxx = (r->x + r->w + pe->map->tw - 1) / pe->map->tw; |
||
666 | maxy = (r->y + r->h + pe->map->th - 1) / pe->map->th; |
||
667 | if(maxx > pe->map->w - 1) |
||
668 | maxx = pe->map->w - 1; |
||
669 | if(maxy > pe->map->h - 1) |
||
670 | maxy = pe->map->h - 1; |
||
671 | tilesperrow = pe->map->tiles->w / pe->map->tw; |
||
672 | |||
673 | for(y = starty; y <= maxy; ++y) |
||
674 | for(x = startx; x <= maxx; ++x) |
||
675 | { |
||
676 | SDL_Rect from, to; |
||
677 | int c = pe->map->map[y * pe->map->w + x]; |
||
678 | from.x = c % tilesperrow * pe->map->tw; |
||
679 | from.y = c / tilesperrow * pe->map->th; |
||
680 | from.w = pe->map->tw; |
||
681 | from.h = pe->map->th; |
||
682 | to.x = pe->view.x + x * pe->map->tw; |
||
683 | to.y = pe->view.y + y * pe->map->th; |
||
684 | SDL_BlitSurface(pe->map->tiles, &from, |
||
685 | pe->surface, &to); |
||
686 | } |
||
687 | } |
||
688 | |||
689 | |||
690 | void remove_sprites(PIG_engine *pe) |
||
691 | { |
||
692 | SDL_Rect r; |
||
693 | PIG_sprite *s; |
||
694 | PIG_object *po, *next; |
||
695 | |||
696 | /* |
||
697 | * Remove all objects, using the information that |
||
698 | * remains from the last frame. The actual removal |
||
699 | * is done by drawing over the sprites with tiles |
||
700 | * from the map. |
||
701 | * |
||
702 | * We assume that most objects don't overlap. If |
||
703 | * they do that a lot, we could use a "dirty map" |
||
704 | * to avoid rendering the same tiles multiple times |
||
705 | * in the overlapping areas. |
||
706 | */ |
||
707 | for(po = pe->objects; po; po = next) |
||
708 | { |
||
709 | next = po->next; |
||
710 | if((po->ip.gimage < 0) || (po->ip.gimage >= pe->nsprites)) |
||
711 | continue; |
||
712 | s = pe->sprites[po->ip.gimage]; |
||
713 | r.x = po->ip.gx - s->hotx; |
||
714 | r.y = po->ip.gy - s->hoty; |
||
715 | r.w = s->w; |
||
716 | r.h = s->h; |
||
717 | pig_intersectrect(&pe->view, &r); |
||
718 | if(r.w && r.h) |
||
719 | tile_area(pe, &r); |
||
720 | |||
721 | /* |
||
722 | * Delete dead objects *after* they've |
||
723 | * been removed from the rendering buffer! |
||
724 | */ |
||
725 | if(!po->id) |
||
726 | close_object(po); |
||
727 | } |
||
728 | } |
||
729 | |||
730 | |||
731 | void draw_sprites(PIG_engine *pe) |
||
732 | { |
||
733 | PIG_dirtytable *pdt; |
||
734 | PIG_sprite *s; |
||
735 | PIG_object *po; |
||
736 | float fframe = pe->time - floor(pe->time); |
||
737 | SDL_SetClipRect(pe->surface, &pe->view); |
||
738 | |||
739 | /* Swap the work and display/back page dirtytables */ |
||
740 | pdt = pe->workdirty; |
||
741 | pe->workdirty = pe->pagedirty[pe->page]; |
||
742 | pe->pagedirty[pe->page] = pdt; |
||
743 | |||
744 | /* Clear the display/back page dirtytable */ |
||
745 | pdt->count = 0; |
||
746 | |||
747 | /* Update positions and render all objects */ |
||
748 | po = pe->objects; |
||
749 | while(po) |
||
750 | { |
||
751 | /* Calculate graphic coordinates */ |
||
752 | if(pe->interpolation) |
||
753 | { |
||
754 | po->ip.gx = po->ip.ox * (1 - fframe) + po->x * fframe; |
||
755 | po->ip.gy = po->ip.oy * (1 - fframe) + po->y * fframe; |
||
756 | } |
||
757 | else |
||
758 | { |
||
759 | po->ip.gx = po->x; |
||
760 | po->ip.gy = po->y; |
||
761 | } |
||
762 | po->ip.gimage = po->ibase + po->image; |
||
763 | |||
764 | /* Render the sprite! */ |
||
765 | if((po->ip.gimage >= 0) && (po->ip.gimage < pe->nsprites)) |
||
766 | { |
||
767 | SDL_Rect dr; |
||
768 | s = pe->sprites[po->ip.gimage]; |
||
769 | dr.x = po->ip.gx - s->hotx + pe->view.x; |
||
770 | dr.y = po->ip.gy - s->hoty + pe->view.y; |
||
771 | SDL_BlitSurface(pe->sprites[po->ip.gimage]->surface, |
||
772 | NULL, pe->surface, &dr); |
||
773 | /* |
||
774 | * We use the clipped rect for the dirtyrect! |
||
775 | */ |
||
776 | if(dr.w && dr.h) |
||
777 | pig_dirty_add(pdt, &dr); |
||
778 | } |
||
779 | po = po->next; |
||
780 | } |
||
781 | |||
782 | /* Merge the display/back page table into the work table */ |
||
783 | pig_dirty_merge(pe->workdirty, pdt); |
||
784 | } |
||
785 | |||
786 | |||
787 | void pig_refresh(PIG_engine *pe) |
||
788 | { |
||
789 | remove_sprites(pe); |
||
790 | draw_sprites(pe); |
||
791 | } |
||
792 | |||
793 | |||
794 | void pig_refresh_all(PIG_engine *pe) |
||
795 | { |
||
796 | tile_area(pe, &pe->view); |
||
797 | pig_dirty(pe, NULL); |
||
798 | draw_sprites(pe); |
||
799 | } |
||
800 | |||
801 | |||
802 | static void show_rects(PIG_engine *pe, PIG_dirtytable *pdt) |
||
803 | { |
||
804 | int i; |
||
805 | Uint32 color; |
||
806 | if(!pe->buffer) |
||
807 | { |
||
808 | pe->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, |
||
809 | pe->screen->w, pe->screen->h, |
||
810 | pe->screen->format->BitsPerPixel, |
||
811 | pe->screen->format->Rmask, |
||
812 | pe->screen->format->Gmask, |
||
813 | pe->screen->format->Bmask, |
||
814 | pe->screen->format->Amask); |
||
815 | if(!pe->buffer) |
||
816 | return; |
||
817 | pe->surface = pe->buffer; |
||
818 | tile_area(pe, &pe->view); |
||
819 | } |
||
820 | if(!pe->buffer) |
||
821 | return; |
||
822 | |||
823 | pe->direct = 0; |
||
824 | |||
825 | for(i = 0; i < pdt->count; ++i) |
||
826 | { |
||
827 | SDL_Rect r; |
||
828 | r = pdt->rects[i]; |
||
829 | r.x -= 32; |
||
830 | r.y -= 32; |
||
831 | r.w += 64; |
||
832 | r.h += 64; |
||
833 | SDL_BlitSurface(pe->buffer, &r, pe->screen, &r); |
||
834 | } |
||
835 | |||
836 | color = SDL_MapRGB(pe->screen->format, 255, 0, 255); |
||
837 | for(i = 0; i < pdt->count; ++i) |
||
838 | { |
||
839 | SDL_Rect r; |
||
840 | r = pdt->rects[i]; |
||
841 | r.h = 1; |
||
842 | SDL_FillRect(pe->screen, &r, color); |
||
843 | r.y += pdt->rects[i].h - 1; |
||
844 | SDL_FillRect(pe->screen, &r, color); |
||
845 | r = pdt->rects[i]; |
||
846 | r.w = 1; |
||
847 | SDL_FillRect(pe->screen, &r, color); |
||
848 | r.x += pdt->rects[i].w - 1; |
||
849 | SDL_FillRect(pe->screen, &r, color); |
||
850 | } |
||
851 | } |
||
852 | |||
853 | |||
854 | void pig_flip(PIG_engine *pe) |
||
855 | { |
||
856 | PIG_dirtytable *pdt = pe->workdirty; |
||
857 | int i; |
||
858 | SDL_SetClipRect(pe->surface, NULL); |
||
859 | |||
860 | if(pe->show_dirtyrects) |
||
861 | { |
||
862 | show_rects(pe, pdt); |
||
863 | for(i = 0; i < pdt->count; ++i) |
||
864 | { |
||
865 | pdt->rects[i].x -= 32; |
||
866 | pdt->rects[i].y -= 32; |
||
867 | pdt->rects[i].w += 64; |
||
868 | pdt->rects[i].h += 64; |
||
869 | pig_intersectrect(&pe->buffer->clip_rect, &pdt->rects[i]); |
||
870 | } |
||
871 | } |
||
872 | else if(pe->surface == pe->buffer) |
||
873 | for(i = 0; i < pdt->count; ++i) |
||
874 | SDL_BlitSurface(pe->buffer, pdt->rects + i, |
||
875 | pe->screen, pdt->rects + i); |
||
876 | |||
877 | if((pe->screen->flags & SDL_HWSURFACE) == SDL_HWSURFACE) |
||
878 | { |
||
879 | SDL_Flip(pe->screen); |
||
880 | if(pe->pages > 1) |
||
881 | pe->page = 1 - pe->page; |
||
882 | } |
||
883 | else |
||
884 | SDL_UpdateRects(pe->screen, pdt->count, pdt->rects); |
||
885 | |||
886 | if(pe->direct) |
||
887 | pe->surface = pe->screen; |
||
888 | else |
||
889 | pe->surface = pe->buffer ? pe->buffer : pe->screen; |
||
890 | } |
||
891 | |||
892 | |||
893 | void pig_draw_sprite(PIG_engine *pe, int frame, int x, int y) |
||
894 | { |
||
895 | SDL_Rect dr; |
||
896 | if(frame >= pe->nsprites) |
||
897 | return; |
||
898 | dr.x = x - pe->sprites[frame]->hotx + pe->view.x; |
||
899 | dr.y = y - pe->sprites[frame]->hoty + pe->view.y; |
||
900 | SDL_BlitSurface(pe->sprites[frame]->surface, NULL, |
||
901 | pe->surface, &dr); |
||
902 | } |
||
903 | |||
904 | |||
905 | /*---------------------------------------------------------- |
||
906 | Map |
||
907 | ----------------------------------------------------------*/ |
||
908 | PIG_map *pig_map_open(PIG_engine *pe, int w, int h) |
||
909 | { |
||
910 | if(pe->map) |
||
911 | pig_map_close(pe->map); |
||
912 | |||
913 | pe->map = (PIG_map *)calloc(1, sizeof(PIG_map)); |
||
914 | if(!pe->map) |
||
915 | return NULL; |
||
916 | |||
917 | pe->map->owner = pe; |
||
918 | pe->map->w = w; |
||
919 | pe->map->h = h; |
||
920 | pe->map->hit = (unsigned char *)calloc(w, h); |
||
921 | if(!pe->map->hit) |
||
922 | { |
||
923 | pig_map_close(pe->map); |
||
924 | return NULL; |
||
925 | } |
||
926 | pe->map->map = (unsigned char *)calloc(w, h); |
||
927 | if(!pe->map->map) |
||
928 | { |
||
929 | pig_map_close(pe->map); |
||
930 | return NULL; |
||
931 | } |
||
932 | return pe->map; |
||
933 | } |
||
934 | |||
935 | |||
936 | void pig_map_close(PIG_map *pm) |
||
937 | { |
||
938 | PIG_engine *pe = pm->owner; |
||
939 | if(pm->tiles) |
||
940 | SDL_FreeSurface(pm->tiles); |
||
941 | free(pm->hit); |
||
942 | free(pm->map); |
||
943 | free(pe->map); |
||
944 | pe->map = NULL; |
||
945 | } |
||
946 | |||
947 | |||
948 | int pig_map_tiles(PIG_map *pm, const char *filename, int tw, int th) |
||
949 | { |
||
950 | SDL_Surface *tmp; |
||
951 | pm->tw = tw; |
||
952 | pm->th = th; |
||
953 | tmp = IMG_Load(filename); |
||
954 | if(!tmp) |
||
955 | { |
||
956 | fprintf(stderr, "Could not load '%s'!\n", filename); |
||
957 | return -1; |
||
958 | } |
||
959 | pm->tiles = SDL_DisplayFormat(tmp); |
||
960 | if(!pm->tiles) |
||
961 | { |
||
962 | fprintf(stderr, "Could not convert '%s'!\n", filename); |
||
963 | return -1; |
||
964 | } |
||
965 | SDL_FreeSurface(tmp); |
||
966 | return 0; |
||
967 | } |
||
968 | |||
969 | |||
970 | void pig_map_collisions(PIG_map *pm, unsigned first, unsigned count, PIG_sides sides) |
||
971 | { |
||
972 | int i; |
||
973 | if(first > 255) |
||
974 | return; |
||
975 | if(first + count > 255) |
||
976 | count = 255 - first; |
||
977 | for(i = first; i < first + count; ++i) |
||
978 | pm->hitinfo[i] = sides; |
||
979 | } |
||
980 | |||
981 | |||
982 | /* |
||
983 | * Load a map from a string (one byte/tile). 'trans' |
||
984 | * is a string used for translating 'data' into integer |
||
985 | * tile indices. Each position in 'trans' corresponds |
||
986 | * to one tile in the tile palette. |
||
987 | */ |
||
988 | int pig_map_from_string(PIG_map *pm, const char *trans, const char *data) |
||
989 | { |
||
990 | int x, y, z; |
||
991 | |||
992 | /* Load the map */ |
||
993 | z = 0; |
||
994 | for(y = 0; y < pm->h; ++y) |
||
995 | for(x = 0; x < pm->w; ++x) |
||
996 | { |
||
997 | const char *f; |
||
998 | int c = data[z]; |
||
999 | if(!c) |
||
1000 | { |
||
1001 | fprintf(stderr, "Map string too short!\n"); |
||
1002 | return -1; |
||
1003 | } |
||
1004 | f = strchr(trans, c); |
||
1005 | if(!f) |
||
1006 | { |
||
1007 | fprintf(stderr, "Character '%c' not in" |
||
1008 | " the translation string!\n", |
||
1009 | c); |
||
1010 | return -1; |
||
1011 | } |
||
1012 | pm->map[z] = f - trans; |
||
1013 | ++z; |
||
1014 | } |
||
1015 | /* Generate collision map */ |
||
1016 | for(y = 0; y < pm->h; ++y) |
||
1017 | for(x = 0; x < pm->w; ++x) |
||
1018 | pm->hit[y * pm->w + x] = |
||
1019 | pm->hitinfo[pm->map[y * pm->w + x]]; |
||
1020 | return 0; |
||
1021 | } |
||
1022 | |||
1023 | |||
1024 | /*---------------------------------------------------------- |
||
1025 | Object |
||
1026 | ----------------------------------------------------------*/ |
||
1027 | |||
1028 | |||
1029 | static PIG_object *get_object(PIG_engine *pe) |
||
1030 | { |
||
1031 | PIG_object *po; |
||
1032 | if(pe->object_pool) |
||
1033 | { |
||
1034 | po = pe->object_pool; |
||
1035 | pe->object_pool = po->next; |
||
1036 | memset(po, 0, sizeof(PIG_object)); |
||
1037 | } |
||
1038 | else |
||
1039 | { |
||
1040 | po = (PIG_object *)calloc(1, sizeof(PIG_object)); |
||
1041 | if(!po) |
||
1042 | return NULL; |
||
1043 | } |
||
1044 | po->id = ++pe->object_id_counter; |
||
1045 | return po; |
||
1046 | } |
||
1047 | |||
1048 | |||
1049 | static void free_object(PIG_object *po) |
||
1050 | { |
||
1051 | po->prev = NULL; |
||
1052 | po->next = po->owner->object_pool; |
||
1053 | po->owner->object_pool = po; |
||
1054 | po->id = 0; |
||
1055 | } |
||
1056 | |||
1057 | |||
1058 | PIG_object *pig_object_open(PIG_engine *pe, int x, int y, int last) |
||
1059 | { |
||
1060 | PIG_object *po = get_object(pe); |
||
1061 | if(!po) |
||
1062 | return NULL; |
||
1063 | |||
1064 | po->owner = pe; |
||
1065 | po->tilemask = PIG_ALL; |
||
1066 | po->hitmask = 0; |
||
1067 | po->hitgroup = 0; |
||
1068 | |||
1069 | if(last && pe->objects) |
||
1070 | { |
||
1071 | PIG_object *lo = pe->objects; |
||
1072 | while(lo->next) |
||
1073 | lo = lo->next; |
||
1074 | po->prev = lo; |
||
1075 | po->next = NULL; |
||
1076 | lo->next = po; |
||
1077 | } |
||
1078 | else |
||
1079 | { |
||
1080 | po->prev = NULL; |
||
1081 | po->next = pe->objects; |
||
1082 | if(po->next) |
||
1083 | po->next->prev = po; |
||
1084 | pe->objects = po; |
||
1085 | } |
||
1086 | |||
1087 | po->x = x; |
||
1088 | po->y = y; |
||
1089 | po->ip.ox = x; |
||
1090 | po->ip.oy = y; |
||
1091 | return po; |
||
1092 | } |
||
1093 | |||
1094 | |||
1095 | static void close_object(PIG_object *po) |
||
1096 | { |
||
1097 | if(po == po->owner->objects) |
||
1098 | po->owner->objects = po->next; |
||
1099 | else if(po->prev) |
||
1100 | po->prev->next = po->next; |
||
1101 | if(po->next) |
||
1102 | po->next->prev = po->prev; |
||
1103 | free_object(po); |
||
1104 | } |
||
1105 | |||
1106 | |||
1107 | void pig_object_close(PIG_object *po) |
||
1108 | { |
||
1109 | if(!po->id) |
||
1110 | fprintf(stderr, "Object %p closed more than once!\n", po); |
||
1111 | po->id = 0; /* Mark for eventual removal and destruction */ |
||
1112 | } |
||
1113 | |||
1114 | |||
1115 | void pig_object_close_all(PIG_engine *pe) |
||
1116 | { |
||
1117 | while(pe->objects) |
||
1118 | close_object(pe->objects); |
||
1119 | } |
||
1120 | |||
1121 | |||
1122 | PIG_object *pig_object_find(PIG_object *start, int id) |
||
1123 | { |
||
1124 | PIG_object *pob, *pof; |
||
1125 | if(start) |
||
1126 | pob = pof = start; |
||
1127 | else |
||
1128 | { |
||
1129 | pof = start->owner->objects; |
||
1130 | while(pof) |
||
1131 | { |
||
1132 | if(pof->id == id) |
||
1133 | return pof; |
||
1134 | pof = pof->next; |
||
1135 | } |
||
1136 | return NULL; |
||
1137 | } |
||
1138 | while(1) |
||
1139 | { |
||
1140 | if(pob) |
||
1141 | { |
||
1142 | if(pob->id == id) |
||
1143 | return pob; |
||
1144 | pob = pob->prev; |
||
1145 | } |
||
1146 | if(pof) |
||
1147 | { |
||
1148 | if(pof->id == id) |
||
1149 | return pof; |
||
1150 | pof = pof->next; |
||
1151 | } |
||
1152 | else |
||
1153 | if(!pob) |
||
1154 | return NULL; |
||
1155 | } |
||
1156 | }>>>>>>>>>>>=>=>>=>>>>>>>>>>><>><>><>><>>>>><>><>>><>><>>>>>=>=>> |