scummvm/engines/cge2/hero.cpp
2014-06-22 14:10:47 +02:00

606 lines
15 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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.
*
*/
/*
* This code is based on original Sfinx source code
* Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon
*/
#include "cge2/hero.h"
#include "cge2/text.h"
#include "cge2/map.h"
namespace CGE2 {
Hero::Hero(CGE2Engine *vm)
: Sprite(vm), _contact(nullptr), _dir(kNoDir),
_curDim(0), _tracePtr(-1), _ignoreMap(false) {
}
Sprite *Hero::expand() { // It's very similar to Sprite's expand, but doesn't bother with "labels" for example. TODO: Try to unify the two later!
if (_ext)
return this;
char *text = _vm->_text->getText(_ref + 100);
char fname[kMaxPath];
_vm->mergeExt(fname, _file, kSprExt);
_ext = new SprExt(_vm);
if (_ext == nullptr)
error("No core %s", fname);
if (*_file) {
int cnt[kActions];
Seq *seq;
int section = kIdPhase;
for (int i = 0; i < kDimMax; i++) {
_dim[i] = new Bitmap[_shpCnt];
for (int j = 0; j < _shpCnt; j++)
_dim[i][j].setVM(_vm);
}
if (_seqCnt) {
seq = new Seq[_seqCnt];
if (seq == nullptr)
error("No core %s", fname);
} else
seq = nullptr;
for (int i = 0; i < kActions; i++)
cnt[i] = 0;
for (int i = 0; i < kActions; i++) {
byte n = _actionCtrl[i]._cnt;
if (n) {
_ext->_actions[i] = new CommandHandler::Command[n];
if (_ext->_actions[i] == nullptr)
error("No core %s", fname);
} else
_ext->_actions[i] = nullptr;
}
if (_vm->_resman->exist(fname)) { // sprite description file exist
EncryptedStream sprf(_vm, fname);
if (sprf.err())
error("Bad SPR [%s]", fname);
ID id;
Common::String line;
char tmpStr[kLineMax + 1];
int shpcnt = 0;
int seqcnt = 0;
int maxnow = 0;
int maxnxt = 0;
for (line = sprf.readLine(); !sprf.eos(); line = sprf.readLine()) {
if (line.empty())
continue;
Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr));
char *p = _vm->token(tmpStr);
id = _vm->ident(p);
switch (id) {
case kIdNear:
case kIdMTake:
case kIdFTake:
case kIdPhase:
case kIdSeq:
section = id;
break;
case kIdName:
Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr));
for (p = tmpStr; *p != '='; p++); // We search for the =
setName(_vm->tail(p));
break;
default:
if (id >= kIdNear)
break;
Seq *s;
switch (section) {
case kIdNear:
case kIdMTake:
case kIdFTake:
id = (ID)_vm->_commandHandler->getComId(p);
if (_actionCtrl[section]._cnt) {
CommandHandler::Command *c = &_ext->_actions[section][cnt[section]++];
c->_commandType = CommandType(id);
if ((p = _vm->token(nullptr)) == NULL)
error("Unexpected end of file! %s", fname);
c->_ref = _vm->number(p);
if ((p = _vm->token(nullptr)) == NULL)
error("Unexpected end of file! %s", fname);
c->_val = _vm->number(p);
c->_spritePtr = nullptr;
}
break;
case kIdSeq:
s = &seq[seqcnt++];
s->_now = atoi(p);
if (s->_now > maxnow)
maxnow = s->_now;
if ((p = _vm->token(nullptr)) == NULL)
break;
s->_next = _vm->number(p);
switch (s->_next) {
case 0xFF:
s->_next = seqcnt;
break;
case 0xFE:
s->_next = seqcnt - 1;
break;
}
if (s->_next > maxnxt)
maxnxt = s->_next;
if ((p = _vm->token(nullptr)) == NULL)
error("Unexpected end of file! %s", fname);
s->_dx = _vm->number(p);
if ((p = _vm->token(nullptr)) == NULL)
error("Unexpected end of file! %s", fname);
s->_dy = _vm->number(p);
if ((p = _vm->token(nullptr)) == NULL)
error("Unexpected end of file! %s", fname);
s->_dz = _vm->number(p);
if ((p = _vm->token(nullptr)) == NULL)
error("Unexpected end of file! %s", fname);
s->_dly = _vm->number(p);
break;
case kIdPhase:
for (int i = 0; i < kDimMax; i++) {
char *q = p;
q[1] = '0' + i;
Bitmap b(_vm, q);
if (!b.moveHi())
error("No EMS %s", q);
_dim[i][shpcnt] = b;
if (!shpcnt)
_hig[i] = b._h;
}
++shpcnt;
break;
}
}
}
if (seq) {
if (maxnow >= shpcnt)
error("Bad PHASE in SEQ %s", fname);
if (maxnxt >= seqcnt)
error("Bad JUMP in SEQ %s", fname);
setSeq(seq);
} else
setSeq(_stdSeq8);
setShapeList(_dim[0], shpcnt);
}
}
_reachStart = atoi(_vm->token(text));
_reachCycle = atoi(_vm->token(nullptr));
_sayStart = atoi(_vm->token(nullptr));
_funStart = atoi(_vm->token(nullptr));
_funDel = _funDel0 = (72 / _ext->_seq[0]._dly) * atoi(_vm->token(nullptr));
int i = stepSize() / 2;
_maxDist = sqrt(double(i * i * 2));
setCurrent();
return this;
}
void Hero::setCurrent() {
FXP m = _vm->_eye->_z / (_pos3D._z - _vm->_eye->_z);
FXP tmp = m * _siz.y;
int h = -(tmp.trunc());
int i = 0;
for (; i < kDimMax; i++) {
if (h >= (_hig[i] + _hig[i + 1]) / 2)
break;
}
_ext->_shpList = _dim[_curDim = i];
}
void Hero::hStep() {
if (!_ignoreMap && _ext) {
Seq *seq = _ext->_seq;
int ptr = seq[_seqPtr]._next;
seq += ptr;
if (seq->_dx | seq->_dz) {
V2D p0(_vm, _pos3D._x.round(), _pos3D._z.round());
V2D p1(_vm, p0.x + seq->_dx, p0.y + seq->_dz);
if (mapCross(p0, p1)) {
park();
return;
}
}
}
step();
}
Sprite *Hero::setContact() {
Sprite *spr;
int md = _maxDist << 1;
for (spr = _vm->_vga->_showQ->first(); spr; spr = spr->_next) {
if (spr->_actionCtrl[kNear]._cnt && (spr->_ref & 255) != 255) {
if (distance(spr) <= md) {
if (spr == _contact)
return nullptr;
else
break;
}
}
}
return (_contact = spr);
}
void Hero::tick() {
int z = _pos3D._z.trunc();
//-- maybe not exactly wid/2, but wid/3 ?
int d = ((_siz.x / 2) * _vm->_eye->_z.trunc()) / (_vm->_eye->_z.trunc() - z);
if (_dir != kNoDir) { // just walking...
if (_flags._hold || _tracePtr < 0)
park();
else {
Sprite *spr = setContact();
if (spr)
_vm->feedSnail(spr, kNear, this);
}
}
//---------------------------------------------------------------
if (_tracePtr >= 0) {
if (distance(_trace[_tracePtr]) <= _maxDist)
--_tracePtr;
if (_tracePtr < 0)
park();
else {
int stp = stepSize() / 2;
int dx = _trace[_tracePtr]._x.round() - _pos3D._x.round();
int dz = _trace[_tracePtr]._z.round() - _pos3D._z.round();
Dir dir = (dx > stp) ? kEE : ((-dx > stp) ? kWW : ((dz > stp) ? kNN : kSS));
turn(dir);
}
}
//---------------------------------------------------------------
hStep();
setCurrent();
switch (_dir) {
case kSS:
if (_pos3D._z < stepSize() / 2)
park();
break;
case kWW:
if (_pos2D.x <= d)
park();
break;
case kNN:
if (_pos3D._z > kScrDepth)
park();
break;
case kEE:
if (_pos2D.x >= kScrWidth - 1 - d)
park();
break;
}
if (_flags._trim)
gotoxyz_(_pos2D);
if (_pos3D._z.trunc() != z)
_flags._zmov = true;
if (--_funDel == 0)
fun();
}
int Hero::distance(V3D pos) {
V3D di = _pos3D - pos;
int x = di._x.round();
int z = di._z.round();
return ((int)sqrt((long double)x * x + z * z));
}
int Hero::distance(Sprite *spr) {
V3D pos = spr->_pos3D;
int mdx = (spr->_siz.x >> 1) + (_siz.x >> 1);
int dx = (_pos3D._x - spr->_pos3D._x).round();
if (dx < 0) {
mdx = -mdx;
if (dx > mdx)
pos._x = _pos3D._x;
else
pos._x += mdx;
} else {
if (dx < mdx)
pos._x = _pos3D._x;
else
pos._x += mdx;
}
return distance(pos);
}
void Hero::turn(Dir d) {
Dir dir = (_dir == kNoDir) ? kSS : _dir;
if (d != _dir) {
step((d == dir) ? 57 : (8 + 4 * dir + d));
_dir = d;
}
resetFun();
}
void Hero::park() {
if (_dir != kNoDir) {
step(8 + 5 * _dir);
_dir = kNoDir;
_trace[0] = _pos3D;
_tracePtr = -1;
setCurrent();
_flags._zmov = true;
}
_ignoreMap = false;
if (_time == 0)
++_time;
}
bool Hero::lower(Sprite * spr) {
return (spr->_pos3D._y + (spr->_siz.y >> 2) < 10);
}
void Hero::reach(int mode) {
Sprite *spr = nullptr;
if (mode >= 4) {
spr = _vm->_vga->_showQ->locate(mode);
if (spr) {
mode = !spr->_flags._east; // 0-1
if (lower(spr)) // 2-3
mode += 2;
}
}
// note: insert SNAIL commands in reverse order
_vm->_commandHandler->insertCommand(kCmdPause, -1, 24, nullptr);
_vm->_commandHandler->insertCommand(kCmdSeq, -1, _reachStart + _reachCycle * mode, this);
if (spr) {
_vm->_commandHandler->insertCommand(kCmdWait, -1, -1, this);
_vm->_commandHandler->insertCommand(kCmdWalk, -1, spr->_ref, this);
}
// sequence is not finished,
// now it is just at sprite appear (disappear) point
resetFun();
}
void Hero::fun() {
if (_vm->_commandHandler->idle()) {
park();
_vm->_commandHandler->addCommand(kCmdWait, -1, -1, this);
_vm->_commandHandler->addCommand(kCmdSeq, -1, _funStart, this);
}
_funDel = _funDel0 >> 2;
}
int Hero::len(V2D v) {
return sqrt(double(v.x * v.x + v.y * v.y));
}
bool Hero::findWay(){
V2D p0(_vm, _pos3D._x.round(), _pos3D._z.round());
V2D p1(_vm, _trace[_tracePtr]._x.round(), _trace[_tracePtr]._z.round());
bool pvOk;
bool phOk;
V2D ph(_vm, p1.x, p0.y);
V2D pv(_vm, p0.x, p1.y);
pvOk = (!mapCross(p0, pv) && !mapCross(pv, p1));
phOk = (!mapCross(p0, ph) && !mapCross(ph, p1));
int md = (_maxDist >> 1);
if (pvOk && (len(ph - p0) <= md || len(p1 - ph) <= md))
return true;
if (phOk && (len(pv - p0) <= md || len(p1 - pv) <= md))
return true;
if (pvOk) {
_trace[++_tracePtr] = V3D(pv.x, 0, pv.y);
return true;
}
if (phOk) {
_trace[++_tracePtr] = V3D(ph.x, 0, ph.y);
return true;
}
return false;
}
int Hero::snap(int p, int q, int grid) {
int d = q - p;
d = ((d >= 0) ? d : -d) % grid;
if (d > (grid >> 1))
d -= grid;
return (q >= p) ? (q - d) : (q + d);
}
void Hero::walkTo(V3D pos) {
if (distance(pos) <= _maxDist)
return;
int stp = stepSize();
pos._x = snap(_pos3D._x.round(), pos._x.round(), stp);
pos._y = 0;
pos._z = snap(_pos3D._z.round(), pos._z.round(), stp);
V2D p0(_vm, _pos3D._x.round(), _pos3D._z.round());
V2D p1(_vm, pos._x.round(), pos._z.round());
resetFun();
int cnt = mapCross(p0, p1);
if ((cnt & 1) == 0) { // even == way exists
_trace[_tracePtr = 0] = pos;
if (!findWay()) {
int i;
++_tracePtr;
for (i = stp; i < kMaxTry; i += stp) {
_trace[_tracePtr] = pos + V3D(i, 0, 0);
if (!mapCross(_trace[_tracePtr - 1], _trace[_tracePtr]) && findWay())
break;
_trace[_tracePtr] = pos + V3D(-i, 0, 0);
if (!mapCross(_trace[_tracePtr - 1], _trace[_tracePtr]) && findWay())
break;
_trace[_tracePtr] = pos + V3D(0, 0, i);
if (!mapCross(_trace[_tracePtr - 1], _trace[_tracePtr]) && findWay())
break;
_trace[_tracePtr] = pos + V3D(0, 0, -i);
if (!mapCross(_trace[_tracePtr - 1], _trace[_tracePtr]) && findWay())
break;
}
if (i >= kMaxTry)
_trace[_tracePtr] = V3D(_pos3D._x, 0, pos._z); // not found
}
}
}
void Hero::walkTo(Sprite *spr) {
int mdx = _siz.x >> 1;
int stp = (stepSize() + 1) / 2;
if (!spr->_flags._east)
mdx = -mdx;
walkTo(spr->_pos3D + V3D(mdx, 0, (!spr->_flags._frnt || spr->_pos3D._z < 8) ? stp : -stp));
}
V3D Hero::screenToGround(V2D pos) {
FXP z = _vm->_eye->_z + (_vm->_eye->_y * _vm->_eye->_z) / (FXP(pos.y) - _vm->_eye->_y);
FXP x = _vm->_eye->_x - ((FXP(pos.x) - _vm->_eye->_x) * (z - _vm->_eye->_z)) / _vm->_eye->_z;
return V3D(x.round(), 0, z.round());
}
int Hero::cross(const V2D &a, const V2D &b) {
int x = _pos3D._x.trunc();
int z = _pos3D._z.trunc();
int r = ((_siz.x / 3) * _vm->_eye->_z.trunc()) / (_vm->_eye->_z.trunc()- z);
return _vm->cross(a, b, V2D(_vm, x - r, z), V2D(_vm, x + r, z)) << 1;
}
bool CGE2Engine::cross(const V2D &a, const V2D &b, const V2D &c, const V2D &d) {
if (contain(a, b, c))
return true;
if (contain(a, b, d))
return true;
if (contain(c, d, a))
return true;
if (contain(c, d, b))
return true;
return sgn(det(a, b, c)) != sgn(det(a, b, d)) && sgn(det(c, d, a)) != sgn(det(c, d, b));
}
bool CGE2Engine::contain(const V2D &a, const V2D &b, const V2D &p) {
if (det(a, b, p))
return false;
return ((long)(a.x - p.x) * (p.x - b.x) >= 0 && (long)(a.y - p.y) * (p.y - b.y) >= 0);
}
long CGE2Engine::det(const V2D &a, const V2D &b, const V2D &c) {
long n = ((long)a.x * b.y + (long)b.x * c.y + (long)c.x*a.y) - ((long)c.x*b.y + (long)b.x*a.y + (long)a.x*c.y);
return n;
}
int CGE2Engine::sgn(long n) {
return (n == 0) ? 0 : ((n > 0) ? 1 : -1);
}
int Hero::mapCross(const V2D &a, const V2D &b) {
Hero *o = other();
int n = (o->_scene == _scene) ? o->cross(a, b) : 0;
if (!_ignoreMap)
n += _vm->mapCross(a, b);
return n;
}
int Hero::mapCross(const V3D &a, const V3D &b) {
return mapCross(V2D(_vm, a._x.round(), a._z.round()), V2D(_vm, b._x.round(), b._z.round()));
}
int CGE2Engine::mapCross(const V2D &a, const V2D &b) {
int cnt = 0;
V2D *n0 = nullptr;
V2D *p = nullptr;
for (int i = 0; i < _map->size(); i++) {
V2D *n = _map->getCoord(i);
if (p) {
if (cross(a, b, *n0, *n))
++cnt;
if (*n == *p)
p = nullptr;
} else {
p = n;
}
n0 = n;
}
return cnt;
}
void Hero::setScene(int c) {
Sprite::setScene(c);
resetFun();
}
void Hero::operator++() {
if (_curDim > 0)
_ext->_shpList = _dim[--_curDim];
}
void Hero::operator--() {
if (_curDim < kDimMax - 1)
_ext->_shpList = _dim[++_curDim];
}
bool Sprite::works(Sprite *spr) {
if (!spr || !spr->_ext)
return false;
bool ok = false;
Action a = _vm->_heroTab[_vm->_sex]->_ptr->action();
CommandHandler::Command *ct = spr->_ext->_actions[a];
if (ct) {
int i = spr->_actionCtrl[a]._ptr;
int n = spr->_actionCtrl[a]._cnt;
while (i < n && !ok) {
CommandHandler::Command *c = &ct[i++];
if (c->_commandType != kCmdUse)
break;
ok = (c->_ref == _ref);
if (c->_val > 255) {
if (ok) {
int p = spr->labVal(a, c->_val >> 8);
if (p >= 0)
spr->_actionCtrl[a]._ptr = p;
else
ok = false;
}
} else {
if (c->_val && c->_val != _vm->_now)
ok = false;
break;
}
}
}
return ok;
}
} // End of namespace CGE2