1
/* ScummVM - Graphic Adventure Engine
3
* ScummVM is the legal property of its developers, whose names
4
* are too numerous to list here. Please refer to the COPYRIGHT
5
* file distributed with this source distribution.
7
* This program is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU General Public License
9
* as published by the Free Software Foundation; either version 2
10
* of the License, or (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program; if not, write to the Free Software
19
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23
#include "common/scummsys.h"
24
#include "mads/mads.h"
25
#include "mads/player.h"
29
#define PLAYER_SEQ_INDEX -2
31
const int Player::_directionListIndexes[32] = {
32
0, 7, 4, 3, 6, 0, 2, 5, 0, 1, 9, 4, 1, 2, 7, 9, 3, 8, 9, 6, 7, 2, 3, 6, 1, 7, 9, 4, 7, 8, 0, 0
35
Player::Player(MADSEngine *vm)
38
_facing = FACING_NORTH;
39
_turnToFacing = FACING_NORTH;
40
_targetFacing = FACING_NORTH;
41
_prepareWalkFacing = FACING_NONE;
43
_spritesLoaded = false;
49
_priorVisible = false;
55
_walkAnywhere = false;
60
_scalingVelocity = false;
61
_spritesChanged = false;
62
_forceRefresh = false;
82
_walkOffScreenSceneId = -1;
84
Common::fill(&_stopWalkerList[0], &_stopWalkerList[12], 0);
85
Common::fill(&_stopWalkerTrigger[0], &_stopWalkerTrigger[12], 0);
86
Common::fill(&_spriteSetsPresent[0], &_spriteSetsPresent[PLAYER_SPRITES_FILE_COUNT], false);
89
void Player::cancelWalk() {
90
Scene &scene = _vm->_game->_scene;
91
_action = &scene._action;
92
_targetPos = _playerPos;
93
_targetFacing = FACING_NONE;
94
_turnToFacing = _facing;
96
_walkOffScreen = _walkOffScreenSceneId = 0;
97
scene._rails.resetRoute();
98
_walkAnywhere = false;
101
_readyToWalk = false;
104
bool Player::loadSprites(const Common::String &prefix) {
105
Common::String suffixList = "89632741";
107
Common::String newPrefix;
108
if (prefix.empty()) {
109
newPrefix = _spritesPrefix;
111
_spritesPrefix = prefix;
116
if (!_spritesPrefix.empty()) {
117
for (int fileIndex = 0; fileIndex < PLAYER_SPRITES_FILE_COUNT; ++fileIndex) {
118
Common::String setName = Common::String::format("*%s_%c.SS",
119
newPrefix.c_str(), suffixList[fileIndex]);
123
_spriteSetsPresent[fileIndex] = true;
126
if (Common::File::exists(setName)) {
127
setIndex = _vm->_game->_scene._sprites.addSprites(setName, 4);
129
} else if (fileIndex < 5) {
130
_highSprites = false;
133
_spriteSetsPresent[fileIndex] = false;
137
_spritesStart = setIndex;
140
_spritesLoaded = true;
141
_spritesChanged = false;
143
Common::fill(&_spriteSetsPresent[0], &_spriteSetsPresent[PLAYER_SPRITES_FILE_COUNT], false);
147
_highSprites = false;
151
void Player::setFinalFacing() {
152
if (_targetFacing != FACING_NONE)
153
_turnToFacing = _targetFacing;
156
void Player::changeFacing() {
157
int dirIndex = 0, dirIndex2 = 0;
158
int newDir = 0, newDir2 = 0;
160
if (_facing != _turnToFacing) {
161
// Find the index for the given direction in the player direction list
162
int tempDir = _facing;
166
tempDir = _directionListIndexes[tempDir + 10];
167
} while (tempDir != _turnToFacing);
171
if (_facing != _turnToFacing) {
172
// Find the index for the given direction in the player direction list
173
int tempDir = _facing;
177
tempDir = _directionListIndexes[tempDir + 20];
178
} while (tempDir != _turnToFacing);
181
int diff = dirIndex - dirIndex2;
183
diff = newDir - newDir2;
185
_facing = (diff >= 0) ? (Facing)_directionListIndexes[_facing + 20] :
186
(Facing)_directionListIndexes[_facing + 10];
189
if ((_facing == _turnToFacing) && !_moving)
195
void Player::cancelCommand() {
197
_action->_inProgress = false;
200
void Player::selectSeries() {
201
Scene &scene = _vm->_game->_scene;
206
_spritesIdx = _directionListIndexes[_facing];
207
if (!_spriteSetsPresent[_spritesIdx]) {
208
// Direction isn't present, so use alternate direction, with entries flipped
213
// If the user isn't to be present (such as for a cutscene), exit immediately
214
// WORKAROUND: Original didn't do a secondary check for the sprite set being
215
// present, but it's needed to prevent invalid reads during cutscenes
216
if ((_spritesStart + _spritesIdx) < 0 || !_spriteSetsPresent[_spritesIdx])
219
SpriteAsset &spriteSet = *scene._sprites[_spritesStart + _spritesIdx];
220
assert(spriteSet._charInfo);
221
_velocity = MAX(spriteSet._charInfo->_velocity, 100);
224
_frameCount = spriteSet._charInfo->_totalFrames;
225
if (_frameCount == 0)
226
_frameCount = spriteSet.getCount();
228
_centerOfGravity = spriteSet._charInfo->_centerOfGravity;
230
if ((_frameNumber <= 0) || (_frameNumber > _frameCount))
233
_forceRefresh = true;
236
void Player::updateFrame() {
237
// WORKAROUND: Prevent character info being referenced when not present
238
if ((_spritesStart + _spritesIdx) < 0 || !_spriteSetsPresent[_spritesStart + _spritesIdx])
241
Scene &scene = _vm->_game->_scene;
242
SpriteAsset &spriteSet = *scene._sprites[_spritesStart + _spritesIdx];
243
assert(spriteSet._charInfo);
245
if (!spriteSet._charInfo->_numEntries) {
248
_frameListIndex = _stopWalkerList[_stopWalkerIndex];
251
_upcomingTrigger = 0;
253
_upcomingTrigger = _stopWalkerTrigger[_stopWalkerIndex];
255
if (_stopWalkerIndex > 0)
259
// Set the player frame number
260
int listIndex = ABS(_frameListIndex);
261
_frameNumber = (_frameListIndex >= 0) ? spriteSet._charInfo->_startFrames[listIndex] :
262
spriteSet._charInfo->_stopFrames[listIndex];
264
// Set next waiting period in ticks
265
if (listIndex == 0) {
268
_ticksAmount = spriteSet._charInfo->_ticksList[listIndex];
272
_forceRefresh = true;
275
void Player::update() {
276
Scene &scene = _vm->_game->_scene;
278
if (_forceRefresh || (_visible != _priorVisible)) {
279
int slotIndex = getSpriteSlot();
281
scene._spriteSlots[slotIndex]._flags = IMG_ERASE;
284
int yp = MIN(_playerPos.y, (int16)(MADS_SCENE_HEIGHT - 1));
286
for (int idx = 1; idx < 15; ++idx) {
287
if (scene._sceneInfo->_depthList[newDepth] >= yp)
290
_currentDepth = newDepth;
293
int newScale = getScale(_playerPos.y);
294
_currentScale = MIN(newScale, 100);
297
// Player sprite needs to be rendered
299
slot._flags = IMG_UPDATE;
300
slot._seqIndex = PLAYER_SEQ_INDEX;
301
slot._spritesIndex = _spritesStart + _spritesIdx;
302
slot._frameNumber = _mirror ? -_frameNumber : _frameNumber;
303
slot._position.x = _playerPos.x;
304
slot._position.y = _playerPos.y + (_centerOfGravity * newScale) / 100;
305
slot._depth = newDepth;
306
slot._scale = newScale;
308
if (slotIndex >= 0) {
309
// Check if the existing player slot has the same details, and can be re-used
310
SpriteSlot &s2 = scene._spriteSlots[slotIndex];
311
bool equal = (s2._seqIndex == slot._seqIndex)
312
&& (s2._spritesIndex == slot._spritesIndex)
313
&& (s2._frameNumber == slot._frameNumber)
314
&& (s2._position == slot._position)
315
&& (s2._depth == slot._depth)
316
&& (s2._scale == slot._scale);
319
// Undo the prior expiry of the player sprite
320
s2._flags = IMG_STATIC;
326
// New slot needed, so allocate one and copy the slot data
327
slotIndex = scene._spriteSlots.add();
328
scene._spriteSlots[slotIndex] = slot;
331
// If changing a scene, check to change the scene when the player
332
// has moved off-screen
333
if (_walkOffScreen) {
334
SpriteAsset *asset = scene._sprites[slot._spritesIndex];
335
MSprite *frame = asset->getFrame(_frameNumber - 1);
336
int xScale = frame->w * newScale / 200;
337
int yScale = frame->h * newScale / 100;
338
int playerX = slot._position.x;
339
int playerY = slot._position.y;
341
if ((playerX + xScale) < 0 || (playerX + xScale) >= MADS_SCREEN_WIDTH ||
342
playerY < 0 || (playerY + yScale) >= MADS_SCENE_HEIGHT) {
343
scene._nextSceneId = _walkOffScreen;
345
_walkAnywhere = false;
352
_beenVisible |= _visible;
353
_priorVisible = _visible;
354
_forceRefresh = false;
357
void Player::clearStopList() {
358
_stopWalkerList[0] = 0;
359
_stopWalkerTrigger[0] = 0;
360
_stopWalkerIndex = 0;
361
_upcomingTrigger = 0;
365
void Player::startWalking(const Common::Point &pt, Facing facing) {
366
Scene &scene = _vm->_game->_scene;
371
_targetFacing = facing;
373
bool v = scene._depthSurface.getDepthHighBit(pt);
375
scene._rails.setupRoute(v, _playerPos, pt);
378
void Player::walk(const Common::Point &pos, Facing facing) {
382
_prepareWalkPos = pos;
383
_prepareWalkFacing = facing;
386
void Player::nextFrame() {
387
Scene &scene = _vm->_game->_scene;
389
uint32 newTime = _priorTimer + _ticksAmount;
390
if (scene._frameStartTime >= newTime) {
391
_priorTimer = scene._frameStartTime;
403
void Player::move() {
404
Scene &scene = _vm->_game->_scene;
405
Rails &rails = scene._rails;
406
bool newFacing = false;
409
while (!_walkOffScreen && _playerPos == _targetPos) {
410
bool isRouteEmpty = rails.empty();
412
const WalkNode &node = rails.popNode();
414
_targetPos = node._walkPos;
416
} else if (!_walkOffScreenSceneId) {
417
// End of walking path
423
_walkOffScreen = _walkOffScreenSceneId;
424
_walkAnywhere = true;
425
_walkOffScreenSceneId = 0;
426
_stepEnabled = false;
435
if (newFacing && _moving)
438
if (_turnToFacing != _facing)
443
int velocity = _velocity;
444
if (_scalingVelocity && (_totalDistance > 0)) {
445
int angleRange = 100 - _currentScale;
446
int angleScale = angleRange * (_posDiff.x - 1) / _totalDistance + _currentScale;
447
velocity = MAX(1L, (angleScale * _currentScale * velocity) / 10000L);
450
if (!_moving || (_facing != _turnToFacing))
453
Common::Point newPos = _playerPos;
457
if (_distAccum < velocity) {
459
if (_pixelAccum < _posDiff.x)
460
_pixelAccum += _posDiff.y;
461
if (_pixelAccum >= _posDiff.x) {
462
if ((_posChange.y > 0) || _walkOffScreen)
463
newPos.y += _yDirection;
465
_pixelAccum -= _posDiff.x;
468
if (_pixelAccum < _posDiff.x) {
469
if ((_posChange.x > 0) || _walkOffScreen)
470
newPos.x += _xDirection;
474
if (!_walkAnywhere && !_walkOffScreen && (_walkOffScreenSceneId == 0)) {
475
newFacing = scene._depthSurface.getDepthHighBit(newPos);
478
_special = scene.getDepthHighBits(newPos);
481
_distAccum += _deltaDistance;
483
} while ((_distAccum < velocity) && !newFacing && ((_posChange.x > 0) || (_posChange.y > 0) || (_walkOffScreen != 0)));
486
_distAccum -= velocity;
491
if (!_walkOffScreen) {
492
// If the move is complete, make sure the position is exactly on the given destination
493
if (_posChange.x == 0)
494
newPos.x = _targetPos.x;
495
if (_posChange.y == 0)
496
newPos.y = _targetPos.y;
503
void Player::idle() {
504
Scene &scene = _vm->_game->_scene;
506
if (_facing != _turnToFacing) {
507
// The direction has changed, so reset for new direction
512
if ((_spritesStart + _spritesIdx) < 0 || !_spriteSetsPresent[_spritesStart + _spritesIdx])
515
SpriteAsset &spriteSet = *scene._sprites[_spritesStart + _spritesIdx];
516
assert(spriteSet._charInfo);
517
if (spriteSet._charInfo->_numEntries == 0)
518
// No entries, so exit immediately
521
int frameIndex = ABS(_frameListIndex);
522
int direction = (_frameListIndex < 0) ? -1 : 1;
524
if (frameIndex >= spriteSet._charInfo->_numEntries) {
525
// Reset back to the start of the list
528
_frameNumber += direction;
529
_forceRefresh = true;
531
if (spriteSet._charInfo->_stopFrames[frameIndex] < _frameNumber) {
532
_trigger = _upcomingTrigger;
535
if (spriteSet._charInfo->_startFrames[frameIndex] < _frameNumber) {
536
_trigger = _upcomingTrigger;
542
void Player::setFrame() {
544
if (++_frameNumber > _frameCount)
546
_forceRefresh = true;
553
int Player::getSpriteSlot() {
554
SpriteSlots &spriteSlots = _vm->_game->_scene._spriteSlots;
556
for (uint idx = 0; idx < spriteSlots.size(); ++idx) {
557
if (spriteSlots[idx]._seqIndex == PLAYER_SEQ_INDEX &&
558
spriteSlots[idx]._flags >= IMG_STATIC)
565
int Player::getScale(int yp) {
566
Scene &scene = _vm->_game->_scene;
568
int scale = (scene._bandsRange == 0) ? scene._sceneInfo->_maxScale :
569
(yp - scene._sceneInfo->_yBandsStart) * scene._scaleRange / scene._bandsRange +
570
scene._sceneInfo->_minScale;
572
return MIN(scale, 100);
575
void Player::setBaseFrameRate() {
576
Scene &scene = _vm->_game->_scene;
578
SpriteAsset &spriteSet = *scene._sprites[_spritesStart + _spritesIdx];
579
assert(spriteSet._charInfo);
581
_ticksAmount = spriteSet._charInfo->_ticksAmount;
582
if (_ticksAmount == 0)
586
void Player::startMovement() {
587
int xDiff = _targetPos.x - _playerPos.x;
588
int yDiff = _targetPos.y - _playerPos.y;
589
int srcScale = getScale(_playerPos.y);
590
int destScale = getScale(_targetPos.y);
592
// Sets the X direction
600
// Sets the Y direction
610
int scaleDiff = ABS(srcScale - destScale);
612
int xAmt100 = xDiff * 100;
613
int yAmt100 = yDiff * 100;
614
int xAmt33 = xDiff * 33;
616
int scaleAmount = (_scalingVelocity ? scaleDiff * 3 : 0) + 100 * yDiff / 100;
617
int scaleAmount100 = scaleAmount * 100;
619
// Figure out direction that will need to be moved in
623
} else if (yDiff == 0) {
626
if ((scaleAmount < xDiff) && ((xAmt33 / scaleAmount) >= 141))
628
else if (yDiff <= xDiff)
630
else if ((scaleAmount100 / xDiff) >= 141)
638
_turnToFacing = (_yDirection <= 0) ? FACING_NORTH : FACING_SOUTH;
641
_turnToFacing = (Facing)(((_yDirection <= 0) ? 9 : 3) - ((_xDirection <= 0) ? 2 : 0));
645
_turnToFacing = (_xDirection <= 0) ? FACING_WEST : FACING_EAST;
651
_totalDistance = (int)sqrt((double)(xAmt100 * xAmt100 + yAmt100 * yAmt100));
652
_posDiff.x = xDiff + 1;
653
_posDiff.y = yDiff + 1;
654
_posChange.x = xDiff;
655
_posChange.y = yDiff;
657
int majorChange = MAX(xDiff, yDiff);
658
_deltaDistance = (majorChange == 0) ? 0 : _totalDistance / majorChange;
660
if (_playerPos.x > _targetPos.x)
661
_pixelAccum = MAX(_posChange.x, _posChange.y);
665
_totalDistance /= 100;
666
_distAccum = -_deltaDistance;
669
void Player::newWalk() {
670
if (_needToWalk && _readyToWalk) {
671
startWalking(_prepareWalkPos, _prepareWalkFacing);
676
void Player::addWalker(int walker, int trigger) {
677
Scene &scene = _vm->_game->_scene;
678
SpriteAsset &spriteSet = *scene._sprites[_spritesStart + _spritesIdx];
679
assert(spriteSet._charInfo);
681
if (walker < spriteSet._charInfo->_numEntries && _stopWalkerIndex < 11) {
683
_stopWalkerList[_stopWalkerIndex] = walker;
684
_stopWalkerTrigger[_stopWalkerIndex] = trigger;
690
* Releases any sprites used by the player
692
void Player::releasePlayerSprites() {
693
Scene &scene = _vm->_game->_scene;
695
if (_spritesLoaded && _numSprites > 0) {
696
int spriteEnd = _spritesStart + _numSprites - 1;
698
scene._sprites.remove(spriteEnd);
699
} while (--spriteEnd >= _spritesStart);
703
_spritesLoaded = false;
704
_spritesChanged = true;
706
if (scene._sprites._assetCount > 0) {
707
warning("Player::releasePlayerSprites(): leftover sprites remain, clearing list");
708
scene._sprites.clear();
712
void Player::synchronize(Common::Serializer &s) {
713
s.syncAsByte(_moving);
714
s.syncAsSint16LE(_playerPos.x);
715
s.syncAsSint16LE(_playerPos.y);
716
s.syncAsSint16LE(_targetPos.x);
717
s.syncAsSint16LE(_targetPos.y);
718
s.syncAsSint16LE(_xDirection);
719
s.syncAsSint16LE(_yDirection);
720
s.syncAsSint16LE(_posDiff.x);
721
s.syncAsSint16LE(_posDiff.y);
722
s.syncAsSint16LE(_posChange.x);
723
s.syncAsSint16LE(_posChange.y);
724
s.syncAsUint16LE(_targetFacing);
725
s.syncAsSint16LE(_special);
726
s.syncAsByte(_forceRefresh);
727
s.syncAsSint16LE(_ticksAmount);
728
s.syncAsByte(_walkAnywhere);
729
s.syncAsUint16LE(_walkOffScreenSceneId);
730
s.syncAsByte(_walkOffScreen);
731
s.syncAsByte(_needToWalk);
732
s.syncAsByte(_readyToWalk);
733
s.syncAsUint16LE(_prepareWalkFacing);
734
s.syncAsSint16LE(_prepareWalkPos.x);
735
s.syncAsSint16LE(_prepareWalkPos.y);
736
s.syncAsByte(_stepEnabled);
737
s.syncAsByte(_visible);
738
s.syncAsByte(_priorVisible);
740
for (int i = 0; i < 8; ++i)
741
s.syncAsByte(_spriteSetsPresent[i]);
743
s.syncAsByte(_facing);
744
s.syncAsByte(_turnToFacing);
745
s.syncAsSint16LE(_spritesIdx);
746
s.syncAsSint16LE(_frameNumber);
747
s.syncAsSint16LE(_currentDepth);
748
s.syncAsSint16LE(_currentScale);
749
s.syncAsSint16LE(_frameListIndex);
751
for (int i = 0; i < 12; ++i) {
752
s.syncAsSint16LE(_stopWalkerList[i]);
753
s.syncAsSint16LE(_stopWalkerTrigger[i]);
756
s.syncAsSint16LE(_stopWalkerIndex);
757
s.syncAsSint16LE(_upcomingTrigger);
758
s.syncAsSint16LE(_trigger);
759
s.syncAsSint16LE(_scalingVelocity);
760
s.syncAsSint16LE(_pixelAccum);
761
s.syncAsSint16LE(_distAccum);
762
s.syncAsSint16LE(_deltaDistance);
763
s.syncAsSint16LE(_totalDistance);
764
s.syncAsSint16LE(_velocity);
765
s.syncAsUint16LE(_frameCount);
766
s.syncString(_spritesPrefix);
767
s.syncAsUint32LE(_priorTimer);
768
s.syncAsByte(_loadsFirst);
769
s.syncAsByte(_loadedFirst);
770
s.syncAsByte(_spritesLoaded);
771
s.syncAsByte(_spritesChanged);
772
s.syncAsByte(_beenVisible);
773
s.syncAsSint16LE(_centerOfGravity);
774
s.syncAsByte(_mirror);
777
void Player::removePlayerSprites() {
778
Scene &scene = _vm->_game->_scene;
779
int heroSpriteId = _spritesStart;
780
for (int i = 0; i < 8; i++) {
781
if (_spriteSetsPresent[i]) {
782
scene._sprites.remove(heroSpriteId++);
783
_spriteSetsPresent[i] = false;
787
if (scene._activeAnimation != nullptr)
788
scene._activeAnimation->resetSpriteSetsCount();
790
scene._spriteSlots.fullRefresh();
794
} // End of namespace MADS