537 lines
17 KiB
C
537 lines
17 KiB
C
// AGI v2 PICTURE decoding (AGI specification chapter 7).
|
|
//
|
|
// A picture is a byte stream of drawing actions, 0xF0..0xFA, each followed
|
|
// by argument bytes below 0xF0, ending at 0xFF. It draws into the visual
|
|
// buffer and the priority buffer (160x168, a byte per AGI pixel); each
|
|
// action draws to whichever of the two is enabled.
|
|
|
|
#include "agi.h"
|
|
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
|
|
#define PIC_FIRST_ACTION 0xF0u
|
|
#define PIC_SET_COLOR 0xF0u
|
|
#define PIC_NO_COLOR 0xF1u
|
|
#define PIC_SET_PRIORITY 0xF2u
|
|
#define PIC_NO_PRIORITY 0xF3u
|
|
#define PIC_Y_CORNER 0xF4u
|
|
#define PIC_X_CORNER 0xF5u
|
|
#define PIC_ABS_LINE 0xF6u
|
|
#define PIC_REL_LINE 0xF7u
|
|
#define PIC_FILL 0xF8u
|
|
#define PIC_SET_PEN 0xF9u
|
|
#define PIC_PLOT_PEN 0xFAu
|
|
#define PIC_END 0xFFu
|
|
|
|
// Relative line displacement byte: bit 7 the x sign, bits 4-6 x, bit 3
|
|
// the y sign, bits 0-2 y.
|
|
#define REL_X_SIGN 0x80u
|
|
#define REL_X_SHIFT 4u
|
|
#define REL_Y_SIGN 0x08u
|
|
#define REL_MAG_MASK 0x07u
|
|
|
|
// Pen byte: bit 5 splatter, bit 4 rectangle, bits 0-2 the size.
|
|
#define PEN_SPLATTER 0x20u
|
|
#define PEN_RECTANGLE 0x10u
|
|
#define PEN_SIZE_MASK 0x07u
|
|
#define PEN_SIZES 8u
|
|
#define PEN_MAX_ROWS 15u
|
|
#define PEN_ROW_MSB 0x80u
|
|
#define PEN_ROW_FULL 0xFFu
|
|
#define TEXTURE_BITS_LAST 254u
|
|
#define TEXTURE_CODE_SHIFT 1u
|
|
#define BIT_BYTE_SHIFT 3u
|
|
#define BIT_INDEX_MASK 7u
|
|
|
|
// Flood fill: a scanline fill with an explicit stack of row spans.
|
|
#define FILL_STACK_MAX 1024u
|
|
|
|
|
|
typedef struct {
|
|
uint8_t *visual;
|
|
uint8_t *priority;
|
|
bool visualOn;
|
|
bool priorityOn;
|
|
uint8_t color;
|
|
uint8_t priorityColor;
|
|
uint8_t pen;
|
|
} PicDrawT;
|
|
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static void drawLine(const PicDrawT *d, int16_t x0, int16_t y0, int16_t x1, int16_t y1);
|
|
static void fill(const PicDrawT *d, int16_t x, int16_t y);
|
|
static bool fillable(const PicDrawT *d, uint16_t idx);
|
|
static uint16_t opAbsLine(const PicDrawT *d, const uint8_t *data, uint16_t length, uint16_t pos);
|
|
static uint16_t opCorner(const PicDrawT *d, const uint8_t *data, uint16_t length, uint16_t pos, bool yFirst);
|
|
static uint16_t opPlotPen(const PicDrawT *d, const uint8_t *data, uint16_t length, uint16_t pos);
|
|
static uint16_t opRelLine(const PicDrawT *d, const uint8_t *data, uint16_t length, uint16_t pos);
|
|
static void plot(const PicDrawT *d, int16_t x, int16_t y);
|
|
static void plotPen(const PicDrawT *d, int16_t x, int16_t y, uint8_t texture);
|
|
|
|
|
|
// ----- Tables -----
|
|
|
|
// The circle pens of each size, a row per byte with the leftmost pixel in
|
|
// bit 7: size n is n+1 wide and 2n+1 tall (specification 7.2).
|
|
static const uint8_t kCircleRows[PEN_SIZES][PEN_MAX_ROWS] = {
|
|
{ 0x80 },
|
|
{ 0xC0, 0xC0, 0xC0 },
|
|
{ 0x40, 0xE0, 0xE0, 0xE0, 0x40 },
|
|
{ 0x60, 0x60, 0xF0, 0xF0, 0xF0, 0x60, 0x60 },
|
|
{ 0x20, 0x70, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0x70, 0x20 },
|
|
{ 0x30, 0x78, 0x78, 0x78, 0xFC, 0xFC, 0xFC, 0x78, 0x78, 0x78, 0x30 },
|
|
{ 0x38, 0x7C, 0x7C, 0x7C, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x7C, 0x7C, 0x7C, 0x38 },
|
|
{ 0x18, 0x3C, 0x7E, 0x7E, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x7E, 0x7E, 0x3C, 0x18 }
|
|
};
|
|
|
|
// Splatter pens: 256 bits of texture and each texture number's starting
|
|
// bit within them (specification 7.2).
|
|
static const uint8_t kTextureBits[] = {
|
|
0x20, 0x94, 0x02, 0x24, 0x90, 0x82, 0xa4, 0xa2,
|
|
0x82, 0x09, 0x0a, 0x22, 0x12, 0x10, 0x42, 0x14,
|
|
0x91, 0x4a, 0x91, 0x11, 0x08, 0x12, 0x25, 0x10,
|
|
0x22, 0xa8, 0x14, 0x24, 0x00, 0x50, 0x24, 0x04
|
|
};
|
|
|
|
static const uint8_t kTextureStart[] = {
|
|
0x00, 0x18, 0x30, 0xc4, 0xdc, 0x65, 0xeb, 0x48,
|
|
0x60, 0xbd, 0x89, 0x04, 0x0a, 0xf4, 0x7d, 0x6d,
|
|
0x85, 0xb0, 0x8e, 0x95, 0x1f, 0x22, 0x0d, 0xdf,
|
|
0x2a, 0x78, 0xd5, 0x73, 0x1c, 0xb4, 0x40, 0xa1,
|
|
0xb9, 0x3c, 0xca, 0x58, 0x92, 0x34, 0xcc, 0xce,
|
|
0xd7, 0x42, 0x90, 0x0f, 0x8b, 0x7f, 0x32, 0xed,
|
|
0x5c, 0x9d, 0xc8, 0x99, 0xad, 0x4e, 0x56, 0xa6,
|
|
0xf7, 0x68, 0xb7, 0x25, 0x82, 0x37, 0x3a, 0x51,
|
|
0x69, 0x26, 0x38, 0x52, 0x9e, 0x9a, 0x4f, 0xa7,
|
|
0x43, 0x10, 0x80, 0xee, 0x3d, 0x59, 0x35, 0xcf,
|
|
0x79, 0x74, 0xb5, 0xa2, 0xb1, 0x96, 0x23, 0xe0,
|
|
0xbe, 0x05, 0xf5, 0x6e, 0x19, 0xc5, 0x66, 0x49,
|
|
0xf0, 0xd1, 0x54, 0xa9, 0x70, 0x4b, 0xa4, 0xe2,
|
|
0xe6, 0xe5, 0xab, 0xe4, 0xd2, 0xaa, 0x4c, 0xe3,
|
|
0x06, 0x6f, 0xc6, 0x4a, 0x75, 0xa3, 0x97, 0xe1
|
|
};
|
|
|
|
|
|
// ----- Module state -----
|
|
|
|
// fill's seed stack, in byte coordinates (the picture is 160x168) to keep
|
|
// it small in the IIgs's bank 0.
|
|
static uint8_t gFillX[FILL_STACK_MAX];
|
|
static uint8_t gFillY[FILL_STACK_MAX];
|
|
|
|
|
|
// ----- Internal helpers (alphabetical) -----
|
|
|
|
// A line steps a pixel at a time along its longer side; the shorter side
|
|
// moves when the error it carries (starting at half the longer side's
|
|
// length, growing by the shorter side's each step) reaches the longer
|
|
// side's length -- the exact fraction rounded to nearest, an exact half
|
|
// rounding away from the start, so the last of the longer side + 1 points
|
|
// is the end point. Sierra's interpreter carries the error in a byte
|
|
// (AGI_QUIRK_LINE_BYTE_ERROR): when it passes 255 the step is lost, and a
|
|
// line long on both sides ends short of its end point (measured on 2.435:
|
|
// (0,0) to (159,167) stalls two rows in ten and ends at (134,167)).
|
|
static void drawLine(const PicDrawT *d, int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
|
|
int16_t adx;
|
|
int16_t ady;
|
|
int16_t sx;
|
|
int16_t sy;
|
|
int16_t major;
|
|
int16_t minorLen;
|
|
int16_t majorX;
|
|
int16_t majorY;
|
|
int16_t minorX;
|
|
int16_t minorY;
|
|
int16_t i;
|
|
uint16_t err;
|
|
bool byteError;
|
|
|
|
adx = (int16_t)((x1 < x0) ? x0 - x1 : x1 - x0);
|
|
ady = (int16_t)((y1 < y0) ? y0 - y1 : y1 - y0);
|
|
sx = (int16_t)((x1 < x0) ? -1 : 1);
|
|
sy = (int16_t)((y1 < y0) ? -1 : 1);
|
|
if (adx > ady) {
|
|
major = adx;
|
|
minorLen = ady;
|
|
majorX = sx;
|
|
majorY = 0;
|
|
} else {
|
|
major = ady;
|
|
minorLen = adx;
|
|
majorX = 0;
|
|
majorY = sy;
|
|
}
|
|
minorX = (int16_t)(sx - majorX);
|
|
minorY = (int16_t)(sy - majorY);
|
|
byteError = agiQuirk(AGI_QUIRK_LINE_BYTE_ERROR);
|
|
err = (uint16_t)(major / 2);
|
|
for (i = 0; i <= major; i++) {
|
|
plot(d, x0, y0);
|
|
x0 = (int16_t)(x0 + majorX);
|
|
y0 = (int16_t)(y0 + majorY);
|
|
err = (uint16_t)(err + (uint16_t)minorLen);
|
|
if (byteError) {
|
|
err = (uint8_t)err;
|
|
}
|
|
if (err >= (uint16_t)major) {
|
|
err = (uint16_t)(err - (uint16_t)major);
|
|
x0 = (int16_t)(x0 + minorX);
|
|
y0 = (int16_t)(y0 + minorY);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Flood fill from (x, y) over the pixels fillable() accepts, painting
|
|
// whichever buffers are enabled.
|
|
static void fill(const PicDrawT *d, int16_t x, int16_t y) {
|
|
uint16_t sp;
|
|
int16_t left;
|
|
int16_t right;
|
|
int16_t scan;
|
|
int16_t ny;
|
|
uint16_t row;
|
|
bool open;
|
|
|
|
if (x < 0 || x > AGI_MAX_X || y < 0 || y > AGI_MAX_Y) {
|
|
return;
|
|
}
|
|
// Filling with the colour being searched for would never end.
|
|
if (d->visualOn) {
|
|
if (d->color == AGI_PIC_BG_COLOR) {
|
|
return;
|
|
}
|
|
} else if (!d->priorityOn || d->priorityColor == AGI_PIC_BG_PRIORITY) {
|
|
return;
|
|
}
|
|
gFillX[0] = (uint8_t)x;
|
|
gFillY[0] = (uint8_t)y;
|
|
sp = 1u;
|
|
while (sp > 0u) {
|
|
sp--;
|
|
x = gFillX[sp];
|
|
y = gFillY[sp];
|
|
row = (uint16_t)y * AGI_WIDTH;
|
|
if (!fillable(d, (uint16_t)(row + (uint16_t)x))) {
|
|
continue;
|
|
}
|
|
left = x;
|
|
while (left > 0 && fillable(d, (uint16_t)(row + (uint16_t)(left - 1)))) {
|
|
left--;
|
|
}
|
|
right = x;
|
|
while (right < AGI_MAX_X && fillable(d, (uint16_t)(row + (uint16_t)(right + 1)))) {
|
|
right++;
|
|
}
|
|
for (scan = left; scan <= right; scan++) {
|
|
if (d->visualOn) {
|
|
d->visual[row + (uint16_t)scan] = d->color;
|
|
}
|
|
if (d->priorityOn) {
|
|
d->priority[row + (uint16_t)scan] = d->priorityColor;
|
|
}
|
|
}
|
|
for (ny = (int16_t)(y - 1); ny <= (int16_t)(y + 1); ny = (int16_t)(ny + 2)) {
|
|
if (ny < 0 || ny > AGI_MAX_Y) {
|
|
continue;
|
|
}
|
|
open = false;
|
|
for (scan = left; scan <= right; scan++) {
|
|
if (fillable(d, (uint16_t)((uint16_t)ny * AGI_WIDTH + (uint16_t)scan))) {
|
|
if (!open && sp < FILL_STACK_MAX) {
|
|
gFillX[sp] = (uint8_t)scan;
|
|
gFillY[sp] = (uint8_t)ny;
|
|
sp++;
|
|
}
|
|
open = true;
|
|
} else {
|
|
open = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// A fill spreads over white in the visual buffer when that is being
|
|
// drawn, otherwise over priority 4 in the priority buffer.
|
|
static bool fillable(const PicDrawT *d, uint16_t idx) {
|
|
if (d->visualOn) {
|
|
return d->visual[idx] == AGI_PIC_BG_COLOR;
|
|
}
|
|
return d->priority[idx] == AGI_PIC_BG_PRIORITY;
|
|
}
|
|
|
|
|
|
// The absolute line action: a start point, then a line to each point.
|
|
static uint16_t opAbsLine(const PicDrawT *d, const uint8_t *data, uint16_t length, uint16_t pos) {
|
|
int16_t x;
|
|
int16_t y;
|
|
|
|
if (pos + 1u >= length || data[pos] >= PIC_FIRST_ACTION || data[pos + 1u] >= PIC_FIRST_ACTION) {
|
|
return pos;
|
|
}
|
|
x = data[pos];
|
|
y = data[pos + 1u];
|
|
pos = (uint16_t)(pos + 2u);
|
|
plot(d, x, y);
|
|
while (pos + 1u < length && data[pos] < PIC_FIRST_ACTION && data[pos + 1u] < PIC_FIRST_ACTION) {
|
|
drawLine(d, x, y, data[pos], data[pos + 1u]);
|
|
x = data[pos];
|
|
y = data[pos + 1u];
|
|
pos = (uint16_t)(pos + 2u);
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
// The corner actions: a start point, then alternately a new y and a new x
|
|
// (or x first), a line drawn to each.
|
|
static uint16_t opCorner(const PicDrawT *d, const uint8_t *data, uint16_t length, uint16_t pos, bool yFirst) {
|
|
int16_t x;
|
|
int16_t y;
|
|
bool changeY;
|
|
|
|
if (pos + 1u >= length || data[pos] >= PIC_FIRST_ACTION || data[pos + 1u] >= PIC_FIRST_ACTION) {
|
|
return pos;
|
|
}
|
|
x = data[pos];
|
|
y = data[pos + 1u];
|
|
pos = (uint16_t)(pos + 2u);
|
|
changeY = yFirst;
|
|
plot(d, x, y);
|
|
while (pos < length && data[pos] < PIC_FIRST_ACTION) {
|
|
if (changeY) {
|
|
drawLine(d, x, y, x, data[pos]);
|
|
y = data[pos];
|
|
} else {
|
|
drawLine(d, x, y, data[pos], y);
|
|
x = data[pos];
|
|
}
|
|
pos++;
|
|
changeY = !changeY;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
static uint16_t opPlotPen(const PicDrawT *d, const uint8_t *data, uint16_t length, uint16_t pos) {
|
|
uint8_t texture;
|
|
|
|
texture = 0u;
|
|
for (;;) {
|
|
if ((d->pen & PEN_SPLATTER) != 0u) {
|
|
if (pos >= length || data[pos] >= PIC_FIRST_ACTION) {
|
|
break;
|
|
}
|
|
texture = data[pos];
|
|
pos++;
|
|
}
|
|
if (pos + 1u >= length || data[pos] >= PIC_FIRST_ACTION || data[pos + 1u] >= PIC_FIRST_ACTION) {
|
|
break;
|
|
}
|
|
plotPen(d, data[pos], data[pos + 1u], texture);
|
|
pos = (uint16_t)(pos + 2u);
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
// The relative line action: a start point, then a line per byte to a
|
|
// point at most 7 pixels away on each axis.
|
|
static uint16_t opRelLine(const PicDrawT *d, const uint8_t *data, uint16_t length, uint16_t pos) {
|
|
int16_t x;
|
|
int16_t y;
|
|
int16_t nx;
|
|
int16_t ny;
|
|
uint8_t b;
|
|
|
|
if (pos + 1u >= length || data[pos] >= PIC_FIRST_ACTION || data[pos + 1u] >= PIC_FIRST_ACTION) {
|
|
return pos;
|
|
}
|
|
x = data[pos];
|
|
y = data[pos + 1u];
|
|
pos = (uint16_t)(pos + 2u);
|
|
plot(d, x, y);
|
|
while (pos < length && data[pos] < PIC_FIRST_ACTION) {
|
|
b = data[pos++];
|
|
nx = (int16_t)((b >> REL_X_SHIFT) & REL_MAG_MASK);
|
|
ny = (int16_t)(b & REL_MAG_MASK);
|
|
if ((b & REL_X_SIGN) != 0u) {
|
|
nx = (int16_t)-nx;
|
|
}
|
|
if ((b & REL_Y_SIGN) != 0u) {
|
|
ny = (int16_t)-ny;
|
|
}
|
|
nx = (int16_t)(x + nx);
|
|
ny = (int16_t)(y + ny);
|
|
drawLine(d, x, y, nx, ny);
|
|
x = nx;
|
|
y = ny;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
static void plot(const PicDrawT *d, int16_t x, int16_t y) {
|
|
uint16_t idx;
|
|
|
|
if (x < 0 || x > AGI_MAX_X || y < 0 || y > AGI_MAX_Y) {
|
|
return;
|
|
}
|
|
idx = (uint16_t)((uint16_t)y * AGI_WIDTH + (uint16_t)x);
|
|
if (d->visualOn) {
|
|
d->visual[idx] = d->color;
|
|
}
|
|
if (d->priorityOn) {
|
|
d->priority[idx] = d->priorityColor;
|
|
}
|
|
}
|
|
|
|
|
|
// Plot the current pen centred on (x, y): a rectangle or circle size+1
|
|
// wide and 2*size+1 tall, solid or masked by a texture's bits.
|
|
static void plotPen(const PicDrawT *d, int16_t x, int16_t y, uint8_t texture) {
|
|
uint8_t size;
|
|
uint8_t rows;
|
|
uint8_t r;
|
|
uint8_t c;
|
|
uint8_t bits;
|
|
uint8_t bitPos;
|
|
int16_t left;
|
|
int16_t top;
|
|
bool circle;
|
|
bool splatter;
|
|
|
|
size = (uint8_t)(d->pen & PEN_SIZE_MASK);
|
|
rows = (uint8_t)(size * 2u + 1u);
|
|
circle = (d->pen & PEN_RECTANGLE) == 0u;
|
|
splatter = (d->pen & PEN_SPLATTER) != 0u;
|
|
left = (int16_t)(x - (int16_t)((size + 1u) / 2u));
|
|
top = (int16_t)(y - (int16_t)size);
|
|
bitPos = kTextureStart[(texture >> TEXTURE_CODE_SHIFT) % sizeof(kTextureStart)];
|
|
for (r = 0u; r < rows; r++) {
|
|
bits = circle ? kCircleRows[size][r] : (uint8_t)(PEN_ROW_FULL << (PEN_SIZES - 1u - size));
|
|
for (c = 0u; c <= size; c++) {
|
|
if ((bits & (uint8_t)(PEN_ROW_MSB >> c)) == 0u) {
|
|
continue;
|
|
}
|
|
if (splatter) {
|
|
bool on;
|
|
|
|
on = (kTextureBits[bitPos >> BIT_BYTE_SHIFT] & (uint8_t)(PEN_ROW_MSB >> (bitPos & BIT_INDEX_MASK))) != 0u;
|
|
// The texture wraps at bit 254, not 255 (specification 7.2).
|
|
bitPos = (uint8_t)((bitPos >= TEXTURE_BITS_LAST) ? 0u : bitPos + 1u);
|
|
if (!on) {
|
|
continue;
|
|
}
|
|
}
|
|
plot(d, (int16_t)(left + (int16_t)c), (int16_t)(top + (int16_t)r));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ----- Public API (alphabetical) -----
|
|
|
|
// Draw a picture over the buffers' current contents (draw.pic clears them
|
|
// first, overlay.pic does not). Returns false on a malformed stream, with
|
|
// whatever was drawn so far left in place.
|
|
bool agiPicDecode(uint8_t *visual, uint8_t *priority, const uint8_t *data, uint16_t length) {
|
|
PicDrawT d;
|
|
uint16_t pos;
|
|
uint8_t op;
|
|
|
|
d.visual = visual;
|
|
d.priority = priority;
|
|
d.visualOn = false;
|
|
d.priorityOn = false;
|
|
d.color = 0u;
|
|
d.priorityColor = 0u;
|
|
d.pen = 0u;
|
|
pos = 0u;
|
|
while (pos < length) {
|
|
op = data[pos++];
|
|
switch (op) {
|
|
case PIC_SET_COLOR:
|
|
if (pos >= length) {
|
|
return false;
|
|
}
|
|
d.color = (uint8_t)(data[pos++] & AGI_COLOR_MASK);
|
|
d.visualOn = true;
|
|
break;
|
|
case PIC_NO_COLOR:
|
|
d.visualOn = false;
|
|
break;
|
|
case PIC_SET_PRIORITY:
|
|
if (pos >= length) {
|
|
return false;
|
|
}
|
|
d.priorityColor = (uint8_t)(data[pos++] & AGI_COLOR_MASK);
|
|
d.priorityOn = true;
|
|
break;
|
|
case PIC_NO_PRIORITY:
|
|
d.priorityOn = false;
|
|
break;
|
|
case PIC_Y_CORNER:
|
|
case PIC_X_CORNER:
|
|
pos = opCorner(&d, data, length, pos, op == PIC_Y_CORNER);
|
|
break;
|
|
case PIC_ABS_LINE:
|
|
pos = opAbsLine(&d, data, length, pos);
|
|
break;
|
|
case PIC_REL_LINE:
|
|
pos = opRelLine(&d, data, length, pos);
|
|
break;
|
|
case PIC_FILL:
|
|
while (pos + 1u < length && data[pos] < PIC_FIRST_ACTION && data[pos + 1u] < PIC_FIRST_ACTION) {
|
|
fill(&d, data[pos], data[pos + 1u]);
|
|
pos = (uint16_t)(pos + 2u);
|
|
}
|
|
break;
|
|
case PIC_SET_PEN:
|
|
if (pos >= length) {
|
|
return false;
|
|
}
|
|
d.pen = data[pos++];
|
|
break;
|
|
case PIC_PLOT_PEN:
|
|
pos = opPlotPen(&d, data, length, pos);
|
|
break;
|
|
case PIC_END:
|
|
return true;
|
|
default:
|
|
// Unknown action bytes are skipped with their arguments.
|
|
while (pos < length && data[pos] < PIC_FIRST_ACTION) {
|
|
pos++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
uint8_t agiPriorityForY(int16_t y) {
|
|
if (y < AGI_PRI_BAND_TOP) {
|
|
return AGI_PRIORITY_MIN;
|
|
}
|
|
if (y > AGI_MAX_Y) {
|
|
y = AGI_MAX_Y;
|
|
}
|
|
return (uint8_t)(AGI_PRI_FIRST_BAND + (uint8_t)((y - AGI_PRI_BAND_TOP) / AGI_PRI_BAND_HEIGHT));
|
|
}
|
|
|
|
|
|
// The lowest y that has a priority: where an object with that fixed
|
|
// priority sorts among the others when they are drawn.
|
|
int16_t agiYForPriority(uint8_t priority) {
|
|
if (priority <= AGI_PRIORITY_MIN) {
|
|
return 0;
|
|
}
|
|
return (int16_t)(AGI_PRI_BAND_TOP + (int16_t)(priority - AGI_PRI_FIRST_BAND) * AGI_PRI_BAND_HEIGHT);
|
|
}
|