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