joeyagi/agiObj.c
2026-10-20 17:47:07 -05:00

729 lines
24 KiB
C

// Screen objects: cel cycling, motion and what stops it (AGI
// specification 4.2's object commands and 2.7's control lines).
//
// Each interpreter cycle, before logic 0 runs, objPlanMotion turns the
// special motions (wander, follow.ego, move.obj) into a direction; after
// it, objUpdateAll erases the updated objects, steps their cels and moves
// them a step in their direction, then blits them again. A step is
// refused by an unconditional barrier (control 0), a conditional one
// (control 1) unless the object ignores blocks, water rules, the block
// rectangle and other objects' baselines; objects at fixed priority 15
// ignore control lines. Leaving the screen or crossing the horizon stops
// the object at the edge and reports it in v2 (ego) or v4/v5.
#include "agi.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "joey/core.h"
// Sierra's wander: a new heading lasts rand % 51 cycles, drawn until it is
// 6 or more. follow.ego's detour count starts at FOLLOW_START, its own
// mark for "head straight for ego".
#define WANDER_COUNT_MIN 6u
#define WANDER_COUNT_LIMIT 51u
#define FOLLOW_START 0xFFu
// A mouse walk's x for "past the left edge" (a click left of half ego's
// width): no picture x is 255.
#define MOUSE_OFF_LEFT 0xFFu
#define STEP_DEFAULT 1u
#define SPIRAL_LEGS 4u
#define SPIRAL_MAX (2u * AGI_WIDTH)
#define SIGN_STATES 3u
// Automatic loops by direction (specification 4.2): views with two or
// three loops face right or left, with four also up and down; others keep
// their loop, except under King's Quest IV's 3.002.086, which turns any
// count as four.
// LOOP_KEEP leaves the loop alone.
#define LOOP_KEEP 0xFFu
#define LOOP_RIGHT 0u
#define LOOP_LEFT 1u
#define LOOP_DOWN 2u
#define LOOP_UP 3u
#define LOOPS_FOUR 4u
#define LOOPS_THREE 3u
#define LOOPS_TWO 2u
// The step count of an object stepping this cycle (objUpdateAll counts it
// down to 0 then steps).
#define STEP_DUE 1u
// ----- Prototypes -----
static void advanceCel(uint8_t n);
static bool blockedByObject(uint8_t n, int16_t x, int16_t y);
static uint8_t clampToScreen(AgiObjectT *o, int16_t *x, int16_t *y);
static void followEgo(uint8_t n);
static bool legalPosition(uint8_t n, int16_t x, int16_t y);
static void moveObject(uint8_t n);
static bool onScreen(const AgiObjectT *o, int16_t x, int16_t y);
static void reportEdge(uint8_t n, uint8_t edge);
static void selectLoop(AgiObjectT *o);
static void stopSpecialMotion(uint8_t n);
static void wander(AgiObjectT *o);
// ----- Tables -----
// A step in each direction.
static const int8_t kDirDx[AGI_DIR_COUNT] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
static const int8_t kDirDy[AGI_DIR_COUNT] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
static const uint8_t kLoopFew[AGI_DIR_COUNT] = { LOOP_KEEP, LOOP_KEEP, LOOP_RIGHT, LOOP_RIGHT, LOOP_RIGHT, LOOP_KEEP, LOOP_LEFT, LOOP_LEFT, LOOP_LEFT };
static const uint8_t kLoopFour[AGI_DIR_COUNT] = { LOOP_KEEP, LOOP_UP, LOOP_RIGHT, LOOP_RIGHT, LOOP_RIGHT, LOOP_DOWN, LOOP_LEFT, LOOP_LEFT, LOOP_LEFT };
// Direction for each (sign of dx, sign of dy), indexed [dy + 1][dx + 1].
static const uint8_t kDirFromSigns[SIGN_STATES][SIGN_STATES] = {
{ AGI_DIR_NW, AGI_DIR_N, AGI_DIR_NE },
{ AGI_DIR_W, AGI_DIR_NONE, AGI_DIR_E },
{ AGI_DIR_SW, AGI_DIR_S, AGI_DIR_SE }
};
// objFixPosition's spiral: left, down, right, up.
static const int8_t kSpiralDx[SPIRAL_LEGS] = { -1, 0, 1, 0 };
static const int8_t kSpiralDy[SPIRAL_LEGS] = { 0, 1, 0, -1 };
// ----- Internal helpers (alphabetical) -----
static void advanceCel(uint8_t n) {
AgiObjectT *o;
uint8_t last;
o = &gAgi->objects[n];
if (o->view == NULL || o->loop >= o->view->loopCount) {
return;
}
last = o->view->loops[o->loop].celCount;
if (last == 0u) {
return;
}
last--;
switch (o->cycleMode) {
case AGI_CYCLE_NORMAL:
o->cel = (o->cel >= last) ? 0u : (uint8_t)(o->cel + 1u);
break;
case AGI_CYCLE_REVERSE:
o->cel = (o->cel == 0u || o->cel > last) ? last : (uint8_t)(o->cel - 1u);
break;
case AGI_CYCLE_END_LOOP:
case AGI_CYCLE_REVERSE_LOOP:
// The flag is set as the end cel is reached.
if (o->cycleMode == AGI_CYCLE_END_LOOP && o->cel < last) {
o->cel++;
} else if (o->cycleMode == AGI_CYCLE_REVERSE_LOOP && o->cel > 0u) {
o->cel--;
}
if ((o->cycleMode == AGI_CYCLE_END_LOOP) ? (o->cel >= last) : (o->cel == 0u)) {
gAgi->flags[o->parm[PARM_LOOP_FLAG]] = 1u;
o->flags = (uint16_t)(o->flags & (uint16_t)~OBJ_CYCLING);
o->cycleMode = AGI_CYCLE_NORMAL;
}
break;
default:
break;
}
objSetCel(n, o->cel);
}
// Another object's baseline in the way: the two baselines overlap (or
// touch) and are on the same line, or this step crosses the other's.
static bool blockedByObject(uint8_t n, int16_t x, int16_t y) {
const AgiObjectT *o;
const AgiObjectT *other;
uint8_t i;
o = &gAgi->objects[n];
if ((o->flags & OBJ_IGNORE_OBJS) != 0u) {
return false;
}
for (i = 0u; i < gAgi->objectCount; i++) {
other = &gAgi->objects[i];
if (i == n || (other->flags & (OBJ_ANIMATED | OBJ_DRAWN)) != (OBJ_ANIMATED | OBJ_DRAWN) ||
(other->flags & OBJ_IGNORE_OBJS) != 0u) {
continue;
}
if (x + (int16_t)o->width < other->x || other->x + (int16_t)other->width < x) {
continue;
}
if (y == other->y || (o->y < other->y && y > other->y) || (o->y > other->y && y < other->y)) {
return true;
}
}
return false;
}
// Keep a position on the screen and below the horizon; returns the edge
// it ran into, if any.
static uint8_t clampToScreen(AgiObjectT *o, int16_t *x, int16_t *y) {
uint8_t edge;
bool left;
// King's Quest IV's 3.002.086 counts x = 0 as past the left edge too
// (its room 135 hen walks off there).
edge = AGI_EDGE_NONE;
left = (gAgi->game.interpreterVersion == AGI_VERSION_KQ4) ? *x <= 0 : *x < 0;
if (left) {
*x = 0;
edge = AGI_EDGE_LEFT;
} else if (*x + (int16_t)o->width > AGI_WIDTH) {
*x = (int16_t)(AGI_WIDTH - (int16_t)o->width);
edge = AGI_EDGE_RIGHT;
}
if (*y > AGI_MAX_Y) {
*y = AGI_MAX_Y;
edge = AGI_EDGE_BOTTOM;
} else if ((o->flags & OBJ_IGNORE_HORIZON) == 0u && *y <= (int16_t)gAgi->horizon) {
*y = (int16_t)(gAgi->horizon + 1u);
edge = AGI_EDGE_TOP;
} else if (*y - (int16_t)o->height + 1 < 0) {
*y = (int16_t)(o->height - 1u);
edge = AGI_EDGE_TOP;
}
return edge;
}
// follow.ego, as Sierra's (2.915): head for ego's middle until within
// the distance follow.ego gave; there, stop and set its flag. After a step
// that left it standing, a random heading (never 0) and a detour of
// rand % (half the x and y distance to ego, + 1) steps, drawn until at
// least its step size, worked off a step size at a time; a count Sierra's
// signed byte compare finds below the step size ends the detour.
static void followEgo(uint8_t n) {
AgiObjectT *o;
const AgiObjectT *ego;
int16_t objX;
int16_t egoX;
uint16_t span;
uint8_t dir;
uint8_t *count;
o = &gAgi->objects[n];
ego = &gAgi->objects[AGI_EGO];
objX = (int16_t)(o->x + o->width / 2u);
egoX = (int16_t)(ego->x + ego->width / 2u);
dir = objDirectionTo(objX, o->y, egoX, ego->y, o->parm[PARM_FOLLOW_STEP]);
if (dir == AGI_DIR_NONE) {
stopSpecialMotion(n);
return;
}
count = &o->parm[PARM_FOLLOW_COUNT];
if (*count == FOLLOW_START) {
*count = 0u;
} else if ((o->flags & OBJ_DIDNT_MOVE) != 0u) {
do {
o->direction = (uint8_t)(agiRandom() % AGI_DIR_COUNT);
} while (o->direction == AGI_DIR_NONE);
span = (uint16_t)((uint16_t)(abs(o->y - ego->y) + abs(objX - egoX)) / 2u + 1u);
if (span <= o->stepSize) {
*count = o->stepSize;
return;
}
do {
*count = (uint8_t)(agiRandom() % span);
} while (*count < o->stepSize);
return;
}
if (*count == 0u) {
o->direction = dir;
} else if ((int8_t)*count < (int8_t)o->stepSize) {
*count = 0u;
} else {
*count = (uint8_t)(*count - o->stepSize);
}
}
// Whether the object may stand at (x, y): what its baseline covers in the
// priority screen, the block rectangle and other objects. Sets f0/f3 for
// ego as a side effect.
static bool legalPosition(uint8_t n, int16_t x, int16_t y) {
const AgiObjectT *o;
int16_t i;
uint8_t c;
bool anyWater;
bool allWater;
bool signal;
bool insideNow;
bool insideNew;
o = &gAgi->objects[n];
anyWater = false;
allWater = true;
signal = false;
if (!((o->flags & OBJ_FIX_PRIORITY) != 0u && o->priority == AGI_PRIORITY_MAX)) {
for (i = 0; i < (int16_t)o->width; i++) {
c = gfxControlAt((int16_t)(x + i), y);
if (c == AGI_CTL_BARRIER) {
return false;
}
if (c == AGI_CTL_BLOCK && (o->flags & OBJ_IGNORE_BLOCKS) == 0u) {
return false;
}
if (c == AGI_CTL_WATER) {
anyWater = true;
} else {
allWater = false;
}
if (c == AGI_CTL_SIGNAL) {
signal = true;
}
}
if ((o->flags & OBJ_ON_WATER) != 0u && !allWater) {
return false;
}
if ((o->flags & OBJ_ON_LAND) != 0u && anyWater) {
return false;
}
} else {
allWater = false;
}
if (gAgi->blockActive && (o->flags & OBJ_IGNORE_BLOCKS) == 0u) {
insideNow = o->x >= gAgi->blockX1 && o->x <= gAgi->blockX2 && o->y >= gAgi->blockY1 && o->y <= gAgi->blockY2;
insideNew = x >= gAgi->blockX1 && x <= gAgi->blockX2 && y >= gAgi->blockY1 && y <= gAgi->blockY2;
if (insideNow != insideNew) {
return false;
}
}
if (blockedByObject(n, x, y)) {
return false;
}
if (n == AGI_EGO) {
gAgi->flags[FLAG_EGO_WATER] = allWater ? 1u : 0u;
gAgi->flags[FLAG_EGO_SIGNAL] = signal ? 1u : 0u;
}
return true;
}
// One step in the object's direction, if the way is clear.
static void moveObject(uint8_t n) {
AgiObjectT *o;
int16_t x;
int16_t y;
uint8_t edge;
o = &gAgi->objects[n];
if (o->direction == AGI_DIR_NONE || o->direction >= AGI_DIR_COUNT) {
return;
}
x = (int16_t)(o->x + kDirDx[o->direction] * (int16_t)o->stepSize);
y = (int16_t)(o->y + kDirDy[o->direction] * (int16_t)o->stepSize);
edge = clampToScreen(o, &x, &y);
if ((x != o->x || y != o->y) && legalPosition(n, x, y)) {
o->x = x;
o->y = y;
}
// Ego's edge is current: a step that touches none clears it.
if (edge != AGI_EDGE_NONE) {
reportEdge(n, edge);
} else if (n == AGI_EGO) {
gAgi->vars[VAR_EGO_EDGE] = AGI_EDGE_NONE;
}
}
// The object's cel is wholly on the screen, below the horizon unless it
// ignores it.
static bool onScreen(const AgiObjectT *o, int16_t x, int16_t y) {
return x >= 0 && x + (int16_t)o->width <= AGI_WIDTH && y <= AGI_MAX_Y && y - (int16_t)o->height + 1 >= 0 &&
((o->flags & OBJ_IGNORE_HORIZON) != 0u || y > (int16_t)gAgi->horizon);
}
static void reportEdge(uint8_t n, uint8_t edge) {
if (n == AGI_EGO) {
gAgi->vars[VAR_EGO_EDGE] = edge;
} else {
gAgi->vars[VAR_OBJ_EDGE_OBJ] = n;
gAgi->vars[VAR_OBJ_EDGE] = edge;
}
}
static void selectLoop(AgiObjectT *o) {
uint8_t count;
uint8_t loop;
uint16_t version;
if ((o->flags & OBJ_FIX_LOOP) != 0u || o->view == NULL) {
return;
}
count = o->view->loopCount;
version = gAgi->game.interpreterVersion;
if (count == LOOPS_TWO || count == LOOPS_THREE) {
loop = kLoopFew[o->direction];
} else if (count == LOOPS_FOUR || version == AGI_VERSION_KQ4) {
loop = kLoopFour[o->direction];
} else {
return;
}
// After 2.272 the loop turns only as the object steps.
if (loop != LOOP_KEEP && loop != o->loop && (version <= AGI_VERSION_2272 || o->stepCount == STEP_DUE)) {
objSetLoop((uint8_t)(o - gAgi->objects), loop);
}
}
// A special motion is over: set its flag, stop, and give back what it
// took (the step size, and for ego the keyboard).
static void stopSpecialMotion(uint8_t n) {
AgiObjectT *o;
o = &gAgi->objects[n];
if (o->motion == AGI_MOTION_MOVE || o->motion == AGI_MOTION_MOUSE) {
o->stepSize = o->parm[PARM_MOVE_STEP];
if (n == AGI_EGO) {
gAgi->playerControl = true;
}
}
// A mouse walk has no flag to set.
if (o->motion == AGI_MOTION_MOVE) {
gAgi->flags[o->parm[PARM_MOVE_FLAG]] = 1u;
} else if (o->motion == AGI_MOTION_FOLLOW) {
gAgi->flags[o->parm[PARM_FOLLOW_FLAG]] = 1u;
}
o->motion = AGI_MOTION_NORMAL;
o->direction = AGI_DIR_NONE;
if (n == AGI_EGO) {
gAgi->vars[VAR_EGO_DIR] = AGI_DIR_NONE;
}
}
// wander, as Sierra's (2.915): the count runs down each cycle; when it had
// run out, or the last step left the object standing, a new heading
// (rand % 9, 0 standing still) and, while the count is under 6, a new
// count. A count that ran out has wrapped to 255, so that heading lasts
// about 255 cycles unless something stops the object.
static void wander(AgiObjectT *o) {
uint8_t count;
count = o->parm[PARM_WANDER_COUNT];
o->parm[PARM_WANDER_COUNT] = (uint8_t)(count - 1u);
if (count != 0u && (o->flags & OBJ_DIDNT_MOVE) == 0u) {
return;
}
o->direction = (uint8_t)(agiRandom() % AGI_DIR_COUNT);
while (o->parm[PARM_WANDER_COUNT] < WANDER_COUNT_MIN) {
o->parm[PARM_WANDER_COUNT] = (uint8_t)(agiRandom() % WANDER_COUNT_LIMIT);
}
}
// ----- Public API (alphabetical) -----
// animate.obj: bring an object under the interpreter's control. Its
// step and cycle settings are left as they are: new.room (unanimate.all)
// is what sets them back to the defaults, so before the first new.room
// they are all 0 (measured: such objects neither cycle nor move).
void objAnimate(uint8_t n) {
AgiObjectT *o;
o = &gAgi->objects[n];
if ((o->flags & OBJ_ANIMATED) == 0u) {
o->flags = (uint16_t)((o->flags & OBJ_SKIP_STEP) | OBJ_ANIMATED | OBJ_UPDATE | OBJ_CYCLING);
}
}
// The objects as the interpreter starts: all zero, each yet to make its
// first step.
void objClearAll(void) {
uint8_t i;
memset(gAgi->objects, 0, sizeof(gAgi->objects));
for (i = 0u; i < AGI_MAX_OBJECTS; i++) {
gAgi->objects[i].flags = OBJ_SKIP_STEP;
}
}
// The direction from one point toward another, treating offsets smaller
// than `slack` as none (measured: move.obj with step 2 goes the last 2
// pixels, with step 3 stops 2 short).
uint8_t objDirectionTo(int16_t fromX, int16_t fromY, int16_t toX, int16_t toY, uint8_t slack) {
int16_t dx;
int16_t dy;
int16_t s;
uint8_t sx;
uint8_t sy;
s = (int16_t)((slack == 0u) ? 1u : slack);
dx = (int16_t)(toX - fromX);
dy = (int16_t)(toY - fromY);
sx = (dx <= -s) ? 0u : (dx >= s) ? 2u : 1u;
sy = (dy <= -s) ? 0u : (dy >= s) ? 2u : 1u;
return kDirFromSigns[sy][sx];
}
// Put an object somewhere it may stand (measured from Sierra's
// interpreter): below the horizon, then, if it may not stand there,
// spiralling out a pixel at a time -- left 1, down 1, right 2, up 2,
// left 3, ... -- to the first place on the screen clear of barriers and
// other objects.
void objFixPosition(uint8_t n) {
AgiObjectT *o;
int16_t x;
int16_t y;
uint16_t len;
uint16_t step;
uint8_t leg;
o = &gAgi->objects[n];
x = o->x;
y = o->y;
if ((o->flags & OBJ_IGNORE_HORIZON) == 0u && y <= (int16_t)gAgi->horizon) {
y = (int16_t)(gAgi->horizon + 1u);
}
leg = 0u;
len = 1u;
while (!(onScreen(o, x, y) && legalPosition(n, x, y)) && len <= SPIRAL_MAX) {
for (step = 0u; step < len; step++) {
x = (int16_t)(x + kSpiralDx[leg]);
y = (int16_t)(y + kSpiralDy[leg]);
if (onScreen(o, x, y) && legalPosition(n, x, y)) {
break;
}
}
if (step < len) {
break;
}
// The legs lengthen every second turn.
leg = (uint8_t)((leg + 1u) % SPIRAL_LEGS);
if ((leg & 1u) == 0u) {
len++;
}
}
o->x = x;
o->y = y;
}
// move.obj: head for (x, y) at `step` pixels a step (0 keeps the step
// size); flag is set on arrival.
// follow.ego: within step (at least the object's step size) of ego, set
// flag. The detour count starts at FOLLOW_START.
void objFollowEgo(uint8_t n, uint8_t step, uint8_t flag) {
AgiObjectT *o;
o = &gAgi->objects[n];
o->motion = AGI_MOTION_FOLLOW;
o->parm[PARM_FOLLOW_STEP] = (step > o->stepSize) ? step : o->stepSize;
o->parm[PARM_FOLLOW_FLAG] = flag;
o->parm[PARM_FOLLOW_COUNT] = FOLLOW_START;
gAgi->flags[flag] = 0u;
}
// A click's walk (agiMouse.c): ego heads, as for move.obj but with no flag
// and the player keeping control, for where its baseline's middle is at
// (x, y).
void objMouseWalk(uint8_t x, uint8_t y) {
AgiObjectT *ego;
uint8_t half;
ego = &gAgi->objects[AGI_EGO];
half = (uint8_t)(ego->width / 2u);
ego->motion = AGI_MOTION_MOUSE;
ego->parm[PARM_MOVE_X] = (x < half) ? MOUSE_OFF_LEFT : (uint8_t)(x - half);
ego->parm[PARM_MOVE_Y] = y;
ego->parm[PARM_MOVE_STEP] = ego->stepSize;
}
void objMoveTo(uint8_t n, uint8_t x, uint8_t y, uint8_t step, uint8_t flag) {
AgiObjectT *o;
o = &gAgi->objects[n];
o->motion = AGI_MOTION_MOVE;
o->parm[PARM_MOVE_X] = x;
o->parm[PARM_MOVE_Y] = y;
o->parm[PARM_MOVE_STEP] = o->stepSize;
o->parm[PARM_MOVE_FLAG] = flag;
if (step != 0u) {
o->stepSize = step;
}
o->direction = objDirectionTo(o->x, o->y, x, y, o->stepSize);
gAgi->flags[flag] = 0u;
if (n == AGI_EGO) {
gAgi->playerControl = false;
gAgi->vars[VAR_EGO_DIR] = o->direction;
}
if (o->direction == AGI_DIR_NONE) {
stopSpecialMotion(n);
}
}
// Before logic 0: work out where wandering, following and moving objects
// head this cycle.
void objPlanMotion(void) {
uint8_t i;
AgiObjectT *o;
for (i = 0u; i < gAgi->objectCount; i++) {
o = &gAgi->objects[i];
if ((o->flags & (OBJ_ANIMATED | OBJ_UPDATE | OBJ_DRAWN)) != (OBJ_ANIMATED | OBJ_UPDATE | OBJ_DRAWN)) {
continue;
}
switch (o->motion) {
case AGI_MOTION_WANDER:
wander(o);
break;
case AGI_MOTION_FOLLOW:
followEgo(i);
break;
case AGI_MOTION_MOVE:
case AGI_MOTION_MOUSE:
o->direction = objDirectionTo(o->x, o->y, (o->motion == AGI_MOTION_MOUSE && o->parm[PARM_MOVE_X] == MOUSE_OFF_LEFT) ? -1 : (int16_t)o->parm[PARM_MOVE_X], o->parm[PARM_MOVE_Y], o->stepSize);
if (o->direction == AGI_DIR_NONE) {
stopSpecialMotion(i);
}
break;
default:
break;
}
if (i == AGI_EGO) {
gAgi->vars[VAR_EGO_DIR] = o->direction;
}
}
}
void objSetCel(uint8_t n, uint8_t cel) {
AgiObjectT *o;
const AgiCelT *c;
bool mirror;
o = &gAgi->objects[n];
o->cel = cel;
if (o->view == NULL || o->loop >= o->view->loopCount) {
return;
}
// A cel (or loop) the new view or loop lacks becomes 0 (measured).
if (cel >= o->view->loops[o->loop].celCount) {
o->cel = 0u;
}
c = agiViewCel(o->view, o->loop, o->cel, &mirror);
if (c != NULL) {
o->width = c->width;
o->height = c->height;
}
}
void objSetLoop(uint8_t n, uint8_t loop) {
AgiObjectT *o;
o = &gAgi->objects[n];
if (o->view == NULL) {
o->loop = loop;
return;
}
o->loop = (loop >= o->view->loopCount) ? 0u : loop;
objSetCel(n, o->cel);
}
bool objSetView(uint8_t n, uint8_t viewId) {
AgiObjectT *o;
const AgiViewT *view;
view = agiViewGet(viewId);
if (view == NULL) {
return false;
}
o = &gAgi->objects[n];
o->view = view;
o->viewId = viewId;
if (n == AGI_EGO) {
gAgi->vars[VAR_EGO_VIEW] = viewId;
}
objSetLoop(n, o->loop);
return true;
}
// unanimate.all (and new.room): every object off the screen and out of
// the interpreter's hands, with the default step and cycle settings. Its
// view, loop, cel, position and direction stay (new.room places ego from
// them; measured: ego keeps walking the way it went once animated again).
void objUnanimateAll(void) {
uint8_t i;
AgiObjectT *o;
gfxEraseAll();
for (i = 0u; i < AGI_MAX_OBJECTS; i++) {
o = &gAgi->objects[i];
o->flags = OBJ_SKIP_STEP;
o->motion = AGI_MOTION_NORMAL;
o->cycleMode = AGI_CYCLE_NORMAL;
o->stepSize = STEP_DEFAULT;
o->stepTime = STEP_DEFAULT;
o->stepCount = STEP_DEFAULT;
o->cycleTime = STEP_DEFAULT;
o->cycleCount = STEP_DEFAULT;
}
gfxPresentDirty();
}
// After logic 0: step every updated object's cel and move it, then draw
// them all again.
void objUpdateAll(void) {
uint8_t i;
AgiObjectT *o;
gfxEraseUpdating();
for (i = 0u; i < gAgi->objectCount; i++) {
o = &gAgi->objects[i];
if ((o->flags & (OBJ_ANIMATED | OBJ_UPDATE | OBJ_DRAWN)) != (OBJ_ANIMATED | OBJ_UPDATE | OBJ_DRAWN)) {
continue;
}
selectLoop(o);
// A cycle count of 0 (an object never given a cycle time) never
// comes due; a step count of 0 is due every cycle.
if ((o->flags & OBJ_CYCLING) != 0u && o->cycleCount != 0u) {
o->cycleCount--;
if (o->cycleCount == 0u) {
o->cycleCount = o->cycleTime;
if ((o->flags & OBJ_SKIP_CYCLE) != 0u) {
o->flags = (uint16_t)(o->flags & (uint16_t)~OBJ_SKIP_CYCLE);
} else {
advanceCel(i);
}
}
}
if (o->stepCount > 0u) {
o->stepCount--;
}
if (o->stepCount == 0u) {
o->stepCount = o->stepTime;
if ((o->flags & OBJ_SKIP_STEP) != 0u) {
o->flags = (uint16_t)(o->flags & (uint16_t)~OBJ_SKIP_STEP);
} else {
moveObject(i);
}
// Where the step left it against where the last step that
// moved it (or a position command) did, as Sierra's does at
// each step: wander and follow.ego turn aside when it stood.
if (o->x == o->prevX && o->y == o->prevY) {
o->flags = (uint16_t)(o->flags | OBJ_DIDNT_MOVE);
} else {
o->prevX = o->x;
o->prevY = o->y;
o->flags = (uint16_t)(o->flags & (uint16_t)~OBJ_DIDNT_MOVE);
}
}
}
gfxRedrawUpdating();
}