729 lines
24 KiB
C
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();
|
|
}
|