diff options
Diffstat (limited to 'engines/agi/sprite.cpp')
| -rw-r--r-- | engines/agi/sprite.cpp | 868 | 
1 files changed, 868 insertions, 0 deletions
diff --git a/engines/agi/sprite.cpp b/engines/agi/sprite.cpp new file mode 100644 index 0000000000..3a3ba7354e --- /dev/null +++ b/engines/agi/sprite.cpp @@ -0,0 +1,868 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * Copyright (C) 1999-2003 Sarien Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "agi/agi.h" +#include "agi/list.h" +#include "agi/sprite.h" +#include "agi/graphics.h" +#include "agi/text.h" +#include "agi/savegame.h" + +namespace Agi { + +/** + * Sprite structure. + * This structure holds information on visible and priority data of + * a rectangular area of the AGI screen. Sprites are chained in two + * circular lists, one for updating and other for non-updating sprites. + */ +struct sprite { +	struct list_head list; +	struct vt_entry *v;		/**< pointer to view table entry */ +	int16 x_pos;			/**< x coordinate of the sprite */ +	int16 y_pos;			/**< y coordinate of the sprite */ +	int16 x_size;			/**< width of the sprite */ +	int16 y_size;			/**< height of the sprite */ +	uint8 *buffer;			/**< buffer to store background data */ +#ifdef USE_HIRES +	uint8 *hires;			/**< buffer for hi-res background */ +#endif +}; + +/* + * Sprite pool replaces dynamic allocation + */ +#undef ALLOC_DEBUG + +#ifdef USE_HIRES +#define POOL_SIZE 68000		/* Gold Rush mine room needs > 50000 */ +	/* Speeder bike challenge needs > 67000 */ +#else +#define POOL_SIZE 25000 +#endif +static uint8 *sprite_pool; +static uint8 *pool_top; + +static void *pool_alloc(int size) { +	uint8 *x; + +	/* Adjust size to 32-bit boundary to prevent data misalignment +	 * errors.  +	 */ +	size = (size + 3) & ~3; + +	x = pool_top; +	pool_top += size; + +	if (pool_top >= (uint8 *)sprite_pool + POOL_SIZE) { +		debugC(1, kDebugLevelMain | kDebugLevelResources, "not enough memory"); +		pool_top = x; +		return NULL; +	} + +	return x; +} + +static void pool_release(void *s) { +	pool_top = (uint8 *)s; +} + +/* + * Blitter functions + */ + +/* Blit one pixel considering the priorities */ + +static void blit_pixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, int *hidden) { +	int epr = 0, pr = 0;	/* effective and real priorities */ + +	/* CM: priority 15 overrides control lines and is ignored when +	 *     tracking effective priority. This tweak is needed to fix +	 *     bug #451768, and should not affect Sierra games because +	 *     sprites shouldn't have priority 15 (like the AGI Mouse +	 *     demo "mouse pointer") +	 * +	 * Update: this solution breaks other games, and can't be used. +	 */ + +	if (p >= end) +		return; + +	/* Check if we're on a control line */ +	if ((pr = *p & 0xf0) < 0x30) { +		uint8 *p1; +		/* Yes, get effective priority going down */ +		for (p1 = p; p1 < end && (epr = *p1 & 0xf0) < 0x30; +		    p1 += width); +		if (p1 >= end) +			epr = 0x40; +	} else { +		epr = pr; +	} + +	if (spr >= epr) { +		/* Keep control line information visible, but put our +		 * priority over water (0x30) surface +		 */ +		*p = (pr < 0x30 ? pr : spr) | col; +		*hidden = false; + +		/* Except if our priority is 15, which should never happen +		 * (fixes bug #451768) +		 * +		 * Update: breaks other games, can't be used +		 * +		 * if (spr == 0xf0) +		 *      *p = spr | col; +		 */ +	} +} + +#ifdef USE_HIRES + +#define X_FACT 2		/* Horizontal hires factor */ + +static int blit_hires_cel(int x, int y, int spr, struct view_cel *c) { +	uint8 *q = NULL; +	uint8 *h0, *h, *end; +	int i, j, t, m, col; +	int hidden = true; + +	q = c->data; +	t = c->transparency; +	m = c->mirror; +	spr <<= 4; +	h0 = &game.hires[(x + y * _WIDTH + m * (c->width - 1)) * X_FACT]; + +	end = game.hires + _WIDTH * X_FACT * _HEIGHT; + +	for (i = 0; i < c->height; i++) { +		h = h0; +		while (*q) { +			col = (*q & 0xf0) >> 4; +			for (j = *q & 0x0f; j; j--, h += X_FACT * (1 - 2 * m)) { +				if (col != t) { +					blit_pixel(h, end, col, spr, _WIDTH * X_FACT, &hidden); +					blit_pixel(h + 1, end, col, spr, _WIDTH * X_FACT, &hidden); +				} +			} +			q++; +		} +		h0 += _WIDTH * X_FACT; +		q++; +	} +	return hidden; +} + +#endif + +static int blit_cel(int x, int y, int spr, struct view_cel *c) { +	uint8 *p0, *p, *q = NULL, *end; +	int i, j, t, m, col; +	int hidden = true; + +	/* Fixes bug #477841 (crash in PQ1 map C4 when y == -2) */ +	if (y < 0) +		y = 0; +	if (x < 0) +		x = 0; +	if (y >= _HEIGHT) +		y = _HEIGHT - 1; +	if (x >= _WIDTH) +		x = _WIDTH - 1; + +#ifdef USE_HIRES +	if (opt.hires) +		blit_hires_cel(x, y, spr, c); +#endif + +	q = c->data; +	t = c->transparency; +	m = c->mirror; +	spr <<= 4; +	p0 = &game.sbuf[x + y * _WIDTH + m * (c->width - 1)]; + +	end = game.sbuf + _WIDTH * _HEIGHT; + +	for (i = 0; i < c->height; i++) { +		p = p0; +		while (*q) { +			col = (*q & 0xf0) >> 4; +			for (j = *q & 0x0f; j; j--, p += 1 - 2 * m) { +				if (col != t) { +					blit_pixel(p, end, col, spr, _WIDTH, &hidden); +				} +			} +			q++; +		} +		p0 += _WIDTH; +		q++; +	} + +	return hidden; +} + +static void objs_savearea(struct sprite *s) { +	int y; +	int16 x_pos = s->x_pos, y_pos = s->y_pos; +	int16 x_size = s->x_size, y_size = s->y_size; +	uint8 *p0, *q; +#ifdef USE_HIRES +	uint8 *h0, *k; +#endif + +	if (x_pos + x_size > _WIDTH) +		x_size = _WIDTH - x_pos; + +	if (x_pos < 0) { +		x_size += x_pos; +		x_pos = 0; +	} + +	if (y_pos + y_size > _HEIGHT) +		y_size = _HEIGHT - y_pos; + +	if (y_pos < 0) { +		y_size += y_pos; +		y_pos = 0; +	} + +	if (x_size <= 0 || y_size <= 0) +		return; + +	p0 = &game.sbuf[x_pos + y_pos * _WIDTH]; +	q = s->buffer; +#ifdef USE_HIRES +	h0 = &game.hires[(x_pos + y_pos * _WIDTH) * 2]; +	k = s->hires; +#endif +	for (y = 0; y < y_size; y++) { +		memcpy(q, p0, x_size); +		q += x_size; +		p0 += _WIDTH; +#ifdef USE_HIRES +		memcpy(k, h0, x_size * 2); +		k += x_size * 2; +		h0 += _WIDTH * 2; +#endif +	} +} + +static void objs_restorearea(struct sprite *s) { +	int y, offset; +	int16 x_pos = s->x_pos, y_pos = s->y_pos; +	int16 x_size = s->x_size, y_size = s->y_size; +	uint8 *p0, *q; +#ifdef USE_HIRES +	uint8 *h0, *k; +#endif + +	if (x_pos + x_size > _WIDTH) +		x_size = _WIDTH - x_pos; + +	if (x_pos < 0) { +		x_size += x_pos; +		x_pos = 0; +	} + +	if (y_pos + y_size > _HEIGHT) +		y_size = _HEIGHT - y_pos; + +	if (y_pos < 0) { +		y_size += y_pos; +		y_pos = 0; +	} + +	if (x_size <= 0 || y_size <= 0) +		return; + +	p0 = &game.sbuf[x_pos + y_pos * _WIDTH]; +	q = s->buffer; +#ifdef USE_HIRES +	h0 = &game.hires[(x_pos + y_pos * _WIDTH) * 2]; +	k = s->hires; +#endif +	offset = game.line_min_print * CHAR_LINES; +	for (y = 0; y < y_size; y++) { +		memcpy(p0, q, x_size); +		put_pixels_a(x_pos, y_pos + y + offset, x_size, p0); +		q += x_size; +		p0 += _WIDTH; +#ifdef USE_HIRES +		memcpy(h0, k, x_size * 2); +		if (opt.hires) { +			put_pixels_hires(x_pos * 2, y_pos + y + offset, x_size * 2, h0); +		} +		k += x_size * 2; +		h0 += _WIDTH * 2; +#endif +	} +} + +/* + * Sprite management functions + */ + +static LIST_HEAD(spr_upd_head); +static LIST_HEAD(spr_nonupd_head); + +/** + * Condition to determine whether a sprite will be in the 'updating' list. + */ +static int test_updating(struct vt_entry *v) { +	/* Sanity check (see bug #779302) */ +	if (~game.dir_view[v->current_view].flags & RES_LOADED) +		return 0; + +	return (v->flags & (ANIMATED | UPDATE | DRAWN)) == (ANIMATED | UPDATE | DRAWN); +} + +/** + * Condition to determine whether a sprite will be in the 'non-updating' list. + */ +static int test_not_updating(struct vt_entry *v) { +	/* Sanity check (see bug #779302) */ +	if (~game.dir_view[v->current_view].flags & RES_LOADED) +		return 0; + +	return (v->flags & (ANIMATED | UPDATE | DRAWN)) == (ANIMATED | DRAWN); +} + +/** + * Convert sprite priority to y value. + */ +static INLINE int prio_to_y(int p) { +	int i; + +	if (p == 0) +		return -1; + +	for (i = 167; i >= 0; i--) { +		if (game.pri_table[i] < p) +			return i; +	} + +	return -1;		/* (p - 5) * 12 + 48; */ +} + +/** + * Create and initialize a new sprite structure. + */ +static struct sprite *new_sprite(struct vt_entry *v) { +	struct sprite *s; + +	s = (struct sprite *)pool_alloc(sizeof(struct sprite)); +	if (s == NULL) +		return NULL; + +	s->v = v;		/* link sprite to associated view table entry */ +	s->x_pos = v->x_pos; +	s->y_pos = v->y_pos - v->y_size + 1; +	s->x_size = v->x_size; +	s->y_size = v->y_size; +	s->buffer = (uint8 *) pool_alloc(s->x_size * s->y_size); +#ifdef USE_HIRES +	s->hires = (uint8 *) pool_alloc(s->x_size * s->y_size * 2); +#endif +	v->s = s;		/* link view table entry to this sprite */ + +	return s; +} + +/** + * Insert sprite in the specified sprite list. + */ +static void spr_addlist(struct list_head *head, struct vt_entry *v) { +	struct sprite *s; + +	s = new_sprite(v); +	list_add_tail(&s->list, head); +} + +/** + * Sort sprites from lower y values to build a sprite list. + */ +static struct list_head *build_list(struct list_head *head, +									int (*test) (struct vt_entry *)) { +	int i, j, k; +	struct vt_entry *v; +	struct vt_entry *entry[0x100]; +	int y_val[0x100]; +	int min_y = 0xff, min_index = 0; + +	/* fill the arrays with all sprites that satisfy the 'test' +	 * condition and their y values +	 */ +	i = 0; +	for (v = game.view_table; v < &game.view_table[MAX_VIEWTABLE]; v++) { +		if (test(v)) { +			entry[i] = v; +			y_val[i] = v->flags & FIXED_PRIORITY ? prio_to_y(v->priority) : v->y_pos; +			i++; +		} +	} + +	/* now look for the smallest y value in the array and put that +	 * sprite in the list +	 */ +	for (j = 0; j < i; j++) { +		min_y = 0xff; +		for (k = 0; k < i; k++) { +			if (y_val[k] < min_y) { +				min_index = k; +				min_y = y_val[k]; +			} +		} + +		y_val[min_index] = 0xff; +		spr_addlist(head, entry[min_index]); +	} + +	return head; +} + +/** + * Build list of updating sprites. + */ +static struct list_head *build_upd_blitlist() { +	return build_list(&spr_upd_head, test_updating); +} + +/** + * Build list of non-updating sprites. + */ +static struct list_head *build_nonupd_blitlist() { +	return build_list(&spr_nonupd_head, test_not_updating); +} + +/** + * Clear the given sprite list. + */ +static void free_list(struct list_head *head) { +	struct list_head *h; +	struct sprite *s; + +	list_for_each(h, head, prev) { +		s = list_entry(h, struct sprite, list); +		list_del(h); +#ifdef USE_HIRES +		pool_release(s->hires); +#endif +		pool_release(s->buffer); +		pool_release(s); +	} +} + +/** + * Copy sprites from the pic buffer to the screen buffer, and check if + * sprites of the given list have moved. + */ +static void commit_sprites(struct list_head *head) { +	struct list_head *h; + +	list_for_each(h, head, next) { +		struct sprite *s = list_entry(h, struct sprite, list); +		int x1, y1, x2, y2, w, h; + +		w = (s->v->cel_data->width > s->v->cel_data_2->width) ? +				s->v->cel_data->width : s->v->cel_data_2->width; + +		h = (s->v->cel_data->height > +				s->v->cel_data_2->height) ? s->v->cel_data-> +				height : s->v->cel_data_2->height; + +		s->v->cel_data_2 = s->v->cel_data; + +		if (s->v->x_pos < s->v->x_pos2) { +			x1 = s->v->x_pos; +			x2 = s->v->x_pos2 + w - 1; +		} else { +			x1 = s->v->x_pos2; +			x2 = s->v->x_pos + w - 1; +		} + +		if (s->v->y_pos < s->v->y_pos2) { +			y1 = s->v->y_pos - h + 1; +			y2 = s->v->y_pos2; +		} else { +			y1 = s->v->y_pos2 - h + 1; +			y2 = s->v->y_pos; +		} + +		commit_block(x1, y1, x2, y2); + +		if (s->v->step_time_count != s->v->step_time) +			continue; + +		if (s->v->x_pos == s->v->x_pos2 && s->v->y_pos == s->v->y_pos2) { +			s->v->flags |= DIDNT_MOVE; +			continue; +		} + +		s->v->x_pos2 = s->v->x_pos; +		s->v->y_pos2 = s->v->y_pos; +		s->v->flags &= ~DIDNT_MOVE; +	} + +#ifdef USE_CONSOLE +	if (debug_.statusline) +		write_status(); +#endif +} + +/** + * Erase all sprites in the given list. + */ +static void erase_sprites(struct list_head *head) { +	struct list_head *h; + +	list_for_each(h, head, prev) { +		struct sprite *s = list_entry(h, struct sprite, list); +		objs_restorearea(s); +	} + +	free_list(head); +} + +/** + * Blit all sprites in the given list. + */ +static void blit_sprites(struct list_head *head) { +	struct list_head *h = NULL; +	int hidden; + +	list_for_each(h, head, next) { +		struct sprite *s = list_entry(h, struct sprite, list); +		objs_savearea(s); +		debugC(8, kDebugLevelSprites, "s->v->entry = %d (prio %d)", s->v->entry, s->v->priority); +		hidden = blit_cel(s->x_pos, s->y_pos, s->v->priority, s->v->cel_data); +		if (s->v->entry == 0) {	/* if ego, update f1 */ +			setflag(F_ego_invisible, hidden); +		} +	} +} + +/* + * Public functions + */ + +void commit_upd_sprites() { +	commit_sprites(&spr_upd_head); +} + +void commit_nonupd_sprites() { +	commit_sprites(&spr_nonupd_head); +} + +/* check moves in both lists */ +void commit_both() { +	commit_upd_sprites(); +	commit_nonupd_sprites(); +} + +/** + * Erase updating sprites. + * This function follows the list of all updating sprites and restores + * the visible and priority data of their background buffers back to + * the AGI screen. + * + * @see erase_nonupd_sprites() + * @see erase_both() + */ +void erase_upd_sprites() { +	erase_sprites(&spr_upd_head); +} + +/** + * Erase non-updating sprites. + * This function follows the list of all non-updating sprites and restores + * the visible and priority data of their background buffers back to + * the AGI screen. + * + * @see erase_upd_sprites() + * @see erase_both() + */ +void erase_nonupd_sprites() { +	erase_sprites(&spr_nonupd_head); +} + +/** + * Erase all sprites. + * This function follows the lists of all updating and non-updating + * sprites and restores the visible and priority data of their background + * buffers back to the AGI screen. + * + * @see erase_upd_sprites() + * @see erase_nonupd_sprites() + */ +void erase_both() { +	erase_upd_sprites(); +	erase_nonupd_sprites(); +} + +/** + * Blit updating sprites. + * This function follows the list of all updating sprites and blits + * them on the AGI screen. + * + * @see blit_nonupd_sprites() + * @see blit_both() + */ +void blit_upd_sprites() { +	debugC(7, kDebugLevelSprites, "blit updating"); +	blit_sprites(build_upd_blitlist()); +} + +/** + * Blit non-updating sprites. + * This function follows the list of all non-updating sprites and blits + * them on the AGI screen. + * + * @see blit_upd_sprites() + * @see blit_both() + */ +void blit_nonupd_sprites() { +	debugC(7, kDebugLevelSprites, "blit non-updating"); +	blit_sprites(build_nonupd_blitlist()); +} + +/** + * Blit all sprites. + * This function follows the lists of all updating and non-updating + * sprites and blits them on the AGI screen. + * + * @see blit_upd_sprites() + * @see blit_nonupd_sprites() + */ +void blit_both() { +	blit_nonupd_sprites(); +	blit_upd_sprites(); +} + +/** + * Add view to picture. + * This function is used to implement the add.to.pic AGI command. It + * copies the specified cel from a view resource on the current picture. + * This cel is not a sprite, it can't be moved or removed. + * @param view  number of view resource + * @param loop  number of loop in the specified view resource + * @param cel   number of cel in the specified loop + * @param x     x coordinate to place the view + * @param y     y coordinate to place the view + * @param pri   priority to use + * @param mar   if < 4, create a margin around the the base of the cel + */ +void add_to_pic(int view, int loop, int cel, int x, int y, int pri, int mar) { +	struct view_cel *c = NULL; +	int x1, y1, x2, y2, y3; +	uint8 *p1, *p2; + +	debugC(3, kDebugLevelSprites, "v=%d, l=%d, c=%d, x=%d, y=%d, p=%d, m=%d", view, loop, cel, x, y, pri, mar); + +	record_image_stack_call(ADD_VIEW, view, loop, cel, x, y, pri, mar); + +	/* +	 * Was hardcoded to 8, changed to pri_table[y] to fix Gold +	 * Rush (see bug #587558) +	 */ +	if (pri == 0) +		pri = game.pri_table[y]; + +	c = &game.views[view].loop[loop].cel[cel]; + +	x1 = x; +	y1 = y - c->height + 1; +	x2 = x + c->width - 1; +	y2 = y; + +	if (x1 < 0) { +		x2 -= x1; +		x1 = 0; +	} +	if (y1 < 0) { +		y2 -= y1; +		y1 = 0; +	} +	if (x2 >= _WIDTH) +		x2 = _WIDTH - 1; +	if (y2 >= _HEIGHT) +		y2 = _HEIGHT - 1; + +	erase_both(); + +	debugC(4, kDebugLevelSprites, "blit_cel (%d, %d, %d, c)", x, y, pri); +	blit_cel(x1, y1, pri, c); + +	/* If margin is 0, 1, 2, or 3, the base of the cel is +	 * surrounded with a rectangle of the corresponding priority. +	 * If margin >= 4, this extra margin is not shown. +	 */ +	if (mar < 4) { +		/* add rectangle around object, don't clobber control +		 * info in priority data. The box extends to the end of +		 * its priority band! +		 * +		 * SQ1 needs +1 (see bug #810331) +		 */ +		y3 = (y2 / 12) * 12 + 1; + +		p1 = &game.sbuf[x1 + y3 * _WIDTH]; +		p2 = &game.sbuf[x2 + y3 * _WIDTH]; + +		for (y = y3; y <= y2; y++) { +			if ((*p1 >> 4) >= 4) +				*p1 = (mar << 4) | (*p1 & 0x0f); +			if ((*p2 >> 4) >= 4) +				*p2 = (mar << 4) | (*p2 & 0x0f); +			p1 += _WIDTH; +			p2 += _WIDTH; +		} + +		debugC(4, kDebugLevelSprites, "pri box: %d %d %d %d (%d)", x1, y3, x2, y2, mar); +		p1 = &game.sbuf[x1 + y3 * _WIDTH]; +		p2 = &game.sbuf[x1 + y2 * _WIDTH]; +		for (x = x1; x <= x2; x++) { +			if ((*p1 >> 4) >= 4) +				*p1 = (mar << 4) | (*p1 & 0x0f); +			if ((*p2 >> 4) >= 4) +				*p2 = (mar << 4) | (*p2 & 0x0f); +			p1++; +			p2++; +		} +	} + +	blit_both(); + +	debugC(4, kDebugLevelSprites, "commit_block (%d, %d, %d, %d)", x1, y1, x2, y2); +	commit_block(x1, y1, x2, y2); +} + +/** + * Show object and description + * This function shows an object from the player's inventory, displaying + * a message box with the object description. + * @param n  Number of the object to show + */ +void show_obj(int n) { +	struct view_cel *c; +	struct sprite s; +	int x1, y1, x2, y2; + +	agi_load_resource(rVIEW, n); +	if (!(c = &game.views[n].loop[0].cel[0])) +		return; + +	x1 = (_WIDTH - c->width) / 2; +	y1 = 112; +	x2 = x1 + c->width - 1; +	y2 = y1 + c->height - 1; + +	s.x_pos = x1; +	s.y_pos = y1; +	s.x_size = c->width; +	s.y_size = c->height; +	s.buffer = (uint8 *)malloc(s.x_size * s.y_size); +#ifdef USE_HIRES +	s.hires = (uint8 *)malloc(s.x_size * s.y_size * 2); +#endif + +	objs_savearea(&s); +	blit_cel(x1, y1, s.x_size, c); +	commit_block(x1, y1, x2, y2); +	message_box(game.views[n].descr); +	objs_restorearea(&s); +	commit_block(x1, y1, x2, y2); + +	free(s.buffer); + +	/* Added to fix a memory leak --Vasyl */ +#ifdef USE_HIRES +	free(s.hires); +#endif +} + +void commit_block(int x1, int y1, int x2, int y2) { +	int i, w, offset; +	uint8 *q; +#ifdef USE_HIRES +	uint8 *h; +#endif + +	if (!game.picture_shown) +		return; + +	/* Clipping */ +	if (x1 < 0) +		x1 = 0; +	if (x2 < 0) +		x2 = 0; +	if (y1 < 0) +		y1 = 0; +	if (y2 < 0) +		y2 = 0; +	if (x1 >= _WIDTH) +		x1 = _WIDTH - 1; +	if (x2 >= _WIDTH) +		x2 = _WIDTH - 1; +	if (y1 >= _HEIGHT) +		y1 = _HEIGHT - 1; +	if (y2 >= _HEIGHT) +		y2 = _HEIGHT - 1; + +	debugC(7, kDebugLevelSprites, "%d, %d, %d, %d", x1, y1, x2, y2); + +	w = x2 - x1 + 1; +	q = &game.sbuf[x1 + _WIDTH * y1]; +#ifdef USE_HIRES +	h = &game.hires[(x1 + _WIDTH * y1) * 2]; +#endif +	offset = game.line_min_print * CHAR_LINES; +	for (i = y1; i <= y2; i++) { +		put_pixels_a(x1, i + offset, w, q); +		q += _WIDTH; +#ifdef USE_HIRES +		if (opt.hires) { +			put_pixels_hires(x1 * 2, i + offset, w * 2, h); +		} +		h += _WIDTH * 2; +#endif +	} + +	flush_block_a(x1, y1 + offset, x2, y2 + offset); +} + +int init_sprites() { +	if ((sprite_pool = (uint8 *)malloc(POOL_SIZE)) == NULL) +		return err_NotEnoughMemory; + +	pool_top = sprite_pool; + +	return err_OK; +} + +void deinit_sprites() { +	free(sprite_pool); +} + +}                             // End of namespace Agi  | 
