mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 21:59:17 +00:00
eaab877d66
The first name is Janusz, not Janus. The correct name was used in AUTHORS and credits. In case of doubts, see his personal webpage: https://www.jbw.pl/ - name is in the page footer
625 lines
14 KiB
C++
625 lines
14 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 Janusz 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), _maxDist(0) {
|
|
|
|
for (int i = 0; i < kDimMax; i++)
|
|
_dim[i] = nullptr;
|
|
|
|
_reachStart = _reachCycle = _sayStart = _funStart = 0;
|
|
_funDel0 = _funDel = 0;
|
|
}
|
|
|
|
Hero::~Hero() {
|
|
contract();
|
|
}
|
|
|
|
Sprite *Hero::expand() {
|
|
if (_ext)
|
|
return this;
|
|
|
|
char fname[kMaxPath];
|
|
_vm->mergeExt(fname, _file, kSprExt);
|
|
|
|
if (_ext != nullptr)
|
|
delete _ext;
|
|
|
|
_ext = new SprExt(_vm);
|
|
|
|
if (!*_file)
|
|
return this;
|
|
|
|
for (int i = 0; i < kDimMax; i++) {
|
|
if (_dim[i] != nullptr) {
|
|
delete[] _dim[i];
|
|
_dim[i] = nullptr;
|
|
}
|
|
}
|
|
for (int i = 0; i < kDimMax; i++) {
|
|
_dim[i] = new Bitmap[_shpCnt];
|
|
for (int j = 0; j < _shpCnt; j++)
|
|
_dim[i][j].setVM(_vm);
|
|
}
|
|
|
|
int cnt[kActions];
|
|
|
|
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];
|
|
else
|
|
_ext->_actions[i] = nullptr;
|
|
}
|
|
|
|
Seq *curSeq = nullptr;
|
|
if (_seqCnt)
|
|
curSeq = new Seq[_seqCnt];
|
|
|
|
if (_vm->_resman->exist(fname)) { // sprite description file exist
|
|
EncryptedStream sprf(_vm, fname);
|
|
if (sprf.err())
|
|
error("Bad SPR [%s]", fname);
|
|
|
|
ID section = kIdPhase;
|
|
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);
|
|
c->_ref = _vm->number(nullptr);
|
|
c->_val = _vm->number(nullptr);
|
|
c->_spritePtr = nullptr;
|
|
}
|
|
break;
|
|
case kIdSeq:
|
|
s = &curSeq[seqcnt++];
|
|
s->_now = atoi(p);
|
|
if (s->_now > maxnow)
|
|
maxnow = s->_now;
|
|
s->_next = _vm->number(nullptr);
|
|
switch (s->_next) {
|
|
case 0xFF:
|
|
s->_next = seqcnt;
|
|
break;
|
|
case 0xFE:
|
|
s->_next = seqcnt - 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (s->_next > maxnxt)
|
|
maxnxt = s->_next;
|
|
s->_dx = _vm->number(nullptr);
|
|
s->_dy = _vm->number(nullptr);
|
|
s->_dz = _vm->number(nullptr);
|
|
s->_dly = _vm->number(nullptr);
|
|
break;
|
|
case kIdPhase:
|
|
for (int i = 0; i < kDimMax; i++) {
|
|
char *q = p;
|
|
q[1] = '0' + i;
|
|
Bitmap b(_vm, q);
|
|
_dim[i][shpcnt] = b;
|
|
if (!shpcnt)
|
|
_hig[i] = b._h;
|
|
}
|
|
++shpcnt;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (curSeq) {
|
|
if (maxnow >= shpcnt)
|
|
error("Bad PHASE in SEQ %s", fname);
|
|
if (maxnxt >= seqcnt)
|
|
error("Bad JUMP in SEQ %s", fname);
|
|
setSeq(curSeq);
|
|
} else
|
|
setSeq(_stdSeq8);
|
|
|
|
setShapeList(_dim[0], shpcnt);
|
|
}
|
|
|
|
char *tempStr = _vm->_text->getText(_ref + 100);
|
|
char *text = new char[strlen(tempStr) + 1];
|
|
strcpy(text, tempStr);
|
|
_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));
|
|
delete[] text;
|
|
|
|
int i = stepSize() / 2;
|
|
_maxDist = (int)sqrt(double(i * i * 2));
|
|
setCurrent();
|
|
|
|
return this;
|
|
}
|
|
|
|
Sprite *Hero::contract() {
|
|
for (int i = 0; i < kDimMax; i++) {
|
|
if (_dim[i] != nullptr) {
|
|
delete[] _dim[i];
|
|
if (_ext->_shpList == _dim[i])
|
|
_ext->_shpList = nullptr;
|
|
_dim[i] = nullptr;
|
|
}
|
|
}
|
|
Sprite::contract();
|
|
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 - 1; 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) && (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;
|
|
default:
|
|
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();
|
|
int retval = (int)sqrt((double)x * x + z * z);
|
|
return retval;
|
|
}
|
|
|
|
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 (int)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());
|
|
V2D ph(_vm, p1.x, p0.y);
|
|
V2D pv(_vm, p0.x, p1.y);
|
|
bool pvOk = (!mapCross(p0, pv) && !mapCross(pv, p1));
|
|
bool 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 = abs(q - p) % 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) || contain(a, b, d) || contain(c, d, a) || 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) {
|
|
return ((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);
|
|
}
|
|
|
|
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
|