1 /* Sarien - A Sierra AGI resource interpreter engine
2 * Copyright (C) 1999-2001 Stuart George and Claudio Matsuoka
3 *
4 * $Id: sprite.c,v 1.48 2001/11/03 20:16:10 cmatsuoka Exp $
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; see docs/COPYING for further details.
9 */
10
11 #include <assert.h>
12 #include <string.h> /* for memcpy() */
13 #include "sarien.h"
14 #include "list.h"
15 #include "agi.h"
16 #include "sprite.h"
17 #include "graphics.h"
18 #include "text.h"
19 #include "savegame.h"
20
21 /**
22 * Sprite structure.
23 * This structure holds information on visible and priority data of
24 * a rectangular area of the AGI screen. Sprites are chained in two
25 * circular lists, one for updating and other for non-updating sprites.
26 */
27 struct sprite {
28 struct list_head list;
29 struct vt_entry *v; /**< pointer to view table entry */
30 SINT16 x_pos; /**< x coordinate of the sprite */
31 SINT16 y_pos; /**< y coordinate of the sprite */
32 SINT16 x_size; /**< width of the sprite */
33 SINT16 y_size; /**< height of the sprite */
34 UINT8 *buffer; /**< buffer to store background data */
35 #ifdef USE_HIRES
36 UINT8 *hires; /**< buffer for hi-res background */
37 #endif
38 };
39
40
41 /*
42 * Blitter functions
43 */
44
45 #ifdef USE_HIRES
46
blit_hires_cel(int x,int y,int spr,struct view_cel * c)47 static int blit_hires_cel (int x, int y, int spr, struct view_cel *c)
48 {
49 UINT8 *q = NULL;
50 UINT8 *h0, *h;
51 int i, j, t;
52 int epr, pr; /* effective and real priorities */
53 int hidden = TRUE;
54
55 h0 = &game.hires[(x + y * _WIDTH) * 2];
56 q = c->data;
57 t = c->transparency;
58 spr <<= 4;
59
60 for (i = 0; i < c->height; i++) {
61 h = h0;
62 for (j = c->width * 2; j; j--, h++) {
63 /* Check if we're on a control line */
64 if ((pr = *h & 0xf0) < 0x30) {
65 UINT8 *p1;
66 /* Yes, get effective priority going down */
67 for (p1 = h; (epr = *p1 & 0xf0) < 0x30; p1 += _WIDTH * 2) {
68 if (p1 >= game.sbuf + _WIDTH * _HEIGHT) {
69 epr = 0x40;
70 break;
71 }
72 }
73 } else {
74 epr = pr;
75 }
76 if (*q != t && spr >= epr) {
77 /* Keep control line information visible,
78 * but put our priority over water (0x30)
79 * surface
80 */
81 *h = (pr < 0x30 ? pr : spr) | *q;
82 hidden = FALSE;
83 }
84 q += (j & 1);
85 }
86 h0 += _WIDTH * 2;
87 }
88
89 return hidden;
90 }
91
92 #endif
93
blit_cel(int x,int y,int spr,struct view_cel * c)94 static int blit_cel (int x, int y, int spr, struct view_cel *c)
95 {
96 UINT8 *p0, *p, *q = NULL;
97 int i, j, t;
98 int epr, pr; /* effective and real priorities */
99 int hidden = TRUE;
100
101 /* Fixes bug #477841 (crash in PQ1 map C4 when y == -2) */
102 if (y < 0) y = 0;
103 if (x < 0) x = 0;
104 if (y >= _HEIGHT) y = _HEIGHT - 1;
105 if (x >= _WIDTH) x = _WIDTH - 1;
106
107 #ifdef USE_HIRES
108 if (opt.hires)
109 blit_hires_cel (x, y, spr, c);
110 #endif
111
112 p0 = &game.sbuf[x + y * _WIDTH];
113 q = c->data;
114 t = c->transparency;
115 spr <<= 4;
116
117 for (i = 0; i < c->height; i++) {
118 p = p0;
119 for (j = c->width; j; j--, p++, q++) {
120 /* Check if we're on a control line */
121 if ((pr = *p & 0xf0) < 0x30) {
122 UINT8 *p1;
123 /* Yes, get effective priority going down */
124 for (p1 = p; (epr = *p1 & 0xf0) < 0x30; p1 += _WIDTH) {
125 if (p1 >= game.sbuf + _WIDTH * _HEIGHT) {
126 epr = 0x40;
127 break;
128 }
129 }
130 } else {
131 epr = pr;
132 }
133 if (*q != t && spr >= epr) {
134 /* Keep control line information visible,
135 * but put our priority over water (0x30)
136 * surface
137 */
138 *p = (pr < 0x30 ? pr : spr) | *q;
139 hidden = FALSE;
140 }
141 }
142 p0 += _WIDTH;
143 }
144
145 return hidden;
146 }
147
objs_savearea(struct sprite * s)148 static void objs_savearea (struct sprite *s)
149 {
150 int y;
151 SINT16 x_pos = s->x_pos, y_pos = s->y_pos;
152 SINT16 x_size = s->x_size, y_size = s->y_size;
153 UINT8 *p0, *q;
154 #ifdef USE_HIRES
155 UINT8 *h0, *k;
156 #endif
157
158 if (x_pos + x_size > _WIDTH)
159 x_size = _WIDTH-x_pos;
160
161 if (x_pos < 0) {
162 x_size += x_pos;
163 x_pos = 0;
164 }
165
166 if (y_pos + y_size > _HEIGHT)
167 y_size = _HEIGHT-y_pos;
168
169 if (y_pos < 0) {
170 y_size += y_pos;
171 y_pos = 0;
172 }
173
174 if (x_size <= 0 || y_size <= 0)
175 return;
176
177 p0 = &game.sbuf[x_pos + y_pos * _WIDTH];
178 q = s->buffer;
179 #ifdef USE_HIRES
180 h0 = &game.hires[(x_pos + y_pos * _WIDTH) * 2];
181 k = s->hires;
182 #endif
183 for (y = 0; y < y_size; y++) {
184 memcpy (q, p0, x_size);
185 q += x_size;
186 p0 += _WIDTH;
187 #ifdef USE_HIRES
188 memcpy (k, h0, x_size * 2);
189 k += x_size * 2;
190 h0 += _WIDTH * 2;
191 #endif
192 }
193 }
194
objs_restorearea(struct sprite * s)195 static void objs_restorearea (struct sprite *s)
196 {
197 int y, offset;
198 SINT16 x_pos = s->x_pos, y_pos = s->y_pos;
199 SINT16 x_size = s->x_size, y_size = s->y_size;
200 UINT8 *p0, *q;
201 #ifdef USE_HIRES
202 UINT8 *h0, *k;
203 #endif
204
205 if (x_pos + x_size > _WIDTH)
206 x_size = _WIDTH-x_pos;
207
208 if (x_pos < 0) {
209 x_size += x_pos;
210 x_pos = 0;
211 }
212
213 if (y_pos + y_size > _HEIGHT)
214 y_size = _HEIGHT-y_pos;
215
216 if (y_pos < 0) {
217 y_size += y_pos;
218 y_pos = 0;
219 }
220
221 if (x_size <= 0 || y_size <= 0)
222 return;
223
224 p0 = &game.sbuf[x_pos + y_pos * _WIDTH];
225 q = s->buffer;
226 #ifdef USE_HIRES
227 h0 = &game.hires[(x_pos + y_pos * _WIDTH) * 2];
228 k = s->hires;
229 #endif
230 offset = game.line_min_print * CHAR_LINES;
231 for (y = 0; y < y_size; y++) {
232 memcpy (p0, q, x_size);
233 put_pixels_a (x_pos, y_pos + y + offset, x_size, p0);
234 q += x_size;
235 p0 += _WIDTH;
236 #ifdef USE_HIRES
237 memcpy (h0, k, x_size * 2);
238 if (opt.hires) {
239 put_pixels_hires (x_pos * 2,
240 y_pos + y + offset, x_size * 2, h0);
241 }
242 k += x_size * 2;
243 h0 += _WIDTH * 2;
244 #endif
245 }
246 }
247
248
249 /*
250 * Sprite management functions
251 */
252
253 static LIST_HEAD(spr_upd_head);
254 static LIST_HEAD(spr_nonupd_head);
255
256 /**
257 * Condition to determine whether a sprite will be in the 'updating' list.
258 */
test_updating(struct vt_entry * v)259 static int test_updating (struct vt_entry *v)
260 {
261 return (v->flags & (ANIMATED|UPDATE|DRAWN)) ==
262 (ANIMATED|UPDATE|DRAWN);
263 }
264
265 /**
266 * Condition to determine whether a sprite will be in the 'non-updating' list.
267 */
test_not_updating(struct vt_entry * v)268 static int test_not_updating (struct vt_entry *v)
269 {
270 return (v->flags & (ANIMATED|UPDATE|DRAWN)) == (ANIMATED|DRAWN);
271 }
272
273 /**
274 * Convert sprite priority to y value.
275 */
prio_to_y(int p)276 static INLINE int prio_to_y (int p)
277 {
278 int i;
279
280 if (game.alt_pri) { /* set.pri.base used */
281 if (p == 0)
282 return -1;
283 for (i = 168; i; i--) {
284 if (game.pri_table[i] < p)
285 return i;
286 }
287 }
288
289 return (p - 5) * 12 + 48;
290 }
291
292 /**
293 * Create and initialize a new sprite structure.
294 */
new_sprite(struct vt_entry * v)295 static struct sprite *new_sprite (struct vt_entry *v)
296 {
297 struct sprite *s;
298
299 s = malloc (sizeof (struct sprite));
300 if (s == NULL)
301 abort ();
302 s->v = v; /* link sprite to associated view table entry */
303 s->x_pos = v->x_pos;
304 s->y_pos = v->y_pos - v->y_size + 1;
305 s->x_size = v->x_size;
306 s->y_size = v->y_size;
307 s->buffer = malloc (s->x_size * s->y_size);
308 #ifdef USE_HIRES
309 s->hires = malloc (s->x_size * s->y_size * 2);
310 #endif
311 v->s = s; /* link view table entry to this sprite */
312
313 return s;
314 }
315
316 /**
317 * Insert sprite in the specified sprite list.
318 */
spr_addlist(struct list_head * head,struct vt_entry * v)319 static void spr_addlist (struct list_head *head, struct vt_entry *v)
320 {
321 struct sprite *s;
322
323 s = new_sprite (v);
324 list_add_tail (&s->list, head);
325 }
326
327 /**
328 * Sort sprites from lower y values to build a sprite list.
329 */
330 static struct list_head *
build_list(struct list_head * head,int (* test)(struct vt_entry *))331 build_list (struct list_head *head, int (*test)(struct vt_entry *))
332 {
333 int i, j, k;
334 struct vt_entry *v;
335 struct vt_entry *entry[0x100];
336 int y_val[0x100];
337 int min_y = 0xff, min_index = 0;
338
339 /* fill the arrays with all sprites that satisfy the 'test'
340 * condition and their y values
341 */
342 i = 0;
343 for_each_vt_entry(v) {
344 if (test (v)) {
345 entry[i] = v;
346 y_val[i] = v->flags & UPDATE ? v->y_pos :
347 prio_to_y (v->priority);
348 i++;
349 }
350 }
351
352 /* now look for the smallest y value in the array and put that
353 * sprite in the list
354 */
355 for (j = 0; j < i; j++) {
356 min_y = 0xff;
357 for (k = 0; k < i; k++) {
358 if (y_val[k] <= min_y) {
359 min_index = k;
360 min_y = y_val[k];
361 }
362 }
363
364 y_val[min_index] = 0xff;
365 spr_addlist (head, entry[min_index]);
366 }
367
368 return head;
369 }
370
371 /**
372 * Build list of updating sprites.
373 */
build_upd_blitlist()374 static struct list_head *build_upd_blitlist ()
375 {
376 return build_list (&spr_upd_head, test_updating);
377 }
378
379 /**
380 * Build list of non-updating sprites.
381 */
build_nonupd_blitlist()382 static struct list_head *build_nonupd_blitlist ()
383 {
384 return build_list (&spr_nonupd_head, test_not_updating);
385 }
386
387 /**
388 * Clear the given sprite list.
389 */
free_list(struct list_head * head)390 static void free_list (struct list_head *head)
391 {
392 struct list_head *h;
393 struct sprite *s;
394 struct sprite *doomed = NULL;
395
396 list_for_each (h, head, next) {
397 s = list_entry (h, struct sprite, list);
398 list_del (h);
399 free (s->buffer);
400 #ifdef USE_HIRES
401 free (s->hires);
402 #endif
403 /*
404 * if (h->prev != head)
405 * free (list_entry (h->prev, struct sprite, list));
406 *
407 * Why not just "free(s);"? The problem is that sprite
408 * structure contains list iterator so it cannot be deleted
409 * during iteration (typical mistake of C++ beginners
410 * trying to use STL). So, marked lines have some reasoning.
411 * The idea is that previous element gets free'ed, not the
412 * current one. Now, the bug is obvious -- who kills the
413 * last element? My proposed fix is:
414 */
415
416 if (doomed)
417 free(doomed);
418 doomed = s;
419 }
420 if (doomed)
421 free (doomed);
422 }
423
424 /**
425 * Copy sprites from the pic buffer to the screen buffer, and check if
426 * sprites of the given list have moved.
427 */
commit_sprites(struct list_head * head)428 static void commit_sprites (struct list_head *head)
429 {
430 struct list_head *h;
431
432 list_for_each (h, head, next) {
433 struct sprite *s = list_entry (h, struct sprite, list);
434 int x1, y1, x2, y2, w, h;
435
436 w = (s->v->cel_data->width > s->v->cel_data_2->width) ?
437 s->v->cel_data->width : s->v->cel_data_2->width;
438
439 h = (s->v->cel_data->height > s->v->cel_data_2->height) ?
440 s->v->cel_data->height : s->v->cel_data_2->height;
441
442 s->v->cel_data_2 = s->v->cel_data;
443
444 if (s->v->x_pos < s->v->x_pos2) {
445 x1 = s->v->x_pos;
446 x2 = s->v->x_pos2 + w - 1;
447 } else {
448 x1 = s->v->x_pos2;
449 x2 = s->v->x_pos + w - 1;
450 }
451
452 if (s->v->y_pos < s->v->y_pos2) {
453 y1 = s->v->y_pos - h + 1;
454 y2 = s->v->y_pos2;
455 } else {
456 y1 = s->v->y_pos2 - h + 1;
457 y2 = s->v->y_pos;
458 }
459
460 commit_block (x1, y1, x2, y2);
461
462 if (s->v->step_time_count != s->v->step_time)
463 continue;
464
465 if (s->v->x_pos == s->v->x_pos2 && s->v->y_pos == s->v->y_pos2){
466 s->v->flags |= DIDNT_MOVE;
467 continue;
468 }
469
470 s->v->x_pos2 = s->v->x_pos;
471 s->v->y_pos2 = s->v->y_pos;
472 s->v->flags &= ~DIDNT_MOVE;
473 }
474
475 #ifdef USE_CONSOLE
476 if (debug.statusline)
477 write_status ();
478 #endif
479 }
480
481 /**
482 * Erase all sprites in the given list.
483 */
erase_sprites(struct list_head * head)484 static void erase_sprites (struct list_head *head)
485 {
486 struct list_head *h;
487
488 list_for_each (h, head, prev) {
489 struct sprite *s = list_entry (h, struct sprite, list);
490 objs_restorearea (s);
491 }
492
493 free_list (head);
494 }
495
496 /**
497 * Blit all sprites in the given list.
498 */
blit_sprites(struct list_head * head)499 static void blit_sprites (struct list_head *head)
500 {
501 struct list_head *h = NULL;
502 int hidden;
503
504 list_for_each (h, head, next) {
505 struct sprite *s = list_entry (h, struct sprite, list);
506 objs_savearea (s);
507 hidden = blit_cel (s->x_pos, s->y_pos, s->v->priority,
508 s->v->cel_data);
509 if (s->v->entry == 0) { /* if ego, update f1 */
510 setflag (F_ego_invisible, hidden);
511 }
512 }
513 }
514
515
516 /*
517 * Public functions
518 */
519
520
commit_upd_sprites()521 void commit_upd_sprites ()
522 {
523 commit_sprites (&spr_upd_head);
524 }
525
commit_nonupd_sprites()526 void commit_nonupd_sprites ()
527 {
528 commit_sprites (&spr_nonupd_head);
529 }
530
531 /* check moves in both lists */
commit_both()532 void commit_both ()
533 {
534 commit_upd_sprites ();
535 commit_nonupd_sprites ();
536 }
537
538 /**
539 * Erase updating sprites.
540 * This function follows the list of all updating sprites and restores
541 * the visible and priority data of their background buffers back to
542 * the AGI screen.
543 *
544 * @see erase_nonupd_sprites()
545 * @see erase_both()
546 */
erase_upd_sprites()547 void erase_upd_sprites ()
548 {
549 erase_sprites (&spr_upd_head);
550 }
551
552 /**
553 * Erase non-updating sprites.
554 * This function follows the list of all non-updating sprites and restores
555 * the visible and priority data of their background buffers back to
556 * the AGI screen.
557 *
558 * @see erase_upd_sprites()
559 * @see erase_both()
560 */
erase_nonupd_sprites()561 void erase_nonupd_sprites ()
562 {
563 erase_sprites (&spr_nonupd_head);
564 }
565
566 /**
567 * Erase all sprites.
568 * This function follows the lists of all updating and non-updating
569 * sprites and restores the visible and priority data of their background
570 * buffers back to the AGI screen.
571 *
572 * @see erase_upd_sprites()
573 * @see erase_nonupd_sprites()
574 */
erase_both()575 void erase_both ()
576 {
577 erase_upd_sprites ();
578 erase_nonupd_sprites ();
579 }
580
581 /**
582 * Blit updating sprites.
583 * This function follows the list of all updating sprites and blits
584 * them on the AGI screen.
585 *
586 * @see blit_nonupd_sprites()
587 * @see blit_both()
588 */
blit_upd_sprites()589 void blit_upd_sprites ()
590 {
591 blit_sprites (build_upd_blitlist ());
592 }
593
594 /**
595 * Blit updating sprites.
596 * This function follows the list of all non-updating sprites and blits
597 * them on the AGI screen.
598 *
599 * @see blit_upd_sprites()
600 * @see blit_both()
601 */
blit_nonupd_sprites()602 void blit_nonupd_sprites ()
603 {
604 blit_sprites (build_nonupd_blitlist ());
605 }
606
607 /**
608 * Blit all sprites.
609 * This function follows the lists of all updating and non-updating
610 * sprites and blits them on the AGI screen.
611 *
612 * @see blit_upd_sprites()
613 * @see blit_nonupd_sprites()
614 */
blit_both()615 void blit_both ()
616 {
617 blit_nonupd_sprites ();
618 blit_upd_sprites ();
619 }
620
621 /**
622 * Add view to picture.
623 * This function is used to implement the add.to.pic AGI command. It
624 * copies the specified cel from a view resource on the current picture.
625 * This cel is not a sprite, it can't be moved or removed.
626 * @param view number of view resource
627 * @param loop number of loop in the specified view resource
628 * @param cel number of cel in the specified loop
629 * @param x x coordinate to place the view
630 * @param y y coordinate to place the view
631 * @param pri priority to use
632 * @param mar if < 4, create a margin around the the base of the cel
633 */
add_to_pic(int view,int loop,int cel,int x,int y,int pri,int mar)634 void add_to_pic (int view, int loop, int cel, int x, int y, int pri, int mar)
635 {
636 struct view_cel *c = NULL;
637 int x1, y1, x2, y2, y3;
638 UINT8 *p1, *p2;
639
640 _D ("v=%d, l=%d, c=%d, x=%d, y=%d, p=%d, m=%d",
641 view, loop, cel, x, y, pri, mar);
642
643 record_image_stack_call(ADD_VIEW, view, loop, cel, x, y, pri, mar);
644
645 if (pri == 0)
646 pri = 8; /* ??!? */
647
648 c = &game.views[view].loop[loop].cel[cel];
649
650 x1 = x;
651 y1 = y - c->height + 1;
652 x2 = x + c->width - 1;
653 y2 = y;
654
655 if (x1 < 0) {
656 x2 -= x1;
657 x1 = 0;
658 }
659 if (y1 < 0) {
660 y2 -= y1;
661 y1 = 0;
662 }
663 if (x2 >= _WIDTH) x2 = _WIDTH - 1;
664 if (y2 >= _HEIGHT) y2 = _HEIGHT - 1;
665
666 erase_both ();
667
668 _D (_D_WARN "blit_cel (%d, %d, %d, c)", x, y, pri);
669 blit_cel (x1, y1, pri, c);
670
671 /* If margin is 0, 1, 2, or 3, the base of the cel is
672 * surrounded with a rectangle of the corresponding priority.
673 * If margin >= 4, this extra margin is not shown.
674 */
675 if (mar < 4) {
676 /* add rectangle around object, don't clobber control
677 * info in priority data. The box extends to the end of
678 * its priority band!
679 */
680 y3 = (y2 / 12) * 12;
681
682 p1 = &game.sbuf[x1 + y3 * _WIDTH];
683 p2 = &game.sbuf[x2 + y3 * _WIDTH];
684
685 for (y = y3; y <= y2; y++) {
686 if ((*p1 >> 4) >= 4) *p1 = (mar << 4) | (*p1 & 0x0f);
687 if ((*p2 >> 4) >= 4) *p2 = (mar << 4) | (*p2 & 0x0f);
688 p1 += _WIDTH;
689 p2 += _WIDTH;
690 }
691
692 _D (_D_WARN "pri box: %d %d %d %d (%d)", x1, y3, x2, y2, mar);
693 p1 = &game.sbuf[x1 + y3 * _WIDTH];
694 p2 = &game.sbuf[x1 + y2 * _WIDTH];
695 for (x = x1; x <= x2; x++) {
696 if ((*p1 >> 4) >= 4) *p1 = (mar << 4) | (*p1 & 0x0f);
697 if ((*p2 >> 4) >= 4) *p2 = (mar << 4) | (*p2 & 0x0f);
698 p1++;
699 p2++;
700 }
701 }
702
703 blit_both ();
704
705 _D (_D_WARN "commit_block (%d, %d, %d, %d)", x1, y1, x2, y2);
706 commit_block (x1, y1, x2, y2);
707 }
708
709 /**
710 * Show object and description
711 * This function shows an object from the player's inventory, displaying
712 * a message box with the object description.
713 * @param n Number of the object to show
714 */
show_obj(n)715 void show_obj (n)
716 {
717 struct view_cel *c;
718 struct sprite s;
719 int x1, y1, x2, y2;
720
721 agi_load_resource (rVIEW, n);
722 if (!(c = &game.views[n].loop[0].cel[0]))
723 return;
724
725 x1 = (_WIDTH - c->width) / 2;
726 y1 = 120;
727 x2 = x1 + c->width - 1;
728 y2 = y1 + c->height - 1;
729
730 s.x_pos = x1;
731 s.y_pos = y1;
732 s.x_size = c->width;
733 s.y_size = c->height;
734 s.buffer = malloc (s.x_size * s.y_size);
735 #ifdef USE_HIRES
736 s.hires = malloc (s.x_size * s.y_size * 2);
737 #endif
738
739 objs_savearea (&s);
740 blit_cel (x1, y1, s.x_size, c);
741 commit_block (x1, y1, x2, y2);
742 message_box (game.views[n].descr);
743 objs_restorearea (&s);
744 commit_block (x1, y1, x2, y2);
745
746 free (s.buffer);
747
748 /* Added to fix a memory leak --Vasyl */
749 #ifdef USE_HIRES
750 free(s.hires);
751 #endif
752 }
753
commit_block(int x1,int y1,int x2,int y2)754 void commit_block (int x1, int y1, int x2, int y2)
755 {
756 int i, w, offset;
757 UINT8 *q;
758 #ifdef USE_HIRES
759 UINT8 *h;
760 #endif
761
762 if (!game.picture_shown)
763 return;
764
765 /* Clipping */
766 if (x1 < 0) x1 = 0;
767 if (x2 < 0) x2 = 0;
768 if (y1 < 0) y1 = 0;
769 if (y2 < 0) y2 = 0;
770 if (x1 >= _WIDTH) x1 = _WIDTH - 1;
771 if (x2 >= _WIDTH) x2 = _WIDTH - 1;
772 if (y1 >= _HEIGHT) y1 = _HEIGHT - 1;
773 if (y2 >= _HEIGHT) y2 = _HEIGHT - 1;
774
775 /* _D ("%d, %d, %d, %d", x1, y1, x2, y2); */
776
777 w = x2 - x1 + 1;
778 q = &game.sbuf[x1 + _WIDTH * y1];
779 #ifdef USE_HIRES
780 h = &game.hires[(x1 + _WIDTH * y1) * 2];
781 #endif
782 offset = game.line_min_print * CHAR_LINES;
783 for (i = y1; i <= y2; i++) {
784 put_pixels_a (x1, i + offset, w, q);
785 q += _WIDTH;
786 #ifdef USE_HIRES
787 if (opt.hires) {
788 put_pixels_hires (x1 * 2, i + offset, w * 2, h);
789 }
790 h += _WIDTH * 2;
791 #endif
792 }
793
794 flush_block_a (x1, y1 + offset, x2, y2 + offset);
795 }
796
797 /* end: sprite.c */
798