scummvm/sword2/walker.cpp
Torbjörn Andersson 7f4cda6622 Applied my own patch #1341495, in an attempt to fix alignment issues
reported by Crilith.

To elaborate a bit, the engine no longer accesses resource data through
packed structs. Instead it uses memory streams and the READ/WRITE
functions.

If data is mainly read, not written, I have replaced the old struct with a
new one with a read() function to read the whole thing from memory into the
struct's variables, and a write() function to dump the struct's variables
to memory. In fact, most of these write() functions remain unused.

If data is both read and written, I have replaced the struct with a class
with individual get/set functions to replace the old variables. This
manipulates memory directly.

Since I'm fairly sure that these structs are frequently stored as local
variables for a script, all script variables (both local and global) are
stored as little-endian and accessed through the READ/WRITE functions,
rather than being treated as arrays of 32-bit integers.

On a positive note, the functions for doing endian conversion of resources
and save games have been removed, and some general cleanups have been made
to assist in the rewrite.

Initial reports indicate that this patch indeed fixes alignment issues, and
that I have not - surprisingly - broken the game on big-endian platforms.
At least not in any immediately obvious way. And there's still plenty of
time to fix regressions before 0.9.0, too.

svn-id: r19366
2005-10-29 21:24:54 +00:00

454 lines
12 KiB
C++

