696 lines
21 KiB
C
696 lines
21 KiB
C
// The picture buffers and the screen objects drawn into them.
|
|
//
|
|
// Like Sierra's interpreter, JoeyAGI keeps one 160x168 picture buffer pair
|
|
// -- a colour and a priority byte per AGI pixel -- that holds the picture
|
|
// with the visible objects blitted into it. An object's blit saves the
|
|
// bytes under its cel, then draws the cel's pixels whose priority is at
|
|
// least the buffer's, writing the object's priority with the colour, so a
|
|
// lower object drawn later stays hidden behind it. Erasing puts the saved
|
|
// bytes back, so blits stack: the buffer is always erased in the reverse
|
|
// order it was drawn. Objects that are not updated (stop.update) are drawn
|
|
// first, sorted by depth, and the updated ones on top of them.
|
|
//
|
|
// Changed areas are copied to the stage, each AGI pixel two screen
|
|
// pixels wide, below the status line (configure.screen's picture row).
|
|
// display() text written over the picture lives only on the stage, so an
|
|
// object moving across it wipes it, as in the original.
|
|
|
|
#include "agi.h"
|
|
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include "joey/core.h"
|
|
#include "joey/draw.h"
|
|
#include "joey/input.h"
|
|
#include "joey/present.h"
|
|
#include "joey/sprite.h"
|
|
#include "joey/tile.h"
|
|
#include "surfaceInternal.h"
|
|
|
|
|
|
// Room for the save-unders of every drawn object: two bytes per pixel.
|
|
#define SAVE_ARENA_BYTES 65535u
|
|
// Areas waiting to be copied to the stage; more are merged into one.
|
|
#define DIRTY_MAX 24u
|
|
#define NIBBLE_SHIFT 4u
|
|
// A cel run byte: the colour in the high nibble, the length in the low.
|
|
#define RLE_COUNT_MASK 0x0Fu
|
|
// add.to.pic's margin box: margins 0..3 are control values; 4 and above
|
|
// mean no box.
|
|
#define MARGIN_NONE 4u
|
|
// shake.screen, measured on Sierra's interpreter: each beat shows the
|
|
// whole screen moved 8 pixels right and 4 down (the uncovered edges
|
|
// black) for 4 frames, then in place for 4; a shake is 4 beats per unit
|
|
// counted in 16 bits, so shake.screen(0) is 65536 beats.
|
|
#define SHAKE_DX 8
|
|
#define SHAKE_DY 4
|
|
#define SHAKE_PHASE_FRAMES 4u
|
|
#define SHAKE_BEATS_PER_UNIT 4u
|
|
#define MS_PER_SECOND 1000u
|
|
// A chunky surface's byte holds two pixels.
|
|
#define CHUNKY_PX_PER_BYTE (SURFACE_WIDTH / SURFACE_BYTES_PER_ROW)
|
|
|
|
|
|
typedef struct {
|
|
int16_t x;
|
|
int16_t y;
|
|
int16_t w;
|
|
int16_t h;
|
|
} GfxRectT;
|
|
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static void blitObject(uint8_t n);
|
|
static void buildShifted(jlSurfaceT *shifted, const jlSurfaceT *normal);
|
|
static void drawCel(const AgiCelT *cel, bool mirror, int16_t left, int16_t top, uint8_t priority, bool keepPriority);
|
|
static void drawList(bool updating);
|
|
static uint8_t effectivePriority(int16_t x, int16_t y);
|
|
static void markDirty(int16_t x, int16_t y, int16_t w, int16_t h);
|
|
static void pushRect(const GfxRectT *r);
|
|
static void pushRuns(jlSurfaceT *stage, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t screenY);
|
|
static void restoreRect(const uint8_t *save, int16_t x, int16_t y, uint8_t w, uint8_t h);
|
|
static void saveRect(uint8_t *save, int16_t x, int16_t y, uint8_t w, uint8_t h);
|
|
static void shakePhase(const jlSurfaceT *show, uint32_t phaseMs);
|
|
static int16_t sortKey(const AgiObjectT *o);
|
|
static void unblitTop(void);
|
|
|
|
|
|
// ----- Module state -----
|
|
|
|
static uint8_t *gVis;
|
|
static uint8_t *gPri;
|
|
static uint8_t *gSaveArena;
|
|
static uint16_t gSaveTop;
|
|
// Objects in the order they were blitted; the first gStaticBlits of them
|
|
// are the not-updated batch.
|
|
static uint8_t gBlitStack[AGI_MAX_OBJECTS];
|
|
static uint8_t gBlitCount;
|
|
static uint8_t gStaticBlits;
|
|
static GfxRectT gDirty[DIRTY_MAX];
|
|
static uint8_t gDirtyCount;
|
|
static bool gPicShown;
|
|
|
|
|
|
// ----- Internal helpers (alphabetical) -----
|
|
|
|
static void blitObject(uint8_t n) {
|
|
AgiObjectT *o;
|
|
const AgiCelT *cel;
|
|
bool mirror;
|
|
int16_t left;
|
|
int16_t top;
|
|
uint16_t need;
|
|
|
|
o = &gAgi->objects[n];
|
|
cel = agiViewCel(o->view, o->loop, o->cel, &mirror);
|
|
if (cel == NULL || cel->width == 0u || cel->height == 0u) {
|
|
return;
|
|
}
|
|
left = o->x;
|
|
top = (int16_t)(o->y - (int16_t)cel->height + 1);
|
|
// Clip the save rectangle to the buffer.
|
|
o->blitX = left;
|
|
o->blitY = (top < 0) ? 0 : top;
|
|
o->blitW = cel->width;
|
|
if (left + (int16_t)cel->width > AGI_WIDTH) {
|
|
o->blitW = (uint8_t)(AGI_WIDTH - left);
|
|
}
|
|
o->blitH = (uint8_t)(o->y - o->blitY + 1);
|
|
if (left < 0 || left >= AGI_WIDTH || o->y < 0 || o->y > AGI_MAX_Y || o->blitW == 0u) {
|
|
return;
|
|
}
|
|
need = (uint16_t)((uint16_t)o->blitW * o->blitH * 2u);
|
|
if ((uint32_t)gSaveTop + need > SAVE_ARENA_BYTES) {
|
|
return;
|
|
}
|
|
o->saveUnder = &gSaveArena[gSaveTop];
|
|
saveRect(o->saveUnder, left, o->blitY, o->blitW, o->blitH);
|
|
gSaveTop = (uint16_t)(gSaveTop + need);
|
|
o->blitted = true;
|
|
gBlitStack[gBlitCount] = n;
|
|
gBlitCount++;
|
|
if ((o->flags & OBJ_FIX_PRIORITY) == 0u) {
|
|
o->priority = agiPriorityForY(o->y);
|
|
}
|
|
drawCel(cel, mirror, left, top, o->priority, false);
|
|
markDirty(o->blitX, o->blitY, o->blitW, o->blitH);
|
|
}
|
|
|
|
|
|
// The screen as a shake beat shows it: moved SHAKE_DX right and SHAKE_DY
|
|
// down over black. Chunky surfaces move their bytes; planar ones draw the
|
|
// screen as one big sprite (colour 0 is transparent, and the surface is
|
|
// black beneath).
|
|
static void buildShifted(jlSurfaceT *shifted, const jlSurfaceT *normal) {
|
|
jlSpriteT *sprite;
|
|
uint16_t y;
|
|
|
|
jlSurfaceCopy(shifted, normal);
|
|
jlSurfaceClear(shifted, AGI_COLOR_BLACK);
|
|
if (shifted->pixels != NULL && normal->pixels != NULL) {
|
|
for (y = SHAKE_DY; y < SURFACE_HEIGHT; y++) {
|
|
memcpy(&shifted->pixels[y * SURFACE_BYTES_PER_ROW + SHAKE_DX / CHUNKY_PX_PER_BYTE],
|
|
&normal->pixels[(uint16_t)(y - SHAKE_DY) * SURFACE_BYTES_PER_ROW],
|
|
SURFACE_BYTES_PER_ROW - SHAKE_DX / CHUNKY_PX_PER_BYTE);
|
|
}
|
|
return;
|
|
}
|
|
sprite = jlSpriteCreateFromSurface(normal, 0, 0, TILE_BLOCKS_PER_ROW, TILE_BLOCKS_PER_COL);
|
|
if (sprite != NULL) {
|
|
jlSpriteDraw(shifted, sprite, SHAKE_DX, SHAKE_DY);
|
|
jlSpriteDestroy(sprite);
|
|
}
|
|
}
|
|
|
|
|
|
// Draw a cel's opaque pixels into the buffers where its priority is at
|
|
// least the buffer's (control lines take the priority below them).
|
|
// keepPriority leaves the priority buffer alone (add.to.pic writes it).
|
|
static void drawCel(const AgiCelT *cel, bool mirror, int16_t left, int16_t top, uint8_t priority, bool keepPriority) {
|
|
const uint8_t *rle;
|
|
uint8_t row;
|
|
int16_t col;
|
|
int16_t y;
|
|
int16_t x;
|
|
uint8_t color;
|
|
uint8_t run;
|
|
uint16_t idx;
|
|
|
|
rle = cel->rle;
|
|
for (row = 0u; row < cel->height; row++) {
|
|
y = (int16_t)(top + row);
|
|
col = 0;
|
|
while (*rle != 0u) {
|
|
color = (uint8_t)(*rle >> NIBBLE_SHIFT);
|
|
run = (uint8_t)(*rle & RLE_COUNT_MASK);
|
|
rle++;
|
|
if (color == cel->transparent || y < 0 || y > AGI_MAX_Y) {
|
|
col = (int16_t)(col + run);
|
|
continue;
|
|
}
|
|
while (run > 0u) {
|
|
x = mirror ? (int16_t)(left + (int16_t)cel->width - 1 - col) : (int16_t)(left + col);
|
|
if (x >= 0 && x < AGI_WIDTH && col < (int16_t)cel->width) {
|
|
idx = (uint16_t)((uint16_t)y * AGI_WIDTH + (uint16_t)x);
|
|
if (priority >= effectivePriority(x, y)) {
|
|
gVis[idx] = color;
|
|
if (!keepPriority) {
|
|
gPri[idx] = priority;
|
|
}
|
|
}
|
|
}
|
|
col++;
|
|
run--;
|
|
}
|
|
}
|
|
rle++;
|
|
}
|
|
}
|
|
|
|
|
|
// Blit one batch, sorted by depth: the not-updated objects or the updated
|
|
// ones.
|
|
static void drawList(bool updating) {
|
|
uint8_t order[AGI_MAX_OBJECTS];
|
|
uint8_t count;
|
|
uint8_t i;
|
|
uint8_t j;
|
|
uint16_t want;
|
|
|
|
want = updating ? (uint16_t)(OBJ_ANIMATED | OBJ_DRAWN | OBJ_UPDATE) : (uint16_t)(OBJ_ANIMATED | OBJ_DRAWN);
|
|
count = 0u;
|
|
for (i = 0u; i < gAgi->objectCount; i++) {
|
|
const AgiObjectT *o = &gAgi->objects[i];
|
|
|
|
if ((o->flags & (OBJ_ANIMATED | OBJ_DRAWN | OBJ_UPDATE)) != want || o->blitted || o->view == NULL) {
|
|
continue;
|
|
}
|
|
// Insertion sort: by depth, then by object number.
|
|
j = count;
|
|
while (j > 0u && sortKey(&gAgi->objects[order[j - 1u]]) > sortKey(o)) {
|
|
order[j] = order[j - 1u];
|
|
j--;
|
|
}
|
|
order[j] = i;
|
|
count++;
|
|
}
|
|
for (i = 0u; i < count; i++) {
|
|
blitObject(order[i]);
|
|
}
|
|
}
|
|
|
|
|
|
// The priority a pixel has for drawing: control lines (0..3) take the
|
|
// priority of the first non-control pixel below them.
|
|
static uint8_t effectivePriority(int16_t x, int16_t y) {
|
|
uint8_t p;
|
|
|
|
for (;;) {
|
|
p = gPri[(uint16_t)y * AGI_WIDTH + (uint16_t)x];
|
|
if (p > AGI_CTL_WATER) {
|
|
return p;
|
|
}
|
|
y++;
|
|
if (y > AGI_MAX_Y) {
|
|
return AGI_PRIORITY_MAX;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void markDirty(int16_t x, int16_t y, int16_t w, int16_t h) {
|
|
GfxRectT *r;
|
|
int16_t right;
|
|
int16_t bottom;
|
|
|
|
if (w <= 0 || h <= 0) {
|
|
return;
|
|
}
|
|
if (gDirtyCount < DIRTY_MAX) {
|
|
r = &gDirty[gDirtyCount];
|
|
gDirtyCount++;
|
|
r->x = x;
|
|
r->y = y;
|
|
r->w = w;
|
|
r->h = h;
|
|
return;
|
|
}
|
|
// Out of slots: grow the last one to cover this one too.
|
|
r = &gDirty[DIRTY_MAX - 1u];
|
|
right = (int16_t)(r->x + r->w);
|
|
bottom = (int16_t)(r->y + r->h);
|
|
if (x + w > right) {
|
|
right = (int16_t)(x + w);
|
|
}
|
|
if (y + h > bottom) {
|
|
bottom = (int16_t)(y + h);
|
|
}
|
|
if (x < r->x) {
|
|
r->x = x;
|
|
}
|
|
if (y < r->y) {
|
|
r->y = y;
|
|
}
|
|
r->w = (int16_t)(right - r->x);
|
|
r->h = (int16_t)(bottom - r->y);
|
|
}
|
|
|
|
|
|
// Copy a buffer rectangle to the stage, each AGI pixel two screen pixels:
|
|
// straight into a chunky stage's bytes, or as runs on a planar one.
|
|
static void pushRect(const GfxRectT *r) {
|
|
jlSurfaceT *stage;
|
|
int16_t x0;
|
|
int16_t y0;
|
|
int16_t x1;
|
|
int16_t y1;
|
|
int16_t x;
|
|
int16_t y;
|
|
int16_t screenY;
|
|
uint8_t c;
|
|
|
|
x0 = (r->x < 0) ? 0 : r->x;
|
|
y0 = (r->y < 0) ? 0 : r->y;
|
|
x1 = (int16_t)(r->x + r->w);
|
|
y1 = (int16_t)(r->y + r->h);
|
|
if (x1 > AGI_WIDTH) {
|
|
x1 = AGI_WIDTH;
|
|
}
|
|
if (y1 > AGI_HEIGHT) {
|
|
y1 = AGI_HEIGHT;
|
|
}
|
|
if (x0 >= x1 || y0 >= y1) {
|
|
return;
|
|
}
|
|
stage = jlStageGet();
|
|
screenY = (int16_t)((int16_t)gAgi->picRow * AGI_CELL_PX);
|
|
if (stage->pixels == NULL) {
|
|
pushRuns(stage, x0, y0, x1, y1, screenY);
|
|
return;
|
|
}
|
|
for (y = y0; y < y1; y++) {
|
|
const uint8_t *src = &gVis[(uint16_t)y * AGI_WIDTH];
|
|
uint8_t *dst = &stage->pixels[(uint16_t)(screenY + y) * SURFACE_BYTES_PER_ROW];
|
|
|
|
for (x = x0; x < x1; x++) {
|
|
c = src[x];
|
|
dst[x] = (uint8_t)((c << NIBBLE_SHIFT) | c);
|
|
}
|
|
}
|
|
surfaceMarkDirtyRect(stage, (int16_t)(x0 * AGI_SCREEN_PX_PER_AGI), (int16_t)(screenY + y0),
|
|
(int16_t)((x1 - x0) * AGI_SCREEN_PX_PER_AGI), (int16_t)(y1 - y0));
|
|
}
|
|
|
|
|
|
// Planar stages (Amiga, ST, X68000) have no bytes to write: each row goes
|
|
// out as runs of one colour through jlFillRect, which the ports do fast.
|
|
static void pushRuns(jlSurfaceT *stage, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t screenY) {
|
|
const uint8_t *src;
|
|
int16_t x;
|
|
int16_t y;
|
|
int16_t run;
|
|
uint8_t c;
|
|
|
|
for (y = y0; y < y1; y++) {
|
|
src = &gVis[(uint16_t)y * AGI_WIDTH];
|
|
x = x0;
|
|
while (x < x1) {
|
|
c = src[x];
|
|
run = 1;
|
|
while (x + run < x1 && src[x + run] == c) {
|
|
run++;
|
|
}
|
|
jlFillRect(stage, (int16_t)(x * AGI_SCREEN_PX_PER_AGI), (int16_t)(screenY + y), (uint16_t)(run * AGI_SCREEN_PX_PER_AGI), 1u, c);
|
|
x = (int16_t)(x + run);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Put back a rectangle of both buffers that saveRect kept.
|
|
static void restoreRect(const uint8_t *save, int16_t x, int16_t y, uint8_t w, uint8_t h) {
|
|
uint8_t row;
|
|
uint16_t dst;
|
|
|
|
for (row = 0u; row < h; row++) {
|
|
dst = (uint16_t)((uint16_t)(y + row) * AGI_WIDTH + (uint16_t)x);
|
|
memcpy(&gVis[dst], save, w);
|
|
save += w;
|
|
memcpy(&gPri[dst], save, w);
|
|
save += w;
|
|
}
|
|
}
|
|
|
|
|
|
// Keep a rectangle of both buffers, a row of colours then a row of
|
|
// priorities at a time (2 * w * h bytes).
|
|
static void saveRect(uint8_t *save, int16_t x, int16_t y, uint8_t w, uint8_t h) {
|
|
uint8_t row;
|
|
uint16_t src;
|
|
|
|
for (row = 0u; row < h; row++) {
|
|
src = (uint16_t)((uint16_t)(y + row) * AGI_WIDTH + (uint16_t)x);
|
|
memcpy(save, &gVis[src], w);
|
|
save += w;
|
|
memcpy(save, &gPri[src], w);
|
|
save += w;
|
|
}
|
|
}
|
|
|
|
|
|
// One phase of a shake beat: `show` on the screen for phaseMs (NULL, for
|
|
// want of memory, is only the wait).
|
|
static void shakePhase(const jlSurfaceT *show, uint32_t phaseMs) {
|
|
uint32_t start;
|
|
|
|
if (show != NULL) {
|
|
jlSurfaceCopy(jlStageGet(), show);
|
|
}
|
|
start = jlMillisElapsed();
|
|
do {
|
|
agiIdle();
|
|
} while (jlMillisElapsed() - start < phaseMs);
|
|
}
|
|
|
|
|
|
// Objects sort by their baseline, or by the top of their band when their
|
|
// priority is fixed.
|
|
static int16_t sortKey(const AgiObjectT *o) {
|
|
if ((o->flags & OBJ_FIX_PRIORITY) != 0u) {
|
|
return agiYForPriority(o->priority);
|
|
}
|
|
return o->y;
|
|
}
|
|
|
|
|
|
static void unblitTop(void) {
|
|
AgiObjectT *o;
|
|
|
|
gBlitCount--;
|
|
o = &gAgi->objects[gBlitStack[gBlitCount]];
|
|
restoreRect(o->saveUnder, o->blitX, o->blitY, o->blitW, o->blitH);
|
|
gSaveTop = (uint16_t)(o->saveUnder - gSaveArena);
|
|
o->blitted = false;
|
|
markDirty(o->blitX, o->blitY, o->blitW, o->blitH);
|
|
}
|
|
|
|
|
|
// ----- Public API (alphabetical) -----
|
|
|
|
// add.to.pic: draw a cel into the picture itself with a fixed priority
|
|
// (0 means the priority of its baseline) and, for margins 0..3, a box of
|
|
// that control value along its base.
|
|
void gfxAddToPic(uint8_t viewId, uint8_t loop, uint8_t cel, int16_t x, int16_t y, uint8_t priority, uint8_t margin) {
|
|
const AgiViewT *view;
|
|
const AgiCelT *c;
|
|
bool mirror;
|
|
int16_t top;
|
|
int16_t boxTop;
|
|
int16_t i;
|
|
|
|
view = agiViewGet(viewId);
|
|
c = agiViewCel(view, loop, cel, &mirror);
|
|
if (c == NULL) {
|
|
return;
|
|
}
|
|
if (priority < AGI_PRIORITY_MIN) {
|
|
priority = agiPriorityForY(y);
|
|
}
|
|
gfxEraseAll();
|
|
top = (int16_t)(y - (int16_t)c->height + 1);
|
|
drawCel(c, mirror, x, top, priority, false);
|
|
// The margin box (measured): an outline of the control value, as wide
|
|
// as the cel, from its baseline up to the top of the baseline's band.
|
|
if (margin < MARGIN_NONE) {
|
|
boxTop = agiYForPriority(agiPriorityForY(y));
|
|
for (i = 0; i < (int16_t)c->width; i++) {
|
|
int16_t yy;
|
|
|
|
if (x + i < 0 || x + i > AGI_MAX_X) {
|
|
continue;
|
|
}
|
|
for (yy = boxTop; yy <= y; yy++) {
|
|
if (yy == y || yy == boxTop || i == 0 || i == (int16_t)c->width - 1) {
|
|
gPri[(uint16_t)yy * AGI_WIDTH + (uint16_t)(x + i)] = margin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
markDirty(x, top, c->width, c->height);
|
|
gfxRedrawAll();
|
|
}
|
|
|
|
|
|
// The priority or control value at an AGI pixel, for collision tests.
|
|
uint8_t gfxControlAt(int16_t x, int16_t y) {
|
|
if (x < 0 || x > AGI_MAX_X || y < 0 || y > AGI_MAX_Y) {
|
|
return AGI_PRIORITY_MAX;
|
|
}
|
|
return gPri[(uint16_t)y * AGI_WIDTH + (uint16_t)x];
|
|
}
|
|
|
|
|
|
// draw.pic (clear first) and overlay.pic: draw a picture into the buffers
|
|
// under the objects. The screen keeps the old picture until show.pic.
|
|
void gfxDrawPic(uint8_t picId, bool clearFirst) {
|
|
uint8_t *data;
|
|
uint16_t length;
|
|
|
|
data = agiResLoad(&gAgi->game, AGI_RES_PIC, picId, &length);
|
|
if (data == NULL) {
|
|
return;
|
|
}
|
|
gfxEraseAll();
|
|
if (clearFirst) {
|
|
memset(gVis, AGI_PIC_BG_COLOR, AGI_PIXELS);
|
|
memset(gPri, AGI_PIC_BG_PRIORITY, AGI_PIXELS);
|
|
}
|
|
(void)agiPicDecode(gVis, gPri, data, length);
|
|
jlFree(data);
|
|
gDirtyCount = 0u;
|
|
gPicShown = false;
|
|
drawList(false);
|
|
gStaticBlits = gBlitCount;
|
|
drawList(true);
|
|
gDirtyCount = 0u;
|
|
}
|
|
|
|
|
|
// Draw the objects that are drawn but not yet blitted (after a change),
|
|
// keeping the not-updated ones under the updated ones.
|
|
void gfxDrawStatic(void) {
|
|
drawList(false);
|
|
gStaticBlits = gBlitCount;
|
|
drawList(true);
|
|
}
|
|
|
|
|
|
void gfxEraseAll(void) {
|
|
while (gBlitCount > 0u) {
|
|
unblitTop();
|
|
}
|
|
gStaticBlits = 0u;
|
|
}
|
|
|
|
|
|
void gfxEraseUpdating(void) {
|
|
while (gBlitCount > gStaticBlits) {
|
|
unblitTop();
|
|
}
|
|
}
|
|
|
|
|
|
void gfxFree(void) {
|
|
jlFree(gVis);
|
|
jlFree(gPri);
|
|
jlFree(gSaveArena);
|
|
gVis = NULL;
|
|
gPri = NULL;
|
|
gSaveArena = NULL;
|
|
}
|
|
|
|
|
|
bool gfxInit(void) {
|
|
gVis = (uint8_t *)jlAlloc(AGI_PIXELS);
|
|
gPri = (uint8_t *)jlAlloc(AGI_PIXELS);
|
|
gSaveArena = (uint8_t *)jlAlloc(SAVE_ARENA_BYTES);
|
|
if (gVis == NULL || gPri == NULL || gSaveArena == NULL) {
|
|
gfxFree();
|
|
return false;
|
|
}
|
|
memset(gVis, AGI_COLOR_BLACK, AGI_PIXELS);
|
|
memset(gPri, AGI_PIC_BG_PRIORITY, AGI_PIXELS);
|
|
gSaveTop = 0u;
|
|
gBlitCount = 0u;
|
|
gStaticBlits = 0u;
|
|
gDirtyCount = 0u;
|
|
gPicShown = false;
|
|
return true;
|
|
}
|
|
|
|
|
|
// Copy every area that changed since the last call to the stage.
|
|
void gfxPresentDirty(void) {
|
|
uint8_t i;
|
|
|
|
if (gPicShown && !gAgi->textMode) {
|
|
for (i = 0u; i < gDirtyCount; i++) {
|
|
pushRect(&gDirty[i]);
|
|
}
|
|
}
|
|
gDirtyCount = 0u;
|
|
}
|
|
|
|
|
|
// After a change to which objects are drawn or updated: take them all off
|
|
// and put them back in the right order.
|
|
void gfxRedrawAll(void) {
|
|
gfxEraseAll();
|
|
gfxDrawStatic();
|
|
gfxPresentDirty();
|
|
}
|
|
|
|
|
|
void gfxRedrawUpdating(void) {
|
|
drawList(true);
|
|
}
|
|
|
|
|
|
// Copy the whole picture area to the stage (after text mode or a window).
|
|
void gfxRestorePicture(void) {
|
|
GfxRectT r;
|
|
|
|
if (!gPicShown || gAgi->textMode) {
|
|
return;
|
|
}
|
|
r.x = 0;
|
|
r.y = 0;
|
|
r.w = AGI_WIDTH;
|
|
r.h = AGI_HEIGHT;
|
|
pushRect(&r);
|
|
}
|
|
|
|
|
|
// shake.screen: the whole screen jumps and comes back, beat after beat,
|
|
// as long as the game waits (keys, sound and the clock carry on). Each
|
|
// phase lasts SHAKE_PHASE_FRAMES of this display's frames, counted from
|
|
// when it was put up, so a machine that takes longer than that to change
|
|
// the whole screen (the IIgs) shows each phase for as long as that takes.
|
|
// Without the memory for the two screens it is just as long a pause.
|
|
void gfxShakeScreen(uint8_t times) {
|
|
jlSurfaceT *normal;
|
|
jlSurfaceT *shifted;
|
|
uint16_t beats;
|
|
uint32_t phaseMs;
|
|
|
|
normal = jlSurfaceCreate();
|
|
shifted = jlSurfaceCreate();
|
|
if (normal == NULL || shifted == NULL) {
|
|
jlSurfaceDestroy(shifted);
|
|
jlSurfaceDestroy(normal);
|
|
normal = NULL;
|
|
shifted = NULL;
|
|
} else {
|
|
jlSurfaceCopy(normal, jlStageGet());
|
|
buildShifted(shifted, normal);
|
|
}
|
|
phaseMs = (uint32_t)SHAKE_PHASE_FRAMES * MS_PER_SECOND / jlFrameHz();
|
|
beats = (uint16_t)((uint16_t)times * SHAKE_BEATS_PER_UNIT);
|
|
do {
|
|
shakePhase(shifted, phaseMs);
|
|
shakePhase(normal, phaseMs);
|
|
beats--;
|
|
} while (beats != 0u);
|
|
jlSurfaceDestroy(shifted);
|
|
jlSurfaceDestroy(normal);
|
|
}
|
|
|
|
|
|
// show.obj: a view's first cel at the bottom middle of the picture, on
|
|
// the stage only: drawn into the buffers over everything, copied out and
|
|
// the buffers put back (the caller restores the screen after).
|
|
void gfxShowObjectCel(uint8_t viewId) {
|
|
const AgiCelT *cel;
|
|
bool mirror;
|
|
GfxRectT r;
|
|
uint8_t *save;
|
|
|
|
cel = agiViewCel(agiViewGet(viewId), 0u, 0u, &mirror);
|
|
if (cel == NULL || cel->width > AGI_WIDTH || cel->height > AGI_HEIGHT) {
|
|
return;
|
|
}
|
|
r.x = (int16_t)((AGI_MAX_X - (int16_t)cel->width) / 2);
|
|
r.y = (int16_t)(AGI_HEIGHT - (int16_t)cel->height);
|
|
r.w = cel->width;
|
|
r.h = cel->height;
|
|
if ((uint32_t)gSaveTop + 2u * (uint32_t)r.w * (uint32_t)r.h > SAVE_ARENA_BYTES) {
|
|
return;
|
|
}
|
|
save = &gSaveArena[gSaveTop];
|
|
saveRect(save, r.x, r.y, cel->width, cel->height);
|
|
drawCel(cel, mirror, r.x, r.y, AGI_PRIORITY_MAX, true);
|
|
pushRect(&r);
|
|
restoreRect(save, r.x, r.y, cel->width, cel->height);
|
|
}
|
|
|
|
|
|
void gfxShowPic(void) {
|
|
gPicShown = true;
|
|
gDirtyCount = 0u;
|
|
gfxRestorePicture();
|
|
}
|
|
|
|
|
|
// show.pri.screen: the priority buffer as colours until a key.
|
|
void gfxShowPriority(void) {
|
|
uint8_t *saved;
|
|
|
|
saved = gVis;
|
|
gVis = gPri;
|
|
gfxRestorePicture();
|
|
gVis = saved;
|
|
(void)inputWaitKey();
|
|
gfxRestorePicture();
|
|
}
|