scummvm/engines/darkseed/player.cpp

469 lines
14 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 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 "math/utils.h"
#include "darkseed/player.h"
#include "darkseed/darkseed.h"
namespace Darkseed {
Player::Player() {
_cPlayerSprites.load("cplayer.nsp");
_gPlayerSprites.load("gplayer.nsp");
_connectorList.resize(Room::MAX_CONNECTORS);
}
const Sprite &Player::getSprite(int frameNo) {
if (g_engine->_room->isGiger()) {
return _gPlayerSprites.getSpriteAt(frameNo);
} else {
return _cPlayerSprites.getSpriteAt(frameNo);
}
}
bool Player::loadAnimations(const Common::Path &filename) {
return _animations.load(filename);
}
uint8 playerSpriteIndexDirectionTbl[] = { 24, 26, 28, 26 };
static constexpr bool DIRECTION_FLIP_SPRITE_TBL[] = { false, false, false, true };
uint16 walkFrameOffsetTbl[] = { 0, 8, 16, 8 };
void Player::updateSprite() {
if (!_playerIsChangingDirection) {
if ((_direction == 3) || (_direction == 1)) {
_flipSprite = DIRECTION_FLIP_SPRITE_TBL[_direction];
}
if (_position.x == _walkTarget.x && _position.y == _walkTarget.y && !_heroMoving) {
_frameIdx = playerSpriteIndexDirectionTbl[_direction];
} else {
_frameIdx = _playerWalkFrameIdx + walkFrameOffsetTbl[_direction];
}
if (_direction == 2) {
if (_position.x < _walkTarget.x) {
_flipSprite = true;
} else if (_walkTarget.x < _position.x) {
_flipSprite = false;
}
}
if (_direction == 0) {
if (_walkTarget.x < _position.x) {
_flipSprite = true;
} else if (_position.x < _walkTarget.x) {
_flipSprite = false;
}
}
} else {
_flipSprite = (4 < _playerSpriteWalkIndex_maybe);
if (_flipSprite) {
_frameIdx = 0x20 - _playerSpriteWalkIndex_maybe;
} else {
_frameIdx = _playerSpriteWalkIndex_maybe + 0x18;
}
}
}
bool Player::isAtPosition(int x, int y) const {
return _position.x == x && _position.y == y;
}
bool Player::isAtWalkTarget() const {
return _position == _walkTarget;
}
void Player::changeDirection(int16 oldDir, int16 newDir) {
if (oldDir != newDir) {
_playerIsChangingDirection = true;
_playerSpriteWalkIndex_maybe = (int16)(oldDir * 2);
_playerNewFacingDirection_maybe = (int16)(newDir * 2);
_playerWalkFrameDeltaOffset = 1;
if (oldDir < 4) {
switch (oldDir) {
case 0:
if (newDir == 3) {
_playerWalkFrameDeltaOffset = -1;
}
break;
case 1:
if (newDir == 0) {
_playerWalkFrameDeltaOffset = -1;
}
break;
case 2:
if (newDir == 1) {
_playerWalkFrameDeltaOffset = -1;
}
break;
case 3:
if (newDir == 2) {
_playerWalkFrameDeltaOffset = -1;
}
break;
}
}
}
}
void Player::playerFaceWalkTarget() {
int previousDirection;
int xDelta;
int yDelta;
previousDirection = _direction;
if (_position.x < _walkTarget.x) {
xDelta = _walkTarget.x - _position.x;
} else {
xDelta = _position.x - _walkTarget.x;
}
if (_position.y < _walkTarget.y) {
yDelta = _walkTarget.y - _position.y;
} else {
yDelta = _position.y - _walkTarget.y;
}
if (yDelta * 2 <= xDelta) {
if (_position.x < _walkTarget.x) {
_direction = 1;
} else if (_walkTarget.x < _position.x) {
_direction = 3;
}
} else if (_position.y < _walkTarget.y) {
_direction = 2;
} else {
_direction = 0;
}
changeDirection(previousDirection, _direction);
updateSprite();
_positionLong = _position;
}
void Player::calculateWalkTarget() {
_heroMoving = true;
_playerWalkFrameIdx = 0;
_walkPathIndex = -1;
_numConnectorsInWalkPath = 0;
int selectedObjNum = 0;
if (g_engine->_actionMode == kPointerAction) {
selectedObjNum = g_engine->_room->getRoomExitAtCursor();
}
if (selectedObjNum == 0) {
_walkTarget.x = g_engine->_cursor.getX();
_walkTarget.y = g_engine->_cursor.getY();
} else {
int currentRoomNumber = g_engine->_room->_roomNumber;
if (currentRoomNumber == 34 || (currentRoomNumber > 18 && currentRoomNumber < 24)) {
g_engine->_previousRoomNumber = currentRoomNumber;
if (currentRoomNumber == 34) {
g_engine->changeToRoom(33);
} else {
g_engine->changeToRoom(28);
}
return;
}
g_engine->_room->getWalkTargetForObjectType_maybe(selectedObjNum);
}
if (_walkTarget.y > 237) {
_walkTarget.y = 238;
}
if (!g_engine->_room->canWalkAtLocation(_walkTarget.x, _walkTarget.y)) {
int ty = _walkTarget.y;
for (; !g_engine->_room->canWalkAtLocation(_walkTarget.x, ty) && ty <= 233;) {
ty += 4;
}
if (ty < 235) {
_walkTarget.y = ty;
}
}
if (g_engine->_room->canWalkInLineToTarget(_position.x, _position.y, _walkTarget.x, _walkTarget.y)) {
return;
}
if (!g_engine->_room->canWalkAtLocation(_walkTarget.x, _walkTarget.y)) {
Common::Point connector = getClosestUnusedConnector(_walkTarget.x, _walkTarget.y);
if (connector.x == -1 && connector.y == -1) {
return;
}
int connectorToTargetDist = Math::hypotenuse(connector.x - _walkTarget.x, connector.y - _walkTarget.y);
int playerToTargetDist = Math::hypotenuse(_position.x - _walkTarget.x, _position.y - _walkTarget.y);
if (connectorToTargetDist < playerToTargetDist) {
if (g_engine->_room->canWalkInLineToTarget(_position.x, _position.y, connector.x, connector.y)) {
_finalTarget = _walkTarget;
_walkTarget = connector;
} else {
Common::Point tmpDest = _walkTarget;
_walkTarget = connector;
if (_numConnectorsInWalkPath > 0 && _numConnectorsInWalkPath < Room::MAX_CONNECTORS - 1 && _connectorList[_numConnectorsInWalkPath - 1] != connector) {
_connectorList[_numConnectorsInWalkPath] = connector;
_numConnectorsInWalkPath++;
}
_finalTarget = tmpDest;
}
}
} else {
createConnectorPathToDest();
}
}
int Player::getWidth() {
return getSprite(_frameIdx)._width;
}
int Player::getHeight() {
return getSprite(_frameIdx)._height;
}
void Player::updatePlayerPositionAfterRoomChange() {
int currentRoomNumber = g_engine->_room->_roomNumber;
g_engine->_room->calculateScaledSpriteDimensions(getWidth(), getHeight(), _position.y);
if (currentRoomNumber == 0x29 && g_engine->_previousRoomNumber == 0x2c) {
_position = Common::Point(0x13d, 0xa9);
} else if (currentRoomNumber == 0x2c && g_engine->_previousRoomNumber == 0x29) {
_position = Common::Point(0x16e, 0xb8);
} else if (_direction == 0 || ((currentRoomNumber == 0x29 || currentRoomNumber == 0x2c) && _direction == 2)) {
_position.y = 0xec;
g_engine->_room->calculateScaledSpriteDimensions(getWidth(), getHeight(), _position.y);
while (!g_engine->_room->canWalkAtLocation(_position.x, _position.y + 3) && _position.y > 100) {
_position.y--;
}
} else if (_direction == 2) {
while (!g_engine->_room->canWalkAtLocation(_position.x, _position.y - 5) && _position.y < 0xee && currentRoomNumber != 0x29 && currentRoomNumber != 0x2c) {
_position.y++;
}
} else if (_direction == 3) {
if (currentRoomNumber == 0x20 || currentRoomNumber == 0x1a) {
g_engine->_scaledSpriteHeight = 5;
} else {
g_engine->_room->calculateScaledSpriteDimensions(getWidth(), getHeight(), _position.y + g_engine->_scaledSpriteHeight);
}
_position.y += g_engine->_scaledSpriteHeight;
if (_position.y > 0xee) {
_position.y = 0xee;
}
if (_position.x > 0x27b) {
_position.x = 0x27b;
}
int yUp = _position.y;
int yDown = _position.y;
while (!g_engine->_room->canWalkAtLocation(_position.x, yUp) && yUp < 0xee) {
yUp++;
}
while (!g_engine->_room->canWalkAtLocation(_position.x, yDown) && yDown > 0x28) {
yDown--;
}
if (yUp - _position.y < _position.y - yDown) {
_position.y = yUp;
} else {
_position.y = yDown;
}
} else {
g_engine->_room->calculateScaledSpriteDimensions(getWidth(), getHeight(), _position.y + g_engine->_scaledSpriteHeight);
_position.y += g_engine->_scaledSpriteHeight;
if (_position.y > 0xee) {
_position.y = 0xee;
}
int yUp = _position.y;
int yDown = _position.y;
while (!g_engine->_room->canWalkAtLocation(_position.x, yUp) && yUp < 0xee) {
yUp++;
}
while (!g_engine->_room->canWalkAtLocation(_position.x, yDown) && yDown > 0x28) {
yDown--;
}
if (yUp - _position.y < _position.y - yDown) {
_position.y = yUp;
} else {
_position.y = yDown;
}
}
}
void Player::createConnectorPathToDest() {
constexpr Common::Point noConnectorFound(-1, -1);
Common::Point origWalkTarget = _walkTarget;
Common::Point startPoint = _position;
if (g_engine->_room->_roomNumber != 5 || _position.x > 320) {
startPoint = _walkTarget;
_walkTarget = _position;
}
_numConnectorsInWalkPath = 0;
Common::Point connector;
if (!g_engine->_room->canWalkAtLocation(startPoint.x, startPoint.y)) {
connector = getClosestUnusedConnector(startPoint.x, startPoint.y);
} else {
connector = getClosestUnusedConnector(startPoint.x, startPoint.y, true);
}
if (connector == noConnectorFound) {
if (g_engine->_room->_roomNumber != 5 || _position.x > 320) {
_walkTarget = origWalkTarget;
}
return;
}
_walkPathIndex = 0;
_connectorList[_numConnectorsInWalkPath] = connector;
_numConnectorsInWalkPath++;
while (_numConnectorsInWalkPath < Room::MAX_CONNECTORS && connector != noConnectorFound) {
if (g_engine->_room->canWalkInLineToTarget(connector.x, connector.y, _walkTarget.x, _walkTarget.y)) {
break;
}
connector = getClosestUnusedConnector(connector.x, connector.y, true);
if (connector == _walkTarget) {
break;
}
if (connector != noConnectorFound) {
_connectorList[_numConnectorsInWalkPath] = connector;
_numConnectorsInWalkPath++;
}
}
if (g_engine->_room->_roomNumber != 5 || _position.x > 320) {
reverseConnectorList();
_walkTarget = origWalkTarget;
}
OptimisePath();
if (g_engine->_room->_roomNumber == 5 && _position.x < 321) {
_finalTarget = _walkTarget;
} else {
_finalTarget = origWalkTarget;
}
_walkTarget = _connectorList[0];
}
Common::Point Player::getClosestUnusedConnector(int16 x, int16 y, bool mustHaveCleanLine) {
Common::Point closestPoint = {-1, -1};
int closestDist = 5000;
for (auto &roomConnector : g_engine->_room->_connectors) {
bool containsPoint = false;
for (int i = 0; i < _numConnectorsInWalkPath; i++) {
if (_connectorList[i] == roomConnector) {
containsPoint = true;
}
}
if (!containsPoint) {
int dist = Math::hypotenuse((roomConnector.x - x), (roomConnector.y - y));
if (dist < closestDist) {
if (!mustHaveCleanLine || g_engine->_room->canWalkInLineToTarget(x, y, roomConnector.x, roomConnector.y)) {
closestPoint = roomConnector;
closestDist = dist;
}
}
}
}
return closestPoint;
}
void Player::walkToNextConnector() {
if (_walkPathIndex == -1) {
return;
}
if (_walkPathIndex + 1 < _numConnectorsInWalkPath) {
_walkPathIndex++;
_walkTarget = _connectorList[_walkPathIndex];
} else {
_walkTarget = _finalTarget;
_walkPathIndex = -1;
}
playerFaceWalkTarget();
}
void Player::draw() {
if (g_engine->_debugShowWalkPath) {
if (_walkPathIndex != -1) {
for (int i = _walkPathIndex; i < _numConnectorsInWalkPath; i++) {
if (i == _walkPathIndex) {
g_engine->_screen->drawLine(_position.x, _position.y, _connectorList[i].x, _connectorList[i].y, 2);
} else {
g_engine->_screen->drawLine(_connectorList[i].x, _connectorList[i].y, _connectorList[i - 1].x, _connectorList[i - 1].y, 2);
}
}
g_engine->_screen->drawLine(_connectorList[_numConnectorsInWalkPath - 1].x, _connectorList[_numConnectorsInWalkPath - 1].y, _finalTarget.x, _finalTarget.y, 2);
}
}
}
void Player::reverseConnectorList() {
for (int i = 0; i < _numConnectorsInWalkPath / 2; i++) {
SWAP(_connectorList[i], _connectorList[_numConnectorsInWalkPath - 1 - i]);
}
}
void Player::OptimisePath() {
if (g_engine->_room->_roomNumber != 7 && g_engine->_room->_roomNumber != 32) {
while (_numConnectorsInWalkPath > 1) {
if (g_engine->_room->canWalkInLineToTarget(_connectorList[_numConnectorsInWalkPath - 2].x, _connectorList[_numConnectorsInWalkPath - 2].y, _walkTarget.x, _walkTarget.y)) {
_numConnectorsInWalkPath--;
} else {
break;
}
}
}
}
static constexpr uint8 _closerroom[10] = {
0, 5, 0, 9,
0, 0, 5, 6,
7, 6
};
void Player::setplayertowardsbedroom() {
if (g_engine->_animation->_isPlayingAnimation_maybe) {
return;
}
Common::Point currentCursor = g_engine->_cursor.getPosition();
uint8 currentRoomNumber = g_engine->_room->_roomNumber;
if (currentRoomNumber == 0) {
Common::Point target = {223, 190};
g_engine->_cursor.setPosition(target);
} else {
uint8 local_a = 0;
if (currentRoomNumber < 10) {
local_a = _closerroom[currentRoomNumber];
} else if (currentRoomNumber == 13) {
local_a = 61;
} else if (currentRoomNumber == 61) {
local_a = 5;
} else if (currentRoomNumber == 62) {
local_a = 8;
}
if (currentRoomNumber == 6 && g_engine->_objectVar[137] == 2) {
local_a = 10;
}
Common::Point exitPosition = g_engine->_room->getExitPointForRoom(local_a);
g_engine->_cursor.setPosition(exitPosition);
uint16 exitObjNum = g_engine->_room->getRoomExitAtCursor();
g_engine->_room->getWalkTargetForObjectType_maybe(exitObjNum);
g_engine->_cursor.setPosition(_walkTarget);
}
calculateWalkTarget();
playerFaceWalkTarget();
g_engine->_cursor.setPosition(currentCursor);
}
} // End of namespace Darkseed