1 /*
2 * xrick/src/ents.c
3 *
4 * Copyright (C) 1998-2002 BigOrno (bigorno@bigorno.net). All rights reserved.
5 *
6 * The use and distribution terms for this software are contained in the file
7 * named README, which can be found in the root of this distribution. By
8 * using this software in any fashion, you are agreeing to be bound by the
9 * terms of this license.
10 *
11 * You must not remove this notice, or any other, from this software.
12 */
13
14 #include <stdlib.h>
15
16 #include "system.h"
17 #include "config.h"
18 #include "game.h"
19 #include "ents.h"
20 #include "debug.h"
21
22 #include "e_bullet.h"
23 #include "e_bomb.h"
24 #include "e_rick.h"
25 #include "e_them.h"
26 #include "e_bonus.h"
27 #include "e_box.h"
28 #include "e_sbonus.h"
29 #include "rects.h"
30 #include "maps.h"
31 #include "draw.h"
32
33 /*
34 * global vars
35 */
36 ent_t ent_ents[ENT_ENTSNUM + 1];
37 rect_t *ent_rects = NULL;
38
39
40 /*
41 * prototypes
42 */
43 static void ent_addrect(S16, S16, U16, U16);
44 static U8 ent_creat1(U8 *);
45 static U8 ent_creat2(U8 *, U16);
46
47
48 /*
49 * Reset entities
50 *
51 * ASM 2520
52 */
53 void
ent_reset(void)54 ent_reset(void)
55 {
56 U8 i;
57
58 E_RICK_STRST(E_RICK_STSTOP);
59 e_bomb_lethal = FALSE;
60
61 ent_ents[0].n = 0;
62 for (i = 2; ent_ents[i].n != 0xff; i++)
63 ent_ents[i].n = 0;
64 }
65
66
67 /*
68 * Create an entity on slots 4 to 8 by using the first slot available.
69 * Entities of type e_them on slots 4 to 8, when lethal, can kill
70 * other e_them (on slots 4 to C) as well as rick.
71 *
72 * ASM 209C
73 *
74 * e: anything, CHANGED to the allocated entity number.
75 * return: TRUE/OK FALSE/not
76 */
77 static U8
ent_creat1(U8 * e)78 ent_creat1(U8 *e)
79 {
80 /* look for a slot */
81 for (*e = 0x04; *e < 0x09; (*e)++)
82 if (ent_ents[*e].n == 0) { /* if slot available, use it */
83 ent_ents[*e].c1 = 0;
84 return TRUE;
85 }
86
87 return FALSE;
88 }
89
90
91 /*
92 * Create an entity on slots 9 to C by using the first slot available.
93 * Entities of type e_them on slots 9 to C can kill rick when lethal,
94 * but they can never kill other e_them.
95 *
96 * ASM 20BC
97 *
98 * e: anything, CHANGED to the allocated entity number.
99 * m: number of the mark triggering the creation of the entity.
100 * ret: TRUE/OK FALSE/not
101 */
102 static U8
ent_creat2(U8 * e,U16 m)103 ent_creat2(U8 *e, U16 m)
104 {
105 /* make sure the entity created by this mark is not active already */
106 for (*e = 0x09; *e < 0x0c; (*e)++)
107 if (ent_ents[*e].n != 0 && ent_ents[*e].mark == m)
108 return FALSE;
109
110 /* look for a slot */
111 for (*e = 0x09; *e < 0x0c; (*e)++)
112 if (ent_ents[*e].n == 0) { /* if slot available, use it */
113 ent_ents[*e].c1 = 2;
114 return TRUE;
115 }
116
117 return FALSE;
118 }
119
120
121 /*
122 * Process marks that are within the visible portion of the map,
123 * and create the corresponding entities.
124 *
125 * absolute map coordinate means that they are not relative to
126 * map_frow, as any other coordinates are.
127 *
128 * ASM 1F40
129 *
130 * frow: first visible row of the map -- absolute map coordinate
131 * lrow: last visible row of the map -- absolute map coordinate
132 */
133 void
ent_actvis(U8 frow,U8 lrow)134 ent_actvis(U8 frow, U8 lrow)
135 {
136 U16 m;
137 U8 e;
138 U16 y;
139
140 /*
141 * go through the list and find the first mark that
142 * is visible, i.e. which has a row greater than the
143 * first row (marks being ordered by row number).
144 */
145 for (m = map_submaps[game_submap].mark;
146 map_marks[m].row != 0xff && map_marks[m].row < frow;
147 m++);
148
149 if (map_marks[m].row == 0xff) /* none found */
150 return;
151
152 /*
153 * go through the list and process all marks that are
154 * visible, i.e. which have a row lower than the last
155 * row (marks still being ordered by row number).
156 */
157 for (;
158 map_marks[m].row != 0xff && map_marks[m].row < lrow;
159 m++) {
160
161 /* ignore marks that are not active */
162 if (map_marks[m].ent & MAP_MARK_NACT)
163 continue;
164
165 /*
166 * allocate a slot to the new entity
167 *
168 * slot type
169 * 0 available for e_them (lethal to other e_them, and stops entities
170 * i.e. entities can't move over them. E.g. moving blocks. But they
171 * can move over entities and kill them!).
172 * 1 xrick
173 * 2 bullet
174 * 3 bomb
175 * 4-8 available for e_them, e_box, e_bonus or e_sbonus (lethal to
176 * other e_them, identified by their number being >= 0x10)
177 * 9-C available for e_them, e_box, e_bonus or e_sbonus (not lethal to
178 * other e_them, identified by their number being < 0x10)
179 *
180 * the type of an entity is determined by its .n as detailed below.
181 *
182 * 1 xrick
183 * 2 bullet
184 * 3 bomb
185 * 4, 7, a, d e_them, type 1a
186 * 5, 8, b, e e_them, type 1b
187 * 6, 9, c, f e_them, type 2
188 * 10, 11 box
189 * 12, 13, 14, 15 bonus
190 * 16, 17 speed bonus
191 * >17 e_them, type 3
192 * 47 zombie
193 */
194
195 if (!(map_marks[m].flags & ENT_FLG_STOPRICK)) {
196 if (map_marks[m].ent >= 0x10) {
197 /* boxes, bonuses and type 3 e_them go to slot 4-8 */
198 /* (c1 set to 0 -> all type 3 e_them are sleeping) */
199 if (!ent_creat1(&e)) continue;
200 }
201 else {
202 /* type 1 and 2 e_them go to slot 9-c */
203 /* (c1 set to 2) */
204 if (!ent_creat2(&e, m)) continue;
205 }
206 }
207 else {
208 /* entities stopping rick (e.g. blocks) go to slot 0 */
209 if (ent_ents[0].n) continue;
210 e = 0;
211 ent_ents[0].c1 = 0;
212 }
213
214 /*
215 * initialize the entity
216 */
217 ent_ents[e].mark = m;
218 ent_ents[e].flags = map_marks[m].flags;
219 ent_ents[e].n = map_marks[m].ent;
220
221 /*
222 * if entity is to be already running (i.e. not asleep and waiting
223 * for some trigger to move), then use LETHALR i.e. restart flag, right
224 * from the beginning
225 */
226 if (ent_ents[e].flags & ENT_FLG_LETHALR)
227 ent_ents[e].n |= ENT_LETHAL;
228
229 ent_ents[e].x = map_marks[m].xy & 0xf8;
230
231 y = (map_marks[m].xy & 0x07) + (map_marks[m].row & 0xf8) - map_frow;
232 y <<= 3;
233 if (!(ent_ents[e].flags & ENT_FLG_STOPRICK))
234 y += 3;
235 ent_ents[e].y = y;
236
237 ent_ents[e].xsave = ent_ents[e].x;
238 ent_ents[e].ysave = ent_ents[e].y;
239
240 /*ent_ents[e].w0C = 0;*/ /* in ASM code but never used */
241
242 ent_ents[e].w = ent_entdata[map_marks[m].ent].w;
243 ent_ents[e].h = ent_entdata[map_marks[m].ent].h;
244 ent_ents[e].sprbase = ent_entdata[map_marks[m].ent].spr;
245 ent_ents[e].sprite = (U8)ent_entdata[map_marks[m].ent].spr;
246 ent_ents[e].step_no_i = ent_entdata[map_marks[m].ent].sni;
247 ent_ents[e].trigsnd = (U8)ent_entdata[map_marks[m].ent].snd;
248
249 /*
250 * FIXME what is this? when all trigger flags are up, then
251 * use .sni for sprbase. Why? What is the point? (This is
252 * for type 1 and 2 e_them, ...)
253 *
254 * This also means that as long as sprite has not been
255 * recalculated, a wrong value is used. This is normal, see
256 * what happens to the falling guy on the right on submap 3:
257 * it changes when hitting the ground.
258 */
259 #define ENT_FLG_TRIGGERS \
260 (ENT_FLG_TRIGBOMB|ENT_FLG_TRIGBULLET|ENT_FLG_TRIGSTOP|ENT_FLG_TRIGRICK)
261 if ((ent_ents[e].flags & ENT_FLG_TRIGGERS) == ENT_FLG_TRIGGERS
262 && e >= 0x09)
263 ent_ents[e].sprbase = (U8)(ent_entdata[map_marks[m].ent].sni & 0x00ff);
264 #undef ENT_FLG_TRIGGERS
265
266 ent_ents[e].trig_x = map_marks[m].lt & 0xf8;
267 ent_ents[e].latency = (map_marks[m].lt & 0x07) << 5; /* <<5 eq *32 */
268
269 ent_ents[e].trig_y = 3 + 8 * ((map_marks[m].row & 0xf8) - map_frow +
270 (map_marks[m].lt & 0x07));
271
272 ent_ents[e].c2 = 0;
273 ent_ents[e].offsy = 0;
274 ent_ents[e].ylow = 0;
275
276 ent_ents[e].front = FALSE;
277
278 }
279 }
280
281
282 /*
283 * Add a tile-aligned rectangle containing the given rectangle (indicated
284 * by its MAP coordinates) to the list of rectangles. Clip the rectangle
285 * so it fits into the display zone.
286 */
287 static void
ent_addrect(S16 x,S16 y,U16 width,U16 height)288 ent_addrect(S16 x, S16 y, U16 width, U16 height)
289 {
290 S16 x0, y0;
291 U16 w0, h0;
292
293 /*sys_printf("rect %#04x,%#04x %#04x %#04x ", x, y, width, height);*/
294
295 /* align to tiles */
296 x0 = x & 0xfff8;
297 y0 = y & 0xfff8;
298 w0 = width;
299 h0 = height;
300 if (x - x0) w0 = (w0 + (x - x0)) | 0x0007;
301 if (y - y0) h0 = (h0 + (y - y0)) | 0x0007;
302
303 /* clip */
304 if (draw_clipms(&x0, &y0, &w0, &h0)) { /* do not add if fully clipped */
305 /*sys_printf("-> [clipped]\n");*/
306 return;
307 }
308
309 /*sys_printf("-> %#04x,%#04x %#04x %#04x\n", x0, y0, w0, h0);*/
310
311 #ifdef GFXST
312 y0 += 8;
313 #endif
314
315 /* get to screen */
316 x0 -= DRAW_XYMAP_SCRLEFT;
317 y0 -= DRAW_XYMAP_SCRTOP;
318
319 /* add rectangle to the list */
320 ent_rects = rects_new(x0, y0, w0, h0, ent_rects);
321 }
322
323
324 /*
325 * Draw all entities onto the frame buffer.
326 *
327 * ASM 07a4
328 *
329 * NOTE This may need to be part of draw.c. Also needs better comments,
330 * NOTE and probably better rectangles management.
331 */
332 void
ent_draw(void)333 ent_draw(void)
334 {
335 U8 i;
336 #ifdef ENABLE_CHEATS
337 static U8 ch3 = FALSE;
338 #endif
339 S16 dx, dy;
340
341 draw_tilesBank = map_tilesBank;
342
343 /* reset rectangles list */
344 rects_free(ent_rects);
345 ent_rects = NULL;
346
347 /*sys_printf("\n");*/
348
349 /*
350 * background loop : erase all entities that were visible
351 */
352 for (i = 0; ent_ents[i].n != 0xff; i++) {
353 #ifdef ENABLE_CHEATS
354 if (ent_ents[i].prev_n && (ch3 || ent_ents[i].prev_s))
355 #else
356 if (ent_ents[i].prev_n && ent_ents[i].prev_s)
357 #endif
358 /* if entity was active, then erase it (redraw the map) */
359 draw_spriteBackground(ent_ents[i].prev_x, ent_ents[i].prev_y);
360 }
361
362 /*
363 * foreground loop : draw all entities that are visible
364 */
365 for (i = 0; ent_ents[i].n != 0xff; i++) {
366 /*
367 * If entity is active now, draw the sprite. If entity was
368 * not active before, add a rectangle for the sprite.
369 */
370 #ifdef ENABLE_CHEATS
371 if (ent_ents[i].n && (game_cheat3 || ent_ents[i].sprite))
372 #else
373 if (ent_ents[i].n && ent_ents[i].sprite)
374 #endif
375 /* If entitiy is active, draw the sprite. */
376 draw_sprite2(ent_ents[i].sprite,
377 ent_ents[i].x, ent_ents[i].y,
378 ent_ents[i].front);
379 }
380
381 /*
382 * rectangles loop : figure out which parts of the screen have been
383 * impacted and need to be refreshed, then save state
384 */
385 for (i = 0; ent_ents[i].n != 0xff; i++) {
386 #ifdef ENABLE_CHEATS
387 if (ent_ents[i].prev_n && (ch3 || ent_ents[i].prev_s)) {
388 #else
389 if (ent_ents[i].prev_n && ent_ents[i].prev_s) {
390 #endif
391 /* (1) if entity was active and has been drawn ... */
392 #ifdef ENABLE_CHEATS
393 if (ent_ents[i].n && (game_cheat3 || ent_ents[i].sprite)) {
394 #else
395 if (ent_ents[i].n && ent_ents[i].sprite) {
396 #endif
397 /* (1.1) ... and is still active now and still needs to be drawn, */
398 /* then check if rectangles intersect */
399 dx = abs(ent_ents[i].x - ent_ents[i].prev_x);
400 dy = abs(ent_ents[i].y - ent_ents[i].prev_y);
401 if (dx < 0x20 && dy < 0x16) {
402 /* (1.1.1) if they do, then create one rectangle */
403 ent_addrect((ent_ents[i].prev_x < ent_ents[i].x)
404 ? ent_ents[i].prev_x : ent_ents[i].x,
405 (ent_ents[i].prev_y < ent_ents[i].y)
406 ? ent_ents[i].prev_y : ent_ents[i].y,
407 dx + 0x20, dy + 0x15);
408 }
409 else {
410 /* (1.1.2) else, create two rectangles */
411 ent_addrect(ent_ents[i].x, ent_ents[i].y, 0x20, 0x15);
412 ent_addrect(ent_ents[i].prev_x, ent_ents[i].prev_y, 0x20, 0x15);
413 }
414 }
415 else
416 /* (1.2) ... and is not active anymore or does not need to be drawn */
417 /* then create one single rectangle */
418 ent_addrect(ent_ents[i].prev_x, ent_ents[i].prev_y, 0x20, 0x15);
419 }
420 #ifdef ENABLE_CHEATS
421 else if (ent_ents[i].n && (game_cheat3 || ent_ents[i].sprite)) {
422 #else
423 else if (ent_ents[i].n && ent_ents[i].sprite) {
424 #endif
425 /* (2) if entity is active and needs to be drawn, */
426 /* then create one rectangle */
427 ent_addrect(ent_ents[i].x, ent_ents[i].y, 0x20, 0x15);
428 }
429
430 /* save state */
431 ent_ents[i].prev_x = ent_ents[i].x;
432 ent_ents[i].prev_y = ent_ents[i].y;
433 ent_ents[i].prev_n = ent_ents[i].n;
434 ent_ents[i].prev_s = ent_ents[i].sprite;
435 }
436
437 #ifdef ENABLE_CHEATS
438 ch3 = game_cheat3;
439 #endif
440 }
441
442
443 /*
444 * Clear entities previous state
445 *
446 */
447 void
448 ent_clprev(void)
449 {
450 U8 i;
451
452 for (i = 0; ent_ents[i].n != 0xff; i++)
453 ent_ents[i].prev_n = 0;
454 }
455
456 /*
457 * Table containing entity action function pointers.
458 */
459 void (*ent_actf[])(U8) = {
460 NULL, /* 00 - zero means that the slot is free */
461 e_rick_action, /* 01 - 12CA */
462 e_bullet_action, /* 02 - 1883 */
463 e_bomb_action, /* 03 - 18CA */
464 e_them_t1a_action, /* 04 - 2452 */
465 e_them_t1b_action, /* 05 - 21CA */
466 e_them_t2_action, /* 06 - 2718 */
467 e_them_t1a_action, /* 07 - 2452 */
468 e_them_t1b_action, /* 08 - 21CA */
469 e_them_t2_action, /* 09 - 2718 */
470 e_them_t1a_action, /* 0A - 2452 */
471 e_them_t1b_action, /* 0B - 21CA */
472 e_them_t2_action, /* 0C - 2718 */
473 e_them_t1a_action, /* 0D - 2452 */
474 e_them_t1b_action, /* 0E - 21CA */
475 e_them_t2_action, /* 0F - 2718 */
476 e_box_action, /* 10 - 245A */
477 e_box_action, /* 11 - 245A */
478 e_bonus_action, /* 12 - 242C */
479 e_bonus_action, /* 13 - 242C */
480 e_bonus_action, /* 14 - 242C */
481 e_bonus_action, /* 15 - 242C */
482 e_sbonus_start, /* 16 - 2182 */
483 e_sbonus_stop /* 17 - 2143 */
484 };
485
486
487 /*
488 * Run entities action function
489 *
490 */
491 void
492 ent_action(void)
493 {
494 U8 i, k;
495
496 for (i = 0; ent_ents[i].n != 0xff; i++)
497 {
498 if (ent_ents[i].n)
499 {
500 sys_printf("xrick/ents: slot %#04x, entity %#04x", i, ent_ents[i].n);
501 sys_printf(" (%#06x, %#06x), sprite %#04x.\n",
502 ent_ents[i].x, ent_ents[i].y, ent_ents[i].sprite);
503 }
504 }
505
506 for (i = 0; ent_ents[i].n != 0xff; i++)
507 {
508 if (ent_ents[i].n)
509 {
510 k = ent_ents[i].n & 0x7f;
511 if (k == 0x47)
512 e_them_z_action(i);
513 else if (k >= 0x18)
514 e_them_t3_action(i);
515 else
516 ent_actf[k](i);
517 }
518 }
519 }
520
521
522 /* eof */
523