aboutsummaryrefslogtreecommitdiff
path: root/engines/agi/sprite.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/agi/sprite.cpp')
-rw-r--r--engines/agi/sprite.cpp868
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