/* Copyright (C) 1994-1998 Revolution Software Ltd.
* Copyright (C) 2003-2005 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Header$
*/
// WALKER.CPP by James (14nov96)
// Functions for moving megas about the place & also for keeping tabs on them
#include "common/stdafx.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/interpreter.h"
#include "sword2/logic.h"
#include "sword2/resman.h"
#include "sword2/router.h"
namespace Sword2 {
void Router::setStandbyCoords(int16 x, int16 y, uint8 dir) {
assert(dir <= 7);
_standbyX = x;
_standbyY = y;
_standbyDir = dir;
}
/**
* Work out direction from start to dest.
*/
// Used in whatTarget(); not valid for all megas
#define diagonalx 36
#define diagonaly 8
int Router::whatTarget(int startX, int startY, int destX, int destY) {
int deltaX = destX - startX;
int deltaY = destY - startY;
// 7 0 1
// 6 2
// 5 4 3
// Flat route
if (ABS(deltaY) * diagonalx < ABS(deltaX) * diagonaly / 2)
return (deltaX > 0) ? 2 : 6;
// Vertical route
if (ABS(deltaY) * diagonalx / 2 > ABS(deltaX) * diagonaly)
return (deltaY > 0) ? 4 : 0;
// Diagonal route
if (deltaX > 0)
return (deltaY > 0) ? 3 : 1;
return (deltaY > 0) ? 5 : 7;
}
/**
* Walk meta to (x,y,dir). Set RESULT to 0 if it succeeded. Otherwise, set
* RESULT to 1. Return true if the mega has finished walking.
*/
int Router::doWalk(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y, uint8 target_dir) {
ObjectLogic obLogic(ob_logic);
ObjectGraphic obGraph(ob_graph);
ObjectMega obMega(ob_mega);
// If this is the start of the walk, calculate the route.
if (obLogic.getLooping() == 0) {
// If we're already there, don't even bother allocating
// memory and calling the router, just quit back & continue
// the script! This avoids an embarassing mega stand frame
// appearing for one cycle when we're already in position for
// an anim eg. repeatedly clicking on same object to repeat
// an anim - no mega frame will appear in between runs of the
// anim.
if (obMega.getFeetX() == target_x && obMega.getFeetY() == target_y && obMega.getCurDir() == target_dir) {
_vm->_logic->writeVar(RESULT, 0);
return IR_CONT;
}
assert(target_dir <= 8);
obMega.setWalkPc(0);
// Set up mem for _walkData in route_slots[] & set mega's
// 'route_slot_id' accordingly
allocateRouteMem();
int32 route = routeFinder(ob_mega, ob_walkdata, target_x, target_y, target_dir);
// 0 = can't make route to target
// 1 = created route
// 2 = zero route but may need to turn
if (route != 1 && route != 2) {
freeRouteMem();
_vm->_logic->writeVar(RESULT, 1);
return IR_CONT;
}
// Walk is about to start
obMega.setIsWalking(1);
obLogic.setLooping(1);
obGraph.setAnimResource(obMega.getMegasetRes());
} else if (_vm->_logic->readVar(EXIT_FADING) && _vm->_screen->getFadeStatus() == RDFADE_BLACK) {
// Double clicked an exit, and the screen has faded down to
// black. Ok, that's it. Back to script and change screen.
// We have to clear te EXIT_CLICK_ID variable in case there's a
// walk instruction on the new screen, or it'd be cut short.
freeRouteMem();
obLogic.setLooping(0);
obMega.setIsWalking(0);
_vm->_logic->writeVar(EXIT_CLICK_ID, 0);
_vm->_logic->writeVar(RESULT, 0);
return IR_CONT;
}
// Get pointer to walkanim & current frame position
WalkData *walkAnim = getRouteMem();
int32 walk_pc = obMega.getWalkPc();
// If stopping the walk early, overwrite the next step with a
// slow-out, then finish
if (_vm->_logic->checkEventWaiting() && walkAnim[walk_pc].step == 0 && walkAnim[walk_pc + 1].step == 1) {
// At the beginning of a step
earlySlowOut(ob_mega, ob_walkdata);
}
// Get new frame of walk
obGraph.setAnimPc(walkAnim[walk_pc].frame);
obMega.setCurDir(walkAnim[walk_pc].dir);
obMega.setFeetX(walkAnim[walk_pc].x);
obMega.setFeetY(walkAnim[walk_pc].y);
// Is the NEXT frame is the end-marker (512) of the walk sequence?
if (walkAnim[walk_pc + 1].frame != 512) {
// No, it wasn't. Increment the walk-anim frame number and
// come back next cycle.
obMega.setWalkPc(obMega.getWalkPc() + 1);
return IR_REPEAT;
}
// We have reached the end-marker, which means we can return to the
// script just as the final (stand) frame of the walk is set.
freeRouteMem();
obLogic.setLooping(0);
obMega.setIsWalking(0);
// If George's walk has been interrupted to run a new action script for
// instance or Nico's walk has been interrupted by player clicking on
// her to talk
// There used to be code here for checking if two megas were colliding,
// but it had been commented out, and it was only run if a function
// that always returned zero returned non-zero.
if (_vm->_logic->checkEventWaiting()) {
_vm->_logic->startEvent();
_vm->_logic->writeVar(RESULT, 1);
return IR_TERMINATE;
}
_vm->_logic->writeVar(RESULT, 0);
// CONTINUE the script so that RESULT can be checked! Also, if an anim
// command follows the fnWalk command, the 1st frame of the anim (which
// is always a stand frame itself) can replace the final stand frame of
// the walk, to hide the slight difference between the shrinking on the
// mega frames and the pre-shrunk anim start-frame.
return IR_CONT;
}
/**
* Walk mega to start position of anim
*/
int Router::walkToAnim(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 animRes) {
int16 target_x = 0;
int16 target_y = 0;
uint8 target_dir = 0;
// Walkdata is needed for earlySlowOut if player clicks elsewhere
// during the walk.
// If this is the start of the walk, read anim file to get start coords
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
byte *anim_file = _vm->_resman->openResource(animRes);
AnimHeader anim_head;
anim_head.read(_vm->fetchAnimHeader(anim_file));
target_x = anim_head.feetStartX;
target_y = anim_head.feetStartY;
target_dir = anim_head.feetStartDir;
_vm->_resman->closeResource(animRes);
// If start coords not yet set in anim header, use the standby
// coords (which should be set beforehand in the script).
if (target_x == 0 && target_y == 0) {
target_x = _standbyX;
target_y = _standbyY;
target_dir = _standbyDir;
}
assert(target_dir <= 7);
}
return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
}
/**
* Route to the left or right hand side of target id, if possible.
*/
int Router::walkToTalkToMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId, uint32 separation) {
ObjectMega obMega(ob_mega);
int16 target_x = 0;
int16 target_y = 0;
uint8 target_dir = 0;
// If this is the start of the walk, calculate the route.
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
assert(_vm->_resman->fetchType(megaId) == GAME_OBJECT);
// Call the base script. This is the graphic/mouse service
// call, and will set _engineMega to the ObjectMega of mega we
// want to route to.
_vm->_logic->runResScript(megaId, 3);
ObjectMega targetMega(_vm->_logic->getEngineMega());
// Stand exactly beside the mega, ie. at same y-coord
target_y = targetMega.getFeetY();
int scale = obMega.calcScale();
int mega_separation = (separation * scale) / 256;
debug(4, "Target is at (%d, %d), separation %d", targetMega.getFeetX(), targetMega.getFeetY(), mega_separation);
if (targetMega.getFeetX() < obMega.getFeetX()) {
// Target is left of us, so aim to stand to their
// right. Face down_left
target_x = targetMega.getFeetX() + mega_separation;
target_dir = 5;
} else {
// Ok, must be right of us so aim to stand to their
// left. Face down_right.
target_x = targetMega.getFeetX() - mega_separation;
target_dir = 3;
}
}
return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
}
/**
* Turn mega to the specified direction. Just needs to call doWalk() with
* current feet coords, so router can produce anim of turn frames.
*/
int Router::doFace(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint8 target_dir) {
int16 target_x = 0;
int16 target_y = 0;
// If this is the start of the turn, get the mega's current feet
// coords + the required direction
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
assert(target_dir <= 7);
ObjectMega obMega(ob_mega);
target_x = obMega.getFeetX();
target_y = obMega.getFeetY();
}
return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
}
/**
* Turn mega to face point (x,y) on the floor
*/
int Router::faceXY(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y) {
uint8 target_dir = 0;
// If this is the start of the turn, get the mega's current feet
// coords + the required direction
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
ObjectMega obMega(ob_mega);
target_dir = whatTarget(obMega.getFeetX(), obMega.getFeetY(), target_x, target_y);
}
return doFace(ob_logic, ob_graph, ob_mega, ob_walkdata, target_dir);
}
/**
* Turn mega to face another mega.
*/
int Router::faceMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId) {
uint8 target_dir = 0;
// If this is the start of the walk, decide where to walk to.
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
assert(_vm->_resman->fetchType(megaId) == GAME_OBJECT);
// Call the base script. This is the graphic/mouse service
// call, and will set _engineMega to the ObjectMega of mega we
// want to turn to face.
_vm->_logic->runResScript(megaId, 3);
ObjectMega obMega(ob_mega);
ObjectMega targetMega(_vm->_logic->getEngineMega());
target_dir = whatTarget(obMega.getFeetX(), obMega.getFeetY(), targetMega.getFeetX(), targetMega.getFeetY());
}
return doFace(ob_logic, ob_graph, ob_mega, ob_walkdata, target_dir);
}
/**
* Stand mega at (x,y,dir)
* Sets up the graphic object, but also needs to set the new 'current_dir' in
* the mega object, so the router knows in future
*/
void Router::standAt(byte *ob_graph, byte *ob_mega, int32 x, int32 y, int32 dir) {
assert(dir >= 0 && dir <= 7);
ObjectGraphic obGraph(ob_graph);
ObjectMega obMega(ob_mega);
// Set up the stand frame & set the mega's new direction
obMega.setFeetX(x);
obMega.setFeetY(y);
obMega.setCurDir(dir);
// Mega-set animation file
obGraph.setAnimResource(obMega.getMegasetRes());
// Dir + first stand frame (always frame 96)
obGraph.setAnimPc(dir + 96);
}
/**
* Stand mega at end position of anim
*/
void Router::standAfterAnim(byte *ob_graph, byte *ob_mega, uint32 animRes) {
byte *anim_file = _vm->_resman->openResource(animRes);
AnimHeader anim_head;
anim_head.read(_vm->fetchAnimHeader(anim_file));
int32 x = anim_head.feetEndX;
int32 y = anim_head.feetEndY;
int32 dir = anim_head.feetEndDir;
_vm->_resman->closeResource(animRes);
// If start coords not available either use the standby coords (which
// should be set beforehand in the script)
if (x == 0 && y == 0) {
x = _standbyX;
y = _standbyY;
dir = _standbyDir;
}
standAt(ob_graph, ob_mega, x, y, dir);
}
void Router::standAtAnim(byte *ob_graph, byte *ob_mega, uint32 animRes) {
byte *anim_file = _vm->_resman->openResource(animRes);
AnimHeader anim_head;
anim_head.read(_vm->fetchAnimHeader(anim_file));
int32 x = anim_head.feetStartX;
int32 y = anim_head.feetStartY;
int32 dir = anim_head.feetStartDir;
_vm->_resman->closeResource(animRes);
// If start coords not available use the standby coords (which should
// be set beforehand in the script)
if (x == 0 && y == 0) {
x = _standbyX;
y = _standbyY;
dir = _standbyDir;
}
standAt(ob_graph, ob_mega, x, y, dir);
}
} // End of namespace Sword2