609 lines
19 KiB
C
609 lines
19 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 <string.h>
|
|
|
|
#include "joey/core.h"
|
|
|
|
|
|
// Wandering objects walk a random direction for a random number of steps.
|
|
#define WANDER_STEPS_MIN 6u
|
|
#define WANDER_STEPS_SPAN 45u
|
|
#define FOLLOW_DETOUR_MIN 2u
|
|
#define FOLLOW_DETOUR_SPAN 8u
|
|
#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; four or more also face up and down.
|
|
// 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_TWO 2u
|
|
|
|
|
|
// ----- 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 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);
|
|
|
|
|
|
// ----- 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->loopFlag] = 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;
|
|
|
|
edge = AGI_EDGE_NONE;
|
|
if (*x < 0) {
|
|
*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;
|
|
}
|
|
|
|
|
|
// 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;
|
|
} else if (o->motion == AGI_MOTION_WANDER) {
|
|
o->randomSteps = 0u;
|
|
} else if (o->motion == AGI_MOTION_FOLLOW) {
|
|
// A follower that runs into something wanders off a few steps
|
|
// before it heads for ego again (measured: it never settles
|
|
// against ego's side).
|
|
o->direction = (uint8_t)(AGI_DIR_N + jlRandomRange(AGI_DIR_COUNT - 1u));
|
|
o->randomSteps = (uint8_t)(FOLLOW_DETOUR_MIN + jlRandomRange(FOLLOW_DETOUR_SPAN));
|
|
}
|
|
// 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 loop;
|
|
|
|
if ((o->flags & OBJ_FIX_LOOP) != 0u || o->view == NULL || o->view->loopCount < LOOPS_TWO) {
|
|
return;
|
|
}
|
|
loop = (o->view->loopCount >= LOOPS_FOUR) ? kLoopFour[o->direction] : kLoopFew[o->direction];
|
|
if (loop != LOOP_KEEP && loop != o->loop) {
|
|
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->stepSize = o->savedStepSize;
|
|
if (n == AGI_EGO) {
|
|
gAgi->playerControl = true;
|
|
}
|
|
}
|
|
gAgi->flags[o->motionFlag] = 1u;
|
|
o->motion = AGI_MOTION_NORMAL;
|
|
o->direction = AGI_DIR_NONE;
|
|
if (n == AGI_EGO) {
|
|
gAgi->vars[VAR_EGO_DIR] = AGI_DIR_NONE;
|
|
}
|
|
}
|
|
|
|
|
|
// ----- 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.
|
|
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->targetX = x;
|
|
o->targetY = y;
|
|
o->savedStepSize = o->stepSize;
|
|
if (step != 0u) {
|
|
o->stepSize = step;
|
|
}
|
|
o->motionFlag = flag;
|
|
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;
|
|
AgiObjectT *ego;
|
|
|
|
ego = &gAgi->objects[AGI_EGO];
|
|
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:
|
|
if (o->randomSteps == 0u) {
|
|
o->direction = (uint8_t)jlRandomRange(AGI_DIR_COUNT);
|
|
o->randomSteps = (uint8_t)(WANDER_STEPS_MIN + jlRandomRange(WANDER_STEPS_SPAN));
|
|
} else {
|
|
o->randomSteps--;
|
|
}
|
|
break;
|
|
case AGI_MOTION_FOLLOW:
|
|
if (o->randomSteps > 0u) {
|
|
o->randomSteps--;
|
|
break;
|
|
}
|
|
o->direction = objDirectionTo((int16_t)(o->x + o->width / 2u), o->y,
|
|
(int16_t)(ego->x + ego->width / 2u), ego->y, o->followDistance);
|
|
if (o->direction == AGI_DIR_NONE) {
|
|
stopSpecialMotion(i);
|
|
}
|
|
break;
|
|
case AGI_MOTION_MOVE:
|
|
o->direction = objDirectionTo(o->x, o->y, o->targetX, o->targetY, 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 and position stay (new.room places ego from them).
|
|
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->direction = AGI_DIR_NONE;
|
|
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;
|
|
o->prevX = o->x;
|
|
o->prevY = o->y;
|
|
if ((o->flags & OBJ_SKIP_STEP) != 0u) {
|
|
o->flags = (uint16_t)(o->flags & (uint16_t)~OBJ_SKIP_STEP);
|
|
} else {
|
|
moveObject(i);
|
|
}
|
|
}
|
|
}
|
|
gfxRedrawUpdating();
|
|
}
|