/* 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 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. * */ #include "bladerunner/actor_walk.h" #include "bladerunner/bladerunner.h" #include "bladerunner/actor.h" #include "bladerunner/game_constants.h" #include "bladerunner/game_info.h" #include "bladerunner/obstacles.h" #include "bladerunner/savefile.h" #include "bladerunner/scene.h" #include "bladerunner/scene_objects.h" #include "bladerunner/set.h" namespace BladeRunner { ActorWalk::ActorWalk(BladeRunnerEngine *vm) { _vm = vm; reset(); } ActorWalk::~ActorWalk() {} // added method for bug fix (bad new game state for player actor) and better management of object void ActorWalk::reset() { _walking = false; _running = false; _facing = -1; _status = 0; _destination = Vector3(0.0f, 0.0f, 0.0f); _originalDestination = Vector3(0.0f, 0.0f, 0.0f); _current = Vector3(0.0f, 0.0f, 0.0f); _next = Vector3(0.0f, 0.0f, 0.0f); _nearActors.clear(); } bool ActorWalk::setup(int actorId, bool runFlag, const Vector3 &from, const Vector3 &to, bool mustReach, bool *arrived) { Vector3 next; *arrived = false; int r = nextOnPath(actorId, from, to, next); if (r == 0) { if (actorId != 0) { _current = from; _destination = to; stop(actorId, false, kAnimationModeCombatIdle, kAnimationModeIdle); } else { stop(actorId, true, kAnimationModeCombatIdle, kAnimationModeIdle); } // debug("actor id: %d, arrived: %d - false setup 01", actorId, (*arrived)? 1:0); return false; } if (r == -1) { stop(actorId, true, kAnimationModeCombatIdle, kAnimationModeIdle); *arrived = true; // debug("actor id: %d, arrived: %d - false setup 02", actorId, (*arrived)? 1:0); return false; } _nearActors.clear(); _vm->_sceneObjects->setMoving(actorId + kSceneObjectOffsetActors, true); _vm->_actors[actorId]->setMoving(true); if (_running) { runFlag = true; } int animationMode; if (_vm->_actors[actorId]->inCombat()) { animationMode = runFlag ? kAnimationModeCombatRun : kAnimationModeCombatWalk; } else { animationMode = runFlag ? kAnimationModeRun : kAnimationModeWalk; } _vm->_actors[actorId]->changeAnimationMode(animationMode); _destination = to; _originalDestination = to; _current = from; _next = next; if (next.x == _current.x && next.z == _current.z) { stop(actorId, true, kAnimationModeCombatIdle, kAnimationModeIdle); *arrived = true; // debug("actor id: %d, arrived: %d - false setup 03", actorId, (*arrived)? 1:0); return false; } _facing = angle_1024(_current, next); _walking = true; _running = runFlag; _status = 2; // debug("actor id: %d, arrived: %d - true setup 01", actorId, (*arrived)? 1:0); return true; } bool ActorWalk::tick(int actorId, float stepDistance, bool mustReachWalkDestination) { bool walkboxFound; if (_status == 5) { if (mustReachWalkDestination) { stop(actorId, true, kAnimationModeCombatIdle, kAnimationModeIdle); return true; } if (actorId != 0 && _vm->_rnd.getRandomNumberRng(1, 15) != 1) { // why random? return false; } _status = 3; } bool nearActorExists = addNearActors(actorId); if (_nearActors.size() > 0) { nearActorExists = true; if (_vm->_sceneObjects->existsOnXZ(actorId + kSceneObjectOffsetActors, _destination.x, _destination.z, true, true)) { if (actorId > 0) { if (_vm->_actors[actorId]->mustReachWalkDestination()) { stop(actorId, true, kAnimationModeCombatIdle, kAnimationModeIdle); _nearActors.clear(); return true; } else { Vector3 newDestination; findEmptyPositionAroundToOriginalDestination(actorId, newDestination); _destination = newDestination; return false; } } else { if (_vm->_playerActor->mustReachWalkDestination()) { _destination = _current; } stop(0, true, kAnimationModeCombatIdle, kAnimationModeIdle); _nearActors.clear(); return true; } } } _status = 3; if (stepDistance > distance(_current, _destination)) { stop(actorId, true, kAnimationModeCombatIdle, kAnimationModeIdle); _current = _destination; _current.y = _vm->_scene->_set->getAltitudeAtXZ(_current.x, _current.z, &walkboxFound); return true; } float distanceToNext = distance(_current, _next); if (1.0f < distanceToNext) { _facing = angle_1024(_current, _next); } bool nextIsCloseEnough = stepDistance > distanceToNext; if (nextIsCloseEnough || nearActorExists || _status == 3) { if (nextIsCloseEnough) { _current = _next; } _status = 1; Vector3 next; obstaclesAddNearActors(actorId); int r = nextOnPath(actorId, _current, _destination, next); obstaclesRestore(); if (r == 0) { stop(actorId, actorId == kActorMcCoy, kAnimationModeCombatIdle, kAnimationModeIdle); return false; } if (r != -1) { _next = next; _facing = angle_1024(_current, _next); _status = 2; int animationMode; if (_vm->_actors[actorId]->inCombat()) { animationMode = _running ? kAnimationModeCombatRun : kAnimationModeCombatWalk; } else { animationMode = _running ? kAnimationModeRun : kAnimationModeWalk; } _vm->_actors[actorId]->changeAnimationMode(animationMode); if (nextIsCloseEnough) { return false; } } else { stop(actorId, true, kAnimationModeCombatIdle, kAnimationModeIdle); // too close return true; } } #if !BLADERUNNER_ORIGINAL_BUGS // safety-guard / validator check if (_facing >= 1024) { _facing = (_facing % 1024); } else if (_facing < 0) { _facing = (-1) * _facing; _facing = (_facing % 1024); if (_facing > 0) { _facing = 1024 - _facing; // this will always be in [1, 1023] } } #endif _current.x += stepDistance * _vm->_sinTable1024->at(_facing); _current.z -= stepDistance * _vm->_cosTable1024->at(_facing); _current.y = _vm->_scene->_set->getAltitudeAtXZ(_current.x, _current.z, &walkboxFound); return false; } void ActorWalk::getCurrentPosition(int actorId, Vector3 *pos, int *facing) const { *pos = _current; *facing = _facing; } void ActorWalk::stop(int actorId, bool immediately, int combatAnimationMode, int animationMode) { _vm->_sceneObjects->setMoving(actorId + kSceneObjectOffsetActors, false); _vm->_actors[actorId]->setMoving(false); if (_vm->_actors[actorId]->inCombat()) { _vm->_actors[actorId]->changeAnimationMode(combatAnimationMode, false); } else { _vm->_actors[actorId]->changeAnimationMode(animationMode, false); } if (immediately) { _walking = false; _running = false; _status = 0; } else { _walking = true; _running = false; _status = 5; } } void ActorWalk::run(int actorId) { _running = true; int animationMode = kAnimationModeRun; if (_vm->_actors[actorId]->inCombat()) { animationMode = kAnimationModeCombatRun; } _vm->_actors[actorId]->changeAnimationMode(animationMode, false); } void ActorWalk::save(SaveFileWriteStream &f) { f.writeInt(_walking); f.writeInt(_running); f.writeVector3(_destination); // _originalDestination is not saved f.writeVector3(_current); f.writeVector3(_next); f.writeInt(_facing); assert(_nearActors.size() <= 20); for (Common::HashMap::const_iterator it = _nearActors.begin(); it != _nearActors.end(); ++it) { f.writeInt(it->_key); f.writeBool(it->_value); } f.padBytes(8 * (20 - _nearActors.size())); f.writeInt(_nearActors.size()); f.writeInt(0); // _notUsed f.writeInt(_status); } void ActorWalk::load(SaveFileReadStream &f) { _walking = f.readInt(); _running = f.readInt(); _destination = f.readVector3(); // _originalDestination is not saved _current = f.readVector3(); _next = f.readVector3(); _facing = f.readInt(); int actorId[20]; bool isNear[20]; for (int i = 0; i < 20; ++i) { actorId[i] = f.readInt(); isNear[i] = f.readBool(); } int count = f.readInt(); for (int i = 0; i < count; ++i) { _nearActors.setVal(actorId[i], isNear[i]); } f.skip(4); // _notUsed _status = f.readInt(); } bool ActorWalk::isXYZOccupied(float x, float y, float z, int actorId) const { if (_vm->_scene->_set->findWalkbox(x, z) == -1) { return true; } if (_vm->_actors[actorId]->isImmuneToObstacles()) { return false; } return _vm->_sceneObjects->existsOnXZ(actorId + kSceneObjectOffsetActors, x, z, false, false); } bool ActorWalk::findEmptyPositionAround(int actorId, const Vector3 &destination, int dist, Vector3 &out) const { bool inWalkbox; int facingToMinDistance = -1; float minDistance = -1.0f; float x = 0.0f; float z = 0.0f; out.x = 0.0f; out.y = 0.0f; out.z = 0.0f; for (int facing = 0; facing < 1024; facing += 128) { x = destination.x + _vm->_sinTable1024->at(facing) * dist; z = destination.z - _vm->_cosTable1024->at(facing) * dist; float distanceBetweenActorAndDestination = distance(x, z, _vm->_actors[actorId]->getX(), _vm->_actors[actorId]->getZ()); if (minDistance == -1.0f || minDistance > distanceBetweenActorAndDestination) { minDistance = distanceBetweenActorAndDestination; facingToMinDistance = facing; } } int facingLeft = facingToMinDistance; int facingRight = facingToMinDistance; int facing = -1024; while (facing < 0) { x = destination.x + _vm->_sinTable1024->at(facingRight) * dist; z = destination.z - _vm->_cosTable1024->at(facingRight) * dist; if (!_vm->_sceneObjects->existsOnXZ(actorId + kSceneObjectOffsetActors, x, z, true, true) && _vm->_scene->_set->findWalkbox(x, z) >= 0) { break; } x = destination.x + _vm->_sinTable1024->at(facingLeft) * dist; z = destination.z - _vm->_cosTable1024->at(facingLeft) * dist; if (!_vm->_sceneObjects->existsOnXZ(actorId + kSceneObjectOffsetActors, x, z, true, true) && _vm->_scene->_set->findWalkbox(x, z) >= 0) { break; } facingRight -= 64; if (facingRight < 0) { facingRight += 1024; } facingLeft += 64; if (facingLeft >= 1024) { facingLeft -= 1024; } facing += 64; } float y = _vm->_scene->_set->getAltitudeAtXZ(x, z, &inWalkbox); if (inWalkbox) { out.x = x; out.y = y; out.z = z; return true; } return false; } bool ActorWalk::findEmptyPositionAroundToOriginalDestination(int actorId, Vector3 &out) const { return findEmptyPositionAround(actorId, _originalDestination, 30, out); } bool ActorWalk::addNearActors(int skipActorId) { bool added = false; int setId = _vm->_scene->getSetId(); for (int i = 0; i < (int)_vm->_gameInfo->getActorCount(); i++) { assert(_vm->_actors[i] != nullptr); if (_vm->_actors[skipActorId] != nullptr && _vm->_actors[i]->getSetId() == setId && i != skipActorId ) { if (_nearActors.contains(i)) { _nearActors.setVal(i, false); } else if (_vm->_actors[skipActorId]->distanceFromActor(i) <= 48.0f) { _nearActors.setVal(i, true); added = true; } } } return added; } void ActorWalk::obstaclesAddNearActors(int actorId) const { Vector3 position = _vm->_actors[actorId]->getPosition(); for (Common::HashMap::const_iterator it = _nearActors.begin(); it != _nearActors.end(); ++it) { Actor *otherActor = _vm->_actors[it->_key]; assert(otherActor != nullptr); if ( otherActor->isRetired()) { continue; } Vector3 otherPosition = otherActor->getPosition(); float x0 = otherPosition.x - 12.0f; float z0 = otherPosition.z - 12.0f; float x1 = otherPosition.x + 12.0f; float z1 = otherPosition.z + 12.0f; if (position.x < (x0 - 12.0f) || position.z < (z0 - 12.0f) || position.x > (x1 + 12.0f) || position.z > (z1 + 12.0f)) { _vm->_obstacles->add(x0, z0, x1, z1); } } } void ActorWalk::obstaclesRestore() const { _vm->_obstacles->restore(); } int ActorWalk::nextOnPath(int actorId, const Vector3 &from, const Vector3 &to, Vector3 &next) const { next = from; if (distance(from, to) < 6.0) { // debug("Id: %d Distance: %f::Result -1", actorId, distance(from, to)); return -1; } if (_vm->_actors[actorId]->isImmuneToObstacles()) { next = to; return 1; } if (_vm->_scene->_set->findWalkbox(to.x, to.z) == -1) { // debug("Id: %d No walkbox::Result 0", actorId); return 0; } if (_vm->_sceneObjects->existsOnXZ(actorId + kSceneObjectOffsetActors, to.x, to.z, false, false)) { // debug("Actor Id: %d existsOnXZ::Result 0", actorId); return 0; } Vector3 next1; if (_vm->_obstacles->findNextWaypoint(from, to, &next1)) { next = next1; return 1; } // debug("Id: %d DEFAULTED::Result 0", actorId); return 0; } } // End of namespace BladeRunner