aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/engine/kmovement.c
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/engine/kmovement.c')
-rw-r--r--engines/sci/engine/kmovement.c580
1 files changed, 580 insertions, 0 deletions
diff --git a/engines/sci/engine/kmovement.c b/engines/sci/engine/kmovement.c
new file mode 100644
index 0000000000..5f7d1a7281
--- /dev/null
+++ b/engines/sci/engine/kmovement.c
@@ -0,0 +1,580 @@
+/***************************************************************************
+ kmovement.c Copyright (C) 2001 Christoph Reichenbach
+
+
+ This program may be modified and copied freely according to the terms of
+ the GNU general public license (GPL), as long as the above copyright
+ notice and the licensing information contained herein are preserved.
+
+ Please refer to www.gnu.org for licensing details.
+
+ This work is provided AS IS, without warranty of any kind, expressed or
+ implied, including but not limited to the warranties of merchantibility,
+ noninfringement, and fitness for a specific purpose. The author will not
+ be held liable for any damage caused by this work or derivatives of it.
+
+ By using this source code, you agree to the licensing terms as stated
+ above.
+
+
+ Please contact the maintainer for bug reports or inquiries.
+
+ Current Maintainer:
+
+ Christoph Reichenbach (CR) <jameson@linuxgames.com>
+
+***************************************************************************/
+
+#include <sciresource.h>
+#include <engine.h>
+
+/*
+Compute "velocity" vector (xStep,yStep)=(vx,vy) for a jump from (0,0) to (dx,dy), with gravity gy.
+The gravity is assumed to be non-negative.
+
+If this was ordinary continuous physics, we would compute the desired (floating point!)
+velocity vector (vx,vy) as follows, under the assumption that vx and vy are linearly correlated
+by some constant factor c, i.e. vy = c * vx:
+ dx = t * vx
+ dy = t * vy + gy * t^2 / 2
+=> dy = c * dx + gy * (dx/vx)^2 / 2
+=> |vx| = sqrt( gy * dx^2 / (2 * (dy - c * dx)) )
+Here, the sign of vx must be chosen equal to the sign of dx, obviously.
+
+Clearly, this square root only makes sense in our context if the denominator is positive,
+or equivalently, (dy - c * dx) must be positive. For simplicity and by symmetry
+along the x-axis, we assume dx to be positive for all computations, and only adjust for
+its sign in the end. Switching the sign of c appropriately, we set tmp := (dy + c * dx)
+and compute c so that this term becomes positive.
+
+Remark #1: If the jump is straight up, i.e. dx == 0, then we should not assume the above
+linear correlation vy = c * vx of the velocities (as vx will be 0, but vy shouldn't be,
+unless we drop).
+
+
+Remark #2: We are actually in a discrete setup. The motion is computed iteratively: each iteration,
+we add vx and vy to the position, then add gy to vy. So the real formula is the following
+(where t is ideally close to an int):
+
+ dx = t * vx
+ dy = t * vy + gy * t*(t-1) / 2
+
+But the solution resulting from that is a lot more complicated, so we use the above approximation instead.
+
+Still, what we compute in the end is of course not a real velocity anymore, but an integer approximation,
+used in an iterative stepping algorithm
+*/
+reg_t
+kSetJump(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ // Input data
+ reg_t object = argv[0];
+ int dx = SKPV(1);
+ int dy = SKPV(2);
+ int gy = SKPV(3);
+
+ // Derived data
+ int c;
+ int tmp;
+ int vx = 0; // x velocity
+ int vy = 0; // y velocity
+
+ int dxWasNegative = (dx < 0);
+ dx = abs(dx);
+
+ assert(gy >= 0);
+
+ if (dx == 0) {
+ // Upward jump. Value of c doesn't really matter
+ c = 1;
+ } else {
+ // Compute a suitable value for c respectively tmp.
+ // The important thing to consider here is that we want the resulting
+ // *discrete* x/y velocities to be not-too-big integers, for a smooth
+ // curve (i.e. we could just set vx=dx, vy=dy, and be done, but that
+ // is hardly what you would call a parabolic jump, would ya? ;-).
+ //
+ // So, we make sure that 2.0*tmp will be bigger than dx (that way,
+ // we ensure vx will be less than sqrt(gy * dx)).
+ if (dx + dy < 0) {
+ // dy is negative and |dy| > |dx|
+ c = (2*abs(dy)) / dx;
+ //tmp = abs(dy); // ALMOST the resulting value, except for obvious rounding issues
+ } else {
+ // dy is either positive, or |dy| <= |dx|
+ c = (dx*3/2 - dy) / dx;
+
+ // We force c to be strictly positive
+ if (c < 1)
+ c = 1;
+
+ //tmp = dx*3/2; // ALMOST the resulting value, except for obvious rounding issues
+
+ // FIXME: Where is the 3 coming from? Maybe they hard/coded, by "accident", that usually gy=3 ?
+ // Then this choice of will make t equal to roughly sqrt(dx)
+ }
+ }
+ // POST: c >= 1
+ tmp = c * dx + dy;
+ // POST: (dx != 0) ==> abs(tmp) > abs(dx)
+ // POST: (dx != 0) ==> abs(tmp) ~>=~ abs(dy)
+
+
+ SCIkdebug(SCIkBRESEN, "c: %d, tmp: %d\n", c, tmp);
+
+ // Compute x step
+ if (tmp != 0)
+ vx = (int)(dx * sqrt(gy / (2.0 * tmp)));
+ else
+ vx = 0;
+
+ // Restore the left/right direction: dx and vx should have the same sign.
+ if (dxWasNegative)
+ vx = -vx;
+
+ if ((dy < 0) && (vx == 0)) {
+ // Special case: If this was a jump (almost) straight upward, i.e. dy < 0 (upward),
+ // and vx == 0 (i.e. no horizontal movement, at least not after rounding), then we
+ // compute vy directly.
+ // For this, we drop the assumption on the linear correlation of vx and vy (obviously).
+
+ // FIXME: This choice of vy makes t roughly (2+sqrt(2))/gy * sqrt(dy);
+ // so if gy==3, then t is roughly sqrt(dy)...
+ vy = (int)sqrt(gy * abs(2 * dy)) + 1;
+ } else {
+ // As stated above, the vertical direction is correlated to the horizontal by the
+ // (non-zero) factor c.
+ // Strictly speaking, we should probably be using the value of vx *before* rounding
+ // it to an integer... Ah well
+ vy = c * vx;
+ }
+
+ // Always force vy to be upwards
+ vy = -abs(vy);
+
+ SCIkdebug(SCIkBRESEN, "SetJump for object at "PREG"\n", PRINT_REG(object));
+ SCIkdebug(SCIkBRESEN, "xStep: %d, yStep: %d\n", vx, vy);
+
+ PUT_SEL32V(object, xStep, vx);
+ PUT_SEL32V(object, yStep, vy);
+
+ return s->r_acc;
+}
+
+#define _K_BRESEN_AXIS_X 0
+#define _K_BRESEN_AXIS_Y 1
+
+void
+initialize_bresen(state_t *s, int funct_nr, int argc, reg_t *argv, reg_t mover, int step_factor,
+ int deltax, int deltay)
+{
+ reg_t client = GET_SEL32(mover, client);
+ int stepx = GET_SEL32SV(client, xStep) * step_factor;
+ int stepy = GET_SEL32SV(client, yStep) * step_factor;
+ int numsteps_x = stepx? (abs(deltax) + stepx-1) / stepx : 0;
+ int numsteps_y = stepy? (abs(deltay) + stepy-1) / stepy : 0;
+ int bdi, i1;
+ int numsteps;
+ int deltax_step;
+ int deltay_step;
+
+ if (numsteps_x > numsteps_y) {
+ numsteps = numsteps_x;
+ deltax_step = (deltax < 0)? -stepx : stepx;
+ deltay_step = numsteps? deltay / numsteps : deltay;
+ } else { /* numsteps_x <= numsteps_y */
+ numsteps = numsteps_y;
+ deltay_step = (deltay < 0)? -stepy : stepy;
+ deltax_step = numsteps? deltax / numsteps : deltax;
+ }
+
+ /* if (abs(deltax) > abs(deltay)) {*/ /* Bresenham on y */
+ if (numsteps_y < numsteps_x) {
+
+ PUT_SEL32V(mover, b_xAxis, _K_BRESEN_AXIS_Y);
+ PUT_SEL32V(mover, b_incr, (deltay < 0)? -1 : 1);
+ /*
+ i1 = 2 * (abs(deltay) - abs(deltay_step * numsteps)) * abs(deltax_step);
+ bdi = -abs(deltax);
+ */
+ i1 = 2*(abs(deltay) - abs(deltay_step * (numsteps - 1))) * abs(deltax_step);
+ bdi = -abs(deltax);
+
+ } else { /* Bresenham on x */
+
+ PUT_SEL32V(mover, b_xAxis, _K_BRESEN_AXIS_X);
+ PUT_SEL32V(mover, b_incr, (deltax < 0)? -1 : 1);
+ /*
+ i1= 2 * (abs(deltax) - abs(deltax_step * numsteps)) * abs(deltay_step);
+ bdi = -abs(deltay);
+ */
+ i1 = 2*(abs(deltax) - abs(deltax_step * (numsteps - 1))) * abs(deltay_step);
+ bdi = -abs(deltay);
+
+ }
+
+ PUT_SEL32V(mover, dx, deltax_step);
+ PUT_SEL32V(mover, dy, deltay_step);
+
+ SCIkdebug(SCIkBRESEN, "Init bresen for mover "PREG": d=(%d,%d)\n", PRINT_REG(mover), deltax, deltay);
+ SCIkdebug(SCIkBRESEN, " steps=%d, mv=(%d, %d), i1= %d, i2=%d\n",
+ numsteps, deltax_step, deltay_step, i1, bdi*2);
+
+/* PUT_SEL32V(mover, b_movCnt, numsteps); *//* Needed for HQ1/Ogre? */
+ PUT_SEL32V(mover, b_di, bdi);
+ PUT_SEL32V(mover, b_i1, i1);
+ PUT_SEL32V(mover, b_i2, bdi * 2);
+
+}
+
+reg_t
+kInitBresen(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ reg_t mover = argv[0];
+ reg_t client = GET_SEL32(mover, client);
+
+ int deltax = GET_SEL32SV(mover, x) - GET_SEL32SV(client, x);
+ int deltay = GET_SEL32SV(mover, y) - GET_SEL32SV(client, y);
+
+ initialize_bresen(s, funct_nr, argc, argv, mover, KP_UINT(KP_ALT(1, make_reg(0, 1))), deltax, deltay);
+
+ return s->r_acc;
+}
+
+
+#define MOVING_ON_X (((axis == _K_BRESEN_AXIS_X)&&bi1) || dx)
+#define MOVING_ON_Y (((axis == _K_BRESEN_AXIS_Y)&&bi1) || dy)
+
+static enum {
+ IGNORE_MOVECNT,
+ INCREMENT_MOVECNT,
+ UNINITIALIZED
+} handle_movecnt = UNINITIALIZED;
+
+int parse_reg_t(state_t *s, char *str, reg_t *dest); /* In scriptconsole.c */
+
+static int
+checksum_bytes(byte *data, int size)
+{
+ int result = 0;
+ int i;
+
+ for (i = 0; i < size; i++)
+ {
+ result += *data;
+ data++;
+ }
+
+ return result;
+}
+
+static void
+bresenham_autodetect(state_t *s)
+{
+ reg_t motion_class;
+
+ if (!parse_reg_t(s, "?Motion", &motion_class))
+ {
+ object_t *obj = obj_get(s, motion_class);
+ reg_t fptr;
+ byte *buf;
+
+ if (obj == NULL)
+ {
+ SCIkwarn(SCIkWARNING,"bresenham_autodetect failed!");
+ handle_movecnt = INCREMENT_MOVECNT; /* Most games do this, so best guess */
+ return;
+ }
+
+ if (lookup_selector(s, motion_class, s->selector_map.doit, NULL, &fptr) != SELECTOR_METHOD)
+ {
+ SCIkwarn(SCIkWARNING,"bresenham_autodetect failed!");
+ handle_movecnt = INCREMENT_MOVECNT; /* Most games do this, so best guess */
+ return;
+ }
+
+ buf = s->seg_manager.heap[fptr.segment]->data.script.buf + fptr.offset;
+ handle_movecnt = (SCI_VERSION_MAJOR(s->version) == 0 ||
+ checksum_bytes(buf, 8) == 0x216) ? INCREMENT_MOVECNT : IGNORE_MOVECNT;
+ sciprintf("b-moveCnt action based on checksum: %s\n", handle_movecnt == IGNORE_MOVECNT ?
+ "ignore" : "increment");
+ } else
+ {
+ SCIkwarn(SCIkWARNING,"bresenham_autodetect failed!");
+ handle_movecnt = INCREMENT_MOVECNT; /* Most games do this, so best guess */
+ }
+}
+
+reg_t
+kDoBresen(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ reg_t mover = argv[0];
+ reg_t client = GET_SEL32(mover, client);
+
+ int x = GET_SEL32SV(client, x);
+ int y = GET_SEL32SV(client, y);
+ int oldx, oldy, destx, desty, dx, dy, bdi, bi1, bi2, movcnt, bdelta, axis;
+ word signal = GET_SEL32V(client, signal);
+ int completed = 0;
+ int max_movcnt = GET_SEL32V(client, moveSpeed);
+
+ if (SCI_VERSION_MAJOR(s->version)>0)
+ signal&=~_K_VIEW_SIG_FLAG_HIT_OBSTACLE;
+
+ if (handle_movecnt == UNINITIALIZED)
+ bresenham_autodetect(s);
+
+ PUT_SEL32(client, signal, make_reg(0, signal)); /* This is a NOP for SCI0 */
+ oldx = x;
+ oldy = y;
+ destx = GET_SEL32SV(mover, x);
+ desty = GET_SEL32SV(mover, y);
+ dx = GET_SEL32SV(mover, dx);
+ dy = GET_SEL32SV(mover, dy);
+ bdi = GET_SEL32SV(mover, b_di);
+ bi1 = GET_SEL32SV(mover, b_i1);
+ bi2 = GET_SEL32SV(mover, b_i2);
+ movcnt = GET_SEL32V(mover, b_movCnt);
+ bdelta = GET_SEL32SV(mover, b_incr);
+ axis = GET_SEL32SV(mover, b_xAxis);
+
+// sciprintf("movecnt %d, move speed %d\n", movcnt, max_movcnt);
+
+ if (handle_movecnt)
+ {
+ if (max_movcnt > movcnt)
+ {
+ ++movcnt;
+ PUT_SEL32V(mover, b_movCnt, movcnt); /* Needed for HQ1/Ogre? */
+ return NULL_REG;
+ }
+ else
+ {
+ movcnt = 0;
+ PUT_SEL32V(mover, b_movCnt, movcnt); /* Needed for HQ1/Ogre? */
+ }
+ }
+
+ if ((bdi += bi1) > 0) {
+ bdi += bi2;
+
+ if (axis == _K_BRESEN_AXIS_X)
+ dx += bdelta;
+ else
+ dy += bdelta;
+ }
+
+ PUT_SEL32V(mover, b_di, bdi);
+
+ x += dx;
+ y += dy;
+
+ if ((MOVING_ON_X
+ && (((x < destx) && (oldx >= destx)) /* Moving left, exceeded? */
+ ||
+ ((x > destx) && (oldx <= destx)) /* Moving right, exceeded? */
+ ||
+ ((x == destx) && (abs(dx) > abs(dy))) /* Moving fast, reached? */
+ /* Treat this last case specially- when doing sub-pixel movements
+ ** on the other axis, we could still be far away from the destination */
+ )
+ )
+ || (MOVING_ON_Y
+ && (((y < desty) && (oldy >= desty)) /* Moving upwards, exceeded? */
+ ||
+ ((y > desty) && (oldy <= desty)) /* Moving downwards, exceeded? */
+ ||
+ ((y == desty) && (abs(dy) >= abs(dx))) /* Moving fast, reached? */
+ )
+ )
+ )
+ /* Whew... in short: If we have reached or passed our target position */
+ {
+ x = destx;
+ y = desty;
+ completed = 1;
+
+ SCIkdebug(SCIkBRESEN, "Finished mover "PREG"\n", PRINT_REG(mover));
+ }
+
+
+ PUT_SEL32V(client, x, x);
+ PUT_SEL32V(client, y, y);
+
+ SCIkdebug(SCIkBRESEN, "New data: (x,y)=(%d,%d), di=%d\n", x, y, bdi);
+
+ if (s->version >= SCI_VERSION_FTU_INVERSE_CANBEHERE)
+ invoke_selector(INV_SEL(client, cantBeHere, 0), 0); else
+ invoke_selector(INV_SEL(client, canBeHere, 0), 0);
+
+ s->r_acc = not_register(s, s->r_acc);
+
+ if (!s->r_acc.offset) { /* Contains the return value */
+
+ signal = GET_SEL32V(client, signal);
+
+ PUT_SEL32V(client, x, oldx);
+ PUT_SEL32V(client, y, oldy);
+
+ PUT_SEL32V(client, signal, (signal | _K_VIEW_SIG_FLAG_HIT_OBSTACLE));
+
+ SCIkdebug(SCIkBRESEN, "Finished mover "PREG" by collision\n", PRINT_REG(mover));
+ completed = 1;
+ }
+
+ if (SCI_VERSION_MAJOR(s->version)>0)
+ if (completed)
+ invoke_selector(INV_SEL(mover, moveDone, 0), 0);
+
+ return make_reg(0, completed);
+}
+
+extern void
+_k_dirloop(reg_t obj, word angle, state_t *s, int funct_nr,
+ int argc, reg_t *argv);
+/* From kgraphics.c, used as alternative looper */
+
+int
+is_heap_object(state_t *s, reg_t pos);
+/* From kscripts.c */
+
+extern int
+get_angle(int xrel, int yrel);
+/* from kmath.c, used for calculating angles */
+
+
+reg_t
+kDoAvoider(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ reg_t avoider = argv[0];
+ reg_t client, looper, mover;
+ int angle;
+ int dx, dy;
+ int destx, desty;
+
+
+ s->r_acc = make_reg(0, -1);
+
+ if (!is_heap_object(s, avoider)) {
+ SCIkwarn(SCIkWARNING, "DoAvoider() where avoider "PREG" is not an object\n", PRINT_REG(avoider));
+ return NULL_REG;
+ }
+
+ client = GET_SEL32(avoider, client);
+
+ if (!is_heap_object(s, client)) {
+ SCIkwarn(SCIkWARNING, "DoAvoider() where client "PREG" is not an object\n", PRINT_REG(client));
+ return NULL_REG;
+ }
+
+ looper = GET_SEL32(client, looper);
+
+ mover = GET_SEL32(client, mover);
+
+ if (!is_heap_object(s, mover)) {
+ if (mover.segment) {
+ SCIkwarn(SCIkWARNING, "DoAvoider() where mover "PREG" is not an object\n", PRINT_REG(mover));
+ }
+ return s->r_acc;
+ }
+
+ destx = GET_SEL32V(mover, x);
+ desty = GET_SEL32V(mover, y);
+
+ SCIkdebug(SCIkBRESEN, "Doing avoider %04x (dest=%d,%d)\n", avoider, destx, desty);
+
+ if (invoke_selector(INV_SEL(mover, doit, 1) , 0)) {
+ SCIkwarn(SCIkERROR, "Mover "PREG" of avoider "PREG
+ " doesn't have a doit() funcselector\n",
+ PRINT_REG(mover), PRINT_REG(avoider));
+ return NULL_REG;
+ }
+
+ mover = GET_SEL32(client, mover);
+ if (!mover.segment) /* Mover has been disposed? */
+ return s->r_acc; /* Return gracefully. */
+
+ if (invoke_selector(INV_SEL(client, isBlocked, 1) , 0)) {
+ SCIkwarn(SCIkERROR, "Client "PREG" of avoider "PREG" doesn't"
+ " have an isBlocked() funcselector\n", PRINT_REG(client), PRINT_REG(avoider));
+ return NULL_REG;
+ }
+
+ dx = destx - GET_SEL32V(client, x);
+ dy = desty - GET_SEL32V(client, y);
+ angle = get_angle(dx, dy);
+
+ SCIkdebug(SCIkBRESEN, "Movement (%d,%d), angle %d is %sblocked\n",
+ dx, dy, angle, (s->r_acc.offset)? " ": "not ");
+
+ if (s->r_acc.offset) { /* isBlocked() returned non-zero */
+ int rotation = (rand() & 1)? 45 : (360-45); /* Clockwise/counterclockwise */
+ int oldx = GET_SEL32V(client, x);
+ int oldy = GET_SEL32V(client, y);
+ int xstep = GET_SEL32V(client, xStep);
+ int ystep = GET_SEL32V(client, yStep);
+ int moves;
+
+ SCIkdebug(SCIkBRESEN, " avoider "PREG"\n", PRINT_REG(avoider));
+
+ for (moves = 0; moves < 8; moves++) {
+ int move_x = (int) (sin(angle * PI / 180.0) * (xstep));
+ int move_y = (int) (-cos(angle * PI / 180.0) * (ystep));
+
+ PUT_SEL32V(client, x, oldx + move_x);
+ PUT_SEL32V(client, y, oldy + move_y);
+
+ SCIkdebug(SCIkBRESEN, "Pos (%d,%d): Trying angle %d; delta=(%d,%d)\n",
+ oldx, oldy, angle, move_x, move_y);
+
+ if (invoke_selector(INV_SEL(client, canBeHere, 1) , 0)) {
+ SCIkwarn(SCIkERROR, "Client "PREG" of avoider "PREG" doesn't"
+ " have a canBeHere() funcselector\n",
+ PRINT_REG(client), PRINT_REG(avoider));
+ return NULL_REG;
+ }
+
+ PUT_SEL32V(client, x, oldx);
+ PUT_SEL32V(client, y, oldy);
+
+ if (s->r_acc.offset) { /* We can be here */
+ SCIkdebug(SCIkBRESEN, "Success\n");
+ PUT_SEL32V(client, heading, angle);
+
+ return make_reg(0, angle);
+ }
+
+ angle += rotation;
+
+ if (angle > 360)
+ angle -= 360;
+ }
+
+ SCIkwarn(SCIkWARNING, "DoAvoider failed for avoider "PREG"\n",
+ PRINT_REG(avoider));
+
+ } else {
+ int heading = GET_SEL32V(client, heading);
+
+ if (heading == -1)
+ return s->r_acc; /* No change */
+
+ PUT_SEL32V(client, heading, angle);
+
+ s->r_acc = make_reg(0, angle);
+
+ if (looper.segment) {
+ if (invoke_selector(INV_SEL(looper, doit, 1), 2, angle, client)) {
+ SCIkwarn(SCIkERROR, "Looper "PREG" of avoider "PREG" doesn't"
+ " have a doit() funcselector\n",
+ PRINT_REG(looper), PRINT_REG(avoider));
+ } else return s->r_acc;
+ } else
+ /* No looper? Fall back to DirLoop */
+
+ _k_dirloop(client, (word)angle, s, funct_nr, argc, argv);
+ }
+
+ return s->r_acc;
+}
+