scummvm/sword2/walker.cpp
Torbjörn Andersson 8ad28e3a7e More cleanup, and I've replaced most - not quite all - of BS2's debug
message functions with our own.

We still need to go through them and assign sensible debug levels to them.

svn-id: r10422
2003-09-27 11:02:58 +00:00

801 lines
21 KiB
C++

/* Copyright (C) 1994-2003 Revolution Software Ltd
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Header$
*/
// WALKER.CPP by James (14nov96)
// script functions for moving megas about the place & also for keeping tabs
// on them
// FN_walk() // walk to (x,y,dir)
// FN_walk_to_anim() // walk to start position of anim
// FN_turn() // turn to (dir)
// FN_stand_at() // stand at (x,y,dir)
// FN_stand() // stand facing (dir)
// FN_stand_after_anim() // stand at end position of anim
// FN_face_id() // turn to face object (id)
// FN_face_xy() // turn to face point (x,y)
// FN_is_facing() // is mega (id) facing us?
// FN_get_pos() // get details of another mega's position
#include "stdafx.h"
#include "console.h"
#include "defs.h"
#include "events.h"
#include "function.h"
#include "interpreter.h"
#include "logic.h" // for FN_add_to_kill_list
#include "object.h"
#include "protocol.h"
#include "router.h"
#include "sync.h"
int16 standby_x; // see FN_set_standby_coords
int16 standby_y;
uint8 standby_dir;
// walk mega to (x,y,dir)
int32 FN_walk(int32 *params) {
// params: 0 pointer to object's logic structure
// 1 pointer to object's graphic structure
// 2 pointer to object's mega structure
// 3 pointer to object's walkdata structure
// 4 target x-coord
// 5 target y-coord
// 6 target direction
Object_logic *ob_logic;
Object_graphic *ob_graph;
Object_mega *ob_mega;
Object_walkdata *ob_walkdata;
int16 target_x;
int16 target_y;
uint8 target_dir;
int8 route;
int32 walk_pc;
_walkData *walkAnim;
// get the parameters
ob_logic = (Object_logic *) params[0];
ob_graph = (Object_graphic *) params[1];
ob_mega = (Object_mega *) params[2];
target_x = (int16) params[4];
target_y = (int16) params[5];
target_dir = (uint8) params[6];
// if this is the start of the walk, calculate route
if (ob_logic->looping == 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 (ob_mega->feet_x == target_x && ob_mega->feet_y == target_y && ob_mega->current_dir == target_dir) {
RESULT = 0; // 0 means ok - finished walk
return IR_CONT; // may as well continue the script
}
// invalid direction (NB. '8' means end walk on ANY direction)
if (params[6] < 0 || params[6] > 8)
Con_fatal_error("Invalid direction (%d) in FN_walk", params[6]);
ob_walkdata = (Object_walkdata *) params[3];
ob_mega->walk_pc = 0; // always
// set up mem for _walkData in route_slots[] & set mega's
// 'route_slot_id' accordingly
AllocateRouteMem();
route = (int8) 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) {
// so script FN_walk loop continues until end of
// walk-anim
ob_logic->looping = 1;
// need to animate the route now, so don't set result
// or return yet!
// started walk(James23jun97)
ob_mega->currently_walking = 1;
// (see FN_get_player_savedata() in save_rest.cpp
} else {
// free up the walkdata mem block
FreeRouteMem();
// 1 means error, no walk created
RESULT = 1;
// may as well continue the script
return IR_CONT;
}
// ok, walk is about to start, so set the mega's graphic
// resource
ob_graph->anim_resource = ob_mega->megaset_res;
} else if (EXIT_FADING && GetFadeStatus() == RDFADE_BLACK) {
// double clicked an exit so quit the walk when screen is black
// ok, thats it - back to script and change screen
ob_logic->looping = 0; // so script loop stops
FreeRouteMem(); // free up the walkdata mem block
// must clear in-case on the new screen there's a walk
// instruction (which would get cut short)
EXIT_CLICK_ID = 0;
// this will be reset when we change screens, so we can use
// it in script to check if a 2nd-click came along
// EXIT_FADING = 0;
// finished walk (James23jun97)
ob_mega->currently_walking = 0;
// (see FN_get_player_savedata() in save_rest.cpp
RESULT = 0; // 0 means ok
// continue the script so that RESULT can be checked!
return(IR_CONT);
}
// get pointer to walkanim & current frame position
// lock the _walkData array
walkAnim = LockRouteMem();
walk_pc = ob_mega->walk_pc;
// if stopping the walk early, overwrite the next step with a
// slow-out, then finish
if (Check_event_waiting()) {
if (walkAnim[walk_pc].step == 0 && walkAnim[walk_pc + 1].step == 1) {
// at the beginning of a step
ob_walkdata = (Object_walkdata *) params[3];
EarlySlowOut(ob_mega, ob_walkdata);
}
}
// get new frame of walk
ob_graph->anim_pc = walkAnim[walk_pc].frame;
ob_mega->current_dir = walkAnim[walk_pc].dir;
ob_mega->feet_x = walkAnim[walk_pc].x;
ob_mega->feet_y = walkAnim[walk_pc].y;
// check if NEXT frame is in fact the end-marker of the walk sequence
// so we can return to script just as the final (stand) frame of the
// walk is set - so that if followed by an anim, the anim's first
// frame replaces the final stand-frame of the walk (see below)
// '512' is end-marker
if (walkAnim[walk_pc + 1].frame == 512) {
ob_logic->looping = 0; // so script loop stops
FreeRouteMem(); // free up the walkdata mem block
// finished walk(James23jun97)
ob_mega->currently_walking = 0;
// (see FN_get_player_savedata() in save_rest.cpp
// 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 that code had been commented out, and it
// was only run if a function that always returned zero
// returned non-zero.
if (Check_event_waiting()) {
Start_event();
RESULT = 1; // 1 means didn't finish walk
return IR_TERMINATE;
} else {
RESULT = 0; // 0 means ok - finished walk
// CONTINUE the script so that RESULT can be checked!
// Also, if an anim command follows the FN_walk
// 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;
}
}
// increment the walkanim frame number, float the walkanim & come
// back next cycle
ob_mega->walk_pc++;
// allow _walkData array to float about memory again
FloatRouteMem();
// stop the script, but repeat this call next cycle
return IR_REPEAT;
}
// walk mega to start position of anim
int32 FN_walk_to_anim(int32 *params) {
// params: 0 pointer to object's logic structure
// 1 pointer to object's graphic structure
// 2 pointer to object's mega structure
// 3 pointer to object's walkdata structure
// 4 anim resource id
Object_logic *ob_logic;
uint8 *anim_file;
_animHeader *anim_head;
int32 pars[7];
// if this is the start of the walk, read anim file to get start coords
ob_logic = (Object_logic *) params[0];
if (ob_logic->looping == 0) {
// open anim file
anim_file = res_man.Res_open(params[4]);
// point to animation header
anim_head = FetchAnimHeader( anim_file );
pars[4] = anim_head->feetStartX; // target_x
pars[5] = anim_head->feetStartY; // target_y
pars[6] = anim_head->feetStartDir; // target_dir
// close anim file
res_man.Res_close(params[4]);
// if start coords not yet set in anim header, use the standby
// coords (which should be set beforehand in the script)
if (pars[4] == 0 && pars[5] == 0) {
pars[4] = standby_x;
pars[5] = standby_y;
pars[6] = standby_dir;
debug(5, "WARNING: FN_walk_to_anim(%s) used standby coords", FetchObjectName(params[4]));
}
if (pars[6] < 0 || pars[6] > 7)
Con_fatal_error("Invalid direction (%d) in FN_walk_to_anim", pars[6]);
}
// set up the rest of the parameters for FN_walk()
pars[0] = params[0];
pars[1] = params[1];
pars[2] = params[2];
pars[3] = params[3];
// walkdata (param 3) is needed for EarlySlowOut if player clicks
// elsewhere during the walk
// call FN_walk() with target coords set to anim start position
return FN_walk(pars);
}
// turn mega to <direction>
// just needs to call FN_walk() with current feet coords, so router can
// produce anim of turn frames
int32 FN_turn(int32 *params) {
// params: 0 pointer to object's logic structure
// 1 pointer to object's graphic structure
// 2 pointer to object's mega structure
// 3 pointer to object's walkdata structure
// 4 target direction
Object_logic *ob_logic;
Object_mega *ob_mega;
int32 pars[7];
// if this is the start of the turn, get the mega's current feet
// coords + the required direction
ob_logic = (Object_logic *) params[0];
if (ob_logic->looping == 0) {
if (params[4] < 0 || params[4] > 7)
Con_fatal_error("Invalid direction (%d) in FN_turn", params[4]);
ob_mega = (Object_mega *) params[2];
pars[4] = ob_mega->feet_x;
pars[5] = ob_mega->feet_y;
pars[6] = params[4]; // DIRECTION to turn to
}
// set up the rest of the parameters for FN_walk()
pars[0] = params[0];
pars[1] = params[1];
pars[2] = params[2];
pars[3] = params[3];
// call FN_walk() with target coords set to feet coords
return FN_walk(pars);
}
// 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
int32 FN_stand_at(int32 *params) {
// params: 0 pointer to object's graphic structure
// 1 pointer to object's mega structure
// 2 target x-coord
// 3 target y-coord
// 4 target direction
Object_mega *ob_mega;
Object_graphic *ob_graph;
// check for invalid direction
if (params[4] < 0 || params[4] > 7)
Con_fatal_error("Invalid direction (%d) in FN_stand_at", params[4]);
// set up pointers to the graphic & mega structure
ob_graph = (Object_graphic *) params[0];
ob_mega = (Object_mega *) params[1];
// set up the stand frame & set the mega's new direction
// mega-set animation file
ob_graph->anim_resource = ob_mega->megaset_res;
ob_mega->feet_x = params[2];
ob_mega->feet_y = params[3];
// dir + first stand frame (always frame 96)
ob_graph->anim_pc = params[4] + 96;
ob_mega->current_dir = params[4];
return IR_CONT;
}
// stand mega in <direction> at current feet coords
// just needs to call FN_stand_at() with current feet coords
int32 FN_stand(int32 *params) {
// params: 0 pointer to object's graphic structure
// 1 pointer to object's mega structure
// 2 target direction
Object_mega *ob_mega = (Object_mega *) params[1];
int32 pars[5];
pars[0] = params[0];
pars[1] = params[1];
pars[2] = ob_mega->feet_x;
pars[3] = ob_mega->feet_y;
pars[4] = params[2]; // DIRECTION to stand in
// call FN_stand_at() with target coords set to feet coords
return FN_stand_at(pars);
}
// stand mega at end position of anim
int32 FN_stand_after_anim(int32 *params) {
// params: 0 pointer to object's graphic structure
// 1 pointer to object's mega structure
// 2 anim resource id
uint8 *anim_file;
_animHeader *anim_head;
int32 pars[5];
// open the anim file & set up a pointer to the animation header
// open anim file
anim_file = res_man.Res_open(params[2]);
anim_head = FetchAnimHeader(anim_file);
// set up the parameter list for FN_walk_to()
pars[0] = params[0];
pars[1] = params[1];
pars[2] = anim_head->feetEndX;
pars[3] = anim_head->feetEndY;
pars[4] = anim_head->feetEndDir;
// if start coords not available either use the standby coords (which
// should be set beforehand in the script)
if (pars[2] == 0 && pars[3] == 0) {
pars[2] = standby_x;
pars[3] = standby_y;
pars[4] = standby_dir;
debug(5, "WARNING: FN_stand_after_anim(%s) used standby coords", FetchObjectName(params[2]));
}
if (pars[4] < 0 || pars[4] > 7)
Con_fatal_error("Invalid direction (%d) in FN_stand_after_anim", pars[4]);
// close the anim file
res_man.Res_close(params[2]);
// call FN_stand_at() with target coords set to anim end position
return FN_stand_at(pars);
}
// stand mega at start position of anim
int32 FN_stand_at_anim(int32 *params) {
// params: 0 pointer to object's graphic structure
// 1 pointer to object's mega structure
// 2 anim resource id
uint8 *anim_file;
_animHeader *anim_head;
int32 pars[5];
// open the anim file & set up a pointer to the animation header
// open anim file
anim_file = res_man.Res_open(params[2]);
anim_head = FetchAnimHeader(anim_file);
// set up the parameter list for FN_walk_to()
pars[0] = params[0];
pars[1] = params[1];
pars[2] = anim_head->feetStartX;
pars[3] = anim_head->feetStartY;
pars[4] = anim_head->feetStartDir;
// if start coords not available use the standby coords (which should
// be set beforehand in the script)
if (pars[2] == 0 && pars[3]==0) {
pars[2] = standby_x;
pars[3] = standby_y;
pars[4] = standby_dir;
debug(5, "WARNING: FN_stand_at_anim(%s) used standby coords", FetchObjectName(params[2]));
}
if (pars[4] < 0 || pars[4] > 7)
Con_fatal_error("Invalid direction (%d) in FN_stand_after_anim", pars[4]);
// close the anim file
res_man.Res_close(params[2]);
// call FN_stand_at() with target coords set to anim end position
return FN_stand_at(pars);
}
// Code to workout direction from start to dest
// used in what_target not valid for all megas
#define diagonalx 36
#define diagonaly 8
int What_target(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;
}
// turn mega to face point (x,y) on the floor
// just needs to call FN_walk() with current feet coords & direction computed
// by What_target()
int32 FN_face_xy(int32 *params) {
// params: 0 pointer to object's logic structure
// 1 pointer to object's graphic structure
// 2 pointer to object's mega structure
// 3 pointer to object's walkdata structure
// 4 target x-coord
// 5 target y-coord
Object_logic *ob_logic;
Object_mega *ob_mega;
int32 pars[7];
// if this is the start of the turn, get the mega's current feet
// coords + the required direction
ob_logic = (Object_logic *) params[0];
if (ob_logic->looping == 0) {
ob_mega = (Object_mega *) params[2];
pars[4] = ob_mega->feet_x;
pars[5] = ob_mega->feet_y;
pars[6] = What_target(ob_mega->feet_x, ob_mega->feet_y, params[4], params[5]);
}
// set up the rest of the parameters for FN_walk()
pars[0] = params[0];
pars[1] = params[1];
pars[2] = params[2];
pars[3] = params[3];
// call FN_walk() with target coords set to feet coords
return FN_walk(pars);
}
int32 FN_face_mega(int32 *params) {
// params: 0 pointer to object's logic structure
// 1 pointer to object's graphic structure
// 2 pointer to object's mega structure
// 3 pointer to object's walkdata structure
// 4 id of target mega to face
uint32 null_pc = 3; // get ob_mega
char *raw_script_ad;
int32 pars[7];
Object_logic *ob_logic;
Object_mega *ob_mega;
_standardHeader *head;
ob_mega = (Object_mega *) params[2];
ob_logic = (Object_logic *) params[0];
if (ob_logic->looping == 0) {
// get targets info
head = (_standardHeader*) res_man.Res_open(params[4]);
if (head->fileType != GAME_OBJECT)
Con_fatal_error("FN_face_mega %d not an object", params[4]);
raw_script_ad = (char *) head;
//call the base script - this is the graphic/mouse service call
RunScript(raw_script_ad, raw_script_ad, &null_pc);
res_man.Res_close(params[4]);
// engine_mega is now the Object_mega of mega we want to turn
// to face
pars[3] = params[3];
pars[4] = ob_mega->feet_x;
pars[5] = ob_mega->feet_y;
pars[6] = What_target(ob_mega->feet_x, ob_mega->feet_y, engine_mega.feet_x, engine_mega.feet_y);
}
pars[0] = params[0];
pars[1] = params[1];
pars[2] = params[2];
pars[3] = params[3];
// call FN_walk() with target coords set to feet coords
return FN_walk(pars);
}
int32 FN_walk_to_talk_to_mega(int32 *params) {
// we route to left or right hand side of target id if possible
// target is a shrinking mega
// params: 0 pointer to object's logic structure
// 1 pointer to object's graphic structure
// 2 pointer to object's mega structure
// 3 pointer to object's walkdata structure
// 4 id of target mega to face
// 5 distance
Object_mega *ob_mega;
Object_logic *ob_logic;
uint32 null_pc = 3; // 4th script - get mega
char *raw_script_ad;
int32 pars[7];
int scale;
int mega_seperation = params[5];
_standardHeader *head;
ob_logic = (Object_logic*) params[0];
ob_mega = (Object_mega*) params[2];
pars[0] = params[0]; // standard stuff
pars[1] = params[1];
pars[2] = params[2];
pars[3] = params[3]; // walkdata
// not been here before so decide where to walk-to
if (!ob_logic->looping) {
// first request the targets info
head = (_standardHeader*) res_man.Res_open(params[4]);
if (head->fileType != GAME_OBJECT)
Con_fatal_error("FN_walk_to_talk_to_mega %d not an object", params[4]);
raw_script_ad = (char *) head;
// call the base script - this is the graphic/mouse service
// call
RunScript(raw_script_ad, raw_script_ad, &null_pc);
res_man.Res_close(params[4]);
// engine_mega is now the Object_mega of mega we want to
// route to
// stand exactly beside the mega, ie. at same y-coord
pars[5] = engine_mega.feet_y;
// apply scale factor to walk distance
// Ay+B gives 256 * scale ie. 256 * 256 * true_scale for even
// better accuracy, ie. scale = (Ay + B) / 256
scale = (ob_mega->scale_a * ob_mega->feet_y + ob_mega->scale_b) / 256;
mega_seperation= (mega_seperation * scale) / 256;
debug(5, "seperation %d", mega_seperation);
debug(5, " target x %d, y %d", engine_mega.feet_x, engine_mega.feet_y);
if (engine_mega.feet_x < ob_mega->feet_x)
{
// Target is left of us, so aim to stand to their
// right. Face down_left
pars[4] = engine_mega.feet_x + mega_seperation;
pars[6] = 5;
} else {
// Ok, must be right of us so aim to stand to their
// left. Face down_right.
pars[4] = engine_mega.feet_x - mega_seperation;
pars[6] = 3;
}
}
// first cycle builds the route - thereafter merely follows it
// Call FN_walk() with target coords set to feet coords. RESULT will
// be 1 when it finishes, or 0 if it failed to build route.
return FN_walk(pars);
}
int32 FN_set_walkgrid(int32 *params) {
Con_fatal_error("FN_set_walkgrid no longer valid");
return IR_CONT;
}
// add this walkgrid resource to the list of those used for routing in this
// location - note this is ignored in the resource is already in the list
int32 FN_add_walkgrid(int32 *params) {
// params: 0 id of walkgrid resource
// all objects that add walkgrids must be restarted whenever we
// re-enter a location
// DON'T EVER KILL GEORGE!
if (ID != 8) {
// need to call this in case it wasn't called in script!
// ('params' just used as dummy param)
FN_add_to_kill_list(params);
}
AddWalkGrid(params[0]);
// Touch the grid, getting it into memory.
res_man.Res_open(params[0]);
res_man.Res_close(params[0]);
return IR_CONT;
}
// remove this walkgrid resource from the list of those used for routing in
// this location - note that this is ignored if the resource isn't actually
// in the list
int32 FN_remove_walkgrid(int32 *params) {
// params: 0 id of walkgrid resource
RemoveWalkGrid(params[0]);
return IR_CONT;
}
int32 FN_register_walkgrid(int32 *params) {
Con_fatal_error("FN_register_walkgrid no longer valid");
return IR_CONT;
}
int32 FN_set_scaling(int32 *params) {
// params: 0 pointer to object's mega structure
// 1 scale constant A
// 2 scale constant B
// 256 * s = A * y + B
// where s is system scale, which itself is (256 * actual_scale) ie.
// s == 128 is half size
Object_mega *ob_mega = (Object_mega *) params[0];
ob_mega->scale_a = params[1];
ob_mega->scale_b = params[2];
return IR_CONT;
}
int32 FN_set_standby_coords(int32 *params) {
// set the standby walk coords to be used by FN_walk_to_anim &
// FN_stand_after_anim when the anim header's start/end coords are zero
// useful during development; can stay in final game anyway
// params: 0 x-coord
// 1 y-coord
// 2 direction (0..7)
if (params[2] < 0 || params[2] > 7)
Con_fatal_error("Invalid direction (%d) in FN_set_standby_coords", params[2]);
standby_x = (int16) params[0];
standby_y = (int16) params[1];
standby_dir = (uint8) params[2];
return IR_CONT;
}