mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-26 12:48:16 +00:00
2c601ad06a
Indeed, we were missing some code from the original which handled overlapping actors in which some moved and some did not, which caused the latter to be erased. Thanks eriktorbjorn for the great pointers on this.
4365 lines
110 KiB
C++
4365 lines
110 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/system.h" // for setFocusRectangle/clearFocusRectangle
|
|
#include "common/scummsys.h"
|
|
#include "scumm/scumm.h"
|
|
#include "scumm/actor.h"
|
|
#include "scumm/actor_he.h"
|
|
#include "scumm/akos.h"
|
|
#include "scumm/boxes.h"
|
|
#include "scumm/charset.h"
|
|
#include "scumm/costume.h"
|
|
#include "scumm/he/intern_he.h"
|
|
#include "scumm/object.h"
|
|
#include "scumm/resource.h"
|
|
#include "scumm/scumm_v7.h"
|
|
#include "scumm/scumm_v0.h"
|
|
#include "scumm/he/sound_he.h"
|
|
#include "scumm/he/sprite_he.h"
|
|
#include "scumm/usage_bits.h"
|
|
#include "scumm/util.h"
|
|
|
|
namespace Scumm {
|
|
|
|
byte Actor::kInvalidBox = 0;
|
|
|
|
static const byte v0ActorDemoTalk[25] = {
|
|
0x00,
|
|
0x06, // Syd
|
|
0x06, // Razor
|
|
0x06, // Dave
|
|
0x06, // Michael
|
|
0x06, // Bernard
|
|
0x06, // Wendy
|
|
0x00, // Jeff
|
|
0x46, // Radiation Suit
|
|
0x06, // Dr Fred
|
|
0x06, // Nurse Edna
|
|
0x06, // Weird Ed
|
|
0x06, // Dead Cousin Ted
|
|
0xE2, // Purple Tentacle
|
|
0xE2, // Green Tentacle
|
|
0x06, // Meteor police
|
|
0xC0, // Meteor
|
|
0x06, // Mark Eteer
|
|
0x06, // Talkshow Host
|
|
0x00, // Plant
|
|
0xC0, // Meteor Radiation
|
|
0xC0, // Edsel (small, outro)
|
|
0x00, // Meteor (small, intro)
|
|
0x06, // Sandy (Lab)
|
|
0x06, // Sandy (Cut-Scene)
|
|
};
|
|
|
|
static const byte v0ActorTalk[25] = {
|
|
0x00,
|
|
0x06, // Syd
|
|
0x06, // Razor
|
|
0x06, // Dave
|
|
0x06, // Michael
|
|
0x06, // Bernard
|
|
0x06, // Wendy
|
|
0x00, // Jeff
|
|
0x46, // Radiation Suit
|
|
0x06, // Dr Fred
|
|
0x06, // Nurse Edna
|
|
0x06, // Weird Ed
|
|
0x06, // Dead Cousin Ted
|
|
0xFF, // Purple Tentacle
|
|
0xFF, // Green Tentacle
|
|
0x06, // Meteor police
|
|
0xC0, // Meteor
|
|
0x06, // Mark Eteer
|
|
0x06, // Talkshow Host
|
|
0x00, // Plant
|
|
0xC0, // Meteor Radiation
|
|
0xC0, // Edsel (small, outro)
|
|
0x00, // Meteor (small, intro)
|
|
0x06, // Sandy (Lab)
|
|
0x06, // Sandy (Cut-Scene)
|
|
};
|
|
|
|
static const byte v0WalkboxSlantedModifier[0x16] = {
|
|
0x00,0x01,0x02,0x03,0x03,0x04,0x05,0x06,
|
|
0x06,0x07,0x08,0x09,0x09,0x0A,0x0B,
|
|
0x0C,0x0C,0x0D,0x0E,0x0F,0x10,0x10
|
|
};
|
|
|
|
Actor::Actor(ScummEngine *scumm, int id) :
|
|
_vm(scumm), _number(id), _visible(false), _shadowMode(0), _flip(false), _frame(0), _walkbox(0), _talkPosX(0), _talkPosY(0),
|
|
_talkScript(0), _walkScript(0), _ignoreTurns(false), _drawToBackBuf(false), _layer(0), _heOffsX(0), _heOffsY(0), _heSkipLimbs(false),
|
|
_heCondMask(0), _hePaletteNum(0), _heShadow(0), _elevation(0), _facing(0), _targetFacing(0), _speedx(0), _speedy(0),
|
|
_animProgress(0), _animSpeed(0), _costumeNeedsInit(false) {
|
|
assert(_vm != nullptr);
|
|
}
|
|
|
|
void ActorHE::initActor(int mode) {
|
|
Actor::initActor(mode);
|
|
|
|
if (mode == -1) {
|
|
_heOffsX = _heOffsY = 0;
|
|
_heSkipLimbs = false;
|
|
memset(_heTalkQueue, 0, sizeof(_heTalkQueue));
|
|
}
|
|
|
|
if (mode == 1 || mode == -1) {
|
|
_heCondMask = 1;
|
|
_heNoTalkAnimation = 0;
|
|
_heSkipLimbs = false;
|
|
} else if (mode == 2) {
|
|
_heCondMask = 1;
|
|
_heSkipLimbs = false;
|
|
}
|
|
|
|
_heShadow = 0;
|
|
_hePaletteNum = 0;
|
|
_generalFlags = 0;
|
|
_heTalking = false;
|
|
|
|
if (_vm->_game.heversion >= 61)
|
|
_flip = 0;
|
|
|
|
((ScummEngine_v60he *)_vm)->setActorClippingRect(_number, -1, -1, -1, -1);
|
|
|
|
_auxActor = 0;
|
|
_auxEraseX1 = 0;
|
|
_auxEraseY1 = 0;
|
|
_auxEraseX2 = -1;
|
|
_auxEraseY2 = -1;
|
|
}
|
|
|
|
void Actor::initActor(int mode) {
|
|
|
|
if (mode == -1) {
|
|
_top = _bottom = 0;
|
|
_needRedraw = false;
|
|
_needBgReset = false;
|
|
_costumeNeedsInit = false;
|
|
_visible = false;
|
|
_flip = false;
|
|
_speedx = 8;
|
|
_speedy = 2;
|
|
_frame = 0;
|
|
_walkbox = 0;
|
|
_animProgress = 0;
|
|
_drawToBackBuf = false;
|
|
memset(_animVariable, 0, sizeof(_animVariable));
|
|
memset(_palette, 0, sizeof(_palette));
|
|
memset(_sound, 0, sizeof(_sound));
|
|
_cost.reset();
|
|
_walkdata.reset();
|
|
_walkdata.point3.x = 32000;
|
|
_walkScript = 0;
|
|
}
|
|
|
|
if (mode == 1 || mode == -1) {
|
|
_costume = 0;
|
|
_room = 0;
|
|
_pos.x = 0;
|
|
_pos.y = 0;
|
|
_facing = 180;
|
|
} else if (mode == 2) {
|
|
_facing = 180;
|
|
}
|
|
_elevation = 0;
|
|
_width = 24;
|
|
_talkColor = 15;
|
|
_talkPosX = 0;
|
|
_talkPosY = -80;
|
|
_boxscale = _scaley = _scalex = 0xFF;
|
|
_charset = 0;
|
|
memset(_sound, 0, sizeof(_sound));
|
|
_targetFacing = _facing;
|
|
_lastValidX = 0;
|
|
_lastValidY = 0;
|
|
|
|
_shadowMode = 0;
|
|
_layer = 0;
|
|
|
|
stopActorMoving();
|
|
|
|
setActorWalkSpeed(8, 2);
|
|
|
|
_animSpeed = 0;
|
|
if (_vm->_game.version >= 6)
|
|
_animProgress = 0;
|
|
|
|
_ignoreBoxes = false;
|
|
_forceClip = 0;
|
|
_ignoreTurns = false;
|
|
|
|
_talkFrequency = 256;
|
|
_talkPan = 64;
|
|
_talkVolume = 127;
|
|
|
|
_initFrame = 1;
|
|
_walkFrame = 2;
|
|
_standFrame = 3;
|
|
_talkStartFrame = 4;
|
|
_talkStopFrame = 5;
|
|
|
|
_walkScript = 0;
|
|
_talkScript = 0;
|
|
|
|
_vm->_classData[_number] = 0;
|
|
}
|
|
|
|
void Actor_v2::initActor(int mode) {
|
|
Actor_v3::initActor(mode);
|
|
|
|
_speedx = 1;
|
|
_speedy = 1;
|
|
|
|
_initFrame = 2;
|
|
_walkFrame = 0;
|
|
_standFrame = 1;
|
|
_talkStartFrame = 5;
|
|
_talkStopFrame = 4;
|
|
}
|
|
|
|
void Actor_v3::initActor(int mode) {
|
|
if (mode == -1) {
|
|
_stepX = 1;
|
|
_stepThreshold = 0;
|
|
}
|
|
Actor::initActor(mode);
|
|
}
|
|
|
|
void Actor_v7::initActor(int mode) {
|
|
if (mode == 1 || mode == -1)
|
|
_visible = false;
|
|
|
|
Actor::initActor(mode);
|
|
|
|
_forceClip = 100;
|
|
_vm->_classData[_number] = _vm->_classData[0];
|
|
}
|
|
|
|
void Actor_v0::initActor(int mode) {
|
|
Actor_v2::initActor(mode);
|
|
|
|
_costCommandNew = 0xFF;
|
|
_costCommand = 0xFF;
|
|
_miscflags = 0;
|
|
_speaking = 0;
|
|
|
|
_walkCountModulo = 0;
|
|
_newWalkBoxEntered = false;
|
|
_walkDirX = 0;
|
|
_walkDirY = 0;
|
|
_walkYCountGreaterThanXCount = 0;
|
|
_walkXCount = 0;
|
|
_walkXCountInc = 0;
|
|
_walkYCount = 0;
|
|
_walkYCountInc = 0;
|
|
_walkMaxXYCountInc = 0;
|
|
|
|
_tmp_WalkBox = 0;
|
|
_tmp_NewWalkBoxEntered = 0;
|
|
|
|
_animFrameRepeat = 0;
|
|
for (int i = 0; i < 8; ++i) {
|
|
_limbFrameRepeatNew[i] = 0;
|
|
_limbFrameRepeat[i] = 0;
|
|
_limb_flipped[i] = false;
|
|
}
|
|
|
|
walkBoxQueueReset();
|
|
|
|
if (_vm->_game.features & GF_DEMO) {
|
|
_sound[0] = v0ActorDemoTalk[_number];
|
|
} else {
|
|
_sound[0] = v0ActorTalk[_number];
|
|
}
|
|
}
|
|
|
|
void Actor_v0::walkBoxQueueReset() {
|
|
_walkboxHistory.clear();
|
|
_walkboxQueueIndex = 0;
|
|
|
|
for (uint i = 0; i < ARRAYSIZE(_walkboxQueue); ++i) {
|
|
_walkboxQueue[i] = kInvalidBox;
|
|
}
|
|
}
|
|
|
|
bool Actor_v0::walkBoxQueueAdd(int box) {
|
|
|
|
if (_walkboxQueueIndex == ARRAYSIZE(_walkboxQueue))
|
|
return false;
|
|
|
|
_walkboxQueue[_walkboxQueueIndex++] = box;
|
|
_walkboxHistory.push_back(box);
|
|
return true;
|
|
}
|
|
|
|
void Actor_v0::walkboxQueueReverse() {
|
|
int j = ARRAYSIZE(_walkboxQueue) - 1;
|
|
|
|
while (_walkboxQueue[j] == kInvalidBox && j >= 1)
|
|
--j;
|
|
|
|
if (j <= 1)
|
|
return;
|
|
|
|
for (int i = 1; i < j && j >= 1 ; ++i, --j) {
|
|
|
|
byte tmp = _walkboxQueue[i];
|
|
|
|
_walkboxQueue[i] = _walkboxQueue[j];
|
|
_walkboxQueue[j] = tmp;
|
|
}
|
|
}
|
|
|
|
bool Actor_v0::walkBoxQueueFind(int box) {
|
|
|
|
for (uint i = 0; i < _walkboxHistory.size(); ++i) {
|
|
if (box == _walkboxHistory[i])
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Actor_v0::walkBoxQueuePrepare() {
|
|
walkBoxQueueReset();
|
|
int BoxFound = _walkbox;
|
|
|
|
if (BoxFound == _walkdata.destbox) {
|
|
|
|
_newWalkBoxEntered = true;
|
|
return true;
|
|
}
|
|
|
|
// Build a series of walkboxes from our current position, to the target
|
|
do {
|
|
// Add the current box to the queue
|
|
if (!walkBoxQueueAdd(BoxFound))
|
|
return false;
|
|
|
|
// Loop until we find a walkbox which hasn't been tested
|
|
while (_walkboxQueueIndex > 0) {
|
|
|
|
// Check if the dest box is a direct neighbour
|
|
if ((BoxFound = _vm->getNextBox(BoxFound, _walkdata.destbox)) == kInvalidBox) {
|
|
|
|
// Its not, start hunting through this boxes immediate connections
|
|
byte* boxm = _vm->getBoxConnectionBase(_walkboxQueue[_walkboxQueueIndex - 1]);
|
|
|
|
// Attempt to find one, which we havn't already used
|
|
for (; *boxm != kInvalidBox; ++boxm) {
|
|
if (walkBoxQueueFind(*boxm) != true)
|
|
break;
|
|
}
|
|
|
|
BoxFound = *boxm;
|
|
}
|
|
|
|
// Found one?
|
|
if (BoxFound != kInvalidBox) {
|
|
|
|
// Did we find a connection to the final walkbox
|
|
if (BoxFound == _walkdata.destbox) {
|
|
|
|
_newWalkBoxEntered = true;
|
|
|
|
walkBoxQueueAdd(BoxFound);
|
|
|
|
walkboxQueueReverse();
|
|
return true;
|
|
}
|
|
|
|
// Nope, check the next box
|
|
break;
|
|
}
|
|
|
|
// Drop this box, its useless to us
|
|
_walkboxQueue[--_walkboxQueueIndex] = kInvalidBox;
|
|
|
|
BoxFound = _walkboxQueue[_walkboxQueueIndex - 1];
|
|
}
|
|
|
|
} while (_walkboxQueueIndex > 0);
|
|
|
|
return false;
|
|
}
|
|
|
|
void Actor::setBox(int box) {
|
|
_walkbox = box;
|
|
setupActorScale();
|
|
}
|
|
|
|
void Actor_v3::setupActorScale() {
|
|
// WORKAROUND bug #2556: Under certain circumstances, it is possible
|
|
// for Henry Sr. to reach the front side of Castle Brunwald (following
|
|
// Indy there). But it seems the game has no small costume for Henry,
|
|
// hence he is shown as a giant, triple in size compared to Indy.
|
|
// To workaround this, we override the scale of Henry. Since V3 games
|
|
// like Indy3 don't use the costume scale otherwise, this works fine.
|
|
// The scale factor 0x50 was determined by some guess work.
|
|
//
|
|
// TODO: I can't reproduce this with the EGA DOS, EGA Macintosh and
|
|
// VGA DOS English releases, since Indy says he'd "better not" go back
|
|
// to the front of the castle at this point (script 77-201), as long
|
|
// as a special Bit is set for this (and it's set in room 21 entry
|
|
// script when Henry escapes from his room). Maybe there's a problem
|
|
// in the German release (and then it'd probably be better to restore
|
|
// that safeguard instead, since the game clearly doesn't expect you
|
|
// to go back inside the castle), but I don't own this version. -dwa
|
|
if (_number == 2 && _costume == 7 && _vm->_game.id == GID_INDY3 && _vm->_currentRoom == 12 && _vm->enhancementEnabled(kEnhMinorBugFixes)) {
|
|
_scalex = 0x50;
|
|
_scaley = 0x50;
|
|
} else {
|
|
// TODO: The following could probably be removed
|
|
_scalex = 0xFF;
|
|
_scaley = 0xFF;
|
|
}
|
|
}
|
|
|
|
void Actor::setupActorScale() {
|
|
if (_ignoreBoxes)
|
|
return;
|
|
|
|
// For some boxes, we ignore the scaling and use whatever values the
|
|
// scripts set. This is used e.g. in the Mystery Vortex in Sam&Max.
|
|
// Older games used the flag 0x20 differently, though.
|
|
if (_vm->_game.id == GID_SAMNMAX && (_vm->getBoxFlags(_walkbox) & kBoxIgnoreScale))
|
|
return;
|
|
|
|
_boxscale = _vm->getBoxScale(_walkbox);
|
|
|
|
uint16 scale = _vm->getScale(_walkbox, _pos.x, _pos.y);
|
|
assert(scale <= 0xFF);
|
|
|
|
_scalex = _scaley = (byte)scale;
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Actor walking ---
|
|
#pragma mark -
|
|
|
|
|
|
void ScummEngine::walkActors() {
|
|
for (int i = 1; i < _numActors; ++i) {
|
|
if (_actors[i]->isInCurrentRoom())
|
|
_actors[i]->walkActor();
|
|
}
|
|
}
|
|
|
|
void Actor::stopActorMoving() {
|
|
if (_walkScript)
|
|
_vm->stopScript(_walkScript);
|
|
|
|
if (_vm->_game.version == 0) {
|
|
_moving = 2;
|
|
setDirection(_facing);
|
|
} else {
|
|
_moving = 0;
|
|
}
|
|
}
|
|
|
|
void Actor::setActorWalkSpeed(uint newSpeedX, uint newSpeedY) {
|
|
if (newSpeedX == _speedx && newSpeedY == _speedy)
|
|
return;
|
|
|
|
_speedx = newSpeedX;
|
|
_speedy = newSpeedY;
|
|
|
|
if (_moving) {
|
|
if (_vm->_game.version == 8 && (_moving & MF_IN_LEG) == 0)
|
|
return;
|
|
calcMovementFactor(_walkdata.next);
|
|
}
|
|
}
|
|
|
|
int getAngleFromPos(int x, int y) {
|
|
if (ABS(y) * 2 < ABS(x)) {
|
|
if (x > 0)
|
|
return 90;
|
|
return 270;
|
|
} else {
|
|
if (y > 0)
|
|
return 180;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int Actor::calcMovementFactor(const Common::Point& next) {
|
|
int diffX, diffY;
|
|
int32 deltaXFactor, deltaYFactor;
|
|
|
|
if (_pos == next)
|
|
return 0;
|
|
|
|
diffX = next.x - _pos.x;
|
|
diffY = next.y - _pos.y;
|
|
|
|
deltaYFactor = _speedy << 16;
|
|
if (diffY < 0)
|
|
deltaYFactor = -deltaYFactor;
|
|
|
|
deltaXFactor = deltaYFactor * diffX;
|
|
if (diffY != 0) {
|
|
deltaXFactor /= diffY;
|
|
} else {
|
|
deltaYFactor = 0;
|
|
}
|
|
|
|
// We used to have ABS(deltaXFactor >> 16) for the calculation here, which
|
|
// caused bug no. https://bugs.scummvm.org/ticket/14582
|
|
// For SCUMM4-6 it is obvious from disam that they do the division by 0x10000.
|
|
// SCUMM7/8 original code gives the impression of using deltaXFactor >> 16 at
|
|
// first glance, but it really doesn't. It is a more complicated operation
|
|
// which amounts to the exact same thing as the following...
|
|
if ((uint)ABS(deltaXFactor / 0x10000) > _speedx) {
|
|
deltaXFactor = _speedx << 16;
|
|
if (diffX < 0)
|
|
deltaXFactor = -deltaXFactor;
|
|
|
|
deltaYFactor = deltaXFactor * diffY;
|
|
if (diffX != 0) {
|
|
deltaYFactor /= diffX;
|
|
} else {
|
|
deltaXFactor = 0;
|
|
}
|
|
}
|
|
|
|
_walkdata.xfrac = 0;
|
|
_walkdata.yfrac = 0;
|
|
_walkdata.cur = _pos;
|
|
_walkdata.next = next;
|
|
_walkdata.deltaXFactor = deltaXFactor;
|
|
_walkdata.deltaYFactor = deltaYFactor;
|
|
|
|
if (_vm->_game.version >= 7) {
|
|
_walkdata.facing = ((int)(atan2((double)deltaXFactor, (double)-deltaYFactor) * 180 / M_PI) + 360) % 360;
|
|
startWalkAnim((_moving & MF_IN_LEG) ? 2 : 1, _walkdata.facing);
|
|
_moving |= MF_IN_LEG;
|
|
} else {
|
|
_targetFacing = (ABS(diffY) * 3 > ABS(diffX)) ? (deltaYFactor > 0 ? 180 : 0) : (deltaXFactor > 0 ? 90 : 270);
|
|
}
|
|
|
|
return actorWalkStep();
|
|
}
|
|
|
|
int Actor_v3::calcMovementFactor(const Common::Point& next) {
|
|
int32 deltaXFactor, deltaYFactor;
|
|
|
|
if (_pos == next)
|
|
return 0;
|
|
|
|
int diffX = next.x - _pos.x;
|
|
int diffY = next.y - _pos.y;
|
|
|
|
if (_vm->_game.version == 3) {
|
|
// These two lines fix bug #1052 (INDY3: Hitler facing wrong directions in the Berlin scene).
|
|
// I can't see anything like this in the original SCUMM1/2 code, so I limit this to SCUMM3.
|
|
if (!(_moving & MF_LAST_LEG) && (int)_speedx > ABS(diffX) && (int)_speedy > ABS(diffY))
|
|
return 0;
|
|
|
|
_stepX = ((ABS(diffY) / (int)_speedy) >> 1) > (ABS(diffX) / (int)_speedx) ? _speedy + 1 : _speedx;
|
|
}
|
|
|
|
_stepThreshold = MAX(ABS(diffY) / _speedy, ABS(diffX) / _stepX);
|
|
deltaXFactor = (int32)_stepX;
|
|
deltaYFactor = (int32)_speedy;
|
|
|
|
if (diffX < 0)
|
|
deltaXFactor = -deltaXFactor;
|
|
if (diffY < 0)
|
|
deltaYFactor = -deltaYFactor;
|
|
|
|
_walkdata.xfrac = _walkdata.xAdd = deltaXFactor ? diffX / deltaXFactor : 0;
|
|
_walkdata.yfrac = _walkdata.yAdd = deltaYFactor ? diffY / deltaYFactor : 0;
|
|
_walkdata.cur = _pos;
|
|
_walkdata.next = next;
|
|
_walkdata.deltaXFactor = deltaXFactor;
|
|
_walkdata.deltaYFactor = deltaYFactor;
|
|
|
|
// The x/y distance ratio which determines whether to face up/down instead of left/right is different for SCUMM1/2 and SCUMM3.
|
|
_targetFacing = oldDirToNewDir(((ABS(diffY) * _facingXYratio) > ABS(diffX)) ? 3 - (diffY >= 0 ? 1 : 0) : (diffX >= 0 ? 1 : 0));
|
|
|
|
if (_vm->_game.version <= 2 && _facing != updateActorDirection(true))
|
|
_moving |= MF_TURN;
|
|
|
|
return actorWalkStep();
|
|
}
|
|
|
|
int Actor::actorWalkStep() {
|
|
_needRedraw = true;
|
|
|
|
if (_vm->_game.version < 7) {
|
|
int nextFacing = updateActorDirection(true);
|
|
if ((_walkFrame != _frame && !(_moving & MF_IN_LEG)) || _facing != nextFacing)
|
|
startWalkAnim(1, nextFacing);
|
|
_moving |= MF_IN_LEG;
|
|
}
|
|
|
|
if (_walkbox != _walkdata.curbox && _vm->checkXYInBoxBounds(_walkdata.curbox, _pos.x, _pos.y))
|
|
setBox(_walkdata.curbox);
|
|
|
|
int distX = ABS(_walkdata.next.x - _walkdata.cur.x);
|
|
int distY = ABS(_walkdata.next.y - _walkdata.cur.y);
|
|
|
|
if (ABS(_pos.x - _walkdata.cur.x) >= distX && ABS(_pos.y - _walkdata.cur.y) >= distY) {
|
|
// I have checked that only the v7/8 games have this different (non-)handling of the moving flag. Our code was
|
|
// correct for the lower versions. For COMI this fixes one part of the issues that caused ticket #4424 (wrong
|
|
// movement data being reported by ScummEngine_v8::o8_wait()).
|
|
if (_vm->_game.version < 7)
|
|
_moving &= ~MF_IN_LEG;
|
|
return 0;
|
|
}
|
|
|
|
int tmpX = (_pos.x << 16) + _walkdata.xfrac + (_walkdata.deltaXFactor >> 8) * _scalex;
|
|
_walkdata.xfrac = (uint16)tmpX;
|
|
_pos.x = (tmpX >> 16);
|
|
|
|
int tmpY = (_pos.y << 16) + _walkdata.yfrac + (_walkdata.deltaYFactor >> 8) * _scaley;
|
|
_walkdata.yfrac = (uint16)tmpY;
|
|
_pos.y = (tmpY >> 16);
|
|
|
|
if (ABS(_pos.x - _walkdata.cur.x) > distX)
|
|
_pos.x = _walkdata.next.x;
|
|
|
|
if (ABS(_pos.y - _walkdata.cur.y) > distY)
|
|
_pos.y = _walkdata.next.y;
|
|
|
|
if (_vm->_game.version >= 4 && _vm->_game.version <= 6 && _pos == _walkdata.next) {
|
|
_moving &= ~MF_IN_LEG;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int Actor_v3::actorWalkStep() {
|
|
_needRedraw = true;
|
|
|
|
int nextFacing = updateActorDirection(true);
|
|
if (!(_moving & MF_IN_LEG) || _facing != nextFacing) {
|
|
if (_walkFrame != _frame || _facing != nextFacing)
|
|
startWalkAnim(1, nextFacing);
|
|
|
|
_moving |= MF_IN_LEG;
|
|
// The next two lines fix bug #12278 for ZAK FM-TOWNS (SCUMM3). They are alse required for SCUMM 1/2 to prevent movement while
|
|
// turning, but only if the character has to make a turn. The correct behavior for v1/2 can be tested by letting Zak (only v1/2
|
|
// versions) walk in the starting room from the torn wallpaper to the desk drawer: Zak should first turn around clockwise by
|
|
// 180°, then walk one step to the left, then turn clockwise 90°. For ZAK FM-TOWNS (SCUMM3) this part will look quite different
|
|
// (and a bit weird), but I have confirmed the correctness with the FM-Towns emulator, too.
|
|
if (_vm->_game.version == 3 || (_vm->_game.version <= 2 && (_moving & MF_TURN)))
|
|
return 1;
|
|
}
|
|
|
|
if (_vm->_game.version == 3) {
|
|
if (_walkdata.next.x - (int)_stepX <= _pos.x && _walkdata.next.x + (int)_stepX >= _pos.x)
|
|
_pos.x = _walkdata.next.x;
|
|
if (_walkdata.next.y - (int)_speedy <= _pos.y && _walkdata.next.y + (int)_speedy >= _pos.y)
|
|
_pos.y = _walkdata.next.y;
|
|
|
|
if (_walkbox != _walkdata.curbox && _vm->checkXYInBoxBounds(_walkdata.curbox, _pos.x, _pos.y))
|
|
setBox(_walkdata.curbox);
|
|
|
|
if (_pos == _walkdata.next) {
|
|
_moving &= ~MF_IN_LEG;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ((_walkdata.xfrac += _walkdata.xAdd) >= _stepThreshold) {
|
|
if (_pos.x != _walkdata.next.x)
|
|
_pos.x += _walkdata.deltaXFactor;
|
|
_walkdata.xfrac -= _stepThreshold;
|
|
}
|
|
if ((_walkdata.yfrac += _walkdata.yAdd) >= _stepThreshold) {
|
|
if (_pos.y != _walkdata.next.y)
|
|
_pos.y += _walkdata.deltaYFactor;
|
|
_walkdata.yfrac -= _stepThreshold;
|
|
}
|
|
|
|
if (_vm->_game.version <= 2 && _pos == _walkdata.next) {
|
|
_moving &= ~MF_IN_LEG;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
bool Actor_v0::calcWalkDistances() {
|
|
_walkDirX = 0;
|
|
_walkDirY = 0;
|
|
_walkYCountGreaterThanXCount = 0;
|
|
uint16 A = 0;
|
|
|
|
if (_CurrentWalkTo.x >= _tmp_NewPos.x) {
|
|
A = _CurrentWalkTo.x - _tmp_NewPos.x;
|
|
_walkDirX = 1;
|
|
} else {
|
|
A = _tmp_NewPos.x - _CurrentWalkTo.x;
|
|
}
|
|
|
|
_walkXCountInc = A;
|
|
|
|
if (_CurrentWalkTo.y >= _tmp_NewPos.y) {
|
|
A = _CurrentWalkTo.y - _tmp_NewPos.y;
|
|
_walkDirY = 1;
|
|
} else {
|
|
A = _tmp_NewPos.y - _CurrentWalkTo.y;
|
|
}
|
|
|
|
_walkYCountInc = A;
|
|
if (!_walkXCountInc && !_walkYCountInc)
|
|
return true;
|
|
|
|
if (_walkXCountInc <= _walkYCountInc)
|
|
_walkYCountGreaterThanXCount = 1;
|
|
|
|
// 2FCC
|
|
A = _walkXCountInc;
|
|
if (A <= _walkYCountInc)
|
|
A = _walkYCountInc;
|
|
|
|
_walkMaxXYCountInc = A;
|
|
_walkXCount = _walkXCountInc;
|
|
_walkYCount = _walkYCountInc;
|
|
_walkCountModulo = _walkMaxXYCountInc;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Calculate the result of moving X+1 or X-1 */
|
|
byte Actor_v0::actorWalkXCalculate() {
|
|
byte A = _walkXCount;
|
|
A += _walkXCountInc;
|
|
if (A >= _walkCountModulo) {
|
|
if (!_walkDirX) {
|
|
_tmp_NewPos.x--;
|
|
} else {
|
|
_tmp_NewPos.x++;
|
|
}
|
|
|
|
A -= _walkCountModulo;
|
|
}
|
|
// 2EAC
|
|
_walkXCount = A;
|
|
setActorToTempPosition();
|
|
if (updateWalkbox() == kInvalidBox) {
|
|
// 2EB9
|
|
setActorToOriginalPosition();
|
|
|
|
return 3;
|
|
}
|
|
// 2EBF
|
|
if (_tmp_NewPos.x == _CurrentWalkTo.x)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Calculate the result of moving Y+1 or Y-1 */
|
|
byte Actor_v0::actorWalkYCalculate() {
|
|
byte A = _walkYCount;
|
|
A += _walkYCountInc;
|
|
if (A >= _walkCountModulo) {
|
|
if (!_walkDirY) {
|
|
_tmp_NewPos.y--;
|
|
} else {
|
|
_tmp_NewPos.y++;
|
|
}
|
|
|
|
A -= _walkCountModulo;
|
|
}
|
|
// 2EEB
|
|
_walkYCount = A;
|
|
setActorToTempPosition();
|
|
if (updateWalkbox() == kInvalidBox) {
|
|
// 2EF8
|
|
setActorToOriginalPosition();
|
|
return 4;
|
|
}
|
|
// 2EFE
|
|
if (_walkYCountInc != 0) {
|
|
if (_walkYCountInc == 0xFF) {
|
|
setActorToOriginalPosition();
|
|
return 4;
|
|
}
|
|
}
|
|
// 2F0D
|
|
if (_CurrentWalkTo.y == _tmp_NewPos.y)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Actor::startWalkActor(int destX, int destY, int dir) {
|
|
AdjustBoxResult abr;
|
|
|
|
if (!isInCurrentRoom() && _vm->_game.version >= 7) {
|
|
debugC(DEBUG_ACTORS, "startWalkActor: attempting to walk actor %d who is not in this room", _number);
|
|
return;
|
|
}
|
|
|
|
if (_vm->_game.version <= 4) {
|
|
abr.x = destX;
|
|
abr.y = destY;
|
|
abr.box = kInvalidBox;
|
|
} else {
|
|
abr = adjustXYToBeInBox(destX, destY);
|
|
}
|
|
|
|
if (!isInCurrentRoom() && _vm->_game.version <= 6) {
|
|
_pos.x = abr.x;
|
|
_pos.y = abr.y;
|
|
if (!_ignoreTurns && dir != -1)
|
|
_facing = dir;
|
|
return;
|
|
}
|
|
|
|
if (_vm->_game.version <= 2) {
|
|
abr = adjustXYToBeInBox(abr.x, abr.y);
|
|
if (_pos.x == abr.x && _pos.y == abr.y && (dir == -1 || _facing == dir))
|
|
return;
|
|
} else {
|
|
if (_ignoreBoxes) {
|
|
abr.box = kInvalidBox;
|
|
_walkbox = kInvalidBox;
|
|
} else {
|
|
if (_vm->_game.version < 7) {
|
|
if (_vm->checkXYInBoxBounds(_walkdata.destbox, abr.x, abr.y)) {
|
|
abr.box = _walkdata.destbox;
|
|
} else {
|
|
abr = adjustXYToBeInBox(abr.x, abr.y);
|
|
}
|
|
}
|
|
if (_moving && _walkdata.destdir == dir && _walkdata.dest.x == abr.x && _walkdata.dest.y == abr.y)
|
|
return;
|
|
}
|
|
|
|
if (_pos.x == abr.x && _pos.y == abr.y) {
|
|
if (dir != _facing)
|
|
turnToDirection(dir);
|
|
return;
|
|
}
|
|
}
|
|
|
|
_walkdata.dest.x = abr.x;
|
|
_walkdata.dest.y = abr.y;
|
|
_walkdata.destbox = abr.box;
|
|
_walkdata.destdir = dir;
|
|
_walkdata.point3.x = 32000;
|
|
_walkdata.curbox = _walkbox;
|
|
|
|
if (_vm->_game.version == 0) {
|
|
((Actor_v0 *)this)->walkBoxQueuePrepare();
|
|
|
|
} else if (_vm->_game.version <= 2) {
|
|
_moving = (_moving & ~(MF_LAST_LEG | MF_IN_LEG)) | MF_NEW_LEG;
|
|
} else {
|
|
_moving = (_moving & MF_IN_LEG) | MF_NEW_LEG;
|
|
}
|
|
|
|
}
|
|
|
|
void Actor::startWalkAnim(int cmd, int angle) {
|
|
if (_vm->_game.version >= 7)
|
|
angle = remapDirection(normalizeAngle(_vm->_costumeLoader->hasManyDirections(_costume), angle == -1 ? _walkdata.facing : angle), false);
|
|
else if (angle == -1)
|
|
angle = _facing;
|
|
|
|
if (_walkScript) {
|
|
int args[NUM_SCRIPT_LOCAL];
|
|
memset(args, 0, sizeof(args));
|
|
args[0] = _number;
|
|
args[1] = cmd;
|
|
args[2] = angle;
|
|
_vm->runScript(_walkScript, 1, 0, args);
|
|
} else {
|
|
if (_vm->_game.version >= 7 || cmd == 3)
|
|
turnToDirection(angle);
|
|
else
|
|
setDirection(angle);
|
|
|
|
if (cmd == 1)
|
|
startAnimActor(_walkFrame); /* start walk */
|
|
else if (cmd == 3)
|
|
startAnimActor(_standFrame); /* stop walk */
|
|
}
|
|
}
|
|
|
|
void Actor::walkActor() {
|
|
int new_dir, next_box;
|
|
Common::Point foundPath;
|
|
|
|
if (!_moving)
|
|
return;
|
|
|
|
if (!(_moving & MF_NEW_LEG)) {
|
|
if (_moving & MF_IN_LEG && actorWalkStep())
|
|
return;
|
|
|
|
if (_moving & MF_LAST_LEG) {
|
|
_moving = 0;
|
|
setBox(_walkdata.destbox);
|
|
if (_vm->_game.version <= 6) {
|
|
startAnimActor(_standFrame);
|
|
if (_targetFacing != _walkdata.destdir)
|
|
turnToDirection(_walkdata.destdir);
|
|
} else {
|
|
startWalkAnim(3, _walkdata.destdir);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (_moving & MF_TURN) {
|
|
if (_vm->_game.version <= 6) {
|
|
new_dir = updateActorDirection(false);
|
|
if (_facing != new_dir)
|
|
setDirection(new_dir);
|
|
else
|
|
_moving = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
setBox(_walkdata.curbox);
|
|
_moving &= MF_IN_LEG;
|
|
}
|
|
|
|
_moving &= ~MF_NEW_LEG;
|
|
do {
|
|
if (_walkbox == kInvalidBox) {
|
|
setBox(_walkdata.destbox);
|
|
_walkdata.curbox = _walkdata.destbox;
|
|
break;
|
|
}
|
|
|
|
if (_walkbox == _walkdata.destbox)
|
|
break;
|
|
|
|
next_box = _vm->getNextBox(_walkbox, _walkdata.destbox);
|
|
if (next_box < 0) {
|
|
_walkdata.destbox = _walkbox;
|
|
_moving |= MF_LAST_LEG;
|
|
return;
|
|
}
|
|
|
|
_walkdata.curbox = next_box;
|
|
|
|
if (findPathTowards(_walkbox, next_box, _walkdata.destbox, foundPath))
|
|
break;
|
|
|
|
if (calcMovementFactor(foundPath))
|
|
return;
|
|
|
|
setBox(_walkdata.curbox);
|
|
} while (1);
|
|
|
|
_moving |= MF_LAST_LEG;
|
|
calcMovementFactor(_walkdata.dest);
|
|
}
|
|
|
|
void Actor_v0::walkActor() {
|
|
actorSetWalkTo();
|
|
|
|
_needRedraw = true;
|
|
if (_NewWalkTo != _CurrentWalkTo) {
|
|
_CurrentWalkTo = _NewWalkTo;
|
|
|
|
UpdateActorDirection:;
|
|
_tmp_NewPos = _pos;
|
|
|
|
byte tmp = calcWalkDistances();
|
|
_moving &= 0xF0;
|
|
_moving |= tmp;
|
|
|
|
if (!_walkYCountGreaterThanXCount) {
|
|
if (_walkDirX) {
|
|
_targetFacing = getAngleFromPos(V12_X_MULTIPLIER*1, V12_Y_MULTIPLIER*0);
|
|
} else {
|
|
_targetFacing = getAngleFromPos(V12_X_MULTIPLIER*-1, V12_Y_MULTIPLIER*0);
|
|
}
|
|
} else {
|
|
if (_walkDirY) {
|
|
_targetFacing = getAngleFromPos(V12_X_MULTIPLIER*0, V12_Y_MULTIPLIER*1);
|
|
} else {
|
|
_targetFacing = getAngleFromPos(V12_X_MULTIPLIER*0, V12_Y_MULTIPLIER*-1);
|
|
}
|
|
}
|
|
|
|
directionUpdate();
|
|
|
|
// Need to turn again?
|
|
if (_moving & 0x80)
|
|
return;
|
|
|
|
animateActor(newDirToOldDir(_facing));
|
|
|
|
} else {
|
|
// 2A0A
|
|
if ((_moving & 0x7F) != 1) {
|
|
|
|
if (_NewWalkTo == _pos)
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 2A9A: Nothing to do
|
|
if (_moving == 2)
|
|
return;
|
|
|
|
// Reached Target
|
|
if ((_moving & 0x0F) == 1)
|
|
return stopActorMoving();
|
|
|
|
// 2AAD: Turn actor?
|
|
if (_moving & 0x80) {
|
|
directionUpdate();
|
|
|
|
// Turn again?
|
|
if (_moving & 0x80)
|
|
return;
|
|
|
|
// Start Walk animation
|
|
animateActor(newDirToOldDir(_facing));
|
|
}
|
|
|
|
// Walk X
|
|
if ((_moving & 0x0F) == 3) {
|
|
WalkX:;
|
|
setActorToTempPosition();
|
|
|
|
if (!_walkDirX) {
|
|
_pos.x--;
|
|
} else {
|
|
_pos.x++;
|
|
}
|
|
|
|
// 2C51
|
|
// Does this move us into the walkbox?
|
|
if (updateWalkbox() != kInvalidBox) {
|
|
|
|
// Yes, Lets update our direction
|
|
setActorToOriginalPosition();
|
|
goto UpdateActorDirection;
|
|
}
|
|
|
|
setActorToOriginalPosition();
|
|
|
|
// Have we reached Y Target?
|
|
if (_CurrentWalkTo.y == _tmp_NewPos.y) {
|
|
stopActorMoving();
|
|
return;
|
|
}
|
|
|
|
// Lets check one more pixel up or down
|
|
if (!_walkDirY) {
|
|
_tmp_NewPos.y--;
|
|
} else {
|
|
_tmp_NewPos.y++;
|
|
}
|
|
|
|
setActorToTempPosition();
|
|
|
|
// Are we still inside an invalid walkbox?
|
|
if (updateWalkbox() == kInvalidBox) {
|
|
setActorToOriginalPosition();
|
|
stopActorMoving();
|
|
return;
|
|
}
|
|
|
|
// Found a valid walkbox
|
|
return;
|
|
}
|
|
|
|
// 2ADA: Walk Y
|
|
if ((_moving & 0x0F) == 4) {
|
|
setActorToTempPosition();
|
|
|
|
if (!_walkDirY) {
|
|
_pos.y--;
|
|
} else {
|
|
_pos.y++;
|
|
}
|
|
|
|
// Moved out of walkbox?
|
|
if (updateWalkbox() == kInvalidBox) {
|
|
// 2CC7
|
|
setActorToOriginalPosition();
|
|
|
|
// Reached X?
|
|
if (_CurrentWalkTo.x == _tmp_NewPos.x) {
|
|
stopActorMoving();
|
|
return;
|
|
}
|
|
|
|
// Lets check one more pixel to left or right
|
|
if (!_walkDirX) {
|
|
_tmp_NewPos.x--;
|
|
} else {
|
|
_tmp_NewPos.x++;
|
|
}
|
|
|
|
setActorToTempPosition();
|
|
|
|
// Still in an invalid walkbox?
|
|
if (updateWalkbox() == kInvalidBox) {
|
|
|
|
setActorToOriginalPosition();
|
|
stopActorMoving();
|
|
}
|
|
|
|
return;
|
|
} else {
|
|
|
|
// Lets update our direction
|
|
setActorToOriginalPosition();
|
|
goto UpdateActorDirection;
|
|
}
|
|
}
|
|
|
|
if ((_moving & 0x0F) == 0) {
|
|
// 2AE8
|
|
byte A = actorWalkXCalculate();
|
|
|
|
// Will X movement reach destination
|
|
if (A == 1) {
|
|
|
|
A = actorWalkYCalculate();
|
|
|
|
// Will Y movement also reach destination?
|
|
if (A == 1) {
|
|
_moving &= 0xF0;
|
|
_moving |= A;
|
|
} else {
|
|
if (A == 4)
|
|
stopActorMoving();
|
|
}
|
|
|
|
return;
|
|
|
|
} else {
|
|
// 2B0C: Moving X will put us in an invalid walkbox
|
|
if (A == 3) {
|
|
_moving &= 0xF0;
|
|
_moving |= A;
|
|
|
|
if (_walkDirY) {
|
|
_targetFacing = getAngleFromPos(V12_X_MULTIPLIER*0, V12_Y_MULTIPLIER*1);
|
|
} else {
|
|
_targetFacing = getAngleFromPos(V12_X_MULTIPLIER*0, V12_Y_MULTIPLIER*-1);
|
|
}
|
|
|
|
directionUpdate();
|
|
animateActor(newDirToOldDir(_facing));
|
|
|
|
// FIXME: During the hands-free-demo in the library (room 5), Purple Tentacle gets stuck following Sandy due to the corner of the stairs,
|
|
// This is due to distance, and walkbox gap/layout. This works fine with the original engine, because it 'brute forces'
|
|
// another pixel move in the walk direction before giving up, allowing us to move enough pixels to hit the next walkbox.
|
|
// Why this fails with the return is because script-10 is executing a 'walkActorToActor' every cycle, which restarts the movement process
|
|
// As a work around, we implement the original engine behaviour only for Purple Tentacle in the Demo. Doing this for other actors
|
|
// causes a skipping effect while transitioning walkboxes (the original has another bug in this situation, in which the actor just changes direction for 1 frame during this moment)
|
|
if ((_vm->_game.features & GF_DEMO) && _number == 13)
|
|
goto WalkX;
|
|
|
|
return;
|
|
|
|
} else {
|
|
// 2B39: Moving X was ok, do we also move Y
|
|
A = actorWalkYCalculate();
|
|
|
|
// Are we in a valid walkbox?
|
|
if (A != 4)
|
|
return;
|
|
|
|
// No, we need to change direction
|
|
_moving &= 0xF0;
|
|
_moving |= A;
|
|
|
|
if (_walkDirX) {
|
|
_targetFacing = getAngleFromPos(V12_X_MULTIPLIER*1, V12_Y_MULTIPLIER*0);
|
|
} else {
|
|
_targetFacing = getAngleFromPos(V12_X_MULTIPLIER*-1, V12_Y_MULTIPLIER*0);
|
|
}
|
|
|
|
directionUpdate();
|
|
animateActor(newDirToOldDir(_facing));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor_v2::walkActor() {
|
|
Common::Point foundPath, tmp;
|
|
int new_dir, next_box;
|
|
|
|
if (_moving & MF_TURN) {
|
|
new_dir = updateActorDirection(false);
|
|
if (_facing != new_dir) {
|
|
setDirection(new_dir);
|
|
} else {
|
|
_moving &= ~MF_TURN;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!_moving)
|
|
return;
|
|
|
|
if (_moving & MF_IN_LEG) {
|
|
actorWalkStep();
|
|
} else {
|
|
if (_moving & MF_LAST_LEG) {
|
|
_moving = MF_TURN;
|
|
startAnimActor(_standFrame);
|
|
if (_targetFacing != _walkdata.destdir)
|
|
turnToDirection(_walkdata.destdir);
|
|
} else {
|
|
setBox(_walkdata.curbox);
|
|
if (_walkbox == _walkdata.destbox) {
|
|
foundPath = _walkdata.dest;
|
|
_moving |= MF_LAST_LEG;
|
|
} else {
|
|
next_box = _vm->getNextBox(_walkbox, _walkdata.destbox);
|
|
if (next_box < 0) {
|
|
_moving |= MF_LAST_LEG;
|
|
return;
|
|
}
|
|
|
|
// Can't walk through locked boxes
|
|
int flags = _vm->getBoxFlags(next_box);
|
|
if ((flags & kBoxLocked) && !((flags & kBoxPlayerOnly) && !isPlayer())) {
|
|
_moving |= MF_LAST_LEG;
|
|
//_walkdata.destdir = -1;
|
|
}
|
|
|
|
_walkdata.curbox = next_box;
|
|
|
|
getClosestPtOnBox(_vm->getBoxCoordinates(_walkdata.curbox), _pos.x, _pos.y, tmp.x, tmp.y);
|
|
getClosestPtOnBox(_vm->getBoxCoordinates(_walkbox), tmp.x, tmp.y, foundPath.x, foundPath.y);
|
|
}
|
|
calcMovementFactor(foundPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor_v3::walkActor() {
|
|
Common::Point p2, p3; // Gate locations
|
|
int new_dir, next_box;
|
|
|
|
if (!_moving)
|
|
return;
|
|
|
|
if (!(_moving & MF_NEW_LEG)) {
|
|
if (_moving & MF_IN_LEG && actorWalkStep())
|
|
return;
|
|
|
|
if (_moving & MF_LAST_LEG) {
|
|
_moving = 0;
|
|
startAnimActor(_standFrame);
|
|
if (_targetFacing != _walkdata.destdir)
|
|
turnToDirection(_walkdata.destdir);
|
|
return;
|
|
}
|
|
|
|
if (_moving & MF_TURN) {
|
|
new_dir = updateActorDirection(false);
|
|
if (_facing != new_dir) {
|
|
setDirection(new_dir);
|
|
} else {
|
|
// WORKAROUND for bug #4594 ("SCUMM: Zak McKracken - Zak keeps walk animation without moving")
|
|
// This bug also happens with the original SCUMM3 (ZAK FM-TOWNS) interpreter (unlike SCUMM1/2
|
|
// where the actors are apparently supposed to continue walking after being turned). We have
|
|
// to stop the walking animation here...
|
|
// This also fixes bug #4601 ("SCUMM: Zak McKracken (FM-Towns) - shopkeeper keeps walking"),
|
|
// although that one does not happen with the original interpreter.
|
|
if (_vm->_game.id == GID_ZAK && _moving == MF_TURN)
|
|
startAnimActor(_standFrame);
|
|
_moving = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (_walkdata.point3.x != 32000) {
|
|
if (calcMovementFactor(_walkdata.point3)) {
|
|
_walkdata.point3.x = 32000;
|
|
return;
|
|
}
|
|
_walkdata.point3.x = 32000;
|
|
}
|
|
|
|
setBox(_walkdata.curbox);
|
|
_moving &= MF_IN_LEG;
|
|
}
|
|
|
|
_moving &= ~MF_NEW_LEG;
|
|
do {
|
|
if (_walkbox == kInvalidBox) {
|
|
setBox(_walkdata.destbox);
|
|
_walkdata.curbox = _walkdata.destbox;
|
|
break;
|
|
}
|
|
|
|
if (_walkbox == _walkdata.destbox)
|
|
break;
|
|
|
|
next_box = _vm->getNextBox(_walkbox, _walkdata.destbox);
|
|
if (next_box < 0) {
|
|
_moving |= MF_LAST_LEG;
|
|
return;
|
|
}
|
|
|
|
// This is version specific for ZAK FM-TOWNS. The flags check that is present in later SCUMM versions does not exist
|
|
// in SCUMM3. I have looked at disams of ZAK FM-TOWNS, LOOM FM-TOWNS, LOOM DOS EGA, INDY3 FM-TOWNS, INDY3 DOS VGA.
|
|
if (_vm->_game.id == GID_ZAK) {
|
|
// Check for equals, not for a bit mask (otherwise: bug no. 13399)
|
|
if (_vm->getBoxFlags(next_box) == kBoxLocked) {
|
|
_moving |= MF_LAST_LEG;
|
|
return;
|
|
}
|
|
}
|
|
|
|
_walkdata.curbox = next_box;
|
|
|
|
findPathTowardsOld(_walkbox, next_box, _walkdata.destbox, p2, p3);
|
|
if (p2.x == 32000 && p3.x == 32000) {
|
|
break;
|
|
}
|
|
|
|
if (p2.x != 32000) {
|
|
if (calcMovementFactor(p2)) {
|
|
_walkdata.point3 = p3;
|
|
return;
|
|
}
|
|
}
|
|
if (calcMovementFactor(p3))
|
|
return;
|
|
|
|
setBox(_walkdata.curbox);
|
|
} while (1);
|
|
|
|
_moving |= MF_LAST_LEG;
|
|
calcMovementFactor(_walkdata.dest);
|
|
}
|
|
|
|
void Actor_v7::walkActor() {
|
|
if (!(_moving & MF_FROZEN))
|
|
Actor::walkActor();
|
|
|
|
if (_moving & MF_TURN) {
|
|
int newDir = updateActorDirection();
|
|
if (_facing != newDir)
|
|
setDirection(newDir);
|
|
else
|
|
_moving &= ~MF_TURN;
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Actor direction ---
|
|
#pragma mark -
|
|
|
|
|
|
int Actor::remapDirection(int dir, bool is_walking) {
|
|
int specdir;
|
|
byte flags;
|
|
byte mask;
|
|
bool flipX;
|
|
bool flipY;
|
|
|
|
if ((_vm->_game.version < 5 || !_ignoreBoxes) && (_vm->_game.version < 7 || isInCurrentRoom())) {
|
|
if (_walkbox != kOldInvalidBox) {
|
|
assert(_walkbox < ARRAYSIZE(_vm->_extraBoxFlags));
|
|
specdir = _vm->_extraBoxFlags[_walkbox];
|
|
if (specdir) {
|
|
if (specdir & 0x8000) {
|
|
dir = specdir & 0x3FFF;
|
|
} else {
|
|
specdir = specdir & 0x3FFF;
|
|
if (specdir - 90 < dir && dir < specdir + 90)
|
|
dir = specdir;
|
|
else
|
|
dir = specdir + 180;
|
|
}
|
|
}
|
|
}
|
|
|
|
flags = _vm->getBoxFlags(_walkbox);
|
|
|
|
flipX = (_walkdata.deltaXFactor > 0);
|
|
flipY = (_walkdata.deltaYFactor > 0);
|
|
|
|
// Check for X-Flip
|
|
if ((flags & kBoxXFlip) || isInClass(kObjectClassXFlip)) {
|
|
dir = 360 - dir;
|
|
flipX = !flipX;
|
|
}
|
|
// Check for Y-Flip
|
|
if ((flags & kBoxYFlip) || isInClass(kObjectClassYFlip)) {
|
|
dir = 180 - dir;
|
|
flipY = !flipY;
|
|
}
|
|
|
|
switch (flags & 7) {
|
|
case 1:
|
|
if (_vm->_game.version >= 7) {
|
|
if (dir < 180)
|
|
return 90;
|
|
else
|
|
return 270;
|
|
} else {
|
|
if (is_walking) // Actor is walking
|
|
return flipX ? 90 : 270;
|
|
else // Actor is standing/turning
|
|
return (dir == 90) ? 90 : 270;
|
|
}
|
|
case 2:
|
|
if (_vm->_game.version >= 7) {
|
|
if (dir > 90 && dir < 270)
|
|
return 180;
|
|
else
|
|
return 0;
|
|
} else {
|
|
if (is_walking) // Actor is walking
|
|
return flipY ? 180 : 0;
|
|
else // Actor is standing/turning
|
|
return (dir == 0) ? 0 : 180;
|
|
}
|
|
case 3:
|
|
return 270;
|
|
case 4:
|
|
return 90;
|
|
case 5:
|
|
return 0;
|
|
case 6:
|
|
return 180;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// MM v0 stores flags as a part of the mask
|
|
if (_vm->_game.version == 0) {
|
|
mask = _vm->getMaskFromBox(_walkbox);
|
|
// face the wall if climbing/descending a ladder
|
|
if ((mask & 0x8C) == 0x84)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
dir = (dir + 360) % 360;
|
|
|
|
// OR 0x400 to signal direction interpolation should be done
|
|
if (_vm->_game.version < 7)
|
|
dir |= 0x400;
|
|
|
|
return dir;
|
|
}
|
|
|
|
int Actor::updateActorDirection(bool is_walking) {
|
|
static const uint8 actorTurnInterpolateTable[] = { 0, 2, 2, 3, 2, 1, 2, 3, 0, 1, 2, 1, 0, 1, 0, 3 };
|
|
|
|
if ((_vm->_game.version == 6) && _ignoreTurns)
|
|
return _facing;
|
|
|
|
int dir = remapDirection(_targetFacing, is_walking);
|
|
if (dir & 0x400)
|
|
dir = oldDirToNewDir(actorTurnInterpolateTable[newDirToOldDir(dir & 0x3ff) | (newDirToOldDir(_facing) << 2)]);
|
|
|
|
return dir;
|
|
}
|
|
|
|
int Actor_v7::updateActorDirection() {
|
|
int dirType = _vm->_costumeLoader->hasManyDirections(_costume);
|
|
int from = toSimpleDir(dirType, _facing);
|
|
int to = toSimpleDir(dirType, _targetFacing);
|
|
int num = dirType ? 8 : 4;
|
|
|
|
// Turn left or right, depending on which is shorter.
|
|
int diff = to - from;
|
|
if (ABS(diff) > (num >> 1))
|
|
diff = -diff;
|
|
|
|
if (diff > 0) {
|
|
to = from + 1;
|
|
} else if (diff < 0) {
|
|
to = from - 1;
|
|
}
|
|
|
|
return fromSimpleDir(dirType, (to + num) % num);
|
|
}
|
|
|
|
void Actor::setDirection(int direction) {
|
|
uint aMask;
|
|
int i;
|
|
uint16 vald;
|
|
|
|
direction = (direction + 360) % 360;
|
|
|
|
// Do nothing if actor is already facing in the given direction
|
|
if (_facing == direction)
|
|
return;
|
|
|
|
_facing = direction;
|
|
|
|
// If there is no costume set for this actor, we are finished
|
|
if (_costume == 0)
|
|
return;
|
|
|
|
// Verified for v3-v6 and HE
|
|
if (!isInCurrentRoom() && _vm->_game.version >= 3 && _vm->_game.version <= 6)
|
|
return;
|
|
|
|
// Update the costume for the new direction (and mark the actor for redraw)
|
|
aMask = 0x8000;
|
|
for (i = 0; i < 16; i++, aMask >>= 1) {
|
|
vald = _cost.frame[i];
|
|
if (vald == 0xFFFF)
|
|
continue;
|
|
if (!(_vm->_game.features & GF_NEW_COSTUMES)) {
|
|
// Fix bug mentioned here: https://github.com/scummvm/scummvm/pull/3795/
|
|
// For versions 1 to 6 we need to store the direction info in the frame array (like
|
|
// the original interpreters do). I haven't found any signs that v7/8 require it, though.
|
|
// I haven't checked HE, but since it uses the same AKOS costumes as v7/8 I leave that
|
|
// as it is...
|
|
if ((vald & 3) == newDirToOldDir(_facing)) {
|
|
// v1/2 skip the frame only if everything is equal...
|
|
if (_vm->_game.version > 2 || (vald >> 2) == _frame)
|
|
continue;
|
|
}
|
|
vald >>= 2;
|
|
if (_vm->_game.version < 3)
|
|
_frame = vald;
|
|
}
|
|
_vm->_costumeLoader->costumeDecodeData(this, vald, (_vm->_game.version <= 2) ? 0xFFFF : aMask);
|
|
}
|
|
|
|
_needRedraw = true;
|
|
}
|
|
|
|
void Actor_v0::setDirection(int direction) {
|
|
int dir = newDirToOldDir(direction);
|
|
int res = 0;
|
|
|
|
switch (dir) {
|
|
case 0:
|
|
res = 4; // Left
|
|
break;
|
|
|
|
case 1:
|
|
res = 5; // Right
|
|
break;
|
|
|
|
case 2:
|
|
res = 6; // Face Camera
|
|
break;
|
|
|
|
default:
|
|
res = 7; // Face Away
|
|
break;
|
|
}
|
|
|
|
_animFrameRepeat = -1;
|
|
animateActor(res);
|
|
}
|
|
|
|
void Actor::faceToObject(int obj) {
|
|
int x2, y2, dir, width;
|
|
|
|
if (!isInCurrentRoom())
|
|
return;
|
|
|
|
if (_vm->getObjectOrActorXY(obj, x2, y2) == -1)
|
|
return;
|
|
|
|
if (_vm->_game.version > 4) {
|
|
dir = (x2 > _pos.x) ? 90 : 270;
|
|
} else {
|
|
_vm->getObjectOrActorWidth(obj, width);
|
|
dir = (_pos.x < x2) ? 1 : 0;
|
|
if (abs(_pos.x - x2) < width / 2)
|
|
dir = (_pos.y > y2) ? 3 : 2;
|
|
|
|
dir = oldDirToNewDir(dir);
|
|
}
|
|
|
|
turnToDirection(dir);
|
|
}
|
|
|
|
void Actor::turnToDirection(int newdir) {
|
|
if (newdir == -1 || _ignoreTurns)
|
|
return;
|
|
|
|
_targetFacing = newdir;
|
|
|
|
if (_vm->_game.version == 0)
|
|
setDirection(newdir);
|
|
else if (_vm->_game.version <= 2)
|
|
_moving |= MF_TURN;
|
|
else
|
|
_moving = MF_TURN;
|
|
}
|
|
|
|
void Actor_v7::turnToDirection(int newdir) {
|
|
if (newdir == -1 || _ignoreTurns)
|
|
return;
|
|
|
|
newdir = remapDirection((newdir + 360) % 360, false);
|
|
_moving &= ~MF_TURN;
|
|
|
|
if (isInCurrentRoom() && !_ignoreBoxes) {
|
|
byte flags = _vm->getBoxFlags(_walkbox);
|
|
if ((flags & kBoxXFlip) || isInClass(kObjectClassXFlip))
|
|
newdir = 360 - newdir;
|
|
if ((flags & kBoxYFlip) || isInClass(kObjectClassYFlip))
|
|
newdir = 180 - newdir;
|
|
}
|
|
|
|
if (newdir != _facing) {
|
|
_moving |= MF_TURN;
|
|
_targetFacing = newdir;
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Actor position ---
|
|
#pragma mark -
|
|
|
|
|
|
void ScummEngine::putActors() {
|
|
Actor *a;
|
|
int i;
|
|
|
|
for (i = 1; i < _numActors; i++) {
|
|
a = _actors[i];
|
|
if (a && a->isInCurrentRoom())
|
|
a->putActor();
|
|
}
|
|
}
|
|
|
|
void Actor::putActor(int dstX, int dstY, int newRoom) {
|
|
if (_visible && _vm->_currentRoom != newRoom && _vm->getTalkingActor() == _number) {
|
|
_vm->stopTalk();
|
|
}
|
|
|
|
// WORKAROUND: The green transparency of the tank in the Hall of Oddities
|
|
// is positioned one pixel too far to the left. This appears to be a bug
|
|
// in the original game as well.
|
|
if (_vm->_game.id == GID_SAMNMAX && newRoom == 16 && _number == 5 && dstX == 235 && dstY == 236 && _vm->enhancementEnabled(kEnhMinorBugFixes))
|
|
dstX++;
|
|
|
|
_pos.x = dstX;
|
|
_pos.y = dstY;
|
|
_room = newRoom;
|
|
_needRedraw = true;
|
|
|
|
if (_vm->VAR(_vm->VAR_EGO) == _number) {
|
|
_vm->_egoPositioned = true;
|
|
}
|
|
|
|
if (_visible) {
|
|
if (isInCurrentRoom()) {
|
|
if (_moving) {
|
|
stopActorMoving();
|
|
startAnimActor(_standFrame);
|
|
}
|
|
adjustActorPos();
|
|
} else {
|
|
#ifdef ENABLE_HE
|
|
if (_vm->_game.heversion >= 71)
|
|
((ScummEngine_v71he *)_vm)->heQueueEraseAuxActor((ActorHE *)this);
|
|
#endif
|
|
hideActor();
|
|
}
|
|
} else {
|
|
if (isInCurrentRoom())
|
|
showActor();
|
|
}
|
|
|
|
if (_vm->_game.version == 0) {
|
|
|
|
((Actor_v0 *)this)->_newWalkBoxEntered = false;
|
|
((Actor_v0 *)this)->_CurrentWalkTo = _pos;
|
|
((Actor_v0 *)this)->_NewWalkTo = _pos;
|
|
}
|
|
|
|
// V0-V1 Maniac always sets the actor to face the camera upon entering a room
|
|
if (_vm->_game.id == GID_MANIAC && _vm->_game.version <= 1 && _vm->_game.platform != Common::kPlatformNES)
|
|
setDirection(oldDirToNewDir(2));
|
|
}
|
|
|
|
static bool inBoxQuickReject(const BoxCoords &box, int x, int y, int threshold) {
|
|
int t;
|
|
|
|
t = x - threshold;
|
|
if (t > box.ul.x && t > box.ur.x && t > box.lr.x && t > box.ll.x)
|
|
return true;
|
|
|
|
t = x + threshold;
|
|
if (t < box.ul.x && t < box.ur.x && t < box.lr.x && t < box.ll.x)
|
|
return true;
|
|
|
|
t = y - threshold;
|
|
if (t > box.ul.y && t > box.ur.y && t > box.lr.y && t > box.ll.y)
|
|
return true;
|
|
|
|
t = y + threshold;
|
|
if (t < box.ul.y && t < box.ur.y && t < box.lr.y && t < box.ll.y)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int checkXYInBoxBounds(int boxnum, int x, int y, int &destX, int &destY) {
|
|
BoxCoords box = g_scumm->getBoxCoordinates(boxnum);
|
|
int xmin, xmax;
|
|
|
|
// We are supposed to determine the point (destX,destY) contained in
|
|
// the given box which is closest to the point (x,y), and then return
|
|
// some kind of "distance" between the two points.
|
|
|
|
// First, we determine destY and a range (xmin to xmax) in which destX
|
|
// is contained.
|
|
if (y < box.ul.y) {
|
|
// Point is above the box
|
|
destY = box.ul.y;
|
|
xmin = box.ul.x;
|
|
xmax = box.ur.x;
|
|
} else if (y >= box.ll.y) {
|
|
// Point is below the box
|
|
destY = box.ll.y;
|
|
xmin = box.ll.x;
|
|
xmax = box.lr.x;
|
|
} else if ((x >= box.ul.x) && (x >= box.ll.x) && (x < box.ur.x) && (x < box.lr.x)) {
|
|
// Point is strictly inside the box
|
|
destX = x;
|
|
destY = y;
|
|
xmin = xmax = x;
|
|
} else {
|
|
// Point is to the left or right of the box,
|
|
// so the y coordinate remains unchanged
|
|
destY = y;
|
|
int ul = box.ul.x;
|
|
int ll = box.ll.x;
|
|
int ur = box.ur.x;
|
|
int lr = box.lr.x;
|
|
int top = box.ul.y;
|
|
int bottom = box.ll.y;
|
|
int cury;
|
|
|
|
// Perform a binary search to determine the x coordinate.
|
|
// Note: It would be possible to compute this value in a
|
|
// single step simply by calculating the slope of the left
|
|
// resp. right side and using that to find the correct
|
|
// result. However, the original engine did use the search
|
|
// approach, so we do that, too.
|
|
do {
|
|
xmin = (ul + ll) / 2;
|
|
xmax = (ur + lr) / 2;
|
|
cury = (top + bottom) / 2;
|
|
|
|
if (cury < y) {
|
|
top = cury;
|
|
ul = xmin;
|
|
ur = xmax;
|
|
} else if (cury > y) {
|
|
bottom = cury;
|
|
ll = xmin;
|
|
lr = xmax;
|
|
}
|
|
} while (cury != y);
|
|
}
|
|
|
|
// Now that we have limited the value of destX to a fixed
|
|
// interval, it's a trivial matter to finally determine it.
|
|
if (x < xmin) {
|
|
destX = xmin;
|
|
} else if (x > xmax) {
|
|
destX = xmax;
|
|
} else {
|
|
destX = x;
|
|
}
|
|
|
|
// Compute the distance of the points. We measure the
|
|
// distance with a granularity of 8x8 blocks only (hence
|
|
// yDist must be divided by 4, as we are using 8x2 pixels
|
|
// blocks for actor coordinates).
|
|
int xDist = ABS(x - destX);
|
|
int yDist = ABS(y - destY) / 4;
|
|
int dist;
|
|
|
|
if (g_scumm->_game.version == 0)
|
|
xDist *= 2;
|
|
|
|
if (xDist < yDist)
|
|
dist = (xDist >> 1) + yDist;
|
|
else
|
|
dist = (yDist >> 1) + xDist;
|
|
|
|
return dist;
|
|
}
|
|
|
|
AdjustBoxResult Actor_v0::adjustPosInBorderWalkbox(AdjustBoxResult box) {
|
|
AdjustBoxResult Result = box;
|
|
BoxCoords BoxCoord = _vm->getBoxCoordinates(box.box);
|
|
|
|
byte boxMask = _vm->getMaskFromBox(box.box);
|
|
if (!(boxMask & 0x80))
|
|
return Result;
|
|
|
|
int16 A;
|
|
boxMask &= 0x7C;
|
|
if (boxMask == 0x0C)
|
|
A = 2;
|
|
else {
|
|
if (boxMask != 0x08)
|
|
return Result;
|
|
|
|
A = 1;
|
|
}
|
|
|
|
// 1BC6
|
|
byte Modifier = box.y - BoxCoord.ul.y;
|
|
assert(Modifier < 0x16);
|
|
|
|
if (A == 1) {
|
|
// 1BCF
|
|
A = BoxCoord.ur.x - v0WalkboxSlantedModifier[ Modifier ];
|
|
if (A < box.x)
|
|
return box;
|
|
|
|
if (A <= 0xA0)
|
|
A = 0;
|
|
|
|
Result.x = A;
|
|
} else {
|
|
// 1BED
|
|
A = BoxCoord.ul.x + v0WalkboxSlantedModifier[ Modifier ];
|
|
|
|
if (A < box.x || A == box.x)
|
|
Result.x = A;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
AdjustBoxResult Actor_v0::adjustXYToBeInBox(int dstX, int dstY) {
|
|
AdjustBoxResult Result = Actor_v2::adjustXYToBeInBox(dstX, dstY);
|
|
|
|
if (Result.box == kInvalidBox)
|
|
return Result;
|
|
|
|
return adjustPosInBorderWalkbox(Result);
|
|
}
|
|
|
|
AdjustBoxResult Actor_v2::adjustXYToBeInBox(const int dstX, const int dstY) {
|
|
AdjustBoxResult abr;
|
|
|
|
abr.x = dstX;
|
|
abr.y = dstY;
|
|
abr.box = kInvalidBox;
|
|
|
|
int numBoxes = _vm->getNumBoxes() - 1;
|
|
int bestDist = 0xFF;
|
|
for (int i = 0; i <= numBoxes; i++) {
|
|
// MM v0 prioritizes lower boxes, other engines higher boxes
|
|
int box = (_vm->_game.version == 0 ? i : numBoxes - i);
|
|
int foundX, foundY;
|
|
int flags = _vm->getBoxFlags(box);
|
|
if ((flags & kBoxInvisible) && !((flags & kBoxPlayerOnly) && !isPlayer()))
|
|
continue;
|
|
int dist = checkXYInBoxBounds(box, dstX, dstY, foundX, foundY); // also merged with getClosestPtOnBox
|
|
if (dist == 0) {
|
|
abr.x = foundX;
|
|
abr.y = foundY;
|
|
abr.box = box;
|
|
|
|
break;
|
|
}
|
|
if (dist < bestDist) {
|
|
bestDist = dist;
|
|
abr.x = foundX;
|
|
abr.y = foundY;
|
|
abr.box = box;
|
|
}
|
|
}
|
|
|
|
return abr;
|
|
}
|
|
|
|
AdjustBoxResult Actor::adjustXYToBeInBox(int dstX, int dstY) {
|
|
const uint thresholdTable[] = { 30, 80, 0 };
|
|
AdjustBoxResult abr;
|
|
int16 tmpX, tmpY;
|
|
int tmpDist, bestDist, threshold, numBoxes;
|
|
byte flags, bestBox;
|
|
int box;
|
|
const int firstValidBox = (_vm->_game.features & GF_SMALL_HEADER) ? 0 : 1;
|
|
|
|
// The original v3-4 interpreters register and use the last valid (X,Y) values
|
|
// for the current actor. During the execution of the current function, if
|
|
// the routine can't find a bestDist which is smaller than the init value
|
|
// (0xFFFF), the rest of the code flow just happens to reuse the last valid
|
|
// coordinates obtained from a previous call of the function.
|
|
// This sounds like pseudo-undefined behavior caused by an oversight, but we
|
|
// can get along with it as it appears we actually need this to happen...
|
|
//
|
|
// By simulating what v3 and v4 disasms did, we correctly fix bug #2377.
|
|
// The originals relied on global variables containing the latest valid
|
|
// coordinates modified in here and in startWalkActor, but registering
|
|
// the modifications only in here seems to be enough to cover this edge case.
|
|
//
|
|
// HE and v5-6 games appear to actually have undefined behavior, since they
|
|
// create something akin to our AdjustBoxResult structure right inside the
|
|
// current function, so this means that the coordinates inside it potentially
|
|
// might never be initialized if bestDist is never modified.
|
|
// The same thing applies to v7-8, but it's really unlikely that any distance
|
|
// would end up being higher than 0x7FFFFFFF...
|
|
|
|
bool isOldSystem = _vm->_game.version <= 4;
|
|
|
|
abr.x = isOldSystem ? _lastValidX : dstX;
|
|
abr.y = isOldSystem ? _lastValidY : dstY;
|
|
abr.box = kInvalidBox;
|
|
|
|
if (_ignoreBoxes) {
|
|
abr.x = dstX;
|
|
abr.y = dstY;
|
|
return abr;
|
|
}
|
|
|
|
for (int tIdx = 0; tIdx < ARRAYSIZE(thresholdTable); tIdx++) {
|
|
threshold = thresholdTable[tIdx];
|
|
|
|
numBoxes = _vm->getNumBoxes() - 1;
|
|
if (numBoxes < firstValidBox)
|
|
return abr;
|
|
|
|
bestDist = (_vm->_game.version >= 7) ? 0x7FFFFFFF : 0xFFFF;
|
|
bestBox = kInvalidBox;
|
|
|
|
// We iterate (backwards) over all boxes, searching the one closest
|
|
// to the desired coordinates.
|
|
for (box = numBoxes; box >= firstValidBox; box--) {
|
|
flags = _vm->getBoxFlags(box);
|
|
|
|
// Skip over invisible boxes
|
|
if ((flags & kBoxInvisible) && !((flags & kBoxPlayerOnly) && !isPlayer()))
|
|
continue;
|
|
|
|
// For increased performance, we perform a quick test if
|
|
// the coordinates can even be within a distance of 'threshold'
|
|
// pixels of the box.
|
|
if (threshold > 0 && inBoxQuickReject(_vm->getBoxCoordinates(box), dstX, dstY, threshold))
|
|
continue;
|
|
|
|
// Check if the point is contained in the box. If it is,
|
|
// we don't have to search anymore.
|
|
if (_vm->checkXYInBoxBounds(box, dstX, dstY)) {
|
|
_lastValidX = dstX;
|
|
_lastValidY = dstY;
|
|
abr.x = dstX;
|
|
abr.y = dstY;
|
|
abr.box = box;
|
|
return abr;
|
|
}
|
|
|
|
// Find the point in the box which is closest to our point.
|
|
tmpDist = getClosestPtOnBox(_vm->getBoxCoordinates(box), dstX, dstY, tmpX, tmpY);
|
|
|
|
// Check if the box is closer than the previous boxes.
|
|
if (tmpDist < bestDist) {
|
|
_lastValidX = tmpX;
|
|
_lastValidY = tmpY;
|
|
abr.x = tmpX;
|
|
abr.y = tmpY;
|
|
|
|
if (tmpDist == 0) {
|
|
abr.box = box;
|
|
return abr;
|
|
}
|
|
bestDist = tmpDist;
|
|
bestBox = box;
|
|
}
|
|
}
|
|
|
|
// If the closest ('best') box we found is within the threshold, or if
|
|
// we are on the last run (i.e. threshold == 0), return that box.
|
|
if (threshold == 0 || threshold * threshold >= bestDist) {
|
|
abr.box = bestBox;
|
|
return abr;
|
|
}
|
|
}
|
|
|
|
return abr;
|
|
}
|
|
|
|
void Actor::adjustActorPos() {
|
|
AdjustBoxResult abr;
|
|
|
|
abr = adjustXYToBeInBox(_pos.x, _pos.y);
|
|
|
|
_pos.x = abr.x;
|
|
_pos.y = abr.y;
|
|
_walkdata.destbox = abr.box;
|
|
|
|
setBox(abr.box);
|
|
|
|
_walkdata.dest.x = -1;
|
|
|
|
stopActorMoving();
|
|
_cost.soundCounter = 0;
|
|
_cost.soundPos = 0;
|
|
|
|
if (_walkbox != kInvalidBox) {
|
|
byte flags = _vm->getBoxFlags(_walkbox);
|
|
if (flags & 7) {
|
|
turnToDirection(_facing);
|
|
}
|
|
}
|
|
}
|
|
|
|
int ScummEngine::getActorFromPos(int x, int y) {
|
|
int i;
|
|
|
|
if (!testGfxAnyUsageBits(x / 8))
|
|
return 0;
|
|
|
|
for (i = 1; i < _numActors; i++) {
|
|
if (testGfxUsageBit(x / 8, i) && !getClass(i, kObjectClassUntouchable)
|
|
&& y >= _actors[i]->_top && y <= _actors[i]->_bottom) {
|
|
if (_game.version > 2 || i != VAR(VAR_EGO))
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ScummEngine_v70he::getActorFromPos(int x, int y) {
|
|
int curActor, i;
|
|
|
|
if (!testGfxAnyUsageBits(x / 8))
|
|
return 0;
|
|
|
|
curActor = 0;
|
|
for (i = 1; i < _numActors; i++) {
|
|
if (testGfxUsageBit(x / 8, i) && !getClass(i, kObjectClassUntouchable)
|
|
&& y >= _actors[i]->_top && y <= _actors[i]->_bottom
|
|
&& (_actors[i]->getPos().y > _actors[curActor]->getPos().y || curActor == 0))
|
|
curActor = i;
|
|
}
|
|
|
|
return curActor;
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- TODO ---
|
|
#pragma mark -
|
|
|
|
|
|
void Actor::hideActor() {
|
|
if (!_visible)
|
|
return;
|
|
|
|
if (_moving) {
|
|
stopActorMoving();
|
|
startAnimActor(_standFrame);
|
|
}
|
|
_visible = false;
|
|
_cost.soundCounter = 0;
|
|
_cost.soundPos = 0;
|
|
_needRedraw = false;
|
|
_needBgReset = true;
|
|
}
|
|
|
|
void ActorHE::hideActor() {
|
|
Actor::hideActor();
|
|
_auxActor = 0;
|
|
_auxEraseX1 = 0;
|
|
_auxEraseY1 = 0;
|
|
_auxEraseX2 = -1;
|
|
_auxEraseY2 = -1;
|
|
}
|
|
|
|
void Actor::showActor() {
|
|
if (_vm->_currentRoom == 0 || _visible)
|
|
return;
|
|
|
|
adjustActorPos();
|
|
|
|
_vm->ensureResourceLoaded(rtCostume, _costume);
|
|
|
|
if (_vm->_game.version == 0) {
|
|
Actor_v0 *a = ((Actor_v0 *)this);
|
|
|
|
a->_costCommand = a->_costCommandNew = 0xFF;
|
|
_walkdata.dest = a->_CurrentWalkTo;
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
a->_limbFrameRepeat[i] = 0;
|
|
a->_limbFrameRepeatNew[i] = 0;
|
|
}
|
|
|
|
_cost.reset();
|
|
|
|
a->_animFrameRepeat = 1;
|
|
a->_speaking = 0;
|
|
|
|
startAnimActor(_standFrame);
|
|
_visible = true;
|
|
return;
|
|
|
|
} else if (_vm->_game.version <= 2) {
|
|
_cost.reset();
|
|
startAnimActor(_standFrame);
|
|
startAnimActor(_initFrame);
|
|
startAnimActor(_talkStopFrame);
|
|
} else {
|
|
if (_costumeNeedsInit) {
|
|
startAnimActor(_initFrame);
|
|
_costumeNeedsInit = false;
|
|
}
|
|
}
|
|
|
|
stopActorMoving();
|
|
_visible = true;
|
|
_needRedraw = true;
|
|
}
|
|
|
|
void ScummEngine::showActors() {
|
|
int i;
|
|
|
|
for (i = 1; i < _numActors; i++) {
|
|
if (_actors[i]->isInCurrentRoom())
|
|
_actors[i]->showActor();
|
|
}
|
|
}
|
|
|
|
/* Used in Scumm v5 only. Play sounds associated with actors */
|
|
void ScummEngine::playActorSounds() {
|
|
int i, j;
|
|
int sound;
|
|
|
|
for (i = 1; i < _numActors; i++) {
|
|
if (_actors[i]->_cost.soundCounter && _actors[i]->isInCurrentRoom()) {
|
|
_currentScript = 0xFF;
|
|
if (_game.version == 0) {
|
|
sound = _actors[i]->_sound[0] & 0x3F;
|
|
} else {
|
|
sound = _actors[i]->_sound[0];
|
|
}
|
|
// fast mode will flood the queue with walk sounds
|
|
if (!_fastMode) {
|
|
_sound->startSound(sound);
|
|
}
|
|
for (j = 1; j < _numActors; j++) {
|
|
_actors[j]->_cost.soundCounter = 0;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ScummEngine::isValidActor(int id) const {
|
|
return id >= 0 && id < _numActors && _actors[id]->_number == id;
|
|
}
|
|
|
|
Actor *ScummEngine::derefActor(int id, const char *errmsg) const {
|
|
if (id == 0)
|
|
debugC(DEBUG_ACTORS, "derefActor(0, \"%s\") in script %d, opcode 0x%x",
|
|
errmsg, _currentScript != 0xFF ? vm.slot[_currentScript].number : -1, _opcode);
|
|
|
|
if (!isValidActor(id)) {
|
|
if (errmsg)
|
|
error("Invalid actor %d in %s", id, errmsg);
|
|
else
|
|
error("Invalid actor %d", id);
|
|
}
|
|
return _actors[id];
|
|
}
|
|
|
|
Actor *ScummEngine::derefActorSafe(int id, const char *errmsg) const {
|
|
if (id == 0)
|
|
debugC(DEBUG_ACTORS, "derefActorSafe(0, \"%s\") in script %d, opcode 0x%x",
|
|
errmsg, _currentScript != 0xFF ? vm.slot[_currentScript].number : -1, _opcode);
|
|
|
|
if (!isValidActor(id)) {
|
|
debugC(DEBUG_ACTORS, "Invalid actor %d in %s (script %d, opcode 0x%x)",
|
|
id, errmsg, _currentScript != 0xFF ? vm.slot[_currentScript].number : -1, _opcode);
|
|
return nullptr;
|
|
}
|
|
return _actors[id];
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Actor drawing ---
|
|
#pragma mark -
|
|
|
|
|
|
void ScummEngine::processActors() {
|
|
int numactors = 0;
|
|
|
|
// Make a list of all actors in this room
|
|
for (int i = 1; i < _numActors; i++) {
|
|
if (_game.version == 8 && _actors[i]->_layer < 0)
|
|
continue;
|
|
if (_actors[i]->isInCurrentRoom()) {
|
|
_sortedActors[numactors++] = _actors[i];
|
|
}
|
|
}
|
|
if (!numactors) {
|
|
return;
|
|
}
|
|
|
|
// Sort actors by position before drawing them (to ensure that actors
|
|
// in front are drawn after those "behind" them).
|
|
//
|
|
// Note: This algorithm works exactly the way the original engine did.
|
|
// Please resist any urge to 'optimize' this. Many of the games rely on
|
|
// the quirks of this particular sorting algorithm, and since we are
|
|
// dealing with far less than 100 objects being sorted here, any
|
|
// 'optimization' wouldn't yield a useful gain anyway.
|
|
//
|
|
// In particular, changing this loop caused a number of bugs in the
|
|
// past, including bugs #912, #1055, and #1864.
|
|
//
|
|
// Note that Sam & Max uses a stable sorting method. Older games don't
|
|
// and, according to cyx, neither do newer ones. At least not FT and
|
|
// COMI. See bug #2064 for more details.
|
|
|
|
if (_game.id == GID_SAMNMAX) {
|
|
for (int j = 0; j < numactors; ++j) {
|
|
for (int i = 0; i < numactors; ++i) {
|
|
int sc_actor1 = _sortedActors[j]->getPos().y;
|
|
int sc_actor2 = _sortedActors[i]->getPos().y;
|
|
if (sc_actor1 == sc_actor2) {
|
|
sc_actor1 += _sortedActors[j]->_number;
|
|
sc_actor2 += _sortedActors[i]->_number;
|
|
}
|
|
if (sc_actor1 < sc_actor2) {
|
|
SWAP(_sortedActors[i], _sortedActors[j]);
|
|
}
|
|
}
|
|
}
|
|
} else if (_game.heversion >= 90) {
|
|
for (int j = 0; j < numactors; ++j) {
|
|
for (int i = 0; i < numactors; ++i) {
|
|
int sc_actor1 = _sortedActors[j]->_layer;
|
|
int sc_actor2 = _sortedActors[i]->_layer;
|
|
if (sc_actor1 < sc_actor2) {
|
|
SWAP(_sortedActors[i], _sortedActors[j]);
|
|
} else if (sc_actor1 == sc_actor2) {
|
|
sc_actor1 = _sortedActors[j]->getPos().y;
|
|
sc_actor2 = _sortedActors[i]->getPos().y;
|
|
if (sc_actor1 < sc_actor2) {
|
|
SWAP(_sortedActors[i], _sortedActors[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (_game.version == 0) {
|
|
for (int j = 0; j < numactors; ++j) {
|
|
for (int i = 0; i < numactors; ++i) {
|
|
// Note: the plant is handled different in v0, the y value is not used.
|
|
// In v1/2 this is done by the actor's elevation instead.
|
|
int sc_actor1 = (_sortedActors[j]->_number == 19 ? 0 : _sortedActors[j]->getPos().y);
|
|
int sc_actor2 = (_sortedActors[i]->_number == 19 ? 0 : _sortedActors[i]->getPos().y);
|
|
if (sc_actor1 < sc_actor2) {
|
|
SWAP(_sortedActors[i], _sortedActors[j]);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for (int j = 0; j < numactors; ++j) {
|
|
for (int i = 0; i < numactors; ++i) {
|
|
int sc_actor1 = _sortedActors[j]->getPos().y - _sortedActors[j]->_layer * 2000;
|
|
int sc_actor2 = _sortedActors[i]->getPos().y - _sortedActors[i]->_layer * 2000;
|
|
if (sc_actor1 < sc_actor2) {
|
|
SWAP(_sortedActors[i], _sortedActors[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally draw the now sorted actors
|
|
Actor** end = _sortedActors + numactors;
|
|
for (Actor** ac = _sortedActors; ac != end; ++ac) {
|
|
Actor* a = *ac;
|
|
|
|
if (_game.version == 0) {
|
|
// 0x057B
|
|
Actor_v0 *a0 = (Actor_v0*) a;
|
|
if (a0->_speaking & 1) {
|
|
a0->_speaking ^= 0xFE;
|
|
++_V0Delay._actorRedrawCount;
|
|
}
|
|
// 0x22B5
|
|
if (a0->_miscflags & kActorMiscFlagHide)
|
|
continue;
|
|
|
|
// Sound
|
|
if (a0->_moving != 2 && _currentRoom != 1 && _currentRoom != 44) {
|
|
if (a0->_cost.soundPos == 0)
|
|
a0->_cost.soundCounter++;
|
|
|
|
// Is this the correct location?
|
|
// 0x073C
|
|
if (a0->_sound[0] & 0x3F)
|
|
a0->_cost.soundPos = (a0->_cost.soundPos + 1) % 3;
|
|
}
|
|
}
|
|
// Draw and animate the actors, except those w/o a costume.
|
|
// Note: We could 'optimize' this a little bit by only putting
|
|
// actors with a costume into the _sortedActors array in the
|
|
// first place. However, that would mess up the sorting, and
|
|
// would hence cause regressions. See also the other big
|
|
// comment further up in this method for some details.
|
|
if (a->_costume) {
|
|
|
|
// Unfortunately in V0, the 'animateCostume' call happens right after the call to 'walkActor' (which is before drawing the actor)...
|
|
// doing it the other way with V0, causes animation glitches (when beginnning to walk, as the costume hasnt been updated).
|
|
// Updating the costume directly after 'walkActor' and again, after drawing... causes frame skipping
|
|
if (_game.version == 0) {
|
|
a->animateCostume();
|
|
a->drawActorCostume();
|
|
} else {
|
|
a->drawActorCostume();
|
|
a->animateCostume();
|
|
|
|
if (_game.heversion >= 80) {
|
|
if (VAR_ALWAYS_REDRAW_ACTORS != 0xFF && VAR(VAR_ALWAYS_REDRAW_ACTORS) != 0)
|
|
continue;
|
|
}
|
|
|
|
if (_game.heversion >= 71) {
|
|
// Check if this new actor eclipsed another one...
|
|
for (int i = 0; i < _gdi->_numStrips; i++) {
|
|
int strip = _screenStartStrip + i;
|
|
if (testGfxAnyUsageBits(strip)) {
|
|
for (int j = 1; j < _numActors; j++) {
|
|
if (testGfxUsageBit(strip, j) && testGfxOtherUsageBits(strip, j)) {
|
|
_actors[j]->_needRedraw = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScummEngine_v6::processActors() {
|
|
ScummEngine::processActors();
|
|
|
|
if (_game.features & GF_NEW_COSTUMES)
|
|
akos_processQueue();
|
|
}
|
|
|
|
#ifdef ENABLE_HE
|
|
void ScummEngine_v71he::processActors() {
|
|
heFlushAuxEraseQueue();
|
|
|
|
if (!_disableActorDrawingFlag)
|
|
ScummEngine_v6::processActors();
|
|
|
|
_fullRedraw = false;
|
|
|
|
heFlushAuxQueues();
|
|
}
|
|
|
|
void ScummEngine_v90he::processActors() {
|
|
heFlushAuxEraseQueue();
|
|
|
|
_sprite->checkForForcedRedraws(false);
|
|
_sprite->renderSprites(true);
|
|
|
|
if (!_disableActorDrawingFlag)
|
|
ScummEngine_v6::processActors();
|
|
|
|
_fullRedraw = false;
|
|
|
|
heFlushAuxQueues();
|
|
|
|
_sprite->checkForForcedRedraws(true);
|
|
_sprite->renderSprites(false);
|
|
}
|
|
#endif
|
|
|
|
// Used in Scumm v8, to allow the verb coin to be drawn over the inventory
|
|
// chest. I'm assuming that draw order won't matter here.
|
|
void ScummEngine::processUpperActors() {
|
|
int i;
|
|
|
|
for (i = 1; i < _numActors; i++) {
|
|
if (_actors[i]->isInCurrentRoom() && _actors[i]->_costume && _actors[i]->_layer < 0) {
|
|
_actors[i]->drawActorCostume();
|
|
_actors[i]->animateCostume();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor::drawActorCostume(bool hitTestMode) {
|
|
if (_costume == 0)
|
|
return;
|
|
|
|
if (!hitTestMode) {
|
|
if (!_needRedraw)
|
|
return;
|
|
|
|
_needRedraw = false;
|
|
}
|
|
|
|
setupActorScale();
|
|
|
|
BaseCostumeRenderer *bcr = _vm->_costumeRenderer;
|
|
prepareDrawActorCostume(bcr);
|
|
|
|
// If the actor is partially hidden, redraw it next frame.
|
|
if (bcr->drawCostume(_vm->_virtscr[kMainVirtScreen], _vm->_gdi->_numStrips, this, _drawToBackBuf) & 1) {
|
|
_needRedraw = (_vm->_game.version <= 6);
|
|
}
|
|
|
|
if (!hitTestMode) {
|
|
// Record the vertical extent of the drawn actor
|
|
_top = bcr->_drawTop;
|
|
_bottom = bcr->_drawBottom;
|
|
}
|
|
}
|
|
|
|
|
|
void Actor::prepareDrawActorCostume(BaseCostumeRenderer *bcr) {
|
|
|
|
bcr->_actorID = _number;
|
|
bcr->_actorX = _pos.x - _vm->_virtscr[kMainVirtScreen].xstart;
|
|
bcr->_actorY = _pos.y - _elevation;
|
|
|
|
if (_vm->_game.version == 4 && (_boxscale & 0x8000)) {
|
|
bcr->_scaleX = bcr->_scaleY = _vm->getScaleFromSlot((_boxscale & 0x7fff) + 1, _pos.x, _pos.y);
|
|
} else {
|
|
bcr->_scaleX = _scalex;
|
|
bcr->_scaleY = _scaley;
|
|
}
|
|
|
|
bcr->_shadowMode = _shadowMode;
|
|
if (_vm->_game.version >= 5 && _vm->_game.heversion == 0) {
|
|
bcr->_shadowTable = _vm->_shadowPalette;
|
|
}
|
|
|
|
bcr->setCostume(_costume, (_vm->_game.heversion == 0) ? 0 : _heShadow);
|
|
bcr->setPalette(_palette);
|
|
bcr->setFacing(this);
|
|
|
|
if (_vm->_game.version >= 7) {
|
|
|
|
bcr->_zbuf = _forceClip;
|
|
if (bcr->_zbuf == 100) {
|
|
bcr->_zbuf = _vm->getMaskFromBox(_walkbox);
|
|
if (bcr->_zbuf > _vm->_gdi->_numZBuffer-1)
|
|
bcr->_zbuf = _vm->_gdi->_numZBuffer-1;
|
|
}
|
|
|
|
} else {
|
|
if (_forceClip)
|
|
bcr->_zbuf = _forceClip;
|
|
else if (isInClass(kObjectClassNeverClip))
|
|
bcr->_zbuf = 0;
|
|
else {
|
|
bcr->_zbuf = _vm->getMaskFromBox(_walkbox);
|
|
if (_vm->_game.version == 0)
|
|
bcr->_zbuf &= 0x03;
|
|
if (bcr->_zbuf > _vm->_gdi->_numZBuffer-1)
|
|
bcr->_zbuf = _vm->_gdi->_numZBuffer-1;
|
|
}
|
|
|
|
}
|
|
|
|
bcr->_drawTop = 0x7fffffff;
|
|
bcr->_drawBottom = 0;
|
|
}
|
|
|
|
void ActorHE::prepareDrawActorCostume(BaseCostumeRenderer *bcr) {
|
|
// HE palette number must be set, before setting the costume palette
|
|
bcr->_paletteNum = _hePaletteNum;
|
|
|
|
Actor::prepareDrawActorCostume(bcr);
|
|
|
|
bcr->_actorX += _heOffsX;
|
|
bcr->_actorY += _heOffsY;
|
|
|
|
bcr->_clipOverride = _clipOverride;
|
|
|
|
if (_vm->_game.heversion == 70) {
|
|
bcr->_shadowTable = _vm->_HEV7ActorPalette;
|
|
}
|
|
|
|
bcr->_skipLimbs = (_heSkipLimbs != 0);
|
|
|
|
if (_vm->_game.heversion >= 80 && _heNoTalkAnimation == 0 && _animProgress == 0) {
|
|
if (_vm->getTalkingActor() == _number && !_vm->_string[0].no_talk_anim) {
|
|
int talkState = -1;
|
|
|
|
if (((SoundHE *)_vm->_sound)->isSoundCodeUsed(HSND_TALKIE_SLOT))
|
|
talkState = ((SoundHE *)_vm->_sound)->getSoundVar(HSND_TALKIE_SLOT, 19);
|
|
|
|
// Allow a talkie with tokens to kick into random mouth mode
|
|
if (talkState == -1 || talkState == 0)
|
|
talkState = _vm->_rnd.getRandomNumberRng(1, 10);
|
|
|
|
assertRange(1, talkState, 13, "Talk state");
|
|
setTalkCondition(talkState);
|
|
} else {
|
|
setTalkCondition(1);
|
|
}
|
|
}
|
|
_heNoTalkAnimation = 0;
|
|
}
|
|
|
|
void Actor_v2::prepareDrawActorCostume(BaseCostumeRenderer *bcr) {
|
|
Actor::prepareDrawActorCostume(bcr);
|
|
|
|
bcr->_actorX = _pos.x;
|
|
bcr->_actorY = _pos.y - _elevation;
|
|
|
|
if (_vm->_game.version <= 2) {
|
|
bcr->_actorX *= V12_X_MULTIPLIER;
|
|
bcr->_actorY *= V12_Y_MULTIPLIER;
|
|
}
|
|
bcr->_actorX -= _vm->_virtscr[kMainVirtScreen].xstart;
|
|
|
|
if (_vm->_game.platform == Common::kPlatformNES) {
|
|
// In the NES version, when the actor is facing right,
|
|
// we need to shift it 8 pixels to the left
|
|
if (_facing == 90)
|
|
bcr->_actorX -= 8;
|
|
} else if (_vm->_game.version == 0) {
|
|
bcr->_actorX += 12;
|
|
} else if (_vm->_game.version <= 2) {
|
|
// HACK: We have to adjust the x position by one strip (8 pixels) in
|
|
// V2 games. However, it is not quite clear to me why. And to fully
|
|
// match the original, it seems we have to offset by 2 strips if the
|
|
// actor is facing left (270 degree).
|
|
// V1 games are once again slightly different, here we only have
|
|
// to adjust the 270 degree case...
|
|
if (_facing == 270)
|
|
bcr->_actorX += 16;
|
|
else if (_vm->_game.version == 2)
|
|
bcr->_actorX += 8;
|
|
}
|
|
}
|
|
|
|
#ifdef ENABLE_SCUMM_7_8
|
|
bool Actor::actorHitTest(int x, int y) {
|
|
AkosRenderer *ar = (AkosRenderer *)_vm->_costumeRenderer;
|
|
|
|
ar->_actorHitX = x;
|
|
ar->_actorHitY = y;
|
|
ar->_actorHitMode = true;
|
|
ar->_actorHitResult = false;
|
|
|
|
drawActorCostume(true);
|
|
|
|
ar->_actorHitMode = false;
|
|
|
|
return ar->_actorHitResult;
|
|
}
|
|
#endif
|
|
|
|
void Actor::startAnimActor(int f) {
|
|
switch (f) {
|
|
case 0x38:
|
|
f = _initFrame;
|
|
break;
|
|
case 0x39:
|
|
f = _walkFrame;
|
|
break;
|
|
case 0x3A:
|
|
f = _standFrame;
|
|
break;
|
|
case 0x3B:
|
|
f = _talkStartFrame;
|
|
break;
|
|
case 0x3C:
|
|
f = _talkStopFrame;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
assert(f != 0x3E);
|
|
|
|
if (isInCurrentRoom() && _costume != 0) {
|
|
_animProgress = 0;
|
|
_needRedraw = true;
|
|
_cost.animCounter = 0;
|
|
// V1 - V2 games don't seem to need a _cost.reset() at this point.
|
|
// Causes Zak to lose his body in several scenes, see bug #1032
|
|
if (_vm->_game.version >= 3 && f == _initFrame) {
|
|
_cost.reset();
|
|
if (_vm->_game.heversion != 0) {
|
|
((ActorHE *)this)->_auxActor = 0;
|
|
((ActorHE *)this)->_auxEraseX1 = 0;
|
|
((ActorHE *)this)->_auxEraseY1 = 0;
|
|
((ActorHE *)this)->_auxEraseX2 = -1;
|
|
((ActorHE *)this)->_auxEraseY2 = -1;
|
|
}
|
|
}
|
|
_vm->_costumeLoader->costumeDecodeData(this, f, (uint) - 1);
|
|
_frame = f;
|
|
}
|
|
}
|
|
|
|
void Actor_v0::startAnimActor(int f) {
|
|
if (f == _talkStartFrame) {
|
|
if (_sound[0] & 0x40)
|
|
return;
|
|
|
|
_speaking = 1;
|
|
speakCheck();
|
|
return;
|
|
}
|
|
|
|
if (f == _talkStopFrame) {
|
|
|
|
_speaking = 0;
|
|
return;
|
|
}
|
|
|
|
if (f == _standFrame)
|
|
setDirection(_facing);
|
|
}
|
|
|
|
void Actor_v7::startAnimActor(int f) {
|
|
if (_vm->_game.id == GID_FT && _vm->_game.platform == Common::kPlatformDOS && (_vm->_game.features & GF_DEMO)) {
|
|
Actor::startAnimActor(f);
|
|
return;
|
|
}
|
|
|
|
switch (f) {
|
|
case 1001:
|
|
f = _initFrame;
|
|
break;
|
|
case 1002:
|
|
f = _walkFrame;
|
|
break;
|
|
case 1003:
|
|
f = _standFrame;
|
|
break;
|
|
case 1004:
|
|
f = _talkStartFrame;
|
|
break;
|
|
case 1005:
|
|
f = _talkStopFrame;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (_costume != 0) {
|
|
_animProgress = 0;
|
|
_needRedraw = true;
|
|
if (f == _initFrame)
|
|
_cost.reset();
|
|
_vm->_costumeLoader->costumeDecodeData(this, f, (uint) - 1);
|
|
_frame = f;
|
|
}
|
|
}
|
|
|
|
void Actor::animateActor(int anim) {
|
|
int chore, dir;
|
|
|
|
if (_vm->_game.version >= 7 && !((_vm->_game.id == GID_FT) && (_vm->_game.features & GF_DEMO) && (_vm->_game.platform == Common::kPlatformDOS))) {
|
|
|
|
if (anim == 0xFF)
|
|
anim = 2000;
|
|
|
|
chore = anim / 1000;
|
|
dir = anim % 1000;
|
|
|
|
} else {
|
|
// Format of the input parameter:
|
|
// - The 2 least significant bits are the direction
|
|
// - The rest is the chore command to execute
|
|
chore = anim >> 2;
|
|
dir = oldDirToNewDir(anim & 3);
|
|
|
|
// Convert into old chore code
|
|
chore = 0x3F - chore + 2;
|
|
|
|
}
|
|
|
|
switch (chore) {
|
|
case 2: // stop walking
|
|
if (isInCurrentRoom() ||
|
|
!(_vm->_game.version >= 3 && _vm->_game.version <= 6)) {
|
|
startAnimActor(_standFrame);
|
|
stopActorMoving();
|
|
}
|
|
break;
|
|
case 3: // change direction immediatly
|
|
if (isInCurrentRoom() ||
|
|
!(_vm->_game.version >= 3 && _vm->_game.version <= 6)) {
|
|
_moving &= ~MF_TURN;
|
|
}
|
|
|
|
setDirection(dir);
|
|
break;
|
|
case 4: // turn to new direction
|
|
if (isInCurrentRoom() ||
|
|
!(_vm->_game.version >= 3 && _vm->_game.version <= 6)) {
|
|
turnToDirection(dir);
|
|
}
|
|
break;
|
|
case 64:
|
|
if (_vm->_game.version == 0) {
|
|
_moving &= ~MF_TURN;
|
|
setDirection(dir);
|
|
break;
|
|
}
|
|
// fall through
|
|
default:
|
|
if (_vm->_game.version <= 2)
|
|
startAnimActor(anim >> 2);
|
|
else
|
|
startAnimActor(anim);
|
|
}
|
|
}
|
|
|
|
void Actor::animateCostume() {
|
|
if (_costume == 0)
|
|
return;
|
|
|
|
_animProgress++;
|
|
if (_animProgress >= _animSpeed) {
|
|
_animProgress = 0;
|
|
|
|
_vm->_costumeLoader->loadCostume(_costume);
|
|
if (_vm->_costumeLoader->increaseAnims(this)) {
|
|
_needRedraw = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor_v0::limbFrameCheck(int limb) {
|
|
if (_cost.frame[limb] == 0xFFFF)
|
|
return;
|
|
|
|
if (_cost.start[limb] == _cost.frame[limb])
|
|
return;
|
|
|
|
// 0x25A4
|
|
_cost.start[limb] = _cost.frame[limb];
|
|
|
|
_limbFrameRepeat[limb] = _limbFrameRepeatNew[limb];
|
|
|
|
// 0x25C3
|
|
_cost.animType[limb] = ((V0CostumeLoader *)_vm->_costumeLoader)->getFrame(this, limb);
|
|
_cost.curpos[limb] = 0;
|
|
|
|
_needRedraw = true;
|
|
}
|
|
|
|
void Actor_v0::animateCostume() {
|
|
speakCheck();
|
|
|
|
byte count = _vm->_costumeLoader->increaseAnims(this);
|
|
|
|
if (count) {
|
|
_vm->_V0Delay._actorLimbRedrawDrawCount += count;
|
|
|
|
_needRedraw = true;
|
|
}
|
|
}
|
|
|
|
void Actor_v0::speakCheck() {
|
|
if (_sound[0] & 0x80)
|
|
return;
|
|
|
|
int cmd = newDirToOldDir(_facing);
|
|
|
|
if (_speaking & 0x80)
|
|
cmd += 0x0C;
|
|
else
|
|
cmd += 0x10;
|
|
|
|
_animFrameRepeat = -1;
|
|
animateActor(cmd);
|
|
}
|
|
|
|
#ifdef ENABLE_SCUMM_7_8
|
|
void Actor::animateLimb(int limb, int f) {
|
|
// This methods is very similiar to animateCostume().
|
|
// However, instead of animating *all* the limbs, it only animates
|
|
// the specified limb to be at the frame specified by "f".
|
|
|
|
if (!f)
|
|
return;
|
|
|
|
_animProgress++;
|
|
if (_animProgress >= _animSpeed) {
|
|
_animProgress = 0;
|
|
|
|
if (_costume == 0)
|
|
return;
|
|
|
|
const byte *aksq, *akfo;
|
|
uint size;
|
|
byte *akos = _vm->getResourceAddress(rtCostume, _costume);
|
|
assert(akos);
|
|
|
|
aksq = _vm->findResourceData(MKTAG('A','K','S','Q'), akos);
|
|
akfo = _vm->findResourceData(MKTAG('A','K','F','O'), akos);
|
|
|
|
size = _vm->getResourceDataSize(akfo) / 2;
|
|
|
|
while (f--) {
|
|
if (_cost.animType[limb] != AKAT_Empty)
|
|
((ScummEngine_v6 *)_vm)->akos_increaseAnim(this, limb, aksq, (const uint16 *)akfo, size);
|
|
}
|
|
|
|
// _needRedraw = true;
|
|
// _needBgReset = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void ScummEngine::redrawAllActors() {
|
|
int i;
|
|
|
|
for (i = 1; i < _numActors; ++i) {
|
|
_actors[i]->_needRedraw = true;
|
|
_actors[i]->_needBgReset = true;
|
|
}
|
|
}
|
|
|
|
void ScummEngine::setActorRedrawFlags() {
|
|
int i, j;
|
|
|
|
// Redraw all actors if a full redraw was requested.
|
|
// Also redraw all actors in COMI (see bug #1825 for details).
|
|
if (_fullRedraw || _game.version == 8 || (VAR_ALWAYS_REDRAW_ACTORS != 0xFF && VAR(VAR_ALWAYS_REDRAW_ACTORS) != 0)) {
|
|
for (j = 1; j < _numActors; j++) {
|
|
_actors[j]->_needRedraw = true;
|
|
}
|
|
} else {
|
|
if (_game.heversion >= 72) {
|
|
for (j = 1; j < _numActors; j++) {
|
|
if (_actors[j]->_costume && _actors[j]->_heShadow)
|
|
_actors[j]->_needRedraw = true;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < _gdi->_numStrips; i++) {
|
|
int strip = _screenStartStrip + i;
|
|
if (testGfxAnyUsageBits(strip)) {
|
|
for (j = 1; j < _numActors; j++) {
|
|
if (testGfxUsageBit(strip, j) && testGfxOtherUsageBits(strip, j)) {
|
|
_actors[j]->_needRedraw = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScummEngine::resetActorBgs() {
|
|
int i, j;
|
|
|
|
for (i = 0; i < _gdi->_numStrips; i++) {
|
|
int strip = _screenStartStrip + i;
|
|
clearGfxUsageBit(strip, USAGE_BIT_DIRTY);
|
|
clearGfxUsageBit(strip, USAGE_BIT_RESTORED);
|
|
for (j = 1; j < _numActors; j++) {
|
|
if (_game.heversion != 0 && (((ActorHE *)_actors[j])->_generalFlags & ACTOR_GENERAL_FLAG_IGNORE_ERASE) != 0)
|
|
continue;
|
|
|
|
if (testGfxUsageBit(strip, j) &&
|
|
((_actors[j]->_top != 0x7fffffff && _actors[j]->_needRedraw) || _actors[j]->_needBgReset)) {
|
|
clearGfxUsageBit(strip, j);
|
|
if ((_actors[j]->_bottom - _actors[j]->_top) >= 0)
|
|
_gdi->resetBackground(_actors[j]->_top, _actors[j]->_bottom, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 1; i < _numActors; i++) {
|
|
_actors[i]->_needBgReset = false;
|
|
}
|
|
}
|
|
|
|
// HE specific
|
|
void ActorHE::drawActorToBackBuf(int x, int y) {
|
|
int curTop = _top;
|
|
int curBottom = _bottom;
|
|
|
|
_pos.x = x;
|
|
_pos.y = y;
|
|
|
|
_drawToBackBuf = true;
|
|
_needRedraw = true;
|
|
drawActorCostume();
|
|
|
|
_drawToBackBuf = false;
|
|
_needRedraw = true;
|
|
drawActorCostume();
|
|
_needRedraw = false;
|
|
|
|
if (_top > curTop)
|
|
_top = curTop;
|
|
if (_bottom < curBottom)
|
|
_bottom = curBottom;
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- Actor talking ---
|
|
#pragma mark -
|
|
|
|
|
|
// V1 Maniac doesn't have a ScummVar for VAR_TALK_ACTOR, and just uses
|
|
// an internal variable. Emulate this to prevent overwriting script vars...
|
|
// Maniac NES (V1), however, DOES have a ScummVar for VAR_TALK_ACTOR
|
|
int ScummEngine::getTalkingActor() {
|
|
if (_game.id == GID_MANIAC && _game.version <= 1 && !(_game.platform == Common::kPlatformNES))
|
|
return _V1TalkingActor;
|
|
else
|
|
return VAR(VAR_TALK_ACTOR);
|
|
}
|
|
|
|
void ScummEngine::setTalkingActor(int i) {
|
|
|
|
if (i == 255) {
|
|
_system->clearFocusRectangle();
|
|
} else {
|
|
// Work out the screen co-ordinates of the actor
|
|
int x = _actors[i]->getPos().x - (camera._cur.x - (_screenWidth >> 1));
|
|
int y = _actors[i]->_top - (camera._cur.y - (_screenHeight >> 1));
|
|
|
|
// Set the focus area to the calculated position
|
|
// TODO: Make the size adjust depending on what it's focusing on.
|
|
_system->setFocusRectangle(Common::Rect::center(x, y, 192, 128));
|
|
}
|
|
|
|
if (_game.id == GID_MANIAC && _game.version <= 1 && !(_game.platform == Common::kPlatformNES))
|
|
_V1TalkingActor = i;
|
|
else
|
|
VAR(VAR_TALK_ACTOR) = i;
|
|
}
|
|
|
|
static const int v0MMActorTalkColor[25] = {
|
|
1, 7, 2, 14, 8, 15, 3, 7, 7, 15, 1, 13, 1, 4, 5, 5, 4, 3, 1, 5, 1, 1, 1, 1, 7
|
|
};
|
|
static const int v1MMActorTalkColor[25] = {
|
|
1, 7, 2, 14, 8, 1, 3, 7, 7, 12, 1, 13, 1, 4, 5, 5, 4, 3, 1, 5, 1, 1, 1, 7, 7
|
|
};
|
|
|
|
void ScummEngine::resetV1ActorTalkColor() {
|
|
int i;
|
|
|
|
for (i = 1; i < _numActors; i++) {
|
|
if (_game.version == 0) {
|
|
_actors[i]->_talkColor = v0MMActorTalkColor[i];
|
|
} else {
|
|
_actors[i]->_talkColor = v1MMActorTalkColor[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef ENABLE_SCUMM_7_8
|
|
void ScummEngine_v7::actorTalk(const byte *msg) {
|
|
Actor *a;
|
|
bool stringWrap = false;
|
|
bool usingOldSystem = (_game.id == GID_FT) || (_game.id == GID_DIG && _game.features & GF_DEMO);
|
|
|
|
// WORKAROUND bug #1493: In Puerto Pollo, if you have Guybrush examine
|
|
// the church clock, he'll read out the current time. However, this was
|
|
// disabled in some releases, possibly because of the poor results for
|
|
// some languages (e.g. German, French). The check was done inside the
|
|
// original interpreters, so we replicate their behavior.
|
|
if (_game.id == GID_CMI && _language != Common::EN_ANY && _language != Common::IT_ITA && _language != Common::RU_RUS) {
|
|
if (strncmp((const char *)msg, "/CKGT326/", 9) == 0)
|
|
msg = (const byte *)"/VDSO325/Whoa! Look at the time. Gotta scoot.";
|
|
|
|
// Reject every line which begins with the CKGT tag ("ClocK Guybrush Threepwood")
|
|
if (strncmp((const char *)msg, "/CKGT", 5) == 0)
|
|
return;
|
|
}
|
|
|
|
convertMessageToString(msg, _charsetBuffer, sizeof(_charsetBuffer));
|
|
|
|
// Play associated speech, if any
|
|
playSpeech((byte *)_lastStringTag);
|
|
|
|
if (!usingOldSystem) {
|
|
if (VAR(VAR_HAVE_MSG))
|
|
stopTalk();
|
|
} else {
|
|
if (!_keepText)
|
|
stopTalk();
|
|
}
|
|
if (_actorToPrintStrFor == 0xFF) {
|
|
setTalkingActor(0xFF);
|
|
_charsetColor = (byte)_string[0].color;
|
|
} else {
|
|
a = derefActor(_actorToPrintStrFor, "actorTalk");
|
|
setTalkingActor(a->_number);
|
|
if (!_string[0].no_talk_anim) {
|
|
a->runActorTalkScript(a->_talkStartFrame);
|
|
}
|
|
_charsetColor = a->_talkColor;
|
|
|
|
// This is what the original COMI CJK interpreter does here.
|
|
if (_game.id == GID_CMI && _useCJKMode) {
|
|
if (a->_number == 1 && _currentRoom == 15)
|
|
_charsetColor = 28;
|
|
else if (a->_talkColor == 22)
|
|
_charsetColor = 5;
|
|
}
|
|
}
|
|
|
|
_charsetBufPos = 0;
|
|
_talkDelay = 0;
|
|
_haveMsg = 1;
|
|
if (usingOldSystem)
|
|
VAR(VAR_HAVE_MSG) = 0xFF;
|
|
_haveActorSpeechMsg = usingOldSystem ? true : (!_sound->isSoundRunning(kTalkSoundID));
|
|
|
|
if (!usingOldSystem) {
|
|
stringWrap = _string[0].wrapping;
|
|
_string[0].wrapping = true;
|
|
}
|
|
|
|
displayDialog();
|
|
|
|
if (!usingOldSystem) {
|
|
if (_game.version == 8)
|
|
VAR(VAR_HAVE_MSG) = (_string[0].no_talk_anim) ? 2 : 1;
|
|
else
|
|
VAR(VAR_HAVE_MSG) = 1;
|
|
_string[0].wrapping = stringWrap;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void ScummEngine::actorTalk(const byte *msg) {
|
|
Actor *a;
|
|
|
|
convertMessageToString(msg, _charsetBuffer, sizeof(_charsetBuffer));
|
|
|
|
// I have commented out this workaround, since it did cause another
|
|
// bug (#11480). It is not okay to skip the stopTalk() calls here.
|
|
// Instead, I have added two checks from LOOM DOS EGA disasm (one
|
|
// below and one in displayDialog()).
|
|
// WORKAROUND for bugs #985 and #990
|
|
/*if (_game.id == GID_LOOM) {
|
|
if (!*_charsetBuffer)
|
|
return;
|
|
}*/
|
|
|
|
if (_actorToPrintStrFor == 0xFF) {
|
|
if (!_keepText) {
|
|
stopTalk();
|
|
}
|
|
setTalkingActor(0xFF);
|
|
} else {
|
|
int oldact;
|
|
|
|
a = derefActor(_actorToPrintStrFor, "actorTalk");
|
|
|
|
if (!a->isInCurrentRoom()) {
|
|
oldact = 0xFF;
|
|
} else {
|
|
if (!_keepText) {
|
|
stopTalk();
|
|
}
|
|
setTalkingActor(a->_number);
|
|
if (_game.heversion != 0)
|
|
((ActorHE *)a)->_heTalking = true;
|
|
// The second check is from LOOM DOS EGA disasm. It prevents weird speech animations
|
|
// with empty strings (bug #990). The same code is present in displayDialog(). The FM-Towns
|
|
// versions don't have such code, but I do not get the weird speech animations either.
|
|
// So apparently it is not needed there.
|
|
if (!_string[0].no_talk_anim && !(_game.id == GID_LOOM && _game.platform != Common::kPlatformFMTowns && !*_charsetBuffer)) {
|
|
a->runActorTalkScript(a->_talkStartFrame);
|
|
_useTalkAnims = true;
|
|
}
|
|
oldact = getTalkingActor();
|
|
}
|
|
if (oldact >= 0x80)
|
|
return;
|
|
}
|
|
|
|
if (_game.heversion >= 72 || getTalkingActor() > 0x7F) {
|
|
if (_game.platform == Common::kPlatformNES)
|
|
_charsetColor = 0; // NES MM intro color is always 0
|
|
else
|
|
_charsetColor = (byte)_string[0].color;
|
|
} else if (_game.platform == Common::kPlatformNES) {
|
|
if (_NES_lastTalkingActor != getTalkingActor())
|
|
_NES_talkColor ^= 1;
|
|
_NES_lastTalkingActor = getTalkingActor();
|
|
_charsetColor = _NES_talkColor;
|
|
} else {
|
|
a = derefActor(getTalkingActor(), "actorTalk(2)");
|
|
_charsetColor = a->_talkColor;
|
|
}
|
|
_charsetBufPos = 0;
|
|
_talkDelay = 0;
|
|
_haveMsg = 0xFF;
|
|
VAR(VAR_HAVE_MSG) = 0xFF;
|
|
if (VAR_CHARCOUNT != 0xFF)
|
|
VAR(VAR_CHARCOUNT) = 0;
|
|
_haveActorSpeechMsg = true;
|
|
displayDialog();
|
|
}
|
|
|
|
void Actor::runActorTalkScript(int f) {
|
|
if (_vm->_game.version == 8 && _vm->VAR(_vm->VAR_HAVE_MSG) == 2)
|
|
return;
|
|
|
|
if (_vm->_game.id == GID_FT && _vm->_string[0].no_talk_anim)
|
|
return;
|
|
|
|
if (!_vm->getTalkingActor() || _room != _vm->_currentRoom || _frame == f)
|
|
return;
|
|
|
|
if (_talkScript) {
|
|
int script = _talkScript;
|
|
int args[NUM_SCRIPT_LOCAL];
|
|
memset(args, 0, sizeof(args));
|
|
args[1] = f;
|
|
args[0] = _number;
|
|
|
|
_vm->runScript(script, 1, 0, args);
|
|
} else {
|
|
startAnimActor(f);
|
|
}
|
|
}
|
|
|
|
void ScummEngine::stopTalk() {
|
|
int act;
|
|
|
|
_sound->stopTalkSound();
|
|
|
|
_haveMsg = 0;
|
|
_talkDelay = 0;
|
|
_sound->_digiSndMode = DIGI_SND_MODE_EMPTY;
|
|
|
|
act = getTalkingActor();
|
|
if (act && act < 0x80) {
|
|
Actor *a = derefActor(act, "stopTalk");
|
|
if ((_game.version >= 7 && !_string[0].no_talk_anim) ||
|
|
(_game.version <= 6 && a->isInCurrentRoom() && _useTalkAnims)) {
|
|
a->runActorTalkScript(a->_talkStopFrame);
|
|
_useTalkAnims = false;
|
|
}
|
|
if (_game.version <= 7 && _game.heversion == 0)
|
|
setTalkingActor(0xFF);
|
|
if (_game.heversion != 0) {
|
|
((ActorHE *)a)->_heTalking = false;
|
|
}
|
|
}
|
|
|
|
if ((_game.id == GID_DIG && !(_game.features & GF_DEMO)) || _game.id == GID_CMI) {
|
|
setTalkingActor(0);
|
|
VAR(VAR_HAVE_MSG) = 0;
|
|
} else if (_game.heversion >= 60) {
|
|
setTalkingActor(0);
|
|
}
|
|
|
|
_keepText = false;
|
|
if (_game.version >= 7) {
|
|
#ifdef ENABLE_SCUMM_7_8
|
|
((ScummEngine_v7 *)this)->clearSubtitleQueue();
|
|
#endif
|
|
} else {
|
|
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
|
|
if (_game.platform == Common::kPlatformFMTowns)
|
|
towns_restoreCharsetBg();
|
|
else
|
|
#endif
|
|
restoreCharsetBg();
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- TODO ---
|
|
#pragma mark -
|
|
|
|
|
|
void ActorHE::setActorCostume(int c) {
|
|
if (_vm->_game.heversion >= 61 && (c == -1 || c == -2)) {
|
|
_heSkipLimbs = (c == -1);
|
|
_needRedraw = true;
|
|
return;
|
|
}
|
|
|
|
// Based on disassembly. It seems that high byte is not used at all, though
|
|
// it is attached to all horizontally flipped object, like left eye.
|
|
if (_vm->_game.heversion >= 61 && _vm->_game.heversion <= 62)
|
|
c &= 0xff;
|
|
|
|
if (_vm->_game.features & GF_NEW_COSTUMES) {
|
|
#ifdef ENABLE_HE
|
|
if (_vm->_game.heversion >= 71)
|
|
((ScummEngine_v71he *)_vm)->heQueueEraseAuxActor(this);
|
|
#endif
|
|
_auxActor = 0;
|
|
_auxEraseX1 = 0;
|
|
_auxEraseY1 = 0;
|
|
_auxEraseX2 = -1;
|
|
_auxEraseY2 = -1;
|
|
|
|
if (_visible) {
|
|
if (_vm->_game.heversion >= 60)
|
|
_needRedraw = true;
|
|
}
|
|
}
|
|
|
|
Actor::setActorCostume(c);
|
|
|
|
if (_vm->_game.heversion >= 71 && _vm->getTalkingActor() == _number) {
|
|
if (_vm->_game.heversion <= 95 || (_vm->_game.heversion >= 98 && _vm->VAR(_vm->VAR_SKIP_RESET_TALK_ACTOR) == 0)) {
|
|
_vm->setTalkingActor(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor::setActorCostume(int c) {
|
|
int i;
|
|
|
|
_costumeNeedsInit = true;
|
|
|
|
if (_vm->_game.features & GF_NEW_COSTUMES) {
|
|
memset(_animVariable, 0, sizeof(_animVariable));
|
|
|
|
_costume = c;
|
|
_cost.reset();
|
|
|
|
if (_visible) {
|
|
if (_costume) {
|
|
_vm->ensureResourceLoaded(rtCostume, _costume);
|
|
}
|
|
startAnimActor(_initFrame);
|
|
}
|
|
} else {
|
|
if (_visible) {
|
|
hideActor();
|
|
_cost.reset();
|
|
_costume = c;
|
|
showActor();
|
|
} else {
|
|
_costume = c;
|
|
_cost.reset();
|
|
}
|
|
}
|
|
|
|
|
|
// V1 zak uses palette[] as a dynamic costume color array.
|
|
if (_vm->_game.version <= 1)
|
|
return;
|
|
|
|
if (_vm->_game.features & GF_NEW_COSTUMES) {
|
|
for (i = 0; i < 256; i++)
|
|
_palette[i] = 0xFF;
|
|
} else if (_vm->_game.features & GF_OLD_BUNDLE) {
|
|
for (i = 0; i < 16; i++)
|
|
_palette[i] = i;
|
|
} else {
|
|
for (i = 0; i < 32; i++)
|
|
_palette[i] = 0xFF;
|
|
}
|
|
|
|
// Make stuff more visible on CGA. Based on disassembly. It is exactly the same in INDY3, LOOM and MI1 EGA.
|
|
if (_vm->_renderMode == Common::kRenderCGA && _vm->_game.version > 2 && _vm->_game.version < 5) {
|
|
_palette[6] = 5;
|
|
_palette[7] = 15;
|
|
}
|
|
}
|
|
|
|
static const char *const v0ActorNames_English[25] = {
|
|
"Syd",
|
|
"Razor",
|
|
"Dave",
|
|
"Michael",
|
|
"Bernard",
|
|
"Wendy",
|
|
"Jeff",
|
|
"", // Radiation Suit
|
|
"Dr Fred",
|
|
"Nurse Edna",
|
|
"Weird Ed",
|
|
"Dead Cousin Ted",
|
|
"Purple Tentacle",
|
|
"Green Tentacle",
|
|
"", // Meteor Police
|
|
"Meteor",
|
|
"", // Mark Eteer
|
|
"", // Talkshow Host
|
|
"Plant",
|
|
"", // Meteor Radiation
|
|
"", // Edsel (small, outro)
|
|
"", // Meteor (small, intro)
|
|
"Sandy", // (Lab)
|
|
"", // Sandy (Cut-Scene)
|
|
};
|
|
|
|
static const char *const v0ActorNames_German[25] = {
|
|
"Syd",
|
|
"Razor",
|
|
"Dave",
|
|
"Michael",
|
|
"Bernard",
|
|
"Wendy",
|
|
"Jeff",
|
|
"",
|
|
"Dr.Fred",
|
|
"Schwester Edna",
|
|
"Weird Ed",
|
|
"Ted",
|
|
"Lila Tentakel",
|
|
"Gr<nes Tentakel",
|
|
"",
|
|
"Meteor",
|
|
"",
|
|
"",
|
|
"Pflanze",
|
|
"",
|
|
"",
|
|
"",
|
|
"Sandy",
|
|
"",
|
|
};
|
|
|
|
const byte *Actor::getActorName() {
|
|
const byte *ptr = nullptr;
|
|
|
|
if (_vm->_game.version == 0) {
|
|
if (_number) {
|
|
switch (_vm->_language) {
|
|
case Common::DE_DEU:
|
|
ptr = (const byte *)v0ActorNames_German[_number - 1];
|
|
break;
|
|
default:
|
|
ptr = (const byte *)v0ActorNames_English[_number - 1];
|
|
}
|
|
}
|
|
} else {
|
|
ptr = _vm->getResourceAddress(rtActorName, _number);
|
|
}
|
|
|
|
if (ptr == nullptr) {
|
|
debugC(DEBUG_ACTORS, "Failed to find name of actor %d", _number);
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
int Actor::getAnimVar(byte var) const {
|
|
assertRange(0, var, 26, "getAnimVar:");
|
|
return _animVariable[var];
|
|
}
|
|
|
|
void Actor::setAnimVar(byte var, int value) {
|
|
assertRange(0, var, 26, "setAnimVar:");
|
|
_animVariable[var] = value;
|
|
}
|
|
|
|
void Actor::remapActorPaletteColor(int color, int new_color) {
|
|
const byte *akos, *akpl;
|
|
int akpl_size, i;
|
|
byte akpl_color;
|
|
|
|
akos = _vm->getResourceAddress(rtCostume, _costume);
|
|
if (!akos) {
|
|
debugC(DEBUG_ACTORS, "Actor::remapActorPaletteColor: Can't remap actor %d, costume %d not found", _number, _costume);
|
|
return;
|
|
}
|
|
|
|
akpl = _vm->findResourceData(MKTAG('A','K','P','L'), akos);
|
|
if (!akpl) {
|
|
debugC(DEBUG_ACTORS, "Actor::remapActorPaletteColor: Can't remap actor %d, costume %d doesn't contain an AKPL block", _number, _costume);
|
|
return;
|
|
}
|
|
|
|
// Get the number palette entries
|
|
akpl_size = _vm->getResourceDataSize(akpl);
|
|
|
|
for (i = 0; i < akpl_size; i++) {
|
|
akpl_color = *akpl++;
|
|
if (akpl_color == color) {
|
|
_palette[i] = new_color;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor::remapActorPalette(int r_fact, int g_fact, int b_fact, int threshold) {
|
|
const byte *akos, *rgbs, *akpl;
|
|
int akpl_size, i;
|
|
int r, g, b;
|
|
byte akpl_color;
|
|
|
|
if (!isInCurrentRoom()) {
|
|
debugC(DEBUG_ACTORS, "Actor::remapActorPalette: Actor %d not in current room", _number);
|
|
return;
|
|
}
|
|
|
|
akos = _vm->getResourceAddress(rtCostume, _costume);
|
|
if (!akos) {
|
|
debugC(DEBUG_ACTORS, "Actor::remapActorPalette: Can't remap actor %d, costume %d not found", _number, _costume);
|
|
return;
|
|
}
|
|
|
|
akpl = _vm->findResourceData(MKTAG('A','K','P','L'), akos);
|
|
if (!akpl) {
|
|
debugC(DEBUG_ACTORS, "Actor::remapActorPalette: Can't remap actor %d, costume %d doesn't contain an AKPL block", _number, _costume);
|
|
return;
|
|
}
|
|
|
|
// Get the number palette entries
|
|
akpl_size = _vm->getResourceDataSize(akpl);
|
|
|
|
rgbs = _vm->findResourceData(MKTAG('R','G','B','S'), akos);
|
|
|
|
if (!rgbs) {
|
|
debugC(DEBUG_ACTORS, "Actor::remapActorPalette: Can't remap actor %d costume %d doesn't contain an RGB block", _number, _costume);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < akpl_size; i++) {
|
|
r = *rgbs++;
|
|
g = *rgbs++;
|
|
b = *rgbs++;
|
|
|
|
akpl_color = *akpl++;
|
|
|
|
// allow remap of generic palette entry?
|
|
if (!_shadowMode || akpl_color >= 16) {
|
|
r = (r * r_fact) >> 8;
|
|
g = (g * g_fact) >> 8;
|
|
b = (b * b_fact) >> 8;
|
|
_palette[i] = _vm->remapPaletteColor(r, g, b, threshold);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor::classChanged(int cls, bool value) {
|
|
if (cls == kObjectClassAlwaysClip)
|
|
_forceClip = value;
|
|
if (cls == kObjectClassIgnoreBoxes)
|
|
_ignoreBoxes = value;
|
|
}
|
|
|
|
bool Actor::isInClass(int cls) {
|
|
return _vm->getClass(_number, cls);
|
|
}
|
|
|
|
bool Actor::isPlayer() {
|
|
return isInClass(kObjectClassPlayer);
|
|
}
|
|
|
|
bool Actor_v2::isPlayer() {
|
|
// isPlayer() is not supported by v0
|
|
assert(_vm->_game.version != 0);
|
|
// MM V1 PC uses VAR_EGO and not VARS 42 / 43. ZAK V1 does already have VARS 42 / 43 here.
|
|
// For MM NES I do not have a disasm and the room I used to test it (MM room 24) also has
|
|
// different box flags in the NES version, so it will not even call into this function.
|
|
// However, I could at least confirm that VARS 42 and 43 are both set to 0, so apparently
|
|
// not in use.
|
|
return (_vm->_game.id == GID_MANIAC && _vm->_game.version == 1) ? (_number == _vm->VAR(_vm->VAR_EGO)) : (_vm->VAR(42) <= _number && _number <= _vm->VAR(43));
|
|
}
|
|
|
|
void ActorHE::setActorEraseType(int eraseValue) {
|
|
if (eraseValue) {
|
|
_generalFlags &= ~ACTOR_GENERAL_FLAG_IGNORE_ERASE;
|
|
} else {
|
|
_generalFlags |= ACTOR_GENERAL_FLAG_IGNORE_ERASE;
|
|
}
|
|
|
|
if (_vm->_game.heversion > 99 || _vm->_isHE995) {
|
|
_needBgReset = true;
|
|
_needRedraw = true;
|
|
}
|
|
}
|
|
|
|
void ActorHE::setCondition(int slot, int set) {
|
|
const int condMaskCode = (_vm->_game.heversion >= 85) ? 0x1FFF : 0x3FF;
|
|
assertRange(1, slot, 32, "setCondition: Condition");
|
|
if (set == 0) {
|
|
_heCondMask &= ~(1 << (slot - 1));
|
|
} else {
|
|
_heCondMask |= 1 << (slot - 1);
|
|
}
|
|
if (_heCondMask & condMaskCode) {
|
|
_heCondMask &= ~1;
|
|
} else {
|
|
_heCondMask |= 1;
|
|
}
|
|
}
|
|
|
|
bool ActorHE::isConditionSet(int slot) const {
|
|
assertRange(1, slot, 32, "isConditionSet: Condition");
|
|
return (_heCondMask & (1 << (slot - 1))) != 0;
|
|
}
|
|
|
|
void ActorHE::setUserCondition(int slot, int set) {
|
|
assertRange(1, slot, 16, "setUserCondition: Condition");
|
|
setCondition(slot + 16, set);
|
|
}
|
|
|
|
bool ActorHE::isUserConditionSet(int slot) const {
|
|
assertRange(1, slot, 16, "isUserConditionSet: Condition");
|
|
return isConditionSet(slot + 16);
|
|
}
|
|
|
|
void ActorHE::setTalkCondition(int slot) {
|
|
const int condMaskCode = (_vm->_game.heversion >= 85) ? 0x1FFF : 0x3FF;
|
|
assertRange(1, slot, 16, "setTalkCondition: Condition");
|
|
_heCondMask = (_heCondMask & ~condMaskCode) | 1;
|
|
if (slot != 1) {
|
|
setCondition(slot, 1);
|
|
}
|
|
}
|
|
|
|
bool ActorHE::isTalkConditionSet(int slot) const {
|
|
assertRange(1, slot, 16, "isTalkConditionSet: Condition");
|
|
return isConditionSet(slot);
|
|
}
|
|
|
|
#ifdef ENABLE_HE
|
|
void ScummEngine_v71he::heFlushAuxEraseQueue() {
|
|
if (_disableActorDrawingFlag) {
|
|
_heAuxEraseActorIndex = 0;
|
|
return;
|
|
}
|
|
|
|
// Erase any AUX frames that were marked to be erased...
|
|
for (int i = 0; i < _heAuxEraseActorIndex; i++) {
|
|
if (_heAuxEraseActorTable[i].y1 <= _heAuxEraseActorTable[i].y2) {
|
|
Common::Rect blitRect(
|
|
_heAuxEraseActorTable[i].x1, _heAuxEraseActorTable[i].y1,
|
|
_heAuxEraseActorTable[i].x2, _heAuxEraseActorTable[i].y2);
|
|
backgroundToForegroundBlit(blitRect);
|
|
}
|
|
}
|
|
|
|
_heAuxEraseActorIndex = 0;
|
|
}
|
|
|
|
void ScummEngine_v71he::heFlushAuxQueues() {
|
|
int x, y, w, h, type, whichActor;
|
|
int updateRects, xOffset, yOffset;
|
|
byte *costumeAddress;
|
|
const byte *auxDataBlockPtr;
|
|
const byte *auxDataPtr;
|
|
const byte *auxFrameDataPtr;
|
|
const byte *auxUpdateRectPtr;
|
|
byte *foregroundBufferPtr;
|
|
byte *backgroundBufferPtr;
|
|
const byte *auxEraseRectPtr;
|
|
VirtScreen *pvs = &_virtscr[kMainVirtScreen];
|
|
|
|
if (_disableActorDrawingFlag) {
|
|
_heAuxAnimTableIndex = 0;
|
|
return;
|
|
}
|
|
|
|
// Render queued animations...
|
|
for (int i = 0; i < _heAuxAnimTableIndex; i++) {
|
|
whichActor = _heAuxAnimTable[i].actor;
|
|
if (whichActor == -1)
|
|
continue;
|
|
|
|
ActorHE *a = (ActorHE *)derefActor(whichActor, "heFlushAuxQueues");
|
|
costumeAddress = getResourceAddress(rtCostume, a->_costume);
|
|
|
|
xOffset = a->_heOffsX + a->getPos().x - pvs->xstart;
|
|
yOffset = a->_heOffsY + a->getPos().y;
|
|
|
|
if (_game.heversion >= 72) {
|
|
yOffset -= a->getElevation();
|
|
}
|
|
|
|
auxDataBlockPtr = findResourceData(MKTAG('A', 'K', 'A', 'X'), costumeAddress);
|
|
if (!auxDataBlockPtr) {
|
|
error("heFlushAuxQueue(): NO AKAX block actor %d!", whichActor);
|
|
} else {
|
|
auxDataBlockPtr -= _resourceHeaderSize;
|
|
}
|
|
|
|
auxDataPtr = findPalInPals(auxDataBlockPtr, _heAuxAnimTable[i].auxIndex);
|
|
if (!auxDataPtr) {
|
|
error("heFlushAuxQueue(): NO AUXD block actor %d!", whichActor);
|
|
} else {
|
|
auxDataPtr -= _resourceHeaderSize;
|
|
}
|
|
|
|
// Check the type of the AUXD block...
|
|
auxFrameDataPtr = findResourceData(MKTAG('A', 'X', 'F', 'D'), auxDataPtr);
|
|
if (!auxFrameDataPtr) {
|
|
warning("heFlushAuxQueue(): NO AXFD block actor %d; ignoring...", whichActor);
|
|
continue;
|
|
}
|
|
|
|
type = READ_LE_UINT16(auxFrameDataPtr);
|
|
|
|
if ((type == AKOS_AUXD_TYPE_DRLE_FRAME) || (type == AKOS_AUXD_TYPE_SRLE_FRAME)) {
|
|
x = xOffset + (int16)READ_LE_UINT16(auxFrameDataPtr + 2);
|
|
y = yOffset + (int16)READ_LE_UINT16(auxFrameDataPtr + 4);
|
|
w = READ_LE_UINT16(auxFrameDataPtr + 6);
|
|
h = READ_LE_UINT16(auxFrameDataPtr + 8);
|
|
|
|
auxFrameDataPtr += 10;
|
|
|
|
// Call the render function to go to the main buffer...
|
|
foregroundBufferPtr = pvs->getPixels(0, pvs->topline);
|
|
backgroundBufferPtr = pvs->getBackPixels(0, pvs->topline);
|
|
|
|
if (type == AKOS_AUXD_TYPE_SRLE_FRAME) {
|
|
error("heFlushAuxQueue(): Unimplemented compression type actor %d!", whichActor);
|
|
} else if (type == AKOS_AUXD_TYPE_DRLE_FRAME) {
|
|
_wiz->auxDecompDRLEImage(
|
|
(WizRawPixel *)foregroundBufferPtr, (WizRawPixel *)backgroundBufferPtr, auxFrameDataPtr,
|
|
pvs->w, pvs->h, x, y, w, h, nullptr, nullptr);
|
|
} else {
|
|
error("heFlushAuxQueue(): Unimplemented compression type actor %d!", whichActor);
|
|
}
|
|
}
|
|
|
|
// Add any update rects to the list for the final blit(s)
|
|
auxUpdateRectPtr = findResourceData(MKTAG('A', 'X', 'U', 'R'), auxDataPtr);
|
|
if (!auxUpdateRectPtr) {
|
|
continue;
|
|
}
|
|
|
|
updateRects = READ_LE_UINT16(auxUpdateRectPtr);
|
|
auxUpdateRectPtr += 2;
|
|
|
|
for (int rectCounter = 0; rectCounter < updateRects; rectCounter++) {
|
|
markRectAsDirty(
|
|
kMainVirtScreen,
|
|
xOffset + (int16)READ_LE_UINT16(auxUpdateRectPtr + 0),
|
|
xOffset + (int16)READ_LE_UINT16(auxUpdateRectPtr + 4),
|
|
yOffset + (int16)READ_LE_UINT16(auxUpdateRectPtr + 2),
|
|
yOffset + (int16)READ_LE_UINT16(auxUpdateRectPtr + 6));
|
|
|
|
auxUpdateRectPtr += 8;
|
|
}
|
|
|
|
// Set the actors erase info...
|
|
auxEraseRectPtr = findResourceData(MKTAG('A', 'X', 'E', 'R'), auxDataPtr);
|
|
if (!auxEraseRectPtr) {
|
|
continue;
|
|
}
|
|
|
|
a->_auxActor = 1;
|
|
a->_auxEraseX1 = xOffset + (int16)READ_LE_UINT16(auxEraseRectPtr + 0);
|
|
a->_auxEraseY1 = yOffset + (int16)READ_LE_UINT16(auxEraseRectPtr + 2);
|
|
a->_auxEraseX2 = xOffset + (int16)READ_LE_UINT16(auxEraseRectPtr + 4);
|
|
a->_auxEraseY2 = yOffset + (int16)READ_LE_UINT16(auxEraseRectPtr + 6);
|
|
|
|
}
|
|
|
|
_heAuxAnimTableIndex = 0;
|
|
}
|
|
|
|
void ScummEngine_v90he::heFlushAuxQueues() {
|
|
// TODO: VERIFY HE95
|
|
if (_game.heversion < 98) {
|
|
ScummEngine_v71he::heFlushAuxQueues();
|
|
return;
|
|
}
|
|
|
|
int x, y, w, h, type, whichActor;
|
|
int updateRects, xOffset, yOffset;
|
|
const byte *auxFrameDataPtr;
|
|
const byte *auxUpdateRectPtr;
|
|
WizRawPixel *foregroundBufferPtr;
|
|
WizRawPixel *backgroundBufferPtr;
|
|
const byte *auxEraseRectPtr;
|
|
const byte *colorTablePtr;
|
|
HEAnimAuxData auxInfo;
|
|
int actorBits;
|
|
const WizRawPixel *conversionTablePtr;
|
|
VirtScreen *pvs = &_virtscr[kMainVirtScreen];
|
|
|
|
if (_disableActorDrawingFlag) {
|
|
_heAuxAnimTableIndex = 0;
|
|
return;
|
|
}
|
|
|
|
// Render queued animations...
|
|
for (int i = 0; i < _heAuxAnimTableIndex; i++, heAuxReleaseAuxDataInfo(&auxInfo)) {
|
|
actorBits = 0;
|
|
|
|
whichActor = _heAuxAnimTable[i].actor;
|
|
if (whichActor == -1)
|
|
continue;
|
|
|
|
ActorHE *a = (ActorHE *)derefActor(whichActor, "heFlushAuxQueues");
|
|
|
|
if (_game.heversion > 99 && a->_hePaletteNum) {
|
|
conversionTablePtr = (WizRawPixel *)getHEPaletteSlot(a->_hePaletteNum);
|
|
} else {
|
|
conversionTablePtr = (WizRawPixel *)getHEPaletteSlot(1);
|
|
}
|
|
|
|
xOffset = a->_heOffsX + a->getPos().x - pvs->xstart;
|
|
yOffset = a->_heOffsY + a->getPos().y;
|
|
|
|
if (_game.heversion >= 72) {
|
|
yOffset -= a->getElevation();
|
|
}
|
|
|
|
// Get the frame data ptr
|
|
heAuxGetAuxDataInfo(&auxInfo, whichActor, _heAuxAnimTable[i].auxIndex);
|
|
|
|
// Check the type of the AUXD block...
|
|
auxFrameDataPtr = heAuxFindBlock(&auxInfo, MKTAG('A', 'X', 'F', 'D'));
|
|
if (!auxFrameDataPtr) {
|
|
warning("heFlushAuxQueue(): NO AXFD block actor %d; ignoring...", whichActor);
|
|
continue;
|
|
}
|
|
|
|
type = READ_LE_UINT16(auxFrameDataPtr);
|
|
|
|
if ((type == AKOS_AUXD_TYPE_DRLE_FRAME) ||
|
|
(type == AKOS_AUXD_TYPE_SRLE_FRAME) ||
|
|
(type == AKOS_AUXD_TYPE_WRLE_FRAME)) {
|
|
x = xOffset + (int16)READ_LE_UINT16(auxFrameDataPtr + 2);
|
|
y = yOffset + (int16)READ_LE_UINT16(auxFrameDataPtr + 4);
|
|
w = READ_LE_UINT16(auxFrameDataPtr + 6);
|
|
h = READ_LE_UINT16(auxFrameDataPtr + 8);
|
|
|
|
auxFrameDataPtr += 10;
|
|
|
|
// Call the render function to go to the main buffer...
|
|
foregroundBufferPtr = (WizRawPixel *)pvs->getPixels(0, pvs->topline);
|
|
backgroundBufferPtr = (WizRawPixel *)pvs->getBackPixels(0, pvs->topline);
|
|
|
|
if (type == AKOS_AUXD_TYPE_SRLE_FRAME) {
|
|
colorTablePtr = heAuxFindBlock(&auxInfo, MKTAG('C', 'L', 'R', 'S'));
|
|
if (!colorTablePtr) {
|
|
error("heFlushAuxQueue(): NO CLRS block actor %d!", whichActor);
|
|
}
|
|
|
|
colorTablePtr += _resourceHeaderSize;
|
|
|
|
if ((x != 0) || (y != 0) || (w != 640) || (h != 480)) {
|
|
error("heFlushAuxQueue(): Actor %d invalid (%d,%d)[%d,%d]", whichActor, x, y, w, h);
|
|
}
|
|
|
|
_wiz->auxDecompSRLEStream(
|
|
foregroundBufferPtr, backgroundBufferPtr, colorTablePtr,
|
|
auxFrameDataPtr, w * h,
|
|
conversionTablePtr);
|
|
} else if (type == AKOS_AUXD_TYPE_DRLE_FRAME) {
|
|
_wiz->auxDecompDRLEImage(
|
|
(WizRawPixel *)foregroundBufferPtr, (WizRawPixel *)backgroundBufferPtr, auxFrameDataPtr,
|
|
pvs->w, pvs->h, x, y, w, h, nullptr, conversionTablePtr);
|
|
} else if (AKOS_AUXD_TYPE_WRLE_FRAME == type) {
|
|
// Where is the color table?
|
|
if ((x != 0) || (w != 640)) {
|
|
error("heFlushAuxQueue(): Actor %d invalid (%d,%d)[%d,%d]", whichActor, x, y, w, h);
|
|
}
|
|
|
|
// Where is the color table?
|
|
colorTablePtr = auxFrameDataPtr;
|
|
auxFrameDataPtr += 32;
|
|
|
|
// Handle the uncompress
|
|
_wiz->auxWRLEUncompressPixelStream(
|
|
foregroundBufferPtr + (y * 640),
|
|
colorTablePtr, auxFrameDataPtr, (w * h),
|
|
conversionTablePtr);
|
|
|
|
actorBits = a->_number;
|
|
a->_needRedraw = true;
|
|
|
|
} else {
|
|
error("heFlushAuxQueue(): Unimplemented compression type actor %d!", whichActor);
|
|
}
|
|
}
|
|
|
|
// Add any update rects to the list for the final blit(s)
|
|
auxUpdateRectPtr = heAuxFindBlock(&auxInfo, MKTAG('A', 'X', 'U', 'R'));
|
|
if (!auxUpdateRectPtr) {
|
|
continue;
|
|
}
|
|
|
|
updateRects = READ_LE_UINT16(auxUpdateRectPtr);
|
|
auxUpdateRectPtr += 2;
|
|
|
|
for (int rectCounter = 0; rectCounter < updateRects; rectCounter++) {
|
|
markRectAsDirty(
|
|
kMainVirtScreen,
|
|
xOffset + (int16)READ_LE_UINT16(auxUpdateRectPtr + 0),
|
|
xOffset + (int16)READ_LE_UINT16(auxUpdateRectPtr + 4),
|
|
yOffset + (int16)READ_LE_UINT16(auxUpdateRectPtr + 2),
|
|
yOffset + (int16)READ_LE_UINT16(auxUpdateRectPtr + 6),
|
|
actorBits);
|
|
|
|
auxUpdateRectPtr += 8;
|
|
}
|
|
|
|
// Set the actors erase info...
|
|
auxEraseRectPtr = heAuxFindBlock(&auxInfo, MKTAG('A', 'X', 'E', 'R'));
|
|
if (!auxEraseRectPtr) {
|
|
continue;
|
|
}
|
|
|
|
a->_auxActor = 1;
|
|
a->_auxEraseX1 = xOffset + (int16)READ_LE_UINT16(auxEraseRectPtr + 0);
|
|
a->_auxEraseY1 = yOffset + (int16)READ_LE_UINT16(auxEraseRectPtr + 2);
|
|
a->_auxEraseX2 = xOffset + (int16)READ_LE_UINT16(auxEraseRectPtr + 4);
|
|
a->_auxEraseY2 = yOffset + (int16)READ_LE_UINT16(auxEraseRectPtr + 6);
|
|
}
|
|
|
|
_heAuxAnimTableIndex = 0;
|
|
}
|
|
|
|
const byte *ScummEngine_v90he::heAuxFindBlock(HEAnimAuxData *auxInfoPtr, int32 id) {
|
|
const byte *resultPtr;
|
|
|
|
// Search the external block thing
|
|
if (auxInfoPtr->externalDataPtr) {
|
|
resultPtr = findResourceData(id, auxInfoPtr->externalDataPtr);
|
|
if (resultPtr)
|
|
return resultPtr;
|
|
}
|
|
|
|
// Search the current block first
|
|
resultPtr = findResourceData(id, auxInfoPtr->auxDataBlock);
|
|
if (resultPtr)
|
|
return resultPtr;
|
|
|
|
// If the alt search isn't the same search there...
|
|
if (auxInfoPtr->auxDataBlock == auxInfoPtr->auxDefaultSearchBlock) {
|
|
return resultPtr;
|
|
}
|
|
|
|
// Search the default block
|
|
return findResourceData(id, auxInfoPtr->auxDefaultSearchBlock);
|
|
}
|
|
|
|
void ScummEngine_v90he::heAuxReleaseAuxDataInfo(HEAnimAuxData *auxInfoPtr) {
|
|
auxInfoPtr->auxDefaultSearchBlock = nullptr;
|
|
auxInfoPtr->auxDataBlock = nullptr;
|
|
|
|
if (auxInfoPtr->externalDataPtr) {
|
|
free(auxInfoPtr->externalDataPtr);
|
|
auxInfoPtr->externalDataPtr = nullptr;
|
|
}
|
|
}
|
|
|
|
bool ScummEngine_v90he::heAuxProcessFileRelativeBlock(HEAnimAuxData *auxInfoPtr, const byte *dataBlockPtr) {
|
|
error("heAuxProcessFileRelativeBlock(): This looks like a development path! If you end up here, please report it in our bug tracker!");
|
|
}
|
|
|
|
bool ScummEngine_v90he::heAuxProcessDisplacedBlock(HEAnimAuxData *auxInfoPtr, const byte *displacedBlockPtr) {
|
|
error("heAuxProcessDisplacedBlock(): This looks like a development path! If you end up here, please report it in our bug tracker!");
|
|
}
|
|
|
|
void ScummEngine_v90he::heAuxGetAuxDataInfo(HEAnimAuxData *auxInfoPtr, int whichActor, int auxIndex) {
|
|
const byte *fileRelativeDataBlockPtr;
|
|
const byte *displacedBlockPtr;
|
|
const byte *auxDataBlockPtr;
|
|
const byte *auxDataPtr;
|
|
byte *costumeAddress;
|
|
|
|
// Store off some of the passed in info
|
|
auxInfoPtr->externalDataPtr = nullptr;
|
|
auxInfoPtr->actor = whichActor;
|
|
|
|
// Get the interesting data
|
|
ActorHE *a = (ActorHE *)derefActor(whichActor, "heAuxGetAuxDataInfo");
|
|
costumeAddress = getResourceAddress(rtCostume, a->_costume);
|
|
|
|
auxDataBlockPtr = findResourceData(MKTAG('A', 'K', 'A', 'X'), costumeAddress);
|
|
if (!auxDataBlockPtr) {
|
|
error("heAuxGetAuxDataInfo(): NO AKAX block actor %d!", whichActor);
|
|
} else {
|
|
auxDataBlockPtr -= _resourceHeaderSize;
|
|
}
|
|
|
|
auxDataPtr = findPalInPals(auxDataBlockPtr, auxIndex);
|
|
if (!auxDataPtr) {
|
|
error("heAuxGetAuxDataInfo(): NO AUXD block actor %d!", whichActor);
|
|
} else {
|
|
auxDataPtr -= _resourceHeaderSize;
|
|
}
|
|
|
|
// Check for other outside block types
|
|
fileRelativeDataBlockPtr = findResourceData(MKTAG('F', 'R', 'E', 'L'), auxDataPtr);
|
|
|
|
if (fileRelativeDataBlockPtr) {
|
|
fileRelativeDataBlockPtr -= _resourceHeaderSize;
|
|
if (!heAuxProcessFileRelativeBlock(auxInfoPtr, fileRelativeDataBlockPtr)) {
|
|
error("heAuxGetAuxDataInfo(): Actor %d aux %d failed", whichActor, auxIndex);
|
|
}
|
|
}
|
|
|
|
// This is where the DISP block will be processed!
|
|
displacedBlockPtr = findResourceData(MKTAG('D', 'I', 'S', 'P'), auxDataPtr) ;
|
|
|
|
if (displacedBlockPtr) {
|
|
displacedBlockPtr -= _resourceHeaderSize;
|
|
if (!heAuxProcessDisplacedBlock(auxInfoPtr, displacedBlockPtr)) {
|
|
error("heAuxGetAuxDataInfo(): Actor %d aux %d failed", whichActor, auxIndex);
|
|
}
|
|
}
|
|
|
|
// Fill in the data result
|
|
auxInfoPtr->auxDefaultSearchBlock = costumeAddress;
|
|
auxInfoPtr->auxDataBlock = auxDataPtr;
|
|
}
|
|
|
|
void ScummEngine_v71he::heQueueEraseAuxActor(ActorHE *a) {
|
|
if (_heAuxEraseActorIndex >= ARRAYSIZE(_heAuxEraseActorTable)) {
|
|
warning("heQueueEraseAuxActor(): Queue full, ignoring...");
|
|
return;
|
|
}
|
|
|
|
if (a->_auxActor) {
|
|
_heAuxEraseActorTable[_heAuxEraseActorIndex].actor = a->_number;
|
|
_heAuxEraseActorTable[_heAuxEraseActorIndex].x1 = a->_auxEraseX1;
|
|
_heAuxEraseActorTable[_heAuxEraseActorIndex].y1 = a->_auxEraseY1;
|
|
_heAuxEraseActorTable[_heAuxEraseActorIndex].x2 = a->_auxEraseX2;
|
|
_heAuxEraseActorTable[_heAuxEraseActorIndex].y2 = a->_auxEraseY2;
|
|
_heAuxEraseActorIndex++;
|
|
}
|
|
}
|
|
|
|
void ScummEngine_v71he::heQueueAnimAuxFrame(int actor, int auxIndex) {
|
|
if (_heAuxAnimTableIndex >= ARRAYSIZE(_heAuxAnimTable)) {
|
|
warning("HEQueueAnimAuxFrame(): Queue full, ignoring...");
|
|
return;
|
|
}
|
|
|
|
_heAuxAnimTable[_heAuxAnimTableIndex].actor = actor;
|
|
_heAuxAnimTable[_heAuxAnimTableIndex].auxIndex = auxIndex;
|
|
_heAuxAnimTableIndex++;
|
|
}
|
|
|
|
#endif
|
|
|
|
void Actor_v0::animateActor(int anim) {
|
|
int dir = -1;
|
|
|
|
switch (anim) {
|
|
case 0x00:
|
|
case 0x04:
|
|
dir = 0;
|
|
break;
|
|
|
|
case 0x01:
|
|
case 0x05:
|
|
dir = 1;
|
|
break;
|
|
|
|
case 0x02:
|
|
case 0x06:
|
|
dir = 2;
|
|
break;
|
|
|
|
case 0x03:
|
|
case 0x07:
|
|
dir = 3;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (isInCurrentRoom()) {
|
|
|
|
_costCommandNew = anim;
|
|
_vm->_costumeLoader->costumeDecodeData(this, 0, 0);
|
|
|
|
if (dir == -1)
|
|
return;
|
|
|
|
_facing = normalizeAngle(0, oldDirToNewDir(dir));
|
|
|
|
} else {
|
|
|
|
if (anim >= 4 && anim <= 7)
|
|
_facing = normalizeAngle(0, oldDirToNewDir(dir));
|
|
}
|
|
}
|
|
|
|
byte Actor_v0::updateWalkbox() {
|
|
if (_vm->checkXYInBoxBounds(_walkbox, _pos.x, _pos.y))
|
|
return 0;
|
|
|
|
int numBoxes = _vm->getNumBoxes() - 1;
|
|
for (int i = 0; i <= numBoxes; i++) {
|
|
if (_vm->checkXYInBoxBounds(i, _pos.x, _pos.y) == true) {
|
|
if (_walkdata.curbox == i) {
|
|
setBox(i);
|
|
directionUpdate();
|
|
|
|
_newWalkBoxEntered = true;
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return kInvalidBox;
|
|
}
|
|
|
|
void Actor_v0::directionUpdate() {
|
|
|
|
int nextFacing = updateActorDirection(true);
|
|
if (_facing != nextFacing) {
|
|
// 2A89
|
|
setDirection(nextFacing);
|
|
|
|
// Still need to turn?
|
|
if (_facing != _targetFacing) {
|
|
_moving |= 0x80;
|
|
return;
|
|
}
|
|
}
|
|
|
|
_moving &= ~0x80;
|
|
}
|
|
|
|
void Actor_v0::setActorToTempPosition() {
|
|
_tmp_Pos = _pos;
|
|
_pos = _tmp_NewPos;
|
|
_tmp_WalkBox = _walkbox;
|
|
_tmp_NewWalkBoxEntered = _newWalkBoxEntered;
|
|
}
|
|
|
|
void Actor_v0::setActorToOriginalPosition() {
|
|
_pos = _tmp_Pos;
|
|
_tmp_NewPos = _tmp_Pos;
|
|
_walkbox = _tmp_WalkBox;
|
|
_newWalkBoxEntered = _tmp_NewWalkBoxEntered;
|
|
}
|
|
|
|
void Actor_v0::actorSetWalkTo() {
|
|
|
|
if (_newWalkBoxEntered == false)
|
|
return;
|
|
|
|
_newWalkBoxEntered = false;
|
|
|
|
int nextBox = ((ScummEngine_v0 *)_vm)->walkboxFindTarget(this, _walkdata.destbox, _walkdata.dest);
|
|
if (nextBox != kInvalidBox) {
|
|
_walkdata.curbox = nextBox;
|
|
}
|
|
}
|
|
|
|
void ScummEngine_v60he::setActorClippingRect(int actor, int x1, int y1, int x2, int y2) {
|
|
if (actor == -1) {
|
|
_defaultActorClipping.left = x1;
|
|
_defaultActorClipping.top = y1;
|
|
_defaultActorClipping.right = x2;
|
|
_defaultActorClipping.bottom = y2;
|
|
} else {
|
|
ActorHE *a = (ActorHE *)derefActor(actor, "setActorClippingRect");
|
|
if (x1 == -1 && y1 == -1 && x2 == -1 && y2 == -1) {
|
|
a->_clipOverride.left = _defaultActorClipping.left;
|
|
a->_clipOverride.top = _defaultActorClipping.top;
|
|
a->_clipOverride.right = _defaultActorClipping.right;
|
|
a->_clipOverride.bottom = _defaultActorClipping.bottom;
|
|
} else {
|
|
a->_clipOverride.left = x1;
|
|
a->_clipOverride.top = y1;
|
|
a->_clipOverride.right = x2;
|
|
a->_clipOverride.bottom = y2;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor_v0::saveLoadWithSerializer(Common::Serializer &s) {
|
|
Actor::saveLoadWithSerializer(s);
|
|
|
|
s.syncAsByte(_costCommand, VER(84));
|
|
s.skip(1, VER(84), VER(89)); // _costFrame
|
|
s.syncAsByte(_miscflags, VER(84));
|
|
s.syncAsByte(_speaking, VER(84));
|
|
s.skip(1, VER(84), VER(89)); // _speakingPrev
|
|
s.skip(1, VER(89), VER(89)); // _limbTemp
|
|
s.syncAsByte(_animFrameRepeat, VER(89));
|
|
s.syncArray(_limbFrameRepeatNew, 8, Common::Serializer::SByte, VER(89));
|
|
s.syncArray(_limbFrameRepeat, 8, Common::Serializer::SByte, VER(90));
|
|
s.syncAsSint16LE(_CurrentWalkTo.x, VER(97));
|
|
s.syncAsSint16LE(_CurrentWalkTo.y, VER(97));
|
|
s.syncAsSint16LE(_NewWalkTo.x, VER(97));
|
|
s.syncAsSint16LE(_NewWalkTo.y, VER(97));
|
|
s.syncAsSByte(_walkCountModulo, VER(97));
|
|
s.syncAsByte(_newWalkBoxEntered, VER(97));
|
|
s.syncAsByte(_walkDirX, VER(97));
|
|
s.syncAsByte(_walkDirY, VER(97));
|
|
s.syncAsByte(_walkYCountGreaterThanXCount, VER(97));
|
|
s.syncAsByte(_walkXCount, VER(97));
|
|
s.syncAsByte(_walkXCountInc, VER(97));
|
|
s.syncAsByte(_walkYCount, VER(97));
|
|
s.syncAsByte(_walkYCountInc, VER(97));
|
|
s.syncAsByte(_walkMaxXYCountInc, VER(97));
|
|
|
|
s.syncBytes(_walkboxQueue, 16, VER(98));
|
|
s.syncAsByte(_walkboxQueueIndex, VER(98));
|
|
|
|
// When loading, we need to ensure the limbs are restarted
|
|
if (s.isLoading()) {
|
|
|
|
// valid costume command?
|
|
if (_costCommand != 0xFF) {
|
|
|
|
// Do we have a walkbox queue?
|
|
if (_walkboxQueueIndex < 1) {
|
|
_costCommand = 0xFF;
|
|
|
|
// Standing Still
|
|
setDirection(_facing);
|
|
speakCheck();
|
|
|
|
} else {
|
|
// Force limb direction update
|
|
_facing = 0;
|
|
directionUpdate();
|
|
|
|
// Begin walking
|
|
animateActor(newDirToOldDir(_facing));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Actor::saveLoadWithSerializer(Common::Serializer &s) {
|
|
if (s.isLoading()) {
|
|
// Not all actor data is saved; so when loading, we first reset
|
|
// the actor, to ensure completely reproducible behavior (else,
|
|
// some not saved value in the actor class can cause odd things)
|
|
initActor(-1);
|
|
}
|
|
|
|
s.syncAsSint16LE(_pos.x, VER(8));
|
|
s.syncAsSint16LE(_pos.y, VER(8));
|
|
s.syncAsSint16LE(_heOffsX, VER(32));
|
|
s.syncAsSint16LE(_heOffsY, VER(32));
|
|
s.syncAsSint16LE(_top, VER(8));
|
|
s.syncAsSint16LE(_bottom, VER(8));
|
|
s.syncAsSint16LE(_elevation, VER(8));
|
|
s.syncAsUint16LE(_width, VER(8));
|
|
s.syncAsUint16LE(_facing, VER(8));
|
|
s.syncAsUint16LE(_costume, VER(8));
|
|
s.syncAsByte(_room, VER(8));
|
|
s.syncAsByte(_talkColor, VER(8));
|
|
s.syncAsSint16LE(_talkFrequency, VER(16));
|
|
s.syncAsSint16LE(_talkPan, VER(24));
|
|
s.syncAsSint16LE(_talkVolume, VER(29));
|
|
s.syncAsUint16LE(_boxscale, VER(34));
|
|
s.syncAsByte(_scalex, VER(8));
|
|
s.syncAsByte(_scaley, VER(8));
|
|
s.syncAsByte(_charset, VER(8));
|
|
|
|
// Actor sound grew from 8 to 32 bytes and switched to uint16 in HE games
|
|
s.syncArray(_sound, 8, Common::Serializer::Byte, VER(8), VER(36));
|
|
s.syncArray(_sound, 32, Common::Serializer::Byte, VER(37), VER(61));
|
|
s.syncArray(_sound, 32, Common::Serializer::Uint16LE, VER(62));
|
|
|
|
// Actor animVariable grew from 8 to 27
|
|
s.syncArray(_animVariable, 8, Common::Serializer::Uint16LE, VER(8), VER(40));
|
|
s.syncArray(_animVariable, 27, Common::Serializer::Uint16LE, VER(41));
|
|
|
|
s.syncAsUint16LE(_targetFacing, VER(8));
|
|
s.syncAsByte(_moving, VER(8));
|
|
s.syncAsByte(_ignoreBoxes, VER(8));
|
|
s.syncAsByte(_forceClip, VER(8));
|
|
s.syncAsByte(_initFrame, VER(8));
|
|
s.syncAsByte(_walkFrame, VER(8));
|
|
s.syncAsByte(_standFrame, VER(8));
|
|
s.syncAsByte(_talkStartFrame, VER(8));
|
|
s.syncAsByte(_talkStopFrame, VER(8));
|
|
s.syncAsUint16LE(_speedx, VER(8));
|
|
s.syncAsUint16LE(_speedy, VER(8));
|
|
s.syncAsUint16LE(_cost.animCounter, VER(8));
|
|
s.syncAsByte(_cost.soundCounter, VER(8));
|
|
s.syncAsByte(_drawToBackBuf, VER(32));
|
|
s.syncAsByte(_flip, VER(32));
|
|
s.syncAsByte(_heSkipLimbs, VER(32));
|
|
|
|
// Actor palette grew from 64 to 256 bytes and switched to uint16 in HE games
|
|
s.syncArray(_palette, 64, Common::Serializer::Byte, VER(8), VER(9));
|
|
s.syncArray(_palette, 256, Common::Serializer::Byte, VER(10), VER(79));
|
|
s.syncArray(_palette, 256, Common::Serializer::Uint16LE, VER(80));
|
|
|
|
s.skip(1, VER(8), VER(9)); // _mask
|
|
s.syncAsByte(_shadowMode, VER(8));
|
|
s.syncAsByte(_visible, VER(8));
|
|
s.syncAsByte(_frame, VER(8));
|
|
s.syncAsByte(_animSpeed, VER(8));
|
|
s.syncAsByte(_animProgress, VER(8));
|
|
s.syncAsByte(_walkbox, VER(8));
|
|
s.syncAsByte(_needRedraw, VER(8));
|
|
s.syncAsByte(_needBgReset, VER(8));
|
|
s.syncAsByte(_costumeNeedsInit, VER(8));
|
|
s.syncAsUint32LE(_heCondMask, VER(38));
|
|
s.syncAsUint32LE(_hePaletteNum, VER(59));
|
|
s.syncAsUint32LE(_heShadow, VER(59));
|
|
|
|
s.syncAsSint16LE(_talkPosY, VER(8));
|
|
s.syncAsSint16LE(_talkPosX, VER(8));
|
|
s.syncAsByte(_ignoreTurns, VER(8));
|
|
|
|
// Actor layer switched to int32 in HE games
|
|
s.syncAsByte(_layer, VER(8), VER(57));
|
|
s.syncAsSint32LE(_layer, VER(58));
|
|
|
|
s.syncAsUint16LE(_talkScript, VER(8));
|
|
s.syncAsUint16LE(_walkScript, VER(8));
|
|
|
|
s.syncAsSint16LE(_walkdata.dest.x, VER(8));
|
|
s.syncAsSint16LE(_walkdata.dest.y, VER(8));
|
|
s.syncAsByte(_walkdata.destbox, VER(8));
|
|
s.syncAsUint16LE(_walkdata.destdir, VER(8));
|
|
s.syncAsByte(_walkdata.curbox, VER(8));
|
|
s.syncAsSint16LE(_walkdata.cur.x, VER(8));
|
|
s.syncAsSint16LE(_walkdata.cur.y, VER(8));
|
|
s.syncAsSint16LE(_walkdata.next.x, VER(8));
|
|
s.syncAsSint16LE(_walkdata.next.y, VER(8));
|
|
s.syncAsSint32LE(_walkdata.deltaXFactor, VER(8));
|
|
s.syncAsSint32LE(_walkdata.deltaYFactor, VER(8));
|
|
s.syncAsUint16LE(_walkdata.xfrac, VER(8));
|
|
s.syncAsUint16LE(_walkdata.yfrac, VER(8));
|
|
s.syncAsSint16LE(_walkdata.facing, VER(111));
|
|
|
|
s.syncAsUint16LE(_walkdata.point3.x, VER(42));
|
|
s.syncAsUint16LE(_walkdata.point3.y, VER(42));
|
|
|
|
s.syncBytes(_cost.animType, 16, VER(8));
|
|
s.syncAsUint16LE(_cost.stopped, VER(8));
|
|
s.syncArray(_cost.curpos, 16, Common::Serializer::Uint16LE, VER(8));
|
|
s.syncArray(_cost.start, 16, Common::Serializer::Uint16LE, VER(8));
|
|
s.syncArray(_cost.end, 16, Common::Serializer::Uint16LE, VER(8));
|
|
s.syncArray(_cost.frame, 16, Common::Serializer::Uint16LE, VER(8));
|
|
|
|
s.syncArray(_cost.heJumpOffsetTable, 16, Common::Serializer::Uint16LE, VER(65));
|
|
s.syncArray(_cost.heJumpCountTable, 16, Common::Serializer::Uint16LE, VER(65));
|
|
s.syncArray(_cost.heCondMaskTable, 16, Common::Serializer::Uint32LE, VER(65));
|
|
|
|
if (s.isLoading() && _vm->_game.version <= 2 && s.getVersion() < VER(70)) {
|
|
_pos.x >>= V12_X_SHIFT;
|
|
_pos.y >>= V12_Y_SHIFT;
|
|
|
|
_speedx >>= V12_X_SHIFT;
|
|
_speedy >>= V12_Y_SHIFT;
|
|
_elevation >>= V12_Y_SHIFT;
|
|
|
|
if (_walkdata.dest.x != -1) {
|
|
_walkdata.dest.x >>= V12_X_SHIFT;
|
|
_walkdata.dest.y >>= V12_Y_SHIFT;
|
|
}
|
|
|
|
_walkdata.cur.x >>= V12_X_SHIFT;
|
|
_walkdata.cur.y >>= V12_Y_SHIFT;
|
|
|
|
_walkdata.next.x >>= V12_X_SHIFT;
|
|
_walkdata.next.y >>= V12_Y_SHIFT;
|
|
|
|
if (_walkdata.point3.x != 32000) {
|
|
_walkdata.point3.x >>= V12_X_SHIFT;
|
|
_walkdata.point3.y >>= V12_Y_SHIFT;
|
|
}
|
|
|
|
setDirection(_facing);
|
|
}
|
|
|
|
if (s.isLoading() && _vm->_game.version > 0 && !(_vm->_game.features & GF_NEW_COSTUMES) && s.getVersion() < VER(105)) {
|
|
// For older saves, we can't reconstruct the frame's direction if it is different from the actor
|
|
// direction, this is the best we can do. However, it seems to be relevant only for very rare
|
|
// edge cases, anyway...
|
|
for (int i = 0; i < 16; ++i) {
|
|
if (_cost.frame[i] != 0xffff)
|
|
_cost.frame[i] = (_cost.frame[i] << 2) | newDirToOldDir(_facing);
|
|
}
|
|
}
|
|
|
|
// WORKAROUND: Post-load actor palette fixes for games that were saved with a different render mode
|
|
// (concerns INDY3, LOOM and MI1EGA). The original interpreter does not fix this, savegames from
|
|
// different videomodes will cause glitches there.
|
|
if (s.isLoading() && (_vm->_game.version == 3 || _vm->_game.id == GID_MONKEY_EGA) && _vm->_game.platform == Common::kPlatformDOS) {
|
|
// Loom is not really much of a problem here, since it has extensive scripted post-load
|
|
// treatment in ScummEngine_v3::scummLoop_handleSaveLoad(). But there are situations
|
|
// where it won't be triggered (basically savegames from places where the original does
|
|
// not allow saving). Indy3 is more dependant on this than Loom, since it does have much
|
|
// less scripted post-load magic of its own. Monkey Island always needs this, since V4+
|
|
// games don't do scripted loading of savegames (scripted post-load things) at all.
|
|
bool cga = (_vm->_renderMode == Common::kRenderCGA);
|
|
if (cga && _vm->_game.id == GID_MONKEY_EGA && _palette[6] == 0xFF && _palette[7] == 0xFF) {
|
|
_palette[6] = 5;
|
|
_palette[7] = 15;
|
|
} else if ((cga && _palette[6] == 6 && _palette[7] == 7) || (!cga && _palette[6] == 5 && _palette[7] == 15)) {
|
|
_palette[6] ^= 3;
|
|
_palette[7] ^= 8;
|
|
}
|
|
// Extra fix for Bobbin in his normal costume.
|
|
if (_vm->_game.id == GID_LOOM && _number == 1 && ((cga && _palette[8] == 8) || (!cga && _palette[8] == 0)))
|
|
_palette[8] ^= 8;
|
|
}
|
|
}
|
|
|
|
void Actor_v3::saveLoadWithSerializer(Common::Serializer &s) {
|
|
Actor::saveLoadWithSerializer(s);
|
|
|
|
int rev = (_vm->_game.version == 3 ? 101 : 102);
|
|
|
|
if (s.isLoading() && s.getVersion() < VER(rev)) {
|
|
int diffX = _walkdata.next.x - _pos.x;
|
|
int diffY = _walkdata.next.y - _pos.y;
|
|
|
|
if (_vm->_game.version < 3) {
|
|
_stepThreshold = MAX(ABS(diffX), ABS(diffY));
|
|
_walkdata.deltaXFactor = _walkdata.deltaYFactor = 1;
|
|
} else {
|
|
_stepX = ((ABS(diffY) / (int)_speedy) >> 1) >(ABS(diffX) / (int)_speedx) ? _speedy + 1 : _speedx;
|
|
_stepThreshold = MAX(ABS(diffY) / _speedy, ABS(diffX) / _stepX);
|
|
_walkdata.deltaXFactor = (int32)_stepX;
|
|
_walkdata.deltaYFactor = (int32)_speedy;
|
|
}
|
|
|
|
if (diffX < 0)
|
|
_walkdata.deltaXFactor = -_walkdata.deltaXFactor;
|
|
if (diffY < 0)
|
|
_walkdata.deltaYFactor = -_walkdata.deltaYFactor;
|
|
_walkdata.xfrac = _walkdata.xAdd = _walkdata.deltaXFactor ? diffX / _walkdata.deltaXFactor : 0;
|
|
_walkdata.yfrac = _walkdata.yAdd = _walkdata.deltaYFactor ? diffY / _walkdata.deltaYFactor : 0;
|
|
|
|
} else {
|
|
s.syncAsUint16LE(_walkdata.xAdd, VER(rev));
|
|
s.syncAsUint16LE(_walkdata.yAdd, VER(rev));
|
|
s.syncAsUint16LE(_stepX, VER(rev));
|
|
s.syncAsUint16LE(_stepThreshold, VER(rev));
|
|
}
|
|
}
|
|
|
|
} // End of namespace Scumm
|