scummvm/engines/bladerunner/script/police_maze.cpp
antoniou79 42a0025aed BLADERUNNER: Support secondary subtitles
Main for debugging and fun stats in shooting range and VK test for now
2022-06-30 01:00:06 +03:00

754 lines
19 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 "bladerunner/bladerunner.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/items.h"
#include "bladerunner/mouse.h"
#include "bladerunner/savefile.h"
#include "bladerunner/scene.h"
#include "bladerunner/scene_objects.h"
#include "bladerunner/script/police_maze.h"
#include "bladerunner/script/scene_script.h"
#include "bladerunner/time.h"
#include "bladerunner/subtitles.h"
#include "bladerunner/debugger.h"
#include "bladerunner/settings.h"
// ----------------------
// Maze point system info
// ----------------------
// Maze score starts at zero (0) points at the first room and it can get a negative value later on.
// Exiting each room deducts from maze score the number of targets that were not activated for that room
// Each room has a max number of 20 targets that can be activated for it in total (kPoliceMazePS1xTargetCount).
// Entering a room always auto-activates a set of predefined targets: 4 for PS10, PS11, PS12 and 5 for PS12
//
// - Leaving a room from the forward exit (moving properly through the maze)
// will mark the old room as complete. So, returning to an old room
// (which McCoy had exited from the *forward* exit) won't affect the score.
//
// - Leaving a room from the *backwards* exit (moving backwards through the maze)
// will NOT mark the room that McCoy just left as complete, if it was not already.
// So returning to a previous room (which McCoy had exited from the *backwards* exit)
// may still affect the score and combat may resume. However, upon re-entering that room,
// it will again activate the predefined set of targets for it and those will count
// additively to the total activated targets for that room. So, the room can be resumed at most
// four (PS12) or five (PS10, PS11, PS13) times until it becomes completed (reaches its max activated targets).
//
// Running quickly through the maze (not stopping to shoot targets) amounts to a negative score of:
// 0 - (3 * (20 - 4)) - (20 - 5) = -63 points
//
// However, that is not the lowest score McCoy can get, since points are deducted when:
// a) shooting innocents
// b) shooting unrevealed enemies
// c) getting shot
//
// Combat Point System:
// + 1: gain a point when an enemy (revealed) is shot (at Item_Spin_In_World)
// - 1: lose a point when an innocent or unrevealed enemy is shot (at Item_Spin_In_World)
// + 1: gain a point when an innocent escapes (at kPMTILeave instruction)
// - 1: lose a point when an enemy shoots McCoy (at kPMTIShoot)
//
// For the maximum score, all 4 * 20 = 80 targets have to get activated and handled properly.
// Since McCoy always gains one (1) point per target (enemy or innocent) for that,
// the maximum score *should be*: 80 points.
//
// However, there are some *special* target types:
// 1. Targets that will count as multiple targets without increasing
// the active target count more than once.
// 2. Targets that won't increase the active target count at all,
// and act as bonus points themselves. (eg. kItemPS12Target10, kItemPS12Target14)
//
// With the account of special targets the maximum achievable score will be greater than 80 points.
// Since the special targets can appear randomly (by use of the kPMTIPausedReset1of2, kPMTIPausedReset1of3 instructions)
// the highest score is not a predefined fixed number and will differ per run. It will always be above 80 points.
//
namespace BladeRunner {
PoliceMaze::PoliceMaze(BladeRunnerEngine *vm) : ScriptBase(vm) {
_isPaused = false;
_isActive = false;
_isEnding = false;
_pm_var1 = 0;
_pm_var2 = 0;
for (int i = 0; i < kNumMazeTracks; ++i) {
_tracks[i] = new PoliceMazeTargetTrack(vm);
}
}
PoliceMaze::~PoliceMaze() {
for (int i = 0; i < kNumMazeTracks; ++i) {
delete _tracks[i];
_tracks[i] = nullptr;
}
}
void PoliceMaze::clear(bool isLoadingGame) {
for (int i = 0; i < kNumMazeTracks; ++i) {
if (_tracks[i]->isPresent()) {
_tracks[i]->clear(isLoadingGame);
}
}
}
void PoliceMaze::activate() {
_isActive = true;
_isEnding = false;
}
void PoliceMaze::setPauseState(bool state) {
warning("PAUSE: %d", state);
_isPaused = state;
uint32 timeNow = _vm->_time->current();
for (int i = 0; i < kNumMazeTracks; ++i) {
_tracks[i]->setTime(timeNow);
}
}
void PoliceMaze::tick() {
if (_isPaused) {
return;
}
if (_vm->_scene->getSetId() != kSetPS10_PS11_PS12_PS13) {
return;
}
if (_isEnding) {
_isActive = false;
return;
}
for (int i = 0; i < kNumMazeTracks; ++i) {
_tracks[i]->tick();
}
bool notFound = true;
for (int i = 0; i < kNumMazeTracks; ++i) {
if (!_tracks[i]->isPaused()) {
notFound = false;
break;
}
}
if (_vm->_debugger->_showMazeScore && _isActive && !_isEnding) {
_vm->_subtitles->setGameSubsText(BladeRunner::Subtitles::kSubtitlesSecondary, Common::String::format("Score: %02d", Global_Variable_Query(kVariablePoliceMazeScore)), true);
_vm->_subtitles->show(BladeRunner::Subtitles::kSubtitlesSecondary);
}
if (notFound && _isActive && !_isEnding) {
_isActive = false;
_isEnding = true;
if (_vm->_scene->getSceneId() == kScenePS13) {
Actor_Voice_Over(320, kActorAnsweringMachine);
} else {
Actor_Voice_Over(310, kActorAnsweringMachine);
}
}
}
void PoliceMaze::save(SaveFileWriteStream &f) {
f.writeBool(_isPaused);
f.writeBool(_isActive);
f.writeBool(_isEnding);
for (int i = 0; i < kNumMazeTracks; ++i) {
_tracks[i]->save(f);
}
}
void PoliceMaze::load(SaveFileReadStream &f) {
_isPaused = f.readBool();
_isActive = f.readBool();
_isEnding = f.readBool();
for (int i = 0; i < kNumMazeTracks; ++i) {
_tracks[i]->load(f);
}
}
PoliceMazeTargetTrack::PoliceMazeTargetTrack(BladeRunnerEngine *vm) : ScriptBase(vm) {
reset();
}
PoliceMazeTargetTrack::~PoliceMazeTargetTrack() {
reset();
}
void PoliceMazeTargetTrack::reset() {
_isPresent = false;
_itemId = -1;
_pointCount = 0;
_data = nullptr;
_dataIndex = 0;
_timeLeftUpdate = 0;
_timeLeftWait = 0;
_time = 0;
_isWaiting = false;
_isMoving = false;
_pointIndex = 0;
_pointTarget = 0;
_isRotating = false;
_angleTarget = 0;
_angleDelta = 0;
_isPaused = true;
}
void PoliceMazeTargetTrack::clear(bool isLoadingGame) {
reset();
}
void PoliceMazeTargetTrack::add(int trackId, float startX, float startY, float startZ, float endX, float endY, float endZ, int steps, const int *instructions, bool isActive) {
_data = (const int *)instructions;
if (!_vm->_gameIsLoading) {
_itemId = trackId;
_pointCount = steps;
_dataIndex = 0;
double coef = 1.0f / (long double)steps;
double coefX = (endX - startX) * coef;
double coefY = (endY - startY) * coef;
double coefZ = (endZ - startZ) * coef;
for (int i = 0; i < steps - 1; ++i) {
_points[i].x = i * coefX + startX;
_points[i].y = i * coefY + startY;
_points[i].z = i * coefZ + startZ;
}
_points[steps - 1].x = endX;
_points[steps - 1].y = endY;
_points[steps - 1].z = endZ;
_isPaused = !isActive;
}
_isPresent = true;
}
bool PoliceMazeTargetTrack::tick() {
if (!_isPresent) {
return false;
}
uint32 oldTime = _time;
_time = _vm->_time->current();
uint32 timeDiff = _time - oldTime; // unsigned difference is intentional
_timeLeftUpdate = _timeLeftUpdate - (int32)timeDiff; // should be ok
if (_timeLeftUpdate > 0) {
return false;
}
#if BLADERUNNER_ORIGINAL_BUGS
#else
// here _timeLeftUpdate is <= 0
if (_vm->_settings->getDifficulty() > kGameDifficultyEasy) {
timeDiff = abs(_timeLeftUpdate);
}
#endif // BLADERUNNER_ORIGINAL_BUGS
_timeLeftUpdate = 66; // update the target track 15 times per second
if (_isPaused) {
return false;
}
if (_isWaiting) {
#if BLADERUNNER_ORIGINAL_BUGS
_timeLeftWait -= timeDiff;
#else
if (_vm->_settings->getDifficulty() == kGameDifficultyEasy) {
_timeLeftWait -= timeDiff; // original behavior
} else {
_timeLeftWait = _timeLeftWait - (int32)(timeDiff + _timeLeftUpdate); // this deducts an amount >= 66 // should be ok
}
#endif // BLADERUNNER_ORIGINAL_BUGS
if (_timeLeftWait > 0) {
return true;
}
_isWaiting = false;
_timeLeftWait = 0;
}
if (_vm->_items->isSpinning(_itemId)) {
return true;
}
if (_isRotating) {
float angle = _vm->_items->getFacing(_itemId) + _angleDelta;
if (_angleDelta > 0) {
if (angle >= _angleTarget) {
angle = _angleTarget;
_isRotating = false;
}
} else if (_angleDelta < 0) {
if (angle <= _angleTarget) {
angle = _angleTarget;
_isRotating = false;
}
} else {
_isRotating = false;
}
_vm->_items->setFacing(_itemId, angle);
if (_isRotating) {
return true;
}
}
bool advancePoint = false;
if (_isMoving) {
if (_pointIndex < _pointTarget) {
++_pointIndex;
advancePoint = true;
} else if (_pointIndex > _pointTarget) {
--_pointIndex;
advancePoint = true;
} else {
_isMoving = 0;
}
}
if (advancePoint) {
_vm->_items->setXYZ(_itemId, _points[_pointIndex]);
readdObject(_itemId);
return true;
}
bool cont = true;
while (cont) {
++_dataIndex;
switch (_data[_dataIndex - 1]) {
case kPMTIActivate:
{
int variableId = _data[_dataIndex++];
int maxValue = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Activate, VariableId: %i, Max value: %i", _itemId, variableId, maxValue);
#endif
if (Global_Variable_Query(variableId) >= maxValue) {
setPaused();
cont = false;
} else {
cont = true;
}
break;
}
case kPMTILeave:
{
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Leave", _itemId);
#endif
if (!_vm->_items->isPoliceMazeEnemy(_itemId) && _vm->_items->isTarget(_itemId)) {
Police_Maze_Increment_Score(1);
}
break;
}
case kPMTIShoot:
{
int soundId = _data[_dataIndex++];
++_dataIndex; // second argument is not used
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Shoot, SoundId: %i", _itemId, soundId);
#endif
if (_vm->_items->isTarget(_itemId)) {
Sound_Play(soundId, 90, 0, 0, 50);
Police_Maze_Decrement_Score(1);
Actor_Force_Stop_Walking(kActorMcCoy);
if (Player_Query_Combat_Mode()) {
Actor_Change_Animation_Mode(kActorMcCoy, kAnimationModeCombatHit);
} else {
Actor_Change_Animation_Mode(kActorMcCoy, kAnimationModeHit);
}
int snd;
if (Random_Query(1, 2) == 1) {
snd = 9900;
} else {
snd = 9905;
}
Sound_Play_Speech_Line(kActorMcCoy, snd, 75, 0, 99);
_vm->_mouse->setMouseJitterDown();
}
cont = false;
break;
}
case kPMTIEnemyReset:
{
int itemId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Enemy reset, OtherItemId: %i", _itemId, itemId);
#endif
_vm->_items->setPoliceMazeEnemy(itemId, false);
break;
}
case kPMTIEnemySet:
{
int itemId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Enemy set, OtherItemId: %i", _itemId, itemId);
#endif
_vm->_items->setPoliceMazeEnemy(itemId, true);
break;
}
case kPMTIFlagReset:
{
int gameFlagId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Flag reset, FlagId: %i", _itemId, gameFlagId);
#endif
Game_Flag_Reset(gameFlagId);
break;
}
case kPMTIFlagSet:
{
int gameFlagId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Flag set, FlagId: %i", _itemId, gameFlagId);
#endif
Game_Flag_Set(gameFlagId);
break;
}
case kPMTIVariableDec:
{
int variableId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Variable decrement, VariableId: %i", _itemId, variableId);
#endif
Global_Variable_Decrement(variableId, 1);
break;
}
case kPMTIVariableInc:
{
int variableId = _data[_dataIndex++];
int maxValue = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Variable increment, VariableId: %i, Max value: %i", _itemId, variableId, maxValue);
#endif
if (Global_Variable_Query(variableId) < maxValue) {
Global_Variable_Increment(variableId, 1);
}
break;
}
case kPMTIVariableReset:
{
int variableId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Variable reset, VariableId: %i", _itemId, variableId);
#endif
Global_Variable_Reset(variableId);
break;
}
case kPMTIVariableSet:
{
int variableId = _data[_dataIndex++];
int value = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Variable set, VariableId: %i, Value: %i", _itemId, variableId, value);
#endif
Global_Variable_Set(variableId, value);
break;
}
case kPMTITargetSet:
{
int itemId = _data[_dataIndex++];
int value = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Target set, OtherItemId: %i, Value: %i", _itemId, itemId, value);
#endif
_vm->_items->setIsTarget(itemId, value);
break;
}
case kPMTIPausedReset1of3:
{
int trackId1 = _data[_dataIndex++];
int trackId2 = _data[_dataIndex++];
int trackId3 = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Pause reset 1 of 3, OtherItemId1: %i, OtherItemId2: %i, OtherItemId3: %i", _itemId, trackId1, trackId2, trackId3);
#endif
switch (Random_Query(1, 3)) {
case 1:
_vm->_policeMaze->_tracks[trackId1]->resetPaused();
break;
case 2:
_vm->_policeMaze->_tracks[trackId2]->resetPaused();
break;
case 3:
_vm->_policeMaze->_tracks[trackId3]->resetPaused();
break;
default:
break;
}
break;
}
case kPMTIPausedReset1of2:
{
int trackId1 = _data[_dataIndex++];
int trackId2 = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Pause reset 1 of 2, OtherItemId1: %i, OtherItemId2: %i", _itemId, trackId1, trackId2);
#endif
if (Random_Query(1, 2) == 1) {
_vm->_policeMaze->_tracks[trackId1]->resetPaused();
} else {
_vm->_policeMaze->_tracks[trackId2]->resetPaused();
}
break;
}
case kPMTIPausedSet:
{
int trackId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Pause set, OtherItemId: %i", _itemId, trackId);
#endif
_vm->_policeMaze->_tracks[trackId]->setPaused();
break;
}
case kPMTIPausedReset:
{
int trackId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Pause reset, OtherItemId: %i", _itemId, trackId);
#endif
_vm->_policeMaze->_tracks[trackId]->resetPaused();
break;
}
case kPMTIPlaySound:
{
int soundId = _data[_dataIndex++];
int volume = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Sound, SoundId: %i, Volume: %i", _itemId, soundId, volume);
#endif
Sound_Play(soundId, volume, 0, 0, 50);
break;
}
case kPMTIObstacleReset:
{
int itemId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Obstacle reset, OtherItemId: %i", _itemId, itemId);
#endif
_vm->_items->setIsObstacle(itemId, 0);
break;
}
case kPMTIObstacleSet:
{
int itemId = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Obstacle set, OtherItemId: %i", _itemId, itemId);
#endif
_vm->_items->setIsObstacle(itemId, 1);
break;
}
case kPMTIWaitRandom:
{
int randomMin = _data[_dataIndex++];
int randomMax = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Wait random, Min: %i, Max: %i", _itemId, randomMin, randomMax);
#endif
_timeLeftWait = Random_Query(randomMin, randomMax);
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Wait for = %i", _itemId, _timeLeftWait);
#endif
_isWaiting = true;
cont = false;
break;
}
case kPMTIRotate:
{
_angleTarget = _data[_dataIndex++];
_angleDelta = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Rotate, Target: %i, Delta: %i", _itemId, _angleTarget, _angleDelta);
#endif
_isRotating = true;
cont = false;
break;
}
case kPMTIFacing:
{
int angle = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Set facing, Angle: %i", _itemId, angle);
#endif
_vm->_items->setFacing(_itemId, angle);
break;
}
case kPMTIRestart:
{
_dataIndex = 0;
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Restart", _itemId);
#endif
cont = false;
break;
}
case kPMTIWait:
{
_timeLeftWait = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Wait, Time: %i", _itemId, _timeLeftWait);
#endif
_isWaiting = true;
cont = false;
break;
}
case kPMTIMove:
{
_pointTarget = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Move, Target: %i", _itemId, _pointTarget);
#endif
_isMoving = true;
cont = false;
break;
}
case kPMTIPosition:
{
_pointIndex = _data[_dataIndex++];
#if BLADERUNNER_DEBUG_CONSOLE
debug("ItemId: %3i, Position, Index: %i", _itemId, _pointIndex);
#endif
_isMoving = false;
_vm->_items->setXYZ(_itemId, _points[_pointIndex]);
readdObject(_itemId);
break;
}
default:
return false;
}
if (_isPaused || _isWaiting) {
cont = false;
}
}
return true;
}
void PoliceMazeTargetTrack::readdObject(int itemId) {
if (_vm->_sceneObjects->remove(itemId + kSceneObjectOffsetItems)) {
const BoundingBox &boundingBox = _vm->_items->getBoundingBox(itemId);
const Common::Rect &screenRect = _vm->_items->getScreenRectangle(itemId);
bool targetable = _vm->_items->isTarget(itemId);
bool obstacle = _vm->_items->isVisible(itemId);
_vm->_sceneObjects->addItem(itemId + kSceneObjectOffsetItems, boundingBox, screenRect, targetable, obstacle);
}
}
void PoliceMazeTargetTrack::save(SaveFileWriteStream &f) {
f.writeBool(_isPresent);
f.writeInt(_itemId);
f.writeInt(_pointCount);
f.writeInt(_dataIndex);
f.writeBool(_isWaiting);
f.writeBool(_isMoving);
f.writeInt(_pointIndex);
f.writeInt(_pointTarget);
f.writeBool(_isRotating);
f.writeInt(_angleTarget);
f.writeInt(_angleDelta);
f.writeBool(_isPaused);
for (int i = 0; i < kNumTrackPoints; ++i) {
f.writeVector3(_points[i]);
}
f.writeInt(_timeLeftUpdate);
f.writeInt(_timeLeftWait);
}
void PoliceMazeTargetTrack::load(SaveFileReadStream &f) {
_isPresent = f.readBool();
_itemId = f.readInt();
_pointCount = f.readInt();
_dataIndex = f.readInt();
_isWaiting = f.readBool();
_isMoving = f.readBool();
_pointIndex = f.readInt();
_pointTarget = f.readInt();
_isRotating = f.readBool();
_angleTarget = f.readInt();
_angleDelta = f.readInt();
_isPaused = f.readBool();
for (int i = 0; i < kNumTrackPoints; ++i) {
_points[i] = f.readVector3();
}
_timeLeftUpdate = f.readInt();
_timeLeftWait = f.readInt();
}
} // End of namespace BladeRunner