/* ScummVM - Scumm Interpreter * Copyright (C) 2001 Ludvig Strigeus * Copyright (C) 2001-2006 The ScummVM project * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * */ #include "common/stdafx.h" #include "common/system.h" // for setFocusRectangle/clearFocusRectangle #include "scumm/scumm.h" #include "scumm/actor.h" #include "scumm/akos.h" #include "scumm/boxes.h" #include "scumm/charset.h" #include "scumm/costume.h" #include "scumm/intern.h" #include "scumm/he/intern_he.h" #include "scumm/object.h" #include "scumm/resource.h" #include "scumm/saveload.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; ScummEngine *Actor::_vm = 0; void Actor::initActorClass(ScummEngine *scumm) { _vm = scumm; if (_vm->_game.features & GF_SMALL_HEADER) { kInvalidBox = 255; } } Actor::Actor() { assert(_vm != 0); _number = 0; initActor(-1); } void Actor::initActor(int mode) { if (mode == -1) { _offsX = _offsY = 0; _top = _bottom = 0; _needRedraw = _needBgReset = _costumeNeedsInit = _visible = false; _flip = false; _speedx = 8; _speedy = 2; _frame = 0; _walkbox = 0; _animProgress = 0; _heSkipLimbs = false; _drawToBackBuf = false; memset(_animVariable, 0, sizeof(_animVariable)); memset(_palette, 0, sizeof(_palette)); memset(_sound, 0, sizeof(_sound)); memset(&_cost, 0, sizeof(CostumeData)); memset(&_walkdata, 0, sizeof(ActorWalkData)); _walkdata.point3.x = 32000; _walkScript = 0; memset(_heTalkQueue, 0, sizeof(_heTalkQueue)); _miscflags = 0; mode = 1; } if (mode == 1) { _costume = 0; _room = 0; _pos.x = 0; _pos.y = 0; _facing = 180; _heCondMask = 1; _heNoTalkAnimation = 0; if (_vm->_game.version >= 7) _visible = false; _heSkipLimbs = false; } else if (mode == 2) { _facing = 180; _heCondMask = 1; _heSkipLimbs = false; } _elevation = 0; _width = 24; _talkColor = 15; _talkPosX = 0; _talkPosY = -80; _boxscale = _scaley = _scalex = 0xFF; _charset = 0; memset(_sound, 0, sizeof(_sound)); _targetFacing = _facing; stopActorMoving(); _heXmapNum = 0; _shadowMode = 0; _layer = 0; setActorWalkSpeed(8, 2); _animSpeed = 0; if (_vm->_game.version >= 6) _animProgress = 0; _ignoreBoxes = false; _forceClip = (_vm->_game.version >= 7) ? 100 : 0; _ignoreTurns = false; if (_vm->_game.heversion >= 61) _flip = 0; _talkFrequency = 256; _talkPan = 64; _talkVolume = 127; if (_vm->_game.version <= 2) { _initFrame = 2; _walkFrame = 0; _standFrame = 1; _talkStartFrame = 5; _talkStopFrame = 4; } else { _initFrame = 1; _walkFrame = 2; _standFrame = 3; _talkStartFrame = 4; _talkStopFrame = 5; } _heTalking = false; _walkScript = 0; _talkScript = 0; _clipOverride = _vm->_actorClipOverride; _auxBlock.reset(); _hePaletteNum = 0; _heFlags = 0; _vm->_classData[_number] = (_vm->_game.version >= 7) ? _vm->_classData[0] : 0; } void Actor::stopActorMoving() { if (_walkScript) _vm->stopScript(_walkScript); _moving = 0; } void Actor::setActorWalkSpeed(uint newSpeedX, uint newSpeedY) { if (newSpeedX == _speedx && newSpeedY == _speedy) return; _speedx = newSpeedX; _speedy = newSpeedY; if (_moving) { calcMovementFactor(_walkdata.next); } } int ScummEngine::getAngleFromPos(int x, int y) const { if (_game.id == GID_DIG || _game.id == GID_CMI) { double temp = atan2((double)x, (double)-y); return normalizeAngle((int)(temp * 180 / PI)); } else { 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) { Common::Point _actorPos(_pos); int diffX, diffY; int32 deltaXFactor, deltaYFactor; if (_actorPos == next) return 0; diffX = next.x - _actorPos.x; diffY = next.y - _actorPos.y; deltaYFactor = _speedy << 16; if (diffY < 0) deltaYFactor = -deltaYFactor; deltaXFactor = deltaYFactor * diffX; if (diffY != 0) { deltaXFactor /= diffY; } else { deltaYFactor = 0; } if ((uint) ABS((int)(deltaXFactor >> 16)) > _speedx) { deltaXFactor = _speedx << 16; if (diffX < 0) deltaXFactor = -deltaXFactor; deltaYFactor = deltaXFactor * diffY; if (diffX != 0) { deltaYFactor /= diffX; } else { deltaXFactor = 0; } } _walkdata.cur = _actorPos; _walkdata.next = next; _walkdata.deltaXFactor = deltaXFactor; _walkdata.deltaYFactor = deltaYFactor; _walkdata.xfrac = 0; _walkdata.yfrac = 0; _targetFacing = _vm->getAngleFromPos(deltaXFactor, deltaYFactor); return actorWalkStep(); } int Actor::remapDirection(int dir, bool is_walking) { int specdir; byte flags; bool flipX; bool flipY; // FIXME - It seems that at least in The Dig the original code does // check _ignoreBoxes here. However, it breaks some animations in Loom, // causing Bobbin to face towards the camera instead of away from it // in some places: After the tree has been destroyed by lightning, and // when entering the dark tunnels beyond the dragon's lair at the very // least. Possibly other places as well. // // The Dig also checks if the actor is in the current room, but that's // not necessary here because we never call the function unless the // actor is in the current room anyway. if (!_ignoreBoxes || _vm->_game.id == GID_LOOM) { 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; } } // OR 1024 in to signal direction interpolation should be done return normalizeAngle(dir) | 1024; } int Actor::updateActorDirection(bool is_walking) { int from; bool dirType = false; int dir; bool shouldInterpolate; if ((_vm->_game.version == 6) && _ignoreTurns) return _facing; dirType = (_vm->_game.version >= 7) ? _vm->_costumeLoader->hasManyDirections(_costume) : false; from = toSimpleDir(dirType, _facing); dir = remapDirection(_targetFacing, is_walking); if (_vm->_game.version >= 7) // Direction interpolation interfers with walk scripts in Dig; they perform // (much better) interpolation themselves. shouldInterpolate = false; else shouldInterpolate = (dir & 1024) ? true : false; dir &= 1023; if (shouldInterpolate) { int to = toSimpleDir(dirType, dir); 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; } dir = fromSimpleDir(dirType, (to + num) % num); } return dir; } void Actor::setBox(int box) { _walkbox = box; setupActorScale(); } int Actor::actorWalkStep() { int tmpX, tmpY; Common::Point _actorPos; int distX, distY; int nextFacing; _needRedraw = true; nextFacing = updateActorDirection(true); if (!(_moving & MF_IN_LEG) || _facing != nextFacing) { if (_walkFrame != _frame || _facing != nextFacing) { startWalkAnim(1, nextFacing); } _moving |= MF_IN_LEG; } _actorPos = _pos; if (_walkbox != _walkdata.curbox && _vm->checkXYInBoxBounds(_walkdata.curbox, _actorPos.x, _actorPos.y)) { setBox(_walkdata.curbox); } distX = ABS(_walkdata.next.x - _walkdata.cur.x); distY = ABS(_walkdata.next.y - _walkdata.cur.y); if (ABS(_actorPos.x - _walkdata.cur.x) >= distX && ABS(_actorPos.y - _walkdata.cur.y) >= distY) { _moving &= ~MF_IN_LEG; return 0; } tmpX = (_actorPos.x << 16) + _walkdata.xfrac + (_walkdata.deltaXFactor >> 8) * _scalex; _walkdata.xfrac = (uint16)tmpX; _actorPos.x = (tmpX >> 16); tmpY = (_actorPos.y << 16) + _walkdata.yfrac + (_walkdata.deltaYFactor >> 8) * _scaley; _walkdata.yfrac = (uint16)tmpY; _actorPos.y = (tmpY >> 16); if (ABS(_actorPos.x - _walkdata.cur.x) > distX) { _actorPos.x = _walkdata.next.x; } if (ABS(_actorPos.y - _walkdata.cur.y) > distY) { _actorPos.y = _walkdata.next.y; } _pos = _actorPos; if (_vm->_game.version >= 4 && _vm->_game.version <= 6 && _pos == _walkdata.next) { _moving &= ~MF_IN_LEG; return 0; } return 1; } void Actor::setupActorScale() { if (_vm->_game.features & GF_NO_SCALING) { _scalex = 0xFF; _scaley = 0xFF; return; } 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; } void Actor::startAnimActor(int f) { if (_vm->_game.version >= 7 && !((_vm->_game.id == GID_FT) && (_vm->_game.features & GF_DEMO) && (_vm->_game.platform == Common::kPlatformPC))) { 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; } if (_costume != 0) { _animProgress = 0; _needRedraw = true; if (f == _initFrame) _cost.reset(); _vm->_costumeLoader->costumeDecodeData(this, f, (uint) - 1); _frame = f; } } else { 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; } assert(f != 0x3E); if (isInCurrentRoom() && _costume != 0) { _animProgress = 0; _cost.animCounter = 0; _needRedraw = true; // 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 #771508 if (_vm->_game.version >= 3 && f == _initFrame) { _cost.reset(); _auxBlock.reset(); } _vm->_costumeLoader->costumeDecodeData(this, f, (uint) - 1); _frame = f; } } } void Actor::animateActor(int anim) { int cmd, dir; if (_vm->_game.version >= 7 && !((_vm->_game.id == GID_FT) && (_vm->_game.features & GF_DEMO) && (_vm->_game.platform == Common::kPlatformPC))) { if (anim == 0xFF) anim = 2000; cmd = anim / 1000; dir = anim % 1000; } else { cmd = anim / 4; dir = oldDirToNewDir(anim % 4); // Convert into old cmd code cmd = 0x3F - cmd + 2; } switch (cmd) { case 2: // stop walking startAnimActor(_standFrame); stopActorMoving(); break; case 3: // change direction immediatly _moving &= ~MF_TURN; setDirection(dir); break; case 4: // turn to new direction turnToDirection(dir); break; default: if (_vm->_game.version <= 2) startAnimActor(anim / 4); else startAnimActor(anim); } } void Actor::setDirection(int direction) { uint aMask; int i; uint16 vald; // Do nothing if actor is already facing in the given direction if (_facing == direction) return; // Normalize the angle _facing = normalizeAngle(direction); // If there is no costume set for this actor, we are finished if (_costume == 0) 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; _vm->_costumeLoader->costumeDecodeData(this, vald, (_vm->_game.version <= 2) ? 0xFFFF : aMask); } _needRedraw = true; } void Actor::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; } void Actor::putActor(int dstX, int dstY, byte newRoom) { if (_visible && _vm->_currentRoom != newRoom && _vm->getTalkingActor() == _number) { _vm->stopTalk(); } // WORKAROUND: The green transparency of the tank in the Hall of Oddities is // 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) 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 { #ifndef DISABLE_HE if (_vm->_game.heversion >= 71) ((ScummEngine_v71he *)_vm)->queueAuxBlock(this); #endif hideActor(); } } else { if (isInCurrentRoom()) showActor(); } } int Actor::getActorXYPos(int &xPos, int &yPos) const { if (!isInCurrentRoom()) return -1; xPos = _pos.x; yPos = _pos.y; return 0; } 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; abr.x = dstX; abr.y = dstY; abr.box = kInvalidBox; if (_ignoreBoxes) 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; if (_vm->_game.version <= 2) bestDist *= 8*2; // Adjust for the fact that we multiply x by 8 and y by 2 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 && _vm->inBoxQuickReject(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)) { abr.x = dstX; abr.y = dstY; abr.box = box; return abr; } // Find the point in the box which is closest to our point. tmpDist = _vm->getClosestPtOnBox(box, dstX, dstY, tmpX, tmpY); // Check if the box is closer than the previous boxes. if (tmpDist < bestDist) { 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; if (_walkbox != kInvalidBox) { byte flags = _vm->getBoxFlags(_walkbox); if (flags & 7) { turnToDirection(_facing); } } } void Actor::faceToObject(int obj) { int x2, y2, dir; if (!isInCurrentRoom()) return; if (_vm->getObjectOrActorXY(obj, x2, y2) == -1) return; dir = (x2 > _pos.x) ? 90 : 270; turnToDirection(dir); } void Actor::turnToDirection(int newdir) { if (newdir == -1 || _ignoreTurns) return; if (_vm->_game.version <= 6) { _moving = MF_TURN; _targetFacing = newdir; } else { _moving &= ~MF_TURN; if (newdir != _facing) { _moving |= MF_TURN; _targetFacing = newdir; } } } void Actor::hideActor() { if (!_visible) return; if (_moving) { stopActorMoving(); startAnimActor(_standFrame); } _visible = false; _cost.soundCounter = 0; _needRedraw = false; _needBgReset = true; _auxBlock.reset(); } void Actor::showActor() { if (_vm->_currentRoom == 0 || _visible) return; adjustActorPos(); _vm->ensureResourceLoaded(rtCostume, _costume); 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; } // 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 value) { if (value == 255) { _system->clearFocusRectangle(); } else { // Work out the screen co-ordinates of the actor int x = _actors[value]._pos.x - (camera._cur.x - (_screenWidth >> 1)); int y = _actors[value]._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. Common::Rect rect(x - 96, y - 64, x + 96, y + 64); _system->setFocusRectangle(rect); } if (_game.id == GID_MANIAC && _game.version <= 1 && !(_game.platform == Common::kPlatformNES)) _V1TalkingActor = value; else VAR(VAR_TALK_ACTOR) = value; } void ScummEngine::putActors() { Actor *a; int i; for (i = 1; i < _numActors; i++) { a = &_actors[i]; if (a && a->isInCurrentRoom()) a->putActor(a->_pos.x, a->_pos.y, a->_room); } } static const int c64MMActorTalkColor[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.platform == Common::kPlatformC64) { _actors[i]._talkColor = c64MMActorTalkColor[i]; } else { _actors[i]._talkColor = v1MMActorTalkColor[i]; } } } void ScummEngine::showActors() { int i; for (i = 1; i < _numActors; i++) { if (_actors[i].isInCurrentRoom()) _actors[i].showActor(); } } void ScummEngine::walkActors() { int i; for (i = 1; i < _numActors; i++) { if (_actors[i].isInCurrentRoom()) if (_game.version <= 3) _actors[i].walkActorOld(); else _actors[i].walkActor(); } } /* Used in Scumm v5 only. Play sounds associated with actors */ void ScummEngine::playActorSounds() { int i; for (i = 1; i < _numActors; i++) { if (_actors[i]._cost.soundCounter && _actors[i].isInCurrentRoom() && _actors[i]._sound) { _currentScript = 0xFF; _sound->addSoundToQueue(_actors[i]._sound[0]); for (i = 1; i < _numActors; i++) { _actors[i]._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, vm.slot[_currentScript].number, _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, vm.slot[_currentScript].number, _opcode); if (!isValidActor(id)) { debugC(DEBUG_ACTORS, "Invalid actor %d in %s (script %d, opcode 0x%x)", id, errmsg, vm.slot[_currentScript].number, _opcode); return NULL; } return &_actors[id]; } 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 #758167, #775097, and #1093867. // // 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 #1220168 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]->_pos.y; int sc_actor2 = _sortedActors[i]->_pos.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 { for (int j = 0; j < numactors; ++j) { for (int i = 0; i < numactors; ++i) { int sc_actor1 = _sortedActors[j]->_pos.y - _sortedActors[j]->_layer * 2000; int sc_actor2 = _sortedActors[i]->_pos.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; // 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) { a->drawActorCostume(); a->animateCostume(); } } } void ScummEngine_v6::processActors() { ScummEngine::processActors(); if (_game.features & GF_NEW_COSTUMES) akos_processQueue(); } #ifndef DISABLE_HE void ScummEngine_v71he::processActors() { preProcessAuxQueue(); if (!_skipProcessActors) ScummEngine_v6::processActors(); _fullRedraw = false; postProcessAuxQueue(); } void ScummEngine_v90he::processActors() { preProcessAuxQueue(); _sprite->setRedrawFlags(false); _sprite->processImages(true); if (!_skipProcessActors) ScummEngine_v6::processActors(); _fullRedraw = false; postProcessAuxQueue(); _sprite->setRedrawFlags(true); _sprite->processImages(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) { CHECK_HEAP _actors[i].drawActorCostume(); CHECK_HEAP _actors[i].animateCostume(); } } } void Actor::drawActorCostume(bool hitTestMode) { if (_costume == 0) return; if (!hitTestMode) { if (!_needRedraw) return; _needRedraw = false; } setupActorScale(); BaseCostumeRenderer* bcr = _vm->_costumeRenderer; bcr->_actorID = _number; bcr->_actorX = _pos.x + _offsX - _vm->virtscr[0].xstart; bcr->_actorY = _pos.y + _offsY - _elevation; 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 <= 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; } bcr->_clipOverride = _clipOverride; 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->_shadow_mode = _shadowMode; if (_vm->_game.version >= 5 && _vm->_game.heversion == 0) { bcr->_shadow_table = _vm->_shadowPalette; } else if (_vm->_game.heversion == 70) { bcr->_shadow_table = _vm->_HEV7ActorPalette; } bcr->setCostume(_costume, _heXmapNum); 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 (bcr->_zbuf > _vm->_gdi->_numZBuffer-1) bcr->_zbuf = _vm->_gdi->_numZBuffer-1; } } bcr->_draw_top = 0x7fffffff; bcr->_draw_bottom = 0; bcr->_skipLimbs = (_heSkipLimbs != 0); bcr->_paletteNum = _hePaletteNum; if (_vm->_game.heversion >= 80 && _heNoTalkAnimation == 0 && _animProgress == 0) { if (_vm->getTalkingActor() == _number && !_vm->_string[0].no_talk_anim) { int talkState = 0; if (((SoundHE *)_vm->_sound)->isSoundCodeUsed(1)) talkState = ((SoundHE *)_vm->_sound)->getSoundVar(1, 19); if (talkState == 0) talkState = _vm->_rnd.getRandomNumberRng(1, 10); assertRange(1, talkState, 13, "Talk state"); setTalkCondition(talkState); } else { setTalkCondition(1); } } _heNoTalkAnimation = 0; // If the actor is partially hidden, redraw it next frame. if (bcr->drawCostume(_vm->virtscr[0], _vm->_gdi->_numStrips, this, _drawToBackBuf) & 1) { _needRedraw = (_vm->_game.version <= 6); } if (!hitTestMode) { // Record the vertical extent of the drawn actor _top = bcr->_draw_top; _bottom = bcr->_draw_bottom; } } #ifndef DISABLE_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::animateCostume() { if (_costume == 0) return; _animProgress++; if (_animProgress >= _animSpeed) { _animProgress = 0; _vm->_costumeLoader->loadCostume(_costume); if (_vm->_costumeLoader->increaseAnims(this)) { _needRedraw = true; } } } #ifndef DISABLE_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(MKID_BE('AKSQ'), akos); akfo = _vm->findResourceData(MKID_BE('AKFO'), akos); size = _vm->getResourceDataSize(akfo) / 2; while (f--) { if (_cost.active[limb] != 0) ((ScummEngine_v6 *)_vm)->akos_increaseAnim(this, limb, aksq, (const uint16 *)akfo, size); } // _needRedraw = true; // _needBgReset = true; } } #endif void ScummEngine::redrawAllActors() { int j; for (j = 1; j < _numActors; j++) { _actors[j]._needRedraw = true; _actors[j]._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 #1066329 for details). if (_fullRedraw || _game.version == 8 || (VAR_REDRAW_ALL_ACTORS != 0xFF && VAR(VAR_REDRAW_ALL_ACTORS) != 0)) { for (j = 1; j < _numActors; j++) { _actors[j]._needRedraw = true; } } else { 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 (_actors[j]._heFlags & 1) 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; } } 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; } #ifndef DISABLE_HE 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]._pos.y > _actors[curActor]._pos.y || curActor == 0)) curActor = i; } return curActor; } #endif #ifndef DISABLE_SCUMM_7_8 void ScummEngine_v7::actorTalk(const byte *msg) { Actor *a; convertMessageToString(msg, _charsetBuffer, sizeof(_charsetBuffer)); // Play associated speech, if any playSpeech((byte *)_lastStringTag); if ((_game.version == 7 && !_keepText) || (_game.version == 8 && VAR(VAR_HAVE_MSG))) { stopTalk(); } if (_actorToPrintStrFor == 0xFF) { setTalkingActor(0xFF); } else { a = derefActor(_actorToPrintStrFor, "actorTalk"); setTalkingActor(a->_number); if (!_string[0].no_talk_anim) { a->runActorTalkScript(a->_talkStartFrame); _useTalkAnims = true; } } if (getTalkingActor() > 0x7F) { _charsetColor = (byte)_string[0].color; } else { a = derefActor(getTalkingActor(), "actorTalk(2)"); _charsetColor = a->_talkColor; } _charsetBufPos = 0; _talkDelay = 0; _haveMsg = 1; if (_game.version == 7) VAR(VAR_HAVE_MSG) = 0xFF; _haveActorSpeechMsg = true; CHARSET_1(); if (_game.version == 8) VAR(VAR_HAVE_MSG) = (_string[0].no_talk_anim) ? 2 : 1; } #endif void ScummEngine::actorTalk(const byte *msg) { Actor *a; convertMessageToString(msg, _charsetBuffer, sizeof(_charsetBuffer)); // FIXME: Workaround for bugs #770039 and #770049 if (_game.id == GID_LOOM) { if (!*_charsetBuffer) return; } if (_actorToPrintStrFor == 0xFF) { if (!_keepText) { stopTalk(); } setTalkingActor(0xFF); } else { int oldact; // WORKAROUND bug #770724 if (_game.id == GID_LOOM && _roomResource == 23 && vm.slot[_currentScript].number == 232 && _actorToPrintStrFor == 0) { _actorToPrintStrFor = 2; // Could be anything from 2 to 5. Maybe compare to original? } a = derefActor(_actorToPrintStrFor, "actorTalk"); if (!a->isInCurrentRoom()) { oldact = 0xFF; } else { if (!_keepText) { stopTalk(); } setTalkingActor(a->_number); a->_heTalking = true; if (!_string[0].no_talk_anim) { a->runActorTalkScript(a->_talkStartFrame); _useTalkAnims = true; } oldact = getTalkingActor(); } if (oldact >= 0x80) return; } if (_game.heversion >= 72 || getTalkingActor() > 0x7F) { _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; CHARSET_1(); } void Actor::runActorTalkScript(int f) { if (_vm->_game.version == 8 && _vm->VAR(_vm->VAR_HAVE_MSG) == 2) return; if (_talkScript) { int script = _talkScript; int args[16]; memset(args, 0, sizeof(args)); args[1] = f; args[0] = _number; _vm->runScript(script, 1, 0, args); } else { if (_frame != f) startAnimActor(f); } } void ScummEngine::stopTalk() { int act; _sound->stopTalkSound(); _haveMsg = 0; _talkDelay = 0; 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); a->_heTalking = false; } if (_game.version == 8 || _game.heversion >= 60) setTalkingActor(0); if (_game.version == 8) VAR(VAR_HAVE_MSG) = 0; _keepText = false; if (_game.version >= 7) { #ifndef DISABLE_SCUMM_7_8 ((ScummEngine_v7 *)this)->clearSubtitleQueue(); #endif } else { _charset->restoreCharsetBg(); } } void Actor::setActorCostume(int c) { int i; 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) c &= 0xff; _costumeNeedsInit = true; if (_vm->_game.features & GF_NEW_COSTUMES) { memset(_animVariable, 0, sizeof(_animVariable)); #ifndef DISABLE_HE if (_vm->_game.heversion >= 71) ((ScummEngine_v71he *)_vm)->queueAuxBlock(this); #endif _costume = c; _cost.reset(); _auxBlock.reset(); if (_visible) { if (_costume) { _vm->ensureResourceLoaded(rtCostume, _costume); } startAnimActor(_initFrame); if (_vm->_game.heversion >= 60) _needRedraw = true; } } 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; // Make stuff more visible on CGA. Based on disassembly if (_vm->_renderMode == Common::kRenderCGA && _vm->_game.version > 2) { _palette[6] = 5; _palette[7] = 15; } } else { for (i = 0; i < 32; i++) _palette[i] = 0xFF; } 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::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; } 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 (_ignoreBoxes) { abr.box = kInvalidBox; _walkbox = kInvalidBox; } else { 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; _moving = (_moving & MF_IN_LEG) | MF_NEW_LEG; _walkdata.point3.x = 32000; _walkdata.curbox = _walkbox; } void Actor::startWalkAnim(int cmd, int angle) { if (angle == -1) angle = _facing; /* Note: walk scripts aren't required to make the Dig * work as usual */ if (_walkScript) { int args[16]; memset(args, 0, sizeof(args)); args[0] = _number; args[1] = cmd; args[2] = angle; _vm->runScript(_walkScript, 1, 0, args); } else { switch (cmd) { case 1: /* start walk */ setDirection(angle); startAnimActor(_walkFrame); break; case 2: /* change dir only */ setDirection(angle); break; case 3: /* stop walk */ turnToDirection(angle); startAnimActor(_standFrame); break; } } } void Actor::walkActor() { int new_dir, next_box; Common::Point foundPath; if (_vm->_game.version >= 7) { if (_moving & MF_FROZEN) { 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_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) { 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->getPathToDestBox(_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::walkActorV12() { 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 = 0; return; } if (!_moving) return; if (_moving & MF_IN_LEG) { actorWalkStep(); } else { if (_moving & MF_LAST_LEG) { _moving = 0; 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->getPathToDestBox(_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.curbox = next_box; _vm->getClosestPtOnBox(_walkdata.curbox, x, y, tmp.x, tmp.y); _vm->getClosestPtOnBox(_walkbox, tmp.x, tmp.y, foundPath.x, foundPath.y); } calcMovementFactor(foundPath); } } } */ void Actor::walkActorOld() { 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 _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->getPathToDestBox(_walkbox, _walkdata.destbox); // WORKAROUND: To fully fix bug #774783, we add a special case // here, resulting in a different next_box value for Hitler. if ((_vm->_game.id == GID_INDY3) && _vm->_roomResource == 46 && _walkbox == 1 && _walkdata.destbox == 0 && _number == 9) next_box = 1; 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; // FIXME: Work in progress // _walkdata.destdir = _facing; return; } _walkdata.curbox = next_box; if (_vm->_game.version <= 2) { _vm->getClosestPtOnBox(_walkdata.curbox, _pos.x, _pos.y, p2.x, p2.y); _vm->getClosestPtOnBox(_walkbox, p2.x, p2.y, p3.x, p3.y); // FIXME: Work in progress // calcMovementFactor(p3); // return; } else { 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.destbox); } while (1); _moving |= MF_LAST_LEG; calcMovementFactor(_walkdata.dest); } byte *Actor::getActorName() { byte *ptr = _vm->getResourceAddress(rtActorName, _number); if (ptr == NULL) { 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(MKID_BE('AKPL'), 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(MKID_BE('AKPL'), 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(MKID_BE('RGBS'), 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() { if (_vm->_game.version <= 2) return _vm->VAR(42) <= _number && _number <= _vm->VAR(43); else return isInClass(kObjectClassPlayer); } void Actor::setHEFlag(int bit, int set) { // Note that condition is inverted if (!set) { _heFlags |= bit; } else { _heFlags &= ~bit; } } void Actor::setUserCondition(int slot, int set) { const int condMaskCode = (_vm->_game.heversion >= 85) ? 0x1FFF : 0x3FF; assertRange(1, slot, 32, "setUserCondition: Condition"); if (set == 0) { _heCondMask &= ~(1 << (slot + 0xF)); } else { _heCondMask |= 1 << (slot + 0xF); } if (_heCondMask & condMaskCode) { _heCondMask &= ~1; } else { _heCondMask |= 1; } } bool Actor::isUserConditionSet(int slot) const { assertRange(1, slot, 32, "isUserConditionSet: Condition"); return (_heCondMask & (1 << (slot + 0xF))) != 0; } void Actor::setTalkCondition(int slot) { const int condMaskCode = (_vm->_game.heversion >= 85) ? 0x1FFF : 0x3FF; assertRange(1, slot, 32, "setTalkCondition: Condition"); _heCondMask = (_heCondMask & ~condMaskCode) | 1; if (slot != 1) { _heCondMask |= 1 << (slot - 1); if (_heCondMask & condMaskCode) { _heCondMask &= ~1; } else { _heCondMask |= 1; } } } bool Actor::isTalkConditionSet(int slot) const { assertRange(1, slot, 32, "isTalkConditionSet: Condition"); return (_heCondMask & (1 << (slot - 1))) != 0; } #ifndef DISABLE_HE void ScummEngine_v71he::preProcessAuxQueue() { if (!_skipProcessActors) { for (int i = 0; i < _auxBlocksNum; ++i) { AuxBlock *ab = &_auxBlocks[i]; if (ab->r.top <= ab->r.bottom) { restoreBackgroundHE(ab->r); } } } _auxBlocksNum = 0; } void ScummEngine_v71he::postProcessAuxQueue() { if (!_skipProcessActors) { for (int i = 0; i < _auxEntriesNum; ++i) { AuxEntry *ae = &_auxEntries[i]; if (ae->actorNum != -1) { Actor *a = derefActor(ae->actorNum, "postProcessAuxQueue"); const uint8 *cost = getResourceAddress(rtCostume, a->_costume); int dy = a->_offsY + a->_pos.y - a->getElevation(); int dx = a->_offsX + a->_pos.x; const uint8 *akax = findResource(MKID_BE('AKAX'), cost); assert(akax); const uint8 *auxd = findPalInPals(akax, ae->subIndex) - _resourceHeaderSize; assert(auxd); const uint8 *frel = findResourceData(MKID_BE('FREL'), auxd); if (frel) { error("unhandled FREL block"); } const uint8 *disp = findResourceData(MKID_BE('DISP'), auxd); if (disp) { error("unhandled DISP block"); } const uint8 *axfd = findResourceData(MKID_BE('AXFD'), auxd); assert(axfd); uint16 comp = READ_LE_UINT16(axfd); if (comp != 0) { int x = (int16)READ_LE_UINT16(axfd + 2) + dx; int y = (int16)READ_LE_UINT16(axfd + 4) + dy; int w = (int16)READ_LE_UINT16(axfd + 6); int h = (int16)READ_LE_UINT16(axfd + 8); VirtScreen *pvs = &virtscr[kMainVirtScreen]; uint8 *dst1 = pvs->getPixels(0, pvs->topline); uint8 *dst2 = pvs->getBackPixels(0, pvs->topline); switch (comp) { case 1: Wiz::copyAuxImage(dst1, dst2, axfd + 10, pvs->w, pvs->h, x, y, w, h); break; default: error("unimplemented compression type %d", comp); } } const uint8 *axur = findResourceData(MKID_BE('AXUR'), auxd); if (axur) { uint16 n = READ_LE_UINT16(axur); axur += 2; while (n--) { int x1 = (int16)READ_LE_UINT16(axur + 0) + dx; int y1 = (int16)READ_LE_UINT16(axur + 2) + dy; int x2 = (int16)READ_LE_UINT16(axur + 4) + dx; int y2 = (int16)READ_LE_UINT16(axur + 6) + dy; markRectAsDirty(kMainVirtScreen, x1, x2, y1, y2 + 1); axur += 8; } } const uint8 *axer = findResourceData(MKID_BE('AXER'), auxd); if (axer) { a->_auxBlock.visible = true; a->_auxBlock.r.left = (int16)READ_LE_UINT16(axer + 0) + dx; a->_auxBlock.r.top = (int16)READ_LE_UINT16(axer + 2) + dy; a->_auxBlock.r.right = (int16)READ_LE_UINT16(axer + 4) + dx; a->_auxBlock.r.bottom = (int16)READ_LE_UINT16(axer + 6) + dy; } } } } _auxEntriesNum = 0; } void ScummEngine_v71he::queueAuxBlock(Actor *a) { if (!a->_auxBlock.visible) return; assert(_auxBlocksNum < ARRAYSIZE(_auxBlocks)); _auxBlocks[_auxBlocksNum] = a->_auxBlock; ++_auxBlocksNum; } void ScummEngine_v71he::queueAuxEntry(int actorNum, int subIndex) { assert(_auxEntriesNum < ARRAYSIZE(_auxEntries)); AuxEntry *ae = &_auxEntries[_auxEntriesNum]; ae->actorNum = actorNum; ae->subIndex = subIndex; ++_auxEntriesNum; } #endif void Actor::saveLoadWithSerializer(Serializer *ser) { static const SaveLoadEntry actorEntries[] = { MKLINE(Actor, _pos.x, sleInt16, VER(8)), MKLINE(Actor, _pos.y, sleInt16, VER(8)), MKLINE(Actor, _offsX, sleInt16, VER(32)), MKLINE(Actor, _offsY, sleInt16, VER(32)), MKLINE(Actor, _top, sleInt16, VER(8)), MKLINE(Actor, _bottom, sleInt16, VER(8)), MKLINE(Actor, _elevation, sleInt16, VER(8)), MKLINE(Actor, _width, sleUint16, VER(8)), MKLINE(Actor, _facing, sleUint16, VER(8)), MKLINE(Actor, _costume, sleUint16, VER(8)), MKLINE(Actor, _room, sleByte, VER(8)), MKLINE(Actor, _talkColor, sleByte, VER(8)), MKLINE(Actor, _talkFrequency, sleInt16, VER(16)), MKLINE(Actor, _talkPan, sleInt16, VER(24)), MKLINE(Actor, _talkVolume, sleInt16, VER(29)), MKLINE(Actor, _boxscale, sleUint16, VER(34)), MKLINE(Actor, _scalex, sleByte, VER(8)), MKLINE(Actor, _scaley, sleByte, VER(8)), MKLINE(Actor, _charset, sleByte, VER(8)), // Actor sound grew from 8 to 32 bytes and switched to uint16 in HE games MKARRAY_OLD(Actor, _sound[0], sleByte, 8, VER(8), VER(36)), MKARRAY_OLD(Actor, _sound[0], sleByte, 32, VER(37), VER(61)), MKARRAY(Actor, _sound[0], sleUint16, 32, VER(62)), // Actor animVariable grew from 8 to 27 MKARRAY_OLD(Actor, _animVariable[0], sleUint16, 8, VER(8), VER(40)), MKARRAY(Actor, _animVariable[0], sleUint16, 27, VER(41)), MKLINE(Actor, _targetFacing, sleUint16, VER(8)), MKLINE(Actor, _moving, sleByte, VER(8)), MKLINE(Actor, _ignoreBoxes, sleByte, VER(8)), MKLINE(Actor, _forceClip, sleByte, VER(8)), MKLINE(Actor, _initFrame, sleByte, VER(8)), MKLINE(Actor, _walkFrame, sleByte, VER(8)), MKLINE(Actor, _standFrame, sleByte, VER(8)), MKLINE(Actor, _talkStartFrame, sleByte, VER(8)), MKLINE(Actor, _talkStopFrame, sleByte, VER(8)), MKLINE(Actor, _speedx, sleUint16, VER(8)), MKLINE(Actor, _speedy, sleUint16, VER(8)), MKLINE(Actor, _cost.animCounter, sleUint16, VER(8)), MKLINE(Actor, _cost.soundCounter, sleByte, VER(8)), MKLINE(Actor, _drawToBackBuf, sleByte, VER(32)), MKLINE(Actor, _flip, sleByte, VER(32)), MKLINE(Actor, _heSkipLimbs, sleByte, VER(32)), // Actor palette grew from 64 to 256 bytes MKARRAY_OLD(Actor, _palette[0], sleByte, 64, VER(8), VER(9)), MKARRAY(Actor, _palette[0], sleByte, 256, VER(10)), MK_OBSOLETE(Actor, _mask, sleByte, VER(8), VER(9)), MKLINE(Actor, _shadowMode, sleByte, VER(8)), MKLINE(Actor, _visible, sleByte, VER(8)), MKLINE(Actor, _frame, sleByte, VER(8)), MKLINE(Actor, _animSpeed, sleByte, VER(8)), MKLINE(Actor, _animProgress, sleByte, VER(8)), MKLINE(Actor, _walkbox, sleByte, VER(8)), MKLINE(Actor, _needRedraw, sleByte, VER(8)), MKLINE(Actor, _needBgReset, sleByte, VER(8)), MKLINE(Actor, _costumeNeedsInit, sleByte, VER(8)), MKLINE(Actor, _heCondMask, sleUint32, VER(38)), MKLINE(Actor, _hePaletteNum, sleUint32, VER(59)), MKLINE(Actor, _heXmapNum, sleUint32, VER(59)), MKLINE(Actor, _talkPosY, sleInt16, VER(8)), MKLINE(Actor, _talkPosX, sleInt16, VER(8)), MKLINE(Actor, _ignoreTurns, sleByte, VER(8)), // Actor layer switched to int32 in HE games MKLINE_OLD(Actor, _layer, sleByte, VER(8), VER(57)), MKLINE(Actor, _layer, sleInt32, VER(58)), MKLINE(Actor, _talkScript, sleUint16, VER(8)), MKLINE(Actor, _walkScript, sleUint16, VER(8)), MKLINE(Actor, _walkdata.dest.x, sleInt16, VER(8)), MKLINE(Actor, _walkdata.dest.y, sleInt16, VER(8)), MKLINE(Actor, _walkdata.destbox, sleByte, VER(8)), MKLINE(Actor, _walkdata.destdir, sleUint16, VER(8)), MKLINE(Actor, _walkdata.curbox, sleByte, VER(8)), MKLINE(Actor, _walkdata.cur.x, sleInt16, VER(8)), MKLINE(Actor, _walkdata.cur.y, sleInt16, VER(8)), MKLINE(Actor, _walkdata.next.x, sleInt16, VER(8)), MKLINE(Actor, _walkdata.next.y, sleInt16, VER(8)), MKLINE(Actor, _walkdata.deltaXFactor, sleInt32, VER(8)), MKLINE(Actor, _walkdata.deltaYFactor, sleInt32, VER(8)), MKLINE(Actor, _walkdata.xfrac, sleUint16, VER(8)), MKLINE(Actor, _walkdata.yfrac, sleUint16, VER(8)), MKLINE(Actor, _walkdata.point3.x, sleUint16, VER(42)), MKLINE(Actor, _walkdata.point3.y, sleUint16, VER(42)), MKARRAY(Actor, _cost.active[0], sleByte, 16, VER(8)), MKLINE(Actor, _cost.stopped, sleUint16, VER(8)), MKARRAY(Actor, _cost.curpos[0], sleUint16, 16, VER(8)), MKARRAY(Actor, _cost.start[0], sleUint16, 16, VER(8)), MKARRAY(Actor, _cost.end[0], sleUint16, 16, VER(8)), MKARRAY(Actor, _cost.frame[0], sleUint16, 16, VER(8)), MKARRAY(Actor, _cost.heJumpOffsetTable[0], sleUint16, 16, VER(65)), MKARRAY(Actor, _cost.heJumpCountTable[0], sleUint16, 16, VER(65)), MKARRAY(Actor, _cost.heCondMaskTable[0], sleUint32, 16, VER(65)), MKEND() }; if (ser->isLoading()) { // Not all actor data is saved; so when loading, we first reset // the actor, to ensure completely reproducible behaviour (else, // some not saved value in the actor class can cause odd things) initActor(-1); } ser->saveLoadEntries(this, actorEntries); } } // End of namespace Scumm