aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/actor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/scumm/actor.cpp')
-rw-r--r--engines/scumm/actor.cpp431
1 files changed, 355 insertions, 76 deletions
diff --git a/engines/scumm/actor.cpp b/engines/scumm/actor.cpp
index eb23c30ebe..b8722b6963 100644
--- a/engines/scumm/actor.cpp
+++ b/engines/scumm/actor.cpp
@@ -42,6 +42,14 @@ namespace Scumm {
byte Actor::kInvalidBox = 0;
+static const byte v0ActorTalkArray[0x19] = {
+ 0x00, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x00, 0x46, 0x06,
+ 0x06, 0x06, 0x06, 0xFF, 0xFF,
+ 0x06, 0xC0, 0x06, 0x06, 0x00,
+ 0xC0, 0xC0, 0x00, 0x06, 0x06
+};
+
Actor::Actor(ScummEngine *scumm, int id) :
_vm(scumm), _number(id) {
assert(_vm != 0);
@@ -167,6 +175,21 @@ void Actor_v2::initActor(int mode) {
_talkStopFrame = 4;
}
+void Actor_v0::initActor(int mode) {
+ Actor_v2::initActor(mode);
+
+ _costCommandNew = 0xFF;
+ _costCommand = 0xFF;
+ _miscflags = 0;
+ _speaking = 0;
+
+ _animFrameRepeat = 0;
+ for (int i = 0; i < 8; ++i) {
+ _limbFrameRepeatNew[i] = 0;
+ _limbFrameRepeat[i] = 0;
+ _limb_flipped[i] = false;
+ }
+}
void Actor::setBox(int box) {
_walkbox = box;
@@ -226,12 +249,9 @@ void Actor::stopActorMoving() {
if (_walkScript)
_vm->stopScript(_walkScript);
- // V0 Games will walk on the spot if the actor is stopped mid-walk
- // So we must set the stand still frame
- if (_vm->_game.version == 0)
- startWalkAnim(3, -1);
-
_moving = 0;
+ if (_vm->_game.version == 0)
+ setDirection(_facing);
}
void Actor::setActorWalkSpeed(uint newSpeedX, uint newSpeedY) {
@@ -286,7 +306,7 @@ int Actor::calcMovementFactor(const Common::Point& next) {
deltaYFactor = 0;
}
- if ((uint) ABS((int)(deltaXFactor >> 16)) > _speedx) {
+ if ((uint) ABS(deltaXFactor) > (_speedx << 16)) {
deltaXFactor = _speedx << 16;
if (diffX < 0)
deltaXFactor = -deltaXFactor;
@@ -319,6 +339,9 @@ int Actor::actorWalkStep() {
int distX, distY;
int nextFacing;
+ if (_vm->_game.version == 0)
+ ((Actor_v0 *)this)->_animFrameRepeat = -1;
+
_needRedraw = true;
nextFacing = updateActorDirection(true);
@@ -327,6 +350,10 @@ int Actor::actorWalkStep() {
startWalkAnim(1, nextFacing);
}
_moving |= MF_IN_LEG;
+
+ // V0: Don't move during the turn
+ if (_vm->_game.version == 0)
+ return 0;
}
if (_walkbox != _walkdata.curbox && _vm->checkXYInBoxBounds(_walkdata.curbox, _pos.x, _pos.y)) {
@@ -361,6 +388,10 @@ int Actor::actorWalkStep() {
_moving &= ~MF_IN_LEG;
return 0;
}
+
+ if (_vm->_game.version == 0)
+ ((Actor_v0 *)this)->animateActor(newDirToOldDir(_facing));
+
return 1;
}
@@ -536,23 +567,101 @@ void Actor::walkActor() {
calcMovementFactor(_walkdata.dest);
}
+bool Actor_v2::checkWalkboxesHaveDirectPath(Common::Point &foundPath) {
+ // only MM v0 supports walking in direct line between walkboxes.
+ // MM v1 already does not support it anymore.
+ return false;
+}
+
+bool Actor_v0::intersectLineSegments(const Common::Point &line1Start, const Common::Point &line1End,
+ const Common::Point &line2Start, const Common::Point &line2End, Common::Point &result)
+{
+ const Common::Point v1 = line1End - line1Start; // line1(n1) = line1Start + n1 * v1
+ const Common::Point v2 = line2End - line2Start; // line2(n2) = line2Start + n2 * v2
+
+ double det = v2.x * v1.y - v1.x * v2.y;
+ if (det == 0)
+ return false;
+
+ double n1 = ((double)v2.x * (line2Start.y - line1Start.y) -
+ (double)v2.y * (line2Start.x - line1Start.x)) / det;
+ double n2 = ((double)v1.x * (line2Start.y - line1Start.y) -
+ (double)v1.y * (line2Start.x - line1Start.x)) / det;
+
+ // both coefficients have to be in [0, 1], otherwise the intersection is
+ // not inside of at least one of the two line segments
+ if (n1 < 0.0 || n1 > 1.0 || n2 < 0.0 || n2 > 1.0)
+ return false;
+
+ result.x = line1Start.x + (int)(n1 * v1.x);
+ result.y = line1Start.y + (int)(n1 * v1.y);
+ return true;
+}
+
+/*
+ * MM v0 allows the actor to walk in a direct line between boxes to the target
+ * if actor and target share a horizontal or vertical corridor.
+ * If such a corridor is found the actor is not forced to go horizontally or
+ * vertically from one box to the next but can also walk diagonally.
+ *
+ * Note: the original v0 interpreter sets the target destination for diagonal
+ * walking only once and then rechecks whenever the actor reaches a new box if the
+ * walk destination is still suitable for the current box.
+ * ScummVM does not perform such a check, so it is possible to leave the walkboxes
+ * in some cases, for example L-shaped rooms like the swimming pool (actor walks over water)
+ * or the medical room (actor walks over examination table).
+ * To solve this we intersect the new walk destination with the actor's walkbox borders,
+ * so a recheck is done when the actor leaves his box. This is done by the
+ * intersectLineSegments() routine calls.
+ */
+bool Actor_v0::checkWalkboxesHaveDirectPath(Common::Point &foundPath) {
+ BoxCoords boxCoords = _vm->getBoxCoordinates(_walkbox);
+ BoxCoords curBoxCoords = _vm->getBoxCoordinates(_walkdata.curbox);
+
+ // check if next walkbox is left or right to actor's box
+ if (boxCoords.ll.x > curBoxCoords.lr.x || boxCoords.lr.x < curBoxCoords.ll.x) {
+ // determine horizontal corridor gates
+ int gateUpper = MAX(boxCoords.ul.y, curBoxCoords.ul.y);
+ int gateLower = MIN(boxCoords.ll.y, curBoxCoords.ll.y);
+
+ // check if actor and target are in the same horizontal corridor between the boxes
+ if ((_pos.y >= gateUpper && _pos.y <= gateLower) &&
+ (_walkdata.dest.y >= gateUpper && _walkdata.dest.y <= gateLower)) {
+ if (boxCoords.ll.x > curBoxCoords.lr.x) // next box is left
+ return intersectLineSegments(_pos, _walkdata.dest, boxCoords.ll, boxCoords.ul, foundPath);
+ else // next box is right
+ return intersectLineSegments(_pos, _walkdata.dest, boxCoords.lr, boxCoords.ur, foundPath);
+ }
+ // check if next walkbox is above or below actor's box
+ } else if (boxCoords.ul.y > curBoxCoords.ll.y || boxCoords.ll.y < curBoxCoords.ul.y) {
+ // determine vertical corridor gates
+ int gateLeft = MAX(boxCoords.ll.x, curBoxCoords.ll.x);
+ int gateRight = MIN(boxCoords.lr.x, curBoxCoords.lr.x);
+
+ // check if actor and target are in the same vertical corridor between the boxes
+ if ((_pos.x >= gateLeft && _pos.x <= gateRight) &&
+ (_walkdata.dest.x >= gateLeft && _walkdata.dest.x <= gateRight)) {
+ if (boxCoords.ul.y > curBoxCoords.ll.y) // next box is above
+ return intersectLineSegments(_pos, _walkdata.dest, boxCoords.ul, boxCoords.ur, foundPath);
+ else // next box is below
+ return intersectLineSegments(_pos, _walkdata.dest, boxCoords.ll, boxCoords.lr, foundPath);
+ }
+ }
+
+ return false;
+}
+
void Actor_v2::walkActor() {
Common::Point foundPath, tmp;
int new_dir, next_box;
if (_moving & MF_TURN) {
new_dir = updateActorDirection(false);
- // FIXME: is this correct?
if (_facing != new_dir) {
-
- // Actor never stops walking when an object has been selected without this
- if (_vm->_game.version ==0)
- _moving = 0;
-
setDirection(new_dir);
-
- } else
+ } else {
_moving = 0;
+ }
return;
}
@@ -588,14 +697,7 @@ void Actor_v2::walkActor() {
_walkdata.curbox = next_box;
- // WORKAROUND: The route of the meteor landing in the introduction isn't correct.
- // MM V0 in contrast to MM V2 uses two walkboxes instead of just one. Hence a route
- // from walkbox 1 to 0 is calculated first. This causes the meteor to fly on a
- // horizontal line to walkbox 0 then vertically to the ground.
- // To fix this problem, the box-to-box routing has been disabled in room 33.
- if (_vm->_game.version == 0 && _vm->_currentRoom == 33) {
- foundPath = _walkdata.dest;
- } else {
+ if (!checkWalkboxesHaveDirectPath(foundPath)) {
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);
}
@@ -781,7 +883,7 @@ int Actor::remapDirection(int dir, bool is_walking) {
return 180;
}
- // MM C64 stores flags as a part of the mask
+ // 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
@@ -857,16 +959,6 @@ void Actor::setDirection(int direction) {
if (_costume == 0)
return;
- // V0 MM
- if (_vm->_game.version == 0) {
- if (_moving)
- _vm->_costumeLoader->costumeDecodeData(this, _walkFrame, 0);
- else
- _vm->_costumeLoader->costumeDecodeData(this, _standFrame, 0);
- _needRedraw = true;
- return;
- }
-
// Update the costume for the new direction (and mark the actor for redraw)
aMask = 0x8000;
for (i = 0; i < 16; i++, aMask >>= 1) {
@@ -879,6 +971,34 @@ void Actor::setDirection(int direction) {
_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 Away
+ break;
+
+ default:
+ res = 7; // Face Camera
+ break;
+ }
+
+ _animFrameRepeat = -1;
+ animateActor(res);
+ if (_moving)
+ animateCostume();
+}
+
void Actor::faceToObject(int obj) {
int x2, y2, dir;
@@ -963,6 +1083,10 @@ void Actor::putActor(int dstX, int dstY, int newRoom) {
if (isInCurrentRoom())
showActor();
}
+
+ // V0 always sets the actor to face the camera upon entering a room
+ if (_vm->_game.version == 0)
+ setDirection(oldDirToNewDir(2));
}
static bool inBoxQuickReject(const BoxCoords &box, int x, int y, int threshold) {
@@ -1062,16 +1186,11 @@ static int checkXYInBoxBounds(int boxnum, int x, int y, int &destX, int &destY)
// yDist must be divided by 4, as we are using 8x2 pixels
// blocks for actor coordinates).
int xDist = ABS(x - destX);
- int yDist;
+ int yDist = ABS(y - destY) / 4;
int dist;
- // MM C64: This fixes the trunk bug (#3070065), as well
- // as the fruit bowl, however im not sure if its
- // the proper solution or not.
- if( g_scumm->_game.version == 0 )
- yDist = ABS(y - destY);
- else
- yDist = ABS(y - destY) / 4;
+ if (g_scumm->_game.version == 0)
+ xDist *= 2;
if (xDist < yDist)
dist = (xDist >> 1) + yDist;
@@ -1090,7 +1209,9 @@ AdjustBoxResult Actor_v2::adjustXYToBeInBox(const int dstX, const int dstY) {
int numBoxes = _vm->getNumBoxes() - 1;
int bestDist = 0xFF;
- for (int box = numBoxes; box >= 0; box--) {
+ 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()))
@@ -1286,8 +1407,24 @@ void Actor::showActor() {
_vm->ensureResourceLoaded(rtCostume, _costume);
if (_vm->_game.version == 0) {
+ Actor_v0 *a = ((Actor_v0 *)this);
+
+ a->_costCommand = a->_costCommandNew = 0xFF;
+
+ 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);
@@ -1323,23 +1460,23 @@ static const byte v0ActorSounds[24] = {
0x06, // Bernard
0x06, // Wendy
0x00, // Jeff
- 0x46, // ???
+ 0x46, // Radiation Suit
0x06, // Dr Fred
0x06, // Nurse Edna
0x06, // Weird Ed
0x06, // Dead Cousin Ted
0xFF, // Purple Tentacle
0xFF, // Green Tentacle
- 0x06, // Meteor
- 0xC0, // Plant
- 0x06, // ???
- 0x06, // ???
- 0x00, // ???
- 0xC0, // ???
- 0xC0, // ???
- 0x00, // ???
- 0x06, // Sandy
- 0x06, // ???
+ 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)
};
/* Used in Scumm v5 only. Play sounds associated with actors */
@@ -1355,7 +1492,10 @@ void ScummEngine::playActorSounds() {
} else {
sound = _actors[i]->_sound[0];
}
- _sound->addSoundToQueue(sound);
+ // fast mode will flood the queue with walk sounds
+ if (!_fastMode) {
+ _sound->addSoundToQueue(sound);
+ }
for (j = 1; j < _numActors; j++) {
_actors[j]->_cost.soundCounter = 0;
}
@@ -1462,6 +1602,18 @@ void ScummEngine::processActors() {
}
}
}
+ } 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) {
@@ -1479,11 +1631,26 @@ void ScummEngine::processActors() {
for (Actor** ac = _sortedActors; ac != end; ++ac) {
Actor* a = *ac;
- // V0 MM: 0x057B
if (_game.version == 0) {
- ActorC64 *A = (ActorC64*) a;
- if ((A->_speaking & 1))
- A->_speaking ^= 0xFE;
+ // 0x057B
+ Actor_v0 *a0 = (Actor_v0*) a;
+ if (a0->_speaking & 1)
+ a0->_speaking ^= 0xFE;
+
+ // 0x22B5
+ if (a0->_miscflags & kActorMiscFlagHide)
+ continue;
+
+ // Sound
+ if (a0->_moving && _currentRoom != 1 && _currentRoom != 44) {
+ if (a0->_cost.soundPos == 0)
+ a0->_cost.soundCounter++;
+
+ // Is this the correct location?
+ // 0x073C
+ if (v0ActorTalkArray[a0->_number] & 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
@@ -1779,6 +1946,25 @@ void Actor::startAnimActor(int f) {
}
}
+void Actor_v0::startAnimActor(int f) {
+ if (f == _talkStartFrame) {
+ if (v0ActorTalkArray[_number] & 0x40)
+ return;
+
+ _speaking = 1;
+ return;
+ }
+
+ if (f == _talkStopFrame) {
+
+ _speaking = 0;
+ return;
+ }
+
+ if (f == _standFrame)
+ setDirection(_facing);
+}
+
void Actor::animateActor(int anim) {
int cmd, dir;
@@ -1841,6 +2027,47 @@ void Actor::animateCostume() {
}
}
+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.active[limb] = ((V0CostumeLoader *)_vm->_costumeLoader)->getFrame(this, limb);
+ _cost.curpos[limb] = 0;
+
+ _needRedraw = true;
+}
+
+void Actor_v0::animateCostume() {
+ speakCheck();
+
+ if (_vm->_costumeLoader->increaseAnims(this))
+ _needRedraw = true;
+}
+
+void Actor_v0::speakCheck() {
+ if (v0ActorTalkArray[_number] & 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().
@@ -1994,7 +2221,7 @@ void ScummEngine::setTalkingActor(int i) {
VAR(VAR_TALK_ACTOR) = i;
}
-static const int c64MMActorTalkColor[25] = {
+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] = {
@@ -2006,7 +2233,7 @@ void ScummEngine::resetV1ActorTalkColor() {
for (i = 1; i < _numActors; i++) {
if (_game.version == 0) {
- _actors[i]->_talkColor = c64MMActorTalkColor[i];
+ _actors[i]->_talkColor = v0MMActorTalkColor[i];
} else {
_actors[i]->_talkColor = v1MMActorTalkColor[i];
}
@@ -2295,22 +2522,23 @@ static const char *const v0ActorNames_English[25] = {
"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",
- "",
- "",
- "",
- "Sandy"
+ "", // Meteor Radiation
+ "", // Edsel (small, outro)
+ "", // Meteor (small, intro)
+ "Sandy", // (Lab)
+ "", // Sandy (Cut-Scene)
};
static const char *const v0ActorNames_German[25] = {
@@ -2328,15 +2556,16 @@ static const char *const v0ActorNames_German[25] = {
"Ted",
"Lila Tentakel",
"Gr<nes Tentakel",
- "Meteor",
"",
+ "Meteor",
"",
"",
"Pflanze",
"",
"",
"",
- "Sandy"
+ "Sandy",
+ "",
};
const byte *Actor::getActorName() {
@@ -2467,6 +2696,8 @@ bool Actor::isPlayer() {
}
bool Actor_v2::isPlayer() {
+ // isPlayer() is not supported by v0
+ assert(_vm->_game.version != 0);
return _vm->VAR(42) <= _number && _number <= _vm->VAR(43);
}
@@ -2621,16 +2852,64 @@ void ScummEngine_v71he::queueAuxEntry(int actorNum, int subIndex) {
}
#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(oldDirToNewDir(dir));
+
+ } else {
+
+ if (anim > 4 && anim <= 7)
+ _facing = normalizeAngle(oldDirToNewDir(dir));
+ }
+}
-void ActorC64::saveLoadWithSerializer(Serializer *ser) {
+void Actor_v0::saveLoadWithSerializer(Serializer *ser) {
Actor::saveLoadWithSerializer(ser);
static const SaveLoadEntry actorEntries[] = {
- MKLINE(ActorC64, _costCommand, sleByte, VER(84)),
- MKLINE(ActorC64, _costFrame, sleByte, VER(84)),
- MKLINE(ActorC64, _miscflags, sleByte, VER(84)),
- MKLINE(ActorC64, _speaking, sleByte, VER(84)),
- MKLINE(ActorC64, _speakingPrev, sleByte, VER(84)),
+ MKLINE(Actor_v0, _costCommand, sleByte, VER(84)),
+ MK_OBSOLETE(Actor_v0, _costFrame, sleByte, VER(84), VER(89)),
+ MKLINE(Actor_v0, _miscflags, sleByte, VER(84)),
+ MKLINE(Actor_v0, _speaking, sleByte, VER(84)),
+ MK_OBSOLETE(Actor_v0, _speakingPrev, sleByte, VER(84), VER(89)),
+ MK_OBSOLETE(Actor_v0, _limbTemp, sleByte, VER(89), VER(89)),
+ MKLINE(Actor_v0, _animFrameRepeat, sleByte, VER(89)),
+ MKARRAY(Actor_v0, _limbFrameRepeatNew[0], sleInt8, 8, VER(89)),
+ MKARRAY(Actor_v0, _limbFrameRepeat[0], sleInt8, 8, VER(90)),
MKEND()
};